Linux 中 ARM TCM(紧耦合内存)处理

作者:Linus Walleij <linus.walleij@stericsson.com>

一些 ARM SoC 具有所谓的 TCM(紧耦合内存)。这通常只是 ARM 处理器内部的少量(4-64)KiB 的 RAM。

由于嵌入在 CPU 内部,TCM 具有哈佛架构,因此存在 ITCM(指令 TCM)和 DTCM(数据 TCM)。DTCM 不能包含任何指令,但 ITCM 实际上可以包含数据。DTCM 或 ITCM 的大小最少为 4KiB,因此典型的最小配置为 4KiB ITCM 和 4KiB DTCM。

ARM CPU 具有特殊的寄存器来读取 TCM 内存的状态、物理位置和大小。arch/arm/include/asm/cputype.h 定义了一个 CPUID_TCM 寄存器,您可以从系统控制协处理器读取该寄存器。可以在 http://infocenter.arm.com 上找到 ARM 的文档,搜索“TCM Status Register”以查看所有 CPU 的文档。读取此寄存器可以确定机器中是否存在 ITCM(位 1-0)和/或 DTCM(位 17-16)。

此外,还有一个 TCM 区域寄存器(在 ARM 站点搜索“TCM Region Registers”),它可以报告和修改 TCM 内存在运行时的位置大小。这用于读取和修改 TCM 的位置和大小。请注意,这不是 MMU 表:您实际上是在移动 TCM 的物理位置。在您放置它的位置,它将屏蔽 CPU 的任何底层 RAM,因此通常明智的做法是不要使任何物理 RAM 与 TCM 重叠。

然后可以使用 MMU 将 TCM 内存再次重新映射到另一个地址,但请注意,TCM 通常用于关闭 MMU 的情况。为避免混淆,当前的 Linux 实现会将 TCM 从内核指定的位置以 1 对 1 的方式从物理内存映射到虚拟内存。目前,Linux 会将 ITCM 映射到 0xfffe0000 及以上,将 DTCM 映射到 0xfffe8000 及以上,最多支持 32KiB 的 ITCM 和 32KiB 的 DTCM。

较新版本的区域寄存器还支持将这些 TCM 分为两个单独的库,例如,一个 8KiB 的 ITCM 分为两个 4KiB 的库,每个库都有自己的控制寄存器。这样做的目的是能够锁定和隐藏其中一个库,供安全世界 (TrustZone) 使用。

TCM 用于以下几个方面

  • 需要确定性定时且不能等待缓存未命中的 FIQ 和其他中断处理程序。

  • 空闲循环,其中所有外部 RAM 设置为自刷新保留模式,因此只有片上 RAM 可供 CPU 访问,然后我们挂在 ITCM 内等待中断。

  • 其他操作,这意味着关闭或重新配置外部 RAM 控制器。

在 <asm/tcm.h> 中有一个在 ARM 架构上使用 TCM 的接口。使用此接口可以

  • 定义 ITCM 和 DTCM 的物理地址和大小。

  • 标记要编译到 ITCM 中的函数。

  • 标记要分配给 DTCM 和 ITCM 的数据和常量。

  • 将剩余的 TCM RAM 添加到具有 gen_pool_create()gen_pool_add() 的特殊分配池,并为此内存提供 tcm_alloc() 和 tcm_free()。这样的堆非常适合在关闭设备电源域时保存设备状态之类的事情。

具有 TCM 内存的机器应从 arch/arm/Kconfig 中为其自身选择 HAVE_TCM。需要使用 TCM 的代码应 #include <asm/tcm.h>

进入 itcm 的函数可以这样标记:int __tcmfunc foo(int bar);

由于这些函数被标记为 long_calls,并且您可能希望在 TCM 内部本地调用函数而不会浪费空间,因此还有 __tcmlocalfunc 前缀,它将使调用相对。

进入 dtcm 的变量可以这样标记

int __tcmdata foo;

常量可以这样标记

int __tcmconst foo;

要将汇编程序放入 TCM 中,只需使用

.section ".tcm.text" or .section ".tcm.data"

分别。

示例代码

#include <asm/tcm.h>

/* Uninitialized data */
static u32 __tcmdata tcmvar;
/* Initialized data */
static u32 __tcmdata tcmassigned = 0x2BADBABEU;
/* Constant */
static const u32 __tcmconst tcmconst = 0xCAFEBABEU;

static void __tcmlocalfunc tcm_to_tcm(void)
{
      int i;
      for (i = 0; i < 100; i++)
              tcmvar ++;
}

static void __tcmfunc hello_tcm(void)
{
      /* Some abstract code that runs in ITCM */
      int i;
      for (i = 0; i < 100; i++) {
              tcmvar ++;
      }
      tcm_to_tcm();
}

static void __init test_tcm(void)
{
      u32 *tcmem;
      int i;

      hello_tcm();
      printk("Hello TCM executed from ITCM RAM\n");

      printk("TCM variable from testrun: %u @ %p\n", tcmvar, &tcmvar);
      tcmvar = 0xDEADBEEFU;
      printk("TCM variable: 0x%x @ %p\n", tcmvar, &tcmvar);

      printk("TCM assigned variable: 0x%x @ %p\n", tcmassigned, &tcmassigned);

      printk("TCM constant: 0x%x @ %p\n", tcmconst, &tcmconst);

      /* Allocate some TCM memory from the pool */
      tcmem = tcm_alloc(20);
      if (tcmem) {
              printk("TCM Allocated 20 bytes of TCM @ %p\n", tcmem);
              tcmem[0] = 0xDEADBEEFU;
              tcmem[1] = 0x2BADBABEU;
              tcmem[2] = 0xCAFEBABEU;
              tcmem[3] = 0xDEADBEEFU;
              tcmem[4] = 0x2BADBABEU;
              for (i = 0; i < 5; i++)
                      printk("TCM tcmem[%d] = %08x\n", i, tcmem[i]);
              tcm_free(tcmem, 20);
      }
}