文章目录

  • 1.1 背景
  • 1.1.1 ARM32 内存空间
  • 1.1.2 ioremap 实现
  • 1.1.3 Linux内存属性


上篇文章:ARM Linux 内存管理入门及渐进 4 - 常用接口实现(memcpy/copy_to_user)

1.1 背景

在编写 linux 驱动过程中,不可避免的会涉及操作外设,而外设的地址空间与 DDR的地址空间一般不连续,在 linux上电时,并不会为外设地址空间建立页表,又因为linux 访问内存使用的都是虚拟地址,因此如果想访问外设的寄存器(一般包括数据寄存器、控制寄存器与状态寄存器),需要在驱动初始化中将外设所处的物理地址映射为虚拟地址,linux 为应对该问题提供了较多接口以应对不同的场景需求,arch/arm64(arm)/include/asm/io.h中有如下几种 ioremap 接口:

ioremap
ioremap_wc
devm_ioremap
devm_ioremap_resource
  • ioremap:用来映射 memory type 为 device memory 的设备,同时不使用cache(device memory本身就没有 cacheable 这个属性),即 CPU 的读写操作直接操作设备内存。
  • ioremap_nocache:的实现与 ioremap 完全相同,保留该符号是因为向后兼容使用ioremap_nocache接口的驱动程序。
  • ioremap_cached 用来映射 memory type 为 normal memory 的设备,同时使用cache,这会提高内存的访问速度,提高系统的性能。

ARM Linux 引入设备树特性后,一些支持设备树的设备驱动不再使用直接 ioremap(),改用 drivers/of/address.c/of_iomap(),of_iomap() 的内部仍然会调用 ioremap()

1.1.1 ARM32 内存空间

通常进程的4GB内存空间被人为的分为两个部分:

  • 用户空间与, 用户空间地址分布从0到3GB;
  • 内核空间,3GB-4GB。
    内核空间中,从3G到 vmalloc_start 这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表mem_map等等)。在物理内存映射区之后,就是vmalloc区域,vmalloc_end 的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射)。

1.1.2 ioremap 实现

arch/arm64/include/asm/io.h

#define ioremap(addr, size)             __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
#define ioremap_nocache(addr, size)     __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
#define ioremap_wc(addr, size)          __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC))
#define ioremap_wt(addr, size)          __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
#define iounmap                         __iounmap

从上面的接口定义看,除了addr和size外还有一个__pgprot()参数,上面接口中参数类型有:

  • PROT_DEVICE_nGnRE;
  • PROT_NORMAL_NC。
    两种类型。实际在 pgtable-prot.h 中还有 PROT_NORMAL_WTPROT_NORMAL。ioremap_xxx 接口的功能属性都是由__pgprot()决定的,那__pgprot()具体是什么含义呢?

1.1.3 Linux内存属性

在内核源码memory.h中有定义ioremap接口中可用的内存类型:

arch/arm64/include/asm/memory.h

/*
 * Memory types available.
 */
#define MT_DEVICE_nGnRnE        0
#define MT_DEVICE_nGnRE         1
#define MT_DEVICE_GRE           2
#define MT_NORMAL_NC            3
#define MT_NORMAL               4
#define MT_NORMAL_WT            5

/*
 * Memory types for Stage-2 translation
 */
#define MT_S2_NORMAL            0xf
#define MT_S2_DEVICE_nGnRE      0x1

上面 ioremap_xxx 接口中用到的属性参数主要是PROT_DEVICE_nGnREPROT_NORMAL_NC。看下这两个prot_val分别代表什么含义:

#define PROT_DEFAULT            (_PROT_DEFAULT | PTE_MAYBE_NG)
#define PROT_SECT_DEFAULT       (_PROT_SECT_DEFAULT | PMD_MAYBE_NG)

#define PROT_DEVICE_nGnRnE      (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_DEVICE_nGnRnE))
#define PROT_DEVICE_nGnRE       (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_DEVICE_nGnRE))
#define PROT_NORMAL_NC          (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_NC))
#define PROT_NORMAL_WT          (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL_WT))
#define PROT_NORMAL             (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_WRITE | PTE_ATTRINDX(MT_NORMAL))

#define PROT_SECT_DEVICE_nGnRE  (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_DEVICE_nGnRE))
#define PROT_SECT_NORMAL        (PROT_SECT_DEFAULT | PMD_SECT_PXN | PMD_SECT_UXN | PMD_ATTRINDX(MT_NORMAL))
#define PROT_SECT_NORMAL_EXEC   (PROT_SECT_DEFAULT | PMD_SECT_UXN | PMD_ATTRINDX(MT_NORMAL))

参照ARMv8手册中对内存属性的描述,内存可以分为DEVICENORMAL两大类型以及Device memory依据是否可合并等属性。

  • Normal型:sram或者dram那样的内存空间,一般都是过cache的(当然也可不过cache,如外设访问的地址空间,标记为NC)
  • Device型:设备寄存器那样的io空间,都不会过cache。

Device属性的内存空间还有下面三种子属性,都有打开和关闭的定义。

  • G(gather:对多个memory的访问可以合并) nG与之相反;
  • R(Reordering:对内存访问指令进行重排) nR与之相反;
  • E(Early Write Acknowledgement hint:写操作的ack可提早应答) nE与之相反。

MAIR寄存器定义如下:

__ioremap ioremap_cached_PROT_NORMAL_NC

Linux 预先定义了6种内存属性,分别存在MAIR寄存器的attr0~attr5。内存页表属性部分可以选择这个寄存器的某个index,范围(0~5)作为自己的属性。

上篇文章:ARM Linux 内存管理入门及渐进 4 - 常用接口实现(memcpy/copy_to_user)