/* * This file is part of the coreboot project. * * Copyright 2014 Google Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include /* Maximum number of XLAT Tables available based on ttb buffer size */ static unsigned int max_tables; /* Address of ttb buffer */ static uint64_t *xlat_addr; static int free_idx; static void print_tag(int level, uint64_t tag) { printk(level, tag & MA_MEM_NC ? " cacheable | " : "non-cacheable | "); printk(level, tag & MA_RO ? "read-only | " : "read-write | "); printk(level, tag & MA_NS ? "non-secure | " : " secure | "); printk(level, tag & MA_MEM ? "normal\n" : "device\n"); } /* Func : get_block_attr * Desc : Get block descriptor attributes based on the value of tag in memrange * region */ static uint64_t get_block_attr(unsigned long tag) { uint64_t attr; attr = (tag & MA_NS)? BLOCK_NS : 0; attr |= (tag & MA_RO)? BLOCK_AP_RO : BLOCK_AP_RW; attr |= BLOCK_ACCESS; if (tag & MA_MEM) { attr |= BLOCK_SH_INNER_SHAREABLE; if (tag & MA_MEM_NC) attr |= BLOCK_INDEX_MEM_NORMAL_NC << BLOCK_INDEX_SHIFT; else attr |= BLOCK_INDEX_MEM_NORMAL << BLOCK_INDEX_SHIFT; } else { attr |= BLOCK_INDEX_MEM_DEV_NGNRNE << BLOCK_INDEX_SHIFT; } return attr; } /* Func : table_desc_valid * Desc : Check if a table entry contains valid desc */ static uint64_t table_desc_valid(uint64_t desc) { return((desc & TABLE_DESC) == TABLE_DESC); } /* Func : setup_new_table * Desc : Get next free table from TTB and set it up to match old parent entry. */ static uint64_t *setup_new_table(uint64_t desc, size_t xlat_size) { uint64_t *new, *entry; assert(free_idx < max_tables); new = (uint64_t*)((unsigned char *)xlat_addr + free_idx * GRANULE_SIZE); free_idx++; if (!desc) { memset(new, 0, GRANULE_SIZE); } else { /* Can reuse old parent entry, but may need to adjust type. */ if (xlat_size == L3_XLAT_SIZE) desc |= PAGE_DESC; for (entry = new; (u8 *)entry < (u8 *)new + GRANULE_SIZE; entry++, desc += xlat_size) *entry = desc; } return new; } /* Func : get_table_from_desc * Desc : Get next level table address from table descriptor */ static uint64_t *get_table_from_desc(uint64_t desc) { uint64_t *ptr = (uint64_t*)(desc & XLAT_TABLE_MASK); return ptr; } /* Func: get_next_level_table * Desc: Check if the table entry is a valid descriptor. If not, initialize new * table, update the entry and return the table addr. If valid, return the addr */ static uint64_t *get_next_level_table(uint64_t *ptr, size_t xlat_size) { uint64_t desc = *ptr; if (!table_desc_valid(desc)) { uint64_t *new_table = setup_new_table(desc, xlat_size); desc = ((uint64_t)new_table) | TABLE_DESC; *ptr = desc; } return get_table_from_desc(desc); } /* Func : init_xlat_table * Desc : Given a base address and size, it identifies the indices within * different level XLAT tables which map the given base addr. Similar to table * walk, except that all invalid entries during the walk are updated * accordingly. On success, it returns the size of the block/page addressed by * the final table. */ static uint64_t init_xlat_table(uint64_t base_addr, uint64_t size, uint64_t tag) { uint64_t l1_index = (base_addr & L1_ADDR_MASK) >> L1_ADDR_SHIFT; uint64_t l2_index = (base_addr & L2_ADDR_MASK) >> L2_ADDR_SHIFT; uint64_t l3_index = (base_addr & L3_ADDR_MASK) >> L3_ADDR_SHIFT; uint64_t *table = xlat_addr; uint64_t desc; uint64_t attr = get_block_attr(tag); /* L1 table lookup * If VA has bits more than L2 can resolve, lookup starts at L1 * Assumption: we don't need L0 table in coreboot */ if (BITS_PER_VA > L1_ADDR_SHIFT) { if ((size >= L1_XLAT_SIZE) && IS_ALIGNED(base_addr, (1UL << L1_ADDR_SHIFT))) { /* If block address is aligned and size is greater than * or equal to size addressed by each L1 entry, we can * directly store a block desc */ desc = base_addr | BLOCK_DESC | attr; table[l1_index] = desc; /* L2 lookup is not required */ return L1_XLAT_SIZE; } table = get_next_level_table(&table[l1_index], L2_XLAT_SIZE); } /* L2 table lookup * If lookup was performed at L1, L2 table addr is obtained from L1 desc * else, lookup starts at ttbr address */ if ((size >= L2_XLAT_SIZE) && IS_ALIGNED(base_addr, (1UL << L2_ADDR_SHIFT))) { /* If block address is aligned and size is greater than * or equal to size addressed by each L2 entry, we can * directly store a block desc */ desc = base_addr | BLOCK_DESC | attr; table[l2_index] = desc; /* L3 lookup is not required */ return L2_XLAT_SIZE; } /* L2 entry stores a table descriptor */ table = get_next_level_table(&table[l2_index], L3_XLAT_SIZE); /* L3 table lookup */ desc = base_addr | PAGE_DESC | attr; table[l3_index] = desc; return L3_XLAT_SIZE; } /* Func : sanity_check * Desc : Check address/size alignment of a table or page. */ static void sanity_check(uint64_t addr, uint64_t size) { assert(!(addr & GRANULE_SIZE_MASK) && !(size & GRANULE_SIZE_MASK) && size >= GRANULE_SIZE); } /* Func : mmu_config_range * Desc : This function repeatedly calls init_xlat_table with the base * address. Based on size returned from init_xlat_table, base_addr is updated * and subsequent calls are made for initializing the xlat table until the whole * region is initialized. */ void mmu_config_range(void *start, size_t size, uint64_t tag) { uint64_t base_addr = (uintptr_t)start; uint64_t temp_size = size; if (!IS_ENABLED(CONFIG_SMP)) { printk(BIOS_INFO, "Mapping address range [%p:%p) as ", start, start + size); print_tag(BIOS_INFO, tag); } sanity_check(base_addr, temp_size); while (temp_size) temp_size -= init_xlat_table(base_addr + (size - temp_size), temp_size, tag); /* ARMv8 MMUs snoop L1 data cache, no need to flush it. */ dsb(); tlbiall_current(); dsb(); isb(); } /* Func : mmu_init * Desc : Initialize mmu based on the mmap_ranges passed. ttb_buffer is used as * the base address for xlat tables. ttb_size defines the max number of tables * that can be used */ void mmu_init(struct memranges *mmap_ranges, uint64_t *ttb_buffer, uint64_t ttb_size) { struct range_entry *mmap_entry; sanity_check((uint64_t)ttb_buffer, ttb_size); memset((void*)ttb_buffer, 0, GRANULE_SIZE); max_tables = (ttb_size >> GRANULE_SIZE_SHIFT); xlat_addr = ttb_buffer; free_idx = 1; if (mmap_ranges) memranges_each_entry(mmap_entry, mmap_ranges) { mmu_config_range((void *)range_entry_base(mmap_entry), range_entry_size(mmap_entry), range_entry_tag(mmap_entry)); } } void mmu_enable(void) { uint32_t sctlr; /* Initialize MAIR indices */ raw_write_mair_el3(MAIR_ATTRIBUTES); /* Invalidate TLBs */ tlbiall_el3(); /* Initialize TCR flags */ raw_write_tcr_el3(TCR_TOSZ | TCR_IRGN0_NM_WBWAC | TCR_ORGN0_NM_WBWAC | TCR_SH0_IS | TCR_TG0_4KB | TCR_PS_64GB | TCR_TBI_USED); /* Initialize TTBR */ raw_write_ttbr0_el3((uintptr_t)xlat_addr); /* Ensure system register writes are committed before enabling MMU */ isb(); /* Enable MMU */ sctlr = raw_read_sctlr_el3(); sctlr |= SCTLR_C | SCTLR_M | SCTLR_I; raw_write_sctlr_el3(sctlr); isb(); }