/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
#include <sys/hpet_acpi.h>
#include <sys/inttypes.h>
#include <sys/archsystm.h>
static boolean_t hpet_install_proxy(void);
static boolean_t hpet_resume(void);
int vector);
static void hpet_uninstall_interrupt_handler(void);
static void hpet_expire_all(void);
static void hpet_init_proxy_data(void);
/*
*/
static struct hpet_state {
/*
* hpet_proxy_users is a per-cpu array.
*/
/*
* Provide HPET access from unix.so.
* Set up pointers to access symbols in pcplusmp.
*/
static void
hpet_establish_hooks(void)
{
}
/*
* Get the ACPI "HPET" table.
* acpi_probe() calls this function from mp_startup before drivers are loaded.
* acpi_probe() verified the system is using ACPI before calling this.
*
* There may be more than one ACPI HPET table (Itanium only?).
* Intel's HPET spec defines each timer block to have up to 32 counters and
* be 1024 bytes long. There can be more than one timer block of 32 counters.
* Each timer block would have an additional ACPI HPET table.
* Typical x86 systems today only have 1 HPET with 3 counters.
* On x86 we only consume HPET table "1" for now.
*/
int
{
extern int idle_cpu_no_deep_c;
extern int cpuid_deep_cstates_supported(void);
void *la;
if (idle_cpu_no_deep_c)
return (DDI_FAILURE);
if (!cpuid_deep_cstates_supported())
return (DDI_FAILURE);
/*
* Get HPET ACPI table 1.
*/
(ACPI_TABLE_HEADER **)&hpet_table))) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* Hardware contains the last timer's number.
* Add 1 to get the number of timers.
*/
(long)HPET_MAX_CLK_PERIOD);
return (DDI_FAILURE);
}
"%lx", (long)num_timers);
return (DDI_FAILURE);
}
/*
* Solaris does not use the HPET Legacy Replacement Route capabilities.
* This feature has been off by default on test systems.
* The HPET spec does not specify if Legacy Replacement Route is
* on or off by default, so we explicitely set it off here.
* It should not matter which mode the HPET is in since we use
* the first available non-legacy replacement timer: timer 2.
*/
(void) hpet_set_leg_rt_cnf(&hpet_info, 0);
/*
* Make sure no timers are enabled (think fast reboot or
* virtual hardware).
*/
if (ret & HPET_TIMER_N_INT_ENB_CNF_BIT) {
}
}
/*
* Be aware the Main Counter may need to be initialized in the future
* if it is used for more than just Deep C-State support.
* The HPET's Main Counter does not need to be initialize to a specific
* value before starting it for use to wake up CPUs from Deep C-States.
*/
return (DDI_FAILURE);
}
/*
* Read main counter twice to record HPET latency for debugging.
*/
/*
* HPET main counter reads are supported now.
*/
}
void
hpet_acpi_fini(void)
{
return;
(void) hpet_stop_main_counter(&hpet_info);
}
/*
* Do initial setup to use a HPET timer as a proxy for Deep C-state stalled
* LAPIC Timers. Get a free HPET timer that supports I/O APIC routed interrupt.
* Setup data to handle the timer's ISR, and add the timer's interrupt.
*
* The ddi cannot be use to allocate the HPET timer's interrupt.
* ioapic_init_intr() in mp_platform_common() later sets up the I/O APIC
* to handle the HPET timer's interrupt.
*
* Note: FSB (MSI) interrupts are not currently supported by Intel HPETs as of
* ICH9. The HPET spec allows for MSI. In the future MSI may be prefered.
*/
static int
{
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* Avoid a possibly stuck interrupt by programing the HPET's timer here
* before the I/O APIC is programmed to handle this interrupt.
*/
/*
* All HPET functionality is supported.
*/
return (DDI_SUCCESS);
}
/*
* Called by kernel if it can support Deep C-States.
*/
static boolean_t
hpet_install_proxy(void)
{
return (B_TRUE);
return (B_FALSE);
return (B_TRUE);
}
/*
* Remove the interrupt that was added with add_avintr() in
* hpet_install_interrupt_handler().
*/
static void
{
}
static int
{
"ACPI_TABLE_HPET) %lx.",
(unsigned long)sizeof (ACPI_TABLE_HPET));
return (AE_ERROR);
}
"signature");
return (AE_ERROR);
}
if (!hpet_checksum_table((unsigned char *)hpet_table,
(unsigned int)table_header->Length)) {
return (AE_ERROR);
}
/*
* Sequence should be table number - 1. We are using table 1.
*/
(long)hpet_table->Sequence);
return (AE_ERROR);
}
return (AE_OK);
}
static boolean_t
{
unsigned char checksum = 0;
int i;
return (checksum == 0);
}
static void *
{
}
static int
{
}
static int
{
gcr &= ~HPET_GCFR_ENABLE_CNF;
}
/*
* Set the Legacy Replacement Route bit.
* This should be called before setting up timers.
* The HPET specification is silent regarding setting this after timers are
* programmed.
*/
static uint64_t
{
switch (new_value) {
case 0:
break;
case HPET_GCFR_LEG_RT_CNF:
break;
default:
break;
}
return (gen_conf);
}
static uint64_t
{
}
static uint64_t
{
return (*(uint64_t *)
}
static uint64_t
{
return (hip->gen_intrpt_stat);
}
static uint64_t
{
hip->logical_address, n);
return (conf);
}
static hpet_TN_conf_cap_t
{
return (cc);
}
static uint64_t
{
/*
* 32-bit main counters
*/
return (value);
}
/*
* HPET spec claims a 64-bit read can be split into two 32-bit reads
* by the hardware connection to the HPET.
*/
do {
return (value);
}
static void
{
}
static void
{
}
static void
{
hip->logical_address, n) = l;
else
}
static void
{
}
static void
{
uint64_t l;
l &= ~HPET_TIMER_N_INT_ENB_CNF_BIT;
}
static void
{
uint64_t l;
}
/*
* Add the interrupt handler for I/O APIC interrupt number (interrupt line).
*
* The I/O APIC line (vector) is programmed in ioapic_init_intr() called
* from apic_picinit() psm_ops apic_ops entry point after we return from
* apic_init() psm_ops entry point.
*/
static uint32_t
{
if (retval == 0) {
return (AE_BAD_PARAMETER);
}
return (AE_OK);
}
/*
* The HPET timers specify which I/O APIC interrupts they can be routed to.
* Find the first available non-legacy-replacement timer and its I/O APIC irq.
* Supported I/O APIC IRQs are specified in the int_route_cap bitmap in each
* timer's timer_n_config register.
*/
static int
{
int timer;
int intr;
for (timer = HPET_FIRST_NON_LEGACY_TIMER;
continue;
if (intr >= 0) {
return (timer);
}
}
return (-1);
}
/*
* Mark this timer as used.
*/
static void
{
*allocated_timers |= 1 << n;
}
/*
* Check if this timer is available.
* No mutual exclusion because only one thread uses this.
*/
static int
{
return ((allocated_timers & (1 << n)) == 0);
}
/*
* Setup timer N to route its interrupt to I/O APIC.
*/
static void
{
/*
* Caller is required to verify this interrupt route is supported.
*/
}
/*
* The HPET's Main Counter is not stopped before programming an HPET timer.
* This will allow the HPET to be used as a time source.
* The programmed timer interrupt may occur before this function returns.
* Callers must block interrupts before calling this function if they must
* guarantee the interrupt is handled after this function returns.
*
* Return 0 if main counter is less than timer after enabling timer.
* The interrupt was programmed, but it may fire before this returns.
* Return !0 if main counter is greater than timer after enabling timer.
* In other words: the timer will not fire, and we do not know if it did fire.
*
* delta is in HPET ticks.
*
* Writing a 64-bit value to a 32-bit register will "wrap around".
* A 32-bit HPET timer will wrap around in a little over 5 minutes.
*/
int
{
return (AE_OK);
return (AE_TIME);
}
/*
* CPR and power policy-change callback entry point.
*/
{
switch (code) {
case PM_DEFAULT_CPU_DEEP_IDLE:
/*FALLTHROUGH*/
case PM_ENABLE_CPU_DEEP_IDLE:
/*FALLTHROUGH*/
case PM_DISABLE_CPU_DEEP_IDLE:
return (hpet_deep_idle_config(code));
case CB_CODE_CPR_RESUME:
/*FALLTHROUGH*/
case CB_CODE_CPR_CHKPT:
return (B_TRUE);
case CST_EVENT_ONE_CSTATE:
return (B_TRUE);
default:
return (B_FALSE);
}
}
/*
* According to the HPET spec 1.0a: the Operating System must save and restore
* HPET event timer hardware context through ACPI sleep state transitions.
* Timer registers (including the main counter) may not be preserved through
* ACPI S3, S4, or S5 sleep states. This code does not not support S1 nor S2.
*
* Current HPET state is already in hpet.supported and
* hpet_state.proxy_installed. hpet_info contains the proxy interrupt HPET
* Timer state.
*
* Future projects beware: the HPET Main Counter is undefined after ACPI S3 or
* Main Counter to be monotomically (or accurately) increasing across CPR.
*
* Note: the CPR Checkpoint path later calls pause_cpus() which ensures all
* CPUs are awake and in a spin loop before the system suspends. The HPET is
* not needed for Deep C-state wakeup when CPUs are in cpu_pause().
* It is safe to leave the HPET running as the system suspends; we just
* disable the timer from generating interrupts here.
*/
static boolean_t
{
switch (code) {
case CB_CODE_CPR_CHKPT:
break;
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
/*
* spin
*/
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
return (B_FALSE);
}
}
intr = intr_clear();
}
break;
case CB_CODE_CPR_RESUME:
if (hpet_resume() == B_TRUE)
else
break;
default:
break;
}
return (ret);
}
/*
* Assume the HPET stopped in Suspend state and timer state was lost.
*/
static boolean_t
hpet_resume(void)
{
return (B_TRUE);
/*
* The HPET spec does not specify if Legacy Replacement Route is
* on or off by default, so we set it off here.
*/
(void) hpet_set_leg_rt_cnf(&hpet_info, 0);
}
return (B_FALSE);
}
return (B_TRUE);
return (B_TRUE);
}
/*
* Callback to enable/disable Deep C-States based on power.conf setting.
*/
static boolean_t
{
switch (code) {
case PM_DEFAULT_CPU_DEEP_IDLE:
/*FALLTHROUGH*/
case PM_ENABLE_CPU_DEEP_IDLE:
break;
break;
}
break;
case PM_DISABLE_CPU_DEEP_IDLE:
break;
/*
* The order of these operations is important to avoid
* lost wakeups: Set a flag to refuse all future LAPIC Timer
* proxy requests, then wake up all CPUs from deep C-state,
* and finally disable the HPET interrupt-generating timer.
*/
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
/*
* spin
*/
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
"!hpet_deep_idle_config: deadman");
return (B_FALSE);
}
}
intr = intr_clear();
}
break;
default:
code);
break;
}
return (ret);
}
/*
* Callback for _CST c-state change notifications.
*/
static void
{
switch (code) {
case CST_EVENT_ONE_CSTATE:
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
/*
* spin
*/
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
"!hpet_cst_callback: deadman");
return;
}
}
intr = intr_clear();
}
break;
break;
default:
break;
}
}
/*
* Interrupt Service Routine for HPET I/O-APIC-generated interrupts.
* Used to wakeup CPUs from Deep C-state when their Local APIC Timer stops.
* This ISR runs on one CPU which pokes other CPUs out of Deep C-state as
* needed.
*/
/* ARGSUSED */
static uint_t
{
/*
* We are using a level-triggered interrupt.
* HPET sets timer's General Interrupt Status Register bit N.
* ISR checks this bit to see if it needs servicing.
* ISR then clears this bit by writing 1 to that bit.
*/
if (!(timer_status & timer_mask))
return (DDI_INTR_UNCLAIMED);
/*
* Do not touch ISR data structures before checking the HPET's General
* Interrupt Status register. The General Interrupt Status register
* will not be set by hardware until after timer interrupt generation
* is enabled by software. Software allocates necessary data
* structures before enabling timer interrupts. ASSERT the software
* data structures required to handle this interrupt are initialized.
*/
/*
* CPUs in deep c-states do not enable interrupts until after
* performing idle cleanup which includes descheduling themselves from
* the HPET. The CPU running this ISR will NEVER find itself in the
* proxy list. A lost wakeup may occur if this is false.
*/
/*
* Higher level interrupts may deadlock with CPUs going idle if this
* ISR is prempted while holding hpet_proxy_lock.
*/
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
/*
* spin
*/
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
return (DDI_INTR_CLAIMED);
}
}
intr = intr_clear();
}
(void) hpet_guaranteed_schedule(HPET_INFINITY);
return (DDI_INTR_CLAIMED);
}
/*
* Used when disabling the HPET Timer interrupt. CPUs in Deep C-state must be
* woken up because they can no longer rely on the HPET's Timer to wake them.
* We do not need to wait for CPUs to wakeup.
*/
static void
hpet_expire_all(void)
{
}
}
}
/*
* To avoid missed wakeups this function must guarantee either the HPET timer
* was successfully programmed to the next expire time or there are no waiting
* CPUs.
*
* Callers cannot enter C2 or deeper if the HPET could not be programmed to
* generate its next interrupt to happen at required_wakeup_time or sooner.
* Returns B_TRUE if the HPET was programmed to interrupt by
* required_wakeup_time, B_FALSE if not.
*/
static boolean_t
{
/*
* Loop until we successfully program the HPET,
* or no CPUs are scheduled to use the HPET as a proxy.
*/
do {
/*
* Wake all CPUs that expired before now.
* Find the next CPU to wake up and next HPET program time.
*/
next_proxy_id = id;
}
}
if (next_proxy_time == HPET_INFINITY) {
/*
* There are currently no CPUs using the HPET's Timer
* as a proxy for their LAPIC Timer. The HPET's Timer
* does not need to be programmed.
*
* Letting the HPET timer wrap around to the current
* time is the longest possible timeout.
* A 64-bit timer will wrap around in ~ 2^44 seconds.
* A 32-bit timer will wrap around in ~ 2^12 seconds.
*
* Disabling the HPET's timer interrupt requires a
* (relatively expensive) write to the HPET.
* Instead we do nothing.
*
* We are gambling some CPU will attempt to enter a
* deep c-state before the timer wraps around.
* We assume one spurious interrupt in a little over an
* hour has less performance impact than writing to the
* HPET's timer disable bit every time all CPUs wakeup
* from deep c-state.
*/
} else {
/*
* Idle CPUs disable interrupts before programming the
* HPET to prevent a lost wakeup if the HPET
* interrupts the idle cpu before it can enter a
* Deep C-State.
*/
!= AE_OK) {
/*
* We could not program the HPET to wakeup the
* next CPU. We must wake the CPU ourself to
* avoid a lost wakeup.
*/
} else {
}
}
} while (!done);
return (next_proxy_time <= required_wakeup_time);
}
/*
* Use an HPET timer to act as this CPU's proxy local APIC timer.
* Used in deep c-states C2 and above while the CPU's local APIC timer stalls.
* Called by the idle thread with interrupts enabled.
* Always returns with interrupts disabled.
*
* There are 3 possible outcomes from this function:
* 1. The Local APIC Timer was already disabled before this function was called.
* LAPIC TIMER : disabled
* HPET : not scheduled to wake this CPU
* *lapic_expire : (hrtime_t)HPET_INFINITY
* Returns : B_TRUE
* 2. Successfully programmed the HPET to act as a LAPIC Timer proxy.
* LAPIC TIMER : disabled
* HPET : scheduled to wake this CPU
* *lapic_expire : hrtime_t when LAPIC timer would have expired
* Returns : B_TRUE
* 3. Failed to programmed the HPET to act as a LAPIC Timer proxy.
* LAPIC TIMER : enabled
* HPET : not scheduled to wake this CPU
* *lapic_expire : (hrtime_t)HPET_INFINITY
* Returns : B_FALSE
*
* The idle thread cannot enter Deep C-State in case 3.
* The idle thread must re-enable & re-program the LAPIC_TIMER in case 2.
*/
static boolean_t
{
extern hrtime_t apic_timer_stop_count(void);
extern void apic_timer_restart(hrtime_t);
/*
* A critical section exists between when the HPET is programmed
* to interrupt the CPU and when this CPU enters an idle state.
* Interrupts must be blocked during that time to prevent lost
* CBE wakeup interrupts from either LAPIC or HPET.
*
* Must block interrupts before acquiring hpet_proxy_lock to prevent
* a deadlock with the ISR if the ISR runs on this CPU after the
* idle thread acquires the mutex but before it clears interrupts.
*/
ASSERT(!interrupts_enabled());
/*
* LAPIC timer is currently disabled.
* Will not use the HPET as a LAPIC Timer proxy.
*/
return (B_TRUE);
}
/*
* Serialize hpet_proxy data structure manipulation.
*/
dead_count = 0;
while (!mutex_tryenter(&hpet_proxy_lock)) {
/*
* spin
*/
sti();
cli();
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
(ncpus > 1));
if (hset_update &&
return (B_FALSE);
}
}
/*
* LAPIC timer is currently disabled.
* Will not use the HPET as a LAPIC Timer proxy.
*/
return (B_TRUE);
}
return (B_FALSE);
}
}
return (B_FALSE);
}
/*
* We are done if another cpu is scheduled on the HPET with an
* expire time before us. The next HPET interrupt has been programmed
* to fire before our expire time.
*/
return (B_TRUE);
}
}
/*
* We are the next lAPIC to expire.
* Program the HPET with our expire time.
*/
}
return (rslt);
}
/*
* Called by the idle thread when waking up from Deep C-state before enabling
* interrupts. With an array data structure it is faster to always remove
* ourself from the array without checking if the HPET ISR already removed.
*
* We use a lazy algorithm for removing CPUs from the HPET's schedule.
* We do not reprogram the HPET here because this CPU has real work to do.
* On a idle system the CPU was probably woken up by the HPET's ISR.
* On a heavily loaded system CPUs are not going into Deep C-state.
* On a moderately loaded system another CPU will usually enter Deep C-state
* and reprogram the HPET before the HPET fires with our wakeup.
*/
static void
{
extern void apic_timer_restart(hrtime_t);
ASSERT(!interrupts_enabled());
/*
* Do not enable a LAPIC Timer that was initially disabled.
*/
if (expire != HPET_INFINITY)
}
/*
* Initialize data structure to keep track of CPUs using HPET as a proxy for
* their stalled local APIC timer. For now this is just an array.
*/
static void
hpet_init_proxy_data(void)
{
/*
* Use max_ncpus for hot plug compliance.
*/
KM_SLEEP);
/*
* Unused entries always contain HPET_INFINITY.
*/
}