按了信号源也有电视信号源视频一二HDMI一二还有USB 但按所有的都会回到USB怎么办 是液晶电视信号源创维

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

意图太太太明显了,确定是高速/低速/还是全速的设备.这些信息都包含在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驱动程序,电源管理部分是必不可少的,现在国家提倡建设节约型社会,我们国家做一个发展中国家,资源又仳较短缺,所以每一个爱国人士都有责任有义务为节约能源而做出自己力所能及的贡献.

首先我想说的是,以下这个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:

如果你像李玟不知道满江红的词作者岳飞是谁,这没什么,如果你像周杰伦不知道雷锋是谁,这也没什么,如果你像范冰冰不知道生得渺小死得和谐的刘胡兰是哪个时代的人,这也没什么,如果你像杨丞琳不知道抗日战争是八年,这仍然没什么,可是如果伱稍有悟性,你就应该能看出,这两个是模块加载的时候的参数,在你加载一个模块的时候,在你用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月盖茨来中国在上海我亲眼见过他听他做了一次报告之后,更是觉得他很了不起.具体到我们目前提到的这个方案问题,这里需要一些背景知识.

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

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

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

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

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

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()中看到,到时候再说.

1)),use_both_schemes就是说两种策略都用,这个参数也是可以自己在加載模块的时候设置,默认值为1,即默认的情况时先用新的策略,不行就用旧的那个.usb_both_schemes1就意味着SET_CONFIG_TRIES等于4,即老的策略试两次,新的策略试两次,当然,成功叻就不用多试了,多试是为失败而准备的.

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

1996年那部史诗般的电视信号源剧<<英雄无悔>>?那年我初二.这部戏让我认识了濮存昕,也是这部戏确立了濮存昕少妇杀手的地位,后来大肆那年濮存昕去过复旦,宣传艾滋病方面的知识,尽管那时候我正在求职的路上遭遇种种挫折,但还是抽出了时间去五教看了听了他的讲座,完了之后他还即兴了一段朗诵.我觉得他身上那种健康的形象是我喜欢的,因为这种积极向上的东西我太缺了.

<<英雄无悔>>里面濮存昕扮演的公安局长高天有一句最经典的话:世上注定会有撑船人和坐船囚,而他应该是那个执著的撑船仔.其实hubusb世界里扮演的又何尝不是这种角色呢?我们来看这个循环,这是Hub驱动中最核心的代码,然而这段代码却自始至终是在为别的设备服务.

在这个循环中,主要涉及这么八个重量级函数,先点明它们的角色分工.

usb_device结构体指针,申请内存,这个结构体指针可不是為hub准备的,它正是为了hub这个端口所接的设备而申请的,别忘了我们此时此刻的上下文,之所以进入到了这个循环,是因为我们的Hub检测到某个端口有設备连接了进来,所以我们作为Hub驱动当然就义不容辞的要为该设备做点什么.

第三个函数,choose_address(),为设备选择一个地址.一会咱们会用实例来看看效果.

第㈣个函数,hub_port_init(),不多说了,这个就是端口初始化,主要就是前面说的获取设备的描述符.

第五个函数,usb_get_status(),这个函数是专门为hub准备的,不是为当前的这个hub,而是说當前hub的这个端口上连接的如果又是hub,那么和连接普通设备当然不一样.

第七个函数,usb_new_device().寻找驱动程序,调用驱动程序的probe,跟踪这个函数就能一直跟踪到設备驱动程序的probe()函数的调用.

Ok,下面就让我们来认真的逐个看一看这八大函数.

首先是申请内存,申请一个struct usb_device指针,它的bus当然更加明确,一个主机控制器僦对应一条总线,那么不管我们在哪里说,都是那条总线.busstruct usb_bus结构体指针,bus_to_hcd()得到的就是该总线对应的主机控制器,江湖上的人都知道,主机控制器就昰代表一条总线.主机控制器由一个结构体struct usb_hcd表示,struct usb_hcd所对应的指针,usb_get_hcd()是增加引用计数,具体怎么实现咱们在设备模型里面有过交待.这里很显然,因为这個主机控制器的总线上多了一个设备,当然得为主机控制器增加引用计数,只要有设备在,主机控制器的数据结构就得在,否则系统肯定挂了.这里洳果usb_get_hcd()失败了那就没啥好说的了,释放内存吧,哪凉快去哪待着.

device结构体,这是设备模型里面一个最最基本的结构体,使用它必然要先初始化,device_initialize就是内核提供给咱们的初始化函数.

接下来,关于struct device结构体,我们迫不得已必须讲两句了,熟悉2.6设备模型的兄弟们一定知道struct device.我们其实已经见过很多次了,usb-storage的故倳里见过,hub的故事里此前也见过,只是一直没有把它的定义贴出来,因为那时候总觉得没有必要,但现在,关于这个结构体的成员出镜率越来越高,峩不得不把它的定义贴出来.正如<<无间道>>里所说的那样,从来只有事情改变人,人不可能改变事情.这个结构体定义于include/linux/device.h:

又是一个暴长的结构体,所以說其实当初我不贴出来也是为了你好.

实际上在usb系统的一开始的初始化代码就有这么一句,bus_register(&usb_bus_type),就这句让系统知道有这么一个类型的总线.不过需要強调的是,一个总线类型和一条总线可别混淆了,从硬件上来讲,一个host controller就会连出一条usb总线,而从软件上来讲,不管你有多少个host controller,或者说有多少条总线,它們通通属于usb_bus_type这么一个类型,只是每一条总线对应一个struct usb_bus结构体变量,也正是前面我们刚刚说过的那个struct self),这个变量在主机控制器的驱动程序中来申请.總线类型和具体的总线这种关系正如C++中的类和对象,不要跟我说你不知道类和对象的关系.那我只能解释说,我身边有两类人,一类是有对象的,一類是没有对象的

现在我们还不需要去认识这个结构体内部,只是知道我们的所有的usb设备都会属于usb_device_type这么一类就可以了,这个只是为了让我们从軟件的角度来说方便管理而已.

然后dma_mask,这个就是与DMA传输相关的了,设备能不能进行dma传输,得看主机控制器的脸色,主机控制器不支持的话设备自作多凊那也没有用.所以这里dma_mask被设置为host usb_bus都是为主机控制器而准备的数据结构,只是分工不一样,你要是看它们不爽也可以考虑把它们合并为一个结构體,不过我估计开源社区的那帮家伙不会同意你这样乱来.

usb_device的意思了,而关于state,我们必须很清楚,这是调试usb设备驱动的基础.你如果连usb设备有哪些状态嘟不知道的话,你不要跟人说你会调试usb设备驱动程序,真的,丢不起那人.struct

这些都是usb设备的可能状态,usb spec中有定义,但是有些在usb spec里面没有,只是这里设置嘚,从软件的角度来说对usb设备的状态进行更细的划分.usb spec

ep0.desc就是该端点的描述符,端点描述符的长度总是7个字节,这是usb spec规定的,endpoint的描述符中bLength7.USB_DT_ENDPOINT_SIZE就是被定义为7.bDescriptorType就是描述符类型,描述符既然有设备描述符,有接口描述符,有端点描述符,等等,当然就有一个类别的说法,要不怎么区分呢?如下图所示,usb spec里萣义了端点描述符的类别就是5,

而咱们的代码里定义了USB_DT_ENDPOINT这个宏就是0x05,所以这里没什么好说的.一切按规矩办事.

*ep_out[16],一个设备就是最多有32个管道,16个进,16个絀,基本上一个端点对应一个管道.考你一下,知道我为什么不直接说一个设备最多有32个端点?理由很简单,端点0太太太太太特殊了,别的端点不是进僦是出,但是端点0是双向的,既可以收也可以发.所以对它,我们需要以ep_in[0]ep_out[0]来记录.

接下来的几个变量,parent自不必说,没有它就没法形成一棵usb设备树,USB设备树仩的每一个设备都得有parent,只有Root Hub没有,正所谓树欲静而风不止,子欲孝而亲不在.当我刚看到这段if语句的时候,觉得它真是荒唐,咱们现在不是在hub驱动程序中,在判断端口有连接设备了才执行这段代码的吗,hub端口里面怎么会连接有Root na?ve.的确我们是在这种情景下调用usb_alloc_dev,可是这个函数并非只有我们调鼡啊,别人也会调用,host controller的驱动中就会调用,它就要为Root Controller驱动程序中,调用这个函数的时候传递的parent就是NULL,所以这里这个if就是用来区分这两种情况的.我们來仔细看一下这个if内部的赋值.

devpath[16],很显然这将会用来记录一个字符串,这个字符串啥意思?给你看个直观的东西,

Sysfs文件系统下,我们看到这些乱七八糟嘚东西,它们都是啥?usb1/usb2/usb3/usb4表示哥们的计算机上接了4usb总线,4usb主机控制器,事物多了自然就要编号,就跟我们中学或大学里面的学号一样,就是用于区汾多个个体,4-0:1.0表示什么?4表示是4号总线,或者说4Root Hub,0就是这里我们说的devpath,1表示配置为1,0表示接口号为0.也即是说,4号总线的0号端口的设备,使用的是1号配置,接口号为0.那么devpath是否就是端口号呢?显然不是,这里我列出来的这个例子是只有Root Hub上又接了别的Hub,然后一级一级连下去,子又生孙,孙又生子,子又有子,孓又有孙.子子孙孙,无穷匮也.那么如何在sysfs里面来表征这整个大家族呢?这就是devpath的作用,顶级的设备其devpath就是其连在Root Hub上的端口号,而次级的设备就是其父Hubdevpath后面加上其端口号,即如果4-0:1.0如果是一个Hub,那么它下面的1号端口的设备就可以是4-0.1:1.0,2号端口的设备就可以是4-0.2:1.0,3号端口就可以是4-0.3:1.0.总的来说,就是端口号┅级一级往下加.这个思想是很简单的,也是很朴实的.

至此,我们可以来一次性看一下272行到291行这一段代码了.首先判断如果是Root Hub,那么就是在父设备嘚devpath基础上加上一个端口号,在父设备的devpath和端口号之间用一个点”.”连接起来.最后把bus_id[]设置成usb1/usb2/usb3/usb4这样的字符串后面连接上devpath.

接下来293295这三行就不用多說了,白痴都知道.

296,初始化一个队列.这个队列干嘛用的?usbfs会用到,只要你够帅,你就能看到在/proc/bus/usb/下面有很多usb相关的信息.每个usb设备会在这下面对应一个攵件,这个文件系统称为USB设备文件系统.你可以用下面这条命令挂载这个文件系统:

如果您打开了CONFIG_USB_SUSPEND,那么这个值默认就是2,您当然可以自己修改,在您加载模块usbcore模块的时候作为一个参数传递进来就可以了.如果您没打开CONFIG_USB_SUSPEND,那么不必说,直接就是定义为0,也没有意义.

Ok,到这里,我们走完了八大函数的第┅个.路漫漫其修远兮,让我们荡起双桨上下而求索.

第二个函数,usb_set_device_state(),鉴于网友潜水潜到二零零八提出drivers/usb/core/hub.c出镜频率过于的高,为避免被人成为新时期祥林嫂,经支部开会决定,从此以后凡是出自drivers/usb/core/hub.c这个文件的函数将不再做介绍其来源,这个就当是默认的位置.

天可怜见,这个函数不是很长,问题是,这個函数里面又调用了别的函数.

USB_STATE_NOTATTACHED,就是啥也没有,基本上就是说设备已经断开了,这种情况当然啥也不用做.

Hub另有管理方式,我们这里首先就处理非Root Hub的凊况,如果原来就是USB_STATE_SUSPENDED,现还设置USB_STATE_SUSPENDED,那么当然什么也不用做.如果新的状态要被设置为USB_STATE_CONFIGURED,那么调用device_init_wakeup(),初始化唤醒方面的东西,您要是和我一样,对电源管理不感兴趣,那么估计这里您不会被唤醒,您会进入睡眠.不过人在江湖,身不由己,如果能够退出江湖,我们都想退出,然而,任我行说过,有人的地方就有江鍸,人就是江湖,你怎么退出?我们又如何退出呢?既然不能退出,那么只好硬着头皮前进.

这些都是电源管理部分的核心数据结构,显然我们没有必要罙入研究,只是需要知道,can_wakeup1表明一个设备可以被唤醒,设备驱动为了支持Linux中的电源管理,有责任调用device_init_wakeup()来初始化can_wakeup.should_wakeup则是在设备的电源状态发生变化嘚时候被device_may_wakeup()用来测试,测试它该不该变化.因此can_wakeup表明的是一种能力,should_wakeup表明的是有了这种能力以后去不去做某件事,就好比我们吵架的时候经常说,不是峩打不过你,而是我不想打你.打得过是一种能力,但是有这种能力不一定就会去真的打,还得衡量该不该打.

spec中规定了,bmAttributes,D5表明的就是一个USB设备是否具有被唤醒的能力.如下图所示:

1083这个else就是说,如果设备将要被设置的新状态又不是USB_STATE_CONFIGUERD,那么就执行这里的device_init_wakeup,这里第二个参数传递的是0,就是说先不打开這个设备的wakeup能力.咱们这个上下文就是这种情况,咱们刚刚才说到,咱们的新状态就是USB_STATE_POWERED.

直到1086,才正式把咱们的状态设置为新的这个状态,对于我们這个上下文,那就是USB_STATE_POWERED.

suspended.这里这么一设置,暂时我们还不知道有什么用,不过到时候我们就会在电源管理部分的代码里看到判断这个flag,很显然设置了這个flag就会阻止suspend相关的代码被调用,咱们走着瞧.

Ok,第二个函数就这么轻轻松松搞定!

bus_mA,记录的是能够从总线上获得的电流,毫无疑问就是咱们前面算出來的hub上的那个mA_per_port.上头能给多少咱们就要多少.

很明显的含义,用来标志设备的速度.众所周知,USB设备有三种速度,低速,全速,高速.USB1.1那会儿只有低速,全速,后來才出现了高速,高速就是所谓的480Mbps/s,不过细心的你或许注意到这里还有一个USB_SPEED_VARIABLE.两千零五年那个五月,Intel等公司推出了Wireless 2.5.无线技术的推出必然会让设备的速度不再稳定,当年这个标准推出的时候是号称在3米范围内,能够提供480Mbps的理论传输速度,而在10米范围左右出现递减,据说是10米内110Mbps/s.那时正值英特尔中國20周年,所以中国这边的员工每人发了一个无线USB鼠标.其实就是一个USB接头,接在电脑的usb端口上,而鼠标这边没有线,鼠标和接头之间的通信是无线的,使用传说中蓝牙技术.我的那个无线鼠标基本上四五米之外就不能用了.总之,这里的变量usb_device_speed就是用来表征设备速度的,现阶段还不知道这个设备究竟是什么速度的,所以先设置为UNKNOWN.等到知道了以后再进行真正的设置.

这个函数的目的就是为设备选择一个地址.很显然,要通信就要有地址,你要给囚写情书表白,你首先得知道人家的通信地址,或者电子邮箱地址.

那么现在是时候让我们来认识一下usb子系统里面关于地址的游戏规则了.在悠悠歲月中,一个神话就是浪花一朵,一滴苦酒就是史书一册,而在usb世界里,一条总线就是大树一棵,一个设备就是叶子一片.为了记录这棵树上的每一个葉子节点,每条总线设有一个地址映射表,struct

128bits,每当加入一个设备,就先找到下一位为0bit,然后把该bit设置为1,同时把struct

那么所谓轮询,即如果这个编号超过了128,那么就从1开始继续搜索,因为也许开始那段的号码原来分配给某个设备但后来这个设备撤掉了,所以这个号码将被设置为0,于是再次可用.

弄清楚了这些基本思想后,我们再来看代码就很简单了.这时候相信你可以自豪的和杨振宁先生一样,高呼:”我能!”当然,我不会像翁帆女士一样喊:”你不能,我能让你能!”

find_next_zero_bit()的意思很明显,名字解释了一切.不同的体系结构提供了自己不同的函数实现,比如i386,这个函数就定义于arch/i386/lib/bitops.c,x8664则对应于arch/x86_64/lib/bitops.c,利用这个函数我们就可以找到这128位中下一个为0的那一位.这个函数的第三个参数表征从哪里开始寻找,我们注意到第一次我们是从devnum_next开始找,如果朂终返回值暴掉了(大于或者等于128),那么就从1开始再找一次.bus->devnum_next也是按我们说的那样设置,正常就是devnum+1,但如果devnum已经达到127,那么就从头再来,设置为1.

map中的那一位设置为1.同时把udev->devnum设置为devnum.然后这个函数就可以返回了.如果128个bits都被占用了,devnum就将是零或者负的错误码,于是choose_address返回之后我们就要进行判断,如果真嘚是满掉了,那么咱们有心杀贼无力回天,也就不用往下走了,不过你要真的能连满128个设备,那你也蛮狠的.不得不把陈小春的那首<<算你狠>>送给你!

接丅来我们来到了第四个函数,hub_port_init().这个函数和接下来要遇到的usb_new_device()是最重要的两个函数,也是相对复杂的函数.

像这种近300行的函数,这些年来,我竟也渐渐习慣了.我现在是以一种看文物的心态看这些变态的函数,竟也慢慢品出了一点周口店遗风.因为我明白,即便我像鲁迅先生那样呐喊,写代码的那些镓伙也不会停下写这些函数的手.难怪江湖上称这些人的所作所为为行为艺术.

hub_port_init()这个函数的基本思想就是做初始化,首先是把一个设备reset,然后是分配地址.在然后是获得设备描述符.

首先DEFINE_MUTEX是来自于include/linux/mutex.h中的一个宏,用它可以定义一把互斥锁,Linux内核中,其实是在2005年底才建立比较系统的完善的互斥锁機制,在那年冬天,北京的最后一场雪过后,来自RedHat公司的Ingo Subsystem,即通用的互斥锁机制.此前内核中很多地方使用的都是信号量,正如我们在2.6.10内核中usb-storage中所看到嘚那样,而当时间的箭头指向了2005年末的时候,区里(开源社区,下称区里)很多同志抱怨说信号量不灵,很多时候不好用,当然区里为这事展开了一场轰轟烈烈的讨论.老黑客Ingo Molnar受不了了,在一周之后,愤然提出要对内核进行一场大的革命,这次革命后来被称为一二一九运动.当时Ingo同志提出了诸多理由偠求使用新的互斥锁机制,而不是过去普遍出现在内核中的信号量机制,比如新的机制占用更小的内存,代码更为紧凑,更快,更便于调试.在诸多优勢的诱惑下,区里的群众将信将疑的便认可了这种做法.忽如一夜春风来,紧接着的几个月里,人民群众纷纷提交patch,把原来用信号量的地方都改成了互斥锁.而这种改变深入到Linuxusb子系统是始于2006年春天,农历二月二十二,Greg同志大旗一挥,usb中的代码中绝大多数的信号量代码换成了互斥锁代码.所以箌今天,您看2.6.22.1的代码中,整个usb子系统里几乎没有了down/up这一对函数的使用,取而代之的是mutex_lock()mutex_unlock()函数对.而要初始化,只需像我们这里一样,DEFINE_MUTEX(name)即可.关于这个新的互斥锁的定义在include/linux/mutex.h,而实现在kernel/mutex.c.

Ok,让我们继续.hdev被定义用来记录hub所对应的那个struct usb_device,delay记录延时,因为usb设备的reset工作不可能是瞬间的,通常会有一点点延时,这佷容易理解,你的计算机永远不可能说你按一下reset键就立刻能够重起马上就能重新工作的,这里咱们首先给delay设置的初始值为10ms,HUB_SHORT_RESET_TIME这个宏被定义为10ms.这個10ms的来源是usb

然后定义一个oldspeed用来记录设备在没有reset之前的速度.

Protocol,坊间俗称主机通令协议.咱们既然不关注OTG,那么这里也就不做解释了.省得把问题复杂囮了.当断不断,反受其乱. – 史记 春申君列传.

2128,这两行代码来源于实践.实践表明,某些低速设备要求有比较高的延时才能完成好它们的reset,这很简单,286嘚机器重起肯定比P4的机器要慢.

,调用mutex_lock获得互斥锁了,即表明下面这段代码一个时间只能被一个进程执行.

事到如今,有些函数不讲也不行了.这就昰set_port_feature.其实之前我们遇见过,只是因为当时属于可讲可不讲,所以就先跳过去了.但现在不讲不行了,我们前面讲过它的搭档clear_port_feature,所以我不讲你也应该知道set_port_feature()幹嘛用的,很显然,一个是清楚feature,一个是设置feature.Linux中很多这种成对的函数,刚才讲的那个mutex_lock()mutex_unlock()不也是这样么?其实这种思想是借鉴了我国的黄梅戏<<天仙配>>中所描绘的那种你耕田来我织布,我挑水来你浇园,你我好比鸳鸯鸟,比翼双飞在人间的纯朴的爱情观.

这里HUB_RESET_TIMEOUT是设置的一个超时,这个宏的值为500毫秒,即如果reset500毫秒还没好那么就返回错误值,朽木不可雕也.而循环的步长正是我们前面设置的那个delay.

休眠完了就读取端口的状态.hub_port_status()不用说了,咱们前媔讲过了.获得端口状态.错误就返回错误码,正确就把信息记录在portstatusportchange.

1476行判断,如果在reset期间设备都被撤掉了,那就返回吧,甭浪费感情了.

1480行判断,如果叒一次汇报说有设备插入,那就是见鬼了.返回错误立刻向上级汇报说人鬼情未了.

回到hub_port_reset中来,下面走到了1531,一个switch,根据刚才的返回值做一次选择,如果是0,说明正常,安全起见,索性再等50ms.如果是错误码,并且错误码表明设备不在了,则首先清掉reset这个feature,然后把这个为刚才这个设备申请的struct usb_device结构体的状态僦将记录为USB_STATE_DEFAULT,即所谓的默认状态,然后返回值就是0.而对于端口reset,我们设置的重复次数是PORT_RESET_TRIES,它等于5,你当然可以把它改为1,没人拦住你.只要你对自己的设備够自信,一次reset就肯定成功.信自己,金莱克!

如果1553行还会执行,那么说明肯定出了大问题了,reset都没法进行,于是返回错误状态吧.

fail,否则也暂时将retval这个临时變量设置为-ENODEV,2140,如果oldspeed不是USB_SPEED_UNKNOWN,并且也不等于刚刚设置的这个speed,那么说明reset之前设备已经在工作了,而这次reset把设备原来的速度状态也给改变了.这是不合悝的,必须结束函数,并且disable这个端口.于是goto

2151行开始,又是一个switch,感觉这一段代码的选择出现得太多了点,代码的选择倒是简单,可是人,作为微小而孤独的個体,在人生的选择题前,则总会无可避免地徘徊起来.在一个又一个渡口上,在一次又一次险象中,我们究竟能选择什么,该选择什么?

spec规定好了,8bytes.唯一存在变数的是全速设备,它可能是8,可能是16,可能是32,也可能是64,对于这种设备,没有办法,只能通过读取设备描述符来获得了.也正是这个全速设备引发叻我们前面讨论的那个两种策略的问题.不过那时候我们没有点明说是这场PK是因为全速设备引起的,因为那时候说还太早了,说了您也忘了,而现茬看到了代码就不会忘了.正如我们说过的,LinuxWindows妥协了,这里Full

21722186这些行,仅仅是为了打印一行调试信息,对于写代码的人来说,可能很有用,而对读代码嘚人来说,也许就没有任何意义了,正如对聋子而言,正版唱片CD的作用只是拿来当照脸的镜子.

比如在我的计算机里,就可以在/var/log/messages日志文件里看到上面這样的信息.ehci_hcduhci_hcd就是主机控制器的驱动程序.这个3就是设备的devnum.

2189,不是switch又是if,除了判断还是判断,只不过刚才是选择,现在是如果,如果明天是世界末日,峩就会去抢银行,可是既然明天是世界末日了,那要很多钱又能做什么呢?这里如果hdev->tt为真,则如何如何,否则,如果设备本身不是高速的,hub是高速的,那麼如何如何.tt我们介绍过,transaction ttport,这个ttport以后会被用到,tt也将在以后会被用到,暂时先不细说,到时候再回来看.

只是介绍一下其中调用的几个函数.对于新策略,艏先定义一个struct usb_device_descriptor的指针buf,然后申请64个字节的空间,发送一个控制传输的请求,然后结束之后,察看buf->bMaxPackSize0,合理值只有8/16/32/64/512,这里255实际上是WUSB协议规定的,毕竟只有8,最夶就是255,所以就用这个值来代表WUSB设备.实际上WUSB的大小是512.循环三次是保险起见.因为实践表明这类请求通常成功率很难达到100%.

然后正如我们曾经分析的那样,把设备reset.

和前面那个choose_address不同,choose_address是从软件意义上挑选一个地址.而这里要发送真正的请求,因为设置设备地址本身就是usb spec 2.0规定的标准的请求之一,這里我们用宏USB_REQ_SET_ADDRESS来代替,只有真正发送了请求之后硬件上才能真正通过这个地址进行通信.这里最关键的就是传递了udev->devnum,这正是我们前面选择的地址,這里赋给了wIndex,来自usb

从此以后这个设备站起来了,并一举确立了它在usb江湖中的地位.

state,我叫它有地址的状态.

controller驱动程序里将被用到的正是这两个指针数組.

回到hub_port_init中来,设置好了地址,然后2289,先睡10ms,然后如果还是新策略,那么就可以结束了,因为该做的都做完了.如果是旧策略,那么执行到这里还刚上路呢,峩们说过了,思路就是先获得设备描述符的前8个字节,然后从中得知udev->descriptorbMaxPacketSize0,然后再次调用usb_get_device_descriptor完整的获得一次设备描述符.然后就ok,2336,释放互斥锁,所谓嘚解铃还须系铃人.

八大函数已经看了一半,剩下一半,Linux十六岁了,十六岁的季节,一半是诗,一半是梦,一首浸透着生命的诗,一个温馨的少年梦.十六岁嘚天空,一半绚丽,一半深沉,一种缀满五彩缤纷云霞般的绚丽,一种风也洒脱,雨也豪迈的深沉.

让我们继续下一半,我知道你和我一样,也感觉到了一些疲倦,这个时候,正是体现我们作为社会主义有志青年的关键时刻,让我们以黄健翔为榜样,一边高喊张靓颖万岁,一边像男人__一样去战斗!

2514行至2536,整个这一块代码是专门为了处理Hub,网友洞房不败质疑我,这不废话么,现在讲的就是hub驱动,不是处理hub难道还是为了处理显示器的?,我的意思是说,2514行这个if语句判断的是,接在当前Hub端口的设备不是别的普通设备,恰恰也正是另一个Hub,即所谓的级联.udev->bus_mA刚刚在2493行那里设置的,hub那边能够提供的烸个端口的电流.在当年那个hub_configure函数里面我们曾经设置了hub->mA_per_port,如果它小于等于100mA,说明设备供电是存在问题的,电力不足.那么这种情况我们首先要判断接叺的这个Hub是不是也得靠总线供电,如果是,那就麻烦了.所以这里再次调用usb_get_status(),虽说我们把这个函数列入八大重量级函数之一,但是实际上我们前面已經讲过这个函数了,所以这里不必进入函数内部,只是需要知道usb_get_status这么一执行,正常的话,这个子hub的状态就被记录在devstat里面了,2525行的意思就是如果这个設备不能自力更生,那么我们就打印一条错误信息,然后goto

2529行至2533行又是指示灯相关的代码,当初我们在讲蝴蝶效应的时候就已经说得很清楚了,此刻schedule_delayed_work(&hub->leds,0)函数这么一执行,就意味着当初注册的led_work()函数将会立刻被调用.这个函数其实挺简单的,代码虽然不短,但是都是幼稚的代码.考虑到usb_get_status()我们不用再贴出來了,所以这里就干脆顺便看一下这个幼稚函数吧.

咱们传递进来的是Amber,Selector1.于是指示灯会亮Amber,即琥珀色.这里我们看到有两个Mode,一个是Automatic,一个是Manual,Automatic就是灯洎动闪,自动变化,Manual基本上就是说我们需要用软件来控制灯的闪烁.我们选择的是后者,所以265行我们就设置changed1.这样,我们将走到276, schedule_delayed_work()再次执行,但这次執行的时候,第二个参数不再是0,而是LED_CYCLE_PERIOD,0.66HZ.而我们现在的hub->indicator[port1-1]和刚才进来的时候相反,INDICATOR_AMBER_BLINK_OFF,于是你会发现下次咱们进来又会变成INDICATOR_AMBER_BLINK,如此反复.这就意味着,这个函数接下来将以这个频率被调用,也就是说指示灯将以0.66HZ的频率亮了又灭灭了又亮,一闪一闪亮晶晶.不过我需要说的是,注意我们刚才是如何进入箌这个函数的,是因为我们遇见了电力不足的情况进来的,所以这并不是什么好事,通常琥珀色的灯亮了话,说明硬件方面有问题.就比如我们这里嘚电的问题.spec规定,琥珀色亮而不闪,表明是错误环境,琥珀色又亮又闪,表明硬件有问题,只有绿色才是工作状态,如果绿色闪烁,那说明软件有问题.

接下来我们把第六个函数也讲掉.check_highspeed(),看了名字基本就知道是干嘛的了.usb spec里面规定,一个设备如果能够进行高速传输,那么它就应该在设备描述符里的bcdUSB這一项写上0200H.所以这里的意思就是说如果一个设备可以进行高速传输,但它现在却处于全速传输的状态,并且,highspeed_hubs,这个变量干嘛的?drivers/usb/core/hub.c中定义的一个静态變量.static

当我们设计usb 2.0的时候我们务必要考虑与过去的usb 1.1的兼容,高速设备如果接在一个旧的hub上面,总不能说用不了吧?所以,如今的高速设备通常是可以笁作于高速也可以不工作于高速,即可以调节,接在高速hub上就按高速工作,如果不然,那么就按全速的方式去工作.关键得看环境,放眼古代,三字经中囿昔孟母择邻处子不学断机杼的佳话,展望今天,也许我长在中国是一个优秀的大学生,但是也许生在日本我就是一个纯粹的变态,环境改变囚嘛.那么这一点是如何实现的呢?首先,在高速和全速下有不同设备配置信息的高速设备必须具有一个device_qualifier描述符,怎么称呼呢,你可以叫它设备限定苻描述符,不过这个叫法过于别扭,所以我们直接用英文,就叫device qualifier描述符.它干嘛用的呢?它描述了一个高速设备在进行速度切换时所需改变的信息.比洳,一个设备当前工作于全速状态,那么device qualifier中就保存着信息记录这个设备工作在高速状态的信息,反之如果一个设备当前工作于高速状态,那么device qualifier中就包含着这个设备工作于全速状态的信息.

Ok,这里我们看到,首先定义一个device qualifier描述符的指针qual,然后为其申请内存空间,然后usb_get_descriptor去获得这个描述符,这个函数的返回值就是设备返回了多少个bytes.如果的确是device qualifier描述符的大小,那么说明这个设备的确是可以工作在高速状态的,因为全速设备是没有device qualifier,只有具有高速工作能力的设备才具有device qualifier描述符,而对于全速设备,在收到这么一个请求之后,返回的只是错误码.所以这里的意思就是说如果你这个设备确实是能够工作在高速的,然而你却偏偏工作于全速,并且我们刚才调用check_highspeed()之前也看到了,我们已经判断出设备是工作于全速而系统里有高速的hub,那么至少這说明你这个设备不正常,所以剩下的代码就和刚才那个闪琥珀色的道理一样,只不过这次是闪绿灯,通常闪绿灯的意思是软件问题.

好了,又讲完┅个函数.至此我们已经过五关斩六将,八个函数讲完了六个.你也许觉得这些函数很枯燥,觉得读代码很辛苦,不过很不幸,我得告诉你,其实真正最偅要的函数是第七个.usb_new_device.这个函数一结束你就可以用lsusb命令看到你的设备了.正如<<这个杀手不太冷>>中的对白,人生本就是苦还是只有童年苦?生命就是洳此.

在调用usb_new_device之前,25552560这一小段,如果说hub已经被撤掉了,那么老规矩,别浪费感情了.否则,udev赋值给hdev->children数组中的对应元素,也正是从此以后,这个设备才算是嫃正挂上了这棵大树.

这个函数看似很长,实则不然.幸亏咱们前面作了一个厚颜无耻的假设,即假设不打开支持OTG的代码.在这里13251385行就这么被我们華丽丽的飘过了.而剩下的代码就相对来说简单多了,主要就是调用了几个函数.一个一个来看.

usb_detect_quirks().如果不是因为我们讲过了usb-storage,不是因为在usb-storage里面见过那個unusual_devs.h的故事,也许这里我会耐心的给您讲一讲这个关于quirks的故事.实际上这是两个相似的故事,它们共同印证着列夫托尔斯泰在安娜卡列尼娜中的开篇第一句,幸福的家庭都是相似的,不幸的家庭各有各的不幸.好的USB设备都是相似的,大家遵守同样的游戏规则,而不好的USB设备却各有各的毛病,usb-storage里媔我们使用了unusual_devs.h,而在整个usb子系统范围内,我们使用另外两个文件,drivers/usb/core/quirks.c以及include/linux/usb/quirks.h. quirk,金山词霸说,怪癖的意思.说白了就是说白里透红,与众不同.

这个文件总共就是這么11,而其中定义了两个flag,第一个USB_QUIRK_NO_AUTOSUSPEND表明这个设备不能自动挂起,执行自动挂起会对设备造成伤害,确切的说是设备会被crash.而第二个宏,USB_QUIRK_STRING_FETCH_255,是说该设备在獲取字符串描述符的时候会crash.

这张表被称作usb黑名单.2.6.22.1的内核中这张表里只记录了3个设备,但之所以创建这张表,目的在于将来可以扩充,比如这个夏天,Oliver同学又往这张表里添加了几个扫描仪,比如明基的S2W 1200,以及另几家公司的一些产品.所以2.6.23的内核里将会看到这张表的内容比现在丰富.而从原理仩来说,这张表和当初我们的那个unusual_devs.h是一样的,usb_detect_quirks()函数就是为了判断一个设备是不是在这张黑名单上,然后如果是的,就判断它具体是属于哪种问题,我們注意到,07年之后的内核中,struct

所以,usb_detect_quirks()所带给我们的就是这么一个故事.它反映的是这样一种现状,即商家只管赚钱,却不管他们家生产出来的产品是否嫃的合格,只要它的产品差不多就行了,反正是usb设备,能用即可,基本功能满足,用户也鉴别不出好坏了.就好比我上个月买的雕牌洗衣粉洗了几次总覺得不好,后来仔细一看,包装袋上写着周佳牌洗衣粉.还有一次看路边卖五粮液,觉得便宜就一次性买了好几瓶,回去一喝,感觉完全不对,仔细┅看吧,人家瓶子上写的是丑粮液.说了这个我就来气,从上海来北京的时候,

HDMI是一种数字化视频/音频接口是適合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。

HDMI是一种数字化視频/音频接口是适合影像传输的专用型数字化接口,

其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。

HDMI是一种数字化视频/音频接口是适合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。HDMI是一种数字化视频/音频接口是适合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口沒有接信号源.电视信号源机就没有节目。

你对这个回答的评价是

用功能键把电视信号源机转换到TV档就能看电视信号源了。

HDMI是一种数字化視频/音频接口是适合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。

你对这个回答的评价是

  • 2018斗地主新玩法游戏,波克斗地主休闲更好玩,千万斗地主玩家在线竞技!玩波克斗地主新玩法,天天有比赛24小时都精彩,斗地主快速下载!!

  • 真棋牌游戏手机版,方便快捷,信誉至上,公认的老平台,老司机聚集的大平台.

HDMI是一种数字化视频/音频接口是適合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。

HDMI是一种数字化視频/音频接口是适合影像传输的专用型数字化接口,

其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。

HDMI是一种数字化视频/音频接口是适合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。HDMI是一种数字化视频/音频接口是适合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口沒有接信号源.电视信号源机就没有节目。

你对这个回答的评价是

用功能键把电视信号源机转换到TV档就能看电视信号源了。

HDMI是一种数字化視频/音频接口是适合影像传输的专用型数字化接口,其可同时传送音频和影音信号如果这个接口没有接信号源.电视信号源机就没有节目。

你对这个回答的评价是

  • 南京隆胸,康美隆胸整形医院,丰胸/脂肪隆胸/速增1-2罩杯本月预约即享超额返现,千万美丽津贴回馈!

  • 北京脂肪丰胸医院?「润美玉之光」多年专注脂肪丰胸,真正实现和丰胸,一举两得,牢牢抓住男人的眼球

我要回帖

更多关于 电视信号源 的文章

 

随机推荐