Loading...
Searching...
No Matches

base synchronization/lock primitive support. More...

Files

file  include/pico/lock_core.h

Data Structures

struct  lock_core
 Core state shared by all lock primitives. More...

Macros

#define lock_owner_id_t   int8_t
 type to use to store the 'owner' of a lock.
#define LOCK_INVALID_OWNER_ID   ((lock_owner_id_t)-1)
 marker value to use for a lock_owner_id_t which does not refer to any valid owner
#define lock_get_caller_owner_id()
 return the owner id for the caller
#define lock_internal_spin_unlock_with_wait(lock, save)
 Atomically unlock the lock's spin lock, and wait for a notification.
#define lock_internal_spin_unlock_with_notify(lock, save)
 Atomically unlock the lock's spin lock, and send a notification.
#define lock_internal_spin_unlock_with_best_effort_wait_or_timeout(lock, save, until)
 Atomically unlock the lock's spin lock, and wait for a notification or a timeout.
#define LOCK_INTERNAL_SPIN_UNLOCK_WITH_NOTIFY_WAKES_ALL   1
 Whether a notify reaches all earlier waiters.
#define lock_internal_spin_unlock_maybe_notify(lock, save, others_may_proceed)
 Release a spin lock, notifying other waiters if the implementation does not guarantee that an earlier notification will have reached them.
#define sync_internal_yield_until_before(until)
 yield to other processing until some time before the requested time

Functions

void lock_init (lock_core_t *core, uint lock_num)
 Initialise a lock structure.

Detailed Description

base synchronization/lock primitive support.

Most of the pico_sync locking primitives contain a lock_core_t structure member. This currently just holds a spin lock which is used only to protect the contents of the rest of the structure as part of implementing the synchronization primitive. As such, the spin_lock member of lock core is never still held on return from any function for the primitive.

critical_section is an exceptional case in that it does not have a lock_core_t and simply wraps a spin lock, providing methods to lock and unlock said spin lock.

lock_core based structures work by locking the spin lock, checking state, and then deciding whether they additionally need to block or notify when the spin lock is released. In the blocking case, they will wake up again in the future, and try the process again.

By default the SDK just uses the processors' events via SEV and WEV for notification and blocking as these are sufficient for cross core, and notification from interrupt handlers. However macros are defined in this file that abstract the wait and notify mechanisms to allow the SDK locking functions to effectively be used within an RTOS or other environment.

When implementing an RTOS, it is desirable for the SDK synchronization primitives that wait, to block the calling task (and immediately yield), and those that notify, to wake a blocked task which isn't on processor. At least the wait macro implementation needs to be atomic with the protecting spin_lock unlock from the callers point of view; i.e. the task should unlock the spin lock when it starts its wait. Such implementation is up to the RTOS integration, however the macros are defined such that such operations are always combined into a single call (so they can be performed atomically) even though the default implementation does not need this, as a WFE which starts following the corresponding SEV is not missed.

Macro Definition Documentation

◆ lock_get_caller_owner_id

#define lock_get_caller_owner_id ( )
Value:
#define lock_owner_id_t
type to use to store the 'owner' of a lock.
Definition lock_core.h:96
static __force_inline uint get_core_num(void)
Get the current core number.
Definition platform.h:128

return the owner id for the caller

By default this returns the calling core number, but may be overridden (e.g. to return an RTOS task id)

◆ lock_internal_spin_unlock_maybe_notify

#define lock_internal_spin_unlock_maybe_notify ( lock,
save,
others_may_proceed )
Value:
spin_unlock((lock)->spin_lock, save)
static __force_inline void spin_unlock(spin_lock_t *lock, uint32_t saved_irq)
Release a spin lock safely.
Definition spin_lock.h:385

Release a spin lock, notifying other waiters if the implementation does not guarantee that an earlier notification will have reached them.

The purpose of this primitive is: if multiple waiters are the target of a single notification, but the implementation does not guarantee that the notification reaches all waiters (so LOCK_INTERNAL_SPIN_UNLOCK_WITH_NOTIFY_WAKES_ALL = 0), generate additional notifications on the first waiter's lock release, to propagate the original notification to the remaining waiters.

If LOCK_INTERNAL_SPIN_UNLOCK_WITH_NOTIFY_WAKES_ALL = 1 then no extra propagation is required, so it's just a regular spin_unlock().

The others_may_proceed parameter controls whether a notification is generated. For example, when a semaphore has multiple outstanding permits, the first waiter consumes a permit and then passes others_may_proceed = true, which notifies the next waiter. The chain of notifications continues until others_may_proceed = false is passed: in this example, when the number of outstanding semaphore permits reaches zero.

Parameters
lockthe lock_core
savethe uint32_t value returned by the corresponding spin_lock_blocking()
others_may_proceedtrue if another waiter could still proceed - i.e. this caller has not excluded them

◆ lock_internal_spin_unlock_with_best_effort_wait_or_timeout

#define lock_internal_spin_unlock_with_best_effort_wait_or_timeout ( lock,
save,
until )
Value:
({ \
spin_unlock((lock)->spin_lock, save); \
best_effort_wfe_or_timeout(until); \
})

Atomically unlock the lock's spin lock, and wait for a notification or a timeout.

Atomic here refers to the fact that it should not be possible for a concurrent lock_internal_spin_unlock_with_notify to insert itself between the spin unlock and this wait in a way that the wait does not see the notification (i.e. causing a missed notification). In other words this method should always wake up in response to a lock_internal_spin_unlock_with_notify whose acquisition of the same lock is ordered after this method's lock release.

In an ideal implementation, this method would return exactly after the corresponding lock_internal_spin_unlock_with_notify has subsequently been called on the same lock instance or the timeout has been reached, however this method is free to return at any point before that; this macro is always used in a loop which locks the spin lock, checks the internal locking primitive state and then waits again if the calling thread should not proceed.

By default this simply unlocks the spin lock, and then calls best_effort_wfe_or_timeout but may be overridden (e.g. to actually block the RTOS task with a timeout).

Parameters
lockthe lock_core for the primitive which needs to block
savethe uint32_t value that should be passed to spin_unlock when the spin lock is unlocked. (i.e. the PRIMASK state when the spin lock was acquire)
untilthe absolute_time_t value
Returns
true if the timeout has been reached

◆ lock_internal_spin_unlock_with_notify

#define lock_internal_spin_unlock_with_notify ( lock,
save )
Value:
spin_unlock((lock)->spin_lock, save), __sev()
static __force_inline void __sev(void)
Insert a SEV instruction in to the code path.
Definition sync.h:96

Atomically unlock the lock's spin lock, and send a notification.

Atomic here refers to the fact that it should not be possible for this notification to happen during a lock_internal_spin_unlock_with_wait in a way that that wait does not see the notification (i.e. causing a missed notification).

Restating the above in terms of lock ordering: if lock_internal_spin_unlock_with_notify() releases a lock acquired after lock_internal_spin_unlock_with_wait() released the same lock, the waiter must be notified. Failure to notify can cause lockup. Excess notifications are harmless.

The macro LOCK_INTERNAL_SPIN_UNLOCK_WITH_NOTIFY_WAKES_ALL records whether the implementation upholds the above guarantee. It's set by default when the SDK's built-in implementation is used: this is plain SEV/WFE on RP2040, and slightly more complex on RP2350 due to interactions between events and exclusives. The macros injected by FreeRTOS ports currently do not uphold the guarantee: they consume a shared event-group bit, so a waiter still between its spin unlock and its block finds nothing left.

By default this macro simply unlocks the spin lock, and then performs a SEV, but may be overridden (e.g. to actually un-block RTOS task(s)).

Parameters
lockthe lock_core for the primitive which needs to block
savethe uint32_t value that should be passed to spin_unlock when the spin lock is unlocked. (i.e. the PRIMASK state when the spin lock was acquire)

◆ LOCK_INTERNAL_SPIN_UNLOCK_WITH_NOTIFY_WAKES_ALL

#define LOCK_INTERNAL_SPIN_UNLOCK_WITH_NOTIFY_WAKES_ALL   1

Whether a notify reaches all earlier waiters.

The SDK's built-in notify/wait implementation guarantees the following: if lock_internal_spin_unlock_with_wait() releases a lock, and that same lock is subsequently acquired and then later released by lock_internal_spin_unlock_with_notify(), the waiter is notified. Only the lock acquisition order matters. Even if there are multiple wait calls before the notify call, all waiters are notified. Even if the waiter has released the lock but not yet gone to sleep, once it sleeps, it must be woken.

An RTOS override may not have that property. The FreeRTOS ports multiplex every spin lock onto bits of one event group and consume the bit (xClearOnExit), so each notification is consumed by exactly one waiter.

If any notify/wait primitives are overridden, conservatively mark them as not upholding the same guarantee. In this case we patch things up by emitting extra notifications so the first waiter can wake the next waiter, and so on – see lock_internal_spin_unlock_maybe_notify().

You can redefine this to 1 if you are certain your implementations uphold the same contract as the SDK versions.

◆ lock_internal_spin_unlock_with_wait

#define lock_internal_spin_unlock_with_wait ( lock,
save )
Value:
spin_unlock((lock)->spin_lock, save), __wfe()
static __force_inline void __wfe(void)
Insert a WFE instruction in to the code path.
Definition sync.h:115

Atomically unlock the lock's spin lock, and wait for a notification.

Atomic here refers to the fact that it should not be possible for a concurrent lock_internal_spin_unlock_with_notify to insert itself between the spin unlock and this wait in a way that the wait does not see the notification (i.e. causing a missed notification). In other words this method should always wake up in response to a lock_internal_spin_unlock_with_notify for the same lock, which completes after this call starts.

In an ideal implementation, this method would return exactly after the corresponding lock_internal_spin_unlock_with_notify has subsequently been called on the same lock instance, however this method is free to return at any point before that; this macro is always used in a loop which locks the spin lock, checks the internal locking primitive state and then waits again if the calling thread should not proceed.

By default this macro simply unlocks the spin lock, and then performs a WFE, but may be overridden (e.g. to actually block the RTOS task).

Parameters
lockthe lock_core for the primitive which needs to block
savethe uint32_t value that should be passed to spin_unlock when the spin lock is unlocked. (i.e. the PRIMASK state when the spin lock was acquire

◆ lock_owner_id_t

#define lock_owner_id_t   int8_t

type to use to store the 'owner' of a lock.

By default this is int8_t as it only needs to store the core number or -1, however it may be overridden if a larger type is required (e.g. for an RTOS task id)

◆ sync_internal_yield_until_before

#define sync_internal_yield_until_before ( until)
Value:
((void)0)

yield to other processing until some time before the requested time

This method is provided for cases where the caller has no useful work to do until the specified time.

By default this method does nothing, however it can be overridden (for example by an RTOS which is able to block the current task until the scheduler tick before the given time)

Parameters
untilthe absolute_time_t value

Function Documentation

◆ lock_init()

void lock_init ( lock_core_t * core,
uint lock_num )

Initialise a lock structure.

Inititalize a lock structure, providing the spin lock number to use for protecting internal state.

Parameters
corePointer to the lock_core to initialize
lock_numSpin lock number to use for the lock. As the spin lock is only used internally to the locking primitive method implementations, this does not need to be globally unique, however could suffer contention