blob: 49caff592138693a7bce45d24e6d08fc5f6d9c63 [file] [log] [blame] [edit]
// Copyright 2025 Google LLC
//
// 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 gpubuild provides functionality for compiling and packaging GPU
// drivers.
package gpubuild
import (
"bytes"
"errors"
"fmt"
"io/fs"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
cosfs "cos.googlesource.com/cos/tools.git/src/pkg/fs"
"cos.googlesource.com/cos/tools.git/src/pkg/utils"
)
// DriverPackage describes a tarball generated by the driver compilation
// process. The tarball looks like this:
//
// /drivers
// /drivers/nvidia-uvm.ko
// /drivers/nv-p2p-dummy.ko
// /drivers/gdrdrv.ko
// /drivers/nvidia.ko
// /drivers/nvidia-peermem.ko
// /drivers/nvidia-modeset.ko
// /drivers/nvidia-drm.ko
// /firmware
// /firmware/nvidia
// /firmware/nvidia/580.95.05
// /firmware/nvidia/580.95.05/gsp_tu10x.bin
// /firmware/nvidia/580.95.05/gsp_ga10x.bin
type DriverPackage struct {
// Path is the local file path to the driver package.
Path string
// Version is the Nvidia driver version of the package.
Version string
}
func nvidiaInstallerLSM(runfilePath string) (string, error) {
var outBytes bytes.Buffer
cmd := exec.Command(runfilePath, "--lsm")
cmd.Stdout = &outBytes
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("failed to read LSM from installer, see stderr for details: %v", err)
}
return string(outBytes.Bytes()), nil
}
func getArch(lsm string) (string, error) {
lines := strings.Split(lsm, "\n")
for _, line := range lines {
if strings.Contains(line, "Title:") && strings.Contains(line, "Linux-x86_64") {
return "x86_64", nil
}
if strings.Contains(line, "Title:") && strings.Contains(line, "Linux-aarch64") {
return "aarch64", nil
}
}
return "", errors.New("could not determine architecture from LSM")
}
func getVersion(lsm string) (string, error) {
lines := strings.Split(lsm, "\n")
for _, line := range lines {
if strings.Contains(line, "Version:") {
fields := strings.Fields(line)
if len(fields) < 2 {
return "", errors.New("could not determine version from LSM")
}
return fields[1], nil
}
}
return "", errors.New("could not determine version from LSM")
}
func copyPackageFiles(installerDir, driverDir, firmwareDir string) error {
return filepath.WalkDir(installerDir, func(path string, _ fs.DirEntry, err error) error {
if err != nil {
return err
}
if filepath.Ext(path) == ".ko" {
baseName := filepath.Base(path)
dest := filepath.Join(driverDir, baseName)
log.Printf("Copying %q to %q", path, dest)
if err := utils.CopyFile(path, dest); err != nil {
return err
}
return nil
}
isFirmware, err := filepath.Match(filepath.Join(filepath.Dir(path), "gsp*.bin"), path)
if err != nil {
return err
}
if isFirmware {
baseName := filepath.Base(path)
dest := filepath.Join(firmwareDir, baseName)
log.Printf("Copying %q to %q", path, dest)
if err := utils.CopyFile(path, dest); err != nil {
return err
}
return nil
}
return nil
})
}
// Compile generates a COS GPU driver package from a runfile.
//
// toolchainPath is the local file path to an unpacked toolchain, including
// both cross-compilers and kernel headers. Cross-compilers should be in bin/,
// kernel headers should be in a directory that matches usr/src/linux-headers-*.
//
// Compiles open source drivers if available, and falls back to closed source
// drivers if not available.
//
// The result driver package will go in outDir as a tarball that looks like
// nvidia-drivers-${version}.tgz.
func Compile(runfilePath, toolchainPath, outDir string) (*DriverPackage, error) {
tmpDir, err := os.MkdirTemp("", "nvidia-installer")
if err != nil {
return nil, err
}
defer os.RemoveAll(tmpDir)
runfileExtract := filepath.Join(tmpDir, "runfile")
lsm, err := nvidiaInstallerLSM(runfilePath)
if err != nil {
return nil, err
}
log.Printf("Compiling runfile with details:\n%v", lsm)
driverArch, err := getArch(lsm)
if err != nil {
return nil, err
}
driverVersion, err := getVersion(lsm)
if err != nil {
return nil, err
}
log.Printf("Extracting runfile to %q", runfileExtract)
cmd := exec.Command(runfilePath, "-x", "--target", runfileExtract)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, err
}
toolchainBin := filepath.Join(toolchainPath, "bin")
log.Print("Locating kernel headers")
kernelMatches, err := filepath.Glob(filepath.Join(toolchainPath, "usr/src/linux-headers-*"))
if err != nil {
return nil, err
}
if len(kernelMatches) != 1 {
return nil, fmt.Errorf("kernel headers directory is ambiguous; found %d directories", len(kernelMatches))
}
kernelHeaders := kernelMatches[0]
log.Printf("Found kernel headers at %q", kernelHeaders)
var srcSubDir string
if _, err := os.Stat(filepath.Join(runfileExtract, "kernel-open")); !os.IsNotExist(err) {
log.Println("Found kernel-open subdir in runfile - compiling open source drivers")
srcSubDir = "kernel-open"
} else {
log.Println("Could not find kernel-open subdir in runfile - compiling closed source drivers")
srcSubDir = "kernel"
}
kernelArch := driverArch
if driverArch == "aarch64" {
kernelArch = "arm64"
}
makeArgs := []string{
"-j", strconv.Itoa(runtime.NumCPU()),
"ARCH=" + kernelArch, "CROSS_COMPILE=" + driverArch + "-cros-linux-gnu-",
"CC=" + driverArch + "-cros-linux-gnu-clang", "LD=" + driverArch + "-cros-linux-gnu-ld.lld",
"V=1", "SYSSRC=" + kernelHeaders, "IGNORE_CC_MISMATCH=1",
}
log.Printf("Make args: %v", makeArgs)
cmd = exec.Command("make", makeArgs...)
cmd.Env = append(cmd.Environ(), "PATH="+toolchainBin+":"+os.Getenv("PATH"))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = filepath.Join(runfileExtract, srcSubDir)
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("GPU driver compilation failed, see stdout/stderr for more info: %v", err)
}
tarDir, err := os.MkdirTemp("", "nvidia-driver-output")
if err != nil {
return nil, err
}
defer os.RemoveAll(tarDir)
outputPath := filepath.Join(outDir, "nvidia-drivers-"+driverVersion+".tgz")
log.Printf("Creating result tarball at %q", outputPath)
tarDriverDir := filepath.Join(tarDir, "drivers")
tarFirmwareDir := filepath.Join(tarDir, "firmware/nvidia", driverVersion)
if err := os.Mkdir(tarDriverDir, 0755); err != nil {
return nil, err
}
if err := os.MkdirAll(tarFirmwareDir, 0755); err != nil {
return nil, err
}
if err := copyPackageFiles(runfileExtract, tarDriverDir, tarFirmwareDir); err != nil {
return nil, err
}
if err := cosfs.TarDir(tarDir, outputPath); err != nil {
return nil, err
}
log.Println("Runfile compilation succeeded")
return &DriverPackage{
Path: outputPath,
Version: driverVersion,
}, nil
}