咱們知道默認外設I/O資源是不在Linux內核空間中的(如sram或硬件接口寄存器等),若須要訪問該外設I/O資源,必須先將其地址映射到內核空間中來,而後才能在內核空間中訪問它。html
Linux內核訪問外設I/O內存資源的方式有兩種:動態映射(ioremap)和靜態映射(map_desc)。linux
1、動態映射(ioremap)方式程序員
動態映射方式是你們使用了比較多的,也比較簡單。即直接經過內核提供的ioremap函數動態建立一段外設I/O內存資源到內核虛擬地址的映射表,從而能夠在內核空間中訪問這段I/O資源。數組
Ioremap宏定義在asm/io.h內:cookie
#define ioremap(cookie,size) __ioremap(cookie,size,0)架構
__ioremap函數原型爲(arm/mm/ioremap.c):app
void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);ide
phys_addr:要映射的起始的IO地址函數
size:要映射的空間的大小spa
flags:要映射的IO空間和權限有關的標誌
該函數返回映射後的內核虛擬地址(3G-4G). 接着即可以經過讀寫該返回的內核虛擬地址去訪問之這段I/O內存資源。
舉一個簡單的例子: (取自s3c2410的iis音頻驅動)
好比咱們要訪問s3c2410平臺上的I2S寄存器, 查看datasheet 知道IIS物理地址爲0x55000000,咱們把它定義爲宏S3C2410_PA_IIS,以下:
#define S3C2410_PA_IIS (0x55000000)
若要在內核空間(iis驅動)中訪問這段I/O寄存器(IIS)資源須要先創建到內核地址空間的映射:
our_card->regs = ioremap(S3C2410_PA_IIS, 0x100);
if (our_card->regs == NULL) {
err = -ENXIO;
goto exit_err;
}
建立好了以後,咱們就能夠經過readl(our_card->regs )或writel(value, our_card->regs)等IO接口函數去訪問它。
2、靜態映射(map_desc)方式
下面重點介紹靜態映射方式即經過map_desc結構體靜態建立I/O資源映射表。
內核提供了在系統啓動時經過map_desc結構體靜態建立I/O資源到內核地址空間的線性映射表(即page table)的方式,這種映射表是一種一一映射的關係。程序員能夠本身定義該I/O內存資源映射後的虛擬地址。建立好了靜態映射表,在內核或驅動中訪問該I/O資源時則無需再進行ioreamp動態映射,能夠直接經過映射後的I/O虛擬地址去訪問它。
下面詳細分析這種機制的原理並舉例說明如何經過這種靜態映射的方式訪問外設I/O內存資源。
內核提供了一個重要的結構體struct machine_desc ,這個結構體在內核移植中起到至關重要的做用,內核經過machine_desc結構體來控制系統體系架構相關部分的初始化。
machine_desc結構體的成員包含了體系架構相關部分的幾個最重要的初始化函數,包括map_io, init_irq, init_machine以及phys_io , timer成員等。
machine_desc結構體定義以下:
|
這裏的map_io成員即內核提供給用戶的建立外設I/O資源到內核虛擬地址靜態映射表的接口函數。Map_io成員函數會在系統初始化過程當中被調用,流程以下:
Start_kernel -> setup_arch() --> paging_init() --> devicemaps_init()中被調用
Machine_desc結構體經過MACHINE_START宏來初始化。
注:MACHINE_START的使用及各個成員函數的調用過程請參考:
http://blog.chinaunix.net/u2/60011/showart_1010489.html
用戶能夠在定義Machine_desc結構體時指定Map_io的接口函數,這裏以s3c2410平臺爲例。
s3c2410 machine_desc結構體定義以下:
|
如上,map_io被初始化爲smdk2410_map_io。smdk2410_map_io即咱們本身定義的建立靜態I/O映射表的函數。在Porting內核到新開發板時,這個函數須要咱們本身實現。
(注:這個函數一般狀況下能夠實現得很簡單,只要直接調用iotable_init建立映射表就好了,咱們的板子內核就是。不過s3c2410平臺這個函數實現得稍微有點複雜,主要是由於它將要建立IO映射表的資源分爲了三個部分(smdk2410_iodesc, s3c_iodesc以及s3c2410_iodesc)在不一樣階段分別建立。這裏咱們取其中一個部分進行分析,不影響對整個概念的理解。)
S3c2410平臺的smdk2410_map_io函數最終會調用到s3c2410_map_io函數。
流程以下:s3c2410_map_io -> s3c24xx_init_io -> s3c2410_map_io
下面分析一下s3c2410_map_io函數:
|
iotable_init內核提供,定義以下:
|
由上知道,s3c2410_map_io最終調用iotable_init創建映射表。
iotable_init函數的參數有兩個:一個是map_desc類型的結構體,另外一個是該結構體的數量nr。這裏最關鍵的就是struct map_desc。map_desc結構體定義以下:
|
create_mapping函數就是經過map_desc提供的信息建立線性映射表的。
這樣的話咱們就知道了建立I/O映射表的大體流程爲:只要定義相應I/O資源的map_desc結構體,並將該結構體傳給iotable_init函數執行,就能夠建立相應的I/O資源到內核虛擬地址空間的映射表了。
咱們來看看s3c2410是怎麼定義map_desc結構體的(即上面s3c2410_map_io函數內的s3c2410_iodesc)。
|
IODESC_ENT宏以下:
#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x, MT_DEVICE }
展開後等價於:
|
S3C24XX_PA_ LCD和S3C24XX_VA_ LCD爲定義在map.h內的LCD寄存器的物理地址和虛擬地址。在這裏map_desc 結構體的virtual成員被初始化爲S3C24XX_VA_ LCD,pfn成員值經過__phys_to_pfn內核函數計算,只須要傳遞給它該I/O資源的物理地址就行。Length爲映射資源的大小。MT_DEVICE爲I/O類型,一般定義爲MT_DEVICE。
這裏最重要的即virtual 成員的值S3C24XX_VA_ LCD,這個值即該I/O資源映射後的內核虛擬地址,建立映射表成功後,即可以在內核或驅動中直接經過該虛擬地址訪問這個I/O資源。
S3C24XX_VA_ LCD以及S3C24XX_PA_ LCD定義以下:
/* include/asm-arm/arch-s3c2410/map.h */
/* LCD controller */
#define S3C24XX_VA_LCD S3C2410_ADDR(0x00600000) //LCD映射後的虛擬地址
#define S3C2410_PA_LCD (0x4D000000) //LCD寄存器物理地址
#define S3C24XX_SZ_LCD SZ_1M //LCD寄存器大小
S3C2410_ADDR 定義以下:
#define S3C2410_ADDR(x) ((void __iomem *)0xF0000000 + (x))
這裏就是一種線性偏移關係,即s3c2410建立的I/O靜態映射表會被映射到0xF0000000以後。(這個線性偏移值能夠改,也能夠你本身在virtual成員裏手動定義一個值,只要不和其餘IO資源映射地址衝突,但最好是在0XF0000000以後。)
(注:其實這裏S3C2410_ADDR的線性偏移只是s3c2410平臺的一種作法,不少其餘ARM平臺採用了通用的IO_ADDRESS宏來計算物理地址到虛擬地址以前的偏移。
IO_ADDRESS宏定義以下:
/* include/asm/arch-versatile/hardware.h */
/* macro to get at IO space when running virtually */
#define IO_ADDRESS(x) (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000) )
s3c2410_iodesc這個映射表創建成功後,咱們在內核中即可以直接經過S3C24XX_VA_ LCD訪問LCD的寄存器資源。
如:S3c2410 lcd驅動的probe函數內
|
S3C2410_LCDCON1寄存器地址爲相對於S3C24XX_VA_LCD偏移的一個地址,定義以下:
/* include/asm/arch-s3c2410/regs-lcd.h */
#define S3C2410_LCDREG(x) ((x) + S3C24XX_VA_LCD)
/* LCD control registers */
#define S3C2410_LCDCON1 S3C2410_LCDREG(0x00)
到此,咱們知道了經過map_desc結構體建立I/O內存資源靜態映射表的原理了。總結一下發現其實過程很簡單,一經過定義map_desc結構體建立靜態映射表,二在內核中經過建立映射後虛擬地址訪問該IO資源。
3、I/O靜態映射方式應用實例
I/O靜態映射方式一般是用在寄存器資源的映射上,這樣在編寫內核代碼或驅動時就不須要再進行ioremap,直接使用映射後的內核虛擬地址訪問。一樣的IO資源只須要在內核初始化過程當中映射一次,之後就能夠一直使用。
寄存器資源映射的例子上面講原理時已經介紹得很清楚了,這裏我舉一個SRAM的實例介紹如何應用這種I/O靜態映射方式。固然原理和操做過程同寄存器資源是同樣的,能夠把SRAM當作是大號的I/O寄存器資源。
好比個人板子在0x30000000位置有一塊64KB大小的SRAM。咱們如今須要經過靜態映射的方式去訪問該SRAM。咱們要作的事內容包括修改kernel代碼,添加SRAM資源相應的map_desc結構,建立sram到內核地址空間的靜態映射表。寫一個Sram Module,在Sram Module 內直接經過靜態映射後的內核虛擬地址訪問該sram。
第一步:建立SRAM靜態映射表
在我板子的map_des結構體數組(xxx_io_desc)內添加SRAM資源相應的map_desc。以下:
|
宏XXX_SRAM_BASE爲我板子上SRAM的物理地址,定義爲0x30000000。個人kernel是經過IO_ADDRESS的方式計算內核虛擬地址的,這點和以前介紹的S3c2410有點不同。不過原理都是相同的,爲一個線性偏移, 範圍在0xF0000000以後。
第二步:寫個SRAM Module,在Module中經過映射後的虛擬地址直接訪問該SRAM資源
SRAM Module代碼以下:
|
在開發板上運行結果以下:
/ # insmod bin/sram.ko
Request SRAM mem region ............
Hello,sram! ß 這句即打印的SRAM內的字符串
/ # rmmod sram
Release SRAM mem region success!
SRAM is close
實驗發現能夠經過映射後的地址正常訪問SRAM。
最後,這裏舉SRAM做爲例子的還有一個緣由是經過靜態映射方式訪問SRAM的話,咱們能夠預先知道SRAM映射後的內核虛擬地址(經過IOADDRESS計算)。這樣的話就能夠嘗試在SRAM上作點文章。好比寫個內存分配的MODULE管理SRAM或者其餘方式,將一些critical的數據放在SRAM內運行,這樣能夠提升一些複雜程序的運行效率(SRAM速度比SDRAM快多了),好比音視頻的編解碼過程當中用到的較大的buffer等。