gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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 "gvisor.dev/gvisor/pkg/abi/linux" 23 "gvisor.dev/gvisor/pkg/abi/nvgpu" 24 "gvisor.dev/gvisor/pkg/cleanup" 25 "gvisor.dev/gvisor/pkg/context" 26 "gvisor.dev/gvisor/pkg/devutil" 27 "gvisor.dev/gvisor/pkg/errors/linuxerr" 28 "gvisor.dev/gvisor/pkg/fdnotifier" 29 "gvisor.dev/gvisor/pkg/hostarch" 30 "gvisor.dev/gvisor/pkg/log" 31 "gvisor.dev/gvisor/pkg/sentry/arch" 32 "gvisor.dev/gvisor/pkg/sentry/kernel" 33 "gvisor.dev/gvisor/pkg/sentry/memmap" 34 "gvisor.dev/gvisor/pkg/sentry/mm" 35 "gvisor.dev/gvisor/pkg/sentry/vfs" 36 "gvisor.dev/gvisor/pkg/usermem" 37 "gvisor.dev/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 func (dev *frontendDevice) basename() string { 49 if dev.minor == nvgpu.NV_CONTROL_DEVICE_MINOR { 50 return "nvidiactl" 51 } 52 return fmt.Sprintf("nvidia%d", dev.minor) 53 } 54 55 // Open implements vfs.Device.Open. 56 func (dev *frontendDevice) Open(ctx context.Context, mnt *vfs.Mount, vfsd *vfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) { 57 devClient := devutil.GoferClientFromContext(ctx) 58 if devClient == nil { 59 log.Warningf("devutil.CtxDevGoferClient is not set") 60 return nil, linuxerr.ENOENT 61 } 62 basename := dev.basename() 63 hostFD, err := devClient.OpenAt(ctx, basename, opts.Flags) 64 if err != nil { 65 ctx.Warningf("nvproxy: failed to open host %s: %v", basename, err) 66 return nil, err 67 } 68 fd := &frontendFD{ 69 dev: dev, 70 containerName: devClient.ContainerName(), 71 hostFD: int32(hostFD), 72 } 73 if err := fd.vfsfd.Init(fd, opts.Flags, mnt, vfsd, &vfs.FileDescriptionOptions{ 74 UseDentryMetadata: true, 75 }); err != nil { 76 unix.Close(hostFD) 77 return nil, err 78 } 79 if err := fdnotifier.AddFD(int32(hostFD), &fd.queue); err != nil { 80 unix.Close(hostFD) 81 return nil, err 82 } 83 fd.memmapFile.fd = fd 84 fd.dev.nvp.fdsMu.Lock() 85 defer fd.dev.nvp.fdsMu.Unlock() 86 fd.dev.nvp.frontendFDs[fd] = struct{}{} 87 return &fd.vfsfd, nil 88 } 89 90 // frontendFD implements vfs.FileDescriptionImpl for /dev/nvidia# and 91 // /dev/nvidiactl. 92 // 93 // +stateify savable 94 type frontendFD struct { 95 vfsfd vfs.FileDescription 96 vfs.FileDescriptionDefaultImpl 97 vfs.DentryMetadataFileDescriptionImpl 98 vfs.NoLockFD 99 100 dev *frontendDevice 101 containerName string 102 hostFD int32 103 memmapFile frontendFDMemmapFile 104 105 queue waiter.Queue 106 haveMmapContext atomic.Bool `state:"nosave"` 107 108 // clients are handles of clients owned by this frontendFD. clients is 109 // protected by dev.nvp.objsMu. 110 clients map[nvgpu.Handle]struct{} 111 } 112 113 // Release implements vfs.FileDescriptionImpl.Release. 114 func (fd *frontendFD) Release(ctx context.Context) { 115 fdnotifier.RemoveFD(fd.hostFD) 116 fd.queue.Notify(waiter.EventHUp) 117 118 fd.dev.nvp.fdsMu.Lock() 119 delete(fd.dev.nvp.frontendFDs, fd) 120 fd.dev.nvp.fdsMu.Unlock() 121 122 fd.dev.nvp.objsLock() 123 defer fd.dev.nvp.objsUnlock() 124 unix.Close(int(fd.hostFD)) 125 // src/nvidia/arch/nvalloc/unix/src/osapi.c:rm_cleanup_file_private() => 126 // RmFreeUnusedClients() 127 for h := range fd.clients { 128 fd.dev.nvp.objFree(ctx, h, h) 129 } 130 } 131 132 // EventRegister implements waiter.Waitable.EventRegister. 133 func (fd *frontendFD) EventRegister(e *waiter.Entry) error { 134 fd.queue.EventRegister(e) 135 if err := fdnotifier.UpdateFD(fd.hostFD); err != nil { 136 fd.queue.EventUnregister(e) 137 return err 138 } 139 return nil 140 } 141 142 // EventUnregister implements waiter.Waitable.EventUnregister. 143 func (fd *frontendFD) EventUnregister(e *waiter.Entry) { 144 fd.queue.EventUnregister(e) 145 if err := fdnotifier.UpdateFD(fd.hostFD); err != nil { 146 panic(fmt.Sprint("UpdateFD:", err)) 147 } 148 } 149 150 // Readiness implements waiter.Waitable.Readiness. 151 func (fd *frontendFD) Readiness(mask waiter.EventMask) waiter.EventMask { 152 return fdnotifier.NonBlockingPoll(fd.hostFD, mask) 153 } 154 155 // Epollable implements vfs.FileDescriptionImpl.Epollable. 156 func (fd *frontendFD) Epollable() bool { 157 return true 158 } 159 160 // Ioctl implements vfs.FileDescriptionImpl.Ioctl. 161 func (fd *frontendFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args arch.SyscallArguments) (uintptr, error) { 162 cmd := args[1].Uint() 163 nr := linux.IOC_NR(cmd) 164 argPtr := args[2].Pointer() 165 argSize := linux.IOC_SIZE(cmd) 166 167 t := kernel.TaskFromContext(ctx) 168 if t == nil { 169 panic("Ioctl should be called from a task context") 170 } 171 172 if ctx.IsLogging(log.Debug) { 173 ctx.Debugf("nvproxy: frontend ioctl: nr = %d = %#x, argSize = %d", nr, nr, argSize) 174 } 175 176 fi := frontendIoctlState{ 177 fd: fd, 178 ctx: ctx, 179 t: t, 180 nr: nr, 181 ioctlParamsAddr: argPtr, 182 ioctlParamsSize: argSize, 183 } 184 185 // nr determines the argument type. 186 // Implementors: 187 // - To map nr to a symbol, look in 188 // src/nvidia/arch/nvalloc/unix/include/nv_escape.h, 189 // kernel-open/common/inc/nv-ioctl-numbers.h, and 190 // kernel-open/common/inc/nv-ioctl-numa.h. 191 // - To determine the parameter type, find the implementation in 192 // kernel-open/nvidia/nv.c:nvidia_ioctl() or 193 // src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl(). 194 // - Add symbol and parameter type definitions to //pkg/abi/nvgpu. 195 // - Add filter to seccomp_filters.go. 196 // - Add handling below. 197 handler := fd.dev.nvp.abi.frontendIoctl[nr] 198 if handler == nil { 199 ctx.Warningf("nvproxy: unknown frontend ioctl %d == %#x (argSize=%d, cmd=%#x)", nr, nr, argSize, cmd) 200 return 0, linuxerr.EINVAL 201 } 202 return handler(&fi) 203 } 204 205 func frontendIoctlCmd(nr, argSize uint32) uintptr { 206 return uintptr(linux.IOWR(nvgpu.NV_IOCTL_MAGIC, nr, argSize)) 207 } 208 209 // frontendIoctlState holds the state of a call to frontendFD.Ioctl(). 210 type frontendIoctlState struct { 211 fd *frontendFD 212 ctx context.Context 213 t *kernel.Task 214 nr uint32 215 ioctlParamsAddr hostarch.Addr 216 ioctlParamsSize uint32 217 } 218 219 // frontendIoctlSimple implements a frontend ioctl whose parameters don't 220 // contain any pointers requiring translation, file descriptors, or special 221 // cases or effects, and consequently don't need to be typed by the sentry. 222 func frontendIoctlSimple(fi *frontendIoctlState) (uintptr, error) { 223 if fi.ioctlParamsSize == 0 { 224 return frontendIoctlInvoke[byte](fi, nil) 225 } 226 227 ioctlParams := make([]byte, fi.ioctlParamsSize) 228 if _, err := fi.t.CopyInBytes(fi.ioctlParamsAddr, ioctlParams); err != nil { 229 return 0, err 230 } 231 n, err := frontendIoctlInvoke(fi, &ioctlParams[0]) 232 if err != nil { 233 return n, err 234 } 235 if _, err := fi.t.CopyOutBytes(fi.ioctlParamsAddr, ioctlParams); err != nil { 236 return n, err 237 } 238 return n, nil 239 } 240 241 func rmNumaInfo(fi *frontendIoctlState) (uintptr, error) { 242 // The CPU topology seen by the host driver differs from the CPU 243 // topology presented by the sentry to the application, so reject this 244 // ioctl; doing so is non-fatal. 245 log.Debugf("nvproxy: ignoring NV_ESC_NUMA_INFO") 246 return 0, linuxerr.EINVAL 247 } 248 249 func frontendRegisterFD(fi *frontendIoctlState) (uintptr, error) { 250 var ioctlParams nvgpu.IoctlRegisterFD 251 if fi.ioctlParamsSize != nvgpu.SizeofIoctlRegisterFD { 252 return 0, linuxerr.EINVAL 253 } 254 if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 255 return 0, err 256 } 257 ctlFileGeneric, _ := fi.t.FDTable().Get(ioctlParams.CtlFD) 258 if ctlFileGeneric == nil { 259 return 0, linuxerr.EINVAL 260 } 261 defer ctlFileGeneric.DecRef(fi.ctx) 262 ctlFile, ok := ctlFileGeneric.Impl().(*frontendFD) 263 if !ok { 264 return 0, linuxerr.EINVAL 265 } 266 ioctlParams.CtlFD = ctlFile.hostFD 267 // The returned ctl_fd can't change, so skip copying out. 268 return frontendIoctlInvoke(fi, &ioctlParams) 269 } 270 271 func frontendIoctHasFD[Params any, PtrParams hasFrontendFDPtr[Params]](fi *frontendIoctlState) (uintptr, error) { 272 var ioctlParamsValue Params 273 ioctlParams := PtrParams(&ioctlParamsValue) 274 if int(fi.ioctlParamsSize) != ioctlParams.SizeBytes() { 275 return 0, linuxerr.EINVAL 276 } 277 if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 278 return 0, err 279 } 280 281 origFD := ioctlParams.GetFrontendFD() 282 eventFileGeneric, _ := fi.t.FDTable().Get(origFD) 283 if eventFileGeneric == nil { 284 return 0, linuxerr.EINVAL 285 } 286 defer eventFileGeneric.DecRef(fi.ctx) 287 eventFile, ok := eventFileGeneric.Impl().(*frontendFD) 288 if !ok { 289 return 0, linuxerr.EINVAL 290 } 291 292 ioctlParams.SetFrontendFD(eventFile.hostFD) 293 n, err := frontendIoctlInvoke(fi, ioctlParams) 294 ioctlParams.SetFrontendFD(origFD) 295 if err != nil { 296 return n, err 297 } 298 if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil { 299 return n, err 300 } 301 return n, nil 302 } 303 304 func rmAllocMemory(fi *frontendIoctlState) (uintptr, error) { 305 var ioctlParams nvgpu.IoctlNVOS02ParametersWithFD 306 if fi.ioctlParamsSize != nvgpu.SizeofIoctlNVOS02ParametersWithFD { 307 return 0, linuxerr.EINVAL 308 } 309 if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 310 return 0, err 311 } 312 313 if log.IsLogging(log.Debug) { 314 fi.ctx.Debugf("nvproxy: NV_ESC_RM_ALLOC_MEMORY class %v", ioctlParams.Params.HClass) 315 } 316 // See src/nvidia/arch/nvalloc/unix/src/escape.c:RmIoctl() and 317 // src/nvidia/interface/deprecated/rmapi_deprecated_allocmemory.c:rmAllocMemoryTable 318 // for implementation. 319 switch ioctlParams.Params.HClass { 320 case nvgpu.NV01_MEMORY_SYSTEM_OS_DESCRIPTOR: 321 return rmAllocOSDescriptor(fi, &ioctlParams) 322 default: 323 fi.ctx.Warningf("nvproxy: unknown NV_ESC_RM_ALLOC_MEMORY class %v", ioctlParams.Params.HClass) 324 return 0, linuxerr.EINVAL 325 } 326 } 327 328 func rmAllocOSDescriptor(fi *frontendIoctlState, ioctlParams *nvgpu.IoctlNVOS02ParametersWithFD) (uintptr, error) { 329 // Compare src/nvidia/arch/nvalloc/unix/src/escape.c:RmAllocOsDescriptor() 330 // => RmCreateOsDescriptor(). 331 failWithStatus := func(status uint32) error { 332 ioctlParams.Params.Status = status 333 _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr) 334 return err 335 } 336 appAddr := addrFromP64(ioctlParams.Params.PMemory) 337 if !appAddr.IsPageAligned() { 338 return 0, failWithStatus(nvgpu.NV_ERR_NOT_SUPPORTED) 339 } 340 arLen := ioctlParams.Params.Limit + 1 341 if arLen == 0 { // integer overflow 342 return 0, failWithStatus(nvgpu.NV_ERR_INVALID_LIMIT) 343 } 344 var ok bool 345 arLen, ok = hostarch.PageRoundUp(arLen) 346 if !ok { 347 return 0, failWithStatus(nvgpu.NV_ERR_INVALID_ADDRESS) 348 } 349 appAR, ok := appAddr.ToRange(arLen) 350 if !ok { 351 return 0, failWithStatus(nvgpu.NV_ERR_INVALID_ADDRESS) 352 } 353 354 // The host driver will collect pages from our address space starting at 355 // PMemory, so we must assemble a contiguous mapping equivalent to the 356 // application's. 357 at := hostarch.Read 358 if ((ioctlParams.Params.Flags >> 21) & 0x1) == 0 /* NVOS02_FLAGS_ALLOC_USER_READ_ONLY_NO */ { 359 at.Write = true 360 } 361 // Reserve a range in our address space. 362 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 */) 363 if errno != 0 { 364 return 0, errno 365 } 366 defer unix.RawSyscall(unix.SYS_MUNMAP, m, uintptr(arLen), 0) 367 // Mirror application mappings into the reserved range. 368 prs, err := fi.t.MemoryManager().Pin(fi.ctx, appAR, at, false /* ignorePermissions */) 369 unpinCleanup := cleanup.Make(func() { 370 mm.Unpin(prs) 371 }) 372 defer unpinCleanup.Clean() 373 if err != nil { 374 return 0, err 375 } 376 sentryAddr := uintptr(m) 377 for _, pr := range prs { 378 ims, err := pr.File.MapInternal(memmap.FileRange{pr.Offset, pr.Offset + uint64(pr.Source.Length())}, at) 379 if err != nil { 380 return 0, err 381 } 382 for !ims.IsEmpty() { 383 im := ims.Head() 384 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 { 385 return 0, errno 386 } 387 sentryAddr += uintptr(im.Len()) 388 ims = ims.Tail() 389 } 390 } 391 origPMemory := ioctlParams.Params.PMemory 392 ioctlParams.Params.PMemory = nvgpu.P64(uint64(m)) 393 // NV01_MEMORY_SYSTEM_OS_DESCRIPTOR shouldn't use ioctlParams.FD; clobber 394 // it to be sure. 395 origFD := ioctlParams.FD 396 ioctlParams.FD = -1 397 398 fi.fd.dev.nvp.objsLock() 399 n, err := frontendIoctlInvoke(fi, ioctlParams) 400 if err == nil && ioctlParams.Params.Status == nvgpu.NV_OK { 401 // Transfer ownership of pinned pages to an osDescMem object, to be 402 // unpinned when the driver OsDescMem is freed. 403 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.Params.HRoot, ioctlParams.Params.HObjectNew, nvgpu.NV01_MEMORY_SYSTEM_OS_DESCRIPTOR, &osDescMem{ 404 pinnedRanges: prs, 405 }, ioctlParams.Params.HObjectParent) 406 unpinCleanup.Release() 407 if fi.ctx.IsLogging(log.Debug) { 408 fi.ctx.Debugf("nvproxy: pinned %d bytes for OS descriptor with handle %v", arLen, ioctlParams.Params.HObjectNew) 409 } 410 } 411 fi.fd.dev.nvp.objsUnlock() 412 ioctlParams.Params.PMemory = origPMemory 413 ioctlParams.FD = origFD 414 if err != nil { 415 return n, err 416 } 417 418 if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil { 419 return n, err 420 } 421 422 return n, nil 423 } 424 425 func rmFree(fi *frontendIoctlState) (uintptr, error) { 426 var ioctlParams nvgpu.NVOS00Parameters 427 if fi.ioctlParamsSize != nvgpu.SizeofNVOS00Parameters { 428 return 0, linuxerr.EINVAL 429 } 430 if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 431 return 0, err 432 } 433 434 fi.fd.dev.nvp.objsLock() 435 n, err := frontendIoctlInvoke(fi, &ioctlParams) 436 if err == nil && ioctlParams.Status == nvgpu.NV_OK { 437 fi.fd.dev.nvp.objFree(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectOld) 438 } 439 fi.fd.dev.nvp.objsUnlock() 440 if err != nil { 441 return n, err 442 } 443 444 if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil { 445 return n, err 446 } 447 return n, nil 448 } 449 450 func rmControl(fi *frontendIoctlState) (uintptr, error) { 451 var ioctlParams nvgpu.NVOS54Parameters 452 if fi.ioctlParamsSize != nvgpu.SizeofNVOS54Parameters { 453 return 0, linuxerr.EINVAL 454 } 455 if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 456 return 0, err 457 } 458 459 // Cmd determines the type of Params. 460 if log.IsLogging(log.Debug) { 461 fi.ctx.Debugf("nvproxy: control command %#x, object %#x", ioctlParams.Cmd, ioctlParams.HObject.Val) 462 } 463 if ioctlParams.Cmd&nvgpu.RM_GSS_LEGACY_MASK != 0 { 464 // This is a "legacy GSS control" that is implemented by the GPU System 465 // Processor (GSP). Conseqeuently, its parameters cannot reasonably 466 // contain application pointers, and the control is in any case 467 // undocumented. 468 // See 469 // src/nvidia/src/kernel/rmapi/entry_points.c:_nv04ControlWithSecInfo() 470 // => 471 // src/nvidia/interface/deprecated/rmapi_deprecated_control.c:RmDeprecatedGetControlHandler() 472 // => 473 // src/nvidia/interface/deprecated/rmapi_gss_legacy_control.c:RmGssLegacyRpcCmd(). 474 return rmControlSimple(fi, &ioctlParams) 475 } 476 // Implementors: 477 // - Top two bytes of Cmd specifies class; third byte specifies category; 478 // fourth byte specifies "message ID" (command within class/category). 479 // e.g. 0x800288: 480 // - Class 0x0080 => look in 481 // src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080base.h for categories. 482 // - Category 0x02 => NV0080_CTRL_GPU => look in 483 // src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080gpu.h for 484 // `#define NV0080_CTRL_CMD_GPU_QUERY_SW_STATE_PERSISTENCE (0x800288)` 485 // and accompanying documentation, parameter type. 486 // - If this fails, or to find implementation, grep for `methodId=.*0x<Cmd 487 // in lowercase hex without leading 0s>` to find entry in g_*_nvoc.c; 488 // implementing function is is "pFunc". 489 // - Add symbol definition to //pkg/abi/nvgpu. Parameter type definition is 490 // only required for non-simple commands. 491 // - Add handling below. 492 handler := fi.fd.dev.nvp.abi.controlCmd[ioctlParams.Cmd] 493 if handler == nil { 494 fi.ctx.Warningf("nvproxy: unknown control command %#x (paramsSize=%d)", ioctlParams.Cmd, ioctlParams.ParamsSize) 495 return 0, linuxerr.EINVAL 496 } 497 return handler(fi, &ioctlParams) 498 } 499 500 func rmControlSimple(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) { 501 if ioctlParams.ParamsSize == 0 { 502 if ioctlParams.Params != 0 { 503 return 0, linuxerr.EINVAL 504 } 505 return rmControlInvoke[byte](fi, ioctlParams, nil) 506 } 507 if ioctlParams.Params == 0 { 508 return 0, linuxerr.EINVAL 509 } 510 511 ctrlParams := make([]byte, ioctlParams.ParamsSize) 512 if _, err := fi.t.CopyInBytes(addrFromP64(ioctlParams.Params), ctrlParams); err != nil { 513 return 0, err 514 } 515 n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams[0]) 516 if err != nil { 517 return n, err 518 } 519 if _, err := fi.t.CopyOutBytes(addrFromP64(ioctlParams.Params), ctrlParams); err != nil { 520 return n, err 521 } 522 return n, nil 523 } 524 525 func ctrlCmdFailWithStatus(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters, status uint32) error { 526 ioctlParams.Status = status 527 _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr) 528 return err 529 } 530 531 func ctrlHasFrontendFD[Params any, PtrParams hasFrontendFDPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) { 532 var ctrlParamsValue Params 533 ctrlParams := PtrParams(&ctrlParamsValue) 534 if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) { 535 return 0, linuxerr.EINVAL 536 } 537 if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 538 return 0, err 539 } 540 541 origFD := ctrlParams.GetFrontendFD() 542 ctlFileGeneric, _ := fi.t.FDTable().Get(origFD) 543 if ctlFileGeneric == nil { 544 return 0, linuxerr.EINVAL 545 } 546 defer ctlFileGeneric.DecRef(fi.ctx) 547 ctlFile, ok := ctlFileGeneric.Impl().(*frontendFD) 548 if !ok { 549 return 0, linuxerr.EINVAL 550 } 551 552 ctrlParams.SetFrontendFD(ctlFile.hostFD) 553 n, err := rmControlInvoke(fi, ioctlParams, ctrlParams) 554 ctrlParams.SetFrontendFD(origFD) 555 if err != nil { 556 return n, err 557 } 558 if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 559 return n, err 560 } 561 return n, nil 562 } 563 564 func ctrlClientSystemGetBuildVersion(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) { 565 var ctrlParams nvgpu.NV0000_CTRL_SYSTEM_GET_BUILD_VERSION_PARAMS 566 if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) { 567 return 0, linuxerr.EINVAL 568 } 569 if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 570 return 0, err 571 } 572 573 if ctrlParams.PDriverVersionBuffer == 0 || ctrlParams.PVersionBuffer == 0 || ctrlParams.PTitleBuffer == 0 { 574 // No strings are written if any are null. See 575 // src/nvidia/interface/deprecated/rmapi_deprecated_control.c:V2_CONVERTER(_NV0000_CTRL_CMD_SYSTEM_GET_BUILD_VERSION). 576 return ctrlClientSystemGetBuildVersionInvoke(fi, ioctlParams, &ctrlParams, nil, nil, nil) 577 } 578 579 // Need to buffer strings for copy-out. 580 if ctrlParams.SizeOfStrings == 0 { 581 return 0, linuxerr.EINVAL 582 } 583 driverVersionBuf := make([]byte, ctrlParams.SizeOfStrings) 584 versionBuf := make([]byte, ctrlParams.SizeOfStrings) 585 titleBuf := make([]byte, ctrlParams.SizeOfStrings) 586 n, err := ctrlClientSystemGetBuildVersionInvoke(fi, ioctlParams, &ctrlParams, &driverVersionBuf[0], &versionBuf[0], &titleBuf[0]) 587 if err != nil { 588 return n, err 589 } 590 if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PDriverVersionBuffer), driverVersionBuf); err != nil { 591 return n, err 592 } 593 if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PVersionBuffer), versionBuf); err != nil { 594 return n, err 595 } 596 if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.PTitleBuffer), titleBuf); err != nil { 597 return n, err 598 } 599 return n, nil 600 } 601 602 func ctrlDevGpuGetClasslist(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) { 603 var ctrlParams nvgpu.NV0080_CTRL_GPU_GET_CLASSLIST_PARAMS 604 if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) { 605 return 0, linuxerr.EINVAL 606 } 607 if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 608 return 0, err 609 } 610 611 // This command has two modes. If the classList pointer is NULL, only simple command handling 612 // is required; see src/common/sdk/nvidia/inc/ctrl/ctrl0080gpu.h. 613 if ctrlParams.ClassList == 0 { 614 return rmControlSimple(fi, ioctlParams) 615 } 616 617 // classList pointer is not NULL. Check classList size against limit. See 618 // src/nvidia/src/kernel/rmapi/embedded_param_copy.c:embeddedParamCopyIn() => 619 // case NV0080_CTRL_CMD_GPU_GET_CLASSLIST => RMAPI_PARAM_COPY_INIT(). 620 // paramCopy.paramsSize is initialized as numClasses * sizeof(NvU32). 621 if ctrlParams.NumClasses*4 > nvgpu.RMAPI_PARAM_COPY_MAX_PARAMS_SIZE { 622 return 0, ctrlCmdFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT) 623 } 624 625 classList := make([]uint32, ctrlParams.NumClasses) 626 n, err := ctrlDevGpuGetClasslistInvoke(fi, ioctlParams, &ctrlParams, classList) 627 if err != nil { 628 return n, err 629 } 630 return n, nil 631 } 632 633 func ctrlRegisterVASpace(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) { 634 var ctrlParams nvgpu.NV503C_CTRL_REGISTER_VA_SPACE_PARAMS 635 if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) { 636 return 0, linuxerr.EINVAL 637 } 638 if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 639 return 0, err 640 } 641 fi.fd.dev.nvp.objsLock() 642 n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams) 643 if err == nil && ioctlParams.Status == nvgpu.NV_OK { 644 // src/nvidia/src/kernel/gpu/bus/third_party_p2p.c:CliAddThirdPartyP2PVASpace() 645 // => refAddDependant() 646 fi.fd.dev.nvp.objAddDep(ioctlParams.HClient, ioctlParams.HObject, ctrlParams.HVASpace) 647 } 648 fi.fd.dev.nvp.objsUnlock() 649 if err != nil { 650 return n, err 651 } 652 if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 653 return n, err 654 } 655 return n, nil 656 } 657 658 func ctrlSubdevFIFODisableChannels(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) { 659 var ctrlParams nvgpu.NV2080_CTRL_FIFO_DISABLE_CHANNELS_PARAMS 660 if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) { 661 return 0, linuxerr.EINVAL 662 } 663 if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 664 return 0, err 665 } 666 // This pointer must be NULL; see 667 // src/nvidia/src/kernel/gpu/fifo/kernel_fifo_ctrl.c:subdeviceCtrlCmdFifoDisableChannels_IMPL(). 668 // Consequently, we don't need to translate it, but we do want to ensure 669 // that it actually is NULL. 670 if ctrlParams.PRunlistPreemptEvent != 0 { 671 return 0, linuxerr.EINVAL 672 } 673 n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams) 674 if err != nil { 675 return n, err 676 } 677 if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil { 678 return n, err 679 } 680 return n, nil 681 } 682 683 func rmAlloc(fi *frontendIoctlState) (uintptr, error) { 684 var isNVOS64 bool 685 switch fi.ioctlParamsSize { 686 case nvgpu.SizeofNVOS21Parameters: 687 case nvgpu.SizeofNVOS64Parameters: 688 isNVOS64 = true 689 default: 690 return 0, linuxerr.EINVAL 691 } 692 // Copy in parameters and convert to NVOS64Parameters, which is a super 693 // set of all parameter types we support. 694 buf := nvgpu.GetRmAllocParamObj(isNVOS64) 695 if _, err := buf.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 696 return 0, err 697 } 698 ioctlParams := buf.ToOS64() 699 700 // hClass determines the type of pAllocParms. 701 if log.IsLogging(log.Debug) { 702 fi.ctx.Debugf("nvproxy: allocation class %v", ioctlParams.HClass) 703 } 704 // Implementors: 705 // - To map hClass to a symbol, look in 706 // src/nvidia/generated/g_allclasses.h. 707 // - See src/nvidia/src/kernel/rmapi/resource_list.h for table mapping class 708 // ("External Class") to the type of pAllocParms ("Alloc Param Info") and 709 // the class whose constructor interprets it ("Internal Class"). 710 // - Add symbol and parameter type definitions to //pkg/abi/nvgpu. 711 // - Check constructor for calls to refAddDependant(), 712 // sessionAddDependant(), or sessionAddDependency(), which need to be 713 // mirrored by dependencies in the call to nvproxy.objAddLocked(). 714 // - Add handling below. 715 handler := fi.fd.dev.nvp.abi.allocationClass[ioctlParams.HClass] 716 if handler == nil { 717 fi.ctx.Warningf("nvproxy: unknown allocation class %v", ioctlParams.HClass) 718 // Compare 719 // src/nvidia/src/kernel/rmapi/alloc_free.c:serverAllocResourceUnderLock(), 720 // when RsResInfoByExternalClassId() is null. 721 ioctlParams.Status = nvgpu.NV_ERR_INVALID_CLASS 722 outIoctlParams := nvgpu.GetRmAllocParamObj(isNVOS64) 723 outIoctlParams.FromOS64(ioctlParams) 724 // Any copy-out error from 725 // src/nvidia/src/kernel/rmapi/alloc_free.c:serverAllocApiCopyOut() is 726 // discarded. 727 outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr) 728 return 0, nil 729 } 730 return handler(fi, &ioctlParams, isNVOS64) 731 } 732 733 // rmAllocSimple implements NV_ESC_RM_ALLOC for classes whose parameters don't 734 // contain any pointers or file descriptors requiring translation, and whose 735 // objects require no special handling and depend only on their parents. 736 // 737 // Unlike frontendIoctlSimple and rmControlSimple, rmAllocSimple requires the 738 // parameter type since the parameter's size is otherwise unknown. 739 func rmAllocSimple[Params any, PtrParams marshalPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 740 return rmAllocSimpleParams[Params, PtrParams](fi, ioctlParams, isNVOS64, addSimpleObjDepParentLocked) 741 } 742 743 // addSimpleObjDepParentLocked implements rmAllocInvoke.addObjLocked for 744 // classes that require no special handling and depend only on their parents. 745 func addSimpleObjDepParentLocked[Params any](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *Params) { 746 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent) 747 } 748 749 func rmAllocSimpleParams[Params any, PtrParams marshalPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool, objAddLocked func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *Params)) (uintptr, error) { 750 if ioctlParams.PAllocParms == 0 { 751 return rmAllocInvoke[Params](fi, ioctlParams, nil, isNVOS64, objAddLocked) 752 } 753 754 var allocParamsValue Params 755 allocParams := PtrParams(&allocParamsValue) 756 if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil { 757 return 0, err 758 } 759 n, err := rmAllocInvoke(fi, ioctlParams, allocParams, isNVOS64, objAddLocked) 760 if err != nil { 761 return n, err 762 } 763 if _, err := allocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil { 764 return n, err 765 } 766 return n, nil 767 } 768 769 func rmAllocNoParams(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 770 return rmAllocInvoke[byte](fi, ioctlParams, nil, isNVOS64, addSimpleObjDepParentLocked) 771 } 772 773 func rmAllocRootClient(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 774 return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.Handle) { 775 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRootClient(fi.fd, ioctlParams, rightsRequested, allocParams)) 776 if fi.fd.clients == nil { 777 fi.fd.clients = make(map[nvgpu.Handle]struct{}) 778 } 779 fi.fd.clients[ioctlParams.HObjectNew] = struct{}{} 780 }) 781 } 782 783 func rmAllocEventOSEvent(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 784 var allocParams nvgpu.NV0005_ALLOC_PARAMETERS 785 if _, err := allocParams.CopyIn(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil { 786 return 0, err 787 } 788 eventFileGeneric, _ := fi.t.FDTable().Get(int32(allocParams.Data)) 789 if eventFileGeneric == nil { 790 return 0, linuxerr.EINVAL 791 } 792 defer eventFileGeneric.DecRef(fi.ctx) 793 eventFile, ok := eventFileGeneric.Impl().(*frontendFD) 794 if !ok { 795 return 0, linuxerr.EINVAL 796 } 797 origData := allocParams.Data 798 allocParams.Data = nvgpu.P64(uint64(eventFile.hostFD)) 799 800 n, err := rmAllocInvoke(fi, ioctlParams, &allocParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV0005_ALLOC_PARAMETERS) { 801 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, &osEvent{}, ioctlParams.HObjectParent) 802 }) 803 if err != nil { 804 return n, err 805 } 806 807 allocParams.Data = origData 808 if _, err := allocParams.CopyOut(fi.t, addrFromP64(ioctlParams.PAllocParms)); err != nil { 809 return n, err 810 } 811 return n, nil 812 } 813 814 func rmAllocSMDebuggerSession(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 815 return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV83DE_ALLOC_PARAMETERS) { 816 // Compare 817 // src/nvidia/src/kernel/gpu/gr/kernel_sm_debugger_session.c:ksmdbgssnConstruct_IMPL() 818 // => _ShareDebugger() => sessionAddDependency/sessionAddDependant(); 819 // the driver indirects through a per-KernelGraphicsObject 820 // RmDebuggerSession, which we elide for dependency tracking. 821 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HClass3DObject) 822 }) 823 } 824 825 func rmAllocChannelGroup(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 826 return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CHANNEL_GROUP_ALLOCATION_PARAMETERS) { 827 // See 828 // src/nvidia/src/kernel/gpu/fifo/kernel_channel_group_api.c:kchangrpapiConstruct_IMPL() 829 // => refAddDependant(). 830 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace) 831 // Note: When the channel group's engine type is GR, which is always 832 // true unless MIG is enabled, kchangrpapiConstruct_IMPL() constructs a 833 // KERNEL_GRAPHICS_CONTEXT whose lifetime is the same as the channel 834 // group's (the graphics context is freed when the channel group is). 835 // Channels, context shares, and graphics objects depend on this 836 // graphics context rather than the channel group. Consequently, if MIG 837 // is enabled, these might not depend on the channel group at all. 838 // Since nvproxy currently does not support MIG, we represent these 839 // dependencies as unconditionally on the channel group instead. 840 }) 841 } 842 843 func rmAllocChannel(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 844 return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CHANNEL_ALLOC_PARAMS) { 845 // See 846 // src/nvidia/src/kernel/gpu/fifo/kernel_channel.c:kchannelConstruct_IMPL() 847 // => refAddDependant(). The channel's parent may be a device or 848 // channel group; if it is a channel group then the channel depends on 849 // it via the parent relationship, and if it is not a channel group 850 // then kchannelConstruct_IMPL() constructs one internally and frees it 851 // when the channel is destroyed, so either way no separate dependency 852 // is required. 853 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace, allocParams.HContextShare) 854 }) 855 } 856 857 func rmAllocContextShare(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) { 858 return rmAllocSimpleParams(fi, ioctlParams, isNVOS64, func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *nvgpu.NV_CTXSHARE_ALLOCATION_PARAMETERS) { 859 // See 860 // src/nvidia/src/kernel/gpu/fifo/kernel_ctxshare.c:kctxshareapiConstruct_IMPL() 861 // => refAddDependant(). The context share's parent is the channel 862 // group, so (given that we are representing graphics context 863 // dependencies as channel group dependencies) no separate dependency 864 // is required. 865 fi.fd.dev.nvp.objAdd(fi.ctx, ioctlParams.HRoot, ioctlParams.HObjectNew, ioctlParams.HClass, newRmAllocObject(fi.fd, ioctlParams, rightsRequested, allocParams), ioctlParams.HObjectParent, allocParams.HVASpace) 866 }) 867 } 868 869 func rmVidHeapControl(fi *frontendIoctlState) (uintptr, error) { 870 var ioctlParams nvgpu.NVOS32Parameters 871 if fi.ioctlParamsSize != nvgpu.SizeofNVOS32Parameters { 872 return 0, linuxerr.EINVAL 873 } 874 if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 875 return 0, err 876 } 877 878 // Function determines the type of Data. 879 if fi.ctx.IsLogging(log.Debug) { 880 fi.ctx.Debugf("nvproxy: VID_HEAP_CONTROL function %d", ioctlParams.Function) 881 } 882 // See 883 // src/nvidia/interface/deprecated/rmapi_deprecated_vidheapctrl.c:rmVidHeapControlTable 884 // for implementation. 885 switch ioctlParams.Function { 886 case nvgpu.NVOS32_FUNCTION_ALLOC_SIZE: 887 return rmVidHeapControlAllocSize(fi, &ioctlParams) 888 default: 889 fi.ctx.Warningf("nvproxy: unknown VID_HEAP_CONTROL function %d", ioctlParams.Function) 890 return 0, linuxerr.EINVAL 891 } 892 } 893 894 func rmMapMemory(fi *frontendIoctlState) (uintptr, error) { 895 var ioctlParams nvgpu.IoctlNVOS33ParametersWithFD 896 if fi.ioctlParamsSize != nvgpu.SizeofIoctlNVOS33ParametersWithFD { 897 return 0, linuxerr.EINVAL 898 } 899 if _, err := ioctlParams.CopyIn(fi.t, fi.ioctlParamsAddr); err != nil { 900 return 0, err 901 } 902 mapFileGeneric, _ := fi.t.FDTable().Get(ioctlParams.FD) 903 if mapFileGeneric == nil { 904 return 0, linuxerr.EINVAL 905 } 906 defer mapFileGeneric.DecRef(fi.ctx) 907 mapFile, ok := mapFileGeneric.Impl().(*frontendFD) 908 if !ok { 909 return 0, linuxerr.EINVAL 910 } 911 if mapFile.haveMmapContext.Load() || !mapFile.haveMmapContext.CompareAndSwap(false, true) { 912 fi.ctx.Warningf("nvproxy: attempted to reuse FD %d for NV_ESC_RM_MAP_MEMORY", ioctlParams.FD) 913 return 0, linuxerr.EINVAL 914 } 915 origFD := ioctlParams.FD 916 ioctlParams.FD = mapFile.hostFD 917 918 n, err := frontendIoctlInvoke(fi, &ioctlParams) 919 if err != nil { 920 return n, err 921 } 922 923 ioctlParams.FD = origFD 924 if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil { 925 return n, err 926 } 927 928 return n, nil 929 }