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 }