The Zawrs extension adds a pair of instructions that stall a core until
a memory location is written to. This patch uses one of them to
implement RISC-V specific versions of the rte_wait_until_equal_*
functions. This is potentially more energy efficient than the default
implementation that uses rte_pause/Zihintpause.

The technique works as follows:

* Check the value with a plain load first and return immediately if it
  already matches, as the generic implementation does
* Otherwise create a reservation set containing the address we want to
  wait on using an atomic load (lr.dw)
* Call wrs.nto - this blocks until the reservation set is invalidated by
  someone else writing to that address
* Execution can also resume arbitrarily, so we still need to check
  whether a change occurred and loop if not

Due to RISC-V atomics only supporting naturally aligned word (32 bit)
and double word (64 bit) loads, I've used pointer rounding and bit
shifting to implement waiting on 16-bit values.

The code is enabled at build time when the target ISA string contains
the Zawrs extension, detected through the compiler's __riscv_zawrs
architecture test macro, e.g. -Dcpu_instruction_set=rv64gcv_zawrs.

This is based on an earlier RFC by Daniel Gregory.

Signed-off-by: Daniel Gregory <[email protected]>
Signed-off-by: Sun Yuechi <[email protected]>
---
v2:
* rebased on main
* use build-time detection via __riscv_zawrs instead of a manual meson flag
* change memorder parameter type from int to rte_memory_order
* fix inverted alignment assert in RTE_WAIT_UNTIL_MASKED
* make the size in RTE_WAIT_UNTIL_MASKED a compile-time constant
* add a plain load before taking the reservation
* add release notes and cross-build documentation

 config/riscv/meson.build                      |   9 +
 .../linux_gsg/cross_build_dpdk_for_riscv.rst  |  26 +++
 doc/guides/rel_notes/release_26_11.rst        |   8 +
 lib/eal/riscv/include/rte_pause.h             | 159 ++++++++++++++++++
 4 files changed, 202 insertions(+)

diff --git a/config/riscv/meson.build b/config/riscv/meson.build
index 7f3e3301bb..5370aeac9e 100644
--- a/config/riscv/meson.build
+++ b/config/riscv/meson.build
@@ -153,6 +153,15 @@ if (riscv_extension_macros and
     endif
 endif
 
+# detect Zawrs (wait-on-reservation-set) extension, used by 
rte_wait_until_equal_*
+# to stall the hart until the polled memory location is written instead of 
spinning
+if (riscv_extension_macros and
+        (cc.get_define('__riscv_zawrs', args: machine_args) != ''))
+    message('Compiling with the Zawrs extension')
+    machine_args += ['-DRTE_RISCV_FEATURE_ZAWRS']
+    dpdk_flags += [['RTE_RISCV_FEATURE_ZAWRS', 1],]
+endif
+
 # apply flags
 foreach flag: dpdk_flags
     if flag.length() > 0
diff --git a/doc/guides/linux_gsg/cross_build_dpdk_for_riscv.rst 
b/doc/guides/linux_gsg/cross_build_dpdk_for_riscv.rst
index bcba12a604..c7be173b9e 100644
--- a/doc/guides/linux_gsg/cross_build_dpdk_for_riscv.rst
+++ b/doc/guides/linux_gsg/cross_build_dpdk_for_riscv.rst
@@ -115,3 +115,29 @@ Currently the following targets are supported:
 To add a new target support, ``config/riscv/meson.build`` has to be modified by
 adding a new vendor/architecture id and a corresponding cross-file has to be
 added to ``config/riscv`` directory.
+
+
+Optional ISA extensions
+-----------------------
+
+Some optimisations are enabled at build time when the target ISA string
+includes the extension.
+The build looks at the per-extension macros the compiler defines when
+``__riscv_arch_test`` is available, such as ``__riscv_zawrs``:
+
+``v``
+   Vector implementations (``RTE_RISCV_FEATURE_V``).
+
+``zawrs``
+   ``rte_wait_until_equal_*`` stall the hart with ``wrs.nto``
+   instead of spinning (``RTE_RISCV_FEATURE_ZAWRS``).
+
+Pass the full ISA string to enable them.
+For native builds use ``-Dcpu_instruction_set``::
+
+   meson setup build -Dcpu_instruction_set=rv64gcv_zawrs
+
+For cross builds, set ``cpu`` in the cross-file instead::
+
+   [host_machine]
+   cpu = 'rv64gcv_zawrs'
diff --git a/doc/guides/rel_notes/release_26_11.rst 
b/doc/guides/rel_notes/release_26_11.rst
index 8ca8379cc6..4cce0b478a 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -68,6 +68,14 @@ New Features
   and use pointer arithmetic instead of integer casts to enable compiler 
optimizations.
   See API changes below.
 
+* **Added RISC-V Zawrs support for wait-until-equal helpers.**
+
+  On RISC-V targets built with the Zawrs extension in the ISA string
+  (for example ``-Dcpu_instruction_set=rv64gcv_zawrs``),
+  ``rte_wait_until_equal_*`` and ``RTE_WAIT_UNTIL_MASKED`` now stall the hart
+  with ``wrs.nto`` until the polled address is written instead of spinning
+  on ``pause``.
+
 * **Added API to get CPU socket ID.**
 
   Added the experimental ``rte_cpu_socket_id()`` function
diff --git a/lib/eal/riscv/include/rte_pause.h 
b/lib/eal/riscv/include/rte_pause.h
index 3f473cd8db..99d7f46711 100644
--- a/lib/eal/riscv/include/rte_pause.h
+++ b/lib/eal/riscv/include/rte_pause.h
@@ -7,6 +7,14 @@
 #ifndef RTE_PAUSE_RISCV_H
 #define RTE_PAUSE_RISCV_H
 
+#include <rte_config.h>
+
+#ifdef RTE_RISCV_FEATURE_ZAWRS
+#define RTE_WAIT_UNTIL_EQUAL_ARCH_DEFINED
+#endif
+
+#include <rte_debug.h>
+
 #include "rte_atomic.h"
 
 #include "generic/rte_pause.h"
@@ -24,6 +32,157 @@ static inline void rte_pause(void)
        asm volatile(".int 0x0100000F" : : : "memory");
 }
 
+#ifdef RTE_RISCV_FEATURE_ZAWRS
+
+/*
+ * Atomic load from an address, it returns either a sign-extended word or
+ * doubleword and creates a 'reservation set' containing the read memory
+ * location. When someone else writes to the reservation set, it is 
invalidated,
+ * causing any stalled WRS instructions to resume.
+ *
+ * Address needs to be naturally aligned.
+ */
+#define __RTE_RISCV_LR_32(src, dst, memorder) do {                \
+       if ((memorder) == rte_memory_order_relaxed) {             \
+               asm volatile("lr.w %0, (%1)"                      \
+                               : "=r" (dst)                      \
+                               : "r" (src)                       \
+                               : "memory");                      \
+       } else {                                                  \
+               asm volatile("lr.w.aq %0, (%1)"                   \
+                               : "=r" (dst)                      \
+                               : "r" (src)                       \
+                               : "memory");                      \
+       } } while (0)
+#define __RTE_RISCV_LR_64(src, dst, memorder) do {                \
+       if ((memorder) == rte_memory_order_relaxed) {             \
+               asm volatile("lr.d %0, (%1)"                      \
+                               : "=r" (dst)                      \
+                               : "r" (src)                       \
+                               : "memory");                      \
+       } else {                                                  \
+               asm volatile("lr.d.aq %0, (%1)"                   \
+                               : "=r" (dst)                      \
+                               : "r" (src)                       \
+                               : "memory");                      \
+       } } while (0)
+
+/*
+ * There's not a RISC-V atomic load primitive for halfwords, so cast up to a
+ * _naturally aligned_ word and extract the halfword we want
+ */
+#define __RTE_RISCV_LR_16(src, dst, memorder) do {                      \
+       uint32_t word;                                                  \
+       __RTE_RISCV_LR_32(((uintptr_t)(src) & (~3)), word, (memorder)); \
+       if ((size_t)(src) & 3)                                          \
+               (dst) = (typeof(dst))(word >> 16);                      \
+       else                                                            \
+               (dst) = (typeof(dst))(word & 0xFFFF);                   \
+} while (0)
+
+#define __RTE_RISCV_LR(src, dst, memorder, size) {                \
+       RTE_BUILD_BUG_ON(size != 16 && size != 32 && size != 64); \
+       if (size == 16)                                           \
+               __RTE_RISCV_LR_16(src, dst, memorder);            \
+       else if (size == 32)                                      \
+               __RTE_RISCV_LR_32(src, dst, memorder);            \
+       else if (size == 64)                                      \
+               __RTE_RISCV_LR_64(src, dst, memorder);            \
+}
+
+/*
+ * Wait-on-Reservation-Set extension instruction, it stalls execution until the
+ * reservation set is invalidated or an interrupt is observed.
+ * A loop is likely still needed as it may stop stalling arbitrarily.
+ */
+#define __RTE_RISCV_WRS_NTO() { asm volatile("wrs.nto" : : : "memory"); }
+
+static inline void
+rte_wait_until_equal_16(volatile uint16_t *addr, uint16_t expected,
+               rte_memory_order memorder)
+{
+       uint16_t value;
+
+       RTE_ASSERT(memorder == rte_memory_order_acquire ||
+               memorder == rte_memory_order_relaxed);
+       RTE_ASSERT(((size_t)addr & 1) == 0);
+
+       /* fast path: a plain load is cheaper than taking a reservation */
+       if (rte_atomic_load_explicit((volatile __rte_atomic uint16_t *)addr,
+                       memorder) == expected)
+               return;
+
+       __RTE_RISCV_LR_16(addr, value, memorder);
+       while (value != expected) {
+               __RTE_RISCV_WRS_NTO();
+               __RTE_RISCV_LR_16(addr, value, memorder);
+       }
+}
+
+static inline void
+rte_wait_until_equal_32(volatile uint32_t *addr, uint32_t expected,
+               rte_memory_order memorder)
+{
+       uint32_t value;
+
+       RTE_ASSERT(memorder == rte_memory_order_acquire ||
+               memorder == rte_memory_order_relaxed);
+       RTE_ASSERT(((size_t)addr & 3) == 0);
+
+       /* fast path: a plain load is cheaper than taking a reservation */
+       if (rte_atomic_load_explicit((volatile __rte_atomic uint32_t *)addr,
+                       memorder) == expected)
+               return;
+
+       __RTE_RISCV_LR_32(addr, value, memorder);
+       while (value != expected) {
+               __RTE_RISCV_WRS_NTO();
+               __RTE_RISCV_LR_32(addr, value, memorder);
+       }
+}
+
+static inline void
+rte_wait_until_equal_64(volatile uint64_t *addr, uint64_t expected,
+               rte_memory_order memorder)
+{
+       uint64_t value;
+
+       RTE_ASSERT(memorder == rte_memory_order_acquire ||
+               memorder == rte_memory_order_relaxed);
+       RTE_ASSERT(((size_t)addr & 7) == 0);
+
+       /* fast path: a plain load is cheaper than taking a reservation */
+       if (rte_atomic_load_explicit((volatile __rte_atomic uint64_t *)addr,
+                       memorder) == expected)
+               return;
+
+       __RTE_RISCV_LR_64(addr, value, memorder);
+       while (value != expected) {
+               __RTE_RISCV_WRS_NTO();
+               __RTE_RISCV_LR_64(addr, value, memorder);
+       }
+}
+
+#define RTE_WAIT_UNTIL_MASKED(addr, mask, cond, expected, memorder) do { \
+       RTE_BUILD_BUG_ON(!__builtin_constant_p(memorder));               \
+       RTE_BUILD_BUG_ON(memorder != rte_memory_order_acquire &&         \
+               memorder != rte_memory_order_relaxed);                   \
+       RTE_ASSERT(((size_t)(addr) & (sizeof(*(addr)) - 1)) == 0);       \
+       enum { size = sizeof(*(addr)) << 3 };                            \
+       typeof(*(addr)) expected_value = (expected);                     \
+       typeof(*(addr)) value;                                           \
+       if ((rte_atomic_load_explicit((addr), (memorder)) & (mask))      \
+                       cond expected_value)                             \
+               break;                                                   \
+       __RTE_RISCV_LR((addr), value, memorder, size);                   \
+       while (!((value & (mask)) cond expected_value)) {                \
+               __RTE_RISCV_WRS_NTO();                                   \
+               __RTE_RISCV_LR((addr), value, memorder, size);           \
+       }                                                                \
+} while (0)
+
+#endif /* RTE_RISCV_FEATURE_ZAWRS */
+
 #ifdef __cplusplus
 }
 #endif
-- 
2.56.0

Reply via email to