Skip to content

Clarify whether non-blocking or stateless is the more important aspect of nb #13

Open
@Nemo157

Description

@Nemo157

In reference to @japaric's comment here.

In my mind operations that return nb::Result signal that they can't be started and completed right
now via WouldBlock but once you get an Ok you know that the operation started and completed in one step, without blocking. This also means that operations that return nb::Result carry no program
state
-- there may be some state in the hardware registers or in the kernel but it's not on the
program stack / heap.

Unfortunately this is not always possible, as an example consider the embedded_hal::serial::Write implementation from the nrf51_hal crate:

pub struct Tx<UART> {
    _uart: PhantomData<UART>,
}

impl hal::serial::Write<u8> for Tx<UART0> {
    type Error = !;

    fn flush(&mut self) -> nb::Result<(), !> {
        Ok(())
    }

    fn write(&mut self, byte: u8) -> nb::Result<(), !> {
        /* Write one 8bit value */
        unsafe { (*UART0::ptr()).txd.write(|w| w.bits(u32::from(byte))) }

        /* Wait until written ... */
        while unsafe { (*UART0::ptr()).events_txdrdy.read().bits() } == 0 {}

        /* ... and clear read bit, there's no other way this will work */
        unsafe { (*UART0::ptr()).events_txdrdy.write(|w| w.bits(0)) };
        Ok(())
    }
}

This is currently a blocking implementation. To convert it to a non-blocking implementation we need to introduce some state of whether we're currently transmitting a byte (as far as I can tell, there is no way to derive this from the microcontroller registers):

pub struct Tx<UART> {
    _uart: PhantomData<UART>,
    busy: bool,
}

impl hal::serial::Write<u8> for Tx<UART0> {
    type Error = !;

    fn flush(&mut self) -> nb::Result<(), !> {
        let uart = unsafe { &*UART0::ptr() };
        if self.busy {
            if uart.events_txdrdy.read().bits() == 1 {
                uart.events_txdrdy.reset();
                self.busy = false;
                Ok(())
            } else {
                Err(nb::Error::WouldBlock)
            }
        } else {
            Ok(())
        }
    }

    fn write(&mut self, byte: u8) -> nb::Result<(), !> {
        let uart = unsafe { &*UART0::ptr() };
        self.flush()?;
        uart.txd.write(|w| unsafe { w.bits(u32::from(byte)) });
        self.busy = true;
        Ok(())
    }
}

In my mind the whole purpose of nb is to support implementing non-blocking IO primitives suitable for integrating into something like a futures event loop (although, I'm still trying to work out how to support interrupt-driven task notifications without having to have event loop specific implementations anyway...). If implementations are going to be forced to block to not have a tiny bit of important state, that undermines the entire utility of this crate.

Once this is clarified I will try and open a PR documenting this along with other stuff mentioned in that comment.

cc @therealprof (in case you have some other ideas about non-blocking uart support on the nrf51)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions