| // 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. |
| |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| #include <lib/zx/timer.h> |
| |
| #include <dispatcher-pool/dispatcher-execution-domain.h> |
| #include <dispatcher-pool/dispatcher-interrupt.h> |
| #include <dispatcher-pool/dispatcher-thread-pool.h> |
| |
| #include <utility> |
| |
| namespace dispatcher { |
| |
| // static |
| fbl::RefPtr<Interrupt> Interrupt::Create() { |
| fbl::AllocChecker ac; |
| |
| auto ptr = new (&ac) Interrupt(); |
| if (!ac.check()) { |
| return nullptr; |
| } |
| |
| return fbl::AdoptRef(ptr); |
| } |
| |
| zx_status_t Interrupt::Activate(fbl::RefPtr<ExecutionDomain> domain, zx::interrupt irq, |
| ProcessHandler process_handler) { |
| if (process_handler == nullptr) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock obj_lock(&obj_lock_); |
| |
| zx_status_t res = ActivateLocked(std::move(irq), std::move(domain)); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| res = WaitOnPortLocked(); |
| if (res != ZX_OK) { |
| InternalDeactivateLocked(); |
| return res; |
| } |
| |
| process_handler_ = std::move(process_handler); |
| |
| return ZX_OK; |
| } |
| |
| void Interrupt::Deactivate() { |
| ProcessHandler old_process_handler; |
| |
| { |
| fbl::AutoLock obj_lock(&obj_lock_); |
| InternalDeactivateLocked(); |
| |
| // If we are in the process of actively dispatching, do not discard our |
| // handler just yet. It is currently being used by the dispatch thread. |
| // Instead, wait until the dispatch thread unwinds and allow it to clean |
| // up the handler. |
| // |
| // Otherwise, transfer the handler state into local storage and let it |
| // destruct after we have released the object lock. |
| if (dispatch_state() != DispatchState::Dispatching) { |
| ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::Idle) || |
| (dispatch_state() == DispatchState::WaitingOnPort)); |
| old_process_handler = std::move(process_handler_); |
| } |
| } |
| } |
| |
| void Interrupt::Dispatch(ExecutionDomain* domain) { |
| ZX_DEBUG_ASSERT(domain != nullptr); |
| ZX_DEBUG_ASSERT(process_handler_ != nullptr); |
| ZX_DEBUG_ASSERT(pending_pkt_.type == ZX_PKT_TYPE_INTERRUPT); |
| |
| zx_status_t res = process_handler_(this, pending_pkt_.interrupt.timestamp); |
| ProcessHandler old_process_handler; |
| { |
| fbl::AutoLock obj_lock(&obj_lock_); |
| ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Dispatching); |
| dispatch_state_ = DispatchState::Idle; |
| |
| // Was there a problem during processing? If so, make sure that we |
| // de-activate ourselves. |
| if (res != ZX_OK) { |
| InternalDeactivateLocked(); |
| } |
| |
| // Are we still active? If so, ack the interrupt so that it can produce |
| // new messages. |
| if (is_active()) { |
| res = WaitOnPortLocked(); |
| |
| if (res != ZX_OK) { |
| dispatch_state_ = DispatchState::Idle; |
| InternalDeactivateLocked(); |
| } |
| } |
| |
| // Have we become deactivated (either during dispatching or just now)? |
| // If so, move our process handler state outside of our lock so that it |
| // can safely destruct. |
| if (!is_active()) { |
| old_process_handler = std::move(process_handler_); |
| } |
| } |
| } |
| |
| zx_status_t Interrupt::DoPortWaitLocked() { |
| // Interrupt Event Sources are a bit different from other event sources |
| // because of the differences in how zircon handles associating a physical |
| // interrupt with a port as compared to other handles.. |
| // |
| // Zircon allows an interrupt to be bound to a port once, at which point in |
| // time it remains bound to the port until it is destroyed. There is no way |
| // to "unbind" an interrupt from a port without destroying the interrupt |
| // object with an explicit call to zx_interrupt_destroy. |
| // |
| // When a zircon interrupt fires while bound to a port, it posts a message |
| // to the port. It then will not post any further messages to the port |
| // until zx_interrupt_ack is explicitly called. |
| // |
| // So, when an Interrupt event source in the dispatcher-pool framework |
| // becomes activated, the first time we "wait-on-port" results in a call to |
| // zx_interrupt_bind (via the ThreadPool's BindIrqToPort). Subsequently, |
| // every time that an interrupt fires and is dispatched, we call |
| // zx_interrupt_ack as part of unwinding after dispatch is complete, |
| // assuming that both execution domain and the interrupt event source are |
| // still active. |
| // |
| // The only time that an interrupt event source is canceled |
| // (DoPortCancelLocked) is as a side effect of de-activation, either because |
| // the specific event source is being shut down, or because the entire |
| // execution domain is being shut down. If a method were to be introduced |
| // to allow users to require manual re-arming of an interrupt event source, |
| // new state would need to be introduced to the Interrupt object to allow |
| // for this. |
| |
| zx_status_t res; |
| if (irq_bound_) { |
| // If we have already been bound, then ack the interrupt it order to |
| // cause it to become re-armed. |
| res = zx_interrupt_ack(handle_.get()); |
| } else { |
| // We have not yet been bound, do so now. |
| ZX_DEBUG_ASSERT(thread_pool_ != nullptr); |
| res = thread_pool_->BindIrqToPort(handle_, reinterpret_cast<uint64_t>(this)); |
| if (res == ZX_OK) { |
| irq_bound_ = true; |
| } |
| } |
| |
| return res; |
| } |
| |
| zx_status_t Interrupt::DoPortCancelLocked() { return zx_interrupt_destroy(handle_.get()); } |
| |
| } // namespace dispatcher |