[ndctl,v5,2/6] libncdtl: Add initial support for NVDIMM_FAMILY_PAPR nvdimm family
diff mbox series

Message ID 20200529220600.225320-3-vaibhav@linux.ibm.com
State Superseded
Headers show
Series
  • Add support for reporting papr nvdimm health
Related show

Commit Message

Vaibhav Jain May 29, 2020, 10:05 p.m. UTC
Add necessary scaffolding in libndctl for dimms that support papr_scm
specification[1]. Since there can be platforms that support
Open-Firmware[2] but not the papr_scm specification, hence the changes
proposed first add support for probing if the dimm bus supports
Open-Firmware. This is done via querying for sysfs attribute 'of_node'
in dimm device sysfs directory. If available newly introduced member
'struct ndctl_bus.has_of_node' is set. During the probe of the dimm
and execution of add_dimm(), the newly introduced add_papr_dimm()
is called if dimm bus reports supports Open-Firmware.

Function add_papr_dimm() queries the 'compatible' device tree
attribute and based on its value assign NVDIMM_FAMILY_PAPR to the
dimm command family. In future, based on the contents of 'compatible'
attribute more of_pmem dimm families can be queried.

We also add support for parsing the dimm flags for
NVDIMM_FAMILY_PAPR supporting nvdimms as described at [3]. A newly
introduced function parse_papr_flags() reads the contents of this
flag file and sets appropriate flag bits in 'struct
ndctl_dimm.flags'.

Also we advertise support for monitor mode by allocating a file
descriptor to the dimm 'flags' file and assigning it to 'struct
ndctl_dimm.health_event_fd'.

The dimm-ops implementation for NVDIMM_FAMILY_PAPR is
available in global variable 'papr_dimm_ops' which points to
skeleton implementation in newly introduced file 'lib/papr.c'.

References:
[1] Documentation/powerpc/papr_hcalls.rst
https://lore.kernel.org/linux-nvdimm/20200529214719.223344-2-vaibhav@linux.ibm.com

[2] https://en.wikipedia.org/wiki/Open_Firmware

[3] Documentation/ABI/testing/sysfs-bus-papr-pmem
https://lore.kernel.org/linux-nvdimm/20200529214719.223344-4-vaibhav@linux.ibm.com

Signed-off-by: Vaibhav Jain <vaibhav@linux.ibm.com>
---
Changelog:

v4..v5:
* Renamed file 'papr_scm.c' to 'papr.c'
* s/NVDIMM_FAMILY_PAPR_SCM/NVDIMM_FAMILY_PAPR/g
* s/papr_scm_dimm_ops/papr_dimm_ops/g
* s/parse_papr_scm_flags()/parse_papr_flags()/g
* Updated patch description & title to reflect new command family name
  and file-names.

v3..v4:
* None

v2..v3:
* Renamed add_of_pmem() to add_papr_dimm() [ Aneesh ]

v1..v2:
* Squashed the patch to parse dimm flags
* Updated the dimm flags parsing to add case for 'restore_fail' and
  'flush_fail'.
* Renamed parse_of_pmem_flags() to parse_papr_scm_flags().
* Updated the path to dimm flags file to 'papr/flags'.
* Introduced 'papr_scm.c' file in this patch rather than later in the
  patch series.
* Update add_of_pmem_dimm() to parse dimm flags and enable monitoring
  only if 'ibm,pmemory' compatible nvdimm is found.
---
 ndctl/lib/Makefile.am |  1 +
 ndctl/lib/libndctl.c  | 78 +++++++++++++++++++++++++++++++++++++++++++
 ndctl/lib/papr.c      | 32 ++++++++++++++++++
 ndctl/lib/private.h   |  2 ++
 ndctl/libndctl.h      |  1 +
 ndctl/ndctl.h         |  1 +
 6 files changed, 115 insertions(+)
 create mode 100644 ndctl/lib/papr.c

Comments

Vishal Verma June 3, 2020, 5:47 a.m. UTC | #1
On Sat, 2020-05-30 at 03:35 +0530, Vaibhav Jain wrote:

> diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c
> index d76dbf7e17de..a52c2e208fbe 100644
> --- a/ndctl/lib/libndctl.c
> +++ b/ndctl/lib/libndctl.c
> @@ -799,6 +799,28 @@ static void parse_nfit_mem_flags(struct ndctl_dimm *dimm, char *flags)
>  				ndctl_dimm_get_devname(dimm), flags);
>  }
>  
> +static void parse_papr_flags(struct ndctl_dimm *dimm, char *flags)
> +{
> +	char *start, *end;
> +
> +	start = flags;
> +	while ((end = strchr(start, ' '))) {
> +		*end = '\0';
> +		if (strcmp(start, "not_armed") == 0)
> +			dimm->flags.f_arm = 1;
> +		else if (strcmp(start, "flush_fail") == 0)
> +			dimm->flags.f_flush = 1;
> +		else if (strcmp(start, "restore_fail") == 0)
> +			dimm->flags.f_restore = 1;
> +		else if (strcmp(start, "smart_notify") == 0)
> +			dimm->flags.f_smart = 1;
> +		start = end + 1;
> +	}
> +	if (end != start)
> +		dbg(ndctl_dimm_get_ctx(dimm), "%s: %s\n",
> +				ndctl_dimm_get_devname(dimm), flags);

I think this would be more readable if ctx was obtained and saved in a
'ctx' variable at the start. Also, it might be better if 'flags' was
included in the string - something like "%s flags: %s\n"

> +}
> +
>  static void parse_dimm_flags(struct ndctl_dimm *dimm, char *flags)
>  {
>  	char *start, *end;
> @@ -856,6 +878,12 @@ static void *add_bus(void *parent, int id, const char *ctl_base)
>  		bus->revision = strtoul(buf, NULL, 0);
>  	}
>  
> +	sprintf(path, "%s/device/of_node/compatible", ctl_base);
> +	if (sysfs_read_attr(ctx, path, buf) < 0)
> +		bus->has_of_node = 0;
> +	else
> +		bus->has_of_node = 1;
> +
>  	sprintf(path, "%s/device/nfit/dsm_mask", ctl_base);
>  	if (sysfs_read_attr(ctx, path, buf) < 0)
>  		bus->nfit_dsm_mask = 0;
> @@ -964,6 +992,10 @@ NDCTL_EXPORT int ndctl_bus_has_nfit(struct ndctl_bus *bus)
>  	return bus->has_nfit;
>  }
>  
> +NDCTL_EXPORT int ndctl_bus_has_of_node(struct ndctl_bus *bus)
> +{
> +	return bus->has_of_node;
> +}

New libndctl APIs need to update the 'symbol script' -
ndctl/lib/libndctl.sym. Create a new 'section' at the bottom, and add
all new symbols to that section. The new section would be 'LIBNDCTL_24'
(Everything going into a given release gets a new section in that file,
so any subsequent patches - throughout the release cycle - that add new
APIs can add them to this new section).

>  /**
>   * ndctl_bus_get_major - nd bus character device major number
>   * @bus: ndctl_bus instance returned from ndctl_bus_get_{first|next}
> @@ -1441,6 +1473,47 @@ static int ndctl_bind(struct ndctl_ctx *ctx, struct kmod_module *module,
>  static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath);
>  static struct kmod_module *to_module(struct ndctl_ctx *ctx, const char *alias);
>  
> +static int add_papr_dimm(struct ndctl_dimm *dimm, const char *dimm_base)
> +{
> +	int rc = -ENODEV;
> +	char buf[SYSFS_ATTR_SIZE];
> +	struct ndctl_ctx *ctx = dimm->bus->ctx;
> +	char *path = calloc(1, strlen(dimm_base) + 100);
> +
> +	dbg(ctx, "Probing of_pmem dimm %d at %s\n", dimm->id, dimm_base);
> +
> +	if (!path)
> +		return -ENOMEM;
> +
> +	sprintf(path, "%s/../of_node/compatible", dimm_base);
> +	if (sysfs_read_attr(ctx, path, buf) < 0)
> +		goto out;

Two things here:
1. Why not use the new ndctl_bus_has_of_node helper here? and
2. This looks redundant. add_papr_dimm() is only called if
ndctl_bus_has_of_node() during add_dimm.

> +
> +

Nit: double newline here 

> +	dbg(ctx, "Compatible of_pmem dimm %d at %s\n", dimm->id, buf);
> +	/* construct path to the papr compatible dimm flags file */
> +	sprintf(path, "%s/papr/flags", dimm_base);
> +
> +	if (strcmp(buf, "ibm,pmemory") == 0 &&
> +	    sysfs_read_attr(ctx, path, buf) == 0) {
> +
> +		dbg(ctx, "Found papr dimm %d at %s\n", dimm->id, buf);

Throughout the series - it would be better to print messages such as:

	"%s: adding papr dimm with flags: %s\n", devname, buf

This would result in the canonical dimm names - nmem1, nmem2 and so on
being used instead of "dimm 1" which can be confusing just from a
convention standpoint.

Similarly for the print a few lines above this:

	"%s: of_pmem compatible dimm: %s\n", devname, buf

(In this case, what does the 'compatible' sysfs attrubute contain? Is it
useful to print here? - I havent't looked, just asking).

> diff --git a/ndctl/lib/papr.c b/ndctl/lib/papr.c
> new file mode 100644
> index 000000000000..5ddf2755a950
> --- /dev/null
> +++ b/ndctl/lib/papr.c
> @@ -0,0 +1,32 @@
> +/*
> + * Copyright (C) 2020 IBM Corporation
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU Lesser General Public License,
> + * version 2.1, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope it will be useful, but WITHOUT ANY
> + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
> + * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
> + * more details.
> + */

Prefer SPDX license header instead fo the long form text for new files.
Vaibhav Jain June 3, 2020, 9:57 a.m. UTC | #2
"Verma, Vishal L" <vishal.l.verma@intel.com> writes:

> On Sat, 2020-05-30 at 03:35 +0530, Vaibhav Jain wrote:
>
>> diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c
>> index d76dbf7e17de..a52c2e208fbe 100644
>> --- a/ndctl/lib/libndctl.c
>> +++ b/ndctl/lib/libndctl.c
>> @@ -799,6 +799,28 @@ static void parse_nfit_mem_flags(struct ndctl_dimm *dimm, char *flags)
>>  				ndctl_dimm_get_devname(dimm), flags);
>>  }
>>  
>> +static void parse_papr_flags(struct ndctl_dimm *dimm, char *flags)
>> +{
>> +	char *start, *end;
>> +
>> +	start = flags;
>> +	while ((end = strchr(start, ' '))) {
>> +		*end = '\0';
>> +		if (strcmp(start, "not_armed") == 0)
>> +			dimm->flags.f_arm = 1;
>> +		else if (strcmp(start, "flush_fail") == 0)
>> +			dimm->flags.f_flush = 1;
>> +		else if (strcmp(start, "restore_fail") == 0)
>> +			dimm->flags.f_restore = 1;
>> +		else if (strcmp(start, "smart_notify") == 0)
>> +			dimm->flags.f_smart = 1;
>> +		start = end + 1;
>> +	}
>> +	if (end != start)
>> +		dbg(ndctl_dimm_get_ctx(dimm), "%s: %s\n",
>> +				ndctl_dimm_get_devname(dimm), flags);
>
> I think this would be more readable if ctx was obtained and saved in a
> 'ctx' variable at the start. Also, it might be better if 'flags' was
> included in the string - something like "%s flags: %s\n"
Sure will get this updated in v6. This function definition was derived
from parse_dimm_flags() hence looks this way.

>
>> +}
>> +
>>  static void parse_dimm_flags(struct ndctl_dimm *dimm, char *flags)
>>  {
>>  	char *start, *end;
>> @@ -856,6 +878,12 @@ static void *add_bus(void *parent, int id, const char *ctl_base)
>>  		bus->revision = strtoul(buf, NULL, 0);
>>  	}
>>  
>> +	sprintf(path, "%s/device/of_node/compatible", ctl_base);
>> +	if (sysfs_read_attr(ctx, path, buf) < 0)
>> +		bus->has_of_node = 0;
>> +	else
>> +		bus->has_of_node = 1;
>> +
>>  	sprintf(path, "%s/device/nfit/dsm_mask", ctl_base);
>>  	if (sysfs_read_attr(ctx, path, buf) < 0)
>>  		bus->nfit_dsm_mask = 0;
>> @@ -964,6 +992,10 @@ NDCTL_EXPORT int ndctl_bus_has_nfit(struct ndctl_bus *bus)
>>  	return bus->has_nfit;
>>  }
>>  
>> +NDCTL_EXPORT int ndctl_bus_has_of_node(struct ndctl_bus *bus)
>> +{
>> +	return bus->has_of_node;
>> +}
>
> New libndctl APIs need to update the 'symbol script' -
> ndctl/lib/libndctl.sym. Create a new 'section' at the bottom, and add
> all new symbols to that section. The new section would be 'LIBNDCTL_24'
> (Everything going into a given release gets a new section in that file,
> so any subsequent patches - throughout the release cycle - that add new
> APIs can add them to this new section).
Sure and thanks for pointing this out. Will add this symbol to the library
version script in v6

>
>>  /**
>>   * ndctl_bus_get_major - nd bus character device major number
>>   * @bus: ndctl_bus instance returned from ndctl_bus_get_{first|next}
>> @@ -1441,6 +1473,47 @@ static int ndctl_bind(struct ndctl_ctx *ctx, struct kmod_module *module,
>>  static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath);
>>  static struct kmod_module *to_module(struct ndctl_ctx *ctx, const char *alias);
>>  
>> +static int add_papr_dimm(struct ndctl_dimm *dimm, const char *dimm_base)
>> +{
>> +	int rc = -ENODEV;
>> +	char buf[SYSFS_ATTR_SIZE];
>> +	struct ndctl_ctx *ctx = dimm->bus->ctx;
>> +	char *path = calloc(1, strlen(dimm_base) + 100);
>> +
>> +	dbg(ctx, "Probing of_pmem dimm %d at %s\n", dimm->id, dimm_base);
>> +
>> +	if (!path)
>> +		return -ENOMEM;
>> +
>> +	sprintf(path, "%s/../of_node/compatible", dimm_base);
>> +	if (sysfs_read_attr(ctx, path, buf) < 0)
>> +		goto out;
>
> Two things here:
> 1. Why not use the new ndctl_bus_has_of_node helper here? and
> 2. This looks redundant. add_papr_dimm() is only called if
> ndctl_bus_has_of_node() during add_dimm.
Presently we have two different nvdimm implementations:

* papr-scm: handled by arch/powerpc/platforms/pseries/papr_scm kernel module.
* nvdimm-n: handled by drivers/nvdimm/of_pmem kernel module.

Both nvdimms are exposed to the kernel via device tree nodes but different
'compatible' properties. This patchset only adds support for 'papr-scm'
compatible nvdimms.

'ndctl_bus_has_of_node()' simply indicates if the nvdimm has an
open-firmware compatible devicetree node associated with it and doesnt
necessarily indicate if its papr-scm compliant.

Hence validating the 'compatible' attribute value is necessary here.
Please see a more detailed info below regarding the 'compatible' sysfs
attribute.

>
>> +
>> +
>
> Nit: double newline here
Sure. Will fix this in v6
>
>> +	dbg(ctx, "Compatible of_pmem dimm %d at %s\n", dimm->id, buf);
>> +	/* construct path to the papr compatible dimm flags file */
>> +	sprintf(path, "%s/papr/flags", dimm_base);
>> +
>> +	if (strcmp(buf, "ibm,pmemory") == 0 &&
>> +	    sysfs_read_attr(ctx, path, buf) == 0) {
>> +
>> +		dbg(ctx, "Found papr dimm %d at %s\n", dimm->id, buf);
>
> Throughout the series - it would be better to print messages such as:
>
> 	"%s: adding papr dimm with flags: %s\n", devname, buf
>
> This would result in the canonical dimm names - nmem1, nmem2 and so on
> being used instead of "dimm 1" which can be confusing just from a
> convention standpoint.
>
> Similarly for the print a few lines above this:
>
> 	"%s: of_pmem compatible dimm: %s\n", devname, buf
>
Sure will address this in v6.

> (In this case, what does the 'compatible' sysfs attrubute contain? Is it
> useful to print here? - I havent't looked, just asking).

For papr-scm compatible nvdimms:
# cat /sys/class/nd/ndctl0/device/of_node/compatible
ibm,pmemory

This devicetree property is usually in format "<manufacturer>,<model>" that
tells the which kernel module to bind to the device. The papr_scm
kernel module indicates support for such device value in its device
match table ( https://git.io/JfPzF ) so that kernel can call its
probe-function.

Dumping this value here will be useful in debugging probe failures of nvdimms
that are exposed via open-firmware devicetree nodes but are not
necessarily papr_scm specification compliant.

>
>> diff --git a/ndctl/lib/papr.c b/ndctl/lib/papr.c
>> new file mode 100644
>> index 000000000000..5ddf2755a950
>> --- /dev/null
>> +++ b/ndctl/lib/papr.c
>> @@ -0,0 +1,32 @@
>> +/*
>> + * Copyright (C) 2020 IBM Corporation
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU Lesser General Public License,
>> + * version 2.1, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope it will be useful, but WITHOUT ANY
>> + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
>> + * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
>> + * more details.
>> + */
>
> Prefer SPDX license header instead fo the long form text for new files.
>
Sure will address this in v6.
Vishal Verma June 3, 2020, 3:20 p.m. UTC | #3
On Wed, 2020-06-03 at 15:27 +0530, Vaibhav Jain wrote:
> > 
> > Two things here:
> > 1. Why not use the new ndctl_bus_has_of_node helper here? and
> > 2. This looks redundant. add_papr_dimm() is only called if
> > ndctl_bus_has_of_node() during add_dimm.
> Presently we have two different nvdimm implementations:
> 
> * papr-scm: handled by arch/powerpc/platforms/pseries/papr_scm kernel module.
> * nvdimm-n: handled by drivers/nvdimm/of_pmem kernel module.
> 
> Both nvdimms are exposed to the kernel via device tree nodes but different
> 'compatible' properties. This patchset only adds support for 'papr-scm'
> compatible nvdimms.
> 
> 'ndctl_bus_has_of_node()' simply indicates if the nvdimm has an
> open-firmware compatible devicetree node associated with it and doesnt
> necessarily indicate if its papr-scm compliant.
> 
> Hence validating the 'compatible' attribute value is necessary here.
> Please see a more detailed info below regarding the 'compatible' sysfs
> attribute.
> 
Understood - one more question:

Would it be useful to wrap the 'compatible' check into an API similar to
_has_of_node - say ndctl_bus_is_papr_compatible()? I'm not too strongly
attached this, there is only one user so far after all, but it seemed
like an easy thing that might get copy-pasted around in the future.
Vaibhav Jain June 3, 2020, 4:49 p.m. UTC | #4
"Verma, Vishal L" <vishal.l.verma@intel.com> writes:

> On Wed, 2020-06-03 at 15:27 +0530, Vaibhav Jain wrote:
>> > 
>> > Two things here:
>> > 1. Why not use the new ndctl_bus_has_of_node helper here? and
>> > 2. This looks redundant. add_papr_dimm() is only called if
>> > ndctl_bus_has_of_node() during add_dimm.
>> Presently we have two different nvdimm implementations:
>> 
>> * papr-scm: handled by arch/powerpc/platforms/pseries/papr_scm kernel module.
>> * nvdimm-n: handled by drivers/nvdimm/of_pmem kernel module.
>> 
>> Both nvdimms are exposed to the kernel via device tree nodes but different
>> 'compatible' properties. This patchset only adds support for 'papr-scm'
>> compatible nvdimms.
>> 
>> 'ndctl_bus_has_of_node()' simply indicates if the nvdimm has an
>> open-firmware compatible devicetree node associated with it and doesnt
>> necessarily indicate if its papr-scm compliant.
>> 
>> Hence validating the 'compatible' attribute value is necessary here.
>> Please see a more detailed info below regarding the 'compatible' sysfs
>> attribute.
>> 
> Understood - one more question:
>
> Would it be useful to wrap the 'compatible' check into an API similar to
> _has_of_node - say ndctl_bus_is_papr_compatible()? I'm not too strongly
> attached this, there is only one user so far after all, but it seemed
> like an easy thing that might get copy-pasted around in the future.

Yes, sounds good to me. Should simplify the add_papr_dimm() function a
bit as I can just call ndctl_bus_is_papr_compatible() and if true then
setup the cmd_family.

Will roll out this change in v6 iteration.

Patch
diff mbox series

diff --git a/ndctl/lib/Makefile.am b/ndctl/lib/Makefile.am
index d6be5c3acd26..e15bb2288575 100644
--- a/ndctl/lib/Makefile.am
+++ b/ndctl/lib/Makefile.am
@@ -23,6 +23,7 @@  libndctl_la_SOURCES =\
 	hpe1.c \
 	msft.c \
 	hyperv.c \
+	papr.c \
 	ars.c \
 	firmware.c \
 	libndctl.c \
diff --git a/ndctl/lib/libndctl.c b/ndctl/lib/libndctl.c
index d76dbf7e17de..a52c2e208fbe 100644
--- a/ndctl/lib/libndctl.c
+++ b/ndctl/lib/libndctl.c
@@ -799,6 +799,28 @@  static void parse_nfit_mem_flags(struct ndctl_dimm *dimm, char *flags)
 				ndctl_dimm_get_devname(dimm), flags);
 }
 
+static void parse_papr_flags(struct ndctl_dimm *dimm, char *flags)
+{
+	char *start, *end;
+
+	start = flags;
+	while ((end = strchr(start, ' '))) {
+		*end = '\0';
+		if (strcmp(start, "not_armed") == 0)
+			dimm->flags.f_arm = 1;
+		else if (strcmp(start, "flush_fail") == 0)
+			dimm->flags.f_flush = 1;
+		else if (strcmp(start, "restore_fail") == 0)
+			dimm->flags.f_restore = 1;
+		else if (strcmp(start, "smart_notify") == 0)
+			dimm->flags.f_smart = 1;
+		start = end + 1;
+	}
+	if (end != start)
+		dbg(ndctl_dimm_get_ctx(dimm), "%s: %s\n",
+				ndctl_dimm_get_devname(dimm), flags);
+}
+
 static void parse_dimm_flags(struct ndctl_dimm *dimm, char *flags)
 {
 	char *start, *end;
@@ -856,6 +878,12 @@  static void *add_bus(void *parent, int id, const char *ctl_base)
 		bus->revision = strtoul(buf, NULL, 0);
 	}
 
+	sprintf(path, "%s/device/of_node/compatible", ctl_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		bus->has_of_node = 0;
+	else
+		bus->has_of_node = 1;
+
 	sprintf(path, "%s/device/nfit/dsm_mask", ctl_base);
 	if (sysfs_read_attr(ctx, path, buf) < 0)
 		bus->nfit_dsm_mask = 0;
@@ -964,6 +992,10 @@  NDCTL_EXPORT int ndctl_bus_has_nfit(struct ndctl_bus *bus)
 	return bus->has_nfit;
 }
 
+NDCTL_EXPORT int ndctl_bus_has_of_node(struct ndctl_bus *bus)
+{
+	return bus->has_of_node;
+}
 /**
  * ndctl_bus_get_major - nd bus character device major number
  * @bus: ndctl_bus instance returned from ndctl_bus_get_{first|next}
@@ -1441,6 +1473,47 @@  static int ndctl_bind(struct ndctl_ctx *ctx, struct kmod_module *module,
 static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath);
 static struct kmod_module *to_module(struct ndctl_ctx *ctx, const char *alias);
 
+static int add_papr_dimm(struct ndctl_dimm *dimm, const char *dimm_base)
+{
+	int rc = -ENODEV;
+	char buf[SYSFS_ATTR_SIZE];
+	struct ndctl_ctx *ctx = dimm->bus->ctx;
+	char *path = calloc(1, strlen(dimm_base) + 100);
+
+	dbg(ctx, "Probing of_pmem dimm %d at %s\n", dimm->id, dimm_base);
+
+	if (!path)
+		return -ENOMEM;
+
+	sprintf(path, "%s/../of_node/compatible", dimm_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		goto out;
+
+
+	dbg(ctx, "Compatible of_pmem dimm %d at %s\n", dimm->id, buf);
+	/* construct path to the papr compatible dimm flags file */
+	sprintf(path, "%s/papr/flags", dimm_base);
+
+	if (strcmp(buf, "ibm,pmemory") == 0 &&
+	    sysfs_read_attr(ctx, path, buf) == 0) {
+
+		dbg(ctx, "Found papr dimm %d at %s\n", dimm->id, buf);
+		dimm->cmd_family = NVDIMM_FAMILY_PAPR;
+
+		/* Parse dimm flags */
+		parse_papr_flags(dimm, buf);
+
+		/* Allocate monitor mode fd */
+		dimm->health_eventfd = open(path, O_RDONLY|O_CLOEXEC);
+		rc = 0;
+	}
+	/* Ignore dimm if unknown type */
+
+ out:
+	free(path);
+	return rc;
+}
+
 static int add_nfit_dimm(struct ndctl_dimm *dimm, const char *dimm_base)
 {
 	int i, rc = -1;
@@ -1619,6 +1692,8 @@  static void *add_dimm(void *parent, int id, const char *dimm_base)
 	if (ndctl_bus_has_nfit(bus)) {
 		dimm->formats = formats;
 		rc = add_nfit_dimm(dimm, dimm_base);
+	} else if (ndctl_bus_has_of_node(bus)) {
+		rc = add_papr_dimm(dimm, dimm_base);
 	}
 
 	if (rc == -ENODEV) {
@@ -1636,6 +1711,9 @@  static void *add_dimm(void *parent, int id, const char *dimm_base)
 		dimm->ops = msft_dimm_ops;
 	if (dimm->cmd_family == NVDIMM_FAMILY_HYPERV)
 		dimm->ops = hyperv_dimm_ops;
+	if (dimm->cmd_family == NVDIMM_FAMILY_PAPR)
+		dimm->ops = papr_dimm_ops;
+
  out:
 	if (rc) {
 		err(ctx, "Unable to probe dimm:%d. Err:%d\n", id, rc);
diff --git a/ndctl/lib/papr.c b/ndctl/lib/papr.c
new file mode 100644
index 000000000000..5ddf2755a950
--- /dev/null
+++ b/ndctl/lib/papr.c
@@ -0,0 +1,32 @@ 
+/*
+ * Copyright (C) 2020 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <util/log.h>
+#include <ndctl.h>
+#include <ndctl/libndctl.h>
+#include <lib/private.h>
+
+static bool papr_cmd_is_supported(struct ndctl_dimm *dimm, int cmd)
+{
+	/* Handle this separately to support monitor mode */
+	if (cmd == ND_CMD_SMART)
+		return true;
+
+	return !!(dimm->cmd_mask & (1ULL << cmd));
+}
+
+struct ndctl_dimm_ops * const papr_dimm_ops = &(struct ndctl_dimm_ops) {
+	.cmd_is_supported = papr_cmd_is_supported,
+};
diff --git a/ndctl/lib/private.h b/ndctl/lib/private.h
index 2e537f0a8649..d90236b1f98b 100644
--- a/ndctl/lib/private.h
+++ b/ndctl/lib/private.h
@@ -167,6 +167,7 @@  struct ndctl_bus {
 	int dimms_init;
 	int regions_init;
 	int has_nfit;
+	int has_of_node;
 	char *bus_path;
 	char *bus_buf;
 	size_t buf_len;
@@ -352,6 +353,7 @@  extern struct ndctl_dimm_ops * const intel_dimm_ops;
 extern struct ndctl_dimm_ops * const hpe1_dimm_ops;
 extern struct ndctl_dimm_ops * const msft_dimm_ops;
 extern struct ndctl_dimm_ops * const hyperv_dimm_ops;
+extern struct ndctl_dimm_ops * const papr_dimm_ops;
 
 static inline struct ndctl_bus *cmd_to_bus(struct ndctl_cmd *cmd)
 {
diff --git a/ndctl/libndctl.h b/ndctl/libndctl.h
index 2580f433ade8..daf11b8ce4ea 100644
--- a/ndctl/libndctl.h
+++ b/ndctl/libndctl.h
@@ -119,6 +119,7 @@  struct ndctl_bus *ndctl_bus_get_next(struct ndctl_bus *bus);
              bus = ndctl_bus_get_next(bus))
 struct ndctl_ctx *ndctl_bus_get_ctx(struct ndctl_bus *bus);
 int ndctl_bus_has_nfit(struct ndctl_bus *bus);
+int ndctl_bus_has_of_node(struct ndctl_bus *bus);
 unsigned int ndctl_bus_get_major(struct ndctl_bus *bus);
 unsigned int ndctl_bus_get_minor(struct ndctl_bus *bus);
 const char *ndctl_bus_get_devname(struct ndctl_bus *bus);
diff --git a/ndctl/ndctl.h b/ndctl/ndctl.h
index 008f81cdeb9f..3b64f66d58cc 100644
--- a/ndctl/ndctl.h
+++ b/ndctl/ndctl.h
@@ -263,6 +263,7 @@  struct nd_cmd_pkg {
 #define NVDIMM_FAMILY_HPE2 2
 #define NVDIMM_FAMILY_MSFT 3
 #define NVDIMM_FAMILY_HYPERV 4
+#define NVDIMM_FAMILY_PAPR 5
 
 #define ND_IOCTL_CALL			_IOWR(ND_IOCTL, ND_CMD_CALL,\
 					struct nd_cmd_pkg)