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