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