Linux 驱动开发之WIFI设备分析3

Linux 驱动开发之WIFI设备分析3(基于Linux6.6)---SDIO接口WiFi介绍

一、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驱动的外壳,

在 Linux 内核中,设备驱动的设计通常遵循分层结构,即将不同层次的功能进行模块化,以便于管理、扩展和维护。主要的设计思想包括:

  1. 硬件抽象层:提供对不同硬件设备的统一接口,使得上层软件不需要直接与硬件打交道。硬件的细节和实现被封装在较低层,驱动程序仅关心上层所需的接口。

  2. 分离硬件与操作系统的依赖:驱动程序不仅要能在不同的硬件上运行,还要与操作系统的其他部分进行适配和配合。因此,硬件相关的代码和操作系统核心代码应当分离,减少耦合。

  3. 设备类型抽象:不同种类的设备(如块设备、字符设备、网络设备等)会有不同的处理方式,驱动程序通过在内核中对设备进行类型化,来实现设备与操作系统的松耦合。

下面先分析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 当中给的函数指针操作,不能直接调用具体主控制器的函数。

2.1、编写Host层驱动

例:drivers/mmc/host/s3cmci.c

MODULE_DEVICE_TABLE(platform, s3cmci_driver_ids);

static struct platform_driver s3cmci_driver = {
	.driver	= {
		.name	= "s3c-sdi",
		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
		.of_match_table = s3cmci_dt_match,
	},
	.id_table	= s3cmci_driver_ids,
	.probe		= s3cmci_probe,
	.remove		= s3cmci_remove,
	.shutdown	= s3cmci_shutdown,
};

module_platform_driver(s3cmci_driver);

s3cmci_probe(struct platform_device *pdev)
{
	//....
	struct mmc_host *mmc;
	mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);  //分配mmc_host结构体
 
	//.....
}
 
/*注册中断处理函数s3cmci_irq,来处理数据收发过程引起的各种中断*/
request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host) //注册中断处理函数s3cmci_irq
 
/*注册中断处理s3cmci_irq_cd函数,来处理热拨插引起的中断,中断触发的形式为上升沿、下降沿触发*/
request_irq(host->irq_cd, s3cmci_irq_cd,IRQF_TRIGGER_RISING |IRQF_TRIGGER_FALLING, DRIVER_NAME, host)
 
mmc_add_host(mmc);  //initialise host hardware //向MMC core注册host驱动
----> device_add(&host->class_dev); //添加设备到mmc_bus_type总线上的设备链表中
----> mmc_start_host(host); //启动mmc host
 
/*MMC drivers should call this when they detect a card has been inserted or removed.检测sd卡是否插上或移除*/
 ---->mmc_detect_change(host, 0);
 
/*Schedule delayed work in the MMC work queue.调度延时工作队列*/
 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);//card 从bus上移除时,释放它占有的总线空间
 
/*判断当前mmc host控制器是否被占用,当前mmc控制器如果被占用,那么  host->claimed = 1;否则为0
 *如果为1,那么会在while(1)循环中调用schedule切换出自己,当占用mmc控制器的操作完成之后,执行 *mmc_release_host()的时候,会激活登记到等待队列&host->wq中的其他 程序获得mmc主控制器的使用权
 */
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)
{
     …
     /* Order's important: probe SDIO, then SD, then MMC */
     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)  //匹配sdio接口卡
     --->mmc_attach_bus(host, &mmc_sdio_ops);
 
/*当card与总线上的驱动匹配,就初始化card*/
mmc_sdio_init_card(host, host->ocr, NULL, 0); 
    --->card = mmc_alloc_card(host, NULL);//分配一个card结构体
          mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); //设置mmc_bus的工作模式
 
struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; //SDIO functions (devices)
 
sdio_init_func(host->card, i + 1);
    --->func = sdio_alloc_func(card); //分配struct sdio_fun(sdio功能设备)结构体
          mmc_io_rw_direct();
          card->sdio_func[fn - 1] = func;
 
mmc_add_card(host->card);  //将具体的sdio设备挂载到mmc_bus_types 总线
sdio_add_func(host->card->sdio_func[i]); //将sdio功能设备挂载到sdio_bus_types总线

2.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 驱动部分解析

Linux Wi-Fi 驱动的通用软件架构的各个主要组成部分:

1. 硬件抽象层(HAL)

硬件抽象层是 Wi-Fi 驱动的底层部分,它负责与 Wi-Fi 硬件设备直接交互,管理与硬件相关的操作,如初始化、数据传输、信号处理等。

  • 硬件控制:Wi-Fi 控制器的初始化、关闭、重置等。
  • 数据传输:Wi-Fi 驱动负责处理与硬件的数据交换,包括信号的收发。
  • 信号处理:Wi-Fi 驱动会处理与信号强度、链路质量等相关的操作。
  • 中断处理:驱动负责处理中断,并将硬件事件通知上层软件(如数据包的接收、传输完成通知等)。

在大多数情况下,硬件抽象层(HAL)是特定于硬件的,这意味着不同的 Wi-Fi 硬件会有不同的 HAL。

2. 网卡驱动(NIC Driver)

网卡驱动(Network Interface Card Driver)是与硬件设备进行交互的驱动程序,它负责将从硬件中获取的数据传递到 Linux 内核中的网络子系统,并处理通过网络栈发送的数据包。

网卡驱动包含以下功能:

  • 初始化与配置:为 Wi-Fi 卡配置正确的硬件资源(如内存、DMA、IO 映射等)并完成初始化。
  • 发送与接收数据包:从上层协议栈(如 TCP/IP 协议栈)获取数据包并通过硬件发送出去,同时接收硬件发来的数据并转交给上层网络协议栈。
  • 管理无线链路:例如,连接、断开、扫描周围网络等功能。
  • 中断处理:处理硬件中断,并将事件传递给上层。
  • 资源管理:包括管理缓冲区、DMA(直接内存存取)和内存等资源。

网卡驱动通过 Linux 的 mac80211 子系统进行与网络栈的交互,通常使用 cfg80211mac80211 协议栈来处理无线网络通信。

3. 无线协议栈

无线协议栈是管理 Wi-Fi 连接的关键部分,主要负责以下几个方面:

cfg80211

cfg80211 是 Linux 内核中用于支持无线设备的配置和管理的子系统,它是 Wi-Fi 驱动的核心协议层,处理与无线硬件设备的交互。其主要功能包括:

  • 扫描:扫描可用的 Wi-Fi 网络并返回扫描结果。
  • 连接管理:负责连接到指定的 Wi-Fi 网络、加入或离开一个无线网络。
  • 信号强度报告:向用户空间报告无线信号的强度、质量等。
  • 频道管理:管理无线信号的频道选择和切换。
  • 多频段支持:支持 2.4 GHz、5 GHz 以及未来的 6 GHz 频段。

mac80211

mac80211 是 Linux 无线栈中的一个中间层,它基于 cfg80211 提供的功能来实现更高层的管理和控制。mac80211 主要实现 IEEE 802.11 无线协议标准的功能,提供如下功能:

  • 数据包处理:包括数据包的加密、解密、封装、解封装等。
  • 信道管理:管理无线信道的使用,包括信道切换、干扰避免等。
  • 帧处理:负责无线帧的接收与发送(如控制帧、数据帧等)。
  • 支持 802.11n/ac/ax 标准:实现高效的无线通信协议,支持 MIMO、波束成形、多用户 MIMO 等技术。

mac80211 通过与 cfg80211 协同工作,完成了大多数无线功能,而无需硬件驱动重复实现这些协议栈。

4. 用户空间接口

用户空间应用程序通过与内核的接口来控制和管理 Wi-Fi 设备,常见的接口包括:

wpa_supplicant

wpa_supplicant 是一个用户空间的程序,用于管理 Wi-Fi 连接。它与 cfg80211mac80211 协同工作,提供以下功能:

  • 无线网络认证:支持 WPA、WPA2、WPA3 等无线安全协议。
  • 自动连接:通过扫描可用的网络并选择合适的网络进行连接。
  • 连接配置:管理网络的配置文件,控制连接的参数。
  • EAP(扩展认证协议):支持企业网络中的 EAP 认证方式。

NetworkManager

NetworkManager 是一个高层的网络管理工具,提供统一的网络接口管理功能,支持有线、无线、VPN 等不同类型的连接。它通过 D-Bus 与 wpa_supplicantcfg80211 协同工作,提供更简便的网络配置管理和自动化连接。

iw

iw 是一个用于配置无线设备的命令行工具,提供如网络扫描、信号强度检测、频道切换等操作功能。它通过与 cfg80211 进行交互,实现对无线设备的管理。

5. 系统资源管理

Wi-Fi 驱动需要与 Linux 内核的资源管理系统紧密配合,确保对硬件资源(如内存、IRQ、DMA 等)的高效管理。它包括:

  • 内存管理:如 DMA(直接内存存取)的内存分配和释放。
  • 中断管理:通过请求中断线、处理硬件中断并传递信息到上层。
  • 电源管理:Wi-Fi 驱动通常会集成省电机制,在设备空闲时自动进入低功耗模式。

6. 调度与同步

Wi-Fi 驱动需要通过多线程和中断机制进行数据的异步处理。调度机制确保 Wi-Fi 驱动和网络栈的任务能够有效地并发执行。同步机制则避免了资源竞争,确保数据包能够在适当的时机被发送或接收。      

SDIO设备的驱动由sdio_driver结构体定义,sdio_driver其实是driver的封装。通过sdio_register_driver函数将SDIO设备驱动加载进内核,其实就是挂载到sdio_bus_type总线上去。

3.1、设备驱动的注册与匹配

drivers/net/wireless/marvell/libertas/if_sdio.c

/* SDIO function device driver*/
 
struct sdio_driver {
     char *name;  //设备名
     const struct sdio_device_id *id_table; //设备驱动ID
     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,
         }
};

设备注册函数

/**
 *   sdio_register_driver - register a function driver
 *   @drv: SDIO function driver
 */
 
int sdio_register_driver(struct sdio_driver *drv)
{
     drv->drv.name = drv->name;
     drv->drv.bus = &sdio_bus_type;  //设置driver的bus为sdio_bus_type
     return driver_register(&drv->drv);
}

注意:设备或者驱动注册到系统中的过程中,都会调用相应bus上的匹配函数来进行匹配合适的驱动或者设备,对于sdio设备的匹配是由sdio_bus_matchsdio_bus_probe函数来完成。

sdio_bus_match(struct 

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函数,来完成相关操作。

3.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 //定义一个 if_sdio  card的结构体
 struct if_sdio_card *card;
 struct if_sdio_packet *packet;  //sdio 包的结构体 
 struct mmc_host *host = func->card->host;
 
 // 查询是否有指定的功能寄存器在mmc
   //_sdio_card中
 for (i = 0;i < func->card->num_info;i++) {
  if (sscanf(func->card->info[i],
    "802.11 SDIO ID: %x", &model) == 1)
 
//在这里进行片选  选择到我们使用的marvell 8686 的设备
case MODEL_8686:
  card->scratch_reg = IF_SDIO_SCRATCH;
 
 
//创建sdio 的工作队列 
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)
 
 /* Check if we support this card  选择我们所支持的卡的类型*/
  //赋值为sd8686_helper.bin   sd8686.bin
/*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" },
 
//申请一个host
sdio_claim_host(func);
//使能sdio 的功能 寄存器
ret = sdio_enable_func(func);
if (ret)
  goto release;
2//申请 sdio 的中断  当有数据  ,命令 或者是事件 的时间执行中断
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);
//分配Ethernet设备并注册 
 wdev = lbs_cfg_alloc(dmdev);
//802无线网的具体的操作函数 
wdev->wiphy = wiphy_new(&lbs_cfg80211_ops, sizeof(struct lbs_private));
 
//分配网络设备是整个网络部分操作的
//的核心结构体
dev = alloc_netdev(0, "wlan%d", ether_setup);  //实例化wlan0的属性
dev->ieee80211_ptr = wdev;
 dev->ml_priv = priv;
 //设置设备的物理地址 
 SET_NETDEV_DEV(dev, dmdev);
 wdev->netdev = dev;
 priv->dev = dev;
   //初始化网络设备 ops.  看门狗  
  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);
 
//具体的wifi设备驱动功能 
//网络设备操作的具体函数 
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,   //设置mac地址 
 .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.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);  //从无线网卡接收到数据 或者说是上报数据
//读取端口上的数据 ,放到card的buffer中 
 ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
1.在这里一方面处理中断  还有2 
 switch (type) {   //处理cmd   data  event的请求 
 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);
 
 //如果是中断 ,就把skb这个包提交给协议层,这个函数是
 //协议层提供的  netif_rx(skb)
 if (in_interrupt())
  netif_rx(skb);    //提交给协议层 
 
 
2//读取端口上的数据 ,放到card的buffer中 
 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;
                                
                    //wait for  request  
                     mmc_wait_for_req(card->host, &mrq);
                        开始应答 
                         mmc_start_request(host, mrq);
                         wait_for_completion(&complete);
                                    
                             host->ops->request(host, mrq);

3.4、 数据发送

//IP层通过dev_queue_xmit()将数据交给网络设备协议接口层,网络接口层通过netdevice中的注册函数的数据发送函数
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);
  //调用wifi网络中的ops 
 
  rc = ops->ndo_start_xmit(skb, dev);
 
  dev->netdev_ops = &lbs_netdev_ops;    //设备的操作函数 
 
 //处理sdio firware数据和内核的数据main_thread 主线程  
 priv->main_thread = kthread_run(lbs_thread, dev, "lbs_main");
 
   //调用host_to_card   即if_sdio_card_to_host函数。 
   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)
      //把buf中的数据 copy到sdio 包中,在对sdio 的包进行处理
         memcpy(packet->buffer + 4, buf, nb);
//创建工作队列  
         queue_work(card->workqueue, &card->packet_worker);
 //初始化队列  
 INIT_WORK(&card->packet_worker, if_sdio_host_to_card_worker);
 
 //sdio的写数据   
   ret = sdio_writesb(card->func, card->ioport, packet->buffer, packet->nb);
         //mmc写扩展口 
               ret = mmc_io_rw_extended(func->card, write,func->num, addr, incr_addr, buf,blocks, func->cur_blksize);
 
                    //wait for  request  
                                 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);
   //到底结束  发送数据  

3.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);

四、举例应用

1. 基于 SDIO 的 Wi-Fi 模块硬件平台

许多嵌入式设备(如智能手机、平板电脑、物联网设备)使用 SDIO 接口来连接 Wi-Fi 模块。例如:

  • Broadcom BCM4330/BCM4334/BCM4335:这些 Broadcom Wi-Fi 芯片通常使用 SDIO 接口,广泛应用于嵌入式系统中,如手机和嵌入式 Linux 设备。
  • Qualcomm Atheros AR6003:另一款常见的使用 SDIO 接口的 Wi-Fi 芯片,广泛应用于各种嵌入式设备。
  • Realtek RTL8723BS:Realtek 提供的这款 Wi-Fi + Bluetooth 组合芯片支持 SDIO 接口,适用于小型嵌入式设备。

2. 在 Linux 上使用 SDIO Wi-Fi 驱动

在 Linux 系统中,使用 SDIO 接口的 Wi-Fi 模块通常需要特定的驱动程序来实现硬件与操作系统的通信。Linux 内核通常提供了对 SDIO Wi-Fi 模块的支持,但需要加载适当的驱动程序和配置。

以下是一些典型的步骤和流程,用于在 Linux 上配置和应用 SDIO 接口的 Wi-Fi 模块:

确保内核支持 SDIO 和 Wi-Fi 驱动

Linux 内核的配置中需要启用 SDIO 支持,以及支持相应 Wi-Fi 模块的驱动。以 Broadcom 和 Qualcomm 的芯片为例,通常需要启用以下内核选项:

  1. SDIO 支持

    • CONFIG_SDIO: 启用 SDIO 设备支持。
    • CONFIG_MMC: 启用 MMC/SD 卡支持(SDIO 是 MMC 协议的扩展)。
  2. Wi-Fi 驱动

    • CONFIG_BRCMFMAC: Broadcom Wi-Fi 驱动,支持 Broadcom SDIO 无线适配器。
    • CONFIG_ATH6KL: Qualcomm Atheros 的 Wi-Fi 驱动。
    • CONFIG_RTL8723BS: Realtek RTL8723BS Wi-Fi 驱动。

可以通过 make menuconfigmake xconfig 来配置内核。

加载 SDIO 驱动

在 Linux 内核启用了相应的驱动后,Wi-Fi 模块可以通过 SDIO 接口与内核进行通信。在设备启动时,系统会自动加载相应的 SDIO 驱动。

例如,Broadcom 的 SDIO Wi-Fi 驱动通常为 brcmfmac 驱动,而 Qualcomm Atheros 的 SDIO 驱动为 ath6kl_sdio

你可以使用以下命令查看当前加载的 SDIO 设备驱动:

lsmod | grep brcmfmac

如果设备没有自动加载驱动,可以手动加载:

modprobe brcmfmac

配置和管理 Wi-Fi

配置和管理 SDIO Wi-Fi 模块通常依赖于 wpa_supplicantNetworkManager 这类工具,它们为用户提供网络连接的管理界面。

  1. wpa_supplicantwpa_supplicant 是 Linux 系统中常用的无线连接管理工具,负责连接到 Wi-Fi 网络并管理认证过程。它通常会自动与 cfg80211mac80211 进行交互,并支持使用 WPA/WPA2/WPA3 等安全协议。

    配置 Wi-Fi 连接时,可以在 /etc/wpa_supplicant.conf 文件中配置连接信息。例如:

network={
    ssid="your_network"
    psk="your_password"
}

然后通过以下命令启动 wpa_supplicant

  • sudo wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
    
  • NetworkManagerNetworkManager 是一个更高层的网络管理工具,可以简化 Wi-Fi 连接的管理。它支持图形界面(如 GNOME 或 KDE)和命令行工具 nmcli

    可以通过 nmcli 命令连接 Wi-Fi 网络:

  1. nmcli dev wifi connect "your_network" password "your_password"
    

3. 应用场景举例

嵌入式设备上的 Wi-Fi 连接

例如,一个基于 SDIO 的 Wi-Fi 模块(如 Broadcom BCM4334)被集成到一个嵌入式设备中,如基于 Linux 的智能家居设备、IoT 网关或嵌入式传感器。这些设备通常使用 SDIO 接口连接到 Wi-Fi 模块,以提供无线网络访问功能。

这些设备通过以下步骤使用 SDIO 接口和 Wi-Fi 驱动:

  • 设备启动时,内核加载适当的 Wi-Fi 驱动(如 brcmfmac)。
  • 用户通过 wpa_supplicant 配置连接到无线网络。
  • 设备使用 Wi-Fi 连接与云平台或其他设备进行通信,提供远程控制或数据传输功能。

Wi-Fi 模块在物联网设备中的应用

在物联网(IoT)设备中,SDIO 接口的 Wi-Fi 模块常常被用来提供低功耗无线通信。例如,在一个远程监控系统中,设备通过 SDIO Wi-Fi 模块连接到家庭或企业的无线网络,上传采集到的数据或接收命令。

  • 设备使用 SDIO 接口连接到 Wi-Fi 模块。
  • 内核通过相应的驱动加载和管理设备,确保数据能够稳定传输。
  • Wi-Fi 模块通过低功耗模式(如 802.11n 或 802.11ac)降低功耗,延长设备的电池寿命。

4. 调试和性能监控

在开发过程中,如果遇到问题,可以使用以下工具进行调试和性能监控:

  • dmesg:查看内核日志,检查 SDIO 驱动和 Wi-Fi 连接的状态。

  • dmesg | grep sdio
    dmesg | grep brcmfmac
    
  • iwconfig:查看无线接口的状态和配置信息。

  • iwconfig wlan0
    
  • iw dev wlan0 link:查看当前 Wi-Fi 网络的连接状态。

iw dev wlan0 link
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值