3.4:中断传输过程
1:root hub的中断传输
在usb_hcd_submit_urb()à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)
{
int retval;
unsigned long flags;
int len = 1 + (urb->dev->maxchild / 8);
spin_lock_irqsave (&hcd_root_hub_lock, flags);
//如果root hub已经在处理中断传输或者是urb非法
if (hcd->status_urb || urb->transfer_buffer_length
dev_dbg (hcd->self.controller, "not queuing rh status urb\n");
retval = -EINVAL;
goto done;
}
//将urb添加到urb->ep_>urb_list
retval = usb_hcd_link_urb_to_ep(hcd, urb);
if (retval)
goto done;
hcd->status_urb = urb;
urb->hcpriv = hcd; /* indicate it's queued */
//uhci_start(),会将uses_newpolling设为了1
if (!hcd->uses_new_polling)
mod_timer(&hcd->rh_timer, (jiffies/(HZ/4) + 1) * (HZ/4));
/* If a status change has already occurred, report it ASAP */
else if (hcd->poll_pending)
mod_timer(&hcd->rh_timer, jiffies);
retval = 0;
done:
spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
return retval;
}
从上面的代码可以看到,urb最终会交给hcd->rh_timer这个定时器进行处理.在前面的分析中,我们看到了在uhci_start()中 设置了usrs_new_polling.搜索整个linux源代码,发现其它地方没有更改这个值.所以,上面代码中的if判断是不会满足的.那 hcd->poll_pending会不会满足呢?
要回答这个问题,我们得返回看一下usb_add_hcd()的处理,在那里我们忽略了一个函数的处理.在这个函数中,有这样的代码片段:
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);
return retval;
......
}
在start_rh()中,会将hcd->poll_rh设为1,那在初始化阶段,usb_hcd_poll_rh_status()会得到运行:
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;
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);
}
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没有注册root hub,会出错退出.实际上,这个函数就是为了周期性检测root hub的状态而设的.
之后会调用length = hcd->driver->hub_status_data(hcd, buffer);
它会回溯到UHCI驱动的hub_status_data().这个函数会去取root hub的状态,如果状态发生改变,则将改变的状态存放在buffer中.然后返回1.如果没有必变,则返回0.
hcd->status_urb终于对应到前面rh_queue_status()的分析了.我们知道,如果主机发出中断传输,会将hcd->status_urb指向这个urb.
那,这个函数对应的操作为:
如果主机有发了中断传输来获得root hub的状态,就将状态值赋值到urb的传输缓存区.如果没有,状态却发生了改变,将hcd->poll_pending设为1.
函数后面的if判断是肯定会成功的.因为hcd->uses_new_polling ,hcd->poll_rh都为真.然后,启动了定时器hcd->rh_timer.
这个定时器的初始化如下:
在usb_create_hcd()中:
......
init_timer(&hcd->rh_timer);
hcd->rh_timer.function = rh_timer_func;
hcd->rh_timer.data = (unsigned long) hcd;
......
也就是说,这个定时器的处理函数为rh_timer_func().参数为hcd.
处理函数代码如下:
static void rh_timer_func (unsigned long _hcd)
{
usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
}
看到了吧.这个定时器的处理函数就是上面分析的usb_hcd_poll_rh_status.
也就是说,从系统初始化后,这个定时器就会一直在运行了.
OK.返回到rh_queue_status().如果root_hub的状态发生了改变,就会重新设定定时器hcd->rh_timer.这样做是为了防止某些情况下(例如改变了hcd->uses_new_polling的值)定时器停止.
我们在上面分析过了这个定时器的处理函数.不过还留下了一个尾巴,也就是hcd->driver->hub_status_data().下面就来分析一下这个函数.
这个函数指针的接口为
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned long flags;
int status = 0;
spin_lock_irqsave(&uhci->lock, flags);
//扫描调度队列,将一些已经运先完毕的TD,QH删除掉
uhci_scan_schedule(uhci);
//如果hcd不处于HCD_FLAG_HW_ACCESSIBLE状态,或者处于"死"状态
//即不可操作状态
if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)
goto done;
//检查UHCI的端口,也即root hub的端口
uhci_check_ports(uhci);
//判断端口状态是否改变
status = get_hub_status_data(uhci, buf);
//端口状态改变的相应处理
//uhci->rh_state,是root hub以前的状态
//如果status为1,则状态发生了改变
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 */
//如果旧状态是stop,且发生了改变,那就将root hub启动了
if (status)
wakeup_rh(uhci);
break;
//如果旧状态是RH_RUNNING.则判断root hub下面的端口状态.如果端口末连接
//将其置为UHCI_RH_RUNNING_NODEVS.且设置auto_stop_time为 jiffies + HZ
case UHCI_RH_RUNNING:
/* are any devices attached? */
if (!any_ports_active(uhci)) {
uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
uhci->auto_stop_time = jiffies + HZ;
}
break;
//如果旧状态是UHCI_RH_RUNNING_NODEVS.判断是否有设备连上了root hub
//如果有连上,将其状态置为UHCI_RH_RUNNING
//如果还是超时还是没连上,将端口suspend
case UHCI_RH_RUNNING_NODEVS:
/* auto-stop if nothing connected for 1 second */
if (any_ports_active(uhci))
uhci->rh_state = UHCI_RH_RUNNING;
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;
}
这个函数的操作逻辑还是比较清淅,不过大量状态的判断让人觉得头昏眼花.
首先函数调用uhci_scan_schedule()去扫描挂在UHCI上面的调度队列,如果有TD或者QH被执行完,就应该将它们从调度队列中删除. 这个函数在中断处理过程还会涉及到.到那时再进行详细的分析.在这里只要知道它是干什么的就可以了.它不会影响后续的流程.
然后,会调用uhci_check_ports()去检查端口.它主要是对超时末完成的Reset状态和resume信号的处理
接着,调用get_hub_status_data()去判断端口状态是否发生了改变,如果发生了改变,返回值为1.且将改变状态的端口在buf中置位.
最后,就是一个很长的switch语句来处理对应状态的改变.
如果之前的状态是UHCI_RH_SUSPENDING和UHCI_RH_SUSPENDED,那表明之前root hub处于挂起状态.如果状态发生了改变,就要将它从挂起状态中恢复过来.这一个过程涉及到了电源管理.暂且忽略掉这一倍份.
如果之前是UHCI_RH_AUTO_STOPPED状态.表明设备之前是STOP的,现在状改变了,当然要将它重新启用了.
如果之前是UHCI_RH_RUNNING状态,表示root hub处于正常的联连.那就去判断这个root hub上是否有设备相连.如果没有.则将其置为UHCI_RH_RUNNING_NODEVS.假设root hub上之前挂了一个U盘.此时的状态是UHCI_RH_RUNNING_RUNING的,之后我将U盘拨下,这时候端口会转变为 UHCI_RH_RUNNING_NODEVS状态.
如果之前是UHCI_RH_RUNNING_NODEVS,去判断hub下面是否有设备相联.如果有,则将它转换到UHCI_RH_RUNNING_RUNING.如果还是没有设备相联,且没有设备相联的时候超过了一个HZ.将就端口suspend.用来节省电源.
下面,挨个分析这个函数的几个子函数.
第一个要分析的子函数是: uhci_check_ports()
static void uhci_check_ports(struct uhci_hcd *uhci)
{
unsigned int port;
unsigned long port_addr;
int status;
//遍历UHCI下面的端口
for (port = 0; port rh_numports; ++port) {
port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
status = inw(port_addr);
//端口处于Reset状态
if (unlikely(status & USBPORTSC_PR)) {
//在用户将root hub reset时,会将ports_timeout设置成iffies + //msecs_to_jiffies(50)
//如果超时之后,RESET过程还末完成
if (time_after_eq(jiffies, uhci->ports_timeout)) {
//将一些状态位和PR位清了
CLR_RH_PORTSTAT(USBPORTSC_PR);
udelay(10);
/* HP's server management chip requires
* a longer delay. */
//HP主机的特殊处理
if (to_pci_dev(uhci_dev(uhci))->vendor ==
PCI_VENDOR_ID_HP)
wait_for_HP(port_addr);
/* If the port was enabled before, turning
* reset on caused a port enable change.
* Turning reset off causes a port connect
* status change. Clear these changes. */
//清除掉CSC,PEC,并设置PE
CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC);
SET_RH_PORTSTAT(USBPORTSC_PE);
}
}
//端口探测到了Resume 信号
if (unlikely(status & USBPORTSC_RD)) {
//对于Resume,有两个可能的情况,一种是用户设置端口成Resume
//另外的一种是状态检测到端口变为Resume
//如果用户设置,会将port在resuming_ports置位.否则,便是状态检测到端口变为Resume
if (!test_bit(port, &uhci->resuming_ports)) {
/* Port received a wakeup request */
//如果是轮询检测的.将其在resuing_port中置位.
//并且设置port_timeout为iffies +msecs_to_jiffies(20)
//将状态检测定时器设置在prot_timeout后运行
set_bit(port, &uhci->resuming_ports);
uhci->ports_timeout = jiffies +
msecs_to_jiffies(20);
/* Make sure we see the port again
* after the resuming period is over. */
mod_timer(&uhci_to_hcd(uhci)->rh_timer,
uhci->ports_timeout);
}
//如果在超时过后,还是处于Resume.
//需要软件来清除这个状态
else if (time_after_eq(jiffies,
uhci->ports_timeout)) {
uhci_finish_suspend(uhci, port, port_addr);
}
}
}
}
第一个要处理的是RESET状态,用户可以通过控制传输将root hub 进行Rest操作,如果Reset过程超过了50ms.这就需要软件来处理了,驱动将一些状态标志清除之后,设置PE位,PE即port enable的意思.
第二个要处理的是Resume信息.本来设备在收到这个信号的时候,应该将设备从挂起状态恢复到正常状态.但是如果20ms之后,恢复过程还没有结束,就 需要软件去处理了.在代码中我们可以看到,它会调用uhci_finish_suspend()去完成软件处理的这一过程.代码如下:
static void uhci_finish_suspend(struct uhci_hcd *uhci, int port,
unsigned long port_addr)
{
int status;
int i;
//如果端口处于恢复或者是Resum状态
if (inw(port_addr) & SUSPEND_BITS) {
//将SUSP和RD位清除
CLR_RH_PORTSTAT(SUSPEND_BITS);
//如果端口在resuming被置,将其在suspend置位,表示是刚刚恢复过来的port
if (test_bit(port, &uhci->resuming_ports))
set_bit(port, &uhci->port_c_suspend);
/* The controller won't actually turn off the RD bit until
* it has had a chance to send a low-speed EOP sequence,
* which is supposed to take 3 bit times (= 2 microseconds).
* Experiments show that some controllers take longer, so
* we'll poll for completion. */
//等待UHCI清降RD位
for (i = 0; i
if (!(inw(port_addr) & SUSPEND_BITS))
break;
udelay(1);
}
}
//在resuming_ports中将对应位清除
clear_bit(port, &uhci->resuming_ports);
}
从上面的处理我们可以看到,它先将SUSP和RD位清除,从代码的注释中可以看到,UHCI要发送一个EOP包之后,才会改变它的状态.因此在这里一直延时等待它的状态改变完成.
在这里,注意到刚刚恢复的端口会在port_c_suspend位图中置位.那要到什么时候才会将它在port_c_suspend位图中清除呢?
必须要等待UHCI向root hub发送CLEAR_FEATURE才会将其清除. 只是这个过程我们在分析控制传输的时候将它忽略过去了,:-)
到这里, uhci_check_ports()函数就分析完了.
第二个要分析的函数是get_hub_status_data().它会判断端口状态是否发生了改变.代码如下:
static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)
{
int port;
//mask设定为了SC1的RWC位.这些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. */
//有的主板会误报错误,使mask去掉这几位的匹配
if (ignore_oc)
mask &= ~USBPORTSC_OCC;
*buf = 0;
//如果端口发生了改变,则将buf中的相应位置1
for (port = 0; port rh_numports; ++port) {
if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) ||
//或者是端口刚恢复过来
test_bit(port, &uhci->port_c_suspend))
*buf |= (1
}
//如果buf不为空,返回真
return !!*buf;
}
从UHCI的spec中可以看到,对应PORTSC寄存器的RW/C属性的位,其实都是状态改变位,判断这些位是否被置就可以得知端口状态是否发生了改变.
在这里要注意buf中置位操作,它是*buf |= (1
疑问:在这里通过读取PORTSC的值来判断ROOT HUB的状态是否发生了改变,那代码中又没看到什么地方对它清位.这是为什么呢???有一位高人告诉我,是在hub driver中完成的.初想之下似乎有点不可思议,不过,到分析HUB驱动的时候再来解答这个疑问好了:-(
第三个要分析的函数是wakeup_rh().这个函数将root hub重新运行起来,代码如下:
static void wakeup_rh(struct uhci_hcd *uhci)
__releases(uhci->lock)
__acquires(uhci->lock)
{
dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
"%s%s\n", __FUNCTION__,
uhci->rh_state == UHCI_RH_AUTO_STOPPED ?
" (auto-start)" : "");
/* If we are auto-stopped then no devices are attached so there's
* no need for wakeup signals. Otherwise we send Global Resume
* for 20 ms.
*/
//如果UHCI处于挂起状态,将其恢复
if (uhci->rh_state == UHCI_RH_SUSPENDED) {
//将将态设为UHCI_RH_RESUMING.表示正在恢复
uhci->rh_state = UHCI_RH_RESUMING;
//置FGD,EGSM,CF位
outw(USBCMD_FGR | USBCMD_EGSM | USBCMD_CF,
uhci->io_addr + USBCMD);
spin_unlock_irq(&uhci->lock);
//延迟20s
msleep(20);
spin_lock_irq(&uhci->lock);
//如果uhci dead,非法
if (uhci->dead)
return;
/* End Global Resume and wait for EOP to be sent */
//再次写入CF
outw(USBCMD_CF, uhci->io_addr + USBCMD);
mb();
udelay(4);
//照spec上的说法,20s过后,FGR标志应该是会清除的,如果没有,打印出警告
if (inw(uhci->io_addr + USBCMD) & USBCMD_FGR)
dev_warn(uhci_dev(uhci), "FGR not stopped yet!\n");
}
//启动RH
start_rh(uhci);
/* Restart root hub polling */
mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies);
}
按照UHCI spec上的说法,如果是处于挂起状态的设备,可以给它发送一个强制的RESUME信号.当UHCI检测到USBCMD_FGR位被设置之后,会让设备处于活跃状态.发送强制的RESUME会有20s的时间.
之后的start_rh()我们在之前分析过,这里不再赘述.
第四个要分析的函数是any_ports_active().它会检测设备的端口状态.状态如下:
static int any_ports_active(struct uhci_hcd *uhci)
{
int port;
for (port = 0; port rh_numports; ++port) {
if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) &
(USBPORTSC_CCS | RWC_BITS)) ||
//或者是端口刚刚恢复过来
test_bit(port, &uhci->port_c_suspend))
return 1;
}
return 0;
}
跟前面分析的一样,还是取PORTSC的值,如果状态有改变或者CCS被置,就认为端口上已经连上设备了.CCS位表示Current Connect Status.
第五个要分析的函数是suspend_rh().
static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state)
__releases(uhci->lock)
__acquires(uhci->lock)
{
int auto_stop;
int int_enable, egsm_enable;
auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
dev_dbg(&uhci_to_hcd(uhci)->self.root_hub->dev,
"%s%s\n", __FUNCTION__,
(auto_stop ? " (auto-stop)" : ""));
/* If we get a suspend request when we're already auto-stopped
* then there's nothing to do.
*/
//如果已经在STOP状态了,不需要进行任何处理了
if (uhci->rh_state == UHCI_RH_AUTO_STOPPED) {
uhci->rh_state = new_state;
return;
}
/* Enable resume-detect interrupts if they work.
* Then enter Global Suspend mode if _it_ works, still configured.
*/
egsm_enable = USBCMD_EGSM;
uhci->working_RD = 1;
int_enable = USBINTR_RESUME;
//选择编译函数
if (remote_wakeup_is_broken(uhci))
egsm_enable = 0;
//这代码段也可以忽略,是用来修正某些设备的
if (resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||
!device_may_wakeup(
&uhci_to_hcd(uhci)->self.root_hub->dev))
uhci->working_RD = int_enable = 0;
//置RESUME位,表示如果有Resume信号,将会引发中断
outw(int_enable, uhci->io_addr + USBINTR);
//置位EGSM和CF
outw(egsm_enable | USBCMD_CF, uhci->io_addr + USBCMD);
mb();
udelay(5);
/* If we're auto-stopping then no devices have been attached
* for a while, so there shouldn't be any active URBs and the
* controller should stop after a few microseconds. Otherwise
* we will give the controller one frame to stop.
*/
//调用这个函数的new_start有两种可能,UHCI_RH_SUSPENDED和UHCI_RH_AUTO_STOPPED
//如果UHCI还没有halted?.再等1s
if (!auto_stop && !(inw(uhci->io_addr + USBSTS) & USBSTS_HCH)) {
//将状态置为SUSPENDING.表示正在进行挂起状态
uhci->rh_state = UHCI_RH_SUSPENDING;
spin_unlock_irq(&uhci->lock);
msleep(1);
spin_lock_irq(&uhci->lock);
if (uhci->dead)
return;
}
//如果还没halted,打印出警告
if (!(inw(uhci->io_addr + USBSTS) & USBSTS_HCH))
dev_warn(&uhci_to_hcd(uhci)->self.root_hub->dev,
"Controller not stopped yet!\n");
//得到当前传输的帧号
uhci_get_current_frame_number(uhci);
//更新状态,设is_stopped为1,在正常情况下,poll_rh会设为0.rh_timer也停止了
uhci->rh_state = new_state;
uhci->is_stopped = UHCI_IS_STOPPED;
uhci_to_hcd(uhci)->poll_rh = !int_enable;
//更新一下调度队列
uhci_scan_schedule(uhci);
//停止FSBR
uhci_fsbr_off(uhci);
}
首先,调用这个函数的时候有两种情况,一种是以UHCI_RH_SUSPENDED为参数进行调用,另外的一种是以UHCI_RH_AUTO_STOPPED进行调用.即对root hub进行挂起或者停止操作.
然后,在命令寄存器中置位USBCMD_EGSM.这样,设备就会进入EGSM模式.再启用Resume中断.如果以后有resume就会进入到中断处理程序.
最后,设置poll_rh为0,rh_timer定时器随之停止了.
可能会有这样的疑问,既然root hub的轮询状态定时器已经停止,那之后要怎么去更改root hub的状态呢?我们不是在前面打开了Resume中断么?然后在中断处理中,又会将rh_timer唤起.
到这里, uhci_hub_status_data()函数已经分析完了.相应的,root_hub的中断传输返回了.
2:非root_hub的中断传输
对于非root_hub的中断传输中,流程会转向到hcd->driver->urb_enqueue()进行处理.
首先,在uhci_alloc_qh()对中断传输就有一些特殊的处理.如下代码片段如示 :
static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,
struct usb_device *udev, struct usb_host_endpoint *hep)
{
......
......
//计算传输所占的总线时间,以微秒为单位
if (qh->type == USB_ENDPOINT_XFER_INT ||
qh->type == USB_ENDPOINT_XFER_ISOC)
qh->load = usb_calc_bus_time(udev->speed,
usb_endpoint_dir_in(&hep->desc),
qh->type == USB_ENDPOINT_XFER_ISOC,
le16_to_cpu(hep->desc.wMaxPacketSize))
/ 1000 + 1;
......
}
在代码中判断,如果是中断传输或者是实时传输,就计算传输数据所耗的总线时间.这些计算公式都是由usb2.0 spec上规定的.
细心一点朋友可能发现了,在这里计算传输的字节数是hep->desc.wMaxPacketSize,即一次传输的数据值,但是传输数据总量不是hep->desc.wMaxPacketSize,它有可能会打成多个包嘛?
嗯,不错,有可能中断传输需要多个包,它的数据总量也并不是hep->desc.wMaxPacketSize.对应到UHCI的调度来说,他需要 多个TD.前面我们分析FSBR机制的时候就说过,在一个frame内,默认用深度扫描的方式去扫描各个QH,然后处理一个挂下它下面的TD,然后就去处 理另外的QH.从这里,我们知道,在一个frame内.只会传输一个数据包的,这个数据包的数据最大值就是 hep->desc.wMaxPacketSize,那也就是说,一个frame内,所消耗的总线时间就是 hep->desc.wMaxPacketSize大小的耗时.
哪为什么要计算它所需的总线时间呢?因为USB2.0 spec规定,实时传输加上中断传输所耗的带宽不能超过总带宽的90%.对应到总线时间上,每一个frmae内, 实时传输加上中断传输的时间不能超过900微秒.因为一个frame是1毫秒,也就是1000微秒.
返回到uhci_urb_enqueue(),在switch的判断中,会进入到uhci_submit_interrupt().代码如下:
static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,
struct uhci_qh *qh)
{
int ret;
/* USB 1.1 interrupt transfers only involve one packet per interval.
* Drivers can submit URBs of any length, but longer ones will need
* multiple intervals to complete.
*/
//QH在初始化的时候会将bandwidth_reserved置为0
if (!qh->bandwidth_reserved) {
int exponent;
/* Figure out which power-of-two queue to use */
//计算合适的周期值
for (exponent = 7; exponent >= 0; --exponent) {
if ((1 interval)
break;
}
if (exponent
return -EINVAL;
//qh->period:调度QH的周期
qh->period = 1
//QH所在的frame 项
qh->skel = SKEL_INDEX(exponent);
/* For now, interrupt phase is fixed by the layout
* of the QH lists. */
//检查带宽是否足够
qh->phase = (qh->period / 2) & (MAX_PHASE - 1);
ret = uhci_check_bandwidth(uhci, qh);
if (ret)
return ret;
} else if (qh->period > urb->interval)
return -EINVAL; /* Can't decrease the period */
//将数据保打成TD,然后挂以QH上
ret = uhci_submit_common(uhci, urb, qh);
if (ret == 0) {
urb->interval = qh->period;
//分得所需要的带宽
if (!qh->bandwidth_reserved)
uhci_reserve_bandwidth(uhci, qh);
}
return ret;
}
首先为传输选择一个合适的中断值.在前面我们分析过,UHCI的调度里,分为了从int1,int2,int4...int128这8种类型的中断.那在 urb中的间隔值可能没有落在这几个点上,例如,urb中的间隔值是34.落在int32和int64之间,所以要选择32做为它的周期.
SKEL_INDEX()是将周期值转换为skelqh中的序号.例如,int128对应的就是skelqh[2].
在这里注意到,打成TD的过程是调用uhci_submit_common().这和批量传输过程是一样的,在这里就不再做分析了.
在这里,要特别分析一下两个函数,一个是用来计算带宽是否足够的函数uhci_check_bandwidth().另一个是用来设置占用带宽的函数uhci_reserve_bandwidth().
先来分析uhci_check_bandwidth(),代码如下:
static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
int minimax_load;
/* Find the optimal phase (unless it is already set) and get
* its load value. */
if (qh->phase >= 0)
minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
else {
int phase, load;
int max_phase = min_t(int, MAX_PHASE, qh->period);
qh->phase = 0;
minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
for (phase = 1; phase
load = uhci_highest_load(uhci, phase, qh->period);
if (load
minimax_load = load;
qh->phase = phase;
}
}
}
/* Maximum allowable periodic bandwidth is 90%, or 900 us per frame */
if (minimax_load + qh->load > 900) {
dev_dbg(uhci_dev(uhci), "bandwidth allocation failed: "
"period %d, phase %d, %d + %d us\n",
qh->period, qh->phase, minimax_load, qh->load);
return -ENOSPC;
}
return 0;
}
注意到,在uhci_submit_interrupt()中,将qh->phase设置如下:
qh->phase = (qh->period / 2) & (MAX_PHASE - 1);
这个phase肯定是大于或者等于0的.
根据上面的计算方式,int1,int2...int128分别对应的phase值是:
0,1,2,4,8,16,0,0
那这个phase代表的是什么呢?不着急,先看完整个过程的分析.
先撇开其它的,只看后面的一个if语句:
if (minimax_load + qh->load > 900)
......
根据注释和打印消息的提示,这里是带宽不够的情况,qh->load是我们在前面计算出来的,这次传输在frame所占的总线时间.以微秒为单位. 再根据注释的说明,再加上我们之前的分析:等时传输加上中断传输的带宽不能超过90%.也就是一个frame的900微秒.据此,我们可以肯定 minimax_load就是frame中已经被消耗的时间.
跟踪到uhci_highest_load():
static int uhci_highest_load(struct uhci_hcd *uhci, int phase, int period)
{
int highest_load = uhci->load[phase];
for (phase += period; phase
highest_load = max_t(int, highest_load, uhci->load[phase]);
return highest_load;
}
这个函数用来计算中断传输所属的frame的负载情况.uhci有一个32项的数组.其实就是代表32个frame.在前面分析可以知道,UHCI调度中总共有1024项.这是就是以32项来表示1024项frame 的负载情况.
这个函数本身还是好懂,就是以phase为起点,以period为周期,找出数组项的最大值.
结合上面的分析:
int1,int2...int128分别对应的phase值是:
0,1,2,4,8,16,0,0
0,1,2,4,8,16这几项倒还是好理解,就是对应int1,int2,int4...int32在uhci->frame[]中的起始位置.这几个间隔在前32项数组里呈一个周期重复的关系,用32项数组来表示他们的负载情况毫无疑问是可以的.
Int1所占的位置是0.任何间隔后面都可以跟int1.因此,在这里,起始位置0也可以被其它间隔重用.
疑问:int64,int128都是以0项来表示负载的.即uhci->load[0].如果有这样 的情况,int64的中断传输已经处于饱和状态.那肯定会有: uhci->load[0]即将要大于900.那这时候,int128的中断传输是加不进来的.尽管uhci->frame[] 中,int128所占的frame项处于空闲状态
.
uhci_reserve_bandwidth()的代码如下:
static void uhci_reserve_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
int i;
int load = qh->load;
char *p = "??";
for (i = qh->phase; i period) {
uhci->load
+= load;
uhci->total_load += load;
}
uhci_to_hcd(uhci)->self.bandwidth_allocated =
uhci->total_load / MAX_PHASE;
switch (qh->type) {
case USB_ENDPOINT_XFER_INT:
++uhci_to_hcd(uhci)->self.bandwidth_int_reqs;
p = "INT";
break;
case USB_ENDPOINT_XFER_ISOC:
++uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs;
p = "ISO";
break;
}
qh->bandwidth_reserved = 1;
dev_dbg(uhci_dev(uhci),
"%s dev %d ep%02x-%s, period %d, phase %d, %d us\n",
"reserve", qh->udev->devnum,
qh->hep->desc.bEndpointAddress, p,
qh->period, qh->phase, load);
}
首先,更新load[]数组,因为要传输一个中断数据包.会占据总线时间.在传输完成这个,同样也会更新load[].只不过,到时候是将它的值减下来.因为传输完成了,释放总线时间.
然后,更新uhci中的各项计算.
最后,将qh->bandwidth_reserver置为1.表示已经为这个QH分配带宽了.
就这样, uhci_submit_interrupt()就处理完了.流程返回到uhci_urb_enqueue().
同之前分析过的其它类型的传输一样,流程会进入到uhci_activate_qh().只不过,在函数里调用的是link_interrupt()用来跟具体的调度frame关联起来.代码如下:
static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
struct uhci_qh *pqh;
//挂到skelqh[qh->skel]的最后面
list_add_tail(&qh->node, &uhci->skelqh[qh->skel]->node);
//找到链接在最末尾的QH
pqh = list_entry(qh->node.prev, struct uhci_qh, node);
//链接到这个QH的后面
qh->link = pqh->link;
wmb();
pqh->link = LINK_TO_QH(qh);
}
很简单的操作,就是将qh插到对应的间隔的frame就可以了.
在这里,要注意,流回返回uhci_urb_enqueue之后, uhci_urbp_wants_fsbr()是什么都不会做的.
因为中断传输并没有调用uhci_add_fsbr()将fsbr开启.
到这里,中断传输已经分析完了.
3.5:实时传输过程
Root hub是不支持实时传输的.困此,流程一直流到uhci_urb_enqueue()里面.
对于实时传输, uhci_alloc_qh()有特殊的处理.如下面的代码片段如示:
static int uhci_urb_enqueue(struct usb_hcd *hcd,
struct urb *urb, gfp_t mem_flags)
{
......
......
if (qh->type != USB_ENDPOINT_XFER_ISOC) {
qh->dummy_td = uhci_alloc_td(uhci);
if (!qh->dummy_td) {
dma_pool_free(uhci->qh_pool, qh, dma_handle);
return NULL;
}
}
qh->state = QH_STATE_IDLE;
qh->hep = hep;
qh->udev = udev;
hep->hcpriv = qh;
//计算传输所占的总线时间,以微秒为单位
if (qh->type == USB_ENDPOINT_XFER_INT ||
qh->type == USB_ENDPOINT_XFER_ISOC)
qh->load = usb_calc_bus_time(udev->speed,
usb_endpoint_dir_in(&hep->desc),
qh->type == USB_ENDPOINT_XFER_ISOC,
le16_to_cpu(hep->desc.wMaxPacketSize))
/ 1000 + 1;
......
}
如上面的代码所示,它并不会像其它的传输那样去创建qh->dummy_td.它也会跟中断传输一样去计算它所耗的总线时间.
在uhci_urb_enqueue()中的switch判断中,流程转入到uhci_submit_isochronous(),在分析代码之前,先来看一下中断传输的调度周期和实时传输的调度周期的差别.
首先是调度周期的范围不一样,中断传输的调度周期是从int1到int128,而实时传输的调度周期是从int1到int1024,不过两种传输的调度周期都有一个规律,就是它的周期值都是2的n次方的形式.也就是说它的低n位是对齐的.
再者,他们调度的起始点不一样,从前面UHCI调度架构初始化部份可以看到,UHCI的1024个frmame,全指向的int1到int128,也即 uhci->skelqh[2]~uhci->skelqh[9]项.这些间隔的起始位置在frame中都是被限定好了的.而对于实时传输, 它的调度起点可以任意定.
好了,可以跟进代码了,分段分析,如下示:
static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb,
struct uhci_qh *qh)
{
struct uhci_td *td = NULL; /* Since urb->number_of_packets > 0 */
int i, frame;
unsigned long destination, status;
struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
/* Values must not be too big (could overflow below) */
//间隔时间不能超过1024,数据包个数不能超为1024
if (urb->interval >= UHCI_NUMFRAMES ||
urb->number_of_packets >= UHCI_NUMFRAMES)
return -EFBIG;
/* Check the period and figure out the starting frame number */
if (!qh->bandwidth_reserved) {
qh->period = urb->interval;
//URB_ISO_ASAP这个flag是专门给等时传输用的,
//它的意思就是告诉驱动程序,只要带宽允许,那么就从此点开始设置这个urb的start_frame变//量
if (urb->transfer_flags & URB_ISO_ASAP) {
qh->phase = -1; /* Find the best phase */
//找到一个最合适的phase
i = uhci_check_bandwidth(uhci, qh);
if (i)
return i;
/* Allow a little time to allocate the TDs */
//取得当前的frame
uhci_get_current_frame_number(uhci);
//延迟10ms.让它有足够的时候分配内存
frame = uhci->frame_number + 10;
/* Move forward to the first frame having the
* correct phase */
//找到frame下一个周期的起始位置
urb->start_frame = frame + ((qh->phase - frame) &
(qh->period - 1));
} else {
//指定的起始帧不能在扫描帧的前面,扫描帧,也就是当前UHCI调度的帧号
//起始帧不能放到被调度帧的前面.这样避免要传输的ISO数据的前面的帧要晚于后面的帧调//度
i = urb->start_frame - uhci->last_iso_frame;
if (i = UHCI_NUMFRAMES)
return -EINVAL;
//计算phase
//start_frame要落在这个周期点上
qh->phase = urb->start_frame & (qh->period - 1);
i = uhci_check_bandwidth(uhci, qh);
if (i)
return i;
}
}
该函数先对入口参数进行有效性判断.在UHCI的架构上,进入到这个函数时,qh是刚刚分配初始化好的,它的bandwidth_reserved肯定是0,所以上面的if判断是肯定会满足的.
Urb->interval是驱动程序在提交实时传输时设置的传输间隔,也就是ISO数据包的调度周期,如果用户指明了URB_ISO_ASAP,则表示该ISO数据包要尽快的传送.
对于指定了URB_ISO_ASAP的情况,就是为传输选定一个周期始点和ISO数据的起始帧号.如果没有指定这个标志,那就使用用户自定义的起始帧号,但是请注意,这个起始帧号不能在当前调度帧的前面.为什么?
如果起始帧在当前调度帧的前面,那有可能,ISO数据包后面的帧会在当前调度帧的后面.那有可能后面的ISO数据反而要比前面的ISO数据先进行调度.
这段代码涉及到phase和start_frame计算比较涩晦,稍后再进行详细分析.
else if (qh->period != urb->interval) {
return -EINVAL; /* Can't change the period */
} else {
/* Find the next unused frame */
if (list_empty(&qh->queue)) {
frame = qh->iso_frame;
} else {
struct urb *lurb;
lurb = list_entry(qh->queue.prev,
struct urb_priv, node)->urb;
frame = lurb->start_frame +
lurb->number_of_packets *
lurb->interval;
}
if (urb->transfer_flags & URB_ISO_ASAP) {
/* Skip some frames if necessary to insure
* the start frame is in the future.
*/
uhci_get_current_frame_number(uhci);
if (uhci_frame_before_eq(frame, uhci->frame_number)) {
frame = uhci->frame_number + 1;
frame += ((qh->phase - frame) &
(qh->period - 1));
}
} /* Otherwise pick up where the last URB leaves off */
urb->start_frame = frame;
}
/* Make sure we won't have to go too far into the future */
//数据不能超长
if (uhci_frame_before_eq(uhci->last_iso_frame + UHCI_NUMFRAMES,
urb->start_frame + urb->number_of_packets *
urb->interval))
return -EFBIG;
后面的这几个elseif ,if肯定是不会进去的,可以直接跳过.一次传输的ISO数据包也不能够太长,从上面的计算,可以看出,一次ISO传输不能超过1023个包.
//置ACTIVE和IOS
status = TD_CTRL_ACTIVE | TD_CTRL_IOS;
destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
for (i = 0; i number_of_packets; i++) {
td = uhci_alloc_td(uhci);
if (!td)
return -ENOMEM;
uhci_add_td_to_urbp(td, urbp);
uhci_fill_td(td, status, destination |
uhci_explen(urb->iso_frame_desc.length),
urb->transfer_dma +
urb->iso_frame_desc.offset);
}
/* Set the interrupt-on-completion flag on the last packet. */
//最后的一个TD置IOC
td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);
到这里的话,就是要将数据打成TD了.首先,对于每一个TD,都要置ACTIVE和IOS位,表示TD是一个可被调度的实时传输型的数据包.然后,对于最后的一个TD还要设置IOC位,这样,速个数据传输完成之后就会产生中断.
对于urb->iso_frame_desc数组,这是驱动程序在提交URB的时候,就已经设置了相关项的.上面代码中的urb->iso_frame_desc.length, urb->iso_frame_desc.offset分别表示该帧的长度和在整个数据中的偏移值.
这样,ISO对应的TD包就全部在urbp->td_list上面了
/* Add the TDs to the frame list */
//将TD挂到相应的frame上
frame = urb->start_frame;
list_for_each_entry(td, &urbp->td_list, list) {
uhci_insert_td_in_frame_list(uhci, td, frame);
frame += qh->period;
}
if (list_empty(&qh->queue)) {
qh->iso_packet_desc = &urb->iso_frame_desc[0];
qh->iso_frame = urb->start_frame;
}
//将skel置为SKEL_ISO
qh->skel = SKEL_ISO;
//设置占用带宽
if (!qh->bandwidth_reserved)
uhci_reserve_bandwidth(uhci, qh);
return 0;
}
创建好了TD之后就要跟UHCI的调度系统关联起来了.从上面的代码中可以看到,它是每隔特定的周期就在uhci->frame[]数组里插入对应的TD.
从前面的流程中看来, list_empty(&qh->queue)是肯定会满足的,就这样, qh->iso_packet_desc指定了urb->iso_frame_desc[]的首地址.qh->iso_frame就是 它的起始帧位置.
经过上面的分段分析之后,流程就很清晰了.在代码中,有几个情况必须要详细的分析一下.
1:在指定URB_ISO_ASAP标志的情况下,phase和start_frame的计算
正如之前在分析中断传输的过程一样,phase就是指在frame[]中起始位置.在中断传输中,指定间隔的起始位置都是固定好的.但在ISO传输中,就 没必要了,因为它对数据的实时性要求很高,只要能够传输ISO,就马上让它传输出去,没必须要等到指定的间隔位置到来之后才去调度它.
在代码中,先将phase置为-1.然后调用uhci_check_bandwidth()来选择一个合适的phase值.这个函数在之前分析过,在这里将其关部份列出:
static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
......
......
if (qh->phase >= 0)
minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
else {
int phase, load;
int max_phase = min_t(int, MAX_PHASE, qh->period);
qh->phase = 0;
minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
for (phase = 1; phase
load = uhci_highest_load(uhci, phase, qh->period);
if (load
minimax_load = load;
qh->phase = phase;
}
}
}
......
}
从代码中很容易很出,它就是在找到一个负载最轻的项.
找到合适的phase之后,将选择start_frame的基准,也就是frame变量,在当前调度帧的基础上后移十个帧.注释中说的很明白,这样就是为了有足够的时候让它分配内存.
最终关于start_frame的计算如下:
urb->start_frame = frame + ((qh->phase - frame) &
(qh->period - 1));
start_frame要满足两上条件:
1:start_frame在frame的后面.这点是没什么疑问的.因为我们希望这些ISO数据可以尽快被调度到.
2:start_frame必须要满足:start_frame = phase+K*period(k=0,1,2...).也就是说,start_frame必须要落在它的周期点上.
对应上面的计算就是为了找到frame后的第一个周期点.这里的计算方式很隐晦,下面详细分析一下这个算法.
1:对于phase=frame的情况.上面的计算是满足的.
2:对于phase>frame的情况.
如下图示:
首先,先提醒一下,phase之前,不能还有一个周期的长度,也就是说,phase是周期起点,这必须要满足,phase period的话,那周期起点就不是phase,面是phase-N*period(N=0,1,2...)了.
另外,还是注意这个period是2的n次方形式的,也就低n是为0的.
在这种情况一,(phase-frame)&( period-1)就是等于phase-frame. Frame+(phase-frame)&( period-1)=period.很显然是满足的.
3:对于phase
因为phase-frmae的值要小于0.这样给我们的计算造成了不便,所以不能用常规的方法去分析它.
A+(-A)=0
那A的后面,第一个为period的整数倍的值为
A+(-A)&(period-1)
为什么呢?我们假设period=1
如上图中分析的,A的两种情况:
1:一种是A的后M位为0.显然,A是period的倍数, A+(-A)&(period-1)还是等于A.显然是正确的
2:另一种A的后M位不为0, A+(-A)&(period-1)之后,低M为0,而第M+1有进位,显然结果就是A后面最小的period的倍数值.
假设,现在是从位置0开始计算的周期,那frame后的周期点就是frame+(-frame)&(period-1).现在周期是从phase 开始计算的,那,frmae后的周期点就要加上phase,即frame+(-frame)&(period-1)+phase.根据前面对于 phase的要求,有phase=(phase)&(period-1).那么就有了下面的式子:
frame+(-frame)&(period-1)+(phase)&(period-1)
因为(X+Y)&Z = X&Z+Y&Z
所以下面的式子就等价于:
Frame+(period-frame)&(period-1)
很显然,他们也是满足的
就这样,就确定了urb->start_frame的位置.
2:在没有指定URB_ISO_ASAP的情况下,phase和start_frame的计算
如果没有指定URB_ISO_ASAP,那start_frame就使用用户指定的start_frame.剩下的工作就是计算phase值了.这样的工作刚好跟第1种情况是相反的,在第1种情况里,是根据phase值来计算start_frame.
在这里,计算phase的式子如下:
qh->phase = urb->start_frame & (qh->period - 1);
很显然,在这里必须要满足:start_frame = phase+K*period(k=0,1,2...)只不过start_frmae是已知的,而phase是末知的.
很显然,只需要取start_frame的低K位就可以了.想一想,如果周期起点位置是从0开始的话,那start_frame肯定是低K位对齐的.所以start_frmae的低K位,也就是周期起点的偏移值,即phase.
3: uhci_insert_td_in_frame_list()函数
uhci_insert_td_in_frame_list就是将TD加到相应的frame里面去.它的代码如下:
static inline void uhci_insert_td_in_frame_list(struct uhci_hcd *uhci,
struct uhci_td *td, unsigned framenum)
{
framenum &= (UHCI_NUMFRAMES - 1);
td->frame = framenum;
/* Is there a TD already mapped there? */
if (uhci->frame_cpu[framenum]) {
struct uhci_td *ftd, *ltd;
ftd = uhci->frame_cpu[framenum];
ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list);
list_add_tail(&td->fl_list, &ftd->fl_list);
td->link = ltd->link;
wmb();
ltd->link = LINK_TO_TD(td);
} else {
td->link = uhci->frame[framenum];
wmb();
uhci->frame[framenum] = LINK_TO_TD(td);
uhci->frame_cpu[framenum] = td;
}
}
很显然,在第一次添加的时候, uhci->frame_cpu[framenum]是空的,也就是说流程会进入到else中.在else的操作中,将frame指向TD,再指 TD指向QH.然后将uhci->frame_cpu[framenum]指向这个TD.
很显然,当以后要往这个frame添加TD的时候,它是将TD加到这个TD的后面.那实际上, uhci->frame_cpu[framenum]就是表示,挂在frame[framenum]上的实时TD链表.
用图来表示上述操作过程,如下:
回忆一下之前讲UHCI调度初始化的时候,从UHCI spec中列出来的那副调度图,是不是很像了?*^_^*
到这里, uhci_submit_isochronous()已经全部分析完了.流程返回到uhci_urb_enqueue()中.像其它传输一样,流程会转入 uhci_activate_qh中.只不过,在uhci_activate_qh()中调用的子函数是link_iso().它的代码如下:
static inline void link_iso(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
list_add_tail(&qh->node, &uhci->skel_iso_qh->node);
/* Isochronous QHs aren't linked by the hardware */
}
很显然,就是将实时传输的QH加到了skel_iso_qh的链表上.
在ISO传输中,有一点要特别注意,ISO传输中并没有去创建一个用来表示结尾的TD(因为ISO传输是TD相联的,最后一个TD联QH,如果中间有无效TD,UHCI就不会处理后面的QH了),而其它类型的传输都会有一个表示QH结尾的TD.
到这里,四种传输类型全部都分析完了.下面来个小小的总结:
1:对于控制传输和批量传输,它的qh是加在skel_async_qh上的.另外,这两种传输还支持FSBR.对于FSBR,它会借助于skel_term_qh来构成一个循环
2:对于中断传输,它的qh是加在中断间隔对应的skelqh[]的链表上
3:对于实时传输,它的qh是加在skel_iso_qh上.
四:中断处理过程
在上面的传输分析中,都在在结尾的TD上加上一个IOC属性,这个属性位表示,如果该TD如在的frame处理完成之后,就会给CPU上报一个中断.而UHCI驱动可能根据这个中断来判断urb是否传输完成了.闲言少述,进入代码.
UHCI的中断处理程序为usb_hcd_irq().代码如下:
irqreturn_t usb_hcd_irq (int irq, void *__hcd)
{
struct usb_hcd *hcd = __hcd;
int start = hcd->state;
if (unlikely(start == HC_STATE_HALT ||
!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
return IRQ_NONE;
if (hcd->driver->irq (hcd) == IRQ_NONE)
return IRQ_NONE;
set_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
if (unlikely(hcd->state == HC_STATE_HALT))
usb_hc_died (hcd);
return IRQ_HANDLED;
}
从上面的代码可以看到,流程最终会转向driver->irq().对应的接口为uhci_irq().代码如下:
static irqreturn_t uhci_irq(struct usb_hcd *hcd)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned short status;
/*
* Read the interrupt status, and write it back to clear the
* interrupt cause. Contrary to the UHCI specification, the
* "HC Halted" status bit is persistent: it is RO, not R/WC.
*/
//STS寄存器
status = inw(uhci->io_addr + USBSTS);
//USBSTS_HCH:UHCI停止运行时,设置此位
//没有中断.
if (!(status & ~USBSTS_HCH)) /* shared interrupt, not mine */
return IRQ_NONE;
//R/WC.把值写回去,用来清除其中的erron 位
outw(status, uhci->io_addr + USBSTS); /* Clear it */
//根据不同的错误.打印出不出的错误提示信息
if (status & ~(USBSTS_USBINT | USBSTS_ERROR | USBSTS_RD)) {
if (status & USBSTS_HSE)
dev_err(uhci_dev(uhci), "host system error, "
"PCI problems?\n");
if (status & USBSTS_HCPE)
dev_err(uhci_dev(uhci), "host controller process "
"error, something bad happened!\n");
if (status & USBSTS_HCH) {
spin_lock(&uhci->lock);
if (uhci->rh_state >= UHCI_RH_RUNNING) {
dev_err(uhci_dev(uhci),
"host controller halted, "
"very bad!\n");
if (debug > 1 && errbuf) {
/* Print the schedule for debugging */
uhci_sprint_schedule(uhci,
errbuf, ERRBUF_LEN);
lprintk(errbuf);
}
uhci_hc_died(uhci);
/* Force a callback in case there are
* pending unlinks */
mod_timer(&hcd->rh_timer, jiffies);
}
spin_unlock(&uhci->lock);
}
}
//如果收到了Resume信号,则重新启用定时器轮询.
if (status & USBSTS_RD)
usb_hcd_poll_rh_status(hcd);
else {
spin_lock(&uhci->lock);
//扫描调度队列
uhci_scan_schedule(uhci);
spin_unlock(&uhci->lock);
}
return IRQ_HANDLED;
}
USBSTS寄存器全名叫USB STATUS REGISTER.即状态寄存器,这个寄存器里会反应出系统的状态和中断状态.另外,这个寄存器是R/WC类型的,也就是说往寄存器中某位写入1,会将其置为0.代码中也利用了这个特性来清除STS中的ERROR位.
还记得之前分析root hub的中断传输时候,分析到suspend_rh()函数曾说过,如果设备被挂起,会将端口状态轮询定时器停止的,但是开启了Resume中断,如果收 到了Resume信号,就会产生一个中断,中断处理函数就再次启用轮询定时器.这也就是对应上面代码的if(status & USBSTS_RD)部份.
(末完,待续...)