1. 程式人生 > >裝置地址與IO記憶體對映

裝置地址與IO記憶體對映

在嵌入式程式設計中,絕大部分功能都是通過驅動外設實現的,這些外設不僅可以是CPU外部的某種功能模組,也可以是CPU晶片內部整合的某些器件。這些晶片內部的外設基本都是通過匯流排的方式與CPU核心相連,而對它們的控制也通過對這些總線上的外設暫存器的配置來實現。

外設暫存器也稱為“I/O埠”,通常包括:控制暫存器、狀態暫存器和資料暫存器三大類,而且一個外設的暫存器通常被連續地編址。

但是外設暫存器與CPU核心暫存器不同,核心暫存器是有名字的,不同體系架構的暫存器名也不一樣,這個體現在不同架構的編譯器裡;而外設暫存器是有地址的,不同CPU晶片會有不同匯流排連線方式,所以也會有不同的外設暫存器地址,有了地址以後,CPU核心暫存器就可以通過相應的外設暫存器地址去配置相應裝置的動作了。

CPU對外設IO埠實體地址的編址方式 有兩種:

一種是I/O對映方式(I/O-mapped)稱為埠對映,另一種是儲存空間對映方式(Memory-mapped),稱為記憶體對映。而具體採用哪一種則取決於CPU的體系結構。

埠對映的典型代表是MCS-51系列微控制器和x86體系,這種對映需要有獨立的地址空間對應外設地址,而且還需要另外的彙編命令來控制。

如51微控制器的sfr特殊功能暫存器就是一個特別的命令來控制外設,而sfr所管理的128B的地址也是與RAM地址獨立的,有興趣的同學可以搜尋下sfr的相關介紹。

記憶體對映是嵌入式裝置體系用的比較多的方式,我們熟知的ARM體系,PowerPC就是用了這種與物理RAM地址統一編址的方式。

如STM32裡對各種外設暫存器的操作就使用了簡單的指標方式:

#define rGPACON    (*(volatile unsigned *)0x7F008000) //Port Acontrol

相應的外設暫存器地址可以通過晶片的datasheet找到。  下圖是s3c6410 datasheet的截圖

通過指標配置外設想到的方便,在STM32的程式設計中你完全可以體會到它的便利。但是這僅僅是在裸機程式上,到作業系統層面上,原本簡單的東西也會變得複雜。。。

linux在實體地址的管理上也是考慮周到。

首先介紹下linux中的實體地址和虛擬地址:

在支援MMU的32位處理器平臺上,Linux系統中的物理儲存空間和虛擬儲存空間的地址範圍分別都是從0x00000000到0xFFFFFFFF,共4GB,但物理儲存空間與虛擬儲存空間佈局完全不同。Linux執行在虛擬儲存空間,並負責把系統中實際存在的遠小於4GB的實體記憶體根據不同需求對映到整個4GB的虛擬儲存空間中。

在虛擬地址空間中,linux又將這4G分為使用者空間(0-0xbfffffff)和核心空間(0xc0000000-0xffffffff),為了讓使用者空間沒機會直接接觸實體地址,linux的實體地址對映都是在核心空間完成的。

在32位的CPU中,核心地址區域就1G大小,對於現在的硬體來說這麼點大的地址確實不夠直接對映實體地址,所以linux對這核心區域又進行了劃分:


核心區開始的896M區域被劃分為實體記憶體對映區

接下去的8M區域是隔離區

--------------------------------分界線以上稱為低端記憶體地址,以下稱為高階記憶體地址-------------------------------------------

後面的約120M區域是Vmalloc虛擬記憶體分配區

接下去是8k是隔離區

然後4M區域是KMAP高階記憶體對映區(永久記憶體對映區)

後面4M區域是固定對映區

最後4k是保留區

實體記憶體對映區會將實際的實體記憶體的起始地址到偏移896M的地址直接對映過來(注意這裡直接對映的是SDRAM(記憶體)的實體地址)

但是如果實體記憶體的總量超過了896M,那就要用到KMAP區了,這個區域會臨時對映896M以後的記憶體區域,這樣不管實體記憶體有多大都能對映到這裡。

實體記憶體都分配完了,那麼其他的IO外設地址該如何對映呢?  在linux中並沒有為這些已知的外設I/O記憶體資源的實體地址預定義虛擬地址範圍,驅動程式並不能直接通過實體地址訪問I/O記憶體資源,而必須將它們對映到核心虛地址空間內(通過頁表),然後才能根據對映所得到的核心虛地址範圍,通過訪內指令訪問這些I/O記憶體資源。Linux在io.h標頭檔案中聲明瞭函式ioremap(),用來將I/O記憶體資源的實體地址對映到核心虛地址空間(3GB-4GB)中

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

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

voidiounmap(void * addr);

  這兩個函式都是實現在mm/ioremap.c檔案中。

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

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

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

#definememset_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對映一個裝置,意味著使使用者空間的一段地址關聯到裝置記憶體上,這使得只要程式在分配的地址範圍內進行讀取或者寫入,實際上就是對裝置的訪問,這樣就實現了虛擬地址到實體地址的對映與操作。