use core::{
cell::UnsafeCell,
fmt::Write,
ptr::NonNull,
sync::atomic::{AtomicBool, Ordering},
};
use byte_unit::{Byte, UnitType};
use kernutil::memory::{MemoryDescriptor, MemoryType};
#[cfg(target_arch = "x86_64")]
use some_serial::ns16550::Port;
use some_serial::{
RawUart, SerialEvent, TransferError,
ns16550::{self, Mmio, Ns16550},
pl011,
};
use crate::{
cmdline::EarlyconConfig,
mem::{_fixmap_io, page_size},
};
pub(crate) static mut DEBUG_BASE: usize = 0;
pub(crate) static mut DEBUG_IS_MMIO: bool = false;
pub trait ArchConsoleOps {
fn init() -> bool {
false
}
fn read_byte() -> Option<u8> {
None
}
fn irq_num() -> Option<usize> {
None
}
fn set_input_irq_enabled(_enabled: bool) {}
fn handle_irq() -> u32 {
0
}
}
pub const CONSOLE_IRQ_RX_READY: u32 = 1 << 0;
pub const CONSOLE_IRQ_RX_ERROR: u32 = 1 << 1;
pub const CONSOLE_IRQ_OVERRUN: u32 = 1 << 2;
pub(crate) fn debug_to_memory_desc() -> Option<MemoryDescriptor> {
let debug_base = unsafe { DEBUG_BASE };
let debug_is_mmio = unsafe { DEBUG_IS_MMIO };
if debug_base == 0 || !debug_is_mmio {
return None;
}
Some(MemoryDescriptor::new_aligned(
debug_base,
100,
MemoryType::Mmio,
page_size(),
))
}
pub fn _print(args: core::fmt::Arguments) {
if runtime_output_claimed() {
return;
}
let _ = ConFmt {}.write_fmt(args);
}
pub fn _write_bytes(bytes: &[u8]) -> usize {
if runtime_output_claimed() {
return bytes.len();
}
con().write_bytes(bytes)
}
pub fn _write_str(s: &str) {
if runtime_output_claimed() {
return;
}
con().write_str(s);
}
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::console::_print(core::format_args!($($arg)*)));
}
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::console::_print(core::format_args!("{}{}", core::format_args!($($arg)*), "\n")));
}
#[macro_export]
macro_rules! pr_range {
($name:expr, $b:expr, $s:expr) => {
$crate::println!(
"{:<20}: [0x{:0>16x}, 0x{:0>16x}) ({:>5} Mb)",
$name,
$b,
$b + $s,
($s) / 1024 / 1024
);
};
($name:expr, $b:expr, $s:expr, $($arg:tt)*) => {
$crate::println!(
"{:<20}: [0x{:0>16x}, 0x{:0>16x}) ({:>5} Mb) {}",
$name,
$b,
$b + $s,
($s) / 1024 / 1024,
core::format_args!($($arg)*)
);
};
}
pub fn print_mapping(name: &str, virt: usize, phys: usize, size: usize) {
let fmt = Byte::from(size).get_appropriate_unit(UnitType::Binary);
println!(
"{:<20}: [0x{:0>16x}, 0x{:0>16x}) -> [0x{:0>16x}, 0x{:0>16x}) ({:#.2})",
name,
virt,
virt + size,
phys,
phys + size,
fmt
);
}
#[allow(dead_code)]
struct ConFmt {}
impl Write for ConFmt {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let mut remaining = s;
while let Some(pos) = remaining.find('\n') {
con().write_str(&remaining[..pos]);
con().write_str("\r\n");
remaining = &remaining[pos + 1..];
}
if !remaining.is_empty() {
con().write_str(remaining);
}
Ok(())
}
}
fn con() -> &'static dyn Con {
unsafe { CON }
}
pub(crate) trait Con: Send + Sync {
fn write_bytes(&self, _bytes: &[u8]) -> usize {
_bytes.len()
}
fn write_str(&self, s: &str) {
let bytes = s.as_bytes();
let mut buff = bytes;
while !buff.is_empty() {
let n = self.write_bytes(buff);
buff = &buff[n..];
}
}
}
#[allow(dead_code)]
struct NoCon;
impl Con for NoCon {
fn write_bytes(&self, _bytes: &[u8]) -> usize {
_bytes.len()
}
fn write_str(&self, _s: &str) {
}
}
static mut CON: &dyn Con = &NoCon;
static RUNTIME_OUTPUT_CLAIMED: AtomicBool = AtomicBool::new(false);
pub(crate) unsafe fn set_out(v: &'static dyn Con) {
unsafe {
CON = v;
}
}
pub fn claim_runtime_output() {
RUNTIME_OUTPUT_CLAIMED.store(true, Ordering::Release);
}
#[cfg(not(test))]
fn runtime_output_claimed() -> bool {
crate::mem::mmu::is_mmu_enabled() && RUNTIME_OUTPUT_CLAIMED.load(Ordering::Acquire)
}
#[cfg(test)]
fn runtime_output_claimed() -> bool {
RUNTIME_OUTPUT_CLAIMED.load(Ordering::Acquire)
}
pub struct EarlySerial {
raw: EarlySerialRaw,
tx_state: SerialEvent,
rx_state: SerialEvent,
}
pub enum EarlySerialRaw {
Ns16550Mmio(Ns16550<Mmio>),
#[cfg(target_arch = "x86_64")]
Ns16550Port(Ns16550<Port>),
Pl011(pl011::Pl011),
}
impl EarlySerial {
pub fn new(raw: EarlySerialRaw) -> Self {
Self {
raw,
tx_state: SerialEvent::empty(),
rx_state: SerialEvent::empty(),
}
}
pub fn try_write(&mut self, bytes: &[u8]) -> usize {
let mut written = 0;
while written < bytes.len() {
self.refresh_status();
if !self.tx_state.tx_ready() {
break;
}
self.with_raw(|serial| serial.write_byte(bytes[written]));
self.tx_state
.remove(SerialEvent::TX_READY | SerialEvent::TX_ERROR);
written += 1;
}
written
}
pub fn try_read(&mut self, bytes: &mut [u8]) -> Result<usize, some_serial::TransBytesError> {
let mut read = 0;
let mut first_error = None;
for byte in bytes.iter_mut() {
self.refresh_status();
if !self.rx_state.rx_ready() && !self.rx_state.rx_error() {
break;
}
let status = self.rx_state;
self.rx_state
.remove(SerialEvent::RX_READY | SerialEvent::RX_ERROR | SerialEvent::OVERRUN);
match self.with_raw(|serial| serial.read_byte(status)) {
Some(Ok(b)) => {
*byte = b;
read += 1;
}
Some(Err(TransferError::Overrun(b))) => {
*byte = b;
read += 1;
first_error.get_or_insert(TransferError::Overrun(b));
}
Some(Err(err)) => {
first_error.get_or_insert(err);
}
None => break,
}
}
if let Some(kind) = first_error {
Err(some_serial::TransBytesError {
bytes_transferred: read,
kind,
})
} else {
Ok(read)
}
}
fn refresh_status(&mut self) {
let event = self.with_raw(|serial| serial.poll_status());
self.tx_state |= event & (SerialEvent::TX_READY | SerialEvent::TX_ERROR);
self.rx_state |=
event & (SerialEvent::RX_READY | SerialEvent::RX_ERROR | SerialEvent::OVERRUN);
}
fn with_raw<R>(&mut self, f: impl FnOnce(&mut dyn RawUart) -> R) -> R {
match &mut self.raw {
EarlySerialRaw::Ns16550Mmio(serial) => f(serial),
#[cfg(target_arch = "x86_64")]
EarlySerialRaw::Ns16550Port(serial) => f(serial),
EarlySerialRaw::Pl011(serial) => f(serial),
}
}
}
pub fn set_earlycon_serial(serial: EarlySerial) {
EARLYCON.set_serial(serial);
unsafe { set_out(&EARLYCON) };
}
pub fn read_byte() -> Option<u8> {
if let Some(byte) = <crate::arch::Arch as crate::ArchTrait>::Console::read_byte() {
return Some(byte);
}
EARLYCON.read_byte()
}
pub fn irq_num() -> Option<usize> {
<crate::arch::Arch as crate::ArchTrait>::Console::irq_num()
}
pub fn set_input_irq_enabled(enabled: bool) {
<crate::arch::Arch as crate::ArchTrait>::Console::set_input_irq_enabled(enabled);
}
pub fn handle_irq() -> u32 {
<crate::arch::Arch as crate::ArchTrait>::Console::handle_irq()
}
static EARLYCON: EarlyconCell = EarlyconCell(EarlyconMutex::new(None));
struct EarlyconMutex<T> {
locked: AtomicBool,
inner: UnsafeCell<T>,
}
unsafe impl<T: Send> Sync for EarlyconMutex<T> {}
impl<T> EarlyconMutex<T> {
const fn new(value: T) -> Self {
Self {
locked: AtomicBool::new(false),
inner: UnsafeCell::new(value),
}
}
fn with_lock<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
if !crate::mem::mmu::is_mmu_enabled() {
return unsafe { f(&mut *self.inner.get()) };
}
let irq_enabled = crate::irq::irq_local_is_enabled();
crate::irq::irq_local_set_enable(false);
while self
.locked
.compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
while self.locked.load(Ordering::Acquire) {
core::hint::spin_loop();
}
}
let ret = unsafe { f(&mut *self.inner.get()) };
self.locked.store(false, Ordering::Release);
crate::irq::irq_local_set_enable(irq_enabled);
ret
}
}
struct EarlyconCell(EarlyconMutex<Option<EarlySerial>>);
impl EarlyconCell {
fn set_serial(&self, serial: EarlySerial) {
self.0.with_lock(|earlycon| *earlycon = Some(serial));
}
fn read_byte(&self) -> Option<u8> {
self.0.with_lock(|earlycon| {
let serial = earlycon.as_mut()?;
let mut byte = [0];
match serial.try_read(&mut byte) {
Ok(1) => Some(byte[0]),
Err(err) if err.bytes_transferred == 1 => Some(byte[0]),
_ => None,
}
})
}
fn try_write(&self, bytes: &[u8]) -> Option<usize> {
self.0
.with_lock(|earlycon| earlycon.as_mut().map(|serial| serial.try_write(bytes)))
}
}
impl Con for EarlyconCell {
fn write_bytes(&self, bytes: &[u8]) -> usize {
const MAX_NO_PROGRESS_SPINS: usize = 1 << 20;
let mut written = 0;
let mut no_progress_spins = 0;
while written < bytes.len() {
let Some(n) = self.try_write(&bytes[written..]) else {
return bytes.len();
};
if n == 0 {
no_progress_spins += 1;
if no_progress_spins >= MAX_NO_PROGRESS_SPINS {
return bytes.len();
}
core::hint::spin_loop();
continue;
}
no_progress_spins = 0;
written += n;
}
written
}
}
#[cfg(test)]
mod tests {
use core::sync::atomic::{AtomicUsize, Ordering};
use super::*;
struct CountingCon;
static WRITE_CALLS: AtomicUsize = AtomicUsize::new(0);
impl Con for CountingCon {
fn write_bytes(&self, bytes: &[u8]) -> usize {
WRITE_CALLS.fetch_add(1, Ordering::Relaxed);
bytes.len()
}
}
static COUNTING_CON: CountingCon = CountingCon;
#[test]
fn runtime_output_claim_consumes_without_touching_boot_console() {
WRITE_CALLS.store(0, Ordering::Relaxed);
RUNTIME_OUTPUT_CLAIMED.store(false, Ordering::Relaxed);
unsafe { set_out(&COUNTING_CON) };
assert_eq!(_write_bytes(b"before"), 6);
assert_eq!(WRITE_CALLS.load(Ordering::Relaxed), 1);
claim_runtime_output();
assert_eq!(_write_bytes(b"after"), 5);
assert_eq!(WRITE_CALLS.load(Ordering::Relaxed), 1);
RUNTIME_OUTPUT_CLAIMED.store(false, Ordering::Relaxed);
}
}
pub fn set_earlycon_by_cmdline() -> Result<(), &'static str> {
let config = crate::cmdline::earlycon().ok_or("No earlycon parameter found")?;
let debug_is_mmio = match config.uart_type {
"ns16550" => match config.io_type {
"io" => {
#[cfg(target_arch = "x86_64")]
{
let base = config.base_addr.ok_or("missing io base address")? as u16;
let mut uart = some_serial::ns16550::Ns16550::new_port(base, 1_843_200);
uart.open();
set_earlycon_serial(EarlySerial::new(EarlySerialRaw::Ns16550Port(uart)));
false
}
#[cfg(not(target_arch = "x86_64"))]
{
return Err("io type not supported on this architecture");
}
}
_ => {
set_16550_mmio(&config)?;
true
}
},
"pl011" => {
set_pl011(&config)?;
true
}
_ => {
return Err("unsupported earlycon uart type");
}
};
unsafe {
DEBUG_BASE = config
.base_addr
.map(<crate::arch::Arch as crate::ArchTrait>::canonicalize_paddr)
.unwrap_or(0);
DEBUG_IS_MMIO = debug_is_mmio;
}
Ok(())
}
fn set_pl011(config: &EarlyconConfig) -> Result<(), &'static str> {
let base_addr = earlycon_base_addr(config, "No base address specified for pl011 earlycon")?;
let base_addr =
NonNull::new(_fixmap_io(base_addr)).ok_or("Invalid base address for pl011 earlycon")?;
let mut serial = pl011::Pl011::new(base_addr, 0);
serial.open();
set_earlycon_serial(EarlySerial::new(EarlySerialRaw::Pl011(serial)));
Ok(())
}
fn set_16550_mmio(config: &EarlyconConfig) -> Result<(), &'static str> {
let base_addr = earlycon_base_addr(config, "No base address specified for ns16550 earlycon")?;
let base_addr =
NonNull::new(_fixmap_io(base_addr)).ok_or("Invalid base address for ns16550 earlycon")?;
let width = match config.io_type {
"mmio" => 1,
"mmio16" => 2,
"mmio32" => 4,
_ => return Err("Invalid io_type for ns16550 earlycon"),
};
let mut serial = ns16550::Ns16550::new_mmio(base_addr, 0, width);
serial.open();
set_earlycon_serial(EarlySerial::new(EarlySerialRaw::Ns16550Mmio(serial)));
Ok(())
}
fn earlycon_base_addr(
config: &EarlyconConfig,
missing: &'static str,
) -> Result<usize, &'static str> {
let addr = config.base_addr.ok_or(missing)?;
Ok(<crate::arch::Arch as crate::ArchTrait>::canonicalize_paddr(
addr,
))
}