Spdk/lib/ftl/ftl_reloc.c

670 lines
14 KiB
C
Raw Normal View History

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright (c) Intel Corporation.
* All rights reserved.
*/
#include "ftl_band.h"
#include "ftl_core.h"
#include "ftl_debug.h"
#include "ftl_io.h"
#include "ftl_internal.h"
#include "spdk/ftl.h"
#include "spdk/likely.h"
struct ftl_reloc;
struct ftl_band_reloc;
/* TODO: Should probably change the move naming nomenclature to something more descriptive */
enum ftl_reloc_move_state {
FTL_RELOC_STATE_READ = 0,
FTL_RELOC_STATE_PIN,
FTL_RELOC_STATE_WRITE,
FTL_RELOC_STATE_WAIT,
FTL_RELOC_STATE_HALT,
FTL_RELOC_STATE_MAX
};
struct ftl_reloc_move {
/* FTL device */
struct spdk_ftl_dev *dev;
struct ftl_reloc *reloc;
/* Request for doing IO */
struct ftl_rq *rq;
/* Move state (read, write) */
enum ftl_reloc_move_state state;
/* Entry of circular list */
TAILQ_ENTRY(ftl_reloc_move) qentry;
};
struct ftl_reloc {
/* Device associated with relocate */
struct spdk_ftl_dev *dev;
/* Indicates relocate is about to halt */
bool halt;
/* Band which are read to relocate */
struct ftl_band *band;
/* Bands already read, but waiting for finishing GC */
TAILQ_HEAD(, ftl_band) band_done;
size_t band_done_count;
/* Flags indicating reloc is waiting for a new band */
bool band_waiting;
/* Maximum number of IOs per band */
size_t max_qdepth;
/* Queue of free move objects */
struct ftl_reloc_move *move_buffer;
/* Array of movers queue for each state */
TAILQ_HEAD(, ftl_reloc_move) move_queue[FTL_RELOC_STATE_MAX];
};
static void move_read_cb(struct ftl_rq *rq);
static void move_write_cb(struct ftl_rq *rq);
static void move_set_state(struct ftl_reloc_move *mv, enum ftl_reloc_move_state state);
static void move_write(struct ftl_reloc *reloc, struct ftl_reloc_move *mv);
static void move_read_error_cb(struct ftl_rq *rq, struct ftl_band *band, uint64_t idx,
uint64_t count);
static void
move_deinit(struct ftl_reloc_move *mv)
{
assert(mv);
ftl_rq_del(mv->rq);
}
static int
move_init(struct ftl_reloc *reloc, struct ftl_reloc_move *mv)
{
mv->state = FTL_RELOC_STATE_HALT;
TAILQ_INSERT_TAIL(&reloc->move_queue[FTL_RELOC_STATE_HALT], mv, qentry);
mv->reloc = reloc;
mv->dev = reloc->dev;
mv->rq = ftl_rq_new(mv->dev, mv->dev->md_size);
if (!mv->rq) {
return -ENOMEM;
}
mv->rq->owner.priv = mv;
return 0;
}
struct ftl_reloc *
ftl_reloc_init(struct spdk_ftl_dev *dev)
{
struct ftl_reloc *reloc;
struct ftl_reloc_move *move;
size_t i, count;
reloc = calloc(1, sizeof(*reloc));
if (!reloc) {
return NULL;
}
reloc->dev = dev;
reloc->halt = true;
reloc->max_qdepth = dev->sb->max_reloc_qdepth;
reloc->move_buffer = calloc(reloc->max_qdepth, sizeof(*reloc->move_buffer));
if (!reloc->move_buffer) {
FTL_ERRLOG(dev, "Failed to initialize reloc moves pool");
goto error;
}
/* Initialize movers queues */
count = SPDK_COUNTOF(reloc->move_queue);
for (i = 0; i < count; ++i) {
TAILQ_INIT(&reloc->move_queue[i]);
}
for (i = 0; i < reloc->max_qdepth; ++i) {
move = &reloc->move_buffer[i];
if (move_init(reloc, move)) {
goto error;
}
}
TAILQ_INIT(&reloc->band_done);
return reloc;
error:
ftl_reloc_free(reloc);
return NULL;
}
struct ftl_reloc_task_fini {
struct ftl_reloc_task *task;
spdk_msg_fn cb;
void *cb_arg;
};
void
ftl_reloc_free(struct ftl_reloc *reloc)
{
size_t i;
if (!reloc) {
return;
}
if (reloc->move_buffer) {
for (i = 0; i < reloc->max_qdepth; ++i) {
move_deinit(&reloc->move_buffer[i]);
}
}
free(reloc->move_buffer);
free(reloc);
}
void
ftl_reloc_halt(struct ftl_reloc *reloc)
{
reloc->halt = true;
}
void
ftl_reloc_resume(struct ftl_reloc *reloc)
{
struct ftl_reloc_move *mv, *next;
reloc->halt = false;
TAILQ_FOREACH_SAFE(mv, &reloc->move_queue[FTL_RELOC_STATE_HALT], qentry,
next) {
move_set_state(mv, FTL_RELOC_STATE_READ);
}
}
static void
move_set_state(struct ftl_reloc_move *mv, enum ftl_reloc_move_state state)
{
struct ftl_reloc *reloc = mv->reloc;
switch (state) {
case FTL_RELOC_STATE_READ:
mv->rq->owner.cb = move_read_cb;
mv->rq->owner.error = move_read_error_cb;
mv->rq->iter.idx = 0;
mv->rq->iter.count = 0;
mv->rq->success = true;
break;
case FTL_RELOC_STATE_WRITE:
mv->rq->owner.cb = move_write_cb;
mv->rq->owner.error = NULL;
break;
case FTL_RELOC_STATE_PIN:
case FTL_RELOC_STATE_WAIT:
case FTL_RELOC_STATE_HALT:
break;
default:
ftl_abort();
break;
}
if (mv->state != state) {
/* Remove the mover from previous queue */
TAILQ_REMOVE(&reloc->move_queue[mv->state], mv, qentry);
/* Insert the mover to the new queue */
TAILQ_INSERT_TAIL(&reloc->move_queue[state], mv, qentry);
/* Update state */
mv->state = state;
}
}
static void
move_get_band_cb(struct ftl_band *band, void *cntx, bool status)
{
struct ftl_reloc *reloc = cntx;
if (spdk_likely(status)) {
reloc->band = band;
ftl_band_iter_init(band);
}
reloc->band_waiting = false;
}
static void
move_grab_new_band(struct ftl_reloc *reloc)
{
if (!reloc->band_waiting) {
if (!ftl_needs_reloc(reloc->dev)) {
return;
}
/* Limit number of simultaneously relocated bands */
if (reloc->band_done_count > 2) {
return;
}
reloc->band_waiting = true;
ftl_band_get_next_gc(reloc->dev, move_get_band_cb, reloc);
}
}
static struct ftl_band *
move_get_band(struct ftl_reloc *reloc)
{
struct ftl_band *band = reloc->band;
if (!band) {
move_grab_new_band(reloc);
return NULL;
}
if (!ftl_band_filled(band, band->md->iter.offset)) {
/* Band still not read, we can continue reading */
return band;
}
TAILQ_INSERT_TAIL(&reloc->band_done, band, queue_entry);
reloc->band_done_count++;
reloc->band = NULL;
return NULL;
}
static void
move_advance_rq(struct ftl_rq *rq)
{
struct ftl_band *band = rq->io.band;
uint64_t offset, i;
struct ftl_rq_entry *entry = &rq->entries[rq->iter.idx];
assert(rq->iter.idx + rq->iter.count <= rq->num_blocks);
for (i = 0; i < rq->iter.count; i++) {
offset = ftl_band_block_offset_from_addr(band, rq->io.addr);
assert(offset < ftl_get_num_blocks_in_band(band->dev));
assert(ftl_band_block_offset_valid(band, offset));
entry->lba = band->p2l_map.band_map[offset].lba;
entry->addr = rq->io.addr;
entry->owner.priv = band;
entry->seq_id = band->p2l_map.band_map[offset].seq_id;
entry++;
rq->io.addr = ftl_band_next_addr(band, rq->io.addr, 1);
band->owner.cnt++;
}
/* Increase QD for the request */
rq->iter.qd++;
/* Advanced request iterator */
rq->iter.idx += rq->iter.count;
}
static void
move_init_entries(struct ftl_rq *rq, uint64_t idx, uint64_t count)
{
uint64_t i = 0;
struct ftl_rq_entry *iter = &rq->entries[idx];
assert(idx + count <= rq->num_blocks);
i = 0;
while (i < count) {
iter->addr = FTL_ADDR_INVALID;
iter->owner.priv = NULL;
iter->lba = FTL_LBA_INVALID;
iter->seq_id = 0;
iter++;
i++;
}
}
static void
move_read_error_cb(struct ftl_rq *rq, struct ftl_band *band, uint64_t idx, uint64_t count)
{
move_init_entries(rq, idx, count);
band->owner.cnt -= count;
}
static void
move_read_cb(struct ftl_rq *rq)
{
struct ftl_reloc_move *mv = rq->owner.priv;
/* Decrease QD of the request */
assert(rq->iter.qd > 0);
rq->iter.qd--;
if (rq->iter.idx != rq->num_blocks || rq->iter.qd) {
return;
}
move_set_state(mv, FTL_RELOC_STATE_PIN);
}
static void
move_rq_pad(struct ftl_rq *rq, struct ftl_band *band)
{
struct ftl_rq_entry *entry = &rq->entries[rq->iter.idx];
for (; rq->iter.idx < rq->num_blocks; ++rq->iter.idx) {
entry->addr = rq->io.addr;
entry->owner.priv = band;
entry->lba = FTL_LBA_INVALID;
entry->seq_id = 0;
entry++;
rq->io.addr = ftl_band_next_addr(band, rq->io.addr, 1);
band->owner.cnt++;
}
assert(rq->iter.idx == rq->num_blocks);
}
static void
move_read(struct ftl_reloc *reloc, struct ftl_reloc_move *mv, struct ftl_band *band)
{
struct ftl_rq *rq = mv->rq;
uint64_t blocks = ftl_get_num_blocks_in_band(band->dev);
uint64_t pos = band->md->iter.offset;
uint64_t begin = ftl_bitmap_find_first_set(band->p2l_map.valid, pos, UINT64_MAX);
uint64_t end, band_left, rq_left;
if (spdk_likely(begin < blocks)) {
if (begin > pos) {
ftl_band_iter_advance(band, begin - pos);
} else if (begin == pos) {
/* Valid block at the position of iterator */
} else {
/* Inconsistent state */
ftl_abort();
}
} else if (UINT64_MAX == begin) {
/* No more valid LBAs in the band */
band_left = ftl_band_user_blocks_left(band, pos);
ftl_band_iter_advance(band, band_left);
assert(ftl_band_filled(band, band->md->iter.offset));
if (rq->iter.idx) {
move_rq_pad(rq, band);
move_set_state(mv, FTL_RELOC_STATE_WAIT);
rq->iter.qd++;
rq->owner.cb(rq);
}
return;
} else {
/* Inconsistent state */
ftl_abort();
}
rq_left = rq->num_blocks - rq->iter.idx;
assert(rq_left > 0);
/* Find next clear bit, but no further than max request count */
end = ftl_bitmap_find_first_clear(band->p2l_map.valid, begin + 1, begin + rq_left);
if (end != UINT64_MAX) {
rq_left = end - begin;
}
band_left = ftl_band_user_blocks_left(band, band->md->iter.offset);
rq->iter.count = spdk_min(rq_left, band_left);
ftl_band_rq_read(band, rq);
move_advance_rq(rq);
/* Advance band iterator */
ftl_band_iter_advance(band, rq->iter.count);
/* If band is fully written pad rest of request */
if (ftl_band_filled(band, band->md->iter.offset)) {
move_rq_pad(rq, band);
}
if (rq->iter.idx == rq->num_blocks) {
/*
* All request entries scheduled for reading,
* We can change state to waiting
*/
move_set_state(mv, FTL_RELOC_STATE_WAIT);
}
}
static void
move_pin_cb(struct spdk_ftl_dev *dev, int status, struct ftl_l2p_pin_ctx *pin_ctx)
{
struct ftl_reloc_move *mv = pin_ctx->cb_ctx;
struct ftl_rq *rq = mv->rq;
if (status) {
rq->iter.status = status;
pin_ctx->lba = FTL_LBA_INVALID;
}
if (--rq->iter.remaining == 0) {
if (rq->iter.status) {
/* unpin and try again */
ftl_rq_unpin(rq);
move_set_state(mv, FTL_RELOC_STATE_PIN);
return;
}
move_set_state(mv, FTL_RELOC_STATE_WRITE);
}
}
static void
move_pin(struct ftl_reloc_move *mv)
{
struct ftl_rq *rq = mv->rq;
struct ftl_rq_entry *entry = rq->entries;
uint64_t i;
move_set_state(mv, FTL_RELOC_STATE_WAIT);
rq->iter.remaining = rq->iter.count = rq->num_blocks;
rq->iter.status = 0;
for (i = 0; i < rq->num_blocks; i++) {
if (entry->lba != FTL_LBA_INVALID) {
ftl_l2p_pin(rq->dev, entry->lba, 1, move_pin_cb, mv, &entry->l2p_pin_ctx);
} else {
ftl_l2p_pin_skip(rq->dev, move_pin_cb, mv, &entry->l2p_pin_ctx);
}
entry++;
}
}
static void
move_finish_write(struct ftl_rq *rq)
{
uint64_t i;
struct spdk_ftl_dev *dev = rq->dev;
struct ftl_rq_entry *iter = rq->entries;
ftl_addr addr = rq->io.addr;
struct ftl_band *rq_band = rq->io.band;
struct ftl_band *band;
for (i = 0; i < rq->num_blocks; ++i, ++iter) {
band = iter->owner.priv;
if (band) {
assert(band->owner.cnt > 0);
band->owner.cnt--;
}
if (iter->lba != FTL_LBA_INVALID) {
/* Update L2P table */
ftl_l2p_update_base(dev, iter->lba, addr, iter->addr);
ftl_l2p_unpin(dev, iter->lba, 1);
}
addr = ftl_band_next_addr(rq_band, addr, 1);
}
}
static void
move_write_cb(struct ftl_rq *rq)
{
struct ftl_reloc_move *mv = rq->owner.priv;
assert(rq->iter.qd == 1);
rq->iter.qd--;
if (spdk_likely(rq->success)) {
move_finish_write(rq);
move_set_state(mv, FTL_RELOC_STATE_READ);
} else {
/* Write failed, repeat write */
move_set_state(mv, FTL_RELOC_STATE_WRITE);
}
}
static void
move_write(struct ftl_reloc *reloc, struct ftl_reloc_move *mv)
{
struct spdk_ftl_dev *dev = mv->dev;
struct ftl_rq *rq = mv->rq;
assert(rq->iter.idx == rq->num_blocks);
/* Request contains data to be placed on a new location, submit it */
ftl_writer_queue_rq(&dev->writer_gc, rq);
rq->iter.qd++;
move_set_state(mv, FTL_RELOC_STATE_WAIT);
}
static void
move_run(struct ftl_reloc *reloc, struct ftl_reloc_move *mv)
{
struct ftl_band *band;
switch (mv->state) {
case FTL_RELOC_STATE_READ: {
if (spdk_unlikely(reloc->halt)) {
move_set_state(mv, FTL_RELOC_STATE_HALT);
break;
}
band = move_get_band(reloc);
if (!band) {
break;
}
move_read(reloc, mv, band);
}
break;
case FTL_RELOC_STATE_PIN:
move_pin(mv);
break;
case FTL_RELOC_STATE_WRITE:
if (spdk_unlikely(reloc->halt)) {
ftl_rq_unpin(mv->rq);
move_set_state(mv, FTL_RELOC_STATE_HALT);
break;
}
move_write(reloc, mv);
break;
case FTL_RELOC_STATE_HALT:
case FTL_RELOC_STATE_WAIT:
break;
default:
assert(0);
ftl_abort();
break;
}
}
static void
move_handle_band_error(struct ftl_band *band)
{
struct ftl_reloc *reloc = band->dev->reloc;
/*
* Handle band error, it's because an error occurred during reading,
* Add band to the close band list, will try reloc it in a moment
*/
TAILQ_REMOVE(&reloc->band_done, band, queue_entry);
reloc->band_done_count--;
band->md->state = FTL_BAND_STATE_CLOSING;
ftl_band_set_state(band, FTL_BAND_STATE_CLOSED);
}
static void
move_release_bands(struct ftl_reloc *reloc)
{
struct ftl_band *band;
if (TAILQ_EMPTY(&reloc->band_done)) {
return;
}
band = TAILQ_FIRST(&reloc->band_done);
if (band->owner.cnt || ftl_band_qd(band)) {
/* Band still in use */
return;
}
if (ftl_band_empty(band)) {
assert(ftl_band_filled(band, band->md->iter.offset));
TAILQ_REMOVE(&reloc->band_done, band, queue_entry);
reloc->band_done_count--;
ftl_band_free(band);
} else {
move_handle_band_error(band);
}
}
bool
ftl_reloc_is_halted(const struct ftl_reloc *reloc)
{
size_t i, count;
count = SPDK_COUNTOF(reloc->move_queue);
for (i = 0; i < count; ++i) {
if (i == FTL_RELOC_STATE_HALT) {
continue;
}
if (!TAILQ_EMPTY(&reloc->move_queue[i])) {
return false;
}
}
return true;
}
void
ftl_reloc(struct ftl_reloc *reloc)
{
size_t i, count;
count = SPDK_COUNTOF(reloc->move_queue);
for (i = 0; i < count; ++i) {
if (TAILQ_EMPTY(&reloc->move_queue[i])) {
continue;
}
move_run(reloc, TAILQ_FIRST(&reloc->move_queue[i]));
}
move_release_bands(reloc);
}