当前位置:   article > 正文

usb的hub分析_usb hub分析

usb hub分析

1:在入口函数usb_init调用hub的初始化retval = usb_hub_init();

int usb_hub_init(void)
{
    if (usb_register(&hub_driver) < 0) { //注册hub驱动到usb的子系统总线上
        printk(KERN_ERR "%s: can't register hub driver\n",
            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\n", usbcore_name);

    return -1;
}

usb_register(&hub_driver) 用来注册一个usb的hub驱动


MODULE_DEVICE_TABLE (usb, hub_id_table);

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, //id跟设备配置函数
    .supports_autosuspend =    1,
};

hub的调用流程

一:面提到的echi驱动最终会调用hub_probr的探测函数,hub_probe函数就跟正常usb_driver的初始化一样。

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;
    hdev = interface_to_usbdev(intf);
    pm_runtime_set_autosuspend_delay(&hdev->dev, 0);

    /* Hubs have proper suspend/resume support. */
    usb_enable_autosuspend(hdev);

    if (hdev->level == MAX_TOPO_LEVEL) {
        dev_err(&intf->dev,
            "Unsupported bus topology: hub nested too deep\n");
        return -E2BIG;
    }

#ifdef    CONFIG_USB_OTG_BLACKLIST_HUB
    if (hdev->parent) {
        dev_warn(&intf->dev, "ignoring external hub\n");
        return -ENODEV;
    }
#endif

    /* Some hubs have a subclass of 1, which AFAICT according to the */
    /*  specs is not defined, but it works */
    if ((desc->desc.bInterfaceSubClass != 0) &&
        (desc->desc.bInterfaceSubClass != 1)) {
descriptor_error:
        dev_err (&intf->dev, "bad descriptor, ignoring hub\n");
        return -EIO;
    }

    /* Multiple endpoints? What kind of mutant ninja-hub is this? */
    if (desc->desc.bNumEndpoints != 1)
        goto descriptor_error;

    endpoint = &desc->endpoint[0].desc;

    /* If it's not an interrupt in endpoint, we'd better punt! */
    if (!usb_endpoint_is_int_in(endpoint))
        goto descriptor_error;

    /* We found a hub */
    dev_info (&intf->dev, "USB hub found\n");

    hub = kzalloc(sizeof(*hub), GFP_KERNEL);
    if (!hub) {
        dev_dbg (&intf->dev, "couldn't kmalloc hub struct\n");
        return -ENOMEM;
    }

    kref_init(&hub->kref);
    INIT_LIST_HEAD(&hub->event_list);
    hub->intfdev = &intf->dev;
    hub->hdev = hdev;
    INIT_DELAYED_WORK(&hub->leds, led_work);
    INIT_DELAYED_WORK(&hub->init_work, NULL);
    usb_get_intf(intf);

    usb_set_intfdata (intf, hub);
    intf->needs_remote_wakeup = 1;
    pm_suspend_ignore_children(&intf->dev, true);

    if (hdev->speed == USB_SPEED_HIGH)
        highspeed_hubs++;

    if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
        hub->quirk_check_port_auto_suspend = 1;

    if (hub_configure(hub, endpoint) >= 0)
        return 0;

    hub_disconnect (intf);
    return -ENODEV;
}

hub_config(比较重要的函数)填充usb_hub结构体,搞清楚usb_hub结构体

就不贴程序了:主要工作为获取hub描述符,获取hub状态,填充一个hub_irq中断。激活hub

最终调用kick_khubd用来踢醒线程

static void kick_khubd(struct usb_hub *hub)
{
    unsigned long    flags;

    spin_lock_irqsave(&hub_event_lock, flags);
    if (!hub->disconnected && list_empty(&hub->event_list)) {
        list_add_tail(&hub->event_list, &hub_event_list);

        /* Suppress autosuspend until khubd runs */
        usb_autopm_get_interface_no_resume(
                to_usb_interface(hub->intfdev));
        wake_up(&khubd_wait);
    }
    spin_unlock_irqrestore(&hub_event_lock, flags);
}

二:接下来分析:hub_thread

 内核创建线程:    khubd_task = kthread_run(hub_thread, NULL, "khubd");

static int hub_thread(void *__unused)
{
    set_freezable();

    do {
        hub_events();
        wait_event_freezable(khubd_wait,
                !list_empty(&hub_event_list) ||
                kthread_should_stop());
    } while (!kthread_should_stop() || !list_empty(&hub_event_list));

    pr_debug("%s: khubd exiting\n", usbcore_name);
    return 0;
}

主要用来等待khubd_wait的事件的发生,当主机控制器ehci上有设备接入时(如U盘),ehci将唤醒khubd_wait,(hub_event_list)

在hub中当port端点修改后调用:

static void hub_port_connect_change(struct usb_hub *hub, int port1,   u16 portstatus, u16 portchange)

{ struct usb_device *hdev = hub->hdev;
    struct device *hub_dev = hub->intfdev;
    struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
    unsigned wHubCharacteristics =
            le16_to_cpu(hub->descriptor->wHubCharacteristics);
    struct usb_device *udev;
    int status, i;
    unsigned unit_load;

    dev_dbg (hub_dev,
        "port %d, status %04x, change %04x, %s\n",
        port1, portstatus, portchange, portspeed(hub, portstatus));

    if (hub->has_indicators) {
        set_port_led(hub, port1, HUB_LED_AUTO);
        hub->indicator[port1-1] = INDICATOR_AUTO;
    }

#ifdef    CONFIG_USB_OTG
    /* during HNP, don't repeat the debounce */
    if (hdev->bus->is_b_host)
        portchange &= ~(USB_PORT_STAT_C_CONNECTION |
                USB_PORT_STAT_C_ENABLE);
#endif

    /* Try to resuscitate an existing device */
    udev = hub->ports[port1 - 1]->child;
    if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
            udev->state != USB_STATE_NOTATTACHED) {
        usb_lock_device(udev);
        if (portstatus & USB_PORT_STAT_ENABLE) {
            status = 0;        /* Nothing to do */

#ifdef CONFIG_USB_SUSPEND
        } else if (udev->state == USB_STATE_SUSPENDED &&
                udev->persist_enabled) {
            /* For a suspended device, treat this as a
             * remote wakeup event.
             */
            status = usb_remote_wakeup(udev);
#endif

        } else {
            status = -ENODEV;    /* Don't resuscitate */
        }
        usb_unlock_device(udev);

        if (status == 0) {
            clear_bit(port1, hub->change_bits);
            return;
        }
    }

    /* Disconnect any existing devices under this port */
    if (udev) {
        if (hcd->phy && !hdev->parent &&
                !(portstatus & USB_PORT_STAT_CONNECTION))
            usb_phy_notify_disconnect(hcd->phy, udev->speed);
        usb_disconnect(&hub->ports[port1 - 1]->child);
    }
    clear_bit(port1, hub->change_bits);

    /* We can forget about a "removed" device when there's a physical
     * disconnect or the connect status changes.
     */
    if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
            (portchange & USB_PORT_STAT_C_CONNECTION))
        clear_bit(port1, hub->removed_bits);

    if (portchange & (USB_PORT_STAT_C_CONNECTION |
                USB_PORT_STAT_C_ENABLE)) {
        status = hub_port_debounce_be_stable(hub, port1);
        if (status < 0) {
            if (status != -ENODEV && printk_ratelimit())
                dev_err(hub_dev, "connect-debounce failed, "
                        "port %d disabled\n", port1);
            portstatus &= ~USB_PORT_STAT_CONNECTION;
        } else {
            portstatus = status;
        }
    }

    /* Return now if debouncing failed or nothing is connected or
     * the device was "removed".
     */
    if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
            test_bit(port1, hub->removed_bits)) {

        /* maybe switch power back on (e.g. root hub was reset) */
        if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
                && !port_is_power_on(hub, portstatus))
            set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);

        if (portstatus & USB_PORT_STAT_ENABLE)
              goto done;
        return;
    }
    if (hub_is_superspeed(hub->hdev))
        unit_load = 150;
    else
        unit_load = 100;

    status = 0;
    for (i = 0; i < SET_CONFIG_TRIES; i++) {

        /* reallocate for each attempt, since references
         * to the previous one can escape in various ways
         */
        udev = usb_alloc_dev(hdev, hdev->bus, port1);
        if (!udev) {
            dev_err (hub_dev,
                "couldn't allocate port %d usb_device\n",
                port1);
            goto done;
        }

        usb_set_device_state(udev, USB_STATE_POWERED);
         udev->bus_mA = hub->mA_per_port;
        udev->level = hdev->level + 1;
        udev->wusb = hub_is_wusb(hub);

        /* Only USB 3.0 devices are connected to SuperSpeed hubs. */
        if (hub_is_superspeed(hub->hdev))
            udev->speed = USB_SPEED_SUPER;
        else
            udev->speed = USB_SPEED_UNKNOWN;

        choose_devnum(udev);
        if (udev->devnum <= 0) {
            status = -ENOTCONN;    /* Don't retry */
            goto loop;
        }

        /* reset (non-USB 3.0 devices) and get descriptor */
        status = hub_port_init(hub, udev, port1, i);
        if (status < 0)
            goto loop;

        usb_detect_quirks(udev);
        if (udev->quirks & USB_QUIRK_DELAY_INIT)
            msleep(1000);

        /* consecutive bus-powered hubs aren't reliable; they can
         * violate the voltage drop budget.  if the new child has
         * a "powered" LED, users should notice we didn't enable it
         * (without reading syslog), even without per-port LEDs
         * on the parent.
         */
        if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
                && udev->bus_mA <= unit_load) {
            u16    devstat;

            status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
                    &devstat);
            if (status < 2) {
                dev_dbg(&udev->dev, "get status %d ?\n", status);
                goto loop_disable;
            }
            le16_to_cpus(&devstat);
            if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
                dev_err(&udev->dev,
                    "can't connect bus-powered hub "
                    "to this port\n");
                if (hub->has_indicators) {
                    hub->indicator[port1-1] =
                        INDICATOR_AMBER_BLINK;
                    schedule_delayed_work (&hub->leds, 0);
                }
                status = -ENOTCONN;    /* Don't retry */
                goto loop_disable;
            }
        }
 
        /* check for devices running slower than they could */
        if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
                && udev->speed == USB_SPEED_FULL
                && highspeed_hubs != 0)
            check_highspeed (hub, udev, port1);

        /* Store the parent's children[] pointer.  At this point
         * udev becomes globally accessible, although presumably
         * no one will look at it until hdev is unlocked.
         */
        status = 0;

        /* We mustn't add new devices if the parent hub has
         * been disconnected; we would race with the
         * recursively_mark_NOTATTACHED() routine.
         */
        spin_lock_irq(&device_state_lock);
        if (hdev->state == USB_STATE_NOTATTACHED)
            status = -ENOTCONN;
        else
            hub->ports[port1 - 1]->child = udev;
        spin_unlock_irq(&device_state_lock);

        /* Run it through the hoops (find a driver, etc) */
        if (!status) {
            status = usb_new_device(udev);
            if (status) {
                spin_lock_irq(&device_state_lock);
                hub->ports[port1 - 1]->child = NULL;
                spin_unlock_irq(&device_state_lock);
            }
        }

        if (status)
            goto loop_disable;

        status = hub_power_remaining(hub);
        if (status)
            dev_dbg(hub_dev, "%dmA power budget left\n", status);

        return;

loop_disable:
        hub_port_disable(hub, port1, 1);
loop:
        usb_ep0_reinit(udev);
        release_devnum(udev);
        hub_free_dev(udev);
        usb_put_dev(udev);
        if ((status == -ENOTCONN) || (status == -ENOTSUPP))
            break;
    }
    if (hub->hdev->parent ||
            !hcd->driver->port_handed_over ||
            !(hcd->driver->port_handed_over)(hcd, port1)) {
        if (status != -ENOTCONN && status != -ENODEV)
            dev_err(hub_dev, "unable to enumerate USB device on port %d\n",
                    port1);
    }
 
done:
    hub_port_disable(hub, port1, 1);
    if (hcd->driver->relinquish_port && !hub->hdev->parent)
        hcd->driver->relinquish_port(hcd, port1);
}

最终调用:得到驱动描述符:(复位设备,分配地址,获取分配地址)

static int hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,int retry_counter)

usb_set_configuration()该函数的主要功能时为配置下的每个接口分配内存空间,然后遍历每一个接口,为每一个接口初始化设备模型,包括usb总线的绑定,设备类型绑定为接口,最后为每个接口进行usb总线上的注册

通过配置最终调用函数的探测probe

结构体:struct usb_device_driver usb_generic_driver 

在usb主机控制器ehci、ohci检测到设备接入时都会调用这个通用的usb设备驱动,它的目的纯粹是作为设备接入时的一个接口,真正操作时usb_choose_configuration()、usb_set_configuration(),关于generic_probe()通用设备探测函数等会在分析,先来看下usb通用设备驱动时如何注册的。
备注,在分析该generic_probe()探测函数前,先要看下usb主机控制器驱动的注册流程,否则无法分析generic_probe()内部的工作原理,原因是只有usb主机控制器注册了,然后有usb设备接入到主机控制器上时,主机控制器检测到设备接入,通知hub_thread完成枚举、创建挂接在usb总线上的设备,然后该设备通过usb总线寻找到名称相同的驱动,调用usb_probe_device()-->generic_probe()完成接入usb设备接口的初始化....
usb_choose_configuration():从usb设备中获取一个有效的配置,

usb_set_configuration()该函数的主要功能时为配置下的每个接口分配内存空间,然后遍历每一个接口,为每一个接口初始化设备模型,包括usb总线的绑定,设备类型绑定为接口,最后为每个接口进行usb总线上的注册

usb_device_match:该函数主要时完成id字段匹配,寻找对应的驱动,如果这里匹配成功

usb_register调用:usb_probe_interface()

通过匹配将调用usb_serial_probe()函数。

usb_serial_probe()主要通过search_serial_device()函数遍历链表usb_serial_driver_list链表,根据ID寻找匹配的驱动

这里将调用options.c的探测函数,这个函数也是我们最终的根源,最终将调用option_probe()函数,先是线程检测到链表有变化-->设备枚举-->设备和驱动的匹配-->后面是接口和驱动的匹配-->再后来是调用usb_serial_probe-->再后来是调用option_probe

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/246986?site
推荐阅读
相关标签
  

闽ICP备14008679号