* Re: [PATCH V5 1/2] daxctl: Add support for famfs mode
[not found] ` <0100019ddf06b207-eaf8cb8a-066e-4642-8947-effdb4848c20-000000@email.amazonses.com>
@ 2026-05-13 5:32 ` Alison Schofield
0 siblings, 0 replies; 2+ 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] 2+ messages in thread
* Re: [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions:
[not found] ` <0100019ddf06ce8f-c323d9cd-333b-4076-9717-7c80dbed7620-000000@email.amazonses.com>
@ 2026-05-13 5:34 ` Alison Schofield
0 siblings, 0 replies; 2+ 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] 2+ messages in thread
end of thread, other threads:[~2026-05-13 5:35 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <0100019ddf064477-8322b695-f2d8-481c-9fcd-8b16fc97ad4d-000000@email.amazonses.com>
[not found] ` <20260430153405.84164-1-john@jagalactic.com>
[not found] ` <0100019ddf06b207-eaf8cb8a-066e-4642-8947-effdb4848c20-000000@email.amazonses.com>
2026-05-13 5:32 ` [PATCH V5 1/2] daxctl: Add support for famfs mode Alison Schofield
[not found] ` <20260430153413.84181-1-john@jagalactic.com>
[not found] ` <0100019ddf06ce8f-c323d9cd-333b-4076-9717-7c80dbed7620-000000@email.amazonses.com>
2026-05-13 5:34 ` [PATCH V5 2/2] Add test/daxctl-famfs.sh to test famfs mode transitions: Alison Schofield
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox