blob: 329c1667196e847dd2dd1f462f588794119c8a4e [file] [log] [blame]
/******************************************************************************
*
* Copyright 2013 Altera Corporation. All Rights Reserved.
*
* 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 COPYRIGHT HOLDER "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 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 "alt_mmu.h"
#include <string.h>
#include <stdio.h>
/////
// NOTE: To enable debugging output, delete the next line and uncomment the
// line after.
#define dprintf(...)
// #define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__)
/////
// Generates the bitmask given the MSB and LSB of a register field.
// NOTE: This is problematic for the BITMASK(31, 0) case.
#define BITMASK(msb, lsb) (((1 << ((msb) - (lsb) + 1)) - 1) << (lsb))
// Calculates the array count statically
#define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0]))
// Minimum
#define MIN(a, b) ((a) < (b) ? (a) : (b))
// Index into the pagetable given the virtual address. This is bits va[19:12] >> 12.
#define ALT_MMU_PAGE_TABLE_INDEX(va) (((uintptr_t)(va) >> 12) & 0xff)
/////
// This is the number of 1 MiB sections in the TTB1 table.
#define ALT_MMU_TTB1_SECTION_COUNT 4096
/////
// System Control Register
#define ALT_CPU_SCTLR_C_SET_MSK (1 << 2)
#define ALT_CPU_SCTLR_M_SET_MSK (1 << 0)
#define ALT_CPU_CONTEXTIDR_PROCID_SET_MSK (0xffffff << 8)
#define ALT_CPU_CONTEXTIDR_ASID_SET_MSK (0x0000ff << 0)
// Translation Table Base Register 0 (Process Specific; Changes on context switch)
// (31 to 14 - (TTBCR.N))
#define ALT_CPU_TTBR0_TTB0BASEADDR_SET_MSK(ttbcr_n) BITMASK(31, 14 - (ttbcr_n))
#define ALT_CPU_TTBR0_IRGN_0_SET_MSK (1 << 6)
#define ALT_CPU_TTBR0_NOS_SET_MSK (1 << 5)
#define ALT_CPU_TTBR0_RGN_SET_MSK (3 << 3)
#define ALT_CPU_TTBR0_IMP_SET_MSK (1 << 2)
#define ALT_CPU_TTBR0_S_SET_MSK (1 << 1)
#define ALT_CPU_TTBR0_IRGN_1_SET_MSK (1 << 0)
#define ALT_CPU_TTBR0_RGN_NC (0 << 3) // RGN[1:0] = 00
#define ALT_CPU_TTBR0_RGN_WBA (1 << 3) // RGN[1:0] = 01
#define ALT_CPU_TTBR0_RGN_WT (2 << 3) // RGN[1:0] = 10
#define ALT_CPU_TTBR0_RGN_WB (3 << 3) // RGN[1:0] = 11
// NOTE: IRGN bits are reversed. TTBR0[6] is IRGN[0]; TTBR[0] is IRGN[1].
#define ALT_CPU_TTBR0_IRGN_NC (0 << 0 | 0 << 6) // IRGN[1:0] = 00
#define ALT_CPU_TTBR0_IRGN_WBA (0 << 0 | 1 << 6) // IRGN[1:0] = 01
#define ALT_CPU_TTBR0_IRGN_WT (1 << 0 | 0 << 6) // IRGN[1:0] = 10
#define ALT_CPU_TTBR0_IRGN_WB (1 << 0 | 1 << 6) // IRGN[1:0] = 11
// Translation Table Base Register 1 (OS and IO specific; Static)
#define ALT_CPU_TTBR1_TTB1BASEADDR_SET_MSK (0x3ffffUL << 14)
#define ALT_CPU_TTBR1_IRGN_0_SET_MSK (1 << 6)
#define ALT_CPU_TTBR1_NOS_SET_MSK (1 << 5)
#define ALT_CPU_TTBR1_RGN_SET_MSK (3 << 3)
#define ALT_CPU_TTBR1_IMP_SET_MSK (1 << 2)
#define ALT_CPU_TTBR1_S_SET_MSK (1 << 1)
#define ALT_CPU_TTBR1_IRGN_1_SET_MSK (1 << 0)
// Translation Table Base Control Register
#define ALT_CPU_TTBCR_PD1_SET_MSK (1 << 5)
#define ALT_CPU_TTBCR_PD0_SET_MSK (1 << 4)
#define ALT_CPU_TTBCR_N_SET_MSK (7 << 0)
#define ALT_CPU_TTBCR_N_VALUE_GET(value) (((value) << 0) & ALT_CPU_TTBCR_N_SET_MSK)
/////
static inline __attribute__((always_inline)) uint32_t sctlr_get_helper(void)
{
// Read from SCTLR using CP15.
// See ARMv7-A,R, section B4.1.30.
uint32_t sctlr;
#ifdef __ARMCC_VERSION
__asm("MRC p15, 0, sctlr, c1, c0, 0");
#else
__asm("MRC p15, 0, %0, c1, c0, 0" : "=r" (sctlr));
#endif
return sctlr;
}
static inline __attribute__((always_inline)) void sctlr_set_helper(uint32_t sctlr)
{
// Write to SCTLR using CP15.
// See ARMv7-A,R, section B4.1.30.
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, sctlr, c1, c0, 0");
#else
__asm("MCR p15, 0, %0, c1, c0, 0" : : "r" (sctlr));
#endif
}
/*
__attribute__((always_inline)) uint32_t contextidr_get_helper(void)
{
// Read from CONTEXTIDR using CP15.
// See ARMv7-A,R, section B4.1.36.
uint32_t contextidr;
#ifdef __ARMCC_VERSION
__asm("MRC p15, 0, contextidr, c13, c0, 1");
#else
__asm("MRC p15, 0, %0, c13, c0, 1" : "=r" (contextidr));
#endif
return contextidr;
}
*/
static inline __attribute__((always_inline)) void contextidr_set_helper(uint32_t contextidr)
{
// Write to CONTEXTIDR using CP15.
// See ARMv7-A,R, section B4.1.36.
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, contextidr, c13, c0, 1");
#else
__asm("MCR p15, 0, %0, c13, c0, 1" : : "r" (contextidr));
#endif
}
/*
__attribute__((always_inline)) uint32_t dacr_get_helper(void)
{
// Read from DACR using CP15.
// See ARMv7-A,R, section B4.1.43.
uint32_t dacr;
#ifdef __ARMCC_VERSION
__asm("MRC p15, 0, dacr, c3, c0, 0");
#else
__asm("MRC p15, 0, %0, c3, c0, 0" : "=r" (dacr));
#endif
return dacr;
}
*/
static inline __attribute__((always_inline)) void dacr_set_helper(uint32_t dacr)
{
// Write to DACR using CP15.
// See ARMv7-A,R, section B4.1.43.
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, dacr, c3, c0, 0");
#else
__asm("MCR p15, 0, %0, c3, c0, 0" : : "r" (dacr));
#endif
}
static inline __attribute__((always_inline)) uint32_t ttbcr_get_helper(void)
{
// Read from TTBCR using CP15.
// See ARMv7-A,R, section B4.1.153.
uint32_t ttbcr;
#ifdef __ARMCC_VERSION
__asm("MRC p15, 0, ttbcr, c2, c0, 2");
#else
__asm("MRC p15, 0, %0 , c2, c0, 2" : "=r" (ttbcr));
#endif
return ttbcr;
}
static inline __attribute__((always_inline)) void ttbcr_set_helper(uint32_t ttbcr)
{
// Write to TTBCR using CP15.
// See ARMv7-A,R, section B4.1.153.
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, ttbcr, c2, c0, 2");
#else
__asm("MCR p15, 0, %0, c2, c0, 2" : : "r" (ttbcr));
#endif
}
static inline __attribute__((always_inline)) uint32_t ttbr0_get_helper(void)
{
// Read the TTBR0 using CP15.
// See ARMv7-A,R, section B4.1.154.
uint32_t ttbr0;
#ifdef __ARMCC_VERSION
__asm("MRC p15, 0, ttbr0, c2, c0, 0");
#else
__asm("MRC p15, 0, %0, c2, c0, 0" : "=r" (ttbr0));
#endif
return ttbr0;
}
static inline __attribute__((always_inline)) void ttbr0_set_helper(uint32_t ttbr0)
{
// Write to TTBR0 using CP15.
// See ARMv7-A,R, section B4.1.154.
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, ttbr0, c2, c0, 0");
#else
__asm("MCR p15, 0, %0, c2, c0, 0" : : "r" (ttbr0));
#endif
}
static inline __attribute__((always_inline)) uint32_t ttbr1_get_helper(void)
{
// Read the TTBR1 using CP15.
// See ARMv7-A,R, section B4.1.155.
uint32_t ttbr1;
#ifdef __ARMCC_VERSION
__asm("MRC p15, 0, ttbr1, c2, c0, 1");
#else
__asm("MRC p15, 0, %0, c2, c0, 1" : "=r" (ttbr1));
#endif
return ttbr1;
}
static inline __attribute__((always_inline)) void ttbr1_set_helper(uint32_t ttbr1)
{
// Write to TTBR1 using CP15.
// See ARMv7-A,R, section B4.1.155.
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, ttbr1, c2, c0, 1");
#else
__asm("MCR p15, 0, %0, c2, c0, 1" : : "r" (ttbr1));
#endif
}
/////
ALT_STATUS_CODE alt_mmu_init(void)
{
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_uninit(void)
{
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_ttb1_init(uint32_t* ttb1)
{
uint32_t ttbcr = ttbcr_get_helper();
uint32_t ttbcr_n = ALT_CPU_TTBCR_N_VALUE_GET(ttbcr);
// Verify ttb1 buffer alignment.
if ((uintptr_t)ttb1 & ~ALT_CPU_TTBR0_TTB0BASEADDR_SET_MSK(ttbcr_n))
{
// addr must align to 2^(14 - TTBCR.N) bytes.
return ALT_E_BAD_ARG;
}
// The TTB1 size really depends on TTBCR.N value and if it will be used for
// TTBR0 or TTBR1. The documentation just states that it should be 16 KiB.
// See ARMv7-A,R, section B3.5.4.
memset(ttb1, 0, ALT_MMU_TTB1_SIZE);
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_ttb1_desc_set(uint32_t* ttb1,
const void* va,
const uint32_t desc)
{
bool supersection = 0;
// Validate the [va] parameter alignment based on the entry [desc] is describing.
// - Fault, Page Table, or section: 1 MiB.
// - Supersection: 16 MiB
// - Other: error.
switch (ALT_MMU_TTB1_TYPE_GET(desc))
{
case ALT_MMU_TTB1_TYPE_SET(0x2): // Section or Supersection sans Physical Address Extension
// Check bit 18, which determines if it is a regular or super variant
if (desc & (1 << 18))
{
// Mark that we are describing a supersection.
supersection = true;
// Supersection: Check for 16 MiB alignment
if ((uintptr_t)va & (ALT_MMU_SUPERSECTION_SIZE - 1))
{
return ALT_E_BAD_ARG;
}
break;
}
else
{
// Section, fall through.
}
case ALT_MMU_TTB1_TYPE_SET(0x0): // Fault
case ALT_MMU_TTB1_TYPE_SET(0x1): // Page Table
// Section, Fault, or Page Table: check for 1 MiB alignment
if ((uintptr_t)va & (ALT_MMU_SECTION_SIZE - 1))
{
return ALT_E_BAD_ARG;
}
break;
case ALT_MMU_TTB1_TYPE_SET(0x3): // Supersection with Physical Address Extension
// The SoCFPGA does not support PAE.
return ALT_E_BAD_ARG;
}
// The [va] looks good! Add entry into the TTB1.
// TTB1 is indexed by va[31-N:20]. This function assumes N = 0.
uint32_t index = (uintptr_t)va >> 20;
if (supersection == false)
{
ttb1[index] = desc;
}
else
{
// Supersection needs the entry to be repeated 16x.
for (int i = 0; i < 16; ++i)
{
ttb1[index + i] = desc;
}
}
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_ttb2_desc_set(const uint32_t* ttb1,
const void* va,
const uint32_t desc)
{
bool largepage = false;
// Validate the [va] parameter alignment based on the entry [desc] is describing.
// - Fault, Small Page: 4 KiB
// - Large Page: 64 KiB
switch (ALT_MMU_TTB2_TYPE_GET(desc))
{
case ALT_MMU_TTB2_TYPE_SET(0x0): // Fault
case ALT_MMU_TTB2_TYPE_SET(0x2): // Small Page, XN = 0
case ALT_MMU_TTB2_TYPE_SET(0x3): // Small Page, XN = 1
if ((uintptr_t)va & (ALT_MMU_SMALL_PAGE_SIZE - 1))
{
return ALT_E_BAD_ARG;
}
break;
case ALT_MMU_TTB2_TYPE_SET(0x1): // Large Page
if ((uintptr_t)va & (ALT_MMU_LARGE_PAGE_SIZE - 1))
{
return ALT_E_BAD_ARG;
}
largepage = true;
break;
}
// The [va] looks good! Add entry into TTB1->TTB2.
// Locate the TTB1 entry
uint32_t ttb1_desc = ttb1[(uintptr_t)va >> 20];
// Verify that [ttb1_desc] is a pagetable.
if (ALT_MMU_TTB1_TYPE_GET(ttb1_desc) != ALT_MMU_TTB1_TYPE_SET(0x1))
{
return ALT_E_BAD_ARG;
}
// Locate TTB2 given [ttb1_desc]
uint32_t * ttb2 = (uint32_t *)(ttb1_desc & ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_MASK);
// TTB2 is indexed by va[19:12].
uint32_t index = ALT_MMU_PAGE_TABLE_INDEX(va);
if (largepage == false)
{
ttb2[index] = desc;
}
else
{
// Large page needs the entry to be repeated 16x.
for (int i = 0; i < 16; ++i)
{
ttb2[index + i] = desc;
}
}
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_disable(void)
{
uint32_t sctlr = sctlr_get_helper();
if (sctlr & ALT_CPU_SCTLR_C_SET_MSK)
{
dprintf("WARN[MMU]: Data cache still active.\n");
}
sctlr &= ~ALT_CPU_SCTLR_M_SET_MSK;
sctlr_set_helper(sctlr);
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_enable(void)
{
alt_mmu_tlb_invalidate();
uint32_t sctlr = sctlr_get_helper();
sctlr |= ALT_CPU_SCTLR_M_SET_MSK;
sctlr_set_helper(sctlr);
return ALT_E_SUCCESS;
}
void * alt_mmu_TTBR0_get(void)
{
uint32_t ttbcr = ttbcr_get_helper();
uint32_t ttbcr_n = ALT_CPU_TTBCR_N_VALUE_GET(ttbcr);
uint32_t ttbr0 = ttbr0_get_helper();
return (void *)(ALT_CPU_TTBR0_TTB0BASEADDR_SET_MSK(ttbcr_n) & ttbr0);
}
ALT_STATUS_CODE alt_mmu_TTBR0_set(const void* addr)
{
uint32_t ttbcr = ttbcr_get_helper();
uint32_t ttbcr_n = ALT_CPU_TTBCR_N_VALUE_GET(ttbcr);
if ((uintptr_t)addr & ~ALT_CPU_TTBR0_TTB0BASEADDR_SET_MSK(ttbcr_n))
{
// addr must align to 2^(14 - TTBCR.N) bytes.
return ALT_E_BAD_ARG;
}
// The Translation table must reside in Normal Memory, so pick the most
// performant attributes.
uint32_t ttbr0 = ALT_CPU_TTBR0_RGN_WBA // Translation table is WBA for outer cacheability
| ALT_CPU_TTBR0_IRGN_WBA; // Translation table is WBA for inner cacheability
ttbr0 &= ~ALT_CPU_TTBR0_TTB0BASEADDR_SET_MSK(ttbcr_n);
ttbr0 |= (uint32_t)addr;
ttbr0_set_helper(ttbr0);
return ALT_E_SUCCESS;
}
void * alt_mmu_TTBR1_get(void)
{
uint32_t ttbr1 = ttbr1_get_helper();
return (void *)(ALT_CPU_TTBR1_TTB1BASEADDR_SET_MSK & ttbr1);
}
ALT_STATUS_CODE alt_mmu_TTBR1_set(const void* addr)
{
if ((uintptr_t)addr & ~ALT_CPU_TTBR1_TTB1BASEADDR_SET_MSK)
{
// addr must align to 16 KiB.
return ALT_E_BAD_ARG;
}
uint32_t ttbr1 = ttbr1_get_helper();
ttbr1 &= ~ALT_CPU_TTBR1_TTB1BASEADDR_SET_MSK;
ttbr1 |= (uint32_t)addr;
ttbr1_set_helper(ttbr1);
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_TTBCR_set(const bool enable_ttbr0_walk,
const bool enable_ttbr1_walk,
const uint32_t base_addr_width)
{
uint32_t ttbcr = 0;
if (!enable_ttbr0_walk)
{
ttbcr |= ALT_CPU_TTBCR_PD0_SET_MSK;
}
if (!enable_ttbr1_walk)
{
ttbcr |= ALT_CPU_TTBCR_PD1_SET_MSK;
}
if (base_addr_width > 7)
{
return ALT_E_BAD_ARG;
}
ttbcr |= base_addr_width;
ttbcr_set_helper(ttbcr);
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_DACR_set(const ALT_MMU_DAP_t domain_ap[],
const size_t num_elem)
{
if (num_elem > 16)
{
return ALT_E_BAD_ARG;
}
uint32_t dacr = 0;
for (int i = 0; i < num_elem; ++i)
{
ALT_MMU_DAP_t ap = domain_ap[i];
switch (ap)
{
case ALT_MMU_DAP_NO_ACCESS:
case ALT_MMU_DAP_CLIENT:
case ALT_MMU_DAP_MANAGER:
dacr |= ap << (i * 2);
break;
default:
case ALT_MMU_DAP_RESERVED:
return ALT_E_BAD_ARG;
}
}
dacr_set_helper(dacr);
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_CONTEXTIDR_set(const uint32_t procid, const uint32_t asid)
{
if (procid > 0x00ffffff)
{
return ALT_E_BAD_ARG;
}
if (asid > 0xff)
{
return ALT_E_BAD_ARG;
}
uint32_t contextidr = (procid << 8) | (asid << 0);
contextidr_set_helper(contextidr);
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_tlb_invalidate(void)
{
// Issue TLBIALL (TLB Invalidate All)
// See ARMv7-A,R, section B4.1.135.
uint32_t dummy = 0;
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, dummy, c8, c3, 0");
#else
__asm("MCR p15, 0, %0, c8, c3, 0" : : "r" (dummy));
#endif
// Ensure all TLB maintenance operations complete before returning.
__asm("dsb");
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_tlb_invalidate_is(void)
{
// Issue TLBIALLIS (TLB Invalidate All, Inner Shareable)
// See ARMv7-A,R, section B4.1.138.
uint32_t dummy = 0;
#ifdef __ARMCC_VERSION
__asm("MCR p15, 0, dummy, c8, c7, 0");
#else
__asm("MCR p15, 0, %0, c8, c7, 0" : : "r" (dummy));
#endif
// Ensure all TLB maintenance operations complete before returning.
__asm("dsb");
return ALT_E_SUCCESS;
}
/////
// The #define value for PAGETABLE is designed to make the security check efficient.
#define ALT_VREGION_1MIB (2) /* 2 */
#define ALT_VREGION_PAGETABLE_S ((int)ALT_MMU_TTB_NS_SECURE) /* 0 */
#define ALT_VREGION_PAGETABLE_NS ((int)ALT_MMU_TTB_NS_NON_SECURE) /* 1 */
static ALT_STATUS_CODE alt_vregion_mark_pagetable(char * vregion, ALT_MMU_TTB_NS_t security)
{
if (*vregion == ALT_VREGION_1MIB)
{
*vregion = (int)security;
}
else if (*vregion != (int)security)
{
return ALT_E_ERROR;
}
return ALT_E_SUCCESS;
}
static size_t alt_mmu_va_space_storage_required_internal(const ALT_MMU_MEM_REGION_t* mem_regions,
const size_t num_mem_regions,
char * vregion)
{
for (int i = 0; i < ALT_MMU_TTB1_SECTION_COUNT; ++i)
{
vregion[i] = ALT_VREGION_1MIB;
}
// For each region entry, mark the TTB1 as either fault, section, pagetable.
// The total space required is the space required for the TTB1 (16 KiB) + pagetables * (1 KiB)
for (int i = 0; i < num_mem_regions; ++i)
{
uintptr_t va = (uintptr_t)mem_regions[i].va;
uintptr_t pa = (uintptr_t)mem_regions[i].pa;
uint32_t size = mem_regions[i].size;
ALT_MMU_TTB_NS_t security = mem_regions[i].security;
// Verify [va] aligns to 4 KiB
if (va & (ALT_MMU_SMALL_PAGE_SIZE - 1))
{
return 0;
}
// Verify [pa] aligns to 4 KiB
if (pa & (ALT_MMU_SMALL_PAGE_SIZE - 1))
{
return 0;
}
// Verify [size] aligns to 4 KiB
if (size & (ALT_MMU_SMALL_PAGE_SIZE - 1))
{
return 0;
}
// Mark the regions at the start of an unaligned 1 MiB as pagetable.
// Align the [va] to 1 MiB and subtract that from the [size] left to describe.
if (va & (ALT_MMU_SECTION_SIZE - 1))
{
// Pagetables must be either S or NS. If the pagetable was
// previously marked as something different, the regions described
// will not be implementable.
if (alt_vregion_mark_pagetable(&vregion[va >> 20],
security) != ALT_E_SUCCESS)
{
return 0;
}
uint32_t segment = MIN(ALT_MMU_SECTION_SIZE - (va & (ALT_MMU_SECTION_SIZE - 1)), size);
va += segment;
pa += segment;
size -= segment;
}
// Skip each 1 MiB aligned segment of size 1 MiB. These regions require
// pagetable if the PA is not 1 MiB aligned.
// [pa] is not used after this point.
if (pa & (ALT_MMU_SECTION_SIZE - 1))
{
// PA is not 1 MiB aligned. Everything must use pagetables.
while (size >= ALT_MMU_SECTION_SIZE)
{
// Pagetables must be either S or NS. If the pagetable was
// previously marked as something different, the regions described
// will not be implementable.
if (alt_vregion_mark_pagetable(&vregion[va >> 20],
security) != ALT_E_SUCCESS)
{
return 0;
}
va += ALT_MMU_SECTION_SIZE;
// pa += ALT_MMU_SECTION_SIZE;
size -= ALT_MMU_SECTION_SIZE;
}
}
else
{
// PA is 1 MiB aligned. Sections or supersections can be used.
while (size >= ALT_MMU_SECTION_SIZE)
{
va += ALT_MMU_SECTION_SIZE;
// pa += ALT_MMU_SECTION_SIZE;
size -= ALT_MMU_SECTION_SIZE;
}
}
// The remainder should be a 1 MiB aligned segment of less than 1 MiB. Mark that region as pagetable.
if (size)
{
// Pagetables must be either S or NS. If the pagetable was
// previously marked as something different, the regions described
// will not be implementable.
if (alt_vregion_mark_pagetable(&vregion[va >> 20],
security) != ALT_E_SUCCESS)
{
return 0;
}
}
}
// Calculate the size as 16 KiB (TTB1) + 1 KiB * (TTB2 or the number of pagetables)
size_t reqsize = ALT_MMU_TTB1_SIZE;
for (int i = 0; i < ALT_MMU_TTB1_SECTION_COUNT; ++i)
{
if (vregion[i] != ALT_VREGION_1MIB)
{
reqsize += ALT_MMU_TTB2_SIZE;
}
}
return reqsize;
}
size_t alt_mmu_va_space_storage_required(const ALT_MMU_MEM_REGION_t* mem_regions,
const size_t num_mem_regions)
{
char vregion[ALT_MMU_TTB1_SECTION_COUNT];
return alt_mmu_va_space_storage_required_internal(mem_regions,
num_mem_regions,
vregion);
}
/*
static inline uint32_t alt_mmu_va_space_gen_fault(void)
{
return 0;
}
*/
static inline uint32_t alt_mmu_va_space_gen_pagetable(uintptr_t pagetable,
const ALT_MMU_MEM_REGION_t * mem)
{
if (mem->attributes == ALT_MMU_ATTR_FAULT)
{
return 0;
}
return
ALT_MMU_TTB1_TYPE_SET(0x1)
| ALT_MMU_TTB1_PAGE_TBL_NS_SET(mem->security)
| ALT_MMU_TTB1_PAGE_TBL_DOMAIN_SET(0)
| ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_SET(pagetable >> 10);
}
static inline uint32_t alt_mmu_va_space_gen_section(uintptr_t pa,
const ALT_MMU_MEM_REGION_t * mem)
{
if (mem->attributes == ALT_MMU_ATTR_FAULT)
{
return 0;
}
int tex = (mem->attributes >> 4) & 0x7;
int c = (mem->attributes >> 1) & 0x1;
int b = (mem->attributes >> 0) & 0x1;
return
ALT_MMU_TTB1_TYPE_SET(0x2)
| ALT_MMU_TTB1_SECTION_B_SET(b)
| ALT_MMU_TTB1_SECTION_C_SET(c)
| ALT_MMU_TTB1_SECTION_XN_SET(mem->execute)
| ALT_MMU_TTB1_SECTION_DOMAIN_SET(0)
| ALT_MMU_TTB1_SECTION_AP_SET(mem->access)
| ALT_MMU_TTB1_SECTION_TEX_SET(tex)
| ALT_MMU_TTB1_SECTION_S_SET(mem->shareable)
| ALT_MMU_TTB1_SECTION_NG_SET(0)
| ALT_MMU_TTB1_SECTION_NS_SET(mem->security)
| ALT_MMU_TTB1_SECTION_BASE_ADDR_SET(pa >> 20);
}
static inline uint32_t alt_mmu_va_space_gen_supersection(uintptr_t pa,
const ALT_MMU_MEM_REGION_t * mem)
{
if (mem->attributes == ALT_MMU_ATTR_FAULT)
{
return 0;
}
int tex = (mem->attributes >> 4) & 0x7;
int c = (mem->attributes >> 1) & 0x1;
int b = (mem->attributes >> 0) & 0x1;
return
ALT_MMU_TTB1_TYPE_SET(0x2) | (1 << 18) // bit 18 marks section as being super.
| ALT_MMU_TTB1_SUPERSECTION_B_SET(b)
| ALT_MMU_TTB1_SUPERSECTION_C_SET(c)
| ALT_MMU_TTB1_SUPERSECTION_XN_SET(mem->execute)
| ALT_MMU_TTB1_SUPERSECTION_DOMAIN_SET(0)
| ALT_MMU_TTB1_SUPERSECTION_AP_SET(mem->access)
| ALT_MMU_TTB1_SUPERSECTION_TEX_SET(tex)
| ALT_MMU_TTB1_SUPERSECTION_S_SET(mem->shareable)
| ALT_MMU_TTB1_SUPERSECTION_NG_SET(0)
| ALT_MMU_TTB1_SUPERSECTION_NS_SET(mem->security)
| ALT_MMU_TTB1_SUPERSECTION_BASE_ADDR_SET(pa >> 24);
}
static inline uint32_t alt_mmu_va_space_gen_smallpage(uintptr_t pa,
const ALT_MMU_MEM_REGION_t * mem)
{
if (mem->attributes == ALT_MMU_ATTR_FAULT)
{
return 0;
}
int tex = (mem->attributes >> 4) & 0x7;
int c = (mem->attributes >> 1) & 0x1;
int b = (mem->attributes >> 0) & 0x1;
// NS bit (mem->security) is ignored as it is set in TTB1.
return
ALT_MMU_TTB2_TYPE_SET(0x2)
| ALT_MMU_TTB2_SMALL_PAGE_XN_SET(mem->execute)
| ALT_MMU_TTB2_SMALL_PAGE_B_SET(b)
| ALT_MMU_TTB2_SMALL_PAGE_C_SET(c)
| ALT_MMU_TTB2_SMALL_PAGE_AP_SET(mem->access)
| ALT_MMU_TTB2_SMALL_PAGE_TEX_SET(tex)
| ALT_MMU_TTB2_SMALL_PAGE_S_SET(mem->shareable)
| ALT_MMU_TTB2_SMALL_PAGE_NG_SET(0)
| ALT_MMU_TTB2_SMALL_PAGE_BASE_ADDR_SET(pa >> 12);
}
static inline uint32_t alt_mmu_va_space_gen_largepage(uintptr_t pa,
const ALT_MMU_MEM_REGION_t * mem)
{
if (mem->attributes == ALT_MMU_ATTR_FAULT)
{
return 0;
}
int tex = (mem->attributes >> 4) & 0x7;
int c = (mem->attributes >> 1) & 0x1;
int b = (mem->attributes >> 0) & 0x1;
// NS bit (mem->security) is ignored as it is set in TTB1.
return
ALT_MMU_TTB2_TYPE_SET(0x1)
| ALT_MMU_TTB2_LARGE_PAGE_B_SET(b)
| ALT_MMU_TTB2_LARGE_PAGE_C_SET(c)
| ALT_MMU_TTB2_LARGE_PAGE_AP_SET(mem->access)
| ALT_MMU_TTB2_LARGE_PAGE_S_SET(mem->shareable)
| ALT_MMU_TTB2_LARGE_PAGE_NG_SET(0)
| ALT_MMU_TTB2_LARGE_PAGE_TEX_SET(tex)
| ALT_MMU_TTB2_LARGE_PAGE_XN_SET(mem->execute)
| ALT_MMU_TTB2_LARGE_PAGE_BASE_ADDR_SET(pa >> 16);
}
static ALT_STATUS_CODE alt_mmu_ttb2_init(uint32_t * ttb2)
{
// For TTB2 (page tables), the page table base address in TTB1 is
// bits[31:10]. Thus it must be 2^10 byte aligned or 1 KiB.
// Source: ARMv7-A,R, section B3.5.1.
if ((uintptr_t)ttb2 & ((1 << 10) - 1))
{
return ALT_E_BAD_ARG;
}
memset(ttb2, 0, ALT_MMU_TTB2_SIZE);
return ALT_E_SUCCESS;
}
/////
ALT_STATUS_CODE alt_mmu_va_space_create(uint32_t** ttb1,
const ALT_MMU_MEM_REGION_t* mem_regions,
const size_t num_mem_regions,
alt_mmu_ttb_alloc_t ttb_alloc,
void * ttb_alloc_context)
{
char vregion[ALT_MMU_TTB1_SECTION_COUNT];
size_t reqsize = alt_mmu_va_space_storage_required_internal(mem_regions,
num_mem_regions,
vregion);
if (reqsize == 0)
{
return ALT_E_ERROR;
}
char * memory = ttb_alloc(reqsize, ttb_alloc_context);
size_t allocated = 0;
// Verify allocation
if (memory == NULL)
{
return ALT_E_ERROR;
}
// Verify alignment
// For TTBR0, the translation table must be aligned to 2^x bytes, where
// x = (14 - TTBCR.N). Because VA space sets TTBCR.N = 0, x = 14, and the
// table must be aligned to 2^14 or 16 KiB.
// Source: ARMv7-A,R, section B4.1.154.
// For TTB2 (page tables), the page table base address in TTB1 is
// bits[31:10]. Thus it must be 2^10 byte aligned or 1 KiB.
// Source: ARMv7-A,R, section B3.5.1.
// The stricter of the two alignment is 16 KiB.
if ((uintptr_t)memory & ((1 << 14) - 1))
{
return ALT_E_BAD_ARG;
}
// "allocate" space for the TTB1.
if (allocated + ALT_MMU_TTB1_SIZE > reqsize)
{
return ALT_E_ERROR;
}
*ttb1 = (uint32_t *)memory;
allocated += ALT_MMU_TTB1_SIZE;
if (alt_mmu_ttb1_init(*ttb1) != ALT_E_SUCCESS)
{
return ALT_E_ERROR;
}
// "allocate" space for each pagetable in [vregion]
for (int i = 0; i < ALT_MMU_TTB1_SECTION_COUNT; ++i)
{
if (vregion[i] != ALT_VREGION_1MIB)
{
if (allocated + ALT_MMU_TTB2_SIZE > reqsize)
{
return ALT_E_ERROR;
}
uint32_t * pagetable = (uint32_t *)(memory + allocated);
allocated += ALT_MMU_TTB2_SIZE;
alt_mmu_ttb2_init(pagetable);
ALT_MMU_MEM_REGION_t mem_region;
mem_region.attributes = ALT_MMU_ATTR_STRONG; // Any non-FAULT will work.
mem_region.security = (ALT_MMU_TTB_NS_t)vregion[i];
uint32_t desc = alt_mmu_va_space_gen_pagetable((uintptr_t)pagetable, &mem_region);
(*ttb1)[i] = desc;
}
}
// The allocated size should match the requested size. If not, this means
// that the regions descriptor changed between calling
// alt_mmu_va_space_storage_required() and alt_mmu_va_space_create().
if (reqsize != allocated)
{
return ALT_E_ERROR;
}
// Iterate through all region descriptors
for (size_t i = 0; i < num_mem_regions; ++i)
{
uintptr_t va = (uintptr_t)mem_regions[i].va;
uintptr_t pa = (uintptr_t)mem_regions[i].pa;
uint32_t size = mem_regions[i].size;
// Determine the va/pa relative alignment: 4 KiB, 64 KiB, 1 MiB, 16 MiB
uint32_t alignopt[] =
{
ALT_MMU_SUPERSECTION_SIZE,
ALT_MMU_SECTION_SIZE,
ALT_MMU_LARGE_PAGE_SIZE
};
// Relative alignment of [va] and [pa].
int relalign = ALT_MMU_SMALL_PAGE_SIZE;
for (int j = 0; j < ARRAY_COUNT(alignopt); ++j)
{
if ( (va & (alignopt[j] - 1)) ==
(pa & (alignopt[j] - 1)) )
{
relalign = alignopt[j];
break;
}
}
// Page the 1 MiB unaligned segment of [va]. Areas requiring page tables
// should already have those page tables created previously in this
// function.
if (va & (ALT_MMU_SECTION_SIZE - 1))
{
// This is the size of the memory segment after paging which will cause the [va] to align to a 1 MiB,
// or up to the size of the region being processed, whichever is smaller.
uint32_t segsize = MIN(ALT_MMU_SECTION_SIZE - (va & (ALT_MMU_SECTION_SIZE - 1)), size);
if (relalign >= ALT_MMU_LARGE_PAGE_SIZE)
{
// Because of the 64 KiB relative alignment, try to use large pages.
// Use small pages until [va] is 64KiB aligned.
while (((va & (ALT_MMU_LARGE_PAGE_SIZE - 1)) != 0) && (segsize >= ALT_MMU_SMALL_PAGE_SIZE))
{
uint32_t desc = alt_mmu_va_space_gen_smallpage(pa, &mem_regions[i]);
uint32_t * pagetable = (uint32_t *)((*ttb1)[va >> 20] & ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_MASK);
uint32_t ptindex = ALT_MMU_PAGE_TABLE_INDEX(va);
// Detect if an existing non-fault region has already been created.
// We cannot detect if a fault region is requested and a region description is already a fault,
// which it is by default.
if (pagetable[ptindex] != 0)
{
return ALT_E_ERROR;
}
pagetable[ptindex] = desc;
va += ALT_MMU_SMALL_PAGE_SIZE;
pa += ALT_MMU_SMALL_PAGE_SIZE;
segsize -= ALT_MMU_SMALL_PAGE_SIZE;
size -= ALT_MMU_SMALL_PAGE_SIZE;
}
// Use large pages for the rest of the 64 KiB aligned areas.
while (segsize >= ALT_MMU_LARGE_PAGE_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_largepage(pa, &mem_regions[i]);
uint32_t * pagetable = (uint32_t *)((*ttb1)[va >> 20] & ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_MASK);
uint32_t ptindex = ALT_MMU_PAGE_TABLE_INDEX(va);
for (int j = 0; j < 16; ++j)
{
if (pagetable[ptindex + j] != 0)
{
return ALT_E_ERROR;
}
pagetable[ptindex + j] = desc;
}
va += ALT_MMU_LARGE_PAGE_SIZE;
pa += ALT_MMU_LARGE_PAGE_SIZE;
segsize -= ALT_MMU_LARGE_PAGE_SIZE;
size -= ALT_MMU_LARGE_PAGE_SIZE;
}
// There is a chance that the segment is so small that it does cause the progress to align to the 1 MiB.
// If this is the case, page out the rest of segsize using small pages, and the remaining size to be 0.
while (segsize >= ALT_MMU_SMALL_PAGE_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_smallpage(pa, &mem_regions[i]);
uint32_t * pagetable = (uint32_t *)((*ttb1)[va >> 20] & ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_MASK);
uint32_t ptindex = ALT_MMU_PAGE_TABLE_INDEX(va);
if (pagetable[ptindex] != 0)
{
return ALT_E_ERROR;
}
pagetable[ptindex] = desc;
va += ALT_MMU_SMALL_PAGE_SIZE;
pa += ALT_MMU_SMALL_PAGE_SIZE;
segsize -= ALT_MMU_SMALL_PAGE_SIZE;
size -= ALT_MMU_SMALL_PAGE_SIZE;
}
}
else
{
// No large pages possible, Use small pages only.
while (segsize >= ALT_MMU_SMALL_PAGE_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_smallpage(pa, &mem_regions[i]);
uint32_t * pagetable = (uint32_t *)((*ttb1)[va >> 20] & ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_MASK);
uint32_t ptindex = ALT_MMU_PAGE_TABLE_INDEX(va);
if (pagetable[ptindex] != 0)
{
return ALT_E_ERROR;
}
pagetable[ptindex] = desc;
va += ALT_MMU_SMALL_PAGE_SIZE;
pa += ALT_MMU_SMALL_PAGE_SIZE;
segsize -= ALT_MMU_SMALL_PAGE_SIZE;
size -= ALT_MMU_SMALL_PAGE_SIZE;
}
}
}
// Page each the larger 1 MiB aligned, 1 MiB sized segments.
// If [va] and [pa] are relatively 16 MiB aligned and the size remaining
// to be described is greater than 16 MiB, use supersections.
// If [va] and [pa] are relatively 1 MiB aligned and the size remaining
// to be described is greater than 1 MiB, use sections.
// Otherwise use pagetables for everything remaining.
if ( (relalign >= ALT_MMU_SUPERSECTION_SIZE)
&& (size >= ALT_MMU_SUPERSECTION_SIZE))
{
// Attempt to use supersections. This may not always be possible.
// Use regular sections for the areas before supersections that does not align to 16 MiB
while (((va & (ALT_MMU_SUPERSECTION_SIZE - 1)) != 0) && (size >= ALT_MMU_SECTION_SIZE))
{
uint32_t desc = alt_mmu_va_space_gen_section(pa, &mem_regions[i]);
if ((*ttb1)[va >> 20] != 0)
{
return ALT_E_ERROR;
}
(*ttb1)[va >> 20] = desc;
va += ALT_MMU_SECTION_SIZE;
pa += ALT_MMU_SECTION_SIZE;
size -= ALT_MMU_SECTION_SIZE;
}
// Use supersections for the 16 MiB aligned areas
while (size >= ALT_MMU_SUPERSECTION_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_supersection(pa, &mem_regions[i]);
for (int j = 0; j < 16; ++j)
{
if ((*ttb1)[(va >> 20) + j] != 0)
{
return ALT_E_ERROR;
}
(*ttb1)[(va >> 20) + j] = desc;
}
va += ALT_MMU_SUPERSECTION_SIZE;
pa += ALT_MMU_SUPERSECTION_SIZE;
size -= ALT_MMU_SUPERSECTION_SIZE;
}
// Use regular sections for the areas after supersections that does not align to 16 MiB.
while (size >= ALT_MMU_SECTION_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_section(pa, &mem_regions[i]);
if ((*ttb1)[va >> 20] != 0)
{
return ALT_E_ERROR;
}
(*ttb1)[va >> 20] = desc;
va += ALT_MMU_SECTION_SIZE;
pa += ALT_MMU_SECTION_SIZE;
size -= ALT_MMU_SECTION_SIZE;
}
}
else if ( (relalign >= ALT_MMU_SECTION_SIZE)
&& (size >= ALT_MMU_SECTION_SIZE))
{
// No supersection possible. Use regular sections only.
while (size >= ALT_MMU_SECTION_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_section(pa, &mem_regions[i]);
if ((*ttb1)[va >> 20] != 0)
{
return ALT_E_ERROR;
}
(*ttb1)[va >> 20] = desc;
va += ALT_MMU_SECTION_SIZE;
pa += ALT_MMU_SECTION_SIZE;
size -= ALT_MMU_SECTION_SIZE;
}
}
// The remainder should be [va] 1 MiB aligned segment not able to use
// sections or supersections. Mark that region as pagetable.
// Use large pages if it is suitable.
if ((relalign >= ALT_MMU_LARGE_PAGE_SIZE) && (size >= ALT_MMU_LARGE_PAGE_SIZE))
{
while (size >= ALT_MMU_LARGE_PAGE_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_largepage(pa, &mem_regions[i]);
uint32_t * pagetable = (uint32_t *)((*ttb1)[va >> 20] & ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_MASK);
uint32_t ptindex = ALT_MMU_PAGE_TABLE_INDEX(va);
for (int j = 0; j < 16; ++j)
{
if (pagetable[ptindex + j] != 0)
{
return ALT_E_ERROR;
}
pagetable[ptindex + j] = desc;
}
va += ALT_MMU_LARGE_PAGE_SIZE;
pa += ALT_MMU_LARGE_PAGE_SIZE;
size -= ALT_MMU_LARGE_PAGE_SIZE;
}
}
while (size >= ALT_MMU_SMALL_PAGE_SIZE)
{
uint32_t desc = alt_mmu_va_space_gen_smallpage(pa, &mem_regions[i]);
uint32_t * pagetable = (uint32_t *)((*ttb1)[va >> 20] & ALT_MMU_TTB1_PAGE_TBL_BASE_ADDR_MASK);
uint32_t ptindex = ALT_MMU_PAGE_TABLE_INDEX(va);
if (pagetable[ptindex] != 0)
{
return ALT_E_ERROR;
}
pagetable[ptindex] = desc;
va += ALT_MMU_SMALL_PAGE_SIZE;
pa += ALT_MMU_SMALL_PAGE_SIZE;
size -= ALT_MMU_SMALL_PAGE_SIZE;
}
} // for (size_t i = 0; i < num_mem_regions; ++i)
return ALT_E_SUCCESS;
}
ALT_STATUS_CODE alt_mmu_va_space_enable(const uint32_t * ttb1)
{
ALT_STATUS_CODE status = ALT_E_SUCCESS;
// Set TTBCR to use N=0
if (status == ALT_E_SUCCESS)
{
status = alt_mmu_TTBCR_set(true,
true,
0);
if (status != ALT_E_SUCCESS)
{
dprintf("DEBUG[MMU:VA]: Failure on line %d.\n", __LINE__);
}
}
// Set TTBR0 to use ttb1
if (status == ALT_E_SUCCESS)
{
status = alt_mmu_TTBR0_set(ttb1);
if (status != ALT_E_SUCCESS)
{
dprintf("DEBUG[MMU:VA]: Failure on line %d.\n", __LINE__);
}
}
// Configure DACRs to be client domain.
if (status == ALT_E_SUCCESS)
{
ALT_MMU_DAP_t domain_ap[16];
for (int i = 0; i < 16; ++i)
{
domain_ap[i] = ALT_MMU_DAP_CLIENT;
}
status = alt_mmu_DACR_set(domain_ap, 16);
if (status != ALT_E_SUCCESS)
{
dprintf("DEBUG[MMU:VA]: Failure on line %d.\n", __LINE__);
}
}
// Enable MMU (implicitly invalidates TLBs)
if (status == ALT_E_SUCCESS)
{
status = alt_mmu_enable();
if (status != ALT_E_SUCCESS)
{
dprintf("DEBUG[MMU:VA]: Failure on line %d.\n", __LINE__);
}
}
return status;
}