Expose the Neutron firmware log via debugfs interface. The log resides in internal device memory.
Signed-off-by: Ioana Ciocoi-Radulescu <[email protected]> --- 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
