Linux系统的ioremap()函数详解【包含对页对齐地址的详解】

问题引出和概述

示例代码如下:

#include <asm/io.h>

CCM_CCGR1                               = ioremap(0x20C406C, 4);
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
GPIO5_GDIR                              = ioremap(0x020AC000 + 0x4, 4);
GPIO5_DR                                = ioremap(0x020AC000 + 0, 4);

这段示例代码来自于博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/144973219
注意:ioremap()使用时,要包含相应的头文件,在IMX6ULL开发时使用的ioremap函数要包含的头文件是#include <asm/io.h>

这些代码显然就是把寄存器的物理地址映射到Linux的虚拟地址中,在前面的博文
IMX6ULL开发板基础实验:Framebuffer驱动程序的简单应用实例代码详细分析中,用到了函数mmap,也是把地址映射到虚拟地址,不同的是mmap映射的是设备的内存,而这里咱们需要映射的是CPU的物理地址。
对于函数mmap的讲解详见我的另一篇博文
嵌入式Linux开发中的内存映射函数mmap详解?什么叫内存映射?什么叫对设备的显式和隐式读写操作?什么叫程序的虚拟内存?

ioremap()函数详解

下面对ioremap()函数进行详细解释:

以上面的第1行代码为例进行解释:

CCM_CCGR1 = ioremap(0x20C406C, 4);

在 Linux 内核中,ioremap 是一个重要的函数,用于将物理地址映射到内核虚拟地址空间,允许内核访问特定的硬件寄存器或设备的物理地址。


ioremap 的功能

  1. 目的
    物理地址不能直接被内核访问,尤其是在现代的虚拟内存系统中。ioremap 提供了一种方法将设备寄存器的物理地址映射到内核空间的虚拟地址。

  2. 应用场景

    • 访问硬件寄存器。
    • 控制外设(如 GPIO、时钟控制、DMA 控制器等)。
    • 映射内存映射的设备(如 FPGA 或 SoC 外设)。
  3. 返回值

    • 成功:返回映射后的内核虚拟地址(void __iomem * 类型)。
    • 失败:返回 NULL

ioremap 函数原型

void __iomem *ioremap(resource_size_t phys_addr, unsigned long size);
参数说明:
  1. phys_addr

    • 要映射的设备寄存器或外设的物理地址。
    • 有些时候要求这个参数是页对齐的物理地址(什么是页对齐的物理地址,后面有详述,如果这里传入的不是页对齐地址而程序又能正常运行,那么说明ioremap函数在其内部进行了页对齐处理)。
  2. size

    • 要映射的地址范围的大小,以字节为单位。
    • 通常与外设的寄存器大小一致。
返回值:
  • 虚拟地址
    一个可以在内核空间访问的虚拟地址,返回值需要用 ioread/iowrite 或直接操作进行访问。
  • 如果映射失败,返回 NULL

代码中的解释

在你的代码中:

CCM_CCGR1 = ioremap(0x20C406C, 4);

这是将物理地址 0x20C406C 映射到一个 4 字节大小的内核虚拟地址区域中,并将虚拟地址赋值给指针 CCM_CCGR1,以便后续通过 *CCM_CCGR1 来访问硬件寄存器。

使用步骤:
  1. 使用 ioremap 映射地址:

    volatile unsigned int *reg;
    reg = ioremap(0x20C406C, 4);
    
  2. 访问寄存器内容:

    *reg |= (1 << 3);  // 设置某一位
    
  3. 取消映射(释放资源):

    iounmap(reg);
    

注意事项

  1. 映射范围大小
    必须指定正确的 size,避免超出设备寄存器范围。多映射可能导致访问无效区域。

  2. 对称性
    每次 ioremap 调用后,必须确保在不再需要时调用 iounmap 释放映射的虚拟地址空间。

  3. 地址对齐
    输入的 phys_addr 应该是页对齐的,否则可能出现不可预期的问题。

  4. 读写时的同步问题

    • 对映射的寄存器地址进行操作时,需注意使用内存屏障(mb()/wmb()/rmb())来确保操作顺序正确。
    • 或者使用专门的 ioread/iowrite 函数来确保访问的原子性。

小提示

ioremap 是专门为 设备内存 映射设计的。如果需要访问主内存中的地址或缓冲区,应使用普通的内存操作,而不是 ioremap


与相关函数的对比

  • ioremap_nocache
    旧版本中专用于非缓存的映射,现在已经被合并到 ioremap 中,ioremap 默认进行非缓存映射。

  • phys_to_virt
    用于直接将物理地址转换为内核虚拟地址,但仅适用于内存区域,不能用于 I/O 寄存器或设备地址。

  • vmalloc/kmalloc
    分配内存而不是映射地址,与 ioremap 用途完全不同。


小结

在代码中:

CCM_CCGR1 = ioremap(0x20C406C, 4);

这一行代码的作用是将硬件寄存器的物理地址 0x20C406C 映射到内核的虚拟地址空间,并将映射后的地址赋值给 CCM_CCGR1,从而让程序可以通过普通的内存访问操作(如 *CCM_CCGR1)直接操作寄存器的内容。

页对齐的地址详解

在Linxu系统中,虚拟地址空间被分成固定大小的页(例如 4KB)来进行管理,比如Linux的虚拟内存就是以固定大小的页来进行管理的,关于什么是Linux的虚拟内存,请参考我的另一篇博文(https://blog.youkuaiyun.com/wenhao_ir/article/details/144489705)。

页的大小由系统架构决定,通常是 4KB、2MB 或 1GB 等(在 x86 和 ARM 系统中,默认页大小通常为 4KB)。如果页大小为 4KB,一个页对齐的地址可以是 0x00000x10000x2000【即二进制表示下最低的12位必须为0,十六进制表示下最低3位必须为0】,但不能是 0x12340x1FFF

“页对齐的地址” 是指一个地址必须是内存页大小的整数倍。

在这里,我们使用函数ioremap()把物理地址映射到Linux系统的虚拟地址空间。其第一个参数resource_size_t phys_addr代表物理地址,这个物理地址在有时候要求是页对齐的,如果传入的不是页对齐的物理地址但程序又能跑通,那么说明函数ioremap()内部进行了页对齐调整处理。

您肯定要问,不是虚拟地址空间才是以页为单位管理的吗?怎么要求被映射的物理地址也是页对齐的呢?
这是因为操作系统也会将物理内存(即物理地址空间)按页大小划分为物理内存块(Frame)。虽然物理地址本身不强制分页,但操作系统为了方便管理,也会以页为单位分配和管理物理地址空间,所以函数ioremap()的第一个参数resource_size_t phys_addr有时会严格要求这个物理地址也是页对齐的,如果传入的不是页对齐的物理地址但程序又能跑通,那么说明函数ioremap()内部进行了页对齐调整处理。

在下面的代码中:

CCM_CCGR1 = ioremap(0x20C406C, 4);

地址0x20C406C的十六进制表示的最后三位不是0,所以不是一个页对齐地址,但实测我们的程序是能跑通的,说明咱们这里遇到的ioremap()函数的内部是作了对齐处理的。

如果你以后遇到了没有自动处理的ioremap()函数,那么你需要像下面这样作调整:

#define PHYS_ADDR 0x20C406C
#define SIZE 4

void __iomem *base;
unsigned int *reg;
unsigned int offset;

// 对齐地址
unsigned int aligned_addr = PHYS_ADDR & ~(0x1000 - 1);
offset = PHYS_ADDR - aligned_addr;

// 映射页对齐后的地址
base = ioremap(aligned_addr, offset + SIZE);
if (!base) {
    printk("ioremap failed\n");
    return -1;
}

// 访问寄存器时加上偏移
reg = (unsigned int *)(base + offset);
*reg |= (1 << 3); // 操作寄存器

// 释放映射
iounmap(base);


上面的代码中, aligned_addr代表向下对齐的页的起始地址,PHYS_ADDR 代表传入的物理地址,注意为了保证能映射到想要的物理地址,ioremap的第二个参数,即要映射的地址范围的大小也要调大,其具体的值为offset + SIZE

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值