高通SDX55平台 R8168 PHY驱动适配
1. SDX55 CPE应用场景
高通5G平台SDX55支持5G独立组网(SA)和非独立组网(NSA)两种网络架构,同时兼容LTE和WCDMA制式,拥有更快的传输速度,更优秀的承载能力,以及更低的网络延时,可广泛应用于网关、工业监控、远程医疗、无人机、虚拟现实和沉浸式体验(VR和AR)、智慧能源、车联网、工业互联网、智慧教育、高清视频、智慧城市、家庭娱乐等多个领域。
当SDX55被应用于工业路由器或CPE时,基本都是采取以下连接方案:客户Linux设备通过usb或pcie连接SDX55,使用AT或QMI方式进行拨号,Linux上获取公网ip进行上网,来自Linux的数据流仅可通过USB或pcie方式传输到modem侧,进一步传输到网络侧,完成数据交互。现有方案和数据流程图如下:
(1) 终端设备的数据通过网线或wifi发送到客户设备lan侧端口; (2) lan侧端口收到数据后会通过cpu对数据进行处理和转发,转发到wan侧端口,即usb0/pcie_mhi0; (3) usb0/pcie_mhi0将数据通过usb/pcie转发给modem; (4) modem将数据发送给internet
但是通常CPE厂商考虑到更高的速率和更好的性能,会将控制通路和数据通路进行区分,如控制通路使用USB,而数据通路走PCIE,这就需要在主控和Modem间额外一个PCIE PHY芯片,如RTL8111H、RTL8125、AQC107等。整体框图和数据流方案如下:
(1)modem通过usb与host设备的主控进行连接,用于指令控制(也可作为host的wan口,用于数据传输); (2)增加一个支持PCIE协议的PHY 芯片作为pcie网卡,如RTL8111、r8125等低成本2.5G phy芯片,将phy 通过pcie与modem连接,使用pcie协议进行数据传输; (3)phy与host主控通过MDIO连接,作为host的wan口,用于与modem数据传输; (4)主控的lan口连接RJ45接口,形成有线网口,供终端设备上网; (5)主控的lan口连接wifi模块,形成无线网口,也可供终端上网。
针对以上场景,我们基于高通SDX55平台,调试RTL8111H PHY芯片。
2. R8168驱动调试
调试前提是基于硬件涉及连接完成,可保证pcie主线上可正确识别到PHY芯片。下面以主控MT7621、RTL8111H、SDX55为例,进行调试。
2.1 lspci
通过串口可以确认SDX55已按照硬件形态作为PCIE RC模式启动,并可以查询到模块pci总线上已识别到8111H PHY芯片:
2.2 集成r8168驱动到内核
从REALTEK官网下载R8168最新驱动源码,下载地址:https://www.realtek.com/en/component/zoo/category/network-interface-controllers-10-100-1000m-gigabit-ethernet-pci-express-software,将驱动源码放到SDX55基线内核如下目录:kernelmsm-4.14driversnetethernetrealtek,这个目录主要存放内核驱动中以太网相关驱动源码,realtek中原本有r8125、r8139、r8169等驱动,我们只需要将我们下载的r8168放到该目录下即可:
另外,为了将r8168驱动集成到内核中,需要在Makefile中新增r8168编译选项:
编译验证,出现无法成功加载驱动问题,且无任何异常打印。由于是直接将r8168直接编译进内核,所以无法在文件系统中找到驱动文件,进一步调试。我们有两种怀疑:1.驱动没有编译进内核;2.驱动由于某种原因加载失败。针对这两种思路,我们尝试将驱动编译成.ko模块的方式,然后通过adb导入到模块内进行手动加载:
2.3 could not insert module r8168.ko: Permission denied
手动加载r8168驱动提醒无权限,分析和编译进内核加载失败原因一致:
为了进一步验证,先手动关闭selinux权限,再加载验证:
关闭selinux权限再手动加载r8168.ko是ok的,进一步验证基本功能,首先在模块侧会枚举出以太网卡eth0,然后将eth0网卡加入网桥,并启动dhcp服务用于给host侧分配ip,通常我们可以在SDX55中增加进程用于检测以太网卡枚举,当检测到以太网卡枚举,由进程去up网卡,将网卡加入网桥并启动dhcp服务等,此处先手动完成:
通过如图的配置之后,我们再来检查host侧是否拿到ip,模块是否能通过8111h PHY和MT7621建立起通信:
MT7621的eth1(8168 PHY枚举出的网卡)已获取到ip(192.168.225.50),进行ping网关(192.168.225.1)测试也是ok的,进行反向测试,从模块ping MT7621:
也是ok的,模块拨号,ping外网测试:
2.4 以服务方式加载r8168驱动
上面手动加载验证,整个功能是ok的,基本满足需求,但是存在手动加载驱动无权限问题;这基本可以判断出编译进内核没有加载成功的原因也是由于权限问题导致,虽然手动关闭selinux进行加载测试,r8168基本功能正常,但是通用场景下无法手动加载并主动去关闭selinux,需要解决权限问题。直接编译进内核我们无法控制加载时间和权限问题,因此我们借鉴r8125的驱动加载经验,通过启动服务来使用脚本加载驱动,这样方便我们调整驱动加载时机,控制驱动加载启动方式如是否携带参数等等。首先我们编写了如下service服务,在service中调用r8168_start_stop_le脚本去加载驱动,启动时间在系统初始化服务init_sys_mss.service之后:
代码语言:javascript复制poky/meta-qti-data/recipes/r8168/files/r8168.service
[Unit]
Description=R8168
SourcePath=/etc/initscripts/r8168_start_stop_le
After=init_sys_mss.service
DefaultDependencies=no
[Service]
Restart=no
RemainAfterExit=yes
ExecStart=/etc/initscripts/r8168_start_stop_le start
ExecStop=/etc/initscripts/r8168_start_stop_le stop
[Install]
WantedBy=multi-user.target
r8168_start_stop_le脚本中主要是加载r8168.ko驱动,另外我们还增加了加载驱动时的参数携带,用于支持以太网卡eth0的mac地址可设置,原理是手动发送AT命令去设置mac地址,这个AT下发后会在模块内部创建/data/mac.txt文件,并将下发的mac地址写入到该文件,在驱动加载脚本中可以通过读取文件,获取写入的mac地址,以参数的方式带入驱动,用于驱动设置mac地址,具体脚本见下:
代码语言:javascript复制#! /bin/sh
# r8168 Driver init.d script to load r8168 driver
export MODULE_BASE=/lib/modules/`uname -r`/extra
PHY_MAC=$(cat /data/mac.txt)
echo "mac.txt -> [$PHY_MAC],mac.len -> [${#PHY_MAC}]" > /dev/kmsg
if [ ${#PHY_MAC} -eq 17 ];then
PHY_MAC_VALID=1
else
PHY_MAC_VALID=0
PHY_MAC="00:00:00:00:00:00"
fi
case "$1" in
start)
echo -n "Starting/Loading Ethernet Driver: "
echo "r8168:start/load" > /dev/kmsg
insmod $MODULE_BASE/r8168.ko #phy_mac=$PHY_MAC mac_phy_valid=$PHY_MAC_VALID
echo "lk-test-at-normal" > /dev/kmsg
ifconfig eth0 up
echo "done loading ethernet driver"
;;
stop)
echo -n "Unloading Ethernet Driver "
rmmod r8168
echo "done unloading ethernet driver"
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage { start | stop | restart}" >&2
exit 1
;;
esac
exit 0
驱动中新增参数适配:
代码语言:javascript复制char *phy_mac = "";
module_param(phy_mac, charp, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
int mac_phy_valid=0;
module_param(mac_phy_valid, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
对手动设置mac地址的场景进行适配:
代码语言:javascript复制static int
rtl8168_get_mac_address(struct net_device *dev)
{
...
if (!is_valid_ether_addr(mac_addr)) {
netif_err(tp, probe, dev, "Invalid ether addr %pMn", mac_addr);
if(mac_phy_valid)
{
char *command_buf = strsep(&phy_mac,":");
if(command_buf == NULL)
return -EINVAL;
mac_addr[0] = (unsigned char)simple_strtoull(command_buf,NULL,16);
command_buf = strsep(&phy_mac,":");
if(command_buf == NULL)
return -EINVAL;
mac_addr[1] = (unsigned char)simple_strtoull(command_buf,NULL,16);
command_buf = strsep(&phy_mac,":");
if(command_buf == NULL)
return -EINVAL;
mac_addr[2] = (unsigned char)simple_strtoull(command_buf,NULL,16);
command_buf = strsep(&phy_mac,":");
if(command_buf == NULL)
return -EINVAL;
mac_addr[3] = (unsigned char)simple_strtoull(command_buf,NULL,16);
command_buf = strsep(&phy_mac,":");
if(command_buf == NULL)
return -EINVAL;
mac_addr[4] = (unsigned char)simple_strtoull(command_buf,NULL,16);
command_buf = strsep(&phy_mac,":");
if(command_buf == NULL)
return -EINVAL;
mac_addr[5] = (unsigned char)simple_strtoull(command_buf,NULL,16);
netif_err(tp, probe, dev,"add0 is %d, add1 is %d, add2 is %d, add3 is %d, add4 is %d, add5 is %drn",mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3],mac_addr[4],mac_addr[5]);
netif_info(tp, probe, dev, "manual set ether addr %pMn",mac_addr);
}
else
{
eth_hw_addr_random(dev);
ether_addr_copy(mac_addr, dev->dev_addr);
netif_info(tp, probe, dev, "Random ether addr %pMn",mac_addr);
}
tp->random_mac = 1;
...
}
为了将驱动集成到SDX55,我们还需要编写编译脚本,交叉编译出可在SDX55上可用的驱动,高通平台源码编译使用的是bitbake工具,因此需要我们编写.bb脚本,用于驱动或工具的编译,在bb脚本中指定源码所在位置,另外还需将生成的ko文件、servce文件、脚本文件等拷贝安装到指定目录,便于打包进镜像:
代码语言:javascript复制# r8168.bb
SUMMARY = "R8168"
export R8168_OBJDIR = "${WORKDIR}/kobj"
export R8168_SRCDIR = "${WORKSPACE}/data-kernel/drivers/r8168/src"
HOMEPAGE = "http://support.cdmatech.com"
LICENSE = "Qualcomm-Technologies-Inc.-Proprietary"
LIC_FILES_CHKSUM = "file://${COREBASE}/meta-qti-bsp-prop/files/qcom-licenses/${LICENSE};md5=92b1d0ceea78229551577d4284669bb8"
inherit module
inherit qperf
inherit systemd
FILES_${PN} = "${nonarch_base_libdir}/modules/${KERNEL_VERSION}/kernel/drivers/net/ethernet/realtek/r8168/src/*"
FILES_${PN} = "/etc/initscripts/r8168_start_stop_le"
# Files from meta-qti-data
FILESPATH = "${WORKSPACE}:"
SRC_URI = "file://kobj/Makefile"
SRC_URI = "file://r8168.service"
SRC_URI = "file://r8168_start_stop_le"
S = "${R8168_OBJDIR}"
# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
RPROVIDES_${PN} = "kernel-module-r8168"
SYSTEMD_SERVICE_${PN} = "r8168.service"
INITSCRIPT_NAME = "r8168_start_stop_le"
INITSCRIPT_PARAMS = " start 37 S . stop 63 0 1 6 "
do_install_append() {
# Install unit files to systemd system directory and they will be
# packaged and enabled by the systemd class if 'systemd' feature
# is enabled in the distro.
install -d ${D}${sysconfdir}/initscripts
install -d ${D}${systemd_system_unitdir}/
install -m 0755 ${WORKDIR}/r8168_start_stop_le ${D}${sysconfdir}/initscripts
install -m 0644 ${WORKDIR}/r8168.service
-D ${D}${systemd_system_unitdir}/r8168.service
}
# qperf class adds do_copy_kernel_module() after do_module_signing().
# Since we do not yet support module signing, explicitly add the task to
# execute between compile and package stages.
addtask copy_kernel_module after do_compile before do_package
# vim: syntax=bitbake
编译生成镜像,进行验证,发现在镜像中未找到r8125.ko、service、脚本等文件,检查编译步骤,发现这些文件在poky/build目录下已经生成,因此怀疑是install时安装失败,在pokybuildtmp-glibcworksdxprairie-oe-linux-gnueabimachine-image1.0-r0rootfsetcinitscripts目录下查找确实缺少脚本文件,在其他对应目录下也缺少ko、service文件,对比其他bb脚本,发现缺少如下配置:
修改后文件确实在rootfs目录下存在了,但在镜像中还是没有相关文件,再次排查打包脚本,发现在脚本中并不是所有安装目录下的文件都会打包,会读取配置文件/poky/meta-qti-data/recipes-products/images/sdxprairie/sdxprairie-data-image.inc,检查该配置文件,缺少r8168,增加进一步验证:
代码语言:javascript复制#sdxprairie-data-image.inc
IMAGE_INSTALL = "r8168"
编译进一步验证,集成成功,并且权限问题也没有再出现,分析和加载驱动时机有关系:
经过上述修改,再次验证,驱动加载服务可正常加载驱动,识别PHY芯片,dhcp服务也可自动给MT7621侧分配ip,当SDX55拨号后,MT7621侧可ping通外网。驱动调试完成。