第七章linux的I/O内存访问讲解

7.1内存空间和I/O空间

I/O空间是相对于内存空间而区分的,在x86处理器中存在I/O空间的概念,它们需要通过特定的In和Out指令来进行访问的。如下为Intel的In和Out指令格式:
IN 累加器, {端口号!DX}
OUT {端口号!DX},累加器
一般情况下,大部分的嵌入式处理器中不提供I/O空间,而仅仅提供内存空间,内存空间能够通过地址和指针来进行访问,例如程序中的变量是存在于内存空间的,同时我们通常是通过对IO内存的访问来与挂接到内存空间上外设通信。例如前面讲述裸机开发中的通过IO内存地址指针的操作来控制外围的LED灯的亮灭:
#define GPIOCOUT (*(volatile unsigned int *)0xC001C000)
//点亮
GPIOCOUT&=~(1<<17);
//熄灭
GPIOCOUT|=1<<17;

7.2内存管理单元MMU

目前大部分高性能的处理器一般都具有内存管理单元(MMU),例如x86和ARM9以上的CPU就具备MMU。MMU主要完成的任务:(1)管理虚拟寄存器和物理存储器的控制线路;(2)提供虚拟地址和物理地址的映射操作;(3)内存访问权限保护等。

程序员在使用了MMU的操作系统中编写程序,会感觉到程序可用的内存空间好像非常大,所以程序员在编写程序时就根本不用考虑系统的实际的物理内存了,给开发工作带来极大的便利。

在了解MMU的工作原理前需要对几个概念说明一下:

(1)TLB(转换旁路缓存):TLB是MMU的重要组成部分,主要负责缓存少量的虚拟地址和物理地址的转换对应关系,即转换表的Cache。

(2)TTW(转换表漫游):TTW的主要是在TLB没有缓存相应的地址转换关系信息时,通过对内存中转换表进行访问来获取虚拟地址和物理地址的转换关系,如下图7.1所示为转换表示意图。当TTW成功后,相应的虚拟地址和物理地址的转换关系信息将保存进TLB当中。
图7.1转换表示意图
如下图7.2所示为具备MMU的ARM CPU访问内存的过程:

(1) 当CPU没有使能MMU时,此时访问内存时是直接访问物理地址。

(2) 当CPU使能MMU时,访问内存时则需要经过MMU处理后才能够访问到内存数据。如果TLB中无虚拟地址入口时,转换表会遍历硬件从存放在主存储器中的转换表中获取相应的地址转换数据和访问的权限,即执行了TTW,TTW成功后就会将上述的数据信息写入TLB中。之后在TLB里存储的数据信息控制下,当满足相应的访问权限时,如果需要对实际的物理地址进行访问,此时访问的过程则实际发生在Cache或者内存中,如图7.2所示。
图7.2 ARM的CPU进行数据访问流程
MMU具备虚拟地址和物理地址相互转换、内存访问权限保护等功能,因此使得Linux操作系统能够单独的为系统不同的用户提供独立的内存空间同时能够保证用户空间不能够访问内核空间的地址,为操作系统的虚拟内存管理模块提供了必要的硬件基础。

7.2 LInux内存管理

对于具备了MMU的处理器而言,linux系统提供了复杂的存储管理系统,它能够使得进程能够访问到的内存可达4GB。

如下图7.3所示,进程的4GB内存可分为内核空间和用户空间两部分,用户空间地址通常为0~3GB,而内核空间地址则通常为3~4GB。同时用户进程一般只能够访问用户空间的虚拟地址,不能够直接访问内核空间的虚拟地址,如果用户进程需要访问内核空间的虚拟地址则需要通过系统调用等方式才能够进行访问。
图7.3用户空间和内存空间
在linux操作系统中为了实现不同的进程用户空间相互间独立,linux操作系统中不同的用户进程会拥有各自不同的页表来实现用户空间相互独立的效果。而对于内核空间而言,内核空间是由内核负责映射的,内核空间地址也具有自己独立的页表,但它是不随进程的改变而变化,内核的虚拟空间独立于其他程序。

如下图7.4所示为linux的1GB内核空间地址的分布示意图,内核空间一般会被分为物理内存映射区、虚拟内存映射区、高端内存映射区、专用页面映射区和保留区等几个区域。

物理内存映射区通常情况下最大是896MB,系统物理内存会被映射到内核空间的这个区域中,如果系统物理内存超过896MB时,此时超过的部分物理内存则需要映射到高端内存映射区中,此时如果内核在存取高端内存时则需要将它们映射到高端页面映射区中。

linux保留了内核空间最上层FIXADDR_TOP~4GB的区域作为保留区域。而紧接着下面的专用页面映射区(FIXADDR_START~FIXADDR_TOP)的总大小和每一页的使用用途都是由fixed_address枚举在编译是预定义好的。如下为开始地址和结束地址的宏定义:

#define FIXADDR_START (FIXADDR_TOP - __FIXADDR_SIZE)
#define FIXADDR_TOP ((unsigned long)__FIXADDR_TOP)
#define __FIXADDR_TOP 0xfffff000


图7.4内核地址空间分布

当系统的物理内存超过了4GB时,此时需要使用扩展分页模式所提供的64位页目录才能够存储大于4GB的物理内存,这个是需要CPU的硬件支持的。

7.3 I/O内存动态映射讲解

在日常的设备驱动开发中必不可少的会通过对I/O内存的访问来进行与外围设备进行通信和控制,例如可以通过访问I/O内存进行控制LED和按键的状态等。

如下图7.5所示为I/O内存动态映射及访问的流程,主要流程为:申请I/O内存空间、将申请到的I/O内存空间的物理地址进行动态映射获取得到对应的虚拟地址、通过相应的I/O内存访问函数进行访问I/O内存资源、当不需要该I/O内存资源时需要释放所申请的物理地址资源和虚拟地址的资源。

图7.5 I/O内存动态映射访问流程

(1) 申请与释放I/O内存资源

在进行I/O内存资源进行访问前,需要先申请I/O内存资源获取对应的物理地址(Physical Address PA)的访问控制,在linux内核中申请I/O内存资源可使用如下的函数进行申请I/O内存资源:

/*函数功能:申请I/O内存资源
*函数参数:
*@unsigned long start:需要申请的I/O内存资源的物理地址起始地址
*@unsigned long len:需要申请内存区域的大小,以字节为单位
*@char *name:自定义内存区的名字,若申请成功,就可以在/proc/iomem当中找到该名字
*函数返回值:
*@成功:返回非NULL指针
*@失败:返回NULL指针
*/
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

在工程中如果需要对已经申请的的I/O内存资源进行释放时,此时需要使用如下函数对其进行释放:

/*函数功能:释放I/O内存资源
*函数参数:
*@unsigned long start:需要申请的I/O内存资源的物理地址起始地址
*@unsigned long len:需要申请内存区域的大小,以字节为单位
*函数返回值:无
*/
void release_mem_region(unsigned long start, unsigned long len);

(2) I/O内存动态映射

在申请得到可以控制的I/O内存后,如果我们需要对I/O内存进行访问时并不能直接就可以进行访问的,需要通过对I/O内存的物理地址进行动态映射后得到对应的虚拟地址,才能够通过获得的虚拟地址进行访问I/O内存。在linux内核中可以通过如下函数进行动态映射获得对应内存的虚拟地址:

/*函数功能:动态映射内存获取虚拟地址
*函数参数:
*@unsigned long offset:要映射的物理内存区的起始地址
*@unsigned long size:需要进行动态映射的物理地址范围的大小,以字节为单位
*函数返回值:
*@成功:返回虚拟地址的指针
*@失败:返回NULL
*/
void *ioremap(unsigned long offset, unsigned long size);

与ioremap()配套使用的释放虚拟地址资源的函数是被iounmap(),如下为释放函数的原型:

/*函数功能:释放动态映射内存获得的虚拟地址资源
*函数参数:
*@void * addr:虚拟地址指针
*函数返回值:无
*/
void iounmap(void * addr);

(3) 访问I/O内存资源函数

虽然可以对获取得到的虚拟地址指针进行直接的操作,但是仍然可以使用内核提供的访问I/O内存资源的如下函数,来完成对I/O内存的访问操作:

(a) 读I/O内存

/*函数功能:读I/O内存的值
*函数参数:
*@void * addr:虚拟地址指针
*函数返回值:
*@返回unsigned int型的I/O内存当前值
*/
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
(b) 写I/O内存
/*函数功能:写I/O内存的值
*函数参数:
*@void * addr:虚拟地址指针
*@value需要写入的值
*函数返回值:无
*/
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);

(c) 读一段I/O内存

/*函数功能:读一段I/O内存的值
*函数参数:
*@void * addr:虚拟地址指针
*@ void *buf:用于保存返回的字串指针
*@unsigned long count:需要读取的I/O内存的大小,以字节为单位
*函数返回值:无
*/
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);

(d) 写一段I/O内存

/*函数功能:写一段I/O内存的值
*函数参数:
*@void * addr:虚拟地址指针
*@ void *buf:写入数据的字串指针
*@unsigned long count:需要写入的I/O内存的大小,以字节为单位
*函数返回值:无
*/
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);

(e) 复制I/O内存

/*函数功能:拷贝I/O内存的状态值到目的地址
*函数参数:
*@void *dest:需要将I/O状态值保存起来的目的地址
*@ void *source:需要拷贝状态值的I/O内存地址
*@unsigned long count:需要拷贝的数据的大小,以字节为单位
*函数返回值:无
*/
void memcpy_fromio(void *dest, void *source, unsigned int count);
/*函数功能:拷贝数据到目标I/O内存
*函数参数:
*@void *dest:用于保存数据的I/O地址
*@ void *source:数据源地址
*@unsigned long count:需要拷贝进I/O内存的数据大小,以字节为单位
*函数返回值:无
*/
void memcpy_toio(void *dest, void *source, unsigned int count);

(4) I/O内存动态映射实例展示

下面的驱动程序主要是实现通过向I/O内存写入数据来控制LED灯呈现呼吸灯的效果。

(a)驱动程序led_drv.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#include <linux/io.h>

static struct cdev gec6818_led_cdev;		//字符设备结构体

static dev_t	led_num=0;					//设备号


static struct class 	*leds_class;

static struct device 	*leds_device;

static void __iomem		*gpioe_base_va;		//gpioe的虚拟地址基址
static void __iomem		*gpioe_out_va;		
static void __iomem		*gpioe_outenb_va;	
static void __iomem		*gpioe_altfn0_va;
static void __iomem		*gpioe_altfn1_va;	

static void __iomem		*gpioc_base_va;		//gpioc的虚拟地址基址
static void __iomem		*gpioc_out_va;		
static void __iomem		*gpioc_outenb_va;	
static void __iomem		*gpioc_altfn0_va;
static void __iomem		*gpioc_altfn1_va;			

static int gec6818_led_open (struct inode * inode, struct file *file)
{
	printk("gec6818_led_open \n");
	//配置GPIOE13为输出模式
	iowrite32(ioread32(gpioe_altfn0_va)&(~(3<<26)),gpioe_altfn0_va);
	iowrite32(ioread32(gpioe_outenb_va)|(1<<13),gpioe_outenb_va);	
	
	//配置GPIOC17为输出模式
	iowrite32((ioread32(gpioc_altfn1_va)&(~(3<<2)))|(1<<2),gpioc_altfn1_va);
	iowrite32(ioread32(gpioc_outenb_va)|(1<<17),gpioc_outenb_va);
	
	//配置GPIOC7、GPIOC8为输出模式
	iowrite32((ioread32(gpioc_altfn0_va)&(~((3<<16)|(3<<14))))|((1<<16)|(1<<14)),gpioc_altfn0_va);	
	iowrite32(ioread32(gpioc_outenb_va)|(1<<8)|(1<<7),gpioc_outenb_va);	
	return 0;
}

static int gec6818_led_release (struct inode *inode, struct file *file)
{
	printk("gec6818_led_release \n");
	return 0;
}

static ssize_t gec6818_led_write (struct file *file, constchar __user * buf, size_t len, loff_t * off)
{
	int rt;
	int v;
	char kbuf[2]={0};
	printk("[gec6818_led_write]len=%d\n",len);
	
	//判断当前len是否合法
	if(len > sizeof kbuf)
		return -EINVAL;//返回参数无效错误码
		
	//从用户空间拷贝数据
	rt = copy_from_user(kbuf,buf,len);
	if(rt !=0 )
		return -EFAULT;
	
	printk("[gec6818_led_write]kbuf[0]=%d,kbuf[1]=%d\n",kbuf[0],kbuf[1]);	
	switch(kbuf[0])
	{
	    case 7:		//D7
	    {
		    if(kbuf[1]==1)
		    {
			v = ioread32(gpioe_out_va);
			v &=~(1<<13);
			iowrite32(v,gpioe_out_va);
				
		    }
		    else if(kbuf[1]==0)
		    {
			v = ioread32(gpioe_out_va);
			v |=(1<<13);
			iowrite32(v,gpioe_out_va);				
		    }
		    else
			return -EINVAL;
	    }break;
		
	    case 8:		//D8
	    {
		if(kbuf[1]==1)
		{  v = ioread32(gpioc_out_va);
		    v &=~(1<<17);
		    iowrite32(v,gpioc_out_va);		
		}  else if(kbuf[1]==0)
		{
		    v = ioread32(gpioc_out_va);
		    v |=(1<<17);
		    iowrite32(v,gpioc_out_va);				
		} else
		    return -EINVAL;	
		}break;		
		
	    case 9:		//D9
	    {
		if(kbuf[1]==1)
		{
	            v = ioread32(gpioc_out_va);
		    v &=~(1<<8);
		    iowrite32(v,gpioc_out_va);	
		}
		else if(kbuf[1]==0)
		{
		    v = ioread32(gpioc_out_va);
		    v |=(1<<8);
		    iowrite32(v,gpioc_out_va);						
		}
		else
       		    return -EINVAL;	
		}break;				
		
	case 10:	//D10
	{
	    if(kbuf[1]==1)
	    {
	        v = ioread32(gpioc_out_va);
		v &=~(1<<7);
		iowrite32(v,gpioc_out_va);		
            }    else if(kbuf[1]==0)
	    {
	        v = ioread32(gpioc_out_va);
		v |=(1<<7);
		iowrite32(v,gpioc_out_va);				
				
	    }
	    else
		return -EINVAL;	
	}break;			
		
        default:
	    return -EINVAL;
    }
	
	//获取成功复制的字节数
	len = len - rt;
	return len;
}

static ssize_t gec6818_led_read (struct file *file, char __user *buf, size_t len, loff_t * offs)
{
	int rt;
	char kbuf[6]={'1','2','3','4','\n','\0'};
	
	//判断当前len是否合法
	if(len > sizeof kbuf)
		return -EINVAL;//返回参数无效错误码
		
	//从内核空间拷贝到用户空间
	
	rt = copy_to_user(buf,kbuf,len);
	
	//获取成功复制的字节数
	len = len - rt;
	printk("gec6818_led_read,__user buf[%s],len[%d]\n",buf,len);
	return len;
}

static const struct file_operations gec6818_led_fops = {
 	.owner 		= THIS_MODULE,
	.write 		= gec6818_led_write,
	.open 		= gec6818_led_open,
	.release 	= gec6818_led_release,
	.read 		= gec6818_led_read,
};

//入口函数
static int __init gec6818_led_init(void)
{
	int rt=0;
	struct resource *gpioc_res=NULL;	
	struct resource *gpioe_res=NULL;

	//动态申请设备号
	rt=alloc_chrdev_region(&led_num,0,1,"gec6818_leds");
	if(rt < 0)
	{
	    printk("alloc_chrdev_region fail\n");
	    return rt;	
	}
	
	printk("led_major = %d\n",MAJOR(led_num));
	printk("led_minor = %d\n",MINOR(led_num));	
	
	//字符设备初始化
	cdev_init(&gec6818_led_cdev,&gec6818_led_fops);
	
	//字符设备添加到内核
	rt = cdev_add(&gec6818_led_cdev,led_num,1);
	
	if(rt < 0)
	{
	    printk("cdev_add fail\n");
	    goto fail_cdev_add;	
	}
	
	//创建类
	leds_class=class_create(THIS_MODULE, "gec6818_leds");
	
	if (IS_ERR(leds_class))
	{
	    rt = PTR_ERR(leds_class);
	    printk("class_create gec6818_leds fail\n");
	    goto fail_class_create;
	}
	
	//创建设备
	leds_device=device_create(leds_class, NULL, led_num, NULL, "gec6818_leds");
	
	if (IS_ERR(leds_device))
	{
	    rt = PTR_ERR(leds_device);
	    printk("device_create gec6818_leds fail\n");
	    goto fail_device_create;
	}	
	
	//申请物理内存区
	//起始地址GPIOC寄存器基址0xC001C000
	//申请大小28字节
	//申请到的名字为GPIOC_MEM
	gpioc_res = request_mem_region(0xC001C000,0x28,"GPIOC_MEM");
	if(gpioc_res == NULL)
	{
		printk("request_mem_region 0xC001C000,0x28 fail\n");
		goto fail_request_mem_region_gpioc;		
	}
	
	//IO内存动态映射,得到物理地址相应的虚拟地址
	gpioc_base_va = ioremap(0xC001C000,0x28);
	if(gpioc_base_va == NULL)
	{
		printk("ioremap 0xC001C000,0x28 fail\n");
		goto fail_ioremap_gpioc;			
	}	
	
	//得到每个寄存器的虚拟地址
	gpioc_out_va 		= gpioc_base_va+0x00;
	gpioc_outenb_va 	= gpioc_base_va+0x04;	
	
	gpioc_altfn0_va 	= gpioc_base_va+0x20;		
	gpioc_altfn1_va 	= gpioc_base_va+0x24;		
	
	
	//申请物理内存区
	//起始地址GPIOE寄存器基址0xC001E000
	//申请大小28字节
	//申请到的名字为GPIOE_MEM
	gpioe_res = request_mem_region(0xC001E000,0x28,"GPIOE_MEM");
	
	if(gpioe_res == NULL)
	{
	    printk("request_mem_region 0xC001E000,0x28 fail\n");
	    goto fail_request_mem_region_gpioe;			
	}
	
	//IO内存动态映射,得到物理地址相应的虚拟地址
	gpioe_base_va = ioremap(0xC001E000,0x28);
	
	if(gpioe_base_va == NULL)
	{
		
		printk("ioremap 0xC001E000,0x28 fail\n");
		
		goto fail_ioremap_gpioe;		
		
	}	
	
	//得到每个寄存器的虚拟地址
	gpioe_out_va 		= gpioe_base_va+0x00;
	gpioe_outenb_va 	= gpioe_base_va+0x04;	
	
	gpioe_altfn0_va 	= gpioe_base_va+0x20;		
	gpioe_altfn1_va 	= gpioe_base_va+0x24;	
	
	printk("gec6818 led init\n");
	
	return 0;
		
fail_ioremap_gpioe:
	release_mem_region(0xC001E000,0x28);
	
fail_request_mem_region_gpioe:
	iounmap(gpioc_base_va);

fail_ioremap_gpioc:
	release_mem_region(0xC001C000,0x28);

fail_request_mem_region_gpioc:
	device_destroy(leds_class,led_num);
	
fail_device_create:
	class_destroy(leds_class);

fail_class_create:
	cdev_del(&gec6818_led_cdev);	
	
fail_cdev_add:
	unregister_chrdev_region(led_num,1);
	
	return rt;
}

//出口函数
static void __exit gec6818_led_exit(void)
{
	iounmap(gpioc_base_va);
	iounmap(gpioe_base_va);
	
	release_mem_region(0xC001C000,0x28);
	release_mem_region(0xC001E000,0x28);
	
	device_destroy(leds_class,led_num);
	
	class_destroy(leds_class);
	
	cdev_del(&gec6818_led_cdev);
	
	unregister_chrdev_region(led_num,1);
	
	printk("gec6818 led exit\n");
}

//驱动程序的入口:insmod led_drv.ko调用module_init,module_init又会去调用gec6818_led_init。
module_init(gec6818_led_init);

//驱动程序的出口:rmsmod led_drv调用module_exit,module_exit又会去调用gec6818_led_exit。
module_exit(gec6818_led_exit)


//模块描述
MODULE_AUTHOR("stephenwen88@163.com");			//作者信息
MODULE_DESCRIPTION("gec6818 led driver");		//模块功能说明
MODULE_LICENSE("GPL");							//许可证:驱动遵循GPL协议

(b)测试程序led_test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

static char d7_on [2]={7,1};//D7灯亮
static char d7_off[2]={7,0};//D7灯灭	

static char d8_on [2]={8,1};
static char d8_off[2]={8,0};	

static char d9_on [2]={9,1};
static char d9_off[2]={9,0};	

static char d10_on [2]={10,1};
static char d10_off[2]={10,0};	

static char *leds_on_tbl[4]={
	
	d7_on,
	d8_on,
	d9_on,
	d10_on
};


static char *leds_off_tbl[4]={
	
	d7_off,
	d8_off,
	d9_off,
	d10_off
};

int main(int argc, char **argv)
{
	int fd=-1;
	int len;
	int i=0;
	
	//打开gec6818_leds设备
	fd = open("/dev/gec6818_leds",O_RDWR);
	
	if(fd < 0)
	{
		perror("open /dev/gec6818_leds:");
		return fd;
		
	}
	
	while(1)
	{
		
		for(i=0; i<4; i++)
		{
			len=write(fd,leds_on_tbl[i],2);
			printf("len=%d\r\n",len);
			sleep(1);
			len=write(fd,leds_off_tbl[i],2);
			printf("len=%d\r\n",len);
			sleep(1);	
		}
			
	}
	close(fd);
	
	return 0;
}

在上面的程序中主要实现了通过用户程序向驱动中传递需要控制的灯的状态,例如上面led_test.c中的如下语句即是向驱动中传入leds_on_tbl[i]中的两个字节的数据。

write(fd,leds_on_tbl[i],2);

然后led_drv.c中驱动通过copy_from_user()这个函数来获取刚才用户程序中的leds_on_tbl[i]的两个字节的数据,然后通过判断后使用 ioread32()和iowrite32()两个函数对I/O内存进行读/写操作,最后通过copy_to_user()函数向用户程序返回操作的状态值。

由于用户空间和内核空间的数据不能够直接进行交互,但是linux提供了如下函数可供用户空间和内核空间进行数据的传递:

/*函数功能:从用户空间拷贝数据到内核空间
*函数参数:
*@void *to:内核空间的指针
*@ const void __user *from:用户空间指针
*@unsigned long n:表示从用户空间向内核空间拷贝数据的字节数
*函数返回值:
*@成功:返回0
*@失败:返回还没有完成拷贝的字节数
*/
static inline unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);
/*函数功能:从内核空间拷贝数据到用户空间
*函数参数:
*@void *to:内核空间的指针
*@ const void __user *from:用户空间指针
*@unsigned long n:表示从内核空间向用户空间拷贝数据的字节数
*函数返回值:
*@成功:返回0
*@失败:返回还没有完成拷贝的字节数
*/
unsigned long copy_to_user(void *to, const void *from, unsigned long n)

7.3 I/O内存静态映射讲解

I/O内存映射中除了上面的动态映射外,还存在着一种名为I/O内存静态映射的操作,它是通过结构体struct map_desc静态创建I/O资源映射表。

在linux内核中提供了一种创建I/O内存映射的方式,即在系统启动时通过struct map_desc静态创建I/O资源与内核空间地址进行线性映射,形成一个静态的映射表。工程师们可以自行定义需要使用的I/O内存资源映射后的虚拟地址。在创建好了静态映射表后,在内核中访问相应的I/O资源则不需要再次使用ioremap()进行动态映射,而是可以直接使用静态映射表中的虚拟地址来进行访问操作。

在linux移植过程中,进行I/O内存静态映射时通常需要向结构体struct map_desc数组中添加新的成员来完成映射,其中struct map_desc定义如下:

struct map_desc {
unsigned long virtual;/* 虚拟地址 */
unsigned long pfn ; /* __phys_to_pfn(phy_addr) */
unsigned long length; /* 大小 */
unsigned int type;/* 类型 */
};

如下代码为SmartARM3250相关的物理地址到虚拟地址的静态映射的内容,同时最终建立页映射的函数是lpc32xx_map_io()中的iotable_init()函数,它是通过系统的宏定义好的函数MACHINE_START、MACHINE_END来给map_io()函数进行赋值。在将linux移植到特定平台板子时,MACHINE_START、MACHINE_END宏之间的定义是特别为该平台设计的,其中map_io()函数负责实现I/O内存的静态映射,建立最终的页映射。

static struct map_desc smartarm3250_io_desc[] __initdata = {
    {   /* nCS2, CAN SJA1000 */
        .virtual  = io_p2v(EMC_CS2_BASE),
        .pfn      = __phys_to_pfn(EMC_CS2_BASE),
        .length   = SZ_1M,
        .type     = MT_DEVICE
    },

    {   /* nCS1, CF Card */
        .virtual  = io_p2v(EMC_CS1_BASE),
        .pfn      = __phys_to_pfn(EMC_CS1_BASE),
        .length   = SZ_1M,
        .type     = MT_DEVICE
    }
};
void __init lpc32xx_map_io(void)
{
    iotable_init (lpc32xx_io_desc, ARRAY_SIZE (lpc32xx_io_desc));
}
MACHINE_START (LPC3XXX, "SmartARM3250 board with the LPC3250 Microcontroller")
       /* Maintainer: Kevin Wells, NXP Semiconductors */
       .phys_io  = UART5_BASE,
       .io_pg_offst    = ((io_p2v (UART5_BASE))>>18) & 0xfffc,
       .boot_params  = 0x80000100,
       //.map_io        = lpc32xx_map_io,
       .map_io          = smartarm3250_map_io,
       .init_irq   = lpc32xx_init_irq,
       .timer             = &lpc32xx_timer,
       .init_machine  = smartarm3250_board_init,
MACHINE_END

如下代码为对应IXDP2401电路板内存资源情况的定义,从这份代码清单中可以非常清晰的看出对应的电路板的内存资源分配情况,同时作为一名驱动工程师也可以在此基础上进行修改,从而达到对非常规内存区域的I/O内存区域根据不同特定的板子进行添加资源到map_desc数组中。

在配置完I/O内存后,设备驱动对map_desc数组映射后的内存进行访问时,直接可以在map_desc中此段的虚拟地址上加上偏移量即可使用,不再需要ioremap()这个函数来进行使用。

/*
* 逻辑地址 物理地址
* e8000000 40000000 PCI memory PHYS_PCI_MEM_BASE (max 512M)
* ec000000 61000000 PCI 配置空间 PHYS_PCI_CONFIG_BASE (max 16M)
* ed000000 62000000 PCI V3 regs PHYS_PCI_V3_BASE (max 64k)
* ee000000 60000000 PCI IO PHYS_PCI_IO_BASE (max 16M)
* ef000000 Cache flush
* f1000000 10000000 核心模块寄存器
* f1100000 11000000 系统控制寄存器
* f1200000 12000000 EBI 寄存器
* f1300000 13000000 计数器/定时器
* f1400000 14000000 中断控制器
* f1600000 16000000 UART 0
* f1700000 17000000 UART 1
* f1a00000 1a000000 调试用 LEDs
* f1b00000 1b000000 GPIO
*/

static struct map_desc ap_io_desc[] __initdata = {
    {
        .virtual = IO_ADDRESS(INTEGRATOR_HDR_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_HDR_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = IO_ADDRESS(INTEGRATOR_SC_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_SC_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = IO_ADDRESS(INTEGRATOR_EBI_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_EBI_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = IO_ADDRESS(INTEGRATOR_CT_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_CT_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = IO_ADDRESS(INTEGRATOR_IC_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_IC_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = IO_ADDRESS(INTEGRATOR_UART0_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_UART0_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = IO_ADDRESS(INTEGRATOR_UART1_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_UART1_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    },
    {
        .virtual = IO_ADDRESS(INTEGRATOR_DBG_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_DBG_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = IO_ADDRESS(INTEGRATOR_GPIO_BASE),
        .pfn = __phys_to_pfn(INTEGRATOR_GPIO_BASE),
        .length = SZ_4K,
        .type = MT_DEVICE
    }, 
    {
        .virtual = PCI_MEMORY_VADDR,
        .pfn = __phys_to_pfn(PHYS_PCI_MEM_BASE),
        .length = SZ_16M,
        .type = MT_DEVICE
    }, 
    {
        .virtual = PCI_CONFIG_VADDR,
        .pfn = __phys_to_pfn(PHYS_PCI_CONFIG_BASE),
        .length = SZ_16M,
        .type = MT_DEVICE
    }, 
    {
        .virtual = PCI_V3_VADDR,
        .pfn = __phys_to_pfn(PHYS_PCI_V3_BASE),
        .length = SZ_64K,
        .type = MT_DEVICE
    },
    {
        .virtual = PCI_IO_VADDR,
        .pfn = __phys_to_pfn(PHYS_PCI_IO_BASE),
        .length = SZ_64K,
        .type = MT_DEVICE
    }
};

7.4本章总结

在本章中主要讲解了关于I/O内存进行访问的相关知识,包括MMU工作原理和linux内存管理,在此基础上还进一步讲解了对I/O内存访问的两个重要的内容,动态映射和静态映射的相关知识以及如何使用这两种方式来对I/O内存进行有效的访问,为后续学习设备驱动的其他内容做好铺垫。

























全部评论

相关推荐

02-24 10:34
门头沟学院 Java
已注销:之前发最美的女孩基本爱答不理,发最帅的hr终于有反馈了,女孩子也要自信起来
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务