Skip to content

Commit

Permalink
spinlock: implement read writer spinlock
Browse files Browse the repository at this point in the history
spinlock.c:
Implement read write spinlock.
Readers can take lock simultaneously but only one writer can take lock.

irq_spinlock.c:
Align g_irq_spin_count.
If the lock is NULL, the caller will get global lock (e.g. g_irq_spin) and spin_lock_irqsave() support nest on the same CPU.
If the CPU can write lock, it can call write_lock_irqsave() again (e.g. support nest).
Why is the variable called g_irq_write_spin_count?
Because reader can read data simultaneously and the counter is already record in lock.
So we only need to record the counter of the cpu take the write lock (nest).

Signed-off-by: TaiJu Wu <[email protected]>
  • Loading branch information
TaiJuWu committed Oct 13, 2023
1 parent ffba0d1 commit f4d5624
Show file tree
Hide file tree
Showing 4 changed files with 753 additions and 1 deletion.
316 changes: 316 additions & 0 deletions include/nuttx/spinlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@

#include <nuttx/irq.h>

#ifdef CONFIG_RW_SPINLOCK
typedef int32_t rwlock_t;
#define RW_SP_UNLOCKED 0
#define RW_SP_READ_LOCKED 1
#define RW_SP_WRITE_LOCKED -1
#endif

#ifndef CONFIG_SPINLOCK
# define SP_UNLOCKED 0 /* The Un-locked state */
# define SP_LOCKED 1 /* The Locked state */
Expand Down Expand Up @@ -474,4 +481,313 @@ void spin_unlock_irqrestore_wo_note(FAR spinlock_t *lock, irqstate_t flags);
# define spin_unlock_irqrestore_wo_note(l, f) up_irq_restore(f)
#endif

#ifdef CONFIG_RW_SPINLOCK

/****************************************************************************
* Name: rwlock_init
*
* Description:
* Initialize a non-reentrant spinlock object to its initial,
* unlocked state.
*
* Input Parameters:
* lock - A reference to the spinlock object to be initialized.
*
* Returned Value:
* None.
*
*
****************************************************************************/

#define rwlock_init(l) do { *(l) = RW_SP_UNLOCKED; } while(0)

/****************************************************************************
* Name: read_lock
*
* Description:
* If this task does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is non-reentrant and set a bit of lock.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* None. When the function returns, the spinlock was successfully locked
* by this CPU.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/

void read_lock(FAR volatile rwlock_t *lock);

/****************************************************************************
* Name: read_trylock
*
* Description:
* If this task does not already hold the spinlock, then try to get the
* lock.
*
* This implementation is non-reentrant and set a bit of lock.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* false - Failure, the spinlock was already locked
* true - Success, the spinlock was successfully locked
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/

bool read_trylock(FAR volatile rwlock_t *lock);

/****************************************************************************
* Name: read_unlock
*
* Description:
* Release a bit on a non-reentrant spinlock.
*
* Input Parameters:
* lock - A reference to the spinlock object to unlock.
*
* Returned Value:
* None.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/

void read_unlock(FAR volatile rwlock_t *lock);

/****************************************************************************
* Name: write_lock
*
* Description:
* If this CPU does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is non-reentrant and set all bit on lock to avoid
* readers and writers.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* None. When the function returns, the spinlock was successfully locked
* by this CPU.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/

void write_lock(FAR volatile rwlock_t *lock);

/****************************************************************************
* Name: write_trylock
*
* Description:
* If this task does not already hold the spinlock, then loop until the
* spinlock is successfully locked.
*
* This implementation is non-reentrant and set all bit on lock to avoid
* readers and writers.
*
* The priority of reader is higher than writter if a reader hold the
* lock, a new reader can get its lock but writer can't get this lock.
*
* Input Parameters:
* lock - A reference to the spinlock object to lock.
*
* Returned Value:
* false - Failure, the spinlock was already locked
* true - Success, the spinlock was successfully locked
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/

bool write_trylock(FAR volatile rwlock_t *lock);

/****************************************************************************
* Name: write_unlock
*
* Description:
* Release all bit on a non-reentrant spinlock.
*
* Input Parameters:
* lock - A reference to the spinlock object to unlock.
*
* Returned Value:
* None.
*
* Assumptions:
* Not running at the interrupt level.
*
****************************************************************************/

void write_unlock(FAR volatile rwlock_t *lock);

/****************************************************************************
* Name: read_lock_irqsave
*
* Description:
* If SMP is are enabled:
* If the argument lock is not specified (i.e. NULL), disable local
* interrupts and take the global read write spinlock (g_irq_rw_spin)
* and increase g_irq_rw_spin.
*
* If the argument lock is specified,
* disable local interrupts and take the lock spinlock and return
* the interrupt state.
*
* NOTE: This API is very simple to protect data (e.g. H/W register
* or internal data structure) in SMP mode. But do not use this API
* with kernel APIs which suspend a caller thread. (e.g. nxsem_wait)
*
* If SMP is not enabled:
* This function is equivalent to up_irq_save().
*
* Input Parameters:
* lock - Caller specific spinlock. If specified NULL, g_irq_spin is used
* and can be nested. Otherwise, nested call for the same lock
* would cause a deadlock
*
* Returned Value:
* An opaque, architecture-specific value that represents the state of
* the interrupts prior to the call to write_lock_irqsave(lock);
*
****************************************************************************/

#if defined(CONFIG_SMP)
irqstate_t read_lock_irqsave(FAR rwlock_t *lock);
#else
# define read_lock_irqsave(l) ((void)(l), up_irq_save())
#endif

/****************************************************************************
* Name: read_unlock_irqrestore
*
* Description:
* If SMP is enabled:
* If the argument lock is not specified (i.e. NULL),
* decrement the call counter (g_irq_rw_spin) and restore the interrupt
* state as it was prior to the previous call to read_lock_irqsave(NULL).
*
* If the argument lock is specified, release the lock and
* restore the interrupt state as it was prior to the previous call to
* read_lock_irqsave(lock).
*
* If SMP is not enabled:
* This function is equivalent to up_irq_restore().
*
* Input Parameters:
* lock - Caller specific spinlock. If specified NULL, g_irq_spin is used.
*
* flags - The architecture-specific value that represents the state of
* the interrupts prior to the call to read_lock_irqsave(lock);
*
* Returned Value:
* None
*
****************************************************************************/

#if defined(CONFIG_SMP)
void read_unlock_irqrestore(FAR rwlock_t *lock, irqstate_t flags);
#else
# define read_unlock_irqrestore(l, f) up_irq_restore(f)
#endif

/****************************************************************************
* Name: write_lock_irqsave
*
* Description:
* If SMP is are enabled:
* If the argument lock is not specified (i.e. NULL),
* disable local interrupts and take the global spinlock (g_irq_rw_spin)
* if the call counter (g_irq_write_spin_count[cpu]) equals to 0. Then
* the counter on the CPU is incremented to allow nested calls and return
* the interrupt state.
*
* If the argument lock is specified,
* disable local interrupts and take the lock spinlock and return
* the interrupt state.
*
* NOTE: This API is very simple to protect data (e.g. H/W register
* or internal data structure) in SMP mode. But do not use this API
* with kernel APIs which suspend a caller thread. (e.g. nxsem_wait)
*
* If SMP is not enabled:
* This function is equivalent to up_irq_save().
*
* Input Parameters:
* lock - Caller specific spinlock. If specified NULL, g_irq_spin is used
* and can be nested. Otherwise, nested call for the same lock
* would cause a deadlock
*
* Returned Value:
* An opaque, architecture-specific value that represents the state of
* the interrupts prior to the call to write_lock_irqsave(lock);
*
****************************************************************************/

#if defined(CONFIG_SMP)
irqstate_t write_lock_irqsave(FAR rwlock_t *lock);
#else
# define write_lock_irqsave(l) ((void)(l), up_irq_save())
#endif

/****************************************************************************
* Name: write_unlock_irqrestore
*
* Description:
* If SMP is enabled:
* If the argument lock is not specified (i.e. NULL),
* decrement the call counter (g_irq_rw_spin_count[cpu]) and if it
* decrements to zero then release the spinlock (g_irq_rw_spin) and
* restore the interrupt state as it was prior to the previous call to
* write_lock_irqsave(NULL).
*
* If the argument lock is specified, release the lock and
* restore the interrupt state as it was prior to the previous call to
* write_lock_irqsave(lock).
*
* If SMP is not enabled:
* This function is equivalent to up_irq_restore().
*
* Input Parameters:
* lock - Caller specific spinlock. If specified NULL, g_irq_spin is used.
*
* flags - The architecture-specific value that represents the state of
* the interrupts prior to the call to write_lock_irqsave(lock);
*
* Returned Value:
* None
*
****************************************************************************/

#if defined(CONFIG_SMP)
void write_unlock_irqrestore(FAR rwlock_t *lock, irqstate_t flags);
#else
# define write_unlock_irqrestore(l, f) up_irq_restore(f)
#endif

#endif /* CONFIG_RW_SPINLOCK */
#endif /* __INCLUDE_NUTTX_SPINLOCK_H */
6 changes: 6 additions & 0 deletions sched/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,12 @@ config TICKET_SPINLOCK

endif # SPINLOCK

config RW_SPINLOCK
bool "Support read-write Spinlocks"
default y
---help---
Support Read-write spinlock

config IRQCHAIN
bool "Enable multi handler sharing a IRQ"
default n
Expand Down
Loading

0 comments on commit f4d5624

Please sign in to comment.