This is a companion to the Booting the kernel in RISC-V32.

The C code

It seems in RISC-V32, one may freely address any absolute address within the 4G address space with the

lui
addi

pair. In RISC-V64, one has to specify “medany”, “medlow”, or “large” for the code model.

By default it is “medlow”, loading an absolute address with the

lui
addi

pair. The highest address that can be conjured is 0x80000000 (2G). So the immediate value for lui is maximum 0x80000. By the linker definition file however, the first 20-bit of __stack_top is 0x80220. The linker will fail therefore.

When compiling, specify -mcmodel=medany. An address relative to the program counter pc will be made with

auipc
addi

pair. The first 20-bit of __stack_top relative to the program counter is just 0x00020 in this case.

clang -std=c11 --target=riscv64-unknown-elf -c -ffreestanding -nostdlib -mcmodel=medany -o kernel.o kernel.c
ld.lld -Tkernel.ld -o kernel.elf kernel.o

A simplest kernel in assembly

Let’s write a kernel2.s that only consists of a boot() function, which only sets the stack pointer sp to __stack_top. Remove the .data, .rodata, .bss sections from the linker definition file.

.global boot

.extern __stack_top

.section .text.boot
boot:
    la sp, __stack_top

Note the code model parameter -mcmodel is no longer needed, as the la pseudoinstruction would translate to the auipc, addi pair.

clang --target=riscv64-unknown-elf -c -nostdlib -o kernel2.o kernel2.s
ld.lld -Tkernel2.ld -o kernel2.elf kernel2.o

References

Operating System in 1,000 lines: Booting the kernel, https://operating-system-in-1000-lines.vercel.app/en/04-boot

The RISC-V Code Models (2026 Edition), https://www.sifive.com/blog/all-aboard-part-4-risc-v-code-models

RISC-V calling convention, https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc