1 /*
2 * Copyright (c) 2023 Intel Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/ztest.h>
8 #include <zephyr/timing/timing.h>
9 #include <zephyr/sys/spsc_lockfree.h>
10
11 /*
12 * @brief Produce and Consume a single uint32_t in the same execution context
13 *
14 * @see spsc_acquire(), spsc_produce(), spsc_consume(), spsc_release()
15 *
16 * @ingroup tests
17 */
ZTEST(spsc,test_produce_consume_size1)18 ZTEST(spsc, test_produce_consume_size1)
19 {
20 SPSC_DEFINE(ezspsc, uint32_t, 1);
21
22 const uint32_t magic = 43219876;
23
24 uint32_t *acq = spsc_acquire(&ezspsc);
25
26 zassert_not_null(acq, "Acquire should succeed");
27
28 *acq = magic;
29
30 uint32_t *acq2 = spsc_acquire(&ezspsc);
31
32 zassert_is_null(acq2, "Acquire should fail");
33
34 uint32_t *cons = spsc_consume(&ezspsc);
35
36 zassert_is_null(cons, "Consume should fail");
37
38 zassert_equal(spsc_consumable(&ezspsc), 0, "Consumables should be 0");
39
40 spsc_produce(&ezspsc);
41
42 zassert_equal(spsc_consumable(&ezspsc), 1, "Consumables should be 1");
43
44 uint32_t *cons2 = spsc_consume(&ezspsc);
45
46 zassert_equal(spsc_consumable(&ezspsc), 0, "Consumables should be 0");
47
48 zassert_not_null(cons2, "Consume should not fail");
49 zassert_equal(*cons2, magic, "Consume value should equal magic");
50
51 uint32_t *cons3 = spsc_consume(&ezspsc);
52
53 zassert_is_null(cons3, "Consume should fail");
54
55
56 uint32_t *acq3 = spsc_acquire(&ezspsc);
57
58 zassert_is_null(acq3, "Acquire should not succeed");
59
60 spsc_release(&ezspsc);
61
62 uint32_t *acq4 = spsc_acquire(&ezspsc);
63
64 zassert_not_null(acq4, "Acquire should succeed");
65 }
66
67 /*&*
68 * @brief Produce and Consume 3 items at a time in a spsc of size 4 to validate masking
69 * and wrap around reads/writes.
70 *
71 * @see spsc_acquire(), spsc_produce(), spsc_consume(), spsc_release()
72 *
73 * @ingroup tests
74 */
ZTEST(spsc,test_produce_consume_wrap_around)75 ZTEST(spsc, test_produce_consume_wrap_around)
76 {
77 SPSC_DEFINE(ezspsc, uint32_t, 4);
78
79 for (int i = 0; i < 10; i++) {
80 zassert_equal(spsc_consumable(&ezspsc), 0, "Consumables should be 0");
81 for (int j = 0; j < 3; j++) {
82 uint32_t *entry = spsc_acquire(&ezspsc);
83
84 zassert_not_null(entry, "Acquire should succeed");
85 *entry = i * 3 + j;
86 spsc_produce(&ezspsc);
87 }
88 zassert_equal(spsc_consumable(&ezspsc), 3, "Consumables should be 3");
89
90 for (int k = 0; k < 3; k++) {
91 uint32_t *entry = spsc_consume(&ezspsc);
92
93 zassert_not_null(entry, "Consume should succeed");
94 zassert_equal(*entry, i * 3 + k, "Consume value should equal i*3+k");
95 spsc_release(&ezspsc);
96 }
97
98 zassert_equal(spsc_consumable(&ezspsc), 0, "Consumables should be 0");
99
100 }
101 }
102
103 /**
104 * @brief Ensure that integer wraps continue to work.
105 *
106 * Done by setting all values to UINTPTR_MAX - 2 and writing and reading enough
107 * to ensure integer wraps occur.
108 */
ZTEST(spsc,test_int_wrap_around)109 ZTEST(spsc, test_int_wrap_around)
110 {
111 SPSC_DEFINE(ezspsc, uint32_t, 4);
112 ezspsc._spsc.in = ATOMIC_INIT(UINTPTR_MAX - 2);
113 ezspsc._spsc.out = ATOMIC_INIT(UINTPTR_MAX - 2);
114
115 for (int j = 0; j < 3; j++) {
116 uint32_t *entry = spsc_acquire(&ezspsc);
117
118 zassert_not_null(entry, "Acquire should succeed");
119 *entry = j;
120 spsc_produce(&ezspsc);
121 }
122
123 zassert_equal(atomic_get(&ezspsc._spsc.in), UINTPTR_MAX + 1, "Spsc in should wrap");
124
125 for (int k = 0; k < 3; k++) {
126 uint32_t *entry = spsc_consume(&ezspsc);
127
128 zassert_not_null(entry, "Consume should succeed");
129 zassert_equal(*entry, k, "Consume value should equal i*3+k");
130 spsc_release(&ezspsc);
131 }
132
133 zassert_equal(atomic_get(&ezspsc._spsc.out), UINTPTR_MAX + 1, "Spsc out should wrap");
134 }
135
136 #define MAX_RETRIES 5
137 #define SMP_ITERATIONS 100
138
139 SPSC_DEFINE(spsc, uint32_t, 4);
140
t1_consume(void * p1,void * p2,void * p3)141 static void t1_consume(void *p1, void *p2, void *p3)
142 {
143 ARG_UNUSED(p2);
144 ARG_UNUSED(p3);
145
146 struct spsc_spsc *ezspsc = p1;
147 uint32_t retries = 0;
148 uint32_t *val = NULL;
149
150 for (int i = 0; i < SMP_ITERATIONS; i++) {
151 val = NULL;
152 retries = 0;
153 while (val == NULL && retries < MAX_RETRIES) {
154 val = spsc_consume(ezspsc);
155 retries++;
156 }
157 if (val != NULL) {
158 spsc_release(ezspsc);
159 } else {
160 k_yield();
161 }
162 }
163 }
164
t2_produce(void * p1,void * p2,void * p3)165 static void t2_produce(void *p1, void *p2, void *p3)
166 {
167 ARG_UNUSED(p2);
168 ARG_UNUSED(p3);
169
170 struct spsc_spsc *ezspsc = p1;
171 uint32_t retries = 0;
172 uint32_t *val = NULL;
173
174 for (int i = 0; i < SMP_ITERATIONS; i++) {
175 val = NULL;
176 retries = 0;
177 while (val == NULL && retries < MAX_RETRIES) {
178 val = spsc_acquire(ezspsc);
179 retries++;
180 }
181 if (val != NULL) {
182 *val = SMP_ITERATIONS;
183 spsc_produce(ezspsc);
184 } else {
185 k_yield();
186 }
187 }
188 }
189
190
191 #define STACK_SIZE (384 + CONFIG_TEST_EXTRA_STACK_SIZE)
192 #define THREADS_NUM 2
193
194 struct thread_info {
195 k_tid_t tid;
196 int executed;
197 int priority;
198 int cpu_id;
199 };
200
201 static struct thread_info tinfo[THREADS_NUM];
202 static struct k_thread tthread[THREADS_NUM];
203 static K_THREAD_STACK_ARRAY_DEFINE(tstack, THREADS_NUM, STACK_SIZE);
204
205 /**
206 * @brief Test that the producer and consumer are indeed thread safe
207 *
208 * This can and should be validated on SMP machines where incoherent
209 * memory could cause issues.
210 */
ZTEST(spsc,test_spsc_threaded)211 ZTEST(spsc, test_spsc_threaded)
212 {
213
214 tinfo[0].tid =
215 k_thread_create(&tthread[0], tstack[0], STACK_SIZE,
216 t1_consume,
217 &spsc, NULL, NULL,
218 K_PRIO_PREEMPT(5),
219 K_INHERIT_PERMS, K_NO_WAIT);
220 tinfo[1].tid =
221 k_thread_create(&tthread[1], tstack[1], STACK_SIZE,
222 t2_produce,
223 &spsc, NULL, NULL,
224 K_PRIO_PREEMPT(5),
225 K_INHERIT_PERMS, K_NO_WAIT);
226
227 k_thread_join(tinfo[1].tid, K_FOREVER);
228 k_thread_join(tinfo[0].tid, K_FOREVER);
229 }
230
231 #define THROUGHPUT_ITERS 100000
232
ZTEST(spsc,test_spsc_throughput)233 ZTEST(spsc, test_spsc_throughput)
234 {
235 timing_t start_time, end_time;
236
237 timing_init();
238 timing_start();
239
240 start_time = timing_counter_get();
241
242 uint32_t *x, *y;
243
244 int key = irq_lock();
245
246 for (int i = 0; i < THROUGHPUT_ITERS; i++) {
247 x = spsc_acquire(&spsc);
248 *x = i;
249 spsc_produce(&spsc);
250
251 y = spsc_consume(&spsc);
252 spsc_release(&spsc);
253 }
254
255 irq_unlock(key);
256
257 end_time = timing_counter_get();
258
259 uint64_t cycles = timing_cycles_get(&start_time, &end_time);
260 uint64_t ns = timing_cycles_to_ns(cycles);
261
262 TC_PRINT("%llu ns for %d iterations, %llu ns per op\n", ns,
263 THROUGHPUT_ITERS, ns/THROUGHPUT_ITERS);
264 }
265
spsc_before(void * data)266 static void spsc_before(void *data)
267 {
268 ARG_UNUSED(data);
269
270 spsc_reset(&spsc);
271 }
272
273 ZTEST_SUITE(spsc, NULL, NULL, spsc_before, NULL, NULL);
274