📄 drivers.html
字号:
的设备接口,如果返回NULL,则这个驱动并不适用新的设备。</PRE><PRE> 调用参数结构usb_device代表一个USB硬件,同样定义于usb.h,在此不列出。 这个结构体保存了一些USB规范上的信息,和一些链表用于把usb_device链 成树型,其中非叶子节点是一种特殊的USB设备:HUB。因为USB是可以热拔 插的,所以软件也必须做足够的健壮性处理。 </PRE><PRE> 结构体usb_device_id为热拔插策略的部署提供信息,定义于usb.h。第一个 成员为匹配规则,后面的成员为idVendor/idProduct等。通常一个驱动可以 带一堆同类的设备,所以通常实现出这个结构体的数组,并且数组的最后一 个元素要为零,作为停止匹配的标记。usb.h中同时定义了用于构筑id条目 的宏,使用很方便。</PRE><PRE> 函数指针void (*disconnect)(struct usb_device *, void *)实现和probe 正好相反的功能,在设备被拔走时调用。</PRE><PRE> MN的usb_driver定义在usb-mininurse.h的L102-109。USB CORE提供了注册 和注销函数: extern int usb_register(struct usb_driver *); extern void usb_deregister(struct usb_driver *); usb-mininurse.c在usb_mn_init和usb_mn_exit中分别调用这两个函数,也 就是说每当模块被加载时就向USB CORE注册MN驱动,模块被移除时向USB CORE注销MN驱动。</PRE><PRE> 至此已经为全面分析MN驱动做了足够准备。下面来看看MN是如何实现那三个 系统调用以及两个为USB新增的函数。</PRE><PRE> 通常probe总是第一个被调用,所以也先讨论,它位于usb-mininurse.c的 L134-201。它的参数刚才介绍过了。</PRE><PRE> L142-145 if ((udev->descriptor.idVendor != USB_MN_VENDOR_ID) || (udev->descriptor.idProduct != USB_MN_PRODUCT_ID)) { return NULL; }</PRE><PRE> 这段代码用于检测新设备的各种ID值是否适用于这个驱动。 USB_MNVENDOR_ID和USB_MN_PRODUCT_ID是在usb_mininurse.h定义的宏,其 值分别为0x0471(这是Philips在USB-IF申请的厂商代码)和0x0666,不过似 乎到2003年1月为止,Philips并没有启用0x0666这个产品代码。</PRE><PRE> L147-156,这段代码为新插入的MN选择一个合适的次设备号。数组 minor_table则保存了所有在线的MN。不过,自从驱动版本到0.1.1后,这段 代码和相关的变量不仅显得多余而且迫害了整个代码的清晰度。把它保留在 这里的唯一理由是只要做少量修改,代码就可以有更好的兼容性。以后会说 到版本0.1.1到底增加了神奇的东西。</PRE><PRE> L158-163,这段代码做常规的内存分配。其中的kmalloc函数和malloc函数 在使用上基本相同,但是它的第二个参数使得kamlloc有更多行为方式。在 这里使用的是GFP_KERNEL,通常在系统调用级的函数中会用这个标记分配内 存,它要求调用函数必须可重入。此外kmalloc获得内存后不会清空原有内容, 通常为了安全用memeset将其置空。</PRE><PRE> 之后的一段代码,在填充dev结构,但是不要被它的名字误导,它不是前面 说的代表USB设备的usb_device结构,在这里代表它的是udev,况且这个结 构也不是能随便改动的。dev的结构是usb_mn,定义于usb-mininurse.h。填 充这个结构的目的处了返回一个指针供disconnect日后调用,更重要的是为 日后填写file->private_data做准备,前面说过这个的用处。</PRE><PRE> L173-178 这段内容后面再说,它的功能是在/dev中创建设备文件。</PRE><PRE> 后面直到本函数末尾,这段代码让MN在加载驱动程序时可以更眩些。不过代 码的意义却是重大的。看看调用参数,应该能猜到点儿什么,后面会重点分 析它。</PRE><PRE> 传统的字符设备驱动程序,通常在open系统调用中初始化设备。在USB设备 中probe函数替代了这个功能,并且MN也并不需要特殊的初始化动作。所以 open的实现相对简单,比较重要的是对file_private_data的填写,并返回0 告知系统open文件成功,可以继续。 </PRE><PRE> disconnect函数和release调用和probe/open相似,只是做相反的事情,不 论。</PRE><PRE> 就MN的功能而言,mn_ioctl(系统调用ioctl)实现了其全部。做完常规的技 术处理后,再次碰到函数usb_control_msg。在分析它的调用参数前,我们 要再次明确一下MN的固件设计和一点儿USB协议: MN使用USB厂商请求发送所有的功能控制;MN使用厂商请求的bRequest域传 送控制命令(LEDon/LEDoff),用wIndex域传送命令对象(LED0/LED1/LEDA); 使用端点0传送全部信息。</PRE><PRE> 以点亮LED为例:</PRE><PRE> usb_control_msg(dev->udev,usb_sndctrlpipe(dev->udev,0), LEDon, USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_ENDPOINT, 0,LEDnum, NULL,0,.1*HZ);</PRE><PRE> 函数usb_control_msg的声明如下: extern int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);</PRE> <PRE> 该函数发送控制型消息到给定的端点。一共9个参数,逐一介绍: @dev: 欲发送命令的设备,这里就是由内核传递给ioctl的代表MN的 usb_device结构。 @pipe: 与USB协议中的管道概念对应。USB CORE的开发者对这个概念 进行了调侃,在usb.h里写了一段非常搞笑的注释,此处不便引 出。这里的管道被实现为一个无符号整型,用位图描述出必要 的信息。不过多数情况,没有必要手工创建这个位图,而是使 用创建管道的宏。usb_sndctrlpipe(dev->udev,0)的意思就是 创建主机到udev设备在端点0上的输出控制型管道。 @request: 与USB协议中的bRequest域对应。在刚才的调用中为宏LEDon, 其值为0。这个数字的成因参考《固件》。 @requesttype: 与USB协议中的bmRequestType域对应。在刚才的调用 中为USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_ENDPOINT,可以参考USB协议。 @value: 与USB协议的wValue域对应。MN目前没有用到,赋为0。 @index: 与USB协议的wIndex域对应。在ioctl里,将附加参数做类型转 换后,传递到这里。正确的值应是0x04/0x08/0x0C之一。 @data: USB协议允许传递用户附加的信息,指针data指向待传送的信 息。MN目前没有用到,赋为NULL。 @size: 与USB协议的wLength域对应。没有data,就没有size。赋为0。 @time: 设定多少时间后没有ack就算做超时,0为永远等待。MN为等待 0.1秒种。 如果成功,该函数返回实际传送的字节数,如果失败返回一个负的错误代码。</PRE><PRE> 至此驱动程序代码分析完毕。这是一个能用的驱动程序,并且在我的机器上 还没有测试到有什么致命的问题。不过过于简单的设计,使它会受到一些潜 在因素的威胁。比如这个驱动肯定不能工作在SMP或者NUAM机器上,也不能 工作在打过抢占式内核的Linux2.4或者Linux2.6的抢占方式下。对前一个问 题或许可以使用spin_lock机制,后一个问题则应该可以用down/up机制解决。 在0.2.0中可能会解决这些问题。</PRE><PRE> 除了传统的ioctl(以及read/write)方法外Linux还提供了另一种更加便捷的 机制进行核内/核外的信息交换:procfs。在未来的0.2.0中,将会增加包含 procfs在内的更多内核特性。</PRE><PRE><b><a name="— 第一次跟踪内核 —"><font color="#000080" size="3">— 第一次跟踪内核 —</font></a></b></PRE><PRE> 将usb_control_msg做为侵入内核的镉棒。这部分试图一直跟踪这个函数, 直到我们绝对相信足够的信息和命令已经提交给USB主控的驱动程序为止(暂 时信任主控驱动的真理性吧)。 现在打开usb_control_msg所在的文件:<kernels>/drivers/usb/usb.c</PRE><PRE> usb_control_msg()看上去是由另一个函数全权实现的: usb_internal_control_msg()。在调用这个“核心”之前,分配了一个 usb_ctrlrequest结构体,该结构体定义于usb.h,实际上就是USB协议中的 Setup报头,然后把request/requesttype/value/index/size封装进这个结 构体。用6个参数调用“核心”函数。最后释放临时分配的结构体。</PRE><PRE> 然而仅凭这个所谓的核心函数,尚不能使我们相信数据能被传送。我们继续 跟踪,找到usb_internal_control_msg()的实现,它就在镉棒的上面。用写 注释的方法阅读这段函数: int usb_internal_control_msg(struct usb_device *usb_dev, unsigned int pipe, struct usb_ctrlrequest *cmd, void *data, int len, int timeout) { struct urb *urb; int retv; int length;</PRE><PRE> urb = usb_alloc_urb(0); /* 准备继续跟踪 */ if (!urb) return -ENOMEM;</PRE><PRE> /* 这个宏定义在usb.h中,从语法上看是将第二个以后的参数填充到第一个参数中去 */ FILL_CONTROL_URB(urb, usb_dev, pipe, (unsigned char*)cmd, data, len, usb_api_blocking_completion, 0);</PRE><PRE> /* 准备继续跟踪 */ retv = usb_start_wait_urb(urb, timeout, &length); if (retv < 0) return retv; else return length; }</PRE><PRE> 这里面有一个重要的数据结构和隐藏在其后的概念:Usb Reqest Block(URB)。我们暂且放下代码,先看看 <kernels>/Documents/usb/URB.txt。这个文件描述了URB的概念和用法。对 usb_alloc_urb()的说明是分配一个URB并返回该指针(返回0为错误),对于 控制型请求参数应该是0。它的实现并不复杂,只是分配一块内存区域。调 用下面的宏会填充这段内存。 对usb_start_wait_urb()的说明只能看实现了。不过看它的命名和出现的位 置应该能猜出大意了:发送刚才填充的URB结构。看来,这可能会是一个真 正的“核心”函数。它的注释对它的描述是:启动一个URB并且等待完成直 到超时。</PRE><PRE> 因为能力的问题,不能逐行分析了,只看大意。首先是一些队列的处理。之 后调用了函数usb_submit_urb()并测试其返回值。通过后,就是对超时的检 查。因此usb_submit_urb()是一个更加核心的函数。我们继续跟踪。</PRE><PRE> int usb_submit_urb(struct urb *urb) { if (urb && urb->dev && urb->dev->bus && urb->dev->bus->op) return urb->dev->bus->op->submit_urb(urb); else return -ENODEV; }</PRE><PRE> 看它在正确情况下的返回值:urb->dev->bus->op->submit_urb(urb)。通过 在usb.h里反复跟踪查找,发现op原型是struct usb_operations,里面确实 有个域为submit_urb,为函数指针。但是在usb.c/usb.h中都无法再找到重
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -