From d51665904faa31c39106e4b2afa7541615072e72 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Fri, 29 Nov 2024 22:17:13 +0100 Subject: Allow the kernel to run as a Linux image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to run on real hardware, it's actually easier to re-use workflows from existing bootloaders than loading things in memory manually. In order to achieve this two things had to be settled: 1. The image has to have a Linux header as defined in the RISC-V port. This header will be taken into consideration by bootloaders in order to know where to jump, the image offset, the endianness of the image, etc. 2. The image cannot just be an ELF executable. Rather, we must translate it into binary form via `objcopy`. With all of this at hand, I was able to make the kernel run on a Starfive VisionFive 2 board, and I have recorded an example so it's available as documentation. Signed-off-by: Miquel Sabaté Solà --- Makefile | 24 ++++++++++++++++-------- README.md | 44 +++++++++++++++++++++++++++++++++++++------- doc/vf2.svg | 1 + include/fbos/image.h | 23 +++++++++++++++++++++++ include/fbos/mm.h | 3 ++- kernel/fbos.ld.S | 10 +++++++--- kernel/head.S | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 doc/vf2.svg create mode 100644 include/fbos/image.h diff --git a/Makefile b/Makefile index 70000a1..5c95c02 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ endif CC = $(CROSS_COMPILE)gcc$(CC_SUFFIX) LD = $(CROSS_COMPILE)ld HOSTCC = gcc$(CC_SUFFIX) -QEMU ?= qemu-system-riscv64 +QEMU ?= qemu-system-riscv64 +OBJCPY = $(CROSS_COMPILE)objcopy ISA ?= rv64imafdc_zicntr_zicsr_zifencei_zihpm_zca_zcd_zba_zbb ASFLAGS = -march=$(ISA) -mabi=lp64d -mcmodel=medany -fno-PIE @@ -59,7 +60,8 @@ endif SRC = $(filter-out kernel/fbos.ld.S, $(wildcard kernel/*.S kernel/*.c lib/*.c lib/*.S)) OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SRC))) LINKER = kernel/fbos.ld -KRNL = fbos +IMAGE = fbos +KRNL = $(IMAGE).elf USR = usr/bin/init usr/bin/fizz usr/bin/buzz usr/bin/fizzbuzz INIT = usr/initramfs.cpio TESTS = test/test_dt test/test_initrd @@ -71,7 +73,13 @@ LDFLAGS += -T $(LINKER) # Kernel .PHONY: all -all: clean $(KRNL) usr test +all: clean $(IMAGE) usr test + +.PHONY: $(IMAGE) +$(IMAGE): $(KRNL) + $(E) " OBJCOPY " $@ + $(Q) $(OBJCPY) $(KRNL) -O binary $(IMAGE) + $(Q) rm $(KRNL) .PHONY: $(KRNL) $(KRNL): $(OBJ) $(LINKER).S @@ -134,7 +142,7 @@ test/%: test/%.o .PHONY: archive archive: all $(Q) $(eval DIR := $(shell mktemp -d)) - $(Q) mkdir -p $(DIR)/fbos && cp $(KRNL) $(INIT) $(DIR)/fbos/ + $(Q) mkdir -p $(DIR)/fbos && cp $(IMAGE) $(INIT) $(DIR)/fbos/ $(Q) cd $(DIR) && tar czf $(ARCHIVE) fbos/ $(Q) cp $(DIR)/$(ARCHIVE) . $(E) " TAR $(ARCHIVE)" @@ -143,11 +151,11 @@ archive: all # Hacking .PHONY: qemu -qemu: clean $(KRNL) usr +qemu: clean $(IMAGE) usr ifeq ($(strip $(QEMU_BIOS)),) - $(Q) $(QEMU) $(QEMU_FLAGS) -machine virt -kernel $(KRNL) -initrd $(INIT) + $(Q) $(QEMU) $(QEMU_FLAGS) -machine virt -kernel $(IMAGE) -initrd $(INIT) else - $(Q) $(QEMU) $(QEMU_FLAGS) -machine virt -bios $(QEMU_BIOS) -kernel $(KRNL) -initrd $(INIT) + $(Q) $(QEMU) $(QEMU_FLAGS) -machine virt -bios $(QEMU_BIOS) -kernel $(IMAGE) -initrd $(INIT) endif .PHONY: gdb @@ -156,7 +164,7 @@ gdb: .PHONY: clean clean: - $(Q) rm -rf $(OBJ) $(KRNL) $(LINKER) $(USR) usr/src/*.o $(INIT) test/*.o test/lib/*.o $(TESTS) $(ARCHIVE) + $(Q) rm -rf $(OBJ) $(KRNL) $(IMAGE) $(LINKER) $(USR) usr/src/*.o $(INIT) test/*.o test/lib/*.o $(TESTS) $(ARCHIVE) .PHONY: lint lint: diff --git a/README.md b/README.md index ea7faec..5bec21e 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,9 @@ are unaware of each other and it's up to the kernel to schedule them by using the timer interrupts as given by openSBI (`fizz` on % 3 seconds, `buzz` on % 5 seconds, and `fizzbuzz` on % 15 seconds). -This kernel provides just one system call, `write_and_block`, which allows any -program to pass the string to be written into the serial port and wait for the -kernel to re-schedule it. - -![Demo on QEMU](./doc/qemu.svg) +This kernel provides just one system call, `write`, which allows any program to +pass the string to be written into the serial port and wait for the kernel to +re-schedule it. ## Build @@ -75,8 +73,12 @@ $ make qemu This will open up QEMU in `-nographic` mode (hence the serial output will be simply redirected to stdout), and you will be able to see the whole thing -working. Moreover, the `qemu` target can be paired with the `DEBUG` parameter -that you can pass to make. Hence, you can also call it like so: +working. Just like this: + +![Demo on QEMU](./doc/qemu.svg) + +Moreover, the `qemu` target can be paired with the `DEBUG` parameter that you +can pass to make. Hence, you can also call it like so: ``` $ make qemu DEBUG=1 @@ -106,6 +108,34 @@ $ make gdb GDB_EXTRA_FLAGS="-tui" And now you have started a GDB session with a nice TUI interface. +### VisionFive 2 + +This kernel can also be run on real hardware. In particular, I have tested it +with the Starfive VisionFive 2 board. I have tested this with an existing Linux +installation. In there, I have modified the bootloader configuration so I have +now this entry: + +``` +label l6 + menu label FizzBuzz OS + linux /fbos + initrd /initramfs.cpio + fdtdir /dtbs/ +``` + +In order to get the needed files, you can use the `archive` make target: + +``` +make archive +``` + +Copy this tarball to your board and then place the `fbos` binary image and the +`initramfs.cpio` file into `/boot`. When you reset your board, you will get the +new entry from U-Boot and you will be able to run the kernel from there. Like +this: + +![Demo on VisionFive 2](./doc/vf2.svg) + ## Requirements We do not want to support a myriad of different scenarios, but we want to keep diff --git a/doc/vf2.svg b/doc/vf2.svg new file mode 100644 index 0000000..0936ea2 --- /dev/null +++ b/doc/vf2.svg @@ -0,0 +1 @@ +mssola:~$mssola:~$sudoscreen/dev/ttyUSB1115200[sudo]contrasenyaperaroot:U-BootSPL2021.10(May092024-22:42:02+0800)LPDDR4:8Gversion:g8ad50857.TryingtobootfromSPIOpenSBIv1.2__________________/__\/____|_\__|||||_________|(___||_)|||||||'_\/_\'_\\___\|_<||||__|||_)|__/|||____)||_)|||_\____/|.__/\___|_||_|_____/|___/_____||||_|PlatformName:StarFiveVisionFiveV2PlatformFeatures:medelegPlatformHARTCount:5PlatformPlatformIPIDevice:aclint-mswiPlatformTimerDevice:aclint-mtimer@4000000HzPlatformConsoleDevice:uart8250PlatformHSMDevice:---PlatformPMUDevice:---PlatformRebootDevice:pm-resetPlatformShutdownDevice:pm-resetPlatformSuspendDevice:---FirmwareBase:0x40000000FirmwareSize:392KBFirmwareRWOffset:0x40000RuntimeSBIVersion:1.0Domain0Name:rootDomain0BootHART:1Domain0HARTs:0*,1*,2*,3*,4*Domain0Region00:0x0000000002000000-0x000000000200ffffM:(I,R,W)S/U:()Domain0Region01:0x0000000040000000-0x000000004003ffffM:(R,X)S/U:()Domain0Region02:0x0000000040040000-0x000000004007ffffM:(R,W)S/U:()Domain0Region03:0x0000000000000000-0xffffffffffffffffM:(R,W,X)S/U:(R,W,X)Domain0NextAddress:0x0000000040200000Domain0NextArg1:0x0000000042200000Domain0NextMode:S-modeDomain0SysReset:yesDomain0SysSuspend:yesBootHARTID:1BootHARTDomain:rootBootHARTPrivVersion:v1.11BootHARTBaseISA:rv64imafdcbxBootHARTISAExtensions:noneBBootHARTPMPCount:8BootHARTPMPGranularity:4096BootHARTPMPAddressBits:34BootHARTMHPMCount:2BootHARTMIDELEG:0x0000000000000222BootHARTMEDELEG:0x000000000000b109U-Boot2021.10(May092024-22:42:02+0800),Build:jenkins-github_visionfive2-24CPU:rv64imacu_zba_zbbModel:StarFiveVisionFiveV2DRAM:8GiBMMC:sdio0@16010000:0,sdio1@16020000:1LoadingEnvironmentfromSPIFlash...SF:Detectedgd25lq128withpagesize256Bytes,erasesize4KiB,total16MiB***Warning-badCRC,usingdefaultenvironmentStarFiveEEPROMformatv2--------EEPROMINFO--------Vendor:StarFiveTechnologyCo.,Ltd.ProductfullSN:VF7110B1-2318-D008E000-18004831dataversion:0x2PCBrevision:0xb2BOMrevision:AEthernetMAC0address:6c:cf:39:00:b8:4cEthernetMAC1address:6c:cf:39:00:b8:4dIn:serialOut:serialErr:serialNet:eth0:ethernet@16030000,eth1:ethernet@16040000Hitanykeytostopautoboot:2Hitanykeytostopautoboot:0CarddidnotrespondtovoltageCarddidnotrespondtovoltageselect!:-110starfive_pciepcie@2B000000:Portlinkup.starfive_pciepcie@2B000000:StarfivePCIebusprobed.PCI:Failedautoconfigbar10starfive_pciepcie@2C000000:Portlinkup.starfive_pciepcie@2C000000:StarfivePCIebusprobed.Device0:Vendor:0xc0a9Rev:P8CR002Prod:234444C8444AType:HardDiskCapacity:476940.0MB=465.7GB(976773168x512)...isnowcurrentdeviceTrybootingfromNVME0...Failedtoload'vf2_uEnv.txt'##Warning:Inputdataexceeds1##Warning:Inputdataexceeds1048576bytes-truncated##Inf##Info:inputdatasize=1048578=0##Info:inputdatasize=1048578=0x100002##Error:"boot2"notd##Error:"boot2"notdefinedTringbootingdistro...419bytesreadin4ms(101.6KiB/s)Retrievingfile:/extlinux/extlinux.conf838bytesreadin4ms(204.1KiB/s)U-Bootmenu1:DebianGNU/Linuxbookworm/sid6.12.0mssola2:DebianGNU/Linuxbookworm/sid6.12.0mssola(rescuetarget)3:FizzBuzzOSEnterchoice:3Retrievingfile:/initramfs.cpio6656bytesreadin4ms(1.6MiB/s)Retrievingfile:/fbos14444bytesreadin4ms(3.4MiB/s)Retrievingfile:/dtbs/6.12.0mssola/starfive/jh7110-starfive-visionfive-2-v1.3b.dtb38025bytesreadin5ms(7.3MiB/s)libfdtfdt_path_offset()returnedFDT_ERR_NOTFOUND##FlattenedDeviceTreeblobat46000000Bootingusingthefdtblobat0x46000000UsingDeviceTreeinplaceat0000000046000000,end000000004600c488Startingkernel...clku2_dw_i2c_clk_corealreadydisabledclku2_dw_i2c_clk_apbalreadydisabledclku5_dw_i2c_clk_corealreadydisabledclku5_dw_i2c_clk_apbalreadydisabledclku0_mipitx_dphy_clk_txescalreadydisabledWelcometoFizzBuzzOS!Runningon:StarFiveVisionFive2v1.3Bfizzbuzzfizzbuzz[screenisterminating]TerminatU-BootSPL2021.10(May09202____//__\/____|||||_______||||||||'_\/_\'_\\___\|_<|||__|||_)|__/|||___\____/|.__/\PlatformName:StarPlatformFeaPlatforPlatformTimerDevice:aclPlPPlatformRebootDevice:pm-rPlatformShutdownDevicePlatformSuspendDevFirmwareBaseFirmwareSiFirmwarRuDomain0Name:rooDomain0HARTs:0*Domain0Region00Domain0Region00:0x0000000002000000-0x0Domain0Region00:0x0000000002000000-0x000000000200ffffM:(I,R,W)S/U:Domain0Region01:Domain0Region01:0x0000000040000000-0x00000000400Domain0Domain0Region02:0x0000000040Domain0Region02:0x0000000040040000-0x000000004007ffffM:(R,Domain0Region03Domain0Region03:0x0000000000000000-0xfffDomain0Region03:0x0000000000000000-0xffffffffffffffffM:(R,W,X)S/U:(RDomain0NextAddressDomain0NDomain0NextArg1:0x00000000422Domain0NextModeDomain0SysResetDomain0SysSuspendBootHARTIDBootHARTDomainBootHARTPrivVBootHARTBasBooBoBootHARTMIDELEG:0x000BootHARTMEDELEGU-Boot2021.10(May092024U-Boot2021.10(May092024-22:42:02+0800),Build:jenkinCPU:Model:StarDRAM:MMC:MMC:sdio0@16010000:0,sdio1@1602000LoadingEnvironmentfromSLoadingEnvironmentfromSPIFlash...LoadingEnvironmentfromSPIFlash...SF:Detectedgd25lq128withpageLoadingEnvironmentfromSPIFlash...SF:Detectedgd25lq128withpagesize256Bytes,erasesize4Ki***Warning-badCRC,usingdef---VendorProductfullSN:VF7110B1-2318dataversionBOMrEthernetMAC0addresEthernetMIErrModel:StarFiveVisiNet:Net:eth0:ethernet@16030000Hitanykeytostopautoboot:1starfive_pciepcie@2B000000:Porstarfive_pciepcie@2B000000:Stastarfive_pciepcie@2C000000:Porstarfive_pciepcie@2C000000:StaDevice0:Vendor:0xc0a9Rev:Capacity:476940.0TryFailedDevice0:Vendor:0xc0Device0:Vendor:0xc0a9Rev:P8CR002Prod:234444C84Type:HCapacity:Capacity:476940.0MB=465.7GB(97677316...isnowcurrentdevDevice0:VendDevice0:Vendor:0xc0a9Rev:P8CR002Prod:2CaCapacity:476940.0MB=465.7GB(...isnowcurTrybootingfromNV419bytesreadin4ms(101.6KiRetrievingfile:/extlinux838bytesreadin4ms(204.1Ki1:DebianGNU2:DebianGNU/Linuxbookworm/sEnterchoice:Retrievingfil6656bytesreadin4ms(1.6MiB14444bytesreadin4ms(3.4MiRetrievingfile:/dtbs/6.1Retrievingfile:/dtbs/6.12.0mssola/starfive/jh7110-starfi38025bytesreadin5ms(7.3Milibfdtfdt_path_offset()rlibfdtlibfdtfdt_path_offset()returnedFDT_libfdtfdt_path_oflibfdtfdt_path_offset()returlibfdtfdtlibfdtfdt_path_offset()returnedFDT_ERR_libfdtfdt_path_offset####FlattenedDeviceTreeblobat4BootingusingthefdUsingDUsingDeviceTreeinplaceat000000004clku2_clku2_dw_i2c_clk_apbalreadyclku5_dw_i2c_clk_coreclku5_dw_i2cclku0_mipitx_dphy_clk_txescalrWelcometoFizzBuRunningon:StarFiveVismssola:~$exit \ No newline at end of file diff --git a/include/fbos/image.h b/include/fbos/image.h new file mode 100644 index 0000000..c217c36 --- /dev/null +++ b/include/fbos/image.h @@ -0,0 +1,23 @@ +#ifndef __FBOS_IMAGE_H +#define __FBOS_IMAGE_H + +/* + * General definitions for building up an image that is suitable on real + * hardware that expect a Linux kernel header. + */ + +// Deprecated image magic. +#define RISCV_IMAGE_MAGIC "RISCV\0\0\0" + +// Image magic that the bootloader expects. It's placed at the same position as +// ARM64, which is why the previous 'RISCV_IMAGE_MAGIC' has been deprecated in +// favour of this one. +#define RISCV_IMAGE_MAGIC2 "RSC\x05" + +// Image header version as defined by Linux in the format of +// u32: MSB| u16: major | u16: minor |LSB +#define RISCV_HEADER_VERSION_MAJOR 0 +#define RISCV_HEADER_VERSION_MINOR 2 +#define RISCV_HEADER_VERSION (RISCV_HEADER_VERSION_MAJOR << 16 | RISCV_HEADER_VERSION_MINOR) + +#endif /* __FBOS_IMAGE */ diff --git a/include/fbos/mm.h b/include/fbos/mm.h index 25d18ce..e913fa7 100644 --- a/include/fbos/mm.h +++ b/include/fbos/mm.h @@ -17,7 +17,8 @@ * The code will be linked to start at the first page, which will have a given * offset. */ + #define PAGE_OFFSET 0x80200000 -#define LINK_ADDR PAGE_OFFSET +#define LOAD_OFFSET PAGE_OFFSET #endif /* __FBOS_MM_H */ diff --git a/kernel/fbos.ld.S b/kernel/fbos.ld.S index f74d90f..160a5cb 100644 --- a/kernel/fbos.ld.S +++ b/kernel/fbos.ld.S @@ -1,8 +1,11 @@ #include +OUTPUT_ARCH(riscv) +ENTRY(_start) + SECTIONS { // Ensure that the image starts at the very exact address SBI expects it to. - . = LINK_ADDR; + . = LOAD_OFFSET; _start = .; . = ALIGN(PAGE_SIZE); @@ -10,8 +13,9 @@ SECTIONS { // instead of clumping it into the main `.text` one. Well, I'm no expert on // linker configuration, so patches are welcome :) .text : { + // head.S + KEEP(*(.head.text)) _text = .; - *(.head.text) // Aligning it to a full page is maybe a bit too much considering how // small `.text.head` really is. I just saw this same thing on the Linux @@ -19,7 +23,7 @@ SECTIONS { . = ALIGN(PAGE_SIZE); // From now on the rest of the `.text` could just be a continuation, but - // I further split it into `.text.kernel` so the jump from head isn't + // I further split it into `.kernel.text` so the jump from head isn't // that large. That is, we put first the very core of the kernel, and // the rest can go wherever. __kernel_text_start = .; diff --git a/kernel/head.S b/kernel/head.S index c8f523d..210eb7c 100644 --- a/kernel/head.S +++ b/kernel/head.S @@ -1,9 +1,61 @@ +#include #include .global _start +.global _start_kernel .section .head.text +// Since this kernel boots in virtualization environments and machines that are +// expecting a Linux kernel, we have to adhere to how bootloaders expect Linux +// kernel images to look like. In particular, the entry of the kernel is +// actually a Boot image header, as defined in: +// +// https://docs.kernel.org/arch/riscv/boot-image-header.html +// +// Conveniently, for machines who don't care about this (e.g. QEMU), the first +// 64 bits correspond to two instructions that can be set up, and hence entering +// here will simply jumpt to `_start_kernel`, our real entry. _start: + // The first two words give us room for two executable instructions. Linux + // uses that on EFI support to first allocate a magic value for UEFI and + // then have a `j _start_kernel` instruction. Otherwise it just allocates + // the first one for `j _start_kernel` and leaves the second word empty. The + // latter is what we do here as well. + j _start_kernel + .word 0 + + // Ensure alignment for the next double word. + .balign 8 + + // Load offset. Note that this matches the PAGE_OFFSET as defined in + // `include/mm.h`. + .dword 0x200000 + + // Size of the image. This is *mandatory* as per bootloader request. + .dword _end - _start + + // Flags. As defined by Linux, only one bit matters here, which is related + // to endianness. Setting 0 means little-endian. + .dword 0 + + // Header version. + .word RISCV_HEADER_VERSION + + // Reserved fields. + .word 0 + .dword 0 + + // Deprecated image magic. + .ascii RISCV_IMAGE_MAGIC + .balign 4 + + // Good image magic, in little-endian format. + .ascii RISCV_IMAGE_MAGIC2 + + // Reserved field. + .word 0 + +_start_kernel: // Mask all interrupts csrw sie, zero csrw sip, zero -- cgit v1.2.3