| 1 | #include "sync/tests/test_internal.h" |
| 2 | |
| 3 | #include <test/fleet.h> |
| 4 | |
| 5 | TEST_GROUP_DEFINE(qspinlock); |
| 6 | |
| 7 | TEST_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 | |
| 37 | TEST_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 | |
| 57 | struct qspin_contention_fix { |
| 58 | struct qspinlock lock; |
| 59 | atomic_size_t count; |
| 60 | }; |
| 61 | |
| 62 | static 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 | |
| 76 | TEST_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 | |