TCM(Tightly Coupled Memory)이 내장된 SoC에서 DTCM 및 ITCM 메모리 영역의 갯 수와 크기를 알아내서 다음과 같이 매핑하고 enable 한다.
- DTCM: 0xfffe_8000 가상 주소에 전체 DTCM 영역을 MT_MEMORY_RW_DTCM으로 매핑 예약(static_vmlist에 추가)
- ITCM: 0xfffe_0000 가상 주소에 전체 ITCM 영역을 MT_MEMORY_RWX_ITCM으로 매핑 예약(static_vmlist에 추가)
- rpi2: TCM이 없음.
32bit ARM 리눅스에서는 TCM 메모리의 최대 처리 용량은 각각 32K이다.
tcm_init()
arch/arm/kernel/tcm.c
/*
* This initializes the TCM memory
*/
void __init tcm_init(void)
{
u32 tcm_status;
u8 dtcm_banks;
u8 itcm_banks;
size_t dtcm_code_sz = &__edtcm_data - &__sdtcm_data;
size_t itcm_code_sz = &__eitcm_text - &__sitcm_text;
char *start;
char *end;
char *ram;
int ret;
int i;
/*
* Prior to ARMv5 there is no TCM, and trying to read the status
* register will hang the processor.
*/
if (cpu_architecture() < CPU_ARCH_ARMv5) {
if (dtcm_code_sz || itcm_code_sz)
pr_info("CPU TCM: %u bytes of DTCM and %u bytes of "
"ITCM code compiled in, but no TCM present "
"in pre-v5 CPU\n", dtcm_code_sz, itcm_code_sz);
return;
}
tcm_status = read_cpuid_tcmstatus();
dtcm_banks = (tcm_status >> 16) & 0x03;
itcm_banks = (tcm_status & 0x03);
/* Values greater than 2 for D/ITCM banks are "reserved" */
if (dtcm_banks > 2)
dtcm_banks = 0;
if (itcm_banks > 2)
itcm_banks = 0;
- if (cpu_architecture() < CPU_ARCH_ARMv5) {
- ARMv5 미만 아키텍처는 TCM이 없으므로 루틴을 빠져나간다.
- tcm_status = read_cpuid_tcmstatus();
- DTCM 뱅크 수와 ITCM 뱅크 수를 알아온다.
- ARMv6 format
- DTCM: CPUID_TCM[bit17:16]
- ITCM: CPUID_TCM[bit1:0]
- ARMv7 format
- 현재 리눅스 코드가 ARMv7 format을 지원하지 않는다.
- ARMv6 format
- DTCM 뱅크 수와 ITCM 뱅크 수를 알아온다.
- if (dtcm_banks > 2)
- 뱅크 수는 0~2까지 인데 3이 나오는 경우는 PB11MPCore가 TCM이 없는데도 잘못 보고를 하는 case가 발견되어 이를 수정한 것이다.
- 참고: ARM: 6984/1: enhance TCM robustness
/* Setup DTCM if present */
if (dtcm_banks > 0) {
for (i = 0; i < dtcm_banks; i++) {
ret = setup_tcm_bank(0, i, dtcm_banks, &dtcm_end);
if (ret)
return;
}
/* This means you compiled more code than fits into DTCM */
if (dtcm_code_sz > (dtcm_end - DTCM_OFFSET)) {
pr_info("CPU DTCM: %u bytes of code compiled to "
"DTCM but only %lu bytes of DTCM present\n",
dtcm_code_sz, (dtcm_end - DTCM_OFFSET));
goto no_dtcm;
}
dtcm_res.end = dtcm_end - 1;
request_resource(&iomem_resource, &dtcm_res);
dtcm_iomap[0].length = dtcm_end - DTCM_OFFSET;
iotable_init(dtcm_iomap, 1);
/* Copy data from RAM to DTCM */
start = &__sdtcm_data;
end = &__edtcm_data;
ram = &__dtcm_start;
memcpy(start, ram, dtcm_code_sz);
pr_debug("CPU DTCM: copied data from %p - %p\n",
start, end);
dtcm_present = true;
} else if (dtcm_code_sz) {
pr_info("CPU DTCM: %u bytes of code compiled to DTCM but no "
"DTCM banks present in CPU\n", dtcm_code_sz);
}
- if (dtcm_banks > 0) {
- DTCM 뱅크가 발견되면
- for (i = 0; i < dtcm_banks; i++) {
- DTCM 뱅크 수 만큼 루프를 돈다.
- ret = setup_tcm_bank(0, i, dtcm_banks, &dtcm_end);
- TCM 관련 레지스터를 설정한다.
- dtcm_end는 초기에 TCM의 base 주소가 담겨서 함수에 진입하고 함수를 빠져 나올때엔 TCM 사이즈만큼 증가되어 다음 TCM 메모리의 주소가 될 위치를 가리킨다.
- if (dtcm_code_sz > (dtcm_end – DTCM_OFFSET)) {
- 커널에 있는 DTCM용 데이터 사이즈가 실제 DTCM 영역 크기보다 더 큰 경우 에러를 출력하고 루틴을 빠져 나온다.
- request_resource(&iomem_resource, &dtcm_res)
- iomem_resource에 TCM 영역을 추가한다.
- iotable_init(dtcm_iomap, 1);
- static_vmlist에 영역을 추가하여 매핑 예약을 한다.
- 커널에 있는 DTCM 데이터 영역을 TCM 영역에 복사한다.
- dtcm_present = true;
- DTCM이 준비되었음을 알리는 전역 변수이다.
no_dtcm:
/* Setup ITCM if present */
if (itcm_banks > 0) {
for (i = 0; i < itcm_banks; i++) {
ret = setup_tcm_bank(1, i, itcm_banks, &itcm_end);
if (ret)
return;
}
/* This means you compiled more code than fits into ITCM */
if (itcm_code_sz > (itcm_end - ITCM_OFFSET)) {
pr_info("CPU ITCM: %u bytes of code compiled to "
"ITCM but only %lu bytes of ITCM present\n",
itcm_code_sz, (itcm_end - ITCM_OFFSET));
return;
}
itcm_res.end = itcm_end - 1;
request_resource(&iomem_resource, &itcm_res);
itcm_iomap[0].length = itcm_end - ITCM_OFFSET;
iotable_init(itcm_iomap, 1);
/* Copy code from RAM to ITCM */
start = &__sitcm_text;
end = &__eitcm_text;
ram = &__itcm_start;
memcpy(start, ram, itcm_code_sz);
pr_debug("CPU ITCM: copied code from %p - %p\n",
start, end);
itcm_present = true;
} else if (itcm_code_sz) {
pr_info("CPU ITCM: %u bytes of code compiled to ITCM but no "
"ITCM banks present in CPU\n", itcm_code_sz);
}
}
- ITCM의 코드는 DTCM의 코드와 거의 동일하다.
다음 그림은 DTCM 및 ITCM 메모리가 각각 2개씩 내장된 ARM SoC를 대상으로 가상 주소에 매핑되는 예를 들었다.
- DTCM 및 ITCM이 각각 2개씩인 상황에 대해 메모리를 매핑하였지만 실제 SoC에는 DTCM 및 ITCM 메모리가 없거나 있는 경우 각각 1개가 내장되어 있다.
read_cpuid_tcmstatus()
arch/arm/include/asm/cputype.h
static inline unsigned int __attribute_const__ read_cpuid_tcmstatus(void)
{
return read_cpuid(CPUID_TCM);
}
- TCMTR(TCM Type Register) 레지스터를 통해 DTCM 뱅크 수와 ITCM 뱅크 수를 알아낼 수 있다.
- ARMv6 format
- DTCM: CPUID_TCM[bit17:16]
- ITCM: CPUID_TCM[bit1:0]
- CPUID_TCM=2
- 두 개의 비트를 사용하여 0=no TCM, 1=1개 TCM, 2=2개 TCM, 3=undefined
- ARMv6 format
setup_tcm_bank()
arch/arm/kernel/tcm.c
static int __init setup_tcm_bank(u8 type, u8 bank, u8 banks,
u32 *offset)
{
const int tcm_sizes[16] = { 0, -1, -1, 4, 8, 16, 32, 64, 128,
256, 512, 1024, -1, -1, -1, -1 };
u32 tcm_region;
int tcm_size;
/*
* If there are more than one TCM bank of this type,
* select the TCM bank to operate on in the TCM selection
* register.
*/
if (banks > 1)
asm("mcr p15, 0, %0, c9, c2, 0"
: /* No output operands */
: "r" (bank));
/* Read the special TCM region register c9, 0 */
if (!type)
asm("mrc p15, 0, %0, c9, c1, 0"
: "=r" (tcm_region));
else
asm("mrc p15, 0, %0, c9, c1, 1"
: "=r" (tcm_region));
tcm_size = tcm_sizes[(tcm_region >> 2) & 0x0f];
if (tcm_size < 0) {
pr_err("CPU: %sTCM%d of unknown size\n",
type ? "I" : "D", bank);
return -EINVAL;
} else if (tcm_size > 32) {
pr_err("CPU: %sTCM%d larger than 32k found\n",
type ? "I" : "D", bank);
return -EINVAL;
} else {
pr_info("CPU: found %sTCM%d %dk @ %08x, %senabled\n",
type ? "I" : "D",
bank,
tcm_size,
(tcm_region & 0xfffff000U),
(tcm_region & 1) ? "" : "not ");
}
/* Not much fun you can do with a size 0 bank */
if (tcm_size == 0)
return 0;
/* Force move the TCM bank to where we want it, enable */
tcm_region = *offset | (tcm_region & 0x00000ffeU) | 1;
if (!type)
asm("mcr p15, 0, %0, c9, c1, 0"
: /* No output operands */
: "r" (tcm_region));
else
asm("mcr p15, 0, %0, c9, c1, 1"
: /* No output operands */
: "r" (tcm_region));
/* Increase offset */
*offset += (tcm_size << 10);
pr_info("CPU: moved %sTCM%d %dk to %08x, enabled\n",
type ? "I" : "D",
bank,
tcm_size,
(tcm_region & 0xfffff000U));
return 0;
}
- if (banks > 1)
- 뱅크 수가 1개를 넘어가는 경우
- asm(“mcr p15, 0, %0, c9, c2, 0”
- TCMSR(TCM Selection Register)을 사용하여 TCM 선택을 한다.
- if (!type)
- type=0(DTCM)이면
- asm(“mrc p15, 0, %0, c9, c1, 0”
- DTCMRR(Data TCM Region Register)을 통해 DTCM 정보를 읽어온다.
- asm(“mrc p15, 0, %0, c9, c1, 1”
- ITCMRR(Instruction TCM Region Register)을 통해 ITCM 정보를 읽어온다.
- tcm_size = tcm_sizes[(tcm_region >> 2) & 0x0f];
- 알아온 TCM 정보에서 사이즈를 지정하는 5비트 중 4비트만 읽어낸다.
- tcm_size가 0보다 작거나 32보다 큰 경우 에럴를 출력하고 루틴을 빠져나간다.
- tcm_size가 0인 경우 TCM이 없는 것으로 인식하고 루틴을 빠져나간다.
- tcm_region = *offset | (tcm_region & 0x00000ffeU) | 1;
- tcm_region 속성 값에 TCM base 주소를 가리키는 offset을 갱신하고 TCM enable 비트를 설정한다.
- asm(“mcr p15, 0, %0, c9, c1, 0”
- DTCMRR에 tcm_region 속성값을 기록한다.
- asm(“mcr p15, 0, %0, c9, c1, 1”
- ITCMRR에 tcm_region 속성값을 기록한다
- *offset += (tcm_size << 10);
- offset에 tcm_size * 1K 만큼을 추가하여 다음 TCM base 주소를 리턴한다.
request_resource()
kernel/resource.c
/**
* request_resource - request and reserve an I/O or memory resource
* @root: root resource descriptor
* @new: resource descriptor desired by caller
*
* Returns 0 for success, negative error code on error.
*/
int request_resource(struct resource *root, struct resource *new)
{
struct resource *conflict;
conflict = request_resource_conflict(root, new);
return conflict ? -EBUSY : 0;
}
- new 리소스를 root에 등록할 때 리소스 영역들끼리 겹치는 영역이 있으면 에러가 리턴된다.
request_resource_conflict()
kernel/resource.c
/**
* request_resource_conflict - request and reserve an I/O or memory resource
* @root: root resource descriptor
* @new: resource descriptor desired by caller
*
* Returns 0 for success, conflict resource on error.
*/
struct resource *request_resource_conflict(struct resource *root, struct resource *new)
{
struct resource *conflict;
write_lock(&resource_lock);
conflict = __request_resource(root, new);
write_unlock(&resource_lock);
return conflict;
}
- write_lock(&resource_lock);
- resource들간에 연결을 위해 write lock을 수행
- conflict = __request_resource(root, new);
- root가 관리하는 영역에 new 영역을 추가한다.
- root 영역의 범위에 포함되지 않는 경우 root를 리턴한다.
- 기존 리소스들과 영역이 겹치는 경우 해당 리소스를 리턴한다.
- 기존 리소스들과 영역이 겹치지 않는 경우 new 영역을 추가하고 null을 리턴한다.
- root가 관리하는 영역에 new 영역을 추가한다.
- write_unlock(&resource_lock);
- write unlock을 수행
__request_resource()
kernel/resource.c
/* Return the conflict entry if you can't request it */
static struct resource * __request_resource(struct resource *root, struct resource *new)
{
resource_size_t start = new->start;
resource_size_t end = new->end;
struct resource *tmp, **p;
if (end < start)
return root;
if (start < root->start)
return root;
if (end > root->end)
return root;
p = &root->child;
for (;;) {
tmp = *p;
if (!tmp || tmp->start > end) {
new->sibling = tmp;
*p = new;
new->parent = root;
return NULL;
}
p = &tmp->sibling;
if (tmp->end < start)
continue;
return tmp;
}
}
- if (end < start) return root
- new 영역의 끝 주소가 시작 주소보다 큰 경우는 root를 리턴
- if (start < root->start)
- new 영역이 root의 하단부를 벗어난 경우 root를 리턴
- if (end > root->end) return root;
- new 영역이 root의 상단부를 벗어난 경우 root를 리턴
- if (!tmp || tmp->start > end) {
- new 리소스의 영역이 기존 리소스 영역과 겹치지 않으면서 하단에 위치하는 경우 앞쪽에 추가한다.
- p = &tmp->sibling;
- 비교할 노드를 다음 옆 리소스로 선택한다.
- if (tmp->end < start) continue;
- new 리소스의 영역이 현재 리소스 영역보다 크면 루프를 계속 진행한다.
- new 리소스의 영역이 현재 리소스 영역과 겹치면 현재 리소스를 리턴한다.

TCMSR, DTCMRR, ITCMRR 레지스터
- TCMSR
- 0~3 번 TCM 메모리 영역(뱅크)를 선택한다.
- DTCMRR and ITCMRR
- 현재 선택된 TCM 메모리의 물리 주소, 사이즈를 알아오거나 설정할 수 있다.
- 물리주소를 바꾸거나 사용 가능 사이즈도 줄일 수 있다.
- Enable bit를 사용하여 사용 여부를 결정할 수 있다.
- BaseAddress
- TCM 메모리의 물리 주소로 size에 align되어 사용된다.
- Size는 아래 테이블 표를 참고한다.
- ARM 리눅스는 현재 32K까지 지원한다.
- 현재 선택된 TCM 메모리의 물리 주소, 사이즈를 알아오거나 설정할 수 있다.
구조체 및 전역 변수
resource 구조체
include/linux/ioport.h
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
- start
- 리소스의 시작 물리 주소
- end
- 리소스의 끝 물리 주소
- name
- 리소스 명
- flags
- TCM 메모리의 플래그는 IORESOURCE_MEM(0x00000200) 이다.
- parent
- 리소스의 상위와 연결
- sibling
- 다음 리소스와 연결(ascending order)
- child
- 하위 리소스와 연결
map_desc 구조체
arch/arm/include/asm/mach/map.h
struct map_desc {
unsigned long virtual;
unsigned long pfn;
unsigned long length;
unsigned int type;
};
- virtual
- 가상 시작 주소
- pfn
- 물리 프레임 번호
- length
- 길이
- type
- 매핑 타입
dtcm_res & itcm_res
arch/arm/kernel/tcm.c
/*
* TCM memory resources
*/
static struct resource dtcm_res = {
.name = "DTCM RAM",
.start = DTCM_OFFSET,
.end = DTCM_OFFSET,
.flags = IORESOURCE_MEM
};
static struct resource itcm_res = {
.name = "ITCM RAM",
.start = ITCM_OFFSET,
.end = ITCM_OFFSET,
.flags = IORESOURCE_MEM
};
dtcm_iomap[] & itcm_iomap[]
arch/arm/kernel/tcm.c
static struct map_desc dtcm_iomap[] __initdata = {
{
.virtual = DTCM_OFFSET,
.pfn = __phys_to_pfn(DTCM_OFFSET),
.length = 0,
.type = MT_MEMORY_RW_DTCM
}
};
static struct map_desc itcm_iomap[] __initdata = {
{
.virtual = ITCM_OFFSET,
.pfn = __phys_to_pfn(ITCM_OFFSET),
.length = 0,
.type = MT_MEMORY_RWX_ITCM,
}
};
기타 전역 변수
static struct gen_pool *tcm_pool; static bool dtcm_present; static bool itcm_present; /* TCM section definitions from the linker */ extern char __itcm_start, __sitcm_text, __eitcm_text; extern char __dtcm_start, __sdtcm_data, __edtcm_data; /* These will be increased as we run */ u32 dtcm_end = DTCM_OFFSET; u32 itcm_end = ITCM_OFFSET;
참고
- ARM TCM (Tightly-Coupled Memory) handling in Linux | kernel.org





