Files
Luis Capelo 5535b4df5b nvproxy: support NV_EVENT_BUFFER
Adds the NV_EVENT_BUFFER (0x90cd) event-record buffer API when `capProfiling` is enabled.

- Allocation classes: NV_EVENT_BUFFER (0x90cd), NV_EVENT_BUFFER_BIND (0x7f).
- Control commands: NV_EVENT_BUFFER_CTRL_CMD_{ENABLE_EVENTS,UPDATE_GET,FLUSH}, NV2080_CTRL_CMD_GR_FECS_BIND_EVTBUF_FOR_UID, NV2080_CTRL_CMD_EVENT_VIDEO_BIND_EVTBUF.

Validated with tools/nvidia_driver_differ and the driver struct parity test (20 supported drivers).
2026-08-14 16:48:15 +00:00

1733 lines
66 KiB
Go

// Copyright 2023 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package nvproxy
import (
"fmt"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/abi/nvgpu"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fdnotifier"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal/primitive"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/mm"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/usermem"
"gvisor.dev/gvisor/pkg/waiter"
)
// frontendDevice implements vfs.Device for /dev/nvidia# and /dev/nvidiactl.
//
// +stateify savable
type frontendDevice struct {
nvp *nvproxy
minor uint32
}
func (dev *frontendDevice) isCtlDevice() bool {
return dev.minor == nvgpu.NV_MINOR_DEVICE_NUMBER_CONTROL_DEVICE
}
func (dev *frontendDevice) basename() string {
if dev.isCtlDevice() {
return "nvidiactl"
}
return fmt.Sprintf("nvidia%d", dev.minor)
}
// Open implements vfs.Device.Open.
func (dev *frontendDevice) Open(ctx context.Context, mnt *vfs.Mount, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
fd := &frontendFD{
dev: dev,
}
var err error
fd.hostFD, fd.containerName, err = openHostDevFile(ctx, dev.basename(), dev.nvp.useDevGofer, opts.Flags)
if err != nil {
return nil, err
}
if err := fd.vfsfd.Init(fd, opts.Flags, auth.CredentialsFromContext(ctx), mnt, vfsd, &vfs.FileDescriptionOptions{
UseDentryMetadata: true,
SpecialFile: true,
}); err != nil {
unix.Close(int(fd.hostFD))
return nil, err
}
fd.internalEntry.Init(fd, waiter.AllEvents)
fd.internalQueue.EventRegister(&fd.internalEntry)
if err := fdnotifier.AddFD(fd.hostFD, &fd.internalQueue); err != nil {
unix.Close(int(fd.hostFD))
return nil, err
}
fd.memmapFile.SetFD(int(fd.hostFD))
fd.dev.nvp.fdsMu.Lock()
defer fd.dev.nvp.fdsMu.Unlock()
fd.dev.nvp.frontendFDs[fd] = struct{}{}
return &fd.vfsfd, nil
}
// frontendFD implements vfs.FileDescriptionImpl for /dev/nvidia# and
// /dev/nvidiactl.
//
// +stateify savable
type frontendFD struct {
vfsfd vfs.FileDescription
vfs.FileDescriptionDefaultImpl
vfs.DentryMetadataFileDescriptionImpl
vfs.NoLockFD
memmap.MappableNoTrackMappings
dev *frontendDevice
containerName string
hostFD int32
memmapFile frontendFDMemmapFile
// The driver's implementation of poll() for these files,
// kernel-open/nvidia/nv.c:nvidia_poll(), unsets
// nv_linux_file_private_t::dataless_event_pending if it's set. This makes
// notifications from dataless_event_pending edge-triggered; a host poll()
// or epoll_wait() that returns the notification consumes it, preventing
// future calls to poll() or epoll_wait() from observing the same
// notification again.
//
// This is problematic in gVisor: fdnotifier, which epoll_wait()s on an
// epoll instance that includes our hostFD, will forward notifications to
// registered waiters, but this typically only wakes up blocked task
// goroutines which will later call vfs.FileDescription.Readiness() to get
// the FD's most up-to-date state. If our implementation of Readiness()
// just polls the underlying host FD, it will no longer observe the
// consumed notification.
//
// To work around this, intercept all events from fdnotifier and cache them
// for the first following call to Readiness(), essentially replicating the
// driver's behavior.
internalQueue waiter.Queue
internalEntry waiter.Entry
cachedEvents atomicbitops.Uint64
appQueue waiter.Queue
// clients are handles of clients owned by this frontendFD. clients is
// protected by dev.nvp.clientsMu.
clients map[*rootClient]struct{}
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *frontendFD) Release(ctx context.Context) {
fdnotifier.RemoveFD(fd.hostFD)
fd.appQueue.Notify(waiter.EventHUp)
fd.dev.nvp.fdsMu.Lock()
delete(fd.dev.nvp.frontendFDs, fd)
fd.dev.nvp.fdsMu.Unlock()
fd.dev.nvp.clientsMu.Lock()
clients := fd.clients
fd.clients = nil
fd.dev.nvp.clientsMu.Unlock()
fd.memmapFile.MmapFileRefs.Closer = &fd.memmapFile
fd.memmapFile.nvp = fd.dev.nvp
fd.memmapFile.clients = clients
fd.memmapFile.MappableRelease() // eventually closes fd.hostFD, releases clients
}
// EventRegister implements waiter.Waitable.EventRegister.
func (fd *frontendFD) EventRegister(e *waiter.Entry) error {
fd.appQueue.EventRegister(e)
return nil
}
// EventUnregister implements waiter.Waitable.EventUnregister.
func (fd *frontendFD) EventUnregister(e *waiter.Entry) {
fd.appQueue.EventUnregister(e)
}
// Readiness implements waiter.Waitable.Readiness.
func (fd *frontendFD) Readiness(mask waiter.EventMask) waiter.EventMask {
for {
cachedEvents := waiter.EventMask(fd.cachedEvents.Load())
maskedEvents := cachedEvents & mask
if maskedEvents == 0 {
// Poll for all events and cache any not consumed by this call.
events := fdnotifier.NonBlockingPoll(fd.hostFD, waiter.AllEvents)
if unmaskedEvents := events &^ mask; unmaskedEvents != 0 {
fd.cacheEvents(unmaskedEvents)
}
return events & mask
}
if fd.cachedEvents.CompareAndSwap(uint64(cachedEvents), uint64(cachedEvents&^maskedEvents)) {
return maskedEvents
}
}
}
func (fd *frontendFD) cacheEvents(mask waiter.EventMask) {
for {
oldEvents := waiter.EventMask(fd.cachedEvents.Load())
newEvents := oldEvents | mask
if oldEvents == newEvents {
break
}
if fd.cachedEvents.CompareAndSwap(uint64(oldEvents), uint64(newEvents)) {
break
}
}
}
// NotifyEvent implements waiter.EventListener.NotifyEvent.
func (fd *frontendFD) NotifyEvent(mask waiter.EventMask) {
// Events must be cached before notifying fd.appQueue, in order to ensure
// that the first notified waiter to call fd.Readiness() sees the
// newly-cached events.
fd.cacheEvents(mask)
fd.appQueue.Notify(mask)
}
// Epollable implements vfs.FileDescriptionImpl.Epollable.
func (fd *frontendFD) Epollable() bool {
return true
}
// Ioctl implements vfs.FileDescriptionImpl.Ioctl.
func (fd *frontendFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
cmd := args[1].Uint()
nr := linux.IOC_NR(cmd)
argPtr := args[2].Pointer()
argSize := linux.IOC_SIZE(cmd)
t := kernel.TaskFromContext(ctx)
if t == nil {
panic("Ioctl should be called from a task context")
}
if ctx.IsLogging(log.Debug) {
ctx.Debugf("nvproxy: frontend ioctl: nr = %d = %#x, argSize = %d", nr, nr, argSize)
}
fi := frontendIoctlState{
fd: fd,
ctx: ctx,
t: t,
nr: nr,
ioctlParamsAddr: argPtr,
ioctlParamsSize: argSize,
}
// nr determines the argument type.
// Implementors:
// - To map nr to a symbol, look in
// src/nvidia/arch/nvalloc/unix/include/nv_escape.h,
// kernel-open/common/inc/nv-ioctl-numbers.h, and
// kernel-open/common/inc/nv-ioctl-numa.h.
// - To determine the parameter type, find the implementation in
// kernel-open/nvidia/nv.c:nvidia_ioctl() or
// src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl().
// - Add symbol and parameter type definitions to //pkg/abi/nvgpu.
// - Add filter to seccomp_filters.go.
// - Add handling below.
result, err := fd.dev.nvp.abi.frontendIoctl[nr].handle(&fi)
if err != nil {
if handleErr, ok := err.(*errHandler); ok {
fi.ctx.Warningf("nvproxy: %v for frontend ioctl %d == %#x (argSize=%d, cmd=%#x)", handleErr, nr, nr, argSize, cmd)
return 0, linuxerr.EINVAL
}
}
return result, err
}
// IsNvidiaDeviceFD implements NvidiaDeviceFD.IsNvidiaDeviceFD.
func (fd *frontendFD) IsNvidiaDeviceFD() {}
func frontendIoctlCmd(nr, argSize uint32) uintptr {
return uintptr(linux.IOWR(nvgpu.NV_IOCTL_MAGIC, nr, argSize))
}
// frontendIoctlState holds the state of a call to frontendFD.Ioctl().
type frontendIoctlState struct {
fd *frontendFD
ctx context.Context
t *kernel.Task
nr uint32
ioctlParamsAddr hostarch.Addr
ioctlParamsSize uint32
}
// frontendIoctlSimple implements a frontend ioctl whose parameters don't
// contain any pointers requiring translation, file descriptors, or special
// cases or effects.
func frontendIoctlSimple[Params any, PtrParams hasStatusPtr[Params]](fi *frontendIoctlState) (uintptr, error) {
var ioctlParamsValue Params
ioctlParams := PtrParams(&ioctlParamsValue)
if int(fi.ioctlParamsSize) != ioctlParams.SizeBytes() {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
n, err := frontendIoctlInvoke(fi, ioctlParams)
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
// frontendIoctlSimpleNoStatus is the same as frontendIoctlSimple, but for
// ioctls whose parameters don't contain a NvStatus field.
func frontendIoctlSimpleNoStatus[Params any, PtrParams marshalPtr[Params]](fi *frontendIoctlState) (uintptr, error) {
var ioctlParamsValue Params
ioctlParams := PtrParams(&ioctlParamsValue)
if int(fi.ioctlParamsSize) != ioctlParams.SizeBytes() {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
n, err := frontendIoctlInvokeNoStatus(fi, ioctlParams)
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
// frontendIoctlBytes is like frontendIoctlSimple, but for "simple" ioctls that
// don't have a fixed-size parameter type (like NvU32 array). So these can be
// directly copied into byte buffers and proxied to the host.
func frontendIoctlBytes(fi *frontendIoctlState) (uintptr, error) {
if fi.ioctlParamsSize == 0 {
return frontendIoctlInvokeNoStatus[byte](fi, nil)
}
ioctlParams := make([]byte, fi.ioctlParamsSize)
if _, err := fi.t.CopyInBytes(fi.ioctlParamsAddr, ioctlParams); err != nil {
return 0, err
}
n, err := frontendIoctlInvokeNoStatus(fi, &ioctlParams[0])
if err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(fi.ioctlParamsAddr, ioctlParams); err != nil {
return n, err
}
return n, nil
}
func rmNumaInfo(fi *frontendIoctlState) (uintptr, error) {
// The CPU topology seen by the host driver differs from the CPU
// topology presented by the sentry to the application, so reject this
// ioctl; doing so is non-fatal.
log.Debugf("nvproxy: ignoring NV_ESC_NUMA_INFO")
return 0, linuxerr.EINVAL
}
func frontendRegisterFD(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.IoctlRegisterFD
if fi.ioctlParamsSize != nvgpu.SizeofIoctlRegisterFD {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
ctlFileGeneric, _ := fi.t.FDTable().Get(ioctlParams.CtlFD)
if ctlFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer ctlFileGeneric.DecRef(fi.ctx)
ctlFile, ok := ctlFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
ioctlParams.CtlFD = ctlFile.hostFD
// The returned ctl_fd can't change, so skip copying out.
return frontendIoctlInvokeNoStatus(fi, &ioctlParams)
}
// frontendExportToDMABufFD handles NV_ESC_EXPORT_TO_DMABUF_FD. CUDA uses this
// to export GPU memory as a dma-buf fd for ibv_reg_dmabuf_mr (GPUDirect RDMA).
func frontendExportToDMABufFD[Params any, PtrParams hasFrontendFDAndStatusPtr[Params]](fi *frontendIoctlState) (uintptr, error) {
var ioctlParamsValue Params
ioctlParams := PtrParams(&ioctlParamsValue)
if int(fi.ioctlParamsSize) != ioctlParams.SizeBytes() {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
// The FD field is both input and output (see nv_dma_buf_export() in
// kernel-open/nvidia/nv-dmabuf.c):
// - FD == -1: the driver creates a new dma-buf and returns its fd by
// updating the ioctlParams. We wrap that host fd in a new application
// FD installed in the task's fd table and return it via ioctlParams to
// the app, so the app can hand it to the RDMA verbs library.
// - FD >= 0: the app is appending more handles to a dma-buf it exported
// in an earlier call, identified by that fd. The value is an app FD,
// we translate it back to the host fd of the dma-buf we wrapped
// before. The driver leaves the host fd unchanged, so we restore the
// app fd on the way out.
if appFD := ioctlParams.GetFrontendFD(); appFD >= 0 {
fileGeneric, _ := fi.t.FDTable().Get(appFD)
if fileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer fileGeneric.DecRef(fi.ctx)
wrapper, ok := fileGeneric.Impl().(*dmaBufFDWrapper)
if !ok {
return 0, linuxerr.EINVAL
}
ioctlParams.SetFrontendFD(wrapper.hostFD)
n, err := frontendIoctlInvokeNoStatus(fi, ioctlParams)
ioctlParams.SetFrontendFD(appFD)
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
// Create path: FD == -1 (any other negative value is rejected by the
// driver, so FD < -1 will fail below).
n, err := frontendIoctlInvokeNoStatus(fi, ioctlParams)
if err != nil {
return n, err
}
// The driver only allocates a dma-buf fd when the export succeeded.
if ioctlParams.GetStatus() == nvgpu.NV_OK && ioctlParams.GetFrontendFD() >= 0 {
sandboxFD, err := newDMABufFDWrapper(fi.t, ioctlParams.GetFrontendFD())
if err != nil {
return n, err
}
ioctlParams.SetFrontendFD(sandboxFD)
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
// Roll back the fd we installed so the guest doesn't retain a
// dma-buf it never learned the number of. Remove drops the table's
// reference; DecRef releases it (closing the host fd).
if removed := fi.t.FDTable().Remove(fi.ctx, sandboxFD); removed != nil {
removed.DecRef(fi.ctx)
}
return n, err
}
return n, nil
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
// dmaBufFDWrapper wraps a host dma-buf fd returned by the NVIDIA driver's
// export operation as an application FD. It implements vfs.HostFDProvider so
// that rdmaproxy can recover the host fd to hand to ibv_reg_dmabuf_mr.
//
// +stateify savable
type dmaBufFDWrapper struct {
vfsfd vfs.FileDescription
vfs.FileDescriptionDefaultImpl
vfs.DentryMetadataFileDescriptionImpl
vfs.NoLockFD
hostFD int32
}
// HostFD implements vfs.HostFDProvider.HostFD.
func (fd *dmaBufFDWrapper) HostFD() int {
return int(fd.hostFD)
}
// Release implements vfs.FileDescriptionImpl.Release.
func (fd *dmaBufFDWrapper) Release(ctx context.Context) {
unix.Close(int(fd.hostFD))
}
// newDMABufFDWrapper takes ownership of hostFD, wraps it in an application
// FileDescription and installs it in t's FD table. It returns the application
// FD number.
func newDMABufFDWrapper(t *kernel.Task, hostFD int32) (int32, error) {
vfsObj := t.Kernel().VFS()
vd := vfsObj.NewAnonVirtualDentry("[nvidia-dmabuf]")
defer vd.DecRef(t)
w := &dmaBufFDWrapper{hostFD: hostFD}
if err := w.vfsfd.Init(w, linux.O_RDWR, t.Credentials(), vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{
UseDentryMetadata: true,
}); err != nil {
unix.Close(int(hostFD))
return -1, err
}
defer w.vfsfd.DecRef(t)
return t.NewFDFrom(0, &w.vfsfd, kernel.FDFlags{CloseOnExec: true})
}
func frontendIoctlHasFD[Params any, PtrParams hasFrontendFDAndStatusPtr[Params]](fi *frontendIoctlState) (uintptr, error) {
var ioctlParamsValue Params
ioctlParams := PtrParams(&ioctlParamsValue)
if int(fi.ioctlParamsSize) != ioctlParams.SizeBytes() {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
origFD := ioctlParams.GetFrontendFD()
eventFileGeneric, _ := fi.t.FDTable().Get(origFD)
if eventFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer eventFileGeneric.DecRef(fi.ctx)
eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
ioctlParams.SetFrontendFD(eventFile.hostFD)
n, err := frontendIoctlInvoke(fi, ioctlParams)
ioctlParams.SetFrontendFD(origFD)
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
func rmAllocContextDMA2(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.NVOS39_PARAMETERS
if fi.ioctlParamsSize != nvgpu.SizeofNVOS39Parameters {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
if log.IsLogging(log.Debug) {
fi.ctx.Debugf("nvproxy: NV_ESC_RM_ALLOC_CONTEXT_DMA2 class %v", ioctlParams.HClass)
}
// See src/nvidia/interface/deprecated/rmapi_deprecated_misc.c:RmDeprecatedAllocContextDma().
// HObjectParent acts as the client ...
client, unlock := fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.HObjectParent)
if client == nil {
return 0, frontendFailWithStatus(fi, &ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
}
n, err := frontendIoctlInvoke(fi, &ioctlParams)
if err == nil && ioctlParams.Status == nvgpu.NV_OK {
// ... and HMemory's parent acts as the parent of the new object.
if hMemory := client.getObject(fi.ctx, ioctlParams.HMemory); hMemory != nil {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, &miscObject{}, hMemory.parent)
}
}
unlock()
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
func rmAllocMemory(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.IoctlNVOS02ParametersWithFD
if fi.ioctlParamsSize != nvgpu.SizeofIoctlNVOS02ParametersWithFD {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
if log.IsLogging(log.Debug) {
fi.ctx.Debugf("nvproxy: NV_ESC_RM_ALLOC_MEMORY class %v", ioctlParams.Params.HClass)
}
// See src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl() and
// src/nvidia/interface/deprecated/rmapi_deprecated_allocmemory.c:rmAllocMemoryTable
// for implementation.
switch ioctlParams.Params.HClass {
case nvgpu.NV01_MEMORY_SYSTEM:
return rmAllocMemorySystem(fi, &ioctlParams)
case nvgpu.NV01_MEMORY_LOCAL_PRIVILEGED, nvgpu.NV01_MEMORY_LOCAL_USER:
return rmAllocMemorySimple(fi, &ioctlParams)
case nvgpu.NV01_MEMORY_SYSTEM_OS_DESCRIPTOR:
return rmAllocOSDescriptor(fi, &ioctlParams)
default:
fi.ctx.Warningf("nvproxy: %s for NV_ESC_RM_ALLOC_MEMORY class %v", errUndefinedHandler.Error(), ioctlParams.Params.HClass)
return 0, linuxerr.EINVAL
}
}
func rmAllocMemorySystem(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02ParametersWithFD) (uintptr, error) {
// In src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl(), see case
// cmd == NV_ESC_RM_ALLOC_MEMORY with pParms->hClass == NV01_MEMORY_SYSTEM.
mapFileGeneric, _ := fi.t.FDTable().Get(ioctlParams.FD)
if mapFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer mapFileGeneric.DecRef(fi.ctx)
mapFile, ok := mapFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
// "If the system memory is going to be mapped immediately, create the mmap
// context for it now."
createMmapCtx :=
// !FLD_TEST_DRF(OS02, _FLAGS, _ALLOC, _NONE, flags))
((ioctlParams.Params.Flags>>nvgpu.NVOS02_FLAGS_ALLOC_SHIFT)&nvgpu.NVOS02_FLAGS_ALLOC_MASK) != nvgpu.NVOS02_FLAGS_ALLOC_NONE &&
// !FLD_TEST_DRF(OS02, _FLAGS, _MAPPING, _NO_MAP, flags))
((ioctlParams.Params.Flags>>nvgpu.NVOS02_FLAGS_MAPPING_SHIFT)&nvgpu.NVOS02_FLAGS_MAPPING_MASK) != nvgpu.NVOS02_FLAGS_MAPPING_NO_MAP
if createMmapCtx {
mapFile.memmapFile.mmapMu.Lock()
defer mapFile.memmapFile.mmapMu.Unlock()
if mapFile.memmapFile.mmapLength != 0 {
fi.ctx.Warningf("nvproxy: attempted to reuse FD %d for NV_ESC_RM_MAP_MEMORY", ioctlParams.FD)
return 0, linuxerr.EINVAL
}
}
origFD := ioctlParams.FD
ioctlParams.FD = mapFile.hostFD
client, unlock := fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.Params.HRoot)
if client == nil {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
}
n, err := frontendIoctlInvoke(fi, ioctlParams)
if err == nil && ioctlParams.Params.Status == nvgpu.NV_OK {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.Params.HObjectNew, ioctlParams.Params.HClass, &miscObject{}, ioctlParams.Params.HObjectParent)
if createMmapCtx {
mapFile.memmapFile.mmapLength = ioctlParams.Params.Limit + 1
mapFile.memmapFile.memType = getMemoryType(fi.ctx, mapFile.dev, nvgpu.NVOS33_FLAGS_CACHING_TYPE_DEFAULT)
}
}
unlock()
ioctlParams.FD = origFD
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
func rmAllocMemorySimple(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02ParametersWithFD) (uintptr, error) {
// These shouldn't use ioctlParams.FD; clobber it to be sure.
origFD := ioctlParams.FD
ioctlParams.FD = -1
client, unlock := fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.Params.HRoot)
if client == nil {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
}
n, err := frontendIoctlInvoke(fi, ioctlParams)
if err == nil && ioctlParams.Params.Status == nvgpu.NV_OK {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.Params.HObjectNew, ioctlParams.Params.HClass, &miscObject{}, ioctlParams.Params.HObjectParent)
}
unlock()
ioctlParams.FD = origFD
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
var madvPopulateWriteDisabled atomicbitops.Bool
func rmAllocOSDescriptor(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02ParametersWithFD) (uintptr, error) {
// Compare src/nvidia/arch/nvalloc/unix/src/escape.c:RmAllocOsDescriptor()
// => RmCreateOsDescriptor().
appAddr := addrFromP64(ioctlParams.Params.PMemory)
if !appAddr.IsPageAligned() {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_NOT_SUPPORTED)
}
arLen := ioctlParams.Params.Limit + 1
if arLen == 0 { // integer overflow
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_LIMIT)
}
var ok bool
arLen, ok = hostarch.PageRoundUp(arLen)
if !ok {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ADDRESS)
}
appAR, ok := appAddr.ToRange(arLen)
if !ok {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ADDRESS)
}
// The host driver will collect pages from our address space starting at
// PMemory, so we need a contiguous mapping equivalent to the
// application's.
at := hostarch.Read
if ((ioctlParams.Params.Flags >> 21) & 0x1) == 0 /* NVOS02_FLAGS_ALLOC_USER_READ_ONLY_NO */ {
at.Write = true
}
prs, err := fi.t.MemoryManager().Pin(fi.ctx, appAR, at, false /* ignorePermissions */)
unpinCleanup := cleanup.Make(func() {
mm.Unpin(prs)
})
defer unpinCleanup.Clean()
if err != nil {
return 0, err
}
var m uintptr
mOwned := false
if len(prs) == 1 {
pr := prs[0]
ims, err := pr.File.MapInternal(memmap.FileRange{pr.Offset, pr.Offset + uint64(pr.Source.Length())}, at)
if err != nil {
return 0, err
}
if ims.NumBlocks() == 1 {
// We can use this singular internal mapping directly.
m = ims.Head().Addr()
}
}
if m == 0 {
// Reserve a range in our address space.
var errno unix.Errno
m, _, errno = unix.RawSyscall6(unix.SYS_MMAP, 0 /* addr */, uintptr(arLen), unix.PROT_NONE, unix.MAP_PRIVATE|unix.MAP_ANONYMOUS, ^uintptr(0) /* fd */, 0 /* offset */)
if errno != 0 {
return 0, errno
}
mOwned = true
unpinCleanup.Add(func() {
unix.RawSyscall(unix.SYS_MUNMAP, m, uintptr(arLen), 0)
})
// Mirror application mappings into the reserved range.
sentryAddr := uintptr(m)
for _, pr := range prs {
ims, err := pr.File.MapInternal(memmap.FileRange{pr.Offset, pr.Offset + uint64(pr.Source.Length())}, at)
if err != nil {
return 0, err
}
for !ims.IsEmpty() {
im := ims.Head()
if _, _, errno := unix.RawSyscall6(unix.SYS_MREMAP, im.Addr(), 0 /* old_size */, uintptr(im.Len()), linux.MREMAP_MAYMOVE|linux.MREMAP_FIXED, sentryAddr, 0); errno != 0 {
return 0, errno
}
sentryAddr += uintptr(im.Len())
ims = ims.Tail()
}
}
}
if !madvPopulateWriteDisabled.Load() {
// In the kernel driver,
// src/nvidia/arch/nvalloc/unix/src/escape.c:RmAllocOsDescriptor() =>
// RmCreateOsDescriptor() =>
// kernel-open/nvidia/os-mlock.c:os_lock_user_pages() will call
// NV_PIN_USER_PAGES() => (Linux) mm/gup.c:pin_user_pages() with
// gup_flags=FOLL_WRITE|FOLL_LONGTERM. pin_user_pages() calls
// is_valid_gup_args(locked=NULL), so FOLL_UNLOCKABLE is *not* added to
// gup_flags. Consequently, if pin_user_pages() needs to fault in
// pages, it will not unlock mmap_lock while doing so.
//
// If another thread attempts to lock mmap_lock for writing (in this
// context, this typically occurs when another process also tries to
// rmAllocOSDescriptor() and calls mmap or mremap above), that thread
// will block until mmap_lock is released, and will also prevent other
// threads from locking mmap_lock for reading.
//
// To avoid this, fault in these pages via MADV_POPULATE_WRITE;
// mm/madvise.c:madvise_populate() => mm/gup.c:faultin_page_range()
// does pass FOLL_UNLOCKABLE to __get_user_pages_locked().
if _, _, errno := unix.Syscall(unix.SYS_MADVISE, m, uintptr(arLen), unix.MADV_POPULATE_WRITE); errno != 0 {
if !madvPopulateWriteDisabled.Swap(true) {
log.Infof("nvproxy: disabling MADV_POPULATE_WRITE before NV01_MEMORY_SYSTEM_OS_DESCRIPTOR allocation: %s", errno)
}
}
}
origPMemory := ioctlParams.Params.PMemory
ioctlParams.Params.PMemory = nvgpu.P64(uint64(m))
// NV01_MEMORY_SYSTEM_OS_DESCRIPTOR shouldn't use ioctlParams.FD; clobber
// it to be sure.
origFD := ioctlParams.FD
ioctlParams.FD = -1
client, unlock := fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.Params.HRoot)
if client == nil {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
}
n, err := frontendIoctlInvoke(fi, ioctlParams)
if err == nil && ioctlParams.Params.Status == nvgpu.NV_OK {
// Transfer ownership of pinned pages to an osDescMem object, to be
// unpinned when the driver OsDescMem is freed.
obj := &osDescMem{
pinnedRanges: prs,
}
if mOwned {
// Transfer ownership of the temporary mapping as well. It isn't
// actually needed anymore, but unmapping can be very expensive and
// allocation tends to be a critical path, so not unmapping it
// until the osDescMem object is released improves performance.
obj.m = m
obj.len = uintptr(arLen)
}
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.Params.HObjectNew, ioctlParams.Params.HClass, obj, ioctlParams.Params.HObjectParent)
unpinCleanup.Release()
if fi.ctx.IsLogging(log.Debug) {
fi.ctx.Debugf("nvproxy: pinned %d bytes for OS descriptor with handle %v", arLen, ioctlParams.Params.HObjectNew)
}
}
unlock()
ioctlParams.Params.PMemory = origPMemory
ioctlParams.FD = origFD
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
func rmDupObject(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.NVOS55_PARAMETERS
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
clientDst, clientSrc, unlock := nvp.getClientsWithLock(fi.ctx, ioctlParams.HClient, ioctlParams.HClientSrc)
if clientDst == nil || clientSrc == nil {
return 0, frontendFailWithStatus(fi, &ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
}
n, err := frontendIoctlInvoke(fi, &ioctlParams)
if err == nil && ioctlParams.Status == nvgpu.NV_OK {
nvp.objDup(fi.ctx, clientDst, clientSrc, ioctlParams.HObject, ioctlParams.HParent, ioctlParams.HObjectSrc)
}
unlock()
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.NVOS00_PARAMETERS
if fi.ioctlParamsSize != nvgpu.SizeofNVOS00Parameters {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
client, unlock := fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.HRoot)
if client == nil {
return 0, frontendFailWithStatus(fi, &ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
}
n, err := frontendIoctlInvoke(fi, &ioctlParams)
var deferReleases []func()
if err == nil && ioctlParams.Status == nvgpu.NV_OK {
deferReleases = fi.fd.dev.nvp.objFree(fi.ctx, client, ioctlParams.HObjectOld)
}
unlock()
for _, release := range deferReleases {
release()
}
if err != nil {
return n, err
}
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
func rmControl(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.NVOS54_PARAMETERS
if fi.ioctlParamsSize != nvgpu.SizeofNVOS54Parameters {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
// Cmd determines the type of Params.
if log.IsLogging(log.Debug) {
fi.ctx.Debugf("nvproxy: control command %#x, object %#x", ioctlParams.Cmd, ioctlParams.HObject.Val)
}
if ioctlParams.Cmd&nvgpu.RM_GSS_LEGACY_MASK != 0 {
// This is a "legacy GSS control" that is implemented by the GPU System
// Processor (GSP). Consequently, its parameters cannot reasonably
// contain application pointers, and the control is in any case
// undocumented.
// See
// src/nvidia/src/kernel/rmapi/entry_points.c:_nv04ControlWithSecInfo()
// =>
// src/nvidia/interface/deprecated/rmapi_deprecated_control.c:RmDeprecatedGetControlHandler()
// =>
// src/nvidia/interface/deprecated/rmapi_gss_legacy_control.c:RmGssLegacyRpcCmd().
return rmControlSimple(fi, &ioctlParams)
}
if (ioctlParams.Cmd>>16)&0xffff == nvgpu.NV2081_BINAPI {
// NV2081_BINAPI forwards all control commands to the GSP in
// src/nvidia/src/kernel/rmapi/binary_api.c:binapiControl_IMPL().
// Consequently, its parameters cannot reasonably contain pointers.
return rmControlSimple(fi, &ioctlParams)
}
// Implementors:
// - Top two bytes of Cmd specifies class; third byte specifies category;
// fourth byte specifies "message ID" (command within class/category).
// e.g. 0x800288:
// - Class 0x0080 => look in
// src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080base.h for categories.
// - Category 0x02 => NV0080_CTRL_GPU => look in
// src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080gpu.h for
// `#define NV0080_CTRL_CMD_GPU_QUERY_SW_STATE_PERSISTENCE (0x800288)`
// and accompanying documentation, parameter type.
// - If this fails, or to find implementation, grep for `methodId=.*0x<Cmd
// in lowercase hex without leading 0s>` to find entry in g_*_nvoc.c;
// implementing function is is "pFunc".
// - Add symbol definition to //pkg/abi/nvgpu. Parameter type definition is
// only required for non-simple commands.
// - Add handling below.
result, err := fi.fd.dev.nvp.abi.controlCmd[ioctlParams.Cmd].handle(fi, &ioctlParams)
if err != nil {
if handleErr, ok := err.(*errHandler); ok {
fi.ctx.Warningf("nvproxy: %v for control command %#x (paramsSize=%d)", handleErr, ioctlParams.Cmd, ioctlParams.ParamsSize)
// See src/nvidia/src/libraries/resserv/src/rs_resource.c:resControlLookup_IMPL()
// when pEntry is null.
ioctlParams.Status = nvgpu.NV_ERR_NOT_SUPPORTED
_, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr)
return 0, err
}
}
return result, err
}
func rmControlSimple(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
if ioctlParams.ParamsSize == 0 {
if ioctlParams.Params != 0 {
return 0, linuxerr.EINVAL
}
return rmControlInvoke[byte](fi, ioctlParams, nil)
}
if ioctlParams.Params == 0 {
return 0, linuxerr.EINVAL
}
if ioctlParams.ParamsSize > nvgpu.RMAPI_PARAM_COPY_MAX_PARAMS_SIZE {
return 0, linuxerr.EINVAL
}
ctrlParams := make([]byte, ioctlParams.ParamsSize)
if _, err := fi.t.CopyInBytes(addrFromP64(ioctlParams.Params), ctrlParams); err != nil {
return 0, err
}
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams[0])
if err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(addrFromP64(ioctlParams.Params), ctrlParams); err != nil {
return n, err
}
return n, nil
}
func ctrlHasFrontendFD[Params any, PtrParams hasFrontendFDPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParamsValue Params
ctrlParams := PtrParams(&ctrlParamsValue)
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
origFD := ctrlParams.GetFrontendFD()
ctlFileGeneric, _ := fi.t.FDTable().Get(origFD)
if ctlFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer ctlFileGeneric.DecRef(fi.ctx)
ctlFile, ok := ctlFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
ctrlParams.SetFrontendFD(ctlFile.hostFD)
n, err := rmControlInvoke(fi, ioctlParams, ctrlParams)
ctrlParams.SetFrontendFD(origFD)
if err != nil {
return n, err
}
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return n, err
}
return n, nil
}
func ctrlMemoryMulticastFabricAttachGPU(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParams nvgpu.NV00FD_CTRL_ATTACH_GPU_PARAMS
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
origDevDescriptor := ctrlParams.DevDescriptor
devDescriptor, _ := fi.t.FDTable().Get(int32(origDevDescriptor))
if devDescriptor == nil {
return 0, linuxerr.EINVAL
}
defer devDescriptor.DecRef(fi.ctx)
devDesc, ok := devDescriptor.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
ctrlParams.DevDescriptor = uint64(devDesc.hostFD)
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
ctrlParams.DevDescriptor = origDevDescriptor
// Note that ctrlParams.CopyOut() is not called here because
// NV00FD_CTRL_ATTACH_GPU_PARAMS is an input-only parameter.
return n, err
}
// Check parameter size against the limit of how much can be copied in. It
// returns true if the param size is within limit. If not, caller should return
// NV_ERR_INVALID_ARGUMENT status to the application. Compare
// src/nvidia/src/kernel/rmapi/param_copy.c:rmapiParamsAcquire().
// To find `numElems` and `sizeOfElem` values for a given control command, see
// src/nvidia/src/kernel/rmapi/embedded_param_copy.c:embeddedParamCopyIn() =>
// case <CONTROL CMD> => RMAPI_PARAM_COPY_INIT(..., numElems, sizeOfElem).
func rmapiParamsSizeCheck(numElems uint32, sizeOfElem uint32) bool {
if numElems == 0 {
// The individual implementations of rmapi commands return
// NV_ERR_INVALID_ARGUMENT when numElems is 0. Some examples are:
// - subdeviceCtrlCmdFbGetInfo_IMPL()
// - deviceCtrlCmdKGrGetInfo_IMPL() => _kgraphicsCtrlCmdGrGetInfoV2()
return false
}
// Cast to uint64 to handle overflows. In the driver, this is handled by
// portSafeMulU32().
return uint64(numElems)*uint64(sizeOfElem) <= nvgpu.RMAPI_PARAM_COPY_MAX_PARAMS_SIZE
}
func ctrlClientSystemGetBuildVersion(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParams nvgpu.NV0000_CTRL_SYSTEM_GET_BUILD_VERSION_PARAMS
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
if ctrlParams.PDriverVersionBuffer == 0 || ctrlParams.PVersionBuffer == 0 || ctrlParams.PTitleBuffer == 0 {
// No strings are written if any are null. See
// src/nvidia/interface/deprecated/rmapi_deprecated_control.c:V2_CONVERTER(_NV0000_CTRL_CMD_SYSTEM_GET_BUILD_VERSION).
return ctrlClientSystemGetBuildVersionInvoke(fi, ioctlParams, &ctrlParams, nil, nil, nil)
}
// Need to buffer strings for copy-out.
if ctrlParams.SizeOfStrings == 0 {
return 0, linuxerr.EINVAL
}
// The driver internally uses NV0000_CTRL_CMD_SYSTEM_GET_BUILD_VERSION_V2 to
// fetch the version strings, which has a maximum string size limit. Any
// extra buffer space provided by the user is ignored.
ctrlParams.SizeOfStrings = min(ctrlParams.SizeOfStrings, nvgpu.NV0000_CTRL_SYSTEM_GET_BUILD_VERSION_V2_MAX_STRING_SIZE)
driverVersionBuf := make([]byte, ctrlParams.SizeOfStrings)
versionBuf := make([]byte, ctrlParams.SizeOfStrings)
titleBuf := make([]byte, ctrlParams.SizeOfStrings)
n, err := ctrlClientSystemGetBuildVersionInvoke(fi, ioctlParams, &ctrlParams, &driverVersionBuf[0], &versionBuf[0], &titleBuf[0])
if err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PDriverVersionBuffer), driverVersionBuf); err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PVersionBuffer), versionBuf); err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PTitleBuffer), titleBuf); err != nil {
return n, err
}
return n, nil
}
func ctrlGetNvU32List(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParams nvgpu.RmapiParamNvU32List
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
var list []uint32
if ctrlParams.List != 0 {
if !rmapiParamsSizeCheck(ctrlParams.NumElems, 4 /* sizeof(NvU32) */) {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
}
list = make([]uint32, ctrlParams.NumElems)
if _, err := primitive.CopyUint32SliceIn(fi.t, addrFromP64(ctrlParams.List), list); err != nil {
return 0, err
}
}
return ctrlGetNvU32ListInvoke(fi, ioctlParams, &ctrlParams, list)
}
func ctrlDevGetCaps(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParams nvgpu.NV0080_CTRL_GET_CAPS_PARAMS
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
if !rmapiParamsSizeCheck(ctrlParams.CapsTblSize, 1) {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
}
capsTbl := make([]byte, ctrlParams.CapsTblSize)
// No need to copy into capsTbl from ctrlParams.CapsTbl. All callers specify
// RMAPI_PARAM_COPY_FLAGS_SKIP_COPYIN and RMAPI_PARAM_COPY_FLAGS_ZERO_BUFFER
// in src/nvidia/src/kernel/rmapi/embedded_param_copy.c:embeddedParamCopyIn().
return ctrlDevGRGetCapsInvoke(fi, ioctlParams, &ctrlParams, capsTbl)
}
func ctrlRegisterVASpace(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParams nvgpu.NV503C_CTRL_REGISTER_VA_SPACE_PARAMS
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
client, unlock := fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.HClient)
if client == nil {
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
}
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
if err == nil && ioctlParams.Status == nvgpu.NV_OK {
// src/nvidia/src/kernel/gpu/bus/third_party_p2p.c:CliAddThirdPartyP2PVASpace()
// => refAddDependant()
client.objAddDep(ioctlParams.HObject, ctrlParams.HVASpace)
}
unlock()
if err != nil {
return n, err
}
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return n, err
}
return n, nil
}
func ctrlSubdevFIFODisableChannels(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParams nvgpu.NV2080_CTRL_FIFO_DISABLE_CHANNELS_PARAMS
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
// This pointer must be NULL; see
// src/nvidia/src/kernel/gpu/fifo/kernel_fifo_ctrl.c:subdeviceCtrlCmdFifoDisableChannels_IMPL().
// Consequently, we don't need to translate it, but we do want to ensure
// that it actually is NULL.
if ctrlParams.PRunlistPreemptEvent != 0 {
return 0, linuxerr.EINVAL
}
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
if err != nil {
return n, err
}
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return n, err
}
return n, nil
}
func ctrlGpuGetIDInfo(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
var ctrlParams nvgpu.NV0000_CTRL_GPU_GET_ID_INFO_PARAMS
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
return 0, linuxerr.EINVAL
}
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
return 0, err
}
// szName is not used anywhere in the driver, so we explicitly set it to null.
// See src/nvidia/src/kernel/gpu_mgr/gpu_mgr.c::gpumgrGetGpuIdInfo().
ctrlParams.SzName = 0
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
if err != nil {
return n, err
}
_, err = ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params))
return n, err
}
func rmAlloc(fi *frontendIoctlState) (uintptr, error) {
var isNVOS64 bool
switch fi.ioctlParamsSize {
case nvgpu.SizeofNVOS21Parameters:
case nvgpu.SizeofNVOS64Parameters:
isNVOS64 = true
default:
return 0, linuxerr.EINVAL
}
// Copy in parameters and convert to NVOS64Parameters, which is a super
// set of all parameter types we support.
buf := nvgpu.GetRmAllocParamObj(isNVOS64)
if _, err := buf.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
ioctlParams := buf.ToOS64()
// hClass determines the type of pAllocParms.
if err := fixupHClass(fi, &ioctlParams); err != nil {
return 0, err
}
if log.IsLogging(log.Debug) {
fi.ctx.Debugf("nvproxy: allocation class %v", ioctlParams.HClass)
}
// Implementors:
// - To map hClass to a symbol, look in
// src/nvidia/generated/g_allclasses.h.
// - See src/nvidia/src/kernel/rmapi/resource_list.h for table mapping class
// ("External Class") to the type of pAllocParms ("Alloc Param Info") and
// the class whose constructor interprets it ("Internal Class").
// - Add symbol and parameter type definitions to //pkg/abi/nvgpu.
// - Check constructor for calls to refAddDependant(),
// sessionAddDependant(), or sessionAddDependency(), which need to be
// mirrored by dependencies in the call to nvproxy.objAddLocked().
// - Add handling below.
result, err := fi.fd.dev.nvp.abi.allocationClass[ioctlParams.HClass].handle(fi, &ioctlParams, isNVOS64)
if err != nil {
if handleErr, ok := err.(*errHandler); ok {
fi.ctx.Warningf("nvproxy: %v for allocation class %v", handleErr, ioctlParams.HClass)
// Compare
// src/nvidia/src/kernel/rmapi/alloc_free.c:serverAllocResourceUnderLock(),
// when RsResInfoByExternalClassId() is null.
ioctlParams.Status = nvgpu.NV_ERR_INVALID_CLASS
outIoctlParams := nvgpu.GetRmAllocParamObj(isNVOS64)
outIoctlParams.FromOS64(ioctlParams)
// Any copy-out error from
// src/nvidia/src/kernel/rmapi/alloc_free.c:serverAllocApiCopyOut() is
// discarded.
outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr)
return 0, nil
}
}
return result, err
}
// See src/nvidia/src/kernel/rmapi/alloc_free.c:_fixupAllocParams().
func fixupHClass(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS) error {
// "NV01_EVENT isn't a valid class to allocate so overwrite it with the
// subclass from the event params."
if ioctlParams.HClass == nvgpu.NV01_EVENT {
var allocParams nvgpu.NV0005_ALLOC_PARAMETERS
if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return err
}
if ioctlParams.HClass != allocParams.HClass {
if log.IsLogging(log.Debug) {
fi.ctx.Debugf("nvproxy: overwriting allocation class %#08x with %#08x", ioctlParams.HClass, allocParams.HClass)
}
ioctlParams.HClass = allocParams.HClass
}
}
return nil
}
// rmAllocSimple implements NV_ESC_RM_ALLOC for classes whose parameters don't
// contain any pointers or file descriptors requiring translation, and whose
// objects require no special handling and depend only on their parents.
//
// Unlike frontendIoctlSimple and rmControlSimple, rmAllocSimple requires the
// parameter type since the parameter's size is otherwise unknown.
func rmAllocSimple[Params any, PtrParams marshalPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams[Params, PtrParams](fi, ioctlParams, isNVOS64, addSimpleObjDepParentLocked)
}
// addSimpleObjDepParentLocked implements rmAllocInvoke.addObjLocked for
// classes that require no special handling and depend only on their parents.
func addSimpleObjDepParentLocked[Params any](fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *Params) {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent)
}
func rmAllocSimpleParams[Params any, PtrParams marshalPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool, objAddLocked func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *Params)) (uintptr, error) {
if ioctlParams.PAllocParms == 0 {
return rmAllocInvoke[Params](fi, ioctlParams, nil, isNVOS64, objAddLocked)
}
var allocParamsValue Params
allocParams := PtrParams(&allocParamsValue)
// The driver derives the alloc param size from the class and ignores the
// client-supplied ioctlParams.ParamsSize; see
// src/nvidia/src/kernel/rmapi/alloc_free.c:serverAllocApiCopyIn() =>
// rmapiGetClassAllocParamSize(). nvproxy copies in the fixed-size struct for
// the class, so ParamsSize needs no validation.
if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return 0, err
}
n, err := rmAllocInvoke(fi, ioctlParams, allocParams, isNVOS64, objAddLocked)
if err != nil {
return n, err
}
if _, err := allocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return n, err
}
return n, nil
}
func rmAllocNoParams(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocInvoke[byte](fi, ioctlParams, nil, isNVOS64, addSimpleObjDepParentLocked)
}
// rmAllocEventBuffer handles NV_EVENT_BUFFER allocation.
func rmAllocEventBuffer(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
var allocParams nvgpu.NV_EVENT_BUFFER_ALLOC_PARAMETERS
if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return 0, err
}
if allocParams.HBufferHeader.Val == 0 {
fi.ctx.Warningf("nvproxy: rejecting NV_EVENT_BUFFER allocation with HBufferHeader == 0 (no translation available)")
return 0, linuxerr.EINVAL
}
// notificationHandle is a frontend FD, which the driver translates for
// non-kernel clients; 0 leaves the event buffer without a notification
// handle. See
// src/nvidia/src/kernel/rmapi/event_buffer.c:eventbufferConstruct_IMPL()
// => osUserHandleToKernelPtr().
origNotificationHandle := allocParams.NotificationHandle
if origNotificationHandle != 0 {
eventFileGeneric, _ := fi.t.FDTable().Get(int32(origNotificationHandle))
if eventFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer eventFileGeneric.DecRef(fi.ctx)
eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
allocParams.NotificationHandle = uint64(eventFile.hostFD)
}
n, err := rmAllocInvoke(fi, ioctlParams, &allocParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_EVENT_BUFFER_ALLOC_PARAMETERS) {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, &miscObject{}, ioctlParams.HObjectParent, allocParams.HBufferHeader, allocParams.HRecordBuffer, allocParams.HVardataBuffer)
})
if err != nil {
return n, err
}
allocParams.NotificationHandle = origNotificationHandle
if _, err := allocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return n, err
}
return n, nil
}
func rmAllocRootClient(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
if !ioctlParams.HClass.IsRootClient() {
panic(fmt.Sprintf("rmAllocRootClient() was invoked with HClass whose IsRootClient()==false: %#x", ioctlParams.HClass))
}
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, _ *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.Handle) {
client := newRootClient(fi.fd, ioctlParams, rightsRequested, allocParams)
nvp := fi.fd.dev.nvp
nvp.clientsMu.Lock()
if _, ok := nvp.clients[ioctlParams.HObjectNew]; ok {
fi.ctx.Warningf("nvproxy: client handle %v already in use", ioctlParams.HObjectNew)
}
nvp.clients[ioctlParams.HObjectNew] = client
if fi.fd.clients == nil {
fi.fd.clients = make(map[*rootClient]struct{})
}
fi.fd.clients[client] = struct{}{}
nvp.clientsMu.Unlock()
client.objsMu.Lock()
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, client, nvgpu.Handle{Val: nvgpu.NV01_NULL_OBJECT} /* parentH */)
client.objsMu.Unlock()
})
}
func rmAllocEventOSEvent(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
var allocParams nvgpu.NV0005_ALLOC_PARAMETERS
if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return 0, err
}
eventFileGeneric, _ := fi.t.FDTable().Get(int32(allocParams.Data))
if eventFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer eventFileGeneric.DecRef(fi.ctx)
eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
origData := allocParams.Data
allocParams.Data = nvgpu.P64(uint64(eventFile.hostFD))
n, err := rmAllocInvoke(fi, ioctlParams, &allocParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV0005_ALLOC_PARAMETERS) {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, &miscObject{}, ioctlParams.HObjectParent)
})
if err != nil {
return n, err
}
allocParams.Data = origData
if _, err := allocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return n, err
}
return n, nil
}
func rmAllocMemoryVirtual(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_MEMORY_VIRTUAL_ALLOCATION_PARAMS) {
// See
// src/nvidia/src/kernel/mem_mgr/virt_mem_range.c:vmrangeConstruct_IMPL()
// => refAddDependant().
hvaSpace := allocParams.HVASpace
if allocParams.HVASpace.Val == nvgpu.NV_MEMORY_VIRTUAL_SYSMEM_DYNAMIC_HVASPACE {
// hvaSpace is not added as a dependency when it is
// NV_MEMORY_VIRTUAL_SYSMEM_DYNAMIC_HVASPACE or NV01_NULL_OBJECT. Set it
// to NV01_NULL_OBJECT, which is ignored in nvp.objAdd().
hvaSpace.Val = nvgpu.NV01_NULL_OBJECT
}
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, hvaSpace)
})
}
func rmAllocSMDebuggerSession(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV83DE_ALLOC_PARAMETERS) {
// Compare
// src/nvidia/src/kernel/gpu/gr/kernel_sm_debugger_session.c:ksmdbgssnConstruct_IMPL()
// => _ShareDebugger() => sessionAddDependency/sessionAddDependant();
// the driver indirects through a per-KernelGraphicsObject
// RmDebuggerSession, which we elide for dependency tracking.
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HClass3DObject)
})
}
func rmAllocContextDMA(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CONTEXT_DMA_ALLOCATION_PARAMS) {
// See
// src/nvidia/src/kernel/gpu/mem_mgr/context_dma.c:ctxdmaConstruct_IMPL()
// => refAddDependant().
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HMemory)
})
}
func rmAllocChannelGroup(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CHANNEL_GROUP_ALLOCATION_PARAMETERS) {
// See
// src/nvidia/src/kernel/gpu/fifo/kernel_channel_group_api.c:kchangrpapiConstruct_IMPL()
// => refAddDependant().
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace)
// Note: When the channel group's engine type is GR, which is always
// true unless MIG is enabled, kchangrpapiConstruct_IMPL() constructs a
// KERNEL_GRAPHICS_CONTEXT whose lifetime is the same as the channel
// group's (the graphics context is freed when the channel group is).
// Channels, context shares, and graphics objects depend on this
// graphics context rather than the channel group. Consequently, if MIG
// is enabled, these might not depend on the channel group at all.
// Since nvproxy currently does not support MIG, we represent these
// dependencies as unconditionally on the channel group instead.
})
}
func rmAllocChannel(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CHANNEL_ALLOC_PARAMS) {
// See
// src/nvidia/src/kernel/gpu/fifo/kernel_channel.c:kchannelConstruct_IMPL()
// => refAddDependant(). The channel's parent may be a device or
// channel group; if it is a channel group then the channel depends on
// it via the parent relationship, and if it is not a channel group
// then kchannelConstruct_IMPL() constructs one internally and frees it
// when the channel is destroyed, so either way no separate dependency
// is required.
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace, allocParams.HContextShare)
})
}
// rmAllocChannelV570 is the same as rmAllocChannel, but for 570.86.15.
func rmAllocChannelV570(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CHANNEL_ALLOC_PARAMS_V570) {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace, allocParams.HContextShare)
})
}
// rmAllocChannelV610 is the same as rmAllocChannel, but for 610.43.02.
func rmAllocChannelV610(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CHANNEL_ALLOC_PARAMS_V610) {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace, allocParams.HContextShare)
})
}
func rmAllocContextShare(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CTXSHARE_ALLOCATION_PARAMETERS) {
// See
// src/nvidia/src/kernel/gpu/fifo/kernel_ctxshare.c:kctxshareapiConstruct_IMPL()
// => refAddDependant(). The context share's parent is the channel
// group, so (given that we are representing graphics context
// dependencies as channel group dependencies) no separate dependency
// is required.
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace)
})
}
func rmAllocIMEXSession(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
var allocParams nvgpu.NV00F1_ALLOCATION_PARAMETERS
// As in rmAllocSimpleParams(), the driver ignores ioctlParams.ParamsSize and
// derives the param size from the class, so it is not validated here.
if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return 0, err
}
origCapDescriptor := allocParams.CapDescriptor
capsFileGeneric, _ := fi.t.FDTable().Get(int32(allocParams.CapDescriptor))
if capsFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer capsFileGeneric.DecRef(fi.ctx)
capsFile, ok := capsFileGeneric.Impl().(*openOnlyFD)
if !ok {
fi.ctx.Warningf("nvproxy: rmAllocIMEXSession got capDescriptor file of incompatible type %T", capsFileGeneric.Impl())
return 0, linuxerr.EINVAL
}
allocParams.CapDescriptor = uint64(capsFile.hostFD)
origEventFD := allocParams.POsEvent
eventFileGeneric, _ := fi.t.FDTable().Get(int32(allocParams.POsEvent))
if eventFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer eventFileGeneric.DecRef(fi.ctx)
eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
allocParams.POsEvent = nvgpu.P64(uint64(eventFile.hostFD))
n, err := rmAllocInvoke(fi, ioctlParams, &allocParams, isNVOS64, func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV00F1_ALLOCATION_PARAMETERS) {
fi.fd.dev.nvp.objAdd(fi.ctx, client, ioctlParams.HObjectNew, ioctlParams.HClass, &miscObject{}, ioctlParams.HObjectParent)
})
if err != nil {
return n, err
}
allocParams.CapDescriptor = origCapDescriptor
allocParams.POsEvent = origEventFD
if _, err := allocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return n, err
}
return n, nil
}
func rmAllocMulticastFabric[Params any, PtrParams hasPOsEventPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, isNVOS64 bool) (uintptr, error) {
var allocParamsValue Params
allocParams := PtrParams(&allocParamsValue)
// As in rmAllocSimpleParams(), the driver ignores ioctlParams.ParamsSize and
// derives the param size from the class, so it is not validated here.
if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return 0, err
}
// pOsEvent is optional; only translate it if the sandbox supplied one.
// See src/nvidia/src/kernel/mem_mgr/mem_multicast_fabric.c:_memMulticastFabricDescriptorEnqueueWait().
origEventFD := allocParams.GetPOsEvent()
if origEventFD != 0 {
eventFileGeneric, _ := fi.t.FDTable().Get(int32(origEventFD))
if eventFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer eventFileGeneric.DecRef(fi.ctx)
eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
allocParams.SetPOsEvent(nvgpu.P64(uint64(eventFile.hostFD)))
}
n, err := rmAllocInvoke(fi, ioctlParams, allocParams, isNVOS64, addSimpleObjDepParentLocked)
if err != nil {
return n, err
}
allocParams.SetPOsEvent(origEventFD)
if _, err := allocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
return n, err
}
return n, nil
}
// See src/nvidia/interface/deprecated/rmapi_deprecated_misc.c:RmDeprecatedIdleChannels().
func rmIdleChannels(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.NVOS30_PARAMETERS
if fi.ioctlParamsSize != nvgpu.SizeofNVOS30Parameters {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
if ioctlParams.NumChannels == 0 {
return rmIdleChannelsInvoke(fi, &ioctlParams, nil, nil, nil)
}
if !rmapiParamsSizeCheck(ioctlParams.NumChannels, 4 /* sizeof(NvU32) */) {
log.Warningf("nvproxy: NV_ESC_RM_IDLE_CHANNELS: NumChannels %d is too large", ioctlParams.NumChannels)
return 0, linuxerr.EINVAL
}
bufferSize := ioctlParams.NumChannels * 4
clientsBuf := make([]byte, bufferSize)
if _, err := fi.t.CopyInBytes(addrFromP64(ioctlParams.Clients), clientsBuf); err != nil {
return 0, err
}
devicesBuf := make([]byte, bufferSize)
if _, err := fi.t.CopyInBytes(addrFromP64(ioctlParams.Devices), devicesBuf); err != nil {
return 0, err
}
channelsBuf := make([]byte, bufferSize)
if _, err := fi.t.CopyInBytes(addrFromP64(ioctlParams.Channels), channelsBuf); err != nil {
return 0, err
}
n, err := rmIdleChannelsInvoke(fi, &ioctlParams, &clientsBuf[0], &devicesBuf[0], &channelsBuf[0])
if err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(addrFromP64(ioctlParams.Clients), clientsBuf); err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(addrFromP64(ioctlParams.Devices), devicesBuf); err != nil {
return n, err
}
if _, err := fi.t.CopyOutBytes(addrFromP64(ioctlParams.Channels), channelsBuf); err != nil {
return n, err
}
return n, nil
}
func rmVidHeapControl(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.NVOS32_PARAMETERS
if fi.ioctlParamsSize != nvgpu.SizeofNVOS32Parameters {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
// Function determines the type of Data.
if fi.ctx.IsLogging(log.Debug) {
fi.ctx.Debugf("nvproxy: VID_HEAP_CONTROL function %d", ioctlParams.Function)
}
// See
// src/nvidia/interface/deprecated/rmapi_deprecated_vidheapctrl.c:rmVidHeapControlTable
// for implementation.
switch ioctlParams.Function {
case nvgpu.NVOS32_FUNCTION_ALLOC_SIZE:
return rmVidHeapControlAllocSize(fi, &ioctlParams)
default:
fi.ctx.Warningf("nvproxy: %s for VID_HEAP_CONTROL function %d", errUndefinedHandler.Error(), ioctlParams.Function)
return 0, linuxerr.EINVAL
}
}
func rmMapMemory(fi *frontendIoctlState) (uintptr, error) {
var ioctlParams nvgpu.IoctlNVOS33ParametersWithFD
if fi.ioctlParamsSize != nvgpu.SizeofIoctlNVOS33ParametersWithFD {
return 0, linuxerr.EINVAL
}
if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
return 0, err
}
mapFileGeneric, _ := fi.t.FDTable().Get(ioctlParams.FD)
if mapFileGeneric == nil {
return 0, linuxerr.EINVAL
}
defer mapFileGeneric.DecRef(fi.ctx)
mapFile, ok := mapFileGeneric.Impl().(*frontendFD)
if !ok {
return 0, linuxerr.EINVAL
}
mapFile.memmapFile.mmapMu.Lock()
defer mapFile.memmapFile.mmapMu.Unlock()
if mapFile.memmapFile.mmapLength != 0 {
fi.ctx.Warningf("nvproxy: attempted to reuse FD %d for NV_ESC_RM_MAP_MEMORY", ioctlParams.FD)
return 0, linuxerr.EINVAL
}
origFD := ioctlParams.FD
ioctlParams.FD = mapFile.hostFD
n, err := frontendIoctlInvoke(fi, &ioctlParams)
if err != nil {
return n, err
}
if ioctlParams.Params.Status == nvgpu.NV_OK {
mapFile.memmapFile.mmapLength = ioctlParams.Params.Length
// src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl() forces
// NVOS33_FLAGS_CACHING_TYPE_DEFAULT, but resMap implementations may
// override the "caching type", so in general the memory type depends
// on the mapped object. Conveniently, when this occurs, the caching
// type in pParms->flags must be updated for the call to
// rm_create_mmap_context(), and pParms is subsequently copied back out
// by kernel-open/nvidia/nv.c:nvidia_ioctl(), so we can get the final
// caching type from the updated ioctl params.
mapFile.memmapFile.memType = getMemoryType(fi.ctx, mapFile.dev, (ioctlParams.Params.Flags>>nvgpu.NVOS33_FLAGS_CACHING_TYPE_SHIFT)&nvgpu.NVOS33_FLAGS_CACHING_TYPE_MASK)
}
ioctlParams.FD = origFD
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
return n, err
}
return n, nil
}
func frontendFailWithStatus[Params any, PtrParams hasStatusPtr[Params]](fi *frontendIoctlState, ioctlParams PtrParams, status uint32) error {
return failWithStatus(fi.ctx, fi.t, fi.ioctlParamsAddr, ioctlParams, status)
}
func failWithStatus[Params any, PtrParams hasStatusPtr[Params]](ctx context.Context, t *kernel.Task, ioctlParamsAddr hostarch.Addr, ioctlParams PtrParams, status uint32) error {
if log.IsLogging(log.Debug) {
ctx.Debugf("nvproxy: ioctl internally failed: status=%#x", status)
}
ioctlParams.SetStatus(status)
_, err := ioctlParams.CopyOut(t, ioctlParamsAddr)
return err
}