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

From: Shaohua Li
Date: Fri Mar 07 2014 - 02:58:11 EST


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@xxxxxxxxxxxx>
> ---
> 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@xxxxxxxxxx>");
> +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
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/