SDIO WiFi
SDIO WiFi 是 WiFi 芯片采用 SDIO 总线和主控通信,复用 SD 卡接口,是嵌入式里很常见的 WiFi 方案,主控通过 SDIO 向 WiFi 芯片下发控制指令、收发网络报文。
Hi3516CV610+RTL8189FS 从驱动加载、连接热点、获取 IP 到传输视频的完整流程
#mermaid-svg-6qX4r4I9jykLTvHo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6qX4r4I9jykLTvHo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6qX4r4I9jykLTvHo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6qX4r4I9jykLTvHo .error-icon{fill:#552222;}#mermaid-svg-6qX4r4I9jykLTvHo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6qX4r4I9jykLTvHo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6qX4r4I9jykLTvHo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6qX4r4I9jykLTvHo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6qX4r4I9jykLTvHo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6qX4r4I9jykLTvHo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6qX4r4I9jykLTvHo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6qX4r4I9jykLTvHo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6qX4r4I9jykLTvHo .marker.cross{stroke:#333333;}#mermaid-svg-6qX4r4I9jykLTvHo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6qX4r4I9jykLTvHo p{margin:0;}#mermaid-svg-6qX4r4I9jykLTvHo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6qX4r4I9jykLTvHo .cluster-label text{fill:#333;}#mermaid-svg-6qX4r4I9jykLTvHo .cluster-label span{color:#333;}#mermaid-svg-6qX4r4I9jykLTvHo .cluster-label span p{background-color:transparent;}#mermaid-svg-6qX4r4I9jykLTvHo .label text,#mermaid-svg-6qX4r4I9jykLTvHo span{fill:#333;color:#333;}#mermaid-svg-6qX4r4I9jykLTvHo .node rect,#mermaid-svg-6qX4r4I9jykLTvHo .node circle,#mermaid-svg-6qX4r4I9jykLTvHo .node ellipse,#mermaid-svg-6qX4r4I9jykLTvHo .node polygon,#mermaid-svg-6qX4r4I9jykLTvHo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6qX4r4I9jykLTvHo .rough-node .label text,#mermaid-svg-6qX4r4I9jykLTvHo .node .label text,#mermaid-svg-6qX4r4I9jykLTvHo .image-shape .label,#mermaid-svg-6qX4r4I9jykLTvHo .icon-shape .label{text-anchor:middle;}#mermaid-svg-6qX4r4I9jykLTvHo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6qX4r4I9jykLTvHo .rough-node .label,#mermaid-svg-6qX4r4I9jykLTvHo .node .label,#mermaid-svg-6qX4r4I9jykLTvHo .image-shape .label,#mermaid-svg-6qX4r4I9jykLTvHo .icon-shape .label{text-align:center;}#mermaid-svg-6qX4r4I9jykLTvHo .node.clickable{cursor:pointer;}#mermaid-svg-6qX4r4I9jykLTvHo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6qX4r4I9jykLTvHo .arrowheadPath{fill:#333333;}#mermaid-svg-6qX4r4I9jykLTvHo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6qX4r4I9jykLTvHo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6qX4r4I9jykLTvHo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6qX4r4I9jykLTvHo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6qX4r4I9jykLTvHo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6qX4r4I9jykLTvHo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6qX4r4I9jykLTvHo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6qX4r4I9jykLTvHo .cluster text{fill:#333;}#mermaid-svg-6qX4r4I9jykLTvHo .cluster span{color:#333;}#mermaid-svg-6qX4r4I9jykLTvHo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6qX4r4I9jykLTvHo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6qX4r4I9jykLTvHo rect.text{fill:none;stroke-width:0;}#mermaid-svg-6qX4r4I9jykLTvHo .icon-shape,#mermaid-svg-6qX4r4I9jykLTvHo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6qX4r4I9jykLTvHo .icon-shape p,#mermaid-svg-6qX4r4I9jykLTvHo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6qX4r4I9jykLTvHo .icon-shape .label rect,#mermaid-svg-6qX4r4I9jykLTvHo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6qX4r4I9jykLTvHo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6qX4r4I9jykLTvHo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6qX4r4I9jykLTvHo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一个视频数据包的发送路径
RTSP 视频传输
获取 IP 地址
连接热点
外设层:RTL8189FS 驱动
核心层:Linux 内核框架
主控层:Hi3516 SDIO 控制器
系统启动
解析设备树
SDIO、时钟、GPIO、电源
加载 rtl8189fs.ko
Hi3516 SDIO 主控驱动
配置时钟、总线宽度、DMA、中断
MMC / SDIO Core
Linux 网络核心
net_device
无线核心
cfg80211 / nl80211
SDIO probe
识别 RTL8189FTV
初始化 Wi-Fi 芯片
加载固件、配置寄存器
注册 wiphy 和 net_device
生成网络接口 wlan0
ifconfig wlan0 up
启动 wpa_supplicant
通过 nl80211 请求扫描
cfg80211 调用驱动 scan
RTL8189FS 扫描无线信道
发现热点 liguangdou666
选择目标 AP
802.11 Authentication
802.11 Association
WPA 四次握手
密钥下发给 RTL8189FS
wpa_state = COMPLETED
启动 udhcpc -i wlan0
DHCP Discover
Windows 热点返回 Offer
DHCP Request
DHCP ACK
板子获得 192.168.137.62
设置掩码和默认网关
192.168.137.1
启动 sample_venc 0 1
摄像头采集
VI / VPSS 图像处理
VENC 编码为 H.265
RTSP 服务监听 TCP 554
VLC 连接
192.168.137.62:554
TCP 建立连接
RTSP 控制+H.265 RTP 数据
sample_venc 调用 socket send
Linux TCP/IP 协议栈
根据路由选择 wlan0
生成 sk_buff
调用 RTL8189FS ndo_start_xmit
RTL8189FS 封装并排队
sdio_memcpy_toio
MMC / SDIO Core
Hi3516 SDIO 主控
DMA传输
RTL8189FTV 发射无线帧
Windows 热点接收
VLC 接收并解码 H.265
三层在这条链路中的职责可以压缩为:
text
核心层
├─ MMC/SDIO Core:统一管理SDIO请求
├─ cfg80211/nl80211:统一管理Wi-Fi扫描和连接
└─ net_device/TCP-IP:统一管理网络数据
主控层
└─ Hi3516 SDIO主控:操作时钟、DMA、中断和控制器寄存器
外设层
└─ RTL8189FS驱动:控制Wi-Fi芯片扫描、关联、密钥和数据收发
最关键的两条源码调用链是:
text
连接控制链:
wpa_supplicant
→ nl80211
→ cfg80211
→ ioctl_cfg80211.c
→ rtw_mlme.c
→ RTL8189FTV
text
视频数据链:
sample_venc
→ socket/TCP
→ net_device
→ rtw_xmit.c
→ SDIO Core
→ Hi3516 SDIO主控
→ RTL8189FTV
→ Windows热点
→ VLC
其中 udhcpc、TCP、RTSP 都不属于 Wi‑Fi 驱动。Wi‑Fi 驱动只负责建立无线链路并收发帧;IP地址、路由和视频协议由更上层的软件处理。
SDIO核心注册驱动
精简流程
insmod → module_init → rtw_drv_entry
- 打印版本,WiFi芯片上电
platform_wifi_power_on() - 初始化锁、proc、私有netlink、网络通知链
sdio_register_driver(&r871xs_drv)向SDIO核心注册驱动- 成功:入口结束;失败:逆序释放资源、WiFi下电,返回错误
一句话
入口函数只做上电+资源初始化,向内核登记sdio_driver;芯片识别、创建wlan0是后续probe回调的事。
要点
- 登记的是
struct sdio_driver - probe不由这里直接调用,由SDIO核心匹配设备后回调
rtw_drv_entry
-
找到
sdio_drvpriv.r871xs_drv这个struct sdio_driver
重点看成员:.id_table:设备匹配表(内核靠这个认出 RTL8189FTV).probe:匹配成功后执行的回调函数(核心!).remove:rmmod 时清理
-
进入
.probe指向的函数(一般名字类似rtw_sdio_probe/r871xs_sdio_probe) -
这是干嘛的:静态初始化 SDIO 驱动登记表,告诉内核 SDIO 子系统,设备匹配上、卸载、休眠时分别调用哪个函数。
-
上一步为什么要用它:
rtw_drv_entry里sdio_register_driver需要传入这份sdio_driver描述,完成驱动登记。 -
重点是什么:
id_table是匹配钥匙;.probe=rtw_drv_init是匹配成功后进入的主函数。 -
下一步重点看哪里:
rtw_drv_init(probe 回调)。
类比(平台驱动 platform_driver ↔ SDIO驱动 sdio_driver)
先一句话总:套路一模一样!都是【匹配表 + probe + remove】,只是总线不一样
- platform_driver:平台总线(platform_bus),靠 dts compatible 匹配
- sdio_driver:SDIO总线(sdio_bus),靠 sdio_ids 硬件ID匹配
对照表格
| 平台驱动(你写的platform示例) | SDIO驱动(RTL8189FS这一段) | 作用 |
|---|---|---|
of_device_id of_plat_match[] |
sdio_ids |
✅匹配表:内核拿它去总线上找设备 |
.of_match_table = of_plat_match |
.id_table = sdio_ids |
告诉驱动:用这张表做匹配 |
.probe = plat_probe |
.probe = rtw_drv_init |
匹配成功,内核回调这个函数 |
.remove = plat_remove |
.remove = rtw_dev_remove |
设备移除时回调 |
module_platform_driver(plat_driver) |
sdio_register_driver(&r871xs_drv) |
向对应总线内核框架注册驱动 |
MODULE_DEVICE_TABLE(of, of_plat_match) |
SDIO这边也有MODULE_DEVICE_TABLE(sdio, sdio_ids) | 导出匹配表,支持自动加载 |
通俗类比
- platform:
设备树写compatible="yhai-plat"→ 内核在platform总线扫设备,找到匹配 → 跑plat_probe,拿内存资源 - SDIO(RTL8189):
SDIO总线扫描硬件,读出芯片的厂商ID+设备ID,和sdio_ids比对 → 匹配上 → 跑rtw_drv_init,初始化WiFi芯片
唯一差别
- platform:匹配依据是 dts里的compatible字符串(软件字符串匹配),一般用于片上外设(寄存器、GPIO)
- SDIO:匹配依据是 硬件读到的SDIO vendor ID/device ID(硬件寄存器读出来的数值),SDIO外设(WiFi、蓝牙)
虚拟总线、硬件总线
platform 总线:软件虚拟总线,靠设备树 compatible 字符串匹配;
SDIO 总线:真实硬件总线,靠芯片硬件ID(vendor/device id)匹配。
两套驱动框架套路一致(注册驱动 + 匹配 + 回调probe),只是总线本身、匹配方式、使用场景不一样。
逐项对比
1. 总线性质
- platform(平台总线):虚拟总线,没有真实硬件信号线
内核虚拟出来的,专门管理SOC内部自带外设(寄存器、GPIO、时钟、片上控制器,比如Hi3516内部SDIO控制器、I2C控制器)。
设备靠设备树DTS描述出来,不是总线自动扫描发现。 - SDIO总线:真实物理总线
有CLK、CMD、D0~D3实实在在的引脚;主控上电后,SDIO控制器会在总线上扫描,读取SDIO设备的硬件ID。
RTL8189FTV 这个WiFi芯片,就是挂在这条物理SDIO线上。
2. 匹配规则(最容易考!)
- platform:字符串匹配
DTS里写compatible = "yhai-plat"
驱动of_device_id表里写一样的字符串,对上就匹配成功。 字符串是我们写代码、写dts自己定义的,随便起名,只要两边一致。 - SDIO:硬件数值ID匹配
SDIO Core去读芯片内部寄存器,读出厂商ID、设备ID,和驱动里sdio_ids的数值对比。 ID是芯片出厂固定写死在硬件里的,不能随便改。
3. 设备怎么来的
- platform设备:人在DTS里静态写出来
你不在dts写这个platform设备,内核就看不到这个设备,不会去匹配。总线不会自己去扫描硬件。 - SDIO设备:SDIO控制器自动扫描总线发现
只要板子上电、SDIO电源正常、芯片焊上去,SDIO控制器就会自动识别总线上挂着的SDIO设备,生成设备。
4. 典型使用场景
- platform驱动:SOC内置控制器
Hi3516的SDIO控制器、I2C、UART、GPIO、时钟、电源管理模块。 - SDIO驱动:外接SDIO外设
SDIO WiFi(RTL8189FTV)、SDIO蓝牙、SDIO摄像头。
5. 注册方式
- platform:
module_platform_driver(),内核宏封装,内部调用platform_driver_register - SDIO:手动调用
sdio_register_driver()
相同点(别忘)
都是Linux设备驱动模型:
- 向总线注册驱动(登记表)
- 总线做匹配
- 匹配成功 → 内核回调probe
- 设备移除 → 回调remove
极简背诵版
- platform:虚拟总线,DTS字符串匹配,用来驱动SOC内部控制器;设备需要DTS手动描述。
- SDIO:真实物理总线,硬件ID数值匹配,用来驱动挂在SDIO线上的外设(WiFi);总线自动扫描发现设备。
platform 虚拟总线完整过程
先记住核心:platform总线是内核造出来的,没有真实扫描硬件的电路;设备不是扫出来的,是DTS"告诉"内核的
完整流程
- 你写设备树DTS,在soc节点下加一段platform设备:
dts
yhai_plat@10000000 {
compatible = "yhai-plat";
reg = <0x10000000 0x1000>;
};
compatible:匹配字符串reg:内存地址资源
-
内核启动解析DTS
内核把这段节点转成一个platform_device设备对象,放到platform总线上。
-
加载驱动(insmod 或者自动加载)
驱动里注册
platform_driver,附带of_match_table(字符串匹配表)。 -
platform总线的匹配逻辑(内核总线框架做)
总线拿【platform_device里的compatible字符串】和【驱动of_match_table里的字符串】对比
- 对上 → 回调驱动的
.probe - 对不上 → 不调用probe
- probe里面:用
platform_get_resource拿DTS里的reg、中断等资源,初始化硬件寄存器。
和SDIO物理总线最关键差别
- SDIO:上电 → SDIO控制器硬件自动扫描总线 → 读芯片硬件ID → 内核生成sdio设备 → 和sdio_ids数值匹配
- platform:上电 → 解析DTS脚本 → 内核直接凭空创建platform_device → 字符串匹配
满足进入probe的前提条件

SDIO Core拿总线上扫出来的SDIO设备ID,和sdio_ids比对,匹配成功 → SDIO Core调用这个sdio_driver的 .probe,也就是 rtw_drv_init
给你按你习惯的四段总结:
- 这是干嘛的:给sdio_driver绑定SDIO ID匹配表,告诉内核:哪些SDIO芯片归我这个驱动管
- 上一步为什么要用它:
sdio_register_driver注册驱动后,SDIO总线框架需要一张匹配表用来识别芯片 - 重点是什么:
✅ id_table是匹配钥匙;
✅ 匹配成功只是满足进入probe的前提条件; - 下一步重点看哪里:
rtw_drv_init(probe回调里面的初始化逻辑)
补充一句和platform对照:
- platform:匹配 of_device_id(compatible字符串)→ 进probe
- SDIO:匹配 sdio_ids(硬件Vendor/Device数值)→ 进probe
SDIO的probe回调函数 rtw_drv_init
- 这是干嘛的:SDIO的probe回调函数 rtw_drv_init,id_table匹配成功后内核自动调用;负责创建驱动内部管理对象、初始化适配器、注册wlan网卡、申请中断。
- 上一步为什么要用它:sdio_driver登记好,SDIO Core匹配芯片ID之后,就要跑这个函数,真正把WiFi设备初始化起来。
- 重点是什么:
sdio_dvobj_init:创建驱动顶层设备管理结构体(dvobj)rtw_sdio_primary_adapter_init:WiFi适配器(adapter)初始化,RTL驱动核心管理单元rtw_os_ndevs_init:注册net_device,生成wlan0- 失败是goto逆序释放资源,典型内核驱动写法
- 下一步重点看哪里:
rtw_os_ndevs_init,里面做net_device注册、wiphy/cfg80211注册。
初始化 SDIO 底层设备对象sdio_dvobj_init
- 这是干嘛的:初始化 SDIO 底层设备对象 dvobj,绑定 SDIO 操作集、sdio_func,识别芯片型号,是 RTL 驱动最底层硬件接口初始化。
- 上一步为什么要用它:
rtw_drv_init(probe)最先调用它,先把 SDIO 通道、驱动私有容器建好,后面才能创建 adapter、注册网卡。 - 重点是什么:
devobj_init():分配 dvobj_priv 结构体dvobj->intf_ops = &sdio_ops:绑定 SDIO 收发、读写寄存器的底层函数集sdio_set_drvdata(func, dvobj):内核 sdio_func 和驱动私有 dvobj 互相挂钩子(非常经典)rtw_decide_chip_type_by_device_id:根据匹配到的 sdio_id 确定是哪款芯片(8189FTV)
- 下一步重点看哪里:
sdio_init(dvobj),里面做 SDIO 总线配置、读芯片寄存器、固件相关初始化。
初始化SDIO主适配器函数:rtw_sdio_primary_adapter_init
一句话总述:adapter 是RTL驱动里WiFi的核心管理大对象,这个函数就是创建主WiFi适配器,配置HAL硬件层、读芯片信息、加载软件参数,硬件初始化的核心环节。
下面逐条解释刚才5项:
-
分配/取得核心结构体:padapter(_adapter)
dvobj是SDIO总线层面的设备容器(一个芯片一个dvobj);padapter是WiFi业务适配器,管WiFi本身:扫描、连接、发包、加密、MLME状态。一个WiFi芯片至少有一个主adapter(primary),多AP/并发模式会再加虚拟adapter。
rtw_zvmalloc:内核分配清零内存,拿到padapter。 -
调用的初始化函数含义(只简单解释作用,不钻子函数)
loadparam(padapter):加载驱动配置参数(速率、功率、国家码等)rtw_set_hal_ops:绑定HAL层硬件操作函数集(读写寄存器、发送命令,HAL是芯片硬件抽象层)rtw_init_io_priv:绑定SDIO收发接口,告诉adapter走SDIO总线和芯片通信rtw_hal_read_chip_version:读芯片版本(8189FTV),区分不同版本固件rtw_hal_chip_configure:芯片硬件基础配置(寄存器默认值)rtw_hal_read_chip_info:读efuse,里面存MAC地址、校准信息rtw_init_drv_sw:初始化驱动软件状态、链表、锁、定时器rtw_hal_disable_interrupt:初始化阶段先关掉硬件中断,防止乱中断干扰初始化
-
初始化成功后调用谁
返回padapter给上层
rtw_drv_init;rtw_drv_init拿到合法padapter之后,下一步执行:rtw_os_ndevs_init,这个函数就是注册Linux标准net_device、wiphy(cfg80211),生成wlan0网卡。
-
失败清理标签 free_hal_data / free_adapter
内核驱动经典逆序清理:
- free_hal_data:HAL硬件相关资源释放
- free_adapter:把刚才rtw_zvmalloc分配的padapter内存释放掉,防止内存泄漏
只要中间任意一步初始化失败,就顺着goto走到这里清理,不会留下半初始化对象。
- 返回值
✅成功:返回padapter指针,上层拿到这个适配器继续往下走网卡注册
❌失败:返回NULL,上层rtw_drv_init识别到NULL,跳到free_dvobj,销毁dvobj
函数:rtw_os_ndevs_init
- 分配/取得核心结构体
- 入参:
struct dvobj_priv *dvobj - 内部子函数里分配net_device、wiphy等网卡相关结构体(本层只调用,不直接分配)
- 调用的初始化函数
rtw_os_ndevs_alloc(dvobj):分配net_device、wiphy等网卡对象rtw_os_ndevs_register(dvobj):向内核网络子系统、cfg80211注册网卡,生成wlan0
- 初始化成功后调用谁
返回_SUCCESS,回到上层rtw_drv_init,继续后面的中断、GPIO等初始化 - 失败清理标签
os_ndevs_free
清理动作:rtw_os_ndevs_free,释放网卡相关结构体 - 返回值
✅成功:_SUCCESS
❌失败:_FAIL
简单解释
这是对接Linux内核网络框架的一层封装:
- alloc:把
net_device(内核标准网卡结构)、wiphy(cfg80211无线框架结构)内存申请出来,绑定到adapter - register:注册到内核,系统里才会出现wlan0,上层wpa_supplicant、ifconfig才能识别这个无线网卡
前面dvobj、padapter都是驱动内部自家管理结构;走到这里,才把WiFi设备交给Linux标准网络子系统。
主线关系帮你串一下
- dvobj:SDIO总线设备(管SDIO通道、芯片本身)
- padapter:WiFi业务适配器(管无线功能、连接、收发)
顺序:先有dvobj → 再创建padapter → 再注册wlan0网卡给Linux网络子系统
主线回顾:
rtw_drv_init → sdio_dvobj_init → rtw_sdio_primary_adapter_init → rtw_os_ndevs_init(刚看完)
网卡注册
函数:rtw_os_ndevs_register
函数作用:把adapter对应的网卡注册到内核cfg80211无线框架与网络子系统,生成wlan0这类无线网卡设备;支持多网卡,注册失败时倒序注销已注册网卡,避免残留。
- 分配/取得核心结构体
- 入参:
struct dvobj_priv *dvobj regsty = dvobj_to_regsty(dvobj):拿到registry_priv(网卡名称、配置)adapter:循环取出dvobj管理的各个adapter
- 调用的初始化函数
rtw_cfg80211_dev_res_register(dvobj):cfg80211无线框架资源注册rtw_os_ndev_register(adapter, name):注册单个net_device,绑定网卡名称- 失败分支:
rtw_os_ndev_unregister(adapter)、rtw_cfg80211_dev_res_unregister(dvobj)
- 初始化成功后调用谁
返回_SUCCESS,回到上层rtw_os_ndevs_init - 失败清理标签
无goto标签,反向循环注销已经注册的网卡,同步释放cfg80211资源 - 返回值
✅成功:_SUCCESS
❌失败:_FAIL
清理函数
函数:rtw_os_ndevs_deinit
函数作用:网卡资源反向清理入口,先注销内核里的网卡设备,再释放网卡相关内存,和 rtw_os_ndevs_init 成对。
- 分配/取得核心结构体
- 入参:
struct dvobj_priv *dvobj
- 调用的初始化函数
rtw_os_ndevs_unregister(dvobj)rtw_os_ndevs_free(dvobj)
- 初始化成功后调用谁
执行完毕返回,回到上层清理分支 - 失败清理标签
无goto标签,内部两个子函数依次做注销、释放 - 返回值
void 无返回
函数:rtw_os_ndevs_unregister
函数作用:遍历所有adapter,逐个把net_device从内核网络子系统注销,同时注销cfg80211无线资源,系统不再识别wlan0。
- 分配/取得核心结构体
- 入参:
struct dvobj_priv *dvobj - 循环取出
adapter
- 调用的初始化函数
rtw_os_ndev_unregister(adapter)rtw_cfg80211_dev_res_unregister(dvobj)
- 初始化成功后调用谁
返回,回到rtw_os_ndevs_deinit,继续执行rtw_os_ndevs_free - 失败清理标签
无 - 返回值
void 无返回
函数:rtw_os_ndevs_free
函数作用:注销完成后,释放net_device、wiphy等网卡对象内存,释放cfg80211资源内存。
- 分配/取得核心结构体
- 入参:
struct dvobj_priv *dvobj - 循环取出
adapter
- 调用的初始化函数
rtw_os_ndev_free(adapter)rtw_cfg80211_dev_res_free(dvobj)
- 初始化成功后调用谁
返回,回到rtw_os_ndevs_deinit - 失败清理标签
无 - 返回值
void 无返回
配对关系:
rtw_os_ndevs_init(alloc + register) ↔rtw_os_ndevs_deinit(unregister + free)顺序严格:先注册 → 后分配;释放时反过来:先注销 → 再释放
预先分配cfg80211资源
函数:rtw_os_ndevs_alloc
函数作用:预先分配cfg80211资源、逐个给adapter分配net_device(pnetdev)内存;分配失败时反向释放已经分配好的资源,防止内存泄漏。
- 分配/取得核心结构体
- 入参:
struct dvobj_priv *dvobj - 循环取出
adapter
- 调用的初始化函数
rtw_cfg80211_dev_res_alloc(dvobj):分配cfg80211框架相关资源rtw_os_ndev_alloc(adapter):为单个adapter分配net_device- 失败分支:
rtw_os_ndev_free(adapter)、rtw_cfg80211_dev_res_free(dvobj)
- 初始化成功后调用谁
返回_SUCCESS,回到上层rtw_os_ndevs_init,接着执行rtw_os_ndevs_register - 失败清理标签
无goto标签,倒序释放已经分配好的net_device,同步释放cfg80211资源
串一下这一对:
rtw_os_ndevs_alloc:分配内存(net_device、wiphy)
rtw_os_ndevs_register:向内核注册设备,出现wlan0
顺序:alloc → register
清理顺序:unregister → free
主线回顾:
insmod → 注册sdio_driver → ID匹配 → probe(rtw_drv_init)
→ sdio_dvobj_init
→ rtw_sdio_primary_adapter_init
→ rtw_os_ndevs_init
→ rtw_os_ndevs_alloc(刚看完)
→ rtw_os_ndevs_register
函数:rtw_os_ndevs_register
函数作用:注册cfg80211无线框架资源,遍历adapter逐个注册net_device并绑定网卡名称;任意网卡注册失败时,倒序注销已经注册成功的网卡与cfg80211资源。
- 分配/取得核心结构体
- 入参:
struct dvobj_priv *dvobj regsty = dvobj_to_regsty(dvobj):拿到registry_priv,存放网卡名称配置adapter:循环取出dvobj下的各个WiFi适配器
- 调用的初始化函数
rtw_cfg80211_dev_res_register(dvobj):cfg80211资源注册rtw_os_ndev_register(adapter, name):注册单个net_device,指定网卡名字(wlan0)- 失败分支:
rtw_os_ndev_unregister(adapter)、rtw_cfg80211_dev_res_unregister(dvobj)
- 初始化成功后调用谁
返回_SUCCESS,回到上层rtw_os_ndevs_init - 失败清理标签
无goto标签,循环反向注销网卡、释放cfg80211注册资源 - 返回值
✅成功:_SUCCESS
❌失败:_FAIL
主线位置:
rtw_os_ndevs_init → rtw_os_ndevs_alloc → rtw_os_ndevs_register
总结
RTL8189FS SDIO WiFi驱动完整流程总结(面试背诵版,分正向insmod + 反向rmmod)
正向流程:insmod rtl8189fs.ko
- 模块入口 :
rtw_drv_entry,调用sdio_register_driver,注册sdio_driver- 里面绑定:
id_table(sdio_ids)、.probe = rtw_drv_init、.remove = rtw_dev_remove
- 里面绑定:
- SDIO总线匹配
SDIO控制器硬件扫描SDIO设备,读取芯片Vendor/Device ID,和驱动里sdio_ids对比;匹配成功,内核回调probe:rtw_drv_init - probe:rtw_drv_init(主线核心)
sdio_dvobj_init:创建dvobj_priv(SDIO通道层容器,绑定sdio_func、识别芯片型号)rtw_sdio_primary_adapter_init:创建_adapter(WiFi业务适配器)- 分配adapter内存、加载驱动参数、绑定HAL硬件操作集
- 读取芯片版本、efuse(MAC、校准参数)、初始化驱动软件资源
rtw_os_ndevs_init(网卡注册总入口)rtw_os_ndevs_alloc:分配cfg80211资源、分配每个adapter对应的net_devicertw_os_ndevs_register:注册cfg80211无线框架 + net_device,内核生成wlan0
- 可选宏分支(你的配置下被关闭,灰色不编译):
sdio_alloc_irq:申请SDIO中断,芯片异步上报数据包/事件- GPIO唤醒、BT共存初始化
- probe全部成功,返回0;上层应用(wpa_supplicant、iw、ifconfig)可以操作wlan0,扫描、连接WiFi
✅ 关键结构体区分
dvobj_priv:SDIO芯片、总线通道层面,一个芯片一个dvobj_adapter:WiFi业务管理对象,负责扫描、连接、收发报文net_device:Linux内核标准网卡结构,内核网络子系统识别它wiphy:cfg80211无线子系统对象,负责无线配置
反向流程:rmmod rtl8189fs.ko / probe中途失败goto清理
- 触发
.remove回调rtw_dev_remove;或者probe中间任意一步失败,走goto清理链 rtw_os_ndevs_deinit(网卡清理入口,和rtw_os_ndevs_init成对)rtw_os_ndevs_unregister:先把net_device、cfg80211从内核注销,系统看不到wlan0rtw_os_ndevs_free:释放net_device、wiphy、cfg80211内存
- 释放adapter:
rtw_sdio_primary_adapter_deinit - 释放dvobj:
sdio_dvobj_deinit - 可选:释放中断
sdio_free_irq、释放GPIO资源 - 最后
sdio_unregister_driver
顺序口诀(必背)
正向:注册驱动 → ID匹配 → probe
dvobj → adapter → alloc网卡 → register网卡(wlan0出现)→ 可选中断
反向:注销网卡 → 释放网卡 → 释放adapter → 释放dvobj