Linux 字符设备驱动开发基础 —— ioremap() 函数解析

转载自 Linux 字符设备驱动开发基础(五)—— ioremap() 函数解析_zqixiao_09的博客-CSDN博客_ioremap函数

linux内存操作————ioremap函数_fzjcycp的博客-CSDN博客

 

 

linux上的驱动程序,是基于操作系统之上的,它并不直接和底层的硬件打交道,但是我们写的驱动必须能使硬件“跑”起来,即与硬件紧密相连。

就拿最简单的LED驱动来说,我们的驱动程序是在虚拟的内存上面跑的,但是最终,LED的点亮还是必须靠GPIO管脚的高低电平来控制。那么,我们的虚拟的内存怎么才能和实际的硬件上面的寄存器对应起来呢?

这篇要写的就是ioremap这个映射函数,它可以将我们硬件上面的寄存器,映射为虚拟的内存,从而使驱动程序在我们的虚拟的内存中运行。

 

 

 

对于一个系统来讲,会有很多的外设,这些外设的管理都是通过CPU完成。那么CPU在这个过程中是如何找到外设呢?

尽管在一个系统中会有诸多的外设,在每个外设的接口电路中会有多个端口。但是如果系统能够每个端口都被赋予一个具体的地址值,那么在系统中就能轻易的找到任何一个外设。系统在管理的时候,不管是内存还是外设都需要分配一个内存地址。对于一个32bit的系统来讲,可寻址的范围为2^32=4G的地址空间。

既然说到地址空间,就要明确地址空间的种类:物理地址、总线地址、虚拟地址。

(1)物理地址

CPU地址总线传来的地址,由硬件电路控制其具体含义。物理地址中很大一部分是留给内存条中内存的,但也常被映射到其他存储器上(如显存、bios等)。在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址线上。

(2)总线地址

总线的地址线或在地址周期上产生的信号。外设使用的是地址总线,cpu使用的是物理地址。

物理地址和总线地址之间的关系有系统设计决定的。在X86平台上,物理地址就是总线地址,这是因为它们共享相同的地址空间。在其他平台上,可能需要转换/映射。

(3)虚拟地址

现代操作系统普遍采用虚拟内存管理(virtual memory management)机制,这需要MMU的支持。MMU通常是CPU的一部分,如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(物理内存)接收,这成为物理地址;如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址,而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。

linux中,进程的4GB内存分为用户空间和内核空间。用户空间分布为1~3GB剩下的1GB为内核空间。程序员只能使用虚拟地址。系统中每个进程有各自的私有用户控件(0~3GB),这个空间对系统中的其他进程是不可见的。

编址方式

外设都是通过读写设备上的寄存器来进行工作的,外设寄存器也称为“IO端口”,而IO端口的编址方式有两种,独立编址和统一编址。

统一编址:外设接口中的IO寄存器(即IO端口)与主存单元一样看待,每个端口占用一个存储单元的地址,将主存的一部分划出来用作IO地址空间。 统一编址的原理是将IO的端口地址存储器寻址的地址空间范围之内,此方法也成为存储器映像编址。CPU访问一个端口的操作与访问内存的操作相同,也使用访问内存的指令。

独立编址是为端口地址单独开辟一部分地址空间,其访问指令也需要使用单独的指令(不同于内存访问指令)。

 

 

一、 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内存资源。将一个IO地址空间映射到内核的虚拟地址空间上去,便于访问。

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 空间读取的数值。
具体定义如下:

 

 

三、使用实例
还是拿我们写PWM驱动的实例来解析

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

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

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

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

timer_base = ipremap(TIMER_BASE,0x14);

在虚拟地址空间中申请一块起始虚拟地址为timer_base,长度为0x14的连续空间;物理地址的起始地址为TIMER_BASE(0x139D0000),这样物理地址0x139D0000 - 0x139D0014的地址就对应了虚拟地址timer_base - timer_base + 0x14。

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

 

 


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

posted on 2022-03-25 14:11  放空飞翔  阅读(1401)  评论(0)    收藏  举报

导航