-
Notifications
You must be signed in to change notification settings - Fork 3k
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
RFC SPI: Reference Implementation. #8445
RFC SPI: Reference Implementation. #8445
Conversation
sigh I think the feature branch needs to be updated. |
Ooor maybe this PR needs work? I dunno. The specific failure in cloud-client-test:
|
we might need to rerebase the feature branch indeed.
and
These are expected failures because the port of the driver is only valid for the FRDM_K66F and the NUCLEO_F429ZI. |
drivers/SPI.cpp
Outdated
@@ -16,97 +16,146 @@ | |||
#include "drivers/SPI.h" | |||
#include "platform/mbed_critical.h" | |||
|
|||
#if DEVICE_SPI_ASYNCH | |||
#if 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this code disabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because the async API in the driver is not very clear on :
- Locks :
- Shall lock match the lock of the peripheral (thread safety of the peripheral)
- Shall lock match the lock of the driver instance to race (thread safety of the instance)
- chip select behaviours
- How should that behave when chip select is handled in hardware ?
- What is the relation between the chip select and the locks ?
drivers/SPI.cpp
Outdated
struct SPI::spi_peripheral_s *result = NULL; | ||
core_util_critical_section_enter(); | ||
for (uint32_t idx = 0; idx < SPI_COUNT; idx++) { | ||
printf("SPI::lookup(%08x) found at %lu", name, idx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
printf should be removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
drivers/SPI.cpp
Outdated
} | ||
|
||
int SPI::write(int value) | ||
{ | ||
lock(); | ||
_acquire(); | ||
int ret = spi_master_write(&_spi, value); | ||
uint32_t ret = 0; | ||
spi_transfer(&_self->spi, &value, _bits/8, &ret, _bits/8, NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll probably want to round up here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed ! Fixed :)
drivers/SPISlave.cpp
Outdated
@@ -45,17 +45,17 @@ void SPISlave::frequency(int hz) | |||
|
|||
int SPISlave::receive(void) | |||
{ | |||
return (spi_slave_receive(&_spi)); | |||
return 0;//(spi_slave_receive(&_spi)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is function commented out? Was the intent to remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like I forgot the commit SPISlave driver changes. This should be ok now.
@@ -185,7 +185,7 @@ const PinMap PinMap_SPI_SCLK[] = { | |||
{NC , NC , 0} | |||
}; | |||
|
|||
const PinMap PinMap_SPI_MOSI[] = { | |||
const PinMap PinMap_SPI_SOUT[] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this changed from MOSI to SOUT? Same for MISO to SIN below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because on Freescale's targets the pins aren't MISO & MOSI. If you switch from master to slave, the direction of the pin will not be changed to match it's new role.
On Freescale's targets, the SIN is always the input what ever the mode (master or slave) of the peripheral.
} else { | ||
_acquire(); | ||
} | ||
unlock(); | ||
} | ||
|
||
void SPI::frequency(int hz) | ||
uint32_t SPI::frequency(uint32_t hz) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable/Parameter types should be consistent in driver. Existing driver uses int
, char
.. etc, changed here for few API's to uint32_t
uint8_t
and not all. I would expect API's to be consistent in a driver.
Also if we are changing the return type, should we deprecate the existing one? In case of format
for addition of new parameter we are adding new API, but old is not deprecated and for frequency
we are changing the return type and parameter type of API without any deprecation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes to this driver are provisional and meant to be minimal.
Any change to bring consistency may break existing API.
We cannot deprecate the void returning version of SPI::frequency
because return types are not "overloadable". Also, going from void
to uint32_t
do not present risk on ignoring this return value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bus-separating logic here will be pretty vital - we're currently hitting problems with SD card traffic interfering with 802.15.4 radios on a different bus, and this will solve it.
(I had knocked up a quick hack to confirm this, and came back to check what the state was with this PR).
drivers/SPI.h
Outdated
struct spi_peripheral_s { | ||
SPIName name; | ||
spi_t spi; | ||
PlatformMutex *mutex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it not be easier to make this a SingletonPtr<PlatformMutex>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's probably pushing too much stuff into the static allocation though. After more thought, this seems like a bad hybrid. There is a benefit to being fully-static, but if you're going to start doing some dynamic allocation, it may as well be fully dynamic to minimise overhead. I think rather than this static array, it would be better just to have the head of a linked list of peripherals, each of which with an embedded PlatformMutex
and spi_t
, so one heap block per peripheral.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally I would want the PlatformMutex to be statically allocated but AFAIU this is causing issues with the constructor not being invoked when instantiated from a static array..
drivers/SPI.h
Outdated
// Drawback: it costs ram size even if the device is not used. | ||
static spi_peripheral_s _peripherals[SPI_COUNT]; | ||
|
||
spi_peripheral_s *_self; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_self
as a name is far too vague - it should surely be _peripheral
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little bikeshedding but I picked _self because :
_peripheral
do not reflect enough that this member is the peripheral associated tothis
SPI instance._this
would have been ambiguous withthis
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_peripheral
do not reflect enough that this member is the peripheral associated to this SPI instance.
I'm not sure how it could be any clearer. It tells us it's a/the peripheral, and obviously it's associated with this SPI
instance, because our SPI
object pointing to it. It's this->_peripheral
.
SPI
is the object representing an SPI slave we're talking to, and then we have a pointer to the peripheral controlling it.
this->_self
reads to me just the same as saying this->_this
.
drivers/SPI.h
Outdated
@@ -74,9 +77,14 @@ namespace mbed { | |||
* @ingroup drivers | |||
*/ | |||
class SPI : private NonCopyable<SPI> { | |||
|
|||
protected: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why protected? Is this intentionally exposed as API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the member variables using this type are protected too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_self
? Does that need to be protected rather than private? You want to permit derived classes to mess with it? I'd be inclined to keep it private until there's a definite need to reveal it.
(Watching all the "protected exclusion" markers being applied for Doxygen recently has brought home to me how much excess protected there is. protected
is often unintentionally exposed API. Either it shouldn't be protected, or it should be documented (as @internal
if necessary.)
// unless not assigned to a peripheral, this mutex will never be deleted. | ||
PlatformMutex *mutex = new PlatformMutex(); | ||
|
||
core_util_critical_section_enter(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical section seems heavy, and I'm wondering if that's tying your hands from using SingletonPtr
for the PlatformMutex
.
You don't need your own mutex, you could just borrow singleton_lock
from SingletonPtr
, which is used by that and all other static initialisation.
drivers/SPI.cpp
Outdated
} | ||
if (_self->name == 0) { | ||
_self->name = name; | ||
spi_init(&_self->spi, false, mosi, miso, sclk, NC); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ssel
is now being totally ignored. I still think that's a retrograde step - it is notionally within the bounds of existing platform-specific behaviour, but it would surely be better to have lock
manipulate ssel
as a GPIO.
AFAIK, we're already in the happy state where all portable code already passes NC as ssel and does it manually because of the variable behaviour of platforms, so we now have the room to start making it work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On further reflection, probably best to not put it in actual lock
, as we do have a common pattern where lock
is just a "peripheral lock", and it's virtual so that people can inherit and override, in particular for bypassing the mutex for use from interrupt, if they know they're the only user of the peripheral.
There are some UnlockedSPI
out there that inherit from SPI
for use from IRQ, overriding lock
+unlock
. Shouldn't break that pattern.
Instead, having select
and deselect
methods that are lock; acquire; cs=1 (reference counted)
would be the way to go - that would still work when someone overrides lock to eliminate the peripheral mutex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, this NC should be ssel.
Any work regarding locks/selections is out of scope from this PR though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has to be NC
- can't be anything else or the entire "peripheral" concept won't work. That inherently prevents any use of the HAL ssel, because you're sharing spi_t
for the same bus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I guess you /could/ pass ssel to the HAL, but only if you did it on every acquire. But that means a full init
every time you change owner of a peripheral, I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any work regarding locks/selections is out of scope from this PR though.
From my fairly prolonged personal experience of using the SPI API, the two biggest problems are the system-wide transfer mutex, and the total uselessness of the SSEL parameter. (Lack of trust of the async API implementations would be a runner-up)
Great that you're fixing the first one - which is also a purely C++ layer and non-HAL thing - but I don't see how you can do any sort of SPI rework that doesn't address the SSEL problem. If it's not going to be addressed when during an SPI API rework, when is it going to be addressed?
The peripheral change as you've written it here is effectively is going to force some SSEL change. You're going to have to do something. Any of these are possible:
- render SSEL totally non-functional for all platforms, as the current PR, everyone does SSEL manually via GPIO, as portable code already does anyway.
- rework the acquire mechanism to do
spi_init
to change SSEL - give up the shared
spi_t
- put the GPIO for SSEL inside
SPI
- doesn't break anyone with non-portable code relying on the SPI SSEL, and the portable code can be cleaned up to take out the manual handling.
I think the last is by far the best combination of easy+beneficial.
drivers/SPI.cpp
Outdated
} | ||
|
||
int SPI::write(int value) | ||
{ | ||
lock(); | ||
_acquire(); | ||
int ret = spi_master_write(&_spi, value); | ||
uint32_t ret = 0; | ||
spi_transfer(&_self->spi, &value, (_bits+7)/8, &ret, (_bits+7)/8, NULL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only works little-endian. If attempting portability you need a 3-way switch using uint8_t
, 1;uint16_t
, 2; or uint32_t
, 4.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although, do we ever care? Not sure mbed OS supports big-endian at all, or have any plans to do so. It's just that the HAL api said something about platform endianness, suggesting coping with either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO this method should be deprecated as it encourages reading symbols per symbol which is really bad for performances.
This fallback is provided for compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this will be second step - another PR (deprecation note for this byte oriented methods) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't disagree with this - it's a convenient short-hand, but other APIs like Socket
and FileHandle
only give you the block form. If people really need byte-wise stuff, they can probably wrap a helper up themselves.
drivers/SPI.cpp
Outdated
@@ -115,29 +162,29 @@ int SPI::write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_len | |||
{ | |||
lock(); | |||
_acquire(); | |||
int ret = spi_master_block_write(&_spi, tx_buffer, tx_length, rx_buffer, rx_length, _write_fill); | |||
int ret = spi_transfer(&_self->spi, tx_buffer, tx_length, rx_buffer, rx_length, &_write_fill); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this use of _write_fill assumes little endian. Possibly simplest to have a 3-way uint8_t
/uint16_t
/uint32_t
union to store it.
spi_transfer_async_abort(&_spi); | ||
_is_pending = false; | ||
} | ||
_is_pending = spi_transfer_async(&_spi, &value, 1, (void *)&_buffer, 1, &_dummy, &SPISlave::irq_handler, this, (DMAUsage)0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More illegal-if-big-endian stuff.
|
||
protected: | ||
spi_t _spi; | ||
// holds spi_peripheral_s per peripheral on the device. | ||
// Drawback: it costs ram size even if the device is not used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be costing space if SPI isn't used at all, I hope. I guess we can live with using more than necessary for buses that aren't used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, all my issues on this are to do with the state of C++ SPI
class - I've got no issue with the HAL specification or implementation. As far as I can see all the tools are in place to get SPI
working the way I think it should be. (And that would include not actually using the HAL SSEL, to have maximum flexibility for multiple devices on one bus). I believe it should also be possible to resurrect async with proper peripheral ownership management with current HAL API.
Therefore I'll approve this to allow HAL development to proceed, but more work on SPI
and its APIs will be required before going to master. I will try to find time to put forward my own SPI
-refining PR for this feature branch.
Thanks @kjbracey-arm ! Looking forward for SPI-refining |
689890e
to
85413ff
Compare
85413ff
to
a290cfe
Compare
@ithinuel The branch was recently rebased, can you update this one (there's one conflict as well now) |
a290cfe
to
0892667
Compare
Co-authored-by: Wilfried Chauveau <[email protected]> Co-authored-by: Przemyslaw Stekiel <[email protected]>
Co-authored-by: Wilfried Chauveau <[email protected]> Co-authored-by: Przemyslaw Stekiel <[email protected]>
…PI within the SoC
Test run: FAILEDSummary: 1 of 11 test jobs failed Failed test jobs:
|
@@ -198,6 +198,7 @@ MBED_WEAK const PinMap PinMap_PWM[] = { | |||
}; | |||
|
|||
/*************SPI**************/ | |||
#if SPI_DEVICE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it be DEVICE_SPI
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks @deepikabhavnani!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@donatieng - All the #if
changes are relevant for master branch as well, it will be good to move common changes to master branch instead feature
branch to have minimal conflicts in future, just a suggestion not blocker.
Analysis of the failing tests: List of the failed tests below:
I tried to run the tests on master branch (
Conclusion: |
When "SD" component is unavailable the following tests try to use Flash as storage and fail: features-storage-tests-blockdevice-general_block_device features-device_key-tests-device_key-functionality features-storage-tests-kvstore-static_tests features-storage-tests-kvstore-general_tests_phase_1 features-storage-tests-kvstore-securestore_whitebox tests-psa-prot_internal_storage Additional investigation is required to find the reason of the failures.
Thanks @mprse for the detailed investigation, we'll have to track the storage issue. @ARMmbed/mbed-os-maintainers this is now ready for CI (again ;)) |
CI started |
Test run: SUCCESSSummary: 11 of 11 test jobs passed |
Woohoo! |
🕺 |
This commit takes some of the work done on the SPI class from ARMmbed#8445, and refines it, to provide the per-peripheral mutex functionality. This also implements GPIO-based SSEL, which exposes a new select()/deselect() API for users to group transfers, and should work on every platform (unlike the HAL-based SSEL). This requires users to use a new constructor to avoid backwards compatibility issues. To activate the per-peripheral mutex, the HAL must define SPI_COUNT and provide spi_get_peripheral_name(). (In ARMmbed#8445 this is a reworked spi_get_module, but the name is changed here to avoid a collision with existing HALs - this commit is designed to work without wider HAL changes). Fixes: ARMmbed#9149
Description
This PR contains reference implementations of the SPI HAL RFC changes for the FRDM-K66F and NUCLEO_F429ZI boards.
Known issues :
Pull request type