Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UartDriver as embedded_io::Read blocks until buffer is full #475

Closed
DaneSlattery opened this issue Aug 21, 2024 · 3 comments
Closed

UartDriver as embedded_io::Read blocks until buffer is full #475

DaneSlattery opened this issue Aug 21, 2024 · 3 comments

Comments

@DaneSlattery
Copy link
Contributor

DaneSlattery commented Aug 21, 2024

In the below code snippet, modified from the uart loopback.
We know that the uart TX has written 1 byte.
The receive buffer is [0;10] , which is typical when we don't know how large the receive buffer is.

In this case, the read blocks forever, instead of popping 1 byte from the UART buffer (into buf) and returning 1. If the buffer is [0;1], the read returns instantly.

This means that the embedded_io::Read contract is not being upheld:

/// Read some bytes from this source into the specified buffer, returning how many bytes were read.
    ///
    /// If no bytes are currently available to read, this function blocks until at least one byte is available.
    ///
    /// If bytes are available, a non-zero amount of bytes is read to the beginning of `buf`, and the amount
    /// is returned. It is not guaranteed that *all* available bytes are returned, it is possible for the
    /// implementation to read an amount of bytes less than `buf.len()` while there are more bytes immediately
    /// available.
    ///
    /// If the reader is at end-of-file (EOF), `Ok(0)` is returned. There is no guarantee that a reader at EOF
    /// will always be so in the future, for example a reader can stop being at EOF if another process appends
    /// more bytes to the underlying file.
    ///
    /// If `buf.len() == 0`, `read` returns without blocking, with either `Ok(0)` or an error.
    /// The `Ok(0)` doesn't indicate EOF, unlike when called with a non-empty buffer.
//! UART loopback test
//!
//! Folowing pins are used:
//! TX    GPIO12
//! RX    GPIO13
//!
//! Depending on your target and the board you are using you have to change the pins.
//!
//! This example transfers data via UART.
//! Connect TX and RX pins to see the outgoing data is read as incoming data.

use embedded_io::Read;
use esp_idf_hal::delay::BLOCK;
use esp_idf_hal::gpio;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::prelude::*;
use esp_idf_hal::uart::*;
use esp_idf_sys::EspError;

fn main() -> anyhow::Result<()> {
    esp_idf_hal::sys::link_patches();

    let peripherals = Peripherals::take()?;
    let tx = peripherals.pins.gpio12;
    let rx = peripherals.pins.gpio13;

    println!("Starting UART loopback test");
    let config = config::Config::new().baudrate(Hertz(115_200));
    let mut uart = UartDriver::new(
        peripherals.uart1,
        tx,
        rx,
        Option::<gpio::Gpio0>::None,
        Option::<gpio::Gpio1>::None,
        &config,
    )?;

    loop {
        uart.write(&[0xaa])?;


        read(&mut uart)?;


    }
}

fn read<R: embedded_io::Read>(r: &mut R) -> Result<(), anyhow::Error> {
    let mut buf = [0u8; 10];
    match r.read(&mut buf) {
        Err(x) => println!("Error reading"),
        Ok(x) => println!("Read :{:?} ", &buf[..x]),
    };
    Ok(())
}

The approach in UartRxDriver is to call uart_read_bytes with length=buffer.len().

I propose the following:

 /// Read multiple bytes into a slice; block until specified timeout
    pub fn read(&self, buf: &mut [u8], delay: TickType_t) -> Result<usize, EspError> {
        // uart_read_bytes() returns error (-1) or how many bytes were read out
        // 0 means timeout and nothing is yet read out
        let available = self.count()?;
        if (available > buf.len()) {
            // should we throw? or only read as many as the buffer has space for
        }

        let len = unsafe {
            uart_read_bytes(
                self.port(),
                buf.as_mut_ptr().cast(),
                available as u32,
                delay,
            )
        };

        if len >= 0 {
            Ok(len as usize)
        } else {
            Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>())
        }
    }
@ivmarkov
Copy link
Collaborator

ivmarkov commented Aug 22, 2024

UPDATED:

You might be right to request the semantics you request as I polled the "Embedded Rust" Matrix channel, and it seems the consensus is it should operate as you intuitively expect. Though - I must say and the other folks agreed - this is not explicitly documented on the embedded_io::Read trait, nor on the std::io::Read trait. The POSIX read and recv syscalls seem a bit more explicit but IMO still leave room for interpretation!

(
I'll probably PR an update of the docu of embedded_io::Read and embedded_io_async::Read to be more explicit in this regard. And will probably PR or at least open an issue for std::io::Read as it has similar vagueness that leaves room for interpretation.
)

With that said, I just pushed a potential fix to master, so if you could try it out?

The reason why I did not accept your proposal from above ^^^ verbatim is because it has several problems. For completeness, let me comment on these (look for the comments marked with "(Ivan)"):

    /// Read multiple bytes into a slice; block until specified timeout
    pub fn read(&self, buf: &mut [u8], delay: TickType_t) -> Result<usize, EspError> {
        // uart_read_bytes() returns error (-1) or how many bytes were read out
        // 0 means timeout and nothing is yet read out
        // (Ivan) -1 also might mean a timeout. -1 is returned when `uart_read_bytes` cannot lock the internal semaphore, which 
        // might happen if multiple threads try to call this `read` method
        let available = self.count()?;
        if (available > buf.len()) {
            // should we throw? or only read as many as the buffer has space for
            // (Ivan) We should only read as many bytes as the buffer has space for
        }

        // (Ivan) The problem with this code is when available = 0
        // In this case, the call below will return immediately (as that's how `uart_read_bytes` is implemented)
        // - yet - you want it to block until at least one byte becomes available, or until the `delay` expires
        let len = unsafe {
            uart_read_bytes(
                self.port(),
                buf.as_mut_ptr().cast(),
                available as u32,
                delay,
            )
        };

        if len >= 0 {
            Ok(len as usize)
        } else {
            Err(EspError::from_infallible::<ESP_ERR_INVALID_STATE>())
        }
    }

ivmarkov added a commit that referenced this issue Aug 22, 2024
@ivmarkov
Copy link
Collaborator

@DaneSlattery ^^^

@DaneSlattery
Copy link
Contributor Author

That fixes it, thank you! Happy with the solution you came to.

@github-project-automation github-project-automation bot moved this from Todo to Done in esp-rs Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

2 participants