xref: /illumos-gate/usr/src/uts/i86pc/io/ppm_plat.c (revision 444f66e7)
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
ppm_set_topspeed(ppm_dev_t * cpup,int speed)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
ppm_redefine_topspeed(void * ctx)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
ppm_manage_early_cpus(dev_info_t * dip,int new,int * result)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
ppm_change_cpu_power(ppm_dev_t * ppmd,int newlevel)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
ppm_alloc_pstate_domains(cpu_t * cp)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
ppm_free_pstate_domains(cpu_t * cp)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