diff --git a/src/arch/aarch64/kernel/interrupts.rs b/src/arch/aarch64/kernel/interrupts.rs index 0717b2222f..0a5deafb6b 100644 --- a/src/arch/aarch64/kernel/interrupts.rs +++ b/src/arch/aarch64/kernel/interrupts.rs @@ -33,6 +33,10 @@ pub(crate) const SGI_RESCHED: u8 = 1; /// Number of the timer interrupt static mut TIMER_INTERRUPT: u32 = 0; +/// Timer interrupt and its trigger mode, parsed once from the device tree by +/// [`init`]. Timer PPIs are per-core, so [`init_cpu`] reuses this to enable the +/// exact same interrupt in every application processor's redistributor. +static TIMER_IRQ: OnceCell<(IntId, Trigger)> = OnceCell::new(); /// Number of the UART interrupt static mut UART_INTERRUPT: u32 = 0; /// Possible interrupt handlers @@ -263,6 +267,28 @@ pub fn wakeup_core(core_id: CoreId) { .unwrap(); } +/// Decodes an `(interrupt-type, interrupt-number, flags)` triplet from a device +/// tree `interrupts` property into its [`IntId`] and [`Trigger`]. +fn decode_interrupt(irqtype: u32, irq: u32, irqflags: u32) -> (IntId, Trigger) { + let id = if irqtype == 1 { + IntId::ppi(irq) + } else if irqtype == 0 { + IntId::spi(irq) + } else { + panic!("Invalid interrupt type"); + }; + + let trigger = if (irqflags & 0xf) == 4 || (irqflags & 0xf) == 8 { + Trigger::Level + } else if (irqflags & 0xf) == 2 || (irqflags & 0xf) == 1 { + Trigger::Edge + } else { + panic!("Invalid interrupt level!"); + }; + + (id, trigger) +} + pub(crate) fn init() { info!("Initialize generic interrupt controller"); @@ -356,25 +382,15 @@ pub(crate) fn init() { .lock() .insert(u8::try_from(irq).unwrap() + PPI_START, "Timer"); + // Cache the parsed interrupt so that every application processor enables + // the exact same PPI in its redistributor (see `init_cpu`). + let (timer_irqid, trigger) = decode_interrupt(irqtype, irq, irqflags); + TIMER_IRQ.set((timer_irqid, trigger)).unwrap(); + // enable timer interrupt - let timer_irqid = if irqtype == 1 { - IntId::ppi(irq) - } else if irqtype == 0 { - IntId::spi(irq) - } else { - panic!("Invalid interrupt type"); - }; gic.set_interrupt_priority(timer_irqid, Some(cpu_id), 0x00) .unwrap(); - if (irqflags & 0xf) == 4 || (irqflags & 0xf) == 8 { - gic.set_trigger(timer_irqid, Some(cpu_id), Trigger::Level) - .unwrap(); - } else if (irqflags & 0xf) == 2 || (irqflags & 0xf) == 1 { - gic.set_trigger(timer_irqid, Some(cpu_id), Trigger::Edge) - .unwrap(); - } else { - panic!("Invalid interrupt level!"); - } + gic.set_trigger(timer_irqid, Some(cpu_id), trigger).unwrap(); gic.enable_interrupt(timer_irqid, Some(cpu_id), true) .unwrap(); } @@ -445,41 +461,13 @@ pub fn init_cpu() { GicCpuInterface::enable_group1(true); GicCpuInterface::set_priority_mask(0xff); - let fdt = env::fdt().unwrap(); - - if let Some(timer_node) = fdt.find_compatible(&["arm,armv8-timer", "arm,armv7-timer"]) { - let irq_slice = timer_node.property("interrupts").unwrap().value; - /* Secure Phys IRQ */ - let (_irqtype, irq_slice) = irq_slice.split_at(size_of::()); - let (_irq, irq_slice) = irq_slice.split_at(size_of::()); - let (_irqflags, irq_slice) = irq_slice.split_at(size_of::()); - /* Non-secure Phys IRQ */ - let (irqtype, irq_slice) = irq_slice.split_at(size_of::()); - let (irq, irq_slice) = irq_slice.split_at(size_of::()); - let (irqflags, _irq_slice) = irq_slice.split_at(size_of::()); - let irqtype = u32::from_be_bytes(irqtype.try_into().unwrap()); - let irq = u32::from_be_bytes(irq.try_into().unwrap()); - let irqflags = u32::from_be_bytes(irqflags.try_into().unwrap()); - - // enable timer interrupt - let timer_irqid = if irqtype == 1 { - IntId::ppi(irq) - } else if irqtype == 0 { - IntId::spi(irq) - } else { - panic!("Invalid interrupt type"); - }; + // Enable the same timer interrupt that `init()` parsed from the device + // tree. The timer is a per-core PPI, so it has to be enabled in every + // redistributor; reusing the cached value keeps all cores in sync. + if let Some(&(timer_irqid, trigger)) = TIMER_IRQ.get() { gic.set_interrupt_priority(timer_irqid, Some(cpu_id), 0x00) .unwrap(); - if (irqflags & 0xf) == 4 || (irqflags & 0xf) == 8 { - gic.set_trigger(timer_irqid, Some(cpu_id), Trigger::Level) - .unwrap(); - } else if (irqflags & 0xf) == 2 || (irqflags & 0xf) == 1 { - gic.set_trigger(timer_irqid, Some(cpu_id), Trigger::Edge) - .unwrap(); - } else { - panic!("Invalid interrupt level!"); - } + gic.set_trigger(timer_irqid, Some(cpu_id), trigger).unwrap(); gic.enable_interrupt(timer_irqid, Some(cpu_id), true) .unwrap(); } diff --git a/xtask/src/ci/qemu.rs b/xtask/src/ci/qemu.rs index 39803a0cd9..ff46d090d0 100644 --- a/xtask/src/ci/qemu.rs +++ b/xtask/src/ci/qemu.rs @@ -3,7 +3,8 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream, UdpSocket}; use std::path::Path; use std::process::{Child, Command, ExitStatus, Stdio}; use std::str::from_utf8; -use std::time::Duration; +use std::sync::mpsc; +use std::time::{Duration, Instant}; use std::{env, fs, io, thread}; use anyhow::{Context, Result, bail, ensure}; @@ -570,9 +571,27 @@ fn get_frequency() -> u64 { } fn test_stdin(child: &mut Child) -> Result<()> { + /// Upper bound for the guest to echo back every message. This only + /// caps the failure case — the test stops waiting as soon as the last + /// message shows up. + const ECHO_TIMEOUT: Duration = Duration::from_secs(60); + thread::sleep(Duration::from_secs(10)); let messages = ["Hello, there!", "Hello, again!", "Bye-bye!"]; + // Reading blocks, so the echo is collected on a separate thread while + // the main thread feeds stdin. + let stdout = child.stdout.take().unwrap(); + let (tx, rx) = mpsc::channel(); + thread::spawn(move || { + for line in BufReader::new(stdout).lines() { + let Ok(line) = line else { break }; + if tx.send(line).is_err() { + break; + } + } + }); + let mut stdin = child.stdin.take().unwrap(); for message in messages { writeln!(&mut stdin, "{message}")?; @@ -580,20 +599,31 @@ fn test_stdin(child: &mut Child) -> Result<()> { thread::sleep(Duration::from_secs(1)); } - child.terminate()?; + // Wait for the guest to echo everything back before terminating QEMU. + // Terminating after a fixed delay cut the output off mid-line on slow + // targets, such as aarch64_be, which is emulated without hardware + // acceleration. + let mut stdout_lines = Vec::new(); + let mut missing = messages.to_vec(); + let deadline = Instant::now() + ECHO_TIMEOUT; + while !missing.is_empty() { + let Some(timeout) = deadline.checked_duration_since(Instant::now()) else { + break; + }; + let Ok(line) = rx.recv_timeout(timeout) else { + break; + }; + missing.retain(|message| !line.contains(message)); + stdout_lines.push(line); + } - let stdout = child.stdout.take().unwrap(); - let stdout_lines = BufReader::new(stdout) - .lines() - .collect::, _>>()?; + child.terminate()?; for line in &stdout_lines { println!("{line}"); } - for message in messages { - assert!(stdout_lines.iter().any(|line| line.contains(message))); - } + assert!(missing.is_empty(), "missing from output: {missing:?}"); Ok(()) }