You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Andre Richter 53f6b02787
Remove obsolete #[no_mange]
5 years ago
..
.vscode Tiny rustfmt and vscode changes 5 years ago
src Remove obsolete #[no_mange] 5 years ago
.rustfmt.toml Tiny rustfmt and vscode changes 5 years ago
Cargo.lock Add code for tutorial 06 5 years ago
Cargo.toml Add code for tutorial 06 5 years ago
Makefile Add code for tutorial 06 5 years ago
README.md Add write_char() function early 5 years ago
kernel Remove obsolete #[no_mange] 5 years ago
kernel8.img Add write_char() function early 5 years ago

README.md

Tutorial 06 - Drivers: GPIO and UART

tl;dr

Now that we enabled safe globals in the previous tutorial, the infrastructure is laid for adding the first real device drivers. We throw out the magic QEMU console and use a real UART now. Like real tough embedded people do!

  • A DeviceDriver trait is added for abstracting BSP driver implementations from kernel code.
  • Drivers are stored in bsp/driver, and can be reused between BSPs.
    • Introducing the GPIO driver, which pinmuxes the RPi's Mini UART.
    • Most importantly, the MiniUart driver: It implements the Console traits and is from now on used as the system console output.
  • BSPs now contain amemory_map.rs. In the specific case, they contain the RPi's MMIO addresses which are used to instantiate compatible device drivers from bsp/driver.

Boot it from SD card

  1. Make a single FAT32 partition named boot.
  2. Copy bootcode.bin, fixup.dat and start.elf from the Raspberry Pi firmware repo onto the SD card.
  3. Copy our kernel8.img onto the SD card.
  4. Insert the SD card into the RPi and connect the USB serial to your host PC.
  5. Run screen (you might need to install it first):
sudo screen /dev/ttyUSB0 115200
  1. Hit Enter to kick off the kernel boot process.
  2. Exit screen by pressing ctrl-a ctrl-d or disconnecting the USB serial.

Diff to previous


diff -uNr 05_safe_globals/Cargo.toml 06_drivers_gpio_uart/Cargo.toml
--- 05_safe_globals/Cargo.toml
+++ 06_drivers_gpio_uart/Cargo.toml
@@ -10,10 +10,11 @@
 # The features section is used to select the target board.
 [features]
 default = []
-bsp_rpi3 = ["cortex-a"]
+bsp_rpi3 = ["cortex-a", "register"]

 [dependencies]
 r0 = "0.2.*"

 # Optional dependencies
 cortex-a = { version = "2.*", optional = true }
+register = { version = "0.3.*", optional = true }

diff -uNr 05_safe_globals/src/arch/aarch64.rs 06_drivers_gpio_uart/src/arch/aarch64.rs
--- 05_safe_globals/src/arch/aarch64.rs
+++ 06_drivers_gpio_uart/src/arch/aarch64.rs
@@ -34,6 +34,8 @@
 // Implementation of the kernel's architecture abstraction code
 ////////////////////////////////////////////////////////////////////////////////

+pub use asm::nop;
+
 /// Pause execution on the calling CPU core.
 #[inline(always)]
 pub fn wait_forever() -> ! {

diff -uNr 05_safe_globals/src/bsp/driver/bcm/bcm2837_gpio.rs 06_drivers_gpio_uart/src/bsp/driver/bcm/bcm2837_gpio.rs
--- 05_safe_globals/src/bsp/driver/bcm/bcm2837_gpio.rs
+++ 06_drivers_gpio_uart/src/bsp/driver/bcm/bcm2837_gpio.rs
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
+
+//! GPIO driver.
+
+use crate::{arch, arch::sync::NullLock, interface};
+use core::ops;
+use register::{mmio::ReadWrite, register_bitfields};
+
+// GPIO registers.
+//
+// Descriptions taken from
+// https://github.com/raspberrypi/documentation/files/1888662/BCM2837-ARM-Peripherals.-.Revised.-.V2-1.pdf
+register_bitfields! {
+    u32,
+
+    /// GPIO Function Select 1
+    GPFSEL1 [
+        /// Pin 15
+        FSEL15 OFFSET(15) NUMBITS(3) [
+            Input = 0b000,
+            Output = 0b001,
+            AltFunc5 = 0b010  // Mini UART RX
+
+        ],
+
+        /// Pin 14
+        FSEL14 OFFSET(12) NUMBITS(3) [
+            Input = 0b000,
+            Output = 0b001,
+            AltFunc5 = 0b010  // Mini UART TX
+        ]
+    ],
+
+    /// GPIO Pull-up/down Clock Register 0
+    GPPUDCLK0 [
+        /// Pin 15
+        PUDCLK15 OFFSET(15) NUMBITS(1) [
+            NoEffect = 0,
+            AssertClock = 1
+        ],
+
+        /// Pin 14
+        PUDCLK14 OFFSET(14) NUMBITS(1) [
+            NoEffect = 0,
+            AssertClock = 1
+        ]
+    ]
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+pub struct RegisterBlock {
+    pub GPFSEL0: ReadWrite<u32>,                        // 0x00
+    pub GPFSEL1: ReadWrite<u32, GPFSEL1::Register>,     // 0x04
+    pub GPFSEL2: ReadWrite<u32>,                        // 0x08
+    pub GPFSEL3: ReadWrite<u32>,                        // 0x0C
+    pub GPFSEL4: ReadWrite<u32>,                        // 0x10
+    pub GPFSEL5: ReadWrite<u32>,                        // 0x14
+    __reserved_0: u32,                                  // 0x18
+    GPSET0: ReadWrite<u32>,                             // 0x1C
+    GPSET1: ReadWrite<u32>,                             // 0x20
+    __reserved_1: u32,                                  //
+    GPCLR0: ReadWrite<u32>,                             // 0x28
+    __reserved_2: [u32; 2],                             //
+    GPLEV0: ReadWrite<u32>,                             // 0x34
+    GPLEV1: ReadWrite<u32>,                             // 0x38
+    __reserved_3: u32,                                  //
+    GPEDS0: ReadWrite<u32>,                             // 0x40
+    GPEDS1: ReadWrite<u32>,                             // 0x44
+    __reserved_4: [u32; 7],                             //
+    GPHEN0: ReadWrite<u32>,                             // 0x64
+    GPHEN1: ReadWrite<u32>,                             // 0x68
+    __reserved_5: [u32; 10],                            //
+    pub GPPUD: ReadWrite<u32>,                          // 0x94
+    pub GPPUDCLK0: ReadWrite<u32, GPPUDCLK0::Register>, // 0x98
+    pub GPPUDCLK1: ReadWrite<u32>,                      // 0x9C
+}
+
+/// The driver's private data.
+struct GPIOInner {
+    base_addr: usize,
+}
+
+/// Deref to RegisterBlock.
+impl ops::Deref for GPIOInner {
+    type Target = RegisterBlock;
+
+    fn deref(&self) -> &Self::Target {
+        unsafe { &*self.ptr() }
+    }
+}
+
+impl GPIOInner {
+    const fn new(base_addr: usize) -> GPIOInner {
+        GPIOInner { base_addr }
+    }
+
+    /// Return a pointer to the register block.
+    fn ptr(&self) -> *const RegisterBlock {
+        self.base_addr as *const _
+    }
+
+    /// Map Mini UART as standard output.
+    ///
+    /// TX to pin 14
+    /// RX to pin 15
+    pub fn map_mini_uart(&mut self) {
+        // Map to pins.
+        self.GPFSEL1
+            .modify(GPFSEL1::FSEL14::AltFunc5 + GPFSEL1::FSEL15::AltFunc5);
+
+        // Enable pins 14 and 15.
+        self.GPPUD.set(0);
+        for _ in 0..150 {
+            arch::nop();
+        }
+
+        self.GPPUDCLK0
+            .write(GPPUDCLK0::PUDCLK14::AssertClock + GPPUDCLK0::PUDCLK15::AssertClock);
+        for _ in 0..150 {
+            arch::nop();
+        }
+
+        self.GPPUDCLK0.set(0);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BSP-public
+////////////////////////////////////////////////////////////////////////////////
+use interface::sync::Mutex;
+
+/// The driver's main struct.
+pub struct GPIO {
+    inner: NullLock<GPIOInner>,
+}
+
+impl GPIO {
+    pub const unsafe fn new(base_addr: usize) -> GPIO {
+        GPIO {
+            inner: NullLock::new(GPIOInner::new(base_addr)),
+        }
+    }
+
+    // Only visible to other BSP code.
+    pub fn map_mini_uart(&self) {
+        let mut r = &self.inner;
+        r.lock(|inner| inner.map_mini_uart());
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OS interface implementations
+////////////////////////////////////////////////////////////////////////////////
+
+impl interface::driver::DeviceDriver for GPIO {
+    fn compatible(&self) -> &str {
+        "GPIO"
+    }
+}

diff -uNr 05_safe_globals/src/bsp/driver/bcm/bcm2xxx_mini_uart.rs 06_drivers_gpio_uart/src/bsp/driver/bcm/bcm2xxx_mini_uart.rs
--- 05_safe_globals/src/bsp/driver/bcm/bcm2xxx_mini_uart.rs
+++ 06_drivers_gpio_uart/src/bsp/driver/bcm/bcm2xxx_mini_uart.rs
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
+
+//! Mini UART driver.
+
+use crate::{arch, arch::sync::NullLock, interface};
+use core::{fmt, ops};
+use register::{mmio::*, register_bitfields};
+
+// 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 Baudrate
+    AUX_MU_BAUD [
+        /// Mini UART baudrate counter
+        RATE OFFSET(0) NUMBITS(16) []
+    ]
+}
+
+#[allow(non_snake_case)]
+#[repr(C)]
+pub struct RegisterBlock {
+    __reserved_0: u32,                                  // 0x00
+    AUX_ENABLES: ReadWrite<u32, AUX_ENABLES::Register>, // 0x04
+    __reserved_1: [u32; 14],                            // 0x08
+    AUX_MU_IO: ReadWrite<u32>,                          // 0x40 - Mini Uart I/O Data
+    AUX_MU_IER: WriteOnly<u32>,                         // 0x44 - Mini Uart Interrupt Enable
+    AUX_MU_IIR: WriteOnly<u32, AUX_MU_IIR::Register>,   // 0x48
+    AUX_MU_LCR: WriteOnly<u32, AUX_MU_LCR::Register>,   // 0x4C
+    AUX_MU_MCR: WriteOnly<u32>,                         // 0x50
+    AUX_MU_LSR: ReadOnly<u32, AUX_MU_LSR::Register>,    // 0x54
+    __reserved_2: [u32; 2],                             // 0x58
+    AUX_MU_CNTL: WriteOnly<u32, AUX_MU_CNTL::Register>, // 0x60
+    __reserved_3: u32,                                  // 0x64
+    AUX_MU_BAUD: WriteOnly<u32, AUX_MU_BAUD::Register>, // 0x68
+}
+
+/// The driver's mutex protected part.
+struct MiniUartInner {
+    base_addr: usize,
+    chars_written: usize,
+}
+
+/// Deref to RegisterBlock.
+///
+/// Allows writing
+/// ```
+/// self.MU_IER.read()
+/// ```
+/// instead of something along the lines of
+/// ```
+/// unsafe { (*MiniUart::ptr()).MU_IER.read() }
+/// ```
+impl ops::Deref for MiniUartInner {
+    type Target = RegisterBlock;
+
+    fn deref(&self) -> &Self::Target {
+        unsafe { &*self.ptr() }
+    }
+}
+
+impl MiniUartInner {
+    const fn new(base_addr: usize) -> MiniUartInner {
+        MiniUartInner {
+            base_addr,
+            chars_written: 0,
+        }
+    }
+
+    /// Return a pointer to the register block.
+    fn ptr(&self) -> *const RegisterBlock {
+        self.base_addr as *const _
+    }
+
+    /// Send a character.
+    fn write_char(&mut self, c: char) {
+        // Wait until we can send.
+        loop {
+            if self.AUX_MU_LSR.is_set(AUX_MU_LSR::TX_EMPTY) {
+                break;
+            }
+
+            arch::nop();
+        }
+
+        // Write the character to the buffer.
+        self.AUX_MU_IO.set(c as u32);
+    }
+}
+
+/// 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.
+///
+/// The function takes an `&mut self`, so it must be implemented for the inner
+/// struct.
+///
+/// See [`src/print.rs`].
+///
+/// [`src/print.rs`]: ../../print/index.html
+impl fmt::Write for MiniUartInner {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        for c in s.chars() {
+            // Convert newline to carrige return + newline.
+            if c == '
' {
+                self.write_char('
')
+            }
+
+            self.write_char(c);
+        }
+
+        self.chars_written += s.len();
+
+        Ok(())
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BSP-public
+////////////////////////////////////////////////////////////////////////////////
+
+/// The driver's main struct.
+pub struct MiniUart {
+    inner: NullLock<MiniUartInner>,
+}
+
+impl MiniUart {
+    /// # Safety
+    ///
+    /// The user must ensure to provide the correct `base_addr`.
+    pub const unsafe fn new(base_addr: usize) -> MiniUart {
+        MiniUart {
+            inner: NullLock::new(MiniUartInner::new(base_addr)),
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OS interface implementations
+////////////////////////////////////////////////////////////////////////////////
+use interface::sync::Mutex;
+
+impl interface::driver::DeviceDriver for MiniUart {
+    fn compatible(&self) -> &str {
+        "MiniUart"
+    }
+
+    /// Set up baud rate and characteristics (115200 8N1).
+    fn init(&self) -> interface::driver::Result {
+        let mut r = &self.inner;
+        r.lock(|inner| {
+            // Enable register access to the MiniUart
+            inner.AUX_ENABLES.modify(AUX_ENABLES::MINI_UART_ENABLE::SET);
+            inner.AUX_MU_IER.set(0); // disable RX and TX interrupts
+            inner.AUX_MU_CNTL.set(0); // disable send and receive
+            inner.AUX_MU_LCR.write(AUX_MU_LCR::DATA_SIZE::EightBit);
+            inner.AUX_MU_BAUD.write(AUX_MU_BAUD::RATE.val(270)); // 115200 baud
+            inner.AUX_MU_MCR.set(0); // set "ready to send" high
+
+            // Clear FIFOs before using the device.
+            inner.AUX_MU_IIR.write(AUX_MU_IIR::FIFO_CLEAR::All);
+
+            // Enable receive and send.
+            inner
+                .AUX_MU_CNTL
+                .write(AUX_MU_CNTL::RX_EN::Enabled + AUX_MU_CNTL::TX_EN::Enabled);
+        });
+
+        Ok(())
+    }
+}
+
+/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded
+/// by a Mutex to serialize access.
+impl interface::console::Write for MiniUart {
+    fn write_char(&self, c: char) {
+        let mut r = &self.inner;
+        r.lock(|inner| inner.write_char(c));
+    }
+
+    fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result {
+        // Fully qualified syntax for the call to
+        // `core::fmt::Write::write:fmt()` to increase readability.
+        let mut r = &self.inner;
+        r.lock(|inner| fmt::Write::write_fmt(inner, args))
+    }
+}
+
+impl interface::console::Read for MiniUart {
+    fn read_char(&self) -> char {
+        let mut r = &self.inner;
+        r.lock(|inner| {
+            // Wait until buffer is is filled.
+            loop {
+                if inner.AUX_MU_LSR.is_set(AUX_MU_LSR::DATA_READY) {
+                    break;
+                }
+
+                arch::nop();
+            }
+
+            // Read one character.
+            let mut ret = inner.AUX_MU_IO.get() as u8 as char;
+
+            // Convert carrige return to newline.
+            if ret == '
' {
+                ret = '
'
+            }
+
+            ret
+        })
+    }
+}
+
+impl interface::console::Statistics for MiniUart {
+    fn chars_written(&self) -> usize {
+        let mut r = &self.inner;
+        r.lock(|inner| inner.chars_written)
+    }
+}

diff -uNr 05_safe_globals/src/bsp/driver/bcm.rs 06_drivers_gpio_uart/src/bsp/driver/bcm.rs
--- 05_safe_globals/src/bsp/driver/bcm.rs
+++ 06_drivers_gpio_uart/src/bsp/driver/bcm.rs
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
+
+//! BCM driver top level.
+
+mod bcm2837_gpio;
+mod bcm2xxx_mini_uart;
+
+pub use bcm2837_gpio::GPIO;
+pub use bcm2xxx_mini_uart::MiniUart;

diff -uNr 05_safe_globals/src/bsp/driver.rs 06_drivers_gpio_uart/src/bsp/driver.rs
--- 05_safe_globals/src/bsp/driver.rs
+++ 06_drivers_gpio_uart/src/bsp/driver.rs
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
+
+//! Drivers.
+
+#[cfg(feature = "bsp_rpi3")]
+mod bcm;
+
+#[cfg(feature = "bsp_rpi3")]
+pub use bcm::*;

diff -uNr 05_safe_globals/src/bsp/rpi3/memory_map.rs 06_drivers_gpio_uart/src/bsp/rpi3/memory_map.rs
--- 05_safe_globals/src/bsp/rpi3/memory_map.rs
+++ 06_drivers_gpio_uart/src/bsp/rpi3/memory_map.rs
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: MIT
+//
+// Copyright (c) 2018-2019 Andre Richter <andre.o.richter@gmail.com>
+
+//! The board's memory map.
+
+/// Physical devices.
+#[rustfmt::skip]
+pub mod mmio {
+    pub const BASE:           usize =        0x3F00_0000;
+    pub const GPIO_BASE:      usize = BASE + 0x0020_0000;
+    pub const MINI_UART_BASE: usize = BASE + 0x0021_5000;
+}

diff -uNr 05_safe_globals/src/bsp/rpi3.rs 06_drivers_gpio_uart/src/bsp/rpi3.rs
--- 05_safe_globals/src/bsp/rpi3.rs
+++ 06_drivers_gpio_uart/src/bsp/rpi3.rs
@@ -4,115 +4,59 @@

 //! Board Support Package for the Raspberry Pi 3.

-use crate::{arch::sync::NullLock, interface};
-use core::fmt;
+mod memory_map;
+
+use super::driver;
+use crate::interface;

 pub const BOOT_CORE_ID: u64 = 0;
 pub const BOOT_CORE_STACK_START: u64 = 0x80_000;

-/// A mystical, magical device for generating QEMU output out of the void.
-///
-/// The mutex protected part.
-struct QEMUOutputInner {
-    chars_written: usize,
-}
-
-impl QEMUOutputInner {
-    const fn new() -> QEMUOutputInner {
-        QEMUOutputInner { chars_written: 0 }
-    }
-
-    /// Send a character.
-    fn write_char(&mut self, c: char) {
-        unsafe {
-            core::ptr::write_volatile(0x3F21_5040 as *mut u8, c as u8);
-        }
-    }
-}
-
-/// 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.
-///
-/// The function takes an `&mut self`, so it must be implemented for the inner
-/// struct.
-///
-/// See [`src/print.rs`].
-///
-/// [`src/print.rs`]: ../../print/index.html
-impl fmt::Write for QEMUOutputInner {
-    fn write_str(&mut self, s: &str) -> fmt::Result {
-        for c in s.chars() {
-            // Convert newline to carrige return + newline.
-            if c == '
' {
-                self.write_char('
')
-            }
-
-            self.write_char(c);
-        }
-
-        self.chars_written += s.len();
-
-        Ok(())
-    }
-}
-
 ////////////////////////////////////////////////////////////////////////////////
-// BSP-public
+// Global BSP driver instances
 ////////////////////////////////////////////////////////////////////////////////

-/// The main struct.
-pub struct QEMUOutput {
-    inner: NullLock<QEMUOutputInner>,
-}
-
-impl QEMUOutput {
-    pub const fn new() -> QEMUOutput {
-        QEMUOutput {
-            inner: NullLock::new(QEMUOutputInner::new()),
-        }
-    }
-}
+static GPIO: driver::GPIO = unsafe { driver::GPIO::new(memory_map::mmio::GPIO_BASE) };
+static MINI_UART: driver::MiniUart =
+    unsafe { driver::MiniUart::new(memory_map::mmio::MINI_UART_BASE) };

 ////////////////////////////////////////////////////////////////////////////////
-// OS interface implementations
+// Implementation of the kernel's BSP calls
 ////////////////////////////////////////////////////////////////////////////////

-/// Passthrough of `args` to the `core::fmt::Write` implementation, but guarded
-/// by a Mutex to serialize access.
-impl interface::console::Write for QEMUOutput {
-    fn write_fmt(&self, args: core::fmt::Arguments) -> fmt::Result {
-        use interface::sync::Mutex;
-
-        // Fully qualified syntax for the call to
-        // `core::fmt::Write::write:fmt()` to increase readability.
-        let mut r = &self.inner;
-        r.lock(|inner| fmt::Write::write_fmt(inner, args))
-    }
+/// Board identification.
+pub fn board_name() -> &'static str {
+    "Raspberry Pi 3"
 }

-impl interface::console::Read for QEMUOutput {}
-
-impl interface::console::Statistics for QEMUOutput {
-    fn chars_written(&self) -> usize {
-        use interface::sync::Mutex;
-
-        let mut r = &self.inner;
-        r.lock(|inner| inner.chars_written)
-    }
+/// Return a reference to a `console::All` implementation.
+pub fn console() -> &'static impl interface::console::All {
+    &MINI_UART
 }

-////////////////////////////////////////////////////////////////////////////////
-// Global instances
-////////////////////////////////////////////////////////////////////////////////
-
-static QEMU_OUTPUT: QEMUOutput = QEMUOutput::new();
+/// Return an array of references to all `DeviceDriver` compatible `BSP`
+/// drivers.
+///
+/// # Safety
+///
+/// The order of devices is the order in which `DeviceDriver::init()` is called.
+pub fn device_drivers() -> [&'static dyn interface::driver::DeviceDriver; 2] {
+    [&GPIO, &MINI_UART]
+}

-////////////////////////////////////////////////////////////////////////////////
-// Implementation of the kernel's BSP calls
-////////////////////////////////////////////////////////////////////////////////
+/// The BSP's main initialization function.
+///
+/// Called early on kernel start.
+pub fn init() {
+    for i in device_drivers().iter() {
+        if let Err(()) = i.init() {
+            // This message will only be readable if, at the time of failure,
+            // the return value of `bsp::console()` is already in functioning
+            // state.
+            panic!("Error loading driver: {}", i.compatible())
+        }
+    }

-/// Return a reference to a `console::All` implementation.
-pub fn console() -> &'static impl interface::console::All {
-    &QEMU_OUTPUT
+    // Configure MiniUart's output pins.
+    GPIO.map_mini_uart();
 }

diff -uNr 05_safe_globals/src/bsp.rs 06_drivers_gpio_uart/src/bsp.rs
--- 05_safe_globals/src/bsp.rs
+++ 06_drivers_gpio_uart/src/bsp.rs
@@ -4,6 +4,8 @@

 //! Conditional exporting of Board Support Packages.

+mod driver;
+
 #[cfg(feature = "bsp_rpi3")]
 mod rpi3;


diff -uNr 05_safe_globals/src/interface.rs 06_drivers_gpio_uart/src/interface.rs
--- 05_safe_globals/src/interface.rs
+++ 06_drivers_gpio_uart/src/interface.rs
@@ -24,6 +24,7 @@

     /// Console write functions.
     pub trait Write {
+        fn write_char(&self, c: char);
         fn write_fmt(&self, args: fmt::Arguments) -> fmt::Result;
     }

@@ -85,3 +86,20 @@
         fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R;
     }
 }
+
+/// Driver interfaces.
+pub mod driver {
+    /// Driver result type, e.g. for indicating successful driver init.
+    pub type Result = core::result::Result<(), ()>;
+
+    /// Device Driver operations.
+    pub trait DeviceDriver {
+        /// Return a compatibility string for identifying the driver.
+        fn compatible(&self) -> &str;
+
+        /// Called by the kernel to bring up the device.
+        fn init(&self) -> Result {
+            Ok(())
+        }
+    }
+}

diff -uNr 05_safe_globals/src/main.rs 06_drivers_gpio_uart/src/main.rs
--- 05_safe_globals/src/main.rs
+++ 06_drivers_gpio_uart/src/main.rs
@@ -36,12 +36,30 @@

 /// Entrypoint of the `kernel`.
 fn kernel_entry() -> ! {
-    use interface::console::Statistics;
+    use interface::console::All;

-    println!("[0] Hello from pure Rust!");
+    // Run the BSP's initialization code.
+    bsp::init();

-    println!("[1] Chars written: {}", bsp::console().chars_written());
+    // UART should be functional now. Wait for user to hit Enter.
+    loop {
+        if bsp::console().read_char() == '
' {
+            break;
+        }
+    }

-    println!("[2] Stopping here.");
-    arch::wait_forever()
+    println!("[0] Booting on: <{}>.", bsp::board_name());
+
+    println!("[1] Drivers loaded:");
+    for (i, driver) in bsp::device_drivers().iter().enumerate() {
+        println!("      {}. {}", i + 1, driver.compatible());
+    }
+
+    println!("[2] Chars written: {}", bsp::console().chars_written());
+    println!("[3] Echoing input now.");
+
+    loop {
+        let c = bsp::console().read_char();
+        bsp::console().write_char(c);
+    }
 }