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

SPI upgrade - per-peripheral mutex and GPIO-based SSEL #9469

Merged
merged 4 commits into from
Mar 1, 2019

Conversation

kjbracey
Copy link
Contributor

@kjbracey kjbracey commented Jan 23, 2019

Description

This commit takes some of the work done on the SPI class from #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 #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: #9149

Pull request type

[ ] Fix
[ ] Refactor
[ ] Target update
[X] Functionality change
[ ] Docs update
[ ] Test update
[ ] Breaking change

Reviewers

@jarlamsa, @c1728p9, @ithinuel

@kjbracey
Copy link
Contributor Author

First draft - just for discussion and @jarlamsa to try out at the minute, really.

@kjbracey kjbracey force-pushed the spi_muxing branch 2 times, most recently from 0fae068 to ae6feea Compare January 23, 2019 12:45
@kjbracey kjbracey changed the title SPI upgrade - per-bus mutex and GPIO-based SSEL SPI upgrade - per-peripheral mutex and GPIO-based SSEL Jan 23, 2019
@ciarmcom ciarmcom requested review from c1728p9, ithinuel, jarlamsa and a team January 23, 2019 14:00
@ciarmcom
Copy link
Member

@kjbracey-arm, thank you for your changes.
@c1728p9 @ithinuel @jarlamsa @ARMmbed/mbed-os-core @ARMmbed/mbed-os-hal @ARMmbed/mbed-os-maintainers please review.

@jarlamsa
Copy link
Contributor

jarlamsa commented Jan 24, 2019

[Error] SPI.cpp@239,37: 'struct SingletonPtr<mbed::CircularBuffer<mbed::Transaction<mbed::SPI>, 2ul> >' has no member named 'reset'
[Error] SPI.cpp@271,41: 'struct SingletonPtr<mbed::CircularBuffer<mbed::Transaction<mbed::SPI>, 2ul> >' has no member named 'full'
[Error] SPI.cpp@275,41: 'struct SingletonPtr<mbed::CircularBuffer<mbed::Transaction<mbed::SPI>, 2ul> >' has no member named 'push'
[Error] SPI.cpp@323,41: 'struct SingletonPtr<mbed::CircularBuffer<mbed::Transaction<mbed::SPI>, 2ul> >' has no member named 'pop'

You probably wanted to use the member access through pointer operator with the access to the transaction_buffer.

drivers/SPI.cpp Outdated
@@ -167,7 +236,7 @@ void SPI::abort_transfer()
void SPI::clear_transfer_buffer()
{
#if TRANSACTION_QUEUE_SIZE_SPI
_transaction_buffer.reset();
_peripheral->transaction_buffer.reset();
Copy link
Contributor

Choose a reason for hiding this comment

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

transaction_buffer->reset

drivers/SPI.cpp Outdated
@@ -199,12 +268,12 @@ int SPI::queue_transfer(const void *tx_buffer, int tx_length, void *rx_buffer, i
t.callback = callback;
t.width = bit_width;
Transaction<SPI> transaction(this, t);
if (_transaction_buffer.full()) {
if (_peripheral->transaction_buffer.full()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

transaction_buffer->full

drivers/SPI.cpp Outdated
return -1; // the buffer is full
} else {
core_util_critical_section_enter();
_transaction_buffer.push(transaction);
if (!spi_active(&_spi)) {
_peripheral->transaction_buffer.push(transaction);
Copy link
Contributor

Choose a reason for hiding this comment

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

transaction_buffer->push

drivers/SPI.cpp Outdated
@@ -250,7 +320,7 @@ void SPI::start_transaction(transaction_t *data)
void SPI::dequeue_transaction()
{
Transaction<SPI> t;
if (_transaction_buffer.pop(t)) {
if (_peripheral->transaction_buffer.pop(t)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

transaction_buffer->pop

@jarlamsa
Copy link
Contributor

Otherwise seems to be working without issues on NUCLEO_F429ZI when made changes to stm_spi_api.c and TARGET_STM32F4/TARGET_STM32F429xI/TARGET_NUCLEO_F429ZI/PeripheralNames.h

Do we bring the HAL-changes in with this PR or in a separate PR?

@ithinuel
Copy link
Member

ithinuel commented Jan 30, 2019

Won't this change collide with the work done on branch feature-hal-spec-spi ?

Wouldn't it be better to keep the status quo until the feature gets actually merged into master ?
(I am asking this with in mind the maintenance cost induced by changes on master when time comes to rebase the feature branch.)

@kjbracey
Copy link
Contributor Author

Yes, it collides, and the feature branch will need to be rebased and aligned with this :/

The problem is that this feature is required for 5.12, but your feature branch isn't due to land for 5.12.

The system-wide mutex is a blocker for @jarlamsa's project - SD card accesses break the radio driver.

@cmonr
Copy link
Contributor

cmonr commented Jan 30, 2019

@bulislaw @ARMmbed/mbed-os-maintainers Fyi ^^^

@deepikabhavnani
Copy link

First draft - just for discussion and @jarlamsa to try out at the minute, really.

Will like to see at least some test code to verify the changes in this PR

@kjbracey
Copy link
Contributor Author

kjbracey commented Jan 31, 2019

I'm not sure what the current SPI test infrastructure consists of. I see #7976 in progress for specific testing, but that's part of the pending feature branch, and it's testing the HAL, not class SPI.

For the new select/deselect functionality, bringing it into use by SD drivers and 802.15.4 radio drivers would get that exercised by their test suites. (I've asked @jarlamsa to look at that - would simplify their code and offset code size increase here).

@kjbracey kjbracey force-pushed the spi_muxing branch 2 times, most recently from 9fd003e to c60000f Compare January 31, 2019 11:19
@kjbracey
Copy link
Contributor Author

kjbracey commented Feb 1, 2019

@ithinuel - new commit reworks the peripheral blocks to make them allocated on-demand from the heap. This optimises RAM usage for the typical "SPI peripherals in use much fewer than those available in hardware" case.

I think that's the least worst option, having considered the "static array of spi peripheral count" and "put 1 peripheral object inside each SPI object" options. The last would have been cunning, and optimal for the typical "1 device per peripheral" case, but I couldn't figure out how to hand over correctly and safely when an SPI object was deleted.

Does eliminate the need for SPI_COUNT from the HAL, but increases ROM size. Ends up pulling in mutex deletion code in a simple test image, which the static version avoids. Can't see a cunning way around that. (It comes via SPI's virtual destructor).

@kjbracey
Copy link
Contributor Author

kjbracey commented Feb 1, 2019

Requires fix in #9580.

@0xc0170
Copy link
Contributor

0xc0170 commented Mar 1, 2019

CI restarted

@mbed-ci
Copy link

mbed-ci commented Mar 1, 2019

Test run: FAILED

Summary: 1 of 9 test jobs failed
Build number : 3
Build artifacts

Failed test jobs:

  • jenkins-ci/mbed-os-ci_build-ARMC5

@0xc0170
Copy link
Contributor

0xc0170 commented Mar 1, 2019

Some targets report [DEBUG] Errors: Error: L6218E: Undefined symbol spi_get_peripheral_name (referred from BUILD/tests/NRF52_DK/ARM/drivers/SPI.o). . Not certain why only ARMC5 failed.

kjbracey added 4 commits March 1, 2019 14:33
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
SPIFBlockDevice was using SingletonPtr without an include,
and only getting it via SPI.h.

Spotted while changing SPI to not use SingletonPtr - now
abandoned, but still this shouldn't have been relying on it.
Avoid collision with some HALs that already define SPI_COUNT.
@kjbracey
Copy link
Contributor Author

kjbracey commented Mar 1, 2019

Some HALs had their own existing SPI_COUNT definition. Moved our new one to DEVICE_SPI_COUNT to avoid collision.

Rebased.

@0xc0170
Copy link
Contributor

0xc0170 commented Mar 1, 2019

CI restarted

Copy link
Member

@ithinuel ithinuel left a comment

Choose a reason for hiding this comment

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

Aside from the two "style" comment, LGTM

void SPI::select()
{
lock();
if (_select_count++ == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

This is just a matter of coding style, but for clarity, could this increment be done after the if ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I did that quite deliberately because a couple of days before writing it I saw some existing code containing this:

core_util_atomic_incr_u32(foo, 1);
if (foo == 1) {

which is not thread-safe, and needs to be

if (core_util_atomic_incr_u32(foo, 1) == 1) {

I think if people can't get comfortable with the increment/decrement pattern, it just encourages that sort of mistake. (Although breaking it up doesn't hurt in this case, as we are in a lock).


void SPI::deselect()
{
if (--_select_count == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

Like in the previous commend, could this decrement be done before the if to avoid any confusion ?

@mbed-ci
Copy link

mbed-ci commented Mar 1, 2019

Test run: FAILED

Summary: 1 of 14 test jobs failed
Build number : 4
Build artifacts

Failed test jobs:

  • jenkins-ci/mbed-os-ci_tools-test

@0xc0170
Copy link
Contributor

0xc0170 commented Mar 1, 2019

@ithinuel Happy with this PR?

@stevew817
Copy link
Contributor

@0xc0170 This broke SPI for Silicon Labs targets, as we currently don't allow instantiating NC pins and the new constructor does exactly that.

How was this PR tested?

@kjbracey
Copy link
Contributor Author

kjbracey commented Apr 25, 2019

Expected behaviour is that targets should permit gpio_init with NC, but are free to assert or crash if any real read/write operations are made on the NC pin.

This isn't explicitly documented, as far as I can see, but is implied by the existence of gpio_is_connected:

/* Checks if gpio object is connected (pin was not initialized with NC)
 * @param pin The pin to be set as GPIO
 * @return 0 if port is initialized with NC
 **/
int gpio_is_connected(const gpio_t *obj);

The PR would have been tested on whichever devices we have present and operational in CI - but as I believe we don't have any sort of universal SPI test rigs it would have been exercised in devices using 802.15.4 radio modules and the like, and I guess Silicon Labs targets would not have been represented.

The Silicon Labs targets should be updated to permit NC GPIO initialisation - I would suggest removing the assert from gpio_init, and instead having MBED_ASSERT(gpio_is_connected(obj)) in other calls.

@stevew817
Copy link
Contributor

@kjbracey-arm The other issue is targets using asynchronous SPI. the irq_handler is called from IRQ context, and this PR changed the transaction buffer to be a singletonPtr, see https://github.com/ARMmbed/mbed-os/pull/9469/files#diff-ade6379acb4d9069bc0cc21788bb6883R359

Inside the IRQ handler, dequeue_transaction can be called, which in turn tries to dequeue from the transaction buffer. This will cause failure, since the transaction buffer now needs to lock a mutex to get to the singletonPtr. That evidently won't work from IRQ context...

@kjbracey
Copy link
Contributor Author

SingletonPtr's mutex lock only occurs on first use, so AFAICT there would only be an issue if the very first asynchronous transaction was started directly from an interrupt handler. Are you doing that?

I guess we could prime the transaction buffer in the constructor to help that case - a call to its get() in _do_construct.

@stevew817
Copy link
Contributor

Priming would be helpful.
Starting an asynch transaction on a timer is not really an exotic use case?

@kjbracey
Copy link
Contributor Author

kjbracey commented Apr 25, 2019

The fact that async calls can be initiated from interrupt does illustrate a (pre-existing) design problem - the mutex locks for the peripheral are being bypassed, which means it won't work correctly in any sort of environment where there are multiple users sharing one SPI peripheral.

We do need to pin down exactly what the rules about async use are - I believe the guys working on the SPI HAL upgrade feature branch have been looking at this.

Still, for the current environment where asynch is historically lockless, it would make sense to prime it so it continues to work in that scenario. I'll make a patch for that. Will cost a bit of ROM space pulling in the init code for unused asynch, but not a lot.

@0xc0170
Copy link
Contributor

0xc0170 commented Apr 29, 2019

Thanks @stevew817 for getting back to us with these issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet