Chinaunix首页 | 论坛 | 博客
  • 博客访问: 270801
  • 博文数量: 46
  • 博客积分: 4125
  • 博客等级: 上校
  • 技术积分: 575
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-25 16:40
文章分类

全部博文(46)

文章存档

2011年(1)

2010年(4)

2009年(38)

2008年(3)

我的朋友

分类: LINUX

2009-03-18 02:37:39

现在开始uhci与设备的通信分析
先看分析枚举过程,再分析数据通信
USB总线上设备的枚举:
1. 当设备插入时,设备的上拉电阻使信号线的电位升高,这时候根集线器检测到设备的插入
2. 主机发送Get_status到根集线器来获得当前端口的状态
3. 主机发送Set_Feature,让根集线器复位端口,使得端口上的设备处于复位状态
4. 主机发送Get_status检测端口的复位是否完成,如果完成,设备现在处于默认状态,并准备好使用端点0进行控制传输,这时候设备的地址为0
5. 主机检测设备的速度类型
6. 主机向设备发送Get_Device_Descriptor获得设备端点0包的最大值
7. 主机向设备发送Set_Address来给设备设定一个新的地址
8. 主机向设备发送Get_Device_Descriptor获得完整的设备描述符
9. 主机向设备发送Get_Device_Configuration来获取所有的配置信息
10. 主机按照配置信息匹配驱动
其中6 7步在微软和LINUX中的处理并不一样,微软为先6再7,而LINUX为先7再6,而且取得包最大值的方法也不一样,这会在后面详细说明

好,现在就从设备插入根集线器开始看
回到呢永不停息的uhci_hub_status_data

uhci_hub_status_data在/drivers/usb/core/hcd.c中

void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
    struct urb    *urb;
    int        length;
    unsigned long    flags;
    char        buffer[4];    /* Any root hubs with > 31 ports? */
    //检测主机控制器驱动是否已经注册
    if (unlikely(!hcd->rh_registered))
        return;
    if (!hcd->uses_new_polling && !hcd->status_urb)
        return;
    //进行集线器设备状态检测
    length = hcd->driver->hub_status_data(hcd, buffer);
    //端口有设备
    if (length > 0)
    {
        /* try to complete the status urb */
        spin_lock_irqsave(&hcd_root_hub_lock, flags);
        urb = hcd->status_urb;
        //检测urb是否存在
        if (urb)
        {
            hcd->poll_pending = 0;
            //清除hcd的状态urb
            hcd->status_urb = NULL;
            //置实际传输长度为1
            urb->actual_length = length;
            //拷贝端口状态描述组到urb中
            memcpy(urb->transfer_buffer, buffer, length);
            //卸载urb与节点的连接
            usb_hcd_unlink_urb_from_ep(hcd, urb);        
            spin_unlock(&hcd_root_hub_lock);
            //返回urb给驱动程序
            usb_hcd_giveback_urb(hcd, urb, 0);
            spin_lock(&hcd_root_hub_lock);
        }
        else
        {
            length = 0;
            hcd->poll_pending = 1;
        }
        spin_unlock_irqrestore(&hcd_root_hub_lock, flags);
    }
    /* The USB 2.0 spec says 256 ms. This is close enough and won't
     * exceed that limit if HZ is 100. The math is more clunky than
     * maybe expected, this is to make sure that all timers for USB devices
     * fire at the same time to give the CPU a break inbetween */

    if (hcd->uses_new_polling ? hcd->poll_rh :(length == 0 && hcd->status_urb != NULL))
        mod_timer (&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
}

hcd->driver->hub_status_data负责检测端口和td队列的状态
hcd->driver->hub_status_data在UHCI中为uhci_hub_status_data
uhci_hub_status_data在/drivers/usb/host/uhci-hub.c中

static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{
    //取得uhci_hcd结构
    struct uhci_hcd *uhci = hcd_to_uhci(hcd);
    unsigned long flags;
    int status = 0;
    spin_lock_irqsave(&uhci->lock, flags);
    //调度uhci中的帧队列
    uhci_scan_schedule(uhci);
    if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
        goto done;
    //检测端口
    uhci_check_ports(uhci);
    //获得端口状态
    status = get_hub_status_data(uhci, buf);
    //检测根集线器的状态
    switch (uhci->rh_state)
    {
     case UHCI_RH_SUSPENDING:
     case UHCI_RH_SUSPENDED:
        /* if port change, ask to be resumed */
        if (status)
            //唤醒根集线器
            usb_hcd_resume_root_hub(hcd);
        break;
     case UHCI_RH_AUTO_STOPPED:
        /* if port change, auto start */
        if (status)
            //唤醒主机控制器
            wakeup_rh(uhci);
        break;
     //uhci的状态为运行
     case UHCI_RH_RUNNING:
        /* are any devices attached? */
        //检测是否有连接的设备
        if (!any_ports_active(uhci))
        {
            //改变uhci的状态为运行但无设备
            uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
            //改变uhci的自动停止时间
            uhci->auto_stop_time = jiffies + HZ;
        }
        break;
     //uhci的状态为运行但无设备
     case UHCI_RH_RUNNING_NODEVS:
        /* auto-stop if nothing connected for 1 second */
        //检测是否有连接的设备
        if (any_ports_active(uhci))
            //改变uhci的状态为运行
            uhci->rh_state = UHCI_RH_RUNNING;
        //检测jiffies是否大于uhci->auto_stop_time
        else if (time_after_eq(jiffies, uhci->auto_stop_time))
            //悬挂主机控制器
            suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);
        break;
     default:
        break;
    }
done:
    spin_unlock_irqrestore(&uhci->lock, flags);
    return status;
}

我们的目标就是get_hub_status_data,他负责端口连接状态的检测
get_hub_status_data在/drivers/usb/host/uhci-hub.c中

static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)
{
    int port;
    //检测RWC的三个状态改变位
    int mask = RWC_BITS;
    /* Some boards (both VIA and Intel apparently) report bogus
     * overcurrent indications, causing massive log spam unless
     * we completely ignore them. This doesn't seem to be a problem
     * with the chipset so much as with the way it is connected on
     * the motherboard; if the overcurrent input is left to float
     * then it may constantly register false positives. */

    if (ignore_oc)
        mask &= ~USBPORTSC_OCC;
    *buf = 0;
    //历遍根集线器上的端口
    for (port = 0; port < uhci->rh_numports; ++port)
    {
        //检测端口的状态改变位,或者端口悬挂位
        if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) || test_bit(port, &uhci->port_c_suspend))
            //记录端口号
            *buf |= (1 << (port + 1));
    }
    return !!*buf;
}

当有设备连接到端口时, get_hub_status_data负责记录端口号到buf中,并返回1
uhci_hub_status_data会将这个1返回给usb_hcd_poll_rh_status的length,length为1,终于可以进入if中了
先取得主机控制器的状态检测urb,设置一些属性之后就进入到usb_hcd_giveback_urb中
在usb_hcd_giveback_urb中会运行urb的complete函数,还记得UHCI主机控制器中urb的complete函数是什么嘛?hub_irq,忘了的话回顾一下hub接口驱动中的注册内容 = 3=
hub_irq在/drivers/usb/core/hub.c中

static void hub_irq(struct urb *urb)
{
    //取得集线器描述结构
    struct usb_hub *hub = urb->context;
    //取得urb的状态
    int status = urb->status;
    int i;
    unsigned long bits;
    //检测urb的状态
    switch (status)
    {
    case -ENOENT:        /* synchronous unlink */
    case -ECONNRESET:    /* async unlink */
    case -ESHUTDOWN:    /* hardware going away */
        return;
    //推测为错误
    default:        /* presumably an error */
        /* Cause a hub reset after 10 consecutive errors */
        dev_dbg (hub->intfdev, "transfer --> %d\n", status);
        if ((++hub->nerrors < 10) || hub->error)
            goto resubmit;
        hub->error = status;
        /* FALL THROUGH */
    /* let khubd handle things */
    //激活khubd线程
    case 0:            /* we got data: port status changed */
        bits = 0;
        for (i = 0; i < urb->actual_length; ++i)
            bits |= ((unsigned long) ((*hub->buffer)[i])) << (i*8);
        hub->event_bits[0] = bits;
        break;
    }
    hub->nerrors = 0;
    /* Something happened, let khubd figure it out */
    kick_khubd(hub);
resubmit:
    if (hub->quiescing)
        return;
    //再次发送hcd的中断类型urb
    if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
            && status != -ENODEV && status != -EPERM)
        dev_err (hub->intfdev, "resubmit --> %d\n", status);
}

主要任务有两个,一是敲醒khubd线程,一是再次发送主机控制器的urb,让以后仍有状态urb可用
发送中断类型的urb就不多说了,来看kick_khubd(hub);
kick_khubd在/drivers/usb/core/hub.c中

static void kick_khubd(struct usb_hub *hub)
{
    unsigned long    flags;
    /* Suppress autosuspend until khubd runs */
    //将自动悬挂标志置1
    to_usb_interface(hub->intfdev)->pm_usage_cnt = 1;
    spin_lock_irqsave(&hub_event_lock, flags);
    //检测集线器的连接以及事件队列是否为空
    if (!hub->disconnected && list_empty(&hub->event_list))
    {
        //将hub挂进hub_event_list队列里
        list_add_tail(&hub->event_list, &hub_event_list);
        //唤醒khubd_wait
        wake_up(&khubd_wait);
    }
    spin_unlock_irqrestore(&hub_event_lock, flags);
}

主要任务也是两个, 将hub挂进hub_event_list队列里, 唤醒khubd_wait
唤醒khubd_wait是啥呢?给点提示,和khubd线程有关
khubd线程的主函数为hub_thread
hub_thread在/drivers/usb/core/hub.c中

static int hub_thread(void *__unused)
{
    /* khubd needs to be freezable to avoid intefering with USB-PERSIST
     * port handover. Otherwise it might see that a full-speed device
     * was gone before the EHCI controller had handed its port over to
     * the companion full-speed controller.
     */

     //设置该线程可冻结
    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;
}

当hub_event_list队列不会空,并且唤醒khubd后,我们终于可以来到神秘的hub_events中了
hub_events在/drivers/usb/core/hub.c中

static void hub_events(void)
{
    struct list_head *tmp;
    struct usb_device *hdev;
    struct usb_interface *intf;
    struct usb_hub *hub;
    struct device *hub_dev;
    u16 hubstatus;
    u16 hubchange;
    u16 portstatus;
    u16 portchange;
    int i, ret;
    int connect_change;
    /*
     * We restart the list every time to avoid a deadlock with
     * deleting hubs downstream from this one. This should be
     * safe since we delete the hub from the event list.
     * Not the most efficient, but avoids deadlocks.
     */

    while (1)
    {
        /* Grab the first entry at the beginning of the list */
        spin_lock_irq(&hub_event_lock);
        //测试hub_event_list队列是否为空
        if (list_empty(&hub_event_list))
        {
            spin_unlock_irq(&hub_event_lock);
            break;
        }
        //获取队列的第一个事件
        tmp = hub_event_list.next;
        //卸载事件与hub_event_list的关联
        list_del_init(tmp);
        //从tmp算出包含它的集线器结构
        hub = list_entry(tmp, struct usb_hub, event_list);
        kref_get(&hub->kref);
        spin_unlock_irq(&hub_event_lock);
        //获取usb设备结构
        hdev = hub->hdev;
        //获取集线器的接口设备
        hub_dev = hub->intfdev;
        //获取接口结构
        intf = to_usb_interface(hub_dev);
        dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
                hdev->state, hub->descriptor
                    ? hub->descriptor->bNbrPorts
                    : 0,
                /* NOTE: expects max 15 ports... */
                (u16) hub->change_bits[0],
                (u16) hub->event_bits[0]);
        /* Lock the device, then check to see if we were
         * disconnected while waiting for the lock to succeed. */

        usb_lock_device(hdev);
        //检测集线器的连接状态
        if (unlikely(hub->disconnected))
            goto loop;
        /* If the hub has died, clean up after it */
        //检测usb设备的状态是否为0
        if (hdev->state == USB_STATE_NOTATTACHED)
        {
            hub->error = -ENODEV;
            //卸载集线器上的所有设备
            hub_stop(hub);
            goto loop;
        }
        /* Autoresume */
        //自动恢复
        ret = usb_autopm_get_interface(intf);
        if (ret)
        {
            dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
            goto loop;
        }
        /* If this is an inactive hub, do nothing */
        //检测集线器是否为有效设备
        if (hub->quiescing)
            goto loop_autopm;
        //检测集线器的错误标志
        if (hub->error)
        {
            dev_dbg (hub_dev, "resetting for error %d\n",hub->error);
            ret = usb_reset_composite_device(hdev, intf);
            if (ret)
            {
                dev_dbg (hub_dev,"error resetting hub: %d\n", ret);
                goto loop_autopm;
            }
            hub->nerrors = 0;
            hub->error = 0;
        }
        /* deal with port status changes */
        //历遍下行端口
        for (i = 1; i <= hub->descriptor->bNbrPorts; i++)
        {
            //测试端口重置恢复位
            if (test_bit(i, hub->busy_bits))
                continue;
            //取得逻辑连接状态改变位
            connect_change = test_bit(i, hub->change_bits);
            //检测状态改变位并清除它
            //检测连接状态改变位和集线器启动位
            if (!test_and_clear_bit(i, hub->event_bits) && !connect_change && !hub->activating)
                continue;
            //取得集线器端口状态
            ret = hub_port_status(hub, i,&portstatus, &portchange);
            if (ret < 0)
                continue;
            //检测集线器的活动位
            //检测端口对应的usb设备的子设备位是否为空
            //检测端口状态是否为连接
            if (hub->activating
                    && !hdev->children[i-1]
                    && (portstatus &USB_PORT_STAT_CONNECTION))
                connect_change = 1;
            //检测端口连接状态改变位中的连接状态是否有改变
            if (portchange & USB_PORT_STAT_C_CONNECTION)
            {
                clear_port_feature(hdev, i,USB_PORT_FEAT_C_CONNECTION);

                connect_change = 1;
            }
            //检测端口连接状态改变位中的端口使能位
            if (portchange & USB_PORT_STAT_C_ENABLE)
            {
                if (!connect_change)
                    dev_dbg (hub_dev,"port %d enable change, " "status %08x\n",i, portstatus);
                //清除端口有效位
                clear_port_feature(hdev, i,USB_PORT_FEAT_C_ENABLE);
                /*
                 * EM interference sometimes causes badly
                 * shielded USB devices to be shutdown by
                 * the hub, this hack enables them again.
                 * Works at least with mouse driver.
                 */

                if (!(portstatus & USB_PORT_STAT_ENABLE) && !connect_change && hdev->children[i-1])
                {
                    dev_err (hub_dev, "port %i " "disabled by hub (EMI?), " "re-enabling...\n", i);
                    connect_change = 1;
                }
            }
            //检测端口连接状态改变位中的端口悬挂是否改变
            if (portchange & USB_PORT_STAT_C_SUSPEND)
            {
                //清除悬挂位
                clear_port_feature(hdev, i,USB_PORT_FEAT_C_SUSPEND);
                //检测端口对应的设备是否存在
                if (hdev->children[i-1])
                {
                    ret = remote_wakeup(hdev->children[i-1]);
                    
                    if (ret < 0)
                        connect_change = 1;
                }
                else
                {
                    ret = -ENODEV;
                    hub_port_disable(hub, i, 1);
                }   
                dev_dbg (hub_dev,"resume on port %d, status %d\n",i, ret);
            }
            //检测端口状态改变位中的过载电源位
            if (portchange & USB_PORT_STAT_C_OVERCURRENT)
            {
                dev_err (hub_dev,"over-current change on port %d\n",i);
                //清除过载电源位
                clear_port_feature(hdev, i,USB_PORT_FEAT_C_OVER_CURRENT);
                hub_power_on(hub);
            }
            //检测端口重置位
            if (portchange & USB_PORT_STAT_C_RESET)
            {
                dev_dbg (hub_dev,"reset change on port %d\n",i);
                //清除端口重置位
                clear_port_feature(hdev, i,USB_PORT_FEAT_C_RESET);
            }
            //检测连接状态改变位
            if (connect_change)
                //处理物理或者逻辑上的连接改变事件
                hub_port_connect_change(hub, i,portstatus, portchange);
        } /* end for i */
        /* deal with hub status changes */
        if (test_and_clear_bit(0, hub->event_bits) == 0)
            ;    /* do nothing */
        else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
            dev_err (hub_dev, "get_hub_status failed\n");
        else
        {
            if (hubchange & HUB_CHANGE_LOCAL_POWER)
            {
                dev_dbg (hub_dev, "power change\n");
                clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
                if (hubstatus & HUB_STATUS_LOCAL_POWER)
                    /* FIXME: Is this always true? */
                    hub->limited_power = 1;
                else
                    hub->limited_power = 0;
            }
            if (hubchange & HUB_CHANGE_OVERCURRENT)
            {
                dev_dbg (hub_dev, "overcurrent change\n");
                msleep(500);    /* Cool down */
                clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
                            hub_power_on(hub);
            }
        }
        hub->activating = 0;
        /* If this is a root hub, tell the HCD it's okay to
         * re-enable port-change interrupts now. */

        if (!hdev->parent && !hub->busy_bits[0])
            usb_enable_root_hub_irq(hdev->bus);
loop_autopm:
        /* Allow autosuspend if we're not going to run again */
        if (list_empty(&hub->event_list))
            usb_autopm_enable(intf);
loop:
        usb_unlock_device(hdev);
        kref_put(&hub->kref, hub_release);
        } /* end while (1) */
}

hub = list_entry(tmp, struct usb_hub, event_list);
这句代码取得产生事件的根集线器
ret = hub_port_status(hub, i,&portstatus, &portchange); 取得集线器端口状态,有没有什么灵感呢?其实到这里我们就进入到枚举的第2步了
2. 主机发送Get_status到根集线器来获得当前端口的状态
兴奋吧,第3步也离我们不远了

进行一轮检测之后进入到hub_port_connect_change
hub_port_connect_change在/drivers/usb/core/hub.c中

static void hub_port_connect_change(struct usb_hub *hub, int port1,
                    u16 portstatus, u16 portchange)
{
    //取得usb设备
    struct usb_device *hdev = hub->hdev;
    //取得接口的设备结构
    struct device *hub_dev = hub->intfdev;
    //取得主机控制器驱动
    struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
    //取得集线器描述符中的特征字段    
    u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
    int status, i;
    dev_dbg (hub_dev,"port %d, status %04x, change %04x, %s\n",
        port1, portstatus, portchange, portspeed (portstatus));
    //检测集线器是否有指示器(LED灯)
    if (hub->has_indicators)
    {
        set_port_led(hub, port1, HUB_LED_AUTO);
        hub->indicator[port1-1] = INDICATOR_AUTO;
    }
    /* Disconnect any existing devices under this port */
    //检测端口号对应的设备是否存在
    if (hdev->children[port1-1])
        //卸载该设备
        usb_disconnect(&hdev->children[port1-1]);
    //清除端口对应的状态改变位
    clear_bit(port1, hub->change_bits);
#ifdef    CONFIG_USB_OTG
    /* during HNP, don't repeat the debounce */
    if (hdev->bus->is_b_host)
        portchange &= ~USB_PORT_STAT_C_CONNECTION;
#endif
    //检测端口最近状态改变位
    if (portchange & USB_PORT_STAT_C_CONNECTION)
    {
        //端口防反跳
        status = hub_port_debounce(hub, port1);
        if (status < 0)
        {
            if (printk_ratelimit())
                dev_err (hub_dev, "connect-debounce failed, " "port %d disabled\n", port1);
            goto done;
        }
        portstatus = status;
    }
    /* Return now if nothing is connected */
    //检测端口的连接位
    if (!(portstatus & USB_PORT_STAT_CONNECTION))
    {
        /* maybe switch power back on (e.g. root hub was reset) */
        if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2 && !(portstatus & (1 << USB_PORT_FEAT_POWER)))
            set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);
         //检测端口有效位
        if (portstatus & USB_PORT_STAT_ENABLE)
              goto done;
        return;
    }
    //重复一定次数
    for (i = 0; i < SET_CONFIG_TRIES; i++)
    {
        //声明一个usb设备结构
        struct usb_device *udev;
        /* 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设备的状态
        usb_set_device_state(udev, USB_STATE_POWERED);
        //设置该usb设备的速度模式
        udev->speed = USB_SPEED_UNKNOWN;
        //设置电流
         udev->bus_mA = hub->mA_per_port;
        //设置设备的层数
        udev->level = hdev->level + 1;
        //检测上层集线器是否为wusb
        udev->wusb = hub_is_wusb(hub);
        /* set the address */
        //寻找一个空的设备号
        choose_address(udev);
        //检测设备号是否分配成功
        if (udev->devnum <= 0)
        {
            status = -ENOTCONN;    /* Don't retry */
            goto loop;
        }
        /* reset and get descriptor */
        //重置设备,取得描述符
        status = hub_port_init(hub, udev, port1, i);
        
        if (status < 0)
            goto loop;
        /* 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.
         */

         //检测设备是否为集线器设备,并且需要的电流小于100
        if (udev->descriptor.bDeviceClass == USB_CLASS_HUB && udev->bus_mA <= 100)
        {
            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
            hdev->children[port1-1] = udev;
        spin_unlock_irq(&device_state_lock);
        /* Run it through the hoops (find a driver, etc) */
        //检测是否有异常
        if (!status)
        {
            //建立usb设备
            status = usb_new_device(udev);
            if (status)
            {
                spin_lock_irq(&device_state_lock);
                hdev->children[port1-1] = 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_address(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))
        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);
}

usb_alloc_dev为插入的usb设备分配一个usb-device数据结构
choose_address为插入的usb设备在所连接的主机控制器的usb总线上分配一个设备号
虽然分配了,但是此时还是用地址0进行通信,只是预先分配好而已
choose_address在/drivers/usb/core/hub.c中

static void choose_address(struct usb_device *udev)
{
    int        devnum;
    struct usb_bus    *bus = udev->bus;
    /* If khubd ever becomes multithreaded, this will need a lock */
    //检测设备是否为wusb设备
    if (udev->wusb)
    {
        devnum = udev->portnum + 1;
        BUG_ON(test_bit(devnum, bus->devmap.devicemap));
    }
    else
    {
        /* Try to allocate the next devnum beginning at
         * bus->devnum_next. */

         //需找usb总线的设备号字段上第一个空的bit
        devnum = find_next_zero_bit(bus->devmap.devicemap, 128,bus->devnum_next);
        //如果大于128
        if (devnum >= 128)
             //需找usb总线的设备号字段上的第一位不适用的bit
            devnum = find_next_zero_bit(bus->devmap.devicemap,128, 1);
        //更新设备号
        //当前设备号大于127则为1,否则为当先设备号+1
        bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
    }
    //检测当前设备号是否小于128
    if (devnum < 128)
    {
        //设置usb总线的设备号字段上的相应bit
        set_bit(devnum, bus->devmap.devicemap);
        //设置设备的设备号
        udev->devnum = devnum;
    }
}

现在进入到hub_port_init
hub_port_init在/drivers/usb/core/hub.c中

static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        int retry_counter)
{
    static DEFINE_MUTEX(usb_address0_mutex);
    //取得集线器的usb设备结构
    struct usb_device    *hdev = hub->hdev;
    int            i, j, retval;
    unsigned        delay = HUB_SHORT_RESET_TIME;
    //获取设备的速度模式
    enum usb_device_speed    oldspeed = udev->speed;
    char             *speed, *type;
    //获取设备的设备号
    int            devnum = udev->devnum;
    /* root hub ports have a slightly longer reset period
     * (from USB 2.0 spec, section 7.1.7.5)
     */

     //检测是否为根集线器
    if (!hdev->parent)
    {
        delay = HUB_ROOT_RESET_TIME;
        
        if (port1 == hdev->bus->otg_port)
            hdev->bus->b_hnp_enable = 0;
    }
    /* Some low speed devices have problems with the quick delay, so */
    /* be a bit pessimistic with those devices. RHbug #23670 */
    //检测是否为低速模式
    if (oldspeed == USB_SPEED_LOW)
        delay = HUB_LONG_RESET_TIME;
    mutex_lock(&usb_address0_mutex);
    /* Reset the device; full speed may morph to high speed */
    //重置设备
    retval = hub_port_reset(hub, port1, udev, delay);
    if (retval < 0)        /* error or disconnect */
        goto fail;
                /* success, speed is known */
    retval = -ENODEV;
    //检测设备的速度模式是否改变
    if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed)
    {
        dev_dbg(&udev->dev, "device reset changed speed!\n");
        goto fail;
    }
    oldspeed = udev->speed;
    /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
     * it's fixed size except for full speed devices.
     * For Wireless USB devices, ep0 max packet is always 512 (tho
     * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
     */

     //检测设备的速度模式,为端点0分配不同的包大小
    switch (udev->speed)
    {
    case USB_SPEED_VARIABLE:    /* fixed at 512 */
        udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(512);
        break;
    case USB_SPEED_HIGH:        /* fixed at 64 */
        udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64);
        break;
    case USB_SPEED_FULL:        /* 8, 16, 32, or 64 */
        /* to determine the ep0 maxpacket size, try to read
         * the device descriptor to get bMaxPacketSize0 and
         * then correct our initial guess.
         */

        udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(64);
        break;
    case USB_SPEED_LOW:        /* fixed at 8 */
        udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(8);
        break;
    default:
        goto fail;
    }
    type = "";
    switch (udev->speed)
    {
    case USB_SPEED_LOW:    speed = "low";    break;
    case USB_SPEED_FULL:    speed = "full";    break;
    case USB_SPEED_HIGH:    speed = "high";    break;
    case USB_SPEED_VARIABLE:
                speed = "variable";
                type = "Wireless ";
                break;
    default:         speed = "?";    break;
    }
    dev_info (&udev->dev,
         "%s %s speed %sUSB device using %s and address %d\n",
         (udev->config) ? "reset" : "new", speed, type,
         udev->bus->controller->driver->name, devnum);
    /* Set up TT records, if needed */
    //检测集线器是否有高低速转换电路
    if (hdev->tt)
    {
        udev->tt = hdev->tt;
        udev->ttport = hdev->ttport;
    }
    else if (udev->speed != USB_SPEED_HIGH && hdev->speed == USB_SPEED_HIGH)
    {
        udev->tt = &hub->tt;
        udev->ttport = port1;
    }
    /* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
     * Because device hardware and firmware is sometimes buggy in
     * this area, and this is how Linux has done it for ages.
     * Change it cautiously.
     *
     * NOTE: If USE_NEW_SCHEME() is true we will start by issuing
     * a 64-byte GET_DESCRIPTOR request. This is what Windows does,
     * so it may help with some non-standards-compliant devices.
     * Otherwise we start with SET_ADDRESS and then try to read the
     * first 8 bytes of the device descriptor to get the ep0 maxpacket
     * value.
     */

     //尝试读取设备描述符到2次
    for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100)))
    {
        //是否使用新方案
        //微软的方案,一次读取64字节
        if (USE_NEW_SCHEME(retry_counter))
        {
            struct usb_device_descriptor *buf;
            int r = 0;
#define GET_DESCRIPTOR_BUFSIZE    64
            buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
            if (!buf)
            {
                retval = -ENOMEM;
                continue;
            }
            /* Retry on all errors; some devices are flakey.
             * 255 is for WUSB devices, we actually need to use
             * 512 (WUSB1.0[4.8.1]).
             */

            for (j = 0; j < 3; ++j)
            {
                buf->bMaxPacketSize0 = 0;
                r = usb_control_msg(udev, usb_rcvaddr0pipe(),
                    USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
                    USB_DT_DEVICE << 8, 0,
                    buf, GET_DESCRIPTOR_BUFSIZE,
                    USB_CTRL_GET_TIMEOUT);
                switch (buf->bMaxPacketSize0)
                {
                case 8: case 16: case 32: case 64: case 255:
                    if (buf->bDescriptorType ==USB_DT_DEVICE)
                    {
                        r = 0;
                        break;
                    }
                    /* FALL THROUGH */
                default:
                    if (r == 0)
                        r = -EPROTO;
                    break;
                }
                if (r == 0)
                    break;
            }
            udev->descriptor.bMaxPacketSize0 = buf->bMaxPacketSize0;
            kfree(buf);
            retval = hub_port_reset(hub, port1, udev, delay);
            if (retval < 0)        /* error or disconnect */
                goto fail;
            if (oldspeed != udev->speed)
            {
                dev_dbg(&udev->dev,"device reset changed speed!\n");
                retval = -ENODEV;
                goto fail;
            }
            if (r)
            {
                dev_err(&udev->dev, "device descriptor " "read/%s, error %d\n","64", r);
                retval = -EMSGSIZE;
                continue;
            }
#undef GET_DESCRIPTOR_BUFSIZE
        }
         /*
          * If device is WUSB, we already assigned an
          * unauthorized address in the Connect Ack sequence;
          * authorization will assign the final address.
          */

          //检测设备是否不为wusb设备
         if (udev->wusb == 0)
        {
            //重试设置地址到2次
            for (j = 0; j < SET_ADDRESS_TRIES; ++j)
            {
                retval = hub_set_address(udev, devnum);
                if (retval >= 0)
                    break;
                msleep(200);
            }
            if (retval < 0)
            {
                dev_err(&udev->dev,"device not accepting address %d, error %d\n",devnum, retval);
                goto fail;
            }
            /* cope with hardware quirkiness:
             * - let SET_ADDRESS settle, some device hardware wants it
             * - read ep0 maxpacket even for high and low speed,
             */

            msleep(10);
            if (USE_NEW_SCHEME(retry_counter))
                break;
          }
        //获取设备描述符
        retval = usb_get_device_descriptor(udev, 8);
        if (retval < 8)
        {
            dev_err(&udev->dev, "device descriptor " "read/%s, error %d\n","8", retval);
            if (retval >= 0)
                retval = -EMSGSIZE;
        }
        else
        {
            retval = 0;
            break;
        }
    }
    if (retval)
        goto fail;
    i = udev->descriptor.bMaxPacketSize0 == 0xff?    /* wusb device? */
     512 : udev->descriptor.bMaxPacketSize0;
    if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i)
    {
        if (udev->speed != USB_SPEED_FULL || !(i == 8 || i == 16 || i == 32 || i == 64))
        {
            dev_err(&udev->dev, "ep0 maxpacket = %d\n", i);
            retval = -EMSGSIZE;
            goto fail;
        }
        dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
        udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
        usb_ep0_reinit(udev);
    }
    retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
    if (retval < (signed)sizeof(udev->descriptor))
    {
        dev_err(&udev->dev, "device descriptor read/%s, error %d\n","all", retval);
        if (retval >= 0)
            retval = -ENOMSG;
        goto fail;
    }
    retval = 0;
fail:
    if (retval)
    {
        hub_port_disable(hub, port1, 0);
        update_address(udev, devnum);    /* for disconnect processing */
    }
    mutex_unlock(&usb_address0_mutex);  
    return retval;
}

枚举的第3步来了
3.主机发送Set_Feature,让根集线器复位端口,使得端口上的设备处于复位状态
hub_port_reset负责完成这个任务
hub_port_reset在/drivers/usb/core/hub.c中

static int hub_port_reset(struct usb_hub *hub, int port1,
                struct usb_device *udev, unsigned int delay)
{
    int i, status;
    /* Block EHCI CF initialization during the port reset.
     * Some companion controllers don't like it when they mix.
     */

    down_read(&ehci_cf_port_reset_rwsem);
    /* Reset the port */
    //重试到5次
    for (i = 0; i < PORT_RESET_TRIES; i++)
    {
        //发送端口复位信号
        status = set_port_feature(hub->hdev,port1, USB_PORT_FEAT_RESET);
        //检测复位信号是否发送成功
        if (status)
            dev_err(hub->intfdev,"cannot reset port %d (err = %d)\n",port1, status);
        else
        {
            //等待复位结束
            status = hub_port_wait_reset(hub, port1, udev, delay);
            if (status && status != -ENOTCONN)
                dev_dbg(hub->intfdev,"port_wait_reset: err = %d\n",status);
        }
        /* return on disconnect or reset */
        //检测错误标志
        switch (status)
        {
        case 0:
            /* TRSTRCY = 10 ms; plus some extra */
            msleep(10 + 40);
            //设置设备的设备号为0
            update_address(udev, 0);
            /* FALL THROUGH */
        case -ENOTCONN:
        case -ENODEV:
            clear_port_feature(hub->hdev,port1, USB_PORT_FEAT_C_RESET);
            /* FIXME need disconnect() for NOTATTACHED device */
            usb_set_device_state(udev, status? USB_STATE_NOTATTACHED: USB_STATE_DEFAULT);
            goto done;
        }
        dev_dbg (hub->intfdev,"port %d not enabled, trying reset again...\n",port1);
        delay = HUB_LONG_RESET_TIME;
    }
    dev_err (hub->intfdev,"Cannot enable port %i. Maybe the USB cable is bad?\n",port1);
 done:
    up_read(&ehci_cf_port_reset_rwsem);
    return status;
}

这样接下来就是4步了,继续走下去
hub_port_wait_reset负责第4步和第5步

阅读(2537) | 评论(3) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-06-23 16:54:42

原来一直想总结一下这个过程的,看代码直接绕进去 出不来了

chinaunix网友2010-05-07 11:14:50

谢谢, 写得真好

chinaunix网友2009-04-05 21:57:54

楼主好样的!感谢中!