某设备需要提供多路USB串口的功能给主机端使用,比如一路用作业务1通信功能,一路用作业务2通信功能,一路用作debug抓log用途,诸如此类。如下图所示。
要实现上述设备功能,可以参考如下步骤。
1)首先,了解一下背景知识。Linux kernel为设备端USB驱动提供了名为USB Gadget的驱动框架,设备端要基于Linux系统实现USB device功能,都需要基于Gadget框架。各种USB class定义的功能,在设备端的实现,称之为USB function。常见的USB function,比如 serial, ecm, storage, video, audio等,kernel原生代码都已经实现了。产品开发的大部分工作是放在理解并使用这些代码,并调试可能出现的bug;以及针对某些usb controller的特性,需要在function driver层面处理的时候,打一些补丁,当然这种补丁是很难合入kernel社区的,只能是在自家的产品上用用。
2)其次,了解一下gadget驱动代码目录结构。如下图所示。
gadget驱动包含三大部分:
-
function驱动 —— 实现各种usb class功能
-
udc驱动 —— 实现usb controller driver
-
辅助驱动 —— configfs.c实现用户空间配置usb, composite.c实现复合设备
进入function目录,可以看到各种已经实现的function,接下来我们要用到的serial function也在其中。
3)了解具体如何开启usb串口的功能。其实很简单,要开启usb serial function driver,在kernel config中开启以下CONFIG即可:
CONFIG_USB_GADGET=y
CONFIG_USB_U_SERIAL=y
CONFIG_USB_F_SERIAL=y
开启以上CONFIG后,只是打开了usb driver支持serial的能力;要生成多路串口,还需要通过configfs动态配置相关功能,以下就是生成三路USB generic serial串口的示例:
mkdir -p /sys/kernel/config/usb_gadget/g1/functions/gser.gs0 chmod 755 /sys/kernel/config/usb_gadget/g1/functions/gser.gs0 mkdir -p /sys/kernel/config/usb_gadget/g1/functions/gser.gs1 chmod 755 /sys/kernel/config/usb_gadget/g1/functions/gser.gs1 mkdir -p /sys/kernel/config/usb_gadget/g1/functions/gser.gs2 chmod 755 /sys/kernel/config/usb_gadget/g1/functions/gser.gs2 ln -s /sys/kernel/config/usb_gadget/g1/functions/gser.gs2 /sys/kernel/config/usb_gadget/g1/configs/b.1/f1 ln -s /sys/kernel/config/usb_gadget/g1/functions/gser.gs0 /sys/kernel/config/usb_gadget/g1/configs/b.1/f2 ln -s /sys/kernel/config/usb_gadget/g1/functions/gser.gs1 /sys/kernel/config/usb_gadget/g1/configs/b.1/f3
configfs本身的介绍,可参考kernel文档:
Documentation\filesystems\configfs\configfs.txt
USB gadget configfs的使用介绍,可以参考kernel文档:
Documentation\ABI\testing\configfs-usb-gadget
Documentation\ABI\testing\configfs-usb-gadget-serial
三路USB串口启用成功后,在设备端会生成三个ttyGS设备:
-
/dev/ttyGS0
-
/dev/ttyGS1
-
/dev/ttyGS2
4)主机端看到的情况
主机端识别USB串口和加载相关驱动的方法,可以参考我的另一篇文章
这里主要讲一讲主机端生成了多个名为ttyUSBx(x=0~n)的设备,我们如何确定它们与设备端多路USB串口(ttyGSx)的对应关系?
方法之一,当然可以通过遍历测试串口通信的方式来找到对应关系。比如主机端用串口工具或者echo指令发送数据,设备端用串口工具或者cat指令接收数据,一个一个遍历尝试,能正常通信的,就说明两边是对应的。
方法之二,通过主机端和设备端的USB interface number (接口号)找到对应关系。以Ubuntu主机为例:
在Ubuntu端执行 ls -l /sys/class/tty/ttyUSB*,可以看到ttyUSBx和USB接口号的对应关系。比如ttyUSB1对应3-6:1.2,这个末尾的数字2就表示接口2(简称f2)。
user@PC1002:~$ ls -l /sys/class/tty/ttyUSB* lrwxrwxrwx 1 root root 0 5月 21 13:15 /sys/class/tty/ttyUSB0 -> ../../devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6:1.1/ttyUSB0/tty/ttyUSB0 lrwxrwxrwx 1 root root 0 5月 21 13:15 /sys/class/tty/ttyUSB1 -> ../../devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6:1.2/ttyUSB1/tty/ttyUSB1 lrwxrwxrwx 1 root root 0 5月 21 13:15 /sys/class/tty/ttyUSB2 -> ../../devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6:1.3/ttyUSB2/tty/ttyUSB2
类似的,在设备端也可以获得ttyGSx与接口号的对应关系。进入设备端shell,执行如下指令。
ls -l /sys/kernel/config/usb_gadget/g1/configs/b.1/ -rw-r--r-- 1 root root 4096 May 4 10:40 MaxPower -rw-r--r-- 1 root root 4096 May 4 10:40 bmAttributes lrwxrwxrwx 1 root root 0 May 4 10:40 f1 -> ../../../../usb_gadget/g1/functions/gser.gs2 lrwxrwxrwx 1 root root 0 May 4 10:40 f2 -> ../../../../usb_gadget/g1/functions/gser.gs0 lrwxrwxrwx 1 root root 0 May 4 10:40 f3 -> ../../../../usb_gadget/g1/functions/gser.gs1
可以看到f2对应gser.gs0,表示gser.gs0对应接口2。那么gser.gs0是不是一定就与ttyGS0对应呢?先说答案,不一定。准确的做法是读取gser.gs0目录下的port_num的值来获知是ttyGS几。比如port_num是0,那就说明gser.gs0对应/dev/ttyGS0,如果port_num是1,那就说明gser.gs0对应/dev/ttyGS1。
cat /sys/kernel/config/usb_gadget/g1/functions/gser.gs0/port_num 0
这里假定gser.gs0对应/dev/ttyGS0,那么到此就可以得知主机和设备的对应关系:ttyUSB1对应ttyGS0。如果设备端的业务2代码是通过读写/dev/ttyGS0来实现通信,那么主机端的业务2代码就需要通过读写/dev/ttyUSB1来实现和设备端业务2的交互。
至于port_num编号的背后规律,与每个gser.gsN这个末尾数字N无关;与我们前面编写configfs配置USB的指令时,mkdir指令执行的次序有关,port_num从0开始,依次+1。从我们前面的编写的指令看,先mkdir gser.gs0,再mkdir gser.gs1,那么gser.gs0的port_num是0,gser.gs1的port_num就是1。如果先mkdir gser.gs1,再mkdir gser.gs0,那么就会反过来,gser.gs1的port_num是0,gser.gs0的port_num是1。
5)USB gadget serial function的驱动实现细节,不是本文的重点,暂且不讲,后续会专门介绍USB gadget function driver。
以上是本文全部内容,谢谢阅读,希望能帮到你。
文章会在公众号“大鱼嵌入式”同步发布,欢迎关注,一起交流。