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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * Platform Power Management master pseudo driver platform support. 30 */ 31 32 #include <sys/ddi.h> 33 #include <sys/sunddi.h> 34 #include <sys/ppmvar.h> 35 #include <sys/cpupm.h> 36 37 static struct ppm_domit * 38 ppm_get_domit_by_model(int model) 39 { 40 struct ppm_domit *domit_p; 41 for (domit_p = ppm_domit_data; (domit_p->name && 42 (domit_p->model != model)); domit_p++) 43 ; 44 ASSERT(domit_p); 45 return (domit_p); 46 } 47 48 void 49 ppm_rebuild_cpu_domains(void) 50 { 51 char *str = "ppm_rebuild_cpu_domains"; 52 cpupm_cpu_dependency_t *dep; 53 cpupm_cpu_dependency_t *dep_next; 54 cpupm_cpu_node_t *cpu_next; 55 struct ppm_domit *domit_p; 56 ppm_domain_t *domp_old; 57 ppm_domain_t *domp; 58 ppm_dev_t *devp; 59 ppm_db_t *dbp; 60 61 /* 62 * Get the CPU domain data 63 */ 64 domit_p = ppm_get_domit_by_model(PPMD_CPU); 65 66 /* 67 * Find the CPU domain created from ppm.conf. It's only a 68 * temporary domain used to make sure that all CPUs are 69 * claimed. There should only be one such domain defined. 70 */ 71 for (domp = ppm_domain_p; (domp && (domp->model != PPMD_CPU)); 72 domp = domp->next) 73 ; 74 if (domp == NULL) { 75 cmn_err(CE_WARN, "%s: ppm.conf does not define a CPU domain!", 76 str); 77 return; 78 } 79 domp_old = domp; 80 for (domp = domp->next; domp; domp = domp->next) { 81 if (domp->model == PPMD_CPU) { 82 cmn_err(CE_WARN, "%s: Multiple CPU domains defined " 83 "in ppm.conf!", str); 84 return; 85 } 86 } 87 88 /* 89 * It is quite possible that the platform does not contain any 90 * power manageable CPUs. If so, devlist will be NULL. 91 */ 92 if (domp_old->devlist == NULL) { 93 PPMD(D_CPU, ("%s: No CPUs claimed by ppm!\n", str)); 94 return; 95 } 96 97 /* 98 * Get the CPU dependencies as determined by the CPU driver. If 99 * the CPU driver didn't create a valid set of dependencies, then 100 * leave the domain as it is (which is unmanageable since 101 * PPM_CPU_READY is off). 102 */ 103 dep = cpupm_get_cpu_dependencies(); 104 if (dep == NULL) { 105 PPMD(D_CPU, ("%s: No CPU dependency info!\n", str)); 106 return; 107 } 108 109 /* 110 * Build real CPU domains. OFFLINE the old one as we don't 111 * want it to be used when we're done. 112 */ 113 mutex_enter(&domp_old->lock); 114 domp_old->dflags |= PPMD_OFFLINE; 115 for (dep_next = dep; dep_next; dep_next = dep_next->cd_next) { 116 domp = kmem_zalloc(sizeof (*domp), KM_SLEEP); 117 domp->name = kmem_zalloc(MAXNAMELEN, KM_SLEEP); 118 (void) snprintf(domp->name, MAXNAMELEN, "acpi_cpu_domain_%d", 119 dep_next->cd_dependency_id); 120 mutex_init(&domp->lock, NULL, MUTEX_DRIVER, NULL); 121 mutex_enter(&domp->lock); 122 domp->dflags = domit_p->dflags | PPMD_CPU_READY; 123 domp->pwr_cnt = 0; 124 domp->propname = domp_old->propname; 125 domp->model = domit_p->model; 126 domp->status = domit_p->status; 127 128 /* 129 * Add devices to new domain. As a precaution, 130 * make sure that the device is currently owned by the 131 * ppm.conf defined CPU domain. Adding the device to the 132 * domain will result in the domain's "devlist" and "owned" 133 * lists being properly formed. It will also update the 134 * dip pointer to the device structure. We have to manually 135 * build the "conflist" for the domain. But conveniently, the 136 * "conflist" data is easily obtainable from the "devlist". 137 */ 138 for (cpu_next = dep_next->cd_cpu; cpu_next; 139 cpu_next = cpu_next->cn_next) { 140 devp = PPM_GET_PRIVATE(cpu_next->cn_dip); 141 ASSERT(devp && devp->domp == domp_old); 142 devp = ppm_add_dev(cpu_next->cn_dip, domp); 143 dbp = kmem_zalloc(sizeof (struct ppm_db), KM_SLEEP); 144 dbp->name = kmem_zalloc((strlen(devp->path) + 1), 145 KM_SLEEP); 146 (void) strcpy(dbp->name, devp->path); 147 dbp->next = domp->conflist; 148 domp->conflist = dbp; 149 } 150 151 /* 152 * Note that we do not bother creating a "dc" list as there 153 * isn't one for x86 CPU power management. If this changes 154 * in the future some more work will need to be done to 155 * support it. 156 */ 157 ASSERT(domp_old->dc == NULL); 158 159 /* 160 * Add the domain to the live list. 161 */ 162 domp->next = ppm_domain_p; 163 ppm_domain_p = domp; 164 165 mutex_exit(&domp->lock); 166 } 167 mutex_exit(&domp_old->lock); 168 cpupm_free_cpu_dependencies(); 169 } 170 171 /* 172 * Used by ppm_redefine_topspeed() to set the highest power level of all CPUs 173 * in a domain. 174 */ 175 void 176 ppm_set_topspeed(ppm_dev_t *cpup, int speed) 177 { 178 for (cpup = cpup->domp->devlist; cpup != NULL; cpup = cpup->next) 179 (*cpupm_set_topspeed)(cpup->dip, speed); 180 } 181 182 /* 183 * Redefine the highest power level for all CPUs in a domain. This 184 * functionality is necessary because ACPI uses the _PPC to define 185 * a CPU's highest power level *and* allows the _PPC to be redefined 186 * dynamically. _PPC changes are communicated through _PPC change 187 * notifications caught by the CPU device driver. 188 */ 189 void 190 ppm_redefine_topspeed(void *ctx) 191 { 192 char *str = "ppm_redefine_topspeed"; 193 ppm_dev_t *cpup; 194 ppm_dev_t *ncpup; 195 int topspeed; 196 int newspeed = -1; 197 198 cpup = PPM_GET_PRIVATE((dev_info_t *)ctx); 199 200 if (cpupm_get_topspeed == NULL || cpupm_set_topspeed == NULL) { 201 cmn_err(CE_WARN, "%s: Cannot process request for instance %d " 202 "since cpupm interfaces are not initialized", str, 203 ddi_get_instance(cpup->dip)); 204 return; 205 } 206 207 if (!(cpup->domp->dflags & PPMD_CPU_READY)) { 208 PPMD(D_CPU, ("%s: instance %d received _PPC change " 209 "notification before PPMD_CPU_READY", str, 210 ddi_get_instance(cpup->dip))); 211 return; 212 } 213 214 /* 215 * Process each CPU in the domain. 216 */ 217 for (ncpup = cpup->domp->devlist; ncpup != NULL; ncpup = ncpup->next) { 218 topspeed = (*cpupm_get_topspeed)(ncpup->dip); 219 if (newspeed == -1 || topspeed < newspeed) 220 newspeed = topspeed; 221 } 222 223 ppm_set_topspeed(cpup, newspeed); 224 } 225 226 /* 227 * Traverses all domains looking for CPU domains and for each CPU domain 228 * redefines the topspeed for that domain. The reason that this is necessary 229 * is that on x86 platforms ACPI allows the highest power level to be 230 * redefined dynamically. Once all CPU devices have been started it we 231 * need to go back and reinitialize the topspeeds (just in case it's changed). 232 */ 233 void 234 ppm_init_topspeed(void) 235 { 236 ppm_domain_t *domp; 237 for (domp = ppm_domain_p; domp; domp = domp->next) { 238 if (domp->model != PPMD_CPU || !PPM_DOMAIN_UP(domp)) 239 continue; 240 if (domp->devlist == NULL) 241 continue; 242 ppm_redefine_topspeed(domp->devlist->dip); 243 } 244 } 245 246 /* 247 * For x86 platforms CPU domains must be built dynamically at bootime. 248 * Until the domains have been built, refuse all power transition 249 * requests. 250 */ 251 /* ARGSUSED */ 252 boolean_t 253 ppm_manage_early_cpus(dev_info_t *dip, int new, int *result) 254 { 255 ppm_dev_t *ppmd = PPM_GET_PRIVATE(dip); 256 257 if (!(ppmd->domp->dflags & PPMD_CPU_READY)) { 258 PPMD(D_CPU, ("ppm_manage_early_cpus: attempt to manage CPU " 259 "before it was ready dip(0x%p)", (void *)dip)); 260 return (B_TRUE); 261 } 262 *result = DDI_FAILURE; 263 return (B_FALSE); 264 } 265 266 int 267 ppm_change_cpu_power(ppm_dev_t *ppmd, int newlevel) 268 { 269 #ifdef DEBUG 270 char *str = "ppm_change_cpu_power"; 271 #endif 272 ppm_unit_t *unitp; 273 ppm_domain_t *domp; 274 ppm_dev_t *cpup; 275 dev_info_t *dip; 276 int oldlevel; 277 int ret; 278 279 unitp = ddi_get_soft_state(ppm_statep, ppm_inst); 280 ASSERT(unitp); 281 domp = ppmd->domp; 282 cpup = domp->devlist; 283 284 dip = cpup->dip; 285 ASSERT(dip); 286 287 oldlevel = cpup->level; 288 289 PPMD(D_CPU, ("%s: old %d, new %d\n", str, oldlevel, newlevel)) 290 291 if (newlevel == oldlevel) 292 return (DDI_SUCCESS); 293 294 /* bring each cpu to next level */ 295 for (; cpup; cpup = cpup->next) { 296 ret = pm_power(cpup->dip, 0, newlevel); 297 PPMD(D_CPU, ("%s: \"%s\", changed to level %d, ret %d\n", 298 str, cpup->path, newlevel, ret)) 299 if (ret == DDI_SUCCESS) { 300 cpup->level = newlevel; 301 cpup->rplvl = PM_LEVEL_UNKNOWN; 302 continue; 303 } 304 305 /* 306 * If the driver was unable to lower cpu speed, 307 * the cpu probably got busy. Best to change 308 * speed back to normal. 309 */ 310 if (newlevel < oldlevel) { 311 oldlevel = pm_get_normal_power(dip, 0); 312 ret = ppm_revert_cpu_power(cpup, oldlevel); 313 } 314 return (ret); 315 } 316 317 return (DDI_SUCCESS); 318 } 319