首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Linux那些事儿之我是Hub(23)是月亮惹的祸还是spec的错?

Linux那些事儿之我是Hub(23)是月亮惹的祸还是spec的错?

来北京九个月了,和同学去五道口的光合作用酒吧玩了一夜,其间和两个乌拉圭的女留学生搭讪,其间学会了吸水烟,其间和我一个北邮的同学聊到生活的艰难,他说,人们为了求生而来到大都市,但是依我看,他们是为了求死而来.出来之后,独自叹息,工作两年,没有省下一分钱,生活压力大得不得了,买房买车这些事看起来和我简直有一万光年的距离.而你不得不承认,这种距离,就像稀盐酸,腐蚀的是我的人生观,腐蚀的是我对生活的信心.可恶的是那些专家还说,买不起房子可以租房子,那么我想问,我娶不起媳妇,可不可以也租一个来?怎么没见哪个专家提出愿意出租他的女儿来给我做一个月媳妇呢?难怪大家都说,杀掉一批专家有利于更好的建设和谐社会.
想到这些烦心事心里就郁闷,在站台等375路公交车回家,一边等一边看了看代码,九月的天气,下起大雨,淋湿我的思绪,可笑的是,却没能遮住我发现Bug的双眼.雨后的花瓣,散落一地,把它做成书签,藏在日记,记录下这个Linux 内核中的Bug.
让我们用代码来说话.走在hub_events()的小路上,2777,hub->event_bits给清掉,然后读一次hub的状态,HUB_STATUS_LOCAL_POWER我们以前在hub_configure中见过,用来标志这个hub是有专门的外接电源的还是从usb总线上获取电源,C_HUB_LOACL_POWER用来标志这一位有变化,这种情况下,先把C_HUB_LOCAL_POWER清掉,同时判断,如果是原来没有电源现在有了电源,那么可以取消limited_power,把它设置为0,如果是反之,原来是有电源的,而现在没了,那么没什么说的,limited_power设置为1.
这种解释,用孙俪的新歌<<爱如空气>>中的那句歌词来说,看似很美丽.但是,也正如孙俪唱的那样,看似很美丽,却无法触及,因为真理和错误有时候只有一步之遥.我们来对照一下usb spec.如下图所示:


看出问题来了吗?代码和我的解释刚好相反.代码的意思是HUB_STATUS_LOCAL_POWER1,就设置limited_power0,反之则设置limited_power1.你说你应该相信代码还是应该相信我?网友硬把红杏拽出墙提醒大家,<<偷天陷阱>>里有一句话,不要相信漂亮女人,尤其是不穿衣服的裸体女人.不过还好,我四处张望了以后发现,这里没有女人.但我必须郑重声明,我要像新闻联播里声明北京电视台的纸包子报道是假新闻一样郑重声明:我从不说假话.这句除外.
让我们多说两句,众所周知,一个hub可以用两种供电方式,一种是自带电源,hub上面自己有根电源线,插到插座上,ok.另一种是没有自带电源,由总线来供电.具体这个hub是使用的哪种方式供电,就是从这个状态位里面可以读出来,即上面这个Local power source,Spec的意思是,Local Power Source如果为0,表明本地供电正常,即说明是自带电源,如果Local Power Source1,则说明本地供电挂了,或者根本就没有本地供电.而我们Hub设备驱动中之所以引入一个叫做limited_power的变量,就是为了记录这一现象,如果你这个Hub有自己的电源,那么你就可以为所欲为,可以无法无天,因为你有后台撑腰,你有Power,但是如果你是一个普通老百姓,你要依赖政府,你要依靠总线来给你解决电源问题,那么驱动程序就要记录下来,因为总的资源是有限的,你占用了这么多,分给别人的就少了这么多,所以这种情况下设置limited_power1,也算是记下这么一件事.
所以说,正确的赋值应该是HUB_STATUS_LOCAL_POWER1,设置limited_power1,HUB_STATUS_LOCAL_POWER0,设置limited_power0.所以这就是传说中的Bug.有趣的是这个Bug2005年平安夜由三剑客之一的Alan提出,并于2006年一月由Greg正式引入Linux内核,在连续几个稳定版的内核中隐藏了近两年,终于在这个早上,就在我在酒吧一夜未曾合眼然后出来坐上375路公共汽车之后发现了.你说我容易么?
不过我想问,这究竟是月亮惹的祸还是spec的错?其实spec算不上错,但是spec中这种定义是不合理的,它这一位就不该叫做Local Power Source,因为这样一叫别人就会误以为这位为1的时候表示有电源,0表示没有电源,所以才导致了这个Bug,更合理的叫法应该是叫Local Power Lost. 现在好了,既然被我发现了,那么2.6.23的正式版内核里不会有这个Bug.不过你别以为这样的Bug很幼稚,Alan同志说过,对一个人显然的事情未必会对另一个人显然,很多Bug都存在这样的情况,当你事后去看的话,你会觉得它很不可思议,太低级了,但是有时候低级的错误你却未必能够在短时间内发现.
Ok,我们继续看代码,2791,对于有过流的改变也是同样的处理,因为过流可能导致端口关闭,所以重新给它上电.hub_power_on().其实这个函数我们以前见过,只是当时出于情节考虑,先飘过了,现在我们对hub有了这么多认识了之后再来看这个函数就好比一个大学生去看小学数学题一样简单.
    475 static void hub_power_on(struct usb_hub *hub)
    476 {
    477         int port1;
    478         unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2;
    479         u16 wHubCharacteristics =
    480                         le16_to_cpu(hub->descriptor->wHubCharacteristics);
    481
    482         /* Enable power on each port.  Some hubs have reserved values
    483          * of LPSM (> 2) in their descriptors, even though they are
    484          * USB 2.0 hubs.  Some hubs do not implement port-power switching
    485          * but only emulate it.  In all cases, the ports won't work
    486          * unless we send these messages to the hub.
    487          */
    488         if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2)
    489                 dev_dbg(hub->intfdev, "enabling power on all ports/n");
    490         else
    491                 dev_dbg(hub->intfdev, "trying to enable port power on "
    492                                 "non-switchable hub/n");
    493         for (port1 = 1; port1 <= hub->descriptor->bNbrPorts; port1++)
    494                 set_port_feature(hub->hdev, port1, USB_PORT_FEAT_POWER);
    495
    496         /* Wait at least 100 msec for power to become stable */
    497         msleep(max(pgood_delay, (unsigned) 100));
    498 }
关键的代码就是一行,494,set_port_feature,USB_PORT_FEAT_POWER,这一位如果为0,表示该端口处于Powered-off状态.同样,任何事情引发该端口进入Powered-off状态的话都会使得这一位为0.set_port_feature就会把这一位设置为1,这叫做使得端口power on.关于对HUB_CHAR_LPSM的判断,本来是没有必要的,这里又一次出了怪事了,关于这件事情的解释完全在482487这段注释里面,我就不用中文重复了,总之最终的做法就是对每个端口都执行一次set_port_feature.然后最后497,睡眠,经验值是100ms,Hub描述符里有一位bPwrOn2PwrGood,全称就是b-Power On to Power Good,即从打开电源到电源稳定的时间,显然我们应该在电源稳定了之后再去访问每一个端口所以这里睡眠时间就取这两个中的较大的那一个.
2799,设置hub->activating0,也就是说以上这一段被称为activating,而这个变量本身,就是一个标志而已.别忘了我们是从hub_actiavte调用kick_khubd()从而进入到这个hub_events().而在hub_activate()中我们设置了hub->activating1.而那个函数也是唯一一个设置这个变量为1的地方.
2803,如果是Root Hub,并且hub->busy_bits[0]0,hub->busy_bits只有在一个端口为reset或者resume的时候才会被设置成1.我们暂时先不管.对于这种情况,即既是Root Hub,又没有端口处于reset/resume状态,调用usb_enable_root_hub_irq()函数,这个函数来自drivers/usb/core/hcd.c,host controller driver相关的,有些host controller的驱动程序提供了一个叫做hub_irq_enable的函数,这里就会去调用它,不过目前主流的ehci/uhci/ohci都没有提供这个函数,所以你可以认为这个函数什么也没干.这个函数的作用正如它的名字解释的那样,开启端口中断.关于这个函数,涉及到一些比较专业性的东西,有两种中断的方式,边缘触发和电平触发.只有电平触发的中断才需要这个函数,边缘触发的中断不需要这个函数.算了,不跟你多说了,想你也不感兴趣.
2808,判断,如果hubevent_list没有东西了,那么就调用usb_autopm_enable(),调用了这个函数这个hub就可以被挂起了,强调一下,可以做某事不等于马上就做某事了,真的被挂起是有条件的,首先它的子设备必须先挂起了,而且确实一段时间内没有总线活动了,才会被挂起.
最后,释放hdev的锁,减少intf的引用计数,至此,hub_events()这个永垂不朽的函数就算结束了!对于大部分人来说,特别是对于那些不求甚解的同志们来说,你们需要学习的hub驱动就算学完了.因为你已经完全明白了hub驱动在设备插入之后会做一些什么事情,会如何为设备服务,并最终把控制权交给设备驱动.hub_thread()/hub_events()将永远这么循环下去.
不过我的故事可没有结束,最起码还有一个重要的函数没有讲.hub_irq.之前我们这里的故事都是基于一个事实就是我们主动去读了hub端口的状态,而以后正常工作的hub驱动是不会莫名其妙就去读的,只有发生了中断才会去读.而这个中断的服务函数就是hub_irq,也即是说,凡是真正的有端口变化事件发生,hub_irq就会被调用,hub_irq()最终会调用kick_khubd(),触发hubevent_list,于是再次调用hub_events().
下节我们会讲hub_irq().然后还会讲剩下的一些函数,这其中最关键的就是电源管理部分的代码.因为电源管理是眼下Linux内核中最热门的话题之一.usb中实现电源也是最近两年才开始的,并且经常有Bug,所以,我们有必要,我们很有必要,我们非常有必要来看看这部分代码.网友"有钱人终成眷属"问我,有Bug为什么还要看啊?我想,男人,最起码应该有点责任心,我们对国家,对集体,对家庭,都应该有点责任心,要有使命感,不要见困难就让,见容易就上.这方面我做得就不错,现在台海局势不太好,我就想好了,万一哪天攻打台湾,我就会勇敢的上前线,我听说,台湾那边最危险的人物叫林志玲,所以,请组织把林志玲交给我,我保证完成任务!
返回列表