裝置I O 埠和I O 記憶體的訪問

2021-08-03 12:24:41 字數 3441 閱讀 6390

幾乎每一種外設都是通過讀寫裝置上的暫存器來進行的,通常包括控制暫存器、狀態暫存器和資料暫存器三大類,外設的暫存器通常被連續地編址。根據cpu體系結構的不同,cpu對io埠的編址方式有兩種:

典型地,如x86處理器為外設專門實現了乙個單獨的位址空間,稱為"i/o位址空間"或者"i/o埠空間",cpu通過專門的i/o指令(如x86的in和out指令)來訪問這一空間中的位址單元。

但是,這兩者在硬體實現上的差異對於軟體來說是完全透明的,驅動程式開發人員可以將記憶體對映方式的i/o埠和外設記憶體統一看作是"i/o記憶體"資源。

一般來說,在系統執行時,外設的i/o記憶體資源的實體地址是已知的,由硬體的設計決定。但是cpu通常並沒有為這些已知的外設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()所做的對映,原型如下:

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對映乙個裝置,意味著使

使用者空間

的一段位址關聯到裝置記憶體上,這使得只要程式在分配的位址範圍內進行讀取或者寫入,實際上就是對裝置的訪問。

筆者在linux源**中進行包含"ioremap"文字的搜尋,發現真正出現的ioremap的地方相當少。所以筆者追根索源地尋找i/o操作的實體地址轉換到虛擬位址的真實所在,發現

linux有替代ioremap的語句,但是這個轉換過程卻是不可或缺的

。譬如我們再次摘取s3c2410這個arm晶元rtc(實時鐘)驅動中的一小段:

static void get_rtc_time(int alm, struct rtc_time *rtc_tm)

else

}spin_unlock_irq(&rtc_lock);

bcd_to_bin(rtc_tm->tm_year);

bcd_to_bin(rtc_tm->tm_mon);

bcd_to_bin(rtc_tm->tm_mday);

bcd_to_bin(rtc_tm->tm_hour);

bcd_to_bin(rtc_tm->tm_min);

bcd_to_bin(rtc_tm->tm_sec);

/* the epoch of tm_year is 1900 */

rtc_tm->tm_year += rtc_leap_year - 1900;

/* tm_mon starts at 0, but rtc month starts at 1 */

rtc_tm->tm_mon--;}

i/o操作似乎就是對almyear、almmon、almday定義的暫存器進行操作,那這些巨集究竟定義為什麼呢?

#define almday brtc(0x60)

#define almmon brtc(0x64)

#define almyear brtc(0x68)

其中借助了巨集brtc,這個巨集定義為:

#define brtc(nb) __reg(0x57000000 + (nb))

其中又借助了巨集__reg,而__reg又定義為:

# define __reg(x) io_p2v(x)

最後的io_p2v才是真正"玩"虛擬位址和實體地址轉換的地方: 

#define io_p2v(x) ((x) | 0xa0000000)

與__reg對應的有個__preg:

# define __preg(x) io_v2p(x)

與io_p2v對應的有個io_v2p:

#define io_v2p(x) ((x) & ~0xa0000000)

可見有沒有出現ioremap是次要的,關鍵問題是有無虛擬位址和實體地址的轉換!

下面的程式在啟動的時候保留一段記憶體,然後使用ioremap將它對映到核心虛擬空間,同時又用remap_page_range對映到使用者虛擬空間,這樣一來,核心和使用者都能訪問。如果在核心虛擬位址將這段記憶體初始化串"abcd",那麼在使用者虛擬位址能夠讀出來:

remap_page_range函式的功能是構造用於對映一段實體地址的新頁表,實現了核心空間與使用者空間的對映,其原型如下: 

int remap_page_range(vma_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_tprot);

使用mmap最典型的例子是顯示卡的驅動,將視訊記憶體空間直接從核心對映到使用者空間將可提供視訊記憶體的讀寫效率。

(在核心驅動程式的初始化階段,通過ioremap()將物理位址對映到核心虛擬空間;在驅動程式的mmap系統呼叫中,使用remap_page_range()將該塊rom對映到使用者虛擬空間。這樣核心空間和使用者空間都能訪問這段被對映後的虛擬位址。)

I O埠和I O記憶體

每種外設都通過讀寫暫存器進行控制,大部分外設都有幾個暫存器,不管在記憶體位址空間還是在i o位址空間,這些暫存器的訪問位址是連續的。在硬體層,記憶體區域和io區域沒有概念上的區別 他們都通過向位址匯流排和控制匯流排傳送電平訊號進行訪問,再通過資料匯流排讀寫資料。儘管硬體暫存器和記憶體非常相似,但程式...

LinuxI O埠和I O記憶體的訪問介面

裝置通常會提供一組暫存器來用於控制裝置 讀寫裝置和獲取裝置狀態,即控制暫存器 資料暫存器和狀態暫存器。這些暫存器可能位於i o 空間,也可能位於記憶體空間。當位於i o 空間時,通常被稱為i o埠,位於記憶體空間時,對應的記憶體空間被稱為i o 記憶體。1 i o 埠 常用介面有inb,outb,i...

I O埠與I O記憶體

埠的概念 裝置通過系統匯流排上的介面與cpu相連,介面電路中含有多種暫存器,cpu向裝置讀寫資料實際上是向介面上的暫存器讀寫資料,這些暫存器稱為i o埠。乙個介面通常包含控制埠,資料埠,狀態埠。對於x86平台,實體地址就是匯流排位址。linux中,程序中的4gb虛擬記憶體分為使用者空間和核心空間,使...