Skip to content

Commit

Permalink
Move PL011 driver into the main workspace (#1040)
Browse files Browse the repository at this point in the history
We now use that driver to implement the serial port on aarch64.
  • Loading branch information
NathanRoyer authored Oct 3, 2023
1 parent ac51fc1 commit 0bd4447
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 34 deletions.
22 changes: 11 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 8 additions & 6 deletions kernel/serial_port/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ pub fn init_serial_port(
serial_port_address: SerialPortAddress,
serial_port: SerialPortBasic,
) -> Option<&'static Arc<IrqSafeMutex<SerialPort>>> {
// Note: if we're called by device_manager, we cannot log (as we're modifying the logger config)

#[cfg(target_arch = "aarch64")]
if serial_port_address != SerialPortAddress::COM1 {
return None;
Expand Down Expand Up @@ -309,7 +311,7 @@ fn serial_port_receive_deferred(
let mut buf = DataChunk::empty();
let bytes_read;
let base_port;

let mut input_was_ignored = false;
let mut send_result = Ok(());

Expand All @@ -331,9 +333,6 @@ fn serial_port_receive_deferred(
// other than data being received, which is the only one we currently care about.
return Ok(());
}

#[cfg(target_arch = "aarch64")]
sp.enable_interrupt(SerialPortInterruptEvent::DataReceived, true);
}

if let Err(e) = send_result {
Expand Down Expand Up @@ -387,15 +386,18 @@ static INTERRUPT_ACTION_COM2_COM4: Once<Box<dyn Fn() + Send + Sync>> = Once::new

// Cross-platform interrupt handler for COM1 and COM3 (IRQ 0x24 on x86_64).
interrupt_handler!(com1_com3_interrupt_handler, Some(interrupts::IRQ_BASE_OFFSET + 0x4), _stack_frame, {
// trace!("COM1/COM3 serial handler");
// log::trace!("COM1/COM3 serial handler");

#[cfg(target_arch = "aarch64")] {
let mut sp = COM1_SERIAL_PORT.get().unwrap().as_ref().lock();
sp.enable_interrupt(SerialPortInterruptEvent::DataReceived, false);
sp.acknowledge_interrupt(SerialPortInterruptEvent::DataReceived);
}

if let Some(func) = INTERRUPT_ACTION_COM1_COM3.get() {
func()
}

// log::trace!("COM1/COM3 serial handler done");
EoiBehaviour::HandlerDidNotSendEoi
});

Expand Down
3 changes: 1 addition & 2 deletions kernel/serial_port_basic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ spin = "0.9.4"
port_io = { path = "../../libs/port_io" }

[target.'cfg(target_arch = "aarch64")'.dependencies]
pl011 = { git = "https://github.com/theseus-os/pl011/", rev = "464dbf22" }
uart_pl011 = { path = "../uart_pl011" }
arm_boards = { path = "../arm_boards" }
memory = { path = "../memory" }

[lib]
crate-type = ["rlib"]
29 changes: 14 additions & 15 deletions kernel/serial_port_basic/src/aarch64.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use memory::{MappedPages, PAGE_SIZE, map_frame_range, MMIO_FLAGS};
use super::{TriState, SerialPortInterruptEvent};
use arm_boards::BOARD_CONFIG;
use pl011::PL011;
use uart_pl011::Pl011;
use core::fmt;

/// The base port I/O addresses for COM serial ports.
Expand All @@ -19,11 +18,10 @@ pub enum SerialPortAddress {
}

/// A serial port and its various data and control registers.
#[derive(Debug)]
pub struct SerialPort {
port_address: SerialPortAddress,
inner: Option<PL011>,
// Owner of the MMIO frames for the PL011 registers
_mapped_pages: Option<MappedPages>,
inner: Option<Pl011>,
}

impl Drop for SerialPort {
Expand All @@ -33,7 +31,6 @@ impl Drop for SerialPort {
if let TriState::Taken = &*sp_locked {
let dummy = SerialPort {
inner: None,
_mapped_pages: None,
port_address: self.port_address,
};
let dropped = core::mem::replace(self, dummy);
Expand All @@ -57,19 +54,12 @@ impl SerialPort {
None => panic!("Board doesn't have {:?}", serial_port_address),
};

let mapped_pages = map_frame_range(*mmio_base, PAGE_SIZE, MMIO_FLAGS)
.expect("serial_port_basic: couldn't map the UART interface");
let addr = mapped_pages.start_address().value();
let mut pl011 = PL011::new(addr as *mut _);

pl011.enable_rx_interrupt(true);
pl011.set_fifo_mode(false);
// pl011.log_status();
let pl011 = Pl011::new(*mmio_base)
.expect("SerialPort::new: Couldn't initialize PL011 UART");

SerialPort {
port_address: serial_port_address,
inner: Some(pl011),
_mapped_pages: Some(mapped_pages),
}
}

Expand All @@ -84,6 +74,15 @@ impl SerialPort {
}
}

/// Clears an interrupt in the serial port controller
pub fn acknowledge_interrupt(&mut self, event: SerialPortInterruptEvent) {
if matches!(event, SerialPortInterruptEvent::DataReceived) {
self.inner.as_mut().unwrap().acknowledge_rx_interrupt();
} else {
unimplemented!()
}
}

/// Write the given string to the serial port, blocking until data can be transmitted.
///
/// # Special characters
Expand Down
5 changes: 5 additions & 0 deletions kernel/serial_port_basic/src/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,11 @@ impl SerialPort {
}
}

/// Clears an interrupt in the serial port controller
pub fn acknowledge_interrupt(&mut self, _event: SerialPortInterruptEvent) {
// no-op on x86_64
}

/// Write the given string to the serial port, blocking until data can be transmitted.
///
/// # Special characters
Expand Down
12 changes: 12 additions & 0 deletions kernel/uart_pl011/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "uart_pl011"
authors = ["Nathan Royer <[email protected]>"]
description = "Simple Driver for PL011 UARTs"
version = "0.1.0"
edition = "2021"

[dependencies]
log = "0.4.8"
volatile = "0.2.7"
zerocopy = "0.5.0"
memory = { path = "../memory" }
192 changes: 192 additions & 0 deletions kernel/uart_pl011/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Driver for pl011 UARTs
#![no_std]
use core::fmt;
use zerocopy::FromBytes;
use volatile::{Volatile, ReadOnly, WriteOnly};
use memory::{BorrowedMappedPages, Mutable, PhysicalAddress, PAGE_SIZE, map_frame_range, MMIO_FLAGS};

/// Struct representing Pl011 registers. Not intended to be directly used
#[derive(Debug, FromBytes)]
#[repr(C)]
pub struct Pl011_Regs {
/// Data Register
pub uartdr: Volatile<u32>,
/// receive status / error clear
pub uartrsr: Volatile<u32>,
reserved0: [u32; 4],
/// flag register
pub uartfr: ReadOnly<u32>,
reserved1: u32,
/// IrDA Low power counter register
pub uartilpr: Volatile<u32>,
/// integer baud rate
pub uartibrd: Volatile<u32>,
/// fractional baud rate
pub uartfbrd: Volatile<u32>,
/// line control
pub uartlcr_h: Volatile<u32>,
/// control
pub uartcr: Volatile<u32>,
/// interrupt fifo level select
pub uartifls: Volatile<u32>,
/// interrupt mask set/clear
pub uartimsc: Volatile<u32>,
/// raw interrupt status
pub uartris: ReadOnly<u32>,
/// masked interrupt status
pub uartmis: ReadOnly<u32>,
/// interrupt clear
pub uarticr: WriteOnly<u32>,
/// dma control
pub uartdmacr: Volatile<u32>,
reserved2: [u32; 997],
/// UART Periph ID0
pub uartperiphid0: ReadOnly<u32>,
/// UART Periph ID1
pub uartperiphid1: ReadOnly<u32>,
/// UART Periph ID2
pub uartperiphid2: ReadOnly<u32>,
/// UART Periph ID3
pub uartperiphid3: ReadOnly<u32>,
/// UART PCell ID0
pub uartpcellid0: ReadOnly<u32>,
/// UART PCell ID1
pub uartpcellid1: ReadOnly<u32>,
/// UART PCell ID2
pub uartpcellid2: ReadOnly<u32>,
/// UART PCell ID3
pub uartpcellid3: ReadOnly<u32>,
}

const UARTIMSC_RXIM: u32 = 1 << 4;
const UARTUCR_RXIC: u32 = 1 << 4;

const UARTLCR_FEN: u32 = 1 << 4;

const UARTCR_RX_ENABLED: u32 = 1 << 9;
const UARTCR_TX_ENABLED: u32 = 1 << 8;
const UARTCR_UART_ENABLED: u32 = 1 << 0;

const UARTFR_RX_BUF_EMPTY: u32 = 1 << 4;
const UARTFR_TX_BUF_FULL: u32 = 1 << 5;

/// A Pl011 Single-Serial-Port Controller.
pub struct Pl011 {
regs: BorrowedMappedPages<Pl011_Regs, Mutable>
}

impl core::fmt::Debug for Pl011 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.regs.fmt(f)
}
}

/// Generic methods
impl Pl011 {
/// Initialize a UART driver.
pub fn new(base: PhysicalAddress) -> Result<Self, &'static str> {
let mapped_pages = map_frame_range(base, PAGE_SIZE, MMIO_FLAGS)?;

let mut this = Self {
regs: mapped_pages.into_borrowed_mut(0).map_err(|(_, e)| e)?,
};

this.enable_rx_interrupt(true);
this.set_fifo_mode(false);

Ok(this)
}

/// Enable on-receive interrupt
pub fn enable_rx_interrupt(&mut self, enable: bool) {
let mut reg = self.regs.uartimsc.read();

match enable {
true => reg |= UARTIMSC_RXIM,
false => reg &= !UARTIMSC_RXIM,
};

self.regs.uartimsc.write(reg);
}

pub fn acknowledge_rx_interrupt(&mut self) {
self.regs.uarticr.write(UARTUCR_RXIC);
}

/// Set FIFO mode
pub fn set_fifo_mode(&mut self, enable: bool) {
let mut reg = self.regs.uartlcr_h.read();

match enable {
true => reg |= UARTLCR_FEN,
false => reg &= !UARTLCR_FEN,
};

self.regs.uartlcr_h.write(reg);
}

/// Outputs a summary of the state of the controller using `log::info!()`
pub fn log_status(&self) {
let reg = self.regs.uartcr.read();
log::info!("RX enabled: {}", (reg & UARTCR_RX_ENABLED) > 0);
log::info!("TX enabled: {}", (reg & UARTCR_TX_ENABLED) > 0);
log::info!("UART enabled: {}", (reg & UARTCR_UART_ENABLED) > 0);
}

/// Returns true if the receive-buffer-empty flag is clear.
pub fn has_incoming_data(&self) -> bool {
let uartfr = self.regs.uartfr.read();
uartfr & UARTFR_RX_BUF_EMPTY == 0
}

/// Reads a single byte out the uart
///
/// Spins until a byte is available in the fifo.
pub fn read_byte(&self) -> u8 {
while !self.has_incoming_data() {}
self.regs.uartdr.read() as u8
}

/// Reads bytes into a slice until there is none available.
pub fn read_bytes(&self, bytes: &mut [u8]) -> usize {
let mut read = 0;

while read < bytes.len() && self.has_incoming_data() {
bytes[read] = self.read_byte();
read += 1;
}

read
}

/// Returns true if the transmit-buffer-full flag is clear.
pub fn is_writeable(&self) -> bool {
let uartfr = self.regs.uartfr.read();
uartfr & UARTFR_TX_BUF_FULL == 0
}

/// Writes a single byte out the uart.
///
/// Spins until space is available in the fifo.
pub fn write_byte(&mut self, data: u8) {
while !self.is_writeable() {}
self.regs.uartdr.write(data as u32);
}

/// Writes a byte slice out the uart.
///
/// Spins until space is available in the fifo.
pub fn write_bytes(&mut self, bytes: &[u8]) {
for b in bytes {
self.write_byte(*b);
}
}
}

impl fmt::Write for Pl011 {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_bytes(s.as_bytes());
Ok(())
}
}

0 comments on commit 0bd4447

Please sign in to comment.