linux设备驱动-wifi驱动详解2 sdio_wifi驱动详解
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 //到底结束 发送数据