Unverified Commit c6fe665d authored by bors[bot]'s avatar bors[bot] Committed by GitHub
Browse files

Try #11:

parents 6b162daf 9dafafe1
Pipeline #469918 failed with stages
in 2 minutes and 22 seconds
......@@ -11,3 +11,7 @@ rustflags = [
rustflags = [
"-C", "link-arg=-Tsrc/arch/aarch64/link.ld"
rustflags = ['-Clink-arg=-Tsrc/arch/riscv64gc/link.ld']
runner = "qemu-system-riscv64 -machine virt -cpu rv64 -smp 4 -m 128M -nographic -serial mon:stdio -bios none -device virtio-rng-device -device virtio-gpu-device -device virtio-net-device -device virtio-tablet-device -device virtio-keyboard-device -kernel "
......@@ -11,8 +11,14 @@ pub use crate::arch::x86_64::*;
#[cfg(target_arch = "aarch64")]
pub use crate::arch::aarch64::*;
#[cfg(target_arch = "riscv64")]
pub use crate::arch::riscv64gc::*;
#[cfg(target_arch = "x86_64")]
pub mod x86_64;
#[cfg(target_arch = "aarch64")]
pub mod aarch64;
#[cfg(target_arch = "riscv64")]
pub mod riscv64gc;
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019
.option norvc
.section .data
.global HEAP_SIZE
HEAP_SIZE: .dword _heap_size
.global HEAP_START
HEAP_START: .dword _heap_start
.section .text.init
.global _start
# Any hardware threads (hart) that are not bootstrapping
# need to wait for an IPI
csrr t0, mhartid
bnez t0, 3f
# SATP should be zero, but let's make sure
csrw satp, zero
.option push
.option norelax
la gp, _global_pointer
.option pop
# The BSS section is expected to be zero
la a0, _bss_start
la a1, _bss_end
bgeu a0, a1, 2f
sd zero, (a0)
addi a0, a0, 8
bltu a0, a1, 1b
# Control registers, set the stack, mstatus, mepc,
# and mtvec to return to the main function.
# li t5, 0xffff;
# csrw medeleg, t5
# csrw mideleg, t5
la sp, _stack_end
# We use mret here so that the mstatus register
# is properly updated.
li t0, (0b11 << 11) | (1 << 7) | (1 << 3)
csrw mstatus, t0
la t1, loader_main
csrw mepc, t1
la t2, asm_trap_vector
csrw mtvec, t2
li t3, (1 << 3) | (1 << 7) | (1 << 11)
csrw mie, t3
la ra, 4f
# Parked harts go here. We need to set these
# to only awaken if it receives a software interrupt,
# which we're going to call the SIPI (Software Intra-Processor Interrupt).
# We only use these to run user-space programs, although this may
# change.
lb t1, 0(t0)
addi t1, t1, 1
sb t1, 0(t0)
li t0, 1 << 3
csrw mstatus, t0
li t1, 1 << 3
csrw mie, t1
la t2, cpu_ipi
csrw mtvec, t2
# Waiting loop. An IPI will cause the other harts to
# wake up and start executing.
j 4b
// TODO determine what goes in this struct
pub struct BootInfo {
pub base: u64,
pub image_size: u64,
pub tls_start: u64,
pub tls_filesz: u64,
pub tls_memsz: u64,
impl BootInfo {
pub const fn new() -> Self {
BootInfo {
base: 0,
image_size: 0,
tls_start: 0,
tls_filesz: 0,
tls_memsz: 0,
Linker script for outputting to RISC-V QEMU "virt" machine.
Stephen Marz
6 October 2019
riscv is the name of the architecture that the linker understands
for any RISC-V target (64-bit or 32-bit).
We will further refine this by using -mabi=lp64 and -march=rv64gc
OUTPUT_ARCH( "riscv" )
We're setting our entry point to a symbol
called _start which is inside of boot.S. This
essentially stores the address of _start as the
"entry point", or where CPU instructions should start
In the rest of this script, we are going to place _start
right at the beginning of 0x8000_0000 because this is where
the virtual machine and many RISC-V boards will start executing.
ENTRY( _start )
The MEMORY section will explain that we have "ram" that contains
a section that is 'w' (writeable), 'x' (executable), and 'a' (allocatable).
We use '!' to invert 'r' (read-only) and 'i' (initialized). We don't want
our memory to be read-only, and we're stating that it is NOT initialized
at the beginning.
The ORIGIN is the memory address 0x8000_0000. If we look at the virt
spec or the specification for the RISC-V HiFive Unleashed, this is the
starting memory address for our code.
Side note: There might be other boot ROMs at different addresses, but
their job is to get to this point.
Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
The linker will double check this to make sure everything can fit.
The HiFive Unleashed has a lot more RAM than this, but for the virtual
machine, I went with 128M since I think that's enough RAM for now.
We can provide other pieces of memory, such as QSPI, or ROM, but we're
telling the linker script here that we have one pool of RAM.
ram (wxa) : ORIGIN = 0x80000000, LENGTH = 128M
PHDRS is short for "program headers", which we specify three here:
text - CPU instructions (executable sections)
data - Global, initialized variables
bss - Global, uninitialized variables (all will be set to 0 by boot.S)
The command PT_LOAD tells the linker that these sections will be loaded
from the file into memory.
We can actually stuff all of these into a single program header, but by
splitting it up into three, we can actually use the other PT_* commands
such as PT_DYNAMIC, PT_INTERP, PT_NULL to tell the linker where to find
additional information.
However, for our purposes, every section will be loaded from the program
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
We are now going to organize the memory based on which
section it is in. In assembly, we can change the section
with the ".section" directive. However, in C++ and Rust,
CPU instructions go into text, global constants go into
rodata, global initialized variables go into data, and
global uninitialized variables go into bss.
The first part of our RAM layout will be the text section.
Since our CPU instructions are here, and our memory starts at
0x8000_0000, we need our entry point to line up here.
.text : {
PROVIDE allows me to access a symbol called _text_start so
I know where the text section starts in the operating system.
This should not move, but it is here for convenience.
The period '.' tells the linker to set _text_start to the
CURRENT location ('.' = current memory location). This current
memory location moves as we add things.
PROVIDE(kernel_start = .);
PROVIDE(_text_start = .);
We are going to layout all text sections here, starting with
.text.init. The asterisk in front of the parentheses means to match
the .text.init section of ANY object file. Otherwise, we can specify
which object file should contain the .text.init section, for example,
boot.o(.text.init) would specifically put the .text.init section of
our bootloader here.
Because we might want to change the name of our files, we'll leave it
with a *.
Inside the parentheses is the name of the section. I created my own
called .text.init to make 100% sure that the _start is put right at the
beginning. The linker will lay this out in the order it receives it:
.text.init first
all .text sections next
any .text.* sections last
.text.* means to match anything after .text. If we didn't already specify
.text.init, this would've matched here. The assembler and linker can place
things in "special" text sections, so we match any we might come across here.
*(.text.init) *(.text .text.*)
Again, with PROVIDE, we're providing a readable symbol called _text_end, which is
set to the memory address AFTER .text.init, .text, and .text.*'s have been added.
PROVIDE(_text_end = .);
The portion after the right brace is in an odd format. However, this is telling the
linker what memory portion to put it in. We labeled our RAM, ram, with the constraints
that it is writeable, allocatable, and executable. The linker will make sure with this
that we can do all of those things.
>ram - This just tells the linker script to put this entire section (.text) into the
ram region of memory. To my knowledge, the '>' does not mean "greater than". Instead,
it is a symbol to let the linker know we want to put this in ram.
AT>ram - This sets the LMA (load memory address) region to the same thing. LMA is the final
translation of a VMA (virtual memory address). With this linker script, we're loading
everything into its physical location. We'll let the kernel copy and sort out the
virtual memory. That's why >ram and AT>ram are continually the same thing.
:text - This tells the linker script to put this into the :text program header. We've only
defined three: text, data, and bss. In this case, we're telling the linker script
to go into the text section.
} >ram AT>ram :text
The global pointer allows the linker to position global variables and constants into
independent positions relative to the gp (global pointer) register. The globals start
after the text sections and are only relevant to the rodata, data, and bss sections.
PROVIDE(_global_pointer = .);
Most compilers create a rodata (read only data) section for global constants. However,
we're going to place ours in the text section. We can actually put this in :data, but
since the .text section is read-only, we can place it there.
NOTE: This doesn't actually do anything, yet. The actual "protection" cannot be done
at link time. Instead, when we program the memory management unit (MMU), we will be
able to choose which bits (R=read, W=write, X=execute) we want each memory segment
to be able to do.
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
Again, we're placing the rodata section in the memory segment "ram" and we're putting
it in the :text program header. We don't have one for rodata anyway.
} >ram AT>ram :text
.data : {
. = ALIGN(4096) tells the linker to align the current memory location (which is
0x8000_0000 + text section + rodata section) to 4096 bytes. This is because our paging
system's resolution is 4,096 bytes or 4 KiB.
. = ALIGN(4096);
PROVIDE(_data_start = .);
sdata and data are essentially the same thing. However, compilers usually use the
sdata sections for shorter, quicker loading sections. So, usually critical data
is loaded there. However, we're loading all of this in one fell swoop.
So, we're looking to put all of the following sections under the umbrella .data:
...in that order.
*(.sdata .sdata.*) *(.data .data.*)
PROVIDE(_data_end = .);
} >ram AT>ram :data
.bss : {
PROVIDE(_bss_start = .);
PROVIDE(bss_start = .);
*(.sbss .sbss.*) *(.bss .bss.*)
PROVIDE(_bss_end = .);
PROVIDE(bss_end = .);
} >ram AT>ram :bss
PROVIDE(kernel_end = .);
The following will be helpful when we allocate the kernel stack (_stack) and
determine where the heap begnis and ends (_heap_start and _heap_start + _heap_size)/
When we do memory allocation, we can use these symbols.
We use the symbols instead of hard-coding an address because this is a floating target.
As we add code, the heap moves farther down the memory and gets shorter.
_memory_start will be set to 0x8000_0000 here. We use ORIGIN(ram) so that it will take
whatever we set the origin of ram to. Otherwise, we'd have to change it more than once
if we ever stray away from 0x8000_0000 as our entry point.
PROVIDE(_memory_start = ORIGIN(ram));
Our kernel stack starts at the end of the bss segment (_bss_end). However, we're allocating
0x80000 bytes (524 KiB) to our kernel stack. This should be PLENTY of space. The reason
we add the memory is because the stack grows from higher memory to lower memory (bottom to top).
Therefore we set the stack at the very bottom of its allocated slot.
When we go to allocate from the stack, we'll subtract the number of bytes we need.
PROVIDE(_stack_start = _bss_end);
PROVIDE(_stack_end = _stack_start + 0x80000);
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
Finally, our heap starts right after the kernel stack. This heap will be used mainly
to dole out memory for user-space applications. However, in some circumstances, it will
be used for kernel memory as well.
We don't align here because we let the kernel determine how it wants to do this.
PROVIDE(_heap_start = _stack_end);
PROVIDE(_heap_size = _memory_end - _heap_start);
use goblin::elf;
pub mod bootinfo;
pub mod paging;
pub mod physicalmem;
pub mod uart;
use crate::arch::bootinfo::*;
use crate::arch::uart::*;
use crate::arch::paging::GigaPageSize;
use crate::arch::riscv64gc::paging::PageSize;
pub static mut BOOT_INFO: BootInfo = BootInfo::new();
pub const ELF_ARCH: u16 = elf::header::EM_RISCV;
const UART_ADDRESS: *mut u8 = 0x1000_0000 as *mut u8;
const UART: Uart = Uart::new(UART_ADDRESS);
extern "C" {
static kernel_end: u8;
pub fn message_output_init() {
println!("in riscv");
pub unsafe fn get_memory(_memory_size: u64) -> u64 {
align_up!(&kernel_end as *const u8 as u64, GigaPageSize::SIZE as u64)
pub fn output_message_byte(byte: u8) {
pub fn find_kernel() -> &'static [u8] {
// HERMIT_APP is the absolute path of the RustyHermit kernel
pub unsafe fn boot_kernel(virtual_address: u64, mem_size: u64, entry_point: u64) -> ! {
"Jumping to HermitCore Application Entry Point at 0x{:x}",
BOOT_INFO.base = virtual_address;
BOOT_INFO.image_size = mem_size;
let kernel_entry: extern "C" fn(boot_info: &'static mut BootInfo) -> ! =
kernel_entry(&mut BOOT_INFO);
pub fn cpu_ipi() -> ! {
use core::marker::PhantomData;
use crate::arch::physicalmem;
/// Number of Offset bits of a virtual address for a 4 KiB page, which are shifted away to get its Page Frame Number (PFN).
const PAGE_BITS: usize = 12;
/// Number of bits of the index in each table (PML4, PDPT, PDT, PGT).
const PAGE_MAP_BITS: usize = 9;
/// A mask where PAGE_MAP_BITS are set to calculate a table index.
const PAGE_MAP_MASK: usize = 0x1FF;
bitflags! {
/// Possible flags for an entry in a RISC-V paging structure
/// See RISC-V Privileged Spec, Section 4.3
pub struct PageTableEntryFlags: usize {
/// Set if the page table is valid and points to a page or table
const VALID = 1 << 0;
/// If READ | WRITE | EXECUTE = 0, this PTE points to another table
/// Otherwise, the PTE is a leaf.
/// Set if the current PTE is a leaf and the frame is readable
const READ = 1 << 1;
/// Set if the current PTE is a leaf and the frame is writeable
const WRITE = 1 << 2;
/// Set if the current PTE is a leaf and the frame is executable
const EXECUTE = 1 << 3;
/// Set if this is a U-mode mapping
const USER = 1 << 4;
/// Set if this mapping is present in all address spaces
const GLOBAL = 1 << 5;
/// Set if this page has been accessed since the last time the A bit was cleared
const ACCESSED = 1 << 6;
/// Set if this page has been written since the last time the D bit was cleared
const DIRTY = 1 << 7;
impl PageTableEntryFlags {
/// An empty set of flags for unused/zeroed table entries.
/// Needed as long as empty() is no const function.
const BLANK: PageTableEntryFlags = PageTableEntryFlags { bits: 0 };
// An entry in a page table (any level)
#[derive(Debug, Clone, Copy)]
pub struct PageTableEntry {
/// Physical memory address this entry refers, combined with flags from PageTableEntryFlags.
physical_address_and_flags: usize,
impl PageTableEntry {
/// Returns whether this entry is valid (present).
fn is_present(&self) -> bool {
(self.physical_address_and_flags & PageTableEntryFlags::VALID.bits()) != 0
/// Mark this as a valid (present) entry and set address translation and flags.
/// # Arguments
/// * `physical_address` - The physical memory address this entry shall translate to
/// * `flags` - Flags from PageTableEntryFlags (note that the VALID flag is set automatically)
fn set(&mut self, physical_address: usize, flags: PageTableEntryFlags) {
self.physical_address_and_flags =
physical_address | (PageTableEntryFlags::VALID | flags).bits();
/// A generic interface to support all possible page sizes.
/// This is defined as a subtrait of Copy to enable #[derive(Clone, Copy)] for Page.
/// Currently, deriving implementations for these traits only works if all dependent types implement it as well.
pub trait PageSize: Copy {
/// The page size in bytes.
const SIZE: usize;
/// The page table level at which a page of this size is mapped (from 0 for PGT through 3 for PML4).
/// Implemented as a numeric value to enable numeric comparisons.
const MAP_LEVEL: usize;
/// Any extra flag that needs to be set to map a page of this size.
/// For example: PageTableEntryFlags::HUGE_PAGE
const MAP_EXTRA_FLAG: PageTableEntryFlags;
/// A 4 KiB page mapped in the level 0 PT.
#[derive(Clone, Copy)]
pub enum BasePageSize {}
impl PageSize for BasePageSize {
const SIZE: usize = 4096;
const MAP_LEVEL: usize = 0;
const MAP_EXTRA_FLAG: PageTableEntryFlags = PageTableEntryFlags::BLANK;
/// A 2 MiB page mapped in the level 1 PT
#[derive(Clone, Copy)]
pub enum MegaPageSize {}
impl PageSize for MegaPageSize {
const SIZE: usize = 2 * 1024 * 1024;
const MAP_LEVEL: usize = 1;
const MAP_EXTRA_FLAG: PageTableEntryFlags = PageTableEntryFlags::BLANK;
/// A 1 GiB page mapped in the level 2 PT
#[derive(Clone, Copy)]
pub enum GigaPageSize {}
impl PageSize for GigaPageSize {
const SIZE: usize = 1024 * 1024 * 1024;
const MAP_LEVEL: usize = 2;
const MAP_EXTRA_FLAG: PageTableEntryFlags = PageTableEntryFlags::BLANK;
/// A 512 GiB page mapped in the level 3 PT
#[derive(Clone, Copy)]
pub enum TeraPageSize {}
impl PageSize for TeraPageSize {
const SIZE: usize = 512 * 1024 * 1024 * 1024;
const MAP_LEVEL: usize = 3;
const MAP_EXTRA_FLAG: PageTableEntryFlags = PageTableEntryFlags::BLANK;
/// A memory page of the size given by S.
#[derive(Clone, Copy)]
struct Page<S: PageSize> {
/// Virtual memory address of this page.
/// This is rounded to a page size boundary on creation.
virtual_address: usize,
/// Required by Rust to support the S parameter.
size: PhantomData<S>,
impl<S: PageSize> Page<S> {
/// Flushes this page from the TLB of this CPU.
fn flush_from_tlb(&self) {
/// Returns whether the given virtual address is a valid one in the RISC-V Sv48 memory model.
/// RISC-V Sv48 supports 48-bit for virtual memory addresses.
/// This is enforced by requiring bits 63 through 48 to replicate bit 47 (cf. RISC-V Privileged Spec Section 4.5).
/// As a consequence, the address space is divided into the two valid regions 0x8000_0000_0000
/// and 0xFFFF_8000_0000_0000.
/// Although we could make this check depend on the actual linear address width from the CPU,
/// any extension above 48-bit would require a new page table level, which we don't implement.
fn is_valid_address(virtual_address: usize) -> bool {
virtual_address < 0x8000_0000_0000 || virtual_address >= 0xFFFF_8000_0000_0000
/// Returns a Page including the given virtual address.
/// That means, the address is rounded down to a page size boundary.
fn including_address(virtual_address: usize) -> Self {
Self {
virtual_address: align_down!(virtual_address, S::SIZE),
size: PhantomData,
/// Returns a PageIter to iterate from the given first Page to the given last Page (inclusive).
fn range(first: Self, last: Self) -> PageIter<S> {
assert!(first.virtual_address <= last.virtual_address);
PageIter {
current: first,
last: last,
/// Returns the index of this page in the table given by L.
fn table_index<L: PageTableLevel>(&self) -> usize {
assert!(L::LEVEL >= S::MAP_LEVEL);
self.virtual_address >> PAGE_BITS >> L::LEVEL * PAGE_MAP_BITS & PAGE_MAP_MASK