Files
visor/tools/gpu/drivers/install_driver.go
Etienne PerotandgVisor bot d70b32dc57 Add driver ABI version registration and support for MMNCCD ioctls
- `NV2080_CTRL_CMD_GPU_GET_GID_INFO`
- `NV2080_CTRL_CMD_GET_GPU_FABRIC_PROBE_INFO`
- `NV2080_CTRL_CMD_NVLINK_GET_PLATFORM_INFO`
- `NV2080_CTRL_CMD_NVLINK_LOCK_REMAP_TABLE_AND_MSE`
- `NV2080_CTRL_CMD_NVLINK_SETUP_NVLE_ENCRYPTION_KEY`
- `NV2080_CTRL_CMD_NVLINK_GET_REMAP_TABLE_INFO_V2`
- `NV2080_CTRL_CMD_NVLINK_GET_UPDATE_NVLE_LIDS_V2`

PiperOrigin-RevId: 956089141
2026-07-29 14:28:53 -07:00

424 lines
13 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 drivers contains methods to download and install drivers.
package drivers
import (
"context"
"crypto/sha256"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"sort"
"strings"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy/nvconf"
)
const (
nvidiaSMIPath = "/usr/bin/nvidia-smi"
nvidiaUninstallPath = "/usr/bin/nvidia-uninstall"
nvidiaBaseURLX86_64 = "https://us.download.nvidia.com/tesla/%s/NVIDIA-Linux-x86_64-%s.run"
nvidiaARM64BaseURL = "https://us.download.nvidia.com/XFree86/aarch64/%s/NVIDIA-Linux-aarch64-%s.run"
archAMD64 = "amd64"
archARM64 = "arm64"
)
func init() {
nvproxy.Init()
}
func getNvidiaBaseURL(driverVersion, arch string) string {
switch arch {
case archARM64:
return fmt.Sprintf(nvidiaARM64BaseURL, driverVersion, driverVersion)
case archAMD64:
return fmt.Sprintf(nvidiaBaseURLX86_64, driverVersion, driverVersion)
}
panic(fmt.Sprintf("unsupported arch: %q", arch))
}
// Installer handles the logic to install drivers.
type Installer struct {
requestedVersion nvconf.DriverVersion
// include functions so they can be mocked in tests.
expectedChecksumFunc func(nvconf.DriverVersion) (nvproxy.Checksums, bool)
getCurrentDriverFunc func() (nvconf.DriverVersion, error)
downloadFunc func(context.Context, string, string) (io.ReadCloser, error)
installFunc func(string) error
}
func latestInstallableDriver() (nvconf.DriverVersion, error) {
var latest nvconf.DriverVersion
var found bool
nvproxy.ForEachSupportDriver(func(version nvconf.DriverVersion, checksums nvproxy.Checksums) {
checksum, err := checksums.Checksum()
if err != nil {
return
}
if checksum != nvproxy.ChecksumNoDriver && checksum != "" {
if version.IsGreaterThan(latest) {
latest = version
found = true
}
}
})
if !found {
return nvconf.DriverVersion{}, fmt.Errorf("no installable driver found for arch %q", runtime.GOARCH)
}
return latest, nil
}
// NewInstaller returns a driver installer instance.
func NewInstaller(requestedVersion string, latest bool) (*Installer, error) {
ret := &Installer{
expectedChecksumFunc: nvproxy.ExpectedDriverChecksum,
getCurrentDriverFunc: getCurrentDriver,
downloadFunc: DownloadDriver,
installFunc: installDriver,
}
switch {
case latest:
version, err := latestInstallableDriver()
if err != nil {
return nil, err
}
ret.requestedVersion = version
default:
d, err := nvconf.DriverVersionFrom(requestedVersion)
if err != nil {
return nil, fmt.Errorf("failed to parse requested driver version: %w", err)
}
ret.requestedVersion = d
}
return ret, nil
}
// MaybeInstall installs a driver if 1) no driver is present on the system already or 2) the
// driver currently installed does not match the requested version.
func (i *Installer) MaybeInstall(ctx context.Context) error {
// If we don't support the driver, don't attempt to install it.
if _, ok := i.expectedChecksumFunc(i.requestedVersion); !ok {
return fmt.Errorf("requested driver %q is not supported", i.requestedVersion)
}
existingDriver, err := i.getCurrentDriverFunc()
if err != nil {
log.Warningf("failed to get current driver: %v", err)
}
if existingDriver.Equals(i.requestedVersion) {
log.Infof("Driver already installed: %s", i.requestedVersion)
return nil
}
if !existingDriver.Equals(nvconf.DriverVersion{}) {
log.Infof("Uninstalling driver: %s", existingDriver)
if err := i.uninstallDriver(ctx, existingDriver.String()); err != nil {
return fmt.Errorf("failed to uninstall driver: %w", err)
}
log.Infof("Driver uninstalled: %s", i.requestedVersion)
}
log.Infof("Downloading driver: %s", i.requestedVersion)
reader, err := i.downloadFunc(ctx, i.requestedVersion.String(), runtime.GOARCH)
if err != nil {
return fmt.Errorf("failed to download driver: %w", err)
}
f, err := os.CreateTemp("", "")
if err != nil {
return fmt.Errorf("failed to open driver file: %w", err)
}
defer os.Remove(f.Name())
if err := i.writeAndCheck(f, reader); err != nil {
f.Close()
return fmt.Errorf("writeAndCheck: %w", err)
}
if err := f.Chmod(0755); err != nil {
return fmt.Errorf("failed to chmod: %w", err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("failed to close driver file: %w", err)
}
log.Infof("Driver downloaded: %s", i.requestedVersion)
log.Infof("Installing driver: %s", i.requestedVersion)
if err := i.installFunc(f.Name()); err != nil {
return fmt.Errorf("failed to install driver: %w", err)
}
log.Infof("Installation Complete!")
return nil
}
func (i *Installer) uninstallDriver(ctx context.Context, driverVersion string) error {
exec.Command(nvidiaUninstallPath, "-s", driverVersion)
cmd := exec.Command(nvidiaUninstallPath, "-s")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run nvidia-uninstall: %w", err)
}
return nil
}
func (i *Installer) writeAndCheck(f *os.File, reader io.ReadCloser) error {
checksum := sha256.New()
buf := make([]byte, 1024*1024)
for {
n, err := reader.Read(buf[0:])
if err != nil && err != io.EOF {
return fmt.Errorf("failed to read: %w", err)
}
if n == 0 || err == io.EOF {
break
}
if _, err := checksum.Write(buf[:n]); err != nil {
return fmt.Errorf("failed to write: %w", err)
}
if _, err := f.Write(buf[:n]); err != nil {
return fmt.Errorf("failed to write: %w", err)
}
}
gotChecksum := fmt.Sprintf("%x", checksum.Sum(nil))
c, ok := i.expectedChecksumFunc(i.requestedVersion)
if !ok {
return fmt.Errorf("requested driver %q is not supported", i.requestedVersion)
}
wantChecksum, err := c.Checksum()
if err != nil {
return fmt.Errorf("failed to get checksum for driver %q: %v", i.requestedVersion, err)
}
if gotChecksum != wantChecksum {
return fmt.Errorf("driver %q checksum mismatch: got %q, want %q", i.requestedVersion, gotChecksum, wantChecksum)
}
return nil
}
func getCurrentDriver() (nvconf.DriverVersion, error) {
_, err := os.Stat(nvidiaSMIPath)
// If the nvidia-smi executable does not exist, then we don't have a driver installed.
if os.IsNotExist(err) {
return nvconf.DriverVersion{}, fmt.Errorf("nvidia-smi does not exist at path: %q", nvidiaSMIPath)
}
if err != nil {
return nvconf.DriverVersion{}, fmt.Errorf("failed to stat nvidia-smi: %w", err)
}
out, err := exec.Command(nvidiaSMIPath, []string{"--query-gpu", "driver_version", "--format=csv,noheader"}...).CombinedOutput()
if err != nil {
log.Warningf("failed to run nvidia-smi: %v", err)
return nvconf.DriverVersion{}, fmt.Errorf("failed to run nvidia-smi: %w", err)
}
// If there are multiple GPUs, there will be one version per line.
// Make sure they are all the same version.
sameVersion := ""
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if sameVersion == "" {
sameVersion = line
continue
}
if line != sameVersion {
return nvconf.DriverVersion{}, fmt.Errorf("multiple driver versions found: %q and %q", sameVersion, line)
}
}
if sameVersion == "" {
return nvconf.DriverVersion{}, fmt.Errorf("no driver version found")
}
return nvconf.DriverVersionFrom(sameVersion)
}
// ListSupportedDrivers prints the driver to stderr in a format that can be
// consumed by the Makefile to iterate tests across drivers.
func ListSupportedDrivers(outfile string) error {
out := os.Stdout
if outfile != "" {
f, err := os.OpenFile(outfile, os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("failed to open outfile: %w", err)
}
defer f.Close()
out = f
}
var list []string
nvproxy.ForEachSupportDriver(func(version nvconf.DriverVersion, checksums nvproxy.Checksums) {
checksum, err := checksums.Checksum()
if err != nil || checksum == nvproxy.ChecksumNoDriver || checksum == "" {
return
}
list = append(list, version.String())
})
sort.Strings(list)
if _, err := out.WriteString(strings.Join(list, " ") + "\n"); err != nil {
return fmt.Errorf("failed to write to outfile: %w", err)
}
return nil
}
// ChecksumDriver downloads and returns the SHA265 checksum of the driver.
func (i *Installer) ChecksumDriver(ctx context.Context, arch string) (string, error) {
f, err := DownloadDriver(ctx, i.requestedVersion.String(), arch)
if err != nil {
return "", fmt.Errorf("failed to download driver: %w", err)
}
checksum := sha256.New()
for {
n, err := io.Copy(checksum, f)
if err == io.EOF || n == 0 {
break
}
if err != nil {
return "", fmt.Errorf("failed to copy driver: %w", err)
}
}
return fmt.Sprintf("%x", checksum.Sum(nil)), nil
}
// DownloadDriver downloads the requested driver and returns the binary as a []byte so it can be
// checked before written to disk.
func DownloadDriver(ctx context.Context, driverVersion, arch string) (io.ReadCloser, error) {
url := getNvidiaBaseURL(driverVersion, arch)
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to download driver: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to download driver with statusCode: %d: status: %s", resp.StatusCode, resp.Status)
}
return resp.Body, nil
}
func installDriver(driverPath string) error {
// Certain VMs can be broken if we attempt to install drivers on them. Do a simple check of the
// PCI device to ensure we have a GPU attached.
out, err := exec.Command("lspci").CombinedOutput()
if err != nil {
return fmt.Errorf("failed to run lspci: %w out: %s", err, string(out))
}
if !strings.Contains(string(out), "NVIDIA") {
return fmt.Errorf("no NVIDIA PCI device on host:\n%s", string(out))
}
driverArgs := strings.Split("--dkms -a -s --no-drm --install-libglvnd -m=kernel-open", " ")
cmd := exec.Command(driverPath, driverArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if osIsUbuntu2204() {
// As of this writing (2024), current Ubuntu 22.04 kernels are built
// with gcc-12, but Ubuntu 22.04 defaults to gcc-11, so unless we force
// the former, building the kernel driver will fail with
// `cc: error: unrecognized command-line option '-ftrivial-auto-var-init=zero'`.
cmd.Env = append(cmd.Environ(), "CC=/usr/bin/gcc-12")
}
if err := cmd.Run(); err != nil {
tryToPrintFailureLogs()
return fmt.Errorf("failed to run nvidia-install: %w out: %s", err, string(out))
}
cmd = exec.Command(nvidiaSMIPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run nvidia-smi post install: %w out: %s", err, string(out))
}
return nil
}
func tryToPrintFailureLogs() {
// nvidia driver installers print failure logs to this path.
const logPath = "/var/log/nvidia-installer.log"
f, err := os.OpenFile(logPath, os.O_RDONLY, 0644)
if err != nil {
log.Warningf("failed to stat nvidia-installer.log: %v", err)
return
}
defer f.Close()
out, err := io.ReadAll(f)
if err != nil {
log.Warningf("failed to read nvidia-installer.log: %v", err)
return
}
for _, line := range strings.Split(string(out), "\n") {
fmt.Printf("[nvidia-installer]: %s\n", line)
}
}
// ValidateChecksum validates the checksum of the driver.
func ValidateChecksum(ctx context.Context, version string, checksums nvproxy.Checksums) error {
for _, arch := range []string{archAMD64, archARM64} {
wantChecksum := checksums.X86_64
if arch == archARM64 {
wantChecksum = checksums.ARM64
}
installer, err := NewInstaller(version, false)
if err != nil {
return fmt.Errorf("failed to create installer for driver %q on arch %q: %v", version, arch, err)
}
gotChecksum, err := installer.ChecksumDriver(ctx, arch)
if wantChecksum == nvproxy.ChecksumNoDriver {
log.Infof("Runfile does not exist for driver %q arch: %q", version, arch)
if err == nil || !strings.Contains(err.Error(), "failed to download driver with statusCode: 404") {
return fmt.Errorf("checksum mismatch for driver %q on arch %q: got %q, want %q", version, arch, gotChecksum, wantChecksum)
}
return nil
}
log.Infof("Checksum for driver %q arch: %q: %q", version, arch, gotChecksum)
if err != nil {
return fmt.Errorf("failed to get checksum for driver %q on arch %q: %v", version, arch, err)
}
if gotChecksum != wantChecksum {
return fmt.Errorf("checksum mismatch for driver %q on arch %q: got %q, want %q", version, arch, gotChecksum, wantChecksum)
}
}
return nil
}
func osIsUbuntu2204() bool {
m, err := getOSRelease()
if err != nil {
log.Warningf("Failed to determine Linux distribution: %v", err)
return false
}
return m["ID"] == "ubuntu" && m["VERSION_ID"] == "22.04"
}
func getOSRelease() (map[string]string, error) {
const path = "/etc/os-release"
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %v", path, err)
}
m := make(map[string]string)
for _, line := range strings.Split(string(data), "\n") {
if kv := strings.SplitN(line, "=", 2); len(kv) == 2 {
m[kv[0]] = strings.Trim(kv[1], "\"")
}
}
return m, nil
}