Linux那些事儿之我是UHCI(8)主机控制器的初始化(二)

485,hcd_to_uhci,来自drivers/usb/host/uhci-hcd.h,

    429 /* Convert between a usb_hcd pointer and the corresponding uhci_hcd */

    430 static inline struct uhci_hcd *hcd_to_uhci(struct usb_hcd *hcd)

    431 {

    432         return (struct uhci_hcd *) (hcd->hcd_priv);

    433 }

    434 static inline struct usb_hcd *uhci_to_hcd(struct uhci_hcd *uhci)

    435 {

    436         return container_of((void *) uhci, struct usb_hcd, hcd_priv);

    437 }

很显然,这两个函数完成的就是uhci_hcdusb_hcd之间的转换.至于你说hcd->hcd_priv是什么?首先我们看到struct usb_hcd中有一个成员unsigned long hcd_priv[0],以前我天真的以为这表示数组的长度为1.直到有一天,在互联网上,我遇到了大侠albcamus,经他指点,我才恍然大悟,原来这就是传说中的零长度数组.除了感慨gcc的强大之外,更是感慨,读十年语文,不如聊半年QQ!网络真是个好东西!

你可以执行这个命令:

localhost:~ # info gcc "c ext" zero

你会知道什么是gcc中所谓的零长度数组.这是gccC的扩展.在标准C中我们定义数组时其长度至少为1,而我们在Linux内核中结构体的定义里却经常看到最后一个元素定义为这种零长度数组,它不占结构的空间,但它意味着这个结构体的长度是充满了变数的,即我们这里sizeof(hcd_priv)==0,其作用就相当于一个占位符,用我们大学校园里的话来说,就是我一向深恶痛绝的占座一族.然后当我们要申请空间的时候我们可以这样做,

hcd = kzalloc(sizeof(*hcd) + driver->hcd_priv_size, GFP_KERNEL);

实际上,这就是usb_create_hcd中的一行,只不过当时我们没讲,然而现在我知道,逃避不是办法,我们必须面对.driver->hcd_priv_size对我们来说,我们可以在uhci_driver中找到,它就是sizeof(struct uhci_hcd),所以最终hcd->hcd_priv代表的就是这个struct uhci_hcd,只不过需要一个强制转换.这样我们就能理解hcd_to_uhci了吧.当然,反过来,uhci_to_hcd的意思就更不用说了.不过我倒是想友情提醒一下,我们现在是在讲uhci主机控制器的驱动程序,那么在我们这个故事里,有一些结构体变量是唯一的,比如以后我们凡是见到那个struct uhci_hcd的结构体指针,那就是咱们这里这个uhci,凡是见到那个struct usb_hcd的结构体指针,那就是咱们这里这个hcd.即以后我们如果见到某个指针名字叫做uhci或者叫做hcd,那就不用再多解释了.uhci即彼uhci,hcd即彼hcd.

接下来, io_sizeio_addr的赋值都很好懂.

然后是决定这个Root Hub到底有几个端口.端口号是从0开始的,UHCIRoot Hub最多不能超过8个端口,port号不能超过7.这段代码的含义注释里面说的很清楚,首先UHCI定义了这么一类寄存器,叫做PORTSC寄存器,全称就是PORT STATUS AND CONTROL REGISTER,端口状态和控制寄存器,只要有一个port就有这么一个寄存器,而每个这类寄存器都是16bits,即两个bytes,因此从地址安排上来说,每一个端口占两个bytes,Spec规定Port 1的地址位于基址开始的第10h11h,Port 2的地址向后顺推,即位于基址开始的第12h13h,再有更多就向后顺推,USBPORTSC1这个宏的值是16,还有一个叫做USBPORTSC2的宏值为18,这两个宏用来标志Port的偏移量,显然16就是10h,18就是12h,UHCI定义的寄存器中,PORTSC是最后一类寄存器,它后面没有更多的寄存器了,但是它究竟有几个PORTSC寄存器就是我们所不知道的了,否则我们也就不用判断有多少个端口了.于是这段代码的意思就是从USBPORTSC1开始往后走,一直循环下去,读取这个寄存器的值,portstatus,SPEC规定,这个寄存器的值中bit7是一个保留位,应该一直为1,所以如果不为1,那么就不用往下走了,说明已经没有寄存器了.另一种常见的错误是读出来都为1,经验表明,这种情况也表示没有寄存器了.说明一下,inw就是读IO端口的函数,w就表示按word.很显然这里要按word,因为PORTSC寄存器是16bits,一个word.inw所接的参数就是具体的IO地址,即基址加偏移量.

510,UHCI_RH_MAXCHILD就是7,port不能大于7,如果大于,那么说明出错了,于是设置port2,因为UHCI spec规定每个Root Hub最少有两个port.

于是,uhci->rh_numports最后用来记录Root Hub的端口数.

然后是520,check_and_reset_hc(),这个函数特虚伪,看似超简单其实暴复杂.定义于drivers/usb/host/uhci-hcd.c.

    164 /*

    165  * Initialize a controller that was newly discovered or has lost power

    166  * or otherwise been reset while it was suspended.  In none of these cases

    167  * can we be sure of its previous state.

    168  */

    169 static void check_and_reset_hc(struct uhci_hcd *uhci)

    170 {

    171         if (uhci_check_and_reset_hc(to_pci_dev(uhci_dev(uhci)), uhci->io_addr))

    172                 finish_reset(uhci);

    173 }

看上去就两行,可这两行足以让我等菜鸟们看半个小时了.首先第一个函数,uhci_check_and_reset_hc来自drivers/usb/host/pci-quirks.c,

     85 /*

     86  * Initialize a controller that was newly discovered or has just been

     87  * resumed.  In either case we can't be sure of its previous state.

     88  *

     89  * Returns: 1 if the controller was reset, 0 otherwise.

     90  */

     91 int uhci_check_and_reset_hc(struct pci_dev *pdev, unsigned long base)

     92 {

     93         u16 legsup;

     94         unsigned int cmd, intr;

     95

     96         /*

     97          * When restarting a suspended controller, we expect all the

     98          * settings to be the same as we left them:

     99          *

    100          *      PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;

    101          *      Controller is stopped and configured with EGSM set;

    102          *      No interrupts enabled except possibly Resume Detect.

    103          *

    104          * If any of these conditions are violated we do a complete reset.

    105          */

    106         pci_read_config_word(pdev, UHCI_USBLEGSUP, &legsup);

    107         if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) {

    108                 dev_dbg(&pdev->dev, "%s: legsup = 0x%04x/n",

    109                                 __FUNCTION__, legsup);

    110                 goto reset_needed;

    111         }

    112

    113         cmd = inw(base + UHCI_USBCMD);

    114         if ((cmd & UHCI_USBCMD_RUN) || !(cmd & UHCI_USBCMD_CONFIGURE) ||

    115                         !(cmd & UHCI_USBCMD_EGSM)) {

    116                 dev_dbg(&pdev->dev, "%s: cmd = 0x%04x/n",

    117                                 __FUNCTION__, cmd);

    118                 goto reset_needed;

    119         }

    120

    121         intr = inw(base + UHCI_USBINTR);

    122         if (intr & (~UHCI_USBINTR_RESUME)) {

    123                 dev_dbg(&pdev->dev, "%s: intr = 0x%04x/n",

    124                                 __FUNCTION__, intr);

    125                 goto reset_needed;

126         }

    127         return 0;

    128

    129 reset_needed:

    130         dev_dbg(&pdev->dev, "Performing full reset/n");

    131         uhci_reset_hc(pdev, base);

    132         return 1;

    133 }

而第二个函数finish_reset来自drivers/usb/host/uhci-hcd.c.

    126 /*

    127  * Finish up a host controller reset and update the recorded state.

    128  */

    129 static void finish_reset(struct uhci_hcd *uhci)

    130 {

    131         int port;

    132

    133         /* HCRESET doesn't affect the Suspend, Reset, and Resume Detect

    134          * bits in the port status and control registers.

    135          * We have to clear them by hand.

    136          */

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

    138                 outw(0, uhci->io_addr + USBPORTSC1 + (port * 2));

    139

    140         uhci->port_c_suspend = uhci->resuming_ports = 0;

    141         uhci->rh_state = UHCI_RH_RESET;

    142         uhci->is_stopped = UHCI_IS_STOPPED;

    143         uhci_to_hcd(uhci)->state = HC_STATE_HALT;

    144         uhci_to_hcd(uhci)->poll_rh = 0;

    145

    146         uhci->dead = 0;         /* Full reset resurrects the controller */

    147 }

这两个函数我们一起来看.

pci_read_config_word这个函数的作用正如同它的字面一一一样.读寄存器,读什么寄存器?就是那张上坟图呗.

drivers/usb/host/quirks.c中有一打的关于这些宏的定义,

     20 #define UHCI_USBLEGSUP          0xc0            /* legacy support */

     21 #define UHCI_USBCMD             0               /* command register */

     22 #define UHCI_USBINTR            4               /* interrupt register */

     23 #define UHCI_USBLEGSUP_RWC      0x8f00          /* the R/WC bits */

     24 #define UHCI_USBLEGSUP_RO       0x5040          /* R/O and reserved bits */

     25 #define UHCI_USBCMD_RUN         0x0001          /* RUN/STOP bit */

     26 #define UHCI_USBCMD_HCRESET     0x0002          /* Host Controller reset */

     27 #define UHCI_USBCMD_EGSM        0x0008          /* Global Suspend Mode */

     28 #define UHCI_USBCMD_CONFIGURE   0x0040          /* Config Flag */

     29 #define UHCI_USBINTR_RESUME     0x0002          /* Resume interrupt enable */

UHCI_USBLEGSUP是一个寄存器,它是一个比较特殊的寄存器,LEGSUP全称为LEGACY SUPPORT REGISTER,UHCI spec的第五章第二节专门介绍了这个寄存器.这里pci_read_config_word这个函数这么一调用就是把这个寄存器的值读出来,而结果被保存在变量legsup,接着下一行就来判断它的值.这里首先我们介绍三个概念,我们注意到寄存器的每一位都有一个属性,比如RO,RW,RWC,前两个很好理解,RO就是Read Only,只读,RW就是Read/Write,可读可写.RWC就是Read/Write Clear.寄存器的某位如果是RWC的属性,那么表示该位可以被读可以被写,然而,RW不同的是,如果你写了一个1到该位,将会把该位清为0,倘若你写的是一个0,则什么也不会发生,对这个世界不产生任何改变.(UHCI Spec中是这么说的:R/WC Read/Write Clear. A register bit with this attribute can be read and written. However, a write of a 1 clears (sets to 0) the corresponding bit and a write of a 0 has no effect.)

UHCI_USBLEGSUP_RO0x5040,0101 0000 0100 0000,它用来标志着个LEGSUP寄存器的bit 6,bit 12,bit 14这三位为1,这几位是只读的(bit 14是保留位).UHCI_USBLEGSUP_RWC0x8f00,1000 1111 0000 0000,即标志着LEGSUP寄存器的bit 8,bit 9,bit 10,bit 11,bit 151,这几位的属性是RWC.这里让这两个宏或一下,然后按位去反,然后让legsup和它们相与,其效果就是判断LEGSUP寄存器的bit 0,bit 1,bit 2,bit 3,bit 4,bit 5,bit 7,bit 13是否为1,这几位其实就是RW.这里的注释说,这几位任何一位为1则表示需要reset,其实这种注释是不太负责任的,仔细看一下UHCI spec我们会发现,RW的这些位并不是因为它们是RW位它们就应该被reset,而是因为它们的作用,实际上bit0~bit5,bit7,bit13这几位都是一些enable/disable的开关,特别是中断相关的使能位,当我们还没有准备就绪的时候,我们理应把它们关掉,这就相当于我新买了一个手机,而我还没有号码,那我出于省电的考虑,基本上会选择把手机先关掉,等到我有了号了,我才会去把手机打开.而其它的位都是一些状态位,它们为0还是为1只是表明不同的状态,那还不随它们去,它们爱表示什么状态就表示什么状态呗,状态位对我们是没有什么影响的.

113, UHCI_USBCMD表征UHCI的命令寄存器,这也是UHCI Spec中定义的寄存器.这个寄存器为00h01h.所以UHCI_USBCMD的值为0.

关于这个寄存器,需要考虑的是这么几位,首先是bit 0,这一位被称作RS bit,Run/Stop,当这一位被设置为1,表示HC开始处理执行调度,调度什么?比如,传说中的URB.显然,现在时机还未成熟,所以这一位必须设置为0.咱们这里代码的意思是如果它为1,就执行reset.UHCI_USBCMD_RUN的值为0x0001,即表征bit 0.另两个需要考虑的是bit 3bit 6.bit 3被称为EGSM,Enter Global Suspend Mode,这一位为1表示HC进入Global Suspend Mode,这期间是不会有USB交易的.把这一位设置为0则是跳出这个模式,显然咱们这里的判断是如果这一位被设置为了0,就执行reset,否则就没有必要.因为reset的目的是为了清除当前存在于总线上的任何交易,让主机控制器和设备都忘了过去,重新开始新的生活.bit 被称为CF,Configure Flag,设置了这一位表示主机控制器当前正在被配置的过程中,显然如果主机控制器还在这个阶段我们就没有必要reset.从逻辑上来说,关于这个寄存器的判断,我们的理念是,如果HC是停止的,并且它还是挂起的,并且它还是配置的.这种情况我们没有必要做reset.

接下来,121,读另一个寄存器,UHCI_USBINTR,值为4.UHCI spec中定义了一个中断使能寄存器.I/O地址位于04h05h.很显然一开始我们得关中断.关于这个寄存器的描述如下图所示:

谢天谢地,bit 15bit 4是保留位,并且默认应该是0,所以我们无需理睬.而剩下几位在现阶段应该要关掉,disable,或者说应该设置为0,唯有bit 1是个例外,Resume Interrupt Enable,正如uhci_check_and_reset_hc函数前的注释里说的一样,调用这个函数有两种可能的上下文,一种是这个主机控制器刚刚被发现的时候,这是一次性的工作,另一种是电源管理中的resume的时候,虽然此时此刻我们调用这个函数是处于第一种上下文,但显然第二种情景发生的频率更高,可能性更大.这也应了那句关于女人的老话,女人就像书架上的书,虽然你买了她,但在你买之前她多多少少被几个男人翻过.而对于resume的情况,显然这个Resume中断使能的寄存器必须被enable.(这里也能解释为何刚才我们不仅不应该清掉LEGSUP寄存器里面的状态位,还应该尽量保持它们.因为我们如果是从suspend回到resume,我们当然希望之前的状态得到保留,否则状态改变了那不就乱了么?那也就不叫恢复了.)

最终,127,如果前面的三条goto语句都没有执行,那么说明并不需要执行reset,这里就直接返回了,返回值为0.反之如果前面任何一条goto语句执行了,那么就往下走,执行uhci_reset_hc,然后返回1.

函数uhci_reset_hc也来自drivers/usb/host/pci-quirks.c:

     55 /*

     56  * Make sure the controller is completely inactive, unable to

     57  * generate interrupts or do DMA.

     58  */

     59 void uhci_reset_hc(struct pci_dev *pdev, unsigned long base)

     60 {

     61         /* Turn off PIRQ enable and SMI enable.  (This also turns off the

     62          * BIOS's USB Legacy Support.)  Turn off all the R/WC bits too.

     63          */

     64         pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);

     65

     66         /* Reset the HC - this will force us to get a

     67          * new notification of any already connected

     68          * ports due to the virtual disconnect that it

     69          * implies.

     70          */

     71         outw(UHCI_USBCMD_HCRESET, base + UHCI_USBCMD);

     72         mb();

     73         udelay(5);

     74         if (inw(base + UHCI_USBCMD) & UHCI_USBCMD_HCRESET)

     75                 dev_warn(&pdev->dev, "HCRESET not completed yet!/n");

     76

     77         /* Just to be safe, disable interrupt requests and

     78          * make sure the controller is stopped.

     79          */

     80         outw(0, base + UHCI_USBINTR);

     81         outw(0, base + UHCI_USBCMD);

     82 }

这个函数其实就是一堆寄存器操作.读寄存器或者写寄存器.这种代码完全就是纸老虎,看上去挺恐怖,一堆的宏啊,寄存器啊,其实这些东西对我们这种经历过应试教育的人来说完全就是小case.

首先64, pci_write_config_word就是写寄存器,写的还是UHCI_USBLEGSUP这个寄存器,LEGSUP寄存器,写入UHCI_USBLEGSUP_RWC,根据我们对RWC的解释,这样做的后果就是让这几位都清零.凡是RWCbit其实都是在传达某种事件的发生,而清零往往代表的是认可这件事情.

然后71,outw的作用就是写端口地址,这里写的是UHCI_USBCMD,即写命令寄存器,UHCI_USBCMD_HCRESET表示什么意思呢?UHCI spec中是这样说的:

Host Controller Reset (HCRESET). When this bit is set, the Host Controller module resets its internal timers, counters, state machines, etc. to their initial value. Any transaction currently in progress on USB is immediately terminated. This bit is reset by the Host Controller when the reset process is complete.

显然,这就是真正的执行硬件上的reset.把计时器,计数器,状态机全都给复位.当然这件事情是需要一段时间的,所以这里调用udelay来延时5ms.最后再读这一位,因为正如上面这段英文里所说的那样,reset完成了之后,这一位会被硬件reset,即这一位应该为0.

最后80行和81,写寄存器,0写入中断使能寄存器和命令寄存器,这就是彻底的Reset,关掉中断请求,并且停止HC.

终于,我们结束了uhci_check_and_reset_hc,如果执行了reset,那么返回值应该为1.这种情况我们将执行finish_reset函数.这个函数的代码前面已经贴出来了,小学六年级的同学也应该能看懂,因为它只是做一些简单的赋值.唯一有一行写寄存器的循环操作,其含义又在注释里写的很明了了.至于这些赋值究竟意味着什么,等我们遇到了再说.

于是我们又跳出了check_and_reset_hc(uhci),这次我们回到了uhci_init,不过幸运的是,这个函数也该结束了.返回值为0,我们于是来了个三级跳,回到了usb_add_hcd.

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值