/* $Id: VBoxSmcUtil-darwin.cpp $ */
/** @file
 * VBoxSmcUtil - Quick hack for viewing SMC data on a mac.
 */

/*
 * Copyright (C) 2013-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.
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include <iprt/asm.h>
#include <iprt/err.h>
#include <iprt/ctype.h>
#include <iprt/initterm.h>
#include <iprt/message.h>
#include <iprt/stream.h>
#include <iprt/string.h>

#include <mach/mach.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOReturn.h>
#include <CoreFoundation/CoreFoundation.h>
#include <unistd.h>


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
enum
{
    kSMCSuccess = 0,
    kSMCError = 1
};

typedef enum
{
    kSMCUserClientOpen = 0,
    kSMCUserClientClose,
    kSMCHandleYPCEvent,

    kSMCPlaceholder1,
    kSMCNumberOfMethods,

    kSMCReadKey,
    kSMCWriteKey,
    kSMCGetKeyCount,
    kSMCGetKeyFromIndex,
    kSMCGetKeyInfo,

    kSMCFireInterrupt,
    kSMCGetPLimits,
    kSMCGetVers,

    kSMCPlaceholder2,

    kSMCReadStatus,
    kSMCReadResult,
    kSMCVariableCommand
} KSMCFUNCTION;

typedef struct
{
    RTUINT32U           uKey;
    struct
    {
        uint8_t         uMajor;
        uint8_t         uMinor;
        uint8_t         uBuild;
        uint8_t         uReserved;
        uint16_t        uRelease;
    }                   Version;
    struct
    {
        uint16_t        uVer;
        uint16_t        cb;
        uint32_t        uCpuPLimit;
        uint32_t        uGpuPLimit;
        uint32_t        uMemPLimit;
    } SMCPLimitData;

    struct
    {
        IOByteCount     cbData;
        RTUINT32U       uDataType;
        uint8_t         fAttr;
    }                   KeyInfo;

    uint8_t             uResult;
    uint8_t             fStatus;
    uint8_t             bData;
    uint32_t            u32Data;
    uint8_t             abValue[32];
} SMCPARAM;


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
io_service_t    g_hSmcService = IO_OBJECT_NULL;
io_connect_t    g_hSmcConnect = IO_OBJECT_NULL;


static int ConnectToSmc(void)
{
    g_hSmcService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSMC"));
    if (g_hSmcService == IO_OBJECT_NULL)
        return VERR_NOT_FOUND;

    IOReturn rcIo = IOServiceOpen(g_hSmcService, mach_task_self(), 1, &g_hSmcConnect);
    if (rcIo == kIOReturnSuccess && g_hSmcConnect != IO_OBJECT_NULL)
    {
        rcIo = IOConnectCallMethod(g_hSmcConnect, kSMCUserClientOpen, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
        if (rcIo == kIOReturnSuccess)
            return VINF_SUCCESS;
        RTMsgError("kSMCUserClientOpen failed: %#x (%#x)\n", rcIo, rcIo);
    }
    else
    {
        RTMsgError("IOServiceOpen failed: %#x (%#x)\n", rcIo, rcIo);
        g_hSmcConnect = IO_OBJECT_NULL;
    }
    return RTErrConvertFromDarwinIO(rcIo);
}


static void DisconnectFromSmc(void)
{
    if (g_hSmcConnect)
    {
        IOConnectCallMethod(g_hSmcConnect, kSMCUserClientClose, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL);
        IOServiceClose(g_hSmcConnect);
        g_hSmcConnect = IO_OBJECT_NULL;
    }

    if (g_hSmcService)
    {
        IOServiceClose(g_hSmcService);
        g_hSmcService = IO_OBJECT_NULL;
    }
}

static int CallSmc(KSMCFUNCTION enmFunction, SMCPARAM *pIn, SMCPARAM *pOut)
{
    RT_ZERO(*pOut);
    pIn->bData    = enmFunction;
    size_t cbOut  = sizeof(*pOut);
    IOReturn rcIo = IOConnectCallStructMethod(g_hSmcConnect, kSMCHandleYPCEvent, pIn, sizeof(*pIn), pOut, &cbOut);
    if (rcIo == kIOReturnSuccess)
        return VINF_SUCCESS;
    RTMsgError("SMC call %d failed: rcIo=%d (%#x)\n", enmFunction, rcIo, rcIo);
    return RTErrConvertFromDarwinIO(rcIo);
}

static int GetKeyCount(uint32_t *pcKeys)
{
    SMCPARAM In;
    SMCPARAM Out;
    RT_ZERO(In); RT_ZERO(Out);
    In.KeyInfo.cbData = sizeof(uint32_t);
    int rc = CallSmc(kSMCGetKeyCount, &In, &Out);
    if (RT_SUCCESS(rc))
        *pcKeys = RT_BE2H_U32(Out.u32Data);
    else
        *pcKeys = 1;
    return rc;
}

static int GetKeyByIndex(uint32_t iKey, SMCPARAM *pKeyData)
{
    SMCPARAM In;
    RT_ZERO(In);
    In.u32Data = iKey;
    int rc = CallSmc(kSMCGetKeyFromIndex, &In, pKeyData);
    if (RT_SUCCESS(rc))
    {
        if (pKeyData->uResult == kSMCSuccess)
        {
            SMCPARAM Tmp = *pKeyData;

            /* Get the key info. */
            RT_ZERO(In);
            In.uKey.u = Tmp.uKey.u;
            rc = CallSmc(kSMCGetKeyInfo, &In, pKeyData);
            if (RT_SUCCESS(rc) && pKeyData->uResult == kSMCSuccess)
            {
                Tmp.KeyInfo = pKeyData->KeyInfo;

                /* Get the key value. */
                RT_ZERO(In);
                In.uKey = Tmp.uKey;
                In.KeyInfo = Tmp.KeyInfo;
                rc = CallSmc(kSMCReadKey, &In, pKeyData);
                if (RT_SUCCESS(rc) && (pKeyData->uResult == kSMCSuccess || pKeyData->uResult == 0x85 /* not readable */))
                {
                    pKeyData->uKey    = Tmp.uKey;
                    pKeyData->KeyInfo = Tmp.KeyInfo;
                    rc = VINF_SUCCESS;
                }
                else if (RT_SUCCESS(rc))
                {
                    RTMsgError("kSMCReadKey failed on #%x/%.4s: %#x\n", iKey, Tmp.uKey.au8, pKeyData->uResult);
                    rc = VERR_IO_GEN_FAILURE;
                }
            }
            else if (RT_SUCCESS(rc))
            {
                RTMsgError("kSMCGetKeyInfo failed on #%x/%.4s: %#x\n", iKey, Tmp.uKey.au8, pKeyData->uResult);
                rc = VERR_IO_GEN_FAILURE;
            }
        }
        else
        {
            RTMsgError("kSMCGetKeyFromIndex failed on #%x: %#x\n", iKey, pKeyData->uResult);
            rc = VERR_IO_GEN_FAILURE;
        }
    }
    return rc;
}


static int GetKeyByName(uint32_t uKey, SMCPARAM *pKeyData)
{
    SMCPARAM In;
    RT_ZERO(In);
    In.uKey.u = uKey;
    int rc = CallSmc(kSMCGetKeyInfo, &In, pKeyData);
    if (RT_SUCCESS(rc) && pKeyData->uResult == kSMCSuccess)
    {
        SMCPARAM Tmp = *pKeyData;

        /* Get the key value. */
        RT_ZERO(In);
        In.uKey.u = uKey;
        In.KeyInfo = Tmp.KeyInfo;
        rc = CallSmc(kSMCReadKey, &In, pKeyData);
        if (RT_SUCCESS(rc) && (pKeyData->uResult == kSMCSuccess || pKeyData->uResult == 0x85 /* not readable */))
        {
            pKeyData->uKey.u  = uKey;
            pKeyData->KeyInfo = Tmp.KeyInfo;
            rc = VINF_SUCCESS;
        }
        else if (RT_SUCCESS(rc))
        {
            RTMsgError("kSMCReadKey failed on %.4s: %#x\n", &uKey, pKeyData->uResult);
            rc = VERR_IO_GEN_FAILURE;
        }
    }
    else if (RT_SUCCESS(rc))
    {
        RTMsgError("kSMCGetKeyInfo failed on %.4s: %#x\n", &uKey, pKeyData->uResult);
        rc = VERR_IO_GEN_FAILURE;
    }
    return rc;
}

static void DisplayKey(SMCPARAM *pKey)
{
    pKey->uKey.u = RT_BE2H_U32(pKey->uKey.u);
    pKey->KeyInfo.uDataType.u = RT_BE2H_U32(pKey->KeyInfo.uDataType.u);
    RTPrintf("key=%4.4s  type=%4.4s  cb=%#04x  fAttr=%#04x",
             pKey->uKey.au8, pKey->KeyInfo.uDataType.au8, pKey->KeyInfo.cbData, pKey->KeyInfo.fAttr);
    if (pKey->uResult == kSMCSuccess)
    {
        bool fPrintable = true;
        for (uint32_t off = 0; off < pKey->KeyInfo.cbData; off++)
            if (!RT_C_IS_PRINT(pKey->abValue[off]))
            {
                fPrintable = false;
                break;
            }
       if (fPrintable)
           RTPrintf("  %.*s\n", pKey->KeyInfo.cbData, pKey->abValue);
       else
           RTPrintf("  %.*Rhxs\n", pKey->KeyInfo.cbData, pKey->abValue);
    }
    else if (pKey->uResult == 0x85)
        RTPrintf("  <not readable>\n");
}

static void DisplayKeyByName(uint32_t uKey)
{
    SMCPARAM Key;
    int rc = GetKeyByName(uKey, &Key);
    if (RT_SUCCESS(rc))
        DisplayKey(&Key);
}


int main(int argc, char **argv)
{
    int rc = RTR3InitExe(argc, &argv, 0);
    if (RT_FAILURE(rc))
        return RTMsgInitFailure(rc);

    rc = ConnectToSmc();
    if (RT_SUCCESS(rc))
    {
        /*
         * Dump the keys.
         */
        uint32_t cKeys;
        rc = GetKeyCount(&cKeys);
        if (RT_SUCCESS(rc))
            RTPrintf("#Keys=%u\n", cKeys);
        for (uint32_t iKey = 0; iKey < cKeys; iKey++)
        {
            SMCPARAM Key;
            rc = GetKeyByIndex(iKey, &Key);
            if (RT_SUCCESS(rc))
            {
                RTPrintf("%#06x: ", iKey);
                DisplayKey(&Key);
            }
        }

        /*
         * Known keys that doesn't make it into the enumeration.
         */
        DisplayKeyByName('OSK0');
        DisplayKeyByName('OSK1');
        DisplayKeyByName('OSK2');

        /* Negative checks, sometimes maybe. */
        DisplayKeyByName('$Num');
        DisplayKeyByName('MSTf');
        DisplayKeyByName('MSDS');
        DisplayKeyByName('LSOF');
    }
    DisconnectFromSmc();

    if (RT_SUCCESS(rc))
        return RTEXITCODE_SUCCESS;
    return RTEXITCODE_FAILURE;
}

