1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2017-2018 Intel Corporation 3 */ 4 5 #include <inttypes.h> 6 #include <limits.h> 7 #include <stdint.h> 8 #include <errno.h> 9 #include <string.h> 10 #include <unistd.h> 11 12 #include <rte_common.h> 13 #include <rte_eal_paging.h> 14 #include <rte_errno.h> 15 #include <rte_log.h> 16 #include <rte_spinlock.h> 17 18 #include "eal_filesystem.h" 19 #include "eal_private.h" 20 21 #include "rte_fbarray.h" 22 23 #define MASK_SHIFT 6ULL 24 #define MASK_ALIGN (1ULL << MASK_SHIFT) 25 #define MASK_LEN_TO_IDX(x) ((x) >> MASK_SHIFT) 26 #define MASK_LEN_TO_MOD(x) ((x) - RTE_ALIGN_FLOOR(x, MASK_ALIGN)) 27 #define MASK_GET_IDX(idx, mod) ((idx << MASK_SHIFT) + mod) 28 29 /* 30 * We use this to keep track of created/attached memory areas to prevent user 31 * errors in API usage. 32 */ 33 struct mem_area { 34 TAILQ_ENTRY(mem_area) next; 35 void *addr; 36 size_t len; 37 int fd; 38 }; 39 TAILQ_HEAD(mem_area_head, mem_area); 40 /* local per-process tailq */ 41 static struct mem_area_head mem_area_tailq = 42 TAILQ_HEAD_INITIALIZER(mem_area_tailq); 43 static rte_spinlock_t mem_area_lock = RTE_SPINLOCK_INITIALIZER; 44 45 /* 46 * This is a mask that is always stored at the end of array, to provide fast 47 * way of finding free/used spots without looping through each element. 48 */ 49 50 struct used_mask { 51 unsigned int n_masks; 52 uint64_t data[]; 53 }; 54 55 static size_t 56 calc_mask_size(unsigned int len) 57 { 58 /* mask must be multiple of MASK_ALIGN, even though length of array 59 * itself may not be aligned on that boundary. 60 */ 61 len = RTE_ALIGN_CEIL(len, MASK_ALIGN); 62 return sizeof(struct used_mask) + 63 sizeof(uint64_t) * MASK_LEN_TO_IDX(len); 64 } 65 66 static size_t 67 calc_data_size(size_t page_sz, unsigned int elt_sz, unsigned int len) 68 { 69 size_t data_sz = elt_sz * len; 70 size_t msk_sz = calc_mask_size(len); 71 return RTE_ALIGN_CEIL(data_sz + msk_sz, page_sz); 72 } 73 74 static struct used_mask * 75 get_used_mask(void *data, unsigned int elt_sz, unsigned int len) 76 { 77 return (struct used_mask *) RTE_PTR_ADD(data, elt_sz * len); 78 } 79 80 static int 81 resize_and_map(int fd, const char *path, void *addr, size_t len) 82 { 83 void *map_addr; 84 85 if (eal_file_truncate(fd, len)) { 86 EAL_LOG(ERR, "Cannot truncate %s", path); 87 return -1; 88 } 89 90 map_addr = rte_mem_map(addr, len, RTE_PROT_READ | RTE_PROT_WRITE, 91 RTE_MAP_SHARED | RTE_MAP_FORCE_ADDRESS, fd, 0); 92 if (map_addr != addr) { 93 return -1; 94 } 95 return 0; 96 } 97 98 static int 99 overlap(const struct mem_area *ma, const void *start, size_t len) 100 { 101 const void *end = RTE_PTR_ADD(start, len); 102 const void *ma_start = ma->addr; 103 const void *ma_end = RTE_PTR_ADD(ma->addr, ma->len); 104 105 /* start overlap? */ 106 if (start >= ma_start && start < ma_end) 107 return 1; 108 /* end overlap? */ 109 if (end > ma_start && end < ma_end) 110 return 1; 111 return 0; 112 } 113 114 static int 115 find_next_n(const struct rte_fbarray *arr, unsigned int start, unsigned int n, 116 bool used) 117 { 118 const struct used_mask *msk = get_used_mask(arr->data, arr->elt_sz, 119 arr->len); 120 unsigned int msk_idx, lookahead_idx, first, first_mod; 121 unsigned int last, last_mod; 122 uint64_t last_msk, ignore_msk; 123 124 /* 125 * mask only has granularity of MASK_ALIGN, but start may not be aligned 126 * on that boundary, so construct a special mask to exclude anything we 127 * don't want to see to avoid confusing ctz. 128 */ 129 first = MASK_LEN_TO_IDX(start); 130 first_mod = MASK_LEN_TO_MOD(start); 131 ignore_msk = ~((1ULL << first_mod) - 1); 132 133 /* array length may not be aligned, so calculate ignore mask for last 134 * mask index. 135 */ 136 last = MASK_LEN_TO_IDX(arr->len); 137 last_mod = MASK_LEN_TO_MOD(arr->len); 138 last_msk = ~(UINT64_MAX << last_mod); 139 140 for (msk_idx = first; msk_idx < msk->n_masks; msk_idx++) { 141 uint64_t cur_msk, lookahead_msk; 142 unsigned int run_start, clz, left; 143 bool found = false; 144 /* 145 * The process of getting n consecutive bits for arbitrary n is 146 * a bit involved, but here it is in a nutshell: 147 * 148 * 1. let n be the number of consecutive bits we're looking for 149 * 2. check if n can fit in one mask, and if so, do n-1 150 * rshift-ands to see if there is an appropriate run inside 151 * our current mask 152 * 2a. if we found a run, bail out early 153 * 2b. if we didn't find a run, proceed 154 * 3. invert the mask and count leading zeroes (that is, count 155 * how many consecutive set bits we had starting from the 156 * end of current mask) as k 157 * 3a. if k is 0, continue to next mask 158 * 3b. if k is not 0, we have a potential run 159 * 4. to satisfy our requirements, next mask must have n-k 160 * consecutive set bits right at the start, so we will do 161 * (n-k-1) rshift-ands and check if first bit is set. 162 * 163 * Step 4 will need to be repeated if (n-k) > MASK_ALIGN until 164 * we either run out of masks, lose the run, or find what we 165 * were looking for. 166 */ 167 cur_msk = msk->data[msk_idx]; 168 left = n; 169 170 /* if we're looking for free spaces, invert the mask */ 171 if (!used) 172 cur_msk = ~cur_msk; 173 174 /* combine current ignore mask with last index ignore mask */ 175 if (msk_idx == last) 176 ignore_msk &= last_msk; 177 178 /* if we have an ignore mask, ignore once */ 179 if (ignore_msk) { 180 cur_msk &= ignore_msk; 181 ignore_msk = 0; 182 } 183 184 /* if n can fit in within a single mask, do a search */ 185 if (n <= MASK_ALIGN) { 186 uint64_t tmp_msk = cur_msk; 187 unsigned int s_idx; 188 for (s_idx = 0; s_idx < n - 1; s_idx++) 189 tmp_msk &= tmp_msk >> 1ULL; 190 /* we found what we were looking for */ 191 if (tmp_msk != 0) { 192 run_start = rte_ctz64(tmp_msk); 193 return MASK_GET_IDX(msk_idx, run_start); 194 } 195 } 196 197 /* 198 * we didn't find our run within the mask, or n > MASK_ALIGN, 199 * so we're going for plan B. 200 */ 201 202 /* count leading zeroes on inverted mask */ 203 if (~cur_msk == 0) 204 clz = sizeof(cur_msk) * 8; 205 else 206 clz = rte_clz64(~cur_msk); 207 208 /* if there aren't any runs at the end either, just continue */ 209 if (clz == 0) 210 continue; 211 212 /* we have a partial run at the end, so try looking ahead */ 213 run_start = MASK_ALIGN - clz; 214 left -= clz; 215 216 for (lookahead_idx = msk_idx + 1; lookahead_idx < msk->n_masks; 217 lookahead_idx++) { 218 unsigned int s_idx, need; 219 uint64_t first_bit = 1; 220 221 lookahead_msk = msk->data[lookahead_idx]; 222 223 /* if we're looking for free space, invert the mask */ 224 if (!used) 225 lookahead_msk = ~lookahead_msk; 226 227 /* figure out how many consecutive bits we need here */ 228 need = RTE_MIN(left, MASK_ALIGN); 229 230 /* count number of shifts we performed */ 231 for (s_idx = 0; s_idx < need - 1; s_idx++) { 232 lookahead_msk &= lookahead_msk >> 1ULL; 233 /* did we lose the run yet? */ 234 if ((lookahead_msk & first_bit) == 0) 235 break; 236 } 237 238 /* if first bit is not set, we've lost the run */ 239 if ((lookahead_msk & first_bit) == 0) { 240 /* 241 * we've scanned this far, so we know there are 242 * no runs in the space we've lookahead-scanned 243 * as well, so skip that on next iteration. 244 */ 245 ignore_msk = ~((1ULL << (s_idx + 1)) - 1); 246 /* outer loop will increment msk_idx so add 1 */ 247 msk_idx = lookahead_idx - 1; 248 break; 249 } 250 251 left -= need; 252 253 /* check if we've found what we were looking for */ 254 if (left == 0) { 255 found = true; 256 break; 257 } 258 } 259 260 /* we didn't find anything, so continue */ 261 if (!found) 262 continue; 263 264 return MASK_GET_IDX(msk_idx, run_start); 265 } 266 /* we didn't find anything */ 267 rte_errno = used ? ENOENT : ENOSPC; 268 return -1; 269 } 270 271 static int 272 find_next(const struct rte_fbarray *arr, unsigned int start, bool used) 273 { 274 const struct used_mask *msk = get_used_mask(arr->data, arr->elt_sz, 275 arr->len); 276 unsigned int idx, first, first_mod; 277 unsigned int last, last_mod; 278 uint64_t last_msk, ignore_msk; 279 280 /* 281 * mask only has granularity of MASK_ALIGN, but start may not be aligned 282 * on that boundary, so construct a special mask to exclude anything we 283 * don't want to see to avoid confusing ctz. 284 */ 285 first = MASK_LEN_TO_IDX(start); 286 first_mod = MASK_LEN_TO_MOD(start); 287 ignore_msk = ~((1ULL << first_mod) - 1ULL); 288 289 /* array length may not be aligned, so calculate ignore mask for last 290 * mask index. 291 */ 292 last = MASK_LEN_TO_IDX(arr->len); 293 last_mod = MASK_LEN_TO_MOD(arr->len); 294 last_msk = ~(-(1ULL) << last_mod); 295 296 for (idx = first; idx < msk->n_masks; idx++) { 297 uint64_t cur = msk->data[idx]; 298 int found; 299 300 /* if we're looking for free entries, invert mask */ 301 if (!used) 302 cur = ~cur; 303 304 if (idx == last) 305 cur &= last_msk; 306 307 /* ignore everything before start on first iteration */ 308 if (idx == first) 309 cur &= ignore_msk; 310 311 /* check if we have any entries */ 312 if (cur == 0) 313 continue; 314 315 /* 316 * find first set bit - that will correspond to whatever it is 317 * that we're looking for. 318 */ 319 found = rte_ctz64(cur); 320 return MASK_GET_IDX(idx, found); 321 } 322 /* we didn't find anything */ 323 rte_errno = used ? ENOENT : ENOSPC; 324 return -1; 325 } 326 327 static int 328 find_contig(const struct rte_fbarray *arr, unsigned int start, bool used) 329 { 330 const struct used_mask *msk = get_used_mask(arr->data, arr->elt_sz, 331 arr->len); 332 unsigned int idx, first, first_mod; 333 unsigned int last, last_mod; 334 uint64_t last_msk; 335 unsigned int need_len, result = 0; 336 337 /* array length may not be aligned, so calculate ignore mask for last 338 * mask index. 339 */ 340 last = MASK_LEN_TO_IDX(arr->len); 341 last_mod = MASK_LEN_TO_MOD(arr->len); 342 last_msk = ~(-(1ULL) << last_mod); 343 344 first = MASK_LEN_TO_IDX(start); 345 first_mod = MASK_LEN_TO_MOD(start); 346 for (idx = first; idx < msk->n_masks; idx++, result += need_len) { 347 uint64_t cur = msk->data[idx]; 348 unsigned int run_len; 349 350 need_len = MASK_ALIGN; 351 352 /* if we're looking for free entries, invert mask */ 353 if (!used) 354 cur = ~cur; 355 356 /* if this is last mask, ignore everything after last bit */ 357 if (idx == last) 358 cur &= last_msk; 359 360 /* ignore everything before start on first iteration */ 361 if (idx == first) { 362 cur >>= first_mod; 363 /* at the start, we don't need the full mask len */ 364 need_len -= first_mod; 365 } 366 367 /* we will be looking for zeroes, so invert the mask */ 368 cur = ~cur; 369 370 /* if mask is zero, we have a complete run */ 371 if (cur == 0) 372 continue; 373 374 /* 375 * see if current run ends before mask end. 376 */ 377 run_len = rte_ctz64(cur); 378 379 /* add however many zeroes we've had in the last run and quit */ 380 if (run_len < need_len) { 381 result += run_len; 382 break; 383 } 384 } 385 return result; 386 } 387 388 static int 389 find_prev_n(const struct rte_fbarray *arr, unsigned int start, unsigned int n, 390 bool used) 391 { 392 const struct used_mask *msk = get_used_mask(arr->data, arr->elt_sz, 393 arr->len); 394 unsigned int msk_idx, lookbehind_idx, first, first_mod; 395 uint64_t ignore_msk; 396 397 /* 398 * mask only has granularity of MASK_ALIGN, but start may not be aligned 399 * on that boundary, so construct a special mask to exclude anything we 400 * don't want to see to avoid confusing ctz. 401 */ 402 first = MASK_LEN_TO_IDX(start); 403 first_mod = MASK_LEN_TO_MOD(start); 404 /* we're going backwards, so mask must start from the top */ 405 ignore_msk = first_mod == MASK_ALIGN - 1 ? 406 UINT64_MAX : /* prevent overflow */ 407 ~(UINT64_MAX << (first_mod + 1)); 408 409 /* go backwards, include zero */ 410 msk_idx = first; 411 do { 412 uint64_t cur_msk, lookbehind_msk; 413 unsigned int run_start, run_end, ctz, left; 414 bool found = false; 415 /* 416 * The process of getting n consecutive bits from the top for 417 * arbitrary n is a bit involved, but here it is in a nutshell: 418 * 419 * 1. let n be the number of consecutive bits we're looking for 420 * 2. check if n can fit in one mask, and if so, do n-1 421 * lshift-ands to see if there is an appropriate run inside 422 * our current mask 423 * 2a. if we found a run, bail out early 424 * 2b. if we didn't find a run, proceed 425 * 3. invert the mask and count trailing zeroes (that is, count 426 * how many consecutive set bits we had starting from the 427 * start of current mask) as k 428 * 3a. if k is 0, continue to next mask 429 * 3b. if k is not 0, we have a potential run 430 * 4. to satisfy our requirements, next mask must have n-k 431 * consecutive set bits at the end, so we will do (n-k-1) 432 * lshift-ands and check if last bit is set. 433 * 434 * Step 4 will need to be repeated if (n-k) > MASK_ALIGN until 435 * we either run out of masks, lose the run, or find what we 436 * were looking for. 437 */ 438 cur_msk = msk->data[msk_idx]; 439 left = n; 440 441 /* if we're looking for free spaces, invert the mask */ 442 if (!used) 443 cur_msk = ~cur_msk; 444 445 /* if we have an ignore mask, ignore once */ 446 if (ignore_msk) { 447 cur_msk &= ignore_msk; 448 ignore_msk = 0; 449 } 450 451 /* if n can fit in within a single mask, do a search */ 452 if (n <= MASK_ALIGN) { 453 uint64_t tmp_msk = cur_msk; 454 unsigned int s_idx; 455 for (s_idx = 0; s_idx < n - 1; s_idx++) 456 tmp_msk &= tmp_msk << 1ULL; 457 /* we found what we were looking for */ 458 if (tmp_msk != 0) { 459 /* clz will give us offset from end of mask, and 460 * we only get the end of our run, not start, 461 * so adjust result to point to where start 462 * would have been. 463 */ 464 run_start = MASK_ALIGN - 465 rte_clz64(tmp_msk) - n; 466 return MASK_GET_IDX(msk_idx, run_start); 467 } 468 } 469 470 /* 471 * we didn't find our run within the mask, or n > MASK_ALIGN, 472 * so we're going for plan B. 473 */ 474 475 /* count trailing zeroes on inverted mask */ 476 if (~cur_msk == 0) 477 ctz = sizeof(cur_msk) * 8; 478 else 479 ctz = rte_ctz64(~cur_msk); 480 481 /* if there aren't any runs at the start either, just 482 * continue 483 */ 484 if (ctz == 0) 485 continue; 486 487 /* we have a partial run at the start, so try looking behind */ 488 run_end = MASK_GET_IDX(msk_idx, ctz); 489 left -= ctz; 490 491 /* go backwards, include zero */ 492 lookbehind_idx = msk_idx - 1; 493 494 /* we can't lookbehind as we've run out of masks, so stop */ 495 if (msk_idx == 0) 496 break; 497 498 do { 499 const uint64_t last_bit = 1ULL << (MASK_ALIGN - 1); 500 unsigned int s_idx, need; 501 502 lookbehind_msk = msk->data[lookbehind_idx]; 503 504 /* if we're looking for free space, invert the mask */ 505 if (!used) 506 lookbehind_msk = ~lookbehind_msk; 507 508 /* figure out how many consecutive bits we need here */ 509 need = RTE_MIN(left, MASK_ALIGN); 510 511 /* count number of shifts we performed */ 512 for (s_idx = 0; s_idx < need - 1; s_idx++) { 513 lookbehind_msk &= lookbehind_msk << 1ULL; 514 /* did we lose the run yet? */ 515 if ((lookbehind_msk & last_bit) == 0) 516 break; 517 } 518 519 /* if last bit is not set, we've lost the run */ 520 if ((lookbehind_msk & last_bit) == 0) { 521 /* 522 * we've scanned this far, so we know there are 523 * no runs in the space we've lookbehind-scanned 524 * as well, so skip that on next iteration. 525 */ 526 ignore_msk = ~(UINT64_MAX << (MASK_ALIGN - s_idx - 1)); 527 /* outer loop will decrement msk_idx so add 1 */ 528 msk_idx = lookbehind_idx + 1; 529 break; 530 } 531 532 left -= need; 533 534 /* check if we've found what we were looking for */ 535 if (left == 0) { 536 found = true; 537 break; 538 } 539 } while ((lookbehind_idx--) != 0); /* decrement after check to 540 * include zero 541 */ 542 543 /* we didn't find anything, so continue */ 544 if (!found) 545 continue; 546 547 /* we've found what we were looking for, but we only know where 548 * the run ended, so calculate start position. 549 */ 550 return run_end - n; 551 } while (msk_idx-- != 0); /* decrement after check to include zero */ 552 /* we didn't find anything */ 553 rte_errno = used ? ENOENT : ENOSPC; 554 return -1; 555 } 556 557 static int 558 find_prev(const struct rte_fbarray *arr, unsigned int start, bool used) 559 { 560 const struct used_mask *msk = get_used_mask(arr->data, arr->elt_sz, 561 arr->len); 562 unsigned int idx, first, first_mod; 563 uint64_t ignore_msk; 564 565 /* 566 * mask only has granularity of MASK_ALIGN, but start may not be aligned 567 * on that boundary, so construct a special mask to exclude anything we 568 * don't want to see to avoid confusing clz. 569 */ 570 first = MASK_LEN_TO_IDX(start); 571 first_mod = MASK_LEN_TO_MOD(start); 572 /* we're going backwards, so mask must start from the top */ 573 ignore_msk = first_mod == MASK_ALIGN - 1 ? 574 UINT64_MAX : /* prevent overflow */ 575 ~(UINT64_MAX << (first_mod + 1)); 576 577 /* go backwards, include zero */ 578 idx = first; 579 do { 580 uint64_t cur = msk->data[idx]; 581 int found; 582 583 /* if we're looking for free entries, invert mask */ 584 if (!used) 585 cur = ~cur; 586 587 /* ignore everything before start on first iteration */ 588 if (idx == first) 589 cur &= ignore_msk; 590 591 /* check if we have any entries */ 592 if (cur == 0) 593 continue; 594 595 /* 596 * find last set bit - that will correspond to whatever it is 597 * that we're looking for. we're counting trailing zeroes, thus 598 * the value we get is counted from end of mask, so calculate 599 * position from start of mask. 600 */ 601 found = MASK_ALIGN - rte_clz64(cur) - 1; 602 603 return MASK_GET_IDX(idx, found); 604 } while (idx-- != 0); /* decrement after check to include zero*/ 605 606 /* we didn't find anything */ 607 rte_errno = used ? ENOENT : ENOSPC; 608 return -1; 609 } 610 611 static int 612 find_rev_contig(const struct rte_fbarray *arr, unsigned int start, bool used) 613 { 614 const struct used_mask *msk = get_used_mask(arr->data, arr->elt_sz, 615 arr->len); 616 unsigned int idx, first, first_mod; 617 unsigned int need_len, result = 0; 618 619 first = MASK_LEN_TO_IDX(start); 620 first_mod = MASK_LEN_TO_MOD(start); 621 622 /* go backwards, include zero */ 623 idx = first; 624 do { 625 uint64_t cur = msk->data[idx]; 626 unsigned int run_len; 627 628 need_len = MASK_ALIGN; 629 630 /* if we're looking for free entries, invert mask */ 631 if (!used) 632 cur = ~cur; 633 634 /* ignore everything after start on first iteration */ 635 if (idx == first) { 636 unsigned int end_len = MASK_ALIGN - first_mod - 1; 637 cur <<= end_len; 638 /* at the start, we don't need the full mask len */ 639 need_len -= end_len; 640 } 641 642 /* we will be looking for zeroes, so invert the mask */ 643 cur = ~cur; 644 645 /* if mask is zero, we have a complete run */ 646 if (cur == 0) 647 goto endloop; 648 649 /* 650 * see where run ends, starting from the end. 651 */ 652 run_len = rte_clz64(cur); 653 654 /* add however many zeroes we've had in the last run and quit */ 655 if (run_len < need_len) { 656 result += run_len; 657 break; 658 } 659 endloop: 660 result += need_len; 661 } while (idx-- != 0); /* decrement after check to include zero */ 662 return result; 663 } 664 665 static int 666 set_used(struct rte_fbarray *arr, unsigned int idx, bool used) 667 { 668 struct used_mask *msk; 669 uint64_t msk_bit = 1ULL << MASK_LEN_TO_MOD(idx); 670 unsigned int msk_idx = MASK_LEN_TO_IDX(idx); 671 bool already_used; 672 int ret = -1; 673 674 if (arr == NULL || idx >= arr->len) { 675 rte_errno = EINVAL; 676 return -1; 677 } 678 msk = get_used_mask(arr->data, arr->elt_sz, arr->len); 679 ret = 0; 680 681 /* prevent array from changing under us */ 682 rte_rwlock_write_lock(&arr->rwlock); 683 684 already_used = (msk->data[msk_idx] & msk_bit) != 0; 685 686 /* nothing to be done */ 687 if (used == already_used) 688 goto out; 689 690 if (used) { 691 msk->data[msk_idx] |= msk_bit; 692 arr->count++; 693 } else { 694 msk->data[msk_idx] &= ~msk_bit; 695 arr->count--; 696 } 697 out: 698 rte_rwlock_write_unlock(&arr->rwlock); 699 700 return ret; 701 } 702 703 static int 704 fully_validate(const char *name, unsigned int elt_sz, unsigned int len) 705 { 706 if (name == NULL || elt_sz == 0 || len == 0 || len > INT_MAX) { 707 rte_errno = EINVAL; 708 return -1; 709 } 710 711 if (strnlen(name, RTE_FBARRAY_NAME_LEN) == RTE_FBARRAY_NAME_LEN) { 712 rte_errno = ENAMETOOLONG; 713 return -1; 714 } 715 return 0; 716 } 717 718 int 719 rte_fbarray_init(struct rte_fbarray *arr, const char *name, unsigned int len, 720 unsigned int elt_sz) 721 { 722 size_t page_sz, mmap_len; 723 char path[PATH_MAX]; 724 struct used_mask *msk; 725 struct mem_area *ma = NULL; 726 void *data = NULL; 727 int fd = -1; 728 const struct internal_config *internal_conf = 729 eal_get_internal_configuration(); 730 731 if (arr == NULL) { 732 rte_errno = EINVAL; 733 return -1; 734 } 735 736 if (fully_validate(name, elt_sz, len)) 737 return -1; 738 739 /* allocate mem area before doing anything */ 740 ma = malloc(sizeof(*ma)); 741 if (ma == NULL) { 742 rte_errno = ENOMEM; 743 return -1; 744 } 745 746 page_sz = rte_mem_page_size(); 747 if (page_sz == (size_t)-1) { 748 free(ma); 749 return -1; 750 } 751 752 /* calculate our memory limits */ 753 mmap_len = calc_data_size(page_sz, elt_sz, len); 754 755 data = eal_get_virtual_area(NULL, &mmap_len, page_sz, 0, 0); 756 if (data == NULL) { 757 free(ma); 758 return -1; 759 } 760 761 rte_spinlock_lock(&mem_area_lock); 762 763 fd = -1; 764 765 if (internal_conf->no_shconf) { 766 /* remap virtual area as writable */ 767 static const int flags = RTE_MAP_FORCE_ADDRESS | 768 RTE_MAP_PRIVATE | RTE_MAP_ANONYMOUS; 769 void *new_data = rte_mem_map(data, mmap_len, 770 RTE_PROT_READ | RTE_PROT_WRITE, flags, fd, 0); 771 if (new_data == NULL) { 772 EAL_LOG(DEBUG, "%s(): couldn't remap anonymous memory: %s", 773 __func__, rte_strerror(rte_errno)); 774 goto fail; 775 } 776 } else { 777 eal_get_fbarray_path(path, sizeof(path), name); 778 779 /* 780 * Each fbarray is unique to process namespace, i.e. the 781 * filename depends on process prefix. Try to take out a lock 782 * and see if we succeed. If we don't, someone else is using it 783 * already. 784 */ 785 fd = eal_file_open(path, EAL_OPEN_CREATE | EAL_OPEN_READWRITE); 786 if (fd < 0) { 787 EAL_LOG(DEBUG, "%s(): couldn't open %s: %s", 788 __func__, path, rte_strerror(rte_errno)); 789 goto fail; 790 } else if (eal_file_lock( 791 fd, EAL_FLOCK_EXCLUSIVE, EAL_FLOCK_RETURN)) { 792 EAL_LOG(DEBUG, "%s(): couldn't lock %s: %s", 793 __func__, path, rte_strerror(rte_errno)); 794 rte_errno = EBUSY; 795 goto fail; 796 } 797 798 /* take out a non-exclusive lock, so that other processes could 799 * still attach to it, but no other process could reinitialize 800 * it. 801 */ 802 if (eal_file_lock(fd, EAL_FLOCK_SHARED, EAL_FLOCK_RETURN)) 803 goto fail; 804 805 if (resize_and_map(fd, path, data, mmap_len)) 806 goto fail; 807 } 808 ma->addr = data; 809 ma->len = mmap_len; 810 ma->fd = fd; 811 812 /* do not close fd - keep it until detach/destroy */ 813 TAILQ_INSERT_TAIL(&mem_area_tailq, ma, next); 814 815 /* initialize the data */ 816 memset(data, 0, mmap_len); 817 818 /* populate data structure */ 819 strlcpy(arr->name, name, sizeof(arr->name)); 820 arr->data = data; 821 arr->len = len; 822 arr->elt_sz = elt_sz; 823 arr->count = 0; 824 825 msk = get_used_mask(data, elt_sz, len); 826 msk->n_masks = MASK_LEN_TO_IDX(RTE_ALIGN_CEIL(len, MASK_ALIGN)); 827 828 rte_rwlock_init(&arr->rwlock); 829 830 rte_spinlock_unlock(&mem_area_lock); 831 832 return 0; 833 fail: 834 if (data) 835 rte_mem_unmap(data, mmap_len); 836 if (fd >= 0) 837 close(fd); 838 free(ma); 839 840 rte_spinlock_unlock(&mem_area_lock); 841 return -1; 842 } 843 844 int 845 rte_fbarray_attach(struct rte_fbarray *arr) 846 { 847 struct mem_area *ma = NULL, *tmp = NULL; 848 size_t page_sz, mmap_len; 849 char path[PATH_MAX]; 850 void *data = NULL; 851 int fd = -1; 852 853 if (arr == NULL) { 854 rte_errno = EINVAL; 855 return -1; 856 } 857 858 /* 859 * we don't need to synchronize attach as two values we need (element 860 * size and array length) are constant for the duration of life of 861 * the array, so the parts we care about will not race. 862 */ 863 864 if (fully_validate(arr->name, arr->elt_sz, arr->len)) 865 return -1; 866 867 ma = malloc(sizeof(*ma)); 868 if (ma == NULL) { 869 rte_errno = ENOMEM; 870 return -1; 871 } 872 873 page_sz = rte_mem_page_size(); 874 if (page_sz == (size_t)-1) { 875 free(ma); 876 return -1; 877 } 878 879 mmap_len = calc_data_size(page_sz, arr->elt_sz, arr->len); 880 881 /* check the tailq - maybe user has already mapped this address space */ 882 rte_spinlock_lock(&mem_area_lock); 883 884 TAILQ_FOREACH(tmp, &mem_area_tailq, next) { 885 if (overlap(tmp, arr->data, mmap_len)) { 886 rte_errno = EEXIST; 887 goto fail; 888 } 889 } 890 891 /* we know this memory area is unique, so proceed */ 892 893 data = eal_get_virtual_area(arr->data, &mmap_len, page_sz, 0, 0); 894 if (data == NULL) 895 goto fail; 896 897 eal_get_fbarray_path(path, sizeof(path), arr->name); 898 899 fd = eal_file_open(path, EAL_OPEN_READWRITE); 900 if (fd < 0) { 901 goto fail; 902 } 903 904 /* lock the file, to let others know we're using it */ 905 if (eal_file_lock(fd, EAL_FLOCK_SHARED, EAL_FLOCK_RETURN)) 906 goto fail; 907 908 if (resize_and_map(fd, path, data, mmap_len)) 909 goto fail; 910 911 /* store our new memory area */ 912 ma->addr = data; 913 ma->fd = fd; /* keep fd until detach/destroy */ 914 ma->len = mmap_len; 915 916 TAILQ_INSERT_TAIL(&mem_area_tailq, ma, next); 917 918 /* we're done */ 919 920 rte_spinlock_unlock(&mem_area_lock); 921 return 0; 922 fail: 923 if (data) 924 rte_mem_unmap(data, mmap_len); 925 if (fd >= 0) 926 close(fd); 927 free(ma); 928 rte_spinlock_unlock(&mem_area_lock); 929 return -1; 930 } 931 932 int 933 rte_fbarray_detach(struct rte_fbarray *arr) 934 { 935 struct mem_area *tmp = NULL; 936 size_t mmap_len; 937 int ret = -1; 938 939 if (arr == NULL) { 940 rte_errno = EINVAL; 941 return -1; 942 } 943 944 /* 945 * we don't need to synchronize detach as two values we need (element 946 * size and total capacity) are constant for the duration of life of 947 * the array, so the parts we care about will not race. if the user is 948 * detaching while doing something else in the same process, we can't 949 * really do anything about it, things will blow up either way. 950 */ 951 952 size_t page_sz = rte_mem_page_size(); 953 if (page_sz == (size_t)-1) 954 return -1; 955 956 mmap_len = calc_data_size(page_sz, arr->elt_sz, arr->len); 957 958 /* does this area exist? */ 959 rte_spinlock_lock(&mem_area_lock); 960 961 TAILQ_FOREACH(tmp, &mem_area_tailq, next) { 962 if (tmp->addr == arr->data && tmp->len == mmap_len) 963 break; 964 } 965 if (tmp == NULL) { 966 rte_errno = ENOENT; 967 ret = -1; 968 goto out; 969 } 970 971 rte_mem_unmap(arr->data, mmap_len); 972 973 /* area is unmapped, close fd and remove the tailq entry */ 974 if (tmp->fd >= 0) 975 close(tmp->fd); 976 TAILQ_REMOVE(&mem_area_tailq, tmp, next); 977 free(tmp); 978 979 ret = 0; 980 out: 981 rte_spinlock_unlock(&mem_area_lock); 982 return ret; 983 } 984 985 int 986 rte_fbarray_destroy(struct rte_fbarray *arr) 987 { 988 struct mem_area *tmp = NULL; 989 size_t mmap_len; 990 int fd, ret; 991 char path[PATH_MAX]; 992 const struct internal_config *internal_conf = 993 eal_get_internal_configuration(); 994 995 if (arr == NULL) { 996 rte_errno = EINVAL; 997 return -1; 998 } 999 1000 /* 1001 * we don't need to synchronize detach as two values we need (element 1002 * size and total capacity) are constant for the duration of life of 1003 * the array, so the parts we care about will not race. if the user is 1004 * detaching while doing something else in the same process, we can't 1005 * really do anything about it, things will blow up either way. 1006 */ 1007 1008 size_t page_sz = rte_mem_page_size(); 1009 if (page_sz == (size_t)-1) 1010 return -1; 1011 1012 mmap_len = calc_data_size(page_sz, arr->elt_sz, arr->len); 1013 1014 /* does this area exist? */ 1015 rte_spinlock_lock(&mem_area_lock); 1016 1017 TAILQ_FOREACH(tmp, &mem_area_tailq, next) { 1018 if (tmp->addr == arr->data && tmp->len == mmap_len) 1019 break; 1020 } 1021 if (tmp == NULL) { 1022 rte_errno = ENOENT; 1023 ret = -1; 1024 goto out; 1025 } 1026 /* with no shconf, there were never any files to begin with */ 1027 if (!internal_conf->no_shconf) { 1028 /* 1029 * attempt to get an exclusive lock on the file, to ensure it 1030 * has been detached by all other processes 1031 */ 1032 fd = tmp->fd; 1033 if (eal_file_lock(fd, EAL_FLOCK_EXCLUSIVE, EAL_FLOCK_RETURN)) { 1034 EAL_LOG(DEBUG, "Cannot destroy fbarray - another process is using it"); 1035 rte_errno = EBUSY; 1036 ret = -1; 1037 goto out; 1038 } 1039 1040 /* we're OK to destroy the file */ 1041 eal_get_fbarray_path(path, sizeof(path), arr->name); 1042 if (unlink(path)) { 1043 EAL_LOG(DEBUG, "Cannot unlink fbarray: %s", 1044 strerror(errno)); 1045 rte_errno = errno; 1046 /* 1047 * we're still holding an exclusive lock, so drop it to 1048 * shared. 1049 */ 1050 eal_file_lock(fd, EAL_FLOCK_SHARED, EAL_FLOCK_RETURN); 1051 1052 ret = -1; 1053 goto out; 1054 } 1055 close(fd); 1056 } 1057 rte_mem_unmap(arr->data, mmap_len); 1058 1059 /* area is unmapped, remove the tailq entry */ 1060 TAILQ_REMOVE(&mem_area_tailq, tmp, next); 1061 free(tmp); 1062 ret = 0; 1063 1064 /* reset the fbarray structure */ 1065 memset(arr, 0, sizeof(*arr)); 1066 out: 1067 rte_spinlock_unlock(&mem_area_lock); 1068 return ret; 1069 } 1070 1071 void * 1072 rte_fbarray_get(const struct rte_fbarray *arr, unsigned int idx) 1073 { 1074 void *ret = NULL; 1075 if (arr == NULL) { 1076 rte_errno = EINVAL; 1077 return NULL; 1078 } 1079 1080 if (idx >= arr->len) { 1081 rte_errno = EINVAL; 1082 return NULL; 1083 } 1084 1085 ret = RTE_PTR_ADD(arr->data, idx * arr->elt_sz); 1086 1087 return ret; 1088 } 1089 1090 int 1091 rte_fbarray_set_used(struct rte_fbarray *arr, unsigned int idx) 1092 { 1093 return set_used(arr, idx, true); 1094 } 1095 1096 int 1097 rte_fbarray_set_free(struct rte_fbarray *arr, unsigned int idx) 1098 { 1099 return set_used(arr, idx, false); 1100 } 1101 1102 int 1103 rte_fbarray_is_used(struct rte_fbarray *arr, unsigned int idx) 1104 { 1105 struct used_mask *msk; 1106 int msk_idx; 1107 uint64_t msk_bit; 1108 int ret = -1; 1109 1110 if (arr == NULL || idx >= arr->len) { 1111 rte_errno = EINVAL; 1112 return -1; 1113 } 1114 1115 /* prevent array from changing under us */ 1116 rte_rwlock_read_lock(&arr->rwlock); 1117 1118 msk = get_used_mask(arr->data, arr->elt_sz, arr->len); 1119 msk_idx = MASK_LEN_TO_IDX(idx); 1120 msk_bit = 1ULL << MASK_LEN_TO_MOD(idx); 1121 1122 ret = (msk->data[msk_idx] & msk_bit) != 0; 1123 1124 rte_rwlock_read_unlock(&arr->rwlock); 1125 1126 return ret; 1127 } 1128 1129 static int 1130 fbarray_find(struct rte_fbarray *arr, unsigned int start, bool next, bool used) 1131 { 1132 int ret = -1; 1133 1134 if (arr == NULL || start >= arr->len) { 1135 rte_errno = EINVAL; 1136 return -1; 1137 } 1138 1139 /* prevent array from changing under us */ 1140 rte_rwlock_read_lock(&arr->rwlock); 1141 1142 /* cheap checks to prevent doing useless work */ 1143 if (!used) { 1144 if (arr->len == arr->count) { 1145 rte_errno = ENOSPC; 1146 goto out; 1147 } 1148 if (arr->count == 0) { 1149 ret = start; 1150 goto out; 1151 } 1152 } else { 1153 if (arr->count == 0) { 1154 rte_errno = ENOENT; 1155 goto out; 1156 } 1157 if (arr->len == arr->count) { 1158 ret = start; 1159 goto out; 1160 } 1161 } 1162 if (next) 1163 ret = find_next(arr, start, used); 1164 else 1165 ret = find_prev(arr, start, used); 1166 out: 1167 rte_rwlock_read_unlock(&arr->rwlock); 1168 return ret; 1169 } 1170 1171 int 1172 rte_fbarray_find_next_free(struct rte_fbarray *arr, unsigned int start) 1173 { 1174 return fbarray_find(arr, start, true, false); 1175 } 1176 1177 int 1178 rte_fbarray_find_next_used(struct rte_fbarray *arr, unsigned int start) 1179 { 1180 return fbarray_find(arr, start, true, true); 1181 } 1182 1183 int 1184 rte_fbarray_find_prev_free(struct rte_fbarray *arr, unsigned int start) 1185 { 1186 return fbarray_find(arr, start, false, false); 1187 } 1188 1189 int 1190 rte_fbarray_find_prev_used(struct rte_fbarray *arr, unsigned int start) 1191 { 1192 return fbarray_find(arr, start, false, true); 1193 } 1194 1195 static int 1196 fbarray_find_n(struct rte_fbarray *arr, unsigned int start, unsigned int n, 1197 bool next, bool used) 1198 { 1199 int ret = -1; 1200 1201 if (arr == NULL || start >= arr->len || n > arr->len || n == 0) { 1202 rte_errno = EINVAL; 1203 return -1; 1204 } 1205 if (next && (arr->len - start) < n) { 1206 rte_errno = used ? ENOENT : ENOSPC; 1207 return -1; 1208 } 1209 if (!next && start < (n - 1)) { 1210 rte_errno = used ? ENOENT : ENOSPC; 1211 return -1; 1212 } 1213 1214 /* prevent array from changing under us */ 1215 rte_rwlock_read_lock(&arr->rwlock); 1216 1217 /* cheap checks to prevent doing useless work */ 1218 if (!used) { 1219 if (arr->len == arr->count || arr->len - arr->count < n) { 1220 rte_errno = ENOSPC; 1221 goto out; 1222 } 1223 if (arr->count == 0) { 1224 ret = next ? start : start - n + 1; 1225 goto out; 1226 } 1227 } else { 1228 if (arr->count < n) { 1229 rte_errno = ENOENT; 1230 goto out; 1231 } 1232 if (arr->count == arr->len) { 1233 ret = next ? start : start - n + 1; 1234 goto out; 1235 } 1236 } 1237 1238 if (next) 1239 ret = find_next_n(arr, start, n, used); 1240 else 1241 ret = find_prev_n(arr, start, n, used); 1242 out: 1243 rte_rwlock_read_unlock(&arr->rwlock); 1244 return ret; 1245 } 1246 1247 int 1248 rte_fbarray_find_next_n_free(struct rte_fbarray *arr, unsigned int start, 1249 unsigned int n) 1250 { 1251 return fbarray_find_n(arr, start, n, true, false); 1252 } 1253 1254 int 1255 rte_fbarray_find_next_n_used(struct rte_fbarray *arr, unsigned int start, 1256 unsigned int n) 1257 { 1258 return fbarray_find_n(arr, start, n, true, true); 1259 } 1260 1261 int 1262 rte_fbarray_find_prev_n_free(struct rte_fbarray *arr, unsigned int start, 1263 unsigned int n) 1264 { 1265 return fbarray_find_n(arr, start, n, false, false); 1266 } 1267 1268 int 1269 rte_fbarray_find_prev_n_used(struct rte_fbarray *arr, unsigned int start, 1270 unsigned int n) 1271 { 1272 return fbarray_find_n(arr, start, n, false, true); 1273 } 1274 1275 static int 1276 fbarray_find_contig(struct rte_fbarray *arr, unsigned int start, bool next, 1277 bool used) 1278 { 1279 int ret = -1; 1280 1281 if (arr == NULL || start >= arr->len) { 1282 rte_errno = EINVAL; 1283 return -1; 1284 } 1285 1286 /* prevent array from changing under us */ 1287 rte_rwlock_read_lock(&arr->rwlock); 1288 1289 /* cheap checks to prevent doing useless work */ 1290 if (used) { 1291 if (arr->count == 0) { 1292 ret = 0; 1293 goto out; 1294 } 1295 if (next && arr->count == arr->len) { 1296 ret = arr->len - start; 1297 goto out; 1298 } 1299 if (!next && arr->count == arr->len) { 1300 ret = start + 1; 1301 goto out; 1302 } 1303 } else { 1304 if (arr->len == arr->count) { 1305 ret = 0; 1306 goto out; 1307 } 1308 if (next && arr->count == 0) { 1309 ret = arr->len - start; 1310 goto out; 1311 } 1312 if (!next && arr->count == 0) { 1313 ret = start + 1; 1314 goto out; 1315 } 1316 } 1317 1318 if (next) 1319 ret = find_contig(arr, start, used); 1320 else 1321 ret = find_rev_contig(arr, start, used); 1322 out: 1323 rte_rwlock_read_unlock(&arr->rwlock); 1324 return ret; 1325 } 1326 1327 static int 1328 fbarray_find_biggest(struct rte_fbarray *arr, unsigned int start, bool used, 1329 bool rev) 1330 { 1331 int cur_idx, next_idx, cur_len, biggest_idx, biggest_len; 1332 /* don't stack if conditions, use function pointers instead */ 1333 int (*find_func)(struct rte_fbarray *, unsigned int); 1334 int (*find_contig_func)(struct rte_fbarray *, unsigned int); 1335 1336 if (arr == NULL || start >= arr->len) { 1337 rte_errno = EINVAL; 1338 return -1; 1339 } 1340 /* the other API calls already do their fair share of cheap checks, so 1341 * no need to do them here. 1342 */ 1343 1344 /* the API's called are thread-safe, but something may still happen 1345 * between the API calls, so lock the fbarray. all other API's are 1346 * read-locking the fbarray, so read lock here is OK. 1347 */ 1348 rte_rwlock_read_lock(&arr->rwlock); 1349 1350 /* pick out appropriate functions */ 1351 if (used) { 1352 if (rev) { 1353 find_func = rte_fbarray_find_prev_used; 1354 find_contig_func = rte_fbarray_find_rev_contig_used; 1355 } else { 1356 find_func = rte_fbarray_find_next_used; 1357 find_contig_func = rte_fbarray_find_contig_used; 1358 } 1359 } else { 1360 if (rev) { 1361 find_func = rte_fbarray_find_prev_free; 1362 find_contig_func = rte_fbarray_find_rev_contig_free; 1363 } else { 1364 find_func = rte_fbarray_find_next_free; 1365 find_contig_func = rte_fbarray_find_contig_free; 1366 } 1367 } 1368 1369 cur_idx = start; 1370 biggest_idx = -1; /* default is error */ 1371 biggest_len = 0; 1372 for (;;) { 1373 cur_idx = find_func(arr, cur_idx); 1374 1375 /* block found, check its length */ 1376 if (cur_idx >= 0) { 1377 cur_len = find_contig_func(arr, cur_idx); 1378 /* decide where we go next */ 1379 next_idx = rev ? cur_idx - cur_len : cur_idx + cur_len; 1380 /* move current index to start of chunk */ 1381 cur_idx = rev ? next_idx + 1 : cur_idx; 1382 1383 if (cur_len > biggest_len) { 1384 biggest_idx = cur_idx; 1385 biggest_len = cur_len; 1386 } 1387 cur_idx = next_idx; 1388 /* in reverse mode, next_idx may be -1 if chunk started 1389 * at array beginning. this means there's no more work 1390 * to do. 1391 */ 1392 if (cur_idx < 0) 1393 break; 1394 } else { 1395 /* nothing more to find, stop. however, a failed API 1396 * call has set rte_errno, which we want to ignore, as 1397 * reaching the end of fbarray is not an error. 1398 */ 1399 rte_errno = 0; 1400 break; 1401 } 1402 } 1403 /* if we didn't find anything at all, set rte_errno */ 1404 if (biggest_idx < 0) 1405 rte_errno = used ? ENOENT : ENOSPC; 1406 1407 rte_rwlock_read_unlock(&arr->rwlock); 1408 return biggest_idx; 1409 } 1410 1411 int 1412 rte_fbarray_find_biggest_free(struct rte_fbarray *arr, unsigned int start) 1413 { 1414 return fbarray_find_biggest(arr, start, false, false); 1415 } 1416 1417 int 1418 rte_fbarray_find_biggest_used(struct rte_fbarray *arr, unsigned int start) 1419 { 1420 return fbarray_find_biggest(arr, start, true, false); 1421 } 1422 1423 int 1424 rte_fbarray_find_rev_biggest_free(struct rte_fbarray *arr, unsigned int start) 1425 { 1426 return fbarray_find_biggest(arr, start, false, true); 1427 } 1428 1429 int 1430 rte_fbarray_find_rev_biggest_used(struct rte_fbarray *arr, unsigned int start) 1431 { 1432 return fbarray_find_biggest(arr, start, true, true); 1433 } 1434 1435 1436 int 1437 rte_fbarray_find_contig_free(struct rte_fbarray *arr, unsigned int start) 1438 { 1439 return fbarray_find_contig(arr, start, true, false); 1440 } 1441 1442 int 1443 rte_fbarray_find_contig_used(struct rte_fbarray *arr, unsigned int start) 1444 { 1445 return fbarray_find_contig(arr, start, true, true); 1446 } 1447 1448 int 1449 rte_fbarray_find_rev_contig_free(struct rte_fbarray *arr, unsigned int start) 1450 { 1451 return fbarray_find_contig(arr, start, false, false); 1452 } 1453 1454 int 1455 rte_fbarray_find_rev_contig_used(struct rte_fbarray *arr, unsigned int start) 1456 { 1457 return fbarray_find_contig(arr, start, false, true); 1458 } 1459 1460 int 1461 rte_fbarray_find_idx(const struct rte_fbarray *arr, const void *elt) 1462 { 1463 void *end; 1464 int ret = -1; 1465 1466 /* 1467 * no need to synchronize as it doesn't matter if underlying data 1468 * changes - we're doing pointer arithmetic here. 1469 */ 1470 1471 if (arr == NULL || elt == NULL) { 1472 rte_errno = EINVAL; 1473 return -1; 1474 } 1475 end = RTE_PTR_ADD(arr->data, arr->elt_sz * arr->len); 1476 if (elt < arr->data || elt >= end) { 1477 rte_errno = EINVAL; 1478 return -1; 1479 } 1480 1481 ret = RTE_PTR_DIFF(elt, arr->data) / arr->elt_sz; 1482 1483 return ret; 1484 } 1485 1486 void 1487 rte_fbarray_dump_metadata(struct rte_fbarray *arr, FILE *f) 1488 { 1489 struct used_mask *msk; 1490 unsigned int i; 1491 1492 if (arr == NULL || f == NULL) { 1493 rte_errno = EINVAL; 1494 return; 1495 } 1496 1497 if (fully_validate(arr->name, arr->elt_sz, arr->len)) { 1498 fprintf(f, "Invalid file-backed array\n"); 1499 return; 1500 } 1501 1502 /* prevent array from changing under us */ 1503 rte_rwlock_read_lock(&arr->rwlock); 1504 1505 fprintf(f, "File-backed array: %s\n", arr->name); 1506 fprintf(f, "size: %i occupied: %i elt_sz: %i\n", 1507 arr->len, arr->count, arr->elt_sz); 1508 1509 msk = get_used_mask(arr->data, arr->elt_sz, arr->len); 1510 1511 for (i = 0; i < msk->n_masks; i++) 1512 fprintf(f, "msk idx %i: 0x%016" PRIx64 "\n", i, msk->data[i]); 1513 rte_rwlock_read_unlock(&arr->rwlock); 1514 } 1515