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