aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile13
-rw-r--r--device_info_procfs.c126
-rw-r--r--mmu.c251
-rw-r--r--nvdebug.h719
-rw-r--r--nvdebug_entry.c288
-rw-r--r--runlist.c221
-rw-r--r--runlist_procfs.c188
-rw-r--r--stubs.h80
8 files changed, 1614 insertions, 272 deletions
diff --git a/Makefile b/Makefile
index 18c07e8..2dc90c7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,13 +1,14 @@
1obj-m += nvdebug.o 1obj-m += nvdebug.o
2nvdebug-objs = runlist_procfs.o runlist.o nvdebug_entry.o 2nvdebug-objs = runlist_procfs.o device_info_procfs.o runlist.o mmu.o nvdebug_entry.o
3KBUILD_CFLAGS += -DGIT_HASH=\"$(shell git --git-dir=$(PWD)/.git rev-parse --short HEAD)\" 3KBUILD_CFLAGS += -DGIT_HASH=\"$(shell git --git-dir=$(PWD)/.git rev-parse --short HEAD)\"
4# -mfentry above if not building due to mcount missing
4 5
5# TODO: Avoid needing to distribute NVIDIA's headers (at least they're MIT...) 6# TODO: Avoid needing to distribute NVIDIA's headers (at least they're MIT...)
6#ccflags-y += -I$(PWD)/include 7ccflags-y += -I$(PWD)/include
7ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/drivers/gpu/nvgpu/include 8#ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/drivers/gpu/nvgpu/include
8ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/drivers/gpu/nvgpu 9#ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/drivers/gpu/nvgpu
9ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/include 10#ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/include
10ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/include/uapi 11#ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/include/uapi
11 12
12all: 13all:
13 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 14 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
diff --git a/device_info_procfs.c b/device_info_procfs.c
new file mode 100644
index 0000000..cd6c53c
--- /dev/null
+++ b/device_info_procfs.c
@@ -0,0 +1,126 @@
1#include "nvdebug.h"
2#include <linux/seq_file.h> // For seq_* functions and types
3#include <linux/uaccess.h> // For copy_to_user()
4
5// Generic register printing function, used for PTOP_*_NUM registers (+more)
6// @param f File being read from. `data` field is register offset to read.
7// @param buf User buffer for result
8// @param size Length of user buffer
9// @param off Requested offset. Updated by number of characters written.
10// @return -errno on error, otherwise number of bytes written to *buf
11// Note: Parent `data` field MUST be the GPU index
12static ssize_t nvdebug_reg32_read(struct file *f, char __user *buf, size_t size, loff_t *off) {
13 char out[16];
14 int chars_written;
15 struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(f)];
16 if (size < 16 || *off != 0)
17 return 0;
18 // 32 bit register will always take less than 16 characters to print
19 chars_written = scnprintf(out, 16, "%#0x\n", nvdebug_readl(g, (uintptr_t)PDE_DATA(file_inode(f))));
20 if (copy_to_user(buf, out, chars_written))
21 printk(KERN_WARNING "Unable to copy all data for %s\n", file_dentry(f)->d_name.name);
22 *off += chars_written;
23 return chars_written;
24}
25const struct file_operations nvdebug_read_reg32_file_ops = {
26 .read = nvdebug_reg32_read,
27};
28
29//// ==v== PTOP_DEVICE_INFO ==v== ////
30
31// Called to start or resume a sequence. Prior to 4.19, *pos is unreliable.
32// Initializes iterator `idx` state and returns it. Ends sequence on NULL.
33static void* device_info_file_seq_start(struct seq_file *s, loff_t *pos) {
34 static int idx;
35 // If start of sequence, reset `idx`
36 if (*pos == 0)
37 idx = 0;
38 // Number of possible info entries is fixed, and list is sparse
39 if (idx >= NV_PTOP_DEVICE_INFO__SIZE_1)
40 return NULL;
41 return &idx;
42}
43
44// Steps to next record. Returns new value of `idx`.
45// Calls show() on non-NULL return
46static void* device_info_file_seq_next(struct seq_file *s, void *idx,
47 loff_t *pos) {
48 (*pos)++; // Required by seq interface
49 // Number of possible info entries is fixed, and list is sparse
50 if ((*(int*)idx)++ >= NV_PTOP_DEVICE_INFO__SIZE_1)
51 return NULL;
52 return idx;
53}
54
55// Print info at index *idx. Returns non-zero on error.
56static int device_info_file_seq_show(struct seq_file *s, void *idx) {
57 ptop_device_info_t curr_info;
58 struct nvdebug_state *g = &g_nvdebug_state[seq2gpuidx(s)];
59
60 curr_info.raw = nvdebug_readl(g, NV_PTOP_DEVICE_INFO(*(int*)idx));
61 // Check for read errors
62 if (curr_info.raw == -1)
63 return -EIO;
64
65 // Parse and print the data
66 switch(curr_info.info_type) {
67 case INFO_TYPE_DATA:
68 // As of early 2022, only the ENUM2 format of this entry exists
69 if (curr_info.is_not_enum2)
70 break;
71 seq_printf(s, "| BAR0 Base %#.8x\n"
72 "| instance %d\n",
73 curr_info.pri_base << 12, curr_info.inst_id);
74 if (curr_info.fault_id_is_valid)
75 seq_printf(s, "| Fault ID: %3d\n", curr_info.fault_id);
76 break;
77 case INFO_TYPE_ENUM:
78 if (curr_info.engine_is_valid)
79 seq_printf(s, "| Host's Engine ID: %2d\n", curr_info.engine_enum);
80 if (curr_info.runlist_is_valid)
81 seq_printf(s, "| Runlist ID: %2d\n", curr_info.runlist_enum);
82 if (curr_info.intr_is_valid)
83 seq_printf(s, "| Interrupt ID: %2d\n", curr_info.intr_enum);
84 if (curr_info.reset_is_valid)
85 seq_printf(s, "| Reset ID: %2d\n", curr_info.reset_enum);
86 break;
87 case INFO_TYPE_ENGINE_TYPE:
88 seq_printf(s, "| Engine Type: %2d (", curr_info.engine_type);
89 if (curr_info.engine_type < ENGINE_TYPES_LEN)
90 seq_printf(s, "%s)\n", ENGINE_TYPES_NAMES[curr_info.engine_type]);
91 else
92 seq_printf(s, "Unknown Engine, introduced post-Ampere)\n");
93 break;
94 case INFO_TYPE_NOT_VALID:
95 default:
96 // Device info records are sparse, so skip unset or unknown ones
97 return 0;
98 }
99
100 // Draw a line between each device entry
101 if (!curr_info.has_next_entry)
102 seq_printf(s, "+---------------------+\n");
103 return 0;
104}
105
106static void device_info_file_seq_stop(struct seq_file *s, void *idx) {
107 // No cleanup needed
108}
109
110static const struct seq_operations device_info_file_seq_ops = {
111 .start = device_info_file_seq_start,
112 .next = device_info_file_seq_next,
113 .stop = device_info_file_seq_stop,
114 .show = device_info_file_seq_show,
115};
116
117static int device_info_file_open(struct inode *inode, struct file *f) {
118 return seq_open(f, &device_info_file_seq_ops);
119}
120
121const struct file_operations device_info_file_ops = {
122 .open = device_info_file_open,
123 .read = seq_read,
124 .llseek = seq_lseek,
125 .release = seq_release,
126};
diff --git a/mmu.c b/mmu.c
new file mode 100644
index 0000000..26c7af5
--- /dev/null
+++ b/mmu.c
@@ -0,0 +1,251 @@
1// Helpers to deal with NVIDIA's MMU and associated page tables
2#include <linux/kernel.h> // Kernel types
3
4#include "nvdebug.h"
5
6/* One of the oldest ways to access video memory on NVIDIA GPUs is by using
7 a configurable 1MB window into VRAM which is mapped into BAR0 (register)
8 space starting at offset NV_PRAMIN. This is still supported on NVIDIA GPUs
9 and appear to be used today to bootstrap page table configuration.
10
11 Why is it mapped at a location called NVIDIA Private RAM Instance? Because
12 this used to point to the entirety of intance RAM, which was seperate from
13 VRAM on older NVIDIA GPUs.
14*/
15
16/* Convert a physical VRAM address to an offset in the PRAMIN window
17 @param addr VRAM address to convert
18 @return 0 on error, PRAMIN offset on success
19
20 Note: Use off2PRAMIN() instead if you want a dereferenceable address
21*/
22uint32_t vram2PRAMIN(struct nvdebug_state *g, uint64_t addr) {
23 uint64_t pramin_base_va;
24 bar0_window_t window;
25 window.raw = nvdebug_readl(g, NV_PBUS_BAR0_WINDOW);
26 // Check if the address is valid (49 bits are addressable on-GPU)
27 if (addr & ~0x0001ffffffffffff) {
28 printk(KERN_ERR "[nvdebug] Invalid address %llx passed to %s!\n",
29 addr, __func__);
30 return 0;
31 }
32 // For unclear (debugging?) reasons, PRAMIN can point to SYSMEM
33 if (window.target != TARGET_VID_MEM)
34 return 0;
35 pramin_base_va = ((uint64_t)window.base) << 16;
36 // Protect against out-of-bounds accesses
37 if (addr < pramin_base_va || addr > pramin_base_va + NV_PRAMIN_LEN)
38 return 0;
39 return addr - pramin_base_va;
40}
41
42/* NVIDIA GMMU (GPU Memory Management Unit) uses page tables that are mostly
43 straight-forward starting with Pascal ("page table version 2"), except for a
44 few quirks (like 16-byte PDE0 entries, but all other entries are 8 bytes).
45
46 All you really need to know is that any given Page Directory Entry (PDE)
47 contains a pointer to the start of a 4k page densely filled with PDEs or Page
48 Table Entries (PTEs).
49
50 == Page Table Refresher ==
51 Page tables convert virtual addresses to physical addresses, and they do this
52 via a tree structure. Leafs (PTEs) contain a physical address, and the path
53 from root to leaf is defined by the virtual address. Non-leaf nodes are PDEs.
54 When decending, the virtual address is sliced into pieces, and one slice is
55 used at each level (as an index) to select the next-visited node (in level+1).
56
57 V2 of NVIDIA's page table format uses 4 levels of PDEs and a final level of
58 PTEs. How the virtual address is sliced to yield an index into each level and
59 a page offset is shown by Fig 1.
60
61 == Figure 1 ==
62 Page Offset (12 bits) <---------------------------------------+
63 Page Table Entry (PTE) (9 bits) <--------------------+ |
64 Page Directory Entry (PDE) 0 (8 bits) <-----+ | |
65 PDE1 (8 bits) <--------------------+ | | |
66 PDE2 (8 bits) <-----------+ | | | |
67 PDE3 (2 bits) <--+ | |