/* * 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 #include #include #include #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); }