一、 ioremap() 函数基础概念

几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据CPU体系结构的不同,CPU对IO端口的编址方式有两种:

a -- I/O 映射方式(I/O-mapped)

典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。

b -- 内存映射方式(Memory-mapped)

  RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间,外设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内存资源的物理地址映射到核心虚地址空间(3GB-4GB)中(这里是内核空间),原型如下:

1、ioremap函数

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). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源。

2、iounmap函数

iounmap函数用于取消ioremap()所做的映射,原型如下:

     void iounmap(void * addr);


二、 ioremap() 相关函数解析

在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。为了保证驱动程序的跨平台的可移植性,我们应该使用Linux中特定的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问

读写I/O的函数如下所示:

a -- writel()

writel()往内存映射的 I/O 空间上写数据,wirtel()  I/O 上写入 32 位数据 (4字节)。

原型:void writel (unsigned char data , unsigned short addr )

b -- readl()

readl() 从内存映射的 I/O 空间上读数据,readl 从 I/O 读取 32 位数据 ( 4 字节 )。
 
原型:unsigned char readl (unsigned int addr )

变量    addr  是 I/O 地址。

返回值 : 从 I/O 空间读取的数值。

具体定义如下:

[cpp] view plaincopy
  1. #define readb __raw_readb
  2. #define readw(addr) __le16_to_cpu(__raw_readw(addr))
  3. #define readl(addr) __le32_to_cpu(__raw_readl(addr))
  4. #ifndef __raw_readb
  5. static inline u8 __raw_readb(const volatile void __iomem *addr)
  6. {
  7. return *(const volatile u8 __force *) addr;
  8. }
  9. #endif
  10. #ifndef __raw_readw
  11. static inline u16 __raw_readw(const volatile void __iomem *addr)
  12. {
  13. return *(const volatile u16 __force *) addr;
  14. }
  15. #endif
  16. #ifndef __raw_readl
  17. static inline u32 __raw_readl(const volatile void __iomem *addr)
  18. {
  19. return *(const volatile u32 __force *) addr;
  20. }
  21. #endif
  22. #define writeb __raw_writeb
  23. #define writew(b,addr) __raw_writew(__cpu_to_le16(b),addr)
  24. #define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)

三、使用实例

还是拿我们写PWM驱动的实例来解析

1、这里我们先定义了一些寄存器,这里使用的地址均是物理地址:

[cpp] view plaincopy
  1. #define GPD0CON       0x114000a0
  2. #define TIMER_BASE    0x139D0000
  3. #define TCFG0         0x0000
  4. #define TCFG1         0x0004
  5. #define TCON          0x0008
  6. #define TCNTB0        0x000C
  7. #define TCMPB0        0x0010

2、为了使用内存映射,我们需先定义指针用来保存内存映射后的地址:

[cpp] view plaincopy
  1. static unsigned int *gpd0con;
  2. static void *timer_base;

注意:这里timer_base 指针指向的类型设为 void, 主要因为上面使用了地址偏移,使用void 更有利于我们使用;

3、使用ioremap() 函数进行内存映射,并将映射的地址赋给我们刚才定义的指针

[cpp] view plaincopy
  1. gpd0con = ioremap(GPD0CON,4);
  2. timer_base = ioremap(TIMER_BASE,0x14);

4、得到地址后,可以调用 writel() 、readl() 函数进行相应的操作

[cpp] view plaincopy
  1. writel ((readl(gpd0con)&~(0xf<<0)) | (0x2<<0),gpd0con);
  2. writel ((readl(timer_base +TCFG0  )&~(0xff<<0)) | (0xff <<0),timer_base +TCFG0);
  3. writel ((readl(timer_base +TCFG1 )&~(0xf<<0)) | (0x2 <<0),timer_base +TCFG1 );
  4. writel (500, timer_base +TCNTB0  );
  5. writel (250, timer_base +TCMPB0 );
  6. writel ((readl(timer_base +TCON )&~(0xf<<0)) | (0x2 <<0),timer_base +TCON );

可以看到,这里先从相应的地址中读取数据,修改完毕后,再利用writel函数进行数据写入。

Linux 字符设备驱动开发基础(五)—— ioremap() 函数解析相关推荐

  1. Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动

    现在,我们来编写自己第一个字符设备驱动 -- 点亮LED.(不完善,后面再完善) 硬件平台:Exynos4412(FS4412) 编写驱动分下面几步: a -- 查看原理图.数据手册,了解设备的操作方 ...

  2. Linux 字符设备驱动开发基础(六)—— VFS 虚拟文件系统解析

    一.VFS 虚拟文件系统基础概念 Linux 允许众多不同的文件系统共存,并支持跨文件系统的文件操作,这是因为有虚拟文件系统的存在.虚拟文件系统,即VFS(Virtual File System)是 ...

  3. Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动

    编写驱动的第一步仍是看原理图: 可以看到,该蜂鸣器由 GPD0_0 来控制 ,查手册可知该I/O口由Time0 来控制,找到相应的寄存器: a -- I/O口寄存器及地址 GPD0CON  0x114 ...

  4. Linux 字符设备驱动开发基础(三)—— read()、write() 相关函数解析

    我们在前面讲到了file_operations,其是一个函数指针的集合,用于存放我们定义的用于操作设备的函数的指针,如果我们不定义,它默认保留为NULL.其中有最重要的几个函数,分别是open().r ...

  5. Linux 字符设备驱动开发基础(四)—— ioctl() 函数解析

    解析完 open.close.read.write 四个函数后,终于到我们的 ioctl() 函数了 一. 什么是ioctl ioctl是设备驱动程序中对设备的I/O通道进行管理的函数.所谓对I/O通 ...

  6. Linux 字符设备驱动开发--内存读写操作

    学习Linux的累计时间已经有两年多了,工作关系,学习的过程总是断断续续的,现在整理一下,下面要分享的是一个简单的linux驱动程序,将内存当作一个虚拟的设备去读写,没有什么实际的用处,像hello ...

  7. Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

    前面在 Linux 字符设备驱动开发基础 (三)-- 字符设备驱动结构(中) ,我们已经介绍了两种重要的数据结构 struct inode{...}与 struct file{...} ,下面来介绍另 ...

  8. Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)

    文章目录 系列文章目录 前言 正文 Device Tree Overlays:"插件"设备树 传统设备树 "插件"设备树 使用前提 案例说明 设备树:foo.d ...

  9. Linux字符设备驱动详解四(使用自属的xbus驱动总线)

    文章目录 系列文章目录 前言 驱动目录 正文 驱动总线 总线管理 总线注册 设备注册 驱动注册 代码示例 总结 系列文章目录 Linux字符设备驱动详解 Linux字符设备驱动详解二(使用设备驱动模型 ...

最新文章

  1. Vim 相关插件整理
  2. Qt学习五 - 对话框
  3. iOS----JSON解析
  4. 1.2开发文档简读,了解全貌.mp4
  5. 利用类定义一个指针会调用默认构造函数吗_C++的拷贝构造函数
  6. SpringCloud 教程 | 第一篇: 服务的注册与发现Eureka
  7. 纳德拉:微软正计划“终极移动设备”
  8. 【MyBatis笔记】01-MyBatis入门程序
  9. 网络爬虫随记:2018-03-12启(refreshing)
  10. Oracle 统计信息收集
  11. WaitForMultipleObjects、WaitForSingleObject、GetExitCodeThread
  12. Python中关于with open file as 的用法
  13. 【期末大作业】基于HTML+CSS+JavaScript南京大学网页校园教育网站html模板(3页)
  14. 数据库设计-多级栏目(标题)分类设计
  15. 如何查询中文期刊影响因子
  16. 第八届蓝桥杯(国赛)——瓷砖样式
  17. 理科爱好者杂志理科爱好者杂志社理科爱好者编辑部2022年第3期目录
  18. export命令在Mac Pycharm上如何设置环境变量!_ CodingPark编程公园
  19. php 项目总结,项目总结
  20. 奶茶介绍-网页实验报告

热门文章

  1. Twitter Storm 序列化
  2. js合并同类数组里面的对象_通过同类群组保留估算客户生命周期价值
  3. 5888. 网络空闲的时刻
  4. Docker 入门(1)虚拟化和容器
  5. 组件分页_如何创建分页组件
  6. 代码走查和代码审查_如何避免代码审查陷阱降低生产率
  7. c++ 时间序列工具包_我的时间序列工具包
  8. PWA - service worker - Workbox(未完)
  9. 20175305张天钰 《java程序设计》第四周课下测试总结
  10. centOS配置国内镜像