diff options
-rw-r--r-- | Makefile | 36 | ||||
-rw-r--r-- | PL0_test.c | 34 | ||||
-rw-r--r-- | PL0_test.ld | 44 | ||||
-rw-r--r-- | cp_regs.h | 21 | ||||
-rw-r--r-- | cpsr.h | 39 | ||||
-rw-r--r-- | demo_functionality.c | 115 | ||||
-rw-r--r-- | demo_functionality.h | 10 | ||||
-rw-r--r-- | global.h | 5 | ||||
-rw-r--r-- | kernel.c | 172 | ||||
-rw-r--r-- | memory.h | 35 | ||||
-rw-r--r-- | paging.c | 114 | ||||
-rw-r--r-- | paging.h | 6 | ||||
-rw-r--r-- | pipe_image.c | 2 | ||||
-rw-r--r-- | psr.h | 122 | ||||
-rw-r--r-- | strings.h | 5 | ||||
-rw-r--r-- | translation_table_descriptors.h | 129 | ||||
-rw-r--r-- | uart.h | 5 |
17 files changed, 621 insertions, 273 deletions
@@ -1,29 +1,27 @@ CFLAGS=-mcpu=cortex-a7 -ffreestanding -std=gnu11 -Wall -Wextra -I. ELFFLAGS=-ffreestanding -O2 -nostdlib -lgcc -I. +ARM_OBJECTS=kernel.o paging.o demo_functionality.o PL0_test.o uart.o loader_stage1.o loader_stage2.o all : kernel7.img -kernel.o : kernel.c +%.o : %.c arm-none-eabi-gcc $(CFLAGS) -c $^ -o $@ -uart.o : uart.c - arm-none-eabi-gcc $(CFLAGS) -c $^ -o $@ - -interrupts.o : interrupts.c uart.o - arm-none-eabi-gcc $(CFLAGS) -c $^ -o $@ - -boot.o : boot.S +%.o : %.S arm-none-eabi-as -mcpu=cortex-a7 $^ -o $@ -kernel.elf : boot.o kernel.o uart.o - arm-none-eabi-gcc -T linker.ld -o $@ $(ELFFLAGS) $^ - -kernel7.img : kernel.elf +%.img : %.elf arm-none-eabi-objcopy $^ -O binary $@ -loader_stage2.o : loader_stage2.c - arm-none-eabi-gcc $(CFLAGS) -c $^ -o $@ +%_embeddable.o : %.img + arm-none-eabi-objcopy -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata $^ $@ + +PL_0_test.elf : PL0_test.o uart.o + arm-none-eabi-gcc -T PL0_test.ld -o $@ $(ELFFLAGS) $^ + +kernel.elf : boot.o kernel.o uart.o PL_0_test_embeddable.o demo_functionality.o paging.o + arm-none-eabi-gcc -T linker.ld -o $@ $(ELFFLAGS) $^ loader_stage2.elf : loader_stage2.o uart.o arm-none-eabi-gcc -T loader_stage2.ld -o $@ $(ELFFLAGS) $^ @@ -32,22 +30,14 @@ loader_stage2.img : loader_stage2.elf arm-none-eabi-objcopy $^ -O binary $@ test -n "$$(find $@ -size -16384c)" || exit -1 -loader_stage2_embeddable.o : loader_stage2.img - arm-none-eabi-objcopy -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata $^ $@ - -loader_stage1.o : loader_stage1.c - arm-none-eabi-gcc $(CFLAGS) -c $^ -o $@ - loader.elf : boot.o loader_stage1.o loader_stage2_embeddable.o arm-none-eabi-gcc -T loader_stage1.ld -o $@ $(ELFFLAGS) $^ -loader.img : loader.elf - arm-none-eabi-objcopy $^ -O binary $@ qemu-elf : kernel.elf qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel $^ -qemu-bin : loader.elf kernel7.img pipe_image +qemu-bin : loader.elf kernel.img pipe_image ./pipe_image --stdout | qemu-system-arm -m 256 -M raspi2 -serial stdio -kernel $< run-on-rpi : kernel7.img pipe_image diff --git a/PL0_test.c b/PL0_test.c new file mode 100644 index 0000000..7609294 --- /dev/null +++ b/PL0_test.c @@ -0,0 +1,34 @@ +#include "uart.h" +#include "psr.h" + +void PL0_main(void) +{ + uart_puts("hello PL0! Switching to user mode!\n\r"); + + asm("cps #0b10000\n\r" + "isb" ::: "memory"); + + // if all went correct, Success! gets printed + uart_puts("Success!\n\r"); + + // if we're indeed i PL0, we should crash now, when trying to access + // memory we're not allowed to + char first_kernel_byte[2]; + + first_kernel_byte[0] = *(char*) ((uint32_t) 0x8000); + first_kernel_byte[1] = '\0'; + + uart_puts(first_kernel_byte); + + while (1) + { + char c; + switch(c = uart_getc()) + { + case '\r': + uart_putc('\n'); + default: + uart_putc(c); + } + } +} diff --git a/PL0_test.ld b/PL0_test.ld new file mode 100644 index 0000000..430e098 --- /dev/null +++ b/PL0_test.ld @@ -0,0 +1,44 @@ +ENTRY(_start) + +SECTIONS +{ + /* 0b10101010101000000000000000000000 */ + . = 0xaaa00000; + __start = .; + __text_start = .; + .text : + { + /* have entry point at the beginning */ + KEEP(*(.text.PL0main)) + *(.text) + } + . = ALIGN(4096); /* align to page size */ + __text_end = .; + + __rodata_start = .; + .rodata : + { + *(.rodata) + } + . = ALIGN(4096); /* align to page size */ + __rodata_end = .; + + __data_start = .; + .data : + { + *(.data) + } + . = ALIGN(4096); /* align to page size */ + __data_end = .; + + __bss_start = .; + .bss : + { + bss = .; + *(.bss) + } + . = ALIGN(4096); /* align to page size */ + __bss_end = .; + __bss_size = __bss_end - __bss_start; + __end = .; +} @@ -1,5 +1,7 @@ -#include <stdint.h> +#ifndef CP_REGS_H +#define CP_REGS_H +#include <stdint.h> // SCTLR - System Control Register @@ -98,14 +100,15 @@ typedef union // 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 +#define TTBR_CACHEABLE_BIT C +#define TTBR_INTERPROCESS_REGION_BITS_1 IRGN_1 +#define TTBR_SHAREABLE_BIT S +#define TTBR_IMPLEMENTATION_DEFINED_BIT IMP +#define TTBR_REGION_BITS_1_0 RGN +#define TTBR_INTERPROCESS_REGION_BITS_0 IRGN_0 +#define TTBR_NON_OUTER_SHAREABLE_BIT NOS +#define TTBR_TRANSLATION_TABLE_BASE_ADDRESS Bits_31_14 } fields; } TTBR_t; +#endif // CP_REGS_H @@ -1,39 +0,0 @@ -#include <stdint.h> - -enum execution_mode { - MODE_USER = 0x10, - MODE_FIQ = 0x11, - MODE_IRQ = 0x12, - MODE_SUPERVISOR = 0x13, - MODE_MONITOR = 0x16, - MODE_ABORT = 0x17, - MODE_HYPERVISOR = 0x1a, - MODE_UNDEFINED = 0x1b, - MODE_SYSTEM = 0x1f, -}; - -inline static uint32_t read_CPSR(void) -{ - uint32_t CPSR; - // get content of current program status register - asm("mrs %0, cpsr" : "=r" (CPSR) ::); - - return CPSR; -} - -inline static enum execution_mode read_processor_mode(void) -{ - /* lowest 5 bits indicate processor mode */ - return read_CPSR() & 0x1f; -} - -inline static void set_system_mode(void) -{ - uint32_t CPSR = read_CPSR(); - - CPSR = (CPSR >> 5) << 5; - - CPSR |= MODE_SYSTEM; - - asm("msr cpsr, %0" :: "r" (CPSR) : "memory"); -} diff --git a/demo_functionality.c b/demo_functionality.c new file mode 100644 index 0000000..7bba6a6 --- /dev/null +++ b/demo_functionality.c @@ -0,0 +1,115 @@ +#include "uart.h" +#include "psr.h" +#include "memory.h" +#include "translation_table_descriptors.h" + +void demo_paging_support(void) +{ + uint32_t ID_MMFR0; + // get contents of coprocessor register to check for paging support + asm("mrc p15, 0, %0, c0, c1, 4" : "=r" (ID_MMFR0)); + + char *paging; + + switch(ID_MMFR0 & 0xf) /* lowest 4 bits indicate VMSA support */ + { + case 0 : paging = "no paging\n\r"; break; + case 1 : paging = "implementation defined paging\n\r"; break; + case 2 : paging = "VMSAv6, with cache and TLB type registers\n\r"; break; + case 3 : paging = "VMSAv7, with support for remapping and access flag\n\r"; break; + case 4 : paging = "VMSAv7 with PXN bit supported\n\r"; break; + case 5 : paging = "VMSAv7, PXN and long format descriptors. EPAE is supported.\n\r"; break; + default : paging = "?_? unknown paging ?_?\n\r"; + } + + uart_puts(paging); +} + +void demo_mode_to_system(void) +{ + + // get content of current program status register to check the current + // processor mode + PSR_t CPSR = read_CPSR(); + + char *mode_name; + + switch(CPSR.fields.PSR_MODE_4_0) + { + 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("current mode: "); + uart_puts(mode_name); + + uart_puts("setting mode to system (PL1)...\r\n"); + set_system_mode(); +} + +extern char + _binary_PL_0_test_img_start, + _binary_PL_0_test_img_end, + _binary_PL_0_test_img_size; + +void demo_go_unprivileged(void) +{ + short_section_descriptor_t *translation_table = + (short_section_descriptor_t*) TRANSLATION_TABLE_BASE; + + volatile short_section_descriptor_t *PL0_section = + &translation_table[PL0_SECTION_NUMBER]; + volatile short_section_descriptor_t *UART_memory_section = + &translation_table[((uint32_t) GPIO_BASE) >> 20]; + + PL0_section->SECTION_BASE_ADDRESS_31_20 = + UNPRIVILEGED_MEMORY_START >> 20; + + // make the selected section and uart section available for PL0 + PL0_section->ACCESS_PERMISSIONS_2 = + AP_2_0_MODEL_RW_ALL >> 2; + PL0_section->ACCESS_PERMISSIONS_1_0 = + AP_2_0_MODEL_RW_ALL & 0b011; + + UART_memory_section->ACCESS_PERMISSIONS_2 = + AP_2_0_MODEL_RW_ALL >> 2; + UART_memory_section->ACCESS_PERMISSIONS_1_0 = + AP_2_0_MODEL_RW_ALL & 0b011; + + + // invalidate main Translation Lookup Buffer (just in case) + asm("mcr p15, 0, %0, c8, c7, 0\n\r" + "isb" :: "r" (0) : "memory"); + + // check that translation works... by copying a string using one + // mapping and reading it using other :D + char message[] = "mapped sections for PL0 code\n\r"; + + unsigned int i; + for (i = 0; i < sizeof(message); i++) + ((volatile char*) UNPRIVILEGED_MEMORY_START)[i] = message[i]; + + uart_puts((char*) VIRTUAL_PL0_MEMORY_START); + + // now paste a userspace program to that section, jump to it and + // switch to PL0 + for (size_t i = 0; i < (size_t) &_binary_PL_0_test_img_size; i++) + ((volatile char*) VIRTUAL_PL0_MEMORY_START)[i] = + (&_binary_PL_0_test_img_start)[i]; + + // jump to that copied code (switch to PL0 is done by that code) + asm volatile("mov r5, #0\n\r" + "movt r5, #"PL0_SECTION_NUMBER_STR"1111\n\r" + "mov sp, r5\n\r" // setting stack is important :D + "mov r5, #0\n\r" + "movt r5, #"PL0_SECTION_NUMBER_STR"0000\n\r" + "blx r5\n\r"); +} diff --git a/demo_functionality.h b/demo_functionality.h new file mode 100644 index 0000000..c8b163f --- /dev/null +++ b/demo_functionality.h @@ -0,0 +1,10 @@ +#ifndef DEMO_FUNCTIONALITY_H +#define DEMO_FUNCTIONALITY_H + +void demo_paging_support(void); + +void demo_mode_to_system(void); + +void demo_go_unprivileged(void); + +#endif // DEMO_FUNCTIONALITY_H @@ -1,3 +1,6 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + // board type, raspi2 #define RASPI 2 @@ -10,3 +13,5 @@ #define GPIO_BASE 0x20200000 #endif // RASPI == 3 || RASPI == 2 #endif // RASPI == 4 + +#endif // GLOBAL_H @@ -1,13 +1,7 @@ #include "uart.h" -#include "cpsr.h" -#include "strings.h" -#include "translation_table_descriptors.h" -#include "cp_regs.h" +#include "demo_functionality.h" +#include "paging.h" -extern char __end; - -void enabling_code_from_the_net(void); - void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags) { // Declare as unused @@ -20,163 +14,25 @@ void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags) // When we attach screen session after loading kernel with socat // we miss kernel's greeting... So we'll make the kernel wait for // one char we're going to send from within screen -// uart_getc(); - uint32_t ID_MMFR0; - // get contents of coprocessor register to check for paging support - asm("mrc p15, 0, %0, c0, c1, 4" : "=r" (ID_MMFR0)); - -// uart_puts("Hello, kernel World!\r\n"); - - char *paging; - switch(ID_MMFR0 & 0xf) /* lowest 4 bits indicate VMSA support */ - { - case 0 : paging = "no paging\n\r"; break; - case 1 : paging = "implementation defined paging\n\r"; break; - case 2 : paging = "VMSAv6, with cache and TLB type registers\n\r"; break; - case 3 : paging = "VMSAv7, with support for remapping and access flag\n\r"; break; - case 4 : paging = "VMSAv7 with PXN bit supported\n\r"; break; - case 5 : paging = "VMSAv7, PXN and long format descriptors. EPAE is supported.\n\r"; break; - default : paging = "?_? unknown paging ?_?\n\r"; - } - - uart_puts(paging); - - uint32_t CPSR; - // get content of current program status register to check the current - // processor mode - asm("mrs %0, cpsr" : "=r" (CPSR) :: "memory"); - - char *mode_name; - - 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("current mode: "); - uart_puts(mode_name); - - uart_puts("setting mode to system (PL1)...\r\n"); - set_system_mode(); - - char bits[33]; // for printing uint32_t bit values + // uart_getc(); - // 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 = - ((kernel_end - 1) & ~((uint32_t) 0x3fff)) + (uint32_t) 0x4000; + uart_puts("Hello, kernel World!\r\n"); - uint32_to_bits(translation_table_base, bits); - uart_puts("binary representation of chosen" - " lvl1 translation table address: "); - uart_puts(bits); uart_puts("\n\r"); - - // flat map all memory - 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].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 - }; + // prints some info + demo_paging_support(); - // meddle with domain settings - uart_puts("setting domain0 to client access" - " and blocking other domains\n\r"); + // prints some info and switches to system mode + demo_mode_to_system(); - 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); + // prints some info and sets upp translation table, turns on MMU + setup_flat_map(); - // the above should do the same as this: - // DACR = 1; + // prints some info and sets up a section for PL0 code, loads a blob + // there and jumps to it... never, ever, ever returns + demo_go_unprivileged(); - 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 + while(1); - // 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_t SCTLR; - asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw)); - - 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 - asm("mcr p15, 0, r0, c7, c5, 0\n\r" // r0 gets ignored - "isb" ::: "memory"); - - // invalidate branch-prediction - asm("mcr p15, 0, r0, c7, c5, 6\n\r" // r0 - same as above - "isb" ::: "memory"); - - // invalidate main Translation Lookup Buffer - asm("mcr p15, 0, %0, c8, c7, 0\n\r" - "isb" :: "r" (0) : "memory"); - - // now set TTBCR to use TTBR0 exclusively - uart_puts("Setting TTBCR.N to 0, so that" - " TTBR0 is used everywhere\n\r"); - - uint32_t TTBCR = 0; - asm("mcr p15, 0, %0, c2, c0, 2" :: "r" (TTBCR)); - - // Now do stuff with TTBR0 - 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 - uart_puts("enabling the MMU\n\r"); - - // redundant - we already have SCTLR contents in the variable - // asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw)); - - SCTLR.fields.M = 1; - - asm("mcr p15, 0, %0, c1, c0, 0\n\r" - "isb" :: "r" (SCTLR.raw) : "memory"); - while (1) { char c; diff --git a/memory.h b/memory.h new file mode 100644 index 0000000..1472a8b --- /dev/null +++ b/memory.h @@ -0,0 +1,35 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#define INTERRUPT_VECTOR_TABLE_START ((uint32_t) 0x0) + +#define STACK_START ((uint32_t) 0x4000) +#define STACK_END ((uint32_t) 0x8000) + +extern char __end; +extern char __start; + +#define KERNEL_START ((uint32_t) &__start) +#define KERNEL_END ((uint32_t) &__end) + +// first 2^14 aligned address after the kernel +#define TRANSLATION_TABLE_BASE \ + (((KERNEL_END - (uint32_t) 1) & ~((uint32_t) 0x3fff)) \ + + (uint32_t) 0x4000) + +#define TRANSLATION_TABLE_END \ + (TRANSLATION_TABLE_BASE + (uint32_t) (4096 * 4)) + +#define PRIVILEGED_MEMORY_END TRANSLATION_TABLE_END + +#define UNPRIVILEGED_MEMORY_START \ + (((PRIVILEGED_MEMORY_END - (uint32_t) 1) & ~((uint32_t) 0xfffff)) \ + + (uint32_t) 0x100000) + +#define PL0_SECTION_NUMBER ((uint32_t) 0b101010101010) +#define PL0_SECTION_NUMBER_STR "0b101010101010" + +#define VIRTUAL_PL0_MEMORY_START (PL0_SECTION_NUMBER << 20) + +#endif // MEMORY_H + diff --git a/paging.c b/paging.c new file mode 100644 index 0000000..7c2a9de --- /dev/null +++ b/paging.c @@ -0,0 +1,114 @@ +#include "cp_regs.h" +#include "uart.h" +#include "strings.h" +#include "memory.h" +#include "translation_table_descriptors.h" + +void setup_flat_map(void) +{ + 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_to_bits(TRANSLATION_TABLE_BASE, bits); + uart_puts("binary representation of chosen" + " lvl1 translation table address: "); + uart_puts(bits); uart_puts("\n\r"); + + // flat map all memory + uart_puts("preparing translation table\n\r"); + short_descriptor_lvl1_t volatile *translation_table = + (short_descriptor_lvl1_t*) TRANSLATION_TABLE_BASE; + + for (uint32_t i = 0; i < 4096; i++) + 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_1 = + SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION >> 1, + // rest of fields are 0s + }; + + // meddle with domain settings + uart_puts("setting domain0 to client access" + " and blocking other domains\n\r"); + + 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 + + // 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_t SCTLR; + asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw)); + + 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 + asm("mcr p15, 0, r0, c7, c5, 0\n\r" // r0 gets ignored + "isb" ::: "memory"); + + // invalidate branch-prediction + asm("mcr p15, 0, r0, c7, c5, 6\n\r" // r0 - same as above + "isb" ::: "memory"); + + // invalidate main Translation Lookup Buffer + asm("mcr p15, 0, %0, c8, c7, 0\n\r" + "isb" :: "r" (0) : "memory"); + + // now set TTBCR to use TTBR0 exclusively + uart_puts("Setting TTBCR.N to 0, so that" + " TTBR0 is used everywhere\n\r"); + + uint32_t TTBCR = 0; + asm("mcr p15, 0, %0, c2, c0, 2" :: "r" (TTBCR)); + + // Now do stuff with TTBR0 + TTBR_t TTBR0; + TTBR0.raw = 0; + TTBR0.fields.TTBR_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 + uart_puts("enabling the MMU\n\r"); + + // redundant - we already have SCTLR contents in the variable + // asm("mrc p15, 0, %0, c1, c0, 0" : "=r" (SCTLR.raw)); + + SCTLR.fields.M = 1; + + asm("mcr p15, 0, %0, c1, c0, 0\n\r" + "isb" :: "r" (SCTLR.raw) : "memory"); +} diff --git a/paging.h b/paging.h new file mode 100644 index 0000000..1218fb8 --- /dev/null +++ b/paging.h @@ -0,0 +1,6 @@ +#ifndef PAGING_H +#define PAGING_H + +void setup_flat_map(void); + +#endif // PAGING_H diff --git a/pipe_image.c b/pipe_image.c index 1266e90..7e27fb9 100644 --- a/pipe_image.c +++ b/pipe_image.c @@ -13,7 +13,7 @@ /* It is intended to be used with our bootloader. */ int main(int argc, const char **argv) { - const char *image_file_name = "kernel7.img"; + const char *image_file_name = "kernel.img"; _Bool stdout_instead_of_uart = 0; if (argc > 1) @@ -0,0 +1,122 @@ +#ifndef PSR_H +#define PSR_H + +#include <stdint.h> + +enum execution_mode { + MODE_USER = 0b10000, + MODE_FIQ = 0b10001, + MODE_IRQ = 0b10010, + MODE_SUPERVISOR = 0b10011, + MODE_MONITOR = 0b10110, + MODE_ABORT = 0b10111, + MODE_HYPERVISOR = 0b11010, + MODE_UNDEFINED = 0b11011, + MODE_SYSTEM = 0b11111, +}; + +typedef union +{ + uint32_t raw; + struct + { + uint32_t M_4_0 : 5; // bits 4:0 + uint32_t T : 1; // bit 5 + uint32_t F : 1; // bit 6 + uint32_t I : 1; // bit 7 + uint32_t A : 1; // bit 8 + uint32_t E : 1; // bit 9 + uint32_t IT_7_2 : 6; // bits 15:10 + uint32_t GE_3_0 : 4; // bits 19:16 + uint32_t Bits_23_20 : 4; // bits 23:20 + uint32_t J : 1; // bit 24 + uint32_t IT_1_0 : 2; // bits 26:25 + uint32_t Q : 1; // bit 27 + uint32_t V : 1; // bit 28 + uint32_t C : 1; // bit 29 + uint32_t Z : 1; // bit 30 + uint32_t N : 1; // bit 31 +#define PSR_MODE_4_0 M_4_0 +#define PSR_THUMB_BIT T +#define PSR_FIQ_MASKK_BIT F +#define PSR_IRQ_MASK_BIT I +#define PSR_ASYNC_ABORT_MASK_BIT A +#define PSR_ENDIANNESS_BIT E +#define PSR_IF_THEN_STATE_7_2 IT_7_2 +#define PSR_GREATER_THAN_OR_EQUAL_FLAGS GE_3_0 + // bits 23:20 are reserved +#define PSR_JAZELLE_BIT J +#define PSR_IF_THEN_STATE_1_0 IT_1_0 +#define PSR_CUMULATIVE_SATURATION_BIT Q +#define PSR_OVERFLOW_CONDITION_BIT V +#define PSR_CARRY_CONDITION_BIT C +#define PSR_ZERO_CONDITION_BIT Z +#define PSR_NEGATIVE_CONDITION_BIT N + } fields; +} PSR_t; + +inline static PSR_t read_CPSR(void) +{ + PSR_t CPSR; + // get content of current program status register + asm("mrs %0, cpsr" : "=r" (CPSR.raw) :: "memory"); + + return CPSR; +} + +//// Write function not working for some reason... Assembly from gcc +//// looks ok. Needs checking on real hw. + +//inline static void write_CPSR(PSR_t CPSR) +//{ + // write to current program status register and synchronize context + // asm("msr cpsr, %0\n\r" + // "isb" :: "r" (CPSR.raw) : "memory"); +//} + +inline static void set_system_mode(void) +{ + // hack to fix an unexplained bug; volatile needed in case of + // compilation with optimizations + volatile PSR_t CPSR __attribute__((unused)) = read_CPSR(); + + //// there are 2 ways of changing mode, both with the same + //// problem (see the long comment below) + + //// way 1 + // CPSR.fields.M_4_0 = MODE_SYSTEM; + // write to current program status register and synchronize context + // asm("msr cpsr, %0\n\r" + // "isb":: "r" (CPSR.raw) : "memory"); + + //// way 2 + asm("cps #0b11111\n\r" + "isb" ::: "memory"); +} + +// The thing with writing to cpsr is weird. I used to have a single +// function that would set the system mode by: +// 1. reading the cpsr +// 2. modifying the value using bit shifts and logical or +// 3. writing the value back +// When introducing structs and bitfields I wanted to have separate +// functions for reading and writing the cpsr and have code up the +// call stack handle value modification. For some reason this didn't +// work - all would just hang when writing the CPSR. It turned out +// everything works if i call read_CPSR() from the same function, in +// which i write to cpsr. And I don't even need to use the value +// I read. I can just discard it (we're compiling without +// optimizations, so read_CPSR() is still called) and use the value +// passed as argument. I noticed an even weirder thing: the 2nd way +// of changing mode (cps instruction) doesn't work normally, but, +// just as msr, it does work when I call read_CPSR() before it. Even +// weirder - I cannot replace read_CPSR() with the actual assembly it +// does, because it stops working again! Asm generated by gcc looks +// ok, so Idk, maybe its some qemu issue (haven't tested on real RPi +// yet). I seem to have experienced uart printing inserted here and +// there do the same magic read_CPSR() does (but, again, not always). +// Unfortunately, nop repeated several hundreds times (or a recursive +// nop function, that just calls itself a given number of times) +// doesn't have this magical property :( + +#endif // PSR_H @@ -1,3 +1,6 @@ +#ifndef STRINGS_H +#define STRINGS_H + #include <stdint.h> void uint32_to_bits(uint32_t number, char *buf) @@ -7,3 +10,5 @@ void uint32_to_bits(uint32_t number, char *buf) buf[32] = '\0'; } + +#endif // STRINGS_H diff --git a/translation_table_descriptors.h b/translation_table_descriptors.h index 8f92473..981c3c7 100644 --- a/translation_table_descriptors.h +++ b/translation_table_descriptors.h @@ -1,3 +1,6 @@ +#ifndef TRANSLATION_TABLE_DESCRIPTORS_H +#define TRANSLATION_TABLE_DESCRIPTORS_H + #include <stdint.h> // ARM lets you choose between 32-bit abd 64-bit translation table @@ -11,6 +14,33 @@ // meanings depending on settings in coprocessor registers... (yeah, ARM // looks a bit messy... all for backward compatibility, i guess) + +////// Here are the definitions for short-format descriptors + +//// short-format page table descriptor + +typedef struct +{ + uint32_t Bits_1_0 : 1; // bits 1:0 + uint32_t PXN : 1; // bit 2 + uint32_t NS : 1; // bit 3 + uint32_t SBZ : 1; // bit 4 + uint32_t Domain_3_0 : 4; // bits 8:5 + uint32_t Bit_9 : 1; // bit 9 + uint32_t Bits_31_10 : 22; // bits 31:10 +#define DESCRIPTOR_TYPE_1_0 Bits_1_0 +#define PRIVILEGED_EXECUTE_NEVER_BIT PXN +#define NON_SECURE_BIT NS + // me thinks SBZ means "should be zero", + // but me sees no point #defining it +#define DOMAIN_3_0 Domain_3_0 +#define IMPLEMENTATION_DEFINED_BIT Bit_9 +#define PAGE_TABLE_BASE_ADDRESS_31_10 Bits_31_10 +} short_page_table_descriptor_t; + + +//// short-format section descriptor + typedef struct { uint32_t PXN : 1; // bit 0 @@ -28,56 +58,26 @@ typedef struct 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 + // some of these are already defined the same for page table + //#define PRIVILEGED_EXECUTE_NEVER_BIT PXN +#define DESCRIPTOR_TYPE_1 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 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 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 +//// short-format supersection descriptor typedef struct { @@ -99,7 +99,7 @@ typedef struct uint32_t PA_31_24 : 8; // bits 31:24 // most of these are already defined the same for section //#define PRIVILEGED_EXECUTE_NEVER_BIT PXN - //#define DESCRIPTOR_TYPE_2 Bit_1 + //#define DESCRIPTOR_TYPE_1 Bit_1 //#define BUFFERABLE_BIT B //#define CACHEABLE_BIT C //#define EXECUTE_NEVER_BIT XN @@ -116,17 +116,59 @@ typedef struct #define SUPERSECTION_BASE_ADDRESS_31_24 PA_31_24 } short_supersection_descriptor_t; + +//// possible access permission field values + +// 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 + + +//// short-format descriptor generic type + typedef union { uint32_t raw; - uint8_t descriptor_type; - + uint8_t descriptor_type : 2; + + short_page_table_descriptor_t page_table_fields; short_section_descriptor_t section_fields; short_supersection_descriptor_t supersection_fields; - // more to come here (e.g. short_supersection_descriptor_t) -} short_descriptor_t; +} short_descriptor_lvl1_t; + + +//// possible values of descriptor_type field: -// possible values of descriptor_type field: #define SHORT_DESCRIPTOR_INVALID 0b00 #define SHORT_DESCRIPTOR_PAGE_TABLE 0b01 #define SHORT_DESCRIPTOR_SECTION_OR_SUPERSECTION 0b10 @@ -135,3 +177,4 @@ typedef union // 0b11 should not be used #define SHORT_DESCRIPTOR_RESERVED 0b11 +#endif // TRANSLATION_TABLE_DESCRIPTORS_H @@ -1,3 +1,6 @@ +#ifndef UART_H +#define UART_H + #include <stddef.h> #include <stdint.h> #include <global.h> @@ -39,3 +42,5 @@ void uart_init(); void uart_putc(unsigned char c); unsigned char uart_getc(); void uart_puts(const char* str); + +#endif // UART_H |