Compare commits

...

2 Commits

Author SHA1 Message Date
8c193f5c15 migrate read and write volatile to a function 2025-06-08 20:10:55 +02:00
5f85a40f13 Basic documentation 2025-06-08 15:49:58 +02:00
7 changed files with 150 additions and 136 deletions

View File

@@ -3,3 +3,13 @@
NovaOS is a expository project where I build a kernel from scratch for a Raspberry PI 3 B+. NovaOS is a expository project where I build a kernel from scratch for a Raspberry PI 3 B+.
[Technical write-up](https://blog.leafnova.net/projects/pi3_kernel/) [Technical write-up](https://blog.leafnova.net/projects/pi3_kernel/)
## Features
- Delay and sleep ✓
- UART ✓
- GPIOs ✓
- GPIO Interrupts ✓
- Frame Buffer
- MMU
- Basic Terminal over UART

View File

@@ -1,10 +1,9 @@
use core::{ use core::{
arch::asm, arch::asm,
ptr::{read_volatile, write_volatile},
sync::atomic::{compiler_fence, Ordering}, sync::atomic::{compiler_fence, Ordering},
}; };
use crate::peripherals::uart::print; use crate::{mmio_read, mmio_write, peripherals::uart::print};
const INTERRUPT_BASE: u32 = 0x3F00_B000; const INTERRUPT_BASE: u32 = 0x3F00_B000;
const IRQ_PENDING_BASE: u32 = INTERRUPT_BASE + 0x204; const IRQ_PENDING_BASE: u32 = INTERRUPT_BASE + 0x204;
@@ -57,7 +56,7 @@ pub fn read_gpio_event_detect_status(id: u32) -> bool {
let register = GPEDS_BASE + (id / 32) * 4; let register = GPEDS_BASE + (id / 32) * 4;
let register_offset = id % 32; let register_offset = id % 32;
let val = unsafe { read_volatile(register as *const u32) >> register_offset }; let val = mmio_read(register) >> register_offset;
(val & 0b1) != 0 (val & 0b1) != 0
} }
@@ -66,7 +65,7 @@ pub fn reset_gpio_event_detect_status(id: u32) {
let register = GPEDS_BASE + (id / 32) * 4; let register = GPEDS_BASE + (id / 32) * 4;
let register_offset = id % 32; let register_offset = id % 32;
unsafe { write_volatile(register as *mut u32, 0b1 << register_offset) } mmio_write(register, 0b1 << register_offset);
compiler_fence(Ordering::SeqCst); compiler_fence(Ordering::SeqCst);
} }
@@ -75,12 +74,10 @@ pub fn enable_irq_source(state: IRQState) {
let nr = state as u32; let nr = state as u32;
let register = ENABLE_IRQ_BASE + 4 * (nr / 32); let register = ENABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32; let register_offset = nr % 32;
unsafe { let current = mmio_read(register);
let current = read_volatile(register as *const u32); let mask = 0b1 << register_offset;
let mask = 0b1 << register_offset; let new_val = current | mask;
let new_val = current | mask; mmio_write(register, new_val);
write_volatile(register as *mut u32, new_val);
}
} }
/// Disable IRQ Source /// Disable IRQ Source
@@ -88,12 +85,10 @@ pub fn disable_irq_source(state: IRQState) {
let nr = state as u32; let nr = state as u32;
let register = DISABLE_IRQ_BASE + 4 * (nr / 32); let register = DISABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32; let register_offset = nr % 32;
unsafe { let current = mmio_read(register);
let current = read_volatile(register as *const u32); let mask = 0b1 << register_offset;
let mask = 0b1 << register_offset; let new_val = current | mask;
let new_val = current | mask; mmio_write(register, new_val);
write_volatile(register as *mut u32, new_val);
}
} }
/// Read current IRQ Source status /// Read current IRQ Source status
@@ -101,7 +96,7 @@ pub fn read_irq_source_status(state: IRQState) -> u32 {
let nr = state as u32; let nr = state as u32;
let register = ENABLE_IRQ_BASE + 4 * (nr / 32); let register = ENABLE_IRQ_BASE + 4 * (nr / 32);
let register_offset = nr % 32; let register_offset = nr % 32;
unsafe { (read_volatile(register as *const u32) >> register_offset) & 0b1 } (mmio_read(register) >> register_offset) & 0b1
} }
/// Status if a IRQ Source is enabled /// Status if a IRQ Source is enabled
@@ -109,7 +104,7 @@ pub fn read_irq_pending(state: IRQState) -> bool {
let nr = state as u32; let nr = state as u32;
let register = IRQ_PENDING_BASE + 4 * (nr / 32); let register = IRQ_PENDING_BASE + 4 * (nr / 32);
let register_offset = nr % 32; let register_offset = nr % 32;
(unsafe { (read_volatile(register as *const u32) >> register_offset) & 0b1 }) != 0 ((mmio_read(register) >> register_offset) & 0b1) != 0
} }
/// Clears the IRQ DAIF Mask /// Clears the IRQ DAIF Mask

View File

@@ -1,6 +1,17 @@
#![no_std] #![no_std]
use core::ptr::{read_volatile, write_volatile};
pub mod peripherals; pub mod peripherals;
pub mod irq_interrupt; pub mod irq_interrupt;
pub mod mailbox;
pub mod timer; pub mod timer;
pub fn mmio_read(address: u32) -> u32 {
unsafe { read_volatile(address as *const u32) }
}
pub fn mmio_write(address: u32, data: u32) {
unsafe { write_volatile(address as *mut u32, data) }
}

View File

@@ -12,7 +12,7 @@ use nova::{
peripherals::{ peripherals::{
gpio::{ gpio::{
gpio_get_state, gpio_high, gpio_low, gpio_pull_up, set_falling_edge_detect, gpio_get_state, gpio_high, gpio_low, gpio_pull_up, set_falling_edge_detect,
set_gpio_state, GPIOState, set_gpio_function, GPIOFunction,
}, },
uart::{print, uart_init}, uart::{print, uart_init},
}, },
@@ -49,7 +49,7 @@ pub extern "C" fn main() -> ! {
enable_uart(); enable_uart();
// Set ACT Led to Outout // Set ACT Led to Outout
let _ = set_gpio_state(21, GPIOState::Output); let _ = set_gpio_function(21, GPIOFunction::Output);
print_current_el_str(); print_current_el_str();
@@ -72,7 +72,7 @@ pub extern "C" fn kernel_main() -> ! {
// Set GPIO 26 to Input // Set GPIO 26 to Input
enable_irq_source(nova::irq_interrupt::IRQState::GpioInt0); //26 is on the first GPIO bank enable_irq_source(nova::irq_interrupt::IRQState::GpioInt0); //26 is on the first GPIO bank
let _ = set_gpio_state(26, GPIOState::Input); let _ = set_gpio_function(26, GPIOFunction::Input);
gpio_pull_up(26); gpio_pull_up(26);
set_falling_edge_detect(26, true); set_falling_edge_detect(26, true);
@@ -112,8 +112,8 @@ pub fn get_current_el() -> u64 {
fn enable_uart() { fn enable_uart() {
uart_init(); uart_init();
// Set GPIO Pins to UART // Set GPIO Pins to UART
let _ = set_gpio_state(14, GPIOState::Alternative0); let _ = set_gpio_function(14, GPIOFunction::Alternative0);
let _ = set_gpio_state(15, GPIOState::Alternative0); let _ = set_gpio_function(15, GPIOFunction::Alternative0);
} }
fn print_current_el_str() { fn print_current_el_str() {

View File

@@ -1,9 +1,8 @@
use core::ptr::{read_volatile, write_volatile};
use core::result::Result; use core::result::Result;
use core::result::Result::Err;
use core::result::Result::Ok; use core::result::Result::Ok;
use crate::timer::delay_nops; use crate::timer::delay_nops;
use crate::{mmio_read, mmio_write};
const GPFSEL_BASE: u32 = 0x3F20_0000; const GPFSEL_BASE: u32 = 0x3F20_0000;
const GPSET_BASE: u32 = 0x3F20_001C; const GPSET_BASE: u32 = 0x3F20_001C;
@@ -15,7 +14,7 @@ const GPREN_BASE: u32 = 0x3F20_004C;
const GPFEN_BASE: u32 = 0x3F20_0058; const GPFEN_BASE: u32 = 0x3F20_0058;
#[repr(u32)] #[repr(u32)]
pub enum GPIOState { pub enum GPIOFunction {
Input = 0b000, Input = 0b000,
Output = 0b001, Output = 0b001,
Alternative0 = 0b100, Alternative0 = 0b100,
@@ -26,64 +25,66 @@ pub enum GPIOState {
Alternative5 = 0b010, Alternative5 = 0b010,
} }
pub fn set_gpio_state(gpio: u8, state: GPIOState) -> Result<(), &'static str> { /// Set the function of the GPIO pin
if gpio > 53 { pub fn set_gpio_function(gpio: u8, state: GPIOFunction) -> Result<(), &'static str> {
return Err("GPIO out of range");
}
let register_index = gpio / 10; let register_index = gpio / 10;
let register_offset = (gpio % 10) * 3; let register_offset = (gpio % 10) * 3;
let register_addr = GPFSEL_BASE + (register_index as u32 * 4); let register_addr = GPFSEL_BASE + (register_index as u32 * 4);
unsafe { let current = mmio_read(register_addr);
let current = core::ptr::read_volatile(register_addr as *const u32);
let mask = !(0b111 << register_offset); let mask = !(0b111 << register_offset);
let cleared = current & mask; let cleared = current & mask;
let new_val = cleared | ((state as u32) << register_offset); let new_val = cleared | ((state as u32) << register_offset);
core::ptr::write_volatile(register_addr as *mut u32, new_val); mmio_write(register_addr, new_val);
}
Ok(()) Ok(())
} }
/// Set the GPIO to high
///
/// Should be used when GPIO function is set to `OUTPUT` via `set_gpio_function`
pub fn gpio_high(gpio: u8) -> Result<(), &'static str> { pub fn gpio_high(gpio: u8) -> Result<(), &'static str> {
unsafe { let register_index = gpio / 32;
let register_index = gpio / 32; let register_offset = gpio % 32;
let register_offset = gpio % 32; let register_addr = GPSET_BASE + (register_index as u32 * 4);
let register_addr = GPSET_BASE + (register_index as u32 * 4);
core::ptr::write_volatile(register_addr as *mut u32, 1 << register_offset); mmio_write(register_addr, 1 << register_offset);
}
Ok(()) Ok(())
} }
/// Set the GPIO to low
///
/// Should be used when GPIO function is set to `OUTPUT` via `set_gpio_function`
pub fn gpio_low(gpio: u8) -> Result<(), &'static str> { pub fn gpio_low(gpio: u8) -> Result<(), &'static str> {
unsafe { let register_index = gpio / 32;
let register_index = gpio / 32; let register_offset = gpio % 32;
let register_offset = gpio % 32; let register_addr = GPCLR_BASE + (register_index as u32 * 4);
let register_addr = GPCLR_BASE + (register_index as u32 * 4);
core::ptr::write_volatile(register_addr as *mut u32, 1 << register_offset); mmio_write(register_addr, 1 << register_offset);
}
Ok(()) Ok(())
} }
/// Read the current GPIO power state
pub fn gpio_get_state(gpio: u8) -> u8 { pub fn gpio_get_state(gpio: u8) -> u8 {
unsafe { let register_index = gpio / 32;
let register_index = gpio / 32; let register_offset = gpio % 32;
let register_offset = gpio % 32; let register_addr = GPLEV_BASE + (register_index as u32 * 4);
let register_addr = GPLEV_BASE + (register_index as u32 * 4);
let state = core::ptr::read_volatile(register_addr as *mut u32); let state = mmio_read(register_addr);
return ((state >> register_offset) & 0b1) as u8; return ((state >> register_offset) & 0b1) as u8;
}
} }
/// Pull GPIO up
///
/// Should be used when GPIO function is set to `INPUT` via `set_gpio_function`
pub fn gpio_pull_up(gpio: u8) { pub fn gpio_pull_up(gpio: u8) {
gpio_pull_up_down(gpio, 0b10); gpio_pull_up_down(gpio, 0b10);
} }
/// Pull GPIO down
///
/// Should be used when GPIO function is set to `INPUT` via `set_gpio_function`
pub fn gpio_pull_down(gpio: u8) { pub fn gpio_pull_down(gpio: u8) {
gpio_pull_up_down(gpio, 0b01); gpio_pull_up_down(gpio, 0b01);
} }
@@ -95,70 +96,73 @@ fn gpio_pull_up_down(gpio: u8, val: u32) {
let register_offset = gpio % 32; let register_offset = gpio % 32;
// 1. Write Pull up // 1. Write Pull up
write_volatile(GPPUD as *mut u32, val); mmio_write(GPPUD, val);
// 2. Delay 150 cycles // 2. Delay 150 cycles
delay_nops(150); delay_nops(150);
// 3. Write to clock // 3. Write to clock
let new_val = 0b1 << register_offset; let new_val = 0b1 << register_offset;
write_volatile(register_addr as *mut u32, new_val); mmio_write(register_addr, new_val);
// 4. Delay 150 cycles // 4. Delay 150 cycles
delay_nops(150); delay_nops(150);
// 5. reset GPPUD // 5. reset GPPUD
write_volatile(GPPUD as *mut u32, 0); mmio_write(GPPUD, 0);
// 6. reset clock // 6. reset clock
write_volatile(register_addr as *mut u32, 0); mmio_write(register_addr, 0);
} }
} }
pub fn read_falling_edge_detect(gpio: u8) -> u32 { /// Get the current status if falling edge detection is set
unsafe { pub fn read_falling_edge_detect(gpio: u8) -> bool {
// Determine GPLEN Register let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32);
let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32); let register_offset = gpio % 32;
let register_offset = gpio % 32;
let current = read_volatile(register_addr as *const u32); let current = mmio_read(register_addr);
return (current >> register_offset) & 0b1; ((current >> register_offset) & 0b1) != 0
}
} }
/// Get the current status if falling edge detection is set
pub fn read_rising_edge_detect(gpio: u8) -> bool {
let register_addr = GPREN_BASE + 4 * (gpio as u32 / 32);
let register_offset = gpio % 32;
let current = mmio_read(register_addr);
((current >> register_offset) & 0b1) != 0
}
/// Enables falling edge detection
pub fn set_falling_edge_detect(gpio: u8, enable: bool) { pub fn set_falling_edge_detect(gpio: u8, enable: bool) {
unsafe { let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32);
// Determine GPLEN Register let register_offset = gpio % 32;
let register_addr = GPFEN_BASE + 4 * (gpio as u32 / 32);
let register_offset = gpio % 32;
let current = read_volatile(register_addr as *const u32); let current = mmio_read(register_addr);
let mask = 0b1 << register_offset; let mask = 0b1 << register_offset;
let new_val = if enable { let new_val = if enable {
current | mask current | mask
} else { } else {
current & !mask current & !mask
}; };
write_volatile(register_addr as *mut u32, new_val); mmio_write(register_addr, new_val);
}
} }
/// Enables rising edge detection
pub fn set_rising_edge_detect(gpio: u8, enable: bool) { pub fn set_rising_edge_detect(gpio: u8, enable: bool) {
unsafe { let register_addr = GPREN_BASE + 4 * (gpio as u32 / 32);
// Determine GPHEN Register let register_offset = gpio % 32;
let register_addr = GPREN_BASE + 4 * (gpio as u32 / 32);
let register_offset = gpio % 32;
let current = read_volatile(register_addr as *const u32); let current = mmio_read(register_addr);
let mask = 0b1 << register_offset; let mask = 0b1 << register_offset;
let new_val = if enable { let new_val = if enable {
current | mask current | mask
} else { } else {
current & !mask current & !mask
}; };
write_volatile(register_addr as *mut u32, new_val); mmio_write(register_addr, new_val);
}
} }

View File

@@ -1,3 +1,5 @@
use crate::{mmio_read, mmio_write};
const BAUD: u32 = 115200; const BAUD: u32 = 115200;
const UART_CLK: u32 = 48_000_000; const UART_CLK: u32 = 48_000_000;
@@ -19,13 +21,11 @@ const UART0_LCRH_FEN: u32 = 1 << 4;
/// Print `s` over UART /// Print `s` over UART
pub fn print(s: &str) { pub fn print(s: &str) {
for byte in s.bytes() { for byte in s.bytes() {
unsafe { while mmio_read(UART0_FR) & UART0_FR_TXFF != 0 {}
while core::ptr::read_volatile(UART0_FR as *const u32) & UART0_FR_TXFF != 0 {} mmio_write(UART0_DR, byte as u32);
core::ptr::write_volatile(UART0_DR as *mut u32, byte as u32);
}
} }
// wait till uart is not busy anymore // wait till uart is not busy anymore
unsafe { while (core::ptr::read_volatile(UART0_FR as *const u32) >> 3) & 0b1 != 0 {} } while (mmio_read(UART0_FR) >> 3) & 0b1 != 0 {}
} }
/// Initialize UART peripheral /// Initialize UART peripheral
@@ -35,60 +35,52 @@ pub fn uart_init() {
let ibrd = baud_div_times_64 / 64; let ibrd = baud_div_times_64 / 64;
let fbrd = baud_div_times_64 % 64; let fbrd = baud_div_times_64 % 64;
unsafe { uart_enable(false);
uart_enable(false); uart_fifo_enable(false);
uart_fifo_enable(false);
core::ptr::write_volatile(UART0_IBRD as *mut u32, ibrd); mmio_write(UART0_IBRD, ibrd);
core::ptr::write_volatile(UART0_FBRD as *mut u32, fbrd); mmio_write(UART0_FBRD, fbrd);
uart_set_lcrh(0b11, true); uart_set_lcrh(0b11, true);
// Enable transmit and uart // Enable transmit and uart
let mut cr = core::ptr::read_volatile(UART0_CR as *mut u32); let mut cr = mmio_read(UART0_CR);
cr |= UART0_CR_UARTEN | UART0_CR_TXE; cr |= UART0_CR_UARTEN | UART0_CR_TXE;
core::ptr::write_volatile(UART0_CR as *mut u32, cr); mmio_write(UART0_CR, cr);
}
} }
/// Enable UARTEN /// Enable UARTEN
fn uart_enable(enable: bool) { fn uart_enable(enable: bool) {
unsafe { let mut cr = mmio_read(UART0_CR);
let mut cr = core::ptr::read_volatile(UART0_CR as *mut u32);
if enable { if enable {
cr |= UART0_CR_UARTEN; cr |= UART0_CR_UARTEN;
} else { } else {
cr &= !UART0_CR_UARTEN; cr &= !UART0_CR_UARTEN;
}
core::ptr::write_volatile(UART0_CR as *mut u32, cr);
} }
mmio_write(UART0_CR, cr);
} }
/// Enable UART FIFO /// Enable UART FIFO
fn uart_fifo_enable(enable: bool) { fn uart_fifo_enable(enable: bool) {
unsafe { let mut lcrh = mmio_read(UART0_LCRH);
let mut lcrh = core::ptr::read_volatile(UART0_LCRH as *mut u32);
if enable { if enable {
lcrh |= UART0_LCRH_FEN; lcrh |= UART0_LCRH_FEN;
} else { } else {
lcrh &= !UART0_LCRH_FEN; lcrh &= !UART0_LCRH_FEN;
}
core::ptr::write_volatile(UART0_LCRH as *mut u32, lcrh);
} }
mmio_write(UART0_LCRH, lcrh);
} }
/// Set UART word length and set FIFO status /// Set UART word length and set FIFO status
fn uart_set_lcrh(wlen: u32, enable_fifo: bool) { fn uart_set_lcrh(wlen: u32, enable_fifo: bool) {
unsafe { let mut value = (wlen & 0b11) << 5;
let mut value = (wlen & 0b11) << 5; if enable_fifo {
if enable_fifo { value |= UART0_LCRH_FEN;
value |= UART0_LCRH_FEN;
}
core::ptr::write_volatile(UART0_LCRH as *mut u32, value);
} }
mmio_write(UART0_LCRH, value);
} }

View File

@@ -1,7 +1,9 @@
use crate::mmio_read;
const TIMER_CLO: u32 = 0x3F00_3004; const TIMER_CLO: u32 = 0x3F00_3004;
fn read_clo() -> u32 { fn read_clo() -> u32 {
unsafe { return core::ptr::read_volatile(TIMER_CLO as *const u32) } return mmio_read(TIMER_CLO);
} }
/// Sleep for `us` microseconds /// Sleep for `us` microseconds