lib/ftl: track non-volatile cache's write sequence

This patch adds tracking of the phase of the writes to the non-volatile
cache. The phase is changed each time the whole buffer is filled. Along
with every block's LBA, current phase is stored in its metadata. This
allows for replaying the sequence of writes when recovering the data
from the cache after (unclean) shutdown.

Since there are only three possible phases to be stored on the device at
a time, phase is defined as a 2-bit counter cycling through 1 -> 2 -> 3
-> 1, with 0 marking blocks that were never written.

Change-Id: Id47880367934027fd102c32f183110acc9d4c62a
Signed-off-by: Konrad Sztyber <konrad.sztyber@intel.com>
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/458098
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Darek Stojaczyk <dariusz.stojaczyk@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Reviewed-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Reviewed-by: Mateusz Kozlowski <mateusz.kozlowski@intel.com>
This commit is contained in:
Konrad Sztyber 2019-06-03 08:44:52 +02:00 committed by Ben Walker
parent c69529d452
commit 4fd4e3db5f
4 changed files with 58 additions and 10 deletions

View File

@ -979,6 +979,7 @@ ftl_nv_cache_wrap(void *ctx)
hdr->uuid = dev->uuid; hdr->uuid = dev->uuid;
hdr->size = spdk_bdev_get_num_blocks(bdev); hdr->size = spdk_bdev_get_num_blocks(bdev);
hdr->version = FTL_NV_CACHE_HEADER_VERSION; hdr->version = FTL_NV_CACHE_HEADER_VERSION;
hdr->phase = (uint8_t)nv_cache->phase;
hdr->checksum = spdk_crc32c_update(hdr, offsetof(struct ftl_nv_cache_header, checksum), 0); hdr->checksum = spdk_crc32c_update(hdr, offsetof(struct ftl_nv_cache_header, checksum), 0);
rc = spdk_bdev_write_blocks(nv_cache->bdev_desc, ioch->cache_ioch, hdr, 0, 1, rc = spdk_bdev_write_blocks(nv_cache->bdev_desc, ioch->cache_ioch, hdr, 0, 1,
@ -992,7 +993,7 @@ ftl_nv_cache_wrap(void *ctx)
} }
static uint64_t static uint64_t
ftl_reserve_nv_cache(struct ftl_nv_cache *nv_cache, size_t *num_lbks) ftl_reserve_nv_cache(struct ftl_nv_cache *nv_cache, size_t *num_lbks, unsigned int *phase)
{ {
struct spdk_bdev *bdev = spdk_bdev_desc_get_bdev(nv_cache->bdev_desc); struct spdk_bdev *bdev = spdk_bdev_desc_get_bdev(nv_cache->bdev_desc);
struct spdk_ftl_dev *dev = SPDK_CONTAINEROF(nv_cache, struct spdk_ftl_dev, nv_cache); struct spdk_ftl_dev *dev = SPDK_CONTAINEROF(nv_cache, struct spdk_ftl_dev, nv_cache);
@ -1017,9 +1018,11 @@ ftl_reserve_nv_cache(struct ftl_nv_cache *nv_cache, size_t *num_lbks)
cache_addr = nv_cache->current_addr; cache_addr = nv_cache->current_addr;
nv_cache->current_addr += *num_lbks; nv_cache->current_addr += *num_lbks;
nv_cache->num_available -= *num_lbks; nv_cache->num_available -= *num_lbks;
*phase = nv_cache->phase;
if (nv_cache->current_addr == spdk_bdev_get_num_blocks(bdev)) { if (nv_cache->current_addr == spdk_bdev_get_num_blocks(bdev)) {
nv_cache->current_addr = FTL_NV_CACHE_DATA_OFFSET; nv_cache->current_addr = FTL_NV_CACHE_DATA_OFFSET;
nv_cache->phase = ftl_nv_cache_next_phase(nv_cache->phase);
nv_cache->ready = false; nv_cache->ready = false;
spdk_thread_send_msg(ftl_get_core_thread(dev), ftl_nv_cache_wrap, dev); spdk_thread_send_msg(ftl_get_core_thread(dev), ftl_nv_cache_wrap, dev);
} }
@ -1095,15 +1098,19 @@ ftl_submit_nv_cache(void *ctx)
} }
static void static void
ftl_nv_cache_fill_md(struct ftl_nv_cache *nv_cache, struct ftl_io *io) ftl_nv_cache_fill_md(struct ftl_io *io, unsigned int phase)
{ {
struct spdk_bdev *bdev = spdk_bdev_desc_get_bdev(nv_cache->bdev_desc); struct spdk_bdev *bdev;
struct ftl_nv_cache *nv_cache = &io->dev->nv_cache;
uint64_t lbk_off, lba;
void *md_buf = io->md; void *md_buf = io->md;
size_t lbk_off;
bdev = spdk_bdev_desc_get_bdev(nv_cache->bdev_desc);
for (lbk_off = 0; lbk_off < io->lbk_cnt; ++lbk_off) { for (lbk_off = 0; lbk_off < io->lbk_cnt; ++lbk_off) {
*(uint64_t *)md_buf = ftl_io_get_lba(io, lbk_off); lba = ftl_nv_cache_pack_lba(ftl_io_get_lba(io, lbk_off), phase);
md_buf = (char *)md_buf + spdk_bdev_get_md_size(bdev); memcpy(md_buf, &lba, sizeof(lba));
md_buf += spdk_bdev_get_md_size(bdev);
} }
} }
@ -1113,6 +1120,7 @@ _ftl_write_nv_cache(void *ctx)
struct ftl_io *child, *io = ctx; struct ftl_io *child, *io = ctx;
struct spdk_ftl_dev *dev = io->dev; struct spdk_ftl_dev *dev = io->dev;
struct spdk_thread *thread; struct spdk_thread *thread;
unsigned int phase;
uint64_t num_lbks; uint64_t num_lbks;
thread = spdk_io_channel_get_thread(io->ioch); thread = spdk_io_channel_get_thread(io->ioch);
@ -1134,7 +1142,7 @@ _ftl_write_nv_cache(void *ctx)
} }
/* Reserve area on the write buffer cache */ /* Reserve area on the write buffer cache */
child->ppa.ppa = ftl_reserve_nv_cache(&dev->nv_cache, &num_lbks); child->ppa.ppa = ftl_reserve_nv_cache(&dev->nv_cache, &num_lbks, &phase);
if (child->ppa.ppa == FTL_LBA_INVALID) { if (child->ppa.ppa == FTL_LBA_INVALID) {
spdk_mempool_put(dev->nv_cache.md_pool, child->md); spdk_mempool_put(dev->nv_cache.md_pool, child->md);
ftl_io_free(child); ftl_io_free(child);
@ -1147,7 +1155,7 @@ _ftl_write_nv_cache(void *ctx)
ftl_io_shrink_iovec(child, num_lbks); ftl_io_shrink_iovec(child, num_lbks);
} }
ftl_nv_cache_fill_md(&dev->nv_cache, child); ftl_nv_cache_fill_md(child, phase);
ftl_submit_nv_cache(child); ftl_submit_nv_cache(child);
} }

View File

@ -112,6 +112,13 @@ struct ftl_nv_cache {
uint64_t num_available; uint64_t num_available;
/* Maximum number of blocks */ /* Maximum number of blocks */
uint64_t num_data_blocks; uint64_t num_data_blocks;
/*
* Phase of the current cycle of writes. Each time whole cache area is filled, the phase is
* advanced. Current phase is saved in every IO's metadata, as well as in the header saved
* in the first sector. By looking at the phase of each block, it's possible to find the
* oldest block and replay the order of the writes when recovering the data from the cache.
*/
unsigned int phase;
/* Indicates that the data can be written to the cache */ /* Indicates that the data can be written to the cache */
bool ready; bool ready;
/* Metadata pool */ /* Metadata pool */
@ -247,6 +254,8 @@ struct ftl_nv_cache_header {
struct spdk_uuid uuid; struct spdk_uuid uuid;
/* Size of the non-volatile cache (in blocks) */ /* Size of the non-volatile cache (in blocks) */
uint64_t size; uint64_t size;
/* Current phase */
uint8_t phase;
/* Checksum of the header, needs to be last element */ /* Checksum of the header, needs to be last element */
uint32_t checksum; uint32_t checksum;
} __attribute__((packed)); } __attribute__((packed));
@ -480,7 +489,31 @@ ftl_vld_map_size(const struct spdk_ftl_dev *dev)
return (size_t)spdk_divide_round_up(ftl_num_band_lbks(dev), CHAR_BIT); return (size_t)spdk_divide_round_up(ftl_num_band_lbks(dev), CHAR_BIT);
} }
#define FTL_NV_CACHE_HEADER_VERSION 1 #define FTL_NV_CACHE_HEADER_VERSION (1)
#define FTL_NV_CACHE_DATA_OFFSET 1 #define FTL_NV_CACHE_DATA_OFFSET (1)
#define FTL_NV_CACHE_PHASE_OFFSET (62)
#define FTL_NV_CACHE_PHASE_MASK (3ULL << FTL_NV_CACHE_PHASE_OFFSET)
static inline bool
ftl_nv_cache_phase_is_valid(unsigned int phase)
{
return phase > 0 && phase <= 3;
}
static inline unsigned int
ftl_nv_cache_next_phase(unsigned int current)
{
static const unsigned int phases[] = { 0, 2, 3, 1 };
assert(ftl_nv_cache_phase_is_valid(current));
return phases[current];
}
static inline uint64_t
ftl_nv_cache_pack_lba(uint64_t lba, unsigned int phase)
{
assert(ftl_nv_cache_phase_is_valid(phase));
return (lba & ~FTL_NV_CACHE_PHASE_MASK) | ((uint64_t)phase << FTL_NV_CACHE_PHASE_OFFSET);
}
#endif /* FTL_CORE_H */ #endif /* FTL_CORE_H */

View File

@ -909,6 +909,7 @@ ftl_clear_nv_cache_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
hdr->uuid = dev->uuid; hdr->uuid = dev->uuid;
hdr->size = spdk_bdev_get_num_blocks(bdev); hdr->size = spdk_bdev_get_num_blocks(bdev);
hdr->version = FTL_NV_CACHE_HEADER_VERSION; hdr->version = FTL_NV_CACHE_HEADER_VERSION;
hdr->phase = dev->nv_cache.phase = 1;
hdr->checksum = spdk_crc32c_update(hdr, offsetof(struct ftl_nv_cache_header, checksum), 0); hdr->checksum = spdk_crc32c_update(hdr, offsetof(struct ftl_nv_cache_header, checksum), 0);
if (spdk_bdev_write_blocks(nv_cache->bdev_desc, ioch->cache_ioch, hdr, 0, 1, if (spdk_bdev_write_blocks(nv_cache->bdev_desc, ioch->cache_ioch, hdr, 0, 1,

View File

@ -422,6 +422,12 @@ ftl_nv_cache_header_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
goto out; goto out;
} }
if (!ftl_nv_cache_phase_is_valid(hdr->phase)) {
SPDK_ERRLOG("Invalid phase of the non-volatile cache (%u)\n", hdr->phase);
rc = -ENOTRECOVERABLE;
goto out;
}
pthread_spin_lock(&nv_cache->lock); pthread_spin_lock(&nv_cache->lock);
nv_cache->ready = true; nv_cache->ready = true;
pthread_spin_unlock(&nv_cache->lock); pthread_spin_unlock(&nv_cache->lock);