幾乎每一種外設都是通過讀寫設備上的寄存器來進行的,通常包括控制寄存器、狀態寄存器和數據寄存器三大類,外設的寄存器通常被連續地編址。根據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,?如下:
Cpp代碼??
???? #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.結構體
Cpp代碼??
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字節大小空間
Cpp代碼??
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;?? ????????????}?? ??????????????kfree(res);????????????????????????????????????? ??????????????? ?????????????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;???????????????????????????????????????? ?????? ????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;?? ????}?? }??
其實說白了,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的函數如下所示:
Cpp代碼??
#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映射一個設備,意味著使用戶空間的一段地址關聯到設備內存上,這使得只要程序在分配的地址范圍內進行讀取或者寫入,實際上就是對設備的訪問。
總結
以上是生活随笔為你收集整理的内核request_mem_region 和 ioremap的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。