1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2024 Arm Ltd.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 */
28
29 #include <sys/types.h>
30 #include <sys/errno.h>
31
32 #include <machine/atomic.h>
33 #include <machine/cpu.h>
34 #include <machine/cpufunc.h>
35
36 #include <assert.h>
37 #include <err.h>
38 #include <getopt.h>
39 #include <pthread.h>
40 #include <stdarg.h>
41 #include <stdatomic.h>
42 #include <stdbool.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47
48 /* #define DEBUG_BUFRING */
49
50 #ifdef DEBUG_BUFRING
51 static void
panic(const char * fmt,...)52 panic(const char *fmt, ...)
53 {
54 va_list ap;
55
56 va_start(ap, fmt);
57 vfprintf(stderr, fmt, ap);
58 va_end(ap);
59 fprintf(stderr, "\n");
60 exit(1);
61 }
62 #endif
63
64 static void
critical_enter(void)65 critical_enter(void)
66 {
67 }
68
69 static void
critical_exit(void)70 critical_exit(void)
71 {
72 }
73
74 #include "../../../sys/sys/buf_ring.h"
75
76 #define PROD_ITERATIONS 100000000
77
78 static enum {
79 CT_UNKNOWN,
80 CT_MC,
81 CT_MC_MT,
82 CT_SC,
83 CT_PEEK,
84 CT_PEEK_CLEAR,
85 } cons_type = CT_UNKNOWN;
86
87 static unsigned int prod_count;
88
89 static struct buf_ring *br;
90 static _Atomic bool prod_done = false;
91 static _Atomic int prod_done_count = 0;
92 static _Atomic size_t total_cons_count = 0;
93
94 static uint64_t *mt_seen;
95
96 static void *
producer(void * arg)97 producer(void *arg)
98 {
99 int id, rv;
100
101 id = (int)(uintptr_t)arg;
102
103 for (size_t i = 0; i < PROD_ITERATIONS;) {
104 rv = buf_ring_enqueue(br, (void *)(i * prod_count + 1 + id));
105 if (rv == 0) {
106 i++;
107 }
108 }
109 if ((unsigned int)atomic_fetch_add(&prod_done_count, 1) ==
110 (prod_count - 1))
111 atomic_store(&prod_done, true);
112
113 return (NULL);
114 }
115
116 static void *
consumer(void * arg)117 consumer(void *arg)
118 {
119 void *val;
120 size_t *max_vals;
121 size_t consume_count, curr;
122 int id;
123
124 (void)arg;
125
126 max_vals = calloc(prod_count, sizeof(*max_vals));
127 assert(max_vals != NULL);
128
129 /* Set the initial value to be the expected value */
130 for (unsigned int i = 1; i < prod_count; i++) {
131 max_vals[i] = (int)(i - prod_count);
132 }
133
134 consume_count = 0;
135 while (!atomic_load(&prod_done) || !buf_ring_empty(br)) {
136 switch(cons_type) {
137 case CT_MC:
138 case CT_MC_MT:
139 val = buf_ring_dequeue_mc(br);
140 break;
141 case CT_SC:
142 val = buf_ring_dequeue_sc(br);
143 break;
144 case CT_PEEK:
145 val = buf_ring_peek(br);
146 if (val != NULL)
147 buf_ring_advance_sc(br);
148 break;
149 case CT_PEEK_CLEAR:
150 val = buf_ring_peek_clear_sc(br);
151 if (val != NULL)
152 buf_ring_advance_sc(br);
153 break;
154 case CT_UNKNOWN:
155 __unreachable();
156 }
157 if (val != NULL) {
158 consume_count++;
159 curr = (size_t)(uintptr_t)val;
160 id = curr % prod_count;
161 if (cons_type != CT_MC_MT) {
162 if (curr != max_vals[id] + prod_count)
163 printf("Incorrect val: %zu Expect: %zu "
164 "Difference: %zd\n", curr,
165 max_vals[id] + prod_count,
166 curr - max_vals[id] - prod_count);
167 } else {
168 size_t idx, bit;
169
170 idx = ((size_t)(uintptr_t)val - 1) /
171 (sizeof(*mt_seen) * NBBY);
172 bit = ((size_t)(uintptr_t)val - 1) %
173 (sizeof(*mt_seen) * NBBY);
174
175 if (atomic_testandset_64(&mt_seen[idx], bit))
176 printf("Repeat ID: %zx\n", (size_t)(uintptr_t)val);
177 }
178
179 max_vals[id] = (uintptr_t)val;
180 }
181 }
182
183 atomic_fetch_add(&total_cons_count, consume_count);
184
185 for (unsigned int i = 0; i < prod_count; i++)
186 printf("max[%d] = %zu\n", i, max_vals[i]);
187
188 return (NULL);
189 }
190
191 static struct option longopts[] = {
192 { "buf-size", required_argument, NULL, 'b' },
193 { "cons-type", required_argument, NULL, 'c' },
194 { "prod-count", required_argument, NULL, 'p' },
195 { "help", no_argument, NULL, 'h' },
196 { NULL, 0, NULL, 0 },
197 };
198
199 static void
usage(void)200 usage(void)
201 {
202 errx(1, "test --cons-type=<mc|mc-mt|sc|peek|peek-clear> --prod-count=<prod thread count> [--buf-size=<buf_ring size>]");
203 }
204
205 static uint32_t
next_power_of_2(uint32_t x)206 next_power_of_2(uint32_t x)
207 {
208 x--;
209 x |= x >> 1;
210 x |= x >> 2;
211 x |= x >> 4;
212 x |= x >> 8;
213 x |= x >> 16;
214 x++;
215 return (x);
216 }
217
218 int
main(int argc,char * argv[])219 main(int argc, char *argv[])
220 {
221 pthread_t *prod;
222 pthread_t cons[2];
223 const char *errstr;
224 uint32_t size;
225 int ch, ret;
226
227 size = 0;
228 while ((ch = getopt_long(argc, argv, "bf:", longopts, NULL)) != -1) {
229 switch(ch) {
230 case 'b':
231 errstr = NULL;
232 size = strtonum(optarg, 1, UINT_MAX, &errstr);
233 if (errstr != NULL) {
234 errx(1, "--bufsize=%s: %s", optarg, errstr);
235 }
236 if (!powerof2(size)) {
237 errx(1, "--bufsize needs a power of 2 size");
238 }
239 break;
240 case 'c':
241 if (strcmp(optarg, "mc") == 0) {
242 cons_type = CT_MC;
243 } else if (strcmp(optarg, "mc-mt") == 0) {
244 cons_type = CT_MC_MT;
245 } else if (strcmp(optarg, "sc") == 0) {
246 cons_type = CT_SC;
247 } else if (strcmp(optarg, "peek") == 0) {
248 cons_type = CT_PEEK;
249 } else if (strcmp(optarg, "peek-clear") == 0) {
250 cons_type = CT_PEEK_CLEAR;
251 } else {
252 errx(1, "Unknown --cons-type: %s", optarg);
253 }
254 break;
255 case 'p':
256 errstr = NULL;
257 prod_count = strtonum(optarg, 1, UINT_MAX, &errstr);
258 if (errstr != NULL) {
259 errx(1, "--prod-count=%s: %s", optarg, errstr);
260 }
261 break;
262 case 'h':
263 default:
264 usage();
265 }
266 }
267 argc -= optind;
268 argv += optind;
269
270 if (cons_type == CT_UNKNOWN)
271 errx(1, "No cons-type set");
272
273 if (prod_count == 0)
274 errx(1, "prod-count is not set");
275
276 if (size == 0)
277 size = next_power_of_2(prod_count);
278
279 if (cons_type == CT_MC_MT) {
280 size_t entries;
281
282 entries = (size_t)PROD_ITERATIONS * prod_count;
283 entries = roundup2(entries, sizeof(*mt_seen));
284 mt_seen = calloc(entries / (sizeof(*mt_seen) * NBBY),
285 sizeof(*mt_seen));
286 }
287
288 br = buf_ring_alloc(size);
289
290 ret = pthread_create(&cons[0], NULL, consumer, NULL);
291 assert(ret == 0);
292 if (cons_type == CT_MC_MT) {
293 ret = pthread_create(&cons[1], NULL, consumer, NULL);
294 assert(ret == 0);
295 }
296
297 prod = calloc(prod_count, sizeof(*prod));
298 assert(prod != NULL);
299 for (unsigned i = 0; i < prod_count; i++) {
300 ret = pthread_create(&prod[i], NULL, producer,
301 (void *)(uintptr_t)i);
302 assert(ret == 0);
303 }
304
305 for (unsigned int i = 0; i < prod_count; i++) {
306 ret = pthread_join(prod[i], NULL);
307 assert(ret == 0);
308 }
309 ret = pthread_join(cons[0], NULL);
310 assert(ret == 0);
311 if (cons_type == CT_MC_MT) {
312 ret = pthread_join(cons[1], NULL);
313 assert(ret == 0);
314 }
315
316 printf("Expected: %zu\n", (size_t)PROD_ITERATIONS * prod_count);
317 printf("Received: %zu\n", total_cons_count);
318
319 buf_ring_free(br);
320
321 return (0);
322 }
323