Merge tag 'armsoc-soc' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[linux-2.6-microblaze.git] / drivers / md / dm-integrity.c
index 86438b2..3788785 100644 (file)
@@ -31,6 +31,8 @@
 #define MIN_LOG2_INTERLEAVE_SECTORS    3
 #define MAX_LOG2_INTERLEAVE_SECTORS    31
 #define METADATA_WORKQUEUE_MAX_ACTIVE  16
+#define RECALC_SECTORS                 8192
+#define RECALC_WRITE_SUPER             16
 
 /*
  * Warning - DEBUG_PRINT prints security-sensitive data to the log,
@@ -44,7 +46,8 @@
  */
 
 #define SB_MAGIC                       "integrt"
-#define SB_VERSION                     1
+#define SB_VERSION_1                   1
+#define SB_VERSION_2                   2
 #define SB_SECTORS                     8
 #define MAX_SECTORS_PER_BLOCK          8
 
@@ -57,9 +60,12 @@ struct superblock {
        __u64 provided_data_sectors;    /* userspace uses this value */
        __u32 flags;
        __u8 log2_sectors_per_block;
+       __u8 pad[3];
+       __u64 recalc_sector;
 };
 
 #define SB_FLAG_HAVE_JOURNAL_MAC       0x1
+#define SB_FLAG_RECALCULATING          0x2
 
 #define        JOURNAL_ENTRY_ROUNDUP           8
 
@@ -139,6 +145,7 @@ struct alg_spec {
 
 struct dm_integrity_c {
        struct dm_dev *dev;
+       struct dm_dev *meta_dev;
        unsigned tag_size;
        __s8 log2_tag_size;
        sector_t start;
@@ -170,7 +177,8 @@ struct dm_integrity_c {
        unsigned short journal_section_sectors;
        unsigned journal_sections;
        unsigned journal_entries;
-       sector_t device_sectors;
+       sector_t data_device_sectors;
+       sector_t meta_device_sectors;
        unsigned initial_sectors;
        unsigned metadata_run;
        __s8 log2_metadata_run;
@@ -178,7 +186,7 @@ struct dm_integrity_c {
        __u8 sectors_per_block;
 
        unsigned char mode;
-       bool suspending;
+       int suspending;
 
        int failed;
 
@@ -186,6 +194,7 @@ struct dm_integrity_c {
 
        /* these variables are locked with endio_wait.lock */
        struct rb_root in_progress;
+       struct list_head wait_list;
        wait_queue_head_t endio_wait;
        struct workqueue_struct *wait_wq;
 
@@ -210,6 +219,11 @@ struct dm_integrity_c {
        struct workqueue_struct *writer_wq;
        struct work_struct writer_work;
 
+       struct workqueue_struct *recalc_wq;
+       struct work_struct recalc_work;
+       u8 *recalc_buffer;
+       u8 *recalc_tags;
+
        struct bio_list flush_bio_list;
 
        unsigned long autocommit_jiffies;
@@ -233,7 +247,14 @@ struct dm_integrity_c {
 struct dm_integrity_range {
        sector_t logical_sector;
        unsigned n_sectors;
-       struct rb_node node;
+       bool waiting;
+       union {
+               struct rb_node node;
+               struct {
+                       struct task_struct *task;
+                       struct list_head wait_entry;
+               };
+       };
 };
 
 struct dm_integrity_io {
@@ -337,10 +358,14 @@ static commit_id_t dm_integrity_commit_id(struct dm_integrity_c *ic, unsigned i,
 static void get_area_and_offset(struct dm_integrity_c *ic, sector_t data_sector,
                                sector_t *area, sector_t *offset)
 {
-       __u8 log2_interleave_sectors = ic->sb->log2_interleave_sectors;
-
-       *area = data_sector >> log2_interleave_sectors;
-       *offset = (unsigned)data_sector & ((1U << log2_interleave_sectors) - 1);
+       if (!ic->meta_dev) {
+               __u8 log2_interleave_sectors = ic->sb->log2_interleave_sectors;
+               *area = data_sector >> log2_interleave_sectors;
+               *offset = (unsigned)data_sector & ((1U << log2_interleave_sectors) - 1);
+       } else {
+               *area = 0;
+               *offset = data_sector;
+       }
 }
 
 #define sector_to_block(ic, n)                                         \
@@ -379,6 +404,9 @@ static sector_t get_data_sector(struct dm_integrity_c *ic, sector_t area, sector
 {
        sector_t result;
 
+       if (ic->meta_dev)
+               return offset;
+
        result = area << ic->sb->log2_interleave_sectors;
        if (likely(ic->log2_metadata_run >= 0))
                result += (area + 1) << ic->log2_metadata_run;
@@ -386,6 +414,8 @@ static sector_t get_data_sector(struct dm_integrity_c *ic, sector_t area, sector
                result += (area + 1) * ic->metadata_run;
 
        result += (sector_t)ic->initial_sectors + offset;
+       result += ic->start;
+
        return result;
 }
 
@@ -395,6 +425,14 @@ static void wraparound_section(struct dm_integrity_c *ic, unsigned *sec_ptr)
                *sec_ptr -= ic->journal_sections;
 }
 
+static void sb_set_version(struct dm_integrity_c *ic)
+{
+       if (ic->meta_dev || ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING))
+               ic->sb->version = SB_VERSION_2;
+       else
+               ic->sb->version = SB_VERSION_1;
+}
+
 static int sync_rw_sb(struct dm_integrity_c *ic, int op, int op_flags)
 {
        struct dm_io_request io_req;
@@ -406,7 +444,7 @@ static int sync_rw_sb(struct dm_integrity_c *ic, int op, int op_flags)
        io_req.mem.ptr.addr = ic->sb;
        io_req.notify.fn = NULL;
        io_req.client = ic->io;
-       io_loc.bdev = ic->dev->bdev;
+       io_loc.bdev = ic->meta_dev ? ic->meta_dev->bdev : ic->dev->bdev;
        io_loc.sector = ic->start;
        io_loc.count = SB_SECTORS;
 
@@ -753,7 +791,7 @@ static void rw_journal(struct dm_integrity_c *ic, int op, int op_flags, unsigned
                io_req.notify.fn = NULL;
        }
        io_req.client = ic->io;
-       io_loc.bdev = ic->dev->bdev;
+       io_loc.bdev = ic->meta_dev ? ic->meta_dev->bdev : ic->dev->bdev;
        io_loc.sector = ic->start + SB_SECTORS + sector;
        io_loc.count = n_sectors;
 
@@ -857,7 +895,7 @@ static void copy_from_journal(struct dm_integrity_c *ic, unsigned section, unsig
        io_req.notify.context = data;
        io_req.client = ic->io;
        io_loc.bdev = ic->dev->bdev;
-       io_loc.sector = ic->start + target;
+       io_loc.sector = target;
        io_loc.count = n_sectors;
 
        r = dm_io(&io_req, 1, &io_loc, NULL);
@@ -867,13 +905,27 @@ static void copy_from_journal(struct dm_integrity_c *ic, unsigned section, unsig
        }
 }
 
-static bool add_new_range(struct dm_integrity_c *ic, struct dm_integrity_range *new_range)
+static bool ranges_overlap(struct dm_integrity_range *range1, struct dm_integrity_range *range2)
+{
+       return range1->logical_sector < range2->logical_sector + range2->n_sectors &&
+              range2->logical_sector + range2->n_sectors > range2->logical_sector;
+}
+
+static bool add_new_range(struct dm_integrity_c *ic, struct dm_integrity_range *new_range, bool check_waiting)
 {
        struct rb_node **n = &ic->in_progress.rb_node;
        struct rb_node *parent;
 
        BUG_ON((new_range->logical_sector | new_range->n_sectors) & (unsigned)(ic->sectors_per_block - 1));
 
+       if (likely(check_waiting)) {
+               struct dm_integrity_range *range;
+               list_for_each_entry(range, &ic->wait_list, wait_entry) {
+                       if (unlikely(ranges_overlap(range, new_range)))
+                               return false;
+               }
+       }
+
        parent = NULL;
 
        while (*n) {
@@ -898,7 +950,22 @@ static bool add_new_range(struct dm_integrity_c *ic, struct dm_integrity_range *
 static void remove_range_unlocked(struct dm_integrity_c *ic, struct dm_integrity_range *range)
 {
        rb_erase(&range->node, &ic->in_progress);
-       wake_up_locked(&ic->endio_wait);
+       while (unlikely(!list_empty(&ic->wait_list))) {
+               struct dm_integrity_range *last_range =
+                       list_first_entry(&ic->wait_list, struct dm_integrity_range, wait_entry);
+               struct task_struct *last_range_task;
+               if (!ranges_overlap(range, last_range))
+                       break;
+               last_range_task = last_range->task;
+               list_del(&last_range->wait_entry);
+               if (!add_new_range(ic, last_range, false)) {
+                       last_range->task = last_range_task;
+                       list_add(&last_range->wait_entry, &ic->wait_list);
+                       break;
+               }
+               last_range->waiting = false;
+               wake_up_process(last_range_task);
+       }
 }
 
 static void remove_range(struct dm_integrity_c *ic, struct dm_integrity_range *range)
@@ -910,6 +977,19 @@ static void remove_range(struct dm_integrity_c *ic, struct dm_integrity_range *r
        spin_unlock_irqrestore(&ic->endio_wait.lock, flags);
 }
 
+static void wait_and_add_new_range(struct dm_integrity_c *ic, struct dm_integrity_range *new_range)
+{
+       new_range->waiting = true;
+       list_add_tail(&new_range->wait_entry, &ic->wait_list);
+       new_range->task = current;
+       do {
+               __set_current_state(TASK_UNINTERRUPTIBLE);
+               spin_unlock_irq(&ic->endio_wait.lock);
+               io_schedule();
+               spin_lock_irq(&ic->endio_wait.lock);
+       } while (unlikely(new_range->waiting));
+}
+
 static void init_journal_node(struct journal_node *node)
 {
        RB_CLEAR_NODE(&node->node);
@@ -1599,8 +1679,12 @@ retry:
 
                        dio->range.n_sectors = min(dio->range.n_sectors,
                                                   ic->free_sectors << ic->sb->log2_sectors_per_block);
-                       if (unlikely(!dio->range.n_sectors))
-                               goto sleep;
+                       if (unlikely(!dio->range.n_sectors)) {
+                               if (from_map)
+                                       goto offload_to_thread;
+                               sleep_on_endio_wait(ic);
+                               goto retry;
+                       }
                        range_sectors = dio->range.n_sectors >> ic->sb->log2_sectors_per_block;
                        ic->free_sectors -= range_sectors;
                        journal_section = ic->free_section;
@@ -1654,22 +1738,20 @@ retry:
                        }
                }
        }
-       if (unlikely(!add_new_range(ic, &dio->range))) {
+       if (unlikely(!add_new_range(ic, &dio->range, true))) {
                /*
                 * We must not sleep in the request routine because it could
                 * stall bios on current->bio_list.
                 * So, we offload the bio to a workqueue if we have to sleep.
                 */
-sleep:
                if (from_map) {
+offload_to_thread:
                        spin_unlock_irq(&ic->endio_wait.lock);
                        INIT_WORK(&dio->work, integrity_bio_wait);
                        queue_work(ic->wait_wq, &dio->work);
                        return;
-               } else {
-                       sleep_on_endio_wait(ic);
-                       goto retry;
                }
+               wait_and_add_new_range(ic, &dio->range);
        }
        spin_unlock_irq(&ic->endio_wait.lock);
 
@@ -1701,14 +1783,18 @@ sleep:
        bio->bi_end_io = integrity_end_io;
 
        bio->bi_iter.bi_size = dio->range.n_sectors << SECTOR_SHIFT;
-       bio->bi_iter.bi_sector += ic->start;
        generic_make_request(bio);
 
        if (need_sync_io) {
                wait_for_completion_io(&read_comp);
+               if (unlikely(ic->recalc_wq != NULL) &&
+                   ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING) &&
+                   dio->range.logical_sector + dio->range.n_sectors > le64_to_cpu(ic->sb->recalc_sector))
+                       goto skip_check;
                if (likely(!bio->bi_status))
                        integrity_metadata(&dio->work);
                else
+skip_check:
                        dec_in_flight(dio);
 
        } else {
@@ -1892,8 +1978,8 @@ static void do_journal_write(struct dm_integrity_c *ic, unsigned write_start,
                        io->range.n_sectors = (k - j) << ic->sb->log2_sectors_per_block;
 
                        spin_lock_irq(&ic->endio_wait.lock);
-                       while (unlikely(!add_new_range(ic, &io->range)))
-                               sleep_on_endio_wait(ic);
+                       if (unlikely(!add_new_range(ic, &io->range, true)))
+                               wait_and_add_new_range(ic, &io->range);
 
                        if (likely(!from_replay)) {
                                struct journal_node *section_node = &ic->journal_tree[i * ic->journal_section_entries];
@@ -1981,7 +2067,7 @@ static void integrity_writer(struct work_struct *w)
        unsigned prev_free_sectors;
 
        /* the following test is not needed, but it tests the replay code */
-       if (READ_ONCE(ic->suspending))
+       if (READ_ONCE(ic->suspending) && !ic->meta_dev)
                return;
 
        spin_lock_irq(&ic->endio_wait.lock);
@@ -2008,6 +2094,108 @@ static void integrity_writer(struct work_struct *w)
        spin_unlock_irq(&ic->endio_wait.lock);
 }
 
+static void recalc_write_super(struct dm_integrity_c *ic)
+{
+       int r;
+
+       dm_integrity_flush_buffers(ic);
+       if (dm_integrity_failed(ic))
+               return;
+
+       sb_set_version(ic);
+       r = sync_rw_sb(ic, REQ_OP_WRITE, 0);
+       if (unlikely(r))
+               dm_integrity_io_error(ic, "writing superblock", r);
+}
+
+static void integrity_recalc(struct work_struct *w)
+{
+       struct dm_integrity_c *ic = container_of(w, struct dm_integrity_c, recalc_work);
+       struct dm_integrity_range range;
+       struct dm_io_request io_req;
+       struct dm_io_region io_loc;
+       sector_t area, offset;
+       sector_t metadata_block;
+       unsigned metadata_offset;
+       __u8 *t;
+       unsigned i;
+       int r;
+       unsigned super_counter = 0;
+
+       spin_lock_irq(&ic->endio_wait.lock);
+
+next_chunk:
+
+       if (unlikely(READ_ONCE(ic->suspending)))
+               goto unlock_ret;
+
+       range.logical_sector = le64_to_cpu(ic->sb->recalc_sector);
+       if (unlikely(range.logical_sector >= ic->provided_data_sectors))
+               goto unlock_ret;
+
+       get_area_and_offset(ic, range.logical_sector, &area, &offset);
+       range.n_sectors = min((sector_t)RECALC_SECTORS, ic->provided_data_sectors - range.logical_sector);
+       if (!ic->meta_dev)
+               range.n_sectors = min(range.n_sectors, (1U << ic->sb->log2_interleave_sectors) - (unsigned)offset);
+
+       if (unlikely(!add_new_range(ic, &range, true)))
+               wait_and_add_new_range(ic, &range);
+
+       spin_unlock_irq(&ic->endio_wait.lock);
+
+       if (unlikely(++super_counter == RECALC_WRITE_SUPER)) {
+               recalc_write_super(ic);
+               super_counter = 0;
+       }
+
+       if (unlikely(dm_integrity_failed(ic)))
+               goto err;
+
+       io_req.bi_op = REQ_OP_READ;
+       io_req.bi_op_flags = 0;
+       io_req.mem.type = DM_IO_VMA;
+       io_req.mem.ptr.addr = ic->recalc_buffer;
+       io_req.notify.fn = NULL;
+       io_req.client = ic->io;
+       io_loc.bdev = ic->dev->bdev;
+       io_loc.sector = get_data_sector(ic, area, offset);
+       io_loc.count = range.n_sectors;
+
+       r = dm_io(&io_req, 1, &io_loc, NULL);
+       if (unlikely(r)) {
+               dm_integrity_io_error(ic, "reading data", r);
+               goto err;
+       }
+
+       t = ic->recalc_tags;
+       for (i = 0; i < range.n_sectors; i += ic->sectors_per_block) {
+               integrity_sector_checksum(ic, range.logical_sector + i, ic->recalc_buffer + (i << SECTOR_SHIFT), t);
+               t += ic->tag_size;
+       }
+
+       metadata_block = get_metadata_sector_and_offset(ic, area, offset, &metadata_offset);
+
+       r = dm_integrity_rw_tag(ic, ic->recalc_tags, &metadata_block, &metadata_offset, t - ic->recalc_tags, TAG_WRITE);
+       if (unlikely(r)) {
+               dm_integrity_io_error(ic, "writing tags", r);
+               goto err;
+       }
+
+       spin_lock_irq(&ic->endio_wait.lock);
+       remove_range_unlocked(ic, &range);
+       ic->sb->recalc_sector = cpu_to_le64(range.logical_sector + range.n_sectors);
+       goto next_chunk;
+
+err:
+       remove_range(ic, &range);
+       return;
+
+unlock_ret:
+       spin_unlock_irq(&ic->endio_wait.lock);
+
+       recalc_write_super(ic);
+}
+
 static void init_journal(struct dm_integrity_c *ic, unsigned start_section,
                         unsigned n_sections, unsigned char commit_seq)
 {
@@ -2210,17 +2398,22 @@ static void dm_integrity_postsuspend(struct dm_target *ti)
 
        del_timer_sync(&ic->autocommit_timer);
 
-       ic->suspending = true;
+       WRITE_ONCE(ic->suspending, 1);
+
+       if (ic->recalc_wq)
+               drain_workqueue(ic->recalc_wq);
 
        queue_work(ic->commit_wq, &ic->commit_work);
        drain_workqueue(ic->commit_wq);
 
        if (ic->mode == 'J') {
+               if (ic->meta_dev)
+                       queue_work(ic->writer_wq, &ic->writer_work);
                drain_workqueue(ic->writer_wq);
                dm_integrity_flush_buffers(ic);
        }
 
-       ic->suspending = false;
+       WRITE_ONCE(ic->suspending, 0);
 
        BUG_ON(!RB_EMPTY_ROOT(&ic->in_progress));
 
@@ -2232,6 +2425,16 @@ static void dm_integrity_resume(struct dm_target *ti)
        struct dm_integrity_c *ic = (struct dm_integrity_c *)ti->private;
 
        replay_journal(ic);
+
+       if (ic->recalc_wq && ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING)) {
+               __u64 recalc_pos = le64_to_cpu(ic->sb->recalc_sector);
+               if (recalc_pos < ic->provided_data_sectors) {
+                       queue_work(ic->recalc_wq, &ic->recalc_work);
+               } else if (recalc_pos > ic->provided_data_sectors) {
+                       ic->sb->recalc_sector = cpu_to_le64(ic->provided_data_sectors);
+                       recalc_write_super(ic);
+               }
+       }
 }
 
 static void dm_integrity_status(struct dm_target *ti, status_type_t type,
@@ -2243,7 +2446,13 @@ static void dm_integrity_status(struct dm_target *ti, status_type_t type,
 
        switch (type) {
        case STATUSTYPE_INFO:
-               DMEMIT("%llu", (unsigned long long)atomic64_read(&ic->number_of_mismatches));
+               DMEMIT("%llu %llu",
+                       (unsigned long long)atomic64_read(&ic->number_of_mismatches),
+                       (unsigned long long)ic->provided_data_sectors);
+               if (ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING))
+                       DMEMIT(" %llu", (unsigned long long)le64_to_cpu(ic->sb->recalc_sector));
+               else
+                       DMEMIT(" -");
                break;
 
        case STATUSTYPE_TABLE: {
@@ -2251,19 +2460,25 @@ static void dm_integrity_status(struct dm_target *ti, status_type_t type,
                watermark_percentage += ic->journal_entries / 2;
                do_div(watermark_percentage, ic->journal_entries);
                arg_count = 5;
+               arg_count += !!ic->meta_dev;
                arg_count += ic->sectors_per_block != 1;
+               arg_count += !!(ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING));
                arg_count += !!ic->internal_hash_alg.alg_string;
                arg_count += !!ic->journal_crypt_alg.alg_string;
                arg_count += !!ic->journal_mac_alg.alg_string;
                DMEMIT("%s %llu %u %c %u", ic->dev->name, (unsigned long long)ic->start,
                       ic->tag_size, ic->mode, arg_count);
+               if (ic->meta_dev)
+                       DMEMIT(" meta_device:%s", ic->meta_dev->name);
+               if (ic->sectors_per_block != 1)
+                       DMEMIT(" block_size:%u", ic->sectors_per_block << SECTOR_SHIFT);
+               if (ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING))
+                       DMEMIT(" recalculate");
                DMEMIT(" journal_sectors:%u", ic->initial_sectors - SB_SECTORS);
                DMEMIT(" interleave_sectors:%u", 1U << ic->sb->log2_interleave_sectors);
                DMEMIT(" buffer_sectors:%u", 1U << ic->log2_buffer_sectors);
                DMEMIT(" journal_watermark:%u", (unsigned)watermark_percentage);
                DMEMIT(" commit_time:%u", ic->autocommit_msec);
-               if (ic->sectors_per_block != 1)
-                       DMEMIT(" block_size:%u", ic->sectors_per_block << SECTOR_SHIFT);
 
 #define EMIT_ALG(a, n)                                                 \
                do {                                                    \
@@ -2286,7 +2501,10 @@ static int dm_integrity_iterate_devices(struct dm_target *ti,
 {
        struct dm_integrity_c *ic = ti->private;
 
-       return fn(ti, ic->dev, ic->start + ic->initial_sectors + ic->metadata_run, ti->len, data);
+       if (!ic->meta_dev)
+               return fn(ti, ic->dev, ic->start + ic->initial_sectors + ic->metadata_run, ti->len, data);
+       else
+               return fn(ti, ic->dev, 0, ti->len, data);
 }
 
 static void dm_integrity_io_hints(struct dm_target *ti, struct queue_limits *limits)
@@ -2319,26 +2537,38 @@ static void calculate_journal_section_size(struct dm_integrity_c *ic)
 static int calculate_device_limits(struct dm_integrity_c *ic)
 {
        __u64 initial_sectors;
-       sector_t last_sector, last_area, last_offset;
 
        calculate_journal_section_size(ic);
        initial_sectors = SB_SECTORS + (__u64)ic->journal_section_sectors * ic->journal_sections;
-       if (initial_sectors + METADATA_PADDING_SECTORS >= ic->device_sectors || initial_sectors > UINT_MAX)
+       if (initial_sectors + METADATA_PADDING_SECTORS >= ic->meta_device_sectors || initial_sectors > UINT_MAX)
                return -EINVAL;
        ic->initial_sectors = initial_sectors;
 
-       ic->metadata_run = roundup((__u64)ic->tag_size << (ic->sb->log2_interleave_sectors - ic->sb->log2_sectors_per_block),
-                                  (__u64)(1 << SECTOR_SHIFT << METADATA_PADDING_SECTORS)) >> SECTOR_SHIFT;
-       if (!(ic->metadata_run & (ic->metadata_run - 1)))
-               ic->log2_metadata_run = __ffs(ic->metadata_run);
-       else
-               ic->log2_metadata_run = -1;
+       if (!ic->meta_dev) {
+               sector_t last_sector, last_area, last_offset;
 
-       get_area_and_offset(ic, ic->provided_data_sectors - 1, &last_area, &last_offset);
-       last_sector = get_data_sector(ic, last_area, last_offset);
+               ic->metadata_run = roundup((__u64)ic->tag_size << (ic->sb->log2_interleave_sectors - ic->sb->log2_sectors_per_block),
+                                          (__u64)(1 << SECTOR_SHIFT << METADATA_PADDING_SECTORS)) >> SECTOR_SHIFT;
+               if (!(ic->metadata_run & (ic->metadata_run - 1)))
+                       ic->log2_metadata_run = __ffs(ic->metadata_run);
+               else
+                       ic->log2_metadata_run = -1;
 
-       if (ic->start + last_sector < last_sector || ic->start + last_sector >= ic->device_sectors)
-               return -EINVAL;
+               get_area_and_offset(ic, ic->provided_data_sectors - 1, &last_area, &last_offset);
+               last_sector = get_data_sector(ic, last_area, last_offset);
+               if (last_sector < ic->start || last_sector >= ic->meta_device_sectors)
+                       return -EINVAL;
+       } else {
+               __u64 meta_size = ic->provided_data_sectors * ic->tag_size;
+               meta_size = (meta_size + ((1U << (ic->log2_buffer_sectors + SECTOR_SHIFT)) - 1))
+                               >> (ic->log2_buffer_sectors + SECTOR_SHIFT);
+               meta_size <<= ic->log2_buffer_sectors;
+               if (ic->initial_sectors + meta_size < ic->initial_sectors ||
+                   ic->initial_sectors + meta_size > ic->meta_device_sectors)
+                       return -EINVAL;
+               ic->metadata_run = 1;
+               ic->log2_metadata_run = 0;
+       }
 
        return 0;
 }
@@ -2350,7 +2580,6 @@ static int initialize_superblock(struct dm_integrity_c *ic, unsigned journal_sec
 
        memset(ic->sb, 0, SB_SECTORS << SECTOR_SHIFT);
        memcpy(ic->sb->magic, SB_MAGIC, 8);
-       ic->sb->version = SB_VERSION;
        ic->sb->integrity_tag_size = cpu_to_le16(ic->tag_size);
        ic->sb->log2_sectors_per_block = __ffs(ic->sectors_per_block);
        if (ic->journal_mac_alg.alg_string)
@@ -2360,28 +2589,55 @@ static int initialize_superblock(struct dm_integrity_c *ic, unsigned journal_sec
        journal_sections = journal_sectors / ic->journal_section_sectors;
        if (!journal_sections)
                journal_sections = 1;
-       ic->sb->journal_sections = cpu_to_le32(journal_sections);
 
-       if (!interleave_sectors)
-               interleave_sectors = DEFAULT_INTERLEAVE_SECTORS;
-       ic->sb->log2_interleave_sectors = __fls(interleave_sectors);
-       ic->sb->log2_interleave_sectors = max((__u8)MIN_LOG2_INTERLEAVE_SECTORS, ic->sb->log2_interleave_sectors);
-       ic->sb->log2_interleave_sectors = min((__u8)MAX_LOG2_INTERLEAVE_SECTORS, ic->sb->log2_interleave_sectors);
-
-       ic->provided_data_sectors = 0;
-       for (test_bit = fls64(ic->device_sectors) - 1; test_bit >= 3; test_bit--) {
-               __u64 prev_data_sectors = ic->provided_data_sectors;
+       if (!ic->meta_dev) {
+               ic->sb->journal_sections = cpu_to_le32(journal_sections);
+               if (!interleave_sectors)
+                       interleave_sectors = DEFAULT_INTERLEAVE_SECTORS;
+               ic->sb->log2_interleave_sectors = __fls(interleave_sectors);
+               ic->sb->log2_interleave_sectors = max((__u8)MIN_LOG2_INTERLEAVE_SECTORS, ic->sb->log2_interleave_sectors);
+               ic->sb->log2_interleave_sectors = min((__u8)MAX_LOG2_INTERLEAVE_SECTORS, ic->sb->log2_interleave_sectors);
+
+               ic->provided_data_sectors = 0;
+               for (test_bit = fls64(ic->meta_device_sectors) - 1; test_bit >= 3; test_bit--) {
+                       __u64 prev_data_sectors = ic->provided_data_sectors;
+
+                       ic->provided_data_sectors |= (sector_t)1 << test_bit;
+                       if (calculate_device_limits(ic))
+                               ic->provided_data_sectors = prev_data_sectors;
+               }
+               if (!ic->provided_data_sectors)
+                       return -EINVAL;
+       } else {
+               ic->sb->log2_interleave_sectors = 0;
+               ic->provided_data_sectors = ic->data_device_sectors;
+               ic->provided_data_sectors &= ~(sector_t)(ic->sectors_per_block - 1);
+
+try_smaller_buffer:
+               ic->sb->journal_sections = cpu_to_le32(0);
+               for (test_bit = fls(journal_sections) - 1; test_bit >= 0; test_bit--) {
+                       __u32 prev_journal_sections = le32_to_cpu(ic->sb->journal_sections);
+                       __u32 test_journal_sections = prev_journal_sections | (1U << test_bit);
+                       if (test_journal_sections > journal_sections)
+                               continue;
+                       ic->sb->journal_sections = cpu_to_le32(test_journal_sections);
+                       if (calculate_device_limits(ic))
+                               ic->sb->journal_sections = cpu_to_le32(prev_journal_sections);
 
-               ic->provided_data_sectors |= (sector_t)1 << test_bit;
-               if (calculate_device_limits(ic))
-                       ic->provided_data_sectors = prev_data_sectors;
+               }
+               if (!le32_to_cpu(ic->sb->journal_sections)) {
+                       if (ic->log2_buffer_sectors > 3) {
+                               ic->log2_buffer_sectors--;
+                               goto try_smaller_buffer;
+                       }
+                       return -EINVAL;
+               }
        }
 
-       if (!ic->provided_data_sectors)
-               return -EINVAL;
-
        ic->sb->provided_data_sectors = cpu_to_le64(ic->provided_data_sectors);
 
+       sb_set_version(ic);
+
        return 0;
 }
 
@@ -2828,6 +3084,7 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                {0, 9, "Invalid number of feature args"},
        };
        unsigned journal_sectors, interleave_sectors, buffer_sectors, journal_watermark, sync_msec;
+       bool recalculate;
        bool should_write_sb;
        __u64 threshold;
        unsigned long long start;
@@ -2848,6 +3105,7 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
        ti->per_io_data_size = sizeof(struct dm_integrity_io);
 
        ic->in_progress = RB_ROOT;
+       INIT_LIST_HEAD(&ic->wait_list);
        init_waitqueue_head(&ic->endio_wait);
        bio_list_init(&ic->flush_bio_list);
        init_waitqueue_head(&ic->copy_to_journal_wait);
@@ -2883,13 +3141,12 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                goto bad;
        }
 
-       ic->device_sectors = i_size_read(ic->dev->bdev->bd_inode) >> SECTOR_SHIFT;
-       journal_sectors = min((sector_t)DEFAULT_MAX_JOURNAL_SECTORS,
-                       ic->device_sectors >> DEFAULT_JOURNAL_SIZE_FACTOR);
+       journal_sectors = 0;
        interleave_sectors = DEFAULT_INTERLEAVE_SECTORS;
        buffer_sectors = DEFAULT_BUFFER_SECTORS;
        journal_watermark = DEFAULT_JOURNAL_WATERMARK;
        sync_msec = DEFAULT_SYNC_MSEC;
+       recalculate = false;
        ic->sectors_per_block = 1;
 
        as.argc = argc - DIRECT_ARGUMENTS;
@@ -2908,7 +3165,7 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                        goto bad;
                }
                if (sscanf(opt_string, "journal_sectors:%u%c", &val, &dummy) == 1)
-                       journal_sectors = val;
+                       journal_sectors = val ? val : 1;
                else if (sscanf(opt_string, "interleave_sectors:%u%c", &val, &dummy) == 1)
                        interleave_sectors = val;
                else if (sscanf(opt_string, "buffer_sectors:%u%c", &val, &dummy) == 1)
@@ -2917,7 +3174,17 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                        journal_watermark = val;
                else if (sscanf(opt_string, "commit_time:%u%c", &val, &dummy) == 1)
                        sync_msec = val;
-               else if (sscanf(opt_string, "block_size:%u%c", &val, &dummy) == 1) {
+               else if (!memcmp(opt_string, "meta_device:", strlen("meta_device:"))) {
+                       if (ic->meta_dev) {
+                               dm_put_device(ti, ic->meta_dev);
+                               ic->meta_dev = NULL;
+                       }
+                       r = dm_get_device(ti, strchr(opt_string, ':') + 1, dm_table_get_mode(ti->table), &ic->meta_dev);
+                       if (r) {
+                               ti->error = "Device lookup failed";
+                               goto bad;
+                       }
+               } else if (sscanf(opt_string, "block_size:%u%c", &val, &dummy) == 1) {
                        if (val < 1 << SECTOR_SHIFT ||
                            val > MAX_SECTORS_PER_BLOCK << SECTOR_SHIFT ||
                            (val & (val -1))) {
@@ -2941,6 +3208,8 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                                            "Invalid journal_mac argument");
                        if (r)
                                goto bad;
+               } else if (!strcmp(opt_string, "recalculate")) {
+                       recalculate = true;
                } else {
                        r = -EINVAL;
                        ti->error = "Invalid argument";
@@ -2948,6 +3217,21 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                }
        }
 
+       ic->data_device_sectors = i_size_read(ic->dev->bdev->bd_inode) >> SECTOR_SHIFT;
+       if (!ic->meta_dev)
+               ic->meta_device_sectors = ic->data_device_sectors;
+       else
+               ic->meta_device_sectors = i_size_read(ic->meta_dev->bdev->bd_inode) >> SECTOR_SHIFT;
+
+       if (!journal_sectors) {
+               journal_sectors = min((sector_t)DEFAULT_MAX_JOURNAL_SECTORS,
+                       ic->data_device_sectors >> DEFAULT_JOURNAL_SIZE_FACTOR);
+       }
+
+       if (!buffer_sectors)
+               buffer_sectors = 1;
+       ic->log2_buffer_sectors = min((int)__fls(buffer_sectors), 31 - SECTOR_SHIFT);
+
        r = get_mac(&ic->internal_hash, &ic->internal_hash_alg, &ti->error,
                    "Invalid internal hash", "Error setting internal hash key");
        if (r)
@@ -3062,7 +3346,7 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                        should_write_sb = true;
        }
 
-       if (ic->sb->version != SB_VERSION) {
+       if (!ic->sb->version || ic->sb->version > SB_VERSION_2) {
                r = -EINVAL;
                ti->error = "Unknown version";
                goto bad;
@@ -3083,11 +3367,19 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                goto bad;
        }
        /* make sure that ti->max_io_len doesn't overflow */
-       if (ic->sb->log2_interleave_sectors < MIN_LOG2_INTERLEAVE_SECTORS ||
-           ic->sb->log2_interleave_sectors > MAX_LOG2_INTERLEAVE_SECTORS) {
-               r = -EINVAL;
-               ti->error = "Invalid interleave_sectors in the superblock";
-               goto bad;
+       if (!ic->meta_dev) {
+               if (ic->sb->log2_interleave_sectors < MIN_LOG2_INTERLEAVE_SECTORS ||
+                   ic->sb->log2_interleave_sectors > MAX_LOG2_INTERLEAVE_SECTORS) {
+                       r = -EINVAL;
+                       ti->error = "Invalid interleave_sectors in the superblock";
+                       goto bad;
+               }
+       } else {
+               if (ic->sb->log2_interleave_sectors) {
+                       r = -EINVAL;
+                       ti->error = "Invalid interleave_sectors in the superblock";
+                       goto bad;
+               }
        }
        ic->provided_data_sectors = le64_to_cpu(ic->sb->provided_data_sectors);
        if (ic->provided_data_sectors != le64_to_cpu(ic->sb->provided_data_sectors)) {
@@ -3101,20 +3393,28 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                ti->error = "Journal mac mismatch";
                goto bad;
        }
+
+try_smaller_buffer:
        r = calculate_device_limits(ic);
        if (r) {
+               if (ic->meta_dev) {
+                       if (ic->log2_buffer_sectors > 3) {
+                               ic->log2_buffer_sectors--;
+                               goto try_smaller_buffer;
+                       }
+               }
                ti->error = "The device is too small";
                goto bad;
        }
+       if (!ic->meta_dev)
+               ic->log2_buffer_sectors = min(ic->log2_buffer_sectors, (__u8)__ffs(ic->metadata_run));
+
        if (ti->len > ic->provided_data_sectors) {
                r = -EINVAL;
                ti->error = "Not enough provided sectors for requested mapping size";
                goto bad;
        }
 
-       if (!buffer_sectors)
-               buffer_sectors = 1;
-       ic->log2_buffer_sectors = min3((int)__fls(buffer_sectors), (int)__ffs(ic->metadata_run), 31 - SECTOR_SHIFT);
 
        threshold = (__u64)ic->journal_entries * (100 - journal_watermark);
        threshold += 50;
@@ -3138,8 +3438,40 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                    (unsigned long long)ic->provided_data_sectors);
        DEBUG_print("   log2_buffer_sectors %u\n", ic->log2_buffer_sectors);
 
-       ic->bufio = dm_bufio_client_create(ic->dev->bdev, 1U << (SECTOR_SHIFT + ic->log2_buffer_sectors),
-                                          1, 0, NULL, NULL);
+       if (recalculate && !(ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING))) {
+               ic->sb->flags |= cpu_to_le32(SB_FLAG_RECALCULATING);
+               ic->sb->recalc_sector = cpu_to_le64(0);
+       }
+
+       if (ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING)) {
+               if (!ic->internal_hash) {
+                       r = -EINVAL;
+                       ti->error = "Recalculate is only valid with internal hash";
+                       goto bad;
+               }
+               ic->recalc_wq = alloc_workqueue("dm-intergrity-recalc", WQ_MEM_RECLAIM, 1);
+               if (!ic->recalc_wq ) {
+                       ti->error = "Cannot allocate workqueue";
+                       r = -ENOMEM;
+                       goto bad;
+               }
+               INIT_WORK(&ic->recalc_work, integrity_recalc);
+               ic->recalc_buffer = vmalloc(RECALC_SECTORS << SECTOR_SHIFT);
+               if (!ic->recalc_buffer) {
+                       ti->error = "Cannot allocate buffer for recalculating";
+                       r = -ENOMEM;
+                       goto bad;
+               }
+               ic->recalc_tags = kvmalloc((RECALC_SECTORS >> ic->sb->log2_sectors_per_block) * ic->tag_size, GFP_KERNEL);
+               if (!ic->recalc_tags) {
+                       ti->error = "Cannot allocate tags for recalculating";
+                       r = -ENOMEM;
+                       goto bad;
+               }
+       }
+
+       ic->bufio = dm_bufio_client_create(ic->meta_dev ? ic->meta_dev->bdev : ic->dev->bdev,
+                       1U << (SECTOR_SHIFT + ic->log2_buffer_sectors), 1, 0, NULL, NULL);
        if (IS_ERR(ic->bufio)) {
                r = PTR_ERR(ic->bufio);
                ti->error = "Cannot initialize dm-bufio";
@@ -3171,9 +3503,11 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                ic->just_formatted = true;
        }
 
-       r = dm_set_target_max_io_len(ti, 1U << ic->sb->log2_interleave_sectors);
-       if (r)
-               goto bad;
+       if (!ic->meta_dev) {
+               r = dm_set_target_max_io_len(ti, 1U << ic->sb->log2_interleave_sectors);
+               if (r)
+                       goto bad;
+       }
 
        if (!ic->internal_hash)
                dm_integrity_set(ti, ic);
@@ -3192,6 +3526,7 @@ static void dm_integrity_dtr(struct dm_target *ti)
        struct dm_integrity_c *ic = ti->private;
 
        BUG_ON(!RB_EMPTY_ROOT(&ic->in_progress));
+       BUG_ON(!list_empty(&ic->wait_list));
 
        if (ic->metadata_wq)
                destroy_workqueue(ic->metadata_wq);
@@ -3201,6 +3536,12 @@ static void dm_integrity_dtr(struct dm_target *ti)
                destroy_workqueue(ic->commit_wq);
        if (ic->writer_wq)
                destroy_workqueue(ic->writer_wq);
+       if (ic->recalc_wq)
+               destroy_workqueue(ic->recalc_wq);
+       if (ic->recalc_buffer)
+               vfree(ic->recalc_buffer);
+       if (ic->recalc_tags)
+               kvfree(ic->recalc_tags);
        if (ic->bufio)
                dm_bufio_client_destroy(ic->bufio);
        mempool_exit(&ic->journal_io_mempool);
@@ -3208,6 +3549,8 @@ static void dm_integrity_dtr(struct dm_target *ti)
                dm_io_client_destroy(ic->io);
        if (ic->dev)
                dm_put_device(ti, ic->dev);
+       if (ic->meta_dev)
+               dm_put_device(ti, ic->meta_dev);
        dm_integrity_free_page_list(ic, ic->journal);
        dm_integrity_free_page_list(ic, ic->journal_io);
        dm_integrity_free_page_list(ic, ic->journal_xor);
@@ -3248,7 +3591,7 @@ static void dm_integrity_dtr(struct dm_target *ti)
 
 static struct target_type integrity_target = {
        .name                   = "integrity",
-       .version                = {1, 1, 0},
+       .version                = {1, 2, 0},
        .module                 = THIS_MODULE,
        .features               = DM_TARGET_SINGLETON | DM_TARGET_INTEGRITY,
        .ctr                    = dm_integrity_ctr,