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 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 65 critical_enter(void) 66 { 67 } 68 69 static 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 * 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 * 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 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 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 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