Skip to content

Commit 69930a4

Browse files
committed
zephyr: device: uart: Implement a blocking transmit
There are a lot of comments here trying to explain how this implementation works. This is complicated by the fact that the interface described in zephyr/drivers/uart.h doesn't actually allow data to ever be transmitted. I make a few assumptions here: - The part about not being able to call `fifo_fill` outside of the interrupt is just plain wrong. Since the irq isn't called until a transmit has finished, keeping this constaint would mean no data could ever be transmitted. - Rather than come up with come complex way of sharing the transmitted data with the irq handler (which we do with receive), we assume that it is permissible to just disable the tx irq from the handler, and can turn it back on when writes to the fifo fill it up completely. I've tested this with an CDC/ACM endpoint, and probably should come up with a better test for at least some kind of real UART. It looks like the pl011 uart driver actually tries to implement a workaround for this nonsensically described interface, and given that, it maybe actually be the case that this current implementation will just get spammed with these software irqs. There is also an async interface, but I don't seem to have any uarts that actually implement it. All in all, uart.h in Zephyr needs major work, as these interfaces are ill-defined. Signed-off-by: David Brown <[email protected]>
1 parent e81c744 commit 69930a4

File tree

1 file changed

+94
-2
lines changed

1 file changed

+94
-2
lines changed

zephyr/src/device/uart.rs

+94-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::error::{Error, Result, to_result_void, to_result};
1111
use crate::printkln;
1212
use crate::sys::sync::Semaphore;
1313
use crate::sync::{Arc, SpinMutex};
14-
use crate::time::{NoWait, Timeout};
14+
use crate::time::{Forever, NoWait, Timeout};
1515

1616
use core::ffi::{c_int, c_uchar, c_void};
1717
use core::ptr;
@@ -164,13 +164,16 @@ const BUFFER_SIZE: usize = 256;
164164
/// mutex because it can only be waited on when the Mutex is not locked.
165165
struct IrqOuterData {
166166
read_sem: Semaphore,
167+
write_sem: Semaphore,
167168
inner: SpinMutex<IrqInnerData>,
168169
}
169170

170171
/// Data for communication with the UART IRQ.
171172
struct IrqInnerData {
172173
/// The Ring buffer holding incoming and read data.
173174
buffer: ArrayDeque<u8, BUFFER_SIZE>,
175+
/// An indicator to the irq if we are waiting for data.
176+
write_waiting: bool,
174177
}
175178

176179
/// This is the irq-driven interface.
@@ -189,8 +192,10 @@ impl UartIrq {
189192
pub unsafe fn new(uart: Uart) -> Result<UartIrq> {
190193
let data = Arc::new(IrqOuterData {
191194
read_sem: Semaphore::new(0, 1)?,
195+
write_sem: Semaphore::new(0, 1)?,
192196
inner: SpinMutex::new(IrqInnerData {
193197
buffer: ArrayDeque::new(),
198+
write_waiting: false,
194199
}),
195200
});
196201

@@ -205,7 +210,7 @@ impl UartIrq {
205210
to_result_void(ret)?;
206211
// Should this be settable?
207212
unsafe {
208-
raw::uart_irq_tx_enable(uart.device);
213+
// raw::uart_irq_tx_enable(uart.device);
209214
raw::uart_irq_rx_enable(uart.device);
210215
}
211216
Ok(UartIrq {
@@ -240,6 +245,28 @@ impl UartIrq {
240245

241246
self.data.try_read(buf)
242247
}
248+
249+
// How the irq handler, and the fifo are documented in uart.h, they are unusable.
250+
//
251+
/// Write data to the UART. At this point, this is blocking. How this interacts with DSR is
252+
/// unclear.
253+
///
254+
/// TODO: Implement a timeout. This will need to be able to update the time of the timeout,
255+
/// which is not yet implemented in Rust.
256+
pub unsafe fn write(&mut self, mut buf: &[u8]) {
257+
while buf.len() > 0 {
258+
let count = self.data.try_write(self.device, buf);
259+
260+
if count == buf.len() {
261+
// The write has completed.
262+
break
263+
}
264+
265+
buf = &buf[count..];
266+
// Wait for the write semaphore.
267+
let _ = self.data.write_sem.take(Forever);
268+
}
269+
}
243270
}
244271

245272
impl IrqOuterData {
@@ -263,6 +290,51 @@ impl IrqOuterData {
263290
}
264291
pos
265292
}
293+
294+
/// Write as much data from the buffer as we can to the device fifo. When this fails to write
295+
/// any more, return how much was written.
296+
///
297+
/// The docs for uart_fifo_fill describe a function that actually can't ever be called, since it
298+
/// claims it can only be called from irq context, but the irq isn't called until a transmit is
299+
/// done. It is unclear what values are used for watermarks, or anything like that.
300+
///
301+
/// We're going to make an assumption here that is _is_ permissible to call this outside of the
302+
/// irq context, and that it is safe for the irq to just give a semaphore, and then turn off the
303+
/// interupt.
304+
///
305+
/// This use of the irq enable is undefined, possibly racy, so to make is possibly more robust,
306+
/// we will enable the irq before we try writing, and then turn it off if we think we have
307+
/// written everything. The hope is that it is safer to wake up more than we need to, rather
308+
/// than less.
309+
///
310+
/// As such, we turn on the irq in this function, and then disable it if the amount written is
311+
/// equal to the entire buffer.
312+
fn try_write(&self, device: *const raw::device, buf: &[u8]) -> usize {
313+
let mut inner = self.inner.lock().unwrap();
314+
315+
let count = unsafe {
316+
raw::uart_fifo_fill(device, buf.as_ptr(), buf.len() as i32)
317+
};
318+
if count < 0 {
319+
panic!("Incorrect use of device fifo");
320+
}
321+
322+
let count = count as usize;
323+
324+
// It is not clear whether it is safe to turn on the irq at the end, or if that needs to
325+
// happen at the begining. For a conventional device, since this code is running with
326+
// interrupts masked, it is likely to finish before a character could finish transmitting,
327+
// so it won't ever be seen.
328+
if count < buf.len() {
329+
// If we have more to write, indicate that we want an irq.
330+
unsafe {
331+
raw::uart_irq_tx_enable(device);
332+
inner.write_waiting = true;
333+
}
334+
}
335+
336+
count
337+
}
266338
}
267339

268340
extern "C" fn irq_callback(
@@ -292,4 +364,24 @@ extern "C" fn irq_callback(
292364
if did_read {
293365
outer.read_sem.give();
294366
}
367+
368+
// We get the same IRQ for the write, but have no way of knowing if that is why we are called.
369+
// To be safe, if there is someone waiting, we'll turn off the irq, and give the semaphore.
370+
// This means what with full-duplex operation, the writer will wake for every read interrupt,
371+
// even if there is still no room in the fifo. We'll only do this if there is data available in
372+
// the fifo.
373+
if !inner.write_waiting {
374+
// Just finish early, the write irq shouldn't be on.
375+
return;
376+
}
377+
378+
let tx_ready = unsafe { raw::uart_irq_tx_ready(dev) };
379+
if tx_ready < 0 {
380+
panic!("tx ready call returned an error");
381+
}
382+
383+
if tx_ready > 0 {
384+
outer.write_sem.give();
385+
inner.write_waiting = false;
386+
}
295387
}

0 commit comments

Comments
 (0)