Skip to content

Commit e67887c

Browse files
author
Dorinda Bassey
committed
vhost-device-gpu: Add support for GPU device path
Add a new --gpu-path CLI argument that allows users to specify which GPU device to use for rendering when using the virglrenderer backend. This is useful for systems with multiple GPUs where a specific device needs to be selected. Signed-off-by: Dorinda Bassey <[email protected]>
1 parent 9d66f7f commit e67887c

File tree

5 files changed

+119
-17
lines changed

5 files changed

+119
-17
lines changed

vhost-device-gpu/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
### Added
55

6+
- [[#903]] (https://github.com/rust-vmm/vhost-device/pull/903) vhost-device-gpu: Add support for GPU device path
7+
68
### Changed
79

810
- [[#852]] (https://github.com/rust-vmm/vhost-device/pull/890) vhost-device-gpu: Refactor vhost-device-gpu

vhost-device-gpu/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ A virtio-gpu device using the vhost-user protocol.
5656
[default: true]
5757
[possible values: true, false]
5858
59+
--gpu-path <PATH>
60+
GPU device path (e.g., /dev/dri/renderD128)
61+
62+
[Optional] Specifies which GPU device to use for rendering. Only
63+
applicable when using the virglrenderer backend.
64+
5965
-h, --help
6066
Print help (see a summary with '-h')
6167
@@ -134,6 +140,12 @@ First start the daemon on the host machine using one of the available gpu modes:
134140
host# vhost-device-gpu --socket-path /tmp/gpu.socket --gpu-mode virglrenderer
135141
```
136142

143+
To specify a particular GPU device (e.g., when you have multiple GPUs):
144+
145+
```shell
146+
host# vhost-device-gpu --socket-path /tmp/gpu.socket --gpu-mode virglrenderer --gpu-path /dev/dri/renderD128
147+
```
148+
137149
With QEMU, there are two device front-ends you can use with this device.
138150
You can either use `vhost-user-gpu-pci` or `vhost-user-vga`, which also
139151
implements VGA, that allows you to see boot messages before the guest

vhost-device-gpu/src/backend/virgl.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,17 @@ impl VirglRendererAdapter {
163163
fence_state.clone(),
164164
));
165165

166-
let renderer = VirglRenderer::init(virglrenderer_flags, fence_handler, None)
167-
.expect("Failed to initialize virglrenderer");
166+
// Use the GPU device FD if provided (already opened and validated at startup)
167+
let render_server_fd = config
168+
.flags()
169+
.render_server_fd
170+
.as_ref()
171+
.map(|fd| fd.try_clone())
172+
.transpose()
173+
.map_err(|e| io::Error::other(format!("Failed to clone GPU device FD: {e}")))?;
174+
175+
let renderer = VirglRenderer::init(virglrenderer_flags, fence_handler, render_server_fd)
176+
.map_err(|e| io::Error::other(format!("Failed to initialize virglrenderer: {e:?}")))?;
168177
Ok(Self {
169178
renderer,
170179
gpu_backend,

vhost-device-gpu/src/lib.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub(crate) mod testutils;
2121

2222
use std::{
2323
fmt::{Display, Formatter},
24-
path::Path,
24+
path::{Path, PathBuf},
2525
};
2626

2727
use bitflags::bitflags;
@@ -117,20 +117,22 @@ impl GpuCapset {
117117
}
118118
}
119119

120-
#[derive(Debug, Clone)]
120+
#[derive(Debug)]
121121
/// This structure holds the configuration for the GPU backend
122122
pub struct GpuConfig {
123123
gpu_mode: GpuMode,
124124
capset: GpuCapset,
125125
flags: GpuFlags,
126126
}
127127

128-
#[derive(Debug, Clone, PartialEq, Eq)]
128+
#[derive(Debug)]
129129
pub struct GpuFlags {
130130
pub use_egl: bool,
131131
pub use_glx: bool,
132132
pub use_gles: bool,
133133
pub use_surfaceless: bool,
134+
#[cfg(feature = "backend-virgl")]
135+
pub render_server_fd: Option<std::os::fd::OwnedFd>,
134136
}
135137

136138
impl GpuFlags {
@@ -141,6 +143,8 @@ impl GpuFlags {
141143
use_glx: false,
142144
use_gles: true,
143145
use_surfaceless: true,
146+
#[cfg(feature = "backend-virgl")]
147+
render_server_fd: None,
144148
}
145149
}
146150
}
@@ -157,6 +161,10 @@ pub enum GpuConfigError {
157161
CapsetUnsupportedByMode(GpuMode, GpuCapset),
158162
#[error("Requested gfxstream-gles capset, but gles is disabled")]
159163
GlesRequiredByGfxstream,
164+
#[error("GPU path can only be specified when using virglrenderer mode")]
165+
GpuPathNotSupportedByMode,
166+
#[error("Failed to open GPU device '{0}'")]
167+
InvalidGpuDevice(PathBuf),
160168
}
161169

162170
impl GpuConfig {
@@ -208,6 +216,12 @@ impl GpuConfig {
208216
return Err(GpuConfigError::GlesRequiredByGfxstream);
209217
}
210218

219+
// Validate that render_server_fd is only used with virglrenderer
220+
#[cfg(feature = "backend-virgl")]
221+
if flags.render_server_fd.is_some() && !matches!(gpu_mode, GpuMode::VirglRenderer) {
222+
return Err(GpuConfigError::GpuPathNotSupportedByMode);
223+
}
224+
211225
Ok(Self {
212226
gpu_mode,
213227
capset,
@@ -240,6 +254,7 @@ pub enum StartError {
240254

241255
pub fn start_backend(socket_path: &Path, config: GpuConfig) -> Result<(), StartError> {
242256
info!("Starting backend");
257+
243258
let backend = VhostUserGpuBackend::new(config).map_err(StartError::CouldNotCreateBackend)?;
244259

245260
let mut daemon = VhostUserDaemon::new(

vhost-device-gpu/src/main.rs

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
//
55
// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
66

7+
#[cfg(feature = "backend-virgl")]
8+
use std::{
9+
fs::File,
10+
os::fd::{FromRawFd, IntoRawFd, OwnedFd},
11+
};
712
use std::{path::PathBuf, process::exit};
813

914
use clap::{ArgAction, Parser, ValueEnum};
@@ -114,6 +119,11 @@ pub struct GpuFlagsArgs {
114119
default_value_t = GpuFlags::new_default().use_surfaceless
115120
)]
116121
pub use_surfaceless: bool,
122+
123+
/// GPU device path (e.g., /dev/dri/renderD128)
124+
#[clap(long, value_name = "PATH")]
125+
#[cfg(feature = "backend-virgl")]
126+
pub gpu_path: Option<PathBuf>,
117127
}
118128

119129
impl From<GpuFlagsArgs> for GpuFlags {
@@ -123,12 +133,36 @@ impl From<GpuFlagsArgs> for GpuFlags {
123133
use_glx: args.use_glx,
124134
use_gles: args.use_gles,
125135
use_surfaceless: args.use_surfaceless,
136+
#[cfg(feature = "backend-virgl")]
137+
render_server_fd: None,
126138
}
127139
}
128140
}
129141

130142
pub fn config_from_args(args: GpuArgs) -> Result<(PathBuf, GpuConfig), GpuConfigError> {
131-
let flags = GpuFlags::from(args.flags);
143+
// Open GPU device file if path was provided
144+
#[cfg(feature = "backend-virgl")]
145+
let render_server_fd = args
146+
.flags
147+
.gpu_path
148+
.as_ref()
149+
.map(|gpu_path| {
150+
let file = File::open(gpu_path)
151+
.map_err(|_| GpuConfigError::InvalidGpuDevice(gpu_path.clone()))?;
152+
// SAFETY: File was just opened successfully
153+
Ok(unsafe { OwnedFd::from_raw_fd(file.into_raw_fd()) })
154+
})
155+
.transpose()?;
156+
157+
let flags = GpuFlags {
158+
use_egl: args.flags.use_egl,
159+
use_glx: args.flags.use_glx,
160+
use_gles: args.flags.use_gles,
161+
use_surfaceless: args.flags.use_surfaceless,
162+
#[cfg(feature = "backend-virgl")]
163+
render_server_fd,
164+
};
165+
132166
let capset = args.capset.map(capset_names_into_capset);
133167
let config = GpuConfig::new(args.gpu_mode, capset, flags)?;
134168
Ok((args.socket_path, config))
@@ -185,7 +219,13 @@ mod tests {
185219
let args: &[&str] = &[];
186220
let flag_args = GpuFlagsArgs::parse_from(args);
187221
let flags: GpuFlags = flag_args.into();
188-
assert_eq!(flags, GpuFlags::default());
222+
let default_flags = GpuFlags::default();
223+
assert_eq!(flags.use_egl, default_flags.use_egl);
224+
assert_eq!(flags.use_glx, default_flags.use_glx);
225+
assert_eq!(flags.use_gles, default_flags.use_gles);
226+
assert_eq!(flags.use_surfaceless, default_flags.use_surfaceless);
227+
#[cfg(feature = "backend-virgl")]
228+
assert!(flags.render_server_fd.is_none());
189229
}
190230

191231
#[test]
@@ -200,22 +240,46 @@ mod tests {
200240
use_glx: true,
201241
use_gles: false,
202242
use_surfaceless: false,
243+
#[cfg(feature = "backend-virgl")]
244+
gpu_path: None,
203245
},
204246
};
205247

206248
let (socket_path, config) = config_from_args(args).unwrap();
207249

208250
assert_eq!(socket_path, expected_path);
209-
assert_eq!(
210-
*config.flags(),
211-
GpuFlags {
212-
use_egl: false,
213-
use_glx: true,
214-
use_gles: false,
215-
use_surfaceless: false,
216-
}
217-
);
251+
let flags = config.flags();
252+
assert!(!flags.use_egl);
253+
assert!(flags.use_glx);
254+
assert!(!flags.use_gles);
255+
assert!(!flags.use_surfaceless);
256+
#[cfg(feature = "backend-virgl")]
257+
assert!(flags.render_server_fd.is_none());
218258
assert_eq!(config.gpu_mode(), GpuMode::VirglRenderer);
219-
assert_eq!(config.capsets(), GpuCapset::VIRGL | GpuCapset::VIRGL2)
259+
assert_eq!(config.capsets(), GpuCapset::VIRGL | GpuCapset::VIRGL2);
260+
261+
// Test with invalid GPU path
262+
#[cfg(feature = "backend-virgl")]
263+
{
264+
let invalid_gpu_path = Path::new("/nonexistent/gpu/device");
265+
let invalid_args = GpuArgs {
266+
socket_path: expected_path.into(),
267+
gpu_mode: GpuMode::VirglRenderer,
268+
capset: Some(vec![CapsetName::Virgl]),
269+
flags: GpuFlagsArgs {
270+
use_egl: true,
271+
use_glx: false,
272+
use_gles: true,
273+
use_surfaceless: true,
274+
gpu_path: Some(invalid_gpu_path.into()),
275+
},
276+
};
277+
278+
let result = config_from_args(invalid_args);
279+
assert!(matches!(
280+
result,
281+
Err(GpuConfigError::InvalidGpuDevice(ref path)) if path == invalid_gpu_path
282+
));
283+
}
220284
}
221285
}

0 commit comments

Comments
 (0)