/* * Copyright 2017 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE.
*/ #define NVKM_VMM_LEVELS_MAX 6 #include"vmm.h"
/* Recurse up the tree, unreferencing/destroying unneeded PDs. */
it->lvl++; if (--pgd->refs[0]) { conststruct nvkm_vmm_desc_func *func = desc[it->lvl].func; /* PD has other valid PDEs, so we need a proper update. */
TRA(it, "PDE unmap %s", nvkm_vmm_desc_type(&desc[it->lvl - 1]));
pgt->pt[type] = NULL; if (!pgt->refs[!type]) { /* PDE no longer required. */ if (pgd->pt[0]) { if (pgt->sparse) {
func->sparse(vmm, pgd->pt[0], pdei, 1);
pgd->pde[pdei] = NVKM_VMM_PDE_SPARSE;
} else {
func->unmap(vmm, pgd->pt[0], pdei, 1);
pgd->pde[pdei] = NULL;
}
} else { /* Special handling for Tesla-class GPUs, * where there's no central PD, but each * instance has its own embedded PD.
*/
func->pde(vmm, pgd, pdei);
pgd->pde[pdei] = NULL;
}
} else { /* PDE was pointing at dual-PTs and we're removing * one of them, leaving the other in place.
*/
func->pde(vmm, pgd, pdei);
}
/* GPU may have cached the PTs, flush before freeing. */
nvkm_vmm_flush_mark(it);
nvkm_vmm_flush(it);
} else { /* PD has no valid PDEs left, so we can just destroy it. */
nvkm_vmm_unref_pdes(it);
}
/* Determine how many SPTEs are being touched under each LPTE, * and drop reference counts.
*/ for (lpti = ptei >> sptb; ptes; spti = 0, lpti++) { const u32 pten = min(sptn - spti, ptes);
pgt->pte[lpti] -= pten;
ptes -= pten;
}
/* We're done here if there's no corresponding LPT. */ if (!pgt->refs[0]) return;
for (ptei = pteb = ptei >> sptb; ptei < lpti; pteb = ptei) { /* Skip over any LPTEs that still have valid SPTEs. */ if (pgt->pte[pteb] & NVKM_VMM_PTE_SPTES) { for (ptes = 1, ptei++; ptei < lpti; ptes++, ptei++) { if (!(pgt->pte[ptei] & NVKM_VMM_PTE_SPTES)) break;
} continue;
}
/* As there's no more non-UNMAPPED SPTEs left in the range * covered by a number of LPTEs, the LPTEs once again take * control over their address range. * * Determine how many LPTEs need to transition state.
*/
pgt->pte[ptei] &= ~NVKM_VMM_PTE_VALID; for (ptes = 1, ptei++; ptei < lpti; ptes++, ptei++) { if (pgt->pte[ptei] & NVKM_VMM_PTE_SPTES) break;
pgt->pte[ptei] &= ~NVKM_VMM_PTE_VALID;
}
if (pgt->pte[pteb] & NVKM_VMM_PTE_SPARSE) {
TRA(it, "LPTE %05x: U -> S %d PTEs", pteb, ptes);
pair->func->sparse(vmm, pgt->pt[0], pteb, ptes);
} else if (pair->func->invalid) { /* If the MMU supports it, restore the LPTE to the * INVALID state to tell the MMU there is no point * trying to fetch the corresponding SPTEs.
*/
TRA(it, "LPTE %05x: U -> I %d PTEs", pteb, ptes);
pair->func->invalid(vmm, pgt->pt[0], pteb, ptes);
}
}
}
if (pfn) { /* Need to clear PTE valid bits before we dma_unmap_page(). */
dma = desc->func->pfn_clear(it->vmm, pgt->pt[type], ptei, ptes); if (dma) { /* GPU may have cached the PT, flush before unmap. */
nvkm_vmm_flush_mark(it);
nvkm_vmm_flush(it);
desc->func->pfn_unmap(it->vmm, pgt->pt[type], ptei, ptes);
}
}
/* Drop PTE references. */
pgt->refs[type] -= ptes;
/* Dual-PTs need special handling, unless PDE becoming invalid. */ if (desc->type == SPT && (pgt->refs[0] || pgt->refs[1]))
nvkm_vmm_unref_sptes(it, pgt, desc, ptei, ptes);
/* PT no longer needed? Destroy it. */ if (!pgt->refs[type]) {
it->lvl++;
TRA(it, "%s empty", nvkm_vmm_desc_type(desc));
it->lvl--;
nvkm_vmm_unref_pdes(it); returnfalse; /* PTE writes for unmap() not necessary. */
}
/* Determine how many SPTEs are being touched under each LPTE, * and increase reference counts.
*/ for (lpti = ptei >> sptb; ptes; spti = 0, lpti++) { const u32 pten = min(sptn - spti, ptes);
pgt->pte[lpti] += pten;
ptes -= pten;
}
/* We're done here if there's no corresponding LPT. */ if (!pgt->refs[0]) return;
for (ptei = pteb = ptei >> sptb; ptei < lpti; pteb = ptei) { /* Skip over any LPTEs that already have valid SPTEs. */ if (pgt->pte[pteb] & NVKM_VMM_PTE_VALID) { for (ptes = 1, ptei++; ptei < lpti; ptes++, ptei++) { if (!(pgt->pte[ptei] & NVKM_VMM_PTE_VALID)) break;
} continue;
}
/* As there are now non-UNMAPPED SPTEs in the range covered * by a number of LPTEs, we need to transfer control of the * address range to the SPTEs. * * Determine how many LPTEs need to transition state.
*/
pgt->pte[ptei] |= NVKM_VMM_PTE_VALID; for (ptes = 1, ptei++; ptei < lpti; ptes++, ptei++) { if (pgt->pte[ptei] & NVKM_VMM_PTE_VALID) break;
pgt->pte[ptei] |= NVKM_VMM_PTE_VALID;
}
if (pgt->pte[pteb] & NVKM_VMM_PTE_SPARSE) { const u32 spti = pteb * sptn; const u32 sptc = ptes * sptn; /* The entire LPTE is marked as sparse, we need * to make sure that the SPTEs are too.
*/
TRA(it, "SPTE %05x: U -> S %d PTEs", spti, sptc);
desc->func->sparse(vmm, pgt->pt[1], spti, sptc); /* Sparse LPTEs prevent SPTEs from being accessed. */
TRA(it, "LPTE %05x: S -> U %d PTEs", pteb, ptes);
pair->func->unmap(vmm, pgt->pt[0], pteb, ptes);
} else if (pair->func->invalid) { /* MMU supports blocking SPTEs by marking an LPTE * as INVALID. We need to reverse that here.
*/
TRA(it, "LPTE %05x: I -> U %d PTEs", pteb, ptes);
pair->func->unmap(vmm, pgt->pt[0], pteb, ptes);
}
}
}
if (desc->type == LPT && pgt->refs[1]) { /* SPT already exists covering the same range as this LPT, * which means we need to be careful that any LPTEs which * overlap valid SPTEs are unmapped as opposed to invalid * or sparse, which would prevent the MMU from looking at * the SPTEs on some GPUs.
*/ for (ptei = pteb = 0; ptei < pten; pteb = ptei) { bool spte = pgt->pte[ptei] & NVKM_VMM_PTE_SPTES; for (ptes = 1, ptei++; ptei < pten; ptes++, ptei++) { bool next = pgt->pte[ptei] & NVKM_VMM_PTE_SPTES; if (spte != next) break;
}
/* Walk down the tree, finding page tables for each level. */ for (; it.lvl; it.lvl--) { const u32 pdei = it.pte[it.lvl]; struct nvkm_vmm_pt *pgd = pgt;
/* Software PT. */ if (ref && NVKM_VMM_PDE_INVALID(pgd->pde[pdei])) { if (!nvkm_vmm_ref_swpt(&it, pgd, pdei)) goto fail;
}
it.pt[it.lvl - 1] = pgt = pgd->pde[pdei];
/* Hardware PT. * * This is a separate step from above due to GF100 and * newer having dual page tables at some levels, which * are refcounted independently.
*/ if (ref && !pgt->refs[desc[it.lvl - 1].type == SPT]) { if (!nvkm_vmm_ref_hwpt(&it, pgd, pdei)) goto fail;
}
}
/* Walk back up the tree to the next position. */
it.pte[it.lvl] += ptes;
it.cnt -= ptes; if (it.cnt) { while (it.pte[it.lvl] == (1 << desc[it.lvl].bits)) {
it.pte[it.lvl++] = 0;
it.pte[it.lvl]++;
}
}
}
nvkm_vmm_flush(&it); return ~0ULL;
fail: /* Reconstruct the failure address so the caller is able to * reverse any partially completed operations.
*/
addr = it.pte[it.max--]; do {
addr = addr << desc[it.max].bits;
addr |= it.pte[it.max];
} while (it.max--);
/* Locate the smallest page size supported by the backend, it will * have the deepest nesting of page tables.
*/ while (page[1].shift)
page++;
/* Locate the structure that describes the layout of the top-level * page table, and determine the number of valid bits in a virtual * address.
*/ for (levels = 0, desc = page->desc; desc->bits; desc++, levels++)
bits += desc->bits;
bits += page->shift;
desc--;
if (WARN_ON(levels > NVKM_VMM_LEVELS_MAX)) return -EINVAL;
/* ... and the GPU storage for it, except on Tesla-class GPUs that * have the PD embedded in the instance structure.
*/ if (desc->size) { const u32 size = pd_header + desc->size * (1 << desc->bits);
vmm->pd->pt[0] = nvkm_mmu_ptc_get(mmu, size, desc->align, true); if (!vmm->pd->pt[0]) return -ENOMEM;
}
if (managed) { /* Address-space will be managed by the client for the most * part, except for a specified area where NVKM allocations * are allowed to be placed.
*/
vmm->start = 0;
vmm->limit = 1ULL << bits; if (addr + size < addr || addr + size > vmm->limit) return -EINVAL;
/* Client-managed area before the NVKM-managed area. */ if (addr && (ret = nvkm_vmm_ctor_managed(vmm, 0, addr))) return ret;
/*TODO: * - Avoid PT readback (for dma_unmap etc), this might end up being dealt * with inside HMM, which would be a lot nicer for us to deal with. * - Support for systems without a 4KiB page size.
*/ int
nvkm_vmm_pfn_map(struct nvkm_vmm *vmm, u8 shift, u64 addr, u64 size, u64 *pfn)
{ conststruct nvkm_vmm_page *page = vmm->func->page; struct nvkm_vma *vma, *tmp;
u64 limit = addr + size;
u64 start = addr; int pm = size >> shift; int pi = 0;
/* Only support mapping where the page size of the incoming page * array matches a page size available for direct mapping.
*/ while (page->shift && (page->shift != shift ||
page->desc->func->pfn == NULL))
page++;
if (!(vma = nvkm_vmm_node_search(vmm, addr))) return -ENOENT;
do { bool map = !!(pfn[pi] & NVKM_VMM_PFN_V); bool mapped = vma->mapped;
u64 size = limit - start;
u64 addr = start; int pn, ret = 0;
/* Narrow the operation window to cover a single action (page * should be mapped or not) within a single VMA.
*/ for (pn = 0; pi + pn < pm; pn++) { if (map != !!(pfn[pi + pn] & NVKM_VMM_PFN_V)) break;
}
size = min_t(u64, size, pn << page->shift);
size = min_t(u64, size, vma->size + vma->addr - addr);
/* Reject any operation to unmanaged regions, and areas that * have nvkm_memory objects mapped in them already.
*/ if (!vma->mapref || vma->memory) {
ret = -EINVAL; goto next;
}
/* In order to both properly refcount GPU page tables, and * prevent "normal" mappings and these direct mappings from * interfering with each other, we need to track contiguous * ranges that have been mapped with this interface. * * Here we attempt to either split an existing VMA so we're * able to flag the region as either unmapped/mapped, or to * merge with adjacent VMAs that are already compatible. * * If the region is already compatible, nothing is required.
*/ if (map != mapped) {
tmp = nvkm_vmm_pfn_split_merge(vmm, vma, addr, size,
page -
vmm->func->page, map); if (WARN_ON(!tmp)) {
ret = -ENOMEM; goto next;
}
next: /* Iterate to next operation. */ if (vma->addr + vma->size == addr + size)
vma = node(vma, next);
start += size;
if (ret) { /* Failure is signalled by clearing the valid bit on * any PFN that couldn't be modified as requested.
*/ while (size) {
pfn[pi++] = NVKM_VMM_PFN_NONE;
size -= 1 << page->shift;
}
} else {
pi += size >> page->shift;
}
} while (vma && start < limit);
/* Make sure we won't overrun the end of the memory object. */ if (unlikely(nvkm_memory_size(map->memory) < map->offset + vma->size)) {
VMM_DEBUG(vmm, "overrun %016llx %016llx %016llx",
nvkm_memory_size(map->memory),
map->offset, (u64)vma->size); return -EINVAL;
}
/* Check remaining arguments for validity. */ if (vma->page == NVKM_VMA_PAGE_NONE &&
vma->refd == NVKM_VMA_PAGE_NONE) { /* Find the largest page size we can perform the mapping at. */ const u32 debug = vmm->debug;
vmm->debug = 0;
ret = nvkm_vmm_map_choose(vmm, vma, argv, argc, map);
vmm->debug = debug; if (ret) {
VMM_DEBUG(vmm, "invalid at any page size");
nvkm_vmm_map_choose(vmm, vma, argv, argc, map); return -EINVAL;
}
} else { /* Page size of the VMA is already pre-determined. */ if (vma->refd != NVKM_VMA_PAGE_NONE)
map->page = &vmm->func->page[vma->refd]; else
map->page = &vmm->func->page[vma->page];
ret = nvkm_vmm_map_valid(vmm, vma, argv, argc, map); if (ret) {
VMM_DEBUG(vmm, "invalid %d\n", ret); return ret;
}
}
/* Deal with the 'offset' argument, and fetch the backend function. */
map->off = map->offset; if (map->mem) { for (; map->off; map->mem = map->mem->next) {
u64 size = (u64)map->mem->length << NVKM_RAM_MM_SHIFT; if (size > map->off) break;
map->off -= size;
}
func = map->page->desc->func->mem;
} else if (map->sgl) { for (; map->off; map->sgl = sg_next(map->sgl)) {
u64 size = sg_dma_len(map->sgl); if (size > map->off) break;
map->off -= size;
}
func = map->page->desc->func->sgl;
} else {
map->dma += map->offset >> PAGE_SHIFT;
map->off = map->offset & PAGE_MASK;
func = map->page->desc->func->dma;
}
/* Perform the map. */ if (vma->refd == NVKM_VMA_PAGE_NONE) {
ret = nvkm_vmm_ptes_get_map(vmm, map->page, vma->addr, vma->size, map, func); if (ret) return ret;
/* Merge regions that are in the same state. */ while ((next = node(next, next)) && next->part &&
(next->mapped == map) &&
(next->memory != NULL) == mem &&
(next->refd == refd))
size += next->size;
if (map) { /* Region(s) are mapped, merge the unmap * and dereference into a single walk of * the page tree.
*/
nvkm_vmm_ptes_unmap_put(vmm, &page[refd], addr,
size, vma->sparse,
!mem);
} else if (refd != NVKM_VMA_PAGE_NONE) { /* Drop allocation-time PTE references. */
nvkm_vmm_ptes_put(vmm, &page[refd], addr, size);
}
} while (next && next->part);
}
/* Merge any mapped regions that were split from the initial * address-space allocation back into the allocated VMA, and * release memory/compression resources.
*/
next = vma; do { if (next->mapped)
nvkm_vmm_unmap_region(vmm, next);
} while ((next = node(vma, next)) && next->part);
if (vma->sparse && !vma->mapref) { /* Sparse region that was allocated with a fixed page size, * meaning all relevant PTEs were referenced once when the * region was allocated, and remained that way, regardless * of whether memory was mapped into it afterwards. * * The process of unmapping, unsparsing, and dereferencing * PTEs can be done in a single page tree walk.
*/
nvkm_vmm_ptes_sparse_put(vmm, &page[vma->refd], vma->addr, vma->size);
} else if (vma->sparse) { /* Sparse region that wasn't allocated with a fixed page size, * PTE references were taken both at allocation time (to make * the GPU see the region as sparse), and when mapping memory * into the region. * * The latter was handled above, and the remaining references * are dealt with here.
*/
nvkm_vmm_ptes_sparse(vmm, vma->addr, vma->size, false);
}
/* Remove VMA from the list of allocated nodes. */
nvkm_vmm_node_remove(vmm, vma);
/* Merge VMA back into the free list. */
vma->page = NVKM_VMA_PAGE_NONE;
vma->refd = NVKM_VMA_PAGE_NONE;
vma->used = false;
nvkm_vmm_put_region(vmm, vma);
}
/* Zero-sized, or lazily-allocated sparse VMAs, make no sense. */ if (unlikely(!size || (!getref && !mapref && sparse))) {
VMM_DEBUG(vmm, "args %016llx %d %d %d",
size, getref, mapref, sparse); return -EINVAL;
}
/* Tesla-class GPUs can only select page size per-PDE, which means * we're required to know the mapping granularity up-front to find * a suitable region of address-space. * * The same goes if we're requesting up-front allocation of PTES.
*/ if (unlikely((getref || vmm->func->page_block) && !shift)) {
VMM_DEBUG(vmm, "page size required: %d %016llx",
getref, vmm->func->page_block); return -EINVAL;
}
/* If a specific page size was requested, determine its index and * make sure the requested size is a multiple of the page size.
*/ if (shift) { for (page = vmm->func->page; page->shift; page++) { if (shift == page->shift) break;
}
/* If the VMA we found isn't already exactly the requested size, * it needs to be split, and the remaining free blocks returned.
*/ if (addr != vma->addr) { if (!(tmp = nvkm_vma_tail(vma, vma->size + vma->addr - addr))) {
nvkm_vmm_put_region(vmm, vma); return -ENOMEM;
}
nvkm_vmm_free_insert(vmm, vma);
vma = tmp;
}
int
nvkm_vmm_join(struct nvkm_vmm *vmm, struct nvkm_memory *inst)
{ int ret = 0; if (vmm->func->join) {
mutex_lock(&vmm->mutex.vmm);
ret = vmm->func->join(vmm, inst);
mutex_unlock(&vmm->mutex.vmm);
} return ret;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.