• USB之hub3


    =============  本系列参考  =============

    《圈圈教你玩USB》、《Linux那些事儿之我是USB》

    协议文档:https://www.usb.org/document-library/usb-20-specification  usb_20_20190524/usb_20.pdf

    调试工具:Beagle USB 480 逻辑分析仪、sys/kernel/debug/usb/usbmon/

    代码:linux-3.10.65/drivers/usb/core/hub.c

    ====================================

    前言

      由于USB设备是先被hub识别的, 所以这次先分析hub代码, 与前面两篇博文连贯起来

    一、 hub加载

      在USB子系统核心模块被调用:

    /* drivers/usb/core/usb.c */
    static int __init usb_init(void)
    {
        int retval;
        if (nousb) {
            pr_info("%s: USB support disabled
    ", usbcore_name);
            return 0;
        }
    
        retval = usb_debugfs_init();
        if (retval)
            goto out;
    
        usb_acpi_register();
        retval = bus_register(&usb_bus_type);
        if (retval)
            goto bus_register_failed;
        retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
        if (retval)
            goto bus_notifier_failed;
        retval = usb_major_init();
        if (retval)
            goto major_init_failed;
        retval = usb_register(&usbfs_driver);
        if (retval)
            goto driver_register_failed;
        retval = usb_devio_init();
        if (retval)
            goto usb_devio_init_failed;
        retval = usb_hub_init();
        if (retval)
            goto hub_init_failed;
        retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
        if (!retval)
            goto out;
    
        usb_hub_cleanup();
    hub_init_failed:
        usb_devio_cleanup();
    usb_devio_init_failed:
        usb_deregister(&usbfs_driver);
    driver_register_failed:
        usb_major_cleanup();
    major_init_failed:
        bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
    bus_notifier_failed:
        bus_unregister(&usb_bus_type);
    bus_register_failed:
        usb_acpi_unregister();
        usb_debugfs_cleanup();
    out:
        return retval;
    }
    
    /*
     * Cleanup
     */
    static void __exit usb_exit(void)
    {
        /* This will matter if shutdown/reboot does exitcalls. */
        if (nousb)
            return;
    
        usb_deregister_device_driver(&usb_generic_driver);
        usb_major_cleanup();
        usb_deregister(&usbfs_driver);
        usb_devio_cleanup();
        usb_hub_cleanup();
        bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
        bus_unregister(&usb_bus_type);
        usb_acpi_unregister();
        usb_debugfs_cleanup();
    }
    
    subsys_initcall(usb_init);
    module_exit(usb_exit);
    MODULE_LICENSE("GPL");
    /* drivers/usb/core/hub.c */
    static struct usb_driver hub_driver = {
        .name =        "hub",
        .probe =    hub_probe,
        .disconnect =    hub_disconnect,
        .suspend =    hub_suspend,
        .resume =    hub_resume,
        .reset_resume =    hub_reset_resume,
        .pre_reset =    hub_pre_reset,
        .post_reset =    hub_post_reset,
        .unlocked_ioctl = hub_ioctl,
        .id_table =    hub_id_table,
        .supports_autosuspend =    1,
    };
    
    int usb_hub_init(void)
    {
        if (usb_register(&hub_driver) < 0) {
            printk(KERN_ERR "%s: can't register hub driver
    ",
                usbcore_name);
            return -1;
        }
    
        khubd_task = kthread_run(hub_thread, NULL, "khubd");
        if (!IS_ERR(khubd_task))
            return 0;
    
        /* Fall through if kernel_thread failed */
        usb_deregister(&hub_driver);
        printk(KERN_ERR "%s: can't start khubd
    ", usbcore_name);
    
        return -1;
    }

      

      usb_hub_init()就做两件事, 一是注册驱动--针对hub接口设备的驱动, 二是创建内核线程“khubd”, 我们后续会详解  

      记住hub本省也是个usb设备, 跟普通的U盘使用的都是同样的结构体, 当有hub设备被创建时,hub驱动的probe()将会match调用, 那问题来了,一个普通设备是被hub创建的, 那hub设备是谁创建的呢?

    很显然最初的root hub设备必须是静态创建的, 且这部分代码没放在hub.c, 而是放到了hcd.c, 可以看出一个Host必然有一个root hub, 是绑定的!

    int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags)
    {
        int retval;
        struct usb_device *rhdev;
    
    
        /* 1. 创建一个root hub设备 */
        if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {
            dev_err(hcd->self.controller, "unable to allocate root hub
    ");
            retval = -ENOMEM;
            goto err_allocate_root_hub;
        }
        /* 2. 让hcd与root hub 紧紧地绑在一起! */
        hcd->self.root_hub = rhdev;
    
    
    
        /* 3. 注册usb设备 */
        if ((retval = register_root_hub(hcd)) != 0)
            goto err_register_root_hub;
    
        return retval;
    
    } 
    EXPORT_SYMBOL_GPL(usb_add_hcd);
    
    =========================================
    /* root hub 设备默认就接在Host, 不是热拔插 */
    static int register_root_hub(struct usb_hcd *hcd)
    {
        struct device *parent_dev = hcd->self.controller;
        struct usb_device *usb_dev = hcd->self.root_hub;
        int retval;
    
        /* 4. 有效设备地址1~127, root hub默认使用地址1 */
        usb_dev->devnum = 1;
        /* 5. 直接进入地址阶段 */
        usb_set_device_state(usb_dev, USB_STATE_ADDRESS);
    
        /* 6. 直接设置ep0 size=64, 看来是协议规定的了 */
        usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
        /* 7. root hub 也是设备, 也要获取各种描述符 */
        retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);
        retval = usb_get_bos_descriptor(usb_dev);
        /* 8. 注册设备(是注册usb_device, 不是usb_interface) */
        retval = usb_new_device (usb_dev);
    
        return retval;
    }

      

    着重说明usb_generic_driver会与所有的usb_device进行match, 然后选择合适的配置描述符,设置配置描述符时自然就设置interace, 也即创建usb_interface, 这个接口设备才是各个驱动对应的设备, 比如我们现在讨论的hub_driver

    就是针对hub的usb_interface, 不是hub的usb_device,  usb_device代表是这个设备整体抽象, usb_interface代表是具体的某样功能, 需要具体的驱动操作。

      当注册一个USB Host时就静态创建root hub设备, 经过简单初始化后注册就会与usb_generic_driver 进行match创建具体的usb_interface, 当注册接口设备时就会match到hub_driver.probe(), 我们继续看看probe做了啥

    二、hub驱动probe()

    static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
    {
        struct usb_host_interface *desc;
        struct usb_endpoint_descriptor *endpoint;
        struct usb_device *hdev;
        struct usb_hub *hub;
    
        desc = intf->cur_altsetting;
        /* 1. 要求除了ep0, 必须有且只有一个ep 还是INT型 */
        if (desc->desc.bNumEndpoints != 1)
            goto descriptor_error;
    
        endpoint = &desc->endpoint[0].desc;
        if (!usb_endpoint_is_int_in(endpoint))
            goto descriptor_error;
    
        /* 2. 这是和普通设备的区别, 除了用usb_device描述外,hub还会创建usb_hub和usb_port */
        hub = kzalloc(sizeof(*hub), GFP_KERNEL);
        
        dev_info (&intf->dev, "USB hub found
    ");
        if (hub_configure(hub, endpoint) >= 0)
            return 0;
    }
    
            static int hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint)
            {
                /* 3. 主要获取hub有多少个port */
                ret = get_hub_descriptor(hdev, hub->descriptor);
                hdev->maxchild = hub->descriptor->bNbrPorts;
                /* 4. 这也是和普通外设的区别, 会创建port, 这里只是创建指针, 真正创建在后面 */
                hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *), GFP_KERNEL);
    
                /* 5. hub内部高速与全/低速不可兼容, 所以需要两部分传输电路, 根据需要进行切换
                      SINGLE_TT指的是hub只有一个转换电路针对整个hub,MULTI_TT表示每个port都有个TT转换电路可以针对每个port(土豪)
                      TTTT指的是转换电路后需要多少时间才稳定, 只有稳定了才可以传输数据*/
                switch (hdev->descriptor.bDeviceProtocol) {
                case USB_HUB_PR_FS:
                    break;
                case USB_HUB_PR_HS_SINGLE_TT:
                    dev_dbg(hub_dev, "Single TT
    ");
                    hub->tt.hub = hdev;
                    break;
                case USB_HUB_PR_HS_MULTI_TT:
                    ret = usb_set_interface(hdev, 0, 1);
                    if (ret == 0) {
                        dev_dbg(hub_dev, "TT per port
    ");
                        hub->tt.multi = 1;
                    } else
                        dev_err(hub_dev, "Using single TT (err %d)
    ",
                            ret);
                    hub->tt.hub = hdev;
                    break;
                case USB_HUB_PR_SS:
                    /* USB 3.0 hubs don't have a TT */
                    break;
                default:
                    dev_dbg(hub_dev, "Unrecognized hub protocol %d
    ",
                        hdev->descriptor.bDeviceProtocol);
                    break;
                }
    
                /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */
                switch (wHubCharacteristics & HUB_CHAR_TTTT) {
                    case HUB_TTTT_8_BITS:
                        if (hdev->descriptor.bDeviceProtocol != 0) {
                            hub->tt.think_time = 666;
                            dev_dbg(hub_dev, "TT requires at most %d "
                                    "FS bit times (%d ns)
    ",
                                8, hub->tt.think_time);
                        }
                        break;
                    case HUB_TTTT_16_BITS:
                        hub->tt.think_time = 666 * 2;
                        dev_dbg(hub_dev, "TT requires at most %d "
                                "FS bit times (%d ns)
    ",
                            16, hub->tt.think_time);
                        break;
                    case HUB_TTTT_24_BITS:
                        hub->tt.think_time = 666 * 3;
                        dev_dbg(hub_dev, "TT requires at most %d "
                                "FS bit times (%d ns)
    ",
                            24, hub->tt.think_time);
                        break;
                    case HUB_TTTT_32_BITS:
                        hub->tt.think_time = 666 * 4;
                        dev_dbg(hub_dev, "TT requires at most %d "
                                "FS bit times (%d ns)
    ",
                            32, hub->tt.think_time);
                        break;
                }
    
                /* 7. 申请一个urb并填充, 后续在hub_activate会调用usb_submit_urb */
                hub->urb = usb_alloc_urb(0, GFP_KERNEL);
                usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval);
    
                /* 8. 真正创建usb_port设备 */
                for (i = 0; i < hdev->maxchild; i++) {
                    ret = usb_hub_create_port_device(hub, i + 1);
                    if (ret < 0) {
                        dev_err(hub->intfdev,
                            "couldn't create port%d device.
    ", i + 1);
                        hdev->maxchild = i;
                        goto fail_keep_maxchild;
                    }
                }
    
                /* 9. 激活 */
                hub_activate(hub, HUB_INIT);
                return 0;
            }
    
    
                                    static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
                                    {
                                        struct usb_device *hdev = hub->hdev;
                                        for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
                                            struct usb_device *udev = hub->ports[port1 - 1]->child;
                                            u16 portstatus, portchange;
    
                                            portstatus = portchange = 0;
                                            status = hub_port_status(hub, port1, &portstatus, &portchange);
    
                                                if (udev || (portstatus & USB_PORT_STAT_CONNECTION) || (portstatus & USB_PORT_STAT_OVERCURRENT))
                                                    /* 10. 前面读取portstatus&portchange 就是为了标志change_bits, 因为内核线程会读取看有没有变化
                                                        其实内核线程也会调用hub_port_status,我觉得可以不需要change_bits, 之所以存在是线程khubd
                                                        一运行就第一时间知道有没有设备插入, 或者线程读之前是不是状态又发生了变化*/
                                                    set_bit(port1, hub->change_bits);
                                        
                                        }
                                        
                                        /* 11. 第一次提交urb,后续的提交就在urb的回调函数hub_irq()里调用 */
                                        status = usb_submit_urb(hub->urb, GFP_NOIO);
    
                                        /* 12. 其实submit后就会调用回调函数hub_irq(), 里面就会调用kick_khubd(hub),不知道为何这里重复调用一次 */
                                        kick_khubd(hub);
                                    }

      整个核心就是创建usb_hub结构体、获取hub描述符知道多少个port后又创建usb_port结构体、初始化TT转换电路、然后顺便读取状态看有没有外设插入, 之所以说是顺便是因为内核线程khubd才是主业做这个查询状态的, 无论是这次顺带读还是

    khubd读总得有个urb, 所以就申请一个urb,采用中断传输模式, 并触发第一次submit提交, 然后在回调函数再次提交urb

    三、 内核线程khubd

      上面probe()是针对每个hub设备都会有的行为, 创建usb_hub、usb_port,申请一个urb, 但有一个共同操作, 就是内核线程khubd, 所以它放在usb_hub_init()

    hub_thread()
        -> hub_events():
                /* 1. 处理每个hub, 并从链表删除这个hub */
                while (1) {
                    /* 2. 如果链表空了就退出 */
                    if (list_empty(&hub_event_list)) {
                        spin_unlock_irq(&hub_event_lock);
                        break;
                    }
    
                    tmp = hub_event_list.next;
                    list_del_init(tmp);
    
                    hub = list_entry(tmp, struct usb_hub, event_list);
                    hub_dev = hub->intfdev;
                    intf = to_usb_interface(hub_dev);
    
                    /* 3. 处理每个hub的每个port */
                    for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {
                        /* 4. 早前提到的顺带读取状态会操作change_bits */
                        connect_change = test_bit(i, hub->change_bits);
                        ret = hub_port_status(hub, i, &portstatus, &portchange);
                        printk("ret=%d, portstatus=0x%x, portchange=0x%x
    ", ret, portstatus, portchange);
    
                        if (portchange & USB_PORT_STAT_C_CONNECTION) {
                            usb_clear_port_feature(hdev, i,
                                USB_PORT_FEAT_C_CONNECTION);
                            connect_change = 1;
                        }
    
                        /* 5. 如果有设备插入, 则创建新的设备 */
                        if (connect_change)
                            hub_port_connect_change(hub, i, portstatus, portchange);
                    } /* end for i */
                } /* end while (1) */

      线程就是不断从链表取出hub, 然后扫描hub的所有port看有没有外设插入, 有的话就通过hub_port_connect_change()创建, 这样所有的hub,所有的port就都被访问到了, 那链表的hub是什么时候挂上去的呢?

    这就是上面提到的urb, 每个hub外设都会申请一个urb, 采用中断传输, 传输的内容就是读取hub的状态, 如果状态改变, 则将这个hub挂到链表上去, 同时启动内核线程, 线程自然就会处理这些hub了!

      

    static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange)
    {
            udev = usb_alloc_dev(hdev, hdev->bus, port1);
            choose_devnum(udev);
            hub_port_init(hub, udev, port1, i);
                -> hub_port_reset()
                -> usb_get_device_descriptor
                -> hub_port_reset
                -> usb_get_device_descriptor
            usb_new_device(udev);
    }
  • 相关阅读:
    java多线程小节, 总结的不错
    奇瑞风云, 你还在路上么
    android NDK 环境建立
    外企下岗白领正成为“新4050”
    搭积木
    祝MORIENTES在LIVERPOOL有所成就
    简单生活
    为什么要更新
    归去来
    随记一笔
  • 原文地址:https://www.cnblogs.com/vedic/p/11005454.html
Copyright © 2020-2023  润新知