237 lines
5.4 KiB
Rust
237 lines
5.4 KiB
Rust
#![feature(let_chains)]
|
|
|
|
use std::{
|
|
fmt::Debug,
|
|
path::{Path, PathBuf},
|
|
process::Command,
|
|
};
|
|
|
|
use device::{QemuDevice, QemuSerialTarget};
|
|
|
|
pub mod aarch64;
|
|
pub mod i386;
|
|
pub mod x86_64;
|
|
|
|
pub mod device;
|
|
|
|
pub trait Architecture: IntoArgs {
|
|
type MachineType: IntoArgs;
|
|
type CpuType: IntoArgs;
|
|
type ImageType: IntoArgs;
|
|
|
|
const DEFAULT_COMMAND: &'static str;
|
|
}
|
|
|
|
pub trait IntoArgs {
|
|
fn add_args(&self, command: &mut Command);
|
|
}
|
|
|
|
impl<T: IntoArgs> IntoArgs for Option<T> {
|
|
fn add_args(&self, command: &mut Command) {
|
|
if let Some(val) = self {
|
|
val.add_args(command);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct QemuCommon {
|
|
devices: Vec<QemuDevice>,
|
|
serial: Option<QemuSerialTarget>,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Qemu<A: Architecture> {
|
|
binary_override: Option<PathBuf>,
|
|
common: QemuCommon,
|
|
cpu: Option<A::CpuType>,
|
|
machine: Option<A::MachineType>,
|
|
boot_image: Option<A::ImageType>,
|
|
cdrom: Option<PathBuf>,
|
|
no_display: bool,
|
|
memory_megabytes: Option<usize>,
|
|
smp: Option<usize>,
|
|
arch: A,
|
|
}
|
|
|
|
impl Qemu<aarch64::QemuAArch64> {
|
|
pub fn new_aarch64() -> Self {
|
|
Qemu::new(aarch64::QemuAArch64)
|
|
}
|
|
}
|
|
|
|
impl Qemu<x86_64::QemuX86_64> {
|
|
pub fn new_x86_64() -> Self {
|
|
Qemu::new(x86_64::QemuX86_64::default())
|
|
}
|
|
|
|
pub fn with_bios_image(&mut self, image: PathBuf) -> &mut Self {
|
|
self.arch.bios_image = Some(image);
|
|
self
|
|
}
|
|
|
|
pub fn with_boot_slot(&mut self, slot: char) -> &mut Self {
|
|
self.arch.boot_slot = Some(slot);
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Qemu<i386::QemuI386> {
|
|
pub fn new_i386() -> Self {
|
|
Qemu::new(i386::QemuI386::default())
|
|
}
|
|
}
|
|
|
|
impl<A: Architecture> Qemu<A> {
|
|
pub fn new(arch: A) -> Self {
|
|
Self {
|
|
binary_override: None,
|
|
common: QemuCommon::default(),
|
|
|
|
memory_megabytes: None,
|
|
no_display: false,
|
|
cpu: None,
|
|
machine: None,
|
|
boot_image: None,
|
|
cdrom: None,
|
|
smp: None,
|
|
arch,
|
|
}
|
|
}
|
|
|
|
pub fn with_device(&mut self, device: QemuDevice) -> &mut Self {
|
|
self.common.devices.push(device);
|
|
self
|
|
}
|
|
|
|
pub fn with_memory_megabytes(&mut self, value: usize) -> &mut Self {
|
|
self.memory_megabytes = Some(value);
|
|
self
|
|
}
|
|
|
|
pub fn with_serial(&mut self, serial: QemuSerialTarget) -> &mut Self {
|
|
self.common.serial = Some(serial);
|
|
self
|
|
}
|
|
|
|
pub fn with_boot_image(&mut self, image: A::ImageType) -> &mut Self {
|
|
self.boot_image = Some(image);
|
|
self
|
|
}
|
|
|
|
pub fn with_cdrom<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
|
|
self.cdrom = Some(path.as_ref().into());
|
|
self
|
|
}
|
|
|
|
pub fn with_cpu(&mut self, cpu: A::CpuType) -> &mut Self {
|
|
self.cpu = Some(cpu);
|
|
self
|
|
}
|
|
|
|
pub fn with_machine(&mut self, machine: A::MachineType) -> &mut Self {
|
|
self.machine = Some(machine);
|
|
self
|
|
}
|
|
|
|
pub fn with_smp(&mut self, smp: usize) -> &mut Self {
|
|
assert_ne!(smp, 0);
|
|
self.smp = Some(smp);
|
|
self
|
|
}
|
|
|
|
pub fn disable_display(&mut self) -> &mut Self {
|
|
self.no_display = true;
|
|
self
|
|
}
|
|
|
|
pub fn override_qemu<S: Into<PathBuf>>(&mut self, qemu: S) -> &mut Self {
|
|
self.binary_override = Some(qemu.into());
|
|
self
|
|
}
|
|
|
|
pub fn into_command(self) -> Command {
|
|
let qemu = self
|
|
.binary_override
|
|
.clone()
|
|
.unwrap_or_else(|| A::DEFAULT_COMMAND.into());
|
|
|
|
let mut command = Command::new(qemu);
|
|
|
|
self.add_args(&mut command);
|
|
|
|
command
|
|
}
|
|
}
|
|
|
|
impl IntoArgs for QemuCommon {
|
|
fn add_args(&self, command: &mut Command) {
|
|
self.serial.add_args(command);
|
|
}
|
|
}
|
|
|
|
impl<A: Architecture> IntoArgs for Qemu<A> {
|
|
fn add_args(&self, command: &mut Command) {
|
|
self.common.add_args(command);
|
|
|
|
for device in &self.common.devices {
|
|
device.add_args(command);
|
|
}
|
|
|
|
if let Some(mem) = self.memory_megabytes {
|
|
command.args(["-m", &format!("{}m", mem)]);
|
|
}
|
|
|
|
self.machine.add_args(command);
|
|
self.cpu.add_args(command);
|
|
self.boot_image.add_args(command);
|
|
|
|
if let Some(cdrom) = &self.cdrom {
|
|
command.args(["-cdrom", &format!("{}", cdrom.display())]);
|
|
}
|
|
|
|
if self.no_display {
|
|
command.args(["-display", "none"]);
|
|
}
|
|
|
|
if let Some(smp) = self.smp {
|
|
command.args(["-smp", &format!("{}", smp)]);
|
|
}
|
|
|
|
self.arch.add_args(command);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{device::QemuSerialTarget, Qemu};
|
|
|
|
#[test]
|
|
fn empty() {
|
|
let builder = Qemu::new_aarch64();
|
|
let command = builder.into_command();
|
|
assert_eq!(command.get_program(), "qemu-system-aarch64");
|
|
assert_eq!(command.get_args().len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn qemu_override() {
|
|
let mut qemu = Qemu::new_aarch64();
|
|
qemu.override_qemu("test-qemu");
|
|
let command = qemu.into_command();
|
|
assert_eq!(command.get_program(), "test-qemu");
|
|
}
|
|
|
|
#[test]
|
|
fn common_options() {
|
|
let mut qemu = Qemu::new_aarch64();
|
|
qemu.with_serial(QemuSerialTarget::MonStdio);
|
|
let command = qemu.into_command();
|
|
|
|
assert_eq!(
|
|
command.get_args().collect::<Vec<_>>(),
|
|
vec!["-serial", "mon:stdio"]
|
|
);
|
|
}
|
|
}
|