天天看點

ioremap,你應該知道的事

因為現在使用是dts來表示闆級,也就是machine,是以現在我們在核心使用核心映射使用的函數是of_iomap。

c代碼:

struct device_node *node = NULL;
 unsigned int irq_info[3] = { 0, 0, 0 };
 u32 phys_base;

 switch (idx) {
 case 0:
  node = of_find_node_by_name(NULL, "uart0");
  break;
 case 1:
  node = of_find_node_by_name(NULL, "uart1");
  break;
 case 2:
  node = of_find_node_by_name(NULL, "uart2");
  break;
 default:
  break;
 }

 if (node) {
  /* iomap registers */
  mtk_uart_default_settings[idx].uart_base = (unsigned long)of_iomap(node, 0);
  /* get IRQ ID */
  mtk_uart_default_settings[idx].irq_num = irq_of_parse_and_map(node, 0);
 }      

dts代碼:

uart0: serial@11002000 {
        compatible = "mediatek,mt8127-uart","mediatek,mt6577-uart";
        reg = <0 0x11002000 0 0x400>;
        interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_LOW>;
        clocks = <&uart_clk>;
        status = "disabled";
};      

重點

看到一篇寫iomap非常不錯的文章,轉載分享給大家看看,這個文章寫的比較久了,我覺得現在是有借鑒意義的。

轉自:

​​

​​

​​

ioremap,你應該知道的事

我們知道預設外設I/O資源是不在Linux核心空間中的(如sram或硬體接口寄存器等),若需要通路該外設I/O資源,必須先将其位址映射到核心空間中來,然後才能在核心空間中通路它。

Linux核心通路外設I/O記憶體資源的方式有兩種:動态映射(ioremap)和靜态映射(map_desc)。

一、動态映射(ioremap)方式

動态映射方式是大家使用了比較多的,也比較簡單。即直接通過核心提供的ioremap函數動态建立一段外設I/O記憶體資源到核心虛拟位址的映射表,進而可以在核心空間中通路這段I/O資源。

Ioremap宏定義在asm/io.h内:

#define ioremap(cookie,size)           __ioremap(cookie,size,0)



__ioremap函數原型為(arm/mm/ioremap.c):

void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);      
  • phys_addr:要映射的起始的IO位址
  • size:要映射的空間的大小
  • 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接口函數去通路它。

二、靜态映射(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結構體定義如下:

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成員即核心提供給使用者的建立外設I/O資源到核心虛拟位址靜态映射表的接口函數。Map_io成員函數會在系統初始化過程中被調用,流程如下:

Start_kernel -> setup_arch() --> paging_init() --> devicemaps_init()中被調用      

Machine_desc結構體通過MACHINE_START宏來初始化。

注:MACHINE_START的使用及各個成員函數的調用過程請參考:

​​

​​      

​​

使用者可以在定義Machine_desc結構體時指定Map_io的接口函數,這裡以s3c2410平台為例。

s3c2410 machine_desc結構體定義如下:

/* 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即我們自己定義的建立靜态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函數:

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核心提供,定義如下:

/*
 * 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最終調用iotable_init建立映射表。

iotable_init函數的參數有兩個:一個是map_desc類型的結構體,另一個是該結構體的數量nr。這裡最關鍵的就是struct map_desc。map_desc結構體定義如下:

/* include/asm-arm/mach/map.h */
struct map_desc {
    unsigned long virtual;    /* 映射後的虛拟位址 */
    unsigned long pfn;        /* I/O資源實體位址所在的頁幀号 */
    unsigned long length;    /* I/O資源長度 */
    unsigned int type;        /* I/O資源類型 */
};      

create_mapping函數就是通過map_desc提供的資訊建立線性映射表的。

這樣的話我們就知道了建立I/O映射表的大緻流程為:隻要定義相應I/O資源的map_desc結構體,并将該結構體傳給iotable_init函數執行,就可以建立相應的I/O資源到核心虛拟位址空間的映射表了。

我們來看看s3c2410是怎麼定義map_desc結構體的(即上面s3c2410_map_io函數内的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 }      

展開後等價于:

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内的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函數内

/* 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寄存器位址為相對于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資源。

三、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。如下:

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是通過IO_ADDRESS的方式計算核心虛拟位址的,這點和之前介紹的S3c2410有點不一樣。不過原理都是相同的,為一個線性偏移, 範圍在0xF0000000之後。

第二步:寫個SRAM Module,在Module中通過映射後的虛拟位址直接通路該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); /* 通過IO_ADDRESS宏得到SRAM映射後的虛拟位址 */
    memcpy(sram_p, str, sizeof(str));    //将 str字元數組拷貝到sram内
    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);      

在開發闆上運作結果如下:

/ # 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等。

關注公衆号,背景回複「​1024​」擷取學習資料網盤連結。

​歡迎點贊,關注,轉發,您的每一次鼓勵,我都将銘記于心~​

嵌入式Linux

繼續閱讀