首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

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

  • 25-03-05 05:15
  • 2616
  • 13009
blog.csdn.net

问题引出和概述

示例代码如下:

#include 

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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这段示例代码来自于博文 http://iyenn.com/rec/1709453.html
注意:ioremap()使用时,要包含相应的头文件,在IMX6ULL开发时使用的ioremap函数要包含的头文件是#include

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

ioremap()函数详解

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

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

CCM_CCGR1 = ioremap(0x20C406C, 4);
  • 1

在 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
参数说明:
  1. phys_addr:

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

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

代码中的解释

在你的代码中:

CCM_CCGR1 = ioremap(0x20C406C, 4);
  • 1

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

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

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

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

    iounmap(reg);
    
    • 1

注意事项

  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);
  • 1

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

页对齐的地址详解

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

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

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

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

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

在下面的代码中:

CCM_CCGR1 = ioremap(0x20C406C, 4);
  • 1

地址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);


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

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

昊虹嵌入式技术交流群
QQ群名片
注:本文转载自blog.csdn.net的昊虹AI笔记的文章"https://blog.csdn.net/wenhao_ir/article/details/144985338"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top