/* $Id: powernotification-r0drv.c $ */ /** @file * IPRT - Power Management, Ring-0 Driver, Event Notifications. */ /* * Copyright (C) 2008-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox 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. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include "internal/iprt.h" #include #if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) # include #endif #include #include #include #include #include #include #include "r0drv/mp-r0drv.h" #include "r0drv/power-r0drv.h" /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Notification registration record tracking * RTPowerRegisterNotification() calls. */ typedef struct RTPOWERNOTIFYREG { /** Pointer to the next record. */ struct RTPOWERNOTIFYREG * volatile pNext; /** The callback. */ PFNRTPOWERNOTIFICATION pfnCallback; /** The user argument. */ void *pvUser; /** Bit mask indicating whether we've done this callback or not. */ uint8_t bmDone[sizeof(void *)]; } RTPOWERNOTIFYREG; /** Pointer to a registration record. */ typedef RTPOWERNOTIFYREG *PRTPOWERNOTIFYREG; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** The spinlock protecting the list. */ static RTSPINLOCK volatile g_hRTPowerNotifySpinLock = NIL_RTSPINLOCK; /** List of callbacks, in registration order. */ static PRTPOWERNOTIFYREG volatile g_pRTPowerCallbackHead = NULL; /** The current done bit. */ static uint32_t volatile g_iRTPowerDoneBit; /** The list generation. * This is increased whenever the list has been modified. The callback routine * make use of this to avoid having restart at the list head after each callback. */ static uint32_t volatile g_iRTPowerGeneration; RTDECL(int) RTPowerSignalEvent(RTPOWEREVENT enmEvent) { PRTPOWERNOTIFYREG pCur; RTSPINLOCK hSpinlock; /* * This is a little bit tricky as we cannot be holding the spinlock * while calling the callback. This means that the list might change * while we're walking it, and that multiple events might be running * concurrently (depending on the OS). * * So, the first measure is to employ a 32-bitmask for each * record where we'll use a bit that rotates for each call to * this function to indicate which records that has been * processed. This will take care of both changes to the list * and a reasonable amount of concurrent events. * * In order to avoid having to restart the list walks for every * callback we make, we'll make use a list generation number that is * incremented everytime the list is changed. So, if it remains * unchanged over a callback we can safely continue the iteration. */ uint32_t iDone = ASMAtomicIncU32(&g_iRTPowerDoneBit); iDone %= RT_SIZEOFMEMB(RTPOWERNOTIFYREG, bmDone) * 8; hSpinlock = g_hRTPowerNotifySpinLock; if (hSpinlock == NIL_RTSPINLOCK) return VERR_ACCESS_DENIED; RTSpinlockAcquire(hSpinlock); /* Clear the bit. */ for (pCur = g_pRTPowerCallbackHead; pCur; pCur = pCur->pNext) ASMAtomicBitClear(&pCur->bmDone[0], iDone); /* Iterate the records and perform the callbacks. */ do { uint32_t const iGeneration = ASMAtomicUoReadU32(&g_iRTPowerGeneration); pCur = g_pRTPowerCallbackHead; while (pCur) { if (!ASMAtomicBitTestAndSet(&pCur->bmDone[0], iDone)) { PFNRTPOWERNOTIFICATION pfnCallback = pCur->pfnCallback; void *pvUser = pCur->pvUser; pCur = pCur->pNext; RTSpinlockRelease(g_hRTPowerNotifySpinLock); pfnCallback(enmEvent, pvUser); /* carefully require the lock here, see RTR0MpNotificationTerm(). */ hSpinlock = g_hRTPowerNotifySpinLock; if (hSpinlock == NIL_RTSPINLOCK) return VERR_ACCESS_DENIED; RTSpinlockAcquire(hSpinlock); if (ASMAtomicUoReadU32(&g_iRTPowerGeneration) != iGeneration) break; } else pCur = pCur->pNext; } } while (pCur); RTSpinlockRelease(hSpinlock); return VINF_SUCCESS; } RT_EXPORT_SYMBOL(RTPowerSignalEvent); RTDECL(int) RTPowerNotificationRegister(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser) { PRTPOWERNOTIFYREG pCur; PRTPOWERNOTIFYREG pNew; /* * Validation. */ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER); AssertReturn(g_hRTPowerNotifySpinLock != NIL_RTSPINLOCK, VERR_WRONG_ORDER); RT_ASSERT_PREEMPTIBLE(); RTSpinlockAcquire(g_hRTPowerNotifySpinLock); for (pCur = g_pRTPowerCallbackHead; pCur; pCur = pCur->pNext) if ( pCur->pvUser == pvUser && pCur->pfnCallback == pfnCallback) break; RTSpinlockRelease(g_hRTPowerNotifySpinLock); AssertMsgReturn(!pCur, ("pCur=%p pfnCallback=%p pvUser=%p\n", pCur, pfnCallback, pvUser), VERR_ALREADY_EXISTS); /* * Allocate a new record and attempt to insert it. */ pNew = (PRTPOWERNOTIFYREG)RTMemAlloc(sizeof(*pNew)); if (!pNew) return VERR_NO_MEMORY; pNew->pNext = NULL; pNew->pfnCallback = pfnCallback; pNew->pvUser = pvUser; memset(&pNew->bmDone[0], 0xff, sizeof(pNew->bmDone)); RTSpinlockAcquire(g_hRTPowerNotifySpinLock); pCur = g_pRTPowerCallbackHead; if (!pCur) g_pRTPowerCallbackHead = pNew; else { for (pCur = g_pRTPowerCallbackHead; ; pCur = pCur->pNext) if ( pCur->pvUser == pvUser && pCur->pfnCallback == pfnCallback) break; else if (!pCur->pNext) { pCur->pNext = pNew; pCur = NULL; break; } } ASMAtomicIncU32(&g_iRTPowerGeneration); RTSpinlockRelease(g_hRTPowerNotifySpinLock); /* duplicate? */ if (pCur) { RTMemFree(pCur); AssertMsgFailedReturn(("pCur=%p pfnCallback=%p pvUser=%p\n", pCur, pfnCallback, pvUser), VERR_ALREADY_EXISTS); } return VINF_SUCCESS; } RT_EXPORT_SYMBOL(RTPowerNotificationRegister); RTDECL(int) RTPowerNotificationDeregister(PFNRTPOWERNOTIFICATION pfnCallback, void *pvUser) { PRTPOWERNOTIFYREG pPrev; PRTPOWERNOTIFYREG pCur; /* * Validation. */ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER); AssertReturn(g_hRTPowerNotifySpinLock != NIL_RTSPINLOCK, VERR_WRONG_ORDER); RT_ASSERT_INTS_ON(); /* * Find and unlink the record from the list. */ RTSpinlockAcquire(g_hRTPowerNotifySpinLock); pPrev = NULL; for (pCur = g_pRTPowerCallbackHead; pCur; pCur = pCur->pNext) { if ( pCur->pvUser == pvUser && pCur->pfnCallback == pfnCallback) break; pPrev = pCur; } if (pCur) { if (pPrev) pPrev->pNext = pCur->pNext; else g_pRTPowerCallbackHead = pCur->pNext; ASMAtomicIncU32(&g_iRTPowerGeneration); } RTSpinlockRelease(g_hRTPowerNotifySpinLock); if (!pCur) return VERR_NOT_FOUND; /* * Invalidate and free the record. */ pCur->pNext = NULL; pCur->pfnCallback = NULL; RTMemFree(pCur); return VINF_SUCCESS; } RT_EXPORT_SYMBOL(RTPowerNotificationDeregister); DECLHIDDEN(int) rtR0PowerNotificationInit(void) { int rc = RTSpinlockCreate((PRTSPINLOCK)&g_hRTPowerNotifySpinLock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "RTR0Power"); if (RT_SUCCESS(rc)) { /** @todo OS specific init here */ return rc; #if 0 RTSpinlockDestroy(g_hRTPowerNotifySpinLock); g_hRTPowerNotifySpinLock = NIL_RTSPINLOCK; #endif } return rc; } DECLHIDDEN(void) rtR0PowerNotificationTerm(void) { PRTPOWERNOTIFYREG pHead; RTSPINLOCK hSpinlock = g_hRTPowerNotifySpinLock; AssertReturnVoid(hSpinlock != NIL_RTSPINLOCK); /** @todo OS specific term here */ /* pick up the list and the spinlock. */ RTSpinlockAcquire(hSpinlock); ASMAtomicWriteHandle(&g_hRTPowerNotifySpinLock, NIL_RTSPINLOCK); pHead = g_pRTPowerCallbackHead; g_pRTPowerCallbackHead = NULL; ASMAtomicIncU32(&g_iRTPowerGeneration); RTSpinlockRelease(hSpinlock); /* free the list. */ while (pHead) { PRTPOWERNOTIFYREG pFree = pHead; pHead = pHead->pNext; pFree->pNext = NULL; pFree->pfnCallback = NULL; RTMemFree(pFree); } RTSpinlockDestroy(hSpinlock); }