SDIO接口WiFi驱动浅析[通俗易懂]SDIO-Wifi模块是基于SDIO接口的符合wifi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转换。SDIO具有传输数据快,兼容SD、MMC接口等特点。 对于SDIO接口的wifi,首先,它是一个sdio的卡的设备,然后具备了wifi的功能,所以,注册的时候还是先以sdio的卡的
大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。 Jetbrains全系列IDE稳定放心使用
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 ,而当卡被移除后就会调用I f_sdio_remove 。
下面先看下If_sdio_probe t函数,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);
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/186181.html 原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...