diff --git a/pkg/abi/nvgpu/nvgpu.go b/pkg/abi/nvgpu/nvgpu.go index c32079f1b..c1c1f87b7 100644 --- a/pkg/abi/nvgpu/nvgpu.go +++ b/pkg/abi/nvgpu/nvgpu.go @@ -40,6 +40,9 @@ func (h Handle) String() string { return fmt.Sprintf("%#x", h.Val) } +// NV01_NULL_OBJECT is a Handle representing no object. +var NV01_NULL_OBJECT = Handle{0} + // P64 is NvP64, from src/common/sdk/nvidia/inc/nvtypes.h. // // +marshal diff --git a/pkg/sentry/devices/nvproxy/frontend.go b/pkg/sentry/devices/nvproxy/frontend.go index f265907c2..82926674a 100644 --- a/pkg/sentry/devices/nvproxy/frontend.go +++ b/pkg/sentry/devices/nvproxy/frontend.go @@ -493,6 +493,32 @@ func rmAllocOSDescriptor(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02P return n, nil } +func rmDupObject(fi *frontendIoctlState) (uintptr, error) { + var ioctlParams nvgpu.NVOS55Parameters + if fi.ioctlParamsSize != nvgpu.SizeofNVOS55Parameters { + return 0, linuxerr.EINVAL + } + if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { + return 0, err + } + + nvp := fi.fd.dev.nvp + nvp.objsLock() + n, err := frontendIoctlInvoke(fi, &ioctlParams) + if err == nil && ioctlParams.Status == nvgpu.NV_OK { + nvp.objDup(fi.ctx, ioctlParams.HClient, ioctlParams.HObject, ioctlParams.HParent, ioctlParams.HClientSrc, ioctlParams.HObjectSrc) + } + nvp.objsUnlock() + if err != nil { + return n, err + } + + if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil { + return n, err + } + return n, nil +} + func rmFree(fi *frontendIoctlState) (uintptr, error) { var ioctlParams nvgpu.NVOS00Parameters if fi.ioctlParamsSize != nvgpu.SizeofNVOS00Parameters { @@ -902,7 +928,7 @@ func rmAllocNoParams(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters func rmAllocRootClient(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.Handle) { - fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRootClient(fi.fd, ioctlParams, rightsRequested, allocParams)) + fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRootClient(fi.fd, ioctlParams, rightsRequested, allocParams), nvgpu.NV01_NULL_OBJECT /* parentH */) if fi.fd.clients == nil { fi.fd.clients = make(map[nvgpu.Handle]struct{}) } @@ -928,7 +954,7 @@ func rmAllocEventOSEvent(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parame allocParams.Data = nvgpu.P64(uint64(eventFile.hostFD)) n, err := rmAllocInvoke(fi, ioctlParams, &allocParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV0005_ALLOC_PARAMETERS) { - fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, &osEvent{}, ioctlParams.HObjectParent) + fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, &miscObject{}, ioctlParams.HObjectParent) }) if err != nil { return n, err diff --git a/pkg/sentry/devices/nvproxy/frontend_unsafe.go b/pkg/sentry/devices/nvproxy/frontend_unsafe.go index d4d4a174e..63d48d473 100644 --- a/pkg/sentry/devices/nvproxy/frontend_unsafe.go +++ b/pkg/sentry/devices/nvproxy/frontend_unsafe.go @@ -326,7 +326,7 @@ func rmVidHeapControlAllocSize(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS32 n, err := frontendIoctlInvoke(fi, ioctlParams) if err == nil && ioctlParams.Status == nvgpu.NV_OK { // src/nvidia/src/kernel/mem_mgr/virtual_mem.c:virtmemConstruct_IMPL() => refAddDependant() - fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, allocSizeParams.HMemory, nvgpu.NV50_MEMORY_VIRTUAL, &virtMem{}, ioctlParams.HObjectParent, ioctlParams.HVASpace) + fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, allocSizeParams.HMemory, nvgpu.NV50_MEMORY_VIRTUAL, &miscObject{}, ioctlParams.HObjectParent, ioctlParams.HVASpace) } fi.fd.dev.nvp.objsUnlock() allocSizeParams.Address = origAddress diff --git a/pkg/sentry/devices/nvproxy/object.go b/pkg/sentry/devices/nvproxy/object.go index 85b25b287..5f6e9baa7 100644 --- a/pkg/sentry/devices/nvproxy/object.go +++ b/pkg/sentry/devices/nvproxy/object.go @@ -31,6 +31,7 @@ type object struct { client *rootClient // may be == impl class nvgpu.ClassID handle nvgpu.Handle // in client.resources, and also nvp.clients if impl is rootClient + parent nvgpu.Handle impl objectImpl // The driver tracks parent/child relationships and "arbitrary dependency" @@ -73,13 +74,15 @@ func (nvp *nvproxy) objsUnlock() { // objAdd records the allocation of a driver object with class c and handle h, // in the client with handle clientH, represented by oi. Each non-zero handle -// in deps is a dependency of the created object, such that the freeing of any -// of those objects also results in the freeing of the recorded object. -func (nvp *nvproxy) objAdd(ctx context.Context, clientH, h nvgpu.Handle, c nvgpu.ClassID, oi objectImpl, deps ...nvgpu.Handle) { - if h.Val == 0 { - log.Traceback("nvproxy: new object (class %v) has invalid handle 0", c) +// in parentH and deps is a dependency of the created object, such that the +// freeing of any of those objects also results in the freeing of the recorded +// object. +func (nvp *nvproxy) objAdd(ctx context.Context, clientH, h nvgpu.Handle, c nvgpu.ClassID, oi objectImpl, parentH nvgpu.Handle, deps ...nvgpu.Handle) { + if h == nvgpu.NV01_NULL_OBJECT { + log.Traceback("nvproxy: new object (class %v) has invalid handle %v", c, h) return } + var client *rootClient // The driver forced NV01_ROOT and NV01_ROOT_NON_PRIV to NV01_ROOT_CLIENT, // so we only need to check for the latter. @@ -98,18 +101,29 @@ func (nvp *nvproxy) objAdd(ctx context.Context, clientH, h nvgpu.Handle, c nvgpu return } } + o := oi.Object() o.nvp = nvp o.client = client o.class = c o.handle = h + o.parent = parentH o.impl = oi if _, ok := client.resources[h]; ok { ctx.Warningf("nvproxy: handle %v:%v already in use", clientH, h) } client.resources[h] = o + + if parentH != nvgpu.NV01_NULL_OBJECT { + parent, ok := client.resources[parentH] + if !ok { + log.Traceback("nvproxy: new object %v:%v (class %v) has invalid parent handle %v", clientH, h, c, parentH) + } else { + nvp.objDep(o, parent) + } + } for _, depH := range deps { - if depH.Val == 0 /* aka NV01_NULL_OBJECT */ { + if depH == nvgpu.NV01_NULL_OBJECT { continue } dep, ok := client.resources[depH] @@ -119,8 +133,9 @@ func (nvp *nvproxy) objAdd(ctx context.Context, clientH, h nvgpu.Handle, c nvgpu } nvp.objDep(o, dep) } + if ctx.IsLogging(log.Debug) { - ctx.Debugf("nvproxy: added object %v:%v (class %v) with dependencies %v", clientH, h, c, deps) + ctx.Debugf("nvproxy: added object %v:%v (class %v) with parent %v, dependencies %v", clientH, h, c, parentH, deps) } } @@ -161,6 +176,31 @@ func (nvp *nvproxy) objDep(o1, o2 *object) { o2.rdeps[o1] = struct{}{} } +// objDup records the duplication of the driver object with handle srcH in the +// client with handle clientSrcH, to handle dstH in the client with handle +// clientDstH, with new parent parentDstH. +func (nvp *nvproxy) objDup(ctx context.Context, clientDstH, dstH, parentDstH, clientSrcH, srcH nvgpu.Handle) { + clientSrc, ok := nvp.clients[clientSrcH] + if !ok { + ctx.Warningf("nvproxy: duplicating object handle %v with unknown client handle %v", srcH, clientSrcH) + return + } + oSrc, ok := clientSrc.resources[srcH] + if !ok { + ctx.Warningf("nvproxy: duplicating object with unknown handle %v:%v", clientSrcH, srcH) + return + } + oDst := &miscObject{} + nvp.objAdd(ctx, clientDstH, dstH, oSrc.class, oDst, parentDstH) + parentSrc := clientSrc.resources[oSrc.parent] + // Copy all non-parent dependencies. + for dep := range oSrc.deps { + if dep != parentSrc { + nvp.objDep(oDst.Object(), dep) + } + } +} + // objFree marks an object and its transitive dependents as freed. // // Compare @@ -279,6 +319,18 @@ func (o *rmAllocObject) Release(ctx context.Context) { // no-op } +// miscObject is an objectImpl tracking a driver object allocated by something +// other than an invocation of NV_ESC_RM_ALLOC, whose class is not represented +// by a more specific type. +type miscObject struct { + object +} + +// Release implements objectImpl.Release. +func (o *miscObject) Release(ctx context.Context) { + // no-op +} + // rootClient is an objectImpl tracking a NV01_ROOT_CLIENT. // // +stateify savable @@ -323,23 +375,3 @@ func (o *osDescMem) Release(ctx context.Context) { } }) } - -// osEvent is an objectImpl tracking a NV01_EVENT_OS_EVENT. -type osEvent struct { - object -} - -// Release implements objectImpl.Release. -func (o *osEvent) Release(ctx context.Context) { - // no-op -} - -// virtMem is an objectImpl tracking a NV50_MEMORY_VIRTUAL. -type virtMem struct { - object -} - -// Release implements objectImpl.Release. -func (o *virtMem) Release(ctx context.Context) { - // no-op -} diff --git a/pkg/sentry/devices/nvproxy/version.go b/pkg/sentry/devices/nvproxy/version.go index 67dc6d5b7..d67dde5fd 100644 --- a/pkg/sentry/devices/nvproxy/version.go +++ b/pkg/sentry/devices/nvproxy/version.go @@ -180,7 +180,7 @@ func Init() { nvgpu.NV_ESC_CHECK_VERSION_STR: feHandler(frontendIoctlSimple, compUtil), // nv_rm_api_version_t nvgpu.NV_ESC_ATTACH_GPUS_TO_FD: feHandler(frontendIoctlSimple, compUtil), // NvU32 array containing GPU IDs nvgpu.NV_ESC_SYS_PARAMS: feHandler(frontendIoctlSimple, compUtil), // nv_ioctl_sys_params_t - nvgpu.NV_ESC_RM_DUP_OBJECT: feHandler(frontendIoctlSimple, compUtil), // NVOS55_PARAMETERS + nvgpu.NV_ESC_RM_DUP_OBJECT: feHandler(rmDupObject, compUtil), // NVOS55_PARAMETERS nvgpu.NV_ESC_RM_SHARE: feHandler(frontendIoctlSimple, compUtil), // NVOS57_PARAMETERS nvgpu.NV_ESC_RM_UNMAP_MEMORY: feHandler(frontendIoctlSimple, compUtil), // NVOS34_PARAMETERS nvgpu.NV_ESC_RM_UPDATE_DEVICE_MAPPING_INFO: feHandler(frontendIoctlSimple, compUtil), // NVOS56_PARAMETERS