diff mbox

[4/4] ndctl: add device dax error clear test

Message ID 148883634307.72901.8906703230765193251.stgit@djiang5-desk3.ch.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Dave Jiang March 6, 2017, 9:39 p.m. UTC
Adding a test/example on clearing of error/poison for device DAX. Most of
the code were moved from the test/libndctl.c check bus commands.

Signed-off-by: Dave Jiang <dave.jiang@intel.com>
---
 test/Makefile.am      |   10 +
 test/daxdev-errors.c  |  361 +++++++++++++++++++++++++++++++++++++++++++++++++
 test/daxdev-errors.sh |   71 ++++++++++
 3 files changed, 440 insertions(+), 2 deletions(-)
 create mode 100644 test/daxdev-errors.c
 create mode 100755 test/daxdev-errors.sh
diff mbox

Patch

diff --git a/test/Makefile.am b/test/Makefile.am
index 524fafa..cd2226f 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -8,7 +8,8 @@  TESTS =\
 	multi-pmem \
 	create.sh \
 	clear.sh \
-	dax-errors.sh
+	dax-errors.sh \
+	daxdev-errors.sh
 
 check_PROGRAMS =\
 	libndctl \
@@ -16,7 +17,8 @@  check_PROGRAMS =\
 	dpa-alloc \
 	parent-uuid \
 	multi-pmem \
-	dax-errors
+	dax-errors \
+	daxdev-errors
 
 if ENABLE_DESTRUCTIVE
 TESTS +=\
@@ -72,6 +74,10 @@  dax_dev_LDADD = $(LIBNDCTL_LIB) $(KMOD_LIBS)
 dax_pmd_SOURCES = dax-pmd.c
 mmap_SOURCES = mmap.c
 dax_errors_SOURCES = dax-errors.c
+daxdev_errors_SOURCES = daxdev-errors.c \
+			../util/log.c \
+			../util/sysfs.c
+daxdev_errors_LDADD = $(LIBNDCTL_LIB)
 device_dax_SOURCES = \
 		device-dax.c \
 		dax-dev.c \
diff --git a/test/daxdev-errors.c b/test/daxdev-errors.c
new file mode 100644
index 0000000..687e593
--- /dev/null
+++ b/test/daxdev-errors.c
@@ -0,0 +1,361 @@ 
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <stdlib.h>
+#include <linux/fs.h>
+#include <linux/fiemap.h>
+#include <setjmp.h>
+#include <limits.h>
+
+#include <util/log.h>
+#include <util/sysfs.h>
+#include <ccan/array_size/array_size.h>
+#include <ndctl/libndctl.h>
+#include <daxctl/libdaxctl.h>
+#ifdef HAVE_NDCTL_H
+#include <linux/ndctl.h>
+#else
+#include <ndctl.h>
+#endif
+
+#define fail() fprintf(stderr, "%s: failed at: %d\n", __func__, __LINE__)
+
+struct check_cmd {
+	struct ndctl_cmd *cmd;
+	struct ndctl_test *test;
+};
+
+static sigjmp_buf sj_env;
+static int sig_count;
+
+static const char *NFIT_PROVIDER0 = "nfit_test.0";
+static struct check_cmd *check_cmds;
+
+static void sigbus_hdl(int sig, siginfo_t *siginfo, void *ptr)
+{
+	fprintf(stderr, "** Received a SIGBUS **\n");
+	sig_count++;
+	siglongjmp(sj_env, 1);
+}
+
+static int check_ars_cap(struct ndctl_bus *bus, uint64_t start,
+		size_t size, struct check_cmd *check)
+{
+	struct ndctl_cmd *cmd;
+	int rc;
+
+	if (check->cmd != NULL) {
+		fprintf(stderr, "%s: expected a NULL command, by default\n",
+				__func__);
+		return -EINVAL;
+	}
+
+	cmd = ndctl_bus_cmd_new_ars_cap(bus, start, size);
+	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) < sizeof(struct nd_cmd_ars_status)){
+		fprintf(stderr, "%s: bus: %s expected size >= %zd got: %d\n",
+				__func__, ndctl_bus_get_provider(bus),
+				sizeof(struct nd_cmd_ars_status),
+				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 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: expected a NULL command, by default\n",
+				__func__);
+		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 check_cmd *check)
+{
+	struct ndctl_cmd *cmd_ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd;
+	struct ndctl_cmd *cmd;
+	unsigned long tmo = 5;
+	unsigned int i;
+	int rc;
+
+	if (check->cmd != NULL) {
+		fprintf(stderr, "%s: expected a NULL command, by default\n",
+				__func__);
+		return -ENXIO;
+	}
+
+ retry:
+	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;
+	}
+
+	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 (!tmo) {
+		fprintf(stderr, "%s: bus: %s ars timeout\n", __func__,
+				ndctl_bus_get_provider(bus));
+		return -EIO;
+	}
+
+	if (ndctl_cmd_ars_in_progress(cmd)) {
+		tmo--;
+		sleep(1);
+		goto retry;
+	}
+
+	for (i = 0; i < ndctl_cmd_ars_num_records(cmd); i++) {
+		fprintf(stderr, "%s: record[%d].addr: 0x%llx\n", __func__, i,
+				ndctl_cmd_ars_get_record_addr(cmd, i));
+		fprintf(stderr, "%s: record[%d].length: 0x%llx\n", __func__, i,
+				ndctl_cmd_ars_get_record_len(cmd, i));
+	}
+
+	check->cmd = cmd;
+	return 0;
+}
+
+static int check_clear_error(struct ndctl_bus *bus, struct check_cmd *check)
+{
+	struct ndctl_cmd *ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd;
+	struct ndctl_cmd *clear_err;
+	unsigned long long cleared;
+	struct ndctl_range range;
+	int rc;
+
+	if (check->cmd != NULL) {
+		fprintf(stderr, "%s: expected a NULL command, by default\n",
+				__func__);
+		return -ENXIO;
+	}
+
+	if (ndctl_cmd_ars_cap_get_range(ars_cap, &range)) {
+		fprintf(stderr, "failed to get ars_cap range\n");
+		return -ENXIO;
+	}
+
+	fprintf(stderr, "%s: clearing at %#llx for %llu bytes\n",
+			__func__, range.address, range.length);
+
+	clear_err = ndctl_bus_cmd_new_clear_error(range.address,
+					range.length, ars_cap);
+	if (!clear_err) {
+		fprintf(stderr, "%s: bus: %s failed to create cmd\n",
+				__func__, ndctl_bus_get_provider(bus));
+		return -ENOTTY;
+	}
+
+	rc = ndctl_cmd_submit(clear_err);
+	if (rc) {
+		fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n",
+				__func__, ndctl_bus_get_provider(bus), rc);
+		ndctl_cmd_unref(clear_err);
+		return rc;
+	}
+
+	cleared = ndctl_cmd_clear_error_get_cleared(clear_err);
+	if (cleared != range.length) {
+		fprintf(stderr, "%s: bus: %s expected to clear: %lld actual: %lld\
+n",
+				__func__, ndctl_bus_get_provider(bus),
+				range.length, cleared);
+		return -ENXIO;
+	}
+
+	check->cmd = clear_err;
+	return 0;
+}
+
+static struct ndctl_dax * get_dax_region(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 int test_daxdev_clear_error(void)
+{
+	int rc = 0, i;
+	struct ndctl_ctx *ctx;
+	struct ndctl_bus *bus;
+	struct ndctl_region *region;
+	struct ndctl_dax *dax = NULL;
+	uint64_t base, start, offset, blocks, size;
+        struct check_cmd __bus_cmds[] = {
+                [ND_CMD_ARS_CAP] = {},
+                [ND_CMD_ARS_START] = {},
+                [ND_CMD_ARS_STATUS] = {},
+                [ND_CMD_CLEAR_ERROR] = {},
+        };
+	char path[256];
+	char buf[SYSFS_ATTR_SIZE];
+	struct log_ctx log_ctx;
+
+	log_init(&log_ctx, "test/init", "NDCTL_DAXDEV_TEST");
+	rc = ndctl_new(&ctx);
+	if (rc)
+		return rc;
+
+	bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0);
+	if (!bus) {
+		rc = -ENODEV;
+		goto cleanup;
+	}
+
+	ndctl_region_foreach(bus, region) {
+		/* find the dax region */
+		dax = get_dax_region(region);
+		if (dax)
+			break;
+	}
+
+	if (!dax) {
+		rc = -ENODEV;
+		goto cleanup;
+	}
+
+	/* get badblocks */
+	if (snprintf(path, 256,
+			"/sys/devices/platform/%s/ndbus0/%s/badblocks",
+			NFIT_PROVIDER0,
+			ndctl_region_get_devname(region)) >= 256) {
+		fprintf(stderr, "%s: buffer too small!\n",
+				ndctl_region_get_devname(region));
+		rc = -ENXIO;
+		goto cleanup;
+	}
+
+	if (__sysfs_read_attr(&log_ctx, path, buf) < 0) {
+		rc = -ENXIO;
+		goto cleanup;
+	}
+
+	/* retrieve badblocks from buf */
+	rc = sscanf(buf, "%lu %lu", &offset, &blocks);
+	if (rc == EOF) {
+		rc = -errno;
+		goto cleanup;
+	}
+
+	/* get resource base */
+	base = ndctl_region_get_resource(region);
+	if (base == ULLONG_MAX) {
+		rc = -ERANGE;
+		goto cleanup;
+	}
+
+	check_cmds = __bus_cmds;
+	start = base + offset * 512;
+	size = 512 * blocks;
+
+	rc = check_ars_cap(bus, start, size, &check_cmds[ND_CMD_ARS_CAP]);
+	if (rc < 0)
+		goto cleanup;
+
+	rc = check_ars_start(bus, &check_cmds[ND_CMD_ARS_START]);
+	if (rc < 0)
+		goto cleanup;
+
+	rc = check_ars_status(bus, &check_cmds[ND_CMD_ARS_STATUS]);
+	if (rc < 0)
+		goto cleanup;
+
+	rc = check_clear_error(bus, &check_cmds[ND_CMD_CLEAR_ERROR]);
+	if (rc < 0)
+		goto cleanup;
+
+	for (i = 1; i < (int)ARRAY_SIZE(__bus_cmds); i++) {
+		if (__bus_cmds[i].cmd) {
+			ndctl_cmd_unref(__bus_cmds[i].cmd);
+			__bus_cmds[i].cmd = NULL;
+                }
+	}
+
+cleanup:
+	ndctl_unref(ctx);
+	return rc;
+}
+
+
+int main(int argc, char *argv[])
+{
+	int rc;
+	struct sigaction act;
+
+	if (argc < 1)
+		return -EINVAL;
+
+	memset(&act, 0, sizeof(act));
+	act.sa_sigaction = sigbus_hdl;
+	act.sa_flags = SA_SIGINFO;
+
+	if (sigaction(SIGBUS, &act, 0)) {
+		fail();
+		return 1;
+	}
+
+	rc = test_daxdev_clear_error();
+
+	return rc;
+}
diff --git a/test/daxdev-errors.sh b/test/daxdev-errors.sh
new file mode 100755
index 0000000..4ba8fb5
--- /dev/null
+++ b/test/daxdev-errors.sh
@@ -0,0 +1,71 @@ 
+#!/bin/bash -x
+
+DEV=""
+NDCTL="../ndctl/ndctl"
+DAXCTL="../daxctl/daxctl"
+BUS="-b nfit_test.0"
+BUS1="-b nfit_test.1"
+rc=77
+
+err() {
+	rc=1
+	echo "test/daxdev-errors: failed at line $1"
+	exit $rc
+}
+
+eval $(uname -r | awk -F. '{print "maj="$1 ";" "min="$2}')
+if [ $maj -lt 4 ]; then
+	echo "kernel $maj.$min lacks dax dev error handling"
+	exit $rc
+elif [ $maj -eq 4 -a $min -lt 10 ]; then
+	echo "kernel $maj.$min lacks dax dev error handling"
+	exit $rc
+fi
+
+set -e
+trap 'err $LINENO' ERR
+
+# setup (reset nfit_test dimms)
+modprobe nfit_test
+$NDCTL disable-region $BUS all
+$NDCTL zero-labels $BUS all
+$NDCTL enable-region $BUS all
+
+json=$($NDCTL create-namespace $BUS -t pmem -m dax -a 4096)
+chardev=$(echo $json | jq ". | select(.mode == \"dax\") | .daxregion.devices[0].chardev")
+
+#  "dev":"namespace5.0",
+#  "mode":"dax",
+#  "size":64475136,
+#  "uuid":"068776fa-50e6-4c27-b99a-4b1e710c627c",
+#  "daxregion":{
+#    "id":5,
+#    "size":64475136,
+#    "align":4096,
+#    "devices":[
+#      {
+#        "chardev":"dax5.0",
+#        "size":64475136
+#      }
+#    ]
+#  }
+
+read sector len < /sys/bus/platform/devices/nfit_test.0/ndbus0/region5/badblocks
+echo "sector: $sector len: $len"
+
+# run the daxdev-errors test
+test -x ./daxdev-errors
+./daxdev-errors
+
+# check badblocks, should be empty
+if read sector len < /sys/bus/platform/devices/nfit_test.0/ndbus0/region5/badblocks; then
+	echo "badblocks empty, expected"
+fi
+[ -n "$sector" ] && echo "fail: $LINENO" && exit 1
+
+# cleanup
+$NDCTL disable-region $BUS all
+$NDCTL disable-region $BUS1 all
+modprobe -r nfit_test
+
+exit 0