/*
* 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
* or http://www.opensolaris.org/os/licensing.
* 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
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2009, Intel Corporation.
* All rights reserved.
*/
/*
* Platform Power Management master pseudo driver platform support.
*/
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ppmvar.h>
#include <sys/cpupm.h>
#define PPM_CPU_PSTATE_DOMAIN_FLG 0x100
/*
* Used by ppm_redefine_topspeed() to set the highest power level of all CPUs
* in a domain.
*/
void
ppm_set_topspeed(ppm_dev_t *cpup, int speed)
{
for (cpup = cpup->domp->devlist; cpup != NULL; cpup = cpup->next)
(*cpupm_set_topspeed_callb)(cpup->dip, speed);
}
/*
* Redefine the highest power level for all CPUs in a domain. This
* functionality is necessary because ACPI uses the _PPC to define
* a CPU's highest power level *and* allows the _PPC to be redefined
* dynamically. _PPC changes are communicated through _PPC change
* notifications caught by the CPU device driver.
*/
void
ppm_redefine_topspeed(void *ctx)
{
char *str = "ppm_redefine_topspeed";
ppm_dev_t *cpup;
ppm_dev_t *ncpup;
int topspeed;
int newspeed = -1;
cpup = PPM_GET_PRIVATE((dev_info_t *)ctx);
if (cpupm_get_topspeed_callb == NULL ||
cpupm_set_topspeed_callb == NULL) {
cmn_err(CE_WARN, "%s: Cannot process request for instance %d "
"since cpupm interfaces are not initialized", str,
ddi_get_instance(cpup->dip));
return;
}
if (!(cpup->domp->dflags & PPMD_CPU_READY)) {
PPMD(D_CPU, ("%s: instance %d received _PPC change "
"notification before PPMD_CPU_READY", str,
ddi_get_instance(cpup->dip)));
return;
}
/*
* Process each CPU in the domain.
*/
for (ncpup = cpup->domp->devlist; ncpup != NULL; ncpup = ncpup->next) {
topspeed = (*cpupm_get_topspeed_callb)(ncpup->dip);
if (newspeed == -1 || topspeed < newspeed)
newspeed = topspeed;
}
ppm_set_topspeed(cpup, newspeed);
}
/*
* For x86 platforms CPU domains must be built dynamically at bootime.
* Until the domains have been built, refuse all power transition
* requests.
*/
/* ARGSUSED */
boolean_t
ppm_manage_early_cpus(dev_info_t *dip, int new, int *result)
{
ppm_dev_t *ppmd = PPM_GET_PRIVATE(dip);
if (!(ppmd->domp->dflags & PPMD_CPU_READY)) {
PPMD(D_CPU, ("ppm_manage_early_cpus: attempt to manage CPU "
"before it was ready dip(0x%p)", (void *)dip));
return (B_TRUE);
}
*result = DDI_FAILURE;
return (B_FALSE);
}
int
ppm_change_cpu_power(ppm_dev_t *ppmd, int newlevel)
{
#ifdef DEBUG
char *str = "ppm_change_cpu_power";
#endif
ppm_unit_t *unitp;
ppm_domain_t *domp;
ppm_dev_t *cpup;
dev_info_t *dip;
int oldlevel;
int ret;
unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
ASSERT(unitp);
domp = ppmd->domp;
cpup = domp->devlist;
dip = cpup->dip;
ASSERT(dip);
oldlevel = cpup->level;
PPMD(D_CPU, ("%s: old %d, new %d\n", str, oldlevel, newlevel))
if (newlevel == oldlevel)
return (DDI_SUCCESS);
/* bring each cpu to next level */
for (; cpup; cpup = cpup->next) {
ret = pm_power(cpup->dip, 0, newlevel);
PPMD(D_CPU, ("%s: \"%s\", changed to level %d, ret %d\n",
str, cpup->path, newlevel, ret))
if (ret == DDI_SUCCESS) {
cpup->level = newlevel;
cpup->rplvl = PM_LEVEL_UNKNOWN;
continue;
}
/*
* If the driver was unable to lower cpu speed,
* the cpu probably got busy; set the previous
* cpus back to the original level
*/
if (newlevel < oldlevel)
ret = ppm_revert_cpu_power(cpup, oldlevel);
return (ret);
}
return (DDI_SUCCESS);
}
/*
* allocate ppm CPU pstate domain if non-existence,
* otherwise, add the CPU to the corresponding ppm
* CPU pstate domain.
*/
void
ppm_alloc_pstate_domains(cpu_t *cp)
{
cpupm_mach_state_t *mach_state;
uint32_t pm_domain;
int sub_domain;
ppm_domain_t *domp;
dev_info_t *cpu_dip;
ppm_db_t *dbp;
char path[MAXNAMELEN];
mach_state = (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state);
ASSERT(mach_state);
pm_domain = mach_state->ms_pstate.cma_domain->pm_domain;
/*
* There are two purposes of sub_domain:
* 1. skip the orignal ppm CPU domain generated by ppm.conf
* 2. A CPU ppm domain could have several pstate domains indeed.
*/
sub_domain = pm_domain | PPM_CPU_PSTATE_DOMAIN_FLG;
/*
* Find ppm CPU pstate domain
*/
for (domp = ppm_domain_p; domp; domp = domp->next) {
if ((domp->model == PPMD_CPU) &&
(domp->sub_domain == sub_domain)) {
break;
}
}
/*
* Create one ppm CPU pstate domain if no found
*/
if (domp == NULL) {
domp = kmem_zalloc(sizeof (*domp), KM_SLEEP);
mutex_init(&domp->lock, NULL, MUTEX_DRIVER, NULL);
mutex_enter(&domp->lock);
domp->name = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
(void) snprintf(domp->name, MAXNAMELEN, "cpu_pstate_domain_%d",
pm_domain);
domp->sub_domain = sub_domain;
domp->dflags = PPMD_LOCK_ALL | PPMD_CPU_READY;
domp->pwr_cnt = 0;
domp->pwr_cnt++;
domp->propname = NULL;
domp->model = PPMD_CPU;
domp->status = PPMD_ON;
cpu_dip = mach_state->ms_dip;
(void) ddi_pathname(cpu_dip, path);
dbp = kmem_zalloc(sizeof (struct ppm_db), KM_SLEEP);
dbp->name = kmem_zalloc((strlen(path) + 1),
KM_SLEEP);
(void) strcpy(dbp->name, path);
dbp->next = domp->conflist;
domp->conflist = dbp;
domp->next = ppm_domain_p;
ppm_domain_p = domp;
mutex_exit(&domp->lock);
}
/*
* We found one matched ppm CPU pstate domain,
* add cpu to this domain
*/
else {
mutex_enter(&domp->lock);
cpu_dip = mach_state->ms_dip;
(void) ddi_pathname(cpu_dip, path);
dbp = kmem_zalloc(sizeof (struct ppm_db), KM_SLEEP);
dbp->name = kmem_zalloc((strlen(path) + 1),
KM_SLEEP);
(void) strcpy(dbp->name, path);
dbp->next = domp->conflist;
domp->conflist = dbp;
domp->pwr_cnt++;
mutex_exit(&domp->lock);
}
}
/*
* remove CPU from the corresponding ppm CPU pstate
* domain. We only remove CPU from conflist here.
*/
void
ppm_free_pstate_domains(cpu_t *cp)
{
cpupm_mach_state_t *mach_state;
ppm_domain_t *domp;
ppm_dev_t *devp;
dev_info_t *cpu_dip;
ppm_db_t **dbpp, *pconf;
char path[MAXNAMELEN];
mach_state = (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state);
ASSERT(mach_state);
cpu_dip = mach_state->ms_dip;
(void) ddi_pathname(cpu_dip, path);
/*
* get ppm CPU pstate domain
*/
devp = PPM_GET_PRIVATE(cpu_dip);
ASSERT(devp);
domp = devp->domp;
ASSERT(domp);
/*
* remove CPU from conflist
*/
mutex_enter(&domp->lock);
for (dbpp = &domp->conflist; (pconf = *dbpp) != NULL; ) {
if (strcmp(pconf->name, path) != 0) {
dbpp = &pconf->next;
continue;
}
*dbpp = pconf->next;
kmem_free(pconf->name, strlen(pconf->name) + 1);
kmem_free(pconf, sizeof (*pconf));
}
mutex_exit(&domp->lock);
}