Spdk/module/bdev/ftl/bdev_ftl.c
Kozlowski Mateusz 0e33da4974 ftl: fast shutdown
Adds API for fast shutdown - the ability for FTL to skip most
of the metadata persists made during clean shutdown, and relying
on their representation in shared memory instead. This allows for
faster update of SPDK (or just FTL, assuming no metadata changes),
with downtime reduction from 2-5 seconds to 500-1000 ms (for
14TiB+800GiB base and cache drives).

Signed-off-by: Artur Paszkiewicz <artur.paszkiewicz@intel.com>
Signed-off-by: Kozlowski Mateusz <mateusz.kozlowski@intel.com>
Change-Id: I5999d31698a81512db8d5893eabee7b505c80d06
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/13348
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
2022-08-30 14:48:50 +00:00

526 lines
12 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (c) Intel Corporation.
* All rights reserved.
*/
#include "spdk/stdinc.h"
#include "spdk/bdev.h"
#include "spdk/env.h"
#include "spdk/thread.h"
#include "spdk/json.h"
#include "spdk/string.h"
#include "spdk/likely.h"
#include "spdk/util.h"
#include "spdk/string.h"
#include "spdk/ftl.h"
#include "spdk/log.h"
#include "bdev_ftl.h"
struct ftl_bdev {
struct spdk_bdev bdev;
struct spdk_ftl_dev *dev;
ftl_bdev_init_fn init_cb;
void *init_arg;
int rc;
struct spdk_bdev_desc *base_bdev_desc;
struct spdk_bdev_desc *cache_bdev_desc;
};
struct ftl_deferred_init {
struct spdk_ftl_conf conf;
LIST_ENTRY(ftl_deferred_init) entry;
};
static LIST_HEAD(, ftl_deferred_init) g_deferred_init = LIST_HEAD_INITIALIZER(g_deferred_init);
static int bdev_ftl_initialize(void);
static void bdev_ftl_finish(void);
static void bdev_ftl_examine(struct spdk_bdev *bdev);
static int
bdev_ftl_get_ctx_size(void)
{
return spdk_ftl_io_size();
}
static struct spdk_bdev_module g_ftl_if = {
.name = "ftl",
.module_init = bdev_ftl_initialize,
.module_fini = bdev_ftl_finish,
.examine_disk = bdev_ftl_examine,
.get_ctx_size = bdev_ftl_get_ctx_size,
};
SPDK_BDEV_MODULE_REGISTER(ftl, &g_ftl_if)
static void
bdev_ftl_free(struct ftl_bdev *ftl_bdev)
{
spdk_bdev_close(ftl_bdev->base_bdev_desc);
spdk_bdev_close(ftl_bdev->cache_bdev_desc);
free(ftl_bdev->bdev.name);
free(ftl_bdev);
}
static void
bdev_ftl_dev_free_cb(void *ctx, int status)
{
struct ftl_bdev *ftl_bdev = ctx;
spdk_bdev_destruct_done(&ftl_bdev->bdev, status);
bdev_ftl_free(ftl_bdev);
}
static int
bdev_ftl_destruct(void *ctx)
{
struct ftl_bdev *ftl_bdev = ctx;
spdk_ftl_dev_free(ftl_bdev->dev, bdev_ftl_dev_free_cb, ftl_bdev);
/* return 1 to indicate that the destruction is asynchronous */
return 1;
}
static void
bdev_ftl_cb(void *arg, int rc)
{
struct spdk_bdev_io *bdev_io = arg;
enum spdk_bdev_io_status status;
switch (rc) {
case 0:
status = SPDK_BDEV_IO_STATUS_SUCCESS;
break;
case -ENOMEM:
status = SPDK_BDEV_IO_STATUS_NOMEM;
break;
default:
status = SPDK_BDEV_IO_STATUS_FAILED;
break;
}
spdk_bdev_io_complete(bdev_io, status);
}
static void
bdev_ftl_get_buf_cb(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io,
bool success)
{
struct ftl_bdev *ftl_bdev;
int rc;
ftl_bdev = bdev_io->bdev->ctxt;
if (!success) {
spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
return;
}
rc = spdk_ftl_readv(ftl_bdev->dev, (struct ftl_io *)bdev_io->driver_ctx,
ch,
bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks,
bdev_io->u.bdev.iovs, bdev_io->u.bdev.iovcnt, bdev_ftl_cb, bdev_io);
if (spdk_unlikely(rc != 0)) {
spdk_bdev_io_complete(bdev_io, rc);
}
}
static int
_bdev_ftl_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io)
{
struct ftl_bdev *ftl_bdev = (struct ftl_bdev *)bdev_io->bdev->ctxt;
switch (bdev_io->type) {
case SPDK_BDEV_IO_TYPE_READ:
spdk_bdev_io_get_buf(bdev_io, bdev_ftl_get_buf_cb,
bdev_io->u.bdev.num_blocks * bdev_io->bdev->blocklen);
return 0;
case SPDK_BDEV_IO_TYPE_WRITE:
return spdk_ftl_writev(ftl_bdev->dev, (struct ftl_io *)bdev_io->driver_ctx,
ch, bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks, bdev_io->u.bdev.iovs,
bdev_io->u.bdev.iovcnt, bdev_ftl_cb, bdev_io);
case SPDK_BDEV_IO_TYPE_UNMAP:
case SPDK_BDEV_IO_TYPE_FLUSH:
spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS);
return 0;
default:
return -ENOTSUP;
}
}
static void
bdev_ftl_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io)
{
int rc = _bdev_ftl_submit_request(ch, bdev_io);
if (spdk_unlikely(rc != 0)) {
spdk_bdev_io_complete(bdev_io, rc);
}
}
static bool
bdev_ftl_io_type_supported(void *ctx, enum spdk_bdev_io_type io_type)
{
switch (io_type) {
case SPDK_BDEV_IO_TYPE_READ:
case SPDK_BDEV_IO_TYPE_WRITE:
case SPDK_BDEV_IO_TYPE_FLUSH:
case SPDK_BDEV_IO_TYPE_UNMAP:
return true;
default:
return false;
}
}
static struct spdk_io_channel *
bdev_ftl_get_io_channel(void *ctx)
{
struct ftl_bdev *ftl_bdev = ctx;
return spdk_ftl_get_io_channel(ftl_bdev->dev);
}
static void
bdev_ftl_write_config_json(struct spdk_bdev *bdev, struct spdk_json_write_ctx *w)
{
struct ftl_bdev *ftl_bdev = bdev->ctxt;
struct spdk_ftl_conf conf;
char uuid[SPDK_UUID_STRING_LEN];
spdk_ftl_dev_get_conf(ftl_bdev->dev, &conf);
spdk_json_write_object_begin(w);
spdk_json_write_named_string(w, "method", "bdev_ftl_create");
spdk_json_write_named_object_begin(w, "params");
spdk_json_write_named_string(w, "name", ftl_bdev->bdev.name);
spdk_json_write_named_uint64(w, "overprovisioning", conf.overprovisioning);
if (conf.core_mask) {
spdk_json_write_named_string(w, "core_mask", conf.core_mask);
}
spdk_uuid_fmt_lower(uuid, sizeof(uuid), &conf.uuid);
spdk_json_write_named_string(w, "uuid", uuid);
spdk_json_write_named_bool(w, "fast_shutdown", conf.fast_shutdown);
spdk_json_write_named_string(w, "base_bdev", conf.base_bdev);
if (conf.cache_bdev) {
spdk_json_write_named_string(w, "cache", conf.cache_bdev);
}
spdk_json_write_object_end(w);
spdk_json_write_object_end(w);
}
static int
bdev_ftl_dump_info_json(void *ctx, struct spdk_json_write_ctx *w)
{
struct ftl_bdev *ftl_bdev = ctx;
struct spdk_ftl_attrs attrs;
struct spdk_ftl_conf conf;
spdk_ftl_dev_get_attrs(ftl_bdev->dev, &attrs);
spdk_ftl_dev_get_conf(ftl_bdev->dev, &conf);
spdk_json_write_named_object_begin(w, "ftl");
spdk_json_write_named_string(w, "base_bdev", conf.base_bdev);
if (conf.cache_bdev) {
spdk_json_write_named_string(w, "cache", conf.cache_bdev);
}
/* ftl */
spdk_json_write_object_end(w);
return 0;
}
static const struct spdk_bdev_fn_table ftl_fn_table = {
.destruct = bdev_ftl_destruct,
.submit_request = bdev_ftl_submit_request,
.io_type_supported = bdev_ftl_io_type_supported,
.get_io_channel = bdev_ftl_get_io_channel,
.write_config_json = bdev_ftl_write_config_json,
.dump_info_json = bdev_ftl_dump_info_json,
};
static void
bdev_ftl_create_err_complete(struct ftl_bdev *ftl_bdev)
{
ftl_bdev_init_fn init_cb = ftl_bdev->init_cb;
void *init_arg = ftl_bdev->init_arg;
int rc = ftl_bdev->rc;
bdev_ftl_free(ftl_bdev);
assert(rc);
init_cb(NULL, init_arg, rc);
}
static void
bdev_ftl_create_err_cleanup_cb(void *ctx, int status)
{
struct ftl_bdev *ftl_bdev = ctx;
if (status) {
SPDK_ERRLOG("Fatal ERROR of FTL cleanup, name %s\n", ftl_bdev->bdev.name);
}
bdev_ftl_create_err_complete(ftl_bdev);
}
static void
bdev_ftl_create_cb(struct spdk_ftl_dev *dev, void *ctx, int status)
{
struct ftl_bdev *ftl_bdev = ctx;
struct ftl_bdev_info info = {};
struct spdk_ftl_attrs attrs;
struct spdk_ftl_conf conf;
ftl_bdev_init_fn init_cb = ftl_bdev->init_cb;
void *init_arg = ftl_bdev->init_arg;
if (status) {
SPDK_ERRLOG("Failed to create FTL device (%d)\n", status);
ftl_bdev->rc = status;
goto error;
}
spdk_ftl_dev_get_attrs(dev, &attrs);
spdk_ftl_dev_get_conf(dev, &conf);
ftl_bdev->dev = dev;
ftl_bdev->bdev.product_name = "FTL disk";
ftl_bdev->bdev.write_cache = 0;
ftl_bdev->bdev.blocklen = attrs.block_size;
ftl_bdev->bdev.blockcnt = attrs.num_blocks;
ftl_bdev->bdev.uuid = conf.uuid;
ftl_bdev->bdev.optimal_io_boundary = attrs.optimum_io_size;
ftl_bdev->bdev.split_on_optimal_io_boundary = true;
SPDK_DEBUGLOG(bdev_ftl, "Creating bdev %s:\n", ftl_bdev->bdev.name);
SPDK_DEBUGLOG(bdev_ftl, "\tblock_len:\t%zu\n", attrs.block_size);
SPDK_DEBUGLOG(bdev_ftl, "\tnum_blocks:\t%"PRIu64"\n", attrs.num_blocks);
ftl_bdev->bdev.ctxt = ftl_bdev;
ftl_bdev->bdev.fn_table = &ftl_fn_table;
ftl_bdev->bdev.module = &g_ftl_if;
status = spdk_bdev_register(&ftl_bdev->bdev);
if (status) {
ftl_bdev->rc = status;
goto error;
}
info.name = ftl_bdev->bdev.name;
info.uuid = ftl_bdev->bdev.uuid;
init_cb(&info, init_arg, 0);
return;
error:
if (ftl_bdev->dev) {
/* Cleanup all FTL */
spdk_ftl_dev_set_fast_shutdown(ftl_bdev->dev, false);
/* FTL was created, but we have got an error, so we need to delete it */
spdk_ftl_dev_free(dev, bdev_ftl_create_err_cleanup_cb, ftl_bdev);
} else {
bdev_ftl_create_err_complete(ftl_bdev);
}
}
static void
bdev_ftl_defer_free(struct ftl_deferred_init *init)
{
spdk_ftl_conf_deinit(&init->conf);
free(init);
}
int
bdev_ftl_defer_init(const struct spdk_ftl_conf *conf)
{
struct ftl_deferred_init *init;
int rc;
init = calloc(1, sizeof(*init));
if (!init) {
return -ENOMEM;
}
rc = spdk_ftl_conf_copy(&init->conf, conf);
if (rc) {
free(init);
return -ENOMEM;
}
LIST_INSERT_HEAD(&g_deferred_init, init, entry);
return 0;
}
static void
bdev_ftl_create_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *ctx)
{
}
int
bdev_ftl_create_bdev(const struct spdk_ftl_conf *conf, ftl_bdev_init_fn cb, void *cb_arg)
{
struct ftl_bdev *ftl_bdev;
struct spdk_bdev_desc *base_bdev_desc, *cache_bdev_desc;
int rc;
rc = spdk_bdev_open_ext(conf->base_bdev, false, bdev_ftl_create_bdev_event_cb, NULL,
&base_bdev_desc);
if (rc) {
return rc;
}
rc = spdk_bdev_open_ext(conf->cache_bdev, false, bdev_ftl_create_bdev_event_cb, NULL,
&cache_bdev_desc);
if (rc) {
spdk_bdev_close(base_bdev_desc);
return rc;
}
ftl_bdev = calloc(1, sizeof(*ftl_bdev));
if (!ftl_bdev) {
SPDK_ERRLOG("Could not allocate ftl_bdev\n");
spdk_bdev_close(base_bdev_desc);
spdk_bdev_close(cache_bdev_desc);
return -ENOMEM;
}
ftl_bdev->base_bdev_desc = base_bdev_desc;
ftl_bdev->cache_bdev_desc = cache_bdev_desc;
ftl_bdev->bdev.name = strdup(conf->name);
if (!ftl_bdev->bdev.name) {
rc = -ENOMEM;
goto error;
}
ftl_bdev->init_cb = cb;
ftl_bdev->init_arg = cb_arg;
rc = spdk_ftl_dev_init(conf, bdev_ftl_create_cb, ftl_bdev);
if (rc) {
SPDK_ERRLOG("Could not create FTL device\n");
goto error;
}
return 0;
error:
bdev_ftl_free(ftl_bdev);
return rc;
}
static int
bdev_ftl_initialize(void)
{
return spdk_ftl_init();
}
static void
bdev_ftl_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *ctx)
{
}
void
bdev_ftl_delete_bdev(const char *name, bool fast_shutdown, spdk_bdev_unregister_cb cb_fn,
void *cb_arg)
{
struct spdk_bdev_desc *ftl_bdev_desc;
struct spdk_bdev *bdev;
struct ftl_bdev *ftl;
int rc;
rc = spdk_bdev_open_ext(name, false, bdev_ftl_event_cb, NULL, &ftl_bdev_desc);
if (rc) {
goto not_found;
}
bdev = spdk_bdev_desc_get_bdev(ftl_bdev_desc);
if (bdev->module != &g_ftl_if) {
goto bdev_opened;
}
ftl = bdev->ctxt;
assert(ftl);
spdk_ftl_dev_set_fast_shutdown(ftl->dev, fast_shutdown);
spdk_bdev_close(ftl_bdev_desc);
rc = spdk_bdev_unregister_by_name(name, &g_ftl_if, cb_fn, cb_arg);
if (rc) {
cb_fn(cb_arg, rc);
}
return;
bdev_opened:
spdk_bdev_close(ftl_bdev_desc);
not_found:
cb_fn(cb_arg, -ENODEV);
}
static void
bdev_ftl_finish(void)
{
spdk_ftl_fini();
}
static void
bdev_ftl_create_defered_cb(const struct ftl_bdev_info *info, void *ctx, int status)
{
struct ftl_deferred_init *opts = ctx;
if (status) {
SPDK_ERRLOG("Failed to initialize FTL bdev '%s'\n", opts->conf.name);
}
bdev_ftl_defer_free(opts);
spdk_bdev_module_examine_done(&g_ftl_if);
}
static void
bdev_ftl_examine(struct spdk_bdev *bdev)
{
struct ftl_deferred_init *opts;
int rc;
LIST_FOREACH(opts, &g_deferred_init, entry) {
/* spdk_bdev_module_examine_done will be called by bdev_ftl_create_defered_cb */
rc = bdev_ftl_create_bdev(&opts->conf, bdev_ftl_create_defered_cb, opts);
if (rc == -ENODEV) {
continue;
}
LIST_REMOVE(opts, entry);
if (rc) {
bdev_ftl_create_defered_cb(NULL, opts, rc);
}
return;
}
spdk_bdev_module_examine_done(&g_ftl_if);
}
SPDK_LOG_REGISTER_COMPONENT(bdev_ftl)