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