github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/sentry/devices/nvproxy/frontend.go (about)

     1  // Copyright 2023 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nvproxy
    16  
    17  import (
    18  	"fmt"
    19  	"sync/atomic"
    20  
    21  	"golang.org/x/sys/unix"
    22  	"github.com/metacubex/gvisor/pkg/abi/linux"
    23  	"github.com/metacubex/gvisor/pkg/abi/nvgpu"
    24  	"github.com/metacubex/gvisor/pkg/cleanup"
    25  	"github.com/metacubex/gvisor/pkg/context"
    26  	"github.com/metacubex/gvisor/pkg/devutil"
    27  	"github.com/metacubex/gvisor/pkg/errors/linuxerr"
    28  	"github.com/metacubex/gvisor/pkg/fdnotifier"
    29  	"github.com/metacubex/gvisor/pkg/hostarch"
    30  	"github.com/metacubex/gvisor/pkg/log"
    31  	"github.com/metacubex/gvisor/pkg/sentry/arch"
    32  	"github.com/metacubex/gvisor/pkg/sentry/kernel"
    33  	"github.com/metacubex/gvisor/pkg/sentry/memmap"
    34  	"github.com/metacubex/gvisor/pkg/sentry/mm"
    35  	"github.com/metacubex/gvisor/pkg/sentry/vfs"
    36  	"github.com/metacubex/gvisor/pkg/usermem"
    37  	"github.com/metacubex/gvisor/pkg/waiter"
    38  )
    39  
    40  // frontendDevice implements vfs.Device for /dev/nvidia# and /dev/nvidiactl.
    41  //
    42  // +stateify savable
    43  type frontendDevice struct {
    44  	nvp   *nvproxy
    45  	minor uint32
    46  }
    47  
    48  // Open implements vfs.Device.Open.
    49  func (dev *frontendDevice) Open(ctx context.Context, mnt *vfs.Mount, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
    50  	devClient := devutil.GoferClientFromContext(ctx)
    51  	if devClient == nil {
    52  		log.Warningf("devutil.CtxDevGoferClient is not set")
    53  		return nil, linuxerr.ENOENT
    54  	}
    55  	var devName string
    56  	if dev.minor == nvgpu.NV_CONTROL_DEVICE_MINOR {
    57  		devName = "nvidiactl"
    58  	} else {
    59  		devName = fmt.Sprintf("nvidia%d", dev.minor)
    60  	}
    61  	hostFD, err := devClient.OpenAt(ctx, devName, opts.Flags)
    62  	if err != nil {
    63  		ctx.Warningf("nvproxy: failed to open host %s: %v", devName, err)
    64  		return nil, err
    65  	}
    66  	fd := &frontendFD{
    67  		nvp:       dev.nvp,
    68  		hostFD:    int32(hostFD),
    69  		isControl: dev.minor == nvgpu.NV_CONTROL_DEVICE_MINOR,
    70  	}
    71  	if err := fd.vfsfd.Init(fd, opts.Flags, mnt, vfsd, &vfs.FileDescriptionOptions{
    72  		UseDentryMetadata: true,
    73  	}); err != nil {
    74  		unix.Close(hostFD)
    75  		return nil, err
    76  	}
    77  	if err := fdnotifier.AddFD(int32(hostFD), &fd.queue); err != nil {
    78  		unix.Close(hostFD)
    79  		return nil, err
    80  	}
    81  	fd.memmapFile.fd = fd
    82  	return &fd.vfsfd, nil
    83  }
    84  
    85  // frontendFD implements vfs.FileDescriptionImpl for /dev/nvidia# and
    86  // /dev/nvidiactl.
    87  //
    88  // frontendFD is not savable; we do not implement save/restore of host GPU
    89  // state.
    90  type frontendFD struct {
    91  	vfsfd vfs.FileDescription
    92  	vfs.FileDescriptionDefaultImpl
    93  	vfs.DentryMetadataFileDescriptionImpl
    94  	vfs.NoLockFD
    95  
    96  	nvp        *nvproxy
    97  	hostFD     int32
    98  	isControl  bool
    99  	memmapFile frontendFDMemmapFile
   100  
   101  	queue waiter.Queue
   102  
   103  	haveMmapContext atomic.Bool
   104  }
   105  
   106  // Release implements vfs.FileDescriptionImpl.Release.
   107  func (fd *frontendFD) Release(context.Context) {
   108  	fdnotifier.RemoveFD(fd.hostFD)
   109  	fd.queue.Notify(waiter.EventHUp)
   110  	unix.Close(int(fd.hostFD))
   111  }
   112  
   113  // EventRegister implements waiter.Waitable.EventRegister.
   114  func (fd *frontendFD) EventRegister(e *waiter.Entry) error {
   115  	fd.queue.EventRegister(e)
   116  	if err := fdnotifier.UpdateFD(fd.hostFD); err != nil {
   117  		fd.queue.EventUnregister(e)
   118  		return err
   119  	}
   120  	return nil
   121  }
   122  
   123  // EventUnregister implements waiter.Waitable.EventUnregister.
   124  func (fd *frontendFD) EventUnregister(e *waiter.Entry) {
   125  	fd.queue.EventUnregister(e)
   126  	if err := fdnotifier.UpdateFD(fd.hostFD); err != nil {
   127  		panic(fmt.Sprint("UpdateFD:", err))
   128  	}
   129  }
   130  
   131  // Readiness implements waiter.Waitable.Readiness.
   132  func (fd *frontendFD) Readiness(mask waiter.EventMask) waiter.EventMask {
   133  	return fdnotifier.NonBlockingPoll(fd.hostFD, mask)
   134  }
   135  
   136  // Epollable implements vfs.FileDescriptionImpl.Epollable.
   137  func (fd *frontendFD) Epollable() bool {
   138  	return true
   139  }
   140  
   141  // Ioctl implements vfs.FileDescriptionImpl.Ioctl.
   142  func (fd *frontendFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) {
   143  	cmd := args[1].Uint()
   144  	nr := linux.IOC_NR(cmd)
   145  	argPtr := args[2].Pointer()
   146  	argSize := linux.IOC_SIZE(cmd)
   147  
   148  	t := kernel.TaskFromContext(ctx)
   149  	if t == nil {
   150  		panic("Ioctl should be called from a task context")
   151  	}
   152  
   153  	if log.IsLogging(log.Debug) {
   154  		ctx.Debugf("nvproxy: frontend ioctl: nr = %#08x, argSize = %#08x", nr, argSize)
   155  	}
   156  
   157  	fi := frontendIoctlState{
   158  		fd:              fd,
   159  		ctx:             ctx,
   160  		t:               t,
   161  		nr:              nr,
   162  		ioctlParamsAddr: argPtr,
   163  		ioctlParamsSize: argSize,
   164  	}
   165  
   166  	// nr determines the argument type.
   167  	// Implementors:
   168  	// - To map nr to a symbol, look in
   169  	// src/nvidia/arch/nvalloc/unix/include/nv_escape.h,
   170  	// kernel-open/common/inc/nv-ioctl-numbers.h, and
   171  	// kernel-open/common/inc/nv-ioctl-numa.h.
   172  	// - To determine the parameter type, find the implementation in
   173  	// kernel-open/nvidia/nv.c:nvidia_ioctl() or
   174  	// src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl().
   175  	// - Add symbol and parameter type definitions to //pkg/abi/nvgpu.
   176  	// - Add filter to seccomp_filters.go.
   177  	// - Add handling below.
   178  	handler := fd.nvp.abi.frontendIoctl[nr]
   179  	if handler == nil {
   180  		ctx.Warningf("nvproxy: unknown frontend ioctl %d == %#x (argSize=%d, cmd=%#x)", nr, nr, argSize, cmd)
   181  		return 0, linuxerr.EINVAL
   182  	}
   183  	return handler(&fi)
   184  }
   185  
   186  func frontendIoctlCmd(nr, argSize uint32) uintptr {
   187  	return uintptr(linux.IOWR(nvgpu.NV_IOCTL_MAGIC, nr, argSize))
   188  }
   189  
   190  // frontendIoctlState holds the state of a call to frontendFD.Ioctl().
   191  type frontendIoctlState struct {
   192  	fd              *frontendFD
   193  	ctx             context.Context
   194  	t               *kernel.Task
   195  	nr              uint32
   196  	ioctlParamsAddr hostarch.Addr
   197  	ioctlParamsSize uint32
   198  }
   199  
   200  // frontendIoctlSimple implements a frontend ioctl whose parameters don't
   201  // contain any pointers requiring translation, file descriptors, or special
   202  // cases or effects, and consequently don't need to be typed by the sentry.
   203  func frontendIoctlSimple(fi *frontendIoctlState) (uintptr, error) {
   204  	if fi.ioctlParamsSize == 0 {
   205  		return frontendIoctlInvoke[byte](fi, nil)
   206  	}
   207  
   208  	ioctlParams := make([]byte, fi.ioctlParamsSize)
   209  	if _, err := fi.t.CopyInBytes(fi.ioctlParamsAddr, ioctlParams); err != nil {
   210  		return 0, err
   211  	}
   212  	n, err := frontendIoctlInvoke(fi, &ioctlParams[0])
   213  	if err != nil {
   214  		return n, err
   215  	}
   216  	if _, err := fi.t.CopyOutBytes(fi.ioctlParamsAddr, ioctlParams); err != nil {
   217  		return n, err
   218  	}
   219  	return n, nil
   220  }
   221  
   222  func rmNumaInfo(fi *frontendIoctlState) (uintptr, error) {
   223  	// The CPU topology seen by the host driver differs from the CPU
   224  	// topology presented by the sentry to the application, so reject this
   225  	// ioctl; doing so is non-fatal.
   226  	log.Debugf("nvproxy: ignoring NV_ESC_NUMA_INFO")
   227  	return 0, linuxerr.EINVAL
   228  }
   229  
   230  func frontendRegisterFD(fi *frontendIoctlState) (uintptr, error) {
   231  	var ioctlParams nvgpu.IoctlRegisterFD
   232  	if fi.ioctlParamsSize != nvgpu.SizeofIoctlRegisterFD {
   233  		return 0, linuxerr.EINVAL
   234  	}
   235  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   236  		return 0, err
   237  	}
   238  	ctlFileGeneric, _ := fi.t.FDTable().Get(ioctlParams.CtlFD)
   239  	if ctlFileGeneric == nil {
   240  		return 0, linuxerr.EINVAL
   241  	}
   242  	defer ctlFileGeneric.DecRef(fi.ctx)
   243  	ctlFile, ok := ctlFileGeneric.Impl().(*frontendFD)
   244  	if !ok {
   245  		return 0, linuxerr.EINVAL
   246  	}
   247  	sentryIoctlParams := nvgpu.IoctlRegisterFD{
   248  		CtlFD: ctlFile.hostFD,
   249  	}
   250  	// The returned ctl_fd can't change, so skip copying out.
   251  	return frontendIoctlInvoke(fi, &sentryIoctlParams)
   252  }
   253  
   254  func rmAllocOSEvent(fi *frontendIoctlState) (uintptr, error) {
   255  	var ioctlParams nvgpu.IoctlAllocOSEvent
   256  	if fi.ioctlParamsSize != nvgpu.SizeofIoctlAllocOSEvent {
   257  		return 0, linuxerr.EINVAL
   258  	}
   259  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   260  		return 0, err
   261  	}
   262  	eventFileGeneric, _ := fi.t.FDTable().Get(int32(ioctlParams.FD))
   263  	if eventFileGeneric == nil {
   264  		return 0, linuxerr.EINVAL
   265  	}
   266  	defer eventFileGeneric.DecRef(fi.ctx)
   267  	eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
   268  	if !ok {
   269  		return 0, linuxerr.EINVAL
   270  	}
   271  	sentryIoctlParams := ioctlParams
   272  	sentryIoctlParams.FD = uint32(eventFile.hostFD)
   273  
   274  	n, err := frontendIoctlInvoke(fi, &sentryIoctlParams)
   275  	if err != nil {
   276  		return n, err
   277  	}
   278  
   279  	outIoctlParams := sentryIoctlParams
   280  	outIoctlParams.FD = ioctlParams.FD
   281  	if _, err := outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
   282  		return n, err
   283  	}
   284  
   285  	return n, nil
   286  }
   287  
   288  func rmFreeOSEvent(fi *frontendIoctlState) (uintptr, error) {
   289  	var ioctlParams nvgpu.IoctlFreeOSEvent
   290  	if fi.ioctlParamsSize != nvgpu.SizeofIoctlFreeOSEvent {
   291  		return 0, linuxerr.EINVAL
   292  	}
   293  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   294  		return 0, err
   295  	}
   296  	eventFileGeneric, _ := fi.t.FDTable().Get(int32(ioctlParams.FD))
   297  	if eventFileGeneric == nil {
   298  		return 0, linuxerr.EINVAL
   299  	}
   300  	defer eventFileGeneric.DecRef(fi.ctx)
   301  	eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
   302  	if !ok {
   303  		return 0, linuxerr.EINVAL
   304  	}
   305  	sentryIoctlParams := ioctlParams
   306  	sentryIoctlParams.FD = uint32(eventFile.hostFD)
   307  
   308  	n, err := frontendIoctlInvoke(fi, &sentryIoctlParams)
   309  	if err != nil {
   310  		return n, err
   311  	}
   312  
   313  	outIoctlParams := sentryIoctlParams
   314  	outIoctlParams.FD = ioctlParams.FD
   315  	if _, err := outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
   316  		return n, err
   317  	}
   318  
   319  	return n, nil
   320  }
   321  
   322  func rmAllocMemory(fi *frontendIoctlState) (uintptr, error) {
   323  	var ioctlParams nvgpu.IoctlNVOS02ParametersWithFD
   324  	if fi.ioctlParamsSize != nvgpu.SizeofIoctlNVOS02ParametersWithFD {
   325  		return 0, linuxerr.EINVAL
   326  	}
   327  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   328  		return 0, err
   329  	}
   330  
   331  	if log.IsLogging(log.Debug) {
   332  		fi.ctx.Debugf("nvproxy: NV_ESC_RM_ALLOC_MEMORY class %#08x", ioctlParams.Params.HClass)
   333  	}
   334  	// See src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl() and
   335  	// src/nvidia/interface/deprecated/rmapi_deprecated_allocmemory.c:rmAllocMemoryTable
   336  	// for implementation.
   337  	switch ioctlParams.Params.HClass {
   338  	case nvgpu.NV01_MEMORY_SYSTEM_OS_DESCRIPTOR:
   339  		return rmAllocOSDescriptor(fi, &ioctlParams)
   340  	default:
   341  		fi.ctx.Warningf("nvproxy: unknown NV_ESC_RM_ALLOC_MEMORY class %#08x", ioctlParams.Params.HClass)
   342  		return 0, linuxerr.EINVAL
   343  	}
   344  }
   345  
   346  func rmAllocOSDescriptor(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02ParametersWithFD) (uintptr, error) {
   347  	// Compare src/nvidia/arch/nvalloc/unix/src/escape.c:RmAllocOsDescriptor()
   348  	// => RmCreateOsDescriptor().
   349  	failWithStatus := func(status uint32) error {
   350  		outIoctlParams := *ioctlParams
   351  		outIoctlParams.Params.Status = status
   352  		_, err := outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr)
   353  		return err
   354  	}
   355  	appAddr := addrFromP64(ioctlParams.Params.PMemory)
   356  	if !appAddr.IsPageAligned() {
   357  		return 0, failWithStatus(nvgpu.NV_ERR_NOT_SUPPORTED)
   358  	}
   359  	arLen := ioctlParams.Params.Limit + 1
   360  	if arLen == 0 { // integer overflow
   361  		return 0, failWithStatus(nvgpu.NV_ERR_INVALID_LIMIT)
   362  	}
   363  	var ok bool
   364  	arLen, ok = hostarch.PageRoundUp(arLen)
   365  	if !ok {
   366  		return 0, failWithStatus(nvgpu.NV_ERR_INVALID_ADDRESS)
   367  	}
   368  	appAR, ok := appAddr.ToRange(arLen)
   369  	if !ok {
   370  		return 0, failWithStatus(nvgpu.NV_ERR_INVALID_ADDRESS)
   371  	}
   372  
   373  	// The host driver will collect pages from our address space starting at
   374  	// PMemory, so we must assemble a contiguous mapping equivalent to the
   375  	// application's.
   376  	at := hostarch.Read
   377  	if ((ioctlParams.Params.Flags >> 21) & 0x1) == 0 /* NVOS02_FLAGS_ALLOC_USER_READ_ONLY_NO */ {
   378  		at.Write = true
   379  	}
   380  	// Reserve a range in our address space.
   381  	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 */)
   382  	if errno != 0 {
   383  		return 0, errno
   384  	}
   385  	cu := cleanup.Make(func() {
   386  		unix.RawSyscall(unix.SYS_MUNMAP, m, uintptr(arLen), 0)
   387  	})
   388  	defer cu.Clean()
   389  	// Mirror application mappings into the reserved range.
   390  	prs, err := fi.t.MemoryManager().Pin(fi.ctx, appAR, at, false /* ignorePermissions */)
   391  	cu.Add(func() {
   392  		mm.Unpin(prs)
   393  	})
   394  	if err != nil {
   395  		return 0, err
   396  	}
   397  	sentryAddr := uintptr(m)
   398  	for _, pr := range prs {
   399  		ims, err := pr.File.MapInternal(memmap.FileRange{pr.Offset, pr.Offset + uint64(pr.Source.Length())}, at)
   400  		if err != nil {
   401  			return 0, err
   402  		}
   403  		for !ims.IsEmpty() {
   404  			im := ims.Head()
   405  			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 {
   406  				return 0, errno
   407  			}
   408  			sentryAddr += uintptr(im.Len())
   409  			ims = ims.Tail()
   410  		}
   411  	}
   412  	sentryIoctlParams := *ioctlParams
   413  	sentryIoctlParams.Params.PMemory = nvgpu.P64(uint64(m))
   414  	// NV01_MEMORY_SYSTEM_OS_DESCRIPTOR shouldn't use ioctlParams.FD; clobber
   415  	// it to be sure.
   416  	sentryIoctlParams.FD = -1
   417  
   418  	fi.fd.nvp.objsMu.Lock()
   419  	n, err := frontendIoctlInvoke(fi, &sentryIoctlParams)
   420  	if err != nil {
   421  		fi.fd.nvp.objsMu.Unlock()
   422  		return n, err
   423  	}
   424  	// Transfer ownership of pinned pages to an osDescMem object, to be
   425  	// unpinned when the driver OsDescMem is freed.
   426  	o := &osDescMem{
   427  		pinnedRanges: prs,
   428  	}
   429  	o.object.init(o)
   430  	fi.fd.nvp.objsLive[sentryIoctlParams.Params.HObjectNew] = &o.object
   431  	fi.fd.nvp.objsMu.Unlock()
   432  	cu.Release()
   433  	fi.ctx.Infof("nvproxy: pinned pages for OS descriptor with handle %#x", sentryIoctlParams.Params.HObjectNew)
   434  	// Unmap the reserved range, which is no longer required.
   435  	unix.RawSyscall(unix.SYS_MUNMAP, m, uintptr(arLen), 0)
   436  
   437  	outIoctlParams := sentryIoctlParams
   438  	outIoctlParams.Params.PMemory = ioctlParams.Params.PMemory
   439  	outIoctlParams.FD = ioctlParams.FD
   440  	if _, err := outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
   441  		return n, err
   442  	}
   443  
   444  	return n, nil
   445  }
   446  
   447  func rmFree(fi *frontendIoctlState) (uintptr, error) {
   448  	var ioctlParams nvgpu.NVOS00Parameters
   449  	if fi.ioctlParamsSize != nvgpu.SizeofNVOS00Parameters {
   450  		return 0, linuxerr.EINVAL
   451  	}
   452  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   453  		return 0, err
   454  	}
   455  
   456  	fi.fd.nvp.objsMu.Lock()
   457  	n, err := frontendIoctlInvoke(fi, &ioctlParams)
   458  	if err != nil {
   459  		fi.fd.nvp.objsMu.Unlock()
   460  		return n, err
   461  	}
   462  	o, ok := fi.fd.nvp.objsLive[ioctlParams.HObjectOld]
   463  	if ok {
   464  		delete(fi.fd.nvp.objsLive, ioctlParams.HObjectOld)
   465  	}
   466  	fi.fd.nvp.objsMu.Unlock()
   467  	if ok {
   468  		o.Release(fi.ctx)
   469  	}
   470  
   471  	if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
   472  		return n, err
   473  	}
   474  	return n, nil
   475  }
   476  
   477  func rmControl(fi *frontendIoctlState) (uintptr, error) {
   478  	var ioctlParams nvgpu.NVOS54Parameters
   479  	if fi.ioctlParamsSize != nvgpu.SizeofNVOS54Parameters {
   480  		return 0, linuxerr.EINVAL
   481  	}
   482  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   483  		return 0, err
   484  	}
   485  
   486  	// Cmd determines the type of Params.
   487  	if log.IsLogging(log.Debug) {
   488  		fi.ctx.Debugf("nvproxy: control command %#x", ioctlParams.Cmd)
   489  	}
   490  	if ioctlParams.Cmd&nvgpu.RM_GSS_LEGACY_MASK != 0 {
   491  		// This is a "legacy GSS control" that is implemented by the GPU System
   492  		// Processor (GSP). Conseqeuently, its parameters cannot reasonably
   493  		// contain application pointers, and the control is in any case
   494  		// undocumented.
   495  		// See
   496  		// src/nvidia/src/kernel/rmapi/entry_points.c:_nv04ControlWithSecInfo()
   497  		// =>
   498  		// src/nvidia/interface/deprecated/rmapi_deprecated_control.c:RmDeprecatedGetControlHandler()
   499  		// =>
   500  		// src/nvidia/interface/deprecated/rmapi_gss_legacy_control.c:RmGssLegacyRpcCmd().
   501  		return rmControlSimple(fi, &ioctlParams)
   502  	}
   503  	// Implementors:
   504  	// - Top two bytes of Cmd specifies class; third byte specifies category;
   505  	// fourth byte specifies "message ID" (command within class/category).
   506  	//   e.g. 0x800288:
   507  	//   - Class 0x0080 => look in
   508  	//   src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080base.h for categories.
   509  	//   - Category 0x02 => NV0080_CTRL_GPU => look in
   510  	//   src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080gpu.h for
   511  	//   `#define NV0080_CTRL_CMD_GPU_QUERY_SW_STATE_PERSISTENCE (0x800288)`
   512  	//   and accompanying documentation, parameter type.
   513  	// - If this fails, or to find implementation, grep for `methodId=.*0x<Cmd
   514  	// in lowercase hex without leading 0s>` to find entry in g_*_nvoc.c;
   515  	// implementing function is is "pFunc".
   516  	// - Add symbol definition to //pkg/abi/nvgpu. Parameter type definition is
   517  	// only required for non-simple commands.
   518  	// - Add handling below.
   519  	handler := fi.fd.nvp.abi.controlCmd[ioctlParams.Cmd]
   520  	if handler == nil {
   521  		fi.ctx.Warningf("nvproxy: unknown control command %#x (paramsSize=%d)", ioctlParams.Cmd, ioctlParams.ParamsSize)
   522  		return 0, linuxerr.EINVAL
   523  	}
   524  	return handler(fi, &ioctlParams)
   525  }
   526  
   527  func rmControlSimple(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) {
   528  	if ioctlParams.ParamsSize == 0 {
   529  		if ioctlParams.Params != 0 {
   530  			return 0, linuxerr.EINVAL
   531  		}
   532  		return rmControlInvoke[byte](fi, ioctlParams, nil)
   533  	}
   534  	if ioctlParams.Params == 0 {
   535  		return 0, linuxerr.EINVAL
   536  	}
   537  
   538  	ctrlParams := make([]byte, ioctlParams.ParamsSize)
   539  	if _, err := fi.t.CopyInBytes(addrFromP64(ioctlParams.Params), ctrlParams); err != nil {
   540  		return 0, err
   541  	}
   542  	n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams[0])
   543  	if err != nil {
   544  		return n, err
   545  	}
   546  	if _, err := fi.t.CopyOutBytes(addrFromP64(ioctlParams.Params), ctrlParams); err != nil {
   547  		return n, err
   548  	}
   549  	return n, nil
   550  }
   551  
   552  func ctrlCmdFailWithStatus(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters, status uint32) error {
   553  	outIoctlParams := *ioctlParams
   554  	outIoctlParams.Status = status
   555  	_, err := outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr)
   556  	return err
   557  }
   558  
   559  func ctrlClientSystemGetBuildVersion(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) {
   560  	var ctrlParams nvgpu.NV0000_CTRL_SYSTEM_GET_BUILD_VERSION_PARAMS
   561  	if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
   562  		return 0, linuxerr.EINVAL
   563  	}
   564  	if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
   565  		return 0, err
   566  	}
   567  
   568  	if ctrlParams.PDriverVersionBuffer == 0 || ctrlParams.PVersionBuffer == 0 || ctrlParams.PTitleBuffer == 0 {
   569  		// No strings are written if any are null. See
   570  		// src/nvidia/interface/deprecated/rmapi_deprecated_control.c:V2_CONVERTER(_NV0000_CTRL_CMD_SYSTEM_GET_BUILD_VERSION).
   571  		return ctrlClientSystemGetBuildVersionInvoke(fi, ioctlParams, &ctrlParams, nil, nil, nil)
   572  	}
   573  
   574  	// Need to buffer strings for copy-out.
   575  	if ctrlParams.SizeOfStrings == 0 {
   576  		return 0, linuxerr.EINVAL
   577  	}
   578  	driverVersionBuf := make([]byte, ctrlParams.SizeOfStrings)
   579  	versionBuf := make([]byte, ctrlParams.SizeOfStrings)
   580  	titleBuf := make([]byte, ctrlParams.SizeOfStrings)
   581  	n, err := ctrlClientSystemGetBuildVersionInvoke(fi, ioctlParams, &ctrlParams, &driverVersionBuf[0], &versionBuf[0], &titleBuf[0])
   582  	if err != nil {
   583  		return n, err
   584  	}
   585  	if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PDriverVersionBuffer), driverVersionBuf); err != nil {
   586  		return n, err
   587  	}
   588  	if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PVersionBuffer), versionBuf); err != nil {
   589  		return n, err
   590  	}
   591  	if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PTitleBuffer), titleBuf); err != nil {
   592  		return n, err
   593  	}
   594  	return n, nil
   595  }
   596  
   597  func ctrlDevGpuGetClasslist(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) {
   598  	var ctrlParams nvgpu.NV0080_CTRL_GPU_GET_CLASSLIST_PARAMS
   599  	if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
   600  		return 0, linuxerr.EINVAL
   601  	}
   602  	if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
   603  		return 0, err
   604  	}
   605  
   606  	// This command has two modes. If the classList pointer is NULL, only simple command handling
   607  	// is required; see src/common/sdk/nvidia/inc/ctrl/ctrl0080gpu.h.
   608  	if ctrlParams.ClassList == 0 {
   609  		return rmControlSimple(fi, ioctlParams)
   610  	}
   611  
   612  	// classList pointer is not NULL. Check classList size against limit. See
   613  	// src/nvidia/src/kernel/rmapi/embedded_param_copy.c:embeddedParamCopyIn() =>
   614  	// case NV0080_CTRL_CMD_GPU_GET_CLASSLIST => RMAPI_PARAM_COPY_INIT().
   615  	// paramCopy.paramsSize is initialized as numClasses * sizeof(NvU32).
   616  	if ctrlParams.NumClasses*4 > nvgpu.RMAPI_PARAM_COPY_MAX_PARAMS_SIZE {
   617  		return 0, ctrlCmdFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
   618  	}
   619  
   620  	classList := make([]uint32, ctrlParams.NumClasses)
   621  	n, err := ctrlDevGpuGetClasslistInvoke(fi, ioctlParams, &ctrlParams, classList)
   622  	if err != nil {
   623  		return n, err
   624  	}
   625  	return n, nil
   626  }
   627  
   628  func ctrlSubdevFIFODisableChannels(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) {
   629  	var ctrlParams nvgpu.NV2080_CTRL_FIFO_DISABLE_CHANNELS_PARAMS
   630  	if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
   631  		return 0, linuxerr.EINVAL
   632  	}
   633  	if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
   634  		return 0, err
   635  	}
   636  	// This pointer must be NULL; see
   637  	// src/nvidia/src/kernel/gpu/fifo/kernel_fifo_ctrl.c:subdeviceCtrlCmdFifoDisableChannels_IMPL().
   638  	// Consequently, we don't need to translate it, but we do want to ensure
   639  	// that it actually is NULL.
   640  	if ctrlParams.PRunlistPreemptEvent != 0 {
   641  		return 0, linuxerr.EINVAL
   642  	}
   643  	n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
   644  	if err != nil {
   645  		return n, err
   646  	}
   647  	if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
   648  		return n, err
   649  	}
   650  	return n, nil
   651  }
   652  
   653  func rmAlloc(fi *frontendIoctlState) (uintptr, error) {
   654  	var isNVOS64 bool
   655  	switch fi.ioctlParamsSize {
   656  	case nvgpu.SizeofNVOS21Parameters:
   657  	case nvgpu.SizeofNVOS64Parameters:
   658  		isNVOS64 = true
   659  	default:
   660  		return 0, linuxerr.EINVAL
   661  	}
   662  	// Copy in parameters and convert to NVOS64ParametersV535, which is a super
   663  	// set of all parameter types we support.
   664  	buf := nvgpu.GetRmAllocParamObj(isNVOS64)
   665  	if _, err := buf.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   666  		return 0, err
   667  	}
   668  	ioctlParams := buf.ToOS64()
   669  
   670  	// hClass determines the type of pAllocParms.
   671  	if log.IsLogging(log.Debug) {
   672  		fi.ctx.Debugf("nvproxy: allocation class %#08x", ioctlParams.HClass)
   673  	}
   674  	// Implementors:
   675  	// - To map hClass to a symbol, look in
   676  	// src/nvidia/generated/g_allclasses.h.
   677  	// - See src/nvidia/src/kernel/rmapi/resource_list.h for table mapping class
   678  	// ("External Class") to the type of pAllocParms ("Alloc Param Info") and
   679  	// the class whose constructor interprets it ("Internal Class").
   680  	// - Add symbol and parameter type definitions to //pkg/abi/nvgpu.
   681  	// - Add handling below.
   682  	handler := fi.fd.nvp.abi.allocationClass[ioctlParams.HClass]
   683  	if handler == nil {
   684  		fi.ctx.Warningf("nvproxy: unknown allocation class %#08x", ioctlParams.HClass)
   685  		// Compare
   686  		// src/nvidia/src/kernel/rmapi/alloc_free.c:serverAllocResourceUnderLock(),
   687  		// when RsResInfoByExternalClassId() is null.
   688  		ioctlParams.Status = nvgpu.NV_ERR_INVALID_CLASS
   689  		outIoctlParams := nvgpu.GetRmAllocParamObj(isNVOS64)
   690  		outIoctlParams.FromOS64(ioctlParams)
   691  		// Any copy-out error from
   692  		// src/nvidia/src/kernel/rmapi/alloc_free.c:serverAllocApiCopyOut() is
   693  		// discarded.
   694  		outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr)
   695  		return 0, nil
   696  	}
   697  	return handler(fi, &ioctlParams, isNVOS64)
   698  }
   699  
   700  // Unlike frontendIoctlSimple and rmControlSimple, rmAllocSimple requires the
   701  // parameter type since the parameter's size is otherwise unknown.
   702  func rmAllocSimple[Params any, PParams marshalPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) {
   703  	if ioctlParams.PAllocParms == 0 {
   704  		return rmAllocInvoke[byte](fi, ioctlParams, nil, isNVOS64)
   705  	}
   706  
   707  	var allocParams Params
   708  	if _, err := (PParams)(&allocParams).CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
   709  		return 0, err
   710  	}
   711  	n, err := rmAllocInvoke(fi, ioctlParams, &allocParams, isNVOS64)
   712  	if err != nil {
   713  		return n, err
   714  	}
   715  	if _, err := (PParams)(&allocParams).CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
   716  		return n, err
   717  	}
   718  	return n, nil
   719  }
   720  
   721  func rmAllocNoParams(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) {
   722  	return rmAllocInvoke[byte](fi, ioctlParams, nil, isNVOS64)
   723  }
   724  
   725  func rmAllocEventOSEvent(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) {
   726  	var allocParams nvgpu.NV0005_ALLOC_PARAMETERS
   727  	if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
   728  		return 0, err
   729  	}
   730  	eventFileGeneric, _ := fi.t.FDTable().Get(int32(allocParams.Data))
   731  	if eventFileGeneric == nil {
   732  		return 0, linuxerr.EINVAL
   733  	}
   734  	defer eventFileGeneric.DecRef(fi.ctx)
   735  	eventFile, ok := eventFileGeneric.Impl().(*frontendFD)
   736  	if !ok {
   737  		return 0, linuxerr.EINVAL
   738  	}
   739  	sentryAllocParams := allocParams
   740  	sentryAllocParams.Data = nvgpu.P64(uint64(eventFile.hostFD))
   741  
   742  	n, err := rmAllocInvoke(fi, ioctlParams, &sentryAllocParams, isNVOS64)
   743  	if err != nil {
   744  		return n, err
   745  	}
   746  
   747  	outAllocParams := sentryAllocParams
   748  	outAllocParams.Data = allocParams.Data
   749  	if _, err := outAllocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil {
   750  		return n, err
   751  	}
   752  	return n, nil
   753  }
   754  
   755  func rmVidHeapControl(fi *frontendIoctlState) (uintptr, error) {
   756  	var ioctlParams nvgpu.NVOS32Parameters
   757  	if fi.ioctlParamsSize != nvgpu.SizeofNVOS32Parameters {
   758  		return 0, linuxerr.EINVAL
   759  	}
   760  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   761  		return 0, err
   762  	}
   763  
   764  	// Function determines the type of Data.
   765  	if log.IsLogging(log.Debug) {
   766  		fi.ctx.Debugf("nvproxy: VID_HEAP_CONTROL function %d", ioctlParams.Function)
   767  	}
   768  	// See
   769  	// src/nvidia/interface/deprecated/rmapi_deprecated_vidheapctrl.c:rmVidHeapControlTable
   770  	// for implementation.
   771  	switch ioctlParams.Function {
   772  	case nvgpu.NVOS32_FUNCTION_ALLOC_SIZE:
   773  		return rmVidHeapControlAllocSize(fi, &ioctlParams)
   774  	default:
   775  		fi.ctx.Warningf("nvproxy: unknown VID_HEAP_CONTROL function %d", ioctlParams.Function)
   776  		return 0, linuxerr.EINVAL
   777  	}
   778  }
   779  
   780  func rmMapMemory(fi *frontendIoctlState) (uintptr, error) {
   781  	var ioctlParams nvgpu.IoctlNVOS33ParametersWithFD
   782  	if fi.ioctlParamsSize != nvgpu.SizeofIoctlNVOS33ParametersWithFD {
   783  		return 0, linuxerr.EINVAL
   784  	}
   785  	if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil {
   786  		return 0, err
   787  	}
   788  	mapFileGeneric, _ := fi.t.FDTable().Get(ioctlParams.FD)
   789  	if mapFileGeneric == nil {
   790  		return 0, linuxerr.EINVAL
   791  	}
   792  	defer mapFileGeneric.DecRef(fi.ctx)
   793  	mapFile, ok := mapFileGeneric.Impl().(*frontendFD)
   794  	if !ok {
   795  		return 0, linuxerr.EINVAL
   796  	}
   797  	if mapFile.haveMmapContext.Load() || !mapFile.haveMmapContext.CompareAndSwap(false, true) {
   798  		fi.ctx.Warningf("nvproxy: attempted to reuse FD %d for NV_ESC_RM_MAP_MEMORY", ioctlParams.FD)
   799  		return 0, linuxerr.EINVAL
   800  	}
   801  	sentryIoctlParams := ioctlParams
   802  	sentryIoctlParams.FD = mapFile.hostFD
   803  
   804  	n, err := frontendIoctlInvoke(fi, &sentryIoctlParams)
   805  	if err != nil {
   806  		return n, err
   807  	}
   808  
   809  	outIoctlParams := sentryIoctlParams
   810  	outIoctlParams.FD = ioctlParams.FD
   811  	if _, err := outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
   812  		return n, err
   813  	}
   814  
   815  	return n, nil
   816  }