Linux那些事儿之我是Hub(11)While You Were Sleeping(四) – fuda

我们说过,hub里面的中断端点是IN的,不是OUT的.但这并不说明凡是中断传输数据一定是从设备到主机,没这种说法,别起哄.不过hub需要的确实只是IN的传输.首先,每一个男人都应该知道,中断是由设备产生的.在usb的世界里两个重要角色,主机,设备.主机就像一个人民公仆,设备就像人民群众,公仆常常日理万机,或者日理万鸡,他也许不会去理会每一个子民的水深火热,群众如果要想引起公仆的注意,只能做一些有创意的事情,他们知道,像许茹芸唱的那样,”没有星星的夜里,我用泪光吸引你”,其实是没有意义的,他们知道只有一些诸如山西黑砖窑事件,如无锡绿藻泛滥事件,如九江大桥坍塌事件,如唐山的黑军车事件,如安徽的粽子事件,才是有意义的,一次事件就可以引发公仆的一次中断,会引发一些事情(数据传输).

那么什么是interval呢?interval就是间隔期,啥叫间隔期?首先你要明白,中断传输绝对不是一种周期性的传输,你说黑砖窑事件会不会一个月来一次?绿藻泛滥事件会不会?黑军车事件会不会?肯定不会对不对.真要是每个月总有几天发生这些事件,那大家都别活了,也甭买安尔乐护舒宝了.那为什么还要有一个间隔期呢?实际上是这样的,尽管中断本身不会定期发生,但是有一个事情是周期性的,对于IN的中断端点,主机会定期向设备问寒问暖,就好比一个父母官定期微服私访,去民间了解民情,那么如果人民生活很艰苦,饱受通货膨胀之苦,买不起房子,人民哭爹叫娘的,父母官就会知道,然后就会进行处理,会想办法去拯救万民于水火之中,会严惩万恶的房地产商.只要每一个当官的都能这样做,何愁天下不太平,人民不安居不乐业.

那么具体来讲,在usb的世界里,体现的是一种设计者们美好的愿望.就是说,设计者们眼看着现实世界中的公仆们都很让人失望,所以在设计spec的时候是这么规定的,host必须要体恤民情,而且,很重要的一点,人民可以提出自己的愿望,比如你希望父母官多久来探望你一次,一个端点的端点描述符里bInterval这么一项写的很清楚,就是她渴望的总线访问周期,或者说她期望host隔多久就能看望一下她.设备要进行中断传输,需要提交一个urb,里面注明这个探访周期,这算是一种民意,而host答应不答应呢,这个在host controller的驱动程序里才知道,我们不管,host当然有一个标准,中断传输可以用在低速/全速/高速设备中,而高速传输可以接受在每个微帧有多达80%的时间是进行定期传输,(包括中断传输和等时传输),而全速/低速传输则可以接受每个帧最多有90%的时间来进行定期传输.所以呢,host那边会统一来安排,他会听取每一个群众(设备)的意见或者说愿望,然后他来统一安排,日程安排得过来就给安排,安排不过来那么就返回错误.这些都是host controller驱动做的,暂且不表.那么如果说host同意,或者说host controller接受了群众的意见,比如你告诉他你希望他每周来看你一次,那好,他每周来一次,来问候你一下,问你有没有什么困难(中断).

从技术的角度来讲,就是,host controller定期给你发送一个IN token,就是说发送这么一个包, 而你如果有中断等在那里,那你就告诉她,你有了(中断).同时你会把与这个中断相关的数据发送给主机,这就是中断传输的数据阶段,显然,这就是IN方向的传输.然后主机接收到数据之后他会发送回来一个包,向你确认一下.那么如果host controller发送给你一个IN token的时候,你没有中断,那怎么办呢?你还是要回应一声,说你没有.当然还有另一种情况是,你可能人不在家,你出差去了,那么你可以在家留个条,告诉人家你没法回答.以上三种情况,对应专业术语来说就是,第一种,你回应的是DATA,主机回应的是ACK或者是Error.第二种,你回应的是NAK,第三种,你回应的是STALL.咱们别玩深沉的,没必要说这些专业术语.就这么说吧,用一段电话对白来解释中断传输的过程中,设备需要做些什么:

对话一: 问:复旦有处女吗? 答:有.在女生肚子里. (DATA)

对话二: 问:复旦有处女吗? 答:没有. (NAK)

对话三: 问:复旦有处女吗? 答:对不起,您所呼叫的用户不在服务区,暂时无法接通. (STALL)

三种情况,你都会有回应,但是意义明显不同.

顺便解释一下OUT类型的中断端点是如何进行数据传输,虽然Hub里根本就没有这种款式的端点.分三步走,第一步,host发送一个OUT token包,然后第二步就直接发送数据包,第三步,设备回应,也许回应ACK,表示成功接收到了数据,也许回应NAK,表示失败了,也许回应STALL,表示设备端点本身有问题,传输没法进行.

Ok,现在可以回到刚才那个interval的问题来了,因为不同速度的interval的单位不一样,所以同样一个数字表达的意思也不一样,那么对于高速设备来说,比如它的端点的bInterval的值为n,那么这表示它渴望的周期是2的(n-1)次方个微帧,比如n为4,那么就表示2的3次方个微帧,即8个125微秒,换句话说就是1毫秒.对于高速设备来说,spec里规定,n的取值必须在1到16之间,而对于全设备来说,其渴望周期在spec里有规定,必须是在1毫秒到255毫秒之间,对于低速设备来说,其渴望周期必须在10毫秒到255毫秒之间.可见,对于全速/低速设备来说,不存在这种指数关系,所以urb->interval直接被赋值为bInterval,而高速设备由于这种指数关系,bInterval的含义就不是那么直接,而是表示那个幂指数.而start_frame是专门给等时传输用的,所以我们不用管了,这里当然直接设置为-1即可.

终于,我们明白了这个中断传输,明白了这个usb_fill_int_urb()函数,于是我们再次回到hub_configure()函数中来,830和831行,这个没什么好说的,usb-storage里面也就这么设的,能用DMA传输当然要用DMA传输.

834行,has_indicators不用说了,刚刚才介绍的,有就设置为了1,没有就是0,不过hub驱动里提供了一个参数,叫做blinkenlights,指示灯这东西有两种特性,一个是亮,一个是闪,我们常说的一闪一闪亮晶晶,有灯了,亮了,但是不一定会闪,所以blinkenlights就表示闪不闪,这个参数可以在我们加载模块的时候设置,默认值是0,在drivers/usb/core/hub.c中有定义:

90 /* cycle leds on hubs that aren’t blinking for attention */

91 static int blinkenlights = 0;

92 module_param (blinkenlights, bool, S_IRUGO);

93 MODULE_PARM_DESC (blinkenlights, "true to cycle leds on hubs");

以上都是和模块参数有关的.如果这两个条件都满足,就设置hub->indicator [0]为INDICATOR_CYCLE.这么设置有什么用,咱们以后会看到.不过老实说,指示灯这东西怎么工作根本不是我们感兴趣的,作为一个有志青年我们更应该关心关心国家大事,关心关心巴以冲突,关心关心韩国人质,哪有闲功夫去理睬这种指示灯的事呢.

837行,hub_power_on(),这个函数的意图司马昭之心,路人皆知.无非就是相当于打开电视机开关.不过这里涉及到一个重要的函数,暂且不细讲,稍后会专门讲.

838行, hub_activate().在讲这个函数之前,先看一下hub_configure()中剩下的最后几行,hub_activate()之后就return 0,返回了.841行的fail是行标,之前那些出错的地方都有goto fail语句跳转过来,而且错误码也记录在了ret里面,于是返回ret.好,让我们来看一下hub_activate().这个函数不长,依然来自drivers/usb/core/hub.c:

514 static void hub_activate(struct usb_hub *hub)

515 {

516 int status;

517

518 hub->quiescing = 0;

519 hub->activating = 1;

520

521 status = usb_submit_urb(hub->urb, GFP_NOIO);

522 if (status < 0)

523 dev_err(hub->intfdev, "activate –> %d/n", status);

524 if (hub->has_indicators && blinkenlights)

525 schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);

526

527 /* scan all ports ASAP */

528 kick_khubd(hub);

529 }

quiescing和activating就是两个标志符.activating这个标志的意思不言自明,而quiescing这个标志的意思就容易让人疑惑了,永垂不朽的国产软件金山词霸告诉我们,quiescing是停顿,停止,停息的意思,咋一看activating和quiescing就是一对反义词,可是如果真的是起相反的作用,那么一个变量不就够了么?可以为1,可以为0,不就表达了两种意思了吗?嗯,套用小龙人的歌词,就不告诉你,就不告诉你.至少现在不用告诉你,后面用上了再说.

512行,这个我们太熟悉了,非常熟悉,相当熟悉.前面我们调用usb_fill_int_urb()填充好了一个urb,这会儿就该提交了,然后host controller就知道了,然后如果一切顺利的话,host controller就会定期来询问hub,问它有没有中断,有的话就进行中断传输,这个我们前面讲过了.

524行,又和刚才一样的判断,不过这次判断条件满足了以后就会执行一个函数,schedule_delayed_work(),终于看到这个函数被调用了,不枉费我们前面专门分析的蝴蝶效应吧,前面分析清楚了,这里一看我们就知道,延时调用,调用的是leds对应的那个work函数,即我们当初注册的那个led_work().这里LED_CYCLE_PERIOD就是一个宏,表明延时多久,这个宏在drivers/usb/core/hub.c里定义好了,

208 #define LED_CYCLE_PERIOD ((2*HZ)/3)

关于这个指示灯的代码我们以后再分析,趁着年轻,我们应该赶紧把该做的事情做了,像指示灯相关的代码以后再看也不迟,我们真正需要花时间关注的是hub作为一个特殊的usb设备它是如何扮演好连接主机和设备的作用的.席慕蓉说:这个世界上有许多事情,你以为明天一定可以再继续做的,有很多人,你以为一定可以再见到面的.于是,在你暂时放下手,或者暂时转过身的时候,你心中所想的,只是明日又将重聚的希望.有时候,甚至连这种希望都感觉不到.因为,你以为日子既然这样一天一天过来,当然也应该就这样一天一天过去.昨天,今天,明天应该是没有什么不同的.但是,就会有那么一次,在你一放手,一转身的一刹那,有的事情就完全改变了.太阳落下去,而在它重新开始以前,有些人有些事就从此和你永别.

唉,怎么说着说着这么伤感,继续看,528行,kick_khubd(hub),来自drivers/usb/core/hub.c,

316 static void kick_khubd(struct usb_hub *hub)

317 {

318 unsigned long flags;

319

320 /* Suppress autosuspend until khubd runs */

321 to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;

322

323 spin_lock_irqsave(&hub_event_lock, flags);

324 if (list_empty(&hub->event_list)) {

325 list_add_tail(&hub->event_list, &hub_event_list);

326 wake_up(&khubd_wait);

327 }

328 spin_unlock_irqrestore(&hub_event_lock, flags);

329 }

这才是我们真正期待的一个函数,看见wake_up()函数了吧,一看到这里就知道怎么回事了吧,先看321行,int pm_usage_cnt是struct usb_interface的一个成员,pm就是电源管理,usage_cnt就是使用计数,这里的意图很明显,hub要使用了,就别让电源管理把它给挂起来了,不明白?用过笔记本吧?2005年进Intel的时候每人发一本IBM T42,我发现很多时候我合上笔记本它就会自动进入休眠,这叫autosuspend,可是有时候我会发现我合上笔记本以后,笔记本并没有休眠,后来总结出来规律了,下A片的时候基本上计算机是不会进入睡眠的,理由很简单,它发现你有活动着的网络线程,那时候用的是Windows XP,当然Windows也知道计数,别把人家想的那么傻.不过,这里,我们计数的目的不是记录网络线程,而是告诉usb core,咱们拒绝自动挂起,具体的处理会由usb core来统一操作,不用咱们瞎操心,usb core那边自然会判断pm_usage_cnt的,只要我们这里设置了就可以了.

324到327行这段if判断语句,很显然,现在我们是第一次来到这里, 不用说,hub->event_list是空的,所以,条件满足,于是list_add_tail()会被执行,关于队列操作函数,咱们也讲过了,所以这里也不用困惑了,就是往那个总的队列hub_event_list里面加入hub->event_list,然后调用wake_up(&khubd_wait)去唤醒那个昏睡了N多年的hub_thread().如我们前面说过的那样,从此hub_events()函数将再次被执行.

323行和328行,关于自旋锁,老规矩,放到最后面去讲.统一讲.

至此为止,整个关于hub的配置就讲完了,从现在开始,hub就可以正式上班了.而我们也终于完成了一个目标,唤醒了那个该死的hub_thread(),进入hub_events().不论你在什么时候结束,重要的是结束之後就不要悔恨

Linux那些事儿之我是Hub(11)While You Were Sleeping(四) – fuda

相关文章:

你感兴趣的文章:

标签云: