diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 80840a384..a0002aa20 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -319,6 +319,15 @@ jobs: cd resources/testfragments ../../build/c3c compile --reloc=none --target wasm32 -g0 --link-libc=no --no-entry -Os wasm4.c3 + - name: Install QEMU and Risc-V toolchain + run: | + sudo apt-get install opensbi qemu-system-misc u-boot-qemu gcc-riscv64-unknown-elf + + - name: Compile and run Baremetal Risc-V Example + run: | + cd resources/examples/embedded/riscv-qemu + make C3C_PATH=../../../../build/ run + - name: Build testproject direct linker run: | cd resources/testproject diff --git a/resources/examples/embedded/riscv-qemu/Makefile b/resources/examples/embedded/riscv-qemu/Makefile new file mode 100644 index 000000000..842b7e6cf --- /dev/null +++ b/resources/examples/embedded/riscv-qemu/Makefile @@ -0,0 +1,21 @@ +SRCS_C3 := $(wildcard *.c3) + +default: hello.elf + +hello.a: $(SRCS_C3) + $(C3C_PATH)c3c --use-stdlib=no --no-entry --target elf-riscv32 static-lib $(SRCS_C3) + +start.o: start.s + riscv64-unknown-elf-as -g -march=rv32i -mabi=ilp32 -misa-spec=20191213 -o start.o start.s + +hello.elf: hello.a start.o baremetal.ld + riscv64-unknown-elf-ld -T baremetal.ld -m elf32lriscv -o hello.elf hello.a start.o + +run: hello.elf + qemu-system-riscv32 -nographic -serial mon:stdio -machine virt -semihosting -bios hello.elf + +debug: hello.elf + qemu-system-riscv32 -nographic -serial mon:stdio -machine virt -semihosting -s -S -bios hello.elf + +clean: + rm -f *.o *.a hello.elf diff --git a/resources/examples/embedded/riscv-qemu/README.md b/resources/examples/embedded/riscv-qemu/README.md new file mode 100644 index 000000000..23f12db77 --- /dev/null +++ b/resources/examples/embedded/riscv-qemu/README.md @@ -0,0 +1,8 @@ +# Risc-V 32 Embedded Example With QEMU +## Prereqs +- QEMU +- Risc-V toolchain +- C3C +- Make +## Running +`make run` diff --git a/resources/examples/embedded/riscv-qemu/baremetal.ld b/resources/examples/embedded/riscv-qemu/baremetal.ld new file mode 100644 index 000000000..f37432d4a --- /dev/null +++ b/resources/examples/embedded/riscv-qemu/baremetal.ld @@ -0,0 +1,14 @@ +EXTERN(main) + +SECTIONS +{ + . = 0x80000000; /* QEMU default load address to run bios */ + .text : { + KEEP(*(.text._start)); /* Ensure _start is placed first */ + *(.text*); /* Program code here */ + } + . = ALIGN (CONSTANT (COMMONPAGESIZE)); /* Make sure linker does not jam data into text section, making text writable */ + .data : { + *(.data*) /* Stack goes here */ + } +} \ No newline at end of file diff --git a/resources/examples/embedded/riscv-qemu/hello.c3 b/resources/examples/embedded/riscv-qemu/hello.c3 new file mode 100644 index 000000000..2f4295457 --- /dev/null +++ b/resources/examples/embedded/riscv-qemu/hello.c3 @@ -0,0 +1,11 @@ +import uart; +import semihost; + +const UART0_BASE = 0x10000000; + +fn void main() @export("main") { + Uart* uart0 = (Uart*)UART0_BASE; // Create pointer to UART 0 + uart0.fcr = uart::UARTFCR_FFENA; // Enable FIFO + uart0.puts("Hello World!\n"); // Write the string to the UART + semihost::exit(0); // Semihosting call to exit host +} diff --git a/resources/examples/embedded/riscv-qemu/semihost.c3 b/resources/examples/embedded/riscv-qemu/semihost.c3 new file mode 100644 index 000000000..4904bbdd3 --- /dev/null +++ b/resources/examples/embedded/riscv-qemu/semihost.c3 @@ -0,0 +1,20 @@ +module semihost; + +// See: https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#sys-exit-extended-0x20 + +extern fn int sys_semihost(int operation, SemihostParameters* parms); + +struct SemihostParameters { + int field1; + int field2; +} + +const int SYS_EXIT_EXTENDED = 0x20; +const int ADP_STOPPED_APPLICATIONEXIT = 0x20026; + +fn void exit(int status) { + SemihostParameters parms; + parms.field1 = ADP_STOPPED_APPLICATIONEXIT; + parms.field2 = status; + sys_semihost(SYS_EXIT_EXTENDED, &parms); +} diff --git a/resources/examples/embedded/riscv-qemu/start.s b/resources/examples/embedded/riscv-qemu/start.s new file mode 100644 index 000000000..95972bc04 --- /dev/null +++ b/resources/examples/embedded/riscv-qemu/start.s @@ -0,0 +1,29 @@ +# Simple C runtime startup bootstrap +# Two primary functions: +# - Stack allocation and initializing stack pointer +# - Jumping to main + +.section .text._start +.global _start +_start: + la sp, __stack_top # Load the stack pointer + add s0, sp, zero # Set the frame pointer + jal zero, main # Run main entry point - no argc +loop: j loop # Spin forever in case main returns + +# Support semi-hosting calls +.option norvc +.text +.balign 16 +.global sys_semihost +.type sys_semihost @function +sys_semihost: # https://github.com/riscv-non-isa/riscv-semihosting/blob/main/src/binary-interface.adoc + slli zero, zero, 0x1f + ebreak + srai zero, zero, 0x7 + ret + +.section .data +.space 1024*8 # allocate 8K of memory to serve as initial stack +.align 16 # Smallest stack allocation is 16 bytes, so align accordingly +__stack_top: # The stack grows downward according the Risc-V ABI diff --git a/resources/examples/embedded/riscv-qemu/uart.c3 b/resources/examples/embedded/riscv-qemu/uart.c3 new file mode 100644 index 000000000..8f05b218f --- /dev/null +++ b/resources/examples/embedded/riscv-qemu/uart.c3 @@ -0,0 +1,28 @@ +module uart; + +const UARTFCR_FFENA = 0x01; // UART FIFO Control Register enable bit +const UARTLSR_THRE = 0x20; // UART Line Status Register Transmit Hold Register Empty bit + +struct Uart { + char dr; // UART Data Register + char filler1; + char fcr; // FIFO Control Register + char filler2; + char filler3; + char lsr; // Line Status Register +} + +fn bool Uart.uart_ff_thr_empty(Uart* this) { + return (bool)($$volatile_load(&this.lsr) & UARTLSR_THRE); +} + +fn void Uart.putc(Uart* this, char c) { + while (!this.uart_ff_thr_empty()); // Wait until the FIFO holding register is empty + $$volatile_store(&this.dr, c); // Write character to transmit register +} + +fn void Uart.puts(Uart* this, char *str) { + while (*str) { // Loop until value at string pointer is zero + this.putc(*str++); // Write the character and increment pointer + } +} diff --git a/src/compiler/linker.c b/src/compiler/linker.c index 0b772d920..5d5325a4a 100644 --- a/src/compiler/linker.c +++ b/src/compiler/linker.c @@ -247,50 +247,54 @@ static const char *find_freebsd_crt(void) return NULL; } -static const char *find_linux_crt(void) +static const char *find_arch_glob_path(const char *glob_path, int file_len) { - if (active_target.linuxpaths.crt) return active_target.linuxpaths.crt; #if PLATFORM_POSIX glob_t globbuf; - if (!glob("/usr/lib/*/crt1.o", 0, NULL, &globbuf) && globbuf.gl_pathc) + if (!glob(glob_path, 0, NULL, &globbuf)) { - const char *path = globbuf.gl_pathv[0]; - INFO_LOG("Found crt at %s", path); - size_t len = strlen(path); - assert(len > 6); - const char *res = str_copy(path, len - 6); + for (int i = 0; i < globbuf.gl_pathc; i++) + { + const char *path = globbuf.gl_pathv[i]; + // Avoid qemu problems + if (platform_target.arch != ARCH_TYPE_RISCV64 + && platform_target.arch != ARCH_TYPE_RISCV32 + && strstr(path, "riscv")) continue; + size_t len = strlen(path); + assert(len > file_len); + const char *res = str_copy(path, len - file_len); + globfree(&globbuf); + return res; + } globfree(&globbuf); - return res; - } - else - { - INFO_LOG("No crt in /usr/lib/*/"); } #endif return NULL; } +static const char *find_linux_crt(void) +{ + if (active_target.linuxpaths.crt) return active_target.linuxpaths.crt; + const char *path = find_arch_glob_path("/usr/lib/*/crt1.o", 6); + if (!path) + { + INFO_LOG("No crt in /usr/lib/*/"); + return NULL; + } + INFO_LOG("Found crt at %s", path); + return path; +} static const char *find_linux_crt_begin(void) { if (active_target.linuxpaths.crtbegin) return active_target.linuxpaths.crtbegin; -#if PLATFORM_POSIX - glob_t globbuf; - if (!glob("/usr/lib/gcc/*/*/crtbegin.o", 0, NULL, &globbuf) && globbuf.gl_pathc) - { - const char *path = globbuf.gl_pathv[0]; - INFO_LOG("Found crtbegin at %s", path); - size_t len = strlen(path); - assert(len > 10); - const char *res = str_copy(path, len - 10); - globfree(&globbuf); - return res; - } - else + const char *path = find_arch_glob_path("/usr/lib/gcc/*/*/crtbegin.o", 10); + if (!path) { INFO_LOG("No crtbegin in /usr/lib/gcc/*/*/"); + return NULL; } -#endif - return NULL; + INFO_LOG("Found crtbegin at %s", path); + return path; } static void linker_setup_linux(const char ***args_ref, Linker linker_type)