LINUX下USB1.1设备学习小记(5)_uhci与设备(1)
现在开始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*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步
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/57901/showart_1866665.html
LINUX下的USB1。1设备学习小记
最新推荐文章于 2022-10-22 17:11:22 发布