diff --git a/arch/powerpc/include/uapi/asm/papr_scm_pdsm.h b/arch/powerpc/include/uapi/asm/papr_scm_pdsm.h
index 40ec55d06f4c..2db4ffdff285 100644
--- a/arch/powerpc/include/uapi/asm/papr_scm_pdsm.h
+++ b/arch/powerpc/include/uapi/asm/papr_scm_pdsm.h
@@ -115,6 +115,7 @@ enum papr_scm_pdsm {
 	PAPR_SCM_PDSM_MIN = 0x0,
 	PAPR_SCM_PDSM_HEALTH,
 	PAPR_SCM_PDSM_FETCH_PERF_STATS,
+	PAPR_SCM_PDSM_READ_PERF_STATS,
 	PAPR_SCM_PDSM_MAX,
 };
 
@@ -183,4 +184,38 @@ struct nd_pdsm_fetch_perf_stats_v1 {
 #define nd_pdsm_fetch_perf_stats nd_pdsm_fetch_perf_stats_v1
 #define ND_PDSM_FETCH_PERF_STATS_VERSION 1
 
+/*
+ * Holds a single performance stat. papr_scm owns a buffer that holds an array
+ * of all the available stats and their values. Access to the buffer is provided
+ * via PERF_STAT_SIZE and READ_PERF_STATS psdm.
+ * id : id of the performance stat. Usually acsii encode stat name.
+ * val : Non normalized value of the id.
+ */
+
+struct nd_pdsm_perf_stat {
+	__u64 id;
+	__u64 val;
+};
+
+/*
+ * Returns a chunk of performance stats buffer data to libndctl.
+ * This is needed to overcome the 256 byte envelope size limit enforced by
+ * libnvdimm.
+ * in_offset: The starting offset to perf stats data buffer.
+ * in_length: Length of data to be copied to 'stats_data'
+ * stats_data: Holds the chunk of requested perf stats data buffer.
+ *
+ * Note: To prevent races in reading performance stats, in_offset and in_length
+ * should multiple of 16-Bytes. If they are not then papr_scm will return an
+ * -EINVAL error.
+ */
+struct nd_pdsm_read_perf_stats_v1 {
+	__u32 in_offset;
+	__u32 in_length;
+	struct nd_pdsm_perf_stat stats_data[];
+} __packed;
+
+#define nd_pdsm_read_perf_stats nd_pdsm_read_perf_stats_v1
+#define ND_PDSM_READ_PERF_STATS_VERSION 1
+
 #endif /* _UAPI_ASM_POWERPC_PAPR_SCM_PDSM_H_ */
diff --git a/arch/powerpc/platforms/pseries/papr_scm.c b/arch/powerpc/platforms/pseries/papr_scm.c
index f8b37a830aed..06744d7fe727 100644
--- a/arch/powerpc/platforms/pseries/papr_scm.c
+++ b/arch/powerpc/platforms/pseries/papr_scm.c
@@ -525,6 +525,94 @@ static int is_cmd_valid(struct nvdimm *nvdimm, unsigned int cmd, void *buf,
 	return 0;
 }
 
+/*
+ * Read the contents of dimm performance statistics buffer at the given
+ * 'in_offset' and copy 'in_length' number of bytes to the pkg payload.
+ * Both 'in_offset' and 'in_length' are expected to be in multiples of
+ * 16-Bytes to prevent a read/write race that may cause malformed values
+ * top be returned as performance statistics buffer content.
+ */
+static int papr_scm_read_perf_stats(struct papr_scm_priv *p,
+				    struct nd_pdsm_cmd_pkg *pkg)
+{
+	int rc;
+	struct nd_pdsm_read_perf_stats *stats =
+		(struct nd_pdsm_read_perf_stats *)pdsm_cmd_to_payload(pkg);
+	const size_t copysize = sizeof(struct nd_pdsm_read_perf_stats);
+	off_t offset;
+
+	/*
+	 * If the requested payload version is greater than one we know
+	 * about, return the payload version we know about and let
+	 * caller/userspace handle.
+	 */
+	if (pkg->payload_version > ND_PDSM_READ_PERF_STATS_VERSION)
+		pkg->payload_version = ND_PDSM_READ_PERF_STATS_VERSION;
+
+	if (pkg->hdr.nd_size_out < copysize) {
+		dev_dbg(&p->pdev->dev, "Truncated payload (%u). Expected (%lu)",
+			pkg->hdr.nd_size_out, copysize);
+		rc = -ENOSPC;
+		goto out;
+	}
+
+	/* Protect concurrent modifications to papr_scm_priv */
+	rc = mutex_lock_interruptible(&p->health_mutex);
+	if (rc)
+		goto out;
+
+	if (!p->len_stat_buffer) {
+		dev_dbg(&p->pdev->dev, "Perf stats: req for unsupported device");
+		rc = -ENOENT;
+		goto mutex_unlock_out;
+	}
+
+	/* calculate offset skipping the perf_stats buffer header */
+	offset = stats->in_offset + sizeof(*p->perf_stats);
+	/* Cap the copy length to extend of stats buffer */
+	stats->in_length = min(stats->in_length,
+			       (__u32)(p->len_stat_buffer - offset));
+
+	/*
+	 * Ensure that offset and length are valid and multiples of 16 bytes.
+	 * PDSM FETCH_PERF_STATS can interleave in between PDSM READ_PERF_STAT.
+	 * Since this is a read/write race hence malformed performance stats
+	 * buffer contents that may be returned.
+	 * A 16-Byte read alignment constraint forces a read granularity of
+	 * same the size of each performance stat and they are guaranteed to
+	 * remain stable during 'health_mutex' lock context.
+	 */
+	if (offset >= p->len_stat_buffer || (offset % 16) ||
+	    (stats->in_length % 16)) {
+		dev_dbg(&p->pdev->dev,
+			"Perf stats: Invalid offset(0x%lx) or length(0x%x)",
+			offset, stats->in_length);
+		rc = -EINVAL;
+		goto mutex_unlock_out;
+	}
+
+	/* Put the stats buffer data in the payload buffer */
+	memcpy(stats->stats_data,
+	       (void *)p->perf_stats + offset, stats->in_length);
+
+	pkg->hdr.nd_fw_size = stats->in_length;
+
+	dev_dbg(&p->pdev->dev, "Copying payload size=%u version=0x%x\n",
+		stats->in_length, pkg->payload_version);
+
+mutex_unlock_out:
+	mutex_unlock(&p->health_mutex);
+out:
+	/*
+	 * Put the error in out package and return success from function
+	 * so that errors if any are propogated back to userspace.
+	 */
+	pkg->cmd_status = rc;
+	dev_dbg(&p->pdev->dev, "completion code = %d\n", rc);
+
+	return 0;
+}
+
 /* Return the size in bytes for returning all perf stats to libndctl */
 static int papr_scm_fetch_perf_stats(struct papr_scm_priv *p,
 				     struct nd_pdsm_cmd_pkg *pkg)
@@ -664,6 +752,9 @@ static int papr_scm_service_pdsm(struct papr_scm_priv *p,
 	case PAPR_SCM_PDSM_FETCH_PERF_STATS:
 		return papr_scm_fetch_perf_stats(p, call_pkg);
 
+	case PAPR_SCM_PDSM_READ_PERF_STATS:
+		return papr_scm_read_perf_stats(p, call_pkg);
+
 	default:
 		dev_dbg(&p->pdev->dev, "Unsupported PDSM request 0x%llx\n",
 			call_pkg->hdr.nd_command);
