1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <zircon/syscalls.h>
6 #include <zircon/types.h>
7 #include <lib/zx/timer.h>
8
9 #include <dispatcher-pool/dispatcher-execution-domain.h>
10 #include <dispatcher-pool/dispatcher-interrupt.h>
11 #include <dispatcher-pool/dispatcher-thread-pool.h>
12
13 #include <utility>
14
15 namespace dispatcher {
16
17 // static
Create()18 fbl::RefPtr<Interrupt> Interrupt::Create() {
19 fbl::AllocChecker ac;
20
21 auto ptr = new (&ac) Interrupt();
22 if (!ac.check()) {
23 return nullptr;
24 }
25
26 return fbl::AdoptRef(ptr);
27 }
28
Activate(fbl::RefPtr<ExecutionDomain> domain,zx::interrupt irq,ProcessHandler process_handler)29 zx_status_t Interrupt::Activate(fbl::RefPtr<ExecutionDomain> domain,
30 zx::interrupt irq,
31 ProcessHandler process_handler) {
32 if (process_handler == nullptr) {
33 return ZX_ERR_INVALID_ARGS;
34 }
35
36 fbl::AutoLock obj_lock(&obj_lock_);
37
38 zx_status_t res = ActivateLocked(std::move(irq), std::move(domain));
39 if (res != ZX_OK) {
40 return res;
41 }
42
43 res = WaitOnPortLocked();
44 if (res != ZX_OK) {
45 InternalDeactivateLocked();
46 return res;
47 }
48
49 process_handler_ = std::move(process_handler);
50
51 return ZX_OK;
52 }
53
Deactivate()54 void Interrupt::Deactivate() {
55 ProcessHandler old_process_handler;
56
57 {
58 fbl::AutoLock obj_lock(&obj_lock_);
59 InternalDeactivateLocked();
60
61 // If we are in the process of actively dispatching, do not discard our
62 // handler just yet. It is currently being used by the dispatch thread.
63 // Instead, wait until the dispatch thread unwinds and allow it to clean
64 // up the handler.
65 //
66 // Otherwise, transfer the handler state into local storage and let it
67 // destruct after we have released the object lock.
68 if (dispatch_state() != DispatchState::Dispatching) {
69 ZX_DEBUG_ASSERT((dispatch_state() == DispatchState::Idle) ||
70 (dispatch_state() == DispatchState::WaitingOnPort));
71 old_process_handler = std::move(process_handler_);
72 }
73 }
74 }
75
Dispatch(ExecutionDomain * domain)76 void Interrupt::Dispatch(ExecutionDomain* domain) {
77 ZX_DEBUG_ASSERT(domain != nullptr);
78 ZX_DEBUG_ASSERT(process_handler_ != nullptr);
79 ZX_DEBUG_ASSERT(pending_pkt_.type == ZX_PKT_TYPE_INTERRUPT);
80
81 zx_status_t res = process_handler_(this, pending_pkt_.interrupt.timestamp);
82 ProcessHandler old_process_handler;
83 {
84 fbl::AutoLock obj_lock(&obj_lock_);
85 ZX_DEBUG_ASSERT(dispatch_state() == DispatchState::Dispatching);
86 dispatch_state_ = DispatchState::Idle;
87
88 // Was there a problem during processing? If so, make sure that we
89 // de-activate ourselves.
90 if (res != ZX_OK) {
91 InternalDeactivateLocked();
92 }
93
94 // Are we still active? If so, ack the interrupt so that it can produce
95 // new messages.
96 if (is_active()) {
97 res = WaitOnPortLocked();
98
99 if (res != ZX_OK) {
100 dispatch_state_ = DispatchState::Idle;
101 InternalDeactivateLocked();
102 }
103 }
104
105 // Have we become deactivated (either during dispatching or just now)?
106 // If so, move our process handler state outside of our lock so that it
107 // can safely destruct.
108 if (!is_active()) {
109 old_process_handler = std::move(process_handler_);
110 }
111 }
112 }
113
DoPortWaitLocked()114 zx_status_t Interrupt::DoPortWaitLocked() {
115 // Interrupt Event Sources are a bit different from other event sources
116 // because of the differences in how zircon handles associating a physical
117 // interrupt with a port as compared to other handles..
118 //
119 // Zircon allows an interrupt to be bound to a port once, at which point in
120 // time it remains bound to the port until it is destroyed. There is no way
121 // to "unbind" an interrupt from a port without destroying the interrupt
122 // object with an explicit call to zx_interrupt_destroy.
123 //
124 // When a zircon interrupt fires while bound to a port, it posts a message
125 // to the port. It then will not post any further messages to the port
126 // until zx_interrupt_ack is explicitly called.
127 //
128 // So, when an Interrupt event source in the dispatcher-pool framework
129 // becomes activated, the first time we "wait-on-port" results in a call to
130 // zx_interrupt_bind (via the ThreadPool's BindIrqToPort). Subsequently,
131 // every time that an interrupt fires and is dispatched, we call
132 // zx_interrupt_ack as part of unwinding after dispatch is complete,
133 // assuming that both execution domain and the interrupt event source are
134 // still active.
135 //
136 // The only time that an interrupt event source is canceled
137 // (DoPortCancelLocked) is as a side effect of de-activation, either because
138 // the specific event source is being shut down, or because the entire
139 // execution domain is being shut down. If a method were to be introduced
140 // to allow users to require manual re-arming of an interrupt event source,
141 // new state would need to be introduced to the Interrupt object to allow
142 // for this.
143
144 zx_status_t res;
145 if (irq_bound_) {
146 // If we have already been bound, then ack the interrupt it order to
147 // cause it to become re-armed.
148 res = zx_interrupt_ack(handle_.get());
149 } else {
150 // We have not yet been bound, do so now.
151 ZX_DEBUG_ASSERT(thread_pool_ != nullptr);
152 res = thread_pool_->BindIrqToPort(handle_, reinterpret_cast<uint64_t>(this));
153 if (res == ZX_OK) {
154 irq_bound_ = true;
155 }
156 }
157
158 return res;
159 }
160
DoPortCancelLocked()161 zx_status_t Interrupt::DoPortCancelLocked() {
162 return zx_interrupt_destroy(handle_.get());
163 }
164
165 } // namespace dispatcher
166