From 078c0d3bb558dada1a07cb652c600d3a6455fc57 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Wed, 11 Sep 2024 11:40:01 +0200 Subject: [PATCH 01/11] build_sdk.py: add support for x86_64 boards Signed-off-by: Mathieu Mirmont --- build_sdk.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build_sdk.py b/build_sdk.py index 0dfda740..1e85b906 100644 --- a/build_sdk.py +++ b/build_sdk.py @@ -34,6 +34,7 @@ TOOLCHAIN_AARCH64 = "aarch64-none-elf" TOOLCHAIN_RISCV = "riscv64-unknown-elf" +TOOLCHAIN_X86_64 = "x86_64-linux-gnu" KERNEL_CONFIG_TYPE = Union[bool, str] KERNEL_OPTIONS = Dict[str, Union[bool, str]] @@ -42,12 +43,15 @@ class KernelArch(IntEnum): AARCH64 = 1 RISCV64 = 2 + X86_64 = 3 def c_toolchain(self) -> str: if self == KernelArch.AARCH64: return TOOLCHAIN_AARCH64 elif self == KernelArch.RISCV64: return TOOLCHAIN_RISCV + elif self == KernelArch.X86_64: + return TOOLCHAIN_X86_64 else: raise Exception(f"Unsupported toolchain architecture '{self}'") @@ -57,11 +61,16 @@ def is_riscv(self) -> bool: def is_arm(self) -> bool: return self == KernelArch.AARCH64 + def is_x86_64(self) -> bool: + return self == KernelArch.X86_64 + def to_str(self) -> str: if self == KernelArch.AARCH64: return "aarch64" elif self == KernelArch.RISCV64: return "riscv64" + elif self == KernelArch.X86_64: + return "x86_64" else: raise Exception(f"Unsupported arch {self}") @@ -74,6 +83,7 @@ class BoardInfo: loader_link_address: int kernel_options: KERNEL_OPTIONS examples: Dict[str, Path] + gcc_march: Optional[str] = None @dataclass @@ -477,6 +487,8 @@ def build_elf_component( if board.gcc_cpu is not None: defines_str += f" GCC_CPU={board.gcc_cpu}" + if board.gcc_march is not None: + defines_str += f" GCC_MARCH={board.gcc_march}" r = system( f"{defines_str} make -C {component_name}" @@ -523,6 +535,8 @@ def build_lib_component( if board.gcc_cpu is not None: defines_str += f" GCC_CPU={board.gcc_cpu}" + if board.gcc_march is not None: + defines_str += f" GCC_MARCH={board.gcc_march}" r = system( f"{defines_str} make -C {component_name}" From 1cd25adc8ec170bdcea84b93ca899d2b57978fed Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Wed, 11 Sep 2024 11:54:53 +0200 Subject: [PATCH 02/11] libmicrokit: add x86_64 support Signed-off-by: Mathieu Mirmont --- libmicrokit/Makefile | 5 +++++ libmicrokit/include/microkit.h | 4 ++++ libmicrokit/src/x86_64/crt0.s | 10 ++++++++++ 3 files changed, 19 insertions(+) create mode 100644 libmicrokit/src/x86_64/crt0.s diff --git a/libmicrokit/Makefile b/libmicrokit/Makefile index 5df25f37..49f3ba62 100644 --- a/libmicrokit/Makefile +++ b/libmicrokit/Makefile @@ -25,6 +25,11 @@ else ifeq ($(ARCH),riscv64) CFLAGS_RISCV64 := -mcmodel=medany -march=rv64imafdc_zicsr_zifencei -mabi=lp64d CFLAGS_ARCH := $(CFLAGS_RISCV64) ARCH_DIR := riscv +else ifeq ($(ARCH),x86_64) + ASM_FLAGS := -march=generic64 + CFLAGS_X86_64 := -march=$(GCC_MARCH) -DARCH_x86_64 + CFLAGS_ARCH := $(CFLAGS_X86_64) + ARCH_DIR := x86_64 endif CFLAGS := -std=gnu11 \ diff --git a/libmicrokit/include/microkit.h b/libmicrokit/include/microkit.h index 26d93e1b..fa88ab43 100644 --- a/libmicrokit/include/microkit.h +++ b/libmicrokit/include/microkit.h @@ -79,7 +79,11 @@ static inline void microkit_pd_restart(microkit_child pd, seL4_Word entry_point) { seL4_Error err; seL4_UserContext ctxt = {0}; +#ifdef ARCH_x86_64 + ctxt.rip = entry_point; +#else ctxt.pc = entry_point; +#endif err = seL4_TCB_WriteRegisters( BASE_TCB_CAP + pd, seL4_True, diff --git a/libmicrokit/src/x86_64/crt0.s b/libmicrokit/src/x86_64/crt0.s new file mode 100644 index 00000000..546c1d83 --- /dev/null +++ b/libmicrokit/src/x86_64/crt0.s @@ -0,0 +1,10 @@ +/* + * Copyright 2024, Neutrality Sarl. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + + .section .text.start + .globl _start +_start: + call main From 16cb8659640faade84f3096c85c4d0473dfd58d9 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Wed, 11 Sep 2024 12:18:10 +0200 Subject: [PATCH 03/11] monitor: add x86_64 support Signed-off-by: Mathieu Mirmont --- monitor/Makefile | 10 +++++++++- monitor/src/main.c | 9 +++++++++ monitor/src/x86_64/crt0.s | 11 +++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 monitor/src/x86_64/crt0.s diff --git a/monitor/Makefile b/monitor/Makefile index 3479d5b1..b8eabe89 100644 --- a/monitor/Makefile +++ b/monitor/Makefile @@ -27,11 +27,19 @@ else ifeq ($(ARCH),riscv64) CFLAGS_ARCH := -mcmodel=medany -march=rv64imac_zicsr_zifencei -mabi=lp64 -DARCH_riscv64 ARCH_DIR := riscv +else ifeq ($(ARCH),x86_64) + CFLAGS_ARCH := -march=$(GCC_MARCH) -DARCH_x86_64 + ASM_CPP_FLAGS := -x assembler-with-cpp -c -g -march=$(GCC_MARCH) + ASM_FLAGS := -march=generic64 + LDFLAGS_ARCH := -z noexecstack + + ARCH_DIR := x86_64 else $(error ARCH is unsupported) endif CFLAGS := -std=gnu11 -g -O3 -nostdlib -ffreestanding -Wall -Wno-maybe-uninitialized -Werror -I$(SEL4_SDK)/include $(CFLAGS_ARCH) -DARCH_$(ARCH) +LDFLAGS := $(LDFLAGS_ARCH) PROGS := monitor.elf OBJECTS := main.o crt0.o debug.o util.o @@ -51,4 +59,4 @@ OBJPROG = $(addprefix $(BUILD_DIR)/, $(PROGS)) all: $(OBJPROG) $(OBJPROG): $(addprefix $(BUILD_DIR)/, $(OBJECTS)) $(LINKSCRIPT) - $(TOOLCHAIN)ld -T$(LINKSCRIPT) $(addprefix $(BUILD_DIR)/, $(OBJECTS)) -o $@ + $(TOOLCHAIN)ld -T$(LINKSCRIPT) $(LDFLAGS) $(addprefix $(BUILD_DIR)/, $(OBJECTS)) -o $@ diff --git a/monitor/src/main.c b/monitor/src/main.c index a821384f..b871a34c 100644 --- a/monitor/src/main.c +++ b/monitor/src/main.c @@ -792,6 +792,13 @@ static void aarch64_print_vm_fault() } #endif +#ifdef ARCH_x86_64 +static void x86_64_print_vm_fault() +{ + puts("TODO: implement x86_64_print_vm_fault\n"); +} +#endif + static void monitor(void) { for (;;) { @@ -911,6 +918,8 @@ static void monitor(void) aarch64_print_vm_fault(); #elif defined(ARCH_riscv64) riscv_print_vm_fault(); +#elif defined(ARCH_x86_64) + x86_64_print_vm_fault(); #else #error "Unknown architecture to print a VM fault for" #endif diff --git a/monitor/src/x86_64/crt0.s b/monitor/src/x86_64/crt0.s new file mode 100644 index 00000000..5e46c3f1 --- /dev/null +++ b/monitor/src/x86_64/crt0.s @@ -0,0 +1,11 @@ +/* + * Copyright 2024, Neutrality Sarl. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + + .section .text.start + .globl _start +_start: + leaq 0xff0 + _stack(%rip), %rsp + call main From e69925e838cd3ea03a3639483432e3c829267a33 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Wed, 11 Sep 2024 12:34:59 +0200 Subject: [PATCH 04/11] loader: add x86_64 support The boot protocol is sufficiently different on x86 compared to ARM and RISC-V to justify making a custom loader. Signed-off-by: Mathieu Mirmont --- loader/Makefile | 31 +++++-- loader/src/x86_64/crt0.s | 59 +++++++++++++ loader/src/x86_64/loader.c | 161 ++++++++++++++++++++++++++++++++++ loader/src/x86_64/multiboot.S | 69 +++++++++++++++ loader/src/x86_64/multiboot.h | 74 ++++++++++++++++ loader/src/x86_64/utils.h | 33 +++++++ loader/x86_64.ld | 39 ++++++++ 7 files changed, 461 insertions(+), 5 deletions(-) create mode 100644 loader/src/x86_64/crt0.s create mode 100644 loader/src/x86_64/loader.c create mode 100644 loader/src/x86_64/multiboot.S create mode 100644 loader/src/x86_64/multiboot.h create mode 100644 loader/src/x86_64/utils.h create mode 100644 loader/x86_64.ld diff --git a/loader/Makefile b/loader/Makefile index 4195f1e3..96534661 100644 --- a/loader/Makefile +++ b/loader/Makefile @@ -30,19 +30,33 @@ endif ifeq ($(ARCH),aarch64) CFLAGS_AARCH64 := -DPHYSICAL_ADDRESS_BITS=$(PHYSICAL_ADDRESS_BITS) -mcpu=$(GCC_CPU) -mgeneral-regs-only CFLAGS_ARCH := $(CFLAGS_AARCH64) -DARCH_aarch64 - ASM_FLAGS_ARCH := -DPHYSICAL_ADDRESS_BITS=$(PHYSICAL_ADDRESS_BITS) -mcpu=$(GCC_CPU) + ASM_CPP_FLAGS_ARCH := -DPHYSICAL_ADDRESS_BITS=$(PHYSICAL_ADDRESS_BITS) -mcpu=$(GCC_CPU) ARCH_DIR := aarch64 else ifeq ($(ARCH),riscv64) CFLAGS_RISCV64 := -mcmodel=medany -march=rv64imac_zicsr_zifencei -mabi=lp64 CFLAGS_ARCH := $(CFLAGS_RISCV64) -DARCH_riscv64 - ASM_FLAGS_ARCH := -march=rv64imac_zicsr_zifencei -mabi=lp64 -DFIRST_HART_ID=$(FIRST_HART_ID) + ASM_CPP_FLAGS_ARCH := -march=rv64imac_zicsr_zifencei -mabi=lp64 -DFIRST_HART_ID=$(FIRST_HART_ID) ARCH_DIR := riscv +else ifeq ($(ARCH),x86_64) + CFLAGS_X86_64 := -m32 \ + -DCONFIG_MULTIBOOT_GRAPHICS_MODE_NONE \ + -DCONFIG_MULTIBOOT1_HEADER \ + -DCONFIG_MULTIBOOT2_HEADER \ + -DARCH_x86_64 + CFLAGS_ARCH := $(CFLAGS_X86_64) + ASM_CPP_FLAGS_ARCH := $(CFLAGS_X86_64) + ASM_FLAGS_ARCH := --32 + LD_FLAGS_ARCH := -m elf_i386 -z noexecstack + ARCH_DIR := x86_64 endif CFLAGS := -std=gnu11 -g -O3 -nostdlib -ffreestanding $(CFLAGS_ARCH) -DBOARD_$(BOARD) -DPRINTING=$(PRINTING) -Wall -Werror -Wno-unused-function +ASM_CPP_FLAGS := $(ASM_CPP_FLAGS_ARCH) -D__ASM__HEADER__ -g ASM_FLAGS := $(ASM_FLAGS_ARCH) -g +LD_FLAGS := $(LD_FLAGS_ARCH) + PROGS := loader.elf OBJECTS := loader.o crt0.o @@ -50,14 +64,21 @@ ifeq ($(ARCH),aarch64) OBJECTS += util64.o endif +ifeq ($(ARCH),x86_64) + OBJECTS += multiboot.o +endif + LINKSCRIPT_INPUT := $(ARCH).ld LINKSCRIPT := $(BUILD_DIR)/link.ld $(BUILD_DIR)/%.o : src/$(ARCH_DIR)/%.S - $(TOOLCHAIN)gcc -x assembler-with-cpp -c $(ASM_FLAGS) $< -o $@ + $(TOOLCHAIN)gcc -x assembler-with-cpp -c $(ASM_CPP_FLAGS) $< -o $@ $(BUILD_DIR)/%.o : src/$(ARCH_DIR)/%.s - $(TOOLCHAIN)as $< -o $@ + $(TOOLCHAIN)as $(ASM_FLAGS) $< -o $@ + +$(BUILD_DIR)/%.o : src/$(ARCH_DIR)/%.c + $(TOOLCHAIN)gcc -c $(CFLAGS) $< -o $@ $(BUILD_DIR)/%.o : src/%.c $(TOOLCHAIN)gcc -c $(CFLAGS) $< -o $@ @@ -70,4 +91,4 @@ $(LINKSCRIPT): $(LINKSCRIPT_INPUT) $(TOOLCHAIN)cpp -DLINK_ADDRESS=$(LINK_ADDRESS) $< | grep -v "^#" > $@ $(OBJPROG): $(addprefix $(BUILD_DIR)/, $(OBJECTS)) $(LINKSCRIPT) - $(TOOLCHAIN)ld -T$(LINKSCRIPT) $(addprefix $(BUILD_DIR)/, $(OBJECTS)) -o $@ + $(TOOLCHAIN)ld $(LD_FLAGS) -T$(LINKSCRIPT) $(addprefix $(BUILD_DIR)/, $(OBJECTS)) -o $@ diff --git a/loader/src/x86_64/crt0.s b/loader/src/x86_64/crt0.s new file mode 100644 index 00000000..6c5819ea --- /dev/null +++ b/loader/src/x86_64/crt0.s @@ -0,0 +1,59 @@ +/* + * Copyright 2023, Neutrality. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +.section .text.start +.code32 + +/* + * Loader entry point. + */ + .globl start +start: + /* Load a stack. */ + leal stack_top, %esp + + /* Reset the EFLAGS register. */ + pushl $0 + popf + + /* Save the %eax and %ebx registers. */ + pushl %ebx /* multiboot_info_ptr */ + pushl %eax /* multiboot_magic */ + + /* Call the loader C function to tweak the multiboot info structure. + * The multiboot args are already on the stack. */ + call loader + testl %eax, %eax + js halt + + /* Jump into the seL4 kernel. */ + popl %eax + popl %ebx + leal kernel_entry, %ecx + jmp *(%ecx) + +halt: + /* This makes QEMU exit. It probably does not cause damage + * on real hardware. Well hopefully not too much at least. My + * laptop is still fine. The fire was probably caused by + * something else. */ + mov $0x2000, %ax + mov $0x0604, %dx + outw %ax, %dx + + /* The end. */ + cli +1: + hlt + jmp 1b + +/* + * Allocate a small stack for the few things we need to push there. + */ + .align 16 +stack: + .fill 0x100 +stack_top: diff --git a/loader/src/x86_64/loader.c b/loader/src/x86_64/loader.c new file mode 100644 index 00000000..8cd96c80 --- /dev/null +++ b/loader/src/x86_64/loader.c @@ -0,0 +1,161 @@ +/* + * Copyright 2023, Neutrality. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include "utils.h" +#include "multiboot.h" + +/* + * These global variables are overwritten by the microkit tool when building the + * image. + */ +uint32_t kernel_entry; +uint32_t monitor_addr; +uint32_t monitor_size; +uint64_t extra_device_addr_p; +uint64_t extra_device_size; + +/* Name the initial task. This adds nothing but flare to the boot logs. */ +static char *monitor_cmdline = "microkit"; + +/* Hardcode the serial port address. + * @mat: one day this should be configurable. */ +static const uint16_t serial_port = 0x3f8; + +/* Round a number up to the next 64-bit boundary. */ +static uint32_t roundup64(uint32_t n) +{ + if (n & 7) + n = (n & ~7) + 8; + return n; +} + +/* Serial code taken from seL4/src/plat/pc99/machine/io.c */ +static void serial_init(void) +{ + while (!(in8(serial_port + 5) & 0x60)) /* wait until not busy */ + ; + + out8(serial_port + 1, 0x00); /* disable generating interrupts */ + out8(serial_port + 3, 0x80); /* line control register: command: set divisor */ + out8(serial_port, 0x01); /* set low byte of divisor to 0x01 = 115200 baud */ + out8(serial_port + 1, 0x00); /* set high byte of divisor to 0x00 */ + out8(serial_port + 3, 0x03); /* line control register: set 8 bit, no parity, 1 stop bit */ + out8(serial_port + 4, 0x0b); /* modem control register: set DTR/RTS/OUT2 */ + + in8(serial_port); /* clear receiver serial_port */ + in8(serial_port + 5); /* clear line status serial_port */ + in8(serial_port + 6); /* clear modem status serial_port */ +} + +static inline void putc(uint8_t ch) +{ + while ((in8(serial_port + 5) & 0x20) == 0) + ; + out8(serial_port, ch); +} + +static inline void puts(const char *s) +{ + while (*s) + putc(*s++); +} + +static int loader_multiboot2(uint32_t multiboot_info_ptr) +{ + uint32_t *total_size = (uint32_t *) multiboot_info_ptr; + uint32_t last_tag_offset = 0; + + /* Walk the list of multiboot info tags. */ + for (uint32_t i = 2 * sizeof (uint32_t); i < *total_size; ) { + struct multiboot2_tag *tag = (void *) (multiboot_info_ptr + i); + + /* Fail if we were given any multiboot module. */ + if (tag->type == MULTIBOOT2_INFO_TAG_MODULE) { + puts("LDR|ERROR: multiboot modules not supported\r\n"); + return -1; + } + + /* Break on the closing tag. */ + if (tag->type == MULTIBOOT2_INFO_TAG_END && tag->size == 8) { + last_tag_offset = i; + break; + } + + /* Skip this tag and round up to the next 64-bit boundary. */ + i = roundup64(i + tag->size); + } + + /* That shouldn't happen but who knows. */ + if (!last_tag_offset) { + puts("LDR|ERROR: invalid boot information tag list\r\n"); + return -1; + } + + /* + * From here onwards we are carelessly extending the list of multiboot2 + * tags without checking that we do not overwrite anything important. + * So far there seem to be quite a lot of space between this tag list + * and the next memory region in use so that's good enough for a + * proof-of-concept implementation, but one this this should really be + * cleaned up. + */ + + /* Add a module tag for the monitor inittask ELF file. */ + struct multiboot2_tag_module *module = (void *) (multiboot_info_ptr + last_tag_offset); + module->head.type = MULTIBOOT2_INFO_TAG_MODULE; + module->head.size = sizeof (*module) + strlen(monitor_cmdline) + 1; + module->mod_start = monitor_addr; + module->mod_end = monitor_addr + monitor_size; + memcpy(&module->cmdline[0], monitor_cmdline, strlen(monitor_cmdline) + 1); + + /* Account for the new tag. */ + *total_size += roundup64(module->head.size); + last_tag_offset += roundup64(module->head.size); + + /* Add a custom tag to register device memory: memory regions that will + * be marked as device untyped by the kernel. This is an unofficial + * addition to the multiboot2 specs. */ + struct multiboot2_tag_device_memory *devmem = (void *) (multiboot_info_ptr + last_tag_offset); + devmem->head.type = MULTIBOOT2_INFO_TAG_DEVICE_MEMORY; + devmem->head.size = sizeof (*devmem); + devmem->dmem_addr = extra_device_addr_p; + devmem->dmem_size = extra_device_size; + + /* Account for the new tag. */ + *total_size += roundup64(devmem->head.size); + last_tag_offset += roundup64(devmem->head.size); + + /* Add a new end tag to close the list. Note that we do not need to + * account for this end tag since we have overwritten the previous one + * which was already accounted for. */ + struct multiboot2_tag *end = (void *) (multiboot_info_ptr + last_tag_offset); + end->type = MULTIBOOT2_INFO_TAG_END; + end->size = sizeof (*end); + + puts("LDR|INFO: loading complete, have a safe journey\r\n"); + return 0; +} + +int loader(uint32_t multiboot_magic, uint32_t multiboot_info_ptr) +{ + serial_init(); + + switch (multiboot_magic) { + case MULTIBOOT1_BOOT_MAGIC: + puts("LDR|INFO: booted as Multiboot v1\r\n"); + puts("LDR|ERROR: multiboot v1 not supported\r\n"); + return -1; + + case MULTIBOOT2_BOOT_MAGIC: + puts("LDR|INFO: booted as Multiboot v2\r\n"); + return loader_multiboot2(multiboot_info_ptr); + + default: + puts("LDR|ERROR: invalid multiboot magic\r\n"); + return -1; + } +} diff --git a/loader/src/x86_64/multiboot.S b/loader/src/x86_64/multiboot.S new file mode 100644 index 00000000..c522c6d7 --- /dev/null +++ b/loader/src/x86_64/multiboot.S @@ -0,0 +1,69 @@ +/* + * Copyright 2023, Neutrality. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Note that a lot of this is taken from seL4. Some fields from seL4's + * multiboot structure are configurable and we should make sure that we use the + * same values here because we'll hand over the multiboot information data + * (mostly) untouched to seL4. + */ + +#include "multiboot.h" + +#ifdef CONFIG_MULTIBOOT_GRAPHICS_MODE_NONE +# define MBT1_FLAGS (MULTIBOOT1_HEADER_FLAG_PAGE_ALIGN | MULTIBOOT1_HEADER_FLAG_REQ_MEMORY) +# define MBT1_GFXMODE MULTIBOOT1_HEADER_GFX_MODE_GFX +#else +# define MBT1_FLAGS (MULTIBOOT1_HEADER_FLAG_PAGE_ALIGN | MULTIBOOT1_HEADER_FLAG_REQ_MEMORY | MULTIBOOT1_HEADER_FLAG_REQ_VIDEO) +# ifdef CONFIG_MULTIBOOT_GRAPHICS_MODE_TEXT +# define MBT1_GFXMODE MULTIBOOT1_HEADER_GFX_MODE_TEXT +# else +# define MBT1_GFXMODE MULTIBOOT1_HEADER_GFX_MODE_GFX +# endif +#endif + +#ifndef CONFIG_MULTIBOOT_GRAPHICS_MODE_HEIGHT +#define CONFIG_MULTIBOOT_GRAPHICS_MODE_HEIGHT 0 +#endif +#ifndef CONFIG_MULTIBOOT_GRAPHICS_MODE_WIDTH +#define CONFIG_MULTIBOOT_GRAPHICS_MODE_WIDTH 0 +#endif +#ifndef CONFIG_MULTIBOOT_GRAPHICS_MODE_DEPTH +#define CONFIG_MULTIBOOT_GRAPHICS_MODE_DEPTH 0 +#endif + +.section .text.start +.code32 + +#ifdef CONFIG_MULTIBOOT1_HEADER + .align 4 + .long MULTIBOOT1_HEADER_MAGIC /* magic */ + .long MBT1_FLAGS /* flags */ + .long -(MBT1_FLAGS + MULTIBOOT1_HEADER_MAGIC) /* checksum */ + .long 0 /* header_addr */ + .long 0 /* load_addr */ + .long 0 /* load_end_addr */ + .long 0 /* bss_end_addr */ + .long 0 /* entry_addr */ + .long MBT1_GFXMODE /* mode_type */ + .long CONFIG_MULTIBOOT_GRAPHICS_MODE_WIDTH /* width */ + .long CONFIG_MULTIBOOT_GRAPHICS_MODE_HEIGHT /* height */ + .long CONFIG_MULTIBOOT_GRAPHICS_MODE_DEPTH /* depth */ +#endif + +#ifdef CONFIG_MULTIBOOT2_HEADER +#define MBT2_SIZE (__mbi2_end - __mbi2_start) + .align 8 +__mbi2_start: + .long MULTIBOOT2_HEADER_MAGIC /* magic */ + .long 0 /* architecture (i386) */ + .long MBT2_SIZE /* header size */ + .long -(MULTIBOOT2_HEADER_MAGIC + MBT2_SIZE) /* checksum */ + .word 0x0 /* end tag: type */ + .word 0x0 /* end tag: flags */ + .long 0x8 /* end tag: size */ +__mbi2_end: +#endif diff --git a/loader/src/x86_64/multiboot.h b/loader/src/x86_64/multiboot.h new file mode 100644 index 00000000..616577ce --- /dev/null +++ b/loader/src/x86_64/multiboot.h @@ -0,0 +1,74 @@ +/* + * Copyright 2023, Neutrality. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Multiboot 1 + */ + +#define MULTIBOOT1_HEADER_MAGIC 0x1BADB002 + +#define MULTIBOOT1_HEADER_FLAG_PAGE_ALIGN (1 << 0) +#define MULTIBOOT1_HEADER_FLAG_REQ_MEMORY (1 << 1) +#define MULTIBOOT1_HEADER_FLAG_REQ_VIDEO (1 << 2) + +#define MULTIBOOT1_HEADER_GFX_MODE_GFX 0 +#define MULTIBOOT1_HEADER_GFX_MODE_TEXT 1 + +#define MULTIBOOT1_BOOT_MAGIC 0x2BADB002 + +/* + * Multiboot 2 + */ + +#define MULTIBOOT2_HEADER_MAGIC 0xE85250D6 + +#define MULTIBOOT2_BOOT_MAGIC 0x36d76289 + +#define MULTIBOOT2_INFO_TAG_END 0 +#define MULTIBOOT2_INFO_TAG_COMMAND_LINE 1 +#define MULTIBOOT2_INFO_TAG_BOOTLOADER_NAME 2 +#define MULTIBOOT2_INFO_TAG_MODULE 3 +#define MULTIBOOT2_INFO_TAG_MEMORY 4 +#define MULTIBOOT2_INFO_TAG_BIOS_BOOT_DEVICE 5 +#define MULTIBOOT2_INFO_TAG_MEMORY_MAP 6 +#define MULTIBOOT2_INFO_TAG_VBE_INFO 7 +#define MULTIBOOT2_INFO_TAG_FRAMEBUFFER_INFO 8 +#define MULTIBOOT2_INFO_TAG_ELF_SYMBOLS 9 +#define MULTIBOOT2_INFO_TAG_APM_TABLE 10 +#define MULTIBOOT2_INFO_TAG_EFI32_SYSTEM_TABLE 11 +#define MULTIBOOT2_INFO_TAG_EFI64_SYSTEM_TABLE 12 +#define MULTIBOOT2_INFO_TAG_SMBIOS_TABLES 13 +#define MULTIBOOT2_INFO_TAG_ACPI_OLD_RSDP 14 +#define MULTIBOOT2_INFO_TAG_ACPI_NEW_RSDP 15 +#define MULTIBOOT2_INFO_TAG_NETWORK_INFO 16 +#define MULTIBOOT2_INFO_TAG_EFI_MEMORY_MAP 17 +#define MULTIBOOT2_INFO_TAG_EFI_BOOTSVC_RUNNING 18 +#define MULTIBOOT2_INFO_TAG_EFI32_IMAGE_HANDLE 19 +#define MULTIBOOT2_INFO_TAG_EFI64_IMAGE_HANDLE 20 +#define MULTIBOOT2_INFO_TAG_LOAD_BASE_PADDR 21 +#define MULTIBOOT2_INFO_TAG_DEVICE_MEMORY 42 /* Custom extension. */ + +#ifndef __ASM__HEADER__ + +struct multiboot2_tag { + uint32_t type; + uint32_t size; +} __attribute__((packed)); + +struct multiboot2_tag_module { + struct multiboot2_tag head; + uint32_t mod_start; + uint32_t mod_end; + char cmdline[0]; +} __attribute__((packed)); + +struct multiboot2_tag_device_memory { + struct multiboot2_tag head; + uint64_t dmem_addr; + uint64_t dmem_size; +} __attribute__((packed)); + +#endif diff --git a/loader/src/x86_64/utils.h b/loader/src/x86_64/utils.h new file mode 100644 index 00000000..4b5a0be5 --- /dev/null +++ b/loader/src/x86_64/utils.h @@ -0,0 +1,33 @@ +/* + * Copyright 2023, Neutrality. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +static inline void memcpy(char *dst, char *src, uint32_t len) +{ + while (len--) + *dst++ = *src++; +} + +static inline uint32_t strlen(char *str) +{ + uint32_t len = 0; + while (*str++) + len++; + return len; +} + +static inline uint8_t in8(uint16_t port) +{ + uint8_t value; + __asm__ __volatile__ ("inb %w1,%0":"=a" (value):"Nd" (port)); + return value; +} + +static inline void out8(uint16_t port, uint8_t value) +{ + __asm__ __volatile__ ("outb %b0,%w1": :"a" (value), "Nd" (port)); +} diff --git a/loader/x86_64.ld b/loader/x86_64.ld new file mode 100644 index 00000000..6a583113 --- /dev/null +++ b/loader/x86_64.ld @@ -0,0 +1,39 @@ +/* + * Copyright 2021, Breakaway Consulting Pty. Ltd. + * + * SPDX-License-Identifier: BSD-2-Clause + */ +PHDRS +{ + all PT_LOAD AT (LINK_ADDRESS); +} + +SECTIONS +{ + . = LINK_ADDRESS; + + .text : + { + _text = .; + *(.text.start) + *(.text*) + *(.rodata) + _text_end = .; + } :all + + .data : + { + _data = .; + *(.data) + _data_end = .; + } :all + + .bss : + { + _bss = .; + *(.bss) + *(COMMON) + . = ALIGN(4); + _bss_end = .; + } :all +} From cb11dcc21f179b5665fd18606e78ae7d97697b90 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Wed, 11 Sep 2024 22:04:33 +0200 Subject: [PATCH 05/11] tool/microkit: add x86_64 support Signed-off-by: Mathieu Mirmont --- tool/microkit/Cargo.lock | 21 ++ tool/microkit/Cargo.toml | 4 +- tool/microkit/src/elf.rs | 472 +++++++++++++++++++++----- tool/microkit/src/lib.rs | 41 ++- tool/microkit/src/loader.rs | 3 + tool/microkit/src/main.rs | 589 ++++++++++++++++++++++++++------- tool/microkit/src/sdf.rs | 175 ++++++++-- tool/microkit/src/sel4.rs | 426 +++++++++++++++++++++++- tool/microkit/src/x86loader.rs | 95 ++++++ tool/microkit/tests/test.rs | 6 +- 10 files changed, 1574 insertions(+), 258 deletions(-) create mode 100644 tool/microkit/src/x86loader.rs diff --git a/tool/microkit/Cargo.lock b/tool/microkit/Cargo.lock index 9373a798..94eb33a7 100644 --- a/tool/microkit/Cargo.lock +++ b/tool/microkit/Cargo.lock @@ -2,6 +2,25 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "concat-idents" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "itoa" version = "1.0.11" @@ -12,6 +31,8 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" name = "microkit-tool" version = "1.4.1-dev" dependencies = [ + "bincode", + "concat-idents", "roxmltree", "serde", "serde_json", diff --git a/tool/microkit/Cargo.toml b/tool/microkit/Cargo.toml index ceaad815..ac76ca1c 100644 --- a/tool/microkit/Cargo.toml +++ b/tool/microkit/Cargo.toml @@ -16,8 +16,10 @@ path = "src/main.rs" [dependencies] roxmltree = "0.19.0" -serde = "1.0.203" +serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" +bincode = "1.3.3" +concat-idents = "1.1" [profile.release] strip = true diff --git a/tool/microkit/src/elf.rs b/tool/microkit/src/elf.rs index d5f261e2..211aaa0a 100644 --- a/tool/microkit/src/elf.rs +++ b/tool/microkit/src/elf.rs @@ -4,12 +4,22 @@ // SPDX-License-Identifier: BSD-2-Clause // -use crate::util::bytes_to_struct; +use crate::round_up; use std::collections::HashMap; use std::fs; use std::path::Path; +use std::mem; + +use bincode::{serialize,deserialize}; +use serde::{Serialize, Deserialize}; +use concat_idents::concat_idents; + +// +// Elf32 definitions +// #[repr(C, packed)] +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] struct ElfHeader32 { ident_magic: u32, ident_class: u8, @@ -34,43 +44,50 @@ struct ElfHeader32 { } #[repr(C, packed)] -#[derive(Copy, Clone)] -struct ElfSymbol64 { - name: u32, - info: u8, - other: u8, - shndx: u16, - value: u64, - size: u64, +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] +struct ElfProgramHeader32 { + type_: u32, + offset: u32, + vaddr: u32, + paddr: u32, + filesz: u32, + memsz: u32, + flags: u32, + align: u32, } #[repr(C, packed)] -struct ElfSectionHeader64 { +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] +struct ElfSectionHeader32 { name: u32, type_: u32, - flags: u64, - addr: u64, - offset: u64, - size: u64, + flags: u32, + addr: u32, + offset: u32, + size: u32, link: u32, info: u32, - addralign: u64, - entsize: u64, + addralign: u32, + entsize: u32, } #[repr(C, packed)] -struct ElfProgramHeader64 { - type_: u32, - flags: u32, - offset: u64, - vaddr: u64, - paddr: u64, - filesz: u64, - memsz: u64, - align: u64, +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] +struct ElfSymbol32 { + name: u32, + value: u32, + size: u32, + info: u8, + other: u8, + shndx: u16, } +// +// Elf64 definitions +// + #[repr(C, packed)] +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] struct ElfHeader64 { ident_magic: u32, ident_class: u8, @@ -94,14 +111,185 @@ struct ElfHeader64 { shstrndx: u16, } +#[repr(C, packed)] +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] +struct ElfProgramHeader64 { + type_: u32, + flags: u32, + offset: u64, + vaddr: u64, + paddr: u64, + filesz: u64, + memsz: u64, + align: u64, +} + +#[repr(C, packed)] +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] +struct ElfSectionHeader64 { + name: u32, + type_: u32, + flags: u64, + addr: u64, + offset: u64, + size: u64, + link: u32, + info: u32, + addralign: u64, + entsize: u64, +} + +#[repr(C, packed)] +#[derive(Serialize, Deserialize, Copy, Clone, Default, Debug)] +struct ElfSymbol64 { + name: u32, + info: u8, + other: u8, + shndx: u16, + value: u64, + size: u64, +} + +// +// Elf32/Elf64 wrapping enumerates +// + +macro_rules! define_sizeof { + ($type:ident) => { + fn sizeof(&self) -> usize { + match self { + $type::EH32(x) => mem::size_of_val(x), + $type::EH64(x) => mem::size_of_val(x), + } + } + } +} + +macro_rules! define_accessors { + ($type:ident, $name:ident, $type32:ident, $type64:ident) => { + fn $name(&self) -> $type64 { + match self { + $type::EH32(x) => x.$name as $type64, + $type::EH64(x) => x.$name as $type64, + } + } + concat_idents!(fn_name = set_, $name { + fn fn_name(&mut self, value: $type64) { + match self { + $type::EH32(x) => (*x).$name = value as $type32, + $type::EH64(x) => (*x).$name = value, + }; + } + }); + }; +} + +#[derive(Copy, Clone, Debug)] +enum ElfHeader { + EH32(ElfHeader32), + EH64(ElfHeader64), +} + +#[allow(dead_code)] +impl ElfHeader { + define_sizeof!(ElfHeader); + + define_accessors!(ElfHeader, ident_magic, u32, u32); + define_accessors!(ElfHeader, ident_class, u8, u8); + define_accessors!(ElfHeader, ident_data, u8, u8); + define_accessors!(ElfHeader, ident_version, u8, u8); + define_accessors!(ElfHeader, ident_osabi, u8, u8); + define_accessors!(ElfHeader, ident_abiversion, u8, u8); + define_accessors!(ElfHeader, type_, u16, u16); + define_accessors!(ElfHeader, machine, u16, u16); + define_accessors!(ElfHeader, version, u32, u32); + define_accessors!(ElfHeader, entry, u32, u64); + define_accessors!(ElfHeader, phoff, u32, u64); + define_accessors!(ElfHeader, shoff, u32, u64); + define_accessors!(ElfHeader, flags, u32, u32); + define_accessors!(ElfHeader, ehsize, u16, u16); + define_accessors!(ElfHeader, phentsize, u16, u16); + define_accessors!(ElfHeader, phnum, u16, u16); + define_accessors!(ElfHeader, shentsize, u16, u16); + define_accessors!(ElfHeader, shnum, u16, u16); + define_accessors!(ElfHeader, shstrndx, u16, u16); +} + +#[derive(Copy, Clone)] +#[derive(Debug)] +enum ElfProgramHeader { + EH32(ElfProgramHeader32), + EH64(ElfProgramHeader64), +} + +#[allow(dead_code)] +impl ElfProgramHeader { + define_sizeof!(ElfProgramHeader); + + define_accessors!(ElfProgramHeader, type_, u32, u32); + define_accessors!(ElfProgramHeader, flags, u32, u32); + define_accessors!(ElfProgramHeader, offset, u32, u64); + define_accessors!(ElfProgramHeader, vaddr, u32, u64); + define_accessors!(ElfProgramHeader, paddr, u32, u64); + define_accessors!(ElfProgramHeader, filesz, u32, u64); + define_accessors!(ElfProgramHeader, memsz, u32, u64); + define_accessors!(ElfProgramHeader, align, u32, u64); +} + +#[derive(Copy, Clone)] +#[derive(Debug)] +enum ElfSectionHeader { + EH32(ElfSectionHeader32), + EH64(ElfSectionHeader64), +} + +#[allow(dead_code)] +impl ElfSectionHeader { + define_sizeof!(ElfSectionHeader); + + define_accessors!(ElfSectionHeader, name, u32, u32); + define_accessors!(ElfSectionHeader, type_, u32, u32); + define_accessors!(ElfSectionHeader, flags, u32, u64); + define_accessors!(ElfSectionHeader, addr, u32, u64); + define_accessors!(ElfSectionHeader, offset, u32, u64); + define_accessors!(ElfSectionHeader, size, u32, u64); + define_accessors!(ElfSectionHeader, link, u32, u32); + define_accessors!(ElfSectionHeader, info, u32, u32); + define_accessors!(ElfSectionHeader, addralign, u32, u64); + define_accessors!(ElfSectionHeader, entsize, u32, u64); +} + +#[derive(Copy, Clone)] +enum ElfSymbol { + EH32(ElfSymbol32), + EH64(ElfSymbol64), +} + +#[allow(dead_code)] +impl ElfSymbol { + define_sizeof!(ElfSymbol); + + define_accessors!(ElfSymbol, name, u32, u32); + define_accessors!(ElfSymbol, info, u8, u8); + define_accessors!(ElfSymbol, other, u8, u8); + define_accessors!(ElfSymbol, shndx, u16, u16); + define_accessors!(ElfSymbol, value, u32, u64); + define_accessors!(ElfSymbol, size, u32, u64); +} + +// +// Common Elf definitions +// + const ELF_MAGIC: &[u8; 4] = b"\x7FELF"; +#[derive(Clone, Debug)] pub struct ElfSegment { pub data: Vec, pub phys_addr: u64, pub virt_addr: u64, pub loadable: bool, - attrs: u32, + pub attrs: u32, } impl ElfSegment { @@ -122,7 +310,7 @@ impl ElfSegment { } } -enum ElfSegmentAttributes { +pub enum ElfSegmentAttributes { /// Corresponds to PF_X Execute = 0x1, /// Corresponds to PF_W @@ -135,7 +323,8 @@ pub struct ElfFile { pub word_size: usize, pub entry: u64, pub segments: Vec, - symbols: HashMap, + symbols: HashMap, + header: ElfHeader, } impl ElfFile { @@ -150,19 +339,10 @@ impl ElfFile { return Err(format!("ELF '{}': magic check failed", path.display())); } - let word_size; - let hdr_size; - - let class = &bytes[4..5][0]; - match class { - 1 => { - hdr_size = std::mem::size_of::(); - word_size = 32; - } - 2 => { - hdr_size = std::mem::size_of::(); - word_size = 64; - } + let class: u8 = bytes[4..5][0]; + let (hdr_size, word_size) = match class { + 1 => (std::mem::size_of::(), 32), + 2 => (std::mem::size_of::(), 64), _ => { return Err(format!( "ELF '{}': invalid class '{}'", @@ -174,64 +354,71 @@ impl ElfFile { // Now need to read the header into a struct let hdr_bytes = &bytes[..hdr_size]; - let hdr = unsafe { bytes_to_struct::(hdr_bytes) }; + let hdr: ElfHeader = match class { + 1 => ElfHeader::EH32(deserialize(hdr_bytes).unwrap()), + 2 => ElfHeader::EH64(deserialize(hdr_bytes).unwrap()), + _ => unimplemented!(), + }; // We have checked this above but we should check again once we actually cast it to // a struct. - assert!(hdr.ident_magic.to_le_bytes() == *magic); - assert!(hdr.ident_class == *class); - - if hdr.ident_data != 1 { - return Err(format!( - "ELF '{}': incorrect endianness, only little endian architectures are supported", - path.display() - )); - } + assert!(hdr.ident_magic().to_le_bytes() == *magic); + assert!(hdr.ident_class() == class); + assert!(hdr.ident_data() == 1, "only little endian architectures are supported"); - let entry = hdr.entry; + let entry = hdr.entry(); // Read all the segments - let mut segments = Vec::with_capacity(hdr.phnum as usize); - for i in 0..hdr.phnum { - let phent_start = hdr.phoff + (i * hdr.phentsize) as u64; - let phent_end = phent_start + (hdr.phentsize as u64); + let mut segments = Vec::with_capacity(hdr.phnum() as usize); + for i in 0..hdr.phnum() { + let phent_start = hdr.phoff() + (i * hdr.phentsize()) as u64; + let phent_end = phent_start + (hdr.phentsize() as u64); let phent_bytes = &bytes[phent_start as usize..phent_end as usize]; - let phent = unsafe { bytes_to_struct::(phent_bytes) }; + let phent: ElfProgramHeader = match class { + 1 => ElfProgramHeader::EH32(deserialize(phent_bytes).unwrap()), + 2 => ElfProgramHeader::EH64(deserialize(phent_bytes).unwrap()), + _ => unimplemented!(), + }; - let segment_start = phent.offset as usize; - let segment_end = phent.offset as usize + phent.filesz as usize; + let segment_start = phent.offset() as usize; + let segment_end = phent.offset() as usize + phent.filesz() as usize; - if phent.type_ != 1 { + if phent.type_() != 1 { continue; } - let mut segment_data = vec![0; phent.memsz as usize]; - segment_data[..phent.filesz as usize] + let mut segment_data = vec![0; phent.memsz() as usize]; + segment_data[..phent.filesz() as usize] .copy_from_slice(&bytes[segment_start..segment_end]); let segment = ElfSegment { data: segment_data, - phys_addr: phent.paddr, - virt_addr: phent.vaddr, - loadable: phent.type_ == 1, - attrs: phent.flags, + phys_addr: phent.paddr(), + virt_addr: phent.vaddr(), + loadable: phent.type_() == 1, + attrs: phent.flags(), }; segments.push(segment) } // Read all the section headers - let mut shents = Vec::with_capacity(hdr.shnum as usize); - let mut symtab_shent: Option<&ElfSectionHeader64> = None; - let mut shstrtab_shent: Option<&ElfSectionHeader64> = None; - for i in 0..hdr.shnum { - let shent_start = hdr.shoff + (i * hdr.shentsize) as u64; - let shent_end = shent_start + hdr.shentsize as u64; + let mut shents = Vec::with_capacity(hdr.shnum() as usize); + let mut symtab_shent: Option = None; + let mut shstrtab_shent: Option = None; + for i in 0..hdr.shnum() { + let shent_start = hdr.shoff() + (i * hdr.shentsize()) as u64; + let shent_end = shent_start + hdr.shentsize() as u64; let shent_bytes = &bytes[shent_start as usize..shent_end as usize]; - let shent = unsafe { bytes_to_struct::(shent_bytes) }; - match shent.type_ { + let shent: ElfSectionHeader = match class { + 1 => ElfSectionHeader::EH32(deserialize(shent_bytes).unwrap()), + 2 => ElfSectionHeader::EH64(deserialize(shent_bytes).unwrap()), + _ => unimplemented!(), + }; + + match shent.type_() { 2 => symtab_shent = Some(shent), 3 => shstrtab_shent = Some(shent), _ => {} @@ -255,29 +442,44 @@ impl ElfFile { } // Reading the symbol table - let symtab_start = symtab_shent.unwrap().offset as usize; - let symtab_end = symtab_start + symtab_shent.unwrap().size as usize; + let symtab_start = symtab_shent.unwrap().offset() as usize; + let symtab_end = symtab_start + symtab_shent.unwrap().size() as usize; let symtab = &bytes[symtab_start..symtab_end]; - let symtab_str_shent = shents[symtab_shent.unwrap().link as usize]; - let symtab_str_start = symtab_str_shent.offset as usize; - let symtab_str_end = symtab_str_start + symtab_str_shent.size as usize; + let symtab_str_shent = shents[symtab_shent.unwrap().link() as usize]; + let symtab_str_start = symtab_str_shent.offset() as usize; + let symtab_str_end = symtab_str_start + symtab_str_shent.size() as usize; let symtab_str = &bytes[symtab_str_start..symtab_str_end]; // Read all the symbols - let mut symbols: HashMap = HashMap::new(); + let mut symbols: HashMap = HashMap::new(); let mut offset = 0; - let symbol_size = std::mem::size_of::(); + let symbol_size = match class { + 1 => std::mem::size_of::(), + 2 => std::mem::size_of::(), + _ => unimplemented!(), + }; while offset < symtab.len() { let sym_bytes = &symtab[offset..offset + symbol_size]; - let (sym_head, sym_body, sym_tail) = unsafe { sym_bytes.align_to::() }; - assert!(sym_head.is_empty()); - assert!(sym_body.len() == 1); - assert!(sym_tail.is_empty()); - - let sym = sym_body[0]; + let sym: ElfSymbol = match class { + 1 => { + let (sym_head, sym_body, sym_tail) = unsafe { sym_bytes.align_to::() }; + assert!(sym_head.is_empty()); + assert!(sym_body.len() == 1); + assert!(sym_tail.is_empty()); + ElfSymbol::EH32(sym_body[0]) + } + 2 => { + let (sym_head, sym_body, sym_tail) = unsafe { sym_bytes.align_to::() }; + assert!(sym_head.is_empty()); + assert!(sym_body.len() == 1); + assert!(sym_tail.is_empty()); + ElfSymbol::EH64(sym_body[0]) + } + _ => unimplemented!(), + }; - let name = Self::get_string(symtab_str, sym.name as usize)?; + let name = Self::get_string(symtab_str, sym.name() as usize)?; // It is possible for a valid ELF to contain multiple global symbols with the same name. // Because we are making the hash map of symbols now, what we do is keep track of how many // times we encounter the symbol name. Only when we go to find a particular symbol, do @@ -299,6 +501,7 @@ impl ElfFile { entry, segments, symbols, + header: hdr, }) } @@ -309,7 +512,7 @@ impl ElfFile { "Found multiple symbols with name '{variable_name}'" )) } else { - Ok((sym.value, sym.size)) + Ok((sym.value(), sym.size())) } } else { Err(format!("No symbol named '{variable_name}' not found")) @@ -363,4 +566,95 @@ impl ElfFile { pub fn loadable_segments(&self) -> Vec<&ElfSegment> { self.segments.iter().filter(|s| s.loadable).collect() } + + pub fn add_segment(&mut self, segment: ElfSegment) { + self.segments.push(segment); + } + + pub fn write(&self, writer: &mut W) -> std::io::Result<()> { + let (phentsize, shentsize) = match self.header { + ElfHeader::EH32(_) => {( + std::mem::size_of::() as u16, + std::mem::size_of::() as u16, + )} + ElfHeader::EH64(_) => {( + std::mem::size_of::() as u16, + std::mem::size_of::() as u16, + )} + }; + + // Start the program headers on an 8-byte boundary. + let phoff = round_up(self.header.sizeof(), 8) as u64; + + // Add the segment headers to the end of the file. + let shoff = phoff + + (self.segments.len() as u64 * phentsize as u64) + + self.segments.iter().map(|s| s.data.len() as u64).sum::(); + let shoff = round_up(shoff as usize, 8) as u64; + + // As we will rewrite the ELF file we need to update a few fields in the header. + let mut header = self.header.clone(); + header.set_entry(self.entry); + header.set_phoff(phoff); + header.set_shoff(shoff); + header.set_phentsize(phentsize); + header.set_phnum(self.segments.len() as u16); + header.set_shentsize(shentsize); + header.set_shnum(1); + header.set_shstrndx(0); + + // Write the ELF header. + let header_bytes = match header { + ElfHeader::EH32(hdr) => serialize(&hdr).unwrap(), + ElfHeader::EH64(hdr) => serialize(&hdr).unwrap(), + }; + assert!(header_bytes.len() == header.sizeof()); + writer.write_all(&header_bytes)?; + + // Write all program headers, bumping the data offset within the file as we go. + writer.seek(std::io::SeekFrom::Start(phoff))?; + let mut data_offset = phoff + self.segments.len() as u64 * phentsize as u64; + for segment in &self.segments { + let mut pheader: ElfProgramHeader = match header { + ElfHeader::EH32(_) => ElfProgramHeader::EH32(ElfProgramHeader32::default()), + ElfHeader::EH64(_) => ElfProgramHeader::EH64(ElfProgramHeader64::default()), + }; + pheader.set_type_(1); + pheader.set_flags(segment.attrs); + pheader.set_offset(data_offset); + pheader.set_vaddr(segment.virt_addr); + pheader.set_paddr(segment.phys_addr); + pheader.set_filesz(segment.mem_size()); + pheader.set_memsz(segment.mem_size()); + pheader.set_align(0x1000); + + let pheader_bytes = match pheader { + ElfProgramHeader::EH32(hdr) => serialize(&hdr).unwrap(), + ElfProgramHeader::EH64(hdr) => serialize(&hdr).unwrap(), + }; + assert!(pheader_bytes.len() == pheader.sizeof()); + writer.write_all(&pheader_bytes)?; + + data_offset += segment.mem_size(); + } + + // Write all segments. + for segment in &self.segments { + writer.write_all(&segment.data)?; + } + + // Write one dummy section header with a single empty section to pacify grub. + writer.seek(std::io::SeekFrom::Start(shoff))?; + let sheader: ElfSectionHeader = match header { + ElfHeader::EH32(_) => ElfSectionHeader::EH32(ElfSectionHeader32::default()), + ElfHeader::EH64(_) => ElfSectionHeader::EH64(ElfSectionHeader64::default()), + }; + let sheader_bytes = match sheader { + ElfSectionHeader::EH32(hdr) => serialize(&hdr).unwrap(), + ElfSectionHeader::EH64(hdr) => serialize(&hdr).unwrap(), + }; + writer.write_all(&sheader_bytes)?; + + Ok(()) + } } diff --git a/tool/microkit/src/lib.rs b/tool/microkit/src/lib.rs index ea5d8d97..d0eaf775 100644 --- a/tool/microkit/src/lib.rs +++ b/tool/microkit/src/lib.rs @@ -6,6 +6,7 @@ pub mod elf; pub mod loader; +pub mod x86loader; pub mod sdf; pub mod sel4; pub mod util; @@ -114,12 +115,23 @@ impl MemoryRegion { self.end - self.base } - pub fn aligned_power_of_two_regions(&self, max_bits: u64) -> Vec { + // During the boot phase, the kernel creates all of the untyped regions + // based on the kernel virtual addresses, rather than the physical + // memory addresses. This has a subtle side affect in the process of + // creating untypeds as even though all the kernel virtual addresses are + // a constant offest of the corresponding physical address, overflow can + // occur when dealing with virtual addresses. This precisely occurs in + // this function, causing different regions depending on whether + // you use kernel virtual or physical addresses. In order to properly + // emulate the kernel booting process, we also have to emulate the interger + // overflow that can occur. + pub fn aligned_power_of_two_regions(&self, kernel_virtual_base: u64, max_bits: u64) -> Vec { let mut regions = Vec::new(); - let mut base = self.base; + let mut base = kernel_virtual_base + self.base; let mut bits; - while base != self.end { - let size = self.end - base; + let end = kernel_virtual_base + self.end; + while base != end { + let size = end - base; let size_bits = util::msb(size); if base == 0 { bits = size_bits; @@ -131,7 +143,14 @@ impl MemoryRegion { bits = max_bits; } let sz = 1 << bits; - regions.push(MemoryRegion::new(base, base + sz)); + let base_paddr = base - kernel_virtual_base; + // The seL4 kernel does not create untyped caps for memory regions + // smaller than seL4_MinUntypedBits since they wouldn't be large enough + // to be retyped into objects. This value is set to 4 on all + // architectures so it is hardcoded for now. + if size_bits >= 4 { + regions.push(MemoryRegion::new(base_paddr, base_paddr + sz)); + } base += sz; } @@ -206,10 +225,10 @@ impl DisjointMemoryRegion { self.check(); } - pub fn aligned_power_of_two_regions(&self, max_bits: u64) -> Vec { + pub fn aligned_power_of_two_regions(&self, kernel_virtual_base: u64, max_bits: u64) -> Vec { let mut aligned_regions = Vec::new(); for region in &self.regions { - aligned_regions.extend(region.aligned_power_of_two_regions(max_bits)); + aligned_regions.extend(region.aligned_power_of_two_regions(kernel_virtual_base, max_bits)); } aligned_regions @@ -362,3 +381,11 @@ impl ObjectAllocator { panic!("Can't alloc of size {}, count: {} - no space", size, count); } } + +pub fn round_up(n: usize, x: usize) -> usize { + let (_d, m) = (n / x, n % x); + match m { + 0 => n, + _ => n + x - m, + } +} diff --git a/tool/microkit/src/loader.rs b/tool/microkit/src/loader.rs index ab031550..9b85a6cf 100644 --- a/tool/microkit/src/loader.rs +++ b/tool/microkit/src/loader.rs @@ -228,6 +228,9 @@ impl<'a> Loader<'a> { kernel_first_vaddr.unwrap(), kernel_first_paddr.unwrap(), ), + _ => { + panic!("Target architecture not supported by the Loader"); + }, }; let image_segment = elf diff --git a/tool/microkit/src/main.rs b/tool/microkit/src/main.rs index 63cd3b35..52a04825 100644 --- a/tool/microkit/src/main.rs +++ b/tool/microkit/src/main.rs @@ -9,18 +9,19 @@ use elf::ElfFile; use loader::Loader; +use x86loader::X86Loader; use microkit_tool::{ - elf, loader, sdf, sel4, util, DisjointMemoryRegion, MemoryRegion, ObjectAllocator, Region, + elf, loader, x86loader, sdf, sel4, util, DisjointMemoryRegion, MemoryRegion, ObjectAllocator, Region, UntypedObject, MAX_PDS, PD_MAX_NAME_LENGTH, }; use sdf::{ - parse, ProtectionDomain, SysMap, SysMapPerms, SysMemoryRegion, SystemDescription, + parse, ProtectionDomain, SysMap, SysMapPerms, SysMemoryRegion, SysIrqKind, SystemDescription, VirtualMachine, }; use sel4::{ default_vm_attr, Aarch64Regs, Arch, ArmVmAttributes, BootInfo, Config, Invocation, InvocationArgs, Object, ObjectType, PageSize, Rights, Riscv64Regs, RiscvVirtualMemory, - RiscvVmAttributes, + RiscvVmAttributes, X86_64Regs, X86VmAttributes }; use std::cmp::{max, min}; use std::collections::{HashMap, HashSet}; @@ -73,6 +74,9 @@ const IRQ_CONTROL_CAP_ADDRESS: u64 = 4; // Singleton const INIT_ASID_POOL_CAP_ADDRESS: u64 = 6; const SMC_CAP_ADDRESS: u64 = 15; +const N_VTD_CONTEXTS: u64 = 256; +const VTD_PT_INDEX_BITS: u64 = 9; + // const ASID_CONTROL_CAP_ADDRESS: u64 = 5; // Singleton // const IO_PORT_CONTROL_CAP_ADDRESS: u64 = 7; // Null on this platform // const IO_SPACE_CAP_ADDRESS: u64 = 8; // Null on this platform @@ -592,6 +596,7 @@ fn kernel_device_addrs(config: &Config, kernel_elf: &ElfFile) -> Vec { let kernel_frame_size = match config.arch { Arch::Aarch64 => size_of::(), Arch::Riscv64 => size_of::(), + Arch::X86_64 => unimplemented!(), }; let mut offset: usize = 0; while offset < size as usize { @@ -609,6 +614,7 @@ fn kernel_device_addrs(config: &Config, kernel_elf: &ElfFile) -> Vec { ); (frame.user_accessible, frame.paddr) } + Arch::X86_64 => unimplemented!() } }; if user_accessible == 0 { @@ -653,7 +659,9 @@ fn kernel_self_mem(kernel_elf: &ElfFile) -> MemoryRegion { let (ki_end_v, _) = kernel_elf .find_symbol("ki_end") .expect("Could not find 'ki_end' symbol"); - let ki_end_p = ki_end_v - segments[0].virt_addr + base; + let last_segment = segments.last().unwrap(); + let v_p_offset = last_segment.virt_addr - last_segment.phys_addr; + let ki_end_p = ki_end_v - v_p_offset; MemoryRegion::new(base, ki_end_p) } @@ -664,7 +672,9 @@ fn kernel_boot_mem(kernel_elf: &ElfFile) -> MemoryRegion { let (ki_boot_end_v, _) = kernel_elf .find_symbol("ki_boot_end") .expect("Could not find 'ki_boot_end' symbol"); - let ki_boot_end_p = ki_boot_end_v - segments[0].virt_addr + base; + let last_segment = segments.last().unwrap(); + let v_p_offset = last_segment.virt_addr - last_segment.phys_addr; + let ki_boot_end_p = ki_boot_end_v - v_p_offset; MemoryRegion::new(base, ki_boot_end_p) } @@ -676,7 +686,11 @@ fn kernel_boot_mem(kernel_elf: &ElfFile) -> MemoryRegion { /// This factors the common parts of 'emulate_kernel_boot' and /// 'emulate_kernel_boot_partial' to avoid code duplication. /// -fn kernel_partial_boot(kernel_config: &Config, kernel_elf: &ElfFile) -> KernelPartialBootInfo { +fn kernel_partial_boot( + kernel_config: &Config, + kernel_elf: &ElfFile, + x86_machine: &Option, +) -> KernelPartialBootInfo { // Determine the untyped caps of the system // This lets allocations happen correctly. let mut device_memory = DisjointMemoryRegion::default(); @@ -690,21 +704,49 @@ fn kernel_partial_boot(kernel_config: &Config, kernel_elf: &ElfFile) -> KernelPa // NOTE: There is an assumption each kernel device is one frame // in size only. It's possible this assumption could break in the // future. - for paddr in kernel_device_addrs(kernel_config, kernel_elf) { - device_memory.remove_region(paddr, paddr + kernel_config.kernel_frame_size); - } + match kernel_config.arch { + Arch::Aarch64 | Arch::Riscv64 => { + for paddr in kernel_device_addrs(kernel_config, kernel_elf) { + device_memory.remove_region(paddr, paddr + kernel_config.kernel_frame_size); + } + } + Arch::X86_64 => { + for kdev in x86_machine.as_ref().unwrap()["kdevs"].as_array().unwrap() { + let base = kdev["base"].as_u64().unwrap(); + let size = kdev["size"].as_u64().unwrap(); + device_memory.remove_region(base, base + size); + } + } + }; // Remove all the actual physical memory from the device regions // but add it all to the actual normal memory regions - for (start, end) in kernel_phys_mem(kernel_config, kernel_elf) { - device_memory.remove_region(start, end); - normal_memory.insert_region(start, end); + match kernel_config.arch { + Arch::Aarch64 | Arch::Riscv64 => { + for (start, end) in kernel_phys_mem(kernel_config, kernel_elf) { + device_memory.remove_region(start, end); + normal_memory.insert_region(start, end); + } + } + Arch::X86_64 => { + for mr in x86_machine.as_ref().unwrap()["memory"].as_array().unwrap() { + let base = mr["base"].as_u64().unwrap(); + let size = mr["size"].as_u64().unwrap(); + device_memory.remove_region(base, base + size); + normal_memory.insert_region(base, base + size); + } + } } // Remove the kernel image itself let self_mem = kernel_self_mem(kernel_elf); normal_memory.remove_region(self_mem.base, self_mem.end); + // Remove the MSI region on X86 + if let Arch::X86_64 = kernel_config.arch { + device_memory.remove_region(0xfffffff8, 0x100000000); + } + // but get the boot region, we'll add that back later // FIXME: Why calcaultae it now if we add it back later? let boot_region = kernel_boot_mem(kernel_elf); @@ -719,8 +761,9 @@ fn kernel_partial_boot(kernel_config: &Config, kernel_elf: &ElfFile) -> KernelPa fn emulate_kernel_boot_partial( kernel_config: &Config, kernel_elf: &ElfFile, + x86_machine: &Option, ) -> (DisjointMemoryRegion, MemoryRegion) { - let partial_info = kernel_partial_boot(kernel_config, kernel_elf); + let partial_info = kernel_partial_boot(kernel_config, kernel_elf, x86_machine); (partial_info.normal_memory, partial_info.boot_region) } @@ -731,6 +774,75 @@ fn get_n_paging(region: MemoryRegion, bits: u64) -> u64 { (end - start) / (1 << bits) } +struct AcpiRmrr { + device: u32, + base: u64, + limit: u64, +} + +impl AcpiRmrr { + pub fn from_json_object(obj: &serde_json::Value) -> AcpiRmrr { + match obj { + serde_json::Value::Object(x) => + AcpiRmrr { + device: x["devid"].as_u64().unwrap() as u32, + base: x["base"].as_u64().unwrap(), + limit: x["limit"].as_u64().unwrap(), + }, + _ => panic!("AcpiRmrr::fromJsonObject called with an invalid argument") + } + } + + fn get_pci_bus(&self) -> u32 { + (self.device >> 8) & 0xff + } +} + +fn get_vtd_n_paging(x86_machine: &Option) -> u64 { + let rmrrs = x86_machine.as_ref().unwrap()["rmrrs"].as_array().unwrap(); + + let mut size = 1; // one for the root table + size += N_VTD_CONTEXTS; // one for each context + size += rmrrs.len() as u64; // one for each device + + if rmrrs.len() == 0 { + return size; + } + + // Filter the identical regions by pci bus id + let mut filtered: Vec = vec![AcpiRmrr::from_json_object(&rmrrs[0])]; + for rmrr in rmrrs { + let this = AcpiRmrr::from_json_object(rmrr); + let last = filtered.last().unwrap(); + if this.get_pci_bus() != last.get_pci_bus() + && this.base != last.base + && this.limit != last.limit { + filtered.push(this); + } + } + + let bootinfo = x86_machine.as_ref().unwrap()["bootinfo"].as_object().unwrap(); + let nlevels = bootinfo["numIOPTLevels"].as_u64().unwrap(); + + for i in (1..nlevels).rev() { + let nbits = VTD_PT_INDEX_BITS * i + 12; + // If we are still looking up bits beyond the 32bit of physical + // that we support then we select entry 0 in the current PT + if nbits >= 32 { + size += 1; + } else { + for rmrr in &filtered { + let region = MemoryRegion { + base: rmrr.base, + end: rmrr.limit, + }; + size += get_n_paging(region, 32 - nbits); + } + } + } + size +} + fn get_arch_n_paging(config: &Config, region: MemoryRegion) -> u64 { match config.arch { Arch::Aarch64 => { @@ -748,7 +860,15 @@ fn get_arch_n_paging(config: &Config, region: MemoryRegion) -> u64 { get_n_paging(region, LVL2_INDEX_OFFSET) + get_n_paging(region, LVL1_INDEX_OFFSET) } - }, + } + Arch::X86_64 => { + const PT_INDEX_OFFSET: u64 = 12; + const PD_INDEX_OFFSET: u64 = PT_INDEX_OFFSET + 9; + const PDPT_INDEX_OFFSET: u64 = PD_INDEX_OFFSET + 9; + const PML4_INDEX_OFFSET: u64 = PDPT_INDEX_OFFSET + 9; + + get_n_paging(region, PML4_INDEX_OFFSET) + get_n_paging(region, PDPT_INDEX_OFFSET) + get_n_paging(region, PD_INDEX_OFFSET) + } } } @@ -761,7 +881,9 @@ fn rootserver_max_size_bits(config: &Config) -> u64 { max(cnode_size_bits, vspace_bits) } -fn calculate_rootserver_size(config: &Config, initial_task_region: MemoryRegion) -> u64 { +fn calculate_rootserver_size(config: &Config, + initial_task_region: MemoryRegion, + x86_machine: &Option) -> u64 { // FIXME: These constants should ideally come from the config / kernel // binary not be hard coded here. // But they are constant so it isn't too bad. @@ -779,10 +901,16 @@ fn calculate_rootserver_size(config: &Config, initial_task_region: MemoryRegion) size += 1 << (tcb_bits); size += 2 * (1 << page_bits); size += 1 << asid_pool_bits; + if let Arch::X86_64 = config.arch { + // One more page the the extra boot info. + size += 1 << page_bits; + } size += 1 << vspace_bits; - size += get_arch_n_paging(config, initial_task_region) * (1 << page_table_bits); size += 1 << min_sched_context_bits; - + size += get_arch_n_paging(config, initial_task_region) * (1 << page_table_bits); + if let Arch::X86_64 = config.arch { + size += get_vtd_n_paging(x86_machine) * (1 << page_table_bits); + } size } @@ -794,9 +922,10 @@ fn emulate_kernel_boot( initial_task_phys_region: MemoryRegion, initial_task_virt_region: MemoryRegion, reserved_region: MemoryRegion, + x86_machine: &Option, ) -> BootInfo { assert!(initial_task_phys_region.size() == initial_task_virt_region.size()); - let partial_info = kernel_partial_boot(config, kernel_elf); + let partial_info = kernel_partial_boot(config, kernel_elf, x86_machine); let mut normal_memory = partial_info.normal_memory; let device_memory = partial_info.device_memory; let boot_region = partial_info.boot_region; @@ -805,7 +934,7 @@ fn emulate_kernel_boot( normal_memory.remove_region(reserved_region.base, reserved_region.end); // Now, the tricky part! determine which memory is used for the initial task objects - let initial_objects_size = calculate_rootserver_size(config, initial_task_virt_region); + let initial_objects_size = calculate_rootserver_size(config, initial_task_virt_region, x86_machine); let initial_objects_align = rootserver_max_size_bits(config); // Find an appropriate region of normal memory to allocate the objects @@ -832,23 +961,34 @@ fn emulate_kernel_boot( let fixed_cap_count = 0x10; let sched_control_cap_count = 1; let paging_cap_count = get_arch_n_paging(config, initial_task_virt_region); - let page_cap_count = initial_task_virt_region.size() / config.minimum_page_size; + let virt_page_cap_count = initial_task_virt_region.size() / config.minimum_page_size; + let page_cap_count = match config.arch { + Arch::Aarch64 | Arch::Riscv64 => virt_page_cap_count, + // On x84_64 we have one more frame for the extra bootinfo region. + Arch::X86_64 => virt_page_cap_count + 1, + }; let first_untyped_cap = fixed_cap_count + paging_cap_count + sched_control_cap_count + page_cap_count; - let sched_control_cap = fixed_cap_count + paging_cap_count; + let sched_control_cap = match config.arch { + Arch::Aarch64 | Arch::Riscv64 => fixed_cap_count + paging_cap_count, + // On x86_64 the schedcontrol caps are created before the paging caps. + Arch::X86_64 => fixed_cap_count, + }; let max_bits = match config.arch { Arch::Aarch64 => 47, Arch::Riscv64 => 38, + Arch::X86_64 => 47, }; + let kernel_virtual_base = config.kernel_virtual_base(); let device_regions: Vec = [ - reserved_region.aligned_power_of_two_regions(max_bits), - device_memory.aligned_power_of_two_regions(max_bits), + reserved_region.aligned_power_of_two_regions(kernel_virtual_base, max_bits), + device_memory.aligned_power_of_two_regions(kernel_virtual_base, max_bits), ] .concat(); let normal_regions: Vec = [ - boot_region.aligned_power_of_two_regions(max_bits), - normal_memory.aligned_power_of_two_regions(max_bits), + boot_region.aligned_power_of_two_regions(kernel_virtual_base, max_bits), + normal_memory.aligned_power_of_two_regions(kernel_virtual_base, max_bits), ] .concat(); let mut untyped_objects = Vec::new(); @@ -882,6 +1022,7 @@ fn build_system( system: &SystemDescription, invocation_table_size: u64, system_cnode_size: u64, + x86_machine: &Option, ) -> Result { assert!(util::is_power_of_two(system_cnode_size)); assert!(invocation_table_size % config.minimum_page_size == 0); @@ -920,19 +1061,39 @@ fn build_system( // Now that the size is determined, find a free region in the physical memory // space. let (mut available_memory, kernel_boot_region) = - emulate_kernel_boot_partial(config, kernel_elf); - - // The kernel relies on the reserved region being allocated above the kernel - // boot/ELF region, so we have the end of the kernel boot region as the lower - // bound for allocating the reserved region. - let reserved_base = available_memory.allocate_from(reserved_size, kernel_boot_region.end); - assert!(kernel_boot_region.base < reserved_base); - // The kernel relies on the initial task being allocated above the reserved - // region, so we have the address of the end of the reserved region as the - // lower bound for allocating the initial task. - let initial_task_phys_base = - available_memory.allocate_from(initial_task_size, reserved_base + reserved_size); - assert!(reserved_base < initial_task_phys_base); + emulate_kernel_boot_partial(config, kernel_elf, x86_machine); + + let (reserved_base, initial_task_phys_base) = match config.arch { + Arch::Aarch64 | Arch::Riscv64 => { + // The kernel relies on the reserved region being allocated above the kernel + // boot/ELF region, so we have the end of the kernel boot region as the lower + // bound for allocating the reserved region. + let reserved_base = available_memory.allocate_from(reserved_size, kernel_boot_region.end); + assert!(kernel_boot_region.base < reserved_base); + // The kernel relies on the initial task being allocated above the reserved + // region, so we have the address of the end of the reserved region as the + // lower bound for allocating the initial task. + let initial_task_phys_base = + available_memory.allocate_from(initial_task_size, reserved_base + reserved_size); + assert!(reserved_base < initial_task_phys_base); + (reserved_base, initial_task_phys_base) + } + Arch::X86_64 => { + // On x86 the kernel loads the inittask at the end of the boot/ELF + // region, so we have the end of the kernel boot region as the lower + // bound for allocating the initial task. + let initial_task_phys_base = + available_memory.allocate_from(initial_task_size, kernel_boot_region.end); + assert!(kernel_boot_region.base < initial_task_phys_base); + // On x86 the kernel relies on the reserved region being allocated above + // the inittask region, so we have the address of the end of the inittask + // region as the lower bound for allocating the reserved region. + let reserved_base = + available_memory.allocate_from(reserved_size, initial_task_phys_base + initial_task_size); + assert!(initial_task_phys_base < reserved_base); + (reserved_base, initial_task_phys_base) + } + }; let initial_task_phys_region = MemoryRegion::new( initial_task_phys_base, @@ -957,6 +1118,7 @@ fn build_system( initial_task_phys_region, initial_task_virt_region, reserved_region, + x86_machine, ); for ut in &kernel_boot_info.untyped_objects { @@ -1225,6 +1387,7 @@ fn build_system( let bootstrap_pt_attr = match config.arch { Arch::Aarch64 => ArmVmAttributes::default(), Arch::Riscv64 => RiscvVmAttributes::default(), + Arch::X86_64 => X86VmAttributes::default(), }; let mut pt_map_invocation = Invocation::new( config, @@ -1251,6 +1414,7 @@ fn build_system( let bootstrap_page_attr = match config.arch { Arch::Aarch64 => ArmVmAttributes::default() | ArmVmAttributes::ExecuteNever as u64, Arch::Riscv64 => RiscvVmAttributes::default() | RiscvVmAttributes::ExecuteNever as u64, + Arch::X86_64 => X86VmAttributes::default(), }; let mut map_invocation = Invocation::new( config, @@ -1661,6 +1825,9 @@ fn build_system( } } Arch::Riscv64 => {} + Arch::X86_64 => { + upper_directory_vaddrs.insert(util::mask_bits(vaddr, 12 + 9 + 9 + 9)); + } } directory_vaddrs.insert(util::mask_bits(vaddr, 12 + 9 + 9)); @@ -1769,33 +1936,70 @@ fn build_system( let pd_vspace_objs = &vspace_objs[..system.protection_domains.len()]; let vm_vspace_objs = &vspace_objs[system.protection_domains.len()..]; - let pd_ud_names: Vec = all_pd_uds - .iter() - .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) - .collect(); - let vm_ud_names: Vec = all_vm_uds - .iter() - .map(|(vm_idx, vaddr)| format!("PageTable: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) - .collect(); - - let pd_ud_objs = init_system.allocate_objects(ObjectType::PageTable, pd_ud_names, None); - let vm_ud_objs = init_system.allocate_objects(ObjectType::PageTable, vm_ud_names, None); - - if !config.hypervisor { - assert!(vm_ud_objs.is_empty()); - } + // Page upper directories + let (pd_ud_objs, vm_ud_objs) = match config.arch { + Arch::Aarch64 | Arch::Riscv64 => { + let pd_ud_names: Vec = all_pd_uds + .iter() + .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) + .collect(); + let vm_ud_names: Vec = all_vm_uds + .iter() + .map(|(vm_idx, vaddr)| format!("PageTable: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) + .collect(); + let pd_ud_objs = init_system.allocate_objects(ObjectType::PageTable, pd_ud_names, None); + let vm_ud_objs = init_system.allocate_objects(ObjectType::PageTable, vm_ud_names, None); + if !config.hypervisor { + assert!(vm_ud_objs.is_empty()); + } + (pd_ud_objs, vm_ud_objs) + } + Arch::X86_64 => { + let pd_ud_names: Vec = all_pd_uds + .iter() + .map(|(pd_idx, vaddr)| format!("PdPt: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) + .collect(); + let vm_ud_names: Vec = all_vm_uds + .iter() + .map(|(vm_idx, vaddr)| format!("PdPt: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) + .collect(); + let pd_ud_objs = init_system.allocate_objects(ObjectType::PdPt, pd_ud_names, None); + let vm_ud_objs = init_system.allocate_objects(ObjectType::PdPt, vm_ud_names, None); + (pd_ud_objs, vm_ud_objs) + } + }; - let pd_d_names: Vec = all_pd_ds - .iter() - .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) - .collect(); - let vm_d_names: Vec = all_vm_ds - .iter() - .map(|(vm_idx, vaddr)| format!("PageTable: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) - .collect(); - let pd_d_objs = init_system.allocate_objects(ObjectType::PageTable, pd_d_names, None); - let vm_d_objs = init_system.allocate_objects(ObjectType::PageTable, vm_d_names, None); + // Page directories + let (pd_d_objs, vm_d_objs) = match config.arch { + Arch::Aarch64 | Arch::Riscv64 => { + let pd_d_names: Vec = all_pd_ds + .iter() + .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) + .collect(); + let vm_d_names: Vec = all_vm_ds + .iter() + .map(|(vm_idx, vaddr)| format!("PageTable: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) + .collect(); + let pd_d_objs = init_system.allocate_objects(ObjectType::PageTable, pd_d_names, None); + let vm_d_objs = init_system.allocate_objects(ObjectType::PageTable, vm_d_names, None); + (pd_d_objs, vm_d_objs) + } + Arch::X86_64 => { + let pd_d_names: Vec = all_pd_ds + .iter() + .map(|(pd_idx, vaddr)| format!("PageDirectory: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) + .collect(); + let vm_d_names: Vec = all_vm_ds + .iter() + .map(|(vm_idx, vaddr)| format!("PageDirectory: VM={} VADDR=0x{:x}", vm_names[*vm_idx], vaddr)) + .collect(); + let pd_d_objs = init_system.allocate_objects(ObjectType::PageDirectory, pd_d_names, None); + let vm_d_objs = init_system.allocate_objects(ObjectType::PageDirectory, vm_d_names, None); + (pd_d_objs, vm_d_objs) + } + }; + // Page tables let pd_pt_names: Vec = all_pd_pts .iter() .map(|(pd_idx, vaddr)| format!("PageTable: PD={} VADDR=0x{:x}", pd_names[*pd_idx], vaddr)) @@ -1839,20 +2043,52 @@ fn build_system( irq_cap_addresses.insert(pd, vec![]); for sysirq in &pd.irqs { let cap_address = system_cap_address_mask | cap_slot; - system_invocations.push(Invocation::new( - config, - InvocationArgs::IrqControlGetTrigger { - irq_control: IRQ_CONTROL_CAP_ADDRESS, - irq: sysirq.irq, - trigger: sysirq.trigger, - dest_root: root_cnode_cap, - dest_index: cap_address, - dest_depth: config.cap_address_bits, - }, - )); + let (invocation, description) = match sysirq.kind { + SysIrqKind::Simple { irq, trigger } => ( + InvocationArgs::IrqControlGetTrigger { + irq_control: IRQ_CONTROL_CAP_ADDRESS, + irq: irq, + trigger: trigger, + dest_root: root_cnode_cap, + dest_index: cap_address, + dest_depth: config.cap_address_bits, + }, + format!("IRQ Handler: irq={}", irq), + ), + SysIrqKind::IOAPIC { ioapic, pin, level, polarity, vector } => ( + InvocationArgs::IrqControlGetIOAPIC { + irq_control: IRQ_CONTROL_CAP_ADDRESS, + dest_root: root_cnode_cap, + dest_index: cap_address, + dest_depth: config.cap_address_bits, + ioapic: ioapic, + pin: pin, + level: level, + polarity: polarity, + vector: vector, + }, + format!("IRQ Handler: ioapic={} pin={}", ioapic, pin), + ), + SysIrqKind::MSI { pci_bus, pci_dev, pci_func, handle, vector } => ( + InvocationArgs::IrqControlGetMSI { + irq_control: IRQ_CONTROL_CAP_ADDRESS, + dest_root: root_cnode_cap, + dest_index: cap_address, + dest_depth: config.cap_address_bits, + pci_bus: pci_bus, + pci_dev: pci_dev, + pci_func: pci_func, + handle: handle, + vector: vector, + }, + format!("IRQ Handler: pcidev={:02x}:{:02x}:{:02x}", pci_bus, pci_dev, pci_func), + ), + }; + + system_invocations.push(Invocation::new(config, invocation)); cap_slot += 1; - cap_address_names.insert(cap_address, format!("IRQ Handler: irq={}", sysirq.irq)); + cap_address_names.insert(cap_address, description); irq_cap_addresses.get_mut(pd).unwrap().push(cap_address); } } @@ -1888,6 +2124,7 @@ fn build_system( let mut attrs = match config.arch { Arch::Aarch64 => ArmVmAttributes::ParityEnabled as u64, Arch::Riscv64 => 0, + Arch::X86_64 => 0, }; if mp.perms & SysMapPerms::Read as u8 != 0 { rights |= Rights::Read as u64; @@ -1899,12 +2136,20 @@ fn build_system( match config.arch { Arch::Aarch64 => attrs |= ArmVmAttributes::ExecuteNever as u64, Arch::Riscv64 => attrs |= RiscvVmAttributes::ExecuteNever as u64, + Arch::X86_64 => {}, } } if mp.cached { match config.arch { Arch::Aarch64 => attrs |= ArmVmAttributes::Cacheable as u64, Arch::Riscv64 => {} + Arch::X86_64 => {} + } + } else { + match config.arch { + Arch::Aarch64 => {} + Arch::Riscv64 => {} + Arch::X86_64 => attrs |= X86VmAttributes::CacheDisable as u64, } } @@ -1974,6 +2219,7 @@ fn build_system( let mut attrs = match config.arch { Arch::Aarch64 => ArmVmAttributes::ParityEnabled as u64, Arch::Riscv64 => 0, + Arch::X86_64 => 0, }; if mp.perms & SysMapPerms::Read as u8 != 0 { rights |= Rights::Read as u64; @@ -1985,12 +2231,20 @@ fn build_system( match config.arch { Arch::Aarch64 => attrs |= ArmVmAttributes::ExecuteNever as u64, Arch::Riscv64 => attrs |= RiscvVmAttributes::ExecuteNever as u64, + Arch::X86_64 => {}, } } if mp.cached { match config.arch { Arch::Aarch64 => attrs |= ArmVmAttributes::Cacheable as u64, - Arch::Riscv64 => {} + Arch::Riscv64 => {}, + Arch::X86_64 => {}, + } + } else { + match config.arch { + Arch::Aarch64 => {}, + Arch::Riscv64 => {}, + Arch::X86_64 => attrs |= X86VmAttributes::CacheDisable as u64, } } @@ -2459,21 +2713,48 @@ fn build_system( // Initialise the VSpaces -- assign them all the the initial asid pool. let pd_vspace_invocations = [ - (all_pd_uds, pd_ud_objs), - (all_pd_ds, pd_d_objs), - (all_pd_pts, pd_pt_objs), + (3, all_pd_uds, pd_ud_objs), + (2, all_pd_ds, pd_d_objs), + (1, all_pd_pts, pd_pt_objs), ]; - for (descriptors, objects) in pd_vspace_invocations { + for (level, descriptors, objects) in pd_vspace_invocations { for ((pd_idx, vaddr), obj) in zip(descriptors, objects) { - system_invocations.push(Invocation::new( - config, - InvocationArgs::PageTableMap { - page_table: obj.cap_addr, - vspace: pd_vspace_objs[pd_idx].cap_addr, - vaddr, - attr: default_vm_attr(config), - }, - )); + match level { + 1 => { + system_invocations.push(Invocation::new( + config, + InvocationArgs::PageTableMap { + page_table: obj.cap_addr, + vspace: pd_vspace_objs[pd_idx].cap_addr, + vaddr, + attr: default_vm_attr(config), + }, + )); + } + 2 => { + system_invocations.push(Invocation::new( + config, + InvocationArgs::PageDirectoryMap { + page_directory: obj.cap_addr, + vspace: pd_vspace_objs[pd_idx].cap_addr, + vaddr, + attr: default_vm_attr(config), + }, + )); + } + 3 => { + system_invocations.push(Invocation::new( + config, + InvocationArgs::PageUpperDirectoryMap { + page_upper_directory: obj.cap_addr, + vspace: pd_vspace_objs[pd_idx].cap_addr, + vaddr, + attr: default_vm_attr(config), + }, + )); + } + _ => panic!(), + } } } @@ -2484,21 +2765,48 @@ fn build_system( } let vm_vspace_invocations = [ - (all_vm_uds, vm_ud_objs), - (all_vm_ds, vm_d_objs), - (all_vm_pts, vm_pt_objs), + (3, all_vm_uds, vm_ud_objs), + (2, all_vm_ds, vm_d_objs), + (1, all_vm_pts, vm_pt_objs), ]; - for (descriptors, objects) in vm_vspace_invocations { + for (level, descriptors, objects) in vm_vspace_invocations { for ((vm_idx, vaddr), obj) in zip(descriptors, objects) { - system_invocations.push(Invocation::new( - config, - InvocationArgs::PageTableMap { - page_table: obj.cap_addr, - vspace: vm_vspace_objs[vm_idx].cap_addr, - vaddr, - attr: default_vm_attr(config), - }, - )); + match level { + 1 => { + system_invocations.push(Invocation::new( + config, + InvocationArgs::PageTableMap { + page_table: obj.cap_addr, + vspace: vm_vspace_objs[vm_idx].cap_addr, + vaddr, + attr: default_vm_attr(config), + }, + )); + } + 2 => { + system_invocations.push(Invocation::new( + config, + InvocationArgs::PageDirectoryMap { + page_directory: obj.cap_addr, + vspace: vm_vspace_objs[vm_idx].cap_addr, + vaddr, + attr: default_vm_attr(config), + }, + )); + } + 3 => { + system_invocations.push(Invocation::new( + config, + InvocationArgs::PageUpperDirectoryMap { + page_upper_directory: obj.cap_addr, + vspace: vm_vspace_objs[vm_idx].cap_addr, + vaddr, + attr: default_vm_attr(config), + }, + )); + } + _ => panic!(), + } } } @@ -2554,6 +2862,7 @@ fn build_system( let ipc_buffer_attr = match config.arch { Arch::Aarch64 => ArmVmAttributes::default() | ArmVmAttributes::ExecuteNever as u64, Arch::Riscv64 => RiscvVmAttributes::default() | RiscvVmAttributes::ExecuteNever as u64, + Arch::X86_64 => X86VmAttributes::default(), }; for pd_idx in 0..system.protection_domains.len() { let (vaddr, _) = pd_elf_files[pd_idx] @@ -2749,6 +3058,12 @@ fn build_system( ..Default::default() } .field_names(), + Arch::X86_64 => X86_64Regs { + rip: pd_elf_files[pd_idx].entry, + rsp: config.pd_stack_top(), + ..Default::default() + } + .field_names(), }; system_invocations.push(Invocation::new( @@ -2996,6 +3311,7 @@ struct Args<'a> { report: &'a str, output: &'a str, search_paths: Vec<&'a String>, + x86_machine: Option<&'a str>, } impl<'a> Args<'a> { @@ -3008,6 +3324,7 @@ impl<'a> Args<'a> { let mut system = None; let mut board = None; let mut config = None; + let mut x86_machine = None; if args.len() <= 1 { print_usage(available_boards); @@ -3066,6 +3383,15 @@ impl<'a> Args<'a> { "--search-path" => { in_search_path = true; } + "--x86-machine" => { + if i < args.len() - 1 { + x86_machine = Some(args[i + 1].as_str()); + i += 1; + } else { + eprintln!("microkit: error: argument --x86-machine: expected one argument"); + std::process::exit(1); + } + } _ => { if in_search_path { search_paths.push(&args[i]); @@ -3118,6 +3444,7 @@ impl<'a> Args<'a> { report, output, search_paths, + x86_machine: x86_machine, } } } @@ -3283,13 +3610,14 @@ fn main() -> Result<(), String> { let arch = match json_str(&kernel_config_json, "SEL4_ARCH")? { "aarch64" => Arch::Aarch64, "riscv64" => Arch::Riscv64, + "x86_64" => Arch::X86_64, _ => panic!("Unsupported kernel config architecture"), }; let hypervisor = match arch { Arch::Aarch64 => json_str_as_bool(&kernel_config_json, "ARM_HYPERVISOR_SUPPORT")?, - // Hypervisor mode is not available on RISC-V - Arch::Riscv64 => false, + // Hypervisor mode is not available on RISC-V or X86 + Arch::Riscv64 | Arch::X86_64 => false, }; let arm_pa_size_bits = match arch { @@ -3302,7 +3630,7 @@ fn main() -> Result<(), String> { panic!("Expected ARM platform to have 40 or 44 physical address bits") } } - Arch::Riscv64 => None, + Arch::Riscv64 | Arch::X86_64 => None, }; let arm_smc = match arch { @@ -3313,6 +3641,22 @@ fn main() -> Result<(), String> { let kernel_frame_size = match arch { Arch::Aarch64 => 1 << 12, Arch::Riscv64 => 1 << 21, + Arch::X86_64 => 1 << 12, + }; + + let x86_machine: Option = match arch { + Arch::X86_64 => { + match args.x86_machine { + Some(path) => Some(serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap()), + None => panic!("Tool argument --x86-machine is mandatory on x86"), + } + }, + _ => { + match args.x86_machine { + Some(_) => panic!("Tool argument --x86-machine is only valid for x86"), + None => None, + } + } }; let kernel_config = Config { @@ -3331,6 +3675,7 @@ fn main() -> Result<(), String> { arm_smc, riscv_pt_levels: Some(RiscvVirtualMemory::Sv39), invocations_labels, + x86_xsave_size: json_str_as_u64(&kernel_config_json, "XSAVE_SIZE").ok(), }; if let Arch::Aarch64 = kernel_config.arch { @@ -3411,6 +3756,7 @@ fn main() -> Result<(), String> { &system, invocation_table_size, system_cnode_size, + &x86_machine, )?; println!("BUILT: system_cnode_size={} built_system.number_of_system_caps={} invocation_table_size={} built_system.invocation_data_size={}", system_cnode_size, built_system.number_of_system_caps, invocation_table_size, built_system.invocation_data_size); @@ -3617,16 +3963,29 @@ fn main() -> Result<(), String> { } } - let loader = Loader::new( - &kernel_config, - Path::new(&loader_elf_path), - &kernel_elf, - &monitor_elf, - Some(built_system.initial_task_phys_region.base), - built_system.reserved_region, - loader_regions, - ); - loader.write_image(Path::new(args.output)); + if let Arch::X86_64 = kernel_config.arch { + let loader = X86Loader::new( + &kernel_config, + Path::new(&loader_elf_path), + &kernel_elf, + &monitor_elf, + Some(built_system.initial_task_phys_region.base), + built_system.reserved_region, + loader_regions, + ); + loader.write_image(Path::new(args.output)).unwrap(); + } else { + let loader = Loader::new( + &kernel_config, + Path::new(&loader_elf_path), + &kernel_elf, + &monitor_elf, + Some(built_system.initial_task_phys_region.base), + built_system.reserved_region, + loader_regions, + ); + loader.write_image(Path::new(args.output)); + } Ok(()) } diff --git a/tool/microkit/src/sdf.rs b/tool/microkit/src/sdf.rs index 92b940eb..8b80d71f 100644 --- a/tool/microkit/src/sdf.rs +++ b/tool/microkit/src/sdf.rs @@ -121,11 +121,33 @@ impl SysMemoryRegion { } } +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum SysIrqKind { + // Please someone find a better name than "Simple" + Simple { + irq: u64, + trigger: IrqTrigger, + }, + IOAPIC { + ioapic: u64, + pin: u64, + level: u64, + polarity: u64, + vector: u64, + }, + MSI { + pci_bus: u64, + pci_dev: u64, + pci_func: u64, + handle: u64, + vector: u64, + }, +} + #[derive(Debug, PartialEq, Eq, Hash)] pub struct SysIrq { - pub irq: u64, pub id: u64, - pub trigger: IrqTrigger, + pub kind: SysIrqKind, } #[derive(Debug, PartialEq, Eq, Hash)] @@ -521,10 +543,6 @@ impl ProtectionDomain { maps.push(map); } "irq" => { - check_attributes(xml_sdf, &child, &["irq", "id", "trigger"])?; - let irq = checked_lookup(xml_sdf, &child, "irq")? - .parse::() - .unwrap(); let id = checked_lookup(xml_sdf, &child, "id")? .parse::() .unwrap(); @@ -539,29 +557,116 @@ impl ProtectionDomain { return Err(value_error(xml_sdf, &child, "id must be >= 0".to_string())); } - let trigger = if let Some(trigger_str) = child.attribute("trigger") { - match trigger_str { - "level" => IrqTrigger::Level, - "edge" => IrqTrigger::Edge, - _ => { - return Err(value_error( - xml_sdf, - &child, - "trigger must be either 'level' or 'edge'".to_string(), - )) + if let Some(irq_str) = child.attribute("irq") { + // ARM and RISC-V interrupts must have an "irq" attribute. + check_attributes(xml_sdf, &child, &["irq", "id", "trigger"])?; + let irq = irq_str.parse::().unwrap(); + let trigger = if let Some(trigger_str) = child.attribute("trigger") { + match trigger_str { + "level" => IrqTrigger::Level, + "edge" => IrqTrigger::Edge, + _ => { + return Err(value_error( + xml_sdf, + &child, + "trigger must be either 'level' or 'edge'".to_string(), + )) + } } + } else { + // Default to level triggered + IrqTrigger::Level + }; + let irq = SysIrq { + id: id as u64, + kind: SysIrqKind::Simple { + irq: irq, + trigger: trigger, + }, + }; + irqs.push(irq); + } else if let Some(pin_str) = child.attribute("pin") { + // IOAPIC interrupts (X86_64) must have a "pin" attribute. + check_attributes(xml_sdf, &child, &["id", "ioapic", "pin", "level", "polarity", "vector"])?; + let ioapic = if let Some(ioapic_str) = child.attribute("ioapic") { + ioapic_str.parse::().unwrap() + } else { + // Default to the first unit. + 0 + }; + let pin = pin_str.parse::().unwrap(); + let level = if let Some(level_str) = child.attribute("level") { + level_str.parse::().unwrap() + } else { + // Default to level trigger. + 1 + }; + let polarity = if let Some(polarity_str) = child.attribute("polarity") { + polarity_str.parse::().unwrap() + } else { + // Default to normal polarity + 1 + }; + let vector = checked_lookup(xml_sdf, &child, "vector")? + .parse::() + .unwrap(); + let irq = SysIrq { + id: id as u64, + kind: SysIrqKind::IOAPIC { + ioapic: ioapic, + pin: pin, + level: level, + polarity: polarity, + vector: vector, + }, + }; + irqs.push(irq); + } else if let Some(pcidev_str) = child.attribute("pcidev") { + // MSI interrupts (X86_64) have a "pcidev" attribute. + check_attributes(xml_sdf, &child, &["id", "pcidev", "handle", "vector"])?; + + let pciparts: Vec = pcidev_str.split(|c: char| c == ':' || c == '.') + .map(str::trim) + .map(|x| u64::from_str_radix(x, 16) + .expect("Error: Failed to parse parts of the PCI device address") + ) + .collect(); + if pciparts.len() != 3 { + return Err(format!( + "Error: failed to parse PCI address '{}' on element '{}'", + pcidev_str, + child.tag_name().name())); } + let handle = checked_lookup(xml_sdf, &child, "handle")? + .parse::() + .unwrap(); + let vector = checked_lookup(xml_sdf, &child, "vector")? + .parse::() + .unwrap(); + let irq = SysIrq { + id: id as u64, + kind: SysIrqKind::MSI { + pci_bus: pciparts[0], + pci_dev: pciparts[1], + pci_func: pciparts[2], + handle: handle, + vector: vector, + }, + }; + irqs.push(irq); } else { - // Default the level triggered - IrqTrigger::Level - }; - - let irq = SysIrq { - irq, - id: id as u64, - trigger, - }; - irqs.push(irq); + // We can't figure out what type interrupt is specified. + let pos = xml_sdf.doc.text_pos_at(child.range().start); + return Err(value_error( + xml_sdf, + &child, + format!("Missing required attribute 'irq', or 'pin' (x86 IOAPIC), or 'pcidev' (x86 MSI) on element '{}': {}:{}:{}", + child.tag_name().name(), + xml_sdf.filename, + pos.row, + pos.col) + )); + } } "setvar" => { check_attributes(xml_sdf, &child, &["symbol", "region_paddr"])?; @@ -1259,16 +1364,20 @@ pub fn parse(filename: &str, xml: &str, config: &Config) -> Result { + if all_irqs.contains(&irq) { + return Err(format!( + "Error: duplicate irq: {} in protection domain: '{}' @ {}:{}:{}", + irq, pd.name, filename, pd.text_pos.row, pd.text_pos.col + )); + } + all_irqs.push(irq); + } + _ => {}, } - all_irqs.push(sysirq.irq); } } - // Ensure no duplicate channel identifiers. // This means checking that no interrupt IDs clash with any channel IDs let mut ch_ids = vec![vec![]; pds.len()]; diff --git a/tool/microkit/src/sel4.rs b/tool/microkit/src/sel4.rs index 314c6272..67bb9dc2 100644 --- a/tool/microkit/src/sel4.rs +++ b/tool/microkit/src/sel4.rs @@ -57,6 +57,7 @@ pub struct Config { /// RISC-V specific, what kind of virtual memory system (e.g Sv39) pub riscv_pt_levels: Option, pub invocations_labels: serde_json::Value, + pub x86_xsave_size: Option, } impl Config { @@ -71,12 +72,30 @@ impl Config { false => 0x800000000000, }, Arch::Riscv64 => 0x0000003ffffff000, + // On x86 USER_TOP is really 0x7fffffffffff but since it + // isn't a very nicely aligned address we round this down. + // This way stack pages can be allocated there and the + // world is at peace, at the cost of one wasted page. + Arch::X86_64 => 0x7ffffffff000, + } + } + + pub fn kernel_virtual_base(&self) -> u64 { + match self.arch { + Arch::Aarch64 => match self.hypervisor { + true => 0x0000008000000000, + false => u64::pow(2, 64) - u64::pow(2, 39), + } + Arch::Riscv64 => match self.riscv_pt_levels.unwrap() { + RiscvVirtualMemory::Sv39 => u64::pow(2, 64) - u64::pow(2,38), + } + Arch::X86_64 => u64::pow(2, 64) - u64::pow(2,39), } } pub fn page_sizes(&self) -> [u64; 2] { match self.arch { - Arch::Aarch64 | Arch::Riscv64 => [0x1000, 0x200_000], + Arch::Aarch64 | Arch::Riscv64 | Arch::X86_64=> [0x1000, 0x200_000], } } @@ -110,6 +129,7 @@ impl Config { pub enum Arch { Aarch64, Riscv64, + X86_64, } /// RISC-V supports multiple virtual memory systems and so we use this enum @@ -143,6 +163,14 @@ pub enum ObjectType { LargePage, PageTable, Vcpu, + PageDirectory, + PdPt, + Pml4, + IOPageTable, + EptPml4, + EptPdPt, + EptPageDirectory, + EptPageTable, } impl ObjectType { @@ -156,6 +184,14 @@ impl ObjectType { true => Some(11), false => Some(10), }, + Arch::X86_64 => match config.x86_xsave_size { + Some(size) => if size >= 832 { + Some(12) + } else { + Some(11) + } + None => Some(11) + } }, ObjectType::Endpoint => Some(4), ObjectType::Notification => Some(6), @@ -172,6 +208,7 @@ impl ObjectType { false => Some(12), }, Arch::Riscv64 => Some(12), + Arch::X86_64 => Some(12), }, ObjectType::PageTable => Some(12), ObjectType::HugePage => Some(30), @@ -179,8 +216,17 @@ impl ObjectType { ObjectType::SmallPage => Some(12), ObjectType::Vcpu => match config.arch { Arch::Aarch64 => Some(12), + Arch::X86_64 => Some(14), _ => panic!("Unexpected architecture asking for vCPU size bits"), }, + ObjectType::PageDirectory => Some(12), + ObjectType::PdPt => Some(12), + ObjectType::Pml4 => Some(12), + ObjectType::IOPageTable => Some(12), + ObjectType::EptPml4 => Some(12), + ObjectType::EptPdPt => Some(12), + ObjectType::EptPageDirectory => Some(12), + ObjectType::EptPageTable => Some(12), _ => None, } } @@ -204,6 +250,14 @@ impl ObjectType { ObjectType::LargePage => "SEL4_LARGE_PAGE_OBJECT", ObjectType::PageTable => "SEL4_PAGE_TABLE_OBJECT", ObjectType::Vcpu => "SEL4_VCPU_OBJECT", + ObjectType::PageDirectory => "SEL4_PAGE_DIRECTORY_OBJECT", + ObjectType::PdPt => "SEL4_PDPT_OBJECT", + ObjectType::Pml4 => "SEL4_PML4_OBJECT", + ObjectType::IOPageTable => "SEL4_IO_PAGE_TABLE_OBJECT", + ObjectType::EptPml4 => "SEL4_EPT_PML4_OBJECT", + ObjectType::EptPdPt => "SEL4_EPT_PDPT_OBJECT", + ObjectType::EptPageDirectory => "SEL4_EPT_PAGE_DIRECTORY_OBJECT", + ObjectType::EptPageTable => "SEL4_EPT_PAGE_TABLE_OBJECT", } } @@ -220,27 +274,44 @@ impl ObjectType { ObjectType::CNode => 4, ObjectType::SchedContext => 5, ObjectType::Reply => 6, - ObjectType::HugePage => 7, + ObjectType::HugePage => match config.arch { + Arch::Aarch64 => 7, + Arch::Riscv64 => 7, + Arch::X86_64 => 9, + }, ObjectType::VSpace => match config.arch { Arch::Aarch64 => 8, Arch::Riscv64 => 10, + Arch::X86_64 => 8, }, ObjectType::SmallPage => match config.arch { Arch::Aarch64 => 9, Arch::Riscv64 => 8, + Arch::X86_64 => 10, }, ObjectType::LargePage => match config.arch { Arch::Aarch64 => 10, Arch::Riscv64 => 9, + Arch::X86_64 => 11, }, ObjectType::PageTable => match config.arch { Arch::Aarch64 => 11, Arch::Riscv64 => 10, + Arch::X86_64 => 12, }, ObjectType::Vcpu => match config.arch { Arch::Aarch64 => 12, + Arch::X86_64 => 15, _ => panic!("Unknown vCPU object type value for given kernel config"), }, + ObjectType::PdPt => 7, + ObjectType::Pml4 => 8, + ObjectType::PageDirectory => 13, + ObjectType::IOPageTable => 14, + ObjectType::EptPml4 => 16, + ObjectType::EptPdPt => 17, + ObjectType::EptPageDirectory => 18, + ObjectType::EptPageTable => 19, } } @@ -294,6 +365,16 @@ pub enum RiscvVmAttributes { ExecuteNever = 1, } +/// Virtual memory attributes for X86 +/// The values for each enum variant corresponds to what seL4 +/// expects when doing a virtual memory invocation. +#[repr(u64)] +pub enum X86VmAttributes { + PageAttributeTable = 1, // x86PATBit + CacheDisable = 2, // x86PCDBit + WriteThrough = 4, // x86PWTBit +} + impl ArmVmAttributes { #[allow(clippy::should_implement_trait)] // Default::default would return Self, not u64 pub fn default() -> u64 { @@ -308,10 +389,18 @@ impl RiscvVmAttributes { } } +impl X86VmAttributes { + #[allow(clippy::should_implement_trait)] // Default::default would return Self, not u64 + pub fn default() -> u64 { + 0 + } +} + pub fn default_vm_attr(config: &Config) -> u64 { match config.arch { Arch::Aarch64 => ArmVmAttributes::default(), Arch::Riscv64 => RiscvVmAttributes::default(), + Arch::X86_64 => X86VmAttributes::default(), } } @@ -421,6 +510,58 @@ enum InvocationLabel { RISCVASIDPoolAssign, // RISC-V IRQ RISCVIRQIssueIRQHandlerTrigger, + // X86 PDPT + X86PDPTMap, + X86PDPTUnmap, + // X86 Page Directory + X86PageDirectoryMap, + X86PageDirectoryUnmap, + // X86 Page Table + X86PageTableMap, + X86PageTableUnmap, + // X86 IO Page + X86IOPageTableMap, + X86IOPageTableUnmap, + // X86 Page + X86PageMap, + X86PageUnmap, + X86PageMapIO, + X86PageGetAddress, + X86PageMapEPT, + // X86 ASID + X86ASIDControlMakePool, + // X86 ASID Pool + X86ASIDPoolAssign, + // X86 IO Port Control + X86IOPortControlIssue, + // X86 IO PORT + X86IOPortIn8, + X86IOPortIn16, + X86IOPortIn32, + X86IOPortOut8, + X86IOPortOut16, + X86IOPortOut32, + // X86 IRQ + X86IRQIssueIRQHandlerIOAPIC, + X86IRQIssueIRQHandlerMSI, + // X86 TCB + TCBSetEPTRoot, + // X86 VCPU + X86VCPUSetTCB, + X86VCPUReadVMCS, + X86VCPUWriteVMCS, + X86VCPUEnableIOPort, + X86VCPUDisableIOPort, + X86VCPUWriteRegisters, + // X86 EPTPDPT + X86EPTPDPTMap, + X86EPTPDPTUnmap, + // X86 EPTPD + X86EPTPDMap, + X86EPTPDUnmap, + // X86 EPTPT + X86EPTPTMap, + X86EPTPTUnmap, } impl std::fmt::Display for InvocationLabel { @@ -517,6 +658,86 @@ impl Riscv64Regs { pub const LEN: usize = 32; } +#[derive(Copy, Clone, Default)] +#[allow(dead_code)] +pub struct X86_64Regs { + pub rip: u64, + pub rsp: u64, + pub rflags: u64, + pub rax: u64, + pub rbx: u64, + pub rcx: u64, + pub rdx: u64, + pub rsi: u64, + pub rdi: u64, + pub rbp: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + pub fs_base: u64, + pub gs_base: u64, +} + +impl X86_64Regs { + pub fn field_names(&self) -> Vec<(&'static str, u64)> { + vec![ + ("rip", self.rip), + ("rsp", self.rsp), + ("rflags", self.rflags), + ("rax", self.rax), + ("rbx", self.rbx), + ("rcx", self.rcx), + ("rdx", self.rdx), + ("rsi", self.rsi), + ("rdi", self.rdi), + ("rbp", self.rbp), + ("r8", self.r8), + ("r9", self.r9), + ("r10", self.r10), + ("r11", self.r11), + ("r12", self.r12), + ("r13", self.r13), + ("r14", self.r14), + ("r15", self.r15), + ("fs_base", self.fs_base), + ("gs_base", self.gs_base), + ] + } + + pub fn as_slice(&self) -> Vec { + vec![ + self.rip, + self.rsp, + self.rflags, + self.rax, + self.rbx, + self.rcx, + self.rdx, + self.rsi, + self.rdi, + self.rbp, + self.r8, + self.r9, + self.r10, + self.r11, + self.r12, + self.r13, + self.r14, + self.r15, + self.fs_base, + self.gs_base, + ] + } + + /// Number of registers + pub const LEN: usize = 20; +} + #[derive(Copy, Clone, Default)] #[allow(dead_code)] pub struct Aarch64Regs { @@ -923,6 +1144,56 @@ impl Invocation { arg_strs.push(Invocation::fmt_field("dest_depth", dest_depth)); (irq_control, &cap_lookup[&irq_control]) } + InvocationArgs::IrqControlGetIOAPIC { + irq_control, + dest_root, + dest_index, + dest_depth, + ioapic, + pin, + level, + polarity, + vector, + } => { + arg_strs.push(Invocation::fmt_field_cap( + "dest_root", + dest_root, + cap_lookup, + )); + arg_strs.push(Invocation::fmt_field("dest_index", dest_index)); + arg_strs.push(Invocation::fmt_field("dest_depth", dest_depth)); + arg_strs.push(Invocation::fmt_field("ioapic", ioapic)); + arg_strs.push(Invocation::fmt_field("pin", pin)); + arg_strs.push(Invocation::fmt_field("level", level)); + arg_strs.push(Invocation::fmt_field("polarity", polarity)); + arg_strs.push(Invocation::fmt_field("vector", vector)); + (irq_control, &cap_lookup[&irq_control]) + } + InvocationArgs::IrqControlGetMSI { + irq_control, + dest_root, + dest_index, + dest_depth, + pci_bus, + pci_dev, + pci_func, + handle, + vector, + } => { + arg_strs.push(Invocation::fmt_field_cap( + "dest_root", + dest_root, + cap_lookup, + )); + arg_strs.push(Invocation::fmt_field("dest_index", dest_index)); + arg_strs.push(Invocation::fmt_field("dest_depth", dest_depth)); + arg_strs.push(Invocation::fmt_field("pci_bus", pci_bus)); + arg_strs.push(Invocation::fmt_field("pci_dev", pci_dev)); + arg_strs.push(Invocation::fmt_field("pci_func", pci_func)); + arg_strs.push(Invocation::fmt_field("handle", handle)); + arg_strs.push(Invocation::fmt_field("vector", vector)); + (irq_control, &cap_lookup[&irq_control]) + } InvocationArgs::IrqHandlerSetNotification { irq_handler, notification, @@ -934,6 +1205,28 @@ impl Invocation { )); (irq_handler, &cap_lookup[&irq_handler]) } + InvocationArgs::PageUpperDirectoryMap { + page_upper_directory, + vspace, + vaddr, + attr, + } => { + arg_strs.push(Invocation::fmt_field_cap("vspace", vspace, cap_lookup)); + arg_strs.push(Invocation::fmt_field_hex("vaddr", vaddr)); + arg_strs.push(Invocation::fmt_field("attr", attr)); + (page_upper_directory, cap_lookup.get(&page_upper_directory).unwrap()) + } + InvocationArgs::PageDirectoryMap { + page_directory, + vspace, + vaddr, + attr, + } => { + arg_strs.push(Invocation::fmt_field_cap("vspace", vspace, cap_lookup)); + arg_strs.push(Invocation::fmt_field_hex("vaddr", vaddr)); + arg_strs.push(Invocation::fmt_field("attr", attr)); + (page_directory, cap_lookup.get(&page_directory).unwrap()) + } InvocationArgs::PageTableMap { page_table, vspace, @@ -1043,15 +1336,24 @@ impl Invocation { | InvocationLabel::TCBResume | InvocationLabel::TCBWriteRegisters | InvocationLabel::TCBBindNotification => "TCB", - InvocationLabel::ARMASIDPoolAssign | InvocationLabel::RISCVASIDPoolAssign => { - "ASID Pool" - } + InvocationLabel::ARMASIDPoolAssign + | InvocationLabel::RISCVASIDPoolAssign + | InvocationLabel::X86ASIDPoolAssign => "ASID Pool", InvocationLabel::ARMIRQIssueIRQHandlerTrigger - | InvocationLabel::RISCVIRQIssueIRQHandlerTrigger => "IRQ Control", + | InvocationLabel::RISCVIRQIssueIRQHandlerTrigger + | InvocationLabel::X86IRQIssueIRQHandlerIOAPIC + | InvocationLabel::X86IRQIssueIRQHandlerMSI => "IRQ Control", InvocationLabel::IRQSetIRQHandler => "IRQ Handler", - InvocationLabel::ARMPageTableMap | InvocationLabel::RISCVPageTableMap => "Page Table", - InvocationLabel::ARMPageMap | InvocationLabel::RISCVPageMap => "Page", - InvocationLabel::CNodeCopy | InvocationLabel::CNodeMint => "CNode", + InvocationLabel::X86PDPTMap => "Page Upper Directory", + InvocationLabel::X86PageDirectoryMap => "Page Directory", + InvocationLabel::ARMPageTableMap + | InvocationLabel::RISCVPageTableMap + | InvocationLabel::X86PageTableMap => "Page Table", + InvocationLabel::ARMPageMap + | InvocationLabel::RISCVPageMap + | InvocationLabel::X86PageMap => "Page", + InvocationLabel::CNodeCopy + | InvocationLabel::CNodeMint => "CNode", InvocationLabel::SchedControlConfigureFlags => "SchedControl", InvocationLabel::ARMVCPUSetTCB => "VCPU", _ => panic!( @@ -1070,14 +1372,22 @@ impl Invocation { InvocationLabel::TCBResume => "Resume", InvocationLabel::TCBWriteRegisters => "WriteRegisters", InvocationLabel::TCBBindNotification => "BindNotification", - InvocationLabel::ARMASIDPoolAssign | InvocationLabel::RISCVASIDPoolAssign => "Assign", + InvocationLabel::ARMASIDPoolAssign + | InvocationLabel::RISCVASIDPoolAssign + | InvocationLabel::X86ASIDPoolAssign => "Assign", InvocationLabel::ARMIRQIssueIRQHandlerTrigger - | InvocationLabel::RISCVIRQIssueIRQHandlerTrigger => "Get", + | InvocationLabel::RISCVIRQIssueIRQHandlerTrigger + | InvocationLabel::X86IRQIssueIRQHandlerIOAPIC + | InvocationLabel::X86IRQIssueIRQHandlerMSI => "Get", InvocationLabel::IRQSetIRQHandler => "SetNotification", InvocationLabel::ARMPageTableMap | InvocationLabel::ARMPageMap | InvocationLabel::RISCVPageTableMap - | InvocationLabel::RISCVPageMap => "Map", + | InvocationLabel::RISCVPageMap + | InvocationLabel::X86PDPTMap + | InvocationLabel::X86PageDirectoryMap + | InvocationLabel::X86PageTableMap + | InvocationLabel::X86PageMap => "Map", InvocationLabel::CNodeCopy => "Copy", InvocationLabel::CNodeMint => "Mint", InvocationLabel::SchedControlConfigureFlags => "ConfigureFlags", @@ -1103,19 +1413,35 @@ impl InvocationArgs { InvocationArgs::AsidPoolAssign { .. } => match config.arch { Arch::Aarch64 => InvocationLabel::ARMASIDPoolAssign, Arch::Riscv64 => InvocationLabel::RISCVASIDPoolAssign, + Arch::X86_64 => InvocationLabel::X86ASIDPoolAssign, }, InvocationArgs::IrqControlGetTrigger { .. } => match config.arch { Arch::Aarch64 => InvocationLabel::ARMIRQIssueIRQHandlerTrigger, Arch::Riscv64 => InvocationLabel::RISCVIRQIssueIRQHandlerTrigger, + Arch::X86_64 => panic!("IrqControlGetTrigger is not supported on X86") }, + InvocationArgs::IrqControlGetIOAPIC { .. } => InvocationLabel::X86IRQIssueIRQHandlerIOAPIC, + InvocationArgs::IrqControlGetMSI { .. } => InvocationLabel::X86IRQIssueIRQHandlerMSI, InvocationArgs::IrqHandlerSetNotification { .. } => InvocationLabel::IRQSetIRQHandler, + InvocationArgs::PageUpperDirectoryMap { .. } => match config.arch { + Arch::Aarch64 => InvocationLabel::ARMPageTableMap, + Arch::Riscv64 => InvocationLabel::RISCVPageTableMap, + Arch::X86_64 => InvocationLabel::X86PDPTMap, + }, + InvocationArgs::PageDirectoryMap { .. } => match config.arch { + Arch::Aarch64 => InvocationLabel::ARMPageTableMap, + Arch::Riscv64 => InvocationLabel::RISCVPageTableMap, + Arch::X86_64 => InvocationLabel::X86PageDirectoryMap, + }, InvocationArgs::PageTableMap { .. } => match config.arch { Arch::Aarch64 => InvocationLabel::ARMPageTableMap, Arch::Riscv64 => InvocationLabel::RISCVPageTableMap, + Arch::X86_64 => InvocationLabel::X86PageTableMap, }, InvocationArgs::PageMap { .. } => match config.arch { Arch::Aarch64 => InvocationLabel::ARMPageMap, Arch::Riscv64 => InvocationLabel::RISCVPageMap, + Arch::X86_64 => InvocationLabel::X86PageMap, }, InvocationArgs::CnodeCopy { .. } => InvocationLabel::CNodeCopy, InvocationArgs::CnodeMint { .. } => InvocationLabel::CNodeMint, @@ -1216,10 +1542,52 @@ impl InvocationArgs { vec![irq, trigger as u64, dest_index, dest_depth], vec![dest_root], ), + InvocationArgs::IrqControlGetIOAPIC { + irq_control, + dest_root, + dest_index, + dest_depth, + ioapic, + pin, + level, + polarity, + vector, + } => ( + irq_control, + vec![dest_index, dest_depth, ioapic, pin, level, polarity, vector], + vec![dest_root], + ), + InvocationArgs::IrqControlGetMSI { + irq_control, + dest_root, + dest_index, + dest_depth, + pci_bus, + pci_dev, + pci_func, + handle, + vector, + } => ( + irq_control, + vec![dest_index, dest_depth, pci_bus, pci_dev, pci_func, handle, vector], + vec![dest_root], + ), InvocationArgs::IrqHandlerSetNotification { irq_handler, notification, } => (irq_handler, vec![], vec![notification]), + InvocationArgs::PageUpperDirectoryMap { + page_upper_directory, + vspace, + vaddr, + attr, + } => (page_upper_directory, vec![vaddr, attr], vec![vspace]), + InvocationArgs::PageDirectoryMap { + page_directory, + vspace, + vaddr, + attr, + } => (page_directory, vec![vaddr, attr], vec![vspace]), InvocationArgs::PageTableMap { page_table, vspace, @@ -1338,10 +1706,44 @@ pub enum InvocationArgs { dest_index: u64, dest_depth: u64, }, + IrqControlGetIOAPIC { + irq_control: u64, + dest_root: u64, + dest_index: u64, + dest_depth: u64, + ioapic: u64, + pin: u64, + level: u64, + polarity: u64, + vector: u64, + }, + IrqControlGetMSI { + irq_control: u64, + dest_root: u64, + dest_index: u64, + dest_depth: u64, + pci_bus: u64, + pci_dev: u64, + pci_func: u64, + handle: u64, + vector: u64, + }, IrqHandlerSetNotification { irq_handler: u64, notification: u64, }, + PageUpperDirectoryMap { + page_upper_directory: u64, + vspace: u64, + vaddr: u64, + attr: u64, + }, + PageDirectoryMap { + page_directory: u64, + vspace: u64, + vaddr: u64, + attr: u64, + }, PageTableMap { page_table: u64, vspace: u64, diff --git a/tool/microkit/src/x86loader.rs b/tool/microkit/src/x86loader.rs new file mode 100644 index 00000000..3f260d89 --- /dev/null +++ b/tool/microkit/src/x86loader.rs @@ -0,0 +1,95 @@ +// +// Copyright 2024, Neutrality Sàrl +// +// SPDX-License-Identifier: BSD-2-Clause +// + +use crate::elf::{ElfFile,ElfSegment,ElfSegmentAttributes}; +use crate::sel4::Config; +use crate::round_up; +use crate::MemoryRegion; +use std::path::Path; + +pub struct X86Loader { + elf: ElfFile, +} + +impl X86Loader { + pub fn new( + _config: &Config, + loader_elf_path: &Path, + kernel_elf: &ElfFile, + initial_task_elf: &ElfFile, + _initial_task_phys_base: Option, + reserved_region: MemoryRegion, + system_regions: Vec<(u64, &[u8])>, + ) -> X86Loader { + + // Load the loader ELF file. + let mut elf = ElfFile::from_path(loader_elf_path).unwrap(); + + // Add the PD memory regions as segments. + for region in system_regions { + let segment = ElfSegment { + data: region.1.to_vec(), + phys_addr: region.0, + virt_addr: 0, + loadable: true, + attrs: ElfSegmentAttributes::Read as u32, + }; + elf.add_segment(segment); + } + + // Add the kernel memory regions as segments. + for segment in &kernel_elf.segments { + // Wipe the virtual address fields that are unnecessary and + // cause issues since they are 64-bit wide. + let mut segment = segment.clone(); + segment.virt_addr = 0; + elf.add_segment(segment); + } + + // Save the kernel's entry point address so we can jump into it + // once we're done with our boot dance. + let entry = kernel_elf.entry as u32; + elf.write_symbol("kernel_entry", &entry.to_le_bytes()).unwrap(); + + // Save the address and size of the reserved memory region that + // holds the PD regions and the monitor invocation table. + elf.write_symbol("extra_device_addr_p", &reserved_region.base.to_le_bytes()).unwrap(); + elf.write_symbol("extra_device_size", &reserved_region.size().to_le_bytes()).unwrap(); + + // # Export the monitor task as a binary ELF64 file in memory. + let mut monitor_raw = std::io::Cursor::new(Vec::new()); + initial_task_elf.write(&mut monitor_raw).unwrap(); + + // Add the monitor ELF file as a segment to the loader, and + // have it loaded at a page aligned address just after the + // loader. + let (bss_end, _) = elf.find_symbol("_bss_end").unwrap(); + let monitor_addr = round_up(bss_end as usize, 0x1000) as u32; + let monitor_size = monitor_raw.get_ref().len() as u32; + let monitor_segment = ElfSegment { + data: monitor_raw.get_ref().to_vec(), + phys_addr: monitor_addr as u64, + virt_addr: 0, + loadable: true, + attrs: ElfSegmentAttributes::Read as u32, + }; + elf.add_segment(monitor_segment); + + // Save the monitor's loaded address and size. + elf.write_symbol("monitor_addr", &monitor_addr.to_le_bytes()).unwrap(); + elf.write_symbol("monitor_size", &monitor_size.to_le_bytes()).unwrap(); + + X86Loader { + elf: elf, + } + } + + pub fn write_image(&self, path: &Path) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + self.elf.write(&mut file)?; + Ok(()) + } +} diff --git a/tool/microkit/tests/test.rs b/tool/microkit/tests/test.rs index 698c70df..36159645 100644 --- a/tool/microkit/tests/test.rs +++ b/tool/microkit/tests/test.rs @@ -24,6 +24,7 @@ const DEFAULT_KERNEL_CONFIG: sel4::Config = sel4::Config { riscv_pt_levels: None, // Not necessary for SDF parsing invocations_labels: json!(null), + x86_xsave_size: None, }; fn check_error(test_name: &str, expected_err: &str) { @@ -145,7 +146,10 @@ mod protection_domain { #[test] fn test_missing_irq() { - check_missing("pd_missing_irq.system", "irq", "irq") + check_error( + "pd_missing_irq.system", + "Error: Missing required attribute 'irq', or 'pin' (x86 IOAPIC), or 'pcidev' (x86 MSI) on element 'irq': " + ) } #[test] From f0e2611738ccaa1f508fe36d91ee23ceadeeb7c2 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Fri, 18 Oct 2024 00:59:07 +0200 Subject: [PATCH 06/11] Add support for x86 I/O ports Signed-off-by: Mathieu Mirmont --- libmicrokit/include/microkit.h | 8 ++++++ tool/microkit/src/main.rs | 49 +++++++++++++++++++++++++++++++++- tool/microkit/src/sdf.rs | 28 ++++++++++++++++++- tool/microkit/src/sel4.rs | 42 +++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 2 deletions(-) diff --git a/libmicrokit/include/microkit.h b/libmicrokit/include/microkit.h index fa88ab43..5764bfc2 100644 --- a/libmicrokit/include/microkit.h +++ b/libmicrokit/include/microkit.h @@ -26,6 +26,7 @@ typedef seL4_MessageInfo_t microkit_msginfo; #define BASE_TCB_CAP 202 #define BASE_VM_TCB_CAP 266 #define BASE_VCPU_CAP 330 +#define BASE_IOPORT_CAP 394 #define MICROKIT_MAX_CHANNELS 62 #define MICROKIT_PD_NAME_LENGTH 64 @@ -238,3 +239,10 @@ static inline void microkit_deferred_irq_ack(microkit_channel ch) microkit_signal_msg = seL4_MessageInfo_new(IRQAckIRQ, 0, 0, 0); microkit_signal_cap = (BASE_IRQ_CAP + ch); } + +#if defined(CONFIG_ARCH_X86_64) +static inline seL4_Word microkit_ioport_cap(seL4_Word id) +{ + return (seL4_Word) BASE_IOPORT_CAP + id; +} +#endif diff --git a/tool/microkit/src/main.rs b/tool/microkit/src/main.rs index 52a04825..d93b1758 100644 --- a/tool/microkit/src/main.rs +++ b/tool/microkit/src/main.rs @@ -56,6 +56,7 @@ const BASE_IRQ_CAP: u64 = BASE_OUTPUT_ENDPOINT_CAP + 64; const BASE_PD_TCB_CAP: u64 = BASE_IRQ_CAP + 64; const BASE_VM_TCB_CAP: u64 = BASE_PD_TCB_CAP + 64; const BASE_VCPU_CAP: u64 = BASE_VM_TCB_CAP + 64; +const BASE_IOPORT_CAP: u64 = BASE_VCPU_CAP + 64; const MAX_SYSTEM_INVOCATION_SIZE: u64 = util::mb(128); @@ -78,7 +79,7 @@ const N_VTD_CONTEXTS: u64 = 256; const VTD_PT_INDEX_BITS: u64 = 9; // const ASID_CONTROL_CAP_ADDRESS: u64 = 5; // Singleton -// const IO_PORT_CONTROL_CAP_ADDRESS: u64 = 7; // Null on this platform +const IO_PORT_CONTROL_CAP_ADDRESS: u64 = 7; // X86 only // const IO_SPACE_CAP_ADDRESS: u64 = 8; // Null on this platform // const BOOT_INFO_FRAME_CAP_ADDRESS: u64 = 9; // const INIT_THREAD_IPC_BUFFER_CAP_ADDRESS: u64 = 10; @@ -1035,6 +1036,7 @@ fn build_system( cap_address_names.insert(INIT_VSPACE_CAP_ADDRESS, "VSpace: init".to_string()); cap_address_names.insert(INIT_ASID_POOL_CAP_ADDRESS, "ASID Pool: init".to_string()); cap_address_names.insert(IRQ_CONTROL_CAP_ADDRESS, "IRQ Control".to_string()); + cap_address_names.insert(IO_PORT_CONTROL_CAP_ADDRESS, "I/O Port Control".to_string()); cap_address_names.insert(SMC_CAP_IDX, "SMC".to_string()); let system_cnode_bits = system_cnode_size.ilog2() as u64; @@ -2093,6 +2095,30 @@ fn build_system( } } + // Create all x86 I/O port objects. + let mut ioport_cap_addresses: HashMap<&ProtectionDomain, Vec> = HashMap::new(); + for pd in &system.protection_domains { + ioport_cap_addresses.insert(pd, vec![]); + for ioport in &pd.ioports { + let cap_address = system_cap_address_mask | cap_slot; + system_invocations.push(Invocation::new( + config, + InvocationArgs::IoPortControlIssue { + ioport_control: IO_PORT_CONTROL_CAP_ADDRESS, + first_port: ioport.addr, + last_port: ioport.addr + ioport.size, + dest_root: root_cnode_cap, + dest_index: cap_address, + dest_depth: config.cap_address_bits + }, + )); + + cap_slot += 1; + cap_address_names.insert(cap_address, format!("I/O Port: addr={:#x} size={}", ioport.addr, ioport.size)); + ioport_cap_addresses.get_mut(pd).unwrap().push(cap_address); + } + } + // This has to be done prior to minting! let num_asid_invocations = system.protection_domains.len() + virtual_machines.len(); let mut asid_invocation = Invocation::new( @@ -2517,6 +2543,27 @@ fn build_system( } } + // Mint access to x86 I/O ports in the PD CSpace + for (pd_idx, pd) in system.protection_domains.iter().enumerate() { + for (ioport, ioport_cap_address) in zip(&pd.ioports, &ioport_cap_addresses[pd]) { + let cap_idx = BASE_IOPORT_CAP + ioport.id; + assert!(cap_idx < PD_CAP_SIZE); + system_invocations.push(Invocation::new( + config, + InvocationArgs::CnodeMint { + cnode: cnode_objs[pd_idx].cap_addr, + dest_index: cap_idx, + dest_depth: PD_CAP_BITS, + src_root: root_cnode_cap, + src_obj: *ioport_cap_address, + src_depth: config.cap_address_bits, + rights: Rights::All as u64, + badge: 0, + }, + )); + } + } + // Mint access to the child TCB in the CSpace of root PDs for (pd_idx, _) in system.protection_domains.iter().enumerate() { for (maybe_child_idx, maybe_child_pd) in system.protection_domains.iter().enumerate() { diff --git a/tool/microkit/src/sdf.rs b/tool/microkit/src/sdf.rs index 8b80d71f..392436d1 100644 --- a/tool/microkit/src/sdf.rs +++ b/tool/microkit/src/sdf.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: BSD-2-Clause // -use crate::sel4::{Config, IrqTrigger, PageSize}; +use crate::sel4::{Config, IrqTrigger, PageSize, Arch}; use crate::util::str_to_bool; use crate::MAX_PDS; use std::path::{Path, PathBuf}; @@ -176,6 +176,13 @@ pub struct Channel { pub end_b: ChannelEnd, } +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct IOPort { + pub id: u64, + pub addr: u64, + pub size: u64, +} + #[derive(Debug, PartialEq, Eq, Hash)] pub struct ProtectionDomain { /// Only populated for child protection domains @@ -192,6 +199,7 @@ pub struct ProtectionDomain { pub irqs: Vec, pub setvars: Vec, pub virtual_machine: Option, + pub ioports: Vec, /// Only used when parsing child PDs. All elements will be removed /// once we flatten each PD and its children into one list. pub child_pds: Vec, @@ -479,6 +487,7 @@ impl ProtectionDomain { let mut maps = Vec::new(); let mut irqs = Vec::new(); let mut setvars: Vec = Vec::new(); + let mut ioports: Vec = Vec::new(); let mut child_pds = Vec::new(); let mut program_image = None; @@ -687,6 +696,22 @@ impl ProtectionDomain { kind: SysSetVarKind::Paddr { region }, }) } + "ioport" => { + if let Arch::X86_64 = config.arch { + check_attributes(xml_sdf, &child, &["id", "addr", "size"])?; + ioports.push(IOPort { + id: checked_lookup(xml_sdf, &child, "id")?.parse::().unwrap(), + addr: sdf_parse_number(checked_lookup(xml_sdf, &child, "addr")?, &child)?, + size: sdf_parse_number(checked_lookup(xml_sdf, &child, "size")?, &child)?, + }) + } else { + return Err(value_error( + xml_sdf, + node, + "ioports are only available on x86 hardware".to_string(), + )); + } + } "protection_domain" => { child_pds.push(ProtectionDomain::from_xml(config, xml_sdf, &child, true)?) } @@ -738,6 +763,7 @@ impl ProtectionDomain { setvars, child_pds, virtual_machine, + ioports, has_children, parent: None, text_pos: xml_sdf.doc.text_pos_at(node.range().start), diff --git a/tool/microkit/src/sel4.rs b/tool/microkit/src/sel4.rs index 67bb9dc2..7c3c69c2 100644 --- a/tool/microkit/src/sel4.rs +++ b/tool/microkit/src/sel4.rs @@ -1205,6 +1205,25 @@ impl Invocation { )); (irq_handler, &cap_lookup[&irq_handler]) } + InvocationArgs::IoPortControlIssue { + ioport_control, + first_port, + last_port, + dest_root, + dest_index, + dest_depth, + } => { + arg_strs.push(Invocation::fmt_field("addr", first_port)); + arg_strs.push(Invocation::fmt_field("size", last_port - first_port)); + arg_strs.push(Invocation::fmt_field_cap( + "dest_root", + dest_root, + cap_lookup, + )); + arg_strs.push(Invocation::fmt_field("dest_index", dest_index)); + arg_strs.push(Invocation::fmt_field("dest_depth", dest_depth)); + (ioport_control, cap_lookup.get(&ioport_control).unwrap()) + } InvocationArgs::PageUpperDirectoryMap { page_upper_directory, vspace, @@ -1344,6 +1363,7 @@ impl Invocation { | InvocationLabel::X86IRQIssueIRQHandlerIOAPIC | InvocationLabel::X86IRQIssueIRQHandlerMSI => "IRQ Control", InvocationLabel::IRQSetIRQHandler => "IRQ Handler", + InvocationLabel::X86IOPortControlIssue => "I/O Port", InvocationLabel::X86PDPTMap => "Page Upper Directory", InvocationLabel::X86PageDirectoryMap => "Page Directory", InvocationLabel::ARMPageTableMap @@ -1380,6 +1400,7 @@ impl Invocation { | InvocationLabel::X86IRQIssueIRQHandlerIOAPIC | InvocationLabel::X86IRQIssueIRQHandlerMSI => "Get", InvocationLabel::IRQSetIRQHandler => "SetNotification", + InvocationLabel::X86IOPortControlIssue => "Issue", InvocationLabel::ARMPageTableMap | InvocationLabel::ARMPageMap | InvocationLabel::RISCVPageTableMap @@ -1423,6 +1444,7 @@ impl InvocationArgs { InvocationArgs::IrqControlGetIOAPIC { .. } => InvocationLabel::X86IRQIssueIRQHandlerIOAPIC, InvocationArgs::IrqControlGetMSI { .. } => InvocationLabel::X86IRQIssueIRQHandlerMSI, InvocationArgs::IrqHandlerSetNotification { .. } => InvocationLabel::IRQSetIRQHandler, + InvocationArgs::IoPortControlIssue { .. } => InvocationLabel::X86IOPortControlIssue, InvocationArgs::PageUpperDirectoryMap { .. } => match config.arch { Arch::Aarch64 => InvocationLabel::ARMPageTableMap, Arch::Riscv64 => InvocationLabel::RISCVPageTableMap, @@ -1576,6 +1598,18 @@ impl InvocationArgs { irq_handler, notification, } => (irq_handler, vec![], vec![notification]), + InvocationArgs::IoPortControlIssue { + ioport_control, + first_port, + last_port, + dest_root, + dest_index, + dest_depth, + } => ( + ioport_control, + vec![first_port, last_port, dest_index, dest_depth], + vec![dest_root], + ), InvocationArgs::PageUpperDirectoryMap { page_upper_directory, vspace, @@ -1732,6 +1766,14 @@ pub enum InvocationArgs { irq_handler: u64, notification: u64, }, + IoPortControlIssue { + ioport_control: u64, + first_port: u64, + last_port: u64, + dest_root: u64, + dest_index: u64, + dest_depth: u64, + }, PageUpperDirectoryMap { page_upper_directory: u64, vspace: u64, From d7cc019a6c21aaa8e05e46528bae19271dcf1027 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Wed, 30 Oct 2024 15:05:59 +0100 Subject: [PATCH 07/11] Add the qemu_virt_x86_64 example board Signed-off-by: Mathieu Mirmont --- build_sdk.py | 17 ++++++ example/x86_64_nehalem/hello/Makefile | 55 ++++++++++++++++++++ example/x86_64_nehalem/hello/hello.c | 31 +++++++++++ example/x86_64_nehalem/hello/hello.system | 12 +++++ example/x86_64_nehalem/hello/machine.json | 33 ++++++++++++ example/x86_64_nehalem/sim | 63 +++++++++++++++++++++++ 6 files changed, 211 insertions(+) create mode 100644 example/x86_64_nehalem/hello/Makefile create mode 100644 example/x86_64_nehalem/hello/hello.c create mode 100644 example/x86_64_nehalem/hello/hello.system create mode 100644 example/x86_64_nehalem/hello/machine.json create mode 100755 example/x86_64_nehalem/sim diff --git a/build_sdk.py b/build_sdk.py index 1e85b906..2a66602b 100644 --- a/build_sdk.py +++ b/build_sdk.py @@ -290,6 +290,23 @@ class ConfigInfo: "hello": Path("example/star64/hello") } ), + BoardInfo( + name="x86_64_nehalem", + arch=KernelArch.X86_64, + gcc_cpu=None, + gcc_march="nehalem", + loader_link_address=0x10000000, # 256MB + kernel_options = { + "KernelIsMCS": True, + "KernelPlatform": "pc99", + "KernelSel4Arch": "x86_64", + "KernelVTX": True, + "KernelX86MicroArch": "nehalem", + }, + examples = { + "hello": Path("example/x86_64_nehalem/hello") + } + ), ) SUPPORTED_CONFIGS = ( diff --git a/example/x86_64_nehalem/hello/Makefile b/example/x86_64_nehalem/hello/Makefile new file mode 100644 index 00000000..ecc55321 --- /dev/null +++ b/example/x86_64_nehalem/hello/Makefile @@ -0,0 +1,55 @@ +# +# Copyright 2021, Breakaway Consulting Pty. Ltd. +# +# SPDX-License-Identifier: BSD-2-Clause +# +ifeq ($(strip $(BUILD_DIR)),) +$(error BUILD_DIR must be specified) +endif + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif + +ifeq ($(strip $(MICROKIT_CONFIG)),) +$(error MICROKIT_CONFIG must be specified) +endif + +TOOLCHAIN := x86_64-linux-gnu + +ARCH := nehalem + +CC := $(TOOLCHAIN)-gcc +LD := $(TOOLCHAIN)-ld +AS := $(TOOLCHAIN)-as +MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit + +HELLO_OBJS := hello.o + +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) + +IMAGES := hello.elf +CFLAGS := -march=$(ARCH) -DARCH_x86_64 -nostdlib -ffreestanding -g3 -O3 -Wall -Wno-unused-function -Werror -I$(BOARD_DIR)/include +LDFLAGS := -L$(BOARD_DIR)/lib -z noexecstack +LIBS := -lmicrokit -Tmicrokit.ld + +IMAGE_FILE = $(BUILD_DIR)/loader.img +REPORT_FILE = $(BUILD_DIR)/report.txt + +all: $(IMAGE_FILE) + +$(BUILD_DIR)/%.o: %.c Makefile + $(CC) -c $(CFLAGS) $< -o $@ + +$(BUILD_DIR)/%.o: %.s Makefile + $(AS) -g3 -march=$(ARCH) $< -o $@ + +$(BUILD_DIR)/hello.elf: $(addprefix $(BUILD_DIR)/, $(HELLO_OBJS)) + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(IMAGE_FILE) $(REPORT_FILE): $(addprefix $(BUILD_DIR)/, $(IMAGES)) hello.system + $(MICROKIT_TOOL) hello.system --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) --x86-machine machine.json -o $(IMAGE_FILE) -r $(REPORT_FILE) diff --git a/example/x86_64_nehalem/hello/hello.c b/example/x86_64_nehalem/hello/hello.c new file mode 100644 index 00000000..de2a4893 --- /dev/null +++ b/example/x86_64_nehalem/hello/hello.c @@ -0,0 +1,31 @@ +/* + * Copyright 2021, Breakaway Consulting Pty. Ltd. + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include + +static inline void serial_putc(char ch) +{ + seL4_X86_IOPort_Out8(microkit_ioport_cap(0), 0x3f8, ch); +} + +static inline void serial_puts(const char *s) +{ + while (*s) { + if (*s == '\n') + serial_putc('\r'); + serial_putc(*s++); + } +} + +void init(void) +{ + microkit_dbg_puts("hello, debug port\n"); + serial_puts("hello, serial port\n"); +} + +void notified(microkit_channel ch) +{ +} diff --git a/example/x86_64_nehalem/hello/hello.system b/example/x86_64_nehalem/hello/hello.system new file mode 100644 index 00000000..0cc86664 --- /dev/null +++ b/example/x86_64_nehalem/hello/hello.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/x86_64_nehalem/hello/machine.json b/example/x86_64_nehalem/hello/machine.json new file mode 100644 index 00000000..6204e7b9 --- /dev/null +++ b/example/x86_64_nehalem/hello/machine.json @@ -0,0 +1,33 @@ +{ + "memory": [ + { + "base": 1048576, + "size": 2146299904 + }, + { + "base": 4294967296, + "size": 2147483648 + } + ], + "kdevs": [ + { + "name": "apic", + "base": 4276092928, + "size": 4096 + }, + { + "name": "ioapic.0", + "base": 4273995776, + "size": 4096 + }, + { + "name": "drhu.0", + "base": 4275634176, + "size": 4096 + } + ], + "rmrrs": [], + "bootinfo": { + "numIOPTLevels": 3 + } +} diff --git a/example/x86_64_nehalem/sim b/example/x86_64_nehalem/sim new file mode 100755 index 00000000..24b0b8b4 --- /dev/null +++ b/example/x86_64_nehalem/sim @@ -0,0 +1,63 @@ +#!/bin/sh +# +# Copyright 2024, Neutrality Sarl. +# SPDX-License-Identifier: BSD-2-Clause +# +# This is a wrapper around QEMU that can be used to test the images built for +# the qemu_virt_x86_64 microkit board. Pass the "loader.img" file as first argument. +# +set -eu + +# QEMU machine setup. +MACHINE=q35,accel=kvm,kernel-irqchip=split +CPU=Nehalem,+fsgsbase,+pdpe1gb,+pcid,+invpcid,+xsave,+xsaves,+xsaveopt,+vmx,+vme +RAM=4G + +# Parse the command line. +if [ $# -lt 1 ]; then + echo "Usage: $(basename "$0") [qemu args]" >&2 + exit 1 +fi +image="$1" +shift + +# Create a temporary directory. +TMPDIR="$(mktemp -d)" +trap "rm -rf -- '$TMPDIR'" EXIT + +# A subdirectory for the ISO files. +ISODIR="$TMPDIR"/iso +mkdir -p "$ISODIR" + +# Copy the microkit image. +cp "$image" "$ISODIR"/image + +# Create the grub configuration file. +mkdir -p "$ISODIR"/boot/grub +cat > "$ISODIR"/boot/grub/grub.cfg <<-EOF +set default=0 +set timeout=0 + +serial --unit=0 --speed=115200 +terminal_input serial +terminal_output serial + +menuentry 'microkit' { + multiboot2 /image +} +EOF + +# Build a bootable grub ISO image. +grub-mkrescue -o "$TMPDIR"/grub.iso "$ISODIR" + +# Run qemu. +set -x +qemu-system-x86_64 \ + -machine "$MACHINE" \ + -cpu "$CPU" \ + -m "$RAM" \ + -display none \ + -serial mon:stdio \ + -device intel-iommu \ + -cdrom "$TMPDIR"/grub.iso \ + "$@" From 4c4163549a09e8dc9278d391cd676430a4fb66a5 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Thu, 24 Oct 2024 15:04:34 +0200 Subject: [PATCH 08/11] Add the ODROID H2+ x86 board Signed-off-by: Mathieu Mirmont --- build_sdk.py | 18 +++++ example/x86_64_goldmont_plus/hello/Makefile | 55 ++++++++++++++++ example/x86_64_goldmont_plus/hello/hello.c | 39 +++++++++++ .../x86_64_goldmont_plus/hello/hello.system | 12 ++++ .../hello/odroidh2plus.json | 65 +++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 example/x86_64_goldmont_plus/hello/Makefile create mode 100644 example/x86_64_goldmont_plus/hello/hello.c create mode 100644 example/x86_64_goldmont_plus/hello/hello.system create mode 100644 example/x86_64_goldmont_plus/hello/odroidh2plus.json diff --git a/build_sdk.py b/build_sdk.py index 2a66602b..35fe707b 100644 --- a/build_sdk.py +++ b/build_sdk.py @@ -307,6 +307,24 @@ class ConfigInfo: "hello": Path("example/x86_64_nehalem/hello") } ), + BoardInfo( + name="x86_64_goldmont_plus", + arch=KernelArch.X86_64, + gcc_cpu=None, + gcc_march="goldmont-plus", + loader_link_address=0x10000000, # 256MB + kernel_options = { + "KernelIsMCS": True, + "KernelPlatform": "pc99", + "KernelSel4Arch": "x86_64", + "KernelVTX": True, + "KernelX86MicroArch": "goldmont-plus", + "KernelSupportPCID": False, + }, + examples = { + "hello": Path("example/x86_64_goldmont_plus/hello") + } + ), ) SUPPORTED_CONFIGS = ( diff --git a/example/x86_64_goldmont_plus/hello/Makefile b/example/x86_64_goldmont_plus/hello/Makefile new file mode 100644 index 00000000..82dc685f --- /dev/null +++ b/example/x86_64_goldmont_plus/hello/Makefile @@ -0,0 +1,55 @@ +# +# Copyright 2021, Breakaway Consulting Pty. Ltd. +# +# SPDX-License-Identifier: BSD-2-Clause +# +ifeq ($(strip $(BUILD_DIR)),) +$(error BUILD_DIR must be specified) +endif + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif + +ifeq ($(strip $(MICROKIT_CONFIG)),) +$(error MICROKIT_CONFIG must be specified) +endif + +TOOLCHAIN := x86_64-linux-gnu + +ARCH := nehalem + +CC := $(TOOLCHAIN)-gcc +LD := $(TOOLCHAIN)-ld +AS := $(TOOLCHAIN)-as +MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit + +HELLO_OBJS := hello.o + +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) + +IMAGES := hello.elf +CFLAGS := -march=$(ARCH) -DARCH_x86_64 -nostdlib -ffreestanding -g3 -O3 -Wall -Wno-unused-function -Werror -I$(BOARD_DIR)/include +LDFLAGS := -L$(BOARD_DIR)/lib -z noexecstack +LIBS := -lmicrokit -Tmicrokit.ld + +IMAGE_FILE = $(BUILD_DIR)/loader.img +REPORT_FILE = $(BUILD_DIR)/report.txt + +all: $(IMAGE_FILE) + +$(BUILD_DIR)/%.o: %.c Makefile + $(CC) -c $(CFLAGS) $< -o $@ + +$(BUILD_DIR)/%.o: %.s Makefile + $(AS) -g3 -march=$(ARCH) $< -o $@ + +$(BUILD_DIR)/hello.elf: $(addprefix $(BUILD_DIR)/, $(HELLO_OBJS)) + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(IMAGE_FILE) $(REPORT_FILE): $(addprefix $(BUILD_DIR)/, $(IMAGES)) hello.system + $(MICROKIT_TOOL) hello.system --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) --x86-machine odroidh2plus.json -o $(IMAGE_FILE) -r $(REPORT_FILE) diff --git a/example/x86_64_goldmont_plus/hello/hello.c b/example/x86_64_goldmont_plus/hello/hello.c new file mode 100644 index 00000000..18cca204 --- /dev/null +++ b/example/x86_64_goldmont_plus/hello/hello.c @@ -0,0 +1,39 @@ +/* + * Copyright 2021, Breakaway Consulting Pty. Ltd. + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include + +static inline void serial_putc(char ch) +{ + /* The I/O port capability and address defined here must match the system + * description file (hello.system). */ + int cap = microkit_ioport_cap(0); + uint16_t base = 0x3f8; + + while ((seL4_X86_IOPort_In8(cap, base + 5).result & 0x20) == 0) + ; + seL4_X86_IOPort_Out8(cap, base, ch); +} + +static inline void serial_puts(const char *s) +{ + while (*s) { + if (*s == '\n') + serial_putc('\r'); + serial_putc(*s++); + } +} + +void init(void) +{ + microkit_dbg_puts("hello, debug port\n"); + serial_puts("hello, serial port\n"); +} + +void notified(microkit_channel ch) +{ + (void) ch; +} diff --git a/example/x86_64_goldmont_plus/hello/hello.system b/example/x86_64_goldmont_plus/hello/hello.system new file mode 100644 index 00000000..0cc86664 --- /dev/null +++ b/example/x86_64_goldmont_plus/hello/hello.system @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/x86_64_goldmont_plus/hello/odroidh2plus.json b/example/x86_64_goldmont_plus/hello/odroidh2plus.json new file mode 100644 index 00000000..e7ea8711 --- /dev/null +++ b/example/x86_64_goldmont_plus/hello/odroidh2plus.json @@ -0,0 +1,65 @@ +{ + "memory": [ + { + "base": 1048576, + "size": 267386880 + }, + { + "base": 303370240, + "size": 1673691136 + }, + { + "base": 2036105216, + "size": 352256 + }, + { + "base": 2040844288, + "size": 13807616 + }, + { + "base": 2055356416, + "size": 4046848 + }, + { + "base": 4294967296, + "size": 6442450944 + } + ], + "kdevs": [ + { + "name": "apic", + "base": 4276092928, + "size": 4096 + }, + { + "name": "ioapic.0", + "base": 4273995776, + "size": 4096 + }, + { + "name": "drhu.0", + "base": 4275453952, + "size": 4096 + }, + { + "name": "drhu.1", + "base": 4275458048, + "size": 4096 + } + ], + "rmrrs": [ + { + "devid": 168, + "base": 2035703808, + "limit": 2035834879 + }, + { + "devid": 16, + "base": 2071986176, + "limit": 2147483647 + } + ], + "bootinfo": { + "numIOPTLevels": 4 + } +} From 5c74550f5b39aa35becb5acb0afa8fa6862387cd Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Tue, 29 Oct 2024 12:12:55 +0100 Subject: [PATCH 09/11] Add the list of untyped to the report This list can be quite handy when porting to a new board. Signed-off-by: Mathieu Mirmont --- tool/microkit/src/main.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tool/microkit/src/main.rs b/tool/microkit/src/main.rs index d93b1758..01e8bce9 100644 --- a/tool/microkit/src/main.rs +++ b/tool/microkit/src/main.rs @@ -3262,6 +3262,20 @@ fn write_report( " # of untyped objects: {:>8}", comma_sep_usize(built_system.kernel_boot_info.untyped_objects.len()) )?; + writeln!(buf, "\n# Untyped Objects\n")?; + for (i,ut) in built_system.kernel_boot_info.untyped_objects.iter().enumerate() { + let memtype = match ut.is_device { + true => "device", + false => "normal", + }; + let size = ut.region.end - ut.region.base; + writeln!(buf, " {:#010x} slot: {:#010x} paddr: {:#018x} - {:#018x} ({}) bits: {:#010x}", + i, ut.cap, + ut.region.base, + ut.region.end, + memtype, + size.trailing_zeros())?; + } writeln!(buf, "\n# Loader Regions\n")?; for regions in &built_system.pd_elf_regions { for region in regions { From afa517b7938a9e971cb27c732c35d9cfd2bd8a4a Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Thu, 31 Oct 2024 11:22:03 +0100 Subject: [PATCH 10/11] README.md: add the x86_64 boards Signed-off-by: Mathieu Mirmont --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9141d2a8..9f380afe 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Please file an issue if additional packages are required. * xmllint * qemu-system-aarch64 * qemu-system-riscv64 +* qemu-system-x86 To build the documentation you also need * pandoc @@ -68,7 +69,7 @@ On a Debian-like system you can do: pandoc texlive-latex-base texlive-latex-recommended \ texlive-fonts-recommended texlive-fonts-extra \ python3.9 python3.9-venv \ - qemu-system-arm qemu-system-misc \ + qemu-system-arm qemu-system-misc qemu-system-x86 \ gcc-riscv64-unknown-elf $ python3.9 -m venv pyenv $ ./pyenv/bin/pip install --upgrade pip setuptools wheel @@ -210,8 +211,10 @@ The currently supported boards are: * maaxboard * odroidc2 * odroidc4 +* odroidh2plus * qemu_virt_aarch64 * qemu_virt_riscv64 +* qemu_virt_x86_64 * rockpro64 * star64 * tqma8xqp1gb From 6643919cb4da1f8eebd04f5783da717e428ddf98 Mon Sep 17 00:00:00 2001 From: Mathieu Mirmont Date: Thu, 31 Oct 2024 12:23:59 +0100 Subject: [PATCH 11/11] docs/manual.md: add the x86 specifics Signed-off-by: Mathieu Mirmont --- docs/manual.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/docs/manual.md b/docs/manual.md index 0ec7ede6..215df580 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -102,6 +102,7 @@ This document attempts to clearly describe all of these terms, however as the co * [notification](#notification) * [interrupt](#irq) * [fault](#fault) +* [ioport](#ioport) ## System {#system} @@ -311,6 +312,10 @@ protection domain. The same applies for a virtual machine. This means that whenever a fault is caused by a child, it will be delivered to the parent PD instead of the system fault handler via the `fault` entry point. It is then up to the parent to decide how the fault is handled. +## I/O Ports {#ioport} + +I/O ports are mechanisms present on x86 hardware to access certain physical devices (e.g. PC serial ports) using the `in` and `out` CPU instructions. On seL4, I/O ports are used by invoking capabilities via the `seL4_X86_IOPort_*` functions. The capabilities must be explicity associated to the protection domain and are accessed by calling `microkit_ioport_cap`. + # SDK {#sdk} Microkit is distributed as a software development kit (SDK). @@ -377,12 +382,15 @@ The format of the system description is described in a subsequent chapter. Usage: microkit [-h] [-o OUTPUT] [-r REPORT] --board [BOARD] --config CONFIG - [--search-path [SEARCH_PATH ...]] system + [--search-path [SEARCH_PATH ...]] + [--x86-machine [MACHINE_FILE]] system The path to the system description file, board to build the system for, and configuration to build for must be provided. The search paths provided tell the tool where to find any program images specified in the system description file. +The x86 machine file is a json file that describes certain properties of the target hardware. This file is mandatory for x86 targets. It may be produced by booting the [Microkit x86 machine dump](https://github.com/Neutrality-ch/microkit-x86-machine-dump) tool on the target board. + In the case of errors, a diagnostic message shall be output to `stderr` and a non-zero code returned. In the case of success, a loadable image file and a report shall be produced. @@ -439,6 +447,7 @@ If the protection domain has children it must also implement: seL4_Word microkit_vcpu_arm_read_reg(microkit_child vcpu, seL4_Word reg); void microkit_vcpu_arm_write_reg(microkit_child vcpu, seL4_Word reg, seL4_Word value); void microkit_arm_smc_call(seL4_ARM_SMCContext *args, seL4_ARM_SMCContext *response); + seL4_Word microkit_ioport_cap(seL4_Word id); ## `void init(void)` @@ -616,6 +625,11 @@ response values will be placed into the `response` structure. The `seL4_ARM_SMCContext` structure contains fields for registers x0 to x7. +## `seL4_Word microkit_ioport_cap(seL4_Word id)` + +This API is available only on X86. The API takes in an I/O port identifier and returns its +associated capability, which can be provided to the `seL4_X86_IOPort_*` functions. + # System Description File {#sysdesc} This section describes the format of the System Description File (SDF). @@ -651,6 +665,7 @@ Additionally, it supports the following child elements: * `program_image`: (exactly one) Describes the program image for the protection domain. * `map`: (zero or more) Describes mapping of memory regions into the protection domain. * `irq`: (zero or more) Describes hardware interrupt associations. +* `ioport`: (zero or more) Describes x86 I/O ports to be exposed to the protection domain. * `setvar`: (zero or more) Describes variable rewriting. * `protection_domain`: (zero or more) Describes a child protection domain. * `virtual_machine`: (zero or one) Describes a child virtual machine. @@ -665,12 +680,34 @@ The `map` element has the following attributes: * `cached`: (optional) Determines if mapped with caching enabled or disabled. Defaults to `true`. * `setvar_vaddr`: (optional) Specifies a symbol in the program image. This symbol will be rewritten with the virtual address of the memory region. -The `irq` element has the following attributes: +The `irq` element has the following attributes on ARM and RISC-V platforms: * `irq`: The hardware interrupt number. * `id`: The channel identifier. Must be at least 0 and less than 63. * `trigger`: (optional) Whether the IRQ is edge triggered ("edge") or level triggered ("level"). Defaults to "level". +The `irq` element has the following attributes when registering X86_64 IOAPIC interrupts: + +* `id`: The channel identifier. Must be at least 0 and less than 63. +* `ioapic`: (optional) Zero based index of the IOAPIC to get the interrupt from. Defaults to 0. +* `pin`: IOAPIC pin that generates the interrupt. +* `level`: (optional) Whether the IRQ is level triggered (1) or edge triggered (0). Defaults to level (1). +* `polarity`: (optional) Whether the line polarity is high (1) or low (0). Defaults to high (1). +* `vector`: The IRQ vector number. + +The `irq` element has the following attributes when registering X86_64 MSI interrupts: + +* `id`: The channel identifier. Must be at least 0 and less than 63. +* `pcidev`: The PCI device address of the device that will generate the interrupt, in BUS:DEV:FUNC notation (e.g. 01:1f:2). +* `handle`: Value of the handle programmed into the data portion of the MSI. +* `vector`: CPU vector to deliver the interrupt to. + +The `ioport` element has the following attributes: + +* `id`: The I/O port identifier. Must be at least 0 and less than 63. +* `addr`: The base address of the I/O port. +* `size`: The size in bytes of the I/O port region. + The `setvar` element has the following attributes: * `symbol`: Name of a symbol in the ELF file. @@ -723,6 +760,11 @@ Below are the available page sizes for each architecture that Microkit supports. * 0x1000 (4KiB) * 0x200000 (2MiB) +#### X86 64-bit + +* 0x1000 (4KiB) +* 0x200000 (2MiB) + ## `channel` The `channel` element has exactly two `end` children elements for specifying the two PDs associated with the channel. @@ -786,6 +828,21 @@ Microkit produces a raw binary file, so when using U-Boot you must execute the i => go 0x20000000 +## Odroid-H2+ + +The HardKernel Odroid-H2+ is an X86_64 SBC based on the Intel J4115 processor. It should +be noted that the Odroid-H2+ is no longer available for purchase but its successor, the +Odroid-H3, is readily available at the time of writing. + +Microkit produces an ELF file that can be directly booted by a multiboot2 bootloader. When +using GNU GRUB you can boot the image using this menu entry: + +``` +menuentry 'Microkit' { + multiboot2 /loader.img +} +``` + ## TQMa8XQP 1GB The TQMa8XQP is a system-on-module designed by TQ-Systems GmbH. @@ -892,6 +949,19 @@ QEMU will start the system image using its packaged version of OpenSBI. You can find more about the QEMU virt platform in the [QEMU documentation](https://www.qemu.org/docs/master/system/target-riscv.html). + +## QEMU virt (X86 64-bit) + +Support is available for the virtual X86_64 QEMU platform. This is a platform that is not based +on any specific SoC or hardware platform and is intended for simulating systems for +development or testing. + +You can use the `sim` shell script from the `example/qemu_virt_x86_64` directory to +simulate a Microkit system. + +You can find more about the QEMU virt platform in the +[QEMU documentation](https://www.qemu.org/docs/master/system/target-i386.html). + ## Pine64 ROCKPro64 Microkit produces a raw binary file, so when using U-Boot you must execute the image using: