SDIO-Wifi模塊是基於SDIO接口的符合wifi無線網絡標準的嵌入式模塊,內置無線網絡協議IEEE802.11協議棧以及TCP/IP協議棧,能夠實現用戶主平臺數據通過SDIO口到無線網絡之間的轉換。SDIO具有傳輸數據快,兼容SD、MMC接口等特點。
對於SDIO接口的wifi,首先,它是一個sdio的卡的設備,然後具備了wifi的功能,所以,註冊的時候還是先以sdio的卡的設備去註冊的。然後檢測到卡之後就要驅動他的wifi功能了,顯然,他是用sdio的協議,通過發命令和數據來控制的。下面先簡單回顧一下SDIO的相關知識:
一、SDIO相關基礎知識解析
1、SDIO接口
SDIO 故名思義,就是 SD 的 I/O 接口(interface)的意思,不過這樣解釋可能還有點抽像。更具體的說明,SD 本來是記憶卡的標準,但是現在也可以把 SD 拿來插上一些外圍接口使用,這樣的技術便是 SDIO。
所以 SDIO 本身是一種相當單純的技術,透過 SD 的 I/O 接腳來連接外部外圍,並且透過 SD 上的 I/O 數據接位與這些外圍傳輸數據,而且 SD 協會會員也推出很完整的 SDIO stack 驅動程序,使得 SDIO 外圍(我們稱爲SDIO 卡)的開發與應用變得相當熱門。
現在已經有非常多的手機或是手持裝置都支持 SDIO 的功能(SD 標準原本就是針對 mobile device 而制定),而且許多 SDIO 外圍也都被開發出來,讓手機外接外圍更加容易,並且開發上更有彈性(不需要內建外圍)。目前常見的 SDIO 外圍(SDIO 卡)有:
· Wi-Fi card(無線網絡卡)
· CMOS sensor card(照相模塊)
· GPS card
· GSM/GPRS modem card
· Bluetooth card
SDIO 的應用將是未來嵌入式系統最重要的接口技術之一,並且也會取代目前 GPIO 式的 SPI 接口。
2、SDIO總線
SDIO總線 和 USB總線 類似,SDIO也有兩端,其中一端是HOST端,另一端是device端。所有的通信都是由HOST端 發送 命令 開始的,Device端只要能解析命令,就可以相互通信。
CLK信號:HOST給DEVICE的 時鐘信號,每個時鐘週期傳輸一個命令。
CMD信號:雙向 的信號,用於傳送 命令 和 反應。
DAT0-DAT3 信號:四條用於傳送的數據線。
VDD信號:電源信號。
VSS1,VSS2:電源地信號。
3、SDIO熱插拔原理
方法:設置一個 定時器檢查 或 插拔中斷檢測
硬件:假如GPG10(EINT18)用於SD卡檢測
GPG10 爲高電平 即沒有插入SD卡
GPG10爲低電平 即插入了SD卡
4、SDIO命令
SDIO總線上都是HOST端發起請求,然後DEVICE端迴應請求。sdio命令由6個字節組成。
a -- Command:用於開始傳輸的命令,是由HOST端發往DEVICE端的。其中命令是通過CMD信號線傳送的。
b -- Response:迴應是DEVICE返回的HOST的命令,作爲Command的迴應。也是通過CMD線傳送的。
c -- Data:數據是雙向的傳送的。可以設置爲1線模式,也可以設置爲4線模式。數據是通過DAT0-DAT3信號線傳輸的。
SDIO的每次操作都是由HOST在CMD線上發起一個CMD,對於有的CMD,DEVICE需要返回Response,有的則不需要。
對於讀命令,首先HOST會向DEVICE發送命令,緊接着DEVICE會返回一個握手信號,此時,當HOST收到迴應的握手信號後,會將數據放在4位的數據線上,在傳送數據的同時會跟隨着CRC校驗碼。當整個讀傳送完畢後,HOST會再次發送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。
對於寫命令,首先HOST會向DEVICE發送命令,緊接着DEVICE會返回一個握手信號,此時,當HOST收到迴應的握手信號後,會將數據放在4位的數據線上,在傳送數據的同時會跟隨着CRC校驗碼。當整個寫傳送完畢後,HOST會再次發送一個命令,通知DEVICE操作完畢,DEVICE同時會返回一個響應。
二、SDIO接口驅動
前面講到,SDIO接口的wifi,首先,它是一個sdio的卡的設備,然後具備了wifi的功能,所以SDIO接口的WiFi驅動就是在wifi驅動外面套上了一個SDIO驅動的外殼,SDIO驅動仍然符合設備驅動的分層與分離思想:
設備驅動層(wifi 設備)
|
核心層(向上向下提供接口)
|
主機驅動層 (實現SDIO驅動)
下面先分析SDIO接口驅動的實現,看幾個重要的數據結構(用於核心層與主機驅動層 的數據交換處理)。
[ /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卡驅動開發(二) —— SD 卡驅動分析HOST篇 有詳細闡述,下面只簡單回顧一下一些重要函數處理
1、編寫Host層驅動
這裏參考的是S3C24XX的HOST驅動程序 /drivers/mmc/host/s3cmci.c
-
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]
-
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設備,會觸發中斷通知到CPU,然後執行卡檢測中斷處理函數在這個中斷服務函數中,mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay), INIT_DELAYED_WORK(&host->detect, mmc_rescan)會調度mmc_rescan函數延時調度工作隊列,這樣也會觸發SDIO設備的初始化流程,檢測到有效的SDIO設備後,會將它註冊到系統中去。
-
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. 其中固件部分的主要工作是:因爲天線接受和發送回來的都是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]
-
-
-
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;
-
};
下面是具體函數的填充:
-
-
-
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,
-
}
-
};
設備註冊函數
-
-
-
-
-
-
int sdio_register_driver(struct sdio_driver *drv)
-
{
-
drv->drv.name = drv->name;
-
drv->drv.bus = &sdio_bus_type;
-
return driver_register(&drv->drv);
-
}
總線函數
-
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,
-
};
注意:設備或者驅動註冊到系統中的過程中,都會調用相應bus上的匹配函數來進行匹配合適的驅動或者設備,對於sdio設備的匹配是由sdio_bus_match和sdio_bus_probe函數來完成。
-
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 函數 主要做了兩件事
-
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、數據的接收,通過中斷的方式來解決
網絡設備接收數據的主要方法是由中斷引發設備的中斷處理函數,中斷處理函數判斷中斷的類型,如果爲接收中斷,則讀取接收到的數據,分配sk_buff數據結構和數據緩衝區,並將接收的數據複製到數據緩存區,並調用netif_rx()函數將sk_buff傳遞給上層協議。
搜索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 中斷被觸發時將調用if_sdio_interrupt()函數,進行接收數據。
-
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、數據發送
-
-
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。
-
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);