+ return true;
+}
+
+/*
+ * work_interval_auto_join_propagate()
+ *
+ * Routine to auto-join a thread into another thread's work interval
+ *
+ * Should only be invoked if work_interval_should_propagate() returns
+ * true. Also expects "from" thread to be current thread and "to" thread
+ * to be locked.
+ */
+void
+work_interval_auto_join_propagate(thread_t from, thread_t to)
+{
+ assert(from == current_thread());
+ work_interval_retain(from->th_work_interval);
+ work_interval_auto_join_increment(from->th_work_interval);
+ __assert_only kern_return_t kr = thread_set_work_interval(to, from->th_work_interval,
+ THREAD_WI_AUTO_JOIN_POLICY | THREAD_WI_THREAD_LOCK_HELD | THREAD_WI_THREAD_CTX_SWITCH);
+ assert(kr == KERN_SUCCESS);
+}
+
+/*
+ * work_interval_auto_join_unwind()
+ *
+ * Routine to un-join an auto-joined work interval for a thread that is blocking.
+ *
+ * Expects thread to be locked.
+ */
+void
+work_interval_auto_join_unwind(thread_t thread)
+{
+ __assert_only kern_return_t kr = thread_set_work_interval(thread, NULL,
+ THREAD_WI_AUTO_JOIN_POLICY | THREAD_WI_THREAD_LOCK_HELD | THREAD_WI_THREAD_CTX_SWITCH);
+ assert(kr == KERN_SUCCESS);
+}
+
+/*
+ * work_interval_auto_join_demote()
+ *
+ * Routine to un-join an auto-joined work interval when a thread is changing from
+ * realtime to non-realtime scheduling mode. This could happen due to multiple
+ * reasons such as RT failsafe, thread backgrounding or thread termination. Also,
+ * the thread being demoted may not be the current thread.
+ *
+ * Expects thread to be locked.
+ */
+void
+work_interval_auto_join_demote(thread_t thread)
+{
+ __assert_only kern_return_t kr = thread_set_work_interval(thread, NULL,
+ THREAD_WI_AUTO_JOIN_POLICY | THREAD_WI_THREAD_LOCK_HELD);
+ assert(kr == KERN_SUCCESS);
+}
+
+static void
+work_interval_deallocate_queue_invoke(mpsc_queue_chain_t e,
+ __assert_only mpsc_daemon_queue_t dq)
+{
+ struct work_interval *work_interval = NULL;
+ work_interval = mpsc_queue_element(e, struct work_interval, wi_deallocate_link);
+ assert(dq == &work_interval_deallocate_queue);
+ assert(os_ref_get_count(&work_interval->wi_ref_count) == 0);
+ work_interval_deallocate(work_interval);
+}
+
+#endif /* CONFIG_SCHED_AUTO_JOIN */
+
+void
+work_interval_subsystem_init(void)
+{
+#if CONFIG_SCHED_AUTO_JOIN
+ /*
+ * The work interval deallocation queue must be a thread call based queue
+ * because it is woken up from contexts where the thread lock is held. The
+ * only way to perform wakeups safely in those contexts is to wakeup a
+ * thread call which is guaranteed to be on a different waitq and would
+ * not hash onto the same global waitq which might be currently locked.
+ */
+ mpsc_daemon_queue_init_with_thread_call(&work_interval_deallocate_queue,
+ work_interval_deallocate_queue_invoke, THREAD_CALL_PRIORITY_KERNEL);
+#endif /* CONFIG_SCHED_AUTO_JOIN */