[PATCH 8/9] accel/neutron: Add logging support

Ioana Ciocoi-Radulescu ruxandra.radulescu at nxp.com
Thu Feb 26 05:40:47 PST 2026


Expose the Neutron firmware log via debugfs interface. The log resides
in internal device memory.

Signed-off-by: Ioana Ciocoi-Radulescu <ruxandra.radulescu at nxp.com>
---
 drivers/accel/neutron/Makefile          |  2 +
 drivers/accel/neutron/neutron_debugfs.c | 34 ++++++++++++++++
 drivers/accel/neutron/neutron_debugfs.h | 15 +++++++
 drivers/accel/neutron/neutron_device.c  | 69 +++++++++++++++++++++++++++++++++
 drivers/accel/neutron/neutron_device.h  | 17 ++++++++
 drivers/accel/neutron/neutron_driver.c  |  3 ++
 6 files changed, 140 insertions(+)

diff --git a/drivers/accel/neutron/Makefile b/drivers/accel/neutron/Makefile
index ac6dd576521c..6d5c204460af 100644
--- a/drivers/accel/neutron/Makefile
+++ b/drivers/accel/neutron/Makefile
@@ -8,3 +8,5 @@ neutron-y := \
 	neutron_gem.o \
 	neutron_job.o \
 	neutron_mailbox.o
+
+neutron-$(CONFIG_DEBUG_FS) += neutron_debugfs.o
diff --git a/drivers/accel/neutron/neutron_debugfs.c b/drivers/accel/neutron/neutron_debugfs.c
new file mode 100644
index 000000000000..a392286e40b7
--- /dev/null
+++ b/drivers/accel/neutron/neutron_debugfs.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright 2025 NXP */
+
+#include <linux/debugfs.h>
+
+#include "neutron_device.h"
+#include "neutron_debugfs.h"
+
+static ssize_t fw_log_read(struct file *f, char __user *buf, size_t count, loff_t *pos)
+{
+	struct neutron_device *ndev = file_inode(f)->i_private;
+
+	if (!ndev->log.size)
+		return 0;
+
+	if (ndev->flags & NEUTRON_BOOTED)
+		neutron_read_log(ndev, count);
+
+	return simple_read_from_buffer(buf, count, pos, ndev->log.buf,
+				       ndev->log.buf_count);
+}
+
+static const struct file_operations fw_log_fops = {
+	.owner = THIS_MODULE,
+	.read = fw_log_read,
+};
+
+void neutron_debugfs_init(struct neutron_device *ndev)
+{
+	struct dentry *debugfs_root;
+
+	debugfs_root = ndev->base.debugfs_root;
+	debugfs_create_file("fw_log", 0444, debugfs_root, ndev, &fw_log_fops);
+}
diff --git a/drivers/accel/neutron/neutron_debugfs.h b/drivers/accel/neutron/neutron_debugfs.h
new file mode 100644
index 000000000000..7cd4b5af55a6
--- /dev/null
+++ b/drivers/accel/neutron/neutron_debugfs.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright 2025 NXP */
+
+#ifndef __NEUTRON_DEBUGFS_H__
+#define __NEUTRON_DEBUGFS_H__
+
+struct neutron_device;
+
+#if defined(CONFIG_DEBUG_FS)
+void neutron_debugfs_init(struct neutron_device *ndev);
+#else
+static inline void neutron_debugfs_init(struct neutron_device *ndev) {}
+#endif
+
+#endif /* __NEUTRON_DEBUGFS_H__ */
diff --git a/drivers/accel/neutron/neutron_device.c b/drivers/accel/neutron/neutron_device.c
index 571ec906ad72..a5cedc91ad25 100644
--- a/drivers/accel/neutron/neutron_device.c
+++ b/drivers/accel/neutron/neutron_device.c
@@ -77,6 +77,70 @@ static void neutron_stop(struct neutron_device *ndev)
 			   100, 100 * USEC_PER_MSEC);
 }
 
+static void neutron_init_logging(struct neutron_device *ndev)
+{
+	size_t old_size = ndev->log.size;
+	u32 ringctrl;
+
+	ringctrl = readl_relaxed(NEUTRON_REG(ndev, RINGCTRL));
+
+	ndev->log.base = ndev->mem_regions[NEUTRON_MEM_DTCM].va +
+			 NEUTRON_DTCM_BANK1_OFFSET +
+			 FIELD_GET(RINGCTRL_ADDR_MASK, ringctrl);
+	ndev->log.size = FIELD_GET(RINGCTRL_SIZE_MASK, ringctrl) *
+			 RINGCTRL_SIZE_MULT;
+
+	if (ndev->log.size == 0) {
+		dev_info(ndev->dev, "Firmware logging is disabled\n");
+		return;
+	}
+
+	/* If size didn't change, keep using the old buffer */
+	if (old_size == ndev->log.size)
+		return;
+
+	devm_kfree(ndev->dev, ndev->log.buf);
+	ndev->log.buf = devm_kmalloc(ndev->dev, ndev->log.size, GFP_KERNEL);
+	if (!ndev->log.buf) {
+		ndev->log.size = 0;
+		dev_warn(ndev->dev, "Failed to allocate log buffer, logging is disabled\n");
+	}
+}
+
+/* Read up to count bytes from device log into local buffer */
+void neutron_read_log(struct neutron_device *ndev, size_t count)
+{
+	size_t bytes, remaining;
+	u32 head, tail;
+
+	ndev->log.buf_count = 0;
+
+	if (!(ndev->flags & NEUTRON_BOOTED) || !ndev->log.size)
+		return;
+
+	tail = readl_relaxed(NEUTRON_REG(ndev, TAIL));
+	head = readl_relaxed(NEUTRON_REG(ndev, HEAD));
+
+	if (tail == head)
+		return;
+
+	/* Read from head to end of buffer or tail */
+	bytes = (head < tail) ? (tail - head) : (ndev->log.size - head);
+	bytes = min(count, bytes);
+	memcpy_fromio(ndev->log.buf, ndev->log.base + head, bytes);
+	ndev->log.buf_count = bytes;
+
+	/* Read from start of buffer, if it wraps around */
+	if (head > tail && bytes < count) {
+		remaining = min(count - bytes, tail);
+		memcpy_fromio(ndev->log.buf + bytes, ndev->log.base, remaining);
+		ndev->log.buf_count += remaining;
+	}
+
+	head = (head + ndev->log.buf_count) % ndev->log.size;
+	writel_relaxed(head, NEUTRON_REG(ndev, HEAD));
+}
+
 static void __iomem *neutron_tcm_da_to_va(struct neutron_device *ndev, u64 da)
 {
 	struct neutron_mem_region *mem;
@@ -158,6 +222,8 @@ int neutron_boot(struct neutron_device *ndev)
 	/* Prepare device to receive jobs */
 	neutron_mbox_reset_state(ndev);
 
+	neutron_init_logging(ndev);
+
 	ndev->flags |= NEUTRON_BOOTED;
 
 	return 0;
@@ -165,6 +231,9 @@ int neutron_boot(struct neutron_device *ndev)
 
 void neutron_shutdown(struct neutron_device *ndev)
 {
+	/* Device log becomes unavailable after shutdown, save it */
+	neutron_read_log(ndev, ndev->log.size);
+
 	neutron_stop(ndev);
 	ndev->flags &= ~NEUTRON_BOOTED;
 }
diff --git a/drivers/accel/neutron/neutron_device.h b/drivers/accel/neutron/neutron_device.h
index 0ed72965774d..bf06878ac370 100644
--- a/drivers/accel/neutron/neutron_device.h
+++ b/drivers/accel/neutron/neutron_device.h
@@ -85,11 +85,26 @@ enum neutron_mem_id {
 	NEUTRON_MEM_MAX
 };
 
+/**
+ * struct neutron_log - Neutron log buffer descriptor
+ * @base: base address of the log buffer in device memory
+ * @size: Size of the log buffer
+ * @buf: Kernel buffer for log data
+ * @buf_count: Number of bytes available in the kernel buffer
+ */
+struct neutron_log {
+	void __iomem *base;
+	size_t size;
+	void *buf;
+	size_t buf_count;
+};
+
 /**
  * struct neutron_device - Neutron device structure
  * @base: Base DRM device
  * @dev: Pointer to underlying device
  * @mem_regions: Array of memory region descriptors
+ * @log: Log buffer descriptor
  * @irq: IRQ number
  * @clks: Neutron clocks
  * @num_clks: Number of clocks
@@ -107,6 +122,7 @@ struct neutron_device {
 	struct device *dev;
 
 	struct neutron_mem_region mem_regions[NEUTRON_MEM_MAX];
+	struct neutron_log log;
 
 	int irq;
 	struct clk_bulk_data *clks;
@@ -137,5 +153,6 @@ void neutron_shutdown(struct neutron_device *ndev);
 void neutron_enable_irq(struct neutron_device *ndev);
 void neutron_disable_irq(struct neutron_device *ndev);
 void neutron_handle_irq(struct neutron_device *ndev);
+void neutron_read_log(struct neutron_device *ndev, size_t bytes);
 
 #endif /* __NEUTRON_DEVICE_H__ */
diff --git a/drivers/accel/neutron/neutron_driver.c b/drivers/accel/neutron/neutron_driver.c
index ceae1f7e8359..14b4bc3a79d1 100644
--- a/drivers/accel/neutron/neutron_driver.c
+++ b/drivers/accel/neutron/neutron_driver.c
@@ -18,6 +18,7 @@
 
 #include "neutron_device.h"
 #include "neutron_driver.h"
+#include "neutron_debugfs.h"
 #include "neutron_gem.h"
 #include "neutron_job.h"
 
@@ -168,6 +169,8 @@ static int neutron_probe(struct platform_device *pdev)
 	if (ret)
 		goto free_reserved;
 
+	neutron_debugfs_init(ndev);
+
 	ret = devm_pm_runtime_enable(dev);
 	if (ret)
 		goto free_job;

-- 
2.34.1




More information about the linux-arm-kernel mailing list