dm cache: add support for discard passdown to the origin device [Linux 5.1]

dm cache: add support for discard passdown to the origin device [Linux 5.1]

This Linux kernel change "dm cache: add support for discard passdown to the origin device" is included in the Linux 5.1 release. This change is authored by Mike Snitzer <snitzer [at] redhat.com> on Mon Feb 25 11:07:10 2019 -0500. The commit for this change in Linux stable tree is de7180f (patch).

dm cache: add support for discard passdown to the origin device

DM cache now defaults to passing discards down to the origin device.
User may disable this using the "no_discard_passdown" feature when
creating the cache device.

If the cache's underlying origin device doesn't support discards then
passdown is disabled (with warning).  Similarly, if the underlying
origin device's max_discard_sectors is less than a cache block discard
passdown will be disabled (this is required because sizing of the cache
internal discard bitset depends on it).

Signed-off-by: Mike Snitzer <snitzer@redhat.com>

There are 129 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 Documentation/device-mapper/cache.txt |   3 +
 drivers/md/dm-cache-target.c          | 126 +++++++++++++++++++++++++++-------
 2 files changed, 103 insertions(+), 26 deletions(-)

diff --git a/Documentation/device-mapper/cache.txt b/Documentation/device-mapper/cache.txt
index ff08417..8ae1cf8 100644
--- a/Documentation/device-mapper/cache.txt
+++ b/Documentation/device-mapper/cache.txt
@@ -206,6 +206,9 @@ Optional feature arguments are:
                   in a separate btree, which improves speed of shutting
          down the cache.

+   no_discard_passdown : disable passing down discards from the cache
+             to the origin's data device.
+
 A policy called 'default' is always registered.  This is an alias for
 the policy we currently think is giving best all round performance.

diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
index adc529f..d249cf8 100644
--- a/drivers/md/dm-cache-target.c
+++ b/drivers/md/dm-cache-target.c
@@ -353,6 +353,7 @@ struct cache_features {
    enum cache_metadata_mode mode;
    enum cache_io_mode io_mode;
    unsigned metadata_version;
+   bool discard_passdown:1;
 };

 struct cache_stats {
@@ -1899,7 +1900,11 @@ static bool process_discard_bio(struct cache *cache, struct bio *bio)
        b = to_dblock(from_dblock(b) + 1);
    }

-   bio_endio(bio);
+   if (cache->features.discard_passdown) {
+       remap_to_origin(cache, bio);
+       generic_make_request(bio);
+   } else
+       bio_endio(bio);

    return false;
 }
@@ -2233,13 +2238,14 @@ static void init_features(struct cache_features *cf)
    cf->mode = CM_WRITE;
    cf->io_mode = CM_IO_WRITEBACK;
    cf->metadata_version = 1;
+   cf->discard_passdown = true;
 }

 static int parse_features(struct cache_args *ca, struct dm_arg_set *as,
              char **error)
 {
    static const struct dm_arg _args[] = {
-       {0, 2, "Invalid number of cache feature arguments"},
+       {0, 3, "Invalid number of cache feature arguments"},
    };

    int r, mode_ctr = 0;
@@ -2274,6 +2280,9 @@ static int parse_features(struct cache_args *ca, struct dm_arg_set *as,
        else if (!strcasecmp(arg, "metadata2"))
            cf->metadata_version = 2;

+       else if (!strcasecmp(arg, "no_discard_passdown"))
+           cf->discard_passdown = false;
+
        else {
            *error = "Unrecognised cache feature requested";
            return -EINVAL;
@@ -3119,6 +3128,39 @@ static void cache_resume(struct dm_target *ti)
    do_waker(&cache->waker.work);
 }

+static void emit_flags(struct cache *cache, char *result,
+              unsigned maxlen, ssize_t *sz_ptr)
+{
+   ssize_t sz = *sz_ptr;
+   struct cache_features *cf = &cache->features;
+   unsigned count = (cf->metadata_version == 2) + !cf->discard_passdown + 1;
+
+   DMEMIT("%u ", count);
+
+   if (cf->metadata_version == 2)
+       DMEMIT("metadata2 ");
+
+   if (writethrough_mode(cache))
+       DMEMIT("writethrough ");
+
+   else if (passthrough_mode(cache))
+       DMEMIT("passthrough ");
+
+   else if (writeback_mode(cache))
+       DMEMIT("writeback ");
+
+   else {
+       DMEMIT("unknown ");
+       DMERR("%s: internal error: unknown io mode: %d",
+             cache_device_name(cache), (int) cf->io_mode);
+   }
+
+   if (!cf->discard_passdown)
+       DMEMIT("no_discard_passdown ");
+
+   *sz_ptr = sz;
+}
+
 /*
  * Status format:
  *
@@ -3185,25 +3227,7 @@ static void cache_status(struct dm_target *ti, status_type_t type,
               (unsigned) atomic_read(&cache->stats.promotion),
               (unsigned long) atomic_read(&cache->nr_dirty));

-       if (cache->features.metadata_version == 2)
-           DMEMIT("2 metadata2 ");
-       else
-           DMEMIT("1 ");
-
-       if (writethrough_mode(cache))
-           DMEMIT("writethrough ");
-
-       else if (passthrough_mode(cache))
-           DMEMIT("passthrough ");
-
-       else if (writeback_mode(cache))
-           DMEMIT("writeback ");
-
-       else {
-           DMERR("%s: internal error: unknown io mode: %d",
-                 cache_device_name(cache), (int) cache->features.io_mode);
-           goto err;
-       }
+       emit_flags(cache, result, maxlen, &sz);

        DMEMIT("2 migration_threshold %llu ", (unsigned long long) cache->migration_threshold);

@@ -3432,14 +3456,62 @@ static int cache_iterate_devices(struct dm_target *ti,
    return r;
 }

+static bool origin_dev_supports_discard(struct block_device *origin_bdev)
+{
+   struct request_queue *q = bdev_get_queue(origin_bdev);
+
+   return q && blk_queue_discard(q);
+}
+
+/*
+ * If discard_passdown was enabled verify that the origin device
+ * supports discards.  Disable discard_passdown if not.
+ */
+static void disable_passdown_if_not_supported(struct cache *cache)
+{
+   struct block_device *origin_bdev = cache->origin_dev->bdev;
+   struct queue_limits *origin_limits = &bdev_get_queue(origin_bdev)->limits;
+   const char *reason = NULL;
+   char buf[BDEVNAME_SIZE];
+
+   if (!cache->features.discard_passdown)
+       return;
+
+   if (!origin_dev_supports_discard(origin_bdev))
+       reason = "discard unsupported";
+
+   else if (origin_limits->max_discard_sectors < cache->sectors_per_block)
+       reason = "max discard sectors smaller than a block";
+
+   if (reason) {
+       DMWARN("Origin device (%s) %s: Disabling discard passdown.",
+              bdevname(origin_bdev, buf), reason);
+       cache->features.discard_passdown = false;
+   }
+}
+
 static void set_discard_limits(struct cache *cache, struct queue_limits *limits)
 {
+   struct block_device *origin_bdev = cache->origin_dev->bdev;
+   struct queue_limits *origin_limits = &bdev_get_queue(origin_bdev)->limits;
+
+   if (!cache->features.discard_passdown) {
+       /* No passdown is done so setting own virtual limits */
+       limits->max_discard_sectors = min_t(sector_t, cache->discard_block_size * 1024,
+                           cache->origin_sectors);
+       limits->discard_granularity = cache->discard_block_size << SECTOR_SHIFT;
+       return;
+   }
+
    /*
-    * FIXME: these limits may be incompatible with the cache device
+    * cache_iterate_devices() is stacking both origin and fast device limits
+    * but discards aren't passed to fast device, so inherit origin's limits.
     */
-   limits->max_discard_sectors = min_t(sector_t, cache->discard_block_size * 1024,
-                       cache->origin_sectors);
-   limits->discard_granularity = cache->discard_block_size << SECTOR_SHIFT;
+   limits->max_discard_sectors = origin_limits->max_discard_sectors;
+   limits->max_hw_discard_sectors = origin_limits->max_hw_discard_sectors;
+   limits->discard_granularity = origin_limits->discard_granularity;
+   limits->discard_alignment = origin_limits->discard_alignment;
+   limits->discard_misaligned = origin_limits->discard_misaligned;
 }

 static void cache_io_hints(struct dm_target *ti, struct queue_limits *limits)
@@ -3456,6 +3528,8 @@ static void cache_io_hints(struct dm_target *ti, struct queue_limits *limits)
        blk_limits_io_min(limits, cache->sectors_per_block << SECTOR_SHIFT);
        blk_limits_io_opt(limits, cache->sectors_per_block << SECTOR_SHIFT);
    }
+
+   disable_passdown_if_not_supported(cache);
    set_discard_limits(cache, limits);
 }

@@ -3463,7 +3537,7 @@ static void cache_io_hints(struct dm_target *ti, struct queue_limits *limits)

 static struct target_type cache_target = {
    .name = "cache",
-   .version = {2, 0, 0},
+   .version = {2, 1, 0},
    .module = THIS_MODULE,
    .ctr = cache_ctr,
    .dtr = cache_dtr,

Leave a Reply

Your email address will not be published. Required fields are marked *