/* $Id: mpnotification-r0drv-linux.c $ */
/** @file
 * IPRT - Multiprocessor Event Notifications, Ring-0 Driver, Linux.
 */

/*
 * Copyright (C) 2008-2024 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 <https://www.gnu.org/licenses>.
 *
 * 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 "the-linux-kernel.h"
#include "internal/iprt.h"

#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
# include <iprt/asm-amd64-x86.h>
#endif
#include <iprt/errcore.h>
#include <iprt/cpuset.h>
#include <iprt/thread.h>
#include "r0drv/mp-r0drv.h"

#if RTLNX_VER_MIN(4,10,0)

static enum cpuhp_state g_rtR0MpOnline;

/*
 * Linux 4.10 completely removed CPU notifiers. So let's switch to CPU hotplug
 * notification.
 */

static int rtR0MpNotificationLinuxOnline(unsigned int cpu)
{
    RTCPUID idCpu = RTMpCpuIdFromSetIndex(cpu);
    rtMpNotificationDoCallbacks(RTMPEVENT_ONLINE, idCpu);
    return 0;
}

static int rtR0MpNotificationLinuxOffline(unsigned int cpu)
{
    RTCPUID idCpu = RTMpCpuIdFromSetIndex(cpu);
    rtMpNotificationDoCallbacks(RTMPEVENT_OFFLINE, idCpu);
    return 0;
}

DECLHIDDEN(int) rtR0MpNotificationNativeInit(void)
{
    int rc;
    IPRT_LINUX_SAVE_EFL_AC();
    rc = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, "vboxdrv:online",
                                   rtR0MpNotificationLinuxOnline, rtR0MpNotificationLinuxOffline);
    IPRT_LINUX_RESTORE_EFL_AC();
    /*
     * cpuhp_setup_state_nocalls() returns a positive state number for
     * CPUHP_AP_ONLINE_DYN or -ENOSPC if there is no free slot available
     * (see cpuhp_reserve_state / definition of CPUHP_AP_ONLINE_DYN).
     */
    AssertMsgReturn(rc > 0, ("%d\n", rc), RTErrConvertFromErrno(rc));
    g_rtR0MpOnline = rc;
    return VINF_SUCCESS;
}


DECLHIDDEN(void) rtR0MpNotificationNativeTerm(void)
{
    IPRT_LINUX_SAVE_EFL_AC();
    cpuhp_remove_state_nocalls(g_rtR0MpOnline);
    IPRT_LINUX_RESTORE_EFL_AC();
}

#elif RTLNX_VER_MIN(2,5,71) && defined(CONFIG_SMP)

static int rtMpNotificationLinuxCallback(struct notifier_block *pNotifierBlock, unsigned long ulNativeEvent, void *pvCpu);

/**
 * The notifier block we use for registering the callback.
 */
static struct notifier_block g_NotifierBlock =
{
    .notifier_call = rtMpNotificationLinuxCallback,
    .next = NULL,
    .priority = 0
};

# ifdef CPU_DOWN_FAILED
/**
 * The set of CPUs we've seen going offline recently.
 */
static RTCPUSET g_MpPendingOfflineSet;
# endif


/**
 * The native callback.
 *
 * @returns NOTIFY_DONE.
 * @param   pNotifierBlock  Pointer to g_NotifierBlock.
 * @param   ulNativeEvent   The native event.
 * @param   pvCpu           The cpu id cast into a pointer value.
 *
 * @remarks This can fire with preemption enabled and on any CPU.
 */
static int rtMpNotificationLinuxCallback(struct notifier_block *pNotifierBlock, unsigned long ulNativeEvent, void *pvCpu)
{
    bool fProcessEvent = false;
    RTCPUID idCpu      = (uintptr_t)pvCpu;
    NOREF(pNotifierBlock);

    /*
     * Note that redhat/CentOS ported _some_ of the FROZEN macros
     * back to their 2.6.18-92.1.10.el5 kernel but actually don't
     * use them. Thus we have to test for both CPU_TASKS_FROZEN and
     * the individual event variants.
     */
    switch (ulNativeEvent)
    {
        /*
         * Pick up online events or failures to go offline.
         * Ignore failure events for CPUs we didn't see go offline.
         */
# ifdef CPU_DOWN_FAILED
        case CPU_DOWN_FAILED:
#  if defined(CPU_TASKS_FROZEN) && defined(CPU_DOWN_FAILED_FROZEN)
        case CPU_DOWN_FAILED_FROZEN:
#  endif
            if (!RTCpuSetIsMember(&g_MpPendingOfflineSet, idCpu))
                break;      /* fProcessEvents = false */
        /* fall thru */
# endif
        case CPU_ONLINE:
# if defined(CPU_TASKS_FROZEN) && defined(CPU_ONLINE_FROZEN)
        case CPU_ONLINE_FROZEN:
# endif
# ifdef CPU_DOWN_FAILED
            RTCpuSetDel(&g_MpPendingOfflineSet, idCpu);
# endif
            fProcessEvent = true;
            break;

        /*
         * Pick the earliest possible offline event.
         * The only important thing here is that we get the event and that
         * it's exactly one.
         */
# ifdef CPU_DOWN_PREPARE
        case CPU_DOWN_PREPARE:
#  if defined(CPU_TASKS_FROZEN) && defined(CPU_DOWN_PREPARE_FROZEN)
        case CPU_DOWN_PREPARE_FROZEN:
#  endif
            fProcessEvent = true;
# else
        case CPU_DEAD:
#  if defined(CPU_TASKS_FROZEN) && defined(CPU_DEAD_FROZEN)
        case CPU_DEAD_FROZEN:
#  endif
            /* Don't process CPU_DEAD notifications. */
# endif
# ifdef CPU_DOWN_FAILED
            RTCpuSetAdd(&g_MpPendingOfflineSet, idCpu);
# endif
            break;
    }

    if (!fProcessEvent)
        return NOTIFY_DONE;

    switch (ulNativeEvent)
    {
# ifdef CPU_DOWN_FAILED
        case CPU_DOWN_FAILED:
#  if defined(CPU_TASKS_FROZEN) && defined(CPU_DOWN_FAILED_FROZEN)
        case CPU_DOWN_FAILED_FROZEN:
#  endif
# endif
        case CPU_ONLINE:
# if defined(CPU_TASKS_FROZEN) && defined(CPU_ONLINE_FROZEN)
        case CPU_ONLINE_FROZEN:
# endif
            rtMpNotificationDoCallbacks(RTMPEVENT_ONLINE, idCpu);
            break;

# ifdef CPU_DOWN_PREPARE
        case CPU_DOWN_PREPARE:
#  if defined(CPU_TASKS_FROZEN) && defined(CPU_DOWN_PREPARE_FROZEN)
        case CPU_DOWN_PREPARE_FROZEN:
#  endif
            rtMpNotificationDoCallbacks(RTMPEVENT_OFFLINE, idCpu);
            break;
# endif
    }

    return NOTIFY_DONE;
}


DECLHIDDEN(int) rtR0MpNotificationNativeInit(void)
{
    int rc;
    IPRT_LINUX_SAVE_EFL_AC();

# ifdef CPU_DOWN_FAILED
    RTCpuSetEmpty(&g_MpPendingOfflineSet);
# endif

    rc = register_cpu_notifier(&g_NotifierBlock);
    IPRT_LINUX_RESTORE_EFL_AC();
    AssertMsgReturn(!rc, ("%d\n", rc), RTErrConvertFromErrno(rc));
    return VINF_SUCCESS;
}


DECLHIDDEN(void) rtR0MpNotificationNativeTerm(void)
{
    IPRT_LINUX_SAVE_EFL_AC();
    unregister_cpu_notifier(&g_NotifierBlock);
    IPRT_LINUX_RESTORE_EFL_AC();
}

#else   /* Not supported / Not needed */

DECLHIDDEN(int) rtR0MpNotificationNativeInit(void)
{
    return VINF_SUCCESS;
}

DECLHIDDEN(void) rtR0MpNotificationNativeTerm(void)
{
}

#endif  /* Not supported / Not needed */

