| /* |
| * 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 <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <memrange.h> |
| #include <arch/mmu.h> |
| #include <arch/lib_helpers.h> |
| #include <arch/cache.h> |
| |
| /* 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 const uint64_t level_to_addr_mask[] = { |
| L1_ADDR_MASK, |
| L2_ADDR_MASK, |
| L3_ADDR_MASK, |
| }; |
| |
| static const uint64_t level_to_addr_shift[] = { |
| L1_ADDR_SHIFT, |
| L2_ADDR_SHIFT, |
| L3_ADDR_SHIFT, |
| }; |
| |
| /* 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 : get_index_from_addr |
| * Desc : Get index into table at a given level using appropriate bits from the |
| * base address |
| */ |
| static uint64_t get_index_from_addr(uint64_t addr, uint8_t level) |
| { |
| uint64_t mask = level_to_addr_mask[level-1]; |
| uint8_t shift = level_to_addr_shift[level-1]; |
| |
| return ((addr & mask) >> shift); |
| } |
| |
| /* 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 : get_new_table |
| * Desc : Return the next free XLAT table from ttb buffer |
| */ |
| static uint64_t *get_new_table(void) |
| { |
| uint64_t *new; |
| |
| if (free_idx >= max_tables) { |
| return NULL; |
| } |
| |
| new = (uint64_t*)((unsigned char *)xlat_addr + free_idx * GRANULE_SIZE); |
| free_idx++; |
| |
| memset(new, 0, GRANULE_SIZE); |
| |
| 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, allocate 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) |
| { |
| uint64_t desc = *ptr; |
| |
| if (!table_desc_valid(desc)) { |
| uint64_t *new_table = get_new_table(); |
| if (new_table == NULL) |
| return NULL; |
| 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 = get_index_from_addr(base_addr,1); |
| uint64_t l2_index = get_index_from_addr(base_addr,2); |
| uint64_t l3_index = get_index_from_addr(base_addr,3); |
| 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; |
| } else { |
| table = get_next_level_table(&table[l1_index]); |
| if (!table) |
| return 0; |
| } |
| } |
| |
| /* 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; |
| } else { |
| /* L2 entry stores a table descriptor */ |
| table = get_next_level_table(&table[l2_index]); |
| if (!table) |
| return 0; |
| } |
| |
| /* L3 table lookup */ |
| desc = base_addr | PAGE_DESC | attr; |
| table[l3_index] = desc; |
| return L3_XLAT_SIZE; |
| } |
| |
| /* Func : sanity_check |
| * Desc : Check if the address is aligned and size is atleast the granule size |
| */ |
| static uint64_t sanity_check(uint64_t addr, |
| uint64_t size) |
| { |
| /* Address should be atleast 64 KiB aligned */ |
| if (addr & GRANULE_SIZE_MASK) |
| return 1; |
| |
| /* Size should be atleast granule size */ |
| if (size < GRANULE_SIZE) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Func : init_mmap_entry |
| * Desc : For each mmap entry, this function 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. |
| */ |
| static void init_mmap_entry(struct range_entry *r) |
| { |
| uint64_t base_addr = range_entry_base(r); |
| uint64_t size = range_entry_size(r); |
| uint64_t tag = range_entry_tag(r); |
| uint64_t temp_size = size; |
| |
| while (temp_size) { |
| uint64_t ret; |
| |
| if (sanity_check(base_addr,temp_size)) { |
| return; |
| } |
| |
| ret = init_xlat_table(base_addr + (size - temp_size), |
| temp_size,tag); |
| |
| if (ret == 0) |
| return; |
| |
| temp_size -= ret; |
| } |
| } |
| |
| /* 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; |
| |
| if (sanity_check((uint64_t)ttb_buffer, ttb_size)) { |
| return; |
| } |
| |
| memset((void*)ttb_buffer, 0, GRANULE_SIZE); |
| max_tables = (ttb_size >> GRANULE_SIZE_SHIFT); |
| xlat_addr = ttb_buffer; |
| free_idx = 1; |
| |
| memranges_each_entry(mmap_entry, mmap_ranges) { |
| init_mmap_entry(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 all translation table writes are committed before enabling MMU */ |
| dsb(); |
| isb(); |
| |
| /* Enable MMU */ |
| sctlr = raw_read_sctlr_el3(); |
| sctlr |= SCTLR_C | SCTLR_M | SCTLR_I; |
| raw_write_sctlr_el3(sctlr); |
| |
| isb(); |
| } |