SDIO-Wifi模塊是基於SDIO接口的符合wifi無線網絡標准的嵌入式模塊,內置無線網絡協議IEEE802.11協議棧以及TCP/IP協議棧,能夠實現用戶主平台數據通過SDIO口到無線網絡之間的轉換。SDIO具有傳輸數據快,兼容SD、MMC接口等特點。
對於SDIO接口的wifi,首先,它是一個sdio的卡的設備,然后具備了wifi的功能,所以,注冊的時候還是先以sdio的卡的設備去注冊的。然后檢測到卡之后就要驅動他的wifi功能了,顯然,他是用sdio的協議,通過發命令和數據來控制的。sdio協議是一種單主多從模式。
MMC/SD/SDIO的驅動程序主要分為兩大塊,主設備驅動和從設備驅動。對於wifi來說,CPU上的MMC模塊就是主設備,而WIFI模塊就是從設備。本文主要分析wifi sdio driver的注冊。
1 wifi模塊驅動作為sdio的從設備
wifi模塊驅動的通用的軟件架構
(1)分為兩部分,上面為linux的wifi驅動,下面是wifi chip端的firmware
(2)其中固件部分的主要工作是:因為天線接受和發送回來的都是802.11幀的幀,而主機接受和傳送出來的數據都必須是802.3的幀,所以必須由firmware來負責802.3的幀和802.11幀之間的轉換。所以linux中有線網絡和無線網絡驅動是復用的。
(3)當天線收到數據,並被firmware處理好后會放在一個buffer里,並產生一個中斷,主機在收到中斷后就去讀這個buffer。
SDIO設備的驅動由sdio_driver結構體定義,sdio_driver其實是driver的封裝。通過sdio_register_driver函數將SDIO設備驅動加載進內核,其實就是掛載到sdio_bus_type總線上去。
1 sdio driver的注冊
以linux-4.9.73\drivers\net\wireless\marvell\libertas\If_sdio.c的wifi driver為例
driver module init
1 static int __init if_sdio_init_module(void) 2 { 3 int ret = 0; 4 5 lbs_deb_enter(LBS_DEB_SDIO); 6 7 printk(KERN_INFO "libertas_sdio: Libertas SDIO driver\n"); 8 printk(KERN_INFO "libertas_sdio: Copyright Pierre Ossman\n"); 9 10 ret = sdio_register_driver(&if_sdio_driver);//注冊sdio從設備的driver 11 12 /* Clear the flag in case user removes the card. */ 13 user_rmmod = 0; 14 15 lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); 16 17 return ret; 18 }
if_sdio_driver的定義
1 static struct sdio_driver if_sdio_driver = { 2 .name = "libertas_sdio", 3 .id_table = if_sdio_ids, 4 .probe = if_sdio_probe, 5 .remove = if_sdio_remove, 6 .drv = { 7 .pm = &if_sdio_pm_ops, 8 }, 9 };
sdio_register_driver
1 int sdio_register_driver(struct sdio_driver *drv) 2 { 3 drv->drv.name = drv->name;//幫忙driver name 4 drv->drv.bus = &sdio_bus_type;//綁定總線 5 return driver_register(&drv->drv);//向內核注冊driver 6 }
sdio driver probe函數
1 static int if_sdio_probe(struct sdio_func *func, 2 const struct sdio_device_id *id) 3 { 4 struct if_sdio_card *card;//定義一個 if_sdio card的結構體 5 struct lbs_private *priv; 6 int ret, i; 7 unsigned int model; 8 struct if_sdio_packet *packet;//sdio 包的結構體 9 10 lbs_deb_enter(LBS_DEB_SDIO); 11 /*// 查詢是否有指定的功能寄存器在mmc_sdio_card中*/ 12 for (i = 0;i < func->card->num_info;i++) { 13 if (sscanf(func->card->info[i], 14 "802.11 SDIO ID: %x", &model) == 1) 15 break; 16 if (sscanf(func->card->info[i], 17 "ID: %x", &model) == 1) 18 break; 19 if (!strcmp(func->card->info[i], "IBIS Wireless SDIO Card")) { 20 model = MODEL_8385; 21 break; 22 } 23 } 24 25 if (i == func->card->num_info) { 26 pr_err("unable to identify card model\n"); 27 return -ENODEV; 28 } 29 30 card = kzalloc(sizeof(struct if_sdio_card), GFP_KERNEL);//分配card 31 if (!card) 32 return -ENOMEM; 33 34 card->func = func; 35 card->model = model; 36 //在這里進行片選 選擇到使用的marvell 8686 的設備 37 switch (card->model) { 38 case MODEL_8385: 39 card->scratch_reg = IF_SDIO_SCRATCH_OLD; 40 break; 41 case MODEL_8686: 42 card->scratch_reg = IF_SDIO_SCRATCH; 43 break; 44 case MODEL_8688: 45 default: /* for newer chipsets */ 46 card->scratch_reg = IF_SDIO_FW_STATUS; 47 break; 48 } 49 50 spin_lock_init(&card->lock); 51 card->workqueue = alloc_workqueue("libertas_sdio", WQ_MEM_RECLAIM, 0);//分配隊列 52 INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker); 53 init_waitqueue_head(&card->pwron_waitq); 54 55 /* Check if we support this card */ 56 for (i = 0; i < ARRAY_SIZE(fw_table); i++) { 57 if (card->model == fw_table[i].model) 58 break; 59 } 60 if (i == ARRAY_SIZE(fw_table)) { 61 pr_err("unknown card model 0x%x\n", card->model); 62 ret = -ENODEV; 63 goto free; 64 } 65 66 sdio_set_drvdata(func, card); 67 68 lbs_deb_sdio("class = 0x%X, vendor = 0x%X, " 69 "device = 0x%X, model = 0x%X, ioport = 0x%X\n", 70 func->class, func->vendor, func->device, 71 model, (unsigned)card->ioport); 72 73 74 priv = lbs_add_card(card, &func->dev);//添加網絡結構體 分配設備並注冊 75 if (!priv) { 76 ret = -ENOMEM; 77 goto free; 78 } 79 80 card->priv = priv; 81 82 priv->card = card; 83 priv->hw_host_to_card = if_sdio_host_to_card; 84 priv->enter_deep_sleep = if_sdio_enter_deep_sleep; 85 priv->exit_deep_sleep = if_sdio_exit_deep_sleep; 86 priv->reset_deep_sleep_wakeup = if_sdio_reset_deep_sleep_wakeup; 87 priv->reset_card = if_sdio_reset_card; 88 priv->power_save = if_sdio_power_save; 89 priv->power_restore = if_sdio_power_restore; 90 priv->is_polling = !(func->card->host->caps & MMC_CAP_SDIO_IRQ); 91 ret = if_sdio_power_on(card); 92 if (ret) 93 goto err_activate_card; 94 95 out: 96 lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); 97 98 return ret; 99 100 err_activate_card: 101 flush_workqueue(card->workqueue); 102 lbs_remove_card(priv); 103 free: 104 destroy_workqueue(card->workqueue); 105 while (card->packets) { 106 packet = card->packets; 107 card->packets = card->packets->next; 108 kfree(packet); 109 } 110 111 kfree(card); 112 113 goto out; 114 }
函數lbs_add_card
1 struct lbs_private *lbs_add_card(void *card, struct device *dmdev) 2 { 3 struct net_device *dev; 4 struct wireless_dev *wdev;//創建無線設備結構體 5 struct lbs_private *priv = NULL; 6 7 lbs_deb_enter(LBS_DEB_MAIN); 8 9 /* Allocate an Ethernet device and register it */ 10 wdev = lbs_cfg_alloc(dmdev);//分配無線設備結構體 11 if (IS_ERR(wdev)) { 12 pr_err("cfg80211 init failed\n"); 13 goto done; 14 } 15 16 wdev->iftype = NL80211_IFTYPE_STATION; 17 priv = wdev_priv(wdev); 18 priv->wdev = wdev; 19 20 if (lbs_init_adapter(priv)) { 21 pr_err("failed to initialize adapter structure\n"); 22 goto err_wdev; 23 } 24 25 dev = alloc_netdev(0, "wlan%d", NET_NAME_UNKNOWN, ether_setup);//分配網絡設備 26 if (!dev) { 27 dev_err(dmdev, "no memory for network device instance\n"); 28 goto err_adapter; 29 } 30 31 dev->ieee80211_ptr = wdev; 32 dev->ml_priv = priv; 33 SET_NETDEV_DEV(dev, dmdev); 34 wdev->netdev = dev;//網絡設備結構體賦值給無線設備結構體 35 priv->dev = dev; 36 37 dev->netdev_ops = &lbs_netdev_ops;//賦值設備操作函數指針結構體 38 dev->watchdog_timeo = 5 * HZ; 39 dev->ethtool_ops = &lbs_ethtool_ops; 40 dev->flags |= IFF_BROADCAST | IFF_MULTICAST; 41 42 priv->card = card; 43 44 strcpy(dev->name, "wlan%d");//wifi name 45 46 lbs_deb_thread("Starting main thread...\n"); 47 init_waitqueue_head(&priv->waitq); 48 priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");//創建main函數thread處理RX/TX 49 if (IS_ERR(priv->main_thread)) { 50 lbs_deb_thread("Error creating main thread.\n"); 51 goto err_ndev; 52 } 53 54 priv->work_thread = create_singlethread_workqueue("lbs_worker"); 55 INIT_WORK(&priv->mcast_work, lbs_set_mcast_worker); 56 57 priv->wol_criteria = EHS_REMOVE_WAKEUP; 58 priv->wol_gpio = 0xff; 59 priv->wol_gap = 20; 60 priv->ehs_remove_supported = true; 61 62 goto done; 63 64 err_ndev: 65 free_netdev(dev); 66 67 err_adapter: 68 lbs_free_adapter(priv); 69 70 err_wdev: 71 lbs_cfg_free(priv); 72 73 priv = NULL; 74 75 done: 76 lbs_deb_leave_args(LBS_DEB_MAIN, "priv %p", priv); 77 return priv; 78 }
函數lbs_cfg_alloc
1 struct wireless_dev *lbs_cfg_alloc(struct device *dev) 2 { 3 int ret = 0; 4 struct wireless_dev *wdev; 5 6 lbs_deb_enter(LBS_DEB_CFG80211); 7 8 wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL);//分配無線設備結構體 9 if (!wdev) 10 return ERR_PTR(-ENOMEM); 11 12 wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private));//注冊網絡設備??? 13 if (!wdev->wiphy) { 14 dev_err(dev, "cannot allocate wiphy\n"); 15 ret = -ENOMEM; 16 goto err_wiphy_new; 17 } 18 19 lbs_deb_leave(LBS_DEB_CFG80211); 20 return wdev; 21 22 err_wiphy_new: 23 kfree(wdev); 24 lbs_deb_leave_args(LBS_DEB_CFG80211, "ret %d", ret); 25 return ERR_PTR(ret); 26 }
2 sdio device
sdio device系統啟動時根據dts來創建,然后根據sdio_bus_match函數匹配上述的sdio driver
3 sdio bus
sdio bus定義
1 static struct bus_type sdio_bus_type = {
2 .name = "sdio", 3 .dev_groups = sdio_dev_groups, 4 .match = sdio_bus_match, 5 .uevent = sdio_bus_uevent, 6 .probe = sdio_bus_probe, 7 .remove = sdio_bus_remove, 8 .pm = &sdio_bus_pm_ops, 9 };
sdio_bus_match函數
1 static int sdio_bus_match(struct device *dev, struct device_driver *drv) 2 { 3 struct sdio_func *func = dev_to_sdio_func(dev); 4 struct sdio_driver *sdrv = to_sdio_driver(drv); 5 6 if (sdio_match_device(func, sdrv)) 7 return 1; 8 9 return 0; 10 } 11 12 static const struct sdio_device_id *sdio_match_device(struct sdio_func *func, 13 struct sdio_driver *sdrv) 14 { 15 const struct sdio_device_id *ids; 16 17 ids = sdrv->id_table; 18 19 if (ids) { 20 while (ids->class || ids->vendor || ids->device) { 21 if (sdio_match_one(func, ids))//根據device id來匹配 22 return ids; 23 ids++; 24 } 25 } 26 27 return NULL; 28 }
4 wifi數據接收
數據的接收,通過中斷的方式來解決
網絡設備接收數據的主要方法是由中斷引發設備的中斷處理函數,中斷處理函數判斷中斷的類型,如果為接收中斷,則讀取接收到的數據,分配sk_buff數據結構和數據緩沖區,並將接收的數據復制到數據緩存區,並調用netif_rx()函數將sk_buff傳遞給上層協議。
搜索if_sdio_interrupt,可知道它是在if_sdio.c文件中if_sdio_finish_power_on函數中sdio_claim_irq(func, if_sdio_interrupt) ,func->irq_handler = if_sdio_interrupt。當s3cmci_irq中斷處理函數的S3C2410_SDIIMSK_SDIOIRQ 中斷被觸發時將調用if_sdio_interrupt()函數,進行接收數據。
1 /* Finish power on sequence (after firmware is loaded) */ 2 static void if_sdio_finish_power_on(struct if_sdio_card *card) 3 { 4 ... 5 ret = sdio_claim_irq(func, if_sdio_interrupt); 6 ... 7 } 8 9 static void if_sdio_interrupt(struct sdio_func *func) 10 { 11 int ret; 12 struct if_sdio_card *card; 13 u8 cause; 14 15 lbs_deb_enter(LBS_DEB_SDIO); 16 17 card = sdio_get_drvdata(func); 18 19 cause = sdio_readb(card->func, IF_SDIO_H_INT_STATUS, &ret);//讀取端口上的數據 ,放到card的buffer中 20 if (ret || !cause) 21 goto out; 22 23 lbs_deb_sdio("interrupt: 0x%X\n", (unsigned)cause); 24 25 sdio_writeb(card->func, ~cause, IF_SDIO_H_INT_STATUS, &ret); 26 if (ret) 27 goto out; 28 29 /* 30 * Ignore the define name, this really means the card has 31 * successfully received the command. 32 */ 33 card->priv->is_activity_detected = 1; 34 if (cause & IF_SDIO_H_INT_DNLD) 35 lbs_host_to_card_done(card->priv); 36 37 38 if (cause & IF_SDIO_H_INT_UPLD) { 39 ret = if_sdio_card_to_host(card);//從無線網卡接收到數據 或者說是上報數據 40 if (ret) 41 goto out; 42 } 43 44 ret = 0; 45 46 out: 47 lbs_deb_leave_args(LBS_DEB_SDIO, "ret %d", ret); 48 }
5 wifi數據的發送
1 //IP層通過dev_queue_xmit()將數據交給網絡設備協議接口層,網絡接口層通過netdevice中的注冊函數的數據發送函數 2 int dev_queue_xmit(struct sk_buff *skb) 3 4 if (!netif_tx_queue_stopped(txq)) { 5 __this_cpu_inc(xmit_recursion); 6 //設備硬件開始發送 7 rc = dev_hard_start_xmit(skb, dev, txq); 8 //調用wifi網絡中的ops 9 10 rc = ops->ndo_start_xmit(skb, dev); 11 12 dev->netdev_ops = &lbs_netdev_ops; //設備的操作函數 13 14 //處理sdio firware數據和內核的數據main_thread 主線程 15 priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main"); 16 17 //調用host_to_card 即if_sdio_card_to_host函數。 18 int ret = priv->hw_host_to_card(priv, MVMS_DAT,priv->tx_pending_buf,priv->tx_pending_len); 19 為什么是if_sdio_to_host呢 ?因為在prob函數中定義了這一個 20 //設置主機發送數據到卡 21 priv->hw_host_to_card = if_sdio_host_to_card; 22 23 static int if_sdio_host_to_card(struct lbs_private *priv,u8 type, u8 *buf, u16 nb) 24 //把buf中的數據 copy到sdio 包中,在對sdio 的包進行處理 25 memcpy(packet->buffer + 4, buf, nb); 26 //創建工作隊列 27 queue_work(card->workqueue, &card->packet_worker); 28 //初始化隊列 29 INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker); 30 31 //sdio的寫數據 32 ret = sdio_writesb(card->func, card->ioport, packet->buffer, packet->nb); 33 //mmc寫擴展口 34 ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize); 35 36 //wait for request 37 mmc_wait_for_req(card->host, &mrq); 38 39 mrq->done_data = &complete; 40 mrq->done = mmc_wait_done; 41 mmc_start_request(host, mrq); 42 //完成等待 寫數據結束 43 wait_for_completion(&complete); 44 45 46 host->ops->request(host, mrq); 47 //到底結束 發送數據