요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
==================================================
ARM TCM (Tightly-Coupled Memory) handling in Linux
==================================================
Written by Linus Walleij <[email protected]>
Some ARM SoCs have a so-called TCM (Tightly-Coupled Memory).
This is usually just a few (4-64) KiB of RAM inside the ARM
processor.
Due to being embedded inside the CPU, the TCM has a
Harvard-architecture, so there is an ITCM (instruction TCM)
and a DTCM (data TCM). The DTCM can not contain any
instructions, but the ITCM can actually contain data.
The size of DTCM or ITCM is minimum 4KiB so the typical
minimum configuration is 4KiB ITCM and 4KiB DTCM.
ARM CPUs have special registers to read out status, physical
location and size of TCM memories. arch/arm/include/asm/cputype.h
defines a CPUID_TCM register that you can read out from the
system control coprocessor. Documentation from ARM can be found
at http://infocenter.arm.com, search for "TCM Status Register"
to see documents for all CPUs. Reading this register you can
determine if ITCM (bits 1-0) and/or DTCM (bit 17-16) is present
in the machine.
There is further a TCM region register (search for "TCM Region
Registers" at the ARM site) that can report and modify the location
size of TCM memories at runtime. This is used to read out and modify
TCM location and size. Notice that this is not a MMU table: you
actually move the physical location of the TCM around. At the
place you put it, it will mask any underlying RAM from the
CPU so it is usually wise not to overlap any physical RAM with
the TCM.
The TCM memory can then be remapped to another address again using
the MMU, but notice that the TCM is often used in situations where
the MMU is turned off. To avoid confusion the current Linux
implementation will map the TCM 1 to 1 from physical to virtual
memory in the location specified by the kernel. Currently Linux
will map ITCM to 0xfffe0000 and on, and DTCM to 0xfffe8000 and
on, supporting a maximum of 32KiB of ITCM and 32KiB of DTCM.
Newer versions of the region registers also support dividing these
TCMs in two separate banks, so for example an 8KiB ITCM is divided
into two 4KiB banks with its own control registers. The idea is to
be able to lock and hide one of the banks for use by the secure
world (TrustZone).
TCM is used for a few things:
- FIQ and other interrupt handlers that need deterministic
timing and cannot wait for cache misses.
- Idle loops where all external RAM is set to self-refresh
retention mode, so only on-chip RAM is accessible by
the CPU and then we hang inside ITCM waiting for an
interrupt.
- Other operations which implies shutting off or reconfiguring
the external RAM controller.
There is an interface for using TCM on the ARM architecture
in <asm/tcm.h>. Using this interface it is possible to:
- Define the physical address and size of ITCM and DTCM.
- Tag functions to be compiled into ITCM.
- Tag data and constants to be allocated to DTCM and ITCM.
- Have the remaining TCM RAM added to a special
allocation pool with gen_pool_create() and gen_pool_add()
and provide tcm_alloc() and tcm_free() for this
memory. Such a heap is great for things like saving
device state when shutting off device power domains.
A machine that has TCM memory shall select HAVE_TCM from
arch/arm/Kconfig for itself. Code that needs to use TCM shall
#include <asm/tcm.h>
Functions to go into itcm can be tagged like this:
int __tcmfunc foo(int bar);
Since these are marked to become long_calls and you may want
to have functions called locally inside the TCM without
wasting space, there is also the __tcmlocalfunc prefix that
will make the call relative.
Variables to go into dtcm can be tagged like this::
int __tcmdata foo;
Constants can be tagged like this::
int __tcmconst foo;
To put assembler into TCM just use::
.section ".tcm.text" or .section ".tcm.data"
respectively.
Example code::
#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);
}
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
ARM TCM (Tightly-Coupled Memory) handling in Linux
1-6Linus Walleij `<[email protected]>`가 작성한 ARM TCM Linux 처리 문서입니다.
ITCM과 DTCM 구조
7-17일부 ARM SoC에는 ARM processor 내부에 보통 4~64KiB 정도의 작은 TCM(Tightly-Coupled Memory)이 있습니다.
CPU 내부에 있으므로 TCM은 Harvard architecture를 따르고 instruction TCM인 ITCM과 data TCM인 DTCM으로 나뉩니다. DTCM에는 instruction을 넣을 수 없지만 ITCM에는 data도 넣을 수 있습니다. 각각 최소 크기가 4KiB이므로 일반적인 최소 구성은 ITCM 4KiB + DTCM 4KiB입니다.
TCM status register
18-26ARM CPU에는 TCM의 status, physical location, size를 읽는 special register가 있습니다. `arch/arm/include/asm/cputype.h`는 system-control coprocessor에서 읽을 `CPUID_TCM` register를 정의합니다.
ARM 문서의 `TCM Status Register`를 참고하면 이 register의 ITCM presence bit 1-0과 DTCM presence bit 17-16으로 machine에 각 memory가 있는지 판단할 수 있습니다.
TCM region register
27-35`TCM Region Registers`는 runtime에 TCM memory의 location과 size를 보고하고 수정합니다. 이것은 MMU table을 바꾸는 일이 아니라 TCM의 실제 physical location을 이동하는 것입니다. 배치한 위치에서는 아래의 RAM이 CPU에 가려지므로 physical RAM과 겹치지 않게 하는 편이 좋습니다.
Linux의 1:1 mapping
36-43TCM을 MMU로 다른 address에 다시 mapping할 수 있지만 TCM은 MMU를 끈 상황에서도 자주 사용됩니다. 혼동을 피하려고 현재 Linux 구현은 kernel이 지정한 위치에서 physical과 virtual을 1:1로 mapping합니다.
| 영역 | Linux address | 최대 지원 크기 |
|---|---|---|
| ITCM | `0xfffe0000` 이상 | 32KiB |
| DTCM | `0xfffe8000` 이상 | 32KiB |
분할 bank와 TrustZone
44-49새 region register는 TCM을 두 bank로 나눌 수도 있습니다. 예를 들어 8KiB ITCM을 control register가 각각 있는 4KiB bank 두 개로 나눕니다. 목적은 한 bank를 lock하고 숨겨 secure world, 즉 TrustZone이 쓰게 하는 것입니다.
TCM 사용 사례
50-62- cache miss를 기다릴 수 없고 deterministic timing이 필요한 FIQ와 다른 interrupt handler
- 외부 RAM 전체를 self-refresh retention mode에 두어 on-chip RAM만 접근 가능한 동안 ITCM 안에서 interrupt를 기다리는 idle loop
- 외부 RAM controller를 끄거나 재구성해야 하는 다른 operation
<asm/tcm.h> interface
63-77ARM architecture의 TCM interface는 `<asm/tcm.h>`에 있으며 다음을 지원합니다.
- ITCM과 DTCM의 physical address와 size 정의
- ITCM에 compile할 function tagging
- DTCM·ITCM에 할당할 data와 constant tagging
- 남는 TCM RAM을 `gen_pool_create()`와 `gen_pool_add()`로 special allocation pool에 넣고 `tcm_alloc()`·`tcm_free()` 제공
이 heap은 device power domain을 끌 때 device state를 저장하는 용도 등에 적합합니다.
Kconfig와 function tag
78-88TCM memory가 있는 machine은 `arch/arm/Kconfig`에서 스스로 `HAVE_TCM`을 select해야 합니다. TCM을 쓰는 code는 `#include <asm/tcm.h>`를 포함합니다.
ITCM에 둘 function에는 다음과 같이 `__tcmfunc` tag를 붙입니다.
int __tcmfunc foo(int bar);
이 function은 `long_calls`가 됩니다. TCM 안에서 local function을 호출하면서 공간을 낭비하지 않으려면 call을 relative로 만드는 `__tcmlocalfunc` prefix를 사용할 수 있습니다.
Data, constant and assembly tags
89-103DTCM에 둘 variable에는 `__tcmdata`를 붙입니다.
int __tcmdata foo;
constant에는 `__tcmconst`를 붙입니다.
int __tcmconst foo;
assembler를 TCM에 넣을 때는 해당 section을 사용합니다.
.section ".tcm.text" or .section ".tcm.data"
각각 `.tcm.text`는 instruction 쪽, `.tcm.data`는 data 쪽 배치에 대응합니다.
Example code
104-161다음 원문 예제는 uninitialized·initialized DTCM data와 constant를 선언하고, ITCM local/function tag를 사용한 뒤, 남은 TCM pool에서 20 byte를 allocate해 값을 쓰고 free하는 전체 흐름을 보여 줍니다.
#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);
}
}
요약과 해설
tcm.rst:1-161TCM은 cache보다 작은 대신 접근 시간이 deterministic하고 외부 RAM controller 상태와 독립적입니다. Linux는 MMU-off code와의 혼동을 줄이기 위해 물리·가상 주소를 같은 위치에 두며 code, data, allocator의 세 방식으로 활용합니다.
CPU 내부 memory가 linker tag와 gen_pool interface를 통해 code와 data에 연결됩니다.
두 memory는 허용하는 content와 대표 tag가 다릅니다.