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,因为bit0和bit1一定是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行,frame为0的话,__ffs的返回值为10,所以skelnum就应该为-2,frame为1到1023这个过程中,skelnum为8的次数为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次.(为1的4次,为0的2次,为-1的1次,为-2的一次)
因此我们就知道skelnum的取值范围是2到9,而这也就意味着我们这里这个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]找到了归宿.在1024个frame中,有8个frame指向了skelqh[2],即skel int128 QH,1024除以8等于128,岂不正是每隔128ms这个qh会被访问到么?同理,16个frame指向了skelqh[3],即skel int64 QH,1024除以16等于64,也正意味着每隔64ms会被访问到.一直到skelqh[8],即skel int2 QH,有512个frame指向了它,所以这就代表每隔2ms会被访问的那个队列.剩下的skelqh[9],即skel int1 QH,总共也有8次,不过你别误会,skel int1 QH代表的是1ms周期,显然应该是1024个frame都指向它.可是你别忘了,刚才我们不是把skel int2 QH到skel int8 QH的link指针都指向了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是用队列诠释调度.