diff --git a/Documentation/Makefile.am b/Documentation/Makefile.am
index 6daeb56..0c04fde 100644
--- a/Documentation/Makefile.am
+++ b/Documentation/Makefile.am
@@ -13,6 +13,7 @@ man1_MANS = \
 	ndctl-create-namespace.1 \
 	ndctl-destroy-namespace.1 \
 	ndctl-list.1 \
+	ndctl-msft-dsm.1 \
 	daxctl-list.1
 
 CLEANFILES = $(man1_MANS)
diff --git a/Documentation/ndctl-msft-dsm.txt b/Documentation/ndctl-msft-dsm.txt
new file mode 100644
index 0000000..8fe03be
--- /dev/null
+++ b/Documentation/ndctl-msft-dsm.txt
@@ -0,0 +1,83 @@
+ndctl-msft-dsm(1)
+=================
+
+NAME
+----
+ndctl-msft-dsm - dump the NVDIMM hardware data in json via _DSM calls
+
+SYNOPSIS
+--------
+[verse]
+'ndctl msft-dsm' [<options>]
+
+Walk all the nvdimm buses in the system and list all attached devices
+along with some of their hardware data.
+
+Options can be specified to limit the output to devices of a certain
+class.  Where the classes are buses, dimms.
+
+EXAMPLE
+-------
+[verse]
+# ndctl msft-dsm --buses --dimms --idle
+
+["literal"]
+{
+  "provider":"ACPI.NFIT",
+  "dev":"ndbus0",
+  "dimms":[
+    {
+      "dev":"nmem0",
+      "id":"0000-00000000",
+      "state":"disabled",
+      "_DSM":{
+        "firmware_revision":23129,
+        "save_op_timeout":"70 seconds",
+        "restore_op_timeout":"45 seconds",
+        "erase_op_timeout":"5 seconds",
+        "arm_op_timeout":"10 seconds",
+        "firmware_op_timeout":"10 seconds",
+        "abort_op_timeout_milliseconds":255,
+        "averag_power_required_save_op_milliwatts":0,
+        "averag_idle_power_required_after_save_op_completes_milliwatts":0,
+        "min_voltage_ES_service_during_save_op_millivolts":5799,
+        "max_voltage_ES_service_during_save_op_millivolts":13799,
+        "NVM_lifetime_percentage_warning_threshold":0,
+        "NVM_lifetime_percentage_error_threshold":0,
+        "ES_lifetime_percentage_warning_threshold":0,
+        "ES_lifetime_percentage_error_threshold":0,
+        "ES_temperature_warning_threshold_celsius":0,
+        "ES_temperature_error_threshold_celsius":0,
+        "ES_lifetime_percentage":0,
+        "ES_current_temperature_celsius":0,
+        "ES_total_runtime_hours":0
+      }
+    }
+  ]
+}
+
+OPTIONS
+-------
+-b::
+--bus=::
+	A bus id. Filter listing by devices that reference the given bus.
+
+-d::
+--dimm=::
+	An 'nmemX' device name, or dimm id number. Filter listing by
+	devices that reference the given dimm.
+-B::
+--buses::
+	Include bus info in the listing
+
+-D::
+--dimms::
+	Include dimm info in the listing
+
+-i::
+--idle::
+	Include idle (not enabled) devices in the listing
+
+SEE ALSO
+--------
+linkndctl:ndctl-list[1]
diff --git a/builtin.h b/builtin.h
index 9b66196..425929d 100644
--- a/builtin.h
+++ b/builtin.h
@@ -22,6 +22,7 @@ int cmd_read_labels(int argc, const char **argv, void *ctx);
 int cmd_init_labels(int argc, const char **argv, void *ctx);
 int cmd_check_labels(int argc, const char **argv, void *ctx);
 int cmd_list(int argc, const char **argv, void *ctx);
+int cmd_msft_dsm(int argc, const char **argv, void *ctx);
 #ifdef ENABLE_TEST
 int cmd_test(int argc, const char **argv, void *ctx);
 #endif
diff --git a/ndctl/Makefile.am b/ndctl/Makefile.am
index f85f9cb..0519e52 100644
--- a/ndctl/Makefile.am
+++ b/ndctl/Makefile.am
@@ -9,6 +9,7 @@ ndctl_SOURCES = ndctl.c \
 		builtin-dimm.c \
 		 ../util/log.c \
 		builtin-list.c \
+		builtin-msft-dsm.c \
 		builtin-test.c \
 		../util/json.c \
 		util/json-msft.c
diff --git a/ndctl/builtin-msft-dsm.c b/ndctl/builtin-msft-dsm.c
new file mode 100644
index 0000000..c7a5f7d
--- /dev/null
+++ b/ndctl/builtin-msft-dsm.c
@@ -0,0 +1,173 @@
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <util/json.h>
+#include <util/filter.h>
+#include <json-c/json.h>
+#include <ndctl/libndctl.h>
+#include <util/parse-options.h>
+#include <ccan/array_size/array_size.h>
+
+#ifdef HAVE_NDCTL_H
+#include <linux/ndctl.h>
+#else
+#include <ndctl.h>
+#endif
+
+static struct {
+	bool buses;
+	bool dimms;
+	bool idle;
+} list;
+
+static struct {
+	const char *bus;
+	const char *type;
+	const char *dimm;
+} param;
+
+static int did_fail;
+static int jflag = JSON_C_TO_STRING_PRETTY;
+
+#define fail(fmt, ...) \
+do { \
+	did_fail = 1; \
+	fprintf(stderr, "ndctl-%s:%s:%d: " fmt, \
+			VERSION, __func__, __LINE__, ##__VA_ARGS__); \
+} while (0)
+
+static int num_list_flags(void)
+{
+	return list.buses + list.dimms;
+}
+
+int cmd_msft_dsm(int argc, const char **argv, void *ctx)
+{
+	const struct option options[] = {
+		OPT_STRING('b', "bus", &param.bus, "bus-id", "filter by bus"),
+		OPT_STRING('d', "dimm", &param.dimm, "dimm-id",
+				"filter by dimm"),
+		OPT_BOOLEAN('B', "buses", &list.buses, "include bus info"),
+		OPT_BOOLEAN('D', "dimms", &list.dimms, "include dimm info"),
+		OPT_BOOLEAN('i', "idle", &list.idle, "include idle devices"),
+		OPT_END(),
+	};
+	const char * const u[] = {
+		"ndctl msft-dsm [<options>]",
+		NULL
+	};
+	struct json_object *jdimms = NULL;
+	struct json_object *jbuses = NULL;
+	struct ndctl_bus *bus;
+	int i;
+
+        argc = parse_options(argc, argv, options, u, 0);
+	for (i = 0; i < argc; i++)
+		error("unknown parameter \"%s\"\n", argv[i]);
+
+	if (argc)
+		usage_with_options(u, options);
+
+	if (num_list_flags() == 0) {
+		list.buses = !!param.bus;
+		list.dimms = !!param.dimm;
+	}
+
+	ndctl_bus_foreach(ctx, bus) {
+		struct json_object *jbus = NULL;
+		struct ndctl_dimm *dimm;
+
+		if (!util_bus_filter(bus, param.bus)
+				|| !util_bus_filter_by_dimm(bus, param.dimm))
+			continue;
+
+		if (list.buses) {
+			if (!jbuses) {
+				jbuses = json_object_new_array();
+				if (!jbuses) {
+					fail("\n");
+					continue;
+				}
+			}
+
+			jbus = util_bus_to_json(bus);
+			if (!jbus) {
+				fail("\n");
+				continue;
+			}
+			json_object_array_add(jbuses, jbus);
+		}
+
+		ndctl_dimm_foreach(bus, dimm) {
+			struct json_object *jdimm;
+
+			/* are we emitting dimms? */
+			if (!list.dimms)
+				break;
+
+			if (!util_dimm_filter(dimm, param.dimm))
+				continue;
+
+			if (!list.idle && !ndctl_dimm_is_enabled(dimm))
+				continue;
+
+			if (!jdimms) {
+				jdimms = json_object_new_array();
+				if (!jdimms) {
+					fail("\n");
+					continue;
+				}
+
+				if (jbus)
+					json_object_object_add(jbus, "dimms", jdimms);
+			}
+
+			jdimm = util_dimm_to_json(dimm);
+			if (!jdimm) {
+				fail("\n");
+				continue;
+			}
+
+			if (ndctl_dimm_get_dsm_family(dimm) == NVDIMM_FAMILY_MSFT) {
+				struct json_object *jdsm;
+				jdsm = util_dimm_dsm_to_json_msft(dimm);
+				if (jdsm)
+					json_object_object_add(jdimm, "_DSM", jdsm);
+			}
+
+			/*
+			 * Without a bus we are collecting dimms anonymously
+			 * across the platform.
+			 */
+			json_object_array_add(jdimms, jdimm);
+		}
+
+		if (jbuses) {
+			jdimms = NULL;
+		}
+	}
+
+	if (jbuses)
+		util_display_json_array(stdout, jbuses, jflag);
+	else if ((!!jdimms) > 1) {
+		struct json_object *jplatform = json_object_new_object();
+
+		if (!jplatform) {
+			fail("\n");
+			return -ENOMEM;
+		}
+
+		if (jdimms)
+			json_object_object_add(jplatform, "dimms", jdimms);
+		printf("%s\n", json_object_to_json_string_ext(jplatform,
+					jflag));
+		json_object_put(jplatform);
+	} else if (jdimms)
+		util_display_json_array(stdout, jdimms, jflag);
+
+	if (did_fail)
+		return -ENOMEM;
+	return 0;
+}
diff --git a/ndctl/ndctl.c b/ndctl/ndctl.c
index 80a0491..aef2fc2 100644
--- a/ndctl/ndctl.c
+++ b/ndctl/ndctl.c
@@ -67,6 +67,7 @@ static struct cmd_struct commands[] = {
 	{ "check-labels", cmd_check_labels },
 	{ "list", cmd_list },
 	{ "help", cmd_help },
+	{ "msft-dsm", cmd_msft_dsm },
 	#ifdef ENABLE_TEST
 	{ "test", cmd_test },
 	#endif
diff --git a/ndctl/util/json-msft.c b/ndctl/util/json-msft.c
index 9e41fde..2dca173 100644
--- a/ndctl/util/json-msft.c
+++ b/ndctl/util/json-msft.c
@@ -79,6 +79,8 @@ struct json_object *util_dimm_health_to_json_msft(struct ndctl_dimm *dimm)
 		goto err_fd;
 	}
 
+	/* ======================== */
+
 	error = call_func(FUNC10, SIZE_IN_FUNC10, SIZE_OUT_FUNC10, fd, payload);
 	if (!error) {
 		if (!!(payload[4] & 0x0F))
@@ -115,6 +117,8 @@ struct json_object *util_dimm_health_to_json_msft(struct ndctl_dimm *dimm)
 			json_object_object_add(jhealth, "last_save_operation", jobj);
 	}
 
+	/* ======================== */
+
 	close(fd);
 	return jhealth;
 
@@ -123,3 +127,178 @@ err_fd:
 
 	return NULL;
 }
+
+struct json_object *util_dimm_dsm_to_json_msft(struct ndctl_dimm *dimm)
+{
+
+	struct json_object *jdsm = json_object_new_object();
+	struct json_object *jobj;
+	char dev_name[DEV_NAME_MAX_LENGTH + 1];
+	unsigned char payload[SIZE_OUT_MAX];
+	char str[100];
+	int fd;
+	int error;
+	int number;
+
+	memset(dev_name, 0, DEV_NAME_MAX_LENGTH + 1);
+	snprintf(dev_name, DEV_NAME_MAX_LENGTH + 1, "/dev/nmem%d", ndctl_dimm_get_id(dimm));
+	fd = open(dev_name, O_RDWR);
+	if (fd == -1) {
+		fprintf(stderr, "error opening %s errno=%d\n", dev_name, errno);
+		goto err_fd;
+	}
+
+	/* ======================== */
+
+	error = call_func(FUNC1, SIZE_IN_FUNC1, SIZE_OUT_FUNC1, fd, payload);
+	if (!error) {
+		number = payload[13] << 8 | payload[12];
+		jobj = json_object_new_int(number);
+		if (jobj)
+			json_object_object_add(jdsm, "firmware_revision", jobj);
+
+		number = ((payload[21] & 0x7f) << 8) | payload[20];
+		if (!!(payload[21] & 0x80)) {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d seconds", number);
+			jobj = json_object_new_string(str);
+		} else {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d milliseconds", number);
+			jobj = json_object_new_string(str);
+		}
+		if (jobj)
+			json_object_object_add(jdsm, "save_op_timeout", jobj);
+
+		number = ((payload[25] & 0x7f) << 8) | payload[24];
+		if (!!(payload[25] & 0x80)) {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d seconds", number);
+			jobj = json_object_new_string(str);
+		} else {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d milliseconds", number);
+			jobj = json_object_new_string(str);
+		}
+		if (jobj)
+			json_object_object_add(jdsm, "restore_op_timeout", jobj);
+
+		number = ((payload[29] & 0x7f) << 8) | payload[28];
+		if (!!(payload[29] & 0x80)) {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d seconds", number);
+			jobj = json_object_new_string(str);
+		} else {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d milliseconds", number);
+			jobj = json_object_new_string(str);
+		}
+		if (jobj)
+			json_object_object_add(jdsm, "erase_op_timeout", jobj);
+
+		number = ((payload[33] & 0x7f) << 8) | payload[32];
+		if (!!(payload[33] & 0x80)) {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d seconds", number);
+			jobj = json_object_new_string(str);
+		} else {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d milliseconds", number);
+			jobj = json_object_new_string(str);
+		}
+		if (jobj)
+			json_object_object_add(jdsm, "arm_op_timeout", jobj);
+
+		number = ((payload[37] & 0x7f) << 8) | payload[36];
+		if (!!(payload[37] & 0x80)) {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d seconds", number);
+			jobj = json_object_new_string(str);
+		} else {
+			memset(str, 0, 100);
+			snprintf(str, 100, "%d milliseconds", number);
+			jobj = json_object_new_string(str);
+		}
+		if (jobj)
+			json_object_object_add(jdsm, "firmware_op_timeout", jobj);
+
+		jobj = json_object_new_int(payload[40]);
+		if (jobj)
+			json_object_object_add(jdsm, "abort_op_timeout_milliseconds", jobj);
+	}
+
+
+	error = call_func(FUNC2, SIZE_IN_FUNC2, SIZE_OUT_FUNC2, fd, payload);
+	if (!error) {
+		jobj = json_object_new_int(payload[5] << 8 | payload[4]);
+		if (jobj)
+			json_object_object_add(jdsm, "averag_power_required_save_op_milliwatts", jobj);
+
+		jobj = json_object_new_int(payload[7] << 8 | payload[6]);
+		if (jobj)
+			json_object_object_add(jdsm, "averag_idle_power_required_after_save_op_completes_milliwatts", jobj);
+
+		jobj = json_object_new_int(payload[9] << 8 | payload[8]);
+		if (jobj)
+			json_object_object_add(jdsm, "min_voltage_ES_service_during_save_op_millivolts", jobj);
+
+		jobj = json_object_new_int(payload[11] << 8 | payload[10]);
+		if (jobj)
+			json_object_object_add(jdsm, "max_voltage_ES_service_during_save_op_millivolts", jobj);
+	}
+
+
+	error = call_func(FUNC5, SIZE_IN_FUNC5, SIZE_OUT_FUNC5, fd, payload);
+	if (!error) {
+		jobj = json_object_new_int(payload[4]);
+		if (jobj)
+			json_object_object_add(jdsm, "NVM_lifetime_percentage_warning_threshold", jobj);
+
+		jobj = json_object_new_int(payload[5]);
+		if (jobj)
+			json_object_object_add(jdsm, "NVM_lifetime_percentage_error_threshold", jobj);
+	}
+
+	error = call_func(FUNC7, SIZE_IN_FUNC7, SIZE_OUT_FUNC7, fd, payload);
+	if (!error) {
+		jobj = json_object_new_int(payload[4]);
+		if (jobj)
+			json_object_object_add(jdsm, "ES_lifetime_percentage_warning_threshold", jobj);
+
+		jobj = json_object_new_int(payload[5]);
+		if (jobj)
+			json_object_object_add(jdsm, "ES_lifetime_percentage_error_threshold", jobj);
+
+		jobj = json_object_new_int(payload[6]);
+		if (jobj)
+			json_object_object_add(jdsm, "ES_temperature_warning_threshold_celsius", jobj);
+
+		jobj = json_object_new_int(payload[7]);
+		if (jobj)
+			json_object_object_add(jdsm, "ES_temperature_error_threshold_celsius", jobj);
+	}
+
+	error = call_func(FUNC12, SIZE_IN_FUNC12, SIZE_OUT_FUNC12, fd, payload);
+	if (!error) {
+		jobj = json_object_new_int(payload[4]);
+		if (jobj)
+			json_object_object_add(jdsm, "ES_lifetime_percentage", jobj);
+
+		jobj = json_object_new_int((payload[6] << 8) | payload[5]);
+		if (jobj)
+			json_object_object_add(jdsm, "ES_current_temperature_celsius", jobj);
+
+		jobj = json_object_new_int((payload[8] << 8) | payload[7]);
+		if (jobj)
+			json_object_object_add(jdsm, "ES_total_runtime_hours", jobj);
+	}
+
+	/* ======================== */
+	close(fd);
+	return jdsm;
+
+err_fd:
+	json_object_put(jdsm);
+
+	return NULL;
+}
diff --git a/util/json.h b/util/json.h
index 3a41977..675132f 100644
--- a/util/json.h
+++ b/util/json.h
@@ -30,4 +30,5 @@ static inline struct json_object *util_dimm_health_to_json(
 }
 #endif
 struct json_object *util_dimm_health_to_json_msft(struct ndctl_dimm *dimm);
+struct json_object *util_dimm_dsm_to_json_msft(struct ndctl_dimm *dimm);
 #endif /* __NDCTL_JSON_H__ */
