dm: improve block integrity support
authorMike Snitzer <snitzer@redhat.com>
Fri, 1 Apr 2011 19:02:31 +0000 (21:02 +0200)
committerJens Axboe <jaxboe@fusionio.com>
Tue, 5 Apr 2011 21:52:43 +0000 (23:52 +0200)
The current block integrity (DIF/DIX) support in DM is verifying that
all devices' integrity profiles match during DM device resume (which
is past the point of no return).  To some degree that is unavoidable
(stacked DM devices force this late checking).  But for most DM
devices (which aren't stacking on other DM devices) the ideal time to
verify all integrity profiles match is during table load.

Introduce the notion of an "initialized" integrity profile: a profile
that was blk_integrity_register()'d with a non-NULL 'blk_integrity'
template.  Add blk_integrity_is_initialized() to allow checking if a
profile was initialized.

Update DM integrity support to:
- check all devices with _initialized_ integrity profiles match
  during table load; uninitialized profiles (e.g. for underlying DM
  device(s) of a stacked DM device) are ignored.
- disallow a table load that would result in an integrity profile that
  conflicts with a DM device's existing (in-use) integrity profile
- avoid clearing an existing integrity profile
- validate all integrity profiles match during resume; but if they
  don't all we can do is report the mismatch (during resume we're past
  the point of no return)

Signed-off-by: Mike Snitzer <snitzer@redhat.com>
Cc: Martin K. Petersen <martin.petersen@oracle.com>
Signed-off-by: Jens Axboe <jaxboe@fusionio.com>
block/blk-integrity.c
drivers/md/dm-table.c
include/linux/blkdev.h

index 54bcba6c02a75ed87af935c49d4428f18eb6a615..129b9e209a3b46dcf06ab36c0629e632e9b75035 100644 (file)
@@ -30,6 +30,8 @@
 
 static struct kmem_cache *integrity_cachep;
 
+static const char *bi_unsupported_name = "unsupported";
+
 /**
  * blk_rq_count_integrity_sg - Count number of integrity scatterlist elements
  * @q:         request queue
@@ -358,6 +360,14 @@ static struct kobj_type integrity_ktype = {
        .release        = blk_integrity_release,
 };
 
+bool blk_integrity_is_initialized(struct gendisk *disk)
+{
+       struct blk_integrity *bi = blk_get_integrity(disk);
+
+       return (bi && bi->name && strcmp(bi->name, bi_unsupported_name) != 0);
+}
+EXPORT_SYMBOL(blk_integrity_is_initialized);
+
 /**
  * blk_integrity_register - Register a gendisk as being integrity-capable
  * @disk:      struct gendisk pointer to make integrity-aware
@@ -407,7 +417,7 @@ int blk_integrity_register(struct gendisk *disk, struct blk_integrity *template)
                bi->get_tag_fn = template->get_tag_fn;
                bi->tag_size = template->tag_size;
        } else
-               bi->name = "unsupported";
+               bi->name = bi_unsupported_name;
 
        return 0;
 }
index 416d4e258df6df536d2cd7d38795e8e58b94efbc..cb8380c9767fd3d5bb815d7fec4a46308b0270c5 100644 (file)
@@ -926,21 +926,81 @@ static int dm_table_build_index(struct dm_table *t)
        return r;
 }
 
+/*
+ * Get a disk whose integrity profile reflects the table's profile.
+ * If %match_all is true, all devices' profiles must match.
+ * If %match_all is false, all devices must at least have an
+ * allocated integrity profile; but uninitialized is ok.
+ * Returns NULL if integrity support was inconsistent or unavailable.
+ */
+static struct gendisk * dm_table_get_integrity_disk(struct dm_table *t,
+                                                   bool match_all)
+{
+       struct list_head *devices = dm_table_get_devices(t);
+       struct dm_dev_internal *dd = NULL;
+       struct gendisk *prev_disk = NULL, *template_disk = NULL;
+
+       list_for_each_entry(dd, devices, list) {
+               template_disk = dd->dm_dev.bdev->bd_disk;
+               if (!blk_get_integrity(template_disk))
+                       goto no_integrity;
+               if (!match_all && !blk_integrity_is_initialized(template_disk))
+                       continue; /* skip uninitialized profiles */
+               else if (prev_disk &&
+                        blk_integrity_compare(prev_disk, template_disk) < 0)
+                       goto no_integrity;
+               prev_disk = template_disk;
+       }
+
+       return template_disk;
+
+no_integrity:
+       if (prev_disk)
+               DMWARN("%s: integrity not set: %s and %s profile mismatch",
+                      dm_device_name(t->md),
+                      prev_disk->disk_name,
+                      template_disk->disk_name);
+       return NULL;
+}
+
 /*
  * Register the mapped device for blk_integrity support if
- * the underlying devices support it.
+ * the underlying devices have an integrity profile.  But all devices
+ * may not have matching profiles (checking all devices isn't reliable
+ * during table load because this table may use other DM device(s) which
+ * must be resumed before they will have an initialized integity profile).
+ * Stacked DM devices force a 2 stage integrity profile validation:
+ * 1 - during load, validate all initialized integrity profiles match
+ * 2 - during resume, validate all integrity profiles match
  */
 static int dm_table_prealloc_integrity(struct dm_table *t, struct mapped_device *md)
 {
-       struct list_head *devices = dm_table_get_devices(t);
-       struct dm_dev_internal *dd;
+       struct gendisk *template_disk = NULL;
 
-       list_for_each_entry(dd, devices, list)
-               if (bdev_get_integrity(dd->dm_dev.bdev)) {
-                       t->integrity_supported = 1;
-                       return blk_integrity_register(dm_disk(md), NULL);
-               }
+       template_disk = dm_table_get_integrity_disk(t, false);
+       if (!template_disk)
+               return 0;
 
+       if (!blk_integrity_is_initialized(dm_disk(md))) {
+               t->integrity_supported = 1;
+               return blk_integrity_register(dm_disk(md), NULL);
+       }
+
+       /*
+        * If DM device already has an initalized integrity
+        * profile the new profile should not conflict.
+        */
+       if (blk_integrity_is_initialized(template_disk) &&
+           blk_integrity_compare(dm_disk(md), template_disk) < 0) {
+               DMWARN("%s: conflict with existing integrity profile: "
+                      "%s profile mismatch",
+                      dm_device_name(t->md),
+                      template_disk->disk_name);
+               return 1;
+       }
+
+       /* Preserve existing initialized integrity profile */
+       t->integrity_supported = 1;
        return 0;
 }
 
@@ -1094,41 +1154,27 @@ combine_limits:
 
 /*
  * Set the integrity profile for this device if all devices used have
- * matching profiles.
+ * matching profiles.  We're quite deep in the resume path but still
+ * don't know if all devices (particularly DM devices this device
+ * may be stacked on) have matching profiles.  Even if the profiles
+ * don't match we have no way to fail (to resume) at this point.
  */
 static void dm_table_set_integrity(struct dm_table *t)
 {
-       struct list_head *devices = dm_table_get_devices(t);
-       struct dm_dev_internal *prev = NULL, *dd = NULL;
+       struct gendisk *template_disk = NULL;
 
        if (!blk_get_integrity(dm_disk(t->md)))
                return;
 
-       list_for_each_entry(dd, devices, list) {
-               if (prev &&
-                   blk_integrity_compare(prev->dm_dev.bdev->bd_disk,
-                                         dd->dm_dev.bdev->bd_disk) < 0) {
-                       DMWARN("%s: integrity not set: %s and %s mismatch",
-                              dm_device_name(t->md),
-                              prev->dm_dev.bdev->bd_disk->disk_name,
-                              dd->dm_dev.bdev->bd_disk->disk_name);
-                       goto no_integrity;
-               }
-               prev = dd;
+       template_disk = dm_table_get_integrity_disk(t, true);
+       if (!template_disk &&
+           blk_integrity_is_initialized(dm_disk(t->md))) {
+               DMWARN("%s: device no longer has a valid integrity profile",
+                      dm_device_name(t->md));
+               return;
        }
-
-       if (!prev || !bdev_get_integrity(prev->dm_dev.bdev))
-               goto no_integrity;
-
        blk_integrity_register(dm_disk(t->md),
-                              bdev_get_integrity(prev->dm_dev.bdev));
-
-       return;
-
-no_integrity:
-       blk_integrity_register(dm_disk(t->md), NULL);
-
-       return;
+                              blk_get_integrity(template_disk));
 }
 
 void dm_table_set_restrictions(struct dm_table *t, struct request_queue *q,
index 16a902f099ac0ee7b1f64c4322c6255a82aa0e09..32176cc8e715e450a40e7ba24d39010603cf75f3 100644 (file)
@@ -1206,6 +1206,7 @@ struct blk_integrity {
        struct kobject          kobj;
 };
 
+extern bool blk_integrity_is_initialized(struct gendisk *);
 extern int blk_integrity_register(struct gendisk *, struct blk_integrity *);
 extern void blk_integrity_unregister(struct gendisk *);
 extern int blk_integrity_compare(struct gendisk *, struct gendisk *);
@@ -1262,6 +1263,7 @@ queue_max_integrity_segments(struct request_queue *q)
 #define queue_max_integrity_segments(a)                (0)
 #define blk_integrity_merge_rq(a, b, c)                (0)
 #define blk_integrity_merge_bio(a, b, c)       (0)
+#define blk_integrity_is_initialized(a)                (0)
 
 #endif /* CONFIG_BLK_DEV_INTEGRITY */