一、相关概念
使用IO内存将物理地址映射为虚拟地址,再通过对虚拟地址的操作来控制硬件。所谓的IO内存是指一种编址方式,不同cpu平台使用的编址方式不同,一种是“IO内存”方式,也叫统一编址方式,是指内存和外设的地址是在同一个地址空间上的,如:ARM、powerpc 、MIPS等平台;另一种是“IO端口”方式,也叫独立编址方式,是指内存的地址空间和外设的地址空间是分开的,如x86平台。在嵌入式系统中,可以通过查看两个文件中的内容来确认是哪一种编址方式:/proc/iomem 和/proc/ioports,哪一个文件有内容就表示该平台使用哪一种编址方式。
二、程序设计思路
驱动安装—>申请IO内存资源—>物理地址的映射—>得到虚拟地址—>访问虚拟地址—>控制外设硬件
驱动卸载—>解除地址映射—>释放IO内存资源
用到的函数有:
request_mem_region()、ioremap()、release_mem_region()、iounmap()
三、程序设计与分析
1)申请IO内存资源
struct resource * request_mem_region( resource_size_t start, resource_size_t n, const char *name)
参数分析:
resource_size_t start:IO内存的开始地址,该地址是物理地址;
resource_size_t n:要申请的IO内存资源的大小,单位为字节;
const char *name:给所申请的IP内存资源取一个名字;
返回值:
申请成功,返回一个struct resource类型的指针;
申请失败,返回NULL;
2)释放IO内存资源
void release_mem_region(resource_size_t start, resource_size_t n) 参数分析: resource_size_t start:IO内存的开始地址,该地址是物理地址; resource_size_t n:要申请的IO内存资源的大小,单位为字节;
3)IO内存映射(物理地址—>虚拟地址)
void __iomem *ioremap(phys_addr_t offset, unsigned long size)
参数分析:
phys_addr_t offset:准备映射的物理地址空间的开始地址
unsigned long size:要映射的物理地址空间的大小,单位为字节
返回值:
映射成功,返回一个虚拟地址
映射失败,返回NULL;
4)IO内存映射的解除
void iounmap(volatile void __iomem *addr);
参数分析:
volatile void __iomem *addr:前面映射得到的虚拟地址
四、简单的实例
简单的LED驱动,实现对基于S5PV210平台的LED灯控制,所用寄存器:GPJ2CON(0xe0200280)、GPJ2DAT(0xe0200284),主要代码如下:
static int * GPJ2CON_VA; /*定义全局变量*/
static int * GPJ2DAT_VA;
static struct resource * gpio_led_source;
.....
在驱动的初始化函数static int __init leddev_init(void)中添加相关代码:
gpio_led_source = request_mem_region(0xe0200280, 8, “GPIO_LED”);
if(gpio_led_source == NULL)
{
printk("led_request_mem_region error!\n");
goto err_led_request_mem_region; /*出错处理*/
}
GPJ2CON_VA = ioremap(0xe0200280, 8);
if(GPJ2CON_VA == NULL)
{
printk("led ioremap error!\n");
goto err_led_ioremap; /*出错处理*/
}
GPJ2DAT_VA = GPJ2CON_VA + 1; /* GPJ2CON和GPJ2DAT的地址刚好相邻*/
*GPJ2CON_VA &= ~0xffff; /*initial led GPIO*/
*GPJ2CON_VA |= 0x1111;
*GPJ2DAT_VA |= 0xf;
printk("GPIO initial success!\n");
在驱动的退出函数static int __exit leddev_exit(void)中:
iounmap(GPJ2CON_VA); /*解除IO映射*/
release_mem_region(0xe0200280, 8); /*释放IO内存资源*/
unregister_chrdev_region(MKDEV(leddev_Major, leddev_Minor), 1); /*注销设备号*/
cdev_del(&ledodev); /*从内核中删除设备 */