📄 driver.txt
字号:
<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><font color="#0000FF" size="3"><a href="#第一次跟踪内核">— 第一次跟踪内核 —</a></font></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中都无法再找到重
载的实体。推断这个函数可能就是“最底层”的调用,果然在
uhci/ehci/ohci以及sl811等主控的驱动程序中找到了submit_urb的具体实
现。</PRE>
<PRE> 不要指望通过一次地毯式搜索就能建立USB CORE的整体框架,但配合其它文
本和再一次的搜索,总有一天会的。
</PRE>
<PRE><font size="3" color="#0000FF"><b><a href="#更多内核设施和机制">— 更多内核设施和机制 —</a></b></font></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><font size="3" color="#0000FF"><a href="#一些讨论">— 一些讨论 —</a></font></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 + -