RV1109与hi3861L SD卡槽WiFi驱动移植实战:内核适配与调试技巧

张开发
2026/4/16 5:04:42 15 分钟阅读

分享文章

RV1109与hi3861L SD卡槽WiFi驱动移植实战:内核适配与调试技巧
1. 从零开始的WiFi驱动移植挑战最近在做一个智能家居网关项目需要把海思hi3861L WiFi模块移植到瑞芯微RV1109平台上。刚开始接到这个任务时我整个人都是懵的——两个不同架构的芯片内核版本还差这么多hi3861L驱动基于Linux 4.9而RV1109跑的是4.19这要怎么搞但经过一个月的折腾总算把这块硬骨头啃下来了。今天就把我的实战经验分享给大家特别是那些和我一样第一次做驱动移植的新手朋友们。先说说硬件环境。RV1109开发板本身没有引出SDIO接口这给我们出了个大难题。好在天无绝人之路我发现SD卡槽用的也是SDIO协议于是灵机一动做个转接板把WiFi模块接到SD卡槽上不就行了这个方案实测可行成本还低。具体做法是用金手指PCB转接板把SD卡的CLK、CMD、DATA0三个信号线引出来接WiFi模块DATA1作为中断线这样既实现了通信又节省了GPIO资源。软件方面最大的挑战是内核版本差异。Linux 4.9到4.19这三年间内核改动不小直接编译原驱动会出现各种报错。我统计了下主要遇到6类问题结构体成员改名、等待队列类型变更、定时器API更新、调度参数缺失、信号发送函数未定义以及系统时钟接口变化。每个问题都够喝一壶的特别是对于我这种内核新手来说。2. 内核差异问题逐个击破2.1 结构体成员变更引发的血案第一个报错就给我来了个下马威hichannel//oal/oal_net.h:38:57: error: oal_net_device_stru has no member named destructor原来在4.19内核里net_device结构体的destructor成员改名为priv_destructor了。这种改名还算好处理全局搜索替换就行。但有些结构体变动就比较隐蔽比如wait_queue_t类型在4.13版本后被wait_queue_entry_t取代如果不熟悉内核更新历史光看报错信息很难定位。我后来总结出一个技巧遇到不认识的类型或成员直接去内核源码里搜。比如用git grep struct net_device查看结构体定义对比不同版本的变化。Linux内核的Documentation/process/stable-api-nonsense.rst文件也很有帮助它解释了为什么内核API不保持稳定。2.2 定时器接口的大变革最头疼的是定时器相关的修改error: implicit declaration of function init_timer从4.15内核开始旧的init_timer()完全被timer_setup()取代了。这不仅仅是函数名变化整个使用逻辑都变了。以前需要手动设置data和function成员现在改用from_timer()宏来获取容器结构体。我参考了LWN上的文章《The timer API transition》那个链接后来发现打不开了好在有archive.org花了整整两天才改对。具体修改涉及三个文件// 旧代码 init_timer(timer); timer.data (unsigned long)dev; timer.function callback; // 新代码 timer_setup(timer, callback, 0);回调函数原型也从void func(unsigned long)变成了void func(struct timer_list *)需要重写所有定时器回调。3. 平台适配实战技巧3.1 设备树配置的玄学硬件平台差异主要体现在设备树(DTS)配置上。hi3861L使用一线SDIO模式1-bit数据线而RV1109默认SD卡配置是4-bit的。我们需要修改sdmmc节点sdmmc { max-frequency 50000000; bus-width 1; // 关键修改 cap-sdio-irq; keep-power-in-suspend; supports-sdio; // 启用SDIO模式 pinctrl-0 sdmmc0_clk sdmmc0_cmd sdmmc0_det sdmmc0_bus1; };这里有个坑pinctrl配置必须和bus-width匹配。我一开始漏改了pinctrl-0导致数据线无法正常工作。建议修改前后用diff工具对比diff -u old.dts new.dts | less3.2 中断处理的那些坑GPIO中断配置也踩了不少坑。hi3861L的中断线接在SDMMC_D1上对应RV1109的GPIO1_A5。这里要注意GPIO编号计算方式GPIO0: 0~31GPIO1: 32~63GPIO1_A5 32 5 37在驱动中需要这样配置#define WIFI_IRQ_GPIO 37 gpio_request(WIFI_IRQ_GPIO, wifi_irq); gpio_direction_input(WIFI_IRQ_GPIO); irq gpio_to_irq(WIFI_IRQ_GPIO); request_irq(irq, wifi_irq_handler, IRQF_TRIGGER_FALLING, wifi_irq, NULL);中断触发方式也要特别注意hi3861L要求下降沿触发。如果配置错误会导致中断不触发或者频繁触发。我用示波器抓了信号波形才确认硬件没问题是软件配置问题。4. 调试与优化经验谈4.1 打印的艺术调试驱动最离不开printk但乱用打印会影响性能。我的经验是关键路径用pr_info比如初始化流程错误处理用pr_err并带设备标识高频操作用pr_debug通过动态调试控制// 在Makefile添加 ccflags-y -DDEBUG // 运行时控制 echo 8 /proc/sys/kernel/printk echo file sdio_host.c p /sys/kernel/debug/dynamic_debug/control4.2 性能调优实战移植完成后测速发现吞吐量只有2Mbps远低于预期的20Mbps。用perf工具分析发现是SDIO时钟配置问题perf stat -e sdhci:* -a sleep 10结果显示时钟频繁切换。最终在设备树添加fixed-clock配置解决sdmmc { assigned-clocks cru SCLK_SDMMC; assigned-clock-rates 50000000; };电源管理也需要注意WiFi模块在suspend时要保持供电sdmmc { keep-power-in-suspend; enable-sdio-wakeup; };5. 常见问题解决方案5.1 驱动加载失败排查遇到驱动加载失败时我的一般排查步骤检查内核日志dmesg | grep hichannel确认模块依赖lsmod查看依赖模块是否加载验证设备节点ls /sys/bus/sdio/devices检查中断注册cat /proc/interrupts | grep wifi测试SDIO通信mmc_test工具测试总线5.2 稳定性问题处理项目上线后偶现WiFi断连经过两周抓log发现是电源管理导致[ 1234.567890] mmc1: card 0001 removed [ 1234.571234] hichannel: sdio removed解决方法是在驱动中禁用自动PMmmc-caps | MMC_CAP_NEEDS_POLL; mmc-pm_caps ~MMC_PM_KEEP_POWER;6. 进阶开发建议6.1 功耗优化技巧对于电池设备功耗优化很关键。实测发现关闭SDIO时钟门控可降低延迟调整WiFi唤醒间隔能平衡功耗和响应速度合理设置DTIM间隔节省电量具体参数可通过sysfs调整echo 3 /sys/module/hichannel/parameters/wakeup_interval6.2 自动化测试方案建议搭建自动化测试环境我的方案是Python脚本控制开发板iperf3测试吞吐量ping监控延迟sysfs接口触发各种状态切换测试用例要覆盖正常数据传输反复插拔SDIO卡休眠唤醒循环长时间稳定性测试7. 移植后的思考整个移植过程最大的体会是内核版本差异带来的问题远比硬件差异棘手。建议后来者先通读内核的stable_api_nonsense.rst文档用cscope建立代码索引方便查找善用git blame查看代码变更历史保持驱动与内核版本的同步更新这次项目让我对Linux内核的理解深了不少特别是设备驱动模型和电源管理部分。虽然踩了不少坑但看到WiFi图标亮起的那一刻所有的熬夜都值了。

更多文章