天天看點

Linux那些事兒之我是UHCI(4)IO記憶體和IO端口

usb_bus_init來自drivers/usb/core/hcd.c,很顯然,它就是初始化struct usb_bus結構體指針.而這個結構體變量hcd->self的記憶體已經在剛才為hcd申請記憶體的時候一并申請了.

    688

    695 static void usb_bus_init (struct usb_bus *bus)

    696 {

    697         memset (&bus->devmap, 0, sizeof(struct usb_devmap));

    698

    699         bus->devnum_next = 1;

    700

    701         bus->root_hub = NULL;

    702         bus->busnum = -1;

    703         bus->bandwidth_allocated = 0;

    704         bus->bandwidth_int_reqs  = 0;

    705         bus->bandwidth_isoc_reqs = 0;

    706

    707         INIT_LIST_HEAD (&bus->bus_list);

708 }

我相信你早已忘記了,當初在hub驅動中我就講過,devnum_next在總線初始化的時候會被設為1,說的就是這裡.現在證明當初我沒有忽悠你吧.這裡其它的指派就不多說了,用到的時候我會告訴你的,相信我,這是同志的信任.

回到usb_create_hcd中來,又是幾行指派,飄過.

倒是1511行引起了我的注意,又是可惡的時間機制,init_timer,這個函數我們也見過多次了,usb-storage裡見過,hub裡見過,斑駁的陌生終于在時間的撫摸下變成了今日的熟悉.這裡我們設定的函數是rh_timer_func,而傳遞給這個函數的參數是hcd.這個函數具體做什麼我們走着瞧,不過你放心,咱們這個故事裡會多次接觸到這個timer,想逃是逃不掉的,躲得過初一躲不過十五.

1515行,INIT_WORK咱們也在hub驅動裡見過了,這裡這個hcd_resume_work什麼時候會被調用咱們也到時候再看.

剩下兩行指派,1518行沒啥好說的,struct usb_hcd有一個struct hc_driver的結構體指針成員,是以就這樣把它和咱們這個uhci_driver給聯系起來了.而在uhci_driver中我們看到,其中有一個product_desc被指派為"UHCI Host Controller",是以這裡也賦給hcd->product_desc,因為struct hc_driver和struct usb_hcd這兩個結構體中都有一個成員const char *product_desc,你說這不浪費嗎?就一個破字元串,還得儲存在兩個地方,這些家夥九年制義務教育怎麼學的?長此以往,國将不國矣!

至此,usb_create_hcd結束了,傳回了這個申請好賦好值的hcd.我們繼續回到probe函數中來.

89到124這個if-else的确讓我開心了一把,因為if裡說的是EHCI和OHCI的情況,else裡針對的才是UHCI,鑒于EHCI将由某人來寫,而OHCI和UHCI性質一樣,我們不會講,是以這就意味着這個if-else我隻要從105行開始看,即直接看UHCI那部分的代碼.爽!

不過我們也得知道這裡為何要判斷HCD_MEMORY,這個宏的意思是表明該HC的寄存器是使用memory的,而沒有設定這個flag的HC的寄存器是使用I/O的.這些寄存器俗稱I/O端口,或者說I/O ports,這個I/O端口可以被映射在Memory Space,也可以被映射在I/O Space.UHCI是屬于後者,而EHCI/OHCI屬于前者.

這裡看上去必須多說幾句,否則很難說清楚.以我們家Intel為代表的i386系列處理器中,記憶體和外部IO是獨立編址獨立尋址的,于是有一個位址空間叫做記憶體空間,另有一個位址空間叫做I/O空間.也就是說,從處理器的角度來說,i386提供了一些單獨的指令用來通路I/O空間.換言之,通路I/O空間和通路普通的記憶體得使用不同的指令.而在一些玩嵌入式的處理器中,比如PowerPC,他們家就隻使用一個空間,那就是記憶體空間,那像這種情況,外設的I/O端口的實體位址就被映射到記憶體位址空間中,這就是傳說中的Memory-mapped,記憶體映射.而我們家那種情況,外設的I/O端口的實體位址就被映射到I/O位址空間中,這就是傳說中的I/O-mapped,即I/O映射.

那麼EHCI/OHCI呢,它們除了有寄存器以外,還有記憶體,而它們把這些統統映射到Memory Space中去,而UHCI隻使用寄存器來通信,是以它隻需要映射寄存器,即I/O端口,而它的spec規定,它是映射到I/O空間.Linux中I/O Memory和I/O ports都被視作一種資源,它們分别被記錄在/proc/iomem和/proc/ioports中.

是以我們可以在這裡看到uhci-hcd,

localhost:~ # cat /proc/ioports

0000-001f : dma1

0020-0021 : pic1

(此處省略若幹行)

bca0-bcbf : 0000:00:1d.2

  bca0-bcbf : uhci_hcd

bcc0-bcdf : 0000:00:1d.1

  bcc0-bcdf : uhci_hcd

bce0-bcff : 0000:00:1d.0

  bce0-bcff : uhci_hcd

c000-cfff : PCI Bus #10

  cc00-ccff : 0000:10:0d.0

d000-dfff : PCI Bus #0e

  dcc0-dcdf : 0000:0e:00.1

    dcc0-dcdf : e1000

  dce0-dcff : 0000:0e:00.0

    dce0-dcff : e1000

e000-efff : PCI Bus #0c

  e800-e8ff : 0000:0c:00.1

    e800-e8ff : qla2xxx

  ec00-ecff : 0000:0c:00.0

    ec00-ecff : qla2xxx

fc00-fc0f : 0000:00:1f.1

  fc00-fc07 : ide0

而在這裡看到ehci-hcd,

localhost:~ # cat /proc/iomem

00000000-0009ffff : System RAM

  00000000-00000000 : Crash kernel

(此處省略若幹行)

d8000000-d80fffff : PCI Bus #01

  d8000000-d80fffff : PCI Bus #02

    d80f0000-d80fffff : 0000:02:0e.0

      d80f0000-d80fffff : megasas: LSI Logic

d8100000-d81fffff : PCI Bus #0c

  d8100000-d813ffff : 0000:0c:00.1

e0000000-efffffff : reserved

f2000000-f7ffffff : PCI Bus #06

  f4000000-f7ffffff : PCI Bus #07

    f4000000-f7ffffff : PCI Bus #08

      f4000000-f7ffffff : PCI Bus #09

        f4000000-f5ffffff : 0000:09:00.0

          f4000000-f5ffffff : bnx2

f8000000-fbffffff : PCI Bus #04

  f8000000-fbffffff : PCI Bus #05

    f8000000-f9ffffff : 0000:05:00.0

      f8000000-f9ffffff : bnx2

(此處省略若幹行)

fca00400-fca007ff : 0000:00:1d.7

  fca00400-fca007ff : ehci_hcd

fe000000-ffffffff : reserved

100000000-22fffffff : System RAM

要使用I/O記憶體首先要申請,然後要映射,而要使用I/O端口首先要申請,或者叫請求,對于I/O端口的請求意思是讓核心知道你要通路這個端口,這樣核心知道了以後它就不會再讓别人也通路這個端口了.畢竟這個世界僧多粥少啊.申請I/O端口的函數是request_region,這個函數來自include/linux/ioport.h,

    116

    117 #define request_region(start,n,name)    __request_region(&ioport_resource, (start), (n), (name))

    118 #define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))

    119 #define rename_region(region, newname) do { (region)->name = (newname); } while (0)

    120

    121 extern struct resource * __request_region(struct resource *,

    122                                         resource_size_t start,

    123                                         resource_size_t n, const char *name);

這裡我們看到的那個request_mem_region是申請I/O記憶體用的.申請了之後,還需要使用ioremap或者ioremap_nocache函數來映射.

對于request_region,三個參數start,n,name表示你想使用從start開始的size為n的I/O port資源,name自然就是你的名字了.這三個概念在我們剛才貼出來的cat /proc/ioports裡面顯示的很清楚,name就是uhci-hcd.

那麼對于uhci-hcd,我們究竟需要請求哪些位址,需要多少空間呢?嗯,又要提到那張上墳圖了.PCI裝置本身有一堆的位址空間,記憶體空間和IO空間.那麼如何把這些空間映射到總線上來呢?用什麼?寄存器.看上墳圖中的那幾個Base Address 0,1,2,3,4,5,即每個裝置都有6個位址空間,這叫做六個基址寄存器,有的裝置還有一個ROM,是以又有一個Expansion ROM Base Address,它對應第七個區間,或者說區間6,而在上墳圖上對應的就叫做擴充ROM基址寄存器.每個寄存器都是四個位元組.而我們在include/linux/pci.h中定義了,

    227

    234

    235 #define PCI_ROM_RESOURCE        6

是以在我們的代碼中我們看到循環條件就是從0到PCI_ROM_RESOURCE之前,即循環六次,因為有六個區間,區間也叫region.那麼這些寄存器究竟取的什麼值呢?這就是在PCI總線初始化的時候做的事情了,它會把你每個基址寄存器賦上值,而實際上就是映射于總線上的位址,總線驅動的作用就是讓各個裝置都需要的位址資源都得到滿足,并且沒有裝置與裝置之間的位址發生沖突.PCI總線驅動做了這些之後,我們PCI裝置驅動就簡單了,在需要使用的時候直接請求即可,正如這裡的request_region.那麼我們傳遞給request_region的具體參數是什麼呢?

兩個函數,pci_resource_start和pci_resource_len,就是去獲得一個區間的起始位址和長度,是以我們就很好了解這段代碼了.至于109行這個if判斷,pci_resource_flags是用來判斷一個資源是哪種類型的,include/linux/ioport.h中一共定義了四種資源:

     36 #define IORESOURCE_IO           0x00000100     

     37 #define IORESOURCE_MEM          0x00000200

     38 #define IORESOURCE_IRQ          0x00000400

     39 #define IORESOURCE_DMA          0x00000800

它們是IO,Memory,中斷,DMA.對應我們在/proc下看到的ioports,iomem,interrupt,dma四個檔案.是以這裡的意思就是判斷說如果不是IO Port資源,那麼就不予理睬.因為UHCI主機控制器隻需要理财I/O Port.

request_region函數如果成功将傳回非NULL,失敗了才傳回NULL.是以代碼的意思就是一旦成功就跳出循環.反之,如果循環都結束了還未能請求到,那就說明出錯了.那麼你說為何一旦成功就跳出循環?老實說,這個問題足足困擾了我13秒鐘,别小看13秒鐘,有這麼長時間劉翔都已經完成一次110米跨欄了.讓spec來告訴你.

看到沒有,20-23h,四個位元組,這裡正好對應UHCI的IO空間基址寄存器.換言之,UHCI就定義了一個基址寄存器,是以我們隻需要使用一個基址寄存器就可以映射我們需要的位址了.是以,成功一次我們就可以結束循環了.

又一次,我們回到了usb_hcd_pci_probe中,126行,pci_set_master函數.還是看那張上墳圖,注意到第三個寄存器,叫做Command Reg.,其實就是指令寄存器.讓我們用PCI spec來告訴你這個指令寄存器的格局:

看到其中有一位叫做Bus Master了麼?沒錯,就是那個Bit 2.用毛德操先生的話說就是PCI裝置要進行DMA操作就得具有競争成為總線主的能力.而這個Bus Master位就是用來打開或關閉PCI裝置競争成為總線主的能力的.在完成PCI總線的初始化時,所有PCI裝置的DMA功能都是關閉的,是以這裡要調用pci_set_master啟用USB主機控制器競争成為總線主的能力.就是說PCI裝置有沒有這麼一種能力是可以設定的.

USB spec 2.0中10.2.9節在講到USB Host Interface的時候,說了USB HC是應該具備這種能力的:

The Host Controller provides a high-speed bus-mastering interface to and from main system memory. The physical transfer between memory and the USB wire is performed automatically by the Host Controller.

同時在UHCI spec裡面我們也能找到這麼一句話,For the implementation example in this document, the Host Controller is a PCI device. PCI Bus master capability in the Host Controller permits high performance data transfers to system memory.

是以我們需要啟用這種能力.

不過你要問了,究竟什麼是PCI的Bus Master?從我們讀電子的人的角度來看,連接配接到PCI總線上的裝置有兩種,即主要裝置和目标裝置.或者說一個是master裝置,一個是target-only裝置,用我們專業的術語來看,這兩者最直接的差別就是target-only最少需要47根pin,而master最少需要49根pin,就是說它們所必須支援的總線信号就是不一樣的.PCI裝置如果是以一個target-only的方式工作,那麼它就完全是在主機的CPU的控制之下工作,比如裝置接收到某一個外部事件,然後中斷主機,然後主機CPU讀寫裝置,這樣裝置就可以工作了.這樣的裝置也被一小撮人稱為slave裝置,或者說從裝置.這就是典型的奴才型的裝置,主說什麼就是什麼,完全沒有自己的見解.而master類型的裝置就比這個要複雜了,master裝置能夠不在主機CPU的幹預下通路主機的位址空間,包括主存和其它PCI裝置,很顯然,DMA就屬于這種情況,即不需要主機CPU幹涉的情況下USB主機控制器通過DMA直接讀寫記憶體.是以我們需要打開這種能力.不過PCI總線在同一時刻隻能供一對裝置完成傳輸.至于有了競争力能不能競争得到Master,那就得看人品了.上天給了你一幅天使的面孔和魔鬼的身材,但你能不能成為明星就得看造化了,當然隻要你遵守圈中的潛規則,你離成功就不遠了. 

繼續閱讀