Linux 中的 ARM TCM(紧耦合内存)处理¶
作者:Linus Walleij <linus.walleij@stericsson.com>
一些 ARM SoC 具有所谓的 TCM(Tightly-Coupled Memory,紧耦合内存)。这通常只是 ARM 处理器内部少量的(4-64 KiB)RAM。
由于嵌入在 CPU 内部,TCM 具有哈佛架构(Harvard-architecture),因此存在 ITCM(指令 TCM)和 DTCM(数据 TCM)。DTCM 不能包含任何指令,但 ITCM 实际上可以包含数据。DTCM 或 ITCM 的最小大小为 4KiB,因此典型的最小配置是 4KiB ITCM 和 4KiB DTCM。
ARM CPU 具有特殊的寄存器来读取 TCM 内存的状态、物理位置和大小。`arch/arm/include/asm/cputype.h` 定义了一个可以从系统控制协处理器中读取的 CPUID_TCM 寄存器。ARM 的文档可以在 http://infocenter.arm.com 找到,搜索“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 分为两个独立的库(bank),例如,一个 8KiB 的 ITCM 被分成两个带有自己控制寄存器的 4KiB 库。这样做的目的是能够锁定并隐藏其中一个库供安全世界(TrustZone)使用。
TCM 用于以下几个方面:
需要确定性时序且不能等待缓存未命中的 FIQ 和其他中断处理程序。
空闲循环(Idle loops),其中所有外部 RAM 都设置为自刷新保持模式,因此只有片上 RAM 可由 CPU 访问,然后我们在 ITCM 内部挂起以等待中断。
意味着关闭或重新配置外部 RAM 控制器的其他操作。
在 ARM 架构上使用 TCM 的接口位于 <asm/tcm.h> 中。使用此接口可以:
定义 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_call,并且您可能希望在 TCM 内部进行本地调用而不浪费空间,因此还有一个 __tcmlocalfunc 前缀,它将使调用变为相对调用(relative)。
要放入 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);
}
}