io map

咱们知道默认外设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结构体定义以下:

 

 

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的使用及各个成员函数的调用过程请参考:

http://blog.chinaunix.net/u2/60011/showart_1010489.html

 

用户能够在定义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资源。

 

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。以下:

 

 

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

相关文章
相关标签/搜索