1625 lines · 37.1 KB · C
RawDownload
/* * Copyright (C) 2015 IT University of Copenhagen * Initial release: Matias Bjorling <m@bjorling.me> * * 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. * * Implementation of a Round-robin page-based Hybrid FTL for Open-channel SSDs. */ #include "rrpc.h" static struct kmem_cache *rrpc_gcb_cache, *rrpc_rq_cache;static DECLARE_RWSEM(rrpc_lock); static int rrpc_submit_io(struct rrpc *rrpc, struct bio *bio,				struct nvm_rq *rqd, unsigned long flags); #define rrpc_for_each_lun(rrpc, rlun, i) \		for ((i) = 0, rlun = &(rrpc)->luns[0]; \			(i) < (rrpc)->nr_luns; (i)++, rlun = &(rrpc)->luns[(i)]) static void rrpc_page_invalidate(struct rrpc *rrpc, struct rrpc_addr *a){	struct nvm_tgt_dev *dev = rrpc->dev;	struct rrpc_block *rblk = a->rblk;	unsigned int pg_offset; 	lockdep_assert_held(&rrpc->rev_lock); 	if (a->addr == ADDR_EMPTY || !rblk)		return; 	spin_lock(&rblk->lock); 	div_u64_rem(a->addr, dev->geo.sec_per_blk, &pg_offset);	WARN_ON(test_and_set_bit(pg_offset, rblk->invalid_pages));	rblk->nr_invalid_pages++; 	spin_unlock(&rblk->lock); 	rrpc->rev_trans_map[a->addr].addr = ADDR_EMPTY;} static void rrpc_invalidate_range(struct rrpc *rrpc, sector_t slba,							unsigned int len){	sector_t i; 	spin_lock(&rrpc->rev_lock);	for (i = slba; i < slba + len; i++) {		struct rrpc_addr *gp = &rrpc->trans_map[i]; 		rrpc_page_invalidate(rrpc, gp);		gp->rblk = NULL;	}	spin_unlock(&rrpc->rev_lock);} static struct nvm_rq *rrpc_inflight_laddr_acquire(struct rrpc *rrpc,					sector_t laddr, unsigned int pages){	struct nvm_rq *rqd;	struct rrpc_inflight_rq *inf; 	rqd = mempool_alloc(rrpc->rq_pool, GFP_ATOMIC);	if (!rqd)		return ERR_PTR(-ENOMEM); 	inf = rrpc_get_inflight_rq(rqd);	if (rrpc_lock_laddr(rrpc, laddr, pages, inf)) {		mempool_free(rqd, rrpc->rq_pool);		return NULL;	} 	return rqd;} static void rrpc_inflight_laddr_release(struct rrpc *rrpc, struct nvm_rq *rqd){	struct rrpc_inflight_rq *inf = rrpc_get_inflight_rq(rqd); 	rrpc_unlock_laddr(rrpc, inf); 	mempool_free(rqd, rrpc->rq_pool);} static void rrpc_discard(struct rrpc *rrpc, struct bio *bio){	sector_t slba = bio->bi_iter.bi_sector / NR_PHY_IN_LOG;	sector_t len = bio->bi_iter.bi_size / RRPC_EXPOSED_PAGE_SIZE;	struct nvm_rq *rqd; 	while (1) {		rqd = rrpc_inflight_laddr_acquire(rrpc, slba, len);		if (rqd)			break; 		schedule();	} 	if (IS_ERR(rqd)) {		pr_err("rrpc: unable to acquire inflight IO\n");		bio_io_error(bio);		return;	} 	rrpc_invalidate_range(rrpc, slba, len);	rrpc_inflight_laddr_release(rrpc, rqd);} static int block_is_full(struct rrpc *rrpc, struct rrpc_block *rblk){	struct nvm_tgt_dev *dev = rrpc->dev; 	return (rblk->next_page == dev->geo.sec_per_blk);} /* Calculate relative addr for the given block, considering instantiated LUNs */static u64 block_to_rel_addr(struct rrpc *rrpc, struct rrpc_block *rblk){	struct nvm_tgt_dev *dev = rrpc->dev;	struct rrpc_lun *rlun = rblk->rlun; 	return rlun->id * dev->geo.sec_per_blk;} static struct ppa_addr rrpc_ppa_to_gaddr(struct nvm_tgt_dev *dev,					 struct rrpc_addr *gp){	struct rrpc_block *rblk = gp->rblk;	struct rrpc_lun *rlun = rblk->rlun;	u64 addr = gp->addr;	struct ppa_addr paddr; 	paddr.ppa = addr;	paddr = rrpc_linear_to_generic_addr(&dev->geo, paddr);	paddr.g.ch = rlun->bppa.g.ch;	paddr.g.lun = rlun->bppa.g.lun;	paddr.g.blk = rblk->id; 	return paddr;} /* requires lun->lock taken */static void rrpc_set_lun_cur(struct rrpc_lun *rlun, struct rrpc_block *new_rblk,						struct rrpc_block **cur_rblk){	struct rrpc *rrpc = rlun->rrpc; 	if (*cur_rblk) {		spin_lock(&(*cur_rblk)->lock);		WARN_ON(!block_is_full(rrpc, *cur_rblk));		spin_unlock(&(*cur_rblk)->lock);	}	*cur_rblk = new_rblk;} static struct rrpc_block *__rrpc_get_blk(struct rrpc *rrpc,							struct rrpc_lun *rlun){	struct rrpc_block *rblk = NULL; 	if (list_empty(&rlun->free_list))		goto out; 	rblk = list_first_entry(&rlun->free_list, struct rrpc_block, list); 	list_move_tail(&rblk->list, &rlun->used_list);	rblk->state = NVM_BLK_ST_TGT;	rlun->nr_free_blocks--; out:	return rblk;} static struct rrpc_block *rrpc_get_blk(struct rrpc *rrpc, struct rrpc_lun *rlun,							unsigned long flags){	struct nvm_tgt_dev *dev = rrpc->dev;	struct rrpc_block *rblk;	int is_gc = flags & NVM_IOTYPE_GC; 	spin_lock(&rlun->lock);	if (!is_gc && rlun->nr_free_blocks < rlun->reserved_blocks) {		pr_err("nvm: rrpc: cannot give block to non GC request\n");		spin_unlock(&rlun->lock);		return NULL;	} 	rblk = __rrpc_get_blk(rrpc, rlun);	if (!rblk) {		pr_err("nvm: rrpc: cannot get new block\n");		spin_unlock(&rlun->lock);		return NULL;	}	spin_unlock(&rlun->lock); 	bitmap_zero(rblk->invalid_pages, dev->geo.sec_per_blk);	rblk->next_page = 0;	rblk->nr_invalid_pages = 0;	atomic_set(&rblk->data_cmnt_size, 0); 	return rblk;} static void rrpc_put_blk(struct rrpc *rrpc, struct rrpc_block *rblk){	struct rrpc_lun *rlun = rblk->rlun; 	spin_lock(&rlun->lock);	if (rblk->state & NVM_BLK_ST_TGT) {		list_move_tail(&rblk->list, &rlun->free_list);		rlun->nr_free_blocks++;		rblk->state = NVM_BLK_ST_FREE;	} else if (rblk->state & NVM_BLK_ST_BAD) {		list_move_tail(&rblk->list, &rlun->bb_list);		rblk->state = NVM_BLK_ST_BAD;	} else {		WARN_ON_ONCE(1);		pr_err("rrpc: erroneous type (ch:%d,lun:%d,blk%d-> %u)\n",					rlun->bppa.g.ch, rlun->bppa.g.lun,					rblk->id, rblk->state);		list_move_tail(&rblk->list, &rlun->bb_list);	}	spin_unlock(&rlun->lock);} static void rrpc_put_blks(struct rrpc *rrpc){	struct rrpc_lun *rlun;	int i; 	for (i = 0; i < rrpc->nr_luns; i++) {		rlun = &rrpc->luns[i];		if (rlun->cur)			rrpc_put_blk(rrpc, rlun->cur);		if (rlun->gc_cur)			rrpc_put_blk(rrpc, rlun->gc_cur);	}} static struct rrpc_lun *get_next_lun(struct rrpc *rrpc){	int next = atomic_inc_return(&rrpc->next_lun); 	return &rrpc->luns[next % rrpc->nr_luns];} static void rrpc_gc_kick(struct rrpc *rrpc){	struct rrpc_lun *rlun;	unsigned int i; 	for (i = 0; i < rrpc->nr_luns; i++) {		rlun = &rrpc->luns[i];		queue_work(rrpc->krqd_wq, &rlun->ws_gc);	}} /* * timed GC every interval. */static void rrpc_gc_timer(unsigned long data){	struct rrpc *rrpc = (struct rrpc *)data; 	rrpc_gc_kick(rrpc);	mod_timer(&rrpc->gc_timer, jiffies + msecs_to_jiffies(10));} static void rrpc_end_sync_bio(struct bio *bio){	struct completion *waiting = bio->bi_private; 	if (bio->bi_status)		pr_err("nvm: gc request failed (%u).\n", bio->bi_status); 	complete(waiting);} /* * rrpc_move_valid_pages -- migrate live data off the block * @rrpc: the 'rrpc' structure * @block: the block from which to migrate live pages * * Description: *   GC algorithms may call this function to migrate remaining live *   pages off the block prior to erasing it. This function blocks *   further execution until the operation is complete. */static int rrpc_move_valid_pages(struct rrpc *rrpc, struct rrpc_block *rblk){	struct nvm_tgt_dev *dev = rrpc->dev;	struct request_queue *q = dev->q;	struct rrpc_rev_addr *rev;	struct nvm_rq *rqd;	struct bio *bio;	struct page *page;	int slot;	int nr_sec_per_blk = dev->geo.sec_per_blk;	u64 phys_addr;	DECLARE_COMPLETION_ONSTACK(wait); 	if (bitmap_full(rblk->invalid_pages, nr_sec_per_blk))		return 0; 	bio = bio_alloc(GFP_NOIO, 1);	if (!bio) {		pr_err("nvm: could not alloc bio to gc\n");		return -ENOMEM;	} 	page = mempool_alloc(rrpc->page_pool, GFP_NOIO); 	while ((slot = find_first_zero_bit(rblk->invalid_pages,					    nr_sec_per_blk)) < nr_sec_per_blk) { 		/* Lock laddr */		phys_addr = rrpc_blk_to_ppa(rrpc, rblk) + slot; try:		spin_lock(&rrpc->rev_lock);		/* Get logical address from physical to logical table */		rev = &rrpc->rev_trans_map[phys_addr];		/* already updated by previous regular write */		if (rev->addr == ADDR_EMPTY) {			spin_unlock(&rrpc->rev_lock);			continue;		} 		rqd = rrpc_inflight_laddr_acquire(rrpc, rev->addr, 1);		if (IS_ERR_OR_NULL(rqd)) {			spin_unlock(&rrpc->rev_lock);			schedule();			goto try;		} 		spin_unlock(&rrpc->rev_lock); 		/* Perform read to do GC */		bio->bi_iter.bi_sector = rrpc_get_sector(rev->addr);		bio_set_op_attrs(bio,  REQ_OP_READ, 0);		bio->bi_private = &wait;		bio->bi_end_io = rrpc_end_sync_bio; 		/* TODO: may fail when EXP_PG_SIZE > PAGE_SIZE */		bio_add_pc_page(q, bio, page, RRPC_EXPOSED_PAGE_SIZE, 0); 		if (rrpc_submit_io(rrpc, bio, rqd, NVM_IOTYPE_GC)) {			pr_err("rrpc: gc read failed.\n");			rrpc_inflight_laddr_release(rrpc, rqd);			goto finished;		}		wait_for_completion_io(&wait);		if (bio->bi_status) {			rrpc_inflight_laddr_release(rrpc, rqd);			goto finished;		} 		bio_reset(bio);		reinit_completion(&wait); 		bio->bi_iter.bi_sector = rrpc_get_sector(rev->addr);		bio_set_op_attrs(bio, REQ_OP_WRITE, 0);		bio->bi_private = &wait;		bio->bi_end_io = rrpc_end_sync_bio; 		bio_add_pc_page(q, bio, page, RRPC_EXPOSED_PAGE_SIZE, 0); 		/* turn the command around and write the data back to a new		 * address		 */		if (rrpc_submit_io(rrpc, bio, rqd, NVM_IOTYPE_GC)) {			pr_err("rrpc: gc write failed.\n");			rrpc_inflight_laddr_release(rrpc, rqd);			goto finished;		}		wait_for_completion_io(&wait); 		rrpc_inflight_laddr_release(rrpc, rqd);		if (bio->bi_status)			goto finished; 		bio_reset(bio);	} finished:	mempool_free(page, rrpc->page_pool);	bio_put(bio); 	if (!bitmap_full(rblk->invalid_pages, nr_sec_per_blk)) {		pr_err("nvm: failed to garbage collect block\n");		return -EIO;	} 	return 0;} static void rrpc_block_gc(struct work_struct *work){	struct rrpc_block_gc *gcb = container_of(work, struct rrpc_block_gc,									ws_gc);	struct rrpc *rrpc = gcb->rrpc;	struct rrpc_block *rblk = gcb->rblk;	struct rrpc_lun *rlun = rblk->rlun;	struct ppa_addr ppa; 	mempool_free(gcb, rrpc->gcb_pool);	pr_debug("nvm: block 'ch:%d,lun:%d,blk:%d' being reclaimed\n",			rlun->bppa.g.ch, rlun->bppa.g.lun,			rblk->id); 	if (rrpc_move_valid_pages(rrpc, rblk))		goto put_back; 	ppa.ppa = 0;	ppa.g.ch = rlun->bppa.g.ch;	ppa.g.lun = rlun->bppa.g.lun;	ppa.g.blk = rblk->id; 	if (nvm_erase_sync(rrpc->dev, &ppa, 1))		goto put_back; 	rrpc_put_blk(rrpc, rblk); 	return; put_back:	spin_lock(&rlun->lock);	list_add_tail(&rblk->prio, &rlun->prio_list);	spin_unlock(&rlun->lock);} /* the block with highest number of invalid pages, will be in the beginning * of the list */static struct rrpc_block *rblk_max_invalid(struct rrpc_block *ra,							struct rrpc_block *rb){	if (ra->nr_invalid_pages == rb->nr_invalid_pages)		return ra; 	return (ra->nr_invalid_pages < rb->nr_invalid_pages) ? rb : ra;} /* linearly find the block with highest number of invalid pages * requires lun->lock */static struct rrpc_block *block_prio_find_max(struct rrpc_lun *rlun){	struct list_head *prio_list = &rlun->prio_list;	struct rrpc_block *rblk, *max; 	BUG_ON(list_empty(prio_list)); 	max = list_first_entry(prio_list, struct rrpc_block, prio);	list_for_each_entry(rblk, prio_list, prio)		max = rblk_max_invalid(max, rblk); 	return max;} static void rrpc_lun_gc(struct work_struct *work){	struct rrpc_lun *rlun = container_of(work, struct rrpc_lun, ws_gc);	struct rrpc *rrpc = rlun->rrpc;	struct nvm_tgt_dev *dev = rrpc->dev;	struct rrpc_block_gc *gcb;	unsigned int nr_blocks_need; 	nr_blocks_need = dev->geo.blks_per_lun / GC_LIMIT_INVERSE; 	if (nr_blocks_need < rrpc->nr_luns)		nr_blocks_need = rrpc->nr_luns; 	spin_lock(&rlun->lock);	while (nr_blocks_need > rlun->nr_free_blocks &&					!list_empty(&rlun->prio_list)) {		struct rrpc_block *rblk = block_prio_find_max(rlun); 		if (!rblk->nr_invalid_pages)			break; 		gcb = mempool_alloc(rrpc->gcb_pool, GFP_ATOMIC);		if (!gcb)			break; 		list_del_init(&rblk->prio); 		WARN_ON(!block_is_full(rrpc, rblk)); 		pr_debug("rrpc: selected block 'ch:%d,lun:%d,blk:%d' for GC\n",					rlun->bppa.g.ch, rlun->bppa.g.lun,

Lines 1–500 of 1625.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t