1. 项目背景

使用Quectel移远EC200A型号的4G模块给NVIDIA® Jetson AGX Xavier™平台添加移动网络。

(1)硬件平台介绍

  • NVIDIA® Jetson AGX Xavier™嵌入式平台,如图1左;
  • Quectel EC200A 4G USB Dongle通信模块,如图1右。
AGX平台 4G模块
图1 AGX平台(左)和4G模块(右)

(2)编译平台和工具

  • 系统版本:Ubuntu 20.04.5 LTS
  • 编译工具:aarch64-linux-gnu-gcc 9.4.0

本文是在目标设备AGX(arm64/aarch64)上配置并编译Linux驱动,也可以在x64架构的电脑主机上采用交叉编译方式进行配置。如果采用交叉编译的方式则推荐使用的交叉编译工具链为aarch64-linux-gnu-gcc 7.3.1 (Linaro GCC 7.3-2018.05),下载地址:Linaro Releases

2. 准备材料和知识

(1)AGX内核源码

NVIDIA Jetson AGX系列是专为嵌入式人工智能应用而设计的开发板,它具有自己特定的硬件架构和外设。为了适配硬件平台,我们不能使用普通的kernel内核源码。

根据AGX的Linux内核版本,在NVIDIA官网下载对应的Linux内核源码。

1
2
3
4
mv@ubuntu:~$ uname -a
Linux ubuntu 5.10.104-tegra #1 SMP PREEMPT Sun Mar 19 07:55:28 PDT 2023 aarch64 aarch64 aarch64 GNU/Linux
mv@ubuntu:~$ head -n 1 /etc/nv_tegra_release
# R35 (release), REVISION: 3.1, GCID: 32827747, BOARD: t186ref, EABI: aarch64, DATE: Sun Mar 19 15:19:21 UTC 2023

这里使用的AGX版本是R35,内核版本是Linux 5.10.104。于是在NVIDIA DEVELOPER下载中心(需要注意的是NVIDIA开发者的.cn网站不提供下载页面,访问后提示找不到页面,这里给出的是NVIDIA开发者.com的网站:Jetson Download Center | NVIDIA Developer)搜索kernel,并选择对应的版本(R35.1),如图2所示。点击下载跳转到对应的下载页面,如图3所示。

图2 Jetson Download Center页面
图3 Jetson Linux 35.1页面

现在NVIDIA产品套件的源码是打包在一起的,也就是图3中对应的红框部分${\color{red} \textbf{Driver Package (BSP) Sources}}$。这里我们也可以使用NVIDIA提供的交叉编译工具链aarch64-buildroot-linux-gnu-gcc 9.3(如图3中橙色线框,Bootlin Toolchain gcc 9.3)进行交叉编译。

下载图3中对应的红框部分Driver Package (BSP) Sources后得到public_sources.tbz2。在${\color{orange} \textbf{[DECOMPRESS_OUT] /Linux_for_Tegra/source/public/}}$文件夹内找到kernel_src.tbz2,这就是AGX所使用kernel内核源码文件。将内核源码解压到普通用户目录下的某个文件夹内,便于后续添加USB驱动代码,内核源码解压后的文件如图4所示。

图4 kernel内核源码解压文件目录

图4中显示的内核源码解压文件,后续我们主要会使用其中的${\color{green} \textbf{[KERNEL_SRC] /kernel/kernel-5.10/}}$ 这个文件夹及其内的源文件。

(2)4G模块EC200A驱动

Quectel EC200A_CN型号的4G模块只需用到USB接口,没有使用RJ45(8P8C)网络接口。于是仅需配置EC200A在Linux系统下的USB驱动即可,无需配置GobiNet驱动和QMI驱动。${\color{skyblue} \textbf{因为GobiNet驱动和QMI驱动是与网卡相关的驱动}}$。因此,这里我们主要移植4G模块在Linux系统下的USB驱动。

3. 移植USB驱动代码

参考Quectel官方提供的《Quectel LTE&5G Linux USB Driver User Guide V2.0.pdf》文件配置USB驱动。

(1)查询4G模块的VID和PID信息

将4G模块的USB口连接到AGX上,在AGX平台使用命令行 lsusb 查询当前设备中所有的USB信息,如下图5所示。从图5中,我们可以看到EC200A模块的 [VID:PID] 为 [2C7C:6005] 。

图5 查询4G模块USB信息

(2)添加USB驱动代码

A. 修改 [KERNEL_SRC] /kernel/kernel-5.10/drivers/usb/serial/option.c 文件

(i) 添加VID和PID

在option.c文件中的static const struct usb_device_id option_ids[] 中添加设备的VID和PID,如图所示6。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static const struct usb_device_id option_ids[] = {

/****************** Added by Helmholtz_optic *************************/

{ USB_DEVICE(0x2C7C, 0x6005) },/* Quectel EC200A: add VID and PID */

/******************************************************************************/
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COLT) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_LIGHT) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_QUAD) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_QUAD_LIGHT) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_LIGHT) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_QUAD) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_RICOLA_NDIS_QUAD_LIGHT) },
{ USB_DEVICE(OPTION_VENDOR_ID, OPTION_PRODUCT_COBRA) },

......

}
图6 添加VID和PID

(ii) 添加掉电恢复机制

在option.c文件中的static struct usb_serial_driver option_1port_device 中添加掉电恢复机制,如图7所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static struct usb_serial_driver option_1port_device = {
.driver = {
.owner = THIS_MODULE,
.name = "option1",
},
.description = "GSM modem (1-port)",
.id_table = option_ids,
.num_ports = 1,
.probe = option_probe,
.open = usb_wwan_open,
.close = usb_wwan_close,
.dtr_rts = usb_wwan_dtr_rts,
.write = usb_wwan_write,
.write_room = usb_wwan_write_room,
.chars_in_buffer = usb_wwan_chars_in_buffer,
.tiocmget = usb_wwan_tiocmget,
.tiocmset = usb_wwan_tiocmset,
.get_serial = usb_wwan_get_serial_info,
.set_serial = usb_wwan_set_serial_info,
.attach = option_attach,
.release = option_release,
.port_probe = usb_wwan_port_probe,
.port_remove = usb_wwan_port_remove,
.read_int_callback = option_instat_callback,
#ifdef CONFIG_PM
.suspend = usb_wwan_suspend,
.resume = usb_wwan_resume,

/****************** Added by Helmholtz_optic *************************/

.reset_resume = usb_wwan_resume, /*Quectel EC200A: add reset resume*/

/******************************************************************************/

#endif
};
图7 添加掉电恢复机制

B. 修改 [KERNEL_SRC] /kernel/kernel-5.10/drivers/usb/serial/usb_wwan.c 文件

(·) 添加零包机制

在usb_wwan.c文件中的static struct urb *usb_wwan_setup_urb() 中添加休眠唤醒机制,如图8所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static struct urb *usb_wwan_setup_urb(struct usb_serial_port *port,
int endpoint,
int dir, void *ctx, char *buf, int len,
void (*callback) (struct urb *))
{
struct usb_serial *serial = port->serial;
struct usb_wwan_intf_private *intfdata = usb_get_serial_data(serial);
struct urb *urb;

urb = usb_alloc_urb(0, GFP_KERNEL); /* No ISO */
if (!urb)
return NULL;

usb_fill_bulk_urb(urb, serial->dev,
usb_sndbulkpipe(serial->dev, endpoint) | dir,
buf, len, callback, ctx);

if (intfdata->use_zlp && dir == USB_DIR_OUT)
urb->transfer_flags |= URB_ZERO_PACKET;

/****************** Added by Helmholtz_optic *************************/

/*Quectel 2C7C modules: add the zero packet mechanism*/
if(dir == USB_DIR_OUT){
struct usb_device_descriptor *desc = &serial->dev->descriptor;
if(desc->idVendor == cpu_to_le16(0x2c7c))
urb->transfer_flags |= URB_ZERO_PACKET;
}

/******************************************************************************/

return urb;
}
图8 添加零包机制

C. 配置并编译内核

进入并在 [KERNEL_SRC] /kernel/kernel-5.10/ 目录下进行内核编译。

1
2
3
$mkdir ../../kernel_out
$TEGRA_KERNEL_OUT=../../kernel_out
$export CROSS_COMPILE=aarch64-linux-gnu-

[KERNEL_SRC] / 目录下建立了名为 kernel_out 的编译输出文件夹,并导入编译工具链。

接着配置编译选项,如下:

1
$sudo make ARCH=arm64 O=$TEGRA_KERNEL_OUT menuconfig

在弹出的menuconfig中配置编译输出选项,这里推荐将USB驱动编译成模块(m),便于移植和加载。

1
2
3
4
[ ] Device Drivers →
[*] USB Support →
[M] USB Serial Converter support →
[M] USB driver for GSM and CDMA modems

需要注意的是,按照Quectel官方提供的《Quectel LTE&5G Linux USB Driver User Guide V2.0.pdf》中的menuconfig设置后,无法生成独立的.ko文件。它的做法是将驱动模块文件编译进Linux内核中(y),而不是模块化(m)编译生成独立的驱动模块文件(.ko文件)。

最后就可以编译内核了,这里使用的AGX的CPU核心数为8。

1
$sudo make ARCH=arm64 O=$TEGRA_KERNEL_OUT -j8

成功编译完成后,有如下输出显示,如图9所示。

图9 成功编译输出

D. 移植驱动文件(.ko文件)

在编译输出的kernel_out文件夹中,${\color{orange} \textbf{[kernel_out] /drivers/usb/serial/}}$ 文件夹中找到生成的option.ko,usb_wwan.ko 和 qcserial.ko 文件,并将这些文件复制到AGX的驱动文件夹中。

1
2
3
$sudo cp option.ko /usr/lib/modules/5.10.104-tegra/kernel/drivers/usb/serial
$sudo cp usb_wwan.ko /usr/lib/modules/5.10.104-tegra/kernel/drivers/usb/serial
$sudo cp qcserial.ko /usr/lib/modules/5.10.104-tegra/kernel/drivers/usb/serial

完成后,加载.ko驱动模块并重启。

1
2
$sudo depmod -a
$sudo reboot

4. 驱动测试

(1)驱动查看

重启后,接入4G模块并查看查看驱动加载情况。

1
2
3
$sudo modprobe usbmon    // 加载usbmon驱动模块
$sudo dmesg
$lsmod
图10 dmesg查看USB驱动情况
图11 lsmod查看驱动情况

如果编译无误,则会出现如图10和11所示的情况。如果没有,则需要考虑重新编译。

(2)串口测试

接着我们使用串口调试工具minicom连接ttyUSB1,尝试对4G模块发送AT指令,测试结果如图12所示。

1
$sudo minicom -c on
图12 串口测试结果

图12中,可以看到AGX检测到4G模块并且成功识别了中国移动SIM卡。

(3)实物测试

将AGX连接显示屏后,发现在连接4G模块后会多出一个移动网络的标识符,如图13左所示。使用ping命令发现也可以连通,驱动移植成功。

移动网络标识符 ping命令结果
图13 移动网络标识符(左)和ping命令结果(右)

参考文献

[1] 移植EC20F 4G模块驱动基于Jetson-xavier-CSDN博客.

[2] 基于Xavier 移植移远EG25G 4G模块-CSDN博客.

[3] 移远EC20 4G模块Linux驱动移植和测试_ec25 4g模块驱动-CSDN博客.

[4] 4G模块 EC20 R2.0 USB Serial/GobiNet/QMI WWAN 驱动移植过程_qmi_wwan-CSDN博客.