tcm_init()

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-1

 

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을 지원하지 않는다.
  • 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개가 내장되어 있다.

tcm_init-2a

 

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

 

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을 리턴한다.
  • 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를 리턴

resource-1

  • if (!tmp || tmp->start > end) {
    • new 리소스의 영역이 기존 리소스 영역과 겹치지 않으면서 하단에 위치하는 경우 앞쪽에 추가한다.

resource-3

  • p = &tmp->sibling;
    • 비교할 노드를 다음 옆 리소스로 선택한다.
  • if (tmp->end < start) continue;
    • new 리소스의 영역이 현재 리소스 영역보다 크면 루프를 계속 진행한다.

resource-4

  • new 리소스의 영역이 현재 리소스 영역과 겹치면 현재 리소스를 리턴한다.

resource-2

TCMSR, DTCMRR, ITCMRR 레지스터

  • TCMSR
    • 0~3 번 TCM 메모리 영역(뱅크)를 선택한다.
  • DTCMRR and ITCMRR
    • 현재 선택된 TCM 메모리의 물리 주소, 사이즈를 알아오거나 설정할 수 있다.
      • 물리주소를 바꾸거나 사용 가능 사이즈도 줄일 수 있다.
    • Enable bit를 사용하여 사용 여부를 결정할 수 있다.
    • BaseAddress
      • TCM 메모리의 물리 주소로 size에 align되어 사용된다.
      • Size는 아래 테이블 표를 참고한다.
        • ARM 리눅스는 현재 32K까지 지원한다.

tcm_init-3

 

구조체 및 전역 변수

 

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;

 

참고

댓글 남기기