在嵌入式开发中,所有芯片对外设进行处理都是通过读写设备上的寄存器进行的。外设的寄存器在内存中单独分出一部分作为特殊功能寄存器进行编址。在低级嵌入式设备中,我们通过直接操作外设寄存器即可控制外设的工作。在高级设备中,设备加载了操作系统,操作系统中的内存管理单元(MMU)对设备内存进行重新管理,从而无法直接进行操作。目前市面上,根据不同CPU体系架构,CPU对外设端口的编址方式一般有两种:IO映射方式(IO mapped)和内存映射方式(memory mapped)。
IO映射方式,主要指外设地址空间和内存地址空间是独立开的,根据不同的访问指令进行对应操作。典型的如x86处理器的设备。
图 1 IO映射方式
内存映射方式,主要指内存地址空间和外设地址空间的访问时一样的。只是访问的地址不同。典型的如RISC指令系统的嵌入式设备CPU(如ARM、PowerPC等)。
图 2 内存映射方式
通常情况,外设的IO内存资源的物理地址在硬件设计的时候已经确定,并且该物理地址是已知的。但是操作系统通过内存管理单元(MMU)管理系统的虚拟地址到物理地址的访问。在设备运行时,对于驱动开发人员来说,可以被直接用于编程的是虚拟地址,开发人员需要使用内存映射机制将物理地址映射到虚拟地址,从而通过虚拟地址访问外设IO。
Linux内核源码中io.h文件声明了函数ioremap()和iounmap(),该函数主要用于将IO内存资源的物理地址到虚拟地址的映射和解除映射。
// 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) iounmap函数用于解除ioremap()所做的映射,原型如下: void iounmap(void * addr);
在将I/O外设的寄存器物理地址映射成虚拟地址后,我们就可以象读写内存那样直接对I/O内存资源进行读写了。Linux内核为了保证驱动程序的跨平台的可移植性,提供了特定的读写函数来访问I/O内存资源。
读写I/O的函数如下所示:
// IO内存读取函数,参数p为读取的虚拟地址。分别可以读取8位、16位和32位 #define ioread8(p) ({ unsigned int __v = __raw_readb(p); __iormb(); __v; }) #define ioread16(p) ({ unsigned int __v = le16_to_cpu((__force __le16)__raw_readw(p)); __iormb(); __v; }) #define ioread32(p) ({ unsigned int __v = le32_to_cpu((__force __le32)__raw_readl(p)); __iormb(); __v; }) // IO内存写入函数,参数p为写入的虚拟地址,v为写入值。分别可以写入8位、16位和32位 #define iowrite8(v,p) ({ __iowmb(); __raw_writeb(v, p); }) #define iowrite16(v,p) ({ __iowmb(); __raw_writew((__force __u16)cpu_to_le16(v), p); }) #define iowrite32(v,p) ({ __iowmb(); __raw_writel((__force __u32)cpu_to_le32(v), p); })
以下示例程序以tiny4412开发板为例,对开发板LED灯进行IO控制。
#include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/sched.h> #include <asm/io.h> MODULE_LICENSE("GPL"); // 内核IO映射后的虚拟地址 unsigned int virt ; int test_init() { // 映射的物理地址范围:0x11000 000 - 0x11000 fff virt = ioremap( 0x11000000, 4096 ); // 通过指针读写内存 // unsigned int *gpm4con = (unsigned int *)(virt + 0x02e0); // unsigned int *gpm4dat = (unsigned int *)(virt + 0x02e4); // *gpm4con = 0x1111; // *gpm4dat &= ~0xf; // *gpm4dat |= 0xf; // 通过IO函数操作 iowrite32( 0x1111, virt + 0x02e0 ); unsigned int val = ioread32( virt + 0x02e4 ); iowrite32( val | 0xf , virt +0x02e4 ); return 0; } void test_exit() { iounmap( virt ); } module_init( test_init ); module_exit( test_exit );