ttyUSBn设备节点名不固定问题

ttyUSBn用于表示usb转串口驱动的设备节点,对应着一块基于usb转串口芯片做的硬件,像ch341。为和后面会说的usb_serial驱动作区分,它们称为option驱动。名称中的n为0~511,这512(USB_SERIAL_TTY_MINORS)个设备节点的主设备号是188(USB_SERIAL_TTY_MAJOR),次设备号就是n。至于每次插入设备时,linux内核会根据插入设备的先后顺序分配编号n,比如第一个插入的设备编号为0,即设备节点名ttyUSB0,后面插入的是ttyUSB1。

如果以设备节点ttyUSBn来区分具体是哪个设备,因为编号n是随时会变的,像重启了设备、多个设备不知道次序地拔出、插入,这无疑会造成混乱。总之,linux不会保证设备A一直就是ttyUSB0,设备B一直就是ttyUSB1。那app如何知道我要的设备A是哪个ttyUSBn?——设备插入usb口,每个usb口有一个唯一路径,这个路径不会随着设备重启或多个设备不知道次序地拔出、插入而发生改变。为此,app要知道A用的是哪个ttyUSBn,分为两个步骤。

  1. 记住设备A插在的usb口路径。
  2. 根据usb口路径算出是哪个ttyUSBn。

要解决这两个问题,可通过解析文件:/proc/tty/driver/usbserial

 

一、/proc/tty/driver/usbserial

usbserinfo:1.0 driver:2.0
0: name:"ch341-uart" vendor:1a86 product:7523 num_ports:1 port:0 path:usb-fe3c0000.usb-1.4
1: name:"cp210x" vendor:10c4 product:ea60 num_ports:1 port:0 path:usb-fe3c0000.usb-1.7

以上是usbserial文件的一次实例,它表示android正插着两个ttyUSB设备。这文件是由管理众多option驱动的use_serial驱动生成,因为是proc虚拟文件,具体来自usb_serial驱动的serial_proc_show函数。

<aosp>/kernel/drivers/usb/serial/usb-serial.c
---
static int serial_proc_show(struct seq_file *m, void *v)
{
	struct usb_serial *serial;
	struct usb_serial_port *port;
	int i;
	char tmp[40];

	seq_puts(m, "usbserinfo:1.0 driver:2.0\n");
	for (i = 0; i < USB_SERIAL_TTY_MINORS; ++i) {
		port = usb_serial_port_get_by_minor(i);
		if (port == NULL)
			continue;
		serial = port->serial;

		seq_printf(m, "%d:", i);
		if (serial->type->driver.owner)
			seq_printf(m, " module:%s",
				module_name(serial->type->driver.owner));
		seq_printf(m, " name:\"%s\"",
				serial->type->description);
		seq_printf(m, " vendor:%04x product:%04x",
			le16_to_cpu(serial->dev->descriptor.idVendor),
			le16_to_cpu(serial->dev->descriptor.idProduct));
		seq_printf(m, " num_ports:%d", serial->num_ports);
		seq_printf(m, " port:%d", port->port_number);
		usb_make_path(serial->dev, tmp, sizeof(tmp));
		seq_printf(m, " path:%s", tmp);

		seq_putc(m, '\n');
		usb_serial_put(serial);
		mutex_unlock(&serial->disc_mutex);
	}
	return 0;
}

第一列是该ttyUSB设备对应的编号n,最后一列便是该ttyUSB设备插在的USB口的路径。有了这文件,便可解决android中ttyUSBn设备节点名不固定问题了。

  • VID_1a86&PID_7523设备,它插在“usb-fe3c0000.usb-1.4”,目前正在用的设备节点是/dev/ttyUSB0。
  • VID_10c4&PID_ea60设备,它插在“usb-fe3c0000.usb-1.7”,目前正在用的设备节点是/dev/ttyUSB1。

重启andorid设备后,/proc/tty/driver/usbserial也许会变成这样了。

0: name:"cp210x" vendor:10c4 product:ea60 num_ports:1 port:0 path:usb-fe3c0000.usb-1.7
1: name:"ch341-uart" vendor:1a86 product:7523 num_ports:1 port:0 path:usb-fe3c0000.usb-1.4

虽然VID_1a86&PID_7523设备变成了设备节点/dev/ttyUSB0,但对应的path不会变。为此只要知道path,由path解析usbserial,便可找到对应的ttyUSBn。

这里有个问题,一个只拥有普通权限的android app如何读取usbserial文件?——如果android没被root过,root指通过“su”可以提升到管理员权限,我认为没办法。root过的,则可通过popen。

{
  const char* cmd = "su -c \"cat /proc/tty/driver/usbserial\"";
  FILE* fp = popen(cmd, "r");
  if (fp == NULL) {
    return 0;
  }
  // 调用fread或其它api读fp便可得到内容。
  pclose(fp);
}

注意,不要分开两个popen:首先popen("su"),然后popen("cat /proc/tty/driver/usbserial")。要是这么做了,第二个popen或许返回fp!=NULL,但由这个fp会读不到内容。

 

二、usb_serial驱动

上面这种解决方法依赖usbserial文件格式,当然,将来变动格式可能性应该微乎其微。但对有android源码的开发者,有没有更好办法。这或许可从usb_serial驱动入手。

usb转串口驱动的驱动个数是“1+N”,N指的是对应某个ttyUSB设备的option驱动,1则是管理这N个option驱动的usb_serial驱动。因此usb_serial有着这N个option驱动的信息,也正是这原因,要由它生成usbserial文件。

但是,usb_serial驱动没有向/dev目录生成设备节点,这也意味着app无法直接访问这个驱动。

如果让我实现的话,或许会用这种方法:写一个驱动,假设驱动名叫hook。app打开hook,因为hook在内核态,app某个操作让hook去调用usb_serial_port_get_by_minor(i),然后app便可得到类似usbserial文件内容的struct结构。

更多理解usb_serial驱动可参考“Linux的USB-Serial驱动(从系统初始化到生成tty设备的全过程)”、“ttyUSB串口设备节点生成过程”。

全部评论: 0

    写评论: