2022-06-21 13:05:02 +00:00
|
|
|
/* SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
* Copyright (c) Intel Corporation.
|
|
|
|
* All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "ftl_sb.h"
|
|
|
|
#include "ftl_core.h"
|
|
|
|
#include "ftl_layout.h"
|
|
|
|
|
|
|
|
bool
|
|
|
|
ftl_superblock_check_magic(struct ftl_superblock *sb)
|
|
|
|
{
|
|
|
|
return sb->header.magic == FTL_SUPERBLOCK_MAGIC;
|
|
|
|
}
|
2022-06-28 14:26:04 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|