aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWojtek Kosior <kwojtus@protonmail.com>2019-11-09 15:36:45 +0100
committerWojtek Kosior <kwojtus@protonmail.com>2019-11-09 15:36:45 +0100
commiteb286810ae841301b7647f3350b29a70d4a479b8 (patch)
tree3967ca19a9b4a04a2e2e19c688c2cdbdea6a5c7e
parentaf9b1bca3ecb1451665d9e859a7af40afb183d14 (diff)
downloadrpi-MMU-example-eb286810ae841301b7647f3350b29a70d4a479b8.tar.gz
rpi-MMU-example-eb286810ae841301b7647f3350b29a70d4a479b8.zip
enable the MMU - a cleaner way (describing registers with bitfield structs and unions)
-rw-r--r--cp_regs.h111
-rw-r--r--kernel.c218
-rw-r--r--translation_table_descriptors.h99
3 files changed, 288 insertions, 140 deletions
diff --git a/cp_regs.h b/cp_regs.h
new file mode 100644
index 0000000..98a8bb8
--- /dev/null
+++ b/cp_regs.h
@@ -0,0 +1,111 @@
+#include <stdint.h>
+
+
+// SCTLR - System Control Register
+
+// Wandering why I didn't typedef this struct with fields?
+// That's because
+typedef union
+{
+ uint32_t raw;
+ struct
+ {
+ uint32_t M : 1; // bit 0
+ uint32_t A : 1; // bit 1
+ uint32_t C : 1; // bit 2
+ uint32_t Bits_4_3 : 2; // bits 3:4
+ uint32_t CP15BEN : 1; // bit 5
+ uint32_t Bit_6 : 1; // bit 6
+ uint32_t B : 1; // bit 7
+ uint32_t Bits_9_8 : 2; // bits 9:8
+ uint32_t SW : 1; // bit 10
+ uint32_t Z : 1; // bit 11
+ uint32_t I : 1; // bit 12
+ uint32_t V : 1; // bit 13
+ uint32_t RR : 1; // bit 14
+ uint32_t Bit_15 : 1; // bit 15
+ uint32_t Bit_16 : 1; // bit 16
+ uint32_t HA : 1; // bit 17
+ uint32_t Bit_18 : 1; // bit 18
+ uint32_t WXN : 1; // bit 19
+ uint32_t UWXN : 1; // bit 20
+ uint32_t FI : 1; // bit 21
+ uint32_t U : 1; // bit 22
+ uint32_t Bit_23 : 1; // bit 23
+ uint32_t VE : 1; // bit 24
+ uint32_t EE : 1; // bit 25
+ uint32_t Bit_26 : 1; // bit 26
+ uint32_t NMFI : 1; // bit 27
+ uint32_t TRE : 1; // bit 28
+ uint32_t AFE : 1; // bit 29
+ uint32_t TE : 1; // bit 30
+ uint32_t Bit_31 : 1; // bit 31
+ } fields;
+} SCTLR_t;
+
+// DACR - Domain Access Control Register
+// DACR holds 16 pairs of bits; each pair represents access
+// permissions to a respective memory domain. There's no point
+// declaring a union for this.
+typedef uint32_t DACR_t;
+
+inline static uint8_t domain_permissions(DACR_t DACR_contents,
+ int domain)
+{
+ return (DACR_contents << (30 - 2 * domain)) >> 30;
+}
+
+inline static DACR_t set_domain_permissions(DACR_t DACR_contents,
+ int domain,
+ uint8_t permissions)
+{
+ uint32_t clear_domain_permissions_mask = ~(0b11 << (2 * domain));
+ uint32_t new_domain_permissions_mask =
+ ((uint32_t) permissions) << (2 * domain);
+
+ return (DACR_contents & clear_domain_permissions_mask)
+ | new_domain_permissions_mask;
+}
+
+#define DOMAIN_NO_ACCESS 0b00
+#define DOMAIN_CLIENT_ACCESS 0b01
+#define DOMAIN_RESERVED 0b10
+#define DOMAIN_MANAGER_ACCESS 0b11
+
+// TTBR - Translation Table Base Register (there're 2 of them with
+// (almost) the same structure)
+
+// A field in TTBCR determines how long the address field is in TTBR0,
+// but here we'll ignore this and just assume the greatest possible
+// length of this field (18 bits). In TTBR1 it's always 18 bits.
+typedef union
+{
+ uint32_t raw;
+ struct
+ {
+
+ uint32_t C : 1; // bit 0
+ uint32_t S : 1; // bit 1
+ uint32_t IMP : 1; // bit 2
+ uint32_t RGN : 2; // bits 4:3
+ uint32_t NOS : 1; // bit 5
+ uint32_t IRGN_0 : 1; // bit 6
+ uint32_t Bits_13_6 : 7; // bits 13:7
+ uint32_t Bits_31_14 : 18; // bits 31:14
+ // with multiprocessing extensions the cacheable bit becomes
+ // upper IRGN bit
+#define IRGN_1 C
+
+ // i'm not sure 'interprocess region bits' is the right name,
+ // I'm just guessing (by analogy to RGN -> region bits)
+#define CACHEABLE_BIT C
+#define INTERPROCESS_REGION_BITS_1 IRGN_1
+#define SHAREABLE_BIT S
+#define IMPLEMENTATION_DEFINED_BIT IMP
+#define REGION_BITS_1_0 RGN
+#define INTERPROCESS_REGION_BITS_0 IRGN_0
+#define NON_OUTER_SHAREABLE_BIT NOS
+#define TRANSLATION_TABLE_BASE_ADDRESS Bits_31_14
+ } fields;
+} TTBR_t;
+
diff --git a/kernel.c b/kernel.c
index dcfac11..8d2fdef 100644
--- a/kernel.c
+++ b/kernel.c
@@ -1,7 +1,8 @@
#include "uart.h"
#include "cpsr.h"
#include "strings.h"
-#include "short_descriptor.h"
+#include "translation_table_descriptors.h"
+#include "cp_regs.h"
extern char __end;
@@ -66,190 +67,127 @@ void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
uart_puts("current mode: ");
uart_puts(mode_name);
- uart_puts("setting mode to system...\r\n");
- uart_puts("current mode: ");
+ uart_puts("setting mode to system (PL1)...\r\n");
set_system_mode();
- switch(read_processor_mode())
- {
- case MODE_USER : mode_name = "User (PL0)\r\n"; break;
- case MODE_FIQ : mode_name = "FIQ (PL1)\r\n"; break;
- case MODE_IRQ : mode_name = "IRQ (PL1)\r\n"; break;
- case MODE_SUPERVISOR : mode_name = "Supervisor (PL1)\r\n"; break;
- case MODE_MONITOR : mode_name = "Monitor (PL1)\r\n"; break;
- case MODE_ABORT : mode_name = "Abort (PL1)\r\n"; break;
- case MODE_HYPERVISOR : mode_name = "Hyp (PL2)\r\n"; break;
- case MODE_UNDEFINED : mode_name = "Undefined (PL1)\r\n"; break;
- case MODE_SYSTEM : mode_name = "System (PL1)\r\n"; break;
- default : mode_name = "Unknown mode\r\n"; break;
- }
-
- uart_puts(mode_name);
-
- char bits[33];
-
- // compute translation table address for TTBR0
+ char bits[33]; // for printing uint32_t bit values
+
+ // compute translation table base address
// translation table shall start at first 2^14-bytes aligned
// address after the kernel image
- // uint32_t kernel_end = (uint32_t) &__end;
- uint32_t translation_table_base = 0x4000; // for now try 0x4000
- // ((kernel_end - 1) & ~((uint32_t) 0x3fff)) + (uint32_t) 0x4000;
+ uint32_t kernel_end = (uint32_t) &__end;
+ uint32_t translation_table_base =
+ ((kernel_end - 1) & ~((uint32_t) 0x3fff)) + (uint32_t) 0x4000;
uint32_to_bits(translation_table_base, bits);
- uart_puts("\n\rbinary representation of chosen"
+ uart_puts("binary representation of chosen"
" lvl1 translation table address: ");
- uart_puts(bits);
+ uart_puts(bits); uart_puts("\n\r");
- uart_puts("\n\rpreparing translation table\n\r");
- uint32_t *translation_table = (uint32_t*) translation_table_base;
-
// flat map all memory
- translation_table[0] = sd_lvl1_make_section(0x0);
- // make all other entries in translation table invalid :)
+ uart_puts("preparing translation table\n\r");
+ short_descriptor_t *translation_table =
+ (short_descriptor_t*) translation_table_base;
+
for (uint32_t i = 0; i < 4096; i++)
- translation_table[i] = sd_lvl1_make_section(i << 20);
+ translation_table[i].section_fields =
+ (short_section_descriptor_t) {
+ .SECTION_BASE_ADDRESS_31_20 = i,
+ .SECTION_OR_SUPERSECTION_BIT = DESCRIBES_SECTION,
+ .ACCESS_PERMISSIONS_2 = AP_2_0_MODEL_RW_PL1 >> 2,
+ .ACCESS_PERMISSIONS_1_0 = AP_2_0_MODEL_RW_PL1 & 0b011,
+ .DESCRIPTOR_TYPE_2 =
+ SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION >> 1,
+ // rest of fields are 0s
+ };
- uint32_to_bits(translation_table[0], bits);
- uart_puts("translation_table_entry 0: \n\r");
- uart_puts(bits);
-
- // uart_puts("\n\renabling the MMU\n\r");
- // asm volatile("" ::: "memory");
- // enabling_code_from_the_net();
- // goto skip;
-
-
// meddle with domain settings
- uint32_t DACR;
- asm("mrc p15, 0, %0, c3, c0, 0" : "=r" (DACR));
- uint32_to_bits(DACR, bits);
-
- uart_puts("initial DACR contents: ");
- uart_puts(bits);
-
- uart_puts("\n\rsetting domain0 to client access"
+ uart_puts("setting domain0 to client access"
" and blocking other domains\n\r");
- DACR = 1;
- asm("mcr p15, 0, %0, c3, c0, 0" :: "r" (DACR));
-
- asm("mrc p15, 0, %0, c3, c0, 0" : "=r" (DACR));
- uint32_to_bits(DACR, bits);
-
- uart_puts("new DACR contents: ");
- uart_puts(bits);
+ DACR_t DACR = 0;
+ DACR = set_domain_permissions(DACR, 0, DOMAIN_CLIENT_ACCESS);
+ for (int i = 1; i < 16; i++)
+ DACR = set_domain_permissions(DACR, i, DOMAIN_NO_ACCESS);
+ // the above should do the same as this:
+ // DACR = 1;
+
+ asm("mcr p15, 0, %0, c3, c0, 0" :: "r" (DACR));
// meddle with SCTLR, which determines how some bits in
// table descriptors work and also controls caches
// we don't want to use access flag, so we set AFE to 0
// we don't want TEX remap, so we set TRE to 0
// we also disable data and instruction caches and the MMU
- uint32_t SCTLR;
- asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR));
- uint32_to_bits(SCTLR, bits);
-
- uart_puts("\n\rSCTLR contents: ");
- uart_puts(bits);
- uart_puts("\n\rsetting C, I, AFE and TRE to 0 in SCTLR\n\r");
+ // some of this is redundant (i.e. MMU should already be disabled)
+ uart_puts("setting C, I, AFE and TRE to 0 in SCTLR\n\r");
- SCTLR &= ~((((uint32_t) 1) << 29) |
- (((uint32_t) 1) << 28) |
- (((uint32_t) 1) << 12) |
- (((uint32_t) 1) << 2)); // set AFE and TRE to 0
- asm("mcr p15, 0, %0, c1, c0, 0\n\r"
- "isb" :: "r" (SCTLR));
+ SCTLR_t SCTLR;
+ asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw));
- asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR));
- uint32_to_bits(SCTLR, bits);
-
- uart_puts("new SCTLR contents: ");
- uart_puts(bits);
+ SCTLR.fields.M = 0; // disable MMU
+ SCTLR.fields.C = 0; // disable data cache
+ SCTLR.fields.I = 0; // disable instruction cache
+ SCTLR.fields.TRE = 0; // disable TEX remap
+ SCTLR.fields.AFE = 0; // disable access flag usage
+ asm("mcr p15, 0, %0, c1, c0, 0\n\r"
+ "isb" :: "r" (SCTLR.raw) : "memory");
+ // TODO: move invalidation instructions to some header as inlines
+
+ uart_puts("invalidating instruction cache, branch prediction,"
+ " and entire main TLB\n\r");
+
// invalidate instruction cache
- uart_puts("\n\rinvalidating instruction cache\n\r");
asm("mcr p15, 0, r0, c7, c5, 0\n\r" // r0 gets ignored
"isb" ::: "memory");
// invalidate branch-prediction
- uart_puts("\n\rinvalidating branch-prediction\n\r");
- asm("mcr p15, 0, r0, c7, c5, 6\n\r"
+ asm("mcr p15, 0, r0, c7, c5, 6\n\r" // r0 - same as above
"isb" ::: "memory");
- // invalidate instruction cache
- uart_puts("\n\rinvalidating entire main TLB\n\r");
+ // invalidate main Translation Lookup Buffer
asm("mcr p15, 0, %0, c8, c7, 0\n\r"
"isb" :: "r" (0) : "memory");
- // now see what's in TTBCR
- // set it use TTBR0 exclusively
- uint32_t TTBCR;
- asm("mrc p15, 0, %0, c2, c0, 2" : "=r" (TTBCR));
- uint32_to_bits(TTBCR, bits);
-
- uart_puts("\n\rTTBCR contents: ");
- uart_puts(bits);
-
- uart_puts("\n\rSetting TTBCR.N to 0, so that"
+ // now set TTBCR to use TTBR0 exclusively
+ uart_puts("Setting TTBCR.N to 0, so that"
" TTBR0 is used everywhere\n\r");
-
- TTBCR &= ~((uint32_t) 0x7); // set N to 0
+
+ uint32_t TTBCR = 0;
asm("mcr p15, 0, %0, c2, c0, 2" :: "r" (TTBCR));
-
- asm("mrc p15, 0, %0, c2, c0, 2" : "=r" (TTBCR));
- uint32_to_bits(TTBCR, bits);
-
- uart_puts("new TTBCR contents: ");
- uart_puts(bits);
-
// Now do stuff with TTBR0
- uint32_t TTBR0;
- asm("mrc p15, 0, %0, c2, c0, 0" : "=r" (TTBR0));
- uint32_to_bits(TTBR0, bits);
-
- uart_puts("\n\rTTBR0 contents: ");
- uart_puts(bits);
-
- uart_puts("\n\r");
-
- TTBR0 = ((TTBR0 << 18) >> 18) | translation_table_base;
- TTBR0 &= ~((uint32_t) 0x1a); // set RGN and S in TTBR0 to 0
- asm("mcr p15, 0, %0, c2, c0, 0" :: "r" (TTBR0));
-
- asm("mrc p15, 0, %0, c2, c0, 0" : "=r" (TTBR0));
- uint32_to_bits(TTBR0, bits);
-
- uart_puts("new TTBR0 contents: ");
- uart_puts(bits);
+ TTBR_t TTBR0;
+ TTBR0.raw = 0;
+ TTBR0.fields.TRANSLATION_TABLE_BASE_ADDRESS =
+ translation_table_base >> 14;
+ // rest of TTBR0 remains 0s
+ asm("mcr p15, 0, %0, c2, c0, 0" :: "r" (TTBR0.raw));
// enable MMU
- asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR));
- uint32_to_bits(SCTLR, bits);
-
- uart_puts("\n\rSCTLR contents before MMU enabling: ");
- uart_puts(bits);
+ uart_puts("enabling the MMU\n\r");
- uart_puts("\n\renabling the MMU\n\r");
-
- // set M to 0
- SCTLR |= (uint32_t) 1;
-
- asm("mcr p15, 0, %0, c1, c0, 0" :: "r" (SCTLR));
+ // redundant - we already have SCTLR contents in the variable
+ // asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw));
- asm("mrc p15, 0, %0, c1, c0, 0\r\n"
- "isb" : "=r" (SCTLR));
+ SCTLR.fields.M = 1;
- uint32_to_bits(SCTLR, bits);
-
- uart_puts("SCTLR contents after MMU enabling: ");
- uart_puts(bits);
+ asm("mcr p15, 0, %0, c1, c0, 0\n\r"
+ "isb" :: "r" (SCTLR.raw) : "memory");
- skip:
- uart_puts("skip here\n\r");
-
while (1)
- uart_putc(uart_getc());
+ {
+ char c;
+ switch(c = uart_getc())
+ {
+ case '\r':
+ uart_putc('\n');
+ default:
+ uart_putc(c);
+ }
+ }
}
diff --git a/translation_table_descriptors.h b/translation_table_descriptors.h
new file mode 100644
index 0000000..35062f8
--- /dev/null
+++ b/translation_table_descriptors.h
@@ -0,0 +1,99 @@
+#include <stdint.h>
+
+// ARM lets you choose between 32-bit abd 64-bit translation table
+// descriptors (called short and long descriptors respectively).
+// The format of the descriptor differs depending on what it describes
+// (section, supersection, a page table, etc...) and table of which
+// level of lookup it belongs to.
+
+// Even in case of descriptor of a specified type (e.g. short-format
+// section descriptor), a given field inside it may have different
+// meanings depending on settings in coprocessor registers... (yeah, ARM
+// looks a bit messy... all for backward compatibility, i guess)
+
+typedef struct
+{
+ uint32_t PXN : 1; // bit 0
+ uint32_t Bit_1 : 1; // bit 1
+ uint32_t B : 1; // bit 2
+ uint32_t C : 1; // bit 3
+ uint32_t XN : 1; // bit 4
+ uint32_t Domain_3_0 : 4; // bits 8:5
+ uint32_t Bit_9 : 1; // bit 9
+ uint32_t AP_1_0 : 2; // bit 11:10
+ uint32_t TEX_2_0 : 3; // bits 14:12
+ uint32_t AP_2 : 1; // bit 15
+ uint32_t S : 1; // bit 16
+ uint32_t nG : 1; // bit 17
+ uint32_t Bit_18 : 1; // bit 18
+ uint32_t NS : 1; // bit 19
+ uint32_t PA_31_20 : 12; // bits 31:20
+#define PRIVILEGED_EXECUTE_NEVER_BIT PXN
+#define DESCRIPTOR_TYPE_2 Bit_1
+#define BUFFERABLE_BIT B
+#define CACHEABLE_BIT C
+#define EXECUTE_NEVER_BIT XN
+#define DOMAIN_3_0 Domain_3_0
+#define IMPLEMENTATION_DEFINED_BIT Bit_9
+#define ACCESS_PERMISSIONS_1_0 AP_1_0
+#define TYPE_EXTENSION_2_0 TEX_2_0
+#define ACCESS_PERMISSIONS_2 AP_2
+#define SHAREABLE_BIT S
+#define NON_GLOBAL_BIT nG
+#define SECTION_OR_SUPERSECTION_BIT Bit_18
+#define NON_SECURE_BIT NS
+#define SECTION_BASE_ADDRESS_31_20 PA_31_20
+} short_section_descriptor_t;
+
+
+// How AP[2:0] is used depends on settings in SCTLR.AFE
+
+// Meaning of #define'd names below:
+// RW - read-write
+// RO - read-only
+// PL1 - a given permission applies to privilege level PL1
+// PL2 - a given permission applies to privilege level PL2
+// ALL - a given permission applies to both privilege levels
+// If only a permission for one privilege level is given in the name,
+// it means the other one has no access.
+
+// When SCTLR.AFE is 0 (access flag not used) and short-format
+// descritor table is used, the following access permission control
+// schema for AP[2:0] is used:
+#define AP_2_0_MODEL_NO_ACCESS 0b000
+#define AP_2_0_MODEL_RW_PL1 0b001
+#define AP_2_0_MODEL_RW_PL1_RO_PL0 0b010
+#define AP_2_0_MODEL_RW_ALL 0b011
+#define AP_2_0_MODEL_RESERVED 0b100
+#define AP_2_0_MODEL_RO_PL1 0b101
+#define AP_2_0_MODEL_RO_ALL_DEPRECATED 0b110 // use 0b111 instead
+#define AP_2_0_MODEL_RO_ALL 0b111 // reserved in VMSAv6
+// TODO: the #define's of RO_ALL and reserved could be done
+// conditionally depending on the VMSA version available (either give
+// the programmer #including this the possibility to #define their
+// VMSA version or assume the VMSA version respective to the ARM
+// version we're compiling against)
+
+// Values for bit18, that determines whether a descriptor describes
+// section or supersection:
+#define DESCRIBES_SECTION 0b0
+#define DESCRIBES_SUPERSECTION 0b1
+
+typedef union
+{
+ uint32_t raw;
+ uint8_t descriptor_type;
+
+ short_section_descriptor_t section_fields;
+ // more to come here (e.g. short_supersection_descriptor_t)
+} short_descriptor_t;
+
+// possible values of descriptor_type field:
+#define SHORT_DESCRIPTOR_INVALID 0b00
+#define SHORT_DESCRIPTOR_PAGE_TABLE 0b01
+#define SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION 0b10
+#define SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION_PXN 0b11
+// on an implementation that does not support the PXN attribute
+// 0b11 should not be used
+#define SHORT_DESCRIPTOR_RESERVED 0b11
+