现在的位置: 首页 > 综合 > 正文

Linux那些事儿之我是UHCI(13)一个函数引发的故事(四)

2013年09月01日 ⁄ 综合 ⁄ 共 3509字 ⁄ 字号 评论关闭

uhci_frame_skel_link来自drivers/usb/host/uhci-hcd.c:

     94 /*

     95  * Calculate the link pointer DMA value for the first Skeleton QH in a frame.

     96  */

     97 static __le32 uhci_frame_skel_link(struct uhci_hcd *uhci, int frame)

     98 {

     99         int skelnum;

    100

    101         /*

    102          * The interrupt queues will be interleaved as evenly as possible.

    103          * There's not much to be done about period-1 interrupts; they have

    104          * to occur in every frame.  But we can schedule period-2 interrupts

    105          * in odd-numbered frames, period-4 interrupts in frames congruent

    106          * to 2 (mod 4), and so on.  This way each frame only has two

    107          * interrupt QHs, which will help spread out bandwidth utilization.

    108          *

    109          * ffs (Find First bit Set) does exactly what we need:

    110          * 1,3,5,...  => ffs = 0 => use period-2 QH = skelqh[8],

    111          * 2,6,10,... => ffs = 1 => use period-4 QH = skelqh[7], etc.

    112          * ffs >= 7 => not on any high-period queue, so use

    113          *      period-1 QH = skelqh[9].

    114          * Add in UHCI_NUMFRAMES to insure at least one bit is set.

    115          */

    116         skelnum = 8 - (int) __ffs(frame | UHCI_NUMFRAMES);

    117         if (skelnum <= 1)

    118                 skelnum = 9;

    119         return LINK_TO_QH(uhci->skelqh[skelnum]);

    120 }

俗话说,彪悍的人生不需要解释,彪悍的代码不需要注释.但是像这个函数这样,仅仅几行代码,却用了一堆的注释,不可谓不彪悍也.首先__ffs是一个位操作函数,其意思已经在注释里说的很清楚了,给它一个输入参数,它为你找到这个输入的参数的第一个被set的位,set就是说为1.这个函数涉及到汇编代码,对我这个汇编语言不会编,微机原理闹危机的人来说,显然是不愿意仔细去看这个函数具体是怎么实现的,只是知道,每个体系结构都实现了自己的这个函数__ffs.比如,i386的就在include/asm-i386/bitops.h,x8664的就在include/asm-x86_64/bitops.h.在我家Intel以结果为导向的理念指导下,我们来看一下这个函数的返回值,很显然,正如注释里说的那样,如果输入参数是1,3,5,7,9等等奇数,那么返回值必然是0,因为bit0肯定是1.如果参数是2,6,10,14,18,22,26这一系列的数,那么返回值就是1,因为bit0一定是0,bit1一定是1.如果参数是4,12,20,28,36这一系列的数,那么返回值就是2,因为bit0bit1一定是0,bit2一定是1.参加过初中数学奥赛的兄弟们一定不难看出,其实第一组数就是除以2余数为1的数列,第二组数就是除以4余数为2的数列,第三组就是除以8余数为4的数列,用我们当年奥赛的术语取模符号(mod)来说,就是第一组是1 mod 2,第二组是2 mod 4,第三组是4 mod 8,如此下去,返回值为0的一共有512,返回值为1的一共有256,返回值为2的一共有128,返回值为3的一共有64,返回值为4的一共有32,返回值为5的一共有16,返回值为6的一共有8,返回值为7的一共有4,返回值为8的一共有2,返回值为9的一共有1(这一个就是512).N年前我们就知道,1+2+4+…+512=1023.

结合咱们这里代码的116,frame0的话,__ffs的返回值为10,所以skelnum就应该为-2,frame11023这个过程中,skelnum8的次数为512,7的次数为256,6的次数为128,5的次数为64,4的次数为32,3的次数为16,2的次数为8,1的次数为4,0的次数为2,-1的次数为1.117行这个if语句就使得skelnum小于等于1的那几次都把skelnum设置为9,这总共有8.(14,02,-11,-2的一次)

因此我们就知道skelnum的取值范围是29,而这也就意味着我们这里这个uhci_frame_skel_link函数的返回值实际上就是uhci->skelqh[]这个数组中的7个元素.前面我们已经知道这个数组一共有11个元素,除了skelqh[2]skelqh[9]8个元素以外,skelqh[1]是为等时传输准备的,skel[10]是休止符(skel_term_qh),skel[0]表示没有连接的孤魂野鬼状态.要深刻理解这个数组需要时间的沉淀,但是很明显,uhci_frame_skel_link的效果就是为为skelqh[2]skelqh[9]找到了归宿.1024frame,8frame指向了skelqh[2],skel int128 QH,1024除以8等于128,岂不正是每隔128ms这个qh会被访问到么?同理,16frame指向了skelqh[3],skel int64 QH,1024除以16等于64,也正意味着每隔64ms会被访问到.一直到skelqh[8],skel int2 QH,512frame指向了它,所以这就代表每隔2ms会被访问的那个队列.剩下的skelqh[9],skel int1 QH,总共也有8,不过你别误会,skel int1 QH代表的是1ms周期,显然应该是1024frame都指向它.可是你别忘了,刚才我们不是把skel int2 QHskel int8 QHlink指针都指向了skel int1 QH了么?

还没明白?我们说了要用图解法来理解这个问题.所以我们不妨先画出此时此刻这整个框架.

framelist[]

[  0 ]----> QH -------/

[  1 ]----> QH --------> QH ----> UHCI_PTR_TERM

  ...        QH -------/

[1023]----> QH ------/

              ^^          ^^                ^^

          7 QHs for      1 QH for   f    End Chain

            INT (2-128ms)  1ms-INT(plus CTRL Chain,BULK Chain)

skel实际上就是skeleton,框架或者骨骼的意思.这个skelqh数组扮演着一个框架的作用.实际上一个QH对应着一个Endpoint,即从主机控制器的角度来说,它为每一个endpoint建立一个队列,这就要求每个队列有一个队列头,而许多个endpoint的队列如何组织呢?我们按上图构建了一个框架之后,将来有了一个端点,就为它建立相应的队列,根据需要来建立,然后把它插入到框架中的对应位置.我们走着瞧.一切自会明了.以后我们还会继续通过画以上这样的Skeleton(或者叫框架图)来理解UHCI主机控制器的驱动程序.汤唯告诉我们说床戏是用身体诠释爱情”,而写代码的人告诉我们说Skeleton是用队列诠释调度.

 

抱歉!评论已关闭.