From cce5b85235bd6d90742b2d5310811efc9284c6bb Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Wed, 4 Oct 2023 20:30:18 -0700 Subject: [PATCH] Add driver versioning support in nvproxy. This adds utilities to make it easier to add versioning support in nvproxy. This way we can have different handling of ioctls based on driver versions. The largest downside of this solution is the usage of maps instead of switch statements. Switch statements are *way* faster than map accesses. The rationale is that most of the latency bound work happens on the GPU for GPU workloads. So micro-optimizations like using switch statements should not be visible. Co-authored-by: Jamie Liu PiperOrigin-RevId: 570889581 --- pkg/sentry/devices/nvproxy/BUILD | 9 +- pkg/sentry/devices/nvproxy/frontend.go | 170 ++------------ pkg/sentry/devices/nvproxy/nvproxy.go | 21 +- pkg/sentry/devices/nvproxy/nvproxy_test.go | 27 +++ pkg/sentry/devices/nvproxy/uvm.go | 44 +--- pkg/sentry/devices/nvproxy/version.go | 257 +++++++++++++++++++++ runsc/boot/loader.go | 5 + 7 files changed, 332 insertions(+), 201 deletions(-) create mode 100644 pkg/sentry/devices/nvproxy/nvproxy_test.go create mode 100644 pkg/sentry/devices/nvproxy/version.go diff --git a/pkg/sentry/devices/nvproxy/BUILD b/pkg/sentry/devices/nvproxy/BUILD index 30d4a0290..fce7f032b 100644 --- a/pkg/sentry/devices/nvproxy/BUILD +++ b/pkg/sentry/devices/nvproxy/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "go_library") +load("//tools:defs.bzl", "go_library", "go_test") load("//pkg/sync/locking:locking.bzl", "declare_mutex") package(default_applicable_licenses = ["//:license"]) @@ -25,6 +25,7 @@ go_library( "uvm.go", "uvm_mmap.go", "uvm_unsafe.go", + "version.go", ], visibility = ["//pkg/sentry:internal"], deps = [ @@ -53,3 +54,9 @@ go_library( "@org_golang_x_sys//unix:go_default_library", ], ) + +go_test( + name = "nvproxy_test", + srcs = ["nvproxy_test.go"], + library = ":nvproxy", +) diff --git a/pkg/sentry/devices/nvproxy/frontend.go b/pkg/sentry/devices/nvproxy/frontend.go index 36be253a7..07428ab04 100644 --- a/pkg/sentry/devices/nvproxy/frontend.go +++ b/pkg/sentry/devices/nvproxy/frontend.go @@ -167,44 +167,12 @@ func (fd *frontendFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, // - Add symbol and parameter type definitions to //pkg/abi/nvgpu. // - Add filter to seccomp_filters.go. // - Add handling below. - switch nr { - case - nvgpu.NV_ESC_CARD_INFO, // nv_ioctl_card_info_t - nvgpu.NV_ESC_CHECK_VERSION_STR, // nv_rm_api_version_t - nvgpu.NV_ESC_SYS_PARAMS, // nv_ioctl_sys_params_t - nvgpu.NV_ESC_RM_DUP_OBJECT, // NVOS55_PARAMETERS - nvgpu.NV_ESC_RM_SHARE, // NVOS57_PARAMETERS - nvgpu.NV_ESC_RM_UNMAP_MEMORY, // NVOS34_PARAMETERS - nvgpu.NV_ESC_RM_UPDATE_DEVICE_MAPPING_INFO: // NVOS56_PARAMETERS - return frontendIoctlSimple(&fi) - case nvgpu.NV_ESC_REGISTER_FD: - return frontendRegisterFD(&fi) - case nvgpu.NV_ESC_ALLOC_OS_EVENT: - return rmAllocOSEvent(&fi) - case nvgpu.NV_ESC_FREE_OS_EVENT: - return rmFreeOSEvent(&fi) - case nvgpu.NV_ESC_NUMA_INFO: - // The CPU topology seen by the host driver differs from the CPU - // topology presented by the sentry to the application, so reject this - // ioctl; doing so is non-fatal. - ctx.Debugf("nvproxy: ignoring NV_ESC_NUMA_INFO") - return 0, linuxerr.EINVAL - case nvgpu.NV_ESC_RM_ALLOC_MEMORY: - return rmAllocMemory(&fi) - case nvgpu.NV_ESC_RM_FREE: - return rmFree(&fi) - case nvgpu.NV_ESC_RM_CONTROL: - return rmControl(&fi) - case nvgpu.NV_ESC_RM_ALLOC: - return rmAlloc(&fi) - case nvgpu.NV_ESC_RM_VID_HEAP_CONTROL: - return rmVidHeapControl(&fi) - case nvgpu.NV_ESC_RM_MAP_MEMORY: - return rmMapMemory(&fi) - default: + handler := fd.nvp.abi.frontendIoctl[nr] + if handler == nil { ctx.Warningf("nvproxy: unknown frontend ioctl %d == %#x (argSize=%d, cmd=%#x)", nr, nr, argSize, cmd) return 0, linuxerr.EINVAL } + return handler(&fi) } func frontendIoctlCmd(nr, argSize uint32) uintptr { @@ -243,6 +211,14 @@ func frontendIoctlSimple(fi *frontendIoctlState) (uintptr, error) { return n, nil } +func rmNumaInfo(fi *frontendIoctlState) (uintptr, error) { + // The CPU topology seen by the host driver differs from the CPU + // topology presented by the sentry to the application, so reject this + // ioctl; doing so is non-fatal. + log.Debugf("nvproxy: ignoring NV_ESC_NUMA_INFO") + return 0, linuxerr.EINVAL +} + func frontendRegisterFD(fi *frontendIoctlState) (uintptr, error) { var ioctlParams nvgpu.IoctlRegisterFD if fi.ioctlParamsSize != nvgpu.SizeofIoctlRegisterFD { @@ -532,95 +508,12 @@ func rmControl(fi *frontendIoctlState) (uintptr, error) { // - Add symbol definition to //pkg/abi/nvgpu. Parameter type definition is // only required for non-simple commands. // - Add handling below. - switch ioctlParams.Cmd { - case - nvgpu.NV0000_CTRL_CMD_CLIENT_GET_ADDR_SPACE_TYPE, - nvgpu.NV0000_CTRL_CMD_CLIENT_SET_INHERITED_SHARE_POLICY, - nvgpu.NV0000_CTRL_CMD_GPU_GET_ATTACHED_IDS, - nvgpu.NV0000_CTRL_CMD_GPU_GET_ID_INFO, - nvgpu.NV0000_CTRL_CMD_GPU_GET_ID_INFO_V2, - nvgpu.NV0000_CTRL_CMD_GPU_GET_PROBED_IDS, - nvgpu.NV0000_CTRL_CMD_GPU_ATTACH_IDS, - nvgpu.NV0000_CTRL_CMD_GPU_DETACH_IDS, - nvgpu.NV0000_CTRL_CMD_GPU_GET_PCI_INFO, - nvgpu.NV0000_CTRL_CMD_GPU_QUERY_DRAIN_STATE, - nvgpu.NV0000_CTRL_CMD_GPU_GET_MEMOP_ENABLE, - nvgpu.NV0000_CTRL_CMD_SYNC_GPU_BOOST_GROUP_INFO, - nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_P2P_CAPS, - nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_FABRIC_STATUS, - nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_P2P_CAPS_MATRIX, - nvgpu.NV0080_CTRL_CMD_FB_GET_CAPS_V2, - nvgpu.NV0080_CTRL_CMD_GPU_GET_NUM_SUBDEVICES, - nvgpu.NV0080_CTRL_CMD_GPU_QUERY_SW_STATE_PERSISTENCE, - nvgpu.NV0080_CTRL_CMD_GPU_GET_VIRTUALIZATION_MODE, - 0x80028b, // unknown, paramsSize == 1 - nvgpu.NV0080_CTRL_CMD_GPU_GET_CLASSLIST_V2, - nvgpu.NV0080_CTRL_CMD_HOST_GET_CAPS_V2, - nvgpu.NV2080_CTRL_CMD_BUS_GET_PCI_INFO, - nvgpu.NV2080_CTRL_CMD_BUS_GET_PCI_BAR_INFO, - nvgpu.NV2080_CTRL_CMD_BUS_GET_INFO_V2, - nvgpu.NV2080_CTRL_CMD_BUS_GET_PCIE_SUPPORTED_GPU_ATOMICS, - nvgpu.NV2080_CTRL_CMD_CE_GET_ALL_CAPS, - nvgpu.NV2080_CTRL_CMD_FB_GET_INFO_V2, - nvgpu.NV2080_CTRL_CMD_GPU_GET_INFO_V2, - nvgpu.NV2080_CTRL_CMD_GPU_GET_NAME_STRING, - nvgpu.NV2080_CTRL_CMD_GPU_GET_SHORT_NAME_STRING, - nvgpu.NV2080_CTRL_CMD_GPU_GET_SIMULATION_INFO, - nvgpu.NV2080_CTRL_CMD_GPU_QUERY_ECC_STATUS, - nvgpu.NV2080_CTRL_CMD_GPU_QUERY_COMPUTE_MODE_RULES, - nvgpu.NV2080_CTRL_CMD_GPU_ACQUIRE_COMPUTE_MODE_RESERVATION, - nvgpu.NV2080_CTRL_CMD_GPU_RELEASE_COMPUTE_MODE_RESERVATION, - nvgpu.NV2080_CTRL_CMD_GPU_GET_GID_INFO, - nvgpu.NV2080_CTRL_CMD_GPU_GET_ENGINES_V2, - nvgpu.NV2080_CTRL_CMD_GPU_GET_ACTIVE_PARTITION_IDS, - nvgpu.NV2080_CTRL_CMD_GPU_GET_COMPUTE_POLICY_CONFIG, - nvgpu.NV2080_CTRL_CMD_GET_GPU_FABRIC_PROBE_INFO, - nvgpu.NV2080_CTRL_CMD_GR_SET_CTXSW_PREEMPTION_MODE, - nvgpu.NV2080_CTRL_CMD_GR_GET_CTX_BUFFER_SIZE, - nvgpu.NV2080_CTRL_CMD_GR_GET_GLOBAL_SM_ORDER, - nvgpu.NV2080_CTRL_CMD_GR_GET_CAPS_V2, - nvgpu.NV2080_CTRL_CMD_GR_GET_GPC_MASK, - nvgpu.NV2080_CTRL_CMD_GR_GET_TPC_MASK, - nvgpu.NV2080_CTRL_CMD_GSP_GET_FEATURES, - nvgpu.NV2080_CTRL_CMD_MC_GET_ARCH_INFO, - nvgpu.NV2080_CTRL_CMD_MC_SERVICE_INTERRUPTS, - nvgpu.NV2080_CTRL_CMD_NVLINK_GET_NVLINK_STATUS, - nvgpu.NV2080_CTRL_CMD_PERF_BOOST, - nvgpu.NV2080_CTRL_CMD_RC_GET_WATCHDOG_INFO, - nvgpu.NV2080_CTRL_CMD_RC_RELEASE_WATCHDOG_REQUESTS, - nvgpu.NV2080_CTRL_CMD_RC_SOFT_DISABLE_WATCHDOG, - nvgpu.NV2080_CTRL_CMD_TIMER_GET_GPU_CPU_TIME_CORRELATION_INFO, - nvgpu.NV503C_CTRL_CMD_REGISTER_VA_SPACE, - nvgpu.NV503C_CTRL_CMD_REGISTER_VIDMEM, - nvgpu.NV503C_CTRL_CMD_UNREGISTER_VIDMEM, - nvgpu.NV83DE_CTRL_CMD_DEBUG_SET_EXCEPTION_MASK, - nvgpu.NV83DE_CTRL_CMD_DEBUG_READ_ALL_SM_ERROR_STATES, - nvgpu.NV83DE_CTRL_CMD_DEBUG_CLEAR_ALL_SM_ERROR_STATES, - nvgpu.NV906F_CTRL_CMD_RESET_CHANNEL, - nvgpu.NV90E6_CTRL_CMD_MASTER_GET_VIRTUAL_FUNCTION_ERROR_CONT_INTR_MASK, - nvgpu.NVC36F_CTRL_GET_CLASS_ENGINEID, - nvgpu.NVC36F_CTRL_CMD_GPFIFO_GET_WORK_SUBMIT_TOKEN, - nvgpu.NVA06C_CTRL_CMD_GPFIFO_SCHEDULE, - nvgpu.NVA06C_CTRL_CMD_SET_TIMESLICE, - nvgpu.NVA06C_CTRL_CMD_PREEMPT: - return rmControlSimple(fi, &ioctlParams) - - case nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_BUILD_VERSION: - return ctrlClientSystemGetBuildVersion(fi, &ioctlParams) - - case nvgpu.NV0080_CTRL_CMD_FIFO_GET_CHANNELLIST: - return ctrlDevFIFOGetChannelList(fi, &ioctlParams) - - case nvgpu.NV2080_CTRL_CMD_FIFO_DISABLE_CHANNELS: - return ctrlSubdevFIFODisableChannels(fi, &ioctlParams) - - case nvgpu.NV2080_CTRL_CMD_GR_GET_INFO: - return ctrlSubdevGRGetInfo(fi, &ioctlParams) - - default: + handler := fi.fd.nvp.abi.controlCmd[ioctlParams.Cmd] + if handler == nil { fi.ctx.Warningf("nvproxy: unknown control command %#x (paramsSize=%d)", ioctlParams.Cmd, ioctlParams.ParamsSize) return 0, linuxerr.EINVAL } + return handler(fi, &ioctlParams) } func rmControlSimple(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) { @@ -752,41 +645,12 @@ func rmAlloc(fi *frontendIoctlState) (uintptr, error) { // the class whose constructor interprets it ("Internal Class"). // - Add symbol and parameter type definitions to //pkg/abi/nvgpu. // - Add handling below. - switch ioctlParams.HClass { - case nvgpu.NV01_ROOT, nvgpu.NV01_ROOT_NON_PRIV, nvgpu.NV01_ROOT_CLIENT: - return rmAllocSimple[nvgpu.Handle](fi, &ioctlParams, isNVOS64) - case nvgpu.NV01_EVENT_OS_EVENT: - return rmAllocEventOSEvent(fi, &ioctlParams, isNVOS64) - case nvgpu.NV01_DEVICE_0: - return rmAllocSimple[nvgpu.NV0080_ALLOC_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.NV20_SUBDEVICE_0: - return rmAllocSimple[nvgpu.NV2080_ALLOC_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.NV50_THIRD_PARTY_P2P: - return rmAllocSimple[nvgpu.NV503C_ALLOC_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.GT200_DEBUGGER: - return rmAllocSimple[nvgpu.NV83DE_ALLOC_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.FERMI_CONTEXT_SHARE_A: - return rmAllocSimple[nvgpu.NV_CTXSHARE_ALLOCATION_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.FERMI_VASPACE_A: - return rmAllocSimple[nvgpu.NV_VASPACE_ALLOCATION_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.KEPLER_CHANNEL_GROUP_A: - return rmAllocSimple[nvgpu.NV_CHANNEL_GROUP_ALLOCATION_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.TURING_CHANNEL_GPFIFO_A, nvgpu.AMPERE_CHANNEL_GPFIFO_A: - return rmAllocSimple[nvgpu.NV_CHANNEL_ALLOC_PARAMS](fi, &ioctlParams, isNVOS64) - case nvgpu.TURING_DMA_COPY_A, nvgpu.AMPERE_DMA_COPY_A, nvgpu.AMPERE_DMA_COPY_B, nvgpu.HOPPER_DMA_COPY_A: - return rmAllocSimple[nvgpu.NVB0B5_ALLOCATION_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.TURING_COMPUTE_A, nvgpu.AMPERE_COMPUTE_A, nvgpu.AMPERE_COMPUTE_B, nvgpu.ADA_COMPUTE_A, nvgpu.HOPPER_COMPUTE_A: - return rmAllocSimple[nvgpu.NV_GR_ALLOCATION_PARAMETERS](fi, &ioctlParams, isNVOS64) - case nvgpu.HOPPER_USERMODE_A: - return rmAllocSimple[nvgpu.NV_HOPPER_USERMODE_A_PARAMS](fi, &ioctlParams, isNVOS64) - case nvgpu.GF100_SUBDEVICE_MASTER, nvgpu.TURING_USERMODE_A: - return rmAllocNoParams(fi, &ioctlParams, isNVOS64) - case nvgpu.NV_MEMORY_FABRIC: - return rmAllocSimple[nvgpu.NV00F8_ALLOCATION_PARAMETERS](fi, &ioctlParams, isNVOS64) - default: + handler := fi.fd.nvp.abi.allocationClass[ioctlParams.HClass] + if handler == nil { fi.ctx.Warningf("nvproxy: unknown allocation class %#08x", ioctlParams.HClass) return 0, linuxerr.EINVAL } + return handler(fi, &ioctlParams, isNVOS64) } // Unlike frontendIoctlSimple and rmControlSimple, rmAllocSimple requires the diff --git a/pkg/sentry/devices/nvproxy/nvproxy.go b/pkg/sentry/devices/nvproxy/nvproxy.go index 7903165e1..bd58cc4d3 100644 --- a/pkg/sentry/devices/nvproxy/nvproxy.go +++ b/pkg/sentry/devices/nvproxy/nvproxy.go @@ -35,22 +35,22 @@ import ( func Register(vfsObj *vfs.VirtualFilesystem, uvmDevMajor uint32) error { // The kernel driver's interface is unstable, so only allow versions of the // driver that are known to be supported. - version, err := hostDriverVersion() + versionStr, err := hostDriverVersion() if err != nil { return fmt.Errorf("failed to get Nvidia driver version: %w", err) } - switch version { - case - "525.60.13", - "525.105.17", - "525.125.06": - log.Infof("Nvidia driver version: %s", version) - default: - return fmt.Errorf("unsupported Nvidia driver version: %s", version) + version, err := driverVersionFrom(versionStr) + if err != nil { + return fmt.Errorf("failed to parse Nvidia driver version %s: %w", versionStr, err) } - + abiCons, ok := abis[version] + if !ok { + return fmt.Errorf("unsupported Nvidia driver version: %s", versionStr) + } + log.Infof("Nvidia driver version: %s", versionStr) nvp := &nvproxy{ objsLive: make(map[nvgpu.Handle]*object), + abi: abiCons(), } for minor := uint32(0); minor <= nvgpu.NV_CONTROL_DEVICE_MINOR; minor++ { if err := vfsObj.RegisterDevice(vfs.CharDevice, nvgpu.NV_MAJOR_DEVICE_NUMBER, minor, &frontendDevice{ @@ -95,6 +95,7 @@ func CreateIndexDevtmpfsFile(ctx context.Context, dev *devtmpfs.Accessor, minor type nvproxy struct { objsMu objsMutex `state:"nosave"` objsLive map[nvgpu.Handle]*object + abi *driverABI } // object tracks an object allocated through the driver. diff --git a/pkg/sentry/devices/nvproxy/nvproxy_test.go b/pkg/sentry/devices/nvproxy/nvproxy_test.go new file mode 100644 index 000000000..a819200b5 --- /dev/null +++ b/pkg/sentry/devices/nvproxy/nvproxy_test.go @@ -0,0 +1,27 @@ +// 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 ( + "testing" +) + +func TestInit(t *testing.T) { + // Test that initializing all driverABI works (does not panic or anything). + Init() + for _, cons := range abis { + cons() + } +} diff --git a/pkg/sentry/devices/nvproxy/uvm.go b/pkg/sentry/devices/nvproxy/uvm.go index 108091e4a..74085348a 100644 --- a/pkg/sentry/devices/nvproxy/uvm.go +++ b/pkg/sentry/devices/nvproxy/uvm.go @@ -131,46 +131,12 @@ func (fd *uvmFD) Ioctl(ctx context.Context, uio usermem.IO, sysno uintptr, args cmd: cmd, ioctlParamsAddr: argPtr, } - - switch cmd { - case nvgpu.UVM_INITIALIZE: - return uvmInitialize(&ui) - case nvgpu.UVM_DEINITIALIZE: - return uvmIoctlInvoke[byte](&ui, nil) - case nvgpu.UVM_CREATE_RANGE_GROUP: - return uvmIoctlSimple[nvgpu.UVM_CREATE_RANGE_GROUP_PARAMS](&ui) - case nvgpu.UVM_DESTROY_RANGE_GROUP: - return uvmIoctlSimple[nvgpu.UVM_DESTROY_RANGE_GROUP_PARAMS](&ui) - case nvgpu.UVM_REGISTER_GPU_VASPACE: - return uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_GPU_VASPACE_PARAMS](&ui) - case nvgpu.UVM_UNREGISTER_GPU_VASPACE: - return uvmIoctlSimple[nvgpu.UVM_UNREGISTER_GPU_VASPACE_PARAMS](&ui) - case nvgpu.UVM_REGISTER_CHANNEL: - return uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_CHANNEL_PARAMS](&ui) - case nvgpu.UVM_UNREGISTER_CHANNEL: - return uvmIoctlSimple[nvgpu.UVM_UNREGISTER_CHANNEL_PARAMS](&ui) - case nvgpu.UVM_MAP_EXTERNAL_ALLOCATION: - return uvmIoctlHasRMCtrlFD[nvgpu.UVM_MAP_EXTERNAL_ALLOCATION_PARAMS](&ui) - case nvgpu.UVM_FREE: - return uvmIoctlSimple[nvgpu.UVM_FREE_PARAMS](&ui) - case nvgpu.UVM_REGISTER_GPU: - return uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_GPU_PARAMS](&ui) - case nvgpu.UVM_UNREGISTER_GPU: - return uvmIoctlSimple[nvgpu.UVM_UNREGISTER_GPU_PARAMS](&ui) - case nvgpu.UVM_PAGEABLE_MEM_ACCESS: - return uvmIoctlSimple[nvgpu.UVM_PAGEABLE_MEM_ACCESS_PARAMS](&ui) - case nvgpu.UVM_MAP_DYNAMIC_PARALLELISM_REGION: - return uvmIoctlSimple[nvgpu.UVM_MAP_DYNAMIC_PARALLELISM_REGION_PARAMS](&ui) - case nvgpu.UVM_ALLOC_SEMAPHORE_POOL: - return uvmIoctlSimple[nvgpu.UVM_ALLOC_SEMAPHORE_POOL_PARAMS](&ui) - case nvgpu.UVM_VALIDATE_VA_RANGE: - return uvmIoctlSimple[nvgpu.UVM_VALIDATE_VA_RANGE_PARAMS](&ui) - case nvgpu.UVM_CREATE_EXTERNAL_RANGE: - return uvmIoctlSimple[nvgpu.UVM_CREATE_EXTERNAL_RANGE_PARAMS](&ui) - default: + handler := fd.nvp.abi.uvmIoctl[cmd] + if handler == nil { ctx.Warningf("nvproxy: unknown uvm ioctl %d", cmd) return 0, linuxerr.EINVAL } + return handler(&ui) } // uvmIoctlState holds the state of a call to uvmFD.Ioctl(). @@ -182,6 +148,10 @@ type uvmIoctlState struct { ioctlParamsAddr hostarch.Addr } +func uvmIoctlNoParams(ui *uvmIoctlState) (uintptr, error) { + return uvmIoctlInvoke[byte](ui, nil) +} + func uvmIoctlSimple[Params any, PParams marshalPtr[Params]](ui *uvmIoctlState) (uintptr, error) { var ioctlParams Params if _, err := (PParams)(&ioctlParams).CopyIn(ui.t, ui.ioctlParamsAddr); err != nil { diff --git a/pkg/sentry/devices/nvproxy/version.go b/pkg/sentry/devices/nvproxy/version.go new file mode 100644 index 000000000..dad6708ff --- /dev/null +++ b/pkg/sentry/devices/nvproxy/version.go @@ -0,0 +1,257 @@ +// Copyright 2023 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package nvproxy + +import ( + "fmt" + "strconv" + "strings" + + "gvisor.dev/gvisor/pkg/abi/nvgpu" + "gvisor.dev/gvisor/pkg/sync" +) + +type driverVersion struct { + major int + minor int + patch int +} + +func driverVersionFrom(version string) (driverVersion, error) { + parts := strings.Split(version, ".") + if len(parts) != 3 { + return driverVersion{}, fmt.Errorf("invalid format of version string %q", version) + } + var ( + res driverVersion + err error + ) + res.major, err = strconv.Atoi(parts[0]) + if err != nil { + return driverVersion{}, fmt.Errorf("invalid format for major version %q: %v", version, err) + } + res.minor, err = strconv.Atoi(parts[1]) + if err != nil { + return driverVersion{}, fmt.Errorf("invalid format for minor version %q: %v", version, err) + } + res.patch, err = strconv.Atoi(parts[2]) + if err != nil { + return driverVersion{}, fmt.Errorf("invalid format for patch version %q: %v", version, err) + } + return res, nil +} + +func (v driverVersion) String() string { + return fmt.Sprintf("%02d.%02d.%02d", v.major, v.minor, v.patch) +} + +type frontendIoctlHandler func(fi *frontendIoctlState) (uintptr, error) +type controlCmdHandler func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS54Parameters) (uintptr, error) +type allocationClassHandler func(fi *frontendIoctlState, ioctlParams *nvgpu.NVOS64Parameters, isNVOS64 bool) (uintptr, error) +type uvmIoctlHandler func(ui *uvmIoctlState) (uintptr, error) + +// A driverABIFunc constructs and returns a driverABI. +// This indirection exists to avoid memory usage from unused driver ABIs. +type driverABIFunc func() *driverABI + +// driverABI defines the Nvidia kernel driver ABI proxied at a given version. +// +// The Nvidia driver's ioctl interface branches widely at various places in the +// kernel driver. As for now, versioning is only supported for the following +// points of branching: +// 1. frontend device ioctls (based on IOC_NR(cmd)). +// 2. uvm device ioctls (based on cmd). +// 3. control commands within NV_ESC_RM_CONTROL in frontend device (based on +// NVOS54_PARAMETERS.Cmd). Note that commands that have RM_GSS_LEGACY_MASK +// set are not versioned. +// 4. allocation classes within NV_ESC_RM_ALLOC in frontend device (based on +// NVOS64_PARAMETERS.HClass). +type driverABI struct { + frontendIoctl map[uint32]frontendIoctlHandler + uvmIoctl map[uint32]uvmIoctlHandler + controlCmd map[uint32]controlCmdHandler + allocationClass map[uint32]allocationClassHandler +} + +// abis is a global map containing all supported Nvidia driver ABIs. This is +// initialized on Init() and is immutable henceforth. +var abis map[driverVersion]driverABIFunc +var abisOnce sync.Once + +func addDriverABI(major, minor, patch int, cons driverABIFunc) driverABIFunc { + if abis == nil { + abis = make(map[driverVersion]driverABIFunc) + } + abis[driverVersion{major, minor, patch}] = cons + return cons +} + +// Init initializes abis global map. +func Init() { + abisOnce.Do(func() { + v525_60_13 := addDriverABI(525, 60, 13, func() *driverABI { + // 525.60.13 is the earliest driver version supported by nvproxy. Since + // there is no parent to inherit from, the driverABI needs to be constructed + // with the entirety of the nvproxy functionality at this version. + return &driverABI{ + frontendIoctl: map[uint32]frontendIoctlHandler{ + nvgpu.NV_ESC_CARD_INFO: frontendIoctlSimple, // nv_ioctl_card_info_t + nvgpu.NV_ESC_CHECK_VERSION_STR: frontendIoctlSimple, // nv_rm_api_version_t + nvgpu.NV_ESC_SYS_PARAMS: frontendIoctlSimple, // nv_ioctl_sys_params_t + nvgpu.NV_ESC_RM_DUP_OBJECT: frontendIoctlSimple, // NVOS55_PARAMETERS + nvgpu.NV_ESC_RM_SHARE: frontendIoctlSimple, // NVOS57_PARAMETERS + nvgpu.NV_ESC_RM_UNMAP_MEMORY: frontendIoctlSimple, // NVOS34_PARAMETERS + nvgpu.NV_ESC_RM_UPDATE_DEVICE_MAPPING_INFO: frontendIoctlSimple, // NVOS56_PARAMETERS + nvgpu.NV_ESC_REGISTER_FD: frontendRegisterFD, + nvgpu.NV_ESC_ALLOC_OS_EVENT: rmAllocOSEvent, + nvgpu.NV_ESC_FREE_OS_EVENT: rmFreeOSEvent, + nvgpu.NV_ESC_NUMA_INFO: rmNumaInfo, + nvgpu.NV_ESC_RM_ALLOC_MEMORY: rmAllocMemory, + nvgpu.NV_ESC_RM_FREE: rmFree, + nvgpu.NV_ESC_RM_CONTROL: rmControl, + nvgpu.NV_ESC_RM_ALLOC: rmAlloc, + nvgpu.NV_ESC_RM_VID_HEAP_CONTROL: rmVidHeapControl, + nvgpu.NV_ESC_RM_MAP_MEMORY: rmMapMemory, + }, + uvmIoctl: map[uint32]uvmIoctlHandler{ + nvgpu.UVM_INITIALIZE: uvmInitialize, + nvgpu.UVM_DEINITIALIZE: uvmIoctlNoParams, + nvgpu.UVM_CREATE_RANGE_GROUP: uvmIoctlSimple[nvgpu.UVM_CREATE_RANGE_GROUP_PARAMS], + nvgpu.UVM_DESTROY_RANGE_GROUP: uvmIoctlSimple[nvgpu.UVM_DESTROY_RANGE_GROUP_PARAMS], + nvgpu.UVM_REGISTER_GPU_VASPACE: uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_GPU_VASPACE_PARAMS], + nvgpu.UVM_UNREGISTER_GPU_VASPACE: uvmIoctlSimple[nvgpu.UVM_UNREGISTER_GPU_VASPACE_PARAMS], + nvgpu.UVM_REGISTER_CHANNEL: uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_CHANNEL_PARAMS], + nvgpu.UVM_UNREGISTER_CHANNEL: uvmIoctlSimple[nvgpu.UVM_UNREGISTER_CHANNEL_PARAMS], + nvgpu.UVM_MAP_EXTERNAL_ALLOCATION: uvmIoctlHasRMCtrlFD[nvgpu.UVM_MAP_EXTERNAL_ALLOCATION_PARAMS], + nvgpu.UVM_FREE: uvmIoctlSimple[nvgpu.UVM_FREE_PARAMS], + nvgpu.UVM_REGISTER_GPU: uvmIoctlHasRMCtrlFD[nvgpu.UVM_REGISTER_GPU_PARAMS], + nvgpu.UVM_UNREGISTER_GPU: uvmIoctlSimple[nvgpu.UVM_UNREGISTER_GPU_PARAMS], + nvgpu.UVM_PAGEABLE_MEM_ACCESS: uvmIoctlSimple[nvgpu.UVM_PAGEABLE_MEM_ACCESS_PARAMS], + nvgpu.UVM_MAP_DYNAMIC_PARALLELISM_REGION: uvmIoctlSimple[nvgpu.UVM_MAP_DYNAMIC_PARALLELISM_REGION_PARAMS], + nvgpu.UVM_ALLOC_SEMAPHORE_POOL: uvmIoctlSimple[nvgpu.UVM_ALLOC_SEMAPHORE_POOL_PARAMS], + nvgpu.UVM_VALIDATE_VA_RANGE: uvmIoctlSimple[nvgpu.UVM_VALIDATE_VA_RANGE_PARAMS], + nvgpu.UVM_CREATE_EXTERNAL_RANGE: uvmIoctlSimple[nvgpu.UVM_CREATE_EXTERNAL_RANGE_PARAMS], + }, + controlCmd: map[uint32]controlCmdHandler{ + nvgpu.NV0000_CTRL_CMD_CLIENT_GET_ADDR_SPACE_TYPE: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_CLIENT_SET_INHERITED_SHARE_POLICY: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_GET_ATTACHED_IDS: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_GET_ID_INFO: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_GET_ID_INFO_V2: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_GET_PROBED_IDS: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_ATTACH_IDS: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_DETACH_IDS: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_GET_PCI_INFO: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_QUERY_DRAIN_STATE: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_GPU_GET_MEMOP_ENABLE: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_SYNC_GPU_BOOST_GROUP_INFO: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_P2P_CAPS: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_FABRIC_STATUS: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_P2P_CAPS_MATRIX: rmControlSimple, + nvgpu.NV0080_CTRL_CMD_FB_GET_CAPS_V2: rmControlSimple, + nvgpu.NV0080_CTRL_CMD_GPU_GET_NUM_SUBDEVICES: rmControlSimple, + nvgpu.NV0080_CTRL_CMD_GPU_QUERY_SW_STATE_PERSISTENCE: rmControlSimple, + nvgpu.NV0080_CTRL_CMD_GPU_GET_VIRTUALIZATION_MODE: rmControlSimple, + 0x80028b: rmControlSimple, // unknown, paramsSize == 1 + nvgpu.NV0080_CTRL_CMD_GPU_GET_CLASSLIST_V2: rmControlSimple, + nvgpu.NV0080_CTRL_CMD_HOST_GET_CAPS_V2: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_BUS_GET_PCI_INFO: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_BUS_GET_PCI_BAR_INFO: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_BUS_GET_INFO_V2: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_BUS_GET_PCIE_SUPPORTED_GPU_ATOMICS: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_CE_GET_ALL_CAPS: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_FB_GET_INFO_V2: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_INFO_V2: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_NAME_STRING: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_SHORT_NAME_STRING: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_SIMULATION_INFO: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_QUERY_ECC_STATUS: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_QUERY_COMPUTE_MODE_RULES: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_ACQUIRE_COMPUTE_MODE_RESERVATION: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_RELEASE_COMPUTE_MODE_RESERVATION: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_GID_INFO: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_ENGINES_V2: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_ACTIVE_PARTITION_IDS: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GPU_GET_COMPUTE_POLICY_CONFIG: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GET_GPU_FABRIC_PROBE_INFO: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GR_SET_CTXSW_PREEMPTION_MODE: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GR_GET_CTX_BUFFER_SIZE: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GR_GET_GLOBAL_SM_ORDER: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GR_GET_CAPS_V2: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GR_GET_GPC_MASK: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GR_GET_TPC_MASK: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_GSP_GET_FEATURES: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_MC_GET_ARCH_INFO: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_MC_SERVICE_INTERRUPTS: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_NVLINK_GET_NVLINK_STATUS: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_PERF_BOOST: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_RC_GET_WATCHDOG_INFO: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_RC_RELEASE_WATCHDOG_REQUESTS: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_RC_SOFT_DISABLE_WATCHDOG: rmControlSimple, + nvgpu.NV2080_CTRL_CMD_TIMER_GET_GPU_CPU_TIME_CORRELATION_INFO: rmControlSimple, + nvgpu.NV503C_CTRL_CMD_REGISTER_VA_SPACE: rmControlSimple, + nvgpu.NV503C_CTRL_CMD_REGISTER_VIDMEM: rmControlSimple, + nvgpu.NV503C_CTRL_CMD_UNREGISTER_VIDMEM: rmControlSimple, + nvgpu.NV83DE_CTRL_CMD_DEBUG_SET_EXCEPTION_MASK: rmControlSimple, + nvgpu.NV83DE_CTRL_CMD_DEBUG_READ_ALL_SM_ERROR_STATES: rmControlSimple, + nvgpu.NV83DE_CTRL_CMD_DEBUG_CLEAR_ALL_SM_ERROR_STATES: rmControlSimple, + nvgpu.NV906F_CTRL_CMD_RESET_CHANNEL: rmControlSimple, + nvgpu.NV90E6_CTRL_CMD_MASTER_GET_VIRTUAL_FUNCTION_ERROR_CONT_INTR_MASK: rmControlSimple, + nvgpu.NVC36F_CTRL_GET_CLASS_ENGINEID: rmControlSimple, + nvgpu.NVC36F_CTRL_CMD_GPFIFO_GET_WORK_SUBMIT_TOKEN: rmControlSimple, + nvgpu.NVA06C_CTRL_CMD_GPFIFO_SCHEDULE: rmControlSimple, + nvgpu.NVA06C_CTRL_CMD_SET_TIMESLICE: rmControlSimple, + nvgpu.NVA06C_CTRL_CMD_PREEMPT: rmControlSimple, + nvgpu.NV0000_CTRL_CMD_SYSTEM_GET_BUILD_VERSION: ctrlClientSystemGetBuildVersion, + nvgpu.NV0080_CTRL_CMD_FIFO_GET_CHANNELLIST: ctrlDevFIFOGetChannelList, + nvgpu.NV2080_CTRL_CMD_FIFO_DISABLE_CHANNELS: ctrlSubdevFIFODisableChannels, + nvgpu.NV2080_CTRL_CMD_GR_GET_INFO: ctrlSubdevGRGetInfo, + }, + allocationClass: map[uint32]allocationClassHandler{ + nvgpu.NV01_ROOT: rmAllocSimple[nvgpu.Handle], + nvgpu.NV01_ROOT_NON_PRIV: rmAllocSimple[nvgpu.Handle], + nvgpu.NV01_ROOT_CLIENT: rmAllocSimple[nvgpu.Handle], + nvgpu.NV01_EVENT_OS_EVENT: rmAllocEventOSEvent, + nvgpu.NV01_DEVICE_0: rmAllocSimple[nvgpu.NV0080_ALLOC_PARAMETERS], + nvgpu.NV20_SUBDEVICE_0: rmAllocSimple[nvgpu.NV2080_ALLOC_PARAMETERS], + nvgpu.NV50_THIRD_PARTY_P2P: rmAllocSimple[nvgpu.NV503C_ALLOC_PARAMETERS], + nvgpu.GT200_DEBUGGER: rmAllocSimple[nvgpu.NV83DE_ALLOC_PARAMETERS], + nvgpu.FERMI_CONTEXT_SHARE_A: rmAllocSimple[nvgpu.NV_CTXSHARE_ALLOCATION_PARAMETERS], + nvgpu.FERMI_VASPACE_A: rmAllocSimple[nvgpu.NV_VASPACE_ALLOCATION_PARAMETERS], + nvgpu.KEPLER_CHANNEL_GROUP_A: rmAllocSimple[nvgpu.NV_CHANNEL_GROUP_ALLOCATION_PARAMETERS], + nvgpu.TURING_CHANNEL_GPFIFO_A: rmAllocSimple[nvgpu.NV_CHANNEL_ALLOC_PARAMS], + nvgpu.AMPERE_CHANNEL_GPFIFO_A: rmAllocSimple[nvgpu.NV_CHANNEL_ALLOC_PARAMS], + nvgpu.TURING_DMA_COPY_A: rmAllocSimple[nvgpu.NVB0B5_ALLOCATION_PARAMETERS], + nvgpu.AMPERE_DMA_COPY_A: rmAllocSimple[nvgpu.NVB0B5_ALLOCATION_PARAMETERS], + nvgpu.AMPERE_DMA_COPY_B: rmAllocSimple[nvgpu.NVB0B5_ALLOCATION_PARAMETERS], + nvgpu.HOPPER_DMA_COPY_A: rmAllocSimple[nvgpu.NVB0B5_ALLOCATION_PARAMETERS], + nvgpu.TURING_COMPUTE_A: rmAllocSimple[nvgpu.NV_GR_ALLOCATION_PARAMETERS], + nvgpu.AMPERE_COMPUTE_A: rmAllocSimple[nvgpu.NV_GR_ALLOCATION_PARAMETERS], + nvgpu.AMPERE_COMPUTE_B: rmAllocSimple[nvgpu.NV_GR_ALLOCATION_PARAMETERS], + nvgpu.ADA_COMPUTE_A: rmAllocSimple[nvgpu.NV_GR_ALLOCATION_PARAMETERS], + nvgpu.HOPPER_COMPUTE_A: rmAllocSimple[nvgpu.NV_GR_ALLOCATION_PARAMETERS], + nvgpu.HOPPER_USERMODE_A: rmAllocSimple[nvgpu.NV_HOPPER_USERMODE_A_PARAMS], + nvgpu.GF100_SUBDEVICE_MASTER: rmAllocNoParams, + nvgpu.TURING_USERMODE_A: rmAllocNoParams, + nvgpu.NV_MEMORY_FABRIC: rmAllocSimple[nvgpu.NV00F8_ALLOCATION_PARAMETERS], + }, + } + }) + + v525_105_17 := addDriverABI(525, 105, 17, v525_60_13) + + _ = addDriverABI(525, 125, 06, v525_105_17) + }) +} diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index 200a3369e..51d3e4374 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -38,6 +38,7 @@ import ( "gvisor.dev/gvisor/pkg/rand" "gvisor.dev/gvisor/pkg/refs" "gvisor.dev/gvisor/pkg/sentry/control" + "gvisor.dev/gvisor/pkg/sentry/devices/nvproxy" "gvisor.dev/gvisor/pkg/sentry/fdimport" "gvisor.dev/gvisor/pkg/sentry/fsimpl/host" "gvisor.dev/gvisor/pkg/sentry/fsimpl/tmpfs" @@ -304,6 +305,10 @@ func New(args Args) (*Loader, error) { return nil, fmt.Errorf("setting up memory usage: %w", err) } + if args.Conf.NVProxy { + nvproxy.Init() + } + kernel.IOUringEnabled = args.Conf.IOUring info := containerInfo{