588726d3ec
Due to user writes being decoupled from media writes because of the need of an intermediate write buffer, irrecoverable media write errors lead to pblk stalling; user writes fill up the buffer and end up in an infinite retry loop. In order to let user writes fail gracefully, it is necessary for pblk to keep track of its own internal state and prevent further writes from being placed into the write buffer. This patch implements a state machine to keep track of internal errors and, in case of failure, fail further user writes in an standard way. Depending on the type of error, pblk will do its best to persist buffered writes (which are already acknowledged) and close down on a graceful manner. This way, data might be recovered by re-instantiating pblk. Such state machine paves out the way for a state-based FTL log. Signed-off-by: Javier González <javier@cnexlabs.com> Signed-off-by: Matias Bjørling <matias@cnexlabs.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
121 lines
3.1 KiB
C
121 lines
3.1 KiB
C
/*
|
|
* Copyright (C) 2016 CNEX Labs
|
|
* Initial release: Javier Gonzalez <javier@cnexlabs.com>
|
|
* Matias Bjorling <matias@cnexlabs.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License version
|
|
* 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* pblk-cache.c - pblk's write cache
|
|
*/
|
|
|
|
#include "pblk.h"
|
|
|
|
int pblk_write_to_cache(struct pblk *pblk, struct bio *bio, unsigned long flags)
|
|
{
|
|
struct pblk_w_ctx w_ctx;
|
|
sector_t lba = pblk_get_lba(bio);
|
|
unsigned int bpos, pos;
|
|
int nr_entries = pblk_get_secs(bio);
|
|
int i, ret;
|
|
|
|
/* Update the write buffer head (mem) with the entries that we can
|
|
* write. The write in itself cannot fail, so there is no need to
|
|
* rollback from here on.
|
|
*/
|
|
retry:
|
|
ret = pblk_rb_may_write_user(&pblk->rwb, bio, nr_entries, &bpos);
|
|
switch (ret) {
|
|
case NVM_IO_REQUEUE:
|
|
io_schedule();
|
|
goto retry;
|
|
case NVM_IO_ERR:
|
|
pblk_pipeline_stop(pblk);
|
|
goto out;
|
|
}
|
|
|
|
if (unlikely(!bio_has_data(bio)))
|
|
goto out;
|
|
|
|
w_ctx.flags = flags;
|
|
pblk_ppa_set_empty(&w_ctx.ppa);
|
|
|
|
for (i = 0; i < nr_entries; i++) {
|
|
void *data = bio_data(bio);
|
|
|
|
w_ctx.lba = lba + i;
|
|
|
|
pos = pblk_rb_wrap_pos(&pblk->rwb, bpos + i);
|
|
pblk_rb_write_entry_user(&pblk->rwb, data, w_ctx, pos);
|
|
|
|
bio_advance(bio, PBLK_EXPOSED_PAGE_SIZE);
|
|
}
|
|
|
|
#ifdef CONFIG_NVM_DEBUG
|
|
atomic_long_add(nr_entries, &pblk->inflight_writes);
|
|
atomic_long_add(nr_entries, &pblk->req_writes);
|
|
#endif
|
|
|
|
pblk_rl_inserted(&pblk->rl, nr_entries);
|
|
|
|
out:
|
|
pblk_write_should_kick(pblk);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* On GC the incoming lbas are not necessarily sequential. Also, some of the
|
|
* lbas might not be valid entries, which are marked as empty by the GC thread
|
|
*/
|
|
int pblk_write_gc_to_cache(struct pblk *pblk, void *data, u64 *lba_list,
|
|
unsigned int nr_entries, unsigned int nr_rec_entries,
|
|
struct pblk_line *gc_line, unsigned long flags)
|
|
{
|
|
struct pblk_w_ctx w_ctx;
|
|
unsigned int bpos, pos;
|
|
int i, valid_entries;
|
|
|
|
/* Update the write buffer head (mem) with the entries that we can
|
|
* write. The write in itself cannot fail, so there is no need to
|
|
* rollback from here on.
|
|
*/
|
|
retry:
|
|
if (!pblk_rb_may_write_gc(&pblk->rwb, nr_rec_entries, &bpos)) {
|
|
io_schedule();
|
|
goto retry;
|
|
}
|
|
|
|
w_ctx.flags = flags;
|
|
pblk_ppa_set_empty(&w_ctx.ppa);
|
|
|
|
for (i = 0, valid_entries = 0; i < nr_entries; i++) {
|
|
if (lba_list[i] == ADDR_EMPTY)
|
|
continue;
|
|
|
|
w_ctx.lba = lba_list[i];
|
|
|
|
pos = pblk_rb_wrap_pos(&pblk->rwb, bpos + valid_entries);
|
|
pblk_rb_write_entry_gc(&pblk->rwb, data, w_ctx, gc_line, pos);
|
|
|
|
data += PBLK_EXPOSED_PAGE_SIZE;
|
|
valid_entries++;
|
|
}
|
|
|
|
WARN_ONCE(nr_rec_entries != valid_entries,
|
|
"pblk: inconsistent GC write\n");
|
|
|
|
#ifdef CONFIG_NVM_DEBUG
|
|
atomic_long_add(valid_entries, &pblk->inflight_writes);
|
|
atomic_long_add(valid_entries, &pblk->recov_gc_writes);
|
|
#endif
|
|
|
|
pblk_write_should_kick(pblk);
|
|
return NVM_IO_OK;
|
|
}
|