[dm-devel] [patch v3]DM: dm-insitu-comp: a compressed DM target for SSD

Shaohua Li shli at kernel.org
Fri Mar 7 07:57:33 UTC 2014


ping!

On Tue, Feb 18, 2014 at 06:13:04PM +0800, Shaohua Li wrote:
> 
> This is a simple DM target supporting compression for SSD only. Under layer SSD
> must support 512B sector size, the target only supports 4k sector size.
> 
> Disk layout:
> |super|...meta...|..data...|
> 
> Store unit is 4k (a block). Super is 1 block, which stores meta and data size
> and compression algorithm. Meta is a bitmap. For each data block, there are 5
> bits meta.
> 
> Data:
> Data of a block is compressed. Compressed data is round up to 512B, which is
> the payload. In disk, payload is stored at the begining of logical sector of
> the block. Let's look at an example. Say we store data to block A, which is in
> sector B(A*8), its orginal size is 4k, compressed size is 1500. Compressed data
> (CD) will use 3 sectors (512B). The 3 sectors are the payload. Payload will be
> stored at sector B.
> 
> ---------------------------------------------------
> ... | CD1 | CD2 | CD3 |   |   |   |   |    | ...
> ---------------------------------------------------
>     ^B    ^B+1  ^B+2                  ^B+7 ^B+8
> 
> For this block, we will not use sector B+3 to B+7 (a hole). We use 4 meta bits
> to present payload size. The compressed size (1500) isn't stored in meta
> directly. Instead, we store it at the last 32bits of payload. In this example,
> we store it at the end of sector B+2. If compressed size + sizeof(32bits)
> crosses a sector, payload size will increase one sector. If payload uses 8
> sectors, we store uncompressed data directly.
> 
> If IO size is bigger than one block, we can store the data as an extent. Data
> of the whole extent will compressed and stored in the similar way like above.
> The first block of the extent is the head, all others are the tail. If extent
> is 1 block, the block is head. We have 1 bit of meta to present if a block is
> head or tail. If 4 meta bits of head block can't store extent payload size, we
> will borrow tail block meta bits to store payload size. Max allowd extent size
> is 128k, so we don't compress/decompress too big size data.
> 
> Meta:
> Modifying data will modify meta too. Meta will be written(flush) to disk
> depending on meta write policy. We support writeback and writethrough mode. In
> writeback mode, meta will be written to disk in an interval or a FLUSH request.
> In writethrough mode, data and meta data will be written to disk together.
> 
> Advantages:
> 1. simple. Since we store compressed data in-place, we don't need complicated
> disk data management.
> 2. efficient. For each 4k, we only need 5 bits meta. 1T data will use less than
> 200M meta, so we can load all meta into memory. And actual compression size is
> in payload. So if IO doesn't need RMW and we use writeback meta flush, we don't
> need extra IO for meta.
> 
> Disadvantages:
> 1. hole. Since we store compressed data in-place, there are a lot of holes (in
> above example, B+3 - B+7) Hole can impact IO, because we can't do IO merge.
> 2. 1:1 size. Compression doesn't change disk size. If disk is 1T, we can only store
> 1T data even we do compression.
> 
> But this is for SSD only. Generally SSD firmware has a FTL layer to map disk
> sectors to flash nand. High end SSD firmware has filesystem-like FTL.
> 1. hole. Disk has a lot of holes, but SSD FTL can still store data continuous
> in nand. Even we can't do IO merge in OS layer, SSD firmware can do it.
> 2. 1:1 size. On one side, we write compressed data to SSD, which means less
> data is written to SSD. This will be very helpful to improve SSD garbage
> collection, and so write speed and life cycle. So even this is a problem, the
> target is still helpful. On the other side, advanced SSD FTL can easily do thin
> provision. For example, if nand is 1T and we let SSD report it as 2T, and use
> the SSD as compressed target. In such SSD, we don't have the 1:1 size issue.
> 
> So if SSD FTL can map non-continuous disk sectors to continuous nand and
> support thin provision, the compressed target will work very well.
> 
> V2->V3:
> Updated with new bio iter API
> 
> V1->V2:
> 1. Change name to insitu_comp, cleanup code, add comments and doc
> 2. Improve performance (extent locking, dedicated workqueue)
> 
> Signed-off-by: Shaohua Li <shli at fusionio.com>
> ---
>  Documentation/device-mapper/insitu-comp.txt |   50 
>  drivers/md/Kconfig                          |    6 
>  drivers/md/Makefile                         |    1 
>  drivers/md/dm-insitu-comp.c                 | 1480 ++++++++++++++++++++++++++++
>  drivers/md/dm-insitu-comp.h                 |  158 ++
>  5 files changed, 1695 insertions(+)
> 
> Index: linux/drivers/md/Kconfig
> ===================================================================
> --- linux.orig/drivers/md/Kconfig	2014-02-17 17:34:45.431464714 +0800
> +++ linux/drivers/md/Kconfig	2014-02-17 17:34:45.423464815 +0800
> @@ -295,6 +295,12 @@ config DM_CACHE_CLEANER
>           A simple cache policy that writes back all data to the
>           origin.  Used when decommissioning a dm-cache.
>  
> +config DM_INSITU_COMPRESSION
> +       tristate "Insitu compression target"
> +       depends on BLK_DEV_DM
> +       ---help---
> +         Allow volume managers to insitu compress data for SSD.
> +
>  config DM_MIRROR
>         tristate "Mirror target"
>         depends on BLK_DEV_DM
> Index: linux/drivers/md/Makefile
> ===================================================================
> --- linux.orig/drivers/md/Makefile	2014-02-17 17:34:45.431464714 +0800
> +++ linux/drivers/md/Makefile	2014-02-17 17:34:45.423464815 +0800
> @@ -53,6 +53,7 @@ obj-$(CONFIG_DM_VERITY)		+= dm-verity.o
>  obj-$(CONFIG_DM_CACHE)		+= dm-cache.o
>  obj-$(CONFIG_DM_CACHE_MQ)	+= dm-cache-mq.o
>  obj-$(CONFIG_DM_CACHE_CLEANER)	+= dm-cache-cleaner.o
> +obj-$(CONFIG_DM_INSITU_COMPRESSION)		+= dm-insitu-comp.o
>  
>  ifeq ($(CONFIG_DM_UEVENT),y)
>  dm-mod-objs			+= dm-uevent.o
> Index: linux/drivers/md/dm-insitu-comp.c
> ===================================================================
> --- /dev/null	1970-01-01 00:00:00.000000000 +0000
> +++ linux/drivers/md/dm-insitu-comp.c	2014-02-17 20:16:38.093360018 +0800
> @@ -0,0 +1,1480 @@
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/blkdev.h>
> +#include <linux/bio.h>
> +#include <linux/slab.h>
> +#include <linux/device-mapper.h>
> +#include <linux/dm-io.h>
> +#include <linux/crypto.h>
> +#include <linux/lzo.h>
> +#include <linux/kthread.h>
> +#include <linux/page-flags.h>
> +#include <linux/completion.h>
> +#include "dm-insitu-comp.h"
> +
> +#define DM_MSG_PREFIX "dm_insitu_comp"
> +
> +static struct insitu_comp_compressor_data compressors[] = {
> +	[INSITU_COMP_ALG_LZO] = {
> +		.name = "lzo",
> +		.comp_len = lzo_comp_len,
> +	},
> +	[INSITU_COMP_ALG_ZLIB] = {
> +		.name = "deflate",
> +	},
> +};
> +static int default_compressor;
> +
> +static struct kmem_cache *insitu_comp_io_range_cachep;
> +static struct kmem_cache *insitu_comp_meta_io_cachep;
> +
> +static struct insitu_comp_io_worker insitu_comp_io_workers[NR_CPUS];
> +static struct workqueue_struct *insitu_comp_wq;
> +
> +/* each block has 5 bits metadata */
> +static u8 insitu_comp_get_meta(struct insitu_comp_info *info, u64 block_index)
> +{
> +	u64 first_bit = block_index * INSITU_COMP_META_BITS;
> +	int bits, offset;
> +	u8 data, ret = 0;
> +
> +	offset = first_bit & 7;
> +	bits = min_t(u8, INSITU_COMP_META_BITS, 8 - offset);
> +
> +	data = info->meta_bitmap[first_bit >> 3];
> +	ret = (data >> offset) & ((1 << bits) - 1);
> +
> +	if (bits < INSITU_COMP_META_BITS) {
> +		data = info->meta_bitmap[(first_bit >> 3) + 1];
> +		bits = INSITU_COMP_META_BITS - bits;
> +		ret |= (data & ((1 << bits) - 1)) <<
> +			(INSITU_COMP_META_BITS - bits);
> +	}
> +	return ret;
> +}
> +
> +static void insitu_comp_set_meta(struct insitu_comp_info *info,
> +	u64 block_index, u8 meta, bool dirty_meta)
> +{
> +	u64 first_bit = block_index * INSITU_COMP_META_BITS;
> +	int bits, offset;
> +	u8 data;
> +	struct page *page;
> +
> +	offset = first_bit & 7;
> +	bits = min_t(u8, INSITU_COMP_META_BITS, 8 - offset);
> +
> +	data = info->meta_bitmap[first_bit >> 3];
> +	data &= ~(((1 << bits) - 1) << offset);
> +	data |= (meta & ((1 << bits) - 1)) << offset;
> +	info->meta_bitmap[first_bit >> 3] = data;
> +
> +	/*
> +	 * For writethrough, we write metadata directly. For writeback, if
> +	 * request is FUA, we do this too; otherwise we just dirty the page,
> +	 * which will be flush out in an interval
> +	 */
> +	if (info->write_mode == INSITU_COMP_WRITE_BACK) {
> +		page = vmalloc_to_page(&info->meta_bitmap[first_bit >> 3]);
> +		if (dirty_meta)
> +			SetPageDirty(page);
> +		else
> +			ClearPageDirty(page);
> +	}
> +
> +	if (bits < INSITU_COMP_META_BITS) {
> +		meta >>= bits;
> +		data = info->meta_bitmap[(first_bit >> 3) + 1];
> +		bits = INSITU_COMP_META_BITS - bits;
> +		data = (data >> bits) << bits;
> +		data |= meta & ((1 << bits) - 1);
> +		info->meta_bitmap[(first_bit >> 3) + 1] = data;
> +
> +		if (info->write_mode == INSITU_COMP_WRITE_BACK) {
> +			page = vmalloc_to_page(&info->meta_bitmap[
> +						(first_bit >> 3) + 1]);
> +			if (dirty_meta)
> +				SetPageDirty(page);
> +			else
> +				ClearPageDirty(page);
> +		}
> +	}
> +}
> +
> +/*
> + * set metadata for an extent since block @block_index, length is
> + * @logical_blocks.  The extent uses @data_sectors sectors
> + */
> +static void insitu_comp_set_extent(struct insitu_comp_req *req,
> +	u64 block_index, u16 logical_blocks, sector_t data_sectors)
> +{
> +	int i;
> +	u8 data;
> +
> +	for (i = 0; i < logical_blocks; i++) {
> +		data = min_t(sector_t, data_sectors, 8);
> +		data_sectors -= data;
> +		if (i != 0)
> +			data |= INSITU_COMP_TAIL_MASK;
> +		/* For FUA, we write out meta data directly */
> +		insitu_comp_set_meta(req->info, block_index + i, data,
> +					!(insitu_req_rw(req) & REQ_FUA));
> +	}
> +}
> +
> +/*
> + * get metadata for an extent covering block @block_index. @first_block_index
> + * returns the first block of the extent. @logical_sectors returns the extent
> + * length. @data_sectors returns the sectors the extent uses
> + */
> +static void insitu_comp_get_extent(struct insitu_comp_info *info,
> +	u64 block_index, u64 *first_block_index, u16 *logical_sectors,
> +	u16 *data_sectors)
> +{
> +	u8 data;
> +
> +	data = insitu_comp_get_meta(info, block_index);
> +	while (data & INSITU_COMP_TAIL_MASK) {
> +		block_index--;
> +		data = insitu_comp_get_meta(info, block_index);
> +	}
> +	*first_block_index = block_index;
> +	*logical_sectors = INSITU_COMP_BLOCK_SIZE >> 9;
> +	*data_sectors = data & INSITU_COMP_LENGTH_MASK;
> +	block_index++;
> +	while (block_index < info->data_blocks) {
> +		data = insitu_comp_get_meta(info, block_index);
> +		if (!(data & INSITU_COMP_TAIL_MASK))
> +			break;
> +		*logical_sectors += INSITU_COMP_BLOCK_SIZE >> 9;
> +		*data_sectors += data & INSITU_COMP_LENGTH_MASK;
> +		block_index++;
> +	}
> +}
> +
> +static int insitu_comp_access_super(struct insitu_comp_info *info,
> +	void *addr, int rw)
> +{
> +	struct dm_io_region region;
> +	struct dm_io_request req;
> +	unsigned long io_error = 0;
> +	int ret;
> +
> +	region.bdev = info->dev->bdev;
> +	region.sector = 0;
> +	region.count = INSITU_COMP_BLOCK_SIZE >> 9;
> +
> +	req.bi_rw = rw;
> +	req.mem.type = DM_IO_KMEM;
> +	req.mem.offset = 0;
> +	req.mem.ptr.addr = addr;
> +	req.notify.fn = NULL;
> +	req.client = info->io_client;
> +
> +	ret = dm_io(&req, 1, &region, &io_error);
> +	if (ret || io_error)
> +		return -EIO;
> +	return 0;
> +}
> +
> +static void insitu_comp_meta_io_done(unsigned long error, void *context)
> +{
> +	struct insitu_comp_meta_io *meta_io = context;
> +
> +	meta_io->fn(meta_io->data, error);
> +	kmem_cache_free(insitu_comp_meta_io_cachep, meta_io);
> +}
> +
> +static int insitu_comp_write_meta(struct insitu_comp_info *info,
> +	u64 start_page, u64 end_page, void *data,
> +	void (*fn)(void *data, unsigned long error), int rw)
> +{
> +	struct insitu_comp_meta_io *meta_io;
> +
> +	BUG_ON(end_page > info->meta_bitmap_pages);
> +
> +	meta_io = kmem_cache_alloc(insitu_comp_meta_io_cachep, GFP_NOIO);
> +	if (!meta_io) {
> +		fn(data, -ENOMEM);
> +		return -ENOMEM;
> +	}
> +	meta_io->data = data;
> +	meta_io->fn = fn;
> +
> +	meta_io->io_region.bdev = info->dev->bdev;
> +	meta_io->io_region.sector = INSITU_COMP_META_START_SECTOR +
> +					(start_page << (PAGE_SHIFT - 9));
> +	meta_io->io_region.count = (end_page - start_page) << (PAGE_SHIFT - 9);
> +
> +	atomic64_add(meta_io->io_region.count << 9, &info->meta_write_size);
> +
> +	meta_io->io_req.bi_rw = rw;
> +	meta_io->io_req.mem.type = DM_IO_VMA;
> +	meta_io->io_req.mem.offset = 0;
> +	meta_io->io_req.mem.ptr.addr = info->meta_bitmap +
> +						(start_page << PAGE_SHIFT);
> +	meta_io->io_req.notify.fn = insitu_comp_meta_io_done;
> +	meta_io->io_req.notify.context = meta_io;
> +	meta_io->io_req.client = info->io_client;
> +
> +	dm_io(&meta_io->io_req, 1, &meta_io->io_region, NULL);
> +	return 0;
> +}
> +
> +struct writeback_flush_data {
> +	struct completion complete;
> +	atomic_t cnt;
> +};
> +
> +static void writeback_flush_io_done(void *data, unsigned long error)
> +{
> +	struct writeback_flush_data *wb = data;
> +
> +	if (atomic_dec_return(&wb->cnt))
> +		return;
> +	complete(&wb->complete);
> +}
> +
> +static void insitu_comp_flush_dirty_meta(struct insitu_comp_info *info,
> +			struct writeback_flush_data *data)
> +{
> +	struct page *page;
> +	u64 start = 0, index;
> +	u32 pending = 0, cnt = 0;
> +	bool dirty;
> +	struct blk_plug plug;
> +
> +	blk_start_plug(&plug);
> +	for (index = 0; index < info->meta_bitmap_pages; index++, cnt++) {
> +		if (cnt == 256) {
> +			cnt = 0;
> +			cond_resched();
> +		}
> +
> +		page = vmalloc_to_page(info->meta_bitmap +
> +					(index << PAGE_SHIFT));
> +		dirty = TestClearPageDirty(page);
> +
> +		if (pending == 0 && dirty) {
> +			start = index;
> +			pending++;
> +			continue;
> +		} else if (pending == 0)
> +			continue;
> +		else if (pending > 0 && dirty) {
> +			pending++;
> +			continue;
> +		}
> +
> +		/* pending > 0 && !dirty */
> +		atomic_inc(&data->cnt);
> +		insitu_comp_write_meta(info, start, start + pending, data,
> +			writeback_flush_io_done, WRITE);
> +		pending = 0;
> +	}
> +
> +	if (pending > 0) {
> +		atomic_inc(&data->cnt);
> +		insitu_comp_write_meta(info, start, start + pending, data,
> +			writeback_flush_io_done, WRITE);
> +	}
> +	blkdev_issue_flush(info->dev->bdev, GFP_NOIO, NULL);
> +	blk_finish_plug(&plug);
> +}
> +
> +/* writeback thread flushs all dirty metadata to disk in an interval */
> +static int insitu_comp_meta_writeback_thread(void *data)
> +{
> +	struct insitu_comp_info *info = data;
> +	struct writeback_flush_data wb;
> +
> +	atomic_set(&wb.cnt, 1);
> +	init_completion(&wb.complete);
> +
> +	while (!kthread_should_stop()) {
> +		schedule_timeout_interruptible(
> +			msecs_to_jiffies(info->writeback_delay * 1000));
> +		insitu_comp_flush_dirty_meta(info, &wb);
> +	}
> +
> +	insitu_comp_flush_dirty_meta(info, &wb);
> +
> +	writeback_flush_io_done(&wb, 0);
> +	wait_for_completion(&wb.complete);
> +	return 0;
> +}
> +
> +static int insitu_comp_init_meta(struct insitu_comp_info *info, bool new)
> +{
> +	struct dm_io_region region;
> +	struct dm_io_request req;
> +	unsigned long io_error = 0;
> +	struct blk_plug plug;
> +	int ret;
> +	ssize_t len = DIV_ROUND_UP_ULL(info->meta_bitmap_bits, BITS_PER_LONG);
> +
> +	len *= sizeof(unsigned long);
> +
> +	region.bdev = info->dev->bdev;
> +	region.sector = INSITU_COMP_META_START_SECTOR;
> +	region.count = (len + 511) >> 9;
> +
> +	req.mem.type = DM_IO_VMA;
> +	req.mem.offset = 0;
> +	req.mem.ptr.addr = info->meta_bitmap;
> +	req.notify.fn = NULL;
> +	req.client = info->io_client;
> +
> +	blk_start_plug(&plug);
> +	if (new) {
> +		memset(info->meta_bitmap, 0, len);
> +		req.bi_rw = WRITE_FLUSH;
> +		ret = dm_io(&req, 1, &region, &io_error);
> +	} else {
> +		req.bi_rw = READ;
> +		ret = dm_io(&req, 1, &region, &io_error);
> +	}
> +	blk_finish_plug(&plug);
> +
> +	if (ret || io_error) {
> +		info->ti->error = "Access metadata error";
> +		return -EIO;
> +	}
> +
> +	if (info->write_mode == INSITU_COMP_WRITE_BACK) {
> +		info->writeback_tsk = kthread_run(
> +			insitu_comp_meta_writeback_thread,
> +			info, "insitu_comp_writeback");
> +		if (!info->writeback_tsk) {
> +			info->ti->error = "Create writeback thread error";
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int insitu_comp_alloc_compressor(struct insitu_comp_info *info)
> +{
> +	int i;
> +
> +	for_each_possible_cpu(i) {
> +		info->tfm[i] = crypto_alloc_comp(
> +			compressors[info->comp_alg].name, 0, 0);
> +		if (IS_ERR(info->tfm[i])) {
> +			info->tfm[i] = NULL;
> +			goto err;
> +		}
> +	}
> +	return 0;
> +err:
> +	for_each_possible_cpu(i) {
> +		if (info->tfm[i]) {
> +			crypto_free_comp(info->tfm[i]);
> +			info->tfm[i] = NULL;
> +		}
> +	}
> +	return -ENOMEM;
> +}
> +
> +static void insitu_comp_free_compressor(struct insitu_comp_info *info)
> +{
> +	int i;
> +
> +	for_each_possible_cpu(i) {
> +		if (info->tfm[i]) {
> +			crypto_free_comp(info->tfm[i]);
> +			info->tfm[i] = NULL;
> +		}
> +	}
> +}
> +
> +static int insitu_comp_read_or_create_super(struct insitu_comp_info *info)
> +{
> +	void *addr;
> +	struct insitu_comp_super_block *super;
> +	u64 total_blocks;
> +	u64 data_blocks, meta_blocks;
> +	u32 rem, cnt;
> +	bool new_super = false;
> +	int ret;
> +	ssize_t len;
> +
> +	total_blocks = i_size_read(info->dev->bdev->bd_inode) >>
> +					INSITU_COMP_BLOCK_SHIFT;
> +	data_blocks = total_blocks - 1;
> +	rem = do_div(data_blocks, INSITU_COMP_BLOCK_SIZE * 8 +
> +			INSITU_COMP_META_BITS);
> +	meta_blocks = data_blocks * INSITU_COMP_META_BITS;
> +	data_blocks *= INSITU_COMP_BLOCK_SIZE * 8;
> +
> +	cnt = rem;
> +	rem /= (INSITU_COMP_BLOCK_SIZE * 8 / INSITU_COMP_META_BITS + 1);
> +	data_blocks += rem * (INSITU_COMP_BLOCK_SIZE * 8 /
> +				INSITU_COMP_META_BITS);
> +	meta_blocks += rem;
> +
> +	cnt %= (INSITU_COMP_BLOCK_SIZE * 8 / INSITU_COMP_META_BITS + 1);
> +	meta_blocks += 1;
> +	data_blocks += cnt - 1;
> +
> +	info->data_blocks = data_blocks;
> +	info->data_start = (1 + meta_blocks) << INSITU_COMP_BLOCK_SECTOR_SHIFT;
> +
> +	addr = kzalloc(INSITU_COMP_BLOCK_SIZE, GFP_KERNEL);
> +	if (!addr) {
> +		info->ti->error = "Cannot allocate super";
> +		return -ENOMEM;
> +	}
> +
> +	super = addr;
> +	ret = insitu_comp_access_super(info, addr, READ);
> +	if (ret)
> +		goto out;
> +
> +	if (le64_to_cpu(super->magic) == INSITU_COMP_SUPER_MAGIC) {
> +		if (le64_to_cpu(super->version) != INSITU_COMP_VERSION ||
> +		    le64_to_cpu(super->meta_blocks) != meta_blocks ||
> +		    le64_to_cpu(super->data_blocks) != data_blocks) {
> +			info->ti->error = "Super is invalid";
> +			ret = -EINVAL;
> +			goto out;
> +		}
> +		if (!crypto_has_comp(compressors[super->comp_alg].name, 0, 0)) {
> +			info->ti->error =
> +					"Compressor algorithm doesn't support";
> +			ret = -EINVAL;
> +			goto out;
> +		}
> +	} else {
> +		super->magic = cpu_to_le64(INSITU_COMP_SUPER_MAGIC);
> +		super->version = cpu_to_le64(INSITU_COMP_VERSION);
> +		super->meta_blocks = cpu_to_le64(meta_blocks);
> +		super->data_blocks = cpu_to_le64(data_blocks);
> +		super->comp_alg = default_compressor;
> +		ret = insitu_comp_access_super(info, addr, WRITE_FUA);
> +		if (ret) {
> +			info->ti->error = "Access super fails";
> +			goto out;
> +		}
> +		new_super = true;
> +	}
> +
> +	info->comp_alg = super->comp_alg;
> +	if (insitu_comp_alloc_compressor(info)) {
> +		ret = -ENOMEM;
> +		goto out;
> +	}
> +
> +	info->meta_bitmap_bits = data_blocks * INSITU_COMP_META_BITS;
> +	len = DIV_ROUND_UP_ULL(info->meta_bitmap_bits, BITS_PER_LONG);
> +	len *= sizeof(unsigned long);
> +	info->meta_bitmap_pages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
> +	info->meta_bitmap = vmalloc(info->meta_bitmap_pages * PAGE_SIZE);
> +	if (!info->meta_bitmap) {
> +		ret = -ENOMEM;
> +		goto bitmap_err;
> +	}
> +
> +	ret = insitu_comp_init_meta(info, new_super);
> +	if (ret)
> +		goto meta_err;
> +
> +	return 0;
> +meta_err:
> +	vfree(info->meta_bitmap);
> +bitmap_err:
> +	insitu_comp_free_compressor(info);
> +out:
> +	kfree(addr);
> +	return ret;
> +}
> +
> +/*
> + * <dev> <writethough>/<writeback> <meta_commit_delay>
> + */
> +static int insitu_comp_ctr(struct dm_target *ti, unsigned int argc, char **argv)
> +{
> +	struct insitu_comp_info *info;
> +	char write_mode[15];
> +	int ret, i;
> +
> +	if (argc < 2) {
> +		ti->error = "Invalid argument count";
> +		return -EINVAL;
> +	}
> +
> +	info = kzalloc(sizeof(*info), GFP_KERNEL);
> +	if (!info) {
> +		ti->error = "Cannot allocate context";
> +		return -ENOMEM;
> +	}
> +	info->ti = ti;
> +
> +	if (sscanf(argv[1], "%s", write_mode) != 1) {
> +		ti->error = "Invalid argument";
> +		ret = -EINVAL;
> +		goto err_para;
> +	}
> +
> +	if (strcmp(write_mode, "writeback") == 0) {
> +		if (argc != 3) {
> +			ti->error = "Invalid argument";
> +			ret = -EINVAL;
> +			goto err_para;
> +		}
> +		info->write_mode = INSITU_COMP_WRITE_BACK;
> +		if (sscanf(argv[2], "%u", &info->writeback_delay) != 1) {
> +			ti->error = "Invalid argument";
> +			ret = -EINVAL;
> +			goto err_para;
> +		}
> +	} else if (strcmp(write_mode, "writethrough") == 0) {
> +		info->write_mode = INSITU_COMP_WRITE_THROUGH;
> +	} else {
> +		ti->error = "Invalid argument";
> +		ret = -EINVAL;
> +		goto err_para;
> +	}
> +
> +	if (dm_get_device(ti, argv[0], dm_table_get_mode(ti->table),
> +							&info->dev)) {
> +		ti->error = "Can't get device";
> +		ret = -EINVAL;
> +		goto err_para;
> +	}
> +
> +	info->io_client = dm_io_client_create();
> +	if (!info->io_client) {
> +		ti->error = "Can't create io client";
> +		ret = -EINVAL;
> +		goto err_ioclient;
> +	}
> +
> +	if (bdev_logical_block_size(info->dev->bdev) != 512) {
> +		ti->error = "Can't logical block size too big";
> +		ret = -EINVAL;
> +		goto err_blocksize;
> +	}
> +
> +	ret = insitu_comp_read_or_create_super(info);
> +	if (ret)
> +		goto err_blocksize;
> +
> +	for (i = 0; i < BITMAP_HASH_LEN; i++) {
> +		info->bitmap_locks[i].io_running = 0;
> +		spin_lock_init(&info->bitmap_locks[i].wait_lock);
> +		INIT_LIST_HEAD(&info->bitmap_locks[i].wait_list);
> +	}
> +
> +	atomic64_set(&info->compressed_write_size, 0);
> +	atomic64_set(&info->uncompressed_write_size, 0);
> +	atomic64_set(&info->meta_write_size, 0);
> +	ti->num_flush_bios = 1;
> +	/* doesn't support discard yet */
> +	ti->per_bio_data_size = sizeof(struct insitu_comp_req);
> +	ti->private = info;
> +	return 0;
> +err_blocksize:
> +	dm_io_client_destroy(info->io_client);
> +err_ioclient:
> +	dm_put_device(ti, info->dev);
> +err_para:
> +	kfree(info);
> +	return ret;
> +}
> +
> +static void insitu_comp_dtr(struct dm_target *ti)
> +{
> +	struct insitu_comp_info *info = ti->private;
> +
> +	if (info->write_mode == INSITU_COMP_WRITE_BACK)
> +		kthread_stop(info->writeback_tsk);
> +	insitu_comp_free_compressor(info);
> +	vfree(info->meta_bitmap);
> +	dm_io_client_destroy(info->io_client);
> +	dm_put_device(ti, info->dev);
> +	kfree(info);
> +}
> +
> +static u64 insitu_comp_sector_to_block(sector_t sect)
> +{
> +	return sect >> INSITU_COMP_BLOCK_SECTOR_SHIFT;
> +}
> +
> +static struct insitu_comp_hash_lock *
> +insitu_comp_block_hash_lock(struct insitu_comp_info *info, u64 block_index)
> +{
> +	return &info->bitmap_locks[(block_index >> HASH_LOCK_SHIFT) &
> +			BITMAP_HASH_MASK];
> +}
> +
> +static struct insitu_comp_hash_lock *
> +insitu_comp_trylock_block(struct insitu_comp_info *info,
> +	struct insitu_comp_req *req, u64 block_index)
> +{
> +	struct insitu_comp_hash_lock *hash_lock;
> +
> +	hash_lock = insitu_comp_block_hash_lock(req->info, block_index);
> +
> +	spin_lock_irq(&hash_lock->wait_lock);
> +	if (!hash_lock->io_running) {
> +		hash_lock->io_running = 1;
> +		spin_unlock_irq(&hash_lock->wait_lock);
> +		return hash_lock;
> +	}
> +	list_add_tail(&req->sibling, &hash_lock->wait_list);
> +	spin_unlock_irq(&hash_lock->wait_lock);
> +	return NULL;
> +}
> +
> +static void insitu_comp_queue_req_list(struct insitu_comp_info *info,
> +	struct list_head *list);
> +static void insitu_comp_unlock_block(struct insitu_comp_info *info,
> +	struct insitu_comp_req *req, struct insitu_comp_hash_lock *hash_lock)
> +{
> +	LIST_HEAD(pending_list);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&hash_lock->wait_lock, flags);
> +	/* wakeup all pending reqs to avoid live lock */
> +	list_splice_init(&hash_lock->wait_list, &pending_list);
> +	hash_lock->io_running = 0;
> +	spin_unlock_irqrestore(&hash_lock->wait_lock, flags);
> +
> +	insitu_comp_queue_req_list(info, &pending_list);
> +}
> +
> +static void insitu_comp_unlock_req_range(struct insitu_comp_req *req)
> +{
> +	insitu_comp_unlock_block(req->info, req, req->lock);
> +}
> +
> +/* Check comments of HASH_LOCK_SHIFT. each request only need take one lock */
> +static int insitu_comp_lock_req_range(struct insitu_comp_req *req)
> +{
> +	u64 block_index, tmp;
> +
> +	block_index = insitu_comp_sector_to_block(insitu_req_start_sector(req));
> +	tmp = insitu_comp_sector_to_block(insitu_req_end_sector(req) - 1);
> +	BUG_ON(insitu_comp_block_hash_lock(req->info, block_index) !=
> +			insitu_comp_block_hash_lock(req->info, tmp));
> +
> +	req->lock = insitu_comp_trylock_block(req->info, req, block_index);
> +	if (!req->lock)
> +		return 0;
> +
> +	return 1;
> +}
> +
> +static void insitu_comp_queue_req(struct insitu_comp_info *info,
> +	struct insitu_comp_req *req)
> +{
> +	unsigned long flags;
> +	struct insitu_comp_io_worker *worker =
> +		&insitu_comp_io_workers[req->cpu];
> +
> +	spin_lock_irqsave(&worker->lock, flags);
> +	list_add_tail(&req->sibling, &worker->pending);
> +	spin_unlock_irqrestore(&worker->lock, flags);
> +
> +	queue_work_on(req->cpu, insitu_comp_wq, &worker->work);
> +}
> +
> +static void insitu_comp_queue_req_list(struct insitu_comp_info *info,
> +	struct list_head *list)
> +{
> +	struct insitu_comp_req *req;
> +	while (!list_empty(list)) {
> +		req = list_first_entry(list, struct insitu_comp_req, sibling);
> +		list_del_init(&req->sibling);
> +		insitu_comp_queue_req(info, req);
> +	}
> +}
> +
> +static void insitu_comp_get_req(struct insitu_comp_req *req)
> +{
> +	atomic_inc(&req->io_pending);
> +}
> +
> +static void insitu_comp_free_io_range(struct insitu_comp_io_range *io)
> +{
> +	kfree(io->decomp_data);
> +	kfree(io->comp_data);
> +	kmem_cache_free(insitu_comp_io_range_cachep, io);
> +}
> +
> +static void insitu_comp_put_req(struct insitu_comp_req *req)
> +{
> +	struct insitu_comp_io_range *io;
> +
> +	if (atomic_dec_return(&req->io_pending))
> +		return;
> +
> +	if (req->stage == STAGE_INIT) /* waiting for locking */
> +		return;
> +
> +	if (req->stage == STAGE_READ_DECOMP ||
> +	    req->stage == STAGE_WRITE_COMP ||
> +	    req->result)
> +		req->stage = STAGE_DONE;
> +
> +	if (req->stage != STAGE_DONE) {
> +		insitu_comp_queue_req(req->info, req);
> +		return;
> +	}
> +
> +	while (!list_empty(&req->all_io)) {
> +		io = list_entry(req->all_io.next, struct insitu_comp_io_range,
> +			next);
> +		list_del(&io->next);
> +		insitu_comp_free_io_range(io);
> +	}
> +
> +	insitu_comp_unlock_req_range(req);
> +
> +	insitu_req_endio(req, req->result);
> +}
> +
> +static void insitu_comp_io_range_done(unsigned long error, void *context)
> +{
> +	struct insitu_comp_io_range *io = context;
> +
> +	if (error)
> +		io->req->result = error;
> +	insitu_comp_put_req(io->req);
> +}
> +
> +static inline int insitu_comp_compressor_len(struct insitu_comp_info *info,
> +	int len)
> +{
> +	if (compressors[info->comp_alg].comp_len)
> +		return compressors[info->comp_alg].comp_len(len);
> +	return len;
> +}
> +
> +/*
> + * caller should set region.sector, region.count. bi_rw. IO always to/from
> + * comp_data
> + */
> +static struct insitu_comp_io_range *
> +insitu_comp_create_io_range(struct insitu_comp_req *req, int comp_len,
> +	int decomp_len)
> +{
> +	struct insitu_comp_io_range *io;
> +
> +	io = kmem_cache_alloc(insitu_comp_io_range_cachep, GFP_NOIO);
> +	if (!io)
> +		return NULL;
> +
> +	io->comp_data = kmalloc(insitu_comp_compressor_len(req->info, comp_len),
> +								GFP_NOIO);
> +	io->decomp_data = kmalloc(decomp_len, GFP_NOIO);
> +	if (!io->decomp_data || !io->comp_data) {
> +		kfree(io->decomp_data);
> +		kfree(io->comp_data);
> +		kmem_cache_free(insitu_comp_io_range_cachep, io);
> +		return NULL;
> +	}
> +
> +	io->io_req.notify.fn = insitu_comp_io_range_done;
> +	io->io_req.notify.context = io;
> +	io->io_req.client = req->info->io_client;
> +	io->io_req.mem.type = DM_IO_KMEM;
> +	io->io_req.mem.ptr.addr = io->comp_data;
> +	io->io_req.mem.offset = 0;
> +
> +	io->io_region.bdev = req->info->dev->bdev;
> +
> +	io->decomp_len = decomp_len;
> +	io->comp_len = comp_len;
> +	io->req = req;
> +	return io;
> +}
> +
> +static void insitu_comp_req_copy(struct insitu_comp_req *req, off_t req_off, void *buf,
> +		ssize_t len, bool to_buf)
> +{
> +	struct bio *bio = req->bio;
> +	struct bvec_iter iter;
> +	off_t buf_off = 0;
> +	ssize_t size;
> +	void *addr;
> +
> +	iter = bio->bi_iter;
> +	bio_advance_iter(bio, &iter, req_off);
> +
> +	while (len) {
> +		addr = kmap_atomic(bio_iter_page(bio, iter));
> +		size = min_t(ssize_t, len, bio_iter_len(bio, iter));
> +		if (to_buf)
> +			memcpy(buf + buf_off, addr + bio_iter_offset(bio, iter),
> +				size);
> +		else
> +			memcpy(addr + bio_iter_offset(bio, iter), buf + buf_off,
> +				size);
> +		kunmap_atomic(addr);
> +
> +		buf_off += size;
> +		len -= size;
> +
> +		bio_advance_iter(bio, &iter, size);
> +	}
> +}
> +
> +/*
> + * return value:
> + * < 0 : error
> + * == 0 : ok
> + * == 1 : ok, but comp/decomp is skipped
> + * Compressed data size is roundup of 512, which makes the payload.
> + * We store the actual compressed length in the last u32 of the payload.
> + * If there is no free space, we add 512 to the payload size.
> + */
> +static int insitu_comp_io_range_comp(struct insitu_comp_info *info,
> +	void *comp_data, unsigned int *comp_len, void *decomp_data,
> +	unsigned int decomp_len, bool do_comp)
> +{
> +	struct crypto_comp *tfm;
> +	u32 *addr;
> +	unsigned int actual_comp_len;
> +	int ret;
> +
> +	if (do_comp) {
> +		actual_comp_len = *comp_len;
> +
> +		tfm = info->tfm[get_cpu()];
> +		ret = crypto_comp_compress(tfm, decomp_data, decomp_len,
> +			comp_data, &actual_comp_len);
> +		put_cpu();
> +
> +		atomic64_add(decomp_len, &info->uncompressed_write_size);
> +		if (ret || decomp_len < actual_comp_len + sizeof(u32) + 512) {
> +			*comp_len = decomp_len;
> +			atomic64_add(*comp_len, &info->compressed_write_size);
> +			return 1;
> +		}
> +
> +		*comp_len = round_up(actual_comp_len, 512);
> +		if (*comp_len - actual_comp_len < sizeof(u32))
> +			*comp_len += 512;
> +		atomic64_add(*comp_len, &info->compressed_write_size);
> +		addr = comp_data + *comp_len;
> +		addr--;
> +		*addr = cpu_to_le32(actual_comp_len);
> +	} else {
> +		if (*comp_len == decomp_len)
> +			return 1;
> +		addr = comp_data + *comp_len;
> +		addr--;
> +		actual_comp_len = le32_to_cpu(*addr);
> +
> +		tfm = info->tfm[get_cpu()];
> +		ret = crypto_comp_decompress(tfm, comp_data, actual_comp_len,
> +			decomp_data, &decomp_len);
> +		put_cpu();
> +		if (ret)
> +			return -EINVAL;
> +	}
> +	return 0;
> +}
> +
> +/*
> + * compressed data is updated. We decompress it and fill req. If there is no
> + * valid compressed data, we just zero req
> + */
> +static void insitu_comp_handle_read_decomp(struct insitu_comp_req *req)
> +{
> +	struct insitu_comp_io_range *io;
> +	off_t req_off = 0;
> +	int ret;
> +
> +	req->stage = STAGE_READ_DECOMP;
> +
> +	if (req->result)
> +		return;
> +
> +	list_for_each_entry(io, &req->all_io, next) {
> +		ssize_t dst_off = 0, src_off = 0, len;
> +
> +		io->io_region.sector -= req->info->data_start;
> +
> +		/* Do decomp here */
> +		ret = insitu_comp_io_range_comp(req->info, io->comp_data,
> +			&io->comp_len, io->decomp_data, io->decomp_len, false);
> +		if (ret < 0) {
> +			req->result = -EIO;
> +			return;
> +		}
> +
> +		if (io->io_region.sector >= insitu_req_start_sector(req))
> +			dst_off = (io->io_region.sector - insitu_req_start_sector(req))
> +				<< 9;
> +		else
> +			src_off = (insitu_req_start_sector(req) - io->io_region.sector)
> +				<< 9;
> +		len = min_t(ssize_t, io->decomp_len - src_off,
> +			(insitu_req_sectors(req) << 9) - dst_off);
> +
> +		/* io range in all_io list is ordered for read IO */
> +		while (req_off != dst_off) {
> +			ssize_t size = min_t(ssize_t, PAGE_SIZE,
> +					dst_off - req_off);
> +			insitu_comp_req_copy(req, req_off,
> +				empty_zero_page, size, false);
> +			req_off += size;
> +		}
> +
> +		if (ret == 1) /* uncompressed, valid data is in .comp_data */
> +			insitu_comp_req_copy(req, dst_off,
> +					io->comp_data + src_off, len, false);
> +		else
> +			insitu_comp_req_copy(req, dst_off,
> +					io->decomp_data + src_off, len, false);
> +		req_off = dst_off + len;
> +	}
> +
> +	while (req_off != (insitu_req_sectors(req) << 9)) {
> +		ssize_t size = min_t(ssize_t, PAGE_SIZE,
> +			(insitu_req_sectors(req) << 9) - req_off);
> +		insitu_comp_req_copy(req, req_off, empty_zero_page,
> +			size, false);
> +		req_off += size;
> +	}
> +}
> +
> +/*
> + * read one extent data from disk. The extent starts from block @block and has
> + * @data_sectors data
> + */
> +static void insitu_comp_read_one_extent(struct insitu_comp_req *req, u64 block,
> +	u16 logical_sectors, u16 data_sectors)
> +{
> +	struct insitu_comp_io_range *io;
> +
> +	io = insitu_comp_create_io_range(req, data_sectors << 9,
> +		logical_sectors << 9);
> +	if (!io) {
> +		req->result = -EIO;
> +		return;
> +	}
> +
> +	insitu_comp_get_req(req);
> +	list_add_tail(&io->next, &req->all_io);
> +
> +	io->io_region.sector = (block << INSITU_COMP_BLOCK_SECTOR_SHIFT) +
> +				req->info->data_start;
> +	io->io_region.count = data_sectors;
> +
> +	io->io_req.bi_rw = READ;
> +	dm_io(&io->io_req, 1, &io->io_region, NULL);
> +}
> +
> +static void insitu_comp_handle_read_read_existing(struct insitu_comp_req *req)
> +{
> +	u64 block_index, first_block_index;
> +	u16 logical_sectors, data_sectors;
> +
> +	req->stage = STAGE_READ_EXISTING;
> +
> +	block_index = insitu_comp_sector_to_block(insitu_req_start_sector(req));
> +again:
> +	insitu_comp_get_extent(req->info, block_index, &first_block_index,
> +		&logical_sectors, &data_sectors);
> +	if (data_sectors > 0)
> +		insitu_comp_read_one_extent(req, first_block_index,
> +			logical_sectors, data_sectors);
> +
> +	if (req->result)
> +		return;
> +
> +	block_index = first_block_index + (logical_sectors >>
> +				INSITU_COMP_BLOCK_SECTOR_SHIFT);
> +	/* the request might cover several extents */
> +	if ((block_index << INSITU_COMP_BLOCK_SECTOR_SHIFT) <
> +			insitu_req_end_sector(req))
> +		goto again;
> +
> +	/* A shortcut if all data is in already */
> +	if (list_empty(&req->all_io))
> +		insitu_comp_handle_read_decomp(req);
> +}
> +
> +static void insitu_comp_handle_read_request(struct insitu_comp_req *req)
> +{
> +	insitu_comp_get_req(req);
> +
> +	if (req->stage == STAGE_INIT) {
> +		if (!insitu_comp_lock_req_range(req)) {
> +			insitu_comp_put_req(req);
> +			return;
> +		}
> +
> +		insitu_comp_handle_read_read_existing(req);
> +	} else if (req->stage == STAGE_READ_EXISTING)
> +		insitu_comp_handle_read_decomp(req);
> +
> +	insitu_comp_put_req(req);
> +}
> +
> +static void insitu_comp_write_meta_done(void *context, unsigned long error)
> +{
> +	struct insitu_comp_req *req = context;
> +	insitu_comp_put_req(req);
> +}
> +
> +static u64 insitu_comp_block_meta_page_index(u64 block, bool end)
> +{
> +	u64 bits = block * INSITU_COMP_META_BITS - !!end;
> +	/* (1 << 3) bits per byte */
> +	return bits >> (3 + PAGE_SHIFT);
> +}
> +
> +/*
> + * the request covers some extents partially. Decompress data of the extents,
> + * compress remaining valid data, and finally write them out
> + */
> +static int insitu_comp_handle_write_modify(struct insitu_comp_io_range *io,
> +	u64 *meta_start, u64 *meta_end, bool *handle_req)
> +{
> +	struct insitu_comp_req *req = io->req;
> +	sector_t start, count;
> +	unsigned int comp_len;
> +	off_t offset;
> +	u64 page_index;
> +	int ret;
> +
> +	io->io_region.sector -= req->info->data_start;
> +
> +	/* decompress original data */
> +	ret = insitu_comp_io_range_comp(req->info, io->comp_data, &io->comp_len,
> +			io->decomp_data, io->decomp_len, false);
> +	if (ret < 0) {
> +		req->result = -EINVAL;
> +		return -EIO;
> +	}
> +
> +	start = io->io_region.sector;
> +	count = io->decomp_len >> 9;
> +	if (start < insitu_req_start_sector(req) && start + count >
> +					insitu_req_end_sector(req)) {
> +		/* we don't split an extent */
> +		if (ret == 1) {
> +			memcpy(io->decomp_data, io->comp_data, io->decomp_len);
> +			insitu_comp_req_copy(req, 0,
> +			   io->decomp_data + ((insitu_req_start_sector(req) - start) <<
> +			   9), insitu_req_sectors(req) << 9, true);
> +		} else {
> +			insitu_comp_req_copy(req, 0,
> +			   io->decomp_data + ((insitu_req_start_sector(req) - start) <<
> +			   9), insitu_req_sectors(req) << 9, true);
> +			kfree(io->comp_data);
> +			/* New compressed len might be bigger */
> +			io->comp_data = kmalloc(insitu_comp_compressor_len(
> +				req->info, io->decomp_len), GFP_NOIO);
> +			io->comp_len = io->decomp_len;
> +			if (!io->comp_data) {
> +				req->result = -ENOMEM;
> +				return -EIO;
> +			}
> +			io->io_req.mem.ptr.addr = io->comp_data;
> +		}
> +		/* need compress data */
> +		ret = 0;
> +		offset = 0;
> +		*handle_req = false;
> +	} else if (start < insitu_req_start_sector(req)) {
> +		count = insitu_req_start_sector(req) - start;
> +		offset = 0;
> +	} else {
> +		offset = insitu_req_end_sector(req) - start;
> +		start = insitu_req_end_sector(req);
> +		count = count - offset;
> +	}
> +
> +	/* Original data is uncompressed, we don't need writeback */
> +	if (ret == 1) {
> +		comp_len = count << 9;
> +		goto handle_meta;
> +	}
> +
> +	/* assume compress less data uses less space (at least 4k lsess data) */
> +	comp_len = io->comp_len;
> +	ret = insitu_comp_io_range_comp(req->info, io->comp_data, &comp_len,
> +		io->decomp_data + (offset << 9), count << 9, true);
> +	if (ret < 0) {
> +		req->result = -EIO;
> +		return -EIO;
> +	}
> +
> +	insitu_comp_get_req(req);
> +	if (ret == 1)
> +		io->io_req.mem.ptr.addr = io->decomp_data + (offset << 9);
> +	io->io_region.count = comp_len >> 9;
> +	io->io_region.sector = start + req->info->data_start;
> +
> +	io->io_req.bi_rw = insitu_req_rw(req);
> +	dm_io(&io->io_req, 1, &io->io_region, NULL);
> +handle_meta:
> +	insitu_comp_set_extent(req, start >> INSITU_COMP_BLOCK_SECTOR_SHIFT,
> +		count >> INSITU_COMP_BLOCK_SECTOR_SHIFT, comp_len >> 9);
> +
> +	page_index = insitu_comp_block_meta_page_index(start >>
> +					INSITU_COMP_BLOCK_SECTOR_SHIFT, false);
> +	if (*meta_start > page_index)
> +		*meta_start = page_index;
> +	page_index = insitu_comp_block_meta_page_index(
> +		(start + count) >> INSITU_COMP_BLOCK_SECTOR_SHIFT, true);
> +	if (*meta_end < page_index)
> +		*meta_end = page_index;
> +	return 0;
> +}
> +
> +/* Compress data and write it out */
> +static void insitu_comp_handle_write_comp(struct insitu_comp_req *req)
> +{
> +	struct insitu_comp_io_range *io;
> +	sector_t count;
> +	unsigned int comp_len;
> +	u64 meta_start = -1L, meta_end = 0, page_index;
> +	int ret;
> +	bool handle_req = true;
> +
> +	req->stage = STAGE_WRITE_COMP;
> +
> +	if (req->result)
> +		return;
> +
> +	list_for_each_entry(io, &req->all_io, next) {
> +		if (insitu_comp_handle_write_modify(io, &meta_start, &meta_end,
> +						&handle_req))
> +			return;
> +	}
> +
> +	if (!handle_req)
> +		goto update_meta;
> +
> +	count = insitu_req_sectors(req);
> +	io = insitu_comp_create_io_range(req, count << 9, count << 9);
> +	if (!io) {
> +		req->result = -EIO;
> +		return;
> +	}
> +	insitu_comp_req_copy(req, 0, io->decomp_data, count << 9, true);
> +
> +	/* compress data */
> +	comp_len = io->comp_len;
> +	ret = insitu_comp_io_range_comp(req->info, io->comp_data, &comp_len,
> +		io->decomp_data, count << 9, true);
> +	if (ret < 0) {
> +		insitu_comp_free_io_range(io);
> +		req->result = -EIO;
> +		return;
> +	}
> +
> +	insitu_comp_get_req(req);
> +	list_add_tail(&io->next, &req->all_io);
> +	io->io_region.sector = insitu_req_start_sector(req) + req->info->data_start;
> +	if (ret == 1)
> +		io->io_req.mem.ptr.addr = io->decomp_data;
> +	io->io_region.count = comp_len >> 9;
> +	io->io_req.bi_rw = insitu_req_rw(req);
> +	dm_io(&io->io_req, 1, &io->io_region, NULL);
> +	insitu_comp_set_extent(req,
> +		insitu_req_start_sector(req) >> INSITU_COMP_BLOCK_SECTOR_SHIFT,
> +		count >> INSITU_COMP_BLOCK_SECTOR_SHIFT, comp_len >> 9);
> +
> +	page_index = insitu_comp_block_meta_page_index(
> +		insitu_req_start_sector(req) >> INSITU_COMP_BLOCK_SECTOR_SHIFT, false);
> +	if (meta_start > page_index)
> +		meta_start = page_index;
> +	page_index = insitu_comp_block_meta_page_index(
> +		(insitu_req_start_sector(req) + count) >> INSITU_COMP_BLOCK_SECTOR_SHIFT,
> +		true);
> +	if (meta_end < page_index)
> +		meta_end = page_index;
> +update_meta:
> +	if (req->info->write_mode == INSITU_COMP_WRITE_THROUGH ||
> +						(insitu_req_rw(req) & REQ_FUA)) {
> +		insitu_comp_get_req(req);
> +		insitu_comp_write_meta(req->info, meta_start, meta_end + 1, req,
> +			insitu_comp_write_meta_done, insitu_req_rw(req));
> +	}
> +}
> +
> +/* request might cover some extents partially, read them first */
> +static void insitu_comp_handle_write_read_existing(struct insitu_comp_req *req)
> +{
> +	u64 block_index, first_block_index;
> +	u16 logical_sectors, data_sectors;
> +
> +	req->stage = STAGE_READ_EXISTING;
> +
> +	block_index = insitu_comp_sector_to_block(insitu_req_start_sector(req));
> +	insitu_comp_get_extent(req->info, block_index, &first_block_index,
> +		&logical_sectors, &data_sectors);
> +	if (data_sectors > 0 && (first_block_index < block_index ||
> +	    first_block_index + insitu_comp_sector_to_block(logical_sectors) >
> +	    insitu_comp_sector_to_block(insitu_req_end_sector(req))))
> +		insitu_comp_read_one_extent(req, first_block_index,
> +			logical_sectors, data_sectors);
> +
> +	if (req->result)
> +		return;
> +
> +	if (first_block_index + insitu_comp_sector_to_block(logical_sectors) >=
> +	    insitu_comp_sector_to_block(insitu_req_end_sector(req)))
> +		goto out;
> +
> +	block_index = insitu_comp_sector_to_block(insitu_req_end_sector(req)) - 1;
> +	insitu_comp_get_extent(req->info, block_index, &first_block_index,
> +		&logical_sectors, &data_sectors);
> +	if (data_sectors > 0 &&
> +	    first_block_index + insitu_comp_sector_to_block(logical_sectors) >
> +	    block_index + 1)
> +		insitu_comp_read_one_extent(req, first_block_index,
> +			logical_sectors, data_sectors);
> +
> +	if (req->result)
> +		return;
> +out:
> +	if (list_empty(&req->all_io))
> +		insitu_comp_handle_write_comp(req);
> +}
> +
> +static void insitu_comp_handle_write_request(struct insitu_comp_req *req)
> +{
> +	insitu_comp_get_req(req);
> +
> +	if (req->stage == STAGE_INIT) {
> +		if (!insitu_comp_lock_req_range(req)) {
> +			insitu_comp_put_req(req);
> +			return;
> +		}
> +
> +		insitu_comp_handle_write_read_existing(req);
> +	} else if (req->stage == STAGE_READ_EXISTING)
> +		insitu_comp_handle_write_comp(req);
> +
> +	insitu_comp_put_req(req);
> +}
> +
> +/* For writeback mode */
> +static void insitu_comp_handle_flush_request(struct insitu_comp_req *req)
> +{
> +	struct writeback_flush_data wb;
> +
> +	atomic_set(&wb.cnt, 1);
> +	init_completion(&wb.complete);
> +
> +	insitu_comp_flush_dirty_meta(req->info, &wb);
> +
> +	writeback_flush_io_done(&wb, 0);
> +	wait_for_completion(&wb.complete);
> +
> +	insitu_req_endio(req, 0);
> +}
> +
> +static void insitu_comp_handle_request(struct insitu_comp_req *req)
> +{
> +	if (insitu_req_rw(req) & REQ_FLUSH)
> +		insitu_comp_handle_flush_request(req);
> +	else if (insitu_req_rw(req) & REQ_WRITE)
> +		insitu_comp_handle_write_request(req);
> +	else
> +		insitu_comp_handle_read_request(req);
> +}
> +
> +static void insitu_comp_do_request_work(struct work_struct *work)
> +{
> +	struct insitu_comp_io_worker *worker = container_of(work,
> +			struct insitu_comp_io_worker, work);
> +	LIST_HEAD(list);
> +	struct insitu_comp_req *req;
> +	struct blk_plug plug;
> +	bool repeat;
> +
> +	blk_start_plug(&plug);
> +again:
> +	spin_lock_irq(&worker->lock);
> +	list_splice_init(&worker->pending, &list);
> +	spin_unlock_irq(&worker->lock);
> +
> +	repeat = !list_empty(&list);
> +	while (!list_empty(&list)) {
> +		req = list_first_entry(&list, struct insitu_comp_req, sibling);
> +		list_del(&req->sibling);
> +
> +		insitu_comp_handle_request(req);
> +	}
> +	if (repeat)
> +		goto again;
> +	blk_finish_plug(&plug);
> +}
> +
> +static int insitu_comp_map(struct dm_target *ti, struct bio *bio)
> +{
> +	struct insitu_comp_info *info = ti->private;
> +	struct insitu_comp_req *req;
> +
> +	req = dm_per_bio_data(bio, sizeof(struct insitu_comp_req));
> +
> +	if ((bio->bi_rw & REQ_FLUSH) &&
> +			info->write_mode == INSITU_COMP_WRITE_THROUGH) {
> +		bio->bi_bdev = info->dev->bdev;
> +		return DM_MAPIO_REMAPPED;
> +	}
> +
> +	req->bio = bio;
> +	req->info = info;
> +	atomic_set(&req->io_pending, 0);
> +	INIT_LIST_HEAD(&req->all_io);
> +	req->result = 0;
> +	req->stage = STAGE_INIT;
> +
> +	req->cpu = raw_smp_processor_id();
> +	insitu_comp_queue_req(info, req);
> +
> +	return DM_MAPIO_SUBMITTED;
> +}
> +
> +/*
> + * INFO: uncompressed_data_size compressed_data_size metadata_size
> + * TABLE: writethrough/writeback commit_delay
> + */
> +static void insitu_comp_status(struct dm_target *ti, status_type_t type,
> +			  unsigned status_flags, char *result, unsigned maxlen)
> +{
> +	struct insitu_comp_info *info = ti->private;
> +	unsigned int sz = 0;
> +
> +	switch (type) {
> +	case STATUSTYPE_INFO:
> +		DMEMIT("%lu %lu %lu",
> +			atomic64_read(&info->uncompressed_write_size),
> +			atomic64_read(&info->compressed_write_size),
> +			atomic64_read(&info->meta_write_size));
> +		break;
> +	case STATUSTYPE_TABLE:
> +		if (info->write_mode == INSITU_COMP_WRITE_BACK)
> +			DMEMIT("%s %s %d", info->dev->name, "writeback",
> +				info->writeback_delay);
> +		else
> +			DMEMIT("%s %s", info->dev->name, "writethrough");
> +		break;
> +	}
> +}
> +
> +static int insitu_comp_iterate_devices(struct dm_target *ti,
> +				  iterate_devices_callout_fn fn, void *data)
> +{
> +	struct insitu_comp_info *info = ti->private;
> +
> +	return fn(ti, info->dev, info->data_start,
> +		info->data_blocks << INSITU_COMP_BLOCK_SECTOR_SHIFT, data);
> +}
> +
> +static void insitu_comp_io_hints(struct dm_target *ti,
> +			    struct queue_limits *limits)
> +{
> +	/* No blk_limits_logical_block_size */
> +	limits->logical_block_size = limits->physical_block_size =
> +		limits->io_min = INSITU_COMP_BLOCK_SIZE;
> +	blk_limits_max_hw_sectors(limits, INSITU_COMP_MAX_SIZE >> 9);
> +}
> +
> +static int insitu_comp_merge(struct dm_target *ti, struct bvec_merge_data *bvm,
> +			struct bio_vec *biovec, int max_size)
> +{
> +	/* Guarantee request can only cover one aligned 128k range */
> +	return min_t(int, max_size, INSITU_COMP_MAX_SIZE - bvm->bi_size -
> +			((bvm->bi_sector << 9) % INSITU_COMP_MAX_SIZE));
> +}
> +
> +static struct target_type insitu_comp_target = {
> +	.name   = "insitu_comp",
> +	.version = {1, 0, 0},
> +	.module = THIS_MODULE,
> +	.ctr    = insitu_comp_ctr,
> +	.dtr    = insitu_comp_dtr,
> +	.map    = insitu_comp_map,
> +	.status = insitu_comp_status,
> +	.iterate_devices = insitu_comp_iterate_devices,
> +	.io_hints = insitu_comp_io_hints,
> +	.merge = insitu_comp_merge,
> +};
> +
> +static int __init insitu_comp_init(void)
> +{
> +	int r;
> +
> +	for (r = 0; r < ARRAY_SIZE(compressors); r++)
> +		if (crypto_has_comp(compressors[r].name, 0, 0))
> +			break;
> +	if (r >= ARRAY_SIZE(compressors)) {
> +		DMWARN("No crypto compressors are supported");
> +		return -EINVAL;
> +	}
> +
> +	default_compressor = r;
> +
> +	r = -ENOMEM;
> +	insitu_comp_io_range_cachep = kmem_cache_create("insitu_comp_io_range",
> +		sizeof(struct insitu_comp_io_range), 0, 0, NULL);
> +	if (!insitu_comp_io_range_cachep) {
> +		DMWARN("Can't create io_range cache");
> +		goto err;
> +	}
> +
> +	insitu_comp_meta_io_cachep = kmem_cache_create("insitu_comp_meta_io",
> +		sizeof(struct insitu_comp_meta_io), 0, 0, NULL);
> +	if (!insitu_comp_meta_io_cachep) {
> +		DMWARN("Can't create meta_io cache");
> +		goto err;
> +	}
> +
> +	insitu_comp_wq = alloc_workqueue("insitu_comp_io",
> +		WQ_UNBOUND|WQ_MEM_RECLAIM|WQ_CPU_INTENSIVE, 0);
> +	if (!insitu_comp_wq) {
> +		DMWARN("Can't create io workqueue");
> +		goto err;
> +	}
> +
> +	r = dm_register_target(&insitu_comp_target);
> +	if (r < 0) {
> +		DMWARN("target registration failed");
> +		goto err;
> +	}
> +
> +	for_each_possible_cpu(r) {
> +		INIT_LIST_HEAD(&insitu_comp_io_workers[r].pending);
> +		spin_lock_init(&insitu_comp_io_workers[r].lock);
> +		INIT_WORK(&insitu_comp_io_workers[r].work,
> +			insitu_comp_do_request_work);
> +	}
> +	return 0;
> +err:
> +	if (insitu_comp_io_range_cachep)
> +		kmem_cache_destroy(insitu_comp_io_range_cachep);
> +	if (insitu_comp_meta_io_cachep)
> +		kmem_cache_destroy(insitu_comp_meta_io_cachep);
> +	if (insitu_comp_wq)
> +		destroy_workqueue(insitu_comp_wq);
> +
> +	return r;
> +}
> +
> +static void __exit insitu_comp_exit(void)
> +{
> +	dm_unregister_target(&insitu_comp_target);
> +	kmem_cache_destroy(insitu_comp_io_range_cachep);
> +	kmem_cache_destroy(insitu_comp_meta_io_cachep);
> +	destroy_workqueue(insitu_comp_wq);
> +}
> +
> +module_init(insitu_comp_init);
> +module_exit(insitu_comp_exit);
> +
> +MODULE_AUTHOR("Shaohua Li <shli at kernel.org>");
> +MODULE_DESCRIPTION(DM_NAME " target with insitu data compression for SSD");
> +MODULE_LICENSE("GPL");
> Index: linux/drivers/md/dm-insitu-comp.h
> ===================================================================
> --- /dev/null	1970-01-01 00:00:00.000000000 +0000
> +++ linux/drivers/md/dm-insitu-comp.h	2014-02-17 18:37:07.108425465 +0800
> @@ -0,0 +1,158 @@
> +#ifndef __DM_INSITU_COMPRESSION_H__
> +#define __DM_INSITU_COMPRESSION_H__
> +#include <linux/types.h>
> +
> +struct insitu_comp_super_block {
> +	__le64 magic;
> +	__le64 version;
> +	__le64 meta_blocks;
> +	__le64 data_blocks;
> +	u8 comp_alg;
> +} __attribute__((packed));
> +
> +#define INSITU_COMP_SUPER_MAGIC 0x106526c206506c09
> +#define INSITU_COMP_VERSION 1
> +#define INSITU_COMP_ALG_LZO 0
> +#define INSITU_COMP_ALG_ZLIB 1
> +
> +#ifdef __KERNEL__
> +struct insitu_comp_compressor_data {
> +	char *name;
> +	int (*comp_len)(int comp_len);
> +};
> +
> +static inline int lzo_comp_len(int comp_len)
> +{
> +	return lzo1x_worst_compress(comp_len);
> +}
> +
> +/*
> + * Minium logical sector size of this target is 4096 byte, which is a block.
> + * Data of a block is compressed. Compressed data is round up to 512B, which is
> + * the payload. For each block, we have 5 bits meta data. bit 0 - 3 stands
> + * payload length. 0 - 8 sectors. If compressed payload length is 8 sectors, we
> + * just store uncompressed data. Actual compressed data length is stored at the
> + * last 32 bits of payload if data is compressed. In disk, payload is stored at
> + * the begining of logical sector of the block. If IO size is bigger than one
> + * block, we store the whole data as an extent. Bit 4 stands tail for an
> + * extent. Max allowed extent size is 128k.
> + */
> +#define INSITU_COMP_BLOCK_SIZE 4096
> +#define INSITU_COMP_BLOCK_SHIFT 12
> +#define INSITU_COMP_BLOCK_SECTOR_SHIFT (INSITU_COMP_BLOCK_SHIFT - 9)
> +
> +#define INSITU_COMP_MIN_SIZE 4096
> +/* Change this should change HASH_LOCK_SHIFT too */
> +#define INSITU_COMP_MAX_SIZE (128 * 1024)
> +
> +#define INSITU_COMP_LENGTH_MASK ((1 << 4) - 1)
> +#define INSITU_COMP_TAIL_MASK (1 << 4)
> +#define INSITU_COMP_META_BITS 5
> +
> +#define INSITU_COMP_META_START_SECTOR (INSITU_COMP_BLOCK_SIZE >> 9)
> +
> +enum INSITU_COMP_WRITE_MODE {
> +	INSITU_COMP_WRITE_BACK,
> +	INSITU_COMP_WRITE_THROUGH,
> +};
> +
> +/*
> + * request can cover one aligned 128k (4k * (1 << 5)) range. Since maxium
> + * request size is 128k, we only need take one lock for each request
> + */
> +#define HASH_LOCK_SHIFT 5
> +
> +#define BITMAP_HASH_SHIFT 9
> +#define BITMAP_HASH_MASK ((1 << BITMAP_HASH_SHIFT) - 1)
> +#define BITMAP_HASH_LEN (1 << BITMAP_HASH_SHIFT)
> +
> +struct insitu_comp_hash_lock {
> +	int io_running;
> +	spinlock_t wait_lock;
> +	struct list_head wait_list;
> +};
> +
> +struct insitu_comp_info {
> +	struct dm_target *ti;
> +	struct dm_dev *dev;
> +
> +	int comp_alg;
> +	struct crypto_comp *tfm[NR_CPUS];
> +
> +	sector_t data_start;
> +	u64 data_blocks;
> +
> +	char *meta_bitmap;
> +	u64 meta_bitmap_bits;
> +	u64 meta_bitmap_pages;
> +	struct insitu_comp_hash_lock bitmap_locks[BITMAP_HASH_LEN];
> +
> +	enum INSITU_COMP_WRITE_MODE write_mode;
> +	unsigned int writeback_delay; /* second unit */
> +	struct task_struct *writeback_tsk;
> +	struct dm_io_client *io_client;
> +
> +	atomic64_t compressed_write_size;
> +	atomic64_t uncompressed_write_size;
> +	atomic64_t meta_write_size;
> +};
> +
> +struct insitu_comp_meta_io {
> +	struct dm_io_request io_req;
> +	struct dm_io_region io_region;
> +	void *data;
> +	void (*fn)(void *data, unsigned long error);
> +};
> +
> +struct insitu_comp_io_range {
> +	struct dm_io_request io_req;
> +	struct dm_io_region io_region;
> +	void *decomp_data;
> +	unsigned int decomp_len;
> +	void *comp_data;
> +	unsigned int comp_len; /* For write, this is estimated */
> +	struct list_head next;
> +	struct insitu_comp_req *req;
> +};
> +
> +enum INSITU_COMP_REQ_STAGE {
> +	STAGE_INIT,
> +	STAGE_READ_EXISTING,
> +	STAGE_READ_DECOMP,
> +	STAGE_WRITE_COMP,
> +	STAGE_DONE,
> +};
> +
> +struct insitu_comp_req {
> +	struct bio *bio;
> +	struct insitu_comp_info *info;
> +	struct list_head sibling;
> +
> +	struct list_head all_io;
> +	atomic_t io_pending;
> +	enum INSITU_COMP_REQ_STAGE stage;
> +
> +	struct insitu_comp_hash_lock *lock;
> +	int result;
> +
> +	int cpu;
> +};
> +
> +#define insitu_req_start_sector(req) (req->bio->bi_iter.bi_sector)
> +#define insitu_req_end_sector(req) (bio_end_sector(req->bio))
> +#define insitu_req_rw(req) (req->bio->bi_rw)
> +#define insitu_req_sectors(req) (bio_sectors(req->bio))
> +
> +static inline void insitu_req_endio(struct insitu_comp_req *req, int error)
> +{
> +	bio_endio(req->bio, error);
> +}
> +
> +struct insitu_comp_io_worker {
> +	struct list_head pending;
> +	spinlock_t lock;
> +	struct work_struct work;
> +};
> +#endif
> +
> +#endif
> Index: linux/Documentation/device-mapper/insitu-comp.txt
> ===================================================================
> --- /dev/null	1970-01-01 00:00:00.000000000 +0000
> +++ linux/Documentation/device-mapper/insitu-comp.txt	2014-02-17 17:34:45.427464765 +0800
> @@ -0,0 +1,50 @@
> +This is a simple DM target supporting compression for SSD only. Under layer SSD
> +must support 512B sector size, the target only supports 4k sector size.
> +
> +Disk layout:
> +|super|...meta...|..data...|
> +
> +Store unit is 4k (a block). Super is 1 block, which stores meta and data size
> +and compression algorithm. Meta is a bitmap. For each data block, there are 5
> +bits meta.
> +
> +Data:
> +Data of a block is compressed. Compressed data is round up to 512B, which is
> +the payload. In disk, payload is stored at the begining of logical sector of
> +the block. Let's look at an example. Say we store data to block A, which is in
> +sector B(A*8), its orginal size is 4k, compressed size is 1500. Compressed data
> +(CD) will use 3 sectors (512B). The 3 sectors are the payload. Payload will be
> +stored at sector B.
> +
> +---------------------------------------------------
> +... | CD1 | CD2 | CD3 |   |   |   |   |    | ...
> +---------------------------------------------------
> +    ^B    ^B+1  ^B+2                  ^B+7 ^B+8
> +
> +For this block, we will not use sector B+3 to B+7 (a hole). We use 4 meta bits
> +to present payload size. The compressed size (1500) isn't stored in meta
> +directly. Instead, we store it at the last 32bits of payload. In this example,
> +we store it at the end of sector B+2. If compressed size + sizeof(32bits)
> +crosses a sector, payload size will increase one sector. If payload uses 8
> +sectors, we store uncompressed data directly.
> +
> +If IO size is bigger than one block, we can store the data as an extent. Data
> +of the whole extent will compressed and stored in the similar way like above.
> +The first block of the extent is the head, all others are the tail. If extent
> +is 1 block, the block is head. We have 1 bit of meta to present if a block is
> +head or tail. If 4 meta bits of head block can't store extent payload size, we
> +will borrow tail block meta bits to store payload size. Max allowd extent size
> +is 128k, so we don't compress/decompress too big size data.
> +
> +Meta:
> +Modifying data will modify meta too. Meta will be written(flush) to disk
> +depending on meta write policy. We support writeback and writethrough mode. In
> +writeback mode, meta will be written to disk in an interval or a FLUSH request.
> +In writethrough mode, data and meta data will be written to disk together.
> +
> +=========================
> +Parameters: <dev> [<writethrough>|<writeback> <meta_commit_delay>]
> +   <dev>: underlying device
> +   <writethrough>: metadata flush to disk with writetrough mode
> +   <writeback>: metadata flush to disk with writeback mode
> +   <meta_commit_delay>: metadata flush to disk interval in writeback mode




More information about the dm-devel mailing list