/* * Copyright 2014, General Dynamics C4 Systems * * SPDX-License-Identifier: GPL-2.0-only */ #include #include #include #include #include #include #include #include #include static inline tcb_queue_t PURE ntfn_ptr_get_queue(notification_t *ntfnPtr) { tcb_queue_t ntfn_queue; ntfn_queue.head = (tcb_t *)notification_ptr_get_ntfnQueue_head(ntfnPtr); ntfn_queue.end = (tcb_t *)notification_ptr_get_ntfnQueue_tail(ntfnPtr); return ntfn_queue; } static inline void ntfn_ptr_set_queue(notification_t *ntfnPtr, tcb_queue_t ntfn_queue) { notification_ptr_set_ntfnQueue_head(ntfnPtr, (word_t)ntfn_queue.head); notification_ptr_set_ntfnQueue_tail(ntfnPtr, (word_t)ntfn_queue.end); } static inline void ntfn_set_active(notification_t *ntfnPtr, word_t badge) { notification_ptr_set_state(ntfnPtr, NtfnState_Active); notification_ptr_set_ntfnMsgIdentifier(ntfnPtr, badge); } #ifdef CONFIG_KERNEL_MCS static inline void maybeDonateSchedContext(tcb_t *tcb, notification_t *ntfnPtr) { if (tcb->tcbSchedContext == NULL) { sched_context_t *sc = SC_PTR(notification_ptr_get_ntfnSchedContext(ntfnPtr)); if (sc != NULL && sc->scTcb == NULL) { schedContext_donate(sc, tcb); if (sc != NODE_STATE(ksCurSC)) { /* refill_unblock_check should not be called on the * current SC as it is already running. The current SC * may have been bound to a notificaiton object if the * current thread was deleted in a long-running deletion * that became preempted. */ refill_unblock_check(sc); } schedContext_resume(sc); } } } #endif #ifdef CONFIG_KERNEL_MCS #define MCS_DO_IF_SC(tcb, ntfnPtr, _block) \ maybeDonateSchedContext(tcb, ntfnPtr); \ if (isSchedulable(tcb)) { \ _block \ } #else #define MCS_DO_IF_SC(tcb, ntfnPtr, _block) \ { \ _block \ } #endif void sendSignal(notification_t *ntfnPtr, word_t badge) { switch (notification_ptr_get_state(ntfnPtr)) { case NtfnState_Idle: { tcb_t *tcb = (tcb_t *)notification_ptr_get_ntfnBoundTCB(ntfnPtr); /* Check if we are bound and that thread is waiting for a message */ if (tcb) { if (thread_state_ptr_get_tsType(&tcb->tcbState) == ThreadState_BlockedOnReceive) { /* Send and start thread running */ cancelIPC(tcb); setThreadState(tcb, ThreadState_Running); setRegister(tcb, badgeRegister, badge); MCS_DO_IF_SC(tcb, ntfnPtr, { possibleSwitchTo(tcb); }) #ifdef CONFIG_VTX } else if (thread_state_ptr_get_tsType(&tcb->tcbState) == ThreadState_RunningVM) { #ifdef ENABLE_SMP_SUPPORT if (tcb->tcbAffinity != getCurrentCPUIndex()) { ntfn_set_active(ntfnPtr, badge); doRemoteVMCheckBoundNotification(tcb->tcbAffinity, tcb); } else #endif /* ENABLE_SMP_SUPPORT */ { setThreadState(tcb, ThreadState_Running); setRegister(tcb, badgeRegister, badge); Arch_leaveVMAsyncTransfer(tcb); MCS_DO_IF_SC(tcb, ntfnPtr, { possibleSwitchTo(tcb); }) } #endif /* CONFIG_VTX */ } else { /* In particular, this path is taken when a thread * is waiting on a reply cap since BlockedOnReply * would also trigger this path. I.e, a thread * with a bound notification will not be awakened * by signals on that bound notification if it is * in the middle of an seL4_Call. */ ntfn_set_active(ntfnPtr, badge); } } else { ntfn_set_active(ntfnPtr, badge); } break; } case NtfnState_Waiting: { tcb_queue_t ntfn_queue; tcb_t *dest; ntfn_queue = ntfn_ptr_get_queue(ntfnPtr); dest = ntfn_queue.head; /* Haskell error "WaitingNtfn Notification must have non-empty queue" */ assert(dest); /* Dequeue TCB */ ntfn_queue = tcbEPDequeue(dest, ntfn_queue); ntfn_ptr_set_queue(ntfnPtr, ntfn_queue); /* set the thread state to idle if the queue is empty */ if (!ntfn_queue.head) { notification_ptr_set_state(ntfnPtr, NtfnState_Idle); } setThreadState(dest, ThreadState_Running); setRegister(dest, badgeRegister, badge); MCS_DO_IF_SC(dest, ntfnPtr, { possibleSwitchTo(dest); }) break; } case NtfnState_Active: { word_t badge2; badge2 = notification_ptr_get_ntfnMsgIdentifier(ntfnPtr); badge2 |= badge; notification_ptr_set_ntfnMsgIdentifier(ntfnPtr, badge2); break; } } } void receiveSignal(tcb_t *thread, cap_t cap, bool_t isBlocking) { notification_t *ntfnPtr; ntfnPtr = NTFN_PTR(cap_notification_cap_get_capNtfnPtr(cap)); switch (notification_ptr_get_state(ntfnPtr)) { case NtfnState_Idle: case NtfnState_Waiting: { tcb_queue_t ntfn_queue; if (isBlocking) { /* Block thread on notification object */ thread_state_ptr_set_tsType(&thread->tcbState, ThreadState_BlockedOnNotification); thread_state_ptr_set_blockingObject(&thread->tcbState, NTFN_REF(ntfnPtr)); #ifdef CONFIG_KERNEL_MCS maybeReturnSchedContext(ntfnPtr, thread); #endif scheduleTCB(thread); /* Enqueue TCB */ ntfn_queue = ntfn_ptr_get_queue(ntfnPtr); ntfn_queue = tcbEPAppend(thread, ntfn_queue); notification_ptr_set_state(ntfnPtr, NtfnState_Waiting); ntfn_ptr_set_queue(ntfnPtr, ntfn_queue); } else { doNBRecvFailedTransfer(thread); } break; } case NtfnState_Active: setRegister( thread, badgeRegister, notification_ptr_get_ntfnMsgIdentifier(ntfnPtr)); notification_ptr_set_state(ntfnPtr, NtfnState_Idle); #ifdef CONFIG_KERNEL_MCS maybeDonateSchedContext(thread, ntfnPtr); #endif break; } } void cancelAllSignals(notification_t *ntfnPtr) { if (notification_ptr_get_state(ntfnPtr) == NtfnState_Waiting) { tcb_t *thread = TCB_PTR(notification_ptr_get_ntfnQueue_head(ntfnPtr)); notification_ptr_set_state(ntfnPtr, NtfnState_Idle); notification_ptr_set_ntfnQueue_head(ntfnPtr, 0); notification_ptr_set_ntfnQueue_tail(ntfnPtr, 0); /* Set all waiting threads to Restart */ for (; thread; thread = thread->tcbEPNext) { setThreadState(thread, ThreadState_Restart); #ifdef CONFIG_KERNEL_MCS possibleSwitchTo(thread); #else SCHED_ENQUEUE(thread); #endif } rescheduleRequired(); } } void cancelSignal(tcb_t *threadPtr, notification_t *ntfnPtr) { tcb_queue_t ntfn_queue; /* Haskell error "cancelSignal: notification object must be in a waiting" state */ assert(notification_ptr_get_state(ntfnPtr) == NtfnState_Waiting); /* Dequeue TCB */ ntfn_queue = ntfn_ptr_get_queue(ntfnPtr); ntfn_queue = tcbEPDequeue(threadPtr, ntfn_queue); ntfn_ptr_set_queue(ntfnPtr, ntfn_queue); /* Make notification object idle */ if (!ntfn_queue.head) { notification_ptr_set_state(ntfnPtr, NtfnState_Idle); } /* Make thread inactive */ setThreadState(threadPtr, ThreadState_Inactive); } void completeSignal(notification_t *ntfnPtr, tcb_t *tcb) { word_t badge; if (likely(tcb && notification_ptr_get_state(ntfnPtr) == NtfnState_Active)) { badge = notification_ptr_get_ntfnMsgIdentifier(ntfnPtr); setRegister(tcb, badgeRegister, badge); notification_ptr_set_state(ntfnPtr, NtfnState_Idle); #ifdef CONFIG_KERNEL_MCS maybeDonateSchedContext(tcb, ntfnPtr); #endif } else { fail("tried to complete signal with inactive notification object"); } } static inline void doUnbindNotification(notification_t *ntfnPtr, tcb_t *tcbptr) { notification_ptr_set_ntfnBoundTCB(ntfnPtr, (word_t) 0); tcbptr->tcbBoundNotification = NULL; } void unbindMaybeNotification(notification_t *ntfnPtr) { tcb_t *boundTCB; boundTCB = (tcb_t *)notification_ptr_get_ntfnBoundTCB(ntfnPtr); if (boundTCB) { doUnbindNotification(ntfnPtr, boundTCB); } } void unbindNotification(tcb_t *tcb) { notification_t *ntfnPtr; ntfnPtr = tcb->tcbBoundNotification; if (ntfnPtr) { doUnbindNotification(ntfnPtr, tcb); } } void bindNotification(tcb_t *tcb, notification_t *ntfnPtr) { notification_ptr_set_ntfnBoundTCB(ntfnPtr, (word_t)tcb); tcb->tcbBoundNotification = ntfnPtr; } #ifdef CONFIG_KERNEL_MCS void reorderNTFN(notification_t *ntfnPtr, tcb_t *thread) { tcb_queue_t queue = ntfn_ptr_get_queue(ntfnPtr); queue = tcbEPDequeue(thread, queue); queue = tcbEPAppend(thread, queue); ntfn_ptr_set_queue(ntfnPtr, queue); } #endif