Spdk/lib/ftl/ftl_sb.c

299 lines
8.1 KiB
C
Raw Normal View History

/* 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;
}
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;
}
}
}