Skip to content

Commit fecbabd

Browse files
committed
fix: Fix device node issues related to Wayland in both CDI and legacy
Signed-off-by: Seungmin Kim <8457324+ehfd@users.noreply.github.com>
1 parent 2d26818 commit fecbabd

5 files changed

Lines changed: 336 additions & 2 deletions

File tree

cmd/nvidia-ctk/cdi/generate/generate.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/NVIDIA/nvidia-container-toolkit/api/config/v1"
3636
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
3737
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
38+
"github.com/NVIDIA/nvidia-container-toolkit/internal/system/devicenodes"
3839
"github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi"
3940
"github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/spec"
4041
"github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/transform"
@@ -316,6 +317,10 @@ func (m command) validateFlags(c *cli.Command, opts *options) error {
316317
}
317318

318319
func (m command) run(opts *options) error {
320+
if err := devicenodes.CreateControlDeviceNodes(m.logger, opts.driverRoot, opts.devRoot); err != nil {
321+
m.logger.Warningf("Failed to create missing NVIDIA control device nodes: %v", err)
322+
}
323+
319324
specs, err := m.generateSpecs(opts)
320325
if err != nil {
321326
return fmt.Errorf("failed to generate CDI spec: %v", err)

internal/modifier/cdi.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/NVIDIA/nvidia-container-toolkit/internal/modifier/cdi"
2828
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
2929
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
30+
"github.com/NVIDIA/nvidia-container-toolkit/internal/system/devicenodes"
3031
"github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi"
3132
)
3233

@@ -183,6 +184,10 @@ func (f *Factory) newAutomaticCDISpecModifier(devices []string) (oci.SpecModifie
183184
csvFiles = csv.BaseFilesOnly(csvFiles)
184185
}
185186

187+
if err := devicenodes.CreateControlDeviceNodes(f.logger, f.driver.Root, f.driver.DevRoot); err != nil {
188+
f.logger.Warningf("Failed to create missing NVIDIA control device nodes: %v", err)
189+
}
190+
186191
cdiModeIdentifiers := cdiModeIdentfiersFromDevices(devices...)
187192
f.logger.Debugf("Per-mode identifiers: %v", cdiModeIdentifiers)
188193
var modifiers oci.SpecModifiers
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
**/
17+
18+
package devicenodes
19+
20+
import (
21+
"fmt"
22+
"os"
23+
"path/filepath"
24+
25+
"github.com/NVIDIA/go-nvlib/pkg/nvlib/info"
26+
27+
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
28+
"github.com/NVIDIA/nvidia-container-toolkit/pkg/system/nvdevices"
29+
"github.com/NVIDIA/nvidia-container-toolkit/pkg/system/nvmodules"
30+
)
31+
32+
type moduleLoader interface {
33+
LoadAll() error
34+
}
35+
36+
type deviceCreator interface {
37+
CreateNVIDIAControlDevices() error
38+
}
39+
40+
// A creator creates NVIDIA control device nodes.
41+
type creator struct {
42+
logger logger.Interface
43+
// devRoot is the root under which the device nodes are created.
44+
devRoot string
45+
46+
nodeExists func(string) bool
47+
resolvePlatform func() info.Platform
48+
moduleLoader moduleLoader
49+
// newDeviceCreator constructs a device creator on demand. The device major
50+
// numbers are read from /proc/devices at construction and only become
51+
// available once the associated kernel modules have been loaded.
52+
newDeviceCreator func() (deviceCreator, error)
53+
}
54+
55+
// CreateControlDeviceNodes loads the NVIDIA kernel modules and creates the
56+
// NVIDIA control device nodes that do not exist. It is the programmatic
57+
// equivalent of running
58+
// `nvidia-ctk system create-device-nodes --control-devices --load-kernel-modules`.
59+
// The NVIDIA driver creates control device nodes such as /dev/nvidia-modeset
60+
// on demand and does not register them with devtmpfs, meaning that they may
61+
// not exist on a host even if the installed driver supports them.
62+
// The driverRoot is used to resolve the kernel modules of the NVIDIA driver
63+
// installation and devRoot is the root under which the device nodes are
64+
// created. If devRoot is empty, the driverRoot is assumed.
65+
// If all control device nodes already exist, this is a no-op. On platforms
66+
// that do not use NVIDIA device nodes, such as Tegra-based systems and WSL,
67+
// this is also a no-op. The kernel modules are loaded on a best-effort basis
68+
// so that device node creation is still attempted for modules that are
69+
// already loaded.
70+
func CreateControlDeviceNodes(logger logger.Interface, driverRoot string, devRoot string) error {
71+
return newCreator(logger, driverRoot, devRoot).createControlDeviceNodes()
72+
}
73+
74+
func newCreator(logger logger.Interface, driverRoot string, devRoot string) *creator {
75+
if devRoot == "" {
76+
devRoot = driverRoot
77+
}
78+
if devRoot == "" {
79+
devRoot = "/"
80+
}
81+
return &creator{
82+
logger: logger,
83+
devRoot: devRoot,
84+
nodeExists: nodeExists,
85+
resolvePlatform: func() info.Platform {
86+
return info.New(info.WithLogger(logger)).ResolvePlatform()
87+
},
88+
moduleLoader: nvmodules.New(
89+
nvmodules.WithLogger(logger),
90+
nvmodules.WithRoot(driverRoot),
91+
),
92+
newDeviceCreator: func() (deviceCreator, error) {
93+
return nvdevices.New(
94+
nvdevices.WithLogger(logger),
95+
nvdevices.WithDevRoot(devRoot),
96+
)
97+
},
98+
}
99+
}
100+
101+
func (c *creator) createControlDeviceNodes() error {
102+
if c.controlDeviceNodesExist() {
103+
c.logger.Debugf("Skipping the creation of control device nodes: all control device nodes exist")
104+
return nil
105+
}
106+
if c.resolvePlatform() != info.PlatformNVML {
107+
c.logger.Debugf("Skipping the creation of control device nodes on a non-NVML platform")
108+
return nil
109+
}
110+
if err := c.moduleLoader.LoadAll(); err != nil {
111+
c.logger.Debugf("Failed to load the NVIDIA kernel modules: %v", err)
112+
}
113+
devices, err := c.newDeviceCreator()
114+
if err != nil {
115+
return fmt.Errorf("failed to query NVIDIA device majors: %w", err)
116+
}
117+
return devices.CreateNVIDIAControlDevices()
118+
}
119+
120+
// controlDeviceNodesExist returns whether all NVIDIA control device nodes
121+
// exist at the configured device root.
122+
// Device nodes that cannot be checked are treated as missing so that their
123+
// creation is attempted.
124+
func (c *creator) controlDeviceNodesExist() bool {
125+
for _, node := range nvdevices.ControlDeviceNodes() {
126+
if !c.nodeExists(filepath.Join(c.devRoot, "dev", node)) {
127+
return false
128+
}
129+
}
130+
return true
131+
}
132+
133+
func nodeExists(path string) bool {
134+
_, err := os.Stat(path)
135+
return err == nil
136+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/**
2+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
**/
17+
18+
package devicenodes
19+
20+
import (
21+
"errors"
22+
"testing"
23+
24+
"github.com/stretchr/testify/require"
25+
26+
"github.com/NVIDIA/go-nvlib/pkg/nvlib/info"
27+
28+
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
29+
)
30+
31+
type fakeModuleLoader struct {
32+
err error
33+
calls *[]string
34+
}
35+
36+
func (f *fakeModuleLoader) LoadAll() error {
37+
*f.calls = append(*f.calls, "load-modules")
38+
return f.err
39+
}
40+
41+
type fakeDeviceCreator struct {
42+
err error
43+
calls *[]string
44+
}
45+
46+
func (f *fakeDeviceCreator) CreateNVIDIAControlDevices() error {
47+
*f.calls = append(*f.calls, "create-devices")
48+
return f.err
49+
}
50+
51+
func TestDevRootResolution(t *testing.T) {
52+
testCases := []struct {
53+
description string
54+
driverRoot string
55+
devRoot string
56+
expectedDevRoot string
57+
}{
58+
{
59+
description: "unspecified roots resolve to the filesystem root",
60+
expectedDevRoot: "/",
61+
},
62+
{
63+
description: "an unspecified dev root assumes the driver root",
64+
driverRoot: "/driver-root",
65+
expectedDevRoot: "/driver-root",
66+
},
67+
{
68+
description: "an explicit dev root is used",
69+
driverRoot: "/driver-root",
70+
devRoot: "/dev-root",
71+
expectedDevRoot: "/dev-root",
72+
},
73+
}
74+
75+
for _, tc := range testCases {
76+
t.Run(tc.description, func(t *testing.T) {
77+
c := newCreator(logger.New(), tc.driverRoot, tc.devRoot)
78+
require.Equal(t, tc.expectedDevRoot, c.devRoot)
79+
})
80+
}
81+
}
82+
83+
func TestCreateControlDeviceNodes(t *testing.T) {
84+
errModuleLoad := errors.New("module load error")
85+
errNewDeviceCreator := errors.New("device creator error")
86+
errCreateDevices := errors.New("create devices error")
87+
88+
testCases := []struct {
89+
description string
90+
nodesExist bool
91+
platform info.Platform
92+
moduleLoadError error
93+
newDeviceCreatorError error
94+
createDevicesError error
95+
expectedError error
96+
expectedCalls []string
97+
}{
98+
{
99+
description: "existing control device nodes are not created",
100+
nodesExist: true,
101+
platform: info.PlatformNVML,
102+
},
103+
{
104+
description: "non-NVML platforms are skipped",
105+
platform: info.PlatformWSL,
106+
expectedCalls: []string{"resolve-platform"},
107+
},
108+
{
109+
description: "kernel modules are loaded before device nodes are created",
110+
platform: info.PlatformNVML,
111+
expectedCalls: []string{"resolve-platform", "load-modules", "new-device-creator", "create-devices"},
112+
},
113+
{
114+
description: "module load failures do not prevent device node creation",
115+
platform: info.PlatformNVML,
116+
moduleLoadError: errModuleLoad,
117+
expectedCalls: []string{"resolve-platform", "load-modules", "new-device-creator", "create-devices"},
118+
},
119+
{
120+
description: "device creator construction errors are returned",
121+
platform: info.PlatformNVML,
122+
newDeviceCreatorError: errNewDeviceCreator,
123+
expectedError: errNewDeviceCreator,
124+
expectedCalls: []string{"resolve-platform", "load-modules", "new-device-creator"},
125+
},
126+
{
127+
description: "device node creation errors are returned",
128+
platform: info.PlatformNVML,
129+
createDevicesError: errCreateDevices,
130+
expectedError: errCreateDevices,
131+
expectedCalls: []string{"resolve-platform", "load-modules", "new-device-creator", "create-devices"},
132+
},
133+
}
134+
135+
for _, tc := range testCases {
136+
t.Run(tc.description, func(t *testing.T) {
137+
var calls []string
138+
var checkedNodes []string
139+
c := &creator{
140+
logger: logger.New(),
141+
devRoot: "/dev-root",
142+
nodeExists: func(path string) bool {
143+
checkedNodes = append(checkedNodes, path)
144+
return tc.nodesExist
145+
},
146+
resolvePlatform: func() info.Platform {
147+
calls = append(calls, "resolve-platform")
148+
return tc.platform
149+
},
150+
moduleLoader: &fakeModuleLoader{
151+
err: tc.moduleLoadError,
152+
calls: &calls,
153+
},
154+
newDeviceCreator: func() (deviceCreator, error) {
155+
calls = append(calls, "new-device-creator")
156+
if tc.newDeviceCreatorError != nil {
157+
return nil, tc.newDeviceCreatorError
158+
}
159+
return &fakeDeviceCreator{
160+
err: tc.createDevicesError,
161+
calls: &calls,
162+
}, nil
163+
},
164+
}
165+
166+
err := c.createControlDeviceNodes()
167+
if tc.expectedError != nil {
168+
require.ErrorIs(t, err, tc.expectedError)
169+
} else {
170+
require.NoError(t, err)
171+
}
172+
require.Equal(t, tc.expectedCalls, calls)
173+
// The control device nodes are checked at the configured device
174+
// root before any other action is taken. The check is stopped as
175+
// soon as a device node is found to be missing.
176+
if tc.nodesExist {
177+
require.Contains(t, checkedNodes, "/dev-root/dev/nvidia-modeset")
178+
} else {
179+
require.Equal(t, []string{"/dev-root/dev/nvidiactl"}, checkedNodes)
180+
}
181+
})
182+
}
183+
}

pkg/system/nvdevices/devices.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,15 @@ func New(opts ...Option) (*Interface, error) {
7070
return i, nil
7171
}
7272

73+
// ControlDeviceNodes returns the names of the NVIDIA control device nodes.
74+
// These are the device nodes that are not associated with a specific GPU.
75+
func ControlDeviceNodes() []string {
76+
return []string{"nvidiactl", "nvidia-modeset", "nvidia-uvm", "nvidia-uvm-tools"}
77+
}
78+
7379
// CreateNVIDIAControlDevices creates the NVIDIA control device nodes at the configured devRoot.
7480
func (m *Interface) CreateNVIDIAControlDevices() error {
75-
controlNodes := []string{"nvidiactl", "nvidia-modeset", "nvidia-uvm", "nvidia-uvm-tools"}
76-
for _, node := range controlNodes {
81+
for _, node := range ControlDeviceNodes() {
7782
err := m.CreateNVIDIADevice(node)
7883
if err != nil {
7984
return fmt.Errorf("failed to create device node %s: %w", node, err)

0 commit comments

Comments
 (0)