Linux裝置驅動程式設計之記憶體與I O操作

2021-06-05 08:06:12 字數 2781 閱讀 7520

risc指令系統的cpu(如arm、powerpc等)通常只實現乙個實體地址空間,外設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記憶體資源的物理位址對映到核心虛位址空間(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)

#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是次要的,關鍵問題是有無虛擬位址和實體地址的轉換!

Linux裝置驅動程式設計之記憶體與I O操作

對於提供了mmu 儲存管理器,輔助作業系統進行記憶體管理,提供虛實位址轉換等硬體支援 的處理器而言,linux提供了複雜的儲存管理系統,使得程序所能訪問的記憶體達到4gb。程序的4gb記憶體空間被人為的分為兩個部分 使用者空間與核心空間。使用者空間位址分布從0到3gb page offset,在0x...

Linux裝置驅動之記憶體對映

linux裝置驅動之記憶體對映 日期 2011 04 11 1.記憶體對映 所謂的記憶體對映就是把物理記憶體對映到程序的位址空間之內,這些應用程式就可以直接使用輸入輸出的位址空間,從而提高讀寫的效率。linux提供了mmap 函式,用來對映物理記憶體。在驅動程式中,應用程式以裝置檔案為物件,呼叫mm...

Linux塊裝置驅動之記憶體模擬塊裝置

用記憶體代替塊裝置的總結,相對來簡單得多,對記憶體操作想必大家都很熟悉,直接分配一塊記憶體就可以直接讀寫操作了 參考 drivers block xd.c drivers block z2ram.c define ramblock size 1024 1024 乙個扇區是512位元組 static ...