diff mbox

[ndctl,2/2] ndctl: Add DSM support for Address Range Scrub commands

Message ID 1437675275-13999-2-git-send-email-vishal.l.verma@intel.com (mailing list archive)
State Accepted
Commit b6cfd3071962
Delegated to: Dan Williams
Headers show

Commit Message

Vishal Verma July 23, 2015, 6:14 p.m. UTC
Add support for the three ARS DSM commands:
- Query ARS Capabilities - Queries the firmware to check if a given
  range supports scrub, and if so, which type (persistent vs. volatile)
- Start ARS - Starts a scrub for a given range/type
- Query ARS Status - Checks status of a previously started scrub, and
  provides the error logs if any.

The commands are described by the example DSM spec at:
http://pmem.io/documents/NVDIMM_DSM_Interface_Example.pdf

Also add these commands to the unit test which tests for canned values
expected from the kernel's nfit_test modules.

Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
---
 Makefile.am          |   4 +-
 lib/libndctl.c       | 209 +++++++++++++++++++++++++++++++++++++++++++++++++--
 lib/libndctl.sym     |   8 ++
 lib/ndctl/libndctl.h |  11 +++
 lib/test-libndctl.c  | 193 ++++++++++++++++++++++++++++++++++++++++++-----
 ndctl.h              |  10 +++
 6 files changed, 408 insertions(+), 27 deletions(-)
diff mbox

Patch

diff --git a/Makefile.am b/Makefile.am
index cd6e190..070192f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -32,9 +32,9 @@  SED_PROCESS = \
 %.pc: %.pc.in Makefile
 	$(SED_PROCESS)
 
-LIBNDCTL_CURRENT=3
+LIBNDCTL_CURRENT=4
 LIBNDCTL_REVISION=0
-LIBNDCTL_AGE=0
+LIBNDCTL_AGE=1
 
 pkginclude_HEADERS = lib/ndctl/libndctl.h
 lib_LTLIBRARIES = lib/libndctl.la
diff --git a/lib/libndctl.c b/lib/libndctl.c
index 86a3a36..b87c7cf 100644
--- a/lib/libndctl.c
+++ b/lib/libndctl.c
@@ -338,6 +338,9 @@  struct ndctl_cmd {
 	} iter;
 	struct ndctl_cmd *source;
 	union {
+		struct nd_cmd_ars_cap ars_cap[0];
+		struct nd_cmd_ars_start ars_start[0];
+		struct nd_cmd_ars_status ars_status[0];
 		struct nd_cmd_get_config_size get_size[0];
 		struct nd_cmd_get_config_data_hdr get_data[0];
 		struct nd_cmd_set_config_hdr set_data[0];
@@ -1652,6 +1655,13 @@  static const char *ndctl_device_type_name(int type)
 	}
 }
 
+static struct ndctl_bus *cmd_to_bus(struct ndctl_cmd *cmd)
+{
+	if (cmd->dimm)
+		return ndctl_dimm_get_bus(cmd->dimm);
+	return cmd->bus;
+}
+
 NDCTL_EXPORT const char *ndctl_region_get_type_name(struct ndctl_region *region)
 {
 	return ndctl_device_type_name(ndctl_region_get_type(region));
@@ -1697,6 +1707,198 @@  NDCTL_EXPORT struct ndctl_dimm *ndctl_region_get_next_dimm(struct ndctl_region *
 	return NULL;
 }
 
+NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_cap(struct ndctl_bus *bus,
+		unsigned long long address, unsigned long long len)
+{
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
+	struct ndctl_cmd *cmd;
+	size_t size;
+
+	if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_CAP)) {
+		dbg(ctx, "unsupported cmd\n");
+		return NULL;
+	}
+
+	size = sizeof(*cmd) + sizeof(struct nd_cmd_ars_cap);
+	cmd = calloc(1, size);
+	if (!cmd)
+		return NULL;
+
+	cmd->bus = bus;
+	ndctl_cmd_ref(cmd);
+	cmd->type = ND_CMD_ARS_CAP;
+	cmd->size = size;
+	cmd->status = 1;
+	cmd->firmware_status = &cmd->ars_cap->status;
+	cmd->ars_cap->address = address;
+	cmd->ars_cap->length = len;
+
+	return cmd;
+}
+
+NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_start(struct ndctl_cmd *ars_cap,
+		int type)
+{
+	struct ndctl_bus *bus = ars_cap->bus;
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
+	struct ndctl_cmd *cmd;
+	size_t size;
+
+	if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_START)) {
+		dbg(ctx, "unsupported cmd\n");
+		return NULL;
+	}
+	if (ars_cap->status != 0) {
+		dbg(ctx, "expected sucessfully completed ars_cap command\n");
+		return NULL;
+	}
+	if ((*ars_cap->firmware_status & ARS_STATUS_MASK) != 0) {
+		dbg(ctx, "expected sucessfully completed ars_cap command\n");
+		return NULL;
+	}
+	if (!(*ars_cap->firmware_status >> ARS_EXT_STATUS_SHIFT & type)) {
+		dbg(ctx, "ars_cap does not show requested type as supported\n");
+		return NULL;
+	}
+
+	size = sizeof(*cmd) + sizeof(struct nd_cmd_ars_start);
+	cmd = calloc(1, size);
+	if (!cmd)
+		return NULL;
+
+	cmd->bus = bus;
+	ndctl_cmd_ref(cmd);
+	cmd->type = ND_CMD_ARS_START;
+	cmd->size = size;
+	cmd->status = 1;
+	cmd->firmware_status = &cmd->ars_start->status;
+	cmd->ars_start->address = ars_cap->ars_cap->address;
+	cmd->ars_start->length = ars_cap->ars_cap->length;
+	cmd->ars_start->type = type;
+
+	return cmd;
+}
+
+NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_status(struct ndctl_cmd *ars_cap)
+{
+	struct nd_cmd_ars_cap *ars_cap_cmd = ars_cap->ars_cap;
+	struct ndctl_bus *bus = ars_cap->bus;
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus);
+	struct ndctl_cmd *cmd;
+	size_t size;
+
+	if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_CAP)) {
+		dbg(ctx, "unsupported cmd\n");
+		return NULL;
+	}
+	if (ars_cap->status != 0) {
+		dbg(ctx, "expected sucessfully completed ars_cap command\n");
+		return NULL;
+	}
+	if ((*ars_cap->firmware_status & ARS_STATUS_MASK) != 0) {
+		dbg(ctx, "expected sucessfully completed ars_cap command\n");
+		return NULL;
+	}
+	if (ars_cap_cmd->max_ars_out == 0) {
+		dbg(ctx, "invalid ars_cap\n");
+		return NULL;
+	}
+
+	size = sizeof(*cmd) + sizeof(struct nd_cmd_ars_status) +
+			ars_cap_cmd->max_ars_out;
+	cmd = calloc(1, size);
+	if (!cmd)
+		return NULL;
+
+	cmd->bus = bus;
+	ndctl_cmd_ref(cmd);
+	cmd->type = ND_CMD_ARS_STATUS;
+	cmd->size = size;
+	cmd->status = 1;
+	cmd->firmware_status = &cmd->ars_status->status;
+
+	return cmd;
+}
+
+NDCTL_EXPORT unsigned int ndctl_cmd_ars_cap_get_size(struct ndctl_cmd *ars_cap)
+{
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_cap));
+
+	if (ars_cap->type == ND_CMD_ARS_CAP && ars_cap->status == 0) {
+		dbg(ctx, "max_ars_out: %d\n",
+			ars_cap->ars_cap->max_ars_out);
+		return ars_cap->ars_cap->max_ars_out;
+	}
+
+	dbg(ctx, "invalid ars_cap\n");
+	return 0;
+}
+
+NDCTL_EXPORT unsigned int ndctl_cmd_ars_in_progress(struct ndctl_cmd *ars_stat)
+{
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
+	int rc;
+
+	if (ars_stat->type == ND_CMD_ARS_STATUS && ars_stat->status == 0) {
+		rc = (((*ars_stat->firmware_status >> ARS_EXT_STATUS_SHIFT) == 1) ? 1 : 0);
+		/*
+		 * If in-progress, invalidate the ndctl_cmd, so that if we're
+		 * called again without a fresh ars_stat command, we fail.
+		 */
+		if (rc)
+			ars_stat->status = 1;
+		return rc;
+	}
+
+	dbg(ctx, "invalid ars_status\n");
+	return 0;
+}
+
+NDCTL_EXPORT unsigned int ndctl_cmd_ars_num_records(struct ndctl_cmd *ars_stat)
+{
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
+
+	if (ars_stat->type == ND_CMD_ARS_STATUS && ars_stat->status == 0)
+		return ars_stat->ars_status->num_records;
+
+	dbg(ctx, "invalid ars_status\n");
+	return 0;
+}
+
+NDCTL_EXPORT unsigned int ndctl_cmd_ars_get_record_addr(struct ndctl_cmd *ars_stat,
+		unsigned int rec_index)
+{
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
+
+	if (rec_index >= ars_stat->ars_status->num_records) {
+		dbg(ctx, "invalid record index\n");
+		return 0;
+	}
+
+	if (ars_stat->type == ND_CMD_ARS_STATUS && ars_stat->status == 0)
+		return ars_stat->ars_status->records[rec_index].err_address;
+
+	dbg(ctx, "invalid ars_status\n");
+	return 0;
+}
+
+NDCTL_EXPORT unsigned int ndctl_cmd_ars_get_record_len(struct ndctl_cmd *ars_stat,
+		unsigned int rec_index)
+{
+	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat));
+
+	if (rec_index >= ars_stat->ars_status->num_records) {
+		dbg(ctx, "invalid record index\n");
+		return 0;
+	}
+
+	if (ars_stat->type == ND_CMD_ARS_STATUS && ars_stat->status == 0)
+		return ars_stat->ars_status->records[rec_index].length;
+
+	dbg(ctx, "invalid ars_status\n");
+	return 0;
+}
+
 static struct nd_cmd_vendor_tail *to_vendor_tail(struct ndctl_cmd *cmd)
 {
 	struct nd_cmd_vendor_tail *tail = (struct nd_cmd_vendor_tail *)
@@ -1800,13 +2002,6 @@  NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_size(struct ndctl_dimm *di
 	return cmd;
 }
 
-static struct ndctl_bus *cmd_to_bus(struct ndctl_cmd *cmd)
-{
-	if (cmd->dimm)
-		return ndctl_dimm_get_bus(cmd->dimm);
-	return cmd->bus;
-}
-
 NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_read(struct ndctl_cmd *cfg_size)
 {
 	struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(cfg_size));
diff --git a/lib/libndctl.sym b/lib/libndctl.sym
index 3391a09..3d56037 100644
--- a/lib/libndctl.sym
+++ b/lib/libndctl.sym
@@ -64,6 +64,14 @@  global:
 	ndctl_dimm_is_enabled;
 	ndctl_dimm_disable;
 	ndctl_dimm_enable;
+	ndctl_bus_cmd_new_ars_cap;
+	ndctl_bus_cmd_new_ars_start;
+	ndctl_bus_cmd_new_ars_status;
+	ndctl_cmd_ars_cap_get_size;
+	ndctl_cmd_ars_in_progress;
+	ndctl_cmd_ars_num_records;
+	ndctl_cmd_ars_get_record_addr;
+	ndctl_cmd_ars_get_record_len;
 	ndctl_dimm_cmd_new_vendor_specific;
 	ndctl_cmd_vendor_set_input;
 	ndctl_cmd_vendor_get_output_size;
diff --git a/lib/ndctl/libndctl.h b/lib/ndctl/libndctl.h
index c92910d..4c64f78 100644
--- a/lib/ndctl/libndctl.h
+++ b/lib/ndctl/libndctl.h
@@ -151,6 +151,17 @@  int ndctl_dimm_disable(struct ndctl_dimm *dimm);
 int ndctl_dimm_enable(struct ndctl_dimm *dimm);
 
 struct ndctl_cmd;
+struct ndctl_cmd *ndctl_bus_cmd_new_ars_cap(struct ndctl_bus *bus,
+		unsigned long long address, unsigned long long len);
+struct ndctl_cmd *ndctl_bus_cmd_new_ars_start(struct ndctl_cmd *ars_cap, int type);
+struct ndctl_cmd *ndctl_bus_cmd_new_ars_status(struct ndctl_cmd *ars_cap);
+unsigned int ndctl_cmd_ars_cap_get_size(struct ndctl_cmd *ars_cap);
+unsigned int ndctl_cmd_ars_in_progress(struct ndctl_cmd *ars_status);
+unsigned int ndctl_cmd_ars_num_records(struct ndctl_cmd *ars_stat);
+unsigned int ndctl_cmd_ars_get_record_addr(struct ndctl_cmd *ars_stat,
+		unsigned int rec_index);
+unsigned int ndctl_cmd_ars_get_record_len(struct ndctl_cmd *ars_stat,
+		unsigned int rec_index);
 struct ndctl_cmd *ndctl_dimm_cmd_new_vendor_specific(struct ndctl_dimm *dimm,
 		unsigned int opcode, size_t input_size, size_t output_size);
 ssize_t ndctl_cmd_vendor_set_input(struct ndctl_cmd *cmd, void *buf,
diff --git a/lib/test-libndctl.c b/lib/test-libndctl.c
index 8c1d6c9..86441b2 100644
--- a/lib/test-libndctl.c
+++ b/lib/test-libndctl.c
@@ -337,10 +337,14 @@  static struct region regions1[] = {
 	},
 };
 
-static unsigned long commands0 = 1UL << ND_CMD_GET_CONFIG_SIZE
+static unsigned long dimm_commands0 = 1UL << ND_CMD_GET_CONFIG_SIZE
 		| 1UL << ND_CMD_GET_CONFIG_DATA
 		| 1UL << ND_CMD_SET_CONFIG_DATA;
 
+static unsigned long bus_commands0 = 1UL << ND_CMD_ARS_CAP
+		| 1UL << ND_CMD_ARS_START
+		| 1UL << ND_CMD_ARS_STATUS;
+
 static struct ndctl_dimm *get_dimm_by_handle(struct ndctl_bus *bus, unsigned int handle)
 {
 	struct ndctl_dimm *dimm;
@@ -1054,13 +1058,14 @@  static int check_btts(struct ndctl_region *region, struct btt **btts)
 }
 
 struct check_cmd {
-	int (*check_fn)(struct ndctl_dimm *dimm, struct check_cmd *check);
+	int (*check_fn)(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check);
 	struct ndctl_cmd *cmd;
 };
 
 static struct check_cmd *check_cmds;
 
-static int check_get_config_size(struct ndctl_dimm *dimm, struct check_cmd *check)
+static int check_get_config_size(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
+		struct check_cmd *check)
 {
 	struct ndctl_cmd *cmd;
 	int rc;
@@ -1098,7 +1103,8 @@  static int check_get_config_size(struct ndctl_dimm *dimm, struct check_cmd *chec
 	return 0;
 }
 
-static int check_get_config_data(struct ndctl_dimm *dimm, struct check_cmd *check)
+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);
@@ -1131,7 +1137,8 @@  static int check_get_config_data(struct ndctl_dimm *dimm, struct check_cmd *chec
 	return 0;
 }
 
-static int check_set_config_data(struct ndctl_dimm *dimm, struct check_cmd *check)
+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);
@@ -1198,9 +1205,121 @@  static int check_set_config_data(struct ndctl_dimm *dimm, struct check_cmd *chec
 	return 0;
 }
 
+static int check_ars_cap(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_bus_cmd_new_ars_cap(bus, 0, 64);
+	if (!cmd) {
+		fprintf(stderr, "%s: bus: %s failed to create cmd\n",
+				__func__, ndctl_bus_get_provider(bus));
+		return -ENOTTY;
+	}
+
+	rc = ndctl_cmd_submit(cmd);
+	if (rc) {
+		fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
+				__func__, ndctl_bus_get_provider(bus), rc);
+		ndctl_cmd_unref(cmd);
+		return rc;
+	}
+
+	if (ndctl_cmd_ars_cap_get_size(cmd) != 256) {
+		fprintf(stderr, "%s: bus: %s expect size: %d got: %d\n",
+				__func__, ndctl_bus_get_provider(bus), 256,
+				ndctl_cmd_ars_cap_get_size(cmd));
+		ndctl_cmd_unref(cmd);
+		return -ENXIO;
+	}
+
+	check->cmd = cmd;
+	return 0;
+}
+
+static int check_ars_start(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
+		struct check_cmd *check)
+{
+	struct ndctl_cmd *cmd_ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd;
+	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_bus_cmd_new_ars_start(cmd_ars_cap, ND_ARS_PERSISTENT);
+	if (!cmd) {
+		fprintf(stderr, "%s: bus: %s failed to create cmd\n",
+				__func__, ndctl_bus_get_provider(bus));
+		return -ENOTTY;
+	}
+
+	rc = ndctl_cmd_submit(cmd);
+	if (rc) {
+		fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
+				__func__, ndctl_bus_get_provider(bus), rc);
+		ndctl_cmd_unref(cmd);
+		return rc;
+	}
+
+	check->cmd = cmd;
+	return 0;
+}
+
+static int check_ars_status(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
+		struct check_cmd *check)
+{
+	struct ndctl_cmd *cmd_ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd;
+	struct ndctl_cmd *cmd;
+	unsigned int i;
+	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_bus_cmd_new_ars_status(cmd_ars_cap);
+	if (!cmd) {
+		fprintf(stderr, "%s: bus: %s failed to create cmd\n",
+				__func__, ndctl_bus_get_provider(bus));
+		return -ENOTTY;
+	}
+
+	do {
+		rc = ndctl_cmd_submit(cmd);
+		if (rc) {
+			fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
+				__func__, ndctl_bus_get_provider(bus), rc);
+			ndctl_cmd_unref(cmd);
+			return rc;
+		}
+	} while (ndctl_cmd_ars_in_progress(cmd));
+
+	for (i = 0; i < ndctl_cmd_ars_num_records(cmd); i++) {
+		fprintf(stderr, "%s: record[%d].addr: 0x%x\n", __func__, i,
+			ndctl_cmd_ars_get_record_addr(cmd, i));
+		fprintf(stderr, "%s: record[%d].length: 0x%x\n", __func__, i,
+			ndctl_cmd_ars_get_record_len(cmd, i));
+	}
+
+	check->cmd = cmd;
+	return 0;
+}
+
 #define BITS_PER_LONG 32
 static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
-		unsigned long commands)
+		unsigned long bus_commands, unsigned long dimm_commands)
 {
 	/*
 	 * For now, by coincidence, these are indexed in test execution
@@ -1210,19 +1329,25 @@  static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
 	 * check_set_config_data can assume that both
 	 * check_get_config_size and check_get_config_data have run
 	 */
-	static struct check_cmd __check_cmds[] = {
+	static 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_THRESHOLD] = { },
 	};
+	static struct check_cmd __check_bus_cmds[] = {
+		[ND_CMD_ARS_CAP] = { check_ars_cap },
+		[ND_CMD_ARS_START] = { check_ars_start },
+		[ND_CMD_ARS_STATUS] = { check_ars_status },
+	};
 	unsigned int i, rc;
 
-	check_cmds = __check_cmds;
+	/* Check DIMM commands */
+	check_cmds = __check_dimm_cmds;
 	for (i = 0; i < BITS_PER_LONG; i++) {
 		struct check_cmd *check = &check_cmds[i];
 
-		if ((commands & (1UL << i)) == 0)
+		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",
@@ -1235,22 +1360,53 @@  static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm,
 
 		if (!check->check_fn)
 			continue;
-		rc = check->check_fn(dimm, check);
+		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;
+
+	/* Check Bus commands */
+	check_cmds = __check_bus_cmds;
+	for (i = 1; i < BITS_PER_LONG; i++) {
+		struct check_cmd *check = &check_cmds[i];
+
+		if ((bus_commands & (1UL << i)) == 0)
+			continue;
+		if (!ndctl_bus_is_cmd_supported(bus, i)) {
+			fprintf(stderr, "%s: bus: %s expected cmd: %s supported\n",
+					__func__,
+					ndctl_bus_get_provider(bus),
+					ndctl_bus_get_cmd_name(bus, 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_cmds); i++) {
-		if (__check_cmds[i].cmd)
-			ndctl_cmd_unref(__check_cmds[i].cmd);
-		__check_cmds[i].cmd = NULL;
+	for (i = 1; i < ARRAY_SIZE(__check_bus_cmds); i++) {
+		if (__check_bus_cmds[i].cmd)
+			ndctl_cmd_unref(__check_bus_cmds[i].cmd);
+		__check_bus_cmds[i].cmd = NULL;
 	}
 
+ out:
 	return rc;
 }
 
 static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n,
-		unsigned long commands)
+		unsigned long bus_commands, unsigned long dimm_commands)
 {
 	int i, rc;
 
@@ -1296,7 +1452,7 @@  static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n,
 			return -ENXIO;
 		}
 
-		rc = check_commands(bus, dimm, commands);
+		rc = check_commands(bus, dimm, bus_commands, dimm_commands);
 		if (rc)
 			return rc;
 	}
@@ -1317,7 +1473,8 @@  static int do_test0(struct ndctl_ctx *ctx)
 	ndctl_region_foreach(bus, region)
 		ndctl_region_disable_invalidate(region);
 
-	rc = check_dimms(bus, dimms0, ARRAY_SIZE(dimms0), commands0);
+	rc = check_dimms(bus, dimms0, ARRAY_SIZE(dimms0), bus_commands0,
+			dimm_commands0);
 	if (rc)
 		return rc;
 
@@ -1336,7 +1493,7 @@  static int do_test1(struct ndctl_ctx *ctx)
 	if (!bus)
 		return -ENXIO;
 
-	rc = check_dimms(bus, dimms1, ARRAY_SIZE(dimms1), 0);
+	rc = check_dimms(bus, dimms1, ARRAY_SIZE(dimms1), 0, 0);
 	if (rc)
 		return rc;
 
diff --git a/ndctl.h b/ndctl.h
index f4e15f8..076cc92 100644
--- a/ndctl.h
+++ b/ndctl.h
@@ -111,6 +111,11 @@  enum {
 	ND_CMD_VENDOR = 9,
 };
 
+enum {
+	ND_ARS_VOLATILE = 1,
+	ND_ARS_PERSISTENT = 2,
+};
+
 static __inline__ const char *nvdimm_bus_cmd_name(unsigned cmd)
 {
 	static const char * const names[] = {
@@ -194,4 +199,9 @@  enum nd_driver_flags {
 enum {
 	ND_MIN_NAMESPACE_SIZE = 0x00400000,
 };
+
+enum ars_masks {
+	ARS_STATUS_MASK = 0x0000FFFF,
+	ARS_EXT_STATUS_SHIFT = 16,
+};
 #endif /* __NDCTL_H__ */