mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Adds the five missing `ioctls` required to generate [CUDA GPU coredumps](https://docs.nvidia.com/cuda/cuda-gdb/index.html#gpu-core-dump-support) (`CUDA_ENABLE_COREDUMP_ON_EXCEPTION=1`) under nvproxy, all on the `GT200_DEBUGGER` (`0x83de`) class: - `NV83DE_CTRL_CMD_DEBUG_SET_MODE_MMU_DEBUG` (`0x83de0307`) control command — enables MMU debug mode during exception handling. - `NV83DE_CTRL_CMD_DEBUG_READ_MEMORY` (`0x83de0315`) control command, with its `NV83DE_CTRL_DEBUG_READ_MEMORY_PARAMS` struct — reads a block of memory from the debugged context into an embedded pointer (`buffer`), which requires a custom handler. Note that the driver passes `RMAPI_PARAM_COPY_FLAGS_DISABLE_MAX_SIZE_CHECK` for this control (see `src/nvidia/src/kernel/rmapi/embedded_param_copy.c`), so unlike most embedded pointers the buffer is *not* bounded by `RMAPI_PARAM_COPY_MAX_PARAMS_SIZE`; the handler mirrors this. - `NV83DE_CTRL_CMD_DEBUG_SUSPEND_CONTEXT` (`0x83de0317`) control command — suspends the faulting SM context so its state can be captured. - `NV83DE_CTRL_CMD_DEBUG_RESUME_CONTEXT` (`0x83de0318`) control command — resumes the context after capture. No params. - `NV83DE_CTRL_CMD_DEBUG_SET_MODE_ERRBAR_DEBUG` (`0x83de031f`) control command — enables error-barrier debug mode (Volta+), a prerequisite for capturing SM exception state. All are simple pass-through controls except `DEBUG_READ_MEMORY`. They are gated on `compute,utility`, consistent with the existing `NV83DE` debug controls (`SET_EXCEPTION_MASK`, `READ_ALL_SM_ERROR_STATES`, `CLEAR_ALL_SM_ERROR_STATES`). Coredump generation additionally uses `NV2080_CTRL_CMD_GPU_EXEC_REG_OPS` (already supported), so the feature requires running with `--nvproxy-allowed-driver-capabilities=all,profiling`. Tested on an A10G with a faulting Triton kernel: the generated `.nvcudmp` matches native (runc) byte-for-byte in size, and `cuda-gdb` fully decodes it (`CUDA Exception: Warp Illegal Address`, faulting PC/kernel/warp/lane and backtrace). Without this change, coredump generation silently produces an empty dump (`No exception was found on any device`). x
536 lines
20 KiB
Go
536 lines
20 KiB
Go
// Copyright 2023 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package nvproxy
|
|
|
|
import (
|
|
"runtime"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/abi/nvgpu"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
|
)
|
|
|
|
func frontendIoctlInvoke[Params any, PtrParams hasStatusPtr[Params]](fi *frontendIoctlState, ioctlParams PtrParams) (uintptr, error) {
|
|
n, err := frontendIoctlInvokeNoStatus(fi, ioctlParams)
|
|
if err == nil && log.IsLogging(log.Debug) {
|
|
if status := ioctlParams.GetStatus(); status != nvgpu.NV_OK {
|
|
fi.ctx.Debugf("nvproxy: frontend ioctl failed: status=%#x", status)
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func frontendIoctlInvokeNoStatus[Params any](fi *frontendIoctlState, ioctlParams *Params) (uintptr, error) {
|
|
n, _, errno := unix.RawSyscall(unix.SYS_IOCTL, uintptr(fi.fd.hostFD), frontendIoctlCmd(fi.nr, fi.ioctlParamsSize), uintptr(unsafe.Pointer(ioctlParams)))
|
|
if errno != 0 {
|
|
return n, errno
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func rmControlInvoke[Params any](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS, ctrlParams *Params) (uintptr, error) {
|
|
defer runtime.KeepAlive(ctrlParams) // since we convert to non-pointer-typed P64
|
|
origParams := ioctlParams.Params
|
|
ioctlParams.Params = p64FromPtr(unsafe.Pointer(ctrlParams))
|
|
n, err := frontendIoctlInvoke(fi, ioctlParams)
|
|
ioctlParams.Params = origParams
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func ctrlClientSystemGetBuildVersionInvoke(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS, ctrlParams *nvgpu.NV0000_CTRL_SYSTEM_GET_BUILD_VERSION_PARAMS, driverVersionBuf, versionBuf, titleBuf *byte) (uintptr, error) {
|
|
// *Buf arguments don't need runtime.KeepAlive() since our caller
|
|
// ctrlClientSystemGetBuildVersion() copies them out, keeping them alive
|
|
// during this function.
|
|
origPDriverVersionBuffer := ctrlParams.PDriverVersionBuffer
|
|
origPVersionBuffer := ctrlParams.PVersionBuffer
|
|
origPTitleBuffer := ctrlParams.PTitleBuffer
|
|
ctrlParams.PDriverVersionBuffer = p64FromPtr(unsafe.Pointer(driverVersionBuf))
|
|
ctrlParams.PVersionBuffer = p64FromPtr(unsafe.Pointer(versionBuf))
|
|
ctrlParams.PTitleBuffer = p64FromPtr(unsafe.Pointer(titleBuf))
|
|
n, err := rmControlInvoke(fi, ioctlParams, ctrlParams)
|
|
ctrlParams.PDriverVersionBuffer = origPDriverVersionBuffer
|
|
ctrlParams.PVersionBuffer = origPVersionBuffer
|
|
ctrlParams.PTitleBuffer = origPTitleBuffer
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func ctrlIoctlHasInfoList[Params any, PtrParams hasCtrlInfoListPtr[Params]](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
|
|
var ctrlParamsValue Params
|
|
ctrlParams := PtrParams(&ctrlParamsValue)
|
|
|
|
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return 0, err
|
|
}
|
|
var infoList []byte
|
|
if listSize := ctrlParams.ListSize(); listSize > 0 {
|
|
if !rmapiParamsSizeCheck(listSize, nvgpu.CtrlXxxInfoSize) {
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
|
|
}
|
|
infoList = make([]byte, listSize*nvgpu.CtrlXxxInfoSize)
|
|
if _, err := fi.t.CopyInBytes(addrFromP64(ctrlParams.CtrlInfoList()), infoList); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
origInfoList := ctrlParams.CtrlInfoList()
|
|
if infoList == nil {
|
|
ctrlParams.SetCtrlInfoList(p64FromPtr(unsafe.Pointer(nil)))
|
|
} else {
|
|
ctrlParams.SetCtrlInfoList(p64FromPtr(unsafe.Pointer(&infoList[0])))
|
|
}
|
|
n, err := rmControlInvoke(fi, ioctlParams, ctrlParams)
|
|
ctrlParams.SetCtrlInfoList(origInfoList)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
if infoList != nil {
|
|
if _, err := fi.t.CopyOutBytes(addrFromP64(origInfoList), infoList); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func ctrlGetNvU32ListInvoke(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS, ctrlParams *nvgpu.RmapiParamNvU32List, list []uint32) (uintptr, error) {
|
|
origList := ctrlParams.List
|
|
ctrlParams.List = 0
|
|
if len(list) != 0 {
|
|
ctrlParams.List = p64FromPtr(unsafe.Pointer(&list[0]))
|
|
}
|
|
n, err := rmControlInvoke(fi, ioctlParams, ctrlParams)
|
|
ctrlParams.List = origList
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if len(list) != 0 {
|
|
if _, err := primitive.CopyUint32SliceOut(fi.t, addrFromP64(ctrlParams.List), list); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func ctrlGpuExecRegOps(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
|
|
var ctrlParams nvgpu.NV2080_CTRL_GPU_EXEC_REG_OPS_PARAMS
|
|
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return 0, err
|
|
}
|
|
var regOps []byte
|
|
if ctrlParams.RegOps != 0 {
|
|
if !rmapiParamsSizeCheck(ctrlParams.RegOpCount, nvgpu.CtrlGpuRegOpSize) {
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
|
|
}
|
|
regOps = make([]byte, uint64(ctrlParams.RegOpCount)*uint64(nvgpu.CtrlGpuRegOpSize))
|
|
if _, err := fi.t.CopyInBytes(addrFromP64(ctrlParams.RegOps), regOps); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
origRegOps := ctrlParams.RegOps
|
|
ctrlParams.RegOps = 0
|
|
if len(regOps) != 0 {
|
|
ctrlParams.RegOps = p64FromPtr(unsafe.Pointer(®Ops[0]))
|
|
}
|
|
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
|
|
ctrlParams.RegOps = origRegOps
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if len(regOps) != 0 {
|
|
if _, err := fi.t.CopyOutBytes(addrFromP64(ctrlParams.RegOps), regOps); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
// debugReadMemoryMaxLength bounds the size of the sentry buffer allocated to
|
|
// proxy a single NV83DE_CTRL_CMD_DEBUG_READ_MEMORY request, preventing a
|
|
// malicious application from forcing an arbitrarily large (up to ^uint32(0)
|
|
// bytes) sentry allocation. The driver itself does not impose a limit below
|
|
// the size of the memory object being read (it passes
|
|
// RMAPI_PARAM_COPY_FLAGS_DISABLE_MAX_SIZE_CHECK for this control). In
|
|
// practice, CUDA GPU coredump generation is expected to stay comfortably
|
|
// within 1 GiB.
|
|
const debugReadMemoryMaxLength = 1 << 30 // 1 GiB
|
|
|
|
// ctrlDebugReadMemory implements NV83DE_CTRL_CMD_DEBUG_READ_MEMORY, whose
|
|
// parameters contain an embedded pointer (Buffer) that the driver fills with
|
|
// Length bytes read from the debugged context. Compare
|
|
// src/nvidia/src/kernel/rmapi/embedded_param_copy.c:embeddedParamCopyIn();
|
|
// note that the driver passes RMAPI_PARAM_COPY_FLAGS_DISABLE_MAX_SIZE_CHECK
|
|
// for this control, so Buffer's size is not limited by
|
|
// RMAPI_PARAM_COPY_MAX_PARAMS_SIZE; we bound it by debugReadMemoryMaxLength
|
|
// instead.
|
|
func ctrlDebugReadMemory(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
|
|
var ctrlParams nvgpu.NV83DE_CTRL_DEBUG_READ_MEMORY_PARAMS
|
|
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return 0, err
|
|
}
|
|
if ctrlParams.Length > debugReadMemoryMaxLength {
|
|
// NV_ERR_INVALID_ARGUMENT is consistent with the status returned by
|
|
// src/nvidia/src/kernel/rmapi/param_copy.c:rmapiParamsAcquire() when
|
|
// RMAPI_PARAM_COPY_MAX_PARAMS_SIZE is exceeded, for controls where
|
|
// that check is enabled.
|
|
fi.ctx.Warningf("nvproxy: NV83DE_CTRL_CMD_DEBUG_READ_MEMORY length %d exceeds limit %d", ctrlParams.Length, uint32(debugReadMemoryMaxLength))
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
|
|
}
|
|
var buffer []byte
|
|
if ctrlParams.Buffer != 0 && ctrlParams.Length != 0 {
|
|
buffer = make([]byte, ctrlParams.Length)
|
|
}
|
|
|
|
origBuffer := ctrlParams.Buffer
|
|
ctrlParams.Buffer = 0
|
|
if len(buffer) != 0 {
|
|
ctrlParams.Buffer = p64FromPtr(unsafe.Pointer(&buffer[0]))
|
|
}
|
|
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
|
|
ctrlParams.Buffer = origBuffer
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
// The driver copies the buffer out unconditionally (even if the control
|
|
// returned a non-OK status); see
|
|
// src/nvidia/src/kernel/rmapi/embedded_param_copy.c:embeddedParamCopyOut().
|
|
if len(buffer) != 0 {
|
|
if _, err := fi.t.CopyOutBytes(addrFromP64(origBuffer), buffer); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func ctrlDevGRGetCapsInvoke(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS, ctrlParams *nvgpu.NV0080_CTRL_GET_CAPS_PARAMS, capsTbl []byte) (uintptr, error) {
|
|
origCapsTbl := ctrlParams.CapsTbl
|
|
ctrlParams.CapsTbl = p64FromPtr(unsafe.Pointer(&capsTbl[0]))
|
|
n, err := rmControlInvoke(fi, ioctlParams, ctrlParams)
|
|
ctrlParams.CapsTbl = origCapsTbl
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := primitive.CopyByteSliceOut(fi.t, addrFromP64(ctrlParams.CapsTbl), capsTbl); err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func ctrlDevFIFOGetChannelList(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
|
|
var ctrlParams nvgpu.NV0080_CTRL_FIFO_GET_CHANNELLIST_PARAMS
|
|
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return 0, err
|
|
}
|
|
if !rmapiParamsSizeCheck(ctrlParams.NumChannels, 4 /* sizeof(NvU32) */) {
|
|
// Compare
|
|
// src/nvidia/src/kernel/gpu/fifo/kernel_fifo_ctrl.c:deviceCtrlCmdFifoGetChannelList_IMPL().
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
channelHandleList := make([]uint32, ctrlParams.NumChannels)
|
|
if _, err := primitive.CopyUint32SliceIn(fi.t, addrFromP64(ctrlParams.PChannelHandleList), channelHandleList); err != nil {
|
|
return 0, err
|
|
}
|
|
channelList := make([]uint32, ctrlParams.NumChannels)
|
|
if _, err := primitive.CopyUint32SliceIn(fi.t, addrFromP64(ctrlParams.PChannelList), channelList); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
origPChannelHandleList := ctrlParams.PChannelHandleList
|
|
origPChannelList := ctrlParams.PChannelList
|
|
ctrlParams.PChannelHandleList = p64FromPtr(unsafe.Pointer(&channelHandleList[0]))
|
|
ctrlParams.PChannelList = p64FromPtr(unsafe.Pointer(&channelList[0]))
|
|
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
|
|
ctrlParams.PChannelHandleList = origPChannelHandleList
|
|
ctrlParams.PChannelList = origPChannelList
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
if _, err := primitive.CopyUint32SliceOut(fi.t, addrFromP64(origPChannelHandleList), channelHandleList); err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := primitive.CopyUint32SliceOut(fi.t, addrFromP64(origPChannelList), channelList); err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func ctrlClientSystemGetP2PCapsInitializeArray(origArr nvgpu.P64, gpuCount uint32) (nvgpu.P64, []uint32, bool) {
|
|
// The driver doesn't try and copy memory if the array is null. See
|
|
// src/nvidia/src/kernel/rmapi/embedded_param_copy.c::embeddedParamCopyIn(),
|
|
// case NV0000_CTRL_CMD_SYSTEM_GET_P2P_CAPS.
|
|
if origArr == 0 {
|
|
return 0, nil, true
|
|
}
|
|
|
|
// Params size is gpuCount * gpuCount * sizeof(NvU32).
|
|
// Use uint64 to handle overflow. In the driver, this is handled by
|
|
// portSafeMulU32(). See
|
|
// src/nvidia/src/kernel/rmapi/embedded_param_copy.c::embeddedParamCopyIn().
|
|
numEntries := uint64(gpuCount) * uint64(gpuCount)
|
|
if numEntries == 0 || numEntries > nvgpu.RMAPI_PARAM_COPY_MAX_PARAMS_SIZE/4 {
|
|
return 0, nil, false
|
|
}
|
|
|
|
arr := make([]uint32, numEntries)
|
|
return p64FromPtr(unsafe.Pointer(&arr[0])), arr, true
|
|
}
|
|
|
|
func ctrlClientSystemGetP2PCaps(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
|
|
var ctrlParams nvgpu.NV0000_CTRL_SYSTEM_GET_P2P_CAPS_PARAMS
|
|
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
origBusPeerIDs := ctrlParams.BusPeerIDs
|
|
busPeerIDs, busPeerIDsBuf, ok := ctrlClientSystemGetP2PCapsInitializeArray(origBusPeerIDs, ctrlParams.GpuCount)
|
|
if !ok {
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
|
|
}
|
|
ctrlParams.BusPeerIDs = busPeerIDs
|
|
|
|
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
|
|
ctrlParams.BusPeerIDs = origBusPeerIDs
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
if _, err := primitive.CopyUint32SliceOut(fi.t, addrFromP64(origBusPeerIDs), busPeerIDsBuf); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
_, err = ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params))
|
|
return n, err
|
|
}
|
|
|
|
func ctrlClientSystemGetP2PCapsV550(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54_PARAMETERS) (uintptr, error) {
|
|
var ctrlParams nvgpu.NV0000_CTRL_SYSTEM_GET_P2P_CAPS_PARAMS_V550
|
|
if ctrlParams.SizeBytes() != int(ioctlParams.ParamsSize) {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if _, err := ctrlParams.CopyIn(fi.t, addrFromP64(ioctlParams.Params)); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
origBusPeerIDs := ctrlParams.BusPeerIDs
|
|
busPeerIDs, busPeerIDsBuf, ok := ctrlClientSystemGetP2PCapsInitializeArray(origBusPeerIDs, ctrlParams.GpuCount)
|
|
if !ok {
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
|
|
}
|
|
ctrlParams.BusPeerIDs = busPeerIDs
|
|
|
|
origBusEgmPeerIDs := ctrlParams.BusEgmPeerIDs
|
|
busEgmPeerIDs, busEgmPeerIDsBuf, ok := ctrlClientSystemGetP2PCapsInitializeArray(origBusEgmPeerIDs, ctrlParams.GpuCount)
|
|
if !ok {
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_ARGUMENT)
|
|
}
|
|
ctrlParams.BusEgmPeerIDs = busEgmPeerIDs
|
|
|
|
n, err := rmControlInvoke(fi, ioctlParams, &ctrlParams)
|
|
ctrlParams.BusPeerIDs = origBusPeerIDs
|
|
ctrlParams.BusEgmPeerIDs = origBusEgmPeerIDs
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
// If origBufPeerIDS or origBusEgmPeerIDs is null, the corresponding buffer will be nil
|
|
// and CopyUint32SliceOut() will be a no-op.
|
|
if _, err := primitive.CopyUint32SliceOut(fi.t, addrFromP64(origBusPeerIDs), busPeerIDsBuf); err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := primitive.CopyUint32SliceOut(fi.t, addrFromP64(origBusEgmPeerIDs), busEgmPeerIDsBuf); err != nil {
|
|
return n, err
|
|
}
|
|
_, err = ctrlParams.CopyOut(fi.t, addrFromP64(ioctlParams.Params))
|
|
return n, err
|
|
}
|
|
|
|
func rmAllocInvoke[Params any](fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64_PARAMETERS, allocParams *Params, isNVOS64 bool, addObjLocked func(fi *frontendIoctlState, client *rootClient, ioctlParams *nvgpu.NVOS64_PARAMETERS, rightsRequested nvgpu.RS_ACCESS_MASK, allocParams *Params)) (uintptr, error) {
|
|
defer runtime.KeepAlive(allocParams) // since we convert to non-pointer-typed P64
|
|
|
|
// Temporarily replace application pointers with sentry pointers.
|
|
origPAllocParms := ioctlParams.PAllocParms
|
|
origPRightsRequested := ioctlParams.PRightsRequested
|
|
var rightsRequested nvgpu.RS_ACCESS_MASK
|
|
if ioctlParams.PRightsRequested != 0 {
|
|
if _, err := rightsRequested.CopyIn(fi.t, addrFromP64(ioctlParams.PRightsRequested)); err != nil {
|
|
return 0, err
|
|
}
|
|
ioctlParams.PRightsRequested = p64FromPtr(unsafe.Pointer(&rightsRequested))
|
|
}
|
|
ioctlParams.PAllocParms = p64FromPtr(unsafe.Pointer(allocParams))
|
|
|
|
var (
|
|
client *rootClient
|
|
unlock = func() {}
|
|
)
|
|
if !ioctlParams.HClass.IsRootClient() {
|
|
client, unlock = fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.HRoot)
|
|
if client == nil {
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
|
|
}
|
|
}
|
|
// Invoke the driver ioctl and restore application pointers. We always pass
|
|
// NVOS64Parameters to the driver even if !isNVOS64, as this is handled
|
|
// identically to the equivalent NVOS21Parameters; compare
|
|
// src/nvidia/src/kernel/rmapi/entry_points.c:_nv04AllocWithSecInfo() and
|
|
// _nv04AllocWithAccessSecInfo().
|
|
origParamsSize := fi.ioctlParamsSize
|
|
fi.ioctlParamsSize = nvgpu.SizeofNVOS64Parameters
|
|
n, err := frontendIoctlInvoke(fi, ioctlParams)
|
|
fi.ioctlParamsSize = origParamsSize
|
|
if err == nil && ioctlParams.Status == nvgpu.NV_OK {
|
|
addObjLocked(fi, client, ioctlParams, rightsRequested, allocParams)
|
|
}
|
|
unlock()
|
|
ioctlParams.PAllocParms = origPAllocParms
|
|
ioctlParams.PRightsRequested = origPRightsRequested
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
// Copy updated params out to the application.
|
|
outIoctlParams := nvgpu.GetRmAllocParamObj(isNVOS64)
|
|
outIoctlParams.FromOS64(*ioctlParams)
|
|
if ioctlParams.PRightsRequested != 0 {
|
|
if _, err := rightsRequested.CopyOut(fi.t, addrFromP64(ioctlParams.PRightsRequested)); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
if _, err := outIoctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func rmIdleChannelsInvoke(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS30_PARAMETERS, clientsBuf, devicesBuf, channelsBuf *byte) (uintptr, error) {
|
|
origClients := ioctlParams.Clients
|
|
origDevices := ioctlParams.Devices
|
|
origChannels := ioctlParams.Channels
|
|
ioctlParams.Clients = p64FromPtr(unsafe.Pointer(clientsBuf))
|
|
ioctlParams.Devices = p64FromPtr(unsafe.Pointer(devicesBuf))
|
|
ioctlParams.Channels = p64FromPtr(unsafe.Pointer(channelsBuf))
|
|
n, err := frontendIoctlInvoke(fi, ioctlParams)
|
|
ioctlParams.Clients = origClients
|
|
ioctlParams.Devices = origDevices
|
|
ioctlParams.Channels = origChannels
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
|
|
return n, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func rmVidHeapControlAllocSize(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS32_PARAMETERS) (uintptr, error) {
|
|
allocSizeParams := (*nvgpu.NVOS32AllocSize)(unsafe.Pointer(&ioctlParams.Data))
|
|
origAddress := allocSizeParams.Address
|
|
var addr uint64
|
|
if allocSizeParams.Address != 0 {
|
|
if _, err := primitive.CopyUint64In(fi.t, addrFromP64(allocSizeParams.Address), &addr); err != nil {
|
|
return 0, err
|
|
}
|
|
allocSizeParams.Address = p64FromPtr(unsafe.Pointer(&addr))
|
|
}
|
|
|
|
client, unlock := fi.fd.dev.nvp.getClientWithLock(fi.ctx, ioctlParams.HRoot)
|
|
if client == nil {
|
|
return 0, frontendFailWithStatus(fi, ioctlParams, nvgpu.NV_ERR_INVALID_CLIENT)
|
|
}
|
|
n, err := frontendIoctlInvoke(fi, ioctlParams)
|
|
if err == nil && ioctlParams.Status == nvgpu.NV_OK {
|
|
// src/nvidia/interface/deprecated/rmapi_deprecated_vidheapctrl.c:_rmVidHeapControlAllocCommon()
|
|
if allocSizeParams.Flags&nvgpu.NVOS32_ALLOC_FLAGS_VIRTUAL != 0 {
|
|
// src/nvidia/src/kernel/mem_mgr/virtual_mem.c:virtmemConstruct_IMPL() => refAddDependant()
|
|
fi.fd.dev.nvp.objAdd(fi.ctx, client, allocSizeParams.HMemory, nvgpu.NV50_MEMORY_VIRTUAL, &miscObject{}, ioctlParams.HObjectParent, ioctlParams.HVASpace)
|
|
} else {
|
|
classID := nvgpu.ClassID(nvgpu.NV01_MEMORY_SYSTEM)
|
|
if (allocSizeParams.Attr2>>nvgpu.NVOS32_ATTR2_USE_EGM_SHIFT)&nvgpu.NVOS32_ATTR2_USE_EGM_MASK == nvgpu.NVOS32_ATTR2_USE_EGM_TRUE {
|
|
classID = nvgpu.NV_MEMORY_EXTENDED_USER
|
|
} else if (allocSizeParams.Attr>>nvgpu.NVOS32_ATTR_LOCATION_SHIFT)&nvgpu.NVOS32_ATTR_LOCATION_MASK == nvgpu.NVOS32_ATTR_LOCATION_VIDMEM {
|
|
classID = nvgpu.NV01_MEMORY_LOCAL_USER
|
|
}
|
|
fi.fd.dev.nvp.objAdd(fi.ctx, client, allocSizeParams.HMemory, classID, &miscObject{}, ioctlParams.HObjectParent)
|
|
}
|
|
}
|
|
unlock()
|
|
allocSizeParams.Address = origAddress
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
if allocSizeParams.Address != 0 {
|
|
if _, err := primitive.CopyUint64Out(fi.t, addrFromP64(allocSizeParams.Address), addr); err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
if _, err := ioctlParams.CopyOut(fi.t, fi.ioctlParamsAddr); err != nil {
|
|
return n, err
|
|
}
|
|
|
|
return n, nil
|
|
}
|