1
0
mirror of https://github.com/sgmarz/osblog.git synced 2024-11-23 18:06:20 +04:00

Added chapter 2

This commit is contained in:
Stephen Marz 2019-10-02 08:41:58 -04:00
parent 1402b8c8bd
commit 10c493738e
9 changed files with 382 additions and 0 deletions

5
risc_v/ch2/.cargo/config Normal file
View 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/ch2/Cargo.toml Normal file
View 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/ch2/Makefile Normal file
View 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/ch2/make_hdd.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
dd if=/dev/zero of=hdd.dsk bs=1M count=32

57
risc_v/ch2/src/asm/boot.S Executable file
View File

@ -0,0 +1,57 @@
# boot.S
# bootloader for SoS
# Stephen Marz
# 8 February 2019
.option norvc
.section .data
.section .text.init
.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
.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
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
# 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, kmain
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
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

View 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

View File

@ -0,0 +1,49 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS
{
.text : {
PROVIDE(_text_start = .);
*(.text.init) *(.text .text.*)
PROVIDE(_text_end = .);
} >ram AT>ram :text
PROVIDE(_global_pointer = .);
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram AT>ram :text
.data : {
. = ALIGN(4096);
PROVIDE(_data_start = .);
*(.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
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_stack = _bss_end + 0x80000);
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
PROVIDE(_heap_start = _stack);
PROVIDE(_heap_size = _memory_end - _stack);
}

92
risc_v/ch2/src/lib.rs Executable file
View File

@ -0,0 +1,92 @@
// Steve Operating System
// Stephen Marz
// 21 Sep 2019
#![no_std]
#![feature(panic_info_message,asm)]
// ///////////////////////////////////
// / RUST MACROS
// ///////////////////////////////////
#[macro_export]
macro_rules! print
{
($($args:tt)+) => ({
use core::fmt::Write;
let _ = write!(crate::uart::Uart::new(0x1000_0000), $($args)+);
});
}
#[macro_export]
macro_rules! println
{
() => ({
print!("\r\n")
});
($fmt:expr) => ({
print!(concat!($fmt, "\r\n"))
});
($fmt:expr, $($args:tt)+) => ({
print!(concat!($fmt, "\r\n"), $($args)+)
});
}
// ///////////////////////////////////
// / LANGUAGE STRUCTURES / FUNCTIONS
// ///////////////////////////////////
#[no_mangle]
extern "C" fn eh_personality() {}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
print!("Aborting: ");
if let Some(p) = info.location() {
println!(
"line {}, file {}: {}",
p.line(),
p.file(),
info.message().unwrap()
);
}
else {
println!("no information available.");
}
abort();
}
#[no_mangle]
extern "C"
fn abort() -> ! {
loop {
unsafe {
asm!("wfi"::::"volatile");
}
}
}
// ///////////////////////////////////
// / CONSTANTS
// ///////////////////////////////////
// ///////////////////////////////////
// / ENTRY POINT
// ///////////////////////////////////
#[no_mangle]
extern "C"
fn kmain() {
// Main should initialize all sub-systems and get
// ready to start scheduling. The last thing this
// should do is start the timer.
// Let's try using our newly minted UART by initializing it first.
// The UART is sitting at MMIO address 0x1000_0000, so for testing
// now, lets connect to it and see if we can initialize it and write
// to it.
uart::Uart::new(0x1000_0000).init();
// Now test println! macro!
println!("This is my operating system!");
}
// ///////////////////////////////////
// / RUST MODULES
// ///////////////////////////////////
pub mod uart;

117
risc_v/ch2/src/uart.rs Executable file
View File

@ -0,0 +1,117 @@
// uart.rs
// UART routines and driver
use core::convert::TryInto;
use core::fmt::Write;
use core::fmt::Error;
pub struct Uart {
base_address: usize,
}
impl Write for Uart {
fn write_str(&mut self, out: &str) -> Result<(), Error> {
for c in out.as_bytes() {
self.put(*c);
}
Ok(())
}
}
impl Uart {
pub fn new(base_address: usize) -> Self {
Uart {
base_address
}
}
pub fn init(&mut self) {
let ptr = self.base_address as *mut u8;
unsafe {
// First, set the word length and stop bits, which
// are bits 0, 1, and 2 of the line control register (LCR)
// which is at base_address + 3
// We can easily write the value 7 here or 0b111, but I'm
// extending it so that it is clear we're setting two individual
// fields
// Word len Stop bits
// ~~~~~~~~ ~~~~~~~~~
ptr.add(3).write_volatile((0b11 << 0) | (1 << 2));
// Now, enable the FIFO, which is bit index 0 of the FIFO
// control register (FCR at offset 2).
// Again, we can just write 1 here, but when we use left shift,
// it's easier to see that we're trying to write bit index #0.
ptr.add(2).write_volatile(1 << 0);
// Enable receiver buffer interrupts, which is at bit index
// 0 of the interrupt enable register (IER at offset 1).
ptr.add(1).write_volatile(1 << 0);
// If we cared about the divisor, the code below would set the divisor
// from a global clock rate of 22.729 MHz (22,729,000 cycles per second)
// to a signaling rate of 2400 (BAUD). We usually have much faster signalling
// rates nowadays, but this demonstrates what the divisor actually does.
// The formula given in the NS16500A specification for calculating the divisor
// is:
// divisor = ceil( (clock_hz) / (baud_sps x 16) )
// So, we substitute our values and get:
// divisor = ceil( 22_729_000 / (2400 x 16) )
// divisor = ceil( 22_729_000 / 38_400 )
// divisor = ceil( 591.901 ) = 592
// The divisor register is two bytes (16 bits), so we need to split the value
// 592 into two bytes. Typically, we would calculate this based on measuring
// the clock rate, but again, for our purposes [qemu], this doesn't really do
// anything.
// let divisor: u16 = 592;
// let divisor_least: u8 = (divisor & 0xff).try_into().unwrap();
// let divisor_most: u8 = (divisor >> 8).try_into().unwrap();
// Notice that the divisor register DLL (divisor latch least) and DLM (divisor latch most)
// have the same base address as the receiver/transmitter and the interrupt enable register.
// To change what the base address points to, we open the "divisor latch" by writing 1 into
// the Divisor Latch Access Bit (DLAB), which is bit index 7 of the Line Control Register (LCR)
// which is at base_address + 3.
// ptr.add(3).write_volatile(1 << 7);
// Now, base addresses 0 and 1 point to DLL and DLM, respectively.
// Put the lower 8 bits of the divisor into DLL
// ptr.add(0).write_volatile(divisor_least);
// ptr.add(1).write_volatile(divisor_most);
// Now that we've written the divisor, we never have to touch this again. In hardware, this
// will divide the global clock (22.729 MHz) into one suitable for 2,400 signals per second.
// So, to once again get access to the RBR/THR/IER registers, we need to close the DLAB bit
// by clearing it to 0.
// let lcr = ptr.add(3).read_volatile();
// ptr.add(3).write_volatile(lcr & !(1 << 7));
}
}
pub fn put(&mut self, c: u8) {
let ptr = self.base_address as *mut u8;
unsafe {
while ptr.add(5).read_volatile() & (1 << 6) == 0 {
// Wait for the transmitter to drain.
}
// If we get here, the transmitter is empty, so transmit
// our stuff!
ptr.add(0).write_volatile(c);
}
}
pub fn get(&mut self) -> Option<u8> {
let ptr = self.base_address as *mut u8;
unsafe {
if ptr.add(5).read_volatile() & 1 == 0 {
// The DR bit is 0, meaning no data
None
}
else {
// The DR bit is 1, meaning data!
Some(ptr.add(0).read_volatile())
}
}
}
}