No.107312 作者:edu118gct 邮件:754634522@qq.com ID:149414 登陆:8次 文章数:39篇 最后登陆IP:202.101.230.141 最后登陆:2014/12/11 11:38:07 注册:2014/3/26 10:04:47 财富:303 发帖时间:2014/7/17 15:06:52 发贴者IP:14.154.149.88 标题:edu118gct:[新手引导]linux系统内核中ioremap映射分析 摘要:No.107312[新手引导]linux系统内核中ioremap映射分析 几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据CPU体系结构的不同,CPU对IO端口的编址方式有两种: (1)I/O映射方式(I/O-mapped) 典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。 (2)内存映射方式(Memory-mapped) RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。 但是,这两者在硬件实现上的差异对于软件来说是完全透明的,驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是"I/O内存"资源。 一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源(也即是我们可以像读写RAM那样直接读写I/O内存(外设寄存器)资源了)。 为了配置寄存器,我们需要知道寄存器在操作系统中的虚拟地址,因为驱动中要使用的是虚拟地址而非物理地址。在s3c2410中获得GPIO各个寄存器的方法有两种。 第一种是静态映射法:使用系统初始化已经设置好的虚拟地址,这种方法在操作系统启动过程中,页表已经生成,可以直接使用,这时候可以使用内核导出函数在arch/arm/plat-s3c24xx/Gpio.c s3c2410_gpio_cfgpin s3c2410_gpio_getcfg s3c2410_gpio_setpin s3c2410_gpio_getpin等函数配置寄存器GPIO引脚功能 以setpin函数为例: void s3c2410_gpio_setpin(unsigned int pin, unsigned int to) { void __iomem *base = S3C24XX_GPIO_BASE(pin); /*算出端口所在组虚拟基址*/ unsigned long offs = S3C2410_GPIO_OFFSET(pin); /*算出端口所在组的偏移量(0-31)*/ unsigned long flags; unsigned long dat; local_irq_save(flags); dat = __raw_readl(base + 0x04); /*基址 + 0x04为DAT寄存器 */ dat &= ~(1 < < offs); dat |= to < < offs; __raw_writel(dat, base + 0x04); local_irq_restore(flags); } 分析代码: void __iomem *base = S3C24XX_GPIO_BASE(pin); //定义一个指向所要配置的控制寄存器的基地址的指针,并赋初值(是个虚拟地址) /* #define S3C24XX_GPIO_BASE(x) S3C2410_GPIO_BASE(x) #define S3C2410_GPIO_BASE(pin) ((((pin)&~31)> > 1)+ S3C24XX_VA_GPIO) #define S3C24XX_VA_GPIO ((S3C24XX_PA_GPIO-S3C24XX_PA_UART)+ S3C24XX_VA_UART) #define S3C24XX_VA_UART S3C_VA_UART #define S3C_VA_UART S3C_ADDR(0x01000000) #define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x)) #define S3C_ADDR_BASE (0xF4000000) */ __iomem是linux2.6.9内核中加入的特性。是用来个表示指针是指向一个I/O的内存空间。主要是为了驱动程序的通用性考虑。由于不同的CPU体系结构对I/O空间的表示可能不同。当使用__iomem时,编译器会忽略对变量的检查(因为用的是void __iomem)。若要对它进行检查,当__iomem的指针和正常的指针混用时,就会发出一些警告。 dat = __raw_readl(base + 0x04);//取原寄存器的值初始化dat dat &= ~(1 < < offs); //掩码运算,清零对应位 dat |= to < < offs; //功能码生成,配置对应位的值 __raw_writel(dat, base + 0x04); //写入控制寄存器 __raw_readl和__raw_writel: Linux对I/O的操作都定义在asm/io.h中,相应的在arm平台下,就在asm-arm/io.h中。 #define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v)) #define __raw_readl(a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a)) 在include\linux\compiler.h中: #ifdef __CHECKER__ …… extern void __chk_io_ptr(void __iomem *); #else …… # define __chk_io_ptr(x) (void)0 …… #endif __raw_readl(a)展开是:((void)0, *(volatile unsigned int _force *)(a))。在定义了__CHECKER__的时候先调用__chk_io_ptr检查该地址,否则__chk_io_ptr什么也不做,* (volatile unsigned int _force *)(a)就是返回地址为a处的值。(void)xx的做法有时候是有用的,例如编译器打开了检查未使用的参数的时候需要将没有用到的参数这么弄一下才能 编译通过。CPU对I/O的物理地址的编程方式有两种:一种是I/O映射,一种是内存映射。 __raw_readl和__raw_writel等是原始的操作I/O的方法,由此派生出来的操作方法有:inb、outb、 _memcpy_fromio、readb、writeb、ioread8、iowrite8等。 静态映射方式下虚拟地址的建立是由操作系统完成的,不需要用户进行干涉。在arch/arm/mach-s3c2440/mach-mini2440.c中,其中包含了io端口虚拟地址到物理地址映射的初始化函数: MACHINE_START(S3C2440, "s3cARM s3c2440 development board") 。。。。。 .map_io = s3c2440_map_io, 。。。。。 MACHINE_END static void __init s3c2440_map_io(void) { /*该函数完成静态的物理地址到虚拟地址的转换*/ s3c24xx_init_io(s3c2440_iodesc, ARRAY_SIZE(s3c2440_iodesc)); s3c24xx_init_clocks(12000000); s3c24xx_init_uarts(s3c2440_uartcfgs, ARRAY_SIZE(s3c2440_uartcfgs)); } 第二种是动态映射法,通过ioremap函数获得相应的虚拟地址。Linux在io.h头文件中声明了函数ioremap(),用来将I/ ......
>>返回讨论的主题
|