clinkerqemuuartriscv

Writing to qemu RISCV UART using c


I made a helloworld program on RISC-V qemu using the UART using https://popovicu.com/posts/bare-metal-programming-risc-v/ Uros Popovicu's guide in RISC-V assembly. I was wondering how I could write to the UART using c and RISC-V qemu. I tried :

#define UART_TX 0x10000000  

void _start() {
    char *str = "Hello, World!\n";
    for (int i = 0; str[i] != '\0'; ++i) {
        *(volatile char*)UART_TX = str[i];
    }

    while (1);
}

I tried compiling the c code :

riscv64-linux-gnu-gcc -S -o hello.s hello.c
riscv64-linux-gnu-as -march=rv64i -mabi=lp64 -o hello.o -c hello.s

to RISC-V and using the same linker file as in popovicu's example :

 riscv64-linux-gnu-ld -T linker.ld --no-dynamic-linker -m elf64lriscv -static -nostdlib -s -o hello hello.o

using the following linker file (with only some slight modifications) :

ENTRY(_start)

MEMORY {
  dram_space (rwx) : ORIGIN = 0x80000000, LENGTH = 1024
}

SECTIONS {
  .text : {
    hello.o(.text.bios)
  } > dram_space
}

And running it using :

qemu-system-riscv64 -machine virt -bios hello

This results in nothing being print. Is this the correct way to access the UART or does my error lie elsewhere? Thanks in advance.


Solution

  • The primary issue you have is that you're not properly setting up an environment that C can use. A _start symbol will generally always be in assembly.

    In particular, using GDB to attach to QEMU, I see that the stack pointer sp will still have a default 0x0 value. The C code relies on the stack being in valid memory, and the system crashes on the second instruction of your program when it tries to write to an area on the stack.

    With the -bios QEMU flag, you're saying that you will do all of the setup work (beyond a few instructions that load the HART ID into a0 then jump to your code.) You need to start in assembly, set up a usable environment (including loading registers with the required values,) then you can jump to you C code.


    As a side note, everything is in the .text.bios section, in the assembly program in the article that you link to. This isn't the case with the C program, where you should use a less minimal linker script. While the correct sections are being linked in, hello.o(.text.bios) isn't doing what you think it is here.