diff options
Diffstat (limited to 'src/VBox/Runtime/r0drv/linux/threadctxhooks-r0drv-linux.c')
-rw-r--r-- | src/VBox/Runtime/r0drv/linux/threadctxhooks-r0drv-linux.c | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r0drv/linux/threadctxhooks-r0drv-linux.c b/src/VBox/Runtime/r0drv/linux/threadctxhooks-r0drv-linux.c new file mode 100644 index 00000000..fbd2eb90 --- /dev/null +++ b/src/VBox/Runtime/r0drv/linux/threadctxhooks-r0drv-linux.c @@ -0,0 +1,330 @@ +/* $Id: threadctxhooks-r0drv-linux.c $ */ +/** @file + * IPRT - Thread Context Switching Hook, Ring-0 Driver, Linux. + */ + +/* + * Copyright (C) 2013-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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "the-linux-kernel.h" +#include "internal/iprt.h" + +#include <iprt/mem.h> +#include <iprt/assert.h> +#include <iprt/thread.h> +#include <iprt/errcore.h> +#include <iprt/asm.h> +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# include <iprt/asm-amd64-x86.h> +#endif +#include "internal/thread.h" + + +/* + * Linux kernel 2.6.23 introduced preemption notifiers but RedHat 2.6.18 kernels + * got it backported. + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) && defined(CONFIG_PREEMPT_NOTIFIERS) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The internal hook object for linux. + */ +typedef struct RTTHREADCTXHOOKINT +{ + /** Magic value (RTTHREADCTXHOOKINT_MAGIC). */ + uint32_t volatile u32Magic; + /** The thread handle (owner) for which the hook is registered. */ + RTNATIVETHREAD hOwner; + /** The preemption notifier object. */ + struct preempt_notifier LnxPreemptNotifier; + /** Whether the hook is enabled or not. If enabled, the LnxPreemptNotifier + * is linked into the owning thread's list of preemption callouts. */ + bool fEnabled; + /** Pointer to the user callback. */ + PFNRTTHREADCTXHOOK pfnCallback; + /** User argument passed to the callback. */ + void *pvUser; + /** The linux callbacks. */ + struct preempt_ops PreemptOps; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 19) && defined(RT_ARCH_AMD64) + /** Starting with 3.1.19, the linux kernel doesn't restore kernel RFLAGS during + * task switch, so we have to do that ourselves. (x86 code is not affected.) */ + RTCCUINTREG fSavedRFlags; +#endif +} RTTHREADCTXHOOKINT; +typedef RTTHREADCTXHOOKINT *PRTTHREADCTXHOOKINT; + + +/** + * Hook function for the thread schedule out event. + * + * @param pPreemptNotifier Pointer to the preempt_notifier struct. + * @param pNext Pointer to the task that is being scheduled + * instead of the current thread. + * + * @remarks Called with the rq (runqueue) lock held and with preemption and + * interrupts disabled! + */ +static void rtThreadCtxHooksLnxSchedOut(struct preempt_notifier *pPreemptNotifier, struct task_struct *pNext) +{ + PRTTHREADCTXHOOKINT pThis = RT_FROM_MEMBER(pPreemptNotifier, RTTHREADCTXHOOKINT, LnxPreemptNotifier); +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + RTCCUINTREG fSavedEFlags = ASMGetFlags(); + stac(); +#endif + RT_NOREF_PV(pNext); + + AssertPtr(pThis); + AssertPtr(pThis->pfnCallback); + Assert(pThis->fEnabled); + Assert(!RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + + pThis->pfnCallback(RTTHREADCTXEVENT_OUT, pThis->pvUser); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + ASMSetFlags(fSavedEFlags); +# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 19) && defined(RT_ARCH_AMD64) + pThis->fSavedRFlags = fSavedEFlags; +# endif +#endif +} + + +/** + * Hook function for the thread schedule in event. + * + * @param pPreemptNotifier Pointer to the preempt_notifier struct. + * @param iCpu The CPU this thread is being scheduled on. + * + * @remarks Called without holding the rq (runqueue) lock and with preemption + * enabled! + * @todo r=bird: Preemption is of course disabled when it is called. + */ +static void rtThreadCtxHooksLnxSchedIn(struct preempt_notifier *pPreemptNotifier, int iCpu) +{ + PRTTHREADCTXHOOKINT pThis = RT_FROM_MEMBER(pPreemptNotifier, RTTHREADCTXHOOKINT, LnxPreemptNotifier); +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) + RTCCUINTREG fSavedEFlags = ASMGetFlags(); + stac(); +#endif + RT_NOREF_PV(iCpu); + + AssertPtr(pThis); + AssertPtr(pThis->pfnCallback); + Assert(pThis->fEnabled); + + pThis->pfnCallback(RTTHREADCTXEVENT_IN, pThis->pvUser); + +#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) +# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 19) && defined(RT_ARCH_AMD64) + fSavedEFlags &= ~RT_BIT_64(18) /*X86_EFL_AC*/; + fSavedEFlags |= pThis->fSavedRFlags & RT_BIT_64(18) /*X86_EFL_AC*/; +# endif + ASMSetFlags(fSavedEFlags); +#endif +} + + +/** + * Worker function for RTThreadCtxHooks(Deregister|Release)(). + * + * @param pThis Pointer to the internal thread-context object. + */ +DECLINLINE(void) rtThreadCtxHookDisable(PRTTHREADCTXHOOKINT pThis) +{ + Assert(pThis->PreemptOps.sched_out == rtThreadCtxHooksLnxSchedOut); + Assert(pThis->PreemptOps.sched_in == rtThreadCtxHooksLnxSchedIn); + preempt_disable(); + preempt_notifier_unregister(&pThis->LnxPreemptNotifier); + pThis->fEnabled = false; + preempt_enable(); +} + + +RTDECL(int) RTThreadCtxHookCreate(PRTTHREADCTXHOOK phCtxHook, uint32_t fFlags, PFNRTTHREADCTXHOOK pfnCallback, void *pvUser) +{ + IPRT_LINUX_SAVE_EFL_AC(); + + /* + * Validate input. + */ + PRTTHREADCTXHOOKINT pThis; + Assert(RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER); + AssertReturn(fFlags == 0, VERR_INVALID_FLAGS); + + /* + * Allocate and initialize a new hook. We don't register it yet, just + * create it. + */ + pThis = (PRTTHREADCTXHOOKINT)RTMemAllocZ(sizeof(*pThis)); + if (RT_UNLIKELY(!pThis)) + { + IPRT_LINUX_RESTORE_EFL_AC(); + return VERR_NO_MEMORY; + } + pThis->u32Magic = RTTHREADCTXHOOKINT_MAGIC; + pThis->hOwner = RTThreadNativeSelf(); + pThis->fEnabled = false; + pThis->pfnCallback = pfnCallback; + pThis->pvUser = pvUser; + preempt_notifier_init(&pThis->LnxPreemptNotifier, &pThis->PreemptOps); + pThis->PreemptOps.sched_out = rtThreadCtxHooksLnxSchedOut; + pThis->PreemptOps.sched_in = rtThreadCtxHooksLnxSchedIn; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) + preempt_notifier_inc(); +#endif + + *phCtxHook = pThis; + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTThreadCtxHookCreate); + + +RTDECL(int ) RTThreadCtxHookDestroy(RTTHREADCTXHOOK hCtxHook) +{ + IPRT_LINUX_SAVE_EFL_AC(); + + /* + * Validate input. + */ + PRTTHREADCTXHOOKINT pThis = hCtxHook; + if (pThis == NIL_RTTHREADCTXHOOK) + return VINF_SUCCESS; + AssertPtr(pThis); + AssertMsgReturn(pThis->u32Magic == RTTHREADCTXHOOKINT_MAGIC, ("pThis->u32Magic=%RX32 pThis=%p\n", pThis->u32Magic, pThis), + VERR_INVALID_HANDLE); + Assert(RTThreadPreemptIsEnabled(NIL_RTTHREAD)); + Assert(!pThis->fEnabled || pThis->hOwner == RTThreadNativeSelf()); + + /* + * If there's still a registered thread-context hook, deregister it now before destroying the object. + */ + if (pThis->fEnabled) + { + Assert(pThis->hOwner == RTThreadNativeSelf()); + rtThreadCtxHookDisable(pThis); + Assert(!pThis->fEnabled); /* paranoia */ + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0) + preempt_notifier_dec(); +#endif + + ASMAtomicWriteU32(&pThis->u32Magic, ~RTTHREADCTXHOOKINT_MAGIC); + RTMemFree(pThis); + + IPRT_LINUX_RESTORE_EFL_AC(); + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTThreadCtxHookDestroy); + + +RTDECL(int) RTThreadCtxHookEnable(RTTHREADCTXHOOK hCtxHook) +{ + /* + * Validate input. + */ + PRTTHREADCTXHOOKINT pThis = hCtxHook; + AssertPtr(pThis); + AssertMsgReturn(pThis->u32Magic == RTTHREADCTXHOOKINT_MAGIC, ("pThis->u32Magic=%RX32 pThis=%p\n", pThis->u32Magic, pThis), + VERR_INVALID_HANDLE); + Assert(pThis->hOwner == RTThreadNativeSelf()); + Assert(!pThis->fEnabled); + if (!pThis->fEnabled) + { + IPRT_LINUX_SAVE_EFL_AC(); + Assert(pThis->PreemptOps.sched_out == rtThreadCtxHooksLnxSchedOut); + Assert(pThis->PreemptOps.sched_in == rtThreadCtxHooksLnxSchedIn); + + /* + * Register the callback. + */ + preempt_disable(); + pThis->fEnabled = true; + preempt_notifier_register(&pThis->LnxPreemptNotifier); + preempt_enable(); + + IPRT_LINUX_RESTORE_EFL_AC(); + } + + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTThreadCtxHookEnable); + + +RTDECL(int) RTThreadCtxHookDisable(RTTHREADCTXHOOK hCtxHook) +{ + /* + * Validate input. + */ + PRTTHREADCTXHOOKINT pThis = hCtxHook; + if (pThis != NIL_RTTHREADCTXHOOK) + { + AssertPtr(pThis); + AssertMsgReturn(pThis->u32Magic == RTTHREADCTXHOOKINT_MAGIC, ("pThis->u32Magic=%RX32 pThis=%p\n", pThis->u32Magic, pThis), + VERR_INVALID_HANDLE); + Assert(pThis->hOwner == RTThreadNativeSelf()); + + /* + * Deregister the callback. + */ + if (pThis->fEnabled) + { + IPRT_LINUX_SAVE_EFL_AC(); + rtThreadCtxHookDisable(pThis); + IPRT_LINUX_RESTORE_EFL_AC(); + } + } + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTThreadCtxHookDisable); + + +RTDECL(bool) RTThreadCtxHookIsEnabled(RTTHREADCTXHOOK hCtxHook) +{ + /* + * Validate input. + */ + PRTTHREADCTXHOOKINT pThis = hCtxHook; + if (pThis == NIL_RTTHREADCTXHOOK) + return false; + AssertPtr(pThis); + AssertMsgReturn(pThis->u32Magic == RTTHREADCTXHOOKINT_MAGIC, ("pThis->u32Magic=%RX32 pThis=%p\n", pThis->u32Magic, pThis), + false); + + return pThis->fEnabled; +} + +#else /* Not supported / Not needed */ +# include "../generic/threadctxhooks-r0drv-generic.cpp" +#endif /* Not supported / Not needed */ + |