Skip to content
Merged
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
92 changes: 64 additions & 28 deletions os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use alloc::collections::vec_deque::VecDeque;
use core::{any::Any, ptr::NonNull, task::Context};
use core::{
any::Any,
ptr::NonNull,
sync::atomic::{AtomicUsize, Ordering},
task::Context,
};

use ax_errno::{AxError, LinuxError};
use ax_kspin::SpinNoIrq;
use ax_memory_addr::{PhysAddr, pa};
use ax_runtime::hal::mem::phys_to_virt;
use ax_sync::Mutex;
use ax_task::future::{block_on, poll_io};
use axfs_ng_vfs::{NodeFlags, VfsResult};
Expand All @@ -25,13 +29,36 @@ const UART1_IRQ: usize = 45;
const UART2_IRQ: usize = 46;
const RX_BUF_CAP: usize = 4096;

/// MMIO span of a single UART block. Covers the DW APB shadow registers (USR at
/// 0x7c etc.) — one page is more than enough and keeps the mapping page-aligned.
const UART_MMIO_SIZE: usize = 0x1000;
/// MMIO span covering the pinmux register groups. FMUX (0x03001000) and the
/// Active-Domain IOBLK groups (0x03001800 + G1/G7/G10/G12 offsets) live in the
/// same 4K page; GRTC (0x05027000) is mapped separately.
const PINMUX_MMIO_SIZE: usize = 0x1000;

static UART1_RX_BUF: SpinNoIrq<VecDeque<u8>> = SpinNoIrq::new(VecDeque::new());
static UART2_RX_BUF: SpinNoIrq<VecDeque<u8>> = SpinNoIrq::new(VecDeque::new());
static UART1_POLL: PollSet = PollSet::new();
static UART2_POLL: PollSet = PollSet::new();
/// Mapped virtual base of each UART, published by `TtySerial::new` so the raw
/// IRQ handlers can reach the registers without recomputing a (now invalid on
/// dynamic platforms) `phys_to_virt` address.
static UART1_VADDR: AtomicUsize = AtomicUsize::new(0);
static UART2_VADDR: AtomicUsize = AtomicUsize::new(0);

/// Map a physical MMIO region into the kernel address space and return its
/// virtual base. Unlike `phys_to_virt`, this works on dynamic platforms where
/// `PHYS_VIRT_OFFSET == 0` and there is no static linear MMIO window — `iomap`
/// installs a real device mapping and is idempotent for already-mapped pages.
fn iomap_usize(paddr: PhysAddr, size: usize) -> usize {
ax_mm::iomap(paddr, size)
.unwrap_or_else(|err| panic!("failed to iomap MMIO at {paddr:#x}+{size:#x}: {err:?}"))
.as_usize()
}

fn uart_irq_handler(paddr: PhysAddr, buf: &SpinNoIrq<VecDeque<u8>>, poll: &PollSet) {
let mut uart = DwApbUart::new(phys_to_virt(paddr).as_usize());
fn uart_irq_handler(vaddr: usize, buf: &SpinNoIrq<VecDeque<u8>>, poll: &PollSet) {
let mut uart = DwApbUart::new(vaddr);
let mut rx = buf.lock();
let mut got_data = false;
while let Some(c) = uart.getchar() {
Expand All @@ -48,10 +75,18 @@ fn uart_irq_handler(paddr: PhysAddr, buf: &SpinNoIrq<VecDeque<u8>>, poll: &PollS
}

fn uart1_irq_handler(_irq: usize) {
uart_irq_handler(UART1_PADDR, &UART1_RX_BUF, &UART1_POLL);
uart_irq_handler(
UART1_VADDR.load(Ordering::Relaxed),
&UART1_RX_BUF,
&UART1_POLL,
);
}
fn uart2_irq_handler(_irq: usize) {
uart_irq_handler(UART2_PADDR, &UART2_RX_BUF, &UART2_POLL);
uart_irq_handler(
UART2_VADDR.load(Ordering::Relaxed),
&UART2_RX_BUF,
&UART2_POLL,
);
}

unsafe fn uart1_raw_irq_handler(
Expand Down Expand Up @@ -130,7 +165,7 @@ struct SerialConfig {
}

pub struct TtySerial {
paddr: PhysAddr,
vaddr: usize,
irq: usize,
rx_buf: &'static SpinNoIrq<VecDeque<u8>>,
poll_set: &'static PollSet,
Expand All @@ -144,17 +179,21 @@ impl TtySerial {
baud: u32,
rx_buf: &'static SpinNoIrq<VecDeque<u8>>,
poll_set: &'static PollSet,
vaddr_slot: &AtomicUsize,
irq_handler: ax_runtime::hal::irq::RawIrqHandler,
) -> Self {
let vaddr = phys_to_virt(paddr).as_usize();
let vaddr = iomap_usize(paddr, UART_MMIO_SIZE);
// Publish the mapped base before enabling the IRQ so the handler never
// observes a zero (unmapped) address.
vaddr_slot.store(vaddr, Ordering::Relaxed);
let mut uart = DwApbUart::new(vaddr);
uart.init_with_baud_clk(baud, SG2002_UART_CLOCK);
uart.set_ier(true);
let _ = ax_runtime::hal::irq::request_shared_irq(irq, irq_handler, NonNull::dangling())
.map_err(|err| warn!("failed to request serial IRQ {irq}: {err:?}"));
ax_runtime::hal::irq::set_enable(irq, true);
Self {
paddr,
vaddr,
irq,
rx_buf,
poll_set,
Expand All @@ -166,8 +205,7 @@ impl TtySerial {
}

fn set_baud(&self, baud: u32) {
let vaddr = phys_to_virt(self.paddr).as_usize();
let mut uart = DwApbUart::new(vaddr);
let mut uart = DwApbUart::new(self.vaddr);
uart.init_with_baud_clk(baud, SG2002_UART_CLOCK);
uart.set_ier(true);
ax_runtime::hal::irq::set_enable(self.irq, true);
Expand All @@ -193,8 +231,7 @@ impl DeviceOps for TtySerial {
}

fn write_at(&self, buf: &[u8], _offset: u64) -> VfsResult<usize> {
let vaddr = phys_to_virt(self.paddr).as_usize();
let mut uart = DwApbUart::new(vaddr);
let mut uart = DwApbUart::new(self.vaddr);
for &b in buf {
uart.putchar(b);
}
Expand Down Expand Up @@ -283,34 +320,32 @@ impl Pollable for TtySerial {
}
}

/// Map the pinmux register groups and build a `Pinmux` over the mapped virtual
/// bases. FMUX and the Active-Domain IOBLK groups share a page, so the two
/// `iomap` calls resolve to the same mapping (idempotent); GRTC is its own page.
fn map_pinmux() -> Pinmux {
let fmux_vaddr = iomap_usize(pa!(FMUX_BASE), PINMUX_MMIO_SIZE);
let ioblk_vaddr = iomap_usize(pa!(IOBLK_BASE), PINMUX_MMIO_SIZE);
let ioblk_grtc_vaddr = iomap_usize(pa!(IOBLK_GRTC_BASE), PINMUX_MMIO_SIZE);
unsafe { Pinmux::new(fmux_vaddr, ioblk_vaddr, ioblk_grtc_vaddr) }
}

pub fn new_tty_s1(baud: u32) -> TtySerial {
let pinmux = unsafe {
Pinmux::new(
FMUX_BASE + ax_config::plat::PHYS_VIRT_OFFSET,
IOBLK_BASE + ax_config::plat::PHYS_VIRT_OFFSET,
IOBLK_GRTC_BASE + ax_config::plat::PHYS_VIRT_OFFSET,
)
};
pinmux.set_uart1();
map_pinmux().set_uart1();
TtySerial::new(
UART1_PADDR,
UART1_IRQ,
baud,
&UART1_RX_BUF,
&UART1_POLL,
&UART1_VADDR,
uart1_raw_irq_handler,
)
}

pub fn new_tty_s2(baud: u32) -> TtySerial {
use sg200x_bsp::pinmux::{FMUX_IIC0_SCL, FMUX_IIC0_SDA};
let pinmux = unsafe {
Pinmux::new(
FMUX_BASE + ax_config::plat::PHYS_VIRT_OFFSET,
IOBLK_BASE + ax_config::plat::PHYS_VIRT_OFFSET,
IOBLK_GRTC_BASE + ax_config::plat::PHYS_VIRT_OFFSET,
)
};
let pinmux = map_pinmux();
// Wire UART2 to IIC0_SCL/SDA (0x03001070/74), matching the
// original StarryOS sg2002 board layout: SCL → UART2_TX,
// SDA → UART2_RX. Wrong pinmux (e.g. pwr_gpio0/1) sends bytes
Expand All @@ -323,6 +358,7 @@ pub fn new_tty_s2(baud: u32) -> TtySerial {
baud,
&UART2_RX_BUF,
&UART2_POLL,
&UART2_VADDR,
uart2_raw_irq_handler,
)
}
Loading