diff mbox

[ndctl,1/3] ndctl: add support for 'pfn' devices

Message ID 20151215021931.37242.87729.stgit@dwillia2-desk3.jf.intel.com (mailing list archive)
State Accepted
Commit 3115b36647bf
Headers show

Commit Message

Dan Williams Dec. 15, 2015, 2:19 a.m. UTC
In support of dma operations on dax mappings the libnvdimm sub-system
provides infrastructure for establishing pmem ranges in the kernel
memmap array.  Similar to a btt configuration a pfn configuration writes
an info block signature to a namespace to designate where the memmap
should be established.  Given the potentially large capacities of pmem
relative to ram it may not be possible to store the extended memmap in
ram.  For this reason the kernel optionally allows the memmap to be
allocated from pmem itself.

Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
 lib/libndctl.c       |  526 +++++++++++++++++++++++++++++++++++++++++++++++++-
 lib/libndctl.sym     |   23 ++
 lib/ndctl/libndctl.h |   47 ++++
 3 files changed, 588 insertions(+), 8 deletions(-)
diff mbox

Patch

diff --git a/lib/libndctl.c b/lib/libndctl.c
index 8693fb5268b8..c0730690ed79 100644
--- a/lib/libndctl.c
+++ b/lib/libndctl.c
@@ -187,16 +187,19 @@  struct ndctl_region {
 	int mappings_init;
 	int namespaces_init;
 	int btts_init;
+	int pfns_init;
 	unsigned long long size;
 	char *region_path;
 	char *region_buf;
 	int buf_len;
 	int generation;
 	struct list_head btts;
+	struct list_head pfns;
 	struct list_head mappings;
 	struct list_head namespaces;
 	struct list_head stale_namespaces;
 	struct list_head stale_btts;
+	struct list_head stale_pfns;
 	struct list_node list;
 	/**
 	 * struct ndctl_interleave_set - extra info for interleave sets
@@ -274,6 +277,33 @@  struct ndctl_btt {
 };
 
 /**
+ * struct ndctl_pfn - reservation for per-page-frame metadata
+ * @module: kernel module (nd_pfn)
+ * @ndns: host namespace for the pfn instance
+ * @loc: host metadata location (ram or pmem (default))
+ * @align: data offset alignment
+ * @region: parent region
+ * @pfn_path: pfn devpath
+ * @uuid: unique identifier for a pfn instance
+ * @pfn_buf: space to print paths for bind/unbind operations
+ * @bdev: block device associated with a pfn
+ */
+struct ndctl_pfn {
+	struct kmod_module *module;
+	struct ndctl_region *region;
+	struct ndctl_namespace *ndns;
+	struct list_node list;
+	enum ndctl_pfn_loc loc;
+	unsigned long align;
+	char *pfn_path;
+	char *pfn_buf;
+	char *bdev;
+	int buf_len;
+	uuid_t uuid;
+	int id, generation;
+};
+
+/**
  * struct ndctl_ctx - library user context to find "nd" instances
  *
  * Instantiate with ndctl_new(), which takes an initial reference.  Free
@@ -502,6 +532,33 @@  static void free_stale_btts(struct ndctl_region *region)
 		free_btt(btt, &region->stale_btts);
 }
 
+static void free_pfn(struct ndctl_pfn *pfn, struct list_head *head)
+{
+	if (head)
+		list_del_from(head, &pfn->list);
+	kmod_module_unref(pfn->module);
+	free(pfn->pfn_path);
+	free(pfn->pfn_buf);
+	free(pfn->bdev);
+	free(pfn);
+}
+
+static void free_pfns(struct ndctl_region *region)
+{
+	struct ndctl_pfn *pfn, *_b;
+
+	list_for_each_safe(&region->pfns, pfn, _b, list)
+		free_pfn(pfn, &region->pfns);
+}
+
+static void free_stale_pfns(struct ndctl_region *region)
+{
+	struct ndctl_pfn *pfn, *_b;
+
+	list_for_each_safe(&region->stale_pfns, pfn, _b, list)
+		free_pfn(pfn, &region->stale_pfns);
+}
+
 static void free_region(struct ndctl_region *region)
 {
 	struct ndctl_bus *bus = region->bus;
@@ -513,6 +570,8 @@  static void free_region(struct ndctl_region *region)
 	}
 	free_btts(region);
 	free_stale_btts(region);
+	free_pfns(region);
+	free_stale_pfns(region);
 	free_namespaces(region);
 	free_stale_namespaces(region);
 	list_del_from(&bus->regions, &region->list);
@@ -983,6 +1042,29 @@  NDCTL_EXPORT struct ndctl_btt *ndctl_region_get_btt_seed(struct ndctl_region *re
 	return NULL;
 }
 
+NDCTL_EXPORT struct ndctl_pfn *ndctl_region_get_pfn_seed(struct ndctl_region *region)
+{
+	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
+	char *path = region->region_buf;
+	int len = region->buf_len;
+	struct ndctl_pfn *pfn;
+	char buf[SYSFS_ATTR_SIZE];
+
+	if (snprintf(path, len, "%s/pfn_seed", region->region_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_region_get_devname(region));
+		return NULL;
+	}
+
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		return NULL;
+
+	ndctl_pfn_foreach(region, pfn)
+		if (strcmp(buf, ndctl_pfn_get_devname(pfn)) == 0)
+			return pfn;
+	return NULL;
+}
+
 NDCTL_EXPORT int ndctl_region_get_ro(struct ndctl_region *region)
 {
 	return region->ro;
@@ -1414,7 +1496,9 @@  static int add_region(void *parent, int id, const char *region_base)
 	if (!region)
 		goto err_region;
 	list_head_init(&region->btts);
+	list_head_init(&region->pfns);
 	list_head_init(&region->stale_btts);
+	list_head_init(&region->stale_pfns);
 	list_head_init(&region->mappings);
 	list_head_init(&region->namespaces);
 	list_head_init(&region->stale_namespaces);
@@ -2205,6 +2289,7 @@  NDCTL_EXPORT void ndctl_region_cleanup(struct ndctl_region *region)
 {
 	free_stale_namespaces(region);
 	free_stale_btts(region);
+	free_stale_pfns(region);
 }
 
 static int ndctl_region_disable(struct ndctl_region *region, int cleanup)
@@ -2223,8 +2308,10 @@  static int ndctl_region_disable(struct ndctl_region *region, int cleanup)
 	}
 	region->namespaces_init = 0;
 	region->btts_init = 0;
+	region->pfns_init = 0;
 	list_append_list(&region->stale_namespaces, &region->namespaces);
 	list_append_list(&region->stale_btts, &region->btts);
+	list_append_list(&region->stale_pfns, &region->pfns);
 	region->generation++;
 	if (cleanup)
 		ndctl_region_cleanup(region);
@@ -2736,6 +2823,30 @@  NDCTL_EXPORT struct ndctl_btt *ndctl_namespace_get_btt(struct ndctl_namespace *n
 	return NULL;
 }
 
+NDCTL_EXPORT struct ndctl_pfn *ndctl_namespace_get_pfn(struct ndctl_namespace *ndns)
+{
+	struct ndctl_region *region = ndctl_namespace_get_region(ndns);
+	struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns);
+	char *path = ndns->ndns_buf;
+	int len = ndns->buf_len;
+	struct ndctl_pfn *pfn;
+	char buf[SYSFS_ATTR_SIZE];
+
+	if (snprintf(path, len, "%s/holder", ndns->ndns_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_namespace_get_devname(ndns));
+		return NULL;
+	}
+
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		return NULL;
+
+	ndctl_pfn_foreach(region, pfn)
+		if (strcmp(buf, ndctl_pfn_get_devname(pfn)) == 0)
+			return pfn;
+	return NULL;
+}
+
 NDCTL_EXPORT const char *ndctl_namespace_get_block_device(struct ndctl_namespace *ndns)
 {
 	struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns);
@@ -2881,6 +2992,7 @@  static int ndctl_unbind(struct ndctl_ctx *ctx, const char *devpath)
 }
 
 static int add_btt(void *parent, int id, const char *btt_base);
+static int add_pfn(void *parent, int id, const char *pfn_base);
 
 static void btts_init(struct ndctl_region *region)
 {
@@ -2895,15 +3007,29 @@  static void btts_init(struct ndctl_region *region)
 	device_parse(bus->ctx, bus, region->region_path, btt_fmt, region, add_btt);
 }
 
+static void pfns_init(struct ndctl_region *region)
+{
+	struct ndctl_bus *bus = ndctl_region_get_bus(region);
+	char pfn_fmt[20];
+
+	if (region->pfns_init)
+		return;
+	region->pfns_init = 1;
+
+	sprintf(pfn_fmt, "pfn%d.", region->id);
+	device_parse(bus->ctx, bus, region->region_path, pfn_fmt, region, add_pfn);
+}
+
 static void region_refresh_children(struct ndctl_region *region)
 {
 	region->namespaces_init = 0;
 	region->btts_init = 0;
+	region->pfns_init = 0;
 	namespaces_init(region);
 	btts_init(region);
+	pfns_init(region);
 }
 
-
 /*
  * Return 0 if enabled, < 0 if failed to enable, and > 0 if claimed by
  * another device and that device is enabled.  In the > 0 case a
@@ -2923,18 +3049,26 @@  NDCTL_EXPORT int ndctl_namespace_enable(struct ndctl_namespace *ndns)
 
 	/*
 	 * Rescan now as successfully enabling a namespace device leads
-	 * to a new one being created, and potentially btts being attached
+	 * to a new one being created, and potentially btts or pfns being
+	 * attached
 	 */
 	region_refresh_children(region);
 
 	if (!ndctl_namespace_is_enabled(ndns)) {
 		struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns);
+		struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns);
 
 		if (btt && ndctl_btt_is_enabled(btt)) {
 			dbg(ctx, "%s: enabled via %s\n", devname,
 					ndctl_btt_get_devname(btt));
 			return 1;
 		}
+		if (pfn && ndctl_pfn_is_enabled(pfn)) {
+			dbg(ctx, "%s: enabled via %s\n", devname,
+					ndctl_pfn_get_devname(pfn));
+			return 1;
+		}
+
 		err(ctx, "%s: failed to enable\n", devname);
 		return rc ? rc : -ENXIO;
 	}
@@ -2970,13 +3104,15 @@  NDCTL_EXPORT int ndctl_namespace_disable(struct ndctl_namespace *ndns)
 NDCTL_EXPORT int ndctl_namespace_disable_invalidate(struct ndctl_namespace *ndns)
 {
 	struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns);
+	struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns);
+	int rc = 0;
 
-	if (btt) {
-		int rc = ndctl_btt_delete(btt);
-
-		if (rc)
-			return rc;
-	}
+	if (btt)
+		rc = ndctl_btt_delete(btt);
+	if (pfn)
+		rc = ndctl_pfn_delete(pfn);
+	if (rc)
+		return rc;
 
 	return ndctl_namespace_disable(ndns);
 }
@@ -3619,3 +3755,377 @@  NDCTL_EXPORT int ndctl_btt_is_configured(struct ndctl_btt *btt)
 
 	return 0;
 }
+
+static int add_pfn(void *parent, int id, const char *pfn_base)
+{
+	struct ndctl_ctx *ctx = ndctl_region_get_ctx(parent);
+	char *path = calloc(1, strlen(pfn_base) + 100);
+	struct ndctl_region *region = parent;
+	struct ndctl_pfn *pfn, *pfn_dup;
+	char buf[SYSFS_ATTR_SIZE];
+	int rc = -ENOMEM;
+
+	if (!path)
+		return -ENOMEM;
+
+	pfn = calloc(1, sizeof(*pfn));
+	if (!pfn)
+		goto err_pfn;
+	pfn->id = id;
+	pfn->region = region;
+	pfn->generation = region->generation;
+
+	pfn->pfn_path = strdup(pfn_base);
+	if (!pfn->pfn_path)
+		goto err_read;
+
+	pfn->pfn_buf = calloc(1, strlen(pfn_base) + 50);
+	if (!pfn->pfn_buf)
+		goto err_read;
+	pfn->buf_len = strlen(pfn_base) + 50;
+
+	sprintf(path, "%s/modalias", pfn_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		goto err_read;
+	pfn->module = to_module(ctx, buf);
+
+	sprintf(path, "%s/uuid", pfn_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		goto err_read;
+	if (strlen(buf) && uuid_parse(buf, pfn->uuid) < 0) {
+		rc = -EINVAL;
+		goto err_read;
+	}
+
+	sprintf(path, "%s/mode", pfn_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		goto err_read;
+	if (strcmp(buf, "none") == 0)
+		pfn->loc = NDCTL_PFN_LOC_NONE;
+	else if (strcmp(buf, "ram") == 0)
+		pfn->loc = NDCTL_PFN_LOC_RAM;
+	else if (strcmp(buf, "pmem") == 0)
+		pfn->loc = NDCTL_PFN_LOC_PMEM;
+	else
+		goto err_read;
+
+	sprintf(path, "%s/align", pfn_base);
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		pfn->align = 0;
+	else
+		pfn->align = strtoul(buf, NULL, 0);
+
+	free(path);
+	ndctl_pfn_foreach(region, pfn_dup)
+		if (pfn->id == pfn_dup->id) {
+			free_pfn(pfn, NULL);
+			return 1;
+		}
+
+	list_add(&region->pfns, &pfn->list);
+	return 0;
+
+ err_read:
+	free(pfn->pfn_buf);
+	free(pfn->pfn_path);
+	free(pfn);
+ err_pfn:
+	free(path);
+	return rc;
+}
+
+NDCTL_EXPORT struct ndctl_pfn *ndctl_pfn_get_first(struct ndctl_region *region)
+{
+	pfns_init(region);
+
+	return list_top(&region->pfns, struct ndctl_pfn, list);
+}
+
+NDCTL_EXPORT struct ndctl_pfn *ndctl_pfn_get_next(struct ndctl_pfn *pfn)
+{
+	struct ndctl_region *region = pfn->region;
+
+	return list_next(&region->pfns, pfn, list);
+}
+
+NDCTL_EXPORT unsigned int ndctl_pfn_get_id(struct ndctl_pfn *pfn)
+{
+	return pfn->id;
+}
+
+NDCTL_EXPORT struct ndctl_namespace *ndctl_pfn_get_namespace(struct ndctl_pfn *pfn)
+{
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	struct ndctl_namespace *ndns, *found = NULL;
+	struct ndctl_region *region = pfn->region;
+	char *path = region->region_buf;
+	int len = region->buf_len;
+	char buf[SYSFS_ATTR_SIZE];
+
+	if (pfn->ndns)
+		return pfn->ndns;
+
+	if (snprintf(path, len, "%s/namespace", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_pfn_get_devname(pfn));
+		return NULL;
+	}
+
+	if (sysfs_read_attr(ctx, path, buf) < 0)
+		return NULL;
+
+	ndctl_namespace_foreach(region, ndns)
+		if (strcmp(buf, ndctl_namespace_get_devname(ndns)) == 0)
+			found = ndns;
+	pfn->ndns = found;
+	return found;
+}
+
+NDCTL_EXPORT void ndctl_pfn_get_uuid(struct ndctl_pfn *pfn, uuid_t uu)
+{
+	memcpy(uu, pfn->uuid, sizeof(uuid_t));
+}
+
+NDCTL_EXPORT int ndctl_pfn_set_uuid(struct ndctl_pfn *pfn, uuid_t uu)
+{
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	char *path = pfn->pfn_buf;
+	int len = pfn->buf_len;
+	char uuid[40];
+
+	if (snprintf(path, len, "%s/uuid", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_pfn_get_devname(pfn));
+		return -ENXIO;
+	}
+
+	uuid_unparse(uu, uuid);
+	if (sysfs_write_attr(ctx, path, uuid) != 0)
+		return -ENXIO;
+	memcpy(pfn->uuid, uu, sizeof(uuid_t));
+	return 0;
+}
+
+NDCTL_EXPORT enum ndctl_pfn_loc ndctl_pfn_get_location(struct ndctl_pfn *pfn)
+{
+	return pfn->loc;
+}
+
+NDCTL_EXPORT int ndctl_pfn_set_location(struct ndctl_pfn *pfn,
+		enum ndctl_pfn_loc loc)
+{
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	char *path = pfn->pfn_buf;
+	int len = pfn->buf_len;
+	const char *locations[] = {
+		[NDCTL_PFN_LOC_NONE] = "none",
+		[NDCTL_PFN_LOC_RAM] = "ram",
+		[NDCTL_PFN_LOC_PMEM] = "pmem",
+	};
+
+	switch (loc) {
+	case NDCTL_PFN_LOC_NONE:
+	case NDCTL_PFN_LOC_RAM:
+	case NDCTL_PFN_LOC_PMEM:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (snprintf(path, len, "%s/mode", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_pfn_get_devname(pfn));
+		return -ENXIO;
+	}
+
+	if (sysfs_write_attr(ctx, path, locations[loc]) != 0)
+		return -ENXIO;
+	pfn->loc = loc;
+	return 0;
+}
+
+NDCTL_EXPORT unsigned long ndctl_pfn_get_align(struct ndctl_pfn *pfn)
+{
+	return pfn->align;
+}
+
+NDCTL_EXPORT int ndctl_pfn_set_align(struct ndctl_pfn *pfn, unsigned long align)
+{
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	char *path = pfn->pfn_buf;
+	int len = pfn->buf_len;
+	char align_str[40];
+
+	if (snprintf(path, len, "%s/align", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_pfn_get_devname(pfn));
+		return -ENXIO;
+	}
+
+	sprintf(align_str, "%lu\n", align);
+	if (sysfs_write_attr(ctx, path, align_str) != 0)
+		return -ENXIO;
+	pfn->align = align;
+	return 0;
+}
+
+NDCTL_EXPORT int ndctl_pfn_set_namespace(struct ndctl_pfn *pfn,
+		struct ndctl_namespace *ndns)
+{
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	char *path = pfn->pfn_buf;
+	int len = pfn->buf_len;
+
+	if (snprintf(path, len, "%s/namespace", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_pfn_get_devname(pfn));
+		return -ENXIO;
+	}
+
+	if (sysfs_write_attr(ctx, path, ndns
+				? ndctl_namespace_get_devname(ndns) : "\n"))
+		return -ENXIO;
+
+	pfn->ndns = ndns;
+	return 0;
+}
+
+NDCTL_EXPORT struct ndctl_bus *ndctl_pfn_get_bus(struct ndctl_pfn *pfn)
+{
+	return pfn->region->bus;
+}
+
+NDCTL_EXPORT struct ndctl_ctx *ndctl_pfn_get_ctx(struct ndctl_pfn *pfn)
+{
+	return ndctl_bus_get_ctx(ndctl_pfn_get_bus(pfn));
+}
+
+NDCTL_EXPORT const char *ndctl_pfn_get_devname(struct ndctl_pfn *pfn)
+{
+	return devpath_to_devname(pfn->pfn_path);
+}
+
+NDCTL_EXPORT const char *ndctl_pfn_get_block_device(struct ndctl_pfn *pfn)
+{
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	struct ndctl_bus *bus = ndctl_pfn_get_bus(pfn);
+	char *path = pfn->pfn_buf;
+	int len = pfn->buf_len;
+
+	if (pfn->bdev)
+		return pfn->bdev;
+
+	if (snprintf(path, len, "%s/block", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_pfn_get_devname(pfn));
+		return "";
+	}
+
+	ndctl_bus_wait_probe(bus);
+	pfn->bdev = get_block_device(ctx, path);
+	return pfn->bdev ? pfn->bdev : "";
+}
+
+NDCTL_EXPORT int ndctl_pfn_is_valid(struct ndctl_pfn *pfn)
+{
+	struct ndctl_region *region = ndctl_pfn_get_region(pfn);
+
+	return pfn->generation == region->generation;
+}
+
+NDCTL_EXPORT int ndctl_pfn_is_enabled(struct ndctl_pfn *pfn)
+{
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	char *path = pfn->pfn_buf;
+	int len = pfn->buf_len;
+
+	if (snprintf(path, len, "%s/driver", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				ndctl_pfn_get_devname(pfn));
+		return 0;
+	}
+
+	return is_enabled(ndctl_pfn_get_bus(pfn), path);
+}
+
+NDCTL_EXPORT struct ndctl_region *ndctl_pfn_get_region(struct ndctl_pfn *pfn)
+{
+	return pfn->region;
+}
+
+NDCTL_EXPORT int ndctl_pfn_enable(struct ndctl_pfn *pfn)
+{
+	struct ndctl_region *region = ndctl_pfn_get_region(pfn);
+	const char *devname = ndctl_pfn_get_devname(pfn);
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	char *path = pfn->pfn_buf;
+	int len = pfn->buf_len;
+
+	if (ndctl_pfn_is_enabled(pfn))
+		return 0;
+
+	ndctl_bind(ctx, pfn->module, devname);
+
+	if (!ndctl_pfn_is_enabled(pfn)) {
+		err(ctx, "%s: failed to enable\n", devname);
+		return -ENXIO;
+	}
+
+	dbg(ctx, "%s: enabled\n", devname);
+
+	if (snprintf(path, len, "%s/block", pfn->pfn_path) >= len) {
+		err(ctx, "%s: buffer too small!\n",
+				devname);
+	} else {
+		pfn->bdev = get_block_device(ctx, path);
+	}
+
+	/*
+	 * Rescan now as successfully enabling a pfn device leads to a
+	 * new one being created, and potentially the backing namespace
+	 * as well.
+	 */
+	region_refresh_children(region);
+
+	return 0;
+}
+
+NDCTL_EXPORT int ndctl_pfn_delete(struct ndctl_pfn *pfn)
+{
+	struct ndctl_region *region = ndctl_pfn_get_region(pfn);
+	struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn);
+	int rc;
+
+	if (!ndctl_pfn_is_valid(pfn)) {
+		free_pfn(pfn, &region->stale_pfns);
+		return 0;
+	}
+
+	ndctl_unbind(ctx, pfn->pfn_path);
+
+	rc = ndctl_pfn_set_namespace(pfn, NULL);
+	if (rc) {
+		dbg(ctx, "%s: failed to clear namespace: %d\n",
+			ndctl_pfn_get_devname(pfn), rc);
+		return rc;
+	}
+
+	free_pfn(pfn, &region->pfns);
+	region->pfns_init = 0;
+
+	return 0;
+}
+
+NDCTL_EXPORT int ndctl_pfn_is_configured(struct ndctl_pfn *pfn)
+{
+	if (ndctl_pfn_get_namespace(pfn))
+		return 1;
+
+	if (ndctl_pfn_get_location(pfn) != NDCTL_PFN_LOC_NONE)
+		return 1;
+
+	if (memcmp(&pfn->uuid, null_uuid, sizeof(null_uuid)) != 0)
+		return 1;
+
+	return 0;
+}
diff --git a/lib/libndctl.sym b/lib/libndctl.sym
index 22fa12e29649..9cb2f0002c52 100644
--- a/lib/libndctl.sym
+++ b/lib/libndctl.sym
@@ -127,6 +127,7 @@  global:
 	ndctl_namespace_get_ctx;
 	ndctl_namespace_get_bus;
 	ndctl_namespace_get_btt;
+	ndctl_namespace_get_pfn;
 	ndctl_namespace_get_region;
 	ndctl_namespace_get_id;
 	ndctl_namespace_get_devname;
@@ -184,4 +185,26 @@  global:
 	ndctl_cmd_get_status;
 	ndctl_cmd_get_firmware_status;
 	ndctl_cmd_submit;
+	ndctl_region_get_pfn_seed;
+	ndctl_pfn_get_first;
+	ndctl_pfn_get_next;
+	ndctl_pfn_get_id;
+	ndctl_pfn_get_namespace;
+	ndctl_pfn_get_uuid;
+	ndctl_pfn_set_uuid;
+	ndctl_pfn_get_location;
+	ndctl_pfn_set_location;
+	ndctl_pfn_get_align;
+	ndctl_pfn_set_align;
+	ndctl_pfn_set_namespace;
+	ndctl_pfn_get_bus;
+	ndctl_pfn_get_ctx;
+	ndctl_pfn_get_devname;
+	ndctl_pfn_get_block_device;
+	ndctl_pfn_is_valid;
+	ndctl_pfn_is_enabled;
+	ndctl_pfn_get_region;
+	ndctl_pfn_enable;
+	ndctl_pfn_delete;
+	ndctl_pfn_is_configured;
 } LIBNDCTL_1;
diff --git a/lib/ndctl/libndctl.h b/lib/ndctl/libndctl.h
index 0bfec1291036..bf35f4ecf800 100644
--- a/lib/ndctl/libndctl.h
+++ b/lib/ndctl/libndctl.h
@@ -253,6 +253,7 @@  struct ndctl_namespace *ndctl_region_get_namespace_seed(
 int ndctl_region_get_ro(struct ndctl_region *region);
 int ndctl_region_set_ro(struct ndctl_region *region, int ro);
 struct ndctl_btt *ndctl_region_get_btt_seed(struct ndctl_region *region);
+struct ndctl_pfn *ndctl_region_get_pfn_seed(struct ndctl_region *region);
 unsigned int ndctl_region_get_nstype(struct ndctl_region *region);
 const char *ndctl_region_get_type_name(struct ndctl_region *region);
 struct ndctl_bus *ndctl_region_get_bus(struct ndctl_region *region);
@@ -326,11 +327,19 @@  struct ndctl_ctx *ndctl_namespace_get_ctx(struct ndctl_namespace *ndns);
 struct ndctl_bus *ndctl_namespace_get_bus(struct ndctl_namespace *ndns);
 struct ndctl_region *ndctl_namespace_get_region(struct ndctl_namespace *ndns);
 struct ndctl_btt *ndctl_namespace_get_btt(struct ndctl_namespace *ndns);
+struct ndctl_pfn *ndctl_namespace_get_pfn(struct ndctl_namespace *ndns);
 unsigned int ndctl_namespace_get_id(struct ndctl_namespace *ndns);
 const char *ndctl_namespace_get_devname(struct ndctl_namespace *ndns);
 unsigned int ndctl_namespace_get_type(struct ndctl_namespace *ndns);
 const char *ndctl_namespace_get_type_name(struct ndctl_namespace *ndns);
 const char *ndctl_namespace_get_block_device(struct ndctl_namespace *ndns);
+enum ndctl_namespace_mode {
+	NDCTL_NS_MODE_MEMORY,
+	NDCTL_NS_MODE_SAFE,
+	NDCTL_NS_MODE_RAW,
+};
+enum ndctl_namespace_mode ndctl_namespace_get_mode(
+		struct ndctl_namespace *ndns);
 int ndctl_namespace_is_enabled(struct ndctl_namespace *ndns);
 int ndctl_namespace_enable(struct ndctl_namespace *ndns);
 int ndctl_namespace_disable(struct ndctl_namespace *ndns);
@@ -388,6 +397,44 @@  int ndctl_btt_enable(struct ndctl_btt *btt);
 int ndctl_btt_delete(struct ndctl_btt *btt);
 int ndctl_btt_is_configured(struct ndctl_btt *btt);
 
+struct ndctl_pfn;
+struct ndctl_pfn *ndctl_pfn_get_first(struct ndctl_region *region);
+struct ndctl_pfn *ndctl_pfn_get_next(struct ndctl_pfn *pfn);
+#define ndctl_pfn_foreach(region, pfn) \
+        for (pfn = ndctl_pfn_get_first(region); \
+             pfn != NULL; \
+             pfn = ndctl_pfn_get_next(pfn))
+#define ndctl_pfn_foreach_safe(region, pfn, _pfn) \
+	for (pfn = ndctl_pfn_get_first(region), \
+	     _pfn = ndctl_pfn_get_next(pfn); \
+	     pfn != NULL; \
+	     pfn = _pfn, \
+	     _pfn = _pfn ? ndctl_pfn_get_next(_pfn) : NULL)
+struct ndctl_ctx *ndctl_pfn_get_ctx(struct ndctl_pfn *pfn);
+struct ndctl_bus *ndctl_pfn_get_bus(struct ndctl_pfn *pfn);
+struct ndctl_region *ndctl_pfn_get_region(struct ndctl_pfn *pfn);
+unsigned int ndctl_pfn_get_id(struct ndctl_pfn *pfn);
+int ndctl_pfn_is_enabled(struct ndctl_pfn *pfn);
+int ndctl_pfn_is_valid(struct ndctl_pfn *pfn);
+const char *ndctl_pfn_get_devname(struct ndctl_pfn *pfn);
+const char *ndctl_pfn_get_block_device(struct ndctl_pfn *pfn);
+enum ndctl_pfn_loc {
+	NDCTL_PFN_LOC_NONE,
+	NDCTL_PFN_LOC_RAM,
+	NDCTL_PFN_LOC_PMEM,
+};
+int ndctl_pfn_set_location(struct ndctl_pfn *pfn, enum ndctl_pfn_loc loc);
+enum ndctl_pfn_loc ndctl_pfn_get_location(struct ndctl_pfn *pfn);
+int ndctl_pfn_set_uuid(struct ndctl_pfn *pfn, uuid_t uu);
+void ndctl_pfn_get_uuid(struct ndctl_pfn *pfn, uuid_t uu);
+int ndctl_pfn_set_align(struct ndctl_pfn *pfn, unsigned long align);
+unsigned long ndctl_pfn_get_align(struct ndctl_pfn *pfn);
+int ndctl_pfn_set_namespace(struct ndctl_pfn *pfn, struct ndctl_namespace *ndns);
+struct ndctl_namespace *ndctl_pfn_get_namespace(struct ndctl_pfn *pfn);
+int ndctl_pfn_enable(struct ndctl_pfn *pfn);
+int ndctl_pfn_delete(struct ndctl_pfn *pfn);
+int ndctl_pfn_is_configured(struct ndctl_pfn *pfn);
+
 #ifdef __cplusplus
 } /* extern "C" */
 #endif