use std::collections::VecDeque;
use std::io::Write;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use std::sync::{Mutex, OnceLock};
use super::mmio_bus::MmioDevice;
static RX_QUEUE: Mutex<VecDeque<u8>> = Mutex::new(VecDeque::new());
static UART_IMSC: AtomicU32 = AtomicU32::new(0);
static IRQ_RAISER: OnceLock<Box<dyn Fn(bool) + Send + Sync>> = OnceLock::new();
pub fn set_pl011_irq_raiser<F>(f: F)
where
F: Fn(bool) + Send + Sync + 'static,
{
let _ = IRQ_RAISER.set(Box::new(f));
}
pub fn push_rx_byte(b: u8) -> usize {
let depth = {
let mut q = RX_QUEUE.lock().unwrap();
q.push_back(b);
q.len()
};
if let Some(raiser) = IRQ_RAISER.get() {
raiser(true);
}
depth
}
pub fn drain_rx_queue_for_snapshot() -> usize {
let mut q = RX_QUEUE.lock().unwrap();
let n = q.len();
q.clear();
n
}
pub static HEARTBEAT_COUNT: AtomicU64 = AtomicU64::new(0);
pub static WORKLOAD_PARKED: AtomicBool = AtomicBool::new(false);
pub static PRE_EXEC_READY: AtomicBool = AtomicBool::new(false);
pub static SMPARK_STATE_GPA: AtomicU64 = AtomicU64::new(0);
pub static LOG_SINK: Mutex<Option<std::fs::File>> = Mutex::new(None);
static LOG_SINK_SET: AtomicBool = AtomicBool::new(false);
static HEARTBEAT_DETECTION_ENABLED: AtomicBool = AtomicBool::new(false);
static PL011_LINE: Mutex<Vec<u8>> = Mutex::new(Vec::new());
const HEARTBEAT_MARKER: &[u8] = b"heartbeat counter=";
const PARKED_MARKER: &[u8] = b"parking PID 1";
const PRE_EXEC_MARKER: &[u8] = b"init-oci: workload-pre-exec";
const SMPARK_GPA_PREFIX: &[u8] = b"[SUPERMACHINE-SMPARK] state_gpa=0x";
const KCACHE_READY_MARKER: &[u8] = b"[SUPERMACHINE-KCACHE] ready";
pub static KCACHE_READY: AtomicBool = AtomicBool::new(false);
pub static KCACHE_RESUMED: AtomicBool = AtomicBool::new(false);
const KCACHE_RESUMED_MARKER: &[u8] = b"[SUPERMACHINE-KCACHE] resumed";
const PRE_EXEC_SYNC_READY_MARKER: &[u8] = b"[SUPERMACHINE-PRE-EXEC] ready";
pub static PRE_EXEC_SYNC_READY: AtomicBool = AtomicBool::new(false);
pub static PRE_EXEC_SYNC_RESUMED: AtomicBool = AtomicBool::new(false);
const PRE_EXEC_SYNC_RESUMED_MARKER: &[u8] = b"[SUPERMACHINE-PRE-EXEC] resumed";
fn console_log_enabled() -> bool {
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| {
!matches!(
std::env::var("SUPERMACHINE_CONSOLE_LOG").as_deref(),
Ok("0") | Ok("false") | Ok("no") | Ok("off")
)
})
}
pub fn set_log_sink(path: &str) -> std::io::Result<()> {
let f = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
*LOG_SINK.lock().unwrap() = Some(f);
LOG_SINK_SET.store(true, Ordering::Release);
Ok(())
}
pub fn set_heartbeat_detection(enabled: bool) {
HEARTBEAT_DETECTION_ENABLED.store(enabled, Ordering::Release);
}
pub struct SerialPl011;
impl SerialPl011 {
pub fn new() -> Self {
Self
}
}
impl MmioDevice for SerialPl011 {
fn read(&self, offset: u64, _size: u8) -> u64 {
match offset {
0x000 => {
let (byte, now_empty) = {
let mut q = RX_QUEUE.lock().unwrap();
let b = q.pop_front().unwrap_or(0);
(b, q.is_empty())
};
if now_empty {
if let Some(raiser) = IRQ_RAISER.get() {
raiser(false);
}
}
byte as u64
}
0x018 => {
let rxfe = if RX_QUEUE.lock().unwrap().is_empty() {
1u64 << 4
} else {
0
};
(1u64 << 7) | rxfe
}
0x038 => UART_IMSC.load(Ordering::Acquire) as u64,
0x03c => {
if RX_QUEUE.lock().unwrap().is_empty() {
0
} else {
1u64 << 4
}
}
0x040 => {
let ris = if RX_QUEUE.lock().unwrap().is_empty() {
0u32
} else {
1u32 << 4
};
(ris & UART_IMSC.load(Ordering::Acquire)) as u64
}
0xfe0 => 0x11,
0xfe4 => 0x10,
0xfe8 => 0x14,
0xfec => 0x00,
0xff0 => 0x0d,
0xff4 => 0xf0,
0xff8 => 0x05,
0xffc => 0xb1,
_ => 0,
}
}
fn write(&self, offset: u64, value: u64, _size: u8) {
if offset == 0x038 {
UART_IMSC.store(value as u32, Ordering::Release);
return;
}
if offset == 0x044 {
return;
}
if offset != 0x000 {
return;
}
let b = (value & 0xff) as u8;
if !console_log_enabled()
&& !LOG_SINK_SET.load(Ordering::Acquire)
&& !HEARTBEAT_DETECTION_ENABLED.load(Ordering::Acquire)
{
return;
}
{
let mut sink = LOG_SINK.lock().unwrap();
if let Some(f) = sink.as_mut() {
let _ = f.write_all(&[b]);
} else if console_log_enabled() {
drop(sink);
let mut out = std::io::stdout().lock();
let _ = out.write_all(&[b]);
let _ = out.flush();
}
}
if !HEARTBEAT_DETECTION_ENABLED.load(Ordering::Acquire) {
return;
}
let mut line = PL011_LINE.lock().unwrap();
if b == b'\n' {
if line
.windows(HEARTBEAT_MARKER.len())
.any(|w| w == HEARTBEAT_MARKER)
{
HEARTBEAT_COUNT.fetch_add(1, Ordering::SeqCst);
}
if line
.windows(PARKED_MARKER.len())
.any(|w| w == PARKED_MARKER)
{
WORKLOAD_PARKED.store(true, Ordering::SeqCst);
}
if line
.windows(PRE_EXEC_MARKER.len())
.any(|w| w == PRE_EXEC_MARKER)
{
PRE_EXEC_READY.store(true, Ordering::SeqCst);
}
if line
.windows(KCACHE_READY_MARKER.len())
.any(|w| w == KCACHE_READY_MARKER)
{
KCACHE_READY.store(true, Ordering::SeqCst);
let auto_push_disabled = std::env::var(
"SUPERMACHINE_KCACHE_AUTO_PUSH",
)
.map(|v| v == "0" || v == "false")
.unwrap_or(false);
if !auto_push_disabled {
push_rx_byte(b'R');
push_rx_byte(b'\n');
}
}
if line
.windows(KCACHE_RESUMED_MARKER.len())
.any(|w| w == KCACHE_RESUMED_MARKER)
{
KCACHE_RESUMED.store(true, Ordering::SeqCst);
}
if line
.windows(PRE_EXEC_SYNC_READY_MARKER.len())
.any(|w| w == PRE_EXEC_SYNC_READY_MARKER)
{
PRE_EXEC_SYNC_READY.store(true, Ordering::SeqCst);
}
if line
.windows(PRE_EXEC_SYNC_RESUMED_MARKER.len())
.any(|w| w == PRE_EXEC_SYNC_RESUMED_MARKER)
{
PRE_EXEC_SYNC_RESUMED.store(true, Ordering::SeqCst);
}
if let Some(pos) = line
.windows(SMPARK_GPA_PREFIX.len())
.position(|w| w == SMPARK_GPA_PREFIX)
{
let hex_start = pos + SMPARK_GPA_PREFIX.len();
let mut acc: u64 = 0;
let mut any = false;
for &b in line[hex_start..].iter().take(16) {
let nib = match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b - b'a' + 10,
b'A'..=b'F' => b - b'A' + 10,
_ => break,
};
acc = (acc << 4) | nib as u64;
any = true;
}
if any {
SMPARK_STATE_GPA.store(acc, Ordering::SeqCst);
}
}
line.clear();
} else if line.len() < 4096 {
line.push(b);
}
}
fn len(&self) -> u64 {
super::super::arch::aarch64::layout::SERIAL_MMIO_SIZE
}
}
#[cfg(test)]
mod rx_tests {
use super::*;
use std::sync::Mutex as StdMutex;
static SERIALIZE: StdMutex<()> = StdMutex::new(());
fn fresh_state() -> std::sync::MutexGuard<'static, ()> {
let guard = SERIALIZE
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
drain_rx_queue_for_snapshot();
UART_IMSC.store(0, Ordering::SeqCst);
guard
}
#[test]
fn empty_queue_reads_zero_with_rxfe_set() {
let _guard = fresh_state();
let dev = SerialPl011::new();
assert_eq!(dev.read(0x000, 4), 0);
assert_eq!(dev.read(0x018, 4), (1u64 << 7) | (1u64 << 4));
assert_eq!(dev.read(0x03c, 4), 0);
}
#[test]
fn push_then_read_round_trip() {
let _guard = fresh_state();
let dev = SerialPl011::new();
push_rx_byte(b'X');
assert_eq!(dev.read(0x018, 4) & (1 << 4), 0);
assert_eq!(dev.read(0x03c, 4), 1 << 4);
assert_eq!(dev.read(0x000, 4), b'X' as u64);
assert_ne!(dev.read(0x018, 4) & (1 << 4), 0);
assert_eq!(dev.read(0x03c, 4), 0);
}
#[test]
fn mis_masks_with_imsc() {
let _guard = fresh_state();
let dev = SerialPl011::new();
push_rx_byte(b'A');
assert_eq!(dev.read(0x040, 4), 0);
dev.write(0x038, 1 << 4, 4);
assert_eq!(UART_IMSC.load(Ordering::SeqCst), 1 << 4);
assert_eq!(dev.read(0x040, 4), 1 << 4);
assert_eq!(dev.read(0x000, 4), b'A' as u64);
assert_eq!(dev.read(0x040, 4), 0);
}
#[test]
fn icr_write_is_noop() {
let _guard = fresh_state();
let dev = SerialPl011::new();
push_rx_byte(b'B');
assert_eq!(dev.read(0x03c, 4), 1 << 4);
dev.write(0x044, 1 << 4, 4);
assert_eq!(dev.read(0x03c, 4), 1 << 4);
assert_eq!(dev.read(0x000, 4), b'B' as u64);
}
#[test]
fn fifo_order_preserved() {
let _guard = fresh_state();
let dev = SerialPl011::new();
push_rx_byte(b'a');
push_rx_byte(b'b');
push_rx_byte(b'c');
assert_eq!(dev.read(0x000, 4), b'a' as u64);
assert_eq!(dev.read(0x000, 4), b'b' as u64);
assert_eq!(dev.read(0x000, 4), b'c' as u64);
assert_eq!(dev.read(0x000, 4), 0);
assert_ne!(dev.read(0x018, 4) & (1 << 4), 0);
}
#[test]
fn drain_for_snapshot_returns_count() {
let _guard = fresh_state();
push_rx_byte(b'1');
push_rx_byte(b'2');
assert_eq!(drain_rx_queue_for_snapshot(), 2);
assert_eq!(drain_rx_queue_for_snapshot(), 0);
}
#[test]
fn periph_id_registers_unchanged() {
let _guard = fresh_state();
let dev = SerialPl011::new();
assert_eq!(dev.read(0xfe0, 4), 0x11);
assert_eq!(dev.read(0xfe4, 4), 0x10);
assert_eq!(dev.read(0xfe8, 4), 0x14);
assert_eq!(dev.read(0xfec, 4), 0x00);
assert_eq!(dev.read(0xff0, 4), 0x0d);
assert_eq!(dev.read(0xff4, 4), 0xf0);
assert_eq!(dev.read(0xff8, 4), 0x05);
assert_eq!(dev.read(0xffc, 4), 0xb1);
}
}