blob: c9e64e996e39c576a8a9abbee7f9d726a9e98336 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Type-safe bindings for Zircon port objects.
use std::mem;
use crate::{AsHandleRef, HandleBased, Handle, HandleRef, Signals, Status, Time, GPAddr, guest};
use crate::ok;
use fuchsia_zircon_sys as sys;
/// An object representing a Zircon
/// [port](https://fuchsia.googlesource.com/zircon/+/master/docs/objects/port.md).
///
/// As essentially a subtype of `Handle`, it can be freely interconverted.
#[derive(Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Port(Handle);
impl_handle_based!(Port);
/// A packet sent through a port. This is a type-safe wrapper for
/// [zx_port_packet_t](https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/port_wait.md).
#[derive(PartialEq, Eq, Debug)]
pub struct Packet(sys::zx_port_packet_t);
/// The contents of a `Packet`.
#[derive(Debug, Copy, Clone)]
pub enum PacketContents {
/// A user-generated packet.
User(UserPacket),
/// A one-shot signal packet generated via `object_wait_async`.
SignalOne(SignalPacket),
/// A repeating signal packet generated via `object_wait_async`.
SignalRep(SignalPacket),
/// A guest bell packet
GuestBell(GuestBellPacket),
/// A guest mem packet
GuestMem(GuestMemPacket),
/// A Guest I/O Packet
GuestIo(GuestIoPacket),
#[doc(hidden)]
__Nonexhaustive
}
/// Contents of a user packet (one sent by `port_queue`). This is a type-safe wrapper for
/// [zx_packet_user_t](https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/port_wait.md).
#[derive(Debug, Copy, Clone)]
pub struct UserPacket(sys::zx_packet_user_t);
/// Contents of a signal packet (one generated by the kernel). This is a type-safe wrapper for
/// [zx_packet_signal_t](https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/port_wait.md).
#[derive(Debug, Copy, Clone)]
pub struct SignalPacket(sys::zx_packet_signal_t);
/// Contents of a guest bell packet generated by the kernel. This is a type-safe wrapper for
/// zx_packet_guest_bell_t
#[derive(Debug, Copy, Clone)]
pub struct GuestBellPacket(sys::zx_packet_guest_bell_t);
/// Contents of a guest memory packet generated by the kernel. This is a type-safe wrapper for
/// zx_packet_guest_memory_t
#[derive(Debug, Copy, Clone)]
pub struct GuestMemPacket(sys::zx_packet_guest_mem_t);
/// Contents of a guest I/O packet generated by the kernel. This is a type-safe wrapper for
/// zx_packet_guest_io_t
#[derive(Debug, Copy, Clone)]
pub struct GuestIoPacket(sys::zx_packet_guest_io_t);
impl Packet {
/// Creates a new packet with `UserPacket` data.
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,
}
)
}
/// The packet's key.
pub fn key(&self) -> u64 {
self.0.key
}
/// The packet's status.
// TODO: should this type be wrapped?
pub fn status(&self) -> i32 {
self.0.status
}
/// The contents of the packet.
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_SIGNAL_REP => {
PacketContents::SignalRep(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)
}))
}
_ => 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 {
/// The signals used in the call to `object_wait_async`.
pub fn trigger(&self) -> Signals {
Signals::from_bits_truncate(self.0.trigger)
}
/// The observed signals.
pub fn observed(&self) -> Signals {
Signals::from_bits_truncate(self.0.observed)
}
/// A per object count of pending operations.
pub fn count(&self) -> u64 {
self.0.count
}
}
impl GuestBellPacket {
/// The guest physical address that was accessed that triggered the bell.
pub fn addr(&self) -> GPAddr {
GPAddr(self.0.addr)
}
}
impl GuestMemPacket {
/// The guest physical address that was accessed that triggered this signal.
pub fn addr(&self) -> GPAddr {
GPAddr(self.0.addr)
}
}
#[cfg(target_arch = "aarch64")]
impl GuestMemPacket {
/// Size of the access.
///
/// Returns `None` should it find and invalid size in the packet.
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,
}
}
/// Whether or not data should be sign extended.
pub fn sign_extend(&self) -> bool {
self.0.sign_extend
}
/// Register number of the Rt operand of the faulting instruction.
pub fn reg(&self) -> u8 {
self.0.xt
}
/// For AccessType::Write this is the data that was being written.
pub fn data(&self) -> Option<u64> {
match self.access_type() {
guest::AccessType::Read => None,
guest::AccessType::Write => Some(self.0.data),
}
}
/// Type of access (read or write).
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 {
/// Specifies the default operand size encoded by the CS descriptor.
///
/// Returns a `None` should it find an invalid size in the packet.
pub fn default_operand_size(&self) -> Option<guest::CSDefaultOperandSize> {
// TODO: use try_from when it is stable.
match self.0.default_operand_size {
2 => Some(guest::CSDefaultOperandSize::Bits16),
4 => Some(guest::CSDefaultOperandSize::Bits32),
_ => None,
}
}
/// Retrieves the instruction that caused this trap.
///
/// Returns a `None` if the packet is corrupt and claims to have an instruction that is too
/// long.
pub fn instruction(&self) -> Option<&[u8]> {
self.0.inst_buf.get(0..self.0.inst_len.into())
}
}
impl GuestIoPacket {
/// First port number of the attempted access
pub fn port(&self) -> u16 {
self.0.port
}
/// Size of the access.
///
/// Returns `None` should it find an invalid size in the packet.
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,
}
}
/// Type of access (read or write).
pub fn access_type(&self) -> guest::AccessType {
match self.0.input {
true => guest::AccessType::Read,
false => guest::AccessType::Write,
}
}
/// For `PortAccessType::Write` this is the data that was being written.
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 = &*(self.0.data.as_ptr() as *const DataUnion);
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 Port {
/// Create an IO port, allowing IO packets to be read and enqueued.
///
/// Wraps the
/// [zx_port_create](https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/port_create.md)
/// syscall.
pub fn create() -> Result<Port, Status> {
unsafe {
let mut handle = 0;
let opts = 0;
let status = sys::zx_port_create(opts, &mut handle);
ok(status)?;
Ok(Handle::from_raw(handle).into())
}
}
/// Attempt to queue a user packet to the IO port.
///
/// Wraps the
/// [zx_port_queue](https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/port_queue.md)
/// syscall.
pub fn queue(&self, packet: &Packet) -> Result<(), Status> {
let status = unsafe {
sys::zx_port_queue(self.raw_handle(),
&packet.0 as *const sys::zx_port_packet_t)
};
ok(status)
}
/// Wait for a packet to arrive on a (V2) port.
///
/// Wraps the
/// [zx_port_wait](https://fuchsia.googlesource.com/zircon/+/master/docs/syscalls/port_wait.md)
/// syscall.
pub fn wait(&self, deadline: Time) -> Result<Packet, Status> {
let mut packet = Default::default();
let status = unsafe {
sys::zx_port_wait(self.raw_handle(), deadline.nanos(),
&mut packet as *mut sys::zx_port_packet_t)
};
ok(status)?;
Ok(Packet(packet))
}
/// Cancel pending wait_async calls for an object with the given key.
///
/// Wraps the
/// [zx_port_cancel](https://fuchsia.googlesource.com/zircon/+/HEAD/docs/syscalls/port_cancel.md)
/// syscall.
pub fn cancel<H>(&self, source: &H, key: u64) -> Result<(), Status> where H: HandleBased {
let status = unsafe {
sys::zx_port_cancel(self.raw_handle(), source.raw_handle(), key)
};
ok(status)
}
}
/// Options for wait_async.
#[repr(u32)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum WaitAsyncOpts {
Once = sys::ZX_WAIT_ASYNC_ONCE,
Repeating = sys::ZX_WAIT_ASYNC_REPEATING,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DurationNum, Event};
#[test]
fn port_basic() {
let ten_ms = 10.millis();
let port = Port::create().unwrap();
// Waiting now should time out.
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// Send a valid packet.
let packet = Packet::from_user_packet(
42,
123,
UserPacket::from_u8_array([13; 32]),
);
assert!(port.queue(&packet).is_ok());
// Waiting should succeed this time. We should get back the packet we sent.
let read_packet = port.wait(ten_ms.after_now()).unwrap();
assert_eq!(read_packet, packet);
}
#[test]
fn wait_async_once() {
let ten_ms = 10.millis();
let key = 42;
let port = Port::create().unwrap();
let event = Event::create().unwrap();
assert!(event.wait_async_handle(&port, key, Signals::USER_0 | Signals::USER_1,
WaitAsyncOpts::Once).is_ok());
// Waiting without setting any signal should time out.
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// If we set a signal, we should be able to wait for it.
assert!(event.signal_handle(Signals::NONE, Signals::USER_0).is_ok());
let read_packet = port.wait(ten_ms.after_now()).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"),
}
// Shouldn't get any more packets.
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// Calling wait_async again should result in another packet.
assert!(event.wait_async_handle(&port, key, Signals::USER_0, WaitAsyncOpts::Once).is_ok());
let read_packet = port.wait(ten_ms.after_now()).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"),
}
// Calling wait_async_handle then cancel, we should not get a packet as cancel will
// remove it from the queue.
assert!(event.wait_async_handle(&port, key, Signals::USER_0, WaitAsyncOpts::Once).is_ok());
assert!(port.cancel(&event, key).is_ok());
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// If the event is signalled after the cancel, we also shouldn't get a packet.
assert!(event.signal_handle(Signals::USER_0, Signals::NONE).is_ok()); // clear signal
assert!(event.wait_async_handle(&port, key, Signals::USER_0, WaitAsyncOpts::Once).is_ok());
assert!(port.cancel(&event, key).is_ok());
assert!(event.signal_handle(Signals::NONE, Signals::USER_0).is_ok());
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
}
#[test]
fn wait_async_repeating() {
let ten_ms = 10.millis();
let key = 42;
let port = Port::create().unwrap();
let event = Event::create().unwrap();
assert!(event.wait_async_handle(&port, key, Signals::USER_0 | Signals::USER_1,
WaitAsyncOpts::Repeating).is_ok());
// Waiting without setting any signal should time out.
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// If we set a signal, we should be able to wait for it.
assert!(event.signal_handle(Signals::NONE, Signals::USER_0).is_ok());
let read_packet = port.wait(ten_ms.after_now()).unwrap();
assert_eq!(read_packet.key(), key);
assert_eq!(read_packet.status(), 0);
match read_packet.contents() {
PacketContents::SignalRep(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"),
}
// Should not get any more packets, as ZX_WAIT_ASYNC_REPEATING is edge triggered rather than
// level triggered.
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// If we clear and resignal, we should get the same packet again,
// even though we didn't call event.wait_async again.
assert!(event.signal_handle(Signals::USER_0, Signals::NONE).is_ok()); // clear signal
assert!(event.signal_handle(Signals::NONE, Signals::USER_0).is_ok());
let read_packet = port.wait(ten_ms.after_now()).unwrap();
assert_eq!(read_packet.key(), key);
assert_eq!(read_packet.status(), 0);
match read_packet.contents() {
PacketContents::SignalRep(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"),
}
// Cancelling the wait should stop us getting packets...
assert!(port.cancel(&event, key).is_ok());
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// ... even if we clear and resignal
assert!(event.signal_handle(Signals::USER_0, Signals::NONE).is_ok()); // clear signal
assert!(event.signal_handle(Signals::NONE, Signals::USER_0).is_ok());
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
// Calling wait_async again should result in another packet.
assert!(event.wait_async_handle(
&port, key, Signals::USER_0, WaitAsyncOpts::Repeating).is_ok());
let read_packet = port.wait(ten_ms.after_now()).unwrap();
assert_eq!(read_packet.key(), key);
assert_eq!(read_packet.status(), 0);
match read_packet.contents() {
PacketContents::SignalRep(sig) => {
assert_eq!(sig.trigger(), Signals::USER_0);
assert_eq!(sig.observed(), Signals::USER_0);
assert_eq!(sig.count(), 1);
}
_ => panic!("wrong packet type"),
}
// Closing the handle should stop us getting packets.
drop(event);
assert_eq!(port.wait(ten_ms.after_now()), Err(Status::TIMED_OUT));
}
}