查看文章
 
Linux内核访问外设I/O资源的方式(2)
2009-03-24 16:35

我们来看看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_ LCDS3C24XX_VA_ LCD为定义在map.h内的LCD寄存器的物理地址和虚拟地址。在这里map_desc 结构体的virtual成员被初始化为S3C24XX_VA_ LCDpfn成员值通过__phys_to_pfn内核函数计算,只需要传递给它该I/O资源的物理地址就行。Length为映射资源的大小。MT_DEVICEI/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等。


类别:linux_os||添加到搜藏 |分享到i贴吧|浏览(584)|评论 (0)
 
最近读者:
 
网友评论:
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
     

   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu