您的位置:首页 > 运维架构 > Linux

Linux那些事儿之我是Hub(15)一个都不能少

2007-08-30 23:56 351 查看
烟波江声里,何处是江南.一晃神,一转眼,我们就这样垂垂老去.可是我们直到现在还根本就没有看明白hub驱动究竟是怎么工作的.但我相信,红莲即将绽放,双星终会汇聚,命运的轮转已经开始,我们只需耐心的等待.
2686行,苦苦追寻之后,终于发现从这里开始针对端口进行分析了,有几个端口就对几个端口进行分析,分析每一个端口的状态变化,一个都不能少,很显然,这就是我们期待看到的代码,马上我们就可以知道,当我们把一个usb设备插入usb端口之后究竟会发生什么,知道usb设备提供的那些接口函数究竟是如何被调用的,特别是那个probe函数.这一刻,红领巾迎着太阳,阳光洒在海面上,水中鱼儿望着我们,悄悄的听我们愉快的歌唱,小船儿轻轻飘荡在水中,迎面吹来了凉爽的风!
bNbrports是前面我们获得的hub descriptor的一个成员,表征这个hub有几个端口.很显然,月可无光,星可无语,usb设备却不可以没有描述符.这里就是遍历每一个端口.busy_bits是struct usb_hub的一个成员,unsigned long busy_bits[1],接下来的event_bits也是,change_bits也是,unsigned long event_bits[1],unsigned long change_bits[1],test_bit()我们太熟悉了,在usb-storage里面到处都是,只不过当时我们测试的是us->flags里面的某个flag是否设置了,而这里我们要测试的有三个东东,首先测试busy_bits,这个flag实际上只有在reset和resume的函数内部才会设置,所以这里我们先不用管,而这里的意思是,如果眼下这个端口正在执行reset或者resume操作,那么咱们就跳过去,不予理睬.
2689行,测试change_bits.结合2690,2691,2692行一起看.如果这个端口对应的change_bits没有设置,event_bits没有设置过,hub->activating也为0,那么这里就执行continue,不过我们想都不用想,因为我们就是从hub_activate进来的.我们来的时候activating就是设置成了1的,所以这里的continue我们是不用执行的.换言之,我们继续往下走.
2694行,hub_port_status(),portstatus和portchange是我们在hub_events()伊始定义的两个变量,u16 portstatus,u16 portchange,即两个都是16位.尽管说了N遍了,但是我还是得说第N+1遍,这个函数仍然是来自drivers/usb/core/hub.c:
1413 static int hub_port_status(struct usb_hub *hub, int port1,
1414 u16 *status, u16 *change)
1415 {
1416 int ret;
1417
1418 mutex_lock(&hub->status_mutex);
1419 ret = get_port_status(hub->hdev, port1, &hub->status->port);
1420 if (ret < 4) {
1421 dev_err (hub->intfdev,
1422 "%s failed (err = %d)/n", __FUNCTION__, ret);
1423 if (ret >= 0)
1424 ret = -EIO;
1425 } else {
1426 *status = le16_to_cpu(hub->status->port.wPortStatus);
1427 *change = le16_to_cpu(hub->status->port.wPortChange);
1428 ret = 0;
1429 }
1430 mutex_unlock(&hub->status_mutex);
1431 return ret;
1432 }
重要的是其中的那个get_port_status()函数.
300 /*
301 * USB 2.0 spec Section 11.24.2.7
302 */
303 static int get_port_status(struct usb_device *hdev, int port1,
304 struct usb_port_status *data)
305 {
306 int i, status = -ETIMEDOUT;
307
308 for (i = 0; i < USB_STS_RETRIES && status == -ETIMEDOUT; i++) {
309 status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
310 USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1,
311 data, sizeof(*data), USB_STS_TIMEOUT);
312 }
313 return status;
314 }
一路从泥泞走到美景,我们再也不会对usb_control_msg()函数陌生了,这个函数做什么勾当我们完全是一目了然.Get Port Status是Hub的一个标准请求,对我们来说就是一次控制传输就可以搞定.这个请求的格式如下图所示:

其中这个GET_STATUS的对应具体的数值可以在下面这张表中对比得到,

而关于各种请求,咱们在include/linux/usb/ch9.h中也定义了相应的宏,
72 /*
73 * Standard requests, for the bRequest field of a SETUP packet.
74 *
75 * These are qualified by the bRequestType field, so that for example
76 * TYPE_CLASS or TYPE_VENDOR specific feature flags could be retrieved
77 * by a GET_STATUS request.
78 */
79 #define USB_REQ_GET_STATUS 0x00
80 #define USB_REQ_CLEAR_FEATURE 0x01
81 #define USB_REQ_SET_FEATURE 0x03
82 #define USB_REQ_SET_ADDRESS 0x05
83 #define USB_REQ_GET_DESCRIPTOR 0x06
84 #define USB_REQ_SET_DESCRIPTOR 0x07
85 #define USB_REQ_GET_CONFIGURATION 0x08
86 #define USB_REQ_SET_CONFIGURATION 0x09
87 #define USB_REQ_GET_INTERFACE 0x0A
88 #define USB_REQ_SET_INTERFACE 0x0B
89 #define USB_REQ_SYNCH_FRAME 0x0C
90
91 #define USB_REQ_SET_ENCRYPTION 0x0D /* Wireless USB */
92 #define USB_REQ_GET_ENCRYPTION 0x0E
93 #define USB_REQ_RPIPE_ABORT 0x0E
94 #define USB_REQ_SET_HANDSHAKE 0x0F
95 #define USB_REQ_RPIPE_RESET 0x0F
96 #define USB_REQ_GET_HANDSHAKE 0x10
97 #define USB_REQ_SET_CONNECTION 0x11
98 #define USB_REQ_SET_SECURITY_DATA 0x12
99 #define USB_REQ_GET_SECURITY_DATA 0x13
100 #define USB_REQ_SET_WUSB_DATA 0x14
101 #define USB_REQ_LOOPBACK_DATA_WRITE 0x15
102 #define USB_REQ_LOOPBACK_DATA_READ 0x16
103 #define USB_REQ_SET_INTERFACE_DS 0x17
比如这里咱们传递给usb_control_msg的request就是USB_REQ_GET_STATUS,它的值为0,和usb spec中定义的GET_STATUS的值是对应的.这个请求返回两个咚咚,一个称为Port Status,一个称为Port Change Status.usb_control_msg()的调用注定了返回值将保存在struct usb_port_status *data里面,这个结构体定义与drivers/usb/core/hub.h中:
58 /*
59 * Hub Status and Hub Change results
60 * See USB 2.0 spec Table 11-19 and Table 11-20
61 */
62 struct usb_port_status {
63 __le16 wPortStatus;
64 __le16 wPortChange;
65 } __attribute__ ((packed));
很显然这个格式是和实际的spec规范对应的,我们给get_port_status()传递的实参是&hub->status->port,port也是一个struct usb_port_status结构体变量,所以在hub_port_status()里面,1426和1427两行我们就得到了Status Bits和Status Change Bits.get_port_status()返回值就是GET PORT STATUS请求的返回数据的长度,它至少应该能够保存wPortStatus和wPortChange,所以至少不能小于4,所以1420行有这么一个错误判断.这样,hub_port_status()就返回了,而status和change这两个指针也算是满载而归了,正常的话返回值就是0.
继续往下走,2699行,children[i-1],这么一个冬冬我们从没有见过,但是我想白痴都知道,正是像parent和children这样的指针才能把USB树给建立起来,而我们才刚上路,肯定还没有设置children,所以对我们来说,至少目前children数组肯定为空,而我们又知道hub->activating这时候肯定为1,所以就看第三个条件了,portstatus&USB_PORT_STAT_CONNECTION,这是啥意思?稍有悟性的人就能看出来,这表明这个端口连了设备,没错,USB_PORT_STAT_CONNECTION这个宏定义于drivers/usb/core/hub.h中:
67 /*
68 * wPortStatus bit field
69 * See USB 2.0 spec Table 11-21
70 */
71 #define USB_PORT_STAT_CONNECTION 0x0001
72 #define USB_PORT_STAT_ENABLE 0x0002
73 #define USB_PORT_STAT_SUSPEND 0x0004
74 #define USB_PORT_STAT_OVERCURRENT 0x0008
75 #define USB_PORT_STAT_RESET 0x0010
76 /* bits 5 to 7 are reserved */
77 #define USB_PORT_STAT_POWER 0x0100
78 #define USB_PORT_STAT_LOW_SPEED 0x0200
79 #define USB_PORT_STAT_HIGH_SPEED 0x0400
80 #define USB_PORT_STAT_TEST 0x0800
81 #define USB_PORT_STAT_INDICATOR 0x1000
82 /* bits 13 to 15 are reserved */
83
84 /*
85 * wPortChange bit field
86 * See USB 2.0 spec Table 11-22
87 * Bits 0 to 4 shown, bits 5 to 15 are reserved
88 */
89 #define USB_PORT_STAT_C_CONNECTION 0x0001
90 #define USB_PORT_STAT_C_ENABLE 0x0002
91 #define USB_PORT_STAT_C_SUSPEND 0x0004
92 #define USB_PORT_STAT_C_OVERCURRENT 0x0008
93 #define USB_PORT_STAT_C_RESET 0x0010
这都是这两个变量对应的宏,usb 2.0的spec里面对这些宏的意义说得很清楚,USB_PORT_STAT_CONNECTION的意思的确是表征是否有设备连接在这个端口上,我们不妨假设有,那么portstatus和它相与的结果就是1,在usb spec里面,这一位叫做Current Connect Status位,于是这里我们会看到connect_change被设置成了1.而接下来,USB_PORT_STAT_C_CONNECTION则是表征这个端口的Current Connect Status位是否有变化,如果有变化,那么portchange和USB_PORT_STAT_C_CONNECTION相与的结果就是1,对于这种情况,我们需要发送另一个请求以清除这个flag,并且将connect_change也设置为1.这个请求叫做Clear Port Feature.这个请求也是Hub的标准请求,

它的作用就是reset hub端口的某种feature.clear_port_feature()定义于drivers/usb/core/hub.c:
162 /*
163 * USB 2.0 spec Section 11.24.2.2
164 */
165 static int clear_port_feature(struct usb_device *hdev, int port1, int feature)
166 {
167 return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
168 USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1,
169 NULL, 0, 1000);
170 }
对比上面贴出来的定义可知,USB_REQ_CLEAR_FEATURE和usb spec中的CLEAR_FEATURE这个请求是对应的,那么一共有些什么Feature呢?在drivers/usb/core/hub.h中是这样定义的,
38 /*
39 * Port feature numbers
40 * See USB 2.0 spec Table 11-17
41 */
42 #define USB_PORT_FEAT_CONNECTION 0
43 #define USB_PORT_FEAT_ENABLE 1
44 #define USB_PORT_FEAT_SUSPEND 2
45 #define USB_PORT_FEAT_OVER_CURRENT 3
46 #define USB_PORT_FEAT_RESET 4
47 #define USB_PORT_FEAT_POWER 8
48 #define USB_PORT_FEAT_LOWSPEED 9
49 #define USB_PORT_FEAT_HIGHSPEED 10
50 #define USB_PORT_FEAT_C_CONNECTION 16
51 #define USB_PORT_FEAT_C_ENABLE 17
52 #define USB_PORT_FEAT_C_SUSPEND 18
53 #define USB_PORT_FEAT_C_OVER_CURRENT 19
54 #define USB_PORT_FEAT_C_RESET 20
55 #define USB_PORT_FEAT_TEST 21
56 #define USB_PORT_FEAT_INDICATOR 22
而在usb spec中,有一张与之相对应的表格,定义了许多的feature,如图所示:

所以,我们清除的是C_PORT_CONNECTION这一个feature.spec里面说了,清除一个状态改变的feature就等于承认这么一个feature.(clearing that status change acknowledges the change)理由很简单,每次你检测到一个flag被设置之后,你都应该清除掉它,以便下次人家一设你就知道是人家设了,否则你不清你下次判断你就不知道是不是又有人设了.同理,接下来的每个与portchange相关的判断语句都要这么做.所以如果portchange与上USB_PORT_STAT_C_CONNECTION确实为1,那么我们就要清除这个feature.同时我们当然也要记录connect_change为1.
继续,每个端口都有一个开关,这叫做enable或者disable一个端口.portchange和USB_PORT_STAT_C_ENABLE相与如果为1的话,说明端口开关有变化.和刚才一样,首先我们要做的是,清除掉这个变化的feature.但是这里需要注意,spec里对这个feature是这样规定的,如果portchange和USB_PORT_STAT_C_ENABLE为1,说明这个port是从enable状态进入了disable状态.为什么呢?因为在spec规定了,Hub的端口是不可以直接设置成enable的.通常让Hub端口enable的方法是reset hub port.用spec的话说,这叫做发送另一个request,名为SET_FEATURE.SET_FEATURE和CLEAR_FEATURE是对应的,一个设置一个清除. 对于PORT_ENABLE这一位,用spec里的话说,This bit may be set only as a result of a SetPortFeature(PORT_RESET) request,PORT_RESET是为hub定义的众多feature中的一种.最后提醒一点,2711至2715这段if语句仅仅是为了打印调试信息的,就是说如果port enable改变了,但是端口连接没有改变,那么打印出信息来通知调试者,不要把clear_port_feature这一行也纳入到if语句里去了.因为port enable的改变有多种可能,其中一种可能就是由于检测到了disconnection,但是对于这种情况,我们下面要处理的,所以甭急.
下面这段代码就比较帅了,电磁干扰都给扯进来了.EMI,就是电磁干扰.就是说有的时候hub port的enable变成disable有可能是由于电磁干扰造成的,这个if条件判断的是,端口被disable了,但是连接没有变化,并且hdev->children[i]还有值,这就说明明明有子设备连在端口上,可是端口却被disable了,基本上这种情况就是电磁干扰造成的,否则hub端口不会有这种抽风的举动.那么这种情况就设置connect_change为1.因为接下来我们会看到对于connect_change为1的情况,我们会专门进行处理,而更犀利更一针见血的说法就是,hub_events()其实最重要的任务就是对于端口连接有变化的情况进行处理,或者说进行响应.
再往下,portchange和USB_PORT_STAT_C_SUSPEND相与如果为1,表明连在该端口的设备的suspend状态有变化,并且是从suspended状态出来,也就是说resume完成.(别问我为什么,spec就这么规定的,没什么理由,一定要问理由那你问郑源去,他不是唱那什么如果你真的需要什么理由,一万个够不够吗.)那么首先我们就调用clear_port_feature清掉这个feature.接下来这个if牵扯到的东西比较高深,涉及到电源管理中很华丽的部分,我们只能先跳过.否则深陷其中难免会走火入魔欲罢不能.总之这里做的就是对于该端口连了子设备的情况就把子设备唤醒,否则如果端口没有连子设备,那么就把端口disable掉.
2754行,portchange如果和USB_PORT_STAT_C_OVERCURRENT相与结果为1的话,说明这个端口可能曾经存在电流过大的情况,而现在这种情况不存在了,或者本来不存在而现在存在了.对此我们能做的就是首先清除这个feature.有一种比较特别的情况是,如果其它的端口电流过大,那么将会导致本端口断电,即hub上一个端口出现over-current条件将有可能引起hub上其它端口陷入powered off的状态.不管怎么说,对于over-current的情况我们都把hub重新上电,执行hub_power_on().
2763行,portchange如果和USB_PORT_STAT_C_RESET相与为1的话,这叫做一个端口从Resetting状态进入到Enabled状态.
2771行,connect_change如果为1,就执行hub_port_connect_change(),啥也不说了,这是每一个看hub驱动的人最期待的函数,因为这正是我们的原始动机,即当一个usb设备插入usb接口之后究竟会发生什么,usb设备驱动程序提供那个probe函数究竟是如何被调用的.这些疑问统统会在这个函数里得到答案.来自drivers/usb/core/hub.c:
2404 /* Handle physical or logical connection change events.
2405 * This routine is called when:
2406 * a port connection-change occurs;
2407 * a port enable-change occurs (often caused by EMI);
2408 * usb_reset_device() encounters changed descriptors (as from
2409 * a firmware download)
2410 * caller already locked the hub
2411 */
2412 static void hub_port_connect_change(struct usb_hub *hub, int port1,
2413 u16 portstatus, u16 portchange)
2414 {
2415 struct usb_device *hdev = hub->hdev;
2416 struct device *hub_dev = hub->intfdev;
2417 u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
2418 int status, i;
2419
2420 dev_dbg (hub_dev,
2421 "port %d, status %04x, change %04x, %s/n",
2422 port1, portstatus, portchange, portspeed (portstatus));
2423
2424 if (hub->has_indicators) {
2425 set_port_led(hub, port1, HUB_LED_AUTO);
2426 hub->indicator[port1-1] = INDICATOR_AUTO;
2427 }
2428
2429 /* Disconnect any existing devices under this port */
2430 if (hdev->children[port1-1])
2431 usb_disconnect(&hdev->children[port1-1]);
2432 clear_bit(port1, hub->change_bits);
2433
2434 #ifdef CONFIG_USB_OTG
2435 /* during HNP, don't repeat the debounce */
2436 if (hdev->bus->is_b_host)
2437 portchange &= ~USB_PORT_STAT_C_CONNECTION;
2438 #endif
2439
2440 if (portchange & USB_PORT_STAT_C_CONNECTION) {
2441 status = hub_port_debounce(hub, port1);
2442 if (status < 0) {
2443 if (printk_ratelimit())
2444 dev_err (hub_dev, "connect-debounce failed, "
2445 "port %d disabled/n", port1);
2446 goto done;
2447 }
2448 portstatus = status;
2449 }
2450
2451 /* Return now if nothing is connected */
2452 if (!(portstatus & USB_PORT_STAT_CONNECTION)) {
2453
2454 /* maybe switch power back on (e.g. root hub was reset) */
2455 if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
2456 && !(portstatus & (1 << USB_PORT_FEAT_POWER)))
2457 set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
2458
2459 if (portstatus & USB_PORT_STAT_ENABLE)
2460 goto done;
2461 return;
2462 }
2463
2464 #ifdef CONFIG_USB_SUSPEND
2465 /* If something is connected, but the port is suspended, wake it up. */
2466 if (portstatus & USB_PORT_STAT_SUSPEND) {
2467 status = hub_port_resume(hub, port1, NULL);
2468 if (status < 0) {
2469 dev_dbg(hub_dev,
2470 "can't clear suspend on port %d; %d/n",
2471 port1, status);
2472 goto done;
2473 }
2474 }
2475 #endif
2476
2477 for (i = 0; i < SET_CONFIG_TRIES; i++) {
2478 struct usb_device *udev;
2479
2480 /* reallocate for each attempt, since references
2481 * to the previous one can escape in various ways
2482 */
2483 udev = usb_alloc_dev(hdev, hdev->bus, port1);
2484 if (!udev) {
2485 dev_err (hub_dev,
2486 "couldn't allocate port %d usb_device/n",
2487 port1);
2488 goto done;
2489 }
2490
2491 usb_set_device_state(udev, USB_STATE_POWERED);
2492 udev->speed = USB_SPEED_UNKNOWN;
2493 udev->bus_mA = hub->mA_per_port;
2494 udev->level = hdev->level + 1;
2495
2496 /* set the address */
2497 choose_address(udev);
2498 if (udev->devnum <= 0) {
2499 status = -ENOTCONN; /* Don't retry */
2500 goto loop;
2501 }
2502
2503 /* reset and get descriptor */
2504 status = hub_port_init(hub, udev, port1, i);
2505 if (status < 0)
2506 goto loop;
2507
2508 /* consecutive bus-powered hubs aren't reliable; they can
2509 * violate the voltage drop budget. if the new child has
2510 * a "powered" LED, users should notice we didn't enable it
2511 * (without reading syslog), even without per-port LEDs
2512 * on the parent.
2513 */
2514 if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
2515 && udev->bus_mA <= 100) {
2516 u16 devstat;
2517
2518 status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
2519 &devstat);
2520 if (status < 2) {
2521 dev_dbg(&udev->dev, "get status %d ?/n", status);
2522 goto loop_disable;
2523 }
2524 le16_to_cpus(&devstat);
2525 if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
2526 dev_err(&udev->dev,
2527 "can't connect bus-powered hub "
2528 "to this port/n");
2529 if (hub->has_indicators) {
2530 hub->indicator[port1-1] =
2531 INDICATOR_AMBER_BLINK;
2532 schedule_delayed_work (&hub->leds, 0);
2533 }
2534 status = -ENOTCONN; /* Don't retry */
2535 goto loop_disable;
2536 }
2537 }
2538
2539 /* check for devices running slower than they could */
2540 if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
2541 && udev->speed == USB_SPEED_FULL
2542 && highspeed_hubs != 0)
2543 check_highspeed (hub, udev, port1);
2544
2545 /* Store the parent's children[] pointer. At this point
2546 * udev becomes globally accessible, although presumably
2547 * no one will look at it until hdev is unlocked.
2548 */
2549 status = 0;
2550
2551 /* We mustn't add new devices if the parent hub has
2552 * been disconnected; we would race with the
2553 * recursively_mark_NOTATTACHED() routine.
2554 */
2555 spin_lock_irq(&device_state_lock);
2556 if (hdev->state == USB_STATE_NOTATTACHED)
2557 status = -ENOTCONN;
2558 else
2559 hdev->children[port1-1] = udev;
2560 spin_unlock_irq(&device_state_lock);
2561
2562 /* Run it through the hoops (find a driver, etc) */
2563 if (!status) {
2564 status = usb_new_device(udev);
2565 if (status) {
2566 spin_lock_irq(&device_state_lock);
2567 hdev->children[port1-1] = NULL;
2568 spin_unlock_irq(&device_state_lock);
2569 }
2570 }
2571
2572 if (status)
2573 goto loop_disable;
2574
2575 status = hub_power_remaining(hub);
2576 if (status)
2577 dev_dbg(hub_dev, "%dmA power budget left/n", status);
2578
2579 return;
2580
2581 loop_disable:
2582 hub_port_disable(hub, port1, 1);
2583 loop:
2584 ep0_reinit(udev);
2585 release_address(udev);
2586 usb_put_dev(udev);
2587 if (status == -ENOTCONN)
2588 break;
2589 }
2590
2591 done:
2592 hub_port_disable(hub, port1, 1);
2593 }
到今天我算是看明白了,内核里面这些函数,没有最变态只有更变态,变态哪都有,可是开源社区尤其多!你们他妈的不是我的冤家派来故意玩我的吧?面对这个函数,我真的想吐血!我打算不像过去那样一行一行讲了,我必须先来个提纲挈领,必须先开门见山把这个函数的哲学思想讲清楚,否则一行一行往下讲肯定晕菜.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: