├── .gitignore ├── LICENSE ├── README.md ├── rvcore ├── Kconfig ├── configs │ └── defconfig ├── objects.mk ├── platform.c └── rvcore.dts └── uart.cc /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 CmdBlock 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 | # RVCORE Mini Linux 2 | 3 | 为手搓的模拟器/RTL核实现,构建mini版本的Linux(with mmu)! 4 | 5 | 以尽量少的额外功能实现、尽量低的(与核本身无关的)硬件复杂度、尽量少的配置为宗旨! 6 | 7 | - 核心本身方面 8 | - 只需要实现启动Linux的最低要求:RV32IMA 9 | 10 | - 系统外设方面 11 | - CLINT肯定是要实现的,行为也需要和手册相符,但是只支持一个Hart就好了 12 | - 你甚至不需要实现PLIC 13 | - 你甚至不需要完整模拟真实串口的设备模型,只需要一个极简的实现 14 | - 当然,这样做就对shell输入支持很差。不过可以预制输入字符串,自动执行命令 15 | 16 | 17 | ## 核实现要求 18 | 19 | - RV32IMA 20 | - 由于只有一个硬件线程,原子指令的实现非常简单 21 | - 支持zifencei,zicsr,zicntr 22 | - 不需要实现zihpm 23 | - 支持S模式和U模式,以及Sv32虚拟内存机制 24 | - 无需支持指令集功能选择,misa只读,写入忽略即可 25 | - 无需支持字节序切换,mstatus.MBE/SBE/UBE可均硬编码为0 26 | - wip可实现为nop,U模式执行WFI可以立即触发非法指令异常,mstatus.TW可以忽略 27 | - mconfigptr可硬编码为0,不实现相关功能 28 | - 性能监视器,xenvcfg,mseccfg,PMP都可以不实现,访问相关CSR直接非法指令异常即可 29 | 30 | ## 外设实现 31 | 32 | - CLINT需要完整实现,但只需要支持一个Hart 33 | - 超简单的串口实现 34 | - 基地址偏移为0的写入直接输出即可,其他写操作全部忽略 35 | - 基地址偏移为0的读操作,返回下一个可读取的字符。若无字符可读取,返回0xff 36 | - 基地址偏移为5字节的读操作,返回`0x60 & x`,其中x表示是否有字符可供读取,有1无0 37 | - 其他读操作返回0即可 38 | - 由于驱动打开串口设备时会读取一次偏移为0的位置,预置输入字符串需要在最前面加一个占位的空格 39 | - 仓库根目录下`uart.cc`为一个参考实现 40 | 41 | ## 编译安装32位工具链 42 | 43 | ubuntu软件包安装的工具链不带multilib,没有32位的软浮点等等实现,且libc中带有压缩指令,很难正常进行接下来的编译。所以我们需要自己编译工具链。 44 | 45 | ``` 46 | $ git clone https://github.com/riscv/riscv-gnu-toolchain 47 | $ git submodule update --init --recursive 48 | ``` 49 | 50 | 进入目录,配置并开始构建 51 | 52 | ``` 53 | $ cd riscv-gnu-toolchain 54 | $ ./configure --prefix=/opt/riscv --with-arch=rv32ima --with-abi=ilp32 55 | $ sudo make linux -j 56 | ``` 57 | 58 | 这样,工具链将只编译出RV32IMA指令。 59 | 60 | ## 构建initramfs 61 | 62 | Linux运行需要一个根文件系统。正常的根文件系统需要实现一个块设备,但是那个实现起来就麻烦了。Linux提供两种内存文件系统:initramfs和initrd。选用initramfs是因为initrd已经deprecated了,并且initramfs构建也更加简单。 63 | 64 | 一般的做法是在initramfs中放一个busybox,不过也可以自己构建根文件系统,往里面放自己编译的程序。我们的系统是带有MMU的完整Linux,可以运行elf格式的可执行文件。 65 | 66 | ### 编译BusyBox 67 | 68 | 直接去busybox.net下载源码并解压,这里的版本是1.36.1 69 | 70 | ``` 71 | $ cd busybox-1.36.1 72 | $ make CROSS_COMPILE=riscv32-unknown-linux-gnu- defconfig 73 | $ make CROSS_COMPILE=riscv32-unknown-linux-gnu- menuconfig 74 | ``` 75 | 76 | 进入menuconfig界面,选中 Settings -> Build static binary (no shared libs) 77 | 78 | ``` 79 | $ make CROSS_COMPILE=riscv32-unknown-linux-gnu- -j 80 | $ make CROSS_COMPILE=riscv32-unknown-linux-gnu- install 81 | ``` 82 | 83 | 编译产物应该就在`_install`目录下了 84 | 85 | ### 编译自己的程序 86 | 87 | 直接用我们编译的工具链编译一般的C程序就好了。不过记得要静态编译,因为我们的系统中不包含libc 88 | 89 | ### 镜像文件构建 90 | 91 | initramfs是和内核打包在一起的。内核编译时可以接受两种initramfs输入:镜像cpio镜像文件,和描述文本文件。 92 | 93 | #### CPIO镜像 94 | 95 | 将所有需要打包的文件移动到当前目录下,例如: 96 | 97 | ``` 98 | $ cp -r busybox-1.36.1/_install ./initramfs 99 | $ cd initramfs 100 | ``` 101 | 102 | 将busybox全部挪过来,创建虚拟文件系统的挂载点,预置/dev下的node文件 103 | 104 | ``` 105 | $ mkdir proc sys dev 106 | $ cd dev 107 | $ sudo mknod -m 644 console c 5 1 108 | $ sudo mknod -m 644 null c 1 3 109 | $ cd .. 110 | ``` 111 | 112 | 然后我们还需要为linux创建一个init程序放在initramfs的根目录下。由于我们有busybox,可以使用shell脚本 113 | 114 | ```sh 115 | #!/bin/sh 116 | 117 | mount -t proc none /proc 118 | mount -t sysfs none /sys 119 | 120 | echo "Welcome to CmdBlock's RISC-V 32 mini Linux!" 121 | 122 | exec /bin/sh 123 | ``` 124 | 125 | 脚本挂载了必要的虚拟文件系统,输出了一条Hello信息,并运行sh。别忘了加上可执行权限。 126 | 127 | 当然你也可以在initramfs中加入你自己编译的程序,并可通过shell运行。 128 | 129 | 接下来打包所有文件并稍微进行一个压缩,得到一个镜像文件 130 | 131 | ``` 132 | $ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz 133 | ``` 134 | 135 | #### 描述文本文件 136 | 137 | ``` 138 | dir /dev 755 0 0 139 | 140 | nod /dev/console 644 0 0 c 5 1 141 | nod /dev/null 644 0 0 c 1 3 142 | 143 | file /init /home/cmdblock/Develop/initramfs-new/hello 755 0 0 144 | ``` 145 | 146 | 适用于文件比较少的情况,比如只有一个hello程序,打包工作交给Linux内核的构建程序。虽然文件列表也可以用find命令生成,但感觉不如自己打包。 147 | 148 | ## 构建Linux内核 149 | 150 | 直接去kernel.org下载源码并解压,这里的版本是6.10.9 151 | 152 | Linux的编译选项基于默认配置调整而来,而非最小配置。这样做会让编译/运行跑的慢一些,但是配置起来比较简单,对于核的实现测试也会更加充分(这点是笔者猜的,没什么依据)。 153 | 154 | ``` 155 | $ make ARCH=riscv CROSS_COMPILE=riscv32-unknown-linux-gnu- defconfig 156 | $ make ARCH=riscv CROSS_COMPILE=riscv32-unknown-linux-gnu- menuconfig 157 | ``` 158 | 159 | 进入menuconfig界面,调整以下设置 160 | 161 | - 进入 Platform type 162 | - 启用 Allow configurations that result in non-portable kernels 163 | - Base ISA 改为 RV32I 164 | - 关闭 VECTOR extension support 165 | - 关闭 Zbb extension support for bit manipulation instructions 166 | - 关闭 Zicbom extension support for non-coherent DMA operation 167 | - 关闭 Zicboz extension support for faster zeroing of memory 168 | - 关闭 FPU support 169 | - Unaligned Accesses Support 根据实际情况选择 170 | - OpenSBI能够探测硬件对于非对齐访存的支持情况,不支持时会使用软件进行模拟 171 | - Linux内核也能够探测。但是OpenSBI对非对齐访存的软件支持,对于Linux内核来说是透明的 172 | - 也就是说Linux会认为机器支持非对齐的访存,但实际上这是OpenSBI模拟的 173 | - 默认选项会对非对齐访存进行速度测试,机器不支持非对齐访存的情况下这个过程非常缓慢 174 | - 所以建议选择 Assume the system supports slow unaligned memory access 175 | - 如果你的硬件支持快速的非对齐访问,可以选择自动探测,或者假设快速非对齐访存 176 | - 进入 Boot options 177 | - 关闭 UEFI runtime support 178 | - 这需要对压缩指令的支持 179 | - 重新进入 Platform type 180 | - 关闭 Emit compressed instructions when building Linux 181 | - 内核编译不产生压缩指令 182 | - 进入 Kernel features 183 | - 打开 SBI v0.1 support 184 | - 进入 Device Drivers - Character devices 185 | - 打开 RISC-V SBI console support 186 | - 进入 Serial drivers 187 | - 打开 Early console using RISC-V SBI 188 | - 进入 General setup 189 | - 将 Initramfs source file(s) 改为镜像文件/描述文件的绝对路径 190 | 191 | ``` 192 | $ make ARCH=riscv CROSS_COMPILE=riscv32-unknown-linux-gnu- -j 193 | ``` 194 | 195 | 编译得到`arch/riscv/boot/Image`内核镜像文件 196 | 197 | ## 构建OpenSBI 198 | 199 | ``` 200 | $ git clone https://github.com/riscv-software-src/opensbi.git 201 | $ cd opensbi 202 | $ git checkout release-1.5.x 203 | ``` 204 | 205 | 笔者实验时的commit hash为`43cace6c3671e5172d0df0a8963e552bb04b7b20` 206 | 207 | ### 创建 Platform 208 | 209 | 进入`platform`目录,将`template`目录复制一份,重命名为你喜欢的名字,并进入该目录。 210 | 211 | - 创建设备树文件`xxx.dts` 212 | 213 | - 可参考仓库根目录下`rvcore/rvcore.dts` 214 | - 需要注意的是内核启动参数,`console=hvc0 earlycon=sbi`,这样做允许我们使用简陋的串口仿真模型运行真实的linux 215 | - 因为所有的输入输出都通过OpenSBI,whose串口驱动十分简单 216 | - 然后使用`dtc xxx.dts -o xxx.dtb`编译得到设备树blob(`.dtb`)文件 217 | 218 | - 编辑`platform.c` 219 | 220 | - 编辑开头的宏定义 221 | - `PLATFORM_HART_COUNT`改为1,表示一个硬件线程 222 | - `PLATFORM_CLINT_ADDR`改为CLINT的地址 223 | - `PLATFORM_ACLINT_MTIMER_FREQ`可根据你的实现确定 224 | - `PLATFORM_UART_ADDR`改为串口设备地址 225 | - 将`plic`结构体变量的定义注释掉 226 | - 编辑`mtimer`结构体成员`has_64bit_mmio`为`false` 227 | - 将`platform_irqchip_init`函数注释掉 228 | - 编辑`platform_ops`结构体成员`irqchip_init`为`NULL` 229 | - 编辑`platform`结构体成员`name`为你喜欢的名字 230 | 231 | - 编辑`objects.mk` 232 | 233 | - 编辑 Platform RISC-V XLEN, ABI, ISA and Code Model configuration 234 | 235 | - ``` 236 | PLATFORM_RISCV_XLEN = 32 237 | PLATFORM_RISCV_ABI = ilp32 238 | PLATFORM_RISCV_ISA = rv32ima_zicsr_zifencei_zicntr 239 | PLATFORM_RISCV_CODE_MODEL = medany 240 | ``` 241 | 242 | - 取消`# platform-objs-y +=
.o`注释,根据设备树文件名修改值,如`rvcore.o` 243 | 244 | - 取消`FW_FDT_PATH`注释,值改为设备树blob(`.dtb`)文件**绝对路径** 245 | 246 | - `FW_DYNAMIC`,`FW_JUMP`值改为`n` 247 | 248 | - `FW_PAYLOAD`值改为`y` 249 | 250 | - 将对`FW_PAYLOAD_OFFSET`赋值的ifeq语句直接全部注释掉 251 | 252 | - 取消`FW_PAYLOAD_ALIGN`注释,值改为`0x400000` 253 | 254 | - 取消`FW_PAYLOAD_PATH`注释,值改为Linux内核镜像文件**绝对路径** 255 | 256 | 仓库根目录下`rvcore`目录为一个platform示例。 257 | 258 | ### 编译生成镜像 259 | 260 | ``` 261 | $ make CROSS_COMPILE=riscv32-unknown-linux-gnu- FW_TEXT_START=0x80000000 PLATFORM=rvcore 262 | ``` 263 | 264 | 其中`FW_TEXT_START`为OpenSBI执行开始时的PC值。OpenSBI执行前,需要将整个镜像复制到该值开始的内存区域,并跳转到该地址。 265 | 266 | `PLATFORM`即平台名,是你喜欢的名字。 267 | 268 | 镜像位于`platform/<你喜欢的名字>/firmware/fw_payload.bin`,打包了包括OpenSBI,Linux内核,initramfs在内的全部内容,可以直接在模拟器/RTL仿真中运行!(当然你或许需要一个bootloader) 269 | 270 | 修改makefile内容后可能需要清理构建产物后重新编译:`make clean`,或者更加彻底地,`make distclean` 271 | -------------------------------------------------------------------------------- /rvcore/Kconfig: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-2-Clause 2 | 3 | # 4 | # All mandatory drivers or libraries for this platform should 5 | # be directly selected by the PLATFORM_xyz kconfig symbol. 6 | # 7 | # All optional drivers or libraries for this platform should 8 | # be enabled via configs/defconfig of this platform. 9 | # 10 | config PLATFORM_TEMPLATE 11 | bool 12 | select IPI_MSWI 13 | select IRQCHIP_PLIC 14 | select SERIAL_UART8250 15 | select TIMER_MTIMER 16 | default y 17 | -------------------------------------------------------------------------------- /rvcore/configs/defconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CmdBlockZQG/rvcore-mini-linux/3ca07d4744a45cd065ae002cf6bb4d56a998b607/rvcore/configs/defconfig -------------------------------------------------------------------------------- /rvcore/objects.mk: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | # 4 | # Copyright (c) 2019 Western Digital Corporation or its affiliates. 5 | # 6 | 7 | # Compiler pre-processor flags 8 | platform-cppflags-y = 9 | 10 | # C Compiler and assembler flags. 11 | platform-cflags-y = 12 | platform-asflags-y = 13 | 14 | # Linker flags: additional libraries and object files that the platform 15 | # code needs can be added here 16 | platform-ldflags-y = 17 | 18 | # 19 | # Command for platform specific "make run" 20 | # Useful for development and debugging on plaftform simulator (such as QEMU) 21 | # 22 | # platform-runcmd = your_platform_run.sh 23 | 24 | # 25 | # Platform RISC-V XLEN, ABI, ISA and Code Model configuration. 26 | # These are optional parameters but platforms can optionaly provide it. 27 | # Some of these are guessed based on GCC compiler capabilities 28 | # 29 | PLATFORM_RISCV_XLEN = 32 30 | PLATFORM_RISCV_ABI = ilp32 31 | PLATFORM_RISCV_ISA = rv32ima_zicsr_zifencei_zicntr 32 | PLATFORM_RISCV_CODE_MODEL = medany 33 | 34 | # Space separated list of object file names to be compiled for the platform 35 | platform-objs-y += platform.o 36 | 37 | # 38 | # If the platform support requires a builtin device tree file, the name of 39 | # the device tree compiled file should be specified here. The device tree 40 | # source file be in the form
.dts 41 | # 42 | platform-objs-y += rvcore.o 43 | 44 | # Optional parameter for path to external FDT 45 | FW_FDT_PATH=/home/cmdblock/Develop/rvcore-mini-linux/build/opensbi/platform/rvcore/rvcore.dtb 46 | 47 | # 48 | # Dynamic firmware configuration. 49 | # Optional parameters are commented out. Uncomment and define these parameters 50 | # as needed. 51 | # 52 | FW_DYNAMIC=n 53 | 54 | # 55 | # Jump firmware configuration. 56 | # Optional parameters are commented out. Uncomment and define these parameters 57 | # as needed. 58 | # 59 | FW_JUMP=n 60 | # This needs to be 4MB aligned for 32-bit support 61 | # This needs to be 2MB aligned for 64-bit support 62 | # ifeq ($(PLATFORM_RISCV_XLEN), 32) 63 | # FW_JUMP_OFFSET=0x400000 64 | # else 65 | # FW_JUMP_OFFSET=0x200000 66 | # endif 67 | # FW_JUMP_FDT_OFFSET=0x2200000 68 | # 69 | # You can use fixed address for jump firmware as an alternative option. 70 | # SBI will prefer "_ADDR" if both "_ADDR" and "_OFFSET" are 71 | # defined 72 | # ifeq ($(PLATFORM_RISCV_XLEN), 32) 73 | # FW_JUMP_ADDR=0x80400000 74 | # else 75 | # FW_JUMP_ADDR=0x80200000 76 | # endif 77 | # FW_JUMP_FDT_ADDR=0x82200000 78 | 79 | # 80 | # Firmware with payload configuration. 81 | # Optional parameters are commented out. Uncomment and define these parameters 82 | # as needed. 83 | # 84 | FW_PAYLOAD=y 85 | # This needs to be 4MB aligned for 32-bit support 86 | # This needs to be 2MB aligned for 64-bit support 87 | # ifeq ($(PLATFORM_RISCV_XLEN), 32) 88 | # FW_PAYLOAD_OFFSET=0x400000 89 | # else 90 | # FW_PAYLOAD_OFFSET=0x200000 91 | # endif 92 | FW_PAYLOAD_ALIGN=0x400000 93 | FW_PAYLOAD_PATH=/home/cmdblock/Develop/rvcore-mini-linux/build/linux-6.10.9/arch/riscv/boot/Image 94 | # FW_PAYLOAD_FDT_OFFSET=0x2200000 95 | # 96 | # You can use fixed address for payload firmware as an alternative option. 97 | # SBI will prefer "FW_PAYLOAD_FDT_ADDR" if both "FW_PAYLOAD_FDT_OFFSET" 98 | # and "FW_PAYLOAD_FDT_ADDR" are defined. 99 | # FW_PAYLOAD_FDT_ADDR=0x82200000 100 | -------------------------------------------------------------------------------- /rvcore/platform.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) 2019 Western Digital Corporation or its affiliates. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /* 13 | * Include these files as needed. 14 | * See objects.mk PLATFORM_xxx configuration parameters. 15 | */ 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define PLATFORM_PLIC_ADDR 0xc000000 22 | #define PLATFORM_PLIC_SIZE (0x200000 + \ 23 | (PLATFORM_HART_COUNT * 0x1000)) 24 | #define PLATFORM_PLIC_NUM_SOURCES 1 25 | #define PLATFORM_HART_COUNT 1 26 | #define PLATFORM_CLINT_ADDR 0x20000000 27 | #define PLATFORM_ACLINT_MTIMER_FREQ 10000000 28 | #define PLATFORM_ACLINT_MSWI_ADDR (PLATFORM_CLINT_ADDR + \ 29 | CLINT_MSWI_OFFSET) 30 | #define PLATFORM_ACLINT_MTIMER_ADDR (PLATFORM_CLINT_ADDR + \ 31 | CLINT_MTIMER_OFFSET) 32 | #define PLATFORM_UART_ADDR 0x10000000 33 | #define PLATFORM_UART_INPUT_FREQ 10000000 34 | #define PLATFORM_UART_BAUDRATE 115200 35 | 36 | // static struct plic_data plic = { 37 | // .addr = PLATFORM_PLIC_ADDR, 38 | // .size = PLATFORM_PLIC_SIZE, 39 | // .num_src = PLATFORM_PLIC_NUM_SOURCES, 40 | // }; 41 | 42 | static struct aclint_mswi_data mswi = { 43 | .addr = PLATFORM_ACLINT_MSWI_ADDR, 44 | .size = ACLINT_MSWI_SIZE, 45 | .first_hartid = 0, 46 | .hart_count = PLATFORM_HART_COUNT, 47 | }; 48 | 49 | static struct aclint_mtimer_data mtimer = { 50 | .mtime_freq = PLATFORM_ACLINT_MTIMER_FREQ, 51 | .mtime_addr = PLATFORM_ACLINT_MTIMER_ADDR + 52 | ACLINT_DEFAULT_MTIME_OFFSET, 53 | .mtime_size = ACLINT_DEFAULT_MTIME_SIZE, 54 | .mtimecmp_addr = PLATFORM_ACLINT_MTIMER_ADDR + 55 | ACLINT_DEFAULT_MTIMECMP_OFFSET, 56 | .mtimecmp_size = ACLINT_DEFAULT_MTIMECMP_SIZE, 57 | .first_hartid = 0, 58 | .hart_count = PLATFORM_HART_COUNT, 59 | .has_64bit_mmio = false, 60 | }; 61 | 62 | /* 63 | * Platform early initialization. 64 | */ 65 | static int platform_early_init(bool cold_boot) 66 | { 67 | return 0; 68 | } 69 | 70 | /* 71 | * Platform final initialization. 72 | */ 73 | static int platform_final_init(bool cold_boot) 74 | { 75 | return 0; 76 | } 77 | 78 | /* 79 | * Initialize the platform console. 80 | */ 81 | static int platform_console_init(void) 82 | { 83 | /* Example if the generic UART8250 driver is used */ 84 | return uart8250_init(PLATFORM_UART_ADDR, PLATFORM_UART_INPUT_FREQ, 85 | PLATFORM_UART_BAUDRATE, 0, 1, 0); 86 | } 87 | 88 | /* 89 | * Initialize the platform interrupt controller for current HART. 90 | */ 91 | // static int platform_irqchip_init(bool cold_boot) 92 | // { 93 | // u32 hartid = current_hartid(); 94 | // int ret; 95 | // 96 | // /* Example if the generic PLIC driver is used */ 97 | // if (cold_boot) { 98 | // ret = plic_cold_irqchip_init(&plic); 99 | // if (ret) 100 | // return ret; 101 | // } 102 | // 103 | // return plic_warm_irqchip_init(&plic, 2 * hartid, 2 * hartid + 1); 104 | // } 105 | 106 | /* 107 | * Initialize IPI for current HART. 108 | */ 109 | static int platform_ipi_init(bool cold_boot) 110 | { 111 | int ret; 112 | 113 | /* Example if the generic ACLINT driver is used */ 114 | if (cold_boot) { 115 | ret = aclint_mswi_cold_init(&mswi); 116 | if (ret) 117 | return ret; 118 | } 119 | 120 | return aclint_mswi_warm_init(); 121 | } 122 | 123 | /* 124 | * Initialize platform timer for current HART. 125 | */ 126 | static int platform_timer_init(bool cold_boot) 127 | { 128 | int ret; 129 | 130 | /* Example if the generic ACLINT driver is used */ 131 | if (cold_boot) { 132 | ret = aclint_mtimer_cold_init(&mtimer, NULL); 133 | if (ret) 134 | return ret; 135 | } 136 | 137 | return aclint_mtimer_warm_init(); 138 | } 139 | 140 | /* 141 | * Platform descriptor. 142 | */ 143 | const struct sbi_platform_operations platform_ops = { 144 | .early_init = platform_early_init, 145 | .final_init = platform_final_init, 146 | .console_init = platform_console_init, 147 | .irqchip_init = NULL, //platform_irqchip_init, 148 | .ipi_init = platform_ipi_init, 149 | .timer_init = platform_timer_init 150 | }; 151 | const struct sbi_platform platform = { 152 | .opensbi_version = OPENSBI_VERSION, 153 | .platform_version = SBI_PLATFORM_VERSION(0x0, 0x00), 154 | .name = "rvcore", 155 | .features = SBI_PLATFORM_DEFAULT_FEATURES, 156 | .hart_count = 1, 157 | .hart_stack_size = SBI_PLATFORM_DEFAULT_HART_STACK_SIZE, 158 | .heap_size = SBI_PLATFORM_DEFAULT_HEAP_SIZE(1), 159 | .platform_ops_addr = (unsigned long)&platform_ops 160 | }; 161 | -------------------------------------------------------------------------------- /rvcore/rvcore.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | / { 3 | #address-cells = <0x2>; 4 | #size-cells = <0x2>; 5 | 6 | chosen { 7 | bootargs = "console=hvc0 earlycon=sbi"; 8 | }; 9 | 10 | memory@80000000 { 11 | device_type = "memory"; 12 | reg = <0x0 0x80000000 0x0 0x10000000>; 13 | }; 14 | 15 | cpus { 16 | #address-cells = <0x1>; 17 | #size-cells = <0x0>; 18 | timebase-frequency = <100000000>; 19 | cpu0: cpu@0 { 20 | phandle = <0x1>; 21 | device_type = "cpu"; 22 | reg = <0x0>; 23 | status = "okay"; 24 | compatible = "riscv"; 25 | riscv,isa = "rv32ima"; 26 | mmu-type = "riscv,sv32"; 27 | cpuintr0: interrupt-controller { 28 | #interrupt-cells = <0x1>; 29 | interrupt-controller; 30 | compatible = "riscv,cpu-intc"; 31 | phandle = <0x2>; 32 | }; 33 | }; 34 | cpu-map { 35 | cluster0 { 36 | core0 { 37 | cpu = <0x1>; 38 | }; 39 | }; 40 | }; 41 | }; 42 | 43 | serial0: serial@10000000 { 44 | clock-frequency = <1000000>; 45 | reg = <0x00 0x10000000 0x00 0x100>; 46 | compatible = "ns16850"; 47 | }; 48 | 49 | clint@20000000 { 50 | interrupts-extended = <&cpuintr0 0x3 &cpuintr0 0x7>; 51 | reg = <0x0 0x20000000 0x0 0x10000>; 52 | compatible = "riscv,clint0"; 53 | }; 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /uart.cc: -------------------------------------------------------------------------------- 1 | #include "local-include/uart.h" 2 | 3 | #include 4 | 5 | // 预置的输入字符串 6 | static const char *uart_input_ptr = " " // 占位空格字符 7 | "busybox | head -n4\n" 8 | "ls\n" 9 | "./hello\n"; 10 | 11 | static inline bool input_available() { 12 | return *uart_input_ptr != '\0'; 13 | } 14 | 15 | // 0x1000表示mmio区域大小 16 | UART::UART(paddr_t addr): Device(addr, 0x1000) { } 17 | 18 | UART::~UART() { } 19 | 20 | void UART::write(paddr_t addr, int len, word_t data) { 21 | if (addr == 0) { 22 | putchar(data & 0xff); 23 | fflush(stdout); 24 | } 25 | } 26 | 27 | word_t UART::read(paddr_t addr, int len) { 28 | if (addr == 0) { 29 | if (input_available()) { 30 | return *uart_input_ptr++; 31 | } else { 32 | return 0xff; 33 | } 34 | } else if (addr == 5) { 35 | return 0x60 | input_available(); 36 | } 37 | return 0; 38 | } 39 | 40 | word_t UART::get_ip() { 41 | return input_available(); 42 | } 43 | --------------------------------------------------------------------------------