Skip to content

Commit

Permalink
zephyr: device: uart: Add IRQ mode
Browse files Browse the repository at this point in the history
Add an `.into_irq()` to the Uart device that turns it into an interrupt
driven interface.  Currently, read is implemented, with a `.try_read()`
method that will try, with a timeout, to read data from the interface.

The methods are all marked as 'unsafe' currently, until a more thorough
safety analysis can be made.

Signed-off-by: David Brown <[email protected]>
  • Loading branch information
d3zd3z committed Oct 31, 2024
1 parent cea439d commit e81c744
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 1 deletion.
5 changes: 5 additions & 0 deletions zephyr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ version = "0.2.2"
# should be safe to build the crate even if the Rust code doesn't use it because of configs.
features = ["alloc"]

# Gives us an ArrayDeque type to implement a basic ring buffer.
[dependencies.arraydeque]
version = "0.5.1"
default-features = false

# These are needed at build time.
# Whether these need to be vendored is an open question. They are not
# used by the core Zephyr tree, but are needed by zephyr applications.
Expand Down
203 changes: 202 additions & 1 deletion zephyr/src/device/uart.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
//! Simple (and unsafe) wrappers around USB devices.
// TODO! Remove this.
#![allow(dead_code)]
#![allow(unused_variables)]

use arraydeque::ArrayDeque;

use crate::raw;
use crate::error::{Error, Result, to_result_void, to_result};
use crate::printkln;
use crate::sys::sync::Semaphore;
use crate::sync::{Arc, SpinMutex};
use crate::time::{NoWait, Timeout};

use core::ffi::{c_uchar, c_int};
use core::ffi::{c_int, c_uchar, c_void};
use core::ptr;

use super::Unique;

Expand Down Expand Up @@ -91,4 +102,194 @@ impl Uart {
raw::uart_line_ctrl_get(self.device, item as u32, &mut result)
}).map(|()| result)
}

/// Set one of the UART line control values.
pub unsafe fn line_ctrl_set(&mut self, item: LineControl, value: u32) -> Result<()> {
to_result_void(unsafe {
raw::uart_line_ctrl_set(self.device, item as u32, value)
})
}

/// Convert this UART into an async one.
pub unsafe fn into_async(self) -> Result<UartAsync> {
UartAsync::new(self)
}

/// Convert into an IRQ one.
pub unsafe fn into_irq(self) -> Result<UartIrq> {
UartIrq::new(self)
}
}

/// The uart is safe to Send, as long as it is only used from one thread at a time. As such, it is
/// not Sync.
unsafe impl Send for Uart {}

/// This is the async interface to the uart.
///
/// Until we can analyze this for safety, it will just be declared as unsafe.
///
/// It is unclear from the docs what context this callback api is called from, so we will assume
/// that it might be called from an irq. As such, we'll need to use a critical-section and it's
/// mutex to protect the data.
pub struct UartAsync();

impl UartAsync {
/// Take a Uart device and turn it into an async interface.
///
/// TODO: Return the uart back if this fails.
pub unsafe fn new(uart: Uart) -> Result<UartAsync> {
let ret = unsafe {
raw::uart_callback_set(uart.device, Some(async_callback), ptr::null_mut())
};
to_result_void(ret)?;
Ok(UartAsync())
}
}

extern "C" fn async_callback(
_dev: *const raw::device,
_evt: *mut raw::uart_event,
_user_data: *mut c_void,
) {
printkln!("Async");
}

/// Size of the irq buffer used for UartIrq.
///
/// TODO: Make this a parameter of the type.
const BUFFER_SIZE: usize = 256;

/// The "outer" struct holds the semaphore, and the mutex. The semaphore has to live outside of the
/// mutex because it can only be waited on when the Mutex is not locked.
struct IrqOuterData {
read_sem: Semaphore,
inner: SpinMutex<IrqInnerData>,
}

/// Data for communication with the UART IRQ.
struct IrqInnerData {
/// The Ring buffer holding incoming and read data.
buffer: ArrayDeque<u8, BUFFER_SIZE>,
}

/// This is the irq-driven interface.
pub struct UartIrq {
/// The raw device.
device: *const raw::device,
/// Critical section protected data.
data: Arc<IrqOuterData>,
}

// UartIrq is also Send, !Sync, for the same reasons as for Uart.
unsafe impl Send for UartIrq {}

impl UartIrq {
/// Convert uart into irq driven one.
pub unsafe fn new(uart: Uart) -> Result<UartIrq> {
let data = Arc::new(IrqOuterData {
read_sem: Semaphore::new(0, 1)?,
inner: SpinMutex::new(IrqInnerData {
buffer: ArrayDeque::new(),
}),
});

// Clone the arc, and convert to a raw pointer, to give to the callback.
// This will leak the Arc (which prevents deallocation).
let data_raw = Arc::into_raw(data.clone());
let data_raw = data_raw as *mut c_void;

let ret = unsafe {
raw::uart_irq_callback_user_data_set(uart.device, Some(irq_callback), data_raw)
};
to_result_void(ret)?;
// Should this be settable?
unsafe {
raw::uart_irq_tx_enable(uart.device);
raw::uart_irq_rx_enable(uart.device);
}
Ok(UartIrq {
device: uart.device,
data,
})
}

/// Get the underlying UART to be able to change line control and such.
pub unsafe fn inner(&mut self) -> Uart {
Uart {
device: self.device
}
}

/// Attempt to read data from the UART into the buffer. If no data is available, it will
/// attempt, once, to wait using the given timeout.
///
/// Returns the number of bytes that were read, with zero indicating that a timeout occurred.
pub unsafe fn try_read<T>(&mut self, buf: &mut [u8], timeout: T) -> usize
where T: Into<Timeout>,
{
// Start with a read, before any blocking.
let count = self.data.try_read(buf);
if count > 0 {
return count;
}

// Otherwise, wait for the semaphore. Ignore the result, as we will try to read again, in
// case there was a race.
let _ = self.data.read_sem.take(timeout);

self.data.try_read(buf)
}
}

impl IrqOuterData {
/// Try reading from the inner data, filling the buffer with as much data as makes sense.
/// Returns the number of bytes actually read, or Zero if none.
fn try_read(&self, buf: &mut [u8]) -> usize {
let mut inner = self.inner.lock().unwrap();
let mut pos = 0;
while pos < buf.len() {
if let Some(elt) = inner.buffer.pop_front() {
buf[pos] = elt;
pos += 1;
} else {
break;
}
}

if pos > 0 {
// Any time we do a read, clear the semaphore.
let _ = self.read_sem.take(NoWait);
}
pos
}
}

extern "C" fn irq_callback(
dev: *const raw::device,
user_data: *mut c_void,
) {
// Convert our user data, back to the CS Mutex.
let outer = unsafe { &*(user_data as *const IrqOuterData) };
let mut inner = outer.inner.lock().unwrap();

// TODO: Make this more efficient.
let mut byte = 0u8;
let mut did_read = false;
loop {
match unsafe { raw::uart_fifo_read(dev, &mut byte, 1) } {
0 => break,
1 => {
// TODO: should we warn about overflow here?
let _ = inner.buffer.push_back(byte);
did_read = true;
}
e => panic!("Uart fifo read not implemented: {}", e),
}
}

// This is safe (and important) to do while the mutex is held.
if did_read {
outer.read_sem.give();
}
}

0 comments on commit e81c744

Please sign in to comment.