Add multicore safety, FIFO, update pico-sdk (#122)

Update pico-sdk to 1.1.2

Add methods to block the opposite core while doing flash updates.
Ensure opposite core is stopped in LittleFS and EEPROM while doing
flash updates.

Update documentation with new calls.
This commit is contained in:
Earle F. Philhower, III 2021-05-06 19:57:21 -07:00 committed by GitHub
parent 6cf0b30fdf
commit 70a30dc219
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 187 additions and 12 deletions

View file

@ -18,8 +18,96 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <hardware/pio.h>
#include <hardware/clocks.h>
#include <hardware/irq.h>
#include <hardware/pio.h>
#include <pico/multicore.h>
#include <pico/util/queue.h>
class _MFIFO {
public:
_MFIFO() { /* noop */ };
~_MFIFO() { /* noop */ };
void begin(int cores) {
constexpr int FIFOCNT = 8;
if (cores == 1) {
_multicore = false;
return;
}
mutex_init(&_idleMutex);
queue_init(&_queue[0], sizeof(uint32_t), FIFOCNT);
queue_init(&_queue[1], sizeof(uint32_t), FIFOCNT);
_multicore = true;
}
void registerCore() {
multicore_fifo_clear_irq();
irq_set_exclusive_handler(SIO_IRQ_PROC0 + get_core_num(), _irq);
irq_set_enabled(SIO_IRQ_PROC0 + get_core_num(), true);
}
void push(uint32_t val) {
while (!push_nb(val)) { /* noop */ }
}
bool push_nb(uint32_t val) {
// Push to the other FIFO
return queue_try_add(&_queue[get_core_num()^1], &val);
}
uint32_t pop() {
uint32_t ret;
while (!pop_nb(&ret)) { /* noop */ }
return ret;
}
bool pop_nb(uint32_t *val) {
// Pop from my own FIFO
return queue_try_remove(&_queue[get_core_num()], val);
}
int available() {
return queue_get_level(&_queue[get_core_num()]);
}
void idleOtherCore() {
if (!_multicore) return;
mutex_enter_blocking(&_idleMutex);
_otherIdled = false;
multicore_fifo_push_blocking(_GOTOSLEEP);
while (!_otherIdled) { /* noop */ }
}
void resumeOtherCore() {
if (!_multicore) return;
mutex_exit(&_idleMutex);
_otherIdled = false;
// Other core will exit busy-loop and return to operation
// once otherIdled == false.
}
private:
static void __no_inline_not_in_flash_func(_irq)() {
multicore_fifo_clear_irq();
noInterrupts(); // We need total control, can't run anything
while (multicore_fifo_rvalid()) {
if (_GOTOSLEEP == multicore_fifo_pop_blocking()) {
_otherIdled = true;
while (_otherIdled) { /* noop */ }
break;
}
}
interrupts();
}
bool _multicore = false;
mutex_t _idleMutex;
static volatile bool _otherIdled;
queue_t _queue[2];
static constexpr int _GOTOSLEEP = 0x66666666;
};
class RP2040 {
public:
@ -33,8 +121,16 @@ public:
static int f_cpu() {
return clock_get_hz(clk_sys);
}
void idleOtherCore() { fifo.idleOtherCore(); }
void resumeOtherCore() { fifo.resumeOtherCore(); }
// Multicore comms FIFO
_MFIFO fifo;
};
extern RP2040 rp2040;
// Wrapper class for PIO programs, abstracting common operations out
// TODO - Make dualcore safe
// TODO - Add unload/destructor

View file

@ -22,6 +22,10 @@
#include <pico/stdlib.h>
#include <pico/multicore.h>
RP2040 rp2040;
volatile bool _MFIFO::_otherIdled = false;
extern void setup();
extern void loop();
@ -35,6 +39,7 @@ void initVariant() { }
extern void setup1() __attribute__((weak));
extern void loop1() __attribute__((weak));
static void main1() {
rp2040.fifo.registerCore();
if (setup1) {
setup1();
}
@ -62,8 +67,12 @@ extern "C" int main() {
#endif
if (setup1 || loop1) {
rp2040.fifo.begin(2);
multicore_launch_core1(main1);
} else {
rp2040.fifo.begin(1);
}
rp2040.fifo.registerCore();
setup();
while (true) {

View file

@ -33,8 +33,9 @@ extern "C" void interrupts() {
// ERROR
return;
}
restore_interrupts(_irqStack[get_core_num()].top());
auto oldIrqs = _irqStack[get_core_num()].top();
_irqStack[get_core_num()].pop();
restore_interrupts(oldIrqs);
}
extern "C" void noInterrupts() {

View file

@ -9,5 +9,69 @@ By adding a ``setup1()`` and ``loop1()`` function to your sketch you can make
use of the second core. Anything called from within the ``setup1()`` or
``loop1()`` routines will execute on the second core.
``setup()`` and ``setup1()`` will be called at the same time, and the ``loop()``
or ``loop1()`` will be started as soon as the core's ``setup()`` completes (i.e.
not necessarily simultaneously!).
See the ``Multicore.ino`` example in the ``rp2040`` example directory for a
quick introduction.
Pausing Cores
-------------
Sometimes an application needs to pause the other core on chip (i.e. it is
writing to flash or needs to stop processing while some other event occurs).
void rp2040.idleOtherCore()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sends a message to stop the other core (i.e. when called from core 0 it
pauses core 1, and vice versa). Waits for the other core to acknowledge
before returning.
The other core will have its interrupts disabled and be busy-waiting in
an RAM-based routine, so flash and other peripherals can be accesses.
**NOTE** If you idle core 0 too long, then the USB port can become frozen.
This is because core 0 manages the USB and needs to service IRQs in a
timely manner (which it can't do when idled).
void rp2040.resumeOtherCore()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Resumes processing in the other core, where it left off.
Communicating Between Cores
---------------------------
The RP2040 provides a hardware FIFO for communicating between cores, but it
is used exclusively for the idle/resume calls described above. Instead, please
use the following functions to access a softwarae-managed, multicore safe
FIFO.
void rp2040.fifo.push(uint32_t)
~~~~~~~~~~~~~~~~~~~~~~~~~~
Pushes a value to the other core. Will block if the FIFO is full.
bool rp2040.fifo.push_nb(uint32_t)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pushes a value to the other core. If the FIFO is full, returns ``false``
immediately and doesn't block. If the push is successful, returns ``true``.
uint32_t rp2040.fifo.pop()
~~~~~~~~~~~~~~~~~~~~~
Reads a value from this core's FIFO. Blocks until one is available.
bool rp2040.fifo.pop_nb(uint32_t *dest)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Reads a value from this core's FIFO and places it in dest. Will return
``true`` if successful, or ``false`` if the pop would block.
int rp2040.fifo.available()
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns the number of values available in this core's FIFO.

Binary file not shown.

View file

@ -105,11 +105,12 @@ bool EEPROMClass::commit() {
if (!_data)
return false;
// TODO - this only stops IRQs on 1 core, need to do it on both
uint32_t save = save_and_disable_interrupts();
noInterrupts();
rp2040.idleOtherCore();
flash_range_erase((intptr_t)_sector - (intptr_t)XIP_BASE, 4096);
flash_range_program((intptr_t)_sector - (intptr_t)XIP_BASE, _data, _size);
restore_interrupts(save);
rp2040.resumeOtherCore();
interrupts();
return true;
}

View file

@ -173,10 +173,12 @@ int LittleFSImpl::lfs_flash_prog(const struct lfs_config *c,
lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) {
LittleFSImpl *me = reinterpret_cast<LittleFSImpl*>(c->context);
uint8_t *addr = me->_start + (block * me->_blockSize) + off;
uint32_t save = save_and_disable_interrupts();
noInterrupts();
rp2040.idleOtherCore();
// Serial.printf("WRITE: %p, $d\n", (intptr_t)addr - (intptr_t)XIP_BASE, size);
flash_range_program((intptr_t)addr - (intptr_t)XIP_BASE, (const uint8_t *)buffer, size);
restore_interrupts(save);
rp2040.resumeOtherCore();
interrupts();
return 0;
}
@ -184,9 +186,11 @@ int LittleFSImpl::lfs_flash_erase(const struct lfs_config *c, lfs_block_t block)
LittleFSImpl *me = reinterpret_cast<LittleFSImpl*>(c->context);
uint8_t *addr = me->_start + (block * me->_blockSize);
// Serial.printf("ERASE: %p, %d\n", (intptr_t)addr - (intptr_t)XIP_BASE, me->_blockSize);
uint32_t save = save_and_disable_interrupts();
noInterrupts();
rp2040.idleOtherCore();
flash_range_erase((intptr_t)addr - (intptr_t)XIP_BASE, me->_blockSize);
restore_interrupts(save);
rp2040.resumeOtherCore();
interrupts();
return 0;
}

@ -1 +1 @@
Subproject commit fc10a97c386f65c1a44c68684fe52a56aaf50df0
Subproject commit afc10f3599c27147a6f34781b7102d86f58aa5f6

View file

@ -13,7 +13,7 @@
#define PICO_SDK_VERSION_MAJOR 1
#define PICO_SDK_VERSION_MINOR 1
#define PICO_SDK_VERSION_REVISION 0
#define PICO_SDK_VERSION_STRING "1.1.0"
#define PICO_SDK_VERSION_REVISION 2
#define PICO_SDK_VERSION_STRING "1.1.2"
#endif