blob: a67aad61f55084086a64099141f43c99943ba7f9 [file] [log] [blame]
// 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
#include "object/virtual_interrupt_dispatcher.h"
#include <lib/counters.h>
#include <platform.h>
#include <zircon/rights.h>
#include <dev/interrupt.h>
#include <fbl/alloc_checker.h>
#include <kernel/mutex.h>
KCOUNTER(dispatcher_virtual_interrupt_create_count, "dispatcher.virtual_interrupt.create")
KCOUNTER(dispatcher_virtual_interrupt_destroy_count, "dispatcher.virtual_interrupt.destroy")
zx_status_t VirtualInterruptDispatcher::Create(KernelHandle<InterruptDispatcher>* handle,
zx_rights_t* rights, uint32_t options) {
if (!(options & ZX_INTERRUPT_VIRTUAL)) {
return ZX_ERR_INVALID_ARGS;
}
constexpr uint32_t permitted_options =
ZX_INTERRUPT_VIRTUAL | ZX_INTERRUPT_TIMESTAMP_MONO | ZX_INTERRUPT_WAKE_VECTOR;
if (options & ~permitted_options) {
return ZX_ERR_INVALID_ARGS;
}
Flags flags = INTERRUPT_VIRTUAL;
if (options & ZX_INTERRUPT_TIMESTAMP_MONO) {
flags = Flags(flags | INTERRUPT_TIMESTAMP_MONO);
}
if (options & ZX_INTERRUPT_WAKE_VECTOR) {
flags = Flags(flags | INTERRUPT_WAKE_VECTOR);
}
// Attempt to construct the dispatcher.
fbl::AllocChecker ac;
KernelHandle new_handle(fbl::AdoptRef(new (&ac) VirtualInterruptDispatcher(flags, options)));
if (!ac.check())
return ZX_ERR_NO_MEMORY;
// Transfer control of the new dispatcher to the creator and we are done.
*rights = default_rights();
*handle = ktl::move(new_handle);
return ZX_OK;
}
VirtualInterruptDispatcher::VirtualInterruptDispatcher(Flags flags, uint32_t options)
: InterruptDispatcher(flags, options) {
// Immediately after construction, virtual interrupt objects are always in the
// untriggered state. Make sure that our initial signal state reflects this.
UpdateState(0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
kcounter_add(dispatcher_virtual_interrupt_create_count, 1);
if (is_wake_vector()) {
InitializeWakeEvent();
}
}
VirtualInterruptDispatcher::~VirtualInterruptDispatcher() {
if (is_wake_vector()) {
DestroyWakeEvent();
}
kcounter_add(dispatcher_virtual_interrupt_destroy_count, 1);
}
void VirtualInterruptDispatcher::GetDiagnostics(WakeVector::Diagnostics& diagnostics_out) const {
diagnostics_out.enabled = is_wake_vector();
diagnostics_out.koid = get_koid();
diagnostics_out.PrintExtra("VirtIRQ %" PRIu64, get_koid());
}
void VirtualInterruptDispatcher::MaskInterrupt() {}
void VirtualInterruptDispatcher::UnmaskInterrupt() {}
void VirtualInterruptDispatcher::DeactivateInterrupt() {}
void VirtualInterruptDispatcher::UnregisterInterruptHandler() {}
zx_status_t VirtualInterruptDispatcher::WaitForInterrupt(zx_time_t* out_timestamp) {
while (true) {
// 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.
{
Guard<CriticalMutex> guard{get_lock()};
const ktl::optional<zx_status_t> opt_status = BeginWaitForInterrupt(out_timestamp);
if (opt_status.has_value()) {
return opt_status.value();
}
UpdateStateLocked(0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
}
const zx_status_t block_status = DoWaitForInterruptBlock();
if (block_status != ZX_OK) {
return block_status;
}
}
}
zx_status_t VirtualInterruptDispatcher::Trigger(zx_time_t timestamp) {
Guard<CriticalMutex> dispatcher_guard{get_lock()};
const zx_status_t result = InterruptDispatcher::Trigger(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 == ZX_OK) {
UpdateStateLocked(ZX_VIRTUAL_INTERRUPT_UNTRIGGERED, 0);
}
return result;
}
zx_status_t VirtualInterruptDispatcher::Ack() {
// Hold the dispatcher lock while we perform our lower level ack operation.
// If this succeeds, make sure that the UNTRIGGERED signal is asserted.
Guard<CriticalMutex> guard{get_lock()};
const zx::result<PostAckState> result = AckInternal();
// If something goes wrong with the ack, then our UNTRIGGERED signal state
// should not have changed.
if (result.is_error()) {
return result.error_value();
}
// 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.
UpdateStateLocked(0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
if (result.value() == PostAckState::Retriggered) {
UpdateStateLocked(ZX_VIRTUAL_INTERRUPT_UNTRIGGERED, 0);
} else {
DEBUG_ASSERT(result.value() == PostAckState::FullyAcked);
}
return ZX_OK;
}
zx_status_t VirtualInterruptDispatcher::Destroy() {
// 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.
Guard<CriticalMutex> guard{get_lock()};
const zx_status_t result = InterruptDispatcher::Destroy();
UpdateStateLocked(0, ZX_VIRTUAL_INTERRUPT_UNTRIGGERED);
return result;
}