Skip to content

Commit e81c744

Browse files
committed
zephyr: device: uart: Add IRQ mode
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]>
1 parent cea439d commit e81c744

File tree

2 files changed

+207
-1
lines changed

2 files changed

+207
-1
lines changed

zephyr/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ version = "0.2.2"
3939
# should be safe to build the crate even if the Rust code doesn't use it because of configs.
4040
features = ["alloc"]
4141

42+
# Gives us an ArrayDeque type to implement a basic ring buffer.
43+
[dependencies.arraydeque]
44+
version = "0.5.1"
45+
default-features = false
46+
4247
# These are needed at build time.
4348
# Whether these need to be vendored is an open question. They are not
4449
# used by the core Zephyr tree, but are needed by zephyr applications.

zephyr/src/device/uart.rs

+202-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
//! Simple (and unsafe) wrappers around USB devices.
22
3+
// TODO! Remove this.
4+
#![allow(dead_code)]
5+
#![allow(unused_variables)]
6+
7+
use arraydeque::ArrayDeque;
8+
39
use crate::raw;
410
use crate::error::{Error, Result, to_result_void, to_result};
11+
use crate::printkln;
12+
use crate::sys::sync::Semaphore;
13+
use crate::sync::{Arc, SpinMutex};
14+
use crate::time::{NoWait, Timeout};
515

6-
use core::ffi::{c_uchar, c_int};
16+
use core::ffi::{c_int, c_uchar, c_void};
17+
use core::ptr;
718

819
use super::Unique;
920

@@ -91,4 +102,194 @@ impl Uart {
91102
raw::uart_line_ctrl_get(self.device, item as u32, &mut result)
92103
}).map(|()| result)
93104
}
105+
106+
/// Set one of the UART line control values.
107+
pub unsafe fn line_ctrl_set(&mut self, item: LineControl, value: u32) -> Result<()> {
108+
to_result_void(unsafe {
109+
raw::uart_line_ctrl_set(self.device, item as u32, value)
110+
})
111+
}
112+
113+
/// Convert this UART into an async one.
114+
pub unsafe fn into_async(self) -> Result<UartAsync> {
115+
UartAsync::new(self)
116+
}
117+
118+
/// Convert into an IRQ one.
119+
pub unsafe fn into_irq(self) -> Result<UartIrq> {
120+
UartIrq::new(self)
121+
}
122+
}
123+
124+
/// The uart is safe to Send, as long as it is only used from one thread at a time. As such, it is
125+
/// not Sync.
126+
unsafe impl Send for Uart {}
127+
128+
/// This is the async interface to the uart.
129+
///
130+
/// Until we can analyze this for safety, it will just be declared as unsafe.
131+
///
132+
/// It is unclear from the docs what context this callback api is called from, so we will assume
133+
/// that it might be called from an irq. As such, we'll need to use a critical-section and it's
134+
/// mutex to protect the data.
135+
pub struct UartAsync();
136+
137+
impl UartAsync {
138+
/// Take a Uart device and turn it into an async interface.
139+
///
140+
/// TODO: Return the uart back if this fails.
141+
pub unsafe fn new(uart: Uart) -> Result<UartAsync> {
142+
let ret = unsafe {
143+
raw::uart_callback_set(uart.device, Some(async_callback), ptr::null_mut())
144+
};
145+
to_result_void(ret)?;
146+
Ok(UartAsync())
147+
}
148+
}
149+
150+
extern "C" fn async_callback(
151+
_dev: *const raw::device,
152+
_evt: *mut raw::uart_event,
153+
_user_data: *mut c_void,
154+
) {
155+
printkln!("Async");
156+
}
157+
158+
/// Size of the irq buffer used for UartIrq.
159+
///
160+
/// TODO: Make this a parameter of the type.
161+
const BUFFER_SIZE: usize = 256;
162+
163+
/// The "outer" struct holds the semaphore, and the mutex. The semaphore has to live outside of the
164+
/// mutex because it can only be waited on when the Mutex is not locked.
165+
struct IrqOuterData {
166+
read_sem: Semaphore,
167+
inner: SpinMutex<IrqInnerData>,
168+
}
169+
170+
/// Data for communication with the UART IRQ.
171+
struct IrqInnerData {
172+
/// The Ring buffer holding incoming and read data.
173+
buffer: ArrayDeque<u8, BUFFER_SIZE>,
174+
}
175+
176+
/// This is the irq-driven interface.
177+
pub struct UartIrq {
178+
/// The raw device.
179+
device: *const raw::device,
180+
/// Critical section protected data.
181+
data: Arc<IrqOuterData>,
182+
}
183+
184+
// UartIrq is also Send, !Sync, for the same reasons as for Uart.
185+
unsafe impl Send for UartIrq {}
186+
187+
impl UartIrq {
188+
/// Convert uart into irq driven one.
189+
pub unsafe fn new(uart: Uart) -> Result<UartIrq> {
190+
let data = Arc::new(IrqOuterData {
191+
read_sem: Semaphore::new(0, 1)?,
192+
inner: SpinMutex::new(IrqInnerData {
193+
buffer: ArrayDeque::new(),
194+
}),
195+
});
196+
197+
// Clone the arc, and convert to a raw pointer, to give to the callback.
198+
// This will leak the Arc (which prevents deallocation).
199+
let data_raw = Arc::into_raw(data.clone());
200+
let data_raw = data_raw as *mut c_void;
201+
202+
let ret = unsafe {
203+
raw::uart_irq_callback_user_data_set(uart.device, Some(irq_callback), data_raw)
204+
};
205+
to_result_void(ret)?;
206+
// Should this be settable?
207+
unsafe {
208+
raw::uart_irq_tx_enable(uart.device);
209+
raw::uart_irq_rx_enable(uart.device);
210+
}
211+
Ok(UartIrq {
212+
device: uart.device,
213+
data,
214+
})
215+
}
216+
217+
/// Get the underlying UART to be able to change line control and such.
218+
pub unsafe fn inner(&mut self) -> Uart {
219+
Uart {
220+
device: self.device
221+
}
222+
}
223+
224+
/// Attempt to read data from the UART into the buffer. If no data is available, it will
225+
/// attempt, once, to wait using the given timeout.
226+
///
227+
/// Returns the number of bytes that were read, with zero indicating that a timeout occurred.
228+
pub unsafe fn try_read<T>(&mut self, buf: &mut [u8], timeout: T) -> usize
229+
where T: Into<Timeout>,
230+
{
231+
// Start with a read, before any blocking.
232+
let count = self.data.try_read(buf);
233+
if count > 0 {
234+
return count;
235+
}
236+
237+
// Otherwise, wait for the semaphore. Ignore the result, as we will try to read again, in
238+
// case there was a race.
239+
let _ = self.data.read_sem.take(timeout);
240+
241+
self.data.try_read(buf)
242+
}
243+
}
244+
245+
impl IrqOuterData {
246+
/// Try reading from the inner data, filling the buffer with as much data as makes sense.
247+
/// Returns the number of bytes actually read, or Zero if none.
248+
fn try_read(&self, buf: &mut [u8]) -> usize {
249+
let mut inner = self.inner.lock().unwrap();
250+
let mut pos = 0;
251+
while pos < buf.len() {
252+
if let Some(elt) = inner.buffer.pop_front() {
253+
buf[pos] = elt;
254+
pos += 1;
255+
} else {
256+
break;
257+
}
258+
}
259+
260+
if pos > 0 {
261+
// Any time we do a read, clear the semaphore.
262+
let _ = self.read_sem.take(NoWait);
263+
}
264+
pos
265+
}
266+
}
267+
268+
extern "C" fn irq_callback(
269+
dev: *const raw::device,
270+
user_data: *mut c_void,
271+
) {
272+
// Convert our user data, back to the CS Mutex.
273+
let outer = unsafe { &*(user_data as *const IrqOuterData) };
274+
let mut inner = outer.inner.lock().unwrap();
275+
276+
// TODO: Make this more efficient.
277+
let mut byte = 0u8;
278+
let mut did_read = false;
279+
loop {
280+
match unsafe { raw::uart_fifo_read(dev, &mut byte, 1) } {
281+
0 => break,
282+
1 => {
283+
// TODO: should we warn about overflow here?
284+
let _ = inner.buffer.push_back(byte);
285+
did_read = true;
286+
}
287+
e => panic!("Uart fifo read not implemented: {}", e),
288+
}
289+
}
290+
291+
// This is safe (and important) to do while the mutex is held.
292+
if did_read {
293+
outer.read_sem.give();
294+
}
94295
}

0 commit comments

Comments
 (0)