几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据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, 如下:

Cpp代码  
  1. /* Convenience shorthand with allocation */
  2. #define request_region(start,n,name)    __request_region(&ioport_resource, (start), (n), (name))
  3. #define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
  4. #define rename_region(region, newname) do { (region)->name = (newname); } while (0)
  5. extern struct resource * __request_region(struct resource *,
  6. resource_size_t start,
  7. 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.结构体

Cpp代码  
  1. 1.1>struct resource iomem_resource = { "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };
  2. 1.2>struct resource {
  3. const char *name;
  4. unsigned long start, end;
  5. unsigned long flags;
  6. struct resource *parent, *sibling, *child;
  7. };

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字节大小空间

Cpp代码  
  1. struct resource * __request_region(struct resource *parent, unsigned long start, unsigned long n, const char *name)
  2. {
  3. struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);
  4. if (res) {
  5. memset(res, 0, sizeof(*res));
  6. res->name = name;
  7. res->start = start;
  8. res->end = start + n - 1;
  9. res->flags = IORESOURCE_BUSY;
  10. write_lock(&resource_lock);
  11. for (;;) {
  12. struct resource *conflict;
  13. conflict = __request_resource(parent, res);
  14. //sibling parent下的所有单元,检测申请部分是否存在交叠冲突
  15. if (!conflict)
  16. //conflict=0;申请成功,正常安置了[start,end]到相应位置
  17. break;
  18. if (conflict != parent) {
  19. parent = conflict;
  20. if (!(conflict->flags & IORESOURCE_BUSY))
  21. continue;
  22. }
  23. kfree(res);
  24. //检测到了资源交叠冲突,kfree归还kmalloc申请的内存
  25. res = NULL;
  26. break;
  27. }
  28. write_unlock(&resource_lock);
  29. }
  30. return res;
  31. }
  32. static struct resource * __request_resource(struct resource *root, struct resource *new)
  33. {
  34. unsigned long start = new->start;
  35. unsigned long end = new->end;
  36. struct resource *tmp, **p;
  37. if (end < start)
  38. return root;
  39. if (start < root->start)
  40. return root;
  41. if (end > root->end)
  42. return root;
  43. p = &root->child;
  44. //root下的第一个链表元素*p.[child链表是以I/O资源物理地址从低到高的顺序排列的]
  45. for (;;) {
  46. tmp = *p;
  47. if (!tmp || tmp->start > end) {
  48. new->sibling = tmp;
  49. *p = new;
  50. //可以从root->child=null开始我们的分析考虑,此时tmp=null,那么第一个申请将以!tmp条件满足而进入
  51. //这时root->child的值为new指针,new->sibling = tmp = null;当第二次申请发生时:如果tmp->start > end成立,
  52. //那么,root->child的值为new指针,new->sibling = tmp;这样就链接上了,空间分布图如:
  53. //child=[start,end]-->[tmp->start,tmp->end](1);
  54. //如果条件tmp->start > end不成立,那么只能是!tmp条件进入
  55. //那么,root->child的值不变,tmp->sibling = new;new->sibling = tmp = null这样就链接上了,空间分布图如:
  56. //child=[child->start,child->end]-->[start,end](2);
  57. //当第三次申请发生时:如果start在(2)中的[child->end,end]之间,那么tmp->end < start将成立,继而continue,
  58. //此时tmp = (2)中的[start,end],因为tmp->start < end,所以继续执行p = &tmp->slibing = null,
  59. //因为tmp->end > start,所以资源冲突,返回(2)中的[start,end]域
  60. //综上的两个边界值情况和一个中间值情况的分析,可以知道代码实现了一个从地地址到高地址的顺序链表
  61. //模型图:childe=[a,b]-->[c,d]-->[e,f],此时有一个[x,y]需要插入进去,tmp作为sibling指针游动
  62. //tmp指向child=[a,b],
  63. //tmp指向[a,b],当tmp->start>y时,插入后的链接图为:child=[x,y]-->[a,b]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
  64. //tmp指向[c,d],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[x,y]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
  65. //tmp指向[e,f],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[c,d]-->[x,y]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
  66. //tmp指向null                  ,插入后的链接图为:child=[a,b]-->[c,d]-->[e,f]-->[x,y]-->null;
  67. //顺利的达到了检测冲突,顺序链接的目的
  68. new->parent = root;
  69. return NULL;
  70. }
  71. p = &tmp->sibling;
  72. if (tmp->end < start)
  73. continue;
  74. return tmp;
  75. }
  76. }

其实说白了,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的函数如下所示:

Cpp代码  
  1. #define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
  2. #define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
  3. #define readl(addr) (*(volatile unsigned int *) __io_virt(addr))
  4. #define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
  5. #define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
  6. #define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))
  7. #define memset_io(a,b,c) memset(__io_virt(a),(b),(c))
  8. #define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
  9. #define memcpy_toio(a,b,c) memcpy(__io_virt(a),(b),(c))

最后,特别强调驱动程序中mmap函数的实现方法。用mmap映射一个设备,意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。

内核request_mem_region 和 ioremap的理解相关推荐

  1. 用户态与内核态的区别与理解

    用户态与内核态的区别与理解 先给大家看看Linux进程的地址空间,如下: 对于Linux内核态的地址空间,3G-4G是大家共享的.这里存放的是整个内核的代码和所有的内核模块以及内核所维护的数据. 1. ...

  2. Linux内核中container_of宏的理解

    对 typeof 的理解: 实际上, typeof 并不是宏定义,它是GCC的关键字,是GCC特有的特性.如果只知道一个变量的名字要得到其类型,并不是宏定义能够完成的,这需要编译时的信息.所以,typ ...

  3. 对于Linux内核tty设备的一点理解

    虽然一直做嵌入式Linux,宿主机和开发板通信天天都在用tty设备通信,但是其实自己对TTY设备及终端的概念认识几乎是0.对于Linux内核的终端.tty.控制台等概念的认识很模糊.由于在学习的时候碰 ...

  4. 【内核链表】数据结构——深入理解内核链表的概念和操作笔记

    内核链表 一.内核链表的前置概念 1.容器 2.通用解决方案 二.通用型链表节点的设计 1.初始化 2.增删操作 3.查找节点 4.遍历链表 5.示例代码 三.内核链表 1.普通链表弊端 2.内核链表 ...

  5. linux内核网络协议栈--linux网络设备理解(十四)

    1.网络设备驱动结构 1)网络协议接口层向网络层协议提供提供统一的数据包收发接口,不论上层协议为ARP还是IP,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()函数接受数 ...

  6. linux内核网络协议栈--linux网络设备理解(十三)

    网络层次 linux网络设备驱动与字符设备和块设备有很大的不同. 字符设备和块设备对应/dev下的一个设备文件.而网络设备不存在这样的设备文件.网络设备使用套接字socket访问,虽然也使用read, ...

  7. linux文件未识别,linux的内核文件vmlinuz介绍-深入理解Linux重定向的使用-网卡eth0,未识别的端口的解决方法_169IT.COM...

    介绍:Linux重定向是指修改原来默认的一些东西,对原来系统命令的默认执行方式进行改变,比如说简单的我不想看到在显示器的输出而是希望输出到某一文件中就可以通过Linux重定向来进行这项工作. Linu ...

  8. Linux内核访问外设I/O--动态映射(ioremap)和静态映射(map_desc)

    本篇文章主要介绍了"Linux内核访问外设I/O--动态映射(ioremap)和静态映射(map_desc)",主要涉及到Linux内核访问外设I/O--动态映射(ioremap) ...

  9. Linux内核访问外设I/O--动态映射(ioremap)和静态映射(map_desc) (转载)

    [转](转)Linux内核访问外设I/O资源的方式-静态映射(map_desc)方式 Linux内核访问外设I/O资源的方式 Author: Dongas Date: 08-08-02 我们知道默认外 ...

最新文章

  1. 数学推导+纯Python实现机器学习算法:逻辑回归
  2. java--String与int相互转换
  3. 注意设置httpclient连接数
  4. 在Windows10上安装ROS并测试
  5. 如何用Visual Studio Code远程调试运行在服务器上的nodejs应用
  6. 什么是python函数_什么是python函数
  7. 大数据之Azkaban部署
  8. 代码组织和部署 文件操作 node.js 1
  9. Linux查看非root流程执行
  10. proxool数据库连接池使用方法
  11. Unity 单元测试(NUnit,UnityTestTools)
  12. 联想台式计算机驱动程序,联想台式机网卡驱动,详细教您联想台式机网卡驱动...
  13. 使用python对bin文件进行操作
  14. 传奇3便捷架设管理工具-BY来世今生
  15. 校本课程——宝宝爱上学
  16. Android 4.4(KitKat)窗口管理子系统 - 体系框架
  17. 【数据架构系列-02】从《数据中台能力成熟度模型》的发布,聊聊火了的中台
  18. hdu 4043 概率
  19. 数据分析师八大能力之一:收集信息的能力
  20. ML-Diary02

热门文章

  1. Python ImportError: No module named 'requests'解决方法
  2. Consul入门06 - 键/值对数据
  3. SpringBoot非官方教程 | 第一篇:构建第一个SpringBoot工程
  4. Spring Cloud构建微服务架构(七)消息总线(续:Kafka)
  5. 数据库:redis和MySQL如何做到数据的一致性?
  6. 网络:IP协议与寻址
  7. 剑指offer:约瑟夫环的问题
  8. yolov3的缺点_YOLOv3:训练自己的数据(附优化与问题总结)
  9. 边缘计算网关linux开发,边缘计算网关(BMG800系列)二次开发指导手册
  10. 数据仓库—stg层_数据仓库之Hive快速入门 - 离线实时数仓架构