summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/r0drv/solaris/semeventwait-r0drv-solaris.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/Runtime/r0drv/solaris/semeventwait-r0drv-solaris.h
parentInitial commit. (diff)
downloadvirtualbox-upstream.tar.xz
virtualbox-upstream.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Runtime/r0drv/solaris/semeventwait-r0drv-solaris.h')
-rw-r--r--src/VBox/Runtime/r0drv/solaris/semeventwait-r0drv-solaris.h496
1 files changed, 496 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r0drv/solaris/semeventwait-r0drv-solaris.h b/src/VBox/Runtime/r0drv/solaris/semeventwait-r0drv-solaris.h
new file mode 100644
index 00000000..31424ec9
--- /dev/null
+++ b/src/VBox/Runtime/r0drv/solaris/semeventwait-r0drv-solaris.h
@@ -0,0 +1,496 @@
+/* $Id: semeventwait-r0drv-solaris.h $ */
+/** @file
+ * IPRT - Solaris Ring-0 Driver Helpers for Event Semaphore Waits.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+ * VirtualBox OSE distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ */
+
+#ifndef IPRT_INCLUDED_SRC_r0drv_solaris_semeventwait_r0drv_solaris_h
+#define IPRT_INCLUDED_SRC_r0drv_solaris_semeventwait_r0drv_solaris_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "the-solaris-kernel.h"
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+
+
+/** The resolution (nanoseconds) specified when using timeout_generic. */
+#define RTR0SEMSOLWAIT_RESOLUTION 50000
+
+/** Disables the cyclic fallback code for old S10 installs - see @bugref{5342}.
+ * @todo Fixed by @bugref{5595}, can be reenabled after checking out
+ * CY_HIGH_LEVEL. */
+#define RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+
+#define SOL_THREAD_TINTR_PTR ((kthread_t **)((char *)curthread + g_offrtSolThreadIntrThread))
+
+
+/**
+ * Solaris semaphore wait structure.
+ */
+typedef struct RTR0SEMSOLWAIT
+{
+ /** The absolute timeout given as nanoseconds since the start of the
+ * monotonic clock. */
+ uint64_t uNsAbsTimeout;
+ /** The timeout in nanoseconds relative to the start of the wait. */
+ uint64_t cNsRelTimeout;
+ /** The native timeout value. */
+ union
+ {
+ /** The timeout (in ticks) when fHighRes is false. */
+ clock_t lTimeout;
+ } u;
+ /** Set if we use high resolution timeouts. */
+ bool fHighRes;
+ /** Set if it's an indefinite wait. */
+ bool fIndefinite;
+ /** Set if the waiting thread is ready to be woken up.
+ * Avoids false setrun() calls due to temporary mutex exits. */
+ bool volatile fWantWakeup;
+ /** Set if we've already timed out.
+ * Set by rtR0SemSolWaitDoIt or rtR0SemSolWaitHighResTimeout, read by
+ * rtR0SemSolWaitHasTimedOut. */
+ bool volatile fTimedOut;
+ /** Whether the wait was interrupted. */
+ bool fInterrupted;
+ /** Interruptible or uninterruptible wait. */
+ bool fInterruptible;
+ /** The thread to wake up. */
+ kthread_t *pThread;
+#ifndef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+ /** Cylic timer ID (used by the timeout callback). */
+ cyclic_id_t idCy;
+#endif
+ /** The mutex associated with the condition variable wait. */
+ void volatile *pvMtx;
+} RTR0SEMSOLWAIT;
+/** Pointer to a solaris semaphore wait structure. */
+typedef RTR0SEMSOLWAIT *PRTR0SEMSOLWAIT;
+
+
+/**
+ * Initializes a wait.
+ *
+ * The caller MUST check the wait condition BEFORE calling this function or the
+ * timeout logic will be flawed.
+ *
+ * @returns VINF_SUCCESS or VERR_TIMEOUT.
+ * @param pWait The wait structure.
+ * @param fFlags The wait flags.
+ * @param uTimeout The timeout.
+ */
+DECLINLINE(int) rtR0SemSolWaitInit(PRTR0SEMSOLWAIT pWait, uint32_t fFlags, uint64_t uTimeout)
+{
+ /*
+ * Process the flags and timeout.
+ */
+ if (!(fFlags & RTSEMWAIT_FLAGS_INDEFINITE))
+ {
+ if (fFlags & RTSEMWAIT_FLAGS_MILLISECS)
+ uTimeout = uTimeout < UINT64_MAX / RT_NS_1MS
+ ? uTimeout * RT_NS_1MS
+ : UINT64_MAX;
+ if (uTimeout == UINT64_MAX)
+ fFlags |= RTSEMWAIT_FLAGS_INDEFINITE;
+ else
+ {
+ uint64_t u64Now;
+ if (fFlags & RTSEMWAIT_FLAGS_RELATIVE)
+ {
+ if (uTimeout == 0)
+ return VERR_TIMEOUT;
+
+ u64Now = RTTimeSystemNanoTS();
+ pWait->cNsRelTimeout = uTimeout;
+ pWait->uNsAbsTimeout = u64Now + uTimeout;
+ if (pWait->uNsAbsTimeout < u64Now) /* overflow */
+ fFlags |= RTSEMWAIT_FLAGS_INDEFINITE;
+ }
+ else
+ {
+ u64Now = RTTimeSystemNanoTS();
+ if (u64Now >= uTimeout)
+ return VERR_TIMEOUT;
+
+ pWait->cNsRelTimeout = uTimeout - u64Now;
+ pWait->uNsAbsTimeout = uTimeout;
+ }
+ }
+ }
+
+ if (!(fFlags & RTSEMWAIT_FLAGS_INDEFINITE))
+ {
+ pWait->fIndefinite = false;
+ if ( ( (fFlags & (RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_ABSOLUTE))
+ || pWait->cNsRelTimeout < UINT32_C(1000000000) / 100 /*Hz*/ * 4)
+#ifdef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+ && g_pfnrtR0Sol_timeout_generic != NULL
+#endif
+ )
+ pWait->fHighRes = true;
+ else
+ {
+ uint64_t cTicks = NSEC_TO_TICK_ROUNDUP(uTimeout);
+ if (cTicks >= LONG_MAX)
+ fFlags |= RTSEMWAIT_FLAGS_INDEFINITE;
+ else
+ {
+ pWait->u.lTimeout = cTicks;
+ pWait->fHighRes = false;
+ }
+ }
+ }
+
+ if (fFlags & RTSEMWAIT_FLAGS_INDEFINITE)
+ {
+ pWait->fIndefinite = true;
+ pWait->fHighRes = false;
+ pWait->uNsAbsTimeout = UINT64_MAX;
+ pWait->cNsRelTimeout = UINT64_MAX;
+ pWait->u.lTimeout = LONG_MAX;
+ }
+
+ pWait->fWantWakeup = false;
+ pWait->fTimedOut = false;
+ pWait->fInterrupted = false;
+ pWait->fInterruptible = !!(fFlags & RTSEMWAIT_FLAGS_INTERRUPTIBLE);
+ pWait->pThread = curthread;
+ pWait->pvMtx = NULL;
+#ifndef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+ pWait->idCy = CYCLIC_NONE;
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+#ifndef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+/**
+ * Cyclic timeout callback that sets the timeout indicator and wakes up the
+ * waiting thread.
+ *
+ * @param pvUser The wait structure.
+ */
+static void rtR0SemSolWaitHighResTimeout(void *pvUser)
+{
+ PRTR0SEMSOLWAIT pWait = (PRTR0SEMSOLWAIT)pvUser;
+ kthread_t *pThread = pWait->pThread;
+ kmutex_t *pMtx = (kmutex_t *)ASMAtomicReadPtr(&pWait->pvMtx);
+ if (VALID_PTR(pMtx))
+ {
+ /* Enter the mutex here to make sure the thread has gone to sleep
+ before we wake it up.
+ Note: Trying to take the cpu_lock here doesn't work. */
+ mutex_enter(pMtx);
+ if (mutex_owner(&cpu_lock) == curthread)
+ {
+ cyclic_remove(pWait->idCy);
+ pWait->idCy = CYCLIC_NONE;
+ }
+ bool const fWantWakeup = pWait->fWantWakeup;
+ ASMAtomicWriteBool(&pWait->fTimedOut, true);
+ mutex_exit(pMtx);
+
+ if (fWantWakeup)
+ setrun(pThread);
+ }
+}
+#endif
+
+
+/**
+ * Timeout callback that sets the timeout indicator and wakes up the waiting
+ * thread.
+ *
+ * @param pvUser The wait structure.
+ */
+static void rtR0SemSolWaitTimeout(void *pvUser)
+{
+ PRTR0SEMSOLWAIT pWait = (PRTR0SEMSOLWAIT)pvUser;
+ kthread_t *pThread = pWait->pThread;
+ kmutex_t *pMtx = (kmutex_t *)ASMAtomicReadPtr((void * volatile *)&pWait->pvMtx);
+ if (VALID_PTR(pMtx))
+ {
+ /* Enter the mutex here to make sure the thread has gone to sleep
+ before we wake it up. */
+ mutex_enter(pMtx);
+ bool const fWantWakeup = pWait->fWantWakeup;
+ ASMAtomicWriteBool(&pWait->fTimedOut, true);
+ mutex_exit(pMtx);
+
+ if (fWantWakeup)
+ setrun(pThread);
+ }
+}
+
+
+/**
+ * Do the actual wait.
+ *
+ * @param pWait The wait structure.
+ * @param pCnd The condition variable to wait on.
+ * @param pMtx The mutex related to the condition variable.
+ * The caller has entered this.
+ * @param pfState The state variable to check if have changed
+ * after leaving the mutex (spinlock).
+ * @param fCurState The current value of @a pfState. We'll return
+ * without sleeping if @a pfState doesn't hold
+ * this value after reacquiring the mutex.
+ *
+ * @remarks This must be call with the object mutex (spinlock) held.
+ */
+DECLINLINE(void) rtR0SemSolWaitDoIt(PRTR0SEMSOLWAIT pWait, kcondvar_t *pCnd, kmutex_t *pMtx,
+ uint32_t volatile *pfState, uint32_t const fCurState)
+{
+ union
+ {
+ callout_id_t idCo;
+ timeout_id_t idTom;
+ } u;
+
+ /*
+ * Arm the timeout callback.
+ *
+ * We will have to leave the mutex (spinlock) when doing this because S10
+ * (didn't check S11) will not correctly preserve PIL across calls to
+ * timeout_generic() - @bugref{5595}. We do it for all timeout methods to
+ * be on the safe side, the nice sideeffect of which is that it solves the
+ * lock inversion problem found in @bugref{5342}.
+ */
+ bool const fHasTimeout = !pWait->fIndefinite;
+ bool fGoToSleep = !fHasTimeout;
+ if (fHasTimeout)
+ {
+ pWait->fWantWakeup = false; /* only want fTimedOut */
+ ASMAtomicWritePtr(&pWait->pvMtx, pMtx); /* atomic is paranoia */
+ mutex_exit(pMtx);
+
+ if (pWait->fHighRes)
+ {
+#ifndef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+ if (g_pfnrtR0Sol_timeout_generic != NULL)
+#endif
+ {
+ /*
+ * High resolution timeout - arm a high resolution timeout callback
+ * for waking up the thread at the desired time.
+ */
+ u.idCo = g_pfnrtR0Sol_timeout_generic(CALLOUT_REALTIME, rtR0SemSolWaitTimeout, pWait,
+ pWait->uNsAbsTimeout, RTR0SEMSOLWAIT_RESOLUTION,
+ CALLOUT_FLAG_ABSOLUTE);
+ }
+#ifndef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+ else
+ {
+ /*
+ * High resolution timeout - arm a one-shot cyclic for waking up
+ * the thread at the desired time.
+ */
+ cyc_handler_t Cyh;
+ Cyh.cyh_arg = pWait;
+ Cyh.cyh_func = rtR0SemSolWaitHighResTimeout;
+ Cyh.cyh_level = CY_LOW_LEVEL; /// @todo try CY_LOCK_LEVEL and CY_HIGH_LEVEL?
+
+ cyc_time_t Cyt;
+ Cyt.cyt_when = pWait->uNsAbsTimeout;
+ Cyt.cyt_interval = UINT64_C(1000000000) * 60;
+
+ mutex_enter(&cpu_lock);
+ pWait->idCy = cyclic_add(&Cyh, &Cyt);
+ mutex_exit(&cpu_lock);
+ }
+#endif
+ }
+ else
+ {
+ /*
+ * Normal timeout.
+ * We're better off with our own callback like on the timeout man page,
+ * than calling cv_timedwait[_sig]().
+ */
+ u.idTom = realtime_timeout(rtR0SemSolWaitTimeout, pWait, pWait->u.lTimeout);
+ }
+
+ /*
+ * Reacquire the mutex and check if the sleep condition still holds and
+ * that we didn't already time out.
+ */
+ mutex_enter(pMtx);
+ pWait->fWantWakeup = true;
+ fGoToSleep = !ASMAtomicUoReadBool(&pWait->fTimedOut)
+ && ASMAtomicReadU32(pfState) == fCurState;
+ }
+
+ /*
+ * Do the waiting if that's still desirable.
+ * (rc > 0 - normal wake-up; rc == 0 - interruption; rc == -1 - timeout)
+ */
+ if (fGoToSleep)
+ {
+ if (pWait->fInterruptible)
+ {
+ int rc = cv_wait_sig(pCnd, pMtx);
+ if (RT_UNLIKELY(rc <= 0))
+ {
+ if (RT_LIKELY(rc == 0))
+ pWait->fInterrupted = true;
+ else
+ AssertMsgFailed(("rc=%d\n", rc)); /* no timeouts, see above! */
+ }
+ }
+ else
+ cv_wait(pCnd, pMtx);
+ }
+
+ /*
+ * Remove the timeout callback. Drop the lock while we're doing that
+ * to reduce lock contention / deadlocks. Before dropping the lock,
+ * indicate that the callback shouldn't do anything.
+ *
+ * (Too bad we are stuck with the cv_* API here, it's doing a little
+ * bit too much.)
+ */
+ if (fHasTimeout)
+ {
+ pWait->fWantWakeup = false;
+ ASMAtomicWritePtr(&pWait->pvMtx, NULL);
+ mutex_exit(pMtx);
+
+ if (pWait->fHighRes)
+ {
+#ifndef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+ if (g_pfnrtR0Sol_timeout_generic != NULL)
+#endif
+ g_pfnrtR0Sol_untimeout_generic(u.idCo, 0 /*nowait*/);
+#ifndef RTR0SEMSOLWAIT_NO_OLD_S10_FALLBACK
+ else
+ {
+ mutex_enter(&cpu_lock);
+ if (pWait->idCy != CYCLIC_NONE)
+ {
+ cyclic_remove(pWait->idCy);
+ pWait->idCy = CYCLIC_NONE;
+ }
+ mutex_exit(&cpu_lock);
+ }
+#endif
+ }
+ else
+ untimeout(u.idTom);
+
+ mutex_enter(pMtx);
+ }
+}
+
+
+/**
+ * Checks if a solaris wait was interrupted.
+ *
+ * @returns true / false
+ * @param pWait The wait structure.
+ * @remarks This shall be called before the first rtR0SemSolWaitDoIt().
+ */
+DECLINLINE(bool) rtR0SemSolWaitWasInterrupted(PRTR0SEMSOLWAIT pWait)
+{
+ return pWait->fInterrupted;
+}
+
+
+/**
+ * Checks if a solaris wait has timed out.
+ *
+ * @returns true / false
+ * @param pWait The wait structure.
+ */
+DECLINLINE(bool) rtR0SemSolWaitHasTimedOut(PRTR0SEMSOLWAIT pWait)
+{
+ return pWait->fTimedOut;
+}
+
+
+/**
+ * Deletes a solaris wait.
+ *
+ * @param pWait The wait structure.
+ */
+DECLINLINE(void) rtR0SemSolWaitDelete(PRTR0SEMSOLWAIT pWait)
+{
+ pWait->pThread = NULL;
+}
+
+
+/**
+ * Enters the mutex, unpinning the underlying current thread if contended and
+ * we're on an interrupt thread.
+ *
+ * The unpinning is done to prevent a deadlock, see s this could lead to a
+ * deadlock (see @bugref{4259} for the full explanation)
+ *
+ * @param pMtx The mutex to enter.
+ */
+DECLINLINE(void) rtR0SemSolWaitEnterMutexWithUnpinningHack(kmutex_t *pMtx)
+{
+ int fAcquired = mutex_tryenter(pMtx);
+ if (!fAcquired)
+ {
+ /*
+ * Note! This assumes nobody is using the RTThreadPreemptDisable() in an
+ * interrupt context and expects it to work right. The swtch will
+ * result in a voluntary preemption. To fix this, we would have to
+ * do our own counting in RTThreadPreemptDisable/Restore() like we do
+ * on systems which doesn't do preemption (OS/2, linux, ...) and
+ * check whether preemption was disabled via RTThreadPreemptDisable()
+ * or not and only call swtch if RTThreadPreemptDisable() wasn't called.
+ */
+ kthread_t **ppIntrThread = SOL_THREAD_TINTR_PTR;
+ if ( *ppIntrThread
+ && getpil() < DISP_LEVEL)
+ {
+ RTTHREADPREEMPTSTATE PreemptState = RTTHREADPREEMPTSTATE_INITIALIZER;
+ RTThreadPreemptDisable(&PreemptState);
+ preempt();
+ RTThreadPreemptRestore(&PreemptState);
+ }
+ mutex_enter(pMtx);
+ }
+}
+
+
+/**
+ * Gets the max resolution of the timeout machinery.
+ *
+ * @returns Resolution specified in nanoseconds.
+ */
+DECLINLINE(uint32_t) rtR0SemSolWaitGetResolution(void)
+{
+ return g_pfnrtR0Sol_timeout_generic != NULL
+ ? RTR0SEMSOLWAIT_RESOLUTION
+ : cyclic_getres();
+}
+
+#endif /* !IPRT_INCLUDED_SRC_r0drv_solaris_semeventwait_r0drv_solaris_h */
+