[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