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

get_hub_descriptor()结束了,然后就返回hub_configure()中来.635到642行,判断刚才的返回值,小于零当然是出错了,大于零也还要多判断一次, USB_MAXCHILDREN是咱们自己定义的一个宏,值为31.看include/linux/usb.h:

324 #define USB_MAXCHILDREN (31)

其实hub可以接一共255个端口,不过实际上遇到的usb hub最多的也就是说自己支持10个端口的.所以31基本上够用了.当然你要是心血来潮把这个宏改成100,200,那也不会出事,我就不信你能在一台机器上连上百个usb设备.真要那样,你绝对是没事找抽型的.

我们来看一下hub描述符对应的数据结构,struct usb_hub中有一个成员,struct usb_hub_descriptor *descriptor,就是表征hub描述符的,来看struct usb_hub_descriptor,定义于drivers/usb/core/hub.h,

130 struct usb_hub_descriptor {

131 __u8 bDescLength;

132 __u8 bDescriptorType;

133 __u8 bNbrPorts;

134 __le16 wHubCharacteristics;

135 __u8 bPwrOn2PwrGood;

136 __u8 bHubContrCurrent;

137 /* add 1 bit for hub status change; round to bytes */

138 __u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];

139 __u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8];

140 } __attribute__ ((packed));

看见了没有,至少9个字节吧,接下来我们会用到bNbrPorts,它代表Number of downstream facing ports that this hub supports,就是说这个hub所支持的下行端口,刚才这里判断的就是这个值是不是比31还大,如果是,那么对不起,出错了.bHubContrCurrent是Hub控制器的最大电流需求,DeviceRemoveable是用来判断这个端口连接的设备是否是可以移除的,每一个bit代表一个端口,如果该bit为0,则说明可以被移除,为1,就说明不可以移除.而wHubCharacteristics就相对来说麻烦一点了,它记录了很多信息,后面有相当一部分的代码都是在判断这个值,我们这里把hub spec里hub descriptor的定义给贴出来,如下图所示,一会儿让我们对照这张图来看wHubCharacteristics相关的代码:

648行,用一个临时变量wHubCharacteristics来代替描述符里的那个wHubCharacteristics,从650行就开始判断了,首先判断是不是复合设备.在drivers/usb/core/hub.h中定义了如下一些宏,

95 /*

96 * wHubCharacteristics (masks)

97 * See USB 2.0 spec Table 11-13, offset 3

98 */

99 #define HUB_CHAR_LPSM 0x0003 /* D1 .. D0 */

100 #define HUB_CHAR_COMPOUND 0x0004 /* D2 */

101 #define HUB_CHAR_OCPM 0x0018 /* D4 .. D3 */

102 #define HUB_CHAR_TTTT 0x0060 /* D6 .. D5 */

103 #define HUB_CHAR_PORTIND 0x0080 /* D7 */

结合上面这张图,意思很明显.650到661行,如果是复合设备,符合设备就是说这个设备它可能是几种设备绑在一起的,比如既可以当hub用又可以有别的功能,那么就用一个数组portstr[]来记录每一个端口的设备是否可以被移除.然后打印出调试信息来.不要说你看不懂,把i用0,1,2,3这些数代入进去就明白了.

663至674行, HUB_CHAR_LPSM,表征电源切换的方式,不同的hub有不同的power switching的方式,ganged power switching指的是所有port一次性上电,individual port power switching当然就是各人自扫门前雪,哪管他人瓦上霜.而usb 1.0的hub压根儿就没有power switching这么一个说法.

676到687行, HUB_CHAR_OCPM,表征过流保护模式,其实也没啥,不明白也无所谓,这几行无非就是打印一些调试信息.

689到691行,先是初始化一个自旋锁,hub->tt.lock,struct usb_hub中的成员,struct usb_tt tt,

166 /*

167 * As of USB 2.0, full/low speed devices are segregated into trees.

168 * One type grows from USB 1.1 host controllers (OHCI, UHCI etc).

169 * The other type grows from high speed hubs when they connect to

170 * full/low speed devices using "Transaction Translators" (TTs).

171 *

172 * TTs should only be known to the hub driver, and high speed bus

173 * drivers (only EHCI for now). They affect periodic scheduling and

174 * sometimes control/bulk error recovery.

175 */

176 struct usb_tt {

177 struct usb_device *hub; /* upstream highspeed hub */

178 int multi; /* true means one TT per port */

179 unsigned think_time; /* think time in ns */

180

181 /* for control/bulk error recovery (CLEAR_TT_BUFFER) */

182 spinlock_t lock;

183 struct list_head clear_list; /* of usb_tt_clear */

184 struct work_struct kevent;

185 };

知道tt干嘛的吗?tt叫做transaction translator.你可以把它想成一块特殊的电路,是hub里面的电路,确切的说是高速hub中的电路,我们知道usb设备有三种速度的,分别是low speed,full speed,high speed.即所谓的低速/全速/高速,抗日战争那会儿,这个世界上只有low speed/full speed的设备,没有high speed的设备,后来解放后,国民生产力的大幅度提升催生了一种high speed的设备,包括主机控制器,以前只有两种接口的,OHCI/UHCI,这都是在usb spec 1.0的时候,后来2.0推出了EHCI,高速设备应运而生.Hub也有高速hub和过去的hub,但是这里就有一个兼容性问题了,高速的hub是否能够支持低速/全速的设备呢?一般来说是不支持的,于是有了一个叫做TT的电路,它就负责高速和低速/全速的数据转换,于是,如果一个高速设备里有这么一个TT,那么就可以连接低速/全速设备,如不然,那低速/全速设备没法用,只能连接到OHCI/UHCI那边出来的hub口里.我们先不多说,看代码.690行,初始化一个队列,hub->tt.clear_list.然后691行,yeah,终于见到了INIT_WORK(),我没忽悠你吧, hub->tt.kevent是一个struct work_struct的结构体,而hub_tt_kevent是我们定义的函数,将会在未来某年某月的某一天去执行.另外,tt有两种,一种是single tt,一种是multi tt.前者表示整个hub就是一个TT,而multi tt表示每个端口都配了一个TT.大多数hub是single TT,因为一个hub一个TT就够了,国家资源那么紧张,何必铺张浪费.使用single TT就是支持国家反腐倡廉!

692行的switch, hdev->descriptor.bDeviceProtocol,别看走眼了,刚才咱们一直是判断hub->descriptor,而这里是hdev->descriptor,hdev是struct usb_device结构体指针,咱们一进入这个hub_configure()函数就赋了值的,其实就是和这个hub相关的那个struct usb_device指针.所以这里判断的描述符是标准的usb设备描述符,而其中bDeviceProtocol的含义在hub spec里有专门的规定.这一段就是为了设置tt,对照hub spec可知,full/low speed的hub的bDeviceProtocol是0,这种hub就没有tt.所以直接break,啥也不用设.对于high speed的hub,其bDeviceProtocol为1表示是single tt的.为2表示是multiple tt的.对于case 2.这里调用了usb_set_interface,根据usb spec 2.0,11.23.1一节,对于full/low speed的hub,其device descriptor中的bDeviceProtocol为0,而interface descriptor中的bInterfaceProtocol也为0.而对于high speed的hub,其中,single TT的hub其device descriptor中的bDeviceProtocol是1,而interface descriptor的bInterfaceProtocol则是0.然而,multiple TT hub另外还有一个interface descriptor以及相应的一个endpoint descriptor,它的device descriptor的bDeviceProtocol必须设置成2.其第一个interface descriptor的bInterfaceProtocol为1.而第二个interface descriptor的bInterfaceProtocol则是2.hubs只有一个interface,但是可以有两种setting.usb_set_interface就是把这个interface(interface 0)设置成setting 1.因为默认都是setting 0.关于SET_INTERFACE这个request,是usb spec 2.0的一个错误,errata里边有更正.另外,hub->tt.hub就是struct usb_device的结构体指针.hub->tt.multi就是一个int型的flag,设为1就表示这是一个multi tt的hub.

716行, HUB_CHAR_TTTT,后两个TT就是think time的意思.就是说TT在处理两个低速/全速的交易之间需要一点点时间来缓冲.这个最大的间隔就叫做TT think time.这个时间当然不会很长.不过需要注意,这里用的单位是FS bit time,我们知道FS就是Full Speed,其速度是12Mbps,其实也就是1200 0000bps,8 FS bit time就是8bits / 1200 0000 bits per second,即约等于666ns.所以这里就用666ns来记录了.不过以后你会发现,其实think_time这个值我们就没用过.

746到749行, HUB_CHAR_PORTIND,这个表征一个叫做port indicator的冬冬.0说明不支持,1说明支持.indicator是干嘛用的?考考你.虽说六级只考了69分,可是我还是知道indicator就是hub上面的那个指示灯,一闪一闪亮晶晶的指示灯.通常是两种颜色,绿色和琥珀色.你是不是还经常看见红色?这我不发表评论,其实什么颜色无所谓,萝卜白菜各有所爱,不过usb spec上面是给出的这两种颜色.具体实现其实就是一个LED灯,提供两种颜色,或者两个LED灯.有一定生活常识的人就应该知道,其实大多数hub是有指示灯的,不管usb hub还是别的hub,或者switch什么的,统统有指示灯,因为指示灯对于工程师调试硬件产品是很有帮助的.产品出了问题,首先看看指示灯也许就知道怎么回事了,我记得以前在家里上网的时候,网络坏了,打上海电信的电话,人家首先就是问我那几个指示灯是如何亮的.所以说,不支持port indicator的公司一定是脑子进水了.

757行,usb_get_status(),来自drivers/usb/core/message.c:

878 /**

879 * usb_get_status – issues a GET_STATUS call

880 * @dev: the device whose status is being checked

881 * @type: USB_RECIP_*; for device, interface, or endpoint

882 * @target: zero (for device), else interface or endpoint number

883 * @data: pointer to two bytes of bitmap data

884 * Context: !in_interrupt ()

885 *

886 * Returns device, interface, or endpoint status. Normally only of

887 * interest to see if the device is self powered, or has enabled the

888 * remote wakeup facility; or whether a bulk or interrupt endpoint

889 * is halted ("stalled").

890 *

891 * Bits in these status bitmaps are set using the SET_FEATURE request,

892 * and cleared using the CLEAR_FEATURE request. The usb_clear_halt()

893 * function should be used to clear halt ("stall") status.

894 *

895 * This call is synchronous, and may not be used in an interrupt context.

896 *

897 * Returns the number of bytes received on success, or else the status code

898 * returned by the underlying usb_control_msg() call.

899 */

900 int usb_get_status(struct usb_device *dev, int type, int target, void *data)

901 {

902 int ret;

903 u16 *status = kmalloc(sizeof(*status), GFP_KERNEL);

904

905 if (!status)

906 return -ENOMEM;

907

908 ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),

909 USB_REQ_GET_STATUS, USB_DIR_IN | type, 0, target, status,

910 sizeof(*status), USB_CTRL_GET_TIMEOUT);

911

912 *(u16 *)data = *status;

913 kfree(status);

914 return ret;

915 }

又是一个控制传输,发送的是一个控制请求,GET_STATUS是USB的标准请求之一,

这个请求的类型有三种,一种是获得Device的状态,一种是获得Interface的状态,一种是获得端点的状态,这里咱们传递的是USB_RECIP_DEVICE,也就是获得Device的状态.那么函数返回之后,Device的状态就被保存在了hubstatus里面了.

763至788行,就是一个if/elseif/else组合.首先if判断当年这个device是不是root hub,如果是root hub,然后判断hdev->bus_mA,这个值是在host controller的驱动程序中设置的,通常来讲,计算机的usb端口可以提供500mA的电流,不过host controller那边有一个成员power_budget,在host controller的驱动程序中,root hub的hdev->bus_mA被设置为500mA与power_budget中较小的那一个,即如果你有兴趣看一下drivers/usb/core/hcd.c,你会注意到在usb_add_hcd这个函数中有这么两行,

1637 /* starting here, usbcore will pay attention to this root hub */

1638 rhdev->bus_mA = min(500u, hcd->power_budget);

power_budget是一个host controller自己提供的,它可以是0,如果是0,表示没有限制.所以我们这里判断是不是等于0,或者是不是大于等于500mA,如果是的,那么就设置hub->mA_per_port为500mA,mA_per_port就是提供给每一个port的电流.那么如果说bus_mA是0到500之间的某个数,那么说明这个hub没法提供达到500mA的电流,就是host controller那边提供不了这么大的电流,那么hub->mA_per_port就设置为hdev->bus_mA,这种小市民的想法很简单,钱多就多花点,钱少就少花点,没钱就不花.同时,对于这种host controller那边限制了电流的情况,记录下来, hub->limited_power这么一个标志位设置为1.

那么如果不是root hub呢,又有两种情况, USB_DEVICE_SELF_POWERED,hubstatus里的这一位表征这个hub是不是自己供电的,因为外接的hub也有两种供电方式,自己供电或者选择吃大锅饭的形式,即请求总线供电.770行如果满足,那就说明这又是一个要总线供电的hub,于是limited_power也设置为1.774行,maxchild>0那简直是一定的.然后定义了一个int变量remaining来记录剩下多少电流,hdev->bus_mA就是这个hub(不是root hub)上行口的电流,而bHubContrCurrent我们说过了,就是hub需要的电流.两者相减就是剩下的.但是在usb端口上,最小的电流负载就是100mA,这个叫做单元负载(unit load),这个我还是比较清楚的,怎么说大学四年第一次考进系里前5名的课程就是模拟电子线路的期中考试,至今还记得,88分.不吹了,继续,所以778行的意思很显然,比如你这个hub有四个口,即maxchild为4,那么你最起码你得剩下个400mA电流吧,因为如果某个端口电流小于100mA的话,设备是没法正常工作的.然后,782行,警告归警告,最终还是设置mA_per_port为100mA.

784行,如果是自己供电的那种hub,那没得说,直接设置为500mA吧.

793行,hub_hub_status(),这个函数还是来自drivers/usb/core/hub.c:

531 static int hub_hub_status(struct usb_hub *hub,

532 u16 *status, u16 *change)

533 {

534 int ret;

535

536 mutex_lock(&hub->status_mutex);

537 ret = get_hub_status(hub->hdev, &hub->status->hub);

538 if (ret < 0)

539 dev_err (hub->intfdev,

540 "%s failed (err = %d)/n", __FUNCTION__, ret);

541 else {

542 *status = le16_to_cpu(hub->status->hub.wHubStatus);

543 *change = le16_to_cpu(hub->status->hub.wHubChange);

544 ret = 0;

545 }

546 mutex_unlock(&hub->status_mutex);

547 return ret;

548 }

和刚才那个get_hub_status不一样的是,刚才那个GET_STATUS是标准的usb设备请求,每个设备都会有的,但是现在这个请求是hub自己定义的,其格式如下图所示:

最后状态保存在status和change里面,即hubstatus和hubchange里面.而函数hub_hub_status的返回值正常就是0,否则就是负的错误码.

799至807行都是打印调试信息.飘过.

809行开始就是真正的干正经事了.我们知道usb-storage里面最常见的传输方式就是control/bulk传输,而对于hub,它的传输方式就是control/interrupt,而最有特色的正是它的中断传输.注意咱们在调用hub_configure的时候传递进来的第二个参数是endpoint,前面我们已经说了,这正是代表着hub的中断端点,所以815行,一路走过来的兄弟们应该一眼就能看出这一行就是获得连接host与这个端点的这条管道,这条中断传输的管道.不过注意,hub里面的中断端点一定是IN的而不是OUT的.别问我,usb spec就这么规定的,我想改人家也不同意.

816行,usb_maxpacket我们倒是第一次遇见,其实它就是获得一个endpoint描述符里面的wMaxPacketSize,赋给maxp,一个端点一次最多传输的数据就是wMaxPacketSize.不可以超过它.咱们前面为hub->buffer申请了内存,这里maxp如果大于这个size,那么不可以,就让它等于hub->buffer的size.

然后821行开始就是老套路了,申请一个urb,然后填充一个urb,usb_alloc_urb()不用多说,usb_fill_int_urb()可以看一下,来自include/linux/usb.h:

1224 /**

1225 * usb_fill_int_urb – macro to help initialize a interrupt urb

1226 * @urb: pointer to the urb to initialize.

1227 * @dev: pointer to the struct usb_device for this urb.

1228 * @pipe: the endpoint pipe

1229 * @transfer_buffer: pointer to the transfer buffer

1230 * @buffer_length: length of the transfer buffer

1231 * @complete_fn: pointer to the usb_complete_t function

1232 * @context: what to set the urb context to.

1233 * @interval: what to set the urb interval to, encoded like

1234 * the endpoint descriptor’s bInterval value.

1235 *

1236 * Initializes a interrupt urb with the proper information needed to submit

1237 * it to a device.

1238 * Note that high speed interrupt endpoints use a logarithmic encoding of

1239 * the endpoint interval, and express polling intervals in microframes

1240 * (eight per millisecond) rather than in frames (one per millisecond).

1241 */

1242 static inline void usb_fill_int_urb (struct urb *urb,

1243 struct usb_device *dev,

1244 unsigned int pipe,

1245 void *transfer_buffer,

1246 int buffer_length,

1247 usb_complete_t complete_fn,

1248 void *context,

1249 int interval)

1250 {

1251 spin_lock_init(&urb->lock);

1252 urb->dev = dev;

1253 urb->pipe = pipe;

1254 urb->transfer_buffer = transfer_buffer;

1255 urb->transfer_buffer_length = buffer_length;

1256 urb->complete = complete_fn;

1257 urb->context = context;

1258 if (dev->speed == USB_SPEED_HIGH)

1259 urb->interval = 1 << (interval – 1);

1260 else

1261 urb->interval = interval;

1262 urb->start_frame = -1;

1263 }

对比一下形参和实参,重点关注这么几项,transfer_buffer就是hub->buffer,transfer_buffer_length就是maxp,complete就是hub_irq,context被赋为了hub,而interval被赋为endpoint->bInterval,不过这里做了一次判断,如果是高速设备(高速hub),那么interval就等于多少多少,否则interval又等于多少多少.至于为什么不一样,其实是因为单位不一样,早年,提到usb协议,人们会提到frame,即帧,改革开放之后,出现了一个新的名词,叫做微帧,即microframe.一个帧是1毫秒,而一个微帧是八分之一毫秒,也就是125微秒.要知道我们刚才说了,这里传递进来的interval实际上是endpoint->bInterval,要深刻认识这段代码背后的哲学意义,需要知道usb中断传输究竟是怎么进行的.

但我想说,我做了一个善良的平凡女子,并且一直在爱,

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

相关文章:

你感兴趣的文章:

标签云: