Skip to content

spi: make SpiDevice transaction take an operation slice instead of a closure. #443

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

Merged
merged 1 commit into from
Apr 1, 2023

Conversation

Dirbaio
Copy link
Member

@Dirbaio Dirbaio commented Mar 14, 2023

As discussed in today's WG meeting.

@Dirbaio Dirbaio changed the title spi: SpiDevice transactiontake an operation slice instead of a closure. spi: make SpiDevice transaction take an operation slice instead of a closure. Mar 14, 2023
@Dirbaio Dirbaio force-pushed the spi-transaction-2 branch from 415f64c to 5cf01db Compare March 14, 2023 21:01
@Dirbaio Dirbaio marked this pull request as ready for review March 14, 2023 21:02
@Dirbaio Dirbaio requested a review from a team as a code owner March 14, 2023 21:02
@Dirbaio Dirbaio force-pushed the spi-transaction-2 branch from 5cf01db to 08f7e13 Compare March 14, 2023 21:05
Copy link
Member

@eldruin eldruin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an interesting idea, thank you!
However, I do not have everything discussed in issues like #35, #350, #351, #245 and #299 in my mind.
Those should be considered for evaluation.

TODO:

  • Update changelog

@eldruin eldruin added this to the v1.0.0 milestone Mar 15, 2023
@eldruin eldruin mentioned this pull request Mar 15, 2023
@Dirbaio Dirbaio force-pushed the spi-transaction-2 branch 2 times, most recently from 471b487 to b8b5489 Compare March 16, 2023 14:05
@ryankurte
Copy link
Contributor

hey seems reasonable to me! a couple of questions:

  • how do we represent busses with actual hardware CS, pass through a NullCS of some sort?
  • can we test this with a PR against linux-embedded-hal as well as another hal and a driver?

bors bot added a commit that referenced this pull request Mar 28, 2023
445: bus/i2c: add RefCell, CriticalSection and Mutex shared bus implementations. r=eldruin a=Dirbaio

Requires #440 

Same as #443 but for I2C.


This adds a few bus sharing implementations, with varying tradeoffs:
- `RefCellDevice`: single thread only
- `CriticalSectionDevice`: thread-safe, coarse locking, nostd.
- `MutexDevice`: thread-safe, fine-grained locking, std only.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
@Dirbaio
Copy link
Member Author

Dirbaio commented Mar 28, 2023

how do we represent busses with actual hardware CS, pass through a NullCS of some sort?

implementations with hardware CS would directly implement SpiDevice, not SpiBus. You dont take a "SpiBus with hardware CS" and then wrap it on an ExclusiveDevice with NullCS.

@Dirbaio
Copy link
Member Author

Dirbaio commented Mar 28, 2023

can we test this with a PR against linux-embedded-hal as well as another hal and a driver?

@ryankurte @eldruin @therealprof could you please take a look? I'd really like to get this unblocked :)

@Dirbaio Dirbaio force-pushed the spi-transaction-2 branch from d3836ad to 2217a92 Compare March 28, 2023 12:34
@Dirbaio Dirbaio force-pushed the spi-transaction-2 branch from 2217a92 to b6764ec Compare March 28, 2023 12:42
Copy link
Member

@eldruin eldruin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vollbrecht made an interesting comment about read_transaction but it seems only a mild HAL-impl-side inconvenience:

fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error>;

Its a lot of duplication here from transaction. The problem is the transaction takes an &mut [Operation<'_, u8>] but this read_transaction is &mut [&mut [u8]]. We would need something that converts the &mut [u8] in an Operation::Read

It seems to me like this should be fine to implement on linux-embedded-hal but we should check on it.

Copy link
Contributor

@ryankurte ryankurte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aight thanks for all your work / bearing with me / lgtm!

note that one consequence of the move from closures is that it is no longer possible to represent devices that need to do things within as CS assertion using SpiDevice, where this behaviour is required drivers should instead bind SpiBus with an OutputPin (ie. implement software/userspace CS control themselves)

It seems to me like this should be fine to implement on linux-embedded-hal but we should check on it.

worst case we can duplicate the spidev call which has no problems with a list of arrays (in fact, i believe we already have to throw around some vecs to map from operations to the actual ioctl)

@Dirbaio
Copy link
Member Author

Dirbaio commented Mar 29, 2023

@eldruin: @Vollbrecht made an interesting comment about read_transaction but it seems only a mild HAL-impl-side inconvenience:

Yes, it's somewhat annoying but I think it's better than taking a &[Operation] and then asserting at runtime that it has only reads (or only writes). And I can't think of any other solution...

The duplication also shows up in #444 . It won't affect most HALs though, as they'll likely implement only SpiBus and let the user use embedded-hal-bus to get an SpiDevice. The only reasons a HAL would implement SpiDevice is for hardware CS (which is very annoying and the perf benefits are questionable in MCUs), or when the kernel handles CS for you (like in esp-idf or Linux).

@eldruin: It seems to me like this should be fine to implement on linux-embedded-hal but we should check on it.

Yeah I think so too. It has to convert from either &[Operation] or &[&[u8]] to the linux ioctl struct anyway...

Copy link
Member

@eldruin eldruin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this in this week's meeting.
As @ryankurte also mentioned, there are some things that are not possible anymore.
@GrantM11235 Came up with two interesting examples of these:

// with the slices approach we would need to create copies of the data
fn send_data(&mut self, data: &[u16]) {
    self.spi.transaction( |bus|
        for chunk in data.chunks(32) {
            let mut buffer = [0_u16; 32];
            let mut buffer = &mut buffer[0..chunk.len()];
            buffer.copy_from_slice(chunk);

            for word in buffer {
                *word = word.to_be();
            }

            let buffer = transmute_u16_to_u8(buffer);

            bus.send(buffer);
        }
    )
}
// this version is more efficient than what would be possible with slices
fn send_packet(&mut self, packet: &[u8]) {
    let mut control_flow = Continue;
    while control_flow == Continue {
        self.spi.transaction( |bus| {
            let mut first_byte = [COMMAND_SEND];
            bus.transfer_inplace(&mut first_byte);
            let [status_byte] = first_byte;

            if check_can_send(status_byte) {
                bus.write(packet);
                control_flow = Break;
            }
        });
        // maybe add a delay here
    }
}

The MAX3421E driver might benefit from the closure-based approach.

On balance, it is clear that the slice-based approach is simpler to understand, to implement and to use while probably covering > 90% of the use cases as well as resolving the problem with the linux implementation.
As such, let's take the slices-based approach for 1.0 and let's discuss re-adding a closure-based approach in a future release.

Thank you everybody for your work, insights and the fruitful discussions.

bors r+

@bors bors bot merged commit a8ea55f into rust-embedded:master Apr 1, 2023
bors bot added a commit that referenced this pull request Apr 1, 2023
444: bus/spi: add RefCell, CriticalSection and Mutex shared bus implementations. r=eldruin a=Dirbaio

Requires #443 

This adds a few bus sharing implementations, with varying tradeoffs:
- `RefCellDevice`: single thread only
- `CriticalSectionDevice`: thread-safe, coarse locking, nostd.
- `MutexDevice`: thread-safe, fine-grained locking, std only.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
bors bot added a commit to embassy-rs/embassy that referenced this pull request Apr 6, 2023
1306: Update embedded-hal r=Dirbaio a=Dirbaio

- [x] Wait for merge rust-embedded/embedded-hal#443
- [x] Wait for release
- [x] embassy-embedded-hal
- [x] embassy-nrf
- [x] embassy-stm32
- [x] embassy-rp

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
bors bot added a commit to embassy-rs/embassy that referenced this pull request Apr 6, 2023
1306: Update embedded-hal r=Dirbaio a=Dirbaio

- [x] Wait for merge rust-embedded/embedded-hal#443
- [x] Wait for release
- [x] embassy-embedded-hal
- [x] embassy-nrf
- [x] embassy-stm32
- [x] embassy-rp

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
bors bot added a commit that referenced this pull request May 19, 2023
461: spi: remove write-only and read-only traits. r=Dirbaio a=Dirbaio

When introducing the Bus/Device split in #351, I kept the ability to represent "read-only" and "write-only" buses, with separate `SpiBusRead`, `SpiBusWrite` buses. This didn't have much discussion, as it was the logical consequence of keeping the separation in the already existing traits (`Read`, `Write`, `Transfer`, ...).

Later, in #443, when switching from the closure-based API to the operation-slice-based API, this required adding `SpiDeviceRead`, `SpiDeviceWrite` as well, because you can no longer put a bound on the underlying bus with the new API.

This means we now have *seven* traits, which we can reduce to *two* if we drop the distinction. So, is the distinction worth it?

I've always been on the fence about it, now I'm sort of leaning towards no.

First, using write-only or read-only SPI is rare. 
- write-only SPI: for SPI displays you don't want to read from, or to abuse it to bitbang stuff like ws2812b,
- read-only SPI: some ADCs that can't be configured at all, you just clock out bits. Or even weirder abuses, like to build a logic analyzer

Second, for it to be useful HALs have to track "read-onlyness / write-onlyness" in the type signature, so a read-only SPI really only implements `SpiBusRead` and not `SpiBus`. HALs already have enough generics. For example, Embassy HALs don't implement the distinction (you can create MOSI-only or MISO-only SPIs, but they all impl the full `SpiBus`, because I didn't want to make the types carry pin information).

Third, it makes the API easier to use. Simpler, users only have to import one trait, docs have all the methods in one place. Much less boilerplate to impl the traits (look at how shorter `embedded-hal-bus` gets!).

So I think we should remove them.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SPI implementation doesn't follow embedded-hal requirements for CS
3 participants