mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Aurelien DESBRIERES <aurelien@hackers.camp>
To: Aurelien DESBRIERES <aurelien@hackers.camp>,
	Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Christian Brauner <brauner@kernel.org>, Jan Kara <jack@suse.cz>
Cc: linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-doc@vger.kernel.org, aurelien.desbrieres@gmail.com,
	linux-api@vger.kernel.org
Subject: [RFC PATCH 02/18] beamfs: add in-core header and tracepoints
Date: Mon, 19 Oct 2026 06:17:12 +0200	[thread overview]
Message-ID: <20261019041728.327785-3-aurelien@hackers.camp> (raw)
In-Reply-To: <20261019041728.327785-1-aurelien@hackers.camp>

beamfs.h holds the in-core superblock and inode, and the declarations
shared between the translation units.

beamfs_trace.h defines the tracepoints, among them one event per store
into the block tree and one per read of a slot, which the usual event
filters narrow to a single inode.

Assisted-by: LLM
Signed-off-by: Aurelien DESBRIERES <aurelien@hackers.camp>
---
 fs/beamfs/beamfs.h       | 813 +++++++++++++++++++++++++++++++++++++++
 fs/beamfs/beamfs_trace.h | 341 ++++++++++++++++
 2 files changed, 1154 insertions(+)
 create mode 100644 fs/beamfs/beamfs.h
 create mode 100644 fs/beamfs/beamfs_trace.h

diff --git a/fs/beamfs/beamfs.h b/fs/beamfs/beamfs.h
new file mode 100644
index 000000000000..dc01bcdf2f17
--- /dev/null
+++ b/fs/beamfs/beamfs.h
@@ -0,0 +1,813 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * beamfs - resilient filesystem
+ * Based on: Fuchs, Langer, Trinitis - ARCS 2015
+ *
+ * Author: Aurelien DESBRIERES <aurelien@hackers.camp>
+ */
+
+#ifndef _BEAMFS_H
+#define _BEAMFS_H
+
+#include <linux/hashtable.h>
+#include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+/*
+ * mempool_t and its operations, for the scratch reserve created at
+ * mount. Included explicitly rather than relied on: the aarch64 build
+ * got it through some other header's chain and the x86 one did not,
+ * which is the kind of thing a second architecture is for.
+ */
+#include <linux/mempool.h>
+/*
+ * sb_bread and mark_buffer_dirty, for the wrappers below and for the
+ * many callers that reach them through this header.
+ */
+#include <linux/buffer_head.h>
+
+/*
+ * On-disk format lives in its own header so the userspace tools can
+ * include it without dragging in kernel types.
+ */
+#include "beamfs_format.h"
+
+struct beamfs_sb_info {
+#ifdef CONFIG_BEAMFS_DEBUG_TREE
+	/*
+	 * Every live indirect pointer, so a store that finds a slot at
+	 * zero where one was recorded is caught where it happens rather
+	 * than counted by fsck twenty seconds later.
+	 */
+	DECLARE_HASHTABLE(s_tc, 14);
+	/*
+	 * The same entries keyed by child, for beamfs_free_block: it asks
+	 * "which slots name this block", and walking the whole table for
+	 * that, once per freed block under s_tc_lock, was 70% of the CPU
+	 * of generic/074 (2026-09-23).
+	 */
+	DECLARE_HASHTABLE(s_tc_child, 14);
+	spinlock_t        s_tc_lock;  /* s_tc, s_tc_child, s_tc_entries */
+	unsigned int      s_tc_entries;
+	unsigned int      s_tc_violations;
+#endif
+	/* Block allocator */
+	unsigned long    *s_block_bitmap;  /* In-memory free block bitmap */
+
+	/*
+	 * Scratch pages for decode and encode, reserved at mount.
+	 *
+	 * Every path that touches a block needs one: a lookup decodes a
+	 * directory block, writeback decodes before it re-encodes, the
+	 * sweep decodes what it checks. They were kmalloc'd per call --
+	 * a ten-block directory meant ten allocations to resolve one name
+	 * -- and under memory pressure that turns a path which must not
+	 * block into one that does. generic/558 had twelve tasks in
+	 * uninterruptible sleep and one was in kfree beneath
+	 * beamfs_lookup.
+	 *
+	 * A mempool never returns NULL for GFP_NOFS and does not wait
+	 * while it holds a free element. Thirty-two 4 KiB elements, 128 KiB
+	 * per mount, against a class of stall that only appears when the
+	 * machine is already in trouble -- which is exactly when a
+	 * filesystem has to keep working.
+	 */
+	mempool_t        *s_scratch_pool;
+	/*
+	 * Pages for encoded data blocks on their way to the device, so
+	 * writeback never encodes through the block device cache and
+	 * never holds a buffer lock. Sixty-four is two full bios; a
+	 * flusher that finds the pool empty submits what it holds and
+	 * waits for its own pages to come back.
+	 */
+	mempool_t        *s_wb_pages;
+	/*
+	 * Staging for the superblock's RS encode, allocated at mount.
+	 *
+	 * beamfs_dirty_super_now used to kvmalloc it on every call, and
+	 * beamfs_free_block calls that under s_lock -- a spinlock. An
+	 * allocation that sleeps under a spinlock is a deadlock waiting
+	 * for a machine busy enough, and DEBUG_ATOMIC_SLEEP found it on
+	 * the first generic/076 after it was turned on: "BUG: sleeping
+	 * function called from invalid context", rm holding three locks.
+	 *
+	 * 2769 bytes a mount against an allocation on every block freed.
+	 * Written under s_lock, which every caller of dirty_super_now
+	 * already holds or takes.
+	 */
+	u8               *s_sb_rs_staging;
+	/*
+	 * Bumped by every indirect-parity update.
+	 *
+	 * The per-cpu decode cache in indparity.c keys on it: a cached
+	 * decode from before an update describes a region that has
+	 * changed since, and one counter for the filesystem is enough --
+	 * a redundant decode costs what it was going to cost anyway,
+	 * while a stale hit would report a block as sound that is not.
+	 */
+	atomic64_t        s_ind_parity_gen;
+	unsigned long     s_nblocks;       /* Number of data blocks */
+	unsigned long     s_data_start;    /* First data block number */
+	/* Inode allocator */
+	unsigned long    *s_inode_bitmap;  /* In-memory free inode bitmap */
+	unsigned long     s_ninodes;       /* Total number of inodes */
+	/* Superblock */
+	struct beamfs_super_block *s_beamfs_sb; /* On-disk superblock copy */
+	struct buffer_head       *s_sbh;      /* Buffer head for superblock */
+	/* Array of K bhs; K = s_bitmap_blocks_count (multi-block bitmap). */
+	struct buffer_head      **s_bitmap_blkhs;
+	u32                       s_bitmap_blocks_count;
+
+	/*
+	 * Which bitmap blocks need their on-disk image rebuilt.
+	 *
+	 * Rebuilding means walking 3824 bytes bit by bit out of the
+	 * in-memory bitmap and running sixteen RS encodes over the result.
+	 * Doing that per allocation cost one full re-encode per data block
+	 * touched -- measured at 25264 rebuilds for 25264 allocations, and
+	 * a second RS pass over the superblock alongside it, so two
+	 * metadata encodes for every block of payload. Writing 800 MiB
+	 * meant 220000 of each.
+	 *
+	 * The bit itself is what changed; the codeword only has to be
+	 * right when the buffer reaches the disk. Marking here and
+	 * rebuilding in sync_fs collapses thousands of rebuilds into one
+	 * per block that was actually touched.
+	 *
+	 * s_super_needs_encode is the superblock's equivalent, but the
+	 * superblock is not deferred: the flusher can write its buffer at
+	 * any time, so beamfs_dirty_super rebuilds it at once and the flag
+	 * stays clear.
+	 */
+	unsigned long            *s_bitmap_needs_encode;
+	/*
+	 * Metadata buffers the whole volume shares: bitmap blocks and
+	 * indirect parity regions. One list for all of them, never an
+	 * inode's. A buffer that moves between per-inode lists meets the
+	 * window in mmb_sync where b_mmb is NULL under one inode's lock
+	 * while mmb_mark_buffer_dirty, under another inode's lock, relinks
+	 * the same b_assoc_buffers: list_del corruption, then a soft lockup
+	 * holding mmb->lock (dbench, generic/241, x86-01, 2026-10-15).
+	 * write_inode, sync_inode_metadata and evict sync this list before
+	 * the inode's own; put_super empties it before releasing them.
+	 */
+	struct mapping_metadata_bhs s_shared_bhs;
+	bool                      s_super_needs_encode;
+	spinlock_t                s_lock;     /* Superblock lock */
+
+	/*
+	 * Set once the volume has failed, and never cleared.
+	 *
+	 * A filesystem whose device has gone away must stop writing to
+	 * it. Without this, beamfs kept marking buffers dirty on a device
+	 * that answered every request with EIO -- and the kernel says so:
+	 * mark_buffer_dirty starts with WARN_ON_ONCE(!buffer_uptodate),
+	 * because a buffer that could not be read holds nothing worth
+	 * writing back. generic/338 replaces the device with dm-error and
+	 * watches for exactly that.
+	 *
+	 * Checking buffer_uptodate at each of the thirty-nine
+	 * mark_buffer_dirty sites would silence the warning and treat the
+	 * symptom. The volume is what failed, so the volume is what
+	 * records it: one flag, tested where writes begin, and the mount
+	 * goes read-only so the VFS stops sending more.
+	 */
+	bool                      s_failed;
+	/* Found MOUNTED or ERRORS set at mount: the last mount did not end well. */
+	bool                      s_unclean;
+	unsigned long             s_free_blocks;
+	unsigned long             s_free_inodes;
+
+	/*
+	 * Where the last allocation landed.
+	 *
+	 * find_first_bit scans from zero every time, so the cost of an
+	 * allocation grows with how full the volume is and filling one is
+	 * quadratic. Measured: 240 writes/s on an empty volume, 17 on a
+	 * mostly full one, a fifteenfold fall that made generic/015 and
+	 * generic/074 exceed a twenty-minute timeout.
+	 *
+	 * Resuming from the last position makes the common case -- a
+	 * sequential writer -- constant time. Wrapping to zero on the
+	 * first miss keeps it exhaustive, so a volume with a hole near
+	 * the start still fills completely.
+	 *
+	 * ext2 has done this since 1993 and calls it the goal.
+	 */
+	unsigned long             s_alloc_goal;
+	/*
+	 * Reservation windows of the inodes being written, under s_lock:
+	 * ext2's rsv_window. Each writer allocates inside its own
+	 * interval and nobody else does, so three files written at once
+	 * come out as three contiguous runs instead of one interleaving.
+	 */
+	struct list_head          s_rsv_windows;
+	/* enum BEAMFS_DATA_PROTECTION_*, cached from on-disk SB */
+	u32                       s_scheme;
+	u64                       s_feat_incompat; /* cached from on-disk SB at mount time */
+	bool                      s_data_csum;      /* DATA_CSUM active, cached at mount */
+	bool                      s_data_selfid;    /* DATA_SELFID active, cached at mount */
+
+	/* Indirection parity, cached from the on-disk superblock. */
+	u64                       s_ind_parity_blk;
+	u32                       s_ind_parity_len;
+	u32                       s_ind_parity_mode;
+
+	/* Error budget region, cached from the superblock. */
+	u64                       s_budget_blk;
+	u32                       s_budget_len;
+
+	/*
+	 * Back-pointer to the VFS superblock.
+	 *
+	 * The sysfs attributes are handed an sb_info and need a
+	 * super_block to read the medium. bd_super used to serve; it was
+	 * removed from struct block_device, and reaching for it through
+	 * the buffer_head was always the long way round for something
+	 * this object could simply hold.
+	 */
+	struct super_block       *s_sb;
+
+	/* Clock anchor, cached and refreshed by the scrubber. */
+	u64                       s_anchor_mono;
+	u64                       s_anchor_real;
+	u32                       s_anchor_quality;
+
+	/* Alert rate limiting, one slot per event class. */
+	unsigned long             s_alert_last[3];
+	u64                       s_alert_last_corrected;
+	u32                       s_alert_rate_limit;
+
+	/*
+	 * Background scrubber.
+	 *
+	 * Correction happens on read, so a block nobody reads accumulates
+	 * upsets until it passes the eight-symbol radius and becomes
+	 * unrecoverable. Cold data is exactly the case this filesystem
+	 * exists for -- an archive on a spacecraft is written once and
+	 * read years later -- so waiting for a reader is waiting for the
+	 * failure.
+	 *
+	 * The thread walks allocated blocks at a bounded rate, decodes
+	 * each, and journals what it finds. A block that needed
+	 * correction is written back whole, under the buffer lock: only
+	 * while its owner still maps it at the same place, only if no
+	 * subblock was beyond correction and the buffer is unchanged
+	 * since the decode, and never on a read-only or frozen volume.
+	 * Each upset is correctable on its own; it is their sum that
+	 * loses data. Blocks reached through the wear map have no known
+	 * owner and are checked and reported, never written.
+	 */
+	struct task_struct       *s_scrub_thread;
+	unsigned int              s_scrub_interval_ms; /* between blocks; 0 = idle */
+	u64                       s_scrub_cursor;      /* next block to visit */
+	u64                       s_scrub_passes;      /* completed sweeps */
+	u64                       s_scrub_blocks;      /* blocks checked */
+	u64                       s_scrub_corrected;   /* blocks with corrections */
+	u64                       s_scrub_uncorrectable;
+
+	/*
+	 * Sweep pacing, closed on what the last sweep found.
+	 *
+	 * The interval was a constant the operator set once, so the
+	 * scrubber consumed the same bandwidth on a volume where nothing
+	 * had been corrected in months as on one correcting steadily. In
+	 * a radiotherapy vault the neutron flux exists only during
+	 * treatment: a fixed rate is both wasteful at night and too slow
+	 * during a session, and it is the one environment this filesystem
+	 * was built for.
+	 *
+	 * s_scrub_base_ms is what the operator asked for and the pace
+	 * returns to when the volume is quiet. s_scrub_last_corrected is
+	 * the correction count at the end of the previous sweep, which is
+	 * all that is needed to know whether this one found more.
+	 */
+	/*
+	 * Where the wear scan has reached, and how many worn blocks the
+	 * last pass over the budget found. The cursor is a block offset
+	 * within the data region, not a byte offset in the budget.
+	 */
+	u64                       s_wear_cursor;
+	u64                       s_wear_visits;
+
+	unsigned int              s_scrub_base_ms;
+	u64                       s_scrub_last_corrected;
+	struct kobject            s_kobj;
+	struct completion         s_kobj_unregister;
+};
+
+/*
+ * In-memory inode info (embedded in VFS inode via container_of)
+ */
+struct beamfs_inode_info {
+	__le64          i_direct[BEAMFS_DIRECT_BLOCKS];
+	__le64          i_indirect;
+	__le64          i_dindirect;
+	__le64          i_tindirect;
+	__u32           i_flags;
+	/*
+	 * Whether i_blocks holds the count of the blocks the inode has on
+	 * the medium. The inode does not store it: beamfs_getattr takes it
+	 * from the tree the first time, beamfs_alloc_block and
+	 * beamfs_free_block keep it from then on. Set under i_alloc_mutex,
+	 * with a release; beamfs_getattr reads it without, with an acquire.
+	 */
+	bool            i_blocks_counted;
+	struct mutex    i_alloc_mutex;  /* serialize lookup_or_alloc_phys */
+	/*
+	 * This inode's reservation window, [i_rsv_start, i_rsv_end), on
+	 * s_rsv_windows while it holds one; i_rsv_next is the first block
+	 * of it not yet handed out, i_rsv_size what the next window asks
+	 * for, i_last_alloc the goal. All under s_lock, none on the disk.
+	 */
+	struct list_head i_rsv_list;
+	unsigned long    i_rsv_start;
+	unsigned long    i_rsv_end;
+	unsigned long    i_rsv_next;
+	unsigned int     i_rsv_size;
+	unsigned long    i_last_alloc;
+	/*
+	 * Metadata buffer_heads owned by this inode (bitmap blocks dirtied
+	 * on its behalf). The kernel replaced the old i_data.i_private_list
+	 * mechanism, along with mark_buffer_dirty_inode() and
+	 * invalidate_inode_buffers(), with this per-inode list and the
+	 * mmb_* helpers; ext2 carries the same field as i_metadata_bhs.
+	 */
+	struct mapping_metadata_bhs i_metadata_bhs;
+	struct inode    vfs_inode;  /* Must be last */
+};
+
+static inline struct beamfs_inode_info *BEAMFS_I(struct inode *inode)
+{
+	return container_of(inode, struct beamfs_inode_info, vfs_inode);
+}
+
+static inline struct beamfs_sb_info *BEAMFS_SB(struct super_block *sb)
+{
+	return sb->s_fs_info;
+}
+
+/* Function prototypes */
+/* super.c */
+int beamfs_fill_super(struct super_block *sb, struct fs_context *fc);
+
+/* scrub.c */
+int  beamfs_scrub_init(struct super_block *sb);
+void beamfs_scrub_exit(struct super_block *sb);
+int  beamfs_scrub_check_block(struct super_block *sb, u64 phys,
+			      unsigned int *corrected,
+			      struct inode *owner, u64 iblock);
+void beamfs_scrub_stop(struct super_block *sb);
+
+/* file_inline.c */
+int  beamfs_count_blocks(struct inode *inode, u64 *count);
+int  beamfs_inline_lookup_phys(struct inode *inode, u64 iblock_logical,
+			       u64 *phys_out);
+
+/* super.c -- the RS journal */
+/*
+ * beamfs_log_rs_event -- record a Reed-Solomon correction event in the
+ *                       persistent superblock journal.
+ *
+ * @sb:           mounted superblock (sbi must be initialized)
+ * @block_no:     block number where correction occurred, OR a SB
+ *                sub-block sentinel (see BEAMFS_RS_BLOCK_NO_SB_MARKER)
+ * @positions:    array of byte positions corrected within the codeword.
+ *                MUST be non-NULL when n_positions >= 1, valid for
+ *                n_positions entries. MUST be NULL when n_positions == 0
+ *                (uncorrectable event, see UNCORRECTABLE policy below).
+ * @n_positions:  number of corrections; MUST be in [0, BEAMFS_RS_PARITY/2].
+ *                When >= 1, equals the symbol count and is written
+ *                verbatim into re_symbol_count. When == 0, denotes an
+ *                uncorrectable event (see UNCORRECTABLE policy below).
+ * @code_len_bytes: data length of the codeword that produced @positions
+ *                  (BEAMFS_INODE_RS_DATA for inodes, BEAMFS_SUBBLOCK_DATA
+ *                  for bitmap subblocks, BEAMFS_SB_RS_DATA_LEN for SB
+ *                  subblocks). Required by the entropy estimator to
+ *                  compute the bin index from each byte position.
+ *                  MUST be in [1, BEAMFS_SUBBLOCK_DATA], even for
+ *                  uncorrectable events.
+ *
+ * Forensic policy:
+ *   n_positions >= 2  -> entropy computed, ENTROPY_VALID flag set,
+ *                        re_symbol_count = n_positions
+ *   n_positions == 1  -> entropy zeroed, ENTROPY_VALID cleared,
+ *                        re_symbol_count = 1 (single-sample event is
+ *                        not forensically significant; Family A vs B
+ *                        distinction relies on timestamp clustering
+ *                        for these entries)
+ *   n_positions == 0  -> UNCORRECTABLE flag set, ENTROPY_VALID cleared,
+ *                        re_symbol_count = 0, re_entropy_q16_16 = 0.
+ *                        re_block_no and re_timestamp retain their
+ *                        normal meaning, allowing the entry to
+ *                        participate in temporal and spatial clustering
+ *                        analyses alongside correctable events.
+ *
+ * Computes the Shannon entropy estimate from @positions and stores it
+ * in re_entropy_q16_16 with BEAMFS_RS_EVENT_FLAG_ENTROPY_VALID set
+ * (only when n_positions >= 2; see Forensic policy above).
+ *
+ * Invariants enforced via WARN_ON_ONCE: a call that breaks one is not
+ * journalled, and reserved bits in extra_flags are masked off; nothing
+ * panics. Takes s_lock with spin_lock(), so not for interrupt context.
+ */
+/*
+ * beamfs_log_rs_event_flagged -- variant of beamfs_log_rs_event that
+ * accepts an OR-mask of extra_flags to set on the journal entry in
+ * addition to the policy-derived flags (ENTROPY_VALID, UNCORRECTABLE).
+ *
+ * Used by call sites that can distinguish caller context (e.g.
+ * read path vs RMW-neutralised write path in file_inline.c) and
+ * want that distinction recorded in re_flags. Pass extra_flags = 0
+ * for the legacy behaviour (then beamfs_log_rs_event is the
+ * idiomatic alias).
+ *
+ * extra_flags MUST only contain BEAMFS_RS_EVENT_FLAG_* bits and the
+ * subblock field built by beamfs_rs_event_subblock_bits().
+ * ENTROPY_VALID and UNCORRECTABLE are reserved for the policy and MUST
+ * NOT appear in extra_flags (WARN_ON_ONCE enforces).
+ */
+void beamfs_log_rs_event_flagged(struct super_block *sb,
+				 u64 block_no,
+			const u8 *positions,
+			unsigned int n_positions,
+			size_t code_len_bytes,
+			u32 extra_flags);
+
+void beamfs_log_rs_event(struct super_block *sb,
+			 u64 block_no,
+			const u8 *positions,
+			unsigned int n_positions,
+			size_t code_len_bytes);
+void beamfs_dirty_super(struct beamfs_sb_info *sbi);
+
+/* file.c - BEAMFS_DATA_PROTECTION_UNIVERSAL_INLINE data path (v2) */
+extern const struct address_space_operations beamfs_inline_aops;
+extern const struct file_operations          beamfs_inline_file_operations;
+
+/* inode.c */
+struct inode *beamfs_iget(struct super_block *sb, unsigned long ino);
+int beamfs_getattr(const struct mnt_idmap *idmap, const struct path *path,
+		   struct kstat *stat, u32 request_mask,
+		   unsigned int query_flags);
+/* namei.c */
+struct inode *beamfs_new_inode(struct inode *dir, umode_t mode);
+
+/* dir.c */
+extern const struct file_operations beamfs_dir_operations;
+
+/* namei.c */
+extern const struct inode_operations beamfs_dir_inode_operations;
+extern const struct inode_operations beamfs_symlink_inode_operations;
+
+/* Directory block resolver (defined in namei.c). */
+int beamfs_dir_get_block(struct inode *dir, unsigned int block_idx,
+			 bool alloc, u64 *out_block);
+
+/* file.c */
+extern const struct file_operations beamfs_file_operations;
+/* S2.2: fiemap support shared by both schemes (file.c). */
+int beamfs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
+		  u64 start, u64 len);
+extern const struct inode_operations beamfs_file_inode_operations;
+extern const struct address_space_operations beamfs_aops;
+
+/* file_inline.c -- getattr, setattr and fiemap for inline-path regular files */
+extern const struct inode_operations beamfs_inline_inode_operations;
+
+/* edac.c */
+int beamfs_rs_init_tables(void);
+void beamfs_rs_exit_tables(void);
+__u32 beamfs_crc32(const void *buf, size_t len);
+
+/*
+ * beamfs_inode_crc -- checksum over everything the inode's parity
+ * protects, which is the head and the pointers but not i_crc32 itself.
+ *
+ * Staged into a contiguous buffer: the covered bytes are not contiguous
+ * on disk, and computing over them in two calls would need a seeded
+ * primitive that beamfs_crc32 does not offer. 168 bytes on the stack,
+ * once per inode read or write.
+ */
+static inline __u32 beamfs_inode_crc(const struct beamfs_inode *raw)
+{
+	__u8 staging[BEAMFS_INODE_CRC_BYTES];
+
+	memcpy(staging, raw, BEAMFS_INODE_CRC_HEAD_LEN);
+	memcpy(staging + BEAMFS_INODE_CRC_HEAD_LEN,
+	       (const __u8 *)raw + BEAMFS_INODE_CRC_TAIL_OFF,
+	       BEAMFS_INODE_CRC_TAIL_LEN);
+	return beamfs_crc32(staging, sizeof(staging));
+}
+
+/*
+ * beamfs_data_selfid -- 64-bit identity digest of (ino, iblock), stored in
+ * the block descriptor when DATA_SELFID is active. Two independent CRC32s,
+ * one per dimension, concatenated: reuses the single hashing primitive
+ * already backing i_crc32, s_crc32 and DATA_CSUM rather than introducing a
+ * second algorithm for an auditor to review. Collision probability 2^-64.
+ */
+static inline __u64 beamfs_data_selfid(__u64 ino, __u64 iblock)
+{
+	__le64 a = cpu_to_le64(ino);
+	__le64 b = cpu_to_le64(iblock);
+	__u32  lo = beamfs_crc32(&a, sizeof(a));
+	__u32  hi = beamfs_crc32(&b, sizeof(b));
+
+	return ((__u64)hi << 32) | lo;
+}
+
+__u32 beamfs_crc32_sb(const struct beamfs_super_block *fsb);
+int beamfs_rs_encode(u8 *data, size_t len, u8 *parity);
+
+/*
+ * beamfs_rs_decode -- decode and correct a shortened RS(255,239)
+ *                    codeword in place, optionally exposing the
+ *                    list of corrected byte positions for entropy.
+ *
+ * @data:         data bytes, corrected in place on success
+ * @len:          number of data bytes (must match the encode call)
+ * @parity:       parity bytes (BEAMFS_RS_PARITY)
+ * @positions:    optional output, BEAMFS_RS_PARITY/2 = 8 entries; on a
+ *                positive return holds the corrected DATA byte
+ *                positions, in [0, len). Corrections that fell in the
+ *                parity are not listed. May be NULL if the caller does
+ *                not need the position list (no entropy logging).
+ * @max_positions: capacity of @positions in entries; ignored if NULL.
+ *
+ * Returns:
+ *   < 0  uncorrectable (-EBADMSG) or invalid input (-EINVAL)
+ *   = 0  no errors detected
+ *   > 0  number of symbol errors corrected in place, data and parity
+ *
+ * The return value is therefore not the number of entries in
+ * @positions when a parity symbol was among those corrected; the
+ * entropy estimator must only be given the data positions.
+ */
+int beamfs_rs_decode(u8 *data, size_t len, u8 *parity,
+		     u8 *positions, unsigned int max_positions,
+		     const char *who);
+
+/*
+ * Shannon entropy estimator for the RS journal.
+ *
+ * @positions:       byte positions corrected (output of beamfs_rs_decode)
+ * @n_positions:     number of valid entries in @positions, in [1, 8]
+ * @code_len_bytes:  data length of the codeword, in [n_positions, 239]
+ *
+ * Returns H in Q16.16, range [0, 3*65536). Deterministic, no FPU,
+ * no runtime division except for bin index computation. Backed by a
+ * compile-time LUT, defined in edac.c.
+ *
+ * Pre: positions != NULL, 1 <= n_positions <= BEAMFS_RS_PARITY/2
+ *      code_len_bytes >= n_positions
+ */
+__u32 beamfs_rs_compute_entropy_q16_16(const u8 *positions,
+				       unsigned int n_positions,
+				      size_t code_len_bytes);
+
+/*
+ * How many bytes of a file one block holds.
+ *
+ * 3824 in the alternating layout, where the whole coded area is user
+ * data. 3808 in a capsule, where the last sixteen of it are the
+ * descriptor -- the csum and the selfid, moved inside the codewords so
+ * a burst cannot condemn a block whose data is intact.
+ *
+ * Every place that turns a file offset into a block number goes
+ * through this. Writing the constant instead reads a file back shifted
+ * by sixteen bytes on a capsule volume, silently: measured on a live
+ * volume as eight megabytes in and a different md5sum out, with every
+ * check green.
+ */
+static inline u32 beamfs_block_payload(struct super_block *sb)
+{
+	if (BEAMFS_SB(sb)->s_feat_incompat &
+	    BEAMFS_FEATURE_INCOMPAT_RS_INTERLEAVE)
+		return BEAMFS_CAPSULE_DATA_BYTES;
+
+	return BEAMFS_DATA_INLINE_BYTES;
+}
+
+/*
+ * Lay a data block's payload down, stamp its descriptor and seal it,
+ * in that order and in the volume's layout. Defined in file_inline.c.
+ *
+ * Shared because a symlink's target is a data block written from
+ * namei.c, and it followed the alternating layout on a capsule volume
+ * until generic/360 read back an empty path.
+ */
+void beamfs_lay_data_payload(struct super_block *sb, u8 *block,
+			     const u8 *payload);
+int  beamfs_seal_data_block(struct super_block *sb, u8 *block);
+void beamfs_stamp_data_block(struct super_block *sb, u8 *block,
+			     const u8 *payload, u64 ino, u64 iblock);
+
+int beamfs_rs_encode_block(u8 *block);
+int beamfs_rs_decode_block(u8 *block, int *results, const char *who);
+void beamfs_block_read(const u8 *block, size_t off, size_t len, u8 *out);
+void beamfs_block_write(u8 *block, size_t off, size_t len, const u8 *in);
+int beamfs_rs_encode_woven(u8 *data_buf, u8 *parity_buf,
+			   size_t parity_stride, size_t data_len,
+			   unsigned int n_subblocks);
+int beamfs_rs_decode_woven(u8 *data_buf, u8 *parity_buf,
+			   size_t parity_stride, size_t data_len,
+			   unsigned int n_subblocks, int *results,
+			   const char *who);
+/*
+ * Encode/decode N RS(255,239) shortened subblocks across a region.
+ * data and parity may live in the same buffer (alternating layout,
+ * like the bitmap) or in two separate buffers (contiguous-parity
+ * layout, like the superblock). The strides decouple the two cases.
+ *
+ * The decode helper additionally exposes per-subblock corrected
+ * position lists for entropy logging. Each subblock writes up to
+ * positions_stride entries into positions_buf at offset
+ * i * positions_stride; results[i] holds beamfs_rs_decode's return
+ * for that subblock, which also counts corrections in the parity
+ * that the list leaves out.
+ *
+ * positions_buf may be NULL (and positions_stride == 0) if the caller
+ * does not need entropy logging for any of the subblocks.
+ */
+int beamfs_rs_encode_region(u8 *data_buf, size_t data_stride,
+			    u8 *parity_buf, size_t parity_stride,
+			   size_t data_len, unsigned int n_subblocks);
+int beamfs_rs_decode_region(u8 *data_buf, size_t data_stride,
+			    u8 *parity_buf, size_t parity_stride,
+			   size_t data_len, unsigned int n_subblocks,
+			   int *results,
+			   u8 *positions_buf,
+			   unsigned int positions_stride,
+			    const char *who);
+
+/* alloc.c */
+int  beamfs_setup_bitmap(struct super_block *sb);
+void beamfs_bitmap_encode_pending(struct super_block *sb);
+/* super.c */
+void beamfs_super_encode_pending(struct beamfs_sb_info *sbi);
+void beamfs_dirty_super_now(struct beamfs_sb_info *sbi);
+/* alloc.c */
+int  beamfs_write_bitmap_block(struct super_block *sb,
+			       unsigned long bit_global,
+			       struct inode *owner);
+void beamfs_destroy_bitmap(struct super_block *sb);
+u64  beamfs_alloc_block(struct super_block *sb, struct inode *owner);
+void beamfs_rsv_init(struct beamfs_inode_info *fi);
+void beamfs_rsv_discard(struct inode *inode);
+void beamfs_free_block(struct super_block *sb, u64 block, struct inode *owner);
+bool beamfs_block_is_allocated(struct super_block *sb, u64 block);
+/* super.c */
+/*
+ * beamfs_free_ind_range -- free part or all of an indirection subtree.
+ *
+ * @depth: 1 = pointers are data blocks, 2 = single-indirect blocks,
+ *         3 = double-indirect blocks.
+ * @base:  logical index of the first data block the subtree covers.
+ * @first: logical index from which to free; everything below survives.
+ *
+ * Returns true when the subtree ended up empty and @blk was freed with
+ * it, false when something survived and @blk was kept.
+ */
+bool beamfs_free_ind_range(struct super_block *sb, u64 blk,
+			   unsigned int depth, u64 base, u64 first,
+			   struct inode *inode);
+
+/* indparity.c */
+void beamfs_ind_parity_update(struct super_block *sb, struct buffer_head *bh,
+			      struct inode *inode);
+int  beamfs_ind_parity_verify(struct super_block *sb, struct buffer_head *bh);
+int  beamfs_ind_parity_verify_medium(struct super_block *sb, struct buffer_head *bh);
+/*
+ * An indirect block whose copy in memory has been checked against its
+ * parity, or written by us with the parity filed: ext4's
+ * buffer_verified. Set once per life of the buffer, so a pointer
+ * lookup does not decode sixteen codewords of the parity region each
+ * time -- 19 % of the CPU of generic/074 (2026-09-23), because the
+ * decode cache was keyed on a counter every parity update bumped.
+ */
+#define BH_BeamfsVerified BH_PrivateStart
+BUFFER_FNS(BeamfsVerified, beamfs_verified)
+void beamfs_ind_parity_touched(struct beamfs_sb_info *sbi);
+void beamfs_ind_parity_cache_free(void);
+
+#ifdef CONFIG_BEAMFS_DEBUG_TREE
+void beamfs_tc_init(struct beamfs_sb_info *sbi);
+void beamfs_tc_exit(struct beamfs_sb_info *sbi);
+void beamfs_tc_store(struct super_block *sb, unsigned long ino, u64 parent,
+		     u32 slot, u64 old, u64 child, const char *who);
+void beamfs_tc_clear(struct super_block *sb, u64 parent, u32 slot);
+void beamfs_tc_zeroed(struct super_block *sb, unsigned long ino, u64 parent,
+		      const char *who);
+void beamfs_tc_forget_child(struct super_block *sb, u64 child);
+void beamfs_tc_forget_parent(struct super_block *sb, u64 parent);
+#else
+static inline void beamfs_tc_init(struct beamfs_sb_info *sbi) { }
+static inline void beamfs_tc_exit(struct beamfs_sb_info *sbi) { }
+static inline void beamfs_tc_store(struct super_block *sb, unsigned long ino,
+				   u64 parent, u32 slot, u64 old, u64 child,
+				   const char *who) { }
+static inline void beamfs_tc_clear(struct super_block *sb, u64 parent,
+				   u32 slot) { }
+static inline void beamfs_tc_zeroed(struct super_block *sb, unsigned long ino,
+				    u64 parent, const char *who) { }
+static inline void beamfs_tc_forget_child(struct super_block *sb, u64 child) { }
+static inline void beamfs_tc_forget_parent(struct super_block *sb, u64 parent) { }
+#endif
+
+/* Scratch pages, from the mount's reserve. Never NULL under GFP_NOFS. */
+void *beamfs_scratch_get(struct super_block *sb);
+void  beamfs_scratch_put(struct super_block *sb, void *p);
+
+/*
+ * Record that the volume has failed and take it read-only.
+ *
+ * Safe to call more than once and from any context; the first caller
+ * logs, the rest are ignored.
+ */
+void beamfs_fail(struct super_block *sb, const char *where, int err);
+
+/*
+ * sb_bread, and mark the volume failed when it comes back empty.
+ *
+ * Forty-seven sites read blocks and each handles its own NULL with an
+ * EIO. That is right locally and blind globally: nothing recorded that
+ * the device had stopped answering, so the next write went ahead and
+ * dirtied a buffer nobody could read -- the WARN_ON_ONCE inside
+ * mark_buffer_dirty, which generic/338 provokes with dm-error.
+ *
+ * One wrapper, so a read that fails is a volume that has failed. What a
+ * failed volume refuses is listed in beamfs_fail.
+ */
+static inline struct buffer_head *beamfs_bread(struct super_block *sb,
+					       sector_t block,
+					       const char *where)
+{
+	struct buffer_head *bh = sb_bread(sb, block);
+
+	if (!bh)
+		beamfs_fail(sb, where, -EIO);
+	return bh;
+}
+
+/* Has the volume already failed? Writes must not start if it has. */
+static inline bool beamfs_failed(struct super_block *sb)
+{
+	struct beamfs_sb_info *sbi = BEAMFS_SB(sb);
+
+	return sbi && READ_ONCE(sbi->s_failed);
+}
+
+/* dirent.c -- variable-length directory entries, and parity over them */
+u32  beamfs_dirent_place(u32 off, u16 len);
+u32  beamfs_dirent_next(const struct beamfs_dir_entry *de, u32 off);
+bool beamfs_dirent_valid(const struct beamfs_dir_entry *de, u32 off);
+void beamfs_dirent_encode(struct buffer_head *bh);
+int  beamfs_dirent_decode(struct super_block *sb, struct buffer_head *bh,
+			  u8 *dst);
+/* file_inline.c */
+int  beamfs_inline_decode_symlink(struct super_block *sb,
+				  struct buffer_head *bh, u64 phys,
+				  struct inode *inode, u8 *dst, u32 len);
+
+/* alert.c -- uevent on threshold crossing */
+enum {
+	BEAMFS_ALERT_UNCORRECTABLE = 0,
+	BEAMFS_ALERT_MARGIN,
+	BEAMFS_ALERT_RATE,
+	BEAMFS_ALERT_CLASSES
+};
+
+void beamfs_alert_uncorrectable(struct super_block *sb, u64 phys);
+void beamfs_alert_margin(struct super_block *sb, u64 phys, unsigned int used);
+void beamfs_alert_check_rate(struct super_block *sb);
+
+/* clock.c -- anchoring the journal's monotonic stamps to a date */
+void beamfs_clock_anchor(struct super_block *sb);
+u64  beamfs_clock_to_real(struct beamfs_sb_info *sbi, u64 mono);
+
+/* budget.c -- per-block correction margin */
+void beamfs_budget_record(struct super_block *sb, u64 phys,
+			  unsigned int symbols);
+u8   beamfs_budget_read(struct super_block *sb, u64 phys);
+void beamfs_budget_histogram(struct super_block *sb, u64 *hist);
+u64  beamfs_budget_next_worn(struct super_block *sb, u64 from, u8 threshold);
+
+/* rsbench.c -- codec cost, no block layer underneath */
+void beamfs_debugfs_init(void);
+void beamfs_debugfs_exit(void);
+
+/* alloc.c -- inode numbers */
+u64  beamfs_alloc_inode_num(struct super_block *sb);
+void beamfs_free_inode_num(struct super_block *sb, u64 ino);
+
+/* dir.c */
+struct dentry *beamfs_lookup(struct inode *dir, struct dentry *dentry,
+			     unsigned int flags);
+
+/* namei.c */
+int beamfs_write_inode(struct inode *inode, struct writeback_control *wbc);
+int beamfs_write_inode_raw(struct inode *inode);
+
+#endif /* _BEAMFS_H */
diff --git a/fs/beamfs/beamfs_trace.h b/fs/beamfs/beamfs_trace.h
new file mode 100644
index 000000000000..618313a066c1
--- /dev/null
+++ b/fs/beamfs/beamfs_trace.h
@@ -0,0 +1,341 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM beamfs
+
+#if !defined(_BEAMFS_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _BEAMFS_TRACE_H
+
+#include <linux/tracepoint.h>
+#include <linux/kdev_t.h>
+
+/*
+ * One event per store into the block tree, and one per read of a slot.
+ * Filterable in the usual way -- echo 'ino == 60' > filter -- so a run
+ * can be narrowed to the inode that leaked without rebuilding anything.
+ */
+TRACE_EVENT(beamfs_slot_store,
+	TP_PROTO(dev_t dev, unsigned long ino, u64 parent, u64 slot, u64 old, u64 new,
+		 unsigned int level),
+	TP_ARGS(dev, ino, parent, slot, old, new, level),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u64, parent)
+		__field(u64, slot)
+		__field(u64, old)
+		__field(u64, new)
+		__field(unsigned int, level)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__entry->parent = parent;
+		__entry->slot = slot;
+		__entry->old = old;
+		__entry->new = new;
+		__entry->level = level;
+	),
+	TP_printk("dev=%u:%u ino=%lu parent=%llu slot=%llu %llu->%llu lvl=%u",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->parent, __entry->slot,
+		  __entry->old, __entry->new, __entry->level)
+);
+
+/*
+ * An indirect block read on the allocation path.
+ *
+ * generic/464 has two writers install a pointer into the same slot of
+ * the same indirect block, twenty-five seconds apart, and the second
+ * one reads it as zero: xfs_io writes 23967 into slot 5 of block 36508
+ * at t=2158.33, and a writeback kworker writes 105019 into the same
+ * slot at t=2183.22, with the trace showing 0-> both times. The block
+ * held the first pointer; the second writer did not see it.
+ *
+ * @uptodate says whether the buffer came back already valid -- a
+ * buffer that had to be read from the medium is one the cache had
+ * dropped, and what comes back is whatever was last written out,
+ * which need not be what the last writer put in memory.
+ *
+ * @slotval is what sits in the slot about to be written, so a read
+ * that returns zero where a pointer was installed shows up here rather
+ * than being inferred from two slot_store events.
+ */
+TRACE_EVENT(beamfs_ind_read,
+	TP_PROTO(dev_t dev, unsigned long ino, u64 blk, unsigned int slot, u64 slotval,
+		 int uptodate, int fresh),
+	TP_ARGS(dev, ino, blk, slot, slotval, uptodate, fresh),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u64, blk)
+		__field(unsigned int, slot)
+		__field(u64, slotval)
+		__field(int, uptodate)
+		__field(int, fresh)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__entry->blk = blk;
+		__entry->slot = slot;
+		__entry->slotval = slotval;
+		__entry->uptodate = uptodate;
+		__entry->fresh = fresh;
+	),
+	TP_printk("dev=%u:%u ino=%lu ind=%llu slot=%u val=%llu uptodate=%d fresh=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->blk, __entry->slot,
+		  __entry->slotval, __entry->uptodate, __entry->fresh)
+);
+
+/*
+ * One parity slot written.
+ *
+ * A region block holds fourteen slots under RS, and several inodes'
+ * indirect blocks land in the same one. generic/464 leaves slots
+ * describing a block that is not the block on the medium -- subblocks
+ * 0..6 with data and no parity, 7..15 with parity and no data, two
+ * states in one slot -- and the question is which writer left what,
+ * and in which order. @nz is how many of the indirect block's pointers
+ * were set when the parity was taken, so a slot can be matched to the
+ * block it was computed from.
+ */
+TRACE_EVENT(beamfs_parity_slot,
+	TP_PROTO(dev_t dev, unsigned long ino, u64 phys, u64 region, unsigned int slot,
+		 unsigned int nz),
+	TP_ARGS(dev, ino, phys, region, slot, nz),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u64, phys)
+		__field(u64, region)
+		__field(unsigned int, slot)
+		__field(unsigned int, nz)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__entry->phys = phys;
+		__entry->region = region;
+		__entry->slot = slot;
+		__entry->nz = nz;
+	),
+	TP_printk("dev=%u:%u ino=%lu ind=%llu region=%llu slot=%u ptrs=%u",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->phys, __entry->region,
+		  __entry->slot, __entry->nz)
+);
+TRACE_EVENT(beamfs_block_alloc,
+	TP_PROTO(dev_t dev, unsigned long ino, u64 blk, unsigned int level),
+	TP_ARGS(dev, ino, blk, level),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u64, blk)
+		__field(unsigned int, level)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__entry->blk = blk;
+		__entry->level = level;
+	),
+	TP_printk("dev=%u:%u ino=%lu blk=%llu lvl=%u",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->blk, __entry->level)
+);
+
+TRACE_EVENT(beamfs_block_free,
+	TP_PROTO(dev_t dev, unsigned long ino, u64 blk, unsigned long site),
+	TP_ARGS(dev, ino, blk, site),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(u64, blk)
+		__field(unsigned long, site)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__entry->blk = blk;
+		__entry->site = site;
+	),
+	/*
+	 * site names the caller, by its return address.
+	 *
+	 * It was the constant 0 in every one of 56215 frees, so a capture
+	 * could say a block had been freed and never which of the
+	 * twenty-nine call sites did it. That matters here more than
+	 * anywhere: freeing a block whose buffer is still on an inode's
+	 * metadata list is how a later owner's pointers are lost, the
+	 * fix is right for some callers and wrong for others -- applied
+	 * to all of them it turned 58 leaked blocks into 345 -- and
+	 * telling them apart needs the caller's name.
+	 */
+	TP_printk("dev=%u:%u ino=%lu blk=%llu site=%pS",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->blk, (void *)__entry->site)
+);
+
+/*
+ * An inode's metadata buffers, written or dropped.
+ *
+ * An indirect block is dirtied and put on the inode's metadata list,
+ * and something has to take it from there to the medium. Two things
+ * can: mmb_sync, from write_inode and from sync_inode_metadata, which
+ * writes them; and mmb_invalidate, from evict_inode, which does not --
+ * it empties the list and the buffers go with whatever they held.
+ *
+ * generic/464 leaves one indirect block per run filled with 0xcd, the
+ * kernel's poison for memory nobody wrote: block 41641 of inode 24 in
+ * one capture, 35433 of inode 65 in the next, each named by its inode
+ * on disk and each never written. Something is taking these buffers
+ * off the list without writing them, and @how says which.
+ *
+ * @n is how many buffers the list held when the call started, so a
+ * sync that finds nothing and an evict that drops four are told apart.
+ */
+TRACE_EVENT(beamfs_mmb,
+	TP_PROTO(dev_t dev, unsigned long ino, const char *how, int n, int err),
+	TP_ARGS(dev, ino, how, n, err),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__string(how, how)
+		__field(int, n)
+		__field(int, err)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__assign_str(how);
+		__entry->n = n;
+		__entry->err = err;
+	),
+	TP_printk("dev=%u:%u ino=%lu %s buffers=%d err=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __get_str(how), __entry->n, __entry->err)
+);
+/*
+ * What a flush of the bitmap found, and what it managed to write.
+ *
+ * The bitmap's blocks are the one place a freed block is recorded, and
+ * 47% of the updates to them are attached to no inode at all -- the
+ * free path passes no owner -- so nothing carries them to the medium
+ * except the flusher, sync_fs and put_super. Those two are static and
+ * inlined, so a kprobe cannot count them and the question "did the
+ * free reach the disk" had no answer from outside.
+ *
+ * @dirty is how many bitmap blocks were dirty when the flush started,
+ * @written how many reached the medium, @err the first failure.
+ */
+TRACE_EVENT(beamfs_bitmap_flush,
+	TP_PROTO(dev_t dev, const char *where, unsigned int dirty,
+		 unsigned int written, int err),
+	TP_ARGS(dev, where, dirty, written, err),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__string(where, where)
+		__field(unsigned int, dirty)
+		__field(unsigned int, written)
+		__field(int, err)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__assign_str(where);
+		__entry->dirty = dirty;
+		__entry->written = written;
+		__entry->err = err;
+	),
+	TP_printk("dev=%u:%u %s dirty=%u written=%u err=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __get_str(where), __entry->dirty, __entry->written,
+		  __entry->err)
+);
+
+TRACE_EVENT(beamfs_write_inode,
+	TP_PROTO(dev_t dev, unsigned long ino, int sync, u64 indirect, int err),
+	TP_ARGS(dev, ino, sync, indirect, err),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(int, sync)
+		__field(u64, indirect)
+		__field(int, err)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__entry->sync = sync;
+		__entry->indirect = indirect;
+		__entry->err = err;
+	),
+	TP_printk("dev=%u:%u ino=%lu sync=%d i_indirect=%llu err=%d",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->sync, __entry->indirect, __entry->err)
+);
+
+TRACE_EVENT(beamfs_inode_dirty,
+	TP_PROTO(dev_t dev, unsigned long ino, unsigned long state),
+	TP_ARGS(dev, ino, state),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(unsigned long, ino)
+		__field(unsigned long, state)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->ino = ino;
+		__entry->state = state;
+	),
+	TP_printk("dev=%u:%u ino=%lu state=0x%lx",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->ino, __entry->state)
+);
+
+/*
+ * One event per RS decode that found something, on every path: data
+ * read, RMW transit, directory, bitmap, indirect parity, scrub and
+ * superblock. It is raised in beamfs_log_rs_event_flagged, the one
+ * place every decoder reports to, so a capture misses none of them and
+ * none is rate-limited the way the dmesg lines are. The journal in the
+ * superblock keeps the last sixty-four; this keeps as many as the ring
+ * buffer is sized for.
+ *
+ * @blk is the physical block, the number emufi's flip_log gives as
+ * sector / 8, so a flip and its verdict join on it directly. @sub is
+ * the subblock, -1 when the event has none (a superblock or inode
+ * correction). @symbols is how many the decoder corrected, 0 when it
+ * could not; @flags is re_flags as journalled, so uncorrectable
+ * (BIT(1)), RMW-neutralised (BIT(2)) and inode (BIT(3)) read off it.
+ */
+TRACE_EVENT(beamfs_rs_event,
+	TP_PROTO(dev_t dev, u64 blk, int sub, unsigned int symbols, u32 flags),
+	TP_ARGS(dev, blk, sub, symbols, flags),
+	TP_STRUCT__entry(
+		__field(dev_t, dev)
+		__field(u64, blk)
+		__field(int, sub)
+		__field(unsigned int, symbols)
+		__field(u32, flags)
+	),
+	TP_fast_assign(
+		__entry->dev = dev;
+		__entry->blk = blk;
+		__entry->sub = sub;
+		__entry->symbols = symbols;
+		__entry->flags = flags;
+	),
+	TP_printk("dev=%u:%u blk=%llu sub=%d symbols=%u flags=0x%x",
+		  MAJOR(__entry->dev), MINOR(__entry->dev),
+		  __entry->blk, __entry->sub, __entry->symbols,
+		  __entry->flags)
+);
+
+#endif /* _BEAMFS_TRACE_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE beamfs_trace
+#include <trace/define_trace.h>
-- 
2.55.0


  parent reply	other threads:[~2026-10-19  4:17 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19  4:17 [RFC PATCH 00/18] beamfs: Reed-Solomon protected filesystem Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 01/18] beamfs: add on-disk format definitions Aurelien DESBRIERES
2026-10-19  4:17 ` Aurelien DESBRIERES [this message]
2026-10-19  4:17 ` [RFC PATCH 03/18] beamfs: add CRC32 and Reed-Solomon layer Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 04/18] beamfs: add error budget, alerts and clock Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 05/18] beamfs: add indirect parity, tree checker Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 06/18] beamfs: add block and inode allocator Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 07/18] beamfs: add superblock operations Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 08/18] beamfs: add inode and directory operations Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 09/18] beamfs: add namespace operations Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 10/18] beamfs: add iomap file operations Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 11/18] beamfs: inline data path 1/4, block map Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 12/18] beamfs: inline data path 2/4, allocation Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 13/18] beamfs: inline data path 3/4, read side Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 14/18] beamfs: inline data path 4/4, write side Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 15/18] beamfs: add scrubber and RS benchmark Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 16/18] Documentation: filesystems: add beamfs Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 17/18] beamfs: add Kconfig and Makefile Aurelien DESBRIERES
2026-10-19  4:17 ` [RFC PATCH 18/18] MAINTAINERS: add beamfs Aurelien DESBRIERES

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261019041728.327785-3-aurelien@hackers.camp \
    --to=aurelien@hackers.camp \
    --cc=aurelien.desbrieres@gmail.com \
    --cc=brauner@kernel.org \
    --cc=corbet@lwn.net \
    --cc=jack@suse.cz \
    --cc=linux-api@vger.kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=rdunlap@infradead.org \
    --cc=skhan@linuxfoundation.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®