From a240d29135fd2d356edf9fd1e623f45117e395d8 Mon Sep 17 00:00:00 2001 From: Garen Tyler Date: Sun, 29 Oct 2023 15:25:17 -0600 Subject: [PATCH] Make UART good --- kernel/rustkernel/src/console/mod.rs | 18 +- kernel/rustkernel/src/console/uart.rs | 354 ++++++++++---------- kernel/rustkernel/src/mem/virtual_memory.rs | 2 +- kernel/rustkernel/src/riscv/memlayout.rs | 2 +- kernel/rustkernel/src/trap.rs | 2 +- 5 files changed, 179 insertions(+), 199 deletions(-) diff --git a/kernel/rustkernel/src/console/mod.rs b/kernel/rustkernel/src/console/mod.rs index 0747162..4f2f2d0 100644 --- a/kernel/rustkernel/src/console/mod.rs +++ b/kernel/rustkernel/src/console/mod.rs @@ -17,7 +17,7 @@ use crate::{ sync::spinmutex::SpinMutex, }; use core::{ffi::c_void, ptr::addr_of_mut}; -use uart::{uartinit, uartputc, Uart}; +use uart::UART0; extern "C" { fn either_copyin(dst: *mut c_void, user_src: i32, src: u64, len: u64) -> i32; @@ -48,9 +48,7 @@ impl Console { } impl core::fmt::Write for Console { fn write_str(&mut self, s: &str) -> core::fmt::Result { - for b in s.as_bytes() { - Uart::write_byte_sync(*b); - } + UART0.write_slice(s.as_bytes()); core::fmt::Result::Ok(()) } } @@ -75,11 +73,11 @@ const fn ctrl_x(x: u8) -> u8 { pub fn consputc(c: u8) { if c == BACKSPACE { // If the user typed backspace, overwrite with a space. - Uart::write_byte_sync(0x08); - Uart::write_byte_sync(b' '); - Uart::write_byte_sync(0x08); + UART0.write_byte(0x08); + UART0.write_byte(b' '); + UART0.write_byte(0x08); } else { - Uart::write_byte_sync(c); + UART0.write_byte(c); } } @@ -92,7 +90,7 @@ pub fn consolewrite(user_src: i32, src: u64, n: i32) -> i32 { if either_copyin(addr_of_mut!(c).cast(), user_src, src + i as u64, 1) == -1 { return i; } else { - uartputc(c as u8); + UART0.write_byte_buffered(c as u8); } } 0 @@ -160,7 +158,7 @@ pub fn consoleread(user_dst: i32, mut dst: u64, mut n: i32) -> i32 { } pub unsafe fn consoleinit() { - uartinit(); + UART0.initialize(); // Connect read and write syscalls // to consoleread and consolewrite. diff --git a/kernel/rustkernel/src/console/uart.rs b/kernel/rustkernel/src/console/uart.rs index 26f153d..86bdac6 100644 --- a/kernel/rustkernel/src/console/uart.rs +++ b/kernel/rustkernel/src/console/uart.rs @@ -3,99 +3,11 @@ use crate::{ console::consoleintr, - proc::{sleep_lock, wakeup}, - riscv::memlayout::UART0, + proc::wakeup, sync::spinlock::Spinlock, - sync::spinmutex::SpinMutex, trap::InterruptBlocker, }; -use core::ptr::addr_of_mut; - -enum Register { - ReceiveHolding, - TransmitHolding, - InterruptEnable, - FIFOControl, - InterruptStatus, - LineControl, - LineStatus, -} -impl Register { - pub fn as_ptr(&self) -> *mut u8 { - let addr = UART0 - + match self { - Register::ReceiveHolding => 0, - Register::TransmitHolding => 0, - Register::InterruptEnable => 1, - Register::FIFOControl => 2, - Register::InterruptStatus => 2, - Register::LineControl => 2, - Register::LineStatus => 5, - }; - addr as *mut u8 - } - pub fn read(&self) -> u8 { - unsafe { self.as_ptr().read_volatile() } - } - pub fn write(&self, value: u8) { - unsafe { self.as_ptr().write_volatile(value) } - } -} - -pub static uart: SpinMutex = SpinMutex::new(Uart { - buffer: [0u8; UART_TX_BUF_SIZE], - write_index: 0, - read_index: 0, -}); - -pub struct Uart { - pub buffer: [u8; UART_TX_BUF_SIZE], - pub write_index: usize, - pub read_index: usize, -} -impl Uart { - /// Alternate version of Uart::write_byte() that doesn't - /// use interrupts, for use by kernel printf() and - /// to echo characters. It spins waiting for the UART's - /// output register to be empty. - pub fn write_byte_sync(x: u8) { - let _ = InterruptBlocker::new(); - - if unsafe { crate::PANICKED } { - loop { - core::hint::spin_loop(); - } - } - - // Wait for Transmit Holding Empty to be set in LSR. - while Register::LineStatus.read() & LSR_TX_IDLE == 0 { - core::hint::spin_loop(); - } - - Register::TransmitHolding.write(x); - } - /// If the UART is idle, and a character is - /// waiting in the transmit buffer, send it. - pub fn send_queued_bytes(&mut self) { - loop { - self.write_index %= self.buffer.len(); - self.read_index %= self.buffer.len(); - - if self.write_index == self.read_index { - // Transmit buffer is ready. - return; - } - - let c = self.buffer[self.read_index]; - self.read_index += 1; - - // Maybe uartputc() is waiting for space in the buffer. - unsafe { wakeup(addr_of_mut!(self.read_index).cast()) }; - - Register::TransmitHolding.write(c); - } - } -} +use core::ptr::addr_of; // The UART control registers. // Some have different meanings for read vs write. @@ -115,116 +27,186 @@ const LSR_RX_READY: u8 = 1 << 0; /// THR can accept another character to send const LSR_TX_IDLE: u8 = 1 << 5; -static mut uart_tx_lock: Spinlock = Spinlock::new(); const UART_TX_BUF_SIZE: usize = 32; -static mut uart_tx_buf: [u8; UART_TX_BUF_SIZE] = [0u8; UART_TX_BUF_SIZE]; -// static uart_tx_buf: SpinMutex<[u8; UART_TX_BUF_SIZE]> = SpinMutex::new([0u8; UART_TX_BUF_SIZE]); -/// Write next to uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] -static mut uart_tx_w: usize = 0; -/// Read next from uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE] -static mut uart_tx_r: usize = 0; -pub(crate) unsafe fn uartinit() { - // Disable interrupts. - Register::InterruptEnable.write(0x00); - // Special mode to set baud rate. - Register::LineControl.write(LCR_BAUD_LATCH); - unsafe { - // LSB for baud rate of 38.4K. - *(UART0 as *mut u8) = 0x03; - // MSB for baud rate of 38.4K. - *((UART0 + 1) as *mut u8) = 0x00; +pub static UART0: Uart = Uart::new(crate::riscv::memlayout::UART0); + +enum Register { + ReceiveHolding, + TransmitHolding, + InterruptEnable, + FIFOControl, + InterruptStatus, + LineControl, + LineStatus, +} +impl Register { + pub fn as_offset(&self) -> usize { + match self { + Register::ReceiveHolding => 0, + Register::TransmitHolding => 0, + Register::InterruptEnable => 1, + Register::FIFOControl => 2, + Register::InterruptStatus => 2, + Register::LineControl => 2, + Register::LineStatus => 5, + } + } + pub fn as_ptr(&self, base_address: usize) -> *mut u8 { + (base_address + self.as_offset()) as *mut u8 + } + pub fn read(&self, base_address: usize) -> u8 { + unsafe { self.as_ptr(base_address).read_volatile() } + } + pub fn write(&self, base_address: usize, value: u8) { + unsafe { self.as_ptr(base_address).write_volatile(value) } } - // Leave set-baud mode and set - // word length to 8 bits, no parity. - Register::LineControl.write(LCR_EIGHT_BITS); - // Reset and enable FIFOs. - Register::FIFOControl.write(FCR_FIFO_ENABLE | FCR_FIFO_CLEAR); - // Enable transmit and receive interrupts. - Register::InterruptEnable.write(IER_TX_ENABLE | IER_RX_ENABLE); - - uart_tx_lock = Spinlock::new(); } -/// Add a character to the output buffer and tell the -/// UART to start sending if it isn't already. -/// Blocks if the output buffer is full. -/// Because it may block, it can't be called -/// from interrupts, it's only suitable for use -/// by write(). -pub(crate) unsafe fn uartputc(c: u8) { - let _guard = uart_tx_lock.lock(); - // let mut buf = uart_tx_buf.lock_unguarded(); - // let u = uart.lock_unguarded(); +pub struct Uart { + pub lock: Spinlock, + pub base_address: usize, + pub buffer: [u8; UART_TX_BUF_SIZE], + pub queue_start: usize, + pub queue_end: usize, +} +impl Uart { + pub const fn new(base_address: usize) -> Uart { + Uart { + lock: Spinlock::new(), + base_address, + buffer: [0u8; UART_TX_BUF_SIZE], + queue_start: 0, + queue_end: 0, + } + } + /// Initialize the UART. + pub unsafe fn initialize(&self) { + // Disable interrupts. + Register::InterruptEnable.write(self.base_address, 0x00); + // Special mode to set baud rate. + Register::LineControl.write(self.base_address, LCR_BAUD_LATCH); + // LSB for baud rate of 38.4K. + *(self.base_address as *mut u8) = 0x03; + // MSB for baud rate of 38.4K. + *((self.base_address + 1) as *mut u8) = 0x00; + // Leave set-baud mode and set + // word length to 8 bits, no parity. + Register::LineControl.write(self.base_address, LCR_EIGHT_BITS); + // Reset and enable FIFOs. + Register::FIFOControl.write(self.base_address, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR); + // Enable transmit and receive interrupts. + Register::InterruptEnable.write(self.base_address, IER_TX_ENABLE | IER_RX_ENABLE); + } + pub fn interrupt(&self) { + // Read and process incoming data. + while let Some(b) = self.read_byte() { + consoleintr(b); + } - if crate::PANICKED { - loop { + // Send buffered characters. + let _guard = self.lock.lock(); + self.send_buffered_bytes(); + } + /// Read one byte from the UART. + pub fn read_byte(&self) -> Option { + if Register::LineStatus.read(self.base_address) & 0x01 != 0 { + // Input data is ready. + Some(Register::ReceiveHolding.read(self.base_address)) + } else { + None + } + } + /// Write a byte to the UART without interrupts. + /// Used for kernel printing and character echoing. + pub fn write_byte(&self, b: u8) { + let _ = InterruptBlocker::new(); + + if unsafe { crate::PANICKED } { + loop { + core::hint::spin_loop(); + } + } + + // Wait for Transmit Holding Empty to be set in LSR. + while Register::LineStatus.read(self.base_address) & LSR_TX_IDLE == 0 { core::hint::spin_loop(); } + + Register::TransmitHolding.write(self.base_address, b); } - - while uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE { - // Buffer is full. - // Wait for uartstart() to open up space in the buffer. - sleep_lock( - addr_of_mut!(uart_tx_r).cast(), - addr_of_mut!(uart_tx_lock).cast(), - ); - } - - uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c; - uart_tx_w += 1; - uartstart(); -} - -/// If the UART is idle, and a character is waiting -/// in the transmit buffer, send it. -/// Caller must hold uart_tx_lock. -/// Called from both the top and bottom halves. -unsafe fn uartstart() { - loop { - if uart_tx_w == uart_tx_r { - // Transmit buffer is ready. - return; + pub fn write_slice(&self, bytes: &[u8]) { + for b in bytes { + self.write_byte(*b); } - if Register::LineStatus.read() & LSR_TX_IDLE == 0 { - // The UART transmit holding register is full, - // so we cannot give it another byte. - // It will interrupt when it's ready for a new byte. - return; + } + /// Write a byte to the UART and buffer it. + /// Should not be used in interrupts. + pub fn write_byte_buffered(&self, b: u8) { + let guard = self.lock.lock(); + + if unsafe { crate::PANICKED } { + loop { + core::hint::spin_loop(); + } } - // let buf = uart_tx_buf.lock_unguarded(); - let c = uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE]; - uart_tx_r += 1; + while self.queue_start == self.queue_end + 1 { + unsafe { + guard.sleep(addr_of!(*self).cast_mut().cast()); + } + } - // Maybe uartputc() is waiting for space in the buffer. - wakeup(addr_of_mut!(uart_tx_r).cast()); + // Unsafely cast self as mutable. + // self.lock is held so it should be fine. + let this: &mut Uart = unsafe { &mut *addr_of!(*self).cast_mut() }; - Register::TransmitHolding.write(c); + // Add the byte onto the end of the queue. + this.buffer[this.queue_end] = b; + this.queue_end += 1; + this.queue_end %= this.buffer.len(); + + this.send_buffered_bytes(); + } + pub fn write_slice_buffered(&self, bytes: &[u8]) { + for b in bytes { + self.write_byte_buffered(*b); + } + } + /// If the UART is idle, and a character is + /// waiting in the transmit buffer, send it. + /// self.lock should be held. + fn send_buffered_bytes(&self) { + let this: &mut Uart = unsafe { &mut *addr_of!(*self).cast_mut() }; + + loop { + // Ensure the indices are correct. + this.queue_start %= this.buffer.len(); + this.queue_end %= this.buffer.len(); + + if this.queue_start == this.queue_end { + // The buffer is empty, we're finished sending bytes. + return; + } + if Register::LineStatus.read(this.base_address) & LSR_TX_IDLE == 0 { + // The UART transmit holding register is full, + // so we cannot give it another byte. + // It will interrupt when it's ready for a new byte. + return; + } + + // Pop a byte from the front of the queue. + let b = this.buffer[this.queue_start]; + this.queue_start += 1; + this.queue_start %= this.buffer.len(); + + // Maybe uartputc() is waiting for space in the buffer. + unsafe { + wakeup(addr_of!(*self).cast_mut().cast()); + } + + Register::TransmitHolding.write(this.base_address, b); + } } } -/// Read one input byte from the UART. -pub(crate) fn uartgetc() -> Option { - if Register::LineStatus.read() & 0x01 != 0 { - // Input data is ready. - Some(Register::ReceiveHolding.read()) - } else { - None - } -} - -/// Handle a UART interrupt, raised because input has -/// arrived, or the uart is ready for more output, or -/// both. Called from devintr(). -pub(crate) unsafe fn uartintr() { - // Read and process incoming characters. - while let Some(c) = uartgetc() { - consoleintr(c); - } - - // Send buffered characters. - let _guard = uart_tx_lock.lock(); - uartstart(); -} diff --git a/kernel/rustkernel/src/mem/virtual_memory.rs b/kernel/rustkernel/src/mem/virtual_memory.rs index 94cd01a..e67bb60 100644 --- a/kernel/rustkernel/src/mem/virtual_memory.rs +++ b/kernel/rustkernel/src/mem/virtual_memory.rs @@ -30,7 +30,7 @@ pub unsafe fn kvmmake() -> Pagetable { kvmmap(pagetable, QEMU_POWER, QEMU_POWER, PGSIZE, PTE_R | PTE_W); // UART registers - kvmmap(pagetable, UART0, UART0, PGSIZE, PTE_R | PTE_W); + kvmmap(pagetable, UART0 as u64, UART0 as u64, PGSIZE, PTE_R | PTE_W); // VirtIO MMIO disk interface kvmmap(pagetable, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W); diff --git a/kernel/rustkernel/src/riscv/memlayout.rs b/kernel/rustkernel/src/riscv/memlayout.rs index 25d0eaa..058e526 100644 --- a/kernel/rustkernel/src/riscv/memlayout.rs +++ b/kernel/rustkernel/src/riscv/memlayout.rs @@ -22,7 +22,7 @@ use super::{MAXVA, PGSIZE}; pub const QEMU_POWER: u64 = 0x100000u64; // QEMU puts UART registers here in physical memory. -pub const UART0: u64 = 0x10000000; +pub const UART0: usize = 0x10000000; pub const UART0_IRQ: i32 = 10; // Virtio MMIO interface diff --git a/kernel/rustkernel/src/trap.rs b/kernel/rustkernel/src/trap.rs index 9251519..98f265b 100644 --- a/kernel/rustkernel/src/trap.rs +++ b/kernel/rustkernel/src/trap.rs @@ -56,7 +56,7 @@ pub unsafe extern "C" fn devintr() -> i32 { let irq = plic::plic_claim(); if irq == UART0_IRQ { - crate::console::uart::uartintr(); + crate::console::uart::UART0.interrupt(); } else if irq == VIRTIO0_IRQ { virtio_disk_intr(); } else if irq > 0 {