request_mem_region和ioremap

本文轉自:內核request_mem_region 和 ioremap的理解

幾乎每一種外設都是通過讀寫設備上的寄存器來進行的,通常包括控制寄存器、狀態寄存器和數據寄存器三大類,外設的寄存器通常被連續地編址。根據CPU體系結構的不同,CPU對IO端口的編址方式有兩種:

  (1)I/O映射方式(I/O-mapped)

  典型地,如X86處理器爲外設專門實現了一個單獨的地址空間,稱爲"I/O地址空間"或者"I/O端口空間",CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元。 

  (2)內存映射方式(Memory-mapped)

  RISC指令系統的CPU(如MIPS ARM PowerPC等)通常只實現一個物理地址空間,像這種情況,外設的I/O端口的物理地址就被映射到內存地址空間中,外設I/O端口成爲內存的一部分。此時,CPU可以象訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的外設I/O指令。

  但是,這兩者在硬件實現上的差異對於軟件來說是完全透明的,驅動程序開發人員可以將內存映射方式的I/O端口和外設內存統一看作是"I/O內存"資源。

  一般來說,在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定。但是CPU通常並沒有爲這些已知的外設I/O內存資源的物理地址預定義虛擬地址範圍,驅動程序並不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(通過頁表),然後才能根據映射所得到的核心虛地址範圍,通過訪內指令訪問這些I/O內存資源。Linux在io.h頭文件中聲明瞭函數ioremap(),用來將I/O內存資源的物理地址映射到核心虛地址空間。

     但要使用I/O內存首先要申請,然後才能映射,使用I/O端口首先要申請,或者叫請求,對於I/O端口的請求意思是讓內核知道你要訪問這個端口,這樣內核知道了以後它就不會再讓別人也訪問這個端口了.畢竟這個世界僧多粥少啊.申請I/O端口的函數是request_region, 申請I/O內存的函數是request_mem_region, 來自include/linux/ioport.h,  如下:

  /* Convenience shorthand with allocation */
#define request_region(start,n,name)    __request_region(&ioport_resource, (start), (n), (name))
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
#define rename_region(region, newname) do { (region)->name = (newname); } while (0)
extern struct resource * __request_region(struct resource *,
                                         resource_size_t start,
                                         resource_size_t n, const char *name);

這裏關鍵來解析一下request_mem_region函數。

Linux把基於I/O映射方式的I/O端口和基於內存映射方式的I/O端口資源統稱爲“I/O區域”(I/O Region)。I/O Region仍然是一種I/O資源,因此它仍然可以用resource結構類型來描述。

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

1.結構體

1.1>struct resource iomem_resource = { "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };
   1.2>struct resource {
                const char *name;
                unsigned long start, end;
                unsigned long flags;
                struct resource *parent, *sibling, *child;
             };

2.調用函數
   request_mem_region(S1D_PHYSICAL_REG_ADDR,S1D_PHYSICAL_REG_SIZE, "EpsonFB_RG")
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
__request_region檢查是否可以安全佔用起始物理地址S1D_PHYSICAL_REG_ADDR之後的連續S1D_PHYSICAL_REG_SIZE字節大小空間

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);    
             //sibling parent下的所有單元,檢測申請部分是否存在交疊衝突
            if (!conflict)                                 
             //conflict=0;申請成功,正常安置了[start,end]到相應位置
                break;
            if (conflict != parent) {
                 parent = conflict;
                if (!(conflict->flags & IORESOURCE_BUSY))
                    continue;
            }
              kfree(res);                                   
             //檢測到了資源交疊衝突,kfree歸還kmalloc申請的內存
             res = NULL;
            break;
        }
         write_unlock(&resource_lock);
    }
    return res;
}

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;                                      
    //root下的第一個鏈表元素*p.[child鏈表是以I/O資源物理地址從低到高的順序排列的]
    for (;;) {
         tmp = *p;
        if (!tmp || tmp->start > end) {
            new->sibling = tmp;
            *p = new;
//可以從root->child=null開始我們的分析考慮,此時tmp=null,那麼第一個申請將以!tmp條件滿足而進入
//這時root->child的值爲new指針,new->sibling = tmp = null;當第二次申請發生時:如果tmp->start > end成立,
//那麼,root->child的值爲new指針,new->sibling = tmp;這樣就鏈接上了,空間分佈圖如:
//child=[start,end]-->[tmp->start,tmp->end](1);
//如果條件tmp->start > end不成立,那麼只能是!tmp條件進入
//那麼,root->child的值不變,tmp->sibling = new;new->sibling = tmp = null這樣就鏈接上了,空間分佈圖如:
//child=[child->start,child->end]-->[start,end](2);
//當第三次申請發生時:如果start在(2)中的[child->end,end]之間,那麼tmp->end < start將成立,繼而continue,
//此時tmp = (2)中的[start,end],因爲tmp->start < end,所以繼續執行p = &tmp->slibing = null,
//因爲tmp->end > start,所以資源衝突,返回(2)中的[start,end]域
//綜上的兩個邊界值情況和一箇中間值情況的分析,可以知道代碼實現了一個從地地址到高地址的順序鏈表
//模型圖:childe=[a,b]-->[c,d]-->[e,f],此時有一個[x,y]需要插入進去,tmp作爲sibling指針遊動
//tmp指向child=[a,b],
//tmp指向[a,b],當tmp->start>y時,插入後的鏈接圖爲:child=[x,y]-->[a,b]-->[c,d]-->[e,f]-->null;當tmp->end>=x時,衝突返回tmp
//tmp指向[c,d],當tmp->start>y時,插入後的鏈接圖爲:child=[a,b]-->[x,y]-->[c,d]-->[e,f]-->null;當tmp->end>=x時,衝突返回tmp
//tmp指向[e,f],當tmp->start>y時,插入後的鏈接圖爲:child=[a,b]-->[c,d]-->[x,y]-->[e,f]-->null;當tmp->end>=x時,衝突返回tmp
//tmp指向null                  ,插入後的鏈接圖爲:child=[a,b]-->[c,d]-->[e,f]-->[x,y]-->null;
//順利的達到了檢測衝突,順序鏈接的目的
            new->parent = root;    
            return NULL;
        }
         p = &tmp->sibling;
        if (tmp->end < start)
            continue;
        return tmp;
    }
}

其實說白了,request_mem_region函數並沒有做實際性的映射工作,只是告訴內核要使用一塊內存地址,聲明佔有,也方便內核管理這些資源。

重要的還是ioremap函數,ioremap主要是檢查傳入地址的合法性,建立頁表(包括訪問權限),完成物理地址到虛擬地址的轉換。

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

  iounmap函數用於取消ioremap()所做的映射,原型如下:

void iounmap(void * addr);

  這兩個函數都是實現在mm/ioremap.c文件中。

  在將I/O內存資源的物理地址映射成核心虛地址後,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內存資源了。爲了保證驅動程序的跨平臺的可移植性,我們應該使用Linux中特定的函數來訪問I/O內存資源,而不應該通過指向核心虛地址的指針來訪問。如在x86平臺上,讀寫I/O的函數如下所示:

#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))

最後,特別強調驅動程序中mmap函數的實現方法。用mmap映射一個設備,意味着使用戶空間的一段地址關聯到設備內存上,這使得只要程序在分配的地址範圍內進行讀取或者寫入,實際上就是對設備的訪問。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章