Linux CXL
 help / color / mirror / Atom feed
* [PATCH V5 0/2] daxctl: Add support for famfs mode
       [not found] <20260430153331.84139-1-john@jagalactic.com>
@ 2026-04-30 15:33 ` John Groves
  2026-04-30 15:34   ` [PATCH V5 1/2] " John Groves
  2026-04-30 15:34   ` [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions: John Groves
  0 siblings, 2 replies; 8+ messages in thread
From: John Groves @ 2026-04-30 15:33 UTC (permalink / raw)
  To: John Groves, John Groves, Dan Williams, Alison Schofield
  Cc: John Groves, Vishal Verma, Dave Jiang, Jonathan Cameron,
	Aravind Ramesh, Ajay Joshi, venkataravis@micron.com,
	dev.srinivasulu@gmail.com, linux-kernel@vger.kernel.org,
	nvdimm@lists.linux.dev, linux-cxl@vger.kernel.org, John Groves

From: John Groves <john@groves.net>

This series adds famfs mode support to daxctl, alongside the existing
devdax and system-ram modes.  A daxdev is in famfs mode when it is bound
to fsdev_dax.ko (drivers/dax/fsdev.c).

Patch 1 adds the library plumbing: mode detection helpers, an enable
function, and the device.c reconfigure-device wiring.  Patch 2 adds a
test that exercises mode transitions.

This series depends on the fsdev_dax kernel driver (which provides famfs
mode) and on the famfs kernel patch series.

Patches:
  1/2 daxctl: Add support for famfs mode
  2/2 Add test/daxctl-famfs.sh to test famfs mode transitions

Changes since V4 (addressing Alison and Ira's comments):
- Consolidate the per-mode driver-symlink lookups into a single static
  helper daxctl_dev_bound_to_module(); the three mode predicates now
  delegate to it.
- Use PATH_MAX instead of a hardcoded char[200] in the mode-check path.
- Use path_basename() instead of basename().
- Emit a dbg() message when realpath() on the driver symlink returns
  NULL.
- Revert the json.c change that reported unbound devices as 'unknown'.
  An unbound device is reported as 'devdax' again, matching the prior
  behavior; the configured-vs-active distinction Alison raised goes
  away.
- Add missing fprintf(stderr) "disable failed" messages in the
  "already in target mode, just re-enable" branches of
  reconfig_mode_devdax() and reconfig_mode_famfs(), matching the
  pattern used by the disable_*_device() helpers.
- Reword commit messages: clarify that the test is in a separate
  patch, drop the stale "json.c shows 'unknown'" line, and replace
  "Fix mode transition logic" with "Update mode transition logic"
  since these are extensions for the new mode rather than fixes to
  broken existing behavior.

John Groves (2):
  daxctl: Add support for famfs mode
  Add test/daxctl-famfs.sh to test famfs mode transitions:

 daxctl/device.c                | 132 +++++++++++++++--
 daxctl/json.c                  |  13 +-
 daxctl/lib/libdaxctl-private.h |   2 +
 daxctl/lib/libdaxctl.c         |  39 ++++-
 daxctl/lib/libdaxctl.sym       |   7 +
 daxctl/libdaxctl.h             |   3 +
 test/daxctl-famfs.sh           | 253 +++++++++++++++++++++++++++++++++
 test/meson.build               |   2 +
 8 files changed, 436 insertions(+), 15 deletions(-)
 create mode 100755 test/daxctl-famfs.sh


base-commit: 8ad90e54f0ff4f7291e7f21d44d769d10f24e2b6
-- 
2.53.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH V5 1/2] daxctl: Add support for famfs mode
  2026-04-30 15:33 ` [PATCH V5 0/2] daxctl: Add support for famfs mode John Groves
@ 2026-04-30 15:34   ` John Groves
  2026-04-30 16:20     ` Dave Jiang
  2026-05-13  5:32     ` Alison Schofield
  2026-04-30 15:34   ` [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions: John Groves
  1 sibling, 2 replies; 8+ messages in thread
From: John Groves @ 2026-04-30 15:34 UTC (permalink / raw)
  To: John Groves, John Groves, Dan Williams, Alison Schofield
  Cc: John Groves, Vishal Verma, Dave Jiang, Jonathan Cameron,
	Aravind Ramesh, Ajay Joshi, venkataravis@micron.com,
	dev.srinivasulu@gmail.com, linux-kernel@vger.kernel.org,
	nvdimm@lists.linux.dev, linux-cxl@vger.kernel.org, John Groves

From: John Groves <John@Groves.net>

Putting a daxdev in famfs mode means binding it to fsdev_dax.ko
(drivers/dax/fsdev.c). Finding a daxdev bound to fsdev_dax means
it is in famfs mode.

A test for this functionality is added in the next commit.

With devdax, famfs, and system-ram modes, the previous logic that assumed
'not in mode X means in mode Y' needed to get slightly more complicated.

Add explicit mode detection functions:
- daxctl_dev_is_famfs_mode(): check if bound to fsdev_dax driver
- daxctl_dev_is_devdax_mode(): check if bound to device_dax driver
Both delegate to a shared static helper daxctl_dev_bound_to_module() to
avoid duplicating the driver-symlink lookup, as does the pre-existing
daxctl_dev_is_system_ram_capable().

Update mode transition logic in device.c:
- disable_devdax_device(): verify device is actually in devdax mode
- disable_famfs_device(): verify device is actually in famfs mode
- All reconfig_mode_*() functions explicitly check each mode
- Handle unrecognized mode with an error instead of wrong assumption

Update json.c to report fsdev_dax-bound devices as 'famfs' mode.  An
unbound device continues to be reported as 'devdax' (the legacy default
when no driver is bound), to preserve existing behavior.

Signed-off-by: John Groves <john@groves.net>
---
 daxctl/device.c                | 132 ++++++++++++++++++++++++++++++---
 daxctl/json.c                  |  13 +++-
 daxctl/lib/libdaxctl-private.h |   2 +
 daxctl/lib/libdaxctl.c         |  39 +++++++++-
 daxctl/lib/libdaxctl.sym       |   7 ++
 daxctl/libdaxctl.h             |   3 +
 6 files changed, 181 insertions(+), 15 deletions(-)

diff --git a/daxctl/device.c b/daxctl/device.c
index a4e36b1..003609e 100644
--- a/daxctl/device.c
+++ b/daxctl/device.c
@@ -42,6 +42,7 @@ enum dev_mode {
 	DAXCTL_DEV_MODE_UNKNOWN,
 	DAXCTL_DEV_MODE_DEVDAX,
 	DAXCTL_DEV_MODE_RAM,
+	DAXCTL_DEV_MODE_FAMFS,
 };
 
 struct mapping {
@@ -471,6 +472,13 @@ static const char *parse_device_options(int argc, const char **argv,
 					"--no-online is incompatible with --mode=devdax\n");
 				rc =  -EINVAL;
 			}
+		} else if (strcmp(param.mode, "famfs") == 0) {
+			reconfig_mode = DAXCTL_DEV_MODE_FAMFS;
+			if (param.no_online) {
+				fprintf(stderr,
+					"--no-online is incompatible with --mode=famfs\n");
+				rc =  -EINVAL;
+			}
 		}
 		break;
 	case ACTION_CREATE:
@@ -696,8 +704,42 @@ static int disable_devdax_device(struct daxctl_dev *dev)
 	int rc;
 
 	if (mem) {
-		fprintf(stderr, "%s was already in system-ram mode\n",
-			devname);
+		fprintf(stderr, "%s is in system-ram mode\n", devname);
+		return 1;
+	}
+	if (daxctl_dev_is_famfs_mode(dev)) {
+		fprintf(stderr, "%s is in famfs mode\n", devname);
+		return 1;
+	}
+	if (!daxctl_dev_is_devdax_mode(dev)) {
+		fprintf(stderr, "%s is not in devdax mode\n", devname);
+		return 1;
+	}
+	rc = daxctl_dev_disable(dev);
+	if (rc) {
+		fprintf(stderr, "%s: disable failed: %s\n",
+			daxctl_dev_get_devname(dev), strerror(-rc));
+		return rc;
+	}
+	return 0;
+}
+
+static int disable_famfs_device(struct daxctl_dev *dev)
+{
+	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
+	const char *devname = daxctl_dev_get_devname(dev);
+	int rc;
+
+	if (mem) {
+		fprintf(stderr, "%s is in system-ram mode\n", devname);
+		return 1;
+	}
+	if (daxctl_dev_is_devdax_mode(dev)) {
+		fprintf(stderr, "%s is in devdax mode\n", devname);
+		return 1;
+	}
+	if (!daxctl_dev_is_famfs_mode(dev)) {
+		fprintf(stderr, "%s is not in famfs mode\n", devname);
 		return 1;
 	}
 	rc = daxctl_dev_disable(dev);
@@ -711,6 +753,7 @@ static int disable_devdax_device(struct daxctl_dev *dev)
 
 static int reconfig_mode_system_ram(struct daxctl_dev *dev)
 {
+	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
 	const char *devname = daxctl_dev_get_devname(dev);
 	int rc, skip_enable = 0;
 
@@ -724,11 +767,21 @@ static int reconfig_mode_system_ram(struct daxctl_dev *dev)
 	}
 
 	if (daxctl_dev_is_enabled(dev)) {
-		rc = disable_devdax_device(dev);
-		if (rc < 0)
-			return rc;
-		if (rc > 0)
+		if (mem) {
+			/* already in system-ram mode */
 			skip_enable = 1;
+		} else if (daxctl_dev_is_famfs_mode(dev)) {
+			rc = disable_famfs_device(dev);
+			if (rc)
+				return rc;
+		} else if (daxctl_dev_is_devdax_mode(dev)) {
+			rc = disable_devdax_device(dev);
+			if (rc)
+				return rc;
+		} else {
+			fprintf(stderr, "%s: unknown mode\n", devname);
+			return -EINVAL;
+		}
 	}
 
 	if (!skip_enable) {
@@ -750,7 +803,7 @@ static int disable_system_ram_device(struct daxctl_dev *dev)
 	int rc;
 
 	if (!mem) {
-		fprintf(stderr, "%s was already in devdax mode\n", devname);
+		fprintf(stderr, "%s is not in system-ram mode\n", devname);
 		return 1;
 	}
 
@@ -786,12 +839,31 @@ static int disable_system_ram_device(struct daxctl_dev *dev)
 
 static int reconfig_mode_devdax(struct daxctl_dev *dev)
 {
+	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
+	const char *devname = daxctl_dev_get_devname(dev);
 	int rc;
 
 	if (daxctl_dev_is_enabled(dev)) {
-		rc = disable_system_ram_device(dev);
-		if (rc)
-			return rc;
+		if (mem) {
+			rc = disable_system_ram_device(dev);
+			if (rc)
+				return rc;
+		} else if (daxctl_dev_is_famfs_mode(dev)) {
+			rc = disable_famfs_device(dev);
+			if (rc)
+				return rc;
+		} else if (daxctl_dev_is_devdax_mode(dev)) {
+			/* already in devdax mode, just re-enable */
+			rc = daxctl_dev_disable(dev);
+			if (rc) {
+				fprintf(stderr, "%s: disable failed: %s\n",
+					devname, strerror(-rc));
+				return rc;
+			}
+		} else {
+			fprintf(stderr, "%s: unknown mode\n", devname);
+			return -EINVAL;
+		}
 	}
 
 	rc = daxctl_dev_enable_devdax(dev);
@@ -801,6 +873,43 @@ static int reconfig_mode_devdax(struct daxctl_dev *dev)
 	return 0;
 }
 
+static int reconfig_mode_famfs(struct daxctl_dev *dev)
+{
+	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
+	const char *devname = daxctl_dev_get_devname(dev);
+	int rc;
+
+	if (daxctl_dev_is_enabled(dev)) {
+		if (mem) {
+			fprintf(stderr,
+				"%s is in system-ram mode; must be in devdax mode to convert to famfs\n",
+				devname);
+			return -EINVAL;
+		} else if (daxctl_dev_is_famfs_mode(dev)) {
+			/* already in famfs mode, just re-enable */
+			rc = daxctl_dev_disable(dev);
+			if (rc) {
+				fprintf(stderr, "%s: disable failed: %s\n",
+					devname, strerror(-rc));
+				return rc;
+			}
+		} else if (daxctl_dev_is_devdax_mode(dev)) {
+			rc = disable_devdax_device(dev);
+			if (rc)
+				return rc;
+		} else {
+			fprintf(stderr, "%s: unknown mode\n", devname);
+			return -EINVAL;
+		}
+	}
+
+	rc = daxctl_dev_enable_famfs(dev);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
 static int do_create(struct daxctl_region *region, long long val,
 		     struct json_object **jdevs)
 {
@@ -887,6 +996,9 @@ static int do_reconfig(struct daxctl_dev *dev, enum dev_mode mode,
 	case DAXCTL_DEV_MODE_DEVDAX:
 		rc = reconfig_mode_devdax(dev);
 		break;
+	case DAXCTL_DEV_MODE_FAMFS:
+		rc = reconfig_mode_famfs(dev);
+		break;
 	default:
 		fprintf(stderr, "%s: unknown mode requested: %d\n",
 			devname, mode);
diff --git a/daxctl/json.c b/daxctl/json.c
index 3cbce9d..2a4b12c 100644
--- a/daxctl/json.c
+++ b/daxctl/json.c
@@ -48,8 +48,19 @@ struct json_object *util_daxctl_dev_to_json(struct daxctl_dev *dev,
 
 	if (mem)
 		jobj = json_object_new_string("system-ram");
-	else
+	else if (daxctl_dev_is_famfs_mode(dev))
+		jobj = json_object_new_string("famfs");
+	else if (daxctl_dev_is_devdax_mode(dev))
 		jobj = json_object_new_string("devdax");
+	else {
+		/* Legacy condition; if a daxdev is not in any "mode", that
+		 * means no driver is bound. We report that as a disabled
+		 * device in devdax mode. (the disabled modifier is added later
+		 * in this function if applicable)
+		 */
+		jobj = json_object_new_string("devdax");
+	}
+
 	if (jobj)
 		json_object_object_add(jdev, "mode", jobj);
 
diff --git a/daxctl/lib/libdaxctl-private.h b/daxctl/lib/libdaxctl-private.h
index ae45311..0bb73e8 100644
--- a/daxctl/lib/libdaxctl-private.h
+++ b/daxctl/lib/libdaxctl-private.h
@@ -21,12 +21,14 @@ static const char *dax_subsystems[] = {
 enum daxctl_dev_mode {
 	DAXCTL_DEV_MODE_DEVDAX = 0,
 	DAXCTL_DEV_MODE_RAM,
+	DAXCTL_DEV_MODE_FAMFS,
 	DAXCTL_DEV_MODE_END,
 };
 
 static const char *dax_modules[] = {
 	[DAXCTL_DEV_MODE_DEVDAX] = "device_dax",
 	[DAXCTL_DEV_MODE_RAM] = "kmem",
+	[DAXCTL_DEV_MODE_FAMFS] = "fsdev_dax",
 };
 
 enum memory_op {
diff --git a/daxctl/lib/libdaxctl.c b/daxctl/lib/libdaxctl.c
index 02ae7e5..33121dc 100644
--- a/daxctl/lib/libdaxctl.c
+++ b/daxctl/lib/libdaxctl.c
@@ -385,13 +385,13 @@ static bool device_model_is_dax_bus(struct daxctl_dev *dev)
 	return false;
 }
 
-DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
+static int daxctl_dev_bound_to_module(struct daxctl_dev *dev, const char *mod_name)
 {
 	const char *devname = daxctl_dev_get_devname(dev);
 	struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev);
 	const char *mod_base;
 	char *mod_path;
-	char path[200];
+	char path[PATH_MAX];
 	const int len = sizeof(path);
 
 	if (!device_model_is_dax_bus(dev))
@@ -406,11 +406,13 @@ DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
 	}
 
 	mod_path = realpath(path, NULL);
-	if (!mod_path)
+	if (!mod_path) {
+		dbg(ctx, "%s: realpath failed for driver link\n", devname);
 		return false;
+	}
 
 	mod_base = path_basename(mod_path);
-	if (strcmp(mod_base, dax_modules[DAXCTL_DEV_MODE_RAM]) == 0) {
+	if (strcmp(mod_base, mod_name) == 0) {
 		free(mod_path);
 		return true;
 	}
@@ -419,6 +421,30 @@ DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
 	return false;
 }
 
+DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
+{
+	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_RAM]);
+}
+
+/*
+ * Check if device is currently in famfs mode (bound to fsdev_dax driver).
+ * Returns false for disabled devices: the DAX bus does not retain the previous
+ * driver binding after unbind, so mode cannot be determined without a driver.
+ */
+DAXCTL_EXPORT int daxctl_dev_is_famfs_mode(struct daxctl_dev *dev)
+{
+	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_FAMFS]);
+}
+
+/*
+ * Check if device is currently in devdax mode (bound to device_dax driver).
+ * Returns false for disabled devices; see daxctl_dev_is_famfs_mode().
+ */
+DAXCTL_EXPORT int daxctl_dev_is_devdax_mode(struct daxctl_dev *dev)
+{
+	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_DEVDAX]);
+}
+
 /*
  * This checks for the device to be in system-ram mode, so calling
  * daxctl_dev_get_memory() on a devdax mode device will always return NULL.
@@ -983,6 +1009,11 @@ DAXCTL_EXPORT int daxctl_dev_enable_ram(struct daxctl_dev *dev)
 	return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_RAM);
 }
 
+DAXCTL_EXPORT int daxctl_dev_enable_famfs(struct daxctl_dev *dev)
+{
+	return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_FAMFS);
+}
+
 DAXCTL_EXPORT int daxctl_dev_disable(struct daxctl_dev *dev)
 {
 	const char *devname = daxctl_dev_get_devname(dev);
diff --git a/daxctl/lib/libdaxctl.sym b/daxctl/lib/libdaxctl.sym
index 3098811..2a812c6 100644
--- a/daxctl/lib/libdaxctl.sym
+++ b/daxctl/lib/libdaxctl.sym
@@ -104,3 +104,10 @@ LIBDAXCTL_10 {
 global:
 	daxctl_dev_is_system_ram_capable;
 } LIBDAXCTL_9;
+
+LIBDAXCTL_11 {
+global:
+	daxctl_dev_enable_famfs;
+	daxctl_dev_is_famfs_mode;
+	daxctl_dev_is_devdax_mode;
+} LIBDAXCTL_10;
diff --git a/daxctl/libdaxctl.h b/daxctl/libdaxctl.h
index 53c6bbd..84fcdb4 100644
--- a/daxctl/libdaxctl.h
+++ b/daxctl/libdaxctl.h
@@ -72,12 +72,15 @@ int daxctl_dev_is_enabled(struct daxctl_dev *dev);
 int daxctl_dev_disable(struct daxctl_dev *dev);
 int daxctl_dev_enable_devdax(struct daxctl_dev *dev);
 int daxctl_dev_enable_ram(struct daxctl_dev *dev);
+int daxctl_dev_enable_famfs(struct daxctl_dev *dev);
 int daxctl_dev_get_target_node(struct daxctl_dev *dev);
 int daxctl_dev_will_auto_online_memory(struct daxctl_dev *dev);
 int daxctl_dev_has_online_memory(struct daxctl_dev *dev);
 
 struct daxctl_memory;
 int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev);
+int daxctl_dev_is_famfs_mode(struct daxctl_dev *dev);
+int daxctl_dev_is_devdax_mode(struct daxctl_dev *dev);
 struct daxctl_memory *daxctl_dev_get_memory(struct daxctl_dev *dev);
 struct daxctl_dev *daxctl_memory_get_dev(struct daxctl_memory *mem);
 const char *daxctl_memory_get_node_path(struct daxctl_memory *mem);
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions:
  2026-04-30 15:33 ` [PATCH V5 0/2] daxctl: Add support for famfs mode John Groves
  2026-04-30 15:34   ` [PATCH V5 1/2] " John Groves
@ 2026-04-30 15:34   ` John Groves
  2026-04-30 16:27     ` Dave Jiang
  2026-05-13  5:34     ` Alison Schofield
  1 sibling, 2 replies; 8+ messages in thread
From: John Groves @ 2026-04-30 15:34 UTC (permalink / raw)
  To: John Groves, John Groves, Dan Williams, Alison Schofield
  Cc: John Groves, Vishal Verma, Dave Jiang, Jonathan Cameron,
	Aravind Ramesh, Ajay Joshi, venkataravis@micron.com,
	dev.srinivasulu@gmail.com, linux-kernel@vger.kernel.org,
	nvdimm@lists.linux.dev, linux-cxl@vger.kernel.org, John Groves

From: John Groves <John@Groves.net>

- devdax <-> famfs mode switches
- Verify famfs -> system-ram is rejected (must go via devdax)
- Test JSON output shows correct mode
- Test error handling for invalid modes

Signed-off-by: John Groves <john@groves.net>
---
 test/daxctl-famfs.sh | 253 +++++++++++++++++++++++++++++++++++++++++++
 test/meson.build     |   2 +
 2 files changed, 255 insertions(+)
 create mode 100755 test/daxctl-famfs.sh

diff --git a/test/daxctl-famfs.sh b/test/daxctl-famfs.sh
new file mode 100755
index 0000000..12fbfef
--- /dev/null
+++ b/test/daxctl-famfs.sh
@@ -0,0 +1,253 @@
+#!/bin/bash -Ex
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2025 Micron Technology, Inc. All rights reserved.
+#
+# Test daxctl famfs mode transitions and mode detection
+
+rc=77
+. $(dirname $0)/common
+
+trap 'cleanup $LINENO' ERR
+
+daxdev=""
+original_mode=""
+
+cleanup()
+{
+	printf "Error at line %d\n" "$1"
+	# Try to restore to original mode if we know it
+	if [[ $daxdev && $original_mode ]]; then
+		"$DAXCTL" reconfigure-device -f -m "$original_mode" "$daxdev" 2>/dev/null || true
+	fi
+	exit $rc
+}
+
+# Check if fsdev_dax module is available
+check_fsdev_dax()
+{
+	if modinfo fsdev_dax &>/dev/null; then
+		return 0
+	fi
+	if grep -qF "fsdev_dax" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null; then
+		return 0
+	fi
+	printf "fsdev_dax module not available, skipping\n"
+	exit 77
+}
+
+# Check if kmem module is available (needed for system-ram mode tests)
+check_kmem()
+{
+	if modinfo kmem &>/dev/null; then
+		return 0
+	fi
+	if grep -qF "kmem" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null; then
+		return 0
+	fi
+	printf "kmem module not available, skipping system-ram tests\n"
+	return 1
+}
+
+# Find an existing dax device to test with
+find_daxdev()
+{
+	# Look for any available dax device
+	daxdev=$("$DAXCTL" list | jq -er '.[0].chardev // empty' 2>/dev/null) || true
+
+	if [[ ! $daxdev ]]; then
+		printf "No dax device found, skipping\n"
+		exit 77
+	fi
+
+	# Save the original mode so we can restore it
+	original_mode=$("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode')
+
+	printf "Found dax device: %s (current mode: %s)\n" "$daxdev" "$original_mode"
+}
+
+daxctl_get_mode()
+{
+	"$DAXCTL" list -d "$1" | jq -er '.[].mode'
+}
+
+# Ensure device is in devdax mode for testing
+ensure_devdax_mode()
+{
+	local mode
+	mode=$(daxctl_get_mode "$daxdev")
+
+	if [[ "$mode" == "devdax" ]]; then
+		return 0
+	fi
+
+	if [[ "$mode" == "system-ram" ]]; then
+		printf "Device is in system-ram mode, attempting to convert to devdax...\n"
+		"$DAXCTL" reconfigure-device -f -m devdax "$daxdev"
+	elif [[ "$mode" == "famfs" ]]; then
+		printf "Device is in famfs mode, converting to devdax...\n"
+		"$DAXCTL" reconfigure-device -m devdax "$daxdev"
+	else
+		printf "Device is in unknown mode: %s\n" "$mode"
+		return 1
+	fi
+
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+}
+
+#
+# Test basic mode transitions involving famfs
+#
+test_famfs_mode_transitions()
+{
+	printf "\n=== Testing famfs mode transitions ===\n"
+
+	# Ensure starting in devdax mode
+	ensure_devdax_mode
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+	printf "Initial mode: devdax - OK\n"
+
+	# Test: devdax -> famfs
+	printf "Testing devdax -> famfs... "
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+	printf "OK\n"
+
+	# Test: famfs -> famfs (re-enable in same mode)
+	printf "Testing famfs -> famfs (re-enable)... "
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+	printf "OK\n"
+
+	# Test: famfs -> devdax
+	printf "Testing famfs -> devdax... "
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+	printf "OK\n"
+
+	# Test: devdax -> devdax (re-enable in same mode)
+	printf "Testing devdax -> devdax (re-enable)... "
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+	printf "OK\n"
+}
+
+#
+# Test mode transitions with system-ram (requires kmem)
+#
+test_system_ram_transitions()
+{
+	printf "\n=== Testing system-ram transitions with famfs ===\n"
+
+	# Ensure we start in devdax mode
+	ensure_devdax_mode
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+
+	# Test: devdax -> system-ram
+	printf "Testing devdax -> system-ram... "
+	"$DAXCTL" reconfigure-device -N -m system-ram "$daxdev"
+	[[ $(daxctl_get_mode "$daxdev") == "system-ram" ]]
+	printf "OK\n"
+
+	# Test: system-ram -> famfs should fail
+	printf "Testing system-ram -> famfs (should fail)... "
+	if "$DAXCTL" reconfigure-device -m famfs "$daxdev" 2>/dev/null; then
+		printf "FAILED - should have been rejected\n"
+		return 1
+	fi
+	printf "OK (correctly rejected)\n"
+
+	# Test: system-ram -> devdax -> famfs (proper path)
+	printf "Testing system-ram -> devdax -> famfs... "
+	"$DAXCTL" reconfigure-device -f -m devdax "$daxdev"
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+	printf "OK\n"
+
+	# Restore to devdax for subsequent tests
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
+}
+
+#
+# Test JSON output shows correct mode
+#
+test_json_output()
+{
+	printf "\n=== Testing JSON output for mode field ===\n"
+
+	# Test devdax mode in JSON
+	ensure_devdax_mode
+	printf "Testing JSON output for devdax mode... "
+	mode=$("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode')
+	[[ "$mode" == "devdax" ]]
+	printf "OK\n"
+
+	# Test famfs mode in JSON
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
+	printf "Testing JSON output for famfs mode... "
+	mode=$("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode')
+	[[ "$mode" == "famfs" ]]
+	printf "OK\n"
+
+	# Restore to devdax
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
+}
+
+#
+# Test error messages for invalid transitions
+#
+test_error_handling()
+{
+	printf "\n=== Testing error handling ===\n"
+
+	# Ensure we're in famfs mode
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
+
+	# Test that invalid mode is rejected
+	printf "Testing invalid mode rejection... "
+	if "$DAXCTL" reconfigure-device -m invalidmode "$daxdev" 2>/dev/null; then
+		printf "FAILED - invalid mode should be rejected\n"
+		return 1
+	fi
+	printf "OK (correctly rejected)\n"
+
+	# Restore to devdax
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
+}
+
+#
+# Main test sequence
+#
+main()
+{
+	check_fsdev_dax
+	find_daxdev
+
+	rc=1  # From here on, failures are real failures
+
+	test_famfs_mode_transitions
+	test_json_output
+	test_error_handling
+
+	# System-ram tests require kmem module
+	if check_kmem; then
+		# Save and disable online policy for system-ram tests
+		saved_policy="$(cat /sys/devices/system/memory/auto_online_blocks)"
+		echo "offline" > /sys/devices/system/memory/auto_online_blocks
+
+		test_system_ram_transitions
+
+		# Restore online policy
+		echo "$saved_policy" > /sys/devices/system/memory/auto_online_blocks
+	fi
+
+	# Restore original mode
+	printf "\nRestoring device to original mode: %s\n" "$original_mode"
+	"$DAXCTL" reconfigure-device -f -m "$original_mode" "$daxdev"
+
+	printf "\n=== All famfs tests passed ===\n"
+
+	exit 0
+}
+
+main
diff --git a/test/meson.build b/test/meson.build
index 8a3718d..5b75c07 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -213,6 +213,7 @@ if get_option('destructive').enabled()
   device_dax_fio = find_program('device-dax-fio.sh')
   daxctl_devices = find_program('daxctl-devices.sh')
   daxctl_create = find_program('daxctl-create.sh')
+  daxctl_famfs = find_program('daxctl-famfs.sh')
   dm = find_program('dm.sh')
   mmap_test = find_program('mmap.sh')
 
@@ -230,6 +231,7 @@ if get_option('destructive').enabled()
     [ 'device-dax-fio.sh', device_dax_fio, 'dax'   ],
     [ 'daxctl-devices.sh', daxctl_devices, 'dax'   ],
     [ 'daxctl-create.sh',  daxctl_create,  'dax'   ],
+    [ 'daxctl-famfs.sh',   daxctl_famfs,   'dax'   ],
     [ 'dm.sh',             dm,		   'dax'   ],
     [ 'mmap.sh',           mmap_test,	   'dax'   ],
   ]
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH V5 1/2] daxctl: Add support for famfs mode
  2026-04-30 15:34   ` [PATCH V5 1/2] " John Groves
@ 2026-04-30 16:20     ` Dave Jiang
  2026-05-13  5:32     ` Alison Schofield
  1 sibling, 0 replies; 8+ messages in thread
From: Dave Jiang @ 2026-04-30 16:20 UTC (permalink / raw)
  To: John Groves, John Groves, John Groves, Dan Williams,
	Alison Schofield
  Cc: John Groves, Vishal Verma, Jonathan Cameron, Aravind Ramesh,
	Ajay Joshi, venkataravis@micron.com, dev.srinivasulu@gmail.com,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org



On 4/30/26 8:34 AM, John Groves wrote:
> From: John Groves <John@Groves.net>
> 
> Putting a daxdev in famfs mode means binding it to fsdev_dax.ko
> (drivers/dax/fsdev.c). Finding a daxdev bound to fsdev_dax means
> it is in famfs mode.
> 
> A test for this functionality is added in the next commit.
> 
> With devdax, famfs, and system-ram modes, the previous logic that assumed
> 'not in mode X means in mode Y' needed to get slightly more complicated.
> 
> Add explicit mode detection functions:
> - daxctl_dev_is_famfs_mode(): check if bound to fsdev_dax driver
> - daxctl_dev_is_devdax_mode(): check if bound to device_dax driver
> Both delegate to a shared static helper daxctl_dev_bound_to_module() to
> avoid duplicating the driver-symlink lookup, as does the pre-existing
> daxctl_dev_is_system_ram_capable().
> 
> Update mode transition logic in device.c:
> - disable_devdax_device(): verify device is actually in devdax mode
> - disable_famfs_device(): verify device is actually in famfs mode
> - All reconfig_mode_*() functions explicitly check each mode
> - Handle unrecognized mode with an error instead of wrong assumption
> 
> Update json.c to report fsdev_dax-bound devices as 'famfs' mode.  An
> unbound device continues to be reported as 'devdax' (the legacy default
> when no driver is bound), to preserve existing behavior.
> 
> Signed-off-by: John Groves <john@groves.net>

Reviewed-by: Dave Jiang <dave.jiang@intel.com>


> ---
>  daxctl/device.c                | 132 ++++++++++++++++++++++++++++++---
>  daxctl/json.c                  |  13 +++-
>  daxctl/lib/libdaxctl-private.h |   2 +
>  daxctl/lib/libdaxctl.c         |  39 +++++++++-
>  daxctl/lib/libdaxctl.sym       |   7 ++
>  daxctl/libdaxctl.h             |   3 +
>  6 files changed, 181 insertions(+), 15 deletions(-)
> 
> diff --git a/daxctl/device.c b/daxctl/device.c
> index a4e36b1..003609e 100644
> --- a/daxctl/device.c
> +++ b/daxctl/device.c
> @@ -42,6 +42,7 @@ enum dev_mode {
>  	DAXCTL_DEV_MODE_UNKNOWN,
>  	DAXCTL_DEV_MODE_DEVDAX,
>  	DAXCTL_DEV_MODE_RAM,
> +	DAXCTL_DEV_MODE_FAMFS,
>  };
>  
>  struct mapping {
> @@ -471,6 +472,13 @@ static const char *parse_device_options(int argc, const char **argv,
>  					"--no-online is incompatible with --mode=devdax\n");
>  				rc =  -EINVAL;
>  			}
> +		} else if (strcmp(param.mode, "famfs") == 0) {
> +			reconfig_mode = DAXCTL_DEV_MODE_FAMFS;
> +			if (param.no_online) {
> +				fprintf(stderr,
> +					"--no-online is incompatible with --mode=famfs\n");
> +				rc =  -EINVAL;
> +			}
>  		}
>  		break;
>  	case ACTION_CREATE:
> @@ -696,8 +704,42 @@ static int disable_devdax_device(struct daxctl_dev *dev)
>  	int rc;
>  
>  	if (mem) {
> -		fprintf(stderr, "%s was already in system-ram mode\n",
> -			devname);
> +		fprintf(stderr, "%s is in system-ram mode\n", devname);
> +		return 1;
> +	}
> +	if (daxctl_dev_is_famfs_mode(dev)) {
> +		fprintf(stderr, "%s is in famfs mode\n", devname);
> +		return 1;
> +	}
> +	if (!daxctl_dev_is_devdax_mode(dev)) {
> +		fprintf(stderr, "%s is not in devdax mode\n", devname);
> +		return 1;
> +	}
> +	rc = daxctl_dev_disable(dev);
> +	if (rc) {
> +		fprintf(stderr, "%s: disable failed: %s\n",
> +			daxctl_dev_get_devname(dev), strerror(-rc));
> +		return rc;
> +	}
> +	return 0;
> +}
> +
> +static int disable_famfs_device(struct daxctl_dev *dev)
> +{
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
> +	const char *devname = daxctl_dev_get_devname(dev);
> +	int rc;
> +
> +	if (mem) {
> +		fprintf(stderr, "%s is in system-ram mode\n", devname);
> +		return 1;
> +	}
> +	if (daxctl_dev_is_devdax_mode(dev)) {
> +		fprintf(stderr, "%s is in devdax mode\n", devname);
> +		return 1;
> +	}
> +	if (!daxctl_dev_is_famfs_mode(dev)) {
> +		fprintf(stderr, "%s is not in famfs mode\n", devname);
>  		return 1;
>  	}
>  	rc = daxctl_dev_disable(dev);
> @@ -711,6 +753,7 @@ static int disable_devdax_device(struct daxctl_dev *dev)
>  
>  static int reconfig_mode_system_ram(struct daxctl_dev *dev)
>  {
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
>  	const char *devname = daxctl_dev_get_devname(dev);
>  	int rc, skip_enable = 0;
>  
> @@ -724,11 +767,21 @@ static int reconfig_mode_system_ram(struct daxctl_dev *dev)
>  	}
>  
>  	if (daxctl_dev_is_enabled(dev)) {
> -		rc = disable_devdax_device(dev);
> -		if (rc < 0)
> -			return rc;
> -		if (rc > 0)
> +		if (mem) {
> +			/* already in system-ram mode */
>  			skip_enable = 1;
> +		} else if (daxctl_dev_is_famfs_mode(dev)) {
> +			rc = disable_famfs_device(dev);
> +			if (rc)
> +				return rc;
> +		} else if (daxctl_dev_is_devdax_mode(dev)) {
> +			rc = disable_devdax_device(dev);
> +			if (rc)
> +				return rc;
> +		} else {
> +			fprintf(stderr, "%s: unknown mode\n", devname);
> +			return -EINVAL;
> +		}
>  	}
>  
>  	if (!skip_enable) {
> @@ -750,7 +803,7 @@ static int disable_system_ram_device(struct daxctl_dev *dev)
>  	int rc;
>  
>  	if (!mem) {
> -		fprintf(stderr, "%s was already in devdax mode\n", devname);
> +		fprintf(stderr, "%s is not in system-ram mode\n", devname);
>  		return 1;
>  	}
>  
> @@ -786,12 +839,31 @@ static int disable_system_ram_device(struct daxctl_dev *dev)
>  
>  static int reconfig_mode_devdax(struct daxctl_dev *dev)
>  {
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
> +	const char *devname = daxctl_dev_get_devname(dev);
>  	int rc;
>  
>  	if (daxctl_dev_is_enabled(dev)) {
> -		rc = disable_system_ram_device(dev);
> -		if (rc)
> -			return rc;
> +		if (mem) {
> +			rc = disable_system_ram_device(dev);
> +			if (rc)
> +				return rc;
> +		} else if (daxctl_dev_is_famfs_mode(dev)) {
> +			rc = disable_famfs_device(dev);
> +			if (rc)
> +				return rc;
> +		} else if (daxctl_dev_is_devdax_mode(dev)) {
> +			/* already in devdax mode, just re-enable */
> +			rc = daxctl_dev_disable(dev);
> +			if (rc) {
> +				fprintf(stderr, "%s: disable failed: %s\n",
> +					devname, strerror(-rc));
> +				return rc;
> +			}
> +		} else {
> +			fprintf(stderr, "%s: unknown mode\n", devname);
> +			return -EINVAL;
> +		}
>  	}
>  
>  	rc = daxctl_dev_enable_devdax(dev);
> @@ -801,6 +873,43 @@ static int reconfig_mode_devdax(struct daxctl_dev *dev)
>  	return 0;
>  }
>  
> +static int reconfig_mode_famfs(struct daxctl_dev *dev)
> +{
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
> +	const char *devname = daxctl_dev_get_devname(dev);
> +	int rc;
> +
> +	if (daxctl_dev_is_enabled(dev)) {
> +		if (mem) {
> +			fprintf(stderr,
> +				"%s is in system-ram mode; must be in devdax mode to convert to famfs\n",
> +				devname);
> +			return -EINVAL;
> +		} else if (daxctl_dev_is_famfs_mode(dev)) {
> +			/* already in famfs mode, just re-enable */
> +			rc = daxctl_dev_disable(dev);
> +			if (rc) {
> +				fprintf(stderr, "%s: disable failed: %s\n",
> +					devname, strerror(-rc));
> +				return rc;
> +			}
> +		} else if (daxctl_dev_is_devdax_mode(dev)) {
> +			rc = disable_devdax_device(dev);
> +			if (rc)
> +				return rc;
> +		} else {
> +			fprintf(stderr, "%s: unknown mode\n", devname);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	rc = daxctl_dev_enable_famfs(dev);
> +	if (rc)
> +		return rc;
> +
> +	return 0;
> +}
> +
>  static int do_create(struct daxctl_region *region, long long val,
>  		     struct json_object **jdevs)
>  {
> @@ -887,6 +996,9 @@ static int do_reconfig(struct daxctl_dev *dev, enum dev_mode mode,
>  	case DAXCTL_DEV_MODE_DEVDAX:
>  		rc = reconfig_mode_devdax(dev);
>  		break;
> +	case DAXCTL_DEV_MODE_FAMFS:
> +		rc = reconfig_mode_famfs(dev);
> +		break;
>  	default:
>  		fprintf(stderr, "%s: unknown mode requested: %d\n",
>  			devname, mode);
> diff --git a/daxctl/json.c b/daxctl/json.c
> index 3cbce9d..2a4b12c 100644
> --- a/daxctl/json.c
> +++ b/daxctl/json.c
> @@ -48,8 +48,19 @@ struct json_object *util_daxctl_dev_to_json(struct daxctl_dev *dev,
>  
>  	if (mem)
>  		jobj = json_object_new_string("system-ram");
> -	else
> +	else if (daxctl_dev_is_famfs_mode(dev))
> +		jobj = json_object_new_string("famfs");
> +	else if (daxctl_dev_is_devdax_mode(dev))
>  		jobj = json_object_new_string("devdax");
> +	else {
> +		/* Legacy condition; if a daxdev is not in any "mode", that
> +		 * means no driver is bound. We report that as a disabled
> +		 * device in devdax mode. (the disabled modifier is added later
> +		 * in this function if applicable)
> +		 */
> +		jobj = json_object_new_string("devdax");
> +	}
> +
>  	if (jobj)
>  		json_object_object_add(jdev, "mode", jobj);
>  
> diff --git a/daxctl/lib/libdaxctl-private.h b/daxctl/lib/libdaxctl-private.h
> index ae45311..0bb73e8 100644
> --- a/daxctl/lib/libdaxctl-private.h
> +++ b/daxctl/lib/libdaxctl-private.h
> @@ -21,12 +21,14 @@ static const char *dax_subsystems[] = {
>  enum daxctl_dev_mode {
>  	DAXCTL_DEV_MODE_DEVDAX = 0,
>  	DAXCTL_DEV_MODE_RAM,
> +	DAXCTL_DEV_MODE_FAMFS,
>  	DAXCTL_DEV_MODE_END,
>  };
>  
>  static const char *dax_modules[] = {
>  	[DAXCTL_DEV_MODE_DEVDAX] = "device_dax",
>  	[DAXCTL_DEV_MODE_RAM] = "kmem",
> +	[DAXCTL_DEV_MODE_FAMFS] = "fsdev_dax",
>  };
>  
>  enum memory_op {
> diff --git a/daxctl/lib/libdaxctl.c b/daxctl/lib/libdaxctl.c
> index 02ae7e5..33121dc 100644
> --- a/daxctl/lib/libdaxctl.c
> +++ b/daxctl/lib/libdaxctl.c
> @@ -385,13 +385,13 @@ static bool device_model_is_dax_bus(struct daxctl_dev *dev)
>  	return false;
>  }
>  
> -DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
> +static int daxctl_dev_bound_to_module(struct daxctl_dev *dev, const char *mod_name)
>  {
>  	const char *devname = daxctl_dev_get_devname(dev);
>  	struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev);
>  	const char *mod_base;
>  	char *mod_path;
> -	char path[200];
> +	char path[PATH_MAX];
>  	const int len = sizeof(path);
>  
>  	if (!device_model_is_dax_bus(dev))
> @@ -406,11 +406,13 @@ DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
>  	}
>  
>  	mod_path = realpath(path, NULL);
> -	if (!mod_path)
> +	if (!mod_path) {
> +		dbg(ctx, "%s: realpath failed for driver link\n", devname);
>  		return false;
> +	}
>  
>  	mod_base = path_basename(mod_path);
> -	if (strcmp(mod_base, dax_modules[DAXCTL_DEV_MODE_RAM]) == 0) {
> +	if (strcmp(mod_base, mod_name) == 0) {
>  		free(mod_path);
>  		return true;
>  	}
> @@ -419,6 +421,30 @@ DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
>  	return false;
>  }
>  
> +DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_RAM]);
> +}
> +
> +/*
> + * Check if device is currently in famfs mode (bound to fsdev_dax driver).
> + * Returns false for disabled devices: the DAX bus does not retain the previous
> + * driver binding after unbind, so mode cannot be determined without a driver.
> + */
> +DAXCTL_EXPORT int daxctl_dev_is_famfs_mode(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_FAMFS]);
> +}
> +
> +/*
> + * Check if device is currently in devdax mode (bound to device_dax driver).
> + * Returns false for disabled devices; see daxctl_dev_is_famfs_mode().
> + */
> +DAXCTL_EXPORT int daxctl_dev_is_devdax_mode(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_DEVDAX]);
> +}
> +
>  /*
>   * This checks for the device to be in system-ram mode, so calling
>   * daxctl_dev_get_memory() on a devdax mode device will always return NULL.
> @@ -983,6 +1009,11 @@ DAXCTL_EXPORT int daxctl_dev_enable_ram(struct daxctl_dev *dev)
>  	return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_RAM);
>  }
>  
> +DAXCTL_EXPORT int daxctl_dev_enable_famfs(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_FAMFS);
> +}
> +
>  DAXCTL_EXPORT int daxctl_dev_disable(struct daxctl_dev *dev)
>  {
>  	const char *devname = daxctl_dev_get_devname(dev);
> diff --git a/daxctl/lib/libdaxctl.sym b/daxctl/lib/libdaxctl.sym
> index 3098811..2a812c6 100644
> --- a/daxctl/lib/libdaxctl.sym
> +++ b/daxctl/lib/libdaxctl.sym
> @@ -104,3 +104,10 @@ LIBDAXCTL_10 {
>  global:
>  	daxctl_dev_is_system_ram_capable;
>  } LIBDAXCTL_9;
> +
> +LIBDAXCTL_11 {
> +global:
> +	daxctl_dev_enable_famfs;
> +	daxctl_dev_is_famfs_mode;
> +	daxctl_dev_is_devdax_mode;
> +} LIBDAXCTL_10;
> diff --git a/daxctl/libdaxctl.h b/daxctl/libdaxctl.h
> index 53c6bbd..84fcdb4 100644
> --- a/daxctl/libdaxctl.h
> +++ b/daxctl/libdaxctl.h
> @@ -72,12 +72,15 @@ int daxctl_dev_is_enabled(struct daxctl_dev *dev);
>  int daxctl_dev_disable(struct daxctl_dev *dev);
>  int daxctl_dev_enable_devdax(struct daxctl_dev *dev);
>  int daxctl_dev_enable_ram(struct daxctl_dev *dev);
> +int daxctl_dev_enable_famfs(struct daxctl_dev *dev);
>  int daxctl_dev_get_target_node(struct daxctl_dev *dev);
>  int daxctl_dev_will_auto_online_memory(struct daxctl_dev *dev);
>  int daxctl_dev_has_online_memory(struct daxctl_dev *dev);
>  
>  struct daxctl_memory;
>  int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev);
> +int daxctl_dev_is_famfs_mode(struct daxctl_dev *dev);
> +int daxctl_dev_is_devdax_mode(struct daxctl_dev *dev);
>  struct daxctl_memory *daxctl_dev_get_memory(struct daxctl_dev *dev);
>  struct daxctl_dev *daxctl_memory_get_dev(struct daxctl_memory *mem);
>  const char *daxctl_memory_get_node_path(struct daxctl_memory *mem);


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions:
  2026-04-30 15:34   ` [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions: John Groves
@ 2026-04-30 16:27     ` Dave Jiang
  2026-04-30 16:50       ` John Groves
  2026-05-13  5:34     ` Alison Schofield
  1 sibling, 1 reply; 8+ messages in thread
From: Dave Jiang @ 2026-04-30 16:27 UTC (permalink / raw)
  To: John Groves, John Groves, John Groves, Dan Williams,
	Alison Schofield
  Cc: John Groves, Vishal Verma, Jonathan Cameron, Aravind Ramesh,
	Ajay Joshi, venkataravis@micron.com, dev.srinivasulu@gmail.com,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org



On 4/30/26 8:34 AM, John Groves wrote:
> From: John Groves <John@Groves.net>
> 
> - devdax <-> famfs mode switches
> - Verify famfs -> system-ram is rejected (must go via devdax)
> - Test JSON output shows correct mode
> - Test error handling for invalid modes
> 
> Signed-off-by: John Groves <john@groves.net>
> ---
>  test/daxctl-famfs.sh | 253 +++++++++++++++++++++++++++++++++++++++++++
>  test/meson.build     |   2 +
>  2 files changed, 255 insertions(+)
>  create mode 100755 test/daxctl-famfs.sh
> 
> diff --git a/test/daxctl-famfs.sh b/test/daxctl-famfs.sh
> new file mode 100755
> index 0000000..12fbfef
> --- /dev/null
> +++ b/test/daxctl-famfs.sh
> @@ -0,0 +1,253 @@
> +#!/bin/bash -Ex
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (C) 2025 Micron Technology, Inc. All rights reserved.
> +#
> +# Test daxctl famfs mode transitions and mode detection
> +
> +rc=77
> +. $(dirname $0)/common
> +
> +trap 'cleanup $LINENO' ERR
> +
> +daxdev=""
> +original_mode=""
> +
> +cleanup()
> +{
> +	printf "Error at line %d\n" "$1"
> +	# Try to restore to original mode if we know it
> +	if [[ $daxdev && $original_mode ]]; then
> +		"$DAXCTL" reconfigure-device -f -m "$original_mode" "$daxdev" 2>/dev/null || true
> +	fi
> +	exit $rc
> +}
> +
> +# Check if fsdev_dax module is available
> +check_fsdev_dax()
> +{
> +	if modinfo fsdev_dax &>/dev/null; then
> +		return 0
> +	fi
> +	if grep -qF "fsdev_dax" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null; then
> +		return 0
> +	fi
> +	printf "fsdev_dax module not available, skipping\n"
> +	exit 77
> +}
> +
> +# Check if kmem module is available (needed for system-ram mode tests)
> +check_kmem()
> +{
> +	if modinfo kmem &>/dev/null; then
> +		return 0
> +	fi
> +	if grep -qF "kmem" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null; then
> +		return 0
> +	fi
> +	printf "kmem module not available, skipping system-ram tests\n"
> +	return 1
> +}
> +
> +# Find an existing dax device to test with
> +find_daxdev()
> +{
> +	# Look for any available dax device
> +	daxdev=$("$DAXCTL" list | jq -er '.[0].chardev // empty' 2>/dev/null) || true
> +
> +	if [[ ! $daxdev ]]; then
> +		printf "No dax device found, skipping\n"
> +		exit 77

Can you use 'do_skip' here?

DJ

> +	fi
> +
> +	# Save the original mode so we can restore it
> +	original_mode=$("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode')
> +
> +	printf "Found dax device: %s (current mode: %s)\n" "$daxdev" "$original_mode"
> +}
> +
> +daxctl_get_mode()
> +{
> +	"$DAXCTL" list -d "$1" | jq -er '.[].mode'
> +}
> +
> +# Ensure device is in devdax mode for testing
> +ensure_devdax_mode()
> +{
> +	local mode
> +	mode=$(daxctl_get_mode "$daxdev")
> +
> +	if [[ "$mode" == "devdax" ]]; then
> +		return 0
> +	fi
> +
> +	if [[ "$mode" == "system-ram" ]]; then
> +		printf "Device is in system-ram mode, attempting to convert to devdax...\n"
> +		"$DAXCTL" reconfigure-device -f -m devdax "$daxdev"
> +	elif [[ "$mode" == "famfs" ]]; then
> +		printf "Device is in famfs mode, converting to devdax...\n"
> +		"$DAXCTL" reconfigure-device -m devdax "$daxdev"
> +	else
> +		printf "Device is in unknown mode: %s\n" "$mode"
> +		return 1
> +	fi
> +
> +	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
> +}
> +
> +#
> +# Test basic mode transitions involving famfs
> +#
> +test_famfs_mode_transitions()
> +{
> +	printf "\n=== Testing famfs mode transitions ===\n"
> +
> +	# Ensure starting in devdax mode
> +	ensure_devdax_mode
> +	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
> +	printf "Initial mode: devdax - OK\n"
> +
> +	# Test: devdax -> famfs
> +	printf "Testing devdax -> famfs... "
> +	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
> +	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
> +	printf "OK\n"
> +
> +	# Test: famfs -> famfs (re-enable in same mode)
> +	printf "Testing famfs -> famfs (re-enable)... "
> +	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
> +	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
> +	printf "OK\n"
> +
> +	# Test: famfs -> devdax
> +	printf "Testing famfs -> devdax... "
> +	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
> +	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
> +	printf "OK\n"
> +
> +	# Test: devdax -> devdax (re-enable in same mode)
> +	printf "Testing devdax -> devdax (re-enable)... "
> +	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
> +	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
> +	printf "OK\n"
> +}
> +
> +#
> +# Test mode transitions with system-ram (requires kmem)
> +#
> +test_system_ram_transitions()
> +{
> +	printf "\n=== Testing system-ram transitions with famfs ===\n"
> +
> +	# Ensure we start in devdax mode
> +	ensure_devdax_mode
> +	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
> +
> +	# Test: devdax -> system-ram
> +	printf "Testing devdax -> system-ram... "
> +	"$DAXCTL" reconfigure-device -N -m system-ram "$daxdev"
> +	[[ $(daxctl_get_mode "$daxdev") == "system-ram" ]]
> +	printf "OK\n"
> +
> +	# Test: system-ram -> famfs should fail
> +	printf "Testing system-ram -> famfs (should fail)... "
> +	if "$DAXCTL" reconfigure-device -m famfs "$daxdev" 2>/dev/null; then
> +		printf "FAILED - should have been rejected\n"
> +		return 1
> +	fi
> +	printf "OK (correctly rejected)\n"
> +
> +	# Test: system-ram -> devdax -> famfs (proper path)
> +	printf "Testing system-ram -> devdax -> famfs... "
> +	"$DAXCTL" reconfigure-device -f -m devdax "$daxdev"
> +	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
> +	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
> +	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
> +	printf "OK\n"
> +
> +	# Restore to devdax for subsequent tests
> +	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
> +}
> +
> +#
> +# Test JSON output shows correct mode
> +#
> +test_json_output()
> +{
> +	printf "\n=== Testing JSON output for mode field ===\n"
> +
> +	# Test devdax mode in JSON
> +	ensure_devdax_mode
> +	printf "Testing JSON output for devdax mode... "
> +	mode=$("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode')
> +	[[ "$mode" == "devdax" ]]
> +	printf "OK\n"
> +
> +	# Test famfs mode in JSON
> +	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
> +	printf "Testing JSON output for famfs mode... "
> +	mode=$("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode')
> +	[[ "$mode" == "famfs" ]]
> +	printf "OK\n"
> +
> +	# Restore to devdax
> +	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
> +}
> +
> +#
> +# Test error messages for invalid transitions
> +#
> +test_error_handling()
> +{
> +	printf "\n=== Testing error handling ===\n"
> +
> +	# Ensure we're in famfs mode
> +	"$DAXCTL" reconfigure-device -m famfs "$daxdev"
> +
> +	# Test that invalid mode is rejected
> +	printf "Testing invalid mode rejection... "
> +	if "$DAXCTL" reconfigure-device -m invalidmode "$daxdev" 2>/dev/null; then
> +		printf "FAILED - invalid mode should be rejected\n"
> +		return 1
> +	fi
> +	printf "OK (correctly rejected)\n"
> +
> +	# Restore to devdax
> +	"$DAXCTL" reconfigure-device -m devdax "$daxdev"
> +}
> +
> +#
> +# Main test sequence
> +#
> +main()
> +{
> +	check_fsdev_dax
> +	find_daxdev
> +
> +	rc=1  # From here on, failures are real failures
> +
> +	test_famfs_mode_transitions
> +	test_json_output
> +	test_error_handling
> +
> +	# System-ram tests require kmem module
> +	if check_kmem; then
> +		# Save and disable online policy for system-ram tests
> +		saved_policy="$(cat /sys/devices/system/memory/auto_online_blocks)"
> +		echo "offline" > /sys/devices/system/memory/auto_online_blocks
> +
> +		test_system_ram_transitions
> +
> +		# Restore online policy
> +		echo "$saved_policy" > /sys/devices/system/memory/auto_online_blocks
> +	fi
> +
> +	# Restore original mode
> +	printf "\nRestoring device to original mode: %s\n" "$original_mode"
> +	"$DAXCTL" reconfigure-device -f -m "$original_mode" "$daxdev"
> +
> +	printf "\n=== All famfs tests passed ===\n"
> +
> +	exit 0
> +}
> +
> +main
> diff --git a/test/meson.build b/test/meson.build
> index 8a3718d..5b75c07 100644
> --- a/test/meson.build
> +++ b/test/meson.build
> @@ -213,6 +213,7 @@ if get_option('destructive').enabled()
>    device_dax_fio = find_program('device-dax-fio.sh')
>    daxctl_devices = find_program('daxctl-devices.sh')
>    daxctl_create = find_program('daxctl-create.sh')
> +  daxctl_famfs = find_program('daxctl-famfs.sh')
>    dm = find_program('dm.sh')
>    mmap_test = find_program('mmap.sh')
>  
> @@ -230,6 +231,7 @@ if get_option('destructive').enabled()
>      [ 'device-dax-fio.sh', device_dax_fio, 'dax'   ],
>      [ 'daxctl-devices.sh', daxctl_devices, 'dax'   ],
>      [ 'daxctl-create.sh',  daxctl_create,  'dax'   ],
> +    [ 'daxctl-famfs.sh',   daxctl_famfs,   'dax'   ],
>      [ 'dm.sh',             dm,		   'dax'   ],
>      [ 'mmap.sh',           mmap_test,	   'dax'   ],
>    ]


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions:
  2026-04-30 16:27     ` Dave Jiang
@ 2026-04-30 16:50       ` John Groves
  0 siblings, 0 replies; 8+ messages in thread
From: John Groves @ 2026-04-30 16:50 UTC (permalink / raw)
  To: Dave Jiang, John Groves, John Groves, Dan Williams,
	Alison Schofield
  Cc: John Groves, Vishal Verma, Jonathan Cameron, Aravind Ramesh,
	Ajay Joshi, venkataravis@micron.com, dev.srinivasulu@gmail.com,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org



On Thu, Apr 30, 2026, at 11:27 AM, Dave Jiang wrote:
> 
> 
> On 4/30/26 8:34 AM, John Groves wrote:
> > From: John Groves <John@Groves.net>
> > 
> > - devdax <-> famfs mode switches
> > - Verify famfs -> system-ram is rejected (must go via devdax)
> > - Test JSON output shows correct mode
> > - Test error handling for invalid modes
> > 
> > Signed-off-by: John Groves <john@groves.net>
> > ---
> >  test/daxctl-famfs.sh | 253 +++++++++++++++++++++++++++++++++++++++++++
> >  test/meson.build     |   2 +
> >  2 files changed, 255 insertions(+)
> >  create mode 100755 test/daxctl-famfs.sh
> > 
> > diff --git a/test/daxctl-famfs.sh b/test/daxctl-famfs.sh
> > new file mode 100755
> > index 0000000..12fbfef
> > --- /dev/null
> > +++ b/test/daxctl-famfs.sh
> > @@ -0,0 +1,253 @@
> > +#!/bin/bash -Ex
> > +# SPDX-License-Identifier: GPL-2.0
> > +# Copyright (C) 2025 Micron Technology, Inc. All rights reserved.
> > +#
> > +# Test daxctl famfs mode transitions and mode detection
> > +
> > +rc=77
> > +. $(dirname $0)/common
> > +
> > +trap 'cleanup $LINENO' ERR
> > +
> > +daxdev=""
> > +original_mode=""
> > +
> > +cleanup()
> > +{
> > + printf "Error at line %d\n" "$1"
> > + # Try to restore to original mode if we know it
> > + if [[ $daxdev && $original_mode ]]; then
> > + "$DAXCTL" reconfigure-device -f -m "$original_mode" "$daxdev" 2>/dev/null || true
> > + fi
> > + exit $rc
> > +}
> > +
> > +# Check if fsdev_dax module is available
> > +check_fsdev_dax()
> > +{
> > + if modinfo fsdev_dax &>/dev/null; then
> > + return 0
> > + fi
> > + if grep -qF "fsdev_dax" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null; then
> > + return 0
> > + fi
> > + printf "fsdev_dax module not available, skipping\n"
> > + exit 77
> > +}
> > +
> > +# Check if kmem module is available (needed for system-ram mode tests)
> > +check_kmem()
> > +{
> > + if modinfo kmem &>/dev/null; then
> > + return 0
> > + fi
> > + if grep -qF "kmem" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null; then
> > + return 0
> > + fi
> > + printf "kmem module not available, skipping system-ram tests\n"
> > + return 1
> > +}
> > +
> > +# Find an existing dax device to test with
> > +find_daxdev()
> > +{
> > + # Look for any available dax device
> > + daxdev=$("$DAXCTL" list | jq -er '.[0].chardev // empty' 2>/dev/null) || true
> > +
> > + if [[ ! $daxdev ]]; then
> > + printf "No dax device found, skipping\n"
> > + exit 77
> 
> Can you use 'do_skip' here?
> 
> DJ

Yes! Done in 2 places...

<snip>

Thanks,
John

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH V5 1/2] daxctl: Add support for famfs mode
  2026-04-30 15:34   ` [PATCH V5 1/2] " John Groves
  2026-04-30 16:20     ` Dave Jiang
@ 2026-05-13  5:32     ` Alison Schofield
  1 sibling, 0 replies; 8+ messages in thread
From: Alison Schofield @ 2026-05-13  5:32 UTC (permalink / raw)
  To: John Groves
  Cc: John Groves, John Groves, Dan Williams, John Groves, Vishal Verma,
	Dave Jiang, Jonathan Cameron, Aravind Ramesh, Ajay Joshi,
	venkataravis@micron.com, dev.srinivasulu@gmail.com,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org

On Thu, Apr 30, 2026 at 03:34:11PM +0000, John Groves wrote:
> From: John Groves <John@Groves.net>

Hi John,

Please add an intro paragraph saying what famfs is, preferably
with links to reference material.

This patch doesn't update any docs. Please take a look at what is
needed. I think at least this one:
Documentation/daxctl/daxctl-reconfigure-device.txt needs to add
famfs as a 'mode' option.

> Putting a daxdev in famfs mode means binding it to fsdev_dax.ko
> (drivers/dax/fsdev.c). Finding a daxdev bound to fsdev_dax means
> it is in famfs mode.
>
> A test for this functionality is added in the next commit.
>
> With devdax, famfs, and system-ram modes, the previous logic that assumed
> 'not in mode X means in mode Y' needed to get slightly more complicated.
>
> Add explicit mode detection functions:
> - daxctl_dev_is_famfs_mode(): check if bound to fsdev_dax driver
> - daxctl_dev_is_devdax_mode(): check if bound to device_dax driver
> Both delegate to a shared static helper daxctl_dev_bound_to_module() to
> avoid duplicating the driver-symlink lookup, as does the pre-existing
> daxctl_dev_is_system_ram_capable().
>
> Update mode transition logic in device.c:
> - disable_devdax_device(): verify device is actually in devdax mode
> - disable_famfs_device(): verify device is actually in famfs mode
> - All reconfig_mode_*() functions explicitly check each mode
> - Handle unrecognized mode with an error instead of wrong assumption
>
> Update json.c to report fsdev_dax-bound devices as 'famfs' mode.  An
> unbound device continues to be reported as 'devdax' (the legacy default
> when no driver is bound), to preserve existing behavior.
>
> Signed-off-by: John Groves <john@groves.net>
> ---
>  daxctl/device.c                | 132 +++++++++++++++++++++++++++++++++++++----
>  daxctl/json.c                  |  13 +++-
>  daxctl/lib/libdaxctl-private.h |   2 +
>  daxctl/lib/libdaxctl.c         |  39 ++++++++++--
>  daxctl/lib/libdaxctl.sym       |   7 +++
>  daxctl/libdaxctl.h             |   3 +
>  6 files changed, 181 insertions(+), 15 deletions(-)
>
> diff --git a/daxctl/device.c b/daxctl/device.c
> index a4e36b130a09..003609e4abba 100644
> --- a/daxctl/device.c
> +++ b/daxctl/device.c
> @@ -42,6 +42,7 @@ enum dev_mode {
>  	DAXCTL_DEV_MODE_UNKNOWN,
>  	DAXCTL_DEV_MODE_DEVDAX,
>  	DAXCTL_DEV_MODE_RAM,
> +	DAXCTL_DEV_MODE_FAMFS,
>  };


The above enum dev_mode in device.c shares enum names with enum
daxctl_dev_mode later in this patch (in libdaxctl-private.h) but assigns
different numeric values. The overlap predates this patch, so not
something you introduced, but adding FAMFS to both is a good moment to
fix it before it gets more entrenched.

Two enums with identical member names but different values is confusing
and risks silent cross-assignment bugs with no compiler warning.

Suggest renaming this local enum to `enum reconfig_mode` with members
`RECONFIG_MODE_{UNKNOWN,DEVDAX,RAM,FAMFS}`. That confines it to the reconfig
path. Then the library enum gets unambiguous ownership of `DAXCTL_DEV_MODE_*`.
After that accidental cross-assignments are obvious type mismatches rather
than a silent wrong-value bug.


>
>  struct mapping {
>  	unsigned long long start, end, pgoff;
> @@ -471,6 +472,13 @@ static const char *parse_device_options(int argc, const char **argv,
>  					"--no-online is incompatible with --mode=devdax\n");
>  				rc =  -EINVAL;
>  			}
> +		} else if (strcmp(param.mode, "famfs") == 0) {
> +			reconfig_mode = DAXCTL_DEV_MODE_FAMFS;
> +			if (param.no_online) {
> +				fprintf(stderr,
> +					"--no-online is incompatible with --mode=famfs\n");
> +				rc =  -EINVAL;


rm extra whitespace after =


> +			}
>  		}
>  		break;
>  	case ACTION_CREATE:
> @@ -696,8 +704,42 @@ static int disable_devdax_device(struct daxctl_dev *dev)
>  	int rc;
>
>  	if (mem) {
> -		fprintf(stderr, "%s was already in system-ram mode\n",
> -			devname);
> +		fprintf(stderr, "%s is in system-ram mode\n", devname);
> +		return 1;
> +	}
> +	if (daxctl_dev_is_famfs_mode(dev)) {
> +		fprintf(stderr, "%s is in famfs mode\n", devname);
> +		return 1;
> +	}
> +	if (!daxctl_dev_is_devdax_mode(dev)) {
> +		fprintf(stderr, "%s is not in devdax mode\n", devname);
> +		return 1;
> +	}
> +	rc = daxctl_dev_disable(dev);
> +	if (rc) {
> +		fprintf(stderr, "%s: disable failed: %s\n",
> +			daxctl_dev_get_devname(dev), strerror(-rc));
> +		return rc;
> +	}
> +	return 0;
> +}
> +
> +static int disable_famfs_device(struct daxctl_dev *dev)
> +{
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
> +	const char *devname = daxctl_dev_get_devname(dev);
> +	int rc;
> +
> +	if (mem) {
> +		fprintf(stderr, "%s is in system-ram mode\n", devname);
> +		return 1;
> +	}
> +	if (daxctl_dev_is_devdax_mode(dev)) {
> +		fprintf(stderr, "%s is in devdax mode\n", devname);
> +		return 1;
> +	}
> +	if (!daxctl_dev_is_famfs_mode(dev)) {
> +		fprintf(stderr, "%s is not in famfs mode\n", devname);
>  		return 1;
>  	}
>  	rc = daxctl_dev_disable(dev);


disable_devdax_device() and disable_famfs_device() differ only in
which mode they accept vs. reject. With reconfig_mode_* switching on
daxctl_dev_get_mode() first, the caller already knows the mode, so
the internal mode-sanity checks are redundant.

What's left in each function is daxctl_dev_disable() and an error
fprintf, the same code in both, so collapse to a single helper:

    static int disable_mode_device(struct daxctl_dev *dev);

No mode parameter: the precondition (caller has matched the mode)
means nothing mode-specific remains.


> @@ -711,6 +753,7 @@ static int disable_devdax_device(struct daxctl_dev *dev)
>
>  static int reconfig_mode_system_ram(struct daxctl_dev *dev)
>  {
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
>  	const char *devname = daxctl_dev_get_devname(dev);
>  	int rc, skip_enable = 0;
>
> @@ -724,11 +767,21 @@ static int reconfig_mode_system_ram(struct daxctl_dev *dev)
>  	}
>
>  	if (daxctl_dev_is_enabled(dev)) {
> -		rc = disable_devdax_device(dev);
> -		if (rc < 0)
> -			return rc;
> -		if (rc > 0)
> +		if (mem) {
> +			/* already in system-ram mode */
>  			skip_enable = 1;
> +		} else if (daxctl_dev_is_famfs_mode(dev)) {
> +			rc = disable_famfs_device(dev);
> +			if (rc)
> +				return rc;
> +		} else if (daxctl_dev_is_devdax_mode(dev)) {
> +			rc = disable_devdax_device(dev);
> +			if (rc)
> +				return rc;
> +		} else {
> +			fprintf(stderr, "%s: unknown mode\n", devname);
> +			return -EINVAL;
> +		}
>  	}

This if-else chain is repeated in all three reconfig_mode_*() functions below.
Please add a private helper and switch on the result. Something like:

    static enum daxctl_dev_mode daxctl_dev_get_mode(struct daxctl_dev *dev)
    {
            if (daxctl_dev_get_memory(dev))
                    return DAXCTL_DEV_MODE_RAM;
            if (daxctl_dev_is_famfs_mode(dev))
                    return DAXCTL_DEV_MODE_FAMFS;
            if (daxctl_dev_is_devdax_mode(dev))
                    return DAXCTL_DEV_MODE_DEVDAX;
            return DAXCTL_DEV_MODE_UNKNOWN;
    }

Then each reconfig_mode_* becomes a switch on the current mode. The
precedence (system-ram first because it's detected via `mem` rather
than driver binding) lives in one place instead of three.

> @@ -750,7 +803,7 @@ static int disable_system_ram_device(struct daxctl_dev *dev)
>  	int rc;
>
>  	if (!mem) {
> -		fprintf(stderr, "%s was already in devdax mode\n", devname);
> +		fprintf(stderr, "%s is not in system-ram mode\n", devname);
>  		return 1;
>  	}
>
> @@ -786,12 +839,31 @@ static int disable_system_ram_device(struct daxctl_dev *dev)
>
>  static int reconfig_mode_devdax(struct daxctl_dev *dev)
>  {
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
> +	const char *devname = daxctl_dev_get_devname(dev);
>  	int rc;
>
>  	if (daxctl_dev_is_enabled(dev)) {
> -		rc = disable_system_ram_device(dev);
> -		if (rc)
> -			return rc;
> +		if (mem) {
> +			rc = disable_system_ram_device(dev);
> +			if (rc)
> +				return rc;
> +		} else if (daxctl_dev_is_famfs_mode(dev)) {
> +			rc = disable_famfs_device(dev);
> +			if (rc)
> +				return rc;
> +		} else if (daxctl_dev_is_devdax_mode(dev)) {
> +			/* already in devdax mode, just re-enable */
> +			rc = daxctl_dev_disable(dev);
> +			if (rc) {
> +				fprintf(stderr, "%s: disable failed: %s\n",
> +					devname, strerror(-rc));
> +				return rc;
> +			}
> +		} else {
> +			fprintf(stderr, "%s: unknown mode\n", devname);
> +			return -EINVAL;
> +		}
>  	}


Same if-else chain, second copy. See the helper suggestion above.


>  	rc = daxctl_dev_enable_devdax(dev);
> @@ -801,6 +873,43 @@ static int reconfig_mode_devdax(struct daxctl_dev *dev)
>  	return 0;
>  }
>
> +static int reconfig_mode_famfs(struct daxctl_dev *dev)
> +{
> +	struct daxctl_memory *mem = daxctl_dev_get_memory(dev);
> +	const char *devname = daxctl_dev_get_devname(dev);
> +	int rc;
> +
> +	if (daxctl_dev_is_enabled(dev)) {
> +		if (mem) {
> +			fprintf(stderr,
> +				"%s is in system-ram mode; must be in devdax mode to convert to famfs\n",
> +				devname);
> +			return -EINVAL;
> +		} else if (daxctl_dev_is_famfs_mode(dev)) {
> +			/* already in famfs mode, just re-enable */
> +			rc = daxctl_dev_disable(dev);
> +			if (rc) {
> +				fprintf(stderr, "%s: disable failed: %s\n",
> +					devname, strerror(-rc));
> +				return rc;
> +			}
> +		} else if (daxctl_dev_is_devdax_mode(dev)) {
> +			rc = disable_devdax_device(dev);
> +			if (rc)
> +				return rc;
> +		} else {
> +			fprintf(stderr, "%s: unknown mode\n", devname);
> +			return -EINVAL;
> +		}
> +	}


Same if-else chain, third copy.


> +
> +	rc = daxctl_dev_enable_famfs(dev);
> +	if (rc)
> +		return rc;
> +
> +	return 0;
> +}
> +
>  static int do_create(struct daxctl_region *region, long long val,
>  		     struct json_object **jdevs)
>  {
> @@ -887,6 +996,9 @@ static int do_reconfig(struct daxctl_dev *dev, enum dev_mode mode,
>  	case DAXCTL_DEV_MODE_DEVDAX:
>  		rc = reconfig_mode_devdax(dev);
>  		break;
> +	case DAXCTL_DEV_MODE_FAMFS:
> +		rc = reconfig_mode_famfs(dev);
> +		break;
>  	default:
>  		fprintf(stderr, "%s: unknown mode requested: %d\n",
>  			devname, mode);
> diff --git a/daxctl/json.c b/daxctl/json.c
> index 3cbce9dcd651..2a4b12c2f925 100644
> --- a/daxctl/json.c
> +++ b/daxctl/json.c
> @@ -48,8 +48,19 @@ struct json_object *util_daxctl_dev_to_json(struct daxctl_dev *dev,
>
>  	if (mem)
>  		jobj = json_object_new_string("system-ram");
> -	else
> +	else if (daxctl_dev_is_famfs_mode(dev))
> +		jobj = json_object_new_string("famfs");
> +	else if (daxctl_dev_is_devdax_mode(dev))
>  		jobj = json_object_new_string("devdax");


The 'else if' above, and the 'else' below, both assign "devdax",
so the 'else if' is redundant. And once daxctl_dev_get_mode()
exists, this becomes a switch on the result with "devdax"
as the UNKNOWN fallback.


> +	else {
> +		/* Legacy condition; if a daxdev is not in any "mode", that
> +		 * means no driver is bound. We report that as a disabled
> +		 * device in devdax mode. (the disabled modifier is added later
> +		 * in this function if applicable)
> +		 */
> +		jobj = json_object_new_string("devdax");
> +	}
> +
>  	if (jobj)
>  		json_object_object_add(jdev, "mode", jobj);
>
> diff --git a/daxctl/lib/libdaxctl-private.h b/daxctl/lib/libdaxctl-private.h
> index ae45311e5d57..0bb73e8c04bf 100644
> --- a/daxctl/lib/libdaxctl-private.h
> +++ b/daxctl/lib/libdaxctl-private.h
> @@ -21,12 +21,14 @@ static const char *dax_subsystems[] = {
>  enum daxctl_dev_mode {
>  	DAXCTL_DEV_MODE_DEVDAX = 0,
>  	DAXCTL_DEV_MODE_RAM,
> +	DAXCTL_DEV_MODE_FAMFS,
>  	DAXCTL_DEV_MODE_END,
>  };
>
>  static const char *dax_modules[] = {
>  	[DAXCTL_DEV_MODE_DEVDAX] = "device_dax",
>  	[DAXCTL_DEV_MODE_RAM] = "kmem",
> +	[DAXCTL_DEV_MODE_FAMFS] = "fsdev_dax",
>  };


Add a DAXCTL_DEV_MODE_UNKNOWN sentinel here to support the
daxctl_dev_get_mode() helper cleanly. Doesn't have to be assigned a
value, can reuse END or add it alongside, your call.


>  enum memory_op {
> diff --git a/daxctl/lib/libdaxctl.c b/daxctl/lib/libdaxctl.c
> index 02ae7e50b123..33121dcb1d1b 100644
> --- a/daxctl/lib/libdaxctl.c
> +++ b/daxctl/lib/libdaxctl.c
> @@ -385,13 +385,13 @@ static bool device_model_is_dax_bus(struct daxctl_dev *dev)
>  	return false;
>  }
>
> -DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
> +static int daxctl_dev_bound_to_module(struct daxctl_dev *dev, const char *mod_name)
>  {
>  	const char *devname = daxctl_dev_get_devname(dev);
>  	struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev);
>  	const char *mod_base;
>  	char *mod_path;
> -	char path[200];
> +	char path[PATH_MAX];
>  	const int len = sizeof(path);
>
>  	if (!device_model_is_dax_bus(dev))


Nice!


> @@ -406,11 +406,13 @@ DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
>  	}
>
>  	mod_path = realpath(path, NULL);
> -	if (!mod_path)
> +	if (!mod_path) {
> +		dbg(ctx, "%s: realpath failed for driver link\n", devname);
>  		return false;
> +	}
>
>  	mod_base = path_basename(mod_path);
> -	if (strcmp(mod_base, dax_modules[DAXCTL_DEV_MODE_RAM]) == 0) {
> +	if (strcmp(mod_base, mod_name) == 0) {
>  		free(mod_path);
>  		return true;
>  	}
> @@ -419,6 +421,30 @@ DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
>  	return false;
>  }
>
> +DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_RAM]);
> +}


With daxctl_dev_is_famfs_mode() and daxctl_dev_is_devdax_mode() now
alongside it, the _capable vs _mode naming split looks inconsistent.
Post-refactor all three delegate to daxctl_dev_bound_to_module(), which
checks current driver binding.

Either document the semantic distinction more explicitly or consider
adding daxctl_dev_is_system_ram_mode() as the preferred interface.


> +
> +/*
> + * Check if device is currently in famfs mode (bound to fsdev_dax driver).
> + * Returns false for disabled devices: the DAX bus does not retain the previous
> + * driver binding after unbind, so mode cannot be determined without a driver.
> + */
> +DAXCTL_EXPORT int daxctl_dev_is_famfs_mode(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_FAMFS]);
> +}
> +
> +/*
> + * Check if device is currently in devdax mode (bound to device_dax driver).
> + * Returns false for disabled devices; see daxctl_dev_is_famfs_mode().
> + */
> +DAXCTL_EXPORT int daxctl_dev_is_devdax_mode(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_bound_to_module(dev, dax_modules[DAXCTL_DEV_MODE_DEVDAX]);
> +}

These comments claim "Returns false for disabled devices" as if that
were unique to these two helpers, but it isn't. daxctl_dev_is_system_ram_capable
returns false for disabled devices too. Either drop the comment or move it to
to daxctl_dev_bound_to_module().

> +
>  /*
>   * This checks for the device to be in system-ram mode, so calling
>   * daxctl_dev_get_memory() on a devdax mode device will always return NULL.
> @@ -983,6 +1009,11 @@ DAXCTL_EXPORT int daxctl_dev_enable_ram(struct daxctl_dev *dev)
>  	return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_RAM);
>  }
>
> +DAXCTL_EXPORT int daxctl_dev_enable_famfs(struct daxctl_dev *dev)
> +{
> +	return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_FAMFS);
> +}

OK.

> +
>  DAXCTL_EXPORT int daxctl_dev_disable(struct daxctl_dev *dev)
>  {
>  	const char *devname = daxctl_dev_get_devname(dev);
> diff --git a/daxctl/lib/libdaxctl.sym b/daxctl/lib/libdaxctl.sym
> index 309881196c86..2a812c6ad918 100644
> --- a/daxctl/lib/libdaxctl.sym
> +++ b/daxctl/lib/libdaxctl.sym
> @@ -104,3 +104,10 @@ LIBDAXCTL_10 {
>  global:
>  	daxctl_dev_is_system_ram_capable;
>  } LIBDAXCTL_9;
> +
> +LIBDAXCTL_11 {
> +global:
> +	daxctl_dev_enable_famfs;
> +	daxctl_dev_is_famfs_mode;
> +	daxctl_dev_is_devdax_mode;
> +} LIBDAXCTL_10;


If you resolve the _capable/_mode naming inconsistency by adding
daxctl_dev_is_system_ram_mode(), please export it here as well.


> diff --git a/daxctl/libdaxctl.h b/daxctl/libdaxctl.h
> index 53c6bbdae5c3..84fcdb40c7a9 100644
> --- a/daxctl/libdaxctl.h
> +++ b/daxctl/libdaxctl.h
> @@ -72,12 +72,15 @@ int daxctl_dev_is_enabled(struct daxctl_dev *dev);
>  int daxctl_dev_disable(struct daxctl_dev *dev);
>  int daxctl_dev_enable_devdax(struct daxctl_dev *dev);
>  int daxctl_dev_enable_ram(struct daxctl_dev *dev);
> +int daxctl_dev_enable_famfs(struct daxctl_dev *dev);
>  int daxctl_dev_get_target_node(struct daxctl_dev *dev);
>  int daxctl_dev_will_auto_online_memory(struct daxctl_dev *dev);
>  int daxctl_dev_has_online_memory(struct daxctl_dev *dev);
>
>  struct daxctl_memory;
>  int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev);
> +int daxctl_dev_is_famfs_mode(struct daxctl_dev *dev);
> +int daxctl_dev_is_devdax_mode(struct daxctl_dev *dev);


Mirror whatever you do about _capable vs _mode here. If you add
daxctl_dev_get_mode() as a public library function, declare it here
too.


>  struct daxctl_memory *daxctl_dev_get_memory(struct daxctl_dev *dev);
>  struct daxctl_dev *daxctl_memory_get_dev(struct daxctl_memory *mem);
>  const char *daxctl_memory_get_node_path(struct daxctl_memory *mem);

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions:
  2026-04-30 15:34   ` [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions: John Groves
  2026-04-30 16:27     ` Dave Jiang
@ 2026-05-13  5:34     ` Alison Schofield
  1 sibling, 0 replies; 8+ messages in thread
From: Alison Schofield @ 2026-05-13  5:34 UTC (permalink / raw)
  To: John Groves
  Cc: John Groves, John Groves, Dan Williams, John Groves, Vishal Verma,
	Dave Jiang, Jonathan Cameron, Aravind Ramesh, Ajay Joshi,
	venkataravis@micron.com, dev.srinivasulu@gmail.com,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org

On Thu, Apr 30, 2026 at 03:34:18PM +0000, John Groves wrote:
> From: John Groves <John@Groves.net>

Hi John,

Thanks for the famfs mode-transition test. As promised, I took a look
at how we can make it work as a unit test in an NDCTL test environment.

The main blocker is the device-selection assumption:
    region_id="$("$DAXCTL" list -R |
        jq -r ".[] | select(.path | contains(\"cxl_acpi.0\")) | .id")"

and then the test grabs whatever happens to be the first dax device on
that region. That works in your QEMU setup but not in the ndctl unit-test
model. NDCTL tests here are always tied to one of the emulated backends
(nfit_test or cxl_test) and build their own test device from a known
starting state. They don't scan for "an existing dax device" because there
is no such thing in a fresh `meson test` run. Whatever is there may be
leftover state from something else.

So the test as written will either skip (no dax device found) or get
tangled up with whatever prior test left the system in. Traditionally
DAX has used nfit for its emulated backing, but CXL is a reasonable
choice too. I've prepared both examples and appended to the end of
this message.

test/daxctl-famfs-nfit.sh
    ACPI.NFIT-backed testdev with full devdax/famfs/system-ram coverage

test/daxctl-famfs-cxltest.sh
    cxl_test-backed testdev with devdax/famfs only, plus the 
    system-ram -> famfs rejection via -N

The cxl_test version cannot exercise kmem onlining because the memory
has no real DRAM backing.

My preference is the nfit version since it exercises the full transition
matrix end-to-end, but if FAMFS is primarily a CXL story the cxl_test
version is the equivalent. And of course, there is no lock-down here,
ie. we can start with nfit and then when DCD support comes around and
that is in cxl-test, we can switch completely to CXL or use both nfit
and CXL. But - you do need to pick a lane to start in.

Beyond selecting a backend, both versions were also aligned with the
existing test style:
- Drop per-step narration as set -x logs commands
- Only emit failure path messages
- Replaced printf with echo 
- Suppress reconfigure-device JSON output with >/dev/null.
- Remove restore-to-original-mode cleanup logic; rely on fixture
  teardown instead.
- Use err helpers for traps and cleanup
- Extend ensure_devdax_mode to handle system-ram start state
- Add check_dmesg "$LINENO" at test completion.

Both versions have been tested, but please verify that the exact
coverage and transition matrix still match what you want after my
editing.

Please post the selected version as part of the next revision of
this series

Patches appended below...

-- Alison

======

test/daxctl-famfs-nfit.sh: test famfs mode transitions on nfit_test

Exercise devdax <-> famfs <-> system-ram transitions and JSON mode
reporting on a dax device backed by the emulated ACPI.NFIT bus.

nfit_test was chosen because its pmem ranges have real DRAM backing, so
kmem onlining of the system-ram mode works normally and the full matrix
of transitions can be tested end-to-end.
---
 test/daxctl-famfs-nfit.sh | 215 ++++++++++++++++++++++++++++++++++++++
 test/meson.build          |   4 +-
 2 files changed, 218 insertions(+), 1 deletion(-)
 create mode 100755 test/daxctl-famfs-nfit.sh

diff --git a/test/daxctl-famfs-nfit.sh b/test/daxctl-famfs-nfit.sh
new file mode 100755
index 000000000000..57302795c89f
--- /dev/null
+++ b/test/daxctl-famfs-nfit.sh
@@ -0,0 +1,215 @@
+#!/bin/bash -Ex
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2025 Micron Technology, Inc. All rights reserved.
+#
+# Test daxctl famfs mode transitions and mode detection, targeting a
+# nfit_test-backed dax device.
+#
+# nfit_test-backed dax devices have real DRAM backing, so kmem onlining
+# works normally. This test exercises the full matrix of transitions
+# between devdax, famfs, and system-ram.
+
+rc=77
+. $(dirname $0)/common
+
+trap 'cleanup $LINENO' ERR
+
+testbus=""
+testdev=""
+daxdev=""
+
+cleanup()
+{
+	# Best-effort return to devdax so destroy-namespace can succeed.
+	if [[ -n $daxdev ]]; then
+		"$DAXCTL" reconfigure-device -f -m devdax "$daxdev" 2>/dev/null || true
+	fi
+	[[ -n $testdev ]] && reset_dev
+	err "$1"
+}
+
+check_fsdev_dax()
+{
+	modinfo fsdev_dax &>/dev/null && return 0
+	grep -qF "fsdev_dax" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null && return 0
+	do_skip "fsdev_dax module not available"
+}
+
+check_kmem()
+{
+	modinfo kmem &>/dev/null && return 0
+	grep -qF "kmem" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null && return 0
+	do_skip "kmem module not available"
+}
+
+find_testdev()
+{
+	testbus="$ACPI_BUS"
+
+	# Ensure the bus has labels, like align.sh / daxctl-devices.sh rely on.
+	"$NDCTL" disable-region -b "$testbus" all
+	"$NDCTL" init-labels -f -b "$testbus" all
+	"$NDCTL" enable-region -b "$testbus" all
+
+	testdev=$("$NDCTL" list -b "$testbus" -Ni | jq -er '.[0].dev | .//""')
+	[[ $testdev ]] || do_skip "no victim device on $testbus"
+}
+
+setup_dev()
+{
+	test -n "$testbus"
+	test -n "$testdev"
+
+	"$NDCTL" destroy-namespace -f -b "$testbus" "$testdev"
+	# x86_64 memory hotplug can require up to a 2GiB-aligned chunk of
+	# memory. Create a 4GiB namespace, so enough space is left after
+	# alignment for kmem + online.
+	testdev=$("$NDCTL" create-namespace -b "$testbus" -m devdax -fe "$testdev" -s 4G | \
+		jq -er '.dev')
+	test -n "$testdev"
+
+	daxdev=$("$NDCTL" list -n "$testdev" -X | jq -er '.[].daxregion.devices[0].chardev')
+	test -n "$daxdev"
+}
+
+reset_dev()
+{
+	"$NDCTL" destroy-namespace -f -b "$testbus" "$testdev"
+}
+
+daxctl_get_mode()
+{
+	"$DAXCTL" list -d "$1" | jq -er '.[].mode'
+}
+
+save_online_policy()
+{
+	saved_policy="$(cat /sys/devices/system/memory/auto_online_blocks)"
+}
+
+restore_online_policy()
+{
+	echo "$saved_policy" > /sys/devices/system/memory/auto_online_blocks
+}
+
+unset_online_policy()
+{
+	echo "offline" > /sys/devices/system/memory/auto_online_blocks
+}
+
+ensure_devdax_mode()
+{
+	local mode
+	mode=$(daxctl_get_mode "$daxdev")
+
+	case "$mode" in
+	devdax)      return 0 ;;
+	famfs)       "$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null ;;
+	system-ram)  "$DAXCTL" reconfigure-device -f -m devdax "$daxdev" >/dev/null ;;
+	*)
+		echo "unexpected starting mode: $mode"
+		return 1
+		;;
+	esac
+
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+}
+
+test_famfs_mode_transitions()
+{
+	ensure_devdax_mode
+
+	# devdax -> famfs
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+
+	# famfs -> famfs (re-enable in same mode)
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+
+	# famfs -> devdax
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+
+	# devdax -> devdax (re-enable in same mode)
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+}
+
+test_json_output()
+{
+	ensure_devdax_mode
+	[[ $("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode') == "devdax" ]]
+
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+	[[ $("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode') == "famfs" ]]
+
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+}
+
+test_error_handling()
+{
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+
+	# Invalid mode must be rejected
+	if "$DAXCTL" reconfigure-device -m invalidmode "$daxdev" &>/dev/null; then
+		echo "FAIL: invalid mode should be rejected"
+		return 1
+	fi
+
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+}
+
+# Full system-ram transitions (real backing, so online_pages() works).
+# Turns auto-online off so daxctl drives onlining explicitly.
+test_system_ram_transitions()
+{
+	save_online_policy
+	unset_online_policy
+
+	ensure_devdax_mode
+
+	# devdax -> system-ram (no-online)
+	"$DAXCTL" reconfigure-device -N -m system-ram "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "system-ram" ]]
+
+	# system-ram -> famfs must be rejected
+	if "$DAXCTL" reconfigure-device -m famfs "$daxdev" &>/dev/null; then
+		echo "FAIL: system-ram -> famfs should be rejected"
+		restore_online_policy
+		return 1
+	fi
+
+	# system-ram -> devdax -> famfs
+	"$DAXCTL" reconfigure-device -f -m devdax "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+
+	# Full online cycle: devdax -> system-ram (with online) -> devdax.
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+	"$DAXCTL" reconfigure-device -m system-ram "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "system-ram" ]]
+	"$DAXCTL" reconfigure-device -f -m devdax "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+
+	restore_online_policy
+}
+
+check_fsdev_dax
+check_kmem
+
+rc=1
+
+find_testdev
+setup_dev
+
+test_famfs_mode_transitions
+test_json_output
+test_error_handling
+test_system_ram_transitions
+
+ensure_devdax_mode
+reset_dev
+
+check_dmesg "$LINENO"
diff --git a/test/meson.build b/test/meson.build
index 56aed9cc3c9d..d765fd99a4b1 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -218,6 +218,7 @@ if get_option('destructive').enabled()
   daxctl_devices = find_program('daxctl-devices.sh')
   daxctl_create = find_program('daxctl-create.sh')
   daxctl_famfs = find_program('daxctl-famfs.sh')
+  daxctl_famfs_nfit = find_program('daxctl-famfs-nfit.sh')
   dm = find_program('dm.sh')
   mmap_test = find_program('mmap.sh')
 
@@ -235,7 +236,8 @@ if get_option('destructive').enabled()
     [ 'device-dax-fio.sh', device_dax_fio, 'dax'   ],
     [ 'daxctl-devices.sh', daxctl_devices, 'dax'   ],
     [ 'daxctl-create.sh',  daxctl_create,  'dax'   ],
-    [ 'daxctl-famfs.sh',   daxctl_famfs,   'dax'   ],
+    [ 'daxctl-famfs.sh',        daxctl_famfs,       'dax' ],
+    [ 'daxctl-famfs-nfit.sh',   daxctl_famfs_nfit,  'dax' ],
     [ 'dm.sh',             dm,		   'dax'   ],
     [ 'mmap.sh',           mmap_test,	   'dax'   ],
   ]
-- 
2.37.3

=========

test/daxctl-famfs-cxltest.sh: test famfs mode transitions on cxl_test

Exercise devdax <-> famfs transitions and JSON mode reporting on a dax
device backed by the emulated cxl_test bus.

cxl_test was chosen as an alternative to nfit_test for callers who want
the famfs unit test to run against a CXL-backed dax device. Note that
cxl_test emulated memory has no real DRAM backing, so this test avoids
code paths that online memory through kmem; only the userspace-side
rejection of system-ram -> famfs is exercised (via -N, which skips
online_pages()).
---
 test/daxctl-famfs-cxltest.sh | 139 +++++++++++++++++++++++++++++++++++
 test/meson.build             |   4 +-
 2 files changed, 142 insertions(+), 1 deletion(-)
 create mode 100755 test/daxctl-famfs-cxltest.sh

diff --git a/test/daxctl-famfs-cxltest.sh b/test/daxctl-famfs-cxltest.sh
new file mode 100755
index 000000000000..aa85946c42ee
--- /dev/null
+++ b/test/daxctl-famfs-cxltest.sh
@@ -0,0 +1,139 @@
+#!/bin/bash -Ex
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2025 Micron Technology, Inc. All rights reserved.
+#
+# Test daxctl famfs mode transitions and mode detection, targeting a
+# cxl_test-backed dax device.
+#
+# NOTE: cxl_test backs its "memory" with a gen_pool region carved from high
+# iomem space above iomem_resource.end. There is no real DRAM behind those
+# pfns. Therefore this test does NOT exercise any code path that calls
+# online_pages() on cxl_test memory. In particular there is no "-m system-ram"
+# that runs without -N, and no explicit 'daxctl online-memory'.
+#
+# The 'system-ram -> famfs rejection' case below uses -N so that add_memory()
+# runs but online_pages() does not. The rejection itself is a userspace-only
+# check in reconfig_mode_famfs() (refuses when daxctl_memory is present), so
+# it does not depend on the memory being onlined and is safe here.
+
+rc=77
+. $(dirname $0)/common
+
+trap 'err $LINENO' ERR
+
+modprobe -r cxl_test
+modprobe cxl_test
+
+rc=1
+
+daxdev=""
+
+check_fsdev_dax()
+{
+	modinfo fsdev_dax &>/dev/null && return 0
+	grep -qF "fsdev_dax" "/lib/modules/$(uname -r)/modules.builtin" 2>/dev/null && return 0
+	do_skip "fsdev_dax module not available"
+}
+
+find_daxdev()
+{
+	region_id="$("$DAXCTL" list -R |
+		jq -r ".[] | select(.path | contains(\"cxl_acpi.0\")) | .id")"
+	[[ $region_id ]] || do_skip "Unable to find a CXL region"
+
+	daxdev=$("$DAXCTL" list -D -r "$region_id" |
+		jq -er '.[0].chardev | .//""')
+	[[ $daxdev ]] || do_skip "Unable to find a DAX device"
+}
+
+daxctl_get_mode()
+{
+	"$DAXCTL" list -d "$1" | jq -er '.[].mode'
+}
+
+ensure_devdax_mode()
+{
+	local mode
+	mode=$(daxctl_get_mode "$daxdev")
+
+	case "$mode" in
+	devdax)      return 0 ;;
+	famfs)       "$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null ;;
+	system-ram)  "$DAXCTL" reconfigure-device -f -m devdax "$daxdev" >/dev/null ;;
+	*)           err "$LINENO" ;;
+	esac
+
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+}
+
+test_famfs_mode_transitions()
+{
+	ensure_devdax_mode
+
+	# devdax -> famfs
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+
+	# famfs -> famfs (re-enable in same mode)
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "famfs" ]]
+
+	# famfs -> devdax
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+
+	# devdax -> devdax (re-enable in same mode)
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+}
+
+test_json_output()
+{
+	ensure_devdax_mode
+	[[ $("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode') == "devdax" ]]
+
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+	[[ $("$DAXCTL" list -d "$daxdev" | jq -er '.[].mode') == "famfs" ]]
+
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+}
+
+test_error_handling()
+{
+	"$DAXCTL" reconfigure-device -m famfs "$daxdev" >/dev/null
+
+	# Invalid mode must be rejected
+	if "$DAXCTL" reconfigure-device -m invalidmode "$daxdev" &>/dev/null; then
+		err "$LINENO"
+	fi
+
+	"$DAXCTL" reconfigure-device -m devdax "$daxdev" >/dev/null
+}
+
+test_system_ram_rejects_famfs()
+{
+	ensure_devdax_mode
+
+	# -N: bind kmem without onlining memory
+	"$DAXCTL" reconfigure-device -N -m system-ram "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "system-ram" ]]
+
+	if "$DAXCTL" reconfigure-device -m famfs "$daxdev" &>/dev/null; then
+		err "$LINENO"
+	fi
+
+	"$DAXCTL" reconfigure-device -f -m devdax "$daxdev" >/dev/null
+	[[ $(daxctl_get_mode "$daxdev") == "devdax" ]]
+}
+
+check_fsdev_dax
+find_daxdev
+
+test_famfs_mode_transitions
+test_json_output
+test_error_handling
+test_system_ram_rejects_famfs
+
+check_dmesg "$LINENO"
+
+modprobe -r cxl_test
diff --git a/test/meson.build b/test/meson.build
index d765fd99a4b1..ae5477408617 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -219,6 +219,7 @@ if get_option('destructive').enabled()
   daxctl_create = find_program('daxctl-create.sh')
   daxctl_famfs = find_program('daxctl-famfs.sh')
   daxctl_famfs_nfit = find_program('daxctl-famfs-nfit.sh')
+  daxctl_famfs_cxltest = find_program('daxctl-famfs-cxltest.sh')
   dm = find_program('dm.sh')
   mmap_test = find_program('mmap.sh')
 
@@ -237,7 +238,8 @@ if get_option('destructive').enabled()
     [ 'daxctl-devices.sh', daxctl_devices, 'dax'   ],
     [ 'daxctl-create.sh',  daxctl_create,  'dax'   ],
     [ 'daxctl-famfs.sh',        daxctl_famfs,       'dax' ],
-    [ 'daxctl-famfs-nfit.sh',   daxctl_famfs_nfit,  'dax' ],
+    [ 'daxctl-famfs-nfit.sh',     daxctl_famfs_nfit,     'dax' ],
+    [ 'daxctl-famfs-cxltest.sh',  daxctl_famfs_cxltest,  'dax' ],
     [ 'dm.sh',             dm,		   'dax'   ],
     [ 'mmap.sh',           mmap_test,	   'dax'   ],
   ]
-- 
2.37.3




^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-05-13  5:35 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260430153331.84139-1-john@jagalactic.com>
2026-04-30 15:33 ` [PATCH V5 0/2] daxctl: Add support for famfs mode John Groves
2026-04-30 15:34   ` [PATCH V5 1/2] " John Groves
2026-04-30 16:20     ` Dave Jiang
2026-05-13  5:32     ` Alison Schofield
2026-04-30 15:34   ` [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions: John Groves
2026-04-30 16:27     ` Dave Jiang
2026-04-30 16:50       ` John Groves
2026-05-13  5:34     ` Alison Schofield

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox