ioremap,你应该知道的事
因?yàn)楝F(xiàn)在使用是dts來(lái)表示板級(jí),也就是machine,所以現(xiàn)在我們?cè)趦?nèi)核使用內(nèi)核映射使用的函數(shù)是of_iomap。
c代碼:
dts代碼:
重點(diǎn)
看到一篇寫(xiě)iomap非常不錯(cuò)的文章,轉(zhuǎn)載分享給大家看看,這個(gè)文章寫(xiě)的比較久了,我覺(jué)得現(xiàn)在是有借鑒意義的。
轉(zhuǎn)自:
https://blog.csdn.net/dinuliang/article/details/5823937
我們知道默認(rèn)外設(shè)I/O資源是不在Linux內(nèi)核空間中的(如sram或硬件接口寄存器等),若需要訪問(wèn)該外設(shè)I/O資源,必須先將其地址映射到內(nèi)核空間中來(lái),然后才能在內(nèi)核空間中訪問(wèn)它。
Linux內(nèi)核訪問(wèn)外設(shè)I/O內(nèi)存資源的方式有兩種:動(dòng)態(tài)映射(ioremap)和靜態(tài)映射(map_desc)。
一、動(dòng)態(tài)映射(ioremap)方式
動(dòng)態(tài)映射方式是大家使用了比較多的,也比較簡(jiǎn)單。即直接通過(guò)內(nèi)核提供的ioremap函數(shù)動(dòng)態(tài)創(chuàng)建一段外設(shè)I/O內(nèi)存資源到內(nèi)核虛擬地址的映射表,從而可以在內(nèi)核空間中訪問(wèn)這段I/O資源。
Ioremap宏定義在asm/io.h內(nèi):
#define?ioremap(cookie,size)???????????__ioremap(cookie,size,0)__ioremap函數(shù)原型為(arm/mm/ioremap.c):void?__iomem?*?__ioremap(unsigned?long?phys_addr,?size_t?size,?unsigned?long?flags);phys_addr:要映射的起始的IO地址
size:要映射的空間的大小
flags:要映射的IO空間和權(quán)限有關(guān)的標(biāo)志
該函數(shù)返回映射后的內(nèi)核虛擬地址(3G-4G). 接著便可以通過(guò)讀寫(xiě)該返回的內(nèi)核虛擬地址去訪問(wèn)之這段I/O內(nèi)存資源。
舉一個(gè)簡(jiǎn)單的例子: (取自s3c2410的iis音頻驅(qū)動(dòng))
比如我們要訪問(wèn)s3c2410平臺(tái)上的I2S寄存器, 查看datasheet 知道IIS物理地址為0x55000000,我們把它定義為宏S3C2410_PA_IIS,如下:
#define?S3C2410_PA_IIS????(0x55000000)若要在內(nèi)核空間(iis驅(qū)動(dòng))中訪問(wèn)這段I/O寄存器(IIS)資源需要先建立到內(nèi)核地址空間的映射:
our_card->regs?=?ioremap(S3C2410_PA_IIS,?0x100);if?(our_card->regs?==?NULL)?{err?=?-ENXIO;goto?exit_err;}創(chuàng)建好了之后,我們就可以通過(guò)readl(our_card->regs )或writel(value, our_card->regs)等IO接口函數(shù)去訪問(wèn)它。
二、靜態(tài)映射(map_desc)方式
下面重點(diǎn)介紹靜態(tài)映射方式即通過(guò)map_desc結(jié)構(gòu)體靜態(tài)創(chuàng)建I/O資源映射表。
內(nèi)核提供了在系統(tǒng)啟動(dòng)時(shí)通過(guò)map_desc結(jié)構(gòu)體靜態(tài)創(chuàng)建I/O資源到內(nèi)核地址空間的線性映射表(即page table)的方式,這種映射表是一種一一映射的關(guān)系。程序員可以自己定義該I/O內(nèi)存資源映射后的虛擬地址。創(chuàng)建好了靜態(tài)映射表,在內(nèi)核或驅(qū)動(dòng)中訪問(wèn)該I/O資源時(shí)則無(wú)需再進(jìn)行ioreamp動(dòng)態(tài)映射,可以直接通過(guò)映射后的I/O虛擬地址去訪問(wèn)它。
下面詳細(xì)分析這種機(jī)制的原理并舉例說(shuō)明如何通過(guò)這種靜態(tài)映射的方式訪問(wèn)外設(shè)I/O內(nèi)存資源。
內(nèi)核提供了一個(gè)重要的結(jié)構(gòu)體struct machine_desc ,這個(gè)結(jié)構(gòu)體在內(nèi)核移植中起到相當(dāng)重要的作用,內(nèi)核通過(guò)machine_desc結(jié)構(gòu)體來(lái)控制系統(tǒng)體系架構(gòu)相關(guān)部分的初始化。
machine_desc結(jié)構(gòu)體的成員包含了體系架構(gòu)相關(guān)部分的幾個(gè)最重要的初始化函數(shù),包括map_io, init_irq, init_machine以及phys_io , timer成員等。
machine_desc結(jié)構(gòu)體定義如下:
struct?machine_desc?{/**?Note!?The?first?four?elements?are?used*?by?assembler?code?in?head-armv.S*/unsigned?int????????nr;????????/*?architecture?number????*/unsigned?int????????phys_io;????/*?start?of?physical?io????*/unsigned?int????????io_pg_offst;????/*?byte?offset?for?io?*?page?tabe?entry????*/const?char????????*name;????????/*?architecture?name????*/unsigned?long????????boot_params;????/*?tagged?list????????*/unsigned?int????????video_start;????/*?start?of?video?RAM????*/unsigned?int????????video_end;????/*?end?of?video?RAM????*/unsigned?int????????reserve_lp0?:1;????/*?never?has?lp0????*/unsigned?int????????reserve_lp1?:1;????/*?never?has?lp1????*/unsigned?int????????reserve_lp2?:1;????/*?never?has?lp2????*/unsigned?int????????soft_reboot?:1;????/*?soft?reboot????????*/void????????????(*fixup)(struct?machine_desc?*,struct?tag?*,?char?**,struct?meminfo?*);void????????????(*map_io)(void);/*?IO?mapping?function????*/void????????????(*init_irq)(void);struct?sys_timer????*timer;????????/*?system?tick?timer????*/void????????????(*init_machine)(void); };這里的map_io成員即內(nèi)核提供給用戶(hù)的創(chuàng)建外設(shè)I/O資源到內(nèi)核虛擬地址靜態(tài)映射表的接口函數(shù)。Map_io成員函數(shù)會(huì)在系統(tǒng)初始化過(guò)程中被調(diào)用,流程如下:
Start_kernel?->?setup_arch()?-->?paging_init()?-->?devicemaps_init()中被調(diào)用Machine_desc結(jié)構(gòu)體通過(guò)MACHINE_START宏來(lái)初始化。
注:MACHINE_START的使用及各個(gè)成員函數(shù)的調(diào)用過(guò)程請(qǐng)參考:
http://blog.chinaunix.net/u2/60011/showart_1010489.html用戶(hù)可以在定義Machine_desc結(jié)構(gòu)體時(shí)指定Map_io的接口函數(shù),這里以s3c2410平臺(tái)為例。
s3c2410 machine_desc結(jié)構(gòu)體定義如下:
/*?arch/arm/mach-s3c2410/Mach-smdk2410.c?*/ MACHINE_START(SMDK2410,?"SMDK2410")?/*?@TODO:?request?a?new?identifier?and?switch*?to?SMDK2410?*//*?Maintainer:?Jonas?Dietsche?*/.phys_io????=?S3C2410_PA_UART,.io_pg_offst????=?(((u32)S3C24XX_VA_UART)?>>?18)?&?0xfffc,.boot_params????=?S3C2410_SDRAM_PA?+?0x100,.map_io????????=?smdk2410_map_io,.init_irq????=?s3c24xx_init_irq,.init_machine????=?smdk2410_init,.timer????????=?&s3c24xx_timer, MACHINE_END如上,map_io被初始化為smdk2410_map_io。smdk2410_map_io即我們自己定義的創(chuàng)建靜態(tài)I/O映射表的函數(shù)。在Porting內(nèi)核到新開(kāi)發(fā)板時(shí),這個(gè)函數(shù)需要我們自己實(shí)現(xiàn)。
(注:這個(gè)函數(shù)通常情況下可以實(shí)現(xiàn)得很簡(jiǎn)單,只要直接調(diào)用iotable_init創(chuàng)建映射表就行了,我們的板子內(nèi)核就是。不過(guò)s3c2410平臺(tái)這個(gè)函數(shù)實(shí)現(xiàn)得稍微有點(diǎn)復(fù)雜,主要是因?yàn)樗鼘⒁獎(jiǎng)?chuàng)建IO映射表的資源分為了三個(gè)部分(smdk2410_iodesc, s3c_iodesc以及s3c2410_iodesc)在不同階段分別創(chuàng)建。這里我們?nèi)∑渲幸粋€(gè)部分進(jìn)行分析,不影響對(duì)整個(gè)概念的理解。)
S3c2410平臺(tái)的smdk2410_map_io函數(shù)最終會(huì)調(diào)用到s3c2410_map_io函數(shù)。
流程如下:
s3c2410_map_io?->?s3c24xx_init_io?->?s3c2410_map_io下面分析一下s3c2410_map_io函數(shù):
void?__init?s3c2410_map_io(struct?map_desc?*mach_desc,?int?mach_size) {/*?register?our?io-tables?*/iotable_init(s3c2410_iodesc,?ARRAY_SIZE(s3c2410_iodesc));…… }iotable_init內(nèi)核提供,定義如下:
/**?Create?the?architecture?specific?mappings*/ void?__init?iotable_init(struct?map_desc?*io_desc,?int?nr) {int?i;for?(i?=?0;?i?<?nr;?i++)create_mapping(io_desc?+?i); }由上知道,s3c2410_map_io最終調(diào)用iotable_init建立映射表。
iotable_init函數(shù)的參數(shù)有兩個(gè):一個(gè)是map_desc類(lèi)型的結(jié)構(gòu)體,另一個(gè)是該結(jié)構(gòu)體的數(shù)量nr。這里最關(guān)鍵的就是struct map_desc。map_desc結(jié)構(gòu)體定義如下:
/*?include/asm-arm/mach/map.h?*/ struct?map_desc?{unsigned?long?virtual;????/*?映射后的虛擬地址?*/unsigned?long?pfn;????????/*?I/O資源物理地址所在的頁(yè)幀號(hào)?*/unsigned?long?length;????/*?I/O資源長(zhǎng)度?*/unsigned?int?type;????????/*?I/O資源類(lèi)型?*/ };create_mapping函數(shù)就是通過(guò)map_desc提供的信息創(chuàng)建線性映射表的。
這樣的話(huà)我們就知道了創(chuàng)建I/O映射表的大致流程為:只要定義相應(yīng)I/O資源的map_desc結(jié)構(gòu)體,并將該結(jié)構(gòu)體傳給iotable_init函數(shù)執(zhí)行,就可以創(chuàng)建相應(yīng)的I/O資源到內(nèi)核虛擬地址空間的映射表了。
我們來(lái)看看s3c2410是怎么定義map_desc結(jié)構(gòu)體的(即上面s3c2410_map_io函數(shù)內(nèi)的s3c2410_iodesc)。
/*?arch/arm/mach-s3c2410/s3c2410.c?*/ static?struct?map_desc?s3c2410_iodesc[]?__initdata?=?{IODESC_ENT(USBHOST),IODESC_ENT(CLKPWR),IODESC_ENT(LCD),IODESC_ENT(TIMER),IODESC_ENT(ADC),IODESC_ENT(WATCHDOG), };IODESC_ENT宏如下:
#define?IODESC_ENT(x)?{?(unsigned?long)S3C24XX_VA_##x,?__phys_to_pfn(S3C24XX_PA_##x),?S3C24XX_SZ_##x,?MT_DEVICE?}展開(kāi)后等價(jià)于:
static?struct?map_desc?s3c2410_iodesc[]?__initdata?=?{{.virtual????=?????(unsigned?long)S3C24XX_VA_?LCD),.pfn????????=?????__phys_to_pfn(S3C24XX_PA_?LCD),.length????=????S3C24XX_SZ_?LCD,.type????=?????MT_DEVICE},…… };S3C24XX_PA_ LCD和S3C24XX_VA_ LCD為定義在map.h內(nèi)的LCD寄存器的物理地址和虛擬地址。在這里map_desc 結(jié)構(gòu)體的virtual成員被初始化為S3C24XX_VA_ LCD,pfn成員值通過(guò)__phys_to_pfn內(nèi)核函數(shù)計(jì)算,只需要傳遞給它該I/O資源的物理地址就行。Length為映射資源的大小。MT_DEVICE為I/O類(lèi)型,通常定義為MT_DEVICE。
這里最重要的即virtual 成員的值S3C24XX_VA_ LCD,這個(gè)值即該I/O資源映射后的內(nèi)核虛擬地址,創(chuàng)建映射表成功后,便可以在內(nèi)核或驅(qū)動(dòng)中直接通過(guò)該虛擬地址訪問(wèn)這個(gè)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))這里就是一種線性偏移關(guān)系,即s3c2410創(chuàng)建的I/O靜態(tài)映射表會(huì)被映射到0xF0000000之后。(這個(gè)線性偏移值可以改,也可以你自己在virtual成員里手動(dòng)定義一個(gè)值,只要不和其他IO資源映射地址沖突,但最好是在0XF0000000之后。)
(注:其實(shí)這里S3C2410_ADDR的線性偏移只是s3c2410平臺(tái)的一種做法,很多其他ARM平臺(tái)采用了通用的IO_ADDRESS宏來(lái)計(jì)算物理地址到虛擬地址之前的偏移。
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這個(gè)映射表建立成功后,我們?cè)趦?nèi)核中便可以直接通過(guò)S3C24XX_VA_ LCD訪問(wèn)LCD的寄存器資源。
如:S3c2410 lcd驅(qū)動(dòng)的probe函數(shù)內(nèi)
?/*?Stop?the?video?and?unset?ENVID?if?set?*/ info->regs.lcdcon1?&=?~S3C2410_LCDCON1_ENVID; lcdcon1?=?readl(S3C2410_LCDCON1);?//read映射后的寄存器虛擬地址 writel(lcdcon1?&?~S3C2410_LCDCON1_ENVID,?S3C2410_LCDCON1);?//write映射后的虛擬地址S3C2410_LCDCON1寄存器地址為相對(duì)于S3C24XX_VA_LCD偏移的一個(gè)地址,定義如下:
/*?include/asm/arch-s3c2410/regs-lcd.h?*/#define?S3C2410_LCDREG(x)?((x)?+?S3C24XX_VA_LCD)/*?LCD?control?registers?*/#define?S3C2410_LCDCON1????????S3C2410_LCDREG(0x00)到此,我們知道了通過(guò)map_desc結(jié)構(gòu)體創(chuàng)建I/O內(nèi)存資源靜態(tài)映射表的原理了。總結(jié)一下發(fā)現(xiàn)其實(shí)過(guò)程很簡(jiǎn)單,一通過(guò)定義map_desc結(jié)構(gòu)體創(chuàng)建靜態(tài)映射表,二在內(nèi)核中通過(guò)創(chuàng)建映射后虛擬地址訪問(wèn)該IO資源。
三、I/O靜態(tài)映射方式應(yīng)用實(shí)例
I/O靜態(tài)映射方式通常是用在寄存器資源的映射上,這樣在編寫(xiě)內(nèi)核代碼或驅(qū)動(dòng)時(shí)就不需要再進(jìn)行ioremap,直接使用映射后的內(nèi)核虛擬地址訪問(wèn)。同樣的IO資源只需要在內(nèi)核初始化過(guò)程中映射一次,以后就可以一直使用。
寄存器資源映射的例子上面講原理時(shí)已經(jīng)介紹得很清楚了,這里我舉一個(gè)SRAM的實(shí)例介紹如何應(yīng)用這種I/O靜態(tài)映射方式。當(dāng)然原理和操作過(guò)程同寄存器資源是一樣的,可以把SRAM看成是大號(hào)的I/O寄存器資源。
比如我的板子在0x30000000位置有一塊64KB大小的SRAM。我們現(xiàn)在需要通過(guò)靜態(tài)映射的方式去訪問(wèn)該SRAM。我們要做的事內(nèi)容包括修改kernel代碼,添加SRAM資源相應(yīng)的map_desc結(jié)構(gòu),創(chuàng)建sram到內(nèi)核地址空間的靜態(tài)映射表。寫(xiě)一個(gè)Sram Module,在Sram Module 內(nèi)直接通過(guò)靜態(tài)映射后的內(nèi)核虛擬地址訪問(wèn)該sram。
第一步:創(chuàng)建SRAM靜態(tài)映射表
在我板子的map_des結(jié)構(gòu)體數(shù)組(xxx_io_desc)內(nèi)添加SRAM資源相應(yīng)的map_desc。如下:
static?struct?map_desc?xxx_io_desc[]?__initdata?=?{{.virtual????=?IO_ADDRESS(XXX?_UART2_BASE),.pfn????????=?__phys_to_pfn(XXX?_UART2_BASE),.length????????=?SZ_4K,.type????????=?MT_DEVICE},{.virtual????=?IO_ADDRESS(XXX_SRAM_BASE),.pfn????????=?__phys_to_pfn(XXX_SRAM_BASE),.length????????=?SZ_4K,.type????????=?MT_DEVICE}, };宏XXX_SRAM_BASE為我板子上SRAM的物理地址,定義為0x30000000。我的kernel是通過(guò)IO_ADDRESS的方式計(jì)算內(nèi)核虛擬地址的,這點(diǎn)和之前介紹的S3c2410有點(diǎn)不一樣。不過(guò)原理都是相同的,為一個(gè)線性偏移, 范圍在0xF0000000之后。
第二步:寫(xiě)個(gè)SRAM Module,在Module中通過(guò)映射后的虛擬地址直接訪問(wèn)該SRAM資源
SRAM Module代碼如下:
/*?Sram?Testing?Module?*/ …… static?void?sram_test(void) {void?*?sram_p;char?str[]?=?"Hello,sram!/n";sram_p?=?(void?*)IO_ADDRESS?(XXX_SRAM_BASE);?/*?通過(guò)IO_ADDRESS宏得到SRAM映射后的虛擬地址?*/memcpy(sram_p,?str,?sizeof(str));????//將?str字符數(shù)組拷貝到sram內(nèi)printk(sram_p);printk("/n"); }static?int?__init?sram_init(void) {struct?resource?*?ret;printk("Request?SRAM?mem?region?............/n");ret?=?request_mem_region(SRAM_BASE,?SRAM_SIZE,?"SRAM?Region");if?(ret?==NULL)?{printk("Request?SRAM?mem?region?failed!/n");return?-1;}sram_test();return?0; }static?void?__exit?sram_exit(void) {release_mem_region(SRAM_BASE,?SRAM_SIZE);????printk("Release?SRAM?mem?region?success!/n");printk("SRAM?is?closed/n"); }module_init(sram_init); module_exit(sram_exit);在開(kāi)發(fā)板上運(yùn)行結(jié)果如下:
/ # insmod bin/sram.koRequest SRAM mem region ............Hello,sram! ? 這句即打印的SRAM內(nèi)的字符串/ # rmmod sramRelease SRAM mem region success!SRAM is close實(shí)驗(yàn)發(fā)現(xiàn)可以通過(guò)映射后的地址正常訪問(wèn)SRAM。
最后,這里舉SRAM作為例子的還有一個(gè)原因是通過(guò)靜態(tài)映射方式訪問(wèn)SRAM的話(huà),我們可以預(yù)先知道SRAM映射后的內(nèi)核虛擬地址(通過(guò)IOADDRESS計(jì)算)。這樣的話(huà)就可以嘗試在SRAM上做點(diǎn)文章。比如寫(xiě)個(gè)內(nèi)存分配的MODULE管理SRAM或者其他方式,將一些critical的數(shù)據(jù)放在SRAM內(nèi)運(yùn)行,這樣可以提高一些復(fù)雜程序的運(yùn)行效率(SRAM速度比SDRAM快多了),比如音視頻的編解碼過(guò)程中用到的較大的buffer等。
推薦閱讀:
專(zhuān)輯|Linux文章匯總
專(zhuān)輯|程序人生
專(zhuān)輯|C語(yǔ)言
我的知識(shí)小密圈
關(guān)注公眾號(hào),后臺(tái)回復(fù)「1024」獲取學(xué)習(xí)資料網(wǎng)盤(pán)鏈接。
歡迎點(diǎn)贊,關(guān)注,轉(zhuǎn)發(fā),在看,您的每一次鼓勵(lì),我都將銘記于心~
嵌入式Linux
微信掃描二維碼,關(guān)注我的公眾號(hào)
總結(jié)
以上是生活随笔為你收集整理的ioremap,你应该知道的事的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 让程序像人一样的去批量下载歌曲?Pyth
- 下一篇: 学了CPDA数据分析师认证课程对工作有什