From 2e236c53c8a0aa85b6f0fb78867adcf7d6a82cc5 Mon Sep 17 00:00:00 2001 From: Rob Bradford Date: Fri, 4 Jun 2021 11:42:36 +0000 Subject: [PATCH] vfio_user: Implement client side support Implement (most) of the client side (i.e. VMM side) of the vfio-user protocol: https://github.com/nutanix/libvfio-user/blob/master/docs/vfio-user.rst Items that are not implemented (because they are optimisations or unused due to alternative solutions: * VFIO_USER_DMA_READ/WRITE - this is a way for the server to read guest memory if the guest memory is not shared by fd where the client doesn't support it. However since we do support sharing the memory by fd this is not required. * VFIO_USER_GET_REGION_IO_FDS - an optimisation to bypass the VMM by having KVM talk directly to the backend using ioregionfd * VFIO_USER_DIRTY_PAGES - for the implementation of live migration Signed-off-by: Rob Bradford --- Cargo.lock | 67 +++-- Cargo.toml | 1 + vfio_user/Cargo.toml | 20 ++ vfio_user/src/lib.rs | 675 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 743 insertions(+), 20 deletions(-) create mode 100644 vfio_user/Cargo.toml create mode 100644 vfio_user/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 80867e074..a62fd14b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,7 +6,7 @@ version = 3 name = "acpi_tables" version = "0.1.0" dependencies = [ - "vm-memory", + "vm-memory 0.6.0", ] [[package]] @@ -78,7 +78,7 @@ dependencies = [ "versionize", "versionize_derive", "vm-fdt", - "vm-memory", + "vm-memory 0.6.0", "vm-migration", "vmm-sys-util", ] @@ -142,7 +142,7 @@ dependencies = [ "versionize", "versionize_derive", "virtio-bindings", - "vm-memory", + "vm-memory 0.6.0", "vm-virtio", "vmm-sys-util", ] @@ -209,7 +209,7 @@ dependencies = [ "signal-hook", "test_infra", "thiserror", - "vm-memory", + "vm-memory 0.6.0", "vmm", "vmm-sys-util", "wait-timeout", @@ -255,7 +255,7 @@ dependencies = [ "versionize", "versionize_derive", "vm-device", - "vm-memory", + "vm-memory 0.6.0", "vm-migration", "vmm-sys-util", ] @@ -397,7 +397,7 @@ dependencies = [ "serde_derive", "serde_json", "thiserror", - "vm-memory", + "vm-memory 0.6.0", "vmm-sys-util", ] @@ -501,7 +501,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c819cc8275b0f2c1ed9feec455ca288b45d82932384a6a5f7a86812ee3427459" dependencies = [ - "vm-memory", + "vm-memory 0.6.0", ] [[package]] @@ -592,7 +592,7 @@ dependencies = [ "versionize", "versionize_derive", "virtio-bindings", - "vm-memory", + "vm-memory 0.6.0", "vm-virtio", "vmm-sys-util", ] @@ -663,7 +663,7 @@ dependencies = [ "vfio-ioctls", "vm-allocator", "vm-device", - "vm-memory", + "vm-memory 0.6.0", "vm-migration", "vmm-sys-util", ] @@ -1116,7 +1116,23 @@ dependencies = [ "kvm-ioctls", "log", "vfio-bindings", - "vm-memory", + "vm-memory 0.6.0", + "vmm-sys-util", +] + +[[package]] +name = "vfio_user" +version = "0.1.0" +dependencies = [ + "anyhow", + "libc", + "log", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "vfio-bindings", + "vm-memory 0.5.0", "vmm-sys-util", ] @@ -1128,7 +1144,7 @@ checksum = "8a6b90237e10f1a61b35fba73885c3567e1a5a8c40d44daae335f7710210a7dc" dependencies = [ "bitflags", "libc", - "vm-memory", + "vm-memory 0.6.0", "vmm-sys-util", ] @@ -1141,7 +1157,7 @@ dependencies = [ "log", "vhost", "virtio-bindings", - "vm-memory", + "vm-memory 0.6.0", "vm-virtio", "vmm-sys-util", ] @@ -1161,7 +1177,7 @@ dependencies = [ "vhost", "vhost_user_backend", "virtio-bindings", - "vm-memory", + "vm-memory 0.6.0", "vmm-sys-util", ] @@ -1179,7 +1195,7 @@ dependencies = [ "vhost", "vhost_user_backend", "virtio-bindings", - "vm-memory", + "vm-memory 0.6.0", "vmm-sys-util", ] @@ -1216,7 +1232,7 @@ dependencies = [ "virtio-bindings", "vm-allocator", "vm-device", - "vm-memory", + "vm-memory 0.6.0", "vm-migration", "vm-virtio", "vmm-sys-util", @@ -1228,7 +1244,7 @@ version = "0.1.0" dependencies = [ "arch", "libc", - "vm-memory", + "vm-memory 0.6.0", ] [[package]] @@ -1241,7 +1257,7 @@ dependencies = [ "serde_json", "thiserror", "vfio-ioctls", - "vm-memory", + "vm-memory 0.6.0", "vmm-sys-util", ] @@ -1250,6 +1266,17 @@ name = "vm-fdt" version = "0.1.0" source = "git+https://github.com/rust-vmm/vm-fdt?branch=master#af59838e5df826cd3153d92ed1546f0b2cc454f7" +[[package]] +name = "vm-memory" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625f401b1b8b3ac3d43f53903cd138cfe840bd985f8581e553027b31d2bb8ae8" +dependencies = [ + "arc-swap", + "libc", + "winapi", +] + [[package]] name = "vm-memory" version = "0.6.0" @@ -1272,7 +1299,7 @@ dependencies = [ "thiserror", "versionize", "versionize_derive", - "vm-memory", + "vm-memory 0.6.0", ] [[package]] @@ -1281,7 +1308,7 @@ version = "0.1.0" dependencies = [ "log", "virtio-bindings", - "vm-memory", + "vm-memory 0.6.0", ] [[package]] @@ -1322,7 +1349,7 @@ dependencies = [ "virtio-devices", "vm-allocator", "vm-device", - "vm-memory", + "vm-memory 0.6.0", "vm-migration", "vm-virtio", "vmm-sys-util", diff --git a/Cargo.toml b/Cargo.toml index 8b604b195..9d9bc428c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,7 @@ members = [ "pci", "qcow", "rate_limiter", + "vfio_user", "vhost_user_backend", "vhost_user_block", "vhost_user_net", diff --git a/vfio_user/Cargo.toml b/vfio_user/Cargo.toml new file mode 100644 index 000000000..75afc5487 --- /dev/null +++ b/vfio_user/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "vfio_user" +version = "0.1.0" +authors = ["The Cloud Hypervisor Authors"] +edition = "2018" + +[dependencies] +anyhow = "1.0.42" +libc = "0.2.95" +log = "0.4.14" +serde = {version = ">=1.0.27", features = ["rc"] } +serde_derive = ">=1.0.27" +serde_json = ">=1.0.9" +thiserror = "1.0.26" +vm-memory = { version = "0.5.0", features = ["backend-mmap", "backend-atomic"] } +vmm-sys-util = ">=0.3.1" + +[dependencies.vfio-bindings] +version = "0.2.0" +features = ["fam-wrappers"] diff --git a/vfio_user/src/lib.rs b/vfio_user/src/lib.rs new file mode 100644 index 000000000..885455e89 --- /dev/null +++ b/vfio_user/src/lib.rs @@ -0,0 +1,675 @@ +// Copyright © 2021 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +use std::ffi::CString; +use std::io::{IoSlice, Read, Write}; +use std::num::Wrapping; +use std::os::unix::net::UnixStream; +use std::os::unix::prelude::RawFd; +use std::path::Path; +use thiserror::Error; +use vfio_bindings::bindings::vfio::*; +use vm_memory::{ByteValued, FileOffset}; +use vmm_sys_util::sock_ctrl_msg::ScmSocket; + +#[macro_use] +extern crate serde_derive; + +#[macro_use] +extern crate log; + +#[allow(dead_code)] +#[repr(u16)] +#[derive(Clone, Copy, Debug)] +enum Command { + Unknown = 0, + Version = 1, + DmaMap = 2, + DmaUnmap = 3, + DeviceGetInfo = 4, + DeviceGetRegionInfo = 5, + GetRegionIoFds = 6, + GetIrqInfo = 7, + SetIrqs = 8, + RegionRead = 9, + RegionWrite = 10, + DmaRead = 11, + DmaWrite = 12, + DeviceReset = 13, + UserDirtyPages = 14, +} + +impl Default for Command { + fn default() -> Self { + Command::Unknown + } +} + +#[allow(dead_code)] +#[repr(u32)] +#[derive(Clone, Copy, Debug, PartialEq)] +enum HeaderFlags { + Command = 0, + Reply = 1, + NoReply = 1 << 4, + Error = 1 << 5, +} + +impl Default for HeaderFlags { + fn default() -> Self { + HeaderFlags::Command + } +} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct Header { + message_id: u16, + command: Command, + message_size: u32, + flags: u32, + error: u32, +} + +unsafe impl ByteValued for Header {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct Version { + header: Header, + major: u16, + minor: u16, +} +unsafe impl ByteValued for Version {} + +#[derive(Serialize, Deserialize, Debug)] +struct MigrationCapabilities { + pgsize: u32, +} + +const fn default_max_msg_fds() -> u32 { + 1 +} + +const fn default_max_data_xfer_size() -> u32 { + 1048576 +} + +const fn default_migration_capabilities() -> MigrationCapabilities { + MigrationCapabilities { pgsize: 4096 } +} + +#[repr(u32)] +#[derive(Clone, Copy, Debug)] +#[allow(dead_code)] +enum DmaMapFlags { + Unknown = 0, + ReadOnly = 1, + WriteOnly = 2, + ReadWrite = 3, +} + +impl Default for DmaMapFlags { + fn default() -> Self { + Self::Unknown + } +} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct DmaMap { + header: Header, + argsz: u32, + flags: DmaMapFlags, + offset: u64, + address: u64, + size: u64, +} + +unsafe impl ByteValued for DmaMap {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct DmaUnmap { + header: Header, + argsz: u32, + flags: u32, + address: u64, + size: u64, +} + +unsafe impl ByteValued for DmaUnmap {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct DeviceGetInfo { + header: Header, + argsz: u32, + flags: u32, + num_regions: u32, + num_irqs: u32, +} + +unsafe impl ByteValued for DeviceGetInfo {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct DeviceGetRegionInfo { + header: Header, + region_info: vfio_region_info, +} + +unsafe impl ByteValued for DeviceGetRegionInfo {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct RegionAccess { + header: Header, + offset: u64, + region: u32, + count: u32, +} + +unsafe impl ByteValued for RegionAccess {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct GetIrqInfo { + header: Header, + argsz: u32, + flags: u32, + index: u32, + count: u32, +} + +unsafe impl ByteValued for GetIrqInfo {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct SetIrqs { + header: Header, + argsz: u32, + flags: u32, + index: u32, + start: u32, + count: u32, +} + +unsafe impl ByteValued for SetIrqs {} + +#[repr(C)] +#[derive(Default, Clone, Copy, Debug)] +struct DeviceReset { + header: Header, +} + +unsafe impl ByteValued for DeviceReset {} + +#[derive(Serialize, Deserialize, Debug)] +struct Capabilities { + #[serde(default = "default_max_msg_fds")] + max_msg_fds: u32, + #[serde(default = "default_max_data_xfer_size")] + max_data_xfer_size: u32, + #[serde(default = "default_migration_capabilities")] + migration: MigrationCapabilities, +} + +impl Default for Capabilities { + fn default() -> Self { + Self { + max_msg_fds: default_max_msg_fds(), + max_data_xfer_size: default_max_data_xfer_size(), + migration: default_migration_capabilities(), + } + } +} + +pub struct Client { + stream: UnixStream, + next_message_id: Wrapping, + num_irqs: u32, + resettable: bool, + regions: Vec, +} + +#[derive(Debug)] +pub struct Region { + pub flags: u32, + pub index: u32, + pub size: u64, + pub file_offset: Option, +} + +#[derive(Debug)] +pub struct IrqInfo { + pub index: u32, + pub flags: u32, + pub count: u32, +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("Error connecting: {0}")] + Connect(#[source] std::io::Error), + #[error("Error serializing capabilities: {0}")] + SerializeCapabilites(#[source] serde_json::Error), + #[error("Error deserializing capabilities: {0}")] + DeserializeCapabilites(#[source] serde_json::Error), + #[error("Error writing to stream: {0}")] + StreamWrite(#[source] std::io::Error), + #[error("Error reading from stream: {0}")] + StreamRead(#[source] std::io::Error), + #[error("Error writing with file descriptors: {0}")] + SendWithFd(#[source] vmm_sys_util::errno::Error), + #[error("Error reading with file descriptors: {0}")] + ReceiveWithFd(#[source] vmm_sys_util::errno::Error), + #[error("Not a PCI device")] + NotPciDevice, +} + +impl Client { + pub fn new(path: &Path) -> Result { + let stream = UnixStream::connect(path).map_err(Error::Connect)?; + + let mut client = Client { + next_message_id: Wrapping(0), + stream, + num_irqs: 0, + resettable: false, + regions: Vec::new(), + }; + + client.negotiate_version()?; + + client.regions = client.get_regions()?; + + Ok(client) + } + + fn negotiate_version(&mut self) -> Result<(), Error> { + let caps = Capabilities::default(); + + let version_data = serde_json::to_string(&caps).map_err(Error::SerializeCapabilites)?; + + let version = Version { + header: Header { + message_id: self.next_message_id.0, + command: Command::Version, + flags: HeaderFlags::Command as u32, + message_size: (std::mem::size_of::() + version_data.len() + 1) as u32, + ..Default::default() + }, + major: 0, + minor: 1, + }; + info!("Command: {:?}", version); + + let version_data = CString::new(version_data.as_bytes()).unwrap(); + let bufs = vec![ + IoSlice::new(version.as_slice()), + IoSlice::new(version_data.as_bytes_with_nul()), + ]; + + // TODO: Use write_all_vectored() when ready + let _ = self + .stream + .write_vectored(&bufs) + .map_err(Error::StreamWrite)?; + + info!( + "Sent client version information: major = {} minor = {} capabilities = {:?}", + version.major, version.minor, &caps + ); + + self.next_message_id += Wrapping(1); + + let mut server_version: Version = Version::default(); + self.stream + .read_exact(server_version.as_mut_slice()) + .map_err(Error::StreamRead)?; + + info!("Reply: {:?}", server_version); + + let mut server_version_data = Vec::new(); + server_version_data.resize( + server_version.header.message_size as usize - std::mem::size_of::(), + 0, + ); + self.stream + .read_exact(server_version_data.as_mut_slice()) + .map_err(Error::StreamRead)?; + + let server_caps: Capabilities = + serde_json::from_slice(&server_version_data[0..server_version_data.len() - 1]) + .map_err(Error::DeserializeCapabilites)?; + + info!( + "Received server version information: major = {} minor = {} capabilities = {:?}", + server_version.major, server_version.minor, &server_caps + ); + + Ok(()) + } + + pub fn dma_map( + &mut self, + offset: u64, + address: u64, + size: u64, + fd: RawFd, + ) -> Result<(), Error> { + let dma_map = DmaMap { + header: Header { + message_id: self.next_message_id.0, + command: Command::DmaMap, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + argsz: (std::mem::size_of::() - std::mem::size_of::
()) as u32, + flags: DmaMapFlags::ReadWrite, + offset, + address, + size, + }; + info!("Command: {:?}", dma_map); + self.next_message_id += Wrapping(1); + self.stream + .send_with_fd(dma_map.as_slice(), fd) + .map_err(Error::SendWithFd)?; + + let mut reply = Header::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + + Ok(()) + } + + pub fn dma_unmap(&mut self, address: u64, size: u64) -> Result<(), Error> { + let dma_unmap = DmaUnmap { + header: Header { + message_id: self.next_message_id.0, + command: Command::DmaUnmap, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + argsz: (std::mem::size_of::() - std::mem::size_of::
()) as u32, + flags: 0, + address, + size, + }; + info!("Command: {:?}", dma_unmap); + self.next_message_id += Wrapping(1); + self.stream + .write_all(dma_unmap.as_slice()) + .map_err(Error::StreamWrite)?; + + let mut reply = Header::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + + Ok(()) + } + + pub fn reset(&mut self) -> Result<(), Error> { + let reset = DeviceReset { + header: Header { + message_id: self.next_message_id.0, + command: Command::DeviceReset, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + }; + info!("Command: {:?}", reset); + self.next_message_id += Wrapping(1); + self.stream + .write_all(reset.as_slice()) + .map_err(Error::StreamWrite)?; + + let mut reply = Header::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + + Ok(()) + } + + fn get_regions(&mut self) -> Result, Error> { + let get_info = DeviceGetInfo { + header: Header { + message_id: self.next_message_id.0, + command: Command::DeviceGetInfo, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + argsz: std::mem::size_of::() as u32, + ..Default::default() + }; + info!("Command: {:?}", get_info); + self.next_message_id += Wrapping(1); + + self.stream + .write_all(get_info.as_slice()) + .map_err(Error::StreamWrite)?; + + let mut reply = DeviceGetInfo::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + self.num_irqs = reply.num_irqs; + + if reply.flags & VFIO_DEVICE_FLAGS_PCI != VFIO_DEVICE_FLAGS_PCI { + return Err(Error::NotPciDevice); + } + + self.resettable = reply.flags & VFIO_DEVICE_FLAGS_RESET != VFIO_DEVICE_FLAGS_RESET; + + let num_regions = reply.num_regions; + let mut regions = Vec::new(); + for index in 0..num_regions { + let get_region_info = DeviceGetRegionInfo { + header: Header { + message_id: self.next_message_id.0, + command: Command::DeviceGetRegionInfo, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + region_info: vfio_region_info { + argsz: 1024, // Arbitrary max size + index, + ..Default::default() + }, + }; + info!("Command: {:?}", get_region_info); + self.next_message_id += Wrapping(1); + + self.stream + .write_all(get_region_info.as_slice()) + .map_err(Error::StreamWrite)?; + + let mut reply = DeviceGetRegionInfo::default(); + info!("Reply: {:?}", reply); + let (_, fd) = self + .stream + .recv_with_fd(reply.as_mut_slice()) + .map_err(Error::ReceiveWithFd)?; + + regions.push(Region { + flags: reply.region_info.flags, + index: reply.region_info.index, + size: reply.region_info.size, + file_offset: fd.map(|fd| FileOffset::new(fd, reply.region_info.offset)), + }); + + // TODO: Handle region with capabilities + let mut _cap_data = Vec::with_capacity( + reply.header.message_size as usize - std::mem::size_of::(), + ); + _cap_data.resize(_cap_data.capacity(), 0u8); + self.stream + .read_exact(_cap_data.as_mut_slice()) + .map_err(Error::StreamRead)?; + } + + Ok(regions) + } + + pub fn region_read(&mut self, region: u32, offset: u64, data: &mut [u8]) -> Result<(), Error> { + let region_read = RegionAccess { + header: Header { + message_id: self.next_message_id.0, + command: Command::RegionRead, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + offset, + count: data.len() as u32, + region, + }; + info!("Command: {:?}", region_read); + self.next_message_id += Wrapping(1); + self.stream + .write_all(region_read.as_slice()) + .map_err(Error::StreamWrite)?; + + let mut reply = RegionAccess::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + self.stream.read_exact(data).map_err(Error::StreamRead)?; + Ok(()) + } + + pub fn region_write(&mut self, region: u32, offset: u64, data: &[u8]) -> Result<(), Error> { + let region_write = RegionAccess { + header: Header { + message_id: self.next_message_id.0, + command: Command::RegionWrite, + flags: HeaderFlags::Command as u32, + message_size: (std::mem::size_of::() + data.len()) as u32, + ..Default::default() + }, + offset, + count: data.len() as u32, + region, + }; + info!("Command: {:?}", region_write); + self.next_message_id += Wrapping(1); + + let bufs = vec![IoSlice::new(region_write.as_slice()), IoSlice::new(data)]; + + // TODO: Use write_all_vectored() when ready + let _ = self + .stream + .write_vectored(&bufs) + .map_err(Error::StreamWrite)?; + + let mut reply = RegionAccess::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + Ok(()) + } + + pub fn get_irq_info(&mut self, index: u32) -> Result { + let get_irq_info = GetIrqInfo { + header: Header { + message_id: self.next_message_id.0, + command: Command::GetIrqInfo, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + argsz: (std::mem::size_of::() - std::mem::size_of::
()) as u32, + flags: 0, + index, + count: 0, + }; + info!("Command: {:?}", get_irq_info); + self.next_message_id += Wrapping(1); + + self.stream + .write_all(get_irq_info.as_slice()) + .map_err(Error::StreamWrite)?; + + let mut reply = GetIrqInfo::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + + Ok(IrqInfo { + index: reply.index, + flags: reply.flags, + count: reply.count, + }) + } + + pub fn set_irqs( + &mut self, + index: u32, + flags: u32, + start: u32, + count: u32, + fds: &[RawFd], + ) -> Result<(), Error> { + let set_irqs = SetIrqs { + header: Header { + message_id: self.next_message_id.0, + command: Command::SetIrqs, + flags: HeaderFlags::Command as u32, + message_size: std::mem::size_of::() as u32, + ..Default::default() + }, + argsz: (std::mem::size_of::() - std::mem::size_of::
()) as u32, + flags, + start, + index, + count, + }; + info!("Command: {:?}", set_irqs); + self.next_message_id += Wrapping(1); + + self.stream + .send_with_fds(&[set_irqs.as_slice()], fds) + .map_err(Error::SendWithFd)?; + + let mut reply = Header::default(); + self.stream + .read_exact(reply.as_mut_slice()) + .map_err(Error::StreamRead)?; + info!("Reply: {:?}", reply); + + Ok(()) + } + + pub fn region(&self, region_index: u32) -> Option<&Region> { + for region in &self.regions { + if region.index == region_index { + return Some(region); + } + } + + None + } + + pub fn resettable(&self) -> bool { + self.resettable + } +}