Home | History | Annotate | Download | only in io
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 /*
     26  * Copyright (c) 2009, Intel Corporation.
     27  * All rights reserved.
     28  */
     29 
     30 /*
     31  * Platform Power Management master pseudo driver platform support.
     32  */
     33 
     34 #include <sys/ddi.h>
     35 #include <sys/sunddi.h>
     36 #include <sys/ppmvar.h>
     37 #include <sys/cpupm.h>
     38 
     39 #define	PPM_CPU_PSTATE_DOMAIN_FLG	0x100
     40 
     41 /*
     42  * Used by ppm_redefine_topspeed() to set the highest power level of all CPUs
     43  * in a domain.
     44  */
     45 void
     46 ppm_set_topspeed(ppm_dev_t *cpup, int speed)
     47 {
     48 	for (cpup = cpup->domp->devlist; cpup != NULL; cpup = cpup->next)
     49 		(*cpupm_set_topspeed_callb)(cpup->dip, speed);
     50 }
     51 
     52 /*
     53  * Redefine the highest power level for all CPUs in a domain. This
     54  * functionality is necessary because ACPI uses the _PPC to define
     55  * a CPU's highest power level *and* allows the _PPC to be redefined
     56  * dynamically. _PPC changes are communicated through _PPC change
     57  * notifications caught by the CPU device driver.
     58  */
     59 void
     60 ppm_redefine_topspeed(void *ctx)
     61 {
     62 	char *str = "ppm_redefine_topspeed";
     63 	ppm_dev_t *cpup;
     64 	ppm_dev_t *ncpup;
     65 	int topspeed;
     66 	int newspeed = -1;
     67 
     68 	cpup = PPM_GET_PRIVATE((dev_info_t *)ctx);
     69 
     70 	if (cpupm_get_topspeed_callb == NULL ||
     71 	    cpupm_set_topspeed_callb == NULL) {
     72 		cmn_err(CE_WARN, "%s: Cannot process request for instance %d "
     73 		    "since cpupm interfaces are not initialized", str,
     74 		    ddi_get_instance(cpup->dip));
     75 		return;
     76 	}
     77 
     78 	if (!(cpup->domp->dflags & PPMD_CPU_READY)) {
     79 		PPMD(D_CPU, ("%s: instance %d received _PPC change "
     80 		    "notification before PPMD_CPU_READY", str,
     81 		    ddi_get_instance(cpup->dip)));
     82 		return;
     83 	}
     84 
     85 	/*
     86 	 * Process each CPU in the domain.
     87 	 */
     88 	for (ncpup = cpup->domp->devlist; ncpup != NULL; ncpup = ncpup->next) {
     89 		topspeed = (*cpupm_get_topspeed_callb)(ncpup->dip);
     90 		if (newspeed == -1 || topspeed < newspeed)
     91 			newspeed = topspeed;
     92 	}
     93 
     94 	ppm_set_topspeed(cpup, newspeed);
     95 }
     96 
     97 /*
     98  * For x86 platforms CPU domains must be built dynamically at bootime.
     99  * Until the domains have been built, refuse all power transition
    100  * requests.
    101  */
    102 /* ARGSUSED */
    103 boolean_t
    104 ppm_manage_early_cpus(dev_info_t *dip, int new, int *result)
    105 {
    106 	ppm_dev_t *ppmd = PPM_GET_PRIVATE(dip);
    107 
    108 	if (!(ppmd->domp->dflags & PPMD_CPU_READY)) {
    109 		PPMD(D_CPU, ("ppm_manage_early_cpus: attempt to manage CPU "
    110 		    "before it was ready dip(0x%p)", (void *)dip));
    111 		return (B_TRUE);
    112 	}
    113 	*result = DDI_FAILURE;
    114 	return (B_FALSE);
    115 }
    116 
    117 int
    118 ppm_change_cpu_power(ppm_dev_t *ppmd, int newlevel)
    119 {
    120 #ifdef DEBUG
    121 	char *str = "ppm_change_cpu_power";
    122 #endif
    123 	ppm_unit_t *unitp;
    124 	ppm_domain_t *domp;
    125 	ppm_dev_t *cpup;
    126 	dev_info_t *dip;
    127 	int oldlevel;
    128 	int ret;
    129 
    130 	unitp = ddi_get_soft_state(ppm_statep, ppm_inst);
    131 	ASSERT(unitp);
    132 	domp = ppmd->domp;
    133 	cpup = domp->devlist;
    134 
    135 	dip = cpup->dip;
    136 	ASSERT(dip);
    137 
    138 	oldlevel = cpup->level;
    139 
    140 	PPMD(D_CPU, ("%s: old %d, new %d\n", str, oldlevel, newlevel))
    141 
    142 	if (newlevel == oldlevel)
    143 		return (DDI_SUCCESS);
    144 
    145 	/* bring each cpu to next level */
    146 	for (; cpup; cpup = cpup->next) {
    147 		ret = pm_power(cpup->dip, 0, newlevel);
    148 		PPMD(D_CPU, ("%s: \"%s\", changed to level %d, ret %d\n",
    149 		    str, cpup->path, newlevel, ret))
    150 		if (ret == DDI_SUCCESS) {
    151 			cpup->level = newlevel;
    152 			cpup->rplvl = PM_LEVEL_UNKNOWN;
    153 			continue;
    154 		}
    155 
    156 		/*
    157 		 * If the driver was unable to lower cpu speed,
    158 		 * the cpu probably got busy; set the previous
    159 		 * cpus back to the original level
    160 		 */
    161 		if (newlevel < oldlevel)
    162 			ret = ppm_revert_cpu_power(cpup, oldlevel);
    163 
    164 		return (ret);
    165 	}
    166 
    167 	return (DDI_SUCCESS);
    168 }
    169 
    170 /*
    171  * allocate ppm CPU pstate domain if non-existence,
    172  * otherwise, add the CPU to the corresponding ppm
    173  * CPU pstate domain.
    174  */
    175 void
    176 ppm_alloc_pstate_domains(cpu_t *cp)
    177 {
    178 	cpupm_mach_state_t	*mach_state;
    179 	uint32_t		pm_domain;
    180 	int			sub_domain;
    181 	ppm_domain_t		*domp;
    182 	dev_info_t		*cpu_dip;
    183 	ppm_db_t		*dbp;
    184 	char			path[MAXNAMELEN];
    185 
    186 	mach_state = (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state);
    187 	ASSERT(mach_state);
    188 	pm_domain = mach_state->ms_pstate.cma_domain->pm_domain;
    189 
    190 	/*
    191 	 * There are two purposes of sub_domain:
    192 	 * 1. skip the orignal ppm CPU domain generated by ppm.conf
    193 	 * 2. A CPU ppm domain could have several pstate domains indeed.
    194 	 */
    195 	sub_domain = pm_domain | PPM_CPU_PSTATE_DOMAIN_FLG;
    196 
    197 	/*
    198 	 * Find ppm CPU pstate domain
    199 	 */
    200 	for (domp = ppm_domain_p; domp; domp = domp->next) {
    201 		if ((domp->model == PPMD_CPU) &&
    202 		    (domp->sub_domain == sub_domain)) {
    203 			break;
    204 		}
    205 	}
    206 
    207 	/*
    208 	 * Create one ppm CPU pstate domain if no found
    209 	 */
    210 	if (domp == NULL) {
    211 		domp = kmem_zalloc(sizeof (*domp), KM_SLEEP);
    212 		mutex_init(&domp->lock, NULL, MUTEX_DRIVER, NULL);
    213 		mutex_enter(&domp->lock);
    214 		domp->name = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
    215 		(void) snprintf(domp->name, MAXNAMELEN, "cpu_pstate_domain_%d",
    216 		    pm_domain);
    217 		domp->sub_domain = sub_domain;
    218 		domp->dflags = PPMD_LOCK_ALL | PPMD_CPU_READY;
    219 		domp->pwr_cnt = 0;
    220 		domp->pwr_cnt++;
    221 		domp->propname = NULL;
    222 		domp->model = PPMD_CPU;
    223 		domp->status = PPMD_ON;
    224 		cpu_dip = mach_state->ms_dip;
    225 		(void) ddi_pathname(cpu_dip, path);
    226 		dbp = kmem_zalloc(sizeof (struct ppm_db), KM_SLEEP);
    227 		dbp->name = kmem_zalloc((strlen(path) + 1),
    228 		    KM_SLEEP);
    229 		(void) strcpy(dbp->name, path);
    230 		dbp->next = domp->conflist;
    231 		domp->conflist = dbp;
    232 		domp->next = ppm_domain_p;
    233 		ppm_domain_p = domp;
    234 		mutex_exit(&domp->lock);
    235 	}
    236 	/*
    237 	 * We found one matched ppm CPU pstate domain,
    238 	 * add cpu to this domain
    239 	 */
    240 	else {
    241 		mutex_enter(&domp->lock);
    242 		cpu_dip = mach_state->ms_dip;
    243 		(void) ddi_pathname(cpu_dip, path);
    244 		dbp = kmem_zalloc(sizeof (struct ppm_db), KM_SLEEP);
    245 		dbp->name = kmem_zalloc((strlen(path) + 1),
    246 		    KM_SLEEP);
    247 		(void) strcpy(dbp->name, path);
    248 		dbp->next = domp->conflist;
    249 		domp->conflist = dbp;
    250 		domp->pwr_cnt++;
    251 		mutex_exit(&domp->lock);
    252 	}
    253 }
    254 
    255 /*
    256  * remove CPU from the corresponding ppm CPU pstate
    257  * domain. We only remove CPU from conflist here.
    258  */
    259 void
    260 ppm_free_pstate_domains(cpu_t *cp)
    261 {
    262 	cpupm_mach_state_t	*mach_state;
    263 	ppm_domain_t		*domp;
    264 	ppm_dev_t		*devp;
    265 	dev_info_t		*cpu_dip;
    266 	ppm_db_t		**dbpp, *pconf;
    267 	char			path[MAXNAMELEN];
    268 
    269 	mach_state = (cpupm_mach_state_t *)(cp->cpu_m.mcpu_pm_mach_state);
    270 	ASSERT(mach_state);
    271 	cpu_dip = mach_state->ms_dip;
    272 	(void) ddi_pathname(cpu_dip, path);
    273 
    274 	/*
    275 	 * get ppm CPU pstate domain
    276 	 */
    277 	devp = PPM_GET_PRIVATE(cpu_dip);
    278 	ASSERT(devp);
    279 	domp = devp->domp;
    280 	ASSERT(domp);
    281 
    282 	/*
    283 	 * remove CPU from conflist
    284 	 */
    285 	mutex_enter(&domp->lock);
    286 	for (dbpp = &domp->conflist; (pconf = *dbpp) != NULL; ) {
    287 		if (strcmp(pconf->name, path) != 0) {
    288 			dbpp = &pconf->next;
    289 			continue;
    290 		}
    291 		*dbpp = pconf->next;
    292 		kmem_free(pconf->name, strlen(pconf->name) + 1);
    293 		kmem_free(pconf, sizeof (*pconf));
    294 	}
    295 	mutex_exit(&domp->lock);
    296 }
    297