/* * Copyright 2018 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.
*/ #include"nouveau_svm.h" #include"nouveau_drv.h" #include"nouveau_chan.h" #include"nouveau_dmem.h"
/* FIXME support CPU target ie all target value < GPU_VRAM */
target = args->header >> NOUVEAU_SVM_BIND_TARGET_SHIFT;
target &= NOUVEAU_SVM_BIND_TARGET_MASK; switch (target) { case NOUVEAU_SVM_BIND_TARGET__GPU_VRAM: break; default: return -EINVAL;
}
/* * FIXME: For now refuse non 0 stride, we need to change the migrate * kernel function to handle stride to avoid to create a mess within * each device driver.
*/ if (args->stride) return -EINVAL;
/* * Ok we are ask to do something sane, for now we only support migrate * commands but we will add things like memory policy (what to do on * page fault) and maybe some other commands.
*/
mm = get_task_mm(current); if (!mm) { return -EINVAL;
}
mmap_read_lock(mm);
if (!cli->svm.svmm) {
mmap_read_unlock(mm);
mmput(mm); return -EINVAL;
}
for (addr = args->va_start, end = args->va_end; addr < end;) { struct vm_area_struct *vma; unsignedlong next;
vma = find_vma_intersection(mm, addr, end); if (!vma) break;
addr = max(addr, vma->vm_start);
next = min(vma->vm_end, end); /* This is a best effort so we ignore errors */
nouveau_dmem_migrate_vma(cli->drm, cli->svm.svmm, vma, addr,
next);
addr = next;
}
/* * FIXME Return the number of page we have migrated, again we need to * update the migrate API to return that information so that we can * report it to user space.
*/
args->result = 0;
mutex_lock(&svmm->mutex); if (unlikely(!svmm->vmm)) goto out;
/* * Ignore invalidation callbacks for device private pages since * the invalidation is handled as part of the migration process.
*/ if (update->event == MMU_NOTIFY_MIGRATE &&
update->owner == svmm->vmm->cli->drm->dev) goto out;
/* Check that SVM isn't already enabled for the client. */
mutex_lock(&cli->mutex); if (cli->svm.cli) {
ret = -EBUSY; goto out_free;
}
/* Allocate a new GPU VMM that can support SVM (managed by the * client, with replayable faults enabled). * * All future channel/memory allocations will make use of this * VMM instead of the standard one.
*/
ret = nvif_vmm_ctor(&cli->mmu, "svmVmm",
cli->vmm.vmm.object.oclass, MANAGED,
args->unmanaged_addr, args->unmanaged_size,
&(struct gp100_vmm_v0) {
.fault_replay = true,
}, sizeof(struct gp100_vmm_v0), &cli->svm.vmm); if (ret) goto out_free;
mmap_write_lock(current->mm);
svmm->notifier.ops = &nouveau_mn_ops;
ret = __mmu_notifier_register(&svmm->notifier, current->mm); if (ret) goto out_mm_unlock; /* Note, ownership of svmm transfers to mmu_notifier */
if (range->event == MMU_NOTIFY_EXCLUSIVE &&
range->owner == sn->svmm->vmm->cli->drm->dev) returntrue;
/* * serializes the update to mni->invalidate_seq done by caller and * prevents invalidation of the PTE from progressing while HW is being * programmed. This is very hacky and only works because the normal * notifier that does invalidation is always called after the range * notifier.
*/ if (mmu_notifier_range_blockable(range))
mutex_lock(&sn->svmm->mutex); elseif (!mutex_trylock(&sn->svmm->mutex)) returnfalse;
mmu_interval_set_seq(mni, cur_seq);
mutex_unlock(&sn->svmm->mutex); returntrue;
}
/* * The address prepared here is passed through nvif_object_ioctl() * to an eventual DMA map in something like gp100_vmm_pgt_pfn() * * This is all just encoding the internal hmm representation into a * different nouveau internal representation.
*/ if (!(range->hmm_pfns[0] & HMM_PFN_VALID)) {
args->p.phys[0] = 0; return;
}
page = hmm_pfn_to_page(range->hmm_pfns[0]); /* * Only map compound pages to the GPU if the CPU is also mapping the * page as a compound page. Otherwise, the PTE protections might not be * consistent (e.g., CPU only maps part of a compound page). * Note that the underlying page might still be larger than the * CPU mapping (e.g., a PUD sized compound page partially mapped with * a PMD sized page table entry).
*/ if (hmm_pfn_to_map_order(range->hmm_pfns[0])) { unsignedlong addr = args->p.addr;
/* Parse available fault buffer entries into a cache, and update * the GET pointer so HW can reuse the entries.
*/
SVM_DBG(svm, "fault handler"); if (buffer->get == buffer->put) {
buffer->put = nvif_rd32(device, buffer->putaddr);
buffer->get = nvif_rd32(device, buffer->getaddr); if (buffer->get == buffer->put) return;
}
buffer->fault_nr = 0;
/* Sort parsed faults by instance pointer to prevent unnecessary * instance to SVMM translations, followed by address and access * type to reduce the amount of work when handling the faults.
*/
sort(buffer->fault, buffer->fault_nr, sizeof(*buffer->fault),
nouveau_svm_fault_cmp, NULL);
/* Process list of faults. */
args->i.version = 0;
args->i.type = NVIF_IOCTL_V0_MTHD;
args->m.version = 0;
args->m.method = NVIF_VMM_V0_PFNMAP;
args->p.version = 0;
for (fi = 0; fn = fi + 1, fi < buffer->fault_nr; fi = fn) { struct svm_notifier notifier; struct mm_struct *mm;
/* Cancel any faults from non-SVM channels. */ if (!(svmm = buffer->fault[fi]->svmm)) {
nouveau_svm_fault_cancel_fault(svm, buffer->fault[fi]); continue;
}
SVMM_DBG(svmm, "addr %016llx", buffer->fault[fi]->addr);
/* We try and group handling of faults within a small * window into a single update.
*/
start = buffer->fault[fi]->addr;
limit = start + PAGE_SIZE; if (start < svmm->unmanaged.limit)
limit = min_t(u64, limit, svmm->unmanaged.start);
/* * Prepare the GPU-side update of all pages within the * fault window, determining required pages and access * permissions based on pending faults.
*/
args->p.addr = start;
args->p.page = PAGE_SHIFT;
args->p.size = PAGE_SIZE; /* * Determine required permissions based on GPU fault * access flags.
*/ switch (buffer->fault[fi]->access) { case 0: /* READ. */
hmm_flags = HMM_PFN_REQ_FAULT; break; case 2: /* ATOMIC. */
atomic = true; break; case 3: /* PREFETCH. */
hmm_flags = 0; break; default:
hmm_flags = HMM_PFN_REQ_FAULT | HMM_PFN_REQ_WRITE; break;
}
mm = svmm->notifier.mm; if (!mmget_not_zero(mm)) {
nouveau_svm_fault_cancel_fault(svm, buffer->fault[fi]); continue;
}
notifier.svmm = svmm; if (atomic)
ret = nouveau_atomic_range_fault(svmm, svm->drm, args,
__struct_size(args),
¬ifier); else
ret = nouveau_range_fault(svmm, svm->drm, args,
__struct_size(args),
hmm_flags, ¬ifier);
mmput(mm);
limit = args->p.addr + args->p.size; for (fn = fi; ++fn < buffer->fault_nr; ) { /* It's okay to skip over duplicate addresses from the * same SVMM as faults are ordered by access type such * that only the first one needs to be handled. * * ie. WRITE faults appear first, thus any handling of * pending READ faults will already be satisfied. * But if a large page is mapped, make sure subsequent * fault addresses have sufficient access permission.
*/ if (buffer->fault[fn]->svmm != svmm ||
buffer->fault[fn]->addr >= limit ||
(buffer->fault[fi]->access == FAULT_ACCESS_READ &&
!(args->p.phys[0] & NVIF_VMM_PFNMAP_V0_V)) ||
(buffer->fault[fi]->access != FAULT_ACCESS_READ &&
buffer->fault[fi]->access != FAULT_ACCESS_PREFETCH &&
!(args->p.phys[0] & NVIF_VMM_PFNMAP_V0_W)) ||
(buffer->fault[fi]->access != FAULT_ACCESS_READ &&
buffer->fault[fi]->access != FAULT_ACCESS_WRITE &&
buffer->fault[fi]->access != FAULT_ACCESS_PREFETCH &&
!(args->p.phys[0] & NVIF_VMM_PFNMAP_V0_A))) break;
}
/* If handling failed completely, cancel all faults. */ if (ret) { while (fi < fn) { struct nouveau_svm_fault *fault =
buffer->fault[fi++];
/* Disable on Volta and newer until channel recovery is fixed, * otherwise clients will have a trivial way to trash the GPU * for everyone.
*/ if (drm->client.device.info.family > NV_DEVICE_INFO_V0_PASCAL) return;
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.