/* $Id: powernotification-r0drv.c 135976 2020-02-04 10:35:17Z bird $ */
/** @file
 * IPRT - Power Management, Ring-0 Driver, Event Notifications.
 */

/*
 * Copyright (C) 2008-2020 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 <iprt/power.h>
#include "internal/iprt.h"

#include <iprt/asm.h>
#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
# include <iprt/asm-amd64-x86.h>
#endif
#include <iprt/assert.h>
#include <iprt/err.h>
#include <iprt/mem.h>
#include <iprt/spinlock.h>
#include <iprt/string.h>
#include <iprt/thread.h>
#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);
}

