Files
Ayush RanjanandgVisor bot f70c2f5dc8 nvproxy: Add support for registering and allowing unsupported drivers
Introduce a mechanism to register unqualified NVIDIA driver
versions as "unsupported" in nvproxy. These versions are not officially
supported or continuously tested.

Added a new runsc flag `--nvproxy-allow-unsupported-driver` to allow
running with these unsupported drivers (which will log a warning), while
still failing by default. Completely unknown drivers will continue to always
fail.

PiperOrigin-RevId: 931375818
2026-06-12 15:55:03 -07:00

303 lines
10 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 implements proxying for the Nvidia GPU Linux kernel driver:
// https://github.com/NVIDIA/open-gpu-kernel-modules.
//
// Supported Nvidia GPUs: T4, L4, A100, A10G and H100.
//
// Lock ordering:
//
// - nvproxy.fdsMu
// - rootClient.objsMu
// - nvproxy.clientsMu
package nvproxy
import (
"fmt"
"path/filepath"
"strings"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/nvgpu"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/devutil"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/marshal"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy/nvconf"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
)
// Options holds arguments to Register.
type Options struct {
// DriverVersion is the Nvidia GPU driver version.
DriverVersion nvconf.DriverVersion
// AllowUnsupportedDriver allows nvproxy to be initialized with an
// unsupported driver version.
AllowUnsupportedDriver bool
// DriverCaps is the set of driver capabilities exposed to applications.
DriverCaps nvconf.DriverCaps
HostSettings *nvconf.HostSettings
// If UseDevGofer is true, open device files via gofer.
UseDevGofer bool
}
// Register registers all devices implemented by this package, and specified by
// opts, in vfsObj. If it succeeds, it returns information about registered
// devices; the returned DeviceInfo must not be mutated.
func Register(vfsObj *vfs.VirtualFilesystem, opts *Options) (*DeviceInfo, error) {
// The kernel driver's interface is unstable, so only allow versions of the
// driver that are known to be supported.
log.Infof("NVIDIA driver version: %s", opts.DriverVersion)
abiEntry, ok := abis[opts.DriverVersion]
if !ok {
return nil, fmt.Errorf("unsupported Nvidia driver version: %s", opts.DriverVersion)
}
if !abiEntry.supported {
if !opts.AllowUnsupportedDriver {
return nil, fmt.Errorf("unsupported Nvidia driver version: %s", opts.DriverVersion)
}
log.Warningf("NVIDIA driver version %s is not officially supported", opts.DriverVersion)
}
if opts.DriverCaps == 0 {
log.Warningf("nvproxy: NVIDIA driver capability set is empty; all GPU operations will fail")
}
nvp := &nvproxy{
abi: abiEntry.cons(),
version: opts.DriverVersion,
capsEnabled: opts.DriverCaps,
useDevGofer: opts.UseDevGofer,
procDriverNvidiaParams: opts.HostSettings.ProcDriverNvidiaParams,
frontendFDs: make(map[*frontendFD]struct{}),
clients: make(map[nvgpu.Handle]*rootClient),
}
// Force ModifyDeviceFiles in /proc/driver/nvidia/params to 0. This is
// consistent with libnvidia-container's src/nvc_mount.c:mount_procfs().
nvp.procDriverNvidiaParams = strings.Replace(nvp.procDriverNvidiaParams, "ModifyDeviceFiles: 1", "ModifyDeviceFiles: 0", 1)
for minor := uint32(0); minor <= nvgpu.NV_MINOR_DEVICE_NUMBER_REGULAR_MAX; minor++ {
dev := &frontendDevice{
nvp: nvp,
minor: minor,
}
nvp.regularDevs[minor] = dev
if err := vfsObj.RegisterDevice(vfs.CharDevice, nvgpu.NV_MAJOR_DEVICE_NUMBER, minor, dev, &vfs.RegisterDeviceOptions{
GroupName: "nvidia",
}); err != nil {
return nil, err
}
}
if err := vfsObj.RegisterDevice(vfs.CharDevice, nvgpu.NV_MAJOR_DEVICE_NUMBER, nvgpu.NV_MINOR_DEVICE_NUMBER_CONTROL_DEVICE, &frontendDevice{
nvp: nvp,
minor: nvgpu.NV_MINOR_DEVICE_NUMBER_CONTROL_DEVICE,
}, &vfs.RegisterDeviceOptions{
GroupName: "nvidiactl",
}); err != nil {
return nil, err
}
uvmDevMajor, err := vfsObj.GetDynamicCharDevMajor()
if err != nil {
return nil, fmt.Errorf("allocating device major number for nvidia-uvm: %w", err)
}
nvp.devInfo.UVMDevMajor = uvmDevMajor
if err := vfsObj.RegisterDevice(vfs.CharDevice, uvmDevMajor, nvgpu.NVIDIA_UVM_PRIMARY_MINOR_NUMBER, &uvmDevice{
nvp: nvp,
}, &vfs.RegisterDeviceOptions{
GroupName: "nvidia-uvm",
}); err != nil {
return nil, err
}
if opts.DriverCaps&nvconf.CapFabricIMEXManagement != 0 {
if !opts.HostSettings.HaveFabricIMEXManagement {
return nil, fmt.Errorf("driver capability %s is enabled, but fabric-imex-mgmt device minor number is unavailable", nvconf.CapFabricIMEXManagement)
}
capsDevMajor, err := vfsObj.GetDynamicCharDevMajor()
if err != nil {
return nil, fmt.Errorf("allocating device major number for nvidia-caps: %w", err)
}
nvp.devInfo.CapsDevMajor = capsDevMajor
if err := vfsObj.RegisterDevice(vfs.CharDevice, capsDevMajor, opts.HostSettings.FabricIMEXManagementDevMinor, &openOnlyDevice{
nvp: nvp,
relpath: fmt.Sprintf("nvidia-caps/nvidia-cap%d", opts.HostSettings.FabricIMEXManagementDevMinor),
}, &vfs.RegisterDeviceOptions{
GroupName: "nvidia-caps",
}); err != nil {
return nil, err
}
nvp.devInfo.HaveFabricIMEXManagement = true
nvp.devInfo.FabricIMEXManagementDevMinor = opts.HostSettings.FabricIMEXManagementDevMinor
}
if imexChannelCount := opts.HostSettings.IMEXChannelCount(); imexChannelCount != 0 {
capsIMEXChannelsDevMajor, err := vfsObj.GetDynamicCharDevMajor()
if err != nil {
return nil, fmt.Errorf("allocating device major number for nvidia-caps-imex-channels: %w", err)
}
nvp.devInfo.CapsIMEXChannelsDevMajor = capsIMEXChannelsDevMajor
for minor := range imexChannelCount {
if err := vfsObj.RegisterDevice(vfs.CharDevice, capsIMEXChannelsDevMajor, minor, &openOnlyDevice{
nvp: nvp,
relpath: fmt.Sprintf("nvidia-caps-imex-channels/channel%d", minor),
}, &vfs.RegisterDeviceOptions{
GroupName: "nvidia-caps-imex-channels",
}); err != nil {
return nil, err
}
}
}
return &nvp.devInfo, nil
}
// DeviceInfo contains information on registered nvproxy devices.
//
// +stateify savable
type DeviceInfo struct {
// CapsDevMajor is nvidia-caps' device major number. If CapsDevMajor is 0,
// nvidia-caps is not enabled.
CapsDevMajor uint32
// If HaveFabricIMEXManagement is true, FabricIMEXManagementDevMinor is the
// fabric-imex-mgmt capability's device minor number, which matches the
// value on the host. (Its device major number is CapsDevMajor, which must
// be non-zero and might not match the host's value.)
HaveFabricIMEXManagement bool
FabricIMEXManagementDevMinor uint32
// CapsIMEXChannelsDevMajor is nvidia-caps-imex-channels's device major
// number. If CapsIMEXChannelsDevMajor is 0, nvidia-caps-imex-channels is
// not enabled.
CapsIMEXChannelsDevMajor uint32
// UVMDevMajor is nvidia-uvm's device major number. If UVMDevMajor is 0,
// nvidia-uvm is enabled.
UVMDevMajor uint32
}
// DeviceInfoFromVFS returns device information for nvproxy devices registered
// in vfsObj. The returned DeviceInfo must not be mutated. If DeviceInfoFromVFS
// returns nil, nvproxy.Register(vfsObj) has not been called.
func DeviceInfoFromVFS(vfsObj *vfs.VirtualFilesystem) *DeviceInfo {
if nvp := nvproxyFromVFS(vfsObj); nvp != nil {
return &nvp.devInfo
}
return nil
}
// +stateify savable
type nvproxy struct {
abi *driverABI `state:"nosave"`
version nvconf.DriverVersion
capsEnabled nvconf.DriverCaps
useDevGofer bool
procDriverNvidiaParams string
devInfo DeviceInfo
regularDevs [nvgpu.NV_MINOR_DEVICE_NUMBER_REGULAR_MAX + 1]*frontendDevice
fdsMu fdsMutex `state:"nosave"`
frontendFDs map[*frontendFD]struct{}
clientsMu sync.RWMutex `state:"nosave"`
clients map[nvgpu.Handle]*rootClient
}
func nvproxyFromVFS(vfsObj *vfs.VirtualFilesystem) *nvproxy {
ctlDeviceAny := vfsObj.GetRegisteredDevice(vfs.CharDevice, nvgpu.NV_MAJOR_DEVICE_NUMBER, nvgpu.NV_MINOR_DEVICE_NUMBER_CONTROL_DEVICE)
if ctlDeviceAny == nil {
// nvproxy.Register() was never called.
return nil
}
ctlDevice, ok := ctlDeviceAny.(*frontendDevice)
if !ok {
// Something else took nvidiactl's device number, which is surprising
// but also implies that nvproxy.Register() was never called.
return nil
}
return ctlDevice.nvp
}
type marshalPtr[T any] interface {
*T
marshal.Marshallable
}
func addrFromP64(p nvgpu.P64) hostarch.Addr {
return hostarch.Addr(uintptr(uint64(p)))
}
type hasFrontendFDPtr[T any] interface {
marshalPtr[T]
nvgpu.HasFrontendFD
}
type hasStatusPtr[T any] interface {
marshalPtr[T]
nvgpu.HasStatus
}
type hasFrontendFDAndStatusPtr[T any] interface {
marshalPtr[T]
nvgpu.HasFrontendFD
nvgpu.HasStatus
}
type hasCtrlInfoListPtr[T any] interface {
marshalPtr[T]
nvgpu.HasCtrlInfoList
}
type hasPOsEventPtr[T any] interface {
marshalPtr[T]
nvgpu.HasPOsEvent
}
// NvidiaDeviceFD is an interface that should be implemented by all
// vfs.FileDescriptionImpl of Nvidia devices.
type NvidiaDeviceFD interface {
IsNvidiaDeviceFD()
}
func openHostDevFile(ctx context.Context, relpath string, useDevGofer bool, openFlags uint32) (int32, string, error) {
if useDevGofer {
devClient := devutil.GoferClientFromContext(ctx)
if devClient == nil {
ctx.Warningf("nvproxy: failed to open device gofer %s: devutil.CtxDevGoferClient is not set", relpath)
return -1, "", linuxerr.ENOENT
}
containerName := devClient.ContainerName()
hostFD, err := devClient.OpenAt(ctx, relpath, openFlags)
if err != nil {
ctx.Warningf("nvproxy: failed to open device gofer %s: %v", relpath, err)
return -1, "", err
}
return int32(hostFD), containerName, nil
}
abspath := filepath.Join("/dev", relpath)
hostFD, err := unix.Openat(-1, abspath, int(openFlags&unix.O_ACCMODE|unix.O_NOFOLLOW), 0)
if err != nil {
ctx.Warningf("nvproxy: failed to open host %s: %v", abspath, err)
return -1, "", err
}
return int32(hostFD), "", nil
}