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