📄 drivers.html
字号:
载的实体。推断这个函数可能就是“最底层”的调用,不属于USB CORE,果然在 uhci/ehci/ohci以及sl811等主控的驱动程序中找到了submit_urb的具体实 现。</PRE> <PRE> 不能指望通过一次地毯式搜索就能建立USB CORE的整体框架,因为这是一种自底 向上的学习方法,只有配合其它文本和再一次的搜索,慢慢建立概念。 Linux USB子系统分为三个层次,处于核心地位的是USB CORE,它提供一个USB “库”,主要由usb.c实现(device.c/devio.c还实现了usbdevfs);之下是各 种USB主控芯片的驱动程序;之上是各类设备的驱动程序。USB主控通常是PCI设 备(sl811等嵌入芯片除外),不在本文讨论范围之内。 下面重点分析USB CORE:usb.c中的一些函数实现。</PRE> <PRE> 首先看MN的驱动中使用到的几个。usb_control_msg这个函数前面已经做了详细 分析,再看usb_register,其实现为 </PRE> <font SIZE="2"> <blockquote> <p>int usb_register(struct usb_driver *new_driver)<br> {<br> if (new_driver->fops != NULL) {<br> if (usb_minors[new_driver->minor/16]) {<br> err("error registering %s driver", new_driver->name);<br> </font> <font SIZE="2">return -EINVAL;<br> </font> <font SIZE="2">}<br> </font> <font SIZE="2">usb_minors[new_driver->minor/16] = new_driver;<br> }</p> <p> info("registered new driver %s", new_driver->name);<br> init_MUTEX(&new_driver->serialize);</p> <p> /* Add it to the list of known drivers */<br> list_add_tail(&new_driver->driver_list, &usb_driver_list);</p> <p> usb_scan_devices();</p> <p> return 0;<br> }</p> </font> <p>首先检查是检查设备是否过多,如果成功做一些入链等操作后,就<br> 调用了另外一个函数usb_scan_devices。让我们跟踪这个函数:</p> <font SIZE="2"> <p>void usb_scan_devices(void)<br> {<br> </font> <font SIZE="2">struct list_head *tmp;<br> <br> down (&usb_bus_list_lock);<br> tmp = usb_bus_list.next;<br> </font> <font SIZE="2">while (tmp != &usb_bus_list) {<br> </font> <font SIZE="2">struct usb_bus *bus = list_entry(tmp,struct usb_bus, bus_list);<br> <br> </font> <font SIZE="2">tmp = tmp->next;<br> </font> <font SIZE="2">usb_check_support(bus->root_hub);<br> </font> <font SIZE="2">}<br> </font> <font SIZE="2">up (&usb_bus_list_lock);<br> }</p> </font> <p>这个函数遍历所有未被驱动起来的USB接口,usb_check_support是深<br> 度优先遍历算法,对每个未驱动的接口调用usb_find_interface_driver。<br> 判断一个新接口的方法是,用USB驱动队列中的每一个成员比对这个接口。<br> 对于老式的驱动,这个函数直接调用驱动的probe函数,让这个函数自行<br> 比对;而新的驱动增加了id的概念,该函数会首先比对这个id值,然后调<br> 用probe函数。<br> 如果比对成功则调用usb_driver_claim_interface,向该接口填写数据并<br> 返回0,说明成功匹配,如果没有则使用驱动队列的下一个成员,至到所有<br> 成员比对过所有接口。</p> <p>usb_deregister做了和usb_register虽然调用函数有所不同,但几乎做了<br> 完全相反的事情,在此不论。</p> <p>从这个角度分析,看上去没有任何问题。然而,对于一个刚刚启动的计算<br> 机,它怎么会顺着刚才的思路,先调用MN驱动中的usb_register,从而引<br> 发一系列的内核动作呢,换句话说,Linux是如何知道MN的驱动就是MN的<br> 驱动呢?</p> <p>如果我们使用一些“标准”的USB设备,像USB鼠标或者USB接口的闪存,只<br> 要设备上电,相应的驱动模块就会自动加载。MN当然也可以实现这种全状态<br> 的即插即用,前提是编译内核是要选中"HOT-PLUG"一项。</p> <p>当一个USB设备插入到总线上时,HUB的电路会监测到D+/-线上的变化,它会<br> 记录下新设备所在的位置。HUB具有一个中断型端点,当主控的下一个查询周<br> 期到来时,HUB会告之有新设备。主控随后向操作系统转发这一情况。接下来<br> 发生的事件就被称为枚举。<br> Linux首先会从设备号位图中选择一个合适的地址,准备发送给设备。有两种<br> 算法,一种每次都从1开始寻找可用的设备号,另一种是从上次分配的设备号<br> 之后开始寻找。<br> 然后调用usb_new_device。根据USB规范,这个函数尝试使用最小包大小发送<br> 数据,首先是把计算好的设备号送出去,如果失败,要回收设备号。然后读取<br> 设备描述符的前8个字节,以获得设备最大的包传送能力。用这个值获取设备<br> 其它描述符,并填充相应的数据结构。<br> 现在枚举基本上完成了。之后向USB设备文件系统中添加关于这个设备的节点。<br> 调用usb_find_drivers,这个函数为这个新的设备在驱动列表中比对是否有合<br> 适的成员。</p> <p>最后,有一个重要的调用<br> call_policy("add",dev);<br> 它的注释上说,用户空间可以借此完成加载模块或者做进一步的配置。是的,<br> Linux把即插即用的实现放在内核空间。完成这个任务的程序通常是<br> /sbin/hotplug,或者看看/proc/sys/kernel/hotplug,如果修改可以定位自己<br> 实现的加载策略,有的Linux发行版本在拔插设备时会让系统扬声器叫唤一下。</p> <p>以前,要想使用MN,除了把她插在USB总线上外,还要用insmod手工加载她的驱<br> 动模块。但是只要对hotplug的配置文件做一些简单的修改,MN也能实现全状态<br> 即插即用。</p> <p>hotplug的配置文件通常在/etc/hotplug/。这个目录下包含了scsi/usb/1394等<br> 一些处理各种即插即用的脚本和产品信息。我们只关心USB。找到所有看上去和<br> USB有关的文件,阅读它们。最后可以发现usb.distmap和usb.usermap中包含了<br> 大量产品信息,在这些文件尾部按格式添加MN的识别码。最后把MN的驱动模块复<br> 制到/lib/modules/2.4.25/kernel/drivers/usb/里,这样就可以了。<br> </p> </blockquote><PRE><b><a name="— 更多内核设施和机制 —"><font size="3" color="#000080">— 更多内核设施和机制 —</font></a></b></PRE><PRE> 这部分讨论前面提到的“后面讨论”,却还没有讨论的几个问题: * 设备文件系统(devfs) * ioctl的一个技术细节 * 机制的实现</PRE><PRE> 老式的*nix将所有的设备文件放在/dev目录里,采用平面方式组织,就是说 没有子目录。如果细心还会发现,这个目录里经常会数个软盘驱动器,而实 际上却只有一台,这些尚未使用的文件占用磁盘空间,降低检索速度。</PRE><PRE> 老式的*nix使用静态的设备号,即所有设备按照事项约定的主设备号工作。 正如互联网初期使用hosts文件分配机器IP与主机名那样,这种方法很快被 越来越多的设备搞的混乱不堪。</PRE><PRE> 现在Linux实现了一种新的设备文件管理方法:devfs。这个方法把设备文件 的管理权转移到内核,内核在内存中建立与机器情况相同的设备文件列表并 像procfs那样映射到结构化文件系统上。其命名空间也要比以前的方案大得 多。</PRE><PRE> MN在0.1.0中就使用了devfs,在0.1.1中使用了devfs的一个更好的特性:动 态分配主、次设备号。这也就是为什么前面说probe的部分代码在0.1.1后就 变得冗余。此外devfs_fs_kernel.h中也准备了大量可用的工具,比如MN就 使用了其中读取主次设备号的函数。</PRE><PRE> 在使用ioctl时,为了防止正确的命令下在错误的设备上(如对让软驱执行 eject),ioctl的命令号被建议服从全局的分配。在<kernels>/Documents里 有一份ioctl-number.txt,记录了已经分配的ioctl号。通常使用_ IO/_IOR/_IOW/_IOWR宏构筑命令号,因为较新的Linux使用了命令号的分段。 通常_IOR为从设备读的命令,_IOW相反。但是似乎这是无关紧要的,不过为 了符合更好的命名规则,还是按照建议中的办吧。</PRE><PRE> 关于机制实现的问题,可以参考<The Art of Unix Programming>。</PRE><PRE><b><a name="— 一些讨论 —"><font size="3" color="#000080">— 一些讨论 —</font></a></b></PRE><PRE> 关于内核的调试。说起Linux内核调试,那方法可真多了去了。尽管教科书 上说宏核是不容易在线调试的。可是就算是一个新手,也能通过很舒服的操 作获得丰富的调试信息。如果是黑客,使用更高级的工具,调试真的跟玩一 样。</PRE><PRE> 在MN的驱动和应用程序完成最初的编写之后,难免会有一些疏忽造成代码无 法工作。我使用了两种最简单的调试方法,然而却迅速地找到了错误,快得 几乎难以置信。这两钟方法,一种就是之前说的strace程序。它让我迅速定 位了ioctl参数的问题。另外一个就是靠输出内核的调试信息以及在驱动中 添加一些语句。 这些信息都使用printk()这个内核函数,不过通常用info()/err()等宏更加 方便。printk()输出的信息是有级别的,如果低于当前控制台的默认级别信 息就只能输出到系统日志了。使用这个命令可以提升控制台级别: echo 8 > /proc/sys/kernel/printk 这样一来,所有的信息都会直接显示。用这个方法,我几乎查出了其它所有 的错误。</PRE><PRE> 顺便说一下,用这个方法还查出了MN固件上的一个小bug:所有厂商请求后 没有向主控发送ack,造成每次usb_control_msg()后都产生超时错误,这 是一个小小的遗憾,但并不影响使用。</PRE> <PRE> 关于学习方式。在*nix里学习编程或别的什么,感觉很舒服,并且能够学习 到高超的编程技巧和极好的思想。你不用为了一个特简单的要求,而花费大 量的时间精力记住那些为了众所周知的目的而经过层层封装的API;你可以 和整个iNET分享你的成果和快乐。 就说编写MN驱动,真的是看着源代码学习内核,编写驱动。一种黑客的感觉。</PRE><PRE> .end</PRE><PRE> </PRE> </blockquote> </blockquote></blockquote>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -