diff --git a/lib/ftl/ftl_layout.h b/lib/ftl/ftl_layout.h index f5904abcd..73614b1e1 100644 --- a/lib/ftl/ftl_layout.h +++ b/lib/ftl/ftl_layout.h @@ -81,6 +81,8 @@ struct ftl_layout_region_descriptor { /* Number of blocks in FTL_BLOCK_SIZE unit */ uint64_t blocks; + + struct ftl_superblock_md_region *sb_md_reg; }; /* Data or metadata region on devices */ diff --git a/lib/ftl/ftl_sb.c b/lib/ftl/ftl_sb.c index 568273bd0..d60405c3a 100644 --- a/lib/ftl/ftl_sb.c +++ b/lib/ftl/ftl_sb.c @@ -12,3 +12,287 @@ ftl_superblock_check_magic(struct ftl_superblock *sb) { return sb->header.magic == FTL_SUPERBLOCK_MAGIC; } +bool +ftl_superblock_md_layout_is_empty(struct ftl_superblock *sb) +{ + return sb->md_layout_head.type == FTL_LAYOUT_REGION_TYPE_INVALID; +} + +static bool +md_region_is_fixed(int reg_type) +{ + switch (reg_type) { +#ifdef SPDK_FTL_VSS_EMU + case FTL_LAYOUT_REGION_TYPE_VSS: +#endif + case FTL_LAYOUT_REGION_TYPE_SB: + case FTL_LAYOUT_REGION_TYPE_SB_BASE: + case FTL_LAYOUT_REGION_TYPE_DATA_BASE: + return true; + + default: + return false; + } +} + +static uint32_t +md_regions_count(void) +{ + uint32_t reg_cnt = 0, reg_type; + + for (reg_type = 0; reg_type < FTL_LAYOUT_REGION_TYPE_MAX; reg_type++) { + if (md_region_is_fixed(reg_type)) { + continue; + } + + reg_cnt++; + } + return reg_cnt; +} + +static bool +superblock_md_region_overflow(struct spdk_ftl_dev *dev, struct ftl_superblock_md_region *sb_reg) +{ + /* sb_reg is part of the sb structure - the pointer should be at a positive offset */ + if ((uintptr_t)sb_reg < (uintptr_t)dev->sb) { + return true; + } + + /* There's only a finite (FTL_SUPERBLOCK_SIZE) amount of space in the superblock. Make sure the region wholly fits in that space. */ + if ((uintptr_t)(sb_reg + 1) > ((uintptr_t)(dev->sb) + FTL_SUPERBLOCK_SIZE)) { + return true; + } + + /* Make sure the entry doesn't overflow the pointer value (probably overkill to check) */ + if ((uintptr_t)(sb_reg + 1) < (uintptr_t)sb_reg) { + return true; + } + + return false; +} + +static int +superblock_md_layout_add(struct spdk_ftl_dev *dev, struct ftl_superblock_md_region *sb_reg, + uint32_t reg_type, uint32_t reg_version, uint64_t blk_offs, uint64_t blk_sz) +{ + if (superblock_md_region_overflow(dev, sb_reg)) { + FTL_ERRLOG(dev, "Buffer overflow\n"); + return -EOVERFLOW; + } + + sb_reg->type = reg_type; + sb_reg->version = reg_version; + sb_reg->blk_offs = blk_offs; + sb_reg->blk_sz = blk_sz; + return 0; +} + +static int +superblock_md_layout_add_free(struct spdk_ftl_dev *dev, struct ftl_superblock_md_region **sb_reg, + uint32_t reg_type, uint32_t free_type, uint64_t total_blocks) +{ + struct ftl_layout *layout = &dev->layout; + struct ftl_layout_region *reg = &layout->region[reg_type]; + uint64_t blks_left = total_blocks - reg->current.offset - reg->current.blocks; + + if (blks_left == 0) { + return 0; + } + + (*sb_reg)->df_next = ftl_df_get_obj_id(dev->sb, (*sb_reg) + 1); + (*sb_reg) = (*sb_reg) + 1; + + if (superblock_md_layout_add(dev, *sb_reg, free_type, 0, + reg->current.offset + reg->current.blocks, blks_left)) { + return -1; + } + + (*sb_reg)->df_next = FTL_DF_OBJ_ID_INVALID; + + return 0; +} + +int +ftl_superblock_md_layout_build(struct spdk_ftl_dev *dev) +{ + struct ftl_superblock *sb = dev->sb; + struct ftl_layout *layout = &dev->layout; + struct ftl_layout_region *reg; + int n = 0; + bool is_empty = ftl_superblock_md_layout_is_empty(dev->sb); + struct ftl_superblock_md_region *sb_reg = &sb->md_layout_head; + + /* TODO: major upgrades: add all free regions being tracked + * For now SB MD layout must be empty - otherwise md free regions may be lost */ + assert(is_empty); + + for (; n < FTL_LAYOUT_REGION_TYPE_MAX;) { + reg = &layout->region[n]; + if (md_region_is_fixed(reg->type)) { + reg->current.sb_md_reg = NULL; + n++; + + if (n >= FTL_LAYOUT_REGION_TYPE_MAX) { + /* For VSS emulation the last layout type is a fixed region, we need to move back the list and end the list on previous entry */ + sb_reg--; + break; + } + continue; + } + + if (superblock_md_layout_add(dev, sb_reg, reg->type, reg->current.version, + reg->current.offset, reg->current.blocks)) { + return -1; + } + reg->current.sb_md_reg = sb_reg; + + n++; + if (n < FTL_LAYOUT_REGION_TYPE_MAX) { + /* next region */ + sb_reg->df_next = ftl_df_get_obj_id(sb, sb_reg + 1); + sb_reg++; + } + } + + /* terminate the list */ + sb_reg->df_next = FTL_DF_OBJ_ID_INVALID; + + /* create free_nvc/free_base regions on the first run */ + if (is_empty) { + superblock_md_layout_add_free(dev, &sb_reg, FTL_LAYOUT_REGION_LAST_NVC, + FTL_LAYOUT_REGION_TYPE_FREE_NVC, layout->nvc.total_blocks); + + superblock_md_layout_add_free(dev, &sb_reg, FTL_LAYOUT_REGION_LAST_BASE, + FTL_LAYOUT_REGION_TYPE_FREE_BASE, layout->base.total_blocks); + } + + return 0; +} + +int +ftl_superblock_md_layout_load_all(struct spdk_ftl_dev *dev) +{ + struct ftl_superblock *sb = dev->sb; + struct ftl_superblock_md_region *sb_reg = &sb->md_layout_head; + struct ftl_layout *layout = &dev->layout; + uint32_t regs_found = 0; + + for (int n = 0; n < FTL_LAYOUT_REGION_TYPE_MAX; n++) { + if (md_region_is_fixed(sb_reg->type)) { + continue; + } + + layout->region[n].current.sb_md_reg = NULL; + layout->region[n].prev = layout->region[n].current; + } + + while (sb_reg->type != FTL_LAYOUT_REGION_TYPE_INVALID) { + struct ftl_layout_region *reg; + + /* TODO: major upgrades: add free regions tracking */ + if (sb_reg->type == FTL_LAYOUT_REGION_TYPE_FREE_NVC || + sb_reg->type == FTL_LAYOUT_REGION_TYPE_FREE_BASE) { + goto next_sb_reg; + } + + if (sb_reg->type >= FTL_LAYOUT_REGION_TYPE_MAX) { + FTL_ERRLOG(dev, "Invalid MD region type found\n"); + return -1; + } + + if (md_region_is_fixed(sb_reg->type)) { + FTL_ERRLOG(dev, "Unsupported MD region type found\n"); + return -1; + } + + reg = &layout->region[sb_reg->type]; + if (sb_reg->version == reg->current.version) { + if (reg->current.sb_md_reg) { + FTL_ERRLOG(dev, "Multiple/looping current regions found\n"); + return -1; + } + + reg->current.offset = sb_reg->blk_offs; + reg->current.blocks = sb_reg->blk_sz; + reg->current.sb_md_reg = sb_reg; + + if (!reg->prev.sb_md_reg) { + regs_found++; + } + } else { + if (sb_reg->version > reg->current.version) { + FTL_ERRLOG(dev, "Unknown region version found\n"); + return -1; + } + /* + * The metadata regions are kept as a linked list, it's therefore possible to have it corrupted and contain loops. + * If the prev region has already been updated to the oldest found version (see following comment/block) and we see + * the same version again, it's a loop and error. + */ + if (sb_reg->version == reg->prev.version) { + FTL_ERRLOG(dev, "Multiple/looping prev regions found\n"); + return -1; + } + + /* + * The following check is in preparation for major metadata upgrade. + * It's possible that a region will need to be increased in size. It will allocate an additional region in this case + * and upgrade each entries one by one. If in the middle of this upgrade process a dirty shutdown occurs, there will be + * multiple entries of the same metadata region in the superblock (old original and partially updated one). This will then + * effectively rollback the upgrade to the oldest found metadata section and start from the beginning. + */ + if (sb_reg->version < reg->prev.version) { + if ((!reg->current.sb_md_reg) && (!reg->prev.sb_md_reg)) { + regs_found++; + } + + reg->prev.offset = sb_reg->blk_offs; + reg->prev.blocks = sb_reg->blk_sz; + reg->prev.version = sb_reg->version; + reg->prev.sb_md_reg = sb_reg; + } + } + +next_sb_reg: + if (sb_reg->df_next == FTL_DF_OBJ_ID_INVALID) { + break; + } + + sb_reg = ftl_df_get_obj_ptr(sb, sb_reg->df_next); + if (superblock_md_region_overflow(dev, sb_reg)) { + FTL_ERRLOG(dev, "Buffer overflow\n"); + return -EOVERFLOW; + } + } + + if (regs_found != md_regions_count()) { + FTL_ERRLOG(dev, "Missing regions\n"); + return -1; + } + + return 0; +} + +void +ftl_superblock_md_layout_dump(struct spdk_ftl_dev *dev) +{ + struct ftl_superblock *sb = dev->sb; + struct ftl_superblock_md_region *sb_reg = &sb->md_layout_head; + + FTL_NOTICELOG(dev, "SB metadata layout:\n"); + while (sb_reg->type != FTL_LAYOUT_REGION_TYPE_INVALID) { + FTL_NOTICELOG(dev, + "Region df:0x%"PRIx64" type:0x%"PRIx32" ver:%"PRIu32" blk_offs:0x%"PRIx64" blk_sz:0x%"PRIx64"\n", + ftl_df_get_obj_id(sb, sb_reg), sb_reg->type, sb_reg->version, sb_reg->blk_offs, sb_reg->blk_sz); + + if (sb_reg->df_next == FTL_DF_OBJ_ID_INVALID) { + break; + } + + sb_reg = ftl_df_get_obj_ptr(sb, sb_reg->df_next); + if (superblock_md_region_overflow(dev, sb_reg)) { + FTL_ERRLOG(dev, "Buffer overflow\n"); + return; + } + } +} diff --git a/lib/ftl/ftl_sb.h b/lib/ftl/ftl_sb.h index 68a1fe15c..1615b0b75 100644 --- a/lib/ftl/ftl_sb.h +++ b/lib/ftl/ftl_sb.h @@ -14,4 +14,12 @@ struct spdk_ftl_dev; bool ftl_superblock_check_magic(struct ftl_superblock *sb); +bool ftl_superblock_md_layout_is_empty(struct ftl_superblock *sb); + +int ftl_superblock_md_layout_build(struct spdk_ftl_dev *dev); + +int ftl_superblock_md_layout_load_all(struct spdk_ftl_dev *dev); + +void ftl_superblock_md_layout_dump(struct spdk_ftl_dev *dev); + #endif /* FTL_SB_H */ diff --git a/lib/ftl/ftl_sb_common.h b/lib/ftl/ftl_sb_common.h index 41e5fa624..c667e288b 100644 --- a/lib/ftl/ftl_sb_common.h +++ b/lib/ftl/ftl_sb_common.h @@ -8,6 +8,7 @@ #include "spdk/stdinc.h" #include "utils/ftl_defs.h" +#include "utils/ftl_df.h" /* Size of superblock on NV cache, make it bigger for future fields */ #define FTL_SUPERBLOCK_SIZE (128ULL * KiB) @@ -45,6 +46,7 @@ struct ftl_superblock_md_region { uint32_t version; uint64_t blk_offs; uint64_t blk_sz; + ftl_df_obj_id df_next; }; struct ftl_superblock_shm { diff --git a/lib/ftl/mngt/ftl_mngt_md.c b/lib/ftl/mngt/ftl_mngt_md.c index 8a7ef805b..14bd5a127 100644 --- a/lib/ftl/mngt/ftl_mngt_md.c +++ b/lib/ftl/mngt/ftl_mngt_md.c @@ -373,6 +373,7 @@ ftl_mngt_init_default_sb(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt /* md layout isn't initialized yet. * empty region list => all regions in the default location */ sb->md_layout_head.type = FTL_LAYOUT_REGION_TYPE_INVALID; + sb->md_layout_head.df_next = FTL_DF_OBJ_ID_INVALID; sb->header.crc = get_sb_crc(sb);