| 1 | #include "sync/tests/test_internal.h" |
| 2 | |
| 3 | TEST_GROUP_DECLARE(qspinlock); |
| 4 | |
| 5 | TEST_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 | |
| 35 | TEST_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 | |
| 55 | static struct qspinlock qspinlock_contention_lock = QSPINLOCK_INIT; |
| 56 | static atomic_bool qspinlock_contention_start = false; |
| 57 | static _Atomic size_t qspinlock_contention_count = 0; |
| 58 | |
| 59 | static 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 | |
| 70 | TEST_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 | |