目录
1、WiFi driver驱动框架
1.1、SDIO-Wifi模块介绍
SDIO-Wifi模块是基于SDIO接口的符合WiFi无线网络标准的嵌入式模块,内置无线网络协议IEEE802.11协议栈以及TCP/IP协议栈,能够实现用户主平台数据通过SDIO口到无线网络之间的转换。SDIO具有传输数据快,兼容SD、MMC接口等特点。
对于SDIO接口的WiFi,首先,它是一个SDIO的卡设备,然后具备了WiFi的功能。所以,注册的时候还是先以sdio设备去注册,然后检测到卡之后再去实现它的wifi功能。显然,他是用SDIO的协议,通过发命令和数据来控制实现WiFi功能。
SDIO 故名思义,就是 SD 的 I/O 接口(interface)的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO。
所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 引脚来连接外围,并且透过 SD 上的 I/O 数据引脚与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。目前常见的 SDIO 外围(SDIO 卡)有:
- Wi-Fi card(无线网络卡)
- CMOS sensor card(照相模块)
- GPS card
- GSM/GPRS modem card
- Bluetooth card
1.2、SDIO总线协议
SDIO总线 和 USB总线 类似,SDIO也有两端,其中一端是HOST端,另一端是device端。所有的通信都是由HOST端发送命令开始的,Device端只要能解析命令,就可以相互通信。 对于SDIO总线,它的HOST端是开发板mmc控制器,而device端则是各种带SDIO接口的模块,比如SDIO WiFi模块。
SDIO协议是由SD卡协议演化升级而来的,很多地方保留了SD卡的读写协议,同时SDIO协议又在SD卡协议之上添加了CMD52和CMD53命令。由于这个,SDIO和SD卡规范间的一个重要区别就是增加了低速标准。低速卡的目标应用是以最小的硬件来支持低速I/O能力。
SD总线通信是基于指令和数据比特流,起始位开始和停止位结束。SD总线通信有三个元素:
Command:由host发送到卡设备,使用CMD线发送;
Response:从card端发送到host端,作为对前一个CMD的相应,通过CMD线发送;
Data:即能从host传输到card,也能从card传输到host,通过data线传输。
SDIO的硬件电路
可以看到,SDIO接口总共有七个引脚,分别是DAT0-3(数据),CLK(时钟),CMD(命令),VIO(电源)
1.3、网络驱动架构
字符设备在 /dev 目录下会有对应设备文件节点并且在注册时会有设备号。网络设备没有对应设备节点和设备号,网络设备使用套接字来实现网络数据的接收和发送。 Linux网络设备驱动程序的体系结构如图中黄色部分所示所示,从上到下可以划分为4层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层。
网络协议接口层:
最主要的功能就是给上层协议提供了透明的数据包发送和接收接口。当上层的协议需要发送数据包时,就会调用 dev_queue_xmit() 函数。当上层需要接收数据包时,就会调用netif_rx()函数。函数定义如下:
网络设备接口层
网络设备接口层的主要功能是为千变万化的网络设备定义了统一、 抽象的数据结构 net_device 结构体,实现多种硬件在软件层次上的统一。
Linux内核使用 net_device结构体表示一个具体的网络设备,网络驱动的核心就是初始化net_device 结构体中的各个成员变量,然后将初始化完成以后的net_device 注册到 Linux内核中。
net_device数据结构定义在include/linux/netdevice.h,如下所示
- name[IFNAMSIZ]; 设备的名称(如:eth0)
- mem_start, mem_end:描述设备所用的共享内存,用于设备与内核沟通
- base_addr设备自有内存映射到I/O内存的起始地址
- irq:络设备的中断号。
- dev_list 是全局网络设备列表。
- napi_list 是 napi 网络设备的列表入口。
- wireless_handlers,wireless_data用于无线设备的参数和指针函数
- if_port接口所使用的端口类型
- dma设备所使用的DMA通道
- mtu MTU代表最大传输单元 ,表示设备能处理的帧的最大尺寸
- ethtool_ops 是网络管理工具相关函数集,用户空间网络管理工具会调用此结构体中的相关函数获取网卡状态或者配置网卡。
- netdev_ops是网络设备的操作集函数,包含了一系列的网络设备操作回调函数,
类似字符设备中的 file_operations
13.Ifindex:独一无二的ID,当设备以dev_new_index注册时分配给每个设备
14.dev_id用于区别可由不同OS同时共享的同一种设备的诸多虚拟实例
设备驱动功能层
对应net_device结构体中的设备驱动功能函数netdev_ops,例如xxx_open()、xxx_stop()、xxx_tx()等。另一部分是中断处理函数,它负责接收硬件上的数据包并传给上层协议。 这也是wifi驱动主要需要实现的接口:路径 os_dep\linux\os_intfs.c,rtw_netdev_ops最后在probe中赋值给net_device中设备驱动功能函数netdev_ops。
1.4、MMC整体框架
Linux内核中,MMC不仅是一个驱动,而是一个子系统。内核把mmc, sd以及sdio三者的驱动代码整合在一起,俗称MMC子系统。
Linux MMC子系统主要分成三个部分:
- MMC核心层:完成不同协议和规范的实现,为host层和设备驱动层提供接口函数。MMC核心层由三个部分组成:MMC,SD和SDIO,分别为三类设备驱动提供接口函数;
- Host 驱动层:针对不同主机端的SDHC、MMC控制器的驱动;
- Client 驱动层:针对不同客户端的设备驱动程序。如SD卡、T-flash卡、SDIO接口的GPS和wifi等设备驱动
2、WiFi 驱动
分为两部分,上面为主机端驱动,下面firmware。
主机端驱动包括上面讲的网络子系统,mmc子系统。
Firmware在WiFi芯片内部运行,用来实现802.3的帧和802.11帧之间的转换。
Wifi工作流程如下:
Wifi driver (mmc子系统中Client 驱动层)
sdio驱动结构体
模块加载函数:
rtw_drv_entry
|---> platform_wifi_power_on 打开Wi-Fi 电源
|---> rtw_suspend_lock_init 初始化唤醒锁
|---> rtw_ndev_notifier_register注册回调函数,当网络设备状态变化时会被调用
|---> sdio_register_driver 注册SDIO驱动
当sdio控制器扫描到设备的时候会去读取设备的id即wifi模块的pid和vid,如果设备id匹配上了之后会调用使用sdio_register_driver注册进去的probe函数。
rtw_drv_init
|---> sdio_dvobj_init
|--->rtw_sdio_if1_init 初始化sdio适配器结构体
|--->loadparam
|--->rtw_set_hal_ops 注册硬件操作函数
|--->rtw_init_io_priv注册host io读写函数操作集,wifi驱动用它来与硬件通信
|--->rtw_hal_read_chip_version读取芯片版本
|--->rtw_init_drv_sw 初始化wifi驱动数据
|--->_rtw_init_xmit_priv初始化发送队列
|--->_rtw_init_recv_priv初始化接收队列
|--->rtw_macaddr_cfg从eeprom读mac地址
|---> rtw_os_ndevs_init 分配和注册操作系统层网络设备和相关结构
|--->rtw_os_ndev_alloc
|--->rtw_init_netdev
|--->rtw_hook_if_ops 完成net_device中设备驱动功能函数netdev_ops注册
|--->pnetdev->wireless_handlers= (struct iw_handler_def *)&rtw_handlers_def; 无线扩展功能相关
|--->sdio_alloc_irq 注册接收中断函数
|---sdio_claim_irq(func, &sd_sync_int_hdl);
|--->func->irq_handler = handler;
当使用命令ifconfig eth0 up时,netdev_open接口将会被调用:
netdev_open
|---> rtw_hal_init硬件抽象层的初始化
|--->rtw_start_drv_threads
|--->kthread_run(rtw_cmd_thread, padapter, "RTW_CMD_THREAD"); 开一个cmd线程,用来发送命令
|--->rtw_hal_start_thread开一个数据传输线程
|--->rtw_hal_enable_interrupt使能host中断
|--->rtw_cfg80211_init_wiphy 初始化cfg80211wifi的phy
|--->rtw_netif_wake_queue 唤醒队列
接收数据:
sd_sync_int_hdl
|--->sd_int_hdl
|--->sd_int_dpc
|--->sd_recv_rxfifo从接收fifo读数据
|--->sd_rxhandler
|--->rtw_enqueue_recvbuf 入接收队列
|--->tasklet_schedule 调度tasklet处理数据包
中断下半部分:
rtl8188fs_recv_tasklet
|--->rtw_start_drv_threads
|--->rtl8188fs_recv_hdl
|--->rtw_dequeue_recvbuf从接收队列取出数据包
|--->rtw_skb_alloc申请skbuff缓冲区
|--->pre_recv_entry数据包上报
数据发送:
rtw_xmit_entry
|--->_rtw_xmit_entry
|--->rtw_xmit
|--->do_queue_select选择一个发送队列
|--->rtl8188fs_hal_xmit
|--->rtw_xmitframe_enqueue
|--->rtw_xmit_classifier将数据包放入发送队列中
3.Host驱动
SDHC:Secure Digital(SD) Host Controller,是指一套sd host控制器的设计标准,一般挂载sd和sdio设备,隶属于mmc子系统,其寄存器偏移以及意义都有一定的规范,并且提供了对应的驱动程序,方便vendor进行host controller的开发。
mmc core将sdhci host抽象出struct sdhci_host来进行管理和维护。
- hw_name 硬件总线名
- ops 硬件操作函数集
- mmc mmc设备结构体
- max_clk,timeout_clk,clk_mu,pwr 硬件相关,最大支持的频率,电流,电压等。
- mrq mmc请求
- cmd用于存放当前要向硬件发送的命令
- data 用于存放当前mmc请求的数据
- sg_miter 存放pio当前状态,目前进行io操作的页面地址,当前页面指针,里面指向了当前页面的scatterlist,实现不连续的物理内存块到连续的虚拟内存块映射。scatterlist介绍查看Linux 4.14内核———— scatterlist介绍_struct scatterlist-CSDN博客
- blocks 剩余需要进行PIO传输的块数量
- adma_table-align_mask ADMA传输相关,当使用ADMA传输数据时被使用
- finish_tasklet mmc请求,命令发送完成,数据传输完成的回调函数
- buf_ready_int 用于唤醒发送读取命令后的读准备函数
- tuning_done 读取命令是否发送成功
sdhci core要求host提供标准的访问硬件的一些方法,用于实际和硬件交互的驱动,这些方法就被定义在了struct sdhci_ops结构体内部。
sdhci core使用sdhci_ops作为sdhci host抽象出来的mmc host的操作集,用于实现mmc core关于host的操作函数,如向硬件发送命令请求(request方法),检测host的卡槽中card的插入状态(get_cd方法)。pre_req , pre_req是为了实现异步请求处理而设置的,当一个异步请求还没有处理完成的时候,可以先准备另外一个异步请求而不必等待。
Probe硬件探测函数:
sdhci_esdhc_imx_probe
|--->sdhci_pltfm_init
|--->sdhci_alloc_host 申请一个host
|--->host->ops = &sdhci_pltfm_ops;注册通用电源管理操作函数
|--->request_mem_region 向内核申请io内存
|--->ioremap将io内存地址映射到内核虚拟地址
|--->devm_kzalloc申请host设备资源内存
|--->devm_clk_get获取时钟源
|--->sdhci_esdhc_imx_probe_dt检查设备树配置,初始化pltfm_imx_data和wifi_mmc_host结构体
|--->sdhci_add_host
|--->mmc->ops = &sdhci_ops;
|--->tasklet_init(,sdhci_tasklet_finish,)请求处理完成时的软中断函数,通知mmc core request已经处理完成
|--->request_threaded_irq注册host中断,中断上半部分sdhci_irq,下半部分线程化中断sdhci_thread_irq。
|--->mmc_add_host注册mmc_host到mmc core中
|--->sdhci_enable_card_detection 扫描硬件检查是否有卡插入
收发数据:
mmc_wait_for_req ->__mmc_start_req->mmc_start_request-> host->ops->request
mmc core 最后调用host->ops-> request发送请求命令。
sdhci_request
|--->sdhci_do_get_cd 检查card还在不在
|--->sdhci_send_command
|--->sdhci_prepare_data准备数据,设置需要的寄存器
|--->sdhci_set_transfer_irqs 根据传输方式使能PIO或者DMA中断
|--->sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); 清中断标志
|--->sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);使能中断
|--->sdhci_set_transfer_mode设置传输模式PIO or DMA
|--->sdhci_writew(host,SDHCI_MAKE_CMD(cmd->opcode,flags), SDHCI_COMMAND);向设备IOMEM写命令,随后触发中断sdhci_irq
|--->mmiowb 保证编译器顺序编译,防止编译器优化打乱执行顺序
|--->sdhci_data_irq
|--->sdhci_transfer_pio
|--->sdhci_read_block_pio 从card的IO设备缓冲区读报文到内存
|--->sdhci_write_block_pio 把报文从内存写入IO设备缓冲区
|--->sg_miter_stop停止物理地址到虚拟地址映射
sdhci_irq 如果是卡插拔或者读数据中断返回IRQ_WAKE_THREAD,唤醒线程化中断,
sdhci_thread_irq
|--->sdhci_card_event 向mmc core报告卡插入或者拔出
|--->sdio_run_irqs 运行wifi接收数据中断sd_sync_int_hdl
|--->process_sdio_pending_irqs
|--->func->irq_handler(func); wifi驱动只注册了一个接收中断,直接调用,这里的irq_handler就是wifi driver probe函数注册的sdio中断函数sd_sync_int_hdl
3.WIFI驱动移植
设备树节点:
下面两个同名节点会合并成一个节点,Pingctrl和时针由host驱动自己管理,pinctrl-0~ pinctrl-3对应设备三种工作状态的引脚配置。cd-gpios用来监测卡是否存在,vmmc-supply为电源管理支持。
修改设备树节点后直接将wifi driver和host driver编译进内核,插入wifi模块后使用ifconfig -a可以看到wlan0 节点,这样wifi驱动就移植好了。
4.总结
Wifi模块对于网络子系统来说,是一个网络设备,对于mmc子系统来说,Wifi模块就是一个块设备,报文收发就是对一个存储卡的读写。wifi驱动就是一个存储卡到网络设备转换的媒介。对于驱动开发者来说,需要关注的是host驱动和设备树节点。