內存映射-IO空間-ioremap-iounremap

內存映射-IO空間-ioremap-iounremap

1 )關於 IO 與內存空間: 函數

X86 處理器中存在着 I/O 空間的概念, I/O 空間是相對於內存空間而言的,它經過特定的指令 in out 來訪問。端口號標識了外設的寄存器地址。 Intel 語法的 in out 指令格式爲: spa

IN 累加器 ,{ 端口號 │DX} .net

OUT{ 端口號 │DX}, 累加器 設計

目前,大多數嵌入式微控制器如 ARM PowerPC 等中並不提供 I/O 空間,而僅存在內存空間。內存空間能夠直接經過地址、指針來訪問,程序和程序運行中使用的變量和其餘數據都存在於內存空間中。 指針

即使是在 X86 處理器中,雖然提供了 I/O 空間,若是由咱們本身設計電路板,外設仍然能夠只掛接在內存空間。此時, CPU 能夠像訪問一個內存單元那樣訪問外設 I/O 端口,而不須要設立專門的 I/O 指令。所以,內存空間是必須的,而 I/O 空間是可選的。 blog

2 inb outb 接口

Linux 設備驅動中,宜使用 Linux 內核提供的函數來訪問定位於 I/O 空間的端口,這些函數包括: 進程

· 讀寫字節端口( 8 位寬) 內存

unsignedinb(unsignedport); 開發

voidoutb(unsignedcharbyte,unsignedport);

· 讀寫字端口( 16 位寬)

unsignedinw(unsignedport);

voidoutw(unsignedshortword,unsignedport);

· 讀寫長字端口( 32 位寬)

unsignedinl(unsignedport);

voidoutl(unsignedlongword,unsignedport);

· 讀寫一串字節

voidinsb(unsignedport,void*addr,unsignedlongcount);

voidoutsb(unsignedport,void*addr,unsignedlongcount);

·insb() 從端口 port 開始讀 count 個字節端口,並將讀取結果寫入 addr 指向的內存; outsb() addr 指向的內存的 count 個字節連續地寫入 port 開始的端口。

· 讀寫一串字

voidinsw(unsignedport,void*addr,unsignedlongcount);

voidoutsw(unsignedport,void*addr,unsignedlongcount);

· 讀寫一串長字

voidinsl(unsignedport,void*addr,unsignedlongcount);

voidoutsl(unsignedport,void*addr,unsignedlongcount);

上述各函數中 I/O 端口號 port 的類型高度依賴於具體的硬件平臺,所以,只是寫出了 unsigned

3 readb writeb:

在設備的物理地址被映射到虛擬地址以後,儘管能夠直接經過指針訪問這些地址,可是工程師宜使用 Linux 內核的以下一組函數來完成設備內存映射的虛擬地址的讀寫,這些函數包括:

· I/O 內存

unsignedintioread8(void*addr);

unsignedintioread16(void*addr);

unsignedintioread32(void*addr);

與上述函數對應的較早版本的函數爲(這些函數在 Linux2.6 中仍然被支持):

unsignedreadb(address);

unsignedreadw(address);

unsignedreadl(address);

· I/O 內存

voidiowrite8(u8value,void*addr);

voidiowrite16(u16value,void*addr);

voidiowrite32(u32value,void*addr);

與上述函數對應的較早版本的函數爲(這些函數在 Linux2.6 中仍然被支持):

voidwriteb(unsignedvalue,address);

voidwritew(unsignedvalue,address);

voidwritel(unsignedvalue,address);

4 )把 I/O 端口映射到 內存空間 」:

void*ioport_map(unsignedlongport,unsignedintcount);

經過這個函數,能夠把 port 開始的 count 個連續的 I/O 端口重映射爲一段 內存空間 。而後就能夠在其返回的地址上像訪問 I/O 內存同樣訪問這些 I/O 端口。當再也不須要這種映射時,須要調用下面的函數來撤消:

voidioport_unmap(void*addr);

實際上,分析 ioport_map() 的源代碼可發現,所謂的映射到內存空間行爲其實是給開發人員製造的一個 假象 ,並無映射到內核虛擬地址,僅僅是爲了讓工程師可以使用統一的 I/O 內存訪問接口訪問 I/O 端口。

 

11.2.7 I/O 空間的映射

不少硬件設備都有本身的內存,一般稱之爲 I/O 空間。例如,全部比較新的圖形卡都有幾 MB RAM ,稱爲顯存,用它來存放要在屏幕上顯示的屏幕影像。

1 .地址映射

根據設備和總線類型的不一樣, PC 體系結構中的 I/O 空間能夠在三個不一樣的物理地址範圍之間進行映射:

1 )對於鏈接到 ISA 總線上的大多數設備

I/O 空間一般被映射到從 0xa0000 0xfffff 的物理地址範圍,這就在 640K 1MB 之間留出了一段空間,這就是所謂的

2 )對於使用 VESA 本地總線( VLB )的一些老設備

這是主要由圖形卡使用的一條專用總線: I/O 空間被映射到從 0xe00000 0xffffff 的地址範圍中,也就是 14MB 16MB 之間。由於這些設備使頁表的初始化更加複雜,所以已經不生產這種設備。

3 )對於鏈接到 PCI 總線的設備

I/O 空間被映射到很大的物理地址區間,位於 RAM 物理地址的頂端。這種設備的處理比較簡單。

2 .訪問 I/O 空間

內核如何訪問一個 I/O 空間單元?讓咱們從 PC 體系結構開始入手,這個問題很容易就能夠解決,以後咱們再進一步討論其餘體系結構。

不要忘了內核程序做用於虛擬地址,所以 I/O 空間單元必須表示成大於 PAGE_OFFSET 的地址。在後面的討論中,咱們假設 PAGE_OFFSET 等於 0xc0000000 ,也就是說,內核虛擬地址是在第 4G

內核驅動程序必須把 I/O 空間單元的物理地址轉換成內核空間的虛擬地址。在 PC 體系結構中,這能夠簡單地把 32 位的物理地址和 0xc0000000 常量進行或運算獲得。例如,假設內核須要把物理地址爲 0x000b0fe4 I/O 單元的值存放在 t1 中,把物理地址爲 0xfc000000 I/O 單元的值存放在 t2 中,就可使用下面的表達式來完成這項功能:

 

t1=*((unsignedchar*)(0xc00b0fe4));

t2=*((unsignedchar*)(0xfc000000));

 

在第六章咱們已經介紹過 , 在初始化階段 , 內核已經把可用的 RAM 物理地址映射到虛擬地址空間第 4G 的最初部分。所以,分頁機制把出如今第一個語句中的虛擬地址 0xc00b0fe4 映射回到原來的 I/O 物理地址 0x000b0fe4 ,這正好落在從 640K 1MB 的這段 「ISA 中。這正是咱們所指望的。

可是,對於第二個語句來講,這裏有一個問題,由於其 I/O 物理地址超過了系統 RAM 的最大物理地址。所以,虛擬地址 0xfc000000 就不須要與物理地址 0xfc000000 相對應。在這種狀況下,爲了在內核頁表中包括對這個 I/O 物理地址進行映射的虛擬地址,必須對頁表進行修改:這能夠經過調用 ioremap() 函數來實現。 ioremap() vmalloc() 函數相似,都調用 get_vm_area() 創建一個新的 vm_struct 描述符,其描述的虛擬地址區間爲所請求 I/O 空間區的大小。而後, ioremap() 函數適當地更新全部進程的對應頁表項。

所以,第二個語句的正確形式應該爲:

 

io_mem=ioremap(0xfb000000,0x200000);

t2=*((unsignedchar*)(io_mem+0x100000));

 

第一條語句創建一個 2MB 的虛擬地址區間,從 0xfb000000 開始;第二條語句讀取地址 0xfc000000 的內存單元。驅動程序之後要取消這種映射,就必須使用 iounmap() 函數。

 

如今讓咱們考慮一下除 PC 以外的體系結構。在這種狀況下,把 I/O 物理地址加上 0xc0000000 常量所獲得的相應虛擬地址並不老是正確的。爲了提升內核的可移植性, Linux 特地包含了下面這些宏來訪問 I/O 空間:

readb,readw,readl

分別從一個 I/O 空間單元讀取 1 2 或者 4 個字節

writeb,writew,writel

分別向一個 I/O 空間單元寫入 1 2 或者 4 個字節

memcpy_fromio,memcpy_toio

把一個數據塊從一個 I/O 空間單元拷貝到動態內存中,另外一個函數正好相反,把一個數據塊從動態內存中拷貝到一個 I/O 空間單元

memset_io

用一個固定的值填充一個 I/O 空間區域

對於 0xfc000000I/O 單元的訪問推薦使用這樣的方法:

io_mem=ioremap(0xfb000000,0x200000);

t2=readb(io_mem+0x100000);

使用這些宏,就能夠隱藏不一樣平臺訪問 I/O 空間所用方法的差別。
相關文章
相關標籤/搜索