【程序】Marvell 88W8686 WiFi模塊(WM-G-MR-09)創建或連接熱點,並使用lwip2.0.3建立http服務器(20180312版)

該程序是舊版本!最新版本爲20180706版:

https://blog.csdn.net/ZLK1214/article/details/80941657

本程序所用的單片機型號爲:STM32F103RE

PB12端口爲外接的WiFi模塊電源開關,當PB12輸出低電平時接通電源。WiFi模塊的電源引腳VCC不可直接連接到電源上,必須要串聯一組PNP三極管(或場效應管),並把基極接到PB12端口上,基極要接限流電阻。

注意:WM-G-MR-09模塊的芯片組(Chip Set)就是Marvell 88W8686。

Keil5工程下載地址:https://pan.baidu.com/s/1Dw5skiXV5-OmDVqDFrewQA

代碼說明:http://blog.csdn.net/ZLK1214/article/details/79278871
Windows下根據計算機名獲取IP地址的C語言程序:http://blog.csdn.net/ZLK1214/article/details/79595245

此版本修復了上一個版本(20180129版)存在的許多問題,其中包括無法連接部分WPA/WPA2路由器的問題。添加了庫函數版本的代碼,還添加了SDIO錯誤處理代碼,取消了數據幀的重傳功能(因爲802.11數據鏈路層上已經有了該功能)。該版本仍有如下問題未解決:
(1) 連接僅支持設置WPA2密碼的路由器後(這類路由器通常僅支持WPA/WPA2混合模式,體現出來是掃描熱點後同時擁有WPA_IE和RSN_IE信息結構), 有時路由器會進行Group Key Handshake更新GTK密鑰(也就是組密鑰),但程序回覆的Message 2無效被路由器丟棄,最後會斷開連接。解決該bug的困難在於,該類路由器往往不能在管理頁面中修改組密鑰更新頻率,也無法通過發送EAPOL請求幀(key_info=REQUEST+MIC=0x902)請求GTK更新,並且更新頻率爲每天一次,給程序調試帶來很大麻煩。臨時解決辦法:在WiFi_EventHandler中調用associate_example函數重新連接路由器,但這樣會影響已連接的TCP和UDP客戶,並且IP地址也有可能改變。
筆者在電腦上用Microsoft Network Monitor 3.4抓取電腦發送的Message 2認證幀,按照他的格式修改了程序發送的Message 2,經檢驗MIC的值是正確的,其他字段一模一樣,但還是不能解決問題。說明發出去的幀內容上應該沒有問題。
有一個簡單的方法可以快速復現該bug。在WiFi_Associate_Callback函數中,把添加RSN參數的代碼去掉,只添加WPA的Vendor參數(目的是在連接WPA/WPA2混合模式的路由器時,選WPA認證方式):

cmd_size = (uint8_t *)(auth + 1) - wifi_buffer_command;
if (security == WIFI_SECURITYTYPE_WPA || security == WIFI_SECURITYTYPE_WPA2)
{
  // WPA網絡必須在命令中加入Vendor參數才能成功連接
  vendor = (MrvlIETypes_VendorParamSet_t *)(auth + 1);
  memcpy(vendor, &wifi_ssid_info.wpa, TLV_STRUCTLEN(wifi_ssid_info.wpa));
  cmd_size += TLV_STRUCTLEN(wifi_ssid_info.wpa);
}

然後在WiFi_EAPOLProcess函數中,也把添加RSN參數的代碼去掉(選擇WPA方式認證):

/* 在待發送的Message 2中添加Key Data信息 */
// https://community.arubanetworks.com/t5/Technology-Blog/A-closer-look-at-WiFi-Security-IE-Information-Elements/ba-p/198867
// 使用WPA的熱點一定有WPA IE信息項,一定沒有RSN IE信息項
// 使用WPA2的熱點一定有RSN IE信息項,可能有WPA IE信息項
packet_tx = (WiFi_EAPOLKeyFrame *)WiFi_GetPacketBuffer();
kde = (WiFi_KDE *)packet_tx->key_data;
if (wifi_ssid_info.wpa.header.type)
{
  // 路由器提供了WPA IE信息項時可直接複製
  //printf("WPA IE copied!\n");
  kde->type = WIFI_MRVLIETYPES_VENDORPARAMSET;
  kde->length = wifi_ssid_info.wpa.header.length;
  memcpy(kde->oui, wifi_ssid_info.wpa.vendor, wifi_ssid_info.wpa.header.length);
  key_data_len = sizeof(kde->type) + sizeof(kde->length) + kde->length;
}
else
{
  printf("IE not copied!\n");
  key_data_len = 0;
}
這樣,關聯時就會強制採用WPA方式認證,每次關聯時都可以產生Group key handshake,不用再等上一天了。

修改後就會發現,路由器產生了Group key handshake,程序也迴應了Message 2但路由器沒有接受,最後Deauthenticated!被強制斷開連接。
進行WPA認證時組密鑰握手失敗的原因是:WiFi_EAPOLProcess函數中,CMD_802_11_KEY_MATERIAL命令幀和EAPOL迴應數據幀不能同時發送。必須先發送其中一個,成功後在回調函數中發送另一個。
這個解決辦法只能修復WPA認證時最開始握手失敗的問題,仍不能解決WPA和WPA2認證成功過後一天內出現的組密鑰更新失敗的問題。

(2) 發送了很多數據幀後,有時會卡在WiFi_Wait(0x02): timeout!上,再也發不出任何數據幀了。臨時解決辦法:把WiFi.h中WIFI_DEFAULT_TIMEOUT_DATAACK的值改大,比如1000,10000等。
(3) 連接某些WPA的熱點,卡在Waiting for authentication!上無法繼續,接收不到Message 1。
(4) TCP和UDP的發送速率很慢,只有幾到幾十KB/s,並且UDP丟包率也很嚴重,大約達到了30%。臨時解決辦法:WiFi_SendPacket函數中將packet->tx_control的值改爲0x001c(優先使用54Mbps傳輸速率,默認的最大重傳次數2),這樣能使UDP發送速率穩定在300~700KB/s左右,但不能改善UDP丟包率和TCP發送速率。
(5) 連接某些手機熱點時,單片機復位後第一次掃描到的概率很大,但之後就很難再次掃描到了,有時長達好幾分鐘一直顯示SSID not found!
(6) 長時間連接路由器後沒有通信,電腦無法ping通WiFi開發板。


【勘誤】

2018年4月1日(重要):DHCP長時間獲取不到IP地址,是因爲sys_now函數的實現有問題。該函數沒有考慮到毫秒向秒進位時發生的同步問題,有時候後調用的時間值小於先調用的時間值,使sys_check_timeouts函數中的diff值爲負,破壞了next_timeout鏈表,導致sys_check_timeouts函數不能正常工作。下面是解決方法,把代碼複製到工程裏就可以修復此問題:

/* 獲取RTC分頻計數器的值 */
uint32_t rtc_divider(void)
{
  uint32_t div[2];
  do
  {
    div[0] = RTC_GetDivider();
    div[1] = RTC_GetDivider();
  } while ((div[0] >> 16) != (div[1] >> 16));
  
  // RTC_GetDivider函數先讀取DIVH再讀取DIVL
  // 如果更新發生在第一次讀取DIVH或DIVL後, 則div[0]和div[1]的高16位不相等, 兩個值都會被丟棄
  // 如果更新發生在第二次讀取DIVH後, 則div[0]和div[1]的高16位相等, 且div[0]值的高、低16位匹配,div[1]值的高、低16位不匹配
  return div[0]; // 所以應該取第一次讀取的值
}

/* RTC時間轉化爲毫秒數 (lwip協議棧要求實現的函數) */
// 該函數必須保證: 除非定時器溢出, 否則後獲取的時間必須大於先獲取的時間
uint32_t sys_now(void)
{
  uint32_t sec[2];
  uint32_t div, milli;
  do
  {
    time(&sec[0]); // 秒
    div = rtc_divider();
    time(&sec[1]);
  } while (sec[0] != sec[1]);
  
  // CNT是在DIV從P-1跳變到P-2瞬間發生更新的 (P=RTC_PRESCALER)
  if (div == RTC_PRESCALER - 1)
    milli = div;
  else
    milli = RTC_PRESCALER - div - 2;
  milli = milli * 1000 / RTC_PRESCALER; // 毫秒
  return sec[0] * 1000 + milli;
}

/* 獲取RTC秒數 */
time_t time(time_t *timer)
{
  // CNTH和CNTL是同時更新的
  // 但由於這兩個寄存器不是同時讀取的, 所以有可能一個讀到的是更新前的值, 另一個讀到的是更新後的值
  uint32_t now[2];
  do
  {
    now[0] = RTC_GetCounter();
    now[1] = RTC_GetCounter();
  } while ((now[0] >> 16) != (now[1] >> 16)); // 使用循環可以避免中斷的影響
  
  // RTC_GetCounter函數先讀取CNTL再讀取CNTH
  // 如果更新發生在第一次讀取CNTL後, 則now[0]和now[1]的高16位相等, 且now[0]值的高、低16位不匹配,now[1]值的高、低16位匹配
  // 如果更新發生在第一次讀取CNTH後或第二次讀取CNTL後, 則now[0]和now[1]的高16位不相等, 兩個值都會被丟棄
  if (timer)
    *timer = now[1];
  return now[1]; // 所以應該取第二次讀取的值
}

Wi-Fi模塊電源引腳的連接方法:


程序支持連接無密碼的熱點以及WEP、WPA-PSK和WPA2-PSK認證類型的熱點,加密方式支持TKIP和AES。
支持創建無密碼或是帶有WEP密碼的ADHOC熱點,ADHOC模式下不支持WPA和WPA2!

注意:雖然SDIO標準規定可以總線上可以接多張SD卡,但STM32單片機的SDIO接口只支持接一張卡,STM32F103芯片手冊Datasheet(不是參考手冊)中有聲明:
The current version supports only one SD/SDIO/MMC4.2 card at any one time and a stack of MMC4.1 or previous.
如果想要同時使用WiFi模塊和SD內存卡,建議SD內存卡採用SPI總線通信。

【程序運行截圖】

連上路由器後DHCP分配得到IP地址:(花的時間有時候長,有時候短。解決方法見2018年4月1日的勘誤)

下面是把WiFi模塊固件寫入單片機芯片Flash固定區域的程序(用於減少調試主程序時下載程序的時間)的運行結果:

電腦上ping IP地址和計算機名:

通過計算機名在電腦上訪問開發板上的HTTP服務器(lwip自帶的httpd):

【程序運行結果(掃描熱點並連接安卓手機開的WPA2熱點)】

STM32F103RE SDIO 88W8686
RESPCMD63, RESP1_90ff8000
RESPCMD63, RESP1_90300000
Number of I/O Functions: 1
Memory Present: 0
Relative Card Address: 0x0001
Card selected! RESP1_00001e00
SDIO Clock: 24MHz
[CIS] func=0, ptr=0x00008000
Product Information: Marvell 802.11 SDIO ID: 0B
Manufacturer Code: 0x02df
Manufacturer Information: 0x9103
Card Function Code: 0x0c
System Initialization Bit Mask: 0x00
Maximum Block Size: 256
Maximum Transfer Rate Code: 0x32
[CIS] func=1, ptr=0x00008080
Card Function Code: 0x0c
System Initialization Bit Mask: 0x00
Maximum Block Size: 256
Firmware is successfully downloaded!
MAC Addr: 00:1A:6B:A4:AA:B4
SSID 'CMCC-EDU', MAC 96:14:4B:6F:A5:DA, RSSI 73, Channel 1
  Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-Young', MAC 96:14:4B:6F:A5:DB, RSSI 69, Channel 1
  Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID '???????', MAC BC:46:99:9B:1E:E4, RSSI 71, Channel 1
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC E6:14:4B:57:40:0F, RSSI 75, Channel 1
  Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-Young', MAC E6:14:4B:57:40:00, RSSI 75, Channel 1
  Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC F6:14:4B:6C:19:50, RSSI 86, Channel 6
  Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'TP-LINK_PLC', MAC 30:FC:68:38:6E:2C, RSSI 66, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'TP-LINK_ORANGE', MAC B0:95:8E:05:82:CA, RSSI 56, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'vivo Y29L', MAC F4:29:81:98:F3:78, RSSI 49, Channel 6
  Capability: 0x8431 (Security: WPA2, Mode: Infrastructure)
SSID 'CDU_Free', MAC D4:61:FE:71:36:D0, RSSI 72, Channel 6
  Capability: 0x8421 (Security: Unsecured, Mode: Infrastructure)
SSID 'CDU', MAC D4:61:FE:71:36:D1, RSSI 73, Channel 6
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID 'CMCC-EDU', MAC D6:14:4B:6F:A6:0E, RSSI 58, Channel 11
  Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'CMCC-Young', MAC D6:14:4B:6F:A6:0F, RSSI 58, Channel 11
  Capability: 0x0621 (Security: Unsecured, Mode: Infrastructure)
SSID 'xgxy666', MAC DC:FE:18:67:76:14, RSSI 73, Channel 11
  Capability: 0x0431 (Security: WPA2, Mode: Infrastructure)
SSID '10505diansai', MAC 50:FA:84:53:5B:8E, RSSI 77, Channel 13
  Capability: 0x0411 (Security: WPA2, Mode: Infrastructure)
Scan finished!
Waiting for authentication!
Message 1 received!
Message 2 sent!
Message 3 received!
Message 4 sent!
Authenticated!
[Send] len=350
PTK & GTK set!
[Recv] len=351
[Send] len=350
[Recv] len=351
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
[Send] len=42
DHCP supplied address at 0.65s!
IP address: 192.168.43.64
Subnet mask: 255.255.255.0
Default gateway: 192.168.43.1
DNS Server: 192.168.43.1
[Send] len=42
Not in cache! err=-5
[Recv] len=42
[Send] len=76
[Recv] len=92
DNS Found IP: 106.186.126.193
Connecting to 106.186.126.193...
[Send] len=58
[Recv] len=58
Connected! err=0
Connection is successfully closed!
[Send] len=54
[Send] len=42
[Recv] len=54
[Send] len=54
[Send] len=42
[Send] len=42
[Send] len=42
[Recv] len=74
[Send] len=58
[Recv] len=54
[Recv] len=524
[Send] len=590
[Send] len=590
[Recv] len=54
[Send] len=590
[Recv] len=54
[Send] len=304
[Recv] len=54
[Recv] len=54
[Send] len=54
[Recv] len=74
[Send] len=58
[Recv] len=54
[Recv] len=488
[Send] len=590
[Send] len=349
[Recv] len=54
[Recv] len=54
[Send] len=54
[Recv] len=74
[Send] len=58
[Recv] len=54
[Recv] len=419
[Send] len=590
[Send] len=202
[Recv] len=54
[Recv] len=54
[Send] len=54

【程序主要代碼(庫函數版本)】

main.c:

#include <lwip/apps/httpd.h> // http服務器
#include <lwip/apps/netbiosns.h> // NetBIOS服務
#include <lwip/dhcp.h> // DHCP客戶端
#include <lwip/dns.h> // DNS客戶端
#include <lwip/init.h> // lwip_init函數所在的頭文件
#include <lwip/timeouts.h> // sys_check_timeouts函數所在的頭文件
#include <netif/ethernet.h> // ethernet_input函數所在頭文件
#include <stm32f10x.h>
#include <string.h>
#include "common.h"
#include "WiFi.h"

// 這兩個函數位於ethernetif.c中, 但沒有頭文件聲明
err_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);

// 這兩個函數位於dns_test.c中
void dns_test(void);
void display_time(void);

static struct netif wifi_88w8686;
#if LWIP_DHCP
static uint32_t dhcp_start_time = 0;
#endif

int fputc(int ch, FILE *fp)
{
  if (fp == stdout)
  {
    if (ch == '\n')
    {
      while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待前一字符發送完畢
      USART_SendData(USART1, '\r');
    }
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    USART_SendData(USART1, ch);
  }
  return ch;
}

/* 通知lwip網卡的連接狀態 */
static void set_netif(struct netif *netif, uint8_t up)
{
  if (up)
  {
    if (!netif_is_up(netif))
    {
      netif_set_up(netif);
#if LWIP_DHCP
      dhcp_start(netif); // 路由器中顯示的DHCP客戶名稱在ethernetif_init函數中設置
      dhcp_start_time = sys_now();
#endif
    }
  }
  else
  {
    netif_set_down(netif);
#if LWIP_DHCP
    dhcp_release(netif);
    dhcp_stop(netif);
#endif
  }
}

/* WiFi認證成功回調函數 */
void WiFi_AuthenticationCompleteHandler(void)
{
  printf("Authenticated!\n");
  set_netif(&wifi_88w8686, 1); // 在lwip中啓用WiFi網卡
}

/* WiFi事件回調函數 */
void WiFi_EventHandler(const WiFi_Event *event)
{
  printf("[Event %d] size=%d", event->event_id, event->header.length);
  if (event->header.length >= sizeof(WiFi_Event) - sizeof(event->mac_addr))
    printf(", reason=%d", event->reason_code);
  if (event->header.length >= sizeof(WiFi_Event))
    printf(", MAC: %02X:%02X:%02X:%02X:%02X:%02X", event->mac_addr[0], event->mac_addr[1], event->mac_addr[2], event->mac_addr[3], event->mac_addr[4], event->mac_addr[5]);
  printf("\n");
  
  switch (event->event_id)
  {
    case 3:
      // 收不到信號 (例如和手機熱點建立連接後, 把手機拿走), WiFi模塊不會自動重連
      printf("Beacon Loss/Link Loss\n");
      set_netif(&wifi_88w8686, 0);
      break;
    case 4:
      // Ad-Hoc網絡中不止1個結點, 且連接數發生了變化
      printf("The number of stations in this ad hoc newtork has changed!\n");
      set_netif(&wifi_88w8686, 1);
      break;
    case 8:
      // 認證已解除 (例如手機關閉了熱點, 或者連接路由器後因認證失敗而自動斷開連接)
      printf("Deauthenticated!\n");
      set_netif(&wifi_88w8686, 0);
      break;
    case 9:
      // 解除了關聯
      printf("Disassociated!\n");
      set_netif(&wifi_88w8686, 0);
      break;
    case 17:
      // Ad-Hoc網絡中只剩本結點
      printf("All other stations have been away from this ad hoc network!\n");
      set_netif(&wifi_88w8686, 0);
      break;
    case 30:
      printf("IBSS coalescing process is finished and BSSID has changed!\n");
      break;
  }
  
  if (event->header.length > sizeof(WiFi_Event))
    dump_data(event + 1, event->header.length - sizeof(WiFi_Event));
}

/* WiFi模塊收到新的數據幀 */
void WiFi_PacketHandler(const WiFi_DataRx *data)
{
  ethernetif_input(&wifi_88w8686); // 交給lwip處理
}

void associate_callback(void *arg, void *data, WiFi_Status status)
{
  switch (status)
  {
    case WIFI_STATUS_OK:
      printf("Associated!\n");
      set_netif(&wifi_88w8686, 1); // 在lwip中啓用WiFi網卡
      break;
    case WIFI_STATUS_NOTFOUND:
      printf("SSID not found!\n");
      break;
    case WIFI_STATUS_FAIL:
      printf("Association failed!\n");
      break;
    case WIFI_STATUS_INPROGRESS:
      printf("Waiting for authentication!\n");
      break;
    default:
      printf("Unknown error! status=%d\n", status);
  }
}

void associate_example(void)
{
  WiFi_Connection conn;
  //WiFi_WEPKey wepkey = {0}; // 未使用的成員必須設爲0
  
  /*
  conn.security = WIFI_SECURITYTYPE_WEP;
  conn.ssid = "Oct1158-2";
  conn.password = &wepkey;
  wepkey.keys[0] = "1234567890123";
  wepkey.index = 0; // 密鑰序號必須要正確
  WiFi_AssociateEx(&conn, WIFI_AUTH_MODE_OPEN, -1, associate_callback, NULL); // 開放系統方式
  //WiFi_AssociateEx(&conn, WIFI_AUTH_MODE_SHARED, -1, associate_callback, NULL); // 共享密鑰方式
  */
  
  conn.security = WIFI_SECURITYTYPE_WPA; // WPA和WPA2都可以使用此選項
  conn.ssid = "vivo Y29L";
  conn.password = "2345678am"; // WPA密碼直接指定, 不需要WiFi_WEPKey結構體
  WiFi_AssociateEx(&conn, WIFI_AUTH_MODE_OPEN, -1, associate_callback, NULL); // 必須使用WIFI_AUTH_MODE_OPEN選項
}

void adhoc_callback(void *arg, void *data, WiFi_Status status)
{
  if (status == WIFI_STATUS_OK)
    printf("ADHOC %sed!\n", (char *)arg);
  else
    printf("Cannot %s ADHOC!\n", (char *)arg);
}

void adhoc_example(void)
{
  WiFi_Connection conn;
  WiFi_WEPKey wepkey = {0}; // 未使用的成員必須設爲0
  
  /*
  conn.security = WIFI_SECURITYTYPE_WEP;
  conn.ssid = "Octopus-WEP";
  conn.password = &wepkey;
  wepkey.keys[0] = "3132333435";
  wepkey.index = 0; // 範圍: 0~3
  WiFi_JoinADHOCEx(&conn, -1, adhoc_callback, "join");
  */
  
  ///*
  // 注意: 電腦上無論密碼輸入是否正確都可以連接, 但只有正確的密碼纔可以通信
  conn.security = WIFI_SECURITYTYPE_WEP;
  conn.ssid = "WM-G-MR-09";
  conn.password = &wepkey;
  wepkey.keys[0] = "1234567890123";
  wepkey.index = 0;
  WiFi_StartADHOCEx(&conn, adhoc_callback, "start");
  //*/
  
  /*
  conn.security = WIFI_SECURITYTYPE_NONE;
  conn.ssid = "Octopus-WEP";
  WiFi_JoinADHOCEx(&conn, -1, adhoc_callback, "join");
  */
  
  /*
  conn.security = WIFI_SECURITYTYPE_NONE;
  conn.ssid = "WM-G-MR-09";
  WiFi_StartADHOCEx(&conn, adhoc_callback, "start");
  */
}

void scan_callback(void *arg, void *data, WiFi_Status status)
{
  if (status == WIFI_STATUS_OK)
    printf("Scan finished!\n");
  else
    printf("Scan failed!\n");
  
  //adhoc_example();
  associate_example();
}

// 獲取網卡MAC地址成功後, 就立即將網卡添加到lwip中, 但暫不把網卡設爲"已連接"狀態
void mac_address_callback(void *arg, void *data, WiFi_Status status)
{
#if !LWIP_DHCP
  struct ip4_addr ipaddr, netmask, gw;
#endif
  if (status == WIFI_STATUS_OK)
  {
    WiFi_Scan(scan_callback, NULL); // 掃描熱點
    
    memcpy(wifi_88w8686.hwaddr, data, 6); // 將獲得的MAC地址複製到全局變量中
    printf("MAC Addr: %02X:%02X:%02X:%02X:%02X:%02X\n", wifi_88w8686.hwaddr[0], wifi_88w8686.hwaddr[1], wifi_88w8686.hwaddr[2], wifi_88w8686.hwaddr[3], wifi_88w8686.hwaddr[4], wifi_88w8686.hwaddr[5]);
    
#if LWIP_DHCP
    netif_add(&wifi_88w8686, IP_ADDR_ANY, IP_ADDR_ANY, IP_ADDR_ANY, NULL, ethernetif_init, ethernet_input);
#else
    IP4_ADDR(&ipaddr, 192, 168, 43, 15); // IP地址
    IP4_ADDR(&netmask, 255, 255, 255, 0); // 子網掩碼
    IP4_ADDR(&gw, 192, 168, 43, 1); // 網關
    netif_add(&wifi_88w8686, &ipaddr, &netmask, &gw, NULL, ethernetif_init, ethernet_input); // 添加WiFi模塊到lwip中

#if LWIP_DNS
    IP4_ADDR(&ipaddr, 8, 8, 8, 8); // 首選DNS服務器
    dns_setserver(0, &ipaddr);
    IP4_ADDR(&ipaddr, 8, 8, 4, 4); // 備用DNS服務器
    dns_setserver(1, &ipaddr);
#endif
#endif
    netif_set_default(&wifi_88w8686); // 設爲默認網卡
  }
  else
    printf("Cannot get MAC address!\n");
}

void stop_callback(void *arg, void *data, WiFi_Status status)
{
  char *s1 = (char *)arg;
  char *s2 = s1 + strlen(s1) + 1;
  if (status == WIFI_STATUS_OK)
  {
    set_netif(&wifi_88w8686, 0);
    printf("%s %s!\n", s1, s2);
  }
  else
    printf("%s not %s!\n", s1, s2);
}

int main(void)
{
  GPIO_InitTypeDef gpio;
  USART_InitTypeDef usart;
#if LWIP_DHCP
  struct dhcp *dhcp;
#endif
  uint8_t data;
  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
  
  // 串口發送引腳PA9設爲複用推輓輸出, 串口接收引腳PA10保持默認的浮空輸入
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_9;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &gpio);
  
  USART_StructInit(&usart);
  usart.USART_BaudRate = 115200;
  USART_Init(USART1, &usart);
  USART_Cmd(USART1, ENABLE);
  printf("STM32F103RE SDIO 88W8686\n");
  
  rtc_init();
  
  WiFi_Init();
  WiFi_GetMACAddress(mac_address_callback, NULL);
  
  lwip_init();
  
  netbiosns_init();
  netbiosns_set_name("STM32F103RE"); // 計算機名
  
  httpd_init();
  while (1)
  {
    // WiFi模塊中斷和超時處理
    if (SDIO_GetFlagStatus(SDIO_FLAG_SDIOIT) == SET)
    {
      SDIO_ClearFlag(SDIO_FLAG_SDIOIT);
      WiFi_Input();
    }
    else
      WiFi_CheckTimeout();
    
    // lwip協議棧定時處理函數
    sys_check_timeouts();
    
    // 顯示DHCP獲取到的IP地址
#if LWIP_DHCP
    if (dhcp_supplied_address(&wifi_88w8686))
    {
      if (dhcp_start_time != 0)
      {
        printf("DHCP supplied address at %.2fs!\n", (sys_now() - dhcp_start_time) / 1000.0);
        dhcp_start_time = 0;
        
        dhcp = netif_dhcp_data(&wifi_88w8686);
        printf("IP address: %s\n", ip4addr_ntoa(&dhcp->offered_ip_addr));
        printf("Subnet mask: %s\n", ip4addr_ntoa(&dhcp->offered_sn_mask));
        printf("Default gateway: %s\n", ip4addr_ntoa(&dhcp->offered_gw_addr));
#if LWIP_DNS
        printf("DNS Server: %s\n", ip4addr_ntoa(dns_getserver(0)));
        dns_test();
#endif
      }
    }
#endif
    
    // 串口調試命令
    if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
    {
      data = USART_ReceiveData(USART1);
      switch (data)
      {
        case 'A':
          // 離開ADHOC網絡
          WiFi_StopADHOC(stop_callback, "ADHOC\0stopped"); // 如果當前未處於ADHOC模式, 則命令不會收到迴應, 但最終回調函數肯定會調用
          break;
        case 'D':
          // 取消關聯熱點
          WiFi_Deauthenticate(3, stop_callback, "Connection\0closed"); // LEAVING_NETWORK_DEAUTH
          break;
        case 'd':
          // WiFi模塊規定, 如果程序一直沒有讀取新收到的幀, 則不會接收新的幀
          // 這個命令用於強制丟棄WiFi緩衝區中的數據
          WiFi_DiscardData();
          break;
#if LWIP_DNS
        case 'n':
          // DNS測試
          dns_test();
          break;
#endif
        case 'R':
          // 重新連接熱點
          if (netif_is_up(&wifi_88w8686))
            printf("Please disconnect first!\n");
          else
          {
            printf("Reconnecting...\n");
            associate_example();
          }
          break;
        case 's':
          // 顯示狀態寄存器的值
          printf("SDIO->STA=0x%08x, ", SDIO->STA);
          printf("CARDSTATUS=%d, INTSTATUS=%d\n", WiFi_LowLevel_ReadReg(1, WIFI_CARDSTATUS), WiFi_LowLevel_ReadReg(1, WIFI_INTSTATUS));
          break;
        case 't':
          // 顯示當前時間
          display_time();
          break;
      }
    }
  }
}

void HardFault_Handler(void)
{
  printf("Hard Error!\n");
  while (1);
}

common.h:

void delay(uint16_t nms);
void dump_data(const void *data, uint32_t len);
void rtc_init(void);
uint32_t sys_now(void);

common.c:

#include <stdio.h>
#include <stm32f10x.h>
#include <time.h>
#include "common.h"

#define RTC_USELSI // 因爲板上沒有LSE晶振, 所以RTC時鐘選LSI

#ifdef RTC_USELSI
#define RTC_PRESCALER 40000 // LSI頻率
#else
#define RTC_PRESCALER 32768 // LSE頻率
#endif

/* 延時n毫秒 (不精確) */
// 實際延遲的時間t: nms<t<=nms+1
void delay(uint16_t nms)
{
  uint32_t newtime = sys_now() + nms;
  while (sys_now() <= newtime);
}

/* 顯示數據內容 */
void dump_data(const void *data, uint32_t len)
{
  const uint8_t *p = data;
  while (len--)
    printf("%02X", *p++);
  printf("\n");
}

/* 初始化RTC外設 */
// 如果sys_now函數不是用RTC實現的, 則可以刪掉這個函數
void rtc_init(void)
{
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
  PWR_BackupAccessCmd(ENABLE); // 允許寫後備寄存器(如RCC->BDCR)
  
#ifdef RTC_USELSI
  RCC_LSICmd(ENABLE);
  while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET); // 等待LSI啓動
#else
  if (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
  {
    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待LSE啓動
  }
#endif
  
  if ((RCC->BDCR & RCC_BDCR_RTCEN) == 0) // 這個操作無法用庫函數代碼表示
  {
    // 若RTC未打開, 則初始化RTC
    // 必須要先選擇時鐘, 然後再開啓RTC時鐘
#ifdef RTC_USELSI
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI); // 選LSI作爲RTC時鐘
#else
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
#endif
    RCC_RTCCLKCmd(ENABLE); // 開啓RTC時鐘, RTC開始走時
    
    RTC_SetPrescaler(RTC_PRESCALER - 1); // 設置分頻係數: 定時1s
    RTC_WaitForLastTask();
    //RTC_SetCounter(50); // 設置初始時間: STM32F1系列的RTC沒有年月日、小時等寄存器, 只有一個32位的計數器, 要想實現日期和時間的功能必須調用C庫<time.h>中的mktime函數, 用軟件來實現這些功能
    //RTC_WaitForLastTask();
  }
  else
    RTC_WaitForSynchro(); // 等待RTC與APB1時鐘同步
}

/* RTC時間轉化爲毫秒數 (lwip協議棧要求實現的函數) */
uint32_t sys_now(void)
{
  uint32_t sec = time(NULL); // 秒
  uint32_t div = RTC_GetDivider();
  uint32_t milli = (RTC_PRESCALER - div - 1) * 1000 / RTC_PRESCALER; // 毫秒
  return sec * 1000 + milli;
}

/* 獲取RTC秒數 */
time_t time(time_t *timer)
{
  time_t t = RTC_GetCounter();
  if (timer)
    *timer = t;
  return t;
}

WiFi.h:

/* 選項 */
#define WIFI_DEFAULT_MAXRETRY 2 // 命令幀默認最大嘗試發送的次數
#define WIFI_DEFAULT_TIMEOUT_CMDACK 20 // WiFi命令幀確認的超時時間(ms)
#define WIFI_DEFAULT_TIMEOUT_CMDRESP 1000 // WiFi命令幀迴應的超時時間(ms): 如果發現命令無論怎麼重傳都收不到迴應, 就要考慮增大超時時間
#define WIFI_DEFAULT_TIMEOUT_DATAACK 20 // WiFi數據幀確認的超時時間(ms)
#define WIFI_DISPLAY_PACKET_SIZE // 顯示收發的數據包的大小
//#define WIFI_DISPLAY_PACKET_RX // 顯示收到的數據包內容
//#define WIFI_DISPLAY_PACKET_TX // 顯示發送的數據包內容
//#define WIFI_DISPLAY_RESPTIME // 顯示命令幀和數據幀從發送到收到確認和迴應所經過的時間
#define WIFI_HIGHSPEED // 採用SDIO高速模式
#define WIFI_USEDMA // SDIO採用DMA方式收發數據

#define WIFI_FIRMWAREAREA_ADDR 0x08061000 // 固件存儲區首地址

#ifdef WIFI_FIRMWAREAREA_ADDR
// 固件存儲區的格式: helper固件大小(4B)+固件大小(4B)+helper固件內容+固件內容+CRC校驗碼(4B)
#define WIFI_HELPER_SIZE (*(const uint32_t *)WIFI_FIRMWAREAREA_ADDR)
#define WIFI_FIRMWARE_SIZE (*(const uint32_t *)(WIFI_FIRMWAREAREA_ADDR + 4))
#define WIFI_HELPER_ADDR ((const uint8_t *)WIFI_FIRMWAREAREA_ADDR + 8)
#define WIFI_FIRMWARE_ADDR (WIFI_HELPER_ADDR + WIFI_HELPER_SIZE)
#else
// 這裏sd表示這兩個固件是SDIO接口專用的, G-SPI接口對應的則是gspi
// 取消WIFI_FIRMWAREAREA_ADDR宏定義後, 需要把helper_sd.c和sd8686.c添加到工程中來, 並確保變量的聲明已添加__attribute__((aligned))
extern const unsigned char firmware_helper_sd[2516];
extern const unsigned char firmware_sd8686[122916];
#define WIFI_HELPER_SIZE sizeof(firmware_helper_sd)
#define WIFI_FIRMWARE_SIZE sizeof(firmware_sd8686)
#define WIFI_HELPER_ADDR firmware_helper_sd
#define WIFI_FIRMWARE_ADDR firmware_sd8686
#endif

/* WiFi寄存器及位定義 */
#define _BV(n) (1u << (n))

// 6.9 Card Common Control Registers (CCCR)
#define SDIO_CCCR_IOEN 0x02
#define SDIO_CCCR_IOEN_IOE1 _BV(1)

#define SDIO_CCCR_IORDY 0x03
#define SDIO_CCCR_IORDY_IOR1 _BV(1)

#define SDIO_CCCR_INTEN 0x04
#define SDIO_CCCR_INTEN_IENM _BV(0)
#define SDIO_CCCR_INTEN_IEN1 _BV(1)

#define SDIO_CCCR_BUSIFCTRL 0x07 // Bus Interface Control
#define SDIO_CCCR_BUSIFCTRL_BUSWID_1Bit 0
#define SDIO_CCCR_BUSIFCTRL_BUSWID_4Bit 0x02
#define SDIO_CCCR_BUSIFCTRL_BUSWID_8Bit 0x03

#define WIFI_IOPORT0 0x00
#define WIFI_IOPORT1 0x01
#define WIFI_IOPORT2 0x02

#define WIFI_INTMASK 0x04 // Host Interrupt Mask
#define WIFI_INTMASK_HOSTINTMASK 0x0f // enable/disable SDU to SD host interrupt

#define WIFI_INTSTATUS 0x05 // Host Interrupt Status
#define WIFI_INTSTATUS_ALL 0x0f
#define WIFI_INTSTATUS_OVERFLOW _BV(3)
#define WIFI_INTSTATUS_UNDERFLOW _BV(2)
#define WIFI_INTSTATUS_DNLD _BV(1) // Download Host Interrupt Status
#define WIFI_INTSTATUS_UPLD _BV(0) // Upload Host Interrupt Status (可隨時手動清除, 無論UPLDCARDRDY是否爲1)

#define WIFI_SQREADBASEADDR0 0x10
#define WIFI_SQREADBASEADDR1 0x11
#define WIFI_SQREADBASEADDR2 0x12
#define WIFI_SQREADBASEADDR3 0x13

#define WIFI_CARDSTATUS 0x20 // Card Status
//#define WIFI_CARDSTATUS_IOREADY _BV(3) // I/O Ready Indicator
//#define WIFI_CARDSTATUS_CISCARDRDY _BV(2) // Card Information Structure Card Ready
//#define WIFI_CARDSTATUS_UPLDCARDRDY _BV(1) // Upload Card Ready (CMD53讀寫命令均會清除該位)
//#define WIFI_CARDSTATUS_DNLDCARDRDY _BV(0) // Download Card Ready

#define WIFI_SCRATCHPAD4_0 0x34
#define WIFI_SCRATCHPAD4_1 0x35

/* WiFi模塊用到的枚舉類型 */
// Authentication Type to be used to authenticate with AP
typedef enum
{
  WIFI_AUTH_MODE_OPEN = 0x00,
  WIFI_AUTH_MODE_SHARED = 0x01,
  WIFI_AUTH_MODE_NETWORK_EAP = 0x80
} WiFi_AuthenticationType;

// BSS type
typedef enum
{
  WIFI_BSS_INFRASTRUCTURE = 0x01,
  WIFI_BSS_INDEPENDENT = 0x02,
  WIFI_BSS_ANY = 0x03
} WiFi_BSSType;

// 部分WiFi命令的action字段
typedef enum
{
  WIFI_ACT_GET = 0,
  WIFI_ACT_SET = 1,
  WIFI_ACT_ADD = 2,
  WIFI_ACT_BITWISE_SET = 2,
  WIFI_ACT_BITWISE_CLR = 3,
  WIFI_ACT_REMOVE = 4
} WiFi_CommandAction;

// WiFi命令列表
typedef enum
{
  CMD_802_11_SCAN = 0x0006, // Starts the scan process
  CMD_802_11_ASSOCIATE = 0x0012, // Initiate an association with the AP
  CMD_802_11_SET_WEP = 0x0013, // Configures the WEP keys
  CMD_802_11_DEAUTHENTICATE = 0x0024, // Starts de-authentication process with the AP
  CMD_MAC_CONTROL = 0x0028, // Controls hardware MAC
  CMD_802_11_AD_HOC_START = 0x002b, // Starts an Ad-Hoc network
  CMD_802_11_AD_HOC_JOIN = 0x002c, // Join an Ad-Hoc network
  CMD_802_11_AD_HOC_STOP = 0x0040, // Stops Ad-Hoc Network
  CMD_802_11_MAC_ADDR = 0x004d, // WLAN MAC address
  CMD_802_11_KEY_MATERIAL = 0x005e, // Gets/sets key material used to do Tx encryption or Rx decryption
  CMD_802_11_BG_SCAN_CONFIG = 0x006b, // Gets/sets background scan configuration
  CMD_802_11_BG_SCAN_QUERY = 0x006c, // Gets background scan results
  CMD_802_11_SUBSCRIBE_EVENT = 0x0075 // Subscribe to events and set thresholds
} WiFi_CommandList;

// WiFi命令執行結果
typedef enum
{
  CMD_STATUS_SUCCESS = 0x0000, // No error
  CMD_STATUS_ERROR = 0x0001, // Command failed
  CMD_STATUS_UNSUPPORTED = 0x0002 // Command is not supported (result=2表示WiFi模塊不支持此命令)
} WiFi_CommandResult;

// WiFi密鑰類型
typedef enum
{
  WIFI_KEYTYPE_WEP = 0,
  WIFI_KEYTYPE_TKIP = 1,
  WIFI_KEYTYPE_AES = 2
} WiFi_KeyType;

// Table 45: IEEE 802.11 Standard IE Translated to Marvell IE
// PDF中的表45有一些拼寫錯誤, MRVIIE應該改爲MRVLIE
typedef enum
{
  WIFI_MRVLIETYPES_SSIDPARAMSET = 0x0000,
  WIFI_MRVLIETYPES_RATESPARAMSET = 0x0001,
  WIFI_MRVLIETYPES_PHYPARAMDSSET = 0x0003,
  WIFI_MRVLIETYPES_CFPARAMSET = 0x0004,
  WIFI_MRVLIETYPES_IBSSPARAMSET = 0x0006,
  WIFI_MRVLIETYPES_RSNPARAMSET = 0x0030,
  WIFI_MRVLIETYPES_VENDORPARAMSET = 0x00dd,
  WIFI_MRVLIETYPES_KEYPARAMSET = 0x0100,
  WIFI_MRVLIETYPES_CHANLISTPARAMSET = 0x0101,
  WIFI_MRVLIETYPES_TSFTIMESTAMP = 0x0113,
  WIFI_MRVLIETYPES_AUTHTYPE = 0x011f
} WiFi_MrvlIETypes;

// SDIO幀類型
typedef enum
{
  WIFI_SDIOFRAME_DATA = 0x00,
  WIFI_SDIOFRAME_COMMAND = 0x01,
  WIFI_SDIOFRAME_EVENT = 0x03
} WiFi_SDIOFrameType;

// 16.5 SDIO Card Metaformat
typedef enum
{
  CISTPL_NULL = 0x00, // Null tuple
  CISTPL_VERS_1 = 0x15, // Level 1 version/product-information
  CISTPL_MANFID = 0x20, // Manufacturer Identification String Tuple
  CISTPL_FUNCID = 0x21, // Function Identification Tuple
  CISTPL_FUNCE = 0x22, // Function Extensions
  CISTPL_END = 0xff // The End-of-chain Tuple
} WiFi_SDIOTupleCode;

// 無線認證類型
typedef enum
{
  WIFI_SECURITYTYPE_NONE = 0,
  WIFI_SECURITYTYPE_WEP = 1,
  WIFI_SECURITYTYPE_WPA = 2,
  WIFI_SECURITYTYPE_WPA2 = 3
} WiFi_SecurityType;

// 回調函數中的狀態參數
typedef enum
{
  WIFI_STATUS_OK = 0, // 成功收到了迴應
  WIFI_STATUS_FAIL = 1, // 未能完成請求的操作 (例如找到了AP熱點但關聯失敗)
  WIFI_STATUS_BUSY = 2, // 之前的操作尚未完成
  WIFI_STATUS_NORESP = 3, // 重試了幾遍都沒有收到迴應
  WIFI_STATUS_MEM = 4, // 內存不足
  WIFI_STATUS_INVALID = 5, // 無效的參數
  WIFI_STATUS_NOTFOUND = 6, // 未找到目標 (如AP熱點)
  WIFI_STATUS_INPROGRESS = 7 // 成功執行命令, 但還需要後續的操作 (比如關聯AP成功但還需要後續的認證操作)
} WiFi_Status;

// WEP密鑰長度
typedef enum
{
  WIFI_WEPKEYTYPE_40BIT = 1,
  WIFI_WEPKEYTYPE_104BIT = 2
} WiFi_WEPKeyType;

/* 回調函數類型 */
typedef void (*WiFi_Callback)(void *arg, void *data, WiFi_Status status); // data爲NULL表示沒有收到任何迴應

/* WiFi命令字段位定義 */
// Capability information
#define WIFI_CAPABILITY_ESS _BV(0)
#define WIFI_CAPABILITY_IBSS _BV(1)
#define WIFI_CAPABILITY_CF_POLLABLE _BV(2)
#define WIFI_CAPABILITY_CF_POLL_REQUEST _BV(3)
#define WIFI_CAPABILITY_PRIVACY _BV(4)
#define WIFI_CAPABILITY_SHORT_PREAMBLE _BV(5)
#define WIFI_CAPABILITY_PBCC _BV(6)
#define WIFI_CAPABILITY_CHANNEL_AGILITY _BV(7)
#define WIFI_CAPABILITY_SPECTRUM_MGMT _BV(8)
#define WIFI_CAPABILITY_SHORT_SLOT _BV(10)
#define WIFI_CAPABILITY_DSSS_OFDM _BV(13)

#define WIFI_KEYINFO_KEYENABLED _BV(2)
#define WIFI_KEYINFO_UNICASTKEY _BV(1)
#define WIFI_KEYINFO_MULTICASTKEY _BV(0)

#define WIFI_MACCTRL_RX _BV(0)
#define WIFI_MACCTRL_TX _BV(1) // 此位必須要置1才能發送數據!!!
#define WIFI_MACCTRL_LOOPBACK _BV(2)
#define WIFI_MACCTRL_WEP _BV(3)
#define WIFI_MACCTRL_ETHERNET2 _BV(4)
#define WIFI_MACCTRL_PROMISCUOUS _BV(7)
#define WIFI_MACCTRL_ALLMULTICAST _BV(8)
#define WIFI_MACCTRL_ENFORCEPROTECTION _BV(10) // strict protection
#define WIFI_MACCTRL_ADHOCGPROTECTIONMODE _BV(13) // 802.11g protection mode

/* 常用的宏函數 */
// 已知結構體大小sizeof(tlv), 求數據域的大小, 一般用於給header.length賦值
// 例如定義一個MrvlIETypes_CfParamSet_t param變量, 賦值param.header.length=TLV_PAYLOADLEN(param)
#define TLV_PAYLOADLEN(tlv) (sizeof(tlv) - sizeof((tlv).header))

// 已知數據域大小, 求整個結構體的大小
// 例如定義一個很大的buffer, 然後定義一個IEEEType *的指針p指向該buffer
// buffer接收到數據後, 要求出接收到的IEEEType數據的實際大小顯然不能用sizeof(IEEEType), 因爲定義IEEEType結構體時data的長度定義的是1
// 此時就應該使用TLV_STRUCTLEN(*p)
#define TLV_STRUCTLEN(tlv) (sizeof((tlv).header) + (tlv).header.length)

// 已知本TLV的地址和大小, 求下一個TLV的地址
#define TLV_NEXT(tlv) ((uint8_t *)(tlv) + TLV_STRUCTLEN(*(tlv)))

// 字節序轉換函數
#ifndef htons
#define htons(x) ((((x) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8))
#endif
#ifndef ntohs
#define ntohs htons
#endif

#define WiFi_GetCommandCode(data) (((data) == NULL) ? 0 : (((const WiFi_CommandHeader *)(data))->cmd_code & 0x7fff))
#define WiFi_IsCommandResponse(data) ((data) != NULL && ((const WiFi_CommandHeader *)(data))->cmd_code & 0x8000)

/* TLV (Tag Length Value) of IEEE IE Type Format */
typedef __packed struct
{
  uint8_t type;
  uint8_t length; // 數據域的大小
} IEEEHeader;

// information element parameter
// 所有IEEETypes_*類型的基類型
typedef __packed struct
{
  IEEEHeader header;
  uint8_t data[1];
} IEEEType;

typedef __packed struct
{
  IEEEHeader header;
  uint8_t channel;
} IEEETypes_DsParamSet_t;

typedef __packed struct
{
  IEEEHeader header;
  uint16_t atim_window;
} IEEETypes_IbssParamSet_t;

/* TLV (Tag Length Value) of MrvllEType Format */
typedef __packed struct
{
  uint16_t type;
  uint16_t length;
} MrvlIEHeader;

// 所有MrvlIETypes_*類型的基類型
typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t data[1];
} MrvlIEType;

typedef __packed struct
{
  MrvlIEHeader header;
  uint16_t auth_type;
} MrvlIETypes_AuthType_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t count;
  uint8_t period;
  uint16_t max_duration;
  uint16_t duration_remaining;
} MrvlIETypes_CfParamSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  __packed struct
  {
    uint8_t band_config_type;
    uint8_t chan_number;
    uint8_t scan_type;
    uint16_t min_scan_time;
    uint16_t max_scan_time;
  } channels[1];
} MrvlIETypes_ChanListParamSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint16_t key_type_id;
  uint16_t key_info;
  uint16_t key_len;
  uint8_t key[32];
} MrvlIETypes_KeyParamSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t channel;
} MrvlIETypes_PhyParamDSSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t rates[14];
} MrvlIETypes_RatesParamSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t rsn[64];
} MrvlIETypes_RsnParamSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t ssid[32];
} MrvlIETypes_SSIDParamSet_t;

typedef __packed struct
{
  MrvlIEHeader header;
  uint64_t tsf_table[1];
} MrvlIETypes_TsfTimestamp_t;

// 整個結構體的最大大小爲256字節
typedef __packed struct
{
  MrvlIEHeader header;
  uint8_t vendor[64]; // 通常情況下64字節已足夠
} MrvlIETypes_VendorParamSet_t;

/* WiFi命令幀和數據幀格式 */
// WiFi模塊所有類型的幀的頭部
typedef __packed struct
{
  uint16_t length; // 大小包括此成員本身
  uint16_t type;
} WiFi_SDIOFrameHeader;

// WiFi模塊命令幀的頭部
typedef __packed struct
{
  WiFi_SDIOFrameHeader frame_header;
  uint16_t cmd_code;
  uint16_t size;
  uint16_t seq_num;
  uint16_t result;
} WiFi_CommandHeader;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint8_t bssid[6]; // MAC address
  uint8_t ssid[32];
  uint8_t bss_type;
  uint16_t bcn_period;
  uint8_t dtim_period; // Specify DTIM period (TBTTs)
  uint8_t timestamp[8];
  uint8_t start_ts[8]; // Starting timestamp
  IEEETypes_DsParamSet_t ds_param_set; // IEEE DS parameter set element
  uint32_t reserved1;
  IEEETypes_IbssParamSet_t ibss_param_set; // IEEE IBSS parameter set
  uint32_t reserved2;
  uint16_t cap_info;
  uint8_t data_rates[14];
  uint32_t reserved3;
} WiFi_Cmd_ADHOCJoin;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint8_t ssid[32];
  uint8_t bss_type;
  uint16_t bcn_period;
  uint8_t reserved1;
  IEEETypes_IbssParamSet_t ibss_param_set; // ATIM window length in TU
  uint32_t reserved2;
  IEEETypes_DsParamSet_t ds_param_set; // The channel for ad-hoc network
  uint8_t reserved3[6];
  uint16_t cap_info; // Capability information
  uint8_t data_rate[14];
} WiFi_Cmd_ADHOCStart;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint8_t peer_sta_addr[6];
  uint16_t reason_code; // Reason code defined in IEEE 802.11 specification section 7.3.1.7 to indicate de-authentication reason
} WiFi_Cmd_Deauthenticate;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  MrvlIETypes_KeyParamSet_t keys;
} WiFi_Cmd_KeyMaterial;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  uint8_t mac_addr[6];
} WiFi_Cmd_MACAddr;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  uint16_t reserved;
} WiFi_Cmd_MACCtrl;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t action;
  uint16_t tx_key_index; // Key set being used for transmit (0~3)
  uint8_t wep_types[4]; // use 40 or 104 bits
  uint8_t keys[4][16];
} WiFi_Cmd_SetWEP;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint8_t peer_sta_addr[6]; // Peer MAC address
  uint16_t cap_info; // Capability information
  uint16_t listen_interval; // Listen interval
  uint16_t bcn_period; // Beacon period
  uint8_t dtim_period; // DTIM period
} WiFi_CmdRequest_Associate;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint8_t bss_type;
  uint8_t bss_id[6];
} WiFi_CmdRequest_Scan;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t capability;
  uint16_t status_code;
  uint16_t association_id;
  IEEEType ie_buffer;
} WiFi_CmdResponse_Associate;

typedef __packed struct
{
  WiFi_CommandHeader header;
  uint16_t buf_size;
  uint8_t num_of_set;
} WiFi_CmdResponse_Scan;

// WiFi模塊接收的數據幀
// Table 2: Fields in Receive Packet Descriptor
typedef __packed struct
{
  WiFi_SDIOFrameHeader header;
  uint16_t reserved1;
  int8_t snr; // Signal to noise ratio for this packet (dB)
  uint8_t reserved2;
  uint16_t rx_packet_length; // Number of bytes in the payload
  uint8_t nf; // Noise floor for this packet (dBm). Noise floor is always negative. The absolute value is passed.
  uint8_t rx_rate; // Rate at which this packet is received
  uint32_t rx_packet_offset; // Offset from the start of the packet to the beginning of the payload data packet
  uint32_t reserved3;
  uint8_t priority; // Specifies the user priority of received packet
  uint8_t reserved4[3];
  uint8_t payload[1]; // 數據鏈路層上的幀
} WiFi_DataRx;

// WiFi模塊發送的數據幀
// Table 3: Fields in Transmit Packet Descriptor
typedef __packed struct
{
  WiFi_SDIOFrameHeader header;
  uint32_t reserved1;
  uint32_t tx_control; // See 3.2.1 Per-Packet Settings
  uint32_t tx_packet_offset; // Offset of the beginning of the payload data packet (802.3 or 802.11 frames) from the beginning of the packet (bytes)
  uint16_t tx_packet_length; // Number of bytes in the payload data frame
  uint16_t tx_dest_addr_high; // Destination MAC address bytes 4 to 5
  uint32_t tx_dest_addr_low; // Destination MAC address bytes 0 to 3
  uint8_t priority; // Specifies the user priority of transmit packet
  uint8_t flags;
  uint8_t pkt_delay_2ms; // Amount of time the packet has been queued in the driver layer for WMM implementations
  uint8_t reserved2;
  uint8_t payload[1]; // 數據鏈路層上的幀
} WiFi_DataTx;

// EAPOL認證幀
// https://www.vocal.com/secure-communication/eapol-extensible-authentication-protocol-over-lan/
typedef __packed struct
{
  uint8_t dest[6];
  uint8_t src[6];
  uint16_t type;
  uint8_t version;
  uint8_t packet_type;
  uint16_t packet_body_length; // big endian
  
  /* packet body */
  // 802.11-2016.pdf: Figure 12-32 EAPOL-Key frame
  // http://etutorials.org/Networking/802.11+security.+wi-fi+protected+access+and+802.11i/Part+II+The+Design+of+Wi-Fi+Security/Chapter+10.+WPA+and+RSN+Key+Hierarchy/Details+of+Key+Derivation+for+WPA/
  uint8_t descriptor_type;
  uint16_t key_information;
  uint16_t key_length;
  uint8_t key_replay_counter[8];
  uint8_t key_nonce[32];
  uint8_t key_iv[16];
  uint8_t key_rsc[8]; // receive sequence counter
  uint8_t reserved[8]; // not used in WPA
  uint8_t key_mic[16];
  uint16_t key_data_length;
  uint8_t key_data[1];
} WiFi_EAPOLKeyFrame;

// WiFi模塊事件幀
typedef __packed struct
{
  WiFi_SDIOFrameHeader header;
  uint32_t event_id; // Enumerated identifier for the event
  uint16_t reason_code; // IEEE Reason Code as described in the 802.11 standard
  uint8_t mac_addr[6]; // Peer STA Address
} WiFi_Event;

/* WiFi的常用數據結構 */
typedef __packed struct
{
  uint16_t ie_length; // Total information element length (不含sizeof(ie_length))
  uint8_t bssid[6]; // BSSID
  uint8_t rssi; // RSSI value as received from peer
  
  // Probe Response/Beacon Payload
  uint64_t pkt_time_stamp; // Timestamp
  uint16_t bcn_interval; // Beacon interval
  uint16_t cap_info; // Capabilities information
  IEEEType ie_parameters; // 存放的是一些IEEE類型的數據
} WiFi_BssDescSet;

typedef struct
{
  WiFi_SecurityType security;
  char *ssid;
  void *password;
} WiFi_Connection;

typedef __packed struct
{
  uint8_t TK[16];
  uint8_t TKIPTxMICKey[8];
  uint8_t TKIPRxMICKey[8];
} WiFi_GTK;

typedef __packed struct
{
  uint8_t type;
  uint8_t length;
  uint8_t oui[3];
  uint8_t data_type;
  uint8_t data[1];
} WiFi_KDE;

typedef __packed struct
{
  uint8_t MIC[16];
  uint8_t RESERVED[4]; // 用於給sha1函數留足緩衝區
} WiFi_MIC;

typedef __packed struct
{
  uint8_t KCK[16];
  uint8_t KEK[16];
  uint8_t TK[16];
  uint8_t TKIPTxMICKey[8];
  uint8_t TKIPRxMICKey[8];
} WiFi_PTK;

typedef __packed struct
{
  uint8_t MAC[2][6];
  uint8_t nonce[2][32];
} WiFi_PTKB;

// WiFi熱點信息
typedef struct
{
  MrvlIETypes_SSIDParamSet_t ssid;
  uint8_t mac_addr[6];
  uint16_t cap_info;
  uint16_t bcn_period;
  uint8_t channel;
  MrvlIETypes_RatesParamSet_t rates;
  MrvlIETypes_RsnParamSet_t rsn;
  MrvlIETypes_VendorParamSet_t wpa;
  MrvlIETypes_VendorParamSet_t wwm;
  MrvlIETypes_VendorParamSet_t wps;
} WiFi_SSIDInfo;

typedef struct
{
  WiFi_Callback callback;
  void *arg;
  uint8_t busy;
  uint8_t ready; // 數據幀緩衝區不使用這兩個成員
  uint8_t retry; //
  uint32_t start_time;
  uint32_t timeout;
} WiFi_TxBuffer;

typedef __packed struct
{
  uint8_t oui[3];
  uint8_t oui_type;
  uint16_t version;
  uint8_t multicast_oui[4];
  uint16_t unicast_num;
  uint8_t unicast_oui[1][4]; // 這裏假定unicast_num=1
  uint16_t auth_num; // 只有當unicast_num=1時, 該成員纔會在這個位置上
  uint8_t auth_oui[1][4];
} WiFi_Vendor;

typedef struct
{
  char *keys[4];
  uint8_t index; // 0~3
} WiFi_WEPKey;

/* WiFi模塊底層函數 */
uint8_t WiFi_LowLevel_GetFunctionNum(void);
void WiFi_LowLevel_Init(void);
uint8_t WiFi_LowLevel_ReadData(uint8_t func, uint32_t addr, void *data, uint32_t size, uint32_t bufsize);
uint8_t WiFi_LowLevel_ReadReg(uint8_t func, uint32_t addr);
void WiFi_LowLevel_SetBlockSize(uint8_t func, uint32_t size);
uint8_t WiFi_LowLevel_WriteData(uint8_t func, uint32_t addr, const void *data, uint32_t size, uint32_t bufsize);
uint8_t WiFi_LowLevel_WriteReg(uint8_t func, uint32_t addr, uint8_t value);

/* WiFi模塊主要函數 */
void WiFi_Associate(const char *ssid, WiFi_AuthenticationType auth_type, WiFi_Callback callback, void *arg);
void WiFi_AssociateEx(const WiFi_Connection *conn, WiFi_AuthenticationType auth_type, int8_t max_retry, WiFi_Callback callback, void *arg);
void WiFi_CheckTimeout(void);
void WiFi_Deauthenticate(uint16_t reason, WiFi_Callback callback, void *arg);
void WiFi_DiscardData(void);
uint8_t WiFi_GetBSSID(uint8_t mac_addr[6]);
uint16_t WiFi_GetDataLength(void);
void WiFi_GetMACAddress(WiFi_Callback callback, void *arg);
uint8_t *WiFi_GetPacketBuffer(void);
const uint8_t *WiFi_GetReceivedPacket(uint16_t *len);
WiFi_SecurityType WiFi_GetSecurityType(const WiFi_SSIDInfo *info);
void WiFi_Init(void);
void WiFi_Input(void);
uint8_t WiFi_IsCommandBusy(void);
void WiFi_JoinADHOC(const char *ssid, WiFi_Callback callback, void *arg);
void WiFi_JoinADHOCEx(const WiFi_Connection *conn, int8_t max_retry, WiFi_Callback callback, void *arg);
void WiFi_KeyMaterial(WiFi_CommandAction action, MrvlIETypes_KeyParamSet_t *key, uint8_t key_count, WiFi_Callback callback, void *arg);
void WiFi_MACAddr(const uint8_t newaddr[6], WiFi_CommandAction action, WiFi_Callback callback, void *arg);
void WiFi_MACControl(uint16_t action, WiFi_Callback callback, void *arg);
void WiFi_Scan(WiFi_Callback callback, void *arg);
void WiFi_ScanSSID(const char *ssid, WiFi_SSIDInfo *info, WiFi_Callback callback, void *arg);
void WiFi_SendCommand(uint16_t code, const void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout, uint8_t max_retry);
void WiFi_SendPacket(void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout);
void WiFi_SetWEP(WiFi_CommandAction action, const WiFi_WEPKey *key, WiFi_Callback callback, void *arg);
void WiFi_SetWPA(const char *ssid, const char *password);
void WiFi_ShowCIS(void);
void WiFi_StartADHOC(const char *ssid, uint16_t cap_info, WiFi_Callback callback, void *arg);
void WiFi_StartADHOCEx(const WiFi_Connection *conn, WiFi_Callback callback, void *arg);
void WiFi_StopADHOC(WiFi_Callback callback, void *arg);
uint8_t WiFi_TranslateTLV(MrvlIEType *mrvlie_tlv, const IEEEType *ieee_tlv, uint16_t mrvlie_payload_size);
uint8_t WiFi_Wait(uint8_t status, uint32_t timeout);
void WiFi_WaitForLastTask(void);

/* 外部自定義回調函數 */
void WiFi_AuthenticationCompleteHandler(void);
void WiFi_EventHandler(const WiFi_Event *event);
void WiFi_PacketHandler(const WiFi_DataRx *data);

WiFi_LowLevel.c:

// 定義與單片機寄存器操作相關的函數, 方便在不同平臺間移植
#include <stdio.h>
#include <stm32f10x.h>
#include <string.h>
#include "common.h"
#include "WiFi.h"

#define CMD52_WRITE _BV(31)
#define CMD52_READAFTERWRITE _BV(27)
#define CMD53_WRITE _BV(31)
#define CMD53_BLOCKMODE _BV(27)
#define CMD53_INCREMENTING _BV(26)

// 高速模式下必須使用DMA
#if defined(WIFI_HIGHSPEED) && !defined(WIFI_USEDMA)
#error "DMA must be enabled when SDIO is in high speed mode!"
#endif

static uint8_t WiFi_LowLevel_CheckError(const char *msg_title);
static uint16_t WiFi_LowLevel_GetBlockNum(uint8_t func, uint32_t *psize);
static void WiFi_LowLevel_GPIOInit(void);
static void WiFi_LowLevel_SDIOInit(void);
static void WiFi_LowLevel_SendCMD52(uint8_t func, uint32_t addr, uint8_t data, uint32_t flags);
static void WiFi_LowLevel_SendCMD53(uint8_t func, uint32_t addr, uint16_t count, uint32_t flags);
static void WiFi_LowLevel_SetSDIOBlockSize(uint32_t size);
#ifdef WIFI_FIRMWAREAREA_ADDR
static uint8_t WiFi_LowLevel_VerifyFirmware(void);
#endif
static void WiFi_LowLevel_WaitForResponse(const char *msg_title);

static uint16_t sdio_block_size[2]; // 各功能區的塊大小, 保存在此變量中避免每次都去發送CMD52命令讀SDIO寄存器
static uint8_t sdio_func_num = 0; // 功能區總數 (0號功能區除外)
static uint16_t sdio_rca; // RCA相對地址: 雖然SDIO標準規定SDIO接口上可以接多張SD卡, 但是STM32的SDIO接口只能接一張卡 (芯片手冊上有說明)
static SDIO_CmdInitTypeDef sdio_cmd;
static SDIO_DataInitTypeDef sdio_data;

/* 檢查並清除錯誤標誌位 */
static uint8_t WiFi_LowLevel_CheckError(const char *msg_title)
{
  uint8_t err = 0;
  if (SDIO_GetFlagStatus(SDIO_FLAG_CCRCFAIL) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CCRCFAIL);
    err++;
    printf("%s: CMD%d CRC failed!\n", msg_title, SDIO->CMD & SDIO_CMD_CMDINDEX);
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_CTIMEOUT) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_CTIMEOUT);
    err++;
    printf("%s: CMD%d timeout!\n", msg_title, SDIO->CMD & SDIO_CMD_CMDINDEX);
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
    err++;
    printf("%s: data CRC failed!\n", msg_title);
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
    err++;
    printf("%s: data timeout!\n", msg_title);
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_STBITERR);
    err++;
    printf("%s: start bit error!\n", msg_title);
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_TXUNDERR);
    err++;
    printf("%s: data underrun!\n", msg_title);
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == SET)
  {
    SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
    err++;
    printf("%s: data overrun!\n", msg_title);
  }
#ifdef WIFI_USEDMA
  if (DMA_GetFlagStatus(DMA2_FLAG_TE4) == SET)
  {
    DMA_ClearFlag(DMA2_FLAG_TE4);
    err++;
    printf("%s: DMA transfer error!\n", msg_title);
  }
#endif
  return err;
}

/* 判斷應該採用哪種方式傳輸數據 */
// 返回值: 0爲多字節模式, 否則表示塊傳輸模式的數據塊數
// *psize的值會做適當調整, 有可能大於原值
static uint16_t WiFi_LowLevel_GetBlockNum(uint8_t func, uint32_t *psize)
{
  uint16_t block_num = 0;
#ifndef WIFI_HIGHSPEED
  if (*psize > 512) // 大於512字節時才用數據塊方式傳輸
  {
#endif
    // 塊傳輸模式 (DTMODE=0)
    WiFi_LowLevel_SetSDIOBlockSize(sdio_block_size[func]);
    
    block_num = *psize / sdio_block_size[func];
    if (*psize % sdio_block_size[func] != 0)
      block_num++;
    *psize = block_num * sdio_block_size[func]; // 塊數*塊大小
#ifndef WIFI_HIGHSPEED
  }
  else
  {
    // 多字節傳輸模式 (DTMODE=1, SDIO的頻率低於16MHz時才支持)
    *psize = (*psize + 3) & ~3; // WiFi模塊要求寫入的字節數必須爲4的整數倍
  }
#endif
  return block_num;
}

/* 獲取WiFi模塊支持SDIO功能區個數 (0號功能區除外) */
uint8_t WiFi_LowLevel_GetFunctionNum(void)
{
  return sdio_func_num;
}

/* 初始化WiFi模塊有關的所有GPIO引腳 */
static void WiFi_LowLevel_GPIOInit(void)
{
  GPIO_InitTypeDef gpio;
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
  
  // WiFi模塊的電源引腳是通過場效應管(相當於PNP三極管)接到VCC上的
  // 基極接的是單片機的PB12, 發射極接的是電源VCC, 集電極接的是WiFi模塊的VCC, 基極必須串聯一個限流電阻
  // 單片機復位時PB12輸出高阻態, 三極管不導通, WiFi模塊不通電
  // 現將PB12設爲輸出低電平, 三極管導通, WiFi模塊上電 (這起到了復位的效果)
  gpio.GPIO_Mode = GPIO_Mode_Out_PP; // PB12設爲推輓輸出, 並立即輸出默認的低電平
  gpio.GPIO_Pin = GPIO_Pin_12;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &gpio);
  
  // SDIO相關引腳
  // PC8~11: SDIO_D0~3, PC12: SDIO_CK, 設爲複用推輓輸出
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
  GPIO_Init(GPIOC, &gpio);
  // PD2: SDIO_CMD, 設爲複用推輓輸出
  gpio.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &gpio);
}

void WiFi_LowLevel_Init(void)
{
  // 在此處打開WiFi模塊所需要的除GPIO和SDIO外所有其他外設的時鐘
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);
  
  // 檢查Flash中保存的固件內容是否已被破壞
#ifdef WIFI_FIRMWAREAREA_ADDR
  if (!WiFi_LowLevel_VerifyFirmware())
  {
    printf("Error: The firmware stored in flash memory is corrupted!\n");
    while (1);
  }
#endif
  
  WiFi_LowLevel_GPIOInit();
  WiFi_LowLevel_SDIOInit();
}

/* 接收數據, 自動判斷採用哪種傳輸模式 */
// size爲要接收的字節數, bufsize爲data緩衝區的大小
// 若bufsize=0, 則只讀取數據, 但不保存到data中, 此時data可以爲NULL
uint8_t WiFi_LowLevel_ReadData(uint8_t func, uint32_t addr, void *data, uint32_t size, uint32_t bufsize)
{
  uint16_t block_num; // 數據塊個數
#ifdef WIFI_USEDMA
  DMA_InitTypeDef dma;
  uint32_t temp; // 丟棄數據用的變量
#else
  uint32_t *p = data;
#endif
  if ((uintptr_t)data & 3)
    printf("WiFi_LowLevel_ReadData: data must be 4-byte aligned!\n"); // DMA每次傳輸多個字節時, 內存和外設地址必須要對齊, 否則將不能正確傳輸且不會提示錯誤
  if (size == 0)
  {
    printf("WiFi_LowLevel_ReadData: size cannot be 0!\n");
    return 0;
  }
  
  block_num = WiFi_LowLevel_GetBlockNum(func, &size);
  if (bufsize > 0 && bufsize < size)
  {
    printf("WiFi_LowLevel_ReadData: a buffer of at least %d bytes is required! bufsize=%d\n", size, bufsize);
    return 0;
  }
  
#ifdef WIFI_USEDMA
  dma.DMA_BufferSize = size / 4;
  dma.DMA_DIR = DMA_DIR_PeripheralSRC;
  dma.DMA_M2M = DMA_M2M_Disable;
  dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  dma.DMA_Mode = DMA_Mode_Normal;
  dma.DMA_PeripheralBaseAddr = (uint32_t)&SDIO->FIFO;
  dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  dma.DMA_Priority = DMA_Priority_VeryHigh;
  if (bufsize > 0)
  {
    dma.DMA_MemoryBaseAddr = (uint32_t)data;
    dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
  }
  else
  {
    // 數據丟棄模式
    dma.DMA_MemoryBaseAddr = (uint32_t)&temp;
    dma.DMA_MemoryInc = DMA_MemoryInc_Disable;
  }
  DMA_Init(DMA2_Channel4, &dma);
  DMA_Cmd(DMA2_Channel4, ENABLE);
#endif
  
  if (block_num)
  {
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Block;
    WiFi_LowLevel_SendCMD53(func, addr, block_num, CMD53_BLOCKMODE);
  }
  else
  {
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Stream;
    WiFi_LowLevel_SendCMD53(func, addr, size, 0);
  }
  
  sdio_data.SDIO_DataLength = size;
  sdio_data.SDIO_DPSM = SDIO_DPSM_Enable;
  sdio_data.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
  SDIO_DataConfig(&sdio_data);
  
  while (SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == RESET); // 等待開始接收數據
#ifdef WIFI_USEDMA
  while (DMA_GetFlagStatus(DMA2_FLAG_TC4) == RESET && SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == RESET); // 等待DMA讀取數據完畢且沒有出現錯誤
  DMA_ClearFlag(DMA2_FLAG_TC4); // 清除DMA傳輸完成標誌位
  DMA_Cmd(DMA2_Channel4, DISABLE); // 關閉DMA
#else
  while (size && SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == RESET)
  {
    // 如果有數據到來就讀取數據
    if (SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) == SET)
    {
      size -= 4;
      if (bufsize > 0)
        *p++ = SDIO_ReadData();
      else
        SDIO_ReadData(); // 讀寄存器, 但不保存數據
    }
  }
#endif
  
  while ((SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET || SDIO_GetFlagStatus(SDIO_FLAG_RXACT) == SET) && SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) == RESET); // 等待接收完畢
  sdio_data.SDIO_DPSM = SDIO_DPSM_Disable;
  SDIO_DataConfig(&sdio_data);
  
  // 清除相關標誌位
  SDIO_ClearFlag(SDIO_FLAG_CMDREND | SDIO_FLAG_DATAEND | SDIO_FLAG_DBCKEND);
  return WiFi_LowLevel_CheckError(__func__) == 0;
}

/* 讀SDIO寄存器 */
uint8_t WiFi_LowLevel_ReadReg(uint8_t func, uint32_t addr)
{
  WiFi_LowLevel_SendCMD52(func, addr, 0, 0);
  WiFi_LowLevel_WaitForResponse(__func__);
  return SDIO_GetResponse(SDIO_RESP1) & 0xff;
}

/* 初始化SDIO外設並完成WiFi模塊的枚舉 */
// SDIO Simplified Specification Version 3.00: 3. SDIO Card Initialization
static void WiFi_LowLevel_SDIOInit(void)
{
  SDIO_InitTypeDef sdio;
  uint32_t resp;
  
  // SDIO外設擁有兩個時鐘: SDIOCLK=HCLK=72MHz(分頻後用於產生SDIO_CK=PC12引腳時鐘), AHB bus clock=HCLK/2=36MHz(用於訪問寄存器)
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO, ENABLE);
#ifdef WIFI_USEDMA
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
#endif
  
  SDIO_SetPowerState(SDIO_PowerState_ON); // 打開SDIO外設
  SDIO_StructInit(&sdio);
  sdio.SDIO_ClockDiv = 178; // 初始化時最高允許的頻率: 72MHz/(178+2)=400kHz
  SDIO_Init(&sdio);
  SDIO_ClockCmd(ENABLE);
  
  SDIO_SetSDIOOperation(ENABLE); // 設爲SDIO模式
#ifdef WIFI_USEDMA
  SDIO_DMACmd(ENABLE); // 設爲DMA傳輸模式
#endif
  
  // 不需要發送CMD0, 因爲SD I/O card的初始化命令是CMD52
  // An I/O only card or the I/O portion of a combo card is NOT reset by CMD0. (See 4.4 Reset for SDIO)
  delay(10); // 延時可防止CMD5重發
  
  /* 發送CMD5: IO_SEND_OP_COND */
  sdio_cmd.SDIO_Argument = 0;
  sdio_cmd.SDIO_CmdIndex = 5;
  sdio_cmd.SDIO_CPSM = SDIO_CPSM_Enable;
  sdio_cmd.SDIO_Response = SDIO_Response_Short; // 接收短迴應
  sdio_cmd.SDIO_Wait = SDIO_Wait_No;
  SDIO_SendCommand(&sdio_cmd);
  WiFi_LowLevel_WaitForResponse(__func__);
  printf("RESPCMD%d, RESP1_%08x\n", SDIO_GetCommandResponse(), SDIO_GetResponse(SDIO_RESP1));
  
  /* 設置參數VDD Voltage Window: 3.2~3.4V, 並再次發送CMD5 */
  sdio_cmd.SDIO_Argument = 0x300000;
  SDIO_SendCommand(&sdio_cmd);
  WiFi_LowLevel_WaitForResponse(__func__);
  resp = SDIO_GetResponse(SDIO_RESP1);
  printf("RESPCMD%d, RESP1_%08x\n", SDIO_GetCommandResponse(), resp);
  if (resp & _BV(31))
  {
    // Card is ready to operate after initialization
    sdio_func_num = (resp >> 28) & 7;
    printf("Number of I/O Functions: %d\n", sdio_func_num);
    printf("Memory Present: %d\n", (resp & _BV(27)) != 0);
  }
  
  /* 獲取WiFi模塊地址 (CMD3: SEND_RELATIVE_ADDR, Ask the card to publish a new relative address (RCA)) */
  sdio_cmd.SDIO_Argument = 0;
  sdio_cmd.SDIO_CmdIndex = 3;
  SDIO_SendCommand(&sdio_cmd);
  WiFi_LowLevel_WaitForResponse(__func__);
  sdio_rca = SDIO_GetResponse(SDIO_RESP1) >> 16;
  printf("Relative Card Address: 0x%04x\n", sdio_rca);
  
  /* 選中WiFi模塊 (CMD7: SELECT/DESELECT_CARD) */
  sdio_cmd.SDIO_Argument = sdio_rca << 16;
  sdio_cmd.SDIO_CmdIndex = 7;
  SDIO_SendCommand(&sdio_cmd);
  WiFi_LowLevel_WaitForResponse(__func__);
  printf("Card selected! RESP1_%08x\n", SDIO_GetResponse(SDIO_RESP1));
  
  /* 提高時鐘頻率, 並設置數據超時時間爲0.1s */
#ifdef WIFI_HIGHSPEED
  sdio.SDIO_ClockDiv = 1; // 72MHz/(1+2)=24MHz
  sdio_data.SDIO_DataTimeOut = 2400000;
  printf("SDIO Clock: 24MHz\n");
#else
  sdio.SDIO_ClockDiv = 70; // 72MHz/(70+2)=1MHz
  sdio_data.SDIO_DataTimeOut = 100000;
  printf("SDIO Clock: 1MHz\n");
#endif
  
  /* SDIO外設的總線寬度設爲4位 */
  sdio.SDIO_BusWide = SDIO_BusWide_4b;
  SDIO_Init(&sdio);
  WiFi_LowLevel_WriteReg(0, SDIO_CCCR_BUSIFCTRL, WiFi_LowLevel_ReadReg(0, SDIO_CCCR_BUSIFCTRL) | SDIO_CCCR_BUSIFCTRL_BUSWID_4Bit);
}

static void WiFi_LowLevel_SendCMD52(uint8_t func, uint32_t addr, uint8_t data, uint32_t flags)
{
  sdio_cmd.SDIO_Argument = (func << 28) | (addr << 9) | data | flags;
  sdio_cmd.SDIO_CmdIndex = 52;
  sdio_cmd.SDIO_CPSM = SDIO_CPSM_Enable;
  sdio_cmd.SDIO_Response = SDIO_Response_Short;
  sdio_cmd.SDIO_Wait = SDIO_Wait_No;
  SDIO_SendCommand(&sdio_cmd);
}

static void WiFi_LowLevel_SendCMD53(uint8_t func, uint32_t addr, uint16_t count, uint32_t flags)
{
  // 當count=512時, 和0x1ff相與後爲0, 符合SDIO標準
  sdio_cmd.SDIO_Argument = (func << 28) | (addr << 9) | (count & 0x1ff) | flags;
  sdio_cmd.SDIO_CmdIndex = 53;
  sdio_cmd.SDIO_CPSM = SDIO_CPSM_Enable;
  sdio_cmd.SDIO_Response = SDIO_Response_Short;
  sdio_cmd.SDIO_Wait = SDIO_Wait_No;
  SDIO_SendCommand(&sdio_cmd);
}

/* 設置WiFi模塊功能區的數據塊大小 */
void WiFi_LowLevel_SetBlockSize(uint8_t func, uint32_t size)
{
  sdio_block_size[func] = size;
  WiFi_LowLevel_WriteReg(0, (func << 8) | 0x10, size & 0xff);
  WiFi_LowLevel_WriteReg(0, (func << 8) | 0x11, size >> 8);
}

/* 設置SDIO外設的數據塊大小 */
static void WiFi_LowLevel_SetSDIOBlockSize(uint32_t size)
{
  switch (size)
  {
    case 1:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_1b;
      break;
    case 2:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_2b;
      break;
    case 4:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_4b;
      break;
    case 8:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_8b;
      break;
    case 16:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_16b;
      break;
    case 32:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_32b;
      break;
    case 64:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_64b;
      break;
    case 128:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_128b;
      break;
    case 256:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_256b;
      break;
    case 512:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_512b;
      break;
    case 1024:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_1024b;
      break;
    case 2048:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_2048b;
      break;
    case 4096:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_4096b;
      break;
    case 8192:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_8192b;
      break;
    case 16384:
      sdio_data.SDIO_DataBlockSize = SDIO_DataBlockSize_16384b;
      break;
  }
}

/* 檢查Flash中保存的固件內容是否完整 */
#ifdef WIFI_FIRMWAREAREA_ADDR
static uint8_t WiFi_LowLevel_VerifyFirmware(void)
{
  uint32_t crc;
  uint32_t len = (WIFI_HELPER_SIZE + WIFI_FIRMWARE_SIZE) / 4 + 3; // 固件區(包括CRC)總大小的1/4
  
  CRC_ResetDR();
  crc = CRC_CalcBlockCRC((uint32_t *)WIFI_FIRMWAREAREA_ADDR, len);
  return crc == 0;
}
#endif

/* 等待SDIO命令迴應 */
static void WiFi_LowLevel_WaitForResponse(const char *msg_title)
{
  uint8_t first = 1;
  do
  {
    if (!first)
      SDIO_SendCommand(&sdio_cmd); // 重發命令
    else
      first = 0;
    while (SDIO_GetFlagStatus(SDIO_FLAG_CMDACT) == SET); // 等待命令發送完畢
    WiFi_LowLevel_CheckError(msg_title);
  } while (SDIO_GetFlagStatus(SDIO_FLAG_CMDREND) == RESET); // 如果沒有收到迴應, 則重試
  SDIO_ClearFlag(SDIO_FLAG_CMDREND);
}

/* 發送數據, 自動判斷採用哪種傳輸模式 */
// size爲要發送的字節數, bufsize爲data緩衝區的大小
uint8_t WiFi_LowLevel_WriteData(uint8_t func, uint32_t addr, const void *data, uint32_t size, uint32_t bufsize)
{
  uint16_t block_num; // 數據塊個數
#ifdef WIFI_USEDMA
  DMA_InitTypeDef dma;
#else
  const uint32_t *p = data;
#endif
  if ((uintptr_t)data & 3)
    printf("WiFi_LowLevel_WriteData: data must be 4-byte aligned!\n");
  if (size == 0)
  {
    printf("WiFi_LowLevel_WriteData: size cannot be 0!\n");
    return 0;
  }

  block_num = WiFi_LowLevel_GetBlockNum(func, &size);
  if (bufsize < size)
    printf("WiFi_LowLevel_WriteData: a buffer of at least %d bytes is required! bufsize=%d\n", size, bufsize); // 只讀緩衝區越界不會影響數據傳輸, 所以這只是一個警告
  
  if (block_num)
  {
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Block;
    WiFi_LowLevel_SendCMD53(func, addr, block_num, CMD53_WRITE | CMD53_BLOCKMODE);
  }
  else
  {
    sdio_data.SDIO_TransferMode = SDIO_TransferMode_Stream;
    WiFi_LowLevel_SendCMD53(func, addr, size, CMD53_WRITE);
  }
  WiFi_LowLevel_WaitForResponse(__func__); // 必須要等到CMD53收到迴應後才能開始發送數據
  
  // 開始發送數據
#ifdef WIFI_USEDMA
  dma.DMA_BufferSize = size / 4;
  dma.DMA_DIR = DMA_DIR_PeripheralDST;
  dma.DMA_M2M = DMA_M2M_Disable;
  dma.DMA_MemoryBaseAddr = (uint32_t)data;
  dma.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
  dma.DMA_Mode = DMA_Mode_Normal;
  dma.DMA_PeripheralBaseAddr = (uint32_t)&SDIO->FIFO;
  dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  dma.DMA_Priority = DMA_Priority_VeryHigh;
  DMA_Init(DMA2_Channel4, &dma);
  DMA_Cmd(DMA2_Channel4, ENABLE);
#endif
  
  sdio_data.SDIO_DataLength = size;
  sdio_data.SDIO_DPSM = SDIO_DPSM_Enable;
  sdio_data.SDIO_TransferDir = SDIO_TransferDir_ToCard;
  SDIO_DataConfig(&sdio_data);
  
  while (SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == RESET); // 等待開始發送數據
#ifdef WIFI_USEDMA
  while (DMA_GetFlagStatus(DMA2_FLAG_TC4) == RESET && SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == RESET); // 等待數據送入完畢且沒有出現錯誤
  DMA_ClearFlag(DMA2_FLAG_TC4); // 清除DMA傳輸完成標誌位
  DMA_Cmd(DMA2_Channel4, DISABLE); // 關閉DMA
#else
  while (size && SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == RESET)
  {
    size -= 4;
    SDIO_WriteData(*p++); // 向FIFO送入4字節數據
    while (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOF) == SET); // 如果FIFO已滿則等待
  }
#endif
  
  while (SDIO_GetFlagStatus(SDIO_FLAG_TXACT) == SET && SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) == RESET); // 等待發送完畢
  sdio_data.SDIO_DPSM = SDIO_DPSM_Disable;
  SDIO_DataConfig(&sdio_data);
  
  // 清除相關標誌位
  SDIO_ClearFlag(SDIO_FLAG_DATAEND | SDIO_FLAG_DBCKEND);
  return WiFi_LowLevel_CheckError(__func__) == 0;
}

/* 寫寄存器, 返回寫入後寄存器的實際內容 */
uint8_t WiFi_LowLevel_WriteReg(uint8_t func, uint32_t addr, uint8_t value)
{
  WiFi_LowLevel_SendCMD52(func, addr, value, CMD52_WRITE | CMD52_READAFTERWRITE);
  WiFi_LowLevel_WaitForResponse(__func__);
  return SDIO_GetResponse(SDIO_RESP1) & 0xff;
}

WiFi.c:

#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "WPA.h"
#include "WiFi.h"

static void WiFi_Associate_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_AssociateEx_Callback(void *arg, void *data, WiFi_Status status);
static uint8_t WiFi_CheckCommandBusy(WiFi_Callback callback, void *arg);
static uint8_t WiFi_CheckMIC(WiFi_EAPOLKeyFrame *packet, uint16_t len);
static uint8_t WiFi_CheckTxBufferRetry(WiFi_TxBuffer *tbuf, void *data);
static void WiFi_Deauthenticate_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_DownloadFirmware(void);
static void WiFi_EAPOLProcess(WiFi_DataRx *data);
static void WiFi_EAPOLProcess_Callback(void *arg, void *data, WiFi_Status status);
static uint8_t WiFi_ExtractGTK(const WiFi_EAPOLKeyFrame *packet_rx);
static void WiFi_GetMACAddress_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_JoinADHOC_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_JoinADHOCEx_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_TxBufferComplete(WiFi_TxBuffer *tbuf, void *data, WiFi_Status status);
static void WiFi_Scan_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_ScanSSID_Callback(void *arg, void *data, WiFi_Status status);
static void WiFi_SendEAPOLResponse(const WiFi_EAPOLKeyFrame *packet_rx, uint16_t key_info, const void *key_data, uint16_t key_data_len, WiFi_Callback callback, void *arg);
static void WiFi_SetKeyMaterial(WiFi_KeyType key_type, uint8_t key_num, WiFi_Callback callback, void *arg);
static void WiFi_StartADHOCEx_Callback(void *arg, void *data, WiFi_Status status);

static uint8_t wifi_buffer_command[256]; // 命令幀發送緩衝區
static uint8_t wifi_buffer_packet[1792]; // 數據幀發送緩衝區
static uint8_t wifi_buffer_rx[2048]; // 幀接收緩衝區
static uint8_t wifi_psk[32]; // preshared-key
static uint8_t wifi_snonce[32];
static uint32_t wifi_port;
static uint32_t wifi_input_time = 0; // 最後一次調用SDIO中斷處理函數的時間
static WiFi_GTK wifi_gtk;
static WiFi_PTK wifi_ptk;
static WiFi_SSIDInfo wifi_ssid_info = {0}; // 當前連接的熱點信息
static WiFi_TxBuffer wifi_tx_command = {0}; // 命令幀發送緩衝區描述符
static WiFi_TxBuffer wifi_tx_packet = {0}; // 數據幀發送緩衝區描述符

/* 關聯一個熱點 */
// 參數mac_addr用於接收熱點的MAC地址, 可以爲NULL, 但是不能指向局部變量
void WiFi_Associate(const char *ssid, WiFi_AuthenticationType auth_type, WiFi_Callback callback, void *arg)
{
  void **p;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  p = malloc(2 * sizeof(void *) + sizeof(WiFi_AuthenticationType)); // 最後一個成員不是指針, 而是實際數據
  if (p == NULL)
  {
    printf("WiFi_Associate: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  *(WiFi_AuthenticationType *)(p + 2) = auth_type;
  
  WiFi_ScanSSID(ssid, &wifi_ssid_info, WiFi_Associate_Callback, p);
}

static void WiFi_Associate_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg; // 之前分配的內存
  void *app_arg = p[0]; // 用戶指定的參數
  WiFi_Callback app_callback = (WiFi_Callback)p[1]; // 用戶指定的回調函數
  WiFi_AuthenticationType *pauth = (WiFi_AuthenticationType *)(p + 2);
  
  uint16_t cmd_size;
  WiFi_CmdRequest_Associate *cmd;
  WiFi_CmdResponse_Associate *resp;
  WiFi_SecurityType security;
  MrvlIETypes_PhyParamDSSet_t *ds;
  MrvlIETypes_CfParamSet_t *cf;
  MrvlIETypes_AuthType_t *auth;
  MrvlIETypes_VendorParamSet_t *vendor;
  MrvlIETypes_RsnParamSet_t *rsn;
  
  if (status != WIFI_STATUS_OK)
  {
    printf("WiFi_Associate error!\n");
    free(arg);
    if (app_callback)
      app_callback(app_arg, data, status);
    return;
  }
  
  security = WiFi_GetSecurityType(&wifi_ssid_info);
  switch (WiFi_GetCommandCode(data))
  {
    case CMD_802_11_SCAN:
      // WiFi_ScanSSID命令執行完畢
      cmd = (WiFi_CmdRequest_Associate *)wifi_buffer_command;
      memcpy(cmd->peer_sta_addr, wifi_ssid_info.mac_addr, sizeof(wifi_ssid_info.mac_addr));
      cmd->cap_info = wifi_ssid_info.cap_info;
      cmd->listen_interval = 10;
      cmd->bcn_period = wifi_ssid_info.bcn_period;
      cmd->dtim_period = 1;
      memcpy(cmd + 1, &wifi_ssid_info.ssid, TLV_STRUCTLEN(wifi_ssid_info.ssid));
      
      ds = (MrvlIETypes_PhyParamDSSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(wifi_ssid_info.ssid));
      ds->header.type = WIFI_MRVLIETYPES_PHYPARAMDSSET;
      ds->header.length = 1;
      ds->channel = wifi_ssid_info.channel;
      
      cf = (MrvlIETypes_CfParamSet_t *)(ds + 1);
      memset(cf, 0, sizeof(MrvlIETypes_CfParamSet_t));
      cf->header.type = WIFI_MRVLIETYPES_CFPARAMSET;
      cf->header.length = TLV_PAYLOADLEN(*cf);
      
      memcpy(cf + 1, &wifi_ssid_info.rates, TLV_STRUCTLEN(wifi_ssid_info.rates));
      auth = (MrvlIETypes_AuthType_t *)((uint8_t *)(cf + 1) + TLV_STRUCTLEN(wifi_ssid_info.rates));
      auth->header.type = WIFI_MRVLIETYPES_AUTHTYPE;
      auth->header.length = TLV_PAYLOADLEN(*auth);
      auth->auth_type = *pauth;
      
      cmd_size = (uint8_t *)(auth + 1) - wifi_buffer_command;
      if (security == WIFI_SECURITYTYPE_WPA)
      {
        // WPA網絡必須在命令中加入Vendor參數才能成功連接
        vendor = (MrvlIETypes_VendorParamSet_t *)(auth + 1);
        memcpy(vendor, &wifi_ssid_info.wpa, TLV_STRUCTLEN(wifi_ssid_info.wpa));
        cmd_size += TLV_STRUCTLEN(wifi_ssid_info.wpa);
      }
      else if (security == WIFI_SECURITYTYPE_WPA2)
      {
        // WPA2網絡必須在命令中加入RSN參數才能成功連接
        rsn = (MrvlIETypes_RsnParamSet_t *)(auth + 1);
        memcpy(rsn, &wifi_ssid_info.rsn, TLV_STRUCTLEN(wifi_ssid_info.rsn));
        cmd_size += TLV_STRUCTLEN(wifi_ssid_info.rsn);
      }
      
      WiFi_SendCommand(CMD_802_11_ASSOCIATE, wifi_buffer_command, cmd_size, WiFi_Associate_Callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
      // 保留arg內存, 等關聯成功後再釋放
      break;
    case CMD_802_11_ASSOCIATE:
      // 關聯命令執行完畢並收到了迴應
      // 現在需要檢查是否關聯成功
      free(arg);
      resp = (WiFi_CmdResponse_Associate *)data;
      //printf("capability=0x%04x, status_code=0x%04x, aid=0x%04x\n", resp->capability, resp->status_code, resp->association_id);
      if (app_callback)
      {
        if (resp->association_id == 0xffff)
          app_callback(app_arg, data, WIFI_STATUS_FAIL); // 關聯失敗 (在回調函數的data中檢查resp->capability和resp->status_code的值可獲得詳細原因)
        else if (security == WIFI_SECURITYTYPE_WPA || security == WIFI_SECURITYTYPE_WPA2)
          app_callback(app_arg, data, WIFI_STATUS_INPROGRESS); // 等待認證
        else
          app_callback(app_arg, data, WIFI_STATUS_OK); // 關聯成功
      }
      break;
  }
}

/* 關聯一個熱點並輸入密碼 */
// 連接WPA型的熱點時, security成員直接賦值WIFI_SECURITYTYPE_WPA即可, 不需要明確指出WPA版本號
void WiFi_AssociateEx(const WiFi_Connection *conn, WiFi_AuthenticationType auth_type, int8_t max_retry, WiFi_Callback callback, void *arg)
{
  int8_t *pmax_retry;
  uint16_t ssid_len;
  void **p;
  WiFi_AuthenticationType *pauth;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  ssid_len = strlen(conn->ssid);
  p = malloc(2 * sizeof(void *) + sizeof(WiFi_AuthenticationType) + sizeof(int8_t) + ssid_len + 1);
  if (p == NULL)
  {
    printf("WiFi_AssociateEx: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  pauth = (WiFi_AuthenticationType *)(p + 2);
  *pauth = auth_type;
  pmax_retry = (int8_t *)(pauth + 1);
  *pmax_retry = max_retry; // 最大嘗試重新連接的次數, -1表示無限次數, 0表示不重試
  memcpy(pmax_retry + 1, conn->ssid, ssid_len + 1);
  
  if (conn->security == WIFI_SECURITYTYPE_WEP)
    WiFi_SetWEP(WIFI_ACT_ADD, conn->password, WiFi_AssociateEx_Callback, p);
  else
  {
    WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_AssociateEx_Callback, p);
    if (conn->security == WIFI_SECURITYTYPE_WPA || conn->security == WIFI_SECURITYTYPE_WPA2)
      WiFi_SetWPA(conn->ssid, conn->password);
  }
}

static void WiFi_AssociateEx_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  WiFi_AuthenticationType *pauth = (WiFi_AuthenticationType *)(p + 2);
  int8_t *pmax_retry = (int8_t *)(pauth + 1);
  
  char *ssid = (char *)(pmax_retry + 1);
  uint16_t cmd_code = WiFi_GetCommandCode(data);
  
  if (cmd_code == CMD_802_11_ASSOCIATE || cmd_code == CMD_802_11_SCAN)
  {
    if (cmd_code == CMD_802_11_ASSOCIATE && (status == WIFI_STATUS_OK || status == WIFI_STATUS_INPROGRESS))
    {
      // 關聯成功
      free(arg);
      if (app_callback)
        app_callback(app_arg, data, status);
      return;
    }
    else
    {
      // 關聯失敗, 重試
      if (*pmax_retry != 0)
      {
        if (*pmax_retry != -1)
          (*pmax_retry)--;
        cmd_code = CMD_MAC_CONTROL;
        status = WIFI_STATUS_OK;
      }
    }
  }
  
  if (status != WIFI_STATUS_OK)
  {
    printf("WiFi_AssociateEx error!\n");
    free(arg);
    if (app_callback)
      app_callback(app_arg, data, status);
    return;
  }
  
  switch (cmd_code)
  {
    case CMD_802_11_SET_WEP:
      WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_WEP | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_AssociateEx_Callback, arg);
      break;
    case CMD_MAC_CONTROL:
      WiFi_Associate(ssid, *pauth, WiFi_AssociateEx_Callback, arg);
  }
}

/* 如果之前的命令尚未執行完就請求執行新的命令, 則直接調用回調函數報告錯誤 */
static uint8_t WiFi_CheckCommandBusy(WiFi_Callback callback, void *arg)
{
  // 發送新命令前必須確保之前的命令已經發送完畢並收到迴應
  // See 4.2 Protocol: The command exchange protocol is serialized, the host driver must wait until 
  // it has received a command response for the current command request before it can send the next command request.
  if (WiFi_IsCommandBusy())
  {
    printf("Warning: The previous command is in progress!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_BUSY);
    return 1;
  }
  
  WiFi_WaitForLastTask(); // 等待之前的數據幀收到確認
  return 0;
}

/* 驗證EAPOL幀中的MIC值是否正確 */
static uint8_t WiFi_CheckMIC(WiFi_EAPOLKeyFrame *packet, uint16_t len)
{
  uint8_t ret;
  WiFi_KeyType key_type = (WiFi_KeyType)(ntohs(packet->key_information) & 0x07);
  WiFi_MIC mic[2];
  
  memcpy(mic[0].MIC, packet->key_mic, sizeof(packet->key_mic));
  memset(packet->key_mic, 0, sizeof(packet->key_mic));
  if (key_type == WIFI_KEYTYPE_TKIP)
    ret = hmac_md5(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet->version, len - 14, mic[1].MIC);
  else if (key_type == WIFI_KEYTYPE_AES)
    ret = hmac_sha1(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet->version, len - 14, mic[1].MIC);
  else
    return 0;
  
  if (!ret)
  {
    printf("WiFi_CheckMIC: out of memory!\n");
    return 0;
  }
  return memcmp(mic[0].MIC, mic[1].MIC, sizeof(mic[1].MIC)) == 0;
}

/* 數據幀、命令幀發送超時處理 */
void WiFi_CheckTimeout(void)
{
  WiFi_CommandHeader *cmd = (WiFi_CommandHeader *)wifi_buffer_command;
  
  // 定時檢查INTSTATUS寄存器, 避免因爲沒有檢測到SDIOIT中斷而導致程序卡死
  if (sys_now() > wifi_input_time + 2000)
    WiFi_Input();
  
  // 回調函數中的data參數: 失敗時爲發送失敗的數據, 成功時爲收到的迴應
  // 發送數據幀時, data始終爲發送的內容
  if (WiFi_CheckTxBufferRetry(&wifi_tx_command, wifi_buffer_command))
  {
    WiFi_SendCommand(0, NULL, 0, wifi_tx_command.callback, wifi_tx_command.arg, wifi_tx_command.timeout, wifi_tx_command.retry - 1);
    printf("WiFi Command 0x%04x Timeout! Resend...\n", cmd->cmd_code);
  }
  WiFi_CheckTxBufferRetry(&wifi_tx_packet, wifi_buffer_packet);
}

/* 檢查發送緩衝區是否需要重傳 */
// data爲報告錯誤時需要傳給回調函數的數據
static uint8_t WiFi_CheckTxBufferRetry(WiFi_TxBuffer *tbuf, void *data)
{
  if (tbuf->busy && sys_now() > tbuf->start_time + tbuf->timeout) // 若超時時間到了
  {
    if (tbuf->retry != 0)
    {
      tbuf->busy = 0;
      return 1;
    }
    else
      WiFi_TxBufferComplete(tbuf, data, WIFI_STATUS_NORESP); // 超過最大重試次數, 向回調函數報告錯誤
  }
  return 0;
}

/* 與熱點斷開連接 */
void WiFi_Deauthenticate(uint16_t reason, WiFi_Callback callback, void *arg)
{
  uint8_t ret;
  void **p;
  WiFi_Cmd_Deauthenticate *cmd = (WiFi_Cmd_Deauthenticate *)wifi_buffer_command;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  ret = WiFi_GetBSSID(cmd->peer_sta_addr);
  if (!ret)
  {
    printf("WiFi_Deauthenticate: WiFi is not connected!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_FAIL);
    return;
  }
  
  p = malloc(2 * sizeof(void *));
  if (p == NULL)
  {
    printf("WiFi_Deauthenticate: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  
  cmd->reason_code = reason;
  WiFi_SendCommand(CMD_802_11_DEAUTHENTICATE, wifi_buffer_command, sizeof(WiFi_Cmd_Deauthenticate), WiFi_Deauthenticate_Callback, p, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

static void WiFi_Deauthenticate_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  free(arg);

  if (status == WIFI_STATUS_OK)
    memset(wifi_ssid_info.mac_addr, 0, 6);
  else
    printf("WiFi_Deauthenticate failed!\n");
  
  if (app_callback)
    app_callback(app_arg, data, status);
}

/* 固件下載 */
static void WiFi_DownloadFirmware(void)
{
  uint8_t helper_buf[64];
  const uint8_t *data;
  uint16_t curr;
  uint32_t len;
  
  // 下載helper固件
  data = WIFI_HELPER_ADDR;
  len = WIFI_HELPER_SIZE;
  while (len)
  {
    // 每次最多下載60字節固件內容
    curr = (len > 60) ? 60 : len;
    memcpy(helper_buf, &curr, 4); // 前4字節爲本次實際下載的數據量
    memcpy(helper_buf + 4, data, curr);
    
    if (len != WIFI_HELPER_SIZE) // 第一次發送數據前不需要等待
      WiFi_Wait(WIFI_INTSTATUS_DNLD, 0);
    WiFi_LowLevel_WriteData(1, wifi_port, helper_buf, sizeof(helper_buf), sizeof(helper_buf));
    len -= curr;
    data += curr;
  }
  memset(helper_buf, 0, 4);
  WiFi_LowLevel_WriteData(1, wifi_port, helper_buf, sizeof(helper_buf), sizeof(helper_buf)); // 以空數據包結束
  
  // 下載固件
  data = WIFI_FIRMWARE_ADDR; // 該地址必須是4字節對齊的, 否則將會引起傳輸錯誤
  len = WIFI_FIRMWARE_SIZE;
  while (len)
  {
    // 獲取本次應下載的字節數
    // 每次可以發送n>=curr字節的數據, 只能用一個CMD53命令發送, WiFi模塊只認前curr字節的數據
    WiFi_Wait(WIFI_INTSTATUS_DNLD, 0); // 發送前必須等待Download Ready
    while ((curr = WiFi_LowLevel_ReadReg(1, WIFI_SQREADBASEADDR0) | (WiFi_LowLevel_ReadReg(1, WIFI_SQREADBASEADDR1) << 8)) == 0);
    //printf("Required: %d bytes, Remaining: %d bytes\n", curr, len);
    
    if (curr & 1)
    {
      // 若curr爲奇數(如17), 則說明接收端出現了CRC校驗錯誤, 應重新傳送上一次的內容(這部分代碼省略)
      printf("Error: an odd size is invalid!\n");
      while (1);
    }
    if (curr > len)
      curr = len;
    
    // 發送固件數據
#ifdef WIFI_HIGHSPEED // 高速模式下不能使用多字節傳輸模式, 只能使用塊傳輸模式, 因此緩衝區要足夠大
    if (len < 32) // len爲緩衝區剩餘大小
    {
      // 若緩衝區空間不足一個數據塊, 則借用helper_buf
      memcpy(helper_buf, data, curr);
      WiFi_LowLevel_WriteData(1, wifi_port, helper_buf, curr, sizeof(helper_buf));
    }
    else
#endif
      WiFi_LowLevel_WriteData(1, wifi_port, data, curr, len);
    
    len -= curr;
    data += curr;
  }
  
  // 等待Firmware啓動
  WiFi_Wait(WIFI_INTSTATUS_DNLD, 0);
  while (WiFi_GetDataLength() != 0xfedc);
  printf("Firmware is successfully downloaded!\n");
}

/* 丟棄收到但因程序錯誤一直未處理的數據或命令 */
void WiFi_DiscardData(void)
{
  uint16_t len = WiFi_GetDataLength();
  uint8_t ret = WiFi_LowLevel_ReadData(1, wifi_port, wifi_buffer_rx, len, sizeof(wifi_buffer_rx));
  if (ret)
  {
    printf("Discarded %d bytes!\n", len);
    dump_data(wifi_buffer_rx, len);
  }
  else
    printf("Discarding %d bytes of data failed!\n", len);
}

/* 處理EAPOL認證幀 */
static void WiFi_EAPOLProcess(WiFi_DataRx *data)
{
  uint8_t i, ret;
  uint8_t random_b[10];
  uint16_t key_info;
  uint16_t key_data_len;
  uint32_t random_k[16];
  WiFi_EAPOLKeyFrame *packet_rx = (WiFi_EAPOLKeyFrame *)data->payload;
  WiFi_EAPOLKeyFrame *packet_tx;
  WiFi_KDE *kde;
  WiFi_KeyType key_type;
  WiFi_PTKB ptkb;
  
  key_info = ntohs(packet_rx->key_information);
  key_type = (WiFi_KeyType)(key_info & 0x07);
  if (key_type != WIFI_KEYTYPE_TKIP && key_type != WIFI_KEYTYPE_AES)
  {
    printf("Unsupported key descriptor version: %d\n", key_type);
    return;
  }
  
  switch (key_info & 0x23c8) // 移除與EAPOL-Key frame notation前六個參數無關的位
  {
    case 0x88:
      /* 4-way handshake Message 1: EAPOL-Key(0,0,1,0,P,0,...), P=1 */
      printf("Message 1 received!\n");
    
      /* 生成SNonce */
      // PRF-256(Random number, "Init Counter", Local MAC Address || Time)
      srand(sys_now());
      for (i = 0; i < sizeof(random_k) / sizeof(uint32_t); i++)
        random_k[i] = rand();
      memcpy(random_b, packet_rx->dest, sizeof(packet_rx->dest));
      *(uint32_t *)(random_b + sizeof(packet_rx->dest)) = sys_now();
      ret = PRF(random_k, sizeof(random_k), "Init Counter", random_b, sizeof(random_b), wifi_snonce, sizeof(wifi_snonce)); // PRF-256
      if (!ret)
      {
        printf("PRF: out of memory!\n"); // 遇到內存不足的情況, 只需要把STM32啓動文件(.s)中的堆空間大小Heap_Size調大就能解決問題
        break;
      }
      
      /* 生成PTK */
      // 較小的MAC地址在前, 較大的在後
      if (memcmp(packet_rx->dest, packet_rx->src, sizeof(packet_rx->src)) < 0)
        memcpy(ptkb.MAC, packet_rx->dest, sizeof(ptkb.MAC));
      else
      {
        memcpy(ptkb.MAC[0], packet_rx->src, sizeof(packet_rx->src));
        memcpy(ptkb.MAC[1], packet_rx->dest, sizeof(packet_rx->dest));
      }
      // 較小的隨機數在前, 較大的在後
      if (memcmp(packet_rx->key_nonce, wifi_snonce, sizeof(wifi_snonce)) < 0)
      {
        memcpy(ptkb.nonce[0], packet_rx->key_nonce, sizeof(packet_rx->key_nonce));
        memcpy(ptkb.nonce[1], wifi_snonce, sizeof(wifi_snonce));
      }
      else
      {
        memcpy(ptkb.nonce[0], wifi_snonce, sizeof(wifi_snonce));
        memcpy(ptkb.nonce[1], packet_rx->key_nonce, sizeof(packet_rx->key_nonce));
      }
      // wifi_psk是在設置密碼時生成的
      ret = PRF(wifi_psk, sizeof(wifi_psk), "Pairwise key expansion", &ptkb, sizeof(ptkb), &wifi_ptk, sizeof(wifi_ptk)); // PRF-512
      if (!ret)
      {
        printf("PRF: out of memory!\n");
        break;
      }
      
      /* 在待發送的Message 2中添加Key Data信息 */
      // https://community.arubanetworks.com/t5/Technology-Blog/A-closer-look-at-WiFi-Security-IE-Information-Elements/ba-p/198867
      // 使用WPA的熱點一定有WPA IE信息項,一定沒有RSN IE信息項
      // 使用WPA2的熱點一定有RSN IE信息項,可能有WPA IE信息項
      packet_tx = (WiFi_EAPOLKeyFrame *)WiFi_GetPacketBuffer();
      kde = (WiFi_KDE *)packet_tx->key_data;
      if (wifi_ssid_info.rsn.header.type)
      {
        // 路由器提供了RSN IE信息項時可直接複製
        //printf("RSN IE copied!\n");
        kde->type = WIFI_MRVLIETYPES_RSNPARAMSET;
        kde->length = wifi_ssid_info.rsn.header.length;
        memcpy(kde->oui, wifi_ssid_info.rsn.rsn, wifi_ssid_info.rsn.header.length);
        key_data_len = sizeof(kde->type) + sizeof(kde->length) + kde->length;
      }
      else if (wifi_ssid_info.wpa.header.type)
      {
        // 路由器提供了WPA IE信息項時可直接複製
        //printf("WPA IE copied!\n");
        kde->type = WIFI_MRVLIETYPES_VENDORPARAMSET;
        kde->length = wifi_ssid_info.wpa.header.length;
        memcpy(kde->oui, wifi_ssid_info.wpa.vendor, wifi_ssid_info.wpa.header.length);
        key_data_len = sizeof(kde->type) + sizeof(kde->length) + kde->length;
      }
      else
      {
        printf("IE not copied!\n");
        key_data_len = 0;
      }
      
      /* 發送Message 2: EAPOL-Key(0,1,0,0,P,0,...) */
      WiFi_SendEAPOLResponse(packet_rx, 0x108 | key_type, NULL, key_data_len, WiFi_EAPOLProcess_Callback, (void *)2);
      break;
    case 0x1c8: // WPA MSG3
    case 0x3c8: // WPA2 MSG3
      /* 4-way handshake Message 3: EAPOL-Key(1,1,1,1,P,0,...) */
      printf("Message 3 received!\n");
      if (!WiFi_CheckMIC(packet_rx, data->rx_packet_length)) // prevents undetected modification of message 3
      {
        printf("Message 3 is corrupted!\n");
        break;
      }
      if (WiFi_IsCommandBusy()) // 如果命令發送緩衝區被佔用, 則丟棄本次的Msg3不作出迴應, 等待下一個Msg3
        break;
      
      // WPA認證時, 只將PTK發給固件
      // WPA2認證時, 需要獲取GTK並將PTK和GTK發送給固件
      ret = WiFi_ExtractGTK(packet_rx);
      WiFi_SetKeyMaterial(key_type, ret ? 2 : 1, WiFi_EAPOLProcess_Callback, (void *)(ret ? 2 : 3));
      
      /* 發送Message 4: EAPOL-Key(1,1,0,0,P,0,...) */
      WiFi_SendEAPOLResponse(packet_rx, 0x308 | key_type, NULL, 0, WiFi_EAPOLProcess_Callback, (void *)4);
      break;
    case 0x380:
      /* Group key handshake Message 1: EAPOL-Key(1,1,1,0,G,0,...), G=0 */
      printf("Group key handshake!\n");
      if (!WiFi_CheckMIC(packet_rx, data->rx_packet_length))
        break;
      if (WiFi_IsCommandBusy())
        break;
      
      /* 將新的GTK發給固件 */
      if (!WiFi_ExtractGTK(packet_rx))
      {
        printf("Extracting GTK failed!\n");
        break;
      }
      WiFi_SetKeyMaterial(key_type, 2, WiFi_EAPOLProcess_Callback, (void *)1); // 同時發送PTK和GTK, 不能只發GTK, 否則固件中的密鑰無法得到更新
      
      /* 發送Message 2: EAPOL-Key(1,1,0,0,G,0,...) */
      WiFi_SendEAPOLResponse(packet_rx, 0x300 | key_type, NULL, 0, WiFi_EAPOLProcess_Callback, (void *)2);
      break;
    default:
      printf("Unhandled EAPOL frame! key_info=0x%04x\n", key_info);
      dump_data(packet_rx, data->rx_packet_length);
  }
}

static void WiFi_EAPOLProcess_Callback(void *arg, void *data, WiFi_Status status)
{
  WiFi_SDIOFrameHeader *header = (WiFi_SDIOFrameHeader *)data;
  if (status == WIFI_STATUS_OK)
  {
    if (header->type == WIFI_SDIOFRAME_COMMAND)
    {
      switch ((uint32_t)arg)
      {
        case 1:
          printf("GTK set!\n");
          break;
        case 2:
          printf("PTK & GTK set!\n");
          break;
        case 3:
          printf("PTK set!\n");
          break;
      }
    }
    else if (header->type == WIFI_SDIOFRAME_DATA)
    {
      printf("Message %d sent!\n", (uint32_t)arg);
      if ((uint32_t)arg == 4)
        WiFi_AuthenticationCompleteHandler(); // 有了PTK就可以發送廣播幀了, 所以在這裏調用callback比較合適
    }
  }
}

/* 用KEK密鑰對key_data數據解密, 並提取出GTK密鑰 */
static uint8_t WiFi_ExtractGTK(const WiFi_EAPOLKeyFrame *packet_rx)
{
  uint16_t key_info = ntohs(packet_rx->key_information);
  uint16_t key_len = ntohs(packet_rx->key_length);
  uint16_t keydata_len = ntohs(packet_rx->key_data_length);
  WiFi_KDE *kde;
  WiFi_KeyType key_type = (WiFi_KeyType)(key_info & 0x07);
  
  // 解密key_data字段
  kde = (WiFi_KDE *)wifi_buffer_command;
  if (key_type == WIFI_KEYTYPE_TKIP)
    ARC4_decrypt_keydata(wifi_ptk.KEK, packet_rx->key_iv, packet_rx->key_data, keydata_len, wifi_buffer_command);
  else if (key_type == WIFI_KEYTYPE_AES)
    keydata_len = AES_unwrap(wifi_ptk.KEK, packet_rx->key_data, keydata_len, wifi_buffer_command);
  else
    return 0;
  
  if (keydata_len == key_len)
  {
    // 如果認證類型爲WPA, 則解密之後的keydata內容就是GTK
    memcpy(&wifi_gtk, wifi_buffer_command, keydata_len);
    return 1;
  }
  else
  {
    // 如果認證類型爲WPA2, 則解密後的keydata內容是一些KDE結構的數據, GTK在其中的一個KDE裏面
    while (kde->length != 0) // 搜索長度不爲0的KDE結構
    {
      if (kde->type == 0xdd && kde->data_type == 1 && kde->length - 6 == key_len) // GTK KDE
      {
        memcpy(wifi_gtk.TK, kde->data + 2, key_len);
        return 1;
      }
      kde = (WiFi_KDE *)((uint8_t *)kde + kde->length + 2);
      
      if (((uint8_t *)kde - wifi_buffer_command) >= keydata_len - 1) // 保證key->length落在有效數據區域內
        break;
    }
  }
  
  return 0;
}

/* 獲取所連熱點的MAC地址 */
uint8_t WiFi_GetBSSID(uint8_t mac_addr[6])
{
  uint8_t i;
  memcpy(mac_addr, wifi_ssid_info.mac_addr, 6);
  for (i = 0; i < 6; i++)
  {
    if (mac_addr[i] != 0)
      break;
  }
  return i != 6; // 返回值表示MAC地址是否不全爲0
}

/* 獲取需要接收的數據大小 */
uint16_t WiFi_GetDataLength(void)
{
  // 讀Scratch pad 4寄存器的低16位
  return WiFi_LowLevel_ReadReg(1, WIFI_SCRATCHPAD4_0) | (WiFi_LowLevel_ReadReg(1, WIFI_SCRATCHPAD4_1) << 8);
}

/* 獲取MAC地址 */
// callback不能爲NULL
void WiFi_GetMACAddress(WiFi_Callback callback, void *arg)
{
  void **p;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  p = malloc(2 * sizeof(void *));
  if (p == NULL)
  {
    printf("WiFi_GetMACAddress: malloc failed!\n");
    callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  
  WiFi_MACAddr(NULL, WIFI_ACT_GET, WiFi_GetMACAddress_Callback, p);
}

static void WiFi_GetMACAddress_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  WiFi_Cmd_MACAddr *cmd = (WiFi_Cmd_MACAddr *)data;
  free(arg);
  
  if (status == WIFI_STATUS_OK)
    app_callback(app_arg, cmd->mac_addr, status);
  else
  {
    printf("WiFi_GetMACAddress error!\n");
    app_callback(app_arg, NULL, status);
  }
}

/* 請求發送新的數據幀 */
uint8_t *WiFi_GetPacketBuffer(void)
{
  WiFi_DataTx *data = (WiFi_DataTx *)wifi_buffer_packet;
  WiFi_WaitForLastTask(); // 使用前必須確保緩衝區未被佔用
  return data->payload;
}

/* 獲取收到的數據幀的內容和大小 */
const uint8_t *WiFi_GetReceivedPacket(uint16_t *len)
{
  WiFi_DataRx *data = (WiFi_DataRx *)wifi_buffer_rx;
  if (data->header.type == WIFI_SDIOFRAME_DATA)
  {
    *len = data->rx_packet_length;
    return data->payload;
  }
  else
    return NULL;
}

/* 獲取熱點的認證類型 */
WiFi_SecurityType WiFi_GetSecurityType(const WiFi_SSIDInfo *info)
{
  if (info->cap_info & WIFI_CAPABILITY_PRIVACY)
  {
    if (info->rsn.header.type)
      return WIFI_SECURITYTYPE_WPA2;
    else if (info->wpa.header.type)
      return WIFI_SECURITYTYPE_WPA;
    else
      return WIFI_SECURITYTYPE_WEP;
  }
  else
    return WIFI_SECURITYTYPE_NONE;
}

/* 初始化WiFi模塊 */
void WiFi_Init(void)
{
  // 初始化底層寄存器
  WiFi_LowLevel_Init();
  WiFi_ShowCIS();
  
  // 初始化Function 1
  WiFi_LowLevel_WriteReg(0, SDIO_CCCR_IOEN, SDIO_CCCR_IOEN_IOE1); // IOE1=1 (Enable Function)
  while ((WiFi_LowLevel_ReadReg(0, SDIO_CCCR_IORDY) & SDIO_CCCR_IORDY_IOR1) == 0); // 等待IOR1=1 (I/O Function Ready)
  WiFi_LowLevel_WriteReg(0, SDIO_CCCR_INTEN, SDIO_CCCR_INTEN_IENM | SDIO_CCCR_INTEN_IEN1); // 打開SDIO中斷請求
  WiFi_LowLevel_WriteReg(1, WIFI_INTMASK, WIFI_INTMASK_HOSTINTMASK); // 利用中斷標誌位來判定是否有數據要讀取, 可靠性更高
  
  // 下載固件
  wifi_port = WiFi_LowLevel_ReadReg(1, WIFI_IOPORT0) | (WiFi_LowLevel_ReadReg(1, WIFI_IOPORT1) << 8) | (WiFi_LowLevel_ReadReg(1, WIFI_IOPORT2) << 16);
  WiFi_LowLevel_SetBlockSize(1, 32);
  WiFi_DownloadFirmware();
  WiFi_LowLevel_SetBlockSize(1, 256);
}

void WiFi_Input(void)
{
  uint8_t ret, status;
  uint16_t len;
  WiFi_SDIOFrameHeader *rx_header = (WiFi_SDIOFrameHeader *)wifi_buffer_rx;
  WiFi_DataRx *rx_packet = (WiFi_DataRx *)wifi_buffer_rx;
  WiFi_CommandHeader *rx_cmd = (WiFi_CommandHeader *)wifi_buffer_rx;
  WiFi_CommandHeader *tx_cmd = (WiFi_CommandHeader *)wifi_buffer_command;
  
  wifi_input_time = sys_now();
  status = WiFi_LowLevel_ReadReg(1, WIFI_INTSTATUS); // 獲取需要處理的中斷標誌位
  if (status == 0)
    return;
  WiFi_LowLevel_WriteReg(1, WIFI_INTSTATUS, WIFI_INTSTATUS_ALL & ~status); // 必須先清除這些標誌位, 然後再進行處理, 這樣可以避免清除掉處理過程中新來的中斷
  
  if (status & WIFI_INTSTATUS_DNLD)
  {
    // 命令幀收到確認
    if (wifi_tx_command.busy && wifi_tx_command.ready == 0)
    {
#ifdef WIFI_DISPLAY_RESPTIME
      printf("CMD 0x%04x ACK at %dms\n", tx_cmd->cmd_code, sys_now() - wifi_tx_command.start_time);
#endif
      wifi_tx_command.ready = 1;
    }
    
    // 數據幀發送成功並收到確認
    if (wifi_tx_packet.busy)
    {
#ifdef WIFI_DISPLAY_RESPTIME
      printf("Packet ACK at %dms\n", sys_now() - wifi_tx_packet.start_time);
#endif
      WiFi_TxBufferComplete(&wifi_tx_packet, wifi_buffer_packet, WIFI_STATUS_OK);
    }
  }
  
  if (status & WIFI_INTSTATUS_UPLD)
  {
    len = WiFi_GetDataLength();
    ret = WiFi_LowLevel_ReadData(1, wifi_port, wifi_buffer_rx, len, sizeof(wifi_buffer_rx));
    if (ret)
    {
      switch (rx_header->type)
      {
        case WIFI_SDIOFRAME_DATA:
          // 收到以太網數據幀
          if (rx_packet->rx_packet_length >= 14 && rx_packet->payload[12] == 0x88 && rx_packet->payload[13] == 0x8e)
            WiFi_EAPOLProcess(rx_packet); // 處理0x888e類型的EAPOL認證幀
          else
            WiFi_PacketHandler(rx_packet);
          break;
        case WIFI_SDIOFRAME_COMMAND:
          // 收到命令迴應幀
          if (rx_cmd->seq_num == tx_cmd->seq_num) // 序號相符
          {
  #ifdef WIFI_DISPLAY_RESPTIME
            printf("CMDRESP 0x%04x at %dms\n", rx_cmd->cmd_code, sys_now() - wifi_tx_command.start_time);
  #endif
            WiFi_TxBufferComplete(&wifi_tx_command, wifi_buffer_rx, WIFI_STATUS_OK);
          }
          break;
        case WIFI_SDIOFRAME_EVENT:
          // 收到事件幀
          WiFi_EventHandler((WiFi_Event *)wifi_buffer_rx); // 調用事件處理回調函數
      }
    }
  }
}

/* 發送命令幀前, 必須保證命令發送緩衝區爲空 */
uint8_t WiFi_IsCommandBusy(void)
{
  return wifi_tx_command.busy;
}

/* 加入Ad-Hoc網絡 */
void WiFi_JoinADHOC(const char *ssid, WiFi_Callback callback, void *arg)
{
  void **p;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  p = malloc(2 * sizeof(void *));
  if (p == NULL)
  {
    printf("WiFi_JoinADHOC: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  
  WiFi_ScanSSID(ssid, &wifi_ssid_info, WiFi_JoinADHOC_Callback, p);
}

static void WiFi_JoinADHOC_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  WiFi_Cmd_ADHOCJoin *cmd;
  
  if (status != WIFI_STATUS_OK)
  {
    printf("WiFi_JoinADHOC error!\n");
    free(arg);
    if (app_callback)
      app_callback(app_arg, data, status);
    return;
  }
  
  switch (WiFi_GetCommandCode(data))
  {
    case CMD_802_11_SCAN:
      cmd = (WiFi_Cmd_ADHOCJoin *)wifi_buffer_command;
      memcpy(cmd->bssid, wifi_ssid_info.mac_addr, sizeof(cmd->bssid));
      strncpy((char *)cmd->ssid, (char *)wifi_ssid_info.ssid.ssid, sizeof(cmd->ssid)); // strncpy會將未使用的區域填充爲0
      cmd->bss_type = WIFI_BSS_ANY; // recommended for use when joining Ad-Hoc networks
      cmd->bcn_period = wifi_ssid_info.bcn_period;
      cmd->dtim_period = 1;
      memset(cmd->timestamp, 0, sizeof(cmd->timestamp) + sizeof(cmd->start_ts));
      cmd->ds_param_set.header.type = WIFI_MRVLIETYPES_PHYPARAMDSSET;
      cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);
      cmd->ds_param_set.channel = wifi_ssid_info.channel;
      cmd->reserved1 = 0;
      cmd->ibss_param_set.header.type = WIFI_MRVLIETYPES_IBSSPARAMSET;
      cmd->ibss_param_set.header.length = TLV_PAYLOADLEN(cmd->ibss_param_set);
      cmd->ibss_param_set.atim_window = 0;
      cmd->reserved2 = 0;
      cmd->cap_info = wifi_ssid_info.cap_info;
      memcpy(cmd->data_rates, wifi_ssid_info.rates.rates, sizeof(cmd->data_rates));
      cmd->reserved3 = 0;
      WiFi_SendCommand(CMD_802_11_AD_HOC_JOIN, wifi_buffer_command, sizeof(WiFi_Cmd_ADHOCJoin), WiFi_JoinADHOC_Callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
      break;
    case CMD_802_11_AD_HOC_JOIN:
      free(arg);
      cmd = (WiFi_Cmd_ADHOCJoin *)data;
      if (app_callback)
        app_callback(app_arg, data, (cmd->header.result == 0) ? WIFI_STATUS_OK : WIFI_STATUS_FAIL);
      break;
  }
}

/* 加入帶有密碼的Ad-Hoc網絡 */
void WiFi_JoinADHOCEx(const WiFi_Connection *conn, int8_t max_retry, WiFi_Callback callback, void *arg)
{
  int8_t *pmax_retry;
  uint16_t ssid_len;
  void **p;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  if (conn->security == WIFI_SECURITYTYPE_WPA || conn->security == WIFI_SECURITYTYPE_WPA2)
  {
    // 88W8686雖然能夠連上電腦創建的WPA2認證類型的Ad-Hoc熱點, 也能完成EAPOL認證
    // 但是同時也會自己創建一個WEP型的同名Ad-Hoc熱點, 使通信無法正常進行
    printf("WiFi_JoinADHOCEx: WPA is not supported!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_INVALID);
    return;
  }
  
  ssid_len = strlen(conn->ssid);
  p = malloc(2 * sizeof(void *) + sizeof(int8_t) + ssid_len + 1);
  if (p == NULL)
  {
    printf("WiFi_JoinADHOCEx: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  pmax_retry = (int8_t *)(p + 2);
  *pmax_retry = max_retry;
  memcpy(pmax_retry + 1, conn->ssid, ssid_len + 1);
  
  if (conn->security == WIFI_SECURITYTYPE_WEP)
    WiFi_SetWEP(WIFI_ACT_ADD, conn->password, WiFi_JoinADHOCEx_Callback, p);
  else
    WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_JoinADHOCEx_Callback, p);
}

static void WiFi_JoinADHOCEx_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  int8_t *pmax_retry = (int8_t *)(p + 2);
  char *ssid = (char *)(pmax_retry + 1);
  uint16_t cmd_code = WiFi_GetCommandCode(data);
  
  if (cmd_code == CMD_802_11_AD_HOC_JOIN || cmd_code == CMD_802_11_SCAN)
  {
    if (cmd_code == CMD_802_11_AD_HOC_JOIN && status == WIFI_STATUS_OK)
    {
      free(arg);
      if (app_callback)
        app_callback(app_arg, data, status);
      return;
    }
    else
    {
      if (*pmax_retry != 0)
      {
        if (*pmax_retry != -1)
          (*pmax_retry)--;
        cmd_code = CMD_MAC_CONTROL;
        status = WIFI_STATUS_OK;
      }
    }
  }
  
  if (status != WIFI_STATUS_OK)
  {
    printf("WiFi_JoinADHOCEx error!\n");
    free(arg);
    if (app_callback)
      app_callback(app_arg, data, status);
    return;
  }
  
  switch (cmd_code)
  {
    case CMD_802_11_SET_WEP:
      WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_WEP | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_JoinADHOCEx_Callback, arg);
      break;
    case CMD_MAC_CONTROL:
      WiFi_JoinADHOC(ssid, WiFi_JoinADHOCEx_Callback, arg);
  }
}

/* 獲取或設置WPA密鑰 */
void WiFi_KeyMaterial(WiFi_CommandAction action, MrvlIETypes_KeyParamSet_t *key, uint8_t key_count, WiFi_Callback callback, void *arg)
{
  uint8_t i;
  WiFi_Cmd_KeyMaterial *cmd = (WiFi_Cmd_KeyMaterial *)wifi_buffer_command;
  MrvlIETypes_KeyParamSet_t *pkey = &cmd->keys;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  cmd->action = action;
  if (action == WIFI_ACT_SET)
  {
    for (i = 0; i < key_count; i++)
    {
      key[i].header.type = WIFI_MRVLIETYPES_KEYPARAMSET;
      key[i].header.length = sizeof(MrvlIETypes_KeyParamSet_t) - sizeof(key[i].header) - sizeof(key[i].key) + key->key_len;
      memcpy(pkey, key + i, TLV_STRUCTLEN(key[i]));
      pkey = (MrvlIETypes_KeyParamSet_t *)TLV_NEXT(pkey);
    }
  }
  WiFi_SendCommand(CMD_802_11_KEY_MATERIAL, wifi_buffer_command, (uint8_t *)pkey - wifi_buffer_command, callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

/* 獲取或設置MAC地址 */
void WiFi_MACAddr(const uint8_t newaddr[6], WiFi_CommandAction action, WiFi_Callback callback, void *arg)
{
  WiFi_Cmd_MACAddr *cmd = (WiFi_Cmd_MACAddr *)wifi_buffer_command;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  cmd->action = action;
  if (action == WIFI_ACT_SET)
    memcpy(cmd->mac_addr, newaddr, 6);
  else
    memset(cmd->mac_addr, 0, 6);
  WiFi_SendCommand(CMD_802_11_MAC_ADDR, wifi_buffer_command, sizeof(WiFi_Cmd_MACAddr), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

/* 配置MAC */
void WiFi_MACControl(uint16_t action, WiFi_Callback callback, void *arg)
{
  WiFi_Cmd_MACCtrl *cmd = (WiFi_Cmd_MACCtrl *)wifi_buffer_command;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  cmd->action = action;
  cmd->reserved = 0;
  WiFi_SendCommand(CMD_MAC_CONTROL, wifi_buffer_command, sizeof(WiFi_Cmd_MACCtrl), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

/* 掃描全部熱點 (僅顯示) */
void WiFi_Scan(WiFi_Callback callback, void *arg)
{
  uint8_t i;
  void **p;
  WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)wifi_buffer_command; // 要發送的命令
  MrvlIETypes_ChanListParamSet_t *chanlist = (MrvlIETypes_ChanListParamSet_t *)(cmd + 1); // 這裏的+1指的是前進sizeof(指針類型)個地址單元, 而非只前進1個地址單元
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  p = malloc(2 * sizeof(void *));
  if (p == NULL)
  {
    printf("WiFi_Scan: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  
  cmd->bss_type = WIFI_BSS_ANY;
  memset(cmd->bss_id, 0, sizeof(cmd->bss_id));
  
  // 通道的基本參數
  chanlist->header.type = WIFI_MRVLIETYPES_CHANLISTPARAMSET;
  chanlist->header.length = 4 * sizeof(chanlist->channels);
  for (i = 0; i < 4; i++) // 先掃描前4個通道 (i作下標, i+1纔是通道號)
  {
    chanlist->channels[i].band_config_type = 0; // 2.4GHz band, 20MHz channel width
    chanlist->channels[i].chan_number = i + 1; // 通道號
    chanlist->channels[i].scan_type = 0;
    chanlist->channels[i].min_scan_time = 0;
    chanlist->channels[i].max_scan_time = 100;
  }
  
  WiFi_SendCommand(CMD_802_11_SCAN, wifi_buffer_command, sizeof(WiFi_CmdRequest_Scan) + TLV_STRUCTLEN(*chanlist), WiFi_Scan_Callback, p, 3000, WIFI_DEFAULT_MAXRETRY);
}

static void WiFi_Scan_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  
  uint8_t i, j, n;
  WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)wifi_buffer_command;
  MrvlIETypes_ChanListParamSet_t *chanlist = (MrvlIETypes_ChanListParamSet_t *)(cmd + 1);
  
  uint8_t ssid[33], channel;
  uint16_t ie_size;
  WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)data;
  WiFi_BssDescSet *bss_desc_set;
  WiFi_SecurityType security;
  WiFi_Vendor *vendor;
  IEEEType *ie_params;
  //MrvlIETypes_TsfTimestamp_t *tft_table;
  
  if (status != WIFI_STATUS_OK)
  {
    printf("WiFi_Scan error!\n");
    free(arg);
    if (app_callback)
      app_callback(app_arg, data, status);
    return;
  }
  
  // 發送掃描接下來的4個通道的命令
  j = chanlist->channels[0].chan_number + 4;
  if (j < 14)
  {
    if (j == 13)
      n = 2;
    else
      n = 4;
    
    chanlist->header.length = n * sizeof(chanlist->channels);
    for (i = 0; i < n; i++)
      chanlist->channels[i].chan_number = i + j;
    WiFi_SendCommand(CMD_802_11_SCAN, wifi_buffer_command, sizeof(WiFi_CmdRequest_Scan) + TLV_STRUCTLEN(*chanlist), WiFi_Scan_Callback, arg, 3000, WIFI_DEFAULT_MAXRETRY);
  }
  else
    n = 0;
  
  // 顯示本次掃描結果, num_of_set爲熱點數
  if (resp->num_of_set > 0)
  {
    bss_desc_set = (WiFi_BssDescSet *)(resp + 1);
    for (i = 0; i < resp->num_of_set; i++)
    {
      security = WIFI_SECURITYTYPE_WEP;
      ie_params = &bss_desc_set->ie_parameters;
      ie_size = bss_desc_set->ie_length - (sizeof(WiFi_BssDescSet) - sizeof(bss_desc_set->ie_length) - sizeof(bss_desc_set->ie_parameters));
      while (ie_size > 0)
      {
        switch (ie_params->header.type)
        {
          case WIFI_MRVLIETYPES_SSIDPARAMSET:
            // SSID名稱
            memcpy(ssid, ie_params->data, ie_params->header.length);
            ssid[ie_params->header.length] = '\0';
            break;
          case WIFI_MRVLIETYPES_PHYPARAMDSSET:
            // 通道號
            channel = ie_params->data[0];
            break;
          case WIFI_MRVLIETYPES_RSNPARAMSET:
            security = WIFI_SECURITYTYPE_WPA2;
            break;
          case WIFI_MRVLIETYPES_VENDORPARAMSET:
            if (security != WIFI_SECURITYTYPE_WPA2)
            {
              vendor = (WiFi_Vendor *)ie_params->data;
              if (vendor->oui[0] == 0x00 && vendor->oui[1] == 0x50 && vendor->oui[2] == 0xf2 && vendor->oui_type == 0x01)
                security = WIFI_SECURITYTYPE_WPA;
            }
            break;
        }
        ie_size -= TLV_STRUCTLEN(*ie_params);
        ie_params = (IEEEType *)TLV_NEXT(ie_params);
      }
      if ((bss_desc_set->cap_info & WIFI_CAPABILITY_PRIVACY) == 0)
        security = WIFI_SECURITYTYPE_NONE;
      
      printf("SSID '%s', ", ssid); // 熱點名稱
      printf("MAC %02X:%02X:%02X:%02X:%02X:%02X, ", bss_desc_set->bssid[0], bss_desc_set->bssid[1], bss_desc_set->bssid[2], bss_desc_set->bssid[3], bss_desc_set->bssid[4], bss_desc_set->bssid[5]); // MAC地址
      printf("RSSI %d, Channel %d\n", bss_desc_set->rssi, channel); // 信號強度和通道號
      //printf("  Timestamp %lld, Beacon Interval %d\n", bss_desc_set->pkt_time_stamp, bss_desc_set->bcn_interval);
      
      printf("  Capability: 0x%04x (Security: ", bss_desc_set->cap_info);
      switch (security)
      {
        case WIFI_SECURITYTYPE_NONE:
          printf("Unsecured");
          break;
        case WIFI_SECURITYTYPE_WEP:
          printf("WEP");
          break;
        case WIFI_SECURITYTYPE_WPA:
          printf("WPA");
          break;
        case WIFI_SECURITYTYPE_WPA2:
          printf("WPA2");
          break;
      }
      
      printf(", Mode: ");
      if (bss_desc_set->cap_info & WIFI_CAPABILITY_IBSS)
        printf("Ad-Hoc");
      else
        printf("Infrastructure");
      printf(")\n");
      
      // 轉向下一個熱點信息
      bss_desc_set = (WiFi_BssDescSet *)((uint8_t *)bss_desc_set + sizeof(bss_desc_set->ie_length) + bss_desc_set->ie_length);
    }
    
    // resp->buf_size就是bss_desc_set的總大小
    // 因此tft_table == buffer + sizeof(WiFi_CmdResponse_Scan) + resp->buf_size
    /*
    tft_table = (MrvlIETypes_TsfTimestamp_t *)bss_desc_set;
    if (tft_table->header.type == WIFI_MRVLIETYPES_TSFTIMESTAMP && tft_table->header.length == resp->num_of_set * sizeof(uint64_t))
    {
      printf("Timestamps: ");
      for (i = 0; i < resp->num_of_set; i++)
        printf("%lld ", tft_table->tsf_table[i]);
      printf("\n");
    }
    */
    
    // TSF timestamp table是整個數據的末尾, 後面沒有Channel/band table
    //if (((uint8_t *)tft_table - (uint8_t *)data) + TLV_STRUCTLEN(*tft_table) == resp->header.frame_header.length)
    //  printf("data end!\n");
  }
  
  // 掃描完畢時調用回調函數
  if (n == 0)
  {
    free(arg);
    if (app_callback)
      app_callback(app_arg, data, status);
  }
}

/* 獲取指定名稱的熱點的詳細信息 */
void WiFi_ScanSSID(const char *ssid, WiFi_SSIDInfo *info, WiFi_Callback callback, void *arg)
{
  uint8_t i;
  void **p;
  MrvlIETypes_ChanListParamSet_t *chan_list;
  WiFi_CmdRequest_Scan *cmd = (WiFi_CmdRequest_Scan *)wifi_buffer_command;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  p = malloc(3 * sizeof(void *));
  if (p == NULL)
  {
    printf("WiFi_ScanSSID: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  p[2] = info;
  memset(info, 0, sizeof(WiFi_SSIDInfo)); // 將整個info結構體清零
  
  cmd->bss_type = WIFI_BSS_ANY;
  memset(cmd->bss_id, 0, sizeof(cmd->bss_id));
  
  // 給info->ssid成員賦值
  info->ssid.header.type = WIFI_MRVLIETYPES_SSIDPARAMSET;
  info->ssid.header.length = strlen(ssid);
  memcpy(info->ssid.ssid, ssid, info->ssid.header.length);
  memcpy(cmd + 1, &info->ssid, TLV_STRUCTLEN(info->ssid)); // 把info->ssid複製到待發送的命令內容中
  
  chan_list = (MrvlIETypes_ChanListParamSet_t *)((uint8_t *)(cmd + 1) + TLV_STRUCTLEN(info->ssid));
  chan_list->header.type = WIFI_MRVLIETYPES_CHANLISTPARAMSET;
  chan_list->header.length = 14 * sizeof(chan_list->channels); // 一次性掃描14個通道
  for (i = 0; i < 14; i++) // i作下標, i+1纔是通道號
  {
    chan_list->channels[i].band_config_type = 0;
    chan_list->channels[i].chan_number = i + 1;
    chan_list->channels[i].scan_type = 0;
    chan_list->channels[i].min_scan_time = 0;
    chan_list->channels[i].max_scan_time = 100;
  }
  
  WiFi_SendCommand(CMD_802_11_SCAN, wifi_buffer_command, ((uint8_t *)chan_list - wifi_buffer_command) + TLV_STRUCTLEN(*chan_list), WiFi_ScanSSID_Callback, p, 3000, WIFI_DEFAULT_MAXRETRY);
}

static void WiFi_ScanSSID_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  WiFi_SSIDInfo *info = p[2];
  
  uint16_t ie_size;
  WiFi_CmdResponse_Scan *resp = (WiFi_CmdResponse_Scan *)data;
  WiFi_BssDescSet *bss_desc_set = (WiFi_BssDescSet *)(resp + 1);
  IEEEType *ie_params;
  WiFi_Vendor *vendor;
  
  free(arg);
  if (status != WIFI_STATUS_OK)
  {
    printf("WiFi_ScanSSID error!\n");
    if (app_callback)
      app_callback(app_arg, data, status);
    return;
  }
  
  if (resp->num_of_set == 0)
  {
    // 未找到指定的AP熱點, 此時info結構體中除了ssid成員外, 其餘的成員均爲0
    // resp中的內容到了num_of_set成員後就沒有了
    printf("No SSID!\n");
    if (app_callback)
      app_callback(app_arg, data, WIFI_STATUS_NOTFOUND);
    return;
  }
  
  // bss_desc_set以掃描到的第一個信息項爲準
  memcpy(info->mac_addr, bss_desc_set->bssid, sizeof(info->mac_addr));
  info->cap_info = bss_desc_set->cap_info;
  info->bcn_period = bss_desc_set->bcn_interval;
  
  // 若info->xxx.header.type=0, 則表明沒有該項的信息 (除SSID結構體外, 因爲SSID的type=WIFI_MRVLIETYPES_SSIDPARAMSET=0)
  ie_params = &bss_desc_set->ie_parameters;
  ie_size = bss_desc_set->ie_length - (sizeof(WiFi_BssDescSet) - sizeof(bss_desc_set->ie_length) - sizeof(bss_desc_set->ie_parameters)); // 所有IEEE_Type數據的總大小
  while (ie_size > 0)
  {
    switch (ie_params->header.type)
    {
      case WIFI_MRVLIETYPES_RATESPARAMSET:
        // 速率
        WiFi_TranslateTLV((MrvlIEType *)&info->rates, ie_params, sizeof(info->rates.rates));
        break;
      case WIFI_MRVLIETYPES_PHYPARAMDSSET:
        // 通道號
        info->channel = ie_params->data[0];
        break;
      case WIFI_MRVLIETYPES_RSNPARAMSET:
        // 通常只有一個RSN信息 (與WPA2相關)
        WiFi_TranslateTLV((MrvlIEType *)&info->rsn, ie_params, sizeof(info->rsn.rsn));
        break;
      case WIFI_MRVLIETYPES_VENDORPARAMSET:
        // 通常會有多項VENDOR信息 (與WPA相關)
        vendor = (WiFi_Vendor *)ie_params->data;
        if (vendor->oui[0] == 0x00 && vendor->oui[1] == 0x50 && vendor->oui[2] == 0xf2)
        {
          switch (vendor->oui_type)
          {
            case 0x01:
              // wpa_oui
              WiFi_TranslateTLV((MrvlIEType *)&info->wpa, ie_params, sizeof(info->wpa.vendor));
              break;
            case 0x02:
              // wmm_oui
              if (ie_params->header.length == 24) // 合法大小
                WiFi_TranslateTLV((MrvlIEType *)&info->wwm, ie_params, sizeof(info->wwm.vendor));
              break;
            case 0x04:
              // wps_oui
              WiFi_TranslateTLV((MrvlIEType *)&info->wps, ie_params, sizeof(info->wps.vendor));
              break;
          }
        }
        break;
    }
    
    // 轉向下一個TLV
    ie_size -= TLV_STRUCTLEN(*ie_params);
    ie_params = (IEEEType *)TLV_NEXT(ie_params);
  }
  
  if (app_callback)
    app_callback(app_arg, data, status);
}

/* 發送WiFi命令, 收到迴應或超時時調用callback回調函數 */
// size=0表示data包含完整的命令數據, 且code和size可直接從data中獲取(用於重發)
// retry可以爲0(第一次失敗時就直接調用回調函數, 不再重試), 但timeout不能爲0(否則收到迴應前會誤認爲超時並調用回調函數)
//
// 無操作系統的環境下只能使用非阻塞方式執行WiFi命令, 並通過回調函數通知命令執行的結果 (回調函數應保證能夠被調用並只調用一次)
// 如果有操作系統, 某個任務想要以阻塞方式執行WiFi命令, 可以在該函數裏面添加發送命令前阻塞等待表示命令通道是否可用的0-1信號量的代碼
// 當命令通道可用時, 使信號量的值爲1並喚醒其中一個等待發送命令的任務, 發送命令後繼續阻塞等待迴應, 調用回調函數並根據命令執行結果(成功還是失敗)決定函數的返回值
void WiFi_SendCommand(uint16_t code, const void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout, uint8_t max_retry)
{
  static uint16_t seq_num = 0;
  WiFi_CommandHeader *cmdhdr = (WiFi_CommandHeader *)wifi_buffer_command;
  
  if (WiFi_CheckCommandBusy(callback, arg)) // 發送命令前必須確保之前的命令已經發送完畢
    return;
  
  // 直接發送命令: WiFi_SendCommand(0, data, 0, ...)
  // 填充命令頭併發送命令: WiFi_SendCommand(code, data, size, ...)
  // 重試上次命令: WiFi_SendCommand(0, NULL, 0, ...)
  if (data != NULL && data != wifi_buffer_command)
    memmove(wifi_buffer_command, data, (size != 0) ? size : cmdhdr->frame_header.length); // 將要發送的命令內容複製到緩衝區中, 以便出錯時重發
  
  if (size != 0)
  {
    cmdhdr->frame_header.length = size;
    cmdhdr->frame_header.type = WIFI_SDIOFRAME_COMMAND;
    cmdhdr->cmd_code = code;
    cmdhdr->size = size - sizeof(WiFi_SDIOFrameHeader); // 命令大小包括命令頭部, 但不包括SDIO幀頭部
    cmdhdr->seq_num = seq_num++;
    cmdhdr->result = 0;
  }
  else
    size = cmdhdr->frame_header.length; // 重發命令時不填寫cmdhdr
  WiFi_LowLevel_WriteData(1, wifi_port, wifi_buffer_command, size, sizeof(wifi_buffer_command));
  // WriteData函數出錯的概率很小, 這裏簡單起見就不去判斷它的返回值了
  // 即使出錯了(如CRC校驗錯誤), 由於收不到命令迴應, WiFi_CheckTimeout函數也會重傳該命令
  
  wifi_tx_command.arg = arg;
  wifi_tx_command.busy = 1;
  wifi_tx_command.callback = callback;
  wifi_tx_command.ready = 0;
  wifi_tx_command.retry = max_retry;
  wifi_tx_command.start_time = sys_now();
  wifi_tx_command.timeout = timeout;
}

/* 發送EAPOL迴應幀 */
static void WiFi_SendEAPOLResponse(const WiFi_EAPOLKeyFrame *packet_rx, uint16_t key_info, const void *key_data, uint16_t key_data_len, WiFi_Callback callback, void *arg)
{
  uint8_t ret;
  uint16_t len;
  WiFi_EAPOLKeyFrame *packet_tx = (WiFi_EAPOLKeyFrame *)WiFi_GetPacketBuffer();
  WiFi_KeyType key_type = (WiFi_KeyType)(key_info & 0x07);
  WiFi_MIC mic;
  
  memcpy(packet_tx->dest, packet_rx->src, 6); // 目標MAC地址
  memcpy(packet_tx->src, packet_rx->dest, 6); // 源MAC地址
  packet_tx->type = htons(0x888e); // 大端序的0x888e: 802.1X Authentication
  packet_tx->version = packet_rx->version; // 協議版本號 (MIC從此字段開始計算)
  packet_tx->packet_type = packet_rx->packet_type; // 通常爲3: Key
  packet_tx->packet_body_length = packet_tx->key_data - &packet_tx->descriptor_type + key_data_len;
  packet_tx->packet_body_length = htons(packet_tx->packet_body_length);
  
  packet_tx->descriptor_type = packet_rx->descriptor_type; // 描述符類型
  packet_tx->key_information = htons(key_info);
  packet_tx->key_length = packet_rx->key_length;
  memcpy(packet_tx->key_replay_counter, packet_rx->key_replay_counter, sizeof(packet_rx->key_replay_counter));
  memcpy(packet_tx->key_nonce, wifi_snonce, sizeof(wifi_snonce));
  memset(packet_tx->key_iv, 0, sizeof(packet_tx->key_iv) + sizeof(packet_tx->key_rsc) + sizeof(packet_tx->reserved));
  packet_tx->key_data_length = htons(key_data_len);
  if (key_data)
    memcpy(packet_tx->key_data, key_data, key_data_len);
  
  // 用KCK對要發送的EAPOL幀(以太網幀去掉兩個MAC地址字段和type/length=0x888e字段剩下的payload)進行運算得到MIC
  len = sizeof(WiFi_EAPOLKeyFrame) - sizeof(packet_tx->key_data) + key_data_len; // 幀的總長度
  memset(packet_tx->key_mic, 0, sizeof(packet_tx->key_mic)); // 先將MIC字段清零
  if (key_type == WIFI_KEYTYPE_TKIP)
    ret = hmac_md5(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet_tx->version, len - 14, mic.MIC); // 計算MIC
  else if (key_type == WIFI_KEYTYPE_AES)
    ret = hmac_sha1(wifi_ptk.KCK, sizeof(wifi_ptk.KCK), &packet_tx->version, len - 14, mic.MIC);
  else
  {
    printf("WiFi_SendEAPOLResponse: unsupported key type!\n");
    if (callback)
      callback(arg, packet_tx, WIFI_STATUS_INVALID);
    return;
  }
  if (!ret)
  {
    printf("WiFi_SendEAPOLResponse: out of memory!\n");
    if (callback)
      callback(arg, packet_tx, WIFI_STATUS_MEM);
    return;
  }
  
  memcpy(packet_tx->key_mic, mic.MIC, sizeof(mic.MIC)); // 將計算結果放到MIC字段上
  WiFi_SendPacket(packet_tx, len, callback, arg, WIFI_DEFAULT_TIMEOUT_DATAACK);
}

/* 發送數據幀 */
// data指向的是WiFi_DataTx.payload
// size=0表示再次發送上一幀, 此時data參數將被忽略
void WiFi_SendPacket(void *data, uint16_t size, WiFi_Callback callback, void *arg, uint32_t timeout)
{
  WiFi_DataTx *packet = (WiFi_DataTx *)wifi_buffer_packet;
  
  // 發送新數據幀: WiFi_SendPacket(data, size, ...)
  // 重傳前一數據幀: WiFi_SendPacket(NULL, 0, ...)
  if (size == 0)
    data = packet->payload;
  
  WiFi_WaitForLastTask();
  if (data != packet->payload)
    memmove(packet->payload, data, size); // 將要發送的數據內容複製到緩衝區中
  
  if (size != 0)
  {
    // 有關發送數據包的細節, 請參考Firmware Specification PDF的Chapter 3: Data Path
    packet->header.length = sizeof(WiFi_DataTx) - sizeof(packet->payload) + size;
    packet->header.type = WIFI_SDIOFRAME_DATA;
    
    packet->reserved1 = 0;
    packet->tx_control = 0; // 控制信息的格式請參考3.2.1 Per-Packet Settings
    packet->tx_packet_offset = sizeof(WiFi_DataTx) - sizeof(packet->payload) - sizeof(packet->header); // 不包括SDIOFrameHeader
    packet->tx_packet_length = size;
    memcpy((void *)&packet->tx_dest_addr_high, packet->payload, 6);
    packet->priority = 0;
    packet->flags = 0;
    packet->pkt_delay_2ms = 0;
    packet->reserved2 = 0;
  }
  WiFi_LowLevel_WriteData(1, wifi_port, wifi_buffer_packet, packet->header.length, sizeof(wifi_buffer_packet));
  
  // 接下來需要等待Download Ready位置1, 表明數據幀發送成功
  wifi_tx_packet.arg = arg;
  wifi_tx_packet.busy = 1;
  wifi_tx_packet.callback = callback;
  wifi_tx_packet.retry = 0;
  wifi_tx_packet.start_time = sys_now();
  wifi_tx_packet.timeout = timeout;
}

/* 將PTK和GTK密鑰發送給固件使用 */
static void WiFi_SetKeyMaterial(WiFi_KeyType key_type, uint8_t key_num, WiFi_Callback callback, void *arg)
{
  MrvlIETypes_KeyParamSet_t keys[2];
  uint16_t key_len;
  if (key_type == WIFI_KEYTYPE_TKIP)
    key_len = sizeof(wifi_ptk.TK) + sizeof(wifi_ptk.TKIPTxMICKey) + sizeof(wifi_ptk.TKIPRxMICKey);
  else if (key_type == WIFI_KEYTYPE_AES)
    key_len = sizeof(wifi_ptk.TK);
  else
  {
    if (callback)
      callback(arg, NULL, WIFI_STATUS_INVALID);
    return;
  }
  
  keys[0].key_type_id = key_type;
  keys[0].key_info = WIFI_KEYINFO_KEYENABLED | WIFI_KEYINFO_UNICASTKEY; // 單播密鑰
  keys[0].key_len = key_len;
  memcpy(keys[0].key, wifi_ptk.TK, sizeof(wifi_ptk.TK));
  if (key_type == WIFI_KEYTYPE_TKIP)
  {
    // 固件中表示的MIC校驗用的密鑰順序剛好和PRF函數產生的順序相反
    memcpy(keys[0].key + sizeof(wifi_ptk.TK), wifi_ptk.TKIPRxMICKey, sizeof(wifi_ptk.TKIPRxMICKey));
    memcpy(keys[0].key + sizeof(wifi_ptk.TK) + sizeof(wifi_ptk.TKIPRxMICKey), wifi_ptk.TKIPTxMICKey, sizeof(wifi_ptk.TKIPTxMICKey));
  }
  
  if (key_num == 2)
  {
    keys[1].key_type_id = key_type;
    keys[1].key_info = WIFI_KEYINFO_KEYENABLED | WIFI_KEYINFO_MULTICASTKEY; // 多播、廣播密鑰
    keys[1].key_len = key_len;
    memcpy(keys[1].key, wifi_gtk.TK, sizeof(wifi_gtk.TK));
    if (key_type == WIFI_KEYTYPE_TKIP)
    {
      memcpy(keys[1].key + sizeof(wifi_gtk.TK), wifi_gtk.TKIPRxMICKey, sizeof(wifi_gtk.TKIPRxMICKey));
      memcpy(keys[1].key + sizeof(wifi_gtk.TK) + sizeof(wifi_gtk.TKIPRxMICKey), wifi_gtk.TKIPTxMICKey, sizeof(wifi_gtk.TKIPTxMICKey));
    }
  }
  
  WiFi_KeyMaterial(WIFI_ACT_SET, keys, key_num, callback, arg);
}

/* 設置WEP密鑰 (長度必須爲5或13個字符) */
// action: WIFI_ACT_ADD / WIFI_ACT_REMOVE, 移除密鑰時參數key可以設爲NULL
void WiFi_SetWEP(WiFi_CommandAction action, const WiFi_WEPKey *key, WiFi_Callback callback, void *arg)
{
  uint8_t i, j, len;
  uint16_t cmd_size;
  uint32_t temp;
  WiFi_Cmd_SetWEP *cmd = (WiFi_Cmd_SetWEP *)wifi_buffer_command;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  cmd->action = action;
  cmd->tx_key_index = key->index;
  if (action == WIFI_ACT_ADD)
  {
    memset(cmd->wep_types, 0, sizeof(cmd->wep_types) + sizeof(cmd->keys));
    for (i = 0; i < 4; i++)
    {
      if (key->keys[i] == NULL)
        continue;
      
      len = strlen(key->keys[i]);
      if (len == 5 || len == 13)
      {
        // 5個或13個ASCII密鑰字符
        if (len == 5)
          cmd->wep_types[i] = WIFI_WEPKEYTYPE_40BIT;
        else if (len == 13)
          cmd->wep_types[i] = WIFI_WEPKEYTYPE_104BIT;
        memcpy(cmd->keys[i], key->keys[i], len);
      }
      else if (len == 10 || len == 26)
      {
        // 10個或26個16進制密鑰字符
        if (len == 10)
          cmd->wep_types[i] = WIFI_WEPKEYTYPE_40BIT;
        else if (len == 26)
          cmd->wep_types[i] = WIFI_WEPKEYTYPE_104BIT;
        
        for (j = 0; j < len; j += 2)
        {
          if (!isxdigit(key->keys[i][j]) || !isxdigit(key->keys[i][j + 1]))
          {
            printf("WiFi_SetWEP: The hex key %d contains invalid characters!\n", i);
            if (callback)
              callback(arg, NULL, WIFI_STATUS_INVALID);
            return;
          }
          
          sscanf(key->keys[i] + j, "%02x", &temp);
          cmd->keys[i][j / 2] = temp;
        }
      }
      else
      {
        printf("WiFi_SetWEP: The length of key %d is invalid!\n", i);
        if (callback)
          callback(arg, NULL, WIFI_STATUS_INVALID);
        return;
      }
    }
    cmd_size = sizeof(WiFi_Cmd_SetWEP);
  }
  else if (action == WIFI_ACT_REMOVE)
    cmd_size = cmd->wep_types - wifi_buffer_command;
  else
  {
    if (callback)
      callback(arg, NULL, WIFI_STATUS_INVALID);
    return;
  }
  WiFi_SendCommand(CMD_802_11_SET_WEP, wifi_buffer_command, cmd_size, callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

/* 設置WPA密碼, 並生成PSK */
void WiFi_SetWPA(const char *ssid, const char *password)
{
  uint8_t ret = pbkdf2_hmac_sha1(password, strlen(password), ssid, strlen(ssid), 4096, wifi_psk, sizeof(wifi_psk));
  if (!ret)
    printf("WiFi_SetWPA: out of memory!\n");
}

/* 顯示WiFi模塊信息 */
void WiFi_ShowCIS(void)
{
  uint8_t data[255];
  uint8_t func, i, n, len;
  uint8_t tpl_code, tpl_link; // 16.2 Basic Tuple Format and Tuple Chain Structure
  uint32_t cis_ptr;
  
  n = WiFi_LowLevel_GetFunctionNum();
  for (func = 0; func <= n; func++)
  {
    // 獲取CIS的地址
    cis_ptr = (func << 8) | 0x9;
    cis_ptr  = WiFi_LowLevel_ReadReg(0, cis_ptr) | (WiFi_LowLevel_ReadReg(0, cis_ptr + 1) << 8) | (WiFi_LowLevel_ReadReg(0, cis_ptr + 2) << 16);
    printf("[CIS] func=%d, ptr=0x%08x\n", func, cis_ptr);
    
    // 遍歷CIS, 直到尾節點
    while ((tpl_code = WiFi_LowLevel_ReadReg(0, cis_ptr++)) != CISTPL_END)
    {
      if (tpl_code == CISTPL_NULL)
        continue;
      
      tpl_link = WiFi_LowLevel_ReadReg(0, cis_ptr++); // 本結點數據的大小
      for (i = 0; i < tpl_link; i++)
        data[i] = WiFi_LowLevel_ReadReg(0, cis_ptr + i);
      
      switch (tpl_code)
      {
        case CISTPL_VERS_1:
          printf("Product Information:");
          for (i = 2; data[i] != 0xff; i += len + 1)
          {
            // 遍歷所有字符串
            len = strlen((char *)data + i);
            if (len != 0)
              printf(" %s", data + i);
          }
          printf("\n");
          break;
        case CISTPL_MANFID:
          // 16.6 CISTPL_MANFID: Manufacturer Identification String Tuple
          printf("Manufacturer Code: 0x%04x\n", *(uint16_t *)data); // TPLMID_MANF
          printf("Manufacturer Information: 0x%04x\n", *(uint16_t *)(data + 2)); // TPLMID_CARD
          break;
        case CISTPL_FUNCID:
          // 16.7.1 CISTPL_FUNCID: Function Identification Tuple
          printf("Card Function Code: 0x%02x\n", data[0]); // TPLFID_FUNCTION
          printf("System Initialization Bit Mask: 0x%02x\n", data[1]); // TPLFID_SYSINIT
          break;
        case CISTPL_FUNCE:
          // 16.7.2 CISTPL_FUNCE: Function Extension Tuple
          if (data[0] == 0)
          {
            // 16.7.3 CISTPL_FUNCE Tuple for Function 0 (Extended Data 00h)
            printf("Maximum Block Size: %d\n", *(uint16_t *)(data + 1));
            printf("Maximum Transfer Rate Code: 0x%02x\n", data[3]);
          }
          else
          {
            // 16.7.4 CISTPL_FUNCE Tuple for Function 1-7 (Extended Data 01h)
            printf("Maximum Block Size: %d\n", *(uint16_t *)(data + 0x0c)); // TPLFE_MAX_BLK_SIZE
          }
          break;
        default:
          printf("[CIS Tuple 0x%02x] addr=0x%08x size=%d\n", tpl_code, cis_ptr - 2, tpl_link);
          dump_data(data, tpl_link);
      }
      
      if (tpl_link == 0xff)
        break; // 當TPL_LINK爲0xff時說明當前結點爲尾節點
      cis_ptr += tpl_link;
    }
  }
}

/* 創建一個Ad-Hoc型的WiFi熱點 */
// 創建帶有WEP密碼的熱點時, cap_info爲WIFI_CAPABILITY_PRIVACY
// 創建無密碼的熱點時, cap_info爲0
void WiFi_StartADHOC(const char *ssid, uint16_t cap_info, WiFi_Callback callback, void *arg)
{
  WiFi_Cmd_ADHOCStart *cmd = (WiFi_Cmd_ADHOCStart *)wifi_buffer_command;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  strncpy((char *)cmd->ssid, ssid, sizeof(cmd->ssid));
  cmd->bss_type = WIFI_BSS_INDEPENDENT;
  cmd->bcn_period = 100;
  cmd->reserved1 = 0;
  cmd->ibss_param_set.header.type = WIFI_MRVLIETYPES_IBSSPARAMSET;
  cmd->ibss_param_set.header.length = TLV_PAYLOADLEN(cmd->ibss_param_set);
  cmd->ibss_param_set.atim_window = 0;
  cmd->reserved2 = 0;
  cmd->ds_param_set.header.type = WIFI_MRVLIETYPES_PHYPARAMDSSET;
  cmd->ds_param_set.header.length = TLV_PAYLOADLEN(cmd->ds_param_set);
  cmd->ds_param_set.channel = 1;
  memset(cmd->reserved3, 0, sizeof(cmd->reserved3));
  cmd->cap_info = WIFI_CAPABILITY_IBSS | cap_info;
  memset(cmd->data_rate, 0, sizeof(cmd->data_rate));
  *(uint32_t *)cmd->data_rate = 0x968b8482;
  WiFi_SendCommand(CMD_802_11_AD_HOC_START, wifi_buffer_command, sizeof(WiFi_Cmd_ADHOCStart), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

/* 創建一個Ad-Hoc型的WiFi熱點並設置好密碼 */
// conn->mac_addr成員將被忽略
void WiFi_StartADHOCEx(const WiFi_Connection *conn, WiFi_Callback callback, void *arg)
{
  uint16_t ssid_len;
  void **p;
  WiFi_SecurityType *psecurity;
  if (WiFi_CheckCommandBusy(callback, arg))
    return;
  
  if (conn->security == WIFI_SECURITYTYPE_WPA || conn->security == WIFI_SECURITYTYPE_WPA2)
  {
    printf("WiFi_StartADHOCEx: WPA is not supported!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_INVALID);
    return;
  }
  
  ssid_len = strlen(conn->ssid);
  p = malloc(2 * sizeof(void *) + sizeof(WiFi_SecurityType) + ssid_len + 1);
  if (p == NULL)
  {
    printf("WiFi_StartADHOCEx: malloc failed!\n");
    if (callback)
      callback(arg, NULL, WIFI_STATUS_MEM);
    return;
  }
  p[0] = arg;
  p[1] = callback;
  psecurity = (WiFi_SecurityType *)(p + 2);
  *psecurity = conn->security;
  memcpy(psecurity + 1, conn->ssid, ssid_len + 1);
  
  if (conn->security == WIFI_SECURITYTYPE_WEP)
    WiFi_SetWEP(WIFI_ACT_ADD, conn->password, WiFi_StartADHOCEx_Callback, p);
  else
    WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_StartADHOCEx_Callback, p);
}

static void WiFi_StartADHOCEx_Callback(void *arg, void *data, WiFi_Status status)
{
  void **p = (void **)arg;
  void *app_arg = p[0];
  WiFi_Callback app_callback = (WiFi_Callback)p[1];
  WiFi_SecurityType *psecurity = (WiFi_SecurityType *)(p + 2);
  char *ssid = (char *)(psecurity + 1);
  
  if (status != WIFI_STATUS_OK)
  {
    printf("WiFi_StartADHOCEx error!\n");
    free(arg);
    if (app_callback)
      app_callback(app_arg, data, status);
    return;
  }
  
  switch (WiFi_GetCommandCode(data))
  {
    case CMD_802_11_SET_WEP:
      WiFi_MACControl(WIFI_MACCTRL_ETHERNET2 | WIFI_MACCTRL_WEP | WIFI_MACCTRL_TX | WIFI_MACCTRL_RX, WiFi_StartADHOCEx_Callback, arg);
      break;
    case CMD_MAC_CONTROL:
      if (*psecurity == WIFI_SECURITYTYPE_NONE)
        WiFi_StartADHOC(ssid, 0, WiFi_StartADHOCEx_Callback, arg);
      else
        WiFi_StartADHOC(ssid, WIFI_CAPABILITY_PRIVACY, WiFi_StartADHOCEx_Callback, arg);
      break;
    case CMD_802_11_AD_HOC_START:
      free(arg);
      if (app_callback)
        app_callback(app_arg, data, status);
      break;
  }
}

void WiFi_StopADHOC(WiFi_Callback callback, void *arg)
{
  WiFi_SendCommand(CMD_802_11_AD_HOC_STOP, wifi_buffer_command, sizeof(WiFi_CommandHeader), callback, arg, WIFI_DEFAULT_TIMEOUT_CMDRESP, WIFI_DEFAULT_MAXRETRY);
}

/* 釋放發送緩衝區並調用回調函數 */
static void WiFi_TxBufferComplete(WiFi_TxBuffer *tbuf, void *data, WiFi_Status status)
{
  if (tbuf->busy)
  {
    tbuf->busy = 0;
    if (tbuf->callback)
      tbuf->callback(tbuf->arg, data, status); // 調用回調函數
  }
}

/* 將IEEE型的TLV轉換成MrvlIE型的TLV */
uint8_t WiFi_TranslateTLV(MrvlIEType *mrvlie_tlv, const IEEEType *ieee_tlv, uint16_t mrvlie_payload_size)
{
  mrvlie_tlv->header.type = ieee_tlv->header.type;
  if (ieee_tlv->header.length > mrvlie_payload_size)
    mrvlie_tlv->header.length = mrvlie_payload_size; // 若源數據大小超過緩衝區最大容量, 則丟棄剩餘數據
  else
    mrvlie_tlv->header.length = ieee_tlv->header.length;
  memset(mrvlie_tlv->data, 0, mrvlie_payload_size); // 清零
  memcpy(mrvlie_tlv->data, ieee_tlv->data, mrvlie_tlv->header.length); // 複製數據
  return mrvlie_tlv->header.length == ieee_tlv->header.length; // 返回值表示緩衝區大小是否足夠
}

/* 在規定的超時時間內, 等待指定的卡狀態位置位, 並清除相應的中斷標誌位 */
// 成功時返回1
uint8_t WiFi_Wait(uint8_t status, uint32_t timeout)
{
  if (timeout != 0)
    timeout += sys_now();
  
  while ((WiFi_LowLevel_ReadReg(1, WIFI_INTSTATUS) & status) != status)
  {
    if (timeout != 0 && sys_now() > timeout)
    {
      // 若超時時間已到
      printf("WiFi_Wait(0x%02x): timeout!\n", status);
      return 0;
    }
  }
  
  // 清除對應的中斷標誌位
  WiFi_LowLevel_WriteReg(1, WIFI_INTSTATUS, WIFI_INTSTATUS_ALL & ~status); // 不需要清除的位必須爲1
  // 不能將SDIOIT位清除掉! 否則有可能導致該位永遠不再置位
  return 1;
}

/* 等待之前發送的命令幀或數據幀收到確認 */
void WiFi_WaitForLastTask(void)
{
  int32_t remaining;
#ifdef WIFI_DISPLAY_RESPTIME
  WiFi_CommandHeader *tx_cmd = (WiFi_CommandHeader *)wifi_buffer_command;
#endif
  
  // 執行CMD53命令發送數據前, 必須等待之前發送的數據收到DNLDRDY的確認
  // 注: CMDBUSY=DATABUSY=1且CMDRDY=0的情況是不可能出現的
  while ((wifi_tx_command.busy && wifi_tx_command.ready == 0) || wifi_tx_packet.busy)
  {
    if (wifi_tx_command.busy && wifi_tx_command.ready == 0)
    {
      remaining = wifi_tx_command.start_time + WIFI_DEFAULT_TIMEOUT_CMDACK - sys_now() + 1;
      if (remaining > 0)
      {
        if (WiFi_Wait(WIFI_INTSTATUS_DNLD, remaining))
        {
#ifdef WIFI_DISPLAY_RESPTIME
          printf("-- CMD 0x%04x ACK at %dms\n", tx_cmd->cmd_code, sys_now() - wifi_tx_command.start_time);
#endif
        }
      }
      wifi_tx_command.ready = 1;
      
      // 現在命令要麼超時, 要麼已收到確認, 這裏不負責重傳命令
    }
    
    if (wifi_tx_packet.busy)
    {
      // 只需要等待download ready位置位, 不用管新來的數據幀(upload ready)
      remaining = wifi_tx_packet.start_time + wifi_tx_packet.timeout - sys_now() + 1; // 剩餘時間+1 (小於等於0表示超時)
      if (remaining > 0 && WiFi_Wait(WIFI_INTSTATUS_DNLD, remaining)) // 在剩餘時間+1內等待標誌位置位, 並清除中斷標誌位
      {
#ifdef WIFI_DISPLAY_RESPTIME
        printf("-- Packet ACK at %dms\n", sys_now() - wifi_tx_packet.start_time);
#endif
        WiFi_TxBufferComplete(&wifi_tx_packet, wifi_buffer_packet, WIFI_STATUS_OK); // 若DNLDRDY已置位, 則表明數據幀發送成功, 將busy清零並調用相應的回調函數
        // 如果在回調函數中發送了新幀, 那麼busy仍等於1, 需要繼續等待, 所以這裏不能寫break
      }
      else
        WiFi_CheckTxBufferRetry(&wifi_tx_packet, wifi_buffer_packet); // 通知回調函數超時
    }
  }
}

WPA.h:

#define HMAC_MD5_BLOCKSIZE 64
#define HMAC_MD5_OUTPUTSIZE 16
#define HMAC_SHA1_BLOCKSIZE 64
#define HMAC_SHA1_OUTPUTSIZE 20

typedef void (*HashFunction)(unsigned char *input, int ilen, unsigned char *output);

void ARC4_decrypt_keydata(const uint8_t *KEK, const uint8_t *key_iv, const uint8_t *data, uint16_t datalen, uint8_t *output);
uint16_t AES_unwrap(const uint8_t *key, const uint8_t *data, uint16_t datalen, uint8_t *output);
uint8_t hmac(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, HashFunction hash, uint8_t blocksize, uint8_t *output, uint8_t outputsize);
uint8_t hmac_md5(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_MD5_OUTPUTSIZE]);
uint8_t hmac_sha1(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_SHA1_OUTPUTSIZE]);
uint8_t pbkdf2_hmac_sha1(const void *password, uint16_t pwdlen, const void *salt, uint16_t saltlen, uint32_t c, uint8_t *dk, uint16_t dklen);
uint8_t PRF(const void *k, uint16_t klen, const char *a, const void *b, uint16_t blen, void *output, uint8_t n);

WPA.c:

#include <aes.h>
#include <netif/ppp/polarssl/arc4.h>
#include <netif/ppp/polarssl/md5.h>
#include <netif/ppp/polarssl/sha1.h>
#include <stdlib.h>
#include <string.h>
#include "WPA.h"

/* ARC4算法解密TKIP Key Data封包 */
// ARC4的加解密算法相同, 但每次都必須要調用一次arc4_setup函數
// 參考文章: http://www.fenlog.com/post/111.html
void ARC4_decrypt_keydata(const uint8_t *KEK, const uint8_t *key_iv, const uint8_t *data, uint16_t datalen, uint8_t *output)
{
  arc4_context ctx;
  uint8_t dummy[256] = {0};
  uint8_t newkey[2][16];
  
  memcpy(newkey[0], key_iv, sizeof(newkey[0]));
  memcpy(newkey[1], KEK, sizeof(newkey[1]));
  
  arc4_setup(&ctx, newkey[0], sizeof(newkey));
  arc4_crypt(&ctx, dummy, sizeof(dummy)); // discard the first 256 bytes of the RC4 cipher stream output
  memcpy(output, data, datalen);
  arc4_crypt(&ctx, output, datalen);
}

/* AES Key Wrap解密算法解密AES Key Data封包 */
// 88W8686不支持CMD_802_11_CRYPTO命令, 因此必須用軟件實現此算法
// RFC3394.pdf: 2.2.2 Key Unwrap
// 返回值爲輸出結果的數據大小
uint16_t AES_unwrap(const uint8_t *key, const uint8_t *data, uint16_t datalen, uint8_t *output)
{
  uint8_t a[8], b[16];
  uint8_t *r;
  uint16_t i, j, n, t;
  struct AES_ctx ctx;
  AES_init_ctx(&ctx, key);
  
  /* Initialize variables */
  n = (datalen / 8) - 1;
  memcpy(a, data, 8);
  r = output;
  memcpy(r, data + 8, datalen - 8);

  /* Compute intermediate values */
  for (j = 5; j <= 5; j--)
  {
    r = output + (n - 1) * 8;
    for (i = n; i >= 1; i--)
    {
      t = n * j + i;
      memcpy(b, a, 8);
      b[7] ^= t;
      memcpy(b + 8, r, 8);

      AES_ECB_decrypt(&ctx, b);
      memcpy(a, b, 8);
      memcpy(r, b + 8, 8);
      r -= 8;
    }
  }
  return datalen - 8;
}

/* HMAC算法 */
// https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
// hash函數不會改變input參數的值, 所以可以放心去掉const修飾符
uint8_t hmac(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, HashFunction hash, uint8_t blocksize, uint8_t *output, uint8_t outputsize)
{
  uint8_t i, *p;
  uint8_t *obuf = (uint8_t *)malloc(blocksize + outputsize); // 用最前面的blocksize字節存放轉換後的key
  if (obuf == NULL)
    return 0; // 計算失敗: 內存不足

  // Keys longer than blockSize are shortened by hashing them
  if (keylen > blocksize)
  {
    hash((uint8_t *)key, keylen, obuf); // Key becomes outputSize bytes long
    keylen = outputsize;
  }
  else
    memcpy(obuf, key, keylen);

  // Keys shorter than blockSize are padded to blockSize by padding with zeros on the right
  if (keylen < blocksize)
    memset(obuf + keylen, 0, blocksize - keylen); // pad key with zeros to make it blockSize bytes long

  // Inner padded key
  p = (uint8_t *)malloc(blocksize + msglen);
  if (p == NULL)
  {
    free(obuf);
    return 0; // 計算失敗: 內存不足
  }
  for (i = 0; i < blocksize; i++)
    p[i] = obuf[i] ^ 0x36;

  memcpy(p + blocksize, msg, msglen);
  hash(p, blocksize + msglen, obuf + blocksize);
  free(p);

  // Outer padded key
  for (i = 0; i < blocksize; i++)
    obuf[i] ^= 0x5c;
  
  hash(obuf, blocksize + outputsize, (uint8_t *)output);
  free(obuf);
  return 1; // 計算成功
}

/* 利用lwip提供的md5函數實現HMAC_MD5算法 */
uint8_t hmac_md5(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_MD5_OUTPUTSIZE])
{
  return hmac(key, keylen, msg, msglen, md5, HMAC_MD5_BLOCKSIZE, output, HMAC_MD5_OUTPUTSIZE);
}

/* 利用lwip提供的sha1函數實現HMAC_SHA1算法 */
uint8_t hmac_sha1(const void *key, uint16_t keylen, const void *msg, uint16_t msglen, uint8_t output[HMAC_SHA1_OUTPUTSIZE])
{
  return hmac(key, keylen, msg, msglen, sha1, HMAC_SHA1_BLOCKSIZE, output, HMAC_SHA1_OUTPUTSIZE);
}

/* 利用hmac_sha1函數實現PBKDF2_HMAC_SHA1算法 */
// https://en.wikipedia.org/wiki/PBKDF2
uint8_t pbkdf2_hmac_sha1(const void *password, uint16_t pwdlen, const void *salt, uint16_t saltlen, uint32_t c, uint8_t *dk, uint16_t dklen)
{
  uint8_t curr, k, *p;
  uint8_t ret;
  uint8_t u[3][HMAC_SHA1_OUTPUTSIZE];
  uint16_t i;
  uint32_t j;

  p = (uint8_t *)malloc(saltlen + 4);
  if (p == NULL)
    return 0;
  memcpy(p, salt, saltlen);
  memset(p + saltlen, 0, 2);

  for (i = 1; dklen; i++)
  {
    // INT_32_BE(i)
    p[saltlen + 2] = i >> 8;
    p[saltlen + 3] = i & 0xff;

    ret = hmac_sha1(password, pwdlen, p, saltlen + 4, u[1]); // 算出來的U1放到u[1]中
    if (!ret)
    {
      free(p);
      return 0;
    }
    memcpy(u[2], u[1], HMAC_SHA1_OUTPUTSIZE); // F=U1 (u[2]用來存放F)
    for (j = 2; j <= c; j++)
    {
      // F^=Uj -> F^=PRF(password, Uj-1)
      ret = hmac_sha1(password, pwdlen, u[1], HMAC_SHA1_OUTPUTSIZE, u[0]); // 根據Uj-1(位於u[1])算出Uj放到u[0]中
      if (!ret)
      {
        free(p);
        return 0;
      }
      for (k = 0; k < HMAC_SHA1_OUTPUTSIZE; k++)
      {
        u[2][k] ^= u[0][k];
        if (j != c)
          u[1][k] = u[0][k]; // 順便把u[0]複製到u[1]中
      }
    }

    // u[2]爲最終的結果F, 將其複製到結果緩衝區dk中
    curr = (dklen < HMAC_SHA1_OUTPUTSIZE) ? dklen : HMAC_SHA1_OUTPUTSIZE;
    memcpy(dk, u[2], curr);
    dk += curr;
    dklen -= curr;
  }

  free(p);
  return 1;
}

/* Pseudorandom function (PRF-n) */
// http://etutorials.org/Networking/802.11+security.+wi-fi+protected+access+and+802.11i/Part+II+The+Design+of+Wi-Fi+Security/Chapter+10.+WPA+and+RSN+Key+Hierarchy/Computing+the+Temporal+Keys/
// 參數n爲PRF-n中的n除以8
uint8_t PRF(const void *k, uint16_t klen, const char *a, const void *b, uint16_t blen, void *output, uint8_t n)
{
  uint8_t alen = strlen(a);
  uint8_t buf[HMAC_SHA1_OUTPUTSIZE];
  uint8_t curr, i, *p, *q;

  p = (uint8_t *)malloc(alen + blen + 2);
  if (p == NULL)
    return 0;

  q = (uint8_t *)output;
  strcpy((char *)p, a); // application-specific text (including '\0')
  memcpy(p + alen + 1, b, blen); // special data
  for (i = 0; n; i++)
  {
    p[alen + blen + 1] = i; // single byte counter
    hmac_sha1(k, klen, p, alen + blen + 2, buf);

    curr = (n < HMAC_SHA1_OUTPUTSIZE) ? n : HMAC_SHA1_OUTPUTSIZE;
    memcpy(q, buf, curr);
    q += curr;
    n -= curr;
  }

  free(p);
  return 1;
}

dns_test.c:

#include <lwip/tcp.h>
#include <lwip/dns.h>
#include <lwip/sys.h> // sys_now函數所在的頭文件
#include <time.h>

static err_t test_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
  printf("Connected! err=%d\n", err);
  err = tcp_close(tpcb);
  if (err == ERR_OK)
    printf("Connection is successfully closed!\n");
  else
    printf("Connection cannot be closed now! err=%d\n", err);
  return ERR_OK;
}

static void test_err(void *arg, err_t err)
{
  printf("Connection error! code=%d\n", err);
}

void connect_test(const ip_addr_t *ipaddr)
{
  struct tcp_pcb *tpcb;
  printf("Connecting to %s...\n", ip4addr_ntoa(ipaddr));
  tpcb = tcp_new();
  tcp_connect(tpcb, ipaddr, 80, test_connected);
  tcp_err(tpcb, test_err);
}

#if LWIP_DNS
static void dns_found(const char *name, const ip_addr_t *ipaddr, void *callback_arg)
{
  if (ipaddr != NULL)
  {
    printf("DNS Found IP: %s\n", ip4addr_ntoa(ipaddr));
    connect_test(ipaddr);
  }
  else
    printf("DNS Not Found IP!\n");
}

void dns_test(void)
{
  ip_addr_t dnsip;
  err_t err = dns_gethostbyname("zh.arslanbar.net", &dnsip, dns_found, NULL);
  if (err == ERR_OK)
  {
    printf("In cache! IP: %s\n", ip4addr_ntoa(&dnsip));
    connect_test(&dnsip);
  }
  else
    printf("Not in cache! err=%d\n", err); // 緩存中沒有時需要等待DNS解析完畢在dns_found回調函數中返回結果
}
#endif

// 下面這個函數演示瞭如何使用C庫將RTC時間值轉換爲日期時間格式, 不用的話可以刪掉
void display_time(void)
{
  char str[30];
  struct tm *ptm;
  time_t sec;
  
  time(&sec);
  ptm = localtime(&sec);
  strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", ptm);
  
  printf("%s\n", str);
  printf("sys_now()=%d\n", sys_now());
}

以下是lwip協議棧中添加或修改過的文件,修改過的地方都有中文註釋。

lwip-2.0.3/include/lwipopts.h:

#define NO_SYS 1 // 無操作系統

#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define LWIP_STATS 0

#define MEM_ALIGNMENT 4 // STM32單片機是32位的單片機, 因此是4字節對齊的

#define SYS_LIGHTWEIGHT_PROT 0 // 不進行臨界區保護

// 配置DHCP
#define LWIP_DHCP 1
#define LWIP_NETIF_HOSTNAME 1

// 配置DNS
#define LWIP_DNS 1
#define LWIP_RAND() ((u32_t)rand())

// WPA/WPA2認證需要用到lwip中的arc4, md5和sha1函數
// 需要修改各文件的第42行, 註釋掉條件編譯宏
#define LWIP_INCLUDED_POLARSSL_ARC4 1
#define LWIP_INCLUDED_POLARSSL_MD5 1
#define LWIP_INCLUDED_POLARSSL_SHA1 1

lwip-2.0.3/include/arch/cc.h:

#define PACK_STRUCT_BEGIN __packed // struct前的__packed

lwip-2.0.3/netif/ethernetif.c:

/**
 * @file
 * Ethernet Interface Skeleton
 *
 */

/*
 * Copyright (c) 2001-2004 Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * This file is part of the lwIP TCP/IP stack.
 *
 * Author: Adam Dunkels <[email protected]>
 *
 */

/*
 * This file is a skeleton for developing Ethernet network interface
 * drivers for lwIP. Add code to the low_level functions and do a
 * search-and-replace for the word "ethernetif" to replace it with
 * something that better describes your network interface.
 */

#include "lwip/opt.h"

//#if 0 /* don't build, this is only a skeleton, see previous comment */
#if 1 // 允許編譯

#include "lwip/def.h"
#include "lwip/mem.h"
#include "lwip/pbuf.h"
#include "lwip/stats.h"
#include "lwip/snmp.h"
#include "lwip/ethip6.h"
#include "lwip/etharp.h"
#include "netif/ppp/pppoe.h"

// 包含頭文件
#include "../../common.h"
#include "../../WiFi.h"

/* Define those to better describe your network interface. */
#define IFNAME0 'e'
#define IFNAME1 'n'

/**
 * Helper struct to hold private data used to operate your ethernet interface.
 * Keeping the ethernet address of the MAC in this struct is not necessary
 * as it is already kept in the struct netif.
 * But this is only an example, anyway...
 */
struct ethernetif {
  struct eth_addr *ethaddr;
  /* Add whatever per-interface state that is needed here. */
};

/* Forward declarations. */
/*static */void  ethernetif_input(struct netif *netif); // 必須去掉static

/**
 * In this function, the hardware should be initialized.
 * Called from ethernetif_init().
 *
 * @param netif the already initialized lwip network interface structure
 *        for this ethernetif
 */
static void
low_level_init(struct netif *netif)
{
  //struct ethernetif *ethernetif = netif->state; // 無用的變量

  /* set MAC hardware address length */
  netif->hwaddr_len = ETHARP_HWADDR_LEN;

  /* set MAC hardware address */
  //netif->hwaddr[0] = ;
  //...
  //netif->hwaddr[5] = ;
  // MAC地址已設置, 註釋掉這段代碼

  /* maximum transfer unit */
  netif->mtu = 1500;

  /* device capabilities */
  /* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
  netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

#if LWIP_IPV6 && LWIP_IPV6_MLD
  /*
   * For hardware/netifs that implement MAC filtering.
   * All-nodes link-local is handled by default, so we must let the hardware know
   * to allow multicast packets in.
   * Should set mld_mac_filter previously. */
  if (netif->mld_mac_filter != NULL) {
    ip6_addr_t ip6_allnodes_ll;
    ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);
    netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);
  }
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */

  /* Do whatever else is needed to initialize interface. */
}

/**
 * This function should do the actual transmission of the packet. The packet is
 * contained in the pbuf that is passed to the function. This pbuf
 * might be chained.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @param p the MAC packet to send (e.g. IP packet including MAC addresses and type)
 * @return ERR_OK if the packet could be sent
 *         an err_t value if the packet couldn't be sent
 *
 * @note Returning ERR_MEM here if a DMA queue of your MAC is full can lead to
 *       strange results. You might consider waiting for space in the DMA queue
 *       to become available since the stack doesn't retry to send a packet
 *       dropped because of memory failure (except for the TCP timers).
 */

static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{
  //struct ethernetif *ethernetif = netif->state; // 無用的變量
  //struct pbuf *q;
  uint8_t *buffer; // 添加此變量

  //initiate transfer();

#if ETH_PAD_SIZE
  pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

  //for (q = p; q != NULL; q = q->next) {
    /* Send the data from the pbuf to the interface, one pbuf at a
       time. The size of the data in each pbuf is kept in the ->len
       variable. */
    //send data from(q->payload, q->len);
  //}
  buffer = WiFi_GetPacketBuffer(); // 獲取發送緩衝區 (需要等待之前的幀發送完畢)
  pbuf_copy_partial(p, buffer, p->tot_len, 0); // 複製要發送的數據幀到發送緩衝區中
#ifdef WIFI_DISPLAY_PACKET_SIZE
  printf("[Send] len=%hd\n", p->tot_len);
#endif
#ifdef WIFI_DISPLAY_PACKET_TX
  dump_data(buffer, p->tot_len);
#endif

  //signal that packet should be sent();
  WiFi_SendPacket(buffer, p->tot_len, NULL, NULL, WIFI_DEFAULT_TIMEOUT_DATAACK);

  MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len);
  if (((u8_t*)p->payload)[0] & 1) {
    /* broadcast or multicast packet*/
    MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts);
  } else {
    /* unicast packet */
    MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);
  }
  /* increase ifoutdiscards or ifouterrors on error */

#if ETH_PAD_SIZE
  pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

  LINK_STATS_INC(link.xmit);

  return ERR_OK;
}

/**
 * Should allocate a pbuf and transfer the bytes of the incoming
 * packet from the interface into the pbuf.
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return a pbuf filled with the received packet (including MAC header)
 *         NULL on memory error
 */
static struct pbuf *
low_level_input(struct netif *netif)
{
  //struct ethernetif *ethernetif = netif->state; // 無用的變量
  struct pbuf *p/*, *q*/;
  u16_t len;
  
  // 添加變量
  const uint8_t *data;

  /* Obtain the size of the packet and put it into the "len"
     variable. */
  //len = ;
  data = WiFi_GetReceivedPacket(&len); // 獲取數據幀內容和大小
#ifdef WIFI_DISPLAY_PACKET_SIZE
  printf("[Recv] len=%hd\n", len);
#endif
#ifdef WIFI_DISPLAY_PACKET_RX
  dump_data(data, len);
#endif

#if ETH_PAD_SIZE
  len += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif

  /* We allocate a pbuf chain of pbufs from the pool. */
  p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);

  if (p != NULL) {

#if ETH_PAD_SIZE
    pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif

    /* We iterate over the pbuf chain until we have read the entire
     * packet into the pbuf. */
    //for (q = p; q != NULL; q = q->next) {
      /* Read enough bytes to fill this pbuf in the chain. The
       * available data in the pbuf is given by the q->len
       * variable.
       * This does not necessarily have to be a memcpy, you can also preallocate
       * pbufs for a DMA-enabled MAC and after receiving truncate it to the
       * actually received size. In this case, ensure the tot_len member of the
       * pbuf is the sum of the chained pbuf len members.
       */
      //read data into(q->payload, q->len);
    //}
    pbuf_take(p, data, len); // 將數據幀內容複製到pbuf中
    //acknowledge that packet has been read();

    MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);
    if (((u8_t*)p->payload)[0] & 1) {
      /* broadcast or multicast packet*/
      MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);
    } else {
      /* unicast packet*/
      MIB2_STATS_NETIF_INC(netif, ifinucastpkts);
    }
#if ETH_PAD_SIZE
    pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif

    LINK_STATS_INC(link.recv);
  } else {
    //drop packet(); // 註釋掉
    LINK_STATS_INC(link.memerr);
    LINK_STATS_INC(link.drop);
    MIB2_STATS_NETIF_INC(netif, ifindiscards);
  }

  return p;
}

/**
 * This function should be called when a packet is ready to be read
 * from the interface. It uses the function low_level_input() that
 * should handle the actual reception of bytes from the network
 * interface. Then the type of the received packet is determined and
 * the appropriate input function is called.
 *
 * @param netif the lwip network interface structure for this ethernetif
 */
/*static */void // 必須去掉static
ethernetif_input(struct netif *netif)
{
  //struct ethernetif *ethernetif; // 無用的變量
  //struct eth_hdr *ethhdr;
  struct pbuf *p;

  //ethernetif = netif->state; // 註釋掉

  /* move received packet into a new pbuf */
  p = low_level_input(netif);
  /* if no packet could be read, silently ignore this */
  if (p != NULL) {
    /* pass all packets to ethernet_input, which decides what packets it supports */
    if (netif->input(p, netif) != ERR_OK) {
      LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
      pbuf_free(p);
      p = NULL;
    }
  }
}

/**
 * Should be called at the beginning of the program to set up the
 * network interface. It calls the function low_level_init() to do the
 * actual setup of the hardware.
 *
 * This function should be passed as a parameter to netif_add().
 *
 * @param netif the lwip network interface structure for this ethernetif
 * @return ERR_OK if the loopif is initialized
 *         ERR_MEM if private data couldn't be allocated
 *         any other err_t on error
 */
err_t
ethernetif_init(struct netif *netif)
{
  struct ethernetif *ethernetif;

  LWIP_ASSERT("netif != NULL", (netif != NULL));

  ethernetif = mem_malloc(sizeof(struct ethernetif));
  if (ethernetif == NULL) {
    LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
    return ERR_MEM;
  }

#if LWIP_NETIF_HOSTNAME
  /* Initialize interface hostname */
  netif->hostname = "STM32F103RE_SDIO"; // 路由器中顯示的名稱
#endif /* LWIP_NETIF_HOSTNAME */

  /*
   * Initialize the snmp variables and counters inside the struct netif.
   * The last argument should be replaced with your link speed, in units
   * of bits per second.
   */
  MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);

  netif->state = ethernetif;
  netif->name[0] = IFNAME0;
  netif->name[1] = IFNAME1;
  /* We directly use etharp_output() here to save a function call.
   * You can instead declare your own function an call etharp_output()
   * from it if you have to do some checks before sending (e.g. if link
   * is available...) */
  netif->output = etharp_output;
#if LWIP_IPV6
  netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
  netif->linkoutput = low_level_output;

  ethernetif->ethaddr = (struct eth_addr *)&(netif->hwaddr[0]);

  /* initialize the hardware */
  low_level_init(netif);

  return ERR_OK;
}

#endif /* 0 */


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章