Merge tag 'libnvdimm-for-5.8-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git...
[sfrench/cifs-2.6.git] / arch / powerpc / platforms / pseries / papr_scm.c
index f35592423380115ceb0c67444fbb27f7c862f29a..9c569078a09fd3c8564e46c0af6d5f4a798c222b 100644 (file)
 #include <linux/libnvdimm.h>
 #include <linux/platform_device.h>
 #include <linux/delay.h>
+#include <linux/seq_buf.h>
 
 #include <asm/plpar_wrappers.h>
+#include <asm/papr_pdsm.h>
 
 #define BIND_ANY_ADDR (~0ul)
 
 #define PAPR_SCM_DIMM_CMD_MASK \
        ((1ul << ND_CMD_GET_CONFIG_SIZE) | \
         (1ul << ND_CMD_GET_CONFIG_DATA) | \
-        (1ul << ND_CMD_SET_CONFIG_DATA))
-
+        (1ul << ND_CMD_SET_CONFIG_DATA) | \
+        (1ul << ND_CMD_CALL))
+
+/* DIMM health bitmap bitmap indicators */
+/* SCM device is unable to persist memory contents */
+#define PAPR_PMEM_UNARMED                   (1ULL << (63 - 0))
+/* SCM device failed to persist memory contents */
+#define PAPR_PMEM_SHUTDOWN_DIRTY            (1ULL << (63 - 1))
+/* SCM device contents are persisted from previous IPL */
+#define PAPR_PMEM_SHUTDOWN_CLEAN            (1ULL << (63 - 2))
+/* SCM device contents are not persisted from previous IPL */
+#define PAPR_PMEM_EMPTY                     (1ULL << (63 - 3))
+/* SCM device memory life remaining is critically low */
+#define PAPR_PMEM_HEALTH_CRITICAL           (1ULL << (63 - 4))
+/* SCM device will be garded off next IPL due to failure */
+#define PAPR_PMEM_HEALTH_FATAL              (1ULL << (63 - 5))
+/* SCM contents cannot persist due to current platform health status */
+#define PAPR_PMEM_HEALTH_UNHEALTHY          (1ULL << (63 - 6))
+/* SCM device is unable to persist memory contents in certain conditions */
+#define PAPR_PMEM_HEALTH_NON_CRITICAL       (1ULL << (63 - 7))
+/* SCM device is encrypted */
+#define PAPR_PMEM_ENCRYPTED                 (1ULL << (63 - 8))
+/* SCM device has been scrubbed and locked */
+#define PAPR_PMEM_SCRUBBED_AND_LOCKED       (1ULL << (63 - 9))
+
+/* Bits status indicators for health bitmap indicating unarmed dimm */
+#define PAPR_PMEM_UNARMED_MASK (PAPR_PMEM_UNARMED |            \
+                               PAPR_PMEM_HEALTH_UNHEALTHY)
+
+/* Bits status indicators for health bitmap indicating unflushed dimm */
+#define PAPR_PMEM_BAD_SHUTDOWN_MASK (PAPR_PMEM_SHUTDOWN_DIRTY)
+
+/* Bits status indicators for health bitmap indicating unrestored dimm */
+#define PAPR_PMEM_BAD_RESTORE_MASK  (PAPR_PMEM_EMPTY)
+
+/* Bit status indicators for smart event notification */
+#define PAPR_PMEM_SMART_EVENT_MASK (PAPR_PMEM_HEALTH_CRITICAL | \
+                                   PAPR_PMEM_HEALTH_FATAL |    \
+                                   PAPR_PMEM_HEALTH_UNHEALTHY)
+
+/* private struct associated with each region */
 struct papr_scm_priv {
        struct platform_device *pdev;
        struct device_node *dn;
@@ -39,6 +80,15 @@ struct papr_scm_priv {
        struct resource res;
        struct nd_region *region;
        struct nd_interleave_set nd_set;
+
+       /* Protect dimm health data from concurrent read/writes */
+       struct mutex health_mutex;
+
+       /* Last time the health information of the dimm was updated */
+       unsigned long lasthealth_jiffies;
+
+       /* Health information for the dimm */
+       u64 health_bitmap;
 };
 
 static int drc_pmem_bind(struct papr_scm_priv *p)
@@ -144,6 +194,61 @@ err_out:
        return drc_pmem_bind(p);
 }
 
+/*
+ * Issue hcall to retrieve dimm health info and populate papr_scm_priv with the
+ * health information.
+ */
+static int __drc_pmem_query_health(struct papr_scm_priv *p)
+{
+       unsigned long ret[PLPAR_HCALL_BUFSIZE];
+       long rc;
+
+       /* issue the hcall */
+       rc = plpar_hcall(H_SCM_HEALTH, ret, p->drc_index);
+       if (rc != H_SUCCESS) {
+               dev_err(&p->pdev->dev,
+                       "Failed to query health information, Err:%ld\n", rc);
+               return -ENXIO;
+       }
+
+       p->lasthealth_jiffies = jiffies;
+       p->health_bitmap = ret[0] & ret[1];
+
+       dev_dbg(&p->pdev->dev,
+               "Queried dimm health info. Bitmap:0x%016lx Mask:0x%016lx\n",
+               ret[0], ret[1]);
+
+       return 0;
+}
+
+/* Min interval in seconds for assuming stable dimm health */
+#define MIN_HEALTH_QUERY_INTERVAL 60
+
+/* Query cached health info and if needed call drc_pmem_query_health */
+static int drc_pmem_query_health(struct papr_scm_priv *p)
+{
+       unsigned long cache_timeout;
+       int rc;
+
+       /* Protect concurrent modifications to papr_scm_priv */
+       rc = mutex_lock_interruptible(&p->health_mutex);
+       if (rc)
+               return rc;
+
+       /* Jiffies offset for which the health data is assumed to be same */
+       cache_timeout = p->lasthealth_jiffies +
+               msecs_to_jiffies(MIN_HEALTH_QUERY_INTERVAL * 1000);
+
+       /* Fetch new health info is its older than MIN_HEALTH_QUERY_INTERVAL */
+       if (time_after(jiffies, cache_timeout))
+               rc = __drc_pmem_query_health(p);
+       else
+               /* Assume cached health data is valid */
+               rc = 0;
+
+       mutex_unlock(&p->health_mutex);
+       return rc;
+}
 
 static int papr_scm_meta_get(struct papr_scm_priv *p,
                             struct nd_cmd_get_config_data_hdr *hdr)
@@ -246,16 +351,250 @@ static int papr_scm_meta_set(struct papr_scm_priv *p,
        return 0;
 }
 
+/*
+ * Do a sanity checks on the inputs args to dimm-control function and return
+ * '0' if valid. Validation of PDSM payloads happens later in
+ * papr_scm_service_pdsm.
+ */
+static int is_cmd_valid(struct nvdimm *nvdimm, unsigned int cmd, void *buf,
+                       unsigned int buf_len)
+{
+       unsigned long cmd_mask = PAPR_SCM_DIMM_CMD_MASK;
+       struct nd_cmd_pkg *nd_cmd;
+       struct papr_scm_priv *p;
+       enum papr_pdsm pdsm;
+
+       /* Only dimm-specific calls are supported atm */
+       if (!nvdimm)
+               return -EINVAL;
+
+       /* get the provider data from struct nvdimm */
+       p = nvdimm_provider_data(nvdimm);
+
+       if (!test_bit(cmd, &cmd_mask)) {
+               dev_dbg(&p->pdev->dev, "Unsupported cmd=%u\n", cmd);
+               return -EINVAL;
+       }
+
+       /* For CMD_CALL verify pdsm request */
+       if (cmd == ND_CMD_CALL) {
+               /* Verify the envelope and envelop size */
+               if (!buf ||
+                   buf_len < (sizeof(struct nd_cmd_pkg) + ND_PDSM_HDR_SIZE)) {
+                       dev_dbg(&p->pdev->dev, "Invalid pkg size=%u\n",
+                               buf_len);
+                       return -EINVAL;
+               }
+
+               /* Verify that the nd_cmd_pkg.nd_family is correct */
+               nd_cmd = (struct nd_cmd_pkg *)buf;
+
+               if (nd_cmd->nd_family != NVDIMM_FAMILY_PAPR) {
+                       dev_dbg(&p->pdev->dev, "Invalid pkg family=0x%llx\n",
+                               nd_cmd->nd_family);
+                       return -EINVAL;
+               }
+
+               pdsm = (enum papr_pdsm)nd_cmd->nd_command;
+
+               /* Verify if the pdsm command is valid */
+               if (pdsm <= PAPR_PDSM_MIN || pdsm >= PAPR_PDSM_MAX) {
+                       dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Invalid PDSM\n",
+                               pdsm);
+                       return -EINVAL;
+               }
+
+               /* Have enough space to hold returned 'nd_pkg_pdsm' header */
+               if (nd_cmd->nd_size_out < ND_PDSM_HDR_SIZE) {
+                       dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Invalid payload\n",
+                               pdsm);
+                       return -EINVAL;
+               }
+       }
+
+       /* Let the command be further processed */
+       return 0;
+}
+
+/* Fetch the DIMM health info and populate it in provided package. */
+static int papr_pdsm_health(struct papr_scm_priv *p,
+                           union nd_pdsm_payload *payload)
+{
+       int rc;
+
+       /* Ensure dimm health mutex is taken preventing concurrent access */
+       rc = mutex_lock_interruptible(&p->health_mutex);
+       if (rc)
+               goto out;
+
+       /* Always fetch upto date dimm health data ignoring cached values */
+       rc = __drc_pmem_query_health(p);
+       if (rc) {
+               mutex_unlock(&p->health_mutex);
+               goto out;
+       }
+
+       /* update health struct with various flags derived from health bitmap */
+       payload->health = (struct nd_papr_pdsm_health) {
+               .extension_flags = 0,
+               .dimm_unarmed = !!(p->health_bitmap & PAPR_PMEM_UNARMED_MASK),
+               .dimm_bad_shutdown = !!(p->health_bitmap & PAPR_PMEM_BAD_SHUTDOWN_MASK),
+               .dimm_bad_restore = !!(p->health_bitmap & PAPR_PMEM_BAD_RESTORE_MASK),
+               .dimm_scrubbed = !!(p->health_bitmap & PAPR_PMEM_SCRUBBED_AND_LOCKED),
+               .dimm_locked = !!(p->health_bitmap & PAPR_PMEM_SCRUBBED_AND_LOCKED),
+               .dimm_encrypted = !!(p->health_bitmap & PAPR_PMEM_ENCRYPTED),
+               .dimm_health = PAPR_PDSM_DIMM_HEALTHY,
+       };
+
+       /* Update field dimm_health based on health_bitmap flags */
+       if (p->health_bitmap & PAPR_PMEM_HEALTH_FATAL)
+               payload->health.dimm_health = PAPR_PDSM_DIMM_FATAL;
+       else if (p->health_bitmap & PAPR_PMEM_HEALTH_CRITICAL)
+               payload->health.dimm_health = PAPR_PDSM_DIMM_CRITICAL;
+       else if (p->health_bitmap & PAPR_PMEM_HEALTH_UNHEALTHY)
+               payload->health.dimm_health = PAPR_PDSM_DIMM_UNHEALTHY;
+
+       /* struct populated hence can release the mutex now */
+       mutex_unlock(&p->health_mutex);
+       rc = sizeof(struct nd_papr_pdsm_health);
+
+out:
+       return rc;
+}
+
+/*
+ * 'struct pdsm_cmd_desc'
+ * Identifies supported PDSMs' expected length of in/out payloads
+ * and pdsm service function.
+ *
+ * size_in     : Size of input payload if any in the PDSM request.
+ * size_out    : Size of output payload if any in the PDSM request.
+ * service     : Service function for the PDSM request. Return semantics:
+ *               rc < 0 : Error servicing PDSM and rc indicates the error.
+ *               rc >=0 : Serviced successfully and 'rc' indicate number of
+ *                     bytes written to payload.
+ */
+struct pdsm_cmd_desc {
+       u32 size_in;
+       u32 size_out;
+       int (*service)(struct papr_scm_priv *dimm,
+                      union nd_pdsm_payload *payload);
+};
+
+/* Holds all supported PDSMs' command descriptors */
+static const struct pdsm_cmd_desc __pdsm_cmd_descriptors[] = {
+       [PAPR_PDSM_MIN] = {
+               .size_in = 0,
+               .size_out = 0,
+               .service = NULL,
+       },
+       /* New PDSM command descriptors to be added below */
+
+       [PAPR_PDSM_HEALTH] = {
+               .size_in = 0,
+               .size_out = sizeof(struct nd_papr_pdsm_health),
+               .service = papr_pdsm_health,
+       },
+       /* Empty */
+       [PAPR_PDSM_MAX] = {
+               .size_in = 0,
+               .size_out = 0,
+               .service = NULL,
+       },
+};
+
+/* Given a valid pdsm cmd return its command descriptor else return NULL */
+static inline const struct pdsm_cmd_desc *pdsm_cmd_desc(enum papr_pdsm cmd)
+{
+       if (cmd >= 0 || cmd < ARRAY_SIZE(__pdsm_cmd_descriptors))
+               return &__pdsm_cmd_descriptors[cmd];
+
+       return NULL;
+}
+
+/*
+ * For a given pdsm request call an appropriate service function.
+ * Returns errors if any while handling the pdsm command package.
+ */
+static int papr_scm_service_pdsm(struct papr_scm_priv *p,
+                                struct nd_cmd_pkg *pkg)
+{
+       /* Get the PDSM header and PDSM command */
+       struct nd_pkg_pdsm *pdsm_pkg = (struct nd_pkg_pdsm *)pkg->nd_payload;
+       enum papr_pdsm pdsm = (enum papr_pdsm)pkg->nd_command;
+       const struct pdsm_cmd_desc *pdsc;
+       int rc;
+
+       /* Fetch corresponding pdsm descriptor for validation and servicing */
+       pdsc = pdsm_cmd_desc(pdsm);
+
+       /* Validate pdsm descriptor */
+       /* Ensure that reserved fields are 0 */
+       if (pdsm_pkg->reserved[0] || pdsm_pkg->reserved[1]) {
+               dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Invalid reserved field\n",
+                       pdsm);
+               return -EINVAL;
+       }
+
+       /* If pdsm expects some input, then ensure that the size_in matches */
+       if (pdsc->size_in &&
+           pkg->nd_size_in != (pdsc->size_in + ND_PDSM_HDR_SIZE)) {
+               dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Mismatched size_in=%d\n",
+                       pdsm, pkg->nd_size_in);
+               return -EINVAL;
+       }
+
+       /* If pdsm wants to return data, then ensure that  size_out matches */
+       if (pdsc->size_out &&
+           pkg->nd_size_out != (pdsc->size_out + ND_PDSM_HDR_SIZE)) {
+               dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Mismatched size_out=%d\n",
+                       pdsm, pkg->nd_size_out);
+               return -EINVAL;
+       }
+
+       /* Service the pdsm */
+       if (pdsc->service) {
+               dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Servicing..\n", pdsm);
+
+               rc = pdsc->service(p, &pdsm_pkg->payload);
+
+               if (rc < 0) {
+                       /* error encountered while servicing pdsm */
+                       pdsm_pkg->cmd_status = rc;
+                       pkg->nd_fw_size = ND_PDSM_HDR_SIZE;
+               } else {
+                       /* pdsm serviced and 'rc' bytes written to payload */
+                       pdsm_pkg->cmd_status = 0;
+                       pkg->nd_fw_size = ND_PDSM_HDR_SIZE + rc;
+               }
+       } else {
+               dev_dbg(&p->pdev->dev, "PDSM[0x%x]: Unsupported PDSM request\n",
+                       pdsm);
+               pdsm_pkg->cmd_status = -ENOENT;
+               pkg->nd_fw_size = ND_PDSM_HDR_SIZE;
+       }
+
+       return pdsm_pkg->cmd_status;
+}
+
 static int papr_scm_ndctl(struct nvdimm_bus_descriptor *nd_desc,
                          struct nvdimm *nvdimm, unsigned int cmd, void *buf,
                          unsigned int buf_len, int *cmd_rc)
 {
        struct nd_cmd_get_config_size *get_size_hdr;
+       struct nd_cmd_pkg *call_pkg = NULL;
        struct papr_scm_priv *p;
+       int rc;
 
-       /* Only dimm-specific calls are supported atm */
-       if (!nvdimm)
-               return -EINVAL;
+       rc = is_cmd_valid(nvdimm, cmd, buf, buf_len);
+       if (rc) {
+               pr_debug("Invalid cmd=0x%x. Err=%d\n", cmd, rc);
+               return rc;
+       }
+
+       /* Use a local variable in case cmd_rc pointer is NULL */
+       if (!cmd_rc)
+               cmd_rc = &rc;
 
        p = nvdimm_provider_data(nvdimm);
 
@@ -277,7 +616,13 @@ static int papr_scm_ndctl(struct nvdimm_bus_descriptor *nd_desc,
                *cmd_rc = papr_scm_meta_set(p, buf);
                break;
 
+       case ND_CMD_CALL:
+               call_pkg = (struct nd_cmd_pkg *)buf;
+               *cmd_rc = papr_scm_service_pdsm(p, call_pkg);
+               break;
+
        default:
+               dev_dbg(&p->pdev->dev, "Unknown command = %d\n", cmd);
                return -EINVAL;
        }
 
@@ -286,6 +631,64 @@ static int papr_scm_ndctl(struct nvdimm_bus_descriptor *nd_desc,
        return 0;
 }
 
+static ssize_t flags_show(struct device *dev,
+                         struct device_attribute *attr, char *buf)
+{
+       struct nvdimm *dimm = to_nvdimm(dev);
+       struct papr_scm_priv *p = nvdimm_provider_data(dimm);
+       struct seq_buf s;
+       u64 health;
+       int rc;
+
+       rc = drc_pmem_query_health(p);
+       if (rc)
+               return rc;
+
+       /* Copy health_bitmap locally, check masks & update out buffer */
+       health = READ_ONCE(p->health_bitmap);
+
+       seq_buf_init(&s, buf, PAGE_SIZE);
+       if (health & PAPR_PMEM_UNARMED_MASK)
+               seq_buf_printf(&s, "not_armed ");
+
+       if (health & PAPR_PMEM_BAD_SHUTDOWN_MASK)
+               seq_buf_printf(&s, "flush_fail ");
+
+       if (health & PAPR_PMEM_BAD_RESTORE_MASK)
+               seq_buf_printf(&s, "restore_fail ");
+
+       if (health & PAPR_PMEM_ENCRYPTED)
+               seq_buf_printf(&s, "encrypted ");
+
+       if (health & PAPR_PMEM_SMART_EVENT_MASK)
+               seq_buf_printf(&s, "smart_notify ");
+
+       if (health & PAPR_PMEM_SCRUBBED_AND_LOCKED)
+               seq_buf_printf(&s, "scrubbed locked ");
+
+       if (seq_buf_used(&s))
+               seq_buf_printf(&s, "\n");
+
+       return seq_buf_used(&s);
+}
+DEVICE_ATTR_RO(flags);
+
+/* papr_scm specific dimm attributes */
+static struct attribute *papr_nd_attributes[] = {
+       &dev_attr_flags.attr,
+       NULL,
+};
+
+static struct attribute_group papr_nd_attribute_group = {
+       .name = "papr",
+       .attrs = papr_nd_attributes,
+};
+
+static const struct attribute_group *papr_nd_attr_groups[] = {
+       &papr_nd_attribute_group,
+       NULL,
+};
+
 static int papr_scm_nvdimm_init(struct papr_scm_priv *p)
 {
        struct device *dev = &p->pdev->dev;
@@ -312,8 +715,8 @@ static int papr_scm_nvdimm_init(struct papr_scm_priv *p)
        dimm_flags = 0;
        set_bit(NDD_LABELING, &dimm_flags);
 
-       p->nvdimm = nvdimm_create(p->bus, p, NULL, dimm_flags,
-                                 PAPR_SCM_DIMM_CMD_MASK, 0, NULL);
+       p->nvdimm = nvdimm_create(p->bus, p, papr_nd_attr_groups,
+                                 dimm_flags, PAPR_SCM_DIMM_CMD_MASK, 0, NULL);
        if (!p->nvdimm) {
                dev_err(dev, "Error creating DIMM object for %pOF\n", p->dn);
                goto err;
@@ -399,6 +802,9 @@ static int papr_scm_probe(struct platform_device *pdev)
        if (!p)
                return -ENOMEM;
 
+       /* Initialize the dimm mutex */
+       mutex_init(&p->health_mutex);
+
        /* optional DT properties */
        of_property_read_u32(dn, "ibm,metadata-size", &metadata_size);