1#include "sync/tests/test_internal.h"
2
3#include <test/fleet.h>
4
5TEST_GROUP_DEFINE(qspinlock);
6
7TEST_DEFINE_UNIT(qspinlock, tail_encoding) {
8 /* Test tail encoding across CPUs and context levels */
9 cpu_id_t cpus[] = {0, 1, 15, 255, 1024, 65534};
10 enum qspinlock_level levels[] = {QSPINLOCK_LEVEL_NORMAL,
11 QSPINLOCK_LEVEL_IRQ};
12
13 for (size_t c = 0; c < sizeof(cpus) / sizeof(cpus[0]); c++) {
14 for (size_t l = 0; l < sizeof(levels) / sizeof(levels[0]); l++) {
15 cpu_id_t cpu = cpus[c];
16 enum qspinlock_level lvl = levels[l];
17
18 uint32_t tail = ((cpu + 1) << Q_SPIN_TAIL_CPU_OFFSET) |
19 (lvl << Q_SPIN_TAIL_LVL_OFFSET);
20
21 /* Tail bits must not overlap locked byte or pending bit */
22 TEST_ASSERT_EQ((tail & Q_SPIN_LOCKED_PENDING_MASK), 0);
23
24 /* Extract and verify fields */
25 cpu_id_t decoded_cpu = (tail >> Q_SPIN_TAIL_CPU_OFFSET) - 1;
26 enum qspinlock_level decoded_lvl =
27 (tail & Q_SPIN_TAIL_LVL_MASK) >> Q_SPIN_TAIL_LVL_OFFSET;
28
29 TEST_ASSERT_EQ(decoded_cpu, cpu);
30 TEST_ASSERT_EQ(decoded_lvl, lvl);
31 }
32 }
33
34 return TEST_SUCCESS;
35}
36
37TEST_DEFINE_UNIT(qspinlock, pending_to_locked_math) {
38 /* Test the transition: lock has tail + pending bit, and adding
39 * (Q_SPIN_LOCKED_VAL - Q_SPIN_PENDING_VAL) = -255 */
40 uint32_t tail = (42 << Q_SPIN_TAIL_CPU_OFFSET) |
41 (QSPINLOCK_LEVEL_NORMAL << Q_SPIN_TAIL_LVL_OFFSET);
42 uint32_t val = tail | Q_SPIN_PENDING_VAL;
43
44 uint32_t next = val + (Q_SPIN_LOCKED_VAL - Q_SPIN_PENDING_VAL);
45
46 /* Lock bit set, pending bit cleared, tail preserved */
47 TEST_ASSERT_EQ((next & Q_SPIN_LOCKED_MASK), Q_SPIN_LOCKED_VAL);
48 TEST_ASSERT_EQ((next & Q_SPIN_PENDING_MASK), 0);
49 TEST_ASSERT_EQ((next & Q_SPIN_TAIL_MASK), tail);
50
51 return TEST_SUCCESS;
52}
53
54#define QSPINLOCK_CONTENTION_THREADS 12
55#define QSPINLOCK_CONTENTION_ITERS 300
56
57struct qspin_contention_fix {
58 struct qspinlock lock;
59 atomic_size_t count;
60};
61
62static bool qspinlock_contention_worker(struct test_fleet *f,
63 struct test_conc_worker *w) {
64 struct qspin_contention_fix *fix = w->arg;
65 cc_ignore(f);
66
67 for (size_t i = 0; i < QSPINLOCK_CONTENTION_ITERS; i++) {
68 enum irql irql = qspin_lock(&fix->lock);
69 atomic_inc(&fix->count);
70 qspin_unlock(&fix->lock, irql);
71 }
72
73 return true;
74}
75
76TEST_DEFINE_INTEGRATION(qspinlock, contended_handoff, .min_cores = 2) {
77 struct test_fleet *fleet = test_fleet_init(ctx);
78
79 struct qspin_contention_fix *fix = test_new(fix);
80 fix->lock = (struct qspinlock) QSPINLOCK_INIT;
81
82 for (size_t i = 0; i < QSPINLOCK_CONTENTION_THREADS; i++)
83 test_fleet_spawn(fleet, "qspin_contend", qspinlock_contention_worker,
84 .arg = fix);
85
86 test_fleet_start_all(f: fleet);
87
88 struct test_verdict v = test_fleet_join(f: fleet);
89 if (v.result != TEST_RESULT_OK)
90 return v;
91
92 TEST_ASSERT_EQ(atomic_load(&fix->count),
93 QSPINLOCK_CONTENTION_THREADS * QSPINLOCK_CONTENTION_ITERS);
94
95 return TEST_SUCCESS;
96}
97