From ffba0d15a5d2ef41bdbe3523d6068ba2f56b25e7 Mon Sep 17 00:00:00 2001
From: TaiJu Wu <tjwu1217@gmail.com>
Date: Thu, 5 Oct 2023 19:19:08 +0000
Subject: [PATCH] Feature: implement ticket spinlock

test config: ./tools/configure.sh -l qemu-armv8a:nsh_smp

Pass ostest

No matter big-endian or little-endian, ticket spinlock only check the
next and the owner is equal or not.

If they are equal, it means there is a task hold the lock or lock is
free.

Signed-off-by: TaiJu Wu <tjwu1217@gmail.com>

Co-authored-by: Xiang Xiao <xiaoxiang781216@gmail.com>
---
 arch/arm64/src/common/arm64_cpustart.c        |  2 +-
 .../qemu-armv8a/configs/nsh_smp/defconfig     |  1 +
 include/nuttx/spinlock.h                      | 33 +++++++-
 sched/Kconfig                                 | 10 +++
 sched/irq/irq_csection.c                      |  2 +-
 sched/semaphore/CMakeLists.txt                |  4 +
 sched/semaphore/spinlock.c                    | 84 ++++++++++++++++---
 7 files changed, 119 insertions(+), 17 deletions(-)

diff --git a/arch/arm64/src/common/arm64_cpustart.c b/arch/arm64/src/common/arm64_cpustart.c
index e4cf90d7a9..a57eb93c09 100644
--- a/arch/arm64/src/common/arm64_cpustart.c
+++ b/arch/arm64/src/common/arm64_cpustart.c
@@ -33,7 +33,7 @@
 #include <nuttx/sched_note.h>
 #include <sched/sched.h>
 #include <nuttx/cache.h>
-#include <arch/spinlock.h>
+#include <nuttx/spinlock.h>
 #include <nuttx/init.h>
 
 #include "init/init.h"
diff --git a/boards/arm64/qemu/qemu-armv8a/configs/nsh_smp/defconfig b/boards/arm64/qemu/qemu-armv8a/configs/nsh_smp/defconfig
index 75c36428e8..3a21ddf0f7 100644
--- a/boards/arm64/qemu/qemu-armv8a/configs/nsh_smp/defconfig
+++ b/boards/arm64/qemu/qemu-armv8a/configs/nsh_smp/defconfig
@@ -62,6 +62,7 @@ CONFIG_TESTING_GETPRIME=y
 CONFIG_TESTING_OSTEST=y
 CONFIG_TESTING_OSTEST_STACKSIZE=16384
 CONFIG_TESTING_SMP=y
+CONFIG_TICKET_SPINLOCK=y
 CONFIG_UART1_BASE=0x9000000
 CONFIG_UART1_IRQ=33
 CONFIG_UART1_PL011=y
diff --git a/include/nuttx/spinlock.h b/include/nuttx/spinlock.h
index ea23e982d4..c2a34a5008 100644
--- a/include/nuttx/spinlock.h
+++ b/include/nuttx/spinlock.h
@@ -39,6 +39,24 @@
 typedef uint8_t spinlock_t;
 #else
 
+#ifdef CONFIG_TICKET_SPINLOCK
+
+union spinlock_u
+{
+  struct
+  {
+    uint16_t owner;
+    uint16_t next;
+  } tickets;
+  uint32_t value;
+};
+typedef union spinlock_u spinlock_t;
+
+#  define SP_UNLOCKED (union spinlock_u){{0, 0}}
+#  define SP_LOCKED (union spinlock_u){{0, 1}}
+
+#else
+
 /* The architecture specific spinlock.h header file must also provide the
  * following:
  *
@@ -51,6 +69,8 @@ typedef uint8_t spinlock_t;
 
 #include <arch/spinlock.h>
 
+#endif
+
 /****************************************************************************
  * Pre-processor Definitions
  ****************************************************************************/
@@ -80,7 +100,8 @@ typedef uint8_t spinlock_t;
 #  define SP_SEV()
 #endif
 
-#if defined(CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS) && !defined(__SP_UNLOCK_FUNCTION)
+#if !defined(__SP_UNLOCK_FUNCTION) && (defined(CONFIG_TICKET_SPINLOCK) || \
+     defined(CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS))
 #  define __SP_UNLOCK_FUNCTION 1
 #endif
 
@@ -199,7 +220,7 @@ void spin_lock_wo_note(FAR volatile spinlock_t *lock);
  *
  ****************************************************************************/
 
-spinlock_t spin_trylock(FAR volatile spinlock_t *lock);
+bool spin_trylock(FAR volatile spinlock_t *lock);
 
 /****************************************************************************
  * Name: spin_trylock_wo_note
@@ -223,7 +244,7 @@ spinlock_t spin_trylock(FAR volatile spinlock_t *lock);
  *
  ****************************************************************************/
 
-spinlock_t spin_trylock_wo_note(FAR volatile spinlock_t *lock);
+bool spin_trylock_wo_note(FAR volatile spinlock_t *lock);
 
 /****************************************************************************
  * Name: spin_unlock
@@ -285,7 +306,11 @@ void spin_unlock_wo_note(FAR volatile spinlock_t *lock);
  ****************************************************************************/
 
 /* bool spin_islocked(FAR spinlock_t lock); */
-#define spin_islocked(l) (*(l) == SP_LOCKED)
+#ifdef CONFIG_TICKET_SPINLOCK
+#  define spin_islocked(l) ((*l).tickets.owner != (*l).tickets.next)
+#else
+#  define spin_islocked(l) (*(l) == SP_LOCKED)
+#endif
 
 /****************************************************************************
  * Name: spin_setbit
diff --git a/sched/Kconfig b/sched/Kconfig
index fd7a02eead..22b2cd4435 100644
--- a/sched/Kconfig
+++ b/sched/Kconfig
@@ -308,6 +308,16 @@ config SPINLOCK
 		CONFIG_ARCH_HAVE_MULTICPU.  This permits the use of spinlocks in
 		other novel architectures.
 
+if SPINLOCK
+
+config TICKET_SPINLOCK
+	bool "Use ticket Spinlocks"
+	default n
+	---help---
+		Use ticket spinlock algorithm.
+
+endif # SPINLOCK
+
 config IRQCHAIN
 	bool "Enable multi handler sharing a IRQ"
 	default n
diff --git a/sched/irq/irq_csection.c b/sched/irq/irq_csection.c
index a4acd0bc61..b924355054 100644
--- a/sched/irq/irq_csection.c
+++ b/sched/irq/irq_csection.c
@@ -120,7 +120,7 @@ static bool irq_waitlock(int cpu)
    * for the deadlock condition.
    */
 
-  while (spin_trylock_wo_note(&g_cpu_irqlock) == SP_LOCKED)
+  while (!spin_trylock_wo_note(&g_cpu_irqlock))
     {
       /* Is a pause request pending? */
 
diff --git a/sched/semaphore/CMakeLists.txt b/sched/semaphore/CMakeLists.txt
index cea7d21ac2..9ed4e0bdfa 100644
--- a/sched/semaphore/CMakeLists.txt
+++ b/sched/semaphore/CMakeLists.txt
@@ -40,4 +40,8 @@ if(CONFIG_SPINLOCK)
   list(APPEND CSRCS spinlock.c)
 endif()
 
+if(CONFIG_TICKET_SPINLOCK)
+  list(APPEND CSRCS spinlock.c)
+endif()
+
 target_sources(sched PRIVATE ${CSRCS})
diff --git a/sched/semaphore/spinlock.c b/sched/semaphore/spinlock.c
index 6c95a19921..6037affaa5 100644
--- a/sched/semaphore/spinlock.c
+++ b/sched/semaphore/spinlock.c
@@ -32,9 +32,13 @@
 #include <nuttx/sched_note.h>
 #include <arch/irq.h>
 
+#ifdef CONFIG_TICKET_SPINLOCK
+#  include <stdatomic.h>
+#endif
+
 #include "sched/sched.h"
 
-#ifdef CONFIG_SPINLOCK
+#if defined(CONFIG_SPINLOCK) || defined(CONFIG_TICKET_SPINLOCK)
 
 /****************************************************************************
  * Public Functions
@@ -71,7 +75,12 @@ void spin_lock(FAR volatile spinlock_t *lock)
   sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_LOCK);
 #endif
 
+#ifdef CONFIG_TICKET_SPINLOCK
+  uint16_t ticket = atomic_fetch_add(&lock->tickets.next, 1);
+  while (atomic_load(&lock->tickets.owner) != ticket)
+#else /* CONFIG_SPINLOCK */
   while (up_testset(lock) == SP_LOCKED)
+#endif
     {
       SP_DSB();
       SP_WFE();
@@ -109,7 +118,12 @@ void spin_lock(FAR volatile spinlock_t *lock)
 
 void spin_lock_wo_note(FAR volatile spinlock_t *lock)
 {
+#ifdef CONFIG_TICKET_SPINLOCK
+  uint16_t ticket = atomic_fetch_add(&lock->tickets.next, 1);
+  while (atomic_load(&lock->tickets.owner) != ticket)
+#else /* CONFIG_TICKET_SPINLOCK */
   while (up_testset(lock) == SP_LOCKED)
+#endif
     {
       SP_DSB();
       SP_WFE();
@@ -129,15 +143,15 @@ void spin_lock_wo_note(FAR volatile spinlock_t *lock)
  *   lock - A reference to the spinlock object to lock.
  *
  * Returned Value:
- *   SP_LOCKED   - Failure, the spinlock was already locked
- *   SP_UNLOCKED - Success, the spinlock was successfully locked
+ *   false   - Failure, the spinlock was already locked
+ *   true    - Success, the spinlock was successfully locked
  *
  * Assumptions:
  *   Not running at the interrupt level.
  *
  ****************************************************************************/
 
-spinlock_t spin_trylock(FAR volatile spinlock_t *lock)
+bool spin_trylock(FAR volatile spinlock_t *lock)
 {
 #ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
   /* Notify that we are waiting for a spinlock */
@@ -145,7 +159,27 @@ spinlock_t spin_trylock(FAR volatile spinlock_t *lock)
   sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_LOCK);
 #endif
 
+#ifdef CONFIG_TICKET_SPINLOCK
+  uint16_t ticket = atomic_load(&lock->tickets.next);
+
+  spinlock_t old =
+    {
+        {
+          ticket, ticket
+        }
+    };
+
+  spinlock_t new =
+    {
+        {
+          ticket, ticket + 1
+        }
+    };
+
+  if (!atomic_compare_exchange_strong(&lock->value, &old.value, new.value))
+#else /* CONFIG_TICKET_SPINLOCK */
   if (up_testset(lock) == SP_LOCKED)
+#endif /* CONFIG_TICKET_SPINLOCK */ 
     {
 #ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
       /* Notify that we abort for a spinlock */
@@ -153,7 +187,7 @@ spinlock_t spin_trylock(FAR volatile spinlock_t *lock)
       sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_ABORT);
 #endif
       SP_DSB();
-      return SP_LOCKED;
+      return false;
     }
 
 #ifdef CONFIG_SCHED_INSTRUMENTATION_SPINLOCKS
@@ -162,7 +196,7 @@ spinlock_t spin_trylock(FAR volatile spinlock_t *lock)
   sched_note_spinlock(this_task(), lock, NOTE_SPINLOCK_LOCKED);
 #endif
   SP_DMB();
-  return SP_UNLOCKED;
+  return true;
 }
 
 /****************************************************************************
@@ -179,24 +213,44 @@ spinlock_t spin_trylock(FAR volatile spinlock_t *lock)
  *   lock - A reference to the spinlock object to lock.
  *
  * Returned Value:
- *   SP_LOCKED   - Failure, the spinlock was already locked
- *   SP_UNLOCKED - Success, the spinlock was successfully locked
+ *   false   - Failure, the spinlock was already locked
+ *   true    - Success, the spinlock was successfully locked
  *
  * Assumptions:
  *   Not running at the interrupt level.
  *
  ****************************************************************************/
 
-spinlock_t spin_trylock_wo_note(FAR volatile spinlock_t *lock)
+bool spin_trylock_wo_note(FAR volatile spinlock_t *lock)
 {
+#ifdef CONFIG_TICKET_SPINLOCK
+  uint16_t ticket = atomic_load(&lock->tickets.next);
+
+  spinlock_t old =
+    {
+        {
+          ticket, ticket
+        }
+    };
+
+  spinlock_t new =
+    {
+        {
+          ticket, ticket + 1
+        }
+    };
+
+  if (!atomic_compare_exchange_strong(&lock->value, &old.value, new.value))
+#else /* CONFIG_TICKET_SPINLOCK */
   if (up_testset(lock) == SP_LOCKED)
+#endif /* CONFIG_TICKET_SPINLOCK */ 
     {
       SP_DSB();
-      return SP_LOCKED;
+      return false;
     }
 
   SP_DMB();
-  return SP_UNLOCKED;
+  return true;
 }
 
 /****************************************************************************
@@ -226,7 +280,11 @@ void spin_unlock(FAR volatile spinlock_t *lock)
 #endif
 
   SP_DMB();
+#ifdef CONFIG_TICKET_SPINLOCK
+  atomic_fetch_add(&lock->tickets.owner, 1);
+#else
   *lock = SP_UNLOCKED;
+#endif
   SP_DSB();
   SP_SEV();
 }
@@ -255,7 +313,11 @@ void spin_unlock(FAR volatile spinlock_t *lock)
 void spin_unlock_wo_note(FAR volatile spinlock_t *lock)
 {
   SP_DMB();
+#ifdef CONFIG_TICKET_SPINLOCK
+  atomic_fetch_add(&lock->tickets.owner, 1);
+#else
   *lock = SP_UNLOCKED;
+#endif
   SP_DSB();
   SP_SEV();
 }