- Linux那些事儿之我是USB
- 肖林甫 肖季东 任桥伟
- 2254字
- 2020-08-27 00:52:23
20.设备的生命线(一)
设备也有它自己的生命线,当你把它插到Hub上开始,到你把它从Hub上拔下来结束。
当然你将USB设备连接在Hub的某个端口上,Hub检测到有设备连接了进来,它会为设备分配一个struct usb_device结构的对象并初始化,调用设备模型提供的接口将设备添加到USB总线的设备列表里,然后USB总线会遍历驱动列表里的每个驱动,调用自己的match函数看它们和你的设备或接口是否匹配。又走到match函数了。
Hub检测到自己的某个端口有设备连接了进来后,它会调用core里的usb_alloc_dev函数为struct usb_device结构的对象申请内存,这个函数在drivers/usb/core/usb.c文件中定义。
238 struct usb_device * 239 usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus, unsigned port1) 240 { 241 struct usb_device *dev; 242 243 dev = kzalloc(sizeof(*dev), GFP_KERNEL); 244 if (!dev) 245 return NULL; 246 247 if (!usb_get_hcd(bus_to_hcd(bus))) { 248 kfree(dev); 249 return NULL; 250 } 251 252 device_initialize(&dev->dev); 253 dev->dev.bus = &usb_bus_type; 254 dev->dev.type = &usb_device_type; 255 dev->dev.dma_mask = bus->controller->dma_mask; 256 dev->state = USB_STATE_ATTACHED; 257 258 INIT_LIST_HEAD(&dev->ep0.urb_list); 259 dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE; 260 dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT; 261 /* ep0 maxpacket comes later, from device descriptor */ 262 dev->ep_in[0] = dev->ep_out[0] = &dev->ep0; 263 264 /* Save readable and stable topology id, distinguishing devices 265 * by location for diagnostics, tools, driver model, etc. The 266 * string is a path along hub ports, from the root. Each device's 267 * dev->devpath will be stable until USB is re-cabled, and hubs 268 * are often labeled with these port numbers. The bus_id isn't 269 * as stable: bus->busnum changes easily from modprobe order, 270 * cardbus or pci hotplugging, and so on. 271 */ 272 if (unlikely(!parent)) { 273 dev->devpath[0] = ''; 274 275 dev->dev.parent = bus->controller; 276 sprintf(&dev->dev.bus_id[0], "usb%d", bus->busnum); 277 } else { 278 /* match any labeling on the hubs; it's one-based */ 279 if (parent->devpath[0] == '') 280 snprintf(dev->devpath, sizeof dev->devpath, 281 "%d", port1); 282 else 283 snprintf(dev->devpath, sizeof dev->devpath, 284 "%s.%d", parent->devpath, port1); 285 286 dev->dev.parent = &parent->dev; 287 sprintf(&dev->dev.bus_id[0], "%d-%s", 288 bus->busnum, dev->devpath); 289 290 /* hub driver sets up TT records */ 291 } 292 293 dev->portnum = port1; 294 dev->bus = bus; 295 dev->parent = parent; 296 INIT_LIST_HEAD(&dev->filelist); 297 298 #ifdef CONFIG_PM 299 mutex_init(&dev->pm_mutex); 300 INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work); 301 dev->autosuspend_delay = usb_autosuspend_delay * HZ; 302 #endif 303 return dev; 304 }
usb_alloc_dev函数就相当于USB设备的构造函数,在参数中,parent是设备连接的Hub,bus是设备连接的总线,port1就是设备连接在Hub上的端口。
243行,为一个struct usb_device结构的对象申请内存并初始化为0。直到在看到这一行前,我还仍在使用kmalloc加me mset这对最佳拍档来申请内存和初始化,但是在看到kzalloc之后,我知道了kzalloc直接取代了kmalloc/me mset,一个函数起到了两个函数的作用。
然后判断内存申请是否成功,如果不成功就不用往下走了。那么通过这几行,我们应该记住,凡是你想用kmalloc/me mset组合申请内存时,就使用kzalloc代替吧;凡是申请内存的,不要忘了判断是否申请成功了。
247行,这里的两个函数是hcd,是主机控制器驱动里的。要知道USB的世界里一个主机控制器对应着一条USB总线,主机控制器驱动用struct usb_hcd结构表示,一条总线用struct usb_bus结构表示,函数bus_to_hcd是为了获得总线对应的主机控制器驱动,也就是struct usb_hcd结构对象,函数usb_get_hcd只是将得到的usb_hcd结构对象的引用计数加1。为什么?因为总线上多了一个设备,当然得为它增加引用计数。如果这两个函数没有很好地完成自己的任务,那整个usb_alloc_dev函数也就没有必要继续执行下去了,将之前为struct usb_device结构对象申请的内存释放掉就可以了。
252行,device_initialize是设备模型中的函数,第一个dev是struct usb_device结构体指针,而第二个dev是struct device结构体,这是设备模型中一个最基本的结构体,使用它必然要先初始化。device_initialize函数的目的就是将struct usb_device结构中嵌入的struct device结构体初始化掉,方便以后调用。
253行,将设备所在的总线类型设置为usb_bus_type。usb_bus_type在前面见过了,USB子系统的初始化函数usb_init里就把它给注册掉了。
254行,将设备的设备类型初始化为usb_device_type,这是在上节第二次遇到usb_device_match函数,走设备那条路时,使用is_usb_device判断是不是USB设备时留下的疑问,就是在这儿把设备的类型给初始化成usb_device_type了。
255行,就是与DMA传输相关的了,设备能不能进行DMA传输,得看主机控制器的脸色,主机控制器不支持设备也没法使用。所以这里dma_mask被设置为主机控制器的dma_mask。
256行,将USB设备的状态设置为ATTACHED,表示设备已经连接到USB接口上了,是Hub检测到设备时的初始状态。
258行,端点0实在是太特殊了,struct usb_device里直接就有一个ep0成员,这行就将ep0的urb_list给初始化掉了。
259行,260行,分别初始化了端点0的描述符长度和描述符类型。
260行,使struct usb_device结构中的ep_in和ep_out指针数组的第一个成员指向ep0,ep_in[0]和ep_out[0]本来表示的就是端点0。
272行,这里平白无故地多出了一个unlikely,不知道什么意思?先查看它们在include/linux/compiler.h的定义。
60 #define likely(x) __builtin_expect(!!(x), 1) 61 #define unlikely(x) __builtin_expect(!!(x), 0)
除了函数unlikely,还有一个函数likely。定义里__builtin_expect是GCC里内建的一个函数,具体是做什么用的可以看一看GCC的手册。
long __builtin_expect (long exp, long c) You may use __builtin_expect to provide the compiler with branch prediction information. In general, you should prefer to use actual profile feedback for this (‘-fprofile-arcs’), as programmers are notoriously bad at predicting how their progra ms actually perform. However, there are applications in which this data is hard to collect. The return value is the value of exp, which should be an integral expression. The value of c must be a compile-time constant. The semantics of the built-in are that it is expected that exp == c. For example: if (__builtin_expect (x, 0)) foo (); would indicate that we do not expect to call foo, since we expect x to be zero. Since you are limited to integral expressions for exp, you should use constructions such as if (__builtin_expect (ptr != NULL, 1)) error (); when testing pointer or floating-point values.
其大致意思就是由于大部分写代码的人在分支预测方面做得比较糟糕,所以GCC提供了这个内建的函数来帮助处理分支预测,优化程序,它的第一个参数exp为一个整型的表达式,返回值也是这个exp,它的第二个参数c的值必须是一个编译器的常量,那这个内建函数的意思就是exp的预期值为c,编译器可以根据这个信息适当地重排条件语句块的顺序,将符合这个条件的分支放在合适的地方。
具体到unlikely(x)就是告诉编译器条件x发生的可能性不大,那么这个条件块里语句的目标码可能就会被放在一个比较远的位置,以保证经常执行的目标码更紧凑。而likely则相反。
这个函数使用时还是很简单的,直接使用if语句,只是如果你觉得if条件为1的可能性非常大时,可以在条件表达式外面包装一个likely(),如果可能性非常小,则用unlikely()包装。那么这里272行的意思就很明显了,就是说写内核的人觉得你的USB设备直接连接到root hub上的可能性比较小,因为parent指的就是你的设备连接的那个Hub。
272行到291行整个的代码就是首先判断你的设备是不是直接连到Root Hub上的,如果是,将dev->devpath[0]赋值为0,以示特殊,然后父设备设为controller,同时把dev->bus_id[]设置为如usb1/usb2/usb3/usb4这样的字符串。如果你的设备不是直接连到Root Hub上的,分两种情况:如果你的设备连接的Hub是直接连到Root Hub上的,则dev->devpath就等于端口号,否则dev->devpath就等于在父Hub的devpath基础上加一个“”再加一个端口号,最后把bus_id[]设置成1-/2-/3-/4-这样的字符串后面连接上devpath。
296行,初始化一个队列。
298行到302行,用于电源管理。