Also read ARM TCM (Tightly-Coupled Memory) handling in Linux.

Continue reading ARM TCM (Tightly-Coupled Memory) handling in Linux.


Originally, it is a part of the Linux Kernel source code, so it will be treated as GPLv2 (recognition that it should be).

https://www.kernel.org/doc/html/latest/index.html

Licensing documentation

The following describes the license of the Linux kernel source code (GPLv2), how to properly mark the license of individual files in the source tree, as well as links to the full license text.

https://www.kernel.org/doc/html/latest/process/license-rules.html#kernel-licensing

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.

Some ARM SoCs have what is called TCM (Tightly-Coupled Memory). A TCM is usually a very small (4-64) KiB size RAM in an 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.

TCM has a Harvard architecture because it is built inside the CPU. Therefore, there are ITCM (instruction TCM) and DTCM (data TCM). DTCM cannot contain instructions, but ITCM can actually contain data. The minimum size of DTCM and ITCM is 4KiB, so a 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.

The ARM CPU has a special register that reads the state regarding the position and size of the TCM memoriy. In arch / arm / include / asm / cputype.h, CPUID_TCM register for reading from system control coprocessor is defined. Documentation from ARM can be found at http://infocenter.arm.com, and the “TCM Status Register” can be found in the documentation for all CPUs. By reading this register, it is possible to determine whether ITCM (bits 1-0) or / or DTCM (bit 17-16) is valid on 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.

In addition, there is a TCM region register that allows you to report and change the location / size of TCM memory at runtime (search for "TCM Region Registers" on the ARM site). It is used to read and change the location and size of the TCM. Note that this is not an MMU table. It actually moves the physical position around the TCM. Where it is placed, it masks the RAM from the CPU, so it is usually wise not to duplicate the physical RAM and the TCM.

The TCM memory can then be remapped to another address again using the MMU, but notice that the TCM if 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.

The TCM memory can also be reapplied to another address by reusing the MMU. However, keep in mind that the TCM is also used in situations where the MMU is disabled. To avoid this confusion, in the current Linux implementation, TCM maps physical and virtual addresses 1: 1. In current Linux, ITCM maps to 0xfff3000 and DTCM to 0xfffe8000, with 32KiB ITCM and 32KiB DTCM being the largest.

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).

The new version of the region register also has the ability to split the TCM into two banks. For example, an 8KiB ITCM can be split into 4KiB bangs by control registers. This idea makes banks visible or concealed in the 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.

TCM is used for several purposes. --FIQ and other interrupt handlers that can't wait for decisive timing or cache misses --Idle loop with external RAM set to self-refresh retaion mode. Therefore, the CPU is accessible only on chip RAM and waits for interrupts in the ICTM. --Some process for the external RAM controller to terminate or reconfigure.

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 provice 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 ..

The interface for using TCM in ARM architecture is described in <asm / tcm.h>. With this interface, you can: --Definition of physical addresses and locations for ITCM and DTCM. --Tag for function to compile to ITCM --Tags for data and definitions to be placed in DTCM and ITCM. --Use gen_pool_create () and gen_pool_add () to allocate a special allocation pool to the remaining TCM RAM. Such a heap is suitable for holding the state of the device when shutting down the power domain of the device.

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>

If the Machine has a TCM mamory, you need to select HAVE_TCM in arch / arm / Kconfig yourself. Source code that wants to use TCM uses $ include <asm / tcm.h>.

Functions to go into itcm can be tagged like this:

Functions that you want to implement on itcm are 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.

These are marked to be long_callc. There is also a __tcmlocalfunc prefix that makes the calls relative to call the function inside the TCM without wasting space,

Variables to go into dtcm can be tagged like this:

Variables that you want to define in DTCM are tagged like this.

int __tcmdata foo;

Constants can be tagged like this:

Constants are tagged like this.

int __tcmconst foo;

To put assembler into TCM just use:

If you want to put the assembler in TCM, do as follows.

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

respectively.

that's all.

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);
      }
}

Recommended Posts

Also read ARM TCM (Tightly-Coupled Memory) handling in Linux.