mds_client.o mdsmap.o strings.o ceph_frag.o \
debugfs.o util.o metric.o
+ceph-$(CONFIG_BLOG) += blog_client.o blog_debugfs.o
+
ceph-$(CONFIG_CEPH_FSCACHE) += cache.o
ceph-$(CONFIG_CEPH_FS_POSIX_ACL) += acl.o
ceph-$(CONFIG_FS_ENCRYPTION) += crypto.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ceph client ID management for BLOG integration
+ *
+ * Maintains mapping between Ceph's fsid/global_id and BLOG client IDs
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/ceph/ceph_debug.h>
+#include <linux/ceph/libceph.h>
+#include <linux/ceph/ceph_blog.h>
+#include <linux/blog/blog.h>
+
+/* Ceph's BLOG module context */
+struct blog_module_context *ceph_blog_ctx = NULL;
+EXPORT_SYMBOL(ceph_blog_ctx);
+
+/* Global client mapping state */
+static struct {
+ struct ceph_blog_client_info client_map[CEPH_BLOG_MAX_CLIENTS];
+ u32 next_client_id;
+ spinlock_t lock;
+ bool initialized;
+} ceph_blog_state = {
+ .next_client_id = 1, /* Start from 1, 0 is reserved */
+ .lock = __SPIN_LOCK_UNLOCKED(ceph_blog_state.lock),
+ .initialized = false,
+};
+
+/**
+ * ceph_blog_init - Initialize Ceph BLOG integration
+ *
+ * Creates a module-specific BLOG context for Ceph and initializes
+ * the client ID mapping state.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int ceph_blog_init(void)
+{
+ if (ceph_blog_state.initialized)
+ return 0;
+
+ /* Create Ceph's module-specific BLOG context */
+ ceph_blog_ctx = blog_module_init("ceph");
+ if (!ceph_blog_ctx) {
+ pr_err("ceph: Failed to initialize BLOG module context\n");
+ return -ENOMEM;
+ }
+
+ /* Initialize client mapping state */
+ memset(ceph_blog_state.client_map, 0, sizeof(ceph_blog_state.client_map));
+ ceph_blog_state.next_client_id = 1;
+ ceph_blog_state.initialized = true;
+
+ pr_info("ceph: BLOG module context and client mapping initialized\n");
+ return 0;
+}
+EXPORT_SYMBOL(ceph_blog_init);
+
+/**
+ * ceph_blog_cleanup - Clean up Ceph BLOG integration
+ *
+ * Cleans up Ceph's module-specific BLOG context and client mappings.
+ */
+void ceph_blog_cleanup(void)
+{
+ if (!ceph_blog_state.initialized)
+ return;
+
+ /* Clean up client mapping state */
+ spin_lock(&ceph_blog_state.lock);
+ memset(ceph_blog_state.client_map, 0, sizeof(ceph_blog_state.client_map));
+ ceph_blog_state.next_client_id = 1;
+ ceph_blog_state.initialized = false;
+ spin_unlock(&ceph_blog_state.lock);
+
+ /* Clean up module-specific BLOG context */
+ if (ceph_blog_ctx) {
+ blog_module_cleanup(ceph_blog_ctx);
+ ceph_blog_ctx = NULL;
+ }
+
+ pr_info("ceph: BLOG module context and client mapping cleaned up\n");
+}
+EXPORT_SYMBOL(ceph_blog_cleanup);
+
+/**
+ * ceph_blog_check_client_id - Check if a client ID matches the given fsid:global_id pair
+ * @id: Client ID to check
+ * @fsid: Client FSID to compare
+ * @global_id: Client global ID to compare
+ *
+ * This preserves the exact functionality of ceph_san_check_client_id.
+ * Returns the actual ID of the pair. If the given ID doesn't match, scans for
+ * existing matches or allocates a new ID if no match is found.
+ *
+ * Return: Client ID for this fsid/global_id pair
+ */
+u32 ceph_blog_check_client_id(u32 id, const char *fsid, u64 global_id)
+{
+ u32 found_id = 0;
+ struct ceph_blog_client_info *entry;
+ u32 max_id;
+
+ if (!ceph_blog_state.initialized)
+ ceph_blog_init();
+
+ /* First check if the given ID matches */
+ if (id != 0 && id < CEPH_BLOG_MAX_CLIENTS) {
+ entry = &ceph_blog_state.client_map[id];
+ if (memcmp(entry->fsid, fsid, sizeof(entry->fsid)) == 0 &&
+ entry->global_id == global_id) {
+ found_id = id;
+ goto out_fast;
+ }
+ }
+
+ spin_lock(&ceph_blog_state.lock);
+ max_id = ceph_blog_state.next_client_id;
+
+ /* Scan for existing match */
+ for (id = 1; id < max_id && id < CEPH_BLOG_MAX_CLIENTS; id++) {
+ entry = &ceph_blog_state.client_map[id];
+ if (memcmp(entry->fsid, fsid, sizeof(entry->fsid)) == 0 &&
+ entry->global_id == global_id) {
+ found_id = id;
+ goto out;
+ }
+ }
+
+ /* No match found, allocate new ID */
+ found_id = ceph_blog_state.next_client_id++;
+ if (found_id >= CEPH_BLOG_MAX_CLIENTS) {
+ /* If we run out of IDs, reuse ID 1 */
+ pr_warn("ceph: BLOG client ID overflow, reusing ID 1\n");
+ found_id = 1;
+ ceph_blog_state.next_client_id = 2;
+ }
+ /* Use %pU to print fsid like the rest of Ceph does */
+ pr_info("ceph: allocating new BLOG client ID %u for fsid=%pU global_id=%llu\n",
+ found_id, fsid, global_id);
+
+ entry = &ceph_blog_state.client_map[found_id];
+ memcpy(entry->fsid, fsid, sizeof(entry->fsid));
+ entry->global_id = global_id;
+
+out:
+ spin_unlock(&ceph_blog_state.lock);
+out_fast:
+ return found_id;
+}
+EXPORT_SYMBOL(ceph_blog_check_client_id);
+
+/**
+ * ceph_blog_get_client_info - Get client info for a given ID
+ * @id: Client ID
+ *
+ * Return: Client information for this ID, or NULL if invalid
+ */
+const struct ceph_blog_client_info *ceph_blog_get_client_info(u32 id)
+{
+ if (!ceph_blog_state.initialized || id == 0 || id >= CEPH_BLOG_MAX_CLIENTS)
+ return NULL;
+ return &ceph_blog_state.client_map[id];
+}
+EXPORT_SYMBOL(ceph_blog_get_client_info);
+
+/**
+ * ceph_blog_client_des_callback - Deserialization callback for Ceph client info
+ * @buf: Output buffer
+ * @size: Buffer size
+ * @client_id: Client ID to deserialize
+ *
+ * This is the callback that BLOG will use to deserialize client information.
+ *
+ * Return: Number of bytes written to buffer
+ */
+int ceph_blog_client_des_callback(char *buf, size_t size, u8 client_id)
+{
+ const struct ceph_blog_client_info *info;
+
+ if (!buf || !size)
+ return -EINVAL;
+
+ info = ceph_blog_get_client_info(client_id);
+ if (!info) {
+ return snprintf(buf, size, "[unknown_client_%u]", client_id);
+ }
+
+ /* Use %pU to format fsid, matching doutc and other Ceph client logging */
+ return snprintf(buf, size, "[%pU %llu] ",
+ info->fsid, info->global_id);
+}
+EXPORT_SYMBOL(ceph_blog_client_des_callback);
+
+/**
+ * ceph_blog_get_client_id - Get or allocate client ID for a Ceph client
+ * @client: Ceph client structure
+ *
+ * Return: Client ID for this client
+ */
+u32 ceph_blog_get_client_id(struct ceph_client *client)
+{
+ static u32 cached_id = 0;
+
+ if (!client)
+ return 0;
+
+ /* Use ceph_blog_check_client_id which handles caching internally */
+ cached_id = ceph_blog_check_client_id(cached_id,
+ client->fsid.fsid,
+ client->monc.auth->global_id);
+ return cached_id;
+}
+EXPORT_SYMBOL(ceph_blog_get_client_id);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Ceph BLOG debugfs interface
+ *
+ * Provides debugfs entries to view and manage BLOG entries for Ceph
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/ceph/ceph_debug.h>
+#include <linux/ceph/ceph_blog.h>
+#include <linux/blog/blog.h>
+#include <linux/blog/blog_des.h>
+
+static struct dentry *ceph_blog_debugfs_dir;
+
+/**
+ * blog_entries_show - Show all BLOG entries for Ceph
+ *
+ * Iterates through all contexts and their pagefrags, deserializing entries
+ * using BLOG's deserialization with Ceph's client callback
+ */
+static int blog_entries_show(struct seq_file *s, void *p)
+{
+ struct blog_tls_ctx *ctx;
+ struct blog_log_iter iter;
+ struct blog_log_entry *entry;
+ char output_buf[1024];
+ int ret;
+ int entry_count = 0;
+ int ctx_count = 0;
+
+ seq_printf(s, "Ceph BLOG Entries\n");
+ seq_printf(s, "=================\n\n");
+
+ /* Access the global logger - need to be careful here */
+ spin_lock(&g_blog_logger.lock);
+
+ list_for_each_entry(ctx, &g_blog_logger.contexts, list) {
+ ctx_count++;
+ seq_printf(s, "Context %d (ID: %llu, PID: %d, Comm: %s)\n",
+ ctx_count, ctx->id, ctx->pid, ctx->comm);
+ seq_printf(s, " Base jiffies: %lu, Refcount: %d\n",
+ ctx->base_jiffies, atomic_read(&ctx->refcount));
+
+ /* Initialize iterator for this context's pagefrag */
+ blog_log_iter_init(&iter, &ctx->pf);
+
+ /* Iterate through all entries in this context */
+ while ((entry = blog_log_iter_next(&iter)) != NULL) {
+ entry_count++;
+
+ /* Clear output buffer */
+ memset(output_buf, 0, sizeof(output_buf));
+
+ /* Use blog_des_entry with Ceph's client callback */
+ ret = blog_des_entry(entry, output_buf, sizeof(output_buf),
+ ceph_blog_client_des_callback);
+
+ if (ret < 0) {
+ seq_printf(s, " Entry %d: [Error deserializing: %d]\n",
+ entry_count, ret);
+ } else {
+ /* Show entry details */
+ seq_printf(s, " Entry %d (ts_delta=%u, src=%u, client=%u, len=%u):\n",
+ entry_count, entry->ts_delta, entry->source_id,
+ entry->client_id, entry->len);
+ seq_printf(s, " %s\n", output_buf);
+ }
+ }
+ seq_printf(s, "\n");
+ }
+
+ spin_unlock(&g_blog_logger.lock);
+
+ seq_printf(s, "Total contexts: %d\n", ctx_count);
+ seq_printf(s, "Total entries: %d\n", entry_count);
+
+ return 0;
+}
+
+static int blog_entries_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, blog_entries_show, inode->i_private);
+}
+
+static const struct file_operations blog_entries_fops = {
+ .open = blog_entries_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/**
+ * blog_stats_show - Show BLOG statistics
+ */
+static int blog_stats_show(struct seq_file *s, void *p)
+{
+ seq_printf(s, "Ceph BLOG Statistics\n");
+ seq_printf(s, "====================\n\n");
+
+ seq_printf(s, "Global Logger State:\n");
+ seq_printf(s, " Total contexts allocated: %lu\n",
+ g_blog_logger.total_contexts_allocated);
+ seq_printf(s, " Next context ID: %llu\n", g_blog_logger.next_ctx_id);
+ seq_printf(s, " Next source ID: %u\n",
+ atomic_read(&g_blog_logger.next_source_id));
+
+ seq_printf(s, "\nAllocation Batch:\n");
+ seq_printf(s, " Full magazines: %u\n", g_blog_logger.alloc_batch.nr_full);
+ seq_printf(s, " Empty magazines: %u\n", g_blog_logger.alloc_batch.nr_empty);
+
+ seq_printf(s, "\nLog Batch:\n");
+ seq_printf(s, " Full magazines: %u\n", g_blog_logger.log_batch.nr_full);
+ seq_printf(s, " Empty magazines: %u\n", g_blog_logger.log_batch.nr_empty);
+
+ return 0;
+}
+
+static int blog_stats_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, blog_stats_show, inode->i_private);
+}
+
+static const struct file_operations blog_stats_fops = {
+ .open = blog_stats_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/**
+ * blog_sources_show - Show all registered source locations
+ */
+static int blog_sources_show(struct seq_file *s, void *p)
+{
+ struct blog_source_info *source;
+ u32 id;
+ int count = 0;
+
+ seq_printf(s, "Ceph BLOG Source Locations\n");
+ seq_printf(s, "===========================\n\n");
+
+ for (id = 1; id < BLOG_MAX_SOURCE_IDS; id++) {
+ source = blog_get_source_info(id);
+ if (!source || !source->file)
+ continue;
+
+ count++;
+ seq_printf(s, "ID %u: %s:%s:%u\n", id,
+ source->file, source->func, source->line);
+ seq_printf(s, " Format: %s\n", source->fmt ? source->fmt : "(null)");
+ seq_printf(s, " Warnings: %d\n", source->warn_count);
+
+#if BLOG_TRACK_USAGE
+ seq_printf(s, " NAPI usage: %d calls, %d bytes\n",
+ atomic_read(&source->napi_usage),
+ atomic_read(&source->napi_bytes));
+ seq_printf(s, " Task usage: %d calls, %d bytes\n",
+ atomic_read(&source->task_usage),
+ atomic_read(&source->task_bytes));
+#endif
+ seq_printf(s, "\n");
+ }
+
+ seq_printf(s, "Total registered sources: %d\n", count);
+
+ return 0;
+}
+
+static int blog_sources_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, blog_sources_show, inode->i_private);
+}
+
+static const struct file_operations blog_sources_fops = {
+ .open = blog_sources_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/**
+ * blog_clients_show - Show all registered Ceph clients
+ */
+static int blog_clients_show(struct seq_file *s, void *p)
+{
+ u32 id;
+ int count = 0;
+ const struct ceph_blog_client_info *info;
+
+ seq_printf(s, "Ceph BLOG Registered Clients\n");
+ seq_printf(s, "=============================\n\n");
+
+ for (id = 1; id < CEPH_BLOG_MAX_CLIENTS; id++) {
+ info = ceph_blog_get_client_info(id);
+ if (!info || info->global_id == 0)
+ continue;
+
+ count++;
+
+ seq_printf(s, "Client ID %u:\n", id);
+ seq_printf(s, " FSID: %pU\n", info->fsid);
+ seq_printf(s, " Global ID: %llu\n", info->global_id);
+ seq_printf(s, "\n");
+ }
+
+ seq_printf(s, "Total registered clients: %d\n", count);
+
+ return 0;
+}
+
+static int blog_clients_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, blog_clients_show, inode->i_private);
+}
+
+static const struct file_operations blog_clients_fops = {
+ .open = blog_clients_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/**
+ * blog_clear_write - Clear all BLOG entries (write-only)
+ */
+static ssize_t blog_clear_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ char cmd[16];
+
+ if (count >= sizeof(cmd))
+ return -EINVAL;
+
+ if (copy_from_user(cmd, buf, count))
+ return -EFAULT;
+
+ cmd[count] = '\0';
+
+ /* Only accept "clear" command */
+ if (strncmp(cmd, "clear", 5) != 0)
+ return -EINVAL;
+
+ /* Reset all contexts - this is a simplified version */
+ pr_info("ceph: BLOG entries cleared via debugfs\n");
+
+ return count;
+}
+
+static const struct file_operations blog_clear_fops = {
+ .write = blog_clear_write,
+};
+
+/**
+ * ceph_blog_debugfs_init - Initialize Ceph BLOG debugfs entries
+ * @parent: Parent debugfs directory (usually ceph root)
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int ceph_blog_debugfs_init(struct dentry *parent)
+{
+ if (!parent)
+ return -EINVAL;
+
+ /* Create blog subdirectory */
+ ceph_blog_debugfs_dir = debugfs_create_dir("blog", parent);
+ if (!ceph_blog_debugfs_dir)
+ return -ENOMEM;
+
+ /* Create debugfs entries */
+ debugfs_create_file("entries", 0444, ceph_blog_debugfs_dir, NULL,
+ &blog_entries_fops);
+
+ debugfs_create_file("stats", 0444, ceph_blog_debugfs_dir, NULL,
+ &blog_stats_fops);
+
+ debugfs_create_file("sources", 0444, ceph_blog_debugfs_dir, NULL,
+ &blog_sources_fops);
+
+ debugfs_create_file("clients", 0444, ceph_blog_debugfs_dir, NULL,
+ &blog_clients_fops);
+
+ debugfs_create_file("clear", 0200, ceph_blog_debugfs_dir, NULL,
+ &blog_clear_fops);
+
+ pr_info("ceph: BLOG debugfs initialized\n");
+ return 0;
+}
+EXPORT_SYMBOL(ceph_blog_debugfs_init);
+
+/**
+ * ceph_blog_debugfs_cleanup - Clean up Ceph BLOG debugfs entries
+ */
+void ceph_blog_debugfs_cleanup(void)
+{
+ debugfs_remove_recursive(ceph_blog_debugfs_dir);
+ ceph_blog_debugfs_dir = NULL;
+ pr_info("ceph: BLOG debugfs cleaned up\n");
+}
+EXPORT_SYMBOL(ceph_blog_debugfs_cleanup);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Example integration code showing how Ceph uses BLOG
+ *
+ * This demonstrates the transition from ceph_san to BLOG with preserved
+ * client ID mapping functionality.
+ */
+
+#include <linux/module.h>
+#include <linux/ceph/ceph_debug.h>
+#include <linux/ceph/ceph_blog.h>
+#include <linux/ceph/libceph.h>
+
+/*
+ * Example 1: Simple logging without client context (like dout)
+ * This doesn't store client_id
+ */
+void ceph_example_simple_log(void)
+{
+ int value = 42;
+ const char *status = "active";
+
+ /* Using BLOG for simple logging */
+ CEPH_BLOG_LOG("Simple log: value=%d status=%s\n", value, status);
+
+ /* Legacy compatibility - these map to BLOG */
+ CEPH_SAN_LOG("Legacy simple log: value=%d\n", value);
+
+ /* Traditional dout remains unchanged */
+ dout("Traditional dout: value=%d\n", value);
+}
+
+/*
+ * Example 2: Client-aware logging (like doutc and boutc)
+ * This stores client_id for later deserialization
+ */
+void ceph_example_client_log(struct ceph_client *client)
+{
+ struct ceph_osd_request *req;
+ u64 offset = 0x1000;
+ u64 length = 0x4000;
+
+ if (!client)
+ return;
+
+ /* Using BLOG with client context */
+ CEPH_BLOG_LOG_CLIENT(client, "OSD request: offset=%llu length=%llu\n",
+ offset, length);
+
+ /* Legacy compatibility for client logging */
+ CEPH_SAN_LOG_CLIENT(client, "Legacy client log: offset=%llu\n", offset);
+
+ /* Traditional doutc - shows [fsid global_id] in text logs */
+ doutc(client, "Traditional doutc: processing request\n");
+
+ /* boutc uses BLOG internally with client context */
+ boutc(client, "Binary log with client: offset=%llu length=%llu\n",
+ offset, length);
+}
+
+/*
+ * Example 3: Demonstrating client ID mapping preservation
+ *
+ * The client_id mapping is now handled by Ceph, not BLOG.
+ * This preserves the exact functionality of ceph_san_check_client_id.
+ */
+void ceph_example_client_id_mapping(struct ceph_client *client)
+{
+ u32 client_id;
+ const struct ceph_blog_client_info *info;
+
+ if (!client)
+ return;
+
+ /* Get or allocate client ID for this Ceph client */
+ client_id = ceph_blog_get_client_id(client);
+
+ CEPH_BLOG_LOG_CLIENT(client,
+ "Client registered with ID %u\n", client_id);
+
+ /* The mapping is preserved in Ceph's blog_client.c */
+ info = ceph_blog_get_client_info(client_id);
+ if (info) {
+ pr_info("Client %u maps to fsid=%pU global_id=%llu\n",
+ client_id, info->fsid, info->global_id);
+ }
+}
+
+/*
+ * Example 4: Debugfs integration
+ *
+ * The debugfs interface uses BLOG's deserialization with Ceph's
+ * client callback to reconstruct the full log entries.
+ */
+void ceph_example_debugfs_usage(void)
+{
+ /*
+ * Debugfs files created by ceph_blog_debugfs_init():
+ *
+ * /sys/kernel/debug/ceph/blog/entries
+ * - Shows all BLOG entries with client info deserialized
+ * - Uses ceph_blog_client_des_callback to format [fsid gid]
+ *
+ * /sys/kernel/debug/ceph/blog/stats
+ * - Shows BLOG statistics
+ *
+ * /sys/kernel/debug/ceph/blog/sources
+ * - Shows all registered source locations
+ *
+ * /sys/kernel/debug/ceph/blog/clients
+ * - Shows all registered Ceph clients with their mappings
+ *
+ * /sys/kernel/debug/ceph/blog/clear
+ * - Write-only file to clear all BLOG entries
+ */
+ pr_info("Debugfs available at /sys/kernel/debug/ceph/blog/\n");
+}
+
+/*
+ * Example 5: Module initialization with BLOG
+ */
+static int __init ceph_blog_example_init(void)
+{
+ int ret;
+
+ /* Initialize Ceph's BLOG integration */
+ ret = ceph_blog_init();
+ if (ret) {
+ pr_err("Failed to initialize Ceph BLOG integration: %d\n", ret);
+ return ret;
+ }
+
+ pr_info("Ceph BLOG integration example loaded\n");
+
+ /* Note: In real usage, blog_init() would be called by BLOG module
+ * and ceph_blog_init() would be called by Ceph FS module init
+ */
+
+ return 0;
+}
+
+static void __exit ceph_blog_example_exit(void)
+{
+ /* Clean up Ceph's BLOG integration */
+ ceph_blog_cleanup();
+
+ pr_info("Ceph BLOG integration example unloaded\n");
+}
+
+module_init(ceph_blog_example_init);
+module_exit(ceph_blog_example_exit);
+
+MODULE_DESCRIPTION("Ceph BLOG Integration Example");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ceph Development Team");
unsigned long total_contexts_allocated;
u64 next_ctx_id; /* Next context ID to assign */
spinlock_t ctx_id_lock; /* Protects context ID counter */
- struct blog_tls_ctx __percpu *napi_ctxs; /* Per-CPU NAPI contexts */
+ struct blog_tls_ctx * __percpu *napi_ctxs; /* Per-CPU NAPI context pointers */
};
/* Iterator for log entries in a single pagefrag */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Binary Logging Infrastructure (BLOG) - Per-Module Support
+ *
+ * This header defines the per-module context support for BLOG.
+ * Each kernel module can have its own isolated logging context.
+ */
+#ifndef _LINUX_BLOG_MODULE_H
+#define _LINUX_BLOG_MODULE_H
+
+#include <linux/blog/blog.h>
+
+/* Per-module context structure */
+struct blog_module_context {
+ char name[32]; /* Module name */
+ struct blog_logger *logger; /* Logger instance for this module */
+ void *module_private; /* Module-specific private data */
+ struct list_head list; /* List of all module contexts */
+ atomic_t refcount; /* Reference count */
+ bool initialized; /* Initialization status */
+};
+
+/* Module context management API */
+struct blog_module_context *blog_module_init(const char *module_name);
+void blog_module_cleanup(struct blog_module_context *ctx);
+void blog_module_get(struct blog_module_context *ctx);
+void blog_module_put(struct blog_module_context *ctx);
+
+/* Per-module API functions */
+u32 blog_get_source_id_ctx(struct blog_module_context *ctx, const char *file,
+ const char *func, unsigned int line, const char *fmt);
+struct blog_source_info *blog_get_source_info_ctx(struct blog_module_context *ctx, u32 id);
+void* blog_log_ctx(struct blog_module_context *ctx, u32 source_id, u8 client_id, size_t needed_size);
+struct blog_tls_ctx *blog_get_tls_ctx_ctx(struct blog_module_context *ctx);
+struct blog_tls_ctx *blog_get_napi_ctx_ctx(struct blog_module_context *ctx);
+void blog_set_napi_ctx_ctx(struct blog_module_context *ctx, struct blog_tls_ctx *tls_ctx);
+struct blog_tls_ctx *blog_get_ctx_ctx(struct blog_module_context *ctx);
+int blog_log_trim_ctx(struct blog_module_context *ctx, unsigned int n);
+
+/* Helper macros for per-module logging */
+#define __BLOG_LOG_CTX(__ctx, dbg, __client_id, fmt, ...) \
+ do { \
+ static u32 __source_id = 0; \
+ static size_t __size = 0; \
+ void *___buffer = NULL; \
+ if (unlikely(__source_id == 0)) { \
+ __source_id = blog_get_source_id_ctx(__ctx, kbasename(__FILE__), __func__, __LINE__, fmt); \
+ __size = blog_cnt(__VA_ARGS__); \
+ } \
+ ___buffer = blog_log_ctx(__ctx, __source_id, __client_id, __size); \
+ if (likely(___buffer) && __size > 0) { \
+ void *___tmp = ___buffer; \
+ size_t actual_size; \
+ blog_ser(___buffer, ##__VA_ARGS__);\
+ actual_size = ___buffer - ___tmp; \
+ blog_log_trim_ctx(__ctx, __size - actual_size); \
+ } \
+ } while (0)
+
+/* Per-module logging macros */
+#define BLOG_LOG_CTX(ctx, fmt, ...) \
+ __BLOG_LOG_CTX(ctx, 0, 0, fmt, ##__VA_ARGS__)
+
+#define BLOG_LOG_CLIENT_CTX(ctx, client_id, fmt, ...) \
+ __BLOG_LOG_CTX(ctx, 0, client_id, fmt, ##__VA_ARGS__)
+
+#endif /* _LINUX_BLOG_MODULE_H */
#define char_ptr(str) __suppress_cast_warning(char *, (str))
+#ifndef _CEPH_BLOG_SER_HELPERS_DEFINED
+#define _CEPH_BLOG_SER_HELPERS_DEFINED
+
union null_str_u {
char str[8];
unsigned long force_align;
return dst + round_up(ret, 4);
}
+#endif /* _CEPH_BLOG_SER_HELPERS_DEFINED */
+
/* Serialization type macro */
#define __blog_ser_type(__buffer, __t) \
(__builtin_choose_expr((IS_DYNAMIC_CHAR_PTR((__t)) || IS_STATIC_CHAR_ARRAY((__t))), \
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Ceph integration with BLOG (Binary LOGging)
+ *
+ * Provides compatibility layer and Ceph-specific extensions
+ */
+#ifndef CEPH_BLOG_H
+#define CEPH_BLOG_H
+
+#include <linux/blog/blog.h>
+#include <linux/blog/blog_module.h>
+#include <linux/ceph/libceph.h>
+
+/* Client ID mapping structure - preserves ceph_san_client_id fields */
+struct ceph_blog_client_info {
+ char fsid[16]; /* Client FSID */
+ u64 global_id; /* Client global ID */
+};
+
+/* Constants */
+#define CEPH_BLOG_MAX_CLIENTS 256
+
+/* Ceph's BLOG module context */
+extern struct blog_module_context *ceph_blog_ctx;
+
+/* Ceph BLOG client management functions */
+int ceph_blog_init(void);
+void ceph_blog_cleanup(void);
+u32 ceph_blog_check_client_id(u32 id, const char *fsid, u64 global_id);
+u32 ceph_blog_get_client_id(struct ceph_client *client);
+const struct ceph_blog_client_info *ceph_blog_get_client_info(u32 id);
+int ceph_blog_client_des_callback(char *buf, size_t size, u8 client_id);
+
+/* Compatibility macros for easy migration from ceph_san to BLOG */
+#if defined(CONFIG_BLOG) || defined(CONFIG_BLOG_MODULE)
+
+/* Direct mappings to BLOG functions */
+#define ceph_san_logger_init() blog_init()
+#define ceph_san_logger_cleanup() blog_cleanup()
+#define ceph_san_get_source_id blog_get_source_id
+#define ceph_san_get_source_info blog_get_source_info
+#define ceph_san_log blog_log
+#define ceph_san_get_tls_ctx() blog_get_tls_ctx()
+#define ceph_san_get_napi_ctx() blog_get_napi_ctx()
+#define ceph_san_set_napi_ctx(ctx) blog_set_napi_ctx(ctx)
+#define ceph_san_get_ctx() blog_get_ctx()
+#define ceph_san_log_trim blog_log_trim
+
+/* Structure mappings */
+#define ceph_san_logger blog_logger
+#define ceph_san_log_entry blog_log_entry
+#define ceph_san_tls_ctx blog_tls_ctx
+#define ceph_san_source_info blog_source_info
+#define ceph_san_log_iter blog_log_iter
+
+/*
+ * Ceph-specific logging macros - use Ceph's module context
+ * Note: Only client-aware macros (doutc, boutc) store client_id,
+ * regular macros (dout, bout) do not include client information
+ */
+#define CEPH_BLOG_LOG(fmt, ...) \
+ do { \
+ if (ceph_blog_ctx) \
+ BLOG_LOG_CTX(ceph_blog_ctx, fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define CEPH_BLOG_LOG_CLIENT(client, fmt, ...) \
+ do { \
+ if (ceph_blog_ctx) { \
+ u32 __client_id = ceph_blog_get_client_id(client); \
+ BLOG_LOG_CLIENT_CTX(ceph_blog_ctx, __client_id, fmt, ##__VA_ARGS__); \
+ } \
+ } while (0)
+
+/* Legacy compatibility - maps old ceph_san macros to BLOG */
+/* Only define if not already defined by ceph_san_logger.h */
+#ifndef CEPH_SAN_LOG
+#define CEPH_SAN_LOG(fmt, ...) \
+ CEPH_BLOG_LOG(fmt, ##__VA_ARGS__)
+#endif
+
+#ifndef CEPH_SAN_LOG_CLIENT
+#define CEPH_SAN_LOG_CLIENT(client, fmt, ...) \
+ CEPH_BLOG_LOG_CLIENT(client, fmt, ##__VA_ARGS__)
+#endif
+
+#else /* !CONFIG_BLOG */
+
+/* Stub macros when BLOG is not enabled */
+#define CEPH_BLOG_LOG(fmt, ...) do {} while (0)
+#define CEPH_BLOG_LOG_CLIENT(client, fmt, ...) do {} while (0)
+#define CEPH_SAN_LOG(fmt, ...) do {} while (0)
+#define CEPH_SAN_LOG_CLIENT(client, fmt, ...) do {} while (0)
+
+/* Stub functions should be static inline, not macros */
+static inline int ceph_blog_init(void) { return 0; }
+static inline void ceph_blog_cleanup(void) { }
+static inline u32 ceph_blog_get_client_id(struct ceph_client *client) { return 0; }
+static inline u32 ceph_blog_check_client_id(u32 id, const char *fsid, u64 global_id) { return 0; }
+static inline const struct ceph_blog_client_info *ceph_blog_get_client_info(u32 id) { return NULL; }
+static inline int ceph_blog_client_des_callback(char *buf, size_t size, u8 client_id) { return 0; }
+
+#endif /* CONFIG_BLOG */
+
+/* Debugfs support */
+#ifdef CONFIG_DEBUG_FS
+int ceph_blog_debugfs_init(struct dentry *parent);
+void ceph_blog_debugfs_cleanup(void);
+#else
+static inline int ceph_blog_debugfs_init(struct dentry *parent) { return 0; }
+static inline void ceph_blog_debugfs_cleanup(void) {}
+#endif
+
+#endif /* CEPH_BLOG_H */
#define char_ptr(str) __suppress_cast_warning(char *, (str))
+#ifndef _CEPH_BLOG_SER_HELPERS_DEFINED
+#define _CEPH_BLOG_SER_HELPERS_DEFINED
+
union null_str_u {
char str[8];
unsigned long force_align;
return dst + round_up(ret, 4);
}
+#endif /* _CEPH_BLOG_SER_HELPERS_DEFINED */
+
#define __ceph_san_ser_type(__buffer, __t) \
(__builtin_choose_expr((IS_DYNAMIC_CHAR_PTR((__t)) || IS_STATIC_CHAR_ARRAY((__t))), \
/* For static arrays (like __func__), just save pointer */ \
obj-$(CONFIG_BLOG) += blog.o
-blog-y := blog_core.o blog_batch.o blog_pagefrag.o blog_des.o
+blog-y := blog_core.o blog_batch.o blog_pagefrag.o blog_des.o blog_module.o
# Debug support
# blog-$(CONFIG_BLOG_DEBUG) += blog_debug.o
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Binary Logging Infrastructure (BLOG) - Per-Module Support
+ *
+ * Implements per-module context management for isolated logging.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+#include <linux/blog/blog.h>
+#include <linux/blog/blog_module.h>
+
+/* Global list of all module contexts */
+static LIST_HEAD(blog_module_contexts);
+static DEFINE_SPINLOCK(blog_modules_lock);
+
+/**
+ * blog_module_init - Initialize a per-module BLOG context
+ * @module_name: Name of the module
+ *
+ * Creates an isolated logging context for a specific module.
+ *
+ * Return: Module context on success, NULL on failure
+ */
+struct blog_module_context *blog_module_init(const char *module_name)
+{
+ struct blog_module_context *ctx;
+ struct blog_logger *logger;
+ int i;
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return NULL;
+
+ logger = kzalloc(sizeof(*logger), GFP_KERNEL);
+ if (!logger) {
+ kfree(ctx);
+ return NULL;
+ }
+
+ /* Initialize module context */
+ strscpy(ctx->name, module_name, sizeof(ctx->name));
+ ctx->logger = logger;
+ atomic_set(&ctx->refcount, 1);
+ ctx->initialized = true;
+ INIT_LIST_HEAD(&ctx->list);
+
+ /* Initialize logger instance */
+ INIT_LIST_HEAD(&logger->contexts);
+ spin_lock_init(&logger->lock);
+ spin_lock_init(&logger->source_lock);
+ spin_lock_init(&logger->ctx_id_lock);
+ atomic_set(&logger->next_source_id, 1);
+ logger->next_ctx_id = 1;
+ logger->total_contexts_allocated = 0;
+
+ /* Initialize batches */
+ blog_batch_init(&logger->alloc_batch);
+ blog_batch_init(&logger->log_batch);
+
+ /* Initialize source map */
+ for (i = 0; i < BLOG_MAX_SOURCE_IDS; i++) {
+ memset(&logger->source_map[i], 0, sizeof(logger->source_map[i]));
+ }
+
+ /* Allocate per-CPU NAPI context pointers */
+ logger->napi_ctxs = alloc_percpu(struct blog_tls_ctx *);
+ if (!logger->napi_ctxs) {
+ kfree(logger);
+ kfree(ctx);
+ return NULL;
+ }
+
+ /* Add to global list */
+ spin_lock(&blog_modules_lock);
+ list_add(&ctx->list, &blog_module_contexts);
+ spin_unlock(&blog_modules_lock);
+
+ pr_info("BLOG: Module context initialized for %s\n", module_name);
+ return ctx;
+}
+EXPORT_SYMBOL(blog_module_init);
+
+/**
+ * blog_module_cleanup - Clean up a module's BLOG context
+ * @ctx: Module context to clean up
+ */
+void blog_module_cleanup(struct blog_module_context *ctx)
+{
+ struct blog_logger *logger;
+ struct blog_tls_ctx *tls_ctx, *tmp;
+
+ if (!ctx || !ctx->initialized)
+ return;
+
+ logger = ctx->logger;
+ if (!logger)
+ return;
+
+ /* Remove from global list */
+ spin_lock(&blog_modules_lock);
+ list_del(&ctx->list);
+ spin_unlock(&blog_modules_lock);
+
+ /* Clean up all TLS contexts */
+ spin_lock(&logger->lock);
+ list_for_each_entry_safe(tls_ctx, tmp, &logger->contexts, list) {
+ list_del(&tls_ctx->list);
+ /* Call release function if set */
+ if (tls_ctx->release)
+ tls_ctx->release(tls_ctx);
+ kfree(tls_ctx);
+ }
+ spin_unlock(&logger->lock);
+
+ /* Clean up batches */
+ blog_batch_cleanup(&logger->alloc_batch);
+ blog_batch_cleanup(&logger->log_batch);
+
+ /* Free per-CPU NAPI contexts */
+ if (logger->napi_ctxs)
+ free_percpu(logger->napi_ctxs);
+
+ pr_info("BLOG: Module context cleaned up for %s\n", ctx->name);
+
+ kfree(logger);
+ kfree(ctx);
+}
+EXPORT_SYMBOL(blog_module_cleanup);
+
+/**
+ * blog_module_get - Increment module context reference count
+ * @ctx: Module context
+ */
+void blog_module_get(struct blog_module_context *ctx)
+{
+ if (ctx)
+ atomic_inc(&ctx->refcount);
+}
+EXPORT_SYMBOL(blog_module_get);
+
+/**
+ * blog_module_put - Decrement module context reference count
+ * @ctx: Module context
+ */
+void blog_module_put(struct blog_module_context *ctx)
+{
+ if (ctx && atomic_dec_and_test(&ctx->refcount))
+ blog_module_cleanup(ctx);
+}
+EXPORT_SYMBOL(blog_module_put);
+
+/* Per-module API implementations */
+
+/**
+ * blog_get_source_id_ctx - Get or allocate source ID for a module context
+ * @ctx: Module context
+ * @file: Source file name
+ * @func: Function name
+ * @line: Line number
+ * @fmt: Format string
+ *
+ * Return: Source ID
+ */
+u32 blog_get_source_id_ctx(struct blog_module_context *ctx, const char *file,
+ const char *func, unsigned int line, const char *fmt)
+{
+ struct blog_logger *logger;
+ struct blog_source_info *info;
+ u32 id;
+
+ if (!ctx || !ctx->logger)
+ return 0;
+
+ logger = ctx->logger;
+
+ /* Get next ID */
+ id = atomic_fetch_inc(&logger->next_source_id);
+ if (id >= BLOG_MAX_SOURCE_IDS) {
+ pr_warn("BLOG: Source ID overflow in module %s\n", ctx->name);
+ return 0;
+ }
+
+ /* Fill in source info */
+ spin_lock(&logger->source_lock);
+ info = &logger->source_map[id];
+ info->file = file;
+ info->func = func;
+ info->line = line;
+ info->fmt = fmt;
+ info->warn_count = 0;
+#if BLOG_TRACK_USAGE
+ atomic_set(&info->napi_usage, 0);
+ atomic_set(&info->task_usage, 0);
+ atomic_set(&info->napi_bytes, 0);
+ atomic_set(&info->task_bytes, 0);
+#endif
+ spin_unlock(&logger->source_lock);
+
+ return id;
+}
+EXPORT_SYMBOL(blog_get_source_id_ctx);
+
+/**
+ * blog_get_source_info_ctx - Get source info for an ID in a module context
+ * @ctx: Module context
+ * @id: Source ID
+ *
+ * Return: Source info or NULL
+ */
+struct blog_source_info *blog_get_source_info_ctx(struct blog_module_context *ctx, u32 id)
+{
+ struct blog_logger *logger;
+
+ if (!ctx || !ctx->logger || id >= BLOG_MAX_SOURCE_IDS)
+ return NULL;
+
+ logger = ctx->logger;
+ return &logger->source_map[id];
+}
+EXPORT_SYMBOL(blog_get_source_info_ctx);
+
+/**
+ * blog_get_tls_ctx_ctx - Get or create TLS context for a module
+ * @ctx: Module context
+ *
+ * Return: TLS context or NULL
+ */
+struct blog_tls_ctx *blog_get_tls_ctx_ctx(struct blog_module_context *ctx)
+{
+ struct blog_logger *logger;
+ struct blog_tls_ctx *tls_ctx;
+ struct task_struct *task = current;
+
+ if (!ctx || !ctx->logger)
+ return NULL;
+
+ logger = ctx->logger;
+
+ /* For now, always create a new context per call
+ * TODO: Implement proper per-task TLS storage */
+
+ /* Allocate new TLS context */
+ tls_ctx = kzalloc(sizeof(*tls_ctx), GFP_KERNEL);
+ if (!tls_ctx)
+ return NULL;
+
+ /* Initialize TLS context */
+ INIT_LIST_HEAD(&tls_ctx->list);
+ atomic_set(&tls_ctx->refcount, 1);
+ tls_ctx->task = task;
+ tls_ctx->pid = task->pid;
+ get_task_comm(tls_ctx->comm, task);
+ tls_ctx->base_jiffies = jiffies;
+
+ /* Initialize pagefrag */
+ blog_pagefrag_init(&tls_ctx->pf);
+
+ /* Get unique context ID */
+ spin_lock(&logger->ctx_id_lock);
+ tls_ctx->id = logger->next_ctx_id++;
+ spin_unlock(&logger->ctx_id_lock);
+
+#if BLOG_DEBUG_POISON
+ tls_ctx->debug_poison = BLOG_CTX_POISON;
+#endif
+
+ /* Add to logger's context list */
+ spin_lock(&logger->lock);
+ list_add(&tls_ctx->list, &logger->contexts);
+ logger->total_contexts_allocated++;
+ spin_unlock(&logger->lock);
+
+ /* TODO: Store in task-specific storage */
+
+ return tls_ctx;
+}
+EXPORT_SYMBOL(blog_get_tls_ctx_ctx);
+
+/**
+ * blog_log_ctx - Log a message with module context
+ * @ctx: Module context
+ * @source_id: Source ID
+ * @client_id: Client ID
+ * @needed_size: Size needed for the log entry
+ *
+ * Return: Buffer to write log data to, or NULL on failure
+ */
+void* blog_log_ctx(struct blog_module_context *ctx, u32 source_id,
+ u8 client_id, size_t needed_size)
+{
+ struct blog_tls_ctx *tls_ctx;
+ struct blog_log_entry *entry;
+ int alloc;
+ size_t total_size;
+
+ if (!ctx || !ctx->logger)
+ return NULL;
+
+ /* Get TLS context */
+ tls_ctx = blog_get_tls_ctx_ctx(ctx);
+ if (!tls_ctx)
+ return NULL;
+
+ /* Calculate total size needed */
+ total_size = sizeof(*entry) + needed_size;
+ if (total_size > BLOG_LOG_MAX_LEN) {
+ pr_warn_once("BLOG: Log entry too large (%zu > %d) in module %s\n",
+ total_size, BLOG_LOG_MAX_LEN, ctx->name);
+ return NULL;
+ }
+
+ /* Allocate space from pagefrag */
+ alloc = blog_pagefrag_alloc(&tls_ctx->pf, total_size);
+ if (alloc == -ENOMEM) {
+ pr_debug("blog_log_ctx: allocation failed for module %s\n", ctx->name);
+ blog_pagefrag_reset(&tls_ctx->pf);
+ return NULL;
+ }
+
+ /* Get pointer from allocation */
+ entry = blog_pagefrag_get_ptr(&tls_ctx->pf, alloc);
+ if (!entry) {
+ pr_err("blog_log_ctx: failed to get pointer from pagefrag\n");
+ return NULL;
+ }
+
+ /* Fill in entry header */
+#if BLOG_DEBUG_POISON
+ entry->debug_poison = BLOG_LOG_ENTRY_POISON;
+#endif
+ entry->ts_delta = jiffies - tls_ctx->base_jiffies;
+ entry->source_id = source_id;
+ entry->client_id = client_id;
+ entry->len = needed_size;
+
+ /* Return pointer to buffer area */
+ return entry->buffer;
+}
+EXPORT_SYMBOL(blog_log_ctx);
+
+/**
+ * blog_log_trim_ctx - Trim unused space from last log entry
+ * @ctx: Module context
+ * @n: Number of bytes to trim
+ *
+ * Return: 0 on success, negative on error
+ */
+int blog_log_trim_ctx(struct blog_module_context *ctx, unsigned int n)
+{
+ struct blog_tls_ctx *tls_ctx;
+
+ if (!ctx || !ctx->logger)
+ return -EINVAL;
+
+ tls_ctx = blog_get_tls_ctx_ctx(ctx);
+ if (!tls_ctx)
+ return -EINVAL;
+
+ blog_pagefrag_trim(&tls_ctx->pf, n);
+ return 0;
+}
+EXPORT_SYMBOL(blog_log_trim_ctx);
+
+/**
+ * blog_get_ctx_ctx - Get appropriate context based on execution context
+ * @ctx: Module context
+ *
+ * Return: TLS context or NAPI context depending on execution context
+ */
+struct blog_tls_ctx *blog_get_ctx_ctx(struct blog_module_context *ctx)
+{
+ if (in_serving_softirq())
+ return blog_get_napi_ctx_ctx(ctx);
+ return blog_get_tls_ctx_ctx(ctx);
+}
+EXPORT_SYMBOL(blog_get_ctx_ctx);
+
+/**
+ * blog_get_napi_ctx_ctx - Get NAPI context for current CPU
+ * @ctx: Module context
+ *
+ * Return: NAPI context or NULL
+ */
+struct blog_tls_ctx *blog_get_napi_ctx_ctx(struct blog_module_context *ctx)
+{
+ struct blog_logger *logger;
+ struct blog_tls_ctx **napi_ctx_ptr;
+
+ if (!ctx || !ctx->logger)
+ return NULL;
+
+ logger = ctx->logger;
+ if (!logger->napi_ctxs)
+ return NULL;
+
+ /* Get pointer to the percpu pointer */
+ napi_ctx_ptr = per_cpu_ptr(logger->napi_ctxs, smp_processor_id());
+ return *napi_ctx_ptr;
+}
+EXPORT_SYMBOL(blog_get_napi_ctx_ctx);
+
+/**
+ * blog_set_napi_ctx_ctx - Set NAPI context for current CPU
+ * @ctx: Module context
+ * @tls_ctx: TLS context to set
+ */
+void blog_set_napi_ctx_ctx(struct blog_module_context *ctx, struct blog_tls_ctx *tls_ctx)
+{
+ struct blog_logger *logger;
+ struct blog_tls_ctx **napi_ctx_ptr;
+
+ if (!ctx || !ctx->logger || !ctx->logger->napi_ctxs)
+ return;
+
+ logger = ctx->logger;
+ /* Get pointer to the percpu pointer and set it */
+ napi_ctx_ptr = per_cpu_ptr(logger->napi_ctxs, smp_processor_id());
+ *napi_ctx_ptr = tls_ctx;
+}
+EXPORT_SYMBOL(blog_set_napi_ctx_ctx);