mirror of
https://github.com/sgmarz/osblog.git
synced 2024-11-23 18:06:20 +04:00
Added chapter 3
This commit is contained in:
parent
17d53bac20
commit
77994f2289
5
risc_v/ch3/.cargo/config
Normal file
5
risc_v/ch3/.cargo/config
Normal file
@ -0,0 +1,5 @@
|
||||
[build]
|
||||
target = "riscv64gc-unknown-none-elf"
|
||||
|
||||
[target.riscv64gc-unknown-none-elf]
|
||||
linker = "riscv64-unknown-linux-gnu-gcc"
|
12
risc_v/ch3/Cargo.toml
Normal file
12
risc_v/ch3/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "sos"
|
||||
version = "0.1.0"
|
||||
authors = ["Stephen Marz <stephen.marz@utk.edu>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[dependencies]
|
38
risc_v/ch3/Makefile
Normal file
38
risc_v/ch3/Makefile
Normal file
@ -0,0 +1,38 @@
|
||||
#####
|
||||
## BUILD
|
||||
#####
|
||||
CC=riscv64-unknown-linux-gnu-g++
|
||||
CFLAGS=-Wall -Wextra -pedantic -Wextra -O0 -g -std=c++17
|
||||
CFLAGS+=-static -ffreestanding -nostdlib -fno-rtti -fno-exceptions
|
||||
CFLAGS+=-march=rv64gc -mabi=lp64
|
||||
INCLUDES=
|
||||
LINKER_SCRIPT=-Tsrc/lds/virt.lds
|
||||
TYPE=debug
|
||||
RUST_TARGET=./target/riscv64gc-unknown-none-elf/$(TYPE)
|
||||
LIBS=-L$(RUST_TARGET)
|
||||
SOURCES_ASM=$(wildcard src/asm/*.S)
|
||||
LIB=-lsos -lgcc
|
||||
OUT=os.elf
|
||||
|
||||
#####
|
||||
## QEMU
|
||||
#####
|
||||
QEMU=qemu-system-riscv64
|
||||
MACH=virt
|
||||
CPU=rv64
|
||||
CPUS=4
|
||||
MEM=128M
|
||||
DRIVE=hdd.dsk
|
||||
|
||||
all:
|
||||
cargo build
|
||||
$(CC) $(CFLAGS) $(LINKER_SCRIPT) $(INCLUDES) -o $(OUT) $(SOURCES_ASM) $(LIBS) $(LIB)
|
||||
|
||||
run: all
|
||||
$(QEMU) -machine $(MACH) -cpu $(CPU) -smp $(CPUS) -m $(MEM) -nographic -serial mon:stdio -bios none -kernel $(OUT) -drive if=none,format=raw,file=$(DRIVE),id=foo -device virtio-blk-device,scsi=off,drive=foo
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
cargo clean
|
||||
rm -f $(OUT)
|
3
risc_v/ch3/make_hdd.sh
Executable file
3
risc_v/ch3/make_hdd.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
dd if=/dev/zero of=hdd.dsk bs=1M count=32
|
79
risc_v/ch3/src/asm/boot.S
Normal file
79
risc_v/ch3/src/asm/boot.S
Normal file
@ -0,0 +1,79 @@
|
||||
# boot.S
|
||||
# bootloader for SoS
|
||||
# Stephen Marz
|
||||
# 8 February 2019
|
||||
|
||||
# Disable generation of compressed instructions.
|
||||
.option norvc
|
||||
|
||||
# Define a .data section.
|
||||
.section .data
|
||||
|
||||
# Define a .text.init section.
|
||||
.section .text.init
|
||||
|
||||
# Execution starts here.
|
||||
.global _start
|
||||
_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
|
||||
|
||||
# Disable linker instruction relaxation for the `la` instruction below.
|
||||
# This disallows the assembler from assuming that `gp` is already initialized.
|
||||
# This causes the value stored in `gp` to be calculated from `pc`.
|
||||
.option push
|
||||
.option norelax
|
||||
la gp, _global_pointer
|
||||
.option pop
|
||||
# Set all bytes in the BSS section to zero.
|
||||
la a0, _bss_start
|
||||
la a1, _bss_end
|
||||
bgeu a0, a1, 2f
|
||||
1:
|
||||
sd zero, (a0)
|
||||
addi a0, a0, 8
|
||||
bltu a0, a1, 1b
|
||||
2:
|
||||
# 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
|
||||
# Setting `mstatus` register:
|
||||
# 0b11 << 11: Machine's previous protection mode is 3 (MPP=3).
|
||||
# 1 << 7 : Machine's previous interrupt-enable bit is 1 (MPIE=1).
|
||||
# 1 << 3 : Machine's interrupt-enable bit is 1 (MIE=1).
|
||||
li t0, (0b11 << 11) | (1 << 7) | (1 << 3)
|
||||
csrw mstatus, t0
|
||||
# Machine's exception program counter (MEPC) is set to `kmain`.
|
||||
la t1, kmain
|
||||
csrw mepc, t1
|
||||
# Machine's trap vector base address is set to `asm_trap_vector`.
|
||||
la t2, asm_trap_vector
|
||||
csrw mtvec, t2
|
||||
# Setting Machine's interrupt-enable bits (`mie` register):
|
||||
# 1 << 3 : Machine's M-mode software interrupt-enable bit is 1 (MSIE=1).
|
||||
# 1 << 7 : Machine's timer interrupt-enable bit is 1 (MTIE=1).
|
||||
# 1 << 11: Machine's external interrupt-enable bit is 1 (MEIE=1).
|
||||
li t3, (1 << 3) | (1 << 7) | (1 << 11)
|
||||
csrw mie, t3
|
||||
# Set the return address to infinitely wait for interrupts.
|
||||
la ra, 4f
|
||||
# We use mret here so that the mstatus register is properly updated.
|
||||
mret
|
||||
3:
|
||||
|
||||
# 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.
|
||||
4:
|
||||
wfi
|
||||
j 4b
|
||||
|
9
risc_v/ch3/src/asm/trap.S
Normal file
9
risc_v/ch3/src/asm/trap.S
Normal file
@ -0,0 +1,9 @@
|
||||
# trap.S
|
||||
# In the future our trap vector will go here.
|
||||
|
||||
.global asm_trap_vector
|
||||
# This will be our trap vector when we start
|
||||
# handling interrupts.
|
||||
asm_trap_vector:
|
||||
mret
|
||||
|
245
risc_v/ch3/src/lds/virt.lds
Normal file
245
risc_v/ch3/src/lds/virt.lds
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
virt.lds
|
||||
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
|
||||
executing.
|
||||
|
||||
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.
|
||||
*/
|
||||
MEMORY
|
||||
{
|
||||
ram (wxa!ri) : 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
|
||||
headers.
|
||||
*/
|
||||
PHDRS
|
||||
{
|
||||
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.
|
||||
*/
|
||||
SECTIONS
|
||||
{
|
||||
/*
|
||||
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(_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:
|
||||
.sdata
|
||||
.sdata.[anything]
|
||||
.data
|
||||
.data.[anything]
|
||||
|
||||
...in that order.
|
||||
*/
|
||||
*(.sdata .sdata.*) *(.data .data.*)
|
||||
PROVIDE(_data_end = .);
|
||||
} >ram AT>ram :data
|
||||
|
||||
.bss : {
|
||||
PROVIDE(_bss_start = .);
|
||||
*(.sbss .sbss.*) *(.bss .bss.*)
|
||||
PROVIDE(_bss_end = .);
|
||||
} >ram AT>ram :bss
|
||||
|
||||
/*
|
||||
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 = _bss_end + 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);
|
||||
PROVIDE(_heap_size = _memory_end - _stack);
|
||||
}
|
Loading…
Reference in New Issue
Block a user