[PATCH 03/15] ceph: add BLOG page-fragment allocator

From: Alex Markuze

Date: Wed Jun 17 2026 - 11:00:52 EST


Add blog_pagefrag.c: lockless page-fragment buffer allocator for
per-task BLOG contexts.

Signed-off-by: Alex Markuze <amarkuze@xxxxxxxxxx>
---
fs/ceph/blog_pagefrag.c | 95 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 95 insertions(+)
create mode 100644 fs/ceph/blog_pagefrag.c

diff --git a/fs/ceph/blog_pagefrag.c b/fs/ceph/blog_pagefrag.c
new file mode 100644
index 000000000000..bae88ea82aec
--- /dev/null
+++ b/fs/ceph/blog_pagefrag.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Binary Logging Page Fragment Management
+ */
+
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/ceph/blog_pagefrag.h>
+
+/**
+ * blog_pagefrag_reserve - Reserve space in the pagefrag buffer
+ * @pf: pagefrag allocator
+ * @n: number of bytes to reserve
+ *
+ * Checks if there is sufficient space and returns the current head offset
+ * WITHOUT advancing the head pointer. This allows the caller to write data
+ * before making it visible via blog_pagefrag_publish().
+ *
+ * This is lockless - only one writer per pagefrag (per-task context).
+ *
+ * Return: offset to reserved memory, or negative error if not enough space
+ */
+int blog_pagefrag_reserve(struct blog_pagefrag *pf, unsigned int n)
+{
+ if (n > pf->capacity - pf->head)
+ return -ENOMEM; /* No space left */
+ return pf->head; /* Return offset without advancing */
+}
+
+/**
+ * blog_pagefrag_publish - Publish reserved space by advancing head pointer
+ * @pf: pagefrag allocator
+ * @publish_head: new head value (offset + bytes_written)
+ *
+ * Advances the head pointer to make previously written data visible to
+ * readers. Uses store-release so that any reader that observes the new
+ * head value (via load-acquire or under pf->lock) is guaranteed to see
+ * the preceding entry writes.
+ *
+ * Single-writer per pagefrag (per-task context).
+ */
+void blog_pagefrag_publish(struct blog_pagefrag *pf, u64 publish_head)
+{
+ pf->alloc_count++;
+ pf->active_elements++;
+ /* Release-store pairs with the acquire in readers (pf->lock). */
+ smp_store_release(&pf->head, (unsigned int)publish_head);
+}
+
+/**
+ * blog_pagefrag_get_ptr - Get buffer pointer from pagefrag reserve result
+ * @pf: pagefrag allocator
+ * @val: return value from blog_pagefrag_reserve
+ *
+ * Return: pointer to reserved buffer region
+ */
+void *blog_pagefrag_get_ptr(struct blog_pagefrag *pf, u64 val)
+{
+ char *base = (char *)pf->buffer;
+ void *rc = base + val;
+
+ if (unlikely(rc < pf->buffer ||
+ rc >= (void *)(base + pf->capacity))) {
+ pr_err("%s: invalid pointer %llx\n", __func__,
+ (unsigned long long)(uintptr_t)rc);
+ WARN_ON_ONCE(1);
+ return NULL;
+ }
+ return rc;
+}
+
+/**
+ * blog_pagefrag_reset - Reset the pagefrag allocator
+ * @pf: pagefrag allocator to reset
+ *
+ * Resets the head pointer and counters to the beginning of the buffer,
+ * discarding all stored entries.
+ *
+ * Context: Callers must ensure that no writers are active on this
+ * pagefrag before calling reset. The pagefrag is single-writer and
+ * writers do not take pf->lock, so resetting while a writer is mid-
+ * reservation corrupts state. In practice this means the owning task
+ * must have finished its current blog_log() / blog_log_commit() pair.
+ */
+void blog_pagefrag_reset(struct blog_pagefrag *pf)
+{
+ spin_lock(&pf->lock);
+ pf->head = 0;
+ pf->active_elements = 0;
+ pf->alloc_count = 0;
+ pf->last_entry = NULL;
+ spin_unlock(&pf->lock);
+}
--
2.34.1