• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            milkyway的窩

            最初想法的誕生地

             

            [轉]linux下I/O資源的實現,管理和操作

            幾乎每一種外設都是通過讀寫設備上的寄存器來進行的。外設寄存器也稱為“I/O端口”,通常包括:控制寄存器、狀態寄存器和數據寄存器三大類,而且一個外設的寄存器通常被連續地編址。CPU對外設IO端口物理地址的編址方式有兩種:一種是I/O映射方式(I/O-mapped),另一種是內存映射方式(Memory-mapped)。而具體采用哪一種則取決于CPU的體系結構。

              有些體系結構的CPU(如,PowerPC、m68k等)通常只實現一個物理地址空間(RAM)。在這種情況下,外設I/O端口的物理地址就被映射到CPU的單一物理地址空間中,而成為內存的一部分。此時,CPU可以象訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的外設I/O指令。這就是所謂的“內存映射方式”(Memory-mapped)。

              而另外一些體系結構的CPU(典型地如X86)則為外設專門實現了一個單獨地地址空間,稱為“I/O地址空間”或者“I/O端口空間”。這是一個與CPU地RAM物理地址空間不同的地址空間,所有外設的I/O端口均在這一空間中進行編址。CPU通過設立專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元(也即I/O端口)。這就是所謂的“I/O映射方式”(I/O-mapped)。與RAM物理地址空間相比,I/O地址空間通常都比較小,如x86 CPU的I/O空間就只有64KB(0-0xffff)。這是“I/O映射方式”的一個主要缺點。

              Linux將基于I/O映射方式的或內存映射方式的I/O端口通稱為“I/O區域”(I/O region)。在討論對I/O區域的管理之前,我們首先來分析一下Linux是如何實現“I/O資源”這一抽象概念的。

            3.1 Linux對I/O資源的描述

              Linux設計了一個通用的數據結構resource來描述各種I/O資源(如:I/O端口、外設內存、DMA和IRQ等)。該結構定義在include/linux/ioport.h頭文件中:


              struct resource {
            ??????? const char *name;
            ??????? unsigned long start, end;
            ??????? unsigned long flags;
            ??????? struct resource *parent, *sibling, *child;
              };

              各成員的含義如下:

              1. name指針:指向此資源的名稱。
              2. start和end:表示資源的起始物理地址和終止物理地址。它們確定了資源的范圍,也即是一個閉區間[start,end]。
              3. flags:描述此資源屬性的標志(見下面)。
              4. 指針parent、sibling和child:分別為指向父親、兄弟和子資源的指針。

              屬性flags是一個unsigned long類型的32位標志值,用以描述資源的屬性。比如:資源的類型、是否只讀、是否可緩存,以及是否已被占用等。下面是一部分常用屬性標志位的定義(ioport.h):


            /*
            * IO resources have these defined flags.
            */
            #define IORESOURCE_BITS??????? ??????? 0x000000ff??????? /* Bus-specific bits */

            #define IORESOURCE_IO??????? ??????? 0x00000100??????? /* Resource type */
            #define IORESOURCE_MEM??????? ??????? 0x00000200
            #define IORESOURCE_IRQ??????? ??????? 0x00000400
            #define IORESOURCE_DMA??????? ??????? 0x00000800

            #define IORESOURCE_PREFETCH??????? 0x00001000??????? /* No side effects */
            #define IORESOURCE_READONLY??????? 0x00002000
            #define IORESOURCE_CACHEABLE??????? 0x00004000
            #define IORESOURCE_RANGELENGTH??????? 0x00008000
            #define IORESOURCE_SHADOWABLE??????? 0x00010000
            #define IORESOURCE_BUS_HAS_VGA??????? 0x00080000

            #define IORESOURCE_UNSET??????? 0x20000000
            #define IORESOURCE_AUTO??????? ??????? 0x40000000
            #define IORESOURCE_BUSY??????? ??????? 0x80000000
            ??????? /* Driver has marked this resource busy */

            ?

              指針parent、sibling和child的設置是為了以一種樹的形式來管理各種I/O資源。

            3.2 Linux對I/O資源的管理

              Linux是以一種倒置的樹形結構來管理每一類I/O資源(如:I/O端口、外設內存、DMA和IRQ)的。每一類I/O資源都對應有一顆倒置的資源樹,樹中的每一個節點都是一個resource結構,而樹的根結點root則描述了該類資源的整個資源空間。

              基于上述這個思想,Linux在kernel/Resource.c文件中實現了對資源的申請、釋放及查找等操作。

              3.2.1 I/O資源的申請

              假設某類資源有如下這樣一顆資源樹:

              節點root、r1、r2和r3實際上都是一個resource結構類型。子資源r1、r2和r3通過sibling指針鏈接成一條單向非循環鏈表,其表頭由root節點中的child指針定義,因此也稱為父資源的子資源鏈表。r1、r2和r3的parent指針均指向他們的父資源節點,在這里也就是圖中的root節點。

              假設想在root節點中分配一段I/O資源(由圖中的陰影區域表示)。函數request_resource()實現這一功能。它有兩個參數:①root指針,表示要在哪個資源根節點中進行分配;②new指針,指向描述所要分配的資源(即圖中的陰影區域)的resource結構。該函數的源代碼如下(kernel/resource.c):


              int request_resource(struct resource *root, struct resource *new)
              {
            ??????? struct resource *conflict;

            ??????? write_lock(&resource_lock);
            ??????? conflict = __request_resource(root, new);
            ??????? write_unlock(&resource_lock);
            ??????? return conflict ? -EBUSY : 0;
              }

            ?

              對上述函數的NOTE如下:

              ①資源鎖resource_lock對所有資源樹進行讀寫保護,任何代碼段在訪問某一顆資源樹之前都必須先持有該鎖。其定義如下(kernel/Resource.c):

              static rwlock_t resource_lock = RW_LOCK_UNLOCKED;

              ②可以看出,函數實際上是通過調用內部靜態函數__request_resource()來完成實際的資源分配工作。如果該函數返回非空指針,則表示有資源沖突;否則,返回NULL就表示分配成功。

              ③最后,如果conflict指針為NULL,則request_resource()函數返回返回值0,表示成功;否則返回-EBUSY表示想要分配的資源已被占用。

              函數__request_resource()完成實際的資源分配工作。如果參數new所描述的資源中的一部分或全部已經被其它節點所占用,則函數返回與new相沖突的resource結構的指針。否則就返回NULL。該函數的源代碼如下


            (kernel/Resource.c):
            /* Return the conflict entry if you can't request it */
            static struct resource * __request_resource
              (struct resource *root, struct resource *new)
            {
            ??????? unsigned long start = new->start;
            ??????? unsigned long end = new->end;
            ??????? struct resource *tmp, **p;

            ??????? if (end < start)
            ??????? ??????? return root;
            ??????? if (start < root->start)
            ??????? ??????? return root;
            ??????? if (end > root->end)
            ??????? ??????? return root;
            ??????? p = &root->child;
            ??????? for (;;) {
            ??????? ??????? tmp = *p;
            ??????? ??????? if (!tmp || tmp->start > end) {
            ??????? ??????? ??????? new->sibling = tmp;
            ??????? ??????? ??????? *p = new;
            ??????? ??????? ??????? new->parent = root;
            ??????? ??????? ??????? return NULL;
            ??????? ??????? }
            ??????? ??????? p = &tmp->sibling;
            ??????? ??????? if (tmp->end < start)
            ??????? ??????? ??????? continue;
            ??????? ??????? return tmp;
            ??????? }
            }

            ?

              對函數的NOTE:

              ①前三個if語句判斷new所描述的資源范圍是否被包含在root內,以及是否是一段有效的資源(因為end必須大于start)。否則就返回root指針,表示與根結點相沖突。

              ②接下來用一個for循環遍歷根節點root的child鏈表,以便檢查是否有資源沖突,并將new插入到child鏈表中的合適位置(child鏈表是以I/O資源物理地址從低到高的順序排列的)。為此,它用tmp指針指向當前正被掃描的resource結構,用指針p指向前一個resource結構的sibling指針成員變量,p的初始值為指向root->sibling。For循環體的執行步驟如下:

              l 讓tmp指向當前正被掃描的resource結構(tmp=*p)。

              l 判斷tmp指針是否為空(tmp指針為空說明已經遍歷完整個child鏈表),或者當前被掃描節點的起始位置start是否比new的結束位置end還要大。只要這兩個條件之一成立的話,就說明沒有資源沖突,于是就可以把new鏈入child鏈表中:①設置new的sibling指針指向當前正被掃描的節點tmp(new->sibling=tmp);②當前節點tmp的前一個兄弟節點的sibling指針被修改為指向new這個節點(*p=new);③將new的parent指針設置為指向root。然后函數就可以返回了(返回值NULL表示沒有資源沖突)。

              l 如果上述兩個條件都不成立,這說明當前被掃描節點的資源域有可能與new相沖突(實際上就是兩個閉區間有交集),因此需要進一步判斷。為此它首先修改指針p,讓它指向tmp->sibling,以便于繼續掃描child鏈表。然后,判斷tmp->end是否小于new->start,如果小于,則說明當前節點tmp和new沒有資源沖突,因此執行continue語句,繼續向下掃描child鏈表。否則,如果tmp->end大于或等于new->start,則說明tmp->[start,end]和new->[start,end]之間有交集。所以返回當前節點的指針tmp,表示發生資源沖突。

              3.2.2 資源的釋放

              函數release_resource()用于實現I/O資源的釋放。該函數只有一個參數——即指針old,它指向所要釋放的資源。起源代碼如下:


            int release_resource(struct resource *old)
            {
            ??????? int retval;

            ??????? write_lock(&resource_lock);
            ??????? retval = __release_resource(old);
            ??????? write_unlock(&resource_lock);
            ??????? return retval;
            }

            ?

              可以看出,它實際上通過調用__release_resource()這個內部靜態函數來完成實際的資源釋放工作。函數__release_resource()的主要任務就是將資源區域old(如果已經存在的話)從其父資源的child鏈表重摘除,它的源代碼如下:


            static int __release_resource(struct resource *old)
            {
            ??????? struct resource *tmp, **p;

            ??????? p = &old->parent->child;
            ??????? for (;;) {
            ??????? ??????? tmp = *p;
            ??????? ??????? if (!tmp)
            ??????? ??????? ??????? break;
            ??????? ??????? if (tmp == old) {
            ??????? ??????? ??????? *p = tmp->sibling;
            ??????? ??????? ??????? old->parent = NULL;
            ??????? ??????? ??????? return 0;
            ??????? ??????? }
            ??????? ??????? p = &tmp->sibling;
            ??????? }
            ??????? return -EINVAL;
            }

            ?

              對上述函數代碼的NOTE如下:

              同函數__request_resource()相類似,該函數也是通過一個for循環來遍歷父資源的child鏈表。為此,它讓tmp指針指向當前被掃描的資源,而指針p則指向當前節點的前一個節點的sibling成員(p的初始值為指向父資源的child指針)。循環體的步驟如下:

              ①首先,讓tmp指針指向當前被掃描的節點(tmp=*p)。

              ②如果tmp指針為空,說明已經遍歷完整個child鏈表,因此執行break語句推出for循環。由于在遍歷過程中沒有在child鏈表中找到參數old所指定的資源節點,因此最后返回錯誤值-EINVAL,表示參數old是一個無效的值。

              ③接下來,判斷當前被掃描節點是否就是參數old所指定的資源節點。如果是,那就將old從child鏈表中去除,也即讓當前結點tmp的前一個兄弟節點的sibling指針指向tmp的下一個節點,然后將old->parent指針設置為NULL。最后返回0值表示執行成功。

              ④如果當前被掃描節點不是資源old,那就繼續掃描child鏈表中的下一個元素。因此將指針p指向tmp->sibling成員。

              3.2.3 檢查資源是否已被占用,

              函數check_resource()用于實現檢查某一段I/O資源是否已被占用。其源代碼如下:


            int check_resource(struct resource *root, unsigned long start, unsigned long len)
            {
            ??????? struct resource *conflict, tmp;

            ??????? tmp.start = start;
            ??????? tmp.end = start + len - 1;
            ??????? write_lock(&resource_lock);
            ??????? conflict = __request_resource(root, &tmp);
            ??????? if (!conflict)
            ??????? ??????? __release_resource(&tmp);
            ??????? write_unlock(&resource_lock);
            ??????? return conflict ? -EBUSY : 0;
            }

            ?

              對該函數的NOTE如下:

              ①構造一個臨時資源tmp,表示所要檢查的資源[start,start+end-1]。

              ②調用__request_resource()函數在根節點root申請tmp所表示的資源。如果tmp所描述的資源還被人使用,則該函數返回NULL,否則返回非空指針。因此接下來在conflict為NULL的情況下,調用__release_resource()將剛剛申請的資源釋放掉。

              ③最后根據conflict是否為NULL,返回-EBUSY或0值。

              3.2.4 尋找可用資源

              函數find_resource()用于在一顆資源樹中尋找未被使用的、且滿足給定條件的(也即資源長度大小為size,且在[min,max]區間內)的資源。其函數源代碼如下:


            /*
            * Find empty slot in the resource tree given range and alignment.
            */
            static int find_resource(struct resource *root, struct resource *new,
            ??????? ??????? ? unsigned long size,
            ??????? ??????? ? unsigned long min, unsigned long max,
            ??????? ??????? ? unsigned long align,
            ??????? ??????? ? void (*alignf)(void *, struct resource *, unsigned long),
            ??????? ??????? ? void *alignf_data)
            {
            ??????? struct resource *this = root->child;

            ??????? new->start = root->start;
            ??????? for(;;) {
            ??????? ??????? if (this)
            ??????? ??????? ??????? new->end = this->start;
            ??????? ??????? else
            ??????? ??????? ??????? new->end = root->end;
            ??????? ??????? if (new->start < min)
            ??????? ??????? ??????? new->start = min;
            ??????? ??????? if (new->end > max)
            ??????? ??????? ??????? new->end = max;
            ??????? ??????? new->start = (new->start + align - 1) & ~(align - 1);
            ??????? ??????? if (alignf)
            ??????? ??????? ??????? alignf(alignf_data, new, size);
            ??????? ??????? if (new->start < new->end && new->end - new->start + 1 >= size)
            ????????????????? {
            ??????? ??????? ??????? new->end = new->start + size - 1;
            ??????? ??????? ??????? return 0;
            ??????? ??????? }
            ??????? ??????? if (!this)
            ??????? ??????? ??????? break;
            ??????? ??????? new->start = this->end + 1;
            ??????? ??????? this = this->sibling;
            ??????? }
            ??????? return -EBUSY;
            }

            ?

              對該函數的NOTE如下:

              同樣,該函數也要遍歷root的child鏈表,以尋找未被使用的資源空洞。為此,它讓this指針表示當前正被掃描的子資源節點,其初始值等于root->child,即指向child鏈表中的第一個節點,并讓new->start的初始值等于root->start,然后用一個for循環開始掃描child鏈表,對于每一個被掃描的節點,循環體執行如下操作:

              ①首先,判斷this指針是否為NULL。如果不為空,就讓new->end等于this->start,也即讓資源new表示當前資源節點this前面那一段未使用的資源區間。

              ②如果this指針為空,那就讓new->end等于root->end。這有兩層意思:第一種情況就是根結點的child指針為NULL(即根節點沒有任何子資源)。因此此時先暫時將new->end放到最大。第二種情況就是已經遍歷完整個child鏈表,所以此時就讓new表示最后一個子資源后面那一段未使用的資源區間。

              ③根據參數min和max修正new->[start,end]的值,以使資源new被包含在[min,max]區域內。

              ④接下來進行對齊操作。

              ⑤然后,判斷經過上述這些步驟所形成的資源區域new是否是一段有效的資源(end必須大于或等于start),而且資源區域的長度滿足size參數的要求(end-start+1>=size)。如果這兩個條件均滿足,則說明我們已經找到了一段滿足條件的資源空洞。因此在對new->end的值進行修正后,然后就可以返回了(返回值0表示成功)。

              ⑥如果上述兩條件不能同時滿足,則說明還沒有找到,因此要繼續掃描鏈表。在繼續掃描之前,我們還是要判斷一下this指針是否為空。如果為空,說明已經掃描完整個child鏈表,因此就可以推出for循環了。否則就將new->start的值修改為this->end+1,并讓this指向下一個兄弟資源節點,從而繼續掃描鏈表中的下一個子資源節點。

              3.2.5 分配接口allocate_resource()

              在find_resource()函數的基礎上,函數allocate_resource()實現:在一顆資源樹中分配一條指定大小的、且包含在指定區域[min,max]中的、未使用資源區域。其源代碼如下:


            /*
            * Allocate empty slot in the resource tree given range and alignment.
            */
            int allocate_resource(struct resource *root, struct resource *new,
            ??????? ??????? ????? unsigned long size,
            ??????? ??????? ????? unsigned long min, unsigned long max,
            ??????? ??????? ????? unsigned long align,
            ??????? ??????? ????? void (*alignf)(void *, struct resource *, unsigned long),
            ??????? ??????? ????? void *alignf_data)
            {
            ??? int err;

            ??? write_lock(&resource_lock);
            ??? err = find_resource(root, new, size, min, max, align, alignf, alignf_data);
            ??? if (err >= 0 && __request_resource(root, new))
            ??????? err = -EBUSY;
            ??? write_unlock(&resource_lock);
            ??? return err;
            }

            ?

              3.2.6 獲取資源的名稱列表

              函數get_resource_list()用于獲取根節點root的子資源名字列表。該函數主要用來支持/proc/文件系統(比如實現proc/ioports文件和/proc/iomem文件)。其源代碼如下:


            int get_resource_list(struct resource *root, char *buf, int size)
            {
            ??????? char *fmt;
            ??????? int retval;

            ??????? fmt = "??????? %08lx-%08lx : %s
            ";
            ??????? if (root->end < 0x10000)
            ??????? ??????? fmt = "??????? %04lx-%04lx : %s
            ";
            ??????? read_lock(&resource_lock);
            ??????? retval = do_resource_list(root->child, fmt, 8, buf, buf + size) - buf;
            ??????? read_unlock(&resource_lock);
            ??????? return retval;
            }

            ?

              可以看出,該函數主要通過調用內部靜態函數do_resource_list()來實現其功能,其源代碼如下:


            /*
            * This generates reports for /proc/ioports and /proc/iomem
            */
            static char * do_resource_list(struct resource *entry, const char *fmt,
              int offset, char *buf, char *end)
            {
            ??????? if (offset < 0)
            ??????? ??????? offset = 0;

            ??????? while (entry) {
            ??????? ??????? const char *name = entry->name;
            ??????? ??????? unsigned long from, to;

            ??????? ??????? if ((int) (end-buf) < 80)
            ??????? ??????? ??????? return buf;

            ??????? ??????? from = entry->start;
            ??????? ??????? to = entry->end;
            ??????? ??????? if (!name)
            ??????? ??????? ??????? name = "";

            ??????? ??????? buf += sprintf(buf, fmt + offset, from, to, name);
            ??????? ??????? if (entry->child)
            ??????? ??????? ?? buf = do_resource_list(entry->child, fmt, offset-2, buf, end);
            ??????? ??????? entry = entry->sibling;
            ??????? }

            ??????? return buf;
            }

            ?

              函數do_resource_list()主要通過一個while{}循環以及遞歸嵌套調用來實現,較為簡單,這里就不在詳細解釋了。

            3.3 管理I/O Region資源

              Linux將基于I/O映射方式的I/O端口和基于內存映射方式的I/O端口資源統稱為“I/O區域”(I/O Region)。I/O Region仍然是一種I/O資源,因此它仍然可以用resource結構類型來描述。下面我們就來看看Linux是如何管理I/O Region的。

              3.3.1 I/O Region的分配

              在函數__request_resource()的基礎上,Linux實現了用于分配I/O區域的函數__request_region(),如下:


            struct resource * __request_region(struct resource *parent,
              unsigned long start, unsigned long n, const char *name)
            {
            ??????? struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);

            ??????? if (res) {
            ??????? ??????? memset(res, 0, sizeof(*res));
            ??????? ??????? res->name = name;
            ??????? ??????? res->start = start;
            ??????? ??????? res->end = start + n - 1;
            ??????? ??????? res->flags = IORESOURCE_BUSY;

            ??????? ??????? write_lock(&resource_lock);

            ??????? ??????? for (;;) {
            ??????? ??????? ??????? struct resource *conflict;

            ??????? ??????? ??????? conflict = __request_resource(parent, res);
            ??????? ??????? ??????? if (!conflict)
            ??????? ??????? ??????? ??????? break;
            ??????? ??????? ??????? if (conflict != parent) {
            ??????? ??????? ??????? ??????? parent = conflict;
            ??????? ??????? ??????? ??????? if (!(conflict->flags & IORESOURCE_BUSY))
            ??????? ??????? ??????? ??????? ??????? continue;
            ??????? ??????? ??????? }

            ??????? ??????? ??????? /* Uhhuh, that didn't work out.. */
            ??????? ??????? ??????? kfree(res);
            ??????? ??????? ??????? res = NULL;
            ??????? ??????? ??????? break;
            ??????? ??????? }
            ??????? ??????? write_unlock(&resource_lock);
            ??????? }
            ??????? return res;
            }

            ?

            NOTE:

              ①首先,調用kmalloc()函數在SLAB分配器緩存中分配一個resource結構。

              ②然后,相應的根據參數值初始化所分配的resource結構。注意!flags成員被初始化為IORESOURCE_BUSY。

              ③接下來,用一個for循環開始進行資源分配,循環體的步驟如下:

              l 首先,調用__request_resource()函數進行資源分配。如果返回NULL,說明分配成功,因此就執行break語句推出for循環,返回所分配的resource結構的指針,函數成功地結束。

              l 如果__request_resource()函數分配不成功,則進一步判斷所返回的沖突資源節點是否就是父資源節點parent。如果不是,則將分配行為下降一個層次,即試圖在當前沖突的資源節點中進行分配(只有在沖突的資源節點沒有設置IORESOURCE_BUSY的情況下才可以),于是讓parent指針等于conflict,并在conflict->flags&IORESOURCE_BUSY為0的情況下執行continue語句繼續for循環。

              l 否則如果相沖突的資源節點就是父節點parent,或者相沖突資源節點設置了IORESOURCE_BUSY標志位,則宣告分配失敗。于是調用kfree()函數釋放所分配的resource結構,并將res指針置為NULL,最后用break語句推出for循環。

              ④最后,返回所分配的resource結構的指針。

              3.3.2 I/O Region的釋放

              函數__release_region()實現在一個父資源節點parent中釋放給定范圍的I/O Region。實際上該函數的實現思想與__release_resource()相類似。其源代碼如下:


            void __release_region(struct resource *parent,
                unsigned long start, unsigned long n)
            {
            ??????? struct resource **p;
            ??????? unsigned long end;

            ??????? p = &parent->child;
            ??????? end = start + n - 1;

            ??????? for (;;) {
            ??????? ??????? struct resource *res = *p;

            ??????? ??????? if (!res)
            ??????? ??????? ??????? break;
            ??????? ??????? if (res->start <= start && res->end >= end) {
            ??????? ??????? ??????? if (!(res->flags & IORESOURCE_BUSY)) {
            ??????? ??????? ??????? ??????? p = &res->child;
            ??????? ??????? ??????? ??????? continue;
            ??????? ??????? ??????? }
            ??????? ??????? ??????? if (res->start != start'? 'res->end != end)
            ??????? ??????? ??????? ??????? break;
            ??????? ??????? ??????? *p = res->sibling;
            ??????? ??????? ??????? kfree(res);
            ??????? ??????? ??????? return;
            ??????? ??????? }
            ??????? ??????? p = &res->sibling;
            ??????? }
            ??????? printk("Trying to free nonexistent resource <%08lx-%08lx>
            ", start, end);
            }

            ?

              類似地,該函數也是通過一個for循環來遍歷父資源parent的child鏈表。為此,它讓指針res指向當前正被掃描的子資源節點,指針p指向前一個子資源節點的sibling成員變量,p的初始值為指向parent->child。For循環體的步驟如下:

              ①讓res指針指向當前被掃描的子資源節點(res=*p)。

              ②如果res指針為NULL,說明已經掃描完整個child鏈表,所以退出for循環。

              ③如果res指針不為NULL,則繼續看看所指定的I/O區域范圍是否完全包含在當前資源節點中,也即看看[start,start+n-1]是否包含在res->[start,end]中。如果不屬于,則讓p指向當前資源節點的sibling成員,然后繼續for循環。如果屬于,則執行下列步驟:

              l 先看看當前資源節點是否設置了IORESOURCE_BUSY標志位。如果沒有設置該標志位,則說明該資源節點下面可能還會有子節點,因此將掃描過程下降一個層次,于是修改p指針,使它指向res->child,然后執行continue語句繼續for循環。

              l 如果設置了IORESOURCE_BUSY標志位。則一定要確保當前資源節點就是所指定的I/O區域,然后將當前資源節點從其父資源的child鏈表中去除。這可以通過讓前一個兄弟資源節點的sibling指針指向當前資源節點的下一個兄弟資源節點來實現(即讓*p=res->sibling),最后調用kfree()函數釋放當前資源節點的resource結構。然后函數就可以成功返回了。

              3.3.3 檢查指定的I/O Region是否已被占用

              函數__check_region()檢查指定的I/O Region是否已被占用。其源代碼如下:


            int __check_region(struct resource *parent, unsigned long start, unsigned long n)
            {
            ??????? struct resource * res;

            ??????? res = __request_region(parent, start, n, "check-region");
            ??????? if (!res)
            ??????? ??????? return -EBUSY;

            ??????? release_resource(res);
            ??????? kfree(res);
            ??????? return 0;
            }

            ?

              該函數的實現與__check_resource()的實現思想類似。首先,它通過調用__request_region()函數試圖在父資源parent中分配指定的I/O Region。如果分配不成功,將返回NULL,因此此時函數返回錯誤值-EBUSY表示所指定的I/O Region已被占用。如果res指針不為空則說明所指定的I/O Region沒有被占用。于是調用__release_resource()函數將剛剛分配的資源釋放掉(實際上是將res結構從parent的child鏈表去除),然后調用kfree()函數釋放res結構所占用的內存。最后,返回0值表示指定的I/O Region沒有被占用。

            3.4 管理I/O端口資源

              我們都知道,采用I/O映射方式的X86處理器為外設實現了一個單獨的地址空間,也即“I/O空間”(I/O Space)或稱為“I/O端口空間”,其大小是64KB(0x0000-0xffff)。Linux在其所支持的所有平臺上都實現了“I/O端口空間”這一概念。

              由于I/O空間非常小,因此即使外設總線有一個單獨的I/O端口空間,卻也不是所有的外設都將其I/O端口(指寄存器)映射到“I/O端口空間”中。比如,大多數PCI卡都通過內存映射方式來將其I/O端口或外設內存映射到CPU的RAM物理地址空間中。而老式的ISA卡通常將其I/O端口映射到I/O端口空間中。

              Linux是基于“I/O Region”這一概念來實現對I/O端口資源(I/O-mapped 或 Memory-mapped)的管理的。

              3.4.1 資源根節點的定義

              Linux在kernel/Resource.c文件中定義了全局變量ioport_resource和iomem_resource,來分別描述基于I/O映射方式的整個I/O端口空間和基于內存映射方式的I/O內存資源空間(包括I/O端口和外設內存)。其定義如下:


            struct resource ioport_resource =
                { "PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO };
            struct resource iomem_resource =
                { "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };

            ?

              其中,宏IO_SPACE_LIMIT表示整個I/O空間的大小,對于X86平臺而言,它是0xffff(定義在include/asm-i386/io.h頭文件中)。顯然,I/O內存空間的大小是4GB。

              3.4.2 對I/O端口空間的操作

              基于I/O Region的操作函數__XXX_region(),Linux在頭文件include/linux/ioport.h中定義了三個對I/O端口空間進行操作的宏:①request_region()宏,請求在I/O端口空間中分配指定范圍的I/O端口資源。②check_region()宏,檢查I/O端口空間中的指定I/O端口資源是否已被占用。③release_region()宏,釋放I/O端口空間中的指定I/O端口資源。這三個宏的定義如下:


            #define request_region(start,n,name)
            ??????? __request_region(&ioport_resource, (start), (n), (name))
            #define check_region(start,n)
            ??????? __check_region(&ioport_resource, (start), (n))
            #define release_region(start,n)
            ??????? __release_region(&ioport_resource, (start), (n))

            ?

              其中,宏參數start指定I/O端口資源的起始物理地址(是I/O端口空間中的物理地址),宏參數n指定I/O端口資源的大小。

              3.4.3 對I/O內存資源的操作

              基于I/O Region的操作函數__XXX_region(),Linux在頭文件include/linux/ioport.h中定義了三個對I/O內存資源進行操作的宏:①request_mem_region()宏,請求分配指定的I/O內存資源。②check_ mem_region()宏,檢查指定的I/O內存資源是否已被占用。③release_ mem_region()宏,釋放指定的I/O內存資源。這三個宏的定義如下:


            #define request_mem_region(start,n,name)
              __request_region(&iomem_resource, (start), (n), (name))
            #define check_mem_region(start,n)
            ??????? __check_region(&iomem_resource, (start), (n))
            #define release_mem_region(start,n)
            ??????? __release_region(&iomem_resource, (start), (n))

            ?

              其中,參數start是I/O內存資源的起始物理地址(是CPU的RAM物理地址空間中的物理地址),參數n指定I/O內存資源的大小。

              3.4.4 對/proc/ioports和/proc/iomem的支持

              Linux在ioport.h頭文件中定義了兩個宏:

              get_ioport_list()和get_iomem_list(),分別用來實現/proc/ioports文件和/proc/iomem文件。其定義如下:


            #define get_ioport_list(buf) get_resource_list(&ioport_resource, buf, PAGE_SIZE)
            #define get_mem_list(buf)??????? get_resource_list(&iomem_resource, buf, PAGE_SIZE)

            ?

            3.5 訪問I/O端口空間

              在驅動程序請求了I/O端口空間中的端口資源后,它就可以通過CPU的IO指定來讀寫這些I/O端口了。在讀寫I/O端口時要注意的一點就是,大多數平臺都區分8位、16位和32位的端口,也即要注意I/O端口的寬度。

              Linux在include/asm/io.h頭文件(對于i386平臺就是include/asm-i386/io.h)中定義了一系列讀寫不同寬度I/O端口的宏函數。如下所示:

              ⑴讀寫8位寬的I/O端口


              unsigned char inb(unsigned port);
              void outb(unsigned char value,unsigned port);

            ?

              其中,port參數指定I/O端口空間中的端口地址。在大多數平臺上(如x86)它都是unsigned short類型的,其它的一些平臺上則是unsigned int類型的。顯然,端口地址的類型是由I/O端口空間的大小來決定的。

              ⑵讀寫16位寬的I/O端口


              unsigned short inw(unsigned port);
              void outw(unsigned short value,unsigned port);

            ?

              ⑶讀寫32位寬的I/O端口


              unsigned int inl(unsigned port);
              void outl(unsigned int value,unsigned port);

            ?

              3.5.1 對I/O端口的字符串操作

              除了上述這些“單發”(single-shot)的I/O操作外,某些CPU也支持對某個I/O端口進行連續的讀寫操作,也即對單個I/O端口讀或寫一系列字節、字或32位整數,這就是所謂的“字符串I/O指令”(String Instruction)。這種指令在速度上顯然要比用循環來實現同樣的功能要快得多。

              Linux同樣在io.h文件中定義了字符串I/O讀寫函數:

              ⑴8位寬的字符串I/O操作


              void insb(unsigned port,void * addr,unsigned long count);
              void outsb(unsigned port ,void * addr,unsigned long count);

            ?

              ⑵16位寬的字符串I/O操作


              void insw(unsigned port,void * addr,unsigned long count);
              void outsw(unsigned port ,void * addr,unsigned long count);

            ?

              ⑶32位寬的字符串I/O操作


              void insl(unsigned port,void * addr,unsigned long count);
              void outsl(unsigned port ,void * addr,unsigned long count);

            ?

              3.5.2 Pausing I/O


              在一些平臺上(典型地如X86),對于老式總線(如ISA)上的慢速外設來說,如果CPU讀寫其I/O端口的速度太快,那就可能會發生丟失數據的現象。對于這個問題的解決方法就是在兩次連續的I/O操作之間插入一段微小的時延,以便等待慢速外設。這就是所謂的“Pausing I/O”。

              對于Pausing I/O,Linux也在io.h頭文件中定義了它的I/O讀寫函數,而且都以XXX_p命名,比如:inb_p()、outb_p()等等。下面我們就以out_p()為例進行分析。

              將io.h中的宏定義__OUT(b,”b”char)展開后可得如下定義:


            extern inline void outb(unsigned char value, unsigned short port) {
            ??????? __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
            ??????? ??????? ??????? ??????? : : "a" (value), "Nd" (port));
            }

            extern inline void outb_p(unsigned char value, unsigned short port) {
            ??????? __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"
            ??????? ??????? ??????? ??????? __FULL_SLOW_DOWN_IO
            ??????? ??????? ??????? ??????? : : "a" (value), "Nd" (port));
            }

            ?

              可以看出,outb_p()函數的實現中被插入了宏__FULL_SLOWN_DOWN_IO,以實現微小的延時。宏__FULL_SLOWN_DOWN_IO在頭文件io.h中一開始就被定義:


            #ifdef SLOW_IO_BY_JUMPING
            #define __SLOW_DOWN_IO "
            jmp 1f
            1:??????? jmp 1f
            1:"
            #else
            #define __SLOW_DOWN_IO "
            outb %%al,$0x80"
            #endif

            #ifdef REALLY_SLOW_IO
            #define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
              __SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO
            #else
            #define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
            #endif

            ?

              顯然,__FULL_SLOW_DOWN_IO就是一個或四個__SLOW_DOWN_IO(根據是否定義了宏REALLY_SLOW_IO來決定),而宏__SLOW_DOWN_IO則被定義成毫無意義的跳轉語句或寫端口0x80的操作(根據是否定義了宏SLOW_IO_BY_JUMPING來決定)。

            3.6 訪問I/O內存資源

              盡管I/O端口空間曾一度在x86平臺上被廣泛使用,但是由于它非常小,因此大多數現代總線的設備都以內存映射方式(Memory-mapped)來映射它的I/O端口(指I/O寄存器)和外設內存。基于內存映射方式的I/O端口(指I/O寄存器)和外設內存可以通稱為“I/O內存”資源(I/O Memory)。因為這兩者在硬件實現上的差異對于軟件來說是完全透明的,所以驅動程序開發人員可以將內存映射方式的I/O端口和外設內存統一看作是“I/O內存”資源。

              從前幾節的闡述我們知道,I/O內存資源是在CPU的單一內存物理地址空間內進行編址的,也即它和系統RAM同處在一個物理地址空間內。因此通過CPU的訪內指令就可以訪問I/O內存資源。

              一般來說,在系統運行時,外設的I/O內存資源的物理地址是已知的,這可以通過系統固件(如BIOS)在啟動時分配得到,或者通過設備的硬連線(hardwired)得到。比如,PCI卡的I/O內存資源的物理地址就是在系統啟動時由PCI BIOS分配并寫到PCI卡的配置空間中的BAR中的。而ISA卡的I/O內存資源的物理地址則是通過設備硬連線映射到640KB-1MB范圍之內的。但是CPU通常并沒有為這些已知的外設I/O內存資源的物理地址預定義虛擬地址范圍,因為它們是在系統啟動后才已知的(某種意義上講是動態的),所以驅動程序并不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(通過頁表),然后才能根據映射所得到的核心虛地址范圍,通過訪內指令訪問這些I/O內存資源。

              3.6.1 映射I/O內存資源

              Linux在io.h頭文件中聲明了函數ioremap(),用來將I/O內存資源的物理地址映射到核心虛地址空間(3GB-4GB)中,如下:


            void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
            void iounmap(void * addr);

            ?

              函數用于取消ioremap()所做的映射,參數addr是指向核心虛地址的指針。這兩個函數都是實現在mm/ioremap.c文件中。具體實現可參考《情景分析》一書。

              3.6.2 讀寫I/O內存資源

              在將I/O內存資源的物理地址映射成核心虛地址后,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內存資源了。但是,由于在某些平臺上,對I/O內存和系統內存有不同的訪問處理,因此為了確保跨平臺的兼容性,Linux實現了一系列讀寫I/O內存資源的函數,這些函數在不同的平臺上有不同的實現。但在x86平臺上,讀寫I/O內存與讀寫RAM無任何差別。如下所示(include/asm-i386/io.h):


            #define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
            #define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
            #define readl(addr) (*(volatile unsigned int *) __io_virt(addr))

            #define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
            #define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
            #define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))

            #define memset_io(a,b,c)??????? memset(__io_virt(a),(b),(c))
            #define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
            #define memcpy_toio(a,b,c)??????? memcpy(__io_virt(a),(b),(c))

              上述定義中的宏__io_virt()僅僅檢查虛地址addr是否是核心空間中的虛地址。該宏在內核2.4.0中的實現是臨時性的。具體的實現函數在arch/i386/lib/Iodebug.c文件。

              顯然,在x86平臺上訪問I/O內存資源與訪問系統主存RAM是無差別的。但是為了保證驅動程序的跨平臺的可移植性,我們應該使用上面的函數來訪問I/O內存資源,而不應該通過指向核心虛地址的指針來訪問。

            posted on 2007-01-02 14:39 milkyway 閱讀(573) 評論(0)  編輯 收藏 引用 所屬分類: linux

            導航

            統計

            公告

            隨筆皆原創,文章乃轉載. 歡迎留言!

            常用鏈接

            留言簿(37)

            隨筆分類(104)

            隨筆檔案(101)

            文章分類(51)

            文章檔案(53)

            wince牛人

            搜索

            積分與排名

            最新評論

            閱讀排行榜

            評論排行榜

            国产午夜精品理论片久久| 品成人欧美大片久久国产欧美| 国产一级做a爰片久久毛片| 久久国产欧美日韩精品免费| 伊人久久大香线蕉综合热线| 久久国产亚洲高清观看| 性高湖久久久久久久久AAAAA| 久久亚洲国产精品成人AV秋霞| 欧美综合天天夜夜久久| 久久久久久国产a免费观看黄色大片| 久久久噜噜噜久久熟女AA片| 久久免费香蕉视频| 久久精品视频网| 亚洲国产精品无码久久| 婷婷久久综合九色综合98| 久久久久av无码免费网| 久久久噜噜噜久久| 国产欧美久久一区二区| 婷婷伊人久久大香线蕉AV| 亚洲国产成人精品无码久久久久久综合| 亚洲欧美成人综合久久久| 婷婷久久综合九色综合九七| 亚洲一区二区三区日本久久九| 中文无码久久精品| 77777亚洲午夜久久多人| 中文字幕无码av激情不卡久久| 久久黄视频| 久久久久九九精品影院| 久久久久亚洲AV无码去区首| 夜夜亚洲天天久久| 中文精品久久久久国产网址| 久久综合狠狠色综合伊人| 久久婷婷成人综合色综合| 久久这里只有精品18| 无码精品久久久天天影视| 久久99精品久久久久久动态图 | 婷婷久久五月天| 亚洲精品tv久久久久久久久久| 亚洲国产精品嫩草影院久久| 亚洲成av人片不卡无码久久| 偷窥少妇久久久久久久久|