aboutsummaryrefslogtreecommitdiffstats
path: root/mmu.c
diff options
context:
space:
mode:
Diffstat (limited to 'mmu.c')
-rw-r--r--mmu.c414
1 files changed, 411 insertions, 3 deletions
diff --git a/mmu.c b/mmu.c
index ababef5..e2b9a91 100644
--- a/mmu.c
+++ b/mmu.c
@@ -1,9 +1,13 @@
1/* Copyright 2024 Joshua Bakita 1/* Copyright 2024 Joshua Bakita
2 * Helpers to deal with NVIDIA's MMU and associated page tables 2 * Helpers to deal with NVIDIA's MMU and associated page tables
3 */ 3 */
4#include <linux/dma-mapping.h> // dma_map_page() and dma_unmap_page()
4#include <linux/err.h> // ERR_PTR() etc. 5#include <linux/err.h> // ERR_PTR() etc.
6#include <linux/gfp.h> // alloc_pages()
5#include <linux/iommu.h> // iommu_get_domain_for_dev() and iommu_iova_to_phys() 7#include <linux/iommu.h> // iommu_get_domain_for_dev() and iommu_iova_to_phys()
6#include <linux/kernel.h> // Kernel types 8#include <linux/kernel.h> // Kernel types
9#include <linux/list.h> // struct list_head and associated functions
10#include <linux/mm.h> // put_page()
7 11
8#include "nvdebug.h" 12#include "nvdebug.h"
9 13
@@ -15,6 +19,11 @@ int g_verbose = 0;
15#define printk_debug if (g_verbose >= 2) printk 19#define printk_debug if (g_verbose >= 2) printk
16#define printk_info if (g_verbose >= 1) printk 20#define printk_info if (g_verbose >= 1) printk
17 21
22// At least map_page_directory() assumes that pages are 4 KiB
23#if PAGE_SIZE != 4096
24#error nvdebug assumes and requires a 4 KiB page size.
25#endif
26
18/* Convert a page directory (PD) pointer and aperture to be kernel-accessible 27/* Convert a page directory (PD) pointer and aperture to be kernel-accessible
19 28
20 I/O MMU handling inspired by amdgpu_iomem_read() in amdgpu_ttm.c of the 29 I/O MMU handling inspired by amdgpu_iomem_read() in amdgpu_ttm.c of the
@@ -22,7 +31,8 @@ int g_verbose = 0;
22 31
23 @param addr Pointer from page directory entry (PDE) 32 @param addr Pointer from page directory entry (PDE)
24 @param pd_ap PD-type aperture (target address space) for `addr` 33 @param pd_ap PD-type aperture (target address space) for `addr`
25 @return A dereferencable kernel address, or an ERR_PTR-wrapped error 34 @return A dereferencable kernel address, 0 if an I/O MMU is in use and has
35 no available mapping for the bus address, or an ERR_PTR-wrapped error
26 */ 36 */
27static void __iomem *pd_deref(struct nvdebug_state *g, uintptr_t addr, 37static void __iomem *pd_deref(struct nvdebug_state *g, uintptr_t addr,
28 enum PD_TARGET pd_ap) { 38 enum PD_TARGET pd_ap) {
@@ -56,7 +66,7 @@ static void __iomem *pd_deref(struct nvdebug_state *g, uintptr_t addr,
56 // Check for, and translate through, the I/O MMU (if any) 66 // Check for, and translate through, the I/O MMU (if any)
57 if ((dom = iommu_get_domain_for_dev(g->dev))) { 67 if ((dom = iommu_get_domain_for_dev(g->dev))) {
58 phys = iommu_iova_to_phys(dom, addr); 68 phys = iommu_iova_to_phys(dom, addr);
59 printk_debug(KERN_DEBUG "[nvdebug] I/O MMU translated SYS_MEM I/O VA %#lx to physical address %#llx.\n", addr, phys); 69 printk_debug(KERN_DEBUG "[nvdebug] %s: I/O MMU translated SYS_MEM I/O VA %#lx to physical address %#llx.\n", __func__, addr, phys);
60 } else 70 } else
61 phys = addr; 71 phys = addr;
62 72
@@ -143,6 +153,327 @@ uint64_t search_page_directory(struct nvdebug_state *g,
143 return 0; 153 return 0;
144} 154}
145 155
156/* GPU Virtual address -> Physical address ("forward" translation) for V2 tables
157 Index the page directories and tables used by the GPU MMU to determine which
158 physical address a given GPU virtual address has been mapped to.
159
160 The page directory and tables may be located in VID_MEM, SYS_MEM, or spread
161 across multiple apertures.
162
163 @param pd_config Page Directory configuration, containing pointer and
164 aperture for the start of the PDE3 entries
165 @param addr_to_find Virtual address to translate to a physical address
166 @param found_addr Where to store found physical address (0 if unfound)
167 @param found_aperture Where to store aperture of found physical address
168 @return 0 on success, -ENXIO if not found, and -errno on error.
169*/
170int translate_page_directory(struct nvdebug_state *g,
171 page_dir_config_t pd_config,
172 uint64_t addr_to_find,
173 uint64_t *found_addr /* out */,
174 enum INST_TARGET *found_aperture /* out */) {
175 page_dir_entry_t entry;
176 void __iomem *next_kva;
177 unsigned int level, pde_idx;
178 uintptr_t next = (uintptr_t)pd_config.page_dir << 12;
179 enum PD_TARGET next_target = INST2PD_TARGET(pd_config.target);
180
181 *found_addr = 0;
182 *found_aperture = TARGET_INVALID;
183
184 // Make sure that the query is page-aligned (likely mistake otherwise)
185 if (addr_to_find & 0xfff) {
186 printk(KERN_WARNING "[nvdebug] Attempting to translate unaligned address %#llx in translate_page_directory()!\n", addr_to_find);
187 return -EINVAL;
188 }
189
190 printk_info(KERN_INFO "[nvdebug] Translating addr %#018llx in V2 page table with base %#018llx\n", (u64)addr_to_find, (u64)next);
191
192 // Step through each PDE level and the PTE level
193 for (level = 0; level < 5; level++) {
194 // Index into this level
195 pde_idx = (addr_to_find >> NV_MMU_PT_V2_LSB[level]) & (NV_MMU_PT_V2_SZ[level] - 1);
196 printk_debug(KERN_DEBUG "[nvdebug] Using index %u in lvl %d\n", pde_idx, level);
197 // Hack to workaround PDE0 being double-size and strangely formatted
198 if (NV_MMU_PT_V2_ENTRY_SZ[level] == 16)
199 next += 8;
200 // Obtain a kernel-dereferencable address
201 next_kva = pd_deref(g, next, next_target);
202 if (IS_ERR_OR_NULL(next_kva)) {
203 printk(KERN_ERR "[nvdebug] %s: Unable to resolve %#lx in GPU %s to a kernel-accessible address. Error %ld.\n", __func__, next, pd_target_to_text(next_target), PTR_ERR(next_kva));
204 return PTR_ERR(next_kva);
205 }
206 // Obtain entry at this level
207 entry.raw_w = readq(next_kva + NV_MMU_PT_V2_ENTRY_SZ[level] * pde_idx);
208 if (entry.target == PD_AND_TARGET_INVALID)
209 return -ENXIO;
210 printk_debug(KERN_DEBUG "[nvdebug] Found %s pointing to %#018llx in ap '%s' at lvl %d (raw: %#018llx)\n", entry.is_pte ? "PTE" : "PDE", ((u64)entry.addr) << 12, pd_target_to_text(entry.target), level, entry.raw_w);
211 // Just return the physical address if this is the PTE level
212 if (entry.is_pte) { // level == 4 for 4 KiB pages, == 3 for 2 MiB
213 *found_addr = ((uint64_t)entry.addr) << 12;
214 *found_aperture = entry.aperture;
215 return 0;
216 }
217 // Otherwise step to the next table level
218 // TODO: Use addr_w as appropriate
219 next = (uint64_t)entry.addr << 12;
220 next_target = entry.target;
221 }
222
223 return 0;
224}
225
226// This struct is very special. We will never directly allocate this struct;
227// its sole purpose is to provide more intuitive names to the offsets at which
228// we store data in Linux's struct page. Such (ab)use of struct page is
229// explictly permitted (see linux/mm_types.h). This struct is thus used by
230// casting a pointer of struct page to a pointer of struct nvdebug_pd_page,
231// then accessing the associated fields. This pointer may also be freely cast
232// back to a sturct page pointer.
233// We have 24 (32-bit) or 44 (64-bit) bytes available in the page struct
234// (according to the documentation on struct page). Our comments indicate what
235// available parts of struct page we repurpose for our own needs.
236struct nvdebug_pd_page {
237 unsigned long __flags; // From struct page; do not touch!
238 // Overlaps struct page.lru
239 struct list_head list; // 4/8 bytes
240 // Overlaps struct page.mapping (and page.share on 32-bit)
241 uintptr_t parent_addr; // 8 bytes
242 // Overlaps struct page.share (page.private on 32-bit)
243 enum PD_TARGET parent_aperture; // 4 bytes
244 // Overlaps page.private (page.page_type on 32-bit)
245 dma_addr_t dma_addr; // 4/8 bytes
246};
247
248/* Collect and free any now-unused page directory/table allocations
249
250 @param force Deallocate all page directories/tables created by this module,
251 no matter if they appear to be in-use or not.
252 @returns Number of freed pages on success, -errno on error.
253*/
254int gc_page_directory(struct nvdebug_state *g, bool force) {
255 struct nvdebug_pd_page *page, *_page;
256 void __iomem *parent_kva;
257 page_dir_entry_t parent_entry;
258 int freed_pages = 0;
259
260 // Depth-first traversal (from perspective of each page table) of page
261 // allocations.
262 // (This is depth-first because map_page_directory() always allocates and
263 // pushes page directory allocations before page table allocations.)
264 list_for_each_entry_safe_reverse(page, _page, &g->pd_allocs, list) {
265 printk_debug(KERN_DEBUG "[nvdebug] %s: Checking if page directory/table at %llx (SYS_MEM_?) with parent at %lx (%s) is unused...\n", __func__, page->dma_addr, page->parent_addr, pd_target_to_text(page->parent_aperture));
266 // Try to determine if we're still in-use. We consider ourselves
267 // potentially in-use if our parent still points to us.
268 parent_kva = pd_deref(g, page->parent_addr, page->parent_aperture);
269 if (IS_ERR(parent_kva)) {
270 printk(KERN_ERR "[nvdebug] %s: Error resolving %#lx in GPU %s to a kernel-accessible address. Error %ld.\n", __func__, page->parent_addr, pd_target_to_text(page->parent_aperture), PTR_ERR(parent_kva));
271 return -ENOTRECOVERABLE;
272 }
273 // A NULL kva indicates parent no longer exists
274 parent_entry.raw_w = parent_kva ? readq(parent_kva) : 0;
275 // Page directory/table still in-use; do not free unless forced
276 if (parent_entry.addr_w == (page->dma_addr >> 12) && !force)
277 continue;
278 // Free this page table/directory and delete our parent's pointer to us
279 if (parent_entry.addr_w == (page->dma_addr >> 12)) {
280 printk(KERN_WARNING "[nvdebug] %s: Deleting page table/directory at %llx (SYS_MEM_?) with parent at %lx (%s) that may still be in-use!\n", __func__, page->dma_addr, page->parent_addr, pd_target_to_text(page->parent_aperture));
281 writeq(0, parent_kva);
282 }
283 // Unmap, zero, free, and remove from tracking (these all return void)
284 dma_unmap_page(g->dev, page->dma_addr, PAGE_SIZE, DMA_TO_DEVICE);
285 memset(page_to_virt((struct page*)page), 0, PAGE_SIZE);
286 // Necessary to reset mapcount as we (ab)use its state for other things
287 page_mapcount_reset((struct page*)page);
288 // Same reset needed for mapping
289 ((struct page*)page)->mapping = NULL;
290 // Remove this page from our list of allocated pages
291 list_del(&page->list);
292 // Free the page
293 put_page((struct page*)page);
294 freed_pages++;
295 }
296 printk_debug(KERN_DEBUG "[nvdebug] %s: Freed %d pages.", __func__, freed_pages);
297 return freed_pages;
298}
299
300/* Map a GPU virtual address to a physical address in a GPU page table
301 Search for a mapping for specified GPU virtual address, and create a new one
302 if none is found. Automatically creates page directories and page table
303 entries as necessary.
304
305 The page directory and tables may be located in VID_MEM, SYS_MEM, or spread
306 across multiple apertures.
307
308 @param pd_config Page Directory configuration, containing pointer and
309 aperture for the start of the PDE3 entries
310 @param vaddr_to_find Virtual address to check, and map to a physical address
311 if nothing is already mapped (up to 49 bits long)
312 @param paddr_to_map Physical address to use (up to 36 bits long if VID_MEM,
313 and up to 58 bits if SYS_MEM)
314 @param paddr_target Which space does the physical address refer to?
315 @param huge_page Set to map a 2 MiB, rather than 4 KiB, page
316 @return 0 on success, 1 if mapping already exists, -EADDRINUSE if virtual
317 address is already mapped to something else, and -errno on error
318*/
319int map_page_directory(struct nvdebug_state *g,
320 page_dir_config_t pd_config,
321