Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable UART before MMU enabled on aarch64 #17

Merged
merged 2 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 152 additions & 29 deletions aarch64/src/devcons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ use port::fdt::{DeviceTree, RegBlock};
// - Break out mailbox, gpio code

// GPIO registers
const GPPUD: u64 = 0x94;
const GPPUDCLK0: u64 = 0x98;
const GPFSEL1: u64 = 0x04; // GPIO function select register 1
const GPPUD: u64 = 0x94; // GPIO pin pull up/down enable
const GPPUDCLK0: u64 = 0x98; // GPIO pin pull up/down enable clock 0

// UART 0 (PL011) registers
const UART0_DR: u64 = 0x00; // Data register
Expand All @@ -46,6 +47,19 @@ const UART0_CR: u64 = 0x30; // Control register
const UART0_IMSC: u64 = 0x38; // Interrupt mask set clear register
const UART0_ICR: u64 = 0x44; // Interrupt clear register

// AUX registers, offset from aux_reg
const AUX_ENABLE: u64 = 0x04; // AUX enable register (Mini Uart, SPIs)

// UART1 registers, offset from miniuart_reg
const AUX_MU_IO: u64 = 0x00; // AUX IO data register
const AUX_MU_IER: u64 = 0x04; // Mini Uart interrupt enable register
const AUX_MU_IIR: u64 = 0x08; // Mini Uart interrupt identify register
const AUX_MU_LCR: u64 = 0x0c; // Mini Uart line control register
const AUX_MU_MCR: u64 = 0x10; // Mini Uart line control register
const AUX_MU_LSR: u64 = 0x14; // Mini Uart line status register
const AUX_MU_CNTL: u64 = 0x20; // Mini Uart control register
const AUX_MU_BAUD: u64 = 0x28; // Mini Uart baudrate register

const MBOX_READ: u64 = 0x00;
const MBOX_STATUS: u64 = 0x18;
const MBOX_WRITE: u64 = 0x20;
Expand All @@ -68,6 +82,18 @@ fn write_reg(reg: RegBlock, offset: u64, val: u32) {
unsafe { write_volatile(dst as *mut u32, val) }
}

/// Write val|old into the reg RegBlock at offset from reg.addr,
/// where `old` is the existing value.
/// Panics if offset is outside any range specified by reg.len.
pub fn write_or_reg(reg: RegBlock, offset: u64, val: u32) {
let dst = reg.addr + offset;
assert!(reg.len.map_or(true, |len| offset < len));
unsafe {
let old = read_volatile(dst as *const u32);
write_volatile(dst as *mut u32, val | old)
}
}

/// Read from the reg RegBlock at offset from reg.addr.
/// Panics if offset is outside any range specified by reg.len.
fn read_reg(reg: RegBlock, offset: u64) -> u32 {
Expand Down Expand Up @@ -157,8 +183,38 @@ struct Pl011Uart {
pl011_reg: RegBlock,
}

/// PL011 is the default in qemu (UART0), but a bit fiddly to use on a real
/// Raspberry Pi board, as it needs additional configuration in the config
/// and EEPROM (rpi4) to assign to the serial GPIO pins.
impl Pl011Uart {
pub fn init(&self) {
fn new(dt: &DeviceTree) -> Pl011Uart {
// TODO use aliases?
let gpio_reg = dt
.find_compatible("brcm,bcm2835-gpio")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

let mbox_reg = dt
.find_compatible("brcm,bcm2835-mbox")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

// Find a compatible pl011 uart
let pl011_reg = dt
.find_compatible("arm,pl011")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

Pl011Uart { gpio_reg, pl011_reg, mbox_reg }
}

fn init(&self) {
// Disable UART0
write_reg(self.pl011_reg, UART0_CR, 0);

Expand Down Expand Up @@ -241,35 +297,102 @@ impl Uart for Pl011Uart {
}
}

/// MiniUart is assigned to UART1 on the Raspberry Pi. It is easier to use with
/// real hardware, as it requires no additional configuration. Conversely, it's
/// harded to use with QEMU, as it can't be used with the `nographic` switch.
struct MiniUart {
gpio_reg: RegBlock,
aux_reg: RegBlock,
miniuart_reg: RegBlock,
}

impl MiniUart {
fn new(dt: &DeviceTree) -> MiniUart {
// TODO use aliases?
let gpio_reg = dt
.find_compatible("brcm,bcm2835-gpio")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

// Find a compatible aux
let aux_reg = dt
.find_compatible("brcm,bcm2835-aux")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

// Find a compatible miniuart
let miniuart_reg = dt
.find_compatible("brcm,bcm2835-aux-uart")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

MiniUart { gpio_reg, aux_reg, miniuart_reg }
}

fn init(&self) {
// Set GPIO pins 14 and 15 to be used for UART1. This is done by
// setting the appropriate flags in GPFSEL1 to ALT5, which is
// represented by the 0b010
let mut gpfsel1 = read_reg(self.gpio_reg, GPFSEL1);
gpfsel1 &= !((7 << 12) | (7 << 15));
gpfsel1 |= (2 << 12) | (2 << 15);
write_reg(self.gpio_reg, GPFSEL1, gpfsel1);

write_reg(self.gpio_reg, GPPUD, 0);
delay(150);
write_reg(self.gpio_reg, GPPUDCLK0, (1 << 14) | (1 << 15));
delay(150);
write_reg(self.gpio_reg, GPPUDCLK0, 0);

// Enable mini uart - required to write to its registers
write_or_reg(self.aux_reg, AUX_ENABLE, 1);
write_reg(self.miniuart_reg, AUX_MU_CNTL, 0);
// 8-bit
write_reg(self.miniuart_reg, AUX_MU_LCR, 3);
write_reg(self.miniuart_reg, AUX_MU_MCR, 0);
// Disable interrupts
write_reg(self.miniuart_reg, AUX_MU_IER, 0);
// Clear receive/transmit FIFOs
write_reg(self.miniuart_reg, AUX_MU_IIR, 0xc6);

// We want 115200 baud. This is calculated as:
// system_clock_freq / (8 * (baudrate_reg + 1))
// For now we're making assumptions about the clock frequency
// TODO Get the clock freq via the mailbox, and update if it changes.
// let arm_clock_rate = 500000000.0;
// let baud_rate_reg = arm_clock_rate / (8.0 * 115200.0) + 1.0;
//write_reg(self.miniuart_reg, AUX_MU_BAUD, baud_rate_reg as u32);
write_reg(self.miniuart_reg, AUX_MU_BAUD, 270);

// Finally enable transmit
write_reg(self.miniuart_reg, AUX_MU_CNTL, 3);
}
}

impl Uart for MiniUart {
fn putb(&self, b: u8) {
// Wait for UART to become ready to transmit
while read_reg(self.miniuart_reg, AUX_MU_LSR) & (1 << 5) == 0 {
core::hint::spin_loop();
}
write_reg(self.miniuart_reg, AUX_MU_IO, b as u32);
}
}

pub fn init(dt: &DeviceTree) {
// TODO use aliases?
let gpio_reg = dt
.find_compatible("brcm,bcm2835-gpio")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

let mbox_reg = dt
.find_compatible("brcm,bcm2835-mbox")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

// Find a compatible pl011 uart
let pl011_reg = dt
.find_compatible("arm,pl011")
.next()
.and_then(|uart| dt.property_translated_reg_iter(uart).next())
.and_then(|reg| reg.regblock())
.unwrap();

Console::new(|| {
let uart = Pl011Uart { gpio_reg, pl011_reg, mbox_reg };
// Create early console because aarch64 can't use locks until MMU is set up
Console::new_early(|| {
// let uart = Pl011Uart::new(dt);
let uart = MiniUart::new(dt);
uart.init();

static mut UART: MaybeUninit<Pl011Uart> = MaybeUninit::uninit();
static mut UART: MaybeUninit<MiniUart> = MaybeUninit::uninit();
unsafe {
UART.write(uart);
UART.assume_init_mut()
Expand Down
7 changes: 6 additions & 1 deletion aarch64/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(alloc_error_handler)]
#![feature(asm_const)]
#![feature(stdsimd)]
#![cfg_attr(not(any(test, feature = "cargo-clippy")), no_std)]
#![cfg_attr(not(test), no_main)]
#![allow(clippy::upper_case_acronyms)]
Expand All @@ -17,12 +18,16 @@ use port::println;
#[no_mangle]
pub extern "C" fn main9(dtb_ptr: u64) {
let dt = unsafe { DeviceTree::from_u64(dtb_ptr).unwrap() };

devcons::init(&dt);

println!();
println!("r9 from the Internet");
println!("DTB found at: {:#x}", dtb_ptr);

// Assume we've got MMU set up, so drop early console for the locking console
port::devcons::drop_early_console();
println!("looping now");

#[allow(clippy::empty_loop)]
loop {}
}
Expand Down
39 changes: 33 additions & 6 deletions port/src/devcons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ pub trait Uart {
fn putb(&self, b: u8);
}

pub struct Console;

static mut EARLY_CONS: Option<&'static mut dyn Uart> = None;
static CONS: Lock<Option<&'static mut dyn Uart>> = Lock::new("cons", None);

pub struct Console;

impl Console {
/// Create a locking console. Assumes at this point we can use atomics.
pub fn new<F>(uart_fn: F) -> Self
where
F: FnOnce() -> &'static mut dyn Uart,
Expand All @@ -35,6 +37,17 @@ impl Console {
Self
}

/// Create an early, non-locking console. Assumes at this point we cannot use atomics.
/// Once atomics can be used safely, drop_early_console should be called so we switch
/// to the locking console.
pub fn new_early<F>(uart_fn: F) -> Self
where
F: FnOnce() -> &'static mut dyn Uart,
{
unsafe { EARLY_CONS.replace(uart_fn()) };
Self
}

pub fn putb(&mut self, uart: &mut dyn Uart, b: u8) {
if b == b'\n' {
uart.putb(b'\r');
Expand All @@ -47,10 +60,17 @@ impl Console {

pub fn putstr(&mut self, s: &str) {
// XXX: Just for testing.
static mut NODE: LockNode = LockNode::new();
let mut uart = CONS.lock(unsafe { &NODE });
for b in s.bytes() {
self.putb(uart.as_deref_mut().unwrap(), b);

if let Some(uart) = unsafe { &mut EARLY_CONS } {
for b in s.bytes() {
self.putb(*uart, b);
}
} else {
static mut NODE: LockNode = LockNode::new();
let mut uart = CONS.lock(unsafe { &NODE });
for b in s.bytes() {
self.putb(uart.as_deref_mut().unwrap(), b);
}
}
}
}
Expand All @@ -62,6 +82,13 @@ impl fmt::Write for Console {
}
}

pub fn drop_early_console() {
static mut NODE: LockNode = LockNode::new();
let mut cons = CONS.lock(unsafe { &NODE });
let earlycons = unsafe { EARLY_CONS.take() };
*cons = earlycons;
}

pub fn print(args: fmt::Arguments) {
// XXX: Just for testing.
use fmt::Write;
Expand Down
40 changes: 34 additions & 6 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ struct BuildParams {

impl BuildParams {
fn new(matches: &clap::ArgMatches) -> Self {
let profile =
if matches.get_flag("release") { Profile::Release } else { Profile::Debug };
let profile = if matches.get_flag("release") { Profile::Release } else { Profile::Debug };
let verbose = matches.get_flag("verbose");
let arch = matches.try_get_one("arch").ok().flatten().unwrap_or(&Arch::X86_64);
let wait_for_gdb = matches.try_contains_id("gdb").unwrap_or(false) && matches.get_flag("gdb");
let wait_for_gdb =
matches.try_contains_id("gdb").unwrap_or(false) && matches.get_flag("gdb");

Self { arch: *arch, profile: profile, verbose: verbose, wait_for_gdb: wait_for_gdb }
}
Expand Down Expand Up @@ -280,6 +280,25 @@ fn dist(build_params: &BuildParams) -> Result<()> {
if !status.success() {
return Err("objcopy failed".into());
}

// Compress the binary. We do this because they're much faster when used
// for netbooting and qemu also accepts them.
let mut cmd = Command::new("gzip");
cmd.arg("-k");
cmd.arg("-f");
cmd.arg(format!(
"target/{}/{}/aarch64-qemu",
build_params.target(),
build_params.dir()
));
cmd.current_dir(workspace());
if build_params.verbose {
println!("Executing {:?}", cmd);
}
let status = cmd.status()?;
if !status.success() {
return Err("gzip failed".into());
}
}
Arch::X86_64 => {
let mut cmd = Command::new(objcopy());
Expand Down Expand Up @@ -341,8 +360,17 @@ fn run(build_params: &BuildParams) -> Result<()> {
match build_params.arch {
Arch::Aarch64 => {
let mut cmd = Command::new(build_params.qemu_system());
cmd.arg("-nographic");
//cmd.arg("-curses");

// TODO Choose UART at cmdline
// If using UART0 (PL011), this enables serial
//cmd.arg("-nographic");

// If using UART1 (MiniUART), this enables serial
cmd.arg("-serial");
cmd.arg("null");
cmd.arg("-serial");
cmd.arg("stdio");

cmd.arg("-M");
cmd.arg("raspi3b");
if build_params.wait_for_gdb {
Expand All @@ -355,7 +383,7 @@ fn run(build_params: &BuildParams) -> Result<()> {
cmd.arg("int");
cmd.arg("-kernel");
cmd.arg(format!(
"target/{}/{}/aarch64-qemu",
"target/{}/{}/aarch64-qemu.gz",
build_params.target(),
build_params.dir()
));
Expand Down