When firmware is staged check if the platform / kernel also supports live
firmware activation and include that state at both the dimm and bus level.
The "last activate result" is included only as a hint to whether a
powercycle is required to activate the current image. If firmware
activation fails the "need_powercycle" flag will appear, if firmware
activation is successful then the "current_version" field will be
up-to-date.

Signed-off-by: Dan Williams <[email protected]>
---
 Documentation/ndctl/ndctl-list.txt |   39 +++++++--
 ndctl/bus.c                        |    2 
 ndctl/lib/libndctl.c               |  148 ++++++++++++++++++++++++++++++++++++
 ndctl/lib/libndctl.sym             |    7 ++
 ndctl/lib/private.h                |    3 +
 ndctl/libndctl.h                   |   28 +++++++
 ndctl/list.c                       |    3 -
 util/json.c                        |  117 ++++++++++++++++++++++++++--
 util/json.h                        |    3 -
 9 files changed, 325 insertions(+), 25 deletions(-)

diff --git a/Documentation/ndctl/ndctl-list.txt 
b/Documentation/ndctl/ndctl-list.txt
index 7c7e3ac9d05c..b8d517d784b4 100644
--- a/Documentation/ndctl/ndctl-list.txt
+++ b/Documentation/ndctl/ndctl-list.txt
@@ -132,16 +132,35 @@ include::xable-bus-options.txt[]
 
 -F::
 --firmware::
-       Include dimm firmware info in the listing. For example:
-[verse]
-{
-  "dev":"nmem0",
-  "firmware":{
-      "current_version":0,
-      "next_version":1,
-      "need_powercycle":true
-  }
-}
+       Include firmware info in the listing, including the state and
+       capability of runtime firmware activation:
+
+----
+# ndctl list -BDF
+[
+  {
+    "provider":"nfit_test.0",
+    "dev":"ndbus2",
+    "scrub_state":"idle",
+    "firmware":{
+      "activate_method":"suspend",
+      "activate_state":"idle"
+    },
+    "dimms":[
+      {
+        "dev":"nmem1",
+        "id":"cdab-0a-07e0-ffffffff",
+        "handle":0,
+        "phys_id":0,
+        "security":"disabled",
+        "firmware":{
+          "current_version":0,
+          "can_update":true
+        }
+      },
+...
+]
+----
 
 -X::
 --device-dax::
diff --git a/ndctl/bus.c b/ndctl/bus.c
index 86bbd5178df9..6d5bafb86fe4 100644
--- a/ndctl/bus.c
+++ b/ndctl/bus.c
@@ -92,7 +92,7 @@ static int bus_action(int argc, const char **argv, const char 
*usage,
                        rc = scrub_action(bus, action);
                        if (rc == 0) {
                                success++;
-                               jbus = util_bus_to_json(bus);
+                               jbus = util_bus_to_json(bus, 0);
                                if (jbus)
                                        json_object_array_add(jbuses, jbus);
                        } else if (!fail)
diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c
index ee737cbbfe3e..f03635e99a83 100644
--- a/ndctl/lib/libndctl.c
+++ b/ndctl/lib/libndctl.c
@@ -819,6 +819,19 @@ static void parse_dimm_flags(struct ndctl_dimm *dimm, char 
*flags)
                                ndctl_dimm_get_devname(dimm), flags);
 }
 
+static enum ndctl_fwa_state fwa_to_state(const char *fwa)
+{
+       if (strcmp(fwa, "idle") == 0)
+               return NDCTL_FWA_IDLE;
+       if (strcmp(fwa, "busy") == 0)
+               return NDCTL_FWA_BUSY;
+       if (strcmp(fwa, "armed") == 0)
+               return NDCTL_FWA_ARMED;
+       if (strcmp(fwa, "overflow") == 0)
+               return NDCTL_FWA_ARM_OVERFLOW;
+       return NDCTL_FWA_INVALID;
+}
+
 static void *add_bus(void *parent, int id, const char *ctl_base)
 {
        char buf[SYSFS_ATTR_SIZE];
@@ -880,6 +893,12 @@ static void *add_bus(void *parent, int id, const char 
*ctl_base)
        if (!bus->scrub_path)
                goto err_read;
 
+       sprintf(path, "%s/device/firmware_activate", ctl_base);
+       if (sysfs_read_attr(ctx, path, buf) < 0)
+               bus->fwa_state = NDCTL_FWA_INVALID;
+       else
+               bus->fwa_state = fwa_to_state(buf);
+
        bus->bus_path = parent_dev_path("char", bus->major, bus->minor);
        if (!bus->bus_path)
                goto err_dev_path;
@@ -1436,6 +1455,74 @@ NDCTL_EXPORT int 
ndctl_bus_wait_for_scrub_completion(struct ndctl_bus *bus)
        return ndctl_bus_poll_scrub_completion(bus, 0, 0);
 }
 
+NDCTL_EXPORT enum ndctl_fwa_state ndctl_bus_get_fw_activate_state(
+               struct ndctl_bus *bus)
+{
+       struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
+       char *path = bus->bus_buf;
+       char buf[SYSFS_ATTR_SIZE];
+       int len = bus->buf_len;
+
+       if (bus->fwa_state == NDCTL_FWA_INVALID)
+               return NDCTL_FWA_INVALID;
+
+       if (snprintf(path, len, "%s/firmware_activate", bus->bus_path) >= len) {
+               err(ctx, "%s: buffer too small!\n",
+                               ndctl_bus_get_devname(bus));
+               return NDCTL_FWA_INVALID;
+       }
+
+       if (sysfs_read_attr(ctx, path, buf) < 0)
+               return NDCTL_FWA_INVALID;
+
+       bus->fwa_state = fwa_to_state(buf);
+
+       return bus->fwa_state;
+}
+
+NDCTL_EXPORT enum ndctl_fwa_method ndctl_bus_get_fw_activate_method(
+               struct ndctl_bus *bus)
+{
+       struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
+       char *path = bus->bus_buf;
+       int len = bus->buf_len;
+       struct stat st;
+       int rc;
+
+       if (bus->fwa_state == NDCTL_FWA_INVALID)
+               return NDCTL_FWA_METHOD_RESET;
+
+       if (snprintf(path, len, "%s/firmware_activate", bus->bus_path) >= len) {
+               err(ctx, "%s: buffer too small!\n",
+                               ndctl_bus_get_devname(bus));
+               return NDCTL_FWA_INVALID;
+       }
+
+       rc = stat(path, &st);
+       if (rc < 0)
+               return NDCTL_FWA_METHOD_RESET;
+       if ((st.st_mode & 0600) == 0600)
+               return NDCTL_FWA_METHOD_LIVE;
+       if ((st.st_mode & 0400) == 0400)
+               return NDCTL_FWA_METHOD_SUSPEND;
+       return NDCTL_FWA_METHOD_RESET;
+}
+
+static enum ndctl_fwa_result fwa_result_to_result(const char *result)
+{
+       if (strcmp(result, "none") == 0)
+               return NDCTL_FWA_RESULT_NONE;
+       if (strcmp(result, "success") == 0)
+               return NDCTL_FWA_RESULT_SUCCESS;
+       if (strcmp(result, "fail") == 0)
+               return NDCTL_FWA_RESULT_FAIL;
+       if (strcmp(result, "not_staged") == 0)
+               return NDCTL_FWA_RESULT_NOTSTAGED;
+       if (strcmp(result, "need_reset") == 0)
+               return NDCTL_FWA_RESULT_NEEDRESET;
+       return NDCTL_FWA_RESULT_INVALID;
+}
+
 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);
@@ -1515,6 +1602,18 @@ static void *add_dimm(void *parent, int id, const char 
*dimm_base)
        } else
                parse_dimm_flags(dimm, buf);
 
+       sprintf(path, "%s/firmware_activate", dimm_base);
+       if (sysfs_read_attr(ctx, path, buf) < 0)
+               dimm->fwa_state = NDCTL_FWA_INVALID;
+       else
+               dimm->fwa_state = fwa_to_state(buf);
+
+       sprintf(path, "%s/firmware_activate_result", dimm_base);
+       if (sysfs_read_attr(ctx, path, buf) < 0)
+               dimm->fwa_result = NDCTL_FWA_RESULT_INVALID;
+       else
+               dimm->fwa_result = fwa_result_to_result(buf);
+
        if (!ndctl_bus_has_nfit(bus))
                goto out;
 
@@ -1998,6 +2097,55 @@ NDCTL_EXPORT int ndctl_dimm_enable(struct ndctl_dimm 
*dimm)
        return 0;
 }
 
+NDCTL_EXPORT enum ndctl_fwa_state ndctl_dimm_get_fw_activate_state(
+               struct ndctl_dimm *dimm)
+{
+       struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm);
+       char *path = dimm->dimm_buf;
+       char buf[SYSFS_ATTR_SIZE];
+       int len = dimm->buf_len;
+
+       if (dimm->fwa_state == NDCTL_FWA_INVALID)
+               return NDCTL_FWA_INVALID;
+
+       if (snprintf(path, len, "%s/firmware_activate",
+                               dimm->dimm_path) >= len) {
+               err(ctx, "%s: buffer too small!\n",
+                               ndctl_dimm_get_devname(dimm));
+               return NDCTL_FWA_INVALID;
+       }
+
+       if (sysfs_read_attr(ctx, path, buf) < 0)
+               return NDCTL_FWA_INVALID;
+
+       dimm->fwa_state = fwa_to_state(buf);
+       return dimm->fwa_state;
+}
+
+NDCTL_EXPORT enum ndctl_fwa_result ndctl_dimm_get_fw_activate_result(
+               struct ndctl_dimm *dimm)
+{
+       struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm);
+       char *path = dimm->dimm_buf;
+       char buf[SYSFS_ATTR_SIZE];
+       int len = dimm->buf_len;
+
+       if (dimm->fwa_result == NDCTL_FWA_RESULT_INVALID)
+               return NDCTL_FWA_RESULT_INVALID;
+
+       if (snprintf(path, len, "%s/firmware_activate_result",
+                               dimm->dimm_path) >= len) {
+               err(ctx, "%s: buffer too small!\n",
+                               ndctl_dimm_get_devname(dimm));
+               return NDCTL_FWA_RESULT_INVALID;
+       }
+
+       if (sysfs_read_attr(ctx, path, buf) < 0)
+               return NDCTL_FWA_RESULT_INVALID;
+
+       return fwa_result_to_result(buf);
+}
+
 NDCTL_EXPORT struct ndctl_dimm *ndctl_dimm_get_by_handle(struct ndctl_bus *bus,
                unsigned int handle)
 {
diff --git a/ndctl/lib/libndctl.sym b/ndctl/lib/libndctl.sym
index ac575a23d035..37217036b0d8 100644
--- a/ndctl/lib/libndctl.sym
+++ b/ndctl/lib/libndctl.sym
@@ -431,3 +431,10 @@ LIBNDCTL_23 {
        ndctl_region_get_align;
        ndctl_region_set_align;
 } LIBNDCTL_22;
+
+LIBNDCTL_24 {
+       ndctl_dimm_get_fw_activate_state;
+       ndctl_dimm_get_fw_activate_result;
+       ndctl_bus_get_fw_activate_state;
+       ndctl_bus_get_fw_activate_method;
+} LIBNDCTL_23;
diff --git a/ndctl/lib/private.h b/ndctl/lib/private.h
index 2e537f0a8649..e1d61f52fed5 100644
--- a/ndctl/lib/private.h
+++ b/ndctl/lib/private.h
@@ -79,6 +79,8 @@ struct ndctl_dimm {
        unsigned long cmd_mask;
        unsigned long nfit_dsm_mask;
        long long dirty_shutdown;
+       enum ndctl_fwa_state fwa_state;
+       enum ndctl_fwa_result fwa_result;
        char *unique_id;
        char *dimm_path;
        char *dimm_buf;
@@ -174,6 +176,7 @@ struct ndctl_bus {
        char *scrub_path;
        unsigned long cmd_mask;
        unsigned long nfit_dsm_mask;
+       enum ndctl_fwa_state fwa_state;
 };
 
 /**
diff --git a/ndctl/libndctl.h b/ndctl/libndctl.h
index 2580f433ade8..e66a52029481 100644
--- a/ndctl/libndctl.h
+++ b/ndctl/libndctl.h
@@ -110,6 +110,20 @@ enum ndctl_persistence_domain {
        PERSISTENCE_UNKNOWN = INT_MAX,
 };
 
+enum ndctl_fwa_state {
+       NDCTL_FWA_INVALID,
+       NDCTL_FWA_IDLE,
+       NDCTL_FWA_ARMED,
+       NDCTL_FWA_BUSY,
+       NDCTL_FWA_ARM_OVERFLOW,
+};
+
+enum ndctl_fwa_method {
+       NDCTL_FWA_METHOD_RESET,
+       NDCTL_FWA_METHOD_SUSPEND,
+       NDCTL_FWA_METHOD_LIVE,
+};
+
 struct ndctl_bus;
 struct ndctl_bus *ndctl_bus_get_first(struct ndctl_ctx *ctx);
 struct ndctl_bus *ndctl_bus_get_next(struct ndctl_bus *bus);
@@ -139,6 +153,8 @@ 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);
 int ndctl_bus_has_error_injection(struct ndctl_bus *bus);
+enum ndctl_fwa_state ndctl_bus_get_fw_activate_state(struct ndctl_bus *bus);
+enum ndctl_fwa_method ndctl_bus_get_fw_activate_method(struct ndctl_bus *bus);
 
 struct ndctl_dimm;
 struct ndctl_dimm *ndctl_dimm_get_first(struct ndctl_bus *bus);
@@ -702,6 +718,18 @@ enum ND_FW_STATUS ndctl_cmd_fw_xlat_firmware_status(struct 
ndctl_cmd *cmd);
 struct ndctl_cmd *ndctl_dimm_cmd_new_ack_shutdown_count(struct ndctl_dimm 
*dimm);
 int ndctl_dimm_fw_update_supported(struct ndctl_dimm *dimm);
 
+enum ndctl_fwa_result {
+        NDCTL_FWA_RESULT_INVALID,
+        NDCTL_FWA_RESULT_NONE,
+        NDCTL_FWA_RESULT_SUCCESS,
+        NDCTL_FWA_RESULT_NOTSTAGED,
+        NDCTL_FWA_RESULT_NEEDRESET,
+        NDCTL_FWA_RESULT_FAIL,
+};
+
+enum ndctl_fwa_state ndctl_dimm_get_fw_activate_state(struct ndctl_dimm *dimm);
+enum ndctl_fwa_result ndctl_dimm_get_fw_activate_result(struct ndctl_dimm 
*dimm);
+
 int ndctl_cmd_xlat_firmware_status(struct ndctl_cmd *cmd);
 int ndctl_cmd_submit_xlat(struct ndctl_cmd *cmd);
 
diff --git a/ndctl/list.c b/ndctl/list.c
index 1f7cc8ee1deb..f98148aea479 100644
--- a/ndctl/list.c
+++ b/ndctl/list.c
@@ -403,11 +403,12 @@ static bool filter_bus(struct ndctl_bus *bus, struct 
util_filter_ctx *ctx)
                }
        }
 
-       lfa->jbus = util_bus_to_json(bus);
+       lfa->jbus = util_bus_to_json(bus, lfa->flags);
        if (!lfa->jbus) {
                fail("\n");
                return false;
        }
+
        json_object_array_add(lfa->jbuses, lfa->jbus);
        return true;
 }
diff --git a/util/json.c b/util/json.c
index 59a3d07cc4a6..77bd4781551d 100644
--- a/util/json.c
+++ b/util/json.c
@@ -122,10 +122,10 @@ void util_display_json_array(FILE *f_out, struct 
json_object *jarray,
        json_object_put(jarray);
 }
 
-struct json_object *util_bus_to_json(struct ndctl_bus *bus)
+struct json_object *util_bus_to_json(struct ndctl_bus *bus, unsigned long 
flags)
 {
        struct json_object *jbus = json_object_new_object();
-       struct json_object *jobj;
+       struct json_object *jobj, *fw_obj = NULL;
        int scrub;
 
        if (!jbus)
@@ -150,6 +150,49 @@ struct json_object *util_bus_to_json(struct ndctl_bus *bus)
                goto err;
        json_object_object_add(jbus, "scrub_state", jobj);
 
+       if (flags & UTIL_JSON_FIRMWARE) {
+               struct ndctl_dimm *dimm;
+
+               /*
+                * Skip displaying firmware activation capability if no
+                * DIMMs support firmware update.
+                */
+               ndctl_dimm_foreach(bus, dimm)
+                       if (ndctl_dimm_fw_update_supported(dimm) == 0) {
+                               fw_obj = json_object_new_object();
+                               break;
+                       }
+       }
+
+       if (fw_obj) {
+               enum ndctl_fwa_state state;
+               enum ndctl_fwa_method method;
+
+               jobj = NULL;
+               method = ndctl_bus_get_fw_activate_method(bus);
+               if (method == NDCTL_FWA_METHOD_RESET)
+                       jobj = json_object_new_string("reset");
+               if (method == NDCTL_FWA_METHOD_SUSPEND)
+                       jobj = json_object_new_string("suspend");
+               if (method == NDCTL_FWA_METHOD_LIVE)
+                       jobj = json_object_new_string("live");
+               if (jobj)
+                       json_object_object_add(fw_obj, "activate_method", jobj);
+
+               jobj = NULL;
+               state = ndctl_bus_get_fw_activate_state(bus);
+               if (state == NDCTL_FWA_ARMED)
+                       jobj = json_object_new_string("armed");
+               if (state == NDCTL_FWA_IDLE)
+                       jobj = json_object_new_string("idle");
+               if (state == NDCTL_FWA_ARM_OVERFLOW)
+                       jobj = json_object_new_string("overflow");
+               if (jobj)
+                       json_object_object_add(fw_obj, "activate_state", jobj);
+
+               json_object_object_add(jbus, "firmware", fw_obj);
+       }
+
        return jbus;
  err:
        json_object_put(jbus);
@@ -160,10 +203,13 @@ struct json_object *util_dimm_firmware_to_json(struct 
ndctl_dimm *dimm,
                unsigned long flags)
 {
        struct json_object *jfirmware = json_object_new_object();
+       bool can_update, need_powercycle;
+       enum ndctl_fwa_result result;
+       enum ndctl_fwa_state state;
        struct json_object *jobj;
        struct ndctl_cmd *cmd;
-       int rc;
        uint64_t run, next;
+       int rc;
 
        if (!jfirmware)
                return NULL;
@@ -195,10 +241,12 @@ struct json_object *util_dimm_firmware_to_json(struct 
ndctl_dimm *dimm,
                json_object_object_add(jfirmware, "current_version", jobj);
 
        rc = ndctl_dimm_fw_update_supported(dimm);
-       jobj = json_object_new_boolean(rc == 0);
+       can_update = rc == 0;
+       jobj = json_object_new_boolean(can_update);
        if (jobj)
                json_object_object_add(jfirmware, "can_update", jobj);
 
+
        next = ndctl_cmd_fw_info_get_updated_version(cmd);
        if (next == ULLONG_MAX) {
                jobj = util_json_object_hex(-1, flags);
@@ -208,16 +256,61 @@ struct json_object *util_dimm_firmware_to_json(struct 
ndctl_dimm *dimm,
                goto out;
        }
 
-       if (next != 0) {
-               jobj = util_json_object_hex(next, flags);
-               if (jobj)
-                       json_object_object_add(jfirmware,
-                                       "next_version", jobj);
+       if (!next)
+               goto out;
+
+       jobj = util_json_object_hex(next, flags);
+       if (jobj)
+               json_object_object_add(jfirmware,
+                               "next_version", jobj);
+
+       state = ndctl_dimm_get_fw_activate_state(dimm);
+       switch (state) {
+       case NDCTL_FWA_IDLE:
+               jobj = json_object_new_string("idle");
+               break;
+       case NDCTL_FWA_ARMED:
+               jobj = json_object_new_string("armed");
+               break;
+       case NDCTL_FWA_BUSY:
+               jobj = json_object_new_string("busy");
+               break;
+       default:
+               jobj = NULL;
+               break;
+       }
+       if (jobj)
+               json_object_object_add(jfirmware, "activate_state", jobj);
+
+       result = ndctl_dimm_get_fw_activate_result(dimm);
+       switch (result) {
+       case NDCTL_FWA_RESULT_NONE:
+       case NDCTL_FWA_RESULT_SUCCESS:
+       case NDCTL_FWA_RESULT_NOTSTAGED:
+               /*
+                * If a 'next' firmware version is staged then this
+                * result is stale, if the activation succeeds that is
+                * indicated by not finding a 'next' entry.
+                */
+               need_powercycle = false;
+               break;
+       case NDCTL_FWA_RESULT_NEEDRESET:
+       case NDCTL_FWA_RESULT_FAIL:
+       default:
+               /*
+                * If the last activation failed, or if the activation
+                * result is unavailable it is always the case that the
+                * only remediation is powercycle.
+                */
+               need_powercycle = true;
+               break;
+       }
 
+       if (need_powercycle) {
                jobj = json_object_new_boolean(true);
-               if (jobj)
-                       json_object_object_add(jfirmware,
-                                       "need_powercycle", jobj);
+               if (!jobj)
+                       goto out;
+               json_object_object_add(jfirmware, "need_powercycle", jobj);
        }
 
        ndctl_cmd_unref(cmd);
diff --git a/util/json.h b/util/json.h
index fc91a8db034f..39a33789bac9 100644
--- a/util/json.h
+++ b/util/json.h
@@ -32,7 +32,8 @@ enum util_json_flags {
 struct json_object;
 void util_display_json_array(FILE *f_out, struct json_object *jarray,
                unsigned long flags);
-struct json_object *util_bus_to_json(struct ndctl_bus *bus);
+struct json_object *util_bus_to_json(struct ndctl_bus *bus,
+               unsigned long flags);
 struct json_object *util_dimm_to_json(struct ndctl_dimm *dimm,
                unsigned long flags);
 struct json_object *util_mapping_to_json(struct ndctl_mapping *mapping,
_______________________________________________
Linux-nvdimm mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to