DEBUG: nova-core: debugfs support to dump logrm, logintr, loginit

Uses C helpers to expose Nova's log buffers to /sys/kernel/debug/nova/*.
This can then be passed to xxd -r -p to convert to binary, and then to
nvlog_decoder for decoding to text.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig
index 9f93df0..95fe3e6 100644
--- a/drivers/gpu/nova-core/Kconfig
+++ b/drivers/gpu/nova-core/Kconfig
@@ -24,3 +24,14 @@
 
 	  If unset the existing in-tree Nouveau copy of these headers will be used
 	  instead.
+
+config NOVA_DEBUGFS
+	bool "Nova Core debugfs support"
+	depends on NOVA_CORE
+	depends on DEBUG_FS
+	default y
+	help
+	  Enable debugfs support for the Nova driver. Pass the debugfs file to
+	  xxd -r -p to get the binary file and then pass that to nvlog_decoder:
+	  nvlog_decoder --input-file rm_log.bin --elf gsp_log.bin --d none
+                        --fake-nvlog-libos GSP_LIBOS3 -t RM
diff --git a/drivers/gpu/nova-core/debugfs.rs b/drivers/gpu/nova-core/debugfs.rs
new file mode 100644
index 0000000..e57ba9e
--- /dev/null
+++ b/drivers/gpu/nova-core/debugfs.rs
@@ -0,0 +1,103 @@
+use kernel::prelude::*;
+use kernel::{error::to_result, c_str};
+use core::ffi::c_void;
+use core::ptr;
+use crate::gsp::GspSharedMemObjects;
+
+extern "C" {
+    fn nova_debugfs_create(name: *const i8) -> *mut c_void;
+    fn nova_debugfs_destroy(debugfs: *mut c_void);
+    fn nova_debugfs_create_log_files(
+        debugfs: *mut c_void,
+        loginit_info: *mut NovaLogBufferInfo,
+        logintr_info: *mut NovaLogBufferInfo,
+        logrm_info: *mut NovaLogBufferInfo,
+    ) -> i32;
+}
+
+#[repr(C)]
+struct NovaLogBufferInfo {
+    name: *const i8,
+    data: *mut c_void,
+    size: usize,
+}
+
+pub(crate) struct NovaDebugfs {
+    ptr: *mut c_void,
+    loginit_info: KBox<NovaLogBufferInfo>,
+    logintr_info: KBox<NovaLogBufferInfo>,
+    logrm_info: KBox<NovaLogBufferInfo>,
+}
+
+impl NovaDebugfs {
+    pub(crate) fn new(name: &str) -> Result<Self> {
+        let name_cstr = kernel::str::CString::try_from_fmt(fmt!("{}", name))?;
+
+        let ptr = unsafe { nova_debugfs_create(name_cstr.as_char_ptr() as *const i8) };
+
+        if ptr.is_null() {
+            return Err(ENOMEM);
+        }
+
+        let loginit_info = KBox::new(NovaLogBufferInfo {
+            name: c_str!("LOGINIT").as_char_ptr() as *const i8,
+            data: ptr::null_mut(),
+            size: 0,
+        }, GFP_KERNEL)?;
+
+        let logintr_info = KBox::new(NovaLogBufferInfo {
+            name: c_str!("LOGINTR").as_char_ptr() as *const i8,
+            data: ptr::null_mut(),
+            size: 0,
+        }, GFP_KERNEL)?;
+
+        let logrm_info = KBox::new(NovaLogBufferInfo {
+            name: c_str!("LOGRM").as_char_ptr() as *const i8,
+            data: ptr::null_mut(),
+            size: 0,
+        }, GFP_KERNEL)?;
+
+        Ok(Self {
+            ptr,
+            loginit_info,
+            logintr_info,
+            logrm_info,
+        })
+    }
+
+    pub(crate) fn create_log_files(&mut self, gsp_mem: &GspSharedMemObjects) -> Result {
+        self.loginit_info.data = gsp_mem.loginit.start_ptr() as *mut c_void;
+        self.loginit_info.size = gsp_mem.loginit.size();
+
+        self.logintr_info.data = gsp_mem.logintr.start_ptr() as *mut c_void;
+        self.logintr_info.size = gsp_mem.logintr.size();
+
+        self.logrm_info.data = gsp_mem.logrm.start_ptr() as *mut c_void;
+        self.logrm_info.size = gsp_mem.logrm.size();
+
+        let ret = unsafe {
+            nova_debugfs_create_log_files(
+                self.ptr,
+                &mut *self.loginit_info as *mut _,
+                &mut *self.logintr_info as *mut _,
+                &mut *self.logrm_info as *mut _,
+            )
+        };
+
+        to_result(ret)?;
+
+        pr_info!("Nova debugfs: Created log files\n");
+        Ok(())
+    }
+}
+
+impl Drop for NovaDebugfs {
+    fn drop(&mut self) {
+        unsafe {
+            nova_debugfs_destroy(self.ptr);
+        }
+    }
+}
+
+unsafe impl Send for NovaDebugfs {}
+unsafe impl Sync for NovaDebugfs {}
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 67d327ab..aa49955 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
 use kernel::dma::CoherentAllocation;
-use kernel::{device, devres::Devres, error::code::*, pci, prelude::*};
+use kernel::{device, devres::Devres, error::code::*, pci, prelude::*, c_str};
 
 use core::time::Duration;
 
@@ -18,6 +18,10 @@
 use crate::util;
 use crate::vbios::Vbios;
 use core::fmt;
+use crate::debugfs::NovaDebugfs;
+use kernel::sync::{Arc, Mutex};
+
+static mut NOVA_DEBUGFS: Option<Arc<Mutex<NovaDebugfs>>> = None;
 
 macro_rules! define_chipset {
     ({ $($variant:ident = $value:expr),* $(,)* }) =>
@@ -366,6 +370,38 @@ pub(crate) fn new(
             pr_err!("Error receiving INIT_DONE: {:?}\n", e);
         } else {
             pr_info!("INIT_DONE received. GSP is running.\n");
+
+            // debugfs files for GSP log
+            unsafe {
+                if NOVA_DEBUGFS.is_none() {
+                    match NovaDebugfs::new("nova") {
+                        Ok(debugfs) => {
+                            match Arc::pin_init(
+                                Mutex::new(debugfs, c_str!("nova_debugfs"), kernel::static_lock_class!()),
+                                GFP_KERNEL
+                            ) {
+                                Ok(arc) => {
+                                    NOVA_DEBUGFS = Some(arc);
+                                    pr_info!("Created nova debugfs directory\n");
+                                }
+                                Err(e) => {
+                                    pr_err!("Failed to create Arc for debugfs: {:?}\n", e);
+                                }
+                            }
+                        }
+                        Err(e) => {
+                            pr_err!("Failed to create debugfs: {:?}\n", e);
+                        }
+                    }
+                }
+
+                if let Some(ref debugfs_arc) = NOVA_DEBUGFS {
+                    let mut debugfs = debugfs_arc.lock();
+                    if let Err(e) = debugfs.create_log_files(&libos) {
+                        pr_err!("Failed to create debugfs log files: {:?}\n", e);
+                    }
+                }
+            }
         }
 
         Ok(pin_init!(Self {
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 91eac80..001d253 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -736,9 +736,9 @@ unsafe impl AsBytes for fw::GSP_ARGUMENTS_CACHED {}
 #[allow(unused)]
 pub(crate) struct GspSharedMemObjects<'a> {
     pub libos: DmaObject,
-    loginit: DmaObject,
-    logintr: DmaObject,
-    logrm: DmaObject,
+    pub loginit: DmaObject,
+    pub logintr: DmaObject,
+    pub logrm: DmaObject,
     pub rmargs: CoherentAllocation<fw::GSP_ARGUMENTS_CACHED>,
     // kern: Option<DmaObject>,
     pub cmdq: GspCmdq<'a>,
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index 25e2271..9ac72c9 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -67,6 +67,7 @@ pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self> {
 mod regs;
 mod util;
 mod vbios;
+mod debugfs;
 
 kernel::module_pci_driver! {
     type: driver::NovaCore,
diff --git a/include/linux/nova_debugfs_helpers.h b/include/linux/nova_debugfs_helpers.h
new file mode 100644
index 0000000..c685932
--- /dev/null
+++ b/include/linux/nova_debugfs_helpers.h
@@ -0,0 +1,21 @@
+#ifndef _NOVA_DEBUGFS_HELPERS_H
+#define _NOVA_DEBUGFS_HELPERS_H
+
+#include <linux/types.h>
+
+struct nova_debugfs;
+
+struct nova_log_buffer_info {
+    const char *name;
+    void *data;
+    size_t size;
+};
+
+struct nova_debugfs *nova_debugfs_create(const char *name);
+void nova_debugfs_destroy(struct nova_debugfs *debugfs);
+int nova_debugfs_create_log_files(struct nova_debugfs *debugfs,
+                                 struct nova_log_buffer_info *loginit_info,
+                                 struct nova_log_buffer_info *logintr_info,
+                                 struct nova_log_buffer_info *logrm_info);
+
+#endif
diff --git a/kernel/Makefile b/kernel/Makefile
index 434929d..6bbe2e5 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -133,6 +133,7 @@
 obj-$(CONFIG_HAS_IOMEM) += iomem.o
 obj-$(CONFIG_RSEQ) += rseq.o
 obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
+obj-$(CONFIG_NOVA_DEBUGFS) += nova_debugfs_helpers.o
 
 obj-$(CONFIG_RESOURCE_KUNIT_TEST) += resource_kunit.o
 obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
diff --git a/kernel/nova_debugfs_helpers.c b/kernel/nova_debugfs_helpers.c
new file mode 100644
index 0000000..6f2077c
--- /dev/null
+++ b/kernel/nova_debugfs_helpers.c
@@ -0,0 +1,147 @@
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/types.h>
+#include <linux/export.h>
+#include <linux/nova_debugfs_helpers.h>
+
+struct nova_debugfs {
+    struct dentry *root;
+    struct dentry *rm_log0;
+    struct dentry *rm_log1;
+    struct dentry *rm_log2;
+    struct dentry *status;
+    void *gsp_mem;
+};
+
+struct nova_debugfs_file_data {
+    struct nova_log_buffer_info *info;
+    loff_t pos;
+};
+
+static int nova_debugfs_open(struct inode *inode, struct file *file)
+{
+    struct nova_debugfs_file_data *data;
+
+    data = kmalloc(sizeof(*data), GFP_KERNEL);
+    if (!data)
+        return -ENOMEM;
+
+    data->info = inode->i_private;
+    data->pos = 0;
+    file->private_data = data;
+
+    return 0;
+}
+
+static int nova_debugfs_release(struct inode *inode, struct file *file)
+{
+    kfree(file->private_data);
+    return 0;
+}
+
+static ssize_t nova_debugfs_read(struct file *file, char __user *buf,
+                                size_t count, loff_t *ppos)
+{
+    struct nova_debugfs_file_data *data = file->private_data;
+    struct nova_log_buffer_info *info = data->info;
+    loff_t pos = *ppos;
+    char hex_buf[64];
+    size_t bytes_read = 0;
+    size_t offset;
+
+    if (!info || !info->data)
+        return -EINVAL;
+
+    // Each byte becomes 3 chars (2 hex + 1 space), plus newline every 16 bytes
+    offset = pos / 3;
+
+    if (offset >= info->size)
+        return 0;  // EOF
+
+    while (bytes_read < count && offset < info->size) {
+        size_t line_offset = offset % 16;
+        size_t bytes_in_line = min_t(size_t, 16 - line_offset, info->size - offset);
+        size_t hex_pos = 0;
+        size_t i;
+
+        for (i = 0; i < bytes_in_line; i++) {
+            u8 byte = ((u8 *)info->data)[offset + i];
+            hex_pos += scnprintf(hex_buf + hex_pos, sizeof(hex_buf) - hex_pos,
+                               "%02x ", byte);
+        }
+
+        if (line_offset + bytes_in_line == 16 || offset + bytes_in_line == info->size) {
+            hex_buf[hex_pos - 1] = '\n';
+        }
+        if (bytes_read + hex_pos > count)
+            break;
+        if (copy_to_user(buf + bytes_read, hex_buf, hex_pos))
+            return -EFAULT;
+
+        bytes_read += hex_pos;
+        offset += bytes_in_line;
+        *ppos = offset * 3;
+    }
+
+    return bytes_read;
+}
+
+static const struct file_operations nova_debugfs_fops = {
+    .owner = THIS_MODULE,
+    .open = nova_debugfs_open,
+    .release = nova_debugfs_release,
+    .read = nova_debugfs_read,
+    .llseek = default_llseek,
+};
+
+struct nova_debugfs *nova_debugfs_create(const char *name)
+{
+    struct nova_debugfs *debugfs;
+
+    debugfs = kzalloc(sizeof(*debugfs), GFP_KERNEL);
+    if (!debugfs)
+        return NULL;
+
+    debugfs->root = debugfs_create_dir(name, NULL);
+    if (!debugfs->root) {
+        kfree(debugfs);
+        return NULL;
+    }
+
+    return debugfs;
+}
+
+void nova_debugfs_destroy(struct nova_debugfs *debugfs)
+{
+    if (!debugfs)
+        return;
+
+    debugfs_remove_recursive(debugfs->root);
+    kfree(debugfs);
+}
+
+int nova_debugfs_create_log_files(struct nova_debugfs *debugfs,
+                                 struct nova_log_buffer_info *loginit_info,
+                                 struct nova_log_buffer_info *logintr_info,
+                                 struct nova_log_buffer_info *logrm_info)
+{
+    if (!debugfs || !debugfs->root)
+        return -EINVAL;
+
+    debugfs->rm_log0 = debugfs_create_file("init_log", 0444, debugfs->root,
+                                          loginit_info, &nova_debugfs_fops);
+
+    debugfs->rm_log1 = debugfs_create_file("intr_log", 0444, debugfs->root,
+                                          logintr_info, &nova_debugfs_fops);
+
+    debugfs->rm_log2 = debugfs_create_file("rm_log", 0444, debugfs->root,
+                                          logrm_info, &nova_debugfs_fops);
+
+    return 0;
+}
+
+EXPORT_SYMBOL_GPL(nova_debugfs_create);
+EXPORT_SYMBOL_GPL(nova_debugfs_destroy);
+EXPORT_SYMBOL_GPL(nova_debugfs_create_log_files);