├── .vscode ├── c_cpp_properties.json └── settings.json ├── 00-Run-Linux-With-QEMU ├── .gitignore ├── README.md ├── TODO.md ├── build-kernel.sh ├── build-minimal-rootfs.sh ├── build-rootfs.sh ├── config.sh ├── run-kernel-with-rootfs.sh └── run-kernel.sh ├── 01-Run-U-Boot-With-QEMU ├── .gitignore ├── README.md ├── build-uboot.sh ├── config.sh └── run-uboot.sh ├── 02-Run-OpenSBI-With-QEMU ├── .gitignore ├── README.md ├── config.sh ├── get-opensbi.sh └── run-opensbi.sh ├── 03-Bare-Metal-Hello-RISC-V ├── .gitignore ├── Makefile ├── README.md ├── firmware.ld ├── firmware.s └── firmware_minimal.s ├── 04-Bare-Metal-C-Language ├── .gitignore ├── Makefile ├── NOTE.md ├── README.md ├── clang_env_init.ld ├── clang_env_init.s ├── echo.c └── firmware.c ├── 04.1-Bare-Metal-Sifive_u ├── .gdbinit ├── .gitignore ├── Makefile ├── README.md ├── clang_env_init.ld ├── clang_env_init.s ├── firmware.c ├── riscv_asm.h ├── riscv_types.h └── uart.h ├── 05-Interrupt-Basics └── README.md ├── 06-Timer-Interrupt ├── .gdbinit ├── .gitignore ├── Makefile ├── README.md ├── clang_env_init.ld ├── clang_env_init.s ├── firmware.c ├── interrupts.h ├── riscv_arch.h ├── riscv_asm.h ├── riscv_cpu.h ├── timer.h ├── trap.s ├── trap_handler.c ├── uart.h ├── virt.dtb └── virt.dts ├── 07-Simple-Process-Scheduling ├── .gdbinit ├── .gitignore ├── Makefile ├── README.md ├── clang_env_init.ld ├── clang_env_init.s ├── firmware.c ├── interrupts.h ├── kstring.c ├── kstring.h ├── proc.c ├── proc.h ├── riscv_arch.h ├── riscv_asm.h ├── riscv_cpu.h ├── test_processes.c ├── timer.h ├── trap.s ├── trap_handler.c └── uart.h ├── 08-UART-Interrupt ├── .gdbinit ├── .gitignore ├── Makefile ├── README.md ├── clang_env_init.ld ├── clang_env_init.s ├── firmware.c ├── interrupts.h ├── kbool.h ├── kdef.h ├── kstring.c ├── kstring.h ├── plic.h ├── proc.c ├── proc.h ├── riscv_arch.h ├── riscv_asm.h ├── riscv_cpu.h ├── riscv_priv.h ├── riscv_types.h ├── sifive_u.dts ├── test_processes.c ├── timer.h ├── trap.s ├── trap_handler.c └── uart.h ├── 09-Enter-Supervisor-Mode ├── .gdbinit ├── .gitignore ├── Makefile ├── NOTES.md ├── README.md ├── firmware.c ├── firmware.ld ├── firmware_entry.s ├── kernel_stack.c ├── kstring.c ├── kstring.h ├── mtrap_entry.s ├── mtrap_handler.c ├── proc.c ├── proc.h ├── riscv_arch.h ├── riscv_asm.h ├── riscv_asm_csr.gen.h ├── riscv_asm_csr.gen.h.py ├── riscv_cpu.h ├── riscv_priv.h ├── strap_entry.s ├── strap_handler.c ├── test_processes.c ├── timer.h └── uart.h ├── Appendix-00-Assembly-and-Linking ├── README.md ├── assembly-and-linking.drawio.png ├── test ├── test.ld ├── test.o └── test.s ├── LICENSE └── README.md /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/riscv64-linux-gnu-gcc", 10 | "cStandard": "c17", 11 | "cppStandard": "gnu++14", 12 | "intelliSenseMode": "linux-gcc-x64" 13 | } 14 | ], 15 | "version": 4 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "riscv_asm.h": "c", 4 | "riscv_cpu.h": "c", 5 | "timer.h": "c", 6 | "riscv_types.h": "c", 7 | "stdbool.h": "c", 8 | "proc.h": "c", 9 | "stdlib.h": "c", 10 | "kstring.h": "c", 11 | "kdef.h": "c", 12 | "riscv_arch.h": "c", 13 | "uart.h": "c", 14 | "interrupts.h": "c", 15 | "kbool.h": "c" 16 | }, 17 | "[python]": { 18 | "editor.defaultFormatter": "ms-python.autopep8" 19 | }, 20 | "python.formatting.provider": "none" 21 | } -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/README.md: -------------------------------------------------------------------------------- 1 | # 00: Run Linux Kernel With QEMU RISC-V 2 | 3 | ## Build Linux Kernel 4 | Download and build Linux kernel with [build-kernel.sh](build-kernel.sh) 5 | 6 | ```sh 7 | bash build-kernel.sh 8 | ``` 9 | 10 | ## Boot Linux Kernel With QEMU 11 | 12 | Boot just built kernel with QEMU: [run-kernel.sh](run-kernel.sh) 13 | 14 | ```sh 15 | bash run-kernel.sh 16 | ``` 17 | 18 | You can see that Linux kernel boots then panics, because there is no rootfs provided. 19 | 20 | **THE POINT**: QEMU boots linux kernel successfully! 21 | 22 | Now, you know that QEMU can boot Linux kernel. We can go ahead and write our own kernel! 23 | 24 | ## (Optional) NO PANIC - Minimal Rootfs 25 | 26 | The panic looks unpleasant, doesn't it? We can provide a rootfs image for QEMU to get a minimal working Linux environment. 27 | 28 | 29 | ### [What is initramfs?](https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt) 30 | 31 | ``` 32 | All 2.6 Linux kernels contain a gzipped "cpio" format archive, which is 33 | extracted into rootfs when the kernel boots up. After extracting, the kernel 34 | checks to see if rootfs contains a file "init", and if so it executes it as PID 35 | 1. If found, this init process is responsible for bringing the system the 36 | rest of the way up, including locating and mounting the real root device (if 37 | any). If rootfs does not contain an init program after the embedded cpio 38 | archive is extracted into it, the kernel will fall through to the older code 39 | to locate and mount a root partition, then exec some variant of /sbin/init 40 | out of that. 41 | ``` 42 | 43 | ### Build initramfs and run it with Linux kernel 44 | 45 | #### STEP 1. Build linux kernel as before 46 | 47 | ```sh 48 | bash build-kernel.sh 49 | ``` 50 | 51 | #### STEP 2. Build a minimal rootfs with busybox: [build-rootfs.sh](build-rootfs.sh) 52 | 53 | ```sh 54 | bash build-rootfs.sh 55 | ``` 56 | 57 | The above rootfs contains the common Linux folders and utils, and mounts the proc, sysfs and tmpfs virtual filesystems to construct a working Linux rootfs. However, this rootfs is still too complex for understanding Linux kernel boot, we can build an truly minimal initramfs containing only the necessary `/init` with [build-minimal-rootfs.sh](build-minimal-rootfs.sh) 58 | 59 | #### STEP 3. Boot kernel and rootfs with QEMU: [run-kernel-with-rootfs.sh](run-kernel-with-rootfs.sh) 60 | 61 | ```sh 62 | bash run-kernel-with-rootfs.sh 63 | ``` 64 | -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/TODO.md: -------------------------------------------------------------------------------- 1 | ## Something I don't understand yet 2 | 3 | ### Linux kernel is not a ELF file 4 | 5 | The output of Linux kernel `arch/riscv/boot/Image` is a EFI file instead of a ELF file? I expect to get a kernel in ELF format. 6 | 7 | Some references: 8 | 9 | - [The EFI Boot Stub](https://docs.kernel.org/admin-guide/efi-stub.html) 10 | - [RISC-V Linux kernel image format standardization](https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/_xCn1SZn1Gg?pli=1) 11 | 12 | ### What formats of system images are supported by QEMU's `-kernel` parameter? 13 | 14 | - Linux kernel in EFI foramt 15 | - Maybe some other formats? -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/build-kernel.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | mkdir -p ${BUILD_DIR} 4 | 5 | cd ${BUILD_DIR} 6 | 7 | curl -LOJ https://github.com/torvalds/linux/archive/refs/tags/v${KERNEL_VERSION}.tar.gz 8 | tar -xf linux-${KERNEL_VERSION}.tar.gz 9 | 10 | cd linux-${KERNEL_VERSION} 11 | export ARCH=riscv 12 | export CROSS_COMPILE=riscv64-linux-gnu- 13 | make defconfig 14 | make -j $(nproc) 15 | -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/build-minimal-rootfs.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | mkdir -p ${BUILD_DIR} 4 | 5 | cd ${BUILD_DIR} 6 | 7 | curl -LOJ https://busybox.net/downloads/busybox-${BUSYBOX_VERSION}.tar.bz2 8 | tar -xf busybox-${BUSYBOX_VERSION}.tar.bz2 9 | 10 | cd busybox-${BUSYBOX_VERSION} 11 | export CROSS_COMPILE=riscv64-linux-gnu- 12 | make defconfig 13 | sed 's/^.*CONFIG_STATIC[^_].*$/CONFIG_STATIC=y/g' -i .config 14 | make -j $(nproc) 15 | 16 | # initrd 17 | mkdir -p initrd 18 | cd initrd 19 | 20 | cp ../busybox . 21 | 22 | cat < init 23 | #!/busybox sh 24 | /busybox sh 25 | /busybox poweroff -f 26 | EOF 27 | 28 | chmod a+x init 29 | 30 | find . | cpio -o -H newc > ../rootfs.cpio 31 | -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/build-rootfs.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | mkdir -p ${BUILD_DIR} 4 | 5 | cd ${BUILD_DIR} 6 | 7 | curl -LOJ https://busybox.net/downloads/busybox-${BUSYBOX_VERSION}.tar.bz2 8 | tar -xf busybox-${BUSYBOX_VERSION}.tar.bz2 9 | 10 | cd busybox-${BUSYBOX_VERSION} 11 | export CROSS_COMPILE=riscv64-linux-gnu- 12 | make defconfig 13 | sed 's/^.*CONFIG_STATIC[^_].*$/CONFIG_STATIC=y/g' -i .config 14 | make -j $(nproc) 15 | 16 | # initrd 17 | mkdir -p initrd 18 | cd initrd 19 | 20 | mkdir -p bin dev proc sys 21 | pushd bin 22 | cp ../../busybox ./ 23 | 24 | for prog in $(busybox --list); do 25 | ln -s /bin/busybox ./$prog 26 | done 27 | popd # bin 28 | 29 | cat < init 30 | #!/bin/sh 31 | mount -t sysfs sysfs /sys 32 | mount -t proc proc /proc 33 | mount -t devtmpfs udev /dev 34 | sysctl -w kernel.printk="2 4 1 7" 35 | clear 36 | /bin/sh 37 | poweroff -f 38 | EOF 39 | 40 | chmod a+x init 41 | 42 | find . | cpio -o -H newc > ../rootfs.cpio 43 | -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/config.sh: -------------------------------------------------------------------------------- 1 | BUILD_DIR=build 2 | KERNEL_VERSION=5.12 3 | BUSYBOX_VERSION=1.35.0 -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/run-kernel-with-rootfs.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | qemu-system-riscv64 -machine virt \ 4 | -display none -serial stdio \ 5 | -kernel ${BUILD_DIR}/linux-${KERNEL_VERSION}/arch/riscv/boot/Image \ 6 | -initrd ${BUILD_DIR}/busybox-${BUSYBOX_VERSION}/rootfs.cpio -------------------------------------------------------------------------------- /00-Run-Linux-With-QEMU/run-kernel.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | qemu-system-riscv64 -M virt \ 4 | -display none -serial stdio \ 5 | -kernel ${BUILD_DIR}/linux-${KERNEL_VERSION}/arch/riscv/boot/Image -------------------------------------------------------------------------------- /01-Run-U-Boot-With-QEMU/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /01-Run-U-Boot-With-QEMU/README.md: -------------------------------------------------------------------------------- 1 | # 01: Run U-Boot With QEMU RISC-V 2 | 3 | ## Build U-Boot 4 | Download and build U-Boot with [build-uboot.sh](build-uboot.sh) 5 | 6 | ```sh 7 | bash build-uboot.sh 8 | ``` 9 | 10 | ## Run U-Boot With QEMU 11 | 12 | Boot just built kernel with QEMU: [run-uboot.sh](run-uboot.sh) 13 | 14 | ```sh 15 | bash run-uboot.sh 16 | ``` 17 | 18 | Then you can see U-Boot boots. 19 | 20 | **NOTE**: `-kernel` parameter of QEMU accepts both flat binary and elf file. 21 | - `u-boot.bin`: flat binary 22 | ``` 23 | $ file build/u-boot-2021.04/u-boot.bin 24 | build/u-boot-2021.04/u-boot.bin: data 25 | ``` 26 | - `u-boot`: ELF 27 | ``` 28 | $ file build/u-boot-2021.04/u-boot 29 | build/u-boot-2021.04/u-boot: ELF 64-bit LSB executable, UCB RISC-V, version 1 (SYSV), dynamically linked, with debug_info, not stripped 30 | ``` 31 | -------------------------------------------------------------------------------- /01-Run-U-Boot-With-QEMU/build-uboot.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | mkdir -p ${BUILD_DIR} 4 | 5 | cd ${BUILD_DIR} 6 | 7 | curl -LOJ https://github.com/u-boot/u-boot/archive/refs/tags/v${UBOOT_VERSION}.tar.gz 8 | 9 | tar -xf u-boot-${UBOOT_VERSION}.tar.gz 10 | 11 | cd u-boot-${UBOOT_VERSION} 12 | export CROSS_COMPILE=riscv64-linux-gnu- 13 | make qemu-riscv64_smode_defconfig 14 | make -j $(nproc) 15 | -------------------------------------------------------------------------------- /01-Run-U-Boot-With-QEMU/config.sh: -------------------------------------------------------------------------------- 1 | BUILD_DIR=build 2 | UBOOT_VERSION=2021.04 -------------------------------------------------------------------------------- /01-Run-U-Boot-With-QEMU/run-uboot.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | qemu-system-riscv64 -M virt \ 4 | -display none -serial stdio \ 5 | -kernel ${BUILD_DIR}/u-boot-${UBOOT_VERSION}/u-boot.bin -------------------------------------------------------------------------------- /02-Run-OpenSBI-With-QEMU/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /02-Run-OpenSBI-With-QEMU/README.md: -------------------------------------------------------------------------------- 1 | # 02: Run OpenSBI With QEMU RISC-V 2 | 3 | ## Download OpenSBI 4 | Download OpenSBI release binary with [get-opensbi.sh](get-opensbi.sh) 5 | 6 | ```sh 7 | bash get-opensbi.sh 8 | ``` 9 | 10 | ## Run OpenSBI With QEMU 11 | 12 | Script file: [run-opensbi.sh](run-opensbi.sh) 13 | 14 | ### Method 1: Manually load OpenSBI flat binary image directly into the address space of the guest 15 | ``` 16 | qemu-system-riscv64 -M virt \ 17 | -display none -serial stdio \ 18 | -bios none \ 19 | -device loader,file=${BUILD_DIR}/opensbi-${OPENSBI_VERSION}-rv-bin/share/opensbi/lp64/generic/firmware/fw_dynamic.bin,addr=0x80000000 20 | ``` 21 | 22 | ### Method 2: Load OpenSBI flat binary image as bios 23 | ``` 24 | qemu-system-riscv64 -M virt \ 25 | -display none -serial stdio \ 26 | -bios ${BUILD_DIR}/opensbi-${OPENSBI_VERSION}-rv-bin/share/opensbi/lp64/generic/firmware/fw_dynamic.bin 27 | ``` 28 | 29 | ### Method 3: Load OpenSBI ELF as bios 30 | ``` 31 | qemu-system-riscv64 -M virt \ 32 | -display none -serial stdio \ 33 | -bios ${BUILD_DIR}/opensbi-${OPENSBI_VERSION}-rv-bin/share/opensbi/lp64/generic/firmware/fw_dynamic.elf 34 | ``` -------------------------------------------------------------------------------- /02-Run-OpenSBI-With-QEMU/config.sh: -------------------------------------------------------------------------------- 1 | BUILD_DIR=build 2 | OPENSBI_VERSION=1.3 -------------------------------------------------------------------------------- /02-Run-OpenSBI-With-QEMU/get-opensbi.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | mkdir -p ${BUILD_DIR} 4 | 5 | cd ${BUILD_DIR} 6 | 7 | curl -LOJ https://github.com/riscv-software-src/opensbi/releases/download/v${OPENSBI_VERSION}/opensbi-${OPENSBI_VERSION}-rv-bin.tar.xz 8 | 9 | tar -xf opensbi-${OPENSBI_VERSION}-rv-bin.tar.xz 10 | -------------------------------------------------------------------------------- /02-Run-OpenSBI-With-QEMU/run-opensbi.sh: -------------------------------------------------------------------------------- 1 | source config.sh 2 | 3 | # Method 1: Manually load OpenSBI flat binary image directly into the address space of the guest 4 | qemu-system-riscv64 -M virt \ 5 | -display none -serial stdio \ 6 | -bios none \ 7 | -device loader,file=${BUILD_DIR}/opensbi-${OPENSBI_VERSION}-rv-bin/share/opensbi/lp64/generic/firmware/fw_dynamic.bin,addr=0x80000000 8 | 9 | # Method 2: Load OpenSBI flat binary image as bios 10 | # qemu-system-riscv64 -M virt \ 11 | # -display none -serial stdio \ 12 | # -bios ${BUILD_DIR}/opensbi-${OPENSBI_VERSION}-rv-bin/share/opensbi/lp64/generic/firmware/fw_dynamic.bin 13 | 14 | # Method 3: Load OpenSBI ELF as bios 15 | # qemu-system-riscv64 -M virt \ 16 | # -display none -serial stdio \ 17 | # -bios ${BUILD_DIR}/opensbi-${OPENSBI_VERSION}-rv-bin/share/opensbi/lp64/generic/firmware/fw_dynamic.elf -------------------------------------------------------------------------------- /03-Bare-Metal-Hello-RISC-V/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /03-Bare-Metal-Hello-RISC-V/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE := riscv64-linux-gnu- 2 | AS := ${CROSS_COMPILE}as 3 | LD := ${CROSS_COMPILE}ld 4 | OBJCOPY := ${CROSS_COMPILE}objcopy 5 | 6 | bin/firmware.bin: firmware.s firmware.ld 7 | ${AS} firmware.s -o bin/firmware.o 8 | ${LD} -T firmware.ld bin/firmware.o -o bin/firmware 9 | ${OBJCOPY} -O binary -S bin/firmware bin/firmware.bin 10 | 11 | run: bin/firmware.bin 12 | qemu-system-riscv64 -display none -machine virt -serial stdio -bios bin/firmware.bin 13 | 14 | clean: 15 | rm -rf bin/ 16 | 17 | $(shell mkdir -p bin) -------------------------------------------------------------------------------- /03-Bare-Metal-Hello-RISC-V/README.md: -------------------------------------------------------------------------------- 1 | # 03: Bare Metal Hello RISC-V 2 | 3 | ## QEMU RISC-V virt Machine Boot Flow 4 | 5 | ### How does `qemu-system-riscv64 -machine virt` boot? 6 | 7 | #### STEP.1 Power on and set pc to reset vector 8 | 9 | Refer to [Source Code from QEMU](https://github.com/qemu/qemu/blob/v5.1.0/hw/riscv/virt.c#L504) 10 | 11 | - QEMU power on 12 | - `pc` is set to reset vector `0x1000`, where is the instructions from on-chip-rom 13 | 14 | #### STEP.2 Run from on-chip-rom 15 | 16 | Refer to [Source Code from QEMU](https://github.com/qemu/qemu/blob/v5.1.0/hw/riscv/boot.c#L224) 17 | 18 | - Read `mhartid` from Control Status Register to `a0` 19 | - Load value of `fdt_load_addr` to `a1` 20 | - Jump to `start_addr`: `0x8000_0000` 21 | 22 | **What is in `start_addr`**? 23 | 24 | Refer to [Source Code from QEMU](https://github.com/qemu/qemu/blob/v5.1.0/hw/riscv/virt.c#L510), which loads bios firmware to `start_addr`. 25 | 26 | By default, bios firmware is OpenSBI. We can supply our own firmware by `-bios` option. 27 | 28 | In this Example, we load our bare metal program as firmware, to `start_addr` : `0x8000_0000`. 29 | 30 | #### STEP.3 bios, bootloader, kernel 31 | 32 | Typically, OpenSBI loads a bootloader (probably U-Boot) as the next stage. Then, bootloader loads the kernel. 33 | 34 | ``` 35 | On-Chip-Rom => OpenSBI => U-Boot => Kernel 36 | ``` 37 | 38 | Alternatively, OpenSBI can directly load the kernel. 39 | 40 | ``` 41 | On-Chip-Rom => OpenSBI => Kernel 42 | ``` 43 | 44 | ### Real-world RISC-V Boot Flow 45 | 46 | Real-world RISC-V boot flow may be more diverse, refer to [An Introduction to RISC-V Boot Flow](https://riscv.org/wp-content/uploads/2019/12/Summit_bootflow.pdf). 47 | 48 | ## QEMU RISC-V *virt* Machine Memory Map 49 | 50 | According to QEMU [source code](https://github.com/qemu/qemu/blob/v8.0.2/hw/riscv/virt.c#L77), the memory map of *virt* is as follows: 51 | 52 | ``` 53 | static const MemMapEntry virt_memmap[] = { 54 | ... 55 | [VIRT_MROM] = { 0x1000, 0xf000 }, /* On-Chip-ROM */ 56 | ... 57 | [VIRT_UART0] = { 0x10000000, 0x100 }, /* UART Device Registers Base Address */ 58 | ... 59 | [VIRT_DRAM] = { 0x80000000, 0x0 }, /* DRAM */ 60 | }; 61 | ``` 62 | The point is: the UART device registers is mapped to `0x10000000`, we can operate UART by write/read uart registers. 63 | 64 | By reading [QEMU Manual - RISC-V Generic Virtual Platform](https://www.qemu.org/docs/master/system/riscv/virt.html), we can know that the UART device is a *NS16550 compatible UART*. 65 | 66 | However, I don't know how to configure the UART yet after reading datasheet [PC16550D Universal Asynchronous Receiver/Transmitter With FIFOs 67 | ](https://media.digikey.com/pdf/Data%20Sheets/Texas%20Instruments%20PDFs/PC16550D.pdf). 68 | 69 | Thanks for OpenSBI, which implements drivers for many kinds of RISC-V hardware, including *virt* of QEMU RISC-V. After searching *NS16550* keyword in OpenSBI source code for a while, I know that [**OpenSBI UART-8250 Driver**](https://github.com/riscv-software-src/opensbi/blob/master/lib/utils/serial/uart8250.c) serves for RISC-V *virt*'s UART. 70 | 71 | The core code for RISC-V *virt*'s UART initialization is as follows: 72 | ``` 73 | int uart8250_init(unsigned long base, u32 in_freq, u32 baudrate, u32 reg_shift, 74 | u32 reg_width, u32 reg_offset) 75 | { 76 | u16 bdiv = 0; 77 | 78 | uart8250_base = (volatile char *)base + reg_offset; 79 | uart8250_reg_shift = reg_shift; 80 | uart8250_reg_width = reg_width; 81 | uart8250_in_freq = in_freq; 82 | uart8250_baudrate = baudrate; 83 | 84 | if (uart8250_baudrate) { 85 | bdiv = (uart8250_in_freq + 8 * uart8250_baudrate) / 86 | (16 * uart8250_baudrate); 87 | } 88 | 89 | /* Disable all interrupts */ 90 | set_reg(UART_IER_OFFSET, 0x00); 91 | /* Enable DLAB */ 92 | set_reg(UART_LCR_OFFSET, 0x80); 93 | 94 | if (bdiv) { 95 | /* Set divisor low byte */ 96 | set_reg(UART_DLL_OFFSET, bdiv & 0xff); 97 | /* Set divisor high byte */ 98 | set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff); 99 | } 100 | 101 | /* 8 bits, no parity, one stop bit */ 102 | set_reg(UART_LCR_OFFSET, 0x03); 103 | /* Enable FIFO */ 104 | set_reg(UART_FCR_OFFSET, 0x01); 105 | /* No modem control DTR RTS */ 106 | set_reg(UART_MCR_OFFSET, 0x00); 107 | /* Clear line status */ 108 | get_reg(UART_LSR_OFFSET); 109 | /* Read receive buffer */ 110 | get_reg(UART_RBR_OFFSET); 111 | /* Set scratchpad */ 112 | set_reg(UART_SCR_OFFSET, 0x00); 113 | 114 | sbi_console_set_device(&uart8250_console); 115 | 116 | return 0; 117 | } 118 | ``` 119 | 120 | We can write our own UART initialization code according that. 121 | 122 | __UPDATE: The initialization is unnecessary for a minimal *Hello, RISC-V* program! We can write to uart directly. The initialization may be necessary for real-world hardware. The minimal code is [firmware_minimal.s](firmware_minimal.s)__ 123 | 124 | ## Compilation and Linking Basics 125 | 126 | If you are not familiar with compilation and linking, refer to this concise introduction: [Compilation and Linking Basics](/Appendix-00-Assembly-and-Linking/README.md) . 127 | 128 | ## Hello, RISC-V! 129 | 130 | ### Write UART initialization code 131 | 132 | **For simplicity, we write the initialization code in RISC-V assembly so that we don't have to care about** 133 | - how assembly code interacts with C code 134 | - how C code depends on the stack 135 | - how C code passes and returns parameters 136 | - and so on 137 | 138 | Firmware code: *firmware.s* 139 | 140 | ```asm 141 | .section .text 142 | 143 | .global _uart_init 144 | 145 | UART_BASE = 0x10000000 146 | 147 | UART_RBR_OFFSET = 0 148 | UART_THR_OFFSET = 0 149 | UART_DLL_OFFSET = 0 150 | UART_IER_OFFSET = 1 151 | UART_DLM_OFFSET = 1 152 | UART_FCR_OFFSET = 2 153 | UART_IIR_OFFSET = 2 154 | UART_LCR_OFFSET = 3 155 | UART_MCR_OFFSET = 4 156 | UART_LSR_OFFSET = 5 157 | UART_MSR_OFFSET = 6 158 | UART_SCR_OFFSET = 7 159 | UART_MDR1_OFFSET = 8 160 | 161 | PLATFORM_UART_ADDR = UART_BASE 162 | PLATFORM_UART_INPUT_FREQ = 10000000 163 | PLATFORM_UART_BAUDRATE = 115200 164 | 165 | BDIV = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE) 166 | 167 | _uart_init: 168 | li gp, UART_BASE 169 | 170 | sb x0, UART_IER_OFFSET(gp) 171 | 172 | li t0, 0x80 173 | sb t0, UART_LCR_OFFSET(gp) 174 | 175 | li t0, BDIV & 0xFF 176 | sb t0, UART_DLL_OFFSET(gp) 177 | 178 | li t0, (BDIV >> 8) & 0xFF 179 | sb t0, UART_DLM_OFFSET(gp) 180 | 181 | li t0, 0x03 182 | sb t0, UART_LCR_OFFSET(gp) 183 | 184 | li t0, 0x01 185 | sb t0, UART_FCR_OFFSET(gp) 186 | 187 | sb x0, UART_MCR_OFFSET(gp) 188 | 189 | lb t0, UART_LSR_OFFSET(gp) 190 | 191 | lb t0, UART_RBR_OFFSET(gp) 192 | 193 | sb x0, UART_SCR_OFFSET(gp) 194 | 195 | 196 | # print 197 | li t0, 'H' 198 | sb t0, UART_THR_OFFSET(gp) 199 | 200 | li t0, 'e' 201 | sb t0, UART_THR_OFFSET(gp) 202 | 203 | li t0, 'l' 204 | sb t0, UART_THR_OFFSET(gp) 205 | 206 | li t0, 'l' 207 | sb t0, UART_THR_OFFSET(gp) 208 | 209 | li t0, 'o' 210 | sb t0, UART_THR_OFFSET(gp) 211 | 212 | li t0, ',' 213 | sb t0, UART_THR_OFFSET(gp) 214 | 215 | li t0, ' ' 216 | sb t0, UART_THR_OFFSET(gp) 217 | 218 | li t0, 'R' 219 | sb t0, UART_THR_OFFSET(gp) 220 | 221 | li t0, 'I' 222 | sb t0, UART_THR_OFFSET(gp) 223 | 224 | li t0, 'S' 225 | sb t0, UART_THR_OFFSET(gp) 226 | 227 | li t0, 'C' 228 | sb t0, UART_THR_OFFSET(gp) 229 | 230 | li t0, '-' 231 | sb t0, UART_THR_OFFSET(gp) 232 | 233 | li t0, 'V' 234 | sb t0, UART_THR_OFFSET(gp) 235 | 236 | li t0, '!' 237 | sb t0, UART_THR_OFFSET(gp) 238 | 239 | li t0, '\n' 240 | sb t0, UART_THR_OFFSET(gp) 241 | 242 | loop: 243 | j loop 244 | 245 | ``` 246 | 247 | ### Assemble, link and extract flat binary 248 | 249 | To make it simple, we don't care the details of ELF file and how QEMU loads ELF file. We just extract the flat binary from ELF executable file and let QEMU loads the flat binary file. 250 | 251 | A flat binary file is just some machine instructions. The cpu knows how to interpret the instructions and execute them. 252 | 253 | #### Assemble assembly source code to relocatable ELF file 254 | 255 | ``` 256 | riscv64-linux-gnu-as firmware.s -o bin/firmware.o 257 | ``` 258 | 259 | #### Link the relocatable ELF file to a executable file 260 | 261 | *Linker Script: `firmware.ld`* 262 | ``` 263 | ENTRY(_uart_init) /* entry point */ 264 | 265 | . = 0x80000000; /* load address */ 266 | 267 | MEMORY {} /* default */ 268 | SECTIONS {} /* default */ 269 | ``` 270 | 271 | Link with: 272 | 273 | ``` 274 | riscv64-linux-gnu-ld -T firmware.ld bin/firmware.o -o bin/firmware 275 | ``` 276 | 277 | #### Extract flat binary from the executable file 278 | 279 | ``` 280 | riscv64-linux-gnu-objcopy -O binary -S bin/firmware bin/firmware.bin 281 | ``` 282 | 283 | ### Boot QEMU and load our own firmware as bios! 284 | 285 | ``` 286 | qemu-system-riscv64 -display none -machine virt -serial stdio -bios bin/firmware.bin 287 | ``` 288 | 289 | Then we can see `Hello, RISC-V!` being printed on the console! 290 | 291 | ## Possible Improvements 292 | 293 | ### Rewrite with C language 294 | - Configure stack at the beginning so that C code can run 295 | - Rewrite UART initialization code and printing code with C language 296 | 297 | **Done:** [04: Bare Metal Hello RISC-V (C Language Version)](/04-Bare-Metal-C-Language) 298 | 299 | ### Respect OpenSBI: load our program as `-kernel` 300 | OpenSBI is responsible for initializing a number of devices, including the UART. 301 | 302 | We can load our program as the next stage of OpenSBI, omit the initialization and use the UART device directly. 303 | 304 | **Some useful tips:** 305 | 306 | For *qemu-system-riscv64 -machine virt:* 307 | - QEMU loads kernel to `0x80200000` 308 | - OpenSBI jumps to `0x80200000` after initialization 309 | - QEMU accpets both ELF and flat binary as `-kernel` 310 | 311 | ## References 312 | ### About QEMU and *virt* Virtual Machine 313 | - [QEMU Manual - Boot Image or Kernel specific](https://www.qemu.org/docs/master/system/invocation.html#hxtool-8) 314 | - [QEMU Manual - RISC-V CPU firmware](https://www.qemu.org/docs/master/system/target-riscv.html#risc-v-cpu-firmware) 315 | - [QEMU Manual - RISC-V Generic Virtual Platform](https://www.qemu.org/docs/master/system/riscv/virt.html) 316 | - [QEMU RISC-V virt Machine Source Code](https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c) 317 | - [QEMU RISC-V Boot Helper Source Code](https://github.com/qemu/qemu/blob/master/hw/riscv/boot.c) 318 | - [RISC-V from scratch 2: Hardware layouts, linker scripts, and C runtimes](https://twilco.github.io/riscv-from-scratch/2019/04/27/riscv-from-scratch-2.html) 319 | 320 | ### About RISC-V Boot Flow 321 | - [An Introduction to RISC-V Boot Flow](https://riscv.org/wp-content/uploads/2019/12/Summit_bootflow.pdf) 322 | - [OpenSBI Platform Firmwares](https://github.com/riscv-software-src/opensbi/blob/master/docs/firmware/fw.md) 323 | - [QEMU RISC-V virt 平台分析](https://juejin.cn/post/6891922292075397127) 324 | 325 | ### About RISC-V Architecture 326 | - [The RISC-V Reader: An Open Architecture Atlas](http://riscvbook.com/) 327 | - [RISC-V Assembly Programmer's Manual](https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md) 328 | - [RISC-V ELF psABI Document](https://github.com/riscv-non-isa/riscv-elf-psabi-doc) 329 | 330 | ### About *virt*'s NS16550 UART Driver 331 | - [**OpenSBI UART-8250 Driver - Compatible with NS16550 of RISC-V virt**](https://github.com/riscv-software-src/opensbi/blob/master/lib/utils/serial/uart8250.c) 332 | - [PC16550D Universal Asynchronous Receiver/Transmitter With FIFOs 333 | ](https://media.digikey.com/pdf/Data%20Sheets/Texas%20Instruments%20PDFs/PC16550D.pdf) 334 | 335 | ### About ELF/Relocation/Linking/Toolchains 336 | - [Compilation and Linking Basics](/Appendix-00-Assembly-and-Linking/README.md) 337 | - [Executable and Linkable Format (ELF)](http://www.skyfree.org/linux/references/ELF_Format.pdf) 338 | - [as - The GNU assembler](https://ftp.gnu.org/old-gnu/Manuals/gas-2.9.1/html_node/as_toc.html) 339 | - [ld - The GNU Linker: Command Language](https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_chapter/ld_3.html#SEC5) 340 | - [objcopy - copy and translate object files](https://ftp.gnu.org/old-gnu/Manuals/binutils-2.12/html_node/binutils_5.html) -------------------------------------------------------------------------------- /03-Bare-Metal-Hello-RISC-V/firmware.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_uart_init) /* entry point */ 2 | 3 | . = 0x80000000; /* load address */ 4 | 5 | MEMORY {} /* default */ 6 | SECTIONS {} /* default */ -------------------------------------------------------------------------------- /03-Bare-Metal-Hello-RISC-V/firmware.s: -------------------------------------------------------------------------------- 1 | .section .text 2 | 3 | .global _uart_init 4 | 5 | UART_BASE = 0x10000000 6 | 7 | UART_RBR_OFFSET = 0 8 | UART_THR_OFFSET = 0 9 | UART_DLL_OFFSET = 0 10 | UART_IER_OFFSET = 1 11 | UART_DLM_OFFSET = 1 12 | UART_FCR_OFFSET = 2 13 | UART_IIR_OFFSET = 2 14 | UART_LCR_OFFSET = 3 15 | UART_MCR_OFFSET = 4 16 | UART_LSR_OFFSET = 5 17 | UART_MSR_OFFSET = 6 18 | UART_SCR_OFFSET = 7 19 | UART_MDR1_OFFSET = 8 20 | 21 | UART_LSR_FIFOE = 0x80 22 | UART_LSR_TEMT = 0x40 23 | UART_LSR_THRE = 0x20 24 | UART_LSR_BI = 0x10 25 | UART_LSR_FE = 0x08 26 | UART_LSR_PE = 0x04 27 | UART_LSR_OE = 0x02 28 | UART_LSR_DR = 0x01 29 | UART_LSR_BRK_ERROR_BITS = 0x1E 30 | 31 | PLATFORM_UART_ADDR = UART_BASE 32 | PLATFORM_UART_INPUT_FREQ = 10000000 33 | PLATFORM_UART_BAUDRATE = 115200 34 | 35 | BDIV = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE) 36 | 37 | _uart_init: 38 | li gp, UART_BASE 39 | 40 | sb x0, UART_IER_OFFSET(gp) 41 | 42 | li t0, 0x80 43 | sb t0, UART_LCR_OFFSET(gp) 44 | 45 | li t0, BDIV & 0xFF 46 | sb t0, UART_DLL_OFFSET(gp) 47 | 48 | li t0, (BDIV >> 8) & 0xFF 49 | sb t0, UART_DLM_OFFSET(gp) 50 | 51 | li t0, 0x03 52 | sb t0, UART_LCR_OFFSET(gp) 53 | 54 | li t0, 0x01 55 | sb t0, UART_FCR_OFFSET(gp) 56 | 57 | sb x0, UART_MCR_OFFSET(gp) 58 | 59 | lb t0, UART_LSR_OFFSET(gp) 60 | 61 | lb t0, UART_RBR_OFFSET(gp) 62 | 63 | sb x0, UART_SCR_OFFSET(gp) 64 | 65 | 66 | # print 67 | li t0, 'H' 68 | sb t0, UART_THR_OFFSET(gp) 69 | 70 | li t0, 'e' 71 | sb t0, UART_THR_OFFSET(gp) 72 | 73 | li t0, 'l' 74 | sb t0, UART_THR_OFFSET(gp) 75 | 76 | li t0, 'l' 77 | sb t0, UART_THR_OFFSET(gp) 78 | 79 | li t0, 'o' 80 | sb t0, UART_THR_OFFSET(gp) 81 | 82 | li t0, ',' 83 | sb t0, UART_THR_OFFSET(gp) 84 | 85 | li t0, ' ' 86 | sb t0, UART_THR_OFFSET(gp) 87 | 88 | li t0, 'R' 89 | sb t0, UART_THR_OFFSET(gp) 90 | 91 | li t0, 'I' 92 | sb t0, UART_THR_OFFSET(gp) 93 | 94 | li t0, 'S' 95 | sb t0, UART_THR_OFFSET(gp) 96 | 97 | li t0, 'C' 98 | sb t0, UART_THR_OFFSET(gp) 99 | 100 | li t0, '-' 101 | sb t0, UART_THR_OFFSET(gp) 102 | 103 | li t0, 'V' 104 | sb t0, UART_THR_OFFSET(gp) 105 | 106 | li t0, '!' 107 | sb t0, UART_THR_OFFSET(gp) 108 | 109 | li t0, '\n' 110 | sb t0, UART_THR_OFFSET(gp) 111 | 112 | loop: 113 | j loop 114 | -------------------------------------------------------------------------------- /03-Bare-Metal-Hello-RISC-V/firmware_minimal.s: -------------------------------------------------------------------------------- 1 | .section .text 2 | 3 | .global _uart_init 4 | 5 | UART_BASE = 0x10000000 6 | UART_THR_OFFSET = 0 7 | 8 | _uart_init: 9 | li gp, UART_BASE 10 | 11 | # print 12 | li t0, 'H' 13 | sb t0, UART_THR_OFFSET(gp) 14 | 15 | li t0, 'e' 16 | sb t0, UART_THR_OFFSET(gp) 17 | 18 | li t0, 'l' 19 | sb t0, UART_THR_OFFSET(gp) 20 | 21 | li t0, 'l' 22 | sb t0, UART_THR_OFFSET(gp) 23 | 24 | li t0, 'o' 25 | sb t0, UART_THR_OFFSET(gp) 26 | 27 | li t0, ',' 28 | sb t0, UART_THR_OFFSET(gp) 29 | 30 | li t0, ' ' 31 | sb t0, UART_THR_OFFSET(gp) 32 | 33 | li t0, 'R' 34 | sb t0, UART_THR_OFFSET(gp) 35 | 36 | li t0, 'I' 37 | sb t0, UART_THR_OFFSET(gp) 38 | 39 | li t0, 'S' 40 | sb t0, UART_THR_OFFSET(gp) 41 | 42 | li t0, 'C' 43 | sb t0, UART_THR_OFFSET(gp) 44 | 45 | li t0, '-' 46 | sb t0, UART_THR_OFFSET(gp) 47 | 48 | li t0, 'V' 49 | sb t0, UART_THR_OFFSET(gp) 50 | 51 | li t0, '!' 52 | sb t0, UART_THR_OFFSET(gp) 53 | 54 | li t0, '\n' 55 | sb t0, UART_THR_OFFSET(gp) 56 | 57 | loop: 58 | j loop 59 | -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE := riscv64-linux-gnu- 2 | CC := ${CROSS_COMPILE}gcc 3 | AS := ${CROSS_COMPILE}as 4 | LD := ${CROSS_COMPILE}ld 5 | OBJCOPY := ${CROSS_COMPILE}objcopy 6 | 7 | bin/firmware.bin: clang_env_init.s firmware.c clang_env_init.ld 8 | ${AS} clang_env_init.s -o bin/clang_env_init.o 9 | ${CC} -c firmware.c -o bin/firmware.o 10 | ${LD} -T clang_env_init.ld bin/clang_env_init.o bin/firmware.o -o bin/firmware 11 | ${OBJCOPY} -O binary -S bin/firmware bin/firmware.bin 12 | 13 | run: bin/firmware.bin 14 | qemu-system-riscv64 -display none -machine virt -serial stdio -bios bin/firmware.bin 15 | 16 | bin/echo.bin: clang_env_init.s firmware.c clang_env_init.ld 17 | ${AS} clang_env_init.s -o bin/clang_env_init.o 18 | ${CC} -c echo.c -o bin/echo.o 19 | ${LD} -T clang_env_init.ld bin/clang_env_init.o bin/echo.o -o bin/echo 20 | ${OBJCOPY} -O binary -S bin/echo bin/echo.bin 21 | 22 | echo: bin/echo.bin 23 | qemu-system-riscv64 -display none -machine virt -serial stdio -bios bin/echo.bin 24 | 25 | clean: 26 | rm -rf bin/ 27 | 28 | $(shell mkdir -p bin) -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/NOTE.md: -------------------------------------------------------------------------------- 1 | ## 疑问 2 | 3 | ```c 4 | char str[] = "xxxxxx"; 5 | ``` 6 | 7 | 的字符数量一旦超过 6,程序就无法运行,似乎会导致 mcause 显示出异常。 8 | 9 | 测试情况: 10 | 11 | str 不能是 char *,且不能字符数超过 6。 12 | 13 | 观察bin文件可以发现,一旦定义为 char *,或者长度超过 6,则会在bin文件中存储所定义的字符串,但是当以数组形式定义且长度小于等于6时,看不到定义的字符串,也许这时候直接以立即数形式存储了???? 14 | 15 | **已验证**:通过 `riscv64-linux-gnu-gcc -S firmware.c -o bin/firmware.s` 将 C 源代码转换成汇编语言代码,查看汇编语言代码,即可验证上述现象。 16 | 17 | **一种解决方案**:~~改用 elf~~,并在链接脚本中指定 `. = 0x80000000`,~~使 QEMU 直接加载 elf 格式的 firmware。或许是当str以数组形式定义且长度小于等于6时,产生的段比较复杂,objcopy产生的结果有问题???现在还不太清楚~~ 18 | 19 | 猜测可能是对齐问题? 20 | 21 | **新发现**:使用 ELF 不是必须的,重点是在链接脚本中指定 `. = 0x80000000`,且不要随便去控制 sections ,保持默认,无论是 bin 还是 elf,都可以正常输出长字符串!!! 22 | 23 | **猜想**:通过设置、不设置 `. = 0x80000000` 生成了两个 binary 文件,对比发现,不同之处还不少。**猜测,可能是最终的文件依赖加载地址生成了绝对地址引用,因此正确设置加载地址才能保证可以跑!** 24 | 25 | 26 | ## 如何控制目标文件的浮点类型(不知道是传参还是指令生成方面的),比如 soft float 27 | 28 | **mabi 的 f/d 后缀决定浮点处理方式** 29 | 30 | 参考资料:as man page 31 | 32 | ``` 33 | -march=ISA 34 | Select the base isa, as specified by ISA. For example -march=rv32ima. If this 35 | option and the architecture attributes aren't set, then assembler will check the 36 | default configure setting --with-arch=ISA. 37 | 38 | -mabi=ABI 39 | Selects the ABI, which is either "ilp32" or "lp64", optionally followed by "f", 40 | "d", or "q" to indicate single-precision, double-precision, or quad-precision 41 | floating-point calling convention, or none to indicate the soft-float calling 42 | convention. Also, "ilp32" can optionally be followed by "e" to indicate the RVE 43 | ABI, which is always soft-float. 44 | ``` 45 | 46 | [gcc RISC-V Options](https://gcc.gnu.org/onlinedocs/gcc-13.1.0/gcc/RISC-V-Options.html#RISC-V-Options-1) 47 | 48 | ``` 49 | -mabi=ABI-string 50 | 51 | Specify integer and floating-point calling convention. ABI-string contains two parts: the size of integer types and the registers used for floating-point types. For example ‘-march=rv64ifd -mabi=lp64d’ means that ‘long’ and pointers are 64-bit (implicitly defining ‘int’ to be 32-bit), and that floating-point values up to 64 bits wide are passed in F registers. Contrast this with ‘-march=rv64ifd -mabi=lp64f’, which still allows the compiler to generate code that uses the F and D extensions but only allows floating-point values up to 32 bits long to be passed in registers; or ‘-march=rv64ifd -mabi=lp64’, in which no floating-point arguments will be passed in registers. 52 | 53 | The default for this argument is system dependent, users who want a specific calling convention should specify one explicitly. The valid calling conventions are: ‘ilp32’, ‘ilp32f’, ‘ilp32d’, ‘lp64’, ‘lp64f’, and ‘lp64d’. Some calling conventions are impossible to implement on some ISAs: for example, ‘-march=rv32if -mabi=ilp32d’ is invalid because the ABI requires 64-bit values be passed in F registers, but F registers are only 32 bits wide. There is also the ‘ilp32e’ ABI that can only be used with the ‘rv32e’ architecture. This ABI is not well specified at present, and is subject to change. 54 | 55 | 56 | -march=ISA-string 57 | 58 | Generate code for given RISC-V ISA (e.g. ‘rv64im’). ISA strings must be lower-case. Examples include ‘rv64i’, ‘rv32g’, ‘rv32e’, and ‘rv32imaf’. 59 | 60 | When -march= is not specified, use the setting from -mcpu. 61 | 62 | If both -march and -mcpu= are not specified, the default for this argument is system dependent, users who want a specific architecture extensions should specify one explicitly. 63 | ``` -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/README.md: -------------------------------------------------------------------------------- 1 | # 04: Bare Metal Hello RISC-V - C Language Version 2 | 3 | ## Background 4 | 5 | ### C Program and Stack 6 | The most important thing we need to know is that **C programs rely _Stack_ to run**. 7 | 8 | - For RISC-V, the stack pointer register is `sp` or `x2`. 9 | - When a C function is called, it allocates memory for its running by **decreasing** `sp`. 10 | 11 | The default `sp` at startup is `0x0`, and the memory space below `0x0` is of course illegal. 12 | 13 | Therefore, we must set a reasonable value for `sp`, so that we can jump from our startup code written in assembly to a C function. 14 | 15 | ### C Program/RISC-V Calling Convention 16 | 17 | Setuping stack pointer is enough for jumping from our assembly startup code to a C function. 18 | However, to call an assembly code snippet from a C function and return it, we need to learn how a C function passes parameters and receives a return value. 19 | 20 | **Just a reminder, we can ignore this for now in this tutorial.** For deatils, refer to [RISC-V Calling Conventions](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc). 21 | 22 | ## Hello, RISC-V! 23 | 24 | ### Step.1 Setup Stack Pointer for C Program 25 | 26 | Our firmware will be loaded to `0x80000000`, we can give `sp` a proper value for C program. 27 | 28 | ```asm 29 | .global _clang_env_init 30 | 31 | .text 32 | _clang_env_init: 33 | li sp, 0x80200000 # give a proper memory address value to stack pointer 34 | j _uart_init # jump to a C function 35 | 36 | ``` 37 | 38 | ### Step.2 Write UART initialization code with **C Language** 39 | 40 | ```c 41 | typedef char u8; 42 | typedef short u16; 43 | typedef int u32; 44 | typedef long u64; 45 | 46 | #define UART_BASE 0x10000000 47 | 48 | #define UART_RBR_OFFSET 0 /* In: Recieve Buffer Register */ 49 | #define UART_THR_OFFSET 0 /* Out: Transmitter Holding Register */ 50 | #define UART_DLL_OFFSET 0 /* Out: Divisor Latch Low */ 51 | #define UART_IER_OFFSET 1 /* I/O: Interrupt Enable Register */ 52 | #define UART_DLM_OFFSET 1 /* Out: Divisor Latch High */ 53 | #define UART_FCR_OFFSET 2 /* Out: FIFO Control Register */ 54 | #define UART_IIR_OFFSET 2 /* I/O: Interrupt Identification Register */ 55 | #define UART_LCR_OFFSET 3 /* Out: Line Control Register */ 56 | #define UART_MCR_OFFSET 4 /* Out: Modem Control Register */ 57 | #define UART_LSR_OFFSET 5 /* In: Line Status Register */ 58 | #define UART_MSR_OFFSET 6 /* In: Modem Status Register */ 59 | #define UART_SCR_OFFSET 7 /* I/O: Scratch Register */ 60 | #define UART_MDR1_OFFSET 8 /* I/O: Mode Register */ 61 | 62 | #define PLATFORM_UART_INPUT_FREQ 10000000 63 | #define PLATFORM_UART_BAUDRATE 115200 64 | 65 | static u8 *uart_base_addr = (u8 *)UART_BASE; 66 | 67 | static inline void writeb(u8 val, volatile void *addr) 68 | { 69 | asm volatile("sb %0, 0(%1)" : : "r"(val), "r"(addr)); 70 | } 71 | 72 | static inline u8 readb(const volatile void *addr) 73 | { 74 | u8 val; 75 | asm volatile("lb %0, 0(%1)" : "=r"(val) : "r"(addr)); 76 | return val; 77 | } 78 | 79 | static void set_reg(u32 offset, u32 val) 80 | { 81 | writeb(val, uart_base_addr + offset); 82 | } 83 | 84 | static u32 get_reg(u32 offset) 85 | { 86 | return readb(uart_base_addr + offset); 87 | } 88 | 89 | static void uart_putc(u8 ch) { 90 | set_reg(UART_THR_OFFSET, ch); 91 | } 92 | 93 | static void uart_print(char *str) { 94 | while (*str) { 95 | uart_putc(*str++); 96 | } 97 | } 98 | 99 | int _uart_init() { 100 | u16 bdiv = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE); 101 | 102 | /* Disable all interrupts */ 103 | set_reg(UART_IER_OFFSET, 0x00); 104 | /* Enable DLAB */ 105 | set_reg(UART_LCR_OFFSET, 0x80); 106 | 107 | if (bdiv) { 108 | /* Set divisor low byte */ 109 | set_reg(UART_DLL_OFFSET, bdiv & 0xff); 110 | /* Set divisor high byte */ 111 | set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff); 112 | } 113 | 114 | /* 8 bits, no parity, one stop bit */ 115 | set_reg(UART_LCR_OFFSET, 0x03); 116 | /* Enable FIFO */ 117 | set_reg(UART_FCR_OFFSET, 0x01); 118 | /* No modem control DTR RTS */ 119 | set_reg(UART_MCR_OFFSET, 0x00); 120 | /* Clear line status */ 121 | get_reg(UART_LSR_OFFSET); 122 | /* Read receive buffer */ 123 | get_reg(UART_RBR_OFFSET); 124 | /* Set scratchpad */ 125 | set_reg(UART_SCR_OFFSET, 0x00); 126 | char *str = "Hello, RISC-V!\n"; 127 | uart_print(str); 128 | 129 | while (1) {} 130 | } 131 | ``` 132 | 133 | ### Step.3 Build and Run 134 | 135 | **IMPORTANT: Set Proper Location Counter in Linker Script!!!** 136 | 137 | ``` 138 | make run 139 | ``` 140 | 141 | ## Read from console: echo 142 | 143 | We can also read from console/uart, then print what user enters. 144 | 145 | The complete code is in [echo.c](echo.c). Run with `make echo`. 146 | 147 | ```c 148 | /* ... */ 149 | static int readble() { 150 | return get_reg(UART_LSR_OFFSET) & UART_LSR_DR; 151 | } 152 | 153 | /* ... */ 154 | 155 | int _uart_init() { 156 | /* ... */ 157 | uart_print("Hello, RISC-V!\n"); 158 | uart_print("echo > "); 159 | 160 | while (1) { 161 | if (readble()) { 162 | int ch = uart_getc(); 163 | uart_putc(ch); 164 | } 165 | } 166 | /* ...... */ 167 | } 168 | ``` 169 | 170 | ## References 171 | 172 | - [RISC-V Calling Conventions](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc) 173 | - [RISC-V Toolchain Conventions](https://github.com/riscv-non-isa/riscv-toolchain-conventions) 174 | - [RISC-V ELF psABI Document](https://github.com/riscv-non-isa/riscv-elf-psabi-doc) 175 | - [RISC-V Assembly Programmer's Manual](https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md) -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/clang_env_init.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_clang_env_init) 2 | 3 | MEMORY {} /* default */ 4 | 5 | . = 0x80000000; 6 | 7 | SECTIONS { 8 | } -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/clang_env_init.s: -------------------------------------------------------------------------------- 1 | .global _clang_env_init 2 | 3 | .text 4 | _clang_env_init: 5 | li sp, 0x80200000 # give a proper memory address value to stack pointer 6 | j _uart_init # jump to a C function 7 | -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/echo.c: -------------------------------------------------------------------------------- 1 | typedef char u8; 2 | typedef short u16; 3 | typedef int u32; 4 | typedef long u64; 5 | 6 | #define UART_BASE 0x10000000 7 | 8 | #define UART_RBR_OFFSET 0 /* In: Recieve Buffer Register */ 9 | #define UART_THR_OFFSET 0 /* Out: Transmitter Holding Register */ 10 | #define UART_DLL_OFFSET 0 /* Out: Divisor Latch Low */ 11 | #define UART_IER_OFFSET 1 /* I/O: Interrupt Enable Register */ 12 | #define UART_DLM_OFFSET 1 /* Out: Divisor Latch High */ 13 | #define UART_FCR_OFFSET 2 /* Out: FIFO Control Register */ 14 | #define UART_IIR_OFFSET 2 /* I/O: Interrupt Identification Register */ 15 | #define UART_LCR_OFFSET 3 /* Out: Line Control Register */ 16 | #define UART_MCR_OFFSET 4 /* Out: Modem Control Register */ 17 | #define UART_LSR_OFFSET 5 /* In: Line Status Register */ 18 | #define UART_MSR_OFFSET 6 /* In: Modem Status Register */ 19 | #define UART_SCR_OFFSET 7 /* I/O: Scratch Register */ 20 | #define UART_MDR1_OFFSET 8 /* I/O: Mode Register */ 21 | 22 | #define UART_LSR_FIFOE 0x80 /* Fifo error */ 23 | #define UART_LSR_TEMT 0x40 /* Transmitter empty */ 24 | #define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */ 25 | #define UART_LSR_BI 0x10 /* Break interrupt indicator */ 26 | #define UART_LSR_FE 0x08 /* Frame error indicator */ 27 | #define UART_LSR_PE 0x04 /* Parity error indicator */ 28 | #define UART_LSR_OE 0x02 /* Overrun error indicator */ 29 | #define UART_LSR_DR 0x01 /* Receiver data ready */ 30 | #define UART_LSR_BRK_ERROR_BITS 0x1E /* BI, FE, PE, OE bits */ 31 | 32 | #define PLATFORM_UART_INPUT_FREQ 10000000 33 | #define PLATFORM_UART_BAUDRATE 115200 34 | 35 | static u8 *uart_base_addr = (u8 *)UART_BASE; 36 | 37 | static inline void writeb(u8 val, volatile void *addr) 38 | { 39 | asm volatile("sb %0, 0(%1)" : : "r"(val), "r"(addr)); 40 | } 41 | 42 | static inline u8 readb(const volatile void *addr) 43 | { 44 | u8 val; 45 | asm volatile("lb %0, 0(%1)" : "=r"(val) : "r"(addr)); 46 | return val; 47 | } 48 | 49 | static void set_reg(u32 offset, u32 val) 50 | { 51 | writeb(val, uart_base_addr + offset); 52 | } 53 | 54 | static u32 get_reg(u32 offset) 55 | { 56 | return readb(uart_base_addr + offset); 57 | } 58 | 59 | static int writable() { 60 | return (get_reg(UART_LSR_OFFSET) & UART_LSR_THRE) == 0; 61 | } 62 | 63 | static int readble() { 64 | return get_reg(UART_LSR_OFFSET) & UART_LSR_DR; 65 | } 66 | 67 | static int uart_getc() { 68 | return get_reg(UART_RBR_OFFSET); 69 | } 70 | 71 | static void uart_putc(u8 ch) { 72 | set_reg(UART_THR_OFFSET, ch); 73 | } 74 | 75 | static void uart_print(char *str) { 76 | while (*str) { 77 | uart_putc(*str++); 78 | } 79 | } 80 | 81 | int _uart_init() { 82 | u16 bdiv = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE); 83 | 84 | /* Disable all interrupts */ 85 | set_reg(UART_IER_OFFSET, 0x00); 86 | /* Enable DLAB */ 87 | set_reg(UART_LCR_OFFSET, 0x80); 88 | 89 | if (bdiv) { 90 | /* Set divisor low byte */ 91 | set_reg(UART_DLL_OFFSET, bdiv & 0xff); 92 | /* Set divisor high byte */ 93 | set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff); 94 | } 95 | 96 | /* 8 bits, no parity, one stop bit */ 97 | set_reg(UART_LCR_OFFSET, 0x03); 98 | /* Enable FIFO */ 99 | set_reg(UART_FCR_OFFSET, 0x01); 100 | /* No modem control DTR RTS */ 101 | set_reg(UART_MCR_OFFSET, 0x00); 102 | /* Clear line status */ 103 | get_reg(UART_LSR_OFFSET); 104 | /* Read receive buffer */ 105 | get_reg(UART_RBR_OFFSET); 106 | /* Set scratchpad */ 107 | set_reg(UART_SCR_OFFSET, 0x00); 108 | 109 | uart_print("Hello, RISC-V!\n"); 110 | uart_print("echo > "); 111 | 112 | while (1) { 113 | if (readble()) { 114 | int ch = uart_getc(); 115 | uart_putc(ch); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /04-Bare-Metal-C-Language/firmware.c: -------------------------------------------------------------------------------- 1 | typedef char u8; 2 | typedef short u16; 3 | typedef int u32; 4 | typedef long u64; 5 | 6 | #define UART_BASE 0x10000000 7 | 8 | #define UART_RBR_OFFSET 0 /* In: Recieve Buffer Register */ 9 | #define UART_THR_OFFSET 0 /* Out: Transmitter Holding Register */ 10 | #define UART_DLL_OFFSET 0 /* Out: Divisor Latch Low */ 11 | #define UART_IER_OFFSET 1 /* I/O: Interrupt Enable Register */ 12 | #define UART_DLM_OFFSET 1 /* Out: Divisor Latch High */ 13 | #define UART_FCR_OFFSET 2 /* Out: FIFO Control Register */ 14 | #define UART_IIR_OFFSET 2 /* I/O: Interrupt Identification Register */ 15 | #define UART_LCR_OFFSET 3 /* Out: Line Control Register */ 16 | #define UART_MCR_OFFSET 4 /* Out: Modem Control Register */ 17 | #define UART_LSR_OFFSET 5 /* In: Line Status Register */ 18 | #define UART_MSR_OFFSET 6 /* In: Modem Status Register */ 19 | #define UART_SCR_OFFSET 7 /* I/O: Scratch Register */ 20 | #define UART_MDR1_OFFSET 8 /* I/O: Mode Register */ 21 | 22 | #define PLATFORM_UART_INPUT_FREQ 10000000 23 | #define PLATFORM_UART_BAUDRATE 115200 24 | 25 | static u8 *uart_base_addr = (u8 *)UART_BASE; 26 | 27 | static inline void writeb(u8 val, volatile void *addr) 28 | { 29 | asm volatile("sb %0, 0(%1)" : : "r"(val), "r"(addr)); 30 | } 31 | 32 | static inline u8 readb(const volatile void *addr) 33 | { 34 | u8 val; 35 | asm volatile("lb %0, 0(%1)" : "=r"(val) : "r"(addr)); 36 | return val; 37 | } 38 | 39 | static void set_reg(u32 offset, u32 val) 40 | { 41 | writeb(val, uart_base_addr + offset); 42 | } 43 | 44 | static u32 get_reg(u32 offset) 45 | { 46 | return readb(uart_base_addr + offset); 47 | } 48 | 49 | static void uart_putc(u8 ch) { 50 | set_reg(UART_THR_OFFSET, ch); 51 | } 52 | 53 | static void uart_print(char *str) { 54 | while (*str) { 55 | uart_putc(*str++); 56 | } 57 | } 58 | 59 | int _uart_init() { 60 | u16 bdiv = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE); 61 | 62 | /* Disable all interrupts */ 63 | set_reg(UART_IER_OFFSET, 0x00); 64 | /* Enable DLAB */ 65 | set_reg(UART_LCR_OFFSET, 0x80); 66 | 67 | if (bdiv) { 68 | /* Set divisor low byte */ 69 | set_reg(UART_DLL_OFFSET, bdiv & 0xff); 70 | /* Set divisor high byte */ 71 | set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff); 72 | } 73 | 74 | /* 8 bits, no parity, one stop bit */ 75 | set_reg(UART_LCR_OFFSET, 0x03); 76 | /* Enable FIFO */ 77 | set_reg(UART_FCR_OFFSET, 0x01); 78 | /* No modem control DTR RTS */ 79 | set_reg(UART_MCR_OFFSET, 0x00); 80 | /* Clear line status */ 81 | get_reg(UART_LSR_OFFSET); 82 | /* Read receive buffer */ 83 | get_reg(UART_RBR_OFFSET); 84 | /* Set scratchpad */ 85 | set_reg(UART_SCR_OFFSET, 0x00); 86 | char *str = "Hello, RISC-V!\n"; 87 | uart_print(str); 88 | 89 | while (1) {} 90 | } -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/.gdbinit: -------------------------------------------------------------------------------- 1 | set architecture riscv:rv64 2 | set disassemble-next-line on 3 | target extended-remote localhost:1234 4 | add-inferior 5 | inferior 2 6 | attach 2 7 | -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE := riscv64-linux-gnu- 2 | CC := ${CROSS_COMPILE}gcc 3 | AS := ${CROSS_COMPILE}as 4 | LD := ${CROSS_COMPILE}ld 5 | OBJCOPY := ${CROSS_COMPILE}objcopy 6 | 7 | CLANG_SRC := firmware.c 8 | ASM_SRC := clang_env_init.s 9 | HEADERS := riscv_asm.h riscv_types.h 10 | 11 | OBJECTS = $(addprefix bin/,$(ASM_SRC:.s=.asmo)) 12 | OBJECTS += $(addprefix bin/,$(CLANG_SRC:.c=.clango)) 13 | 14 | all: bin/firmware.bin 15 | 16 | bin/firmware.bin: ${OBJECTS} ${HEADERS} clang_env_init.ld 17 | ${LD} --no-relax -T clang_env_init.ld ${OBJECTS} -o bin/firmware 18 | ${OBJCOPY} -O binary -S bin/firmware bin/firmware.bin 19 | 20 | bin/%.asmo: %.s 21 | ${AS} -g $< -o $@ 22 | 23 | bin/%.clango: %.c ${HEADERS} 24 | ${CC} -ffreestanding -c -O0 -g $< -o $@ 25 | 26 | run: bin/firmware.bin 27 | qemu-system-riscv64 -machine sifive_u -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -display none 28 | 29 | debug: bin/firmware.bin 30 | qemu-system-riscv64 -machine sifive_u -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -S 31 | 32 | clean: 33 | rm -rf bin/ 34 | 35 | $(shell mkdir -p bin) -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/README.md: -------------------------------------------------------------------------------- 1 | # 04.1: Bare Metal Firmware for QEMU `sifive_u` 2 | 3 | ## Switch to `sifive_u` 4 | 5 | **Switch to `sifive_u`, because `virt` is not well documented!** 6 | 7 | **Attention: _sifive_u_ has multiple cores!** When I first wrote `Hello, RISC-V!` for _sifive_u_, I ignored its multi-core nature, resulting in duplicate and interleaved output. 8 | Refer to: 9 | - [QEMU - SiFive HiFive Unleashed (sifive_u)](https://qemu.readthedocs.io/en/latest/system/riscv/sifive_u.html) 10 | - [SiFive - HiFive Unleashed](https://www.sifive.com/boards/hifive-unleashed) 11 | - [SiFive FU540-C000 Manual v1p4](https://sifive.cdn.prismic.io/sifive/d3ed5cd0-6e74-46b2-a12d-72b06706513e_fu540-c000-manual-v1p4.pdf) 12 | 13 | For multi-cluster machines, unfortunately gdb does not by default handle multiple inferiors, and so you have to explicitly connect to them. 14 | Refer to [QEMU Debugging multicore machines](https://qemu.readthedocs.io/en/latest/system/gdb.html#debugging-multicore-machines) 15 | 16 | ## Bare Metal Echo Program 17 | 18 | The most important thing to know is: 19 | 20 | - sifive_u has multiple cores 21 | - **hart-0** only supports `M/U-mode`, which means that it does **not support virtual memory** 22 | - **hart-1** supports `M/S/U-mode`, which means it does **support virtual memory** 23 | 24 | Thus, we only use hart-1 and leave the other cores idle. 25 | 26 | Replace startup code with: 27 | ```asm 28 | .text 29 | firmware_entry: 30 | li t0, 0x1 31 | bne a0, t0, firmware_entry # loop if hartid is not 1 32 | li sp, 0x80200000 # setup stack pointer 33 | j firmware_main # jump to c entry 34 | ``` 35 | 36 | No more explanation. Just read the code. 37 | 38 | Run with: 39 | 40 | ```sh 41 | make run 42 | ``` 43 | 44 | ## GDB & QEMU: Debugging multicore machines 45 | 46 | Refer to [QEMU Debugging multicore machines](https://qemu.readthedocs.io/en/latest/system/gdb.html#debugging-multicore-machines) 47 | 48 | ## References 49 | - [QEMU - SiFive HiFive Unleashed (sifive_u)](https://qemu.readthedocs.io/en/latest/system/riscv/sifive_u.html) 50 | - [SiFive - HiFive Unleashed](https://www.sifive.com/boards/hifive-unleashed) 51 | - [SiFive FU540-C000 Manual v1p4](https://sifive.cdn.prismic.io/sifive/d3ed5cd0-6e74-46b2-a12d-72b06706513e_fu540-c000-manual-v1p4.pdf) 52 | -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/clang_env_init.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_clang_env_init) 2 | 3 | MEMORY {} /* default */ 4 | 5 | . = 0x80000000; 6 | 7 | SECTIONS { 8 | } -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/clang_env_init.s: -------------------------------------------------------------------------------- 1 | .global _clang_env_init 2 | 3 | .text 4 | _clang_env_init: 5 | li t1, 0x1 6 | bne a0, t1, _clang_env_init # loop if hartid is not 1 7 | li sp, 0x80200000 # setup stack pointer 8 | j firmware_main # jump to c entry 9 | -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/firmware.c: -------------------------------------------------------------------------------- 1 | #include "uart.h" 2 | 3 | void firmware_main() 4 | { 5 | uart_init(); 6 | uart_print("Hello, Sifive_u!\n"); 7 | uart_print("echo> "); 8 | while (1) 9 | { 10 | u32 rxdata = uart_getc(); 11 | if (!(rxdata & UART_RXFIFO_EMPTY)) 12 | { 13 | uart_putc(rxdata & 0xff); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/riscv_asm.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ASM_H 2 | #define _RISCV_ASM_H 3 | 4 | #include "riscv_types.h" 5 | 6 | #define readu8(addr) (*(const u8 *)(addr)) 7 | #define readu16(addr) (*(const u16 *)(addr)) 8 | #define readu32(addr) (*(const u32 *)(addr)) 9 | #define readu64(addr) (*(const u64 *)(addr)) 10 | 11 | #define writeu8(addr, val) (*(u8 *)(addr) = (val)) 12 | #define writeu16(addr, val) (*(u16 *)(addr) = (val)) 13 | #define writeu32(addr, val) (*(u32 *)(addr) = (val)) 14 | #define writeu64(addr, val) (*(u64 *)(addr) = (val)) 15 | 16 | #endif -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/riscv_types.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_TYPES_H 2 | #define _RISCV_TYPES_H 3 | 4 | typedef unsigned char u8; 5 | typedef unsigned short u16; 6 | typedef unsigned int u32; 7 | typedef unsigned long u64; 8 | 9 | typedef signed char i8; 10 | typedef signed short i16; 11 | typedef signed int i32; 12 | typedef signed long i64; 13 | 14 | #endif -------------------------------------------------------------------------------- /04.1-Bare-Metal-Sifive_u/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _UART_H 2 | #define _UART_H 3 | 4 | #include "riscv_asm.h" 5 | 6 | #define UART_BASE 0x10010000 7 | 8 | #define UART_TXDATA_OFFSET 0x00 9 | #define UART_RXDATA_OFFSET 0x04 10 | #define UART_TXCTRL_OFFSET 0x08 11 | #define UART_RXCTRL_OFFSET 0x0c 12 | #define UART_IE_OFFSET 0x10 13 | #define UART_IP_OFFSET 0x14 14 | #define UART_DIV_OFFSET 0x18 15 | 16 | #define UART_TXFIFO_FULL 0x80000000 17 | #define UART_RXFIFO_EMPTY 0x80000000 18 | #define UART_RXFIFO_DATA 0x000000ff 19 | #define UART_TXCTRL_TXEN 0x1 20 | #define UART_RXCTRL_RXEN 0x1 21 | 22 | #define PLATFORM_UART_INPUT_FREQ 10000000 23 | #define PLATFORM_UART_BAUDRATE 115200 24 | 25 | #define UART_REG(offset) (*(u32 *)(UART_BASE + offset)) 26 | 27 | static u8 *uart_base_addr = (u8 *)UART_BASE; 28 | 29 | static inline void set_reg(u32 offset, u32 val) 30 | { 31 | writeu32(uart_base_addr + offset, val); 32 | } 33 | 34 | static inline u32 get_reg(u32 offset) 35 | { 36 | return readu32(uart_base_addr + offset); 37 | } 38 | 39 | static inline void uart_putc(u32 ch) 40 | { 41 | while (get_reg(UART_TXDATA_OFFSET) & UART_TXFIFO_FULL) 42 | ; 43 | set_reg(UART_TXDATA_OFFSET, ch); 44 | } 45 | 46 | static inline u32 uart_getc() 47 | { 48 | return get_reg(UART_RXDATA_OFFSET); 49 | } 50 | 51 | static inline void uart_print(char *str) 52 | { 53 | while (*str) 54 | { 55 | uart_putc(*str++); 56 | } 57 | } 58 | 59 | static inline void uart_init() 60 | { 61 | /* Configure baudrate */ 62 | set_reg(UART_DIV_OFFSET, 0); 63 | 64 | /* Disable interrupts */ 65 | set_reg(UART_IE_OFFSET, 0); 66 | 67 | /* Enable TX */ 68 | set_reg(UART_TXCTRL_OFFSET, UART_TXCTRL_TXEN); 69 | 70 | /* Enable Rx */ 71 | set_reg(UART_RXCTRL_OFFSET, UART_RXCTRL_RXEN); 72 | } 73 | 74 | #endif -------------------------------------------------------------------------------- /05-Interrupt-Basics/README.md: -------------------------------------------------------------------------------- 1 | # 05: Interrupt Basics 2 | 3 | ## RISC-V Interrupt Basics 4 | ## Related Privilege Registers 5 | ## CLINT - Core Local Interrupt 6 | ## PLIC - Platform Level Interrupt Controller -------------------------------------------------------------------------------- /06-Timer-Interrupt/.gdbinit: -------------------------------------------------------------------------------- 1 | set architecture riscv:rv64 2 | set disassemble-next-line on 3 | target extended-remote localhost:1234 4 | -------------------------------------------------------------------------------- /06-Timer-Interrupt/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /06-Timer-Interrupt/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE := riscv64-linux-gnu- 2 | CC := ${CROSS_COMPILE}gcc 3 | AS := ${CROSS_COMPILE}as 4 | LD := ${CROSS_COMPILE}ld 5 | OBJCOPY := ${CROSS_COMPILE}objcopy 6 | 7 | CLANG_SRC := firmware.c trap_handler.c 8 | ASM_SRC := clang_env_init.s trap.s 9 | HEADERS := riscv_arch.h riscv_asm.h riscv_cpu.h uart.h timer.h interrupts.h 10 | 11 | OBJECTS = $(addprefix bin/,$(ASM_SRC:.s=.asmo)) 12 | OBJECTS += $(addprefix bin/,$(CLANG_SRC:.c=.clango)) 13 | 14 | all: bin/firmware.bin 15 | 16 | bin/firmware.bin: ${OBJECTS} ${HEADERS} clang_env_init.ld 17 | ${LD} --no-relax -T clang_env_init.ld ${OBJECTS} -o bin/firmware 18 | ${OBJCOPY} -O binary -S bin/firmware bin/firmware.bin 19 | 20 | bin/%.asmo: %.s 21 | ${AS} -g $< -o $@ 22 | 23 | bin/%.clango: %.c ${HEADERS} 24 | ${CC} -ffreestanding -c -O0 -g $< -o $@ 25 | 26 | run: bin/firmware.bin 27 | qemu-system-riscv64 -machine virt -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -display none 28 | 29 | debug: bin/firmware.bin 30 | qemu-system-riscv64 -machine virt -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -S 31 | 32 | clean: 33 | rm -rf bin/ 34 | 35 | $(shell mkdir -p bin) -------------------------------------------------------------------------------- /06-Timer-Interrupt/README.md: -------------------------------------------------------------------------------- 1 | # 06: Timer Interrupt 2 | 3 | **The code has been completed, the tutorial has not yet had time to write.** 4 | 5 | ## Run 6 | 7 | ```sh 8 | make run 9 | ``` 10 | -------------------------------------------------------------------------------- /06-Timer-Interrupt/clang_env_init.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_clang_env_init) 2 | 3 | MEMORY {} /* default */ 4 | 5 | . = 0x80000000; 6 | 7 | SECTIONS { 8 | } -------------------------------------------------------------------------------- /06-Timer-Interrupt/clang_env_init.s: -------------------------------------------------------------------------------- 1 | .global _clang_env_init 2 | 3 | .text 4 | _clang_env_init: 5 | bne a0, x0, _clang_env_init # loop if hartid is not 0 6 | li sp, 0x80200000 # setup stack pointer 7 | j firmware_main # jump to c entry 8 | -------------------------------------------------------------------------------- /06-Timer-Interrupt/firmware.c: -------------------------------------------------------------------------------- 1 | #include "riscv_asm.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "interrupts.h" 5 | 6 | extern void trap_entry(); 7 | 8 | void firmware_main() 9 | { 10 | uart_init(); 11 | 12 | // setup timer timout 13 | uart_printf("[firmware_main] default MTIMECMP_0 is %d\n", mtimecmp_0()); 14 | set_timeout(10000000); 15 | 16 | // setup M-mode trap vector 17 | csrw_mtvec((u64)trap_entry); 18 | // enable M-mode timer interrupt 19 | csrw_mie(MIE_MTIE); 20 | // enable MIE in mstatus 21 | csrs_mstatus(MSTAUTS_MIE); 22 | 23 | while (1) 24 | { 25 | } 26 | } -------------------------------------------------------------------------------- /06-Timer-Interrupt/interrupts.h: -------------------------------------------------------------------------------- 1 | #ifndef _INTERRUPTS_H 2 | #define _INTERRUPTS_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | #define MSTAUTS_MIE (0x1L << 3) 7 | 8 | #define MIE_MTIE (0x1L << 7) 9 | 10 | #define MCAUSE_INTR_M_TIMER ((0x1L << (MACHINE_BITS - 1)) | 7) 11 | 12 | #endif /* _INTERRUPTS_H */ -------------------------------------------------------------------------------- /06-Timer-Interrupt/riscv_arch.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ARCH_H 2 | #define _RISCV_ARCH_H 3 | 4 | // machine bits 5 | #define MACHINE_BITS 64 6 | #define BITS_PER_LONG MACHINE_BITS 7 | 8 | // bool 9 | #define bool _Bool 10 | #define true 1 11 | #define false 0 12 | 13 | // unsigned types 14 | typedef unsigned char u8; 15 | typedef unsigned short u16; 16 | typedef unsigned int u32; 17 | typedef unsigned long u64; 18 | 19 | // signed types 20 | typedef signed char i8; 21 | typedef signed short i16; 22 | typedef signed int i32; 23 | typedef signed long i64; 24 | 25 | // size_t 26 | typedef u64 size_t; 27 | 28 | #endif /* _RISCV_ARCH_H */ -------------------------------------------------------------------------------- /06-Timer-Interrupt/riscv_asm.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ASM_H 2 | #define _RISCV_ASM_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | #define readu8(addr) (*(const u8 *)(addr)) 7 | #define readu16(addr) (*(const u16 *)(addr)) 8 | #define readu32(addr) (*(const u32 *)(addr)) 9 | #define readu64(addr) (*(const u64 *)(addr)) 10 | 11 | #define writeu8(addr, val) (*(u8 *)(addr) = (val)) 12 | #define writeu16(addr, val) (*(u16 *)(addr) = (val)) 13 | #define writeu32(addr, val) (*(u32 *)(addr) = (val)) 14 | #define writeu64(addr, val) (*(u64 *)(addr) = (val)) 15 | 16 | static inline void csrw_mtvec(const volatile u64 val) 17 | { 18 | asm volatile("csrw mtvec, %0" 19 | : 20 | : "r"(val)); 21 | } 22 | 23 | static inline void csrw_mie(const volatile u64 val) 24 | { 25 | asm volatile("csrw mie, %0" 26 | : 27 | : "r"(val)); 28 | } 29 | 30 | static inline u64 csrr_mstatus() 31 | { 32 | volatile u64 val; 33 | asm volatile("csrr %0, mstatus" 34 | : "=r"(val) 35 | :); 36 | return val; 37 | } 38 | 39 | static inline void csrw_mstatus(const volatile u64 val) 40 | { 41 | asm volatile("csrw mstatus, %0" 42 | : 43 | : "r"(val)); 44 | } 45 | 46 | static inline void csrs_mstatus(const volatile u64 val) 47 | { 48 | asm volatile("csrs mstatus, %0" 49 | : 50 | : "r"(val)); 51 | } 52 | 53 | static inline void csrc_mstatus(const volatile u64 val) 54 | { 55 | asm volatile("csrc mstatus, %0" 56 | : 57 | : "r"(val)); 58 | } 59 | 60 | static inline u64 csrr_mcause() 61 | { 62 | volatile u64 val; 63 | asm volatile("csrr %0, mcause" 64 | : "=r"(val) 65 | :); 66 | return val; 67 | } 68 | 69 | static inline u64 csrr_mepc() 70 | { 71 | volatile u64 val; 72 | asm volatile("csrr %0, mepc" 73 | : "=r"(val) 74 | :); 75 | return val; 76 | } 77 | 78 | static inline void ecall() 79 | { 80 | asm volatile("ecall" 81 | : 82 | :); 83 | } 84 | 85 | #endif -------------------------------------------------------------------------------- /06-Timer-Interrupt/riscv_cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef _CPU_H 2 | #define _CPU_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | struct cpu 7 | { 8 | u64 x0; // zero 9 | u64 x1; // ra 10 | u64 x2; // sp 11 | u64 x3; // gp 12 | u64 x4; // tp 13 | u64 x5; // t0 14 | u64 x6; // t1 15 | u64 x7; // t2 16 | u64 x8; // s0/fp 17 | u64 x9; // s1 18 | u64 x10; // a0 19 | u64 x11; // a1 20 | u64 x12; // a2 21 | u64 x13; // a3 22 | u64 x14; // a4 23 | u64 x15; // a5 24 | u64 x16; // a6 25 | u64 x17; // a7 26 | u64 x18; // s2 27 | u64 x19; // s3 28 | u64 x20; // s4 29 | u64 x21; // s5 30 | u64 x22; // s6 31 | u64 x23; // s7 32 | u64 x24; // s8 33 | u64 x25; // s9 34 | u64 x26; // s10 35 | u64 x27; // s11 36 | u64 x28; // t3 37 | u64 x29; // t4 38 | u64 x30; // t5 39 | u64 x31; // t6 40 | 41 | u64 pc; // pc 42 | } __attribute__((packed)); 43 | 44 | #endif /* _CPU_H */ -------------------------------------------------------------------------------- /06-Timer-Interrupt/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef _TIMER_H 2 | #define _TIMER_H 3 | 4 | #include "riscv_asm.h" 5 | 6 | #define MTIME 0x200bff8 7 | #define MTIMECMP_0 0x2004000 8 | 9 | static inline u64 mtime() 10 | { 11 | return readu64(MTIME); 12 | } 13 | 14 | static inline u64 mtimecmp_0() 15 | { 16 | return readu64(MTIMECMP_0); 17 | } 18 | 19 | static inline u64 set_timeout(u64 timeout) 20 | { 21 | writeu64(MTIMECMP_0, mtime() + timeout); 22 | } 23 | 24 | #endif -------------------------------------------------------------------------------- /06-Timer-Interrupt/trap.s: -------------------------------------------------------------------------------- 1 | .equ REGSZ, 8 2 | .global trap_entry 3 | 4 | .text 5 | 6 | trap_entry: 7 | 8 | # swap x5/mscratch 9 | csrrw x5, mscratch, x5 10 | # use x5 as cpu state base address register 11 | la x5, trap_cpu 12 | # save general purpose registers 13 | # x0 ~ x4 14 | sd x0, (0 * REGSZ)(x5) 15 | sd x1, (1 * REGSZ)(x5) 16 | sd x2, (2 * REGSZ)(x5) 17 | sd x3, (3 * REGSZ)(x5) 18 | sd x4, (4 * REGSZ)(x5) 19 | # save origin x5 by x1, which has been saved 20 | csrr x1, mscratch 21 | sd x1, (5 * REGSZ)(x5) 22 | # x6 ~ x31 23 | sd x6, (6 * REGSZ)(x5) 24 | sd x7, (7 * REGSZ)(x5) 25 | sd x8, (8 * REGSZ)(x5) 26 | sd x9, (9 * REGSZ)(x5) 27 | sd x10, (10 * REGSZ)(x5) 28 | sd x11, (11 * REGSZ)(x5) 29 | sd x12, (12 * REGSZ)(x5) 30 | sd x13, (13 * REGSZ)(x5) 31 | sd x14, (14 * REGSZ)(x5) 32 | sd x15, (15 * REGSZ)(x5) 33 | sd x16, (16 * REGSZ)(x5) 34 | sd x17, (17 * REGSZ)(x5) 35 | sd x18, (18 * REGSZ)(x5) 36 | sd x19, (19 * REGSZ)(x5) 37 | sd x20, (20 * REGSZ)(x5) 38 | sd x21, (21 * REGSZ)(x5) 39 | sd x22, (22 * REGSZ)(x5) 40 | sd x23, (23 * REGSZ)(x5) 41 | sd x24, (24 * REGSZ)(x5) 42 | sd x25, (25 * REGSZ)(x5) 43 | sd x26, (26 * REGSZ)(x5) 44 | sd x27, (27 * REGSZ)(x5) 45 | sd x28, (28 * REGSZ)(x5) 46 | sd x29, (29 * REGSZ)(x5) 47 | sd x30, (30 * REGSZ)(x5) 48 | sd x31, (31 * REGSZ)(x5) 49 | # save privilege registers 50 | # save mepc by x1, which has been saved 51 | csrr x1, mepc 52 | sd x1, (32 * REGSZ)(x5) 53 | 54 | # call trap_handler 55 | # Need set stack pointer? 56 | la t0, trap_stack_top 57 | ld sp, 0(t0) 58 | call trap_handler 59 | 60 | # use x5 as cpu state base address register 61 | la x5, trap_cpu 62 | # restore privilege registers 63 | # restore mepc by x1, which will be restored later 64 | ld x1, (32 * REGSZ)(x5) 65 | csrw mepc, x1 66 | # restore general purpose registers 67 | # x0 ~ x4 68 | ld x0, (0 * REGSZ)(x5) 69 | ld x1, (1 * REGSZ)(x5) 70 | ld x2, (2 * REGSZ)(x5) 71 | ld x3, (3 * REGSZ)(x5) 72 | ld x4, (4 * REGSZ)(x5) 73 | # postpone the restoration of x5 74 | # because it is being used as the base address register 75 | # x6 ~ x31 76 | ld x6, (6 * REGSZ)(x5) 77 | ld x7, (7 * REGSZ)(x5) 78 | ld x8, (8 * REGSZ)(x5) 79 | ld x9, (9 * REGSZ)(x5) 80 | ld x10, (10 * REGSZ)(x5) 81 | ld x11, (11 * REGSZ)(x5) 82 | ld x12, (12 * REGSZ)(x5) 83 | ld x13, (13 * REGSZ)(x5) 84 | ld x14, (14 * REGSZ)(x5) 85 | ld x15, (15 * REGSZ)(x5) 86 | ld x16, (16 * REGSZ)(x5) 87 | ld x17, (17 * REGSZ)(x5) 88 | ld x18, (18 * REGSZ)(x5) 89 | ld x19, (19 * REGSZ)(x5) 90 | ld x20, (20 * REGSZ)(x5) 91 | ld x21, (21 * REGSZ)(x5) 92 | ld x22, (22 * REGSZ)(x5) 93 | ld x23, (23 * REGSZ)(x5) 94 | ld x24, (24 * REGSZ)(x5) 95 | ld x25, (25 * REGSZ)(x5) 96 | ld x26, (26 * REGSZ)(x5) 97 | ld x27, (27 * REGSZ)(x5) 98 | ld x28, (28 * REGSZ)(x5) 99 | ld x29, (29 * REGSZ)(x5) 100 | ld x30, (30 * REGSZ)(x5) 101 | ld x31, (31 * REGSZ)(x5) 102 | # x5 103 | ld x5, (6 * REGSZ)(x5) 104 | 105 | mret 106 | # j trap_entry 107 | -------------------------------------------------------------------------------- /06-Timer-Interrupt/trap_handler.c: -------------------------------------------------------------------------------- 1 | #include "riscv_cpu.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "interrupts.h" 5 | 6 | struct cpu trap_cpu; 7 | u8 trap_stack[1 << 20]; 8 | void *trap_stack_top = &trap_stack[sizeof(trap_stack) - 1]; 9 | 10 | void trap_handler() 11 | { 12 | u64 mcause = csrr_mcause(); 13 | switch (mcause) 14 | { 15 | case MCAUSE_INTR_M_TIMER: 16 | { 17 | uart_printf("[Trap - M-mode Timer] mcause: 0x%lx, current ticks: %d\n", mcause, mtime()); 18 | set_timeout(10000000); 19 | break; 20 | } 21 | 22 | default: 23 | { 24 | uart_printf("[Trap - Default] mcause: 0x%lX\n", mcause); 25 | break; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /06-Timer-Interrupt/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _UART_H 2 | #define _UART_H 3 | 4 | #include "riscv_arch.h" 5 | #include "riscv_asm.h" 6 | #include 7 | 8 | #define UART_BASE 0x10000000 9 | 10 | #define UART_RBR_OFFSET 0 /* In: Recieve Buffer Register */ 11 | #define UART_THR_OFFSET 0 /* Out: Transmitter Holding Register */ 12 | #define UART_DLL_OFFSET 0 /* Out: Divisor Latch Low */ 13 | #define UART_IER_OFFSET 1 /* I/O: Interrupt Enable Register */ 14 | #define UART_DLM_OFFSET 1 /* Out: Divisor Latch High */ 15 | #define UART_FCR_OFFSET 2 /* Out: FIFO Control Register */ 16 | #define UART_IIR_OFFSET 2 /* I/O: Interrupt Identification Register */ 17 | #define UART_LCR_OFFSET 3 /* Out: Line Control Register */ 18 | #define UART_MCR_OFFSET 4 /* Out: Modem Control Register */ 19 | #define UART_LSR_OFFSET 5 /* In: Line Status Register */ 20 | #define UART_MSR_OFFSET 6 /* In: Modem Status Register */ 21 | #define UART_SCR_OFFSET 7 /* I/O: Scratch Register */ 22 | #define UART_MDR1_OFFSET 8 /* I/O: Mode Register */ 23 | 24 | #define PLATFORM_UART_INPUT_FREQ 10000000 25 | #define PLATFORM_UART_BAUDRATE 115200 26 | 27 | static u8 *uart_base_addr = (u8 *)UART_BASE; 28 | 29 | static void set_reg(u32 offset, u32 val) 30 | { 31 | writeu8(uart_base_addr + offset, val); 32 | } 33 | 34 | static u32 get_reg(u32 offset) 35 | { 36 | return readu8(uart_base_addr + offset); 37 | } 38 | 39 | static void uart_putc(u8 ch) 40 | { 41 | set_reg(UART_THR_OFFSET, ch); 42 | } 43 | 44 | static void uart_print(char *str) 45 | { 46 | while (*str) 47 | { 48 | uart_putc(*str++); 49 | } 50 | } 51 | 52 | static void uart_print_digit(unsigned long val, unsigned long base, bool uppercase) 53 | { 54 | const char *digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; 55 | 56 | if (val < base) 57 | { 58 | uart_putc(digits[val]); 59 | } 60 | else 61 | { 62 | uart_print_digit(val / base, base, uppercase); 63 | uart_putc(digits[val % base]); 64 | } 65 | } 66 | 67 | static inline void uart_print_int(int val, int base) 68 | { 69 | if (val < 0) 70 | { 71 | uart_putc('-'); 72 | val = -val; 73 | } 74 | uart_print_digit(val, base, false); 75 | } 76 | 77 | static inline void uart_print_uint(unsigned int val, int base) 78 | { 79 | uart_print_digit(val, base, false); 80 | } 81 | 82 | static inline void uart_print_uint_upper(unsigned int val, int base) 83 | { 84 | uart_print_digit(val, base, true); 85 | } 86 | 87 | static inline void uart_print_long(long val, int base) 88 | { 89 | if (val < 0) 90 | { 91 | uart_putc('-'); 92 | val = -val; 93 | } 94 | uart_print_digit(val, base, false); 95 | } 96 | 97 | static inline void uart_print_ulong(unsigned long val, int base) 98 | { 99 | uart_print_digit(val, base, false); 100 | } 101 | 102 | static inline void uart_print_ulong_upper(unsigned long val, int base) 103 | { 104 | uart_print_digit(val, base, true); 105 | } 106 | 107 | static inline void uart_printf(const char *format, ...) 108 | { 109 | va_list args; 110 | va_start(args, format); 111 | 112 | while (*format) 113 | { 114 | if (*format == '%') 115 | { 116 | format++; 117 | switch (*format) 118 | { 119 | case 'c': 120 | { 121 | char c = (char)va_arg(args, unsigned int); 122 | uart_putc(c); 123 | break; 124 | } 125 | case 's': 126 | { 127 | char *str = va_arg(args, char *); 128 | uart_print(str); 129 | break; 130 | } 131 | case 'd': 132 | { 133 | int val = va_arg(args, int); 134 | uart_print_int(val, 10); 135 | break; 136 | } 137 | case 'u': 138 | { 139 | unsigned int val = va_arg(args, unsigned int); 140 | uart_print_uint(val, 10); 141 | break; 142 | } 143 | case 'x': 144 | { 145 | unsigned int val = va_arg(args, unsigned int); 146 | uart_print_uint(val, 16); 147 | break; 148 | } 149 | case 'X': 150 | { 151 | unsigned int val = va_arg(args, unsigned int); 152 | uart_print_uint_upper(val, 16); 153 | break; 154 | } 155 | case 'l': 156 | { 157 | format++; 158 | switch (*format) 159 | { 160 | case 'd': 161 | { 162 | long val = va_arg(args, long); 163 | uart_print_long(val, 10); 164 | break; 165 | } 166 | case 'u': 167 | { 168 | unsigned long val = va_arg(args, unsigned long); 169 | uart_print_ulong(val, 10); 170 | break; 171 | } 172 | case 'x': 173 | { 174 | unsigned long val = va_arg(args, unsigned long); 175 | uart_print_ulong(val, 16); 176 | break; 177 | } 178 | case 'X': 179 | { 180 | unsigned long val = va_arg(args, unsigned long); 181 | uart_print_ulong_upper(val, 16); 182 | break; 183 | } 184 | default: 185 | uart_putc(*format); 186 | break; 187 | } 188 | break; 189 | } 190 | default: 191 | uart_putc(*format); 192 | break; 193 | } 194 | } 195 | else 196 | { 197 | uart_putc(*format); 198 | } 199 | format++; 200 | } 201 | 202 | va_end(args); 203 | } 204 | 205 | static inline void uart_init() 206 | { 207 | u16 bdiv = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE); 208 | 209 | /* Disable all interrupts */ 210 | set_reg(UART_IER_OFFSET, 0x00); 211 | /* Enable DLAB */ 212 | set_reg(UART_LCR_OFFSET, 0x80); 213 | 214 | if (bdiv) 215 | { 216 | /* Set divisor low byte */ 217 | set_reg(UART_DLL_OFFSET, bdiv & 0xff); 218 | /* Set divisor high byte */ 219 | set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff); 220 | } 221 | 222 | /* 8 bits, no parity, one stop bit */ 223 | set_reg(UART_LCR_OFFSET, 0x03); 224 | /* Enable FIFO */ 225 | set_reg(UART_FCR_OFFSET, 0x01); 226 | /* No modem control DTR RTS */ 227 | set_reg(UART_MCR_OFFSET, 0x00); 228 | /* Clear line status */ 229 | get_reg(UART_LSR_OFFSET); 230 | /* Read receive buffer */ 231 | get_reg(UART_RBR_OFFSET); 232 | /* Set scratchpad */ 233 | set_reg(UART_SCR_OFFSET, 0x00); 234 | } 235 | 236 | #endif -------------------------------------------------------------------------------- /06-Timer-Interrupt/virt.dtb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peiyuanix/riscv-os/051424636e720090309040f524f117d58510177b/06-Timer-Interrupt/virt.dtb -------------------------------------------------------------------------------- /06-Timer-Interrupt/virt.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | 3 | / { 4 | #address-cells = <0x02>; 5 | #size-cells = <0x02>; 6 | compatible = "riscv-virtio"; 7 | model = "riscv-virtio,qemu"; 8 | 9 | chosen { 10 | bootargs = [00]; 11 | stdout-path = "/soc/uart@10000000"; 12 | }; 13 | 14 | memory@80000000 { 15 | device_type = "memory"; 16 | reg = <0x00 0x80000000 0x00 0x8000000>; 17 | }; 18 | 19 | cpus { 20 | #address-cells = <0x01>; 21 | #size-cells = <0x00>; 22 | timebase-frequency = <0x989680>; 23 | 24 | cpu@0 { 25 | phandle = <0x01>; 26 | device_type = "cpu"; 27 | reg = <0x00>; 28 | status = "okay"; 29 | compatible = "riscv"; 30 | riscv,isa = "rv64imafdcsu"; 31 | mmu-type = "riscv,sv48"; 32 | 33 | interrupt-controller { 34 | #interrupt-cells = <0x01>; 35 | interrupt-controller; 36 | compatible = "riscv,cpu-intc"; 37 | phandle = <0x02>; 38 | }; 39 | }; 40 | 41 | cpu-map { 42 | 43 | cluster0 { 44 | 45 | core0 { 46 | cpu = <0x01>; 47 | }; 48 | }; 49 | }; 50 | }; 51 | 52 | soc { 53 | #address-cells = <0x02>; 54 | #size-cells = <0x02>; 55 | compatible = "simple-bus"; 56 | ranges; 57 | 58 | flash@20000000 { 59 | bank-width = <0x04>; 60 | reg = <0x00 0x20000000 0x00 0x2000000 0x00 0x22000000 0x00 0x2000000>; 61 | compatible = "cfi-flash"; 62 | }; 63 | 64 | rtc@101000 { 65 | interrupts = <0x0b>; 66 | interrupt-parent = <0x03>; 67 | reg = <0x00 0x101000 0x00 0x1000>; 68 | compatible = "google,goldfish-rtc"; 69 | }; 70 | 71 | uart@10000000 { 72 | interrupts = <0x0a>; 73 | interrupt-parent = <0x03>; 74 | clock-frequency = <0x384000>; 75 | reg = <0x00 0x10000000 0x00 0x100>; 76 | compatible = "ns16550a"; 77 | }; 78 | 79 | poweroff { 80 | value = <0x5555>; 81 | offset = <0x00>; 82 | regmap = <0x04>; 83 | compatible = "syscon-poweroff"; 84 | }; 85 | 86 | reboot { 87 | value = <0x7777>; 88 | offset = <0x00>; 89 | regmap = <0x04>; 90 | compatible = "syscon-reboot"; 91 | }; 92 | 93 | test@100000 { 94 | phandle = <0x04>; 95 | reg = <0x00 0x100000 0x00 0x1000>; 96 | compatible = "sifive,test1\0sifive,test0\0syscon"; 97 | }; 98 | 99 | pci@30000000 { 100 | interrupt-map-mask = <0x1800 0x00 0x00 0x07>; 101 | interrupt-map = <0x00 0x00 0x00 0x01 0x03 0x20 0x00 0x00 0x00 0x02 0x03 0x21 0x00 0x00 0x00 0x03 0x03 0x22 0x00 0x00 0x00 0x04 0x03 0x23 0x800 0x00 0x00 0x01 0x03 0x21 0x800 0x00 0x00 0x02 0x03 0x22 0x800 0x00 0x00 0x03 0x03 0x23 0x800 0x00 0x00 0x04 0x03 0x20 0x1000 0x00 0x00 0x01 0x03 0x22 0x1000 0x00 0x00 0x02 0x03 0x23 0x1000 0x00 0x00 0x03 0x03 0x20 0x1000 0x00 0x00 0x04 0x03 0x21 0x1800 0x00 0x00 0x01 0x03 0x23 0x1800 0x00 0x00 0x02 0x03 0x20 0x1800 0x00 0x00 0x03 0x03 0x21 0x1800 0x00 0x00 0x04 0x03 0x22>; 102 | ranges = <0x1000000 0x00 0x00 0x00 0x3000000 0x00 0x10000 0x2000000 0x00 0x40000000 0x00 0x40000000 0x00 0x40000000>; 103 | reg = <0x00 0x30000000 0x00 0x10000000>; 104 | dma-coherent; 105 | bus-range = <0x00 0xff>; 106 | linux,pci-domain = <0x00>; 107 | device_type = "pci"; 108 | compatible = "pci-host-ecam-generic"; 109 | #size-cells = <0x02>; 110 | #interrupt-cells = <0x01>; 111 | #address-cells = <0x03>; 112 | }; 113 | 114 | virtio_mmio@10008000 { 115 | interrupts = <0x08>; 116 | interrupt-parent = <0x03>; 117 | reg = <0x00 0x10008000 0x00 0x1000>; 118 | compatible = "virtio,mmio"; 119 | }; 120 | 121 | virtio_mmio@10007000 { 122 | interrupts = <0x07>; 123 | interrupt-parent = <0x03>; 124 | reg = <0x00 0x10007000 0x00 0x1000>; 125 | compatible = "virtio,mmio"; 126 | }; 127 | 128 | virtio_mmio@10006000 { 129 | interrupts = <0x06>; 130 | interrupt-parent = <0x03>; 131 | reg = <0x00 0x10006000 0x00 0x1000>; 132 | compatible = "virtio,mmio"; 133 | }; 134 | 135 | virtio_mmio@10005000 { 136 | interrupts = <0x05>; 137 | interrupt-parent = <0x03>; 138 | reg = <0x00 0x10005000 0x00 0x1000>; 139 | compatible = "virtio,mmio"; 140 | }; 141 | 142 | virtio_mmio@10004000 { 143 | interrupts = <0x04>; 144 | interrupt-parent = <0x03>; 145 | reg = <0x00 0x10004000 0x00 0x1000>; 146 | compatible = "virtio,mmio"; 147 | }; 148 | 149 | virtio_mmio@10003000 { 150 | interrupts = <0x03>; 151 | interrupt-parent = <0x03>; 152 | reg = <0x00 0x10003000 0x00 0x1000>; 153 | compatible = "virtio,mmio"; 154 | }; 155 | 156 | virtio_mmio@10002000 { 157 | interrupts = <0x02>; 158 | interrupt-parent = <0x03>; 159 | reg = <0x00 0x10002000 0x00 0x1000>; 160 | compatible = "virtio,mmio"; 161 | }; 162 | 163 | virtio_mmio@10001000 { 164 | interrupts = <0x01>; 165 | interrupt-parent = <0x03>; 166 | reg = <0x00 0x10001000 0x00 0x1000>; 167 | compatible = "virtio,mmio"; 168 | }; 169 | 170 | plic@c000000 { 171 | phandle = <0x03>; 172 | riscv,ndev = <0x35>; 173 | reg = <0x00 0xc000000 0x00 0x210000>; 174 | interrupts-extended = <0x02 0x0b 0x02 0x09>; 175 | interrupt-controller; 176 | compatible = "riscv,plic0"; 177 | #interrupt-cells = <0x01>; 178 | #address-cells = <0x00>; 179 | }; 180 | 181 | clint@2000000 { 182 | interrupts-extended = <0x02 0x03 0x02 0x07>; 183 | reg = <0x00 0x2000000 0x00 0x10000>; 184 | compatible = "riscv,clint0"; 185 | }; 186 | }; 187 | }; 188 | -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/.gdbinit: -------------------------------------------------------------------------------- 1 | set architecture riscv:rv64 2 | set disassemble-next-line on 3 | target extended-remote localhost:1234 4 | -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE := riscv64-linux-gnu- 2 | CC := ${CROSS_COMPILE}gcc 3 | AS := ${CROSS_COMPILE}as 4 | LD := ${CROSS_COMPILE}ld 5 | OBJCOPY := ${CROSS_COMPILE}objcopy 6 | 7 | CLANG_SRC := firmware.c trap_handler.c proc.c kstring.c test_processes.c 8 | ASM_SRC := clang_env_init.s trap.s 9 | HEADERS := riscv_arch.h riscv_asm.h uart.h timer.h interrupts.h \ 10 | proc.h kstring.h riscv_cpu.h 11 | 12 | OBJECTS = $(addprefix bin/,$(ASM_SRC:.s=.asmo)) 13 | OBJECTS += $(addprefix bin/,$(CLANG_SRC:.c=.clango)) 14 | 15 | all: bin/firmware.bin 16 | 17 | bin/firmware.bin: ${OBJECTS} ${HEADERS} clang_env_init.ld 18 | ${LD} --no-relax -T clang_env_init.ld ${OBJECTS} -o bin/firmware 19 | ${OBJCOPY} -O binary -S bin/firmware bin/firmware.bin 20 | 21 | bin/%.asmo: %.s 22 | ${AS} -g $< -o $@ 23 | 24 | bin/%.clango: %.c ${HEADERS} 25 | ${CC} -ffreestanding -c -O0 -g $< -o $@ 26 | 27 | run: bin/firmware.bin 28 | qemu-system-riscv64 -machine virt -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -display none 29 | 30 | debug: bin/firmware.bin 31 | qemu-system-riscv64 -machine virt -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -S 32 | 33 | clean: 34 | rm -rf bin/ 35 | 36 | $(shell mkdir -p bin) -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/README.md: -------------------------------------------------------------------------------- 1 | # 07: Timer Interrupt-based Process Scheduling 2 | 3 | ## Timer Interrupt-based Process Scheduling 4 | 5 | ```sh 6 | make run 7 | ``` 8 | 9 | ## References 10 | 11 | - [SiFive FU540-C000 Manual v1p4](https://sifive.cdn.prismic.io/sifive/d3ed5cd0-6e74-46b2-a12d-72b06706513e_fu540-c000-manual-v1p4.pdf) 12 | - [RISC-V Platform-Level Interrupt Controller Specification](https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc#interrupt-targets-and-hart-contexts) -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/clang_env_init.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_clang_env_init) 2 | 3 | MEMORY {} /* default */ 4 | 5 | . = 0x80000000; 6 | 7 | SECTIONS { 8 | } -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/clang_env_init.s: -------------------------------------------------------------------------------- 1 | .global _clang_env_init 2 | 3 | .text 4 | _clang_env_init: 5 | bne a0, x0, _clang_env_init # loop if hartid is not 0 6 | li sp, 0x80200000 # setup stack pointer 7 | j firmware_main # jump to c entry 8 | -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/firmware.c: -------------------------------------------------------------------------------- 1 | #include "riscv_asm.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "interrupts.h" 5 | #include "proc.h" 6 | #include "kstring.h" 7 | 8 | extern void trap_entry(); 9 | extern void test_proc_0_entry(); 10 | extern void test_proc_1_entry(); 11 | extern void test_proc_2_entry(); 12 | extern void *test_proc_0_stack_top; 13 | extern void *test_proc_1_stack_top; 14 | extern void *test_proc_2_stack_top; 15 | 16 | void proc_init() 17 | { 18 | struct proc test_proc_0 = { 19 | .name = "test_proc_0", 20 | .pid = 0, 21 | .hartid = 0, 22 | .state = PROC_STATE_READY, 23 | .cpu = { 24 | .pc = (u64)test_proc_0_entry, 25 | .x2 = (u64)test_proc_0_stack_top, 26 | }}; 27 | uart_printf("[proc_init] test_proc_0: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_0, test_proc_0_stack_top); 28 | proc_list[0] = test_proc_0; 29 | 30 | struct proc test_proc_1 = { 31 | .name = "test_proc_1", 32 | .pid = 1, 33 | .hartid = 0, 34 | .state = PROC_STATE_READY, 35 | .cpu = { 36 | .pc = (u64)test_proc_1_entry, 37 | .x2 = (u64)test_proc_1_stack_top, 38 | }}; 39 | uart_printf("[proc_init] test_proc_1: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_1, test_proc_1_stack_top); 40 | proc_list[1] = test_proc_1; 41 | 42 | struct proc test_proc_2 = { 43 | .name = "test_proc_2", 44 | .pid = 2, 45 | .hartid = 0, 46 | .state = PROC_STATE_READY, 47 | .cpu = { 48 | .pc = (u64)test_proc_2_entry, 49 | .x2 = (u64)test_proc_2_stack_top, 50 | }}; 51 | uart_printf("[proc_init] test_proc_2: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_2, test_proc_2_stack_top); 52 | proc_list[2] = test_proc_2; 53 | 54 | for (int i = 3; i < PROC_TOTAL_COUNT; i++) 55 | { 56 | memset(&proc_list[i], 0, sizeof(proc_list[i])); 57 | proc_list[i].state = PROC_STATE_NONE; 58 | } 59 | 60 | active_pid = -1; 61 | } 62 | 63 | void firmware_main() 64 | { 65 | // initialize UART 66 | uart_init(); 67 | 68 | // prepare processes 69 | proc_init(); 70 | 71 | // setup timer timout 72 | uart_printf("[firmware_main] default MTIMECMP_0 is %d\n", mtimecmp_0()); 73 | set_timeout(10000000); 74 | 75 | // setup M-mode trap vector 76 | csrw_mtvec((u64)trap_entry); 77 | // enable M-mode timer interrupt 78 | csrw_mie(MIE_MTIE); 79 | // enable MIE in mstatus 80 | csrs_mstatus(MSTAUTS_MIE); 81 | 82 | while (1) 83 | { 84 | } 85 | } -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/interrupts.h: -------------------------------------------------------------------------------- 1 | #ifndef _INTERRUPTS_H 2 | #define _INTERRUPTS_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | #define MSTAUTS_MIE (0x1L << 3) 7 | 8 | #define MIE_MTIE (0x1L << 7) 9 | #define MIE_MEIE (0x1L << 11) 10 | 11 | #define MCAUSE_INTR_M_TIMER ((0x1L << (MACHINE_BITS - 1)) | 7) 12 | #define MCAUSE_INTR_M_EXTER ((0x1L << (MACHINE_BITS - 1)) | 11) 13 | 14 | #define MCAUSE_INNER_M_ILLEAGEL_INSTRUCTION (0x2L) 15 | 16 | #endif /* _INTERRUPTS_H */ -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/kstring.c: -------------------------------------------------------------------------------- 1 | #include "kstring.h" 2 | 3 | void *memset(void *s, int c, size_t n) 4 | { 5 | unsigned char *p = s; 6 | while (n--) 7 | *p++ = (unsigned char)c; 8 | return s; 9 | } 10 | 11 | void *memcpy(void *dest, const void *src, size_t n) 12 | { 13 | unsigned char *d = dest; 14 | const unsigned char *s = src; 15 | while (n--) 16 | *d++ = *s++; 17 | return dest; 18 | } 19 | 20 | size_t strlen(const char *str) 21 | { 22 | size_t length = 0; 23 | while (*str != '\0') 24 | { 25 | length++; 26 | str++; 27 | } 28 | return length; 29 | } 30 | 31 | char *strcpy(char *dest, const char *src) 32 | { 33 | char *p = dest; 34 | while ((*p++ = *src++) != '\0') 35 | ; 36 | return dest; 37 | } 38 | -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/kstring.h: -------------------------------------------------------------------------------- 1 | #ifndef _KSTRING_H 2 | #define _KSTRING_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | void *memset(void *s, int c, size_t n); 7 | void *memcpy(void *dest, const void *src, size_t n); 8 | long unsigned int strlen(const char *str); 9 | char *strcpy(char *dest, const char *src); 10 | 11 | #endif /* _KSTRING_H */ -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/proc.c: -------------------------------------------------------------------------------- 1 | #include "proc.h" 2 | 3 | i32 active_pid; 4 | struct proc proc_list[PROC_TOTAL_COUNT]; -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/proc.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROC_H 2 | #define _PROC_H 3 | 4 | #define PROC_NAME_MAXLEN 64 5 | #define PROC_TOTAL_COUNT 16 6 | 7 | #include "riscv_arch.h" 8 | #include "riscv_cpu.h" 9 | 10 | enum proc_state 11 | { 12 | PROC_STATE_NONE = 0, 13 | PROC_STATE_READY, 14 | PROC_STATE_RUNNING, 15 | }; 16 | 17 | struct proc 18 | { 19 | enum proc_state state; 20 | u32 pid; 21 | u8 name[PROC_NAME_MAXLEN]; 22 | struct cpu cpu; 23 | u64 hartid; 24 | }; 25 | 26 | extern i32 active_pid; 27 | extern struct proc proc_list[PROC_TOTAL_COUNT]; 28 | 29 | #endif /* _PROC_H */ -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/riscv_arch.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ARCH_H 2 | #define _RISCV_ARCH_H 3 | 4 | // machine bits 5 | #define MACHINE_BITS 64 6 | #define BITS_PER_LONG MACHINE_BITS 7 | 8 | // bool 9 | #define bool _Bool 10 | #define true 1 11 | #define false 0 12 | 13 | // unsigned types 14 | typedef unsigned char u8; 15 | typedef unsigned short u16; 16 | typedef unsigned int u32; 17 | typedef unsigned long u64; 18 | 19 | // signed types 20 | typedef signed char i8; 21 | typedef signed short i16; 22 | typedef signed int i32; 23 | typedef signed long i64; 24 | 25 | // size_t 26 | typedef u64 size_t; 27 | 28 | #endif /* _RISCV_ARCH_H */ -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/riscv_asm.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ASM_H 2 | #define _RISCV_ASM_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | #define readu8(addr) (*(const u8 *)(addr)) 7 | #define readu16(addr) (*(const u16 *)(addr)) 8 | #define readu32(addr) (*(const u32 *)(addr)) 9 | #define readu64(addr) (*(const u64 *)(addr)) 10 | 11 | #define writeu8(addr, val) (*(u8 *)(addr) = (val)) 12 | #define writeu16(addr, val) (*(u16 *)(addr) = (val)) 13 | #define writeu32(addr, val) (*(u32 *)(addr) = (val)) 14 | #define writeu64(addr, val) (*(u64 *)(addr) = (val)) 15 | 16 | static inline void csrw_mtvec(const volatile u64 val) 17 | { 18 | asm volatile("csrw mtvec, %0" 19 | : 20 | : "r"(val)); 21 | } 22 | 23 | static inline void csrw_mie(const volatile u64 val) 24 | { 25 | asm volatile("csrw mie, %0" 26 | : 27 | : "r"(val)); 28 | } 29 | 30 | static inline u64 csrr_mstatus() 31 | { 32 | volatile u64 val; 33 | asm volatile("csrr %0, mstatus" 34 | : "=r"(val) 35 | :); 36 | return val; 37 | } 38 | 39 | static inline void csrw_mstatus(const volatile u64 val) 40 | { 41 | asm volatile("csrw mstatus, %0" 42 | : 43 | : "r"(val)); 44 | } 45 | 46 | static inline void csrs_mstatus(const volatile u64 val) 47 | { 48 | asm volatile("csrs mstatus, %0" 49 | : 50 | : "r"(val)); 51 | } 52 | 53 | static inline void csrc_mstatus(const volatile u64 val) 54 | { 55 | asm volatile("csrc mstatus, %0" 56 | : 57 | : "r"(val)); 58 | } 59 | 60 | static inline u64 csrr_mcause() 61 | { 62 | volatile u64 val; 63 | asm volatile("csrr %0, mcause" 64 | : "=r"(val) 65 | :); 66 | return val; 67 | } 68 | 69 | static inline u64 csrr_mepc() 70 | { 71 | volatile u64 val; 72 | asm volatile("csrr %0, mepc" 73 | : "=r"(val) 74 | :); 75 | return val; 76 | } 77 | 78 | static inline void ecall() 79 | { 80 | asm volatile("ecall" 81 | : 82 | :); 83 | } 84 | 85 | #endif -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/riscv_cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef _CPU_H 2 | #define _CPU_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | struct cpu 7 | { 8 | u64 x0; // zero 9 | u64 x1; // ra 10 | u64 x2; // sp 11 | u64 x3; // gp 12 | u64 x4; // tp 13 | u64 x5; // t0 14 | u64 x6; // t1 15 | u64 x7; // t2 16 | u64 x8; // s0/fp 17 | u64 x9; // s1 18 | u64 x10; // a0 19 | u64 x11; // a1 20 | u64 x12; // a2 21 | u64 x13; // a3 22 | u64 x14; // a4 23 | u64 x15; // a5 24 | u64 x16; // a6 25 | u64 x17; // a7 26 | u64 x18; // s2 27 | u64 x19; // s3 28 | u64 x20; // s4 29 | u64 x21; // s5 30 | u64 x22; // s6 31 | u64 x23; // s7 32 | u64 x24; // s8 33 | u64 x25; // s9 34 | u64 x26; // s10 35 | u64 x27; // s11 36 | u64 x28; // t3 37 | u64 x29; // t4 38 | u64 x30; // t5 39 | u64 x31; // t6 40 | 41 | u64 pc; // pc 42 | } __attribute__((packed)); 43 | 44 | #endif /* _CPU_H */ -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/test_processes.c: -------------------------------------------------------------------------------- 1 | #include "riscv_arch.h" 2 | #include "uart.h" 3 | 4 | #define INTERVAL 100000000 5 | 6 | u8 test_proc_0_stack[1 << 20]; 7 | void *test_proc_0_stack_top = &test_proc_0_stack[sizeof(test_proc_0_stack) - 1]; 8 | 9 | void test_proc_0_entry() 10 | { 11 | while (true) 12 | { 13 | for (size_t i = 0; i < INTERVAL; i++) 14 | { 15 | } 16 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 0); 17 | } 18 | } 19 | 20 | u8 test_proc_1_stack[1 << 20]; 21 | void *test_proc_1_stack_top = &test_proc_1_stack[sizeof(test_proc_1_stack) - 1]; 22 | 23 | void test_proc_1_entry() 24 | { 25 | while (true) 26 | { 27 | for (size_t i = 0; i < INTERVAL; i++) 28 | { 29 | } 30 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 1); 31 | } 32 | } 33 | 34 | u8 test_proc_2_stack[1 << 20]; 35 | void *test_proc_2_stack_top = &test_proc_2_stack[sizeof(test_proc_2_stack) - 1]; 36 | void test_proc_2_entry() 37 | { 38 | while (true) 39 | { 40 | for (size_t i = 0; i < INTERVAL; i++) 41 | { 42 | } 43 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 2); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef _TIMER_H 2 | #define _TIMER_H 3 | 4 | #include "riscv_asm.h" 5 | 6 | #define MTIME 0x200bff8 7 | #define MTIMECMP_0 0x2004000 8 | 9 | static inline u64 mtime() 10 | { 11 | return readu64(MTIME); 12 | } 13 | 14 | static inline u64 mtimecmp_0() 15 | { 16 | return readu64(MTIMECMP_0); 17 | } 18 | 19 | static inline u64 set_timeout(u64 timeout) 20 | { 21 | writeu64(MTIMECMP_0, mtime() + timeout); 22 | } 23 | 24 | #endif -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/trap.s: -------------------------------------------------------------------------------- 1 | .equ REGSZ, 8 2 | .global trap_entry 3 | 4 | .text 5 | 6 | trap_entry: 7 | 8 | # swap x5/mscratch 9 | csrrw x5, mscratch, x5 10 | # use x5 as cpu state base address register 11 | la x5, trap_cpu 12 | # save general purpose registers 13 | # x0 ~ x4 14 | sd x0, (0 * REGSZ)(x5) 15 | sd x1, (1 * REGSZ)(x5) 16 | sd x2, (2 * REGSZ)(x5) 17 | sd x3, (3 * REGSZ)(x5) 18 | sd x4, (4 * REGSZ)(x5) 19 | # save origin x5 by x1, which has been saved 20 | csrr x1, mscratch 21 | sd x1, (5 * REGSZ)(x5) 22 | # x6 ~ x31 23 | sd x6, (6 * REGSZ)(x5) 24 | sd x7, (7 * REGSZ)(x5) 25 | sd x8, (8 * REGSZ)(x5) 26 | sd x9, (9 * REGSZ)(x5) 27 | sd x10, (10 * REGSZ)(x5) 28 | sd x11, (11 * REGSZ)(x5) 29 | sd x12, (12 * REGSZ)(x5) 30 | sd x13, (13 * REGSZ)(x5) 31 | sd x14, (14 * REGSZ)(x5) 32 | sd x15, (15 * REGSZ)(x5) 33 | sd x16, (16 * REGSZ)(x5) 34 | sd x17, (17 * REGSZ)(x5) 35 | sd x18, (18 * REGSZ)(x5) 36 | sd x19, (19 * REGSZ)(x5) 37 | sd x20, (20 * REGSZ)(x5) 38 | sd x21, (21 * REGSZ)(x5) 39 | sd x22, (22 * REGSZ)(x5) 40 | sd x23, (23 * REGSZ)(x5) 41 | sd x24, (24 * REGSZ)(x5) 42 | sd x25, (25 * REGSZ)(x5) 43 | sd x26, (26 * REGSZ)(x5) 44 | sd x27, (27 * REGSZ)(x5) 45 | sd x28, (28 * REGSZ)(x5) 46 | sd x29, (29 * REGSZ)(x5) 47 | sd x30, (30 * REGSZ)(x5) 48 | sd x31, (31 * REGSZ)(x5) 49 | # save privilege registers 50 | # save mepc by x1, which has been saved 51 | csrr x1, mepc 52 | sd x1, (32 * REGSZ)(x5) 53 | 54 | # call trap_handler 55 | # Need set stack pointer? 56 | la t0, trap_stack_top 57 | ld sp, 0(t0) 58 | call trap_handler 59 | 60 | # use x5 as cpu state base address register 61 | la x5, trap_cpu 62 | # restore privilege registers 63 | # restore mepc by x1, which will be restored later 64 | ld x1, (32 * REGSZ)(x5) 65 | csrw mepc, x1 66 | # restore general purpose registers 67 | # x0 ~ x4 68 | ld x0, (0 * REGSZ)(x5) 69 | ld x1, (1 * REGSZ)(x5) 70 | ld x2, (2 * REGSZ)(x5) 71 | ld x3, (3 * REGSZ)(x5) 72 | ld x4, (4 * REGSZ)(x5) 73 | # postpone the restoration of x5 74 | # because it is being used as the base address register 75 | # x6 ~ x31 76 | ld x6, (6 * REGSZ)(x5) 77 | ld x7, (7 * REGSZ)(x5) 78 | ld x8, (8 * REGSZ)(x5) 79 | ld x9, (9 * REGSZ)(x5) 80 | ld x10, (10 * REGSZ)(x5) 81 | ld x11, (11 * REGSZ)(x5) 82 | ld x12, (12 * REGSZ)(x5) 83 | ld x13, (13 * REGSZ)(x5) 84 | ld x14, (14 * REGSZ)(x5) 85 | ld x15, (15 * REGSZ)(x5) 86 | ld x16, (16 * REGSZ)(x5) 87 | ld x17, (17 * REGSZ)(x5) 88 | ld x18, (18 * REGSZ)(x5) 89 | ld x19, (19 * REGSZ)(x5) 90 | ld x20, (20 * REGSZ)(x5) 91 | ld x21, (21 * REGSZ)(x5) 92 | ld x22, (22 * REGSZ)(x5) 93 | ld x23, (23 * REGSZ)(x5) 94 | ld x24, (24 * REGSZ)(x5) 95 | ld x25, (25 * REGSZ)(x5) 96 | ld x26, (26 * REGSZ)(x5) 97 | ld x27, (27 * REGSZ)(x5) 98 | ld x28, (28 * REGSZ)(x5) 99 | ld x29, (29 * REGSZ)(x5) 100 | ld x30, (30 * REGSZ)(x5) 101 | ld x31, (31 * REGSZ)(x5) 102 | # x5 103 | ld x5, (6 * REGSZ)(x5) 104 | 105 | mret 106 | # j trap_entry 107 | -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/trap_handler.c: -------------------------------------------------------------------------------- 1 | #include "riscv_cpu.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "proc.h" 5 | #include "interrupts.h" 6 | 7 | struct cpu trap_cpu; 8 | u8 trap_stack[1 << 20]; 9 | void *trap_stack_top = &trap_stack[sizeof(trap_stack) - 1]; 10 | 11 | void trap_handler() 12 | { 13 | u64 mcause = csrr_mcause(); 14 | switch (mcause) 15 | { 16 | case MCAUSE_INTR_M_TIMER: 17 | { 18 | // there exists runnable processes 19 | if (proc_list[0].state != PROC_STATE_NONE) 20 | { 21 | // assume proc-0 is the active process if there is no active process 22 | if (active_pid < 0) 23 | { 24 | active_pid = 0; 25 | trap_cpu = proc_list[0].cpu; 26 | uart_printf("[Trap - M-mode Timer] Scheduler Init. Ticks: %ld\n", active_pid, mcause, mtime()); 27 | } 28 | 29 | // save cpu state for the active process 30 | proc_list[active_pid].cpu = trap_cpu; 31 | // suspend the active process 32 | proc_list[active_pid].state = PROC_STATE_READY; 33 | 34 | // iterate the processes from the next process, ending with the active process 35 | for (int ring_index = 1; ring_index <= PROC_TOTAL_COUNT; ring_index++) 36 | { 37 | int real_index = (active_pid + ring_index) % PROC_TOTAL_COUNT; 38 | struct proc *proc = &proc_list[real_index]; 39 | // run this process if it is ready 40 | if (proc->state == PROC_STATE_READY) 41 | { 42 | uart_printf("[Trap - M-mode Timer] Scheduler(Ticks = %ld): (PID = %d, PC = 0x%lx) => (PID = %d, PC = 0x%lx)\n", mtime(), active_pid, trap_cpu.pc, proc->pid, proc->cpu.pc); 43 | trap_cpu = proc->cpu; 44 | active_pid = proc->pid; 45 | break; 46 | } 47 | } 48 | } 49 | set_timeout(10000000); 50 | break; 51 | } 52 | 53 | case MCAUSE_INTR_M_EXTER: 54 | { 55 | uart_printf("[Trap - M-mode Exter] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, mcause, mtime()); 56 | break; 57 | } 58 | 59 | case MCAUSE_INNER_M_ILLEAGEL_INSTRUCTION: 60 | { 61 | uart_printf("[Trap - M-mode Illeagel Instruction] active_pid: %d, mcause: 0x%lX, mepc: %lx\n", active_pid, mcause, csrr_mepc()); 62 | break; 63 | } 64 | 65 | default: 66 | { 67 | uart_printf("[Trap - Default] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, mcause, mtime()); 68 | break; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /07-Simple-Process-Scheduling/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _UART_H 2 | #define _UART_H 3 | 4 | #include "riscv_arch.h" 5 | #include "riscv_asm.h" 6 | #include 7 | 8 | #define UART_BASE 0x10000000 9 | 10 | #define UART_RBR_OFFSET 0 /* In: Recieve Buffer Register */ 11 | #define UART_THR_OFFSET 0 /* Out: Transmitter Holding Register */ 12 | #define UART_DLL_OFFSET 0 /* Out: Divisor Latch Low */ 13 | #define UART_IER_OFFSET 1 /* I/O: Interrupt Enable Register */ 14 | #define UART_DLM_OFFSET 1 /* Out: Divisor Latch High */ 15 | #define UART_FCR_OFFSET 2 /* Out: FIFO Control Register */ 16 | #define UART_IIR_OFFSET 2 /* I/O: Interrupt Identification Register */ 17 | #define UART_LCR_OFFSET 3 /* Out: Line Control Register */ 18 | #define UART_MCR_OFFSET 4 /* Out: Modem Control Register */ 19 | #define UART_LSR_OFFSET 5 /* In: Line Status Register */ 20 | #define UART_MSR_OFFSET 6 /* In: Modem Status Register */ 21 | #define UART_SCR_OFFSET 7 /* I/O: Scratch Register */ 22 | #define UART_MDR1_OFFSET 8 /* I/O: Mode Register */ 23 | 24 | #define PLATFORM_UART_INPUT_FREQ 10000000 25 | #define PLATFORM_UART_BAUDRATE 115200 26 | 27 | static u8 *uart_base_addr = (u8 *)UART_BASE; 28 | 29 | static void set_reg(u32 offset, u32 val) 30 | { 31 | writeu8(uart_base_addr + offset, val); 32 | } 33 | 34 | static u32 get_reg(u32 offset) 35 | { 36 | return readu8(uart_base_addr + offset); 37 | } 38 | 39 | static void uart_putc(u8 ch) 40 | { 41 | set_reg(UART_THR_OFFSET, ch); 42 | } 43 | 44 | static void uart_print(char *str) 45 | { 46 | while (*str) 47 | { 48 | uart_putc(*str++); 49 | } 50 | } 51 | 52 | static void uart_print_digit(unsigned long val, unsigned long base, bool uppercase) 53 | { 54 | const char *digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; 55 | 56 | if (val < base) 57 | { 58 | uart_putc(digits[val]); 59 | } 60 | else 61 | { 62 | uart_print_digit(val / base, base, uppercase); 63 | uart_putc(digits[val % base]); 64 | } 65 | } 66 | 67 | static inline void uart_print_int(int val, int base) 68 | { 69 | if (val < 0) 70 | { 71 | uart_putc('-'); 72 | val = -val; 73 | } 74 | uart_print_digit(val, base, false); 75 | } 76 | 77 | static inline void uart_print_uint(unsigned int val, int base) 78 | { 79 | uart_print_digit(val, base, false); 80 | } 81 | 82 | static inline void uart_print_uint_upper(unsigned int val, int base) 83 | { 84 | uart_print_digit(val, base, true); 85 | } 86 | 87 | static inline void uart_print_long(long val, int base) 88 | { 89 | if (val < 0) 90 | { 91 | uart_putc('-'); 92 | val = -val; 93 | } 94 | uart_print_digit(val, base, false); 95 | } 96 | 97 | static inline void uart_print_ulong(unsigned long val, int base) 98 | { 99 | uart_print_digit(val, base, false); 100 | } 101 | 102 | static inline void uart_print_ulong_upper(unsigned long val, int base) 103 | { 104 | uart_print_digit(val, base, true); 105 | } 106 | 107 | static inline void uart_printf(const char *format, ...) 108 | { 109 | va_list args; 110 | va_start(args, format); 111 | 112 | while (*format) 113 | { 114 | if (*format == '%') 115 | { 116 | format++; 117 | switch (*format) 118 | { 119 | case 'c': 120 | { 121 | char c = (char)va_arg(args, unsigned int); 122 | uart_putc(c); 123 | break; 124 | } 125 | case 's': 126 | { 127 | char *str = va_arg(args, char *); 128 | uart_print(str); 129 | break; 130 | } 131 | case 'd': 132 | { 133 | int val = va_arg(args, int); 134 | uart_print_int(val, 10); 135 | break; 136 | } 137 | case 'u': 138 | { 139 | unsigned int val = va_arg(args, unsigned int); 140 | uart_print_uint(val, 10); 141 | break; 142 | } 143 | case 'x': 144 | { 145 | unsigned int val = va_arg(args, unsigned int); 146 | uart_print_uint(val, 16); 147 | break; 148 | } 149 | case 'X': 150 | { 151 | unsigned int val = va_arg(args, unsigned int); 152 | uart_print_uint_upper(val, 16); 153 | break; 154 | } 155 | case 'l': 156 | { 157 | format++; 158 | switch (*format) 159 | { 160 | case 'd': 161 | { 162 | long val = va_arg(args, long); 163 | uart_print_long(val, 10); 164 | break; 165 | } 166 | case 'u': 167 | { 168 | unsigned long val = va_arg(args, unsigned long); 169 | uart_print_ulong(val, 10); 170 | break; 171 | } 172 | case 'x': 173 | { 174 | unsigned long val = va_arg(args, unsigned long); 175 | uart_print_ulong(val, 16); 176 | break; 177 | } 178 | case 'X': 179 | { 180 | unsigned long val = va_arg(args, unsigned long); 181 | uart_print_ulong_upper(val, 16); 182 | break; 183 | } 184 | default: 185 | uart_putc(*format); 186 | break; 187 | } 188 | break; 189 | } 190 | default: 191 | uart_putc(*format); 192 | break; 193 | } 194 | } 195 | else 196 | { 197 | uart_putc(*format); 198 | } 199 | format++; 200 | } 201 | 202 | va_end(args); 203 | } 204 | 205 | static inline void uart_init() 206 | { 207 | u16 bdiv = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE); 208 | 209 | /* Disable all interrupts */ 210 | set_reg(UART_IER_OFFSET, 0x00); 211 | /* Enable DLAB */ 212 | set_reg(UART_LCR_OFFSET, 0x80); 213 | 214 | if (bdiv) 215 | { 216 | /* Set divisor low byte */ 217 | set_reg(UART_DLL_OFFSET, bdiv & 0xff); 218 | /* Set divisor high byte */ 219 | set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff); 220 | } 221 | 222 | /* 8 bits, no parity, one stop bit */ 223 | set_reg(UART_LCR_OFFSET, 0x03); 224 | /* Enable FIFO */ 225 | set_reg(UART_FCR_OFFSET, 0x01); 226 | /* No modem control DTR RTS */ 227 | set_reg(UART_MCR_OFFSET, 0x00); 228 | /* Clear line status */ 229 | get_reg(UART_LSR_OFFSET); 230 | /* Read receive buffer */ 231 | get_reg(UART_RBR_OFFSET); 232 | /* Set scratchpad */ 233 | set_reg(UART_SCR_OFFSET, 0x00); 234 | } 235 | 236 | #endif -------------------------------------------------------------------------------- /08-UART-Interrupt/.gdbinit: -------------------------------------------------------------------------------- 1 | set architecture riscv:rv64 2 | set disassemble-next-line on 3 | target remote localhost:1234 4 | -------------------------------------------------------------------------------- /08-UART-Interrupt/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /08-UART-Interrupt/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE := riscv64-linux-gnu- 2 | CC := ${CROSS_COMPILE}gcc 3 | AS := ${CROSS_COMPILE}as 4 | LD := ${CROSS_COMPILE}ld 5 | OBJCOPY := ${CROSS_COMPILE}objcopy 6 | 7 | CLANG_SRC := firmware.c trap_handler.c proc.c kstring.c test_processes.c 8 | ASM_SRC := clang_env_init.s trap.s 9 | HEADERS := riscv_asm.h riscv_types.h uart.h timer.h uart.h interrupts.h \ 10 | plic.h proc.h kdef.h kstring.h riscv_arch.h riscv_cpu.h riscv_priv.h 11 | 12 | OBJECTS = $(addprefix bin/,$(ASM_SRC:.s=.asmo)) 13 | OBJECTS += $(addprefix bin/,$(CLANG_SRC:.c=.clango)) 14 | 15 | all: bin/firmware.bin 16 | 17 | bin/firmware.bin: ${OBJECTS} ${HEADERS} clang_env_init.ld 18 | ${LD} --no-relax -T clang_env_init.ld ${OBJECTS} -o bin/firmware 19 | ${OBJCOPY} -O binary -S bin/firmware bin/firmware.bin 20 | 21 | bin/%.asmo: %.s 22 | ${AS} -g $< -o $@ 23 | 24 | bin/%.clango: %.c ${HEADERS} 25 | ${CC} -ffreestanding -c -O0 -g $< -o $@ 26 | 27 | run: bin/firmware.bin 28 | qemu-system-riscv64 -machine sifive_u -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -display none 29 | 30 | debug: bin/firmware.bin 31 | qemu-system-riscv64 -machine sifive_u -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -S 32 | 33 | clean: 34 | rm -rf bin/ 35 | 36 | $(shell mkdir -p bin) -------------------------------------------------------------------------------- /08-UART-Interrupt/README.md: -------------------------------------------------------------------------------- 1 | # 08: UART Interrupt 2 | 3 | ```sh 4 | make run 5 | ``` 6 | 7 | Then press keyboard. -------------------------------------------------------------------------------- /08-UART-Interrupt/clang_env_init.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_clang_env_init) 2 | 3 | MEMORY {} /* default */ 4 | 5 | . = 0x80000000; 6 | 7 | SECTIONS { 8 | } -------------------------------------------------------------------------------- /08-UART-Interrupt/clang_env_init.s: -------------------------------------------------------------------------------- 1 | .global _clang_env_init 2 | 3 | .text 4 | _clang_env_init: 5 | bne a0, x0, _clang_env_init # loop if hartid is not 0 6 | li sp, 0x80200000 # setup stack pointer 7 | j firmware_main # jump to c entry 8 | -------------------------------------------------------------------------------- /08-UART-Interrupt/firmware.c: -------------------------------------------------------------------------------- 1 | #include "riscv_asm.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "interrupts.h" 5 | #include "plic.h" 6 | #include "proc.h" 7 | #include "kstring.h" 8 | 9 | extern void trap_entry(); 10 | extern void test_proc_0_entry(); 11 | extern void test_proc_1_entry(); 12 | extern void test_proc_2_entry(); 13 | extern void *test_proc_0_stack_top; 14 | extern void *test_proc_1_stack_top; 15 | extern void *test_proc_2_stack_top; 16 | 17 | void echo() 18 | { 19 | uart_print("Hello, RISC-V!\n"); 20 | uart_print("echo> "); 21 | for (;;) 22 | { 23 | u32 data = uart_getc(); 24 | if (!(data & UART_RXFIFO_EMPTY)) 25 | { 26 | uart_putc(data & UART_RXFIFO_DATA); 27 | } 28 | } 29 | } 30 | 31 | void trap_init() 32 | { 33 | csrw_mtvec((u64)trap_entry); 34 | } 35 | 36 | void plic_init() 37 | { 38 | u32 u32val = plic_pri_thr(); 39 | plic_pri_thr_write(0x0); 40 | uart_printf("[plic_init] plic_pri_thr 0x%X => 0x%X\n", u32val, plic_pri_thr()); 41 | 42 | u32val = plic_pri_uart0(); 43 | plic_pri_uart0_write(0x7); 44 | uart_printf("[plic_init] plic_pri_uart0 0x%X => 0x%X\n", u32val, plic_pri_uart0()); 45 | 46 | u64 u64val = plic_mie_hart0(); 47 | plic_mie_hart0_write(1 << PLIC_INTRID_UART0); 48 | uart_printf("[plic_init] plic_mie_hart0 0x%X => 0x%X\n", u64val, plic_mie_hart0()); 49 | } 50 | 51 | void uart_interrupt_setup() 52 | { 53 | u32 u32val = _uart_ie(); 54 | _uart_ie_write(UART_IE_RXWM); 55 | uart_printf("[uart_interrupt_setup] _uart_ie => 0x%X => 0x%X\n", u32val, _uart_ie()); 56 | } 57 | 58 | void proc_init() 59 | { 60 | struct proc test_proc_0 = { 61 | .name = "test_proc_0", 62 | .pid = 0, 63 | .hartid = 0, 64 | .state = PROC_STATE_READY, 65 | .cpu = { 66 | .pc = (u64)test_proc_0_entry, 67 | .x2 = (u64)test_proc_0_stack_top, 68 | }}; 69 | uart_printf("[proc_init] test_proc_0: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_0, test_proc_0_stack_top); 70 | proc_list[0] = test_proc_0; 71 | 72 | struct proc test_proc_1 = { 73 | .name = "test_proc_1", 74 | .pid = 1, 75 | .hartid = 0, 76 | .state = PROC_STATE_READY, 77 | .cpu = { 78 | .pc = (u64)test_proc_1_entry, 79 | .x2 = (u64)test_proc_1_stack_top, 80 | }}; 81 | uart_printf("[proc_init] test_proc_1: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_1, test_proc_1_stack_top); 82 | proc_list[1] = test_proc_1; 83 | 84 | struct proc test_proc_2 = { 85 | .name = "test_proc_2", 86 | .pid = 2, 87 | .hartid = 0, 88 | .state = PROC_STATE_READY, 89 | .cpu = { 90 | .pc = (u64)test_proc_2_entry, 91 | .x2 = (u64)test_proc_2_stack_top, 92 | }}; 93 | uart_printf("[proc_init] test_proc_2: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_2, test_proc_2_stack_top); 94 | proc_list[2] = test_proc_2; 95 | 96 | for (int i = 3; i < PROC_TOTAL_COUNT; i++) 97 | { 98 | memset(&proc_list[i], 0, sizeof(proc_list[i])); 99 | proc_list[i].state = PROC_STATE_NONE; 100 | } 101 | 102 | active_pid = -1; 103 | } 104 | 105 | void firmware_main() 106 | { 107 | trap_init(); 108 | uart_init(); 109 | proc_init(); 110 | plic_init(); 111 | uart_interrupt_setup(); 112 | // ecall(); 113 | // echo(); 114 | 115 | uart_printf("default MTIMECMP_0 is %d\n", mtimecmp_0()); 116 | set_timeout(10000000); 117 | 118 | csrw_mie(MIE_MTIE | MIE_MEIE); 119 | // csrw_mie(MIE_MTIE); 120 | csrs_mstatus(MSTAUTS_MIE); 121 | 122 | while (1) 123 | { 124 | } 125 | } -------------------------------------------------------------------------------- /08-UART-Interrupt/interrupts.h: -------------------------------------------------------------------------------- 1 | #ifndef _INTERRUPTS_H 2 | #define _INTERRUPTS_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | #define MSTAUTS_MIE (0x1L << 3) 7 | 8 | #define MIE_MTIE (0x1L << 7) 9 | #define MIE_MEIE (0x1L << 11) 10 | 11 | #define MCAUSE_INTR_M_TIMER ((0x1L << (MACHINE_BITS - 1)) | 7) 12 | #define MCAUSE_INTR_M_EXTER ((0x1L << (MACHINE_BITS - 1)) | 11) 13 | 14 | #define MCAUSE_INNER_M_ILLEAGEL_INSTRUCTION (0x2L) 15 | 16 | #endif /* _INTERRUPTS_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/kbool.h: -------------------------------------------------------------------------------- 1 | #ifndef _KBOOL_H 2 | #define _KBOOL_H 3 | 4 | #define bool _Bool 5 | #define true 1 6 | #define false 0 7 | 8 | #endif -------------------------------------------------------------------------------- /08-UART-Interrupt/kdef.h: -------------------------------------------------------------------------------- 1 | #ifndef _KDEF_H 2 | #define _KDEF_H 3 | 4 | #include "riscv_types.h" 5 | 6 | typedef u64 size_t; 7 | 8 | #endif /* _KDEF_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/kstring.c: -------------------------------------------------------------------------------- 1 | #include "kstring.h" 2 | 3 | void *memset(void *s, int c, size_t n) 4 | { 5 | unsigned char *p = s; 6 | while (n--) 7 | *p++ = (unsigned char)c; 8 | return s; 9 | } 10 | 11 | void *memcpy(void *dest, const void *src, size_t n) 12 | { 13 | unsigned char *d = dest; 14 | const unsigned char *s = src; 15 | while (n--) 16 | *d++ = *s++; 17 | return dest; 18 | } 19 | 20 | size_t strlen(const char *str) 21 | { 22 | size_t length = 0; 23 | while (*str != '\0') 24 | { 25 | length++; 26 | str++; 27 | } 28 | return length; 29 | } 30 | 31 | char *strcpy(char *dest, const char *src) 32 | { 33 | char *p = dest; 34 | while ((*p++ = *src++) != '\0') 35 | ; 36 | return dest; 37 | } 38 | -------------------------------------------------------------------------------- /08-UART-Interrupt/kstring.h: -------------------------------------------------------------------------------- 1 | #ifndef _KSTRING_H 2 | #define _KSTRING_H 3 | 4 | #include "kdef.h" 5 | 6 | void *memset(void *s, int c, size_t n); 7 | void *memcpy(void *dest, const void *src, size_t n); 8 | long unsigned int strlen(const char *str); 9 | char *strcpy(char *dest, const char *src); 10 | 11 | #endif /* _KSTRING_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/plic.h: -------------------------------------------------------------------------------- 1 | #ifndef _PLIC_H 2 | #define _PLIC_H 3 | 4 | #include "riscv_types.h" 5 | #include "riscv_asm.h" 6 | 7 | #define PLIC_INTRID_UART0 4 8 | 9 | #define PLIC_PRI_BASE 0x0C000000 10 | #define PLIC_PRI_CALC(intr_id) (PLIC_PRI_BASE + (intr_id)*4) 11 | #define PLIC_PRI_UART0 PLIC_PRI_CALC(PLIC_INTRID_UART0) 12 | 13 | #define PLIC_PENDING_R0 0x0C001000 14 | #define PLIC_PENDING_R1 0x0C001004 15 | 16 | #define PLIC_MIE_HART0LO 0x0C002000 17 | #define PLIC_MIE_HART0HI 0x0C002004 18 | 19 | #define PLIC_PRI_THR 0x0C200000 20 | 21 | #define PLIC_INTR_CLAIM 0x0C200004 22 | 23 | static inline u32 plic_pri_uart0() 24 | { 25 | return readu32(PLIC_PRI_UART0); 26 | } 27 | 28 | static inline void plic_pri_uart0_write(u32 val) 29 | { 30 | writeu32(PLIC_PRI_UART0, val); 31 | } 32 | 33 | static inline u32 plic_mie_hart0lo() 34 | { 35 | return readu32(PLIC_MIE_HART0LO); 36 | } 37 | 38 | static inline void plic_mie_hart0lo_write(u32 val) 39 | { 40 | writeu32(PLIC_MIE_HART0LO, val); 41 | } 42 | 43 | static inline u32 plic_mie_hart0hi() 44 | { 45 | return readu32(PLIC_MIE_HART0HI); 46 | } 47 | 48 | static inline void plic_mie_hart0hi_write(u32 val) 49 | { 50 | writeu32(PLIC_MIE_HART0HI, val); 51 | } 52 | 53 | static inline u64 plic_mie_hart0() 54 | { 55 | return (((u64)plic_mie_hart0hi()) << 32) | plic_mie_hart0lo(); 56 | } 57 | 58 | static inline void plic_mie_hart0_write(u64 val) 59 | { 60 | plic_mie_hart0lo_write(0xffffffff & val); 61 | plic_mie_hart0hi_write(val >> 32); 62 | } 63 | 64 | static inline u32 plic_pri_thr() 65 | { 66 | return 0x7 & readu32(PLIC_PRI_THR); 67 | } 68 | 69 | static inline void plic_pri_thr_write(u32 val) 70 | { 71 | writeu32(PLIC_PRI_THR, val); 72 | } 73 | 74 | static inline u32 plic_intr_claim() 75 | { 76 | return readu32(PLIC_INTR_CLAIM); 77 | } 78 | 79 | static inline void plic_intr_claim_write(u32 val) 80 | { 81 | writeu32(PLIC_INTR_CLAIM, val); 82 | } 83 | 84 | #endif /* _PLIC_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/proc.c: -------------------------------------------------------------------------------- 1 | #include "proc.h" 2 | 3 | i32 active_pid; 4 | struct proc proc_list[PROC_TOTAL_COUNT]; -------------------------------------------------------------------------------- /08-UART-Interrupt/proc.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROC_H 2 | #define _PROC_H 3 | 4 | #define PROC_NAME_MAXLEN 64 5 | #define PROC_TOTAL_COUNT 16 6 | 7 | #include "riscv_types.h" 8 | #include "riscv_cpu.h" 9 | 10 | enum proc_state 11 | { 12 | PROC_STATE_NONE = 0, 13 | PROC_STATE_READY, 14 | PROC_STATE_RUNNING, 15 | }; 16 | 17 | struct proc 18 | { 19 | enum proc_state state; 20 | u32 pid; 21 | u8 name[PROC_NAME_MAXLEN]; 22 | struct cpu cpu; 23 | u64 hartid; 24 | }; 25 | 26 | extern i32 active_pid; 27 | extern struct proc proc_list[PROC_TOTAL_COUNT]; 28 | 29 | #endif /* _PROC_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/riscv_arch.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ARCH_H 2 | #define _RISCV_ARCH_H 3 | 4 | #define MACHINE_BITS 64 5 | #define BITS_PER_LONG MACHINE_BITS 6 | 7 | #endif /* _RISCV_ARCH_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/riscv_asm.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ASM_H 2 | #define _RISCV_ASM_H 3 | 4 | #include "riscv_types.h" 5 | 6 | #define readu8(addr) (*(const u8 *)(addr)) 7 | #define readu16(addr) (*(const u16 *)(addr)) 8 | #define readu32(addr) (*(const u32 *)(addr)) 9 | #define readu64(addr) (*(const u64 *)(addr)) 10 | 11 | #define writeu8(addr, val) (*(u8 *)(addr) = (val)) 12 | #define writeu16(addr, val) (*(u16 *)(addr) = (val)) 13 | #define writeu32(addr, val) (*(u32 *)(addr) = (val)) 14 | #define writeu64(addr, val) (*(u64 *)(addr) = (val)) 15 | 16 | static inline void csrw_mtvec(const volatile u64 val) 17 | { 18 | asm volatile("csrw mtvec, %0" 19 | : 20 | : "r"(val)); 21 | } 22 | 23 | static inline void csrw_mie(const volatile u64 val) 24 | { 25 | asm volatile("csrw mie, %0" 26 | : 27 | : "r"(val)); 28 | } 29 | 30 | static inline u64 csrr_mstatus() 31 | { 32 | volatile u64 val; 33 | asm volatile("csrr %0, mstatus" 34 | : "=r"(val) 35 | :); 36 | return val; 37 | } 38 | 39 | static inline void csrw_mstatus(const volatile u64 val) 40 | { 41 | asm volatile("csrw mstatus, %0" 42 | : 43 | : "r"(val)); 44 | } 45 | 46 | static inline void csrs_mstatus(const volatile u64 val) 47 | { 48 | asm volatile("csrs mstatus, %0" 49 | : 50 | : "r"(val)); 51 | } 52 | 53 | static inline void csrc_mstatus(const volatile u64 val) 54 | { 55 | asm volatile("csrc mstatus, %0" 56 | : 57 | : "r"(val)); 58 | } 59 | 60 | static inline u64 csrr_mcause() 61 | { 62 | volatile u64 val; 63 | asm volatile("csrr %0, mcause" 64 | : "=r"(val) 65 | :); 66 | return val; 67 | } 68 | 69 | static inline u64 csrr_mepc() 70 | { 71 | volatile u64 val; 72 | asm volatile("csrr %0, mepc" 73 | : "=r"(val) 74 | :); 75 | return val; 76 | } 77 | 78 | static inline void ecall() 79 | { 80 | asm volatile("ecall" 81 | : 82 | :); 83 | } 84 | 85 | #endif -------------------------------------------------------------------------------- /08-UART-Interrupt/riscv_cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef _CPU_H 2 | #define _CPU_H 3 | 4 | #include "riscv_types.h" 5 | 6 | struct cpu 7 | { 8 | u64 x0; // zero 9 | u64 x1; // ra 10 | u64 x2; // sp 11 | u64 x3; // gp 12 | u64 x4; // tp 13 | u64 x5; // t0 14 | u64 x6; // t1 15 | u64 x7; // t2 16 | u64 x8; // s0/fp 17 | u64 x9; // s1 18 | u64 x10; // a0 19 | u64 x11; // a1 20 | u64 x12; // a2 21 | u64 x13; // a3 22 | u64 x14; // a4 23 | u64 x15; // a5 24 | u64 x16; // a6 25 | u64 x17; // a7 26 | u64 x18; // s2 27 | u64 x19; // s3 28 | u64 x20; // s4 29 | u64 x21; // s5 30 | u64 x22; // s6 31 | u64 x23; // s7 32 | u64 x24; // s8 33 | u64 x25; // s9 34 | u64 x26; // s10 35 | u64 x27; // s11 36 | u64 x28; // t3 37 | u64 x29; // t4 38 | u64 x30; // t5 39 | u64 x31; // t6 40 | 41 | u64 pc; // pc 42 | } __attribute__((packed)); 43 | 44 | #endif /* _CPU_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/riscv_priv.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_PRIV_H 2 | #define _RISCV_PRIV_H 3 | 4 | #include "riscv_asm.h" 5 | 6 | #define MSTATUS_SPP_U (0x1UL << 8) 7 | 8 | static inline void mstatus_set_spp_to_u(void) 9 | { 10 | u64 mstatus = csrr_mstatus(); 11 | mstatus &= ~MSTATUS_SPP_U; 12 | mstatus |= MSTATUS_SPP_U; 13 | csrw_mstatus(mstatus); 14 | } 15 | 16 | #endif /* _RISCV_PRIV_H */ -------------------------------------------------------------------------------- /08-UART-Interrupt/riscv_types.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_TYPES_H 2 | #define _RISCV_TYPES_H 3 | 4 | #include "kbool.h" 5 | #include "riscv_arch.h" 6 | 7 | typedef unsigned char u8; 8 | typedef unsigned short u16; 9 | typedef unsigned int u32; 10 | typedef unsigned long u64; 11 | 12 | typedef signed char i8; 13 | typedef signed short i16; 14 | typedef signed int i32; 15 | typedef signed long i64; 16 | 17 | #endif -------------------------------------------------------------------------------- /08-UART-Interrupt/sifive_u.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | 3 | / { 4 | #address-cells = <0x02>; 5 | #size-cells = <0x02>; 6 | compatible = "sifive,hifive-unleashed-a00"; 7 | model = "SiFive HiFive Unleashed A00"; 8 | 9 | chosen { 10 | bootargs = [00]; 11 | stdout-path = "/soc/serial@10010000"; 12 | }; 13 | 14 | aliases { 15 | serial0 = "/soc/serial@10010000"; 16 | ethernet0 = "/soc/ethernet@10090000"; 17 | }; 18 | 19 | gpio-restart { 20 | compatible = "gpio-restart"; 21 | gpios = <0x07 0x0a 0x01>; 22 | }; 23 | 24 | cpus { 25 | #address-cells = <0x01>; 26 | #size-cells = <0x00>; 27 | timebase-frequency = <0x989680>; 28 | 29 | cpu@0 { 30 | device_type = "cpu"; 31 | reg = <0x00>; 32 | status = "okay"; 33 | compatible = "riscv"; 34 | riscv,isa = "rv64imacu"; 35 | 36 | interrupt-controller { 37 | #interrupt-cells = <0x01>; 38 | interrupt-controller; 39 | compatible = "riscv,cpu-intc"; 40 | phandle = <0x04>; 41 | }; 42 | }; 43 | 44 | cpu@1 { 45 | device_type = "cpu"; 46 | reg = <0x01>; 47 | status = "okay"; 48 | compatible = "riscv"; 49 | riscv,isa = "rv64imafdcsu"; 50 | mmu-type = "riscv,sv48"; 51 | 52 | interrupt-controller { 53 | #interrupt-cells = <0x01>; 54 | interrupt-controller; 55 | compatible = "riscv,cpu-intc"; 56 | phandle = <0x03>; 57 | }; 58 | }; 59 | }; 60 | 61 | memory@80000000 { 62 | device_type = "memory"; 63 | reg = <0x00 0x80000000 0x00 0x8000000>; 64 | }; 65 | 66 | rtcclk { 67 | #clock-cells = <0x00>; 68 | compatible = "fixed-clock"; 69 | clock-frequency = <0xf4240>; 70 | clock-output-names = "rtcclk"; 71 | phandle = <0x02>; 72 | }; 73 | 74 | hfclk { 75 | #clock-cells = <0x00>; 76 | compatible = "fixed-clock"; 77 | clock-frequency = <0x1fca055>; 78 | clock-output-names = "hfclk"; 79 | phandle = <0x01>; 80 | }; 81 | 82 | soc { 83 | #address-cells = <0x02>; 84 | #size-cells = <0x02>; 85 | compatible = "simple-bus"; 86 | ranges; 87 | 88 | serial@10010000 { 89 | interrupts = <0x04>; 90 | interrupt-parent = <0x06>; 91 | clocks = <0x05 0x03>; 92 | reg = <0x00 0x10010000 0x00 0x1000>; 93 | compatible = "sifive,uart0"; 94 | }; 95 | 96 | ethernet@10090000 { 97 | #size-cells = <0x00>; 98 | #address-cells = <0x01>; 99 | local-mac-address = [52 54 00 12 34 56]; 100 | clock-names = "pclk\0hclk"; 101 | clocks = <0x05 0x02 0x05 0x02>; 102 | interrupts = <0x35>; 103 | interrupt-parent = <0x06>; 104 | phy-handle = <0x08>; 105 | phy-mode = "gmii"; 106 | reg-names = "control"; 107 | reg = <0x00 0x10090000 0x00 0x2000 0x00 0x100a0000 0x00 0x1000>; 108 | compatible = "sifive,fu540-c000-gem"; 109 | 110 | ethernet-phy@0 { 111 | reg = <0x00>; 112 | phandle = <0x08>; 113 | }; 114 | }; 115 | 116 | cache-controller@2010000 { 117 | compatible = "sifive,fu540-c000-ccache"; 118 | cache-block-size = <0x40>; 119 | cache-level = <0x02>; 120 | cache-sets = <0x400>; 121 | cache-size = <0x200000>; 122 | cache-unified; 123 | interrupt-parent = <0x06>; 124 | interrupts = <0x01 0x02 0x03>; 125 | reg = <0x00 0x2010000 0x00 0x1000>; 126 | }; 127 | 128 | dma@3000000 { 129 | compatible = "sifive,fu540-c000-pdma"; 130 | reg = <0x00 0x3000000 0x00 0x100000>; 131 | interrupt-parent = <0x06>; 132 | interrupts = <0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e>; 133 | #dma-cells = <0x01>; 134 | }; 135 | 136 | gpio@10060000 { 137 | compatible = "sifive,gpio0"; 138 | interrupt-parent = <0x06>; 139 | interrupts = <0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x12 0x13 0x14 0x15 0x16>; 140 | reg = <0x00 0x10060000 0x00 0x1000>; 141 | gpio-controller; 142 | #gpio-cells = <0x02>; 143 | interrupt-controller; 144 | #interrupt-cells = <0x02>; 145 | clocks = <0x05 0x03>; 146 | phandle = <0x07>; 147 | }; 148 | 149 | interrupt-controller@c000000 { 150 | phandle = <0x06>; 151 | riscv,ndev = <0x35>; 152 | reg = <0x00 0xc000000 0x00 0x4000000>; 153 | interrupts-extended = <0x04 0x0b 0x03 0x0b 0x03 0x09>; 154 | interrupt-controller; 155 | compatible = "riscv,plic0"; 156 | #interrupt-cells = <0x01>; 157 | }; 158 | 159 | clock-controller@10000000 { 160 | compatible = "sifive,fu540-c000-prci"; 161 | reg = <0x00 0x10000000 0x00 0x1000>; 162 | clocks = <0x01 0x02>; 163 | #clock-cells = <0x01>; 164 | phandle = <0x05>; 165 | }; 166 | 167 | otp@10070000 { 168 | compatible = "sifive,fu540-c000-otp"; 169 | reg = <0x00 0x10070000 0x00 0x1000>; 170 | fuse-count = <0x1000>; 171 | }; 172 | 173 | clint@2000000 { 174 | interrupts-extended = <0x04 0x03 0x04 0x07 0x03 0x03 0x03 0x07>; 175 | reg = <0x00 0x2000000 0x00 0x10000>; 176 | compatible = "riscv,clint0"; 177 | }; 178 | }; 179 | }; 180 | -------------------------------------------------------------------------------- /08-UART-Interrupt/test_processes.c: -------------------------------------------------------------------------------- 1 | #include "kdef.h" 2 | #include "uart.h" 3 | 4 | #define INTERVAL 100000000 5 | 6 | u8 test_proc_0_stack[1 << 20]; 7 | void *test_proc_0_stack_top = &test_proc_0_stack[sizeof(test_proc_0_stack) - 1]; 8 | 9 | void test_proc_0_entry() 10 | { 11 | while (true) 12 | { 13 | for (size_t i = 0; i < INTERVAL; i++) 14 | { 15 | } 16 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 0); 17 | } 18 | } 19 | 20 | u8 test_proc_1_stack[1 << 20]; 21 | void *test_proc_1_stack_top = &test_proc_1_stack[sizeof(test_proc_1_stack) - 1]; 22 | 23 | void test_proc_1_entry() 24 | { 25 | while (true) 26 | { 27 | for (size_t i = 0; i < INTERVAL; i++) 28 | { 29 | } 30 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 1); 31 | } 32 | } 33 | 34 | u8 test_proc_2_stack[1 << 20]; 35 | void *test_proc_2_stack_top = &test_proc_2_stack[sizeof(test_proc_2_stack) - 1]; 36 | void test_proc_2_entry() 37 | { 38 | while (true) 39 | { 40 | for (size_t i = 0; i < INTERVAL; i++) 41 | { 42 | } 43 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 2); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /08-UART-Interrupt/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef _TIMER_H 2 | #define _TIMER_H 3 | 4 | #include "riscv_asm.h" 5 | 6 | #define MTIME 0x200bff8 7 | #define MTIMECMP_0 0x2004000 8 | 9 | static inline u64 mtime() 10 | { 11 | return readu64(MTIME); 12 | } 13 | 14 | static inline u64 mtimecmp_0() 15 | { 16 | return readu64(MTIMECMP_0); 17 | } 18 | 19 | static inline u64 set_timeout(u64 timeout) 20 | { 21 | writeu64(MTIMECMP_0, mtime() + timeout); 22 | } 23 | 24 | #endif -------------------------------------------------------------------------------- /08-UART-Interrupt/trap.s: -------------------------------------------------------------------------------- 1 | .equ REGSZ, 8 2 | .global trap_entry 3 | 4 | .text 5 | 6 | trap_entry: 7 | 8 | # swap x5/mscratch 9 | csrrw x5, mscratch, x5 10 | # use x5 as cpu state base address register 11 | la x5, trap_cpu 12 | # save general purpose registers 13 | # x0 ~ x4 14 | sd x0, (0 * REGSZ)(x5) 15 | sd x1, (1 * REGSZ)(x5) 16 | sd x2, (2 * REGSZ)(x5) 17 | sd x3, (3 * REGSZ)(x5) 18 | sd x4, (4 * REGSZ)(x5) 19 | # save origin x5 by x1, which has been saved 20 | csrr x1, mscratch 21 | sd x1, (5 * REGSZ)(x5) 22 | # x6 ~ x31 23 | sd x6, (6 * REGSZ)(x5) 24 | sd x7, (7 * REGSZ)(x5) 25 | sd x8, (8 * REGSZ)(x5) 26 | sd x9, (9 * REGSZ)(x5) 27 | sd x10, (10 * REGSZ)(x5) 28 | sd x11, (11 * REGSZ)(x5) 29 | sd x12, (12 * REGSZ)(x5) 30 | sd x13, (13 * REGSZ)(x5) 31 | sd x14, (14 * REGSZ)(x5) 32 | sd x15, (15 * REGSZ)(x5) 33 | sd x16, (16 * REGSZ)(x5) 34 | sd x17, (17 * REGSZ)(x5) 35 | sd x18, (18 * REGSZ)(x5) 36 | sd x19, (19 * REGSZ)(x5) 37 | sd x20, (20 * REGSZ)(x5) 38 | sd x21, (21 * REGSZ)(x5) 39 | sd x22, (22 * REGSZ)(x5) 40 | sd x23, (23 * REGSZ)(x5) 41 | sd x24, (24 * REGSZ)(x5) 42 | sd x25, (25 * REGSZ)(x5) 43 | sd x26, (26 * REGSZ)(x5) 44 | sd x27, (27 * REGSZ)(x5) 45 | sd x28, (28 * REGSZ)(x5) 46 | sd x29, (29 * REGSZ)(x5) 47 | sd x30, (30 * REGSZ)(x5) 48 | sd x31, (31 * REGSZ)(x5) 49 | # save privilege registers 50 | # save mepc by x1, which has been saved 51 | csrr x1, mepc 52 | sd x1, (32 * REGSZ)(x5) 53 | 54 | # call trap_handler 55 | # Need set stack pointer? 56 | call trap_handler 57 | 58 | # use x5 as cpu state base address register 59 | la x5, trap_cpu 60 | # restore privilege registers 61 | # restore mepc by x1, which will be restored later 62 | ld x1, (32 * REGSZ)(x5) 63 | csrw mepc, x1 64 | # restore general purpose registers 65 | # x0 ~ x4 66 | ld x0, (0 * REGSZ)(x5) 67 | ld x1, (1 * REGSZ)(x5) 68 | ld x2, (2 * REGSZ)(x5) 69 | ld x3, (3 * REGSZ)(x5) 70 | ld x4, (4 * REGSZ)(x5) 71 | # postpone the restoration of x5 72 | # because it is being used as the base address register 73 | # x6 ~ x31 74 | ld x6, (6 * REGSZ)(x5) 75 | ld x7, (7 * REGSZ)(x5) 76 | ld x8, (8 * REGSZ)(x5) 77 | ld x9, (9 * REGSZ)(x5) 78 | ld x10, (10 * REGSZ)(x5) 79 | ld x11, (11 * REGSZ)(x5) 80 | ld x12, (12 * REGSZ)(x5) 81 | ld x13, (13 * REGSZ)(x5) 82 | ld x14, (14 * REGSZ)(x5) 83 | ld x15, (15 * REGSZ)(x5) 84 | ld x16, (16 * REGSZ)(x5) 85 | ld x17, (17 * REGSZ)(x5) 86 | ld x18, (18 * REGSZ)(x5) 87 | ld x19, (19 * REGSZ)(x5) 88 | ld x20, (20 * REGSZ)(x5) 89 | ld x21, (21 * REGSZ)(x5) 90 | ld x22, (22 * REGSZ)(x5) 91 | ld x23, (23 * REGSZ)(x5) 92 | ld x24, (24 * REGSZ)(x5) 93 | ld x25, (25 * REGSZ)(x5) 94 | ld x26, (26 * REGSZ)(x5) 95 | ld x27, (27 * REGSZ)(x5) 96 | ld x28, (28 * REGSZ)(x5) 97 | ld x29, (29 * REGSZ)(x5) 98 | ld x30, (30 * REGSZ)(x5) 99 | ld x31, (31 * REGSZ)(x5) 100 | # x5 101 | ld x5, (6 * REGSZ)(x5) 102 | 103 | mret 104 | # j trap_entry 105 | -------------------------------------------------------------------------------- /08-UART-Interrupt/trap_handler.c: -------------------------------------------------------------------------------- 1 | #include "riscv_cpu.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "proc.h" 5 | #include "interrupts.h" 6 | #include "riscv_priv.h" 7 | 8 | struct cpu trap_cpu; 9 | 10 | void trap_handler() 11 | { 12 | u64 mcause = csrr_mcause(); 13 | switch (mcause) 14 | { 15 | case MCAUSE_INTR_M_TIMER: 16 | { 17 | // there exists runnable processes 18 | if (proc_list[0].state != PROC_STATE_NONE) 19 | { 20 | // assume proc-0 is the active process if there is no active process 21 | if (active_pid < 0) 22 | { 23 | active_pid = 0; 24 | trap_cpu = proc_list[0].cpu; 25 | uart_printf("[Trap - M-mode Timer] Scheduler Init. Ticks: %ld\n", active_pid, mcause, mtime()); 26 | } 27 | 28 | // save cpu state for the active process 29 | proc_list[active_pid].cpu = trap_cpu; 30 | // suspend the active process 31 | proc_list[active_pid].state = PROC_STATE_READY; 32 | 33 | // iterate the processes from the next process, ending with the active process 34 | for (int ring_index = 1; ring_index <= PROC_TOTAL_COUNT; ring_index++) 35 | { 36 | int real_index = (active_pid + ring_index) % PROC_TOTAL_COUNT; 37 | struct proc *proc = &proc_list[real_index]; 38 | // run this process if it is ready 39 | if (proc->state == PROC_STATE_READY) 40 | { 41 | uart_printf("[Trap - M-mode Timer] Scheduler(Ticks = %ld): (PID = %d, PC = 0x%lx) => (PID = %d, PC = 0x%lx)\n", mtime(), active_pid, trap_cpu.pc, proc->pid, proc->cpu.pc); 42 | // mstatus_set_spp_to_u(); 43 | trap_cpu = proc->cpu; 44 | active_pid = proc->pid; 45 | break; 46 | } 47 | } 48 | } 49 | set_timeout(10000000); 50 | break; 51 | } 52 | 53 | case MCAUSE_INTR_M_EXTER: 54 | { 55 | uart_printf("[Trap - M-mode Exter] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, mcause, mtime()); 56 | break; 57 | } 58 | 59 | case MCAUSE_INNER_M_ILLEAGEL_INSTRUCTION: 60 | { 61 | uart_printf("[Trap - M-mode Illeagel Instruction] active_pid: %d, mcause: 0x%lX, mepc: %lx\n", active_pid, mcause, csrr_mepc()); 62 | break; 63 | } 64 | 65 | default: 66 | { 67 | uart_printf("[Trap - Default] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, mcause, mtime()); 68 | break; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /08-UART-Interrupt/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _UART_H 2 | #define _UART_H 3 | 4 | #include "riscv_types.h" 5 | #include "riscv_asm.h" 6 | #include 7 | 8 | #define UART_BASE 0x10010000 9 | 10 | #define UART_TXDATA_OFFSET 0x00 11 | #define UART_RXDATA_OFFSET 0x04 12 | #define UART_TXCTRL_OFFSET 0x08 13 | #define UART_RXCTRL_OFFSET 0x0c 14 | #define UART_IE_OFFSET 0x10 15 | #define UART_IP_OFFSET 0x14 16 | #define UART_DIV_OFFSET 0x18 17 | 18 | #define UART_TXFIFO_FULL 0x80000000 19 | #define UART_RXFIFO_EMPTY 0x80000000 20 | #define UART_RXFIFO_DATA 0x000000ff 21 | #define UART_TXCTRL_TXEN 0x1 22 | #define UART_RXCTRL_RXEN 0x1 23 | 24 | #define PLATFORM_UART_INPUT_FREQ 10000000 25 | #define PLATFORM_UART_BAUDRATE 115200 26 | 27 | #define UART_IE_TXWM (1 << 0) 28 | #define UART_IE_RXWM (1 << 1) 29 | 30 | #define UART_REG(offset) (*(u32 *)(UART_BASE + offset)) 31 | 32 | static u8 *uart_base_addr = (u8 *)UART_BASE; 33 | 34 | static inline void set_reg(u32 offset, u32 val) 35 | { 36 | writeu32(uart_base_addr + offset, val); 37 | } 38 | 39 | static inline u32 get_reg(u32 offset) 40 | { 41 | return readu32(uart_base_addr + offset); 42 | } 43 | 44 | static inline void uart_putc(u32 ch) 45 | { 46 | while (get_reg(UART_TXDATA_OFFSET) & UART_TXFIFO_FULL) 47 | ; 48 | set_reg(UART_TXDATA_OFFSET, ch); 49 | } 50 | 51 | static inline u32 uart_getc() 52 | { 53 | return get_reg(UART_RXDATA_OFFSET); 54 | } 55 | 56 | static inline void uart_print(char *str) 57 | { 58 | while (*str) 59 | { 60 | uart_putc(*str++); 61 | } 62 | } 63 | 64 | static void uart_print_digit(unsigned long val, unsigned long base, bool uppercase) 65 | { 66 | const char *digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; 67 | 68 | if (val < base) 69 | { 70 | uart_putc(digits[val]); 71 | } 72 | else 73 | { 74 | uart_print_digit(val / base, base, uppercase); 75 | uart_putc(digits[val % base]); 76 | } 77 | } 78 | 79 | static inline void uart_print_int(int val, int base) 80 | { 81 | if (val < 0) 82 | { 83 | uart_putc('-'); 84 | val = -val; 85 | } 86 | uart_print_digit(val, base, false); 87 | } 88 | 89 | static inline void uart_print_uint(unsigned int val, int base) 90 | { 91 | uart_print_digit(val, base, false); 92 | } 93 | 94 | static inline void uart_print_uint_upper(unsigned int val, int base) 95 | { 96 | uart_print_digit(val, base, true); 97 | } 98 | 99 | static inline void uart_print_long(long val, int base) 100 | { 101 | if (val < 0) 102 | { 103 | uart_putc('-'); 104 | val = -val; 105 | } 106 | uart_print_digit(val, base, false); 107 | } 108 | 109 | static inline void uart_print_ulong(unsigned long val, int base) 110 | { 111 | uart_print_digit(val, base, false); 112 | } 113 | 114 | static inline void uart_print_ulong_upper(unsigned long val, int base) 115 | { 116 | uart_print_digit(val, base, true); 117 | } 118 | 119 | static inline void uart_printf(const char *format, ...) 120 | { 121 | va_list args; 122 | va_start(args, format); 123 | 124 | while (*format) 125 | { 126 | if (*format == '%') 127 | { 128 | format++; 129 | switch (*format) 130 | { 131 | case 'c': 132 | { 133 | char c = (char)va_arg(args, unsigned int); 134 | uart_putc(c); 135 | break; 136 | } 137 | case 's': 138 | { 139 | char *str = va_arg(args, char *); 140 | uart_print(str); 141 | break; 142 | } 143 | case 'd': 144 | { 145 | int val = va_arg(args, int); 146 | uart_print_int(val, 10); 147 | break; 148 | } 149 | case 'u': 150 | { 151 | unsigned int val = va_arg(args, unsigned int); 152 | uart_print_uint(val, 10); 153 | break; 154 | } 155 | case 'x': 156 | { 157 | unsigned int val = va_arg(args, unsigned int); 158 | uart_print_uint(val, 16); 159 | break; 160 | } 161 | case 'X': 162 | { 163 | unsigned int val = va_arg(args, unsigned int); 164 | uart_print_uint_upper(val, 16); 165 | break; 166 | } 167 | case 'l': 168 | { 169 | format++; 170 | switch (*format) 171 | { 172 | case 'd': 173 | { 174 | long val = va_arg(args, long); 175 | uart_print_long(val, 10); 176 | break; 177 | } 178 | case 'u': 179 | { 180 | unsigned long val = va_arg(args, unsigned long); 181 | uart_print_ulong(val, 10); 182 | break; 183 | } 184 | case 'x': 185 | { 186 | unsigned long val = va_arg(args, unsigned long); 187 | uart_print_ulong(val, 16); 188 | break; 189 | } 190 | case 'X': 191 | { 192 | unsigned long val = va_arg(args, unsigned long); 193 | uart_print_ulong_upper(val, 16); 194 | break; 195 | } 196 | default: 197 | uart_putc(*format); 198 | break; 199 | } 200 | break; 201 | } 202 | default: 203 | uart_putc(*format); 204 | break; 205 | } 206 | } 207 | else 208 | { 209 | uart_putc(*format); 210 | } 211 | format++; 212 | } 213 | 214 | va_end(args); 215 | } 216 | 217 | static inline void uart_init() 218 | { 219 | /* Configure baudrate */ 220 | set_reg(UART_DIV_OFFSET, 0); 221 | 222 | /* Disable interrupts */ 223 | set_reg(UART_IE_OFFSET, 0); 224 | 225 | /* Enable TX */ 226 | set_reg(UART_TXCTRL_OFFSET, UART_TXCTRL_TXEN); 227 | 228 | /* Enable Rx */ 229 | set_reg(UART_RXCTRL_OFFSET, UART_RXCTRL_RXEN); 230 | } 231 | 232 | static inline u32 _uart_ie() 233 | { 234 | get_reg(UART_IE_OFFSET); 235 | } 236 | 237 | static inline void _uart_ie_write(u32 val) 238 | { 239 | set_reg(UART_IE_OFFSET, val); 240 | } 241 | 242 | #endif -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/.gdbinit: -------------------------------------------------------------------------------- 1 | set architecture riscv:rv64 2 | set disassemble-next-line on 3 | target extended-remote localhost:1234 4 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/Makefile: -------------------------------------------------------------------------------- 1 | CROSS_COMPILE := riscv64-linux-gnu- 2 | CC := ${CROSS_COMPILE}gcc 3 | AS := ${CROSS_COMPILE}as 4 | LD := ${CROSS_COMPILE}ld 5 | OBJCOPY := ${CROSS_COMPILE}objcopy 6 | 7 | ASM_SRC := firmware_entry.s mtrap_entry.s strap_entry.s 8 | CLANG_SRC := firmware.c mtrap_handler.c strap_handler.c proc.c kstring.c test_processes.c kernel_stack.c 9 | GEN_HEADERS := riscv_asm_csr.gen.h 10 | HEADERS := riscv_arch.h riscv_asm.h uart.h timer.h riscv_priv.h \ 11 | proc.h kstring.h riscv_cpu.h ${GEN_HEADERS} 12 | OBJECTS = $(addprefix bin/,$(ASM_SRC:.s=.asmo)) 13 | OBJECTS += $(addprefix bin/,$(CLANG_SRC:.c=.clango)) 14 | 15 | all: bin/firmware.bin 16 | 17 | bin/firmware.bin: ${OBJECTS} ${HEADERS} firmware.ld 18 | ${LD} --no-relax -T firmware.ld ${OBJECTS} -o bin/firmware 19 | ${OBJCOPY} -O binary -S bin/firmware bin/firmware.bin 20 | 21 | bin/%.asmo: %.s 22 | ${AS} -g $< -o $@ 23 | 24 | bin/%.clango: %.c ${HEADERS} 25 | ${CC} -ffreestanding -c -O0 -g $< -o $@ 26 | 27 | %.gen.h: %.gen.h.py 28 | python3 $< > $@ 29 | 30 | run: bin/firmware.bin 31 | qemu-system-riscv64 -machine virt -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -display none 32 | 33 | debug: bin/firmware.bin 34 | qemu-system-riscv64 -machine virt -smp 2 -m 2G -serial stdio -bios bin/firmware.bin -s -S 35 | 36 | clean: 37 | rm -rf bin/ 38 | 39 | $(shell mkdir -p bin) -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/NOTES.md: -------------------------------------------------------------------------------- 1 | ### 为什么 mstatus 会包含 SIE/SPIE/SPP 字段?它们真的有实际作用吗? 2 | 事实上,只有一个物理 `status` 寄存器,`mstatus` 和 `sstatus` 看到的都是同一个物理 `status` 寄存器的视图!!! 3 | 4 | > Refer to: Volume II: RISC-V Privileged Architectures V20211203 P64 5 | > 6 | > The sstatus register is a subset of the mstatus register. 7 | > 8 | > In a straightforward implementation, reading or writing any field in sstatus is equivalent to reading or writing the homonymous field in mstatus. 9 | 10 | > Refer to: fu540-c000-manual-v1p4.pdf P59 11 | > 12 | > 8.4.2 Supervisor Status Register (sstatus) 13 | Similar to machine mode, supervisor mode has a register dedicated to keeping track of the hart’s current state called sstatus. sstatus is effectively a restricted view of mstatus, described in Section 8.3.1, in that changes made to sstatus are reflected in mstatus and viceversa, with the exception of the machine mode fields, which are not visible in sstatus. 14 | 15 | 实验测试: 16 | ``` 17 | [firmware_main] Clear MSTATUS_MIE | MSTATUS_SIE for mstatus 18 | [firmware_main] mstatus is 0x0 19 | [firmware_main] sstatus is 0x0 20 | [firmware_main] Set MSTATUS_SIE for mstatus 21 | [firmware_main] mstatus is 0x2 22 | [firmware_main] sstatus is 0x2 23 | [firmware_main] Clear MSTATUS_MIE | MSTATUS_SIE for mstatus 24 | [firmware_main] mstatus is 0x0 25 | [firmware_main] sstatus is 0x0 26 | [firmware_main] Set SSTATUS_SIE for sstatus 27 | [firmware_main] mstatus is 0x2 28 | [firmware_main] sstatus is 0x2 29 | ``` 30 | 31 | ### `sie/sip` 是 `mie/mip` 的视图,且在 `mideleg` 启用的位才对 `sie/sip` 可见! 32 | > Restricted views of the mip and mie registers appear as the sip and sie registers for supervisor level. If an interrupt is delegated to S-mode by setting a bit in the mideleg register, it becomes visible in the sip register and is maskable using the sie register. Otherwise, the corresponding bits in sip and sie are read-only zero. 33 | 34 | > Volume II: RISC-V Privileged Architectures V20211203 P68 35 | > The sip and sie registers are subsets of the mip and mie registers. Reading any implemented field, or writing any writable field, of sip/sie effects a read or write of the homonymous field of mip/mie. 36 | > 37 | > Bits 3, 7, and 11 of sip and sie correspond to the machine-mode software, timer, and external interrupts, respectively. Since most platforms will choose not to make these interrupts delegatable from M-mode to S-mode, they are shown as 0 in Figures 4.6 and 4.7. 38 | 39 | ### Traps never transition from a more-privileged mode to a less-privileged mode 40 | > Traps never transition from a more-privileged mode to a less-privileged mode. For example, if M-mode has delegated illegal instruction exceptions to S-mode, and M-mode software later executes an illegal instruction, the trap is taken in M-mode, rather than being delegated to S-mode. By contrast, traps may be taken horizontally. Using the same example, if M-mode has delegated illegal instruction exceptions to S-mode, and S-mode software later executes an illegal instruction, the trap is taken in S-mode. 41 | > 42 | > **What about M-mode timer interrupt?** 43 | > **What about M-mode software interrupt?** 44 | > **Is there S-mode timer interrupt?** 45 | > **Is the external interrupt in M-mode or S-mode?** 46 | 47 | ### Delegated interrupts result in the interrupt being masked at the delegator privilege level 48 | > Delegated interrupts result in the interrupt being masked at the delegator privilege level. For example, if the supervisor timer interrupt (STI) is delegated to S-mode by setting mideleg[5], STIs will not be taken when executing in M-mode. By contrast, if mideleg[5] is clear, STIs can be taken in any mode and regardless of current mode will transfer control to M-mode. 49 | 50 | 51 | ### mtime 和 mtimecmp 只属于机器模式: mtime & mtimecmp 52 | > Reter to: Volume II: RISC-V Privileged Architectures V20211203 P45 53 | > 54 | > Lower privilege levels do not have their own timecmp registers. Instead, machine-mode software can implement any number of virtual timers on a hart by multiplexing the next timer interrupt into the mtimecmp register. 55 | 56 | ### 何时触发 `M-mode external interrupt`? 57 | > Bits mip.MEIP and mie.MEIE are the interrupt-pending and interrupt-enable bits for machinelevel external interrupts. MEIP is read-only in mip, and is set and cleared by a **platform-specific interrupt controller**. 58 | 59 | ### 何时触发 `M-mode timer interrupt`? 60 | > Bits mip.MTIP and mie.MTIE are the interrupt-pending and interrupt-enable bits for machine timer interrupts. MTIP is read-only in mip, and is cleared by writing to the memory-mapped **machine-mode timer compare register**. 61 | 62 | ### 何时触发 `M-mode software interrut`? 63 | > Bits mip.MSIP and mie.MSIE are the interrupt-pending and interrupt-enable bits for machinelevel software interrupts. MSIP is read-only in mip, and is **written by accesses to memory-mapped control registers**, which are used by remote harts to provide machine-level interprocessor interrupts. A hart can write its own MSIP bit using the same memory-mapped control register. If a system has only one hart, or if a platform standard supports the delivery of machine-level interprocessor 64 | interrupts through external interrupts (MEI) instead, then mip.MSIP and mie.MSIE may both be read-only zeros. 65 | 66 | ### 何时触发 `S-mode external interrupt`? 67 | > If supervisor mode is implemented, bits mip.SEIP and mie.SEIE are the interrupt-pending and interrupt-enable bits for supervisor-level external interrupts. SEIP is writable in mip, and may be **written by M-mode software to indicate to S-mode that an external interrupt is pending**. Additionally, the **platform-level interrupt controller may generate supervisor-level external interrupts**. Supervisor-level external interrupts are made pending based on the logical-OR of the softwarewritable SEIP bit and the signal from the external interrupt controller. When mip is read with a CSR instruction, the value of the SEIP bit returned in the rd destination register is the logical-OR of the software-writable bit and the interrupt signal from the interrupt controller, but the signal from the interrupt controller is not used to calculate the value written to SEIP. Only the software-writable SEIP bit participates in the read-modify-write sequence of a CSRRS or CSRRC instruction. 68 | 69 | ### 何时触发 `S-mode timer interrupt`? 70 | > If supervisor mode is implemented, bits mip.STIP and mie.STIE are the interrupt-pending and interrupt-enable bits for supervisor-level timer interrupts. STIP is writable in mip, and may be written by M-mode software to deliver timer interrupts to S-mode. 71 | 72 | ### 何时触发 `S-mode software interrupt`? 73 | > If supervisor mode is implemented, bits mip.SSIP and mie.SSIE are the interrupt-pending and interrupt-enable bits for supervisor-level software interrupts. SSIP is writable in mip and may also be set to 1 by a platform-specific interrupt controller. 74 | 75 | 76 | ### ecall 77 | > ECALL generates a different exception for each originating privilege mode so that environment call exceptions can be selectively delegated. A typical use case for Unix-like operating systems is to delegate to S-mode the environment-call-from-U-mode exception but not the others. 78 | 79 | ### sstatus 80 | > sstatus 81 | > 82 | > The SIE bit enables or disables all interrupts in supervisor mode. When SIE is clear, interrupts are not taken while in supervisor mode. When the hart is running in user-mode, the value in SIE is ignored, and supervisor-level interrupts are enabled. The supervisor can disable individual interrupt sources using the sie CSR. The SPIE bit indicates whether supervisor interrupts were enabled prior to trapping into supervisor mode. When a trap is taken into supervisor mode, SPIE is set to SIE, and SIE is set to 0. When an SRET instruction is executed, SIE is set to SPIE, then SPIE is set to 1. 83 | 84 | ### When trap to M-mode? 85 | > An interrupt i will trap to M-mode (causing the privilege mode to change to M-mode) if all of the following are true: (a) either the current privilege mode is M and the MIE bit in the mstatus register is set, or the current privilege mode has less privilege than M-mode; (b) bit i is set in both mip and mie; and (c) if register mideleg exists, bit i is not set in mideleg. 86 | > 87 | > Interrupts to M-mode take priority over any interrupts to lower privilege modes. 88 | 89 | 90 | ### When trap to S-mode? 91 | > An interrupt i will trap to S-mode if both of the following are true: (a) either the current privilege mode is S and the SIE bit in the sstatus register is set, or the current privilege mode has less privilege than S-mode; and (b) bit i is set in both sip and sie. 92 | > 93 | > Interrupts to S-mode take priority over any interrupts to lower privilege modes. 94 | > 95 | > Each standard interrupt type (SEI, STI, or SSI) may not be implemented, in which case the corresponding interrupt-pending and interrupt-enable bits are read-only zeros. All bits in sip and sie are WARL fields. The implemented interrupts may be found by writing one to every bit location in sie, then reading back to see which bit positions hold a one. 96 | 97 | ### Higher-privilege-level code can disable selected higher-privilege-mode before ceding control to a lower-privilege mode 98 | > When a hart is executing in privilege mode x, interrupts are globally enabled when xIE=1 and globally disabled when xIE=0. Interrupts for lower-privilege modes, wx, are always globally enabled regardless of the setting of the global yIE bit for the higher-privilege mode. Higher-privilege-level code can use separate per-interrupt enable bits to disable selected higher-privilege-mode interrupts before ceding control to a lower-privilege mode. 99 | 100 | ### 如何把时钟中断信息传递给 S 模式? 101 | 102 | 理想情况是 103 | - 当 M 模式时钟中断发生时,在 M 模式中断处理程序中设置 mip.STIP,然后 mret 104 | - 之后会触发 S 模式中断处理程序,S 模式会看到 sip.STIP,并处理时钟中断,处理完毕之后清楚 sip.STIP 105 | 106 | 遇到的问题是,S 模式中断处理程序没法清除 sip.SITP ???可能是实现错误??? 107 | 108 | 解决方案:M 模式中断处理程序 设置 mip.STIP 和 mie.STIE;S 模式中断处理程序处理完毕之后清楚 sie.STIE 以达到间接关闭 STIE 的目的 109 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/README.md: -------------------------------------------------------------------------------- 1 | # 09: Enter Supervisor Mode 2 | 3 | todo -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/firmware.c: -------------------------------------------------------------------------------- 1 | #include "riscv_asm.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "riscv_priv.h" 5 | #include "proc.h" 6 | #include "kstring.h" 7 | 8 | extern void mtrap_entry(); 9 | extern void strap_entry(); 10 | extern void test_proc_0_entry(); 11 | extern void test_proc_1_entry(); 12 | extern void test_proc_2_entry(); 13 | extern void *test_proc_0_stack_top; 14 | extern void *test_proc_1_stack_top; 15 | extern void *test_proc_2_stack_top; 16 | 17 | void proc_init() 18 | { 19 | struct proc test_proc_0 = { 20 | .name = "test_proc_0", 21 | .pid = 0, 22 | .hartid = 0, 23 | .state = PROC_STATE_READY, 24 | .cpu = { 25 | .pc = (u64)test_proc_0_entry, 26 | .x2 = (u64)test_proc_0_stack_top, 27 | }}; 28 | uart_printf("[proc_init] test_proc_0: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_0, test_proc_0_stack_top); 29 | proc_list[0] = test_proc_0; 30 | 31 | struct proc test_proc_1 = { 32 | .name = "test_proc_1", 33 | .pid = 1, 34 | .hartid = 0, 35 | .state = PROC_STATE_READY, 36 | .cpu = { 37 | .pc = (u64)test_proc_1_entry, 38 | .x2 = (u64)test_proc_1_stack_top, 39 | }}; 40 | uart_printf("[proc_init] test_proc_1: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_1, test_proc_1_stack_top); 41 | proc_list[1] = test_proc_1; 42 | 43 | struct proc test_proc_2 = { 44 | .name = "test_proc_2", 45 | .pid = 2, 46 | .hartid = 0, 47 | .state = PROC_STATE_READY, 48 | .cpu = { 49 | .pc = (u64)test_proc_2_entry, 50 | .x2 = (u64)test_proc_2_stack_top, 51 | }}; 52 | uart_printf("[proc_init] test_proc_2: pc is 0x%lx, stack_top is 0x%lx\n", &test_proc_2, test_proc_2_stack_top); 53 | proc_list[2] = test_proc_2; 54 | 55 | for (int i = 3; i < PROC_TOTAL_COUNT; i++) 56 | { 57 | memset(&proc_list[i], 0, sizeof(proc_list[i])); 58 | proc_list[i].state = PROC_STATE_NONE; 59 | } 60 | 61 | active_pid = -1; 62 | } 63 | 64 | void __attribute__((naked)) infi() 65 | { 66 | while (1) 67 | { 68 | } 69 | } 70 | 71 | void firmware_main() 72 | { 73 | // initialize UART 74 | uart_init(); 75 | 76 | // print initial status 77 | uart_printf("[firmware_main] mstatus: 0x%x\n", csrr_mstatus()); 78 | uart_printf("[firmware_main] mie: 0x%x\n", csrr_mie()); 79 | uart_printf("[firmware_main] mip: 0x%x\n", csrr_mip()); 80 | uart_printf("[firmware_main] mtimecmp_0: %ld\n", mtimecmp_0()); 81 | 82 | // prepare processes 83 | proc_init(); 84 | 85 | // setup timer timout 86 | set_timeout(10000000); 87 | 88 | // setup S-mode trap vector 89 | csrw_stvec((u64)strap_entry); 90 | 91 | // setup M-mode trap vector 92 | csrw_mtvec((u64)mtrap_entry); 93 | // enable M-mode timer interrupt 94 | csrw_mie(MIE_MTIE | MIE_SEIE | MIE_SSIE | MIE_STIE); 95 | // delegate all possible interrupts and exceptions 96 | csrw_mideleg(~0UL); 97 | csrw_medeleg(~0UL); 98 | uart_printf("[firmware_main] mideleg: 0x%lx\n", csrr_mideleg()); 99 | uart_printf("[firmware_main] medeleg: 0x%lx\n", csrr_medeleg()); 100 | uart_printf("[firmware_main] mie: 0x%lx\n", csrr_mie()); 101 | uart_printf("[firmware_main] sie: 0x%lx\n", csrr_sie()); 102 | 103 | // setup physical memory protection 104 | // give S-mode access to the whole address memory space 105 | csrw_pmpaddr0(~0UL); 106 | csrw_pmpcfg0(0x0f); 107 | 108 | // set MPP 109 | csrs_mstatus(MSTATUS_MPIE | MSTATUS_MPP_U); 110 | csrw_mepc((u64)infi); 111 | asm volatile("mret"); 112 | } -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/firmware.ld: -------------------------------------------------------------------------------- 1 | ENTRY(firmware_entry) 2 | 3 | MEMORY {} /* default */ 4 | 5 | . = 0x80000000; 6 | 7 | SECTIONS { 8 | } -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/firmware_entry.s: -------------------------------------------------------------------------------- 1 | .global firmware_entry 2 | 3 | .text 4 | firmware_entry: 5 | bne a0, x0, firmware_entry # loop if hartid is not 0 6 | la t1, kernel_stack_top 7 | ld sp, 0(t1) # setup stack pointer 8 | j firmware_main # jump to c entry 9 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/kernel_stack.c: -------------------------------------------------------------------------------- 1 | #include "riscv_arch.h" 2 | 3 | u8 kernel_stack[1 << 20]; 4 | void *kernel_stack_top = &kernel_stack[sizeof(kernel_stack) - 1]; -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/kstring.c: -------------------------------------------------------------------------------- 1 | #include "kstring.h" 2 | 3 | void *memset(void *s, int c, size_t n) 4 | { 5 | unsigned char *p = s; 6 | while (n--) 7 | *p++ = (unsigned char)c; 8 | return s; 9 | } 10 | 11 | void *memcpy(void *dest, const void *src, size_t n) 12 | { 13 | unsigned char *d = dest; 14 | const unsigned char *s = src; 15 | while (n--) 16 | *d++ = *s++; 17 | return dest; 18 | } 19 | 20 | size_t strlen(const char *str) 21 | { 22 | size_t length = 0; 23 | while (*str != '\0') 24 | { 25 | length++; 26 | str++; 27 | } 28 | return length; 29 | } 30 | 31 | char *strcpy(char *dest, const char *src) 32 | { 33 | char *p = dest; 34 | while ((*p++ = *src++) != '\0') 35 | ; 36 | return dest; 37 | } 38 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/kstring.h: -------------------------------------------------------------------------------- 1 | #ifndef _KSTRING_H 2 | #define _KSTRING_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | void *memset(void *s, int c, size_t n); 7 | void *memcpy(void *dest, const void *src, size_t n); 8 | long unsigned int strlen(const char *str); 9 | char *strcpy(char *dest, const char *src); 10 | 11 | #endif /* _KSTRING_H */ -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/mtrap_entry.s: -------------------------------------------------------------------------------- 1 | .equ REGSZ, 8 2 | .global mtrap_entry 3 | 4 | .text 5 | 6 | mtrap_entry: 7 | 8 | # swap x5/mscratch 9 | csrrw x5, mscratch, x5 10 | # use x5 as cpu state base address register 11 | la x5, mtrap_cpu 12 | # save general purpose registers 13 | # x0 ~ x4 14 | sd x0, (0 * REGSZ)(x5) 15 | sd x1, (1 * REGSZ)(x5) 16 | sd x2, (2 * REGSZ)(x5) 17 | sd x3, (3 * REGSZ)(x5) 18 | sd x4, (4 * REGSZ)(x5) 19 | # save origin x5 by x1, which has been saved 20 | csrr x1, mscratch 21 | sd x1, (5 * REGSZ)(x5) 22 | # x6 ~ x31 23 | sd x6, (6 * REGSZ)(x5) 24 | sd x7, (7 * REGSZ)(x5) 25 | sd x8, (8 * REGSZ)(x5) 26 | sd x9, (9 * REGSZ)(x5) 27 | sd x10, (10 * REGSZ)(x5) 28 | sd x11, (11 * REGSZ)(x5) 29 | sd x12, (12 * REGSZ)(x5) 30 | sd x13, (13 * REGSZ)(x5) 31 | sd x14, (14 * REGSZ)(x5) 32 | sd x15, (15 * REGSZ)(x5) 33 | sd x16, (16 * REGSZ)(x5) 34 | sd x17, (17 * REGSZ)(x5) 35 | sd x18, (18 * REGSZ)(x5) 36 | sd x19, (19 * REGSZ)(x5) 37 | sd x20, (20 * REGSZ)(x5) 38 | sd x21, (21 * REGSZ)(x5) 39 | sd x22, (22 * REGSZ)(x5) 40 | sd x23, (23 * REGSZ)(x5) 41 | sd x24, (24 * REGSZ)(x5) 42 | sd x25, (25 * REGSZ)(x5) 43 | sd x26, (26 * REGSZ)(x5) 44 | sd x27, (27 * REGSZ)(x5) 45 | sd x28, (28 * REGSZ)(x5) 46 | sd x29, (29 * REGSZ)(x5) 47 | sd x30, (30 * REGSZ)(x5) 48 | sd x31, (31 * REGSZ)(x5) 49 | # save privilege registers 50 | # save mepc by x1, which has been saved 51 | csrr x1, mepc 52 | sd x1, (32 * REGSZ)(x5) 53 | 54 | # call trap_handler 55 | # Need set stack pointer? 56 | la t0, mtrap_stack_top 57 | ld sp, 0(t0) 58 | call mtrap_handler 59 | 60 | # use x5 as cpu state base address register 61 | la x5, mtrap_cpu 62 | # restore privilege registers 63 | # restore mepc by x1, which will be restored later 64 | ld x1, (32 * REGSZ)(x5) 65 | csrw mepc, x1 66 | # restore general purpose registers 67 | # x0 ~ x4 68 | ld x0, (0 * REGSZ)(x5) 69 | ld x1, (1 * REGSZ)(x5) 70 | ld x2, (2 * REGSZ)(x5) 71 | ld x3, (3 * REGSZ)(x5) 72 | ld x4, (4 * REGSZ)(x5) 73 | # postpone the restoration of x5 74 | # because it is being used as the base address register 75 | # x6 ~ x31 76 | ld x6, (6 * REGSZ)(x5) 77 | ld x7, (7 * REGSZ)(x5) 78 | ld x8, (8 * REGSZ)(x5) 79 | ld x9, (9 * REGSZ)(x5) 80 | ld x10, (10 * REGSZ)(x5) 81 | ld x11, (11 * REGSZ)(x5) 82 | ld x12, (12 * REGSZ)(x5) 83 | ld x13, (13 * REGSZ)(x5) 84 | ld x14, (14 * REGSZ)(x5) 85 | ld x15, (15 * REGSZ)(x5) 86 | ld x16, (16 * REGSZ)(x5) 87 | ld x17, (17 * REGSZ)(x5) 88 | ld x18, (18 * REGSZ)(x5) 89 | ld x19, (19 * REGSZ)(x5) 90 | ld x20, (20 * REGSZ)(x5) 91 | ld x21, (21 * REGSZ)(x5) 92 | ld x22, (22 * REGSZ)(x5) 93 | ld x23, (23 * REGSZ)(x5) 94 | ld x24, (24 * REGSZ)(x5) 95 | ld x25, (25 * REGSZ)(x5) 96 | ld x26, (26 * REGSZ)(x5) 97 | ld x27, (27 * REGSZ)(x5) 98 | ld x28, (28 * REGSZ)(x5) 99 | ld x29, (29 * REGSZ)(x5) 100 | ld x30, (30 * REGSZ)(x5) 101 | ld x31, (31 * REGSZ)(x5) 102 | # x5 103 | ld x5, (6 * REGSZ)(x5) 104 | 105 | mret 106 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/mtrap_handler.c: -------------------------------------------------------------------------------- 1 | #include "riscv_cpu.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "proc.h" 5 | #include "riscv_priv.h" 6 | 7 | struct cpu mtrap_cpu; 8 | u8 mtrap_stack[1 << 20]; 9 | void *mtrap_stack_top = &mtrap_stack[sizeof(mtrap_stack) - 1]; 10 | 11 | void mtrap_handler() 12 | { 13 | u64 mcause = csrr_mcause(); 14 | switch (mcause) 15 | { 16 | case MCAUSE_INTR_M_TIMER: 17 | { 18 | // notify S-mode that M-mode timer timeout 19 | csrs_mip(MIE_STIE); 20 | csrs_mie(MIE_STIE); 21 | set_timeout(10000000); 22 | break; 23 | } 24 | 25 | case MCAUSE_INTR_M_EXTER: 26 | { 27 | uart_printf("[MTrap - Exter] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, mcause, mtime()); 28 | break; 29 | } 30 | 31 | case MCAUSE_INNER_ILLEAGEL_INSTRUCTION: 32 | { 33 | uart_printf("[MTrap - Illeagel Instruction] active_pid: %d, mcause: 0x%lX, mepc: %lx\n", active_pid, mcause, csrr_mepc()); 34 | break; 35 | } 36 | 37 | default: 38 | { 39 | uart_printf("[MTrap - Default] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, mcause, mtime()); 40 | while (1) 41 | ; 42 | break; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/proc.c: -------------------------------------------------------------------------------- 1 | #include "proc.h" 2 | 3 | i32 active_pid; 4 | struct proc proc_list[PROC_TOTAL_COUNT]; -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/proc.h: -------------------------------------------------------------------------------- 1 | #ifndef _PROC_H 2 | #define _PROC_H 3 | 4 | #define PROC_NAME_MAXLEN 64 5 | #define PROC_TOTAL_COUNT 16 6 | 7 | #include "riscv_arch.h" 8 | #include "riscv_cpu.h" 9 | 10 | enum proc_state 11 | { 12 | PROC_STATE_NONE = 0, 13 | PROC_STATE_READY, 14 | PROC_STATE_RUNNING, 15 | }; 16 | 17 | struct proc 18 | { 19 | enum proc_state state; 20 | u32 pid; 21 | u8 name[PROC_NAME_MAXLEN]; 22 | struct cpu cpu; 23 | u64 hartid; 24 | }; 25 | 26 | extern i32 active_pid; 27 | extern struct proc proc_list[PROC_TOTAL_COUNT]; 28 | 29 | #endif /* _PROC_H */ -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/riscv_arch.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ARCH_H 2 | #define _RISCV_ARCH_H 3 | 4 | // machine bits 5 | #define MACHINE_BITS 64 6 | #define BITS_PER_LONG MACHINE_BITS 7 | 8 | // bool 9 | #define bool _Bool 10 | #define true 1 11 | #define false 0 12 | 13 | // unsigned types 14 | typedef unsigned char u8; 15 | typedef unsigned short u16; 16 | typedef unsigned int u32; 17 | typedef unsigned long u64; 18 | 19 | // signed types 20 | typedef signed char i8; 21 | typedef signed short i16; 22 | typedef signed int i32; 23 | typedef signed long i64; 24 | 25 | // size_t 26 | typedef u64 size_t; 27 | 28 | #endif /* _RISCV_ARCH_H */ -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/riscv_asm.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ASM_H 2 | #define _RISCV_ASM_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | #define readu8(addr) (*(const u8 *)(addr)) 7 | #define readu16(addr) (*(const u16 *)(addr)) 8 | #define readu32(addr) (*(const u32 *)(addr)) 9 | #define readu64(addr) (*(const u64 *)(addr)) 10 | 11 | #define writeu8(addr, val) (*(u8 *)(addr) = (val)) 12 | #define writeu16(addr, val) (*(u16 *)(addr) = (val)) 13 | #define writeu32(addr, val) (*(u32 *)(addr) = (val)) 14 | #define writeu64(addr, val) (*(u64 *)(addr) = (val)) 15 | 16 | static inline void ecall() 17 | { 18 | asm volatile("ecall" 19 | : 20 | :); 21 | } 22 | 23 | // Physical Memory Protection 24 | static inline void w_pmpcfg0(u64 x) 25 | { 26 | asm volatile("csrw pmpcfg0, %0" 27 | : 28 | : "r"(x)); 29 | } 30 | 31 | static inline void w_pmpaddr0(u64 x) 32 | { 33 | asm volatile("csrw pmpaddr0, %0" 34 | : 35 | : "r"(x)); 36 | } 37 | 38 | // csr operations 39 | #include "riscv_asm_csr.gen.h" 40 | 41 | #endif /* _RISCV_ASM_H */ -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/riscv_asm_csr.gen.h: -------------------------------------------------------------------------------- 1 | #ifndef _RISCV_ASM_CSR_H 2 | #define _RISCV_ASM_CSR_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | static inline u64 csrr_mstatus() 7 | { 8 | volatile u64 val; 9 | asm volatile("csrr %0, mstatus" 10 | : "=r"(val) 11 | :); 12 | return val; 13 | } 14 | 15 | static inline void csrw_mstatus(const volatile u64 val) 16 | { 17 | asm volatile("csrw mstatus, %0" 18 | : 19 | : "r"(val)); 20 | } 21 | 22 | static inline void csrs_mstatus(const volatile u64 val) 23 | { 24 | asm volatile("csrs mstatus, %0" 25 | : 26 | : "r"(val)); 27 | } 28 | 29 | static inline void csrc_mstatus(const volatile u64 val) 30 | { 31 | asm volatile("csrc mstatus, %0" 32 | : 33 | : "r"(val)); 34 | } 35 | 36 | static inline u64 csrr_mie() 37 | { 38 | volatile u64 val; 39 | asm volatile("csrr %0, mie" 40 | : "=r"(val) 41 | :); 42 | return val; 43 | } 44 | 45 | static inline void csrw_mie(const volatile u64 val) 46 | { 47 | asm volatile("csrw mie, %0" 48 | : 49 | : "r"(val)); 50 | } 51 | 52 | static inline void csrs_mie(const volatile u64 val) 53 | { 54 | asm volatile("csrs mie, %0" 55 | : 56 | : "r"(val)); 57 | } 58 | 59 | static inline void csrc_mie(const volatile u64 val) 60 | { 61 | asm volatile("csrc mie, %0" 62 | : 63 | : "r"(val)); 64 | } 65 | 66 | static inline u64 csrr_mip() 67 | { 68 | volatile u64 val; 69 | asm volatile("csrr %0, mip" 70 | : "=r"(val) 71 | :); 72 | return val; 73 | } 74 | 75 | static inline void csrw_mip(const volatile u64 val) 76 | { 77 | asm volatile("csrw mip, %0" 78 | : 79 | : "r"(val)); 80 | } 81 | 82 | static inline void csrs_mip(const volatile u64 val) 83 | { 84 | asm volatile("csrs mip, %0" 85 | : 86 | : "r"(val)); 87 | } 88 | 89 | static inline void csrc_mip(const volatile u64 val) 90 | { 91 | asm volatile("csrc mip, %0" 92 | : 93 | : "r"(val)); 94 | } 95 | 96 | static inline u64 csrr_mideleg() 97 | { 98 | volatile u64 val; 99 | asm volatile("csrr %0, mideleg" 100 | : "=r"(val) 101 | :); 102 | return val; 103 | } 104 | 105 | static inline void csrw_mideleg(const volatile u64 val) 106 | { 107 | asm volatile("csrw mideleg, %0" 108 | : 109 | : "r"(val)); 110 | } 111 | 112 | static inline void csrs_mideleg(const volatile u64 val) 113 | { 114 | asm volatile("csrs mideleg, %0" 115 | : 116 | : "r"(val)); 117 | } 118 | 119 | static inline void csrc_mideleg(const volatile u64 val) 120 | { 121 | asm volatile("csrc mideleg, %0" 122 | : 123 | : "r"(val)); 124 | } 125 | 126 | static inline u64 csrr_medeleg() 127 | { 128 | volatile u64 val; 129 | asm volatile("csrr %0, medeleg" 130 | : "=r"(val) 131 | :); 132 | return val; 133 | } 134 | 135 | static inline void csrw_medeleg(const volatile u64 val) 136 | { 137 | asm volatile("csrw medeleg, %0" 138 | : 139 | : "r"(val)); 140 | } 141 | 142 | static inline void csrs_medeleg(const volatile u64 val) 143 | { 144 | asm volatile("csrs medeleg, %0" 145 | : 146 | : "r"(val)); 147 | } 148 | 149 | static inline void csrc_medeleg(const volatile u64 val) 150 | { 151 | asm volatile("csrc medeleg, %0" 152 | : 153 | : "r"(val)); 154 | } 155 | 156 | static inline u64 csrr_mcause() 157 | { 158 | volatile u64 val; 159 | asm volatile("csrr %0, mcause" 160 | : "=r"(val) 161 | :); 162 | return val; 163 | } 164 | 165 | static inline void csrw_mcause(const volatile u64 val) 166 | { 167 | asm volatile("csrw mcause, %0" 168 | : 169 | : "r"(val)); 170 | } 171 | 172 | static inline u64 csrr_mtvec() 173 | { 174 | volatile u64 val; 175 | asm volatile("csrr %0, mtvec" 176 | : "=r"(val) 177 | :); 178 | return val; 179 | } 180 | 181 | static inline void csrw_mtvec(const volatile u64 val) 182 | { 183 | asm volatile("csrw mtvec, %0" 184 | : 185 | : "r"(val)); 186 | } 187 | 188 | static inline u64 csrr_mepc() 189 | { 190 | volatile u64 val; 191 | asm volatile("csrr %0, mepc" 192 | : "=r"(val) 193 | :); 194 | return val; 195 | } 196 | 197 | static inline void csrw_mepc(const volatile u64 val) 198 | { 199 | asm volatile("csrw mepc, %0" 200 | : 201 | : "r"(val)); 202 | } 203 | 204 | static inline u64 csrr_sstatus() 205 | { 206 | volatile u64 val; 207 | asm volatile("csrr %0, sstatus" 208 | : "=r"(val) 209 | :); 210 | return val; 211 | } 212 | 213 | static inline void csrw_sstatus(const volatile u64 val) 214 | { 215 | asm volatile("csrw sstatus, %0" 216 | : 217 | : "r"(val)); 218 | } 219 | 220 | static inline void csrs_sstatus(const volatile u64 val) 221 | { 222 | asm volatile("csrs sstatus, %0" 223 | : 224 | : "r"(val)); 225 | } 226 | 227 | static inline void csrc_sstatus(const volatile u64 val) 228 | { 229 | asm volatile("csrc sstatus, %0" 230 | : 231 | : "r"(val)); 232 | } 233 | 234 | static inline u64 csrr_sie() 235 | { 236 | volatile u64 val; 237 | asm volatile("csrr %0, sie" 238 | : "=r"(val) 239 | :); 240 | return val; 241 | } 242 | 243 | static inline void csrw_sie(const volatile u64 val) 244 | { 245 | asm volatile("csrw sie, %0" 246 | : 247 | : "r"(val)); 248 | } 249 | 250 | static inline void csrs_sie(const volatile u64 val) 251 | { 252 | asm volatile("csrs sie, %0" 253 | : 254 | : "r"(val)); 255 | } 256 | 257 | static inline void csrc_sie(const volatile u64 val) 258 | { 259 | asm volatile("csrc sie, %0" 260 | : 261 | : "r"(val)); 262 | } 263 | 264 | static inline u64 csrr_sip() 265 | { 266 | volatile u64 val; 267 | asm volatile("csrr %0, sip" 268 | : "=r"(val) 269 | :); 270 | return val; 271 | } 272 | 273 | static inline void csrw_sip(const volatile u64 val) 274 | { 275 | asm volatile("csrw sip, %0" 276 | : 277 | : "r"(val)); 278 | } 279 | 280 | static inline void csrs_sip(const volatile u64 val) 281 | { 282 | asm volatile("csrs sip, %0" 283 | : 284 | : "r"(val)); 285 | } 286 | 287 | static inline void csrc_sip(const volatile u64 val) 288 | { 289 | asm volatile("csrc sip, %0" 290 | : 291 | : "r"(val)); 292 | } 293 | 294 | static inline u64 csrr_scause() 295 | { 296 | volatile u64 val; 297 | asm volatile("csrr %0, scause" 298 | : "=r"(val) 299 | :); 300 | return val; 301 | } 302 | 303 | static inline void csrw_scause(const volatile u64 val) 304 | { 305 | asm volatile("csrw scause, %0" 306 | : 307 | : "r"(val)); 308 | } 309 | 310 | static inline u64 csrr_stvec() 311 | { 312 | volatile u64 val; 313 | asm volatile("csrr %0, stvec" 314 | : "=r"(val) 315 | :); 316 | return val; 317 | } 318 | 319 | static inline void csrw_stvec(const volatile u64 val) 320 | { 321 | asm volatile("csrw stvec, %0" 322 | : 323 | : "r"(val)); 324 | } 325 | 326 | static inline u64 csrr_sepc() 327 | { 328 | volatile u64 val; 329 | asm volatile("csrr %0, sepc" 330 | : "=r"(val) 331 | :); 332 | return val; 333 | } 334 | 335 | static inline void csrw_sepc(const volatile u64 val) 336 | { 337 | asm volatile("csrw sepc, %0" 338 | : 339 | : "r"(val)); 340 | } 341 | 342 | static inline u64 csrr_pmpaddr0() 343 | { 344 | volatile u64 val; 345 | asm volatile("csrr %0, pmpaddr0" 346 | : "=r"(val) 347 | :); 348 | return val; 349 | } 350 | 351 | static inline void csrw_pmpaddr0(const volatile u64 val) 352 | { 353 | asm volatile("csrw pmpaddr0, %0" 354 | : 355 | : "r"(val)); 356 | } 357 | 358 | static inline u64 csrr_pmpcfg0() 359 | { 360 | volatile u64 val; 361 | asm volatile("csrr %0, pmpcfg0" 362 | : "=r"(val) 363 | :); 364 | return val; 365 | } 366 | 367 | static inline void csrw_pmpcfg0(const volatile u64 val) 368 | { 369 | asm volatile("csrw pmpcfg0, %0" 370 | : 371 | : "r"(val)); 372 | } 373 | 374 | #endif /* _RISCV_ASM_CSR_H */ 375 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/riscv_asm_csr.gen.h.py: -------------------------------------------------------------------------------- 1 | def csrr(reg): 2 | return f''' 3 | static inline u64 csrr_{reg}() 4 | {{ 5 | volatile u64 val; 6 | asm volatile("csrr %0, {reg}" 7 | : "=r"(val) 8 | :); 9 | return val; 10 | }}''' 11 | 12 | 13 | def csrw(reg): 14 | return f''' 15 | static inline void csrw_{reg}(const volatile u64 val) 16 | {{ 17 | asm volatile("csrw {reg}, %0" 18 | : 19 | : "r"(val)); 20 | }}''' 21 | 22 | 23 | def csrs(reg): 24 | return f''' 25 | static inline void csrs_{reg}(const volatile u64 val) 26 | {{ 27 | asm volatile("csrs {reg}, %0" 28 | : 29 | : "r"(val)); 30 | }}''' 31 | 32 | 33 | def csrc(reg): 34 | return f''' 35 | static inline void csrc_{reg}(const volatile u64 val) 36 | {{ 37 | asm volatile("csrc {reg}, %0" 38 | : 39 | : "r"(val)); 40 | }}''' 41 | 42 | 43 | registers = { 44 | # M-mode csr registers 45 | "mstatus": [csrr, csrw, csrs, csrc], 46 | "mie": [csrr, csrw, csrs, csrc], 47 | "mip": [csrr, csrw, csrs, csrc], 48 | "mideleg": [csrr, csrw, csrs, csrc], 49 | "medeleg": [csrr, csrw, csrs, csrc], 50 | "mcause": [csrr, csrw], 51 | "mtvec": [csrr, csrw], 52 | "mepc": [csrr, csrw], 53 | # S-mode csr registers 54 | "sstatus": [csrr, csrw, csrs, csrc], 55 | "sie": [csrr, csrw, csrs, csrc], 56 | "sip": [csrr, csrw, csrs, csrc], 57 | "scause": [csrr, csrw], 58 | "stvec": [csrr, csrw], 59 | "sepc": [csrr, csrw], 60 | # Physical memory protection 61 | "pmpaddr0": [csrr, csrw], 62 | "pmpcfg0": [csrr, csrw], 63 | } 64 | 65 | 66 | def main(): 67 | print("#ifndef _RISCV_ASM_CSR_H") 68 | print("#define _RISCV_ASM_CSR_H") 69 | print("") 70 | print('#include "riscv_arch.h"') 71 | 72 | for reg, ops in registers.items(): 73 | for op in ops: 74 | print(op(reg)) 75 | 76 | print() 77 | print("#endif /* _RISCV_ASM_CSR_H */") 78 | 79 | 80 | if __name__ == '__main__': 81 | main() 82 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/riscv_cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef _CPU_H 2 | #define _CPU_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | struct cpu 7 | { 8 | u64 x0; // zero 9 | u64 x1; // ra 10 | u64 x2; // sp 11 | u64 x3; // gp 12 | u64 x4; // tp 13 | u64 x5; // t0 14 | u64 x6; // t1 15 | u64 x7; // t2 16 | u64 x8; // s0/fp 17 | u64 x9; // s1 18 | u64 x10; // a0 19 | u64 x11; // a1 20 | u64 x12; // a2 21 | u64 x13; // a3 22 | u64 x14; // a4 23 | u64 x15; // a5 24 | u64 x16; // a6 25 | u64 x17; // a7 26 | u64 x18; // s2 27 | u64 x19; // s3 28 | u64 x20; // s4 29 | u64 x21; // s5 30 | u64 x22; // s6 31 | u64 x23; // s7 32 | u64 x24; // s8 33 | u64 x25; // s9 34 | u64 x26; // s10 35 | u64 x27; // s11 36 | u64 x28; // t3 37 | u64 x29; // t4 38 | u64 x30; // t5 39 | u64 x31; // t6 40 | 41 | u64 pc; // pc 42 | } __attribute__((packed)); 43 | 44 | #endif /* _CPU_H */ -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/riscv_priv.h: -------------------------------------------------------------------------------- 1 | #ifndef _INTERRUPTS_H 2 | #define _INTERRUPTS_H 3 | 4 | #include "riscv_arch.h" 5 | 6 | /* Machine Mode Registers */ 7 | // mstatus 8 | #define MSTATUS_SIE (0x1L << 1) 9 | #define MSTATUS_MIE (0x1L << 3) 10 | #define MSTATUS_SPIE (0x1L << 5) 11 | #define MSTATUS_MPIE (0x1L << 7) 12 | #define MSTATUS_SPP_U (0x0L << 8) 13 | #define MSTATUS_SPP_S (0x1L << 8) 14 | #define MSTATUS_SPP_MASK (0x1L << 8) 15 | #define MSTATUS_MPP_U (0x0L << 11) 16 | #define MSTATUS_MPP_S (0x1L << 11) 17 | #define MSTATUS_MPP_M (0x3L << 11) 18 | #define MSTATUS_MPP_MASK (0x3L << 11) 19 | 20 | // mie 21 | #define MIE_SSIE (0x1L << 1) 22 | #define MIE_MSIE (0x1L << 3) 23 | #define MIE_STIE (0x1L << 5) 24 | #define MIE_MTIE (0x1L << 7) 25 | #define MIE_SEIE (0x1L << 9) 26 | #define MIE_MEIE (0x1L << 11) 27 | 28 | // mip 29 | #define MIP_SSIP MIE_SSIE 30 | #define MIP_MSIP MIE_MSIE 31 | #define MIP_STIP MIE_STIE 32 | #define MIP_MTIP MIE_MTIE 33 | #define MIP_SEIP MIE_SEIE 34 | #define MIP_MEIP MIE_MEIE 35 | 36 | // medeleg 37 | #define MEDELEG_U_ECALL (1L << 8) 38 | #define MEDELEG_S_ECALL (1L << 9) 39 | #define MEDELEG_M_ECALL (1L << 11) 40 | 41 | // mcause 42 | #define MCAUSE_INTR_FLAG (0x1L << (MACHINE_BITS - 1)) 43 | #define MCAUSE_INTR_S_SOFT (MCAUSE_INTR_FLAG | 1) 44 | #define MCAUSE_INTR_M_SOFT (MCAUSE_INTR_FLAG | 3) 45 | #define MCAUSE_INTR_S_TIMER (MCAUSE_INTR_FLAG | 5) 46 | #define MCAUSE_INTR_M_TIMER (MCAUSE_INTR_FLAG | 7) 47 | #define MCAUSE_INTR_S_EXTER (MCAUSE_INTR_FLAG | 9) 48 | #define MCAUSE_INTR_M_EXTER (MCAUSE_INTR_FLAG | 11) 49 | #define MCAUSE_INNER_ILLEAGEL_INSTRUCTION (0x2L) 50 | #define MCAUSE_INNER_U_ECALL (8L) 51 | #define MCAUSE_INNER_S_ECALL (9L) 52 | #define MCAUSE_INNER_M_ECALL (11L) 53 | 54 | /* Supervisor Mode Registers */ 55 | // sstatus 56 | #define SSTATUS_SIE MSTATUS_SIE 57 | #define SSTATUS_SPIE MSTATUS_SPIE 58 | #define SSTATUS_SPP_U MSTATUS_SPP_U 59 | #define SSTATUS_SPP_S MSTATUS_SPP_S 60 | #define SSTATUS_SPP_MASK MSTATUS_SPP_MASK 61 | // sie 62 | #define SIE_SSIE MIE_SSIE 63 | #define SIE_STIE MIE_STIE 64 | #define SIE_SEIE MIE_SEIE 65 | // sip 66 | #define SIP_SSIP SIE_SSIE 67 | #define SIP_STIP SIE_STIE 68 | #define SIP_SEIP SIE_SEIE 69 | 70 | #endif /* _INTERRUPTS_H */ -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/strap_entry.s: -------------------------------------------------------------------------------- 1 | .equ REGSZ, 8 2 | .global strap_entry 3 | 4 | .text 5 | 6 | strap_entry: 7 | 8 | # swap x5/sscratch 9 | csrrw x5, sscratch, x5 10 | # use x5 as cpu state base address register 11 | la x5, strap_cpu 12 | # save general purpose registers 13 | # x0 ~ x4 14 | sd x0, (0 * REGSZ)(x5) 15 | sd x1, (1 * REGSZ)(x5) 16 | sd x2, (2 * REGSZ)(x5) 17 | sd x3, (3 * REGSZ)(x5) 18 | sd x4, (4 * REGSZ)(x5) 19 | # save origin x5 by x1, which has been saved 20 | csrr x1, sscratch 21 | sd x1, (5 * REGSZ)(x5) 22 | # x6 ~ x31 23 | sd x6, (6 * REGSZ)(x5) 24 | sd x7, (7 * REGSZ)(x5) 25 | sd x8, (8 * REGSZ)(x5) 26 | sd x9, (9 * REGSZ)(x5) 27 | sd x10, (10 * REGSZ)(x5) 28 | sd x11, (11 * REGSZ)(x5) 29 | sd x12, (12 * REGSZ)(x5) 30 | sd x13, (13 * REGSZ)(x5) 31 | sd x14, (14 * REGSZ)(x5) 32 | sd x15, (15 * REGSZ)(x5) 33 | sd x16, (16 * REGSZ)(x5) 34 | sd x17, (17 * REGSZ)(x5) 35 | sd x18, (18 * REGSZ)(x5) 36 | sd x19, (19 * REGSZ)(x5) 37 | sd x20, (20 * REGSZ)(x5) 38 | sd x21, (21 * REGSZ)(x5) 39 | sd x22, (22 * REGSZ)(x5) 40 | sd x23, (23 * REGSZ)(x5) 41 | sd x24, (24 * REGSZ)(x5) 42 | sd x25, (25 * REGSZ)(x5) 43 | sd x26, (26 * REGSZ)(x5) 44 | sd x27, (27 * REGSZ)(x5) 45 | sd x28, (28 * REGSZ)(x5) 46 | sd x29, (29 * REGSZ)(x5) 47 | sd x30, (30 * REGSZ)(x5) 48 | sd x31, (31 * REGSZ)(x5) 49 | # save privilege registers 50 | # save mepc by x1, which has been saved 51 | csrr x1, sepc 52 | sd x1, (32 * REGSZ)(x5) 53 | 54 | # call trap_handler 55 | # Need set stack pointer? 56 | la t0, strap_stack_top 57 | ld sp, 0(t0) 58 | call strap_handler 59 | 60 | # use x5 as cpu state base address register 61 | la x5, strap_cpu 62 | # restore privilege registers 63 | # restore mepc by x1, which will be restored later 64 | ld x1, (32 * REGSZ)(x5) 65 | csrw sepc, x1 66 | # restore general purpose registers 67 | # x0 ~ x4 68 | ld x0, (0 * REGSZ)(x5) 69 | ld x1, (1 * REGSZ)(x5) 70 | ld x2, (2 * REGSZ)(x5) 71 | ld x3, (3 * REGSZ)(x5) 72 | ld x4, (4 * REGSZ)(x5) 73 | # postpone the restoration of x5 74 | # because it is being used as the base address register 75 | # x6 ~ x31 76 | ld x6, (6 * REGSZ)(x5) 77 | ld x7, (7 * REGSZ)(x5) 78 | ld x8, (8 * REGSZ)(x5) 79 | ld x9, (9 * REGSZ)(x5) 80 | ld x10, (10 * REGSZ)(x5) 81 | ld x11, (11 * REGSZ)(x5) 82 | ld x12, (12 * REGSZ)(x5) 83 | ld x13, (13 * REGSZ)(x5) 84 | ld x14, (14 * REGSZ)(x5) 85 | ld x15, (15 * REGSZ)(x5) 86 | ld x16, (16 * REGSZ)(x5) 87 | ld x17, (17 * REGSZ)(x5) 88 | ld x18, (18 * REGSZ)(x5) 89 | ld x19, (19 * REGSZ)(x5) 90 | ld x20, (20 * REGSZ)(x5) 91 | ld x21, (21 * REGSZ)(x5) 92 | ld x22, (22 * REGSZ)(x5) 93 | ld x23, (23 * REGSZ)(x5) 94 | ld x24, (24 * REGSZ)(x5) 95 | ld x25, (25 * REGSZ)(x5) 96 | ld x26, (26 * REGSZ)(x5) 97 | ld x27, (27 * REGSZ)(x5) 98 | ld x28, (28 * REGSZ)(x5) 99 | ld x29, (29 * REGSZ)(x5) 100 | ld x30, (30 * REGSZ)(x5) 101 | ld x31, (31 * REGSZ)(x5) 102 | # x5 103 | ld x5, (6 * REGSZ)(x5) 104 | 105 | sret 106 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/strap_handler.c: -------------------------------------------------------------------------------- 1 | #include "riscv_cpu.h" 2 | #include "uart.h" 3 | #include "timer.h" 4 | #include "proc.h" 5 | #include "riscv_priv.h" 6 | 7 | struct cpu strap_cpu; 8 | u8 strap_stack[1 << 20]; 9 | void *strap_stack_top = &strap_stack[sizeof(strap_stack) - 1]; 10 | 11 | void strap_handler() 12 | { 13 | u64 scause = csrr_scause(); 14 | switch (scause) 15 | { 16 | case MCAUSE_INTR_S_TIMER: 17 | { 18 | // there exists runnable processes 19 | if (proc_list[0].state != PROC_STATE_NONE) 20 | { 21 | // assume proc-0 is the active process if there is no active process 22 | if (active_pid < 0) 23 | { 24 | active_pid = 0; 25 | strap_cpu = proc_list[0].cpu; 26 | uart_printf("[STrap - Timer] Scheduler Init. Ticks: %ld\n", active_pid, scause, 0); 27 | } 28 | 29 | // save cpu state for the active process 30 | proc_list[active_pid].cpu = strap_cpu; 31 | // suspend the active process 32 | proc_list[active_pid].state = PROC_STATE_READY; 33 | 34 | // iterate the processes from the next process, ending with the active process 35 | for (int ring_index = 1; ring_index <= PROC_TOTAL_COUNT; ring_index++) 36 | { 37 | int real_index = (active_pid + ring_index) % PROC_TOTAL_COUNT; 38 | struct proc *proc = &proc_list[real_index]; 39 | // run this process if it is ready 40 | if (proc->state == PROC_STATE_READY) 41 | { 42 | uart_printf("[STrap - Timer] Scheduler(Ticks = %ld): (PID = %d, PC = 0x%lx) => (PID = %d, PC = 0x%lx)\n", 0, active_pid, strap_cpu.pc, proc->pid, proc->cpu.pc); 43 | strap_cpu = proc->cpu; 44 | active_pid = proc->pid; 45 | break; 46 | } 47 | } 48 | } 49 | csrc_sie(SIE_STIE); 50 | break; 51 | } 52 | 53 | case MCAUSE_INTR_S_EXTER: 54 | { 55 | uart_printf("[STrap - Exter] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, scause, 0); 56 | break; 57 | } 58 | 59 | case MCAUSE_INNER_ILLEAGEL_INSTRUCTION: 60 | { 61 | uart_printf("[STrap - Illeagel Instruction] active_pid: %d, mcause: 0x%lX, mepc: %lx\n", active_pid, scause, csrr_mepc()); 62 | break; 63 | } 64 | 65 | default: 66 | { 67 | uart_printf("[STrap - Default] active_pid: %d, mcause: 0x%lX, current ticks: %d\n", active_pid, scause, 0); 68 | while (1) 69 | ; 70 | break; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/test_processes.c: -------------------------------------------------------------------------------- 1 | #include "riscv_arch.h" 2 | #include "uart.h" 3 | 4 | #define INTERVAL 100000000 5 | 6 | u8 test_proc_0_stack[1 << 20]; 7 | void *test_proc_0_stack_top = &test_proc_0_stack[sizeof(test_proc_0_stack) - 1]; 8 | 9 | void test_proc_0_entry() 10 | { 11 | while (true) 12 | { 13 | for (size_t i = 0; i < INTERVAL; i++) 14 | { 15 | } 16 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 0); 17 | } 18 | } 19 | 20 | u8 test_proc_1_stack[1 << 20]; 21 | void *test_proc_1_stack_top = &test_proc_1_stack[sizeof(test_proc_1_stack) - 1]; 22 | 23 | void test_proc_1_entry() 24 | { 25 | while (true) 26 | { 27 | for (size_t i = 0; i < INTERVAL; i++) 28 | { 29 | } 30 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 1); 31 | // asm volatile ("ecall"); 32 | } 33 | } 34 | 35 | u8 test_proc_2_stack[1 << 20]; 36 | void *test_proc_2_stack_top = &test_proc_2_stack[sizeof(test_proc_2_stack) - 1]; 37 | void test_proc_2_entry() 38 | { 39 | while (true) 40 | { 41 | for (size_t i = 0; i < INTERVAL; i++) 42 | { 43 | } 44 | uart_printf("[PID = %d] Hello, Process Shceduler!\n", 2); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef _TIMER_H 2 | #define _TIMER_H 3 | 4 | #include "riscv_asm.h" 5 | 6 | #define MTIME 0x200bff8 7 | #define MTIMECMP_0 0x2004000 8 | 9 | static inline u64 mtime() 10 | { 11 | return readu64(MTIME); 12 | } 13 | 14 | static inline u64 mtimecmp_0() 15 | { 16 | return readu64(MTIMECMP_0); 17 | } 18 | 19 | static inline u64 set_timeout(u64 timeout) 20 | { 21 | writeu64(MTIMECMP_0, mtime() + timeout); 22 | } 23 | 24 | #endif -------------------------------------------------------------------------------- /09-Enter-Supervisor-Mode/uart.h: -------------------------------------------------------------------------------- 1 | #ifndef _UART_H 2 | #define _UART_H 3 | 4 | #include "riscv_arch.h" 5 | #include "riscv_asm.h" 6 | #include 7 | 8 | #define UART_BASE 0x10000000 9 | 10 | #define UART_RBR_OFFSET 0 /* In: Recieve Buffer Register */ 11 | #define UART_THR_OFFSET 0 /* Out: Transmitter Holding Register */ 12 | #define UART_DLL_OFFSET 0 /* Out: Divisor Latch Low */ 13 | #define UART_IER_OFFSET 1 /* I/O: Interrupt Enable Register */ 14 | #define UART_DLM_OFFSET 1 /* Out: Divisor Latch High */ 15 | #define UART_FCR_OFFSET 2 /* Out: FIFO Control Register */ 16 | #define UART_IIR_OFFSET 2 /* I/O: Interrupt Identification Register */ 17 | #define UART_LCR_OFFSET 3 /* Out: Line Control Register */ 18 | #define UART_MCR_OFFSET 4 /* Out: Modem Control Register */ 19 | #define UART_LSR_OFFSET 5 /* In: Line Status Register */ 20 | #define UART_MSR_OFFSET 6 /* In: Modem Status Register */ 21 | #define UART_SCR_OFFSET 7 /* I/O: Scratch Register */ 22 | #define UART_MDR1_OFFSET 8 /* I/O: Mode Register */ 23 | 24 | #define PLATFORM_UART_INPUT_FREQ 10000000 25 | #define PLATFORM_UART_BAUDRATE 115200 26 | 27 | static u8 *uart_base_addr = (u8 *)UART_BASE; 28 | 29 | static void set_reg(u32 offset, u32 val) 30 | { 31 | writeu8(uart_base_addr + offset, val); 32 | } 33 | 34 | static u32 get_reg(u32 offset) 35 | { 36 | return readu8(uart_base_addr + offset); 37 | } 38 | 39 | static void uart_putc(u8 ch) 40 | { 41 | set_reg(UART_THR_OFFSET, ch); 42 | } 43 | 44 | static void uart_print(char *str) 45 | { 46 | while (*str) 47 | { 48 | uart_putc(*str++); 49 | } 50 | } 51 | 52 | static void uart_print_digit(unsigned long val, unsigned long base, bool uppercase) 53 | { 54 | const char *digits = uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; 55 | 56 | if (val < base) 57 | { 58 | uart_putc(digits[val]); 59 | } 60 | else 61 | { 62 | uart_print_digit(val / base, base, uppercase); 63 | uart_putc(digits[val % base]); 64 | } 65 | } 66 | 67 | static inline void uart_print_int(int val, int base) 68 | { 69 | if (val < 0) 70 | { 71 | uart_putc('-'); 72 | val = -val; 73 | } 74 | uart_print_digit(val, base, false); 75 | } 76 | 77 | static inline void uart_print_uint(unsigned int val, int base) 78 | { 79 | uart_print_digit(val, base, false); 80 | } 81 | 82 | static inline void uart_print_uint_upper(unsigned int val, int base) 83 | { 84 | uart_print_digit(val, base, true); 85 | } 86 | 87 | static inline void uart_print_long(long val, int base) 88 | { 89 | if (val < 0) 90 | { 91 | uart_putc('-'); 92 | val = -val; 93 | } 94 | uart_print_digit(val, base, false); 95 | } 96 | 97 | static inline void uart_print_ulong(unsigned long val, int base) 98 | { 99 | uart_print_digit(val, base, false); 100 | } 101 | 102 | static inline void uart_print_ulong_upper(unsigned long val, int base) 103 | { 104 | uart_print_digit(val, base, true); 105 | } 106 | 107 | static inline void uart_printf(const char *format, ...) 108 | { 109 | va_list args; 110 | va_start(args, format); 111 | 112 | while (*format) 113 | { 114 | if (*format == '%') 115 | { 116 | format++; 117 | switch (*format) 118 | { 119 | case 'c': 120 | { 121 | char c = (char)va_arg(args, unsigned int); 122 | uart_putc(c); 123 | break; 124 | } 125 | case 's': 126 | { 127 | char *str = va_arg(args, char *); 128 | uart_print(str); 129 | break; 130 | } 131 | case 'd': 132 | { 133 | int val = va_arg(args, int); 134 | uart_print_int(val, 10); 135 | break; 136 | } 137 | case 'u': 138 | { 139 | unsigned int val = va_arg(args, unsigned int); 140 | uart_print_uint(val, 10); 141 | break; 142 | } 143 | case 'x': 144 | { 145 | unsigned int val = va_arg(args, unsigned int); 146 | uart_print_uint(val, 16); 147 | break; 148 | } 149 | case 'X': 150 | { 151 | unsigned int val = va_arg(args, unsigned int); 152 | uart_print_uint_upper(val, 16); 153 | break; 154 | } 155 | case 'l': 156 | { 157 | format++; 158 | switch (*format) 159 | { 160 | case 'd': 161 | { 162 | long val = va_arg(args, long); 163 | uart_print_long(val, 10); 164 | break; 165 | } 166 | case 'u': 167 | { 168 | unsigned long val = va_arg(args, unsigned long); 169 | uart_print_ulong(val, 10); 170 | break; 171 | } 172 | case 'x': 173 | { 174 | unsigned long val = va_arg(args, unsigned long); 175 | uart_print_ulong(val, 16); 176 | break; 177 | } 178 | case 'X': 179 | { 180 | unsigned long val = va_arg(args, unsigned long); 181 | uart_print_ulong_upper(val, 16); 182 | break; 183 | } 184 | default: 185 | uart_putc(*format); 186 | break; 187 | } 188 | break; 189 | } 190 | default: 191 | uart_putc(*format); 192 | break; 193 | } 194 | } 195 | else 196 | { 197 | uart_putc(*format); 198 | } 199 | format++; 200 | } 201 | 202 | va_end(args); 203 | } 204 | 205 | static inline void uart_init() 206 | { 207 | u16 bdiv = (PLATFORM_UART_INPUT_FREQ + 8 * PLATFORM_UART_BAUDRATE) / (16 * PLATFORM_UART_BAUDRATE); 208 | 209 | /* Disable all interrupts */ 210 | set_reg(UART_IER_OFFSET, 0x00); 211 | /* Enable DLAB */ 212 | set_reg(UART_LCR_OFFSET, 0x80); 213 | 214 | if (bdiv) 215 | { 216 | /* Set divisor low byte */ 217 | set_reg(UART_DLL_OFFSET, bdiv & 0xff); 218 | /* Set divisor high byte */ 219 | set_reg(UART_DLM_OFFSET, (bdiv >> 8) & 0xff); 220 | } 221 | 222 | /* 8 bits, no parity, one stop bit */ 223 | set_reg(UART_LCR_OFFSET, 0x03); 224 | /* Enable FIFO */ 225 | set_reg(UART_FCR_OFFSET, 0x01); 226 | /* No modem control DTR RTS */ 227 | set_reg(UART_MCR_OFFSET, 0x00); 228 | /* Clear line status */ 229 | get_reg(UART_LSR_OFFSET); 230 | /* Read receive buffer */ 231 | get_reg(UART_RBR_OFFSET); 232 | /* Set scratchpad */ 233 | set_reg(UART_SCR_OFFSET, 0x00); 234 | } 235 | 236 | #endif -------------------------------------------------------------------------------- /Appendix-00-Assembly-and-Linking/README.md: -------------------------------------------------------------------------------- 1 | # Assembly and Linking Basics 2 | ## Related file types and concepts 3 | - machine instruction 4 | - assembly source code file 5 | - relocatable file 6 | - executable file 7 | 8 | ## Assembly and linking workflow 9 | - The CPU can only decode and execute _machine instructions_. 10 | - An _assembly source code file_ consists of assembly instructions, which are symbolic representations of machine instructions. 11 | - The assembler can translate an _assembly source file_ into a _relocatable file_. 12 | - A relocatable file contains machine instructions and some meta information. 13 | - A relocatable file can't be loaded and executed directly, because it may reference some symbols whose address values are not known yet. 14 | - Multiple _relocatable flles_ can be linked to resolve unknown symbols and form an _executable file_. 15 | - An executable file contains machine instructions to be executed, and its meta information specifies the memory address where the program should be loaded. 16 | 17 | ![](./assembly-and-linking.drawio.png) 18 | 19 | ## Why does a symbol with an unknown address value exist? 20 | 21 | In this section, we learn how an assembly source file is converted into an executable file step by step, and why does a symbol with an unknown address value exist? 22 | 23 | ### Example assembly source file 24 | Take the following assembly source code file `test.s` as an example. 25 | ```asm 26 | .text 27 | 28 | /* load a immediate value to t0 */ 29 | lui t0, %hi(0x12345678) /* higher 20 bits */ 30 | addi t0, t0, %lo(0x12345678) /* lower 12 bits */ 31 | 32 | /* load a symbol value to t0 */ 33 | lui t0, %hi(hello_string) 34 | addi t0, t0, %lo(hello_string) 35 | 36 | /* define a string */ 37 | hello_string: 38 | .string "Hello, RISC-V!" 39 | 40 | ``` 41 | 42 | The source file performs three operations: 43 | 1. Load an `immediate number` into the t0 register with two instructions `lui` and `addi` 44 | 2. Load a symbolic `hello_string` into the t1 register with two instructions `lui` and `addi` 45 | 3. Define a string with the symbol `hello_string` 46 | 47 | ## Assembly 48 | 49 | In short, assembly is the process of reading assembly instructions from assembly source file one by one and translating them into machine instructions. 50 | 51 | ### Example Analysis 52 | What should the result look like after assembly? Let's analyse it. 53 | 54 | - The third operation is just a string definition, the assembler just needs to convert it to ASCII. 55 | - For the first operation, whose operand is an immediate number, the assembler can generate the corresponding machine instruction exactly. 56 | 57 | However, the second statement contains a symbol `hello_string`. The value of the symbol actually represents a memory address where the data or instructions following the symbol in assembly source code should be loaded upon execution. 58 | 59 | **The problem** is that when assembling, the assembler doesn't know where the data or instructions which followed a symbol should be loaded upon execution, thus don't know the symbol value. 60 | 61 | In addition to the local symbols, an assembly file can also reference symbols in other assembly files. These symbols obviously cannot be resolved when assembling an assembly source file. 62 | 63 | ### Unresolved symbols 64 | 65 | At assembly time, some symbols are not immediately resolved. When the assembler meets a assembly instruction with a unresolved symbol, it treats the symbol value as `zero`, then translates the assembly instruction to a machine instruction. Obviously, the data bits representing the unresolved symbol are also `zero` in the translated machine instructions. 66 | 67 | To ensure that the unresolved symbols are finally resolved correctly, the assembler places additional auxiliary information in the generated target file. These auxiliary information are used to assist in _symbol resolution_ or _symbol relocation_, so the target files generated by the assembler are called **relocatable files**. 68 | 69 | ### Assemble example file and explore its content 70 | 71 | We can assemble the example file to a relocatable file with `riscv64-linux-gnu-as` . 72 | 73 | ```bash 74 | riscv64-linux-gnu-as test.s -o test.o 75 | ``` 76 | 77 | The above command assembles `test.s` and generates the relocatable file `test.o` . 78 | 79 | Let's explore `test.o` ! 80 | 81 | With command `riscv64-linux-gnu-objdump -S test.o` , we can disassemble `test.o` . 82 | ``` 83 | $ riscv64-linux-gnu-objdump -S test.o 84 | 85 | test.o: file format elf64-littleriscv 86 | 87 | 88 | Disassembly of section .text: 89 | 90 | 0000000000000000 : 91 | 0: 123452b7 lui t0,0x12345 92 | 4: 67828293 addi t0,t0,0x678 # 12345678 93 | 8: 000002b7 lui t0,0x00000 94 | c: 00028293 addi t0,t0,0x000 # mv t0,t0 95 | 96 | 0000000000000010 : (Omitted, because here is just some ASCII data) 97 | ``` 98 | 99 | As we can see, the first operation that operates immediate number `0x12345678` has been translated as follows. The operand `0x12345678` is splited into higher 20 bits `0x12345` and lower 12 bits `0x678`, spreaded in `123452b7 : lui t0,0x12345` and `67828293 : addi t0, t0, 0x678` respectively. 100 | ``` 101 | 0: 123452b7 lui t0,0x12345 102 | 4: 67828293 addi t0,t0,0x678 # 12345678 103 | ``` 104 | 105 | However, the second operation that operates symbol `hello_string`, as we analyse before, the operand is replaced with `0x0`. 106 | ``` 107 | 8: 000002b7 lui t0,0x00000 108 | c: 00028293 addi t0,t0,0x000 # mv t0,t0 109 | ``` 110 | Specifically, compared to the first operation: 111 | - `0x123452b7` -> `0x000002b7` OR `lui t0,0x12345` -> `lui t0,0x00000` 112 | - `0x67828293` -> `0x00028293` OR `addi t0,t0,0x678` -> `addi t0,t0,0x000` (the same as `mv t0,t0`) 113 | 114 | Where is the relocation infomation? Just execute `riscv64-linux-gnu-objdump -r test.o` . 115 | ``` 116 | $ riscv64-linux-gnu-objdump -r test.o 117 | 118 | test.o: file format elf64-littleriscv 119 | 120 | RELOCATION RECORDS FOR [.text]: 121 | OFFSET TYPE VALUE 122 | 0000000000000008 R_RISCV_HI20 hello_string 123 | 0000000000000008 R_RISCV_RELAX *ABS* 124 | 000000000000000c R_RISCV_LO12_I hello_string 125 | 000000000000000c R_RISCV_RELAX *ABS* 126 | ``` 127 | 128 | `0000000000000008 R_RISCV_HI20 hello_string` means that the operand of the machine instruction at offset `0x8` of the relocatable file test.o needs to be replaced by higher 20-bits of the symbol `hello_string` value when linking. 129 | 130 | `000000000000000c R_RISCV_LO12_I hello_string` means that the operand of the machine instruction at offset `0xc` of the relocatable file test.o needs to be replaced by lower 12-bits of the symbol `hello_string` value when linking. 131 | 132 | ## Linking 133 | 134 | The linker accepts multiple relocatable files, reads the data and machine instructions from each relocatable file and arranges all needed data and instructions in the final executable file according to specific rules. 135 | 136 | The linker needs to know where to load the executable's data and machine instructions at the time of execution,so that it can infer the address values of all symbols. 137 | 138 | Then, according to relocation entries of each relocation file, the linker modifies those machine instructions associated with symbols that were previously unresolved during assembling but are now resolved. 139 | 140 | ### Link and explore the executable file 141 | 142 | Firstly, we need prepare a linking script [test.ld](test.ld). 143 | _test.ld_ contains just a line: `. = 0x0;` . It tells the linker that the program load address is 0x0 . 144 | ``` 145 | . = 0x0; 146 | ``` 147 | 148 | Link `test.o` with the following command. Note that we add `--no-relax` option to prevent the linker from performing optimizations, so that we can compare the machine instructions before and after linking. 149 | ``` 150 | riscv64-linux-gnu-ld -T test.ld --no-relax test.o -o test 151 | ``` 152 | 153 | Then we can get a executable file `test`. Just like before, disassemble `test` with the command `riscv64-linux-gnu-objdump -S test` 154 | 155 | The instructions of `test` is as follows: 156 | ``` 157 | $ riscv64-linux-gnu-objdump -S test 158 | 159 | test: file format elf64-littleriscv 160 | 161 | 162 | Disassembly of section .text: 163 | 164 | 0000000000000000 : 165 | 0: 123452b7 lui t0,0x12345 166 | 4: 67828293 addi t0,t0,0x678 # 12345678 167 | 8: 000002b7 lui t0,0x00000 168 | c: 01028293 addi t0,t0,0x010 # 10 169 | 170 | 0000000000000010 : 171 | ``` 172 | 173 | Compare 174 | 175 | ``` 176 | 8: 000002b7 lui t0,0x00000 177 | c: 01028293 addi t0,t0,0x010 # 10 178 | ``` 179 | 180 | with before: 181 | 182 | ``` 183 | 8: 000002b7 lui t0,0x00000 184 | c: 00028293 addi t0,t0,0x000 # mv t0,t0 185 | ``` 186 | 187 | We can see that the symbol `hello_string`'s value has been modified to `0x00000010`. 188 | 189 | **The point is that: the symbol `hello_string` has been resolved and the associated machine instructions has been fixed!** 190 | 191 | With command `riscv64-linux-gnu-objdump -t test`, we can print the symbols table, and we can see that the value of symbol `hello_string` is `0x10` now. 192 | 193 | ``` 194 | $ riscv64-linux-gnu-objdump -t test 195 | 196 | test: file format elf64-littleriscv 197 | 198 | SYMBOL TABLE: 199 | 0000000000000000 l d .text 0000000000000000 .text 200 | 0000000000000000 l df *ABS* 0000000000000000 test.o 201 | 0000000000000010 l .text 0000000000000000 hello_string 202 | ``` 203 | 204 | ## What happens if we change the load address? 205 | 206 | If change the load address to `0x10000000`, the disassembly file is: 207 | ``` 208 | $ riscv64-linux-gnu-objdump -S test 209 | 210 | test: file format elf64-littleriscv 211 | 212 | 213 | Disassembly of section .text: 214 | 215 | 0000000010000000 : 216 | 10000000: 123452b7 lui t0,0x12345 217 | 10000004: 67828293 addi t0,t0,0x678 # 12345678 218 | 10000008: 100002b7 lui t0,0x10000 219 | 1000000c: 01028293 addi t0,t0,0x010 # 10000010 220 | 221 | 0000000010000010 : 222 | ``` 223 | 224 | And the symbols table is: 225 | ``` 226 | $ riscv64-linux-gnu-objdump -t test 227 | 228 | test: file format elf64-littleriscv 229 | 230 | SYMBOL TABLE: 231 | 0000000010000000 l d .text 0000000000000000 .text 232 | 0000000000000000 l df *ABS* 0000000000000000 test.o 233 | 0000000010000010 l .text 0000000000000000 hello_string 234 | ``` 235 | 236 | We can see that the the value of symbol `hello_string` has been modified accordingly. 237 | 238 | ## References 239 | - [Executable and Linkable Format (ELF)](http://www.skyfree.org/linux/references/ELF_Format.pdf) 240 | -------------------------------------------------------------------------------- /Appendix-00-Assembly-and-Linking/assembly-and-linking.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peiyuanix/riscv-os/051424636e720090309040f524f117d58510177b/Appendix-00-Assembly-and-Linking/assembly-and-linking.drawio.png -------------------------------------------------------------------------------- /Appendix-00-Assembly-and-Linking/test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peiyuanix/riscv-os/051424636e720090309040f524f117d58510177b/Appendix-00-Assembly-and-Linking/test -------------------------------------------------------------------------------- /Appendix-00-Assembly-and-Linking/test.ld: -------------------------------------------------------------------------------- 1 | . = 0x0; -------------------------------------------------------------------------------- /Appendix-00-Assembly-and-Linking/test.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peiyuanix/riscv-os/051424636e720090309040f524f117d58510177b/Appendix-00-Assembly-and-Linking/test.o -------------------------------------------------------------------------------- /Appendix-00-Assembly-and-Linking/test.s: -------------------------------------------------------------------------------- 1 | .text 2 | 3 | /* load a immediate value to t0 */ 4 | lui t0, %hi(0x12345678) /* higher 20 bits */ 5 | addi t0, t0, %lo(0x12345678) /* lower 12 bits */ 6 | 7 | /* load a symbol value to t0 */ 8 | lui t0, %hi(hello_string) 9 | addi t0, t0, %lo(hello_string) 10 | 11 | /* define a string */ 12 | hello_string: 13 | .string "Hello, RISC-V!" 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Liu Peiyuan 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build a minimal kernel for RISC-V 2 | 3 | ## Part 1. Explore RISC-V/QEMU/U-Boot/OpenSBI 4 | 5 | Get familiar with QEMU virtual machine firmware loading, kernel loading workflow. 6 | 7 | - [00: Run Linux with QEMU](/00-Run-Linux-With-QEMU) 8 | - [01: Run U-Boot with QEMU](/01-Run-U-Boot-With-QEMU) 9 | - [02: Run OpenSBI with QEMU](/02-Run-OpenSBI-With-QEMU) 10 | 11 | ## Part 2. Bare Metal RISC-V 12 | 13 | Write a bare metal firmware for QEMU `virt` device, which just prints `Hello, RISC-V!` 14 | 15 | - [03: Bare Metal Hello RISC-V](/03-Bare-Metal-Hello-RISC-V) 16 | - [04: Bare Metal Hello RISC-V (C Language Version)](/04-Bare-Metal-C-Language) 17 | 18 | **~~Switch to `sifive_u`, because `virt` is not well documented!~~** 19 | 20 | - ~~[04.1: Bare Metal Hello Sifive_u](/04.1-Bare-Metal-Sifive_u)~~ 21 | 22 | After some attempts, I found that I had to revert back to `virt` because `sifive_u` doesn't support mmio and PCI-E. 23 | 24 | ## Part 3. Interrupts and Process Scheduling 25 | 26 | - [05: Interrupt Basics (TODO)](/05-Interrupt-Basics) 27 | - [06: Timer Interrupt (Code Completed)](/06-Timer-Interrupt) 28 | - [07: Timer Interrupt-based Process Scheduling (Code Completed)](/07-Simple-Process-Scheduling) 29 | - [08: UART Interrupt (Partially Completed)](/08-UART-Interrupt) 30 | 31 | ## Appendix 32 | - [Appendix-00: Assembly and Linking Basics](/Appendix-00-Assembly-and-Linking/README.md) 33 | --------------------------------------------------------------------------------