2022-11-23 15:29:18 +00:00
|
|
|
/**
|
|
|
|
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
**/
|
|
|
|
|
2022-12-02 13:17:52 +00:00
|
|
|
package nvcdi
|
2022-11-23 15:29:18 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2023-11-15 20:36:23 +00:00
|
|
|
"github.com/NVIDIA/go-nvlib/pkg/nvlib/device"
|
|
|
|
"github.com/NVIDIA/go-nvlib/pkg/nvml"
|
2023-12-01 01:10:10 +00:00
|
|
|
"tags.cncf.io/container-device-interface/pkg/cdi"
|
|
|
|
"tags.cncf.io/container-device-interface/specs-go"
|
|
|
|
|
2022-11-23 15:29:18 +00:00
|
|
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
|
2022-12-02 13:17:52 +00:00
|
|
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/edits"
|
2022-11-23 15:29:18 +00:00
|
|
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/info/drm"
|
2023-03-22 12:27:43 +00:00
|
|
|
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
|
2022-11-23 15:29:18 +00:00
|
|
|
)
|
|
|
|
|
2022-12-02 13:17:52 +00:00
|
|
|
// GetGPUDeviceSpecs returns the CDI device specs for the full GPU represented by 'device'.
|
2023-02-16 15:29:53 +00:00
|
|
|
func (l *nvmllib) GetGPUDeviceSpecs(i int, d device.Device) (*specs.Device, error) {
|
2022-12-02 13:17:52 +00:00
|
|
|
edits, err := l.GetGPUDeviceEdits(d)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get edits for device: %v", err)
|
|
|
|
}
|
|
|
|
|
2023-07-18 12:12:33 +00:00
|
|
|
name, err := l.deviceNamer.GetDeviceName(i, convert{d})
|
2022-12-02 13:17:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get device name: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
spec := specs.Device{
|
|
|
|
Name: name,
|
|
|
|
ContainerEdits: *edits.ContainerEdits,
|
|
|
|
}
|
|
|
|
|
|
|
|
return &spec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetGPUDeviceEdits returns the CDI edits for the full GPU represented by 'device'.
|
2023-02-16 15:29:53 +00:00
|
|
|
func (l *nvmllib) GetGPUDeviceEdits(d device.Device) (*cdi.ContainerEdits, error) {
|
2023-11-14 15:57:37 +00:00
|
|
|
device, err := newFullGPUDiscoverer(l.logger, l.devRoot, l.nvidiaCTKPath, d)
|
2022-12-02 13:17:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create device discoverer: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
editsForDevice, err := edits.FromDiscoverer(device)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create container edits for device: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return editsForDevice, nil
|
|
|
|
}
|
|
|
|
|
2022-12-12 13:48:54 +00:00
|
|
|
// byPathHookDiscoverer discovers the entities required for injecting by-path DRM device links
|
|
|
|
type byPathHookDiscoverer struct {
|
2023-03-22 12:27:43 +00:00
|
|
|
logger logger.Interface
|
2023-11-14 15:57:37 +00:00
|
|
|
devRoot string
|
2022-12-12 14:26:21 +00:00
|
|
|
nvidiaCTKPath string
|
|
|
|
pciBusID string
|
2023-02-21 13:51:08 +00:00
|
|
|
deviceNodes discover.Discover
|
2022-11-23 15:29:18 +00:00
|
|
|
}
|
|
|
|
|
2022-12-12 13:48:54 +00:00
|
|
|
var _ discover.Discover = (*byPathHookDiscoverer)(nil)
|
2022-11-23 15:29:18 +00:00
|
|
|
|
2022-12-02 13:17:52 +00:00
|
|
|
// newFullGPUDiscoverer creates a discoverer for the full GPU defined by the specified device.
|
2023-11-14 15:57:37 +00:00
|
|
|
func newFullGPUDiscoverer(logger logger.Interface, devRoot string, nvidiaCTKPath string, d device.Device) (discover.Discover, error) {
|
2022-11-23 15:29:18 +00:00
|
|
|
// TODO: The functionality to get device paths should be integrated into the go-nvlib/pkg/device.Device interface.
|
|
|
|
// This will allow reuse here and in other code where the paths are queried such as the NVIDIA device plugin.
|
|
|
|
minor, ret := d.GetMinorNumber()
|
|
|
|
if ret != nvml.SUCCESS {
|
|
|
|
return nil, fmt.Errorf("error getting GPU device minor number: %v", ret)
|
|
|
|
}
|
|
|
|
path := fmt.Sprintf("/dev/nvidia%d", minor)
|
|
|
|
|
|
|
|
pciInfo, ret := d.GetPciInfo()
|
|
|
|
if ret != nvml.SUCCESS {
|
|
|
|
return nil, fmt.Errorf("error getting PCI info for device: %v", ret)
|
|
|
|
}
|
|
|
|
pciBusID := getBusID(pciInfo)
|
|
|
|
|
|
|
|
drmDeviceNodes, err := drm.GetDeviceNodesByBusID(pciBusID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to determine DRM devices for %v: %v", pciBusID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
deviceNodePaths := append([]string{path}, drmDeviceNodes...)
|
|
|
|
|
2022-12-12 13:48:54 +00:00
|
|
|
deviceNodes := discover.NewCharDeviceDiscoverer(
|
|
|
|
logger,
|
2023-11-14 15:57:37 +00:00
|
|
|
devRoot,
|
2023-11-20 14:03:36 +00:00
|
|
|
deviceNodePaths,
|
2022-12-12 13:48:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
byPathHooks := &byPathHookDiscoverer{
|
2022-12-12 14:26:21 +00:00
|
|
|
logger: logger,
|
2023-11-14 15:57:37 +00:00
|
|
|
devRoot: devRoot,
|
2022-12-12 14:26:21 +00:00
|
|
|
nvidiaCTKPath: nvidiaCTKPath,
|
|
|
|
pciBusID: pciBusID,
|
2023-02-21 13:51:08 +00:00
|
|
|
deviceNodes: deviceNodes,
|
2022-11-23 15:29:18 +00:00
|
|
|
}
|
|
|
|
|
2023-02-22 15:10:28 +00:00
|
|
|
deviceFolderPermissionHooks := newDeviceFolderPermissionHookDiscoverer(
|
|
|
|
logger,
|
2023-11-14 15:57:37 +00:00
|
|
|
devRoot,
|
2023-02-22 15:10:28 +00:00
|
|
|
nvidiaCTKPath,
|
|
|
|
deviceNodes,
|
|
|
|
)
|
|
|
|
|
2022-12-12 13:48:54 +00:00
|
|
|
dd := discover.Merge(
|
|
|
|
deviceNodes,
|
|
|
|
byPathHooks,
|
2023-02-22 15:10:28 +00:00
|
|
|
deviceFolderPermissionHooks,
|
2022-12-12 13:48:54 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return dd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Devices returns the empty list for the by-path hook discoverer
|
|
|
|
func (d *byPathHookDiscoverer) Devices() ([]discover.Device, error) {
|
|
|
|
return nil, nil
|
2022-11-23 15:29:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Hooks returns the hooks for the GPU device.
|
|
|
|
// The following hooks are detected:
|
|
|
|
// 1. A hook to create /dev/dri/by-path symlinks
|
2022-12-12 13:48:54 +00:00
|
|
|
func (d *byPathHookDiscoverer) Hooks() ([]discover.Hook, error) {
|
2022-11-23 15:29:18 +00:00
|
|
|
links, err := d.deviceNodeLinks()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to discover DRA device links: %v", err)
|
|
|
|
}
|
|
|
|
if len(links) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2022-12-12 14:26:21 +00:00
|
|
|
var args []string
|
2022-11-23 15:29:18 +00:00
|
|
|
for _, l := range links {
|
|
|
|
args = append(args, "--link", l)
|
|
|
|
}
|
|
|
|
|
2022-12-12 14:26:21 +00:00
|
|
|
hook := discover.CreateNvidiaCTKHook(
|
|
|
|
d.nvidiaCTKPath,
|
|
|
|
"create-symlinks",
|
|
|
|
args...,
|
|
|
|
)
|
2022-11-23 15:29:18 +00:00
|
|
|
|
2022-12-12 14:26:21 +00:00
|
|
|
return []discover.Hook{hook}, nil
|
2022-11-23 15:29:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Mounts returns an empty slice for a full GPU
|
2022-12-12 13:48:54 +00:00
|
|
|
func (d *byPathHookDiscoverer) Mounts() ([]discover.Mount, error) {
|
2022-11-23 15:29:18 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2022-12-12 13:48:54 +00:00
|
|
|
func (d *byPathHookDiscoverer) deviceNodeLinks() ([]string, error) {
|
2023-02-21 13:51:08 +00:00
|
|
|
devices, err := d.deviceNodes.Devices()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to discover device nodes: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(devices) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
selectedDevices := make(map[string]bool)
|
|
|
|
for _, d := range devices {
|
|
|
|
selectedDevices[d.HostPath] = true
|
|
|
|
}
|
|
|
|
|
2022-11-23 15:29:18 +00:00
|
|
|
candidates := []string{
|
|
|
|
fmt.Sprintf("/dev/dri/by-path/pci-%s-card", d.pciBusID),
|
|
|
|
fmt.Sprintf("/dev/dri/by-path/pci-%s-render", d.pciBusID),
|
|
|
|
}
|
|
|
|
|
|
|
|
var links []string
|
|
|
|
for _, c := range candidates {
|
2023-11-14 15:57:37 +00:00
|
|
|
linkPath := filepath.Join(d.devRoot, c)
|
2022-11-23 15:29:18 +00:00
|
|
|
device, err := os.Readlink(linkPath)
|
|
|
|
if err != nil {
|
|
|
|
d.logger.Warningf("Failed to evaluate symlink %v; ignoring", linkPath)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-02-21 13:51:08 +00:00
|
|
|
deviceNode := device
|
|
|
|
if !filepath.IsAbs(device) {
|
|
|
|
deviceNode = filepath.Join(filepath.Dir(linkPath), device)
|
|
|
|
}
|
|
|
|
if !selectedDevices[deviceNode] {
|
|
|
|
d.logger.Debugf("ignoring device symlink %v -> %v since %v is not mounted", linkPath, device, deviceNode)
|
|
|
|
continue
|
|
|
|
}
|
2022-11-23 15:29:18 +00:00
|
|
|
d.logger.Debugf("adding device symlink %v -> %v", linkPath, device)
|
|
|
|
links = append(links, fmt.Sprintf("%v::%v", device, linkPath))
|
|
|
|
}
|
|
|
|
|
|
|
|
return links, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getBusID provides a utility function that returns the string representation of the bus ID.
|
|
|
|
func getBusID(p nvml.PciInfo) string {
|
|
|
|
var bytes []byte
|
|
|
|
for _, b := range p.BusId {
|
|
|
|
if byte(b) == '\x00' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
bytes = append(bytes, byte(b))
|
|
|
|
}
|
|
|
|
id := strings.ToLower(string(bytes))
|
|
|
|
|
|
|
|
if id != "0000" {
|
|
|
|
id = strings.TrimPrefix(id, "0000")
|
|
|
|
}
|
|
|
|
|
|
|
|
return id
|
|
|
|
}
|