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 //到底结束 发送数据
行胜于言,自强不息。