前情提要
这次是要换一个 wifi 芯片,就把这个换的过程记录下来,因为自己也是新手,很多东西都是自己一点点摸出来的,就希望一些东西能对跟我一样,新入门的人有些帮助,能快速入门。
基本设施
- 基于 君正 x1000e, halley2
- wifi 由原君正的换到 rtl8189es
- 切换前是用的 原厂 demo 板,切换后用的是新画的板子,上面是新的wifi 芯片
一步步的经历
首先可以确定的,在原来的 demo 板上,一切OK。wifi 都是正常的。开始换新的芯片,并且移植代码
代码移植
替换的新 rtl 芯片是供应商提供源码的,所以第一步是将源码放到内核中去编译。当然,编译一般会挺顺利,修改了源码中的配置,设置对应的编译器之类的。(源码是供应商的,所以只能说明思路,代码不能展示)
首次运行
当然,很明显,运行起来没有效果,wlan0 设备没有生成。接着就开始想是哪里的问题。这个 wifi 芯片是 sdio 接口的,看原理图,sdio 部分基本不管,那么可操作的就只有 PWR_EN 和 WAKEUP,看起来,WAKEUP 像是休眠之类的功能,问的供应商可以不管,那么就只有 PWR_EN 了,但是这个不管怎么设置都还是没有效果。
下面肯定就没有什么办法了,也不知道怎么弄了,那么就只有源码对比,我开始对比原来的实现,和现在的实现有什么区别,这么大的代码量,对比起来还是很难下手的。但是大家的接口都是一样的 sdio,所以其实想一想,肯定就是一些配置的问题,于是就从配置部分去考虑,其它的不用去处理。
代码分析
首先开始了解一下代码。之前一直看驱动,但是没有实践,所以有些东西,理解不了它在代码上是个什么展示方式,所以有时候别人给你提示你也不知道怎么弄,想问别人也不知道怎么问。
这里首先有个所谓的 platform 模型,这是说内核已经做好的 驱动和设备的对应框架结构,使用者只需要设计好驱动,配置一下设备就可以用了。而一般驱动都是由芯片厂商提供好。那么终端用户就只要去配置设备部分就可以了。
platform
device <----> driver
name ----- name
arch/xxx/xx driver/net/wireless/xxx
你上面这样,驱动部分一般都放在 driver 的相关目录下,实现 probe 函数,在加载时内核会去实现相互的关联,不管哪个先,哪个后,最终都会去匹配,比如加载 driver ,会去找寻设备,而设备存在了就会去找寻驱动 。在设置 device 和 driver 的时候都要求写个 name ,内核会根据name 部分进行匹配
在更新的内核中,device 部分不在是自己去写代码了,而是基于 dts 来实现了
而设备部分一般放在 arch 目录下对应的平台中,因为这部分是平台相关的,也就是自己设计硬件之后可能需要改动的地方。
arch
| - mips
| - xburst - 芯片架构
| - soc-x1000 - 芯片soc
| - common
| - chip-x1000 - 具体型号
| - halley2 - 具体类型
| - common
| - halley2_v10 - 更细的分类,一般自己修改硬件,需要改动引脚配置就在这里
依据上图,以君正 x1000e,halley2 来看看目录结构图,差不多就是这样。一般需要修改的在 halley2_v10
和 halley2/common
目录下,halley2_v10
一般是引脚配置,halley2/common
一般是相应驱动设备的配置。
比如刚刚 wifi 中提到的可能需要改动的两个引脚
#define GPIO_WIFI_RST_N GPIO_PC(17)
#define GPIO_WIFI_WAKE GPIO_PC(16)
会有这个类似于这样的定义,指明是哪个引脚,这个具体名字,可以根据原理图上的去看,一般会尽可能让名字相近,原理图上会说明对应的引脚是哪个。
那么怎么去操作呢,一般我们会根据这个引脚名在相关平台下去搜索哪里用到了就会找到。
先说一下,君正原生的 wifi 相关的文件有哪些
halley2/common/mmc.c - wifi sdio 接口的配置
halley2/common/43438_wlan_device.c - 设备的配置
halley2/common/43438_wlan_power_control.c - 引脚的配置,主要是定义一些功能函数,会有设备或驱动 调用
drivers/net/wireless/bcmdhd_1_141_66/dhd_linux.c - 驱动入口函数
mmc 暂时可以不关注,是 sdio 接口的使能操作,没有修改都是正常的
43438_wlan_device.c 里面有一段代码
static struct platform_device wlan_device = {
.name = "bcmdhd_wlan",
.id = 1,
.dev = {
.platform_data = NULL,
},
.resource = wlan_resources,
.num_resources = ARRAY_SIZE(wlan_resources),
};
static int __init wlan_device_init(void)
{
int ret;
ret = platform_device_register(&wlan_device);
return ret;
}
再对应驱动部分的
#define WIFI_PLAT_NAME "bcmdhd_wlan"
static struct platform_driver wifi_platform_dev_driver = {
.probe = wifi_plat_dev_drv_probe,
.remove = wifi_plat_dev_drv_remove,
.suspend = wifi_plat_dev_drv_suspend,
.resume = wifi_plat_dev_drv_resume,
.driver = {
.name = WIFI_PLAT_NAME,
}
};
err = platform_driver_register(&wifi_platform_dev_driver);
驱动部分在文件中的各个地方,我截取出重点,可以看到这里驱动部分的name 对应的与 device 中的 name 一样,这样来形成对应。所以驱动部分通过 platform_driver_register
来注册驱动 而,platform_device_register
来注册设备,内核就记录他们并进行匹配。其中那个 xxx_power_control.c 中定义的引脚操作就会被这里的驱动层所使用,具体可以看源码。
驱动部分这里就不多说了,主要是设备部分
static struct resource wlan_resources[] = {
[0] = {
.start = GPIO_WIFI_WAKE,
.end = GPIO_WIFI_WAKE,
.name = "bcmdhd_wlan_irq",
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE,
},
};
这里是对 WAKEUP 部分的定义,就是 device 部分的 resource , 驱动部分会对应这引脚申请 irq,并定义中断函数,这里应该就是去实现休眠相关的东西吧。
电源部分就不取代码了,主要功能就是通过gpio 方式操作引脚实现开和关,当然里也有很多细节的东西,但这里就不说了。
设备与驱动的代码架构和匹配方式就如这里说的,其它的硬件基本也是如此,就可以按这样的方式来看其它的硬件设备。
继续调试
上面说的这些,都是我自己看代码总结出来的东西,下面还是要继续调试设备
在代码移植好之后,内核配置,将原来的wifi 关掉,开启新的,然后运行,当然,没有成功。思考之后,觉得是device 部分没有配置,因为 rtl 的源码中没有找到,而且他们肯定不知道我们的硬件是怎么配置的。所以手动去初始化那个设备。甚至把这些配置加到 rtl 的源码驱动层中,也都没有用。
使用工具测量 wifi 引脚也是有电压的。打印也是有输出的。可见流程都走了,但就是没有生成设备。那么必然还是哪个地方的执行流程不对。于是再跟原来的做对比。
在代码中找了许久,一般 platform 架构要有 platform_driver_register
, platform_device_register
来进行配对,在以前的版本中都能找到,但是在新的代码中没有找到 platform_driver_register
可见这里应该有什么问题。于是想到把以前的 wifi 也开着,现在也开关,那么以前的操作 device 结束后,再由新的wifi 来启动其它操作应该可以。这样一试,果然可以。那么问题肯定就出在 device 操作相关的问题上。
看以前的代码,调用过程应该是
platform_driver_register -> device_init -> sdio_register_driver
当然 , device_init 部分还是有很多具体的操作过程的。而在新的代码中只有 sdio_driver_register 部分,那么想必在新的代码中加入 platform_driver_register 部分的操作应该就是可以的。但是时间不足,暂时没有作具体验证。
其中 sdio_register_driver
是 sdio 接口部分的功能,sdio 与 platform 的匹配部分是不一样的
static const struct sdio_device_id bcmsdh_sdmmc_ids[] = {
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_DEFAULT) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325_SDGWB) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4325) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4329) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4319) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4330) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4334) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_4324) },
{ SDIO_DEVICE(SDIO_VENDOR_ID_BROADCOM, SDIO_DEVICE_ID_BROADCOM_43239) },
{ SDIO_DEVICE_CLASS(SDIO_CLASS_NONE) },
{ /* end: all zeroes */ },
};
MODULE_DEVICE_TABLE(sdio, bcmsdh_sdmmc_ids);
static struct sdio_driver bcmsdh_sdmmc_driver = {
.probe = bcmsdh_sdmmc_probe,
.remove = bcmsdh_sdmmc_remove,
.name = "bcmsdh_sdmmc",
.id_table = bcmsdh_sdmmc_ids,
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM)
.drv = {
.pm = &bcmsdh_sdmmc_pm_ops,
},
#endif /* (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 39)) && defined(CONFIG_PM) */
};
这是老wifi 中的代码部分,可以看到,这里有个 id_table ,sdio 部分的匹配过程,在网上找到是通过搜索找到 是通过找 id_table 来确定对应的设备的。
总结
到这里,基本上调试出来了,后面有时间再验证最后一个。自己也是从不懂一步步看代码,测试过来的。里面也会有遗漏,出错的地方,欢迎指正。