boot.rs56.12%
1
// Copyright 2025 Google LLC2
//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 at6
//7
// https://www.apache.org/licenses/LICENSE-2.08
//9
// Unless required by applicable law or agreed to in writing, software10
// 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 and13
// limitations under the License.14
15
mod config;16
17
use std::collections::HashMap;18
use std::ffi::CString;19
use std::mem;20
use std::path::{Path, PathBuf};21
22
use alioth::board::{BoardConfig, CpuConfig};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
#[cfg(target_os = "linux")]29
use alioth::hv::Kvm;30
use alioth::hv::{Coco, HvConfig, Hypervisor};31
use alioth::loader::{Executable, 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
#[cfg(target_os = "linux")]41
use alioth::virtio::vu::frontend::VuFrontendParam;42
use alioth::virtio::worker::WorkerApi;43
use alioth::vm::Machine;44
use clap::Args;45
use serde_aco::help_text;46
use snafu::{ResultExt, Snafu};47
48
use crate::objects::{DOC_OBJECTS, parse_objects};49
50
use self::config::{BlkParam, Config, FsParam, NetParam, VsockParam};51
52
#[trace_error]53
#[derive(Snafu, DebugTrace)]54
#[snafu(module, context(suffix(false)))]55
pub enum Error {56
#[snafu(display("Failed to parse {arg}"))]57
ParseArg {58
arg: String,59
error: serde_aco::Error,60
},61
#[snafu(display("Failed to parse objects"), context(false))]62
ParseObjects { source: crate::objects::Error },63
#[cfg(target_os = "linux")]64
#[snafu(display("Failed to access system hypervisor"))]65
Hypervisor { source: alioth::hv::Error },66
#[snafu(display("Failed to create a VM"))]67
CreateVm { source: alioth::vm::Error },68
#[snafu(display("Failed to boot a VM"))]69
BootVm { source: alioth::vm::Error },70
#[snafu(display("VM did not shutdown peacefully"))]71
WaitVm { source: alioth::vm::Error },72
}73
74
#[derive(Args, Debug, Clone, Default)]75
#[command(arg_required_else_help = true, alias("run"))]76
pub struct BootArgs {77
#[arg(long, help(78
help_text::<HvConfig>("Specify the Hypervisor to run on.")79
), value_name = "HV")]80
hypervisor: Option<String>,81
82
/// Path to a Linux kernel image.83
#[arg(short, long, value_name = "PATH")]84
kernel: Option<Box<Path>>,85
86
/// Path to an ELF kernel with PVH note.87
#[cfg(target_arch = "x86_64")]88
#[arg(long, value_name = "PATH")]89
pvh: Option<Box<Path>>,90
91
/// Path to a firmware image.92
#[arg(long, short, value_name = "PATH")]93
firmware: Option<Box<Path>>,94
95
/// Command line to pass to the kernel, e.g. `console=ttyS0`.96
#[arg(short, long, alias = "cmd-line", value_name = "ARGS")]97
cmdline: Option<CString>,98
99
/// Path to an initramfs image.100
#[arg(short, long, value_name = "PATH")]101
initramfs: Option<Box<Path>>,102
103
/// DEPRECATED: Use --cpu instead.104
#[arg(long, default_value_t = 1)]105
num_cpu: u16,106
107
#[arg(short('p'), long, help(108
help_text::<CpuConfig>("Configure the VCPUs of the guest.")109
))]110
cpu: Option<Box<str>>,111
112
/// DEPRECATED: Use --memory instead.113
#[arg(long, default_value = "1G")]114
mem_size: String,115
116
#[arg(short, long, help(117
help_text::<MemConfig>("Specify the memory of the guest.")118
))]119
memory: Option<String>,120
121
/// Add a pvpanic device.122
#[arg(long)]123
pvpanic: bool,124
125
#[cfg(target_arch = "x86_64")]126
#[arg(long, help(127
help_text::<FwCfgItemParam>("Add an extra item to the fw_cfg device.")128
), value_name = "ITEM")]129
fw_cfg: Vec<String>,130
131
/// Add a VirtIO entropy device.132
#[arg(long)]133
entropy: bool,134
135
#[arg(long, help(136
help_text::<NetParam>("Add a VirtIO net device.")137
))]138
net: Vec<String>,139
140
#[arg(long, help(141
help_text::<BlkParam>("Add a VirtIO block device.")142
))]143
blk: Vec<String>,144
145
#[arg(long, help(146
help_text::<Coco>("Enable confidential compute supported by host platform.")147
))]148
coco: Option<String>,149
150
#[arg(long, help(151
help_text::<FsParam>("Add a VirtIO filesystem device.")152
))]153
fs: Vec<String>,154
155
#[arg(long, help(156
help_text::<VsockParam>("Add a VirtIO vsock device.")157
))]158
vsock: Option<String>,159
160
#[cfg(target_os = "linux")]161
#[arg(long, help(help_text::<CdevParam>(162
"Assign a host PCI device to the guest using IOMMUFD API."163
) ))]164
vfio_cdev: Vec<String>,165
166
#[cfg(target_os = "linux")]167
#[arg(long, help(help_text::<IoasParam>("Create a new IO address space.")))]168
vfio_ioas: Vec<String>,169
170
#[cfg(target_os = "linux")]171
#[arg(long, help(help_text::<GroupParam>(172
"Assign a host PCI device to the guest using legacy VFIO API."173
)))]174
vfio_group: Vec<String>,175
176
#[cfg(target_os = "linux")]177
#[arg(long, help(help_text::<ContainerParam>("Add a new VFIO container.")))]178
vfio_container: Vec<String>,179
180
#[arg(long)]181
#[arg(long, help(help_text::<BalloonParam>("Add a VirtIO balloon device.")))]182
balloon: Option<String>,183
184
#[arg(short, long("object"), help = DOC_OBJECTS, value_name = "OBJECT")]185
objects: Vec<String>,186
}187
188
fn parse_net_arg(arg: &str, objects: &HashMap<&str, &str>) -> serde_aco::Result<NetParam> {8x189
#[cfg(target_os = "linux")]190
if let Ok(param) = serde_aco::from_args(arg, objects) {6x191
Ok(param)2x192
} else {193
let param = serde_aco::from_args(arg, objects)?;4x194
Ok(NetParam::Tap(param))4x195
}196
197
#[cfg(target_os = "macos")]198
serde_aco::from_args(arg, objects)2x199
}8x200
201
fn parse_blk_arg(arg: &str, objects: &HashMap<&str, &str>) -> BlkParam {17x202
if let Ok(param) = serde_aco::from_args(arg, objects) {17x203
param11x204
} else if let Ok(param) = serde_aco::from_args(arg, objects) {6x205
BlkParam::File(param)3x206
} else {207
eprintln!("Please update the cmd line to --blk file,path={arg}");3x208
BlkParam::File(BlkFileParam {3x209
path: PathBuf::from(arg).into(),3x210
readonly: false,3x211
api: WorkerApi::Mio,3x212
})3x213
}214
}17x215
216
fn parse_mem_arg(8x217
arg: Option<String>,8x218
mem_size: String,8x219
objects: &HashMap<&str, &str>,8x220
) -> Result<MemConfig, Error> {8x221
let config = if let Some(arg) = arg {8x222
serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?5x223
} else {224
#[cfg(target_os = "linux")]225
eprintln!("Please update the cmd line to --memory size={mem_size},backend=memfd");2x226
MemConfig {227
size: serde_aco::from_args(&mem_size, objects)3x228
.context(error::ParseArg { arg: mem_size })?,3x229
#[cfg(target_os = "linux")]230
backend: MemBackend::Memfd,2x231
#[cfg(not(target_os = "linux"))]232
backend: MemBackend::Anonymous,1x233
..Default::default()3x234
}235
};236
Ok(config)8x237
}8x238
239
fn parse_cpu_arg(9x240
arg: Option<Box<str>>,9x241
num_cpu: u16,9x242
objects: &HashMap<&str, &str>,9x243
) -> Result<CpuConfig, Error> {9x244
let config = if let Some(arg) = arg {9x245
serde_aco::from_args(&arg, objects).context(error::ParseArg { arg })?6x246
} else {247
eprintln!("Please update the cmd line to --cpu count={num_cpu}");3x248
CpuConfig {3x249
count: num_cpu,3x250
..Default::default()3x251
}3x252
};253
Ok(config)9x254
}9x255
256
fn parse_payload_arg(args: &mut BootArgs) -> Payload {7x257
let mut payload = Payload {7x258
firmware: args.firmware.take(),7x259
initramfs: args.initramfs.take(),7x260
cmdline: args.cmdline.take(),7x261
..Default::default()7x262
};7x263
payload.executable = args.kernel.take().map(Executable::Linux);7x264
#[cfg(target_arch = "x86_64")]265
if payload.executable.is_none() {3x266
payload.executable = args.pvh.take().map(Executable::Pvh);1x267
}2x268
payload7x269
}7x270
271
fn parse_args(mut args: BootArgs, objects: HashMap<&str, &str>) -> Result<Config, Error> {3x272
let payload = parse_payload_arg(&mut args);3x273
274
let mut board_config = BoardConfig::default();3x275
if let Some(arg) = args.coco {3x276
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;277
board_config.coco = Some(param);278
};3x279
board_config.mem = parse_mem_arg(args.memory, args.mem_size, &objects)?;3x280
board_config.cpu = parse_cpu_arg(args.cpu, args.num_cpu, &objects)?;3x281
282
let mut config = Config {3x283
board: board_config,3x284
pvpanic: args.pvpanic,3x285
payload,3x286
..Default::default()3x287
};3x288
289
#[cfg(target_arch = "x86_64")]290
for arg in args.fw_cfg {2x291
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;2x292
config.fw_cfg.push(param);2x293
}294
295
if args.entropy {3x296
config.entropy = Some(EntropyParam::default());3x297
}3x298
299
for arg in args.net {5x300
let param = parse_net_arg(&arg, &objects).context(error::ParseArg { arg })?;5x301
config.net.push(param);5x302
}303
304
for arg in args.blk {6x305
let param = parse_blk_arg(&arg, &objects);6x306
config.blk.push(param);6x307
}6x308
309
for arg in args.fs {5x310
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;5x311
config.fs.push(param);5x312
}313
314
if let Some(arg) = args.vsock {3x315
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;3x316
config.vsock = Some(param);3x317
}318
319
if let Some(arg) = args.balloon {3x320
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;3x321
config.balloon = Some(param);3x322
}323
324
#[cfg(target_os = "linux")]325
for arg in args.vfio_ioas {2x326
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;2x327
config.vfio_ioas.push(param);2x328
}329
#[cfg(target_os = "linux")]330
for arg in args.vfio_cdev {2x331
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;2x332
config.vfio_cdev.push(param);2x333
}334
#[cfg(target_os = "linux")]335
for arg in args.vfio_container {2x336
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;2x337
config.vfio_container.push(param);2x338
}339
#[cfg(target_os = "linux")]340
for arg in args.vfio_group {2x341
let param = serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?;2x342
config.vfio_group.push(param);2x343
}344
345
Ok(config)3x346
}3x347
348
fn create<H: Hypervisor>(hypervisor: &H, config: Config) -> Result<Machine<H>, alioth::vm::Error> {349
let vm = Machine::new(hypervisor, config.board)?;350
351
#[cfg(target_arch = "x86_64")]352
vm.add_com1()?;353
#[cfg(target_arch = "aarch64")]354
vm.add_pl011()?;355
#[cfg(target_arch = "aarch64")]356
vm.add_pl031();357
358
if config.pvpanic {359
vm.add_pvpanic()?;360
}361
362
#[cfg(target_arch = "x86_64")]363
if config.payload.firmware.is_some() || !config.fw_cfg.is_empty() {364
vm.add_fw_cfg(config.fw_cfg.into_iter())?;365
};366
367
if let Some(param) = config.entropy {368
vm.add_virtio_dev("virtio-entropy", param)?;369
}370
371
for (index, param) in config.net.into_iter().enumerate() {372
match param {373
#[cfg(target_os = "linux")]374
NetParam::Tap(tap_param) => vm.add_virtio_dev(format!("virtio-net-{index}"), tap_param),375
#[cfg(target_os = "linux")]376
NetParam::Vu(sock) => {377
let param = VuFrontendParam {378
id: DeviceId::NET,379
socket: sock.socket,380
};381
vm.add_virtio_dev(format!("vu-net-{index}"), param)382
}383
#[cfg(target_os = "macos")]384
NetParam::Vmnet(p) => vm.add_virtio_dev(format!("virtio-net-{index}"), p),385
}?;386
}387
388
for (index, param) in config.blk.into_iter().enumerate() {389
match param {390
BlkParam::File(p) => vm.add_virtio_dev(format!("virtio-blk-{index}"), p),391
#[cfg(target_os = "linux")]392
BlkParam::Vu(s) => {393
let p = VuFrontendParam {394
id: DeviceId::BLOCK,395
socket: s.socket,396
};397
vm.add_virtio_dev(format!("vu-net-{index}"), p)398
}399
}?;400
}401
402
for (index, param) in config.fs.into_iter().enumerate() {403
match param {404
FsParam::Dir(p) => vm.add_virtio_dev(format!("virtio-fs-{index}"), p),405
#[cfg(target_os = "linux")]406
FsParam::Vu(p) => vm.add_virtio_dev(format!("vu-fs-{index}"), p),407
}?;408
}409
410
if let Some(param) = config.vsock {411
match param {412
#[cfg(target_os = "linux")]413
VsockParam::Vhost(p) => vm.add_virtio_dev("vhost-vsock", p),414
VsockParam::Uds(p) => vm.add_virtio_dev("uds-vsock", p),415
#[cfg(target_os = "linux")]416
VsockParam::Vu(s) => {417
let p = VuFrontendParam {418
id: DeviceId::SOCKET,419
socket: s.socket,420
};421
vm.add_virtio_dev("vu-vsock", p)422
}423
}?;424
}425
426
if let Some(param) = config.balloon {427
vm.add_virtio_dev("virtio-balloon", param)?;428
}429
430
#[cfg(target_os = "linux")]431
for param in config.vfio_ioas.into_iter() {432
vm.add_vfio_ioas(param)?;433
}434
#[cfg(target_os = "linux")]435
for (index, param) in config.vfio_cdev.into_iter().enumerate() {436
vm.add_vfio_cdev(format!("vfio-{index}").into(), param)?;437
}438
439
#[cfg(target_os = "linux")]440
for param in config.vfio_container.into_iter() {441
vm.add_vfio_container(param)?;442
}443
#[cfg(target_os = "linux")]444
for (index, param) in config.vfio_group.into_iter().enumerate() {445
vm.add_vfio_devs_in_group(&index.to_string(), param)?;446
}447
448
vm.add_payload(config.payload);449
450
Ok(vm)451
}452
453
pub fn boot(mut args: BootArgs) -> Result<(), Error> {454
let object_args = mem::take(&mut args.objects);455
let objects = parse_objects(&object_args)?;456
457
let hv_config = if let Some(arg) = args.hypervisor.take() {458
serde_aco::from_args(&arg, &objects).context(error::ParseArg { arg })?459
} else {460
HvConfig::default()461
};462
let hypervisor = match hv_config {463
#[cfg(target_os = "linux")]464
HvConfig::Kvm(kvm_config) => Kvm::new(kvm_config).context(error::Hypervisor)?,465
#[cfg(target_os = "macos")]466
HvConfig::Hvf => Hvf {},467
};468
469
let config = parse_args(args, objects)?;470
471
let vm = create(&hypervisor, config).context(error::CreateVm)?;472
473
vm.boot().context(error::BootVm)?;474
vm.wait().context(error::WaitVm)?;475
Ok(())476
}477
478
#[cfg(test)]479
#[path = "boot_test.rs"]480
mod tests;481