blob: 1ee25f3c800787167e619c3fc21bd59e3d242643 [file] [log] [blame]
/*
* Copyright 2019 Broadcom
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <init.h>
#include <kernel.h>
#include <arch/arm/aarch64/cpu.h>
#include <arch/arm/aarch64/arm_mmu.h>
#include <linker/linker-defs.h>
#include <sys/util.h>
/* Set below flag to get debug prints */
#define MMU_DEBUG_PRINTS 0
/* To get prints from MMU driver, it has to initialized after console driver */
#define MMU_DEBUG_PRIORITY 70
#if MMU_DEBUG_PRINTS
/* To dump page table entries while filling them, set DUMP_PTE macro */
#define DUMP_PTE 0
#define MMU_DEBUG(fmt, ...) printk(fmt, ##__VA_ARGS__)
#else
#define MMU_DEBUG(...)
#endif
/* We support only 4kB translation granule */
#define PAGE_SIZE_SHIFT 12U
#define PAGE_SIZE (1U << PAGE_SIZE_SHIFT)
#define XLAT_TABLE_SIZE_SHIFT PAGE_SIZE_SHIFT /* Size of one complete table */
#define XLAT_TABLE_SIZE (1U << XLAT_TABLE_SIZE_SHIFT)
#define XLAT_TABLE_ENTRY_SIZE_SHIFT 3U /* Each table entry is 8 bytes */
#define XLAT_TABLE_LEVEL_MAX 3U
#define XLAT_TABLE_ENTRIES_SHIFT \
(XLAT_TABLE_SIZE_SHIFT - XLAT_TABLE_ENTRY_SIZE_SHIFT)
#define XLAT_TABLE_ENTRIES (1U << XLAT_TABLE_ENTRIES_SHIFT)
/* Address size covered by each entry at given translation table level */
#define L3_XLAT_VA_SIZE_SHIFT PAGE_SIZE_SHIFT
#define L2_XLAT_VA_SIZE_SHIFT \
(L3_XLAT_VA_SIZE_SHIFT + XLAT_TABLE_ENTRIES_SHIFT)
#define L1_XLAT_VA_SIZE_SHIFT \
(L2_XLAT_VA_SIZE_SHIFT + XLAT_TABLE_ENTRIES_SHIFT)
#define L0_XLAT_VA_SIZE_SHIFT \
(L1_XLAT_VA_SIZE_SHIFT + XLAT_TABLE_ENTRIES_SHIFT)
#define LEVEL_TO_VA_SIZE_SHIFT(level) \
(PAGE_SIZE_SHIFT + (XLAT_TABLE_ENTRIES_SHIFT * \
(XLAT_TABLE_LEVEL_MAX - (level))))
/* Virtual Address Index within given translation table level */
#define XLAT_TABLE_VA_IDX(va_addr, level) \
((va_addr >> LEVEL_TO_VA_SIZE_SHIFT(level)) & (XLAT_TABLE_ENTRIES - 1))
/*
* Calculate the initial translation table level from CONFIG_ARM64_VA_BITS
* For a 4 KB page size,
* (va_bits <= 21) - base level 3
* (22 <= va_bits <= 30) - base level 2
* (31 <= va_bits <= 39) - base level 1
* (40 <= va_bits <= 48) - base level 0
*/
#define GET_XLAT_TABLE_BASE_LEVEL(va_bits) \
((va_bits > L0_XLAT_VA_SIZE_SHIFT) \
? 0U \
: (va_bits > L1_XLAT_VA_SIZE_SHIFT) \
? 1U \
: (va_bits > L2_XLAT_VA_SIZE_SHIFT) \
? 2U : 3U)
#define XLAT_TABLE_BASE_LEVEL GET_XLAT_TABLE_BASE_LEVEL(CONFIG_ARM64_VA_BITS)
#define GET_NUM_BASE_LEVEL_ENTRIES(va_bits) \
(1U << (va_bits - LEVEL_TO_VA_SIZE_SHIFT(XLAT_TABLE_BASE_LEVEL)))
#define NUM_BASE_LEVEL_ENTRIES GET_NUM_BASE_LEVEL_ENTRIES(CONFIG_ARM64_VA_BITS)
#if DUMP_PTE
#define L0_SPACE ""
#define L1_SPACE " "
#define L2_SPACE " "
#define L3_SPACE " "
#define XLAT_TABLE_LEVEL_SPACE(level) \
(((level) == 0) ? L0_SPACE : \
((level) == 1) ? L1_SPACE : \
((level) == 2) ? L2_SPACE : L3_SPACE)
#endif
static u64_t base_xlat_table[NUM_BASE_LEVEL_ENTRIES]
__aligned(NUM_BASE_LEVEL_ENTRIES * sizeof(u64_t));
static u64_t xlat_tables[CONFIG_MAX_XLAT_TABLES][XLAT_TABLE_ENTRIES]
__aligned(XLAT_TABLE_ENTRIES * sizeof(u64_t));
/* Translation table control register settings */
static u64_t get_tcr(int el)
{
u64_t tcr;
u64_t pa_bits = CONFIG_ARM64_PA_BITS;
u64_t va_bits = CONFIG_ARM64_VA_BITS;
u64_t tcr_ps_bits;
switch (pa_bits) {
case 48:
tcr_ps_bits = TCR_PS_BITS_256TB;
break;
case 44:
tcr_ps_bits = TCR_PS_BITS_16TB;
break;
case 42:
tcr_ps_bits = TCR_PS_BITS_4TB;
break;
case 40:
tcr_ps_bits = TCR_PS_BITS_1TB;
break;
case 36:
tcr_ps_bits = TCR_PS_BITS_64GB;
break;
default:
tcr_ps_bits = TCR_PS_BITS_4GB;
break;
}
if (el == 1) {
tcr = (tcr_ps_bits << TCR_EL1_IPS_SHIFT);
/*
* TCR_EL1.EPD1: Disable translation table walk for addresses
* that are translated using TTBR1_EL1.
*/
tcr |= TCR_EPD1_DISABLE;
} else
tcr = (tcr_ps_bits << TCR_EL3_PS_SHIFT);
tcr |= TCR_T0SZ(va_bits);
/*
* Translation table walk is cacheable, inner/outer WBWA and
* inner shareable
*/
tcr |= TCR_TG0_4K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA;
return tcr;
}
static int pte_desc_type(u64_t *pte)
{
return *pte & PTE_DESC_TYPE_MASK;
}
static u64_t *calculate_pte_index(u64_t addr, int level)
{
int base_level = XLAT_TABLE_BASE_LEVEL;
u64_t *pte;
u64_t idx;
unsigned int i;
/* Walk through all translation tables to find pte index */
pte = (u64_t *)base_xlat_table;
for (i = base_level; i <= XLAT_TABLE_LEVEL_MAX; i++) {
idx = XLAT_TABLE_VA_IDX(addr, i);
pte += idx;
/* Found pte index */
if (i == level)
return pte;
/* if PTE is not table desc, can't traverse */
if (pte_desc_type(pte) != PTE_TABLE_DESC)
return NULL;
/* Move to the next translation table level */
pte = (u64_t *)(*pte & 0x0000fffffffff000ULL);
}
return NULL;
}
static void set_pte_table_desc(u64_t *pte, u64_t *table, unsigned int level)
{
#if DUMP_PTE
MMU_DEBUG("%s", XLAT_TABLE_LEVEL_SPACE(level));
MMU_DEBUG("%p: [Table] %p\n", pte, table);
#endif
/* Point pte to new table */
*pte = PTE_TABLE_DESC | (u64_t)table;
}
static void set_pte_block_desc(u64_t *pte, u64_t addr_pa,
unsigned int attrs, unsigned int level)
{
u64_t desc = addr_pa;
unsigned int mem_type;
desc |= (level == 3) ? PTE_PAGE_DESC : PTE_BLOCK_DESC;
/* NS bit for security memory access from secure state */
desc |= (attrs & MT_NS) ? PTE_BLOCK_DESC_NS : 0;
/* AP bits for Data access permission */
desc |= (attrs & MT_RW) ? PTE_BLOCK_DESC_AP_RW : PTE_BLOCK_DESC_AP_RO;
/* the access flag */
desc |= PTE_BLOCK_DESC_AF;
/* memory attribute index field */
mem_type = MT_TYPE(attrs);
desc |= PTE_BLOCK_DESC_MEMTYPE(mem_type);
switch (mem_type) {
case MT_DEVICE_nGnRnE:
case MT_DEVICE_nGnRE:
case MT_DEVICE_GRE:
/* Access to Device memory and non-cacheable memory are coherent
* for all observers in the system and are treated as
* Outer shareable, so, for these 2 types of memory,
* it is not strictly needed to set shareability field
*/
desc |= PTE_BLOCK_DESC_OUTER_SHARE;
/* Map device memory as execute-never */
desc |= PTE_BLOCK_DESC_PXN;
desc |= PTE_BLOCK_DESC_UXN;
break;
case MT_NORMAL_NC:
case MT_NORMAL:
/* Make Normal RW memory as execute never */
if ((attrs & MT_RW) || (attrs & MT_EXECUTE_NEVER))
desc |= PTE_BLOCK_DESC_PXN;
if (mem_type == MT_NORMAL)
desc |= PTE_BLOCK_DESC_INNER_SHARE;
else
desc |= PTE_BLOCK_DESC_OUTER_SHARE;
}
#if DUMP_PTE
MMU_DEBUG("%s", XLAT_TABLE_LEVEL_SPACE(level));
MMU_DEBUG("%p: ", pte);
MMU_DEBUG((mem_type == MT_NORMAL) ? "MEM" :
((mem_type == MT_NORMAL_NC) ? "NC" : "DEV"));
MMU_DEBUG((attrs & MT_RW) ? "-RW" : "-RO");
MMU_DEBUG((attrs & MT_NS) ? "-NS" : "-S");
MMU_DEBUG((attrs & MT_EXECUTE_NEVER) ? "-XN" : "-EXEC");
MMU_DEBUG("\n");
#endif
*pte = desc;
}
/* Returns a new reallocated table */
static u64_t *new_prealloc_table(void)
{
static unsigned int table_idx;
__ASSERT(table_idx < CONFIG_MAX_XLAT_TABLES,
"Enough xlat tables not allocated");
return (u64_t *)(xlat_tables[table_idx++]);
}
/* Splits a block into table with entries spanning the old block */
static void split_pte_block_desc(u64_t *pte, int level)
{
u64_t old_block_desc = *pte;
u64_t *new_table;
unsigned int i = 0;
/* get address size shift bits for next level */
int levelshift = LEVEL_TO_VA_SIZE_SHIFT(level + 1);
MMU_DEBUG("Splitting existing PTE %p(L%d)\n", pte, level);
new_table = new_prealloc_table();
for (i = 0; i < XLAT_TABLE_ENTRIES; i++) {
new_table[i] = old_block_desc | (i << levelshift);
if ((level + 1) == 3)
new_table[i] |= PTE_PAGE_DESC;
}
/* Overwrite existing PTE set the new table into effect */
set_pte_table_desc(pte, new_table, level);
}
/* Create/Populate translation table(s) for given region */
static void init_xlat_tables(const struct arm_mmu_region *region)
{
u64_t *pte;
u64_t virt = region->base_va;
u64_t phys = region->base_pa;
u64_t size = region->size;
u64_t attrs = region->attrs;
u64_t level_size;
u64_t *new_table;
unsigned int level = XLAT_TABLE_BASE_LEVEL;
MMU_DEBUG("mmap: virt %llx phys %llx size %llx\n", virt, phys, size);
/* check minimum alignment requirement for given mmap region */
__ASSERT(((virt & (PAGE_SIZE - 1)) == 0) &&
((size & (PAGE_SIZE - 1)) == 0),
"address/size are not page aligned\n");
while (size) {
__ASSERT(level <= XLAT_TABLE_LEVEL_MAX,
"max translation table level exceeded\n");
/* Locate PTE for given virtual address and page table level */
pte = calculate_pte_index(virt, level);
__ASSERT(pte != NULL, "pte not found\n");
level_size = 1ULL << LEVEL_TO_VA_SIZE_SHIFT(level);
if (size >= level_size && !(virt & (level_size - 1))) {
/* Given range fits into level size,
* create block/page descriptor
*/
set_pte_block_desc(pte, phys, attrs, level);
virt += level_size;
phys += level_size;
size -= level_size;
/* Range is mapped, start again for next range */
level = XLAT_TABLE_BASE_LEVEL;
} else if (pte_desc_type(pte) == PTE_INVALID_DESC) {
/* Range doesn't fit, create subtable */
new_table = new_prealloc_table();
set_pte_table_desc(pte, new_table, level);
level++;
} else if (pte_desc_type(pte) == PTE_BLOCK_DESC) {
split_pte_block_desc(pte, level);
level++;
} else if (pte_desc_type(pte) == PTE_TABLE_DESC)
level++;
}
}
/* zephyr execution regions with appropriate attributes */
static const struct arm_mmu_region mmu_zephyr_regions[] = {
/* Mark text segment cacheable,read only and executable */
MMU_REGION_FLAT_ENTRY("zephyr_code",
(uintptr_t)_image_text_start,
(uintptr_t)_image_text_size,
MT_CODE | MT_SECURE),
/* Mark rodata segment cacheable, read only and execute-never */
MMU_REGION_FLAT_ENTRY("zephyr_rodata",
(uintptr_t)_image_rodata_start,
(uintptr_t)_image_rodata_size,
MT_RODATA | MT_SECURE),
/* Mark rest of the zephyr execution regions (data, bss, noinit, etc.)
* cacheable, read-write
* Note: read-write region is marked execute-ever internally
*/
MMU_REGION_FLAT_ENTRY("zephyr_data",
(uintptr_t)__kernel_ram_start,
(uintptr_t)__kernel_ram_size,
MT_NORMAL | MT_RW | MT_SECURE),
};
static void setup_page_tables(void)
{
unsigned int index;
const struct arm_mmu_region *region;
u64_t max_va = 0, max_pa = 0;
for (index = 0; index < mmu_config.num_regions; index++) {
region = &mmu_config.mmu_regions[index];
max_va = MAX(max_va, region->base_va + region->size);
max_pa = MAX(max_pa, region->base_pa + region->size);
}
__ASSERT(max_va <= (1ULL << CONFIG_ARM64_VA_BITS),
"Maximum VA not supported\n");
__ASSERT(max_pa <= (1ULL << CONFIG_ARM64_PA_BITS),
"Maximum PA not supported\n");
/* create translation tables for user provided platform regions */
for (index = 0; index < mmu_config.num_regions; index++) {
region = &mmu_config.mmu_regions[index];
if (region->size || region->attrs)
init_xlat_tables(region);
}
/* setup translation table for zephyr execution regions */
for (index = 0; index < ARRAY_SIZE(mmu_zephyr_regions); index++) {
region = &mmu_zephyr_regions[index];
if (region->size || region->attrs)
init_xlat_tables(region);
}
}
static void enable_mmu_el1(unsigned int flags)
{
ARG_UNUSED(flags);
u64_t val;
/* Set MAIR, TCR and TBBR registers */
__asm__ volatile("msr mair_el1, %0"
:
: "r" (MEMORY_ATTRIBUTES)
: "memory", "cc");
__asm__ volatile("msr tcr_el1, %0"
:
: "r" (get_tcr(1))
: "memory", "cc");
__asm__ volatile("msr ttbr0_el1, %0"
:
: "r" ((u64_t)base_xlat_table)
: "memory", "cc");
/* Ensure these changes are seen before MMU is enabled */
__ISB();
/* Enable the MMU and data cache */
__asm__ volatile("mrs %0, sctlr_el1" : "=r" (val));
__asm__ volatile("msr sctlr_el1, %0"
:
: "r" (val | SCTLR_M_BIT | SCTLR_C_BIT)
: "memory", "cc");
/* Ensure the MMU enable takes effect immediately */
__ISB();
MMU_DEBUG("MMU enabled with dcache\n");
}
/* ARM MMU Driver Initial Setup */
/*
* @brief MMU default configuration
*
* This function provides the default configuration mechanism for the Memory
* Management Unit (MMU).
*/
static int arm_mmu_init(struct device *arg)
{
u64_t val;
unsigned int idx, flags = 0;
/* Current MMU code supports only EL1 */
__asm__ volatile("mrs %0, CurrentEL" : "=r" (val));
__ASSERT(GET_EL(val) == MODE_EL1,
"Exception level not EL1, MMU not enabled!\n");
/* Ensure that MMU is already not enabled */
__asm__ volatile("mrs %0, sctlr_el1" : "=r" (val));
__ASSERT((val & SCTLR_M_BIT) == 0, "MMU is already enabled\n");
MMU_DEBUG("xlat tables:\n");
MMU_DEBUG("base table(L%d): %p, %d entries\n", XLAT_TABLE_BASE_LEVEL,
(u64_t *)base_xlat_table, NUM_BASE_LEVEL_ENTRIES);
for (idx = 0; idx < CONFIG_MAX_XLAT_TABLES; idx++)
MMU_DEBUG("%d: %p\n", idx, (u64_t *)(xlat_tables + idx));
setup_page_tables();
/* currently only EL1 is supported */
enable_mmu_el1(flags);
return 0;
}
SYS_INIT(arm_mmu_init, PRE_KERNEL_1,
#if MMU_DEBUG_PRINTS
MMU_DEBUG_PRIORITY
#else
CONFIG_KERNEL_INIT_PRIORITY_DEVICE
#endif
);