本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记
在添加主机控制器驱动时,创建了一个定时器hcd->rh_timer,定时器的执行函数是rh_timer_func,这个定时器在register_root_hub完成后启动了。
int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags)
{
……
if (hcd->uses_new_polling && hcd->poll_rh)
usb_hcd_poll_rh_status(hcd);
}
void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
struct urb *urb;
int length;
unsigned long flags;
char buffer[6]; /* Any root hubs with > 31 ports? */
//root hub注册时把hcd->rh_registered设置为1
if (unlikely(!hcd->rh_registered))
return;
//注册时hcd->uses_new_polling
if (!hcd->uses_new_polling && !hcd->status_urb)
return;
//读端口寄存器看端口有没有变化,没有变化length = 0
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;
if (urb) {
hcd->poll_pending = 0;
hcd->status_urb = NULL;
urb->actual_length = length;
memcpy(urb->transfer_buffer, buffer, length);
usb_hcd_unlink_urb_from_ep(hcd, urb);
spin_unlock(&hcd_root_hub_lock);
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 */
//在ohci_run函数中把hcd->uses_new_polling设置为1,在ohci_run函数中也把hcd->poll_rh设置为了1
//启动定时器hcd->rh_timer
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->status_urb不等于NULL。
在hub初始化时,在hub_configure函数中创建了一个中断urb请求。
static int hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint)
{
……
hub->urb = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval);
}
hub_irq函数赋值给了urb->complete函数。
在hub_activate函数中提交了urb请求。
static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
{
……
status = usb_submit_urb(hub->urb, GFP_NOIO);
}
usb_submit_urb中调用usb_hcd_submit_urb函数,将urb给HCD处理
int usb_submit_urb(struct urb *urb, gfp_t mem_flags)
{
……
return usb_hcd_submit_urb(urb, mem_flags);
}
usb_hcd_submit_urb函数里面,如果进来的设备是root hub,那么urb请求由rh_urb_enqueue函数处理,如果不是,调用主机控制器的urb_enqueue函数。
int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
{
……
//root hub调用rh_urb_enqueue进行USB通信
if (is_root_hub(urb->dev))
status = rh_urb_enqueue(hcd, urb);
//如果不是root hub,调用对应的控制器的urb_enqueue函数
else
status = hcd->driver->urb_enqueue(hcd, urb, mem_flags);
}
我们这里还是root hub设备,所以调用rh_urb_enqueue函数。
static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
{
//如果是中断传输
if (usb_endpoint_xfer_int(&urb->ep->desc))
return rh_queue_status (hcd, urb);
//如果是控制传输
if (usb_endpoint_xfer_control(&urb->ep->desc))
return rh_call_control (hcd, urb);
return -EINVAL;
}
我们这里是中断传输,所以调用rh_queue_status函数。
static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
{
……
retval = usb_hcd_link_urb_to_ep(hcd, urb);
if (retval)
goto done;
hcd->status_urb = urb;
urb->hcpriv = hcd; /* indicate it's queued */
}
上面把urb放入urb端口的链表中。
把urb赋值给hcd->status_urb保存起来。这样上面的hcd->status_urb就有值了。
前面说到有一个定时器hcd->rh_timer,执行函数是rh_timer_func
static void rh_timer_func (unsigned long _hcd)
{
usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
}
void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
{
struct urb *urb;
int length;
unsigned long flags;
char buffer[6]; /* 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;
if (urb) {
hcd->poll_pending = 0;
hcd->status_urb = NULL;
urb->actual_length = length;
memcpy(urb->transfer_buffer, buffer, length);
usb_hcd_unlink_urb_from_ep(hcd, urb);
spin_unlock(&hcd_root_hub_lock);
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));
}
usb_hcd_poll_rh_status函数中,调用主机控制器的hub_status_data函数获取端口状态。如果端口的状态有变化,那么length > 0,把获取到的端口状态的数组拷贝到urb->transfer_buffer中,就是前面的hub->buffer中,同时调用usb_hcd_giveback_urb函数。
usb_hcd_giveback_urb函数中调用urb->complete (urb),而urb->complete = hub_irq,这样就返回到了hub中。
返回之前,urb->status = 0,表示urb处理成功了。
urb->actual_length = hcd->driver->hub_status_data返回的length,这个长度根据主机控制器的说明,等于1或2.
进入hub_irq函数。
将获得的端口状态的数组存入一个long型的整数hub->event_bits[0]中,它对应一个Bitmap,bit 0表示Hub有变化,而其它bit则具体表示某一个端口有没有变化,如果一个端口没有变化,对应的那一位就是0。我们通过按位与的方式可以知道哪一个端口发生了改变。
//中断传输就是只有一个urb
static void hub_irq(struct urb *urb)
{ //填充urb的时候,urb->context就是赋的hub
struct usb_hub *hub = urb->context;
int status = urb->status;
unsigned 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);
//刚开始hub->error为0,hub->nerrors也为0
if ((++hub->nerrors < 10) || hub->error)
goto resubmit;
hub->error = status;
/* FALL THROUGH */
/* let khubd handle things */
//urb被顺利的处理
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);
//event_bits[0],是一个数组,对应Bitmap,bit 0表示Hub有变化,而其它bit则具体表示
//某一个端口有没有变化,如果一个端口没有变化,对应的那一位就是0
hub->event_bits[0] = bits;
break;
}
hub->nerrors = 0;
/* Something happened, let khubd figure it out */
//调用kick_khubd()函数,于是会再一次触发hub_events().
kick_khubd(hub);
resubmit:
//hub_suspend ,hub_pre_reset()将hub->quiescing设置为1
//如果hub被挂起了,或者要被reset了,那么就不用重新提交urb了,hub_irq()函数直接返回
if (hub->quiescing)
return;
if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
&& status != -ENODEV && status != -EPERM)
dev_err (hub->intfdev, "resubmit --> %d\n", status);
}
然后调用kick_khubd函数。kick_khubd函数将event_list添加到hub_event_list链表中,表示有事件产生,同时唤醒hub_thread线程。唤醒hub_thread调用的函数是wake_up(&khubd_wait)。
hub_thread线程在hub初始化时就创建好了。
int usb_hub_init(void)
{
if (usb_register(&hub_driver) < 0) {
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;
}
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();
//判断event list不为空或者线程停掉了
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_thread线程在没有事件时,一直在睡眠,除非event list不为空或者线程停掉了。
hub_thread线程被唤醒后,进入了hub_events函数。
在hub_events()函数中我们就会判断是不是有新的usb设备插入了。
而我们这边后面还要继续提交urb,因为我们要一直检测hub端口的状态是否有变化,但是在hcd中上次的urb处理完后就清除了,所以我们要一直提交urb。
现在进入hub_events函数,这个函数的每一步已经有注释了。
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);
if (list_empty(&hub_event_list)) {
spin_unlock_irq(&hub_event_lock);
break;
}
//取出event_list取出消息放入tmp, 并把tmp从队列里删除掉
tmp = hub_event_list.next;
list_del_init(tmp);
//根据tmp得到usb_hub这个结构体
hub = list_entry(tmp, struct usb_hub, event_list);
kref_get(&hub->kref);
spin_unlock_irq(&hub_event_lock);
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 */
//一下几种情况会将hub设置为USB_STATE_NOTATTACHED
//汇报Host Controller异常死机的函数,usb_hc_died()
//hub驱动自己提供的函数,hub_port_disable(),用于关掉一个端口的函数
//断开设备的函数usb_disconnect()
if (hdev->state == USB_STATE_NOTATTACHED) {
hub->error = -ENODEV;
hub_quiesce(hub, HUB_DISCONNECT);
goto loop;
}
/* Autoresume */
//让这个usb interface的电源引用计数加一,只要这个引用计数大于0,这个设备就不允许autosuspend
//autosuspend就是当用户在指定的时间内没有什么活动的话,就自动挂起
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 */
//quiescing是停止的意思,在reset的时候我们会设置它为1,在suspend的时候我们也会把它设置为1,
//一旦把它设置成了1,那么hub驱动程序就不会再提交任何URB
if (hub->quiescing)
goto loop_autopm;
if (hub->error) {
dev_dbg (hub_dev, "resetting for error %d\n",
hub->error);
//把设备reset
ret = usb_reset_device(hdev);
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 */
//表示这个hub有几个端口
for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {
//这个端口正在执行reset或者resume操作
if (test_bit(i, hub->busy_bits))
continue;
//这个端口对应的change_bits没有设置
connect_change = test_bit(i, hub->change_bits);
//这个端口对应的event_bits没有设置
if (!test_and_clear_bit(i, hub->event_bits) &&
!connect_change)
continue;
//获取端口状态,保存在portstatus和portchange
ret = hub_port_status(hub, i,
&portstatus, &portchange);
if (ret < 0)
continue;
//这个端口的Current Connect Status位是否有变化
//如果有变化,发送另一个请求以清除这个flag,并且将connect_change也设置为1
if (portchange & USB_PORT_STAT_C_CONNECTION) {
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_CONNECTION);
connect_change = 1;
}
//每个端口都有一个开关,这叫做enable或者disable一个端口
if (portchange & USB_PORT_STAT_C_ENABLE) {
if (!connect_change)
dev_dbg (hub_dev,
"port %d enable change, "
"status %08x\n",
i, portstatus);
//清除USB_PORT_FEAT_C_ENABLE这个flag
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.
*/
//端口被disable了,但是连接没有变化,并且hdev->children[i]还有值
//有子设备连在端口上,可是端口却被disable了,基本上这种情况就是电磁干扰造成的,设置connect_change为1
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;
}
}
//连在该端口的设备的suspend状态有变化,从suspended状态出来,也就是说resume完成
if (portchange & USB_PORT_STAT_C_SUSPEND) {
struct usb_device *udev;
//清除掉SUSPEND这个flag
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_SUSPEND);
udev = hdev->children[i-1];
//该端口连了子设备的情况就把子设备唤醒,如果端口没有连子设备,那么就把端口disable掉
if (udev) {
usb_lock_device(udev);
ret = remote_wakeup(hdev->
children[i-1]);
usb_unlock_device(udev);
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);
//清除掉OVER_CURRENT这个flag
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_OVER_CURRENT);
//如果其它的端口电流过大,那么将会导致本端口断电,
//即hub上一个端口出现over-current条件将有可能引起hub上其它端口陷入powered off的状态.
//对于over-current的情况我们都把hub重新上电
hub_power_on(hub, true);
}
//一个端口从Resetting状态进入到Enabled状态
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);
}
//如果链接状态发生了变化,执行hub_port_connect_change函数
//有三种情况会调用这个函数,一个是连接有变化,
//一个是端口本身重新使能,即所谓的enable,这种情况通常就是为了对付电磁干扰的
//第三种情况就是在复位一个设备的时候发现其描述符变了,这通常对应的是硬件本身有了升级
if (connect_change)
hub_port_connect_change(hub, i,
portstatus, portchange);
} /* end for i */
/* deal with hub status changes */
//bit 0表示Hub有变化
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 {
//电源有变化,一个hub可以用两种供电方式,一种是自带电源.另一种是没有自带电源,由总线来供电
if (hubchange & HUB_CHANGE_LOCAL_POWER) {
dev_dbg (hub_dev, "power change\n");
//先清除掉标志位
clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
//HUB_STATUS_LOCAL_POWER用来标志这个hub是有专门的外接电源的还是从usb总线上获取电源
//原来是有电源的,而现在没了,把limited_power设置为1
if (hubstatus & HUB_STATUS_LOCAL_POWER)
/* FIXME: Is this always true? */
hub->limited_power = 1;
//如果是原来没有电源现在有了电源,那么可以取消limited_power了,把它设置为0
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, true);
}
}
loop_autopm:
/* Allow autosuspend if we're not going to run again */
if (list_empty(&hub->event_list))
//调用了这个函数这个hub就可以被挂起
usb_autopm_enable(intf);
loop:
usb_unlock_device(hdev);
//减少hub的引用计数
kref_put(&hub->kref, hub_release);
} /* end while (1) */
}
文章参考:https://blog.csdn.net/fudan_abc/article/category/325189