DevHPET.cpp revision 8a54ed337392872c7cfcfb96f173468bbbb0f7fc
/* $Id$ */
/** @file
* HPET virtual device - high precision event timer emulation
*/
/*
* Copyright (C) 2009-2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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_HPET
#include <iprt/asm-math.h>
#include "VBoxDD.h"
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/*
* Current limitations:
* - not entirely correct time of interrupt, i.e. never
* schedule interrupt earlier than in 1ms
* - statistics not implemented
* - level-triggered mode not implemented
*/
/*
* Base address for MMIO
*/
#define HPET_BASE 0xfed00000
/*
* Number of available timers, cannot be changed without
* breaking saved states.
*/
#define HPET_NUM_TIMERS 3
#define HPET_NUM_TIMERS_ICH9 4
/*
* 10000000 femtoseconds == 10ns
*/
/*
* 69841279 femtoseconds == 69.84 ns (1 / 14.31818MHz)
*/
/*
* Femptosecods in nanosecond
*/
#define FS_PER_NS 1000000
/*
* Interrupt type
*/
#define HPET_TIMER_TYPE_LEVEL 1
#define HPET_TIMER_TYPE_EDGE 0
/* Delivery mode */
/* Via APIC */
#define HPET_TIMER_DELIVERY_APIC 0
/* Via FSB */
#define HPET_TIMER_DELIVERY_FSB 1
#define HPET_ID 0x000
#define HPET_PERIOD 0x004
#define HPET_CFG 0x010
#define HPET_STATUS 0x020
#define HPET_COUNTER 0x0f0
#define HPET_TN_CFG 0x000
#define HPET_TN_CMP 0x008
#define HPET_TN_ROUTE 0x010
#define HPET_CFG_WRITE_MASK 0x3
#define HPET_TN_INT_ROUTE_SHIFT 9
#define HPET_TN_INT_ROUTE_CAP_SHIFT 32
#define HPET_TN_CFG_BITS_READONLY_OR_RESERVED 0xffff80b1U
/** The version of the saved state. */
#define HPET_SAVED_STATE_VERSION 2
/** Empty saved state */
#define HPET_SAVED_STATE_VERSION_EMPTY 1
/**
* Acquires the HPET lock or returns.
*/
do { \
if (rcLock != VINF_SUCCESS) \
return rcLock; \
} while (0)
/**
* Releases the HPET lock.
*/
#define DEVHPET_UNLOCK(a_pThis) \
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
struct HpetState;
typedef struct HpetTimer
{
/** The HPET timer - R3 Ptr. */
/** Pointer to the instance data - R3 Ptr. */
/** The HPET timer - R0 Ptr. */
/** Pointer to the instance data - R0 Ptr. */
/** The HPET timer - RC Ptr. */
/** Pointer to the instance data - RC Ptr. */
/** Timer index. */
/** Wrap. */
/** Alignment. */
/** @name Memory-mapped, software visible timer registers.
* @{ */
/** Configuration/capabilities. */
/** Comparator. */
/** FSB route, not supported now. */
/** @} */
/** @name Hidden register state.
* @{ */
/** Last value written to comparator. */
/** @} */
} HpetTimer;
typedef struct HpetState
{
/** Pointer to the device instance. - R3 ptr. */
/** The HPET helpers - R3 Ptr. */
/** Pointer to the device instance. - R0 ptr. */
/** The HPET helpers - R0 Ptr. */
/** Pointer to the device instance. - RC ptr. */
/** The HPET helpers - RC Ptr. */
/** Timer structures. */
/** Offset realtive to the virtual sync clock. */
/** @name Memory-mapped, software visible registers
* @{ */
/** Capabilities. */
/** Configuration. */
/** Interrupt status register. */
/** Main counter. */
/** @} */
/** Global device lock. */
/** If we emulate ICH9 HPET (different frequency).
* @todo different number of timers */
bool fIch9;
} HpetState;
#ifndef VBOX_DEVICE_STRUCT_TESTCASE
{
}
{
}
{
}
{
}
{
}
{
}
{
/*
* We can use any timer to get current time, they all go
* with the same speed.
*/
return nsToHpetTicks(pThis,
+ pThis->u64HpetOffset);
}
{
u64NewValue &= u64Mask;
return u64NewValue;
}
{
return !(u64OldValue & u64Mask)
&& !!(u64NewValue & u64Mask);
}
{
return !!(u64OldValue & u64Mask)
&& !(u64NewValue & u64Mask);
}
{
if (hpet32bitTimer(pHpetTimer))
{
}
else
{
return u64Diff;
}
}
{
&& u64Period != 0)
{
/* While loop is suboptimal */
if (hpet32bitTimer(pHpetTimer))
{
}
else
{
}
}
}
{
/* no wrapping on new timers */
pHpetTimer->u8Wrap = 0;
/*
* HPET spec says in one-shot 32-bit mode, generate an interrupt when
* counter wraps in addition to an interrupt with comparator match.
*/
if ( hpet32bitTimer(pHpetTimer)
{
{
Log(("wrap on timer %d: till=%u ticks=%lld diff64=%lld\n",
}
}
/* Avoid killing VM with interrupts */
#if 1
/* @todo: HACK, rethink, may have negative impact on the guest */
if (u64Diff == 0)
#endif
Log4(("HPET: next IRQ in %lld ticks (%lld ns)\n", u64Diff, hpetTicksToNs(pHpetTimer->CTX_SUFF(pHpet), u64Diff)));
}
/* -=-=-=-=-=- Register accesses -=-=-=-=-=- */
/**
* Reads a HPET timer register.
*
* @returns VBox strict status code.
* @param pThis The HPET instance.
* @param iTimerNo The timer index.
* @param iTimerReg The index of the timer register to read.
* @param pu32Value Where to return the register value.
*
* @remarks ASSUMES the caller does holds the HPET lock.
*/
static int hpetTimerRegRead32(HpetState const *pThis, uint32_t iTimerNo, uint32_t iTimerReg, uint32_t *pu32Value)
{
if (iTimerNo >= HPET_NUM_TIMERS)
{
static unsigned s_cOccurences = 0;
if (s_cOccurences++ < 10)
*pu32Value = 0;
return VINF_SUCCESS;
}
switch (iTimerReg)
{
case HPET_TN_CFG:
break;
case HPET_TN_CFG + 4:
break;
case HPET_TN_CMP:
break;
case HPET_TN_CMP + 4:
Log(("read HPET_TN_CMP+4 on %d: %#x (%#llx)\n", pHpetTimer->idxTimer, u32Value, pHpetTimer->u64Cmp));
break;
case HPET_TN_ROUTE:
u32Value = (uint32_t)(pHpetTimer->u64Fsb >> 32); /** @todo Looks wrong, but since it's not supported, who cares. */
break;
default:
{
static unsigned s_cOccurences = 0;
if (s_cOccurences++ < 10)
u32Value = 0;
break;
}
}
return VINF_SUCCESS;
}
/**
* 32-bit write to a HPET timer register.
*
* @returns Strict VBox status code.
*
* @param pThis The HPET state.
* @param idxReg The register being written to.
* @param u32NewValue The value being written.
*/
static int hpetTimerRegWrite32(HpetState *pThis, uint32_t iTimerNo, uint32_t iTimerReg, uint32_t u32NewValue)
{
if (iTimerNo >= HPET_NUM_TIMERS)
{
return VINF_SUCCESS;
}
switch (iTimerReg)
{
case HPET_TN_CFG:
{
u64Mask |= HPET_TN_32BIT;
else
u32NewValue &= ~HPET_TN_32BIT;
if ((u32NewValue & HPET_TN_32BIT) != 0)
{
}
{
LogRel(("level-triggered config not yet supported\n"));
AssertFailed();
}
/* We only care about lower 32-bits so far */
break;
}
{
Log(("write HPET_TN_CFG + 4, useless\n"));
break;
}
case HPET_TN_CMP: /* lower bits of comparator register */
{
{
| u32NewValue;
}
| u32NewValue;
break;
}
{
if (hpet32bitTimer(pHpetTimer))
break;
Log2(("after HPET_TN_CMP+4 cmp=%llx per=%llx tmr=%d\n", pHpetTimer->u64Cmp, pHpetTimer->u64Period, iTimerNo));
break;
}
case HPET_TN_ROUTE:
{
Log(("write HPET_TN_ROUTE\n"));
break;
}
case HPET_TN_ROUTE + 4:
{
Log(("write HPET_TN_ROUTE + 4\n"));
break;
}
default:
{
AssertFailed();
break;
}
}
return VINF_SUCCESS;
}
/**
* Read a 32-bit HPET register.
*
* @returns Strict VBox status code.
* @param pThis The HPET state.
* @param idxReg The register to read.
* @param pu32Value Where to return the register value.
*/
{
switch (idxReg)
{
case HPET_ID:
break;
case HPET_PERIOD:
break;
case HPET_CFG:
break;
case HPET_CFG + 4:
break;
case HPET_COUNTER:
case HPET_COUNTER + 4:
{
else
/** @todo is it correct? */
Log(("read HPET_COUNTER: %s part value %x (%#llx)\n",
break;
}
case HPET_STATUS:
Log(("read HPET_STATUS\n"));
break;
default:
u32Value = 0;
break;
}
return VINF_SUCCESS;
}
/**
* 32-bit write to a config register.
*
* @returns Strict VBox status code.
*
* @param pThis The HPET state.
* @param idxReg The register being written to.
* @param u32NewValue The value being written.
*/
{
int rc = VINF_SUCCESS;
switch (idxReg)
{
case HPET_ID:
case HPET_ID + 4:
{
Log(("write HPET_ID, useless\n"));
break;
}
case HPET_CFG:
{
/*
* This check must be here, before actual update, as hpetLegacyMode
* may request retry in R3 - so we must keep state intact.
*/
{
#ifdef IN_RING3
if (rc != VINF_SUCCESS)
break;
#else
break;
#endif
}
{
/** @todo Only get the time stamp once when reprogramming? */
/* Enable main counter and interrupt generation. */
for (uint32_t i = 0; i < HPET_NUM_TIMERS; i++)
}
{
/* Halt main counter and disable interrupt generation. */
for (uint32_t i = 0; i < HPET_NUM_TIMERS; i++)
}
break;
}
case HPET_CFG + 4:
{
/** @todo why do we let the guest write to the high bits but not
* read them? */
UINT64_C(0xffffffff00000000));
break;
}
case HPET_STATUS:
{
/* Clear ISR for all set bits in u32NewValue, see p. 14 of the HPET spec. */
break;
}
case HPET_STATUS + 4:
{
if (u32NewValue != 0)
{
static unsigned s_cOccurrences = 0;
if (s_cOccurrences++ < 10)
LogRel(("Writing HPET_STATUS + 4 with non-zero, ignored\n"));
}
break;
}
case HPET_COUNTER:
{
break;
}
case HPET_COUNTER + 4:
{
break;
}
default:
break;
}
return rc;
}
/* -=-=-=-=-=- MMIO callbacks -=-=-=-=-=- */
/**
* @callback_method_impl{FNIOMMMIOREAD}
*/
PDMBOTHCBDECL(int) hpetMMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
{
int rc = VINF_SUCCESS;
switch (cb)
{
case 4:
else
break;
case 8:
{
/* Unaligned accesses not allowed */
{
break;
}
/* Split the access except for timing sensitive registers. The
others assume the protection of the lock. */
if (idxReg == HPET_COUNTER)
{
/* When reading HPET counter we must read it in a single read,
to avoid unexpected time jumps on 32-bit overflow. */
: pThis->u64HpetCounter;
rc = VINF_SUCCESS;
}
{
if (rc == VINF_SUCCESS)
}
else
{
/* for most 8-byte accesses we just split them, happens under lock anyway. */
if (rc == VINF_SUCCESS)
}
break;
}
case 1:
case 2:
rc = VINF_SUCCESS;
break;
default:
rc = VINF_SUCCESS;
}
return rc;
}
/**
* @callback_method_impl{FNIOMMMIOWRITE}
*/
PDMBOTHCBDECL(int) hpetMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
{
LogFlow(("hpetMMIOWrite: cb=%u reg=%03x (%RGp) val=%llx\n",
int rc;
switch (cb)
{
case 4:
else
break;
case 8:
{
/* Unaligned accesses are not allowed. */
{
break;
}
/* Split the access and rely on the locking to prevent trouble. */
{
/** @todo Consider handling iTimerReg == HPET_TN_CMP specially here */
}
else
{
}
break;
}
case 1:
case 2:
rc = VINF_SUCCESS;
break;
default:
break;
}
return rc;
}
#ifdef IN_RING3
/* -=-=-=-=-=- Timer Callback Processing -=-=-=-=-=- */
/**
* Gets the IRQ of an HPET timer.
*
* @returns IRQ number.
* @param pHpetTimer The HPET timer.
*/
{
/*
* Per spec, in legacy mode HPET timers wired as:
* timer 0: IRQ0 for PIC and IRQ2 for APIC
* timer 1: IRQ8 for both PIC and APIC
*
* ISA IRQ delivery logic will take care of correct delivery
* to the different ICs.
*/
}
/**
* Used by hpetTimerCb to update the IRQ status.
*
* @param pThis The HPET device state.
* @param pHpetTimer The HPET timer.
*/
{
/** @todo: is it correct? */
{
/* ISR bits are only set in level-triggered mode. */
level-triggered mode yet. */
else
AssertFailed();
/** @todo: implement IRQs in level-triggered mode */
}
}
/**
* Device timer callback function.
*
* @param pDevIns Device instance of the device which registered the timer.
* @param pTimer The timer handle.
* @param pvUser Pointer to the HPET timer state.
*/
{
/* Lock in R3 must either block or succeed */
{
}
else if ( hpet32bitTimer(pHpetTimer)
{
if (pHpetTimer->u8Wrap)
{
pHpetTimer->u8Wrap = 0;
}
}
/* Should it really be under lock, does it really matter? */
}
/* -=-=-=-=-=- DBGF Info Handlers -=-=-=-=-=- */
/**
* @callback_method_impl{FNDBGFHANDLERDEV}
*/
{
"HPET status:\n"
" config = %016RX64\n"
" offset = %016RX64 counter = %016RX64 isr = %016RX64\n"
" legacy mode is %s\n",
"Timers:\n");
for (unsigned i = 0; i < HPET_NUM_TIMERS; i++)
{
}
}
/* -=-=-=-=-=- Saved State -=-=-=-=-=- */
/**
* @callback_method_impl{FNSSMDEVLIVEEXEC}
*/
{
return VINF_SSM_DONT_CALL_AGAIN;
}
/**
* @callback_method_impl{FNSSMDEVSAVEEXEC}
*/
{
/*
* The config.
*/
/*
* The state.
*/
{
}
}
/**
* @callback_method_impl{FNSSMDEVLOADEXEC}
*/
static DECLCALLBACK(int) hpetLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
{
/*
* Version checks.
*/
return VINF_SUCCESS;
if (uVersion != HPET_SAVED_STATE_VERSION)
/*
* The config.
*/
if (cTimers != HPET_NUM_TIMERS)
return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch - wrong number of timers: saved=%#x config=%#x"),
if (uPass != SSM_PASS_FINAL)
return VINF_SUCCESS;
/*
* The state.
*/
{
}
}
/* -=-=-=-=-=- PDMDEVREG -=-=-=-=-=- */
/**
* @interface_method_impl{PDMDEVREG,pfnRelocate}
*/
{
LogFlow(("hpetRelocate:\n"));
{
}
}
/**
* @interface_method_impl{PDMDEVREG,pfnReset}
*/
{
LogFlow(("hpetReset:\n"));
pThis->u64HpetConfig = 0;
for (unsigned i = 0; i < HPET_NUM_TIMERS; i++)
{
/* capable of periodic operations and 64-bits */
pHpetTimer->u64Config = (i == 0)
: 0;
else
/* We can do all IRQs */
pHpetTimer->u64Period = 0;
pHpetTimer->u8Wrap = 0;
/** @todo shouldn't we stop any active timers at this point? */
}
pThis->u64HpetCounter = 0;
pThis->u64HpetOffset = 0;
/* 64-bit main counter; 3 timers supported; LegacyReplacementRoute. */
| (1 << 13) /* COUNTER_SIZE_CAP - Main counter is 64-bit capable. */
Actually ICH9 has 4 timers, but to avoid breaking
saved state we'll stick with 3 so far. */ /** @todo fix this ICH9 timer count bug. */
| 1 /* REV_ID - Revision, must not be 0 */
;
pThis->u64Capabilities |= ((uint64_t)(pThis->fIch9 ? HPET_CLK_PERIOD_ICH9 : HPET_CLK_PERIOD) << 32);
if (pThis->pHpetHlpR3)
}
/**
* @interface_method_impl{PDMDEVREG,pfnConstruct}
*/
{
/* Only one HPET device now, as we use fixed MMIO region. */
/*
* Validate and read the configuration.
*/
bool fRCEnabled;
if (RT_FAILURE(rc))
N_("Configuration error: Querying \"GCEnabled\" as a bool failed"));
bool fR0Enabled;
if (RT_FAILURE(rc))
N_("Configuration error: failed to read R0Enabled as boolean"));
if (RT_FAILURE(rc))
N_("Configuration error: failed to read ICH9 as boolean"));
/*
* Initialize the device state
*/
/* Init the HPET timers. */
for (unsigned i = 0; i < HPET_NUM_TIMERS; i++)
{
pHpetTimer->idxTimer = i;
TMTIMER_FLAGS_NO_CRIT_SECT, "HPET Timer",
/// @todo TMR3TimerSetCritSect(pThis->aTimers[i].pTimerR3, &pThis->csLock);
}
/* This must be done prior to registering the HPET, right? */
/*
* Register the HPET and get helpers.
*/
/*
* Register the MMIO range, PDM API requests page aligned
* addresses and sizes.
*/
if (fRCEnabled)
{
}
if (fR0Enabled)
{
}
/* Register SSM callbacks */
rc = PDMDevHlpSSMRegister3(pDevIns, HPET_SAVED_STATE_VERSION, sizeof(*pThis), hpetLiveExec, hpetSaveExec, hpetLoadExec);
/* Register an info callback. */
return VINF_SUCCESS;
}
/**
* The device registration structure.
*/
const PDMDEVREG g_DeviceHPET =
{
/* u32Version */
/* szName */
"hpet",
/* szRCMod */
"VBoxDDGC.gc",
/* szR0Mod */
"VBoxDDR0.r0",
/* pszDescription */
" High Precision Event Timer (HPET) Device",
/* fFlags */
PDM_DEVREG_FLAGS_HOST_BITS_DEFAULT | PDM_DEVREG_FLAGS_GUEST_BITS_32_64 | PDM_DEVREG_FLAGS_PAE36 | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
/* fClass */
/* cMaxInstances */
1,
/* cbInstance */
sizeof(HpetState),
/* pfnConstruct */
/* pfnDestruct */
NULL,
/* pfnRelocate */
/* pfnIOCtl */
NULL,
/* pfnPowerOn */
NULL,
/* pfnReset */
/* pfnSuspend */
NULL,
/* pfnResume */
NULL,
/* pfnAttach */
NULL,
/* pfnDetach */
NULL,
/* pfnQueryInterface. */
NULL,
/* pfnInitComplete */
NULL,
/* pfnPowerOff */
NULL,
/* pfnSoftReset */
NULL,
/* u32VersionEnd */
};
#endif /* IN_RING3 */
#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */