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);