diff options
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | device_info_procfs.c | 126 | ||||
| -rw-r--r-- | mmu.c | 251 | ||||
| -rw-r--r-- | nvdebug.h | 719 | ||||
| -rw-r--r-- | nvdebug_entry.c | 288 | ||||
| -rw-r--r-- | runlist.c | 221 | ||||
| -rw-r--r-- | runlist_procfs.c | 188 | ||||
| -rw-r--r-- | stubs.h | 80 |
8 files changed, 1614 insertions, 272 deletions
| @@ -1,13 +1,14 @@ | |||
| 1 | obj-m += nvdebug.o | 1 | obj-m += nvdebug.o |
| 2 | nvdebug-objs = runlist_procfs.o runlist.o nvdebug_entry.o | 2 | nvdebug-objs = runlist_procfs.o device_info_procfs.o runlist.o mmu.o nvdebug_entry.o |
| 3 | KBUILD_CFLAGS += -DGIT_HASH=\"$(shell git --git-dir=$(PWD)/.git rev-parse --short HEAD)\" | 3 | KBUILD_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 | 7 | ccflags-y += -I$(PWD)/include |
| 7 | ccflags-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 |
| 8 | ccflags-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 |
| 9 | ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/include | 10 | #ccflags-y += -I/playpen/Linux_for_Tegra/source/public/kernel/nvgpu/include |
| 10 | ccflags-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 | ||
| 12 | all: | 13 | all: |
| 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 | ||
| 12 | static 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 | } | ||
| 25 | const 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. | ||
| 33 | static 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 | ||
| 46 | static 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. | ||
| 56 | static 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 | |||
| 106 | static void device_info_file_seq_stop(struct seq_file *s, void *idx) { | ||
| 107 | // No cleanup needed | ||
| 108 | } | ||
| 109 | |||
| 110 | static 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 | |||
| 117 | static int device_info_file_open(struct inode *inode, struct file *f) { | ||
| 118 | return seq_open(f, &device_info_file_seq_ops); | ||
| 119 | } | ||
| 120 | |||
| 121 | const 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 | }; | ||
| @@ -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 | */ | ||
| 22 | uint32_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) <--+ | | | ||
