[Skiboot] [PATCH 4/4] hdata/memory: Add NVDIMM support

Oliver O'Halloran oohall at gmail.com
Tue Feb 19 18:53:39 AEDT 2019


NVDIMMs are memory modules that use a battery backup system to allow the
contents RAM to be saved to non-volatile storage if system power goes
away unexpectedly. This allows them to be used a high-performance
storage device, suitable for serving as a cache for SSDs and the like.

Configuration of NVDIMMs is handled by hostboot and communicated to OPAL
via the HDAT. We need to parse out the NVDIMM memory ranges and create
memory regions with the "pmem-region" compatible label to make them
available to the host.

Signed-off-by: Oliver O'Halloran <oohall at gmail.com>
---
 hdata/memory.c | 110 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 100 insertions(+), 10 deletions(-)

diff --git a/hdata/memory.c b/hdata/memory.c
index 3e6271444ce6..1ac03c137dbd 100644
--- a/hdata/memory.c
+++ b/hdata/memory.c
@@ -48,7 +48,25 @@ struct HDIF_ms_area_address_range {
 	__be32 mirror_attr;
 	__be64 mirror_start;
 	__be32 controller_id;
+	__be32 phys_attr;
 } __packed;
+#define PHYS_ATTR_TYPE_MASK 	0xff000000
+#define   PHYS_ATTR_TYPE_STD		0
+#define   PHYS_ATTR_TYPE_NVDIMM		1
+#define   PHYS_ATTR_TYPE_MRAM		2
+#define   PHYS_ATTR_TYPE_PCM		3
+
+#define PHYS_ATTR_STATUS_MASK 	0x00ff0000
+/*
+ * The values here are mutually exclusive. I have no idea why anyone
+ * decided encoding these are flags rather than sequential numbers was
+ * a good idea, but here we are.
+ */
+#define   PHYS_ATTR_STATUS_CANT_SAVE 	0x01
+#define   PHYS_ATTR_STATUS_SAVE_FAILED	0x02
+#define   PHYS_ATTR_STATUS_SAVED	0x04
+#define   PHYS_ATTR_STATUS_NOT_SAVED	0x08
+#define   PHYS_ATTR_STATUS_MEM_INVALID	0xff
 
 #define MS_CONTROLLER_MCBIST_ID(id)	GETFIELD(PPC_BITMASK32(0, 1), id)
 #define MS_CONTROLLER_MCS_ID(id)	GETFIELD(PPC_BITMASK32(4, 7), id)
@@ -92,42 +110,99 @@ static void append_chip_id(struct dt_node *mem, u32 id)
 	p[len] = cpu_to_be32(id);
 }
 
+static void update_status(struct dt_node *mem, uint32_t status)
+{
+	switch (status) {
+	case PHYS_ATTR_STATUS_CANT_SAVE:
+		if (!dt_find_property(mem, "save-trigged-unarmed"))
+			dt_add_property(mem, "save-trigger-unarmed", NULL, 0);
+		break;
+
+	case PHYS_ATTR_STATUS_SAVE_FAILED:
+		if (!dt_find_property(mem, "save-failed"))
+			dt_add_property(mem, "save-failed", NULL, 0);
+
+		break;
+
+	case PHYS_ATTR_STATUS_MEM_INVALID:
+		if (dt_find_property(mem, "save-trigged-unarmed"))
+			dt_add_property_string(mem, "status",
+				"disabled-memory-invalid");
+		break;
+	}
+}
+
 static bool add_address_range(struct dt_node *root,
 			      const struct HDIF_ms_area_id *id,
-			      const struct HDIF_ms_area_address_range *arange)
+			      const struct HDIF_ms_area_address_range *arange,
+			      uint32_t mem_type, uint32_t mem_status)
 {
+	const char *compat = NULL, *dev_type = NULL, *name = NULL;
 	struct dt_node *mem;
-	u32 chip_id, type;
+	u32 chip_id;
 	u64 reg[2];
 
 	chip_id = pcid_to_chip_id(be32_to_cpu(arange->chip));
 
 	prlog(PR_DEBUG, "  Range: 0x%016llx..0x%016llx "
-	      "on Chip 0x%x mattr: 0x%x\n",
+	      "on Chip 0x%x mattr: 0x%x pattr: 0x%x status:0x%x\n",
 	      (long long)be64_to_cpu(arange->start),
 	      (long long)be64_to_cpu(arange->end),
-	      chip_id, arange->mirror_attr);
+	      chip_id, arange->mirror_attr, mem_type, mem_status);
 
 	/* reg contains start and length */
 	reg[0] = cleanup_addr(be64_to_cpu(arange->start));
 	reg[1] = cleanup_addr(be64_to_cpu(arange->end)) - reg[0];
 
+	switch (mem_type) {
+	case PHYS_ATTR_TYPE_STD:
+		name = "memory";
+		dev_type = "memory";
+		break;
+
+	case PHYS_ATTR_TYPE_NVDIMM:
+	case PHYS_ATTR_TYPE_MRAM:
+	case PHYS_ATTR_TYPE_PCM:
+		/* fall through */
+		name = "nvdimm";
+		compat = "pmem-region";
+		break;
+
+	/*
+	 * Future memory types could be volatile or non-volatile. Bail if don't
+	 * recognise the type so we don't end up trashing data accidently.
+	 */
+	default:
+		return false;
+	}
+
 	if (be16_to_cpu(id->flags) & MS_AREA_SHARED) {
-		mem = dt_find_by_name_addr("memory", reg[0]);
+		mem = dt_find_by_name_addr(dt_root, name, reg[0]);
 		if (mem) {
 			append_chip_id(mem, chip_id);
+			if (mem_type == PHYS_ATTR_TYPE_NVDIMM)
+				update_status(mem, mem_status);
 			return true;
 		}
 	}
 
 	mem = dt_new_addr(root, name, reg[0]);
-	dt_add_property_string(mem, "device_type", "memory");
-	dt_add_property_cells(mem, "ibm,chip-id", chip_id);
+	if (compat)
+		dt_add_property_string(mem, "compatible", compat);
+	if (dev_type)
+		dt_add_property_string(mem, "device_type", dev_type);
+
+	/* add in the nvdimm backup status flags */
+	if (mem_type == PHYS_ATTR_TYPE_NVDIMM)
+		update_status(mem, mem_status);
+
+	/* common properties */
+
 	dt_add_property_u64s(mem, "reg", reg[0], reg[1]);
+	dt_add_property_cells(mem, "ibm,chip-id", chip_id);
 	if (be16_to_cpu(id->flags) & MS_AREA_SHARED)
 		dt_add_property_cells(mem, DT_PRIVATE "share-id",
 				      be16_to_cpu(id->share_id));
-
 	return true;
 }
 
@@ -515,12 +590,27 @@ static void get_msareas(struct dt_node *root,
 		/* This offset is from the arr, not the header! */
 		arange = (void *)arr + be32_to_cpu(arr->offset);
 		for (j = 0; j < be32_to_cpu(arr->ecnt); j++) {
+			uint32_t type = 0, status = 0;
+
+			/*
+			 * Check that the required fields are present in this
+			 * version of the HDAT structure.
+			 */
 			offset = offsetof(struct HDIF_ms_area_address_range, controller_id);
 			if (be32_to_cpu(arr->eactsz) >= offset)
 				add_memory_controller(msarea, arange);
 
-			if (!add_address_range(root, id, arange))
-				return;
+			offset = offsetof(struct HDIF_ms_area_address_range, phys_attr);
+			if (be32_to_cpu(arr->eactsz) >= offset) {
+				uint32_t attr = be32_to_cpu(arange->phys_attr);
+
+				type = GETFIELD(PHYS_ATTR_TYPE_MASK, attr);
+				status = GETFIELD(PHYS_ATTR_STATUS_MASK, attr);
+			}
+
+			if (add_address_range(root, id, arange, type, status))
+				prerror("Unable to use memory range %d from MSAREA %d\n", j, i);
+
 			arange = (void *)arange + be32_to_cpu(arr->esize);
 		}
 	}
-- 
2.20.1



More information about the Skiboot mailing list