diff --git a/risc_v/ch2/.cargo/config b/risc_v/ch2/.cargo/config new file mode 100644 index 0000000..2d696f3 --- /dev/null +++ b/risc_v/ch2/.cargo/config @@ -0,0 +1,5 @@ +[build] +target = "riscv64gc-unknown-none-elf" + +[target.riscv64gc-unknown-none-elf] +linker = "riscv64-unknown-linux-gnu-gcc" diff --git a/risc_v/ch2/Cargo.toml b/risc_v/ch2/Cargo.toml new file mode 100644 index 0000000..bab1d79 --- /dev/null +++ b/risc_v/ch2/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "sos" +version = "0.1.0" +authors = ["Stephen Marz "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["staticlib"] + +[dependencies] diff --git a/risc_v/ch2/Makefile b/risc_v/ch2/Makefile new file mode 100644 index 0000000..ddbe925 --- /dev/null +++ b/risc_v/ch2/Makefile @@ -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) diff --git a/risc_v/ch2/make_hdd.sh b/risc_v/ch2/make_hdd.sh new file mode 100755 index 0000000..6da6cb5 --- /dev/null +++ b/risc_v/ch2/make_hdd.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +dd if=/dev/zero of=hdd.dsk bs=1M count=32 diff --git a/risc_v/ch2/src/asm/boot.S b/risc_v/ch2/src/asm/boot.S new file mode 100755 index 0000000..f991365 --- /dev/null +++ b/risc_v/ch2/src/asm/boot.S @@ -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 + diff --git a/risc_v/ch2/src/asm/trap.S b/risc_v/ch2/src/asm/trap.S new file mode 100644 index 0000000..6e7a224 --- /dev/null +++ b/risc_v/ch2/src/asm/trap.S @@ -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 + diff --git a/risc_v/ch2/src/lds/virt.lds b/risc_v/ch2/src/lds/virt.lds new file mode 100644 index 0000000..3fdabf5 --- /dev/null +++ b/risc_v/ch2/src/lds/virt.lds @@ -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); +} diff --git a/risc_v/ch2/src/lib.rs b/risc_v/ch2/src/lib.rs new file mode 100755 index 0000000..28ae687 --- /dev/null +++ b/risc_v/ch2/src/lib.rs @@ -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; diff --git a/risc_v/ch2/src/uart.rs b/risc_v/ch2/src/uart.rs new file mode 100755 index 0000000..a8e8d26 --- /dev/null +++ b/risc_v/ch2/src/uart.rs @@ -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 { + 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()) + } + } + } +}