casaroli commented on code in PR #21342:
URL: https://github.com/apache/nuttx/pull/21342#discussion_r4441922793
##########
include/nuttx/spinlock.h:
##########
@@ -314,10 +334,11 @@ static inline_function void
spin_unlock_notrace(FAR volatile spinlock_t *lock)
{
#ifdef CONFIG_TICKET_SPINLOCK
- atomic_add_release(&lock->owner, 1);
+ atomic_set_release(&lock->owner, lock->owner + 1);
#else
atomic_set_release((FAR atomic_t *)lock, SP_UNLOCKED);
#endif
+ UP_SEV();
Review Comment:
SEV is not a memory access, and a store-release does not make the store
visible before the next instruction. Without a DSB the SEV can arrive before
the unlock store is visible: the waiter reads "locked" again, executes WFE and
sleeps with the lock free, usually with IRQs masked. The ARM ARM WFE/SEV lock
example has a DSB between the store and SEV, and commit 8ea5310 added it for
this reason.
Fix: drop commit 3d4225e and keep `UP_DSB()` before every `UP_SEV()` (lines
341, 932, 1171, 1280).
##########
include/nuttx/spinlock.h:
##########
@@ -849,13 +923,18 @@ void spin_unlock_irqrestore_nopreempt(FAR volatile
spinlock_t *lock,
static inline_function
bool rspin_unlock(FAR rspinlock_t *lock)
{
- DEBUGASSERT(lock->owner == this_cpu() + 1);
- DEBUGASSERT(lock->count >= 1);
+ bool ret = false;
+ uint32_t val = lock->local_val;
- if (--lock->count == 0)
+ if (RSPINLOCK_COUNT(val) == 1u)
{
- atomic_set_release((FAR atomic_t *)&lock->val, 0);
- return true;
+ atomic_set_release(&lock->val, 0u);
+ UP_SEV();
+ ret = true;
+ }
+ else
+ {
+ lock->local_val = val - 1u;
}
return false;
Review Comment:
`rspin_unlock()` sets `ret = true` on the last unlock but returns `false`.
So `rspin_unlock_irqrestore()` never calls `irq_restore()`, and the CPU keeps
interrupts disabled after every rspinlock section. With this PR
qemu-armv8a:knsh with 4 CPUs stops at boot, and CI arm64-01 fails
`qemu-armv8a/citest` with "device boot timeout": `clock_initialize()` calls
`clock_notifier_call_chain()`, which uses an rspinlock (master boots).
Fix: `return ret;`.
##########
include/nuttx/spinlock_type.h:
##########
@@ -75,16 +76,19 @@ typedef uint32_t spinlock_t;
typedef union rspinlock_u
{
- /* Which cpu is holding spinlock, and taking recursive count */
-
- uint32_t val;
struct
Review Comment:
`val` and `local_val` are two members of a struct, so they are two words
(`sizeof(rspinlock_t)` is 8, `local_val` is at offset 4), but the code expects
one word (the comment and `compiler_assume()` in `rspin_lock()` and
`rspin_trylock()`). After the CAS `local_val` keeps its old value, so
`rspin_unlock()` sees count 0 and never clears `val`: the lock is never
released. The new layout also breaks `RSPINLOCK_INITIALIZER` users: CI
xtensa-01, xtensa-02 and risc-v-02 fail in esp-hal-3rdparty with
`-Werror=missing-braces` (sleep_modes.c:318, gpio.c:73, esp_timer.c:92).
Fix: put `atomic_t val` and `uint32_t local_val` directly in the union,
without the inner struct.
##########
include/nuttx/irq.h:
##########
@@ -136,6 +136,20 @@
#define IRQ_WAKE_THREAD 1
+/****************************************************************************
Review Comment:
`irq_save()`/`irq_restore()` are plain aliases of
`up_irq_save()`/`up_irq_restore()` with no instrumentation, and only some of
the ~268 files that call `up_irq_save()` change. The rename is not part of the
spinlock change and brings 26 extra files into this PR.
Fix: move this commit (and its nxstyle commits) to a separate PR, together
with the instrumentation that needs it.
##########
include/nuttx/spinlock.h:
##########
@@ -608,40 +662,60 @@ irqstate_t rspin_lock_irqsave_nopreempt(FAR rspinlock_t
*lock)
****************************************************************************/
#ifdef CONFIG_SPINLOCK
-static inline_function bool rspin_trylock(FAR rspinlock_t *lock)
+# ifdef CONFIG_SPINLOCK_INLINE
+SPINLOCK_INLINE bool rspin_trylock(FAR rspinlock_t *lock)
{
- rspinlock_t new_val;
- rspinlock_t old_val = RSPINLOCK_INITIALIZER;
- int cpu = this_cpu() + 1;
+ uint32_t old_val;
+ bool ret = false;
+ uint32_t owner = (uint32_t)this_cpu() + 1u;
/* Already owned this lock. */
- if (lock->owner == cpu)
+ old_val = atomic_read(&lock->val);
+
+ if (RSPINLOCK_OWNER(old_val) == owner)
{
- lock->count += 1;
- return true;
+ DEBUGASSERT(RSPINLOCK_COUNT(old_val) + 1u <= UINT16_MAX);
+
+ lock->local_val = old_val + 1u;
+ ret = true;
}
+ else if (old_val == 0u)
+ {
+ /* Try seize the ownership of the lock using CAS. */
+
+ ret = atomic_cmpxchg_acquire(&lock->val, &old_val,
+ RSPINLOCK_VALUE(owner, 1u));
- new_val.count = 1;
- new_val.owner = cpu;
+ /* Mark locked when the lock is successfully acquired. */
- /* Try seize the ownership of the lock. */
+ if (ret)
+ {
+ /* The local value shares the same memory location with the
+ * global atomic value. So if the atomic operation success,
+ * the local value should also change to the new_val.
+ */
- return atomic_cmpxchg_acquire((FAR atomic_t *)&lock->val,
- (FAR atomic_t *)&old_val.val, new_val.val);
+ compiler_assume(lock->local_val == RSPINLOCK_VALUE(owner, 1u));
+ }
+ }
}
Review Comment:
`rspin_trylock()` has no `return` statement, so its result is undefined on
all three paths.
Fix: add `return ret;` before the closing brace.
##########
include/nuttx/spinlock.h:
##########
@@ -994,11 +1119,14 @@ static inline_function void read_lock(FAR volatile
rwlock_t *lock)
*
****************************************************************************/
-static inline_function bool read_trylock(FAR volatile rwlock_t *lock)
+# ifdef CONFIG_SPINLOCK_INLINE
+SPINLOCK_INLINE bool read_trylock(FAR volatile rwlock_t *lock)
{
- while (true)
+ bool ret = false;
Review Comment:
`bool ret = false;` is not used.
Fix: remove it.
##########
include/nuttx/spinlock.h:
##########
@@ -951,23 +1069,30 @@ void rspin_restorelock(FAR rspinlock_t *lock, uint16_t
count)
*
****************************************************************************/
-static inline_function void read_lock(FAR volatile rwlock_t *lock)
+# ifdef CONFIG_SPINLOCK_INLINE
+SPINLOCK_INLINE void read_lock(FAR volatile rwlock_t *lock)
{
nxsched_critmon_busywait(true, return_address(0));
-
- while (true)
+ for (; ; )
{
- int old = atomic_read(lock);
+ int32_t old = atomic_read(lock);
- if (old > RW_SP_WRITE_LOCKED &&
- atomic_cmpxchg_acquire(lock, &old, old + 1))
+ if (old > RW_SP_WRITE_LOCKED)
{
- break;
+ if (atomic_cmpxchg_acquire(lock, &old, old + 1))
+ {
+ break;
+ }
+
+ UP_WFE();
Review Comment:
This WFE also runs when the CAS fails only because another reader changed
the count. A read acquire sends no SEV, so this reader sleeps until the next
unlock although it could take the lock now.
Fix: call `UP_WFE()` only when `old == RW_SP_WRITE_LOCKED`, and retry the
CAS at once otherwise.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]