Linux核心中ioremap對映的透徹理解

2021-06-21 14:52:07 字數 3116 閱讀 5427

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

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

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對映到使用者虛擬空間。這樣核心空間和使用者空間都能訪問這段被對映後的虛擬位址。)

Linux核心中ioremap對映的透徹理解

幾乎每一種外設都是通過讀寫裝置上的暫存器來進行的,通常包括控制暫存器 狀態暫存器和資料暫存器三大類,外設的暫存器通常被連續地編址。根據cpu體系結構的不同,cpu對io埠的編址方式有兩種 典型地,如x86處理器為外設專門實現了乙個單獨的位址空間,稱為 i o位址空間 或者 i o埠空間 cpu通過專...

Linux核心中ioremap對映的透徹理解

幾乎每一種外設都是通過讀寫裝置上的暫存器來進行的,通常包括控制暫存器 狀態暫存器和資料暫存器三大類,外設的暫存器通常被連續地編址。根據cpu體系結構的不同,cpu對io埠的編址方式有兩種 典型地,如x86處理器為外設專門實現了乙個單獨的位址空間,稱為 i o位址空間 或者 i o埠空間 cpu通過專...

Linux核心中ioremap對映的透徹理解

幾乎每一種外設都是通過讀寫裝置上的暫存器來進行的,通常包括控制暫存器 狀態暫存器和資料暫存器三大類,外設的暫存器通常被連續地編址。根據cpu體系結構的不同,cpu對io埠的編址方式有兩種 典型地,如x86處理器為外設專門實現了乙個單獨的位址空間,稱為 i o位址空間 或者 i o埠空間 cpu通過專...