lightnvm: merge gennvm with core
For the first iteration of Open-Channel SSDs, it was anticipated that there could be various media managers on top of an open-channel SSD, such to allow vendors to plug in their own host-side FTLs, without the media manager in between. Now that an Open-Channel SSD is exposed as a traditional block device, there is no longer a need for this. Therefore lets merge the gennvm code with core and simplify the stack. Signed-off-by: Matias Bjørling <matias@cnexlabs.com> Signed-off-by: Jens Axboe <axboe@fb.com>
This commit is contained in:
		
							parent
							
								
									400f73b23f
								
							
						
					
					
						commit
						ade69e2432
					
				@ -26,15 +26,6 @@ config NVM_DEBUG
 | 
			
		||||
 | 
			
		||||
	It is required to create/remove targets without IOCTLs.
 | 
			
		||||
 | 
			
		||||
config NVM_GENNVM
 | 
			
		||||
	tristate "General Non-Volatile Memory Manager for Open-Channel SSDs"
 | 
			
		||||
	---help---
 | 
			
		||||
	Non-volatile memory media manager for Open-Channel SSDs that implements
 | 
			
		||||
	physical media metadata management and block provisioning API.
 | 
			
		||||
 | 
			
		||||
	This is the standard media manager for using Open-Channel SSDs, and
 | 
			
		||||
	required for targets to be instantiated.
 | 
			
		||||
 | 
			
		||||
config NVM_RRPC
 | 
			
		||||
	tristate "Round-robin Hybrid Open-Channel SSD target"
 | 
			
		||||
	---help---
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,5 @@
 | 
			
		||||
# Makefile for Open-Channel SSDs.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
obj-$(CONFIG_NVM)		:= core.o sysblk.o
 | 
			
		||||
obj-$(CONFIG_NVM_GENNVM) 	+= gennvm.o
 | 
			
		||||
obj-$(CONFIG_NVM)		:= core.o
 | 
			
		||||
obj-$(CONFIG_NVM_RRPC)		+= rrpc.o
 | 
			
		||||
 | 
			
		||||
@ -29,10 +29,492 @@
 | 
			
		||||
 | 
			
		||||
static LIST_HEAD(nvm_tgt_types);
 | 
			
		||||
static DECLARE_RWSEM(nvm_tgtt_lock);
 | 
			
		||||
static LIST_HEAD(nvm_mgrs);
 | 
			
		||||
static LIST_HEAD(nvm_devices);
 | 
			
		||||
static DECLARE_RWSEM(nvm_lock);
 | 
			
		||||
 | 
			
		||||
/* Map between virtual and physical channel and lun */
 | 
			
		||||
struct nvm_ch_map {
 | 
			
		||||
	int ch_off;
 | 
			
		||||
	int nr_luns;
 | 
			
		||||
	int *lun_offs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct nvm_dev_map {
 | 
			
		||||
	struct nvm_ch_map *chnls;
 | 
			
		||||
	int nr_chnls;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct nvm_area {
 | 
			
		||||
	struct list_head list;
 | 
			
		||||
	sector_t begin;
 | 
			
		||||
	sector_t end;	/* end is excluded */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	TRANS_TGT_TO_DEV =	0x0,
 | 
			
		||||
	TRANS_DEV_TO_TGT =	0x1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct nvm_target *nvm_find_target(struct nvm_dev *dev, const char *name)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_target *tgt;
 | 
			
		||||
 | 
			
		||||
	list_for_each_entry(tgt, &dev->targets, list)
 | 
			
		||||
		if (!strcmp(name, tgt->disk->disk_name))
 | 
			
		||||
			return tgt;
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_reserve_luns(struct nvm_dev *dev, int lun_begin, int lun_end)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	for (i = lun_begin; i <= lun_end; i++) {
 | 
			
		||||
		if (test_and_set_bit(i, dev->lun_map)) {
 | 
			
		||||
			pr_err("nvm: lun %d already allocated\n", i);
 | 
			
		||||
			goto err;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
err:
 | 
			
		||||
	while (--i > lun_begin)
 | 
			
		||||
		clear_bit(i, dev->lun_map);
 | 
			
		||||
 | 
			
		||||
	return -EBUSY;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nvm_release_luns_err(struct nvm_dev *dev, int lun_begin,
 | 
			
		||||
				 int lun_end)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	for (i = lun_begin; i <= lun_end; i++)
 | 
			
		||||
		WARN_ON(!test_and_clear_bit(i, dev->lun_map));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nvm_remove_tgt_dev(struct nvm_tgt_dev *tgt_dev)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct nvm_dev_map *dev_map = tgt_dev->map;
 | 
			
		||||
	int i, j;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < dev_map->nr_chnls; i++) {
 | 
			
		||||
		struct nvm_ch_map *ch_map = &dev_map->chnls[i];
 | 
			
		||||
		int *lun_offs = ch_map->lun_offs;
 | 
			
		||||
		int ch = i + ch_map->ch_off;
 | 
			
		||||
 | 
			
		||||
		for (j = 0; j < ch_map->nr_luns; j++) {
 | 
			
		||||
			int lun = j + lun_offs[j];
 | 
			
		||||
			int lunid = (ch * dev->geo.luns_per_chnl) + lun;
 | 
			
		||||
 | 
			
		||||
			WARN_ON(!test_and_clear_bit(lunid, dev->lun_map));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		kfree(ch_map->lun_offs);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kfree(dev_map->chnls);
 | 
			
		||||
	kfree(dev_map);
 | 
			
		||||
 | 
			
		||||
	kfree(tgt_dev->luns);
 | 
			
		||||
	kfree(tgt_dev);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct nvm_tgt_dev *nvm_create_tgt_dev(struct nvm_dev *dev,
 | 
			
		||||
					      int lun_begin, int lun_end)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_tgt_dev *tgt_dev = NULL;
 | 
			
		||||
	struct nvm_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	struct nvm_dev_map *dev_map;
 | 
			
		||||
	struct ppa_addr *luns;
 | 
			
		||||
	int nr_luns = lun_end - lun_begin + 1;
 | 
			
		||||
	int luns_left = nr_luns;
 | 
			
		||||
	int nr_chnls = nr_luns / dev->geo.luns_per_chnl;
 | 
			
		||||
	int nr_chnls_mod = nr_luns % dev->geo.luns_per_chnl;
 | 
			
		||||
	int bch = lun_begin / dev->geo.luns_per_chnl;
 | 
			
		||||
	int blun = lun_begin % dev->geo.luns_per_chnl;
 | 
			
		||||
	int lunid = 0;
 | 
			
		||||
	int lun_balanced = 1;
 | 
			
		||||
	int prev_nr_luns;
 | 
			
		||||
	int i, j;
 | 
			
		||||
 | 
			
		||||
	nr_chnls = nr_luns / dev->geo.luns_per_chnl;
 | 
			
		||||
	nr_chnls = (nr_chnls_mod == 0) ? nr_chnls : nr_chnls + 1;
 | 
			
		||||
 | 
			
		||||
	dev_map = kmalloc(sizeof(struct nvm_dev_map), GFP_KERNEL);
 | 
			
		||||
	if (!dev_map)
 | 
			
		||||
		goto err_dev;
 | 
			
		||||
 | 
			
		||||
	dev_map->chnls = kcalloc(nr_chnls, sizeof(struct nvm_ch_map),
 | 
			
		||||
								GFP_KERNEL);
 | 
			
		||||
	if (!dev_map->chnls)
 | 
			
		||||
		goto err_chnls;
 | 
			
		||||
 | 
			
		||||
	luns = kcalloc(nr_luns, sizeof(struct ppa_addr), GFP_KERNEL);
 | 
			
		||||
	if (!luns)
 | 
			
		||||
		goto err_luns;
 | 
			
		||||
 | 
			
		||||
	prev_nr_luns = (luns_left > dev->geo.luns_per_chnl) ?
 | 
			
		||||
					dev->geo.luns_per_chnl : luns_left;
 | 
			
		||||
	for (i = 0; i < nr_chnls; i++) {
 | 
			
		||||
		struct nvm_ch_map *ch_rmap = &dev_rmap->chnls[i + bch];
 | 
			
		||||
		int *lun_roffs = ch_rmap->lun_offs;
 | 
			
		||||
		struct nvm_ch_map *ch_map = &dev_map->chnls[i];
 | 
			
		||||
		int *lun_offs;
 | 
			
		||||
		int luns_in_chnl = (luns_left > dev->geo.luns_per_chnl) ?
 | 
			
		||||
					dev->geo.luns_per_chnl : luns_left;
 | 
			
		||||
 | 
			
		||||
		if (lun_balanced && prev_nr_luns != luns_in_chnl)
 | 
			
		||||
			lun_balanced = 0;
 | 
			
		||||
 | 
			
		||||
		ch_map->ch_off = ch_rmap->ch_off = bch;
 | 
			
		||||
		ch_map->nr_luns = luns_in_chnl;
 | 
			
		||||
 | 
			
		||||
		lun_offs = kcalloc(luns_in_chnl, sizeof(int), GFP_KERNEL);
 | 
			
		||||
		if (!lun_offs)
 | 
			
		||||
			goto err_ch;
 | 
			
		||||
 | 
			
		||||
		for (j = 0; j < luns_in_chnl; j++) {
 | 
			
		||||
			luns[lunid].ppa = 0;
 | 
			
		||||
			luns[lunid].g.ch = i;
 | 
			
		||||
			luns[lunid++].g.lun = j;
 | 
			
		||||
 | 
			
		||||
			lun_offs[j] = blun;
 | 
			
		||||
			lun_roffs[j + blun] = blun;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ch_map->lun_offs = lun_offs;
 | 
			
		||||
 | 
			
		||||
		/* when starting a new channel, lun offset is reset */
 | 
			
		||||
		blun = 0;
 | 
			
		||||
		luns_left -= luns_in_chnl;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dev_map->nr_chnls = nr_chnls;
 | 
			
		||||
 | 
			
		||||
	tgt_dev = kmalloc(sizeof(struct nvm_tgt_dev), GFP_KERNEL);
 | 
			
		||||
	if (!tgt_dev)
 | 
			
		||||
		goto err_ch;
 | 
			
		||||
 | 
			
		||||
	memcpy(&tgt_dev->geo, &dev->geo, sizeof(struct nvm_geo));
 | 
			
		||||
	/* Target device only owns a portion of the physical device */
 | 
			
		||||
	tgt_dev->geo.nr_chnls = nr_chnls;
 | 
			
		||||
	tgt_dev->geo.nr_luns = nr_luns;
 | 
			
		||||
	tgt_dev->geo.luns_per_chnl = (lun_balanced) ? prev_nr_luns : -1;
 | 
			
		||||
	tgt_dev->total_secs = nr_luns * tgt_dev->geo.sec_per_lun;
 | 
			
		||||
	tgt_dev->q = dev->q;
 | 
			
		||||
	tgt_dev->map = dev_map;
 | 
			
		||||
	tgt_dev->luns = luns;
 | 
			
		||||
	memcpy(&tgt_dev->identity, &dev->identity, sizeof(struct nvm_id));
 | 
			
		||||
 | 
			
		||||
	tgt_dev->parent = dev;
 | 
			
		||||
 | 
			
		||||
	return tgt_dev;
 | 
			
		||||
err_ch:
 | 
			
		||||
	while (--i > 0)
 | 
			
		||||
		kfree(dev_map->chnls[i].lun_offs);
 | 
			
		||||
	kfree(luns);
 | 
			
		||||
err_luns:
 | 
			
		||||
	kfree(dev_map->chnls);
 | 
			
		||||
err_chnls:
 | 
			
		||||
	kfree(dev_map);
 | 
			
		||||
err_dev:
 | 
			
		||||
	return tgt_dev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct block_device_operations nvm_fops = {
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int nvm_create_tgt(struct nvm_dev *dev, struct nvm_ioctl_create *create)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_ioctl_create_simple *s = &create->conf.s;
 | 
			
		||||
	struct request_queue *tqueue;
 | 
			
		||||
	struct gendisk *tdisk;
 | 
			
		||||
	struct nvm_tgt_type *tt;
 | 
			
		||||
	struct nvm_target *t;
 | 
			
		||||
	struct nvm_tgt_dev *tgt_dev;
 | 
			
		||||
	void *targetdata;
 | 
			
		||||
 | 
			
		||||
	tt = nvm_find_target_type(create->tgttype, 1);
 | 
			
		||||
	if (!tt) {
 | 
			
		||||
		pr_err("nvm: target type %s not found\n", create->tgttype);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&dev->mlock);
 | 
			
		||||
	t = nvm_find_target(dev, create->tgtname);
 | 
			
		||||
	if (t) {
 | 
			
		||||
		pr_err("nvm: target name already exists.\n");
 | 
			
		||||
		mutex_unlock(&dev->mlock);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
	mutex_unlock(&dev->mlock);
 | 
			
		||||
 | 
			
		||||
	if (nvm_reserve_luns(dev, s->lun_begin, s->lun_end))
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	t = kmalloc(sizeof(struct nvm_target), GFP_KERNEL);
 | 
			
		||||
	if (!t)
 | 
			
		||||
		goto err_reserve;
 | 
			
		||||
 | 
			
		||||
	tgt_dev = nvm_create_tgt_dev(dev, s->lun_begin, s->lun_end);
 | 
			
		||||
	if (!tgt_dev) {
 | 
			
		||||
		pr_err("nvm: could not create target device\n");
 | 
			
		||||
		goto err_t;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tqueue = blk_alloc_queue_node(GFP_KERNEL, dev->q->node);
 | 
			
		||||
	if (!tqueue)
 | 
			
		||||
		goto err_dev;
 | 
			
		||||
	blk_queue_make_request(tqueue, tt->make_rq);
 | 
			
		||||
 | 
			
		||||
	tdisk = alloc_disk(0);
 | 
			
		||||
	if (!tdisk)
 | 
			
		||||
		goto err_queue;
 | 
			
		||||
 | 
			
		||||
	sprintf(tdisk->disk_name, "%s", create->tgtname);
 | 
			
		||||
	tdisk->flags = GENHD_FL_EXT_DEVT;
 | 
			
		||||
	tdisk->major = 0;
 | 
			
		||||
	tdisk->first_minor = 0;
 | 
			
		||||
	tdisk->fops = &nvm_fops;
 | 
			
		||||
	tdisk->queue = tqueue;
 | 
			
		||||
 | 
			
		||||
	targetdata = tt->init(tgt_dev, tdisk);
 | 
			
		||||
	if (IS_ERR(targetdata))
 | 
			
		||||
		goto err_init;
 | 
			
		||||
 | 
			
		||||
	tdisk->private_data = targetdata;
 | 
			
		||||
	tqueue->queuedata = targetdata;
 | 
			
		||||
 | 
			
		||||
	blk_queue_max_hw_sectors(tqueue, 8 * dev->ops->max_phys_sect);
 | 
			
		||||
 | 
			
		||||
	set_capacity(tdisk, tt->capacity(targetdata));
 | 
			
		||||
	add_disk(tdisk);
 | 
			
		||||
 | 
			
		||||
	t->type = tt;
 | 
			
		||||
	t->disk = tdisk;
 | 
			
		||||
	t->dev = tgt_dev;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&dev->mlock);
 | 
			
		||||
	list_add_tail(&t->list, &dev->targets);
 | 
			
		||||
	mutex_unlock(&dev->mlock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
err_init:
 | 
			
		||||
	put_disk(tdisk);
 | 
			
		||||
err_queue:
 | 
			
		||||
	blk_cleanup_queue(tqueue);
 | 
			
		||||
err_dev:
 | 
			
		||||
	kfree(tgt_dev);
 | 
			
		||||
err_t:
 | 
			
		||||
	kfree(t);
 | 
			
		||||
err_reserve:
 | 
			
		||||
	nvm_release_luns_err(dev, s->lun_begin, s->lun_end);
 | 
			
		||||
	return -ENOMEM;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void __nvm_remove_target(struct nvm_target *t)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_tgt_type *tt = t->type;
 | 
			
		||||
	struct gendisk *tdisk = t->disk;
 | 
			
		||||
	struct request_queue *q = tdisk->queue;
 | 
			
		||||
 | 
			
		||||
	del_gendisk(tdisk);
 | 
			
		||||
	blk_cleanup_queue(q);
 | 
			
		||||
 | 
			
		||||
	if (tt->exit)
 | 
			
		||||
		tt->exit(tdisk->private_data);
 | 
			
		||||
 | 
			
		||||
	nvm_remove_tgt_dev(t->dev);
 | 
			
		||||
	put_disk(tdisk);
 | 
			
		||||
 | 
			
		||||
	list_del(&t->list);
 | 
			
		||||
	kfree(t);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * nvm_remove_tgt - Removes a target from the media manager
 | 
			
		||||
 * @dev:	device
 | 
			
		||||
 * @remove:	ioctl structure with target name to remove.
 | 
			
		||||
 *
 | 
			
		||||
 * Returns:
 | 
			
		||||
 * 0: on success
 | 
			
		||||
 * 1: on not found
 | 
			
		||||
 * <0: on error
 | 
			
		||||
 */
 | 
			
		||||
static int nvm_remove_tgt(struct nvm_dev *dev, struct nvm_ioctl_remove *remove)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_target *t;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&dev->mlock);
 | 
			
		||||
	t = nvm_find_target(dev, remove->tgtname);
 | 
			
		||||
	if (!t) {
 | 
			
		||||
		mutex_unlock(&dev->mlock);
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	__nvm_remove_target(t);
 | 
			
		||||
	mutex_unlock(&dev->mlock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_register_map(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev_map *rmap;
 | 
			
		||||
	int i, j;
 | 
			
		||||
 | 
			
		||||
	rmap = kmalloc(sizeof(struct nvm_dev_map), GFP_KERNEL);
 | 
			
		||||
	if (!rmap)
 | 
			
		||||
		goto err_rmap;
 | 
			
		||||
 | 
			
		||||
	rmap->chnls = kcalloc(dev->geo.nr_chnls, sizeof(struct nvm_ch_map),
 | 
			
		||||
								GFP_KERNEL);
 | 
			
		||||
	if (!rmap->chnls)
 | 
			
		||||
		goto err_chnls;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < dev->geo.nr_chnls; i++) {
 | 
			
		||||
		struct nvm_ch_map *ch_rmap;
 | 
			
		||||
		int *lun_roffs;
 | 
			
		||||
		int luns_in_chnl = dev->geo.luns_per_chnl;
 | 
			
		||||
 | 
			
		||||
		ch_rmap = &rmap->chnls[i];
 | 
			
		||||
 | 
			
		||||
		ch_rmap->ch_off = -1;
 | 
			
		||||
		ch_rmap->nr_luns = luns_in_chnl;
 | 
			
		||||
 | 
			
		||||
		lun_roffs = kcalloc(luns_in_chnl, sizeof(int), GFP_KERNEL);
 | 
			
		||||
		if (!lun_roffs)
 | 
			
		||||
			goto err_ch;
 | 
			
		||||
 | 
			
		||||
		for (j = 0; j < luns_in_chnl; j++)
 | 
			
		||||
			lun_roffs[j] = -1;
 | 
			
		||||
 | 
			
		||||
		ch_rmap->lun_offs = lun_roffs;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dev->rmap = rmap;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
err_ch:
 | 
			
		||||
	while (--i >= 0)
 | 
			
		||||
		kfree(rmap->chnls[i].lun_offs);
 | 
			
		||||
err_chnls:
 | 
			
		||||
	kfree(rmap);
 | 
			
		||||
err_rmap:
 | 
			
		||||
	return -ENOMEM;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_map_to_dev(struct nvm_tgt_dev *tgt_dev, struct ppa_addr *p)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev_map *dev_map = tgt_dev->map;
 | 
			
		||||
	struct nvm_ch_map *ch_map = &dev_map->chnls[p->g.ch];
 | 
			
		||||
	int lun_off = ch_map->lun_offs[p->g.lun];
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct nvm_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	struct nvm_ch_map *ch_rmap;
 | 
			
		||||
	int lun_roff;
 | 
			
		||||
 | 
			
		||||
	p->g.ch += ch_map->ch_off;
 | 
			
		||||
	p->g.lun += lun_off;
 | 
			
		||||
 | 
			
		||||
	ch_rmap = &dev_rmap->chnls[p->g.ch];
 | 
			
		||||
	lun_roff = ch_rmap->lun_offs[p->g.lun];
 | 
			
		||||
 | 
			
		||||
	if (unlikely(ch_rmap->ch_off < 0 || lun_roff < 0)) {
 | 
			
		||||
		pr_err("nvm: corrupted device partition table\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_map_to_tgt(struct nvm_tgt_dev *tgt_dev, struct ppa_addr *p)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct nvm_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	struct nvm_ch_map *ch_rmap = &dev_rmap->chnls[p->g.ch];
 | 
			
		||||
	int lun_roff = ch_rmap->lun_offs[p->g.lun];
 | 
			
		||||
 | 
			
		||||
	p->g.ch -= ch_rmap->ch_off;
 | 
			
		||||
	p->g.lun -= lun_roff;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_trans_rq(struct nvm_tgt_dev *tgt_dev, struct nvm_rq *rqd,
 | 
			
		||||
			int flag)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
	int ret;
 | 
			
		||||
 | 
			
		||||
	if (rqd->nr_ppas == 1) {
 | 
			
		||||
		if (flag == TRANS_TGT_TO_DEV)
 | 
			
		||||
			return nvm_map_to_dev(tgt_dev, &rqd->ppa_addr);
 | 
			
		||||
		else
 | 
			
		||||
			return nvm_map_to_tgt(tgt_dev, &rqd->ppa_addr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < rqd->nr_ppas; i++) {
 | 
			
		||||
		if (flag == TRANS_TGT_TO_DEV)
 | 
			
		||||
			ret = nvm_map_to_dev(tgt_dev, &rqd->ppa_list[i]);
 | 
			
		||||
		else
 | 
			
		||||
			ret = nvm_map_to_tgt(tgt_dev, &rqd->ppa_list[i]);
 | 
			
		||||
 | 
			
		||||
		if (ret)
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct ppa_addr nvm_trans_ppa(struct nvm_tgt_dev *tgt_dev,
 | 
			
		||||
				     struct ppa_addr p, int dir)
 | 
			
		||||
{
 | 
			
		||||
	struct ppa_addr ppa = p;
 | 
			
		||||
 | 
			
		||||
	if (dir == TRANS_TGT_TO_DEV)
 | 
			
		||||
		nvm_map_to_dev(tgt_dev, &ppa);
 | 
			
		||||
	else
 | 
			
		||||
		nvm_map_to_tgt(tgt_dev, &ppa);
 | 
			
		||||
 | 
			
		||||
	return ppa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nvm_part_to_tgt(struct nvm_dev *dev, sector_t *entries,
 | 
			
		||||
		     int len)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct nvm_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	u64 i;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < len; i++) {
 | 
			
		||||
		struct nvm_ch_map *ch_rmap;
 | 
			
		||||
		int *lun_roffs;
 | 
			
		||||
		struct ppa_addr gaddr;
 | 
			
		||||
		u64 pba = le64_to_cpu(entries[i]);
 | 
			
		||||
		int off;
 | 
			
		||||
		u64 diff;
 | 
			
		||||
 | 
			
		||||
		if (!pba)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		gaddr = linear_to_generic_addr(geo, pba);
 | 
			
		||||
		ch_rmap = &dev_rmap->chnls[gaddr.g.ch];
 | 
			
		||||
		lun_roffs = ch_rmap->lun_offs;
 | 
			
		||||
 | 
			
		||||
		off = gaddr.g.ch * geo->luns_per_chnl + gaddr.g.lun;
 | 
			
		||||
 | 
			
		||||
		diff = ((ch_rmap->ch_off * geo->luns_per_chnl) +
 | 
			
		||||
				(lun_roffs[gaddr.g.lun])) * geo->sec_per_lun;
 | 
			
		||||
 | 
			
		||||
		entries[i] -= cpu_to_le64(diff);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_part_to_tgt);
 | 
			
		||||
 | 
			
		||||
struct nvm_tgt_type *nvm_find_target_type(const char *name, int lock)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_tgt_type *tmp, *tt = NULL;
 | 
			
		||||
@ -92,78 +574,6 @@ void nvm_dev_dma_free(struct nvm_dev *dev, void *addr, dma_addr_t dma_handler)
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_dev_dma_free);
 | 
			
		||||
 | 
			
		||||
static struct nvmm_type *nvm_find_mgr_type(const char *name)
 | 
			
		||||
{
 | 
			
		||||
	struct nvmm_type *mt;
 | 
			
		||||
 | 
			
		||||
	list_for_each_entry(mt, &nvm_mgrs, list)
 | 
			
		||||
		if (!strcmp(name, mt->name))
 | 
			
		||||
			return mt;
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct nvmm_type *nvm_init_mgr(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	struct nvmm_type *mt;
 | 
			
		||||
	int ret;
 | 
			
		||||
 | 
			
		||||
	lockdep_assert_held(&nvm_lock);
 | 
			
		||||
 | 
			
		||||
	list_for_each_entry(mt, &nvm_mgrs, list) {
 | 
			
		||||
		if (strncmp(dev->sb.mmtype, mt->name, NVM_MMTYPE_LEN))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		ret = mt->register_mgr(dev);
 | 
			
		||||
		if (ret < 0) {
 | 
			
		||||
			pr_err("nvm: media mgr failed to init (%d) on dev %s\n",
 | 
			
		||||
								ret, dev->name);
 | 
			
		||||
			return NULL; /* initialization failed */
 | 
			
		||||
		} else if (ret > 0)
 | 
			
		||||
			return mt;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int nvm_register_mgr(struct nvmm_type *mt)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev;
 | 
			
		||||
	int ret = 0;
 | 
			
		||||
 | 
			
		||||
	down_write(&nvm_lock);
 | 
			
		||||
	if (nvm_find_mgr_type(mt->name)) {
 | 
			
		||||
		ret = -EEXIST;
 | 
			
		||||
		goto finish;
 | 
			
		||||
	} else {
 | 
			
		||||
		list_add(&mt->list, &nvm_mgrs);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* try to register media mgr if any device have none configured */
 | 
			
		||||
	list_for_each_entry(dev, &nvm_devices, devices) {
 | 
			
		||||
		if (dev->mt)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		dev->mt = nvm_init_mgr(dev);
 | 
			
		||||
	}
 | 
			
		||||
finish:
 | 
			
		||||
	up_write(&nvm_lock);
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_register_mgr);
 | 
			
		||||
 | 
			
		||||
void nvm_unregister_mgr(struct nvmm_type *mt)
 | 
			
		||||
{
 | 
			
		||||
	if (!mt)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	down_write(&nvm_lock);
 | 
			
		||||
	list_del(&mt->list);
 | 
			
		||||
	up_write(&nvm_lock);
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_unregister_mgr);
 | 
			
		||||
 | 
			
		||||
static struct nvm_dev *nvm_find_nvm_dev(const char *name)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev;
 | 
			
		||||
@ -183,13 +593,13 @@ static void nvm_tgt_generic_to_addr_mode(struct nvm_tgt_dev *tgt_dev,
 | 
			
		||||
 | 
			
		||||
	if (rqd->nr_ppas > 1) {
 | 
			
		||||
		for (i = 0; i < rqd->nr_ppas; i++) {
 | 
			
		||||
			rqd->ppa_list[i] = dev->mt->trans_ppa(tgt_dev,
 | 
			
		||||
			rqd->ppa_list[i] = nvm_trans_ppa(tgt_dev,
 | 
			
		||||
					rqd->ppa_list[i], TRANS_TGT_TO_DEV);
 | 
			
		||||
			rqd->ppa_list[i] = generic_to_dev_addr(dev,
 | 
			
		||||
							rqd->ppa_list[i]);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		rqd->ppa_addr = dev->mt->trans_ppa(tgt_dev, rqd->ppa_addr,
 | 
			
		||||
		rqd->ppa_addr = nvm_trans_ppa(tgt_dev, rqd->ppa_addr,
 | 
			
		||||
						TRANS_TGT_TO_DEV);
 | 
			
		||||
		rqd->ppa_addr = generic_to_dev_addr(dev, rqd->ppa_addr);
 | 
			
		||||
	}
 | 
			
		||||
@ -242,7 +652,7 @@ int nvm_set_tgt_bb_tbl(struct nvm_tgt_dev *tgt_dev, struct ppa_addr *ppas,
 | 
			
		||||
	ret = dev->ops->set_bb_tbl(dev, &rqd.ppa_addr, rqd.nr_ppas, type);
 | 
			
		||||
	nvm_free_rqd_ppalist(dev, &rqd);
 | 
			
		||||
	if (ret) {
 | 
			
		||||
		pr_err("nvm: sysblk failed bb mark\n");
 | 
			
		||||
		pr_err("nvm: failed bb mark\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -262,15 +672,23 @@ int nvm_submit_io(struct nvm_tgt_dev *tgt_dev, struct nvm_rq *rqd)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
 | 
			
		||||
	return dev->mt->submit_io(tgt_dev, rqd);
 | 
			
		||||
	if (!dev->ops->submit_io)
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	/* Convert address space */
 | 
			
		||||
	nvm_generic_to_addr_mode(dev, rqd);
 | 
			
		||||
 | 
			
		||||
	rqd->dev = tgt_dev;
 | 
			
		||||
	return dev->ops->submit_io(dev, rqd);
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_submit_io);
 | 
			
		||||
 | 
			
		||||
int nvm_erase_blk(struct nvm_tgt_dev *tgt_dev, struct ppa_addr *p, int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	/* Convert address space */
 | 
			
		||||
	nvm_map_to_dev(tgt_dev, p);
 | 
			
		||||
 | 
			
		||||
	return dev->mt->erase_blk(tgt_dev, p, flags);
 | 
			
		||||
	return nvm_erase_ppa(tgt_dev->parent, p, 1, flags);
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_erase_blk);
 | 
			
		||||
 | 
			
		||||
@ -289,16 +707,65 @@ EXPORT_SYMBOL(nvm_get_l2p_tbl);
 | 
			
		||||
int nvm_get_area(struct nvm_tgt_dev *tgt_dev, sector_t *lba, sector_t len)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct nvm_area *area, *prev, *next;
 | 
			
		||||
	sector_t begin = 0;
 | 
			
		||||
	sector_t max_sectors = (geo->sec_size * dev->total_secs) >> 9;
 | 
			
		||||
 | 
			
		||||
	return dev->mt->get_area(dev, lba, len);
 | 
			
		||||
	if (len > max_sectors)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	area = kmalloc(sizeof(struct nvm_area), GFP_KERNEL);
 | 
			
		||||
	if (!area)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	prev = NULL;
 | 
			
		||||
 | 
			
		||||
	spin_lock(&dev->lock);
 | 
			
		||||
	list_for_each_entry(next, &dev->area_list, list) {
 | 
			
		||||
		if (begin + len > next->begin) {
 | 
			
		||||
			begin = next->end;
 | 
			
		||||
			prev = next;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ((begin + len) > max_sectors) {
 | 
			
		||||
		spin_unlock(&dev->lock);
 | 
			
		||||
		kfree(area);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	area->begin = *lba = begin;
 | 
			
		||||
	area->end = begin + len;
 | 
			
		||||
 | 
			
		||||
	if (prev) /* insert into sorted order */
 | 
			
		||||
		list_add(&area->list, &prev->list);
 | 
			
		||||
	else
 | 
			
		||||
		list_add(&area->list, &dev->area_list);
 | 
			
		||||
	spin_unlock(&dev->lock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_get_area);
 | 
			
		||||
 | 
			
		||||
void nvm_put_area(struct nvm_tgt_dev *tgt_dev, sector_t lba)
 | 
			
		||||
void nvm_put_area(struct nvm_tgt_dev *tgt_dev, sector_t begin)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct nvm_area *area;
 | 
			
		||||
 | 
			
		||||
	dev->mt->put_area(dev, lba);
 | 
			
		||||
	spin_lock(&dev->lock);
 | 
			
		||||
	list_for_each_entry(area, &dev->area_list, list) {
 | 
			
		||||
		if (area->begin != begin)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		list_del(&area->list);
 | 
			
		||||
		spin_unlock(&dev->lock);
 | 
			
		||||
		kfree(area);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	spin_unlock(&dev->lock);
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_put_area);
 | 
			
		||||
 | 
			
		||||
@ -409,8 +876,15 @@ EXPORT_SYMBOL(nvm_erase_ppa);
 | 
			
		||||
 | 
			
		||||
void nvm_end_io(struct nvm_rq *rqd, int error)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_tgt_dev *tgt_dev = rqd->dev;
 | 
			
		||||
	struct nvm_tgt_instance *ins = rqd->ins;
 | 
			
		||||
 | 
			
		||||
	/* Convert address space */
 | 
			
		||||
	if (tgt_dev)
 | 
			
		||||
		nvm_trans_rq(tgt_dev, rqd, TRANS_DEV_TO_TGT);
 | 
			
		||||
 | 
			
		||||
	rqd->error = error;
 | 
			
		||||
	rqd->end_io(rqd);
 | 
			
		||||
	ins->tt->end_io(rqd);
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_end_io);
 | 
			
		||||
 | 
			
		||||
@ -570,10 +1044,9 @@ EXPORT_SYMBOL(nvm_get_bb_tbl);
 | 
			
		||||
int nvm_get_tgt_bb_tbl(struct nvm_tgt_dev *tgt_dev, struct ppa_addr ppa,
 | 
			
		||||
		       u8 *blks)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	ppa = nvm_trans_ppa(tgt_dev, ppa, TRANS_TGT_TO_DEV);
 | 
			
		||||
 | 
			
		||||
	ppa = dev->mt->trans_ppa(tgt_dev, ppa, TRANS_TGT_TO_DEV);
 | 
			
		||||
	return nvm_get_bb_tbl(dev, ppa, blks);
 | 
			
		||||
	return nvm_get_bb_tbl(tgt_dev->parent, ppa, blks);
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_get_tgt_bb_tbl);
 | 
			
		||||
 | 
			
		||||
@ -691,36 +1164,31 @@ static int nvm_core_init(struct nvm_dev *dev)
 | 
			
		||||
		goto err_fmtype;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	INIT_LIST_HEAD(&dev->area_list);
 | 
			
		||||
	INIT_LIST_HEAD(&dev->targets);
 | 
			
		||||
	mutex_init(&dev->mlock);
 | 
			
		||||
	spin_lock_init(&dev->lock);
 | 
			
		||||
 | 
			
		||||
	blk_queue_logical_block_size(dev->q, geo->sec_size);
 | 
			
		||||
	ret = nvm_register_map(dev);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_fmtype;
 | 
			
		||||
 | 
			
		||||
	blk_queue_logical_block_size(dev->q, geo->sec_size);
 | 
			
		||||
	return 0;
 | 
			
		||||
err_fmtype:
 | 
			
		||||
	kfree(dev->lun_map);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nvm_free_mgr(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	if (!dev->mt)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	dev->mt->unregister_mgr(dev);
 | 
			
		||||
	dev->mt = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nvm_free(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	if (!dev)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	nvm_free_mgr(dev);
 | 
			
		||||
 | 
			
		||||
	if (dev->dma_pool)
 | 
			
		||||
		dev->ops->destroy_dma_pool(dev->dma_pool);
 | 
			
		||||
 | 
			
		||||
	kfree(dev->rmap);
 | 
			
		||||
	kfree(dev->lptbl);
 | 
			
		||||
	kfree(dev->lun_map);
 | 
			
		||||
	kfree(dev);
 | 
			
		||||
@ -731,9 +1199,6 @@ static int nvm_init(struct nvm_dev *dev)
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	int ret = -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (!dev->q || !dev->ops)
 | 
			
		||||
		return ret;
 | 
			
		||||
 | 
			
		||||
	if (dev->ops->identity(dev, &dev->identity)) {
 | 
			
		||||
		pr_err("nvm: device could not be identified\n");
 | 
			
		||||
		goto err;
 | 
			
		||||
@ -779,49 +1244,50 @@ int nvm_register(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	int ret;
 | 
			
		||||
 | 
			
		||||
	ret = nvm_init(dev);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_init;
 | 
			
		||||
	if (!dev->q || !dev->ops)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (dev->ops->max_phys_sect > 256) {
 | 
			
		||||
		pr_info("nvm: max sectors supported is 256.\n");
 | 
			
		||||
		ret = -EINVAL;
 | 
			
		||||
		goto err_init;
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (dev->ops->max_phys_sect > 1) {
 | 
			
		||||
		dev->dma_pool = dev->ops->create_dma_pool(dev, "ppalist");
 | 
			
		||||
		if (!dev->dma_pool) {
 | 
			
		||||
			pr_err("nvm: could not create dma pool\n");
 | 
			
		||||
			ret = -ENOMEM;
 | 
			
		||||
			goto err_init;
 | 
			
		||||
			return -ENOMEM;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (dev->identity.cap & NVM_ID_DCAP_BBLKMGMT) {
 | 
			
		||||
		ret = nvm_get_sysblock(dev, &dev->sb);
 | 
			
		||||
		if (!ret)
 | 
			
		||||
			pr_err("nvm: device not initialized.\n");
 | 
			
		||||
		else if (ret < 0)
 | 
			
		||||
			pr_err("nvm: err (%d) on device initialization\n", ret);
 | 
			
		||||
	}
 | 
			
		||||
	ret = nvm_init(dev);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_init;
 | 
			
		||||
 | 
			
		||||
	/* register device with a supported media manager */
 | 
			
		||||
	down_write(&nvm_lock);
 | 
			
		||||
	if (ret > 0)
 | 
			
		||||
		dev->mt = nvm_init_mgr(dev);
 | 
			
		||||
	list_add(&dev->devices, &nvm_devices);
 | 
			
		||||
	up_write(&nvm_lock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
err_init:
 | 
			
		||||
	kfree(dev->lun_map);
 | 
			
		||||
	dev->ops->destroy_dma_pool(dev->dma_pool);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_register);
 | 
			
		||||
 | 
			
		||||
void nvm_unregister(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_target *t, *tmp;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&dev->mlock);
 | 
			
		||||
	list_for_each_entry_safe(t, tmp, &dev->targets, list) {
 | 
			
		||||
		if (t->dev->parent != dev)
 | 
			
		||||
			continue;
 | 
			
		||||
		__nvm_remove_target(t);
 | 
			
		||||
	}
 | 
			
		||||
	mutex_unlock(&dev->mlock);
 | 
			
		||||
 | 
			
		||||
	down_write(&nvm_lock);
 | 
			
		||||
	list_del(&dev->devices);
 | 
			
		||||
	up_write(&nvm_lock);
 | 
			
		||||
@ -844,11 +1310,6 @@ static int __nvm_configure_create(struct nvm_ioctl_create *create)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!dev->mt) {
 | 
			
		||||
		pr_info("nvm: device has no media manager registered.\n");
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (create->conf.type != NVM_CONFIG_TYPE_SIMPLE) {
 | 
			
		||||
		pr_err("nvm: config type not valid\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
@ -861,7 +1322,7 @@ static int __nvm_configure_create(struct nvm_ioctl_create *create)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return dev->mt->create_tgt(dev, create);
 | 
			
		||||
	return nvm_create_tgt(dev, create);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static long nvm_ioctl_info(struct file *file, void __user *arg)
 | 
			
		||||
@ -923,16 +1384,14 @@ static long nvm_ioctl_get_devices(struct file *file, void __user *arg)
 | 
			
		||||
		struct nvm_ioctl_device_info *info = &devices->info[i];
 | 
			
		||||
 | 
			
		||||
		sprintf(info->devname, "%s", dev->name);
 | 
			
		||||
		if (dev->mt) {
 | 
			
		||||
			info->bmversion[0] = dev->mt->version[0];
 | 
			
		||||
			info->bmversion[1] = dev->mt->version[1];
 | 
			
		||||
			info->bmversion[2] = dev->mt->version[2];
 | 
			
		||||
			sprintf(info->bmname, "%s", dev->mt->name);
 | 
			
		||||
		} else {
 | 
			
		||||
			sprintf(info->bmname, "none");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* kept for compatibility */
 | 
			
		||||
		info->bmversion[0] = 1;
 | 
			
		||||
		info->bmversion[1] = 0;
 | 
			
		||||
		info->bmversion[2] = 0;
 | 
			
		||||
		sprintf(info->bmname, "%s", "gennvm");
 | 
			
		||||
		i++;
 | 
			
		||||
 | 
			
		||||
		if (i > 31) {
 | 
			
		||||
			pr_err("nvm: max 31 devices can be reported.\n");
 | 
			
		||||
			break;
 | 
			
		||||
@ -994,7 +1453,7 @@ static long nvm_ioctl_dev_remove(struct file *file, void __user *arg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	list_for_each_entry(dev, &nvm_devices, devices) {
 | 
			
		||||
		ret = dev->mt->remove_tgt(dev, &remove);
 | 
			
		||||
		ret = nvm_remove_tgt(dev, &remove);
 | 
			
		||||
		if (!ret)
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
@ -1002,47 +1461,7 @@ static long nvm_ioctl_dev_remove(struct file *file, void __user *arg)
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nvm_setup_nvm_sb_info(struct nvm_sb_info *info)
 | 
			
		||||
{
 | 
			
		||||
	info->seqnr = 1;
 | 
			
		||||
	info->erase_cnt = 0;
 | 
			
		||||
	info->version = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static long __nvm_ioctl_dev_init(struct nvm_ioctl_dev_init *init)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev;
 | 
			
		||||
	struct nvm_sb_info info;
 | 
			
		||||
	int ret;
 | 
			
		||||
 | 
			
		||||
	down_write(&nvm_lock);
 | 
			
		||||
	dev = nvm_find_nvm_dev(init->dev);
 | 
			
		||||
	up_write(&nvm_lock);
 | 
			
		||||
	if (!dev) {
 | 
			
		||||
		pr_err("nvm: device not found\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nvm_setup_nvm_sb_info(&info);
 | 
			
		||||
 | 
			
		||||
	strncpy(info.mmtype, init->mmtype, NVM_MMTYPE_LEN);
 | 
			
		||||
	info.fs_ppa.ppa = -1;
 | 
			
		||||
 | 
			
		||||
	if (dev->identity.cap & NVM_ID_DCAP_BBLKMGMT) {
 | 
			
		||||
		ret = nvm_init_sysblock(dev, &info);
 | 
			
		||||
		if (ret)
 | 
			
		||||
			return ret;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	memcpy(&dev->sb, &info, sizeof(struct nvm_sb_info));
 | 
			
		||||
 | 
			
		||||
	down_write(&nvm_lock);
 | 
			
		||||
	dev->mt = nvm_init_mgr(dev);
 | 
			
		||||
	up_write(&nvm_lock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* kept for compatibility reasons */
 | 
			
		||||
static long nvm_ioctl_dev_init(struct file *file, void __user *arg)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_ioctl_dev_init init;
 | 
			
		||||
@ -1058,15 +1477,13 @@ static long nvm_ioctl_dev_init(struct file *file, void __user *arg)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	init.dev[DISK_NAME_LEN - 1] = '\0';
 | 
			
		||||
 | 
			
		||||
	return __nvm_ioctl_dev_init(&init);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Kept for compatibility reasons */
 | 
			
		||||
static long nvm_ioctl_dev_factory(struct file *file, void __user *arg)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_ioctl_dev_factory fact;
 | 
			
		||||
	struct nvm_dev *dev;
 | 
			
		||||
 | 
			
		||||
	if (!capable(CAP_SYS_ADMIN))
 | 
			
		||||
		return -EPERM;
 | 
			
		||||
@ -1079,19 +1496,6 @@ static long nvm_ioctl_dev_factory(struct file *file, void __user *arg)
 | 
			
		||||
	if (fact.flags & ~(NVM_FACTORY_NR_BITS - 1))
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	down_write(&nvm_lock);
 | 
			
		||||
	dev = nvm_find_nvm_dev(fact.dev);
 | 
			
		||||
	up_write(&nvm_lock);
 | 
			
		||||
	if (!dev) {
 | 
			
		||||
		pr_err("nvm: device not found\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nvm_free_mgr(dev);
 | 
			
		||||
 | 
			
		||||
	if (dev->identity.cap & NVM_ID_DCAP_BBLKMGMT)
 | 
			
		||||
		return nvm_dev_factory(dev, fact.flags);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,657 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 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.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
 | 
			
		||||
 * USA.
 | 
			
		||||
 *
 | 
			
		||||
 * Implementation of a general nvm manager for Open-Channel SSDs.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "gennvm.h"
 | 
			
		||||
 | 
			
		||||
static struct nvm_target *gen_find_target(struct gen_dev *gn, const char *name)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_target *tgt;
 | 
			
		||||
 | 
			
		||||
	list_for_each_entry(tgt, &gn->targets, list)
 | 
			
		||||
		if (!strcmp(name, tgt->disk->disk_name))
 | 
			
		||||
			return tgt;
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct block_device_operations gen_fops = {
 | 
			
		||||
	.owner		= THIS_MODULE,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int gen_reserve_luns(struct nvm_dev *dev, struct nvm_target *t,
 | 
			
		||||
			    int lun_begin, int lun_end)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	for (i = lun_begin; i <= lun_end; i++) {
 | 
			
		||||
		if (test_and_set_bit(i, dev->lun_map)) {
 | 
			
		||||
			pr_err("nvm: lun %d already allocated\n", i);
 | 
			
		||||
			goto err;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
 | 
			
		||||
err:
 | 
			
		||||
	while (--i > lun_begin)
 | 
			
		||||
		clear_bit(i, dev->lun_map);
 | 
			
		||||
 | 
			
		||||
	return -EBUSY;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_release_luns_err(struct nvm_dev *dev, int lun_begin,
 | 
			
		||||
				 int lun_end)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	for (i = lun_begin; i <= lun_end; i++)
 | 
			
		||||
		WARN_ON(!test_and_clear_bit(i, dev->lun_map));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_remove_tgt_dev(struct nvm_tgt_dev *tgt_dev)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct gen_dev_map *dev_map = tgt_dev->map;
 | 
			
		||||
	int i, j;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < dev_map->nr_chnls; i++) {
 | 
			
		||||
		struct gen_ch_map *ch_map = &dev_map->chnls[i];
 | 
			
		||||
		int *lun_offs = ch_map->lun_offs;
 | 
			
		||||
		int ch = i + ch_map->ch_off;
 | 
			
		||||
 | 
			
		||||
		for (j = 0; j < ch_map->nr_luns; j++) {
 | 
			
		||||
			int lun = j + lun_offs[j];
 | 
			
		||||
			int lunid = (ch * dev->geo.luns_per_chnl) + lun;
 | 
			
		||||
 | 
			
		||||
			WARN_ON(!test_and_clear_bit(lunid, dev->lun_map));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		kfree(ch_map->lun_offs);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kfree(dev_map->chnls);
 | 
			
		||||
	kfree(dev_map);
 | 
			
		||||
	kfree(tgt_dev->luns);
 | 
			
		||||
	kfree(tgt_dev);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct nvm_tgt_dev *gen_create_tgt_dev(struct nvm_dev *dev,
 | 
			
		||||
					      int lun_begin, int lun_end)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_tgt_dev *tgt_dev = NULL;
 | 
			
		||||
	struct gen_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	struct gen_dev_map *dev_map;
 | 
			
		||||
	struct ppa_addr *luns;
 | 
			
		||||
	int nr_luns = lun_end - lun_begin + 1;
 | 
			
		||||
	int luns_left = nr_luns;
 | 
			
		||||
	int nr_chnls = nr_luns / dev->geo.luns_per_chnl;
 | 
			
		||||
	int nr_chnls_mod = nr_luns % dev->geo.luns_per_chnl;
 | 
			
		||||
	int bch = lun_begin / dev->geo.luns_per_chnl;
 | 
			
		||||
	int blun = lun_begin % dev->geo.luns_per_chnl;
 | 
			
		||||
	int lunid = 0;
 | 
			
		||||
	int lun_balanced = 1;
 | 
			
		||||
	int prev_nr_luns;
 | 
			
		||||
	int i, j;
 | 
			
		||||
 | 
			
		||||
	nr_chnls = nr_luns / dev->geo.luns_per_chnl;
 | 
			
		||||
	nr_chnls = (nr_chnls_mod == 0) ? nr_chnls : nr_chnls + 1;
 | 
			
		||||
 | 
			
		||||
	dev_map = kmalloc(sizeof(struct gen_dev_map), GFP_KERNEL);
 | 
			
		||||
	if (!dev_map)
 | 
			
		||||
		goto err_dev;
 | 
			
		||||
 | 
			
		||||
	dev_map->chnls = kcalloc(nr_chnls, sizeof(struct gen_ch_map),
 | 
			
		||||
								GFP_KERNEL);
 | 
			
		||||
	if (!dev_map->chnls)
 | 
			
		||||
		goto err_chnls;
 | 
			
		||||
 | 
			
		||||
	luns = kcalloc(nr_luns, sizeof(struct ppa_addr), GFP_KERNEL);
 | 
			
		||||
	if (!luns)
 | 
			
		||||
		goto err_luns;
 | 
			
		||||
 | 
			
		||||
	prev_nr_luns = (luns_left > dev->geo.luns_per_chnl) ?
 | 
			
		||||
					dev->geo.luns_per_chnl : luns_left;
 | 
			
		||||
	for (i = 0; i < nr_chnls; i++) {
 | 
			
		||||
		struct gen_ch_map *ch_rmap = &dev_rmap->chnls[i + bch];
 | 
			
		||||
		int *lun_roffs = ch_rmap->lun_offs;
 | 
			
		||||
		struct gen_ch_map *ch_map = &dev_map->chnls[i];
 | 
			
		||||
		int *lun_offs;
 | 
			
		||||
		int luns_in_chnl = (luns_left > dev->geo.luns_per_chnl) ?
 | 
			
		||||
					dev->geo.luns_per_chnl : luns_left;
 | 
			
		||||
 | 
			
		||||
		if (lun_balanced && prev_nr_luns != luns_in_chnl)
 | 
			
		||||
			lun_balanced = 0;
 | 
			
		||||
 | 
			
		||||
		ch_map->ch_off = ch_rmap->ch_off = bch;
 | 
			
		||||
		ch_map->nr_luns = luns_in_chnl;
 | 
			
		||||
 | 
			
		||||
		lun_offs = kcalloc(luns_in_chnl, sizeof(int), GFP_KERNEL);
 | 
			
		||||
		if (!lun_offs)
 | 
			
		||||
			goto err_ch;
 | 
			
		||||
 | 
			
		||||
		for (j = 0; j < luns_in_chnl; j++) {
 | 
			
		||||
			luns[lunid].ppa = 0;
 | 
			
		||||
			luns[lunid].g.ch = i;
 | 
			
		||||
			luns[lunid++].g.lun = j;
 | 
			
		||||
 | 
			
		||||
			lun_offs[j] = blun;
 | 
			
		||||
			lun_roffs[j + blun] = blun;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ch_map->lun_offs = lun_offs;
 | 
			
		||||
 | 
			
		||||
		/* when starting a new channel, lun offset is reset */
 | 
			
		||||
		blun = 0;
 | 
			
		||||
		luns_left -= luns_in_chnl;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dev_map->nr_chnls = nr_chnls;
 | 
			
		||||
 | 
			
		||||
	tgt_dev = kmalloc(sizeof(struct nvm_tgt_dev), GFP_KERNEL);
 | 
			
		||||
	if (!tgt_dev)
 | 
			
		||||
		goto err_ch;
 | 
			
		||||
 | 
			
		||||
	memcpy(&tgt_dev->geo, &dev->geo, sizeof(struct nvm_geo));
 | 
			
		||||
	/* Target device only owns a portion of the physical device */
 | 
			
		||||
	tgt_dev->geo.nr_chnls = nr_chnls;
 | 
			
		||||
	tgt_dev->geo.nr_luns = nr_luns;
 | 
			
		||||
	tgt_dev->geo.luns_per_chnl = (lun_balanced) ? prev_nr_luns : -1;
 | 
			
		||||
	tgt_dev->total_secs = nr_luns * tgt_dev->geo.sec_per_lun;
 | 
			
		||||
	tgt_dev->q = dev->q;
 | 
			
		||||
	tgt_dev->map = dev_map;
 | 
			
		||||
	tgt_dev->luns = luns;
 | 
			
		||||
	memcpy(&tgt_dev->identity, &dev->identity, sizeof(struct nvm_id));
 | 
			
		||||
 | 
			
		||||
	tgt_dev->parent = dev;
 | 
			
		||||
 | 
			
		||||
	return tgt_dev;
 | 
			
		||||
err_ch:
 | 
			
		||||
	while (--i > 0)
 | 
			
		||||
		kfree(dev_map->chnls[i].lun_offs);
 | 
			
		||||
	kfree(luns);
 | 
			
		||||
err_luns:
 | 
			
		||||
	kfree(dev_map->chnls);
 | 
			
		||||
err_chnls:
 | 
			
		||||
	kfree(dev_map);
 | 
			
		||||
err_dev:
 | 
			
		||||
	return tgt_dev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_create_tgt(struct nvm_dev *dev, struct nvm_ioctl_create *create)
 | 
			
		||||
{
 | 
			
		||||
	struct gen_dev *gn = dev->mp;
 | 
			
		||||
	struct nvm_ioctl_create_simple *s = &create->conf.s;
 | 
			
		||||
	struct request_queue *tqueue;
 | 
			
		||||
	struct gendisk *tdisk;
 | 
			
		||||
	struct nvm_tgt_type *tt;
 | 
			
		||||
	struct nvm_target *t;
 | 
			
		||||
	struct nvm_tgt_dev *tgt_dev;
 | 
			
		||||
	void *targetdata;
 | 
			
		||||
 | 
			
		||||
	tt = nvm_find_target_type(create->tgttype, 1);
 | 
			
		||||
	if (!tt) {
 | 
			
		||||
		pr_err("nvm: target type %s not found\n", create->tgttype);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&gn->lock);
 | 
			
		||||
	t = gen_find_target(gn, create->tgtname);
 | 
			
		||||
	if (t) {
 | 
			
		||||
		pr_err("nvm: target name already exists.\n");
 | 
			
		||||
		mutex_unlock(&gn->lock);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
	mutex_unlock(&gn->lock);
 | 
			
		||||
 | 
			
		||||
	t = kmalloc(sizeof(struct nvm_target), GFP_KERNEL);
 | 
			
		||||
	if (!t)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	if (gen_reserve_luns(dev, t, s->lun_begin, s->lun_end))
 | 
			
		||||
		goto err_t;
 | 
			
		||||
 | 
			
		||||
	tgt_dev = gen_create_tgt_dev(dev, s->lun_begin, s->lun_end);
 | 
			
		||||
	if (!tgt_dev) {
 | 
			
		||||
		pr_err("nvm: could not create target device\n");
 | 
			
		||||
		goto err_reserve;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tqueue = blk_alloc_queue_node(GFP_KERNEL, dev->q->node);
 | 
			
		||||
	if (!tqueue)
 | 
			
		||||
		goto err_dev;
 | 
			
		||||
	blk_queue_make_request(tqueue, tt->make_rq);
 | 
			
		||||
 | 
			
		||||
	tdisk = alloc_disk(0);
 | 
			
		||||
	if (!tdisk)
 | 
			
		||||
		goto err_queue;
 | 
			
		||||
 | 
			
		||||
	sprintf(tdisk->disk_name, "%s", create->tgtname);
 | 
			
		||||
	tdisk->flags = GENHD_FL_EXT_DEVT;
 | 
			
		||||
	tdisk->major = 0;
 | 
			
		||||
	tdisk->first_minor = 0;
 | 
			
		||||
	tdisk->fops = &gen_fops;
 | 
			
		||||
	tdisk->queue = tqueue;
 | 
			
		||||
 | 
			
		||||
	targetdata = tt->init(tgt_dev, tdisk);
 | 
			
		||||
	if (IS_ERR(targetdata))
 | 
			
		||||
		goto err_init;
 | 
			
		||||
 | 
			
		||||
	tdisk->private_data = targetdata;
 | 
			
		||||
	tqueue->queuedata = targetdata;
 | 
			
		||||
 | 
			
		||||
	blk_queue_max_hw_sectors(tqueue, 8 * dev->ops->max_phys_sect);
 | 
			
		||||
 | 
			
		||||
	set_capacity(tdisk, tt->capacity(targetdata));
 | 
			
		||||
	add_disk(tdisk);
 | 
			
		||||
 | 
			
		||||
	t->type = tt;
 | 
			
		||||
	t->disk = tdisk;
 | 
			
		||||
	t->dev = tgt_dev;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&gn->lock);
 | 
			
		||||
	list_add_tail(&t->list, &gn->targets);
 | 
			
		||||
	mutex_unlock(&gn->lock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
err_init:
 | 
			
		||||
	put_disk(tdisk);
 | 
			
		||||
err_queue:
 | 
			
		||||
	blk_cleanup_queue(tqueue);
 | 
			
		||||
err_dev:
 | 
			
		||||
	kfree(tgt_dev);
 | 
			
		||||
err_reserve:
 | 
			
		||||
	gen_release_luns_err(dev, s->lun_begin, s->lun_end);
 | 
			
		||||
err_t:
 | 
			
		||||
	kfree(t);
 | 
			
		||||
	return -ENOMEM;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void __gen_remove_target(struct nvm_target *t)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_tgt_type *tt = t->type;
 | 
			
		||||
	struct gendisk *tdisk = t->disk;
 | 
			
		||||
	struct request_queue *q = tdisk->queue;
 | 
			
		||||
 | 
			
		||||
	del_gendisk(tdisk);
 | 
			
		||||
	blk_cleanup_queue(q);
 | 
			
		||||
 | 
			
		||||
	if (tt->exit)
 | 
			
		||||
		tt->exit(tdisk->private_data);
 | 
			
		||||
 | 
			
		||||
	gen_remove_tgt_dev(t->dev);
 | 
			
		||||
	put_disk(tdisk);
 | 
			
		||||
 | 
			
		||||
	list_del(&t->list);
 | 
			
		||||
	kfree(t);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * gen_remove_tgt - Removes a target from the media manager
 | 
			
		||||
 * @dev:	device
 | 
			
		||||
 * @remove:	ioctl structure with target name to remove.
 | 
			
		||||
 *
 | 
			
		||||
 * Returns:
 | 
			
		||||
 * 0: on success
 | 
			
		||||
 * 1: on not found
 | 
			
		||||
 * <0: on error
 | 
			
		||||
 */
 | 
			
		||||
static int gen_remove_tgt(struct nvm_dev *dev, struct nvm_ioctl_remove *remove)
 | 
			
		||||
{
 | 
			
		||||
	struct gen_dev *gn = dev->mp;
 | 
			
		||||
	struct nvm_target *t;
 | 
			
		||||
 | 
			
		||||
	if (!gn)
 | 
			
		||||
		return 1;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&gn->lock);
 | 
			
		||||
	t = gen_find_target(gn, remove->tgtname);
 | 
			
		||||
	if (!t) {
 | 
			
		||||
		mutex_unlock(&gn->lock);
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
	__gen_remove_target(t);
 | 
			
		||||
	mutex_unlock(&gn->lock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_get_area(struct nvm_dev *dev, sector_t *lba, sector_t len)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct gen_dev *gn = dev->mp;
 | 
			
		||||
	struct gen_area *area, *prev, *next;
 | 
			
		||||
	sector_t begin = 0;
 | 
			
		||||
	sector_t max_sectors = (geo->sec_size * dev->total_secs) >> 9;
 | 
			
		||||
 | 
			
		||||
	if (len > max_sectors)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	area = kmalloc(sizeof(struct gen_area), GFP_KERNEL);
 | 
			
		||||
	if (!area)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	prev = NULL;
 | 
			
		||||
 | 
			
		||||
	spin_lock(&dev->lock);
 | 
			
		||||
	list_for_each_entry(next, &gn->area_list, list) {
 | 
			
		||||
		if (begin + len > next->begin) {
 | 
			
		||||
			begin = next->end;
 | 
			
		||||
			prev = next;
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ((begin + len) > max_sectors) {
 | 
			
		||||
		spin_unlock(&dev->lock);
 | 
			
		||||
		kfree(area);
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	area->begin = *lba = begin;
 | 
			
		||||
	area->end = begin + len;
 | 
			
		||||
 | 
			
		||||
	if (prev) /* insert into sorted order */
 | 
			
		||||
		list_add(&area->list, &prev->list);
 | 
			
		||||
	else
 | 
			
		||||
		list_add(&area->list, &gn->area_list);
 | 
			
		||||
	spin_unlock(&dev->lock);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_put_area(struct nvm_dev *dev, sector_t begin)
 | 
			
		||||
{
 | 
			
		||||
	struct gen_dev *gn = dev->mp;
 | 
			
		||||
	struct gen_area *area;
 | 
			
		||||
 | 
			
		||||
	spin_lock(&dev->lock);
 | 
			
		||||
	list_for_each_entry(area, &gn->area_list, list) {
 | 
			
		||||
		if (area->begin != begin)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		list_del(&area->list);
 | 
			
		||||
		spin_unlock(&dev->lock);
 | 
			
		||||
		kfree(area);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
	spin_unlock(&dev->lock);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_free(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	kfree(dev->mp);
 | 
			
		||||
	kfree(dev->rmap);
 | 
			
		||||
	dev->mp = NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_register(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	struct gen_dev *gn;
 | 
			
		||||
	struct gen_dev_map *dev_rmap;
 | 
			
		||||
	int i, j;
 | 
			
		||||
 | 
			
		||||
	if (!try_module_get(THIS_MODULE))
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	gn = kzalloc(sizeof(struct gen_dev), GFP_KERNEL);
 | 
			
		||||
	if (!gn)
 | 
			
		||||
		goto err_gn;
 | 
			
		||||
 | 
			
		||||
	dev_rmap = kmalloc(sizeof(struct gen_dev_map), GFP_KERNEL);
 | 
			
		||||
	if (!dev_rmap)
 | 
			
		||||
		goto err_rmap;
 | 
			
		||||
 | 
			
		||||
	dev_rmap->chnls = kcalloc(dev->geo.nr_chnls, sizeof(struct gen_ch_map),
 | 
			
		||||
								GFP_KERNEL);
 | 
			
		||||
	if (!dev_rmap->chnls)
 | 
			
		||||
		goto err_chnls;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < dev->geo.nr_chnls; i++) {
 | 
			
		||||
		struct gen_ch_map *ch_rmap;
 | 
			
		||||
		int *lun_roffs;
 | 
			
		||||
		int luns_in_chnl = dev->geo.luns_per_chnl;
 | 
			
		||||
 | 
			
		||||
		ch_rmap = &dev_rmap->chnls[i];
 | 
			
		||||
 | 
			
		||||
		ch_rmap->ch_off = -1;
 | 
			
		||||
		ch_rmap->nr_luns = luns_in_chnl;
 | 
			
		||||
 | 
			
		||||
		lun_roffs = kcalloc(luns_in_chnl, sizeof(int), GFP_KERNEL);
 | 
			
		||||
		if (!lun_roffs)
 | 
			
		||||
			goto err_ch;
 | 
			
		||||
 | 
			
		||||
		for (j = 0; j < luns_in_chnl; j++)
 | 
			
		||||
			lun_roffs[j] = -1;
 | 
			
		||||
 | 
			
		||||
		ch_rmap->lun_offs = lun_roffs;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gn->dev = dev;
 | 
			
		||||
	gn->nr_luns = dev->geo.nr_luns;
 | 
			
		||||
	INIT_LIST_HEAD(&gn->area_list);
 | 
			
		||||
	mutex_init(&gn->lock);
 | 
			
		||||
	INIT_LIST_HEAD(&gn->targets);
 | 
			
		||||
	dev->mp = gn;
 | 
			
		||||
	dev->rmap = dev_rmap;
 | 
			
		||||
 | 
			
		||||
	return 1;
 | 
			
		||||
err_ch:
 | 
			
		||||
	while (--i >= 0)
 | 
			
		||||
		kfree(dev_rmap->chnls[i].lun_offs);
 | 
			
		||||
err_chnls:
 | 
			
		||||
	kfree(dev_rmap);
 | 
			
		||||
err_rmap:
 | 
			
		||||
	gen_free(dev);
 | 
			
		||||
err_gn:
 | 
			
		||||
	module_put(THIS_MODULE);
 | 
			
		||||
	return -ENOMEM;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_unregister(struct nvm_dev *dev)
 | 
			
		||||
{
 | 
			
		||||
	struct gen_dev *gn = dev->mp;
 | 
			
		||||
	struct nvm_target *t, *tmp;
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&gn->lock);
 | 
			
		||||
	list_for_each_entry_safe(t, tmp, &gn->targets, list) {
 | 
			
		||||
		if (t->dev->parent != dev)
 | 
			
		||||
			continue;
 | 
			
		||||
		__gen_remove_target(t);
 | 
			
		||||
	}
 | 
			
		||||
	mutex_unlock(&gn->lock);
 | 
			
		||||
 | 
			
		||||
	gen_free(dev);
 | 
			
		||||
	module_put(THIS_MODULE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_map_to_dev(struct nvm_tgt_dev *tgt_dev, struct ppa_addr *p)
 | 
			
		||||
{
 | 
			
		||||
	struct gen_dev_map *dev_map = tgt_dev->map;
 | 
			
		||||
	struct gen_ch_map *ch_map = &dev_map->chnls[p->g.ch];
 | 
			
		||||
	int lun_off = ch_map->lun_offs[p->g.lun];
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct gen_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	struct gen_ch_map *ch_rmap;
 | 
			
		||||
	int lun_roff;
 | 
			
		||||
 | 
			
		||||
	p->g.ch += ch_map->ch_off;
 | 
			
		||||
	p->g.lun += lun_off;
 | 
			
		||||
 | 
			
		||||
	ch_rmap = &dev_rmap->chnls[p->g.ch];
 | 
			
		||||
	lun_roff = ch_rmap->lun_offs[p->g.lun];
 | 
			
		||||
 | 
			
		||||
	if (unlikely(ch_rmap->ch_off < 0 || lun_roff < 0)) {
 | 
			
		||||
		pr_err("nvm: corrupted device partition table\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_map_to_tgt(struct nvm_tgt_dev *tgt_dev, struct ppa_addr *p)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
	struct gen_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	struct gen_ch_map *ch_rmap = &dev_rmap->chnls[p->g.ch];
 | 
			
		||||
	int lun_roff = ch_rmap->lun_offs[p->g.lun];
 | 
			
		||||
 | 
			
		||||
	p->g.ch -= ch_rmap->ch_off;
 | 
			
		||||
	p->g.lun -= lun_roff;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_trans_rq(struct nvm_tgt_dev *tgt_dev, struct nvm_rq *rqd,
 | 
			
		||||
			int flag)
 | 
			
		||||
{
 | 
			
		||||
	gen_trans_fn *f;
 | 
			
		||||
	int i;
 | 
			
		||||
	int ret = 0;
 | 
			
		||||
 | 
			
		||||
	f = (flag == TRANS_TGT_TO_DEV) ? gen_map_to_dev : gen_map_to_tgt;
 | 
			
		||||
 | 
			
		||||
	if (rqd->nr_ppas == 1)
 | 
			
		||||
		return f(tgt_dev, &rqd->ppa_addr);
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < rqd->nr_ppas; i++) {
 | 
			
		||||
		ret = f(tgt_dev, &rqd->ppa_list[i]);
 | 
			
		||||
		if (ret)
 | 
			
		||||
			goto out;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
out:
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_end_io(struct nvm_rq *rqd)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_tgt_dev *tgt_dev = rqd->dev;
 | 
			
		||||
	struct nvm_tgt_instance *ins = rqd->ins;
 | 
			
		||||
 | 
			
		||||
	/* Convert address space */
 | 
			
		||||
	if (tgt_dev)
 | 
			
		||||
		gen_trans_rq(tgt_dev, rqd, TRANS_DEV_TO_TGT);
 | 
			
		||||
 | 
			
		||||
	ins->tt->end_io(rqd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_submit_io(struct nvm_tgt_dev *tgt_dev, struct nvm_rq *rqd)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_dev *dev = tgt_dev->parent;
 | 
			
		||||
 | 
			
		||||
	if (!dev->ops->submit_io)
 | 
			
		||||
		return -ENODEV;
 | 
			
		||||
 | 
			
		||||
	/* Convert address space */
 | 
			
		||||
	gen_trans_rq(tgt_dev, rqd, TRANS_TGT_TO_DEV);
 | 
			
		||||
	nvm_generic_to_addr_mode(dev, rqd);
 | 
			
		||||
 | 
			
		||||
	rqd->dev = tgt_dev;
 | 
			
		||||
	rqd->end_io = gen_end_io;
 | 
			
		||||
	return dev->ops->submit_io(dev, rqd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int gen_erase_blk(struct nvm_tgt_dev *tgt_dev, struct ppa_addr *p,
 | 
			
		||||
			 int flags)
 | 
			
		||||
{
 | 
			
		||||
	/* Convert address space */
 | 
			
		||||
	gen_map_to_dev(tgt_dev, p);
 | 
			
		||||
 | 
			
		||||
	return nvm_erase_ppa(tgt_dev->parent, p, 1, flags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct ppa_addr gen_trans_ppa(struct nvm_tgt_dev *tgt_dev,
 | 
			
		||||
				     struct ppa_addr p, int direction)
 | 
			
		||||
{
 | 
			
		||||
	gen_trans_fn *f;
 | 
			
		||||
	struct ppa_addr ppa = p;
 | 
			
		||||
 | 
			
		||||
	f = (direction == TRANS_TGT_TO_DEV) ? gen_map_to_dev : gen_map_to_tgt;
 | 
			
		||||
	f(tgt_dev, &ppa);
 | 
			
		||||
 | 
			
		||||
	return ppa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_part_to_tgt(struct nvm_dev *dev, sector_t *entries,
 | 
			
		||||
			       int len)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct gen_dev_map *dev_rmap = dev->rmap;
 | 
			
		||||
	u64 i;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < len; i++) {
 | 
			
		||||
		struct gen_ch_map *ch_rmap;
 | 
			
		||||
		int *lun_roffs;
 | 
			
		||||
		struct ppa_addr gaddr;
 | 
			
		||||
		u64 pba = le64_to_cpu(entries[i]);
 | 
			
		||||
		int off;
 | 
			
		||||
		u64 diff;
 | 
			
		||||
 | 
			
		||||
		if (!pba)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		gaddr = linear_to_generic_addr(geo, pba);
 | 
			
		||||
		ch_rmap = &dev_rmap->chnls[gaddr.g.ch];
 | 
			
		||||
		lun_roffs = ch_rmap->lun_offs;
 | 
			
		||||
 | 
			
		||||
		off = gaddr.g.ch * geo->luns_per_chnl + gaddr.g.lun;
 | 
			
		||||
 | 
			
		||||
		diff = ((ch_rmap->ch_off * geo->luns_per_chnl) +
 | 
			
		||||
				(lun_roffs[gaddr.g.lun])) * geo->sec_per_lun;
 | 
			
		||||
 | 
			
		||||
		entries[i] -= cpu_to_le64(diff);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct nvmm_type gen = {
 | 
			
		||||
	.name			= "gennvm",
 | 
			
		||||
	.version		= {0, 1, 0},
 | 
			
		||||
 | 
			
		||||
	.register_mgr		= gen_register,
 | 
			
		||||
	.unregister_mgr		= gen_unregister,
 | 
			
		||||
 | 
			
		||||
	.create_tgt		= gen_create_tgt,
 | 
			
		||||
	.remove_tgt		= gen_remove_tgt,
 | 
			
		||||
 | 
			
		||||
	.submit_io		= gen_submit_io,
 | 
			
		||||
	.erase_blk		= gen_erase_blk,
 | 
			
		||||
 | 
			
		||||
	.get_area		= gen_get_area,
 | 
			
		||||
	.put_area		= gen_put_area,
 | 
			
		||||
 | 
			
		||||
	.trans_ppa		= gen_trans_ppa,
 | 
			
		||||
	.part_to_tgt		= gen_part_to_tgt,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int __init gen_module_init(void)
 | 
			
		||||
{
 | 
			
		||||
	return nvm_register_mgr(&gen);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void gen_module_exit(void)
 | 
			
		||||
{
 | 
			
		||||
	nvm_unregister_mgr(&gen);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module_init(gen_module_init);
 | 
			
		||||
module_exit(gen_module_exit);
 | 
			
		||||
MODULE_LICENSE("GPL v2");
 | 
			
		||||
MODULE_DESCRIPTION("General media manager for Open-Channel SSDs");
 | 
			
		||||
@ -1,62 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright: Matias Bjorling <mb@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.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#ifndef GENNVM_H_
 | 
			
		||||
#define GENNVM_H_
 | 
			
		||||
 | 
			
		||||
#include <linux/module.h>
 | 
			
		||||
#include <linux/vmalloc.h>
 | 
			
		||||
 | 
			
		||||
#include <linux/lightnvm.h>
 | 
			
		||||
 | 
			
		||||
struct gen_dev {
 | 
			
		||||
	struct nvm_dev *dev;
 | 
			
		||||
 | 
			
		||||
	int nr_luns;
 | 
			
		||||
	struct list_head area_list;
 | 
			
		||||
 | 
			
		||||
	struct mutex lock;
 | 
			
		||||
	struct list_head targets;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Map between virtual and physical channel and lun */
 | 
			
		||||
struct gen_ch_map {
 | 
			
		||||
	int ch_off;
 | 
			
		||||
	int nr_luns;
 | 
			
		||||
	int *lun_offs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct gen_dev_map {
 | 
			
		||||
	struct gen_ch_map *chnls;
 | 
			
		||||
	int nr_chnls;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct gen_area {
 | 
			
		||||
	struct list_head list;
 | 
			
		||||
	sector_t begin;
 | 
			
		||||
	sector_t end;	/* end is excluded */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline void *ch_map_to_lun_offs(struct gen_ch_map *ch_map)
 | 
			
		||||
{
 | 
			
		||||
	return ch_map + 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef int (gen_trans_fn)(struct nvm_tgt_dev *, struct ppa_addr *);
 | 
			
		||||
 | 
			
		||||
#define gen_for_each_lun(bm, lun, i) \
 | 
			
		||||
		for ((i) = 0, lun = &(bm)->luns[0]; \
 | 
			
		||||
			(i) < (bm)->nr_luns; (i)++, lun = &(bm)->luns[(i)])
 | 
			
		||||
 | 
			
		||||
#endif /* GENNVM_H_ */
 | 
			
		||||
@ -1,733 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Matias Bjorling. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 * 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.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program; see the file COPYING.  If not, write to
 | 
			
		||||
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
 | 
			
		||||
 * USA.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <linux/lightnvm.h>
 | 
			
		||||
 | 
			
		||||
#define MAX_SYSBLKS 3	/* remember to update mapping scheme on change */
 | 
			
		||||
#define MAX_BLKS_PR_SYSBLK 2 /* 2 blks with 256 pages and 3000 erases
 | 
			
		||||
			      * enables ~1.5M updates per sysblk unit
 | 
			
		||||
			      */
 | 
			
		||||
 | 
			
		||||
struct sysblk_scan {
 | 
			
		||||
	/* A row is a collection of flash blocks for a system block. */
 | 
			
		||||
	int nr_rows;
 | 
			
		||||
	int row;
 | 
			
		||||
	int act_blk[MAX_SYSBLKS];
 | 
			
		||||
 | 
			
		||||
	int nr_ppas;
 | 
			
		||||
	struct ppa_addr ppas[MAX_SYSBLKS * MAX_BLKS_PR_SYSBLK];/* all sysblks */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline int scan_ppa_idx(int row, int blkid)
 | 
			
		||||
{
 | 
			
		||||
	return (row * MAX_BLKS_PR_SYSBLK) + blkid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nvm_sysblk_to_cpu(struct nvm_sb_info *info,
 | 
			
		||||
			      struct nvm_system_block *sb)
 | 
			
		||||
{
 | 
			
		||||
	info->seqnr = be32_to_cpu(sb->seqnr);
 | 
			
		||||
	info->erase_cnt = be32_to_cpu(sb->erase_cnt);
 | 
			
		||||
	info->version = be16_to_cpu(sb->version);
 | 
			
		||||
	strncpy(info->mmtype, sb->mmtype, NVM_MMTYPE_LEN);
 | 
			
		||||
	info->fs_ppa.ppa = be64_to_cpu(sb->fs_ppa);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nvm_cpu_to_sysblk(struct nvm_system_block *sb,
 | 
			
		||||
			      struct nvm_sb_info *info)
 | 
			
		||||
{
 | 
			
		||||
	sb->magic = cpu_to_be32(NVM_SYSBLK_MAGIC);
 | 
			
		||||
	sb->seqnr = cpu_to_be32(info->seqnr);
 | 
			
		||||
	sb->erase_cnt = cpu_to_be32(info->erase_cnt);
 | 
			
		||||
	sb->version = cpu_to_be16(info->version);
 | 
			
		||||
	strncpy(sb->mmtype, info->mmtype, NVM_MMTYPE_LEN);
 | 
			
		||||
	sb->fs_ppa = cpu_to_be64(info->fs_ppa.ppa);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_setup_sysblks(struct nvm_dev *dev, struct ppa_addr *sysblk_ppas)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	int nr_rows = min_t(int, MAX_SYSBLKS, geo->nr_chnls);
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < nr_rows; i++)
 | 
			
		||||
		sysblk_ppas[i].ppa = 0;
 | 
			
		||||
 | 
			
		||||
	/* if possible, place sysblk at first channel, middle channel and last
 | 
			
		||||
	 * channel of the device. If not, create only one or two sys blocks
 | 
			
		||||
	 */
 | 
			
		||||
	switch (geo->nr_chnls) {
 | 
			
		||||
	case 2:
 | 
			
		||||
		sysblk_ppas[1].g.ch = 1;
 | 
			
		||||
		/* fall-through */
 | 
			
		||||
	case 1:
 | 
			
		||||
		sysblk_ppas[0].g.ch = 0;
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		sysblk_ppas[0].g.ch = 0;
 | 
			
		||||
		sysblk_ppas[1].g.ch = geo->nr_chnls / 2;
 | 
			
		||||
		sysblk_ppas[2].g.ch = geo->nr_chnls - 1;
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nr_rows;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void nvm_setup_sysblk_scan(struct nvm_dev *dev, struct sysblk_scan *s,
 | 
			
		||||
						struct ppa_addr *sysblk_ppas)
 | 
			
		||||
{
 | 
			
		||||
	memset(s, 0, sizeof(struct sysblk_scan));
 | 
			
		||||
	s->nr_rows = nvm_setup_sysblks(dev, sysblk_ppas);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int sysblk_get_free_blks(struct nvm_dev *dev, struct ppa_addr ppa,
 | 
			
		||||
					u8 *blks, int nr_blks,
 | 
			
		||||
					struct sysblk_scan *s)
 | 
			
		||||
{
 | 
			
		||||
	struct ppa_addr *sppa;
 | 
			
		||||
	int i, blkid = 0;
 | 
			
		||||
 | 
			
		||||
	nr_blks = nvm_bb_tbl_fold(dev, blks, nr_blks);
 | 
			
		||||
	if (nr_blks < 0)
 | 
			
		||||
		return nr_blks;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < nr_blks; i++) {
 | 
			
		||||
		if (blks[i] == NVM_BLK_T_HOST)
 | 
			
		||||
			return -EEXIST;
 | 
			
		||||
 | 
			
		||||
		if (blks[i] != NVM_BLK_T_FREE)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		sppa = &s->ppas[scan_ppa_idx(s->row, blkid)];
 | 
			
		||||
		sppa->g.ch = ppa.g.ch;
 | 
			
		||||
		sppa->g.lun = ppa.g.lun;
 | 
			
		||||
		sppa->g.blk = i;
 | 
			
		||||
		s->nr_ppas++;
 | 
			
		||||
		blkid++;
 | 
			
		||||
 | 
			
		||||
		pr_debug("nvm: use (%u %u %u) as sysblk\n",
 | 
			
		||||
					sppa->g.ch, sppa->g.lun, sppa->g.blk);
 | 
			
		||||
		if (blkid > MAX_BLKS_PR_SYSBLK - 1)
 | 
			
		||||
			return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pr_err("nvm: sysblk failed get sysblk\n");
 | 
			
		||||
	return -EINVAL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int sysblk_get_host_blks(struct nvm_dev *dev, struct ppa_addr ppa,
 | 
			
		||||
					u8 *blks, int nr_blks,
 | 
			
		||||
					struct sysblk_scan *s)
 | 
			
		||||
{
 | 
			
		||||
	int i, nr_sysblk = 0;
 | 
			
		||||
 | 
			
		||||
	nr_blks = nvm_bb_tbl_fold(dev, blks, nr_blks);
 | 
			
		||||
	if (nr_blks < 0)
 | 
			
		||||
		return nr_blks;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < nr_blks; i++) {
 | 
			
		||||
		if (blks[i] != NVM_BLK_T_HOST)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (s->nr_ppas == MAX_BLKS_PR_SYSBLK * MAX_SYSBLKS) {
 | 
			
		||||
			pr_err("nvm: too many host blks\n");
 | 
			
		||||
			return -EINVAL;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ppa.g.blk = i;
 | 
			
		||||
 | 
			
		||||
		s->ppas[scan_ppa_idx(s->row, nr_sysblk)] = ppa;
 | 
			
		||||
		s->nr_ppas++;
 | 
			
		||||
		nr_sysblk++;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_get_all_sysblks(struct nvm_dev *dev, struct sysblk_scan *s,
 | 
			
		||||
				struct ppa_addr *ppas, int get_free)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	int i, nr_blks, ret = 0;
 | 
			
		||||
	u8 *blks;
 | 
			
		||||
 | 
			
		||||
	s->nr_ppas = 0;
 | 
			
		||||
	nr_blks = geo->blks_per_lun * geo->plane_mode;
 | 
			
		||||
 | 
			
		||||
	blks = kmalloc(nr_blks, GFP_KERNEL);
 | 
			
		||||
	if (!blks)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < s->nr_rows; i++) {
 | 
			
		||||
		s->row = i;
 | 
			
		||||
 | 
			
		||||
		ret = nvm_get_bb_tbl(dev, ppas[i], blks);
 | 
			
		||||
		if (ret) {
 | 
			
		||||
			pr_err("nvm: failed bb tbl for ppa (%u %u)\n",
 | 
			
		||||
							ppas[i].g.ch,
 | 
			
		||||
							ppas[i].g.blk);
 | 
			
		||||
			goto err_get;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (get_free)
 | 
			
		||||
			ret = sysblk_get_free_blks(dev, ppas[i], blks, nr_blks,
 | 
			
		||||
									s);
 | 
			
		||||
		else
 | 
			
		||||
			ret = sysblk_get_host_blks(dev, ppas[i], blks, nr_blks,
 | 
			
		||||
									s);
 | 
			
		||||
 | 
			
		||||
		if (ret)
 | 
			
		||||
			goto err_get;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
err_get:
 | 
			
		||||
	kfree(blks);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * scans a block for latest sysblk.
 | 
			
		||||
 * Returns:
 | 
			
		||||
 *	0 - newer sysblk not found. PPA is updated to latest page.
 | 
			
		||||
 *	1 - newer sysblk found and stored in *cur. PPA is updated to
 | 
			
		||||
 *	    next valid page.
 | 
			
		||||
 *	<0- error.
 | 
			
		||||
 */
 | 
			
		||||
static int nvm_scan_block(struct nvm_dev *dev, struct ppa_addr *ppa,
 | 
			
		||||
						struct nvm_system_block *sblk)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct nvm_system_block *cur;
 | 
			
		||||
	int pg, ret, found = 0;
 | 
			
		||||
 | 
			
		||||
	/* the full buffer for a flash page is allocated. Only the first of it
 | 
			
		||||
	 * contains the system block information
 | 
			
		||||
	 */
 | 
			
		||||
	cur = kmalloc(geo->pfpg_size, GFP_KERNEL);
 | 
			
		||||
	if (!cur)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	/* perform linear scan through the block */
 | 
			
		||||
	for (pg = 0; pg < dev->lps_per_blk; pg++) {
 | 
			
		||||
		ppa->g.pg = ppa_to_slc(dev, pg);
 | 
			
		||||
 | 
			
		||||
		ret = nvm_submit_ppa(dev, ppa, 1, NVM_OP_PREAD, NVM_IO_SLC_MODE,
 | 
			
		||||
							cur, geo->pfpg_size);
 | 
			
		||||
		if (ret) {
 | 
			
		||||
			if (ret == NVM_RSP_ERR_EMPTYPAGE) {
 | 
			
		||||
				pr_debug("nvm: sysblk scan empty ppa (%u %u %u %u)\n",
 | 
			
		||||
							ppa->g.ch,
 | 
			
		||||
							ppa->g.lun,
 | 
			
		||||
							ppa->g.blk,
 | 
			
		||||
							ppa->g.pg);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			pr_err("nvm: read failed (%x) for ppa (%u %u %u %u)",
 | 
			
		||||
							ret,
 | 
			
		||||
							ppa->g.ch,
 | 
			
		||||
							ppa->g.lun,
 | 
			
		||||
							ppa->g.blk,
 | 
			
		||||
							ppa->g.pg);
 | 
			
		||||
			break; /* if we can't read a page, continue to the
 | 
			
		||||
				* next blk
 | 
			
		||||
				*/
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (be32_to_cpu(cur->magic) != NVM_SYSBLK_MAGIC) {
 | 
			
		||||
			pr_debug("nvm: scan break for ppa (%u %u %u %u)\n",
 | 
			
		||||
							ppa->g.ch,
 | 
			
		||||
							ppa->g.lun,
 | 
			
		||||
							ppa->g.blk,
 | 
			
		||||
							ppa->g.pg);
 | 
			
		||||
			break; /* last valid page already found */
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (be32_to_cpu(cur->seqnr) < be32_to_cpu(sblk->seqnr))
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		memcpy(sblk, cur, sizeof(struct nvm_system_block));
 | 
			
		||||
		found = 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kfree(cur);
 | 
			
		||||
 | 
			
		||||
	return found;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_sysblk_set_bb_tbl(struct nvm_dev *dev, struct sysblk_scan *s,
 | 
			
		||||
								int type)
 | 
			
		||||
{
 | 
			
		||||
	return nvm_set_bb_tbl(dev, s->ppas, s->nr_ppas, type);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_write_and_verify(struct nvm_dev *dev, struct nvm_sb_info *info,
 | 
			
		||||
							struct sysblk_scan *s)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct nvm_system_block nvmsb;
 | 
			
		||||
	void *buf;
 | 
			
		||||
	int i, sect, ret = 0;
 | 
			
		||||
	struct ppa_addr *ppas;
 | 
			
		||||
 | 
			
		||||
	nvm_cpu_to_sysblk(&nvmsb, info);
 | 
			
		||||
 | 
			
		||||
	buf = kzalloc(geo->pfpg_size, GFP_KERNEL);
 | 
			
		||||
	if (!buf)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	memcpy(buf, &nvmsb, sizeof(struct nvm_system_block));
 | 
			
		||||
 | 
			
		||||
	ppas = kcalloc(geo->sec_per_pg, sizeof(struct ppa_addr), GFP_KERNEL);
 | 
			
		||||
	if (!ppas) {
 | 
			
		||||
		ret = -ENOMEM;
 | 
			
		||||
		goto err;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Write and verify */
 | 
			
		||||
	for (i = 0; i < s->nr_rows; i++) {
 | 
			
		||||
		ppas[0] = s->ppas[scan_ppa_idx(i, s->act_blk[i])];
 | 
			
		||||
 | 
			
		||||
		pr_debug("nvm: writing sysblk to ppa (%u %u %u %u)\n",
 | 
			
		||||
							ppas[0].g.ch,
 | 
			
		||||
							ppas[0].g.lun,
 | 
			
		||||
							ppas[0].g.blk,
 | 
			
		||||
							ppas[0].g.pg);
 | 
			
		||||
 | 
			
		||||
		/* Expand to all sectors within a flash page */
 | 
			
		||||
		if (geo->sec_per_pg > 1) {
 | 
			
		||||
			for (sect = 1; sect < geo->sec_per_pg; sect++) {
 | 
			
		||||
				ppas[sect].ppa = ppas[0].ppa;
 | 
			
		||||
				ppas[sect].g.sec = sect;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ret = nvm_submit_ppa(dev, ppas, geo->sec_per_pg, NVM_OP_PWRITE,
 | 
			
		||||
					NVM_IO_SLC_MODE, buf, geo->pfpg_size);
 | 
			
		||||
		if (ret) {
 | 
			
		||||
			pr_err("nvm: sysblk failed program (%u %u %u)\n",
 | 
			
		||||
							ppas[0].g.ch,
 | 
			
		||||
							ppas[0].g.lun,
 | 
			
		||||
							ppas[0].g.blk);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ret = nvm_submit_ppa(dev, ppas, geo->sec_per_pg, NVM_OP_PREAD,
 | 
			
		||||
					NVM_IO_SLC_MODE, buf, geo->pfpg_size);
 | 
			
		||||
		if (ret) {
 | 
			
		||||
			pr_err("nvm: sysblk failed read (%u %u %u)\n",
 | 
			
		||||
							ppas[0].g.ch,
 | 
			
		||||
							ppas[0].g.lun,
 | 
			
		||||
							ppas[0].g.blk);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (memcmp(buf, &nvmsb, sizeof(struct nvm_system_block))) {
 | 
			
		||||
			pr_err("nvm: sysblk failed verify (%u %u %u)\n",
 | 
			
		||||
							ppas[0].g.ch,
 | 
			
		||||
							ppas[0].g.lun,
 | 
			
		||||
							ppas[0].g.blk);
 | 
			
		||||
			ret = -EINVAL;
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kfree(ppas);
 | 
			
		||||
err:
 | 
			
		||||
	kfree(buf);
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_prepare_new_sysblks(struct nvm_dev *dev, struct sysblk_scan *s)
 | 
			
		||||
{
 | 
			
		||||
	int i, ret;
 | 
			
		||||
	unsigned long nxt_blk;
 | 
			
		||||
	struct ppa_addr *ppa;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < s->nr_rows; i++) {
 | 
			
		||||
		nxt_blk = (s->act_blk[i] + 1) % MAX_BLKS_PR_SYSBLK;
 | 
			
		||||
		ppa = &s->ppas[scan_ppa_idx(i, nxt_blk)];
 | 
			
		||||
		ppa->g.pg = ppa_to_slc(dev, 0);
 | 
			
		||||
 | 
			
		||||
		ret = nvm_erase_ppa(dev, ppa, 1, 0);
 | 
			
		||||
		if (ret)
 | 
			
		||||
			return ret;
 | 
			
		||||
 | 
			
		||||
		s->act_blk[i] = nxt_blk;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int nvm_get_sysblock(struct nvm_dev *dev, struct nvm_sb_info *info)
 | 
			
		||||
{
 | 
			
		||||
	struct ppa_addr sysblk_ppas[MAX_SYSBLKS];
 | 
			
		||||
	struct sysblk_scan s;
 | 
			
		||||
	struct nvm_system_block *cur;
 | 
			
		||||
	int i, j, found = 0;
 | 
			
		||||
	int ret = -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * 1. setup sysblk locations
 | 
			
		||||
	 * 2. get bad block list
 | 
			
		||||
	 * 3. filter on host-specific (type 3)
 | 
			
		||||
	 * 4. iterate through all and find the highest seq nr.
 | 
			
		||||
	 * 5. return superblock information
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	if (!dev->ops->get_bb_tbl)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	nvm_setup_sysblk_scan(dev, &s, sysblk_ppas);
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&dev->mlock);
 | 
			
		||||
	ret = nvm_get_all_sysblks(dev, &s, sysblk_ppas, 0);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_sysblk;
 | 
			
		||||
 | 
			
		||||
	/* no sysblocks initialized */
 | 
			
		||||
	if (!s.nr_ppas)
 | 
			
		||||
		goto err_sysblk;
 | 
			
		||||
 | 
			
		||||
	cur = kzalloc(sizeof(struct nvm_system_block), GFP_KERNEL);
 | 
			
		||||
	if (!cur)
 | 
			
		||||
		goto err_sysblk;
 | 
			
		||||
 | 
			
		||||
	/* find the latest block across all sysblocks */
 | 
			
		||||
	for (i = 0; i < s.nr_rows; i++) {
 | 
			
		||||
		for (j = 0; j < MAX_BLKS_PR_SYSBLK; j++) {
 | 
			
		||||
			struct ppa_addr ppa = s.ppas[scan_ppa_idx(i, j)];
 | 
			
		||||
 | 
			
		||||
			ret = nvm_scan_block(dev, &ppa, cur);
 | 
			
		||||
			if (ret > 0)
 | 
			
		||||
				found = 1;
 | 
			
		||||
			else if (ret < 0)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nvm_sysblk_to_cpu(info, cur);
 | 
			
		||||
 | 
			
		||||
	kfree(cur);
 | 
			
		||||
err_sysblk:
 | 
			
		||||
	mutex_unlock(&dev->mlock);
 | 
			
		||||
 | 
			
		||||
	if (found)
 | 
			
		||||
		return 1;
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int nvm_update_sysblock(struct nvm_dev *dev, struct nvm_sb_info *new)
 | 
			
		||||
{
 | 
			
		||||
	/* 1. for each latest superblock
 | 
			
		||||
	 * 2. if room
 | 
			
		||||
	 *    a. write new flash page entry with the updated information
 | 
			
		||||
	 * 3. if no room
 | 
			
		||||
	 *    a. find next available block on lun (linear search)
 | 
			
		||||
	 *       if none, continue to next lun
 | 
			
		||||
	 *       if none at all, report error. also report that it wasn't
 | 
			
		||||
	 *       possible to write to all superblocks.
 | 
			
		||||
	 *    c. write data to block.
 | 
			
		||||
	 */
 | 
			
		||||
	struct ppa_addr sysblk_ppas[MAX_SYSBLKS];
 | 
			
		||||
	struct sysblk_scan s;
 | 
			
		||||
	struct nvm_system_block *cur;
 | 
			
		||||
	int i, j, ppaidx, found = 0;
 | 
			
		||||
	int ret = -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	if (!dev->ops->get_bb_tbl)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	nvm_setup_sysblk_scan(dev, &s, sysblk_ppas);
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&dev->mlock);
 | 
			
		||||
	ret = nvm_get_all_sysblks(dev, &s, sysblk_ppas, 0);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_sysblk;
 | 
			
		||||
 | 
			
		||||
	cur = kzalloc(sizeof(struct nvm_system_block), GFP_KERNEL);
 | 
			
		||||
	if (!cur)
 | 
			
		||||
		goto err_sysblk;
 | 
			
		||||
 | 
			
		||||
	/* Get the latest sysblk for each sysblk row */
 | 
			
		||||
	for (i = 0; i < s.nr_rows; i++) {
 | 
			
		||||
		found = 0;
 | 
			
		||||
		for (j = 0; j < MAX_BLKS_PR_SYSBLK; j++) {
 | 
			
		||||
			ppaidx = scan_ppa_idx(i, j);
 | 
			
		||||
			ret = nvm_scan_block(dev, &s.ppas[ppaidx], cur);
 | 
			
		||||
			if (ret > 0) {
 | 
			
		||||
				s.act_blk[i] = j;
 | 
			
		||||
				found = 1;
 | 
			
		||||
			} else if (ret < 0)
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!found) {
 | 
			
		||||
		pr_err("nvm: no valid sysblks found to update\n");
 | 
			
		||||
		ret = -EINVAL;
 | 
			
		||||
		goto err_cur;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * All sysblocks found. Check that they have same page id in their flash
 | 
			
		||||
	 * blocks
 | 
			
		||||
	 */
 | 
			
		||||
	for (i = 1; i < s.nr_rows; i++) {
 | 
			
		||||
		struct ppa_addr l = s.ppas[scan_ppa_idx(0, s.act_blk[0])];
 | 
			
		||||
		struct ppa_addr r = s.ppas[scan_ppa_idx(i, s.act_blk[i])];
 | 
			
		||||
 | 
			
		||||
		if (l.g.pg != r.g.pg) {
 | 
			
		||||
			pr_err("nvm: sysblks not on same page. Previous update failed.\n");
 | 
			
		||||
			ret = -EINVAL;
 | 
			
		||||
			goto err_cur;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Check that there haven't been another update to the seqnr since we
 | 
			
		||||
	 * began
 | 
			
		||||
	 */
 | 
			
		||||
	if ((new->seqnr - 1) != be32_to_cpu(cur->seqnr)) {
 | 
			
		||||
		pr_err("nvm: seq is not sequential\n");
 | 
			
		||||
		ret = -EINVAL;
 | 
			
		||||
		goto err_cur;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * When all pages in a block has been written, a new block is selected
 | 
			
		||||
	 * and writing is performed on the new block.
 | 
			
		||||
	 */
 | 
			
		||||
	if (s.ppas[scan_ppa_idx(0, s.act_blk[0])].g.pg ==
 | 
			
		||||
						dev->lps_per_blk - 1) {
 | 
			
		||||
		ret = nvm_prepare_new_sysblks(dev, &s);
 | 
			
		||||
		if (ret)
 | 
			
		||||
			goto err_cur;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret = nvm_write_and_verify(dev, new, &s);
 | 
			
		||||
err_cur:
 | 
			
		||||
	kfree(cur);
 | 
			
		||||
err_sysblk:
 | 
			
		||||
	mutex_unlock(&dev->mlock);
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int nvm_init_sysblock(struct nvm_dev *dev, struct nvm_sb_info *info)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct ppa_addr sysblk_ppas[MAX_SYSBLKS];
 | 
			
		||||
	struct sysblk_scan s;
 | 
			
		||||
	int ret;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * 1. select master blocks and select first available blks
 | 
			
		||||
	 * 2. get bad block list
 | 
			
		||||
	 * 3. mark MAX_SYSBLKS block as host-based device allocated.
 | 
			
		||||
	 * 4. write and verify data to block
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	if (!dev->ops->get_bb_tbl || !dev->ops->set_bb_tbl)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	if (!(geo->mccap & NVM_ID_CAP_SLC) || !dev->lps_per_blk) {
 | 
			
		||||
		pr_err("nvm: memory does not support SLC access\n");
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Index all sysblocks and mark them as host-driven */
 | 
			
		||||
	nvm_setup_sysblk_scan(dev, &s, sysblk_ppas);
 | 
			
		||||
 | 
			
		||||
	mutex_lock(&dev->mlock);
 | 
			
		||||
	ret = nvm_get_all_sysblks(dev, &s, sysblk_ppas, 1);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_mark;
 | 
			
		||||
 | 
			
		||||
	ret = nvm_sysblk_set_bb_tbl(dev, &s, NVM_BLK_T_HOST);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_mark;
 | 
			
		||||
 | 
			
		||||
	/* Write to the first block of each row */
 | 
			
		||||
	ret = nvm_write_and_verify(dev, info, &s);
 | 
			
		||||
err_mark:
 | 
			
		||||
	mutex_unlock(&dev->mlock);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int factory_nblks(int nblks)
 | 
			
		||||
{
 | 
			
		||||
	/* Round up to nearest BITS_PER_LONG */
 | 
			
		||||
	return (nblks + (BITS_PER_LONG - 1)) & ~(BITS_PER_LONG - 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static unsigned int factory_blk_offset(struct nvm_geo *geo, struct ppa_addr ppa)
 | 
			
		||||
{
 | 
			
		||||
	int nblks = factory_nblks(geo->blks_per_lun);
 | 
			
		||||
 | 
			
		||||
	return ((ppa.g.ch * geo->luns_per_chnl * nblks) + (ppa.g.lun * nblks)) /
 | 
			
		||||
								BITS_PER_LONG;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_factory_blks(struct nvm_dev *dev, struct ppa_addr ppa,
 | 
			
		||||
					u8 *blks, int nr_blks,
 | 
			
		||||
					unsigned long *blk_bitmap, int flags)
 | 
			
		||||
{
 | 
			
		||||
	int i, lunoff;
 | 
			
		||||
 | 
			
		||||
	nr_blks = nvm_bb_tbl_fold(dev, blks, nr_blks);
 | 
			
		||||
	if (nr_blks < 0)
 | 
			
		||||
		return nr_blks;
 | 
			
		||||
 | 
			
		||||
	lunoff = factory_blk_offset(&dev->geo, ppa);
 | 
			
		||||
 | 
			
		||||
	/* non-set bits correspond to the block must be erased */
 | 
			
		||||
	for (i = 0; i < nr_blks; i++) {
 | 
			
		||||
		switch (blks[i]) {
 | 
			
		||||
		case NVM_BLK_T_FREE:
 | 
			
		||||
			if (flags & NVM_FACTORY_ERASE_ONLY_USER)
 | 
			
		||||
				set_bit(i, &blk_bitmap[lunoff]);
 | 
			
		||||
			break;
 | 
			
		||||
		case NVM_BLK_T_HOST:
 | 
			
		||||
			if (!(flags & NVM_FACTORY_RESET_HOST_BLKS))
 | 
			
		||||
				set_bit(i, &blk_bitmap[lunoff]);
 | 
			
		||||
			break;
 | 
			
		||||
		case NVM_BLK_T_GRWN_BAD:
 | 
			
		||||
			if (!(flags & NVM_FACTORY_RESET_GRWN_BBLKS))
 | 
			
		||||
				set_bit(i, &blk_bitmap[lunoff]);
 | 
			
		||||
			break;
 | 
			
		||||
		default:
 | 
			
		||||
			set_bit(i, &blk_bitmap[lunoff]);
 | 
			
		||||
			break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_fact_get_blks(struct nvm_dev *dev, struct ppa_addr *erase_list,
 | 
			
		||||
					int max_ppas, unsigned long *blk_bitmap)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct ppa_addr ppa;
 | 
			
		||||
	int ch, lun, blkid, idx, done = 0, ppa_cnt = 0;
 | 
			
		||||
	unsigned long *offset;
 | 
			
		||||
 | 
			
		||||
	while (!done) {
 | 
			
		||||
		done = 1;
 | 
			
		||||
		nvm_for_each_lun_ppa(geo, ppa, ch, lun) {
 | 
			
		||||
			idx = factory_blk_offset(geo, ppa);
 | 
			
		||||
			offset = &blk_bitmap[idx];
 | 
			
		||||
 | 
			
		||||
			blkid = find_first_zero_bit(offset, geo->blks_per_lun);
 | 
			
		||||
			if (blkid >= geo->blks_per_lun)
 | 
			
		||||
				continue;
 | 
			
		||||
			set_bit(blkid, offset);
 | 
			
		||||
 | 
			
		||||
			ppa.g.blk = blkid;
 | 
			
		||||
			pr_debug("nvm: erase ppa (%u %u %u)\n",
 | 
			
		||||
							ppa.g.ch,
 | 
			
		||||
							ppa.g.lun,
 | 
			
		||||
							ppa.g.blk);
 | 
			
		||||
 | 
			
		||||
			erase_list[ppa_cnt] = ppa;
 | 
			
		||||
			ppa_cnt++;
 | 
			
		||||
			done = 0;
 | 
			
		||||
 | 
			
		||||
			if (ppa_cnt == max_ppas)
 | 
			
		||||
				return ppa_cnt;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ppa_cnt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int nvm_fact_select_blks(struct nvm_dev *dev, unsigned long *blk_bitmap,
 | 
			
		||||
								int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct ppa_addr ppa;
 | 
			
		||||
	int ch, lun, nr_blks, ret = 0;
 | 
			
		||||
	u8 *blks;
 | 
			
		||||
 | 
			
		||||
	nr_blks = geo->blks_per_lun * geo->plane_mode;
 | 
			
		||||
	blks = kmalloc(nr_blks, GFP_KERNEL);
 | 
			
		||||
	if (!blks)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	nvm_for_each_lun_ppa(geo, ppa, ch, lun) {
 | 
			
		||||
		ret = nvm_get_bb_tbl(dev, ppa, blks);
 | 
			
		||||
		if (ret)
 | 
			
		||||
			pr_err("nvm: failed bb tbl for ch%u lun%u\n",
 | 
			
		||||
							ppa.g.ch, ppa.g.blk);
 | 
			
		||||
 | 
			
		||||
		ret = nvm_factory_blks(dev, ppa, blks, nr_blks, blk_bitmap,
 | 
			
		||||
									flags);
 | 
			
		||||
		if (ret)
 | 
			
		||||
			break;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kfree(blks);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int nvm_dev_factory(struct nvm_dev *dev, int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct nvm_geo *geo = &dev->geo;
 | 
			
		||||
	struct ppa_addr *ppas;
 | 
			
		||||
	int ppa_cnt, ret = -ENOMEM;
 | 
			
		||||
	int max_ppas = dev->ops->max_phys_sect / geo->nr_planes;
 | 
			
		||||
	struct ppa_addr sysblk_ppas[MAX_SYSBLKS];
 | 
			
		||||
	struct sysblk_scan s;
 | 
			
		||||
	unsigned long *blk_bitmap;
 | 
			
		||||
 | 
			
		||||
	blk_bitmap = kzalloc(factory_nblks(geo->blks_per_lun) * geo->nr_luns,
 | 
			
		||||
								GFP_KERNEL);
 | 
			
		||||
	if (!blk_bitmap)
 | 
			
		||||
		return ret;
 | 
			
		||||
 | 
			
		||||
	ppas = kcalloc(max_ppas, sizeof(struct ppa_addr), GFP_KERNEL);
 | 
			
		||||
	if (!ppas)
 | 
			
		||||
		goto err_blks;
 | 
			
		||||
 | 
			
		||||
	/* create list of blks to be erased */
 | 
			
		||||
	ret = nvm_fact_select_blks(dev, blk_bitmap, flags);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		goto err_ppas;
 | 
			
		||||
 | 
			
		||||
	/* continue to erase until list of blks until empty */
 | 
			
		||||
	while ((ppa_cnt =
 | 
			
		||||
			nvm_fact_get_blks(dev, ppas, max_ppas, blk_bitmap)) > 0)
 | 
			
		||||
		nvm_erase_ppa(dev, ppas, ppa_cnt, 0);
 | 
			
		||||
 | 
			
		||||
	/* mark host reserved blocks free */
 | 
			
		||||
	if (flags & NVM_FACTORY_RESET_HOST_BLKS) {
 | 
			
		||||
		nvm_setup_sysblk_scan(dev, &s, sysblk_ppas);
 | 
			
		||||
		mutex_lock(&dev->mlock);
 | 
			
		||||
		ret = nvm_get_all_sysblks(dev, &s, sysblk_ppas, 0);
 | 
			
		||||
		if (!ret)
 | 
			
		||||
			ret = nvm_sysblk_set_bb_tbl(dev, &s, NVM_BLK_T_FREE);
 | 
			
		||||
		mutex_unlock(&dev->mlock);
 | 
			
		||||
	}
 | 
			
		||||
err_ppas:
 | 
			
		||||
	kfree(ppas);
 | 
			
		||||
err_blks:
 | 
			
		||||
	kfree(blk_bitmap);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL(nvm_dev_factory);
 | 
			
		||||
@ -372,7 +372,7 @@ static int nvme_nvm_get_l2p_tbl(struct nvm_dev *nvmdev, u64 slba, u32 nlb,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Transform physical address to target address space */
 | 
			
		||||
		nvmdev->mt->part_to_tgt(nvmdev, entries, cmd_nlb);
 | 
			
		||||
		nvm_part_to_tgt(nvmdev, entries, cmd_nlb);
 | 
			
		||||
 | 
			
		||||
		if (update_l2p(cmd_slba, cmd_nlb, entries, priv)) {
 | 
			
		||||
			ret = -EINTR;
 | 
			
		||||
@ -633,10 +633,9 @@ static ssize_t nvm_dev_attr_show(struct device *dev,
 | 
			
		||||
		return scnprintf(page, PAGE_SIZE, "%u\n", id->cap);
 | 
			
		||||
	} else if (strcmp(attr->name, "device_mode") == 0) {
 | 
			
		||||
		return scnprintf(page, PAGE_SIZE, "%u\n", id->dom);
 | 
			
		||||
	/* kept for compatibility */
 | 
			
		||||
	} else if (strcmp(attr->name, "media_manager") == 0) {
 | 
			
		||||
		if (!ndev->mt)
 | 
			
		||||
			return scnprintf(page, PAGE_SIZE, "%s\n", "none");
 | 
			
		||||
		return scnprintf(page, PAGE_SIZE, "%s\n", ndev->mt->name);
 | 
			
		||||
		return scnprintf(page, PAGE_SIZE, "%s\n", "gennvm");
 | 
			
		||||
	} else if (strcmp(attr->name, "ppa_format") == 0) {
 | 
			
		||||
		return scnprintf(page, PAGE_SIZE,
 | 
			
		||||
			"0x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
 | 
			
		||||
 | 
			
		||||
@ -80,8 +80,6 @@ struct nvm_dev_ops {
 | 
			
		||||
	unsigned int		max_phys_sect;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#ifdef CONFIG_NVM
 | 
			
		||||
 | 
			
		||||
#include <linux/blkdev.h>
 | 
			
		||||
@ -272,15 +270,6 @@ enum {
 | 
			
		||||
	NVM_BLK_ST_BAD =	0x8,	/* Bad block */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* system block cpu representation */
 | 
			
		||||
struct nvm_sb_info {
 | 
			
		||||
	unsigned long		seqnr;
 | 
			
		||||
	unsigned long		erase_cnt;
 | 
			
		||||
	unsigned int		version;
 | 
			
		||||
	char			mmtype[NVM_MMTYPE_LEN];
 | 
			
		||||
	struct ppa_addr		fs_ppa;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* Device generic information */
 | 
			
		||||
struct nvm_geo {
 | 
			
		||||
	int nr_chnls;
 | 
			
		||||
@ -308,6 +297,7 @@ struct nvm_geo {
 | 
			
		||||
	int sec_per_lun;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* sub-device structure */
 | 
			
		||||
struct nvm_tgt_dev {
 | 
			
		||||
	/* Device information */
 | 
			
		||||
	struct nvm_geo geo;
 | 
			
		||||
@ -329,17 +319,10 @@ struct nvm_dev {
 | 
			
		||||
 | 
			
		||||
	struct list_head devices;
 | 
			
		||||
 | 
			
		||||
	/* Media manager */
 | 
			
		||||
	struct nvmm_type *mt;
 | 
			
		||||
	void *mp;
 | 
			
		||||
 | 
			
		||||
	/* System blocks */
 | 
			
		||||
	struct nvm_sb_info sb;
 | 
			
		||||
 | 
			
		||||
	/* Device information */
 | 
			
		||||
	struct nvm_geo geo;
 | 
			
		||||
 | 
			
		||||
	/* lower page table */
 | 
			
		||||
	  /* lower page table */
 | 
			
		||||
	int lps_per_blk;
 | 
			
		||||
	int *lptbl;
 | 
			
		||||
 | 
			
		||||
@ -359,6 +342,10 @@ struct nvm_dev {
 | 
			
		||||
 | 
			
		||||
	struct mutex mlock;
 | 
			
		||||
	spinlock_t lock;
 | 
			
		||||
 | 
			
		||||
	/* target management */
 | 
			
		||||
	struct list_head area_list;
 | 
			
		||||
	struct list_head targets;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline struct ppa_addr linear_to_generic_addr(struct nvm_geo *geo,
 | 
			
		||||
@ -452,11 +439,6 @@ static inline int ppa_cmp_blk(struct ppa_addr ppa1, struct ppa_addr ppa2)
 | 
			
		||||
					(ppa1.g.blk == ppa2.g.blk));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline int ppa_to_slc(struct nvm_dev *dev, int slc_pg)
 | 
			
		||||
{
 | 
			
		||||
	return dev->lptbl[slc_pg];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef blk_qc_t (nvm_tgt_make_rq_fn)(struct request_queue *, struct bio *);
 | 
			
		||||
typedef sector_t (nvm_tgt_capacity_fn)(void *);
 | 
			
		||||
typedef void *(nvm_tgt_init_fn)(struct nvm_tgt_dev *, struct gendisk *);
 | 
			
		||||
@ -487,49 +469,6 @@ extern void nvm_unregister_tgt_type(struct nvm_tgt_type *);
 | 
			
		||||
extern void *nvm_dev_dma_alloc(struct nvm_dev *, gfp_t, dma_addr_t *);
 | 
			
		||||
extern void nvm_dev_dma_free(struct nvm_dev *, void *, dma_addr_t);
 | 
			
		||||
 | 
			
		||||
typedef int (nvmm_register_fn)(struct nvm_dev *);
 | 
			
		||||
typedef void (nvmm_unregister_fn)(struct nvm_dev *);
 | 
			
		||||
 | 
			
		||||
typedef int (nvmm_create_tgt_fn)(struct nvm_dev *, struct nvm_ioctl_create *);
 | 
			
		||||
typedef int (nvmm_remove_tgt_fn)(struct nvm_dev *, struct nvm_ioctl_remove *);
 | 
			
		||||
typedef int (nvmm_submit_io_fn)(struct nvm_tgt_dev *, struct nvm_rq *);
 | 
			
		||||
typedef int (nvmm_erase_blk_fn)(struct nvm_tgt_dev *, struct ppa_addr *, int);
 | 
			
		||||
typedef int (nvmm_get_area_fn)(struct nvm_dev *, sector_t *, sector_t);
 | 
			
		||||
typedef void (nvmm_put_area_fn)(struct nvm_dev *, sector_t);
 | 
			
		||||
typedef struct ppa_addr (nvmm_trans_ppa_fn)(struct nvm_tgt_dev *,
 | 
			
		||||
					    struct ppa_addr, int);
 | 
			
		||||
typedef void (nvmm_part_to_tgt_fn)(struct nvm_dev *, sector_t*, int);
 | 
			
		||||
 | 
			
		||||
enum {
 | 
			
		||||
	TRANS_TGT_TO_DEV =	0x0,
 | 
			
		||||
	TRANS_DEV_TO_TGT =	0x1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct nvmm_type {
 | 
			
		||||
	const char *name;
 | 
			
		||||
	unsigned int version[3];
 | 
			
		||||
 | 
			
		||||
	nvmm_register_fn *register_mgr;
 | 
			
		||||
	nvmm_unregister_fn *unregister_mgr;
 | 
			
		||||
 | 
			
		||||
	nvmm_create_tgt_fn *create_tgt;
 | 
			
		||||
	nvmm_remove_tgt_fn *remove_tgt;
 | 
			
		||||
 | 
			
		||||
	nvmm_submit_io_fn *submit_io;
 | 
			
		||||
	nvmm_erase_blk_fn *erase_blk;
 | 
			
		||||
 | 
			
		||||
	nvmm_get_area_fn *get_area;
 | 
			
		||||
	nvmm_put_area_fn *put_area;
 | 
			
		||||
 | 
			
		||||
	nvmm_trans_ppa_fn *trans_ppa;
 | 
			
		||||
	nvmm_part_to_tgt_fn *part_to_tgt;
 | 
			
		||||
 | 
			
		||||
	struct list_head list;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern int nvm_register_mgr(struct nvmm_type *);
 | 
			
		||||
extern void nvm_unregister_mgr(struct nvmm_type *);
 | 
			
		||||
 | 
			
		||||
extern struct nvm_dev *nvm_alloc_dev(int);
 | 
			
		||||
extern int nvm_register(struct nvm_dev *);
 | 
			
		||||
extern void nvm_unregister(struct nvm_dev *);
 | 
			
		||||
@ -559,31 +498,9 @@ extern int nvm_bb_tbl_fold(struct nvm_dev *, u8 *, int);
 | 
			
		||||
extern int nvm_get_bb_tbl(struct nvm_dev *, struct ppa_addr, u8 *);
 | 
			
		||||
extern int nvm_get_tgt_bb_tbl(struct nvm_tgt_dev *, struct ppa_addr, u8 *);
 | 
			
		||||
 | 
			
		||||
/* sysblk.c */
 | 
			
		||||
#define NVM_SYSBLK_MAGIC 0x4E564D53 /* "NVMS" */
 | 
			
		||||
 | 
			
		||||
/* system block on disk representation */
 | 
			
		||||
struct nvm_system_block {
 | 
			
		||||
	__be32			magic;		/* magic signature */
 | 
			
		||||
	__be32			seqnr;		/* sequence number */
 | 
			
		||||
	__be32			erase_cnt;	/* erase count */
 | 
			
		||||
	__be16			version;	/* version number */
 | 
			
		||||
	u8			mmtype[NVM_MMTYPE_LEN]; /* media manager name */
 | 
			
		||||
	__be64			fs_ppa;		/* PPA for media manager
 | 
			
		||||
						 * superblock */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extern int nvm_get_sysblock(struct nvm_dev *, struct nvm_sb_info *);
 | 
			
		||||
extern int nvm_update_sysblock(struct nvm_dev *, struct nvm_sb_info *);
 | 
			
		||||
extern int nvm_init_sysblock(struct nvm_dev *, struct nvm_sb_info *);
 | 
			
		||||
 | 
			
		||||
extern int nvm_dev_factory(struct nvm_dev *, int flags);
 | 
			
		||||
 | 
			
		||||
#define nvm_for_each_lun_ppa(geo, ppa, chid, lunid)			\
 | 
			
		||||
	for ((chid) = 0, (ppa).ppa = 0; (chid) < (geo)->nr_chnls;	\
 | 
			
		||||
					(chid)++, (ppa).g.ch = (chid))	\
 | 
			
		||||
		for ((lunid) = 0; (lunid) < (geo)->luns_per_chnl;	\
 | 
			
		||||
					(lunid)++, (ppa).g.lun = (lunid))
 | 
			
		||||
extern void nvm_part_to_tgt(struct nvm_dev *, sector_t *, int);
 | 
			
		||||
 | 
			
		||||
#else /* CONFIG_NVM */
 | 
			
		||||
struct nvm_dev_ops;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user