// SPDX-License-Identifier: LGPL-2.1
// Copyright (C) 2014-2020, Intel Corporation. All rights reserved.
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <syslog.h>
#include <libkmod.h>
#include <sys/wait.h>
#include <uuid/uuid.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/version.h>

#include <ccan/array_size/array_size.h>
#include <ndctl/libndctl.h>
#include <daxctl/libdaxctl.h>
#include <ndctl/ndctl.h>
#include <test.h>

#define BLKROGET _IO(0x12,94) /* get read-only status (0 = read_write) */
#define BLKROSET _IO(0x12,93) /* set device read-only (0 = read-write) */

/*
 * Kernel provider "nfit_test.0" produces an NFIT with the following attributes:
 *
 *                               (a)               (b)           DIMM
 *            +-------------------+--------+--------+--------+
 *  +------+  |       pm0.0       |  free  | pm1.0  |  free  |    0
 *  | imc0 +--+- - - region0- - - +--------+        +--------+
 *  +--+---+  |       pm0.0       |  free  | pm1.0  |  free  |    1
 *     |      +-------------------+--------v        v--------+
 *  +--+---+                               |                 |
 *  | cpu0 |                                     region1
 *  +--+---+                               |                 |
 *     |      +----------------------------^        ^--------+
 *  +--+---+  |           free             | pm1.0  |  free  |    2
 *  | imc1 +--+----------------------------|        +--------+
 *  +------+  |           free             | pm1.0  |  free  |    3
 *            +----------------------------+--------+--------+
 *
 * In this platform we have four DIMMs and two memory controllers in one
 * socket.  Each PMEM interleave set is identified by a region device with
 * a dynamically assigned id.
 *
 *    1. The first portion of DIMM0 and DIMM1 are interleaved as REGION0. A
 *       single PMEM namespace is created in the REGION0-SPA-range that spans most
 *       of DIMM0 and DIMM1 with a user-specified name of "pm0.0". Some of that
 *       interleaved system-physical-address range is left free for
 *       another PMEM namespace to be defined.
 *
 *    2. In the last portion of DIMM0 and DIMM1 we have an interleaved
 *       system-physical-address range, REGION1, that spans those two DIMMs as
 *       well as DIMM2 and DIMM3.  Some of REGION1 is allocated to a PMEM namespace
 *       named "pm1.0".
 *
 * Kernel provider "nfit_test.1" produces an NFIT with the following attributes:
 *
 * region2
 * +---------------------+
 * |---------------------|
 * ||       pm2.0       ||
 * |---------------------|
 * +---------------------+
 *
 * *) Describes a simple system-physical-address range with a non-aliasing backing
 *    dimm.
 */

static const char *NFIT_PROVIDER0 = "nfit_test.0";
static const char *NFIT_PROVIDER1 = "nfit_test.1";
#define SZ_4K   0x00001000
#define SZ_128K 0x00020000
#define SZ_7M   0x00700000
#define SZ_2M   0x00200000
#define SZ_8M   0x00800000
#define SZ_11M  0x00b00000
#define SZ_12M  0x00c00000
#define SZ_16M  0x01000000
#define SZ_18M  0x01200000
#define SZ_20M  0x01400000
#define SZ_27M  0x01b00000
#define SZ_28M  0x01c00000
#define SZ_32M  0x02000000
#define SZ_64M  0x04000000
#define SZ_1G   0x40000000

struct dimm {
	unsigned int handle;
	unsigned int phys_id;
	unsigned int subsystem_vendor;
	unsigned short manufacturing_date;
	unsigned char manufacturing_location;
	long long dirty_shutdown;
	union {
		unsigned long flags;
		struct {
			unsigned int f_arm:1;
			unsigned int f_save:1;
			unsigned int f_flush:1;
			unsigned int f_smart:1;
			unsigned int f_restore:1;
		};
	};
	int formats;
	int format[2];
};

#define DIMM_HANDLE(n, s, i, c, d) \
	(((n & 0xfff) << 16) | ((s & 0xf) << 12) | ((i & 0xf) << 8) \
	 | ((c & 0xf) << 4) | (d & 0xf))
static struct dimm dimms0[] = {
	{ DIMM_HANDLE(0, 0, 0, 0, 0), 0, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, },
	{ DIMM_HANDLE(0, 0, 0, 0, 1), 1, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, },
	{ DIMM_HANDLE(0, 0, 1, 0, 0), 2, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, },
	{ DIMM_HANDLE(0, 0, 1, 0, 1), 3, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, },
};

static struct dimm dimms1[] = {
	{
		DIMM_HANDLE(0, 0, 0, 0, 0), 0, 0, 2016, 10, 42, {
			.f_arm = 1,
			.f_save = 1,
			.f_flush = 1,
			.f_smart = 1,
			.f_restore = 1,
		},
		1, { 0x101, },
	},
};

static struct btt {
	int enabled;
	uuid_t uuid;
	int num_sector_sizes;
	unsigned int sector_sizes[7];
} default_btt = {
	0, { 0, }, 7, { 512, 520, 528, 4096, 4104, 4160, 4224, },
};

struct pfn {
	int enabled;
	uuid_t uuid;
	enum ndctl_pfn_loc locs[2];
	unsigned long aligns[4];
};

struct dax {
	int enabled;
	uuid_t uuid;
	enum ndctl_pfn_loc locs[2];
	unsigned long aligns[4];
};

static struct pfn_default {
	int enabled;
	uuid_t uuid;
	enum ndctl_pfn_loc loc;
	unsigned long align;
} default_pfn = {
	.enabled = 0,
	.uuid = { 0, },
	.loc = NDCTL_PFN_LOC_NONE,
	.align = SZ_2M,
};

struct region {
	union {
		unsigned int range_index;
		unsigned int handle;
	};
	unsigned int interleave_ways;
	int enabled;
	char *type;
	unsigned long long available_size;
	unsigned long long size;
	struct set {
		int active;
	} iset;
	struct btt *btts[2];
	struct pfn_default *pfns[2];
	struct namespace *namespaces[4];
};

static struct btt btt_settings = {
	.enabled = 1,
	.uuid = {  0,  1,  2,  3,  4,  5,  6,  7,
		   8, 9,  10, 11, 12, 13, 14, 15
	},
	.num_sector_sizes = 7,
	.sector_sizes =  { 512, 520, 528, 4096, 4104, 4160, 4224, },
};

static struct pfn pfn_settings = {
	.enabled = 1,
	.uuid = {  1,  2,  3,  4,  5,  6,  7, 0,
		   8, 9,  10, 11, 12, 13, 14, 15
	},
	.locs = { NDCTL_PFN_LOC_RAM, NDCTL_PFN_LOC_PMEM },
};

static struct dax dax_settings = {
	.enabled = 1,
	.uuid = {  1,  2,  3,  4,  5,  6,  7, 0,
		   8, 9,  10, 11, 12, 13, 14, 15
	},
	.locs = { NDCTL_PFN_LOC_RAM, NDCTL_PFN_LOC_PMEM },
};

struct namespace {
	unsigned int id;
	char *type;
	struct btt *btt_settings;
	struct pfn *pfn_settings;
	struct dax *dax_settings;
	unsigned long long size;
	uuid_t uuid;
	int do_configure;
	int check_alt_name;
	int ro;
	int num_sector_sizes;
	unsigned long *sector_sizes;
};

static uuid_t null_uuid;
static unsigned long pmem_sector_sizes[] = { 512, 4096 };
static unsigned long io_sector_sizes[] = { 0 };

static struct namespace namespace0_pmem0 = {
	0, "namespace_pmem", &btt_settings, &pfn_settings, &dax_settings, SZ_18M,
	{ 1, 1, 1, 1,
	  1, 1, 1, 1,
	  1, 1, 1, 1,
	  1, 1, 1, 1, }, 1, 1, 0,
	ARRAY_SIZE(pmem_sector_sizes), pmem_sector_sizes,
};

static struct namespace namespace1_pmem0 = {
	0, "namespace_pmem", &btt_settings, &pfn_settings, &dax_settings, SZ_20M,
	{ 2, 2, 2, 2,
	  2, 2, 2, 2,
	  2, 2, 2, 2,
	  2, 2, 2, 2, }, 1, 1, 0,
	ARRAY_SIZE(pmem_sector_sizes), pmem_sector_sizes,
};

static struct region regions0[] = {
	{ { 1 }, 2, 1, "pmem", SZ_32M, SZ_32M, { 1 },
		.namespaces = {
			[0] = &namespace0_pmem0,
		},
		.btts = {
			[0] = &default_btt,
		},
		.pfns = {
			[0] = &default_pfn,
		},
	},
	{ { 2 }, 4, 1, "pmem", SZ_64M, SZ_64M, { 1 },
		.namespaces = {
			[0] = &namespace1_pmem0,
		},
		.btts = {
			[0] = &default_btt,
		},
		.pfns = {
			[0] = &default_pfn,
		},
	},
};

static struct namespace namespace1 = {
	0, "namespace_io", &btt_settings, &pfn_settings, &dax_settings, SZ_32M,
	{ 0, 0, 0, 0,
	  0, 0, 0, 0,
	  0, 0, 0, 0,
	  0, 0, 0, 0, }, -1, 0, 1, ARRAY_SIZE(io_sector_sizes), io_sector_sizes,
};

static struct region regions1[] = {
	{ { 1 }, 1, 1, "pmem", 0, SZ_32M,
		.namespaces = {
			[0] = &namespace1,
		},
	},
};

static unsigned long dimm_commands0 = 1UL << ND_CMD_GET_CONFIG_SIZE
		| 1UL << ND_CMD_GET_CONFIG_DATA
		| 1UL << ND_CMD_SET_CONFIG_DATA | 1UL << ND_CMD_SMART
		| 1UL << ND_CMD_SMART_THRESHOLD;

#define CLEAR_ERROR_CMDS (1UL << ND_CMD_CLEAR_ERROR)

#define ARS_CMDS (1UL << ND_CMD_ARS_CAP | 1UL << ND_CMD_ARS_START \
		| 1UL << ND_CMD_ARS_STATUS)

static unsigned long bus_commands0 = CLEAR_ERROR_CMDS | ARS_CMDS;

static struct ndctl_dimm *get_dimm_by_handle(struct ndctl_bus *bus, unsigned int handle)
{
	struct ndctl_dimm *dimm;

	ndctl_dimm_foreach(bus, dimm)
		if (ndctl_dimm_get_handle(dimm) == handle)
			return dimm;

	return NULL;
}

static struct ndctl_btt *get_idle_btt(struct ndctl_region *region)
{
	struct ndctl_btt *btt;

	ndctl_btt_foreach(region, btt)
		if (!ndctl_btt_is_enabled(btt) && !ndctl_btt_is_configured(btt))
			return btt;

	return NULL;
}

static struct ndctl_pfn *get_idle_pfn(struct ndctl_region *region)
{
	struct ndctl_pfn *pfn;

	ndctl_pfn_foreach(region, pfn)
		if (!ndctl_pfn_is_enabled(pfn) && !ndctl_pfn_is_configured(pfn))
			return pfn;

	return NULL;
}

static struct ndctl_dax *get_idle_dax(struct ndctl_region *region)
{
	struct ndctl_dax *dax;

	ndctl_dax_foreach(region, dax)
		if (!ndctl_dax_is_enabled(dax) && !ndctl_dax_is_configured(dax))
			return dax;

	return NULL;
}

static struct ndctl_namespace *get_namespace_by_id(struct ndctl_region *region,
		struct namespace *namespace)
{
	struct ndctl_namespace *ndns;

	if (memcmp(namespace->uuid, null_uuid, sizeof(uuid_t)) != 0)
		ndctl_namespace_foreach(region, ndns) {
			uuid_t ndns_uuid;
			int cmp;

			ndctl_namespace_get_uuid(ndns, ndns_uuid);
			cmp = memcmp(ndns_uuid, namespace->uuid, sizeof(uuid_t));
			if (cmp == 0)
				return ndns;
		}

	/* fall back to nominal id if uuid is not configured yet */
	ndctl_namespace_foreach(region, ndns)
		if (ndctl_namespace_get_id(ndns) == namespace->id)
			return ndns;

	return NULL;
}

static struct ndctl_region *get_pmem_region_by_range_index(struct ndctl_bus *bus,
		unsigned int range_index)
{
	struct ndctl_region *region;

	ndctl_region_foreach(bus, region) {
		if (ndctl_region_get_type(region) != ND_DEVICE_REGION_PMEM)
			continue;
		if (ndctl_region_get_range_index(region) == range_index)
			return region;
	}
	return NULL;
}

enum ns_mode {
	BTT, PFN, DAX,
};
static int check_namespaces(struct ndctl_region *region,
		struct namespace **namespaces, enum ns_mode mode);
static int check_btts(struct ndctl_region *region, struct btt **btts);

static int check_regions(struct ndctl_bus *bus, struct region *regions, int n,
		enum ns_mode mode)
{
	struct ndctl_region *region;
	int i, rc = 0;

	for (i = 0; i < n; i++) {
		struct ndctl_interleave_set *iset;
		char devname[50];

		region = get_pmem_region_by_range_index(bus,
							regions[i].range_index);
		if (!region) {
			fprintf(stderr, "failed to find region type: %s ident: %x\n",
					regions[i].type, regions[i].handle);
			return -ENXIO;
		}

		snprintf(devname, sizeof(devname), "region%d",
				ndctl_region_get_id(region));
		if (strcmp(ndctl_region_get_type_name(region), regions[i].type) != 0) {
			fprintf(stderr, "%s: expected type: %s got: %s\n",
					devname, regions[i].type,
					ndctl_region_get_type_name(region));
			return -ENXIO;
		}
		if (ndctl_region_get_interleave_ways(region) != regions[i].interleave_ways) {
			fprintf(stderr, "%s: expected interleave_ways: %d got: %d\n",
					devname, regions[i].interleave_ways,
					ndctl_region_get_interleave_ways(region));
			return -ENXIO;
		}
		if (regions[i].enabled && !ndctl_region_is_enabled(region)) {
			fprintf(stderr, "%s: expected enabled by default\n",
					devname);
			return -ENXIO;
		}

		if (regions[i].available_size != ndctl_region_get_available_size(region)) {
			fprintf(stderr, "%s: expected available_size: %#llx got: %#llx\n",
					devname, regions[i].available_size,
					ndctl_region_get_available_size(region));
			return -ENXIO;
		}

		if (regions[i].size != ndctl_region_get_size(region)) {
			fprintf(stderr, "%s: expected size: %#llx got: %#llx\n",
					devname, regions[i].size,
					ndctl_region_get_size(region));
			return -ENXIO;
		}

		iset = ndctl_region_get_interleave_set(region);
		if (regions[i].iset.active
				&& !(iset && ndctl_interleave_set_is_active(iset) > 0)) {
			fprintf(stderr, "%s: expected interleave set active by default\n",
					devname);
			return -ENXIO;
		} else if (regions[i].iset.active == 0 && iset) {
			fprintf(stderr, "%s: expected no interleave set\n",
					devname);
			return -ENXIO;
		}

		if (ndctl_region_disable_invalidate(region) < 0) {
			fprintf(stderr, "%s: failed to disable\n", devname);
			return -ENXIO;
		}
		if (regions[i].enabled && ndctl_region_enable(region) < 0) {
			fprintf(stderr, "%s: failed to enable\n", devname);
			return -ENXIO;
		}

		rc = check_btts(region, regions[i].btts);
		if (rc)
			return rc;

		if (regions[i].namespaces[0])
			rc = check_namespaces(region, regions[i].namespaces,
					mode);
		if (rc)
			break;
	}

	if (rc == 0)
		ndctl_region_foreach(bus, region)
			ndctl_region_disable_invalidate(region);

	return rc;
}

static int validate_dax(struct ndctl_dax *dax)
{
	/* TODO: make nfit_test namespaces dax capable */
	struct ndctl_namespace *ndns = ndctl_dax_get_namespace(dax);
	const char *devname = ndctl_namespace_get_devname(ndns);
	struct ndctl_region *region = ndctl_dax_get_region(dax);
	struct ndctl_ctx *ctx = ndctl_dax_get_ctx(dax);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	struct daxctl_region *dax_region = NULL, *found;
	int rc = -ENXIO, fd, count, dax_expect;
	struct daxctl_dev *dax_dev, *seed;
	struct daxctl_ctx *dax_ctx;
	uuid_t uuid, region_uuid;
	char devpath[50];

	dax_region = ndctl_dax_get_daxctl_region(dax);
	if (!dax_region) {
		fprintf(stderr, "%s: failed to retrieve daxctl_region\n",
				devname);
		return -ENXIO;
	}

	dax_ctx = ndctl_get_daxctl_ctx(ctx);
	count = 0;
	daxctl_region_foreach(dax_ctx, found)
		if (found == dax_region)
			count++;
	if (count != 1) {
		fprintf(stderr, "%s: failed to iterate to single region instance\n",
				devname);
		return -ENXIO;
	}

	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 10, 0))) {
		if (daxctl_region_get_size(dax_region)
				!= ndctl_dax_get_size(dax)) {
			fprintf(stderr, "%s: expect size: %llu != %llu\n",
					devname, ndctl_dax_get_size(dax),
					daxctl_region_get_size(dax_region));
			return -ENXIO;
		}

		if (daxctl_region_get_align(dax_region)
				!= ndctl_dax_get_align(dax)) {
			fprintf(stderr, "%s: expect align: %lu != %lu\n",
					devname, ndctl_dax_get_align(dax),
					daxctl_region_get_align(dax_region));
			return -ENXIO;
		}
	}

	rc = -ENXIO;
	ndctl_dax_get_uuid(dax, uuid);
	daxctl_region_get_uuid(dax_region, region_uuid);
	if (uuid_compare(uuid, region_uuid) != 0) {
		char expect[40], actual[40];

		uuid_unparse(region_uuid, actual);
		uuid_unparse(uuid, expect);
		fprintf(stderr, "%s: expected uuid: %s got: %s\n",
				devname, expect, actual);
		goto out;
	}

	if ((int) ndctl_region_get_id(region)
			!= daxctl_region_get_id(dax_region)) {
		fprintf(stderr, "%s: expected region id: %d got: %d\n",
				devname, ndctl_region_get_id(region),
				daxctl_region_get_id(dax_region));
		goto out;
	}

	dax_dev = daxctl_dev_get_first(dax_region);
	if (!dax_dev) {
		fprintf(stderr, "%s: failed to find daxctl_dev\n",
				devname);
		goto out;
	}

	seed = daxctl_region_get_dev_seed(dax_region);
	if (dax_dev != seed && daxctl_dev_get_size(dax_dev) <= 0) {
		fprintf(stderr, "%s: expected non-zero sized dax device\n",
				devname);
		goto out;
	}

	sprintf(devpath, "/dev/%s", daxctl_dev_get_devname(dax_dev));
	fd = open(devpath, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "%s: failed to open %s\n", devname, devpath);
		goto out;
	}
	close(fd);

	count = 0;
	daxctl_dev_foreach(dax_region, dax_dev)
		count++;
	dax_expect = seed ? 2 : 1;
	if (count != dax_expect) {
		fprintf(stderr, "%s: expected %d dax device%s, got %d\n",
				devname, dax_expect, dax_expect == 1 ? "" : "s",
				count);
		rc = -ENXIO;
		goto out;
	}

	rc = 0;

 out:
	daxctl_region_unref(dax_region);
	return rc;
}

static int __check_dax_create(struct ndctl_region *region,
		struct ndctl_namespace *ndns, struct namespace *namespace,
		enum ndctl_pfn_loc loc, uuid_t uuid)
{
	struct ndctl_dax *dax_seed = ndctl_region_get_dax_seed(region);
	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	enum ndctl_namespace_mode mode;
	struct ndctl_dax *dax;
	const char *devname;
	ssize_t rc;

	dax = get_idle_dax(region);
	if (!dax)
		return -ENXIO;

	devname = ndctl_dax_get_devname(dax);
	ndctl_dax_set_uuid(dax, uuid);
	ndctl_dax_set_location(dax, loc);
	/*
	 * nfit_test uses vmalloc()'d resources so the only feasible
	 * alignment is PAGE_SIZE
	 */
	ndctl_dax_set_align(dax, SZ_4K);

	rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_DAX);
	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) {
		fprintf(stderr, "%s: failed to enforce dax mode\n", devname);
		return rc;
	}
	ndctl_dax_set_namespace(dax, ndns);
	rc = ndctl_dax_enable(dax);
	if (rc) {
		fprintf(stderr, "%s: failed to enable dax\n", devname);
		return rc;
	}

	mode = ndctl_namespace_get_mode(ndns);
	if (mode >= 0 && mode != NDCTL_NS_MODE_DAX)
		fprintf(stderr, "%s: expected dax mode got: %d\n",
				devname, mode);

	if (namespace->ro == (rc == 0)) {
		fprintf(stderr, "%s: expected dax enable %s, %s read-%s\n",
				devname,
				namespace->ro ? "failure" : "success",
				ndctl_region_get_devname(region),
				namespace->ro ? "only" : "write");
		return -ENXIO;
	}

	if (dax_seed == ndctl_region_get_dax_seed(region)
			&& dax == dax_seed) {
		fprintf(stderr, "%s: failed to advance dax seed\n",
				ndctl_region_get_devname(region));
		return -ENXIO;
	}

	if (namespace->ro) {
		ndctl_region_set_ro(region, 0);
		rc = ndctl_dax_enable(dax);
		fprintf(stderr, "%s: failed to enable after setting rw\n",
				devname);
		ndctl_region_set_ro(region, 1);
		return -ENXIO;
	}

	rc = validate_dax(dax);
	if (rc) {
		fprintf(stderr, "%s: %s validate_dax failed\n", __func__,
				devname);
		return rc;
	}

	if (namespace->ro)
		ndctl_region_set_ro(region, 1);

	rc = ndctl_dax_delete(dax);
	if (rc)
		fprintf(stderr, "%s: failed to delete dax (%zd)\n", devname, rc);
	return rc;
}

static int check_dax_create(struct ndctl_region *region,
		struct ndctl_namespace *ndns, struct namespace *namespace)
{
	struct dax *dax_s = namespace->dax_settings;
	void *buf = NULL;
	unsigned int i;
	int rc = 0;

	if (!dax_s)
		return 0;

	for (i = 0; i < ARRAY_SIZE(dax_s->locs); i++) {
		/*
		 * The kernel enforces invalidating the previous info
		 * block when the current uuid is does not validate with
		 * the contents of the info block.
		 */
		dax_s->uuid[0]++;
		rc = __check_dax_create(region, ndns, namespace,
				dax_s->locs[i], dax_s->uuid);
		if (rc)
			break;
	}
	free(buf);
	return rc;
}

static int __check_pfn_create(struct ndctl_region *region,
		struct ndctl_namespace *ndns, struct namespace *namespace,
		void *buf, enum ndctl_pfn_loc loc, uuid_t uuid)
{
	struct ndctl_pfn *pfn_seed = ndctl_region_get_pfn_seed(region);
	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	enum ndctl_namespace_mode mode;
	struct ndctl_pfn *pfn;
	const char *devname;
	int fd, retry = 10;
	char bdevpath[50];
	ssize_t rc;

	pfn = get_idle_pfn(region);
	if (!pfn)
		return -ENXIO;

	devname = ndctl_pfn_get_devname(pfn);
	ndctl_pfn_set_uuid(pfn, uuid);
	ndctl_pfn_set_location(pfn, loc);
	/*
	 * nfit_test uses vmalloc()'d resources so the only feasible
	 * alignment is PAGE_SIZE
	 */
	ndctl_pfn_set_align(pfn, SZ_4K);
	rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_MEMORY);
	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) {
		fprintf(stderr, "%s: failed to enforce pfn mode\n", devname);
		return rc;
	}
	ndctl_pfn_set_namespace(pfn, ndns);
	rc = ndctl_pfn_enable(pfn);
	if (rc) {
		fprintf(stderr, "%s: failed to enable pfn\n", devname);
		return rc;
	}

	mode = ndctl_namespace_get_mode(ndns);
	if (mode >= 0 && mode != NDCTL_NS_MODE_MEMORY)
		fprintf(stderr, "%s: expected fsdax mode got: %d\n",
				devname, mode);

	if (namespace->ro == (rc == 0)) {
		fprintf(stderr, "%s: expected pfn enable %s, %s read-%s\n",
				devname,
				namespace->ro ? "failure" : "success",
				ndctl_region_get_devname(region),
				namespace->ro ? "only" : "write");
		return -ENXIO;
	}

	if (pfn_seed == ndctl_region_get_pfn_seed(region)
			&& pfn == pfn_seed) {
		fprintf(stderr, "%s: failed to advance pfn seed\n",
				ndctl_region_get_devname(region));
		return -ENXIO;
	}

	if (namespace->ro) {
		ndctl_region_set_ro(region, 0);
		rc = ndctl_pfn_enable(pfn);
		fprintf(stderr, "%s: failed to enable after setting rw\n",
				devname);
		ndctl_region_set_ro(region, 1);
		return -ENXIO;
	}

	sprintf(bdevpath, "/dev/%s", ndctl_pfn_get_block_device(pfn));
	rc = -ENXIO;
	fd = open(bdevpath, O_RDWR|O_DIRECT);
	if (fd < 0)
		fprintf(stderr, "%s: failed to open %s\n",
				devname, bdevpath);

	while (fd >= 0) {
		rc = pread(fd, buf, 4096, 0);
		if (rc < 4096) {
			/* TODO: track down how this happens! */
			if (errno == ENOENT && retry--) {
				usleep(5000);
				continue;
			}
			fprintf(stderr, "%s: failed to read %s: %d %zd (%s)\n",
					devname, bdevpath, -errno, rc,
					strerror(errno));
			rc = -ENXIO;
			break;
		}
		if (write(fd, buf, 4096) < 4096) {
			fprintf(stderr, "%s: failed to write %s\n",
					devname, bdevpath);
			rc = -ENXIO;
			break;
		}
		rc = 0;
		break;
	}
	if (namespace->ro)
		ndctl_region_set_ro(region, 1);
	if (fd >= 0)
		close(fd);

	if (rc)
		return rc;

	rc = ndctl_pfn_delete(pfn);
	if (rc)
		fprintf(stderr, "%s: failed to delete pfn (%zd)\n", devname, rc);
	return rc;
}

static int check_pfn_create(struct ndctl_region *region,
		struct ndctl_namespace *ndns, struct namespace *namespace)
{
	struct pfn *pfn_s = namespace->pfn_settings;
	void *buf = NULL;
	unsigned int i;
	int rc = 0;

	if (!pfn_s)
		return 0;

	if (posix_memalign(&buf, 4096, 4096) != 0)
		return -ENXIO;

	for (i = 0; i < ARRAY_SIZE(pfn_s->locs); i++) {
		/*
		 * The kernel enforces invalidating the previous info
		 * block when the current uuid is does not validate with
		 * the contents of the info block.
		 */
		pfn_s->uuid[0]++;
		rc = __check_pfn_create(region, ndns, namespace, buf,
				pfn_s->locs[i], pfn_s->uuid);
		if (rc)
			break;
	}
	free(buf);
	return rc;
}

static int check_btt_size(struct ndctl_btt *btt)
{
	unsigned long long ns_size;
	unsigned long sect_size;
	unsigned long long actual, expect;
	int size_select, sect_select;
	struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	struct ndctl_namespace *ndns = ndctl_btt_get_namespace(btt);
	unsigned long long expect_table[][2] = {
		[0] = {
			[0] = 0x11b5400,
			[1] = 0x8daa000,
		},
		[1] = {
			[0] = 0x13b1400,
			[1] = 0x9d8a000,
		},
		[2] = {
			[0] = 0x1aa3600,
			[1] = 0xd51b000,
		},
	};

	if (!ndns)
		return -ENXIO;

	ns_size = ndctl_namespace_get_size(ndns);
	sect_size = ndctl_btt_get_sector_size(btt);

	if (sect_size >= SZ_4K)
		sect_select = 1;
	else if (sect_size >= 512)
		sect_select = 0;
	else {
		fprintf(stderr, "%s: %s unexpected sector size: %lx\n",
				__func__, ndctl_btt_get_devname(btt),
				sect_size);
		return -ENXIO;
	}

	switch (ns_size) {
	case SZ_18M:
		size_select = 0;
		break;
	case SZ_20M:
		size_select = 1;
		break;
	case SZ_27M:
		size_select = 2;
		break;
	default:
		fprintf(stderr, "%s: %s unexpected namespace size: %llx\n",
				__func__, ndctl_namespace_get_devname(ndns),
				ns_size);
		return -ENXIO;
	}

	/* prior to 4.8 btt devices did not have a size attribute */
	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0)))
		return 0;

	expect = expect_table[size_select][sect_select];
	actual = ndctl_btt_get_size(btt);
	if (expect != actual) {
		fprintf(stderr, "%s: namespace: %s unexpected size: %llx (expected: %llx)\n",
				ndctl_btt_get_devname(btt),
				ndctl_namespace_get_devname(ndns), actual, expect);
		return -ENXIO;
	}

	return 0;
}

static int check_btt_create(struct ndctl_region *region, struct ndctl_namespace *ndns,
		struct namespace *namespace)
{
	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	struct btt *btt_s = namespace->btt_settings;
	int i, fd, retry = 10;
	struct ndctl_btt *btt;
	const char *devname;
	char bdevpath[50];
	void *buf = NULL;
	ssize_t rc = 0;

	if (!namespace->btt_settings)
		return 0;

	if (posix_memalign(&buf, 4096, 4096) != 0)
		return -ENXIO;

	for (i = 0; i < btt_s->num_sector_sizes; i++) {
		struct ndctl_btt *btt_seed = ndctl_region_get_btt_seed(region);
		enum ndctl_namespace_mode mode;

		btt = get_idle_btt(region);
		if (!btt)
			goto err;

		devname = ndctl_btt_get_devname(btt);
		ndctl_btt_set_uuid(btt, btt_s->uuid);
		ndctl_btt_set_sector_size(btt, btt_s->sector_sizes[i]);
		rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_SECTOR);
		if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) {
			fprintf(stderr, "%s: failed to enforce btt mode\n", devname);
			goto err;
		}

		ndctl_btt_set_namespace(btt, ndns);
		rc = ndctl_btt_enable(btt);
		if (namespace->ro == (rc == 0)) {
			fprintf(stderr, "%s: expected btt enable %s, %s read-%s\n",
					devname,
					namespace->ro ? "failure" : "success",
					ndctl_region_get_devname(region),
					namespace->ro ? "only" : "write");
			goto err;
		}

		/* prior to v4.5 the mode attribute did not exist */
		if (ndctl_test_attempt(test, KERNEL_VERSION(4, 5, 0))) {
			mode = ndctl_namespace_get_mode(ndns);
			if (mode >= 0 && mode != NDCTL_NS_MODE_SECTOR)
				fprintf(stderr, "%s: expected safe mode got: %d\n",
						devname, mode);
		}

		/* prior to v4.13 the expected sizes were different due to BTT1.1 */
		if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))) {
			rc = check_btt_size(btt);
			if (rc)
				goto err;
		}

		if (btt_seed == ndctl_region_get_btt_seed(region)
				&& btt == btt_seed) {
			fprintf(stderr, "%s: failed to advance btt seed\n",
					ndctl_region_get_devname(region));
			goto err;
		}

		if (namespace->ro) {
			ndctl_region_set_ro(region, 0);
			rc = ndctl_btt_enable(btt);
			fprintf(stderr, "%s: failed to enable after setting rw\n",
					devname);
			ndctl_region_set_ro(region, 1);
			goto err;
		}

		sprintf(bdevpath, "/dev/%s", ndctl_btt_get_block_device(btt));
		rc = -ENXIO;
		fd = open(bdevpath, O_RDWR|O_DIRECT);
		if (fd < 0)
			fprintf(stderr, "%s: failed to open %s\n",
					devname, bdevpath);

		while (fd >= 0) {
			rc = pread(fd, buf, 4096, 0);
			if (rc < 4096) {
				/* TODO: track down how this happens! */
				if (errno == ENOENT && retry--) {
					usleep(5000);
					continue;
				}
				fprintf(stderr, "%s: failed to read %s: %d %zd (%s)\n",
						devname, bdevpath, -errno, rc,
						strerror(errno));
				rc = -ENXIO;
				break;
			}
			if (write(fd, buf, 4096) < 4096) {
				fprintf(stderr, "%s: failed to write %s\n",
						devname, bdevpath);
				rc = -ENXIO;
				break;
			}
			rc = 0;
			break;
		}
		if (namespace->ro)
			ndctl_region_set_ro(region, 1);
		if (fd >= 0)
			close(fd);

		if (rc)
			break;

		rc = ndctl_btt_delete(btt);
		if (rc)
			fprintf(stderr, "%s: failed to delete btt (%zd)\n", devname, rc);
	}
	free(buf);
	return rc;
 err:
	free(buf);
	return -ENXIO;
}

static int configure_namespace(struct ndctl_region *region,
		struct ndctl_namespace *ndns, struct namespace *namespace,
		unsigned long lbasize, enum ns_mode mode)
{
	char devname[50];
	int rc;

	if (namespace->do_configure <= 0)
		return 0;

	snprintf(devname, sizeof(devname), "namespace%d.%d",
			ndctl_region_get_id(region), namespace->id);

	if (!ndctl_namespace_is_configured(ndns)) {
		rc = ndctl_namespace_set_uuid(ndns, namespace->uuid);
		if (rc)
			fprintf(stderr, "%s: set_uuid failed: %d\n", devname, rc);
		rc = ndctl_namespace_set_alt_name(ndns, devname);
		if (rc)
			fprintf(stderr, "%s: set_alt_name failed: %d\n", devname, rc);
		rc = ndctl_namespace_set_size(ndns, namespace->size);
		if (rc)
			fprintf(stderr, "%s: set_size failed: %d\n", devname, rc);
	}

	if (lbasize) {
		rc = ndctl_namespace_set_sector_size(ndns, lbasize);
		if (rc)
			fprintf(stderr, "%s: set_sector_size (%lu) failed: %d\n",
					devname, lbasize, rc);
	}

	rc = ndctl_namespace_is_configured(ndns);
	if (rc < 1)
		fprintf(stderr, "%s: is_configured: %d\n", devname, rc);

	if (mode == BTT) {
		rc = check_btt_create(region, ndns, namespace);
		if (rc < 0) {
			fprintf(stderr, "%s: failed to create btt\n", devname);
			return rc;
		}
	}

	if (mode == PFN) {
		rc = check_pfn_create(region, ndns, namespace);
		if (rc < 0) {
			fprintf(stderr, "%s: failed to create pfn\n", devname);
			return rc;
		}
	}

	if (mode == DAX) {
		rc = check_dax_create(region, ndns, namespace);
		if (rc < 0) {
			fprintf(stderr, "%s: failed to create dax\n", devname);
			return rc;
		}
	}

	rc = ndctl_namespace_enable(ndns);
	if (rc < 0)
		fprintf(stderr, "%s: enable: %d\n", devname, rc);

	return rc;
}

static int check_pfn_autodetect(struct ndctl_bus *bus,
		struct ndctl_namespace *ndns, void *buf,
		struct namespace *namespace)
{
	struct ndctl_region *region = ndctl_namespace_get_region(ndns);
	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
	const char *devname = ndctl_namespace_get_devname(ndns);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	struct pfn *auto_pfn = namespace->pfn_settings;
	struct ndctl_pfn *pfn, *found = NULL;
	enum ndctl_namespace_mode mode;
	ssize_t rc = -ENXIO;
	char bdev[50];
	int fd, ro;

	ndctl_pfn_foreach(region, pfn) {
		struct ndctl_namespace *pfn_ndns;
		uuid_t uu;

		ndctl_pfn_get_uuid(pfn, uu);
		if (uuid_compare(uu, auto_pfn->uuid) != 0)
			continue;
		if (!ndctl_pfn_is_enabled(pfn))
			continue;
		pfn_ndns = ndctl_pfn_get_namespace(pfn);
		if (strcmp(ndctl_namespace_get_devname(pfn_ndns), devname) != 0)
			continue;
		fprintf(stderr, "%s: pfn_ndns: %p ndns: %p\n", __func__,
				pfn_ndns, ndns);
		found = pfn;
		break;
	}

	if (!found)
		return -ENXIO;

	mode = ndctl_namespace_get_enforce_mode(ndns);
	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))
			&& mode != NDCTL_NS_MODE_MEMORY) {
		fprintf(stderr, "%s expected enforce_mode pfn\n", devname);
		return -ENXIO;
	}

	sprintf(bdev, "/dev/%s", ndctl_pfn_get_block_device(pfn));
	fd = open(bdev, O_RDONLY);
	if (fd < 0)
		return -ENXIO;
	rc = ioctl(fd, BLKROGET, &ro);
	if (rc < 0) {
		fprintf(stderr, "%s: failed to open %s\n", __func__, bdev);
		rc = -ENXIO;
		goto out;
	}
	close(fd);
	fd = -1;

	rc = -ENXIO;
	if (ro != namespace->ro) {
		fprintf(stderr, "%s: read-%s expected read-%s by default\n",
				bdev, ro ? "only" : "write",
				namespace->ro ? "only" : "write");
		goto out;
	}

	/* destroy pfn device */
	ndctl_pfn_delete(found);

	/* clear read-write, and enable raw mode */
	ndctl_region_set_ro(region, 0);
	ndctl_namespace_set_raw_mode(ndns, 1);
	ndctl_namespace_enable(ndns);

	/* destroy pfn metadata */
	sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns));
	fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL);
	if (fd < 0) {
		fprintf(stderr, "%s: failed to open %s to destroy pfn\n",
				devname, bdev);
		goto out;
	}

	memset(buf, 0, 4096);
	rc = pwrite(fd, buf, 4096, 4096);
	if (rc < 4096) {
		rc = -ENXIO;
		fprintf(stderr, "%s: failed to overwrite pfn on %s\n",
				devname, bdev);
	}
 out:
	ndctl_region_set_ro(region, namespace->ro);
	ndctl_namespace_set_raw_mode(ndns, 0);
	if (fd >= 0)
		close(fd);

	return rc;
}

static int check_dax_autodetect(struct ndctl_bus *bus,
		struct ndctl_namespace *ndns, void *buf,
		struct namespace *namespace)
{
	struct ndctl_region *region = ndctl_namespace_get_region(ndns);
	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
	const char *devname = ndctl_namespace_get_devname(ndns);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	struct dax *auto_dax = namespace->dax_settings;
	struct ndctl_dax *dax, *found = NULL;
	enum ndctl_namespace_mode mode;
	ssize_t rc = -ENXIO;
	char bdev[50];
	int fd;

	ndctl_dax_foreach(region, dax) {
		struct ndctl_namespace *dax_ndns;
		uuid_t uu;

		ndctl_dax_get_uuid(dax, uu);
		if (uuid_compare(uu, auto_dax->uuid) != 0)
			continue;
		if (!ndctl_dax_is_enabled(dax))
			continue;
		dax_ndns = ndctl_dax_get_namespace(dax);
		if (strcmp(ndctl_namespace_get_devname(dax_ndns), devname) != 0)
			continue;
		fprintf(stderr, "%s: dax_ndns: %p ndns: %p\n", __func__,
				dax_ndns, ndns);
		found = dax;
		break;
	}

	if (!found)
		return -ENXIO;

	mode = ndctl_namespace_get_enforce_mode(ndns);
	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))
			&& mode != NDCTL_NS_MODE_DAX) {
		fprintf(stderr, "%s expected enforce_mode dax\n", devname);
		return -ENXIO;
	}

	rc = validate_dax(dax);
	if (rc) {
		fprintf(stderr, "%s: %s validate_dax failed\n", __func__,
				devname);
		return rc;
	}

	rc = -ENXIO;

	/* destroy dax device */
	ndctl_dax_delete(found);

	/* clear read-write, and enable raw mode */
	ndctl_region_set_ro(region, 0);
	ndctl_namespace_set_raw_mode(ndns, 1);
	ndctl_namespace_enable(ndns);

	/* destroy dax metadata */
	sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns));
	fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL);
	if (fd < 0) {
		fprintf(stderr, "%s: failed to open %s to destroy dax\n",
				devname, bdev);
		goto out;
	}

	memset(buf, 0, 4096);
	rc = pwrite(fd, buf, 4096, 4096);
	if (rc < 4096) {
		rc = -ENXIO;
		fprintf(stderr, "%s: failed to overwrite dax on %s\n",
				devname, bdev);
	}
 out:
	ndctl_region_set_ro(region, namespace->ro);
	ndctl_namespace_set_raw_mode(ndns, 0);
	if (fd >= 0)
		close(fd);

	return rc;
}

static int check_btt_autodetect(struct ndctl_bus *bus,
		struct ndctl_namespace *ndns, void *buf,
		struct namespace *namespace)
{
	struct ndctl_region *region = ndctl_namespace_get_region(ndns);
	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
	const char *devname = ndctl_namespace_get_devname(ndns);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	struct btt *auto_btt = namespace->btt_settings;
	struct ndctl_btt *btt, *found = NULL;
	enum ndctl_namespace_mode mode;
	ssize_t rc = -ENXIO;
	char bdev[50];
	int fd, ro;

	ndctl_btt_foreach(region, btt) {
		struct ndctl_namespace *btt_ndns;
		uuid_t uu;

		ndctl_btt_get_uuid(btt, uu);
		if (uuid_compare(uu, auto_btt->uuid) != 0)
			continue;
		if (!ndctl_btt_is_enabled(btt))
			continue;
		btt_ndns = ndctl_btt_get_namespace(btt);
		if (!btt_ndns || strcmp(ndctl_namespace_get_devname(btt_ndns), devname) != 0)
			continue;
		fprintf(stderr, "%s: btt_ndns: %p ndns: %p\n", __func__,
				btt_ndns, ndns);
		found = btt;
		break;
	}

	if (!found)
		return -ENXIO;

	mode = ndctl_namespace_get_enforce_mode(ndns);
	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))
			&& mode != NDCTL_NS_MODE_SECTOR) {
		fprintf(stderr, "%s expected enforce_mode btt\n", devname);
		return -ENXIO;
	}

	sprintf(bdev, "/dev/%s", ndctl_btt_get_block_device(btt));
	fd = open(bdev, O_RDONLY);
	if (fd < 0)
		return -ENXIO;
	rc = ioctl(fd, BLKROGET, &ro);
	if (rc < 0) {
		fprintf(stderr, "%s: failed to open %s\n", __func__, bdev);
		rc = -ENXIO;
		goto out;
	}
	close(fd);
	fd = -1;

	rc = -ENXIO;
	if (ro != namespace->ro) {
		fprintf(stderr, "%s: read-%s expected read-%s by default\n",
				bdev, ro ? "only" : "write",
				namespace->ro ? "only" : "write");
		goto out;
	}

	/* destroy btt device */
	ndctl_btt_delete(found);

	/* clear read-write, and enable raw mode */
	ndctl_region_set_ro(region, 0);
	ndctl_namespace_set_raw_mode(ndns, 1);
	ndctl_namespace_enable(ndns);

	/* destroy btt metadata */
	sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns));
	fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL);
	if (fd < 0) {
		fprintf(stderr, "%s: failed to open %s to destroy btt\n",
				devname, bdev);
		goto out;
	}

	memset(buf, 0, 4096);
	/* Delete both the first and second 4K pages */
	rc = pwrite(fd, buf, 4096, 4096);
	if (rc < 4096) {
		rc = -ENXIO;
		fprintf(stderr, "%s: failed to overwrite btt on %s\n",
				devname, bdev);
		goto out;
	}
	rc = pwrite(fd, buf, 4096, 0);
	if (rc < 4096) {
		rc = -ENXIO;
		fprintf(stderr, "%s: failed to overwrite btt on %s\n",
				devname, bdev);
	}
 out:
	ndctl_region_set_ro(region, namespace->ro);
	ndctl_namespace_set_raw_mode(ndns, 0);
	if (fd >= 0)
		close(fd);

	return rc;
}

static int validate_bdev(const char *devname, struct ndctl_btt *btt,
		struct ndctl_pfn *pfn, struct ndctl_namespace *ndns,
		struct namespace *namespace, void *buf)
{
	struct ndctl_region *region = ndctl_namespace_get_region(ndns);
	char bdevpath[50];
	int fd, rc, ro;

	if (btt)
		sprintf(bdevpath, "/dev/%s",
				ndctl_btt_get_block_device(btt));
	else if (pfn)
		sprintf(bdevpath, "/dev/%s",
				ndctl_pfn_get_block_device(pfn));
	else
		sprintf(bdevpath, "/dev/%s",
				ndctl_namespace_get_block_device(ndns));

	fd = open(bdevpath, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "%s: failed to open(%s, O_RDONLY)\n",
				devname, bdevpath);
		return -ENXIO;
	}

	rc = ioctl(fd, BLKROGET, &ro);
	if (rc < 0) {
		fprintf(stderr, "%s: BLKROGET failed\n",
				devname);
		rc = -errno;
		goto out;
	}

	if (namespace->ro != ro) {
		fprintf(stderr, "%s: read-%s expected: read-%s\n",
				devname, ro ? "only" : "write",
				namespace->ro ? "only" : "write");
		rc = -ENXIO;
		goto out;
	}

	ro = 0;
	rc = ndctl_region_set_ro(region, ro);
	if (rc < 0) {
		fprintf(stderr, "%s: ndctl_region_set_ro failed\n", devname);
		rc = -errno;
		goto out;
	}

	rc = ioctl(fd, BLKROSET, &ro);
	if (rc < 0) {
		fprintf(stderr, "%s: BLKROSET failed\n",
				devname);
		rc = -errno;
		goto out;
	}

	close(fd);
	fd = open(bdevpath, O_RDWR|O_DIRECT);
	if (fd < 0) {
		fprintf(stderr, "%s: failed to open(%s, O_RDWR|O_DIRECT)\n",
				devname, bdevpath);
		return -ENXIO;
	}
	if (read(fd, buf, 4096) < 4096) {
		fprintf(stderr, "%s: failed to read %s\n",
				devname, bdevpath);
		rc = -ENXIO;
		goto out;
	}
	if (write(fd, buf, 4096) < 4096) {
		fprintf(stderr, "%s: failed to write %s\n",
				devname, bdevpath);
		rc = -ENXIO;
		goto out;
	}

	rc = ndctl_region_set_ro(region, namespace->ro);
	if (rc < 0) {
		fprintf(stderr, "%s: ndctl_region_set_ro reset failed\n", devname);
		rc = -errno;
		goto out;
	}

	rc = 0;
out:
	close(fd);
	return rc;
}

static int validate_write_cache(struct ndctl_namespace *ndns)
{
	const char *devname = ndctl_namespace_get_devname(ndns);
	int wc, mode, type, rc;

	type = ndctl_namespace_get_type(ndns);
	mode = ndctl_namespace_get_mode(ndns);
	wc = ndctl_namespace_write_cache_is_enabled(ndns);

	if ((type == ND_DEVICE_NAMESPACE_PMEM || type == ND_DEVICE_NAMESPACE_IO) &&
			(mode == NDCTL_NS_MODE_FSDAX ||	mode == NDCTL_NS_MODE_RAW)) {
		if (wc != 1) {
			fprintf(stderr, "%s: expected write_cache enabled\n",
				devname);
			return -ENXIO;
		}
		rc = ndctl_namespace_disable_write_cache(ndns);
		if (rc) {
			fprintf(stderr, "%s: failed to disable write_cache\n",
				devname);
			return rc;
		}
		rc = ndctl_namespace_write_cache_is_enabled(ndns);
		if (rc != 0) {
			fprintf(stderr, "%s: write_cache could not be disabled\n",
				devname);
			return rc;
		}
		rc = ndctl_namespace_enable_write_cache(ndns);
		if (rc) {
			fprintf(stderr, "%s: failed to re-enable write_cache\n",
				devname);
			return rc;
		}
		rc = ndctl_namespace_write_cache_is_enabled(ndns);
		if (rc != 1) {
			fprintf(stderr, "%s: write_cache could not be re-enabled\n",
				devname);
			return rc;
		}
	} else {
		if (wc == 0 || wc == 1) {
			fprintf(stderr, "%s: expected write_cache to be absent\n",
				devname);
			return -ENXIO;
		}
	}
	return 0;
}

static int check_namespaces(struct ndctl_region *region,
		struct namespace **namespaces, enum ns_mode mode)
{
	struct ndctl_ctx *ctx = ndctl_region_get_ctx(region);
	struct ndctl_test *test = ndctl_get_private_data(ctx);
	struct ndctl_bus *bus = ndctl_region_get_bus(region);
	struct ndctl_namespace **ndns_save;
	struct namespace *namespace;
	int i, j, rc, retry_cnt = 1;
	void *buf = NULL, *__ndns_save;
	char devname[50];

	if (posix_memalign(&buf, 4096, 4096) != 0)
		return -ENOMEM;

	for (i = 0; (namespace = namespaces[i]); i++)
		if (namespace->do_configure >= 0)
			namespace->do_configure = 1;

 retry:
	ndns_save = NULL;
	for (i = 0; (namespace = namespaces[i]); i++) {
		uuid_t uu;
		struct ndctl_namespace *ndns;
		unsigned long _sizes[] = { 0 }, *sector_sizes = _sizes;
		int num_sector_sizes = (int) ARRAY_SIZE(_sizes);

		snprintf(devname, sizeof(devname), "namespace%d.%d",
				ndctl_region_get_id(region), namespace->id);
		ndns = get_namespace_by_id(region, namespace);
		if (!ndns) {
			fprintf(stderr, "%s: failed to find namespace\n",
					devname);
			break;
		}

		if (ndctl_region_get_type(region) == ND_DEVICE_REGION_PMEM
				&& !ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)))
			/* pass, no sector_size support for pmem prior to 4.13 */;
		else {
			num_sector_sizes = namespace->num_sector_sizes;
			sector_sizes = namespace->sector_sizes;
		}

		for (j = 0; j < num_sector_sizes; j++) {
			struct btt *btt_s = NULL;
			struct pfn *pfn_s = NULL;
			struct dax *dax_s = NULL;
			struct ndctl_btt *btt = NULL;
			struct ndctl_pfn *pfn = NULL;
			struct ndctl_dax *dax = NULL;

			rc = configure_namespace(region, ndns, namespace,
					sector_sizes[j], mode);
			if (rc < 0) {
				fprintf(stderr, "%s: failed to configure namespace\n",
						devname);
				break;
			}

			if (strcmp(ndctl_namespace_get_type_name(ndns),
						namespace->type) != 0) {
				fprintf(stderr, "%s: expected type: %s got: %s\n",
						devname,
						ndctl_namespace_get_type_name(ndns),
						namespace->type);
				rc = -ENXIO;
				break;
			}

			/*
			 * On the second time through this loop we skip
			 * establishing btt|pfn since
			 * check_{btt|pfn}_autodetect() destroyed the
			 * inital instance.
			 */
			if (mode == BTT) {
				btt_s = namespace->do_configure > 0
					? namespace->btt_settings : NULL;
				btt = ndctl_namespace_get_btt(ndns);
				if (!!btt_s != !!btt) {
					fprintf(stderr, "%s expected btt %s by default\n",
							devname, namespace->btt_settings
							? "enabled" : "disabled");
					rc = -ENXIO;
					break;
				}
			}

			if (mode == PFN) {
				pfn_s = namespace->do_configure > 0
					? namespace->pfn_settings : NULL;
				pfn = ndctl_namespace_get_pfn(ndns);
				if (!!pfn_s != !!pfn) {
					fprintf(stderr, "%s expected pfn %s by default\n",
							devname, namespace->pfn_settings
							? "enabled" : "disabled");
					rc = -ENXIO;
					break;
				}
			}

			if (mode == DAX) {
				dax_s = namespace->do_configure > 0
					? namespace->dax_settings : NULL;
				dax = ndctl_namespace_get_dax(ndns);
				if (!!dax_s != !!dax) {
					fprintf(stderr, "%s expected dax %s by default\n",
							devname, namespace->dax_settings
							? "enabled" : "disabled");
					rc = -ENXIO;
					break;
				}
			}

			if (!btt_s && !pfn_s && !dax_s
					&& !ndctl_namespace_is_enabled(ndns)) {
				fprintf(stderr, "%s: expected enabled by default\n",
						devname);
				rc = -ENXIO;
				break;
			}

			if (namespace->size != ndctl_namespace_get_size(ndns)) {
				fprintf(stderr, "%s: expected size: %#llx got: %#llx\n",
						devname, namespace->size,
						ndctl_namespace_get_size(ndns));
				rc = -ENXIO;
				break;
			}

			if (sector_sizes[j] && sector_sizes[j]
					!= ndctl_namespace_get_sector_size(ndns)) {
				fprintf(stderr, "%s: expected lbasize: %#lx got: %#x\n",
						devname, sector_sizes[j],
						ndctl_namespace_get_sector_size(ndns));
				rc = -ENXIO;
				break;
			}

			ndctl_namespace_get_uuid(ndns, uu);
			if (uuid_compare(uu, namespace->uuid) != 0) {
				char expect[40], actual[40];

				uuid_unparse(uu, actual);
				uuid_unparse(namespace->uuid, expect);
				fprintf(stderr, "%s: expected uuid: %s got: %s\n",
						devname, expect, actual);
				rc = -ENXIO;
				break;
			}

			if (namespace->check_alt_name
					&& strcmp(ndctl_namespace_get_alt_name(ndns),
						devname) != 0) {
				fprintf(stderr, "%s: expected alt_name: %s got: %s\n",
						devname, devname,
						ndctl_namespace_get_alt_name(ndns));
				rc = -ENXIO;
				break;
			}

			if (dax)
				rc = validate_dax(dax);
			else
				rc = validate_bdev(devname, btt, pfn, ndns,
						namespace, buf);
			if (rc) {
				fprintf(stderr, "%s: %s validate_%s failed\n",
						__func__, devname, dax ? "dax" : "bdev");
				break;
			}

			rc = validate_write_cache(ndns);
			if (rc) {
				fprintf(stderr, "%s: %s validate_write_cache failed\n",
						__func__, devname);
				break;
			}

			if (ndctl_namespace_disable_invalidate(ndns) < 0) {
				fprintf(stderr, "%s: failed to disable\n", devname);
				rc = -ENXIO;
				break;
			}

			if (ndctl_namespace_enable(ndns) < 0) {
				fprintf(stderr, "%s: failed to enable\n", devname);
				rc = -ENXIO;
				break;
			}

			if (btt_s && check_btt_autodetect(bus, ndns, buf,
						namespace) < 0) {
				fprintf(stderr, "%s, failed btt autodetect\n", devname);
				rc = -ENXIO;
				break;
			}

			if (pfn_s && check_pfn_autodetect(bus, ndns, buf,
						namespace) < 0) {
				fprintf(stderr, "%s, failed pfn autodetect\n", devname);
				rc = -ENXIO;
				break;
			}

			if (dax_s && check_dax_autodetect(bus, ndns, buf,
						namespace) < 0) {
				fprintf(stderr, "%s, failed dax autodetect\n", devname);
				rc = -ENXIO;
				break;
			}

			/*
			 * if the namespace is being tested with a btt, there is no
			 * point testing different sector sizes for the namespace itself
			 */
			if (btt_s || pfn_s || dax_s)
				break;

			/*
			 * If this is the last sector size being tested, don't disable
			 * the namespace
			 */
			if (j == num_sector_sizes - 1)
				break;

			/*
			 * If we're in the second time through this, don't loop for
			 * different sector sizes as ->do_configure is disabled
			 */
			if (!retry_cnt)
				break;

			if (ndctl_namespace_disable_invalidate(ndns) < 0) {
				fprintf(stderr, "%s: failed to disable\n", devname);
				break;
			}
		}
		namespace->do_configure = 0;

		__ndns_save = realloc(ndns_save,
				sizeof(struct ndctl_namespace *) * (i + 1));
		if (!__ndns_save) {
			fprintf(stderr, "%s: %s() -ENOMEM\n",
					devname, __func__);
			rc = -ENOMEM;
			break;
		} else {
			ndns_save = __ndns_save;
			ndns_save[i] = ndns;
		}

		if (rc)
			break;
	}

	if (namespace || ndctl_region_disable_preserve(region) != 0) {
		rc = -ENXIO;
		if (!namespace)
			fprintf(stderr, "failed to disable region%d\n",
					ndctl_region_get_id(region));
		goto out;
	}

	/*
	 * On the second time through configure_namespace() is skipped
	 * to test assembling namespace(s) from an existing label set
	 */
	if (retry_cnt--) {
		ndctl_region_enable(region);
		free(ndns_save);
		goto retry;
	}

	rc = 0;
	for (i--; i >= 0; i--) {
		struct ndctl_namespace *ndns = ndns_save[i];

		snprintf(devname, sizeof(devname), "namespace%d.%d",
				ndctl_region_get_id(region),
				ndctl_namespace_get_id(ndns));
		if (ndctl_namespace_is_valid(ndns)) {
			fprintf(stderr, "%s: failed to invalidate\n", devname);
			rc = -ENXIO;
			break;
		}
	}
	ndctl_region_cleanup(region);
 out:
	free(ndns_save);
	free(buf);

	return rc;
}

static int check_btt_supported_sectors(struct ndctl_btt *btt, struct btt *expect_btt)
{
	int s, t;
	char devname[50];

	snprintf(devname, sizeof(devname), "btt%d", ndctl_btt_get_id(btt));
	for (s = 0; s < expect_btt->num_sector_sizes; s++) {
		for (t = 0; t < expect_btt->num_sector_sizes; t++) {
			if (ndctl_btt_get_supported_sector_size(btt, t)
					== expect_btt->sector_sizes[s])
				break;
		}
		if (t >= expect_btt->num_sector_sizes) {
			fprintf(stderr, "%s: expected sector_size: %d to be supported\n",
					devname, expect_btt->sector_sizes[s]);
			return -ENXIO;
		}
	}

	return 0;
}

static int check_btts(struct ndctl_region *region, struct btt **btts)
{
	struct btt *btt_s;
	int i;

	for (i = 0; (btt_s = btts[i]); i++) {
		struct ndctl_btt *btt;
		char devname[50];
		uuid_t btt_uuid;
		int rc;

		btt = get_idle_btt(region);
		if (!btt) {
			fprintf(stderr, "failed to find idle btt\n");
			return -ENXIO;
		}
		snprintf(devname, sizeof(devname), "btt%d",
				ndctl_btt_get_id(btt));
		ndctl_btt_get_uuid(btt, btt_uuid);
		if (uuid_compare(btt_uuid, btt_s->uuid) != 0) {
			char expect[40], actual[40];

			uuid_unparse(btt_uuid, actual);
			uuid_unparse(btt_s->uuid, expect);
			fprintf(stderr, "%s: expected uuid: %s got: %s\n",
					devname, expect, actual);
			return -ENXIO;
		}
		if (ndctl_btt_get_num_sector_sizes(btt) != btt_s->num_sector_sizes) {
			fprintf(stderr, "%s: expected num_sector_sizes: %d got: %d\n",
					devname, btt_s->num_sector_sizes,
					ndctl_btt_get_num_sector_sizes(btt));
		}
		rc = check_btt_supported_sectors(btt, btt_s);
		if (rc)
			return rc;
		if (btt_s->enabled && ndctl_btt_is_enabled(btt)) {
			fprintf(stderr, "%s: expected disabled by default\n",
					devname);
			return -ENXIO;
		}
	}

	return 0;
}

struct check_cmd {
	int (*check_fn)(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check);
	struct ndctl_cmd *cmd;
	struct ndctl_test *test;
};

static struct check_cmd *check_cmds;

static int check_get_config_size(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
		struct check_cmd *check)
{
	struct ndctl_cmd *cmd;
	int rc;

	if (check->cmd != NULL) {
		fprintf(stderr, "%s: dimm: %#x expected a NULL command, by default\n",
				__func__, ndctl_dimm_get_handle(dimm));
		return -ENXIO;
	}

	cmd = ndctl_dimm_cmd_new_cfg_size(dimm);
	if (!cmd) {
		fprintf(stderr, "%s: dimm: %#x failed to create cmd\n",
				__func__, ndctl_dimm_get_handle(dimm));
		return -ENOTTY;
	}

	rc = ndctl_cmd_submit(cmd);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n",
			__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}

	if (ndctl_cmd_cfg_size_get_size(cmd) != SZ_128K) {
		fprintf(stderr, "%s: dimm: %#x expect size: %d got: %d\n",
				__func__, ndctl_dimm_get_handle(dimm), SZ_128K,
				ndctl_cmd_cfg_size_get_size(cmd));
		ndctl_cmd_unref(cmd);
		return -ENXIO;
	}

	check->cmd = cmd;
	return 0;
}

static int check_get_config_data(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
		struct check_cmd *check)
{
	struct ndctl_cmd *cmd_size = check_cmds[ND_CMD_GET_CONFIG_SIZE].cmd;
	struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_cfg_read(cmd_size);
	static char buf[SZ_128K];
	ssize_t rc;

	if (!cmd) {
		fprintf(stderr, "%s: dimm: %#x failed to create cmd\n",
				__func__, ndctl_dimm_get_handle(dimm));
		return -ENOTTY;
	}

	rc = ndctl_cmd_submit(cmd);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %zd\n",
			__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}

	rc = ndctl_cmd_cfg_read_get_data(cmd, buf, SZ_128K, 0);
	if (rc != SZ_128K) {
		fprintf(stderr, "%s: dimm: %#x expected read %d bytes, got: %zd\n",
			__func__, ndctl_dimm_get_handle(dimm), SZ_128K, rc);
		ndctl_cmd_unref(cmd);
		return -ENXIO;
	}

	check->cmd = cmd;
	return 0;
}

static int check_set_config_data(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
		struct check_cmd *check)
{
	struct ndctl_cmd *cmd_read = check_cmds[ND_CMD_GET_CONFIG_DATA].cmd;
	struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_cfg_write(cmd_read);
	char buf[20], result[sizeof(buf)];
	int rc;

	if (!cmd) {
		fprintf(stderr, "%s: dimm: %#x failed to create cmd\n",
				__func__, ndctl_dimm_get_handle(dimm));
		return -ENOTTY;
	}

	memset(buf, 0, sizeof(buf));
	ndctl_cmd_cfg_write_set_data(cmd, buf, sizeof(buf), 0);
	rc = ndctl_cmd_submit(cmd);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n",
			__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}

	rc = ndctl_cmd_submit(cmd_read);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit read1: %d\n",
				__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}
	ndctl_cmd_cfg_read_get_data(cmd_read, result, sizeof(result), 0);
	if (memcmp(result, buf, sizeof(result)) != 0) {
		fprintf(stderr, "%s: dimm: %#x read1 data miscompare: %d\n",
				__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return -ENXIO;
	}

	sprintf(buf, "dimm-%#x", ndctl_dimm_get_handle(dimm));
	ndctl_cmd_cfg_write_set_data(cmd, buf, sizeof(buf), 0);
	rc = ndctl_cmd_submit(cmd);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n",
			__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}

	rc = ndctl_cmd_submit(cmd_read);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit read2: %d\n",
				__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}
	ndctl_cmd_cfg_read_get_data(cmd_read, result, sizeof(result), 0);
	if (memcmp(result, buf, sizeof(result)) != 0) {
		fprintf(stderr, "%s: dimm: %#x read2 data miscompare: %d\n",
				__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}

	check->cmd = cmd;
	return 0;
}

#define __check_smart(dimm, cmd, field, mask) ({ \
	if ((ndctl_cmd_smart_get_##field(cmd) & mask) != smart_data.field) { \
		fprintf(stderr, "%s dimm: %#x expected \'" #field \
				"\' %#x got: %#x\n", __func__, \
				ndctl_dimm_get_handle(dimm), \
				smart_data.field, \
				ndctl_cmd_smart_get_##field(cmd)); \
		ndctl_cmd_unref(cmd); \
		return -ENXIO; \
	} \
})

/*
 * Note, this is not a command payload, this is just a namespace for
 * smart parameters.
 */
struct smart {
	unsigned int flags, health, temperature, spares, alarm_flags,
		     life_used, shutdown_state, shutdown_count, vendor_size;
};

static int check_smart(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
		struct check_cmd *check)
{
	static const struct smart smart_data = {
		.flags = ND_SMART_HEALTH_VALID | ND_SMART_TEMP_VALID
			| ND_SMART_SPARES_VALID | ND_SMART_ALARM_VALID
			| ND_SMART_USED_VALID | ND_SMART_SHUTDOWN_VALID,
		.health = ND_SMART_NON_CRITICAL_HEALTH,
		.temperature = 23 * 16,
		.spares = 75,
		.alarm_flags = ND_SMART_SPARE_TRIP | ND_SMART_TEMP_TRIP,
		.life_used = 5,
		.shutdown_state = 0,
		.shutdown_count = 42,
		.vendor_size = 0,
	};
	struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_smart(dimm);
	int rc;

	if (!cmd) {
		fprintf(stderr, "%s: dimm: %#x failed to create cmd\n",
				__func__, ndctl_dimm_get_handle(dimm));
		return -ENXIO;
	}

	rc = ndctl_cmd_submit(cmd);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n",
			__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}

	__check_smart(dimm, cmd, flags, ~(ND_SMART_CTEMP_VALID
			| ND_SMART_SHUTDOWN_COUNT_VALID));
	__check_smart(dimm, cmd, health, -1);
	__check_smart(dimm, cmd, temperature, -1);
	__check_smart(dimm, cmd, spares, -1);
	__check_smart(dimm, cmd, alarm_flags, -1);
	__check_smart(dimm, cmd, life_used, -1);
	__check_smart(dimm, cmd, shutdown_state, -1);
	__check_smart(dimm, cmd, vendor_size, -1);
	if (ndctl_cmd_smart_get_flags(cmd) & ND_SMART_SHUTDOWN_COUNT_VALID)
		__check_smart(dimm, cmd, shutdown_count, -1);

	check->cmd = cmd;
	return 0;
}

#define __check_smart_threshold(dimm, cmd, field) ({ \
	if (ndctl_cmd_smart_threshold_get_##field(cmd) != smart_t_data.field) { \
		fprintf(stderr, "%s dimm: %#x expected \'" #field \
				"\' %#x got: %#x\n", __func__, \
				ndctl_dimm_get_handle(dimm), \
				smart_t_data.field, \
				ndctl_cmd_smart_threshold_get_##field(cmd)); \
		ndctl_cmd_unref(cmd_set); \
		ndctl_cmd_unref(cmd); \
		return -ENXIO; \
	} \
})

/*
 * Note, this is not a command payload, this is just a namespace for
 * smart_threshold parameters.
 */
struct smart_threshold {
	unsigned int alarm_control, media_temperature, ctrl_temperature, spares;
};

static int check_smart_threshold(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
		struct check_cmd *check)
{
	static const struct smart_threshold smart_t_data = {
		.alarm_control = ND_SMART_SPARE_TRIP | ND_SMART_TEMP_TRIP,
		.media_temperature = 40 * 16,
		.ctrl_temperature = 30 * 16,
		.spares = 5,
	};
	struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_smart_threshold(dimm);
	struct ndctl_cmd *cmd_smart = check_cmds[ND_CMD_SMART].cmd;
	struct ndctl_cmd *cmd_set;
	struct timeval tm;
	char buf[4096];
	fd_set fds;
	int rc, fd;

	if (!cmd) {
		fprintf(stderr, "%s: dimm: %#x failed to create cmd\n",
				__func__, ndctl_dimm_get_handle(dimm));
		return -ENXIO;
	}

	fd = ndctl_dimm_get_health_eventfd(dimm);
	FD_ZERO(&fds);
	FD_SET(fd, &fds);
	rc = pread(fd, buf, sizeof(buf), 0);
	tm.tv_sec = 0;
	tm.tv_usec = 500;
	rc = select(fd + 1, NULL, NULL, &fds, &tm);
	if (rc) {
		fprintf(stderr, "%s: expected health event timeout\n",
				ndctl_dimm_get_devname(dimm));
		return -ENXIO;
	}

	/*
	 * Starting with v4.9 smart threshold requests trigger the file
	 * descriptor returned by ndctl_dimm_get_health_eventfd().
	 */
	if (ndctl_test_attempt(check->test, KERNEL_VERSION(4, 9, 0))) {
		int pid = fork();

		if (pid == 0) {
			FD_ZERO(&fds);
			FD_SET(fd, &fds);
			tm.tv_sec = 5;
			tm.tv_usec = 0;
			rc = select(fd + 1, NULL, NULL, &fds, &tm);
			if (rc != 1 || !FD_ISSET(fd, &fds))
				exit(EXIT_FAILURE);
			rc = pread(fd, buf, sizeof(buf), 0);
			exit(EXIT_SUCCESS);
		}
	}

	rc = ndctl_cmd_submit(cmd);
	if (rc < 0) {
		fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n",
			__func__, ndctl_dimm_get_handle(dimm), rc);
		ndctl_cmd_unref(cmd);
		return rc;
	}

	/*
	 * The same kernel change that adds nfit_test support for this
	 * command is the same change that moves notifications to
	 * require set_threshold. If we fail to get a command, but the
	 * notification fires then we are on an old kernel, otherwise
	 * whether old kernel or new kernel the notification should
	 * fire.
	 */
	cmd_set = ndctl_dimm_cmd_new_smart_set_threshold(cmd);
	if (cmd_set) {

		/*
		 * These values got reworked when nfit_test gained
		 * set_threshold support
		 */
		__check_smart_threshold(dimm, cmd, media_temperature);
		__check_smart_threshold(dimm, cmd, ctrl_temperature);
		__check_smart_threshold(dimm, cmd, spares);
		__check_smart_threshold(dimm, cmd, alarm_control);


		/*
		 * Set all thresholds to match current values and set
		 * all alarms.
		 */
		rc = ndctl_cmd_smart_threshold_set_alarm_control(cmd_set,
				ndctl_cmd_smart_threshold_get_supported_alarms(cmd_set));
		/* 'set_temperature' and 'set_media_temperature' are aliases */
		rc |= ndctl_cmd_smart_threshold_set_temperature(cmd_set,
				ndctl_cmd_smart_get_media_temperature(cmd_smart));
		rc |= ndctl_cmd_smart_threshold_set_ctrl_temperature(cmd_set,
				ndctl_cmd_smart_get_ctrl_temperature(cmd_smart));
		rc |= ndctl_cmd_smart_threshold_set_spares(cmd_set,
				ndctl_cmd_smart_get_spares(cmd_smart));
		if (rc) {
			fprintf(stderr, "%s: failed set threshold parameters\n",
					__func__);
			ndctl_cmd_unref(cmd_set);
			return -ENXIO;
		}

		rc = ndctl_cmd_submit(cmd_set);
		if (rc < 0) {
			fprintf(stderr, "%s: dimm: %#x failed to submit cmd_set: %d\n",
					__func__, ndctl_dimm_get_handle(dimm), rc);
			ndctl_cmd_unref(cmd_set);
			return rc;
		}
		ndctl_cmd_unref(cmd_set);
	}

	if (ndctl_test_attempt(check->test, KERNEL_VERSION(4, 9, 0))) {
		wait(&rc);
		if (WEXITSTATUS(rc) == EXIT_FAILURE) {
			fprintf(stderr, "%s: expect health event trigger\n",
					ndctl_dimm_get_devname(dimm));
			return -ENXIO;
		}
	}

	ndctl_cmd_unref(cmd);
	return 0;
}

#define BITS_PER_LONG 32
static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
		unsigned long bus_commands, unsigned long dimm_commands,
		struct ndctl_test *test)
{
	/*
	 * For now, by coincidence, these are indexed in test execution
	 * order such that check_get_config_data can assume that
	 * check_get_config_size has updated
	 * check_cmd[ND_CMD_GET_CONFIG_SIZE].cmd and
	 * check_set_config_data can assume that both
	 * check_get_config_size and check_get_config_data have run
	 */
	struct check_cmd __check_dimm_cmds[] = {
		[ND_CMD_GET_CONFIG_SIZE] = { check_get_config_size },
		[ND_CMD_GET_CONFIG_DATA] = { check_get_config_data },
		[ND_CMD_SET_CONFIG_DATA] = { check_set_config_data },
		[ND_CMD_SMART] = { check_smart },
		[ND_CMD_SMART_THRESHOLD] = {
			.check_fn = check_smart_threshold,
			.test = test,
		},
	};

	unsigned int i, rc = 0;

	/*
	 * The kernel did not start emulating v1.2 namespace spec smart data
	 * until 4.9.
	 */
	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0)))
		dimm_commands &= ~((1 << ND_CMD_SMART)
				| (1 << ND_CMD_SMART_THRESHOLD));

	/* Check DIMM commands */
	check_cmds = __check_dimm_cmds;
	for (i = 0; i < BITS_PER_LONG; i++) {
		struct check_cmd *check = &check_cmds[i];

		if ((dimm_commands & (1UL << i)) == 0)
			continue;
		if (!ndctl_dimm_is_cmd_supported(dimm, i)) {
			fprintf(stderr, "%s: bus: %s dimm%d expected cmd: %s supported\n",
					__func__,
					ndctl_bus_get_provider(bus),
					ndctl_dimm_get_id(dimm),
					ndctl_dimm_get_cmd_name(dimm, i));
			return -ENXIO;
		}

		if (!check->check_fn)
			continue;
		rc = check->check_fn(bus, dimm, check);
		if (rc)
			break;
	}

	for (i = 0; i < ARRAY_SIZE(__check_dimm_cmds); i++) {
		if (__check_dimm_cmds[i].cmd)
			ndctl_cmd_unref(__check_dimm_cmds[i].cmd);
		__check_dimm_cmds[i].cmd = NULL;
	}
	if (rc)
		goto out;

	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 6, 0)))
		goto out;

 out:
	return rc;
}

static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n,
		unsigned long bus_commands, unsigned long dimm_commands,
		struct ndctl_test *test)
{
	long long dsc;
	int i, j, rc;

	for (i = 0; i < n; i++) {
		struct ndctl_dimm *dimm = get_dimm_by_handle(bus, dimms[i].handle);

		if (!dimm) {
			fprintf(stderr, "failed to find dimm: %d\n", dimms[i].phys_id);
			return -ENXIO;
		}

		if (ndctl_dimm_get_phys_id(dimm) != dimms[i].phys_id) {
			fprintf(stderr, "dimm%d expected phys_id: %d got: %d\n",
					i, dimms[i].phys_id,
					ndctl_dimm_get_phys_id(dimm));
			return -ENXIO;
		}

		if (ndctl_dimm_has_errors(dimm) != !!dimms[i].flags) {
			fprintf(stderr, "bus: %s dimm%d %s expected%s errors\n",
					ndctl_bus_get_provider(bus), i,
					ndctl_dimm_get_devname(dimm),
					dimms[i].flags ? "" : " no");
			return -ENXIO;
		}

		if (ndctl_dimm_failed_save(dimm) != dimms[i].f_save
				|| ndctl_dimm_failed_arm(dimm) != dimms[i].f_arm
				|| ndctl_dimm_failed_restore(dimm) != dimms[i].f_restore
				|| ndctl_dimm_smart_pending(dimm) != dimms[i].f_smart
				|| ndctl_dimm_failed_flush(dimm) != dimms[i].f_flush) {
			fprintf(stderr, "expected: %s%s%s%s%sgot: %s%s%s%s%s\n",
					dimms[i].f_save ? "save_fail " : "",
					dimms[i].f_arm ? "not_armed " : "",
					dimms[i].f_restore ? "restore_fail " : "",
					dimms[i].f_smart ? "smart_event " : "",
					dimms[i].f_flush ? "flush_fail " : "",
					ndctl_dimm_failed_save(dimm) ? "save_fail " : "",
					ndctl_dimm_failed_arm(dimm) ? "not_armed " : "",
					ndctl_dimm_failed_restore(dimm) ? "restore_fail " : "",
					ndctl_dimm_smart_pending(dimm) ? "smart_event " : "",
					ndctl_dimm_failed_flush(dimm) ? "flush_fail " : "");
			return -ENXIO;
		}

		if (ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) {
			if (ndctl_dimm_get_formats(dimm) != dimms[i].formats) {
				fprintf(stderr, "dimm%d expected formats: %d got: %d\n",
						i, dimms[i].formats,
						ndctl_dimm_get_formats(dimm));
				fprintf(stderr, "continuing...\n");
			}
			for (j = 0; j < dimms[i].formats; j++) {
				if (ndctl_dimm_get_formatN(dimm, j) != dimms[i].format[j]) {
					fprintf(stderr,
						"dimm%d expected format[%d]: %d got: %d\n",
							i, j, dimms[i].format[j],
							ndctl_dimm_get_formatN(dimm, j));
					fprintf(stderr, "continuing...\n");
				}
			}
		}

		if (ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) {
			if (ndctl_dimm_get_subsystem_vendor(dimm)
					!= dimms[i].subsystem_vendor) {
				fprintf(stderr,
					"dimm%d expected subsystem vendor: %d got: %d\n",
						i, dimms[i].subsystem_vendor,
						ndctl_dimm_get_subsystem_vendor(dimm));
				return -ENXIO;
			}
		}

		if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) {
			if (ndctl_dimm_get_manufacturing_date(dimm)
					!= dimms[i].manufacturing_date) {
				fprintf(stderr,
					"dimm%d expected manufacturing date: %d got: %d\n",
						i, dimms[i].manufacturing_date,
						ndctl_dimm_get_manufacturing_date(dimm));
				return -ENXIO;
			}
		}

		dsc = ndctl_dimm_get_dirty_shutdown(dimm);
		if (dsc != -ENOENT && dsc != dimms[i].dirty_shutdown) {
			fprintf(stderr,
				"dimm%d expected dirty shutdown: %lld got: %lld\n",
				i, dimms[i].dirty_shutdown,
				ndctl_dimm_get_dirty_shutdown(dimm));
			return -ENXIO;
		}

		rc = check_commands(bus, dimm, bus_commands, dimm_commands, test);
		if (rc)
			return rc;
	}

	return 0;
}

enum dimm_reset {
	DIMM_INIT,
	DIMM_ZERO,
};

static int reset_dimms(struct ndctl_bus *bus, enum dimm_reset reset)
{
	struct ndctl_dimm *dimm;
	int rc = 0;

	ndctl_dimm_foreach(bus, dimm) {
		if (reset == DIMM_ZERO)
			ndctl_dimm_zero_labels(dimm);
		else {
			ndctl_dimm_read_label_index(dimm);
			ndctl_dimm_init_labels(dimm, NDCTL_NS_VERSION_1_2);
		}
		ndctl_dimm_disable(dimm);
		rc = ndctl_dimm_enable(dimm);
		if (rc)
			break;
	}

	return rc;
}

static void reset_bus(struct ndctl_bus *bus, enum dimm_reset reset)
{
	struct ndctl_region *region;

	/* disable all regions so that set_config_data commands are permitted */
	ndctl_region_foreach(bus, region)
		ndctl_region_disable_invalidate(region);

	reset_dimms(bus, reset);

	/* set regions back to their default state */
	ndctl_region_foreach(bus, region)
		ndctl_region_enable(region);
}

static int do_test0(struct ndctl_ctx *ctx, struct ndctl_test *test)
{
	struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0);
	struct ndctl_region *region;
	int rc;

	if (!bus)
		return -ENXIO;

	ndctl_bus_wait_probe(bus);

	/* disable all regions so that set_config_data commands are permitted */
	ndctl_region_foreach(bus, region)
		ndctl_region_disable_invalidate(region);

	rc = check_dimms(bus, dimms0, ARRAY_SIZE(dimms0), bus_commands0,
			dimm_commands0, test);
	if (rc)
		return rc;

	rc = reset_dimms(bus, DIMM_INIT);
	if (rc < 0) {
		fprintf(stderr, "failed to reset dimms\n");
		return rc;
	}

	/*
	 * Enable regions and adjust the space-align to drop the default
	 * alignment constraints
	 */
	ndctl_region_foreach(bus, region) {
		ndctl_region_enable(region);
		ndctl_region_set_align(region, sysconf(_SC_PAGESIZE)
				* ndctl_region_get_interleave_ways(region));
	}

	/* pfn and dax tests require vmalloc-enabled nfit_test */
	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) {
		rc = check_regions(bus, regions0, ARRAY_SIZE(regions0), DAX);
		if (rc)
			return rc;
		reset_bus(bus, DIMM_INIT);
	}

	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) {
		rc = check_regions(bus, regions0, ARRAY_SIZE(regions0), PFN);
		if (rc)
			return rc;
		reset_bus(bus, DIMM_INIT);
	}

	return check_regions(bus, regions0, ARRAY_SIZE(regions0), BTT);
}

static int do_test1(struct ndctl_ctx *ctx, struct ndctl_test *test)
{
	struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER1);
	int rc;

	if (!bus)
		return -ENXIO;

	ndctl_bus_wait_probe(bus);
	reset_bus(bus, DIMM_ZERO);

	/*
	 * Starting with v4.10 the dimm on nfit_test.1 gets a unique
	 * handle.
	 */
	if (ndctl_test_attempt(test, KERNEL_VERSION(4, 10, 0)))
		dimms1[0].handle = DIMM_HANDLE(1, 0, 0, 0, 0);

	rc = check_dimms(bus, dimms1, ARRAY_SIZE(dimms1), 0, 0, test);
	if (rc)
		return rc;

	return check_regions(bus, regions1, ARRAY_SIZE(regions1), BTT);
}

typedef int (*do_test_fn)(struct ndctl_ctx *ctx, struct ndctl_test *test);
static do_test_fn do_test[] = {
	do_test0,
	do_test1,
};

int test_libndctl(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx)
{
	unsigned int i;
	struct kmod_module *mod;
	struct kmod_ctx *kmod_ctx;
	struct daxctl_ctx *daxctl_ctx;
	int err, result = EXIT_FAILURE;

	if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 2, 0)))
		return 77;

	ndctl_set_log_priority(ctx, loglevel);
	daxctl_ctx = ndctl_get_daxctl_ctx(ctx);
	daxctl_set_log_priority(daxctl_ctx, loglevel);
	ndctl_set_private_data(ctx, test);

	err = ndctl_test_init(&kmod_ctx, &mod, ctx, loglevel, test);
	if (err < 0) {
		ndctl_test_skip(test);
		fprintf(stderr, "nfit_test unavailable skipping tests\n");
		return 77;
	}

	for (i = 0; i < ARRAY_SIZE(do_test); i++) {
		err = do_test[i](ctx, test);
		if (err < 0) {
			fprintf(stderr, "ndctl-test%d failed: %d\n", i, err);
			break;
		}
	}

	if (i >= ARRAY_SIZE(do_test))
		result = EXIT_SUCCESS;
	kmod_module_remove_module(mod, 0);
	kmod_unref(kmod_ctx);
	return result;
}

int __attribute__((weak)) main(int argc, char *argv[])
{
	struct ndctl_test *test = ndctl_test_new(0);
	struct ndctl_ctx *ctx;
	int rc;

	if (!test) {
		fprintf(stderr, "failed to initialize test\n");
		return EXIT_FAILURE;
	}

	rc = ndctl_new(&ctx);
	if (rc)
		return ndctl_test_result(test, rc);
	rc = test_libndctl(LOG_DEBUG, test, ctx);
	ndctl_unref(ctx);
	return ndctl_test_result(test, rc);
}
