use crate::{
guest, ok, sys, AsHandleRef, GPAddr, Handle, HandleBased, HandleRef, MonotonicInstant, Signals,
Status, VcpuContents,
};
use bitflags::bitflags;
use std::mem;
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Port(Handle);
impl_handle_based!(Port);
bitflags! {
pub struct PortOptions: u32 {
const BIND_TO_INTERRUPT = sys::ZX_PORT_BIND_TO_INTERRUPT;
}
}
#[derive(Debug, Copy, Clone)]
pub enum PacketContents {
User(UserPacket),
SignalOne(SignalPacket),
GuestBell(GuestBellPacket),
GuestMem(GuestMemPacket),
GuestIo(GuestIoPacket),
GuestVcpu(GuestVcpuPacket),
Pager(PagerPacket),
Interrupt(InterruptPacket),
PowerTransition(PowerTransitionPacket),
#[doc(hidden)]
__Nonexhaustive,
}
#[derive(Debug, Default, Copy, Clone)]
pub struct UserPacket(sys::zx_packet_user_t);
#[derive(Debug, Copy, Clone)]
pub struct SignalPacket(sys::zx_packet_signal_t);
#[derive(Debug, Copy, Clone)]
pub struct GuestBellPacket(sys::zx_packet_guest_bell_t);
#[derive(Debug, Copy, Clone)]
pub struct GuestMemPacket(sys::zx_packet_guest_mem_t);
#[derive(Debug, Copy, Clone)]
pub struct GuestIoPacket(sys::zx_packet_guest_io_t);
#[derive(Debug, Copy, Clone)]
pub struct GuestVcpuPacket(sys::zx_packet_guest_vcpu_t);
#[derive(Debug, Copy, Clone)]
pub struct PagerPacket(sys::zx_packet_page_request_t);
#[derive(Debug, Copy, Clone)]
pub struct InterruptPacket(sys::zx_packet_interrupt_t);
#[derive(Debug, Copy, Clone)]
pub struct PowerTransitionPacket(sys::zx_packet_processor_power_level_transition_request_t);
#[derive(PartialEq, Eq, Debug)]
pub struct Packet(pub(crate) sys::zx_port_packet_t);
impl Packet {
pub fn from_user_packet(key: u64, status: i32, user: UserPacket) -> Packet {
Packet(sys::zx_port_packet_t {
key: key,
packet_type: sys::zx_packet_type_t::ZX_PKT_TYPE_USER,
status: status,
union: user.0,
})
}
pub fn from_guest_mem_packet(key: u64, status: i32, mem: GuestMemPacket) -> Packet {
let mut raw = sys::zx_port_packet_t {
key,
packet_type: sys::zx_packet_type_t::ZX_PKT_TYPE_GUEST_MEM,
status,
union: Default::default(),
};
let bytes: &[u8; std::mem::size_of::<sys::zx_packet_guest_mem_t>()] =
unsafe { mem::transmute(&mem.0) };
raw.union[0..std::mem::size_of::<sys::zx_packet_guest_mem_t>()].copy_from_slice(bytes);
Packet(raw)
}
pub fn from_guest_io_packet(key: u64, status: i32, io: GuestIoPacket) -> Packet {
let mut raw = sys::zx_port_packet_t {
key,
packet_type: sys::zx_packet_type_t::ZX_PKT_TYPE_GUEST_IO,
status,
union: Default::default(),
};
let bytes: &[u8; std::mem::size_of::<sys::zx_packet_guest_io_t>()] =
unsafe { mem::transmute(&io.0) };
raw.union[0..std::mem::size_of::<sys::zx_packet_guest_io_t>()].copy_from_slice(bytes);
Packet(raw)
}
pub fn from_guest_vcpu_packet(key: u64, status: i32, vcpu: GuestVcpuPacket) -> Packet {
Packet(sys::zx_port_packet_t {
key,
packet_type: sys::zx_packet_type_t::ZX_PKT_TYPE_GUEST_VCPU,
status,
union: unsafe { mem::transmute_copy(&vcpu.0) },
})
}
pub fn from_power_transition_packet(
key: u64,
status: i32,
power_transition_packet: PowerTransitionPacket,
) -> Packet {
Packet(sys::zx_port_packet_t {
key: key,
packet_type:
sys::zx_packet_type_t::ZX_PKT_TYPE_PROCESSOR_POWER_LEVEL_TRANSITION_REQUEST,
status: status,
union: unsafe { mem::transmute_copy(&power_transition_packet.0) },
})
}
pub fn key(&self) -> u64 {
self.0.key
}
pub fn status(&self) -> i32 {
self.0.status
}
pub fn contents(&self) -> PacketContents {
match self.0.packet_type {
sys::zx_packet_type_t::ZX_PKT_TYPE_USER => {
PacketContents::User(UserPacket(self.0.union))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_SIGNAL_ONE => {
PacketContents::SignalOne(SignalPacket(unsafe {
mem::transmute_copy(&self.0.union)
}))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_GUEST_BELL => {
PacketContents::GuestBell(GuestBellPacket(unsafe {
mem::transmute_copy(&self.0.union)
}))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_GUEST_MEM => {
PacketContents::GuestMem(GuestMemPacket(unsafe {
mem::transmute_copy(&self.0.union)
}))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_GUEST_IO => {
PacketContents::GuestIo(GuestIoPacket(unsafe {
mem::transmute_copy(&self.0.union)
}))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_GUEST_VCPU => {
PacketContents::GuestVcpu(GuestVcpuPacket(unsafe {
mem::transmute_copy(&self.0.union)
}))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_PAGE_REQUEST => {
PacketContents::Pager(PagerPacket(unsafe { mem::transmute_copy(&self.0.union) }))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_INTERRUPT => {
PacketContents::Interrupt(InterruptPacket(unsafe {
mem::transmute_copy(&self.0.union)
}))
}
sys::zx_packet_type_t::ZX_PKT_TYPE_PROCESSOR_POWER_LEVEL_TRANSITION_REQUEST => {
PacketContents::PowerTransition(PowerTransitionPacket(unsafe {
mem::transmute_copy(&self.0.union)
}))
}
_ => panic!("unexpected packet type"),
}
}
}
impl UserPacket {
pub fn from_u8_array(val: [u8; 32]) -> UserPacket {
UserPacket(val)
}
pub fn as_u8_array(&self) -> &[u8; 32] {
&self.0
}
pub fn as_mut_u8_array(&mut self) -> &mut [u8; 32] {
&mut self.0
}
}
impl SignalPacket {
pub fn trigger(&self) -> Signals {
Signals::from_bits_truncate(self.0.trigger)
}
pub fn observed(&self) -> Signals {
Signals::from_bits_truncate(self.0.observed)
}
pub fn count(&self) -> u64 {
self.0.count
}
pub fn raw_packet(&self) -> &sys::zx_packet_signal_t {
&self.0
}
}
impl GuestBellPacket {
pub fn addr(&self) -> GPAddr {
GPAddr(self.0.addr)
}
}
impl GuestMemPacket {
pub fn from_raw(mem: sys::zx_packet_guest_mem_t) -> GuestMemPacket {
GuestMemPacket(mem)
}
pub fn addr(&self) -> GPAddr {
GPAddr(self.0.addr)
}
}
#[cfg(target_arch = "aarch64")]
impl GuestMemPacket {
pub fn access_size(&self) -> Option<guest::MemAccessSize> {
match self.0.access_size {
1 => Some(guest::MemAccessSize::Bits8),
2 => Some(guest::MemAccessSize::Bits16),
4 => Some(guest::MemAccessSize::Bits32),
8 => Some(guest::MemAccessSize::Bits64),
_ => None,
}
}
pub fn sign_extend(&self) -> bool {
self.0.sign_extend
}
pub fn reg(&self) -> u8 {
self.0.xt
}
pub fn data(&self) -> Option<guest::MemData> {
if let guest::AccessType::Write = self.access_type() {
self.access_size().map(|size| match size {
guest::MemAccessSize::Bits8 => guest::MemData::Data8(self.0.data as u8),
guest::MemAccessSize::Bits16 => guest::MemData::Data16(self.0.data as u16),
guest::MemAccessSize::Bits32 => guest::MemData::Data32(self.0.data as u32),
guest::MemAccessSize::Bits64 => guest::MemData::Data64(self.0.data),
})
} else {
None
}
}
pub fn access_type(&self) -> guest::AccessType {
match self.0.read {
true => guest::AccessType::Read,
false => guest::AccessType::Write,
}
}
}
#[cfg(target_arch = "x86_64")]
impl GuestMemPacket {
pub fn default_operand_size(&self) -> Option<guest::CSDefaultOperandSize> {
match self.0.default_operand_size {
2 => Some(guest::CSDefaultOperandSize::Bits16),
4 => Some(guest::CSDefaultOperandSize::Bits32),
_ => None,
}
}
}
impl GuestIoPacket {
pub fn from_raw(io: sys::zx_packet_guest_io_t) -> GuestIoPacket {
GuestIoPacket(io)
}
pub fn port(&self) -> u16 {
self.0.port
}
pub fn access_size(&self) -> Option<guest::PortAccessSize> {
match self.0.access_size {
1 => Some(guest::PortAccessSize::Bits8),
2 => Some(guest::PortAccessSize::Bits16),
4 => Some(guest::PortAccessSize::Bits32),
_ => None,
}
}
pub fn access_type(&self) -> guest::AccessType {
match self.0.input {
true => guest::AccessType::Read,
false => guest::AccessType::Write,
}
}
pub fn data(&self) -> Option<guest::PortData> {
#[repr(C)]
union DataUnion {
bit8: [u8; 4],
bit16: [u16; 2],
bit32: [u32; 1],
}
if let guest::AccessType::Write = self.access_type() {
unsafe {
let data = &DataUnion { bit8: self.0.data };
self.access_size().map(|size| match size {
guest::PortAccessSize::Bits8 => guest::PortData::Data8(data.bit8[0]),
guest::PortAccessSize::Bits16 => guest::PortData::Data16(data.bit16[0]),
guest::PortAccessSize::Bits32 => guest::PortData::Data32(data.bit32[0]),
})
}
} else {
None
}
}
}
impl GuestVcpuPacket {
pub fn from_raw(vcpu: sys::zx_packet_guest_vcpu_t) -> GuestVcpuPacket {
GuestVcpuPacket(vcpu)
}
pub fn contents(&self) -> VcpuContents {
match self.0.r#type {
sys::zx_packet_guest_vcpu_type_t::ZX_PKT_GUEST_VCPU_INTERRUPT => unsafe {
VcpuContents::Interrupt {
mask: self.0.union.interrupt.mask,
vector: self.0.union.interrupt.vector,
}
},
sys::zx_packet_guest_vcpu_type_t::ZX_PKT_GUEST_VCPU_STARTUP => unsafe {
VcpuContents::Startup {
id: self.0.union.startup.id,
entry: self.0.union.startup.entry,
}
},
sys::zx_packet_guest_vcpu_type_t::ZX_PKT_GUEST_VCPU_EXIT => unsafe {
VcpuContents::Exit { retcode: self.0.union.exit.retcode }
},
_ => panic!("unexpected VCPU packet type"),
}
}
}
impl PagerPacket {
pub fn command(&self) -> sys::zx_page_request_command_t {
self.0.command
}
pub fn range(&self) -> std::ops::Range<u64> {
self.0.offset..self.0.offset + self.0.length
}
}
impl InterruptPacket {
pub fn timestamp(&self) -> sys::zx_time_t {
self.0.timestamp
}
}
impl PowerTransitionPacket {
pub fn from_raw(
packet: sys::zx_packet_processor_power_level_transition_request_t,
) -> PowerTransitionPacket {
PowerTransitionPacket(packet)
}
pub fn domain_id(&self) -> u32 {
self.0.domain_id
}
pub fn options(&self) -> u32 {
self.0.options
}
pub fn control_interface(&self) -> u64 {
self.0.control_interface
}
pub fn reserved(&self) -> u64 {
self.0.reserved
}
pub fn control_argument(&self) -> u64 {
self.0.control_argument
}
}
impl Port {
pub fn create() -> Self {
Self::create_with_opts(PortOptions::from_bits_truncate(0))
}
pub fn create_with_opts(opts: PortOptions) -> Self {
unsafe {
let mut handle = 0;
let status = sys::zx_port_create(opts.bits(), &mut handle);
ok(status).expect(
"port creation always succeeds except with OOM or when job policy denies it",
);
Handle::from_raw(handle).into()
}
}
pub fn queue(&self, packet: &Packet) -> Result<(), Status> {
let status =
unsafe { sys::zx_port_queue(self.raw_handle(), std::ptr::from_ref(&packet.0)) };
ok(status)
}
pub fn wait(&self, deadline: MonotonicInstant) -> Result<Packet, Status> {
let mut packet = Default::default();
ok(unsafe { sys::zx_port_wait(self.raw_handle(), deadline.into_nanos(), &mut packet) })?;
Ok(Packet(packet))
}
pub fn cancel<H>(&self, source: &H, key: u64) -> Result<(), Status>
where
H: AsHandleRef,
{
let status = unsafe { sys::zx_port_cancel(self.raw_handle(), source.raw_handle(), key) };
ok(status)
}
}
bitflags! {
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WaitAsyncOpts: u32 {
const TIMESTAMP = sys::ZX_WAIT_ASYNC_TIMESTAMP;
const EDGE_TRIGGERED = sys::ZX_WAIT_ASYNC_EDGE;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Duration, Event};
use assert_matches::assert_matches;
#[test]
fn port_basic() {
let ten_ms = Duration::from_millis(10);
let port = Port::create();
assert_eq!(port.wait(MonotonicInstant::after(ten_ms)), Err(Status::TIMED_OUT));
let packet = Packet::from_user_packet(42, 123, UserPacket::from_u8_array([13; 32]));
assert!(port.queue(&packet).is_ok());
let read_packet = port.wait(MonotonicInstant::after(ten_ms)).unwrap();
assert_eq!(read_packet, packet);
}
#[test]
fn create_with_opts() {
let _port = Port::create_with_opts(PortOptions::BIND_TO_INTERRUPT);
}
#[test]
fn wait_async_once() {
let ten_ms = Duration::from_millis(10);
let key = 42;
let port = Port::create();
let event = Event::create();
let no_opts = WaitAsyncOpts::empty();
assert!(event
.wait_async_handle(&port, key, Signals::USER_0 | Signals::USER_1, no_opts)
.is_ok());
assert_eq!(port.wait(MonotonicInstant::after(ten_ms)), Err(Status::TIMED_OUT));
assert!(event.signal_handle(Signals::NONE, Signals::USER_0).is_ok());
let read_packet = port.wait(MonotonicInstant::after(ten_ms)).unwrap();
assert_eq!(read_packet.key(), key);
assert_eq!(read_packet.status(), 0);
match read_packet.contents() {
PacketContents::SignalOne(sig) => {
assert_eq!(sig.trigger(), Signals::USER_0 | Signals::USER_1);
assert_eq!(sig.observed(), Signals::USER_0);
assert_eq!(sig.count(), 1);
}
_ => panic!("wrong packet type"),
}
assert_eq!(port.wait(MonotonicInstant::after(ten_ms)), Err(Status::TIMED_OUT));
assert!(event.wait_async_handle(&port, key, Signals::USER_0, no_opts).is_ok());
let read_packet = port.wait(MonotonicInstant::after(ten_ms)).unwrap();
assert_eq!(read_packet.key(), key);
assert_eq!(read_packet.status(), 0);
match read_packet.contents() {
PacketContents::SignalOne(sig) => {
assert_eq!(sig.trigger(), Signals::USER_0);
assert_eq!(sig.observed(), Signals::USER_0);
assert_eq!(sig.count(), 1);
}
_ => panic!("wrong packet type"),
}
assert!(event.wait_async_handle(&port, key, Signals::USER_0, no_opts).is_ok());
assert!(port.cancel(&event, key).is_ok());
assert_eq!(port.wait(MonotonicInstant::after(ten_ms)), Err(Status::TIMED_OUT));
assert!(event.signal_handle(Signals::USER_0, Signals::NONE).is_ok()); assert!(event.wait_async_handle(&port, key, Signals::USER_0, no_opts).is_ok());
assert!(port.cancel(&event, key).is_ok());
assert!(event.signal_handle(Signals::NONE, Signals::USER_0).is_ok());
assert_eq!(port.wait(MonotonicInstant::after(ten_ms)), Err(Status::TIMED_OUT));
}
#[test]
fn guest_mem_packet() {
#[cfg(target_arch = "x86_64")]
const GUEST_MEM_PACKET: sys::zx_packet_guest_mem_t = sys::zx_packet_guest_mem_t {
addr: 0xaaaabbbbccccdddd,
cr3: 0x0123456789abcdef,
rip: 0xffffffff00000000,
instruction_size: 8,
default_operand_size: 2,
};
#[cfg(target_arch = "aarch64")]
const GUEST_MEM_PACKET: sys::zx_packet_guest_mem_t = sys::zx_packet_guest_mem_t {
addr: 0x8877665544332211,
access_size: 8,
sign_extend: true,
xt: 0xf3,
read: false,
data: 0x1122334455667788,
};
#[cfg(target_arch = "riscv64")]
const GUEST_MEM_PACKET: sys::zx_packet_guest_mem_t =
sys::zx_packet_guest_mem_t { addr: 0x8877665544332211, reserved: [0; 3] };
const KEY: u64 = 0x5555555555555555;
const STATUS: i32 = sys::ZX_ERR_INTERNAL;
let packet =
Packet::from_guest_mem_packet(KEY, STATUS, GuestMemPacket::from_raw(GUEST_MEM_PACKET));
assert_matches!(packet.contents(), PacketContents::GuestMem(GuestMemPacket(packet)) if packet == GUEST_MEM_PACKET);
assert_eq!(packet.key(), KEY);
assert_eq!(packet.status(), STATUS);
}
#[test]
fn guest_io_packet() {
const GUEST_IO_PACKET: sys::zx_packet_guest_io_t = sys::zx_packet_guest_io_t {
port: 0xabcd,
access_size: 4,
input: true,
data: [0xaa, 0xbb, 0xcc, 0xdd],
};
const KEY: u64 = 0x0123456789abcdef;
const STATUS: i32 = sys::ZX_ERR_NO_RESOURCES;
let packet =
Packet::from_guest_io_packet(KEY, STATUS, GuestIoPacket::from_raw(GUEST_IO_PACKET));
assert_matches!(packet.contents(), PacketContents::GuestIo(GuestIoPacket(packet)) if packet == GUEST_IO_PACKET);
assert_eq!(packet.key(), KEY);
assert_eq!(packet.status(), STATUS);
}
#[test]
fn guest_vcpu_interrupt_packet() {
let guest_vcpu_packet: sys::zx_packet_guest_vcpu_t = sys::zx_packet_guest_vcpu_t {
r#type: sys::zx_packet_guest_vcpu_type_t::ZX_PKT_GUEST_VCPU_INTERRUPT,
union: sys::zx_packet_guest_vcpu_union_t {
interrupt: sys::zx_packet_guest_vcpu_interrupt_t {
mask: 0xaaaaaaaaaaaaaaaa,
vector: 0x12,
padding1: Default::default(),
},
},
padding1: Default::default(),
reserved: 0,
};
const KEY: u64 = 0x0123456789abcdef;
const STATUS: i32 = sys::ZX_ERR_NO_RESOURCES;
let packet = Packet::from_guest_vcpu_packet(
KEY,
STATUS,
GuestVcpuPacket::from_raw(guest_vcpu_packet),
);
assert_matches!(packet.contents(), PacketContents::GuestVcpu(GuestVcpuPacket(packet)) if packet == guest_vcpu_packet);
assert_eq!(packet.key(), KEY);
assert_eq!(packet.status(), STATUS);
}
#[test]
fn guest_vcpu_startup_packet() {
let guest_vcpu_packet: sys::zx_packet_guest_vcpu_t = sys::zx_packet_guest_vcpu_t {
r#type: sys::zx_packet_guest_vcpu_type_t::ZX_PKT_GUEST_VCPU_STARTUP,
union: sys::zx_packet_guest_vcpu_union_t {
startup: sys::zx_packet_guest_vcpu_startup_t { id: 16, entry: 0xffffffff11111111 },
},
padding1: Default::default(),
reserved: 0,
};
const KEY: u64 = 0x0123456789abcdef;
const STATUS: i32 = sys::ZX_ERR_NO_RESOURCES;
let packet = Packet::from_guest_vcpu_packet(
KEY,
STATUS,
GuestVcpuPacket::from_raw(guest_vcpu_packet),
);
assert_matches!(packet.contents(), PacketContents::GuestVcpu(GuestVcpuPacket(packet)) if packet == guest_vcpu_packet);
assert_eq!(packet.key(), KEY);
assert_eq!(packet.status(), STATUS);
}
#[test]
fn guest_vcpu_exit_packet() {
let guest_vcpu_packet: sys::zx_packet_guest_vcpu_t = sys::zx_packet_guest_vcpu_t {
r#type: sys::zx_packet_guest_vcpu_type_t::ZX_PKT_GUEST_VCPU_EXIT,
union: sys::zx_packet_guest_vcpu_union_t {
exit: sys::zx_packet_guest_vcpu_exit_t { retcode: 12345678, reserved: 0 },
},
padding1: Default::default(),
reserved: 0,
};
const KEY: u64 = 0x0123456789abcdef;
const STATUS: i32 = sys::ZX_ERR_NO_RESOURCES;
let packet = Packet::from_guest_vcpu_packet(
KEY,
STATUS,
GuestVcpuPacket::from_raw(guest_vcpu_packet),
);
assert_matches!(packet.contents(), PacketContents::GuestVcpu(GuestVcpuPacket(packet)) if packet == guest_vcpu_packet);
assert_eq!(packet.key(), KEY);
assert_eq!(packet.status(), STATUS);
}
#[test]
fn power_transition_packet() {
const POWER_TRANSITION_PACKET: sys::zx_packet_processor_power_level_transition_request_t =
sys::zx_packet_processor_power_level_transition_request_t {
domain_id: 0,
options: 0,
control_argument: 0,
control_interface: 0,
reserved: 0,
};
const KEY: u64 = 0x0123456789abcdef;
const STATUS: i32 = sys::ZX_ERR_NO_RESOURCES;
let packet = Packet::from_power_transition_packet(
KEY,
STATUS,
PowerTransitionPacket::from_raw(POWER_TRANSITION_PACKET),
);
assert_matches!(
packet.contents(),
PacketContents::PowerTransition(PowerTransitionPacket(packet)) if packet == POWER_TRANSITION_PACKET
);
assert_eq!(packet.key(), KEY);
assert_eq!(packet.status(), STATUS);
}
}