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) <--+ | | | | | | ||
| 68 | ^ ^ ^ ^ ^ ^ | ||
| 69 | Virtual addr: [49, 47] [46, 38] [37, 29] [28, 21] [20, 12] [11, 0] | ||
| 70 | |||
| 71 | The following arrays merely represent different projections of Fig. 1, and | ||
| 72 | only one is strictly needed to reconstruct all the others. However, due to | ||
| 73 | the complexity of page tables, we include all of these to aid in readability. | ||
| 74 | */ | ||
| 75 | // How many nodes/entries per level in V2 of NVIDIA's page table format | ||
| 76 | static const int NV_MMU_PT_V2_SZ[5] = {4, 512, 512, 256, 512}; | ||
| 77 | // Size in bytes of an entry at a particular level | ||
| 78 | static const int NV_MMU_PT_V2_ENTRY_SZ[5] = {8, 8, 8, 16, 8}; | ||
| 79 | // Which bit index is the least significant in indexing each page level | ||
| 80 | static const int NV_MMU_PT_V2_LSB[5] = {47, 38, 29, 21, 12}; | ||
| 81 | |||
| 82 | // Convert a GPU physical address to CPU virtual address via the PRAMIN window | ||
| 83 | void __iomem *phy2PRAMIN(struct nvdebug_state* g, uint64_t phy) { | ||
| 84 | return g->regs + NV_PRAMIN + vram2PRAMIN(g, phy); | ||
| 85 | } | ||
| 86 | |||
| 87 | /* FIXME | ||
| 88 | void __iomem *off2BAR2(struct nvdebug_state* g, uint32_t off) { | ||
| 89 | return g->bar2 + off; | ||
| 90 | } | ||
| 91 | */ | ||
| 92 | |||
| 93 | uint64_t search_page_directory_subtree(struct nvdebug_state *g, | ||
| 94 | void __iomem *pde_offset, | ||
| 95 | void __iomem *(*off2addr)(struct nvdebug_state*, uint64_t), | ||
| 96 | uint64_t addr_to_find, | ||
| 97 | uint32_t level) { | ||
| 98 | uint64_t res, i; | ||
| 99 | void __iomem *next; | ||
| 100 | page_dir_entry_t entry; | ||
| 101 | if (level > sizeof(NV_MMU_PT_V2_SZ)) | ||
| 102 | return 0; | ||
| 103 | // Hack to workaround PDE0 being double-size and strangely formatted | ||
| 104 | if (NV_MMU_PT_V2_ENTRY_SZ[level] == 16) | ||
| 105 | pde_offset += 8; | ||
| 106 | entry.raw = readl(pde_offset); | ||
| 107 | // If we reached an invalid (unpopulated) PDE, walk back up the tree | ||
| 108 | if (entry.target == PD_AND_TARGET_INVALID) | ||
| 109 | return 0; | ||
| 110 | // Succeed when we reach a PTE with the address we want | ||
| 111 | if (entry.is_pte) { | ||
| 112 | printk(KERN_INFO "[nvdebug] PTE for phy addr %llx (raw: %x)\n", ((u64)entry.addr) << 12, entry.raw); | ||
| 113 | return (uint64_t)entry.addr << 12 == addr_to_find; | ||
| 114 | } | ||
| 115 | printk(KERN_INFO "[nvdebug] Found PDE pointing to %llx in ap '%d' at lvl %d (raw: %x)\n", ((u64)entry.addr) << 12, entry.target, level, entry.raw); | ||
| 116 | // Depth-first search of the page table | ||
| 117 | for (i = 0; i < NV_MMU_PT_V2_SZ[level]; i++) { | ||
| 118 | next = off2addr(g, ((uint64_t)entry.addr << 12) + NV_MMU_PT_V2_ENTRY_SZ[level + 1] * i); | ||
| 119 | // off2addr can fail | ||
| 120 | if (!next) { | ||
| 121 | printk(KERN_ERR "[nvdebug] %s: Unable to resolve GPU PA to CPU PA\n", __func__); | ||
| 122 | return 0; | ||
| 123 | } | ||
| 124 | res = search_page_directory_subtree(g, next, off2addr, addr_to_find, level + 1); | ||
| 125 | if (res) | ||
| 126 | return res | (i << NV_MMU_PT_V2_LSB[level + 1]); | ||
| 127 | } | ||
| 128 | return 0; | ||
| 129 | } | ||
| 130 | |||
| 131 | /* Search a page directory of the GPU MMU | ||
| 132 | @param pde_offset Dereferenceable pointer to the start of the PDE3 entries | ||
| 133 | @param off2addr Func to converts VRAM phys addresses to valid CPU VAs | ||
| 134 | @param addr_to_find Physical address to reconstruct the virtual address of | ||
| 135 | @return 0 on error, otherwise the virtual address at which addr_to_find is | ||
| 136 | mapped into by this page table. | ||
| 137 | */ | ||
| 138 | uint64_t search_page_directory(struct nvdebug_state *g, | ||
| 139 | void __iomem *pde_offset, | ||
| 140 | void __iomem *(*off2addr)(struct nvdebug_state*, uint64_t), | ||
| 141 | uint64_t addr_to_find) { | ||
| 142 | uint64_t res, i; | ||
| 143 | // Make sure that the query is page-aligned | ||
| 144 | if (addr_to_find & 0xfff) { | ||
| 145 | printk(KERN_WARNING "[nvdebug] Attempting to search for unaligned address %llx in search_page_directory()!\n", addr_to_find); | ||
| 146 | return 0; | ||
| 147 | } | ||
| 148 | // Search the top-level page directory (PDE3) | ||
| 149 | for (i = 0; i < NV_MMU_PT_V2_SZ[0]; i++) | ||
| 150 | if ((res = search_page_directory_subtree(g, pde_offset + NV_MMU_PT_V2_ENTRY_SZ[0] * i, off2addr, addr_to_find, 0))) | ||
| 151 | return (res & ~0xfff) | (i << NV_MMU_PT_V2_LSB[0]); | ||
| 152 | return 0; | ||
| 153 | } | ||
| 154 | |||
| 155 | /* GMMU Page Tables Version 1 | ||
| 156 | This page table only contains 2 levels and is used in the Fermi, Kepler, and | ||
| 157 | Maxwell architectures | ||
| 158 | */ | ||
| 159 | // Number of entries in the PDE and PTE levels | ||
| 160 | static const int NV_MMU_PT_V1_SZ[2] = {512, 1<<13}; // 2<<13 is an educated guess!!! | ||
| 161 | // Which bit index is the least significant in indexing each page level | ||
| 162 | static const int NV_MMU_PT_V1_LSB[2] = {25, 12}; // 25 is an educated guess!!! | ||
| 163 | uint64_t search_v1_page_directory(struct nvdebug_state *g, | ||
| 164 | void __iomem *pde_offset, | ||
| 165 | void __iomem *(*off2addr)(struct nvdebug_state*, uint64_t), | ||
| 166 | uint64_t addr_to_find) { | ||
| 167 | uint64_t j, i = 0; | ||
| 168 | page_dir_entry_v1_t pde; | ||
| 169 | page_tbl_entry_v1_t pte; | ||
| 170 | void __iomem *pte_offset; | ||
| 171 | // For each PDE | ||
| 172 | do { | ||
| 173 | // readq doesn't seem to work on BAR0 | ||
| 174 | pde.raw = readl(pde_offset + i * sizeof(page_dir_entry_v1_t) + 4); | ||
| 175 | pde.raw <<= 32; | ||
| 176 | pde.raw |= readl(pde_offset + i * sizeof(page_dir_entry_v1_t)); | ||
| 177 | // Verify PDE is present | ||
| 178 | if (pde.target == PD_TARGET_INVALID && pde.alt_target == PD_TARGET_INVALID) | ||
| 179 | continue; | ||
| 180 | // Convert to a dereferencable pointer from CPU virtual address space | ||
| 181 | pte_offset = off2addr(g, (uint64_t)pde.alt_addr << 12); | ||
| 182 | if (!pte_offset) | ||
| 183 | continue; | ||
| 184 | // printk(KERN_INFO "[nvdebug] Found %s PDE pointing to PTEs @ %llx in ap '%d' (raw: %llx)\n", pde.is_volatile ? "volatile" : "non-volatile", ((u64)pde.addr) << 12, pde.target, pde.raw); | ||
| 185 | // printk(KERN_INFO "[nvdebug] Found %s PDE pointing to PTEs @ %llx in ap '%d' (raw: %llx)\n", pde.alt_is_volatile ? "volatile" : "non-volatile", ((u64)pde.alt_addr) << 12, pde.target, pde.raw); | ||
| 186 | // For each PTE | ||
| 187 | for (j = 0; j < NV_MMU_PT_V1_SZ[1]; j++) { | ||
| 188 | // Don't overrun the PRAMIN window | ||
| 189 | if (pte_offset > NV_PRAMIN + g->regs + NV_PRAMIN_LEN) | ||
| 190 | return 0; | ||
| 191 | pte.raw = readl(pte_offset + j * sizeof(page_tbl_entry_v1_t) + 4); | ||
| 192 | pte.raw <<= 32; | ||
| 193 | pte.raw |= readl(pte_offset + j * sizeof(page_tbl_entry_v1_t)); | ||
| 194 | // Skip non-present PTEs | ||
| 195 | if (!pte.is_present) | ||
| 196 | continue; | ||
| 197 | // printk(KERN_INFO "[nvdebug] PTE for phy addr %llx %s (raw: %llx)\n", ((u64)pte.addr) << 12, pte.is_present ? "present" : "non-present", pte.raw); | ||
| 198 | // If we find a matching PTE, return its virtual address | ||
| 199 | if ((uint64_t)pte.addr << 12 == addr_to_find) | ||
| 200 | return i << NV_MMU_PT_V1_LSB[0] | j << NV_MMU_PT_V1_LSB[1]; | ||
| 201 | |||
| 202 | } | ||
| 203 | } while (++i < NV_MMU_PT_V1_SZ[0]); | ||
| 204 | return 0; | ||
| 205 | } | ||
| 206 | |||
| 207 | /* GMMU Page Tables Version 0 | ||
| 208 | This page table only contains 2 levels and is used in the Tesla architecture | ||
| 209 | */ | ||
| 210 | /* *** UNTESTED *** | ||
| 211 | #define NV_MMU_PT_V0_SZ 2048 | ||
| 212 | #define NV_MMU_PT_V0_LSB 29 | ||
| 213 | uint64_t search_v0_page_directory(struct nvdebug_state *g, | ||
| 214 | void __iomem *pde_offset, | ||
| 215 | void __iomem *(*off2addr)(struct nvdebug_state*, uint32_t), | ||
| 216 | uint32_t addr_to_find) { | ||
| 217 | int j, i = 0; | ||
| 218 | page_dir_entry_v0_t pde; | ||
| 219 | page_tbl_entry_v0_t pte; | ||
| 220 | void __iomem *pte_offset; | ||
| 221 | // For each PDE | ||
| 222 | do { | ||
| 223 | // readq doesn't seem to work on BAR0 | ||
| 224 | pde.raw = readl(pde_offset + i * sizeof(page_dir_entry_v0_t) + 4); | ||
| 225 | pde.raw <<= 32; | ||
| 226 | pde.raw |= readl(pde_offset + i * sizeof(page_dir_entry_v0_t)); | ||
| 227 | //if (pde.raw) | ||
| 228 | //printk(KERN_INFO "[nvdebug] Read raw PDE @ %x: %llx\n", pde_offset + i * sizeof(page_dir_entry_v1_t), pde.raw); | ||
| 229 | // Skip unpopulated PDEs | ||
| 230 | if (pde.type == NOT_PRESENT) | ||
| 231 | continue; | ||
| 232 | //printk(KERN_INFO "[nvdebug] PDE to %llx present\n", ((uint64_t)pde.addr) << 12); | ||
| 233 | pte_offset = off2addr(g, ((uint64_t)pde.addr) << 12); | ||
| 234 | // For each PTE | ||
| 235 | for (j = 0; j < V0_PDE_SIZE2NUM[pde.sublevel_size]; j++) { | ||
| 236 | pte.raw = readl(pte_offset + j * sizeof(page_tbl_entry_v0_t) + 4); | ||
| 237 | pte.raw <<= 32; | ||
| 238 | pte.raw |= readl(pte_offset + j * sizeof(page_tbl_entry_v0_t)); | ||
| 239 | // Skip non-present PTEs | ||
| 240 | if (!pte.is_present) | ||
| 241 | continue; | ||
| 242 | // If we find a matching PTE, return its virtual address | ||
| 243 | //if (pte.addr != 0x5555555) | ||
| 244 | // printk(KERN_INFO "[nvdebug] PTE for phy addr %llx %s\n", ((uint64_t)pte.addr) << 12, pte.is_present ? "present" : "non-present"); | ||
| 245 | if (pte.addr << 12 == addr_to_find) | ||
| 246 | return i << NV_MMU_PT_V0_LSB | j << 12; | ||
| 247 | } | ||
| 248 | } while (++i < NV_MMU_PT_V0_SZ); | ||
| 249 | return 0; // No match | ||
| 250 | } | ||
| 251 | */ | ||
| @@ -5,14 +5,18 @@ | |||
| 5 | // TODO(jbakita): Don't depend on these. | 5 | // TODO(jbakita): Don't depend on these. |
| 6 | #include <nvgpu/gk20a.h> // For struct gk20a | 6 | #include <nvgpu/gk20a.h> // For struct gk20a |
| 7 | #include <os/linux/os_linux.h> // For struct nvgpu_os_linux | 7 | #include <os/linux/os_linux.h> // For struct nvgpu_os_linux |
| 8 | #include <linux/proc_fs.h> // For PDE_DATA() macro | ||
| 8 | 9 | ||
| 9 | /* Runlist Channel | 10 | /* Runlist Channel |
| 10 | A timeslice group (TSG) is composed of channels. Each channel is a FIFO queue | 11 | A timeslice group (TSG) is composed of channels. Each channel is a FIFO queue |
| 11 | of GPU commands. These commands are typically queued from userspace. | 12 | of GPU commands. These commands are typically queued from userspace. |
| 12 | 13 | ||
| 13 | `INST_PTR` points to a GPU Instance Block which contains pointers to the GPU | 14 | Prior to Volta, channels could also exist independent of a TSG. These are |
| 14 | virtual address space for this context. All channels in a TSG point to the | 15 | called "bare channels" in the Jetson nvgpu driver. |
| 15 | same GPU Instance Block (?). | 16 | |
| 17 | `INST_PTR` points to a GPU Instance Block which contains FIFO states, virtual | ||
| 18 | address space configuration for this context, and a pointer to the page | ||
| 19 | tables. All channels in a TSG point to the same GPU Instance Block (?). | ||
| 16 | 20 | ||
| 17 | "RUNQUEUE_SELECTOR determines to which runqueue the channel belongs, and | 21 | "RUNQUEUE_SELECTOR determines to which runqueue the channel belongs, and |
| 18 | thereby which PBDMA will run the channel. Increasing values select | 22 | thereby which PBDMA will run the channel. Increasing values select |
| @@ -30,7 +34,13 @@ | |||
| 30 | ENTRY_TYPE (T) : type of this entry: ENTRY_TYPE_CHAN | 34 | ENTRY_TYPE (T) : type of this entry: ENTRY_TYPE_CHAN |
| 31 | CHID (ID) : identifier of the channel to run (overlays ENTRY_ID) | 35 | CHID (ID) : identifier of the channel to run (overlays ENTRY_ID) |
| 32 | RUNQUEUE_SELECTOR (Q) : selects which PBDMA should run this channel if | 36 | RUNQUEUE_SELECTOR (Q) : selects which PBDMA should run this channel if |
| 33 | more than one PBDMA is supported by the runlist | 37 | more than one PBDMA is supported by the runlist, |
| 38 | additionally, "A value of 0 targets the first FE | ||
| 39 | pipe, which can process all FE driven engines: | ||
| 40 | Graphics, Compute, Inline2Memory, and TwoD. A value | ||
| 41 | of 1 targets the second FE pipe, which can only | ||
| 42 | process Compute work. Note that GRCE work is allowed | ||
| 43 | on either runqueue.)" | ||
| 34 | 44 | ||
| 35 | INST_PTR_LO : lower 20 bits of the 4k-aligned instance block pointer | 45 | INST_PTR_LO : lower 20 bits of the 4k-aligned instance block pointer |
| 36 | INST_PTR_HI : upper 32 bit of instance block pointer | 46 | INST_PTR_HI : upper 32 bit of instance block pointer |
| @@ -39,6 +49,9 @@ | |||
| 39 | USERD_PTR_LO : upper 24 bits of the low 32 bits, of the 512-byte-aligned USERD pointer | 49 | USERD_PTR_LO : upper 24 bits of the low 32 bits, of the 512-byte-aligned USERD pointer |
| 40 | USERD_PTR_HI : upper 32 bits of USERD pointer | 50 | USERD_PTR_HI : upper 32 bits of USERD pointer |
| 41 | USERD_TARGET (TGU) : aperture of the USERD data structure | 51 | USERD_TARGET (TGU) : aperture of the USERD data structure |
| 52 | |||
| 53 | Channels were around since at least Fermi, but were rearranged with Volta to | ||
| 54 | add a USERD pointer, a longer INST pointer, and a runqueue selector flag. | ||
| 42 | */ | 55 | */ |
| 43 | enum ENTRY_TYPE {ENTRY_TYPE_CHAN = 0, ENTRY_TYPE_TSG = 1}; | 56 | enum ENTRY_TYPE {ENTRY_TYPE_CHAN = 0, ENTRY_TYPE_TSG = 1}; |
| 44 | enum INST_TARGET {TARGET_VID_MEM = 0, TARGET_SYS_MEM_COHERENT = 2, TARGET_SYS_MEM_NONCOHERENT = 3}; | 57 | enum INST_TARGET {TARGET_VID_MEM = 0, TARGET_SYS_MEM_COHERENT = 2, TARGET_SYS_MEM_NONCOHERENT = 3}; |
| @@ -52,11 +65,12 @@ static inline char* target_to_text(enum INST_TARGET t) { | |||
| 52 | return "SYS_MEM_NONCOHERENT"; | 65 | return "SYS_MEM_NONCOHERENT"; |
| 53 | default: | 66 | default: |
| 54 | printk(KERN_WARNING "[nvdebug] Invalid aperture!\n"); | 67 | printk(KERN_WARNING "[nvdebug] Invalid aperture!\n"); |
| 55 | return NULL; | 68 | return "INVALID"; |
| 56 | } | 69 | } |
| 57 | } | 70 | } |
| 58 | 71 | ||
| 59 | struct runlist_chan { | 72 | // Support: Volta, Ampere, Turing |
| 73 | struct gv100_runlist_chan { | ||
| 60 | // 0:63 | 74 | // 0:63 |
| 61 | enum ENTRY_TYPE entry_type:1; | 75 | enum ENTRY_TYPE entry_type:1; |
| 62 | uint32_t runqueue_selector:1; | 76 | uint32_t runqueue_selector:1; |
| @@ -71,6 +85,20 @@ struct runlist_chan { | |||
| 71 | uint32_t inst_ptr_hi:32; | 85 | uint32_t inst_ptr_hi:32; |
| 72 | } __attribute__((packed)); | 86 | } __attribute__((packed)); |
| 73 | 87 | ||
| 88 | // Support: Fermi, Kepler*, Maxwell, Pascal | ||
| 89 | // *In Kepler, inst fields may be unpopulated? | ||
| 90 | struct gm107_runlist_chan { | ||
| 91 | uint32_t chid:12; | ||
| 92 | uint32_t padding0:1; | ||
| 93 | enum ENTRY_TYPE entry_type:1; | ||
| 94 | uint32_t padding1:18; | ||
| 95 | uint32_t inst_ptr_lo:20; | ||
| 96 | enum INST_TARGET inst_target:2; // Totally guessing on this | ||
| 97 | uint32_t padding2:10; | ||
| 98 | } __attribute__((packed)); | ||
| 99 | |||
| 100 | #define gk110_runlist_chan gm107_runlist_chan | ||
| 101 | |||
| 74 | /* Runlist TSG (TimeSlice Group) | 102 | /* Runlist TSG (TimeSlice Group) |
| 75 | The runlist is composed of timeslice groups (TSG). Each TSG corresponds | 103 | The runlist is composed of timeslice groups (TSG). Each TSG corresponds |
| 76 | to a single virtual address space on the GPU and contains `TSG_LENGTH` | 104 | to a single virtual address space on the GPU and contains `TSG_LENGTH` |
| @@ -85,8 +113,15 @@ struct runlist_chan { | |||
| 85 | TIMESLICE_TIMEOUT : timeout amount for the TSG's timeslice | 113 | TIMESLICE_TIMEOUT : timeout amount for the TSG's timeslice |
| 86 | TSG_LENGTH : number of channels that are part of this timeslice group | 114 | TSG_LENGTH : number of channels that are part of this timeslice group |
| 87 | TSGID : identifier of the Timeslice group (overlays ENTRY_ID) | 115 | TSGID : identifier of the Timeslice group (overlays ENTRY_ID) |
| 116 | |||
| 117 | TSGs appear to have been introduced with Kepler and stayed the same until | ||
| 118 | they were rearranged at the time of channel rearrangement to support longer | ||
| 119 | GPU instance addresses with Volta. | ||
| 88 | */ | 120 | */ |
| 89 | struct entry_tsg { | 121 | |
| 122 | // Support: Volta, Ampere*, Turing* | ||
| 123 | // *These treat the top 8 bits of TSGID as GFID (unused) | ||
| 124 | struct gv100_runlist_tsg { | ||
| 90 | // 0:63 | 125 | // 0:63 |
| 91 | enum ENTRY_TYPE entry_type:1; | 126 | enum ENTRY_TYPE entry_type:1; |
| 92 | uint64_t padding:15; | 127 | uint64_t padding:15; |
| @@ -101,14 +136,28 @@ struct entry_tsg { | |||
| 101 | } __attribute__((packed)); | 136 | } __attribute__((packed)); |
| 102 | #define MAX_TSGID (1 << 12) | 137 | #define MAX_TSGID (1 << 12) |
| 103 | 138 | ||
| 139 | // Support: Kepler (v2?), Maxwell, Pascal | ||
| 140 | // Same fields as Volta except tsg_length is 6 bits rather than 8 | ||
| 141 | // Last 32 bits appear to contain an undocumented inst ptr | ||
| 142 | struct gk110_runlist_tsg { | ||
| 143 | uint32_t tsgid:12; | ||
| 144 | uint32_t padding0:1; | ||
| 145 | enum ENTRY_TYPE entry_type:1; | ||
| 146 | uint32_t timeslice_scale:4; | ||
| 147 | uint32_t timeslice_timeout:8; | ||
| 148 | uint32_t tsg_length:6; | ||
| 149 | uint32_t padding1:32; | ||
| 150 | } __attribute__((packed)); | ||
| 151 | |||
| 152 | |||
| 104 | enum PREEMPT_TYPE {PREEMPT_TYPE_CHANNEL = 0, PREEMPT_TYPE_TSG = 1}; | 153 | enum PREEMPT_TYPE {PREEMPT_TYPE_CHANNEL = 0, PREEMPT_TYPE_TSG = 1}; |
| 105 | 154 | ||
| 106 | /* Preempt a TSG or Channel by ID | 155 | /* Preempt a TSG or Channel by ID |
| 107 | ID/CHID : Id of TSG or channel to preempt | 156 | ID/CHID : Id of TSG or channel to preempt |
| 108 | IS_PENDING : ???? | 157 | IS_PENDING : Is a context switch pending? |
| 109 | TYPE : PREEMPT_TYPE_CHANNEL or PREEMPT_TYPE_TSG | 158 | TYPE : PREEMPT_TYPE_CHANNEL or PREEMPT_TYPE_TSG |
| 110 | 159 | ||
| 111 | Support: Kepler, Maxwell, Pascal, Volta | 160 | Support: Kepler, Maxwell, Pascal, Volta, Turing |
| 112 | */ | 161 | */ |
| 113 | #define NV_PFIFO_PREEMPT 0x00002634 | 162 | #define NV_PFIFO_PREEMPT 0x00002634 |
| 114 | typedef union { | 163 | typedef union { |
| @@ -195,26 +244,36 @@ typedef union { | |||
| 195 | */ | 244 | */ |
| 196 | 245 | ||
| 197 | // Note: This is different with Turing | 246 | // Note: This is different with Turing |
| 198 | // Support: Kepler, Maxwell, Pascal, Volta | 247 | // Support: Fermi, Kepler, Maxwell, Pascal, Volta |
| 199 | #define NV_PFIFO_RUNLIST_BASE 0x00002270 | 248 | #define NV_PFIFO_RUNLIST_BASE 0x00002270 |
| 249 | #define NV_PFIFO_ENG_RUNLIST_BASE(i) (0x00002280+(i)*8) | ||
| 200 | typedef union { | 250 | typedef union { |
| 201 | struct { | 251 | struct { |
| 202 | uint32_t ptr:28; | 252 | uint32_t ptr:28; |
| 203 | uint32_t type:2; | 253 | enum INST_TARGET target:2; |
| 204 | uint32_t padding:2; | 254 | uint32_t padding:2; |
| 205 | } __attribute__((packed)); | 255 | } __attribute__((packed)); |
| 206 | uint32_t raw; | 256 | uint32_t raw; |
| 207 | } runlist_base_t; | 257 | } runlist_base_t; |
| 208 | 258 | ||
| 209 | // Support: Kepler, Maxwell, Pascal, Volta | 259 | // Support: Kepler, Maxwell, Pascal, Volta |
| 260 | // Works on Fermi, but id is one bit longer and is b11111 | ||
| 210 | #define NV_PFIFO_RUNLIST 0x00002274 | 261 | #define NV_PFIFO_RUNLIST 0x00002274 |
| 262 | #define NV_PFIFO_ENG_RUNLIST(i) (0x00002284+(i)*8) | ||
| 211 | typedef union { | 263 | typedef union { |
| 264 | // RUNLIST fields | ||
| 212 | struct { | 265 | struct { |
| 213 | uint32_t len:16; | 266 | uint32_t len:16; |
| 214 | uint32_t padding:4; | 267 | uint32_t padding:4; |
| 215 | uint32_t id:4; | 268 | uint32_t id:4; // Runlist ID (each engine may have a seperate runlist) |
| 216 | uint32_t padding2:8; | 269 | uint32_t padding2:8; |
| 217 | } __attribute__((packed)); | 270 | } __attribute__((packed)); |
| 271 | // ENG_RUNLIST fields that differ | ||
| 272 | struct { | ||
| 273 | uint32_t padding3:20; | ||
| 274 | bool is_pending:1; // Is runlist not yet committed? | ||
| 275 | uint32_t padding4:11; | ||
| 276 | } __attribute__((packed)); | ||
| 218 | uint32_t raw; | 277 | uint32_t raw; |
| 219 | } runlist_info_t; | 278 | } runlist_info_t; |
| 220 | 279 | ||
| @@ -301,63 +360,631 @@ typedef union { | |||
| 301 | uint32_t raw; | 360 | uint32_t raw; |
| 302 | } runlist_disable_t; | 361 | } runlist_disable_t; |
| 303 | 362 | ||
| 363 | /* Read GPU descriptors from the Master Controller (MC) | ||
| 364 | |||
| 365 | MINOR_REVISION : Legacy (only used with Celvin in Nouveau) | ||
| 366 | MAJOR_REVISION : Legacy (only used with Celvin in Nouveau) | ||
| 367 | IMPLEMENTATION : Which implementation of the GPU architecture | ||
| 368 | ARCHITECTURE : Which GPU architecture | ||
| 369 | |||
| 370 | CHIP_ID = IMPLEMENTATION + ARCHITECTURE << 4 | ||
| 371 | CHIP_ID : Unique ID of all chips since Kelvin | ||
| 372 | |||
| 373 | Support: Kelvin, Rankline, Curie, Tesla, Fermi, Kepler, Maxwell, Pascal, | ||
| 374 | Volta, Turing, Ampere | ||
| 375 | */ | ||
| 376 | #define NV_MC_BOOT_0 0x00000000 | ||
| 377 | #define NV_CHIP_ID_GP106 0x136 // Discrete GeForce GTX 1060 | ||
| 378 | #define NV_CHIP_ID_GV11B 0x15B // Jetson Xavier embedded GPU | ||
| 379 | #define NV_CHIP_ID_KEPLER 0x0E0 | ||
| 380 | #define NV_CHIP_ID_VOLTA 0x140 | ||
| 381 | |||
| 382 | inline static const char* ARCH2NAME(uint32_t arch) { | ||
| 383 | switch (arch) { | ||
| 384 | case 0x01: | ||
| 385 | return "Celsius"; | ||
| 386 | case 0x02: | ||
| 387 | return "Kelvin"; | ||
| 388 | case 0x03: | ||
| 389 | return "Rankline"; | ||
| 390 | case 0x04: | ||
| 391 | case 0x06: // 0x06 is (nForce 6XX integrated only) | ||
| 392 | return "Curie"; | ||
| 393 | // 0x07 is unused/skipped | ||
| 394 | case 0x05: // First Tesla card was released before the nForce 6XX | ||
| 395 | case 0x08: | ||
| 396 | case 0x09: | ||
| 397 | case 0x0A: | ||
| 398 | return "Tesla"; | ||
| 399 | // 0x0B is unused/skipped | ||
| 400 | case 0x0C: | ||
| 401 | case 0x0D: | ||
| 402 | return "Fermi"; | ||
| 403 | case 0x0E: | ||
| 404 | case 0x0F: | ||
| 405 | case 0x11: | ||
| 406 | return "Kepler"; | ||
| 407 | case 0x12: | ||
| 408 | return "Maxwell"; | ||
| 409 | case 0x13: | ||
| 410 | return "Pascal"; | ||
| 411 | case 0x14: | ||
| 412 | case 0x15: // Volta integrated | ||
| 413 | return "Volta"; | ||
| 414 | case 0x16: | ||
| 415 | return "Turing"; | ||
| 416 | case 0x17: | ||
| 417 | return "Ampere"; | ||
| 418 | case 0x18: | ||
| 419 | case 0x19: | ||
| 420 | return "Hopper (?) or Lovelace (?)"; | ||
| 421 | default: | ||
| 422 | if (arch < 0x19) | ||
| 423 | return "[unknown historical architecture]"; | ||
| 424 | else | ||
| 425 | return "[future]"; | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | typedef union { | ||
| 430 | // Fields as defined in the NVIDIA reference | ||
| 431 | struct { | ||
| 432 | uint32_t minor_revision:4; | ||
| 433 | uint32_t major_revision:4; | ||
| 434 | uint32_t reserved:4; | ||
| 435 | uint32_t padding0:8; | ||
| 436 | uint32_t implementation:4; | ||
| 437 | uint32_t architecture:5; | ||
| 438 | uint32_t padding1:3; | ||
| 439 | } __attribute__((packed)); | ||
| 440 | uint32_t raw; | ||
| 441 | // Arch << 4 + impl is also often used | ||
| 442 | struct { | ||
| 443 | uint32_t padding2:20; | ||
| 444 | uint32_t chip_id:9; | ||
| 445 | uint32_t padding3:3; | ||
| 446 | } __attribute__((packed)); | ||
| 447 | } mc_boot_0_t; | ||
| 448 | |||
| 449 | enum DEVICE_INFO_TYPE {INFO_TYPE_NOT_VALID = 0, INFO_TYPE_DATA = 1, INFO_TYPE_ENUM = 2, INFO_TYPE_ENGINE_TYPE = 3}; | ||
| 450 | enum ENGINE_TYPES { | ||
| 451 | ENGINE_GRAPHICS = 0, // GRAPHICS [/compute] | ||
| 452 | ENGINE_COPY0 = 1, // [raw/physical] COPY #0 | ||
| 453 | ENGINE_COPY1 = 2, // [raw/physical] COPY #1 | ||
| 454 | ENGINE_COPY2 = 3, // [raw/physical] COPY #2 | ||
| 455 | |||
| 456 | ENGINE_MSPDEC = 8, // Picture DECoder | ||
| 457 | ENGINE_MSPPP = 9, // [Video] Post Processing | ||
| 458 | ENGINE_MSVLD = 10, // [Video] Variable Length Decoder | ||
| 459 | ENGINE_MSENC = 11, // [Video] ENCoding | ||
| 460 | ENGINE_VIC = 12, // Video Image Compositor | ||
| 461 | ENGINE_SEC = 13, // SEquenCer [?] | ||
| 462 | ENGINE_NVENC0 = 14, // Nvidia Video ENCoder #0 | ||
| 463 | ENGINE_NVENC1 = 15, // Nvidia Video ENCoder #1 | ||
| 464 | ENGINE_NVDEC = 16, // Nvidia Video DECoder | ||
| 465 | |||
| 466 | ENGINE_IOCTRL = 18, // I/O ConTRoLler [of NVLINK at least] | ||
| 467 | ENGINE_LCE = 19, // Logical Copy Engine | ||
| 468 | ENGINE_GSP = 20, // Gpu System Processor | ||
| 469 | ENGINE_NVJPG = 21, // NVidia JPeG [Decoder] (Ampere+) | ||
| 470 | }; | ||
| 471 | #define ENGINE_TYPES_LEN 22 | ||
| 472 | static const char* const ENGINE_TYPES_NAMES[ENGINE_TYPES_LEN] = { | ||
| 473 | "Graphics/Compute", | ||
| 474 | "COPY0", | ||
| 475 | "COPY1", | ||
| 476 | "COPY2", | ||
| 477 | "Unknown Engine ID#4", | ||
| 478 | "Unknown Engine ID#5", | ||
| 479 | "Unknown Engine ID#6", | ||
| 480 | "Unknown Engine ID#7", | ||
| 481 | "MSPDEC: Picture Decoder", | ||
| 482 | "MSPPP: Post Processing", | ||
| 483 | "MSVLD: Variable Length Decoder", | ||
| 484 | "MSENC: Encoder", | ||
| 485 | "VIC: Video Image Compositor", | ||
| 486 | "SEC: Sequencer", | ||
| 487 | "NVENC0: NVIDIA Video Encoder #0", | ||
| 488 | "NVENC1: NVIDIA Video Encoder #1", | ||
| 489 | "NVDEC: NVIDIA Video Decoder", | ||
| 490 | "Unknown Engine ID#17", | ||
| 491 | "IOCTRL: I/O Controller", | ||
| 492 | "LCE: Logical Copy Engine", | ||
| 493 | "GSP: GPU System Processor", | ||
| 494 | "NVJPG: NVIDIA JPEG Decoder", | ||
| 495 | }; | ||
| 496 | |||
| 497 | /* GPU engine information and control register offsets | ||
| 498 | Each engine is described by one or more entries (terminated by an entry with | ||
| 499 | the `has_next_entry` flag unset) in the fixed-size PTOP_DEVICE_INFO table. A | ||
| 500 | typical device, such as the graphics/compute engine and any copy engines, are | ||
| 501 | described by three entries, one of each type. | ||
| 502 | |||
| 503 | The PTOP_DEVICE_INFO table is sparsely populated (entries of type | ||
| 504 | INFO_TYPE_NOT_VALID may be intermingled with valid entries), so any traversal | ||
| 505 | code should check all NV_PTOP_DEVICE_INFO__SIZE_1 entries and not terminate | ||
| 506 | upon reaching the first entry of INFO_TYPE_NOT_VALID. | ||
| 507 | |||
| 508 | INFO_TYPE : Is this a DATA, ENUM, or ENGINE_TYPE table entry? | ||
| 509 | HAS_NEXT_ENTRY : Does the following entry refer to the same engine? | ||
| 510 | |||
| 511 | == INFO_TYPE_DATA fields == | ||
| 512 | PRI_BASE : BAR0 base = (PRI_BASE << 12) aka 4k aligned. | ||
| 513 | INST_ID : "Note that some instanced [engines] (such as logical copy | ||
| 514 | engines aka LCE) share a PRI_BASE across all [engines] of | ||
| 515 | the same engine type; such [engines] require an additional | ||
| 516 | offset: instanced base = BAR0 base + stride * INST_ID. | ||
| 517 | FAULT_ID_IS_VALID : Does this engine have its own bind point and fault ID | ||
| 518 | with the MMU? | ||
| 519 | FAULT_ID : "The MMU fault id used by this [engine]. These IDs | ||
| 520 | correspond to the NV_PFAULT_MMU_ENG_ID define list." | ||
| 521 | |||
| 522 | == INFO_TYPE_ENUM fields == | ||
| 523 | ENGINE_IS_VALID : Is this engine a host engine? | ||
| 524 | ENGINE_ENUM : "[T]he host engine ID for the current [engine] if it is | ||
| 525 | a host engine, meaning Host can send methods to the | ||
| 526 | engine. This id is used to index into any register array | ||
| 527 | whose __SIZE_1 is equal to NV_HOST_NUM_ENGINES. A given | ||
| 528 | ENGINE_ENUM can be present for at most one device in the | ||
| 529 | table. Devices corresponding to all ENGINE_ENUM ids 0 | ||
| 530 | through NV_HOST_NUM_ENGINES - 1 must be present in the | ||
| 531 | device info table." | ||
| 532 | RUNLIST_IS_VALID : Is this engine a host engine with a runlist? | ||
| 533 | RUNLIST_ENUM : "[T]he Host runlist ID on which methods for the current | ||
| 534 | [engine] should be submitted... The runlist id is used to | ||
| 535 | index into any register array whose __SIZE_1 is equal to | ||
| 536 | NV_HOST_NUM_RUNLISTS. [Engines] corresponding to all | ||
| 537 | RUNLIST_ENUM ids 0 through NV_HOST_NUM_RUNLISTS - 1 must | ||
| 538 | be present in the device info table." | ||
| 539 | INTR_IS_VALID : Does this device have an interrupt? | ||
| 540 | INTR_ENUM : Interrupt ID for use with "the NV_PMC_INTR_*_DEVICE | ||
| 541 | register bitfields." | ||
| 542 | RESET_IS_VALID : Does this engine have a reset ID? | ||
| 543 | RESET_ENUM : Reset ID for use indexing the "NV_PMC_ENABLE_DEVICE(i) | ||
| 544 | and NV_PMC_ELPG_ENABLE_DEVICE(i) register bitfields." | ||
| 545 | |||
| 546 | == INFO_TYPE_ENGINE_TYPE fields == | ||
| 547 | ENGINE_TYPE : What type of engine is this? (see ENGINE_TYPES_NAMES) | ||
| 548 | |||
| 549 | Support: Kepler, Maxwell, Pascal, Volta, Ampere | ||
| 550 | See dev_top.ref.txt of NVIDIA's open-gpu-doc for more info. | ||
| 551 | */ | ||
| 552 | #define NV_PTOP_DEVICE_INFO(i) (0x00022700+(i)*4) | ||
| 553 | #define NV_PTOP_DEVICE_INFO__SIZE_1 64 | ||
| 554 | typedef union { | ||
| 555 | // DATA type fields | ||
| 556 | struct { | ||
| 557 | enum DEVICE_INFO_TYPE info_type:2; | ||
| 558 | bool fault_id_is_valid:1; | ||
| 559 | uint32_t fault_id:7; | ||
| 560 | uint32_t padding0:2; | ||
| 561 | uint32_t pri_base:12; | ||
| 562 | uint32_t padding1:2; | ||
| 563 | uint32_t inst_id:4; | ||
| 564 | uint32_t is_not_enum2:1; | ||
| 565 | bool has_next_entry:1; | ||
| 566 | } __attribute__((packed)); | ||
| 567 | // ENUM type fields | ||
| 568 | struct { | ||
| 569 | uint32_t padding2:2; | ||
| 570 | bool reset_is_valid:1; | ||
| 571 | bool intr_is_valid:1; | ||
| 572 | bool runlist_is_valid:1; | ||
| 573 | bool engine_is_valid:1; | ||
| 574 | uint32_t padding3:3; | ||
| 575 | uint32_t reset_enum:5; | ||
| 576 | uint32_t padding4:1; | ||
| 577 | uint32_t intr_enum:5; | ||
| 578 | uint32_t padding5:1; | ||
| 579 | uint32_t runlist_enum:4; | ||
| 580 | uint32_t padding6:1; | ||
| 581 | uint32_t engine_enum:4; | ||
| 582 | uint32_t padding7:2; | ||
| 583 | } __attribute__((packed)); | ||
| 584 | // ENGINE_TYPE type fields | ||
| 585 | struct { | ||
| 586 | uint32_t padding8:2; | ||
| 587 | enum ENGINE_TYPES engine_type:29; | ||
| 588 | uint32_t padding9:1; | ||
| 589 | } __attribute__((packed)); | ||
| 590 | uint32_t raw; | ||
| 591 | } ptop_device_info_t; | ||
| 592 | |||
| 593 | #define NV_PTOP_SCAL_NUM_GPCS 0x00022430 | ||
| 594 | #define NV_PTOP_SCAL_NUM_TPC_PER_GPC 0x00022434 | ||
| 595 | #define NV_PTOP_SCAL_NUM_CES 0x00022444 | ||
| 596 | // PCE_MAP is Volta+ only | ||
| 597 | #define NV_CE_PCE_MAP 0x00104028 | ||
| 598 | |||
| 599 | // GPC and TPC masks | ||
| 600 | // Support: Maxwell+ | ||
| 601 | #define NV_FUSE_GPC 0x00021c1c | ||
| 602 | #define NV_FUSE_TPC_FOR_GPC(i) (0x00021c38+(i)*4) | ||
| 603 | |||
| 604 | /* Location of the 1Kb instance block with page tables for BAR1 and BAR2. | ||
| 605 | Support: Fermi+ (?), Pascal | ||
| 606 | */ | ||
| 607 | #define NV_PBUS_BAR1_BLOCK 0x00001704 | ||
| 608 | #define NV_PBUS_BAR2_BLOCK 0x00001714 | ||
| 609 | typedef union { | ||
| 610 | struct { | ||
| 611 | uint32_t ptr:28; | ||
| 612 | enum INST_TARGET target:2; | ||
| 613 | uint32_t padding0:1; | ||
| 614 | bool is_virtual:1; | ||
| 615 | } __attribute__((packed)); | ||
| 616 | uint32_t raw; | ||
| 617 | struct { | ||
| 618 | uint32_t map:30; | ||
| 619 | uint32_t padding1:2; | ||
| 620 | } __attribute__((packed)); | ||
| 621 | } bar_config_block_t; | ||
| 622 | |||
| 623 | /* BAR0 PRAMIN (Private RAM Instance) window configuration | ||
| 624 | |||
| 625 | BASE : Base of window >> 16 in [TARGET] virtual address space | ||
| 626 | TARGET : Which address space BASE points into | ||
| 627 | |||
| 628 | Note: This seems to be set to 0x0bff00000 - 0x0c0000000 at least sometimes | ||
| 629 | |||
| 630 | Support: Tesla 2.0, Fermi, Kepler, Maxwell, Pascal, Turing, Ampere | ||
| 631 | */ | ||
| 632 | #define NV_PBUS_BAR0_WINDOW 0x00001700 | ||
| 633 | #define NV_PRAMIN 0x00700000 // Goes until 0x00800000 (1MB window) | ||
| 634 | #define NV_PRAMIN_LEN 0x00100000 | ||
| 635 | typedef union { | ||
| 636 | struct { | ||
| 637 | uint32_t base:24; | ||
| 638 | enum INST_TARGET target:2; | ||
| 639 | uint32_t padding0:6; | ||
| 640 | } __attribute__((packed)); | ||
| 641 | uint32_t raw; | ||
| 642 | } bar0_window_t; | ||
| 643 | |||
| 644 | // Support: Tesla 2.0, Fermi, Kepler, Maxwell, Pascal, Turing, Ampere | ||
| 645 | #define NV_PRAMIN_PDB_CONFIG_OFF 0x200 | ||
| 646 | typedef union { | ||
| 647 | struct { | ||
| 648 | uint32_t target:2; | ||
| 649 | uint32_t vol:1; | ||
| 650 | uint32_t padding0:1; | ||
| 651 | uint32_t fault_replay_tex:1; | ||
| 652 | uint32_t fault_replay_gcc:1; | ||
| 653 | uint32_t padding1:4; | ||
| 654 | bool is_ver2:1; | ||
| 655 | bool is_64k_big_page:1; // 128Kb otherwise | ||
| 656 | uint32_t page_dir_lo:20; | ||
| 657 | uint32_t page_dir_hi:32; | ||
| 658 | } __attribute__((packed)); | ||
| 659 | uint64_t raw; | ||
| 660 | } page_dir_config_t; | ||
| 661 | |||
| 662 | /* Page directory entry | ||
| 663 | |||
| 664 | Note: Format changed with Pascal (how?) | ||
| 665 | |||
| 666 | Support: Pascal, Volta, Turing, Ampere | ||
| 667 | */ | ||
| 668 | // FIXME: PDE/PTEs are actually 64 bits =S | ||
| 669 | // Important: Aperture keys are different with PDEs | ||
| 670 | enum PD_TARGET { | ||
| 671 | PD_AND_TARGET_INVALID = 0, // b000 | ||
| 672 | PD_AND_TARGET_VID_MEM = 2, // b010 | ||
| 673 | PD_AND_TARGET_SYS_MEM_COHERENT = 4, // b100 | ||
| 674 | PD_AND_TARGET_SYS_MEM_NONCOHERENT = 6, // b110 | ||
| 675 | PTE_AND_TARGET_VID_MEM = 1, // b001 | ||
| 676 | PTE_AND_TARGET_PEER = 3, // b011 | ||
| 677 | PTE_AND_TARGET_SYS_MEM_COHERENT = 5, // b101 | ||
| 678 | PTE_AND_TARGET_SYS_MEM_NONCOHERENT = 7, // b111 | ||
| 679 | }; | ||
| 680 | static inline char* pd_target_to_text(enum PD_TARGET t) { | ||
| 681 | switch (t) { | ||
| 682 | case PD_AND_TARGET_INVALID: | ||
| 683 | return "INVALID"; | ||
| 684 | case PD_AND_TARGET_VID_MEM: | ||
| 685 | case PTE_AND_TARGET_VID_MEM: | ||
| 686 | return "VID_MEM"; | ||
| 687 | case PTE_AND_TARGET_PEER: | ||
| 688 | return "PEER"; | ||
| 689 | case PD_AND_TARGET_SYS_MEM_COHERENT: | ||
| 690 | case PTE_AND_TARGET_SYS_MEM_COHERENT: | ||
| 691 | return "SYS_MEM_COHERENT"; | ||
| 692 | case PD_AND_TARGET_SYS_MEM_NONCOHERENT: | ||
| 693 | case PTE_AND_TARGET_SYS_MEM_NONCOHERENT: | ||
| 694 | return "SYS_MEM_NONCOHERENT"; | ||
| 695 | default: | ||
| 696 | printk(KERN_WARNING "[nvdebug] Invalid aperture!\n"); | ||
| 697 | return NULL; | ||
| 698 | } | ||
| 699 | } | ||
| 700 | |||
| 701 | // PDE/PTE V2 type | ||
| 702 | // Note: As the meaning of target (bits 2:1) changes depending on if the entry | ||
| 703 | // is a PTE or not, this combines them into a single target field to | ||
| 704 | // simplify comparisons. | ||
| 705 | // Support: Pascal, Turing, Ampere | ||
| 706 | typedef union { | ||
| 707 | // Page Directory Entry (PDE) | ||
| 708 | struct { | ||
| 709 | bool is_pte:1; | ||
| 710 | uint32_t __target:2; | ||
| 711 | bool is_volatile:1; | ||
| 712 | uint32_t padding1:4; | ||
| 713 | uint32_t addr:24; | ||
| 714 | } __attribute__((packed)); | ||
| 715 | // Page Table Entry (PTE) | ||
| 716 | struct { | ||
| 717 | enum PD_TARGET target:3; | ||
| 718 | uint32_t __is_volatile:1; | ||
| 719 | bool is_encrypted:1; | ||
| 720 | bool is_privileged:1; | ||
| 721 | bool is_readonly:1; | ||
| 722 | bool atomics_disabled:1; | ||
| 723 | uint32_t __addr:24; | ||
| 724 | } __attribute__((packed)); | ||
| 725 | uint32_t raw; | ||
| 726 | } page_dir_entry_t; | ||
| 727 | |||
| 728 | // PDE/PTE V1 types | ||
| 729 | // Support: Fermi, Kepler, Maxwell | ||
| 730 | enum V1_PD_TARGET { | ||
| 731 | PD_TARGET_INVALID = 0, | ||
| 732 | PD_TARGET_VID_MEM = 1, | ||
| 733 | PD_TARGET_SYS_MEM_COHERENT = 2, | ||
| 734 | PD_TARGET_SYS_MEM_NONCOHERENT = 3, | ||
| 735 | }; | ||
| 736 | // Page Directory Entry (PDE) | ||
| 737 | typedef union { | ||
| 738 | // Large page fields | ||
| 739 | struct { | ||
| 740 | // 0:32 | ||
| 741 | enum V1_PD_TARGET target:2; | ||
| 742 | uint32_t padding0:2; | ||
| 743 | uint64_t addr:28; // May be wider? | ||
| 744 | // 32:63 | ||
| 745 | uint32_t padding2:3; | ||
| 746 | uint32_t is_volatile:1; // Might have counted wrong? | ||
| 747 | uint32_t padding3:28; | ||
| 748 | } __attribute__((packed)); | ||
| 749 | // Small page fields | ||
| 750 | struct { | ||
| 751 | // 0:32 | ||
| 752 | uint32_t padding00:32; | ||
| 753 | // 32:63 | ||
| 754 | enum V1_PD_TARGET alt_target:2; | ||
| 755 | uint32_t alt_is_volatile:1; // Might have counted wrong? | ||
| 756 | uint32_t padding03:1; | ||
| 757 | uint64_t alt_addr:28; | ||
| 758 | } __attribute__((packed)); | ||
| 759 | uint64_t raw; | ||
| 760 | } page_dir_entry_v1_t; | ||
| 761 | // Page Table Entry (PTE) | ||
| 762 | // Reconstructed from info in Jetson nvgpu driver | ||
| 763 | typedef union { | ||
| 764 | struct { | ||
| 765 | // 0:32 | ||
| 766 | bool is_present:1; | ||
| 767 | bool is_privileged:1; | ||
| 768 | bool is_readonly:1; | ||
| 769 | uint32_t padding0:1; | ||
| 770 | uint64_t addr:28; | ||
| 771 | // 32:63 | ||
| 772 | bool is_volatile:1; | ||
| 773 | enum INST_TARGET:2; | ||
| 774 | uint32_t padding1:1; | ||
| 775 | uint32_t kind:8; | ||
| 776 | uint32_t comptag:17; | ||
| 777 | uint32_t padding2:1; | ||
| 778 | bool is_read_disabled:1; | ||
| 779 | bool is_write_disabled:1; | ||
| 780 | } __attribute__((packed)); | ||
| 781 | uint64_t raw; | ||
| 782 | } page_tbl_entry_v1_t; | ||
| 783 | //enum V0_PDE_TYPE {NOT_PRESENT = 0, PAGE_64K = 1, PAGE_16K = 2, PAGE_4K = 3}; | ||
| 784 | //enum V0_PDE_SIZE {PDE_SZ_128K = 0, PDE_SZ_32K = 1, PDE_SZ_16K = 2, PDE_SZ_8K = 3}; | ||
| 785 | //static const int V0_PDE_SIZE2NUM[4] = {128*1024, 32*1024, 16*1024, 8*1024}; | ||
| 786 | /* PDE V0 (nv50/Tesla) | ||
| 787 | typedef union { | ||
| 788 | struct { | ||
| 789 | enum V1_PDE_TYPE type:2; | ||
| 790 | enum INST_TARGET target:2; | ||
| 791 | uint32_t padding0:1; | ||
| 792 | enum V1_PDE_SIZE sublevel_size:2; | ||
| 793 | uint32_t padding1:5; | ||
| 794 | uint32_t addr:28; | ||
| 795 | uint32_t padding2:24; | ||
| 796 | } __attribute__((packed)); | ||
| 797 | uint64_t raw; | ||
| 798 | } page_dir_entry_v1_t;*/ | ||
| 799 | /* PTE V0 (nv50) | ||
| 800 | typedef union { | ||
| 801 | struct { | ||
| 802 | bool is_present:1; | ||
| 803 | uint32_t padding3:2; | ||
| 804 | bool is_readonly:1; | ||
| 805 | enum INST_TARGET target:2; | ||
| 806 | bool is_privileged:1; | ||
| 807 | uint32_t contig_blk_sz:3; | ||
| 808 | uint32_t padding4:2; | ||
| 809 | uint32_t addr:28; | ||
| 810 | uint32_t storage_type:7; // ??? | ||
| 811 | uint32_t compression_mode:2; // ??? | ||
| 812 | uint32_t compression_tag:12; // ??? | ||
| 813 | bool is_long_partition_cycle:1; // ??? | ||
| 814 | bool is_encrypted:1; | ||
| 815 | uint32_t padding5:1; | ||
| 816 | } __attribute__((packed)); | ||
| 817 | uint64_t raw; | ||
| 818 | } page_tbl_entry_v1_t;*/ | ||
| 819 | |||
| 304 | // TODO(jbakita): Maybe put the above GPU types in a different file. | 820 | // TODO(jbakita): Maybe put the above GPU types in a different file. |
| 305 | 821 | ||
| 306 | #define for_chan_in_tsg(chan, tsg) \ | 822 | #define NV_PCI_VENDOR 0x10de |
| 307 | for (chan = (struct runlist_chan*)(tsg + 1); \ | 823 | struct nvdebug_state { |
| 308 | (void*)chan < (void*)(tsg + 1) + sizeof(struct runlist_chan) * tsg->tsg_length; \ | 824 | // Pointer to the mapped base address of the GPU control registers (obtained |
| 309 | chan++) | 825 | // via ioremap() originally). For embedded GPUs, we extract this from their |
| 826 | // struct nvgpu_os_linux. For discrete GPUs, we create our own mapping of | ||
| 827 | // BAR0 with pci_iomap(). Access via nvgpu_readl/writel functions. | ||
| 828 | void __iomem *regs; | ||
| 829 | // Depending on the architecture, BAR2 or BAR3 are used to access PRAMIN | ||
| 830 | union { | ||
| 831 | void __iomem *bar2; | ||
| 832 | void __iomem *bar3; | ||
| 833 | }; | ||
| 834 | int chip_id; | ||
| 835 | // Additional state from the built-in driver. Only set iff | ||
| 836 | // chip_id == NV_CHIP_ID_GV11B | ||
| 837 | struct gk20a *g; | ||
| 838 | // Pointer to PCI device needed for pci_iounmap | ||
| 839 | struct pci_dev *pcid; | ||
| 840 | }; | ||
| 841 | |||
| 842 | /*const struct runlist_funcs { | ||
| 843 | u8 size; | ||
| 844 | enum ENTRY_TYPE (*entry_type)(struct nvdebug_state *, void *); | ||
| 845 | uint32_t (*chid)(struct nvdebug_state *, void *); | ||
| 846 | uint32_t (*inst_ptr_lo)(struct nvdebug_state *, void *); | ||
| 847 | enum INST_TARGET (*inst_target)(struct nvdebug_state *, void *): | ||
| 848 | uint32_t (*tsgid)(struct nvdebug_state *, void *); | ||
| 849 | uint32_t (*timeslice_scale)(struct nvdebug_state *, void *); | ||
| 850 | uint32_t (*timeslice_timeout)(struct nvdebug_state *, void *); | ||
| 851 | uint32_t (*tsg_length)(struct nvdebug_state *, void *); | ||
| 852 | };*/ | ||
| 853 | |||
| 854 | // This disgusting macro is a crutch to work around the fact that runlists were | ||
| 855 | // different prior to Volta. | ||
| 856 | #define VERSIONED_RL_ACCESSOR(_ENTRY_TYPE, type, prop) \ | ||
| 857 | __attribute__((unused)) \ | ||
| 858 | static type (prop)(const struct nvdebug_state *g, const void *raw) { \ | ||
| 859 | if (g->chip_id > NV_CHIP_ID_VOLTA) { \ | ||
| 860 | const struct gv100_runlist_ ## _ENTRY_TYPE *entry = (struct gv100_runlist_ ## _ENTRY_TYPE*)raw; \ | ||
| 861 | return entry->prop; \ | ||
| 862 | } else if (g->chip_id > NV_CHIP_ID_KEPLER) { \ | ||
| 863 | const struct gk110_runlist_ ## _ENTRY_TYPE *entry = (struct gk110_runlist_ ## _ENTRY_TYPE*)raw; \ | ||
| 864 | return entry->prop; \ | ||
| 865 | } else { \ | ||
| 866 | printk(KERN_WARNING "[nvdebug] " #prop " unavailable on GPU ID %x, which is older than Kepler.\n", g->chip_id); \ | ||
| 867 | return (type)0; \ | ||
| 868 | } \ | ||
| 869 | } | ||
| 870 | |||
| 871 | VERSIONED_RL_ACCESSOR(chan, uint32_t, chid); | ||
| 872 | VERSIONED_RL_ACCESSOR(chan, uint32_t, inst_ptr_lo); | ||
| 873 | VERSIONED_RL_ACCESSOR(chan, enum INST_TARGET, inst_target); | ||
| 874 | VERSIONED_RL_ACCESSOR(tsg, uint32_t, tsgid); | ||
| 875 | VERSIONED_RL_ACCESSOR(tsg, enum ENTRY_TYPE, entry_type); | ||
| 876 | VERSIONED_RL_ACCESSOR(tsg, uint32_t, timeslice_scale); | ||
| 877 | VERSIONED_RL_ACCESSOR(tsg, uint32_t, timeslice_timeout); | ||
| 878 | VERSIONED_RL_ACCESSOR(tsg, uint32_t, tsg_length); | ||
| 310 | 879 | ||
| 311 | #define next_tsg(tsg) \ | 880 | |
| 312 | (void*)(tsg + 1) + sizeof(struct runlist_chan) * tsg->tsg_length | 881 | #define NV_RL_ENTRY_SIZE(g) \ |
| 882 | ((g)->chip_id >= NV_CHIP_ID_VOLTA ? sizeof(struct gv100_runlist_tsg) : sizeof(struct gk110_runlist_tsg)) | ||
| 883 | |||
| 884 | #define for_chan_in_tsg(g, chan, tsg) \ | ||
| 885 | for (chan = (typeof(chan))(((u8*)tsg) + NV_RL_ENTRY_SIZE(g)); \ | ||
| 886 | (u8*)chan < ((u8*)tsg) + (1 + tsg_length(g, tsg)) * NV_RL_ENTRY_SIZE(g); \ | ||
| 887 | chan = (typeof(chan))(((u8*)chan) + NV_RL_ENTRY_SIZE(g))) | ||
| 888 | |||
| 889 | #define next_tsg(g, tsg) \ | ||
| 890 | (typeof(tsg))((u8*)(tsg) + NV_RL_ENTRY_SIZE(g) * (tsg_length(g, tsg) + 1)) | ||
| 313 | 891 | ||
| 314 | struct runlist_iter { | 892 | struct runlist_iter { |
| 315 | struct entry_tsg *curr_tsg; | 893 | // Pointer to either a TSG or channel entry (they're the same size) |
| 894 | void *curr_entry; | ||
| 895 | // This should be set to tsg_length when a TSG is reached, and | ||
| 896 | // decremented as each subsequent channel is printed. This allows us to | ||
| 897 | // track which channel are and are not part of the TSG. | ||
| 898 | int channels_left_in_tsg; | ||
| 899 | // Total runlist length, etc | ||
| 316 | runlist_info_t rl_info; | 900 | runlist_info_t rl_info; |
| 317 | }; | 901 | }; |
| 318 | 902 | ||
| 903 | #define NVDEBUG_MAX_DEVICES 8 | ||
| 904 | extern struct nvdebug_state g_nvdebug_state[NVDEBUG_MAX_DEVICES]; | ||
| 905 | |||
| 319 | // Defined in runlist.c | 906 | // Defined in runlist.c |
| 320 | struct gk20a* get_live_gk20a(void); | 907 | int get_runlist_iter(struct nvdebug_state *g, int rl_id, struct runlist_iter *rl_iter); |
| 321 | int get_runlist_iter(struct runlist_iter *rl_iter); | 908 | int preempt_tsg(struct nvdebug_state *g, uint32_t tsg_id); |
| 322 | int preempt_tsg(uint32_t tsg_id); | 909 | |
| 910 | // Defined in mmu.c | ||
| 911 | uint32_t vram2PRAMIN(struct nvdebug_state *g, uint64_t addr); | ||
| 912 | void __iomem *phy2PRAMIN(struct nvdebug_state* g, uint64_t phy); | ||
| 913 | uint64_t search_page_directory( | ||
| 914 | struct nvdebug_state *g, | ||
| 915 | void __iomem *pde_offset, | ||
| 916 | void __iomem *(*off2addr)(struct nvdebug_state*, uint64_t), | ||
| 917 | uint64_t addr_to_find); | ||
| 918 | uint64_t search_v1_page_directory( | ||
| 919 | struct nvdebug_state *g, | ||
| 920 | void __iomem *pde_offset, | ||
| 921 | void __iomem *(*off2addr)(struct nvdebug_state*, uint64_t), | ||
| 922 | uint64_t addr_to_find); | ||
| 923 | |||
| 323 | 924 | ||
| 324 | static inline struct gk20a *get_gk20a(struct device *dev) { | 925 | static inline struct gk20a *get_gk20a(struct device *dev) { |
| 325 | // XXX: Only works because gk20a* is the first member of gk20a_platform | 926 | // XXX: Only works because gk20a* is the first member of gk20a_platform |
| 326 | return *((struct gk20a**)dev_get_drvdata(dev)); | 927 | return *((struct gk20a**)dev_get_drvdata(dev)); |
| 327 | } | 928 | } |
| 328 | 929 | ||
| 329 | // Functionally identical to nvgpu_readl() | 930 | // We us the data field of the proc_dir_entry ("PDE" in this function) to store |
| 931 | // our index into the g_nvdebug_state array | ||
| 932 | static inline int seq2gpuidx(struct seq_file *s) { | ||
| 933 | const struct file *f = s->file; | ||
| 934 | return (uintptr_t)PDE_DATA(file_inode(f)); | ||
| 935 | } | ||
| 936 | static inline int file2gpuidx(const struct file *f) { | ||
| 937 | return (uintptr_t)PDE_DATA(file_inode(f)); | ||
| 938 | } | ||
| 939 | static inline int file2parentgpuidx(const struct file *f) { | ||
| 940 | // Should be safe to call on ProcFS entries, as our parent should (?) | ||
| 941 | // still exist if we're called. If not, there are worse races in this | ||
| 942 | // module. | ||
| 943 | return (uintptr_t)PDE_DATA(file_dentry(f)->d_parent->d_inode); | ||
| 944 | } | ||
| 945 | |||
| 946 | #define gk20a_regs(gk20a) (container_of(gk20a, struct nvgpu_os_linux, g)->regs) | ||
| 947 | |||
| 948 | // Similar to nvgpu_readl() | ||
| 330 | // (except we don't try to resolve situations where regs is NULL) | 949 | // (except we don't try to resolve situations where regs is NULL) |
| 331 | static inline u32 nvdebug_readl(struct gk20a* g, u32 r) { | 950 | static inline u32 nvdebug_readl(struct nvdebug_state *s, u32 r) { |
| 332 | struct nvgpu_os_linux* g_os = container_of(g, struct nvgpu_os_linux, g); | 951 | if (unlikely(!s->regs || (s->g && !gk20a_regs(s->g)))) { |
| 333 | if (unlikely(!g_os->regs)) { | 952 | printk(KERN_ERR "[nvdebug] Attempted nvgpu_readl on non-existent registers!\n"); |
| 334 | printk(KERN_ERR "[nvdebug] Attempted nvgpu_readl on non-existent registers!\n"); | 953 | return -1; |
| 335 | return -1; | 954 | } |
| 336 | } | 955 | return readl(s->regs + r); |
| 337 | return readl(g_os->regs + r); | ||
| 338 | } | 956 | } |
| 339 | 957 | ||
| 340 | // quadword version of nvdebug_readl() | 958 | // quadword version of nvdebug_readl() |
| 341 | static inline u64 nvdebug_readq(struct gk20a* g, u32 r) { | 959 | static inline u64 nvdebug_readq(struct nvdebug_state *s, u32 r) { |
| 342 | struct nvgpu_os_linux* g_os = container_of(g, struct nvgpu_os_linux, g); | 960 | u64 ret; |
| 343 | u64 ret; | 961 | if (unlikely(!s->regs || (s->g && !gk20a_regs(s->g)))) { |
| 344 | if (unlikely(!g_os->regs)) { | 962 | printk(KERN_ERR "[nvdebug] Attempted nvgpu_readl on non-existent registers!\n"); |
| 345 | printk(KERN_ERR "[nvdebug] Attempted nvgpu_readl on non-existent registers!\n"); | 963 | return -1; |
| 346 | return -1; | 964 | } |
| 347 | } | ||
| 348 | // readq seems to always return the uppermost 32 bits as 0, so workaround with readl | 965 | // readq seems to always return the uppermost 32 bits as 0, so workaround with readl |
| 349 | ret = readl(g_os->regs + r); | 966 | ret = readl(s->regs + r); |
| 350 | ret |= ((u64)readl(g_os->regs + r + 4)) << 32; | 967 | ret |= ((u64)readl(s->regs + r + 4)) << 32; |
| 351 | return ret; | 968 | return ret; |
| 352 | } | 969 | } |
| 353 | 970 | ||
| 354 | // Functionally identical to nvgpu_writel() | 971 | // Similar to nvgpu_writel() |
| 355 | static inline void nvdebug_writel(struct gk20a* g, u32 r, u32 v) { | 972 | static inline void nvdebug_writel(struct nvdebug_state *s, u32 r, u32 v) { |
| 356 | struct nvgpu_os_linux* g_os = container_of(g, struct nvgpu_os_linux, g); | 973 | if (unlikely(!s->regs || (s->g && !gk20a_regs(s->g)))) { |
| 357 | if (unlikely(!g_os->regs)) { | 974 | printk(KERN_ERR "[nvdebug] Attempted nvgpu_writel on non-existent registers!\n"); |
| 975 | return; | ||
| 976 | } | ||
| 977 | writel_relaxed(v, s->regs + r); | ||
| 978 | wmb(); | ||
| 979 | } | ||
| 980 | |||
| 981 | // quadword version of nvdebug_writel() | ||
| 982 | // XXX: This probably doesn't work XXX: Untested | ||
| 983 | static inline void nvdebug_writeq(struct nvdebug_state *s, u32 r, u64 v) { | ||
| 984 | if (unlikely(!s->regs || (s->g && !gk20a_regs(s->g)))) { | ||
| 358 | printk(KERN_ERR "[nvdebug] Attempted nvgpu_writel on non-existent registers!\n"); | 985 | printk(KERN_ERR "[nvdebug] Attempted nvgpu_writel on non-existent registers!\n"); |
| 359 | return; | 986 | return; |
| 360 | } | 987 | } |
| 361 | writel_relaxed(v, g_os->regs + r); | 988 | writeq_relaxed(v, s->regs + r); |
| 362 | wmb(); | 989 | wmb(); |
| 363 | } | 990 | } |
diff --git a/nvdebug_entry.c b/nvdebug_entry.c index 0854b8b..695b5fd 100644 --- a/nvdebug_entry.c +++ b/nvdebug_entry.c | |||
| @@ -2,64 +2,282 @@ | |||
| 2 | * SPDX-License-Identifier: MIT | 2 | * SPDX-License-Identifier: MIT |
| 3 | */ | 3 | */ |
| 4 | 4 | ||
| 5 | /* TODO | ||
| 6 | * - Add sysfs trigger for a preemption | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/device.h> // For struct device, bus_find_device*(), struct bus_type | 5 | #include <linux/device.h> // For struct device, bus_find_device*(), struct bus_type |
| 6 | #include <linux/interrupt.h> // For hooking the nvidia driver interrupts | ||
| 10 | #include <linux/kernel.h> | 7 | #include <linux/kernel.h> |
| 11 | #include <linux/module.h> | 8 | #include <linux/module.h> |
| 12 | #include <linux/proc_fs.h> // So we can set up entries in /proc | 9 | #include <linux/pci.h> // For PCI device scanning |
| 10 | #include <linux/proc_fs.h> // So we can set up entries in /proc | ||
| 13 | 11 | ||
| 14 | #include "nvdebug.h" | 12 | #include "nvdebug.h" |
| 13 | #include "stubs.h" | ||
| 15 | 14 | ||
| 16 | // LIAR. But without this we can't use GPL-only exported symbols like | 15 | // MIT is GPL-compatible. We need to be GPL-compatible for symbols like |
| 17 | // platform_bus_type or bus_find_device_by_name... | 16 | // platform_bus_type or bus_find_device_by_name... |
| 18 | MODULE_LICENSE("GPL"); | 17 | MODULE_LICENSE("Dual MIT/GPL"); |
| 19 | MODULE_AUTHOR("Joshua Bakita"); | 18 | MODULE_AUTHOR("Joshua Bakita"); |
| 20 | MODULE_DESCRIPTION("A scheduling debugging module for NVIDIA GPUs"); | 19 | MODULE_DESCRIPTION("A scheduling debugging module for NVIDIA GPUs"); |
| 21 | MODULE_SOFTDEP("pre: nvgpu"); // We only support the Jetson boards for now | ||
| 22 | 20 | ||
| 23 | extern const struct file_operations runlist_file_ops; | 21 | extern const struct file_operations runlist_file_ops; |
| 24 | extern const struct file_operations preempt_tsg_file_ops; | 22 | extern const struct file_operations preempt_tsg_file_ops; |
| 25 | extern const struct file_operations disable_channel_file_ops; | 23 | extern const struct file_operations disable_channel_file_ops; |
| 26 | extern const struct file_operations enable_channel_file_ops; | 24 | extern const struct file_operations enable_channel_file_ops; |
| 27 | extern const struct file_operations switch_to_tsg_file_ops; | 25 | extern const struct file_operations switch_to_tsg_file_ops; |
| 26 | extern const struct file_operations device_info_file_ops; | ||
| 27 | extern const struct file_operations nvdebug_read_reg32_file_ops; | ||
| 28 | |||
| 29 | // Bus types are global symbols in the kernel | ||
| 30 | extern struct bus_type platform_bus_type; | ||
| 31 | struct nvdebug_state g_nvdebug_state[NVDEBUG_MAX_DEVICES]; | ||
| 32 | unsigned int g_nvdebug_devices = 0; | ||
| 33 | |||
| 34 | // TEMP | ||
| 35 | irqreturn_t nvdebug_irq_tap(int irq_num, void * dev) { | ||
| 36 | printk(KERN_INFO "[nvdebug] Interrupt tap triggered on IRQ %d.\n", irq_num); | ||
| 37 | return IRQ_NONE; // We don't actually handle any interrupts. Pass them on. | ||
| 38 | } | ||
| 39 | |||
| 40 | // Find any and all NVIDIA GPUs in the system | ||
| 41 | // Note: This function fails if any of them are in a bad state | ||
| 42 | int probe_and_cache_device(void) { | ||
| 43 | // platform bus (SoC) iterators | ||
| 44 | struct device *dev = NULL; | ||
| 45 | struct device *temp_dev; | ||
| 46 | // PCI search iterator and search query | ||
| 47 | struct pci_dev *pcid = NULL; | ||
| 48 | // This query pattern is mirrored off nouveau | ||
| 49 | struct pci_device_id query = { | ||
| 50 | .vendor = NV_PCI_VENDOR, // Match NVIDIA devices | ||
| 51 | .device = PCI_ANY_ID, | ||
| 52 | .subvendor = PCI_ANY_ID, | ||
| 53 | .subdevice = PCI_ANY_ID, | ||
| 54 | .class_mask = 0xff << 16, | ||
| 55 | .class = PCI_BASE_CLASS_DISPLAY << 16, // Match display devs | ||
| 56 | }; | ||
| 57 | int i = 0; | ||
| 58 | // Search the platform bus for the first device that matches our name | ||
| 59 | // Search for GV10B (Jetson Xavier) | ||
| 60 | while (!dev && (temp_dev = bus_find_device_by_name(&platform_bus_type, dev, "17000000.gv11b"))) | ||
| 61 | dev = temp_dev; | ||
| 62 | // Search for GP10B (Jetson TX2) | ||
| 63 | while (!dev && (temp_dev = bus_find_device_by_name(&platform_bus_type, dev, "17000000.gp10b"))) | ||
| 64 | dev = temp_dev; | ||
| 65 | // TODO: Support other platform bus devices (gk20a, gm20b) | ||
| 66 | if (dev) { | ||
| 67 | struct nvgpu_os_linux *l; | ||
| 68 | mc_boot_0_t ids; | ||
| 69 | g_nvdebug_state[i].g = get_gk20a(dev); | ||
| 70 | l = container_of(g_nvdebug_state[i].g, struct nvgpu_os_linux, g); | ||
| 71 | g_nvdebug_state[i].regs = l->regs; | ||
| 72 | if (!g_nvdebug_state[i].regs) | ||
| 73 | return -EADDRNOTAVAIL; | ||
| 74 | ids.raw = nvdebug_readl(&g_nvdebug_state[i], NV_MC_BOOT_0); | ||
| 75 | if (ids.raw == -1) | ||
| 76 | return -EADDRNOTAVAIL; | ||
| 77 | g_nvdebug_state[i].chip_id = ids.chip_id; | ||
| 78 | printk(KERN_INFO "[nvdebug] Chip ID %x (architecture %s) detected on platform bus and initialized.", | ||
| 79 | ids.chip_id, ARCH2NAME(ids.architecture)); | ||
| 80 | i++; | ||
| 81 | } | ||
| 82 | // Search the PCI bus and iterate through all matches | ||
| 83 | // FIXME: State rollback | ||
| 84 | while ((pcid = pci_get_dev_by_id(&query, pcid)) && i < NVDEBUG_MAX_DEVICES) { | ||
| 85 | mc_boot_0_t ids; | ||
| 86 | g_nvdebug_state[i].g = NULL; | ||
| 87 | // Map BAR0 (GPU control registers) | ||
| 88 | g_nvdebug_state[i].regs = pci_iomap(pcid, 0, 0); | ||
| 89 | if (!g_nvdebug_state[i].regs) { | ||
| 90 | pci_err(pcid, "[nvdebug] Unable to map BAR0 on this GPU\n"); | ||
| 91 | return -EADDRNOTAVAIL; | ||
| 92 | } | ||
| 93 | // Map BAR3 (CPU-accessible mappings of GPU DRAM) | ||
| 94 | g_nvdebug_state[i].bar3 = pci_iomap(pcid, 3, 0); | ||
| 95 | // Try mapping only the lower half of BAR3 on fail | ||
| 96 | // (vesafb may map the top half for display) | ||
| 97 | if (!g_nvdebug_state[i].bar3) | ||
| 98 | g_nvdebug_state[i].bar3 = pci_iomap(pcid, 3, pci_resource_len(pcid, 3)/2); | ||
| 99 | g_nvdebug_state[i].pcid = pcid; | ||
| 100 | ids.raw = nvdebug_readl(&g_nvdebug_state[i], NV_MC_BOOT_0); | ||
| 101 | if (ids.raw == -1) { | ||
| 102 | pci_err(pcid, "[nvdebug] Unable to read config from Master Controller on this GPU\n"); | ||
| 103 | return -EADDRNOTAVAIL; | ||
| 104 | } | ||
| 105 | g_nvdebug_state[i].chip_id = ids.chip_id; | ||
| 106 | printk(KERN_INFO "[nvdebug] Chip ID %x (architecture %s) detected on PCI bus and initialized.", | ||
| 107 | ids.chip_id, ARCH2NAME(ids.architecture)); | ||
| 108 | // TEMP | ||
| 109 | if (request_irq(pcid->irq, nvdebug_irq_tap, IRQF_SHARED, "nvdebug tap", pcid)) { | ||
| 110 | printk(KERN_WARNING "[nvdebug] Unable to initialize IRQ tap\n"); | ||
| 111 | } | ||
| 112 | i++; | ||
| 113 | } | ||
| 114 | // Return the number of devices we found | ||
| 115 | if (i > 0) | ||
| 116 | return i; | ||
| 117 | return -ENODEV; | ||
| 118 | } | ||
| 119 | |||
| 120 | // Create files `/proc/gpu#/runlist#`, world readable | ||
| 121 | int create_runlist_files(int device_id, struct proc_dir_entry *dir) { | ||
| 122 | ptop_device_info_t info; | ||
| 123 | struct proc_dir_entry *rl_entry; | ||
| 124 | int i, rl_id; | ||
| 125 | char runlist_name[12]; | ||
| 126 | int max_rl_id = 0; // Always at least one runlist | ||
| 127 | // Figure out how many runlists there are by checking the device info | ||
| 128 | // registers. Runlists are always numbered sequentially, so we just have | ||
| 129 | // to find the highest-valued one and add 1 to get the number of runlists. | ||
| 130 | for (i = 0; i < NV_PTOP_DEVICE_INFO__SIZE_1; i++) { | ||
| 131 | info.raw = nvdebug_readl(&g_nvdebug_state[device_id], NV_PTOP_DEVICE_INFO(i)); | ||
| 132 | if (info.info_type != INFO_TYPE_ENUM || !info.runlist_is_valid) | ||
| 133 | continue; | ||
| 134 | if (info.runlist_enum > max_rl_id) | ||
| 135 | max_rl_id = info.runlist_enum; | ||
| 136 | } | ||
| 137 | // Create files to read each runlist. The read handling code looks at the | ||
| 138 | // PDE_DATA associated with the file to determine what the runlist ID is. | ||
| 139 | for (rl_id = 0; rl_id <= max_rl_id; rl_id++) { | ||
| 140 | snprintf(runlist_name, 12, "runlist%d", rl_id); | ||
| 141 | rl_entry = proc_create_data( | ||
| 142 | runlist_name, 0444, dir, &runlist_file_ops, | ||
| 143 | (void*)(uintptr_t)rl_id); | ||
| 144 | if (!rl_entry) | ||
| 145 | return -ENOMEM; | ||
| 146 | } | ||
| 147 | return 0; | ||
| 148 | } | ||
| 149 | |||
| 150 | // Create files /proc/gpu# | ||
| 151 | // TODO: Don't run this on unsupported GPUs | ||
| 152 | int create_tpc_mask_files(int device_id, struct proc_dir_entry *dir) { | ||
| 153 | char file_name[20]; | ||
| 154 | int i; | ||
| 155 | struct proc_dir_entry *gpc_tpc_mask_entry; | ||
| 156 | // Get a bitmask of which GPCs are disabled | ||
| 157 | uint32_t gpcs_mask = nvdebug_readl(&g_nvdebug_state[device_id], NV_FUSE_GPC); | ||
| 158 | // Get maximum number of enabled GPCs for this chip | ||
| 159 | uint32_t max_gpcs = nvdebug_readl(&g_nvdebug_state[device_id], NV_PTOP_SCAL_NUM_GPCS); | ||
| 160 | // For each enabled GPC, expose a mask of disabled TPCs | ||
| 161 | for (i = 0; i < max_gpcs; i++) { | ||
| 162 | // Do nothing if GPC is disabled | ||
| 163 | if ((1 << i) & gpcs_mask) | ||
| 164 | continue; | ||
| 165 | // If GPC is enabled, create an entry to read disabled TPCs mask | ||
| 166 | snprintf(file_name, 20, "gpc%d_tpc_mask", i); | ||
| 167 | gpc_tpc_mask_entry = proc_create_data( | ||
| 168 | file_name, 0444, dir, &nvdebug_read_reg32_file_ops, | ||
| 169 | (void*)(uintptr_t)NV_FUSE_TPC_FOR_GPC(i)); | ||
| 170 | if (!gpc_tpc_mask_entry) | ||
| 171 | return -ENOMEM; | ||
| 172 | } | ||
| 173 | return 0; | ||
| 174 | } | ||
| 28 | 175 | ||
| 29 | int __init nvdebug_init(void) { | 176 | int __init nvdebug_init(void) { |
| 30 | struct proc_dir_entry *rl_entry, *preempt_entry, *disable_channel_entry, | 177 | struct proc_dir_entry *dir, *preempt_entry, *disable_channel_entry, |
| 31 | *enable_channel_entry, *switch_to_tsg_entry; | 178 | *enable_channel_entry, *switch_to_tsg_entry, *device_info_entry, |
| 32 | // Create file `/proc/preempt_tsg`, world readable | 179 | *num_gpcs_entry; |
| 33 | rl_entry = proc_create("runlist", 0444, NULL, &runlist_file_ops); | 180 | int rl_create_err, tpc_masks_create_err; |
| 34 | // Create file `/proc/preempt_tsg`, world writable | 181 | // Check that an NVIDIA GPU is present and initialize g_nvdebug_state |
| 35 | preempt_entry = proc_create("preempt_tsg", 0222, NULL, &preempt_tsg_file_ops); | 182 | int res = probe_and_cache_device(); |
| 36 | // Create file `/proc/disable_channel`, world writable | 183 | if (res < 0) |
| 37 | disable_channel_entry = proc_create("disable_channel", 0222, NULL, &disable_channel_file_ops); | 184 | return res; |
| 38 | // Create file `/proc/enable_channel`, world writable | 185 | g_nvdebug_devices = res; |
| 39 | enable_channel_entry = proc_create("enable_channel", 0222, NULL, &enable_channel_file_ops); | 186 | // Create seperate ProcFS directories for each gpu |
| 40 | // Create file `/proc/switch_to_tsg`, world writable | 187 | while (res--) { |
| 41 | switch_to_tsg_entry = proc_create("switch_to_tsg", 0222, NULL, &switch_to_tsg_file_ops); | 188 | char device_id_str[7]; |
| 42 | // ProcFS entry creation only fails if out of memory | 189 | uintptr_t device_id = res; // This is uintptr as we abuse the *data field on proc_dir_entry to store the GPU id |
| 43 | if (!rl_entry || !preempt_entry || !disable_channel_entry || !enable_channel_entry || !switch_to_tsg_entry) { | 190 | // Create directory /proc/gpu# where # is the GPU number |
| 44 | remove_proc_entry("runlist", NULL); | 191 | snprintf(device_id_str, 7, "gpu%ld", device_id); |
| 45 | remove_proc_entry("preempt_tsg", NULL); | 192 | if (!(dir = proc_mkdir_data(device_id_str, 0555, NULL, (void*)device_id))) |
| 46 | remove_proc_entry("disable_channel", NULL); | 193 | goto out_nomem; |
| 47 | remove_proc_entry("enable_channel", NULL); | 194 | // Create files `/proc/gpu#/runlist#`, world readable |
| 48 | remove_proc_entry("switch_to_tsg", NULL); | 195 | rl_create_err = create_runlist_files(device_id, dir); |
| 49 | printk(KERN_ERR "[nvdebug] Unable to initialize procfs entries!\n"); | 196 | // Create files `/proc/gpu#/gpc#_tpc_mask`, world readable |
| 50 | return -ENOMEM; | 197 | tpc_masks_create_err = create_tpc_mask_files(device_id, dir); |
| 198 | // Create file `/proc/gpu#/preempt_tsg`, world writable | ||
| 199 | preempt_entry = proc_create_data( | ||
| 200 | "preempt_tsg", 0222, dir, &preempt_tsg_file_ops, | ||
| 201 | (void*)device_id); | ||
| 202 | // Create file `/proc/gpu#/disable_channel`, world writable | ||
| 203 | disable_channel_entry = proc_create_data( | ||
| 204 | "disable_channel", 0222, dir, &disable_channel_file_ops, | ||
| 205 | (void*)device_id); | ||
| 206 | // Create file `/proc/gpu#/enable_channel`, world writable | ||
| 207 | enable_channel_entry = proc_create_data( | ||
| 208 | "enable_channel", 0222, dir, &enable_channel_file_ops, | ||
| 209 | (void*)device_id); | ||
| 210 | // Create file `/proc/gpu#/switch_to_tsg`, world writable | ||
| 211 | switch_to_tsg_entry = proc_create_data( | ||
| 212 | "switch_to_tsg", 0222, dir, &switch_to_tsg_file_ops, | ||
| 213 | (void*)device_id); | ||
| 214 | // Create file `/proc/gpu#/device_info`, world readable | ||
| 215 | device_info_entry = proc_create_data( | ||
| 216 | "device_info", 0444, dir, &device_info_file_ops, | ||
| 217 | (void*)device_id); | ||
| 218 | // Create file `/proc/gpu#/num_gpcs`, world readable | ||
| 219 | num_gpcs_entry = proc_create_data( | ||
| 220 | "num_gpcs", 0444, dir, &nvdebug_read_reg32_file_ops, | ||
| 221 | (void*)NV_PTOP_SCAL_NUM_GPCS); | ||
| 222 | // Create file `/proc/gpu#/num_tpc_per_gpc`, world readable | ||
| 223 | num_gpcs_entry = proc_create_data( | ||
| 224 | "num_tpc_per_gpc", 0444, dir, &nvdebug_read_reg32_file_ops, | ||
| 225 | (void*)NV_PTOP_SCAL_NUM_TPC_PER_GPC); | ||
| 226 | // Create file `/proc/gpu#/num_ces`, world readable | ||
| 227 | num_gpcs_entry = proc_create_data( | ||
| 228 | "num_ces", 0444, dir, &nvdebug_read_reg32_file_ops, | ||
| 229 | (void*)NV_PTOP_SCAL_NUM_CES); | ||
| 230 | // Create file `/proc/gpu#/num_ces`, world readable | ||
| 231 | num_gpcs_entry = proc_create_data( | ||
| 232 | "gpc_mask", 0444, dir, &nvdebug_read_reg32_file_ops, | ||
| 233 | (void*)NV_FUSE_GPC); | ||
| 234 | // In both nouveau and nvgpu, the PCE_MAP register is only available on Volta+ | ||
| 235 | if (g_nvdebug_state[res].chip_id >= NV_CHIP_ID_VOLTA) { | ||
| 236 | // TODO: Redo to num_pces | ||
| 237 | // Create file `/proc/gpu#/pce_map`, world readable | ||
| 238 | num_gpcs_entry = proc_create_data( | ||
| 239 | "pce_map", 0444, dir, &nvdebug_read_reg32_file_ops, | ||
| 240 | (void*)NV_CE_PCE_MAP); | ||
| 241 | } | ||
| 242 | // ProcFS entry creation only fails if out of memory | ||
| 243 | if (rl_create_err || tpc_masks_create_err || !preempt_entry || | ||
| 244 | !disable_channel_entry || !enable_channel_entry || | ||
| 245 | !switch_to_tsg_entry || !device_info_entry || !num_gpcs_entry) | ||
| 246 | goto out_nomem; | ||
| 51 | } | 247 | } |
| 248 | // (See Makefile if you want to know the origin of GIT_HASH.) | ||
| 52 | printk(KERN_INFO "[nvdebug] Module version "GIT_HASH" initialized\n"); | 249 | printk(KERN_INFO "[nvdebug] Module version "GIT_HASH" initialized\n"); |
| 53 | return 0; | 250 | return 0; |
| 251 | out_nomem: | ||
| 252 | // Make sure to clear all ProcFS directories on error | ||
| 253 | while (res < g_nvdebug_devices) { | ||
| 254 | char device_id_str[7]; | ||
| 255 | snprintf(device_id_str, 7, "gpu%d", res); | ||
| 256 | remove_proc_subtree(device_id_str, NULL); | ||
| 257 | res++; | ||
| 258 | } | ||
| 259 | return -ENOMEM; | ||
| 54 | } | 260 | } |
| 55 | 261 | ||
| 56 | static void __exit nvdebug_exit(void) { | 262 | static void __exit nvdebug_exit(void) { |
| 57 | remove_proc_entry("runlist", NULL); | 263 | struct nvdebug_state *g; |
| 58 | remove_proc_entry("preempt_tsg", NULL); | 264 | // Deinitialize each device |
| 59 | remove_proc_entry("disable_channel", NULL); | 265 | while (g_nvdebug_devices--) { |
| 60 | remove_proc_entry("enable_channel", NULL); | 266 | // Remove procfs directory |
| 61 | remove_proc_entry("switch_to_tsg", NULL); | 267 | char device_id[7]; |
| 62 | printk(KERN_INFO "[nvdebug] Exiting...\n"); | 268 | snprintf(device_id, 7, "gpu%d", g_nvdebug_devices); |
| 269 | remove_proc_subtree(device_id, NULL); | ||
| 270 | // Free BAR mappings | ||
| 271 | g = &g_nvdebug_state[g_nvdebug_devices]; | ||
| 272 | if (g && g->regs) | ||
| 273 | pci_iounmap(g->pcid, g->regs); | ||
| 274 | if (g && g->bar2) | ||
| 275 | pci_iounmap(g->pcid, g->bar2); | ||
| 276 | // TEMP | ||
| 277 | free_irq(g->pcid->irq, g->pcid); | ||
| 278 | printk(KERN_INFO "[nvdebug] Chip ID %x deinitialized.", g->chip_id); | ||
| 279 | } | ||
| 280 | printk(KERN_INFO "[nvdebug] Module exit complete.\n"); | ||
| 63 | } | 281 | } |
| 64 | 282 | ||
| 65 | module_init(nvdebug_init); | 283 | module_init(nvdebug_init); |
| @@ -1,122 +1,127 @@ | |||
| 1 | #include <linux/device.h> // For struct device, bus_find_device*(), struct bus_type | ||
| 2 | //#include <linux/iommu.h> // For struct iommu_domain | ||
| 3 | #include <linux/kernel.h> // Kernel types | 1 | #include <linux/kernel.h> // Kernel types |
| 4 | #include <asm/io.h> | ||
| 5 | 2 | ||
| 6 | #include "nvdebug.h" | 3 | #include "nvdebug.h" |
| 7 | 4 | ||
| 8 | // Bus types are global symbols in the kernel | ||
| 9 | extern struct bus_type platform_bus_type; | ||
| 10 | |||
| 11 | struct gk20a* get_live_gk20a(void) { | ||
| 12 | struct device *dev = NULL; | ||
| 13 | struct device *temp_dev; | ||
| 14 | struct gk20a *g; | ||
| 15 | struct nvgpu_os_linux *l; | ||
| 16 | // Get the last device that matches our name | ||
| 17 | while ((temp_dev = bus_find_device_by_name(&platform_bus_type, dev, "17000000.gv11b"))) { | ||
| 18 | dev = temp_dev; | ||
| 19 | printk(KERN_INFO "[nvdebug] Found a matching device %s\n", dev_name(dev)); | ||
| 20 | } | ||
| 21 | if (!dev) | ||
| 22 | return NULL; | ||
| 23 | g = get_gk20a(dev); | ||
| 24 | // The address pointed to `regs` + NV_PFIFO_RUNLIST_BASE seems to not be: | ||
| 25 | // - A GPU address (type is sysmem_coherent) | ||
| 26 | // - A physical address (dereferencing after ioremap crashes) | ||
| 27 | // - A kernel virtual address (dereferencing segfaults) | ||
| 28 | // So maybe it's some sort of custom thing? This is an address that the GPU | ||
| 29 | // can use, so it would make most sense for it to be a physical address. | ||
| 30 | // | ||
| 31 | // BUT, it can't possibly be a physical address, as it would refer to an | ||
| 32 | // address greater than the maximum one on our system (by a lot!). | ||
| 33 | // Maybe I'm reading the runlist base wrong? | ||
| 34 | // Aha, the driver calls it runlist_iova. Sounds like runlist I/O virtual | ||
| 35 | // address! So, what's this I/O address space? All I know is that it's what | ||
| 36 | // nvgpu_mem_get_addr() returns. That function returns the result of either: | ||
| 37 | // - gpu_phys_addr which is __nvgpu_sgl_phys on our platform which (?) | ||
| 38 | // converts an IPA to a PA? | ||
| 39 | // - nvgpu_mem_iommu_translate | ||
| 40 | // | ||
| 41 | // The original memory is allocated with nvgpu_dma_alloc_flags_sys(), which | ||
| 42 | // returns SYSMEM. | ||
| 43 | // | ||
| 44 | // To convert a physical address to a IOMMU address, we add a bit | ||
| 45 | // | ||
| 46 | // BUT, it turns out that it IS JUST A PHYSICAL ADDRESS! It wasn't working | ||
| 47 | // before because the GPU had simply gone to sleep and invalidated its | ||
| 48 | // register state, so nvgpu_readl() was simply returning garbage. | ||
| 49 | l = container_of(g, struct nvgpu_os_linux, g); | ||
| 50 | if (!l->regs) | ||
| 51 | return NULL; | ||
| 52 | return g; | ||
| 53 | } | ||
| 54 | |||
| 55 | /* Get runlist head and info (incl. length) | 5 | /* Get runlist head and info (incl. length) |
| 56 | @param rl_iter Location at which to store output | 6 | @param rl_iter Location at which to store output |
| 7 | @param rl_id Which runlist to obtain? | ||
| 57 | */ | 8 | */ |
| 58 | int get_runlist_iter(struct runlist_iter *rl_iter) { | 9 | int get_runlist_iter(struct nvdebug_state *g, int rl_id, struct runlist_iter *rl_iter) { |
| 59 | struct entry_tsg head; | 10 | runlist_base_t rl_base; |
| 60 | runlist_base_t rl_base; | 11 | runlist_info_t rl_info; |
| 61 | runlist_info_t rl_info; | 12 | u64 runlist_iova; |
| 62 | u64 runlist_iova; | 13 | *rl_iter = (struct runlist_iter){0}; |
| 63 | struct gk20a *g = get_live_gk20a(); | 14 | rl_base.raw = nvdebug_readl(g, NV_PFIFO_ENG_RUNLIST_BASE(rl_id)); |
| 64 | if (!g) | 15 | // Check that reads are working |
| 16 | if (rl_base.raw == -1) | ||
| 65 | return -EIO; | 17 | return -EIO; |
| 66 | rl_base.raw = nvdebug_readl(g, NV_PFIFO_RUNLIST_BASE); | 18 | // The address pointed to `regs` + NV_PFIFO_RUNLIST_BASE seems to not be: |
| 67 | rl_info.raw = nvdebug_readl(g, NV_PFIFO_RUNLIST); | 19 | // - A GPU address (type is sysmem_coherent) |
| 68 | runlist_iova = ((u64)rl_base.ptr) << 12; | 20 | // - A physical address (dereferencing after ioremap crashes) |
| 69 | printk(KERN_INFO "[nvdebug] Runlist ptr: %x, type: %d, raw: %x, IOVA: %px\n", | 21 | // - A kernel virtual address (dereferencing segfaults) |
| 70 | rl_base.ptr, rl_base.type, rl_base.raw, (void*)runlist_iova); | 22 | // So maybe it's some sort of custom thing? This is an address that the GPU |
| 71 | // TODO: Support reading video memory | 23 | // can use, so it would make most sense for it to be a physical address. |
| 72 | if (rl_base.type == TARGET_VID_MEM) { | 24 | // |
| 73 | printk(KERN_ERR "[nvdebug] Runlist is located in video memory. Access to video memory is unimplemented."); | 25 | // BUT, it can't possibly be a physical address, as it would refer to an |
| 74 | return -ENOTSUPP; | 26 | // address greater than the maximum one on our system (by a lot!). |
| 27 | // Maybe I'm reading the runlist base wrong? | ||
| 28 | // Aha, the driver calls it runlist_iova. Sounds like runlist I/O virtual | ||
| 29 | // address! So, what's this I/O address space? All I know is that it's what | ||
| 30 | // nvgpu_mem_get_addr() returns. That function returns the result of either: | ||
| 31 | // - gpu_phys_addr which is __nvgpu_sgl_phys on our platform which (?) | ||
| 32 | // converts an IPA to a PA? | ||
| 33 | // - nvgpu_mem_iommu_translate | ||
| 34 | // | ||
| 35 | // The original memory is allocated with nvgpu_dma_alloc_flags_sys(), which | ||
| 36 | // returns SYSMEM. | ||
| 37 | // | ||
| 38 | // To convert a physical address to a IOMMU address, we add a bit | ||
| 39 | // | ||
| 40 | // BUT, it turns out that it IS JUST A PHYSICAL ADDRESS! It wasn't working | ||
| 41 | // before because the GPU had simply gone to sleep and invalidated its | ||
| 42 | // register state, so nvgpu_readl() was simply returning garbage. | ||
| 43 | rl_info.raw = nvdebug_readl(g, NV_PFIFO_ENG_RUNLIST(rl_id)); | ||
| 44 | runlist_iova = ((u64)rl_base.ptr) << 12; | ||
| 45 | printk(KERN_INFO "[nvdebug] Runlist %d @ %llx in %s (config raw: %x)\n", | ||
| 46 | rl_id, runlist_iova, target_to_text(rl_base.target), rl_base.raw); | ||
| 47 | printk(KERN_INFO "[nvdebug] Runlist length %d, ID %d\n", rl_info.len, rl_info.id); | ||
| 48 | // Return early on an empty runlist | ||
| 49 | if (!rl_info.len) | ||
| 50 | return 0; | ||
| 51 | // If the runlist is in VID_MEM, search the BAR2/3 page tables for a mapping | ||
| 52 | if (rl_base.target == TARGET_VID_MEM) { | ||
| 53 | printk(KERN_WARNING "[nvdebug] Runlist is located in video memory. Access to video memory is experimental."); | ||
| 54 | bar_config_block_t bar1_block, bar2_block; | ||
| 55 | bar1_block.raw = nvdebug_readl(g, NV_PBUS_BAR1_BLOCK); | ||
| 56 | printk(KERN_INFO "[nvdebug] BAR1 inst block @ %llx in %s's %s address space.\n", ((u64)bar1_block.ptr) << 12, target_to_text(bar1_block.target), bar1_block.is_virtual ? "virtual" : "physical"); | ||
| 57 | bar2_block.raw = nvdebug_readl(g, NV_PBUS_BAR2_BLOCK); | ||
| 58 | printk(KERN_INFO "[nvdebug] BAR2 inst block @ %llx in %s's %s address space.\n", ((u64)bar2_block.ptr) << 12, target_to_text(bar2_block.target), bar1_block.is_virtual ? "virtual" : "physical"); | ||
| 59 | uint32_t bar_inst_pramin_offset = vram2PRAMIN(g, (uint64_t)bar2_block.ptr << 12); | ||
| 60 | if (!bar_inst_pramin_offset) { | ||
| 61 | printk(KERN_WARNING "[nvdebug] Unable to find instance block for BAR2/3 in the current NV_PRAMIN window. VRAM inaccessible.\n"); | ||
| 62 | return -EOPNOTSUPP; | ||
| 63 | } | ||
| 64 | /* TODO: Support BAR1? | ||
| 65 | bar_inst_pramin_offset = vram2PRAMIN(g, bar1_block.ptr << 12); | ||
| 66 | if (!bar_inst_pramin_offset) { | ||
| 67 | printk(KERN_WARNING "[nvdebug] Unable to find instance block for BAR1 in the current NV_PRAMIN window. VRAM inaccessible.\n"); | ||
| 68 | return -EOPNOTSUPP; | ||
| 69 | }*/ | ||
| 70 | // Instance blocks (size == 1kb) contain many things, but we only care about | ||
| 71 | // the section which describes the location of the page directory (page table) | ||
| 72 | uint32_t bar_pdb_config_pramin_offset = bar_inst_pramin_offset + NV_PRAMIN_PDB_CONFIG_OFF; | ||
| 73 | page_dir_config_t pd_config; | ||
| 74 | pd_config.raw = nvdebug_readq(g, bar_pdb_config_pramin_offset + NV_PRAMIN); | ||
| 75 | uint64_t bar_pdb_vram_addr = pd_config.page_dir_hi; | ||
| 76 | bar_pdb_vram_addr <<= 20; | ||
| 77 | bar_pdb_vram_addr |= pd_config.page_dir_lo; | ||
| 78 | bar_pdb_vram_addr <<= 12; | ||
| 79 | printk(KERN_INFO "[nvdebug] BAR2 PDB @ %llx in %s of version %s (config raw: %llx)\n", bar_pdb_vram_addr, target_to_text(pd_config.target), pd_config.is_ver2 ? "2" : "1", pd_config.raw); | ||
| 80 | // TODO: SYSMEM support for page table location | ||
| 81 | if (pd_config.target != TARGET_VID_MEM) { | ||
| 82 | printk(KERN_WARNING "[nvdebug] BAR2 PDB is in an unsupported location.\n"); | ||
| 83 | return -EOPNOTSUPP; | ||
| 84 | } | ||
| 85 | uint32_t bar_pdb_pramin_offset = vram2PRAMIN(g, bar_pdb_vram_addr); | ||
| 86 | if (!bar_pdb_pramin_offset) { | ||
| 87 | printk(KERN_WARNING "[nvdebug] Unable to find page directory BAR2/3 in the current NV_PRAMIN window. VRAM inaccessible.\n"); | ||
| 88 | return -EOPNOTSUPP; | ||
| 89 | } | ||
| 90 | uint64_t runlist_bar_vaddr; | ||
| 91 | if (pd_config.is_ver2) | ||
| 92 | runlist_bar_vaddr = search_page_directory(g, g->regs + NV_PRAMIN + bar_pdb_pramin_offset, phy2PRAMIN, runlist_iova); | ||
| 93 | else | ||
| 94 | runlist_bar_vaddr = search_v1_page_directory(g, g->regs + NV_PRAMIN + bar_pdb_pramin_offset, phy2PRAMIN, runlist_iova); | ||
| 95 | if (!runlist_bar_vaddr) { | ||
| 96 | printk(KERN_WARNING "[nvdebug] Unable to find runlist mapping in BAR2/3 page tables.\n"); | ||
| 97 | return -EOPNOTSUPP; | ||
| 98 | } | ||
| 99 | printk(KERN_INFO "[nvdebug] Runlist @ %llx in BAR2 virtual address space.\n", runlist_bar_vaddr); | ||
| 100 | /* XXX: Old test code | ||
| 101 | uint32_t bar2_pd_pramin_offset = vram_to_pramin_off(bar2_pd); | ||
| 102 | //walk_pd_subtree(bar2_pd_pramin_offset); | ||
| 103 | uint64_t runlist_bar2_vaddr = search_pd_subtree(bar2_pd_pramin_offset, runlist_iova); | ||
| 104 | page_dir_entry_t pde_0; | ||
| 105 | pde_0.raw = nvdebug_readl(g, NV_PRAMIN + bar2_pd_pramin_offset); | ||
| 106 | uint32_t pde_1 = nvdebug_readl(g, NV_PRAMIN + vram_to_pramin_off(((u64)pde_0.addr) << 12)); | ||
| 107 | uint64_t pde_bar2_vaddr = search_pd_subtree(bar2_pd_pramin_offset, ((u64)pde_0.addr) << 12); | ||
| 108 | uint32_t pde_2 = readl(g->bar3 + pde_bar2_vaddr); | ||
| 109 | printk(KERN_INFO "[nvdebug] PDE0 via PRAMIN: %x, via BAR3: %x\n", pde_1, pde_2); | ||
| 110 | */ | ||
| 111 | if (!g->bar3) { | ||
| 112 | printk(KERN_WARNING "[nvdebug] BAR2/3 not mapped.\n"); | ||
| 113 | return -ENODEV; | ||
| 114 | } | ||
| 115 | rl_iter->curr_entry = g->bar2 + runlist_bar_vaddr; | ||
| 116 | } else { | ||
| 117 | // Directly access the runlist if stored in SYS_MEM (physically addressed) | ||
| 118 | rl_iter->curr_entry = phys_to_virt(runlist_iova); | ||
| 75 | } | 119 | } |
| 76 | // Segfaults | 120 | rl_iter->rl_info = rl_info; |
| 77 | //u32 attempted_read = ioread32(runlist_iova); | 121 | return 0; |
| 78 | //printk(KERN_INFO "[nvdebug] first word of runlist: %0x\n", attempted_read); | ||
| 79 | |||
| 80 | // Errors out | ||
| 81 | //u32* virt_rt_addr = ioremap(phys_rl_addr, sizeof(struct entry_tsg)); | ||
| 82 | //printk(KERN_INFO "[nvdebug] Runlist virt_addr: %px\n", virt_rt_addr); | ||
| 83 | |||
| 84 | /* Overcomplicated? | ||
| 85 | struct iommu_domain *domain = iommu_get_domain_for_dev(dev); | ||
| 86 | if (!domain) { | ||
| 87 | printk(KERN_INFO "[nvdebug] No IOMMU domain!\n"); | ||
| 88 | return -EIO; | ||
| 89 | } | ||
| 90 | u64 phys_addr = platform_bus_type.iommu_ops->iova_to_phys(domain, runlist_iova); | ||
| 91 | printk(KERN_INFO "[nvdebug] Runlist PA: %px\n", phys_addr); | ||
| 92 | */ | ||
| 93 | |||
| 94 | printk(KERN_INFO "[nvdebug] Runlist phys_to_virt: %px\n", (void*)phys_to_virt(runlist_iova)); | ||
| 95 | printk(KERN_INFO "[nvdebug] Runlist *phys_to_virt: %x\n", *(u32*)phys_to_virt(runlist_iova)); | ||
| 96 | head = *(struct entry_tsg*)phys_to_virt(runlist_iova); | ||
| 97 | |||
| 98 | rl_iter->curr_tsg = (struct entry_tsg*)phys_to_virt(runlist_iova); | ||
| 99 | rl_iter->rl_info = rl_info; | ||
| 100 | return 0; | ||
| 101 | //printk(KERN_INFO "[nvdebug] entry_type: %d\n", head.entry_type); | ||
| 102 | //printk(KERN_INFO "[nvdebug] timeslice_scale: %d\n", head.timeslice_scale); | ||
| 103 | //printk(KERN_INFO "[nvdebug] timeslice_timeout: %d\n", head.timeslice_timeout); | ||
| 104 | //printk(KERN_INFO "[nvdebug] tsg_length: %d\n", head.tsg_length); | ||
| 105 | //printk(KERN_INFO "[nvdebug] tsgid: %d\n", head.tsgid); | ||
| 106 | |||
| 107 | //printk(KERN_INFO "[nvdebug] Mem base phys: %p\n", (void*)virt_to_phys((void*)0xffffffc000000000ULL)); | ||
| 108 | //printk(KERN_INFO "[nvdebug] Mem end phys: %p\n", (void*)virt_to_phys((void*)0xffffffc400000000ULL)); | ||
| 109 | //printk(KERN_INFO "[nvdebug] Runlist *virt_addr: %x\n", readl(virt_rt_addr)); // This crashes | ||
| 110 | //read_bytes(&head, virt_rt_addr, sizeof(struct entry_tsg)); | ||
| 111 | /*printk(KERN_INFO "[nvdebug] entry_type: %d\n", head.entry_type); | ||
| 112 | printk(KERN_INFO "[nvdebug] timeslice_scale: %d\n", head.timeslice_scale); | ||
| 113 | printk(KERN_INFO "[nvdebug] timeslice_timeout: %d\n", head.timeslice_timeout); | ||
| 114 | printk(KERN_INFO "[nvdebug] tsg_length: %d\n", head.tsg_length); | ||
| 115 | printk(KERN_INFO "[nvdebug] tsgid: %d\n", head.tsgid); */ | ||
| 116 | } | 122 | } |
| 117 | 123 | ||
| 118 | int preempt_tsg(uint32_t tsg_id) { | 124 | int preempt_tsg(struct nvdebug_state *g, uint32_t tsg_id) { |
| 119 | struct gk20a *g = get_live_gk20a(); | ||
| 120 | runlist_info_t rl_info; | 125 | runlist_info_t rl_info; |
| 121 | pfifo_preempt_t pfifo_preempt; | 126 | pfifo_preempt_t pfifo_preempt; |
| 122 | runlist_disable_t rl_disable; | 127 | runlist_disable_t rl_disable; |
diff --git a/runlist_procfs.c b/runlist_procfs.c index 411f844..a6b0d94 100644 --- a/runlist_procfs.c +++ b/runlist_procfs.c | |||
| @@ -6,7 +6,14 @@ | |||
| 6 | #define RUNLIST_PROCFS_NAME "runlist" | 6 | #define RUNLIST_PROCFS_NAME "runlist" |
| 7 | #define DETAILED_CHANNEL_INFO | 7 | #define DETAILED_CHANNEL_INFO |
| 8 | 8 | ||
| 9 | static int runlist_detail_seq_show_chan(struct seq_file *s, struct gk20a *g, uint32_t chid) { | 9 | /* Print channel details using PCCSR (Programmable Channel Control System RAM?) |
| 10 | * @param s Pointer to state from seq_file subsystem to pass to seq_printf | ||
| 11 | * @param g Pointer to our internal GPU state | ||
| 12 | * @param chid ID of channel to print details on, range [0, 512) | ||
| 13 | * @param prefix Text string to prefix each line with, or empty string | ||
| 14 | */ | ||
| 15 | #ifdef DETAILED_CHANNEL_INFO | ||
| 16 | static int runlist_detail_seq_show_chan(struct seq_file *s, struct nvdebug_state *g, uint32_t chid, char *prefix) { | ||
| 10 | channel_ctrl_t chan; | 17 | channel_ctrl_t chan; |
| 11 | char *loc_txt; | 18 | char *loc_txt; |
| 12 | u64 instance_ptr; | 19 | u64 instance_ptr; |
| @@ -16,23 +23,37 @@ static int runlist_detail_seq_show_chan(struct seq_file *s, struct gk20a *g, uin | |||
| 16 | return -EIO; | 23 | return -EIO; |
| 17 | instance_ptr = chan.inst_ptr; | 24 | instance_ptr = chan.inst_ptr; |
| 18 | instance_ptr <<= 12; | 25 | instance_ptr <<= 12; |
| 19 | seq_printf(s, " +- Channel Info %-4d -+\n", chid); | 26 | seq_printf(s, "%s+- Channel Info %-4d -+\n", prefix, chid); |
| 20 | seq_printf(s, " | Enabled: %d|\n", chan.enable); | 27 | seq_printf(s, "%s| Enabled: %d|\n", prefix, chan.enable); |
| 21 | seq_printf(s, " | Next: %d|\n", chan.next); | 28 | seq_printf(s, "%s| Next: %d|\n", prefix, chan.next); |
| 22 | seq_printf(s, " | Force CTX Reload: %d|\n", chan.force_ctx_reload); | 29 | seq_printf(s, "%s| Force CTX Reload: %d|\n", prefix, chan.force_ctx_reload); |
| 23 | seq_printf(s, " | Enable set: %d|\n", chan.enable_set); | 30 | seq_printf(s, "%s| Enable set: %d|\n", prefix, chan.enable_set); |
| 24 | seq_printf(s, " | Enable clear: %d|\n", chan.enable_clear); | 31 | seq_printf(s, "%s| Enable clear: %d|\n", prefix, chan.enable_clear); |
| 25 | seq_printf(s, " | PBDMA Faulted: %d|\n", chan.pbdma_faulted); | 32 | seq_printf(s, "%s| PBDMA Faulted: %d|\n", prefix, chan.pbdma_faulted); |
| 26 | seq_printf(s, " | ENG Faulted: %d|\n", chan.eng_faulted); | 33 | seq_printf(s, "%s| ENG Faulted: %d|\n", prefix, chan.eng_faulted); |
| 27 | seq_printf(s, " | Status: %2d|\n", chan.status); | 34 | seq_printf(s, "%s| Status: %2d|\n", prefix, chan.status); |
| 28 | seq_printf(s, " | Busy: %d|\n", chan.busy); | 35 | seq_printf(s, "%s| Busy: %d|\n", prefix, chan.busy); |
| 29 | seq_printf(s, " | Instance PTR: |\n"); | 36 | seq_printf(s, "%s| Instance PTR: |\n", prefix); |
| 30 | seq_printf(s, " | %#018llx |\n", instance_ptr); | 37 | seq_printf(s, "%s| %#018llx |\n", prefix, instance_ptr); |
| 31 | seq_printf(s, " | %-20s|\n", loc_txt); | 38 | seq_printf(s, "%s| %-20s|\n", prefix, loc_txt); |
| 32 | seq_printf(s, " | Instance bound: %d|\n", chan.inst_bind); | 39 | seq_printf(s, "%s| Instance bound: %d|\n", prefix, chan.inst_bind); |
| 33 | seq_printf(s, " +---------------------+\n"); | 40 | // START TEMP |
| 41 | // "runlist_id -1 is synonym for the ENGINE_GR_GK20A runlist id" | ||
| 42 | // GR, GRCE, and ASYNC_CE | ||
| 43 | // Note that this appears to be broken?? | ||
| 44 | // Peek into the channel instance RAM | ||
| 45 | if (chan.inst_target == TARGET_SYS_MEM_COHERENT) { | ||
| 46 | seq_printf(s, "%s| Target Engine: %2d|\n", prefix, *(uint32_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*43/*NV_RAMFC_TARGET*/) & 0x1f); | ||
| 47 | seq_printf(s, "%s| PDB LO: %#08x|\n", prefix, *(uint32_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*128/*NV_RAMIN_PAGE_DIR_BASE_LO*/) & 0xfffff000); | ||
| 48 | seq_printf(s, "%s| Num subcontexts: %2ld|\n", prefix, hweight64(*(uint64_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*166/*NV_RAMIN_SC_PDB_VALID*/))); | ||
| 49 | // This appears to be unset on Xavier | ||
| 50 | //seq_printf(s, "%s| PAS ID: %8ld|\n", prefix, *(uint32_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*135/*NV_RAMIN_PASID*/) & 0xfffff); | ||
| 51 | } | ||
| 52 | // END TEMP | ||
| 53 | seq_printf(s, "%s+---------------------+\n", prefix); | ||
| 34 | return 0; | 54 | return 0; |
| 35 | } | 55 | } |
| 56 | #endif | ||
| 36 | 57 | ||
| 37 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0) | 58 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0) |
| 38 | // Bug workaround. See comment in runlist_file_seq_start() | 59 | // Bug workaround. See comment in runlist_file_seq_start() |
| @@ -41,10 +62,14 @@ static loff_t pos_fixup; | |||
| 41 | 62 | ||
| 42 | static void *runlist_file_seq_start(struct seq_file *s, loff_t *pos) { | 63 | static void *runlist_file_seq_start(struct seq_file *s, loff_t *pos) { |
| 43 | static struct runlist_iter rl_iter; | 64 | static struct runlist_iter rl_iter; |
| 65 | struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(s->file)]; | ||
| 44 | // *pos == 0 for first call after read of file | 66 | // *pos == 0 for first call after read of file |
| 45 | if (*pos == 0) { | 67 | if (*pos == 0) { |
| 46 | int err = get_runlist_iter(&rl_iter); | 68 | int err = get_runlist_iter(g, seq2gpuidx(s), &rl_iter); |
| 47 | if (err) | 69 | if (err) |
| 70 | return ERR_PTR(err); | ||
| 71 | // Don't try to print an empty runlist | ||
| 72 | if (rl_iter.rl_info.len <= 0) | ||
| 48 | return NULL; | 73 | return NULL; |
| 49 | return &rl_iter; | 74 | return &rl_iter; |
| 50 | } | 75 | } |
| @@ -68,12 +93,13 @@ static void* runlist_file_seq_next(struct seq_file *s, void *raw_rl_iter, | |||
| 68 | loff_t *pos) { | 93 | loff_t *pos) { |
| 69 | struct runlist_iter* rl_iter = raw_rl_iter; | 94 | struct runlist_iter* rl_iter = raw_rl_iter; |
| 70 | void *ret = NULL; | 95 | void *ret = NULL; |
| 71 | // Advance by one TSG + channels under last TSG | 96 | struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(s->file)]; |
| 72 | *pos += 1 + rl_iter->curr_tsg->tsg_length; | 97 | // Advance by one TSG or channel |
| 98 | (*pos)++; | ||
| 99 | rl_iter->curr_entry += NV_RL_ENTRY_SIZE(g); | ||
| 73 | // Verify we haven't reached the end of the runlist | 100 | // Verify we haven't reached the end of the runlist |
| 74 | // rl_info.len is the num of tsg entries + total num of channel entries | 101 | // rl_info.len is the num of tsg entries + total num of channel entries |
| 75 | if (*pos < rl_iter->rl_info.len) { | 102 | if (*pos < rl_iter->rl_info.len) { |
| 76 | rl_iter->curr_tsg = next_tsg(rl_iter->curr_tsg); | ||
| 77 | ret = rl_iter; | 103 | ret = rl_iter; |
| 78 | } | 104 | } |
| 79 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0) | 105 | #if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0) |
| @@ -88,57 +114,57 @@ static void runlist_file_seq_stop(struct seq_file *s, void *raw_rl_iter) { | |||
| 88 | } | 114 | } |
| 89 | 115 | ||
| 90 | static int runlist_file_seq_show(struct seq_file *s, void *raw_rl_iter) { | 116 | static int runlist_file_seq_show(struct seq_file *s, void *raw_rl_iter) { |
| 91 | struct entry_tsg* tsg = ((struct runlist_iter*)raw_rl_iter)->curr_tsg; | 117 | struct runlist_iter *rl_iter = raw_rl_iter; |
| 92 | struct runlist_chan* chan; | 118 | void *entry = rl_iter->curr_entry; |
| 93 | struct gk20a *g = get_live_gk20a(); | 119 | struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(s->file)]; |
| 94 | if (!g) | 120 | if (entry_type(g, entry) == ENTRY_TYPE_TSG) { |
| 95 | return -EIO; | 121 | if (rl_iter->channels_left_in_tsg) { |
| 96 | if (tsg->entry_type != ENTRY_TYPE_TSG) { | 122 | printk(KERN_WARNING "[nvdebug] Found a TSG @ %px when %d channels were still expected under the previous TSG in the runlist!\n", entry, rl_iter->channels_left_in_tsg); |
| 97 | printk(KERN_WARNING "[nvdebug] Attempted to print non-TSG in tsg print logic!\n"); | 123 | return -EIO; |
| 98 | return -EIO; | 124 | } |
| 99 | } | 125 | rl_iter->channels_left_in_tsg = tsg_length(g, entry); |
| 100 | seq_printf(s, "+---- TSG Entry %-2d----+\n", tsg->tsgid); | 126 | seq_printf(s, "+---- TSG Entry %-3d---+\n", tsgid(g, entry)); |
| 101 | seq_printf(s, "| Scale: %-13d|\n", tsg->timeslice_scale); | 127 | seq_printf(s, "| Scale: %-13d|\n", timeslice_scale(g, entry)); |
| 102 | seq_printf(s, "| Timeout: %-11d|\n", tsg->timeslice_timeout); | 128 | seq_printf(s, "| Timeout: %-11d|\n", timeslice_timeout(g, entry)); |
| 103 | seq_printf(s, "+---------------------+\n"); | 129 | seq_printf(s, "| Length: %-12d|\n", tsg_length(g, entry)); |
| 104 | for_chan_in_tsg(chan, tsg) { | 130 | seq_printf(s, "+---------------------+\n"); |
| 131 | } else { | ||
| 132 | char *indt = ""; | ||
| 105 | #ifndef DETAILED_CHANNEL_INFO | 133 | #ifndef DETAILED_CHANNEL_INFO |
| 106 | char* loc_txt; | 134 | u64 instance_ptr = 0; |
| 107 | u64 instance_ptr; | ||
| 108 | #endif | 135 | #endif |
| 109 | if (chan->entry_type != ENTRY_TYPE_CHAN) { | 136 | if (rl_iter->channels_left_in_tsg) { |
| 110 | printk(KERN_WARNING "[nvdebug] Attempted to print non-channel in channel print logic!\n"); | 137 | indt = " "; |
| 111 | return -EIO; | 138 | rl_iter->channels_left_in_tsg--; |
| 112 | } | 139 | } |
| 113 | #ifdef DETAILED_CHANNEL_INFO | 140 | #ifdef DETAILED_CHANNEL_INFO |
| 114 | runlist_detail_seq_show_chan(s, g, chan->chid); | 141 | runlist_detail_seq_show_chan(s, g, chid(g, entry), indt); |
| 115 | #else | 142 | #else |
| 116 | loc_txt = target_to_text(chan->inst_target); | ||
| 117 | if (!loc_txt) { | ||
| 118 | printk(KERN_WARNING "[nvdebug] Invalid apature in channel print logic!\n"); | ||
| 119 | return -EIO; | ||
| 120 | } | ||
| 121 | // Reconstruct pointer to channel instance block | 143 | // Reconstruct pointer to channel instance block |
| 122 | instance_ptr = chan->inst_ptr_hi; | 144 | if (g->chip_id >= NV_CHIP_ID_VOLTA) { |
| 123 | instance_ptr <<= 32; | 145 | instance_ptr = ((struct gv100_runlist_chan*)entry)->inst_ptr_hi; |
| 124 | instance_ptr |= chan->inst_ptr_lo << 12; | 146 | instance_ptr <<= 32; |
| 125 | 147 | } | |
| 126 | seq_printf(s, " +- Channel Entry %-4d-+\n", chan->chid); | 148 | instance_ptr |= inst_ptr_lo(g, entry) << 12; |
| 127 | seq_printf(s, " | Runqueue Selector: %d|\n", chan->runqueue_selector); | 149 | |
| 128 | seq_printf(s, " | Instance PTR: |\n"); | 150 | seq_printf(s, "%s+- Channel Entry %-4d-+\n", indt, chid(g, entry)); |
| 129 | seq_printf(s, " | %#018llx |\n", instance_ptr); | 151 | if (g->chip_id >= NV_CHIP_ID_VOLTA) |
| 130 | seq_printf(s, " | %-20s|\n", loc_txt); | 152 | seq_printf(s, "%s| Runqueue Selector: %d|\n", indt, |
| 131 | seq_printf(s, " +---------------------+\n"); | 153 | ((struct gv100_runlist_chan*)entry)->runqueue_selector); |
| 154 | seq_printf(s, "%s| Instance PTR: |\n", indt); | ||
| 155 | seq_printf(s, "%s| %#018llx |\n", indt, instance_ptr); | ||
| 156 | seq_printf(s, "%s| %-20s|\n", indt, target_to_text(inst_target(g, entry))); | ||
| 157 | seq_printf(s, "%s+---------------------+\n", indt); | ||
| 132 | #endif | 158 | #endif |
| 133 | } | 159 | } |
| 134 | return 0; | 160 | return 0; |
| 135 | } | 161 | } |
| 136 | 162 | ||
| 137 | static const struct seq_operations runlist_file_seq_ops = { | 163 | static const struct seq_operations runlist_file_seq_ops = { |
| 138 | .start = runlist_file_seq_start, | 164 | .start = runlist_file_seq_start, |
| 139 | .next = runlist_file_seq_next, | 165 | .next = runlist_file_seq_next, |
| 140 | .stop = runlist_file_seq_stop, | 166 | .stop = runlist_file_seq_stop, |
| 141 | .show = runlist_file_seq_show, | 167 | .show = runlist_file_seq_show, |
| 142 | }; | 168 | }; |
| 143 | 169 | ||
| 144 | static int runlist_file_open(struct inode *inode, struct file *f) { | 170 | static int runlist_file_open(struct inode *inode, struct file *f) { |
| @@ -157,6 +183,7 @@ ssize_t preempt_tsg_file_write(struct file *f, const char __user *buffer, | |||
| 157 | uint32_t target_tsgid; | 183 | uint32_t target_tsgid; |
| 158 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec | 184 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec |
| 159 | int err = kstrtou32_from_user(buffer, count, 0, &target_tsgid); | 185 | int err = kstrtou32_from_user(buffer, count, 0, &target_tsgid); |
| 186 | struct nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)]; | ||
| 160 | if (err) | 187 | if (err) |
| 161 | return err; | 188 | return err; |
| 162 | 189 | ||
| @@ -165,7 +192,7 @@ ssize_t preempt_tsg_file_write(struct file *f, const char __user *buffer, | |||
| 165 | return -ERANGE; | 192 | return -ERANGE; |
| 166 | 193 | ||
| 167 | // Execute preemption | 194 | // Execute preemption |
| 168 | err = preempt_tsg(target_tsgid); | 195 | err = preempt_tsg(g, target_tsgid); |
| 169 | if (err) | 196 | if (err) |
| 170 | return err; | 197 | return err; |
| 171 | 198 | ||
| @@ -181,9 +208,9 @@ ssize_t disable_channel_file_write(struct file *f, const char __user *buffer, | |||
| 181 | uint32_t target_channel; | 208 | uint32_t target_channel; |
| 182 | channel_ctrl_t chan; | 209 | channel_ctrl_t chan; |
| 183 | int err; | 210 | int err; |
| 184 | struct gk20a *g = get_live_gk20a(); | 211 | runlist_info_t rl_info; |
| 185 | if (!g) | 212 | runlist_disable_t rl_disable; |
| 186 | return -EIO; | 213 | struct nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)]; |
| 187 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec | 214 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec |
| 188 | err = kstrtou32_from_user(buffer, count, 0, &target_channel); | 215 | err = kstrtou32_from_user(buffer, count, 0, &target_channel); |
| 189 | if (err) | 216 | if (err) |
| @@ -195,7 +222,16 @@ ssize_t disable_channel_file_write(struct file *f, const char __user *buffer, | |||
| 195 | // Disable channel | 222 | // Disable channel |
| 196 | chan.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(target_channel)); | 223 | chan.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(target_channel)); |
| 197 | chan.enable_clear = true; | 224 | chan.enable_clear = true; |
| 225 | // disable sched | ||
| 226 | rl_info.raw = nvdebug_readl(g, NV_PFIFO_RUNLIST); | ||
| 227 | rl_disable.raw = nvdebug_readl(g, NV_PFIFO_SCHED_DISABLE); | ||
| 228 | rl_disable.raw |= BIT(rl_info.id); | ||
| 229 | nvdebug_writel(g, NV_PFIFO_SCHED_DISABLE, rl_disable.raw); | ||
| 230 | // disable chan | ||
| 198 | nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(target_channel), chan.raw); | 231 | nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(target_channel), chan.raw); |
| 232 | // enable sched | ||
| 233 | rl_disable.raw &= ~BIT(rl_info.id); | ||
| 234 | nvdebug_writel(g, NV_PFIFO_SCHED_DISABLE, rl_disable.raw); | ||
| 199 | 235 | ||
| 200 | return count; | 236 | return count; |
| 201 | } | 237 | } |
| @@ -209,9 +245,7 @@ ssize_t enable_channel_file_write(struct file *f, const char __user *buffer, | |||
| 209 | uint32_t target_channel; | 245 | uint32_t target_channel; |
| 210 | channel_ctrl_t chan; | 246 | channel_ctrl_t chan; |
| 211 | int err; | 247 | int err; |
| 212 | struct gk20a *g = get_live_gk20a(); | 248 | struct nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)]; |
| 213 | if (!g) | ||
| 214 | return -EIO; | ||
| 215 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec | 249 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec |
| 216 | err = kstrtou32_from_user(buffer, count, 0, &target_channel); | 250 | err = kstrtou32_from_user(buffer, count, 0, &target_channel); |
| 217 | if (err) | 251 | if (err) |
| @@ -235,14 +269,12 @@ const struct file_operations enable_channel_file_ops = { | |||
| 235 | ssize_t switch_to_tsg_file_write(struct file *f, const char __user *buffer, | 269 | ssize_t switch_to_tsg_file_write(struct file *f, const char __user *buffer, |
| 236 | size_t count, loff_t *off) { | 270 | size_t count, loff_t *off) { |
| 237 | uint32_t target_tsgid; | 271 | uint32_t target_tsgid; |
| 238 | struct runlist_chan* chan; | 272 | struct gv100_runlist_chan* chan; |
| 239 | channel_ctrl_t chan_ctl; | 273 | channel_ctrl_t chan_ctl; |
| 240 | struct runlist_iter rl_iter; | 274 | struct runlist_iter rl_iter; |
| 241 | int err; | 275 | int err; |
| 242 | loff_t pos = 0; | 276 | loff_t pos = 0; |
| 243 | struct gk20a *g = get_live_gk20a(); | 277 | struct nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)]; |
| 244 | if (!g) | ||
| 245 | return -EIO; | ||
| 246 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec | 278 | // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec |
| 247 | err = kstrtou32_from_user(buffer, count, 0, &target_tsgid); | 279 | err = kstrtou32_from_user(buffer, count, 0, &target_tsgid); |
| 248 | if (err) | 280 | if (err) |
| @@ -251,32 +283,34 @@ ssize_t switch_to_tsg_file_write(struct file *f, const char __user *buffer, | |||
| 251 | if (target_tsgid > MAX_TSGID) | 283 | if (target_tsgid > MAX_TSGID) |
| 252 | return -ERANGE; | 284 | return -ERANGE; |
| 253 | 285 | ||
| 254 | err = get_runlist_iter(&rl_iter); | 286 | err = get_runlist_iter(g, 0, &rl_iter); |
| 255 | if (err) | 287 | if (err) |
| 256 | return err; | 288 | return err; |
| 257 | 289 | ||
| 258 | // Iterate through all TSGs | 290 | // Iterate through all TSGs |
| 259 | while (pos < rl_iter.rl_info.len) { | 291 | while (pos < rl_iter.rl_info.len) { |
| 260 | if (rl_iter.curr_tsg->tsgid == target_tsgid) { | 292 | if (tsgid(g, rl_iter.curr_entry) == target_tsgid) { |
| 261 | // Enable channels of target TSG | 293 | // Enable channels of target TSG |
| 262 | for_chan_in_tsg(chan, rl_iter.curr_tsg) { | 294 | for_chan_in_tsg(g, chan, rl_iter.curr_entry) { |
| 263 | chan_ctl.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(chan->chid)); | 295 | chan_ctl.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(chan->chid)); |
| 264 | chan_ctl.enable_set = true; | 296 | chan_ctl.enable_set = true; |
| 265 | nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(chan->chid), chan_ctl.raw); | 297 | nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(chan->chid), chan_ctl.raw); |
| 266 | } | 298 | } |
| 267 | } else { | 299 | } else { |
| 300 | // XXX: Fix for bare channels. Maybe a "for_chan_until_tsg" macro? | ||
| 268 | // Disable all other channels | 301 | // Disable all other channels |
| 269 | for_chan_in_tsg(chan, rl_iter.curr_tsg) { | 302 | // (This is how the Jetson nvgpu driver disables TSGs) |
| 303 | for_chan_in_tsg(g, chan, rl_iter.curr_entry) { | ||
| 270 | chan_ctl.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(chan->chid)); | 304 | chan_ctl.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(chan->chid)); |
| 271 | chan_ctl.enable_clear = true; | 305 | chan_ctl.enable_clear = true; |
| 272 | nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(chan->chid), chan_ctl.raw); | 306 | nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(chan->chid), chan_ctl.raw); |
| 273 | } | 307 | } |
| 274 | } | 308 | } |
| 275 | pos += 1 + rl_iter.curr_tsg->tsg_length; | 309 | pos += 1 + tsg_length(g, rl_iter.curr_entry); |
| 276 | rl_iter.curr_tsg = next_tsg(rl_iter.curr_tsg); | 310 | rl_iter.curr_entry = next_tsg(g, rl_iter.curr_entry); |
| 277 | } | 311 | } |
| 278 | // Switch to next TSG with active channels (should be our TSG) | 312 | // Switch to next TSG with active channels (should be our TSG) |
| 279 | err = preempt_tsg(target_tsgid); | 313 | err = preempt_tsg(g, target_tsgid); |
| 280 | if (err) | 314 | if (err) |
| 281 | return err; | 315 | return err; |
| 282 | 316 | ||
| @@ -0,0 +1,80 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Helpful private functions copied from elsewhere in the kernel tree | ||
| 4 | * DO NOT MODIFY | ||
| 5 | */ | ||
| 6 | #include <linux/version.h> | ||
| 7 | |||
| 8 | // Functions from drivers/pci/pci.h | ||
| 9 | /** | ||
| 10 | * pci_match_one_device - Tell if a PCI device structure has a matching | ||
| 11 | * PCI device id structure | ||
| 12 | * @id: single PCI device id structure to match | ||
| 13 | * @dev: the PCI device structure to match against | ||
| 14 | * | ||
| 15 | * Returns the matching pci_device_id structure or %NULL if there is no match. | ||
| 16 | */ | ||
| 17 | static inline const struct pci_device_id * | ||
| 18 | pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev) | ||
| 19 | { | ||
| 20 | if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) && | ||
| 21 | (id->device == PCI_ANY_ID || id->device == dev->device) && | ||
| 22 | (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) && | ||
| 23 | (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) && | ||
| 24 | !((id->class ^ dev->class) & id->class_mask)) | ||
| 25 | return id; | ||
| 26 | return NULL; | ||
| 27 | } | ||
| 28 | |||
| 29 | // Functions from drivers/pci/search.h | ||
| 30 | #include <linux/device.h> | ||
| 31 | #include <linux/pci.h> | ||
| 32 | extern struct bus_type pci_bus_type; | ||
| 33 | |||
| 34 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5,3,0) | ||
| 35 | static int match_pci_dev_by_id(struct device *dev, void *data) | ||
| 36 | #else | ||
| 37 | static int match_pci_dev_by_id(struct device *dev, const void *data) | ||
| 38 | #endif | ||
| 39 | { | ||
| 40 | struct pci_dev *pdev = to_pci_dev(dev); | ||
| 41 | const struct pci_device_id *id = data; | ||
| 42 | |||
| 43 | if (pci_match_one_device(id, pdev)) | ||
| 44 | return 1; | ||
| 45 | return 0; | ||
| 46 | } | ||
| 47 | |||
| 48 | /* | ||
| 49 | * pci_get_dev_by_id - begin or continue searching for a PCI device by id | ||
| 50 | * @id: pointer to struct pci_device_id to match for the device | ||
| 51 | * @from: Previous PCI device found in search, or %NULL for new search. | ||
| 52 | * | ||
| 53 | * Iterates through the list of known PCI devices. If a PCI device is found | ||
| 54 | * with a matching id a pointer to its device structure is returned, and the | ||
| 55 | * reference count to the device is incremented. Otherwise, %NULL is returned. | ||
| 56 | * A new search is initiated by passing %NULL as the @from argument. Otherwise | ||
| 57 | * if @from is not %NULL, searches continue from next device on the global | ||
| 58 | * list. The reference count for @from is always decremented if it is not | ||
| 59 | * %NULL. | ||
| 60 | * | ||
| 61 | * This is an internal function for use by the other search functions in | ||
| 62 | * this file. | ||
| 63 | */ | ||
| 64 | static struct pci_dev *pci_get_dev_by_id(const struct pci_device_id *id, | ||
| 65 | struct pci_dev *from) | ||
| 66 | { | ||
| 67 | struct device *dev; | ||
| 68 | struct device *dev_start = NULL; | ||
| 69 | struct pci_dev *pdev = NULL; | ||
| 70 | |||
| 71 | if (from) | ||
| 72 | dev_start = &from->dev; | ||
| 73 | dev = bus_find_device(&pci_bus_type, dev_start, (void *)id, | ||
| 74 | match_pci_dev_by_id); | ||
| 75 | if (dev) | ||
| 76 | pdev = to_pci_dev(dev); | ||
| 77 | pci_dev_put(from); | ||
| 78 | return pdev; | ||
| 79 | } | ||
| 80 | |||
