SDIO-Wifi模塊是基于SDIO接口的符合wifi無線網絡標準的嵌入式模塊,內置無線網絡協(xié)議IEEE802.11協(xié)議棧以及TCP/IP協(xié)議棧,能夠實現(xiàn)用戶主平臺數據通過SDIO口到無線網絡之間的轉換。SDIO具有傳輸數據快,兼容SD、MMC接口等特點。
? ? ?對于SDIO接口的wifi,首先,它是一個sdio的卡的設備,然后具備了wifi的功能,所以,注冊的時候還是先以sdio的卡的設備去注冊的。然后檢測到卡之后就要驅動他的wifi功能了,顯然,他是用sdio的協(xié)議,通過發(fā)命令和數據來控制的。下面先簡單回顧一下SDIO的相關知識:
一、SDIO相關基礎知識解析
1、SDIO接口
? ? ???SDIO?故名思義,就是?SD 的 I/O 接口(interface)的意思,不過這樣解釋可能還有點抽像。更具體的說明,SD 本來是記憶卡的標準,但是現(xiàn)在也可以把 SD 拿來插上一些外圍接口使用,這樣的技術便是 SDIO。
? ? ? ?所以 SDIO 本身是一種相當單純的技術,透過 SD 的 I/O 接腳來連接外部外圍,并且透過 SD 上的 I/O 數據接位與這些外圍傳輸數據,而且 SD 協(xié)會會員也推出很完整的 SDIO stack 驅動程序,使得 SDIO 外圍(我們稱為SDIO 卡)的開發(fā)與應用變得相當熱門。
? ? ? ?現(xiàn)在已經有非常多的手機或是手持裝置都支持 SDIO 的功能(SD 標準原本就是針對 mobile device 而制定),而且許多 SDIO 外圍也都被開發(fā)出來,讓手機外接外圍更加容易,并且開發(fā)上更有彈性(不需要內建外圍)。目前常見的 SDIO 外圍(SDIO 卡)有:
· Wi-Fi card(無線網絡卡)?
· CMOS sensor card(照相模塊)?
· GPS card?
· GSM/GPRS modem card?
· Bluetooth card?
? ? ? ? SDIO 的應用將是未來嵌入式系統(tǒng)最重要的接口技術之一,并且也會取代目前 GPIO 式的 SPI 接口。
2、SDIO總線
? ? ? SDIO總線 和 USB總線 類似,SDIO也有兩端,其中一端是HOST端,另一端是device端。所有的通信都是由HOST端 發(fā)送 命令 開始的,Device端只要能解析命令,就可以相互通信。
CLK信號:HOST給DEVICE的 時鐘信號,每個時鐘周期傳輸一個命令。
CMD信號:雙向 的信號,用于傳送 命令 和 反應。
DAT0-DAT3 信號:四條用于傳送的數據線。
VDD信號:電源信號。
VSS1,VSS2:電源地信號。
3、SDIO熱插拔原理
方法:設置一個?定時器檢查?或?插拔中斷檢測
硬件:假如GPG10(EINT18)用于SD卡檢測
GPG10 為高電平 即沒有插入SD卡
GPG10為低電平 ?即插入了SD卡
4、SDIO命令
? ? ? SDIO總線上都是HOST端發(fā)起請求,然后DEVICE端回應請求。sdio命令由6個字節(jié)組成。
a -- Command:用于開始傳輸的命令,是由HOST端發(fā)往DEVICE端的。其中命令是通過CMD信號線傳送的。
b -- Response:回應是DEVICE返回的HOST的命令,作為Command的回應。也是通過CMD線傳送的。
c -- Data:數據是雙向的傳送的。可以設置為1線模式,也可以設置為4線模式。數據是通過DAT0-DAT3信號線傳輸的。
? ? ? SDIO的每次操作都是由HOST在CMD線上發(fā)起一個CMD,對于有的CMD,DEVICE需要返回Response,有的則不需要。
? ? ?對于讀命令,首先HOST會向DEVICE發(fā)送命令,緊接著DEVICE會返回一個握手信號,此時,當HOST收到回應的握手信號后,會將數據放在4位的數據線上,在傳送數據的同時會跟隨著CRC校驗碼。當整個讀傳送完畢后,HOST會再次發(fā)送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。
? ? 對于寫命令,首先HOST會向DEVICE發(fā)送命令,緊接著DEVICE會返回一個握手信號,此時,當HOST收到回應的握手信號后,會將數據放在4位的數據線上,在傳送數據的同時會跟隨著CRC校驗碼。當整個寫傳送完畢后,HOST會再次發(fā)送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。
二、SDIO接口驅動
? ? ? ? 前面講到,SDIO接口的wifi,首先,它是一個sdio的卡的設備,然后具備了wifi的功能,所以SDIO接口的WiFi驅動就是在wifi驅動外面套上了一個SDIO驅動的外殼,SDIO驅動仍然符合設備驅動的分層與分離思想:
? ? ?設備驅動層(wifi 設備)
? ? ? ? ? ? ? ? ? ? ? |
核心層(向上向下提供接口)
? ? ? ? ? ? ? ? ? ? ? |
主機驅動層 (實現(xiàn)SDIO驅動)
? ? ? ? 下面先分析SDIO接口驅動的實現(xiàn),看幾個重要的數據結構(用于核心層與主機驅動層 的數據交換處理)。
[ /include/linux/mmc/host.h ]
struct mmc_host ? ? 用來描述卡控制器
struct mmc_card ? ? 用來描述卡
struct mmc_driver ?用來描述 mmc 卡驅動
struct sdio_func ? ? ?用來描述 功能設備
struct mmc_host_ops ? 用來描述卡控制器操作接口函數功能,用于從 主機控制器層向 core 層注冊操作函數,從而將core 層與具體的主機控制器隔離。也就是說 core 要操作主機控制器,就用這個 ops 當中給的函數指針操作,不能直接調用具體主控制器的函數。
? ? ? HOST層驅動分析在 前面的系列文章中?Linux SD卡驅動開發(fā)(二) —— SD 卡驅動分析HOST篇?有詳細闡述,下面只簡單回顧一下一些重要函數處理
1、編寫Host層驅動
? ? ?這里參考的是S3C24XX的HOST驅動程序 ? /drivers/mmc/host/s3cmci.c?
[cpp]?view plaincopy
static?struct?platform_driver?s3cmci_driver?=?{?? ?????.driver??=?{?? ?????????.name????=?"s3c-sdi",???? ?????????.owner???=?THIS_MODULE,?? ?????????.pm??=?s3cmci_pm_ops,?? ?????},?? ?????.id_table?=?s3cmci_driver_ids,?? ?????.probe????????=?s3cmci_probe,???? ?????.remove???????=?__devexit_p(s3cmci_remove),?? ?????.shutdown?=?s3cmci_shutdown,?? };?? ?? s3cmci_probe(struct?platform_device?*pdev)?? {?? ?????? ????struct?mmc_host?*mmc;?? ????mmc?=?mmc_alloc_host(sizeof(struct?s3cmci_host),?&pdev->dev);???? ?? ?????? }?? ?? ?? request_irq(host->irq,?s3cmci_irq,?0,?DRIVER_NAME,?host)??? ?? ?? request_irq(host->irq_cd,?s3cmci_irq_cd,IRQF_TRIGGER_RISING?|IRQF_TRIGGER_FALLING,?DRIVER_NAME,?host)?? ?? mmc_add_host(mmc);???? ---->?device_add(&host->class_dev);??? ---->?mmc_start_host(host);??? ?? ?? ?---->mmc_detect_change(host,?0);?? ?? ?? ?mmc_schedule_delayed_work(&host->detect,?delay);??
搜索host->detected得到以下信息:
[/drivers/mmc/core/host.c]
[cpp]?view plaincopy
NIT_DELAYED_WORK(&host->detect,?mmc_rescan);?? ?? mmc_rescan(struct?work_struct?*work)?? ---->mmc_bus_put(host);?? ?? ? ? ?? mmc_claim_host(host);?? ?????mmc_rescan_try_freq(host,?max(freqs[i],?host->f_min);?? ?? static?int?mmc_rescan_try_freq(struct?mmc_host?*host,?unsigned?freq)?? {?? ?????…?? ??????? ?????if?(!mmc_attach_sdio(host))?? ??????????return?0;?? ?????if?(!mmc_attach_sd(host))?? ?????????return?0;?? ?????if?(!mmc_attach_mmc(host))?? ?????????return?0;?? ????????….?? }?? ?? mmc_attach_sdio(struct?mmc_host?*host)???? ?????--->mmc_attach_bus(host,?&mmc_sdio_ops);?? ?? ?? mmc_sdio_init_card(host,?host->ocr,?NULL,?0);??? ????--->card?=?mmc_alloc_card(host,?NULL);?? ??????????mmc_set_bus_mode(host,?MMC_BUSMODE_PUSHPULL);??? ?? struct?sdio_func?*sdio_func[SDIO_MAX_FUNCS];??? ?? sdio_init_func(host->card,?i?+?1);?? ????--->func?=?sdio_alloc_func(card);??? ??????????mmc_io_rw_direct();?? ??????????card->sdio_func[fn?-?1]?=?func;?? ?? mmc_add_card(host->card);???? sdio_add_func(host->card->sdio_func[i]);???
這里一系列函數調用在前面的SD驅動蚊帳中已經闡述過了,不再詳細闡述
2、SDIO設備的熱插拔
? ? ? 當插拔SDIO設備,會觸發(fā)中斷通知到CPU,然后執(zhí)行卡檢測中斷處理函數在這個中斷服務函數中,mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay), INIT_DELAYED_WORK(&host->detect, mmc_rescan)會調度mmc_rescan函數延時調度工作隊列,這樣也會觸發(fā)SDIO設備的初始化流程,檢測到有效的SDIO設備后,會將它注冊到系統(tǒng)中去。
[cpp]?view plaincopy
static?irqreturn_t?s3cmci_irq_cd(int?irq,?void?*dev_id)?? {?? ?????struct?s3cmci_host?*host?=?(struct?s3cmci_host?*)dev_id;?? ?????........?? ?????mmc_detect_change(host->mmc,?msecs_to_jiffies(500));?? ?? ?????return?IRQ_HANDLED;?? }??
三、wifi 驅動部分解析
wifi驅動的通用的軟件架構
1. 分為兩部分,上面為主機端驅動,下面是我們之前所說的firmware
2. 其中固件部分的主要工作是:因為天線接受和發(fā)送回來的都是802.11幀的幀,而主機接受和傳送出來的數據都必須是802.3的幀,所以必須由firmware來負責802.3的幀和802.11幀之間的轉換
3. 當天線收到數據,并被firmware處理好后會放在一個buffer里,并產生一個中斷,主機在收到中斷后就去讀這個buffer。
? ? ??
? ? ?SDIO設備的驅動由sdio_driver結構體定義,sdio_driver其實是driver的封裝。通過sdio_register_driver函數將SDIO設備驅動加載進內核,其實就是掛載到sdio_bus_type總線上去。
1、設備驅動的注冊與匹配
[Drivers/net/wireless/libertas/if_sdio.c]
[cpp]?view plaincopy
?? ?? struct?sdio_driver?{?? ?????char?*name;???? ?????const?struct?sdio_device_id?*id_table;??? ?????int?(*probe)(struct?sdio_func?*,?const?struct?sdio_device_id?*);?? ?????void?(*remove)(struct?sdio_func?*);?? ?????struct?device_driver?drv;?? };??
下面是具體函數的填充:
[cpp]?view plaincopy
?? ?? static?struct?sdio_driver?if_sdio_driver?=?{?? ?????.name?????????=?"libertas_sdio",?? ?????.id_table?=?if_sdio_ids,???? ?????.probe????????=?if_sdio_probe,?? ?????.remove???????=?if_sdio_remove,?? ?????.drv?=?{?? ?????????.pm?=?&if_sdio_pm_ops,?? ?????????}?? };??
設備注冊函數
[cpp]?view plaincopy
? ? ? ?? ?? int?sdio_register_driver(struct?sdio_driver?*drv)?? {?? ?????drv->drv.name?=?drv->name;?? ?????drv->drv.bus?=?&sdio_bus_type;???? ?????return?driver_register(&drv->drv);?? }??
總線函數
[cpp]?view plaincopy
static?struct?bus_type?sdio_bus_type?=?{?? ?????.name?????????=?"sdio",?? ?????.dev_attrs????=?sdio_dev_attrs,?? ?????.match????????=?sdio_bus_match,?? ?????.uevent???????=?sdio_bus_uevent,?? ?????.probe????????=?sdio_bus_probe,?? ?????.remove???????=?sdio_bus_remove,?? ?????.pm??????=?SDIO_PM_OPS_PTR,?? };??
注意:設備或者驅動注冊到系統(tǒng)中的過程中,都會調用相應bus上的匹配函數來進行匹配合適的驅動或者設備,對于sdio設備的匹配是由sdio_bus_match和sdio_bus_probe函數來完成。
[cpp]?view plaincopy
static?int?sdio_bus_match(struct?device?*dev,?struct?device_driver?*drv)?? {?? ?????struct?sdio_func?*func?=?dev_to_sdio_func(dev);?? ?????struct?sdio_driver?*sdrv?=?to_sdio_driver(drv);??? ?????if?(sdio_match_device(func,?sdrv))?? ?????????return?1;??? ?? ?????return?0;?? }?? ?? static?const?struct?sdio_device_id?*sdio_match_device(struct?sdio_func?*func,?? ?????struct?sdio_driver?*sdrv)?? {?? ?????const?struct?sdio_device_id?*ids;?? ?????ids?=?sdrv->id_table;????????????? ?? ????if?(sdio_match_one(func,?ids))?? ???????????????????return?ids;?? }??
由以上匹配過程來看,通過匹配id_table 和 sdio_driver設備驅動中id,來匹配合適的驅動或設備。最終會調用.probe函數,來完成相關操作。
2、If_sdio_probe函數
? ? 當檢測到sdio卡插入了之后就會調用If_sdio_probe,而當卡被移除后就會調用If_sdio_remove。
下面先看下If_sdio_probet函數,if_sdio_prob 函數 主要做了兩件事 ?
[cpp]?view plaincopy
static?struct?sdio_driver?if_sdio_driver?=?{?? ?.name??=?"libertas_sdio",?? ?.id_table?=?if_sdio_ids,????? ?.probe??=?if_sdio_probe,?? ?.remove??=?if_sdio_remove,?? ?.drv?=?{?? ??.pm?=?&if_sdio_pm_ops,?? ?},?? };?? ??? ?? 1??? ?struct?if_sdio_card?*card;?? ?struct?if_sdio_packet?*packet;???? ?struct?mmc_host?*host?=?func->card->host;?? ?? ??? ????? ?for?(i?=?0;i?<?func->card->num_info;i++)?{?? ??if?(sscanf(func->card->info[i],?? ????"802.11?SDIO?ID:?%x",?&model)?==?1)?? ??? ?? case?MODEL_8686:?? ??card->scratch_reg?=?IF_SDIO_SCRATCH;?? ??? ??? ?? card->workqueue?=?create_workqueue("libertas_sdio");?? ?? INIT_WORK(&card->packet_worker,?if_sdio_host_to_card_worker);?? ?? ?? ?? static?void?if_sdio_host_to_card_worker(struct?work_struct?*work)?? ?? ??? ???? /*fw_table?中的??MODEL_8686,?"sd8686_helper.bin",?"sd8686.bin"?},?/?? ?for?(i?=?0;?i?<?ARRAY_SIZE(fw_table);?i++)?{?? ??????if?(card->model?==?fw_table[i].model)?? ???????????break;?? ?}?? ?{?MODEL_8688,?"libertas/sd8688_helper.bin",?"libertas/sd8688.bin"?},?? ??? ?? ?? sdio_claim_host(func);?? ?? ret?=?sdio_enable_func(func);?? if?(ret)?? ??goto?release;?? ?? 2?? ret?=?sdio_claim_irq(func,?if_sdio_interrupt);?? ret?=?if_sdio_card_to_host(card);???? ret?=?if_sdio_handle_data(card,?card->buffer?+?4,?chunk?-?4);????? ret?=?if_sdio_handle_cmd(card,?card->buffer?+?4,?chunk?-?4);????? ret?=?if_sdio_handle_event(card,?card->buffer?+?4,?chunk?-?4);?? ?? ?? ?? priv?=?lbs_add_card(card,?&func->dev);?? ?? ?? ?wdev?=?lbs_cfg_alloc(dmdev);?? ?? wdev->wiphy?=?wiphy_new(&lbs_cfg80211_ops,?sizeof(struct?lbs_private));?? ?? ??? ?? ?? dev?=?alloc_netdev(0,?"wlan%d",?ether_setup);???? dev->ieee80211_ptr?=?wdev;?? ?dev->ml_priv?=?priv;?? ??? ?SET_NETDEV_DEV(dev,?dmdev);?? ?wdev->netdev?=?dev;?? ?priv->dev?=?dev;?? ????? ??dev->netdev_ops?=?&lbs_netdev_ops;?????? ?dev->watchdog_timeo?=?5?*?HZ;?? ?dev->ethtool_ops?=?&lbs_ethtool_ops;????? ?dev->flags?|=?IFF_BROADCAST?|?IFF_MULTICAST;???? ??? ??? ??? ??? ?priv->main_thread?=?kthread_run(lbs_thread,?dev,?"lbs_main");?? ?? ?priv->work_thread?=?create_singlethread_workqueue("lbs_worker");?? ?INIT_WORK(&priv->mcast_work,?lbs_set_mcast_worker);?? ?priv->wol_criteria?=?EHS_REMOVE_WAKEUP;?? ?priv->wol_gpio?=?0xff;?? ?priv->wol_gap?=?20;?? ?priv->ehs_remove_supported?=?true;?? ??? ??? ??? ?? ?priv->hw_host_to_card?=?if_sdio_host_to_card;?? ?priv->enter_deep_sleep?=?if_sdio_enter_deep_sleep;?? ?priv->exit_deep_sleep?=?if_sdio_exit_deep_sleep;?? ?priv->reset_deep_sleep_wakeup?=?if_sdio_reset_deep_sleep_wakeup;?? ?sdio_claim_host(func);???? ?? ???? ?ret?=?lbs_start_card(priv);?? ?if?(lbs_cfg_register(priv))??? ?? ?ret?=?register_netdev(priv->dev);?? ?err?=?register_netdevice(dev);?? ?? ??? ?? ?? static?const?struct?net_device_ops?lbs_netdev_ops?=?{?? ?.ndo_open???=?lbs_dev_open,????? ?.ndo_stop??=?lbs_eth_stop,???? ?.ndo_start_xmit??=?lbs_hard_start_xmit,????? ?.ndo_set_mac_address?=?lbs_set_mac_address,????? ?.ndo_tx_timeout??=?lbs_tx_timeout,?????? ?.ndo_set_multicast_list?=?lbs_set_multicast_list,????? ?.ndo_change_mtu??=?eth_change_mtu,???? ?.ndo_validate_addr?=?eth_validate_addr,????
3、數據的接收,通過中斷的方式來解決
??? ?網絡設備接收數據的主要方法是由中斷引發(fā)設備的中斷處理函數,中斷處理函數判斷中斷的類型,如果為接收中斷,則讀取接收到的數據,分配sk_buff數據結構和數據緩沖區(qū),并將接收的數據復制到數據緩存區(qū),并調用netif_rx()函數將sk_buff傳遞給上層協(xié)議。
? ? 搜索if_sdio_interrupt,可知道它是在if_sdio.c文件中if_sdio_probe()函數中sdio_claim_irq(func, if_sdio_interrupt) ,func->irq_handler = if_sdio_interrupt。當s3cmci_irq中斷處理函數的S3C2410_SDIIMSK_SDIOIRQ 中斷被觸發(fā)時將調用if_sdio_interrupt()函數,進行接收數據。
[cpp]?view plaincopy
static?void?if_sdio_interrupt(struct?sdio_func?*func)?? ?? ret?=?if_sdio_card_to_host(card);???? ?? ?ret?=?sdio_readsb(card->func,?card->buffer,?card->ioport,?chunk);?? 1.在這里一方面處理中斷??還有2??? ?switch?(type)?{????? ?case?MVMS_CMD:?? ??ret?=?if_sdio_handle_cmd(card,?card->buffer?+?4,?chunk?-?4);????? ??if?(ret)?? ???goto?out;?? ??break;?? ?case?MVMS_DAT:?? ??ret?=?if_sdio_handle_data(card,?card->buffer?+?4,?chunk?-?4);?? ??if?(ret)?? ???goto?out;?? ??break;?? ?case?MVMS_EVENT:?? ??ret?=?if_sdio_handle_event(card,?card->buffer?+?4,?chunk?-?4);?? ??? ?? ?lbs_process_rxed_packet(card->priv,?skb);?? ??? ??? ??? ?if?(in_interrupt())?? ??netif_rx(skb);?????? ??? ??? 2?? ?ret?=?sdio_readsb(card->func,?card->buffer,?card->ioport,?chunk);?? ?? int?sdio_readsb(struct?sdio_func?*func,?void?*dst,?unsigned?int?addr,?int?count)?? ?? ?????????return?sdio_io_rw_ext_helper(func,?0,?addr,?0,?dst,?count);?? ?? ????????????????ret?=?mmc_io_rw_extended(func->card,?write,func->num,?addr,?incr_addr,?buf,blocks,?func->cur_blksize);?? ?????????????????????????cmd.arg?=?write???0x80000000?:?0x00000000;?? ?????????????????????????????????? ?????????????????????? ?????????????????????mmc_wait_for_req(card->host,?&mrq);?? ????????????????????????開始應答??? ?????????????????????????mmc_start_request(host,?mrq);?? ?????????????????????????wait_for_completion(&complete);?? ?????????????????????????????????????? ?????????????????????????????host->ops->request(host,?mrq);??
4、
數據發(fā)送
[cpp]?view plaincopy
?? int?dev_queue_xmit(struct?sk_buff?*skb)?? ?? ????if?(!netif_tx_queue_stopped(txq))?{?? ????__this_cpu_inc(xmit_recursion);?? ????? ????rc?=?dev_hard_start_xmit(skb,?dev,?txq);?? ???? ?? ??rc?=?ops->ndo_start_xmit(skb,?dev);?? ?? ??dev->netdev_ops?=?&lbs_netdev_ops;?????? ?? ??? ?priv->main_thread?=?kthread_run(lbs_thread,?dev,?"lbs_main");?? ?? ????? ???int?ret?=?priv->hw_host_to_card(priv,?MVMS_DAT,priv->tx_pending_buf,priv->tx_pending_len);?? 為什么是if_sdio_to_host呢??因為在prob函數中定義了這一個?? ?? ?priv->hw_host_to_card?=?if_sdio_host_to_card;?? ????? static?int?if_sdio_host_to_card(struct?lbs_private?*priv,u8?type,?u8?*buf,?u16?nb)?? ???????? ?????????memcpy(packet->buffer?+?4,?buf,?nb);?? ?? ?????????queue_work(card->workqueue,?&card->packet_worker);?? ??? ?INIT_WORK(&card->packet_worker,?if_sdio_host_to_card_worker);?? ?? ??? ???ret?=?sdio_writesb(card->func,?card->ioport,?packet->buffer,?packet->nb);?? ??????????? ???????????????ret?=?mmc_io_rw_extended(func->card,?write,func->num,?addr,?incr_addr,?buf,blocks,?func->cur_blksize);?? ?? ?????????????????????? ?????????????????????????????????mmc_wait_for_req(card->host,?&mrq);?? ???????????????????????????????????? ?????????????????????????????mrq->done_data?=?&complete;?? ?????????????????????????????mrq->done?=?mmc_wait_done;?? ?????????????????????????????mmc_start_request(host,?mrq);?? ?????????????????????????????????? ?????????????????????????????wait_for_completion(&complete);?? ??? ??? ?????????????????????????????host->ops->request(host,?mrq);?? ?????
5、移除函數
? ? ? ?當sdio卡拔除時,驅動會調用該函數,完成相應操作。如釋放占有的資源,禁止func功能函數,釋放host。
[cpp]?view plaincopy
if_sdio_remove(struct?sdio_func?*func)?? ---->lbs_stop_card(card->priv);?? ?????lbs_remove_card(card->priv);?? ?????---->kthread_stop(priv->main_thread);???? ?? ?????????lbs_free_adapter(priv);?? ?????????lbs_cfg_free(priv);?? ??????????free_netdev(dev);?? ?? ?????flush_workqueue(card->workqueue);???? ?????destroy_workqueue(card->workqueue);?? ?????sdio_claim_host(func);?? ?????sdio_release_irq(func);?? ?????sdio_disable_func(func);?? ??????sdio_release_host(func); ?
總結
以上是生活随笔為你收集整理的Linux 下wifi 驱动开发(三)—— SDIO接口WiFi驱动浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。