Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 | /* NVS: non volatile storage in flash * * Copyright (c) 2018 Laczen * * SPDX-License-Identifier: Apache-2.0 */ #include <drivers/flash.h> #include <string.h> #include <errno.h> #include <inttypes.h> #include <fs/nvs.h> #include <sys/crc.h> #include "nvs_priv.h" #include <logging/log.h> LOG_MODULE_REGISTER(fs_nvs, CONFIG_NVS_LOG_LEVEL); /* basic routines */ /* nvs_al_size returns size aligned to fs->write_block_size */ static inline size_t nvs_al_size(struct nvs_fs *fs, size_t len) { if (fs->write_block_size <= 1U) { return len; } return (len + (fs->write_block_size - 1U)) & ~(fs->write_block_size - 1U); } /* end basic routines */ /* flash routines */ /* basic aligned flash write to nvs address */ static int nvs_flash_al_wrt(struct nvs_fs *fs, u32_t addr, const void *data, size_t len) { const u8_t *data8 = (const u8_t *)data; int rc = 0; off_t offset; size_t blen; u8_t buf[NVS_BLOCK_SIZE]; if (!len) { /* Nothing to write, avoid changing the flash protection */ return 0; } offset = fs->offset; offset += fs->sector_size * (addr >> ADDR_SECT_SHIFT); offset += addr & ADDR_OFFS_MASK; rc = flash_write_protection_set(fs->flash_device, 0); if (rc) { /* flash protection set error */ return rc; } blen = len & ~(fs->write_block_size - 1U); if (blen > 0) { rc = flash_write(fs->flash_device, offset, data8, blen); if (rc) { /* flash write error */ goto end; } len -= blen; offset += blen; data8 += blen; } if (len) { memcpy(buf, data8, len); (void)memset(buf + len, 0xff, fs->write_block_size - len); rc = flash_write(fs->flash_device, offset, buf, fs->write_block_size); if (rc) { /* flash write error */ goto end; } } end: (void) flash_write_protection_set(fs->flash_device, 1); return rc; } /* basic flash read from nvs address */ static int nvs_flash_rd(struct nvs_fs *fs, u32_t addr, void *data, size_t len) { int rc; off_t offset; offset = fs->offset; offset += fs->sector_size * (addr >> ADDR_SECT_SHIFT); offset += addr & ADDR_OFFS_MASK; rc = flash_read(fs->flash_device, offset, data, len); return rc; } /* allocation entry write */ static int nvs_flash_ate_wrt(struct nvs_fs *fs, const struct nvs_ate *entry) { int rc; rc = nvs_flash_al_wrt(fs, fs->ate_wra, entry, sizeof(struct nvs_ate)); fs->ate_wra -= nvs_al_size(fs, sizeof(struct nvs_ate)); return rc; } /* data write */ static int nvs_flash_data_wrt(struct nvs_fs *fs, const void *data, size_t len) { int rc; rc = nvs_flash_al_wrt(fs, fs->data_wra, data, len); fs->data_wra += nvs_al_size(fs, len); return rc; } /* flash ate read */ static int nvs_flash_ate_rd(struct nvs_fs *fs, u32_t addr, struct nvs_ate *entry) { return nvs_flash_rd(fs, addr, entry, sizeof(struct nvs_ate)); } /* end of basic flash routines */ /* advanced flash routines */ /* nvs_flash_block_cmp compares the data in flash at addr to data * in blocks of size NVS_BLOCK_SIZE aligned to fs->write_block_size * returns 0 if equal, 1 if not equal, errcode if error */ static int nvs_flash_block_cmp(struct nvs_fs *fs, u32_t addr, const void *data, size_t len) { const u8_t *data8 = (const u8_t *)data; int rc; size_t bytes_to_cmp, block_size; u8_t buf[NVS_BLOCK_SIZE]; block_size = NVS_BLOCK_SIZE & ~(fs->write_block_size - 1U); while (len) { bytes_to_cmp = MIN(block_size, len); rc = nvs_flash_rd(fs, addr, buf, bytes_to_cmp); if (rc) { return rc; } rc = memcmp(data8, buf, bytes_to_cmp); if (rc) { return 1; } len -= bytes_to_cmp; addr += bytes_to_cmp; data8 += bytes_to_cmp; } return 0; } /* nvs_flash_cmp_const compares the data in flash at addr to a constant * value. returns 0 if all data in flash is equal to value, 1 if not equal, * errcode if error */ static int nvs_flash_cmp_const(struct nvs_fs *fs, u32_t addr, u8_t value, size_t len) { int rc; size_t bytes_to_cmp, block_size; u8_t cmp[NVS_BLOCK_SIZE]; block_size = NVS_BLOCK_SIZE & ~(fs->write_block_size - 1U); (void)memset(cmp, value, block_size); while (len) { bytes_to_cmp = MIN(block_size, len); rc = nvs_flash_block_cmp(fs, addr, cmp, bytes_to_cmp); if (rc) { return rc; } len -= bytes_to_cmp; addr += bytes_to_cmp; } return 0; } /* flash block move: move a block at addr to the current data write location * and updates the data write location. */ static int nvs_flash_block_move(struct nvs_fs *fs, u32_t addr, size_t len) { int rc; size_t bytes_to_copy, block_size; u8_t buf[NVS_BLOCK_SIZE]; block_size = NVS_BLOCK_SIZE & ~(fs->write_block_size - 1U); while (len) { bytes_to_copy = MIN(block_size, len); rc = nvs_flash_rd(fs, addr, buf, bytes_to_copy); if (rc) { return rc; } rc = nvs_flash_data_wrt(fs, buf, bytes_to_copy); if (rc) { return rc; } len -= bytes_to_copy; addr += bytes_to_copy; } return 0; } /* erase a sector by first checking it is used and then erasing if required * return 0 if OK, errorcode on error. */ static int nvs_flash_erase_sector(struct nvs_fs *fs, u32_t addr) { int rc; off_t offset; addr &= ADDR_SECT_MASK; rc = nvs_flash_cmp_const(fs, addr, 0xff, fs->sector_size); if (rc <= 0) { /* flash error or empty sector */ return rc; } offset = fs->offset; offset += fs->sector_size * (addr >> ADDR_SECT_SHIFT); rc = flash_write_protection_set(fs->flash_device, 0); if (rc) { /* flash protection set error */ return rc; } LOG_DBG("Erasing flash at %lx, len %d", (long int) offset, fs->sector_size); rc = flash_erase(fs->flash_device, offset, fs->sector_size); if (rc) { /* flash erase error */ return rc; } (void) flash_write_protection_set(fs->flash_device, 1); return 0; } /* crc update on allocation entry */ static void nvs_ate_crc8_update(struct nvs_ate *entry) { u8_t crc8; crc8 = crc8_ccitt(0xff, entry, offsetof(struct nvs_ate, crc8)); entry->crc8 = crc8; } /* crc check on allocation entry * returns 0 if OK, 1 on crc fail */ static int nvs_ate_crc8_check(const struct nvs_ate *entry) { u8_t crc8; crc8 = crc8_ccitt(0xff, entry, offsetof(struct nvs_ate, crc8)); if (crc8 == entry->crc8) { return 0; } return 1; } /* nvs_ate_cmp_const compares an ATE to a constant value. returns 0 if * the whole ATE is equal to value, 1 if not equal. */ static int nvs_ate_cmp_const(const struct nvs_ate *entry, u8_t value) { const u8_t *data8 = (const u8_t *)entry; int i; for (i = 0; i < sizeof(struct nvs_ate); i++) { if (data8[i] != value) { return 1; } } return 0; } /* store an entry in flash */ static int nvs_flash_wrt_entry(struct nvs_fs *fs, u16_t id, const void *data, size_t len) { int rc; struct nvs_ate entry; size_t ate_size; ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); entry.id = id; entry.offset = (u16_t)(fs->data_wra & ADDR_OFFS_MASK); entry.len = (u16_t)len; entry.part = 0xff; nvs_ate_crc8_update(&entry); rc = nvs_flash_data_wrt(fs, data, len); if (rc) { return rc; } rc = nvs_flash_ate_wrt(fs, &entry); if (rc) { return rc; } return 0; } /* end of flash routines */ /* walking through allocation entry list, from newest to oldest entries * read ate from addr, modify addr to the previous ate */ static int nvs_prev_ate(struct nvs_fs *fs, u32_t *addr, struct nvs_ate *ate) { int rc; struct nvs_ate close_ate, end_ate; u32_t data_end_addr, ate_end_addr; size_t ate_size; ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); rc = nvs_flash_ate_rd(fs, *addr, ate); if (rc) { return rc; } *addr += ate_size; if (((*addr) & ADDR_OFFS_MASK) != (fs->sector_size - ate_size)) { return 0; } /* last ate in sector, do jump to previous sector */ if (((*addr) >> ADDR_SECT_SHIFT) == 0U) { *addr += ((fs->sector_count - 1) << ADDR_SECT_SHIFT); } else { *addr -= (1 << ADDR_SECT_SHIFT); } rc = nvs_flash_ate_rd(fs, *addr, &close_ate); if (rc) { return rc; } rc = nvs_ate_cmp_const(&close_ate, 0xff); /* at the end of filesystem */ if (!rc) { *addr = fs->ate_wra; return 0; } if (!nvs_ate_crc8_check(&close_ate)) { /* update the address so it points to the last added ate. * do a check on close_ate.offset so that it does not point * outside a sector and is aligned to ate size. */ if (close_ate.offset < (fs->sector_size - ate_size) && !(close_ate.offset % ate_size)) { (*addr) &= ADDR_SECT_MASK; (*addr) += close_ate.offset; return 0; } } /* The close_ate had an invalid CRC8 or the last added ate offset was * recognized as incorrect, `lets find out the last valid ate * and point the address to this found ate. */ *addr -= ate_size; ate_end_addr = *addr; data_end_addr = *addr & ADDR_SECT_MASK; while (ate_end_addr > data_end_addr) { rc = nvs_flash_ate_rd(fs, ate_end_addr, &end_ate); if (rc) { return rc; } if (!nvs_ate_crc8_check(&end_ate)) { /* found a valid ate, update data_end_addr and *addr */ data_end_addr &= ADDR_SECT_MASK; data_end_addr += end_ate.offset + end_ate.len; *addr = ate_end_addr; } ate_end_addr -= ate_size; } /* remark: if there was absolutely no valid data in the sector *addr * is kept at sector_end - 2*ate_size, the next read will contain * invalid data and continue with a sector jump */ return 0; } static void nvs_sector_advance(struct nvs_fs *fs, u32_t *addr) { *addr += (1 << ADDR_SECT_SHIFT); if ((*addr >> ADDR_SECT_SHIFT) == fs->sector_count) { *addr -= (fs->sector_count << ADDR_SECT_SHIFT); } } /* allocation entry close (this closes the current sector) by writing offset * of last ate to the sector end. */ static int nvs_sector_close(struct nvs_fs *fs) { int rc; struct nvs_ate close_ate; size_t ate_size; ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); close_ate.id = 0xFFFF; close_ate.len = 0U; close_ate.offset = (u16_t)((fs->ate_wra + ate_size) & ADDR_OFFS_MASK); fs->ate_wra &= ADDR_SECT_MASK; fs->ate_wra += (fs->sector_size - ate_size); nvs_ate_crc8_update(&close_ate); rc = nvs_flash_ate_wrt(fs, &close_ate); nvs_sector_advance(fs, &fs->ate_wra); fs->data_wra = fs->ate_wra & ADDR_SECT_MASK; return 0; } /* garbage collection: the address ate_wra has been updated to the new sector * that has just been started. The data to gc is in the sector after this new * sector. */ static int nvs_gc(struct nvs_fs *fs) { int rc; struct nvs_ate close_ate, gc_ate, wlk_ate; u32_t sec_addr, gc_addr, gc_prev_addr, wlk_addr, wlk_prev_addr, data_addr, stop_addr; size_t ate_size; ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); sec_addr = (fs->ate_wra & ADDR_SECT_MASK); nvs_sector_advance(fs, &sec_addr); gc_addr = sec_addr + fs->sector_size - ate_size; /* if the sector is not closed don't do gc */ rc = nvs_flash_ate_rd(fs, gc_addr, &close_ate); if (rc < 0) { /* flash error */ return rc; } rc = nvs_ate_cmp_const(&close_ate, 0xff); if (!rc) { rc = nvs_flash_erase_sector(fs, sec_addr); if (rc) { return rc; } return 0; } stop_addr = gc_addr - ate_size; gc_addr &= ADDR_SECT_MASK; gc_addr += close_ate.offset; while (1) { gc_prev_addr = gc_addr; rc = nvs_prev_ate(fs, &gc_addr, &gc_ate); if (rc) { return rc; } wlk_addr = fs->ate_wra; while (1) { wlk_prev_addr = wlk_addr; rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate); if (rc) { return rc; } /* if ate with same id is reached we might need to copy. * only consider valid wlk_ate's. Something wrong might * have been written that has the same ate but is * invalid, don't consider these as a match. */ if ((wlk_ate.id == gc_ate.id) && (!nvs_ate_crc8_check(&wlk_ate))) { break; } } /* if walk has reached the same address as gc_addr copy is * needed unless it is a deleted item. */ if ((wlk_prev_addr == gc_prev_addr) && gc_ate.len) { /* copy needed */ LOG_DBG("Moving %d, len %d", gc_ate.id, gc_ate.len); data_addr = (gc_prev_addr & ADDR_SECT_MASK); data_addr += gc_ate.offset; gc_ate.offset = (u16_t)(fs->data_wra & ADDR_OFFS_MASK); nvs_ate_crc8_update(&gc_ate); rc = nvs_flash_block_move(fs, data_addr, gc_ate.len); if (rc) { return rc; } rc = nvs_flash_ate_wrt(fs, &gc_ate); if (rc) { return rc; } } /* stop gc at end of the sector */ if (gc_prev_addr == stop_addr) { break; } } rc = nvs_flash_erase_sector(fs, sec_addr); if (rc) { return rc; } return 0; } static int nvs_startup(struct nvs_fs *fs) { int rc; struct nvs_ate last_ate; size_t ate_size, empty_len; /* Initialize addr to 0 for the case fs->sector_count == 0. This * should never happen as this is verified in nvs_init() but both * Coverity and GCC believe the contrary. */ u32_t addr = 0U; u16_t i, closed_sectors = 0; k_mutex_lock(&fs->nvs_lock, K_FOREVER); ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); /* step through the sectors to find a open sector following * a closed sector, this is where NVS can to write. */ for (i = 0; i < fs->sector_count; i++) { addr = (i << ADDR_SECT_SHIFT) + fs->sector_size - ate_size; rc = nvs_flash_cmp_const(fs, addr, 0xff, sizeof(struct nvs_ate)); if (rc) { /* closed sector */ closed_sectors++; nvs_sector_advance(fs, &addr); rc = nvs_flash_cmp_const(fs, addr, 0xff, sizeof(struct nvs_ate)); if (!rc) { /* open sector */ break; } } } /* all sectors are closed, this is not a nvs fs */ if (closed_sectors == fs->sector_count) { return -EDEADLK; } if (i == fs->sector_count) { /* none of the sectors where closed, in most cases we can set * the address to the first sector, except when there are only * two sectors. Then we can only set it to the first sector if * the last sector contains no ate's. So we check this first */ rc = nvs_flash_cmp_const(fs, addr - ate_size, 0xff, sizeof(struct nvs_ate)); if (!rc) { /* empty ate */ nvs_sector_advance(fs, &addr); } } /* addr contains address of the last ate in the most recent sector * search for the first ate containing all 0xff */ fs->ate_wra = addr - ate_size; fs->data_wra = addr & ADDR_SECT_MASK; while (fs->ate_wra >= fs->data_wra) { rc = nvs_flash_ate_rd(fs, fs->ate_wra, &last_ate); if (rc) { goto end; } rc = nvs_ate_cmp_const(&last_ate, 0xff); if (!rc) { /* found ff empty location */ break; } if (!nvs_ate_crc8_check(&last_ate)) { /* crc8 is ok, complete write of ate was performed */ fs->data_wra = addr & ADDR_SECT_MASK; fs->data_wra += last_ate.offset; fs->data_wra += nvs_al_size(fs, last_ate.len); /* ate on the last possition within the sector is * reserved for deletion an entry */ if (fs->ate_wra == fs->data_wra && last_ate.len) { /* not a delete ate */ return -ESPIPE; } } fs->ate_wra -= ate_size; } /* possible data write after last ate write, update data_wra */ while (fs->ate_wra > fs->data_wra) { empty_len = fs->ate_wra - fs->data_wra; rc = nvs_flash_cmp_const(fs, fs->data_wra, 0xff, empty_len); if (rc < 0) { goto end; } if (!rc) { break; } fs->data_wra += fs->write_block_size; } /* if the sector after the write sector is not empty gc was interrupted * we need to restart gc, first erase the sector before restarting gc * otherwise the data may not fit into the sector. */ addr = fs->ate_wra & ADDR_SECT_MASK; nvs_sector_advance(fs, &addr); rc = nvs_flash_cmp_const(fs, addr, 0xff, fs->sector_size); if (rc < 0) { goto end; } if (rc) { /* the sector after fs->ate_wrt is not empty */ rc = nvs_flash_erase_sector(fs, fs->ate_wra); if (rc) { goto end; } fs->ate_wra &= ADDR_SECT_MASK; fs->ate_wra += (fs->sector_size - 2 * ate_size); fs->data_wra = (fs->ate_wra & ADDR_SECT_MASK); rc = nvs_gc(fs); if (rc) { goto end; } } end: k_mutex_unlock(&fs->nvs_lock); return rc; } int nvs_clear(struct nvs_fs *fs) { int rc; off_t addr; if (!fs->ready) { LOG_ERR("NVS not initialized"); return -EACCES; } for (u16_t i = 0; i < fs->sector_count; i++) { addr = i << ADDR_SECT_SHIFT; rc = nvs_flash_erase_sector(fs, addr); if (rc) { return rc; } } return 0; } int nvs_init(struct nvs_fs *fs, const char *dev_name) { int rc; struct flash_pages_info info; k_mutex_init(&fs->nvs_lock); fs->flash_device = device_get_binding(dev_name); if (!fs->flash_device) { LOG_ERR("No valid flash device found"); return -ENXIO; } fs->write_block_size = flash_get_write_block_size(fs->flash_device); /* check that the write block size is supported */ if (fs->write_block_size > NVS_BLOCK_SIZE) { LOG_ERR("Unsupported write block size"); return -EINVAL; } /* check that sector size is a multiple of pagesize */ rc = flash_get_page_info_by_offs(fs->flash_device, fs->offset, &info); if (rc) { LOG_ERR("Unable to get page info"); return -EINVAL; } if (!fs->sector_size || fs->sector_size % info.size) { LOG_ERR("Invalid sector size"); return -EINVAL; } /* check the number of sectors, it should be at least 2 */ if (fs->sector_count < 2) { LOG_ERR("Configuration error - sector count"); return -EINVAL; } rc = nvs_startup(fs); if (rc) { return rc; } /* nvs is ready for use */ fs->ready = true; LOG_INF("%d Sectors of %d bytes", fs->sector_count, fs->sector_size); LOG_INF("alloc wra: %d, %x", (fs->ate_wra >> ADDR_SECT_SHIFT), (fs->ate_wra & ADDR_OFFS_MASK)); LOG_INF("data wra: %d, %x", (fs->data_wra >> ADDR_SECT_SHIFT), (fs->data_wra & ADDR_OFFS_MASK)); return 0; } ssize_t nvs_write(struct nvs_fs *fs, u16_t id, const void *data, size_t len) { int rc, gc_count; size_t ate_size, data_size; struct nvs_ate wlk_ate; u32_t wlk_addr, rd_addr; u16_t required_space = 0U; /* no space, appropriate for delete ate */ if (!fs->ready) { LOG_ERR("NVS not initialized"); return -EACCES; } ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); data_size = nvs_al_size(fs, len); /* The maximum data size is sector size - 3 ate * where: 1 ate for data, 1 ate for sector close * and 1 ate to always allow a delete. */ if ((len > (fs->sector_size - 3 * ate_size)) || ((len > 0) && (data == NULL))) { return -EINVAL; } /* find latest entry with same id */ wlk_addr = fs->ate_wra; rd_addr = wlk_addr; while (1) { rd_addr = wlk_addr; rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate); if (rc) { return rc; } if ((wlk_ate.id == id) && (!nvs_ate_crc8_check(&wlk_ate))) { break; } if (wlk_addr == fs->ate_wra) { break; } } if (wlk_addr != fs->ate_wra) { /* previous entry found */ rd_addr &= ADDR_SECT_MASK; rd_addr += wlk_ate.offset; if (len == 0) { /* do not try to compare with empty data */ if (wlk_ate.len == 0U) { /* skip delete entry as it is already the * last one */ return 0; } } else { /* compare the data and if equal return 0 */ rc = nvs_flash_block_cmp(fs, rd_addr, data, len); if (rc <= 0) { return rc; } } } else { /* skip delete entry for non-existing entry */ if (len == 0) { return 0; } } /* calculate required space if the entry contains data */ if (data_size) { /* Leave space for delete ate */ required_space = data_size + ate_size; } k_mutex_lock(&fs->nvs_lock, K_FOREVER); gc_count = 0; while (1) { if (gc_count == fs->sector_count) { /* gc'ed all sectors, no extra space will be created * by extra gc. */ rc = -ENOSPC; goto end; } if (fs->ate_wra >= fs->data_wra + required_space) { rc = nvs_flash_wrt_entry(fs, id, data, len); if (rc) { goto end; } break; } rc = nvs_sector_close(fs); if (rc) { goto end; } rc = nvs_gc(fs); if (rc) { goto end; } gc_count++; } rc = len; end: k_mutex_unlock(&fs->nvs_lock); return rc; } int nvs_delete(struct nvs_fs *fs, u16_t id) { return nvs_write(fs, id, NULL, 0); } ssize_t nvs_read_hist(struct nvs_fs *fs, u16_t id, void *data, size_t len, u16_t cnt) { int rc; u32_t wlk_addr, rd_addr; u16_t cnt_his; struct nvs_ate wlk_ate; size_t ate_size; if (!fs->ready) { LOG_ERR("NVS not initialized"); return -EACCES; } ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); if (len > (fs->sector_size - 2 * ate_size)) { return -EINVAL; } cnt_his = 0U; wlk_addr = fs->ate_wra; rd_addr = wlk_addr; while (cnt_his <= cnt) { rd_addr = wlk_addr; rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate); if (rc) { goto err; } if ((wlk_ate.id == id) && (!nvs_ate_crc8_check(&wlk_ate))) { cnt_his++; } if (wlk_addr == fs->ate_wra) { break; } } if (((wlk_addr == fs->ate_wra) && (wlk_ate.id != id)) || (wlk_ate.len == 0U) || (cnt_his < cnt)) { return -ENOENT; } rd_addr &= ADDR_SECT_MASK; rd_addr += wlk_ate.offset; rc = nvs_flash_rd(fs, rd_addr, data, MIN(len, wlk_ate.len)); if (rc) { goto err; } return wlk_ate.len; err: return rc; } ssize_t nvs_read(struct nvs_fs *fs, u16_t id, void *data, size_t len) { int rc; rc = nvs_read_hist(fs, id, data, len, 0); return rc; } ssize_t nvs_calc_free_space(struct nvs_fs *fs) { int rc; struct nvs_ate step_ate, wlk_ate; u32_t step_addr, wlk_addr; size_t ate_size, free_space; if (!fs->ready) { LOG_ERR("NVS not initialized"); return -EACCES; } ate_size = nvs_al_size(fs, sizeof(struct nvs_ate)); free_space = 0; for (u16_t i = 1; i < fs->sector_count; i++) { free_space += (fs->sector_size - ate_size); } step_addr = fs->ate_wra; while (1) { rc = nvs_prev_ate(fs, &step_addr, &step_ate); if (rc) { return rc; } wlk_addr = fs->ate_wra; while (1) { rc = nvs_prev_ate(fs, &wlk_addr, &wlk_ate); if (rc) { return rc; } if ((wlk_ate.id == step_ate.id) || (wlk_addr == fs->ate_wra)) { break; } } if ((wlk_addr == step_addr) && step_ate.len && (!nvs_ate_crc8_check(&step_ate))) { /* count needed */ free_space -= nvs_al_size(fs, step_ate.len); free_space -= ate_size; } if (step_addr == fs->ate_wra) { break; } } return free_space; } |