/* $Id: DrvGpioButton.cpp 101624 2023-10-27 13:44:50Z vboxsync $ */ /** @file * DrvGpioButton - Virtual GPIO driver for power/sleep button presses. */ /* * Copyright (C) 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 . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_GPIO #include #include #include #include #include #include "VBoxDD.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** The power button is currently pressed. */ #define DRV_GPIO_BUTTON_PRESSED_POWER RT_BIT_32(0) /** The sleep button is currently pressed. */ #define DRV_GPIO_BUTTON_PRESSED_SLEEP RT_BIT_32(1) /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * GPIO button driver instance data. * * @implements PDMIEVENTBUTTONPORT * @implements PDMIGPIOCONNECTOR */ typedef struct DRVGPIOBUTTON { /** The button event interface for use by Main. */ PDMIEVENTBUTTONPORT IEventButtonPort; /** The GPIO interface interface. */ PDMIGPIOCONNECTOR IGpioConnector; /** The GPIO port interface above. */ PPDMIGPIOPORT pGpioPort; /** Pointer to the driver instance. */ PPDMDRVINS pDrvIns; /** Currently pressed button. */ volatile uint32_t fButtonsPressed; /** The power button GPIO line to trigger. */ uint32_t uPowerButtonGpio; /** The sleep button GPIO line to trigger. */ uint32_t uSleepButtonGpio; TMTIMERHANDLE hTimerDepress; } DRVGPIOBUTTON; /** Pointer to a GPIO button driver instance. */ typedef DRVGPIOBUTTON *PDRVGPIOBUTTON; /** * @interface_method_impl{PDMIBASE,pfnQueryInterface} */ static DECLCALLBACK(void *) drvGpioButton_QueryInterface(PPDMIBASE pInterface, const char *pszIID) { PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); PDRVGPIOBUTTON pThis = PDMINS_2_DATA(pDrvIns, PDRVGPIOBUTTON); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIGPIOCONNECTOR, &pThis->IGpioConnector); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIEVENTBUTTONPORT, &pThis->IEventButtonPort); return NULL; } /** * @interface_method_impl{PDMIEVENTBUTTONPORT,pfnQueryGuestCanHandleButtonEvents} */ static DECLCALLBACK(int) drvGpioButton_QueryGuestCanHandleButtonEvents(PPDMIEVENTBUTTONPORT pInterface, bool *pfCanHandleButtonEvents) { PDRVGPIOBUTTON pThis = RT_FROM_MEMBER(pInterface, DRVGPIOBUTTON, IEventButtonPort); /** @todo Better interface for this. */ *pfCanHandleButtonEvents = pThis->pGpioPort->pfnGpioLineIsInput(pThis->pGpioPort, pThis->uPowerButtonGpio) || pThis->pGpioPort->pfnGpioLineIsInput(pThis->pGpioPort, pThis->uSleepButtonGpio); return VINF_SUCCESS; } /** * @interface_method_impl{PDMIEVENTBUTTONPORT,pfnPowerButtonPress} */ static DECLCALLBACK(int) drvGpioButton_PowerButtonPress(PPDMIEVENTBUTTONPORT pInterface) { PDRVGPIOBUTTON pThis = RT_FROM_MEMBER(pInterface, DRVGPIOBUTTON, IEventButtonPort); ASMAtomicOrU32(&pThis->fButtonsPressed, DRV_GPIO_BUTTON_PRESSED_POWER); int rc = pThis->pGpioPort->pfnGpioLineChange(pThis->pGpioPort, pThis->uPowerButtonGpio, true /*fVal*/); if (RT_SUCCESS(rc)) rc = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hTimerDepress, 250); return rc; } /** * @interface_method_impl{PDMIEVENTBUTTONPORT,pfnSleepButtonPress} */ static DECLCALLBACK(int) drvGpioButton_SleepButtonPress(PPDMIEVENTBUTTONPORT pInterface) { PDRVGPIOBUTTON pThis = RT_FROM_MEMBER(pInterface, DRVGPIOBUTTON, IEventButtonPort); ASMAtomicOrU32(&pThis->fButtonsPressed, DRV_GPIO_BUTTON_PRESSED_SLEEP); int rc = pThis->pGpioPort->pfnGpioLineChange(pThis->pGpioPort, pThis->uSleepButtonGpio, true /*fVal*/); if (RT_SUCCESS(rc)) rc = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hTimerDepress, 250); return rc; } /** * @interface_method_impl{PDMIEVENTBUTTONPORT,pfnQueryPowerButtonHandled} */ static DECLCALLBACK(int) drvGpioButton_QueryPowerButtonHandled(PPDMIEVENTBUTTONPORT pInterface, bool *pfHandled) { RT_NOREF(pInterface); /** @todo */ *pfHandled = true; return VINF_SUCCESS; } /** * Timer callback that depresses a button. */ static DECLCALLBACK(void) drvGpioButtonTimerDepress(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser) { PDRVGPIOBUTTON pThis = PDMINS_2_DATA(pDrvIns, PDRVGPIOBUTTON); Assert(hTimer == pThis->hTimerDepress); RT_NOREF(hTimer, pvUser); uint32_t fButtonsPressed = ASMAtomicXchgU32(&pThis->fButtonsPressed, 0); if (fButtonsPressed & DRV_GPIO_BUTTON_PRESSED_POWER) pThis->pGpioPort->pfnGpioLineChange(pThis->pGpioPort, pThis->uPowerButtonGpio, false /*fVal*/); if (fButtonsPressed & DRV_GPIO_BUTTON_PRESSED_SLEEP) pThis->pGpioPort->pfnGpioLineChange(pThis->pGpioPort, pThis->uSleepButtonGpio, false /*fVal*/); } /** * Construct an GPIO button driver instance. * * @copydoc FNPDMDRVCONSTRUCT */ static DECLCALLBACK(int) drvGpioButtonConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) { RT_NOREF(pCfg, fFlags); PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); PDRVGPIOBUTTON pThis = PDMINS_2_DATA(pDrvIns, PDRVGPIOBUTTON); PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; int rc = VINF_SUCCESS; /* * Init the static parts. */ /* IBase */ pDrvIns->IBase.pfnQueryInterface = drvGpioButton_QueryInterface; /* IEventButtonPort */ pThis->IEventButtonPort.pfnQueryGuestCanHandleButtonEvents = drvGpioButton_QueryGuestCanHandleButtonEvents; pThis->IEventButtonPort.pfnPowerButtonPress = drvGpioButton_PowerButtonPress; pThis->IEventButtonPort.pfnSleepButtonPress = drvGpioButton_SleepButtonPress; pThis->IEventButtonPort.pfnQueryPowerButtonHandled = drvGpioButton_QueryPowerButtonHandled; pThis->pDrvIns = pDrvIns; pThis->fButtonsPressed = 0; /* * Validate and read the configuration. */ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "PowerButtonGpio" "|SleepButtonGpio", ""); rc = pHlp->pfnCFGMQueryU32(pCfg, "PowerButtonGpio", &pThis->uPowerButtonGpio); if (RT_FAILURE(rc)) return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Configuration error: Failed to get the \"PowerButtonGpio\" value")); rc = pHlp->pfnCFGMQueryU32(pCfg, "SleepButtonGpio", &pThis->uSleepButtonGpio); if (RT_FAILURE(rc)) return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Configuration error: Failed to get the \"SleepButtonGpio\" value")); /* * Check that no-one is attached to us. */ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, ("Configuration error: Not possible to attach anything to this driver!\n"), VERR_PDM_DRVINS_NO_ATTACH); /* * Query the GPIO port interface. */ pThis->pGpioPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIGPIOPORT); if (!pThis->pGpioPort) { AssertMsgFailed(("Configuration error: the above device/driver didn't export the GPIO port interface!\n")); return VERR_PDM_MISSING_INTERFACE_ABOVE; } rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, drvGpioButtonTimerDepress, NULL, TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, "Button depress timer", &pThis->hTimerDepress); return VINF_SUCCESS; } /** * GPIO button driver registration record. */ const PDMDRVREG g_DrvGpioButton = { /* .u32Version = */ PDM_DRVREG_VERSION, /* .szName = */ "GpioButton", /* .szRCMod = */ "", /* .szR0Mod = */ "", /* .pszDescription = */ "GPIO Button Driver", /* .fFlags = */ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, /* .fClass = */ PDM_DRVREG_CLASS_GPIO, /* .cMaxInstances = */ UINT32_MAX, /* .cbInstance = */ sizeof(DRVGPIOBUTTON), /* .pfnConstruct = */ drvGpioButtonConstruct, /* .pfnDestruct = */ NULL, /* .pfnRelocate = */ NULL, /* .pfnIOCtl = */ NULL, /* .pfnPowerOn = */ NULL, /* .pfnReset = */ NULL, /* .pfnSuspend = */ NULL, /* .pfnResume = */ NULL, /* .pfnAttach = */ NULL, /* .pfnDetach = */ NULL, /* .pfnPowerOff = */ NULL, /* .pfnSoftReset = */ NULL, /* .u32EndVersion = */ PDM_DRVREG_VERSION };