/* $Id: DevSmc.cpp 69498 2017-10-28 15:07:25Z vboxsync $ */ /** @file * DevSmc - Apple System Management Controller. * * The SMC is controlling power, fans, take measurements (voltage, temperature, * fan speed, ++), and lock Mac OS X to Apple hardware. For more details see: * - http://en.wikipedia.org/wiki/System_Management_Controller * - http://www.parhelia.ch/blog/statics/k3_keys.html * - http://www.nosuchcon.org/talks/D1_02_Alex_Ninjas_and_Harry_Potter.pdf */ /* * Copyright (C) 2013-2016 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 * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DEV_SMC #include #include #include #include #include #ifdef IN_RING0 # include # include #endif #if defined(RT_OS_DARWIN) && defined(IN_RING3) # include "IOKit/IOKitLib.h" #endif #include "VBoxDD.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** The current version of the saved state. */ #define SMC_SAVED_STATE_VERSION 1 /** @todo later 2 */ /** Empty saved state version. */ #define SMC_SAVED_STATE_VERSION_BAKA 1 /** The ring-0 operation number that attempts to get OSK0 and OSK1 from the real * SMC. */ #define SMC_CALLR0_READ_OSK 1 /** @name Apple SMC port and register definitions. * @{ */ /** The first Apple SMC port. */ #define SMC_PORT_FIRST 0x0300 /** The number of registers (also ports). */ #define SMC_REG_COUNT 0x0020 /** The data register. */ #define SMC_REG_DATA 0x00 #define SMC_PORT_DATA (SMC_PORT_FIRST + SMC_REG_DATA) /** The command register. */ #define SMC_REG_CMD 0x04 #define SMC_PORT_CMD (SMC_PORT_FIRST + SMC_REG_CMD) /** Status code register. */ #define SMC_REG_STATUS_CODE 0x1e #define SMC_PORT_STATUS_CODE (SMC_PORT_FIRST + SMC_REG_STATUS_CODE) /** @} */ /** @name Apple SMC Commands. * @{ */ #define SMC_CMD_GET_KEY_VALUE 0x10 #define SMC_CMD_PUT_KEY 0x11 #define SMC_CMD_GET_KEY_BY_INDEX 0x12 #define SMC_CMD_GET_KEY_INFO 0x13 /** @} */ /** @name Apple SMC Status Codes. * @{ */ #define SMC_STATUS_CD_SUCCESS UINT8_C(0x00) #define SMC_STATUS_CD_COMM_COLLISION UINT8_C(0x80) #define SMC_STATUS_CD_SPURIOUS_DATA UINT8_C(0x81) #define SMC_STATUS_CD_BAD_COMMAND UINT8_C(0x82) #define SMC_STATUS_CD_BAD_PARAMETER UINT8_C(0x83) #define SMC_STATUS_CD_KEY_NOT_FOUND UINT8_C(0x84) #define SMC_STATUS_CD_KEY_NOT_READABLE UINT8_C(0x85) #define SMC_STATUS_CD_KEY_NOT_WRITABLE UINT8_C(0x86) #define SMC_STATUS_CD_KEY_SIZE_MISMATCH UINT8_C(0x87) #define SMC_STATUS_CD_FRAMING_ERROR UINT8_C(0x88) #define SMC_STATUS_CD_BAD_ARGUMENT_ERROR UINT8_C(0x89) #define SMC_STATUS_CD_TIMEOUT_ERROR UINT8_C(0xb7) #define SMC_STATUS_CD_KEY_INDEX_RANGE_ERROR UINT8_C(0xb8) #define SMC_STATUS_CD_BAD_FUNC_PARAMETER UINT8_C(0xc0) #define SMC_STATUS_CD_EVENT_BUFF_WRONG_ORDER UINT8_C(0x??) #define SMC_STATUS_CD_EVENT_BUFF_READ_ERROR UINT8_C(0x??) #define SMC_STATUS_CD_DEVICE_ACCESS_ERROR UINT8_C(0xc7) #define SMC_STATUS_CD_UNSUPPORTED_FEATURE UINT8_C(0xcb) #define SMC_STATUS_CD_SMB_ACCESS_ERROR UINT8_C(0xcc) /** @} */ /** @name Apple SMC Key Attributes. * @{ */ #define SMC_KEY_ATTR_PRIVATE UINT8_C(0x01) #define SMC_KEY_ATTR_UKN_0x02 UINT8_C(0x02) #define SMC_KEY_ATTR_UKN_0x04 UINT8_C(0x04) #define SMC_KEY_ATTR_CONST UINT8_C(0x08) #define SMC_KEY_ATTR_FUNCTION UINT8_C(0x10) #define SMC_KEY_ATTR_UKN_0x20 UINT8_C(0x20) #define SMC_KEY_ATTR_WRITE UINT8_C(0x40) #define SMC_KEY_ATTR_READ UINT8_C(0x80) /** @} */ /** The index of the first enumerable key in g_aSmcKeys. */ #define SMC_KEYIDX_FIRST_ENUM 2 /** Macro for emitting a static DEVSMC4CHID initializer. */ #define SMC4CH(a_sz4) { { a_sz4[0], a_sz4[1], a_sz4[2], a_sz4[3] } } /** * Macro for comparing DEVSMC4CHID with a string value. * @returns true if equal, false if not. */ #define SMC4CH_EQ(a_pSmcKey, a_sz4) ( (a_pSmcKey)->u32 == RT_MAKE_U32_FROM_U8(a_sz4[0], a_sz4[1], a_sz4[2], a_sz4[3]) ) /** Indicates the we want a 2.x SMC. */ #define VBOX_WITH_SMC_2_x /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * 4 char identifier */ typedef union DEVSMC4CHID { /** Byte view. */ uint8_t ab[4]; /** 32-bit unsigned integer view. */ uint32_t u32; } DEVSMC4CHID; /** * Current key data area for communicating with the guest. */ typedef struct DEVSMCCURKEY { /** The key. */ DEVSMC4CHID Key; /** The data type. */ DEVSMC4CHID Type; /** Key attributes. */ uint8_t fAttr; /** The value length. */ uint8_t cbValue; uint8_t abAlignment[2]; /** * The value union. 32 bytes is probably sufficient here, but we provide a * little more room since it doesn't cost us anything. */ union { /** Byte view. */ uint8_t ab[128]; /** 16-bit view. */ uint16_t u16; /** 32-bit view. */ uint32_t u32; } Value; } DEVSMCCURKEY; AssertCompileSize(DEVSMCCURKEY, 128+12); /** Pointer to the current key buffer. */ typedef DEVSMCCURKEY *PDEVSMCCURKEY; /** Const pointer to the current key buffer. */ typedef DEVSMCCURKEY const *PCDEVSMCCURKEY; /** * The device */ typedef struct DEVSMC { /** The current command (SMC_PORT_CMD write). */ uint8_t bCmd; /** Current key offset. */ uint8_t offKey; /** Current value offset. */ uint8_t offValue; /** Number of keys in the aKeys array. */ uint8_t cKeys; /** The current key data the user is accessing. */ DEVSMCCURKEY CurKey; /** * Generic read/write register values. * * The DATA register entry is not used at all. The CMD register entry contains * the state value. */ union { /** Index register view. */ uint8_t abRegsRW[SMC_REG_COUNT]; /** Named register view. */ struct { uint8_t abUnknown0[0x04]; /** The current state (SMC_PORT_CMD read). */ uint8_t bState; uint8_t abUnknown1[0x1e - 0x05]; /** The current status code (SMC_PORT_STATUS_CODE). */ uint8_t bStatusCode; uint8_t abUnknown2[1]; } s; } u; /** @name Key data. * @{ */ /** OSK0 and OSK1. */ char szOsk0And1[64+1]; /** $Num - unknown function. */ uint8_t bDollaryNumber; /** MSSD - shutdown reason. */ uint8_t bShutdownReason; /** NATJ - Ninja action timer job. */ uint8_t bNinjaActionTimerJob; /** @} */ } DEVSMC; #ifndef _MSC_VER AssertCompileMembersAtSameOffset(DEVSMC, u.abRegsRW[SMC_REG_CMD], DEVSMC, u.s.bState); AssertCompileMembersAtSameOffset(DEVSMC, u.abRegsRW[SMC_REG_STATUS_CODE], DEVSMC, u.s.bStatusCode); #endif /** Pointer to the SMC state. */ typedef DEVSMC *PDEVSMC; #ifndef VBOX_DEVICE_STRUCT_TESTCASE /** * Method for retriving the key value and/or optionally also attributes. * * @returns Apple SMC Status Code. * @param pThis The SMC instance data. * @param pCurKey The current key structure (input / output). * @param bCmd The current command (mostly for getters that also * provides attributes or type info). * @param pKeyDesc Pointer to the key descriptor so that the getter can * service more than once key. */ typedef DECLCALLBACK(uint8_t) DEVSMCKEYGETTER(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, struct DEVSMCKEYDESC const *pKeyDesc); /** * Method for setting the key value. * * @returns Apple SMC Status Code. * @param pThis The SMC instance data. * @param pCurKey The current key structure (input / output). * @param bCmd The current command (currently not relevant). * @param pKeyDesc Pointer to the key descriptor so that the getter can * service more than once key. */ typedef DECLCALLBACK(uint8_t) DEVSMCKEYPUTTER(PDEVSMC pThis, PCDEVSMCCURKEY pCurKey, uint8_t bCmd, struct DEVSMCKEYDESC const *pKeyDesc); /** * Key descriptor. */ typedef struct DEVSMCKEYDESC { /** The key 4 character identifier. */ DEVSMC4CHID Key; /** Type 4 character identifier. 0 means the getter will set it dynamically. */ DEVSMC4CHID Type; /** Getter method, see DEVSMCKEYPUTTER. */ DEVSMCKEYGETTER *pfnGet; /** Putter method, see DEVSMCKEYPUTTER. */ DEVSMCKEYPUTTER *pfnPut; /** The keyvalue size. If 0 the pfnGet/pfnPut will define/check the size. */ uint8_t cbValue; /** Attributes. 0 means the getter will set it dynamically. */ uint8_t fAttr; } DEVSMCKEYDESC; /** Pointer to a constant SMC key descriptor. */ typedef DEVSMCKEYDESC const *PCDEVSMCKEYDESC; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ #ifdef IN_RING3 static DEVSMCKEYGETTER scmKeyGetOSKs; static DEVSMCKEYGETTER scmKeyGetKeyCount; static DEVSMCKEYGETTER scmKeyGetRevision; # ifdef VBOX_WITH_SMC_2_x static DEVSMCKEYGETTER scmKeyGetDollarAddress; static DEVSMCKEYGETTER scmKeyGetDollarNumber; static DEVSMCKEYPUTTER scmKeyPutDollarNumber; # endif static DEVSMCKEYGETTER scmKeyGetShutdownReason; static DEVSMCKEYPUTTER scmKeyPutShutdownReason; static DEVSMCKEYGETTER scmKeyGetNinjaTimerAction; static DEVSMCKEYPUTTER scmKeyPutNinjaTimerAction; static DEVSMCKEYGETTER scmKeyGetOne; static DEVSMCKEYGETTER scmKeyGetZero; #endif /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ #ifdef IN_RING3 /** * Apple SMC key descriptor table. */ static const DEVSMCKEYDESC g_aSmcKeys[] = { /* Non-enum keys first. */ { SMC4CH("OSK0"), SMC4CH("ch8*"), scmKeyGetOSKs, NULL, 32, SMC_KEY_ATTR_READ | SMC_KEY_ATTR_FUNCTION }, { SMC4CH("OSK1"), SMC4CH("ch8*"), scmKeyGetOSKs, NULL, 32, SMC_KEY_ATTR_READ | SMC_KEY_ATTR_FUNCTION }, /* The first enum key is the #KEY value. */ { SMC4CH("#KEY"), SMC4CH("ui32"), scmKeyGetKeyCount, NULL, 4, SMC_KEY_ATTR_READ }, # ifdef VBOX_WITH_SMC_2_x { SMC4CH("$Adr"), SMC4CH("ui32"), scmKeyGetDollarAddress, NULL, 4, SMC_KEY_ATTR_READ }, { SMC4CH("$Num"), SMC4CH("ui8 "), scmKeyGetDollarNumber, scmKeyPutDollarNumber, 1, SMC_KEY_ATTR_READ | SMC_KEY_ATTR_WRITE | SMC_KEY_ATTR_PRIVATE }, { SMC4CH("BEMB"), SMC4CH("flag"), scmKeyGetOne, NULL, 1, SMC_KEY_ATTR_READ }, # else { SMC4CH("LSOF"), SMC4CH("flag"), scmKeyGetZero, NULL, 1, SMC_KEY_ATTR_READ }, # endif { SMC4CH("MSSD"), SMC4CH("si8 "), scmKeyGetShutdownReason, scmKeyPutShutdownReason, 1, SMC_KEY_ATTR_READ | SMC_KEY_ATTR_WRITE | SMC_KEY_ATTR_PRIVATE }, /* MSDS is not present on MacPro3,1 nor MacBookPro10,1, so returning not found is fine. */ # ifdef VBOX_WITH_SMC_2_x { SMC4CH("MSTf"), SMC4CH("ui8 "), scmKeyGetZero, NULL, 1, SMC_KEY_ATTR_READ }, # endif { SMC4CH("NATJ"), SMC4CH("ui8 "), scmKeyGetNinjaTimerAction, scmKeyPutNinjaTimerAction, 1, SMC_KEY_ATTR_READ | SMC_KEY_ATTR_WRITE | SMC_KEY_ATTR_PRIVATE }, { SMC4CH("REV "), SMC4CH("{rev"), scmKeyGetRevision, NULL, 6, SMC_KEY_ATTR_READ }, /** @todo MSSP, NTOK and more. */ }; #endif #ifdef IN_RING0 /** Do once for the SMC ring-0 static data (g_abOsk0And1, g_fHaveOsk). */ static RTONCE g_SmcR0Once = RTONCE_INITIALIZER; /** Indicates whether we've successfully queried the OSK* keys. */ static bool g_fHaveOsk = false; /** The OSK0 and OSK1 values. */ static uint8_t g_abOsk0And1[32+32]; /** * Waits for the specified state on the host SMC. * * @returns success indicator. * @param bState The desired state. * @param pszWhat What we're currently doing. For the log. */ static bool devR0SmcWaitHostState(uint8_t bState, const char *pszWhat) { uint8_t bCurState = 0; /* (MSC is potentially uninitialized) */ for (uint32_t cMsSleep = 1; cMsSleep <= 64; cMsSleep <<= 1) { RTThreadSleep(cMsSleep); bCurState = ASMInU16(SMC_PORT_CMD); if ((bCurState & 0xf) == bState) return true; } LogRel(("devR0Smc: %s: bCurState=%#x, wanted %#x.\n", pszWhat, bCurState, bState)); #if 0 uint8_t bCurStatus2 = ASMInU8(SMC_PORT_STATUS_CODE); uint8_t bCurStatus3 = ASMInU8(SMC_PORT_STATUS_CODE); uint16_t wCurStatus3 = ASMInU16(SMC_PORT_STATUS_CODE); uint32_t dwCurStatus3 = ASMInU32(SMC_PORT_STATUS_CODE); LogRel(("SMC: status2=%#x status3=%#x w=%#x dw=%#x\n", bCurStatus2, bCurStatus3, wCurStatus3, dwCurStatus3)); #endif return false; } /** * Reads a key by name from the host SMC. * * @returns success indicator. * @param pszName The key name, must be exactly 4 chars long. * @param pbBuf The output buffer. * @param cbBuf The buffer size. Max 32 bytes. */ static bool devR0SmcQueryHostKey(const char *pszName, uint8_t *pbBuf, size_t cbBuf) { Assert(strlen(pszName) == 4); Assert(cbBuf <= 32); Assert(cbBuf > 0); /* * Issue the READ command. */ uint32_t cMsSleep = 1; for (;;) { ASMOutU32(SMC_PORT_CMD, SMC_CMD_GET_KEY_VALUE); RTThreadSleep(cMsSleep); uint8_t bCurState = ASMInU8(SMC_PORT_CMD); if ((bCurState & 0xf) == 0xc) break; cMsSleep <<= 1; if (cMsSleep > 64) { LogRel(("devR0Smc: %s: bCurState=%#x, wanted %#x.\n", "cmd", bCurState, 0xc)); return false; } } /* * Send it the key. */ for (unsigned off = 0; off < 4; off++) { ASMOutU8(SMC_PORT_DATA, pszName[off]); if (!devR0SmcWaitHostState(4, "key")) return false; } /* * The desired amount of output. */ ASMOutU8(SMC_PORT_DATA, (uint8_t)cbBuf); /* * Read the output. */ for (size_t off = 0; off < cbBuf; off++) { if (!devR0SmcWaitHostState(5, off ? "data" : "len")) return false; pbBuf[off] = ASMInU8(SMC_PORT_DATA); } LogRel(("SMC: pbBuf=%.*s\n", cbBuf, pbBuf)); return true; } /** * RTOnce callback that initializes g_fHaveOsk and g_abOsk0And1. * * @returns VINF_SUCCESS. * @param pvUserIgnored Ignored. */ static DECLCALLBACK(int) devR0SmcInitOnce(void *pvUserIgnored) { g_fHaveOsk = devR0SmcQueryHostKey("OSK0", &g_abOsk0And1[0], 32) && devR0SmcQueryHostKey("OSK1", &g_abOsk0And1[32], 32); #if 0 /* * Dump the device registers. */ for (uint16_t uPort = 0x300; uPort < 0x320; uPort ++) LogRel(("SMC: %#06x=%#010x w={%#06x, %#06x}, b={%#04x %#04x %#04x %#04x}\n", uPort, ASMInU32(uPort), ASMInU16(uPort), ASMInU16(uPort + 2), ASMInU8(uPort), ASMInU8(uPort + 1), ASMInU8(uPort +2), ASMInU8(uPort + 3) )); #endif NOREF(pvUserIgnored); return VINF_SUCCESS; } /** * @callback_method_impl{FNPDMDEVREQHANDLERR0} */ PDMBOTHCBDECL(int) devR0SmcReqHandler(PPDMDEVINS pDevIns, uint32_t uOperation, uint64_t u64Arg) { PDEVSMC pThis = PDMINS_2_DATA(pDevIns, PDEVSMC); int rc = VERR_INVALID_FUNCTION; RT_NOREF_PV(u64Arg); if (uOperation == SMC_CALLR0_READ_OSK) { rc = RTOnce(&g_SmcR0Once, devR0SmcInitOnce, NULL); if ( RT_SUCCESS(rc) && g_fHaveOsk) { AssertCompile(sizeof(g_abOsk0And1) + 1 == sizeof(pThis->szOsk0And1)); memcpy(pThis->szOsk0And1, g_abOsk0And1, sizeof(pThis->szOsk0And1) - 1); pThis->szOsk0And1[sizeof(pThis->szOsk0And1) - 1] = '\0'; } } return rc; } #endif /* IN_RING0 */ #if defined(IN_RING3) && defined(RT_OS_DARWIN) /** * Preferred method to retrieve the SMC key. * * @param pabKey where to store the key. * @param cbKey size of the buffer. */ static int getSmcKeyOs(char *pabKey, uint32_t cbKey) { /* * Method as described in Amit Singh's article: * http://osxbook.com/book/bonus/chapter7/tpmdrmmyth/ */ typedef struct { uint32_t key; uint8_t pad0[22]; uint32_t datasize; uint8_t pad1[10]; uint8_t cmd; uint32_t pad2; uint8_t data[32]; } AppleSMCBuffer; AssertReturn(cbKey >= 65, VERR_INTERNAL_ERROR); io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSMC")); if (!service) return VERR_NOT_FOUND; io_connect_t port = (io_connect_t)0; kern_return_t kr = IOServiceOpen(service, mach_task_self(), 0, &port); IOObjectRelease(service); if (kr != kIOReturnSuccess) return RTErrConvertFromDarwin(kr); AppleSMCBuffer inputStruct = { 0, {0}, 32, {0}, 5, }; AppleSMCBuffer outputStruct; size_t cbOutputStruct = sizeof(outputStruct); for (int i = 0; i < 2; i++) { inputStruct.key = (uint32_t)(i == 0 ? 'OSK0' : 'OSK1'); kr = IOConnectCallStructMethod((mach_port_t)port, (uint32_t)2, (const void *)&inputStruct, sizeof(inputStruct), (void *)&outputStruct, &cbOutputStruct); if (kr != kIOReturnSuccess) { IOServiceClose(port); return RTErrConvertFromDarwin(kr); } for (int j = 0; j < 32; j++) pabKey[j + i*32] = outputStruct.data[j]; } IOServiceClose(port); pabKey[64] = 0; return VINF_SUCCESS; } #endif /* IN_RING3 && RT_OS_DARWIN */ #ifdef IN_RING3 /* For now. */ /** @callback_method_impl{DEVSMCKEYGETTER, OSK0 and OSK1} */ static uint8_t scmKeyGetOSKs(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF1(bCmd); Assert(SMC4CH_EQ(&pKeyDesc->Key, "OSK0") || SMC4CH_EQ(&pKeyDesc->Key, "OSK1")); const char *pszSrc = pThis->szOsk0And1; if (SMC4CH_EQ(&pKeyDesc->Key, "OSK1")) pszSrc += 32; memcpy(pCurKey->Value.ab, pszSrc, 32); return SMC_STATUS_CD_SUCCESS; } /** @callback_method_impl{DEVSMCKEYGETTER, \#KEY} */ static uint8_t scmKeyGetKeyCount(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF3(pThis, bCmd, pKeyDesc); Assert(pKeyDesc == &g_aSmcKeys[SMC_KEYIDX_FIRST_ENUM]); uint32_t cKeys = RT_ELEMENTS(g_aSmcKeys) - SMC_KEYIDX_FIRST_ENUM; pCurKey->Value.u32 = RT_H2BE_U32(cKeys); return SMC_STATUS_CD_SUCCESS; } /** @callback_method_impl{DEVSMCKEYGETTER, REV - Source revision.} */ static uint8_t scmKeyGetRevision(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF3(pThis, bCmd, pKeyDesc); #ifdef VBOX_WITH_SMC_2_x pCurKey->Value.ab[0] = 0x02; pCurKey->Value.ab[1] = 0x03; pCurKey->Value.ab[2] = 0x0f; pCurKey->Value.ab[3] = 0x00; pCurKey->Value.ab[4] = 0x00; pCurKey->Value.ab[5] = 0x35; #else pCurKey->Value.ab[0] = 0x01; pCurKey->Value.ab[1] = 0x25; pCurKey->Value.ab[2] = 0x0f; pCurKey->Value.ab[3] = 0x00; pCurKey->Value.ab[4] = 0x00; pCurKey->Value.ab[5] = 0x04; #endif return SMC_STATUS_CD_SUCCESS; } #ifdef VBOX_WITH_SMC_2_x /** @callback_method_impl{DEVSMCKEYGETTER, $Adr - SMC address.} */ static uint8_t scmKeyGetDollarAddress(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF3(pThis, bCmd, pKeyDesc); pCurKey->Value.u32 = RT_H2BE_U32(SMC_PORT_FIRST); return VINF_SUCCESS; } /** @callback_method_impl{DEVSMCKEYGETTER, $Num - Some kind of number.} */ static uint8_t scmKeyGetDollarNumber(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(bCmd, pKeyDesc); pCurKey->Value.ab[0] = pThis->bDollaryNumber; return VINF_SUCCESS; } /** @callback_method_impl{DEVSMCKEYPUTTER, $Num - Some kind of number.} */ static uint8_t scmKeyPutDollarNumber(PDEVSMC pThis, PCDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(bCmd, pKeyDesc); Log(("scmKeyPutDollarNumber: %#x -> %#x\n", pThis->bDollaryNumber, pCurKey->Value.ab[0])); pThis->bDollaryNumber = pCurKey->Value.ab[0]; return VINF_SUCCESS; } #endif /* VBOX_WITH_SMC_2_x */ /** @callback_method_impl{DEVSMCKEYGETTER, MSSD - Machine Shutdown reason.} */ static uint8_t scmKeyGetShutdownReason(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(bCmd, pKeyDesc); pCurKey->Value.ab[0] = pThis->bShutdownReason; return SMC_STATUS_CD_SUCCESS; } /** @callback_method_impl{DEVSMCKEYPUTTER, MSSD - Machine Shutdown reason.} */ static uint8_t scmKeyPutShutdownReason(PDEVSMC pThis, PCDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(bCmd, pKeyDesc); Log(("scmKeyPutShutdownReason: %#x -> %#x\n", pThis->bShutdownReason, pCurKey->Value.ab[0])); pThis->bShutdownReason = pCurKey->Value.ab[0]; return SMC_STATUS_CD_SUCCESS; } /** @callback_method_impl{DEVSMCKEYGETTER, MSSD - Ninja timer action job.} */ static uint8_t scmKeyGetNinjaTimerAction(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(bCmd, pKeyDesc); pCurKey->Value.ab[0] = pThis->bNinjaActionTimerJob; return SMC_STATUS_CD_SUCCESS; } /** @callback_method_impl{DEVSMCKEYPUTTER, NATJ - Ninja timer action job.} */ static uint8_t scmKeyPutNinjaTimerAction(PDEVSMC pThis, PCDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(bCmd, pKeyDesc); Log(("scmKeyPutNinjaTimerAction: %#x -> %#x\n", pThis->bNinjaActionTimerJob, pCurKey->Value.ab[0])); pThis->bNinjaActionTimerJob = pCurKey->Value.ab[0]; return SMC_STATUS_CD_SUCCESS; } #ifdef VBOX_WITH_SMC_2_x /** @callback_method_impl{DEVSMCKEYGETTER, Generic one getter.} */ static uint8_t scmKeyGetOne(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(pThis, bCmd); memset(&pCurKey->Value.ab[0], 0, pKeyDesc->cbValue); pCurKey->Value.ab[pKeyDesc->cbValue - 1] = 1; return SMC_STATUS_CD_SUCCESS; } #endif /* VBOX_WITH_SMC_2_x */ /** @callback_method_impl{DEVSMCKEYGETTER, Generic zero getter.} */ static uint8_t scmKeyGetZero(PDEVSMC pThis, PDEVSMCCURKEY pCurKey, uint8_t bCmd, PCDEVSMCKEYDESC pKeyDesc) { RT_NOREF2(pThis, bCmd); memset(&pCurKey->Value.ab[0], 0, pKeyDesc->cbValue); return SMC_STATUS_CD_SUCCESS; } /** * Looks up a key and copies its value and attributes into the CurKey. * * @returns Key index on success, UINT32_MAX on failure. * @param uKeyValue The key value (DEVSMC4CHID.u32). */ static uint32_t smcKeyLookup(uint32_t uKeyValue) { uint32_t iKey = RT_ELEMENTS(g_aSmcKeys); while (iKey-- > 0) if (g_aSmcKeys[iKey].Key.u32 == uKeyValue) return iKey; return UINT32_MAX; } /** * Looks up a key and copies its value and attributes into the CurKey. * * @returns Apple SMC Status Code. * @param pThis The SMC instance data. */ static uint8_t smcKeyGetByName(PDEVSMC pThis) { uint8_t bRc; #ifdef LOG_ENABLED uint32_t const uKeyValueLog = RT_H2LE_U32(pThis->CurKey.Key.u32); #endif uint32_t iKey = smcKeyLookup(pThis->CurKey.Key.u32); if (iKey != UINT32_MAX) { if ( g_aSmcKeys[iKey].cbValue == pThis->CurKey.cbValue || !g_aSmcKeys[iKey].cbValue) { pThis->CurKey.Type = g_aSmcKeys[iKey].Type; pThis->CurKey.fAttr = g_aSmcKeys[iKey].fAttr; RT_ZERO(pThis->CurKey.Value); if (g_aSmcKeys[iKey].pfnGet) { bRc = g_aSmcKeys[iKey].pfnGet(pThis, &pThis->CurKey, pThis->bCmd, &g_aSmcKeys[iKey]); if (bRc == SMC_STATUS_CD_SUCCESS) { LogFlow(("smcKeyGetByName: key=%4.4s value=%.*Rhxs\n", &uKeyValueLog, pThis->CurKey.cbValue, &pThis->CurKey.Value)); return SMC_STATUS_CD_SUCCESS; } Log(("smcKeyGetByName: key=%4.4s getter failed! bRc=%#x\n", &uKeyValueLog, bRc)); } else { Log(("smcKeyGetByName: key=%4.4s is not readable!\n", &uKeyValueLog)); bRc = SMC_STATUS_CD_KEY_NOT_READABLE; } } else { Log(("smcKeyGetByName: Wrong value size; user=%#x smc=%#x key=%4.4s !\n", pThis->CurKey.cbValue, g_aSmcKeys[iKey].cbValue, &uKeyValueLog)); bRc = SMC_STATUS_CD_KEY_SIZE_MISMATCH; } } else { Log(("smcKeyGetByName: Key not found! key=%4.4s size=%#x\n", &uKeyValueLog, pThis->CurKey.cbValue)); bRc = SMC_STATUS_CD_KEY_NOT_FOUND; } RT_ZERO(pThis->CurKey); return bRc; } /** * Looks up a key by index and copies its name (and attributes) into the CurKey. * * @returns Apple SMC Status Code. * @param pThis The SMC instance data. */ static uint8_t smcKeyGetByIndex(PDEVSMC pThis) { uint8_t bRc; uint32_t iKey = RT_BE2H_U32(pThis->CurKey.Key.u32); if (iKey < RT_ELEMENTS(g_aSmcKeys) - SMC_KEYIDX_FIRST_ENUM) { pThis->CurKey.Key = g_aSmcKeys[iKey].Key; pThis->CurKey.Type = g_aSmcKeys[iKey].Type; pThis->CurKey.fAttr = g_aSmcKeys[iKey].fAttr; pThis->CurKey.cbValue = g_aSmcKeys[iKey].cbValue; RT_ZERO(pThis->CurKey.Value); Log(("smcKeyGetByIndex: %#x -> %c%c%c%c\n", iKey, pThis->CurKey.Key.ab[3], pThis->CurKey.Key.ab[2], pThis->CurKey.Key.ab[1], pThis->CurKey.Key.ab[0])); bRc = SMC_STATUS_CD_SUCCESS; } else { Log(("smcKeyGetByIndex: Key out or range: %#x, max %#x\n", iKey, RT_ELEMENTS(g_aSmcKeys) - SMC_KEYIDX_FIRST_ENUM)); bRc = SMC_STATUS_CD_KEY_NOT_FOUND; } return bRc; } /** * Looks up a key by index and copies its attributes into the CurKey. * * @returns Apple SMC Status Code. * @param pThis The SMC instance data. */ static uint8_t smcKeyGetAttrByName(PDEVSMC pThis) { uint8_t bRc; #ifdef LOG_ENABLED uint32_t const uKeyValueLog = RT_H2LE_U32(pThis->CurKey.Key.u32); #endif uint32_t iKey = smcKeyLookup(pThis->CurKey.Key.u32); if (iKey != UINT32_MAX) { pThis->CurKey.Type = g_aSmcKeys[iKey].Type; pThis->CurKey.fAttr = g_aSmcKeys[iKey].fAttr; pThis->CurKey.cbValue = g_aSmcKeys[iKey].cbValue; RT_ZERO(pThis->CurKey.Value); if (g_aSmcKeys[iKey].cbValue) bRc = SMC_STATUS_CD_SUCCESS; else bRc = g_aSmcKeys[iKey].pfnGet(pThis, &pThis->CurKey, pThis->bCmd, &g_aSmcKeys[iKey]); if (bRc == SMC_STATUS_CD_SUCCESS) { LogFlow(("smcKeyGetAttrByName: key=%4.4s value=%.*Rhxs\n", &uKeyValueLog, pThis->CurKey.cbValue, &pThis->CurKey.Value)); return SMC_STATUS_CD_SUCCESS; } Log(("smcKeyGetAttrByName: key=%4.4s getter failed! bRc=%#x\n", &uKeyValueLog, bRc)); } else { Log(("smcKeyGetAttrByName: Key not found! key=%4.4s size=%#x\n", &uKeyValueLog, pThis->CurKey.cbValue)); bRc = SMC_STATUS_CD_KEY_NOT_FOUND; } RT_ZERO(pThis->CurKey); return bRc; } static uint8_t smcKeyPutPrepare(PDEVSMC pThis) { RT_NOREF1(pThis); return 0; } static uint8_t smcKeyPutValue(PDEVSMC pThis) { RT_NOREF1(pThis); return 0; } /** * Data register read. * * @returns VINF_SUCCESS or VINF_IOM_R3_IOPORT_WRITE. * @param uReg The register number. * @param pbValue Where to return the value. */ static int smcRegData_r(PDEVSMC pThis, uint8_t uReg, uint8_t *pbValue) { RT_NOREF1(uReg); switch (pThis->bCmd) { case SMC_CMD_GET_KEY_VALUE: if ( pThis->u.s.bState == 0x05 && pThis->offValue < pThis->CurKey.cbValue) { *pbValue = pThis->CurKey.Value.ab[pThis->offValue]; if (++pThis->offValue >= pThis->CurKey.cbValue) pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SUCCESS; } else { Log(("smcRegData_r: Reading too much or at wrong time during SMC_CMD_GET_KEY_INFO! bState=%#x offValue=%#x\n", pThis->u.s.bState, pThis->offValue)); pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SPURIOUS_DATA; /** @todo check status code */ } break; case SMC_CMD_GET_KEY_INFO: if ( pThis->u.s.bState == 0x05 && pThis->offValue < 6) { if (pThis->offValue == 0) *pbValue = pThis->CurKey.cbValue; else if (pThis->offValue < 1 + 4) *pbValue = pThis->CurKey.Type.ab[pThis->offValue - 1]; else *pbValue = pThis->CurKey.fAttr; if (++pThis->offValue >= 6) pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SUCCESS; } else { Log(("smcRegData_r: Reading too much or at wrong time during SMC_CMD_GET_KEY_INFO! bState=%#x offValue=%#x\n", pThis->u.s.bState, pThis->offValue)); pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SPURIOUS_DATA; /** @todo check status code */ } break; case SMC_CMD_GET_KEY_BY_INDEX: if ( pThis->u.s.bState == 0x05 && pThis->offValue < sizeof(pThis->CurKey.Key)) { *pbValue = pThis->CurKey.Key.ab[pThis->offValue]; if (++pThis->offValue >= sizeof(pThis->CurKey.Key)) pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SUCCESS; } else { Log(("smcRegData_r: Reading too much or at wrong time during GET_KEY_BY_INDEX! bState=%#x offValue=%#x\n", pThis->u.s.bState, pThis->offValue)); pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SPURIOUS_DATA; /** @todo check status code */ } break; case SMC_CMD_PUT_KEY: Log(("smcRegData_r: Attempting to read data during PUT_KEY!\n")); *pbValue = 0xff; pThis->u.s.bState = 0; pThis->u.s.bStatusCode = SMC_STATUS_CD_SPURIOUS_DATA; break; default: Log(("smcRegData_r: Unknown command attempts reading data\n")); *pbValue = 0xff; pThis->u.s.bState = 0; pThis->u.s.bStatusCode = SMC_STATUS_CD_SPURIOUS_DATA; break; } return VINF_SUCCESS; } /** * Data register write. * * @returns VINF_SUCCESS or VINF_IOM_R3_IOPORT_WRITE. * @param uReg The register number. * @param bValue The value being written. */ static int smcRegData_w(PDEVSMC pThis, uint8_t uReg, uint8_t bValue) { RT_NOREF1(uReg); switch (pThis->bCmd) { /* * Get or put key value. * * 5 bytes written, first 4 is the key the 5th is the value size. In * the case of a put the value bytes are then written, while a get will * read the value bytes. */ case SMC_CMD_GET_KEY_VALUE: case SMC_CMD_PUT_KEY: if (pThis->offKey < 4) { /* Key byte. */ pThis->CurKey.Key.ab[pThis->offKey++] = bValue; pThis->u.s.bState = 0x04; pThis->u.s.bStatusCode = SMC_STATUS_CD_SUCCESS; } else if (pThis->offKey == 4) { /* Data length. */ pThis->u.s.bState = 0; if (bValue <= sizeof(pThis->CurKey.Value)) { pThis->CurKey.cbValue = bValue; pThis->offKey = 5; Assert(pThis->offValue == 0); if (pThis->bCmd == SMC_CMD_GET_KEY_VALUE) pThis->u.s.bStatusCode = smcKeyGetByName(pThis); else pThis->u.s.bStatusCode = smcKeyPutPrepare(pThis); if (pThis->u.s.bStatusCode == SMC_STATUS_CD_SUCCESS) pThis->u.s.bState = 0x05; } else { Log(("smcRegData_w: Guest attempts to get/put too many value bytes: %#x (max %#x)!\n", bValue, sizeof(pThis->CurKey.Value))); pThis->u.s.bStatusCode = SMC_STATUS_CD_KEY_SIZE_MISMATCH; /** @todo check this case! */ } } else if ( pThis->bCmd == SMC_CMD_PUT_KEY && pThis->offValue < pThis->CurKey.cbValue) { /* More value bytes for put key action. */ pThis->CurKey.Value.ab[pThis->offValue++] = bValue; if (pThis->offValue != pThis->CurKey.cbValue) pThis->u.s.bState = 0x05; else { pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = smcKeyPutValue(pThis); } } else { Log(("smcRegData_w: Writing too much data on %s command!\n", pThis->bCmd == SMC_CMD_PUT_KEY ? "put" : "get")); pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SPURIOUS_DATA; } break; /* * Get key info and key by index seems to take action after the last * key char is written. They then both go into a data reading phase. */ case SMC_CMD_GET_KEY_INFO: case SMC_CMD_GET_KEY_BY_INDEX: if (pThis->offKey < 4) { pThis->CurKey.Key.ab[pThis->offKey] = bValue; if (++pThis->offKey == 4) { if (pThis->bCmd == SMC_CMD_GET_KEY_BY_INDEX) pThis->u.s.bStatusCode = smcKeyGetByIndex(pThis); else pThis->u.s.bStatusCode = smcKeyGetAttrByName(pThis); pThis->u.s.bState = pThis->u.s.bStatusCode == SMC_STATUS_CD_SUCCESS ? 0x05 : 0x00; } else { pThis->u.s.bState = 0x04; pThis->u.s.bStatusCode = SMC_STATUS_CD_SUCCESS; } } else { Log(("smcRegData_w: Writing data beyond 5th byte on get %s command!\n", pThis->bCmd == SMC_CMD_GET_KEY_INFO ? "info" : "by index")); pThis->u.s.bState = 0x00; pThis->u.s.bStatusCode = SMC_STATUS_CD_SPURIOUS_DATA; } break; default: Log(("smcRegData_w: Unknown command %#x!\n", bValue)); pThis->u.s.bState = 0x00; /** @todo Check statuses with real HW. */ pThis->u.s.bStatusCode = SMC_STATUS_CD_BAD_COMMAND; break; } return VINF_SUCCESS; } /** * Command register write. * * @returns VINF_SUCCESS or VINF_IOM_R3_IOPORT_WRITE. * @param uReg The register number. * @param bValue The value being written. */ static int smcRegCmd_w(PDEVSMC pThis, uint8_t uReg, uint8_t bValue) { LogFlow(("smcRegCmd_w: New command: %#x (old=%#x)\n", bValue, pThis->bCmd)); NOREF(uReg); pThis->bCmd = bValue; /* Validate the command. */ switch (bValue) { case SMC_CMD_GET_KEY_VALUE: case SMC_CMD_PUT_KEY: case SMC_CMD_GET_KEY_BY_INDEX: case SMC_CMD_GET_KEY_INFO: pThis->u.s.bState = 0x0c; pThis->u.s.bStatusCode = SMC_STATUS_CD_SUCCESS; break; default: Log(("SMC: Unknown command %#x!\n", bValue)); pThis->u.s.bState = 0x00; /** @todo Check state with real HW. */ pThis->u.s.bStatusCode = SMC_STATUS_CD_BAD_COMMAND; break; } /* Reset the value/key related state. */ pThis->offKey = 0; pThis->offValue = 0; pThis->CurKey.Key.u32 = 0; pThis->CurKey.cbValue = 0; return VINF_SUCCESS; } /** * Generic register write. * * @returns VINF_SUCCESS or VINF_IOM_R3_IOPORT_WRITE. * @param uReg The register number. * @param bValue The value being written. */ static int smcRegGen_w(PDEVSMC pThis, uint8_t uReg, uint8_t bValue) { Log(("smcRegGen_w: %#04x: %#x -> %#x (write)\n", uReg, pThis->u.abRegsRW[uReg], bValue)); pThis->u.abRegsRW[uReg] = bValue; return VINF_SUCCESS; } /** * Read from register that isn't writable and reads as 0xFF. * * @returns VINF_SUCCESS or VINF_IOM_R3_IOPORT_WRITE. * @param uReg The register number. * @param pbValue Where to return the value. */ static int smcRegGen_r(PDEVSMC pThis, uint8_t uReg, uint8_t *pbValue) { Log(("smcRegGen_r: %#04x: %#x (read)\n", uReg, pThis->u.abRegsRW[uReg])); *pbValue = pThis->u.abRegsRW[uReg]; return VINF_SUCCESS; } /** * Write to register that isn't writable and reads as 0xFF. * * @returns VINF_SUCCESS or VINF_IOM_R3_IOPORT_WRITE. * @param uReg The register number. * @param bValue The value being written. */ static int smcRegFF_w(PDEVSMC pThis, uint8_t uReg, uint8_t bValue) { RT_NOREF3(pThis, uReg, bValue); Log(("SMC: %#04x: Writing %#x to unknown register!\n", uReg, bValue)); return VINF_SUCCESS; } /** * Read from register that isn't writable and reads as 0xFF. * * @returns VINF_SUCCESS or VINF_IOM_R3_IOPORT_WRITE. * @param uReg The register number. * @param pbValue Where to return the value. */ static int smcRegFF_r(PDEVSMC pThis, uint8_t uReg, uint8_t *pbValue) { RT_NOREF2(pThis, uReg); Log(("SMC: %#04x: Reading from unknown register!\n", uReg)); *pbValue = 0xff; return VINF_SUCCESS; } /** * SMC register handlers (indexed by relative I/O port). * * The device seems to be all byte registers and will split wider * accesses between registers like if it was MMIO. To better illustrate it * here is the output of the code in devR0SmcInitOnce on a MacPro3,1: * @verbatim * SMC: 0x0300=0xffffff63 w={0xff63, 0xffff}, b={0x63 0xff 0xff 0xff} * SMC: 0x0301=0x0cffffff w={0xffff, 0x0cff}, b={0xff 0xff 0xff 0x0c} * SMC: 0x0302=0xff0cffff w={0xffff, 0xff0c}, b={0xff 0xff 0x0c 0xff} * SMC: 0x0303=0xffff0cff w={0x0cff, 0xffff}, b={0xff 0x0c 0xff 0xff} * SMC: 0x0304=0xffffff0c w={0xff0c, 0xffff}, b={0x0c 0xff 0xff 0xff} * SMC: 0x0305=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x0306=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x0307=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x0308=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x0309=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x030a=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x030b=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x030c=0xffffffff w={0xffff, 0xffff}, b={0xff 0xff 0xff 0xff} * SMC: 0x030d=0x00ffffff w={0xffff, 0x00ff}, b={0xff 0xff 0xff 0x00} * SMC: 0x030e=0x0000ffff w={0xffff, 0x0000}, b={0xff 0xff 0x00 0x00} * SMC: 0x030f=0x000000ff w={0x00ff, 0x0000}, b={0xff 0x00 0x00 0x00} * SMC: 0x0310=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0311=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0312=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0313=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0314=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0315=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0316=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0317=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0318=0x00000000 w={0x0000, 0x0000}, b={0x00 0x00 0x00 0x00} * SMC: 0x0319=0xbe000000 w={0x0000, 0xbe00}, b={0x00 0x00 0x00 0xbe} * SMC: 0x031a=0xbabe0000 w={0x0000, 0xbabe}, b={0x00 0x00 0xbe 0xba} * SMC: 0x031b=0x00babe00 w={0xbe00, 0x00ba}, b={0x00 0xbe 0xba 0x00} * SMC: 0x031c=0xbe00babe w={0xbabe, 0xbe00}, b={0xbe 0xba 0x00 0xbe} * SMC: 0x031d=0xffbe00ba w={0x00ba, 0xffbe}, b={0xba 0x00 0xbe 0xff} * SMC: 0x031e=0xffffbe00 w={0xbe00, 0xffff}, b={0x00 0xbe 0xff 0xff} * SMC: 0x031f=0xffffffbe w={0xffbe, 0xffff}, b={0xbe 0xff 0xff 0xff} * @endverbatim * * The last dword is writable (0xbeXXbabe) where in the register at 0x1e is some * kind of status register for qualifying search failures and the like and will * be cleared under certain conditions. The whole dword can be written and read * back unchanged, according to my experiments. The 0x00 and 0x04 registers * does not read back what is written. * * My guess is that the 0xff values indicates ports that are not writable and * hardwired to 0xff, while the other values indicates ports that can be written * to and normally read back as written. I'm not going to push my luck too far * wrt to exact behavior until I see the guest using the registers. */ static const struct { int (*pfnWrite)(PDEVSMC pThis, uint8_t uReg, uint8_t bValue); int (*pfnRead)(PDEVSMC pThis, uint8_t uReg, uint8_t *pbValue); } g_aSmcRegs[SMC_REG_COUNT] = { /* [0x00] = */ { smcRegData_w, smcRegData_r }, /* [0x01] = */ { smcRegFF_w, smcRegFF_r }, /* [0x02] = */ { smcRegFF_w, smcRegFF_r }, /* [0x03] = */ { smcRegFF_w, smcRegFF_r }, /* [0x04] = */ { smcRegCmd_w, smcRegGen_r }, /* [0x05] = */ { smcRegFF_w, smcRegFF_r }, /* [0x06] = */ { smcRegFF_w, smcRegFF_r }, /* [0x07] = */ { smcRegFF_w, smcRegFF_r }, /* [0x08] = */ { smcRegFF_w, smcRegFF_r }, /* [0x09] = */ { smcRegFF_w, smcRegFF_r }, /* [0x0a] = */ { smcRegFF_w, smcRegFF_r }, /* [0x0b] = */ { smcRegFF_w, smcRegFF_r }, /* [0x0c] = */ { smcRegFF_w, smcRegFF_r }, /* [0x0d] = */ { smcRegFF_w, smcRegFF_r }, /* [0x0e] = */ { smcRegFF_w, smcRegFF_r }, /* [0x0f] = */ { smcRegFF_w, smcRegFF_r }, /* [0x10] = */ { smcRegGen_w, smcRegGen_r }, /* [0x11] = */ { smcRegGen_w, smcRegGen_r }, /* [0x12] = */ { smcRegGen_w, smcRegGen_r }, /* [0x13] = */ { smcRegGen_w, smcRegGen_r }, /* [0x14] = */ { smcRegGen_w, smcRegGen_r }, /* [0x15] = */ { smcRegGen_w, smcRegGen_r }, /* [0x16] = */ { smcRegGen_w, smcRegGen_r }, /* [0x17] = */ { smcRegGen_w, smcRegGen_r }, /* [0x18] = */ { smcRegGen_w, smcRegGen_r }, /* [0x19] = */ { smcRegGen_w, smcRegGen_r }, /* [0x1a] = */ { smcRegGen_w, smcRegGen_r }, /* [0x1b] = */ { smcRegGen_w, smcRegGen_r }, /* [0x1c] = */ { smcRegGen_w, smcRegGen_r }, /* [0x1d] = */ { smcRegGen_w, smcRegGen_r }, /* [0x1e] = */ { smcRegGen_w, smcRegGen_r }, /* [0x1f] = */ { smcRegGen_w, smcRegGen_r }, }; /** @callback_method_impl{FNIOMIOPORTOUT} */ PDMBOTHCBDECL(int) smcIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb) { RT_NOREF1(pvUser); #ifndef IN_RING3 if (cb > 1) return VINF_IOM_R3_IOPORT_WRITE; #endif /* * The first register, usually only one is accessed. */ PDEVSMC pThis = PDMINS_2_DATA(pDevIns, PDEVSMC); uint32_t uReg = Port - SMC_PORT_FIRST; int rc = g_aSmcRegs[uReg].pfnWrite(pThis, uReg, u32); /* * On the off chance that multiple registers are being read. */ if (cb > 1) { while (cb > 1 && uReg < SMC_REG_COUNT - 1) { cb--; uReg++; u32 >>= 8; int rc2 = g_aSmcRegs[uReg].pfnWrite(pThis, uReg, u32); if (rc2 != VINF_SUCCESS) { if ( rc == VINF_SUCCESS || (RT_FAILURE(rc2) && RT_SUCCESS(rc)) || (rc2 < rc && RT_SUCCESS(rc2) && RT_SUCCESS(rc))) rc = rc2; } } } LogFlow(("smcIoPortWrite: %#04x write access: %#x (LB %u) rc=%Rrc\n", uReg, u32, cb, rc)); return rc; } /** @callback_method_impl{FNIOMIOPORTIN} */ PDMBOTHCBDECL(int) smcIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t *pu32, unsigned cb) { RT_NOREF1(pvUser); #ifndef IN_RING3 if (cb > 1) return VINF_IOM_R3_IOPORT_READ; #endif PDEVSMC pThis = PDMINS_2_DATA(pDevIns, PDEVSMC); /* * The first register, usually only one is accessed. */ uint32_t uReg = Port - SMC_PORT_FIRST; Log2(("smcIoPortRead: %#04x read access: LB %u\n", uReg, cb)); uint8_t bValue = 0xff; int rc = g_aSmcRegs[uReg].pfnRead(pThis, uReg, &bValue); *pu32 = bValue; /* * On the off chance that multiple registers are being read. */ if (cb > 1) { do { cb--; uReg++; bValue = 0xff; if (uReg < SMC_REG_COUNT) { int rc2 = g_aSmcRegs[uReg].pfnRead(pThis, uReg, &bValue); if (rc2 != VINF_SUCCESS) { if ( rc == VINF_SUCCESS || (RT_FAILURE(rc2) && RT_SUCCESS(rc)) || (rc2 < rc && RT_SUCCESS(rc2) && RT_SUCCESS(rc))) rc = rc2; } } *pu32 |= (uint32_t)bValue << ((4 - cb) * 8); } while (cb > 1); } LogFlow(("smcIoPortRead: %#04x read access: %#x (LB %u) rc=%Rrc\n", uReg, *pu32, cb, rc)); return rc; } #endif /* IN_RING3 for now */ #ifdef IN_RING3 /** @callback_method_impl{FNSSMDEVSAVEEXEC} */ static DECLCALLBACK(int) smcSaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) { PDEVSMC pThis = PDMINS_2_DATA(pDevIns, PDEVSMC); RT_NOREF2(pSSM, pThis); /** @todo */ return VINF_SUCCESS; } /** @callback_method_impl{FNSSMDEVLOADEXEC} */ static DECLCALLBACK(int) smcLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) { PDEVSMC pThis = PDMINS_2_DATA(pDevIns, PDEVSMC); Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); RT_NOREF2(pSSM, pThis); /* Fend off unsupported versions. */ if ( uVersion != SMC_SAVED_STATE_VERSION #if SMC_SAVED_STATE_VERSION != SMC_SAVED_STATE_VERSION_BAKA && uVersion != SMC_SAVED_STATE_VERSION_BAKA #endif && uVersion != SMC_SAVED_STATE_VERSION_BAKA + 1) return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; /* * Do the actual restoring. */ if (uVersion == SMC_SAVED_STATE_VERSION) { /** @todo */ } return VINF_SUCCESS; } /** * @interface_method_impl{PDMDEVREG,pfnConstruct} */ static DECLCALLBACK(int) smcConstruct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) { RT_NOREF1(iInstance); PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); PDEVSMC pThis = PDMINS_2_DATA(pDevIns, PDEVSMC); Assert(iInstance == 0); /* * Init the data. */ pThis->bDollaryNumber = 1; pThis->bShutdownReason = 3; /* STOP_CAUSE_POWERKEY_GOOD_CODE */ /* * Validate configuration. */ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "DeviceKey|GetKeyFromRealSMC", ""); /* * Read configuration. */ /* The DeviceKey sets OSK0 and OSK1. */ int rc = CFGMR3QueryStringDef(pCfg, "DeviceKey", pThis->szOsk0And1, sizeof(pThis->szOsk0And1), ""); if (RT_FAILURE(rc)) return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("Configuration error: Querying \"DeviceKey\" as a string failed")); /* Query the key from the OS / real hardware if asked to do so. */ bool fGetKeyFromRealSMC; rc = CFGMR3QueryBoolDef(pCfg, "GetKeyFromRealSMC", &fGetKeyFromRealSMC, false); if (RT_FAILURE(rc)) return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("Configuration error: Querying \"GetKeyFromRealSMC\" as a boolean failed")); if (fGetKeyFromRealSMC) { #ifdef RT_OS_DARWIN rc = getSmcKeyOs(pThis->szOsk0And1, sizeof(pThis->szOsk0And1)); if (RT_FAILURE(rc)) { LogRel(("SMC: Retrieving the SMC key from the OS failed (%Rrc), trying to read it from hardware\n", rc)); #endif rc = PDMDevHlpCallR0(pDevIns, SMC_CALLR0_READ_OSK, 0 /*u64Arg*/); if (RT_SUCCESS(rc)) LogRel(("SMC: Successfully retrieved the SMC key from hardware\n")); else LogRel(("SMC: Retrieving the SMC key from hardware failed(%Rrc)\n", rc)); #ifdef RT_OS_DARWIN } else LogRel(("SMC: Successfully retrieved the SMC key from the OS\n")); #endif if (RT_FAILURE(rc)) return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("Failed to query SMC value from the host")); } /* * Register I/O Ports */ rc = PDMDevHlpIOPortRegister(pDevIns, SMC_PORT_FIRST, SMC_REG_COUNT, NULL, smcIoPortWrite, smcIoPortRead, NULL, NULL, "SMC data port"); AssertRCReturn(rc, rc); /** @todo Newer versions (2.03) have an MMIO mapping as well (ACPI). */ /* * Saved state. */ rc = PDMDevHlpSSMRegister(pDevIns, SMC_SAVED_STATE_VERSION, sizeof(*pThis), smcSaveExec, smcLoadExec); if (RT_FAILURE(rc)) return rc; return VINF_SUCCESS; } /** * The device registration structure. */ const PDMDEVREG g_DeviceSmc = { /* u32Version */ PDM_DEVREG_VERSION, /* szName */ "smc", /* szRCMod */ "VBoxDDRC.rc", /* szR0Mod */ "VBoxDDR0.r0", /* pszDescription */ "Apple System Management Controller", /* fFlags */ PDM_DEVREG_FLAGS_HOST_BITS_DEFAULT | PDM_DEVREG_FLAGS_GUEST_BITS_DEFAULT | PDM_DEVREG_FLAGS_R0 | PDM_DEVREG_FLAGS_RC, /* fClass */ PDM_DEVREG_CLASS_ARCH, /* cMaxInstances */ 1, /* cbInstance */ sizeof(DEVSMC), /* pfnConstruct */ smcConstruct, /* pfnDestruct */ NULL, /* pfnRelocate */ NULL, /* pfnMemSetup */ NULL, /* pfnPowerOn */ NULL, /* pfnReset */ NULL, /* pfnSuspend */ NULL, /* pfnResume */ NULL, /* pfnAttach */ NULL, /* pfnDetach */ NULL, /* pfnQueryInterface. */ NULL, /* pfnInitComplete. */ NULL, /* pfnPowerOff */ NULL, /* pfnSoftReset */ NULL, /* u32VersionEnd */ PDM_DEVREG_VERSION }; #endif /* IN_RING3 */ #endif /* VBOX_DEVICE_STRUCT_TESTCASE */