DevHPET.cpp revision 3614117c1132a61599e6190939e775cafe549411
/* $Id$ */
/** @file
* HPET virtual device - high precision event timer emulation
*/
/*
* Copyright (C) 2009-2010 Sun Microsystems, Inc.
*
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DEV_HPET
#include "../Builtins.h"
/*
* Current limitations:
* - not entirely correct time of interrupt, i.e. never
* schedule interrupt earlier than in 1ms
* - statistics 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
/*
* 10000000 femtoseconds == 10ns
*/
#define HPET_CLK_PERIOD 10000000UL
/*
* 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_ENABLE 0x004
#define HPET_TN_PERIODIC 0x008
#define HPET_TN_PERIODIC_CAP 0x010
#define HPET_TN_SIZE_CAP 0x020
#define HPET_TN_SETVAL 0x040
#define HPET_TN_32BIT 0x100
#define HPET_TN_INT_ROUTE_MASK 0x3e00
#define HPET_TN_CFG_WRITE_MASK 0x3f4e
#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
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 number*/
/* Wrap */
/* Alignment */
/* Memory-mapped, software visible timer registers */
/* Configuration/capabilities */
/* comparator */
/* FSB route, not supported now */
/* 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 system clock */
/* Memory-mapped, software visible registers */
/* capabilities */
/* configuration */
/* interrupt status register */
/* main counter */
/* Global device lock */
} HpetState;
#ifndef VBOX_DEVICE_STRUCT_TESTCASE
/*
* We shall declare MMIO accessors as extern "C" to avoid name mangling
* Maybe PDMBOTHCBDECL macro shall have extern "C" part in it.
*/
PDMBOTHCBDECL(int) hpetMMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
PDMBOTHCBDECL(int) hpetMMIORead (PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb);
/*
* Temporary control to disble locking if problems found
*/
static const bool fHpetLocking = true;
{
if (!fHpetLocking)
return VINF_SUCCESS;
}
{
if (!fHpetLocking)
return;
}
{
}
{
}
{
}
{
}
{
/*
* We can use any timer to get current time, they all go
* with the same speed.
*/
}
{
u64NewValue &= u64Mask;
return u64NewValue;
}
{
}
{
}
{
{
} else {
return u64Diff;
}
}
{
{
/* While loop is suboptimal */
{
}
else
{
}
}
}
{
/* no wrapping on new timers */
/* Spec says in one-shot 32-bit mode, generate an interrupt when
* counter wraps in addition to an interrupt with comparator match.
*/
{
{
}
}
/* Avoid killing VM with interrupts */
#if 1
/* @todo: HACK, rethink, may have negative impact on the guest */
if (u64Diff == 0)
#endif
}
{
/*
* 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
* As primary usecase for HPET is APIC config, we pretend
* being always APIC, although for safety we shall check currect IC.
* @todo: implement private interface between HPET and PDM
* PIT and RTC
*/
/*
* nike: Linux refuses to boot with HPET, claiming that 8259 timer not
* connected to IO-APIC, if we use IRQ2, so let's use IRQ0 for now.
*/
else
}
{
if (iTimerNo >= HPET_NUM_TIMERS)
{
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:
break;
case HPET_TN_ROUTE:
break;
default:
break;
}
return VINF_SUCCESS;
}
{
switch (iIndex)
{
case HPET_ID:
Log(("read HPET_ID\n"));
break;
case HPET_PERIOD:
Log(("read HPET_PERIOD\n"));
break;
case HPET_CFG:
Log(("read HPET_CFG\n"));
break;
case HPET_CFG + 4:
Log(("read of HPET_CFG + 4\n"));
break;
case HPET_COUNTER:
case HPET_COUNTER + 4:
{
Log(("read HPET_COUNTER\n"));
else
/** @todo: is it correct? */
break;
}
case HPET_STATUS:
Log(("read HPET_STATUS\n"));
break;
default:
break;
}
return VINF_SUCCESS;
}
{
int rc;
if (iTimerNo >= HPET_NUM_TIMERS)
{
return VINF_SUCCESS;
}
if (RT_FAILURE(rc))
return rc;
switch (iTimerReg)
{
case HPET_TN_CFG:
{
/** We only care about lower 32-bits so far */
if (iNewValue & HPET_TN_32BIT)
{
}
if (iNewValue & HPET_TIMER_TYPE_LEVEL)
{
LogRel(("level-triggered config not yet supported\n"));
Assert(false);
}
break;
}
{
Log(("write HPET_TN_CFG + 4, useless\n"));
break;
}
case HPET_TN_CMP: /* lower bits of comparator register */
{
{
/* HPET_TN_SETVAL allows to adjust comparator w/o updating period, and it's cleared on access */
{
| iNewValue;
}
| iNewValue;
break;
}
{
break;
{
/* HPET_TN_SETVAL allows to adjust comparator w/o updating period, and it's cleared on access */
{
}
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:
{
Assert(false);
break;
}
}
return VINF_SUCCESS;
}
bool fActivate)
{
int rc = VINF_SUCCESS;
#ifndef IN_RING3
/* Don't do anything complicated outside of R3 */
#else /* IN_RING3 */
if (pThis->pHpetHlpR3)
#endif
return rc;
}
{
int rc = VINF_SUCCESS;
switch (iIndex)
{
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.
*/
{
}
{
}
if (rc != VINF_SUCCESS)
return rc;
{
/* Enable main counter and interrupt generation. */
for (i = 0; i < HPET_NUM_TIMERS; i++)
}
{
/* Halt main counter and disable interrupt generation. */
for (i = 0; i < HPET_NUM_TIMERS; i++)
}
break;
}
case HPET_CFG + 4:
{
0xffffffff00000000ULL);
break;
}
case HPET_STATUS:
{
// clear ISR for all set bits in iNewValue, see p. 14 of HPET spec
break;
}
case HPET_STATUS + 4:
{
if (iNewValue != 0)
LogRel(("Writing HPET_STATUS + 4 with non-zero, ignored\n"));
break;
}
case HPET_COUNTER:
{
Log(("write HPET_COUNTER: %#x -> %llx\n",
break;
}
case HPET_COUNTER + 4:
{
Log(("write HPET_COUNTER + 4: %#x -> %llx\n",
break;
}
default:
break;
}
return rc;
}
void * pvUser,
void * pv,
unsigned cb)
{
int rc = VINF_SUCCESS;
return rc;
switch (cb)
{
case 1:
case 2:
break;
case 4:
{
else
break;
}
case 8:
{
union {
} value;
/* Unaligned accesses not allowed */
if (iIndex % 8 != 0)
{
AssertMsgFailed(("Unaligned HPET read access\n"));
break;
}
// for 8-byte accesses we just split them, happens under lock anyway
{
break;
}
else
{
break;
}
if (rc == VINF_SUCCESS)
break;
}
default:
}
return rc;
}
void * pvUser,
void * pv,
unsigned cb)
{
int rc = VINF_SUCCESS;
LogFlow(("hpetMMIOWrite: %llx (%x) <- %x\n",
return rc;
switch (cb)
{
case 1:
case 2:
break;
case 4:
{
else
break;
}
case 8:
{
union {
} value;
/* Unaligned accesses not allowed */
if (iIndex % 8 != 0)
{
AssertMsgFailed(("Unaligned HPET write access\n"));
break;
}
// for 8-byte accesses we just split them, happens under lock anyway
{
break;
}
else
{
break;
}
break;
}
default:
}
return rc;
}
#ifdef IN_RING3
{
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
/**
* @copydoc FNSSMDEVLIVEEXEC
*/
{
return VINF_SSM_DONT_CALL_AGAIN;
}
/**
* Saves a state of the HPET device.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pSSMHandle The handle to save the state to.
*/
{
int rc;
/* The config. */
{
}
return VINF_SUCCESS;
}
/**
* Loads a HPET device state.
*
* @returns VBox status code.
* @param pDevIns The device instance.
* @param pSSMHandle The handle to the saved state.
* @param uVersion The data unit version number.
* @param uPass The data pass.
*/
{
int rc;
return VINF_SUCCESS;
if (uVersion != HPET_SAVED_STATE_VERSION)
if (u8NumTimers != HPET_NUM_TIMERS)
return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Config mismatch - wrong number of timers: saved=%#x config=%#x"), u8NumTimers, HPET_NUM_TIMERS);
if (uPass != SSM_PASS_FINAL)
return VINF_SUCCESS;
{
}
return VINF_SUCCESS;
}
{
/** @todo: is it correct? */
{
{
}
}
}
/**
* 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.
*/
void * pvUser)
{
int rc;
return;
/* Lock in R3 must either block or succeed */
{
}
{
{
}
}
/* Should it really be under lock, does it really matter? */
}
/**
* Relocation notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
* @param offDelta The delta relative to the old address.
*/
{
unsigned i;
LogFlow(("hpetRelocate:\n"));
{
}
}
/**
* Reset notification.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
{
unsigned i;
LogFlow(("hpetReset:\n"));
for (i = 0; i < HPET_NUM_TIMERS; i++)
{
pTimer->u8TimerNumber = i;
/* capable of periodic operations and 64-bits */
/* We can do all IRQs */
}
/* 64-bit main counter; 3 timers supported; LegacyReplacementRoute. */
(1 << 15) /* LEG_RT_CAP, LegacyReplacementRoute capable */ |
(1 << 13) /* COUNTER_SIZE_CAP, main counter is 64-bit capable */ |
1 /* REV_ID, revision, must not be 0 */;
}
/**
* Initialization routine.
*
* @returns VBox status.
* @param pDevIns The device instance data.
*/
{
unsigned i;
int rc;
for (i = 0; i < HPET_NUM_TIMERS; i++)
{
TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "HPET Timer",
if (RT_FAILURE(rc))
return rc;
}
return VINF_SUCCESS;
}
/**
* Info handler, device version.
*
* @param pDevIns Device instance which registered the info.
* @param pHlp Callback functions for doing output.
* @param pszArgs Argument string. Optional and specific to the handler.
*/
{
int i;
"HPET status:\n"
" config = %016RX64\n"
" offset = %016RX64 counter = %016RX64 isr = %016RX64\n"
" legacy mode is %s\n",
"Timers:\n");
for (i = 0; i < HPET_NUM_TIMERS; i++)
{
}
}
/**
* @interface_method_impl{PDMDEVREG,pfnConstruct}
*/
{
int rc;
bool fRCEnabled = false;
bool fR0Enabled = false;
/* Only one HPET device now */
/*
* Validate configuration.
*/
/* Query configuration. */
#if 1
if (RT_FAILURE(rc))
N_("Configuration error: Querying \"GCEnabled\" as a bool failed"));
if (RT_FAILURE(rc))
N_("Configuration error: failed to read R0Enabled as boolean"));
#endif
/* Initialize the device state */
if (RT_FAILURE(rc))
return rc;
/*
* Register the HPET and get helpers.
*/
if (RT_FAILURE(rc))
{
return rc;
}
/*
* Initialize critical section.
*/
if (RT_FAILURE(rc))
/*
* Register the MMIO range, PDM API requests page aligned
* addresses and sizes.
*/
if (RT_FAILURE(rc))
{
return rc;
}
if (fRCEnabled)
{
if (RT_FAILURE(rc))
return rc;
if (!pThis->pHpetHlpRC)
{
AssertReleaseMsgFailed(("cannot get RC helper\n"));
return VERR_INTERNAL_ERROR;
}
}
if (fR0Enabled)
{
if (RT_FAILURE(rc))
return rc;
if (!pThis->pHpetHlpR0)
{
AssertReleaseMsgFailed(("cannot get R0 helper\n"));
return VERR_INTERNAL_ERROR;
}
}
/* Register SSM callbacks */
rc = PDMDevHlpSSMRegister3(pDevIns, HPET_SAVED_STATE_VERSION, sizeof(*pThis), hpetLiveExec, hpetSaveExec, hpetLoadExec);
if (RT_FAILURE(rc))
return rc;
/**
* @todo Register statistics.
*/
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 */