@@ -11,3 +11,4 @@ panic = "abort" | |||
panic = "abort" | |||
[dependencies] | |||
@@ -1,21 +0,0 @@ | |||
// AArch32 mode | |||
.section ".text.boot" | |||
.globl _start | |||
.org 0x8000 | |||
_start: | |||
mov sp, #0x8000 | |||
ldr r4, =__bss_start | |||
ldr r9, =__bss_end | |||
mov r5, #0 | |||
mov r6, #0 | |||
mov r7, #0 | |||
mov r8, #0 | |||
b 2f | |||
1: stmia r4!, {r4-r8} | |||
2: cmp r4, r9 | |||
blo 1b | |||
ldr r4, =kernel_main | |||
blx r3 | |||
halt: wfe | |||
b halt | |||
@@ -1,7 +1,14 @@ | |||
# build the OS | |||
build-os: | |||
arm-none-eabi-gcc -mcpu=cortex-a7 -fpic -ffreestanding -c asm/boot-2.s -o build/boot.o | |||
cargo xbuild --release --target armv7r-none-eabihf | |||
arm-none-eabi-gcc -T linker.ld -o build/grove.img -ffreestanding -O2 -nostdlib build/boot.o target/target/release/libgrove.rlib | |||
rm build/* | |||
arm-none-eabi-gcc -mcpu=cortex-a7 -fpic -ffreestanding -c asm/boot.s -o build/boot.o | |||
cargo xbuild --target armv7a-none-eabi | |||
arm-none-eabi-gcc -T linker.ld -o build/grove.img -ffreestanding -O2 -nostdlib build/boot.o target/armv7a-none-eabi/debug/libgrove.rlib | |||
run-os: build-os | |||
qemu-system-arm -M raspi2 -kernel build/grove.img -serial stdio | |||
# build the OS and run it on QEMU | |||
run: build-os | |||
qemu-system-arm -M raspi2 -kernel build/grove.img -serial stdio | |||
# build just the rust binary | |||
build: | |||
cargo xbuild --target armv7a-none-eabi |
@@ -1,51 +1,59 @@ | |||
#![no_std] | |||
#![feature(asm)] | |||
use core::panic::PanicInfo; | |||
use core::ptr::{read_volatile, write_volatile}; | |||
const UART_DR: u32 = 0x3f20_1000; | |||
const UART_FR: u32 = 0x3f20_1018; | |||
fn mmio_write(reg: u32, val: u32) { | |||
unsafe { write_volatile(reg as *mut u32, val) } | |||
} | |||
mod mem; | |||
fn mmio_read(reg: u32) -> u32 { | |||
unsafe { read_volatile(reg as *const u32) } | |||
} | |||
use core::panic::PanicInfo; | |||
fn transmit_fifo_full() -> bool { | |||
mmio_read(UART_FR) & (1 << 5) > 0 | |||
/// represents the board type | |||
#[derive(PartialEq, Eq)] | |||
pub enum BoardType { | |||
PiZeroOne, | |||
PiTwo, | |||
PiThree, | |||
PiFour, | |||
Unknown, | |||
} | |||
fn receive_fifo_empty() -> bool { | |||
mmio_read(UART_FR) & (1 << 4) > 0 | |||
} | |||
impl BoardType { | |||
/// detect the type of Raspberry Pi board | |||
pub fn detect() -> Self { | |||
let mut reg_data: u32; | |||
fn writec(c: u8) { | |||
while transmit_fifo_full() {} | |||
mmio_write(UART_DR, c as u32); | |||
} | |||
unsafe { | |||
asm!("mrc p15, 0, {}, c0, c0, 0", out(reg) reg_data); | |||
} | |||
fn getc() -> u8 { | |||
while receive_fifo_empty() {} | |||
mmio_read(UART_DR) as u8 | |||
} | |||
match reg_data >> 4 & 0xFFF { | |||
0xB76 => BoardType::PiZeroOne, | |||
0xC07 => BoardType::PiTwo, | |||
0xD03 => BoardType::PiThree, | |||
0xD08 => BoardType::PiFour, | |||
_ => BoardType::Unknown, | |||
} | |||
} | |||
fn write(msg: &str) { | |||
for c in msg.bytes() { | |||
writec(c); | |||
/// return the MMIO base address | |||
pub fn mmio_base_addr(self) -> *const u32 { | |||
match self { | |||
BoardType::PiZeroOne | BoardType::Unknown => 0x2000_0000 as *const u32, | |||
BoardType::PiTwo | BoardType::PiThree => 0x3F00_0000 as *const u32, | |||
BoardType::PiFour => 0xFE00_0000 as *const u32, | |||
} | |||
} | |||
} | |||
#[no_mangle] | |||
pub extern "C" fn kernel_main() { | |||
write("hello rust raspberry pi kernel"); | |||
loop { | |||
writec(getc()) | |||
} | |||
let board_type = BoardType::detect(); | |||
let mem = mem::MemoryMappedIo::init(board_type); | |||
mem.write_str("hello grove kernel <3"); | |||
loop {} | |||
} | |||
/// these functions are here to make the compiler/linker happy :) | |||
#[no_mangle] | |||
pub extern "C" fn __aeabi_unwind_cpp_pr0() {} | |||
@@ -0,0 +1,80 @@ | |||
pub struct MemoryMappedIo { | |||
base_addr: *const u32, | |||
} | |||
impl MemoryMappedIo { | |||
pub fn init(board_type: crate::BoardType) -> Self { | |||
let mem = MemoryMappedIo { base_addr: board_type.mmio_base_addr() }; | |||
mem.uart_init(); | |||
mem | |||
} | |||
fn uart_init(&self) {} | |||
// receive fifo empty | |||
fn recv_fifo_empty(&self) -> bool { | |||
self.read_word(PeripheralAddress::UartFr) & (1 << 5) > 0 | |||
} | |||
// transmit fifo full | |||
fn tran_fifo_full(&self) -> bool { | |||
self.read_word(PeripheralAddress::UartFr) & (1 << 4) > 0 | |||
} | |||
pub fn write_str(&self, s: &str) { | |||
s.bytes().for_each(|byte| self.write_byte(byte)) | |||
} | |||
pub fn write_byte(&self, byte: u8) { | |||
while self.tran_fifo_full() {} | |||
self.write_word(PeripheralAddress::UartDr, byte as u32); | |||
} | |||
pub fn get_byte(&self) -> u8 { | |||
while self.recv_fifo_empty() {} | |||
self.read_word(PeripheralAddress::UartDr) as u8 | |||
} | |||
// write a word to the memory mapped IO | |||
pub fn write_word(&self, peripheral_addr: PeripheralAddress, data: u32) { | |||
unsafe { | |||
let addr = self.base_addr.offset(peripheral_addr as isize) as *mut u32; | |||
addr.write_volatile(data); | |||
} | |||
} | |||
// read a word from memory mapped IO | |||
pub fn read_word(&self, peripheral_addr: PeripheralAddress) -> u32 { | |||
unsafe { | |||
let addr = self.base_addr.offset(peripheral_addr as isize) as *mut u32; | |||
addr.read_volatile() | |||
} | |||
} | |||
} | |||
/// represents the offset from the base address | |||
#[allow(dead_code)] | |||
#[repr(isize)] | |||
pub enum PeripheralAddress { | |||
Gpiobase = 0x0020_0000, | |||
Gppud = 0x0020_0094, | |||
Gppudclk0 = 0x0020_0098, | |||
UartDr = 0x0020_1000, | |||
UartRsrecr = 0x0020_1004, | |||
UartFr = 0x0020_1018, | |||
UartIlpr = 0x0020_1020, | |||
UartIbrd = 0x0020_1024, | |||
UartFbrd = 0x0020_1028, | |||
UartLcrh = 0x0020_102C, | |||
UartCr = 0x0020_1030, | |||
UartIfls = 0x0020_1034, | |||
UartImsc = 0x0020_1038, | |||
Uartris = 0x0020_103C, | |||
UartMis = 0x0020_1040, | |||
UartIcr = 0x0020_1044, | |||
UartDmacr = 0x0020_1048, | |||
UartItcr = 0x0020_1080, | |||
UartItip = 0x0020_1084, | |||
UartItop = 0x0020_1088, | |||
UartTdr = 0x0020_108C, | |||
} |