The kernel ARS state machine implements an exponential backoff timeout
to not spam the platform ARS interface, a potentially high overhead
interface. Recent kernel changes allow root to bypass / reset the
polling interval. Add an option to 'ndctl wait-scrub' to attempt to poll
at a user-specified frequency (when that user is root).

As part of the implementation of the 'wait-scrub' enhancement take the
opportunity to refactor the exported
ndctl_bus_wait_for_scrub_completion() helper function into the more
capable ndctl_bus_poll_scrub_completion().

Reported-by: Erwin Tsaur <[email protected]>
Signed-off-by: Dan Williams <[email protected]>
---
 ndctl/bus.c            |   25 +++++++++++---
 ndctl/lib/libndctl.c   |   88 ++++++++++++++++++++++++++++++++++++++----------
 ndctl/lib/libndctl.sym |    6 +++
 ndctl/libndctl.h       |    2 +
 4 files changed, 97 insertions(+), 24 deletions(-)

diff --git a/ndctl/bus.c b/ndctl/bus.c
index ce7f76add777..86bbd5178df9 100644
--- a/ndctl/bus.c
+++ b/ndctl/bus.c
@@ -16,10 +16,24 @@
 
 static struct {
        bool verbose;
+       unsigned int poll_interval;
 } param;
 
-static const struct option bus_options[] = {
-       OPT_BOOLEAN('v',"verbose", &param.verbose, "turn on debug"),
+
+#define BASE_OPTIONS() \
+       OPT_BOOLEAN('v',"verbose", &param.verbose, "turn on debug")
+
+#define WAIT_OPTIONS() \
+       OPT_UINTEGER('p', "poll", &param.poll_interval, "poll interval 
(seconds)")
+
+static const struct option start_options[] = {
+       BASE_OPTIONS(),
+       OPT_END(),
+};
+
+static const struct option wait_options[] = {
+       BASE_OPTIONS(),
+       WAIT_OPTIONS(),
        OPT_END(),
 };
 
@@ -27,7 +41,8 @@ static int scrub_action(struct ndctl_bus *bus, enum 
device_action action)
 {
        switch (action) {
        case ACTION_WAIT:
-               return ndctl_bus_wait_for_scrub_completion(bus);
+               return ndctl_bus_poll_scrub_completion(bus,
+                               param.poll_interval, 0);
        case ACTION_START:
                return ndctl_bus_start_scrub(bus);
        default:
@@ -100,7 +115,7 @@ static int bus_action(int argc, const char **argv, const 
char *usage,
 int cmd_start_scrub(int argc, const char **argv, struct ndctl_ctx *ctx)
 {
        char *usage = "ndctl start-scrub [<bus-id> <bus-id2> ... <bus-idN>] 
[<options>]";
-       int start = bus_action(argc, argv, usage, bus_options,
+       int start = bus_action(argc, argv, usage, start_options,
                        ACTION_START, ctx);
 
        if (start <= 0) {
@@ -115,7 +130,7 @@ int cmd_start_scrub(int argc, const char **argv, struct 
ndctl_ctx *ctx)
 int cmd_wait_scrub(int argc, const char **argv, struct ndctl_ctx *ctx)
 {
        char *usage = "ndctl wait-scrub [<bus-id> <bus-id2> ... <bus-idN>] 
[<options>]";
-       int wait = bus_action(argc, argv, usage, bus_options,
+       int wait = bus_action(argc, argv, usage, wait_options,
                        ACTION_WAIT, ctx);
 
        if (wait <= 0) {
diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c
index c9e2875d6011..fd36aa0662f4 100644
--- a/ndctl/lib/libndctl.c
+++ b/ndctl/lib/libndctl.c
@@ -1273,22 +1273,33 @@ NDCTL_EXPORT unsigned int 
ndctl_bus_get_scrub_count(struct ndctl_bus *bus)
 }
 
 /**
- * ndctl_bus_wait_for_scrub - wait for a scrub to complete
+ * ndctl_bus_poll_scrub_completion - wait for a scrub to complete
  * @bus: bus for which to check whether a scrub is in progress
+ * @poll_interval: nr seconds between wake up and re-read the status
+ * @timeout: total number of seconds to wait
  *
- * Upon return this bus has completed any in-progress scrubs. This is
- * different from ndctl_cmd_ars_in_progress in that the latter checks
- * the output of an ars_status command to see if the in-progress flag
- * is set, i.e. provides the firmware's view of whether a scrub is in
- * progress. ndctl_bus_wait_for_scrub instead checks the kernel's view
- * of whether a scrub is in progress by looking at the 'scrub' file in
- * sysfs.
+ * Upon return this bus has completed any in-progress scrubs if @timeout
+ * is 0 otherwise -ETIMEDOUT when @timeout seconds have expired. This
+ * is different from ndctl_cmd_ars_in_progress in that the latter checks
+ * the output of an ars_status command to see if the in-progress flag is
+ * set, i.e. provides the firmware's view of whether a scrub is in
+ * progress. ndctl_bus_wait_for_scrub_completion() instead checks the
+ * kernel's view of whether a scrub is in progress by looking at the
+ * 'scrub' file in sysfs.
+ *
+ * The @poll_interval option changes the frequency at which the kernel
+ * status is polled, but it requires a supporting kernel for that poll
+ * interval to be reflected to the kernel's polling of the ARS
+ * interface. Kernel's with poll interval support limit that polling to
+ * root (CAP_SYS_RAWIO) processes.
  */
-NDCTL_EXPORT int ndctl_bus_wait_for_scrub_completion(struct ndctl_bus *bus)
+NDCTL_EXPORT int ndctl_bus_poll_scrub_completion(struct ndctl_bus *bus,
+               unsigned int poll_interval, unsigned int timeout)
 {
        struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
+       const char *provider = ndctl_bus_get_provider(bus);
+       char buf[SYSFS_ATTR_SIZE] = { 0 };
        unsigned int scrub_count;
-       char buf[SYSFS_ATTR_SIZE];
        struct pollfd fds;
        char in_progress;
        int fd = 0, rc;
@@ -1314,32 +1325,71 @@ NDCTL_EXPORT int 
ndctl_bus_wait_for_scrub_completion(struct ndctl_bus *bus)
                        rc = 0;
                        break;
                } else if (rc == 2 && in_progress == '+') {
+                       long tmo;
+
+                       if (!timeout)
+                               tmo = poll_interval;
+                       else if (!poll_interval)
+                               tmo = timeout;
+                       else
+                               tmo = min(poll_interval, timeout);
+
+                       tmo *= 1000;
+                       if (tmo == 0)
+                               tmo = -1;
+
                        /* scrub in progress, wait */
-                       rc = poll(&fds, 1, -1);
-                       if (rc < 0) {
+                       rc = poll(&fds, 1, tmo);
+                       dbg(ctx, "%s: poll wake: rc: %d status: \'%s\'\n",
+                                       provider, rc, buf);
+                       if (rc > 0)
+                               fds.revents = 0;
+                       if (pread(fd, buf, 1, 0) == -1) {
                                rc = -errno;
-                               dbg(ctx, "poll error: %s\n", strerror(errno));
                                break;
                        }
-                       dbg(ctx, "poll wake: revents: %d\n", fds.revents);
-                       if (pread(fd, buf, 1, 0) == -1) {
+
+                       if (rc < 0) {
                                rc = -errno;
+                               dbg(ctx, "%s: poll error: %s\n", provider,
+                                               strerror(errno));
                                break;
+                       } else if (rc == 0) {
+                               dbg(ctx, "%s: poll timeout: interval: %d 
timeout: %d\n",
+                                               provider, poll_interval, 
timeout);
+                               if (!timeout)
+                                       continue;
+
+                               if (!poll_interval || poll_interval > timeout) {
+                                       rc = -ETIMEDOUT;
+                                       break;
+                               }
+
+                               if (timeout > poll_interval)
+                                       timeout -= poll_interval;
+                               else if (timeout == poll_interval) {
+                                       timeout = 1;
+                                       poll_interval = 0;
+                               }
                        }
-                       fds.revents = 0;
                }
        }
 
        if (rc == 0)
-               dbg(ctx, "bus%d: scrub complete\n", ndctl_bus_get_id(bus));
+               dbg(ctx, "%s: scrub complete, status: \'%s\'\n", provider, buf);
        else
-               dbg(ctx, "bus%d: error waiting for scrub completion: %s\n",
-                       ndctl_bus_get_id(bus), strerror(-rc));
+               dbg(ctx, "%s: error waiting for scrub completion: %s\n",
+                       provider, strerror(-rc));
        if (fd)
                close (fd);
        return rc;
 }
 
+NDCTL_EXPORT int ndctl_bus_wait_for_scrub_completion(struct ndctl_bus *bus)
+{
+       return ndctl_bus_poll_scrub_completion(bus, 0, 0);
+}
+
 static int ndctl_bind(struct ndctl_ctx *ctx, struct kmod_module *module,
                const char *devname);
 static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath);
diff --git a/ndctl/lib/libndctl.sym b/ndctl/lib/libndctl.sym
index cb9f769fbbca..297f03d7ae39 100644
--- a/ndctl/lib/libndctl.sym
+++ b/ndctl/lib/libndctl.sym
@@ -404,3 +404,9 @@ global:
        ndctl_dimm_update_master_passphrase;
        ndctl_dimm_master_secure_erase;
 } LIBNDCTL_18;
+
+
+LIBNDCTL_20 {
+global:
+       ndctl_bus_poll_scrub_completion;
+} LIBNDCTL_19;
diff --git a/ndctl/libndctl.h b/ndctl/libndctl.h
index 0debdb61b0ac..e378802ee4c1 100644
--- a/ndctl/libndctl.h
+++ b/ndctl/libndctl.h
@@ -133,6 +133,8 @@ enum ndctl_persistence_domain 
ndctl_bus_get_persistence_domain(
                struct ndctl_bus *bus);
 int ndctl_bus_wait_probe(struct ndctl_bus *bus);
 int ndctl_bus_wait_for_scrub_completion(struct ndctl_bus *bus);
+int ndctl_bus_poll_scrub_completion(struct ndctl_bus *bus,
+               unsigned int poll_interval, unsigned int timeout);
 unsigned int ndctl_bus_get_scrub_count(struct ndctl_bus *bus);
 int ndctl_bus_get_scrub_state(struct ndctl_bus *bus);
 int ndctl_bus_start_scrub(struct ndctl_bus *bus);

_______________________________________________
Linux-nvdimm mailing list
[email protected]
https://lists.01.org/mailman/listinfo/linux-nvdimm

Reply via email to