1/*
2 * Copyright 2014, General Dynamics C4 Systems
3 *
4 * This software may be distributed and modified according to the terms of
5 * the GNU General Public License version 2. Note that NO WARRANTY is provided.
6 * See "LICENSE_GPLv2.txt" for details.
7 *
8 * @TAG(GD_GPL)
9 */
10
11#include <assert.h>
12
13#include <types.h>
14#include <kernel/thread.h>
15#include <object/structures.h>
16#include <object/tcb.h>
17#include <object/endpoint.h>
18#include <model/statedata.h>
19#include <machine/io.h>
20
21#include <object/notification.h>
22
23static inline tcb_queue_t PURE
24ntfn_ptr_get_queue(notification_t *ntfnPtr)
25{
26    tcb_queue_t ntfn_queue;
27
28    ntfn_queue.head = (tcb_t*)notification_ptr_get_ntfnQueue_head(ntfnPtr);
29    ntfn_queue.end = (tcb_t*)notification_ptr_get_ntfnQueue_tail(ntfnPtr);
30
31    return ntfn_queue;
32}
33
34static inline void
35ntfn_ptr_set_queue(notification_t *ntfnPtr, tcb_queue_t ntfn_queue)
36{
37    notification_ptr_set_ntfnQueue_head(ntfnPtr, (word_t)ntfn_queue.head);
38    notification_ptr_set_ntfnQueue_tail(ntfnPtr, (word_t)ntfn_queue.end);
39}
40
41static inline void
42ntfn_set_active(notification_t *ntfnPtr, word_t badge)
43{
44    notification_ptr_set_state(ntfnPtr, NtfnState_Active);
45    notification_ptr_set_ntfnMsgIdentifier(ntfnPtr, badge);
46}
47
48static inline void
49maybeDonateSchedContext(tcb_t *tcb, notification_t *ntfnPtr)
50{
51    if (tcb->tcbSchedContext == NULL) {
52        sched_context_t *sc = SC_PTR(notification_ptr_get_ntfnSchedContext(ntfnPtr));
53        if (sc != NULL && sc->scTcb == NULL) {
54            schedContext_donate(sc, tcb);
55        }
56    }
57}
58
59static inline void
60maybeReturnSchedContext(notification_t *ntfnPtr, tcb_t *tcb)
61{
62
63    sched_context_t *sc = SC_PTR(notification_ptr_get_ntfnSchedContext(ntfnPtr));
64    if (sc == tcb->tcbSchedContext) {
65        tcb->tcbSchedContext = NULL;
66        sc->scTcb = NULL;
67    }
68}
69
70void
71sendSignal(notification_t *ntfnPtr, word_t badge)
72{
73    switch (notification_ptr_get_state(ntfnPtr)) {
74    case NtfnState_Idle: {
75        tcb_t *tcb = (tcb_t*)notification_ptr_get_ntfnBoundTCB(ntfnPtr);
76        /* Check if we are bound and that thread is waiting for a message */
77        if (tcb) {
78            if (thread_state_ptr_get_tsType(&tcb->tcbState) == ThreadState_BlockedOnReceive) {
79                /* Send and start thread running */
80                cancelIPC(tcb);
81                maybeDonateSchedContext(tcb, ntfnPtr);
82                setThreadState(tcb, ThreadState_Running);
83                setRegister(tcb, badgeRegister, badge);
84                possibleSwitchTo(tcb);
85#ifdef CONFIG_VTX
86            } else if (thread_state_ptr_get_tsType(&tcb->tcbState) == ThreadState_RunningVM) {
87#ifdef ENABLE_SMP_SUPPORT
88                if (tcb->tcbAffinity != getCurrentCPUIndex()) {
89                    ntfn_set_active(ntfnPtr, badge);
90                    doRemoteVMCheckBoundNotification(tcb->tcbAffinity, tcb);
91                } else
92#endif /* ENABLE_SMP_SUPPORT */
93                {
94                    maybeDonateSchedContext(tcb, ntfnPtr);
95                    setThreadState(tcb, ThreadState_Running);
96                    setRegister(tcb, badgeRegister, badge);
97                    Arch_leaveVMAsyncTransfer(tcb);
98                    possibleSwitchTo(tcb);
99                }
100#endif /* CONFIG_VTX */
101            } else {
102                /* In particular, this path is taken when a thread
103                 * is waiting on a reply cap since BlockedOnReply
104                 * would also trigger this path. I.e, a thread
105                 * with a bound notification will not be awakened
106                 * by signals on that bound notification if it is
107                 * in the middle of an seL4_Call.
108                 */
109                ntfn_set_active(ntfnPtr, badge);
110            }
111        } else {
112            ntfn_set_active(ntfnPtr, badge);
113        }
114        break;
115    }
116    case NtfnState_Waiting: {
117        tcb_queue_t ntfn_queue;
118        tcb_t *dest;
119
120        ntfn_queue = ntfn_ptr_get_queue(ntfnPtr);
121        dest = ntfn_queue.head;
122
123        /* Haskell error "WaitingNtfn Notification must have non-empty queue" */
124        assert(dest);
125
126        /* Dequeue TCB */
127        ntfn_queue = tcbEPDequeue(dest, ntfn_queue);
128        ntfn_ptr_set_queue(ntfnPtr, ntfn_queue);
129
130        /* set the thread state to idle if the queue is empty */
131        if (!ntfn_queue.head) {
132            notification_ptr_set_state(ntfnPtr, NtfnState_Idle);
133        }
134
135        maybeDonateSchedContext(dest, ntfnPtr);
136        setThreadState(dest, ThreadState_Running);
137        setRegister(dest, badgeRegister, badge);
138        possibleSwitchTo(dest);
139        break;
140    }
141
142    case NtfnState_Active: {
143        word_t badge2;
144
145        badge2 = notification_ptr_get_ntfnMsgIdentifier(ntfnPtr);
146        badge2 |= badge;
147
148        notification_ptr_set_ntfnMsgIdentifier(ntfnPtr, badge2);
149        break;
150    }
151    }
152}
153
154void
155receiveSignal(tcb_t *thread, cap_t cap, bool_t isBlocking)
156{
157    notification_t *ntfnPtr;
158
159    ntfnPtr = NTFN_PTR(cap_notification_cap_get_capNtfnPtr(cap));
160
161    switch (notification_ptr_get_state(ntfnPtr)) {
162    case NtfnState_Idle:
163    case NtfnState_Waiting: {
164        tcb_queue_t ntfn_queue;
165
166        if (isBlocking) {
167            /* Block thread on notification object */
168            thread_state_ptr_set_tsType(&thread->tcbState,
169                                        ThreadState_BlockedOnNotification);
170            thread_state_ptr_set_blockingObject(&thread->tcbState,
171                                                NTFN_REF(ntfnPtr));
172            maybeReturnSchedContext(ntfnPtr, thread);
173            scheduleTCB(thread);
174
175            /* Enqueue TCB */
176            ntfn_queue = ntfn_ptr_get_queue(ntfnPtr);
177            ntfn_queue = tcbEPAppend(thread, ntfn_queue);
178
179            notification_ptr_set_state(ntfnPtr, NtfnState_Waiting);
180            ntfn_ptr_set_queue(ntfnPtr, ntfn_queue);
181        } else {
182            doNBRecvFailedTransfer(thread);
183        }
184
185        break;
186    }
187
188    case NtfnState_Active:
189        setRegister(
190            thread, badgeRegister,
191            notification_ptr_get_ntfnMsgIdentifier(ntfnPtr));
192        notification_ptr_set_state(ntfnPtr, NtfnState_Idle);
193        maybeDonateSchedContext(thread, ntfnPtr);
194        break;
195    }
196}
197
198void
199cancelAllSignals(notification_t *ntfnPtr)
200{
201    if (notification_ptr_get_state(ntfnPtr) == NtfnState_Waiting) {
202        tcb_t *thread = TCB_PTR(notification_ptr_get_ntfnQueue_head(ntfnPtr));
203
204        notification_ptr_set_state(ntfnPtr, NtfnState_Idle);
205        notification_ptr_set_ntfnQueue_head(ntfnPtr, 0);
206        notification_ptr_set_ntfnQueue_tail(ntfnPtr, 0);
207
208        /* Set all waiting threads to Restart */
209        for (; thread; thread = thread->tcbEPNext) {
210            setThreadState(thread, ThreadState_Restart);
211            possibleSwitchTo(thread);
212        }
213        rescheduleRequired();
214    }
215}
216
217void
218cancelSignal(tcb_t *threadPtr, notification_t *ntfnPtr)
219{
220    tcb_queue_t ntfn_queue;
221
222    /* Haskell error "cancelSignal: notification object must be in a waiting" state */
223    assert(notification_ptr_get_state(ntfnPtr) == NtfnState_Waiting);
224
225    /* Dequeue TCB */
226    ntfn_queue = ntfn_ptr_get_queue(ntfnPtr);
227    ntfn_queue = tcbEPDequeue(threadPtr, ntfn_queue);
228    ntfn_ptr_set_queue(ntfnPtr, ntfn_queue);
229
230    /* Make notification object idle */
231    if (!ntfn_queue.head) {
232        notification_ptr_set_state(ntfnPtr, NtfnState_Idle);
233    }
234
235    /* Make thread inactive */
236    setThreadState(threadPtr, ThreadState_Inactive);
237}
238
239void
240completeSignal(notification_t *ntfnPtr, tcb_t *tcb)
241{
242    word_t badge;
243
244    if (likely(tcb && notification_ptr_get_state(ntfnPtr) == NtfnState_Active)) {
245        badge = notification_ptr_get_ntfnMsgIdentifier(ntfnPtr);
246        setRegister(tcb, badgeRegister, badge);
247        notification_ptr_set_state(ntfnPtr, NtfnState_Idle);
248    } else {
249        fail("tried to complete signal with inactive notification object");
250    }
251}
252
253static inline void
254doUnbindNotification(notification_t *ntfnPtr, tcb_t *tcbptr)
255{
256    notification_ptr_set_ntfnBoundTCB(ntfnPtr, (word_t) 0);
257    tcbptr->tcbBoundNotification = NULL;
258}
259
260void
261unbindMaybeNotification(notification_t *ntfnPtr)
262{
263    tcb_t *boundTCB;
264    boundTCB = (tcb_t*)notification_ptr_get_ntfnBoundTCB(ntfnPtr);
265
266    if (boundTCB) {
267        doUnbindNotification(ntfnPtr, boundTCB);
268    }
269}
270
271void
272unbindNotification(tcb_t *tcb)
273{
274    notification_t *ntfnPtr;
275    ntfnPtr = tcb->tcbBoundNotification;
276
277    if (ntfnPtr) {
278        doUnbindNotification(ntfnPtr, tcb);
279    }
280}
281
282void
283bindNotification(tcb_t *tcb, notification_t *ntfnPtr)
284{
285    notification_ptr_set_ntfnBoundTCB(ntfnPtr, (word_t)tcb);
286    tcb->tcbBoundNotification = ntfnPtr;
287}
288
289void
290reorderNTFN(notification_t *ntfnPtr, tcb_t *thread)
291{
292    tcb_queue_t queue = ntfn_ptr_get_queue(ntfnPtr);
293    queue = tcbEPDequeue(thread, queue);
294    queue = tcbEPAppend(thread, queue);
295    ntfn_ptr_set_queue(ntfnPtr, queue);
296}
297