blob: 416a45dc1f3936b0e8cb7acdf6063d77dc7e6fcc [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/interrupt_dispatcher.h"
#include <platform.h>
#include <zircon/syscalls/port.h>
#include <dev/interrupt.h>
#include <kernel/auto_preempt_disabler.h>
#include <object/port_dispatcher.h>
#include <object/process_dispatcher.h>
InterruptDispatcher::InterruptDispatcher() : timestamp_(0), state_(InterruptState::IDLE) {}
zx_status_t InterruptDispatcher::WaitForInterrupt(zx_time_t* out_timestamp) {
bool defer_unmask = false;
while (true) {
{
Guard<SpinLock, IrqSave> guard{&spinlock_};
if (port_dispatcher_ || HasVcpu()) {
return ZX_ERR_BAD_STATE;
}
switch (state_) {
case InterruptState::DESTROYED:
return ZX_ERR_CANCELED;
case InterruptState::TRIGGERED:
state_ = InterruptState::NEEDACK;
*out_timestamp = timestamp_;
timestamp_ = 0;
return event_.Unsignal();
case InterruptState::NEEDACK:
if (flags_ & INTERRUPT_UNMASK_PREWAIT) {
UnmaskInterrupt();
} else if (flags_ & INTERRUPT_UNMASK_PREWAIT_UNLOCKED) {
defer_unmask = true;
}
break;
case InterruptState::IDLE:
break;
default:
return ZX_ERR_BAD_STATE;
}
state_ = InterruptState::WAITING;
}
if (defer_unmask) {
UnmaskInterrupt();
}
{
ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::INTERRUPT);
zx_status_t status = event_.Wait(Deadline::infinite());
if (status != ZX_OK) {
// The Event::Wait call was interrupted and we need to retry
// but before we retry we will set the interrupt state
// back to IDLE if we are still in the WAITING state
Guard<SpinLock, IrqSave> guard{&spinlock_};
if (state_ == InterruptState::WAITING) {
state_ = InterruptState::IDLE;
}
return status;
}
}
}
}
bool InterruptDispatcher::SendPacketLocked(zx_time_t timestamp) {
bool status = port_dispatcher_->QueueInterruptPacket(&port_packet_, timestamp);
if (flags_ & INTERRUPT_MASK_POSTWAIT) {
MaskInterrupt();
}
timestamp_ = 0;
return status;
}
zx_status_t InterruptDispatcher::Trigger(zx_time_t timestamp) {
if (!(flags_ & INTERRUPT_VIRTUAL))
return ZX_ERR_BAD_STATE;
// Use preempt disable for correctness to prevent rescheduling when waking a
// thread while holding the spinlock.
AutoPreemptDisabler preempt_disable;
Guard<SpinLock, IrqSave> guard{&spinlock_};
// only record timestamp if this is the first signal since we started waiting
if (!timestamp_) {
timestamp_ = timestamp;
}
if (state_ == InterruptState::DESTROYED) {
return ZX_ERR_CANCELED;
}
if (state_ == InterruptState::NEEDACK && port_dispatcher_) {
// Cannot trigger a interrupt without ACK
// only record timestamp if this is the first signal since we started waiting
return ZX_OK;
}
if (port_dispatcher_) {
SendPacketLocked(timestamp);
state_ = InterruptState::NEEDACK;
} else {
Signal();
state_ = InterruptState::TRIGGERED;
}
return ZX_OK;
}
void InterruptDispatcher::InterruptHandler() {
// Using preempt disable is not necessary for correctness, since we should
// be in an interrupt context with preemption disabled, but we re-disable anyway
// for clarity and robustness.
AutoPreemptDisabler preempt_disable;
Guard<SpinLock, IrqSave> guard{&spinlock_};
// only record timestamp if this is the first IRQ since we started waiting
if (!timestamp_) {
timestamp_ = current_time();
}
if (state_ == InterruptState::NEEDACK && port_dispatcher_) {
return;
}
if (port_dispatcher_) {
SendPacketLocked(timestamp_);
state_ = InterruptState::NEEDACK;
} else {
if (flags_ & INTERRUPT_MASK_POSTWAIT) {
MaskInterrupt();
}
Signal();
state_ = InterruptState::TRIGGERED;
}
}
zx_status_t InterruptDispatcher::Destroy() {
// The interrupt may presently have been fired and we could already be about to acquire the
// spinlock_ in InterruptHandler. If we were to call UnregisterInterruptHandler whilst holding
// the spinlock_ then we risk a deadlock scenario where the platform interrupt code may have
// taken a lock to call InterruptHandler, and it might take the same lock when we call
// UnregisterInterruptHandler.
MaskInterrupt();
DeactivateInterrupt();
UnregisterInterruptHandler();
// Use preempt disable for correctness to prevent rescheduling when waking a
// thread while holding the spinlock.
AutoPreemptDisabler preempt_disable;
Guard<SpinLock, IrqSave> guard{&spinlock_};
if (port_dispatcher_) {
bool packet_was_in_queue = port_dispatcher_->RemoveInterruptPacket(&port_packet_);
if ((state_ == InterruptState::NEEDACK) && !packet_was_in_queue) {
state_ = InterruptState::DESTROYED;
return ZX_ERR_NOT_FOUND;
}
if ((state_ == InterruptState::IDLE) ||
((state_ == InterruptState::NEEDACK) && packet_was_in_queue)) {
state_ = InterruptState::DESTROYED;
return ZX_OK;
}
} else {
state_ = InterruptState::DESTROYED;
Signal();
}
return ZX_OK;
}
zx_status_t InterruptDispatcher::Bind(fbl::RefPtr<PortDispatcher> port_dispatcher, uint64_t key) {
Guard<SpinLock, IrqSave> guard{&spinlock_};
if (state_ == InterruptState::DESTROYED) {
return ZX_ERR_CANCELED;
} else if (state_ == InterruptState::WAITING) {
return ZX_ERR_BAD_STATE;
} else if (port_dispatcher_ || HasVcpu()) {
return ZX_ERR_ALREADY_BOUND;
}
// If an interrupt is bound to a port there is a conflict between UNMASK_PREWAIT_UNLOCKED
// and MASK_POSTWAIT because the mask operation will by necessity happen before leaving the
// dispatcher spinlock, leading to a mask operation immediately followed by the deferred
// unmask operation.
if ((flags_ & INTERRUPT_UNMASK_PREWAIT_UNLOCKED) && (flags_ & INTERRUPT_MASK_POSTWAIT)) {
return ZX_ERR_INVALID_ARGS;
}
port_dispatcher_ = ktl::move(port_dispatcher);
port_packet_.key = key;
if (state_ == InterruptState::TRIGGERED) {
SendPacketLocked(timestamp_);
state_ = InterruptState::NEEDACK;
}
return ZX_OK;
}
zx_status_t InterruptDispatcher::Unbind(fbl::RefPtr<PortDispatcher> port_dispatcher) {
// Moving port_dispatcher_ to the local variable ensures it will not be destroyed while
// holding this spinlock.
fbl::RefPtr<PortDispatcher> dispatcher;
{
Guard<SpinLock, IrqSave> guard{&spinlock_};
if (port_dispatcher_ != port_dispatcher) {
// This case also covers the HasVcpu() case.
return ZX_ERR_NOT_FOUND;
}
if (state_ == InterruptState::DESTROYED) {
return ZX_ERR_CANCELED;
}
// Remove the packet for this interrupt from this port on an unbind before actually
// doing the unbind. This protects against the case where the interrupt dispatcher
// goes away between an unbind and a port_wait.
port_dispatcher_->RemoveInterruptPacket(&port_packet_);
port_packet_.key = 0;
dispatcher.swap(port_dispatcher_);
}
return ZX_OK;
}
zx_status_t InterruptDispatcher::Ack() {
bool defer_unmask = false;
// Use preempt disable to reduce the likelihood of the woken thread running
// while the spinlock is still held.
AutoPreemptDisabler preempt_disable;
{
Guard<SpinLock, IrqSave> guard{&spinlock_};
if (port_dispatcher_ == nullptr) {
return ZX_ERR_BAD_STATE;
}
if (state_ == InterruptState::DESTROYED) {
return ZX_ERR_CANCELED;
}
if (state_ == InterruptState::NEEDACK) {
if (flags_ & INTERRUPT_UNMASK_PREWAIT) {
UnmaskInterrupt();
} else if (flags_ & INTERRUPT_UNMASK_PREWAIT_UNLOCKED) {
defer_unmask = true;
}
if (timestamp_) {
if (!SendPacketLocked(timestamp_)) {
// We cannot queue another packet here.
// If we reach here it means that the
// interrupt packet has not been processed,
// another interrupt has occurred & then the
// interrupt was ACK'd
return ZX_ERR_BAD_STATE;
}
} else {
state_ = InterruptState::IDLE;
}
}
}
if (defer_unmask) {
UnmaskInterrupt();
}
return ZX_OK;
}
zx_status_t InterruptDispatcher::set_flags(uint32_t flags) {
if ((flags & INTERRUPT_UNMASK_PREWAIT) && (flags & INTERRUPT_UNMASK_PREWAIT_UNLOCKED)) {
return ZX_ERR_INVALID_ARGS;
}
flags_ = flags;
return ZX_OK;
}
void InterruptDispatcher::on_zero_handles() { Destroy(); }