[RFC PATCH 2/4] ext4: add dax_fc_bytelog mount option

From: Li Chen

Date: Thu Feb 26 2026 - 05:21:17 EST


Add dax_fc_bytelog={off,on,force} to control the DAX ByteLog fast commit
backend.
Initialize the ByteLog ring before fast commit replay and release it on
unmount.

Signed-off-by: Li Chen <me@linux.beauty>
---
fs/ext4/super.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 76 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index 504148b2142b..3645456a61dd 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1368,6 +1368,7 @@ static void ext4_put_super(struct super_block *sb)
sbi->s_ea_block_cache = NULL;

ext4_stop_mmpd(sbi);
+ ext4_fc_bytelog_release(sb);

brelse(sbi->s_sbh);
sb->s_fs_info = NULL;
@@ -1685,6 +1686,8 @@ enum {
Opt_max_dir_size_kb, Opt_nojournal_checksum, Opt_nombcache,
Opt_no_prefetch_block_bitmaps, Opt_mb_optimize_scan,
Opt_errors, Opt_data, Opt_data_err, Opt_jqfmt, Opt_dax_type,
+ Opt_dax_fc_bytelog, Opt_dax_fc_bytelog_off, Opt_dax_fc_bytelog_on,
+ Opt_dax_fc_bytelog_force,
#ifdef CONFIG_EXT4_DEBUG
Opt_fc_debug_max_replay, Opt_fc_debug_force
#endif
@@ -1724,6 +1727,13 @@ static const struct constant_table ext4_param_dax[] = {
{}
};

+static const struct constant_table ext4_param_dax_fc_bytelog[] = {
+ {"off", Opt_dax_fc_bytelog_off},
+ {"on", Opt_dax_fc_bytelog_on},
+ {"force", Opt_dax_fc_bytelog_force},
+ {}
+};
+
/*
* Mount option specification
* We don't use fsparam_flag_no because of the way we set the
@@ -1780,6 +1790,8 @@ static const struct fs_parameter_spec ext4_param_specs[] = {
fsparam_flag ("i_version", Opt_removed),
fsparam_flag ("dax", Opt_dax),
fsparam_enum ("dax", Opt_dax_type, ext4_param_dax),
+ fsparam_enum("dax_fc_bytelog", Opt_dax_fc_bytelog,
+ ext4_param_dax_fc_bytelog),
fsparam_u32 ("stripe", Opt_stripe),
fsparam_flag ("delalloc", Opt_delalloc),
fsparam_flag ("nodelalloc", Opt_nodelalloc),
@@ -1965,6 +1977,7 @@ ext4_sb_read_encoding(const struct ext4_super_block *es)
#define EXT4_SPEC_s_fc_debug_max_replay (1 << 17)
#define EXT4_SPEC_s_sb_block (1 << 18)
#define EXT4_SPEC_mb_optimize_scan (1 << 19)
+#define EXT4_SPEC_s_dax_fc_bytelog BIT(20)

struct ext4_fs_context {
char *s_qf_names[EXT4_MAXQUOTAS];
@@ -2370,6 +2383,26 @@ static int ext4_parse_param(struct fs_context *fc, struct fs_parameter *param)
ext4_msg(NULL, KERN_INFO, "dax option not supported");
return -EINVAL;
#endif
+ case Opt_dax_fc_bytelog:
+ switch (result.uint_32) {
+ case Opt_dax_fc_bytelog_off:
+ ctx_clear_mount_opt2(ctx, EXT4_MOUNT2_DAX_FC_BYTELOG);
+ ctx_clear_mount_opt2(ctx,
+ EXT4_MOUNT2_DAX_FC_BYTELOG_FORCE);
+ break;
+ case Opt_dax_fc_bytelog_on:
+ ctx_set_mount_opt2(ctx, EXT4_MOUNT2_DAX_FC_BYTELOG);
+ ctx_clear_mount_opt2(ctx,
+ EXT4_MOUNT2_DAX_FC_BYTELOG_FORCE);
+ break;
+ case Opt_dax_fc_bytelog_force:
+ ctx_set_mount_opt2(ctx, EXT4_MOUNT2_DAX_FC_BYTELOG);
+ ctx_set_mount_opt2(ctx,
+ EXT4_MOUNT2_DAX_FC_BYTELOG_FORCE);
+ break;
+ }
+ ctx->spec |= EXT4_SPEC_s_dax_fc_bytelog;
+ return 0;
case Opt_data_err:
if (result.uint_32 == Opt_data_err_abort)
ctx_set_mount_opt(ctx, m->mount_opt);
@@ -2819,7 +2852,22 @@ static int ext4_check_opt_consistency(struct fs_context *fc,
!(sbi->s_mount_opt2 & EXT4_MOUNT2_DAX_INODE))) {
goto fail_dax_change_remount;
}
- }
+
+ if (ctx->spec & EXT4_SPEC_s_dax_fc_bytelog) {
+ bool new_on = ctx_test_mount_opt2(ctx,
+ EXT4_MOUNT2_DAX_FC_BYTELOG);
+ bool new_force = ctx_test_mount_opt2(ctx,
+ EXT4_MOUNT2_DAX_FC_BYTELOG_FORCE);
+ bool cur_on = test_opt2(sb, DAX_FC_BYTELOG);
+ bool cur_force = test_opt2(sb, DAX_FC_BYTELOG_FORCE);
+
+ if (new_on != cur_on || new_force != cur_force) {
+ ext4_msg(NULL, KERN_ERR,
+ "can't change dax_fc_bytelog mount option while remounting");
+ return -EINVAL;
+ }
+ }
+ }

return ext4_check_quota_consistency(fc, sb);
}
@@ -3038,6 +3086,12 @@ static int _ext4_show_options(struct seq_file *seq, struct super_block *sb,
} else if (test_opt2(sb, DAX_INODE)) {
SEQ_OPTS_PUTS("dax=inode");
}
+ if (test_opt2(sb, DAX_FC_BYTELOG)) {
+ if (test_opt2(sb, DAX_FC_BYTELOG_FORCE))
+ SEQ_OPTS_PUTS("dax_fc_bytelog=force");
+ else
+ SEQ_OPTS_PUTS("dax_fc_bytelog=on");
+ }

if (sbi->s_groups_count >= MB_DEFAULT_LINEAR_SCAN_THRESHOLD &&
!test_opt2(sb, MB_OPTIMIZE_SCAN)) {
@@ -4950,6 +5004,8 @@ static int ext4_load_and_init_journal(struct super_block *sb,
"Failed to set fast commit journal feature");
goto out;
}
+ if (test_opt2(sb, JOURNAL_FAST_COMMIT))
+ ext4_fc_bytelog_init(sb, sbi->s_journal);

/* We have now updated the journal if required, so we can
* validate the data journaling mode. */
@@ -6124,10 +6180,29 @@ static int ext4_load_journal(struct super_block *sb,
char *save = kmalloc(EXT4_S_ERR_LEN, GFP_KERNEL);
__le16 orig_state;
bool changed = false;
+ int fc_err;

if (save)
memcpy(save, ((char *) es) +
EXT4_S_ERR_START, EXT4_S_ERR_LEN);
+
+ /*
+ * Map the ByteLog ring before fast-commit replay so that
+ * EXT4_FC_TAG_DAX_BYTELOG_ANCHOR records can be processed
+ * during jbd2_journal_load().
+ *
+ * For filesystems with the INCOMPAT_DAX_FC_BYTELOG feature
+ * bit set, failing to initialize the ByteLog ring must be
+ * treated as fatal.
+ */
+ if (test_opt2(sb, JOURNAL_FAST_COMMIT)) {
+ fc_err = ext4_fc_bytelog_init(sb, journal);
+ if (fc_err && ext4_has_feature_dax_fc_bytelog(sb)) {
+ kfree(save);
+ err = fc_err;
+ goto err_out;
+ }
+ }
err = jbd2_journal_load(journal);
if (save && memcmp(((char *) es) + EXT4_S_ERR_START,
save, EXT4_S_ERR_LEN)) {
--
2.52.0