From 0e1c6669ac5b46cc9b31b55fa927c6490a1dd74f Mon Sep 17 00:00:00 2001 From: Berkus Decker Date: Wed, 12 Jul 2023 03:03:08 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=93=A6=20Use=20better=20code?= =?UTF-8?q?=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As inspired by andre-richter's tutorials. --- Makefile.toml | 1 + machine/src/arch/aarch64/boot.rs | 2 +- machine/src/console.rs | 68 +++ machine/src/console/null_console.rs | 54 +++ machine/src/devices/console.rs | 240 ++++++----- machine/src/devices/mod.rs | 8 +- machine/src/drivers.rs | 158 +++++++ machine/src/lib.rs | 37 +- machine/src/macros.rs | 7 +- machine/src/mmio_deref_wrapper.rs | 46 ++ machine/src/platform/mod.rs | 46 -- .../platform/rpi3/{ => device_driver}/gpio.rs | 15 +- .../platform/rpi3/device_driver/mini_uart.rs | 405 ++++++++++++++++++ .../src/platform/rpi3/device_driver/mod.rs | 5 + .../rpi3/{ => device_driver}/pl011_uart.rs | 15 +- machine/src/platform/rpi3/drivers.rs | 77 ++++ machine/src/platform/rpi3/mailbox.rs | 44 +- machine/src/platform/rpi3/mini_uart.rs | 332 -------------- machine/src/platform/rpi3/mod.rs | 5 +- machine/src/platform/rpi3/power.rs | 4 +- machine/src/sync.rs | 64 ++- nucleus/src/main.rs | 107 ++--- 22 files changed, 1128 insertions(+), 612 deletions(-) create mode 100644 machine/src/console.rs create mode 100644 machine/src/console/null_console.rs create mode 100644 machine/src/drivers.rs create mode 100644 machine/src/mmio_deref_wrapper.rs rename machine/src/platform/rpi3/{ => device_driver}/gpio.rs (97%) create mode 100644 machine/src/platform/rpi3/device_driver/mini_uart.rs create mode 100644 machine/src/platform/rpi3/device_driver/mod.rs rename machine/src/platform/rpi3/{ => device_driver}/pl011_uart.rs (98%) create mode 100644 machine/src/platform/rpi3/drivers.rs delete mode 100644 machine/src/platform/rpi3/mini_uart.rs diff --git a/Makefile.toml b/Makefile.toml index 554df8c..61a6ca5 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -183,6 +183,7 @@ script = [ ''' writefile ${GDB_CONNECT_FILE} "target extended-remote :5555\n" appendfile ${GDB_CONNECT_FILE} "break *0x80000\n" + appendfile ${GDB_CONNECT_FILE} "break kernel_init\n" appendfile ${GDB_CONNECT_FILE} "break kernel_main\n" echo 🖌️ Generated GDB config file ''' diff --git a/machine/src/arch/aarch64/boot.rs b/machine/src/arch/aarch64/boot.rs index 6b02a27..8b601bf 100644 --- a/machine/src/arch/aarch64/boot.rs +++ b/machine/src/arch/aarch64/boot.rs @@ -29,7 +29,7 @@ macro_rules! entry { #[inline(always)] pub unsafe fn __main() -> ! { // type check the given path - let f: fn() -> ! = $path; + let f: unsafe fn() -> ! = $path; f() } diff --git a/machine/src/console.rs b/machine/src/console.rs new file mode 100644 index 0000000..b4f21e5 --- /dev/null +++ b/machine/src/console.rs @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: BlueOak-1.0.0 + */ + +#![allow(dead_code)] + +use crate::sync::NullLock; + +pub mod null_console; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Console interfaces. +pub mod interface { + use {crate::devices::SerialOps, core::fmt}; + + /// Console write functions. + pub trait Write { + /// Write a Rust format string. + fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result; + } + + /// A trait that must be implemented by devices that are candidates for the + /// global console. + #[allow(unused_variables)] + pub trait ConsoleOps: SerialOps { + /// Send a character + fn write_char(&self, c: char); + /// Display a string + fn write_string(&self, string: &str); + /// Receive a character + fn read_char(&self) -> char; + } + + pub trait ConsoleTools { + fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8]; + } + + /// Trait alias for a full-fledged console. + pub trait All: Write + ConsoleOps + ConsoleTools {} +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static CONSOLE: NullLock<&'static (dyn interface::All + Sync)> = + NullLock::new(&null_console::NULL_CONSOLE); + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +use crate::sync::interface::Mutex; + +/// Register a new console. +pub fn register_console(new_console: &'static (dyn interface::All + Sync)) { + CONSOLE.lock(|con| *con = new_console); +} + +/// Return a reference to the currently registered console. +/// +/// This is the global console used by all printing macros. +pub fn console() -> &'static dyn interface::All { + CONSOLE.lock(|con| *con) +} diff --git a/machine/src/console/null_console.rs b/machine/src/console/null_console.rs new file mode 100644 index 0000000..290f801 --- /dev/null +++ b/machine/src/console/null_console.rs @@ -0,0 +1,54 @@ +use crate::{console::interface, devices::SerialOps}; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// A dummy console that just ignores all I/O. +pub struct NullConsole; + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +pub static NULL_CONSOLE: NullConsole = NullConsole {}; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl interface::Write for NullConsole { + fn write_fmt(&self, args: core::fmt::Arguments) -> core::fmt::Result { + Ok(()) + } +} + +impl interface::ConsoleOps for NullConsole { + fn write_char(&self, _c: char) {} + + fn write_string(&self, _string: &str) {} + + fn read_char(&self) -> char { + ' ' + } +} + +impl SerialOps for NullConsole { + fn read_byte(&self) -> u8 { + 0 + } + + fn write_byte(&self, _byte: u8) {} + + fn flush(&self) {} + + fn clear_rx(&self) {} +} + +impl interface::ConsoleTools for NullConsole { + fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] { + buf + } +} + +impl interface::All for NullConsole {} diff --git a/machine/src/devices/console.rs b/machine/src/devices/console.rs index 4c3e09e..edcfaa7 100644 --- a/machine/src/devices/console.rs +++ b/machine/src/devices/console.rs @@ -1,110 +1,112 @@ -/* - * SPDX-License-Identifier: BlueOak-1.0.0 - */ - -#![allow(dead_code)] - use { - crate::{devices::SerialOps, platform}, + crate::{ + console::interface, + devices::{null_console::NullConsole, SerialOps}, + platform::rpi3::{mini_uart::PreparedMiniUart, pl011_uart::PreparedPL011Uart}, + sync::NullLock, + }, core::fmt, }; -/// A trait that must be implemented by devices that are candidates for the -/// global console. -#[allow(unused_variables)] -pub trait ConsoleOps: SerialOps { - /// Send a character - fn write_char(&self, c: char); - /// Display a string - fn write_string(&self, string: &str); - /// Receive a character - fn read_char(&self) -> char; -} +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- -/// A dummy console that just ignores its inputs. -pub struct NullConsole; - -impl Drop for NullConsole { - fn drop(&mut self) {} -} - -impl ConsoleOps for NullConsole { - fn write_char(&self, _c: char) {} - - fn write_string(&self, _string: &str) {} - - fn read_char(&self) -> char { - ' ' - } -} - -impl SerialOps for NullConsole { - fn read_byte(&self) -> u8 { - 0 - } - - fn write_byte(&self, _byte: u8) {} - - fn flush(&self) {} - - fn clear_rx(&self) {} -} - -/// Possible outputs which the console can store. -pub enum Output { - None(NullConsole), - MiniUart(platform::rpi3::mini_uart::PreparedMiniUart), - Uart(platform::rpi3::pl011_uart::PreparedPL011Uart), -} - -/// Generate boilerplate for converting into one of Output enum values -macro output_from($name:ty, $optname:ident) { - impl From<$name> for Output { - fn from(instance: $name) -> Self { - Output::$optname(instance) - } - } -} - -output_from!(NullConsole, None); -output_from!(platform::rpi3::mini_uart::PreparedMiniUart, MiniUart); -output_from!(platform::rpi3::pl011_uart::PreparedPL011Uart, Uart); - -pub struct Console { +/// The mutex protected part. +struct ConsoleInner { output: Output, } -impl Default for Console { - fn default() -> Self { - Self::new() - } +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// The main struct. +pub struct Console { + inner: NullLock, } -impl Console { +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static CONSOLE: Console = Console::new(); + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl ConsoleInner { pub const fn new() -> Self { - Console { + Self { output: Output::None(NullConsole {}), } } - fn current_ptr(&self) -> &dyn ConsoleOps { + fn current_ptr(&self) -> &dyn interface::ConsoleOps { match &self.output { - Output::None(i) => i, - Output::MiniUart(i) => i, - Output::Uart(i) => i, + Output::None(inner) => inner, + Output::MiniUart(inner) => inner, + Output::Uart(inner) => inner, } } /// Overwrite the current output. The old output will go out of scope and /// its Drop function will be called. pub fn replace_with(&mut self, new_output: Output) { - self.current_ptr().flush(); + self.current_ptr().flush(); // crashed here with Data Abort + // ...with ESR 0x25/0x96000000 + // ...with FAR 0x984f800000028 + // ...with ELR 0x946a8 self.output = new_output; } +} +/// Implementing `core::fmt::Write` enables usage of the `format_args!` macros, which in turn are +/// used to implement the `kernel`'s `print!` and `println!` macros. By implementing `write_str()`, +/// we get `write_fmt()` automatically. +/// See src/macros.rs. +/// +/// The function takes an `&mut self`, so it must be implemented for the inner struct. +impl fmt::Write for ConsoleInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.current_ptr().write_string(s); + // for c in s.chars() { + // // Convert newline to carrige return + newline. + // if c == '\n' { + // self.write_char('\r') + // } + // + // self.write_char(c); + // } + + Ok(()) + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl Console { + /// Create a new instance. + pub const fn new() -> Console { + Console { + inner: NullLock::new(ConsoleInner::new()), + } + } + + pub fn replace_with(&mut self, new_output: Output) { + self.inner.lock(|inner| inner.replace_with(new_output)); + } +} + +impl interface::ConsoleTools for Console { /// A command prompt. - pub fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] { + fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] { + use interface::ConsoleOps; + self.write_string("\n$> "); let mut i = 0; @@ -129,47 +131,89 @@ impl Console { } } -impl Drop for Console { - fn drop(&mut self) {} +/// The global console. Output of the kernel print! and println! macros goes here. +pub fn console() -> &'static dyn crate::console::interface::All { + &CONSOLE +} + +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ +use crate::sync::interface::Mutex; + +/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded by a Mutex to +/// serialize access. +impl interface::Write for Console { + fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result { + self.inner.lock(|inner| fmt::Write::write_fmt(inner, args)) + } } /// Dispatch the respective function to the currently stored output device. -impl ConsoleOps for Console { +impl interface::ConsoleOps for Console { + // @todo implement utf8 serialization here! fn write_char(&self, c: char) { - self.current_ptr().write_char(c); + self.inner.lock(|con| con.current_ptr().write_char(c)); } fn write_string(&self, string: &str) { - self.current_ptr().write_string(string); + self.inner + .lock(|con| con.current_ptr().write_string(string)); } + // @todo implement utf8 deserialization here! fn read_char(&self) -> char { - self.current_ptr().read_char() + self.inner.lock(|con| con.current_ptr().read_char()) } } impl SerialOps for Console { fn read_byte(&self) -> u8 { - self.current_ptr().read_byte() + self.inner.lock(|con| con.current_ptr().read_byte()) } fn write_byte(&self, byte: u8) { - self.current_ptr().write_byte(byte) + self.inner.lock(|con| con.current_ptr().write_byte(byte)) } fn flush(&self) { - self.current_ptr().flush() + self.inner.lock(|con| con.current_ptr().flush()) } fn clear_rx(&self) { - self.current_ptr().clear_rx() + self.inner.lock(|con| con.current_ptr().clear_rx()) } } -/// Implementing this trait enables usage of the format_args! macros, which in -/// turn are used to implement the kernel's print! and println! macros. -/// -/// See src/macros.rs. -impl fmt::Write for Console { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.current_ptr().write_string(s); - Ok(()) +impl interface::All for Console {} + +impl Default for Console { + fn default() -> Self { + Self::new() } } + +impl Drop for Console { + fn drop(&mut self) {} +} + +//------------------------------------------------------------------------------ +// Device Interface Code +//------------------------------------------------------------------------------ + +/// Possible outputs which the console can store. +enum Output { + None(NullConsole), + MiniUart(PreparedMiniUart), + Uart(PreparedPL011Uart), +} + +/// Generate boilerplate for converting into one of Output enum values +macro make_from($optname:ident, $name:ty) { + impl From<$name> for Output { + fn from(instance: $name) -> Self { + Output::$optname(instance) + } + } +} + +make_from!(None, NullConsole); +make_from!(MiniUart, PreparedMiniUart); +make_from!(Uart, PreparedPL011Uart); diff --git a/machine/src/devices/mod.rs b/machine/src/devices/mod.rs index 180be7b..c2ce604 100644 --- a/machine/src/devices/mod.rs +++ b/machine/src/devices/mod.rs @@ -1,10 +1,8 @@ /* * SPDX-License-Identifier: BlueOak-1.0.0 */ -pub mod console; +// pub mod console; +// pub mod null_console; pub mod serial; -pub use { - console::{Console, ConsoleOps}, - serial::SerialOps, -}; +pub use serial::SerialOps; diff --git a/machine/src/drivers.rs b/machine/src/drivers.rs new file mode 100644 index 0000000..47c46c5 --- /dev/null +++ b/machine/src/drivers.rs @@ -0,0 +1,158 @@ +use crate::{ + println, + sync::{interface::Mutex, NullLock}, +}; + +//-------------------------------------------------------------------------------------------------- +// Private Definitions +//-------------------------------------------------------------------------------------------------- + +const NUM_DRIVERS: usize = 5; + +struct DriverManagerInner { + next_index: usize, + descriptors: [Option; NUM_DRIVERS], +} + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +pub mod interface { + pub trait DeviceDriver { + /// Return a compatibility string for identifying the driver. + fn compatible(&self) -> &'static str; + + /// Called by the kernel to bring up the device. + /// The default implementation does nothing. + /// + /// # Safety + /// + /// - During init, drivers might do things with system-wide impact. + unsafe fn init(&self) -> Result<(), &'static str> { + Ok(()) + } + } +} + +/// Type to be used as an optional callback after a driver's init() has run. +pub type DeviceDriverPostInitCallback = unsafe fn() -> Result<(), &'static str>; + +/// A descriptor for device drivers. +#[derive(Copy, Clone)] +pub struct DeviceDriverDescriptor { + device_driver: &'static (dyn interface::DeviceDriver + Sync), + post_init_callback: Option, +} + +/// Provides device driver management functions. +pub struct DriverManager { + inner: NullLock, +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static DRIVER_MANAGER: DriverManager = DriverManager::new(); + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +impl DriverManagerInner { + pub const fn new() -> Self { + Self { + next_index: 0, + descriptors: [None; NUM_DRIVERS], + } + } +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Return a reference to the global DriverManager. +pub fn driver_manager() -> &'static DriverManager { + &DRIVER_MANAGER +} + +impl DeviceDriverDescriptor { + pub fn new( + device_driver: &'static (dyn interface::DeviceDriver + Sync), + post_init_callback: Option, + ) -> Self { + Self { + device_driver, + post_init_callback, + } + } +} + +impl DriverManager { + pub const fn new() -> Self { + Self { + inner: NullLock::new(DriverManagerInner::new()), + } + } + + /// Register a device driver with the kernel. + pub fn register_driver(&self, descriptor: DeviceDriverDescriptor) { + self.inner.lock(|inner| { + assert!(inner.next_index < NUM_DRIVERS); + inner.descriptors[inner.next_index] = Some(descriptor); + inner.next_index += 1; + }) + } + + /// Helper for iterating over registered drivers. + fn for_each_descriptor(&self, f: impl FnMut(&DeviceDriverDescriptor)) { + self.inner.lock(|inner| { + inner + .descriptors + .iter() + .filter_map(|x| x.as_ref()) + .for_each(f) + }) + } + + /// Fully initialize all drivers. + /// + /// # Safety + /// + /// - During init, drivers might do things with system-wide impact. + pub unsafe fn init_drivers(&self) { + self.for_each_descriptor(|descriptor| { + // 1. Initialize driver. + if let Err(x) = descriptor.device_driver.init() { + panic!( + "Error initializing driver: {}: {}", + descriptor.device_driver.compatible(), + x + ); + } + + // 2. Call corresponding post init callback. + if let Some(callback) = &descriptor.post_init_callback { + if let Err(x) = callback() { + panic!( + "Error during driver post-init callback: {}: {}", + descriptor.device_driver.compatible(), + x + ); + } + } + }); + } + + /// Enumerate all registered device drivers. + pub fn enumerate(&self) { + let mut i: usize = 1; + self.for_each_descriptor(|descriptor| { + println!(" {}. {}", i, descriptor.device_driver.compatible()); + + i += 1; + }); + } +} diff --git a/machine/src/lib.rs b/machine/src/lib.rs index 41c2b16..0e9a8fd 100644 --- a/machine/src/lib.rs +++ b/machine/src/lib.rs @@ -16,26 +16,24 @@ #![allow(clippy::nonstandard_macro_braces)] // https://github.com/shepmaster/snafu/issues/296 #![allow(missing_docs)] // Temp: switch to deny #![deny(warnings)] +#![allow(unused)] #[cfg(not(target_arch = "aarch64"))] use architecture_not_supported_sorry; -use { - buddy_alloc::{BuddyAlloc, BuddyAllocParam}, - once_cell::unsync::Lazy, - platform::memory::map::virt::{DMA_HEAP_END, DMA_HEAP_START}, -}; - /// Architecture-specific code. #[macro_use] pub mod arch; pub use arch::*; +pub mod console; pub mod devices; +pub mod drivers; pub mod macros; pub mod memory; mod mm; +pub mod mmio_deref_wrapper; pub mod panic; pub mod platform; pub mod qemu; @@ -43,21 +41,18 @@ mod sync; pub mod tests; pub mod write_to; -/// The global console. Output of the kernel print! and println! macros goes here. -pub static CONSOLE: sync::NullLock = sync::NullLock::new(devices::Console::new()); - -/// The global allocator for DMA-able memory. That is, memory which is tagged -/// non-cacheable in the page tables. -#[allow(dead_code)] -static DMA_ALLOCATOR: sync::NullLock> = - sync::NullLock::new(Lazy::new(|| unsafe { - BuddyAlloc::new(BuddyAllocParam::new( - // @todo Init this after we loaded boot memory map - DMA_HEAP_START as *const u8, - DMA_HEAP_END - DMA_HEAP_START, - 64, - )) - })); +// The global allocator for DMA-able memory. That is, memory which is tagged +// non-cacheable in the page tables. +// #[allow(dead_code)] +// static DMA_ALLOCATOR: sync::NullLock> = +// sync::NullLock::new(Lazy::new(|| unsafe { +// BuddyAlloc::new(BuddyAllocParam::new( +// // @todo Init this after we loaded boot memory map +// DMA_HEAP_START as *const u8, +// DMA_HEAP_END - DMA_HEAP_START, +// 64, +// )) +// })); // Try the following arguments instead to see all mailbox operations // fail. It will cause the allocator to use memory that is marked // cacheable and therefore not DMA-safe. The answer from the VideoCore diff --git a/machine/src/macros.rs b/machine/src/macros.rs index ab02c8f..da7dccd 100644 --- a/machine/src/macros.rs +++ b/machine/src/macros.rs @@ -23,11 +23,8 @@ macro_rules! println { #[doc(hidden)] #[cfg(not(any(test, qemu)))] pub fn _print(args: core::fmt::Arguments) { - use core::fmt::Write; - - crate::CONSOLE.lock(|c| { - c.write_fmt(args).unwrap(); - }) + use {crate::console::console, core::fmt::Write}; + console().write_fmt(args).unwrap(); } /// qemu-based tests use semihosting write0 syscall. diff --git a/machine/src/mmio_deref_wrapper.rs b/machine/src/mmio_deref_wrapper.rs new file mode 100644 index 0000000..9fba4e4 --- /dev/null +++ b/machine/src/mmio_deref_wrapper.rs @@ -0,0 +1,46 @@ +use core::{marker::PhantomData, ops}; + +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +pub struct MMIODerefWrapper { + pub base_addr: usize, // @fixme why pub?? + phantom: PhantomData T>, +} + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +impl MMIODerefWrapper { + /// Create an instance. + /// + /// # Safety + /// + /// You could specify any base address here, no checks. + pub const unsafe fn new(start_addr: usize) -> Self { + Self { + base_addr: start_addr, + phantom: PhantomData, + } + } +} + +/// Deref to RegisterBlock +/// +/// Allows writing +/// ``` +/// self.GPPUD.read() +/// ``` +/// instead of something along the lines of +/// ``` +/// unsafe { (*GPIO::ptr()).GPPUD.read() } +/// ``` +impl ops::Deref for MMIODerefWrapper { + type Target = T; + + fn deref(&self) -> &Self::Target { + unsafe { &*(self.base_addr as *const _) } + } +} diff --git a/machine/src/platform/mod.rs b/machine/src/platform/mod.rs index 536abb7..2ad4f6b 100644 --- a/machine/src/platform/mod.rs +++ b/machine/src/platform/mod.rs @@ -2,54 +2,8 @@ * SPDX-License-Identifier: BlueOak-1.0.0 * Copyright (c) Berkus Decker */ -use core::{marker::PhantomData, ops}; - -//-------------------------------------------------------------------------------------------------- -// Public Definitions -//-------------------------------------------------------------------------------------------------- pub mod rpi3; #[cfg(any(feature = "rpi3", feature = "rpi4"))] pub use rpi3::*; - -pub struct MMIODerefWrapper { - base_addr: usize, - phantom: PhantomData T>, -} - -//-------------------------------------------------------------------------------------------------- -// Public Code -//-------------------------------------------------------------------------------------------------- - -impl MMIODerefWrapper { - /// Create an instance. - /// - /// # Safety - /// - /// Unsafe, duh! - pub const unsafe fn new(start_addr: usize) -> Self { - Self { - base_addr: start_addr, - phantom: PhantomData, - } - } -} - -/// Deref to RegisterBlock -/// -/// Allows writing -/// ``` -/// self.GPPUD.read() -/// ``` -/// instead of something along the lines of -/// ``` -/// unsafe { (*GPIO::ptr()).GPPUD.read() } -/// ``` -impl ops::Deref for MMIODerefWrapper { - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { &*(self.base_addr as *const _) } - } -} diff --git a/machine/src/platform/rpi3/gpio.rs b/machine/src/platform/rpi3/device_driver/gpio.rs similarity index 97% rename from machine/src/platform/rpi3/gpio.rs rename to machine/src/platform/rpi3/device_driver/gpio.rs index 06d4509..e141d66 100644 --- a/machine/src/platform/rpi3/gpio.rs +++ b/machine/src/platform/rpi3/device_driver/gpio.rs @@ -6,8 +6,7 @@ */ use { - super::BcmHost, - crate::platform::MMIODerefWrapper, + crate::{mmio_deref_wrapper::MMIODerefWrapper, platform::BcmHost}, core::marker::PhantomData, tock_registers::{ fields::FieldValue, @@ -94,17 +93,17 @@ pub struct GPIO { registers: Registers, } -pub const GPIO_START: usize = 0x20_0000; +pub const GPIO_BASE: usize = BcmHost::get_peripheral_address() + 0x20_0000; -impl Default for GPIO { - fn default() -> GPIO { - // Default RPi3 GPIO base address - const GPIO_BASE: usize = BcmHost::get_peripheral_address() + GPIO_START; - unsafe { GPIO::new(GPIO_BASE) } +impl crate::drivers::interface::DeviceDriver for GPIO { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE } } impl GPIO { + pub const COMPATIBLE: &'static str = "BCM GPIO"; + /// # Safety /// /// Unsafe, duh! diff --git a/machine/src/platform/rpi3/device_driver/mini_uart.rs b/machine/src/platform/rpi3/device_driver/mini_uart.rs new file mode 100644 index 0000000..f19ae42 --- /dev/null +++ b/machine/src/platform/rpi3/device_driver/mini_uart.rs @@ -0,0 +1,405 @@ +/* + * SPDX-License-Identifier: MIT OR BlueOak-1.0.0 + * Copyright (c) 2018-2019 Andre Richter + * Copyright (c) Berkus Decker + * Original code distributed under MIT, additional changes are under BlueOak-1.0.0 + */ + +#[cfg(not(feature = "noserial"))] +use tock_registers::interfaces::{Readable, Writeable}; +use { + crate::{ + console::interface, + devices::SerialOps, + mmio_deref_wrapper::MMIODerefWrapper, + platform::{device_driver::gpio, BcmHost}, + sync::{interface::Mutex, NullLock}, + }, + cfg_if::cfg_if, + core::{ + convert::From, + fmt::{self, Arguments}, + }, + tock_registers::{ + interfaces::ReadWriteable, + register_bitfields, register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, + }, +}; + +// Auxiliary mini UART registers +// +// Descriptions taken from +// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf +register_bitfields! { + u32, + + /// Auxiliary enables + AUX_ENABLES [ + /// If set the mini UART is enabled. The UART will immediately + /// start receiving data, especially if the UART1_RX line is + /// low. + /// If clear the mini UART is disabled. That also disables any + /// mini UART register access + MINI_UART_ENABLE OFFSET(0) NUMBITS(1) [] + ], + + /// Mini Uart Interrupt Identify + AUX_MU_IIR [ + /// Writing with bit 1 set will clear the receive FIFO + /// Writing with bit 2 set will clear the transmit FIFO + FIFO_CLEAR OFFSET(1) NUMBITS(2) [ + Rx = 0b01, + Tx = 0b10, + All = 0b11 + ] + ], + + /// Mini Uart Line Control + AUX_MU_LCR [ + /// Mode the UART works in + DATA_SIZE OFFSET(0) NUMBITS(2) [ + SevenBit = 0b00, + EightBit = 0b11 + ] + ], + + /// Mini Uart Line Status + AUX_MU_LSR [ + /// This bit is set if the transmit FIFO is empty and the transmitter is + /// idle. (Finished shifting out the last bit). + TX_IDLE OFFSET(6) NUMBITS(1) [], + + /// This bit is set if the transmit FIFO can accept at least + /// one byte. + TX_EMPTY OFFSET(5) NUMBITS(1) [], + + /// This bit is set if the receive FIFO holds at least 1 + /// symbol. + DATA_READY OFFSET(0) NUMBITS(1) [] + ], + + /// Mini Uart Extra Control + AUX_MU_CNTL [ + /// If this bit is set the mini UART transmitter is enabled. + /// If this bit is clear the mini UART transmitter is disabled. + TX_EN OFFSET(1) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ], + + /// If this bit is set the mini UART receiver is enabled. + /// If this bit is clear the mini UART receiver is disabled. + RX_EN OFFSET(0) NUMBITS(1) [ + Disabled = 0, + Enabled = 1 + ] + ], + + /// Mini Uart Status + AUX_MU_STAT [ + TX_DONE OFFSET(9) NUMBITS(1) [ + No = 0, + Yes = 1 + ], + + /// This bit is set if the transmit FIFO can accept at least + /// one byte. + SPACE_AVAILABLE OFFSET(1) NUMBITS(1) [ + No = 0, + Yes = 1 + ], + + /// This bit is set if the receive FIFO holds at least 1 + /// symbol. + SYMBOL_AVAILABLE OFFSET(0) NUMBITS(1) [ + No = 0, + Yes = 1 + ] + ], + + /// Mini Uart Baud rate + AUX_MU_BAUD [ + /// Mini UART baud rate counter + RATE OFFSET(0) NUMBITS(16) [] + ] +} + +register_structs! { + #[allow(non_snake_case)] + RegisterBlock { + // 0x00 - AUX_IRQ? + (0x00 => __reserved_1), + (0x04 => AUX_ENABLES: ReadWrite), + (0x08 => __reserved_2), + (0x40 => AUX_MU_IO: ReadWrite),//Mini Uart I/O Data + (0x44 => AUX_MU_IER: WriteOnly),//Mini Uart Interrupt Enable + (0x48 => AUX_MU_IIR: WriteOnly), + (0x4c => AUX_MU_LCR: WriteOnly), + (0x50 => AUX_MU_MCR: WriteOnly), + (0x54 => AUX_MU_LSR: ReadOnly), + // 0x58 - AUX_MU_MSR + // 0x5c - AUX_MU_SCRATCH + (0x58 => __reserved_3), + (0x60 => AUX_MU_CNTL: WriteOnly), + (0x64 => AUX_MU_STAT: ReadOnly), + (0x68 => AUX_MU_BAUD: WriteOnly), + (0x6c => @END), + } +} + +type Registers = MMIODerefWrapper; + +struct MiniUartInner { + registers: Registers, +} + +pub struct MiniUart { + inner: NullLock, +} + +/// Divisor values for common baud rates +pub enum Rate { + Baud115200 = 270, +} + +impl From for u32 { + fn from(r: Rate) -> Self { + r as u32 + } +} + +// [temporary] Used in mmu.rs to set up local paging +pub const UART1_BASE: usize = BcmHost::get_peripheral_address() + 0x21_5000; + +impl crate::drivers::interface::DeviceDriver for MiniUart { + fn compatible(&self) -> &'static str { + Self::COMPATIBLE + } + + unsafe fn init(&self) -> Result<(), &'static str> { + self.inner.lock(|inner| inner.prepare()) + } +} + +impl MiniUart { + pub const COMPATIBLE: &'static str = "BCM MINI UART"; +} + +impl MiniUart { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(base_addr: usize) -> Self { + Self { + inner: NullLock::new(MiniUartInner::new(base_addr)), + } + } + + /// GPIO pins should be set up first before enabling the UART + pub fn prepare_gpio(gpio: &gpio::GPIO) { + // Pin 14 + const MINI_UART_TXD: gpio::Function = gpio::Function::Alt5; + // Pin 15 + const MINI_UART_RXD: gpio::Function = gpio::Function::Alt5; + + // map UART1 to GPIO pins + gpio.get_pin(14) + .into_alt(MINI_UART_TXD) + .set_pull_up_down(gpio::PullUpDown::Up); + gpio.get_pin(15) + .into_alt(MINI_UART_RXD) + .set_pull_up_down(gpio::PullUpDown::Up); + } +} + +impl MiniUartInner { + /// Create an instance. + /// + /// # Safety + /// + /// - The user must ensure to provide a correct MMIO start address. + pub const unsafe fn new(base_addr: usize) -> Self { + Self { + registers: Registers::new(base_addr), + } + } +} + +impl MiniUartInner { + /// Set baud rate and characteristics (115200 8N1) and map to GPIO + pub fn prepare(&self) -> Result<(), &'static str> { + // initialize UART + self.registers + .AUX_ENABLES + .modify(AUX_ENABLES::MINI_UART_ENABLE::SET); + self.registers.AUX_MU_IER.set(0); + self.registers.AUX_MU_CNTL.set(0); + self.registers + .AUX_MU_LCR + .write(AUX_MU_LCR::DATA_SIZE::EightBit); + self.registers.AUX_MU_MCR.set(0); + self.registers.AUX_MU_IER.set(0); + self.registers + .AUX_MU_BAUD + .write(AUX_MU_BAUD::RATE.val(Rate::Baud115200.into())); + + // Clear FIFOs before using the device + self.registers.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All); + + self.registers + .AUX_MU_CNTL + .write(AUX_MU_CNTL::RX_EN::Enabled + AUX_MU_CNTL::TX_EN::Enabled); + + Ok(()) + } +} + +impl Drop for MiniUartInner { + fn drop(&mut self) { + self.registers + .AUX_ENABLES + .modify(AUX_ENABLES::MINI_UART_ENABLE::CLEAR); + // @todo disable gpio.PUD ? + } +} + +impl SerialOps for MiniUartInner { + /// Receive a byte without console translation + fn read_byte(&self) -> u8 { + // wait until something is in the buffer + crate::arch::loop_until(|| { + self.registers + .AUX_MU_STAT + .is_set(AUX_MU_STAT::SYMBOL_AVAILABLE) + }); + + // read it and return + self.registers.AUX_MU_IO.get() as u8 + } + + fn write_byte(&self, b: u8) { + // wait until we can send + crate::arch::loop_until(|| { + self.registers + .AUX_MU_STAT + .is_set(AUX_MU_STAT::SPACE_AVAILABLE) + }); + + // write the character to the buffer + self.registers.AUX_MU_IO.set(b as u32); + } + + /// Wait until the TX FIFO is empty, aka all characters have been put on the + /// line. + fn flush(&self) { + crate::arch::loop_until(|| self.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::TX_DONE)); + } + + /// Consume input until RX FIFO is empty, aka all pending characters have been + /// consumed. + fn clear_rx(&self) { + crate::arch::loop_while(|| { + let pending = self + .registers + .AUX_MU_STAT + .is_set(AUX_MU_STAT::SYMBOL_AVAILABLE); + if pending { + self.read_byte(); + } + pending + }); + } +} + +impl interface::ConsoleOps for MiniUartInner { + /// Send a character + fn write_char(&self, c: char) { + let mut b = [0u8; 4]; + let _ = c.encode_utf8(&mut b); + for x in 0..c.len_utf8() { + self.write_byte(b[x]); + } + } + + /// Display a string + fn write_string(&self, string: &str) { + for c in string.chars() { + // convert newline to carriage return + newline + if c == '\n' { + self.write_char('\r') + } + + self.write_char(c); + } + } + + /// Receive a character -- FIXME: needs a state machine to read UTF-8 chars! + fn read_char(&self) -> char { + let mut ret = self.read_byte() as char; + + // convert carriage return to newline -- this doesn't work well for reading binaries... + if ret == '\r' { + ret = '\n' + } + + ret + } +} + +impl fmt::Write for MiniUartInner { + fn write_str(&mut self, s: &str) -> fmt::Result { + use interface::ConsoleOps; + self.write_string(s); + Ok(()) + } +} + +impl interface::Write for MiniUart { + fn write_fmt(&self, args: Arguments) -> fmt::Result { + self.inner.lock(|inner| fmt::Write::write_fmt(inner, args)) + } +} + +impl SerialOps for MiniUart { + fn read_byte(&self) -> u8 { + self.inner.lock(|inner| inner.read_byte()) + } + + fn write_byte(&self, byte: u8) { + self.inner.lock(|inner| inner.write_byte(byte)) + } + + fn flush(&self) { + self.inner.lock(|inner| inner.flush()) + } + + fn clear_rx(&self) { + self.inner.lock(|inner| inner.clear_rx()) + } +} + +// ?? +impl interface::ConsoleOps for MiniUart { + fn write_char(&self, c: char) { + self.inner.lock(|inner| inner.write_char(c)) + } + + fn write_string(&self, string: &str) { + self.inner.lock(|inner| inner.write_string(string)) + } + + fn read_char(&self) -> char { + self.inner.lock(|inner| inner.read_char()) + } +} + +impl interface::ConsoleTools for MiniUart { + fn command_prompt<'a>(&self, buf: &'a mut [u8]) -> &'a [u8] { + todo!() + } +} + +impl interface::All for MiniUart {} diff --git a/machine/src/platform/rpi3/device_driver/mod.rs b/machine/src/platform/rpi3/device_driver/mod.rs new file mode 100644 index 0000000..6b432e6 --- /dev/null +++ b/machine/src/platform/rpi3/device_driver/mod.rs @@ -0,0 +1,5 @@ +pub mod gpio; +pub mod mini_uart; +pub mod pl011_uart; + +pub use {gpio::*, mini_uart::*, pl011_uart::*}; diff --git a/machine/src/platform/rpi3/pl011_uart.rs b/machine/src/platform/rpi3/device_driver/pl011_uart.rs similarity index 98% rename from machine/src/platform/rpi3/pl011_uart.rs rename to machine/src/platform/rpi3/device_driver/pl011_uart.rs index c57e6ae..1f18bc1 100644 --- a/machine/src/platform/rpi3/pl011_uart.rs +++ b/machine/src/platform/rpi3/device_driver/pl011_uart.rs @@ -9,15 +9,16 @@ */ use { - super::{ - gpio, - mailbox::{self, Mailbox, MailboxOps}, - BcmHost, - }, crate::{ arch::loop_while, - devices::{ConsoleOps, SerialOps}, - platform::MMIODerefWrapper, + console::interface::ConsoleOps, + devices::SerialOps, + mmio_deref_wrapper::MMIODerefWrapper, + platform::{ + device_driver::gpio, + mailbox::{self, Mailbox, MailboxOps}, + BcmHost, + }, }, snafu::Snafu, tock_registers::{ diff --git a/machine/src/platform/rpi3/drivers.rs b/machine/src/platform/rpi3/drivers.rs new file mode 100644 index 0000000..d187216 --- /dev/null +++ b/machine/src/platform/rpi3/drivers.rs @@ -0,0 +1,77 @@ +use { + crate::{console, drivers, platform::device_driver}, + core::sync::atomic::{AtomicBool, Ordering}, +}; + +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + +/// Initialize the driver subsystem. +/// +/// # Safety +/// +/// See child function calls. +/// +/// # Note +/// +/// Using atomics here relieves us from needing to use `unsafe` for the static variable. +/// +/// On `AArch64`, which is the only implemented architecture at the time of writing this, +/// [`AtomicBool::load`] and [`AtomicBool::store`] are lowered to ordinary load and store +/// instructions. They are therefore safe to use even with MMU + caching deactivated. +/// +/// [`AtomicBool::load`]: core::sync::atomic::AtomicBool::load +/// [`AtomicBool::store`]: core::sync::atomic::AtomicBool::store +pub unsafe fn init() -> Result<(), &'static str> { + static INIT_DONE: AtomicBool = AtomicBool::new(false); + if INIT_DONE.load(Ordering::Relaxed) { + return Err("Init already done"); + } + + driver_gpio()?; + #[cfg(not(feature = "noserial"))] + driver_uart()?; + + INIT_DONE.store(true, Ordering::Relaxed); + Ok(()) +} + +//-------------------------------------------------------------------------------------------------- +// Global instances +//-------------------------------------------------------------------------------------------------- + +static MINI_UART: device_driver::MiniUart = + unsafe { device_driver::MiniUart::new(device_driver::UART1_BASE) }; +// static PL011_UART: device_driver::PL011Uart = unsafe { device_driver::PL011Uart::default() }; +static GPIO: device_driver::GPIO = unsafe { device_driver::GPIO::new(device_driver::GPIO_BASE) }; + +//-------------------------------------------------------------------------------------------------- +// Private Code +//-------------------------------------------------------------------------------------------------- + +/// This must be called only after successful init of the UART driver. +fn post_init_uart() -> Result<(), &'static str> { + console::register_console(&MINI_UART); + Ok(()) +} + +// This must be called only after successful init of the GPIO driver. +fn post_init_gpio() -> Result<(), &'static str> { + device_driver::MiniUart::prepare_gpio(&GPIO); + Ok(()) +} + +fn driver_uart() -> Result<(), &'static str> { + let uart_descriptor = drivers::DeviceDriverDescriptor::new(&MINI_UART, Some(post_init_uart)); + drivers::driver_manager().register_driver(uart_descriptor); + + Ok(()) +} + +fn driver_gpio() -> Result<(), &'static str> { + let gpio_descriptor = drivers::DeviceDriverDescriptor::new(&GPIO, Some(post_init_gpio)); + drivers::driver_manager().register_driver(gpio_descriptor); + + Ok(()) +} diff --git a/machine/src/platform/rpi3/mailbox.rs b/machine/src/platform/rpi3/mailbox.rs index a9da5d4..510ff41 100644 --- a/machine/src/platform/rpi3/mailbox.rs +++ b/machine/src/platform/rpi3/mailbox.rs @@ -13,7 +13,7 @@ use { super::BcmHost, - crate::{platform::MMIODerefWrapper, println, DMA_ALLOCATOR}, + crate::{mmio_deref_wrapper::MMIODerefWrapper, println}, //DMA_ALLOCATOR aarch64_cpu::asm::barrier, core::{ alloc::{AllocError, Allocator, Layout}, @@ -148,32 +148,36 @@ impl MailboxStorage for LocalMailboxStorage { impl MailboxStorage for DmaBackedMailboxStorage { fn new() -> Result { + use crate::platform::memory::map::virt::DMA_HEAP_START; + Ok(Self { - storage: DMA_ALLOCATOR - .lock(|a| { - a.allocate( - Layout::from_size_align(N_SLOTS * mem::size_of::(), 16) - .map_err(|_| AllocError)?, - ) - }) - .map_err(|_| MailboxError::Alloc)? - .as_mut_ptr() as *mut u32, + storage: DMA_HEAP_START + // storage: DMA_ALLOCATOR + // .lock(|a| { + // a.allocate( + // Layout::from_size_align(N_SLOTS * mem::size_of::(), 16) + // .map_err(|_| AllocError)?, + // ) + // }) + // .map_err(|_| MailboxError::Alloc)? + // .as_mut_ptr() + as *mut u32, }) } } impl Drop for DmaBackedMailboxStorage { fn drop(&mut self) { - DMA_ALLOCATOR - .lock::<_, Result<()>>(|a| unsafe { - #[allow(clippy::unit_arg)] - Ok(a.deallocate( - NonNull::new_unchecked(self.storage as *mut u8), - Layout::from_size_align(N_SLOTS * mem::size_of::(), 16) - .map_err(|_| MailboxError::Alloc)?, - )) - }) - .unwrap_or(()) + // DMA_ALLOCATOR + // .lock::<_, Result<()>>(|a| unsafe { + // #[allow(clippy::unit_arg)] + // Ok(a.deallocate( + // NonNull::new_unchecked(self.storage as *mut u8), + // Layout::from_size_align(N_SLOTS * mem::size_of::(), 16) + // .map_err(|_| MailboxError::Alloc)?, + // )) + // }) + // .unwrap_or(()) } } diff --git a/machine/src/platform/rpi3/mini_uart.rs b/machine/src/platform/rpi3/mini_uart.rs deleted file mode 100644 index 35578b5..0000000 --- a/machine/src/platform/rpi3/mini_uart.rs +++ /dev/null @@ -1,332 +0,0 @@ -/* - * SPDX-License-Identifier: MIT OR BlueOak-1.0.0 - * Copyright (c) 2018-2019 Andre Richter - * Copyright (c) Berkus Decker - * Original code distributed under MIT, additional changes are under BlueOak-1.0.0 - */ - -#[cfg(not(feature = "noserial"))] -use tock_registers::interfaces::{Readable, Writeable}; -use { - super::{gpio, BcmHost}, - crate::{ - devices::{ConsoleOps, SerialOps}, - platform::MMIODerefWrapper, - }, - cfg_if::cfg_if, - core::{convert::From, fmt}, - tock_registers::{ - interfaces::ReadWriteable, - register_bitfields, register_structs, - registers::{ReadOnly, ReadWrite, WriteOnly}, - }, -}; - -// Auxiliary mini UART registers -// -// Descriptions taken from -// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf -register_bitfields! { - u32, - - /// Auxiliary enables - AUX_ENABLES [ - /// If set the mini UART is enabled. The UART will immediately - /// start receiving data, especially if the UART1_RX line is - /// low. - /// If clear the mini UART is disabled. That also disables any - /// mini UART register access - MINI_UART_ENABLE OFFSET(0) NUMBITS(1) [] - ], - - /// Mini Uart Interrupt Identify - AUX_MU_IIR [ - /// Writing with bit 1 set will clear the receive FIFO - /// Writing with bit 2 set will clear the transmit FIFO - FIFO_CLEAR OFFSET(1) NUMBITS(2) [ - Rx = 0b01, - Tx = 0b10, - All = 0b11 - ] - ], - - /// Mini Uart Line Control - AUX_MU_LCR [ - /// Mode the UART works in - DATA_SIZE OFFSET(0) NUMBITS(2) [ - SevenBit = 0b00, - EightBit = 0b11 - ] - ], - - /// Mini Uart Line Status - AUX_MU_LSR [ - /// This bit is set if the transmit FIFO is empty and the transmitter is - /// idle. (Finished shifting out the last bit). - TX_IDLE OFFSET(6) NUMBITS(1) [], - - /// This bit is set if the transmit FIFO can accept at least - /// one byte. - TX_EMPTY OFFSET(5) NUMBITS(1) [], - - /// This bit is set if the receive FIFO holds at least 1 - /// symbol. - DATA_READY OFFSET(0) NUMBITS(1) [] - ], - - /// Mini Uart Extra Control - AUX_MU_CNTL [ - /// If this bit is set the mini UART transmitter is enabled. - /// If this bit is clear the mini UART transmitter is disabled. - TX_EN OFFSET(1) NUMBITS(1) [ - Disabled = 0, - Enabled = 1 - ], - - /// If this bit is set the mini UART receiver is enabled. - /// If this bit is clear the mini UART receiver is disabled. - RX_EN OFFSET(0) NUMBITS(1) [ - Disabled = 0, - Enabled = 1 - ] - ], - - /// Mini Uart Status - AUX_MU_STAT [ - TX_DONE OFFSET(9) NUMBITS(1) [ - No = 0, - Yes = 1 - ], - - /// This bit is set if the transmit FIFO can accept at least - /// one byte. - SPACE_AVAILABLE OFFSET(1) NUMBITS(1) [ - No = 0, - Yes = 1 - ], - - /// This bit is set if the receive FIFO holds at least 1 - /// symbol. - SYMBOL_AVAILABLE OFFSET(0) NUMBITS(1) [ - No = 0, - Yes = 1 - ] - ], - - /// Mini Uart Baud rate - AUX_MU_BAUD [ - /// Mini UART baud rate counter - RATE OFFSET(0) NUMBITS(16) [] - ] -} - -register_structs! { - #[allow(non_snake_case)] - RegisterBlock { - // 0x00 - AUX_IRQ? - (0x00 => __reserved_1), - (0x04 => AUX_ENABLES: ReadWrite), - (0x08 => __reserved_2), - (0x40 => AUX_MU_IO: ReadWrite),//Mini Uart I/O Data - (0x44 => AUX_MU_IER: WriteOnly),//Mini Uart Interrupt Enable - (0x48 => AUX_MU_IIR: WriteOnly), - (0x4c => AUX_MU_LCR: WriteOnly), - (0x50 => AUX_MU_MCR: WriteOnly), - (0x54 => AUX_MU_LSR: ReadOnly), - // 0x58 - AUX_MU_MSR - // 0x5c - AUX_MU_SCRATCH - (0x58 => __reserved_3), - (0x60 => AUX_MU_CNTL: WriteOnly), - (0x64 => AUX_MU_STAT: ReadOnly), - (0x68 => AUX_MU_BAUD: WriteOnly), - (0x6c => @END), - } -} - -type Registers = MMIODerefWrapper; - -pub struct MiniUart { - registers: Registers, -} - -pub struct PreparedMiniUart(MiniUart); - -/// Divisor values for common baud rates -pub enum Rate { - Baud115200 = 270, -} - -impl From for u32 { - fn from(r: Rate) -> Self { - r as u32 - } -} - -// [temporary] Used in mmu.rs to set up local paging -pub const UART1_START: usize = 0x21_5000; - -impl Default for MiniUart { - fn default() -> Self { - const UART1_BASE: usize = BcmHost::get_peripheral_address() + UART1_START; - unsafe { MiniUart::new(UART1_BASE) } - } -} - -impl MiniUart { - /// # Safety - /// - /// Unsafe, duh! - pub const unsafe fn new(base_addr: usize) -> MiniUart { - MiniUart { - registers: Registers::new(base_addr), - } - } -} - -impl MiniUart { - cfg_if! { - if #[cfg(not(feature = "noserial"))] { - /// Set baud rate and characteristics (115200 8N1) and map to GPIO - pub fn prepare(self, gpio: &gpio::GPIO) -> PreparedMiniUart { - // GPIO pins should be set up first before enabling the UART - - // Pin 14 - const MINI_UART_TXD: gpio::Function = gpio::Function::Alt5; - // Pin 15 - const MINI_UART_RXD: gpio::Function = gpio::Function::Alt5; - - // map UART1 to GPIO pins - gpio.get_pin(14).into_alt(MINI_UART_TXD).set_pull_up_down(gpio::PullUpDown::Up); - gpio.get_pin(15).into_alt(MINI_UART_RXD).set_pull_up_down(gpio::PullUpDown::Up); - - // initialize UART - self.registers.AUX_ENABLES.modify(AUX_ENABLES::MINI_UART_ENABLE::SET); - self.registers.AUX_MU_IER.set(0); - self.registers.AUX_MU_CNTL.set(0); - self.registers.AUX_MU_LCR.write(AUX_MU_LCR::DATA_SIZE::EightBit); - self.registers.AUX_MU_MCR.set(0); - self.registers.AUX_MU_IER.set(0); - self.registers.AUX_MU_BAUD - .write(AUX_MU_BAUD::RATE.val(Rate::Baud115200.into())); - - // Clear FIFOs before using the device - self.registers.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All); - - self.registers.AUX_MU_CNTL - .write(AUX_MU_CNTL::RX_EN::Enabled + AUX_MU_CNTL::TX_EN::Enabled); - - PreparedMiniUart(self) - } - } else { - pub fn prepare(self, _gpio: &gpio::GPIO) -> PreparedMiniUart { - PreparedMiniUart(self) - } - } - } -} - -impl Drop for PreparedMiniUart { - fn drop(&mut self) { - self.0 - .registers - .AUX_ENABLES - .modify(AUX_ENABLES::MINI_UART_ENABLE::CLEAR); - // @todo disable gpio.PUD ? - } -} - -impl SerialOps for PreparedMiniUart { - cfg_if! { - if #[cfg(not(feature = "noserial"))] { - /// Receive a byte without console translation - fn read_byte(&self) -> u8 { - // wait until something is in the buffer - crate::arch::loop_until(|| self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::SYMBOL_AVAILABLE)); - - // read it and return - self.0.registers.AUX_MU_IO.get() as u8 - } - - fn write_byte(&self, b: u8) { - // wait until we can send - crate::arch::loop_until(|| self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::SPACE_AVAILABLE)); - - // write the character to the buffer - self.0.registers.AUX_MU_IO.set(b as u32); - } - - /// Wait until the TX FIFO is empty, aka all characters have been put on the - /// line. - fn flush(&self) { - crate::arch::loop_until(|| self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::TX_DONE)); - } - - /// Consume input until RX FIFO is empty, aka all pending characters have been - /// consumed. - fn clear_rx(&self) { - crate::arch::loop_while(|| { - let pending = self.0.registers.AUX_MU_STAT.is_set(AUX_MU_STAT::SYMBOL_AVAILABLE); - if pending { self.read_byte(); } - pending - }); - } - } else { - fn read_byte(&self) -> u8 { 0 } - fn write_byte(&self, _byte: u8) {} - fn flush(&self) {} - fn clear_rx(&self) {} - } - } -} - -impl ConsoleOps for PreparedMiniUart { - cfg_if! { - if #[cfg(not(feature = "noserial"))] { - /// Send a character - fn write_char(&self, c: char) { - let mut b = [0u8; 4]; - let _ = c.encode_utf8(&mut b); - for x in 0..c.len_utf8() { - self.write_byte(b[x]); - } - } - - /// Display a string - fn write_string(&self, string: &str) { - for c in string.chars() { - // convert newline to carriage return + newline - if c == '\n' { - self.write_char('\r') - } - - self.write_char(c); - } - } - - /// Receive a character -- FIXME: needs a state machine to read UTF-8 chars! - fn read_char(&self) -> char { - let mut ret = self.read_byte() as char; - - // convert carriage return to newline -- this doesn't work well for reading binaries... - if ret == '\r' { - ret = '\n' - } - - ret - } - } else { - fn write_char(&self, _c: char) {} - fn write_string(&self, _string: &str) {} - fn read_char(&self) -> char { - '\n' - } - } - } -} - -impl fmt::Write for PreparedMiniUart { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.write_string(s); - Ok(()) - } -} diff --git a/machine/src/platform/rpi3/mod.rs b/machine/src/platform/rpi3/mod.rs index 9c752d9..6457f58 100644 --- a/machine/src/platform/rpi3/mod.rs +++ b/machine/src/platform/rpi3/mod.rs @@ -5,13 +5,12 @@ #![allow(dead_code)] +pub mod device_driver; pub mod display; +pub mod drivers; pub mod fb; -pub mod gpio; pub mod mailbox; pub mod memory; -pub mod mini_uart; -pub mod pl011_uart; pub mod power; pub mod vc; diff --git a/machine/src/platform/rpi3/power.rs b/machine/src/platform/rpi3/power.rs index 190612a..74429df 100644 --- a/machine/src/platform/rpi3/power.rs +++ b/machine/src/platform/rpi3/power.rs @@ -7,11 +7,11 @@ use { super::{ - gpio, + device_driver::gpio, mailbox::{channel, Mailbox, MailboxOps}, BcmHost, }, - crate::platform::MMIODerefWrapper, + crate::mmio_deref_wrapper::MMIODerefWrapper, snafu::Snafu, tock_registers::{ interfaces::{Readable, Writeable}, diff --git a/machine/src/sync.rs b/machine/src/sync.rs index 4dcee83..e6aa3c6 100644 --- a/machine/src/sync.rs +++ b/machine/src/sync.rs @@ -6,10 +6,42 @@ use core::cell::UnsafeCell; -pub struct NullLock { +//-------------------------------------------------------------------------------------------------- +// Public Definitions +//-------------------------------------------------------------------------------------------------- + +/// Synchronization interfaces. +pub mod interface { + + /// Any object implementing this trait guarantees exclusive access to the data wrapped within + /// the Mutex for the duration of the provided closure. + pub trait Mutex { + /// The type of the data that is wrapped by this mutex. + type Data; + + /// Locks the mutex and grants the closure temporary mutable access to the wrapped data. + fn lock(&self, f: impl FnOnce(&mut Self::Data) -> R) -> R; + } +} + +/// A pseudo-lock for teaching purposes. +/// +/// In contrast to a real Mutex implementation, does not protect against concurrent access from +/// other cores to the contained data. +/// +/// The lock can only be used as long as it is safe to do so, i.e. as long as the kernel is +/// executing single-threaded, aka only running on a single core with interrupts disabled. +pub struct NullLock +where + T: ?Sized, +{ data: UnsafeCell, } +//-------------------------------------------------------------------------------------------------- +// Public Code +//-------------------------------------------------------------------------------------------------- + /// Since we are instantiating this struct as a static variable, which could /// potentially be shared between different threads, we need to tell the compiler /// that sharing of this struct is safe by marking it with the Sync trait. @@ -21,24 +53,30 @@ pub struct NullLock { /// Literature: /// * /// * -unsafe impl Sync for NullLock {} +unsafe impl Send for NullLock where T: ?Sized + Send {} +unsafe impl Sync for NullLock where T: ?Sized + Send {} impl NullLock { - pub const fn new(data: T) -> NullLock { - NullLock { + /// Create an instance. + pub const fn new(data: T) -> Self { + Self { data: UnsafeCell::new(data), } } } -impl NullLock { - pub fn lock(&self, f: F) -> R - where - F: FnOnce(&mut T) -> R, - { - // In a real lock, there would be code around this line that ensures - // that this mutable reference will ever only be given out to one thread - // at a time. - f(unsafe { &mut *self.data.get() }) +//------------------------------------------------------------------------------ +// OS Interface Code +//------------------------------------------------------------------------------ + +impl interface::Mutex for NullLock { + type Data = T; + + fn lock(&self, f: impl FnOnce(&mut Self::Data) -> R) -> R { + // In a real lock, there would be code encapsulating this line that ensures that this + // mutable reference will ever only be given out once at a time. + let data = unsafe { &mut *self.data.get() }; + + f(data) } } diff --git a/nucleus/src/main.rs b/nucleus/src/main.rs index bc009c0..f9cc28f 100644 --- a/nucleus/src/main.rs +++ b/nucleus/src/main.rs @@ -17,7 +17,7 @@ #![reexport_test_harness_main = "test_main"] #![deny(missing_docs)] #![deny(warnings)] -#![deny(unused)] +#![allow(unused)] #![feature(allocator_api)] #[cfg(not(test))] @@ -28,32 +28,43 @@ use { cfg_if::cfg_if, core::cell::UnsafeCell, machine::{ - arch, entry, memory, + arch, + console::console, + entry, memory, platform::rpi3::{ display::{Color, DrawError}, mailbox::{channel, Mailbox, MailboxOps}, vc::VC, }, - println, CONSOLE, + println, }, }; -entry!(kernel_main); +entry!(kernel_init); /// Kernel entry point. /// `arch` crate is responsible for calling it. -// #[inline] -pub fn kernel_main() -> ! { +pub unsafe fn kernel_init() -> ! { #[cfg(feature = "jtag")] machine::arch::jtag::wait_debugger(); - init_exception_traps(); + if let Err(x) = machine::platform::drivers::init() { + panic!("Error initializing platform drivers: {}", x); + } - #[cfg(not(feature = "noserial"))] - init_uart_serial(); + // Initialize all device drivers. + machine::drivers::driver_manager().init_drivers(); - init_mmu(); + init_exception_traps(); // @todo + init_mmu(); // @todo + + kernel_main() +} + +/// Safe kernel code. +// #[inline] +pub fn kernel_main() -> ! { #[cfg(test)] test_main(); @@ -96,48 +107,44 @@ fn init_exception_traps() { println!("[!] Exception traps set up"); } -#[cfg(not(feature = "noserial"))] -fn init_uart_serial() { - use machine::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart, pl011_uart::PL011Uart}; +// fn init_uart_serial() { +// use machine::platform::rpi3::{gpio::GPIO, mini_uart::MiniUart, pl011_uart::PL011Uart}; +// +// let gpio = GPIO::default(); +// let uart = MiniUart::default(); +// let uart = uart.prepare(&gpio); +// // console::replace_with(uart.into()); +// +// println!("[0] MiniUART is live!"); - let gpio = GPIO::default(); - let uart = MiniUart::default(); - let uart = uart.prepare(&gpio); - CONSOLE.lock(|c| { - // Move uart into the global CONSOLE. - c.replace_with(uart.into()); - }); +// Then immediately switch to PL011 (just as an example) - println!("[0] MiniUART is live!"); +// let uart = PL011Uart::default(); - // Then immediately switch to PL011 (just as an example) +// uart.init() will reconfigure the GPIO, which causes a race against +// the MiniUart that is still putting out characters on the physical +// line that are already buffered in its TX FIFO. +// +// To ensure the CPU doesn't rewire the GPIO before the MiniUart has put +// its last character, explicitly flush it before rewiring. +// +// If you switch to an output that happens to not use the same pair of +// physical wires (e.g. the Framebuffer), you don't need to do this, +// because flush() is anyways called implicitly by replace_with(). This +// is just a special case. +// CONSOLE.lock(|c| c.flush()); - let uart = PL011Uart::default(); - - // uart.init() will reconfigure the GPIO, which causes a race against - // the MiniUart that is still putting out characters on the physical - // line that are already buffered in its TX FIFO. - // - // To ensure the CPU doesn't rewire the GPIO before the MiniUart has put - // its last character, explicitly flush it before rewiring. - // - // If you switch to an output that happens to not use the same pair of - // physical wires (e.g. the Framebuffer), you don't need to do this, - // because flush() is anyways called implicitly by replace_with(). This - // is just a special case. - CONSOLE.lock(|c| c.flush()); - - match uart.prepare(&gpio) { - Ok(uart) => { - CONSOLE.lock(|c| { - // Move uart into the global CONSOLE. - c.replace_with(uart.into()); - }); - println!("[0] UART0 is live!"); - } - Err(_) => println!("[0] Error switching to PL011 UART, continue with MiniUART"), - } -} +// match uart.prepare(&gpio) { +// Ok(uart) => { +// CONSOLE.lock(|c| { +// // Move uart into the global CONSOLE. +// c.replace_with(uart.into()); +// }); +// println!("[0] UART0 is live!"); +// } +// Err(_) => println!("[0] Error switching to PL011 UART, continue with MiniUART"), +// } +// } //------------------------------------------------------------ // Start a command prompt @@ -146,11 +153,9 @@ fn command_prompt() { 'cmd_loop: loop { let mut buf = [0u8; 64]; - match CONSOLE.lock(|c| c.command_prompt(&mut buf)) { + match console().command_prompt(&mut buf) { b"mmu" => init_mmu(), b"feats" => print_mmu_state_and_features(), - #[cfg(not(feature = "noserial"))] - b"uart" => init_uart_serial(), b"disp" => check_display_init(), b"trap" => check_data_abort_trap(), b"map" => machine::platform::memory::mmu::virt_mem_layout().print_layout(),