md/raid5: Fix CPU hotplug callback registration
[firefly-linux-kernel-4.4.55.git] / drivers / md / dm-log-userspace-base.c
index 9429159d9ee335c78c57f19e023b68d602683acc..b953db6cc229ae65f30520c23918c2cf0abf4fd5 100644 (file)
 #include <linux/device-mapper.h>
 #include <linux/dm-log-userspace.h>
 #include <linux/module.h>
+#include <linux/workqueue.h>
 
 #include "dm-log-userspace-transfer.h"
 
-#define DM_LOG_USERSPACE_VSN "1.1.0"
+#define DM_LOG_USERSPACE_VSN "1.3.0"
 
 struct flush_entry {
        int type;
@@ -58,6 +59,18 @@ struct log_c {
        spinlock_t flush_lock;
        struct list_head mark_list;
        struct list_head clear_list;
+
+       /*
+        * Workqueue for flush of clear region requests.
+        */
+       struct workqueue_struct *dmlog_wq;
+       struct delayed_work flush_log_work;
+       atomic_t sched_flush;
+
+       /*
+        * Combine userspace flush and mark requests for efficiency.
+        */
+       uint32_t integrated_flush;
 };
 
 static mempool_t *flush_entry_pool;
@@ -122,6 +135,9 @@ static int build_constructor_string(struct dm_target *ti,
 
        *ctr_str = NULL;
 
+       /*
+        * Determine overall size of the string.
+        */
        for (i = 0, str_size = 0; i < argc; i++)
                str_size += strlen(argv[i]) + 1; /* +1 for space between args */
 
@@ -141,18 +157,39 @@ static int build_constructor_string(struct dm_target *ti,
        return str_size;
 }
 
+static void do_flush(struct work_struct *work)
+{
+       int r;
+       struct log_c *lc = container_of(work, struct log_c, flush_log_work.work);
+
+       atomic_set(&lc->sched_flush, 0);
+
+       r = userspace_do_request(lc, lc->uuid, DM_ULOG_FLUSH, NULL, 0, NULL, NULL);
+
+       if (r)
+               dm_table_event(lc->ti->table);
+}
+
 /*
  * userspace_ctr
  *
  * argv contains:
- *     <UUID> <other args>
- * Where 'other args' is the userspace implementation specific log
- * arguments.  An example might be:
- *     <UUID> clustered-disk <arg count> <log dev> <region_size> [[no]sync]
+ *     <UUID> [integrated_flush] <other args>
+ * Where 'other args' are the userspace implementation-specific log
+ * arguments.
+ *
+ * Example:
+ *     <UUID> [integrated_flush] clustered-disk <arg count> <log dev>
+ *     <region_size> [[no]sync]
+ *
+ * This module strips off the <UUID> and uses it for identification
+ * purposes when communicating with userspace about a log.
  *
- * So, this module will strip off the <UUID> for identification purposes
- * when communicating with userspace about a log; but will pass on everything
- * else.
+ * If integrated_flush is defined, the kernel combines flush
+ * and mark requests.
+ *
+ * The rest of the line, beginning with 'clustered-disk', is passed
+ * to the userspace ctr function.
  */
 static int userspace_ctr(struct dm_dirty_log *log, struct dm_target *ti,
                         unsigned argc, char **argv)
@@ -188,12 +225,22 @@ static int userspace_ctr(struct dm_dirty_log *log, struct dm_target *ti,
                return -EINVAL;
        }
 
+       lc->usr_argc = argc;
+
        strncpy(lc->uuid, argv[0], DM_UUID_LEN);
+       argc--;
+       argv++;
        spin_lock_init(&lc->flush_lock);
        INIT_LIST_HEAD(&lc->mark_list);
        INIT_LIST_HEAD(&lc->clear_list);
 
-       str_size = build_constructor_string(ti, argc - 1, argv + 1, &ctr_str);
+       if (!strcasecmp(argv[0], "integrated_flush")) {
+               lc->integrated_flush = 1;
+               argc--;
+               argv++;
+       }
+
+       str_size = build_constructor_string(ti, argc, argv, &ctr_str);
        if (str_size < 0) {
                kfree(lc);
                return str_size;
@@ -246,6 +293,19 @@ static int userspace_ctr(struct dm_dirty_log *log, struct dm_target *ti,
                        DMERR("Failed to register %s with device-mapper",
                              devices_rdata);
        }
+
+       if (lc->integrated_flush) {
+               lc->dmlog_wq = alloc_workqueue("dmlogd", WQ_MEM_RECLAIM, 0);
+               if (!lc->dmlog_wq) {
+                       DMERR("couldn't start dmlogd");
+                       r = -ENOMEM;
+                       goto out;
+               }
+
+               INIT_DELAYED_WORK(&lc->flush_log_work, do_flush);
+               atomic_set(&lc->sched_flush, 0);
+       }
+
 out:
        kfree(devices_rdata);
        if (r) {
@@ -253,7 +313,6 @@ out:
                kfree(ctr_str);
        } else {
                lc->usr_argv_str = ctr_str;
-               lc->usr_argc = argc;
                log->context = lc;
        }
 
@@ -264,9 +323,16 @@ static void userspace_dtr(struct dm_dirty_log *log)
 {
        struct log_c *lc = log->context;
 
+       if (lc->integrated_flush) {
+               /* flush workqueue */
+               if (atomic_read(&lc->sched_flush))
+                       flush_delayed_work(&lc->flush_log_work);
+
+               destroy_workqueue(lc->dmlog_wq);
+       }
+
        (void) dm_consult_userspace(lc->uuid, lc->luid, DM_ULOG_DTR,
-                                NULL, 0,
-                                NULL, NULL);
+                                   NULL, 0, NULL, NULL);
 
        if (lc->log_dev)
                dm_put_device(lc->ti, lc->log_dev);
@@ -283,8 +349,7 @@ static int userspace_presuspend(struct dm_dirty_log *log)
        struct log_c *lc = log->context;
 
        r = dm_consult_userspace(lc->uuid, lc->luid, DM_ULOG_PRESUSPEND,
-                                NULL, 0,
-                                NULL, NULL);
+                                NULL, 0, NULL, NULL);
 
        return r;
 }
@@ -294,9 +359,14 @@ static int userspace_postsuspend(struct dm_dirty_log *log)
        int r;
        struct log_c *lc = log->context;
 
+       /*
+        * Run planned flush earlier.
+        */
+       if (lc->integrated_flush && atomic_read(&lc->sched_flush))
+               flush_delayed_work(&lc->flush_log_work);
+
        r = dm_consult_userspace(lc->uuid, lc->luid, DM_ULOG_POSTSUSPEND,
-                                NULL, 0,
-                                NULL, NULL);
+                                NULL, 0, NULL, NULL);
 
        return r;
 }
@@ -308,8 +378,7 @@ static int userspace_resume(struct dm_dirty_log *log)
 
        lc->in_sync_hint = 0;
        r = dm_consult_userspace(lc->uuid, lc->luid, DM_ULOG_RESUME,
-                                NULL, 0,
-                                NULL, NULL);
+                                NULL, 0, NULL, NULL);
 
        return r;
 }
@@ -405,7 +474,8 @@ static int flush_one_by_one(struct log_c *lc, struct list_head *flush_list)
        return r;
 }
 
-static int flush_by_group(struct log_c *lc, struct list_head *flush_list)
+static int flush_by_group(struct log_c *lc, struct list_head *flush_list,
+                         int flush_with_payload)
 {
        int r = 0;
        int count;
@@ -431,15 +501,29 @@ static int flush_by_group(struct log_c *lc, struct list_head *flush_list)
                                break;
                }
 
-               r = userspace_do_request(lc, lc->uuid, type,
-                                        (char *)(group),
-                                        count * sizeof(uint64_t),
-                                        NULL, NULL);
-               if (r) {
-                       /* Group send failed.  Attempt one-by-one. */
-                       list_splice_init(&tmp_list, flush_list);
-                       r = flush_one_by_one(lc, flush_list);
-                       break;
+               if (flush_with_payload) {
+                       r = userspace_do_request(lc, lc->uuid, DM_ULOG_FLUSH,
+                                                (char *)(group),
+                                                count * sizeof(uint64_t),
+                                                NULL, NULL);
+                       /*
+                        * Integrated flush failed.
+                        */
+                       if (r)
+                               break;
+               } else {
+                       r = userspace_do_request(lc, lc->uuid, type,
+                                                (char *)(group),
+                                                count * sizeof(uint64_t),
+                                                NULL, NULL);
+                       if (r) {
+                               /*
+                                * Group send failed.  Attempt one-by-one.
+                                */
+                               list_splice_init(&tmp_list, flush_list);
+                               r = flush_one_by_one(lc, flush_list);
+                               break;
+                       }
                }
        }
 
@@ -476,6 +560,8 @@ static int userspace_flush(struct dm_dirty_log *log)
        struct log_c *lc = log->context;
        LIST_HEAD(mark_list);
        LIST_HEAD(clear_list);
+       int mark_list_is_empty;
+       int clear_list_is_empty;
        struct flush_entry *fe, *tmp_fe;
 
        spin_lock_irqsave(&lc->flush_lock, flags);
@@ -483,23 +569,51 @@ static int userspace_flush(struct dm_dirty_log *log)
        list_splice_init(&lc->clear_list, &clear_list);
        spin_unlock_irqrestore(&lc->flush_lock, flags);
 
-       if (list_empty(&mark_list) && list_empty(&clear_list))
+       mark_list_is_empty = list_empty(&mark_list);
+       clear_list_is_empty = list_empty(&clear_list);
+
+       if (mark_list_is_empty && clear_list_is_empty)
                return 0;
 
-       r = flush_by_group(lc, &mark_list);
+       r = flush_by_group(lc, &clear_list, 0);
        if (r)
-               goto fail;
+               goto out;
 
-       r = flush_by_group(lc, &clear_list);
+       if (!lc->integrated_flush) {
+               r = flush_by_group(lc, &mark_list, 0);
+               if (r)
+                       goto out;
+               r = userspace_do_request(lc, lc->uuid, DM_ULOG_FLUSH,
+                                        NULL, 0, NULL, NULL);
+               goto out;
+       }
+
+       /*
+        * Send integrated flush request with mark_list as payload.
+        */
+       r = flush_by_group(lc, &mark_list, 1);
        if (r)
-               goto fail;
+               goto out;
 
-       r = userspace_do_request(lc, lc->uuid, DM_ULOG_FLUSH,
-                                NULL, 0, NULL, NULL);
+       if (mark_list_is_empty && !atomic_read(&lc->sched_flush)) {
+               /*
+                * When there are only clear region requests,
+                * we schedule a flush in the future.
+                */
+               queue_delayed_work(lc->dmlog_wq, &lc->flush_log_work, 3 * HZ);
+               atomic_set(&lc->sched_flush, 1);
+       } else {
+               /*
+                * Cancel pending flush because we
+                * have already flushed in mark_region.
+                */
+               cancel_delayed_work(&lc->flush_log_work);
+               atomic_set(&lc->sched_flush, 0);
+       }
 
-fail:
+out:
        /*
-        * We can safely remove these entries, even if failure.
+        * We can safely remove these entries, even after failure.
         * Calling code will receive an error and will know that
         * the log facility has failed.
         */
@@ -603,8 +717,7 @@ static int userspace_get_resync_work(struct dm_dirty_log *log, region_t *region)
 
        rdata_size = sizeof(pkg);
        r = userspace_do_request(lc, lc->uuid, DM_ULOG_GET_RESYNC_WORK,
-                                NULL, 0,
-                                (char *)&pkg, &rdata_size);
+                                NULL, 0, (char *)&pkg, &rdata_size);
 
        *region = pkg.r;
        return (r) ? r : (int)pkg.i;
@@ -630,8 +743,7 @@ static void userspace_set_region_sync(struct dm_dirty_log *log,
        pkg.i = (int64_t)in_sync;
 
        r = userspace_do_request(lc, lc->uuid, DM_ULOG_SET_REGION_SYNC,
-                                (char *)&pkg, sizeof(pkg),
-                                NULL, NULL);
+                                (char *)&pkg, sizeof(pkg), NULL, NULL);
 
        /*
         * It would be nice to be able to report failures.
@@ -657,8 +769,7 @@ static region_t userspace_get_sync_count(struct dm_dirty_log *log)
 
        rdata_size = sizeof(sync_count);
        r = userspace_do_request(lc, lc->uuid, DM_ULOG_GET_SYNC_COUNT,
-                                NULL, 0,
-                                (char *)&sync_count, &rdata_size);
+                                NULL, 0, (char *)&sync_count, &rdata_size);
 
        if (r)
                return 0;
@@ -685,8 +796,7 @@ static int userspace_status(struct dm_dirty_log *log, status_type_t status_type,
        switch (status_type) {
        case STATUSTYPE_INFO:
                r = userspace_do_request(lc, lc->uuid, DM_ULOG_STATUS_INFO,
-                                        NULL, 0,
-                                        result, &sz);
+                                        NULL, 0, result, &sz);
 
                if (r) {
                        sz = 0;
@@ -699,8 +809,10 @@ static int userspace_status(struct dm_dirty_log *log, status_type_t status_type,
                BUG_ON(!table_args); /* There will always be a ' ' */
                table_args++;
 
-               DMEMIT("%s %u %s %s ", log->type->name, lc->usr_argc,
-                      lc->uuid, table_args);
+               DMEMIT("%s %u %s ", log->type->name, lc->usr_argc, lc->uuid);
+               if (lc->integrated_flush)
+                       DMEMIT("integrated_flush ");
+               DMEMIT("%s ", table_args);
                break;
        }
        return (r) ? 0 : (int)sz;