Make UART good
This commit is contained in:
parent
585da48917
commit
a240d29135
@ -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.
|
||||
|
@ -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<Uart> = 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<u8> {
|
||||
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<u8> {
|
||||
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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user