《Linux那些事儿之我是USB》我是U盘(35)迷雾重重的批量传输(四)

来源:岁月联盟 编辑:exp 时间:2012-03-09

有时候我也被这个问题所困扰,我不知道是我不明白,还是这世界变化太快。连Linux中都引入了过期这么一个概念。设置一个时间,如果时间到了该做的事情还没有做完,那么某些事情就会发生。

比如需要烤蛋糕,现在是8点30,而我们要烤45分钟,所以希望闹钟9点一刻响,当时间到了,闹钟就如期待的一样,响个不停。在计算机中,也需要做这样的事情,有些事情,需要时间控制,特别是网络、通信等,凡是涉及数据传输,就得考虑超时,换句话说,就是要定一个闹钟,你要是在这个给定的时间里还没做好你该做的事情,那么停下来,别做了,肯定有问题。比如,如果烤蛋糕烤了45分钟,发现蛋糕一点香味都没有,颜色也没变,那肯定有问题,别烤了,先检查一下烤箱是不是坏了,或者是不是停电了等。

而具体到这里,需要用一个闹钟,或者叫专业一点,定时器。如果时间到了,就执行某个函数,这个功能Linux内核的时间机制已经实现了,只需要按“说明书”调用相应的接口函数即可。看代码,175行,wait_for_completion_interruptible_timeout()函数被调用,kernel/sched.c中定义了若干个这样的函数,我们不去看它们的定义,但是可以把它们的声明“贴”出来,来自include/linux/completion.h:

45 extern void FASTCALL(wait_for_completion(structcompletion *));

46 extern intFASTCALL(wait_for_completion_interruptible(struct completion *x));

47 extern unsigned long FASTCALL(wait_for_completion_timeout(structcompletion *x,

48                                                   unsigned long timeout));

49 extern unsigned longFASTCALL(wait_for_completion_interruptible_timeout(

50                         struct completion *x,unsigned long timeout));

很显然,wait_for_completion是这一系列函数中最基本的,其他几个函数都是基于它的扩展函数。与wait_for_completion对应的一个函数是complete()。其用法和作用如下所示。

首先我们要用init_completion初始化一个struct completion的结构体变量,然后调用wait_for_completion(),这样当前进程就会进入睡眠,处于一种等待状态,而另一个进程可能会去做某事。

当它做完了某件事情之后,它会调用complete()函数,一旦它调用这个complete()函数,那么刚才睡眠的这个进程就会被唤醒。这样就实现了一种同步机制,或者叫等待机制。

而我们现在用到的这个wait_for_completion_interruptible_timeout()函数则是在简单的同步机制上作了加强。它被唤醒有两种可能,一种就是和之前一样,由调用complete()函数的进程给唤醒,或者,就是设置一个闹钟,时间一到闹钟就响了。

举例来说一下这个方案。比如小时候我如果第二天要参加考试,那么前一天晚上要么跟我妈说好让她第二天早上记得叫我,或者如果我妈不在家,那我就定闹钟,到时间了闹钟就把我唤醒。

总之,这里设置了闹钟,时间为MAX_SCHEDULE_TIMEOUT,这么做的道理就是希望别提交一个urb上去之后半天都没人理,如果真的没人理说明出问题了,别浪费时间了,先撤销这个urb重新提交吧。所以我们看到178行就清除US_FLIDX_URB_ACTIVE flag,然后如果timeleft小于等于0就调用usb_kill_urb ()函数撤销当前这个urb。

那么除了这个闹钟以外,这个同步机制在我们这个案例中又是怎么工作的呢?别忘了刚才我们那句init_completion(&urb_done),urb_done是一个struct completion 结构体变量,这个定义在usb_stor_ msg_common()函数的第1行就出现了。显然completion是Linux中同步机制的一个很重要的结构体。同时我们又把&urb_done作为第1个参数传递给了wait_for_completion_interruptible_timeout()。所以我们就等着看发送唤醒信号的complete()函数在哪里被调用的,换句话说,这里一旦睡去,何时才能醒来。

还记得在调用usb_fill_bulk_urb()填充urb时设置了一个urb->complete指针吗?没错,当时咱们就看到了urb->complete=usb_stor_blocking_completion,这相当于向USB主机控制器驱动传达了一个信息。所以,当urb传输完成了之后,USB主机控制器会唤醒它,但不会直接唤醒它,而是通过执行之前设定的urb的complete()函数指针所指向的函数,即调用usb_stor_blocking_completion()函数去真正唤醒它。usb_stor_blocking_completion()函数定义于drivers/usb/storage/transport.c中:

111 static void usb_stor_blocking_completion(structurb *urb)

112 {

113      struct completion *urb_done_ptr = (structcompletion *)urb->context;

114

115      complete(urb_done_ptr);

116 }

这个函数就两句话,但它调用了complete()函数,urb_done_ptr就被赋为urb->context,而urb->context是什么? usb_stor_ msg_common()函数中,138 行,可不就是把刚初始化好的urb_done赋给了它吗?很显然,这样做就是唤醒刚才睡眠的那个进程。换而言之,到这里,wait_for_completion()将醒来,从而继续往下走。

下面只剩下几行代码了。首先是clear_bit()清除US_FLIDX_URB_ACTIVE,表明这个urb 不再是活跃了。因为该干的事都干完了。如果是超时了,那么也是一样的,urb都要被撤销了,当然就不用设为活跃了。最后一句,usb_stor_ msg_common()函数终于该返回了,return us->current_urb->status,返回的就是urb的“status”。于是我们总算是可以离开这个函数了。也就是我们将回到usb_stor_bulk_transfer_buf()中来。剩下的几行代码无非是处理一下结果,我们暂且先不多说。

回过头来看usb_stor_Bulk_transport()函数,978行,usb_stor_bulk_transfer_buf()函数得到调用。第1个参数,us,无须多说。第2个参数,us->send_bulk_pipe,作为U盘来说,它除了有一个控制管道以外,还会有两个批量管道,一个是IN,一个是OUT。经历过此前的风风雨雨,我们已经对USB中那些名词不再有神秘感,所谓管道无非就是一个unsigned int类型的数。us->send_bulk_pipe和接下来我们立刻会遇到的us->recv_bulk_pipe都是在曾经那个令人回味的storage_probe()中调用get_pipes()函数获得的。然后第3个参数bcb,下面看仔细了。

943行,定义了这么一个指针bcb,是structbulk_cb_wrap结构体的指针,这是一个专门为Bulk-only协议特别准备的数据结构,来自drivers/usb/storage/transport.h:

50 /* command block wrapper */

51 struct bulk_cb_wrap {

52       __le32 Signature;              /*contains 'USBC' */

53       __u32  Tag;                    /* uniqueper command id */

54       __le32 DataTransferLength;     /* size ofdata */

55       __u8   Flags;                  /*direction in bit 0 */

56        __u8   Lun;                    /* LUNnormally 0 */

57       __u8   Length;                 /* of ofthe CDB */

58       __u8   CDB[16];                /* maxcommand */

59 };

在同一个文件中还定义了另一个数据结构:struct bulk_cs_wrap。

66 /* command status wrapper */

67 struct bulk_cs_wrap {

68       __le32 Signature;              /* should = 'USBS' */

69       __u32  Tag;                    /* same asoriginal command */

70        __le32 Residue;                /* amountnot transferred */

71       __u8   Status;                 /* seebelow */

72       __u8   Filler[18];

73 };

这两个数据结构对应于CBW和CSW,即Command Block Wrapper和Command Status Wrapper。事到如今,我们需要关注一下USB Mass Storage Bulk-only Transport协议了,因为U盘是按照这个协议规定的方式去传输数据的。Bulk-only传输方式是首先由主机给设备发送一个CBW,然后设备接收到了CBW,它会进行解释,然后按照CBW中定义的那样去执行它该做的事情,然后它会给主机返回一个CSW。

CBW实际上是命令的封装包,而CSW实际上是状态的封装包。(命令执行后的状态,成功还是失败呢?所以需要使用这么一个状态包)。

这时候我们就可以查看usb_stor_Bulk_transport()函数中,调用usb_stor_bulk_transfer_buf()之前的那几行究竟在干什么了。很明显,这些行都是在为usb_stor_bulk_transfer_buf()这个函数调用做准备,真正精彩的部分还是在usb_stor_bulk_transfer_buf()中。

943行,struct bulk_cb_wrap *bcb,赋值为(struct bulk_cb_wrap *)us->iobuf。944行,struct bulk_cs_wrap *bcs,也赋值为(struct bulk_cb_wrap *)us->iobuf,然后定义一个unsignedint的变量transfer_length,赋值为srb->request_bufflen。然后接下来就开始为bcb的各成员赋值了。如图4.36.1和图4.36.2所示,分别描述了CBW和CSW的数据格式。

Byte / bit

7

6

5

4

3

2

1

0

0-3

dCBWSignature

4-7

dCBWTag

8-11

dCBWDataTransferLength

12

bmCBWFlags

13

Reserved (0)

bCBWLUN

14

Reserved(0)

bCBWCBLength

15-30

CBWCB

图4.36.1 CBW

 

 

Byte / bit

7

6

5

4

3

2

1

0

0-3

dCBWSignature ("LaMS")

 

4-7

dCBWTag

 

8-11

dCSWDataResidue

 

12

bCSWStatus

 

图4.36.2  CSW

959行,bcb->Signature=cpu_to_le32(US_BULK_CB_SIGN),Signature对应USB Mass Storage spec中CBW的前4个Bytes,即dCBWSignature,US_BULK_CB_SIGN这个宏定义于drivers/usb/storage/transport.h中:

62 #define US_BULK_CB_SIGN         0x43425355      /*spells out USBC */

也不知道是谁规定的,只有把dCBWSignature里边写上43425355h才能标志着个数据包是一个CBW。另外,CBW的传输全是遵守Little Endian的,所以cpu_to_le32()这个宏需要使用,来转换数据格式。

然后bcb->DataTransferLength对应CBW中的dCBWDataTransferLength。这个就是标志ho主机希望这个端点传输多少个Bytes的数据。这里把cpu_to_le32(transfer_length)赋给了它。而transfer_length刚才已经说了,就是srb->request_bufflen。其实这几个变量名换来换去最重要记录的还是同一样东西。

bcb->Flags,对应于CBW中的bmCBWFlags,bcb->Flags =srb->sc_data_direction == DMA_FROM_DEVICE ? 1 << 7 : 0;表明的是数据传输的方向,DMA_FROM_DEVICE在前面讲过,表示数据是从设备传向主存。而bmCBWFlags是8位的,其中bit7表示方向;0表示Data-Out,即from host to the device;1表示Data-in,即from the device to the host。所以这里如果是1的话要左移7位。

bcb->Tag = srb->serial_number,这个Tag对应CBW中的dCBWTag,dCBWTag的意义在于,主机会send出去,而设备将会把这个Tag的内容给打印出来。确切地说,设备会回送一个CSW回来,而在CSW中会有一个dCSWTag,它的内容和这个dCBWTag是一样的,所以实际上这就跟接头暗号似的。每一个SCSI命令都会被赋上一个serial_number,这里把它用在了Tag上。

bcb->Lun = srb->device->lun,很简单,对应CBW中的bCBWLUN,就是表示这个命令是发给哪个LUN的,我们知道一个设备如果支持多个LUN,那么显然每个LUN会有一个编号。比如要读写U盘上的某个分区,那么当然得指明是哪个分区了。如果设备不支持多个LUN,那么这儿会被设置为0。不过需要注意,这里bcb->Lun和CBW中的bCBWLUN并不完全对应,bCBWLUN只有4个bit,而咱们这中定义时,LUN是有8位的,低4位用来对应bCBWLUN,而高4位实际上是用来表示target id的。所以接下来判断us->flags里边设了US_FL_SCM_MULT_TARG这个标志没有,如果有,说明是支持多个target的,于是就要记录下是哪个target。

bcb->Length = srb->cmd_len,这个对应于CBW中的bCBWCBLength,即命令的有效长度,单位是Bytes。SCSI命令的有效长度只能是1到16之间。接下来有个CDB数组,数组共16个元素,理由在前面讲struct scsi_cmnd中的cmnd就已经说过了。而969行,970行正是把命令srb->cmnd数组的内容复制至bcb->CDB中。

这时候,usb_stor_bulk_transfer_buf正式被调用了。传递给它的第三个参数正是bcb,而第四个参数是US_BULK_CB_WRAP_LEN,它也定义于drivers/usb/storage/transport.h中:

61 #define US_BULK_CB_WRAP_LEN 31

31就是CBW的长度,CBW正是31个Bytes。而usb_stor_bulk_transfer_buf无非就是提交一个urb,然后就不用管事了,就等结果呗。而最终的结果是由interpret_urb_result()返回的,传输正确那么会返回USB_STOR_XFER_GOOD,而如果不正确,那么usb_stor_Bulk_transport()中就直接返回了,返回值是USB_STOR_TRANSPORT_ERROR。如果正确,那么继续往下走,这才到真正的数据传输阶段。在真正开始将数据传输阶段之前,我们先来查看interpret_urb_result()函数