`
dato0123
  • 浏览: 915004 次
文章分类
社区版块
存档分类
最新评论

Linux那些事儿之我是Hub(16)盖茨家对Linux代码的影响

 
阅读更多

hub_port_connect_change,顾名思义,hub端口上有连接变化时调用这个函数,这种变化既可以是物理变化也可以是逻辑变化.注释里说得也很清楚.有三种情况会调用这个函数,一个是连接有变化,一个是端口本身重新使能,即所谓的enable,这种情况通常就是为了对付电磁干扰的,正如我们前面的判断中所说的那样,第三种情况就是在复位一个设备的时候发现其描述符变了,这通常对应的是硬件本身有了升级.很显然,第一种情况是真正的物理变化,后两者就算是逻辑变化.

前面几行的赋值不用多说,2422行这个portspeed()函数就是获得这个端口所接的设备的speed,来自drivers/usb/core/hub.c:

121 static inline char *portspeed(int portstatus)

122 {

123 if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED))

124 return "480 Mb/s";

125 else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED))

126 return "1.5 Mb/s";

127 else

128 return "12 Mb/s";

129 }

意图太太太明显了,确定是高速/低速/还是全速的设备.这些信息都包含在portstatusbit9bit10里面.

2424行这一小段,不多说,就是那个指示灯的事,有指示灯就设置一下,指示灯可以设置成琥珀色可以设置成绿色可以关闭,也可以设置成自动,这里设置为自动.所谓自动设置就是hub自己根据端口的状态来设置,比如hub挂起了那么指示灯当然就应该熄灭.

2430,如果咱们这个hub的子设备还有值,那么什么也别说,先把它给断了,为嘛?不为嘛.(:这两句对白得用天津话说)

我们说了,端口连接有变化,可是变化可以是两个方向啊.一个是原来没有设备现在有了,一个是恰恰相反,原来有设备而现在没有.对于前者,hdev->children[port1-1]肯定为空,而对于后者这个指针应该就不为空,对于前者,我们接下来要做的事情就是对新连进来的设备进行初始化进行配置分配地址然后为该设备寻找相应的设备驱动程序,如果找到合适的了就调用该设备驱动程序提供的指针函数来再进行更细致更深入更对口的初始化.但是对于后者,就没必要那么麻烦了,直接调用usb_disconnect()函数执行一些清扫工作,并且把hubchange_bits清掉.然后再确定一下端口上确实没有连接什么设备,那就可以返回了.对于usb_disconnect()我们暂时先不看.我们先关心的是如果有设备连接进来该怎么处理.

24342438这一段我们不管,因为我们早已经作了一个无耻的假设,假设我们关闭掉了CONFIG_USB_OTG这个编译开关.

2440,别忘了,对于连接从有到无的变化,刚才我们可是清掉了hubchange_bits,所以这里再次判断portchange&USB_PORT_STAT_C_CONNECTION的意思就是对于连接从无到有的情况,我们还必须做一件事情,那就是判断反弹.啥叫反弹啊?换一种说法,叫做去抖动.比如你在网上聊QQ,关键时刻,你深情表白,按键盘,你按一下键,正常情况下,在按下和松开的过程中触片可能快速的接触和分离好多次,而此时此刻你自己还无比的激动无比的紧张,那么按键肯定是多次抖动,而驱动程序不可能响应你很多次吧,因为你毕竟只是按一下键.同理,这样的去抖动技术在hub中也是需要的.所以原则上,spec规定,只有持续了100ms的插入才算真正的插入,或者说才算稳定的插入.hub_port_debounce就是干这件事情的,这个函数会让你至少等待100ms,如果设备依然在,那么说明稳定了,这种情况下函数返回值就是端口的状态,2448行那样把status赋给portstatus.而如果100ms不到设备又被拔走了,那么返回负的错误码,然后打印信息告诉你去抖动挂了.printk_ratelimit是一个新鲜的函数,其实就是printk的变种,printk_ratelimit的用途就是当你某条消息可能会重复的被多次打印的,甚至极限情况下,打印个成千上万条,直接导致日志文件溢出,把别的信息都冲掉了,所以这样是不好的,于是进化出来一个printk_ratelimit(),它会控制打印消息的频率,如果短期内连续出现打印消息,那么它把消息抛弃,这种情况下这个函数返回0,所以,只有返回非0值的情况下才会真正打印.

2452,如果经历过以上折腾之后,现在端口状态已经是没有设备连接了,那么再作两个判断,一个是说如果该端口的电源被关闭了,那么就打开电源.另一个是说如果该端口还处于ENABLE的状态,那么goto done.可以看到done那里的代码就是把这个端口给disable.否则,如果端口已经是disable状态了,那么就直接返回吧,这次事件就算处理完了.

2466,这一段就是说连接虽然变化了,并且设备也是从无到有了,但是如果之前这个端口是出于suspend状态的话,那么这里就要调用hub_port_resume()把这个端口给恢复.关于电源管理的深层次代码,我们暂时先搁一边.但是作为hub驱动程序,电源管理部分是必不可少的,现在国家提倡建设节约型社会,我们国家做一个发展中国家,资源又比较短缺,所以每一个爱国人士都有责任有义务为节约能源而做出自己力所能及的贡献.

2477,SET_CONFIG_TRIES这个宏,看了就头疼,这又要引出一段故事了.

首先我想说的是,以下这个for循环的目的很明确,为一个usb设备申请内存空间,设置它的状态,把它复位,为它设置地址,获取它的描述符,然后就向设备模型中添加这么一个设备,再然后会为这个设备寻找它的驱动程序,再然后驱动程序提供的probe()函数就会被调用.但是从usb这边来说,只要调用device_add这么一个函数向设备模型核心层添加设备就够了,剩下的事情设备模型层会去处理,这就是设备模型的优点,所以也叫统一的设备模型.就是说不管你是pci还是usb还是scsi,总线驱动的工作就是申请并建立总线的数据结构,而设备驱动的工作就是往这条总线上注册,调用driver_add,而设备这边也是一样,也往该总线上注册,即调用device_add.driver_add就会在总线上寻找每一个设备,如果找到了自己支持的设备,并且该设备没有和别的驱动相绑定,那么就绑定它.反过来,设备这边的做法也一样,device_add在总线上寻找每一个设备驱动,找到了合适的就绑定它.最后,调用probe函数,然后兵权就交给了设备驱动.整个这个过程就叫做usb设备初始化.所以说,作为设备驱动程序本身来讲,它只是参与了设备初始化的一部分,很小的一部分,真正的重要的工作,都是中央集权的,由核心来执行,对于usb设备来说,就是hub驱动来集中处理这些事情.之所以这些工作可以统一来做,是因为凡是usb设备,它都必须遵循一些共同的特征,就好比,我们写托福作文,有那么一些书,就提供了模版,甭管是什么作文题目,都可以套用这些模版.因为不管是写人写猴还是写猪,它们都有五官,所以如果写一篇说明文的话,都一定会写五官.以前我觉得自己还挺帅,可是后来我发现,我有的五官,猪也有.我一个在科大读书的同学更惨,科大女生少,结果时间长了,他说他看到一头母猪也觉得挺眉清目秀的.

Ok,那么这么一个过程为何要循环呢?早在中日甲午战争的时候,Linux内核中是没有SET_CONFIG_TRIES这么一个宏的,后来在非典那年的圣诞节前夕,David Brownell同志提交了一个内核补丁,出现了这么一个宏,并且把这个宏的值被固定为2.再之后,2004年底,这个宏的定义发生了一些变化,变得更加灵活.

第一,为什么设为2?这个理由超级简单,我们说了,要获取设备描述符,如何获得?给设备发送请求, 发送一个GET-DEVICE-DESCRIPTOR的请求,然后正规企业的正规设备就会返回设备描述符.看仔细了,正规,什么叫正规? Sony算不算正规?Philip算不算正规?我不知道.现在不负责任的厂商越来越多,一边号称自己的正规企业,一边生产出让人大跌隐形眼镜的设备来卖.你就说我前几天去清华校内那个桃李餐厅,三层的那个小餐厅,他们也敢说自己正规,结帐的时候发票都不给开,这边收着我们的钱,那边逃着国家的税,你说这能叫正规吗?说回usb,本来usb spec就规定了只要发送这么一个请求你设备就该返回你的设备描述符,可是你这么一试,它偏给你说请求失败.你说急死你不?所以,写代码的人学乖了,发这些重要的请求都按两次发,一次不成功再发一次,成功了就不发,两次还不成功,那没办法了,病入膏肓了

第二,那么后来为何又改为别的值了?我们来看,现在这个宏被改为了什么?drivers/usb/core/hub.c:

1446 #define PORT_RESET_TRIES 5

1447 #define SET_ADDRESS_TRIES 2

1448 #define GET_DESCRIPTOR_TRIES 2

1449 #define SET_CONFIG_TRIES (2 * (use_both_schemes + 1))

1450 #define USE_NEW_SCHEME(i) ((i) / 2 == old_scheme_first)

usb_both_schemesold_scheme_first.这两个参数,

95 /*

96 * As of 2.6.10 we introduce a new USB device initialization scheme which

97 * closely resembles the way Windows works. Hopefully it will be compatible

98 * with a wider range of devices than the old scheme. However some previously

99 * working devices may start giving rise to "device not accepting address"

100 * errors; if that happens the user can try the old scheme by adjusting the

101 * following module parameters.

102 *

103 * For maximum flexibility there are two boolean parameters to control the

104 * hub driver's behavior. On the first initialization attempt, if the

105 * "old_scheme_first" parameter is set then the old scheme will be used,

106 * otherwise the new scheme is used. If that fails and "use_both_schemes"

107 * is set, then the driver will make another attempt, using the other scheme.

108 */

109 static int old_scheme_first = 0;

110 module_param(old_scheme_first, bool, S_IRUGO | S_IWUSR);

111 MODULE_PARM_DESC(old_scheme_first,

112 "start with the old device initialization scheme");

113

114 static int use_both_schemes = 1;

115 module_param(use_both_schemes, bool, S_IRUGO | S_IWUSR);

116 MODULE_PARM_DESC(use_both_schemes,

117 "try the other device initialization scheme if the "

118 "first one fails");

如果你像李玟不知道满江红的词作者岳飞是谁,这没什么,如果你像周杰伦不知道雷锋是谁,这也没什么,如果你像范冰冰不知道生得渺小死得和谐的刘胡兰是哪个时代的人,这也没什么,如果你像杨丞琳不知道抗日战争是八年,这仍然没什么,可是如果你稍有悟性,你就应该能看出,这两个是模块加载的时候的参数,在你加载一个模块的时候,在你用modprobe或者insmod加载一个模块的时候,你可以显示的指定old_scheme_first的值,可以指定usb_both_schemes的值,如果你不指定,那么这里的默认值是old_scheme_first0,use_both_schemes1.

那么他们具体起着什么作用?scheme,盗版的金山词霸告诉我,方案,计划的意思.那么old scheme,both scheme这两个词组似乎已经反映出来说有两个方案,一个旧的,一个新的.这是怎么回事?什么方面的方案?一个是来自Linux的方案,一个是来自Windows的方案,目的是为了获得设备的描述符.

说来话长,首先我想说,你们这些Linux发烧友们,你们老是说微软不好,Windows不好,可是说句良心话,没有Windows,没有微软,你们靠什么学习Linux?你们是如何知道Linux?你不要跟我说你是先学习Linux然后才知道微软的,不要说你是先用Mozilla浏览网页后来才从网页上知道有IE这么一个冬冬存在的.呵呵,我相信大多数人是恰恰相反吧.

所以我一直是喜欢盖茨家的产品的,尤其是在200110月盖茨来中国在上海我亲眼见过他听他做了一次报告之后,更是觉得他很了不起.具体到我们目前提到的这个方案问题,这里需要一些背景知识.

第一个,endpoint 0.0号端点是usb spec中一个特殊的端点.此前我一直没有一本正经的讲过端点0,也许你早就在问了,为何当初计算端点数目的时候没有提到端点0?其实我还不是为你好,多一事不如少一事,有些概念,和爱情一样,只有该认识的时候才去认识比较好.正如电影<<2046>>里所说的那样,爱情这东西,时间很关键,认识得太早或太晚,都不行.

usb spec中是这样规定的,所有的USB设备都有一个默认的控制管道.英文叫Default Control Pipe.与这条管道对应的端点叫做0号端点,也就是传说中的endpoint zero.这个端点的信息不需要纪录在配置描述符里,就是说并没有一个专门的端点描述符来描述这个0号端点,因为不需要,原因是endpoint zero基本上所有的特性都是在spec规定好了的,大家都一样,所以不需要每个设备另外准备一个描述符来描述它.(换言之,在接口描述符里的bNumEndpoints是指的该interface包含的端点,但是这其中并不包含Endpoint zero,如果一个设备除了这个Endpoint zero以外没有别的端点了,那么它的接口描述符里的bNumEndpoints就应该是0,而不是1.)然而,别忘了我说的是基本上”,有一个特性则是不一样的,这叫做maximum packet size,每个端点都有这么一个特性,即告诉你该端点能够发送或者接收的包的最大值.对于通常的端点来说,这个值被保存在该端点描述符中的wMaxPacketSize这一个field,而对于端点0就不一样了,由于它自己没有一个描述符,而每个设备又都有这么一个端点,所以这个信息被保存在了设备描述符里,所以我们在设备描述符里可以看到这么一项,bMaxPacketSize0,而且spec还规定了,这个值只能是8,16,32或者64这四者之一,而且,如果一个设备工作在高速模式,这个值还只能是64,取别的值都不行.

而我们知道,我们所做的很多工作都是通过与endpoint 0打交道来完成的,那么endpoint 0max packet size自然是我们必须要知道的,否则肯定没法正确的进行控制传输.于是问题就出现了.我不知道max packet size就没法进行正常的传输,可是max packet size又在设备描述符里,我不进行传输我就不知道max packet size?我晕!这一刻,我想到了美国作家约瑟夫 海勒的代表作<<22条军规>>.说第二次世界大战末期,飞行大队的一个上校不断给飞行员们增加飞行任务,远远超出一般规定,飞行员们都得了恐惧症,很多人惶惶不可终日,其中投弹手尤塞恩找到一个军医帮忙,想让他证明自己疯了.军医告诉他,虽然按照所谓的22条军规”,疯子可以免于飞行,但同时又规定必须由本人提出申请,而如果本人一旦提出申请,便证明你并未变疯,因为对自身安全表示关注,乃是头脑理性活动的结果”.这样,这条表面讲究人道的军规就成了耍弄人的圈套.难道这里usb spec也是一个温柔的陷阱?

我喘着气对自己说:冷静,冷静,妈的,冷静,冷静,oh,shit,冷静冷静冷静.

后来我终于明白了.

可是星爷说过:”以你的智商我很难跟你解释.”

我刚才说了,max packet size只能是8,16,32或者64,这也就是说,至少你得是8,而我们惊人的发现,设备描述符公共是18bytes,其中,byte7恰恰就是bMaxPacketSize0,byte0byte7算起来就是8个字节,那也就是说,我先读8个字节,然后设备返回device descriptor的前8个字节,于是我就知道它真正的max packet size,于是我再读一次,这次才把整个描述符18个字节都给读出来,不就ok了吗?我靠,写代码的你们他妈的太有才了.

但事情往往不是这么简单,正所谓人算不如天算,生活不是电影,生活比电影苦.写代码的以为自己的算法天衣无缝.可是实践下来却遇到了问题.主义哲学认为,实践是检验人品的唯一标准,这是由人品的本性和实践的特点所决定的.主义哲学把实践的观点引入认识论,把辩证法和唯物主义有机地结合起来,在人类认识史上真正科学地解决了人品标准问题.

200410,即我大四开始艰难找工作的那段日子里,开源社区的同志们发现一个怪事,Sony家的一个摄像机没法在Linux下正常工作.问题就出在获取设备描述符上,当你把一个8个字节的GET-DEVICE-DESCRIPTOR的请求发送给它们家的设备时,你得不到一个8个字节的不完整的描述符,相反,你会遇到溢出的错误,因为Sony它们家的设备只想一口气把18个字节的整个设备描述符全都给你返回,结果导致了错误,而且实践证明这样的错误还有可能会毁坏设备.

,奇怪了,这怎么办?有人爆料说这款设备在Windows下工作是完全正常的.这可不得了了.如何是好?后来又有人爆料,Windows下面人家采取的是另一种策略,或者说方案,人家就是直接发送64个字节的请求过去,即要求你设备返回64个字节过来,如果你设备端点0max packet size32或者64,那么你反正只要把18个字节的设备描述传递过来就可以了,但是如果你设备端点0max packet size就是8或者16,而设备描述符是18个字节,一次肯定传递不完,那么你必然是传递了一次以后还等待着继续传递,但是我从驱动角度来说,我只要获得了8个字节就够了,而对于设备,你不是等着继续传吗,我直接对你做一次reset,让你复位,这样不就清掉了你剩下的想传的数据了么?然后我获得了前8个字节我就可以知道你真正的max packet size,然后我就按这个真正的最大值来进行下面的传输,首先就是获得你那个18个字节的真正的完整的设备描述符.这样子,也就达到了目的了.这就是Windows下面的处理方法.

于是开源社区的兄弟们发现,很多厂商都是按着Windows的这种策略来测试自己的设备的,他们压根儿就没有测试过请求8个字节的设备描述符,于是,也就没人能保证当你发送请求要它返回8个字节的设备描述符的时候它能够正确的响应.所以,Linux开发者们委曲求全,把这种Windows下的策略给加了进来,其实,Linux的那种策略才是usb spec提供的策略,而现实是,Windows没有遵守这种策略,然而厂商们出厂的时候就只是测试了能在Windows下工作,他们认为遵守Windows就是遵守了usb spec.而事实呢,却并非如此.严格意义来说,这是Windows这边的bug,不过这种做法却引导了潮流.呵呵,这不禁让人想起另一桩趣事,毛阿敏刚出道的时候,第一次在中央电视台录像,战战兢兢的,那时候姜昆给他主持节目,姜昆那时候已经是腕儿,她还没成腕儿呢,她向人家姜昆请教自己问人家自己有什么毛病,姜昆说别的都行,就是走路显着不太成熟,毛阿敏感激不尽,而且沉痛的说,姜老师,我就是上台这路这不会走.结果后来,一曲你从哪里来使毛阿敏名声大振,又过了些日子,姜昆发现,所有的小歌星们都开始学毛阿敏那两步走,姜昆这个气哟,向毛阿敏控诉,毛阿敏自己也乐得不行.当了名人了,就是毛病也有人学,现实就是这么可乐.(参考文献,姜昆<<笑面人生>>,1996年出版,我妈1997年来北京的时候买的)

所以,就这样,如今的代码里实现了这两种策略,每种试两次,原来的那种策略叫做old scheme,现在的做法就是具体使用那种策略你作为用户你可以在加载模块的时候自己设置,但是如果你不设置,那么默认的方法就是先使用新的这种机制,试两次,然后如果不行,就使用旧的那种,我们看到前面我贴出来的那个宏, USE_NEW_SCHEME,她就是用来判断是不是使用新的scheme.这个宏会在hub_port_init()函数中用到,如果它为真,那么就用新的策略,即发送那个期望64个字节的请求.如果fail,那才发送那个8个字节的请求.这些我们将会在hub_port_init()中看到,到时候再说.

至此我们总算可以理解SET_CONFIG_TRIES这么一个宏了,它被定义为(2 * (use_both_schemes + 1)),use_both_schemes就是说两种策略都用,这个参数也是可以自己在加载模块的时候设置,默认值为1,即默认的情况时先用新的策略,不行就用旧的那个.usb_both_schemes1就意味着SET_CONFIG_TRIES等于4,即老的策略试两次,新的策略试两次,当然,成功了就不用多试了,多试是为失败而准备的.

看明白了这个宏,我们可以进入到这个for循环来仔细看个究竟了.

分享到:
评论

相关推荐

    Linux那些事儿之我是Hub

    Linux那些事儿之我是Hub,是前面Linux那些事之我是U盘,usb等的姐妹篇

    Linux那些事儿之我是HUB.pdf

    非常不错的一本书,LinuxLinuxLinuxLinuxLinuxLinuxLinuxLinuxLinuxLinuxLinuxLinuxLinuxLinux

    Linux那些事儿

    Linux那些事儿之我是Hub Linux那些事儿之我是USB Core Linux那些事儿之我是UHCI Linux那些事儿之我是EHCI控制器 Linux那些事儿之我是PCI Linux那些事儿之我是SCSI硬盘 Linux那些事儿之我是Block层 Linux那些事儿之我...

    Linux那些事儿1-9合集

    linux那些事儿之我是HUB linux那些事儿之我是USB Core linux那些事儿之我是UHCI Linux那些事儿之我是EHCI主机控制器 Linux那些事儿之我是PCI Linux那些事儿之我是SCSI硬盘 Linux那些事儿之我是Block层 linux那些事儿...

    Linux那些事儿之我是USB(第2版)

    Linux那些事儿第二版我是Hub一章,比较第一版修正了一些错误,增加了电源管理部分的分析,推荐阅读

    linux那些事儿之我是USB(包括第一版和第二版完整文字)

    本文件包括第一第二完成版,第二版基于2.6.22内核,对USB子系统的大部分源代码逐行进行分析,系统地阐释了Linux内核中USB子系统是如何运转的,子系统内部的各个模块之间是如何互相协作、配合的。本次改版修改了第1版...

    Linux那些事儿之全集

    导读.doc Linux那些事儿之我是Block层.pdf Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是Hub.pdf Linux那些事儿之我是USB_core.pdf Linux那些事儿之我是U盘.pdf等等 Linux那些事儿系列全在这里了

    Linux那些事儿系列.rar

    》包括《Linux那些事儿之我是Hub》、《Linux那些事儿之我是Sysfs》《Linux那些事儿之我是UHCI》、《Linux那些事儿之我是USB core》、《Linux那些事儿之我是U盘》,令人叹为观止的一个linux系列书籍。只能说,江山代...

    Linux那些事儿之我是USB(第2版).pdf

    本书基于2.6.22内核,对USB子系统的大部分源代码逐行进行分析,系统地阐释了Linux内核中USB子系统是如何运转的,子系统内部的各个模块之间是如何互相协作、配合的。本次改版修改了第1版中出现的错误,增加了一个附录...

    linux的那些事儿全集

    Linux那些事儿之我是Block层 ...Linux那些事儿之我是Hub Linux那些事儿之我是PCI Linux那些事儿之我是SCSI硬盘 Linux那些事儿之我是Sysfs Linux那些事儿之我是UHCI Linux那些事儿之我是USB core Linux那些事儿之我是U盘

    linux那些事儿(EHCI Block SCSI Sysfs PCI USB U 盘 UHCI Hub)

    Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是Block层.pdf Linux那些事儿之我是SCSI硬盘.pdf Linux那些事儿之我是Sysfs.pdf ...Linux那些事儿之我是Hub.pdf Linux那些事儿之我是UHCI.pdf

    linux那些事全集

    Linux那些事儿之我是U盘 Linux那些事儿之我是USB_core Linux那些事儿之我是UHCI Linux那些事儿之我是Sysfs ...Linux那些事儿之我是Hub Linux那些事儿之我是EHCI主机控制器 Linux那些事儿之我是Block层

    Linux那些事儿系列

    本人整理的fudan_abc的专栏中以完结的文章,在此向原作者表示感谢,给...内容包括:linux那些事儿之我是U盘,linux那些事儿之我是USB,linux那些事儿之我是HUB,linux那些事儿之我是UHCI,linux那些事儿之我是Sysfs。

    Linux那些事儿之我是XXX全集.rar

    Linux那些事儿之我是XXX全集 包含USB core U盘 UHCI PCI SCSI硬盘 Block Hub EHCI 。 想学驱动的童鞋,不妨看看。该书主要是进行源代码的分析

    Linux那些事儿 系列之2 Block+EHCI+PCI+SCSI

    Linux那些事儿之我是Block层.pdf Linux那些事儿之我是EHCI主机控制器.pdf Linux那些事儿之我是PCI.pdf Linux那些事儿之我是SCSI硬盘.pdf 注: 之前有人已经上传了《Linux那些事儿 系列》,其已经包含了:hub,sysfs...

    Linux那些事儿.rar

    包括:Linux那些Linux那些事儿之我是SCSI硬盘,Linux那些事儿之我是Block层,Linux那些事儿之我是EHCI主机控制器,Linux那些事儿之我是HUB,Linux那些事儿之我是PCI,Linux那些事儿之我是Sysfs,Linux那些事儿之我是...

    linux那些事儿之我是USB.zip

    里面包含Linux那些事的九个文档,Block层,ECHI主机控制,HUB,PCI,SCSI硬盘,Sysfs,UHCI,USB+core,U盘等九个文档,内容详细,而且全面都有书签,适合系统学习!

Global site tag (gtag.js) - Google Analytics