瑞芯微Rockchip开发者社区
直播中

jefljel

8年用户 1124经验值
擅长:20762
私信 关注
[问答]

在rockchip平台怎样去实现一套兼容多款wifi模块的自适应框架呢

在rockchip平台怎样去实现一套兼容多款wifi模块的自适应框架呢?
rk3399的wifi模组自适应支持多款wifi是什么技术原理?

回帖(2)

陈小红

2022-3-7 17:12:04
疑问点?

Android hal层需要配置那些东西?
rk3399的wifi模组自适应支持多款wifi是什么技术原理?
wpa_supplicant需要配置那些东西?
回答上面的问题我们需要一步一步往下看。

什么是VID和PID?
答:VID为厂商,PID为产品ID。

硬件接口
单wifi硬件接口

在嵌入式中wifi功能在SOC中有两种方式,一种是集成在SOC当中,芯片代表有MTK和高通的芯片,因为这两家芯片厂商的通讯技术都比较厉害所以一般都会在他们自己的SOC中添加WIFI的模块,从而降低成本和降低板子的面积。另一种是应用处理器(Application processor)加wifi模组的方式,常用的通讯接口有为SDIO、USB、PCIE,其中SDIO、USB最为常用。
常用的wifi芯片厂商有NXP、瑞昱(Realtek)、博通Broadcom、MTK等,然后一些模组厂商会使用这些公司的芯片来做一下wifi模组,常见的模组厂商有富士康、海华、高盛达等等,模组厂商会调制好一些wifi的芯片射频参数,写入模组当中,如果直接使用芯片在生产的时候需要调试wifi的射频参数,从而降低直接使用芯片的难度。所以使用模组的好处是降低生产成本,因为要调制射频参数需要买仪器,坏处是会增加使用成本模组一般都会比芯片贵上1块钱左右(量多的时候)。

wifi蓝牙二合一硬件接口

为了降低成本,现在很多芯片都是集成了wifi和蓝牙功能的二合一芯片,所以我们不只看到有wifi使用的SDIO接口还有用于蓝牙wifi通讯的UART接口,还有PCM接口。我们播放歌曲的时候音乐的数据的传输使用的是UART接口,如果是蓝牙通话的时候使用的是PCM接口。所以我们可以在WIFI和蓝牙二合一的芯片上看到3种接口,其中SDIO是wifi的,UART和PCM是蓝牙的。


上图为wifi蓝牙二合一硬件接口图,黄色为SDIO接口、红色为UART接口、绿色为PCM接口。

其他引脚功能作用

WIFI_REG_ON: SOC输出引脚用于控制wifi模组使能。
WIFI_WAKE_HOST: SOC的输入引脚当系统休眠的时候,如果wifi接收到数据将产生中断将唤醒系统。

wifi模块驱动组成部分

由原理图我们知道在SOC中,wifi模块的接口常用为USB、SDIO、PCIE这3种接口,所以说一定有USB、SDIO、PCIE这3种控制器部分的驱动,这部分一般来说SOC厂商一般都会提供,wifi模块的驱动使用标准的接口API对外设进行读写。对于USB wifi可能不需要上下电的操作,因为USB wifi一插入USB的同时就进行了上电的操作,但是在SDIO的wifi和PCIE接口的WIFI来说一般都有使能的引脚,还有系统休眠的时候,wifi模块如果接收到数据需要唤醒系统,所以还有一个引脚对于SOC来说是输入引脚用于系统休眠的时候被wifi模组唤醒,所以对于这两个接口的wifi来说还需要一部分对于wifi上电和下电的驱动,还有唤醒系统用驱动,在rockchip平台来说这部分功能是放在rfkill-wlan.c。对于不同的wifi模组肯定有一部分是wifi模组特有,对应不同的wifi模组的驱动,这部分的驱动主要是由wifi模组厂商进行提供,这部分的驱动的功能为,向下对接硬件接口SDIO、USB、PCIE向上需要内核注册一个网络设备。因为wifi模组的驱动一般还需要加载一个二进制固件,所以对于wifi部分的驱动来说还有一个功能就是加载二进制固件到模组当中。

wifi应用部分wpa_supplicant

wpa_supplicant是一个开源软件,在Android平台wpa_supplicant被谷歌修改并加入到了Android系统,在linux平台主要就是使用wpa_supplicant的原生版本。wpa_supplicant的作用就是向上对接用户的wifi配网支持WEP,WPA/WPA2和WAPI无线协议和加密认证,向下对接wifi驱动和wifi芯片通讯的桥梁。wpa_supplicant使用C/S架构,有一个服务端,服务端可以和wifi芯片进行通讯。客户端用于和用户交互,比如配网的时候客户端获取用户输入的SSID和PASSWORD,客户端然后用过socket将SSID和PASSWORD发送到服务端,服务端和wifi模组进行通讯然后进行联网操作。


总结
要使用一个wifi功能需要涉及的部分有内核部分wifi驱动,应用部分wpa_supplicant服务。其中wifi驱动又包含很多部分,分为通讯接口的驱动SDIO、USB、PCIE等,还有上下电部分的驱动,wifi模组提供部分的驱动。应用部分不管是Android还是linux平台都是使用wpa_supplicant这个开源项目,这个应用主要的作用为对下和驱动和芯片进行通讯,对上提供接口给用户配网等操作,对于Android系统更上层还有通过JNI接口对上的JAVA层。

rockchip wifi启动流程简介

rockchip平台实现了一套兼容多款wifi模块的自适应框架,简单来说就是将多款wifi模块的驱动编译成xxx.ok文件,比如常用的wifi芯片厂商瑞昱、博通的wifi芯片的驱动统一编译成不同的xxx.ko文件,然后打包成固件放置在文件夹/vendor/lib/modules下面。


上图为多款wifi的ko文件,比如8188eu.ko、8189es.ko。
android系统启动之后,系统启动一个服务,读取u***接口的wifi或者sdio接口的wifi的pid和vid。然后和代码里面写死的pid和vid进行比较,确认是那款wifi,比如我们这里是rtl8821cu,所以识别出来rtl8821cu然后调用insmod加载8821cu.ko的wifi驱动
大致流程如下:



  • 开机对 wifi 模块上电,并自动进行扫描 sdio 操作。
  • 系统启动打开 wifi 操作时,分别对系统 sys/bus/sdio(sdio wifi)
    sys/bus/pic (pcie wifi )文件系统下的 uevent 进行读取。
  • 获取到 wifi 芯片 vid pid 加载相应的 wifi ko 驱动。
  • 识别到 wifi 类型后加载不同的 wpa_supplicant 参数启动 wifi。
实战移植USB wifi RTL8821CU

RTL8821CU简介

RTL8821CU是瑞昱半导体推出的u***接口的wifi,下面的教程将介绍如何将RTL8821CU移植到rk3399的平台上。

移植u*** wifi需要修改的文件

Android hal层需要修改的文件
frameworks/opt/net/wifi/libwifi_hal/rk_wifi_ctrl.cpp
frameworks/opt/net/wifi/libwifi_hal/wifi_hal_common.cpp

//kernel需要修改的文件
kernel/arch/arm64/boot/dts/rockchip/rk3399-tve1030g.dtsi //除去板上的sdio的wifi
kernel/arch/arm64/configs/rockchip_defconfig //添加rtl8821CU的驱动生成模块的方式,生成rtl8821cu.ko
kernel/drivers/net/wireless/rockchip_wlan/Kconfig //将wifi驱动放置到kernel/drivers/net/wireless/rockchip_wlan这个目录下,需要修改Kconfig和Makefile
kernel/drivers/net/wireless/rockchip_wlan/Makefile
kernel/drivers/net/wireless/rockchip_wlan/rtl8821CU/ //rtl8821CU的驱动,一般这部分驱动由原厂,或者wifi模组供应商提供

rfkill-wlan.c的作用

wifi驱动分为两部分,一部分是厂家提供的wifi驱动部分,这部分是标准的基本不用修改,不管是NXP,ROCKCHIP,全志等平台都是使用这一套代码,比如kernel/drivers/net/wireless/rockchip_wlan/rtl8821CU/。另一部分是和平台SOC有差异的,这部分一般是SOC厂家提供,这部分的功能是给WIFI模组上下电使用,比如配置唤醒引脚配置,当数据来的时候用来唤醒系统,比如上下电配置,用户可以使用节点给bt进行上电,这部分的功能就集成在rfkill-wlan.c。

wifi唤醒系统中断

系统进入休眠模式,如果wifi接收到数据,这时候需要唤醒系统,然后通知系统区读取数据。在硬件设计上有一个gpio的输入引脚接连接wifi模块和soc(WIFI_WAKE_HOST,上面的原理图已经说明),在linux中需要把这个引脚设置为中断引脚,然后注册一个中断服务函数,然后在中断中将系统唤醒。
下面的代码以Realtek来展示:

static int rtw_drv_init( struct sdio_func *func,const struct sdio_device_id *id)
         gpio_hostwakeup_alloc_irq(if1);
          err = request_threaded_irq(oob_irq, gpio_hostwakeup_irq_thread, NULL,
                        status, "rtw_wifi_gpio_wakeup", padapter);  //注册gpio中断函数,wifi接收到数据的时候会产生中断

static irqreturn_t gpio_hostwakeup_irq_thread(int irq, void *data)
{
        PADAPTER padapter = (PADAPTER)data;
        DBG_871X_LEVEL(_drv_always_, "gpio_hostwakeup_irq_threadn");
        /* Disable interrupt before calling handler */
        //disable_irq_nosync(oob_irq);
        rtw_lock_suspend_timeout(HZ/2);   //唤醒系统
#ifdef CONFIG_PLATFORM_ARM_SUN6I
        return 0;
#else
        return IRQ_HANDLED;
#endif
}

参考文档

移植之前可以先参考rk官方给到的文档《Rockchip_Introduction_Android9.0_WIFI_Configuration_CN.pdf》《Rockchip_Introduction_RealTek_WIFI_Driver_Porting_CN.pdf》。

如何获取pid 和 vid

将u*** wifi插到rk3399的开发板上,然后输入lsu***


通过和没插入u*** wifi之前输入的lsu***对比,插入后多了上图红色圈出来的设备,所以我们可以知道的u*** wifi rtl8821cu的pid为0bda,vid为c811,我们将这两个pid和vid记下来到到后面使用。

修改hal层

我们前面说了系统启动后会扫描u*** wifi的pid和vid然后在代码里找到是那款wifi芯片,然后找到wifi芯片的.ko文件的路径,这一部分的代码在
frameworks/opt/net/wifi/libwifi_hal/rk_wifi_ctrl.cpp
frameworks/opt/net/wifi/libwifi_hal/wifi_hal_common.cpp
我们在rk_wifi_ctrl.cpp添加rtl8821cu的pid和vid(红色方框为需要添加的)。


在wifi_hal_common.cpp添加rtl8821cu.ko文件的路径,系统启动的时候通过获取到pid和vip匹配之后,根据数组static wifi_device supported_wifi_devices找到"RTL8821CU"这个名字,然后用这个名字从数组wifi_ko_file_name module_list里面获取到RTL8821CU_DRIVER_MODULE_PATH这个宏,这个宏就是指定rtl8821的wifi驱动ko的位置。

#define RTL8821CU_DRIVER_MODULE_PATH   WIFI_MODULE_PATH"8821cu.ko"  //指定rtl8821的wifi驱动ko的位置
#define WIFI_MODULE_PATH                       "/vendor/lib/modules/"



后面就是加载不同的 wpa_supplicant 参数启动 wifi。代码都在frameworks/opt/net/wifi/libwifi_hal/这里,想研究的可以查看这里的代码。

[hal加载wifi驱动分析,请点这里](android hal 加载wifi.md)
举报

游成敏

2022-3-7 17:12:07
修改dts除去sdio wifi

dts路径:kernel/arch/arm64/boot/dts/rockchip/rk3399-tve1030g.dtsi
因为为的板子上有sdio接口的wifi,如果不除去,同时有u*** wifi和sdio wifi可能会产生冲突,所以我们先disable sdio的接口的wifi。


将rtl8816的wifi驱动放到kernel里面

我们将rtl8821CU wifi驱动复制到kernel/drivers/net/wireless/rockchip_wlan/这个目录下面,同时修改当前目录下的Kconfig文件和Makefile文件。
修改Makefile将rtl8821CU添加进去(红色方框为需要添加的)。


修改Kconfig(红色方框为需要添加的)


重新编译内核

我们修改完成全部的需要修改的文件后,需要从新编译内核和安卓系统,编译成功后确认确认rtl8821.ko文件是否打包到了vendor.img。我们可以在outtargetproductrk3399vendorlibmodules目录下查看是否有8821cu.ko这个文件,如果有这个文件就证明已经打包完成。

烧录

全部工作完成后,就可以烧录全部的img,烧录完成后等待开机,然后在Android界面设置选项wifi设置那里可以搜索到wifi信号,然后就可以像正常的使用Android手机一样使用wifi上网了。

问题排查

1.首先确认u*** wifi已经正常。
如果通过lsu***,可以获取到pid和vip证明u***通讯是正常的,sdio的wifi也是这个原理,首先证明sdio和wifi上电是正常的先。
2.加载ko文件的时候可能因为疏忽,导致加载ko文件路径出错,如果加载正常,正常的log应该如下。


05-28 08:38:44.954   259   259
I -service: Wifi Hal is booting up...
05-28 08:39:03.808   399   399 I wificond: wificond is starting up...
06-03 05:44:35.551   466   466 I SystemServiceManager: Starting com.android.server.wifi.WifiService
06-03 05:44:35.724   466   466 I SystemServiceManager: Starting com.android.server.wifi.scanner.WifiScanningService
06-03 05:44:35.726   466   466 I SystemServiceManager: Starting com.android.server.wifi.p2p.WifiP2pService
06-03 05:44:35.746   466   466 D ConnectivityService: wifiOnly=true
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 0
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 2
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 3
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 4
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 5
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 10
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 11
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 12
06-03 05:44:35.746   466   466 D ConnectivityService: networkAttributes - ignoring mobile as this dev is wifiOnly 15
06-03 05:44:36.369   466   485 E BatteryExternalStatsWorker: no controller energy info supplied for wifi
06-03 05:44:59.016   466   485 E BatteryExternalStatsWorker: no controller energy info supplied for wifi
06-03 05:45:02.142   466   485 E BatteryExternalStatsWorker: no controller energy info supplied for wifi
06-03 05:45:04.605   466   466 V SettingsProvider: Notifying for 0: content://settings/global/wifi_on
06-03 05:45:04.609   259   259

wifi驱动怎么知道使用那个sdio控制器

读到这里不知道你们是不是有疑问,我们的SOC上有很多的sdio控制器,有些sdio控制器接emmc flash,有些接wifi模块,wifi驱动怎么知道我接在那个sdio控制器上呢?
其实是在wifi厂商提供的驱动里面会注册一个sdio_register_driver,当上电的时候我的emmc驱动会去扫描sdio的外围设备,当确认扫描到设备的时候会去读取设备的id即wifi模块的pid和vid,如果设备id匹配上了之后会调用使用sdio_register_driver注册进去的probe函数,从而确认我们的设备是接到那个sdio设备上。代码流程如下所示。
代码路径:kerneldriversnetwirelessrockchip_wlanrtl8189fsos_deplinuxsdio_intf.c

static const struct sdio_device_id sdio_ids[] = {
#ifdef CONFIG_RTL8723B
        { SDIO_DEVICE(0x024c, 0xB723), .driver_data = RTL8723B},  //wifi设备pid 和 vid
#endif
};

static struct sdio_drv_priv sdio_drvpriv = {
        .r871xs_drv.probe = rtw_drv_init,
        .r871xs_drv.remove = rtw_dev_remove,
        .r871xs_drv.name = (char*)DRV_NAME,
        .r871xs_drv.id_table = sdio_ids,
        #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29))
        .r871xs_drv.drv = {
                .pm = &rtw_sdio_pm_ops,
        }
        #endif
};

ret = sdio_register_driver(&sdio_drvpriv.r871xs_drv); //将wifi设备注册进sdio,sdio驱动扫描到wifi外围设备后会读取pid和vid,然后匹配上了会调用rtw_drv_init函数。


u*** wifi和sdio的wifi也是一样的,wifi模块插入u***口的时候,会读取u***模块的vid和pid,如果读取到的vid和pid和使用u***_register注册进去的设备的vid和pid是一样证明驱动匹配上了,然后调用probe函数。下面是Realtek u*** wifi部分注册代码。
代码路径:kerneldriversnetwirelessrockchip_wlanrtl8188fuos_deplinuxu***_intf.c

static struct u***_device_id rtw_u***_id_tbl[] ={
#ifdef CONFIG_RTL8188E
        /*=== Realtek demoboard ===*/
        {USB_DEVICE(USB_VENDER_ID_REALTEK, 0x8179),.driver_info = RTL8188E}, /* 8188EUS */ //u*** wifi 的pid和vid,使用匹配驱动。
        {USB_DEVICE(USB_VENDER_ID_REALTEK, 0x0179),.driver_info = RTL8188E}, /* 8188ETV */
};

struct rtw_u***_drv u***_drv = {
        .u***drv.name =(char*)DRV_NAME,
        .u***drv.probe = rtw_drv_init,
        .u***drv.disconnect = rtw_dev_remove,
        .u***drv.id_table = rtw_u***_id_tbl,
        .u***drv.suspend =  rtw_suspend,
        .u***drv.resume = rtw_resume,
        #if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 22))
          .u***drv.reset_resume   = rtw_resume,
        #endif
        #ifdef CONFIG_AUTOSUSPEND
        .u***drv.supports_autosuspend = 1,
        #endif

        #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19))
        .u***drv.drvwrap.driver.shutdown = rtw_dev_shutdown,
        #else
        .u***drv.driver.shutdown = rtw_dev_shutdown,
        #endif
};

ret = u***_register(&u***_drv.u***drv);

wifi射频测试之wl

射频测试的时候一般要使用一个指令wl,这个指令可以设置wifi模块的发射和接收等,然后通过综测仪等仪器可以用来测试wifi的性能,wl指令一般可以向wifi模组供应商索要,一般会给到源码,然后使用交叉编译工具链进行编译,编译好后push的Android系统中,然后使用指令进行测试。

wifi驱动排查步骤

1、linux sdio驱动无法识别(读取)wifi vendro ID硬件排查步骤。
1.1、第一步首先是排查电压,电压供电是否稳定,有没有电压被拉低的情况,一般来说wifi有如下几组电压。
VDD3.3 -> 3.3V的主电压。
DVDDIO_1.8V -> sdio IO的1.8v电压。

1.2、如果wifi模组没有晶振,时钟来自外部,则需要排查模组是否有32k的时钟输入,一般来说这个32k的时钟来自host,但是也可以使用外部的时钟芯片来提供,具体看原理图的实现。

1.3、排查chip_en脚有没有拉高。

1.4、如果上面的步骤都已经排查完毕,但是sdio驱动还是无法获取到wifi的vendor ID,可以排查别的脚是不是被别的电平干扰。
比如在调试amlogic W1芯片的时候,主控的32k接到了BT_WAKE_HOST引脚,本来这个引脚对主控来说是WIFI用来唤醒深度睡眠的HOST的,由于dts没有修改按照官方的默认配置,这个引脚输出了32k HZ的时钟,导致了W1进入了内部的测试模式,导致无法读取到vendor ID,或者sdio发送cmd没有响应。

的实现。

1.3、排查chip_en脚有没有拉高。

1.4、如果上面的步骤都已经排查完毕,但是sdio驱动还是无法获取到wifi的vendor ID,可以排查别的脚是不是被别的电平干扰。
比如在调试amlogic W1芯片的时候,主控的32k接到了BT_WAKE_HOST引脚,本来这个引脚对主控来说是WIFI用来唤醒深度睡眠的HOST的,由于dts没有修改按照官方的默认配置,这个引脚输出了32k HZ的时钟,导致了W1进入了内部的测试模式,导致无法读取到vendor ID,或者sdio发送cmd没有响应。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分