1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2010-2014 Intel Corporation 3 */ 4 5 #include <stdio.h> 6 #include <stdint.h> 7 #include <inttypes.h> 8 #include <unistd.h> 9 #include <sys/queue.h> 10 #include <string.h> 11 12 #include <rte_common.h> 13 #include <rte_memory.h> 14 #include <rte_per_lcore.h> 15 #include <rte_launch.h> 16 #include <rte_atomic.h> 17 #include <rte_rwlock.h> 18 #include <rte_eal.h> 19 #include <rte_lcore.h> 20 #include <rte_cycles.h> 21 22 #include "test.h" 23 24 /* 25 * rwlock test 26 * =========== 27 * Provides UT for rte_rwlock API. 28 * Main concern is on functional testing, but also provides some 29 * performance measurements. 30 * Obviously for proper testing need to be executed with more than one lcore. 31 */ 32 33 #define ITER_NUM 0x80 34 35 #define TEST_SEC 5 36 37 static rte_rwlock_t sl; 38 static rte_rwlock_t sl_tab[RTE_MAX_LCORE]; 39 40 enum { 41 LC_TYPE_RDLOCK, 42 LC_TYPE_WRLOCK, 43 }; 44 45 static struct { 46 rte_rwlock_t lock; 47 uint64_t tick; 48 volatile union { 49 uint8_t u8[RTE_CACHE_LINE_SIZE]; 50 uint64_t u64[RTE_CACHE_LINE_SIZE / sizeof(uint64_t)]; 51 } data; 52 } __rte_cache_aligned try_rwlock_data; 53 54 struct try_rwlock_lcore { 55 int32_t rc; 56 int32_t type; 57 struct { 58 uint64_t tick; 59 uint64_t fail; 60 uint64_t success; 61 } stat; 62 } __rte_cache_aligned; 63 64 static struct try_rwlock_lcore try_lcore_data[RTE_MAX_LCORE]; 65 66 static int 67 test_rwlock_per_core(__attribute__((unused)) void *arg) 68 { 69 rte_rwlock_write_lock(&sl); 70 printf("Global write lock taken on core %u\n", rte_lcore_id()); 71 rte_rwlock_write_unlock(&sl); 72 73 rte_rwlock_write_lock(&sl_tab[rte_lcore_id()]); 74 printf("Hello from core %u !\n", rte_lcore_id()); 75 rte_rwlock_write_unlock(&sl_tab[rte_lcore_id()]); 76 77 rte_rwlock_read_lock(&sl); 78 printf("Global read lock taken on core %u\n", rte_lcore_id()); 79 rte_delay_ms(100); 80 printf("Release global read lock on core %u\n", rte_lcore_id()); 81 rte_rwlock_read_unlock(&sl); 82 83 return 0; 84 } 85 86 /* 87 * - There is a global rwlock and a table of rwlocks (one per lcore). 88 * 89 * - The test function takes all of these locks and launches the 90 * ``test_rwlock_per_core()`` function on each core (except the master). 91 * 92 * - The function takes the global write lock, display something, 93 * then releases the global lock. 94 * - Then, it takes the per-lcore write lock, display something, and 95 * releases the per-core lock. 96 * - Finally, a read lock is taken during 100 ms, then released. 97 * 98 * - The main function unlocks the per-lcore locks sequentially and 99 * waits between each lock. This triggers the display of a message 100 * for each core, in the correct order. 101 * 102 * Then, it tries to take the global write lock and display the last 103 * message. The autotest script checks that the message order is correct. 104 */ 105 static int 106 rwlock_test1(void) 107 { 108 int i; 109 110 rte_rwlock_init(&sl); 111 for (i=0; i<RTE_MAX_LCORE; i++) 112 rte_rwlock_init(&sl_tab[i]); 113 114 rte_rwlock_write_lock(&sl); 115 116 RTE_LCORE_FOREACH_SLAVE(i) { 117 rte_rwlock_write_lock(&sl_tab[i]); 118 rte_eal_remote_launch(test_rwlock_per_core, NULL, i); 119 } 120 121 rte_rwlock_write_unlock(&sl); 122 123 RTE_LCORE_FOREACH_SLAVE(i) { 124 rte_rwlock_write_unlock(&sl_tab[i]); 125 rte_delay_ms(100); 126 } 127 128 rte_rwlock_write_lock(&sl); 129 /* this message should be the last message of test */ 130 printf("Global write lock taken on master core %u\n", rte_lcore_id()); 131 rte_rwlock_write_unlock(&sl); 132 133 rte_eal_mp_wait_lcore(); 134 135 return 0; 136 } 137 138 static int 139 try_read(uint32_t lc) 140 { 141 int32_t rc; 142 uint32_t i; 143 144 rc = rte_rwlock_read_trylock(&try_rwlock_data.lock); 145 if (rc != 0) 146 return rc; 147 148 for (i = 0; i != RTE_DIM(try_rwlock_data.data.u64); i++) { 149 150 /* race condition occurred, lock doesn't work properly */ 151 if (try_rwlock_data.data.u64[i] != 0) { 152 printf("%s(%u) error: unexpected data pattern\n", 153 __func__, lc); 154 rte_memdump(stdout, NULL, 155 (void *)(uintptr_t)&try_rwlock_data.data, 156 sizeof(try_rwlock_data.data)); 157 rc = -EFAULT; 158 break; 159 } 160 } 161 162 rte_rwlock_read_unlock(&try_rwlock_data.lock); 163 return rc; 164 } 165 166 static int 167 try_write(uint32_t lc) 168 { 169 int32_t rc; 170 uint32_t i, v; 171 172 v = RTE_MAX(lc % UINT8_MAX, 1U); 173 174 rc = rte_rwlock_write_trylock(&try_rwlock_data.lock); 175 if (rc != 0) 176 return rc; 177 178 /* update by bytes in reverese order */ 179 for (i = RTE_DIM(try_rwlock_data.data.u8); i-- != 0; ) { 180 181 /* race condition occurred, lock doesn't work properly */ 182 if (try_rwlock_data.data.u8[i] != 0) { 183 printf("%s:%d(%u) error: unexpected data pattern\n", 184 __func__, __LINE__, lc); 185 rte_memdump(stdout, NULL, 186 (void *)(uintptr_t)&try_rwlock_data.data, 187 sizeof(try_rwlock_data.data)); 188 rc = -EFAULT; 189 break; 190 } 191 192 try_rwlock_data.data.u8[i] = v; 193 } 194 195 /* restore by bytes in reverese order */ 196 for (i = RTE_DIM(try_rwlock_data.data.u8); i-- != 0; ) { 197 198 /* race condition occurred, lock doesn't work properly */ 199 if (try_rwlock_data.data.u8[i] != v) { 200 printf("%s:%d(%u) error: unexpected data pattern\n", 201 __func__, __LINE__, lc); 202 rte_memdump(stdout, NULL, 203 (void *)(uintptr_t)&try_rwlock_data.data, 204 sizeof(try_rwlock_data.data)); 205 rc = -EFAULT; 206 break; 207 } 208 209 try_rwlock_data.data.u8[i] = 0; 210 } 211 212 rte_rwlock_write_unlock(&try_rwlock_data.lock); 213 return rc; 214 } 215 216 static int 217 try_read_lcore(__rte_unused void *data) 218 { 219 int32_t rc; 220 uint32_t i, lc; 221 uint64_t ftm, stm, tm; 222 struct try_rwlock_lcore *lcd; 223 224 lc = rte_lcore_id(); 225 lcd = try_lcore_data + lc; 226 lcd->type = LC_TYPE_RDLOCK; 227 228 ftm = try_rwlock_data.tick; 229 stm = rte_get_timer_cycles(); 230 231 do { 232 for (i = 0; i != ITER_NUM; i++) { 233 rc = try_read(lc); 234 if (rc == 0) 235 lcd->stat.success++; 236 else if (rc == -EBUSY) 237 lcd->stat.fail++; 238 else 239 break; 240 rc = 0; 241 } 242 tm = rte_get_timer_cycles() - stm; 243 } while (tm < ftm && rc == 0); 244 245 lcd->rc = rc; 246 lcd->stat.tick = tm; 247 return rc; 248 } 249 250 static int 251 try_write_lcore(__rte_unused void *data) 252 { 253 int32_t rc; 254 uint32_t i, lc; 255 uint64_t ftm, stm, tm; 256 struct try_rwlock_lcore *lcd; 257 258 lc = rte_lcore_id(); 259 lcd = try_lcore_data + lc; 260 lcd->type = LC_TYPE_WRLOCK; 261 262 ftm = try_rwlock_data.tick; 263 stm = rte_get_timer_cycles(); 264 265 do { 266 for (i = 0; i != ITER_NUM; i++) { 267 rc = try_write(lc); 268 if (rc == 0) 269 lcd->stat.success++; 270 else if (rc == -EBUSY) 271 lcd->stat.fail++; 272 else 273 break; 274 rc = 0; 275 } 276 tm = rte_get_timer_cycles() - stm; 277 } while (tm < ftm && rc == 0); 278 279 lcd->rc = rc; 280 lcd->stat.tick = tm; 281 return rc; 282 } 283 284 static void 285 print_try_lcore_stats(const struct try_rwlock_lcore *tlc, uint32_t lc) 286 { 287 uint64_t f, s; 288 289 f = RTE_MAX(tlc->stat.fail, 1ULL); 290 s = RTE_MAX(tlc->stat.success, 1ULL); 291 292 printf("try_lcore_data[%u]={\n" 293 "\trc=%d,\n" 294 "\ttype=%s,\n" 295 "\tfail=%" PRIu64 ",\n" 296 "\tsuccess=%" PRIu64 ",\n" 297 "\tcycles=%" PRIu64 ",\n" 298 "\tcycles/op=%#Lf,\n" 299 "\tcycles/success=%#Lf,\n" 300 "\tsuccess/fail=%#Lf,\n" 301 "};\n", 302 lc, 303 tlc->rc, 304 tlc->type == LC_TYPE_RDLOCK ? "RDLOCK" : "WRLOCK", 305 tlc->stat.fail, 306 tlc->stat.success, 307 tlc->stat.tick, 308 (long double)tlc->stat.tick / 309 (tlc->stat.fail + tlc->stat.success), 310 (long double)tlc->stat.tick / s, 311 (long double)tlc->stat.success / f); 312 } 313 314 static void 315 collect_try_lcore_stats(struct try_rwlock_lcore *tlc, 316 const struct try_rwlock_lcore *lc) 317 { 318 tlc->stat.tick += lc->stat.tick; 319 tlc->stat.fail += lc->stat.fail; 320 tlc->stat.success += lc->stat.success; 321 } 322 323 /* 324 * Process collected results: 325 * - check status 326 * - collect and print statistics 327 */ 328 static int 329 process_try_lcore_stats(void) 330 { 331 int32_t rc; 332 uint32_t lc, rd, wr; 333 struct try_rwlock_lcore rlc, wlc; 334 335 memset(&rlc, 0, sizeof(rlc)); 336 memset(&wlc, 0, sizeof(wlc)); 337 338 rlc.type = LC_TYPE_RDLOCK; 339 wlc.type = LC_TYPE_WRLOCK; 340 rd = 0; 341 wr = 0; 342 343 rc = 0; 344 RTE_LCORE_FOREACH(lc) { 345 rc |= try_lcore_data[lc].rc; 346 if (try_lcore_data[lc].type == LC_TYPE_RDLOCK) { 347 collect_try_lcore_stats(&rlc, try_lcore_data + lc); 348 rd++; 349 } else { 350 collect_try_lcore_stats(&wlc, try_lcore_data + lc); 351 wr++; 352 } 353 } 354 355 if (rc == 0) { 356 RTE_LCORE_FOREACH(lc) 357 print_try_lcore_stats(try_lcore_data + lc, lc); 358 359 if (rd != 0) { 360 printf("aggregated stats for %u RDLOCK cores:\n", rd); 361 print_try_lcore_stats(&rlc, rd); 362 } 363 364 if (wr != 0) { 365 printf("aggregated stats for %u WRLOCK cores:\n", wr); 366 print_try_lcore_stats(&wlc, wr); 367 } 368 } 369 370 return rc; 371 } 372 373 static void 374 try_test_reset(void) 375 { 376 memset(&try_lcore_data, 0, sizeof(try_lcore_data)); 377 memset(&try_rwlock_data, 0, sizeof(try_rwlock_data)); 378 try_rwlock_data.tick = TEST_SEC * rte_get_tsc_hz(); 379 } 380 381 /* all lcores grab RDLOCK */ 382 static int 383 try_rwlock_test_rda(void) 384 { 385 try_test_reset(); 386 387 /* start read test on all avaialble lcores */ 388 rte_eal_mp_remote_launch(try_read_lcore, NULL, CALL_MASTER); 389 rte_eal_mp_wait_lcore(); 390 391 return process_try_lcore_stats(); 392 } 393 394 /* all slave lcores grab RDLOCK, master one grabs WRLOCK */ 395 static int 396 try_rwlock_test_rds_wrm(void) 397 { 398 try_test_reset(); 399 400 rte_eal_mp_remote_launch(try_read_lcore, NULL, SKIP_MASTER); 401 try_write_lcore(NULL); 402 rte_eal_mp_wait_lcore(); 403 404 return process_try_lcore_stats(); 405 } 406 407 /* master and even slave lcores grab RDLOCK, odd lcores grab WRLOCK */ 408 static int 409 try_rwlock_test_rde_wro(void) 410 { 411 uint32_t lc, mlc; 412 413 try_test_reset(); 414 415 mlc = rte_get_master_lcore(); 416 417 RTE_LCORE_FOREACH(lc) { 418 if (lc != mlc) { 419 if ((lc & 1) == 0) 420 rte_eal_remote_launch(try_read_lcore, 421 NULL, lc); 422 else 423 rte_eal_remote_launch(try_write_lcore, 424 NULL, lc); 425 } 426 } 427 try_read_lcore(NULL); 428 rte_eal_mp_wait_lcore(); 429 430 return process_try_lcore_stats(); 431 } 432 433 static int 434 test_rwlock(void) 435 { 436 uint32_t i; 437 int32_t rc, ret; 438 439 static const struct { 440 const char *name; 441 int (*ftst)(void); 442 } test[] = { 443 { 444 .name = "rwlock_test1", 445 .ftst = rwlock_test1, 446 }, 447 { 448 .name = "try_rwlock_test_rda", 449 .ftst = try_rwlock_test_rda, 450 }, 451 { 452 .name = "try_rwlock_test_rds_wrm", 453 .ftst = try_rwlock_test_rds_wrm, 454 }, 455 { 456 .name = "try_rwlock_test_rde_wro", 457 .ftst = try_rwlock_test_rde_wro, 458 }, 459 }; 460 461 ret = 0; 462 for (i = 0; i != RTE_DIM(test); i++) { 463 printf("starting test %s;\n", test[i].name); 464 rc = test[i].ftst(); 465 printf("test %s completed with status %d\n", test[i].name, rc); 466 ret |= rc; 467 } 468 469 return ret; 470 } 471 472 REGISTER_TEST_COMMAND(rwlock_autotest, test_rwlock); 473