几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据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(如MIPS ARM PowerPC等)通常只实现一个物理地址空间,像这种情况,外设的I/O端口的物理地址就被映射到内存地址空间中,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

  但是,这两者在硬件实现上的差异对于软件来说是完全透明的,驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是"I/O内存"资源。

  一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间。
但要使用I/O内存首先要申请,然后才能映射,使用I/O端口首先要申请,或者叫请求,对于I/O端口的请求意思是让内核知道你要访问这个端口,这样内核知道了以后它就不会再让别人也访问这个端口了.毕竟这个世界僧多粥少啊.申请I/O端口的函数是request_region, 申请I/O内存的函数是request_mem_region, 来自include/linux/ioport.h, 如下:
* Convenience shorthand with allocation */
#define request_region(start,n,name) __request_region(&ioport_resource, (start), (n), (name))
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
#define rename_region(region, newname) do { (region)->name = (newname); } while (0)
extern struct resource * __request_region(struct resource *,
resource_size_t start,
resource_size_t n, const char *name);

这里关键来解析一下request_mem_region函数。
Linux把基于I/O映射方式的I/O端口和基于内存映射方式的I/O端口资源统称为“I/O区域”(I/O Region)。I/O Region仍然是一种I/O资源,因此它仍然可以用resource结构类型来描述。
Linux是以一种倒置的树形结构来管理每一类I/O资源(如:I/O端口、外设内存、DMA和IRQ)的。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是一个resource结构,而树的根结点root则描述了该类资源的整个资源空间。

1.结构体
1.1>struct resource iomem_resource = { "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };
1.2>struct resource {
const char *name;
unsigned long start, end;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
2.调用函数
request_mem_region(S1D_PHYSICAL_REG_ADDR,S1D_PHYSICAL_REG_SIZE, "EpsonFB_RG")
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
__request_region检查是否可以安全占用起始物理地址S1D_PHYSICAL_REG_ADDR之后的连续S1D_PHYSICAL_REG_SIZE字节大小空间

struct resource * __request_region(struct resource *parent, unsigned long start, unsigned long n, const char *name)
{
struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);

if (res) {
memset(res, 0, sizeof(*res));
res->name = name;
res->start = start;
res->end = start + n - 1;
res->flags = IORESOURCE_BUSY;

write_lock(&resource_lock);

for (;;) {
struct resource *conflict;

conflict = __request_resource(parent, res);
//sibling parent下的所有单元,检测申请部分是否存在交叠冲突
if (!conflict)
//conflict=0;申请成功,正常安置了[start,end]到相应位置
break;
if (conflict != parent) {
parent = conflict;
if (!(conflict->flags & IORESOURCE_BUSY))
continue;
}
kfree(res);
//检测到了资源交叠冲突,kfree归还kmalloc申请的内存
res = NULL;
break;
}
write_unlock(&resource_lock);
}
return res;
}

static struct resource * __request_resource(struct resource *root, struct resource *new)
{
unsigned long start = new->start;
unsigned long end = new->end;
struct resource *tmp, **p;

if (end < start)
return root;
if (start < root->start)
return root;
if (end > root->end)
return root;
p = &root->child;
//root下的第一个链表元素*p.[child链表是以I/O资源物理地址从低到高的顺序排列的]
for (;;) {
tmp = *p;
if (!tmp || tmp->start > end) {
new->sibling = tmp;
*p = new;
//可以从root->child=null开始我们的分析考虑,此时tmp=null,那么第一个申请将以!tmp条件满足而进入
//这时root->child的值为new指针,new->sibling = tmp = null;当第二次申请发生时:如果tmp->start > end成立,
//那么,root->child的值为new指针,new->sibling = tmp;这样就链接上了,空间分布图如:
//child=[start,end]-->[tmp->start,tmp->end](1);
//如果条件tmp->start > end不成立,那么只能是!tmp条件进入
//那么,root->child的值不变,tmp->sibling = new;new->sibling = tmp = null这样就链接上了,空间分布图如:
//child=[child->start,child->end]-->[start,end](2);
//当第三次申请发生时:如果start在(2)中的[child->end,end]之间,那么tmp->end < start将成立,继而continue,
//此时tmp = (2)中的[start,end],因为tmp->start < end,所以继续执行p = &tmp->slibing = null,
//因为tmp->end > start,所以资源冲突,返回(2)中的[start,end]域
//综上的两个边界值情况和一个中间值情况的分析,可以知道代码实现了一个从地地址到高地址的顺序链表
//模型图:childe=[a,b]-->[c,d]-->[e,f],此时有一个[x,y]需要插入进去,tmp作为sibling指针游动
//tmp指向child=[a,b],
//tmp指向[a,b],当tmp->start>y时,插入后的链接图为:child=[x,y]-->[a,b]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
//tmp指向[c,d],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[x,y]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
//tmp指向[e,f],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[c,d]-->[x,y]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
//tmp指向null ,插入后的链接图为:child=[a,b]-->[c,d]-->[e,f]-->[x,y]-->null;
//顺利的达到了检测冲突,顺序链接的目的
new->parent = root;
return NULL;
}
p = &tmp->sibling;
if (tmp->end < start)
continue;
return tmp;
}
}

其实说白了,request_mem_region函数并没有做实际性的映射工作,只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源。

重要的还是ioremap函数,ioremap主要是检查传入地址的合法性,建立页表(包括访问权限),完成物理地址到虚拟地址的转换。
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
  iounmap函数用于取消ioremap()所做的映射,原型如下:
void iounmap(void * addr);
  这两个函数都是实现在mm/ioremap.c文件中。
  在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。为了保证驱动程序的跨平台的可移植性,我们应该使用Linux中特定的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问。如在x86平台上,读写I/O的函数如下所示:
#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))
#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))
#define memset_io(a,b,c) memset(__io_virt(a),(b),(c))
#define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
#define memcpy_toio(a,b,c) memcpy(__io_virt(a),(b),(c))
  最后,特别强调驱动程序中mmap函数的实现方法。用mmap映射一个设备,意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。

I/O地址映射的更多相关文章

  1. @RequestMapping 用法详解之地址映射

    @RequestMapping 用法详解之地址映射 引言: 前段时间项目中用到了RESTful模式来开发程序,但是当用POST.PUT模式提交数据时,发现服务器端接受不到提交的数据(服务器端参数绑定没 ...

  2. @RequestMapping 用法详解之地址映射(转)

    引言: 前段时间项目中用到了RESTful模式来开发程序,但是当用POST.PUT模式提交数据时,发现服务器端接受不到提交的数据(服务器端参数绑定没有加任何注解),查看了提交方式为applicatio ...

  3. Linux内存管理之地址映射

    写在前面:由于地址映射涉及到各种寄存器的设置访问,Linux对于不同体系结构处理器的地址映射采用不同的方法,例如对于i386及后来的32位的Intel的处理器在页式映射时采用的是2级页表映射,而对于I ...

  4. ARM地址映射

    转自:http://blog.csdn.net/a3163504123/article/details/10958229 重映射之后,一般原来的地址依然有效.也就是说,可能两个地址,对应一个存储单元. ...

  5. s3c6410_MMU地址映射过程详述

    参考: 1)<ARM1176 JZF-S Technical Reference Manual>: Chapter 3 System Control Coprocessor Chapter ...

  6. linux地址映射1、2、3(☆☆☆)

    欢迎关注瘋耔新浪微博:http://weibo.com/cpjphone 一.线性映射与非线性映射                                                   ...

  7. 内存管理概述、内存分配与释放、地址映射机制(mm_struct, vm_area_struct)、malloc/free 的实现

    http://blog.csdn.net/pi9nc/article/details/23334659 注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料 ...

  8. 主存与Cache的地址映射

    最近在复习计算机体系结构,选用的教材是名闻遐迩的<计算机体系结构 量化研究方法 第五版>(Computer Architecture A Quantitative Approach), 关 ...

  9. linux下c通过虚拟地址映射读写文件的代码

    在代码过程中中,把开发过程中比较好的一些代码片段记录起来,如下的代码内容是关于 linux下c通过虚拟地址映射读写文件的代码,应该对小伙伴有些好处.#include<stdio.h>#in ...

随机推荐

  1. 【转】Asp.net中时间格式化的6种方法详细总结

    1. 数据控件绑定时格式化日期方法: 代码如下: <asp:BoundColumn DataField="AddTime" HeaderText="添加时间&quo ...

  2. JDBC - Oracle PreparedStatement (GeneratedKey kind) ArrayIndexOutOfBoundsException

    问题: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 12at oracle.jdbc. ...

  3. Linux 实现自动安装服务组件以及优化内核参数 (转)

    安装好Linux裸机后(安装请参考:http://blog.itpub.net/26230597/viewspace-1380155/),还需要在其上安装一些基础组件,一般是手动一个个安装,比较繁复也 ...

  4. [原]一个简单的Linux TCP Client所涉及到的头文件

    今天在Linux环境下写了一个最简单的TCP Client程序,没想到Linux环境下的头文件竟然这么分散,让我这样的菜鸟很是郁闷啊.编译成功的代码如下: #include <iostream& ...

  5. hibernate.properties官方属性用例(可用于hibernate.cfg.xml属性参考)

    ######################### Query Language ######################### ## define query language constant ...

  6. C#Random()函数详解

    随机数的使用很普遍,可用它随机显示图片,用它防止无聊的人在论坛灌水还可以用来加密信息等等.本文讨论如何在一段数字区间内随机生成若干个互不相同的随机数,比如在从1到20间随机生成6个互不相同的整数,并通 ...

  7. VS2012远程调试(winform+web 远程调试)

    VS2012远程调试   一.调试WinFrom 程序 安装rtools_setup_x64 下载 配置Remote 启动Remote debugger 默认端口4016,选择工具-〉选项,选择 无身 ...

  8. 清理SQL Server日志释放文件空间的终极方法

    清理SQL Server日志释放文件空间的终极方法  转自:http://www.cnblogs.com/dudu/archive/2013/04/10/3011416.html [问题场景]有一个数 ...

  9. bzoj4705: 棋盘游戏

    Description 有一个N*M的棋盘,初始每个格子都是白色的. 行操作是指选定某一行,将这行所有格子的颜色取反(黑白互换). 列操作是指选定某一列,将这列所有格子的颜色取反. XX进行了R次行操 ...

  10. bzoj2618: [Cqoi2006]凸多边形

    Description 逆时针给出n个凸多边形的顶点坐标,求它们交的面积.例如n=2时,两个凸多边形如下图: 则相交部分的面积为5.233. Input 第一行有一个整数n,表示凸多边形的个数,以下依 ...