天天看點

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

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

    429

    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_hcd和usb_hcd之間的轉換.至于你說hcd->hcd_priv是什麼?首先我們看到struct usb_hcd中有一個成員unsigned long hcd_priv[0],以前我天真的以為這表示數組的長度為1.直到有一天,在網際網路上,我遇到了大俠albcamus,經他指點,我才恍然大悟,原來這就是傳說中的零長度數組.除了感慨gcc的強大之外,更是感慨,讀十年國文,不如聊半年QQ啊!網絡真是個好東西!

你可以執行這個指令:

localhost:~ # info gcc "c ext" zero

你會知道什麼是gcc中所謂的零長度數組.這是gcc對C的擴充.在标準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_size和io_addr的指派都很好懂.

然後是決定這個Root Hub到底有幾個端口.端口号是從0開始的,UHCI的Root Hub最多不能超過8個端口,即port号不能超過7.這段代碼的含義注釋裡面說的很清楚,首先UHCI定義了這麼一類寄存器,叫做PORTSC寄存器,全稱就是PORT STATUS AND CONTROL REGISTER,端口狀态和控制寄存器,隻要有一個port就有這麼一個寄存器,而每個這類寄存器都是16個bits,即兩個bytes,是以從位址安排上來說,每一個端口占兩個bytes,而Spec規定Port 1的位址位于基址開始的第10h和11h,Port 2的位址向後順推,即位于基址開始的第12h和13h,再有更多就向後順推,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,如果大于,那麼說明出錯了,于是設定port為2,因為UHCI spec規定每個Root Hub最少有兩個port.

于是,uhci->rh_numports最後用來記錄Root Hub的端口數.

然後是520行,check_and_reset_hc(),這個函數特虛僞,看似超簡單其實暴複雜.定義于drivers/usb/host/uhci-hcd.c中.

    164

    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

     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        

    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

    129 static void finish_reset(struct uhci_hcd *uhci)

    130 {

    131         int port;

    132

    133         

    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;        

    147 }

這兩個函數我們一起來看.

pci_read_config_word這個函數的作用正如同它的字面一一一樣.讀寄存器,讀什麼寄存器?就是那張上墳圖呗.

在drivers/usb/host/quirks.c中有一打的關于這些宏的定義,

     20 #define UHCI_USBLEGSUP          0xc0           

     21 #define UHCI_USBCMD             0              

     22 #define UHCI_USBINTR            4              

     23 #define UHCI_USBLEGSUP_RWC      0x8f00         

     24 #define UHCI_USBLEGSUP_RO       0x5040         

     25 #define UHCI_USBCMD_RUN         0x0001         

     26 #define UHCI_USBCMD_HCRESET     0x0002         

     27 #define UHCI_USBCMD_EGSM        0x0008         

     28 #define UHCI_USBCMD_CONFIGURE   0x0040         

     29 #define UHCI_USBINTR_RESUME     0x0002         

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_RO為0x5040,即0101 0000 0100 0000,它用來标志着個LEGSUP寄存器的bit 6,bit 12,bit 14這三位為1,這幾位是隻讀的(bit 14是保留位).UHCI_USBLEGSUP_RWC為0x8f00,即1000 1111 0000 0000,即标志着LEGSUP寄存器的bit 8,bit 9,bit 10,bit 11,bit 15為1,這幾位的屬性是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中定義的寄存器.這個寄存器為00h和01h處.是以UHCI_USBCMD的值為0.

關于這個寄存器,需要考慮的是這麼幾位,首先是bit 0,這一位被稱作RS bit,即Run/Stop,當這一位被設定為1,表示HC開始處理執行排程,排程什麼?比如,傳說中的URB.顯然,現在時機還未成熟,是以這一位必須設定為0.咱們這裡代碼的意思是如果它為1,就執行reset.UHCI_USBCMD_RUN的值為0x0001,即表征bit 0.另兩個需要考慮的是bit 3和bit 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位址位于04h和05h.很顯然一開始我們得關中斷.關于這個寄存器的描述如下圖所示:

謝天謝地,bit 15到bit 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

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

     60 {

     61        

     64         pci_write_config_word(pdev, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);

     65

     66        

     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        

     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的解釋,這樣做的後果就是讓這幾位都清零.凡是RWC的bit其實都是在傳達某種事件的發生,而清零往往代表的是認可這件事情.

然後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.

繼續閱讀