github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/sentry/devices/nvproxy/nvproxy_unsafe.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  	"bytes"
    19  	"fmt"
    20  	"unsafe"
    21  
    22  	"github.com/ttpreport/gvisor-ligolo/pkg/abi/nvgpu"
    23  	"golang.org/x/sys/unix"
    24  )
    25  
    26  func hostDriverVersion() (string, error) {
    27  	ctlFD, err := unix.Openat(-1, "/dev/nvidiactl", unix.O_RDONLY|unix.O_NOFOLLOW, 0)
    28  	if err != nil {
    29  		return "", fmt.Errorf("failed to open /dev/nvidiactl: %w", err)
    30  	}
    31  	defer unix.Close(ctlFD)
    32  
    33  	// From src/nvidia/arch/nvalloc/unix/include/nv-ioctl.h:
    34  	const NV_RM_API_VERSION_REPLY_RECOGNIZED = 1
    35  
    36  	// 530.30.02 and later versions of the host driver `#define
    37  	// NV_RM_API_VERSION_CMD_QUERY '2'`, which causes this ioctl to return the
    38  	// driver version without performing a check. Earlier versions of the
    39  	// driver `#define NV_RM_API_VERSION_CMD_OVERRIDE '2'`, which causes the
    40  	// ioctl to no-op. Try with Cmd '2' first, hoping that the driver
    41  	// interprets it as _QUERY; if the returned string is empty, then it was
    42  	// interpreted as _OVERRIDE and we need to perform an actual check (Cmd 0),
    43  	// which has the downside of logging an error message.
    44  	ioctlParams := nvgpu.RMAPIVersion{
    45  		Cmd: '2',
    46  	}
    47  	if _, _, errno := unix.RawSyscall(unix.SYS_IOCTL, uintptr(ctlFD), frontendIoctlCmd(nvgpu.NV_ESC_CHECK_VERSION_STR, uint32(unsafe.Sizeof(ioctlParams))), uintptr(unsafe.Pointer(&ioctlParams))); errno != 0 {
    48  		return "", fmt.Errorf("NV_ESC_CHECK_VERSION_STR ioctl error: %w", errno)
    49  	}
    50  	if ioctlParams.Reply != NV_RM_API_VERSION_REPLY_RECOGNIZED {
    51  		return "", fmt.Errorf("unknown NV_ESC_CHECK_VERSION_STR reply: %d", ioctlParams.Reply)
    52  	}
    53  	if ioctlParams.VersionString[0] == '\x00' {
    54  		ioctlParams.Cmd = 0
    55  		ioctlParams.Reply = 0
    56  		// We expect the check to fail on our empty version string, so tolerate
    57  		// EINVAL.
    58  		if _, _, errno := unix.RawSyscall(unix.SYS_IOCTL, uintptr(ctlFD), frontendIoctlCmd(nvgpu.NV_ESC_CHECK_VERSION_STR, uint32(unsafe.Sizeof(ioctlParams))), uintptr(unsafe.Pointer(&ioctlParams))); errno != 0 && errno != unix.EINVAL {
    59  			return "", fmt.Errorf("fallback NV_ESC_CHECK_VERSION_STR ioctl error: %w", errno)
    60  		}
    61  		if ioctlParams.Reply != NV_RM_API_VERSION_REPLY_RECOGNIZED {
    62  			return "", fmt.Errorf("unknown fallback NV_ESC_CHECK_VERSION_STR reply: %d", ioctlParams.Reply)
    63  		}
    64  	}
    65  
    66  	if i := bytes.IndexByte(ioctlParams.VersionString[:], '\x00'); i >= 0 {
    67  		return string(ioctlParams.VersionString[:i]), nil
    68  	}
    69  	return string(ioctlParams.VersionString[:]), nil
    70  }
    71  
    72  func p64FromPtr(ptr unsafe.Pointer) nvgpu.P64 {
    73  	return nvgpu.P64(uint64(uintptr(ptr)))
    74  }