mirror of
https://github.com/sgmarz/osblog.git
synced 2024-11-24 02:16:19 +04:00
Added chapter 2
This commit is contained in:
parent
1402b8c8bd
commit
10c493738e
5
risc_v/ch2/.cargo/config
Normal file
5
risc_v/ch2/.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/ch2/Cargo.toml
Normal file
12
risc_v/ch2/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/ch2/Makefile
Normal file
38
risc_v/ch2/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/ch2/make_hdd.sh
Executable file
3
risc_v/ch2/make_hdd.sh
Executable 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
57
risc_v/ch2/src/asm/boot.S
Executable 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
|
||||
|
9
risc_v/ch2/src/asm/trap.S
Normal file
9
risc_v/ch2/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
|
||||
|
49
risc_v/ch2/src/lds/virt.lds
Normal file
49
risc_v/ch2/src/lds/virt.lds
Normal 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
92
risc_v/ch2/src/lib.rs
Executable 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
117
risc_v/ch2/src/uart.rs
Executable 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user