Linux那些事儿之我是UHCI(16)寂寞在唱歌

接下来就该是usb_hcd_poll_rh_status.这个函数在咱们整个故事将出现多次,甚至可以说在任何一个HCD的故事中都将出现多次.为了继续走下去,我们必须做一个伟大的假设.假设现在Root Hub上还没有连接任何设备,也就是说此时此刻,usb设备树上只有Root Hub形单影只.没有人来陪伴他,他只能静静的看青春难依难舍,只能听寂寞在唱歌,轻轻的,狠狠的,歌声是这么残忍让人忍不住泪流成河.

我们以此为上下文开始往下看.

usb_hcd_poll_rh_status来自drivers/usb/core/hcd.c:

533 /*

534 * Root Hub interrupt transfers are polled using a timer if the

535 * driver requests it; otherwise the driver is responsible for

536 * calling usb_hcd_poll_rh_status() when an event occurs.

537 *

538 * Completions are called in_interrupt(), but they may or may not

539 * be in_irq().

540 */

541 void usb_hcd_poll_rh_status(struct usb_hcd *hcd)

542 {

543 struct urb *urb;

544 int length;

545 unsigned long flags;

546 char buffer[4]; /* Any root hubs with > 31 ports? */

547

548 if (unlikely(!hcd->rh_registered))

549 return;

550 if (!hcd->uses_new_polling && !hcd->status_urb)

551 return;

552

553 length = hcd->driver->hub_status_data(hcd, buffer);

554 if (length > 0) {

555

556 /* try to complete the status urb */

557 local_irq_save (flags);

558 spin_lock(&hcd_root_hub_lock);

559 urb = hcd->status_urb;

560 if (urb) {

561 spin_lock(&urb->lock);

562 if (urb->status == -EINPROGRESS) {

563 hcd->poll_pending = 0;

564 hcd->status_urb = NULL;

565 urb->status = 0;

566 urb->hcpriv = NULL;

567 urb->actual_length = length;

568 memcpy(urb->transfer_buffer, buffer, length);

569 } else /* urb has been unlinked */

570 length = 0;

571 spin_unlock(&urb->lock);

572 } else

573 length = 0;

574 spin_unlock(&hcd_root_hub_lock);

575

576 /* local irqs are always blocked in completions */

577 if (length > 0)

578 usb_hcd_giveback_urb (hcd, urb);

579 else

580 hcd->poll_pending = 1;

581 local_irq_restore (flags);

582 }

583

584 /* The USB 2.0 spec says 256 ms. This is close enough and won't

585 * exceed that limit if HZ is 100. */

586 if (hcd->uses_new_polling ? hcd->poll_rh :

587 (length == 0 && hcd->status_urb != NULL))

588 mod_timer (&hcd->rh_timer, jiffies + msecs_to_jiffies(250));

589 }

这个函数天生是为了中断传输而活的.

前面两个if对咱们来说肯定是不满足的.rh_registered咱们刚刚才设置为1. uses_new_polling咱们也在uhci_start()中设置为了1.所以,咱们继续昂首挺胸的往前走.

553,driver->hub_status_data是每个driver自己定义的,对于uhci来说,咱们定义了uhci_hub_status_data这么一个函数,它来自drivers/usb/host/uhci-hub.c:

184 static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)

185 {

186 struct uhci_hcd *uhci = hcd_to_uhci(hcd);

187 unsigned long flags;

188 int status = 0;

189

190 spin_lock_irqsave(&uhci->lock, flags);

191

192 uhci_scan_schedule(uhci);

193 if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags) || uhci->dead)

194 goto done;

195 uhci_check_ports(uhci);

196

197 status = get_hub_status_data(uhci, buf);

198

199 switch (uhci->rh_state) {

200 case UHCI_RH_SUSPENDING:

201 case UHCI_RH_SUSPENDED:

202 /* if port change, ask to be resumed */

203 if (status)

204 usb_hcd_resume_root_hub(hcd);

205 break;

206

207 case UHCI_RH_AUTO_STOPPED:

208 /* if port change, auto start */

209 if (status)

210 wakeup_rh(uhci);

211 break;

212

213 case UHCI_RH_RUNNING:

214 /* are any devices attached? */

215 if (!any_ports_active(uhci)) {

216 uhci->rh_state = UHCI_RH_RUNNING_NODEVS;

217 uhci->auto_stop_time = jiffies + HZ;

218 }

219 break;

220

221 case UHCI_RH_RUNNING_NODEVS:

222 /* auto-stop if nothing connected for 1 second */

223 if (any_ports_active(uhci))

224 uhci->rh_state = UHCI_RH_RUNNING;

225 else if (time_after_eq(jiffies, uhci->auto_stop_time))

226 suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);

227 break;

228

229 default:

230 break;

231 }

232

233 done:

234 spin_unlock_irqrestore(&uhci->lock, flags);

235 return status;

236 }

坦白说,这个函数一下子就把咱们这个故事的技术含量给拉高了上去.尤其是那个uhci_scan_schedule,一下子就让故事变得复杂了起来,原本清晰的故事,渐渐变得模糊.

uhci_scan_schedule来自drivers/usb/host/uhci-q.c:

1705 /*

1706 * Process events in the schedule, but only in one thread at a time

1707 */

1708 static void uhci_scan_schedule(struct uhci_hcd *uhci)

1709 {

1710 int i;

1711 struct uhci_qh *qh;

1712

1713 /* Don't allow re-entrant calls */

1714 if (uhci->scan_in_progress) {

1715 uhci->need_rescan = 1;

1716 return;

1717 }

1718 uhci->scan_in_progress = 1;

1719 rescan:

1720 uhci->need_rescan = 0;

1721 uhci->fsbr_is_wanted = 0;

1722

1723 uhci_clear_next_interrupt(uhci);

1724 uhci_get_current_frame_number(uhci);

1725 uhci->cur_iso_frame = uhci->frame_number;

1726

1727 /* Go through all the QH queues and process the URBs in each one */

1728 for (i = 0; i < UHCI_NUM_SKELQH - 1; ++i) {

1729 uhci->next_qh = list_entry(uhci->skelqh[i]->node.next,

1730 struct uhci_qh, node);

1731 while ((qh = uhci->next_qh) != uhci->skelqh[i]) {

1732 uhci->next_qh = list_entry(qh->node.next,

1733 struct uhci_qh, node);

1734

1735 if (uhci_advance_check(uhci, qh)) {

1736 uhci_scan_qh(uhci, qh);

1737 if (qh->state == QH_STATE_ACTIVE) {

1738 uhci_urbp_wants_fsbr(uhci,

1739 list_entry(qh->queue.next, struct urb_priv, node));

1740 }

1741 }

1742 }

1743 }

1744

1745 uhci->last_iso_frame = uhci->cur_iso_frame;

1746 if (uhci->need_rescan)

1747 goto rescan;

1748 uhci->scan_in_progress = 0;

1749

1750 if (uhci->fsbr_is_on && !uhci->fsbr_is_wanted &&

1751 !uhci->fsbr_expiring) {

1752 uhci->fsbr_expiring = 1;

1753 mod_timer(&uhci->fsbr_timer, jiffies + FSBR_OFF_DELAY);

1754 }

1755

1756 if (list_empty(&uhci->skel_unlink_qh->node))

1757 uhci_clear_next_interrupt(uhci);

1758 else

1759 uhci_set_next_interrupt(uhci);

1760 }

真的没想到在这里冷不丁的杀出这么一个变态的函数来.这个函数的复杂性让我哭都哭不出来.我做梦也没想到这个函数竟然会牵出一打的函数来.

scan_in_progress初值为0,只有在这个函数中我们才会改变它的值.因为它本来就是被用来表征我们处于这个函数中,注释中也说了,我们使用这个变量的目的就是为了避免这个函数被嵌套调用.所以如果这里判断为0,1718行我们就立刻设置它为1.反之如果已经不为0,就设置need_rescan1,并且函数返回.

1720行和1721,设置need_rescanfsbr_is_wanted都为0.

1723,uhci_clear_next_interrupt()来自drivers/usb/host/uhci-q.c:

35 static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci)

36 {

37 uhci->term_td->status &= ~cpu_to_le32(TD_CTRL_IOC);

38 }

清中断.如果一个TD设置了TD_CTRL_IOC这个flag,就表示在该TD所在的Frame结束之后,USB主机控制器将向CPU发送一次中断.那么在这里我们实际上不希望term_td结束所在的frame发生任何中断,因为我们现在正在处理整个调度队列.

而接下来的另一个函数uhci_get_current_frame_number()则来自drivers/usb/host/uhci-hcd.c:

433 /*

434 * Store the current frame number in uhci->frame_number if the controller

435 * is runnning. Expand from 11 bits (of which we use only 10) to a

436 * full-sized integer.

437 *

438 * Like many other parts of the driver, this code relies on being polled

439 * more than once per second as long as the controller is running.

440 */

441 static void uhci_get_current_frame_number(struct uhci_hcd *uhci)

442 {

443 if (!uhci->is_stopped) {

444 unsigned delta;

445

446 delta = (inw(uhci->io_addr + USBFRNUM) - uhci->frame_number) &

447 (UHCI_NUMFRAMES - 1);

448 uhci->frame_number += delta;

449 }

450 }

正如注释里说的那样,把寄存器中的值更新给uhci->frame_number.

老实说我觉得眼前这些代码目的性不是很明确,让人看了不知所云,昏昏欲睡.不过不要紧,作为一个血气方刚的男青年,并不是只有伊莱克斯的史上最强女助理石靖MM的裸照才能让我兴奋,眼前这段代码就有足够的理由让我兴奋.不信你看1731行那段while循环,对于uhci_scan_schedule这个函数,我相信即便是每天守候在西直门外倒卖发票的那些抱着小孩的中年妇女们也能看出来,17281743行就是这个函数的最关键部分.如果你把这个函数中这一段for循环删除,那么就相当于裸照门事件几天之后某些人把石靖的裸照中的精华部分从网上删除掉.关于后者,其后果是不言自明的,人们千方百计在百度图片上搜索,每天近6万的搜索在找石靖,又有几个人还能看到石靖的裸照?那么关于前者,后果也是不言自明的,没有这段精彩的循环,这个函数就完全失去了意义,其存在就没有了价值.既然这个for循环是精华,那么我们看这个循环究竟会不会被执行?没错,之所以令我兴奋的是,至少此时此刻,这段循环是不做什么事情的,理由很简单,for循环内部的这个while循环条件并不满足.我们结合1729行和1731行来看,注意到这里判断的就是uhci->skelqh[]数组的每个成员的node队列.我们知道struct uhci_qh结构体有一个成员是node,它代表的是一支队伍,uhci_alloc_qh中我们事实上用INIT_LIST_HEAD宏初始化了这个队列,这个宏的所作所为大家也都清楚,就是初始化一个空队列,即一个节点的nextprev指针都指向自己.所以很显然,uhci->next_qh就等于uhci->skelqh[i].于是1731这个while循环就不会执行,因此,1728开始的这个for循环也就没有为国家做出任何贡献.或者至少说,现在还不是它做贡献的时刻.待到山花浪漫时,skelqh[]node队列有内容了,我们自然还会再次回来看这个函数.

飘过了这个for循环uhci_scan_schedule这个函数就没什么好看的了.1748行又把scan_in_progress设置为0.

1750行自然也不用说,fsbr_is_on默认也是0.所以暂时这里也不会执行.

至于1756行这段if-else,skel_unlink_qh实际上就是skelqh[0],同样,此时此刻它的node队列也是空的,list_empty是满足的,因此uhci_clear_next_interrupt会再次被调用.

结束了uhci_scan_schedule,我们继续回到uhci_hub_status_data中来.

193,HCD_FLAG_HW_ACCESSIBLE这个flag我们是设置过的,就在usb_add_hcd中设置的.uhci->dead咱们在finish_reset中设置为了0.

接下来一个函数, uhci_check_ports.来自drivers/usb/host/uhci-hub.c:

136 static void uhci_check_ports(struct uhci_hcd *uhci)

137 {

138 unsigned int port;

139 unsigned long port_addr;

140 int status;

141

142 for (port = 0; port < uhci->rh_numports; ++port) {

143 port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;

144 status = inw(port_addr);

145 if (unlikely(status & USBPORTSC_PR)) {

146 if (time_after_eq(jiffies, uhci->ports_timeout)) {

147 CLR_RH_PORTSTAT(USBPORTSC_PR);

148 udelay(10);

149

150 /* HP's server management chip requires

151 * a longer delay. */

152 if (to_pci_dev(uhci_dev(uhci))->vendor ==

153 PCI_VENDOR_ID_HP)

154 wait_for_HP(port_addr);

155

156 /* If the port was enabled before, turning

157 * reset on caused a port enable change.

158 * Turning reset off causes a port connect

159 * status change. Clear these changes. */

160 CLR_RH_PORTSTAT(USBPORTSC_CSC | USBPORTSC_PEC);

161 SET_RH_PORTSTAT(USBPORTSC_PE);

162 }

163 }

164 if (unlikely(status & USBPORTSC_RD)) {

165 if (!test_bit(port, &uhci->resuming_ports)) {

166

167 /* Port received a wakeup request */

168 set_bit(port, &uhci->resuming_ports);

169 uhci->ports_timeout = jiffies +

170 msecs_to_jiffies(20);

171

172 /* Make sure we see the port again

173 * after the resuming period is over. */

174 mod_timer(&uhci_to_hcd(uhci)->rh_timer,

175 uhci->ports_timeout);

176 } else if (time_after_eq(jiffies,

177 uhci->ports_timeout)) {

178 uhci_finish_suspend(uhci, port, port_addr);

179 }

180 }

181 }

182 }

这个函数倒是挺清晰的,遍历Root Hub的各个端口,读取它们对应的端口寄存器.和这个端口寄存器相关的宏又是一打,来自drivers/usb/host/uhci-hcd.h:

48 /* USB port status and control registers */

49 #define USBPORTSC1 16

50 #define USBPORTSC2 18

51 #define USBPORTSC_CCS 0x0001 /* Current Connect Status

52 * ("device present") */

53 #define USBPORTSC_CSC 0x0002 /* Connect Status Change */

54 #define USBPORTSC_PE 0x0004 /* Port Enable */

55 #define USBPORTSC_PEC 0x0008 /* Port Enable Change */

56 #define USBPORTSC_DPLUS 0x0010 /* D+ high (line status) */

57 #define USBPORTSC_DMINUS 0x0020 /* D- high (line status) */

58 #define USBPORTSC_RD 0x0040 /* Resume Detect */

59 #define USBPORTSC_RES1 0x0080 /* reserved, always 1 */

60 #define USBPORTSC_LSDA 0x0100 /* Low Speed Device Attached */

61 #define USBPORTSC_PR 0x0200 /* Port Reset */

62 /* OC and OCC from Intel 430TX and later (not UHCI 1.1d spec) */

63 #define USBPORTSC_OC 0x0400 /* Over Current condition */

64 #define USBPORTSC_OCC 0x0800 /* Over Current Change R/WC */

65 #define USBPORTSC_SUSP 0x1000 /* Suspend */

66 #define USBPORTSC_RES2 0x2000 /* reserved, write zeroes */

67 #define USBPORTSC_RES3 0x4000 /* reserved, write zeroes */

68 #define USBPORTSC_RES4 0x8000 /* reserved, write zeroes */

首先我们要看的是状态位USBPORTSC_PR,1表示此时此刻该端口正处于Reset的状态.

其次我们看状态位USBPORTSC_RD,这位为1表示端口探测到了Resume信号.

显然以上两种情况都不是我们需要考虑的,至少不是现在需要考虑的,什么时候需要考虑?大约在冬季.

于是下一个函数, get_hub_status_data,来自drivers/usb/host/uhci-hub.c:

55 static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)

56 {

57 int port;

58 int mask = RWC_BITS;

59

60 /* Some boards (both VIA and Intel apparently) report bogus

61 * overcurrent indications, causing massive log spam unless

62 * we completely ignore them. This doesn't seem to be a problem

63 * with the chipset so much as with the way it is connected on

64 * the motherboard; if the overcurrent input is left to float

65 * then it may constantly register false positives. */

66 if (ignore_oc)

67 mask &= ~USBPORTSC_OCC;

68

69 *buf = 0;

70 for (port = 0; port < uhci->rh_numports; ++port) {

71 if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & mask) ||

72 test_bit(port, &uhci->port_c_suspend))

73 *buf |= (1 << (port + 1));

74 }

75 return !!*buf;

76 }

这里RWC_BITS就是用来屏蔽端口寄存器中的RWCbits.这三个都是状态改变位.

这里的ignore_oc又是一个模块参数,uhci-hcdehci-hcd这两个模块都会使用这个参数.注释里说得很清楚为何要用这个,有些主板喜欢谎报军情,对于这种情况,咱们可以使用一个ignore_oc参数来忽略之.

接下来又是遍历端口,读取每个端口的寄存器,如果有戏,就把信息存在buf,直到这时我们才注意到usb_hcd_poll_rh_status函数中定义了一个char buffer[4],这个buffer被一次次的传递下来.buf一共32bits,这里凡是一个端口的寄存器里有东西(除了状态改变位以外),就在buf里设置相应的位为1.如果设置了port_c_suspend也需要这样,port_c_suspend到时候我们在电源管理部分会看到,现在当然没有被设置.

不过这个函数最酷的就是最后这句居然有两个感叹号.这保证返回值要么是0,要么是非0.

接下来判断uhci->rh_state,我们前面在start_rh中设置了它为UHCI_RH_RUNNING,所以这里就是执行any_ports_active.

这个any_ports_active也来自drivers/usb/host/uhci-hub.c:

39 /* A port that either is connected or has a changed-bit set will prevent

40 * us from AUTO_STOPPING.

41 */

42 static int any_ports_active(struct uhci_hcd *uhci)

43 {

44 int port;

45

46 for (port = 0; port < uhci->rh_numports; ++port) {

47 if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) &

48 (USBPORTSC_CCS | RWC_BITS)) ||

49 test_bit(port, &uhci->port_c_suspend))

50 return 1;

51 }

52 return 0;

53 }

这时候再次读端口寄存器.其中CCS表示端口连接有变化.我们假设现在没有变化.那么这里什么也不干,直接返回0.这样uhci_hub_status_data最终返回status,这个status正是get_hub_status_data的返回值,即那个要么是0要么是非0.

如果为0,那么很好办,直接凌波微步来到586,判断hcd->uses_new_polling,咱们在uhci_start中设置为了1.所以这里继续判断hcd->poll_rh,咱们在start_rh中也把它设置为了1.所以,这里mod_timer会被执行.这个函数就相当于恐怖分子埋藏一个定时,时间到了某件事情就会发生,咱们曾经在usb_create_hcd中初始化过hcd->rh_timer,并且为它绑定了函数rh_timer_func,所以不妨来看一下rh_timer_func,来自drivers/usb/core/hcd.c:

592 /* timer callback */

593 static void rh_timer_func (unsigned long _hcd)

594 {

595 usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);

596 }

原来就是调用usb_hcd_poll_rh_status.所以usb_hcd_poll_rh_status这个函数是一个被多次调用的函数,只不过多次之间是有个延时的,而咱们这里调用mod_timer设置的是250ms.而每次所做的就是去询问Root Hub的状态.实际上这就是poll的含义,轮询.

所以,假如,假如咱们永远不往Root Hub里面插入东西,那么Root Hub将永远孤独,他只能看到天黑得像不会再天亮了,他只能听到寂寞在唱歌,温柔的,疯狂的,悲伤越来越深刻怎样才能够让它停呢?

那么咱们这个故事基本上就结束了.我们能看到的是usb_hcd_poll_rh_status这个函数,每隔250ms这样被执行一遍,重复一遍又一遍,可是它忙忙碌碌却什么也不做,但即便如此也比我要好,要知道我的人生就像复印机,每天都在不停的重复,可问题是有时候还他妈的卡纸.

你可能感兴趣的:(linux)