#include <sys/hpet_acpi.h>
#include <sys/hpet.h>
#include <sys/bitmap.h>
#include <sys/inttypes.h>
#include <sys/time.h>
#include <sys/sunddi.h>
#include <sys/ksynch.h>
#include <sys/apic.h>
#include <sys/callb.h>
#include <sys/clock.h>
#include <sys/archsystm.h>
#include <sys/cpupart.h>
#include <sys/x86_archext.h>
#include <sys/prom_debug.h>
#include <sys/psm.h>
#include <sys/bootconf.h>
static int hpet_init_proxy(int *hpet_vect, iflag_t *hpet_flags);
static boolean_t hpet_install_proxy(void);
static boolean_t hpet_callback(int code);
static boolean_t hpet_cpr(int code);
static boolean_t hpet_resume(void);
static void hpet_cst_callback(uint32_t code);
static boolean_t hpet_deep_idle_config(int code);
static int hpet_validate_table(ACPI_TABLE_HPET *hpet_table);
static boolean_t hpet_checksum_table(unsigned char *table, unsigned int len);
static void *hpet_memory_map(ACPI_TABLE_HPET *hpet_table);
static int hpet_start_main_counter(hpet_info_t *hip);
static int hpet_stop_main_counter(hpet_info_t *hip);
static uint64_t hpet_read_main_counter_value(hpet_info_t *hip);
static uint64_t hpet_set_leg_rt_cnf(hpet_info_t *hip, uint32_t new_value);
static uint64_t hpet_read_gen_cap(hpet_info_t *hip);
static uint64_t hpet_read_gen_config(hpet_info_t *hip);
static uint64_t hpet_read_gen_intrpt_stat(hpet_info_t *hip);
static uint64_t hpet_read_timer_N_config(hpet_info_t *hip, uint_t n);
static hpet_TN_conf_cap_t hpet_convert_timer_N_config(uint64_t conf);
static void hpet_write_gen_config(hpet_info_t *hip, uint64_t l);
static void hpet_write_gen_intrpt_stat(hpet_info_t *hip, uint64_t l);
static void hpet_write_timer_N_config(hpet_info_t *hip, uint_t n, uint64_t l);
static void hpet_write_timer_N_comp(hpet_info_t *hip, uint_t n, uint64_t l);
static void hpet_disable_timer(hpet_info_t *hip, uint32_t timer_n);
static void hpet_enable_timer(hpet_info_t *hip, uint32_t timer_n);
static int hpet_get_IOAPIC_intr_capable_timer(hpet_info_t *hip);
static int hpet_timer_available(uint32_t allocated_timers, uint32_t n);
static void hpet_timer_alloc(uint32_t *allocated_timers, uint32_t n);
static void hpet_timer_set_up(hpet_info_t *hip, uint32_t timer_n,
uint32_t interrupt);
static uint_t hpet_isr(caddr_t, caddr_t);
static uint32_t hpet_install_interrupt_handler(avfunc func, int vector);
static void hpet_uninstall_interrupt_handler(void);
static void hpet_expire_all(void);
static boolean_t hpet_guaranteed_schedule(hrtime_t required_wakeup_time);
static boolean_t hpet_use_hpet_timer(hrtime_t *expire);
static void hpet_use_lapic_timer(hrtime_t expire);
static void hpet_init_proxy_data(void);
static kmutex_t hpet_state_lock;
static struct hpet_state {
boolean_t proxy_installed;
boolean_t cpr;
boolean_t cpu_deep_idle;
boolean_t uni_cstate;
} hpet_state = { B_FALSE, B_FALSE, B_TRUE, B_TRUE};
uint64_t hpet_spin_check = HPET_SPIN_CHECK;
uint64_t hpet_spin_timeout = HPET_SPIN_TIMEOUT;
uint64_t hpet_idle_spin_timeout = HPET_SPIN_TIMEOUT;
uint64_t hpet_isr_spin_timeout = HPET_SPIN_TIMEOUT;
static kmutex_t hpet_proxy_lock;
static hpet_proxy_t *hpet_proxy_users;
static boolean_t hpet_early_init_failed;
ACPI_TABLE_HPET *hpet_table;
hpet_info_t hpet_info;
static hrtime_t (*apic_timer_stop_count_fn)(void);
static void (*apic_timer_restart_fn)(hrtime_t);
static void
hpet_establish_hooks(void)
{
hpet.install_proxy = &hpet_install_proxy;
hpet.callback = &hpet_callback;
hpet.use_hpet_timer = &hpet_use_hpet_timer;
hpet.use_lapic_timer = &hpet_use_lapic_timer;
}
int
hpet_early_init(void)
{
extern hrtime_t tsc_read(void);
void *la;
uint64_t ret;
uint_t num_timers;
uint_t ti;
PRM_POINT("Initializing the HPET...");
if (hpet_early_init_failed) {
PRM_POINT("Prior HPET initialization failed, aborting...");
return (DDI_FAILURE);
}
if (hpet.supported >= HPET_TIMER_SUPPORT)
return (DDI_SUCCESS);
(void) memset(&hpet_info, 0, sizeof (hpet_info));
hpet.supported = HPET_NO_SUPPORT;
hpet_early_init_failed = B_TRUE;
if ((get_hwenv() & HW_XEN_HVM) != 0) {
PRM_POINT("will not program HPET in Xen HVM");
return (DDI_FAILURE);
}
if (BOP_GETPROPLEN(bootops, "hpet-table") != 8 ||
BOP_GETPROP(bootops, "hpet-table", (void *)&hpet_table) != 0) {
cmn_err(CE_NOTE, "!hpet_acpi: unable to get ACPI HPET table");
return (DDI_FAILURE);
}
if (hpet_validate_table(hpet_table) != AE_OK) {
cmn_err(CE_NOTE, "!hpet_acpi: invalid HPET table");
return (DDI_FAILURE);
}
PRM_POINT("hpet_memory_map()");
la = hpet_memory_map(hpet_table);
PRM_DEBUG(la);
if (la == NULL) {
cmn_err(CE_NOTE, "!hpet_acpi: memory map HPET failed");
return (DDI_FAILURE);
}
hpet_info.logical_address = la;
PRM_POINT("hpet_read_gen_cap()");
ret = hpet_read_gen_cap(&hpet_info);
PRM_DEBUG(ret);
hpet_info.gen_cap.counter_clk_period = HPET_GCAP_CNTR_CLK_PERIOD(ret);
hpet_info.gen_cap.vendor_id = HPET_GCAP_VENDOR_ID(ret);
hpet_info.gen_cap.leg_route_cap = HPET_GCAP_LEG_ROUTE_CAP(ret);
hpet_info.gen_cap.count_size_cap = HPET_GCAP_CNT_SIZE_CAP(ret);
hpet_info.gen_cap.num_tim_cap = HPET_GCAP_NUM_TIM_CAP(ret) + 1;
hpet_info.gen_cap.rev_id = HPET_GCAP_REV_ID(ret);
if (hpet_info.gen_cap.counter_clk_period > HPET_MAX_CLK_PERIOD) {
cmn_err(CE_NOTE, "!hpet_acpi: COUNTER_CLK_PERIOD 0x%lx > 0x%lx",
(long)hpet_info.gen_cap.counter_clk_period,
(long)HPET_MAX_CLK_PERIOD);
return (DDI_FAILURE);
}
num_timers = (uint_t)hpet_info.gen_cap.num_tim_cap;
PRM_DEBUG(num_timers);
if ((num_timers < 3) || (num_timers > 32)) {
cmn_err(CE_NOTE, "!hpet_acpi: invalid number of HPET timers "
"%lx", (long)num_timers);
return (DDI_FAILURE);
}
hpet_info.timer_n_config = (hpet_TN_conf_cap_t *)kmem_zalloc(
num_timers * sizeof (uint64_t), KM_SLEEP);
PRM_POINT("hpet_read_gen_config()");
ret = hpet_read_gen_config(&hpet_info);
hpet_info.gen_config.leg_rt_cnf = HPET_GCFR_LEG_RT_CNF_BITX(ret);
hpet_info.gen_config.enable_cnf = HPET_GCFR_ENABLE_CNF_BITX(ret);
PRM_POINT("hpet_read_gen_config()");
(void) hpet_set_leg_rt_cnf(&hpet_info, 0);
PRM_POINT("hpet_read_gen_config() again");
ret = hpet_read_gen_config(&hpet_info);
hpet_info.gen_config.leg_rt_cnf = HPET_GCFR_LEG_RT_CNF_BITX(ret);
hpet_info.gen_config.enable_cnf = HPET_GCFR_ENABLE_CNF_BITX(ret);
hpet_info.gen_intrpt_stat = hpet_read_gen_intrpt_stat(&hpet_info);
hpet_info.main_counter_value = hpet_read_main_counter_value(&hpet_info);
PRM_POINT("disable timer loop...");
for (ti = 0; ti < num_timers; ++ti) {
ret = hpet_read_timer_N_config(&hpet_info, ti);
if (ret & HPET_TIMER_N_INT_ENB_CNF_BIT) {
hpet_disable_timer(&hpet_info, ti);
ret &= ~HPET_TIMER_N_INT_ENB_CNF_BIT;
}
hpet_info.timer_n_config[ti] = hpet_convert_timer_N_config(ret);
}
PRM_POINT("disable timer loop complete");
PRM_POINT("hpet_start_main_counter()");
if (hpet_start_main_counter(&hpet_info) != AE_OK) {
cmn_err(CE_NOTE, "!hpet_acpi: hpet_start_main_counter failed");
return (DDI_FAILURE);
}
hpet_info.period = hpet_info.gen_cap.counter_clk_period;
PRM_POINT("TSC and HPET reads:");
hpet_info.tsc[0] = tsc_read();
hpet_info.hpet_main_counter_reads[0] =
hpet_read_main_counter_value(&hpet_info);
hpet_info.tsc[1] = tsc_read();
hpet_info.hpet_main_counter_reads[1] =
hpet_read_main_counter_value(&hpet_info);
hpet_info.tsc[2] = tsc_read();
PRM_DEBUG(hpet_info.hpet_main_counter_reads[0]);
PRM_DEBUG(hpet_info.hpet_main_counter_reads[1]);
PRM_DEBUG(hpet_info.tsc[0]);
PRM_DEBUG(hpet_info.tsc[1]);
PRM_DEBUG(hpet_info.tsc[2]);
ret = hpet_read_gen_config(&hpet_info);
hpet_info.gen_config.leg_rt_cnf = HPET_GCFR_LEG_RT_CNF_BITX(ret);
hpet_info.gen_config.enable_cnf = HPET_GCFR_ENABLE_CNF_BITX(ret);
hpet.supported = HPET_TIMER_SUPPORT;
hpet_early_init_failed = B_FALSE;
PRM_POINT("HPET main counter configured for reading...");
return (DDI_SUCCESS);
}
int
hpet_acpi_init(int *hpet_vect, iflag_t *hpet_flags, hrtime_t (*stop_fn)(void),
void (*restart_fn)(hrtime_t))
{
extern int idle_cpu_no_deep_c;
extern int cpuid_deep_cstates_supported(void);
PRM_POINT("Completing HPET initialization...");
if (hpet_early_init() != DDI_SUCCESS) {
PRM_POINT("Early HPET initialization failed; aborting...");
return (DDI_FAILURE);
}
apic_timer_stop_count_fn = stop_fn;
apic_timer_restart_fn = restart_fn;
hpet_establish_hooks();
if (idle_cpu_no_deep_c ||
!cpuid_deep_cstates_supported() ||
cpuid_arat_supported()) {
PRM_POINT("no need to program the HPET");
return (DDI_FAILURE);
}
return (hpet_init_proxy(hpet_vect, hpet_flags));
}
void
hpet_acpi_fini(void)
{
if (hpet.supported == HPET_NO_SUPPORT)
return;
if (hpet.supported >= HPET_TIMER_SUPPORT)
(void) hpet_stop_main_counter(&hpet_info);
if (hpet.supported > HPET_TIMER_SUPPORT)
hpet_disable_timer(&hpet_info, hpet_info.cstate_timer.timer);
}
static int
hpet_init_proxy(int *hpet_vect, iflag_t *hpet_flags)
{
PRM_POINT("hpet_get_IOAPIC_intr_capable_timer()");
if (hpet_get_IOAPIC_intr_capable_timer(&hpet_info) == -1) {
cmn_err(CE_WARN, "!hpet_acpi: get ioapic intr failed.");
return (DDI_FAILURE);
}
hpet_init_proxy_data();
PRM_POINT("hpet_install_interrupt_handler()");
if (hpet_install_interrupt_handler(&hpet_isr,
hpet_info.cstate_timer.intr) != AE_OK) {
cmn_err(CE_WARN, "!hpet_acpi: install interrupt failed.");
return (DDI_FAILURE);
}
*hpet_vect = hpet_info.cstate_timer.intr;
hpet_flags->intr_el = INTR_EL_LEVEL;
hpet_flags->intr_po = INTR_PO_ACTIVE_HIGH;
hpet_flags->bustype = BUS_PCI;
PRM_POINT("hpet_timer_set_up()");
hpet_timer_set_up(&hpet_info, hpet_info.cstate_timer.timer,
hpet_info.cstate_timer.intr);
PRM_POINT("back from hpet_timer_set_up()");
hpet.supported = HPET_FULL_SUPPORT;
PRM_POINT("HPET full support");
return (DDI_SUCCESS);
}
static boolean_t
hpet_install_proxy(void)
{
if (hpet_state.proxy_installed == B_TRUE)
return (B_TRUE);
if (hpet.supported != HPET_FULL_SUPPORT)
return (B_FALSE);
hpet_enable_timer(&hpet_info, hpet_info.cstate_timer.timer);
hpet_state.proxy_installed = B_TRUE;
return (B_TRUE);
}
static void
hpet_uninstall_interrupt_handler(void)
{
rem_avintr(NULL, CBE_HIGH_PIL, &hpet_isr, hpet_info.cstate_timer.intr);
}
static int
hpet_validate_table(ACPI_TABLE_HPET *hpet_table)
{
ACPI_TABLE_HEADER *table_header = (ACPI_TABLE_HEADER *)hpet_table;
if (table_header->Length != sizeof (ACPI_TABLE_HPET)) {
cmn_err(CE_WARN, "!hpet_validate_table: Length %lx != sizeof ("
"ACPI_TABLE_HPET) %lx.",
(unsigned long)((ACPI_TABLE_HEADER *)hpet_table)->Length,
(unsigned long)sizeof (ACPI_TABLE_HPET));
return (AE_ERROR);
}
if (!ACPI_COMPARE_NAME(table_header->Signature, ACPI_SIG_HPET)) {
cmn_err(CE_WARN, "!hpet_validate_table: Invalid HPET table "
"signature");
return (AE_ERROR);
}
if (!hpet_checksum_table((unsigned char *)hpet_table,
(unsigned int)table_header->Length)) {
cmn_err(CE_WARN, "!hpet_validate_table: Invalid HPET checksum");
return (AE_ERROR);
}
if (hpet_table->Sequence != HPET_TABLE_1 - 1) {
cmn_err(CE_WARN, "!hpet_validate_table: Invalid Sequence %lx",
(long)hpet_table->Sequence);
return (AE_ERROR);
}
return (AE_OK);
}
static boolean_t
hpet_checksum_table(unsigned char *table, unsigned int length)
{
unsigned char checksum = 0;
int i;
for (i = 0; i < length; ++i, ++table)
checksum += *table;
return (checksum == 0);
}
static void *
hpet_memory_map(ACPI_TABLE_HPET *hpet_table)
{
return (psm_map_new(hpet_table->Address.Address, (size_t)HPET_SIZE,
PSM_PROT_WRITE | PSM_PROT_READ));
}
static int
hpet_start_main_counter(hpet_info_t *hip)
{
uint64_t *gcr_ptr;
uint64_t gcr;
gcr_ptr = (uint64_t *)HPET_GEN_CONFIG_ADDRESS(hip->logical_address);
gcr = *gcr_ptr;
gcr |= HPET_GCFR_ENABLE_CNF;
*gcr_ptr = gcr;
gcr = *gcr_ptr;
return (gcr & HPET_GCFR_ENABLE_CNF ? AE_OK : ~AE_OK);
}
static int
hpet_stop_main_counter(hpet_info_t *hip)
{
uint64_t *gcr_ptr;
uint64_t gcr;
gcr_ptr = (uint64_t *)HPET_GEN_CONFIG_ADDRESS(hip->logical_address);
gcr = *gcr_ptr;
gcr &= ~HPET_GCFR_ENABLE_CNF;
*gcr_ptr = gcr;
gcr = *gcr_ptr;
return (gcr & HPET_GCFR_ENABLE_CNF ? ~AE_OK : AE_OK);
}
boolean_t
hpet_timer_is_readable(void)
{
return ((hpet.supported >= HPET_TIMER_SUPPORT) ? B_TRUE : B_FALSE);
}
uint64_t
hpet_read_timer(void)
{
return (hpet_read_main_counter_value(&hpet_info));
}
static uint64_t
hpet_set_leg_rt_cnf(hpet_info_t *hip, uint32_t new_value)
{
uint64_t gen_conf = hpet_read_gen_config(hip);
switch (new_value) {
case 0:
gen_conf &= ~HPET_GCFR_LEG_RT_CNF;
break;
case HPET_GCFR_LEG_RT_CNF:
gen_conf |= HPET_GCFR_LEG_RT_CNF;
break;
default:
ASSERT(new_value == 0 || new_value == HPET_GCFR_LEG_RT_CNF);
break;
}
hpet_write_gen_config(hip, gen_conf);
return (gen_conf);
}
static uint64_t
hpet_read_gen_cap(hpet_info_t *hip)
{
return (*(uint64_t *)HPET_GEN_CAP_ADDRESS(hip->logical_address));
}
static uint64_t
hpet_read_gen_config(hpet_info_t *hip)
{
return (*(uint64_t *)
HPET_GEN_CONFIG_ADDRESS(hip->logical_address));
}
static uint64_t
hpet_read_gen_intrpt_stat(hpet_info_t *hip)
{
hip->gen_intrpt_stat = *(uint64_t *)HPET_GEN_INTR_STAT_ADDRESS(
hip->logical_address);
return (hip->gen_intrpt_stat);
}
static uint64_t
hpet_read_timer_N_config(hpet_info_t *hip, uint_t n)
{
uint64_t conf = *(uint64_t *)HPET_TIMER_N_CONF_ADDRESS(
hip->logical_address, n);
hip->timer_n_config[n] = hpet_convert_timer_N_config(conf);
return (conf);
}
static hpet_TN_conf_cap_t
hpet_convert_timer_N_config(uint64_t conf)
{
hpet_TN_conf_cap_t cc = { 0 };
cc.int_route_cap = HPET_TIMER_N_INT_ROUTE_CAP(conf);
cc.fsb_int_del_cap = HPET_TIMER_N_FSB_INT_DEL_CAP(conf);
cc.fsb_int_en_cnf = HPET_TIMER_N_FSB_EN_CNF(conf);
cc.int_route_cnf = HPET_TIMER_N_INT_ROUTE_CNF(conf);
cc.mode32_cnf = HPET_TIMER_N_MODE32_CNF(conf);
cc.val_set_cnf = HPET_TIMER_N_VAL_SET_CNF(conf);
cc.size_cap = HPET_TIMER_N_SIZE_CAP(conf);
cc.per_int_cap = HPET_TIMER_N_PER_INT_CAP(conf);
cc.type_cnf = HPET_TIMER_N_TYPE_CNF(conf);
cc.int_enb_cnf = HPET_TIMER_N_INT_ENB_CNF(conf);
cc.int_type_cnf = HPET_TIMER_N_INT_TYPE_CNF(conf);
return (cc);
}
static uint64_t
hpet_read_main_counter_value(hpet_info_t *hip)
{
uint64_t value;
uint32_t *counter;
uint32_t high1, high2, low;
counter = (uint32_t *)HPET_MAIN_COUNTER_ADDRESS(hip->logical_address);
if (hip->gen_cap.count_size_cap == 0) {
value = (uint64_t)*counter;
hip->main_counter_value = value;
return (value);
}
high2 = counter[1];
do {
high1 = high2;
low = counter[0];
high2 = counter[1];
} while (high2 != high1);
value = ((uint64_t)high1 << 32) | low;
hip->main_counter_value = value;
return (value);
}
static void
hpet_write_gen_config(hpet_info_t *hip, uint64_t l)
{
*(uint64_t *)HPET_GEN_CONFIG_ADDRESS(hip->logical_address) = l;
}
static void
hpet_write_gen_intrpt_stat(hpet_info_t *hip, uint64_t l)
{
*(uint64_t *)HPET_GEN_INTR_STAT_ADDRESS(hip->logical_address) = l;
}
static void
hpet_write_timer_N_config(hpet_info_t *hip, uint_t n, uint64_t conf)
{
uint32_t *confaddr = (uint32_t *)HPET_TIMER_N_CONF_ADDRESS(
hip->logical_address, n);
uint32_t conf32 = 0xFFFFFFFF & conf;
PRM_DEBUG(n);
PRM_DEBUG(conf);
PRM_DEBUG(conf32);
*confaddr = conf32;
PRM_POINT("write done");
}
static void
hpet_write_timer_N_comp(hpet_info_t *hip, uint_t n, uint64_t l)
{
*(uint64_t *)HPET_TIMER_N_COMP_ADDRESS(hip->logical_address, n) = l;
}
static void
hpet_disable_timer(hpet_info_t *hip, uint32_t timer_n)
{
uint64_t l;
l = hpet_read_timer_N_config(hip, timer_n);
l &= ~HPET_TIMER_N_INT_ENB_CNF_BIT;
hpet_write_timer_N_config(hip, timer_n, l);
}
static void
hpet_enable_timer(hpet_info_t *hip, uint32_t timer_n)
{
uint64_t l;
l = hpet_read_timer_N_config(hip, timer_n);
l |= HPET_TIMER_N_INT_ENB_CNF_BIT;
hpet_write_timer_N_config(hip, timer_n, l);
}
static uint32_t
hpet_install_interrupt_handler(avfunc func, int vector)
{
uint32_t retval;
retval = add_avintr(NULL, CBE_HIGH_PIL, func, "HPET Timer",
vector, NULL, NULL, NULL, NULL);
if (retval == 0) {
cmn_err(CE_WARN, "!hpet_acpi: add_avintr() failed");
return (AE_BAD_PARAMETER);
}
return (AE_OK);
}
static int
hpet_get_IOAPIC_intr_capable_timer(hpet_info_t *hip)
{
int timer;
int intr;
for (timer = HPET_FIRST_NON_LEGACY_TIMER;
timer < hip->gen_cap.num_tim_cap; ++timer) {
if (!hpet_timer_available(hip->allocated_timers, timer))
continue;
intr = lowbit(hip->timer_n_config[timer].int_route_cap) - 1;
PRM_DEBUG(timer);
PRM_DEBUG(intr);
if (intr >= 0) {
hpet_timer_alloc(&hip->allocated_timers, timer);
hip->cstate_timer.timer = timer;
hip->cstate_timer.intr = intr;
return (timer);
}
}
return (-1);
}
static void
hpet_timer_alloc(uint32_t *allocated_timers, uint32_t n)
{
*allocated_timers |= 1 << n;
}
static int
hpet_timer_available(uint32_t allocated_timers, uint32_t n)
{
return ((allocated_timers & (1 << n)) == 0);
}
static void
hpet_timer_set_up(hpet_info_t *hip, uint32_t timer_n, uint32_t interrupt)
{
uint64_t conf;
PRM_DEBUG(timer_n);
PRM_DEBUG(interrupt);
PRM_POINT("hpet_read_timer_N_config()");
conf = hpet_read_timer_N_config(hip, timer_n);
PRM_DEBUG(conf);
ASSERT(HPET_TIMER_N_INT_ROUTE_CAP(conf) & (1 << interrupt));
conf &= ~HPET_TIMER_N_FSB_EN_CNF_BIT;
conf |= HPET_TIMER_N_INT_ROUTE_SHIFT(interrupt);
conf &= ~HPET_TIMER_N_TYPE_CNF_BIT;
conf &= ~HPET_TIMER_N_INT_ENB_CNF_BIT;
conf |= HPET_TIMER_N_INT_TYPE_CNF_BIT;
PRM_POINT("hpet_write_timer_N_config()");
PRM_DEBUG(conf);
hpet_write_timer_N_config(hip, timer_n, conf);
PRM_POINT("back from hpet_write_timer_N_config()");
}
int
hpet_timer_program(hpet_info_t *hip, uint32_t timer, uint64_t delta)
{
uint64_t time, program;
program = hpet_read_main_counter_value(hip);
program += delta;
hpet_write_timer_N_comp(hip, timer, program);
time = hpet_read_main_counter_value(hip);
if (time < program)
return (AE_OK);
return (AE_TIME);
}
boolean_t
hpet_callback(int code)
{
switch (code) {
case PM_DEFAULT_CPU_DEEP_IDLE:
case PM_ENABLE_CPU_DEEP_IDLE:
case PM_DISABLE_CPU_DEEP_IDLE:
return (hpet_deep_idle_config(code));
case CB_CODE_CPR_RESUME:
case CB_CODE_CPR_CHKPT:
return (hpet_cpr(code));
case CST_EVENT_MULTIPLE_CSTATES:
hpet_cst_callback(CST_EVENT_MULTIPLE_CSTATES);
return (B_TRUE);
case CST_EVENT_ONE_CSTATE:
hpet_cst_callback(CST_EVENT_ONE_CSTATE);
return (B_TRUE);
default:
cmn_err(CE_NOTE, "!hpet_callback: invalid code %d\n", code);
return (B_FALSE);
}
}
static boolean_t
hpet_cpr(int code)
{
ulong_t intr, dead_count = 0;
hrtime_t dead = gethrtime() + hpet_spin_timeout;
boolean_t ret = B_TRUE;
mutex_enter(&hpet_state_lock);
switch (code) {
case CB_CODE_CPR_CHKPT:
if (hpet_state.proxy_installed == B_FALSE)
break;
hpet_state.cpr = B_TRUE;
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
intr_restore(intr);
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
if (gethrtime() > dead) {
hpet_state.cpr = B_FALSE;
mutex_exit(&hpet_state_lock);
cmn_err(CE_NOTE, "!hpet_cpr: deadman");
return (B_FALSE);
}
}
intr = intr_clear();
}
hpet_expire_all();
mutex_exit(&hpet_proxy_lock);
intr_restore(intr);
hpet_disable_timer(&hpet_info, hpet_info.cstate_timer.timer);
break;
case CB_CODE_CPR_RESUME:
if (hpet_resume() == B_TRUE)
hpet_state.cpr = B_FALSE;
else
cmn_err(CE_NOTE, "!hpet_resume failed.");
break;
default:
cmn_err(CE_NOTE, "!hpet_cpr: invalid code %d\n", code);
ret = B_FALSE;
break;
}
mutex_exit(&hpet_state_lock);
return (ret);
}
static boolean_t
hpet_resume(void)
{
if (hpet.supported != HPET_TIMER_SUPPORT)
return (B_TRUE);
(void) hpet_set_leg_rt_cnf(&hpet_info, 0);
if (hpet_start_main_counter(&hpet_info) != AE_OK) {
cmn_err(CE_NOTE, "!hpet_resume: start main counter failed");
hpet.supported = HPET_NO_SUPPORT;
if (hpet_state.proxy_installed == B_TRUE) {
hpet_state.proxy_installed = B_FALSE;
hpet_uninstall_interrupt_handler();
}
return (B_FALSE);
}
if (hpet_state.proxy_installed == B_FALSE)
return (B_TRUE);
hpet_timer_set_up(&hpet_info, hpet_info.cstate_timer.timer,
hpet_info.cstate_timer.intr);
if (hpet_state.cpu_deep_idle == B_TRUE)
hpet_enable_timer(&hpet_info, hpet_info.cstate_timer.timer);
return (B_TRUE);
}
static boolean_t
hpet_deep_idle_config(int code)
{
ulong_t intr, dead_count = 0;
hrtime_t dead = gethrtime() + hpet_spin_timeout;
boolean_t ret = B_TRUE;
mutex_enter(&hpet_state_lock);
switch (code) {
case PM_DEFAULT_CPU_DEEP_IDLE:
case PM_ENABLE_CPU_DEEP_IDLE:
if (hpet_state.cpu_deep_idle == B_TRUE)
break;
if (hpet_state.proxy_installed == B_FALSE) {
ret = B_FALSE;
break;
}
hpet_enable_timer(&hpet_info, hpet_info.cstate_timer.timer);
hpet_state.cpu_deep_idle = B_TRUE;
break;
case PM_DISABLE_CPU_DEEP_IDLE:
if ((hpet_state.cpu_deep_idle == B_FALSE) ||
(hpet_state.proxy_installed == B_FALSE))
break;
hpet_state.cpu_deep_idle = B_FALSE;
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
intr_restore(intr);
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
if (gethrtime() > dead) {
hpet_state.cpu_deep_idle = B_TRUE;
mutex_exit(&hpet_state_lock);
cmn_err(CE_NOTE,
"!hpet_deep_idle_config: deadman");
return (B_FALSE);
}
}
intr = intr_clear();
}
hpet_expire_all();
mutex_exit(&hpet_proxy_lock);
intr_restore(intr);
hpet_disable_timer(&hpet_info, hpet_info.cstate_timer.timer);
break;
default:
cmn_err(CE_NOTE, "!hpet_deep_idle_config: invalid code %d\n",
code);
ret = B_FALSE;
break;
}
mutex_exit(&hpet_state_lock);
return (ret);
}
static void
hpet_cst_callback(uint32_t code)
{
ulong_t intr, dead_count = 0;
hrtime_t dead = gethrtime() + hpet_spin_timeout;
switch (code) {
case CST_EVENT_ONE_CSTATE:
hpet_state.uni_cstate = B_TRUE;
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
intr_restore(intr);
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
if (gethrtime() > dead) {
hpet_expire_all();
cmn_err(CE_NOTE,
"!hpet_cst_callback: deadman");
return;
}
}
intr = intr_clear();
}
hpet_expire_all();
mutex_exit(&hpet_proxy_lock);
intr_restore(intr);
break;
case CST_EVENT_MULTIPLE_CSTATES:
hpet_state.uni_cstate = B_FALSE;
break;
default:
cmn_err(CE_NOTE, "!hpet_cst_callback: invalid code %d\n", code);
break;
}
}
static uint_t
hpet_isr(caddr_t arg __unused, caddr_t arg1 __unused)
{
uint64_t timer_status;
uint64_t timer_mask;
ulong_t intr, dead_count = 0;
hrtime_t dead = gethrtime() + hpet_isr_spin_timeout;
timer_mask = HPET_INTR_STATUS_MASK(hpet_info.cstate_timer.timer);
timer_status = hpet_read_gen_intrpt_stat(&hpet_info);
if (!(timer_status & timer_mask))
return (DDI_INTR_UNCLAIMED);
hpet_write_gen_intrpt_stat(&hpet_info, timer_mask);
ASSERT(hpet_proxy_users != NULL);
ASSERT(hpet_proxy_users[CPU->cpu_id] == HPET_INFINITY);
intr = intr_clear();
while (!mutex_tryenter(&hpet_proxy_lock)) {
intr_restore(intr);
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
if (gethrtime() > dead) {
hpet_expire_all();
return (DDI_INTR_CLAIMED);
}
}
intr = intr_clear();
}
(void) hpet_guaranteed_schedule(HPET_INFINITY);
mutex_exit(&hpet_proxy_lock);
intr_restore(intr);
return (DDI_INTR_CLAIMED);
}
static void
hpet_expire_all(void)
{
processorid_t id;
for (id = 0; id < max_ncpus; ++id) {
if (hpet_proxy_users[id] != HPET_INFINITY) {
hpet_proxy_users[id] = HPET_INFINITY;
if (id != CPU->cpu_id)
poke_cpu(id);
}
}
}
static boolean_t
hpet_guaranteed_schedule(hrtime_t required_wakeup_time)
{
hrtime_t now, next_proxy_time;
processorid_t id, next_proxy_id;
int proxy_timer = hpet_info.cstate_timer.timer;
boolean_t done = B_FALSE;
ASSERT(mutex_owned(&hpet_proxy_lock));
do {
now = gethrtime();
next_proxy_time = HPET_INFINITY;
next_proxy_id = CPU->cpu_id;
for (id = 0; id < max_ncpus; ++id) {
if (hpet_proxy_users[id] < now) {
hpet_proxy_users[id] = HPET_INFINITY;
if (id != CPU->cpu_id)
poke_cpu(id);
} else if (hpet_proxy_users[id] < next_proxy_time) {
next_proxy_time = hpet_proxy_users[id];
next_proxy_id = id;
}
}
if (next_proxy_time == HPET_INFINITY) {
done = B_TRUE;
} else {
if (hpet_timer_program(&hpet_info, proxy_timer,
HRTIME_TO_HPET_TICKS(next_proxy_time - gethrtime()))
!= AE_OK) {
hpet_proxy_users[next_proxy_id] = HPET_INFINITY;
if (next_proxy_id != CPU->cpu_id)
poke_cpu(next_proxy_id);
} else {
done = B_TRUE;
}
}
} while (!done);
return (next_proxy_time <= required_wakeup_time);
}
static boolean_t
hpet_use_hpet_timer(hrtime_t *lapic_expire)
{
hrtime_t now, expire, dead;
uint64_t lapic_count, dead_count;
cpupart_t *cpu_part;
processorid_t cpu_sid;
processorid_t cpu_id = CPU->cpu_id;
processorid_t id;
boolean_t rslt;
boolean_t hset_update;
cpu_part = CPU->cpu_part;
cpu_sid = CPU->cpu_seqid;
ASSERT(CPU->cpu_thread == CPU->cpu_idle_thread);
ASSERT(!interrupts_enabled());
lapic_count = apic_timer_stop_count_fn();
now = gethrtime();
dead = now + hpet_idle_spin_timeout;
*lapic_expire = expire = now + lapic_count;
if (lapic_count == (hrtime_t)-1) {
*lapic_expire = (hrtime_t)HPET_INFINITY;
return (B_TRUE);
}
dead_count = 0;
while (!mutex_tryenter(&hpet_proxy_lock)) {
apic_timer_restart_fn(expire);
sti();
cli();
if (dead_count++ > hpet_spin_check) {
dead_count = 0;
hset_update = (((CPU->cpu_flags & CPU_OFFLINE) == 0) &&
(ncpus > 1));
if (hset_update &&
!bitset_in_set(&cpu_part->cp_haltset, cpu_sid)) {
*lapic_expire = (hrtime_t)HPET_INFINITY;
return (B_FALSE);
}
}
lapic_count = apic_timer_stop_count_fn();
now = gethrtime();
*lapic_expire = expire = now + lapic_count;
if (lapic_count == (hrtime_t)-1) {
*lapic_expire = (hrtime_t)HPET_INFINITY;
return (B_TRUE);
}
if (now > dead) {
apic_timer_restart_fn(expire);
*lapic_expire = (hrtime_t)HPET_INFINITY;
return (B_FALSE);
}
}
if ((hpet_state.cpr == B_TRUE) ||
(hpet_state.cpu_deep_idle == B_FALSE) ||
(hpet_state.proxy_installed == B_FALSE) ||
(hpet_state.uni_cstate == B_TRUE)) {
mutex_exit(&hpet_proxy_lock);
apic_timer_restart_fn(expire);
*lapic_expire = (hrtime_t)HPET_INFINITY;
return (B_FALSE);
}
hpet_proxy_users[cpu_id] = expire;
for (id = 0; id < max_ncpus; ++id) {
if ((hpet_proxy_users[id] <= expire) && (id != cpu_id)) {
mutex_exit(&hpet_proxy_lock);
return (B_TRUE);
}
}
rslt = hpet_guaranteed_schedule(expire);
mutex_exit(&hpet_proxy_lock);
if (rslt == B_FALSE) {
apic_timer_restart_fn(expire);
*lapic_expire = (hrtime_t)HPET_INFINITY;
}
return (rslt);
}
static void
hpet_use_lapic_timer(hrtime_t expire)
{
processorid_t cpu_id = CPU->cpu_id;
ASSERT(CPU->cpu_thread == CPU->cpu_idle_thread);
ASSERT(!interrupts_enabled());
hpet_proxy_users[cpu_id] = HPET_INFINITY;
if (expire != HPET_INFINITY)
apic_timer_restart_fn(expire);
}
static void
hpet_init_proxy_data(void)
{
processorid_t id;
hpet_proxy_users = kmem_zalloc(max_ncpus * sizeof (*hpet_proxy_users),
KM_SLEEP);
for (id = 0; id < max_ncpus; ++id)
hpet_proxy_users[id] = HPET_INFINITY;
}