diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38cb815042..a41f675dc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -191,7 +191,7 @@ jobs: - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package hello_world --no-default-features qemu ${{ matrix.flags }} --microvm if: matrix.arch == 'x86_64' - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package stdin qemu - if: matrix.arch == 'x86_64' + if: matrix.arch != 'riscv64' - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package stdin --features hermit/console qemu --devices virtio-console-pci if: matrix.arch != 'riscv64' - run: cargo xtask ci rs --arch ${{ matrix.arch }} --profile ${{ matrix.profile }} --package stdin --features hermit/console --no-default-features qemu --devices virtio-console-mmio diff --git a/Cargo.lock b/Cargo.lock index 723f917f6c..5d48c9596f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,18 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "arm-pl011-uart" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8797e113733a36b5fa906c81aefc126d255b88dd1c10a737749aa22ec7736b6a" +dependencies = [ + "bitflags 2.9.3", + "safe-mmio", + "thiserror", + "zerocopy", +] + [[package]] name = "async-executor" version = "1.13.2" @@ -821,6 +833,7 @@ dependencies = [ "anstyle", "anyhow", "arm-gic", + "arm-pl011-uart", "async-executor", "async-lock", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 0a821ace3d..8c1499c732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -177,6 +177,7 @@ memory_addresses = { version = "0.2.3", default-features = false, features = [ [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64 = { version = "0.0.14", default-features = false } arm-gic = { version = "0.6" } +arm-pl011-uart = { version = "0.3", default-features = false } semihosting = { version = "0.1", optional = true } memory_addresses = { version = "0.2.3", default-features = false, features = [ "aarch64", diff --git a/src/arch/aarch64/kernel/interrupts.rs b/src/arch/aarch64/kernel/interrupts.rs index 02905401b3..8f679150da 100644 --- a/src/arch/aarch64/kernel/interrupts.rs +++ b/src/arch/aarch64/kernel/interrupts.rs @@ -21,6 +21,7 @@ use crate::drivers::mmio::get_interrupt_handlers; #[cfg(feature = "pci")] use crate::drivers::pci::get_interrupt_handlers; use crate::drivers::{InterruptHandlerQueue, InterruptLine}; +use crate::kernel::serial::handle_uart_interrupt; use crate::mm::virtualmem::KERNEL_FREE_LIST; use crate::scheduler::{self, CoreId}; use crate::{core_id, core_scheduler, env}; @@ -35,6 +36,8 @@ pub(crate) const SGI_RESCHED: u8 = 1; /// Number of the timer interrupt static mut TIMER_INTERRUPT: u32 = 0; +/// Number of the UART interrupt +static mut UART_INTERRUPT: u32 = 0; /// Possible interrupt handlers static INTERRUPT_HANDLERS: OnceCell> = OnceCell::new(); @@ -108,6 +111,15 @@ pub(crate) fn install_handlers() { queue.push_back(timer_handler); handlers.insert(u8::try_from(TIMER_INTERRUPT).unwrap() + PPI_START, queue); } + + if let Some(queue) = handlers.get_mut(&(u8::try_from(UART_INTERRUPT).unwrap() + SPI_START)) + { + queue.push_back(handle_uart_interrupt); + } else { + let mut queue = VecDeque::::new(); + queue.push_back(handle_uart_interrupt); + handlers.insert(u8::try_from(UART_INTERRUPT).unwrap() + SPI_START, queue); + } } INTERRUPT_HANDLERS.set(handlers).unwrap(); @@ -379,6 +391,44 @@ pub(crate) fn init() { gic.enable_interrupt(timer_irqid, Some(cpu_id), true); } + if let Some(uart_node) = fdt.find_compatible(&["arm,pl011"]) { + let irq_slice = uart_node.property("interrupts").unwrap().value; + let (irqtype, irq_slice) = irq_slice.split_at(core::mem::size_of::()); + let (irq, irq_slice) = irq_slice.split_at(core::mem::size_of::()); + let (irqflags, _) = irq_slice.split_at(core::mem::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()); + + unsafe { + UART_INTERRUPT = irq; + } + + debug!("UART interrupt: {irq}, type {irqtype}, flags {irqflags}"); + + IRQ_NAMES + .lock() + .insert(u8::try_from(irq).unwrap() + SPI_START, "UART"); + + // enable uart interrupt + let uart_irqid = if irqtype == 1 { + IntId::ppi(irq) + } else if irqtype == 0 { + IntId::spi(irq) + } else { + panic!("Invalid interrupt type"); + }; + gic.set_interrupt_priority(uart_irqid, Some(cpu_id), 0x00); + if (irqflags & 0xf) == 4 || (irqflags & 0xf) == 8 { + gic.set_trigger(uart_irqid, Some(cpu_id), Trigger::Level); + } else if (irqflags & 0xf) == 2 || (irqflags & 0xf) == 1 { + gic.set_trigger(uart_irqid, Some(cpu_id), Trigger::Edge); + } else { + panic!("Invalid interrupt level!"); + } + gic.enable_interrupt(uart_irqid, Some(cpu_id), true); + } + let reschedid = IntId::sgi(SGI_RESCHED.into()); gic.set_interrupt_priority(reschedid, Some(cpu_id), 0x01); gic.enable_interrupt(reschedid, Some(cpu_id), true); diff --git a/src/arch/aarch64/kernel/serial.rs b/src/arch/aarch64/kernel/serial.rs index a8376e27ce..4311315a72 100644 --- a/src/arch/aarch64/kernel/serial.rs +++ b/src/arch/aarch64/kernel/serial.rs @@ -1,14 +1,21 @@ -use core::arch::asm; +use alloc::collections::vec_deque::VecDeque; +use core::ptr::NonNull; +use arm_pl011_uart::{DataBits, Interrupts, LineConfig, Parity, StopBits, Uart, UniqueMmioPointer}; use embedded_io::{ErrorType, Read, ReadReady, Write}; +use hermit_sync::{InterruptTicketMutex, Lazy}; use crate::errno::Errno; -pub(crate) struct SerialDevice { - pub addr: u32, +static UART_DEVICE: Lazy> = + Lazy::new(|| InterruptTicketMutex::new(UartDevice::new())); + +pub(crate) struct UartDevice { + uart: Uart<'static>, + buffer: VecDeque, } -impl SerialDevice { +impl UartDevice { pub fn new() -> Self { let base = crate::env::boot_info() .hardware_info @@ -16,7 +23,33 @@ impl SerialDevice { .map(|uartport| uartport.get()) .unwrap(); - Self { addr: base as u32 } + let uart_pointer = + unsafe { UniqueMmioPointer::new(NonNull::new_unchecked(base as *mut _)) }; + + let mut uart = Uart::new(uart_pointer); + + let line_config = LineConfig { + data_bits: DataBits::Bits8, + parity: Parity::None, + stop_bits: StopBits::One, + }; + uart.enable(line_config, 115_200, 16_000_000).unwrap(); + + uart.set_interrupt_masks(Interrupts::RXI | Interrupts::RTI); + uart.clear_interrupts(Interrupts::all()); + + Self { + uart, + buffer: VecDeque::new(), + } + } +} + +pub(crate) struct SerialDevice; + +impl SerialDevice { + pub fn new() -> Self { + Self } } @@ -26,41 +59,34 @@ impl ErrorType for SerialDevice { impl Read for SerialDevice { fn read(&mut self, buf: &mut [u8]) -> Result { - let _ = buf; - Ok(0) + let mut guard = UART_DEVICE.lock(); + + if guard.buffer.is_empty() { + Ok(0) + } else { + let min = buf.len().min(guard.buffer.len()); + + for (dst, src) in buf[..min].iter_mut().zip(guard.buffer.drain(..min)) { + *dst = src; + } + + Ok(min) + } } } impl ReadReady for SerialDevice { fn read_ready(&mut self) -> Result { - Ok(false) + Ok(!UART_DEVICE.lock().buffer.is_empty()) } } impl Write for SerialDevice { fn write(&mut self, buf: &[u8]) -> Result { - let port = core::ptr::with_exposed_provenance_mut::(self.addr as usize); - for &byte in buf { - // LF newline characters need to be extended to CRLF over a real serial port. - if byte == b'\n' { - unsafe { - asm!( - "strb w8, [{port}]", - port = in(reg) port, - in("x8") b'\r', - options(nostack), - ); - } - } + let mut guard = UART_DEVICE.lock(); - unsafe { - asm!( - "strb w8, [{port}]", - port = in(reg) port, - in("x8") byte, - options(nostack), - ); - } + for byte in buf { + guard.uart.write_word(*byte); } Ok(buf.len()) @@ -70,3 +96,24 @@ impl Write for SerialDevice { Ok(()) } } + +pub(crate) fn handle_uart_interrupt() { + let mut guard = UART_DEVICE.lock(); + + while let Ok(Some(mut byte)) = guard.uart.read_word() { + // Normalize CR to LF + if byte == b'\r' { + byte = b'\n'; + } + + guard.buffer.push_back(byte); + } + + guard + .uart + .clear_interrupts(Interrupts::RXI | Interrupts::RTI); + + drop(guard); + + crate::console::CONSOLE_WAKER.lock().wake(); +}