Linux那些事儿之我是UHCI(12)一个函数引发的故事(三) – fudan

从调度图我们可以看出,等时传输不需要什么QH,只要把几个TD连接起来,让Frame List Pointer指向第一个TD就可以了.换言之,我们需要为等时传输准备一个队列,然后每一个Frame都让Frame List Pointer指向队列的头部.

那么对于中断传输应该如何操作呢?实际上我们把为中断传输建立了8个队列.不同的队列代表了不同的周期,这8个队列分别代表的是1ms,2ms,4ms,8ms,16ms,32ms,64ms,128ms,usb spec里规定,对于全速/低速设备来说,其渴望周期撑死也不能超过255ms.那么这8个队列的队列头就叫做Skeleton QH,而对于正儿八经的传输来说,我们需要另外专门的建立QH,并往该QH中连接上我们相关的TD,这类QH就是这里所称的Normal QH,道上的兄弟更是喜欢把往QH上连接TD称之为为QH装备上TD.而这几个Skeleton QH是不会被装备任何TD的,它们就相当于一群模特,让别的中断QH知道自己该放置在何处.不过Skeleton QH并非只是为中断传输准备的,实际上,我们准备了11个Skeleton QH,除了中断传输的8个以外,还有一个为等时传输准备的qh,一个为表征大部队结束的qh,一个为处理unlink而设计的qh.这三个都有点特殊,我们遇到了再讲.

回到代码中来,从uhci_start函数的角度来看,我们注意到uhci_alloc_qh返回值是qh,而qh是一个struct uhci_qh结构体变量,而刚才uhci_alloc_td函数的返回值td,是一个struct uhci_td结构体变量.TD和QH这两个概念说起来轻松,可是化成代码来表示的这两个结构体绝对不是省油的灯.先看struct uhci_td,来自drivers/usb/host/uhci-hcd.h中:

232 /*

233 * The documentation says "4 words for hardware, 4 words for software".

234 *

235 * That’s silly, the hardware doesn’t care. The hardware only cares that

236 * the hardware words are 16-byte aligned, and we can have any amount of

237 * sw space after the TD entry.

238 *

239 * td->link points to either another TD (not necessarily for the same urb or

240 * even the same endpoint), or nothing (PTR_TERM), or a QH.

241 */

242 struct uhci_td {

243 /* Hardware fields */

244 __le32 link;

245 __le32 status;

246 __le32 token;

247 __le32 buffer;

248

249 /* Software fields */

250 dma_addr_t dma_handle;

251

252 struct list_head list;

253

254 int frame; /* for iso: what frame? */

255 struct list_head fl_list;

256 } __attribute__((aligned(16)));

再看struct uhci_qh,依然来自drivers/usb/host/uhci-hcd.h:

126 struct uhci_qh {

127 /* Hardware fields */

128 __le32 link; /* Next QH in the schedule */

129 __le32 element; /* Queue element (TD) pointer */

130

131 /* Software fields */

132 dma_addr_t dma_handle;

133

134 struct list_head node; /* Node in the list of QHs */

135 struct usb_host_endpoint *hep; /* Endpoint information */

136 struct usb_device *udev;

137 struct list_head queue; /* Queue of urbps for this QH */

138 struct uhci_td *dummy_td; /* Dummy TD to end the queue */

139 struct uhci_td *post_td; /* Last TD completed */

140

141 struct usb_iso_packet_descriptor *iso_packet_desc;

142 /* Next urb->iso_frame_desc entry */

143 unsigned long advance_jiffies; /* Time of last queue advance */

144 unsigned int unlink_frame; /* When the QH was unlinked */

145 unsigned int period; /* For Interrupt and Isochronous QHs */

146 short phase; /* Between 0 and period-1 */

147 short load; /* Periodic time requirement, in us */

148 unsigned int iso_frame; /* Frame # for iso_packet_desc */

149 int iso_status; /* Status for Isochronous URBs */

150

151 int state; /* QH_STATE_xxx; see above */

152 int type; /* Queue type (control, bulk, etc) */

153 int skel; /* Skeleton queue number */

154

155 unsigned int initial_toggle:1; /* Endpoint’s current toggle value */

156 unsigned int needs_fixup:1; /* Must fix the TD toggle values */

157 unsigned int is_stopped:1; /* Queue was stopped by error/unlink */

158 unsigned int wait_expired:1; /* QH_WAIT_TIMEOUT has expired */

159 unsigned int bandwidth_reserved:1; /* Periodic bandwidth has

160 * been allocated */

161 } __attribute__((aligned(16)));

某种意义上来说,struct usb_hcd,struct uhci_hcd这些结构体和struct uhci_td,struct uhci_qh之间的关系就好比宏观经济学与微观经济学的关系.它们都是为了描述主机控制器,只是一个是从宏观角度,一个是从微观角度.从另一个角度来说,这些宏观的数据结构实际上是软件意义上的,而这些微观的数据结构倒是和硬件有着对应关系.从硬件上来说, Frame List,Isochronous Transfer Descriptors(简称TD),Queue Heads(简称QH),以及queued Transfer Descriptors(也简称TD)这都是UHCI spec定义的数据结构.

先看struct uhci_td,对于结构体,我们的原则是,一个结构体的元素我们用到哪个讲哪个,而不是首先就把所有元素说一遍,因为那样除了把你吓倒之外没有别的好处.uhci_alloc_td中,定义了两个局部变量,一个是dma_handle,一个是td.dma_handle传递给了dma_pool_alloc函数,我们于是知道它记录的是td的dma地址.td内部有一个成员,dma_addr_t dma_handle,它被赋值为dma_handle.td内部另一个成员,int frame,它用来表征这个td所对应的frame,目前初始化frame为-1.另外,td还有两个成员,struct list_head list和struct list_head fl_list,这是两个队列.uhci_alloc_td中用INIT_LIST_HEAD把它们俩进行了初始化.而反过来uhci_free_td的工作就是反其道而行之,调用dma_pool_free去释放这个内存.在释放之前检查了一下这两个队列是否为空,如果不为空会先提出警告.

再来看struct uhci_qh,在uhci_alloc_qh中,首先也是定义两个局部变量,qh和dma_handle.使用的也是同样的套路.qh调用dma_pool_alloc来申请.然后用memset给它清零.dma_handle同样传递给了dma_pool_alloc,并且之后也赋值给了qh->dma_handle.qh同样有一个成员dma_addr_t dma_handle.qh中也有两个成员是队列,struct list_head node和struct list_head queue,同样也是在这里被初始化.此外,还有四个成员被赋了值,element,link,state,type.关于这四个赋值,我们暂时不提,用到了再说.不过我们应该回到uhci_start的上下文去看一下uhci_alloc_qh被调用的具体情况,622行这里有一个循环, UHCI_NUM_SKELQH是一个宏,这个宏的值为11,所以这里就是申请了11个qh,这正是我们前面介绍过的那个11个Skeleton QH.与此同时我们注意到struct uhci_hcd中有一个成员struct uhci_qh *skelqh[UHCI_NUM_SKELQH],即有这么一个数组,数组11个元素,而这里就算是为这11个元素申请了内存空间了.

接下来,要具体解释这里的代码我们还得把下面这一把宏贴出来.来自drivers/usb/host/uhci-hcd.h:

272 /*

273 * Skeleton Queue Headers

274 */

275

276 /*

277 * The UHCI driver uses QHs with Interrupt, Control and Bulk URBs for

278 * automatic queuing. To make it easy to insert entries into the schedule,

279 * we have a skeleton of QHs for each predefined Interrupt latency.

280 * Asynchronous QHs (low-speed control, full-speed control, and bulk)

281 * go onto the period-1 interrupt list, since they all get accessed on

282 * every frame.

283 *

284 * When we want to add a new QH, we add it to the list starting from the

285 * appropriate skeleton QH. For instance, the schedule can look like this:

286 *

287 * skel int128 QH

288 * dev 1 interrupt QH

289 * dev 5 interrupt QH

290 * skel int64 QH

291 * skel int32 QH

292 * …

293 * skel int1 + async QH

294 * dev 5 low-speed control QH

295 * dev 1 bulk QH

296 * dev 2 bulk QH

297 *

298 * There is a special terminating QH used to keep full-speed bandwidth

299 * reclamation active when no full-speed control or bulk QHs are linked

300 * into the schedule. It has an inactive TD (to work around a PIIX bug,

301 * see the Intel errata) and it points back to itself.

302 *

303 * There’s a special skeleton QH for Isochronous QHs which never appears

304 * on the schedule. Isochronous TDs go on the schedule before the

305 * the skeleton QHs. The hardware accesses them directly rather than

306 * through their QH, which is used only for bookkeeping purposes.

307 * While the UHCI spec doesn’t forbid the use of QHs for Isochronous,

308 * it doesn’t use them either. And the spec says that queues never

309 * advance on an error completion status, which makes them totally

310 * unsuitable for Isochronous transfers.

311 *

312 * There’s also a special skeleton QH used for QHs which are in the process

313 * of unlinking and so may still be in use by the hardware. It too never

314 * appears on the schedule.

315 */

316

317 #define UHCI_NUM_SKELQH 11

318 #define SKEL_UNLINK 0

319 #define skel_unlink_qh skelqh[SKEL_UNLINK]

320 #define SKEL_ISO 1

321 #define skel_iso_qh skelqh[SKEL_ISO]

322 /* int128, int64, …, int1 = 2, 3, …, 9 */

323 #define SKEL_INDEX(exponent) (9 – exponent)

324 #define SKEL_ASYNC 9

325 #define skel_async_qh skelqh[SKEL_ASYNC]

326 #define SKEL_TERM 10

327 #define skel_term_qh skelqh[SKEL_TERM]

328

329 /* The following entries refer to sublists of skel_async_qh */

330 #define SKEL_LS_CONTROL 20

331 #define SKEL_FS_CONTROL 21

332 #define SKEL_FSBR SKEL_FS_CONTROL

333 #define SKEL_BULK 22

好家伙,光注释就看得我一愣一愣的,可惜还是没看懂.但基本上我们能感觉出,当前我们的目标是为了建立qh数据结构,并把相关的队列给连接起来.

633行,SKEL_ISO是1,SKEL_ASYNC是9.所以这里就是循环7次,实际上,在这个11个元素的数组中,2到9就是对应于中断传输的那8个Skeleton QH,所以这里就是为这8个qh的link元素赋值.道上有一个规矩,对于这8个qh,周期为128ms的那个qh被称为int128,周期为64ms的被称为int64,于是就有了int128,int64,…,int1分别对应这个数组的2,3,…,9号元素.今后我们对这几个QH的称呼也是如此,skel int128 QH,skel int64 QH,…,skel int2 QH,skel int1 QH. 而这里我们还看到另一个家伙,skel_async_qh.它表示async queue,指的是low-speed control,full-speed control,bulk这三种队列,它们都被称作异步队列.与之对应的就是刚才这个SKEL_ASYNC宏,我们说SKEL_ASYNC等于9,而我们同时知道skel int1 QH实际上也是skelqh[]数组的9号元素,所以实际上skel_async_qh和skel int1 QH是共用了同一个qh,这是因为skel int1 QH表示中断传输的周期为1ms,而控制传输和Bulk传输也是每一个ms或者说每一个frame都会被调度的,当然前提是带宽足够.所以这里的做法就是把skel int128 QH到skel int2 QH的link指针全都赋为LINK_TO_QH(uhci->skel_async_qh).

LINK_TO_QH是一个宏,定义于drivers/usb/host/uhci-hcd.h:

174 #define LINK_TO_QH(qh) (UHCI_PTR_QH | cpu_to_le32((qh)->dma_handle))

UHCI_PTR_QH等一系列宏也来自同一文件:

76 #define UHCI_PTR_BITS __constant_cpu_to_le32(0x000F)

77 #define UHCI_PTR_TERM __constant_cpu_to_le32(0x0001)

78 #define UHCI_PTR_QH __constant_cpu_to_le32(0x0002)

79 #define UHCI_PTR_DEPTH __constant_cpu_to_le32(0x0004)

80 #define UHCI_PTR_BREADTH __constant_cpu_to_le32(0x0000)

这样我们就要看struct uhci_qh这个结构体中的成员__le32 link了.这是一个指针,这个指针指向下一个QH,换言之,它包含着下一个QH或者下一个TD的地址.不过它一共32个bits,其中只有bit31到bit4这些位是用来记录地址的,bit3和bit2是保留位,bit1则用来表示该指针指向的是一个QH还是一个TD.bit1如果为1,表示本指针指向的是一个QH,如果为0,表示本指针指向的是一个TD.(刚才这个宏UHCI_PTR_QH正是起这个作用的,实际上QH总是16字节对齐的,即它的低四位总是为0,所以我们总是把低四位拿出来废物利用,比如这里的LINK_TO_QH就是把这个struct uhci_qh的bit1给设置成1,以表明它指向的是一个QH.)而bit0表示本QH是否是最后一个QH.如果bit0位1,则说明本QH是最后一个QH了,所以这个指针实际上是无效的,而bit0为0才表示本指针有效,因为至少本QH后面还有QH或者还有TD.我们看到skel_async_qh的link指针被赋予了UHCI_PTR_TERM.

另外这里还为skel_term_qh的link给赋了值,我们看到它就指向自己.skel_term_qh是skelqa[]数组的第十个元素.其作用暂时还不明了.但以后自然会知道的.

struct uhci_td里面同样也有个指针,__le32 link,它同样指向另一个TD或者QH,而bit1和bit0的作用和struct uhci_qh中的link是一模一样的,bit1为1表示指向QH,为0表示指向TD.bit0为1表示指针无效,即本TD是最后一个TD了,bit0为0表示指针有效.所以

639行uhci_fill_td,来自drivers/usb/host/uhci-q.c:

138 static inline void uhci_fill_td(struct uhci_td *td, u32 status,

139 u32 token, u32 buffer)

140 {

141 td->status = cpu_to_le32(status);

142 td->token = cpu_to_le32(token);

143 td->buffer = cpu_to_le32(buffer);

144 }

实际上就是填充struct uhci_td中的三个成员,__le32 status,__le32 token,__le32 buffer.咱们来看传递给它的参数,status和buffer都是0,只是有一个token不为0, uhci_explen来自drivers/usb/host/uhci-hcd.h:

208 /*

209 * for TD <info>: (a.k.a. Token)

210 */

211 #define td_token(td) le32_to_cpu((td)->token)

212 #define TD_TOKEN_DEVADDR_SHIFT 8

213 #define TD_TOKEN_TOGGLE_SHIFT 19

214 #define TD_TOKEN_TOGGLE (1 << 19)

215 #define TD_TOKEN_EXPLEN_SHIFT 21

216 #define TD_TOKEN_EXPLEN_MASK 0x7FF /* expected length, encoded as n-1 */

217 #define TD_TOKEN_PID_MASK 0xFF

218

219 #define uhci_explen(len) ((((len) – 1) & TD_TOKEN_EXPLEN_MASK) << /

220 TD_TOKEN_EXPLEN_SHIFT)

221

222 #define uhci_expected_length(token) ((((token) >> TD_TOKEN_EXPLEN_SHIFT) + /

223 1) & TD_TOKEN_EXPLEN_MASK)

224 #define uhci_toggle(token) (((token) >> TD_TOKEN_TOGGLE_SHIFT) & 1)

225 #define uhci_endpoint(token) (((token) >> 15) & 0xf)

226 #define uhci_devaddr(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7f)

227 #define uhci_devep(token) (((token) >> TD_TOKEN_DEVADDR_SHIFT) & 0x7ff)

228 #define uhci_packetid(token) ((token) & TD_TOKEN_PID_MASK)

229 #define uhci_packetout(token) (uhci_packetid(token) != USB_PID_IN)

230 #define uhci_packetin(token) (uhci_packetid(token) == USB_PID_IN)

真是一波未平一波又起.麻烦的东西一个又一个的跳出来.让我一次次的感觉到心力交瘁.关于token,UHCI spec为TD定义了4个双字,即四个DWord,其中第三个DWord叫做TD TOKEN.一个DWord一共32个bits.这32个bits中,bit31到bit21表示Maximum Length,即这次传输的最大允许字节.bit20是保留位,bit19表示Data Toggle,bit18到bit15表示Endpoint的地址,即我们曾经说的端点号,bit14到bit8表示设备地址,bit7到bit0表示PID,即Packet ID.以上这一把的宏就是为了提取出一个Token的各个部分,比如uhci_toggle,就是token右移19位,然后和1相与,结果当然就是token的bit19,正是刚才说的Data Toggle.而uhci_expected_length则是获取bit31到bit21即Length这一段(加上1是因为Spec规定,这一段为0表示1个byte,为1表示2个bytes,为2表示3个bytes…)

于是我们很快就能明白这个uhci_fill_td具体做了什么. (0x7f << TD_TOKEN_DEVADDR_SHIFT)表示把7f左移8位,USB_PID_IN等于0x69,UHCI spec规定这就表示PID IN.然后uhci_explen(len)的作用和uhci_expected_length的作用恰恰相反,它把一个length转换成bit31到bit21,这样三块或一下,就构造了一个新的token.至于这个token构造好了之后填充给这td究竟有什么用,我们看不出来,实际上注释说了,这是为了fix一个bug,若干年前,Intel PIIX控制器有一个bug,当时为了绕开这个bug,引入了这么一段,如今这一事件过了快十年了,开源社区里恐怕除了我也没有几个人记得当初究竟发生了什么.虽然自从我加入Intel之后,Intel不断的传出负面消息,先是裁员啊,然后是部门甩卖啊,虽然我的那些老同事们总是说,Intel就是因为有我这样的垃圾员工,才会弄出那么多bug来,然而,尽管我是人渣,但毕竟不是败类,老实说,这个bug确实不是我引入的.关于这个bug的详情,我们在后面会讲,它和一个叫做FSBR的东西有关.只不过我们现在看到的是term_td的link指针被设置为了UHCI_PTR_TERM,和skel_term_qh的link赋值一样,又是那个休止符.其实这里的道理很简单,就相当于我们每次申请一个链表的时候总是把最后一个指针设置成NULL一样.只不过这里不是叫作NULL,是叫作UHCI_PTR_TERM,但其作用都是一样,就相当于五线谱中的休止符.注意uhci->term_td正是我们一开始调用uhci_alloc_td的时候申请并且做的初始化.

642行,struct uhci_qh中另一个成员为__le32 element.它指向一个队列中的第一个元素.LINK_TO_TD来自drivesr/usb/host/uhci-hcd.h:

269 #define LINK_TO_TD(td) (cpu_to_le32((td)->dma_handle))

理解了LINK_TO_QH自然就能理解LINK_TO_TD.这里咱们令skel_async_qh以及skel_term_qh的element等于这个uhci->term_td.

649行,这个循环可够夸张的,UHCI_NUMFRAMES的值为1024,所以这个循环就是惊世骇俗的1024次.仿佛写代码的人受了北京大学经济学院院长刘伟的熏陶.既然你刘伟说:”我把堵车看成是一个城市繁荣的标志,是一件值得欣喜的事情.如果一个城市没有堵车,那它的经济也可能凋零衰败.1998年特大水灾刺激了需求,拉动增长,光水毁房屋就几百万间,所以水灾拉动中国经济增长1.35%.”于是写代码的人说:”我把循环次数看成是一个程序高效的标志,是一件值得欣喜的事情.如果一个程序没有循环,那它的效率也可能惨不忍睹…”

在乎的是看风景的心情,旅行不会因为美丽的风景终止。

Linux那些事儿之我是UHCI(12)一个函数引发的故事(三) – fudan

相关文章:

你感兴趣的文章:

标签云: