18906SEric.Saxe@Sun.COM /* 28906SEric.Saxe@Sun.COM * CDDL HEADER START 38906SEric.Saxe@Sun.COM * 48906SEric.Saxe@Sun.COM * The contents of this file are subject to the terms of the 58906SEric.Saxe@Sun.COM * Common Development and Distribution License (the "License"). 68906SEric.Saxe@Sun.COM * You may not use this file except in compliance with the License. 78906SEric.Saxe@Sun.COM * 88906SEric.Saxe@Sun.COM * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 98906SEric.Saxe@Sun.COM * or http://www.opensolaris.org/os/licensing. 108906SEric.Saxe@Sun.COM * See the License for the specific language governing permissions 118906SEric.Saxe@Sun.COM * and limitations under the License. 128906SEric.Saxe@Sun.COM * 138906SEric.Saxe@Sun.COM * When distributing Covered Code, include this CDDL HEADER in each 148906SEric.Saxe@Sun.COM * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 158906SEric.Saxe@Sun.COM * If applicable, add the following below this CDDL HEADER, with the 168906SEric.Saxe@Sun.COM * fields enclosed by brackets "[]" replaced with your own identifying 178906SEric.Saxe@Sun.COM * information: Portions Copyright [yyyy] [name of copyright owner] 188906SEric.Saxe@Sun.COM * 198906SEric.Saxe@Sun.COM * CDDL HEADER END 208906SEric.Saxe@Sun.COM */ 218906SEric.Saxe@Sun.COM /* 228906SEric.Saxe@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 238906SEric.Saxe@Sun.COM * Use is subject to license terms. 248906SEric.Saxe@Sun.COM */ 258906SEric.Saxe@Sun.COM 268906SEric.Saxe@Sun.COM #include <sys/cpu_pm.h> 278906SEric.Saxe@Sun.COM #include <sys/x86_archext.h> 288906SEric.Saxe@Sun.COM #include <sys/sdt.h> 298906SEric.Saxe@Sun.COM #include <sys/spl.h> 308906SEric.Saxe@Sun.COM #include <sys/machsystm.h> 318906SEric.Saxe@Sun.COM #include <sys/hpet.h> 328906SEric.Saxe@Sun.COM #include <sys/cpupm.h> 338906SEric.Saxe@Sun.COM #include <sys/cpu_idle.h> 348906SEric.Saxe@Sun.COM #include <sys/cpu_acpi.h> 358906SEric.Saxe@Sun.COM #include <sys/cpupm_throttle.h> 36*8983SBill.Holler@Sun.COM #include <sys/dtrace.h> 378906SEric.Saxe@Sun.COM 388906SEric.Saxe@Sun.COM /* 398906SEric.Saxe@Sun.COM * This callback is used to build the PPM CPU domains once 408906SEric.Saxe@Sun.COM * all the CPU devices have been started. The callback is 418906SEric.Saxe@Sun.COM * initialized by the PPM driver to point to a routine that 428906SEric.Saxe@Sun.COM * will build the domains. 438906SEric.Saxe@Sun.COM */ 448906SEric.Saxe@Sun.COM void (*cpupm_rebuild_cpu_domains)(void); 458906SEric.Saxe@Sun.COM 468906SEric.Saxe@Sun.COM /* 478906SEric.Saxe@Sun.COM * This callback is used to reset the topspeed for all the 488906SEric.Saxe@Sun.COM * CPU devices. The callback is initialized by the PPM driver to 498906SEric.Saxe@Sun.COM * point to a routine that will reinitialize all the CPU devices 508906SEric.Saxe@Sun.COM * once all the CPU devices have been started and the CPU domains 518906SEric.Saxe@Sun.COM * built. 528906SEric.Saxe@Sun.COM */ 538906SEric.Saxe@Sun.COM void (*cpupm_init_topspeed)(void); 548906SEric.Saxe@Sun.COM 558906SEric.Saxe@Sun.COM /* 568906SEric.Saxe@Sun.COM * This callback is used to redefine the topspeed for a CPU device. 578906SEric.Saxe@Sun.COM * Since all CPUs in a domain should have identical properties, this 588906SEric.Saxe@Sun.COM * callback is initialized by the PPM driver to point to a routine 598906SEric.Saxe@Sun.COM * that will redefine the topspeed for all devices in a CPU domain. 608906SEric.Saxe@Sun.COM * This callback is exercised whenever an ACPI _PPC change notification 618906SEric.Saxe@Sun.COM * is received by the CPU driver. 628906SEric.Saxe@Sun.COM */ 638906SEric.Saxe@Sun.COM void (*cpupm_redefine_topspeed)(void *); 648906SEric.Saxe@Sun.COM 658906SEric.Saxe@Sun.COM /* 668906SEric.Saxe@Sun.COM * This callback is used by the PPM driver to call into the CPU driver 678906SEric.Saxe@Sun.COM * to find a CPU's current topspeed (i.e., it's current ACPI _PPC value). 688906SEric.Saxe@Sun.COM */ 698906SEric.Saxe@Sun.COM void (*cpupm_set_topspeed_callb)(void *, int); 708906SEric.Saxe@Sun.COM 718906SEric.Saxe@Sun.COM /* 728906SEric.Saxe@Sun.COM * This callback is used by the PPM driver to call into the CPU driver 738906SEric.Saxe@Sun.COM * to set a new topspeed for a CPU. 748906SEric.Saxe@Sun.COM */ 758906SEric.Saxe@Sun.COM int (*cpupm_get_topspeed_callb)(void *); 768906SEric.Saxe@Sun.COM 778906SEric.Saxe@Sun.COM static void cpupm_event_notify_handler(ACPI_HANDLE, UINT32, void *); 788906SEric.Saxe@Sun.COM static void cpupm_free_notify_handlers(cpu_t *); 798906SEric.Saxe@Sun.COM 808906SEric.Saxe@Sun.COM /* 818906SEric.Saxe@Sun.COM * Until proven otherwise, all power states are manageable. 828906SEric.Saxe@Sun.COM */ 838906SEric.Saxe@Sun.COM static uint32_t cpupm_enabled = CPUPM_ALL_STATES; 848906SEric.Saxe@Sun.COM 858906SEric.Saxe@Sun.COM /* 868906SEric.Saxe@Sun.COM * Until all CPUs have started, we do not allow 878906SEric.Saxe@Sun.COM * power management. 888906SEric.Saxe@Sun.COM */ 898906SEric.Saxe@Sun.COM static boolean_t cpupm_ready = B_FALSE; 908906SEric.Saxe@Sun.COM 918906SEric.Saxe@Sun.COM cpupm_state_domains_t *cpupm_pstate_domains = NULL; 928906SEric.Saxe@Sun.COM cpupm_state_domains_t *cpupm_tstate_domains = NULL; 938906SEric.Saxe@Sun.COM cpupm_state_domains_t *cpupm_cstate_domains = NULL; 948906SEric.Saxe@Sun.COM 958906SEric.Saxe@Sun.COM /* 968906SEric.Saxe@Sun.COM * c-state tunables 978906SEric.Saxe@Sun.COM * 988906SEric.Saxe@Sun.COM * cpupm_cs_idle_cost_tunable is the ratio of time CPU spends executing + idle 998906SEric.Saxe@Sun.COM * divided by time spent in the idle state transitions. 1008906SEric.Saxe@Sun.COM * A value of 10 means the CPU will not spend more than 1/10 of its time 1018906SEric.Saxe@Sun.COM * in idle latency. The worst case performance will be 90% of non Deep C-state 1028906SEric.Saxe@Sun.COM * kernel. 1038906SEric.Saxe@Sun.COM * 1048906SEric.Saxe@Sun.COM * cpupm_cs_idle_save_tunable is how long we must stay in a deeper C-state 1058906SEric.Saxe@Sun.COM * before it is worth going there. Expressed as a multiple of latency. 1068906SEric.Saxe@Sun.COM */ 1078906SEric.Saxe@Sun.COM uint32_t cpupm_cs_sample_tunable = 5; /* samples in decision period */ 1088906SEric.Saxe@Sun.COM uint32_t cpupm_cs_idle_cost_tunable = 10; /* work time / latency cost */ 1098906SEric.Saxe@Sun.COM uint32_t cpupm_cs_idle_save_tunable = 2; /* idle power savings */ 1108906SEric.Saxe@Sun.COM uint16_t cpupm_C2_idle_pct_tunable = 70; 1118906SEric.Saxe@Sun.COM uint16_t cpupm_C3_idle_pct_tunable = 80; 1128906SEric.Saxe@Sun.COM 1138906SEric.Saxe@Sun.COM #ifndef __xpv 1148906SEric.Saxe@Sun.COM extern boolean_t cpupm_intel_init(cpu_t *); 1158906SEric.Saxe@Sun.COM extern boolean_t cpupm_amd_init(cpu_t *); 1168906SEric.Saxe@Sun.COM 1178906SEric.Saxe@Sun.COM typedef struct cpupm_vendor { 1188906SEric.Saxe@Sun.COM boolean_t (*cpuv_init)(cpu_t *); 1198906SEric.Saxe@Sun.COM } cpupm_vendor_t; 1208906SEric.Saxe@Sun.COM 1218906SEric.Saxe@Sun.COM /* 1228906SEric.Saxe@Sun.COM * Table of supported vendors. 1238906SEric.Saxe@Sun.COM */ 1248906SEric.Saxe@Sun.COM static cpupm_vendor_t cpupm_vendors[] = { 1258906SEric.Saxe@Sun.COM cpupm_intel_init, 1268906SEric.Saxe@Sun.COM cpupm_amd_init, 1278906SEric.Saxe@Sun.COM NULL 1288906SEric.Saxe@Sun.COM }; 1298906SEric.Saxe@Sun.COM #endif 1308906SEric.Saxe@Sun.COM 1318906SEric.Saxe@Sun.COM /* 1328906SEric.Saxe@Sun.COM * Initialize the machine. 1338906SEric.Saxe@Sun.COM * See if a module exists for managing power for this CPU. 1348906SEric.Saxe@Sun.COM */ 1358906SEric.Saxe@Sun.COM /*ARGSUSED*/ 1368906SEric.Saxe@Sun.COM void 1378906SEric.Saxe@Sun.COM cpupm_init(cpu_t *cp) 1388906SEric.Saxe@Sun.COM { 1398906SEric.Saxe@Sun.COM #ifndef __xpv 1408906SEric.Saxe@Sun.COM cpupm_vendor_t *vendors; 1418906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state; 1428906SEric.Saxe@Sun.COM struct machcpu *mcpu = &(cp->cpu_m); 1438906SEric.Saxe@Sun.COM int *speeds; 1448906SEric.Saxe@Sun.COM uint_t nspeeds; 1458906SEric.Saxe@Sun.COM int ret; 1468906SEric.Saxe@Sun.COM 1478906SEric.Saxe@Sun.COM cpupm_set_supp_freqs(cp, NULL, 1); 1488906SEric.Saxe@Sun.COM 1498906SEric.Saxe@Sun.COM mach_state = cp->cpu_m.mcpu_pm_mach_state = 1508906SEric.Saxe@Sun.COM kmem_zalloc(sizeof (cpupm_mach_state_t), KM_SLEEP); 1518906SEric.Saxe@Sun.COM mach_state->ms_caps = CPUPM_NO_STATES; 1528906SEric.Saxe@Sun.COM mutex_init(&mach_state->ms_lock, NULL, MUTEX_DRIVER, NULL); 1538906SEric.Saxe@Sun.COM 1548906SEric.Saxe@Sun.COM mach_state->ms_acpi_handle = cpu_acpi_init(cp); 1558906SEric.Saxe@Sun.COM if (mach_state->ms_acpi_handle == NULL) { 1568906SEric.Saxe@Sun.COM cpupm_free(cp); 1578906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "!cpupm_init: processor %d: " 1588906SEric.Saxe@Sun.COM "unable to get ACPI handle", cp->cpu_id); 1598906SEric.Saxe@Sun.COM cmn_err(CE_NOTE, "!CPU power management will not function."); 1608906SEric.Saxe@Sun.COM CPUPM_DISABLE(); 1618906SEric.Saxe@Sun.COM return; 1628906SEric.Saxe@Sun.COM } 1638906SEric.Saxe@Sun.COM 1648906SEric.Saxe@Sun.COM /* 1658906SEric.Saxe@Sun.COM * Loop through the CPU management module table and see if 1668906SEric.Saxe@Sun.COM * any of the modules implement CPU power management 1678906SEric.Saxe@Sun.COM * for this CPU. 1688906SEric.Saxe@Sun.COM */ 1698906SEric.Saxe@Sun.COM for (vendors = cpupm_vendors; vendors->cpuv_init != NULL; vendors++) { 1708906SEric.Saxe@Sun.COM if (vendors->cpuv_init(cp)) 1718906SEric.Saxe@Sun.COM break; 1728906SEric.Saxe@Sun.COM } 1738906SEric.Saxe@Sun.COM 1748906SEric.Saxe@Sun.COM /* 1758906SEric.Saxe@Sun.COM * Nope, we can't power manage this CPU. 1768906SEric.Saxe@Sun.COM */ 1778906SEric.Saxe@Sun.COM if (vendors == NULL) { 1788906SEric.Saxe@Sun.COM cpupm_free(cp); 1798906SEric.Saxe@Sun.COM CPUPM_DISABLE(); 1808906SEric.Saxe@Sun.COM return; 1818906SEric.Saxe@Sun.COM } 1828906SEric.Saxe@Sun.COM 1838906SEric.Saxe@Sun.COM /* 1848906SEric.Saxe@Sun.COM * If P-state support exists for this system, then initialize it. 1858906SEric.Saxe@Sun.COM */ 1868906SEric.Saxe@Sun.COM if (mach_state->ms_pstate.cma_ops != NULL) { 1878906SEric.Saxe@Sun.COM ret = mach_state->ms_pstate.cma_ops->cpus_init(cp); 1888906SEric.Saxe@Sun.COM if (ret != 0) { 1898906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "!cpupm_init: processor %d:" 1908906SEric.Saxe@Sun.COM " unable to initialize P-state support", 1918906SEric.Saxe@Sun.COM cp->cpu_id); 1928906SEric.Saxe@Sun.COM mach_state->ms_pstate.cma_ops = NULL; 1938906SEric.Saxe@Sun.COM cpupm_disable(CPUPM_P_STATES); 1948906SEric.Saxe@Sun.COM } else { 1958906SEric.Saxe@Sun.COM nspeeds = cpupm_get_speeds(cp, &speeds); 1968906SEric.Saxe@Sun.COM if (nspeeds == 0) { 1978906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "!cpupm_init: processor %d:" 1988906SEric.Saxe@Sun.COM " no speeds to manage", cp->cpu_id); 1998906SEric.Saxe@Sun.COM } else { 2008906SEric.Saxe@Sun.COM cpupm_set_supp_freqs(cp, speeds, nspeeds); 2018906SEric.Saxe@Sun.COM cpupm_free_speeds(speeds, nspeeds); 2028906SEric.Saxe@Sun.COM mach_state->ms_caps |= CPUPM_P_STATES; 2038906SEric.Saxe@Sun.COM } 2048906SEric.Saxe@Sun.COM } 2058906SEric.Saxe@Sun.COM } 2068906SEric.Saxe@Sun.COM 2078906SEric.Saxe@Sun.COM if (mach_state->ms_tstate.cma_ops != NULL) { 2088906SEric.Saxe@Sun.COM ret = mach_state->ms_tstate.cma_ops->cpus_init(cp); 2098906SEric.Saxe@Sun.COM if (ret != 0) { 2108906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "!cpupm_init: processor %d:" 2118906SEric.Saxe@Sun.COM " unable to initialize T-state support", 2128906SEric.Saxe@Sun.COM cp->cpu_id); 2138906SEric.Saxe@Sun.COM mach_state->ms_tstate.cma_ops = NULL; 2148906SEric.Saxe@Sun.COM cpupm_disable(CPUPM_T_STATES); 2158906SEric.Saxe@Sun.COM } else { 2168906SEric.Saxe@Sun.COM mach_state->ms_caps |= CPUPM_T_STATES; 2178906SEric.Saxe@Sun.COM } 2188906SEric.Saxe@Sun.COM } 2198906SEric.Saxe@Sun.COM 2208906SEric.Saxe@Sun.COM /* 2218906SEric.Saxe@Sun.COM * If C-states support exists for this system, then initialize it. 2228906SEric.Saxe@Sun.COM */ 2238906SEric.Saxe@Sun.COM if (mach_state->ms_cstate.cma_ops != NULL) { 2248906SEric.Saxe@Sun.COM ret = mach_state->ms_cstate.cma_ops->cpus_init(cp); 2258906SEric.Saxe@Sun.COM if (ret != 0) { 2268906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "!cpupm_init: processor %d:" 2278906SEric.Saxe@Sun.COM " unable to initialize C-state support", 2288906SEric.Saxe@Sun.COM cp->cpu_id); 2298906SEric.Saxe@Sun.COM mach_state->ms_cstate.cma_ops = NULL; 2308906SEric.Saxe@Sun.COM mcpu->max_cstates = CPU_ACPI_C1; 2318906SEric.Saxe@Sun.COM cpupm_disable(CPUPM_C_STATES); 2328906SEric.Saxe@Sun.COM idle_cpu = non_deep_idle_cpu; 2338906SEric.Saxe@Sun.COM disp_enq_thread = non_deep_idle_disp_enq_thread; 2348906SEric.Saxe@Sun.COM } else if (cpu_deep_cstates_supported()) { 2358906SEric.Saxe@Sun.COM mcpu->max_cstates = cpu_acpi_get_max_cstates( 2368906SEric.Saxe@Sun.COM mach_state->ms_acpi_handle); 2378906SEric.Saxe@Sun.COM if (mcpu->max_cstates > CPU_ACPI_C1) { 2388906SEric.Saxe@Sun.COM hpet.callback(CST_EVENT_MULTIPLE_CSTATES); 2398906SEric.Saxe@Sun.COM CPU->cpu_m.mcpu_idle_cpu = cpu_acpi_idle; 2408906SEric.Saxe@Sun.COM mcpu->mcpu_idle_type = CPU_ACPI_C1; 2418906SEric.Saxe@Sun.COM disp_enq_thread = cstate_wakeup; 2428906SEric.Saxe@Sun.COM } else { 2438906SEric.Saxe@Sun.COM hpet.callback(CST_EVENT_ONE_CSTATE); 2448906SEric.Saxe@Sun.COM } 2458906SEric.Saxe@Sun.COM mach_state->ms_caps |= CPUPM_C_STATES; 2468906SEric.Saxe@Sun.COM } else { 2478906SEric.Saxe@Sun.COM mcpu->max_cstates = CPU_ACPI_C1; 2488906SEric.Saxe@Sun.COM idle_cpu = non_deep_idle_cpu; 2498906SEric.Saxe@Sun.COM disp_enq_thread = non_deep_idle_disp_enq_thread; 2508906SEric.Saxe@Sun.COM } 2518906SEric.Saxe@Sun.COM } 2528906SEric.Saxe@Sun.COM 2538906SEric.Saxe@Sun.COM 2548906SEric.Saxe@Sun.COM if (mach_state->ms_caps == CPUPM_NO_STATES) { 2558906SEric.Saxe@Sun.COM cpupm_free(cp); 2568906SEric.Saxe@Sun.COM CPUPM_DISABLE(); 2578906SEric.Saxe@Sun.COM return; 2588906SEric.Saxe@Sun.COM } 2598906SEric.Saxe@Sun.COM 2608906SEric.Saxe@Sun.COM if ((mach_state->ms_caps & CPUPM_T_STATES) || 2618906SEric.Saxe@Sun.COM (mach_state->ms_caps & CPUPM_P_STATES) || 2628906SEric.Saxe@Sun.COM (mach_state->ms_caps & CPUPM_C_STATES)) 2638906SEric.Saxe@Sun.COM cpupm_add_notify_handler(cp, cpupm_event_notify_handler, cp); 2648906SEric.Saxe@Sun.COM #endif 2658906SEric.Saxe@Sun.COM } 2668906SEric.Saxe@Sun.COM 2678906SEric.Saxe@Sun.COM /* 2688906SEric.Saxe@Sun.COM * Free any resources allocated by cpupm_init(). 2698906SEric.Saxe@Sun.COM */ 2708906SEric.Saxe@Sun.COM /*ARGSUSED*/ 2718906SEric.Saxe@Sun.COM void 2728906SEric.Saxe@Sun.COM cpupm_free(cpu_t *cp) 2738906SEric.Saxe@Sun.COM { 2748906SEric.Saxe@Sun.COM #ifndef __xpv 2758906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 2768906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state; 2778906SEric.Saxe@Sun.COM 2788906SEric.Saxe@Sun.COM if (mach_state == NULL) 2798906SEric.Saxe@Sun.COM return; 2808906SEric.Saxe@Sun.COM if (mach_state->ms_pstate.cma_ops != NULL) { 2818906SEric.Saxe@Sun.COM mach_state->ms_pstate.cma_ops->cpus_fini(cp); 2828906SEric.Saxe@Sun.COM mach_state->ms_pstate.cma_ops = NULL; 2838906SEric.Saxe@Sun.COM } 2848906SEric.Saxe@Sun.COM 2858906SEric.Saxe@Sun.COM if (mach_state->ms_tstate.cma_ops != NULL) { 2868906SEric.Saxe@Sun.COM mach_state->ms_tstate.cma_ops->cpus_fini(cp); 2878906SEric.Saxe@Sun.COM mach_state->ms_tstate.cma_ops = NULL; 2888906SEric.Saxe@Sun.COM } 2898906SEric.Saxe@Sun.COM 2908906SEric.Saxe@Sun.COM if (mach_state->ms_cstate.cma_ops != NULL) { 2918906SEric.Saxe@Sun.COM mach_state->ms_cstate.cma_ops->cpus_fini(cp); 2928906SEric.Saxe@Sun.COM mach_state->ms_cstate.cma_ops = NULL; 2938906SEric.Saxe@Sun.COM } 2948906SEric.Saxe@Sun.COM 2958906SEric.Saxe@Sun.COM cpupm_free_notify_handlers(cp); 2968906SEric.Saxe@Sun.COM 2978906SEric.Saxe@Sun.COM if (mach_state->ms_acpi_handle != NULL) { 2988906SEric.Saxe@Sun.COM cpu_acpi_fini(mach_state->ms_acpi_handle); 2998906SEric.Saxe@Sun.COM mach_state->ms_acpi_handle = NULL; 3008906SEric.Saxe@Sun.COM } 3018906SEric.Saxe@Sun.COM 3028906SEric.Saxe@Sun.COM mutex_destroy(&mach_state->ms_lock); 3038906SEric.Saxe@Sun.COM kmem_free(mach_state, sizeof (cpupm_mach_state_t)); 3048906SEric.Saxe@Sun.COM cp->cpu_m.mcpu_pm_mach_state = NULL; 3058906SEric.Saxe@Sun.COM #endif 3068906SEric.Saxe@Sun.COM } 3078906SEric.Saxe@Sun.COM 3088906SEric.Saxe@Sun.COM /* 3098906SEric.Saxe@Sun.COM * If all CPUs have started and at least one power state is manageable, 3108906SEric.Saxe@Sun.COM * then the CPUs are ready for power management. 3118906SEric.Saxe@Sun.COM */ 3128906SEric.Saxe@Sun.COM boolean_t 3138906SEric.Saxe@Sun.COM cpupm_is_ready() 3148906SEric.Saxe@Sun.COM { 3158906SEric.Saxe@Sun.COM #ifndef __xpv 3168906SEric.Saxe@Sun.COM if (cpupm_enabled == CPUPM_NO_STATES) 3178906SEric.Saxe@Sun.COM return (B_FALSE); 3188906SEric.Saxe@Sun.COM return (cpupm_ready); 3198906SEric.Saxe@Sun.COM #else 3208906SEric.Saxe@Sun.COM return (B_FALSE); 3218906SEric.Saxe@Sun.COM #endif 3228906SEric.Saxe@Sun.COM 3238906SEric.Saxe@Sun.COM } 3248906SEric.Saxe@Sun.COM 3258906SEric.Saxe@Sun.COM boolean_t 3268906SEric.Saxe@Sun.COM cpupm_is_enabled(uint32_t state) 3278906SEric.Saxe@Sun.COM { 3288906SEric.Saxe@Sun.COM return ((cpupm_enabled & state) == state); 3298906SEric.Saxe@Sun.COM } 3308906SEric.Saxe@Sun.COM 3318906SEric.Saxe@Sun.COM /* 3328906SEric.Saxe@Sun.COM * By default, all states are enabled. 3338906SEric.Saxe@Sun.COM */ 3348906SEric.Saxe@Sun.COM void 3358906SEric.Saxe@Sun.COM cpupm_disable(uint32_t state) 3368906SEric.Saxe@Sun.COM { 3378906SEric.Saxe@Sun.COM 3388906SEric.Saxe@Sun.COM if (state & CPUPM_P_STATES) { 3398906SEric.Saxe@Sun.COM cpupm_free_domains(&cpupm_pstate_domains); 3408906SEric.Saxe@Sun.COM } 3418906SEric.Saxe@Sun.COM if (state & CPUPM_T_STATES) { 3428906SEric.Saxe@Sun.COM cpupm_free_domains(&cpupm_tstate_domains); 3438906SEric.Saxe@Sun.COM } 3448906SEric.Saxe@Sun.COM if (state & CPUPM_C_STATES) { 3458906SEric.Saxe@Sun.COM cpupm_free_domains(&cpupm_cstate_domains); 3468906SEric.Saxe@Sun.COM } 3478906SEric.Saxe@Sun.COM cpupm_enabled &= ~state; 3488906SEric.Saxe@Sun.COM } 3498906SEric.Saxe@Sun.COM 3508906SEric.Saxe@Sun.COM /* 3518906SEric.Saxe@Sun.COM * Once all CPUs have been started, the PPM driver should build CPU 3528906SEric.Saxe@Sun.COM * domains and initialize the topspeed for all CPU devices. 3538906SEric.Saxe@Sun.COM */ 3548906SEric.Saxe@Sun.COM void 3558906SEric.Saxe@Sun.COM cpupm_post_startup() 3568906SEric.Saxe@Sun.COM { 3578906SEric.Saxe@Sun.COM #ifndef __xpv 3588906SEric.Saxe@Sun.COM /* 3598906SEric.Saxe@Sun.COM * The CPU domain built by the PPM during CPUs attaching 3608906SEric.Saxe@Sun.COM * should be rebuilt with the information retrieved from 3618906SEric.Saxe@Sun.COM * ACPI. 3628906SEric.Saxe@Sun.COM */ 3638906SEric.Saxe@Sun.COM if (cpupm_rebuild_cpu_domains != NULL) 3648906SEric.Saxe@Sun.COM (*cpupm_rebuild_cpu_domains)(); 3658906SEric.Saxe@Sun.COM 3668906SEric.Saxe@Sun.COM /* 3678906SEric.Saxe@Sun.COM * Only initialize the topspeed if P-states are enabled. 3688906SEric.Saxe@Sun.COM */ 3698906SEric.Saxe@Sun.COM if (cpupm_enabled & CPUPM_P_STATES && cpupm_init_topspeed != NULL) 3708906SEric.Saxe@Sun.COM (*cpupm_init_topspeed)(); 3718906SEric.Saxe@Sun.COM #endif 3728906SEric.Saxe@Sun.COM cpupm_ready = B_TRUE; 3738906SEric.Saxe@Sun.COM } 3748906SEric.Saxe@Sun.COM 3758906SEric.Saxe@Sun.COM /* 3768906SEric.Saxe@Sun.COM * Allocate power domains for C,P and T States 3778906SEric.Saxe@Sun.COM */ 3788906SEric.Saxe@Sun.COM void 3798906SEric.Saxe@Sun.COM cpupm_alloc_domains(cpu_t *cp, int state) 3808906SEric.Saxe@Sun.COM { 3818906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 3828906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state); 3838906SEric.Saxe@Sun.COM cpu_acpi_handle_t handle = mach_state->ms_acpi_handle; 3848906SEric.Saxe@Sun.COM cpupm_state_domains_t **dom_ptr; 3858906SEric.Saxe@Sun.COM cpupm_state_domains_t *dptr; 3868906SEric.Saxe@Sun.COM cpupm_state_domains_t **mach_dom_state_ptr; 3878906SEric.Saxe@Sun.COM uint32_t domain; 3888906SEric.Saxe@Sun.COM uint32_t type; 3898906SEric.Saxe@Sun.COM 3908906SEric.Saxe@Sun.COM switch (state) { 3918906SEric.Saxe@Sun.COM case CPUPM_P_STATES: 3928906SEric.Saxe@Sun.COM if (CPU_ACPI_IS_OBJ_CACHED(handle, CPU_ACPI_PSD_CACHED)) { 3938906SEric.Saxe@Sun.COM domain = CPU_ACPI_PSD(handle).sd_domain; 3948906SEric.Saxe@Sun.COM type = CPU_ACPI_PSD(handle).sd_type; 3958906SEric.Saxe@Sun.COM } else { 3968906SEric.Saxe@Sun.COM mutex_enter(&cpu_lock); 3978906SEric.Saxe@Sun.COM domain = cpuid_get_chipid(cp); 3988906SEric.Saxe@Sun.COM mutex_exit(&cpu_lock); 3998906SEric.Saxe@Sun.COM type = CPU_ACPI_HW_ALL; 4008906SEric.Saxe@Sun.COM } 4018906SEric.Saxe@Sun.COM dom_ptr = &cpupm_pstate_domains; 4028906SEric.Saxe@Sun.COM mach_dom_state_ptr = &mach_state->ms_pstate.cma_domain; 4038906SEric.Saxe@Sun.COM break; 4048906SEric.Saxe@Sun.COM case CPUPM_T_STATES: 4058906SEric.Saxe@Sun.COM if (CPU_ACPI_IS_OBJ_CACHED(handle, CPU_ACPI_TSD_CACHED)) { 4068906SEric.Saxe@Sun.COM domain = CPU_ACPI_TSD(handle).sd_domain; 4078906SEric.Saxe@Sun.COM type = CPU_ACPI_TSD(handle).sd_type; 4088906SEric.Saxe@Sun.COM } else { 4098906SEric.Saxe@Sun.COM mutex_enter(&cpu_lock); 4108906SEric.Saxe@Sun.COM domain = cpuid_get_chipid(cp); 4118906SEric.Saxe@Sun.COM mutex_exit(&cpu_lock); 4128906SEric.Saxe@Sun.COM type = CPU_ACPI_HW_ALL; 4138906SEric.Saxe@Sun.COM } 4148906SEric.Saxe@Sun.COM dom_ptr = &cpupm_tstate_domains; 4158906SEric.Saxe@Sun.COM mach_dom_state_ptr = &mach_state->ms_tstate.cma_domain; 4168906SEric.Saxe@Sun.COM break; 4178906SEric.Saxe@Sun.COM case CPUPM_C_STATES: 4188906SEric.Saxe@Sun.COM if (CPU_ACPI_IS_OBJ_CACHED(handle, CPU_ACPI_CSD_CACHED)) { 4198906SEric.Saxe@Sun.COM domain = CPU_ACPI_CSD(handle).sd_domain; 4208906SEric.Saxe@Sun.COM type = CPU_ACPI_CSD(handle).sd_type; 4218906SEric.Saxe@Sun.COM } else { 4228906SEric.Saxe@Sun.COM mutex_enter(&cpu_lock); 4238906SEric.Saxe@Sun.COM domain = cpuid_get_coreid(cp); 4248906SEric.Saxe@Sun.COM mutex_exit(&cpu_lock); 4258906SEric.Saxe@Sun.COM type = CPU_ACPI_HW_ALL; 4268906SEric.Saxe@Sun.COM } 4278906SEric.Saxe@Sun.COM dom_ptr = &cpupm_cstate_domains; 4288906SEric.Saxe@Sun.COM mach_dom_state_ptr = &mach_state->ms_cstate.cma_domain; 4298906SEric.Saxe@Sun.COM break; 4308906SEric.Saxe@Sun.COM default: 4318906SEric.Saxe@Sun.COM return; 4328906SEric.Saxe@Sun.COM } 4338906SEric.Saxe@Sun.COM 4348906SEric.Saxe@Sun.COM for (dptr = *dom_ptr; dptr != NULL; dptr = dptr->pm_next) { 4358906SEric.Saxe@Sun.COM if (dptr->pm_domain == domain) 4368906SEric.Saxe@Sun.COM break; 4378906SEric.Saxe@Sun.COM } 4388906SEric.Saxe@Sun.COM 4398906SEric.Saxe@Sun.COM /* new domain is created and linked at the head */ 4408906SEric.Saxe@Sun.COM if (dptr == NULL) { 4418906SEric.Saxe@Sun.COM dptr = kmem_zalloc(sizeof (cpupm_state_domains_t), KM_SLEEP); 4428906SEric.Saxe@Sun.COM dptr->pm_domain = domain; 4438906SEric.Saxe@Sun.COM dptr->pm_type = type; 4448906SEric.Saxe@Sun.COM dptr->pm_next = *dom_ptr; 4458906SEric.Saxe@Sun.COM mutex_init(&dptr->pm_lock, NULL, MUTEX_SPIN, 4468906SEric.Saxe@Sun.COM (void *)ipltospl(DISP_LEVEL)); 4478906SEric.Saxe@Sun.COM CPUSET_ZERO(dptr->pm_cpus); 4488906SEric.Saxe@Sun.COM *dom_ptr = dptr; 4498906SEric.Saxe@Sun.COM } 4508906SEric.Saxe@Sun.COM CPUSET_ADD(dptr->pm_cpus, cp->cpu_id); 4518906SEric.Saxe@Sun.COM *mach_dom_state_ptr = dptr; 4528906SEric.Saxe@Sun.COM } 4538906SEric.Saxe@Sun.COM 4548906SEric.Saxe@Sun.COM /* 4558906SEric.Saxe@Sun.COM * Free C, P or T state power domains 4568906SEric.Saxe@Sun.COM */ 4578906SEric.Saxe@Sun.COM void 4588906SEric.Saxe@Sun.COM cpupm_free_domains(cpupm_state_domains_t **dom_ptr) 4598906SEric.Saxe@Sun.COM { 4608906SEric.Saxe@Sun.COM cpupm_state_domains_t *this_domain, *next_domain; 4618906SEric.Saxe@Sun.COM 4628906SEric.Saxe@Sun.COM this_domain = *dom_ptr; 4638906SEric.Saxe@Sun.COM while (this_domain != NULL) { 4648906SEric.Saxe@Sun.COM next_domain = this_domain->pm_next; 4658906SEric.Saxe@Sun.COM mutex_destroy(&this_domain->pm_lock); 4668906SEric.Saxe@Sun.COM kmem_free((void *)this_domain, 4678906SEric.Saxe@Sun.COM sizeof (cpupm_state_domains_t)); 4688906SEric.Saxe@Sun.COM this_domain = next_domain; 4698906SEric.Saxe@Sun.COM } 4708906SEric.Saxe@Sun.COM *dom_ptr = NULL; 4718906SEric.Saxe@Sun.COM } 4728906SEric.Saxe@Sun.COM 4738906SEric.Saxe@Sun.COM void 4748906SEric.Saxe@Sun.COM cpupm_alloc_ms_cstate(cpu_t *cp) 4758906SEric.Saxe@Sun.COM { 4768906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state; 4778906SEric.Saxe@Sun.COM cpupm_mach_acpi_state_t *ms_cstate; 4788906SEric.Saxe@Sun.COM 4798906SEric.Saxe@Sun.COM mach_state = (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state); 4808906SEric.Saxe@Sun.COM ms_cstate = &mach_state->ms_cstate; 4818906SEric.Saxe@Sun.COM ASSERT(ms_cstate->cma_state.cstate == NULL); 4828906SEric.Saxe@Sun.COM ms_cstate->cma_state.cstate = kmem_zalloc(sizeof (cma_c_state_t), 4838906SEric.Saxe@Sun.COM KM_SLEEP); 4848906SEric.Saxe@Sun.COM ms_cstate->cma_state.cstate->cs_next_cstate = CPU_ACPI_C1; 4858906SEric.Saxe@Sun.COM } 4868906SEric.Saxe@Sun.COM 4878906SEric.Saxe@Sun.COM void 4888906SEric.Saxe@Sun.COM cpupm_free_ms_cstate(cpu_t *cp) 4898906SEric.Saxe@Sun.COM { 4908906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 4918906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state); 4928906SEric.Saxe@Sun.COM cpupm_mach_acpi_state_t *ms_cstate = &mach_state->ms_cstate; 4938906SEric.Saxe@Sun.COM 4948906SEric.Saxe@Sun.COM if (ms_cstate->cma_state.cstate != NULL) { 4958906SEric.Saxe@Sun.COM kmem_free(ms_cstate->cma_state.cstate, sizeof (cma_c_state_t)); 4968906SEric.Saxe@Sun.COM ms_cstate->cma_state.cstate = NULL; 4978906SEric.Saxe@Sun.COM } 4988906SEric.Saxe@Sun.COM } 4998906SEric.Saxe@Sun.COM 5008906SEric.Saxe@Sun.COM void 5018906SEric.Saxe@Sun.COM cpupm_state_change(cpu_t *cp, int level, int state) 5028906SEric.Saxe@Sun.COM { 5038906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 5048906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state); 5058906SEric.Saxe@Sun.COM cpupm_state_ops_t *state_ops; 5068906SEric.Saxe@Sun.COM cpupm_state_domains_t *state_domain; 5078906SEric.Saxe@Sun.COM cpuset_t set; 5088906SEric.Saxe@Sun.COM 5098906SEric.Saxe@Sun.COM DTRACE_PROBE2(cpupm__state__change, cpu_t *, cp, int, level); 5108906SEric.Saxe@Sun.COM 5118906SEric.Saxe@Sun.COM if (mach_state == NULL) { 5128906SEric.Saxe@Sun.COM return; 5138906SEric.Saxe@Sun.COM } 5148906SEric.Saxe@Sun.COM 5158906SEric.Saxe@Sun.COM switch (state) { 5168906SEric.Saxe@Sun.COM case CPUPM_P_STATES: 5178906SEric.Saxe@Sun.COM state_ops = mach_state->ms_pstate.cma_ops; 5188906SEric.Saxe@Sun.COM state_domain = mach_state->ms_pstate.cma_domain; 5198906SEric.Saxe@Sun.COM break; 5208906SEric.Saxe@Sun.COM case CPUPM_T_STATES: 5218906SEric.Saxe@Sun.COM state_ops = mach_state->ms_tstate.cma_ops; 5228906SEric.Saxe@Sun.COM state_domain = mach_state->ms_tstate.cma_domain; 5238906SEric.Saxe@Sun.COM break; 5248906SEric.Saxe@Sun.COM default: 5258906SEric.Saxe@Sun.COM break; 5268906SEric.Saxe@Sun.COM } 5278906SEric.Saxe@Sun.COM 5288906SEric.Saxe@Sun.COM switch (state_domain->pm_type) { 5298906SEric.Saxe@Sun.COM case CPU_ACPI_SW_ANY: 5308906SEric.Saxe@Sun.COM /* 5318906SEric.Saxe@Sun.COM * A request on any CPU in the domain transitions the domain 5328906SEric.Saxe@Sun.COM */ 5338906SEric.Saxe@Sun.COM CPUSET_ONLY(set, cp->cpu_id); 5348906SEric.Saxe@Sun.COM state_ops->cpus_change(set, level); 5358906SEric.Saxe@Sun.COM break; 5368906SEric.Saxe@Sun.COM case CPU_ACPI_SW_ALL: 5378906SEric.Saxe@Sun.COM /* 5388906SEric.Saxe@Sun.COM * All CPUs in the domain must request the transition 5398906SEric.Saxe@Sun.COM */ 5408906SEric.Saxe@Sun.COM case CPU_ACPI_HW_ALL: 5418906SEric.Saxe@Sun.COM /* 5428906SEric.Saxe@Sun.COM * P/T-state transitions are coordinated by the hardware 5438906SEric.Saxe@Sun.COM * For now, request the transition on all CPUs in the domain, 5448906SEric.Saxe@Sun.COM * but looking ahead we can probably be smarter about this. 5458906SEric.Saxe@Sun.COM */ 5468906SEric.Saxe@Sun.COM mutex_enter(&state_domain->pm_lock); 5478906SEric.Saxe@Sun.COM state_ops->cpus_change(state_domain->pm_cpus, level); 5488906SEric.Saxe@Sun.COM mutex_exit(&state_domain->pm_lock); 5498906SEric.Saxe@Sun.COM break; 5508906SEric.Saxe@Sun.COM default: 5518906SEric.Saxe@Sun.COM cmn_err(CE_WARN, "Unknown domain coordination type: %d", 5528906SEric.Saxe@Sun.COM state_domain->pm_type); 5538906SEric.Saxe@Sun.COM } 5548906SEric.Saxe@Sun.COM } 5558906SEric.Saxe@Sun.COM 5568906SEric.Saxe@Sun.COM /* 5578906SEric.Saxe@Sun.COM * CPU PM interfaces exposed to the CPU power manager 5588906SEric.Saxe@Sun.COM */ 5598906SEric.Saxe@Sun.COM /*ARGSUSED*/ 5608906SEric.Saxe@Sun.COM id_t 5618906SEric.Saxe@Sun.COM cpupm_plat_domain_id(cpu_t *cp, cpupm_dtype_t type) 5628906SEric.Saxe@Sun.COM { 5638906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 5648906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state); 5658906SEric.Saxe@Sun.COM 5668906SEric.Saxe@Sun.COM if ((mach_state == NULL) || (!cpupm_is_enabled(CPUPM_P_STATES) && 5678906SEric.Saxe@Sun.COM !cpupm_is_enabled(CPUPM_C_STATES))) { 5688906SEric.Saxe@Sun.COM return (CPUPM_NO_DOMAIN); 5698906SEric.Saxe@Sun.COM } 5708906SEric.Saxe@Sun.COM if (type == CPUPM_DTYPE_ACTIVE) { 5718906SEric.Saxe@Sun.COM /* 5728906SEric.Saxe@Sun.COM * Return P-State domain for the specified CPU 5738906SEric.Saxe@Sun.COM */ 5748906SEric.Saxe@Sun.COM if (mach_state->ms_pstate.cma_domain) { 5758906SEric.Saxe@Sun.COM return (mach_state->ms_pstate.cma_domain->pm_domain); 5768906SEric.Saxe@Sun.COM } 5778906SEric.Saxe@Sun.COM } else if (type == CPUPM_DTYPE_IDLE) { 5788906SEric.Saxe@Sun.COM /* 5798906SEric.Saxe@Sun.COM * Return C-State domain for the specified CPU 5808906SEric.Saxe@Sun.COM */ 5818906SEric.Saxe@Sun.COM if (mach_state->ms_cstate.cma_domain) { 5828906SEric.Saxe@Sun.COM return (mach_state->ms_cstate.cma_domain->pm_domain); 5838906SEric.Saxe@Sun.COM } 5848906SEric.Saxe@Sun.COM } 5858906SEric.Saxe@Sun.COM return (CPUPM_NO_DOMAIN); 5868906SEric.Saxe@Sun.COM } 5878906SEric.Saxe@Sun.COM 5888906SEric.Saxe@Sun.COM /*ARGSUSED*/ 5898906SEric.Saxe@Sun.COM uint_t 5908906SEric.Saxe@Sun.COM cpupm_plat_state_enumerate(cpu_t *cp, cpupm_dtype_t type, 5918906SEric.Saxe@Sun.COM cpupm_state_t *states) 5928906SEric.Saxe@Sun.COM { 5938906SEric.Saxe@Sun.COM int *speeds; 5948906SEric.Saxe@Sun.COM uint_t nspeeds, i; 5958906SEric.Saxe@Sun.COM 5968906SEric.Saxe@Sun.COM /* 5978906SEric.Saxe@Sun.COM * Idle domain support unimplemented 5988906SEric.Saxe@Sun.COM */ 5998906SEric.Saxe@Sun.COM if (type != CPUPM_DTYPE_ACTIVE) { 6008906SEric.Saxe@Sun.COM return (0); 6018906SEric.Saxe@Sun.COM } 6028906SEric.Saxe@Sun.COM nspeeds = cpupm_get_speeds(cp, &speeds); 6038906SEric.Saxe@Sun.COM 6048906SEric.Saxe@Sun.COM /* 6058906SEric.Saxe@Sun.COM * If the caller passes NULL for states, just return the 6068906SEric.Saxe@Sun.COM * number of states. 6078906SEric.Saxe@Sun.COM */ 6088906SEric.Saxe@Sun.COM if (states != NULL) { 6098906SEric.Saxe@Sun.COM for (i = 0; i < nspeeds; i++) { 6108906SEric.Saxe@Sun.COM states[i].cps_speed = speeds[i]; 6118906SEric.Saxe@Sun.COM states[i].cps_handle = (cpupm_handle_t)i; 6128906SEric.Saxe@Sun.COM } 6138906SEric.Saxe@Sun.COM } 6148906SEric.Saxe@Sun.COM cpupm_free_speeds(speeds, nspeeds); 6158906SEric.Saxe@Sun.COM return (nspeeds); 6168906SEric.Saxe@Sun.COM } 6178906SEric.Saxe@Sun.COM 6188906SEric.Saxe@Sun.COM /*ARGSUSED*/ 6198906SEric.Saxe@Sun.COM int 6208906SEric.Saxe@Sun.COM cpupm_plat_change_state(cpu_t *cp, cpupm_state_t *state) 6218906SEric.Saxe@Sun.COM { 6228906SEric.Saxe@Sun.COM if (!cpupm_is_ready()) 6238906SEric.Saxe@Sun.COM return (-1); 6248906SEric.Saxe@Sun.COM 6258906SEric.Saxe@Sun.COM cpupm_state_change(cp, (int)state->cps_handle, CPUPM_P_STATES); 6268906SEric.Saxe@Sun.COM 6278906SEric.Saxe@Sun.COM return (0); 6288906SEric.Saxe@Sun.COM } 6298906SEric.Saxe@Sun.COM 6308906SEric.Saxe@Sun.COM /*ARGSUSED*/ 6318906SEric.Saxe@Sun.COM /* 6328906SEric.Saxe@Sun.COM * Note: It is the responsibility of the users of 6338906SEric.Saxe@Sun.COM * cpupm_get_speeds() to free the memory allocated 6348906SEric.Saxe@Sun.COM * for speeds using cpupm_free_speeds() 6358906SEric.Saxe@Sun.COM */ 6368906SEric.Saxe@Sun.COM uint_t 6378906SEric.Saxe@Sun.COM cpupm_get_speeds(cpu_t *cp, int **speeds) 6388906SEric.Saxe@Sun.COM { 6398906SEric.Saxe@Sun.COM #ifndef __xpv 6408906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 6418906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state; 6428906SEric.Saxe@Sun.COM return (cpu_acpi_get_speeds(mach_state->ms_acpi_handle, speeds)); 6438906SEric.Saxe@Sun.COM #else 6448906SEric.Saxe@Sun.COM return (0); 6458906SEric.Saxe@Sun.COM #endif 6468906SEric.Saxe@Sun.COM } 6478906SEric.Saxe@Sun.COM 6488906SEric.Saxe@Sun.COM /*ARGSUSED*/ 6498906SEric.Saxe@Sun.COM void 6508906SEric.Saxe@Sun.COM cpupm_free_speeds(int *speeds, uint_t nspeeds) 6518906SEric.Saxe@Sun.COM { 6528906SEric.Saxe@Sun.COM #ifndef __xpv 6538906SEric.Saxe@Sun.COM cpu_acpi_free_speeds(speeds, nspeeds); 6548906SEric.Saxe@Sun.COM #endif 6558906SEric.Saxe@Sun.COM } 6568906SEric.Saxe@Sun.COM 6578906SEric.Saxe@Sun.COM /* 6588906SEric.Saxe@Sun.COM * All CPU instances have been initialized successfully. 6598906SEric.Saxe@Sun.COM */ 6608906SEric.Saxe@Sun.COM boolean_t 6618906SEric.Saxe@Sun.COM cpupm_power_ready(void) 6628906SEric.Saxe@Sun.COM { 6638906SEric.Saxe@Sun.COM return (cpupm_is_enabled(CPUPM_P_STATES) && cpupm_is_ready()); 6648906SEric.Saxe@Sun.COM } 6658906SEric.Saxe@Sun.COM 6668906SEric.Saxe@Sun.COM /* 6678906SEric.Saxe@Sun.COM * All CPU instances have been initialized successfully. 6688906SEric.Saxe@Sun.COM */ 6698906SEric.Saxe@Sun.COM boolean_t 6708906SEric.Saxe@Sun.COM cpupm_throttle_ready(void) 6718906SEric.Saxe@Sun.COM { 6728906SEric.Saxe@Sun.COM return (cpupm_is_enabled(CPUPM_T_STATES) && cpupm_is_ready()); 6738906SEric.Saxe@Sun.COM } 6748906SEric.Saxe@Sun.COM 6758906SEric.Saxe@Sun.COM /* 6768906SEric.Saxe@Sun.COM * All CPU instances have been initialized successfully. 6778906SEric.Saxe@Sun.COM */ 6788906SEric.Saxe@Sun.COM boolean_t 6798906SEric.Saxe@Sun.COM cpupm_cstate_ready(void) 6808906SEric.Saxe@Sun.COM { 6818906SEric.Saxe@Sun.COM return (cpupm_is_enabled(CPUPM_C_STATES) && cpupm_is_ready()); 6828906SEric.Saxe@Sun.COM } 6838906SEric.Saxe@Sun.COM 6848906SEric.Saxe@Sun.COM void 6858906SEric.Saxe@Sun.COM cpupm_notify_handler(ACPI_HANDLE obj, UINT32 val, void *ctx) 6868906SEric.Saxe@Sun.COM { 6878906SEric.Saxe@Sun.COM cpu_t *cp = ctx; 6888906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 6898906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state); 6908906SEric.Saxe@Sun.COM cpupm_notification_t *entry; 6918906SEric.Saxe@Sun.COM 6928906SEric.Saxe@Sun.COM mutex_enter(&mach_state->ms_lock); 6938906SEric.Saxe@Sun.COM for (entry = mach_state->ms_handlers; entry != NULL; 6948906SEric.Saxe@Sun.COM entry = entry->nq_next) { 6958906SEric.Saxe@Sun.COM entry->nq_handler(obj, val, entry->nq_ctx); 6968906SEric.Saxe@Sun.COM } 6978906SEric.Saxe@Sun.COM mutex_exit(&mach_state->ms_lock); 6988906SEric.Saxe@Sun.COM } 6998906SEric.Saxe@Sun.COM 7008906SEric.Saxe@Sun.COM /*ARGSUSED*/ 7018906SEric.Saxe@Sun.COM void 7028906SEric.Saxe@Sun.COM cpupm_add_notify_handler(cpu_t *cp, CPUPM_NOTIFY_HANDLER handler, void *ctx) 7038906SEric.Saxe@Sun.COM { 7048906SEric.Saxe@Sun.COM #ifndef __xpv 7058906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 7068906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state; 7078906SEric.Saxe@Sun.COM cpupm_notification_t *entry; 7088906SEric.Saxe@Sun.COM 7098906SEric.Saxe@Sun.COM entry = kmem_zalloc(sizeof (cpupm_notification_t), KM_SLEEP); 7108906SEric.Saxe@Sun.COM entry->nq_handler = handler; 7118906SEric.Saxe@Sun.COM entry->nq_ctx = ctx; 7128906SEric.Saxe@Sun.COM mutex_enter(&mach_state->ms_lock); 7138906SEric.Saxe@Sun.COM if (mach_state->ms_handlers == NULL) { 7148906SEric.Saxe@Sun.COM entry->nq_next = NULL; 7158906SEric.Saxe@Sun.COM mach_state->ms_handlers = entry; 7168906SEric.Saxe@Sun.COM cpu_acpi_install_notify_handler(mach_state->ms_acpi_handle, 7178906SEric.Saxe@Sun.COM cpupm_notify_handler, cp); 7188906SEric.Saxe@Sun.COM 7198906SEric.Saxe@Sun.COM } else { 7208906SEric.Saxe@Sun.COM entry->nq_next = mach_state->ms_handlers; 7218906SEric.Saxe@Sun.COM mach_state->ms_handlers = entry; 7228906SEric.Saxe@Sun.COM } 7238906SEric.Saxe@Sun.COM mutex_exit(&mach_state->ms_lock); 7248906SEric.Saxe@Sun.COM #endif 7258906SEric.Saxe@Sun.COM } 7268906SEric.Saxe@Sun.COM 7278906SEric.Saxe@Sun.COM /*ARGSUSED*/ 7288906SEric.Saxe@Sun.COM static void 7298906SEric.Saxe@Sun.COM cpupm_free_notify_handlers(cpu_t *cp) 7308906SEric.Saxe@Sun.COM { 7318906SEric.Saxe@Sun.COM #ifndef __xpv 7328906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state = 7338906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state; 7348906SEric.Saxe@Sun.COM cpupm_notification_t *entry; 7358906SEric.Saxe@Sun.COM cpupm_notification_t *next; 7368906SEric.Saxe@Sun.COM 7378906SEric.Saxe@Sun.COM mutex_enter(&mach_state->ms_lock); 7388906SEric.Saxe@Sun.COM if (mach_state->ms_handlers == NULL) { 7398906SEric.Saxe@Sun.COM mutex_exit(&mach_state->ms_lock); 7408906SEric.Saxe@Sun.COM return; 7418906SEric.Saxe@Sun.COM } 7428906SEric.Saxe@Sun.COM if (mach_state->ms_acpi_handle != NULL) { 7438906SEric.Saxe@Sun.COM cpu_acpi_remove_notify_handler(mach_state->ms_acpi_handle, 7448906SEric.Saxe@Sun.COM cpupm_notify_handler); 7458906SEric.Saxe@Sun.COM } 7468906SEric.Saxe@Sun.COM entry = mach_state->ms_handlers; 7478906SEric.Saxe@Sun.COM while (entry != NULL) { 7488906SEric.Saxe@Sun.COM next = entry->nq_next; 7498906SEric.Saxe@Sun.COM kmem_free(entry, sizeof (cpupm_notification_t)); 7508906SEric.Saxe@Sun.COM entry = next; 7518906SEric.Saxe@Sun.COM } 7528906SEric.Saxe@Sun.COM mach_state->ms_handlers = NULL; 7538906SEric.Saxe@Sun.COM mutex_exit(&mach_state->ms_lock); 7548906SEric.Saxe@Sun.COM #endif 7558906SEric.Saxe@Sun.COM } 7568906SEric.Saxe@Sun.COM 7578906SEric.Saxe@Sun.COM /* 7588906SEric.Saxe@Sun.COM * Get the current max speed from the ACPI _PPC object 7598906SEric.Saxe@Sun.COM */ 7608906SEric.Saxe@Sun.COM /*ARGSUSED*/ 7618906SEric.Saxe@Sun.COM int 7628906SEric.Saxe@Sun.COM cpupm_get_top_speed(cpu_t *cp) 7638906SEric.Saxe@Sun.COM { 7648906SEric.Saxe@Sun.COM #ifndef __xpv 7658906SEric.Saxe@Sun.COM cpupm_mach_state_t *mach_state; 7668906SEric.Saxe@Sun.COM cpu_acpi_handle_t handle; 7678906SEric.Saxe@Sun.COM int plat_level; 7688906SEric.Saxe@Sun.COM uint_t nspeeds; 7698906SEric.Saxe@Sun.COM int max_level; 7708906SEric.Saxe@Sun.COM 7718906SEric.Saxe@Sun.COM mach_state = 7728906SEric.Saxe@Sun.COM (cpupm_mach_state_t *)cp->cpu_m.mcpu_pm_mach_state; 7738906SEric.Saxe@Sun.COM handle = mach_state->ms_acpi_handle; 7748906SEric.Saxe@Sun.COM 7758906SEric.Saxe@Sun.COM cpu_acpi_cache_ppc(handle); 7768906SEric.Saxe@Sun.COM plat_level = CPU_ACPI_PPC(handle); 7778906SEric.Saxe@Sun.COM 7788906SEric.Saxe@Sun.COM nspeeds = CPU_ACPI_PSTATES_COUNT(handle); 7798906SEric.Saxe@Sun.COM 7808906SEric.Saxe@Sun.COM max_level = nspeeds - 1; 7818906SEric.Saxe@Sun.COM if ((plat_level < 0) || (plat_level > max_level)) { 7828906SEric.Saxe@Sun.COM cmn_err(CE_NOTE, "!cpupm_get_top_speed: CPU %d: " 7838906SEric.Saxe@Sun.COM "_PPC out of range %d", cp->cpu_id, plat_level); 7848906SEric.Saxe@Sun.COM plat_level = 0; 7858906SEric.Saxe@Sun.COM } 7868906SEric.Saxe@Sun.COM 7878906SEric.Saxe@Sun.COM return (plat_level); 7888906SEric.Saxe@Sun.COM #else 7898906SEric.Saxe@Sun.COM return (0); 7908906SEric.Saxe@Sun.COM #endif 7918906SEric.Saxe@Sun.COM } 7928906SEric.Saxe@Sun.COM 7938906SEric.Saxe@Sun.COM /* 7948906SEric.Saxe@Sun.COM * This notification handler is called whenever the ACPI _PPC 7958906SEric.Saxe@Sun.COM * object changes. The _PPC is a sort of governor on power levels. 7968906SEric.Saxe@Sun.COM * It sets an upper threshold on which, _PSS defined, power levels 7978906SEric.Saxe@Sun.COM * are usuable. The _PPC value is dynamic and may change as properties 7988906SEric.Saxe@Sun.COM * (i.e., thermal or AC source) of the system change. 7998906SEric.Saxe@Sun.COM */ 8008906SEric.Saxe@Sun.COM 8018906SEric.Saxe@Sun.COM static void 8028906SEric.Saxe@Sun.COM cpupm_power_manage_notifications(void *ctx) 8038906SEric.Saxe@Sun.COM { 8048906SEric.Saxe@Sun.COM cpu_t *cp = ctx; 8058906SEric.Saxe@Sun.COM int top_speed; 8068906SEric.Saxe@Sun.COM 8078906SEric.Saxe@Sun.COM top_speed = cpupm_get_top_speed(cp); 8088906SEric.Saxe@Sun.COM cpupm_redefine_max_activepwr_state(cp, top_speed); 8098906SEric.Saxe@Sun.COM } 8108906SEric.Saxe@Sun.COM 8118906SEric.Saxe@Sun.COM /* ARGSUSED */ 8128906SEric.Saxe@Sun.COM static void 8138906SEric.Saxe@Sun.COM cpupm_event_notify_handler(ACPI_HANDLE obj, UINT32 val, void *ctx) 8148906SEric.Saxe@Sun.COM { 8158906SEric.Saxe@Sun.COM #ifndef __xpv 8168906SEric.Saxe@Sun.COM /* 8178906SEric.Saxe@Sun.COM * Currently, we handle _TPC,_CST and _PPC change notifications. 8188906SEric.Saxe@Sun.COM */ 8198906SEric.Saxe@Sun.COM if (val == CPUPM_TPC_CHANGE_NOTIFICATION) { 8208906SEric.Saxe@Sun.COM cpupm_throttle_manage_notification(ctx); 8218906SEric.Saxe@Sun.COM } else if (val == CPUPM_CST_CHANGE_NOTIFICATION) { 8228906SEric.Saxe@Sun.COM cpuidle_manage_cstates(ctx); 8238906SEric.Saxe@Sun.COM } else if (val == CPUPM_PPC_CHANGE_NOTIFICATION) { 8248906SEric.Saxe@Sun.COM cpupm_power_manage_notifications(ctx); 8258906SEric.Saxe@Sun.COM } 8268906SEric.Saxe@Sun.COM #endif 8278906SEric.Saxe@Sun.COM } 8288906SEric.Saxe@Sun.COM 8298906SEric.Saxe@Sun.COM /* 8308906SEric.Saxe@Sun.COM * Update cpupm cstate data each time CPU exits idle. 8318906SEric.Saxe@Sun.COM */ 8328906SEric.Saxe@Sun.COM void 8338906SEric.Saxe@Sun.COM cpupm_wakeup_cstate_data(cma_c_state_t *cs_data, hrtime_t end) 8348906SEric.Saxe@Sun.COM { 8358906SEric.Saxe@Sun.COM cs_data->cs_idle_exit = end; 8368906SEric.Saxe@Sun.COM } 8378906SEric.Saxe@Sun.COM 8388906SEric.Saxe@Sun.COM /* 8398906SEric.Saxe@Sun.COM * Determine next cstate based on cpupm data. 8408906SEric.Saxe@Sun.COM * Update cpupm cstate data each time CPU goes idle. 8418906SEric.Saxe@Sun.COM * Do as much as possible in the idle state bookkeeping function because the 8428906SEric.Saxe@Sun.COM * performance impact while idle is minimal compared to in the wakeup function 8438906SEric.Saxe@Sun.COM * when there is real work to do. 8448906SEric.Saxe@Sun.COM */ 8458906SEric.Saxe@Sun.COM uint32_t 846*8983SBill.Holler@Sun.COM cpupm_next_cstate(cma_c_state_t *cs_data, cpu_acpi_cstate_t *cstates, 847*8983SBill.Holler@Sun.COM uint32_t cs_count, hrtime_t start) 8488906SEric.Saxe@Sun.COM { 849*8983SBill.Holler@Sun.COM hrtime_t duration; 850*8983SBill.Holler@Sun.COM hrtime_t ave_interval; 851*8983SBill.Holler@Sun.COM hrtime_t ave_idle_time; 852*8983SBill.Holler@Sun.COM uint32_t i; 8538906SEric.Saxe@Sun.COM 8548906SEric.Saxe@Sun.COM duration = cs_data->cs_idle_exit - cs_data->cs_idle_enter; 8558906SEric.Saxe@Sun.COM scalehrtime(&duration); 8568906SEric.Saxe@Sun.COM cs_data->cs_idle += duration; 8578906SEric.Saxe@Sun.COM cs_data->cs_idle_enter = start; 8588906SEric.Saxe@Sun.COM 8598906SEric.Saxe@Sun.COM ++cs_data->cs_cnt; 8608906SEric.Saxe@Sun.COM if (cs_data->cs_cnt > cpupm_cs_sample_tunable) { 8618906SEric.Saxe@Sun.COM cs_data->cs_smpl_len = start - cs_data->cs_smpl_start; 8628906SEric.Saxe@Sun.COM scalehrtime(&cs_data->cs_smpl_len); 8638906SEric.Saxe@Sun.COM cs_data->cs_smpl_len |= 1; /* protect from DIV 0 */ 8648906SEric.Saxe@Sun.COM cs_data->cs_smpl_idle = cs_data->cs_idle; 8658906SEric.Saxe@Sun.COM cs_data->cs_idle = 0; 8668906SEric.Saxe@Sun.COM cs_data->cs_smpl_idle_pct = ((100 * cs_data->cs_smpl_idle) / 8678906SEric.Saxe@Sun.COM cs_data->cs_smpl_len); 8688906SEric.Saxe@Sun.COM 8698906SEric.Saxe@Sun.COM cs_data->cs_smpl_start = start; 8708906SEric.Saxe@Sun.COM cs_data->cs_cnt = 0; 8718906SEric.Saxe@Sun.COM 8728906SEric.Saxe@Sun.COM /* 8738906SEric.Saxe@Sun.COM * Strand level C-state policy 874*8983SBill.Holler@Sun.COM * The cpu_acpi_cstate_t *cstates array is not required to 875*8983SBill.Holler@Sun.COM * have an entry for both CPU_ACPI_C2 and CPU_ACPI_C3. 876*8983SBill.Holler@Sun.COM * There are cs_count entries in the cstates array. 877*8983SBill.Holler@Sun.COM * cs_data->cs_next_cstate contains the index of the next 878*8983SBill.Holler@Sun.COM * C-state this CPU should enter. 8798906SEric.Saxe@Sun.COM */ 880*8983SBill.Holler@Sun.COM ASSERT(cstates[0].cs_type == CPU_ACPI_C1); 8818906SEric.Saxe@Sun.COM 8828906SEric.Saxe@Sun.COM /* 8838906SEric.Saxe@Sun.COM * Will CPU be idle long enough to save power? 8848906SEric.Saxe@Sun.COM */ 8858906SEric.Saxe@Sun.COM ave_idle_time = (cs_data->cs_smpl_idle / 8868906SEric.Saxe@Sun.COM cpupm_cs_sample_tunable) / 1000; 887*8983SBill.Holler@Sun.COM for (i = 1; i < cs_count; ++i) { 888*8983SBill.Holler@Sun.COM if (ave_idle_time < (cstates[i].cs_latency * 889*8983SBill.Holler@Sun.COM cpupm_cs_idle_save_tunable)) { 890*8983SBill.Holler@Sun.COM cs_count = i; 891*8983SBill.Holler@Sun.COM DTRACE_PROBE2(cpupm__next__cstate, cpu_t *, 892*8983SBill.Holler@Sun.COM CPU, int, i); 893*8983SBill.Holler@Sun.COM } 8948906SEric.Saxe@Sun.COM } 8958906SEric.Saxe@Sun.COM 8968906SEric.Saxe@Sun.COM /* 8978906SEric.Saxe@Sun.COM * Wakeup often (even when non-idle time is very short)? 8988906SEric.Saxe@Sun.COM * Some producer/consumer type loads fall into this category. 8998906SEric.Saxe@Sun.COM */ 9008906SEric.Saxe@Sun.COM ave_interval = (cs_data->cs_smpl_len / cpupm_cs_sample_tunable) 9018906SEric.Saxe@Sun.COM / 1000; 902*8983SBill.Holler@Sun.COM for (i = 1; i < cs_count; ++i) { 903*8983SBill.Holler@Sun.COM if (ave_interval <= (cstates[i].cs_latency * 904*8983SBill.Holler@Sun.COM cpupm_cs_idle_cost_tunable)) { 905*8983SBill.Holler@Sun.COM cs_count = i; 906*8983SBill.Holler@Sun.COM DTRACE_PROBE2(cpupm__next__cstate, cpu_t *, 907*8983SBill.Holler@Sun.COM CPU, int, (CPU_MAX_CSTATES + i)); 908*8983SBill.Holler@Sun.COM } 9098906SEric.Saxe@Sun.COM } 9108906SEric.Saxe@Sun.COM 9118906SEric.Saxe@Sun.COM /* 9128906SEric.Saxe@Sun.COM * Idle percent 9138906SEric.Saxe@Sun.COM */ 914*8983SBill.Holler@Sun.COM for (i = 1; i < cs_count; ++i) { 915*8983SBill.Holler@Sun.COM switch (cstates[i].cs_type) { 916*8983SBill.Holler@Sun.COM case CPU_ACPI_C2: 917*8983SBill.Holler@Sun.COM if (cs_data->cs_smpl_idle_pct < 918*8983SBill.Holler@Sun.COM cpupm_C2_idle_pct_tunable) { 919*8983SBill.Holler@Sun.COM cs_count = i; 920*8983SBill.Holler@Sun.COM DTRACE_PROBE2(cpupm__next__cstate, 921*8983SBill.Holler@Sun.COM cpu_t *, CPU, int, 922*8983SBill.Holler@Sun.COM ((2 * CPU_MAX_CSTATES) + i)); 923*8983SBill.Holler@Sun.COM } 924*8983SBill.Holler@Sun.COM break; 925*8983SBill.Holler@Sun.COM 926*8983SBill.Holler@Sun.COM case CPU_ACPI_C3: 927*8983SBill.Holler@Sun.COM if (cs_data->cs_smpl_idle_pct < 928*8983SBill.Holler@Sun.COM cpupm_C3_idle_pct_tunable) { 929*8983SBill.Holler@Sun.COM cs_count = i; 930*8983SBill.Holler@Sun.COM DTRACE_PROBE2(cpupm__next__cstate, 931*8983SBill.Holler@Sun.COM cpu_t *, CPU, int, 932*8983SBill.Holler@Sun.COM ((2 * CPU_MAX_CSTATES) + i)); 933*8983SBill.Holler@Sun.COM } 934*8983SBill.Holler@Sun.COM break; 935*8983SBill.Holler@Sun.COM } 9368906SEric.Saxe@Sun.COM } 937*8983SBill.Holler@Sun.COM 938*8983SBill.Holler@Sun.COM cs_data->cs_next_cstate = cs_count - 1; 9398906SEric.Saxe@Sun.COM } 9408906SEric.Saxe@Sun.COM 9418906SEric.Saxe@Sun.COM return (cs_data->cs_next_cstate); 9428906SEric.Saxe@Sun.COM } 943