- Linux那些事儿之我是USB
- 肖林甫 肖季东 任桥伟
- 3276字
- 2020-08-27 00:52:23
14.接口是设备的接口
设备可以有多个接口,每个接口代表一个功能,每个接口对应着一个驱动。Linux设备模型中的device落实在USB子系统,成了两个结构:一个是struct usb_device,一个是struct usb_interface。一个USB键盘,上面带一个扬声器,因此有两个接口,那肯定得要两个驱动程序:一个是键盘驱动程序,一个是音频流驱动程序。“道”上的兄弟喜欢把这样两个整合在一起的东西叫做一个设备,那好,让他们去叫吧,我们用interface来区分这两者。于是有了这里提到的数据结构,struct usb_interface:
140 struct usb_interface { 141 /* array of alternate settings for this interface, 142 * stored in no particular order */ 143 struct usb_host_interface *altsetting; 144 145 struct usb_host_interface *cur_altsetting; /* the currently 146 * active alternate setting */ 147 unsigned num_altsetting; /* number of alternate settings */ 148 149 int minor; /* minor number this interface is 150 * bound to */ 151 enum usb_interface_condition condition; /* state of binding */ 152 unsigned is_active:1; /* the interface is not suspended */ 153 unsigned needs_remote_wakeup:1;/*driver requires remote wakeup */ 154 155 struct device dev; /* interface specific device info */ 156 struct device *usb_dev; /* pointer to the usb class's device, if any*/ 157 int pm_usage_cnt; /* usage counter for autosuspend */ 158 };
143行,这里有一个altsetting成员,它的意思就是alternate setting,可选的设置。那么知道145行的cur_altsetting表示当前正在使用的设置,147行的num_altsetting表示这个接口具有可选设置的数量。前面提到过USB设备的配置,那这里的设置是什么意思?
在英语中,配置是configuration,设置是setting。
先说配置,一部手机可以有多种配置,比如可以摄像,还可以连接在计算机里当做一个U盘,那么这两种情况就属于不同的配置,在手机里面有相应的选择菜单,你选择了哪种它就按哪种配置进行工作,供你选择的这个选项就叫做配置。很显然,当你摄像时你不可以把手机当做U盘进行访问,当你把手机当做U盘进行访问时你不可以摄像。第二,既然一个配置代表一种不同的功能,那么很显然,不同的配置可能需要的接口就不一样,假设你的手机从硬件上来说一共有5个接口,那么可能当你配置成U盘时它只需要用到某一个接口;当你配置成摄像时,它可能只需要用到另外两个接口;也许你还有别的配置,可能就会用到剩下的那两个接口。
再说一说设置,一部手机可能各种配置都确定了,是振动还是铃声已经确定了,各种功能也都确定了,但是声音的大小还可以变吧,通常手机的音量是一格一格地变动,大概也就5格至6格,那么这个可以算一个设置。
如果你还是不明白什么是配置什么是设置的话,那就直接用它们大小关系来理解好了,毕竟大家对互相之间的大小关系都更敏感一些,不要说不是。这么说吧,设备大于配置,配置大于接口,接口大于设置。更准确地说是设备可以有多个配置,配置里可以包含一个或更多的接口,而接口通常又具有一个或更多的设置。
149行,minor,分配给接口的次设备号。Linux下所有的硬件设备都是用文件来表示的,俗称“设备文件”,在/dev目录下面,为了显示自己并不是普通的文件,它们都会有一个主设备号和次设备号,如下所示:
brw-r----- 1 root disk 8, 0 Sep 26 09:17 /dev/sda brw-r----- 1 root disk 8, 1 Sep 26 09:17 /dev/sda1 crw-r----- 1 root tty 4, 1 Sep 26 09:17 /dev/tty1
这是在/dev目录下执行ls命令后的部分显示结果。我们可以看到在每一行的日期前面有两个逗号隔开的数字,对于普通文件而言,这个位置显示的是文件的长度。而对于设备文件,这里显示的两个数字表示了该设备的主设备号和次设备号。一般来说,主设备号表明了设备的种类,也表明了设备对应着哪个驱动程序,而次设备号则是因为一个驱动程序要支持多个设备而为了让驱动程序区分它们而设置的。也就是说,主设备号用来帮你找到对应的驱动程序,次设备号决定你的驱动对哪个设备进行操作。在上面代码中就显示了我的移动硬盘主设备号为8,系统里tty设备的主设备号为4。
设备要想在Linux里分得一个主设备号,有一个立足之地,也并不是那么容易的。主设备号虽说不是什么特别稀缺的资源,但还是需要设备先在驱动里提出申请,获得系统的批准后才能拥有一个。因为一部分的主设备号已经被静态地预先指定给了许多常见的设备,申请时要避开它们。这些已经被分配掉的主设备号都列在Documentation/devices.txt文件中。当然,如果你是用动态分配的形式,就可以不去理会这些,直接让系统为你做主,替你选择一个即可。
很显然,USB设备是很常见的,Linux理应为它预留了一个主设备号。下面看一看include/linux/usb.h文件。
7 #define USB_MAJOR 180 8 #define USB_DEVICE_MAJOR 189
苏格拉底说过,学得越多,知道得越多;知道得越多,发现需要知道得更多。当我们知道了主设备号,满怀激情地寻找USB的主设备号时,我们却发现这里在上演“真假李逵”。这两个设备号哪个才是我们苦苦追寻的“真李逵”?
你可以在内核中搜索它们都曾经出现什么地方,或者就跟随我回到usb_init函数。
880 retval = usb_major_init(); 881 if (retval) 882 goto major_init_failed; 883 retval = usb_register(&usbfs_driver); 884 if (retval) 885 goto driver_register_failed; 886 retval = usb_devio_init(); 887 if (retval) 888 goto usb_devio_init_failed; 889 retval = usbfs_init(); 890 if (retval) 891 goto fs_init_failed;
前面只提了一句883行到891行是与usbfs相关的代码就简单略过了,这里稍微多说一点。usbfs提供了在用户空间直接访问USB硬件设备的接口,但是世界上没有免费的午餐,它需要内核的大力支持,usbfs_driver就是用来完成这个光荣任务的。我们可以去usb_devio_init函数中看一看,它在drivers/usb/devio.c文件中定义:
retval = register_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX, "usb_device"); if (retval) { err("unable to register minors for usb_device"); goto out; }
register_chrdev_region函数获得了设备usb_device对应的设备编号,设备usb_device对应的驱动当然就是usbfs_driver,参数USB_DEVICE_DEV也在同一个文件中有定义。
#define USB_DEVICE_DEV MKDEV(USB_DEVICE_MAJOR, 0)
终于再次见到了USB_DEVICE_MAJOR,也终于明白它是为了usbfs而生的,为了让广大人民群众能够在用户空间直接和USB设备通信而生的。因此,它并不是我们所要寻找的。
那么答案很明显了,USB_MAJOR就是我们苦苦追寻的那个“她”,就是Linux为USB设备预留的主设备号。事实上,前面usb_init函数的880行,usb_major_init函数已经使用USB_MAJOR注册了一个字符设备,名字就叫USB。我们可以在文件/proc/devices里看到它们。
localhost:/usr/src/linux/drivers/usb/core # cat /proc/devices Character devices: 1 mem 2 pty 3 ttyp 4 /dev/vc/0 4 tty 4 ttyS 5 /dev/tty 5 /dev/console 5 /dev/ptmx 7 vcs 10 misc 13 input 14 sound 29 fb 116 alsa 128 ptm 136 pts 162 raw 180 usb 189 usb_device
/proc/devices文件中显示了所有当前系统里已经分配出去的主设备号,当然上面只是列出了字符设备,块设备被有意地略过了。很明显,前面提到的usb_device和usb都在里面。
不过到这里还没讲完,USB设备有很多种,并不是都会用到这个预留的主设备号。比如我的移动硬盘显示出来的主设备号就是8,你的摄像头在Linux显示的主设备号也绝对不会是这里的USB_MAJOR。坦白地说,大多数USB设备都会与input、video等子系统关联,并不单单只是作为USB设备而存在。如果USB设备没有与其他任何子系统关联,就需要在对应驱动的probe函数中使用usb_register_dev函数来注册并获得主设备号USB_MAJOR,你可以在drivers/usb/misc目录下看到一些例子,drivers/usb/usb-skeleton.c文件也属于这种。如果USB设备关联了其他子系统,则需要在对应驱动程序的probe函数中使用相应的注册函数,USB_MAJOR也就用不着了。比如,USB键盘关联了input子系统,驱动对应drivers/hid/usbhid目录下的usbkbd.c文件,在它的probe函数中可以看到使用了input_register_device来注册一个输入设备。
准确地说,这里的USB设备应该说成USB接口,毕竟一个USB接口才对应着一个USB驱动。当USB接口关联有其他子系统,也就是说不使用USB_MAJOR作为主设备号时,struct usb_interface的字段minor可以忽略。minor只在USB_MAJOR起作用时才起作用。
说完了设备号,回到struct usb_interface的151行,condition字段表示接口和驱动的绑定状态,enum usb_interface_condition类型在include/linux/usb.h中定义:
83 enum usb_interface_condition { 84 USB_INTERFACE_UNBOUND = 0, 85 USB_INTERFACE_BINDING, 86 USB_INTERFACE_BOUND, 87 USB_INTERFACE_UNBINDING, 88 };
前面介绍Linux设备模型时说了,设备和驱动是相生相依的关系,总线上的每个设备和驱动都在等待着命中的那个“她”,找到了,就会“执子之手,与子偕老”;找不到,只能“孤苦伶仃”一个人了。enum usb_interface_condition形象地描绘了这个过程中接口的各种心情,孤苦、期待、幸福、分开,人生又何尝不是如此?
152行、153行与157行都是关于挂起和唤醒的代码。协议中规定,所有USB设备都必须支持挂起状态,就是说为了达到节电的目的,当设备在指定的时间内(大约3ms),如果没有发生总线传输,就要进入挂起状态。当它收到一个non-idle的信号时,就会被唤醒。
152行,is_active表示接口是否处于挂起状态。153行,needs_remote_wakeup表示是否需要打开远程唤醒功能。远程唤醒功能允许挂起的设备给主机发信号,通知主机它将从挂起状态恢复,注意如果主机处于挂起状态,就会唤醒主机,不然主机仍然在休眠。协议中并没有要求USB设备一定要实现远程唤醒的功能,即使实现了,从主机这边也可以打开或关闭它。
157行中的pm_usage_cnt,pm就是电源管理;usage_cnt就是使用计数,当它为0时,接口允许autosuspend。什么是autosuspend?用过笔记本电脑吧,有时合上笔记本电脑后,它会自动进入休眠,这就叫autosuspend。但不是每次都是这样的,就像这里只有当pm_usage_cnt为0时才会允许接口autosuspend。
接下来就剩下155行的struct device dev和156行的struct device *usb_dev,看到struct device,或许就会认为它们是Linux设备模型中的device嵌套在这里的对象。不过这么想是不准确的,这两个成员里面只有dev才是模型中的device嵌套在这里的,usb_dev则不是。当接口使用USB_MAJOR作为主设备号时,usb_dev才会用到。找遍整个内核,也只在usb_register_dev和usb_deregister_dev两个函数中能够看到它,usb_dev指向的就是usb_register_dev函数中创建的USB class device。