blob: 3a5abc6bd091884c877e5f74c5a00baf75fb6f23 [file]
// Copyright 2016 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use core::ptr::NonNull;
use ksync::lock;
use zx_status::Status;
use zx_types::{
ZX_INTERRUPT_TIMESTAMP_MONO, ZX_INTERRUPT_VIRTUAL, ZX_INTERRUPT_WAKE_VECTOR,
ZX_VIRTUAL_INTERRUPT_UNTRIGGERED, zx_rights_t, zx_time_t,
};
use super::DispatcherOps;
use super::handle::KernelHandle;
use super::interrupt_dispatcher::{
INTERRUPT_TIMESTAMP_MONO, INTERRUPT_VIRTUAL, INTERRUPT_WAKE_VECTOR, InterruptDispatcher,
InterruptKind, PostAckState, WakeVectorDiagnostics,
};
use crate::counters::define_kcounter;
define_kcounter!(
DISPATCHER_VIRTUAL_INTERRUPT_CREATE_COUNT,
"dispatcher.virtual_interrupt.create",
Sum
);
define_kcounter!(
DISPATCHER_VIRTUAL_INTERRUPT_DESTROY_COUNT,
"dispatcher.virtual_interrupt.destroy",
Sum
);
/// Virtual interrupt dispatcher variant triggered via `zx_interrupt_trigger`.
#[repr(C)]
pub struct VirtualInterruptDispatcher {
disp: NonNull<InterruptDispatcher>,
}
impl Drop for VirtualInterruptDispatcher {
fn drop(&mut self) {
// SAFETY: `self.disp` points to the enclosing `InterruptDispatcher`, which is valid
// during `~InterruptDispatcher`.
let disp = unsafe { self.disp.as_ref() };
if disp.is_wake_vector() {
disp.destroy_wake_event();
}
DISPATCHER_VIRTUAL_INTERRUPT_DESTROY_COUNT.add(1);
}
}
impl VirtualInterruptDispatcher {
/// Creates a new virtual interrupt dispatcher with `options`.
pub fn create(
options: u32,
) -> Result<(KernelHandle<InterruptDispatcher>, zx_rights_t), Status> {
if (options & ZX_INTERRUPT_VIRTUAL) == 0 {
return Err(Status::INVALID_ARGS);
}
const PERMITTED_OPTIONS: u32 =
ZX_INTERRUPT_VIRTUAL | ZX_INTERRUPT_TIMESTAMP_MONO | ZX_INTERRUPT_WAKE_VECTOR;
if (options & !PERMITTED_OPTIONS) != 0 {
return Err(Status::INVALID_ARGS);
}
let mut flags = INTERRUPT_VIRTUAL;
if (options & ZX_INTERRUPT_TIMESTAMP_MONO) != 0 {
flags |= INTERRUPT_TIMESTAMP_MONO;
}
if (options & ZX_INTERRUPT_WAKE_VECTOR) != 0 {
flags |= INTERRUPT_WAKE_VECTOR;
}
// Attempt to construct the dispatcher.
let disp = InterruptDispatcher::create_raw(flags, options, |disp| {
Self::new(disp);
})
.ok_or(Status::NO_MEMORY)?;
let new_handle = KernelHandle::new(disp);
// Transfer control of the new dispatcher to the creator and we are done.
Ok((new_handle, InterruptDispatcher::default_rights()))
}
#[allow(clippy::new_ret_no_self)]
fn new(disp: &InterruptDispatcher) {
// SAFETY: Called once during `InterruptDispatcher::create_raw` construction.
unsafe {
disp.init_kind(InterruptKind::Virtual(Self { disp: NonNull::from(disp) }));
}
// Immediately after construction, virtual interrupt objects are always in the
// untriggered state. Make sure that our initial signal state reflects this.
disp.update_state(0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
DISPATCHER_VIRTUAL_INTERRUPT_CREATE_COUNT.add(1);
if disp.is_wake_vector() {
disp.initialize_wake_event();
}
}
/// Populates wake vector diagnostics for this virtual interrupt.
pub fn get_diagnostics(&self, diagnostics_out: &mut WakeVectorDiagnostics) {
// SAFETY: `self.disp` points to the valid enclosing `InterruptDispatcher`.
let disp = unsafe { self.disp.as_ref() };
diagnostics_out.enabled = disp.is_wake_vector();
diagnostics_out.koid = disp.get_koid();
diagnostics_out.print_extra(format_args!("VirtIRQ {}", disp.get_koid()));
}
/// Blocks until the virtual interrupt is triggered or destroyed, updating the
/// `ZX_VIRTUAL_INTERRUPT_UNTRIGGERED` signal.
pub fn wait_for_interrupt(&self, disp: &InterruptDispatcher) -> Result<zx_time_t, Status> {
loop {
// Hold the dispatcher lock during the first phase, and if we decide that we
// need to block, be sure that our UNTRIGGERED signal has been asserted.
{
lock!(let guard = disp.state().lock_lock());
let opt_status = disp.begin_wait_for_interrupt();
if let Some(val) = opt_status {
return val;
}
disp.update_state_locked(guard.token(), 0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
}
disp.do_wait_for_interrupt_block()?;
}
}
/// Triggers the virtual interrupt at `timestamp`, clearing `ZX_VIRTUAL_INTERRUPT_UNTRIGGERED`.
pub fn trigger(&self, disp: &InterruptDispatcher, timestamp: zx_time_t) -> Result<(), Status> {
lock!(let dispatcher_guard = disp.state().lock_lock());
let result = disp.state().trigger(disp, timestamp);
// If the trigger operation succeeded, then we know that we must be in either
// the triggered or need-ack state, and we should clear the UNTRIGGERED
// signal.
if result.is_ok() {
disp.update_state_locked(dispatcher_guard.token(), ZX_VIRTUAL_INTERRUPT_UNTRIGGERED, 0);
}
result
}
/// Acknowledges the virtual interrupt and updates `ZX_VIRTUAL_INTERRUPT_UNTRIGGERED`.
pub fn ack(&self, disp: &InterruptDispatcher) -> Result<(), Status> {
// Hold the dispatcher lock while we perform our lower level ack operation.
// If this succeeds, make sure that the UNTRIGGERED signal is asserted.
lock!(let guard = disp.state().lock_lock());
let result = disp.ack_internal();
// If something goes wrong with the ack, then our UNTRIGGERED signal state
// should not have changed.
let result = result?;
// Things went well, we successfully acked the interrupt and should assert the
// UNTRIGGERED signal. If, however, the interrupt immediately retriggered
// (because it already had another pending signal), be sure to clear the
// UNTRIGGERED signal after asserting it.
disp.update_state_locked(guard.token(), 0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
if result == PostAckState::Retriggered {
disp.update_state_locked(guard.token(), ZX_VIRTUAL_INTERRUPT_UNTRIGGERED, 0);
} else {
debug_assert!(result == PostAckState::FullyAcked);
}
Ok(())
}
/// Destroys the virtual interrupt and asserts `ZX_VIRTUAL_INTERRUPT_UNTRIGGERED`.
pub fn destroy(&self, disp: &InterruptDispatcher) -> Result<(), Status> {
// An interrupt which has been explicitly destroyed (using a call to
// zx_interrupt_destroy) can no longer be in the triggered state. Make sure
// that we assert the virtual interrupt's UNTRIGGERED signal to be sure that
// we don't accidentally end up with waiters stuck waiting on a destroyed
// interrupt.
lock!(let guard = disp.state().lock_lock());
let result = disp.state().destroy(disp);
disp.update_state_locked(guard.token(), 0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
result
}
}