├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── apps ├── init │ ├── Makefile │ └── main.c └── shared │ ├── app.h │ ├── app.ld │ ├── app.mk │ └── syscall.h ├── docs ├── flash.md ├── kernel.bt └── memory.md ├── kernel ├── Makefile ├── src │ ├── arch │ │ ├── cpu.c │ │ ├── cpu.h │ │ ├── interrupts.S │ │ ├── interrupts.c │ │ ├── interrupts.h │ │ └── intrin.h │ ├── drivers │ │ ├── dport.c │ │ ├── dport.h │ │ ├── dport.ld │ │ ├── pid.c │ │ ├── pid.h │ │ ├── pid.ld │ │ ├── rtc_cntl.c │ │ ├── rtc_cntl.h │ │ ├── rtc_cntl.ld │ │ ├── timg.c │ │ ├── timg.h │ │ ├── timg.ld │ │ ├── uart.c │ │ ├── uart.h │ │ └── uart.ld │ ├── initrd.h │ ├── linker.ld │ ├── main.S │ ├── main.c │ ├── mem │ │ ├── mem.c │ │ ├── mem.h │ │ ├── umem.c │ │ └── umem.h │ ├── task │ │ ├── loader.c │ │ ├── loader.h │ │ ├── scheduler.c │ │ ├── scheduler.h │ │ ├── syscall.c │ │ ├── syscall.h │ │ ├── task.c │ │ ├── task.h │ │ └── task_regs.h │ ├── util │ │ ├── cpp_magic.h │ │ ├── defs.h │ │ ├── except.h │ │ ├── printf.c │ │ ├── printf.h │ │ ├── string.h │ │ ├── trace.c │ │ ├── trace.h │ │ └── umm_malloc_cfgport.h │ └── vdso │ │ ├── vdso.c │ │ ├── vdso.h │ │ └── vectors.S └── syms │ ├── bootrom.ld │ └── io.ld ├── loader ├── Makefile ├── linker.ld └── src │ ├── main.c │ ├── rom.h │ ├── trace.c │ └── trace.h ├── scripts ├── create_rootfs.py └── run_esp32.py └── toolchain ├── Makefile ├── esptool.mk ├── qemu.mk └── toolchain.mk /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | toolchain/qemu 3 | toolchain/esptool-*-linux-amd64 4 | toolchain/xtensa-esp32-elf 5 | toolchain/*.tar* 6 | toolchain/*.zip 7 | .idea 8 | cmake-build-debug 9 | CMakeLists.txt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "kernel/libs/umm_malloc"] 2 | path = kernel/libs/umm_malloc 3 | url = git@github.com:rhempel/umm_malloc.git 4 | [submodule "lib/littlefs"] 5 | path = lib/littlefs 6 | url = git@github.com:littlefs-project/littlefs.git 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Itay Almog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # watch-micro-kernel build system 3 | ######################################################################################################################## 4 | 5 | #----------------------------------------------------------------------------------------------------------------------- 6 | # The targets to combine it all 7 | #----------------------------------------------------------------------------------------------------------------------- 8 | 9 | .PHONY: all clean fetch-toolchain loader kernel rootfs 10 | 11 | # Build all the targets 12 | all: out/image.bin 13 | 14 | # Clean everything 15 | clean: 16 | $(MAKE) -C loader clean 17 | $(MAKE) -C kernel clean 18 | $(MAKE) -C apps/init clean 19 | rm -rf out 20 | 21 | # Fetch the toolchain for the given target 22 | fetch-toolchain: 23 | $(MAKE) -C toolchain 24 | 25 | #----------------------------------------------------------------------------------------------------------------------- 26 | # Combine everything into a nice image 27 | #----------------------------------------------------------------------------------------------------------------------- 28 | 29 | out/image.bin: loader rootfs 30 | @mkdir -p $(@D) 31 | 32 | # set the loader at the start and resize it nicely 33 | rm -rf $@ 34 | dd if=loader/out/bin/loader.bin of=$@ bs=1 seek=4k 35 | truncate -s 16K $@ 36 | 37 | # put the rootfs afterwards 38 | cat out/rootfs.bin >> $@ 39 | 40 | # create a version with a full size image 41 | cp $@ $@.full 42 | truncate -s 16M $@.full 43 | 44 | rootfs: kernel init 45 | ./scripts/create_rootfs.py 46 | 47 | loader: 48 | $(MAKE) -C loader 49 | 50 | kernel: 51 | $(MAKE) -C kernel 52 | 53 | init: 54 | $(MAKE) -C apps/init 55 | 56 | #----------------------------------------------------------------------------------------------------------------------- 57 | # Target specific stuff 58 | #----------------------------------------------------------------------------------------------------------------------- 59 | 60 | TOOLCHAIN_PATH := toolchain 61 | include toolchain/esptool.mk 62 | include toolchain/toolchain.mk 63 | include toolchain/qemu.mk 64 | 65 | $(QEMU): 66 | $(MAKE) -C toolchain fetch-qemu 67 | 68 | objdump: kernel 69 | $(OBJDUMP) -d kernel/out/build/kernel.elf > kernel.S 70 | 71 | run: all 72 | ./scripts/run_esp32.py 73 | 74 | qemu-debug: $(QEMU) all 75 | $(QEMU) \ 76 | -machine esp32 \ 77 | -serial stdio \ 78 | -monitor telnet:localhost:1235,server,nowait \ 79 | -drive file=out/image.bin.full,if=mtd,format=raw \ 80 | -m 4M \ 81 | -s -S \ 82 | -d int 83 | 84 | qemu: $(QEMU) all 85 | $(QEMU) \ 86 | --trace "*mtd*" -machine esp32 \ 87 | -serial stdio \ 88 | -monitor telnet:localhost:1235,server,nowait \ 89 | -drive file=out/image.bin.full,if=mtd,format=raw \ 90 | -m 4M 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Microkernel for TTGO T-Watch-2020-V2 2 | 3 | This is a protected microkernel designed to run baremetal on an ESP32 with the main target 4 | being the TTGO T-Watch-2020-V2. 5 | 6 | This projects has multiple aims: 7 | - test if the ESP32 can run a proper microkernel at all 8 | - have a simple yet usable Xtensa/ESP32 baremetal example 9 | 10 | ## Wait.. what? how? 11 | 12 | In brief, the ESP32 we have runs a dual core Xtensa CPU, the CPU itself 13 | has no built-in memory or instruction protections, there is no concept of 14 | userspace or rings. 15 | 16 | So how can we even make a protected microkernel? well the ESP32 does provide an 17 | MMU in the SoC itself, that MMU does allow for both protections and translation of 18 | virtual address range to a physical one. In addition it uses a PID controller 19 | that can be used to switch between PIDs. 20 | 21 | The combination of the MMU and PID controller allows us to protect the kernel 22 | memory, the system resources, and DMA access of all the devices in the system. 23 | Essentially allowing to prevent escalation from a user process to the kernel. 24 | 25 | It is worth noting that the MMU also prevents user code from modifying its own 26 | code, so self modifying code is not allowed. 27 | 28 | But what about interrupts? In Xtensa interrupts go through the vecbase, and because 29 | we have no userspace the user can change it! well, thankfully the esp32 does provide 30 | a way to protect specifically the vecbase! The dport can change it so the CPU will take 31 | the vecbase from the dport instead from the internal register! Additionally we configure 32 | the pid controller to switch to our kernel mode when a fetch from the vecbase is made. 33 | 34 | For more indepth information it is the best to look at the code. 35 | 36 | ## How to build 37 | 38 | Currently, I only support building from linux. 39 | 40 | You will need the following python dependencies: 41 | * `esptool` 42 | * `littlefs-python` 43 | * `tqdm` 44 | 45 | First you will need to get the toolchain: 46 | ```shell 47 | make fetch-toolchain 48 | ``` 49 | 50 | Then you can simply run 51 | ```shell 52 | make 53 | ``` 54 | 55 | This will build a full system image that can be used under `out/image.bin` 56 | 57 | To test run you can use (note that this will also build if needed), note that you might need to run this as 58 | root if you don't have permission to use the serial device directly. 59 | ```shell 60 | make run 61 | ``` 62 | 63 | If you want to use qemu then you will need to build it first 64 | -------------------------------------------------------------------------------- /apps/init/Makefile: -------------------------------------------------------------------------------- 1 | # Set the name 2 | APP_NAME := init 3 | 4 | # set the sources 5 | SRCS := main.c 6 | 7 | # call the shared 8 | APP_SHARED := ../shared 9 | include $(APP_SHARED)/app.mk 10 | -------------------------------------------------------------------------------- /apps/init/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static char buffer[6]; 4 | 5 | void _start() { 6 | buffer[0] = 'H'; 7 | buffer[1] = 'e'; 8 | buffer[2] = 'l'; 9 | buffer[3] = 'l'; 10 | buffer[4] = 'o'; 11 | buffer[5] = '!'; 12 | sys_log(buffer, sizeof(buffer)); 13 | while(1); 14 | } 15 | -------------------------------------------------------------------------------- /apps/shared/app.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define APP_HEADER_MAGIC 0x20505041 6 | 7 | typedef struct app_header { 8 | uint32_t magic; 9 | uint32_t code_size; 10 | uint32_t data_size; 11 | uint32_t bss_size; 12 | uint32_t entry; 13 | } app_header_t; 14 | -------------------------------------------------------------------------------- /apps/shared/app.ld: -------------------------------------------------------------------------------- 1 | 2 | /****************/ 3 | /* kernel entry */ 4 | /****************/ 5 | 6 | ENTRY(_start) 7 | 8 | /*************************************/ 9 | /* memory ranges used for the kernel */ 10 | /*************************************/ 11 | 12 | 13 | /* 14 | * we use SRAM1 for the kernel because it is governed by a static MPU 15 | * the total size is 128K we will separate it half and half 16 | */ 17 | MEMORY { 18 | DUMMY (R) : ORIGIN = 0x00000000, len = 128K 19 | IRAM (RX) : ORIGIN = 0x40080000, len = 128K 20 | DRAM (RW) : ORIGIN = 0x3FFC0000, len = 128K 21 | } 22 | 23 | /**********************/ 24 | /* section definition */ 25 | /**********************/ 26 | 27 | SECTIONS { 28 | 29 | .header : { 30 | /* create the kernel header */ 31 | LONG(0x20505041); /* magic */ 32 | LONG(SIZEOF(.text)); /* code size */ 33 | LONG(SIZEOF(.data)); /* data size */ 34 | LONG(_bss_end - _data_end); /* bss size */ 35 | LONG(_start); /* entry pointer */ 36 | } > DUMMY 37 | 38 | .text : { 39 | *(.text .text.*) 40 | . = ALIGN(4); 41 | } > IRAM 42 | 43 | .data : { 44 | *(.rodata .rodata.*) 45 | *(.data .data.*) 46 | _data_end = .; 47 | } > DRAM 48 | 49 | .bss : { 50 | *(.bss .bss.*) 51 | *(COMMON) 52 | _bss_end = .; 53 | } > DRAM 54 | } 55 | -------------------------------------------------------------------------------- /apps/shared/app.mk: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # Template for building userspace programs 3 | ######################################################################################################################## 4 | 5 | ifndef APP_NAME 6 | $(error APP_NAME must be set to a unique name for the app) 7 | endif 8 | 9 | ifndef APP_SHARED 10 | $(error APP_SHARED must be set to a relative path to the app shared directory) 11 | endif 12 | 13 | TOOLCHAIN_PATH := $(APP_SHARED)/../../toolchain 14 | 15 | #----------------------------------------------------------------------------------------------------------------------- 16 | # Build constants 17 | #----------------------------------------------------------------------------------------------------------------------- 18 | 19 | include $(TOOLCHAIN_PATH)/toolchain.mk 20 | include $(TOOLCHAIN_PATH)/esptool.mk 21 | 22 | OUT_DIR := out 23 | BIN_DIR := $(OUT_DIR)/bin 24 | BUILD_DIR := $(OUT_DIR)/build 25 | 26 | #----------------------------------------------------------------------------------------------------------------------- 27 | # General configurations 28 | #----------------------------------------------------------------------------------------------------------------------- 29 | 30 | CFLAGS ?= -Werror -std=gnu11 31 | CFLAGS += -Wno-unused-label 32 | CFLAGS += -Wno-address-of-packed-member 33 | CFLAGS += -Wno-psabi 34 | CFLAGS += -Wno-stringop-overflow 35 | 36 | # Size optimization, include debug info 37 | CFLAGS += -Os -g3 38 | 39 | # Link time optimization 40 | CFLAGS += -flto -ffat-lto-objects -fuse-linker-plugin 41 | 42 | # Freestanding, static and short wchar_t (instead of int) 43 | CFLAGS += -ffreestanding -static -fshort-wchar 44 | 45 | # Make GCC play nicely with volatile 46 | CFLAGS += -fstrict-volatile-bitfields 47 | 48 | # No stdlib 49 | CFLAGS += -nostdlib 50 | 51 | # Use proper linker script 52 | CFLAGS += -T$(APP_SHARED)/app.ld 53 | 54 | # Include the lib and kernel folders in include path 55 | CFLAGS += -I$(APP_SHARED) 56 | 57 | #----------------------------------------------------------------------------------------------------------------------- 58 | # Target specific stuff 59 | #----------------------------------------------------------------------------------------------------------------------- 60 | 61 | # Use literal pools 62 | CFLAGS += -mauto-litpools 63 | 64 | # We have the const16 instruction, so use it 65 | CFLAGS += -mconst16 66 | 67 | # We are kernel 68 | CFLAGS += -mforce-no-pic 69 | 70 | # Align branch targets 71 | CFLAGS += -mtarget-align 72 | 73 | # Allow VLIW 74 | CFLAGS += -Wa,--flix 75 | 76 | # Tells the assembler that a long call may be needed, it does not seem 77 | # to affect code gen too much with lto 78 | CFLAGS += -mlongcalls 79 | 80 | ######################################################################################################################## 81 | # Targets 82 | ######################################################################################################################## 83 | 84 | .PHONY: all clean 85 | 86 | all: $(BIN_DIR)/$(APP_NAME).bin 87 | 88 | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) 89 | DEPS := $(OBJS:%.o=%.d) 90 | -include $(DEPS) 91 | 92 | $(BIN_DIR)/$(APP_NAME).bin: $(BUILD_DIR)/$(APP_NAME).elf 93 | @mkdir -p $(@D) 94 | @echo OBJCOPY $(BUILD_DIR)/$(APP_NAME).bin.header 95 | @$(OBJCOPY) -O binary -j .header $^ $(BUILD_DIR)/$(APP_NAME).bin.header 96 | @echo OBJCOPY $(BUILD_DIR)/$(APP_NAME).bin.text 97 | @$(OBJCOPY) -O binary -j .text $^ $(BUILD_DIR)/$(APP_NAME).bin.text 98 | @echo OBJCOPY $(BUILD_DIR)/$(APP_NAME).bin.data 99 | @$(OBJCOPY) -O binary -j .data $^ $(BUILD_DIR)/$(APP_NAME).bin.data 100 | @echo CAT $@ 101 | @cat \ 102 | $(BUILD_DIR)/$(APP_NAME).bin.header \ 103 | $(BUILD_DIR)/$(APP_NAME).bin.text \ 104 | $(BUILD_DIR)/$(APP_NAME).bin.data > $@ 105 | 106 | $(BUILD_DIR)/$(APP_NAME).elf: $(OBJS) 107 | @echo LD $@ 108 | @mkdir -p $(@D) 109 | @$(LD) $(CFLAGS) -o $@ $^ 110 | 111 | $(BUILD_DIR)/%.c.o: %.c 112 | @echo CC $@ 113 | @mkdir -p $(@D) 114 | @$(CC) $(CFLAGS) -MMD -c $< -o $@ 115 | 116 | clean: 117 | rm -rf out 118 | -------------------------------------------------------------------------------- /apps/shared/syscall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef enum syscall { 7 | // 8 | // ABI syscalls 9 | // 10 | SYSCALL_SPILL = 0x00, 11 | SYSCALL_XTENSA = 0x01, 12 | // 0x02 13 | // 0x03 14 | // 0x04 15 | // 0x05 16 | // 0x06 17 | // 0x07 18 | 19 | // 20 | // Normal syscalls 21 | // 22 | 23 | SYSCALL_SCHED_PARK = 0x08, 24 | SYSCALL_SCHED_YIELD = 0x09, 25 | SYSCALL_SCHED_DROP = 0x0a, 26 | // 0x0b 27 | // 0x0c 28 | // 0x0d 29 | // 0x0e 30 | SYSCALL_LOG = 0x0f, 31 | } syscall_t; 32 | 33 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 34 | // Syscall helpers 35 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 36 | 37 | static inline uint32_t syscall0(syscall_t syscall) { 38 | register int a2 asm("a2") = syscall; 39 | __asm__ volatile ("SYSCALL" : "+r"(a2) :: "memory"); 40 | return a2; 41 | } 42 | 43 | static inline uint32_t syscall2(syscall_t syscall, size_t arg0, size_t arg1) { 44 | register int a2 asm("a2") = syscall; 45 | register int a6 asm("a6") = arg0; 46 | register int a3 asm("a3") = arg1; 47 | __asm__ volatile ("SYSCALL" : "+r"(a2) : "r"(a6), "r"(a3) : "memory"); 48 | return a2; 49 | } 50 | 51 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 52 | // Wrappers 53 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 54 | 55 | static inline void sys_sched_park() { 56 | syscall0(SYSCALL_SCHED_PARK); 57 | } 58 | 59 | static inline void sys_sched_yield() { 60 | syscall0(SYSCALL_SCHED_YIELD); 61 | } 62 | 63 | static inline void sys_sched_drop() { 64 | syscall0(SYSCALL_SCHED_DROP); 65 | } 66 | 67 | static inline void sys_log(const char* str, size_t size) { 68 | syscall2(SYSCALL_LOG, (uintptr_t)str, size); 69 | } 70 | -------------------------------------------------------------------------------- /docs/flash.md: -------------------------------------------------------------------------------- 1 | # Flash 2 | 3 | ## Hardware 4 | 5 | The TTGO T-Watch-2020-V2 seems to have a Winbond W25Q128W Serial NOR flash. 6 | 7 | ## Software 8 | 9 | We are going to use the internal flash for two main things: 10 | - Storing installed apps 11 | - Storing NVM variables 12 | 13 | For this we are going to base ourselves on the UEFI Firmware File System 14 | set of formats, we are going to use them mostly because of how well tested 15 | they are given every AMI UEFI bios essentially uses them, and they are 16 | optimized for flash devices, with all the downsides and upsides. 17 | 18 | We are also going to support the Fault-Tolerant-Write design that edk2 (idr 19 | if it is part of the UEFI spec) provides, this should allow for flash updates 20 | to work even if the board suddenly shuts down in the middle of a write. Allowing 21 | to restore the contents if needed. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/kernel.bt: -------------------------------------------------------------------------------- 1 | //------------------------------------------------ 2 | //--- 010 Editor v12.0.1 Binary Template 3 | // 4 | // File: kernel.bt 5 | // Authors: Itay Almog (itay2828@gmail.com) 6 | // Version: 1.0 7 | // Purpose: Inspect ESP32 Microkernel Kernel file 8 | // Category: 9 | // File Mask: 10 | // ID Bytes: 11 | // History: 12 | //------------------------------------------------ 13 | 14 | struct kernel_header { 15 | char magic[4]; 16 | uint code_size; 17 | uint data_size; 18 | uint vdso_size; 19 | uint bss_size; 20 | uint entry_point ; 21 | }; 22 | 23 | kernel_header header; 24 | local int code_offset = sizeof(kernel_header); 25 | local int data_offset = code_offset + header.code_size; 26 | local int vdso_offset = data_offset + header.data_size; 27 | 28 | FSeek(code_offset); 29 | char code[header.code_size]; 30 | 31 | FSeek(data_offset); 32 | char data[header.data_size]; 33 | 34 | FSeek(vdso_offset); 35 | char vdso[header.vdso_size]; 36 | -------------------------------------------------------------------------------- /docs/memory.md: -------------------------------------------------------------------------------- 1 | # Memory map 2 | 3 | ## BootROM 4 | 5 | This is the first code which runs, and it will eventually run our 6 | loader code. 7 | 8 | ### SRAM 1 (0x3FFE_0000 - 0x3FFE_FFFF) 9 | 10 | Seems to contain XTOS data. Looking at the symbols it only 11 | takes the first 64KB in there. 12 | 13 | This range also includes the initial stack. 14 | 15 | ### SRAM 2 (0x3FFA_E000 - 0x3FFB_FF70) 16 | Contains alot of the integrated newlib and I think bluetooth drivers 17 | data. It takes about 72KB. 18 | 19 | ## Loader 20 | 21 | The loader needs to respect both the kernel and the bootrom memory ranges, and its main 22 | goal is to essentially setup a nice environment for the kernel to start in. 23 | 24 | For the loader: 25 | - 0x4008_0000 - 0x4009_FFFF (128K): Loader code 26 | - 0x3FFC_0000 - 0x3FFC_1FFF (8K): Loader data 27 | - 0x3FFC_2000 - 0x3FFC_FFFF (56K): initrd 28 | - 0x3FFD_0000 - 0x3FFD_FFFF (64K): Temp buffer 29 | 30 | *TODO: currently we have a huge overkill on the loader code, maybe we 31 | can use this for something else* 32 | 33 | The main problem is the collision between the kernel data and the bootrom using both 34 | the SRAM 1 data range, to avoid having problems when loading the kernel we will read 35 | everything to a temp buffer, and then we will copy from there to the real range, once 36 | we are sure that we don't need the bootrom anymore. 37 | 38 | ## Kernel 39 | 40 | For the kernel mode we have a very defined memory map. 41 | - User code and data: those are set in areas where we have configurable MMU 42 | - Kernel code and data: those are set in areas where we have a static MMU 43 | 44 | | Name | Address Range | Size | Usage | 45 | |-------------------|-----------------------------|---------|---------------| 46 | | SRAM 2 | 0x3FFA_E000 - 0x3FFB_FFFF | 72KB | Kernel heap | 47 | | SRAM 2 | 0x3FFC_0000 - 0x3FFD_FFFF | 128KB | User data | 48 | | SRAM 1 | 0x3FFE_0000 - 0x3FFF_FFFF | 128KB | Kernel Data | 49 | | SRAM 1 | 0x400A_0000 - 0x400B_FFFF | 128KB | Kernel Code | 50 | | SRAM 0 | 0x4007_0000 - 0x4007_FFFF | 64KB | Cache | 51 | | SRAM 0 | 0x4008_0000 - 0x4009_FFFF | 128KB | User Code | 52 | 53 | *Note: SRAM 1 code and data are aliases, so there is a total of 128KB for both* 54 | 55 | ### vDSO 56 | 57 | Because of how the MMU/MPU/PID Controller works, we need to have the vecbase be mapped to usermode, 58 | which is actually fine, usermode can't modify code, so we can safely share this between all the 59 | processes allowing us to only reserve 8kb for this, giving the process 120kb code, we can take this 60 | to also include other useful code while we are at it. 61 | 62 | Note that we must have this page always mapped and can't have it swapped, that is because we are going 63 | to do swapping with an exception, which won't work for us if it is swapped out... 64 | -------------------------------------------------------------------------------- /kernel/Makefile: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # ESP32 loader 3 | ######################################################################################################################## 4 | 5 | #----------------------------------------------------------------------------------------------------------------------- 6 | # Build constants 7 | #----------------------------------------------------------------------------------------------------------------------- 8 | 9 | TOOLCHAIN_PATH := ../toolchain 10 | include $(TOOLCHAIN_PATH)/toolchain.mk 11 | include $(TOOLCHAIN_PATH)/esptool.mk 12 | 13 | OUT_DIR := out 14 | BIN_DIR := $(OUT_DIR)/bin 15 | BUILD_DIR := $(OUT_DIR)/build 16 | 17 | #----------------------------------------------------------------------------------------------------------------------- 18 | # General configurations 19 | #----------------------------------------------------------------------------------------------------------------------- 20 | 21 | CFLAGS := -Werror -std=gnu11 22 | CFLAGS += -Wno-unused-label 23 | CFLAGS += -Wno-address-of-packed-member 24 | CFLAGS += -Wno-psabi 25 | CFLAGS += -Wno-stringop-overflow 26 | 27 | # Size optimization, include debug info 28 | CFLAGS += -Og -g3 29 | 30 | # Link time optimization 31 | #CFLAGS += -flto -ffat-lto-objects -fuse-linker-plugin 32 | 33 | # Freestanding, static and short wchar_t (instead of int) 34 | CFLAGS += -ffreestanding -static -fshort-wchar 35 | 36 | # Make GCC play nicely with volatile 37 | CFLAGS += -fstrict-volatile-bitfields 38 | 39 | # No stdlib 40 | CFLAGS += -nostdlib 41 | 42 | # Use proper linker script 43 | CFLAGS += -Tsrc/linker.ld 44 | 45 | # Include the lib and kernel folders in include path 46 | CFLAGS += -Isrc -I../apps/shared 47 | 48 | #----------------------------------------------------------------------------------------------------------------------- 49 | # Target specific stuff 50 | #----------------------------------------------------------------------------------------------------------------------- 51 | 52 | # Use literal pools 53 | CFLAGS += -mauto-litpools 54 | 55 | # We have the const16 instruction, so use it 56 | CFLAGS += -mconst16 57 | 58 | # We are kernel 59 | CFLAGS += -mforce-no-pic 60 | 61 | # Align branch targets 62 | CFLAGS += -mtarget-align 63 | 64 | # Allow VLIW 65 | CFLAGS += -Wa,--flix 66 | 67 | # Tells the assembler that a long call may be needed, it does not seem 68 | # to affect code gen too much with lto 69 | CFLAGS += -mlongcalls 70 | 71 | #----------------------------------------------------------------------------------------------------------------------- 72 | # Sources 73 | #----------------------------------------------------------------------------------------------------------------------- 74 | 75 | SRCS := $(shell find src -name '*.c') 76 | SRCS += $(shell find src -name '*.S') 77 | 78 | # 79 | # UMM 80 | # 81 | 82 | SRCS += libs/umm_malloc/src/umm_malloc.c 83 | 84 | CFLAGS += -Ilibs/umm_malloc/src 85 | CFLAGS += -DUMM_CFGFILE="" 86 | 87 | ######################################################################################################################## 88 | # Targets 89 | ######################################################################################################################## 90 | 91 | .PHONY: all clean 92 | 93 | all: $(BIN_DIR)/kernel.bin 94 | 95 | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) 96 | DEPS := $(OBJS:%.o=%.d) 97 | -include $(DEPS) 98 | 99 | $(BIN_DIR)/kernel.bin: $(BUILD_DIR)/kernel.elf 100 | @mkdir -p $(@D) 101 | @echo OBJCOPY $(BUILD_DIR)/kernel.bin.header 102 | @$(OBJCOPY) -O binary -j .header $^ $(BUILD_DIR)/kernel.bin.header 103 | @echo OBJCOPY $(BUILD_DIR)/kernel.bin.text 104 | @$(OBJCOPY) -O binary -j .text $^ $(BUILD_DIR)/kernel.bin.text 105 | @echo OBJCOPY $(BUILD_DIR)/kernel.bin.data 106 | @$(OBJCOPY) -O binary -j .data $^ $(BUILD_DIR)/kernel.bin.data 107 | @echo OBJCOPY $(BUILD_DIR)/kernel.bin.vdso 108 | @$(OBJCOPY) -O binary -j .vdso $^ $(BUILD_DIR)/kernel.bin.vdso 109 | @echo CAT $@ 110 | @cat \ 111 | $(BUILD_DIR)/kernel.bin.header \ 112 | $(BUILD_DIR)/kernel.bin.text \ 113 | $(BUILD_DIR)/kernel.bin.data \ 114 | $(BUILD_DIR)/kernel.bin.vdso > $@ 115 | 116 | $(BUILD_DIR)/kernel.elf: $(OBJS) 117 | @echo LD $@ 118 | @mkdir -p $(@D) 119 | @$(LD) $(CFLAGS) -o $@ $^ 120 | 121 | $(BUILD_DIR)/%.c.o: %.c 122 | @echo CC $@ 123 | @mkdir -p $(@D) 124 | @$(CC) $(CFLAGS) -MMD -c $< -o $@ 125 | 126 | $(BUILD_DIR)/%.S.o: %.S 127 | @echo CC $@ 128 | @mkdir -p $(@D) 129 | @$(CC) $(CFLAGS) -c $< -o $@ 130 | 131 | clean: 132 | rm -rf out 133 | -------------------------------------------------------------------------------- /kernel/src/arch/cpu.c: -------------------------------------------------------------------------------- 1 | #include "cpu.h" 2 | #include "intrin.h" 3 | 4 | per_cpu_context_t g_per_cpu_context[CPU_COUNT] = {}; 5 | 6 | /** 7 | * The cpu context for the current cpu 8 | */ 9 | per_cpu_context_t* get_cpu_context() { 10 | return &g_per_cpu_context[get_cpu_index()]; 11 | } 12 | 13 | uint32_t get_cpu_index() { 14 | // this is taken from esp-idf 15 | // cpu_ll_get_core_id 16 | return (__RSR(PRID) >> 13) & 1; 17 | } 18 | -------------------------------------------------------------------------------- /kernel/src/arch/cpu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #define CPU_COUNT 2 9 | 10 | typedef struct per_cpu_context { 11 | //------------------------------ 12 | // For PID management 13 | //------------------------------ 14 | 15 | // Timestamp for LRU of pids 16 | uint64_t next_stamp; 17 | 18 | // the per-cpu pid binding 19 | pid_binding_t pid_bindings[PID_BINDING_COUNT]; 20 | 21 | // currently active pid 22 | pid_binding_t* primary_binding; 23 | 24 | //------------------------------ 25 | // For scheduler 26 | //------------------------------ 27 | 28 | // how many disables we had 29 | int preempt_disable_depth; 30 | 31 | // the currently running task 32 | task_t* current_task; 33 | 34 | // callback to be called when parking 35 | void(*park_callback)(void* arg); 36 | void* park_arg; 37 | } per_cpu_context_t; 38 | 39 | /** 40 | * The per-cpu context, accessible as a global array 41 | */ 42 | extern per_cpu_context_t g_per_cpu_context[CPU_COUNT]; 43 | 44 | /** 45 | * The cpu context for the current cpu 46 | */ 47 | per_cpu_context_t* get_cpu_context(); 48 | 49 | /** 50 | * Get the ID of the current cpu, it is always going to 51 | * be less than CPU_COUNT 52 | */ 53 | uint32_t get_cpu_index(); 54 | -------------------------------------------------------------------------------- /kernel/src/arch/interrupts.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | /** 5 | * this is going to save the full context of the caller 6 | * to the stack, this should be done if we are going to 7 | * call into C code to do stuff instead of doing it 8 | * with assembly and we know what we touch 9 | * 10 | * assume that original a0 and a1 are stored and that 11 | * sp has a valid kernel stack for us to use 12 | */ 13 | .type save_full_interrupt_context,@function 14 | .align 4 15 | .literal_position 16 | .align 4 17 | save_full_interrupt_context: 18 | // save ps and pc 19 | rsr.epc1 a2 20 | s32i a2, sp, TASK_REGS_PC 21 | rsr.ps a2 22 | s32i a2, sp, TASK_REGS_PS 23 | 24 | // save sar 25 | rsr.sar a2 26 | s32i a2, sp, TASK_REGS_SAR 27 | 28 | // save loop regs 29 | rsr.lbeg a2 30 | s32i a2, sp, TASK_REGS_LBEG 31 | rsr.lend a2 32 | s32i a2, sp, TASK_REGS_LEND 33 | rsr.lcount a2 34 | s32i a2, sp, TASK_REGS_LCOUNT 35 | 36 | // 37 | // save the orig window base/start 38 | // 39 | rsr.windowbase a2 40 | rsr.windowstart a3 41 | s32i a2, sp, TASK_REGS_WINDOWBASE 42 | s32i a3, sp, TASK_REGS_WINDOWSTART 43 | 44 | // 45 | // spill all the registers 46 | // 47 | // this may seem complex but is actually really simple: 48 | // 49 | // 1. we are going to start by aligning our windowstart with the windowbase, 50 | // by doing a rotr16 on it, this will make it easier to figure out which 51 | // frames need to be saved later on 52 | // 2. save a4-a15, if we detect that in one of the 3 ranges we have a live range 53 | // we are going to skip saving the rest and use the spill loop (stage 3) 54 | // 3. we are going to figure how much frames should be kept, that is done in couple steps: 55 | // a. clear the first bit representing the current range 56 | // b. keep only the first set bit 57 | // c. count trailing zeros + 1 58 | // that essentially gives us the depth of the register stack as it is 59 | // 4. go in a loop and rotate the window backwards, saving 4 registers at a time 60 | // 5. restore the window base/start 61 | // 62 | // We are also going to store the lower 4 bits of the rotated window and 63 | // the amount of frames that need to be saved in a special windowmask entry 64 | // in our task_regs_t. (will be saved as 2 16bit values) 65 | // 66 | 67 | // save the windowstart for later use 68 | wsr.excsave1 a3 69 | 70 | // 1. rotate the windowstart, we also going to use SAR 71 | // as means to actually save the windowbase for when 72 | // we want to restore it later on after the spilling 73 | ssr a2 74 | slli a2, a3, 32 - 16 75 | src a2, a3, a2 76 | srli a2, a2, 32 - 16 77 | s16i a2, sp, TASK_REGS_WINDOWMASK 78 | 79 | // 2. save live registers, we are going to branch out 80 | // to full spill if there is an overflow 81 | 82 | bbsi a2, 1, .full_spill 83 | s32i a4, sp, TASK_REGS_AR(4) 84 | s32i a5, sp, TASK_REGS_AR(5) 85 | s32i a6, sp, TASK_REGS_AR(6) 86 | s32i a7, sp, TASK_REGS_AR(7) 87 | 88 | bbsi a2, 2, .full_spill 89 | s32i a8, sp, TASK_REGS_AR(8) 90 | s32i a9, sp, TASK_REGS_AR(9) 91 | s32i a10, sp, TASK_REGS_AR(10) 92 | s32i a11, sp, TASK_REGS_AR(11) 93 | 94 | bbsi a2, 3, .full_spill 95 | s32i a12, sp, TASK_REGS_AR(12) 96 | s32i a13, sp, TASK_REGS_AR(13) 97 | s32i a14, sp, TASK_REGS_AR(14) 98 | s32i a15, sp, TASK_REGS_AR(15) 99 | 100 | // lastly check if there are even more frames, if not we 101 | // don't need to spill anything else 102 | bnei a2, 1, .full_spill 103 | j .done_spill 104 | 105 | .full_spill: 106 | // 107 | // 3. figure out how many frames we need to keep 108 | // 109 | 110 | // a. turn off the first bit, we can assume that the first bit 111 | // is always set, so just subtracting one will work fine 112 | addi a3, a2, -1 113 | 114 | // b. negate the number, and then and it with the version that does have 115 | // the first bit set, this will make sure that the only bit that stays 116 | // is the first one, if it was set, especially after we cleared it in 117 | // the previous instruction 118 | neg a3, a3 119 | and a3, a3, a2 120 | 121 | // c. count trailing zeros + 1, we have the NSAU instruction 122 | // that does exactly that, we just need to subtract 15 123 | // since we want the +1 and ws is only 16 bits, so we don't 124 | // need that big of a number 125 | nsau a2, a3 126 | addi a2, a2, -15 127 | 128 | // store the window frame count in the windowsize 129 | s16i a2, sp, TASK_REGS_WINDOWSIZE 130 | 131 | // 4. we are now ready to the rest of the spilling 132 | // because we are going backwards we also need to do 133 | // the storing backwards, so the register window in 134 | // memory will make sense, so we store from the end 135 | // and each iteration move the base backwards so it 136 | // stays the same offset 137 | 138 | .spill_loop: 139 | // rotate the window backwards, we will now have our 140 | // stack pointer at a5 instead 141 | rotw -1 142 | s32i a0, a5, TASK_REGS_AR_END - 16 143 | s32i a1, a5, TASK_REGS_AR_END - 12 144 | s32i a2, a5, TASK_REGS_AR_END - 8 145 | s32i a3, a5, TASK_REGS_AR_END - 4 146 | 147 | // decrement the count, it is now in a6 148 | addi a2, a6, -1 149 | 150 | // we are going to move 16 bytes backwards 151 | addi a1, a5, -16 152 | 153 | // If we reached zero we are done 154 | bnez a2, .spill_loop 155 | 156 | // now set correctly the windowbase and windowstart 157 | // we moved around to spill everything, and now that we 158 | // are done spilling we are going to set the windowstart 159 | // to 1 shift the windowbase, and we are going to set 160 | // the windowbase as it was before, this will essentially 161 | // clear everything but keep the a0 and a1 as they were at the start 162 | rsr.sar a2 163 | movi a3, 1 164 | ssl a2 // Set Shift Amount for Left Shift 165 | sll a3, a3 // Shift Left Logical 166 | wsr.windowstart a3 167 | wsr.windowbase a2 168 | rsync 169 | 170 | .done_spill: 171 | 172 | // return normally 173 | ret 174 | .size save_full_interrupt_context, . - save_full_interrupt_context 175 | 176 | /** 177 | * Just like the save_full_interrupt_context, this is going to take an entire 178 | * task_regs_t context that was saved on exception or modified by the kernel and 179 | * restore it to continue from the exception 180 | */ 181 | .type restore_full_interrupt_context,@function 182 | .align 4 183 | .literal_position 184 | .align 4 185 | restore_full_interrupt_context: 186 | // 187 | // restore the window, this is tricky since we need 188 | // to preserve two registers, the sp and the ar0 189 | // 190 | 191 | // start by saving the sp in excsave1 and a0 in epc1 192 | wsr.excsave1 sp 193 | wsr.epc1 a0 194 | 195 | // now switch it 196 | l32i a2, sp, TASK_REGS_WINDOWBASE 197 | l32i a3, sp, TASK_REGS_WINDOWSTART 198 | wsr.windowstart a3 199 | wsr.windowbase a2 200 | rsync 201 | 202 | // restore the return and the stack pointer 203 | rsr.excsave1 sp 204 | rsr.epc1 a0 205 | 206 | // put the original windowbase in excsave1 207 | // as we will need it to calculate how many 208 | // regs to clear 209 | wsr.excsave1 a2 210 | 211 | // 212 | // now we need to restore all the spill area 213 | // 214 | 215 | // load the window size, aka the count we need to restore 216 | // this was calculated when we last spilled for ease of use 217 | l16ui a2, sp, TASK_REGS_WINDOWSIZE 218 | 219 | // skip the current frame, it will be restored later on 220 | rotw -1 221 | 222 | // check if we need to actually restore anything 223 | beqz a6, .zero_regs 224 | 225 | // TODO: why are we allowed to do this? 226 | mov a2, a6 227 | mov a3, a5 228 | 229 | // to understand this loop it is the best to read the documentation at the 230 | // spill function, we do essentially the same just backwards 231 | .restore_loop: 232 | // go backwards one 233 | rotw -1 234 | 235 | // set the task_regs offset and decrement 236 | // the count, do it before we load the correct regs 237 | addi a3, a7, -16 238 | addi a2, a6, -1 239 | 240 | // load all the registers 241 | l32i a4, a3, TASK_REGS_AR_END 242 | l32i a5, a3, TASK_REGS_AR_END + 4 243 | l32i a6, a3, TASK_REGS_AR_END + 8 244 | l32i a7, a3, TASK_REGS_AR_END + 12 245 | 246 | // If we reached zero we are done 247 | bnez a2, .restore_loop 248 | 249 | .zero_regs: 250 | // we restored everything that we need to restore, now clear 251 | // the rest of the registers so nothing will get leaked from 252 | // the kernel to the user 253 | 254 | // calculate how many registers we need to clear by doing: 255 | // windowbase - original_windowbase 256 | // If it is zero it means there is nothing to clear 257 | rsr.windowbase a0 258 | rsr.excsave1 a3 259 | sub a3, a0, a3 260 | beqz a3, .done_zeroing_regs 261 | 262 | // only take the bits we care about, in case it 263 | // got an underflow 264 | extui a3, a3, 0, 4 265 | 266 | .zero_loop: 267 | // just like the normal restore loop, but 268 | // we are going to just set it to zero in here 269 | // instead of loading it from the task_regs 270 | rotw -1 271 | 272 | // decrement loop count 273 | addi a3, a7, -1 274 | 275 | // zero the regs 276 | movi a4, 0 277 | movi a5, 0 278 | movi a6, 0 279 | movi a7, 0 280 | 281 | // go until we reach the frame we started at, 282 | // so until we reach 1 283 | bgei a3, 1, .zero_loop 284 | 285 | .done_zeroing_regs: 286 | // we are now back to where we were, we can 287 | // continue and restore whatever else we need 288 | // to restore 289 | 290 | // restore the PC and PS 291 | l32i a2, sp, TASK_REGS_PC 292 | wsr.epc1 a2 293 | l32i a2, sp, TASK_REGS_PS 294 | wsr.ps a2 295 | 296 | // restore shift reg 297 | l32i a2, sp, TASK_REGS_SAR 298 | wsr.sar a2 299 | 300 | // restore loop regs 301 | l32i a2, sp, TASK_REGS_LBEG 302 | wsr.lbeg a2 303 | l32i a2, sp, TASK_REGS_LEND 304 | wsr.lend a2 305 | l32i a2, sp, TASK_REGS_LCOUNT 306 | wsr.lcount a2 307 | 308 | // we are done restoring everything else, lets 309 | // finish by restoring the rest of the 16 original 310 | // registers that we have not restored yet, we are 311 | // going to use the mask just like we do in the spilling 312 | l16ui a2, sp, TASK_REGS_WINDOWMASK 313 | 314 | bbsi a2, 1, .done_restore_regs 315 | l32i a4, sp, TASK_REGS_AR(4) 316 | l32i a5, sp, TASK_REGS_AR(5) 317 | l32i a6, sp, TASK_REGS_AR(6) 318 | l32i a7, sp, TASK_REGS_AR(7) 319 | 320 | bbsi a2, 2, .done_restore_regs 321 | l32i a8, sp, TASK_REGS_AR(8) 322 | l32i a9, sp, TASK_REGS_AR(9) 323 | l32i a10, sp, TASK_REGS_AR(10) 324 | l32i a11, sp, TASK_REGS_AR(11) 325 | 326 | bbsi a2, 3, .done_restore_regs 327 | l32i a12, sp, TASK_REGS_AR(12) 328 | l32i a13, sp, TASK_REGS_AR(13) 329 | l32i a14, sp, TASK_REGS_AR(14) 330 | l32i a15, sp, TASK_REGS_AR(15) 331 | 332 | .done_restore_regs: 333 | // the last a0-a3 regs will be manually restored 334 | // later on just before we call the rfe instruction 335 | 336 | // finally rsync it all to make sure that 337 | // everything is updated 338 | rsync 339 | 340 | // return normally 341 | ret 342 | .size restore_full_interrupt_context, . - restore_full_interrupt_context 343 | 344 | /** 345 | * While running in the kernel we want to enable the exception mode 346 | * and enable the use of the register window, but while we are doing 347 | * restore work we don't want this to happen, and we want the rotate 348 | * window to just rotate 349 | */ 350 | enable_kernel_exceptions: 351 | // set WOE = 1 352 | // INTLEVEL = 1 353 | movi a2, (1 << 18) | 0xf 354 | wsr.ps a2 355 | rsync 356 | ret 357 | 358 | /** 359 | * This sets the kernel exception handling 360 | * so we can do proper restore 361 | */ 362 | disable_kernel_exceptions: 363 | // set EXCM = 1 364 | movi a2, (1 << 4) | 0xf 365 | wsr.ps a2 366 | rsync 367 | ret 368 | 369 | /*********************************************************************************************************************** 370 | * Normal exception handler, this calls an exception handler routine that will handle 371 | * specifically exceptions that were raised 372 | **********************************************************************************************************************/ 373 | .global common_exception_entry 374 | .type common_exception_entry,@function 375 | .align 4 376 | .literal_position 377 | .align 4 378 | common_exception_entry: 379 | // spill the full context 380 | call0 save_full_interrupt_context 381 | 382 | // enable exceptions for C code 383 | call0 enable_kernel_exceptions 384 | 385 | // now call the common interrupt handler with 386 | // the task_regs_t 387 | mov a6, sp 388 | call4 common_exception_handler 389 | 390 | // disable exceptions 391 | call0 disable_kernel_exceptions 392 | 393 | // restore the context that we saved 394 | call0 restore_full_interrupt_context 395 | 396 | // confirm PID 397 | movi a0, PIDCTRL_PID_CONFIRM 398 | movi a2, 1 399 | s32i a2, a0, 0 400 | 401 | // finish up by restoring a0-a2 and finally 402 | // return from the exception, a1 must be restored 403 | // last since it is the actual stack pointer 404 | l32i a0, sp, TASK_REGS_AR(0) 405 | l32i a2, sp, TASK_REGS_AR(2) 406 | l32i a3, sp, TASK_REGS_AR(3) 407 | l32i a1, sp, TASK_REGS_AR(1) 408 | rfe 409 | .size common_exception_entry, . - common_exception_entry 410 | 411 | /*********************************************************************************************************************** 412 | * Normal interrupt handler, this calls an exception handler routine that will handle 413 | * specifically exceptions that were raised 414 | **********************************************************************************************************************/ 415 | .global common_interrupt_entry 416 | .type common_interrupt_entry,@function 417 | .align 4 418 | .literal_position 419 | .align 4 420 | common_interrupt_entry: 421 | // spill the full context 422 | call0 save_full_interrupt_context 423 | 424 | // enable exceptions for C code 425 | call0 enable_kernel_exceptions 426 | 427 | // now call the common interrupt handler with 428 | // the task_regs_t 429 | mov a6, sp 430 | call4 common_interrupt_handler 431 | 432 | // disable exceptions 433 | call0 disable_kernel_exceptions 434 | 435 | // restore the context that we saved 436 | call0 restore_full_interrupt_context 437 | 438 | // confirm PID 439 | movi a0, PIDCTRL_PID_CONFIRM 440 | movi a2, 1 441 | s32i a2, a0, 0 442 | 443 | // finish up by restoring a0-a2 and finally 444 | // return from the exception, a1 must be restored 445 | // last since it is the actual stack pointer 446 | l32i a0, sp, TASK_REGS_AR(0) 447 | l32i a2, sp, TASK_REGS_AR(2) 448 | l32i a3, sp, TASK_REGS_AR(3) 449 | l32i a1, sp, TASK_REGS_AR(1) 450 | rfe 451 | .size common_interrupt_entry, . - common_interrupt_entry 452 | 453 | /*********************************************************************************************************************** 454 | * syscall entry, this calls the syscall handling code 455 | **********************************************************************************************************************/ 456 | .global syscall_entry 457 | .type syscall_entry,@function 458 | .align 4 459 | syscall_entry: 460 | // start by fixing the nextPC 461 | rsr.epc1 a0 // skip the syscall instruction 462 | addi a0, a0, 3 463 | 464 | // check if we need to emulate a loop 465 | // aka, PC == LEND && LCOUNT != 0 466 | rsr.lend a2 467 | bne a0, a2, .skipped_syscall 468 | rsr.lcount a2 469 | beqz a2, .skipped_syscall 470 | 471 | // we need to emulate it 472 | addi a2, a2, -1 // decrement LCOUNT 473 | wsr.lcount a2 474 | rsr.lbeg a0 // set the PC to LBEG 475 | 476 | .skipped_syscall: 477 | // actually update the PC 478 | wsr.epc1 a0 479 | 480 | // TODO: fast path, that won't save everything 481 | 482 | j .slow_path 483 | 484 | .fast_path: 485 | //------------------------------------------------- 486 | // fast path, check for IPC syscall and 487 | // small payload 488 | //------------------------------------------------- 489 | 490 | // TODO: do fast path handling 491 | 492 | // return from the fast path 493 | j .return_from_syscall 494 | 495 | .slow_path: 496 | //------------------------------------------------- 497 | // slow path, we are going to call C code, 498 | // save everything 499 | //------------------------------------------------- 500 | 501 | // spill the full context 502 | call0 save_full_interrupt_context 503 | 504 | // enable exceptions for C code 505 | call0 enable_kernel_exceptions 506 | 507 | // now call the common interrupt handler with 508 | // the task_regs_t 509 | mov a6, sp 510 | call4 common_syscall_handler 511 | 512 | // disable exceptions 513 | call0 disable_kernel_exceptions 514 | 515 | // restore the context that we saved 516 | call0 restore_full_interrupt_context 517 | 518 | .return_from_syscall: 519 | // confirm PID 520 | movi a0, PIDCTRL_PID_CONFIRM 521 | movi a2, 1 522 | s32i a2, a0, 0 523 | 524 | // finish up by restoring a0-a2 and finally 525 | // return from the exception, a1 must be restored 526 | // last since it is the actual stack pointer 527 | l32i a0, sp, TASK_REGS_AR(0) 528 | l32i a2, sp, TASK_REGS_AR(2) 529 | l32i a3, sp, TASK_REGS_AR(3) 530 | l32i a1, sp, TASK_REGS_AR(1) 531 | rfe 532 | .size syscall_entry, . - syscall_entry -------------------------------------------------------------------------------- /kernel/src/arch/interrupts.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "interrupts.h" 3 | #include "task/task.h" 4 | #include "intrin.h" 5 | #include "cpu.h" 6 | #include "drivers/timg.h" 7 | #include "task/scheduler.h" 8 | 9 | void common_exception_entry(void); 10 | void common_interrupt_entry(void); 11 | void syscall_entry(void); 12 | 13 | void* g_fast_interrupts[64] = { 14 | // start with everything 15 | [0 ... 63] = common_exception_entry, 16 | 17 | // override the entry for syscall 18 | [SyscallCause] = syscall_entry, 19 | [Level1InterruptCause] = common_interrupt_entry, 20 | }; 21 | 22 | static const char* m_cause_str[] = { 23 | [IllegalInstructionCause] = "IllegalInstructionCause", 24 | [SyscallCause] = "SyscallCause", 25 | [InstructionFetchErrorCause] = "InstructionFetchErrorCause", 26 | [LoadStoreErrorCause] = "LoadStoreErrorCause", 27 | [Level1InterruptCause] = "Level1InterruptCause", 28 | [AllocaCause] = "AllocaCause", 29 | [IntegerDivideByZero] = "IntegerDivideByZero", 30 | [PrivilegedCause] = "PrivilegedCause", 31 | [LoadStoreAlignmentCause] = "LoadStoreAlignmentCause", 32 | [InstrPIFDataErrorCause] = "InstrPIFDataErrorCause", 33 | [LoadStorePIFDataErrorCause] = "LoadStorePIFDataErrorCause", 34 | [InstrPIFAddrErrorCause] = "InstrPIFAddrErrorCause", 35 | [LoadStorePIFAddrErrorCause] = "LoadStorePIFAddrErrorCause", 36 | [InstTLBMissCause] = "InstTLBMissCause", 37 | [InstTLBMultiHitCause] = "InstTLBMultiHitCause", 38 | [InstFetchPrivilegeCause] = "InstFetchPrivilegeCause", 39 | [InstFetchProhibitedCause] = "InstFetchProhibitedCause", 40 | [LoadStoreTLBMissCause] = "LoadStoreTLBMissCause", 41 | [LoadStoreTLBMultiHitCause] = "LoadStoreTLBMultiHitCause", 42 | [LoadStorePrivilegeCause] = "LoadStorePrivilegeCause", 43 | [LoadProhibitedCause] = "LoadProhibitedCause", 44 | [StoreProhibitedCause] = "StoreProhibitedCause", 45 | [Coprocessor0Disabled] = "Coprocessor0Disabled", 46 | [Coprocessor1Disabled] = "Coprocessor1Disabled", 47 | [Coprocessor2Disabled] = "Coprocessor2Disabled", 48 | [Coprocessor3Disabled] = "Coprocessor3Disabled", 49 | [Coprocessor4Disabled] = "Coprocessor4Disabled", 50 | [Coprocessor5Disabled] = "Coprocessor5Disabled", 51 | [Coprocessor6Disabled] = "Coprocessor6Disabled", 52 | [Coprocessor7Disabled] = "Coprocessor7Disabled", 53 | 54 | }; 55 | 56 | void* xthal_memcpy(void *dst, const void *src, unsigned len); 57 | 58 | typedef enum opcode_format { 59 | XTENSA_RRR, 60 | XTENSA_RRI4, 61 | XTENSA_RRI8, 62 | XTENSA_RI16, 63 | XTENSA_RSR, 64 | XTENSA_CALL, 65 | XTENSA_CALLX, 66 | XTENSA_BRI8, 67 | XTENSA_BRI12, 68 | XTENSA_RRRN, 69 | XTENSA_RI7, 70 | XTENSA_RI6, 71 | } opcode_format_t; 72 | 73 | typedef union xtensa_opcode { 74 | uint8_t op0 : 4; 75 | struct { 76 | uint8_t op0 : 4; 77 | uint8_t t : 4; 78 | uint8_t s : 4; 79 | uint8_t r : 4; 80 | uint8_t imm8; 81 | } rri8; 82 | uint32_t value; 83 | } PACKED xtensa_opcode_t; 84 | 85 | static void print_opcodes(void* ptr, int32_t len) { 86 | while (len > 0) { 87 | xtensa_opcode_t opcode = { .value = 0 }; 88 | xthal_memcpy(&opcode, ptr, 3); 89 | 90 | printf("[-] %08x: ", ptr); 91 | 92 | switch (opcode.op0) { 93 | case 0b0010: { 94 | switch (opcode.rri8.r) { 95 | case 0b0010: printf("L32I a%d, a%d, %u", opcode.rri8.t, opcode.rri8.s, opcode.rri8.imm8); len += 3; break; 96 | default: printf("Invalid opcode %08x", opcode.value); len -= 3; break; 97 | } 98 | } break; 99 | default: printf("Invalid opcode %08x", opcode.value); len -= 3; break; 100 | } 101 | 102 | printf("\n\r"); 103 | } 104 | } 105 | 106 | void common_exception_handler(task_regs_t* regs) { 107 | int cause = __RSR(EXCCAUSE); 108 | 109 | wdt_disable(); 110 | 111 | // print the cuase 112 | printf("[-] got exception "); 113 | if (cause < ARRAY_LEN(m_cause_str) && m_cause_str[cause] != NULL) { 114 | printf("%s (%d)", m_cause_str[cause], cause); 115 | } else { 116 | printf("%d", cause); 117 | } 118 | 119 | // print the vaddr if needed 120 | if ( 121 | cause == InstructionFetchErrorCause || 122 | cause == LoadStoreErrorCause || 123 | cause == LoadStoreAlignmentCause || 124 | cause == InstrPIFDataErrorCause || 125 | cause == LoadStorePIFDataErrorCause || 126 | cause == InstrPIFAddrErrorCause || 127 | cause == LoadStorePIFAddrErrorCause || 128 | cause == InstTLBMissCause || 129 | cause == InstTLBMultiHitCause || 130 | cause == InstFetchPrivilegeCause || 131 | cause == InstFetchProhibitedCause || 132 | cause == LoadStoreTLBMissCause || 133 | cause == LoadStoreTLBMultiHitCause || 134 | cause == LoadStorePrivilegeCause || 135 | cause == LoadProhibitedCause || 136 | cause == StoreProhibitedCause 137 | ) { 138 | printf(" EXCVADDR=%08x", __RSR(EXCVADDR)); 139 | printf("\n\r"); 140 | } 141 | 142 | printf("\n\r"); 143 | 144 | // TODO: translate if has mmu 145 | print_opcodes((void*)regs->pc, 16); 146 | ERROR(""); 147 | 148 | // dump everything 149 | task_regs_dump(regs); 150 | 151 | // TODO: kill the task that caused the problem 152 | 153 | while(1); 154 | } 155 | 156 | void common_interrupt_handler(task_regs_t* regs) { 157 | // special case for scheduler 158 | if (wdt_handle()) { 159 | scheduler_on_schedule(regs); 160 | } else { 161 | dport_log_interrupt(); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /kernel/src/arch/interrupts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * The possible exception causes, any commented out exception 5 | * does not exist on our board, but we have it for reference 6 | */ 7 | typedef enum exception_cause { 8 | IllegalInstructionCause = 0, 9 | SyscallCause = 1, 10 | InstructionFetchErrorCause = 2, 11 | LoadStoreErrorCause = 3, 12 | Level1InterruptCause = 4, 13 | AllocaCause = 5, 14 | IntegerDivideByZero = 6, 15 | // = 7, 16 | PrivilegedCause = 8, 17 | LoadStoreAlignmentCause = 9, 18 | // = 10, 19 | // = 11, 20 | InstrPIFDataErrorCause = 12, 21 | LoadStorePIFDataErrorCause = 13, 22 | InstrPIFAddrErrorCause = 14, 23 | LoadStorePIFAddrErrorCause = 15, 24 | InstTLBMissCause = 16, 25 | InstTLBMultiHitCause = 17, 26 | InstFetchPrivilegeCause = 18, 27 | // = 19, 28 | InstFetchProhibitedCause = 20, 29 | // = 21, 30 | // = 22, 31 | // = 23, 32 | LoadStoreTLBMissCause = 24, 33 | LoadStoreTLBMultiHitCause = 25, 34 | LoadStorePrivilegeCause = 26, 35 | // = 27, 36 | LoadProhibitedCause = 28, 37 | StoreProhibitedCause = 29, 38 | // = 30, 39 | // = 31, 40 | Coprocessor0Disabled = 32, 41 | Coprocessor1Disabled = 33, 42 | Coprocessor2Disabled = 34, 43 | Coprocessor3Disabled = 35, 44 | Coprocessor4Disabled = 36, 45 | Coprocessor5Disabled = 37, 46 | Coprocessor6Disabled = 38, 47 | Coprocessor7Disabled = 39, 48 | } exception_cause_t; 49 | -------------------------------------------------------------------------------- /kernel/src/arch/intrin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | //---------------------------------------------------------------------------------------------------------------------- 8 | // Special registers and access to them 9 | //---------------------------------------------------------------------------------------------------------------------- 10 | 11 | #define __WSR(reg, value) \ 12 | do { \ 13 | asm volatile ("wsr %0, " STR(reg) :: "r"((uint32_t)value)); \ 14 | } while (0) 15 | 16 | #define __RSR(reg) \ 17 | ({\ 18 | uint32_t value; \ 19 | asm volatile ("rsr %0, " STR(reg) : "=r"(value)); \ 20 | value; \ 21 | }) 22 | 23 | static inline void __rsync() { 24 | asm volatile ("rsync"); 25 | } 26 | 27 | //---------------------------------------------------------------------------------------------------------------------- 28 | // Wrappers for special registers 29 | //---------------------------------------------------------------------------------------------------------------------- 30 | 31 | typedef union ps { 32 | struct { 33 | uint32_t intlevel : 4; 34 | uint32_t excm : 1; 35 | uint32_t um : 1; 36 | uint32_t ring : 2; 37 | uint32_t owb : 4; 38 | uint32_t _reserved0 : 4; 39 | uint32_t callinc : 2; 40 | uint32_t woe : 1; 41 | uint32_t _reserved1 : 13; 42 | }; 43 | uint32_t packed; 44 | } PACKED ps_t; 45 | STATIC_ASSERT(sizeof(ps_t) == sizeof(uint32_t)); 46 | 47 | static inline ps_t __read_ps() { 48 | return (ps_t) { .packed = __RSR(PS) }; 49 | } 50 | 51 | static inline void __write_ps(ps_t ps) { 52 | __WSR(PS, ps.packed); 53 | } 54 | 55 | static inline uint32_t __ccount() { 56 | return __RSR(CCOUNT); 57 | } 58 | -------------------------------------------------------------------------------- /kernel/src/drivers/dport.c: -------------------------------------------------------------------------------- 1 | #include "dport.h" 2 | #include "pid.h" 3 | #include "arch/cpu.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // Hardware registers 12 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | typedef union _DPORT_CACHE_CTRL { 15 | struct { 16 | uint32_t _reserved0 : 3; 17 | uint32_t cache_enable : 1; 18 | uint32_t cache_flush_ena : 1; 19 | uint32_t cache_flush_done : 1; 20 | uint32_t _reserved1 : 4; 21 | uint32_t single_iram_enable : 1; 22 | uint32_t dram_split : 1; 23 | uint32_t _reserved2 : 4; 24 | uint32_t dram_hl : 1; 25 | uint32_t _reserved3 : 15; 26 | }; 27 | uint32_t packed; 28 | } PACKED DPORT_CACHE_CTRL_REG; 29 | STATIC_ASSERT(sizeof(DPORT_CACHE_CTRL_REG) == sizeof(uint32_t)); 30 | 31 | typedef union _DPORT_MMU_TABLE_REG { 32 | struct { 33 | uint32_t address : 4; 34 | uint32_t access_rights : 3; 35 | uint32_t _reserved : 25; 36 | }; 37 | uint32_t packed; 38 | } PACKED DPORT_MMU_TABLE_REG; 39 | STATIC_ASSERT(sizeof(DPORT_MMU_TABLE_REG) == sizeof(uint32_t)); 40 | 41 | // System and memory registers 42 | extern volatile uint32_t DPORT_PRO_BOOT_REMAP_CTRL; 43 | extern volatile uint32_t DPORT_APP_BOOT_REMAP_CTRL; 44 | extern volatile uint32_t DPORT_CACHE_MUX_MODE; 45 | 46 | // Reset and clock registers 47 | 48 | // Interrupt matrix registers 49 | extern volatile uint32_t DPORT_INTR_FROM_CPU[4]; 50 | extern volatile uint32_t DPORT_PRO_INTR_STATUS[3]; 51 | extern volatile uint32_t DPORT_APP_INTR_STATUS[3]; 52 | extern volatile uint32_t DPORT_PRO_INT_MAP[69]; 53 | extern volatile uint32_t DPORT_APP_INT_MAP[69]; 54 | 55 | // DMA registers 56 | 57 | // MPU/MMU registers 58 | extern volatile DPORT_CACHE_CTRL_REG DPORT_PRO_CACHE_CTRL; 59 | extern volatile DPORT_CACHE_CTRL_REG DPORT_APP_CACHE_CTRL; 60 | 61 | extern volatile uint32_t DPORT_IMMU_PAGE_MODE; 62 | extern volatile uint32_t DPORT_DMMU_PAGE_MODE; 63 | extern volatile uint32_t DPORT_AHB_MPU_TABLE[2]; 64 | extern volatile uint32_t DPORT_AHBLITE_MPU_TABLE[MPU_LAST]; 65 | extern volatile DPORT_MMU_TABLE_REG DPORT_IMMU_TABLE[16]; 66 | extern volatile DPORT_MMU_TABLE_REG DPORT_DMMU_TABLE[16]; 67 | 68 | extern volatile uint32_t DPORT_PRO_VECBASE_CTRL_REG; 69 | extern volatile uint32_t DPORT_PRO_VECBASE_SET_REG; 70 | extern volatile uint32_t DPORT_APP_VECBASE_CTRL_REG; 71 | extern volatile uint32_t DPORT_APP_VECBASE_SET_REG; 72 | 73 | // APP_CPU controller registers 74 | 75 | // Peripheral clock gating and reset registers 76 | 77 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 78 | // Implementation 79 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 80 | 81 | //---------------------------------------------------------------------------------------------------------------------- 82 | // Initialization 83 | //---------------------------------------------------------------------------------------------------------------------- 84 | 85 | extern symbol_t vecbase; 86 | 87 | void init_dport() { 88 | // set no remapping for both cpus 89 | DPORT_PRO_BOOT_REMAP_CTRL = 0; 90 | DPORT_APP_BOOT_REMAP_CTRL = 0; 91 | 92 | // disable the cache before we do anything 93 | // this will also set the iram special mode to 94 | // false and the virtual address mode to normal 95 | DPORT_PRO_CACHE_CTRL.packed = 0; 96 | DPORT_APP_CACHE_CTRL.packed = 0; 97 | 98 | // set the cache mode for: 99 | // Pool 0 -> PRO CPU 100 | // Pool 1 -> APP CPU 101 | DPORT_CACHE_MUX_MODE = 0; 102 | 103 | // flush the caches in case something 104 | // is already in there, do them both 105 | // at the same time 106 | DPORT_PRO_CACHE_CTRL.cache_flush_ena = 1; 107 | DPORT_APP_CACHE_CTRL.cache_flush_ena = 1; 108 | while (!DPORT_PRO_CACHE_CTRL.cache_flush_done); 109 | while (!DPORT_APP_CACHE_CTRL.cache_flush_done); 110 | 111 | // now enable the cache 112 | DPORT_PRO_CACHE_CTRL.cache_enable = 1; 113 | DPORT_APP_CACHE_CTRL.cache_enable = 1; 114 | 115 | // unmap all the interrupts by setting all of them to the default 116 | // value, which is an internal interrupt which is unusable 117 | for (int i = 0; i < ARRAY_LEN(DPORT_PRO_INT_MAP); i++) { 118 | DPORT_PRO_INT_MAP[i] = 16; 119 | DPORT_APP_INT_MAP[i] = 16; 120 | } 121 | 122 | // make sure there are no cross-core interrupts 123 | for (int i = 0; i < 4; i++) { 124 | DPORT_INTR_FROM_CPU[i] = 0; 125 | } 126 | 127 | // setup the vecbase in the dport and tell the cpu to use that, 128 | // this should prevent the user from being able to change the 129 | // vecbase on its own. 130 | ASSERT(((uint32_t)vecbase & ((1 << 10) - 1)) == 0); 131 | DPORT_PRO_VECBASE_SET_REG = (uint32_t)vecbase >> 10; 132 | DPORT_PRO_VECBASE_CTRL_REG |= 0b11; 133 | DPORT_APP_VECBASE_SET_REG = (uint32_t)vecbase >> 10; 134 | DPORT_APP_VECBASE_CTRL_REG |= 0b11; 135 | } 136 | 137 | //---------------------------------------------------------------------------------------------------------------------- 138 | // Interrupt matrix stuff 139 | //---------------------------------------------------------------------------------------------------------------------- 140 | 141 | /** 142 | * All the edge-triggered interrupts 143 | */ 144 | #define PERIPHERALS_EDGE_TRIGGERED (BIT10 | BIT22 | BIT28 | BIT30) 145 | 146 | /** 147 | * Free peripheral interrupts (both edge and level) that are 148 | * only priority level 1 149 | */ 150 | uint32_t m_free_peripheral_interrupt = 151 | BIT0 | BIT1 | BIT2 | BIT3 | BIT4 | BIT5 | BIT8 | 152 | BIT9 | BIT10 | BIT12 | BIT13 | BIT17 | BIT18; 153 | 154 | err_t dport_map_interrupt(interrupt_source_t source, bool edge_triggered) { 155 | err_t err = NO_ERROR; 156 | 157 | // check that source is in a valid range 158 | CHECK(source >= 0); 159 | CHECK(source <= 68); 160 | 161 | // mask properly and make sure there are enough of these 162 | uint32_t free_peripheral_interrupt = m_free_peripheral_interrupt; 163 | if (edge_triggered) { 164 | free_peripheral_interrupt &= PERIPHERALS_EDGE_TRIGGERED; 165 | } else { 166 | free_peripheral_interrupt &= ~PERIPHERALS_EDGE_TRIGGERED; 167 | } 168 | CHECK_ERROR(free_peripheral_interrupt != 0, ERROR_OUT_OF_RESOURCES); 169 | 170 | // allocate the first available one 171 | uint32_t free = __builtin_ffs(free_peripheral_interrupt) - 1; 172 | m_free_peripheral_interrupt &= ~(1 << free); 173 | 174 | TRACE("Mapping %d -> %d", source, free); 175 | 176 | // map it properly 177 | DPORT_PRO_INT_MAP[source] = free; 178 | 179 | cleanup: 180 | return err; 181 | } 182 | 183 | void dport_log_interrupt() { 184 | TRACE("Interrupts:"); 185 | for (int i = 0; i < INTERRUPT_SOURCE_MAX; i++) { 186 | if (DPORT_PRO_INTR_STATUS[i / 32] & (1 << (i % 32))) { 187 | switch (i) { 188 | default: WARN("\tGot interrupt: #%d", i); break; 189 | } 190 | } 191 | } 192 | } 193 | 194 | //---------------------------------------------------------------------------------------------------------------------- 195 | // MMU/MPU stuff 196 | //---------------------------------------------------------------------------------------------------------------------- 197 | 198 | STATIC_ASSERT(0x32C + MPU_UART * 4 == 0x32C); 199 | STATIC_ASSERT(0x32C + MPU_SPI1 * 4 == 0x330); 200 | STATIC_ASSERT(0x32C + MPU_SPI0 * 4 == 0x334); 201 | STATIC_ASSERT(0x32C + MPU_GPIO * 4 == 0x338); 202 | STATIC_ASSERT(0x32C + MPU_RTC * 4 == 0x348); 203 | STATIC_ASSERT(0x32C + MPU_IO_MUX * 4 == 0x34c); 204 | STATIC_ASSERT(0x32C + MPU_HINF * 4 == 0x354); 205 | STATIC_ASSERT(0x32C + MPU_UHCI1 * 4 == 0x358); 206 | STATIC_ASSERT(0x32C + MPU_I2S0 * 4 == 0x364); 207 | STATIC_ASSERT(0x32C + MPU_UART1 * 4 == 0x368); 208 | STATIC_ASSERT(0x32C + MPU_I2C_EXT0 * 4 == 0x374); 209 | STATIC_ASSERT(0x32C + MPU_UHCI0 * 4 == 0x378); 210 | STATIC_ASSERT(0x32C + MPU_SLCHOST * 4 == 0x37c); 211 | STATIC_ASSERT(0x32C + MPU_RMT * 4 == 0x380); 212 | STATIC_ASSERT(0x32C + MPU_PCNT * 4 == 0x384); 213 | STATIC_ASSERT(0x32C + MPU_SLC * 4 == 0x388); 214 | STATIC_ASSERT(0x32C + MPU_LEDC * 4 == 0x38c); 215 | STATIC_ASSERT(0x32C + MPU_EFUSE * 4 == 0x390); 216 | STATIC_ASSERT(0x32C + MPU_SPI_ENCRYPT * 4 == 0x394); 217 | STATIC_ASSERT(0x32C + MPU_PWM0 * 4 == 0x39c); 218 | STATIC_ASSERT(0x32C + MPU_TIMERGROUP * 4 == 0x3a0); 219 | STATIC_ASSERT(0x32C + MPU_TIMERGROUP1 * 4 == 0x3a4); 220 | STATIC_ASSERT(0x32C + MPU_SPI2 * 4 == 0x3a8); 221 | STATIC_ASSERT(0x32C + MPU_SPI3 * 4 == 0x3ac); 222 | STATIC_ASSERT(0x32C + MPU_APB_CTRL * 4 == 0x3b0); 223 | STATIC_ASSERT(0x32C + MPU_I2C_EXT1 * 4 == 0x3b4); 224 | STATIC_ASSERT(0x32C + MPU_SDIO_HOST * 4 == 0x3b8); 225 | STATIC_ASSERT(0x32C + MPU_EMAC * 4 == 0x3bc); 226 | STATIC_ASSERT(0x32C + MPU_PWM1 * 4 == 0x3c4); 227 | STATIC_ASSERT(0x32C + MPU_I2S1 * 4 == 0x3c8); 228 | STATIC_ASSERT(0x32C + MPU_UART2 * 4 == 0x3cc); 229 | STATIC_ASSERT(0x32C + MPU_PWR * 4 == 0x3e4); 230 | 231 | err_t init_mmu() { 232 | err_t err = NO_ERROR; 233 | 234 | // config both IMMU and DMMU with 8kb pages 235 | // also need to enable the first bit to enable the mmu 236 | DPORT_IMMU_PAGE_MODE = 0; 237 | DPORT_DMMU_PAGE_MODE = 0; 238 | 239 | // setup the vdso page, it is always mapped at the last page 240 | // of the usermode area, and is available to all the pages 241 | DPORT_IMMU_TABLE[15].address = 15; 242 | DPORT_IMMU_TABLE[15].access_rights = 1; 243 | 244 | // no DMA is allowed for now 245 | for (int i = 0; i < 2; i++) { 246 | DPORT_AHB_MPU_TABLE[i] = 0; 247 | } 248 | 249 | cleanup: 250 | return err; 251 | } 252 | 253 | void mmu_activate(mmu_t* space) { 254 | ASSERT(space != NULL); 255 | per_cpu_context_t* context = get_cpu_context(); 256 | 257 | // check if we have a binding for this space already 258 | int lru_pid = 0; 259 | for (int i = 0; i < PID_BINDING_COUNT; i++) { 260 | mmu_t* bound = context->pid_bindings[i].bound_space; 261 | if (bound == space) { 262 | // we found a binding for this space 263 | if (!pid_binding_is_primary(&context->pid_bindings[i])) { 264 | // not the current want, move to it 265 | pid_binding_rebind(&context->pid_bindings[i]); 266 | } 267 | return; 268 | } 269 | 270 | // choose the LRU bindings 271 | if (context->pid_bindings[i].primary_stamp < context->pid_bindings[lru_pid].primary_stamp) { 272 | lru_pid = i; 273 | } 274 | } 275 | 276 | // we need to bind a new space, use the one we found 277 | pid_binding_bind(&context->pid_bindings[lru_pid], space); 278 | } 279 | 280 | err_t mmu_map(mmu_t* mmu, mmu_space_type_t type, uint8_t virt, page_entry_t entry) { 281 | err_t err = NO_ERROR; 282 | int bit = (1 << virt); 283 | 284 | CHECK(virt < MAX_PAGE_COUNT); 285 | 286 | // map it 287 | mmu_space_t* space = type == MMU_SPACE_CODE ? &mmu->immu : &mmu->dmmu; 288 | space->entries[virt] = entry; 289 | 290 | // if the process is running, update the mmu itself 291 | if (entry.type == PAGE_MAPPED && pid_binding_is_primary(mmu->binding)) { 292 | volatile DPORT_MMU_TABLE_REG* table_reg = &DPORT_IMMU_TABLE[entry.phys]; 293 | table_reg->address = virt; 294 | table_reg->access_rights = mmu->binding->pid; 295 | } 296 | 297 | cleanup: 298 | return err; 299 | } 300 | 301 | void mmu_load(mmu_t* space) { 302 | int pid = space->binding->pid; 303 | 304 | // go over the mmu entries 305 | for (int virt = 0; virt < 16; virt++) { 306 | // set the iram 307 | if (space->immu.entries[virt].type == PAGE_MAPPED) { 308 | uint8_t phys = space->immu.entries[virt].phys; 309 | DPORT_IMMU_TABLE[phys].address = virt; 310 | DPORT_IMMU_TABLE[phys].access_rights = pid; 311 | } 312 | 313 | // set the dram 314 | if (space->dmmu.entries[virt].type == PAGE_MAPPED) { 315 | uint8_t phys = space->dmmu.entries[virt].phys; 316 | DPORT_DMMU_TABLE[phys].address = virt; 317 | DPORT_DMMU_TABLE[phys].access_rights = pid; 318 | } 319 | } 320 | 321 | // set the peripheral access for the pid 322 | FOR_EACH_BIT(space->mpu_peripheral, it) { 323 | DPORT_AHBLITE_MPU_TABLE[1 << it] = 1 << pid; 324 | } 325 | } 326 | 327 | void mmu_unload(mmu_t* space) { 328 | int pid = space->binding->pid; 329 | 330 | // go over the mmu entries 331 | // TODO: do we want to have a mapped check instead maybe? 332 | // I am not sure if it would be faster than just 333 | // reading the access rights instead... 334 | for (int i = 0; i < 16; i++) { 335 | // remove anything in dram that has this bit 336 | if (DPORT_IMMU_TABLE[i].access_rights == pid) { 337 | DPORT_IMMU_TABLE[i].access_rights = 0; 338 | } 339 | 340 | // remove anything in iram that has this bit 341 | if (DPORT_DMMU_TABLE[i].access_rights == pid) { 342 | DPORT_DMMU_TABLE[i].access_rights = 0; 343 | } 344 | } 345 | 346 | // remove the peripheral access for the pid 347 | FOR_EACH_BIT(space->mpu_peripheral, it) { 348 | DPORT_AHBLITE_MPU_TABLE[1 << it] &= ~(1 << pid); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /kernel/src/drivers/dport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include "util/except.h" 8 | 9 | #define RTC_SLOW_FREZ_HZ 150000 10 | #define XTAL_FREQ_HZ 40000000 11 | #define APB_FREQ_HZ 40000000 12 | 13 | /** 14 | * Initialize the dport for kernel runtime 15 | */ 16 | void init_dport(); 17 | 18 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 19 | // Interrupt abstraction 20 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | typedef enum interrupt_source { 23 | INVALID_INT_SOURCE = -1, 24 | // ... 25 | TG_T0_LEVEL_INT = 14, 26 | TG_T1_LEVEL_INT = 15, 27 | TG_WDT_LEVEL_INT = 16, 28 | TG_LACT_LEVEL_INT = 17, 29 | TG1_T0_LEVEL_INT = 18, 30 | TG1_T1_LEVEL_INT = 19, 31 | TG1_WDT_LEVEL_INT = 20, 32 | TG1_LACT_LEVEL_INT = 21, 33 | GPIO_INTERRUPT = 22, 34 | GPIO_INTERRUPT_NMI = 23, 35 | // ... 36 | SPI_INTR_0 = 28, 37 | SPI_INTR_1 = 29, 38 | SPI_INTR_2 = 30, 39 | SPI_INTR_3 = 31, 40 | // 41 | UART_INTR = 34, 42 | UART1_INTR = 35, 43 | UART2_INTR = 36, 44 | // ... 45 | I2C_EXT0_INTR = 49, 46 | I2C_EXT1_INTR = 50, 47 | // ... 48 | MMU_IA_INT = 66, 49 | MPU_IA_INT = 67, 50 | CACHE_IA_INT = 68, 51 | INTERRUPT_SOURCE_MAX = 69, 52 | } interrupt_source_t; 53 | 54 | /** 55 | * Map an interrupt, getting back the interrupt number 56 | * 57 | * @remark 58 | * Always allocates to the PRO cpu 59 | * 60 | * @param source [IN] The interrupt source 61 | * @param edge_triggered [IN] Should this be an edge-triggered interrupt, otherwise level interrupt 62 | */ 63 | err_t dport_map_interrupt(interrupt_source_t source, bool edge_triggered); 64 | 65 | void dport_log_interrupt(); 66 | 67 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 68 | // MMU/MPU abstraction 69 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 70 | 71 | typedef enum mpu_peripheral { 72 | MPU_UART = 0, 73 | MPU_SPI1 = 1, 74 | MPU_SPI0 = 2, 75 | MPU_GPIO = 3, 76 | /* MPU_ = 4, */ 77 | /* MPU_ = 5, */ 78 | /* MPU_ = 6, */ 79 | MPU_RTC = 7, 80 | MPU_IO_MUX = 8, 81 | /* MPU_ = 9, */ 82 | MPU_HINF = 10, 83 | MPU_UHCI1 = 11, 84 | /* MPU_ = 12, */ 85 | /* MPU_ = 13, */ 86 | MPU_I2S0 = 14, 87 | MPU_UART1 = 15, 88 | /* MPU_ = 16, */ 89 | /* MPU_ = 17, */ 90 | MPU_I2C_EXT0 = 18, 91 | MPU_UHCI0 = 19, 92 | MPU_SLCHOST = 20, 93 | MPU_RMT = 21, 94 | MPU_PCNT = 22, 95 | MPU_SLC = 23, 96 | MPU_LEDC = 24, 97 | MPU_EFUSE = 25, 98 | MPU_SPI_ENCRYPT = 26, 99 | /* MPU_ = 27, */ 100 | MPU_PWM0 = 28, 101 | MPU_TIMERGROUP = 29, 102 | MPU_TIMERGROUP1 = 30, 103 | MPU_SPI2 = 31, 104 | MPU_SPI3 = 32, 105 | MPU_APB_CTRL = 33, 106 | MPU_I2C_EXT1 = 34, 107 | MPU_SDIO_HOST = 35, 108 | MPU_EMAC = 36, 109 | /* MPU_ = 37; */ 110 | MPU_PWM1 = 38, 111 | MPU_I2S1 = 39, 112 | MPU_UART2 = 40, 113 | /* MPU_ = 41; */ 114 | /* MPU_ = 42; */ 115 | /* MPU_ = 43; */ 116 | /* MPU_ = 44; */ 117 | /* MPU_ = 45; */ 118 | MPU_PWR = 46, 119 | MPU_LAST, 120 | } mpu_peripheral_t; 121 | 122 | /** 123 | * Forward declare 124 | */ 125 | struct pid_binding; 126 | 127 | typedef enum page_entry_type { 128 | /** 129 | * The page is unmapped 130 | */ 131 | PAGE_UNMAPPED = 0, 132 | 133 | /** 134 | * The paged is mapped and is in sram 135 | */ 136 | PAGE_MAPPED = 1, 137 | 138 | /** 139 | * The page is mapped but is swapped to psram 140 | */ 141 | PAGE_SWAPPED = 2, 142 | } page_entry_type_t; 143 | 144 | /** 145 | * Each page entry for tracking mapped pages 146 | */ 147 | typedef struct page_entry { 148 | // the physucal page of this, either on 149 | // psram or in sram 150 | uint8_t phys; 151 | 152 | // is the address mapped 153 | page_entry_type_t type : 2; 154 | } page_entry_t; 155 | 156 | /** 157 | * a single address space, either code or data 158 | */ 159 | typedef struct mmu_space { 160 | // virtual->physical 161 | page_entry_t entries[16]; 162 | } mmu_space_t; 163 | 164 | /** 165 | * The mmu context 166 | */ 167 | typedef struct mmu { 168 | // iram area 169 | mmu_space_t immu; 170 | 171 | // dram area 172 | mmu_space_t dmmu; 173 | 174 | // allows to control if we have 175 | // access to the given device's mmio 176 | uint64_t mpu_peripheral; 177 | 178 | // the binding for this space 179 | struct pid_binding* binding; 180 | } mmu_t; 181 | 182 | /** 183 | * Initialize MMU stuff 184 | */ 185 | err_t init_mmu(); 186 | 187 | /** 188 | * The page for the code pages index 189 | */ 190 | #define MAX_PAGE_COUNT 16 191 | 192 | typedef enum mmu_space_type { 193 | MMU_SPACE_CODE, 194 | MMU_SPACE_DATA, 195 | } mmu_space_type_t; 196 | 197 | #define PAGE_ENTRY(page) ((page_entry_t){ .phys = (page), .type = PAGE_MAPPED }) 198 | 199 | err_t mmu_map(mmu_t* mmu, mmu_space_type_t type, uint8_t virt, page_entry_t entry); 200 | 201 | /** 202 | * Activate the given MMU range 203 | */ 204 | void mmu_activate(mmu_t* space); 205 | 206 | /** 207 | * Load the MMU entries 208 | */ 209 | void mmu_load(mmu_t* space); 210 | 211 | /** 212 | * Unload MMU entries for the bound pid 213 | */ 214 | void mmu_unload(mmu_t* space); 215 | -------------------------------------------------------------------------------- /kernel/src/drivers/dport.ld: -------------------------------------------------------------------------------- 1 | 2 | DPORT_PRO_BOOT_REMAP_CTRL = DPORT_BASE + 0x000; 3 | DPORT_APP_BOOT_REMAP_CTRL = DPORT_BASE + 0x004; 4 | DPORT_PERI_CLK_EN = DPORT_BASE + 0x01C; 5 | DPORT_PERI_RST_EN = DPORT_BASE + 0x020; 6 | DPORT_APPCPU_CTRL_REG_A = DPORT_BASE + 0x02C; 7 | DPORT_APPCPU_CTRL_REG_B = DPORT_BASE + 0x030; 8 | DPORT_APPCPU_CTRL_REG_C = DPORT_BASE + 0x034; 9 | DPORT_APPCPU_CTRL_REG_D = DPORT_BASE + 0x038; 10 | DPORT_CPU_PER_CONF = DPORT_BASE + 0x03C; 11 | DPORT_PRO_CACHE_CTRL = DPORT_BASE + 0x040; 12 | DPORT_APP_CACHE_CTRL = DPORT_BASE + 0x058; 13 | DPORT_CACHE_MUX_MODE = DPORT_BASE + 0x07C; 14 | DPORT_IMMU_PAGE_MODE = DPORT_BASE + 0x080; 15 | DPORT_DMMU_PAGE_MODE = DPORT_BASE + 0x084; 16 | DPORT_AHB_MPU_TABLE = DPORT_BASE + 0x0B4; 17 | DPORT_PERIP_CLK_EN = DPORT_BASE + 0x0C0; 18 | DPORT_PERIP_RST_EN = DPORT_BASE + 0x0C4; 19 | DPORT_WIFI_CLK_EN = DPORT_BASE + 0x0CC; 20 | DPORT_WIFI_RST_EN = DPORT_BASE + 0x0D0; 21 | DPORT_IMMU_TABLE = DPORT_BASE + 0x504; 22 | DPORT_DMMU_TABLE = DPORT_BASE + 0x544; 23 | DPORT_AHBLITE_MPU_TABLE = DPORT_BASE + 0x32c; 24 | DPORT_INTR_FROM_CPU = DPORT_BASE + 0x0DC; 25 | DPORT_PRO_INTR_STATUS = DPORT_BASE + 0x0EC; 26 | DPORT_APP_INTR_STATUS = DPORT_BASE + 0x0F8; 27 | DPORT_PRO_INT_MAP = DPORT_BASE + 0x104; 28 | DPORT_APP_INT_MAP = DPORT_BASE + 0x218; 29 | DPORT_MMU_IA_INT_EN_REG = DPORT_BASE + 0x598; 30 | DPORT_MPU_IA_INT_EN_REG = DPORT_BASE + 0x59C; 31 | DPORT_CACHE_IA_INT_EN_REG = DPORT_BASE + 0x5A0; 32 | DPORT_PRO_VECBASE_CTRL_REG = DPORT_BASE + 0x5AC; 33 | DPORT_PRO_VECBASE_SET_REG = DPORT_BASE + 0x5B0; 34 | DPORT_APP_VECBASE_CTRL_REG = DPORT_BASE + 0x5B4; 35 | DPORT_APP_VECBASE_SET_REG = DPORT_BASE + 0x5B8; -------------------------------------------------------------------------------- /kernel/src/drivers/pid.c: -------------------------------------------------------------------------------- 1 | #include "pid.h" 2 | #include "arch/cpu.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // Hardware registers 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | 12 | extern volatile uint32_t PIDCTRL_INTERRUPT_ENABLE; 13 | extern volatile uint32_t PIDCTRL_INTERRUPT_ADDR[7]; 14 | extern volatile uint32_t PIDCTRL_PID_DELAY; 15 | extern volatile uint32_t PIDCTRL_NMI_DELAY; 16 | extern volatile uint32_t PIDCTRL_LEVEL; 17 | extern volatile uint32_t PIDCTRL_FROM[7]; 18 | extern volatile uint32_t PIDCTRL_PID_NEW_REG; 19 | extern volatile uint32_t PIDCTRL_PID_CONFIRM; 20 | extern volatile uint32_t PIDCTRL_NMI_MASK_ENABLE; 21 | extern volatile uint32_t PIDCTRL_NMI_MASK_DISABLE; 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 24 | // Implementation 25 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 26 | 27 | //---------------------------------------------------------------------------------------------------------------------- 28 | // Initialization 29 | //---------------------------------------------------------------------------------------------------------------------- 30 | 31 | extern symbol_t vecbase; 32 | 33 | void init_pid() { 34 | // we are going to skip the kernel exception vector since we care about it less 35 | PIDCTRL_INTERRUPT_ADDR[0] = (uintptr_t)vecbase + 0x340; /* user exception handler */ 36 | PIDCTRL_INTERRUPT_ADDR[1] = (uintptr_t)vecbase + 0x180; /* interrupt level 2 */ 37 | PIDCTRL_INTERRUPT_ADDR[2] = (uintptr_t)vecbase + 0x1c0; /* interrupt level 3 */ 38 | PIDCTRL_INTERRUPT_ADDR[3] = (uintptr_t)vecbase + 0x200; /* interrupt level 4 */ 39 | PIDCTRL_INTERRUPT_ADDR[4] = (uintptr_t)vecbase + 0x240; /* interrupt level 5 */ 40 | PIDCTRL_INTERRUPT_ADDR[5] = (uintptr_t)vecbase + 0x280; /* interrupt level 6 (debug) */ 41 | PIDCTRL_INTERRUPT_ADDR[6] = (uintptr_t)vecbase + 0x2c0; /* interrupt level 7 (nmi) */ 42 | 43 | // set the level and from to zero 44 | PIDCTRL_LEVEL = 0; 45 | for (int i = 0; i < ARRAY_LEN(PIDCTRL_FROM); i++) { 46 | PIDCTRL_FROM[i] = 0; 47 | } 48 | 49 | // set the pid interrupt tracking for all 7 levels 50 | PIDCTRL_INTERRUPT_ENABLE = 0b11111110; 51 | 52 | // set the delay, this was calculated exactly as needed 53 | PIDCTRL_PID_DELAY = 0; 54 | 55 | // setup the pids 56 | pid_binding_t* binding = get_cpu_context()->pid_bindings; 57 | for (int i = 0; i < PID_BINDING_COUNT; i++) { 58 | binding[i].pid = i + 2; 59 | } 60 | } 61 | 62 | __attribute__((noinline)) void pid_prepare() { 63 | // get the pid that we want to switch to 64 | int current_pid = get_cpu_context()->primary_binding->pid; 65 | 66 | // set it 67 | PIDCTRL_PID_NEW_REG = current_pid; 68 | } 69 | 70 | bool pid_binding_is_primary(pid_binding_t* binding) { 71 | return binding != NULL && get_cpu_context()->primary_binding == binding; 72 | } 73 | 74 | void pid_binding_rebind(pid_binding_t* binding) { 75 | per_cpu_context_t* context = get_cpu_context(); 76 | 77 | // set the primary space and the binding stamp 78 | binding->primary_stamp = context->next_stamp++; 79 | context->primary_binding = binding; 80 | } 81 | 82 | void pid_binding_bind(pid_binding_t* binding, mmu_t* space) { 83 | per_cpu_context_t* context = get_cpu_context(); 84 | mmu_t* unbound_space = binding->bound_space; 85 | 86 | // set the new space 87 | binding->bound_space = space; 88 | 89 | // remove the entries of the old state 90 | if (unbound_space != NULL) { 91 | mmu_unload(unbound_space); 92 | } 93 | 94 | // set the entries of the new state 95 | space->binding = binding; 96 | mmu_load(space); 97 | 98 | // set the primary space and the binding stamp 99 | binding->primary_stamp = context->next_stamp++; 100 | context->primary_binding = binding; 101 | } 102 | 103 | void pid_binding_unbind(pid_binding_t* binding) { 104 | // remove the entries for this space 105 | mmu_unload(binding->bound_space); 106 | binding->bound_space->binding = NULL; 107 | 108 | // set the unbound space 109 | binding->bound_space = NULL; 110 | } 111 | -------------------------------------------------------------------------------- /kernel/src/drivers/pid.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dport.h" 4 | 5 | #include 6 | #include 7 | 8 | /** 9 | * The amount of free pid entries 10 | */ 11 | #define PID_BINDING_COUNT 6 12 | 13 | /** 14 | * Represent a pid cache 15 | */ 16 | typedef struct pid_binding { 17 | // the last use timestamp 18 | uint64_t primary_stamp; 19 | 20 | // the currently bound address space 21 | mmu_t* bound_space; 22 | 23 | // the hardware pid for this 24 | uint8_t pid; 25 | } pid_binding_t; 26 | 27 | /** 28 | * Setup the PID controller 29 | * 30 | * This will also setup the vecbase and related stuff 31 | */ 32 | void init_pid(); 33 | 34 | /** 35 | * prepare for switching to the next pid, the exception exit 36 | * will actually do the switching as it can know the correct 37 | * delay to use for this 38 | */ 39 | void pid_prepare(); 40 | 41 | /** 42 | * Checks if this pid binding is the currently bound one 43 | */ 44 | bool pid_binding_is_primary(pid_binding_t* binding); 45 | 46 | /** 47 | * Rebind an already bound pid binding 48 | */ 49 | void pid_binding_rebind(pid_binding_t* binding); 50 | 51 | /** 52 | * Bind a new space to the given binding 53 | */ 54 | void pid_binding_bind(pid_binding_t* binding, mmu_t* space); 55 | 56 | /** 57 | * Unbind the space from the given binding 58 | */ 59 | void pid_binding_unbind(pid_binding_t* binding); 60 | -------------------------------------------------------------------------------- /kernel/src/drivers/pid.ld: -------------------------------------------------------------------------------- 1 | 2 | PIDCTRL_INTERRUPT_ENABLE = PID_CONTROLLER_BASE + 0x000; 3 | PIDCTRL_INTERRUPT_ADDR = PID_CONTROLLER_BASE + 0x004; 4 | PIDCTRL_PID_DELAY = PID_CONTROLLER_BASE + 0x020; 5 | PIDCTRL_NMI_DELAY = PID_CONTROLLER_BASE + 0x024; 6 | PIDCTRL_LEVEL = PID_CONTROLLER_BASE + 0x028; 7 | PIDCTRL_FROM = PID_CONTROLLER_BASE + 0x028 + 0x4; 8 | PIDCTRL_PID_NEW_REG = PID_CONTROLLER_BASE + 0x048; 9 | PIDCTRL_PID_CONFIRM = PID_CONTROLLER_BASE + 0x04c; 10 | PIDCTRL_NMI_MASK_ENABLE = PID_CONTROLLER_BASE + 0x054; 11 | PIDCTRL_NMI_MASK_DISABLE = PID_CONTROLLER_BASE + 0x058; 12 | -------------------------------------------------------------------------------- /kernel/src/drivers/rtc_cntl.c: -------------------------------------------------------------------------------- 1 | #include "rtc_cntl.h" 2 | #include "util/trace.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | // Hardware registers 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | 12 | typedef union _RTC_CNTL_SLP_TIMER1_REG { 13 | struct { 14 | uint32_t slp_val_hi : 16; 15 | uint32_t main_timer_alarm_en : 1; 16 | uint32_t _reserved : 15; 17 | }; 18 | uint32_t packed; 19 | } PACKED RTC_CNTL_SLP_TIMER1_REG; 20 | STATIC_ASSERT(sizeof(RTC_CNTL_SLP_TIMER1_REG) == sizeof(uint32_t)); 21 | 22 | typedef union _RTC_CNTL_INT_REG { 23 | struct { 24 | uint32_t slp_wakeup_int : 1; 25 | uint32_t slp_reject_int : 1; 26 | uint32_t sdio_idle_int : 1; 27 | uint32_t wdt_int : 1; 28 | uint32_t time_valid_int : 1; 29 | uint32_t ulp_cp_int : 1; 30 | uint32_t touch_int : 1; 31 | uint32_t brown_out_int : 1; 32 | uint32_t main_timer_int : 1; 33 | uint32_t _reserved : 23; 34 | }; 35 | uint32_t packed; 36 | } PACKED RTC_CNTL_INT_REG; 37 | STATIC_ASSERT(sizeof(RTC_CNTL_INT_REG) == sizeof(uint32_t)); 38 | 39 | typedef union _RTC_CNTL_TIME_UPDATE_REG { 40 | struct { 41 | uint32_t _reserved : 30; 42 | uint32_t time_valid : 1; 43 | uint32_t time_update : 1; 44 | }; 45 | uint32_t packed; 46 | } PACKED RTC_CNTL_TIME_UPDATE_REG; 47 | STATIC_ASSERT(sizeof(RTC_CNTL_TIME_UPDATE_REG) == sizeof(uint32_t)); 48 | 49 | typedef union _RTC_CNTL_WDTCONFIG0_REG { 50 | struct { 51 | uint32_t _reserved0 : 7; 52 | uint32_t pause_in_slp : 1; 53 | uint32_t appcpu_reset_en : 1; 54 | uint32_t procpu_reset_en : 1; 55 | uint32_t fashboot_mod_en : 1; 56 | uint32_t sys_reset_length : 3; 57 | uint32_t cpu_reset_length : 3; 58 | uint32_t _reserved1 : 1; 59 | uint32_t _reserved2 : 1; 60 | uint32_t stg3 : 3; 61 | uint32_t stg2 : 3; 62 | uint32_t stg1 : 3; 63 | uint32_t stg0 : 3; 64 | uint32_t en : 1; 65 | }; 66 | uint32_t packed; 67 | } PACKED RTC_CNTL_WDTCONFIG0_REG; 68 | STATIC_ASSERT(sizeof(RTC_CNTL_WDTCONFIG0_REG) == sizeof(uint32_t)); 69 | 70 | extern volatile uint32_t RTC_CNTL_TIMER0; 71 | extern volatile RTC_CNTL_SLP_TIMER1_REG RTC_CNTL_SLP_TIMER1; 72 | extern volatile RTC_CNTL_TIME_UPDATE_REG RTC_CNTL_TIME_UPDATE; 73 | extern volatile uint32_t RTC_CNTL_TIME0; 74 | extern volatile uint32_t RTC_CNTL_TIME1; 75 | extern volatile RTC_CNTL_INT_REG RTC_CNTL_INT_ENA; 76 | extern volatile RTC_CNTL_INT_REG RTC_CNTL_INT_RAW; 77 | extern volatile RTC_CNTL_INT_REG RTC_CNTL_INT_ST; 78 | extern volatile RTC_CNTL_INT_REG RTC_CNTL_INT_CLR; 79 | extern volatile uint32_t RTC_CNTL_STORE0[4]; 80 | extern volatile RTC_CNTL_WDTCONFIG0_REG RTC_CNTL_WDTCONFIG0; 81 | extern volatile uint32_t RTC_CNTL_WDTCONFIG[4]; 82 | extern volatile uint32_t RTC_CNTL_WDTFEED; 83 | extern volatile uint32_t RTC_CNTL_WDTWPROTECT; 84 | extern volatile uint32_t RTC_CNTL_STORE1[4]; 85 | 86 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 87 | // Implementation 88 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 89 | -------------------------------------------------------------------------------- /kernel/src/drivers/rtc_cntl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | -------------------------------------------------------------------------------- /kernel/src/drivers/rtc_cntl.ld: -------------------------------------------------------------------------------- 1 | 2 | RTC_CNTL_TIMER0 = RTC_BASE + 0x04; 3 | RTC_CNTL_SLP_TIMER1 = RTC_BASE + 0x08; 4 | RTC_CNTL_TIME_UPDATE = RTC_BASE + 0x0c; 5 | RTC_CNTL_TIME0 = RTC_BASE + 0x10; 6 | RTC_CNTL_TIME1 = RTC_BASE + 0x14; 7 | RTC_CNTL_INT_ENA = RTC_BASE + 0x3c; 8 | RTC_CNTL_INT_RAW = RTC_BASE + 0x40; 9 | RTC_CNTL_INT_ST = RTC_BASE + 0x44; 10 | RTC_CNTL_INT_CLR = RTC_BASE + 0x48; 11 | RTC_CNTL_STORE0 = RTC_BASE + 0x4C; 12 | RTC_CNTL_WDTCONFIG0 = RTC_BASE + 0x8C; 13 | RTC_CNTL_WDTCONFIG = RTC_BASE + 0x90; 14 | RTC_CNTL_WDTFEED = RTC_BASE + 0xA0; 15 | RTC_CNTL_WDTWPROTECT = RTC_BASE + 0xA4; 16 | RTC_CNTL_STORE1 = RTC_BASE + 0xC0; 17 | -------------------------------------------------------------------------------- /kernel/src/drivers/timg.c: -------------------------------------------------------------------------------- 1 | #include "timg.h" 2 | #include "util/trace.h" 3 | #include "util/except.h" 4 | #include "dport.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // Hardware registers 12 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | typedef union _TIMG_CONFIG_REG { 15 | struct { 16 | uint32_t _reserved0 : 10; 17 | uint32_t alarm_en : 1; 18 | uint32_t level_int_en : 1; 19 | uint32_t edge_int_en : 1; 20 | uint32_t divider : 16; 21 | uint32_t autoreload : 1; 22 | uint32_t increase : 1; 23 | uint32_t en : 1; 24 | }; 25 | uint32_t packed; 26 | } MMIO TIMG_CONFIG_REG; 27 | STATIC_ASSERT(sizeof(TIMG_CONFIG_REG) == sizeof(uint32_t)); 28 | 29 | typedef struct _TIMG_REG { 30 | TIMG_CONFIG_REG config; 31 | uint32_t lo; 32 | uint32_t hi; 33 | uint32_t update; 34 | uint32_t alarmlo; 35 | uint32_t alarmhi; 36 | uint32_t loadlo; 37 | uint32_t loadhi; 38 | uint32_t load; 39 | } MMIO TIMG_REG; 40 | STATIC_ASSERT(sizeof(TIMG_REG) == 0x24); 41 | 42 | typedef union _TIMG_WDTCONFIG_REG { 43 | struct { 44 | uint32_t _reserved : 14; 45 | uint32_t flashboot_mod_en : 1; 46 | uint32_t sys_reset_length : 3; 47 | uint32_t cpu_reset_length : 3; 48 | uint32_t level_int_en : 1; 49 | uint32_t edge_int_en : 1; 50 | uint32_t stg3 : 2; 51 | uint32_t stg2 : 2; 52 | uint32_t stg1 : 2; 53 | uint32_t stg0 : 2; 54 | uint32_t en : 1; 55 | }; 56 | uint32_t packed; 57 | } MMIO TIMG_WDTCONFIG_REG; 58 | STATIC_ASSERT(sizeof(TIMG_WDTCONFIG_REG) == sizeof(uint32_t)); 59 | 60 | typedef union _TIMG_INT_REG { 61 | struct { 62 | uint32_t t0_int : 1; 63 | uint32_t t1_int : 1; 64 | uint32_t wdt_int : 1; 65 | uint32_t _reserved : 29; 66 | }; 67 | uint32_t packed; 68 | } MMIO TIMG_INT_REG; 69 | STATIC_ASSERT(sizeof(TIMG_INT_REG) == sizeof(uint32_t)); 70 | 71 | /* Timer group 0 */ 72 | extern volatile TIMG_REG TIMG0[2]; 73 | extern volatile TIMG_WDTCONFIG_REG TIMG0_WDTCONFIG; 74 | extern volatile uint32_t TIMG0_WDTCONFIG1; 75 | extern volatile uint32_t TIMG0_WDTCONFIG2; 76 | extern volatile uint32_t TIMG0_WDTCONFIG3; 77 | extern volatile uint32_t TIMG0_WDTCONFIG4; 78 | extern volatile uint32_t TIMG0_WDTCONFIG5; 79 | extern volatile uint32_t TIMG0_WDTFEED; 80 | extern volatile uint32_t TIMG0_WDTWPROTECT; 81 | extern volatile TIMG_INT_REG TIMG0_INT_ENA; 82 | extern volatile TIMG_INT_REG TIMG0_INT_RAW; 83 | extern volatile TIMG_INT_REG TIMG0_INT_ST; 84 | extern volatile TIMG_INT_REG TIMG0_INT_CLR; 85 | 86 | /* Timer group 1 */ 87 | // TODO: if needed 88 | 89 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 90 | // Implementation 91 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 92 | 93 | static void wdt_unlock() { 94 | TIMG0_WDTWPROTECT = 0x050D83AA1; 95 | } 96 | 97 | static void wdt_lock() { 98 | TIMG0_WDTWPROTECT = 0xDEADBEEF; 99 | } 100 | 101 | __attribute__((noinline)) 102 | err_t init_wdt() { 103 | err_t err = NO_ERROR; 104 | 105 | // allocate an interrupt for the watchdog 106 | CHECK_AND_RETHROW(dport_map_interrupt(TG_WDT_LEVEL_INT, false)); 107 | 108 | wdt_unlock(); 109 | 110 | // for pro cpu 111 | TIMG_WDTCONFIG_REG wdtconfig = { 112 | // do it as long as it is needed 113 | .level_int_en = 1, 114 | 115 | // for the stage 1 raise an interrupt, and we 116 | // will clear it manually 117 | .stg0 = 1, 118 | 119 | // we are going to give a small timeout until 120 | // we reset the System if we have a problem 121 | .stg1 = 3, 122 | }; 123 | TIMG0_WDTCONFIG = wdtconfig; 124 | 125 | // set the prescaler to 40000 * 12.5ns == 0.5ms, which means we have 126 | // two ticks per ms, so we just need to multiply the ms by 2 127 | TIMG0_WDTCONFIG1 = 40000 << 16; 128 | 129 | // 10 ms for the first clock 130 | TIMG0_WDTCONFIG2 = 10; 131 | 132 | // 10ms for the second clock 133 | TIMG0_WDTCONFIG3 = 10; 134 | 135 | // enable it 136 | TIMG0_WDTCONFIG.en = 1; 137 | 138 | wdt_lock(); 139 | 140 | // enable watchdog timeout 141 | TIMG0_INT_ENA.wdt_int = 1; 142 | 143 | // clear it 144 | TIMG0_INT_CLR.wdt_int = 1; 145 | 146 | cleanup: 147 | return err; 148 | } 149 | 150 | void wdt_enable() { 151 | wdt_unlock(); 152 | TIMG0_WDTCONFIG.en = 1; 153 | wdt_lock(); 154 | } 155 | 156 | void wdt_disable() { 157 | wdt_unlock(); 158 | TIMG0_WDTCONFIG.en = 0; 159 | wdt_lock(); 160 | } 161 | 162 | void wdt_feed() { 163 | wdt_unlock(); 164 | TIMG0_WDTFEED = 1; 165 | wdt_lock(); 166 | } 167 | 168 | bool wdt_handle() { 169 | // check if we care 170 | if (!TIMG0_INT_ST.wdt_int) 171 | return false; 172 | 173 | // feed the watchdog 174 | wdt_feed(); 175 | 176 | // clear the interrupts 177 | TIMG0_INT_CLR.wdt_int = 1; 178 | 179 | return true; 180 | } 181 | -------------------------------------------------------------------------------- /kernel/src/drivers/timg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util/except.h" 4 | 5 | #include 6 | 7 | /** 8 | * Initialize the watchdog and starting it 9 | */ 10 | err_t init_wdt(); 11 | 12 | /** 13 | * Enable watchdog 14 | */ 15 | void wdt_enable(); 16 | 17 | /** 18 | * Disable watchdog 19 | */ 20 | void wdt_disable(); 21 | 22 | /** 23 | * Feed watchdog 24 | */ 25 | void wdt_feed(); 26 | 27 | /** 28 | * Handle a watchdog interrupt 29 | */ 30 | bool wdt_handle(); 31 | -------------------------------------------------------------------------------- /kernel/src/drivers/timg.ld: -------------------------------------------------------------------------------- 1 | 2 | TIMG0 = TIMG0_BASE + 0x00; 3 | TIMG0_WDTCONFIG = TIMG0_BASE + 0x48; 4 | TIMG0_WDTCONFIG1 = TIMG0_BASE + 0x4c; 5 | TIMG0_WDTCONFIG2 = TIMG0_BASE + 0x50; 6 | TIMG0_WDTCONFIG3 = TIMG0_BASE + 0x54; 7 | TIMG0_WDTCONFIG4 = TIMG0_BASE + 0x58; 8 | TIMG0_WDTCONFIG5 = TIMG0_BASE + 0x5c; 9 | TIMG0_WDTFEED = TIMG0_BASE + 0x60; 10 | TIMG0_WDTWPROTECT = TIMG0_BASE + 0x64; 11 | TIMG0_INT_ENA = TIMG0_BASE + 0x98; 12 | TIMG0_INT_RAW = TIMG0_BASE + 0x9c; 13 | TIMG0_INT_ST = TIMG0_BASE + 0xa0; 14 | TIMG0_INT_CLR = TIMG0_BASE + 0xa4; 15 | -------------------------------------------------------------------------------- /kernel/src/drivers/uart.c: -------------------------------------------------------------------------------- 1 | #include "uart.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // Hardware registers 9 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | typedef union _UART_STATUS_REG { 12 | struct { 13 | uint32_t rxfifo_cnt : 8; 14 | uint32_t st_urx_out : 4; 15 | uint32_t _reserved0 : 1; 16 | uint32_t dsrn : 1; 17 | uint32_t ctsn : 1; 18 | uint32_t rxd : 1; 19 | uint32_t txfifo_cnt : 8; 20 | uint32_t st_utx_out : 4; 21 | uint32_t _reserved1 : 1; 22 | uint32_t dtrn : 1; 23 | uint32_t rtsn : 1; 24 | uint32_t txd : 1; 25 | }; 26 | uint32_t packed; 27 | } MMIO UART_STATUS_REG; 28 | STATIC_ASSERT(sizeof(UART_STATUS_REG) == sizeof(uint32_t)); 29 | 30 | typedef union _UART_INT_REG { 31 | struct { 32 | uint32_t rxfifo_full : 1; 33 | uint32_t txfifo_empty : 1; 34 | uint32_t parity_err : 1; 35 | uint32_t frm_err : 1; 36 | uint32_t rxfifo_ovf : 1; 37 | uint32_t dsr_chg : 1; 38 | uint32_t cts_chg : 1; 39 | uint32_t brk_det : 1; 40 | uint32_t rxfifo_tout : 1; 41 | uint32_t sw_xon : 1; 42 | uint32_t sw_xoff : 1; 43 | uint32_t glitch_det : 1; 44 | uint32_t tx_brk_done : 1; 45 | uint32_t tx_brk_idle : 1; 46 | uint32_t tx_done : 1; 47 | uint32_t rs485_parity_err : 1; 48 | uint32_t rs485_frm_err : 1; 49 | uint32_t rs485_clash : 1; 50 | uint32_t at_cmd_char_det : 1; 51 | uint32_t _reserved : 13; 52 | }; 53 | uint32_t packed; 54 | } MMIO UART_INT_REG; 55 | STATIC_ASSERT(sizeof(UART_INT_REG) == sizeof(uint32_t)); 56 | 57 | extern volatile UART_INT_REG UART0_INT_RAW; 58 | extern volatile UART_INT_REG UART0_INT_ST; 59 | extern volatile UART_INT_REG UART0_INT_ENA; 60 | extern volatile UART_INT_REG UART0_INT_CLR; 61 | 62 | extern volatile uint32_t UART0_FIFO; 63 | extern volatile UART_STATUS_REG UART0_STATUS; 64 | 65 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 66 | // Implementation 67 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 68 | 69 | void init_uart() { 70 | // disable all interrupts 71 | UART0_INT_ENA.packed = 0; 72 | } 73 | 74 | void uart_write_char(char c) { 75 | // TODO: some delay 76 | 77 | // wait until the tx fifo has space 78 | while (UART0_STATUS.txfifo_cnt >= 128); 79 | UART0_FIFO = (uint32_t)c; 80 | } 81 | 82 | // TODO: init the uart tx size as we want it 83 | -------------------------------------------------------------------------------- /kernel/src/drivers/uart.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void init_uart(); 4 | 5 | void uart_write_char(char c); 6 | -------------------------------------------------------------------------------- /kernel/src/drivers/uart.ld: -------------------------------------------------------------------------------- 1 | 2 | UART0_FIFO = UART0_BASE + 0x00; 3 | UART0_INT_RAW = UART0_BASE + 0x04; 4 | UART0_INT_ST = UART0_BASE + 0x08; 5 | UART0_INT_ENA = UART0_BASE + 0x0C; 6 | UART0_INT_CLR = UART0_BASE + 0x10; 7 | UART0_STATUS = UART0_BASE + 0x1C; 8 | -------------------------------------------------------------------------------- /kernel/src/initrd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * The initrd header 8 | */ 9 | typedef struct initrd_header { 10 | size_t total_size; 11 | uint8_t count; 12 | } initrd_header_t; 13 | 14 | /** 15 | * The entry header 16 | */ 17 | typedef struct initrd_entry { 18 | size_t size; 19 | char name[64]; 20 | } initrd_entry_t; 21 | 22 | #define INITRD_NEXT_ENTRY(entry) \ 23 | ((initrd_entry_t*)((uintptr_t) (entry) + sizeof(initrd_entry_t) + (entry)->size)) 24 | -------------------------------------------------------------------------------- /kernel/src/linker.ld: -------------------------------------------------------------------------------- 1 | 2 | /****************/ 3 | /* kernel entry */ 4 | /****************/ 5 | 6 | ENTRY(_start) 7 | 8 | /*************************************/ 9 | /* memory ranges used for the kernel */ 10 | /*************************************/ 11 | 12 | 13 | /* 14 | * we use SRAM1 for the kernel because it is governed by a static MPU 15 | * the total size is 128K we will separate it half and half 16 | */ 17 | MEMORY { 18 | DUMMY (R) : ORIGIN = 0x00000000, len = 128K 19 | IRAM (RX) : ORIGIN = 0x400A0000, len = 128K 20 | DRAM (RW) : ORIGIN = 0x3FFE0000, len = 128K 21 | VDSO_IRAM (RX) : ORIGIN = 0x4009E000, len = 8K 22 | } 23 | 24 | /**********************************/ 25 | /* symbols for system peripherals */ 26 | /**********************************/ 27 | 28 | /* builtin stuff */ 29 | INCLUDE syms/bootrom.ld 30 | INCLUDE syms/io.ld 31 | 32 | /* drivers */ 33 | INCLUDE src/drivers/dport.ld 34 | INCLUDE src/drivers/pid.ld 35 | INCLUDE src/drivers/rtc_cntl.ld 36 | INCLUDE src/drivers/timg.ld 37 | INCLUDE src/drivers/uart.ld 38 | 39 | /**********************/ 40 | /* section definition */ 41 | /**********************/ 42 | 43 | SECTIONS { 44 | 45 | .header : { 46 | /* create the kernel header */ 47 | LONG(0x4c4e524b); /* magic */ 48 | LONG(SIZEOF(.text)); /* code size */ 49 | LONG(SIZEOF(.data)); /* data size */ 50 | LONG(SIZEOF(.vdso)); /* vdso size */ 51 | LONG(_start); /* entry pointer */ 52 | } > DUMMY 53 | 54 | .text : { 55 | *(.text .text.*) 56 | . = ALIGN(4); 57 | } > IRAM 58 | 59 | .data : { 60 | *(.rodata .rodata.*) 61 | *(.data .data.*) 62 | . = ALIGN(4); 63 | _data_end = .; 64 | 65 | *(.bss .bss.*) 66 | *(COMMON) 67 | _bss_end = .; 68 | 69 | /* the heap starts right after the bss */ 70 | . = ALIGN(16); 71 | _heap_start = .; 72 | } > DRAM 73 | 74 | .vdso : { 75 | *(.vdso .vdso.*) 76 | } > VDSO_IRAM 77 | 78 | /* calculate the heap size, which is the size left between code and data */ 79 | _heap_size = 128K - SIZEOF(.text) - SIZEOF(.data); 80 | _heap_end = _heap_start + _heap_size; 81 | } 82 | -------------------------------------------------------------------------------- /kernel/src/main.S: -------------------------------------------------------------------------------- 1 | 2 | .section .text 3 | 4 | .extern kmain 5 | .extern vecbase 6 | .extern g_pro_cpu_stack 7 | 8 | .global _start 9 | .align 16 10 | _start: 11 | // reset the windowbase and windowstart for function calls 12 | movi a1, 1 13 | movi a0, 0 14 | wsr.windowstart a1 15 | wsr.windowbase a0 16 | rsync 17 | 18 | // set the interrupt vector 19 | movi a0, vecbase 20 | wsr.vecbase a0 21 | rsync 22 | 23 | // clear loop count 24 | movi a0, 0 25 | wsr.lcount a0 26 | rsync 27 | 28 | // set the stack and enable register window, we are going to use 29 | // the app cpu stack for now, just so we will not have a shared stack 30 | // between the interrupt handlers and the init stack 31 | movi a0, 4096 32 | movi sp, g_app_cpu_stack 33 | add sp, sp, a0 34 | 35 | // jump to kernel 36 | call4 kmain 37 | -------------------------------------------------------------------------------- /kernel/src/main.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include "arch/intrin.h" 6 | #include "drivers/pid.h" 7 | #include "mem/mem.h" 8 | #include "arch/cpu.h" 9 | #include "drivers/rtc_cntl.h" 10 | #include "drivers/timg.h" 11 | #include "drivers/uart.h" 12 | #include "task/scheduler.h" 13 | #include "initrd.h" 14 | #include "task/loader.h" 15 | #include "util/string.h" 16 | 17 | /** 18 | * The stacks for each cpu 19 | */ 20 | 21 | __attribute__((aligned(STACK_SIZE))) 22 | char g_pro_cpu_stack[STACK_SIZE] = { 0 }; 23 | 24 | __attribute__((aligned(STACK_SIZE))) 25 | char g_app_cpu_stack[STACK_SIZE] = { 0 }; 26 | 27 | /** 28 | * Array of the kernel stack ptrs, 29 | * for use in exception handlers 30 | */ 31 | void* g_kernel_stacks[2] = { 32 | g_pro_cpu_stack + sizeof(g_pro_cpu_stack), 33 | g_app_cpu_stack + sizeof(g_app_cpu_stack), 34 | }; 35 | 36 | static err_t load_from_initrd() { 37 | err_t err = NO_ERROR; 38 | initrd_header_t* header = NULL; 39 | 40 | // where the loader stores the initrd 41 | initrd_header_t* initrd_handoff = (void*)0x3FFC2000; 42 | TRACE("Loading from initrd (%d entries - %d bytes)", initrd_handoff->count, initrd_handoff->total_size); 43 | 44 | // copy it to the heap instead 45 | header = malloc(initrd_handoff->total_size); 46 | CHECK_ERROR(header != NULL, ERROR_OUT_OF_RESOURCES); 47 | memcpy(header, initrd_handoff, initrd_handoff->total_size); 48 | 49 | initrd_entry_t* entry = (initrd_entry_t*)(header + 1); 50 | TRACE("IN HERE - %s", entry->name); 51 | for (int i = 0; i < header->count; i++, entry = INITRD_NEXT_ENTRY(entry)) { 52 | TRACE("LOL"); 53 | TRACE("\tLoading %s - %d bytes", entry->name, entry->size); 54 | CHECK_AND_RETHROW(loader_load_app(entry->name, entry + 1, entry->size)); 55 | } 56 | 57 | cleanup: 58 | if (header != NULL) { 59 | free(header); 60 | } 61 | 62 | return err; 63 | } 64 | 65 | void kmain() { 66 | err_t err = NO_ERROR; 67 | 68 | // setup the basic state to be ready for action 69 | ps_t ps = __read_ps(); 70 | ps.excm = 0; // normal exception mode 71 | ps.intlevel = 0xF; // interrupts disabled 72 | ps.um = 1; // usermode 73 | ps.woe = 1; // window overflow enabled 74 | __write_ps(ps); 75 | 76 | init_uart(); 77 | TRACE("Hello from kernel!"); 78 | 79 | // make sure we are running from the pro cpu 80 | ASSERT(get_cpu_index() == 0); 81 | 82 | // initialize all the basic dport config 83 | init_dport(); 84 | 85 | // init the mmu and pid units for operation 86 | init_mmu(); 87 | init_pid(); 88 | 89 | // initialize the kernel allocator 90 | CHECK_AND_RETHROW(init_mem()); 91 | 92 | // enable interrupts 93 | ps = __read_ps(); 94 | ps.intlevel = 0; // interrupts enabled 95 | __write_ps(ps); 96 | 97 | // clear all pending interrupts 98 | __WSR(INTCLEAR, BIT0); 99 | 100 | // enable all interrupt levels 101 | __WSR(INTENABLE, BIT0); 102 | 103 | // sync all these configurations 104 | __rsync(); 105 | 106 | // setup all the first tasks 107 | CHECK_AND_RETHROW(load_from_initrd()); 108 | 109 | // init scheduler 110 | CHECK_AND_RETHROW(init_wdt()); 111 | scheduler_drop_current(); 112 | 113 | TRACE("We are done here"); 114 | cleanup: 115 | ASSERT(!IS_ERROR(err)); 116 | while(1); 117 | } 118 | -------------------------------------------------------------------------------- /kernel/src/mem/mem.c: -------------------------------------------------------------------------------- 1 | #include "mem.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | extern void* _heap_start; 11 | extern void* _heap_size; 12 | 13 | void* UMM_MALLOC_CFG_HEAP_ADDR = NULL; 14 | uint32_t UMM_MALLOC_CFG_HEAP_SIZE = 0; 15 | 16 | err_t init_mem() { 17 | err_t err = NO_ERROR; 18 | 19 | void* heap_start = &_heap_start; 20 | uint32_t heap_size = (uint32_t)&_heap_size; 21 | 22 | // align the heap start 23 | void* tmp_start = ALIGN_UP(heap_start, 4); 24 | uint32_t added = tmp_start - heap_start; 25 | heap_start = tmp_start; 26 | heap_size -= added; 27 | 28 | // align the size of the heap properly 29 | heap_size = ALIGN_DOWN(heap_size, 4); 30 | 31 | UMM_MALLOC_CFG_HEAP_ADDR = heap_start; 32 | UMM_MALLOC_CFG_HEAP_SIZE = heap_size; 33 | TRACE("Kernel heap: %p (%S)", UMM_MALLOC_CFG_HEAP_ADDR, UMM_MALLOC_CFG_HEAP_SIZE); 34 | 35 | // initialize it 36 | umm_init(); 37 | 38 | cleanup: 39 | return err; 40 | } 41 | 42 | void* malloc(size_t size) { 43 | return umm_malloc(size); 44 | } 45 | 46 | void free(void* ptr) { 47 | umm_free(ptr); 48 | } 49 | -------------------------------------------------------------------------------- /kernel/src/mem/mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #define USER_CODE_BASE (0x40080000) 9 | #define USER_DATA_BASE (0x3FFC0000) 10 | 11 | #define USER_PAGE_SIZE SIZE_8KB 12 | 13 | // last page of code space, identity mapped 14 | #define VDSO_PAGE_INDEX 15 15 | 16 | // last page of data space 17 | #define UCTX_PAGE_INDEX 15 18 | 19 | err_t init_mem(); 20 | 21 | void* malloc(size_t size); 22 | 23 | void free(void* ptr); 24 | 25 | #define SAFE_FREE(ptr) \ 26 | do { \ 27 | if (ptr != NULL) { \ 28 | free(ptr); \ 29 | ptr = NULL; \ 30 | } \ 31 | } while (0) 32 | -------------------------------------------------------------------------------- /kernel/src/mem/umem.c: -------------------------------------------------------------------------------- 1 | #include "umem.h" 2 | #include "mem.h" 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Code pages that are free to use, we 9 | * start with all of them except the 10 | * last one which is used for vdso 11 | */ 12 | static uint16_t m_code_pages = 0x7fff; 13 | 14 | /** 15 | * Data pages that are free to use, we 16 | * start with all of them 17 | */ 18 | static uint16_t m_data_pages = 0xFFFF; 19 | 20 | int umem_alloc_code_page() { 21 | // nothing to allocate 22 | if (m_code_pages == 0) { 23 | return INVALID_PAGE; 24 | } 25 | 26 | // find a free page 27 | int index = __builtin_ffs(m_code_pages) - 1; 28 | m_code_pages &= ~(1 << index); 29 | 30 | // return it as a pointer 31 | return index; 32 | } 33 | 34 | int umem_alloc_data_page() { 35 | // nothing to allocate 36 | if (m_data_pages == 0) { 37 | return INVALID_PAGE; 38 | } 39 | 40 | // find a free page 41 | int index = __builtin_ffs(m_data_pages) - 1; 42 | m_data_pages &= ~(1 << index); 43 | 44 | // return it as a pointer 45 | return index; 46 | } 47 | 48 | void umem_free_code_page(int index) { 49 | // TODO: verify the pointers 50 | m_code_pages |= 1 << index; 51 | } 52 | 53 | void umem_free_data_page(int index) { 54 | m_data_pages |= 1 << index; 55 | } 56 | -------------------------------------------------------------------------------- /kernel/src/mem/umem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mem.h" 4 | 5 | #define CODE_PAGE_INDEX(x) \ 6 | (((x) - USER_CODE_BASE) / USER_PAGE_SIZE) 7 | 8 | #define DATA_PAGE_INDEX(x) \ 9 | (((x) - USER_DATA_BASE) / USER_PAGE_SIZE) 10 | 11 | #define CODE_PAGE_ADDR(x) \ 12 | (((x) * USER_PAGE_SIZE) + USER_CODE_BASE) 13 | 14 | #define DATA_PAGE_ADDR(x) \ 15 | (((x) * USER_PAGE_SIZE) + USER_DATA_BASE) 16 | 17 | #define INVALID_PAGE -1 18 | 19 | int umem_alloc_code_page(); 20 | 21 | int umem_alloc_data_page(); 22 | 23 | void umem_free_code_page(int ptr); 24 | 25 | void umem_free_data_page(int ptr); 26 | -------------------------------------------------------------------------------- /kernel/src/task/loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "loader.h" 4 | #include "task.h" 5 | #include "app.h" 6 | #include "mem/umem.h" 7 | #include "scheduler.h" 8 | #include "drivers/pid.h" 9 | 10 | err_t loader_load_app(const char* name, void* app, size_t app_size) { 11 | err_t err = NO_ERROR; 12 | int8_t pages[MAX_PAGE_COUNT * 2]; 13 | memset(pages, -1, sizeof(pages)); 14 | 15 | // get and validate the header 16 | app_header_t* header = app; 17 | CHECK(header->magic == APP_HEADER_MAGIC); 18 | CHECK(app_size >= sizeof(app_header_t)); 19 | CHECK(app_size >= header->code_size + header->data_size); 20 | CHECK(USER_CODE_BASE <= header->entry && header->entry < USER_CODE_BASE + header->code_size); 21 | 22 | // create the task 23 | task_t* task = create_task((void*)header->entry, name); 24 | CHECK_ERROR(task != NULL, ERROR_OUT_OF_RESOURCES); 25 | 26 | // prepare the sizes for allocation 27 | size_t code_pages = ALIGN_UP(header->code_size, USER_PAGE_SIZE) / USER_PAGE_SIZE; 28 | size_t data_pages = ALIGN_UP(header->data_size + header->bss_size, USER_PAGE_SIZE) / USER_PAGE_SIZE; 29 | CHECK(code_pages < MAX_PAGE_COUNT); 30 | CHECK(data_pages < MAX_PAGE_COUNT); 31 | 32 | TRACE("Starting app `%s` (%d code pages, %d data pages)", 33 | name, code_pages, data_pages); 34 | 35 | void* ptr = header + 1; 36 | size_t code_size_left = header->code_size; 37 | size_t data_size_left = header->data_size; 38 | 39 | // 40 | // allocate the pages, both data nad code 41 | // 42 | 43 | for (int i = 0; i < code_pages; i++) { 44 | // allocate the page 45 | int page_idx = umem_alloc_code_page(); 46 | CHECK_ERROR(page_idx != -1, ERROR_OUT_OF_RESOURCES); 47 | pages[i] = (int8_t)page_idx; 48 | 49 | // copy it 50 | size_t to_copy = MINU(code_size_left, USER_PAGE_SIZE); 51 | void* krnl_ptr = (void*)CODE_PAGE_ADDR(page_idx); 52 | xthal_memcpy(krnl_ptr, ptr, to_copy); 53 | code_size_left -= to_copy; 54 | ptr += to_copy; 55 | 56 | // map it nicely 57 | CHECK_AND_RETHROW(mmu_map(&task->mmu, MMU_SPACE_CODE, i, PAGE_ENTRY(page_idx))); 58 | TRACE("> %p --> %p", CODE_PAGE_ADDR(i), CODE_PAGE_ADDR(page_idx)); 59 | } 60 | 61 | for (int i = 0; i < data_pages; i++) { 62 | // allocate the page 63 | int page_idx = umem_alloc_data_page(); 64 | CHECK_ERROR(page_idx != -1, ERROR_OUT_OF_RESOURCES); 65 | pages[i + MAX_PAGE_COUNT] = (int8_t)page_idx; 66 | 67 | // copy it 68 | size_t to_copy = MINU(data_size_left, USER_PAGE_SIZE); 69 | void* krnl_ptr = (void*)DATA_PAGE_ADDR(page_idx); 70 | xthal_memcpy(krnl_ptr, ptr, to_copy); 71 | data_size_left -= to_copy; 72 | ptr += to_copy; 73 | 74 | // map it nicely 75 | CHECK_AND_RETHROW(mmu_map(&task->mmu, MMU_SPACE_DATA, i, PAGE_ENTRY(page_idx))); 76 | TRACE("> %p --> %p", DATA_PAGE_ADDR(i), DATA_PAGE_ADDR(page_idx)); 77 | } 78 | 79 | // TODO: clear bss 80 | 81 | // 82 | // The task is ready to run 83 | // 84 | scheduler_ready_task(task); 85 | 86 | cleanup: 87 | if (IS_ERROR(err)) { 88 | // free the pages allocated for the process 89 | for (int i = 0; i < ARRAY_LEN(pages); i++) { 90 | if (pages[i] != -1) { 91 | if (i < MAX_PAGE_COUNT) { 92 | umem_free_code_page(pages[i]); 93 | } else { 94 | umem_free_data_page(pages[i]); 95 | } 96 | } 97 | } 98 | 99 | // release the task, freeing it as well 100 | SAFE_RELEASE_TASK(task); 101 | } 102 | return err; 103 | } 104 | -------------------------------------------------------------------------------- /kernel/src/task/loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | err_t loader_load_app(const char* name, void* app, size_t app_size); 6 | -------------------------------------------------------------------------------- /kernel/src/task/scheduler.c: -------------------------------------------------------------------------------- 1 | #include "scheduler.h" 2 | #include "arch/cpu.h" 3 | #include "drivers/timg.h" 4 | #include "syscall.h" 5 | 6 | // little helper to deal with the global run queue 7 | typedef struct task_queue { 8 | task_t* head; 9 | task_t* tail; 10 | } task_queue_t; 11 | 12 | static void task_queue_push_back(task_queue_t* q, task_t* thread) { 13 | thread->sched_link = NULL; 14 | if (q->tail != NULL) { 15 | q->tail->sched_link = thread; 16 | } else { 17 | q->head = thread; 18 | } 19 | q->tail = thread; 20 | } 21 | 22 | static task_t* task_queue_pop(task_queue_t* q) { 23 | task_t* task = q->head; 24 | if (task != NULL) { 25 | q->head = task->sched_link; 26 | if (q->head == NULL) { 27 | q->tail = NULL; 28 | } 29 | } 30 | return task; 31 | } 32 | 33 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 34 | // Global run queue 35 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 36 | 37 | #define RUN_QUEUE_LEN 256 38 | 39 | // The global run queue 40 | static task_queue_t m_global_run_queue; 41 | static int32_t m_global_run_queue_size; 42 | 43 | // spinlock to protect the scheduler internal stuff 44 | //static irq_spinlock_t m_scheduler_lock = INIT_IRQ_SPINLOCK(); 45 | 46 | static void global_run_queue_put(task_t* task) { 47 | task_queue_push_back(&m_global_run_queue, task); 48 | m_global_run_queue_size++; 49 | } 50 | 51 | static task_t* global_run_queue_get() { 52 | if (m_global_run_queue_size == 0) { 53 | return NULL; 54 | } 55 | m_global_run_queue_size--; 56 | task_t* task = task_queue_pop(&m_global_run_queue); 57 | return task; 58 | } 59 | 60 | static void lock_scheduler() { 61 | // irq_spinlock_lock(&m_scheduler_lock); 62 | } 63 | 64 | static void unlock_scheduler() { 65 | // irq_spinlock_unlock(&m_scheduler_lock); 66 | } 67 | 68 | static void wake_cpu() { 69 | // TODO: this 70 | } 71 | 72 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 73 | // Wake a thread 74 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 75 | 76 | void scheduler_ready_task(task_t* task) { 77 | task->sched_link = NULL; 78 | 79 | scheduler_preempt_disable(); 80 | 81 | ASSERT((get_task_status(task) & ~TASK_SUSPEND) == TASK_STATUS_WAITING); 82 | 83 | // Mark as runnable 84 | cas_task_state(task, TASK_STATUS_WAITING, TASK_STATUS_RUNNABLE); 85 | 86 | // Put in the run queue 87 | lock_scheduler(); 88 | global_run_queue_put(task); 89 | unlock_scheduler(); 90 | 91 | // in case someone can steal 92 | wake_cpu(); 93 | 94 | scheduler_preempt_enable(); 95 | } 96 | 97 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 98 | // Preemption 99 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 100 | 101 | void scheduler_preempt_disable(void) { 102 | if (get_cpu_context()->preempt_disable_depth++ == 0) { 103 | // TODO: enable no preemption 104 | } 105 | } 106 | 107 | void scheduler_preempt_enable(void) { 108 | if (++get_cpu_context()->preempt_disable_depth == 0) { 109 | // TODO: disable no preemption 110 | } 111 | } 112 | 113 | 114 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 115 | // Actual scheduling 116 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 117 | 118 | //---------------------------------------------------------------------------------------------------------------------- 119 | // Actually running a thread 120 | //---------------------------------------------------------------------------------------------------------------------- 121 | 122 | static void scheduler_set_deadline() { 123 | wdt_feed(); 124 | wdt_enable(); 125 | } 126 | 127 | static void scheduler_cancel_deadline() { 128 | wdt_disable(); 129 | } 130 | 131 | /** 132 | * Execute the thread on the current cpu 133 | * 134 | * @param ctx [IN] The context of the scheduler interrupt 135 | * @param thread [IN] The thread to run 136 | */ 137 | static void execute(task_regs_t* ctx, task_t* task) { 138 | // set the current thread 139 | get_cpu_context()->current_task = task; 140 | 141 | // get ready to run it 142 | cas_task_state(task, TASK_STATUS_RUNNABLE, TASK_STATUS_RUNNING); 143 | 144 | // set a new timeslice of 10 milliseconds 145 | scheduler_set_deadline(); 146 | 147 | // set the gprs context 148 | restore_task_context(task, ctx); 149 | 150 | // prepare for the switch 151 | pid_prepare(); 152 | } 153 | 154 | static void save_current_task(task_regs_t* ctx, bool park) { 155 | per_cpu_context_t* pctx = get_cpu_context(); 156 | 157 | ASSERT(pctx->current_task != NULL); 158 | task_t* current_task = pctx->current_task; 159 | pctx->current_task = NULL; 160 | 161 | // save the state and set the thread to runnable 162 | save_task_context(current_task, ctx); 163 | 164 | // put the thread back 165 | if (!park) { 166 | // set the thread to be runnable 167 | cas_task_state(current_task, TASK_STATUS_RUNNING, TASK_STATUS_RUNNABLE); 168 | 169 | // put in the local run queue 170 | global_run_queue_put(current_task); 171 | } else { 172 | cas_task_state(current_task, TASK_STATUS_RUNNING, TASK_STATUS_WAITING); 173 | } 174 | } 175 | 176 | //---------------------------------------------------------------------------------------------------------------------- 177 | // Scheduler itself 178 | //---------------------------------------------------------------------------------------------------------------------- 179 | 180 | static void cpu_put_idle() { 181 | } 182 | 183 | static void cpu_wake_idle() { 184 | } 185 | 186 | static task_t* find_runnable() { 187 | task_t* task = NULL; 188 | 189 | while (true) { 190 | // get from the global run queue 191 | lock_scheduler(); 192 | task = global_run_queue_get(); 193 | unlock_scheduler(); 194 | if (task != NULL) { 195 | return task; 196 | } 197 | 198 | // 199 | // We have nothing to do 200 | // 201 | 202 | lock_scheduler(); 203 | 204 | // we are now idle 205 | cpu_put_idle(); 206 | 207 | unlock_scheduler(); 208 | 209 | // TODO: spinning 210 | 211 | // we have nothing to do, so put the cpu into 212 | // a sleeping state until an interrupt or something 213 | // else happens. we will lower the state 214 | asm volatile ("WAITI 0"); 215 | 216 | lock_scheduler(); 217 | cpu_wake_idle(); 218 | unlock_scheduler(); 219 | 220 | } 221 | } 222 | 223 | static void schedule(task_regs_t* ctx) { 224 | // will block until a thread is ready, essentially an idle loop, 225 | // this must return something eventually. 226 | task_t* thread = find_runnable(); 227 | 228 | // actually run the new thread 229 | execute(ctx, thread); 230 | } 231 | 232 | //---------------------------------------------------------------------------------------------------------------------- 233 | // Scheduler callbacks 234 | //---------------------------------------------------------------------------------------------------------------------- 235 | 236 | void scheduler_on_schedule(task_regs_t* regs) { 237 | // save the current thread, don't park it 238 | save_current_task(regs, false); 239 | 240 | // now schedule a new thread 241 | schedule(regs); 242 | } 243 | 244 | void scheduler_on_park(task_regs_t* regs) { 245 | // save the current thread, park it 246 | save_current_task(regs, true); 247 | 248 | // check if we need to call a callback before we schedule 249 | per_cpu_context_t* pctx = get_cpu_context(); 250 | if (pctx->park_callback != NULL) { 251 | pctx->park_callback(pctx->park_arg); 252 | pctx->park_callback = NULL; 253 | pctx->park_arg = NULL; 254 | } 255 | 256 | // cancel the deadline of the current thread, as it is parked 257 | scheduler_cancel_deadline(); 258 | 259 | // schedule a new thread 260 | schedule(regs); 261 | } 262 | 263 | void scheduler_on_drop(task_regs_t* regs) { 264 | per_cpu_context_t* pctx = get_cpu_context(); 265 | 266 | task_t* current_task = pctx->current_task; 267 | pctx->current_task = NULL; 268 | 269 | if (current_task != NULL) { 270 | // change the status to dead 271 | cas_task_state(current_task, TASK_STATUS_RUNNING, TASK_STATUS_DEAD); 272 | 273 | // release the reference that the scheduler has 274 | release_task(current_task); 275 | } 276 | 277 | // cancel the deadline of the current thread, as it is dead 278 | scheduler_cancel_deadline(); 279 | 280 | schedule(regs); 281 | } 282 | 283 | //---------------------------------------------------------------------------------------------------------------------- 284 | // Interrupts to call the scheduler 285 | //---------------------------------------------------------------------------------------------------------------------- 286 | 287 | void scheduler_yield() { 288 | // don't preempt if we can't preempt 289 | if (get_cpu_context()->preempt_disable_depth > 0) { 290 | return; 291 | } 292 | syscall0(SYSCALL_SCHED_YIELD); 293 | } 294 | 295 | void scheduler_park(void(*callback)(void* arg), void* arg) { 296 | get_cpu_context()->park_callback = callback; 297 | get_cpu_context()->park_arg = arg; 298 | syscall0(SYSCALL_SCHED_PARK); 299 | } 300 | 301 | void scheduler_drop_current() { 302 | syscall0(SYSCALL_SCHED_DROP); 303 | } 304 | 305 | task_t* get_current_task() { 306 | return get_cpu_context()->current_task; 307 | } 308 | -------------------------------------------------------------------------------- /kernel/src/task/scheduler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "task.h" 4 | 5 | #define CPU_COUNT 2 6 | 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // Control scheduling 9 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | /** 12 | * Put a task into a ready state 13 | * 14 | * @param task [IN] 15 | */ 16 | void scheduler_ready_task(task_t* task); 17 | 18 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 19 | // Preemption stuff 20 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | /** 23 | * Disable preemption, nestable 24 | */ 25 | void scheduler_preempt_disable(void); 26 | 27 | /** 28 | * Enable preemption, nestable 29 | */ 30 | void scheduler_preempt_enable(void); 31 | 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | // Callbacks from interrupts to the scheduler 34 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | void scheduler_on_schedule(task_regs_t* regs); 37 | 38 | void scheduler_on_park(task_regs_t* regs); 39 | 40 | void scheduler_on_drop(task_regs_t* regs); 41 | 42 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 43 | // Call the scheduler to do stuff 44 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | /** 47 | * Request the scheduler to yield from our thread, passing our time-slice to the caller, 48 | * putting us at the CPU's local run-queue 49 | */ 50 | void scheduler_yield(); 51 | 52 | /** 53 | * Park the current thread, putting us into sleep and not putting us to the run-queue 54 | */ 55 | void scheduler_park(void(*callback)(void* arg), void* arg); 56 | 57 | /** 58 | * Drop the current thread and schedule a new one instead 59 | */ 60 | void scheduler_drop_current(); 61 | 62 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 63 | // Get the current running thread 64 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 65 | 66 | /** 67 | * Get the currently running task on the current CPU 68 | */ 69 | task_t* get_current_task(); 70 | -------------------------------------------------------------------------------- /kernel/src/task/syscall.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "syscall.h" 3 | #include "scheduler.h" 4 | #include "mem/umem.h" 5 | 6 | static err_t get_user_ptr(uintptr_t user_ptr, size_t user_size, void** ptr) { 7 | err_t err = NO_ERROR; 8 | 9 | // TODO: check no page boundary crossing 10 | // TODO: check in the range 11 | 12 | // convert by getting the page index and 13 | // convert it to the physical index, 14 | // converting back to a full address 15 | task_t* task = get_current_task(); 16 | int idx = DATA_PAGE_INDEX(user_ptr); 17 | *ptr = (void*)DATA_PAGE_ADDR(task->mmu.dmmu.entries[idx].phys); 18 | 19 | cleanup: 20 | return err; 21 | } 22 | 23 | void common_syscall_handler(task_regs_t* regs) { 24 | err_t err = NO_ERROR; 25 | 26 | // get the syscall number and set the default return to 0 27 | uint32_t syscall_num = regs->ar[SYSCALL_NUM]; 28 | regs->ar[SYSCALL_RET] = 0; 29 | 30 | // handle the syscall 31 | switch (syscall_num) { 32 | // scheduling related syscalls 33 | case SYSCALL_SCHED_PARK: scheduler_on_park(regs); break; 34 | case SYSCALL_SCHED_YIELD: scheduler_on_schedule(regs); break; 35 | case SYSCALL_SCHED_DROP: scheduler_on_drop(regs); break; 36 | 37 | // misc syscalls 38 | case SYSCALL_LOG: { 39 | // resolve arguments 40 | void* str_ptr = NULL; 41 | size_t str_len = regs->ar[SYSCALL_ARG2]; 42 | CHECK_AND_RETHROW(get_user_ptr(regs->ar[SYSCALL_ARG1], str_len, &str_ptr)); 43 | 44 | // print it 45 | TRACE("%.64s: %.*s", get_current_task()->ucontext->name, str_len, str_ptr); 46 | } break; 47 | 48 | default: 49 | CHECK_FAIL_ERROR(ERROR_INVALID_SYSCALL, "unknown syscall %d", regs->ar[2]); 50 | } 51 | 52 | cleanup: 53 | // if we had an error set it manually to the error 54 | if (IS_ERROR(err)) { 55 | regs->ar[SYSCALL_RET] = -err; 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /kernel/src/task/syscall.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | // Syscalls ABI 9 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | #define SYSCALL_NUM 2 12 | #define SYSCALL_RET 2 13 | 14 | #define SYSCALL_ARG1 6 15 | #define SYSCALL_ARG2 3 16 | #define SYSCALL_ARG3 4 17 | #define SYSCALL_ARG4 5 18 | #define SYSCALL_ARG5 8 19 | #define SYSCALL_ARG6 9 20 | -------------------------------------------------------------------------------- /kernel/src/task/task.c: -------------------------------------------------------------------------------- 1 | #include "task.h" 2 | #include "mem/umem.h" 3 | #include "vdso/vdso.h" 4 | #include "drivers/pid.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | typedef struct reg_offset { 13 | const char* name; 14 | uint32_t offset; 15 | } reg_offset_t; 16 | 17 | static reg_offset_t m_reg_offsets[] = { 18 | { "LBEG", offsetof(task_regs_t, lbeg) }, 19 | { "LEND", offsetof(task_regs_t, lend) }, 20 | { "LCOUNT", offsetof(task_regs_t, lcount) }, 21 | { "SAR", offsetof(task_regs_t, sar) }, 22 | { "WINDOWBASE", offsetof(task_regs_t, windowbase) }, 23 | { "WINDOWSTART", offsetof(task_regs_t, windowstart) }, 24 | { "PS", offsetof(task_regs_t, ps) }, 25 | }; 26 | 27 | void task_regs_dump(task_regs_t* regs) { 28 | TRACE("PC=%08x", regs->pc); 29 | 30 | TRACE(""); 31 | 32 | for (int i = 0; i < ALIGN_UP(ARRAY_LEN(m_reg_offsets), 4) / 4; i++) { 33 | printf("[*] "); 34 | for (int j = 0; j < 4; j++) { 35 | int idx = i * 4 + j; 36 | if (idx >= ARRAY_LEN(m_reg_offsets)) break; 37 | 38 | size_t value = *(size_t*)((void*)regs + m_reg_offsets[idx].offset); 39 | const char* name = m_reg_offsets[idx].name; 40 | printf("%12s=%08x ", name, value); 41 | } 42 | printf("\n\r"); 43 | } 44 | 45 | TRACE(""); 46 | 47 | for (int i = 0; i < 4; i++) { 48 | printf("[*] "); 49 | for (int j = 0; j < 4; j++) { 50 | int idx = i * 4 + j; 51 | printf(" A%02d=%08x", idx, regs->ar[idx]); 52 | } 53 | printf("\n\r"); 54 | } 55 | 56 | TRACE(""); 57 | 58 | for (int i = 0; i < 64 / 4; i++) { 59 | printf("[*] "); 60 | for (int j = 0; j < 4; j++) { 61 | int abs_idx = i * 4 + j; 62 | 63 | int real_idx = abs_idx - regs->windowbase * 4; 64 | if (real_idx < 0) { 65 | real_idx = 64 + real_idx; 66 | } 67 | 68 | printf("AR%02d=%08x ", abs_idx, regs->ar[real_idx]); 69 | 70 | if ((abs_idx % 4) == 3) { 71 | // check for windowbase/windowstart 72 | bool ws = (regs->windowstart & (1 << (abs_idx / 4))) != 0; 73 | bool cw = regs->windowbase == (abs_idx / 4); 74 | printf("%c%c\n\r", ws ? '<' : ' ', cw ? '=' : ' '); 75 | } 76 | } 77 | } 78 | } 79 | 80 | static pid_t m_pid_gen = 0; 81 | 82 | task_t* create_task(void* entry, const char* fmt, ...) { 83 | // allocate the memory 84 | task_t* task = malloc(sizeof(task_t)); 85 | if (task == NULL) { 86 | return NULL; 87 | } 88 | 89 | // zero it out 90 | memset(task, 0, sizeof(task_t)); 91 | task->status = TASK_STATUS_DEAD; 92 | 93 | // initialize the uctx 94 | task->pid = m_pid_gen++; 95 | 96 | // allocate the uctx 97 | int uctx_page = umem_alloc_data_page(); 98 | if (uctx_page == -1) { 99 | free(task); 100 | return NULL; 101 | } 102 | mmu_map(&task->mmu, MMU_SPACE_DATA, UCTX_PAGE_INDEX, PAGE_ENTRY(uctx_page)); 103 | 104 | task->ucontext = (task_ucontext_t*)DATA_PAGE_ADDR(uctx_page); 105 | memset(task->ucontext, 0, sizeof(task_ucontext_t)); 106 | 107 | // setup the user context 108 | task->ucontext->regs.ps = (ps_t){ 109 | .excm = 0, 110 | .intlevel = 0, 111 | .um = 1, 112 | .woe = 1 113 | }; 114 | task->ucontext->regs.pc = (uintptr_t)task_trampoline; 115 | task->ucontext->regs.ar[2] = (uintptr_t)entry; 116 | 117 | // set the SP to be the end of the stack, which is at the ucontext 118 | // area at the end 119 | task->ucontext->regs.ar[1] = (uint32_t) (DATA_PAGE_ADDR(UCTX_PAGE_INDEX) + offsetof(task_ucontext_t, stack) + STACK_SIZE); 120 | 121 | // set the name 122 | va_list va; 123 | va_start(va, fmt); 124 | vsnprintf(task->ucontext->name, sizeof(task->ucontext->name), fmt, va); 125 | va_end(va); 126 | 127 | // set the state as waiting 128 | cas_task_state(task, TASK_STATUS_DEAD, TASK_STATUS_WAITING); 129 | 130 | return task; 131 | } 132 | 133 | void release_task(task_t* task) { 134 | ASSERT(!"TODO: release_task"); 135 | } 136 | 137 | task_status_t get_task_status(task_t* thread) { 138 | return atomic_load(&thread->status); 139 | } 140 | 141 | void cas_task_state(task_t* task, task_status_t old, task_status_t new) { 142 | // sanity 143 | ASSERT((old & TASK_SUSPEND) == 0); 144 | ASSERT((new & TASK_SUSPEND) == 0); 145 | ASSERT(old != new); 146 | 147 | // loop if status is in a suspend state giving the GC 148 | // time to finish and change the state to old val 149 | task_status_t old_value = old; 150 | for (int i = 0; !atomic_compare_exchange_weak(&task->status, &old_value, new); i++, old_value = old) { 151 | if (old == TASK_STATUS_WAITING && task->status == TASK_STATUS_RUNNABLE) { 152 | ASSERT(!"Waiting for TASK_STATUS_WAITING but is TASK_STATUS_RUNNABLE"); 153 | } 154 | 155 | // pause for max of 10 times polling for status 156 | for (int x = 0; x < 10 && task->status != old; x++) { 157 | asm("":::"memory"); 158 | } 159 | } 160 | } 161 | 162 | void save_task_context(task_t* task, task_regs_t* regs) { 163 | task->ucontext->regs = *regs; 164 | } 165 | 166 | void restore_task_context(task_t* task, task_regs_t* regs) { 167 | // copy the registers 168 | *regs = task->ucontext->regs; 169 | 170 | // activate the mmu entry 171 | mmu_activate(&task->mmu); 172 | } 173 | -------------------------------------------------------------------------------- /kernel/src/task/task.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include "task_regs.h" 8 | #include "arch/intrin.h" 9 | 10 | /** 11 | * Represents a pid, we are going to limit 12 | * to 255 because it is more than we are going 13 | * to be able to handle efficiently 14 | */ 15 | typedef int8_t pid_t; 16 | 17 | typedef enum task_status { 18 | /** 19 | * Means this thread was just allocated and has not 20 | * yet been initialized 21 | */ 22 | TASK_STATUS_IDLE = 0, 23 | 24 | /** 25 | * Means this thread is on a run queue. It is 26 | * not currently executing user code. 27 | */ 28 | TASK_STATUS_RUNNABLE = 1, 29 | 30 | /** 31 | * Means this thread may execute user code. 32 | */ 33 | TASK_STATUS_RUNNING = 2, 34 | 35 | /** 36 | * Means this thread is blocked in the runtime. 37 | * It is not executing user code. It is not on a run queue, 38 | * but should be recorded somewhere so it can be scheduled 39 | * when necessary. 40 | */ 41 | TASK_STATUS_WAITING = 3, 42 | 43 | /** 44 | * Means the thread stopped itself for a suspend 45 | * preemption. IT is like THREAD_STATUS_WAITING, but 46 | * nothing is yet responsible for readying it. some 47 | * suspend must CAS the status to THREAD_STATUS_WAITING 48 | * to take responsibility for readying this thread 49 | */ 50 | TASK_STATUS_PREEMPTED = 4, 51 | 52 | /** 53 | * Means this thread is currently unused. It may be 54 | * just exited, on a free list, or just being initialized. 55 | * It is not executing user code. 56 | */ 57 | TASK_STATUS_DEAD = 5, 58 | 59 | /** 60 | * Indicates someone wants to suspend this thread (probably the 61 | * garbage collector). 62 | */ 63 | TASK_SUSPEND = 0x1000, 64 | } task_status_t; 65 | 66 | /** 67 | * The task regs are used to save the full context of a 68 | * task, and are also used during exception handling 69 | * 70 | * NOTE: Do not change anything in this struct, we have assembly 71 | * code that hardcodes these offsets. 72 | */ 73 | typedef struct task_regs { 74 | uint32_t ar[64]; // 0 75 | uint32_t sar; // 256 76 | uint32_t lbeg; // 260 77 | uint32_t lend; // 264 78 | uint32_t lcount; // 268 79 | uint32_t pc; // 272 80 | ps_t ps; // 276 81 | uint32_t windowbase; // 280 82 | uint32_t windowstart; // 284 83 | uint16_t windowmask; // 288 84 | uint16_t windowsize; // 190 85 | // TODO: save scompare1 (used by atomic operations which userspace might do) 86 | } task_regs_t; 87 | STATIC_ASSERT(offsetof(task_regs_t, sar) == TASK_REGS_SAR); 88 | STATIC_ASSERT(offsetof(task_regs_t, lbeg) == TASK_REGS_LBEG); 89 | STATIC_ASSERT(offsetof(task_regs_t, lend) == TASK_REGS_LEND); 90 | STATIC_ASSERT(offsetof(task_regs_t, lcount) == TASK_REGS_LCOUNT); 91 | STATIC_ASSERT(offsetof(task_regs_t, pc) == TASK_REGS_PC); 92 | STATIC_ASSERT(offsetof(task_regs_t, ps) == TASK_REGS_PS); 93 | STATIC_ASSERT(offsetof(task_regs_t, windowbase) == TASK_REGS_WINDOWBASE); 94 | STATIC_ASSERT(offsetof(task_regs_t, windowstart) == TASK_REGS_WINDOWSTART); 95 | STATIC_ASSERT(offsetof(task_regs_t, windowmask) == TASK_REGS_WINDOWMASK); 96 | STATIC_ASSERT(sizeof(task_regs_t) == TASK_REGS_SIZE); 97 | 98 | void task_regs_dump(task_regs_t* regs); 99 | 100 | /** 101 | * The default stack size 102 | */ 103 | #define STACK_SIZE 4096 104 | 105 | /** 106 | * The task context is essentially the stack 107 | * and the ipc buffer that is given for the 108 | * user task, usermode can modify it as much 109 | * as it wishes, and it is always mapped at 110 | * the end of the user data segment 111 | */ 112 | typedef struct task_ucontext { 113 | // the stack for userspace 114 | char stack[STACK_SIZE]; 115 | 116 | // the task name 117 | char name[64]; 118 | 119 | // the registers of this thread, we don't care about storing them in this 120 | // page instead of the kernel heap because it is going to be saved on every 121 | // context switch anyways, and it might be a cool way to do exception handling 122 | task_regs_t regs; 123 | } PACKED task_ucontext_t; 124 | STATIC_ASSERT(sizeof(task_ucontext_t) <= USER_PAGE_SIZE); 125 | 126 | /** 127 | * The task struct, used to represent a single task 128 | */ 129 | typedef struct task { 130 | // the actual pid of the process 131 | pid_t pid; 132 | 133 | // the mmu context for this task, 134 | // this handles everything from pid 135 | // allocation to the peripheral access 136 | mmu_t mmu; 137 | 138 | // The current status of the task 139 | task_status_t status; 140 | 141 | // Link for the scheduler 142 | struct task* sched_link; 143 | 144 | // The user context of the thread, contains 145 | // the registers as well 146 | task_ucontext_t* ucontext; 147 | } task_t; 148 | 149 | task_t* create_task(void* entry_point, const char* fmt, ...); 150 | 151 | void release_task(task_t* task); 152 | 153 | #define SAFE_RELEASE_TASK(task) \ 154 | do { \ 155 | if (task != NULL) { \ 156 | release_task(task); \ 157 | task = NULL; \ 158 | } \ 159 | } while (0) 160 | 161 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 162 | // Task status 163 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 164 | 165 | /** 166 | * Get the status of a task atomically 167 | * 168 | * @param thread [IN] The target task 169 | */ 170 | task_status_t get_task_status(task_t* thread); 171 | 172 | /** 173 | * Compare and swap the thread state atomically 174 | * 175 | * @remark 176 | * This will suspend until the thread status is equals to old and only then try to 177 | * set it to new, if that fails it will continue to try until it has a success. 178 | * 179 | * @param thread [IN] The target thread 180 | * @param old [IN] The old status 181 | * @param new [IN] The new status 182 | */ 183 | void cas_task_state(task_t* task, task_status_t old, task_status_t new); 184 | 185 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 186 | // Task context 187 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 188 | 189 | void save_task_context(task_t* task, task_regs_t* regs); 190 | 191 | void restore_task_context(task_t* task, task_regs_t* regs); 192 | -------------------------------------------------------------------------------- /kernel/src/task/task_regs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define TASK_REGS_AR(x) ((x) * 4) 4 | #define TASK_REGS_AR_END 256 5 | #define TASK_REGS_SAR 256 6 | #define TASK_REGS_LBEG 260 7 | #define TASK_REGS_LEND 264 8 | #define TASK_REGS_LCOUNT 268 9 | #define TASK_REGS_PC 272 10 | #define TASK_REGS_PS 276 11 | #define TASK_REGS_WINDOWBASE 280 12 | #define TASK_REGS_WINDOWSTART 284 13 | #define TASK_REGS_WINDOWMASK 288 14 | #define TASK_REGS_WINDOWSIZE 290 15 | #define TASK_REGS_SIZE 292 16 | -------------------------------------------------------------------------------- /kernel/src/util/cpp_magic.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This header file contains a library of advanced C Pre-Processor (CPP) macros 3 | * which implement various useful functions, such as iteration, in the 4 | * pre-processor. 5 | * 6 | * Though the file name (quite validly) labels this as magic, there should be 7 | * enough documentation in the comments for a reader only casually familiar 8 | * with the CPP to be able to understand how everything works. 9 | * 10 | * The majority of the magic tricks used in this file are based on those 11 | * described by pfultz2 in his "Cloak" library: 12 | * 13 | * https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms 14 | * 15 | * Major differences are a greater level of detailed explanation in this 16 | * implementation and also a refusal to include any macros which require a O(N) 17 | * macro definitions to handle O(N) arguments (with the exception of DEFERn). 18 | */ 19 | 20 | #ifndef CPP_MAGIC_H 21 | #define CPP_MAGIC_H 22 | 23 | /** 24 | * Force the pre-processor to expand the macro a large number of times. Usage: 25 | * 26 | * EVAL(expression) 27 | * 28 | * This is useful when you have a macro which evaluates to a valid macro 29 | * expression which is not subsequently expanded in the same pass. A contrived, 30 | * but easy to understand, example of such a macro follows. Note that though 31 | * this example is contrived, this behaviour is abused to implement bounded 32 | * recursion in macros such as FOR. 33 | * 34 | * #define A(x) x+1 35 | * #define EMPTY 36 | * #define NOT_QUITE_RIGHT(x) A EMPTY (x) 37 | * NOT_QUITE_RIGHT(999) 38 | * 39 | * Here's what happens inside the C preprocessor: 40 | * 41 | * 1. It sees a macro "NOT_QUITE_RIGHT" and performs a single macro expansion 42 | * pass on its arguments. Since the argument is "999" and this isn't a macro, 43 | * this is a boring step resulting in no change. 44 | * 2. The NOT_QUITE_RIGHT macro is substituted for its definition giving "A 45 | * EMPTY() (x)". 46 | * 3. The expander moves from left-to-right trying to expand the macro: 47 | * The first token, A, cannot be expanded since there are no brackets 48 | * immediately following it. The second token EMPTY(), however, can be 49 | * expanded (recursively in this manner) and is replaced with "". 50 | * 4. Expansion continues from the start of the substituted test (which in this 51 | * case is just empty), and sees "(999)" but since no macro name is present, 52 | * nothing is done. This results in a final expansion of "A (999)". 53 | * 54 | * Unfortunately, this doesn't quite meet expectations since you may expect that 55 | * "A (999)" would have been expanded into "999+1". Unfortunately this requires 56 | * a second expansion pass but luckily we can force the macro processor to make 57 | * more passes by abusing the first step of macro expansion: the preprocessor 58 | * expands arguments in their own pass. If we define a macro which does nothing 59 | * except produce its arguments e.g.: 60 | * 61 | * #define PASS_THROUGH(...) __VA_ARGS__ 62 | * 63 | * We can now do "PASS_THROUGH(NOT_QUITE_RIGHT(999))" causing "NOT_QUITE_RIGHT" to be 64 | * expanded to "A (999)", as described above, when the arguments are expanded. 65 | * Now when the body of PASS_THROUGH is expanded, "A (999)" gets expanded to 66 | * "999+1". 67 | * 68 | * The EVAL defined below is essentially equivalent to a large nesting of 69 | * "PASS_THROUGH(PASS_THROUGH(PASS_THROUGH(..." which results in the 70 | * preprocessor making a large number of expansion passes over the given 71 | * expression. 72 | */ 73 | #define EVAL(...) EVAL1024(__VA_ARGS__) 74 | #define EVAL1024(...) EVAL512(EVAL512(__VA_ARGS__)) 75 | #define EVAL512(...) EVAL256(EVAL256(__VA_ARGS__)) 76 | #define EVAL256(...) EVAL128(EVAL128(__VA_ARGS__)) 77 | #define EVAL128(...) EVAL64(EVAL64(__VA_ARGS__)) 78 | #define EVAL64(...) EVAL32(EVAL32(__VA_ARGS__)) 79 | #define EVAL32(...) EVAL16(EVAL16(__VA_ARGS__)) 80 | #define EVAL16(...) EVAL8(EVAL8(__VA_ARGS__)) 81 | #define EVAL8(...) EVAL4(EVAL4(__VA_ARGS__)) 82 | #define EVAL4(...) EVAL2(EVAL2(__VA_ARGS__)) 83 | #define EVAL2(...) EVAL1(EVAL1(__VA_ARGS__)) 84 | #define EVAL1(...) __VA_ARGS__ 85 | 86 | 87 | /** 88 | * Macros which expand to common values 89 | */ 90 | #define PASS(...) __VA_ARGS__ 91 | #define EMPTY() 92 | #define COMMA() , 93 | #define PLUS() + 94 | #define ZERO() 0 95 | #define ONE() 1 96 | 97 | /** 98 | * Causes a function-style macro to require an additional pass to be expanded. 99 | * 100 | * This is useful, for example, when trying to implement recursion since the 101 | * recursive step must not be expanded in a single pass as the pre-processor 102 | * will catch it and prevent it. 103 | * 104 | * Usage: 105 | * 106 | * DEFER1(IN_NEXT_PASS)(args, to, the, macro) 107 | * 108 | * How it works: 109 | * 110 | * 1. When DEFER1 is expanded, first its arguments are expanded which are 111 | * simply IN_NEXT_PASS. Since this is a function-style macro and it has no 112 | * arguments, nothing will happen. 113 | * 2. The body of DEFER1 will now be expanded resulting in EMPTY() being 114 | * deleted. This results in "IN_NEXT_PASS (args, to, the macro)". Note that 115 | * since the macro expander has already passed IN_NEXT_PASS by the time it 116 | * expands EMPTY() and so it won't spot that the brackets which remain can be 117 | * applied to IN_NEXT_PASS. 118 | * 3. At this point the macro expansion completes. If one more pass is made, 119 | * IN_NEXT_PASS(args, to, the, macro) will be expanded as desired. 120 | */ 121 | #define DEFER1(id) id EMPTY() 122 | 123 | /** 124 | * As with DEFER1 except here n additional passes are required for DEFERn. 125 | * 126 | * The mechanism is analogous. 127 | * 128 | * Note that there doesn't appear to be a way of combining DEFERn macros in 129 | * order to achieve exponentially increasing defers e.g. as is done by EVAL. 130 | */ 131 | #define DEFER2(id) id EMPTY EMPTY()() 132 | #define DEFER3(id) id EMPTY EMPTY EMPTY()()() 133 | #define DEFER4(id) id EMPTY EMPTY EMPTY EMPTY()()()() 134 | #define DEFER5(id) id EMPTY EMPTY EMPTY EMPTY EMPTY()()()()() 135 | #define DEFER6(id) id EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY()()()()()() 136 | #define DEFER7(id) id EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY()()()()()()() 137 | #define DEFER8(id) id EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY EMPTY()()()()()()()() 138 | 139 | 140 | /** 141 | * Indirection around the standard ## concatenation operator. This simply 142 | * ensures that the arguments are expanded (once) before concatenation. 143 | */ 144 | #define CAT(a, ...) a ## __VA_ARGS__ 145 | #define CAT3(a, b, ...) a ## b ## __VA_ARGS__ 146 | 147 | 148 | /** 149 | * Get the first argument and ignore the rest. 150 | */ 151 | #define FIRST(a, ...) a 152 | 153 | /** 154 | * Get the second argument and ignore the rest. 155 | */ 156 | #define SECOND(a, b, ...) b 157 | 158 | /** 159 | * Expects a single input (not containing commas). Returns 1 if the input is 160 | * PROBE() and otherwise returns 0. 161 | * 162 | * This can be useful as the basis of a NOT function. 163 | * 164 | * This macro abuses the fact that PROBE() contains a comma while other valid 165 | * inputs must not. 166 | */ 167 | #define IS_PROBE(...) SECOND(__VA_ARGS__, 0) 168 | #define PROBE() ~, 1 169 | 170 | 171 | /** 172 | * Logical negation. 0 is defined as false and everything else as true. 173 | * 174 | * When 0, _NOT_0 will be found which evaluates to the PROBE. When 1 (or any other 175 | * value) is given, an appropriately named macro won't be found and the 176 | * concatenated string will be produced. IS_PROBE then simply checks to see if 177 | * the PROBE was returned, cleanly converting the argument into a 1 or 0. 178 | */ 179 | #define NOT(x) IS_PROBE(CAT(_NOT_, x)) 180 | #define _NOT_0 PROBE() 181 | 182 | /** 183 | * Macro version of C's famous "cast to bool" operator (i.e. !!) which takes 184 | * anything and casts it to 0 if it is 0 and 1 otherwise. 185 | */ 186 | #define BOOL(x) NOT(NOT(x)) 187 | 188 | /** 189 | * Logical OR. Simply performs a lookup. 190 | */ 191 | #define OR(a,b) CAT3(_OR_, a, b) 192 | #define _OR_00 0 193 | #define _OR_01 1 194 | #define _OR_10 1 195 | #define _OR_11 1 196 | 197 | /** 198 | * Logical AND. Simply performs a lookup. 199 | */ 200 | #define AND(a,b) CAT3(_AND_, a, b) 201 | #define _AND_00 0 202 | #define _AND_01 0 203 | #define _AND_10 0 204 | #define _AND_11 1 205 | 206 | 207 | /** 208 | * Macro if statement. Usage: 209 | * 210 | * IF(c)(expansion when true) 211 | * 212 | * Here's how: 213 | * 214 | * 1. The preprocessor expands the arguments to _IF casting the condition to '0' 215 | * or '1'. 216 | * 2. The casted condition is concatencated with _IF_ giving _IF_0 or _IF_1. 217 | * 3. The _IF_0 and _IF_1 macros either returns the argument or doesn't (e.g. 218 | * they implement the "choice selection" part of the macro). 219 | * 4. Note that the "true" clause is in the extra set of brackets; thus these 220 | * become the arguments to _IF_0 or _IF_1 and thus a selection is made! 221 | */ 222 | #define IF(c) _IF(BOOL(c)) 223 | #define _IF(c) CAT(_IF_,c) 224 | #define _IF_0(...) 225 | #define _IF_1(...) __VA_ARGS__ 226 | 227 | /** 228 | * Macro if/else statement. Usage: 229 | * 230 | * IF_ELSE(c)( \ 231 | * expansion when true, \ 232 | * expansion when false \ 233 | * ) 234 | * 235 | * The mechanism is analogous to IF. 236 | */ 237 | #define IF_ELSE(c) _IF_ELSE(BOOL(c)) 238 | #define _IF_ELSE(c) CAT(_IF_ELSE_,c) 239 | #define _IF_ELSE_0(t,f) f 240 | #define _IF_ELSE_1(t,f) t 241 | 242 | 243 | /** 244 | * Macro which checks if it has any arguments. Returns '0' if there are no 245 | * arguments, '1' otherwise. 246 | * 247 | * Limitation: HAS_ARGS(,1,2,3) returns 0 -- this check essentially only checks 248 | * that the first argument exists. 249 | * 250 | * This macro works as follows: 251 | * 252 | * 1. _END_OF_ARGUMENTS_ is concatenated with the first argument. 253 | * 2. If the first argument is not present then only "_END_OF_ARGUMENTS_" will 254 | * remain, otherwise "_END_OF_ARGUMENTS something_here" will remain. This 255 | * remaining argument can start with parentheses. 256 | * 3. In the former case, the _END_OF_ARGUMENTS_(0) macro expands to a 257 | * 0 when it is expanded. In the latter, a non-zero result remains. If the 258 | * first argument started with parentheses these will mostly not contain 259 | * only a single 0, but e.g a C cast or some arithmetic operation that will 260 | * cause the BOOL in _END_OF_ARGUMENTS_ to be one. 261 | * 4. BOOL is used to force non-zero results into 1 giving the clean 0 or 1 262 | * output required. 263 | */ 264 | #define HAS_ARGS(...) BOOL(FIRST(_END_OF_ARGUMENTS_ __VA_ARGS__)(0)) 265 | #define _END_OF_ARGUMENTS_(...) BOOL(FIRST(__VA_ARGS__)) 266 | 267 | 268 | /** 269 | * Macro map/list comprehension. Usage: 270 | * 271 | * MAP(op, sep, ...) 272 | * 273 | * Produces a 'sep()'-separated list of the result of op(arg) for each arg. 274 | * 275 | * Example Usage: 276 | * 277 | * #define MAKE_HAPPY(x) happy_##x 278 | * #define COMMA() , 279 | * MAP(MAKE_HAPPY, COMMA, 1,2,3) 280 | * 281 | * Which expands to: 282 | * 283 | * happy_1 , happy_2 , happy_3 284 | * 285 | * How it works: 286 | * 287 | * 1. The MAP macro simply maps the inner MAP_INNER function in an EVAL which 288 | * forces it to be expanded a large number of times, thus enabling many steps 289 | * of iteration (see step 6). 290 | * 2. The MAP_INNER macro is substituted for its body. 291 | * 3. In the body, op(cur_val) is substituted giving the value for this 292 | * iteration. 293 | * 4. The IF macro expands according to whether further iterations are required. 294 | * This expansion either produces _IF_0 or _IF_1. 295 | * 5. Since the IF is followed by a set of brackets containing the "if true" 296 | * clause, these become the argument to _IF_0 or _IF_1. At this point, the 297 | * macro in the brackets will be expanded giving the separator followed by 298 | * _MAP_INNER EMPTY()()(op, sep, __VA_ARGS__). 299 | * 5. If the IF was not taken, the above will simply be discarded and everything 300 | * stops. If the IF is taken, The expression is then processed a second time 301 | * yielding "_MAP_INNER()(op, sep, __VA_ARGS__)". Note that this call looks 302 | * very similar to the essentially the same as the original call except the 303 | * first argument has been dropped. 304 | * 6. At this point expansion of MAP_INNER will terminate. However, since we can 305 | * force more rounds of expansion using EVAL1. In the argument-expansion pass 306 | * of the EVAL1, _MAP_INNER() is expanded to MAP_INNER which is then expanded 307 | * using the arguments which follow it as in step 2-5. This is followed by a 308 | * second expansion pass as the substitution of EVAL1() is expanded executing 309 | * 2-5 a second time. This results in up to two iterations occurring. Using 310 | * many nested EVAL1 macros, i.e. the very-deeply-nested EVAL macro, will in 311 | * this manner produce further iterations, hence the outer MAP macro doing 312 | * this for us. 313 | * 314 | * Important tricks used: 315 | * 316 | * * If we directly produce "MAP_INNER" in an expansion of MAP_INNER, a special 317 | * case in the preprocessor will prevent it being expanded in the future, even 318 | * if we EVAL. As a result, the MAP_INNER macro carefully only expands to 319 | * something containing "_MAP_INNER()" which requires a further expansion step 320 | * to invoke MAP_INNER and thus implementing the recursion. 321 | * * To prevent _MAP_INNER being expanded within the macro we must first defer its 322 | * expansion during its initial pass as an argument to _IF_0 or _IF_1. We must 323 | * then defer its expansion a second time as part of the body of the _IF_0. As 324 | * a result hence the DEFER2. 325 | * * _MAP_INNER seemingly gets away with producing itself because it actually only 326 | * produces MAP_INNER. It just happens that when _MAP_INNER() is expanded in 327 | * this case it is followed by some arguments which get consumed by MAP_INNER 328 | * and produce a _MAP_INNER. As such, the macro expander never marks 329 | * _MAP_INNER as expanding to itself and thus it will still be expanded in 330 | * future productions of itself. 331 | */ 332 | #define MAP(...) \ 333 | IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_INNER(__VA_ARGS__))) 334 | #define MAP_INNER(op,sep,cur_val, ...) \ 335 | op(cur_val) \ 336 | IF(HAS_ARGS(__VA_ARGS__))( \ 337 | sep() DEFER2(_MAP_INNER)()(op, sep, ##__VA_ARGS__) \ 338 | ) 339 | #define _MAP_INNER() MAP_INNER 340 | 341 | 342 | /** 343 | * This is a variant of the MAP macro which also includes as an argument to the 344 | * operation a valid C variable name which is different for each iteration. 345 | * 346 | * Usage: 347 | * MAP_WITH_ID(op, sep, ...) 348 | * 349 | * Where op is a macro op(val, id) which takes a list value and an ID. This ID 350 | * will simply be a unary number using the digit "I", that is, I, II, III, IIII, 351 | * and so on. 352 | * 353 | * Example: 354 | * 355 | * #define MAKE_STATIC_VAR(type, name) static type name; 356 | * MAP_WITH_ID(MAKE_STATIC_VAR, EMPTY, int, int, int, bool, char) 357 | * 358 | * Which expands to: 359 | * 360 | * static int I; static int II; static int III; static bool IIII; static char IIIII; 361 | * 362 | * The mechanism is analogous to the MAP macro. 363 | */ 364 | #define MAP_WITH_ID(op,sep,...) \ 365 | IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_WITH_ID_INNER(op,sep,I, ##__VA_ARGS__))) 366 | #define MAP_WITH_ID_INNER(op,sep,id,cur_val, ...) \ 367 | op(cur_val,id) \ 368 | IF(HAS_ARGS(__VA_ARGS__))( \ 369 | sep() DEFER2(_MAP_WITH_ID_INNER)()(op, sep, CAT(id,I), ##__VA_ARGS__) \ 370 | ) 371 | #define _MAP_WITH_ID_INNER() MAP_WITH_ID_INNER 372 | 373 | 374 | /** 375 | * This is a variant of the MAP macro which iterates over pairs rather than 376 | * singletons. 377 | * 378 | * Usage: 379 | * MAP_PAIRS(op, sep, ...) 380 | * 381 | * Where op is a macro op(val_1, val_2) which takes two list values. 382 | * 383 | * Example: 384 | * 385 | * #define MAKE_STATIC_VAR(type, name) static type name; 386 | * MAP_PAIRS(MAKE_STATIC_VAR, EMPTY, char, my_char, int, my_int) 387 | * 388 | * Which expands to: 389 | * 390 | * static char my_char; static int my_int; 391 | * 392 | * The mechanism is analogous to the MAP macro. 393 | */ 394 | #define MAP_PAIRS(op,sep,...) \ 395 | IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_PAIRS_INNER(op,sep,__VA_ARGS__))) 396 | #define MAP_PAIRS_INNER(op,sep,cur_val_1, cur_val_2, ...) \ 397 | op(cur_val_1,cur_val_2) \ 398 | IF(HAS_ARGS(__VA_ARGS__))( \ 399 | sep() DEFER2(_MAP_PAIRS_INNER)()(op, sep, __VA_ARGS__) \ 400 | ) 401 | #define _MAP_PAIRS_INNER() MAP_PAIRS_INNER 402 | 403 | /** 404 | * This is a variant of the MAP macro which iterates over a two-element sliding 405 | * window. 406 | * 407 | * Usage: 408 | * MAP_SLIDE(op, last_op, sep, ...) 409 | * 410 | * Where op is a macro op(val_1, val_2) which takes the two list values 411 | * currently in the window. last_op is a macro taking a single value which is 412 | * called for the last argument. 413 | * 414 | * Example: 415 | * 416 | * #define SIMON_SAYS_OP(simon, next) IF(NOT(simon()))(next) 417 | * #define SIMON_SAYS_LAST_OP(val) last_but_not_least_##val 418 | * #define SIMON_SAYS() 0 419 | * 420 | * MAP_SLIDE(SIMON_SAYS_OP, SIMON_SAYS_LAST_OP, EMPTY, wiggle, SIMON_SAYS, dance, move, SIMON_SAYS, boogie, stop) 421 | * 422 | * Which expands to: 423 | * 424 | * dance boogie last_but_not_least_stop 425 | * 426 | * The mechanism is analogous to the MAP macro. 427 | */ 428 | #define MAP_SLIDE(op,last_op,sep,...) \ 429 | IF(HAS_ARGS(__VA_ARGS__))(EVAL(MAP_SLIDE_INNER(op,last_op,sep,__VA_ARGS__))) 430 | #define MAP_SLIDE_INNER(op,last_op,sep,cur_val, ...) \ 431 | IF(HAS_ARGS(__VA_ARGS__))(op(cur_val,FIRST(__VA_ARGS__))) \ 432 | IF(NOT(HAS_ARGS(__VA_ARGS__)))(last_op(cur_val)) \ 433 | IF(HAS_ARGS(__VA_ARGS__))( \ 434 | sep() DEFER2(_MAP_SLIDE_INNER)()(op, last_op, sep, __VA_ARGS__) \ 435 | ) 436 | #define _MAP_SLIDE_INNER() MAP_SLIDE_INNER 437 | 438 | 439 | /** 440 | * Strip any excess commas from a set of arguments. 441 | */ 442 | #define REMOVE_TRAILING_COMMAS(...) \ 443 | MAP(PASS, COMMA, __VA_ARGS__) 444 | 445 | 446 | #endif -------------------------------------------------------------------------------- /kernel/src/util/defs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) 4 | 5 | #define PACKED __attribute__((packed)) 6 | #define MMIO __attribute__((packed, aligned(4))) 7 | #define STATIC_ASSERT(x) _Static_assert(x, #x) 8 | 9 | #define _STR(x) #x 10 | #define STR(x) _STR(x) 11 | 12 | typedef char symbol_t[]; 13 | 14 | #define ALIGN_UP(x, align) \ 15 | ({ \ 16 | size_t _x = (size_t)(x); \ 17 | size_t _align = (size_t)(align); \ 18 | (__typeof(x))((_x + (_align - 1)) & ~(_align - 1)); \ 19 | }) 20 | 21 | #define ALIGN_DOWN(x, align) \ 22 | ({ \ 23 | size_t _x = (size_t)(x); \ 24 | size_t _align = (size_t)(align); \ 25 | (__typeof(x))(_x - (_x & (_align - 1))); \ 26 | }) 27 | 28 | #define MINU(a, b) \ 29 | ({ \ 30 | uint32_t __a = (a); \ 31 | uint32_t __b = (b); \ 32 | uint32_t __res; \ 33 | asm ( \ 34 | "MINU %0, %1, %2" \ 35 | : "=r"(__res) \ 36 | : "r"(__a), "r"(__b) \ 37 | ); \ 38 | __res; \ 39 | }) 40 | 41 | #define FOR_EACH_BIT(num, it) \ 42 | if (num != 0) \ 43 | for( \ 44 | int it = __builtin_ffs(num); \ 45 | num; \ 46 | num &= ~it, \ 47 | it = __builtin_ffs(num) \ 48 | ) 49 | 50 | #define BIT0 0x00000001 51 | #define BIT1 0x00000002 52 | #define BIT2 0x00000004 53 | #define BIT3 0x00000008 54 | #define BIT4 0x00000010 55 | #define BIT5 0x00000020 56 | #define BIT6 0x00000040 57 | #define BIT7 0x00000080 58 | #define BIT8 0x00000100 59 | #define BIT9 0x00000200 60 | #define BIT10 0x00000400 61 | #define BIT11 0x00000800 62 | #define BIT12 0x00001000 63 | #define BIT13 0x00002000 64 | #define BIT14 0x00004000 65 | #define BIT15 0x00008000 66 | #define BIT16 0x00010000 67 | #define BIT17 0x00020000 68 | #define BIT18 0x00040000 69 | #define BIT19 0x00080000 70 | #define BIT20 0x00100000 71 | #define BIT21 0x00200000 72 | #define BIT22 0x00400000 73 | #define BIT23 0x00800000 74 | #define BIT24 0x01000000 75 | #define BIT25 0x02000000 76 | #define BIT26 0x04000000 77 | #define BIT27 0x08000000 78 | #define BIT28 0x10000000 79 | #define BIT29 0x20000000 80 | #define BIT30 0x40000000 81 | #define BIT31 0x80000000 82 | #define BIT32 0x0000000100000000ULL 83 | #define BIT33 0x0000000200000000ULL 84 | #define BIT34 0x0000000400000000ULL 85 | #define BIT35 0x0000000800000000ULL 86 | #define BIT36 0x0000001000000000ULL 87 | #define BIT37 0x0000002000000000ULL 88 | #define BIT38 0x0000004000000000ULL 89 | #define BIT39 0x0000008000000000ULL 90 | #define BIT40 0x0000010000000000ULL 91 | #define BIT41 0x0000020000000000ULL 92 | #define BIT42 0x0000040000000000ULL 93 | #define BIT43 0x0000080000000000ULL 94 | #define BIT44 0x0000100000000000ULL 95 | #define BIT45 0x0000200000000000ULL 96 | #define BIT46 0x0000400000000000ULL 97 | #define BIT47 0x0000800000000000ULL 98 | #define BIT48 0x0001000000000000ULL 99 | #define BIT49 0x0002000000000000ULL 100 | #define BIT50 0x0004000000000000ULL 101 | #define BIT51 0x0008000000000000ULL 102 | #define BIT52 0x0010000000000000ULL 103 | #define BIT53 0x0020000000000000ULL 104 | #define BIT54 0x0040000000000000ULL 105 | #define BIT55 0x0080000000000000ULL 106 | #define BIT56 0x0100000000000000ULL 107 | #define BIT57 0x0200000000000000ULL 108 | #define BIT58 0x0400000000000000ULL 109 | #define BIT59 0x0800000000000000ULL 110 | #define BIT60 0x1000000000000000ULL 111 | #define BIT61 0x2000000000000000ULL 112 | #define BIT62 0x4000000000000000ULL 113 | #define BIT63 0x8000000000000000ULL 114 | 115 | #define SIZE_1KB 0x00000400 116 | #define SIZE_2KB 0x00000800 117 | #define SIZE_4KB 0x00001000 118 | #define SIZE_8KB 0x00002000 119 | #define SIZE_16KB 0x00004000 120 | #define SIZE_32KB 0x00008000 121 | #define SIZE_64KB 0x00010000 122 | #define SIZE_128KB 0x00020000 123 | #define SIZE_256KB 0x00040000 124 | #define SIZE_512KB 0x00080000 125 | #define SIZE_1MB 0x00100000 126 | #define SIZE_2MB 0x00200000 127 | #define SIZE_4MB 0x00400000 128 | #define SIZE_8MB 0x00800000 129 | #define SIZE_16MB 0x01000000 130 | #define SIZE_32MB 0x02000000 131 | #define SIZE_64MB 0x04000000 132 | #define SIZE_128MB 0x08000000 133 | #define SIZE_256MB 0x10000000 134 | #define SIZE_512MB 0x20000000 135 | #define SIZE_1GB 0x40000000 136 | #define SIZE_2GB 0x80000000 137 | #define SIZE_4GB 0x0000000100000000ULL 138 | #define SIZE_8GB 0x0000000200000000ULL 139 | #define SIZE_16GB 0x0000000400000000ULL 140 | #define SIZE_32GB 0x0000000800000000ULL 141 | #define SIZE_64GB 0x0000001000000000ULL 142 | #define SIZE_128GB 0x0000002000000000ULL 143 | #define SIZE_256GB 0x0000004000000000ULL 144 | #define SIZE_512GB 0x0000008000000000ULL 145 | #define SIZE_1TB 0x0000010000000000ULL 146 | #define SIZE_2TB 0x0000020000000000ULL 147 | #define SIZE_4TB 0x0000040000000000ULL 148 | #define SIZE_8TB 0x0000080000000000ULL 149 | #define SIZE_16TB 0x0000100000000000ULL 150 | #define SIZE_32TB 0x0000200000000000ULL 151 | #define SIZE_64TB 0x0000400000000000ULL 152 | #define SIZE_128TB 0x0000800000000000ULL 153 | #define SIZE_256TB 0x0001000000000000ULL 154 | #define SIZE_512TB 0x0002000000000000ULL 155 | #define SIZE_1PB 0x0004000000000000ULL 156 | #define SIZE_2PB 0x0008000000000000ULL 157 | #define SIZE_4PB 0x0010000000000000ULL 158 | #define SIZE_8PB 0x0020000000000000ULL 159 | #define SIZE_16PB 0x0040000000000000ULL 160 | #define SIZE_32PB 0x0080000000000000ULL 161 | #define SIZE_64PB 0x0100000000000000ULL 162 | #define SIZE_128PB 0x0200000000000000ULL 163 | #define SIZE_256PB 0x0400000000000000ULL 164 | #define SIZE_512PB 0x0800000000000000ULL 165 | #define SIZE_1EB 0x1000000000000000ULL 166 | #define SIZE_2EB 0x2000000000000000ULL 167 | #define SIZE_4EB 0x4000000000000000ULL 168 | #define SIZE_8EB 0x8000000000000000ULL 169 | 170 | #define BASE_1KB 0x00000400 171 | #define BASE_2KB 0x00000800 172 | #define BASE_4KB 0x00001000 173 | #define BASE_8KB 0x00002000 174 | #define BASE_16KB 0x00004000 175 | #define BASE_32KB 0x00008000 176 | #define BASE_64KB 0x00010000 177 | #define BASE_128KB 0x00020000 178 | #define BASE_256KB 0x00040000 179 | #define BASE_512KB 0x00080000 180 | #define BASE_1MB 0x00100000 181 | #define BASE_2MB 0x00200000 182 | #define BASE_4MB 0x00400000 183 | #define BASE_8MB 0x00800000 184 | #define BASE_16MB 0x01000000 185 | #define BASE_32MB 0x02000000 186 | #define BASE_64MB 0x04000000 187 | #define BASE_128MB 0x08000000 188 | #define BASE_256MB 0x10000000 189 | #define BASE_512MB 0x20000000 190 | #define BASE_1GB 0x40000000 191 | #define BASE_2GB 0x80000000 192 | #define BASE_4GB 0x0000000100000000ULL 193 | #define BASE_8GB 0x0000000200000000ULL 194 | #define BASE_16GB 0x0000000400000000ULL 195 | #define BASE_32GB 0x0000000800000000ULL 196 | #define BASE_64GB 0x0000001000000000ULL 197 | #define BASE_128GB 0x0000002000000000ULL 198 | #define BASE_256GB 0x0000004000000000ULL 199 | #define BASE_512GB 0x0000008000000000ULL 200 | #define BASE_1TB 0x0000010000000000ULL 201 | #define BASE_2TB 0x0000020000000000ULL 202 | #define BASE_4TB 0x0000040000000000ULL 203 | #define BASE_8TB 0x0000080000000000ULL 204 | #define BASE_16TB 0x0000100000000000ULL 205 | #define BASE_32TB 0x0000200000000000ULL 206 | #define BASE_64TB 0x0000400000000000ULL 207 | #define BASE_128TB 0x0000800000000000ULL 208 | #define BASE_256TB 0x0001000000000000ULL 209 | #define BASE_512TB 0x0002000000000000ULL 210 | #define BASE_1PB 0x0004000000000000ULL 211 | #define BASE_2PB 0x0008000000000000ULL 212 | #define BASE_4PB 0x0010000000000000ULL 213 | #define BASE_8PB 0x0020000000000000ULL 214 | #define BASE_16PB 0x0040000000000000ULL 215 | #define BASE_32PB 0x0080000000000000ULL 216 | #define BASE_64PB 0x0100000000000000ULL 217 | #define BASE_128PB 0x0200000000000000ULL 218 | #define BASE_256PB 0x0400000000000000ULL 219 | #define BASE_512PB 0x0800000000000000ULL 220 | #define BASE_1EB 0x1000000000000000ULL 221 | #define BASE_2EB 0x2000000000000000ULL 222 | #define BASE_4EB 0x4000000000000000ULL 223 | #define BASE_8EB 0x8000000000000000ULL -------------------------------------------------------------------------------- /kernel/src/util/except.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cpp_magic.h" 4 | #include "trace.h" 5 | 6 | typedef enum err { 7 | /** 8 | * No error 9 | */ 10 | NO_ERROR, 11 | 12 | /** 13 | * Generic unknown error 14 | */ 15 | ERROR_CHECK_FAILED, 16 | 17 | /** 18 | * Could not allocate the requested resource 19 | */ 20 | ERROR_OUT_OF_RESOURCES, 21 | 22 | /** 23 | * The given pointer was invalid 24 | */ 25 | ERROR_INVALID_PTR, 26 | 27 | /** 28 | * The given syscall was invalid 29 | */ 30 | ERROR_INVALID_SYSCALL, 31 | } err_t; 32 | 33 | #define IS_ERROR(x) ((x) != 0) 34 | 35 | //---------------------------------------------------------------------------------------------------------------------- 36 | // A check that fails if the expression returns false 37 | //---------------------------------------------------------------------------------------------------------------------- 38 | 39 | #define CHECK_ERROR_LABEL(check, error, label, ...) \ 40 | do { \ 41 | if (!(check)) { \ 42 | err = error; \ 43 | IF(HAS_ARGS(__VA_ARGS__))(ERROR(__VA_ARGS__)); \ 44 | ERROR("Check failed with error %d in function %s (%s:%d)", err, __FUNCTION__, __FILE__, __LINE__); \ 45 | goto label; \ 46 | } \ 47 | } while(0) 48 | 49 | #define CHECK_ERROR(check, error, ...) CHECK_ERROR_LABEL(check, error, cleanup, ## __VA_ARGS__) 50 | #define CHECK_LABEL(check, label, ...) CHECK_ERROR_LABEL(check, ERROR_CHECK_FAILED, label, ## __VA_ARGS__) 51 | #define CHECK(check, ...) CHECK_ERROR_LABEL(check, ERROR_CHECK_FAILED, cleanup, ## __VA_ARGS__) 52 | 53 | #define DEBUG_CHECK_ERROR(check, error, ...) CHECK_ERROR_LABEL(check, error, cleanup, ## __VA_ARGS__) 54 | #define DEBUG_CHECK_LABEL(check, label, ...) CHECK_ERROR_LABEL(check, ERROR_CHECK_FAILED, label, ## __VA_ARGS__) 55 | #define DEBUG_CHECK(check, ...) CHECK_ERROR_LABEL(check, ERROR_CHECK_FAILED, cleanup, ## __VA_ARGS__) 56 | 57 | //---------------------------------------------------------------------------------------------------------------------- 58 | // A check that fails without a condition 59 | //---------------------------------------------------------------------------------------------------------------------- 60 | 61 | #define CHECK_FAIL(...) CHECK_ERROR_LABEL(0, ERROR_CHECK_FAILED, cleanup, ## __VA_ARGS__) 62 | #define CHECK_FAIL_ERROR(error, ...) CHECK_ERROR_LABEL(0, error, cleanup, ## __VA_ARGS__) 63 | #define CHECK_FAIL_LABEL(label, ...) CHECK_ERROR_LABEL(0, ERROR_CHECK_FAILED, label, ## __VA_ARGS__) 64 | #define CHECK_FAIL_ERROR_LABEL(error, label, ...) CHECK_ERROR_LABEL(0, error, label, ## __VA_ARGS__) 65 | 66 | //---------------------------------------------------------------------------------------------------------------------- 67 | // A check that fails if an error was returned, used around functions returning an error 68 | //---------------------------------------------------------------------------------------------------------------------- 69 | 70 | #define CHECK_AND_RETHROW_LABEL(error, label) \ 71 | do { \ 72 | err = error; \ 73 | if (IS_ERROR(err)) { \ 74 | ERROR("\trethrown at %s (%s:%d)", __FUNCTION__, __FILE__, __LINE__); \ 75 | goto label; \ 76 | } \ 77 | } while(0) 78 | 79 | #define CHECK_AND_RETHROW(error) CHECK_AND_RETHROW_LABEL(error, cleanup) 80 | -------------------------------------------------------------------------------- /kernel/src/util/printf.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // \author (c) Marco Paland (info@paland.com) 3 | // 2014-2019, PALANDesign Hannover, Germany 4 | // 5 | // \license The MIT License (MIT) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | // \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on 26 | // embedded systems with a very limited resources. 27 | // Use this instead of bloated standard/newlib printf. 28 | // These routines are thread safe and reentrant. 29 | // 30 | /////////////////////////////////////////////////////////////////////////////// 31 | 32 | #ifndef _PRINTF_H_ 33 | #define _PRINTF_H_ 34 | 35 | #include 36 | #include 37 | 38 | 39 | #ifdef __cplusplus 40 | extern "C" { 41 | #endif 42 | 43 | 44 | /** 45 | * Output a character to a custom device like UART, used by the printf() function 46 | * This function is declared here only. You have to write your custom implementation somewhere 47 | * \param character Character to output 48 | */ 49 | void _putchar(char character); 50 | 51 | 52 | /** 53 | * Tiny printf implementation 54 | * You have to implement _putchar if you use printf() 55 | * To avoid conflicts with the regular printf() API it is overridden by macro defines 56 | * and internal underscore-appended functions like printf_() are used 57 | * \param format A string that specifies the format of the output 58 | * \return The number of characters that are written into the array, not counting the terminating null character 59 | */ 60 | #define printf printf_ 61 | int printf_(const char* format, ...); 62 | 63 | 64 | /** 65 | * Tiny sprintf implementation 66 | * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! 67 | * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! 68 | * \param format A string that specifies the format of the output 69 | * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character 70 | */ 71 | #define sprintf sprintf_ 72 | int sprintf_(char* buffer, const char* format, ...); 73 | 74 | 75 | /** 76 | * Tiny snprintf/vsnprintf implementation 77 | * \param buffer A pointer to the buffer where to store the formatted string 78 | * \param count The maximum number of characters to store in the buffer, including a terminating null character 79 | * \param format A string that specifies the format of the output 80 | * \param va A value identifying a variable arguments list 81 | * \return The number of characters that COULD have been written into the buffer, not counting the terminating 82 | * null character. A value equal or larger than count indicates truncation. Only when the returned value 83 | * is non-negative and less than count, the string has been completely written. 84 | */ 85 | #define snprintf snprintf_ 86 | #define vsnprintf vsnprintf_ 87 | int snprintf_(char* buffer, size_t count, const char* format, ...); 88 | int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); 89 | 90 | 91 | /** 92 | * Tiny vprintf implementation 93 | * \param format A string that specifies the format of the output 94 | * \param va A value identifying a variable arguments list 95 | * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character 96 | */ 97 | #define vprintf vprintf_ 98 | int vprintf_(const char* format, va_list va); 99 | 100 | 101 | /** 102 | * printf with output function 103 | * You may use this as dynamic alternative to printf() with its fixed _putchar() output 104 | * \param out An output function which takes one character and an argument pointer 105 | * \param arg An argument pointer for user data passed to output function 106 | * \param format A string that specifies the format of the output 107 | * \return The number of characters that are sent to the output function, not counting the terminating null character 108 | */ 109 | int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); 110 | int vfctprintf(void (*out)(char character, void* arg), void* arg, const char* format, va_list va); 111 | 112 | 113 | #ifdef __cplusplus 114 | } 115 | #endif 116 | 117 | 118 | #endif // _PRINTF_H_ -------------------------------------------------------------------------------- /kernel/src/util/string.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void* xthal_memcpy(void *dst, const void *src, size_t len); 6 | 7 | #define memcpy __builtin_memcpy 8 | #define memset __builtin_memset 9 | -------------------------------------------------------------------------------- /kernel/src/util/trace.c: -------------------------------------------------------------------------------- 1 | #include "trace.h" 2 | 3 | #include 4 | #include 5 | 6 | void _putchar(char character) { 7 | uart_write_char(character); 8 | } 9 | 10 | void trace_hex(const void* _data, size_t size) { 11 | const uint8_t* data = _data; 12 | char ascii[17] = { 0 }; 13 | 14 | printf("[*] "); 15 | for (int i = 0; i < size; i++) { 16 | printf("%02x ", data[i]); 17 | 18 | if (data[i] >= ' ' && data[i] <= '~') { 19 | ascii[i % 16] = data[i]; 20 | } else { 21 | ascii[i % 16] = '.'; 22 | } 23 | 24 | if ((i + 1) % 8 == 0 || i + 1 == size) { 25 | printf(" "); 26 | if ((i + 1) % 16 == 0) { 27 | printf("| %s \n\r", ascii); 28 | if (i + 1 != size) { 29 | printf("[*] "); 30 | } 31 | } else if (i + 1 == size) { 32 | ascii[(i + 1) % 16] = '\0'; 33 | if ((i + 1) % 16 <= 8) { 34 | printf(" "); 35 | } 36 | for (int j = (i + 1) % 16; j < 16; ++j) { 37 | printf(" "); 38 | } 39 | printf("| %s \n\r", ascii); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /kernel/src/util/trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "printf.h" 4 | 5 | #define TRACE(fmt, ...) printf("[*] " fmt "\n\r", ## __VA_ARGS__) 6 | #define ERROR(fmt, ...) printf("[-] " fmt "\n\r", ## __VA_ARGS__) 7 | #define WARN(fmt, ...) printf("[!] " fmt "\n\r", ## __VA_ARGS__) 8 | #define DEBUG(fmt, ...) printf("[?] " fmt "\n\r", ## __VA_ARGS__) 9 | 10 | void trace_hex(const void* _data, size_t size); 11 | 12 | #define ASSERT(check) \ 13 | if (!(check)) { \ 14 | ERROR("Assert `%s` failed at %s (%s:%d)", #check, __FUNCTION__, __FILE__, __LINE__); \ 15 | while(1); \ 16 | } 17 | -------------------------------------------------------------------------------- /kernel/src/util/umm_malloc_cfgport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | -------------------------------------------------------------------------------- /kernel/src/vdso/vdso.c: -------------------------------------------------------------------------------- 1 | #include "vdso.h" 2 | 3 | #include 4 | 5 | VDSO void task_trampoline(void(*entry)()) { 6 | entry(); 7 | sys_sched_drop(); 8 | } 9 | -------------------------------------------------------------------------------- /kernel/src/vdso/vdso.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VDSO __attribute__((section(".vdso.text"))) 4 | 5 | /** 6 | * Used as the task entry before actually jumping to the user 7 | * 8 | * @param entry [IN] The user entry 9 | */ 10 | VDSO void task_trampoline(void(*entry)()); 11 | -------------------------------------------------------------------------------- /kernel/src/vdso/vectors.S: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | .section .vdso.text 5 | 6 | /** 7 | * The way interrupt vectors work in xtensa is that there are 64byte between vector entries and 8 | * it jumps to the constant offset between each of them. 9 | * 10 | * This code resides in a user page, and on the first fetch of some of these we will 11 | * enter pid 0, we need to be extra sure that even if we enter this function from normal 12 | * control flow nothing happens. 13 | * 14 | * qemu xtensa_cpu_do_interrupt codes: 15 | * 3 - window overflow 4 16 | * 4 - window underflow 4 17 | * 5 - window overflow 8 18 | * 6 - window underflow 8 19 | * 7 - window overflow 12 20 | * 8 - window underflow 12 21 | * 9 - irq 22 | * 10 - kernel 23 | * 11 - user 24 | * 12 - double 25 | * 13 - debug 26 | */ 27 | 28 | /*********************************************************************************************************************** 29 | * Window overflow/underflow, these are pretty simple 30 | * 31 | * For these we simply need to spill/load to/from the stack the first x gpregs, 32 | * we need to use the special rfwo (return from window overflow) and rfwu (return from window underflow) 33 | * to properly handle these 34 | **********************************************************************************************************************/ 35 | 36 | .global vecbase 37 | 38 | .align 64 39 | vecbase: 40 | window_overflow_4: 41 | /* window overflow 4 - 0x00 */ 42 | s32e a0, a5, -16 43 | s32e a1, a5, -12 44 | s32e a2, a5, -8 45 | s32e a3, a5, -4 46 | rfwo 47 | 48 | .align 64 49 | window_underflow_4: 50 | /* window underflow 4 - 0x40 */ 51 | l32e a0, a5, -16 52 | l32e a1, a5, -12 53 | l32e a2, a5, -8 54 | l32e a3, a5, -4 55 | rfwu 56 | 57 | .align 64 58 | window_overflow_8: 59 | /* windows overflow 8 - 0x80 */ 60 | s32e a0, a9, -16 61 | l32e a0, a1, -12 62 | s32e a1, a9, -12 63 | s32e a2, a9, -8 64 | s32e a3, a9, -4 65 | s32e a4, a0, -32 66 | s32e a5, a0, -28 67 | s32e a6, a0, -24 68 | s32e a7, a0, -20 69 | rfwo 70 | 71 | .align 64 72 | window_underflow_8: 73 | /* windows underflow 8 - 0xc0 */ 74 | l32e a0, a9, -16 75 | l32e a1, a9, -12 76 | l32e a2, a9, -8 77 | l32e a7, a1, -12 78 | l32e a3, a9, -4 79 | l32e a4, a7, -32 80 | l32e a5, a7, -28 81 | l32e a6, a7, -24 82 | l32e a7, a7, -20 83 | rfwu 84 | 85 | .align 64 86 | window_overflow_12: 87 | /* windows overflow 12 - 0x100 */ 88 | s32e a0, a13, -16 89 | l32e a0, a1, -12 90 | s32e a1, a13, -12 91 | s32e a2, a13, -8 92 | s32e a3, a13, -4 93 | s32e a4, a0, -48 94 | s32e a5, a0, -44 95 | s32e a6, a0, -40 96 | s32e a7, a0, -36 97 | s32e a8, a0, -32 98 | s32e a9, a0, -28 99 | s32e a10, a0, -24 100 | s32e a11, a0, -20 101 | rfwo 102 | 103 | .align 64 104 | window_underflow_12: 105 | /* windows underflow 12 - 0x140 */ 106 | l32e a0, a13, -16 107 | l32e a1, a13, -12 108 | l32e a2, a13, -8 109 | l32e a11, a1, -12 110 | l32e a3, a13, -4 111 | l32e a4, a11, -48 112 | l32e a5, a11, -44 113 | l32e a6, a11, -40 114 | l32e a7, a11, -36 115 | l32e a8, a11, -32 116 | l32e a9, a11, -28 117 | l32e a10, a11, -24 118 | l32e a11, a11, -20 119 | rfwu 120 | 121 | /*********************************************************************************************************************** 122 | * Interrupt levels 123 | **********************************************************************************************************************/ 124 | 125 | .align 64 126 | /* interrupt level 2 - 0x180 */ 127 | rsr.excsave2 a0 128 | rfi 2 129 | 130 | .align 64 131 | /* interrupt level 3 - 0x1c0 */ 132 | rsr.excsave3 a0 133 | rfi 3 134 | 135 | .align 64 136 | /* interrupt level 4 - 0x200 */ 137 | rsr.excsave4 a0 138 | rfi 4 139 | 140 | .align 64 141 | /* interrupt level 5 - 0x240 */ 142 | rsr.excsave5 a0 143 | rfi 5 144 | 145 | .align 64 146 | /* interrupt level 6 (debug) - 0x280 */ 147 | rsr.excsave6 a0 148 | rfi 6 149 | 150 | .align 64 151 | /* interrupt level 7 (nmi) - 0x2c0 */ 152 | rsr.excsave7 a0 153 | rfi 7 154 | 155 | /*********************************************************************************************************************** 156 | * Normal exceptions 157 | **********************************************************************************************************************/ 158 | 159 | /* 160 | * kernel exception - 0x300 161 | * This entry does not actually change the pid to 0, since we expect 162 | * to already be in kernel mode for this entry, if we were in usermode 163 | * but it changed PS.UM to 0, we will go here, and once we jump to the 164 | * kernel we will just fault since we are not in the right entry. 165 | */ 166 | .type kernel_exception,@function 167 | .align 64 168 | kernel_exception: 169 | // // save the original a0 and a1 in two scratch 170 | // // registers, specifically a1 and a0 171 | // wsr.excsave1 a1 172 | // wsr.depc a0 173 | // 174 | // // allocate the task_regs_t for the current exception 175 | // addi sp, sp, -TASK_REGS_SIZE 176 | // 177 | // // now save the original a0-a2, we need 178 | // // them to do stuff 179 | // s32i a0, sp, TASK_REGS_AR(0) 180 | // s32i a1, sp, TASK_REGS_AR(1) 181 | // s32i a2, sp, TASK_REGS_AR(2) 182 | // s32i a3, sp, TASK_REGS_AR(3) 183 | // 184 | // // 185 | // // we are going to start by loading a fast handler, which 186 | // // could also be a common slow handler, the idea is that 187 | // // the fast handler can take a quick path of handling some 188 | // // of the exception causes. 189 | // // 190 | // // it is safe to use exccause as index since it is only 6 bits 191 | // // meaning that we only haev a max of 64 entries 192 | // // 193 | // rsr.exccause a0 194 | // movi a2, g_fast_interrupts 195 | // addx4 a0, a0, a2 196 | // l32i a0, a0, 0 197 | // 198 | // // jump to it 199 | // jx a0 200 | rfe 201 | .size kernel_exception, . - kernel_exception 202 | 203 | /* 204 | * user exception - 0x340 205 | * This entry does change the pid to 0 unconditionally, so 206 | * we can assume that now we are in kernel mode and we need 207 | * to be careful 208 | */ 209 | .type user_exception,@function 210 | .align 64 211 | user_exception: 212 | // save the original a0 and a1 in two scratch 213 | // registers, specifically a1 and a0 214 | wsr.excsave1 a1 215 | wsr.depc a0 216 | 217 | // load the kernel stack, we first take 218 | // the prid of the current cpu, then we 219 | // are going to access a pointer array 220 | // and load the stack from it. 221 | rsr.prid a1 222 | extui a1, a1, 13, 1 223 | movi a0, g_kernel_stacks 224 | addx4 a1, a1, a0 225 | l32i sp, a1, 0 226 | 227 | // allocate the task_regs_t for the current exception 228 | addi sp, sp, -TASK_REGS_SIZE 229 | 230 | // now save the original a0-a2, we need 231 | // them to do stuff 232 | rsr.depc a0 233 | s32i a0, sp, TASK_REGS_AR(0) 234 | rsr.excsave1 a0 235 | s32i a0, sp, TASK_REGS_AR(1) 236 | s32i a2, sp, TASK_REGS_AR(2) 237 | s32i a3, sp, TASK_REGS_AR(3) 238 | 239 | // 240 | // we are going to start by loading a fast handler, which 241 | // could also be a common slow handler, the idea is that 242 | // the fast handler can take a quick path of handling some 243 | // of the exception causes. 244 | // 245 | // it is safe to use exccause as index since it is only 6 bits 246 | // meaning that we only haev a max of 64 entries 247 | // 248 | rsr.exccause a0 249 | movi a2, g_fast_interrupts 250 | addx4 a0, a0, a2 251 | l32i a0, a0, 0 252 | 253 | // jump to it 254 | jx a0 255 | .size user_exception, . - user_exception 256 | 257 | .align 64 258 | /* double exception - 0x380 */ 259 | rfde 260 | -------------------------------------------------------------------------------- /kernel/syms/bootrom.ld: -------------------------------------------------------------------------------- 1 | /**********************************************/ 2 | /* libgcc function implemented in the bootrom */ 3 | /**********************************************/ 4 | 5 | /* 6 | * In theory we can implement them ourselves, and even get them inlined... 7 | * I will need to think about this... 8 | * 9 | * for now just have them so the code will compile nicely 10 | */ 11 | 12 | __absvdi2 = 0x4006387c; 13 | __absvsi2 = 0x40063868; 14 | __adddf3 = 0x40002590; 15 | __addsf3 = 0x400020e8; 16 | __addvdi3 = 0x40002cbc; 17 | __addvsi3 = 0x40002c98; 18 | __ashldi3 = 0x4000c818; 19 | __ashrdi3 = 0x4000c830; 20 | __bswapdi2 = 0x40064b08; 21 | __bswapsi2 = 0x40064ae0; 22 | __clrsbdi2 = 0x40064b7c; 23 | __clrsbsi2 = 0x40064b64; 24 | __clzdi2 = 0x4000ca50; 25 | __clzsi2 = 0x4000c7e8; 26 | __cmpdi2 = 0x40063820; 27 | __ctzdi2 = 0x4000ca64; 28 | __ctzsi2 = 0x4000c7f0; 29 | __divdc3 = 0x400645a4; 30 | __divdf3 = 0x40002954; 31 | __divdi3 = 0x4000ca84; 32 | __divsi3 = 0x4000c7b8; 33 | __eqdf2 = 0x400636a8; 34 | __eqsf2 = 0x40063374; 35 | __extendsfdf2 = 0x40002c34; 36 | __ffsdi2 = 0x4000ca2c; 37 | __ffssi2 = 0x4000c804; 38 | __fixdfdi = 0x40002ac4; 39 | __fixdfsi = 0x40002a78; 40 | __fixsfdi = 0x4000244c; 41 | __fixsfsi = 0x4000240c; 42 | __fixunsdfsi = 0x40002b30; 43 | __fixunssfdi = 0x40002504; 44 | __fixunssfsi = 0x400024ac; 45 | __floatdidf = 0x4000c988; 46 | __floatdisf = 0x4000c8c0; 47 | __floatsidf = 0x4000c944; 48 | __floatsisf = 0x4000c870; 49 | __floatundidf = 0x4000c978; 50 | __floatundisf = 0x4000c8b0; 51 | __floatunsidf = 0x4000c938; 52 | __floatunsisf = 0x4000c864; 53 | __gcc_bcmp = 0x40064a70; 54 | __gedf2 = 0x40063768; 55 | __gesf2 = 0x4006340c; 56 | __gtdf2 = 0x400636dc; 57 | __gtsf2 = 0x400633a0; 58 | __ledf2 = 0x40063704; 59 | __lesf2 = 0x400633c0; 60 | __lshrdi3 = 0x4000c84c; 61 | __ltdf2 = 0x40063790; 62 | __ltsf2 = 0x4006342c; 63 | __moddi3 = 0x4000cd4c; 64 | __modsi3 = 0x4000c7c0; 65 | __muldc3 = 0x40063c90; 66 | __muldf3 = 0x4006358c; 67 | __muldi3 = 0x4000c9fc; 68 | __mulsf3 = 0x400632c8; 69 | __mulsi3 = 0x4000c7b0; 70 | __mulvdi3 = 0x40002d78; 71 | __mulvsi3 = 0x40002d60; 72 | __nedf2 = 0x400636a8; 73 | __negdf2 = 0x400634a0; 74 | __negdi2 = 0x4000ca14; 75 | __negsf2 = 0x400020c0; 76 | __negvdi2 = 0x40002e98; 77 | __negvsi2 = 0x40002e78; 78 | __nesf2 = 0x40063374; 79 | __nsau_data = 0x3ff96544; 80 | __paritysi2 = 0x40002f3c; 81 | __popcount_tab = 0x3ff96544; 82 | __popcountdi2 = 0x40002ef8; 83 | __popcountsi2 = 0x40002ed0; 84 | __powidf2 = 0x400638e4; 85 | __subdf3 = 0x400026e4; 86 | __subsf3 = 0x400021d0; 87 | __subvdi3 = 0x40002d20; 88 | __subvsi3 = 0x40002cf8; 89 | __truncdfsf2 = 0x40002b90; 90 | __ucmpdi2 = 0x40063840; 91 | __udiv_w_sdiv = 0x40064bec; 92 | __udivdi3 = 0x4000cff8; 93 | __udivmoddi4 = 0x40064bf4; 94 | __udivsi3 = 0x4000c7c8; 95 | __umoddi3 = 0x4000d280; 96 | __umodsi3 = 0x4000c7d0; 97 | __umulsidi3 = 0x4000c7d8; 98 | __unorddf2 = 0x400637f4; 99 | __unordsf2 = 0x40063478; 100 | 101 | /**********************************************/ 102 | /* other useful and pure functions we can use */ 103 | /**********************************************/ 104 | 105 | /* 106 | * string and memory functions 107 | * we have them because they can take quite a bit of space and 108 | * have little use to be inlined (of course we are going to use 109 | * the builtin variants by default and fallback to these only when 110 | * needed) 111 | */ 112 | memccpy = 0x4000c220; 113 | memchr = 0x4000c244; 114 | memcmp = 0x4000c260; 115 | memcpy = 0x4000c2c8; 116 | memmove = 0x4000c3c0; 117 | memrchr = 0x4000c400; 118 | memset = 0x4000c44c; 119 | strcasecmp = 0x400011cc; 120 | strcasestr = 0x40001210; 121 | strcat = 0x4000c518; 122 | strchr = 0x4000c53c; 123 | strcmp = 0x40001274; 124 | strcoll = 0x40001398; 125 | strcpy = 0x400013ac; 126 | strcspn = 0x4000c558; 127 | strlcat = 0x40001470; 128 | strlcpy = 0x4000c584; 129 | strlen = 0x400014c0; 130 | strlwr = 0x40001524; 131 | strncasecmp = 0x40001550; 132 | strncat = 0x4000c5c4; 133 | strncmp = 0x4000c5f4; 134 | strncpy = 0x400015d4; 135 | strnlen = 0x4000c628; 136 | strrchr = 0x40001708; 137 | strsep = 0x40001734; 138 | strspn = 0x4000c648; 139 | strstr = 0x4000c674; 140 | __strtok_r = 0x4000c6a8; 141 | strtok_r = 0x4000c70c; 142 | strtol = 0x4005681c; 143 | _strtol_r = 0x40056714; 144 | strtoul = 0x4005692c; 145 | _strtoul_r = 0x40056834; 146 | strupr = 0x4000174c; 147 | 148 | /* 149 | * in theory ctype functions exist, but most of them are small and 150 | * can be inlined so if we want to we are going to just have them 151 | * implemented manually 152 | */ 153 | 154 | /* 155 | * a special memcpy that does 32bit operations always 156 | * can be very useful 157 | */ 158 | xthal_memcpy = 0x4000c0bc; 159 | -------------------------------------------------------------------------------- /kernel/syms/io.ld: -------------------------------------------------------------------------------- 1 | 2 | /* IO bases according to the spec */ 3 | DPORT_BASE = 0x3FF00000; 4 | AES_BASE = 0x3FF01000; 5 | RSA_BASE = 0x3FF02000; 6 | SHA_BASE = 0x3FF03000; 7 | CACHE_MMU_TABLE = 0x3FF10000; 8 | PID_CONTROLLER_BASE = 0x3FF1F000; 9 | UART0_BASE = 0x3FF40000; 10 | SPI1_BASE = 0x3FF42000; 11 | SPI0_BASE = 0x3FF43000; 12 | GPIO_BASE = 0x3FF44000; 13 | RTC_BASE = 0x3FF48000; 14 | IO_MUX_BASE = 0x3FF49000; 15 | I2C0_BASE = 0x3FF53000; 16 | TIMG0_BASE = 0x3FF5F000; 17 | TIMG1_BASE = 0x3FF60000; 18 | SPI2_BASE = 0x3FF64000; 19 | SPI3_BASE = 0x3FF65000; 20 | I2C1_BASE = 0x3FF67000; 21 | RNG_BASE = 0x3FF75000; 22 | -------------------------------------------------------------------------------- /loader/Makefile: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # ESP32 loader 3 | ######################################################################################################################## 4 | 5 | #----------------------------------------------------------------------------------------------------------------------- 6 | # Build constants 7 | #----------------------------------------------------------------------------------------------------------------------- 8 | 9 | TOOLCHAIN_PATH := ../toolchain 10 | include $(TOOLCHAIN_PATH)/toolchain.mk 11 | include $(TOOLCHAIN_PATH)/esptool.mk 12 | 13 | OUT_DIR := out 14 | BIN_DIR := $(OUT_DIR)/bin 15 | BUILD_DIR := $(OUT_DIR)/build 16 | 17 | #----------------------------------------------------------------------------------------------------------------------- 18 | # General configurations 19 | #----------------------------------------------------------------------------------------------------------------------- 20 | 21 | CFLAGS := -Werror -std=gnu11 22 | CFLAGS += -Wno-unused-label 23 | CFLAGS += -Wno-address-of-packed-member 24 | CFLAGS += -Wno-psabi 25 | 26 | # Size optimization, include debug info 27 | CFLAGS += -Os -g3 28 | 29 | # Link time optimization 30 | CFLAGS += -flto -ffat-lto-objects -fuse-linker-plugin 31 | 32 | # Freestanding, static and short wchar_t (instead of int) 33 | CFLAGS += -ffreestanding -static -fshort-wchar 34 | 35 | # No stdlib 36 | CFLAGS += -nostdlib 37 | 38 | # Use proper linker script 39 | CFLAGS += -Tlinker.ld 40 | 41 | # Include the lib and kernel folders in include path 42 | CFLAGS += -Isrc -I../kernel/src 43 | 44 | #----------------------------------------------------------------------------------------------------------------------- 45 | # Target specific stuff 46 | #----------------------------------------------------------------------------------------------------------------------- 47 | 48 | # Use literal pools 49 | CFLAGS += -mauto-litpools 50 | 51 | # We have the const16 instruction, so use it 52 | CFLAGS += -mconst16 53 | 54 | # We are kernel 55 | CFLAGS += -mforce-no-pic 56 | 57 | # Align branch targets 58 | CFLAGS += -mtarget-align 59 | 60 | # Allow VLIW 61 | CFLAGS += -Wa,--flix 62 | 63 | # Tells the assembler that a long call may be needed, it does not seem 64 | # to affect code gen too much with lto 65 | CFLAGS += -mlongcalls 66 | 67 | #----------------------------------------------------------------------------------------------------------------------- 68 | # Sources 69 | #----------------------------------------------------------------------------------------------------------------------- 70 | 71 | SRCS := $(shell find src -name '*.c') 72 | 73 | # 74 | # LittleFS driver 75 | # 76 | 77 | SRCS += ../lib/littlefs/lfs.c 78 | SRCS += ../lib/littlefs/lfs_util.c 79 | CFLAGS += -I../lib/littlefs 80 | 81 | # we don't have malloc in the loader 82 | CFLAGS += -DLFS_NO_MALLOC 83 | 84 | # we are readonly in the loader 85 | CFLAGS += -DLFS_READONLY 86 | 87 | ######################################################################################################################## 88 | # Targets 89 | ######################################################################################################################## 90 | 91 | .PHONY: all clean 92 | 93 | all: $(BIN_DIR)/loader.bin 94 | 95 | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) 96 | DEPS := $(OBJS:%.o=%.d) 97 | -include $(DEPS) 98 | 99 | $(BIN_DIR)/loader.bin: $(BIN_DIR)/loader.elf 100 | @echo ESPTOOL ELF2IMAGE $@ 101 | @$(ESPTOOL) --chip esp32 --trace elf2image --output $(BUILD_DIR)/loader.bin --version 3 --min-rev 3 --flash_freq 40m --flash_mode dio --flash_size 16MB $^ 102 | @test `wc -c <$(BUILD_DIR)/loader.bin` -lt 12288 103 | @cp $(BUILD_DIR)/loader.bin $@ 104 | 105 | $(BIN_DIR)/loader.elf: $(OBJS) 106 | @echo LD $@ 107 | @mkdir -p $(@D) 108 | @$(LD) $(CFLAGS) -o $@ $^ 109 | 110 | $(BUILD_DIR)/%.c.o: %.c 111 | @echo CC $@ 112 | @mkdir -p $(@D) 113 | @$(CC) $(CFLAGS) -MMD -c $< -o $@ 114 | 115 | $(BUILD_DIR)/%.S.o: %.S 116 | @echo CC $@ 117 | @mkdir -p $(@D) 118 | @$(CC) $(CFLAGS) -c $< -o $@ 119 | 120 | clean: 121 | rm -rf out 122 | -------------------------------------------------------------------------------- /loader/linker.ld: -------------------------------------------------------------------------------- 1 | 2 | /****************/ 3 | /* loader entry */ 4 | /****************/ 5 | 6 | ENTRY(_start) 7 | 8 | /*************************************/ 9 | /* memory ranges used for the loader */ 10 | /*************************************/ 11 | 12 | /* 13 | * the kernel uses SRAM1 for itself, so we are going to use 14 | * SRAM0 and SRAM2 so we won't override each other 15 | */ 16 | MEMORY { 17 | IRAM (RX) : ORIGIN = 0x40080000, len = 128K 18 | DRAM (RW) : ORIGIN = 0x3FFC0000, len = 64K 19 | } 20 | 21 | /*************************************/ 22 | /* Symbols provided by the bootrom */ 23 | /*************************************/ 24 | 25 | /* ROM functions */ 26 | ets_printf = 0x40007d54; 27 | esp_rom_spiflash_attach = 0x40062a6c; 28 | esp_rom_spiflash_read = 0x40062ed8; 29 | esp_rom_spiflash_config_param = 0x40063238; 30 | uart_tx_one_char = 0x40009200; 31 | uart_rx_one_char = 0x400092d0; 32 | xthal_memcpy = 0x4000c0bc; 33 | memcpy = 0x4000c2c8; 34 | memset = 0x4000c44c; 35 | memcmp = 0x4000c260; 36 | strspn = 0x4000c648; 37 | strcspn = 0x4000c558; 38 | strchr = 0x4000c53c; 39 | strcpy = 0x400013ac; 40 | strrchr = 0x40001708; 41 | printf = ets_printf; 42 | __umoddi3 = 0x4000d280; 43 | __udivdi3 = 0x4000cff8; 44 | __bswapsi2 = 0x40064ae0; 45 | __popcountsi2 = 0x40002ed0; 46 | 47 | /* hardware registers */ 48 | TIMG0 = 0x3FF5F000; 49 | TIMG1 = 0x3FF60000; 50 | RTC = 0x3FF48000; 51 | 52 | TIMG0_Tx_WDTCONFIG0 = TIMG0 + 0x48; 53 | TIMG1_Tx_WDTCONFIG0 = TIMG1 + 0x48; 54 | RTC_CNTL_WDTCONFIG0 = RTC + 0x8c; 55 | 56 | /*************************************/ 57 | /* the sections for the loader */ 58 | /*************************************/ 59 | 60 | SECTIONS { 61 | 62 | .text : { 63 | . = ALIGN(16); 64 | _text = .; 65 | *(.text) 66 | *(.text*) 67 | _text_end = .; 68 | } > IRAM 69 | 70 | .data : { 71 | . = ALIGN(4); 72 | _data = .; 73 | *(.rodata) 74 | *(.rodata*) 75 | 76 | . = ALIGN(4); 77 | *(.data) 78 | *(.data*) 79 | 80 | . = ALIGN(4); 81 | _bss = .; 82 | *(.bss) 83 | *(.bss*) 84 | _data_end = .; 85 | } > DRAM 86 | 87 | } 88 | -------------------------------------------------------------------------------- /loader/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "trace.h" 5 | #include "rom.h" 6 | #include "lfs.h" 7 | #include "initrd.h" 8 | 9 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | // Helper macros 11 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | #define _STR(x) #x 14 | #define STR(x) _STR(x) 15 | 16 | #define ASSERT(check) \ 17 | if (!(check)) { \ 18 | ERROR("Assert `%s` failed at %s (%s:%d)", #check, __FUNCTION__, __FILE__, __LINE__); \ 19 | while(1); \ 20 | } 21 | 22 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 23 | // Registers that we need for our init 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | extern volatile uint32_t TIMG0_Tx_WDTCONFIG0; 27 | extern volatile uint32_t TIMG1_Tx_WDTCONFIG0; 28 | extern volatile uint32_t RTC_CNTL_WDTCONFIG0; 29 | 30 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 31 | // The kernel header 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | 34 | /* the header */ 35 | typedef struct kernel_header { 36 | uint32_t magic; 37 | int32_t code_size; 38 | int32_t data_size; 39 | int32_t vdso_size; 40 | uint32_t entry_point; 41 | } kernel_header_t; 42 | 43 | #define KERNEL_MAGIC 0x4c4e524b 44 | #define KERNEL_CODE_BASE ((void*)0x400A0000) 45 | #define KERNEL_DATA_BASE ((void*)0x3FFE0000) 46 | #define KERNEL_VDSO_BASE ((void*)0x4009E000) 47 | 48 | typedef void (*kernel_entry_func_t)(); 49 | 50 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 51 | // LFS management 52 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 53 | 54 | /** 55 | * A temp buffer used for storing the kernel data while we transition 56 | * from the loader (+ bootrom) to the kernel 57 | */ 58 | static void* m_temp_buffer = (void*) 0x3FFD0000; 59 | 60 | /** 61 | * The buffer for the initrd 62 | */ 63 | static void* m_initrd_buffer = (void*) 0x3FFC2000; 64 | 65 | int spi_flash_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { 66 | ASSERT((off % 4) == 0); 67 | ASSERT((size % 4) == 0); 68 | ASSERT(block + 4 < c->block_count + 4); 69 | 70 | // we need to skip 4 blocks from the start 71 | uint32_t addr = (block + 4) * c->block_size + off; 72 | int err = esp_rom_spiflash_read(addr, buffer, (int32_t)size); 73 | return err == 0 ? LFS_ERR_OK : LFS_ERR_IO; 74 | } 75 | 76 | static struct lfs_config m_lfs_config = { 77 | .read = spi_flash_read, 78 | 79 | // TODO: dynamically figure this out 80 | // choosen specifically for W25Q128FW 81 | .read_size = 4, // our function can read at multiplies of 4 82 | .read_buffer = (char[256]){}, 83 | .prog_size = 256, // page size is 256 bytes, so use that 84 | .prog_buffer = (char[256]){}, 85 | .block_size = 4096, // the minimum erasable block is 16 pages (4kb) 86 | .block_count = 4096 - 4, // first 3 blocks are reserved for the loader 87 | .block_cycles = 500, // idk, this is what the example uses 88 | .cache_size = 256, // use the prog size ig 89 | .lookahead_size = 16, // use the same value as the example 90 | .lookahead_buffer = (char[16]){}, 91 | }; 92 | 93 | static lfs_t m_lfs = {0 }; 94 | 95 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 96 | // The loader itself 97 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 98 | 99 | /** 100 | * Disable watchdogs so we are not 101 | * gonna die from them 102 | */ 103 | static void disable_watchdogs() { 104 | TIMG0_Tx_WDTCONFIG0 = 0; 105 | TIMG1_Tx_WDTCONFIG0 = 0; 106 | RTC_CNTL_WDTCONFIG0 = 0; 107 | } 108 | 109 | static void load_initrd_file(const char* name, initrd_entry_t* entry) { 110 | // open the kernel file 111 | lfs_file_t file; 112 | struct lfs_info file_info; 113 | struct lfs_file_config file_config = { 114 | .buffer = (char[256]){} 115 | }; 116 | ASSERT(lfs_stat(&m_lfs, name, &file_info) == 0); 117 | TRACE("\tLoading %s (%d bytes)", name, file_info.size); 118 | entry->size = file_info.size; 119 | 120 | const char* filename = strrchr(name, '/'); 121 | if (filename == NULL) { 122 | filename = name; 123 | } else { 124 | filename++; 125 | } 126 | strcpy(entry->name, filename); 127 | 128 | ASSERT(lfs_file_opencfg(&m_lfs, &file, name, LFS_O_RDONLY, &file_config) == 0); 129 | ASSERT(lfs_file_read(&m_lfs, &file, entry + 1, file_info.size) == file_info.size); 130 | } 131 | 132 | static void load_initrd() { 133 | TRACE("Loading initrd"); 134 | // we only have one module for now 135 | initrd_header_t* header = m_initrd_buffer; 136 | header->count = 1; 137 | initrd_entry_t* entry = (initrd_entry_t*)(header + 1); 138 | 139 | // firstly load the init module 140 | load_initrd_file("/apps/init", entry); 141 | entry = INITRD_NEXT_ENTRY(entry); 142 | 143 | // set the total size 144 | header->total_size = (uintptr_t)entry - (uintptr_t)header; 145 | TRACE("Total size: %d bytes", header->total_size); 146 | } 147 | 148 | static void load_kernel() { 149 | TRACE("Loading kernel"); 150 | 151 | // open the kernel file 152 | lfs_file_t kernel_file; 153 | struct lfs_file_config kernel_file_config = { 154 | .buffer = (char[256]){} 155 | }; 156 | ASSERT(lfs_file_opencfg(&m_lfs, &kernel_file, "/kernel", LFS_O_RDONLY, &kernel_file_config) == 0); 157 | 158 | // load the kernel header and verify it, don't store it on the stack 159 | // since we might actually destroy our stack... 160 | static kernel_header_t kernel_header; 161 | lfs_file_read(&m_lfs, &kernel_file, &kernel_header, sizeof(kernel_header)); 162 | ASSERT(kernel_header.magic == KERNEL_MAGIC); 163 | 164 | size_t code_offset = sizeof(kernel_header_t); 165 | size_t data_offset = code_offset + kernel_header.code_size; 166 | size_t vdso_offset = data_offset + kernel_header.data_size; 167 | 168 | // make sure the kernel is actually small enough 169 | ASSERT(kernel_header.magic == KERNEL_MAGIC); 170 | ASSERT(kernel_header.code_size + kernel_header.data_size <= 128 * 1024); 171 | ASSERT(kernel_header.code_size % 4 == 0); 172 | ASSERT(kernel_header.data_size % 4 == 0); 173 | ASSERT(kernel_header.vdso_size % 4 == 0); 174 | ASSERT((uintptr_t)KERNEL_CODE_BASE <= kernel_header.entry_point); 175 | ASSERT(kernel_header.entry_point < (uintptr_t)KERNEL_CODE_BASE + kernel_header.code_size); 176 | 177 | // make sure that we have a big enough temp buffer 178 | ASSERT(kernel_header.code_size <= UINT16_MAX); 179 | ASSERT(kernel_header.data_size <= UINT16_MAX); 180 | ASSERT(kernel_header.vdso_size <= UINT16_MAX); 181 | 182 | TRACE("\tLoading code %p (%d bytes)", KERNEL_CODE_BASE, kernel_header.code_size); 183 | ASSERT(lfs_file_seek(&m_lfs, &kernel_file, code_offset, LFS_SEEK_SET) == code_offset); 184 | ASSERT(lfs_file_read(&m_lfs, &kernel_file, m_temp_buffer, kernel_header.code_size) == kernel_header.code_size); 185 | xthal_memcpy(KERNEL_CODE_BASE, m_temp_buffer, kernel_header.code_size); 186 | 187 | TRACE("\tLoading vdso %p (%d bytes)", KERNEL_VDSO_BASE, kernel_header.vdso_size); 188 | ASSERT(lfs_file_seek(&m_lfs, &kernel_file, vdso_offset, LFS_SEEK_SET) == vdso_offset); 189 | ASSERT(lfs_file_read(&m_lfs, &kernel_file, m_temp_buffer, kernel_header.vdso_size) == kernel_header.vdso_size); 190 | xthal_memcpy(KERNEL_VDSO_BASE, m_temp_buffer, kernel_header.vdso_size); 191 | 192 | TRACE("\tLoading data %p (%d bytes)", KERNEL_DATA_BASE, kernel_header.data_size); 193 | ASSERT(lfs_file_seek(&m_lfs, &kernel_file, data_offset, LFS_SEEK_SET) == data_offset); 194 | ASSERT(lfs_file_read(&m_lfs, &kernel_file, m_temp_buffer, kernel_header.data_size) == kernel_header.data_size); 195 | 196 | // once this starts, we can't rely on anything! that's because from here we start to override 197 | // the data section of the runtime kernel 198 | xthal_memcpy(KERNEL_DATA_BASE, m_temp_buffer, kernel_header.data_size); 199 | 200 | // NOTE: from here we should be super careful about everything since we just overrode the data 201 | // region of the bootrom, potentially including the stack itself 202 | 203 | // call the kernel 204 | asm ( 205 | "jx %0" 206 | : 207 | : "r"(kernel_header.entry_point) 208 | ); 209 | } 210 | 211 | void _start() { 212 | disable_watchdogs(); 213 | 214 | uint8_t b; 215 | while (uart_rx_one_char(&b) != 0) {} 216 | 217 | TRACE("Welcome from the loader!"); 218 | 219 | TRACE("\tConfiguring flash"); 220 | esp_rom_spiflash_attach(0, false); 221 | ASSERT(esp_rom_spiflash_config_param( 222 | 0, 223 | 16 * 0x100000, 224 | 0x10000, 225 | 0x1000, 226 | 0x100, 227 | 0xffff 228 | ) == 0); 229 | 230 | TRACE("\tMounting rootfs"); 231 | ASSERT(lfs_mount(&m_lfs, &m_lfs_config) == 0); 232 | 233 | load_initrd(); 234 | load_kernel(); 235 | 236 | while(1); 237 | } 238 | -------------------------------------------------------------------------------- /loader/src/rom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 7 | // ROM functions that we are going to use for ease of use 8 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | 10 | // from xthal 11 | void* xthal_memcpy(void *dst, const void *src, size_t len); 12 | 13 | // other rom functions 14 | int ets_printf(const char *fmt, ...); 15 | int uart_tx_one_char(uint8_t TxChar); 16 | int uart_rx_one_char(uint8_t *pRxChar); 17 | void esp_rom_spiflash_attach(uint32_t ishspi, bool legacy); 18 | int esp_rom_spiflash_read(uint32_t src_addr, uint32_t *dest, int32_t len); 19 | int esp_rom_spiflash_config_param(uint32_t deviceId, uint32_t chip_size, uint32_t block_size, uint32_t sector_size, uint32_t page_size, uint32_t status_mask); 20 | -------------------------------------------------------------------------------- /loader/src/trace.c: -------------------------------------------------------------------------------- 1 | // from https://gitlab.com/qookei/quack 2 | 3 | #include "trace.h" 4 | #include "rom.h" 5 | 6 | #include 7 | 8 | void trace_hex(const void* _data, size_t size) { 9 | const uint8_t* data = _data; 10 | char ascii[17] = { 0 }; 11 | 12 | ets_printf("[*] "); 13 | for (int i = 0; i < size; i++) { 14 | ets_printf("%02x ", data[i]); 15 | 16 | if (data[i] >= ' ' && data[i] <= '~') { 17 | ascii[i % 16] = data[i]; 18 | } else { 19 | ascii[i % 16] = '.'; 20 | } 21 | 22 | if ((i + 1) % 8 == 0 || i + 1 == size) { 23 | ets_printf(" "); 24 | if ((i + 1) % 16 == 0) { 25 | ets_printf("| %s \n", ascii); 26 | if (i + 1 != size) { 27 | ets_printf("[*] "); 28 | } 29 | } else if (i + 1 == size) { 30 | ascii[(i + 1) % 16] = '\0'; 31 | if ((i + 1) % 16 <= 8) { 32 | ets_printf(" "); 33 | } 34 | for (int j = (i + 1) % 16; j < 16; ++j) { 35 | ets_printf(" "); 36 | } 37 | ets_printf("| %s \n", ascii); 38 | } 39 | } 40 | } 41 | } 42 | 43 | void __assert_func(const char* filename, int line, const char* func, const char* expression) { 44 | ERROR("Assert `%s` failed at %s (%s:%d)", expression, func, filename, line); 45 | while(1); 46 | } -------------------------------------------------------------------------------- /loader/src/trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "rom.h" 7 | 8 | #define TRACE(fmt, ...) ets_printf("[*] " fmt "\n\r", ## __VA_ARGS__); 9 | #define ERROR(fmt, ...) ets_printf("[-] " fmt "\n\r", ## __VA_ARGS__); 10 | #define WARN(fmt, ...) ets_printf("[!] " fmt "\n\r", ## __VA_ARGS__); 11 | #define DEBUG(fmt, ...) ets_printf("[?] " fmt "\n\r", ## __VA_ARGS__); 12 | 13 | /** 14 | * Write a string to the serial output 15 | */ 16 | void trace_string(const char* str); 17 | 18 | /** 19 | * Trace a buffer as a pretty hexdump 20 | */ 21 | void trace_hex(const void* data, size_t size); 22 | -------------------------------------------------------------------------------- /scripts/create_rootfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | from littlefs import LittleFS, lfs 5 | 6 | fs = LittleFS( 7 | block_count=4096-4, 8 | block_size=4096, 9 | cache_size=256, 10 | lookahead_size=16, 11 | prog_size=256, 12 | read_size=4 13 | ) 14 | 15 | 16 | def copy_file(orig, to): 17 | with open(orig, 'rb') as f: 18 | data = f.read() 19 | with fs.open(to, 'wb') as f: 20 | f.write(data) 21 | 22 | 23 | # 24 | # Place all the apps 25 | # 26 | fs.mkdir('/apps') 27 | copy_file('apps/init/out/bin/init.bin', '/apps/init') 28 | 29 | # 30 | # Place the kernel in the root folder 31 | # 32 | copy_file('kernel/out/bin/kernel.bin', '/kernel') 33 | 34 | # 35 | # Save it 36 | # 37 | os.makedirs('out', exist_ok=True) 38 | with open('out/rootfs.bin', 'wb') as fh: 39 | fh.write(fs.context.buffer) 40 | -------------------------------------------------------------------------------- /scripts/run_esp32.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | from dataclasses import dataclass 4 | from typing import List, Optional 5 | 6 | import littlefs 7 | from tqdm import tqdm 8 | from esptool import ESP32ROM, ESPLoader 9 | from esptool.targets import CHIP_DEFS 10 | from littlefs import LittleFS, lfs, LittleFSError 11 | 12 | # 13 | # Connect to the ESP32 14 | # 15 | esp = CHIP_DEFS['esp32']() 16 | esp.connect() 17 | print(f'Serial port {esp.serial_port}') 18 | 19 | # 20 | # Print info about the chip just in case 21 | # 22 | assert not esp.secure_download_mode 23 | print(f'Chip is {esp.get_chip_description()}') 24 | print(f'Features: {", ".join(esp.get_chip_features())}') 25 | print(f'Crystal is {esp.get_crystal_freq()}MHz') 26 | print(f'MAC: {":".join(map(lambda x: "%02x" % x, esp.read_mac()))}') 27 | 28 | # 29 | # Run the stub 30 | # 31 | esp: ESP32ROM = esp.run_stub() 32 | 33 | 34 | def write_flash(offset, data): 35 | # Setup for flashing 36 | esp.flash_begin(len(data), offset) 37 | 38 | seq = 0 39 | while len(data) != 0: 40 | # get and pad the block 41 | block = data[:esp.FLASH_WRITE_SIZE] 42 | block = block + b'\xFF' * (esp.FLASH_WRITE_SIZE - len(data)) 43 | 44 | # Send it 45 | esp.flash_block(block, seq) 46 | 47 | # Next step 48 | data = data[esp.FLASH_WRITE_SIZE:] 49 | seq += 1 50 | 51 | # Stub only writes each block to flash after 'ack'ing the receive, 52 | # so do a final dummy operation which will not be 'ack'ed 53 | # until the last block has actually been written out to flash 54 | esp.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR) 55 | 56 | 57 | # write the loader 58 | write_flash(4096, open('loader/out/bin/loader.bin', 'rb').read()) 59 | 60 | 61 | # The offset from the start of the flash (in blocks) where 62 | # the filesystem is located at 63 | ON_FLASH_BLOCK_OFFSET = 4 64 | 65 | 66 | class EspLfsContext(lfs.UserContext): 67 | 68 | def __init__(self): 69 | super().__init__(0) 70 | self._flash: List[Optional[int]] = [None] * 16 * 1024 * 1024 71 | 72 | def read(self, cfg: lfs.LFSConfig, block: int, off: int, size: int) -> bytearray: 73 | offset = block * cfg.block_size + off 74 | 75 | # Get the real data 76 | data = bytearray(esp.read_flash(offset + (cfg.block_size * ON_FLASH_BLOCK_OFFSET), size)) 77 | 78 | # and override it with the written data 79 | for i in range(size): 80 | if self._flash[offset + i] is not None: 81 | data[i] = self._flash[offset + i] 82 | 83 | return data 84 | 85 | def prog(self, cfg: lfs.LFSConfig, block: int, off: int, data: bytes) -> int: 86 | offset = block * cfg.block_size + off 87 | for i in range(len(data)): 88 | self._flash[offset + i] = data[i] 89 | return 0 90 | 91 | def erase(self, cfg: lfs.LFSConfig, block: int) -> int: 92 | for i in range(cfg.block_size): 93 | self._flash[block * cfg.block_size + i] = 0xFF 94 | return 0 95 | 96 | def _sync_range(self, offset, data): 97 | print(f"SYNC CHUNK {offset:x}-{offset + len(data):x}") 98 | offset = offset + (ON_FLASH_BLOCK_OFFSET * 4096) 99 | write_flash(offset, data) 100 | 101 | def sync(self, cfg: lfs.LFSConfig) -> int: 102 | return 0 103 | 104 | def do_full_sync(self) -> int: 105 | print("SYNC START") 106 | offset = 0 107 | write_offset = -1 108 | while offset < len(self._flash): 109 | # find the next non-None byte 110 | if self._flash[offset] is None: 111 | if write_offset != -1: 112 | # we need to write this range 113 | self._sync_range(write_offset, bytes(self._flash[write_offset:offset])) 114 | write_offset = -1 115 | offset += 1 116 | continue 117 | 118 | if write_offset == -1: 119 | # we found the first non-None byte 120 | write_offset = offset 121 | 122 | offset += 1 123 | 124 | # Commit the last change 125 | if write_offset != -1: 126 | self._sync_range(write_offset, bytes(self._flash[write_offset:offset])) 127 | 128 | print("SYNC END") 129 | 130 | self._flash = [None] * 16 * 1024 * 1024 131 | 132 | try: 133 | ctx = EspLfsContext() 134 | fs = LittleFS( 135 | context=ctx, 136 | block_count=4096-4, 137 | block_size=4096, 138 | cache_size=256, 139 | lookahead_size=16, 140 | prog_size=256, 141 | read_size=4, 142 | mount=False 143 | ) 144 | fs.format() 145 | fs.mount() 146 | 147 | rfs = LittleFS( 148 | block_count=4096-4, 149 | block_size=4096, 150 | cache_size=256, 151 | lookahead_size=16, 152 | prog_size=256, 153 | read_size=4, 154 | mount=False 155 | ) 156 | rfs.context.buffer = open('out/rootfs.bin', 'rb').read() 157 | rfs.mount() 158 | 159 | paths = ['/'] 160 | while len(paths) != 0: 161 | path = paths.pop() 162 | stat = rfs.stat(path) 163 | 164 | if stat.type == 2: 165 | # Add all the subdirs 166 | print(f'mkdir {path}') 167 | 168 | for file in rfs.listdir(path): 169 | paths.append(os.path.join(path, file)) 170 | 171 | # Make sure it exists on the other side 172 | fs.makedirs(path, exist_ok=True) 173 | 174 | elif stat.type == 1: 175 | # This is a normal file 176 | print(f'cp {path} - {stat.size} bytes') 177 | 178 | # Read the wanted file 179 | with rfs.open(path, 'rb') as f: 180 | file_data = f.read() 181 | 182 | # Now write it 183 | with fs.open(path, 'wb') as f: 184 | f.write(file_data) 185 | 186 | else: 187 | assert False, "Unknown file type" 188 | 189 | ctx.do_full_sync() 190 | 191 | finally: 192 | # 193 | # Hard reset in any case 194 | # 195 | esp.hard_reset() 196 | 197 | os.system(f'picocom {esp.serial_port} -b {esp.ESP_ROM_BAUD}') 198 | -------------------------------------------------------------------------------- /toolchain/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # By default fetch the toolchain 3 | default: fetch-toolchain 4 | 5 | # The path to the toolchain is in this current directory 6 | TOOLCHAIN_PATH = . 7 | 8 | # Include the toolchain 9 | include toolchain.mk 10 | include qemu.mk 11 | 12 | # Fetch it 13 | .PHONY: fetch-toolchain 14 | fetch-toolchain: 15 | rm -rf $(TOOLCHAIN_NAME) 16 | wget -N $(TOOLCHAIN_DOWNLOAD_URL) 17 | tar -xvf $(TOOLCHAIN_NAME).tar.xz 18 | 19 | .PHONY: fetch-qemu 20 | fetch-qemu: 21 | rm -rf $(QEMU_NAME) 22 | wget -N $(QEMU_DOWNLOAD_URL) 23 | tar -xvf $(QEMU_NAME).tar.bz2 24 | -------------------------------------------------------------------------------- /toolchain/esptool.mk: -------------------------------------------------------------------------------- 1 | 2 | ESPTOOL := esptool.py 3 | ESPEFUSE := espefuse.py 4 | ESPSECURE := espsecure.py 5 | -------------------------------------------------------------------------------- /toolchain/qemu.mk: -------------------------------------------------------------------------------- 1 | ifndef TOOLCHAIN_PATH 2 | $(error TOOLCHAIN_PATH must be set to a relative path to the toolchain directory) 3 | endif 4 | 5 | QEMU_VERSION := esp-develop-20220802 6 | QEMU_NAME := qemu-$(QEMU_VERSION) 7 | QEMU_DOWNLOAD_URL := https://github.com/espressif/qemu/releases/download/$(QEMU_VERSION)/$(QEMU_NAME).tar.bz2 8 | 9 | QEMU := $(TOOLCHAIN_PATH)/qemu/bin/qemu-system-xtensa 10 | -------------------------------------------------------------------------------- /toolchain/toolchain.mk: -------------------------------------------------------------------------------- 1 | ifndef TOOLCHAIN_PATH 2 | $(error TOOLCHAIN_PATH must be set to a relative path to the toolchain directory) 3 | endif 4 | 5 | TOOLCHAIN_VERSION := esp-2022r1-RC1 6 | TOOLCHAIN_TARGET := xtensa-esp32-elf 7 | TOOLCHAIN_NAME := $(TOOLCHAIN_TARGET)-gcc11_2_0-$(TOOLCHAIN_VERSION)-linux-amd64 8 | TOOLCHAIN_DOWNLOAD_URL := https://github.com/espressif/crosstool-NG/releases/download/$(TOOLCHAIN_VERSION)/$(TOOLCHAIN_NAME).tar.xz 9 | 10 | PREFIX := $(TOOLCHAIN_PATH)/$(TOOLCHAIN_TARGET)/bin/$(TOOLCHAIN_TARGET)- 11 | CC := $(PREFIX)gcc 12 | LD := $(PREFIX)gcc 13 | OBJCOPY := $(PREFIX)objcopy 14 | OBJDUMP := $(PREFIX)objdump 15 | --------------------------------------------------------------------------------