• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / alioth / 18602811487

17 Oct 2025 06:27PM UTC coverage: 17.44% (-0.4%) from 17.856%
18602811487

push

github

Lencerf
test(pci): add tests for HostBridge

Signed-off-by: Changyuan Lyu <changyuanl@google.com>

1394 of 7993 relevant lines covered (17.44%)

17.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/alioth-cli/src/boot.rs
1
// Copyright 2025 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
use std::collections::HashMap;
16
#[cfg(target_arch = "x86_64")]
17
use std::ffi::CString;
18
#[cfg(target_arch = "x86_64")]
19
use std::fs::File;
20
use std::path::PathBuf;
21

22
use alioth::board::BoardConfig;
23
#[cfg(target_arch = "x86_64")]
24
use alioth::device::fw_cfg::FwCfgItemParam;
25
use alioth::errors::{DebugTrace, trace_error};
26
#[cfg(target_os = "macos")]
27
use alioth::hv::Hvf;
28
use alioth::hv::{self, Coco};
29
#[cfg(target_os = "linux")]
30
use alioth::hv::{Kvm, KvmConfig};
31
use alioth::loader::{ExecType, Payload};
32
use alioth::mem::{MemBackend, MemConfig};
33
#[cfg(target_os = "linux")]
34
use alioth::vfio::{CdevParam, ContainerParam, GroupParam, IoasParam};
35
#[cfg(target_os = "linux")]
36
use alioth::virtio::DeviceId;
37
use alioth::virtio::dev::balloon::BalloonParam;
38
use alioth::virtio::dev::blk::BlkFileParam;
39
use alioth::virtio::dev::entropy::EntropyParam;
40
use alioth::virtio::dev::fs::shared_dir::SharedDirParam;
41
#[cfg(target_os = "linux")]
42
use alioth::virtio::dev::fs::vu::VuFsParam;
43
#[cfg(target_os = "linux")]
44
use alioth::virtio::dev::net::tap::NetTapParam;
45
#[cfg(target_os = "macos")]
46
use alioth::virtio::dev::net::vmnet::NetVmnetParam;
47
use alioth::virtio::dev::vsock::UdsVsockParam;
48
#[cfg(target_os = "linux")]
49
use alioth::virtio::dev::vsock::VhostVsockParam;
50
#[cfg(target_os = "linux")]
51
use alioth::virtio::vu::frontend::VuFrontendParam;
52
use alioth::vm::Machine;
53
use clap::Args;
54
use serde::Deserialize;
55
use serde_aco::{Help, help_text};
56
use snafu::{ResultExt, Snafu};
57

58
use crate::objects::{DOC_OBJECTS, parse_objects};
59

60
#[trace_error]
61
#[derive(Snafu, DebugTrace)]
62
#[snafu(module, context(suffix(false)))]
63
pub enum Error {
64
    #[snafu(display("Failed to parse {arg}"))]
65
    ParseArg {
66
        arg: String,
67
        error: serde_aco::Error,
68
    },
69
    #[snafu(display("Failed to parse objects"), context(false))]
70
    ParseObjects { source: crate::objects::Error },
71
    #[cfg(target_os = "linux")]
72
    #[snafu(display("Failed to access system hypervisor"))]
73
    Hypervisor { source: alioth::hv::Error },
74
    #[snafu(display("Failed to create a VM"))]
75
    CreateVm { source: alioth::vm::Error },
76
    #[snafu(display("Failed to create a device"))]
77
    CreateDevice { source: alioth::vm::Error },
78
    #[cfg(target_arch = "x86_64")]
79
    #[snafu(display("Failed to open {path:?}"))]
80
    OpenFile {
81
        path: PathBuf,
82
        error: std::io::Error,
83
    },
84
    #[cfg(target_arch = "x86_64")]
85
    #[snafu(display("Failed to configure the fw-cfg device"))]
86
    FwCfg { error: std::io::Error },
87
    #[cfg(target_arch = "x86_64")]
88
    #[snafu(display("{s} is not a valid CString"))]
89
    CreateCString { s: String },
90
    #[snafu(display("Failed to boot a VM"))]
91
    BootVm { source: alioth::vm::Error },
92
    #[snafu(display("VM did not shutdown peacefully"))]
93
    WaitVm { source: alioth::vm::Error },
94
}
95

96
#[derive(Debug, Deserialize, Clone, Help)]
97
#[cfg_attr(target_os = "macos", derive(Default))]
98
enum Hypervisor {
99
    /// KVM backed by the Linux kernel.
100
    #[cfg(target_os = "linux")]
101
    #[serde(alias = "kvm")]
102
    Kvm(KvmConfig),
103
    /// macOS Hypervisor Framework.
104
    #[cfg(target_os = "macos")]
105
    #[serde(alias = "hvf")]
106
    #[default]
107
    Hvf,
108
}
109

110
#[cfg(target_os = "linux")]
111
impl Default for Hypervisor {
112
    fn default() -> Self {
113
        Hypervisor::Kvm(KvmConfig::default())
114
    }
115
}
116

117
#[derive(Debug, Deserialize, Clone, Help)]
118
enum FsParam {
119
    /// VirtIO FS device backed by a shared directory.
120
    #[serde(alias = "dir")]
121
    Dir(SharedDirParam),
122
    #[cfg(target_os = "linux")]
123
    /// VirtIO FS device backed by a vhost-user process, e.g. virtiofsd.
124
    #[serde(alias = "vu")]
125
    Vu(VuFsParam),
126
}
127

128
#[derive(Debug, Deserialize, Clone, Help)]
129
enum VsockParam {
130
    #[cfg(target_os = "linux")]
131
    /// Vsock device backed by host kernel vhost-vsock module.
132
    #[serde(alias = "vhost")]
133
    Vhost(VhostVsockParam),
134
    /// Vsock device mapped to a Unix domain socket.
135
    #[serde(alias = "uds")]
136
    Uds(UdsVsockParam),
137
}
138

139
#[cfg(target_os = "linux")]
140
#[derive(Deserialize, Help)]
141
struct VuSocket {
142
    socket: PathBuf,
143
}
144

145
#[derive(Deserialize, Help)]
146
enum NetParam {
147
    /// VirtIO net device backed by TUN/TAP, MacVTap, or IPVTap.
148
    #[cfg(target_os = "linux")]
149
    #[serde(alias = "tap")]
150
    Tap(NetTapParam),
151
    /// VirtIO net device backed by vmnet framework.
152
    #[cfg(target_os = "macos")]
153
    #[serde(alias = "vmnet")]
154
    Vmnet(NetVmnetParam),
155
    /// vhost-user net device over a Unix domain socket.
156
    #[cfg(target_os = "linux")]
157
    #[serde(alias = "vu")]
158
    Vu(VuSocket),
159
}
160

161
#[derive(Deserialize, Help)]
162
enum BlkParam {
163
    /// VirtIO block device backed a disk image file.
164
    #[serde(alias = "file")]
165
    File(BlkFileParam),
166
    #[cfg(target_os = "linux")]
167
    #[serde(alias = "vu")]
168
    /// vhost-user block device over a Unix domain socket.
169
    Vu(VuSocket),
170
}
171

172
#[derive(Args, Debug, Clone)]
173
#[command(arg_required_else_help = true, alias("run"))]
174
pub struct BootArgs {
175
    #[arg(long, help(
176
        help_text::<Hypervisor>("Specify the Hypervisor to run on.")
177
    ), value_name = "HV")]
178
    hypervisor: Option<String>,
179

180
    /// Path to a Linux kernel image.
181
    #[arg(short, long, value_name = "PATH")]
182
    kernel: Option<PathBuf>,
183

184
    /// Path to an ELF kernel with PVH note.
185
    #[cfg(target_arch = "x86_64")]
186
    #[arg(long, value_name = "PATH")]
187
    pvh: Option<PathBuf>,
188

189
    /// Path to a firmware image.
190
    #[arg(long, short, value_name = "PATH")]
191
    firmware: Option<PathBuf>,
192

193
    /// Command line to pass to the kernel, e.g. `console=ttyS0`.
194
    #[arg(short, long, value_name = "ARGS")]
195
    cmd_line: Option<String>,
196

197
    /// Path to an initramfs image.
198
    #[arg(short, long, value_name = "PATH")]
199
    initramfs: Option<PathBuf>,
200

201
    /// Number of VCPUs assigned to the guest.
202
    #[arg(long, default_value_t = 1)]
203
    num_cpu: u32,
204

205
    /// DEPRECATED: Use --memory instead.
206
    #[arg(long, default_value = "1G")]
207
    mem_size: String,
208

209
    #[arg(short, long, help(
210
        help_text::<MemConfig>("Specify the memory of the guest.")
211
    ))]
212
    memory: Option<String>,
213

214
    /// Add a pvpanic device.
215
    #[arg(long)]
216
    pvpanic: bool,
217

218
    #[cfg(target_arch = "x86_64")]
219
    #[arg(long = "fw-cfg", help(
220
        help_text::<FwCfgItemParam>("Add an extra item to the fw_cfg device.")
221
    ), value_name = "ITEM")]
222
    fw_cfgs: Vec<String>,
223

224
    /// Add a VirtIO entropy device.
225
    #[arg(long)]
226
    entropy: bool,
227

228
    #[arg(long, help(
229
        help_text::<NetParam>("Add a VirtIO net device.")
230
    ))]
231
    net: Vec<String>,
232

233
    #[arg(long, help(
234
        help_text::<BlkParam>("Add a VirtIO block device.")
235
    ))]
236
    blk: Vec<String>,
237

238
    #[arg(long, help(
239
        help_text::<Coco>("Enable confidential compute supported by host platform.")
240
    ))]
241
    coco: Option<String>,
242

243
    #[arg(long, help(
244
        help_text::<FsParam>("Add a VirtIO filesystem device.")
245
    ))]
246
    fs: Vec<String>,
247

248
    #[arg(long, help(
249
        help_text::<VsockParam>("Add a VirtIO vsock device.")
250
    ))]
251
    vsock: Option<String>,
252

253
    #[cfg(target_os = "linux")]
254
    #[arg(long, help(help_text::<CdevParam>(
255
        "Assign a host PCI device to the guest using IOMMUFD API."
256
    ) ))]
257
    vfio_cdev: Vec<String>,
258

259
    #[cfg(target_os = "linux")]
260
    #[arg(long, help(help_text::<IoasParam>("Create a new IO address space.")))]
261
    vfio_ioas: Vec<String>,
262

263
    #[cfg(target_os = "linux")]
264
    #[arg(long, help(help_text::<GroupParam>(
265
        "Assign a host PCI device to the guest using legacy VFIO API."
266
    )))]
267
    vfio_group: Vec<String>,
268

269
    #[cfg(target_os = "linux")]
270
    #[arg(long, help(help_text::<ContainerParam>("Add a new VFIO container.")))]
271
    vfio_container: Vec<String>,
272

273
    #[arg(long)]
274
    #[arg(long, help(help_text::<BalloonParam>("Add a VirtIO balloon device.")))]
275
    balloon: Option<String>,
276

277
    #[arg(short, long("object"), help = DOC_OBJECTS, value_name = "OBJECT")]
278
    objects: Vec<String>,
279
}
280

281
fn add_net<H>(
×
282
    vm: &Machine<H>,
283
    args: Vec<String>,
284
    objects: &HashMap<&str, &str>,
285
) -> Result<(), Error>
286
where
287
    H: hv::Hypervisor + 'static,
288
{
289
    for (index, arg) in args.into_iter().enumerate() {
×
290
        #[cfg(target_os = "linux")]
291
        let param: NetParam = match serde_aco::from_args(&arg, objects) {
×
292
            Ok(p) => p,
×
293
            Err(_) => {
×
294
                let tap_param = serde_aco::from_args::<NetTapParam>(&arg, objects)
×
295
                    .context(error::ParseArg { arg })?;
×
296
                NetParam::Tap(tap_param)
×
297
            }
298
        };
299
        #[cfg(target_os = "macos")]
300
        let param: NetParam =
×
301
            serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?;
×
302
        match param {
×
303
            #[cfg(target_os = "linux")]
304
            NetParam::Tap(tap_param) => vm.add_virtio_dev(format!("virtio-net-{index}"), tap_param),
×
305
            #[cfg(target_os = "linux")]
306
            NetParam::Vu(sock) => {
×
307
                let param = VuFrontendParam {
308
                    id: DeviceId::Net,
309
                    socket: sock.socket,
×
310
                };
311
                vm.add_virtio_dev(format!("vu-net-{index}"), param)
×
312
            }
313
            #[cfg(target_os = "macos")]
314
            NetParam::Vmnet(p) => vm.add_virtio_dev(format!("virtio-net-{index}"), p),
×
315
        }
316
        .context(error::CreateDevice)?;
×
317
    }
318
    Ok(())
×
319
}
320

321
fn add_blk<H>(
×
322
    vm: &Machine<H>,
323
    args: Vec<String>,
324
    objects: &HashMap<&str, &str>,
325
) -> Result<(), Error>
326
where
327
    H: hv::Hypervisor + 'static,
328
{
329
    for (index, opt) in args.into_iter().enumerate() {
×
330
        let param: BlkParam = match serde_aco::from_args(&opt, objects) {
×
331
            Ok(param) => param,
×
332
            Err(_) => match serde_aco::from_args(&opt, objects) {
×
333
                Ok(param) => BlkParam::File(param),
×
334
                Err(_) => {
×
335
                    eprintln!("Please update the cmd line to --blk file,path={opt}");
×
336
                    BlkParam::File(BlkFileParam {
×
337
                        path: opt.into(),
×
338
                        ..Default::default()
×
339
                    })
340
                }
341
            },
342
        };
343
        match param {
×
344
            BlkParam::File(p) => vm.add_virtio_dev(format!("virtio-blk-{index}"), p),
×
345
            #[cfg(target_os = "linux")]
346
            BlkParam::Vu(s) => {
×
347
                let p = VuFrontendParam {
348
                    id: DeviceId::Block,
349
                    socket: s.socket,
×
350
                };
351
                vm.add_virtio_dev(format!("vu-net-{index}"), p)
×
352
            }
353
        }
354
        .context(error::CreateDevice)?;
×
355
    }
356
    Ok(())
×
357
}
358

359
pub fn boot(args: BootArgs) -> Result<(), Error> {
×
360
    let objects = parse_objects(&args.objects)?;
×
361
    let hv_config = if let Some(hv_cfg_opt) = args.hypervisor {
×
362
        serde_aco::from_args(&hv_cfg_opt, &objects).context(error::ParseArg { arg: hv_cfg_opt })?
×
363
    } else {
364
        Hypervisor::default()
×
365
    };
366
    let hypervisor = match hv_config {
367
        #[cfg(target_os = "linux")]
368
        Hypervisor::Kvm(kvm_config) => Kvm::new(kvm_config).context(error::Hypervisor)?,
369
        #[cfg(target_os = "macos")]
370
        Hypervisor::Hvf => Hvf {},
371
    };
372
    let coco = match args.coco {
×
373
        None => None,
×
374
        Some(c) => Some(serde_aco::from_args(&c, &objects).context(error::ParseArg { arg: c })?),
×
375
    };
376
    let mem_config = if let Some(s) = args.memory {
×
377
        serde_aco::from_args(&s, &objects).context(error::ParseArg { arg: s })?
×
378
    } else {
379
        #[cfg(target_os = "linux")]
380
        eprintln!(
381
            "Please update the cmd line to --memory size={},backend=memfd",
382
            args.mem_size
383
        );
384
        let size = serde_aco::from_args(&args.mem_size, &objects)
×
385
            .context(error::ParseArg { arg: args.mem_size })?;
×
386
        MemConfig {
387
            size,
388
            #[cfg(target_os = "linux")]
389
            backend: MemBackend::Memfd,
390
            #[cfg(not(target_os = "linux"))]
391
            backend: MemBackend::Anonymous,
392
            ..Default::default()
393
        }
394
    };
395
    let board_config = BoardConfig {
396
        mem: mem_config,
397
        num_cpu: args.num_cpu,
×
398
        coco,
399
    };
400
    let vm = Machine::new(hypervisor, board_config).context(error::CreateVm)?;
×
401
    #[cfg(target_arch = "x86_64")]
402
    vm.add_com1().context(error::CreateDevice)?;
403
    #[cfg(target_arch = "aarch64")]
404
    vm.add_pl011().context(error::CreateDevice)?;
405
    #[cfg(target_arch = "aarch64")]
406
    vm.add_pl031();
407

408
    if args.pvpanic {
×
409
        vm.add_pvpanic().context(error::CreateDevice)?;
×
410
    }
411

412
    #[cfg(target_arch = "x86_64")]
413
    if args.firmware.is_some() || !args.fw_cfgs.is_empty() {
414
        let params = args
415
            .fw_cfgs
416
            .into_iter()
417
            .map(|s| serde_aco::from_args(&s, &objects).context(error::ParseArg { arg: s }))
418
            .collect::<Result<Vec<_>, _>>()?;
419
        let fw_cfg = vm
420
            .add_fw_cfg(params.into_iter())
421
            .context(error::CreateDevice)?;
422
        let mut dev = fw_cfg.lock();
423

424
        if let Some(kernel) = &args.kernel {
425
            dev.add_kernel_data(File::open(kernel).context(error::OpenFile { path: kernel })?)
426
                .context(error::FwCfg)?
427
        }
428
        if let Some(initramfs) = &args.initramfs {
429
            dev.add_initramfs_data(
430
                File::open(initramfs).context(error::OpenFile { path: initramfs })?,
431
            )
432
            .context(error::FwCfg)?;
433
        }
434
        if let Some(cmdline) = &args.cmd_line {
435
            let Ok(cmdline_c) = CString::new(cmdline.as_str()) else {
436
                return error::CreateCString {
437
                    s: cmdline.to_owned(),
438
                }
439
                .fail();
440
            };
441
            dev.add_kernel_cmdline(cmdline_c);
442
        }
443
    };
444

445
    if args.entropy {
×
446
        vm.add_virtio_dev("virtio-entropy", EntropyParam::default())
×
447
            .context(error::CreateDevice)?;
×
448
    }
449
    add_net(&vm, args.net, &objects)?;
×
450
    add_blk(&vm, args.blk, &objects)?;
×
451
    for (index, fs) in args.fs.into_iter().enumerate() {
×
452
        let param: FsParam =
×
453
            serde_aco::from_args(&fs, &objects).context(error::ParseArg { arg: fs })?;
454
        match param {
×
455
            FsParam::Dir(p) => vm.add_virtio_dev(format!("virtio-fs-{index}"), p),
×
456
            #[cfg(target_os = "linux")]
457
            FsParam::Vu(p) => vm.add_virtio_dev(format!("vu-fs-{index}"), p),
458
        }
459
        .context(error::CreateDevice)?;
×
460
    }
461
    if let Some(vsock) = args.vsock {
×
462
        let param =
×
463
            serde_aco::from_args(&vsock, &objects).context(error::ParseArg { arg: vsock })?;
464
        match param {
×
465
            #[cfg(target_os = "linux")]
466
            VsockParam::Vhost(p) => vm
467
                .add_virtio_dev("vhost-vsock", p)
468
                .context(error::CreateDevice)?,
469
            VsockParam::Uds(p) => vm
×
470
                .add_virtio_dev("uds-vsock", p)
×
471
                .context(error::CreateDevice)?,
×
472
        };
473
    }
474
    if let Some(balloon) = args.balloon {
×
475
        let param: BalloonParam =
×
476
            serde_aco::from_args(&balloon, &objects).context(error::ParseArg { arg: balloon })?;
477
        vm.add_virtio_dev("virtio-balloon", param)
×
478
            .context(error::CreateDevice)?;
×
479
    }
480

481
    #[cfg(target_os = "linux")]
482
    for ioas in args.vfio_ioas.into_iter() {
483
        let param: IoasParam =
484
            serde_aco::from_args(&ioas, &objects).context(error::ParseArg { arg: ioas })?;
485
        vm.add_vfio_ioas(param).context(error::CreateDevice)?;
486
    }
487
    #[cfg(target_os = "linux")]
488
    for (index, vfio) in args.vfio_cdev.into_iter().enumerate() {
489
        let param: CdevParam =
490
            serde_aco::from_args(&vfio, &objects).context(error::ParseArg { arg: vfio })?;
491
        vm.add_vfio_cdev(format!("vfio-{index}").into(), param)
492
            .context(error::CreateDevice)?;
493
    }
494

495
    #[cfg(target_os = "linux")]
496
    for container in args.vfio_container.into_iter() {
497
        let param: ContainerParam = serde_aco::from_args(&container, &objects)
498
            .context(error::ParseArg { arg: container })?;
499
        vm.add_vfio_container(param).context(error::CreateDevice)?;
500
    }
501
    #[cfg(target_os = "linux")]
502
    for (index, group) in args.vfio_group.into_iter().enumerate() {
503
        let param: GroupParam =
504
            serde_aco::from_args(&group, &objects).context(error::ParseArg { arg: group })?;
505
        vm.add_vfio_devs_in_group(&index.to_string(), param)
506
            .context(error::CreateDevice)?;
507
    }
508

509
    let payload = if let Some(fw) = args.firmware {
×
510
        Some(Payload {
×
511
            executable: fw,
512
            exec_type: ExecType::Firmware,
513
            initramfs: None,
×
514
            cmd_line: None,
×
515
        })
516
    } else if let Some(kernel) = args.kernel {
×
517
        Some(Payload {
×
518
            exec_type: ExecType::Linux,
519
            executable: kernel,
520
            initramfs: args.initramfs,
×
521
            cmd_line: args.cmd_line,
×
522
        })
523
    } else {
524
        #[cfg(target_arch = "x86_64")]
525
        if let Some(pvh_kernel) = args.pvh {
526
            Some(Payload {
527
                executable: pvh_kernel,
528
                exec_type: ExecType::Pvh,
529
                initramfs: args.initramfs,
530
                cmd_line: args.cmd_line,
531
            })
532
        } else {
533
            None
534
        }
535
        #[cfg(not(target_arch = "x86_64"))]
536
        None
537
    };
538
    if let Some(payload) = payload {
×
539
        vm.add_payload(payload);
×
540
    }
541

542
    vm.boot().context(error::BootVm)?;
×
543
    vm.wait().context(error::WaitVm)?;
×
544
    Ok(())
×
545
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc