1#include "sync/tests/test_internal.h"
2
3TEST_GROUP_DECLARE(qspinlock);
4
5TEST_DECLARE_UNIT(qspinlock, tail_encoding) {
6 /* Test tail encoding across CPUs and context levels */
7 cpu_id_t cpus[] = {0, 1, 15, 255, 1024, 65534};
8 enum qspinlock_level levels[] = {QSPINLOCK_LEVEL_NORMAL,
9 QSPINLOCK_LEVEL_IRQ};
10
11 for (size_t c = 0; c < sizeof(cpus) / sizeof(cpus[0]); c++) {
12 for (size_t l = 0; l < sizeof(levels) / sizeof(levels[0]); l++) {
13 cpu_id_t cpu = cpus[c];
14 enum qspinlock_level lvl = levels[l];
15
16 uint32_t tail = ((cpu + 1) << Q_SPIN_TAIL_CPU_OFFSET) |
17 (lvl << Q_SPIN_TAIL_LVL_OFFSET);
18
19 /* Tail bits must not overlap locked byte or pending bit */
20 TEST_ASSERT_EQ((tail & Q_SPIN_LOCKED_PENDING_MASK), 0);
21
22 /* Extract and verify fields */
23 cpu_id_t decoded_cpu = (tail >> Q_SPIN_TAIL_CPU_OFFSET) - 1;
24 enum qspinlock_level decoded_lvl =
25 (tail & Q_SPIN_TAIL_LVL_MASK) >> Q_SPIN_TAIL_LVL_OFFSET;
26
27 TEST_ASSERT_EQ(decoded_cpu, cpu);
28 TEST_ASSERT_EQ(decoded_lvl, lvl);
29 }
30 }
31
32 return TEST_SUCCESS;
33}
34
35TEST_DECLARE_UNIT(qspinlock, pending_to_locked_math) {
36 /* Test the transition: lock has tail + pending bit, and adding
37 * (Q_SPIN_LOCKED_VAL - Q_SPIN_PENDING_VAL) = -255 */
38 uint32_t tail = (42 << Q_SPIN_TAIL_CPU_OFFSET) |
39 (QSPINLOCK_LEVEL_NORMAL << Q_SPIN_TAIL_LVL_OFFSET);
40 uint32_t val = tail | Q_SPIN_PENDING_VAL;
41
42 uint32_t next = val + (Q_SPIN_LOCKED_VAL - Q_SPIN_PENDING_VAL);
43
44 /* Lock bit set, pending bit cleared, tail preserved */
45 TEST_ASSERT_EQ((next & Q_SPIN_LOCKED_MASK), Q_SPIN_LOCKED_VAL);
46 TEST_ASSERT_EQ((next & Q_SPIN_PENDING_MASK), 0);
47 TEST_ASSERT_EQ((next & Q_SPIN_TAIL_MASK), tail);
48
49 return TEST_SUCCESS;
50}
51
52#define QSPINLOCK_CONTENTION_THREADS 12
53#define QSPINLOCK_CONTENTION_ITERS 300
54
55static struct qspinlock qspinlock_contention_lock = QSPINLOCK_INIT;
56static atomic_bool qspinlock_contention_start = false;
57static _Atomic size_t qspinlock_contention_count = 0;
58
59static void qspinlock_contention_worker(void *) {
60 while (!atomic_load(&qspinlock_contention_start))
61 cpu_relax();
62
63 for (size_t i = 0; i < QSPINLOCK_CONTENTION_ITERS; i++) {
64 enum irql irql = qspin_lock(&qspinlock_contention_lock);
65 atomic_fetch_add(&qspinlock_contention_count, 1);
66 qspin_unlock(&qspinlock_contention_lock, irql);
67 }
68}
69
70TEST_DECLARE_INTEGRATION(qspinlock, contended_handoff) {
71 if (global.core_count < 2)
72 return TEST_SKIP(TEST_SKIP_NONE);
73
74 atomic_store(&qspinlock_contention_start, false);
75 atomic_store(&qspinlock_contention_count, 0);
76
77 struct thread *workers[QSPINLOCK_CONTENTION_THREADS];
78 for (size_t i = 0; i < QSPINLOCK_CONTENTION_THREADS; i++) {
79 workers[i] = thread_spawn_joinable(name: "qspin_contend",
80 entry: qspinlock_contention_worker, NULL);
81 TEST_ASSERT_NONNULL(workers[i]);
82 }
83
84 atomic_store(&qspinlock_contention_start, true);
85
86 for (size_t i = 0; i < QSPINLOCK_CONTENTION_THREADS; i++)
87 thread_join(t: workers[i]);
88
89 TEST_ASSERT_EQ(atomic_load(&qspinlock_contention_count),
90 QSPINLOCK_CONTENTION_THREADS * QSPINLOCK_CONTENTION_ITERS);
91
92 return TEST_SUCCESS;
93}
94