1 // Copyright 2016 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6 
7 #include <dev/interrupt.h>
8 #include <object/interrupt_dispatcher.h>
9 #include <object/port_dispatcher.h>
10 #include <object/process_dispatcher.h>
11 #include <platform.h>
12 #include <zircon/syscalls/port.h>
13 
InterruptDispatcher()14 InterruptDispatcher::InterruptDispatcher()
15     : timestamp_(0), state_(InterruptState::IDLE) {
16     event_init(&event_, false, EVENT_FLAG_AUTOUNSIGNAL);
17 }
18 
WaitForInterrupt(zx_time_t * out_timestamp)19 zx_status_t InterruptDispatcher::WaitForInterrupt(zx_time_t* out_timestamp) {
20     while (true) {
21         {
22             Guard<SpinLock, IrqSave> guard{&spinlock_};
23             if (port_dispatcher_ || HasVcpu()) {
24                 return ZX_ERR_BAD_STATE;
25             }
26             switch (state_) {
27             case InterruptState::DESTROYED:
28                 return ZX_ERR_CANCELED;
29             case InterruptState::TRIGGERED:
30                 state_ = InterruptState::NEEDACK;
31                 *out_timestamp = timestamp_;
32                 timestamp_ = 0;
33                 return event_unsignal(&event_);
34             case InterruptState::NEEDACK:
35                 if (flags_ & INTERRUPT_UNMASK_PREWAIT) {
36                     UnmaskInterrupt();
37                 }
38                 break;
39             case InterruptState::IDLE:
40                 break;
41             default:
42                 return ZX_ERR_BAD_STATE;
43             }
44             state_ = InterruptState::WAITING;
45         }
46 
47         {
48             ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::INTERRUPT);
49             zx_status_t status = event_wait_deadline(&event_, ZX_TIME_INFINITE, true);
50             if (status != ZX_OK) {
51                 // The event_wait call was interrupted and we need to retry
52                 // but before we retry we will set the interrupt state
53                 // back to IDLE if we are still in the WAITING state
54                 Guard<SpinLock, IrqSave> guard{&spinlock_};
55                 if (state_ == InterruptState::WAITING) {
56                     state_ = InterruptState::IDLE;
57                 }
58                 return status;
59             }
60         }
61     }
62 }
63 
SendPacketLocked(zx_time_t timestamp)64 bool InterruptDispatcher::SendPacketLocked(zx_time_t timestamp) {
65     bool status = port_dispatcher_->QueueInterruptPacket(&port_packet_, timestamp);
66     if (flags_ & INTERRUPT_MASK_POSTWAIT) {
67         MaskInterrupt();
68     }
69     timestamp_ = 0;
70     return status;
71 }
72 
Trigger(zx_time_t timestamp)73 zx_status_t InterruptDispatcher::Trigger(zx_time_t timestamp) {
74 
75     if (!(flags_ & INTERRUPT_VIRTUAL))
76         return ZX_ERR_BAD_STATE;
77 
78     // Using AutoReschedDisable is necessary for correctness to prevent
79     // context-switching to the woken thread while holding spinlock_.
80     AutoReschedDisable resched_disable;
81     resched_disable.Disable();
82     Guard<SpinLock, IrqSave> guard{&spinlock_};
83 
84     // only record timestamp if this is the first signal since we started waiting
85     if (!timestamp_) {
86         timestamp_ = timestamp;
87     }
88     if (state_ == InterruptState::DESTROYED) {
89         return ZX_ERR_CANCELED;
90     }
91     if (state_ == InterruptState::NEEDACK && port_dispatcher_) {
92         // Cannot trigger a interrupt without ACK
93         // only record timestamp if this is the first signal since we started waiting
94         return ZX_OK;
95     }
96 
97     if (port_dispatcher_) {
98         SendPacketLocked(timestamp);
99         state_ = InterruptState::NEEDACK;
100     } else {
101         Signal();
102         state_ = InterruptState::TRIGGERED;
103     }
104     return ZX_OK;
105 }
106 
InterruptHandler()107 void InterruptDispatcher::InterruptHandler() {
108     Guard<SpinLock, IrqSave> guard{&spinlock_};
109 
110     // only record timestamp if this is the first IRQ since we started waiting
111     if (!timestamp_) {
112         timestamp_ = current_time();
113     }
114     if (state_ == InterruptState::NEEDACK && port_dispatcher_) {
115         return;
116     }
117     if (port_dispatcher_) {
118         SendPacketLocked(timestamp_);
119         state_ = InterruptState::NEEDACK;
120     } else {
121         if (flags_ & INTERRUPT_MASK_POSTWAIT) {
122             MaskInterrupt();
123         }
124         Signal();
125         state_ = InterruptState::TRIGGERED;
126     }
127 }
128 
Destroy()129 zx_status_t InterruptDispatcher::Destroy() {
130     // Using AutoReschedDisable is necessary for correctness to prevent
131     // context-switching to the woken thread while holding spinlock_.
132     AutoReschedDisable resched_disable;
133     resched_disable.Disable();
134     Guard<SpinLock, IrqSave> guard{&spinlock_};
135 
136     MaskInterrupt();
137     UnregisterInterruptHandler();
138 
139     if (port_dispatcher_) {
140         bool packet_was_in_queue = port_dispatcher_->RemoveInterruptPacket(&port_packet_);
141         if ((state_ == InterruptState::NEEDACK) &&
142             !packet_was_in_queue) {
143             state_ = InterruptState::DESTROYED;
144             return ZX_ERR_NOT_FOUND;
145         }
146         if ((state_ == InterruptState::IDLE) ||
147             ((state_ == InterruptState::NEEDACK) &&
148              packet_was_in_queue)) {
149             state_ = InterruptState::DESTROYED;
150             return ZX_OK;
151         }
152     } else {
153         state_ = InterruptState::DESTROYED;
154         Signal();
155     }
156     return ZX_OK;
157 }
158 
Bind(fbl::RefPtr<PortDispatcher> port_dispatcher,uint64_t key)159 zx_status_t InterruptDispatcher::Bind(fbl::RefPtr<PortDispatcher> port_dispatcher, uint64_t key) {
160     Guard<SpinLock, IrqSave> guard{&spinlock_};
161     if (state_ == InterruptState::DESTROYED) {
162         return ZX_ERR_CANCELED;
163     } else if (state_ == InterruptState::WAITING) {
164         return ZX_ERR_BAD_STATE;
165     } else if (port_dispatcher_ || HasVcpu()) {
166         return ZX_ERR_ALREADY_BOUND;
167     }
168 
169     port_dispatcher_ = ktl::move(port_dispatcher);
170     port_packet_.key = key;
171     return ZX_OK;
172 }
173 
Ack()174 zx_status_t InterruptDispatcher::Ack() {
175     // Using AutoReschedDisable is necessary for correctness to prevent
176     // context-switching to the woken thread while holding spinlock_.
177     AutoReschedDisable resched_disable;
178     resched_disable.Disable();
179     Guard<SpinLock, IrqSave> guard{&spinlock_};
180     if (port_dispatcher_ == nullptr) {
181         return ZX_ERR_BAD_STATE;
182     }
183     if (state_ == InterruptState::DESTROYED) {
184         return ZX_ERR_CANCELED;
185     }
186     if (state_ == InterruptState::NEEDACK) {
187         if (flags_ & INTERRUPT_UNMASK_PREWAIT) {
188             UnmaskInterrupt();
189         }
190         if (timestamp_) {
191             if (!SendPacketLocked(timestamp_)) {
192                 // We cannot queue another packet here.
193                 // If we reach here it means that the
194                 // interrupt packet has not been processed,
195                 // another interrupt has occurred & then the
196                 // interrupt was ACK'd
197                 return ZX_ERR_BAD_STATE;
198             }
199         } else {
200             state_ = InterruptState::IDLE;
201         }
202     }
203     return ZX_OK;
204 }
205 
on_zero_handles()206 void InterruptDispatcher::on_zero_handles() {
207     Destroy();
208 }
209