The following (chapter D8) in the ARM documentation gives the VMSA (virtual memory system architecture).
Also useful (for me as a refresher) are these:After writing code to dump the basic MMU registers, I see this:
TTBR0_EL1 = 0000000000000000 TTBR1_EL1 = 0000000000000000 TCR_EL1 = 0000000000000000 TTBR0_EL2 = 000000007fff0000 TCR_EL2 = 0000000080803520The MMU is not set up for EL1.
The TTBR0_EL2 register is pointing to a page table at the top of our 1G address space, and it is no bigger than 64K in size.
Both TTBR0 and TTBR1 exist (for EL1) with the idea that TTBR1 will hold the address mapping for the operating system, and TTBR0 will hold the address mapping for the current user process.
The TCR has a multitude of fields.
TCR:ips = 00000000 TCR:t1sz = 00000000 TCR:t0sz = 00000020
The IPS bits (34:32) are 000 indicating a 4G (32 bit) intermediate physical address size.
the t1sz field is zero, which it should be as there is no TTBR1 register for EL2.
The intent of having two pointers (TTBR0 and TTBR1) for el1 is that TTBR1 would hold the constant mapping for the kernel, which TTBR0 would be used for the often changing map for the user process. I am told that linux never does this. Linux ignores TTBR1 and handles everything with TTBR0. Of course we are not dealing with linux, but this is interesting.
The page size is set by these fields:
TCR:tg0 = 00000000 TCR:tg1 = 00000002The tg1 value doesn't matter as we aren't using TTBR1 (and don't have TTBR1 at EL2).
PTE-7fff0000 0000000000000401 PTE-7fff0008 0000000040000711 PTE-7fff0010 0000000080000711 PTE-7fff0018 00000000c0000711 PTE-7fff0020 0000000000000000 (invalid) -- lots of zero entries. PTE-7fff1000 0000000000000401 PTE-7fff1008 0000000040000711 PTE-7fff1010 0000000080000711 PTE-7fff1018 00000000c0000711 -- lots of zero entries. -- just garbage from here on PTE-7fff2000 fffffffffff7ffff PTE-7fff2008 ffffffffffffffff PTE-7fff2010 ffffffffffffffff PTE-7fff2018 ffffffffffefff7f PTE-7fff2020 fffdffffffffffff
So the first level table is 4K bytes in size, so it has 512 entries. Each entry maps 1G, so the entire L1 table maps 512G. For our processor (the h5) we thus need only the first 4 of these entries. The rest are zero (invalid) and accessing those addresses will cause a fault.
The two lowest bits are important.
If the lowest bit is zero, the PTE is invalid.
If the two low bits are 01 it is a block descriptor.
If the two low bits are 11 it is a table descriptor.
It took some time for me to get a grip on what is going on here. There are two identical page tables, each with 512 entires. Only the first 4 are valid and each maps 1G. There are two page tables because the second is an "emergency" page table that gets switched to while the first one gets screwed with.
There are mysteries here that I don't understand yet. We have a 4K page size, yet each of these entries is a final PTE and yet maps 1G.
static struct mm_region sunxi_mem_map[] = {
{
/* SRAM, MMIO regions */
.virt = 0x0UL,
.phys = 0x0UL,
.size = 0x40000000UL,
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_NON_SHARE
}, {
/* RAM */
.virt = 0x40000000UL,
.phys = 0x40000000UL,
.size = CONFIG_SUNXI_DRAM_MAX_SIZE,
.attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) |
PTE_BLOCK_INNER_SHARE
}, {
/* List terminator */
0,
}
};
struct mm_region *mem_map = sunxi_mem_map;
The important value CONFIG_SUNXI_DRAM_MAX_SIZE is in include/generated/autoconf.h
and is:
#define CONFIG_SUNXI_DRAM_MAX_SIZE 0xC0000000So, U-boot should set up 2 regions in the mmu mapping. First a 1G region to hold device registers, then a 3G region for DRAM.
Tom's electronics pages / tom@mmto.org