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
14InterruptDispatcher::InterruptDispatcher()
15    : timestamp_(0), state_(InterruptState::IDLE) {
16    event_init(&event_, false, EVENT_FLAG_AUTOUNSIGNAL);
17}
18
19zx_status_t InterruptDispatcher::WaitForInterrupt(zx_time_t* out_timestamp) {
20    while (true) {
21        {
22            Guard<SpinLock, IrqSave> guard{&spinlock_};
23            if (port_dispatcher_) {
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
64bool 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
73zx_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
107void 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
129zx_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
159zx_status_t InterruptDispatcher::Bind(fbl::RefPtr<PortDispatcher> port_dispatcher,
160                                      fbl::RefPtr<InterruptDispatcher> interrupt, uint64_t key) {
161    Guard<SpinLock, IrqSave> guard{&spinlock_};
162    if (state_ == InterruptState::DESTROYED) {
163        return ZX_ERR_CANCELED;
164    }
165    if (port_dispatcher_) {
166        return ZX_ERR_ALREADY_BOUND;
167    }
168    if (state_ == InterruptState::WAITING) {
169        return ZX_ERR_BAD_STATE;
170    }
171
172    port_dispatcher_ = fbl::move(port_dispatcher);
173    port_packet_.key = key;
174    return ZX_OK;
175}
176
177zx_status_t InterruptDispatcher::Ack() {
178    // Using AutoReschedDisable is necessary for correctness to prevent
179    // context-switching to the woken thread while holding spinlock_.
180    AutoReschedDisable resched_disable;
181    resched_disable.Disable();
182    Guard<SpinLock, IrqSave> guard{&spinlock_};
183    if (port_dispatcher_ == nullptr) {
184        return ZX_ERR_BAD_STATE;
185    }
186    if (state_ == InterruptState::DESTROYED) {
187        return ZX_ERR_CANCELED;
188    }
189    if (state_ == InterruptState::NEEDACK) {
190        if (flags_ & INTERRUPT_UNMASK_PREWAIT) {
191            UnmaskInterrupt();
192        }
193        if (timestamp_) {
194            if (!SendPacketLocked(timestamp_)) {
195                // We cannot queue another packet here.
196                // If we reach here it means that the
197                // interrupt packet has not been processed,
198                // another interrupt has occurred & then the
199                // interrupt was ACK'd
200                return ZX_ERR_BAD_STATE;
201            }
202        } else {
203            state_ = InterruptState::IDLE;
204        }
205    }
206    return ZX_OK;
207}
208
209void InterruptDispatcher::on_zero_handles() {
210    Destroy();
211}
212