]> git.saurik.com Git - apple/xnu.git/blame - osfmk/ppc/pms.c
xnu-792.6.76.tar.gz
[apple/xnu.git] / osfmk / ppc / pms.c
CommitLineData
3a60a9f5
A
1/*
2 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
37839358
A
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
3a60a9f5 11 *
37839358
A
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
3a60a9f5
A
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
37839358
A
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
3a60a9f5
A
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22#include <ppc/machine_routines.h>
23#include <ppc/machine_cpu.h>
24#include <ppc/exception.h>
25#include <ppc/misc_protos.h>
26#include <ppc/Firmware.h>
27#include <ppc/pmap.h>
28#include <ppc/proc_reg.h>
29#include <ppc/pms.h>
30#include <ppc/savearea.h>
31#include <ppc/exception.h>
32#include <kern/processor.h>
33
34extern int real_ncpus;
35
36static uint32_t pmsSyncrolator = 0; /* Only one control operation at a time please */
37uint32_t pmsBroadcastWait = 0; /* Number of outstanding broadcasts */
38
39int pmsInstalled = 0; /* Power Management Stepper can run and has table installed */
40int pmsExperimental = 0; /* Power Management Stepper in experimental mode */
41decl_simple_lock_data(,pmsBuildLock) /* Make sure only one guy can replace table at the same time */
42
43static pmsDef *altDpmsTab = 0; /* Alternate step definition table */
44static uint32_t altDpmsTabSize = 0; /* Size of alternate step definition table */
45
46pmsDef pmsDummy = { /* This is the dummy step for initialization. All it does is to park */
47 .pmsLimit = 0, /* Time doesn't matter for a park */
48 .pmsStepID = pmsMaxStates - 1, /* Use the very last ID number for the dummy */
49 .pmsSetCmd = pmsParkIt, /* Force us to be parked */
50 .sf.pmsSetFuncInd = 0, /* No platform call for this one */
51 .pmsDown = pmsPrepSleep, /* We always park */
52 .pmsNext = pmsPrepSleep /* We always park */
53};
54
55pmsStat pmsStatsd[4][pmsMaxStates]; /* Generate enough statistics blocks for 4 processors */
56
57pmsCtl pmsCtls = { /* Power Management Stepper control */
58 .pmsStats = &pmsStatsd
59};
60
61pmsSetFunc_t pmsFuncTab[pmsSetFuncMax] = {0}; /* This is the function index table */
62pmsQueryFunc_t pmsQueryFunc = 0; /* Pointer to pmsQuery function */
63uint32_t pmsPlatformData = 0; /* Data provided by and passed to platform functions */
64
65
66/*
67 * Do any initialization needed
68 */
69
70void pmsInit(void) {
71
72 int i;
73
74 simple_lock_init(&pmsBuildLock, 0); /* Initialize the build lock */
75 for(i = 0; i < pmsMaxStates; i++) pmsCtls.pmsDefs[i] = &pmsDummy; /* Initialize the table to dummy steps */
76
77 return;
78}
79
80
81/*
82 * Start the power management stepper on all processors
83 *
84 * All processors must be parked. This should be called when the hardware
85 * is ready to step. Probably only at boot and after wake from sleep.
86 *
87 */
88
89 void pmsStart(void) {
90
91 boolean_t intr;
92
93 if(!pmsInstalled) return; /* We can't do this if no table installed */
94
95 intr = ml_set_interrupts_enabled(FALSE); /* No interruptions in here */
96 pmsRun(pmsStartUp); /* Start running the stepper everywhere */
97 (void)ml_set_interrupts_enabled(intr); /* Restore interruptions */
98
99 return;
100
101 }
102
103
104/*
105 * Park the stepper execution. This will force the stepper on this
106 * processor to abandon its current step and stop. No changes to the
107 * hardware state is made and any previous step is lost.
108 *
109 * This is used as the initial state at startup and when the step table
110 * is being changed.
111 *
112 */
113
114void pmsPark(void) {
115
116 boolean_t intr;
117
118 if(!pmsInstalled) return; /* We can't do this if no table installed */
119
120 intr = ml_set_interrupts_enabled(FALSE); /* No interruptions in here */
121 pmsSetStep(pmsParked, 0); /* Park the stepper */
122 (void)ml_set_interrupts_enabled(intr); /* Restore interruptions */
123
124 return;
125
126}
127
128
129/*
130 * Steps down to a lower power.
131 * Interrupts must be off...
132 */
133
134void pmsDown(void) {
135
136 struct per_proc_info *pp;
137 uint32_t nstate;
138
139 pp = getPerProc(); /* Get our per_proc */
140
141 if(!pmsInstalled || pp->pms.pmsState == pmsParked) return; /* No stepping if parked or not installed */
142
143 nstate = pmsCtls.pmsDefs[pp->pms.pmsState]->pmsDown; /* Get the downward step */
144 pmsSetStep(nstate, 0); /* Step to it */
145 return;
146}
147
148
149/*
150 * Steps up to a higher power. The "timer" parameter is true if the
151 * step was driven due to the pms timer expiring.
152 *
153 * Interrupts must be off...
154 */
155
156void pmsStep(int timer) {
157
158 struct per_proc_info *pp;
159 uint32_t nstate;
160 int dir;
161
162 pp = getPerProc(); /* Get our per_proc */
163
164 if(!pmsInstalled || pp->pms.pmsState == pmsParked) return; /* No stepping if parked or not installed */
165
166 nstate = pmsCtls.pmsDefs[pp->pms.pmsState]->pmsNext; /* Assume a normal step */
167 dir = 1; /* A normal step is a step up */
168
169 if(timer && (pmsCtls.pmsDefs[pp->pms.pmsState]->pmsSetCmd == pmsDelay)) { /* If the timer expired and we are in a delay step, use the delay branch */
170 nstate = pmsCtls.pmsDefs[pp->pms.pmsState]->pmsTDelay; /* Get the delayed step */
171 dir = 0; /* Delayed steps are a step down for accounting purposes. */
172 }
173
174 pmsSetStep(nstate, dir); /* Step to it */
175 return;
176}
177
178
179/*
180 * Set a specific step
181 *
182 * We do not do statistics if exiting park
183 * Interrupts must be off...
184 *
185 */
186
187void pmsSetStep(uint32_t nstep, int dir) {
188
189 struct per_proc_info *pp;
190 uint32_t pstate, ret, nCSetCmd, mCSetCmd;
191 pmsDef *pnstate, *pcstate;
192 uint64_t tb, nt, dur;
193 int cpu, frompark;
194
195 pp = getPerProc(); /* Get our per_proc */
196 cpu = cpu_number(); /* Get our processor */
197
198 while(1) { /* Keep stepping until we get a delay */
199
200 if(pp->pms.pmsCSetCmd & pmsMustCmp) { /* Do we have to finish the delay before changing? */
201 while(mach_absolute_time() < pp->pms.pmsPop); /* Yes, spin here... */
202 }
203
204 if((nstep == pmsParked) || ((uint32_t)pmsCtls.pmsDefs[nstep]->pmsSetCmd == pmsParkIt)) { /* Are we parking? */
205
206 tb = mach_absolute_time(); /* What time is it? */
207 pp->pms.pmsStamp = tb; /* Show transition now */
208 pp->pms.pmsPop = HalfwayToForever; /* Set the pop way into the future */
209 pp->pms.pmsState = pmsParked; /* Make sure we are parked */
210 setTimerReq(); /* Cancel our timer if going */
211 return;
212 }
213
214 pnstate = pmsCtls.pmsDefs[nstep]; /* Point to the state definition */
215 pstate = pp->pms.pmsState; /* Save the current step */
216 pp->pms.pmsState = nstep; /* Set the current to the next step */
217
218 if(pnstate->pmsSetCmd != pmsDelay) { /* If this is not a delayed state, change the actual hardware now */
219 if(pnstate->pmsSetCmd & pmsCngCPU) pmsCPUSet(pnstate->pmsSetCmd); /* We have some CPU work to do... */
220 if((uint32_t)pnstate->sf.pmsSetFunc) pnstate->sf.pmsSetFunc(pnstate->pmsSetCmd, cpu, pmsPlatformData); /* Tell the platform to set power mode */
221
222 mCSetCmd = pnstate->pmsSetCmd & (pmsCngXClk | pmsCngCPU | pmsCngVolt); /* Isolate just the change flags */
223 mCSetCmd = (mCSetCmd - (mCSetCmd >> 7)) | pmsSync | pmsMustCmp | pmsPowerID; /* Form mask of bits that come from new command */
224 nCSetCmd = pp->pms.pmsCSetCmd & ~mCSetCmd; /* Clear changing bits */
225 nCSetCmd = nCSetCmd | (pnstate->pmsSetCmd & mCSetCmd); /* Flip on the changing bits and the always copy bits */
226
227 pp->pms.pmsCSetCmd = nCSetCmd; /* Set it for real */
228 }
229
230 tb = mach_absolute_time(); /* What time is it? */
231 pp->pms.pmsPop = tb + pnstate->pmsLimit; /* Set the next pop */
232
233 if((pnstate->pmsSetCmd != pmsDelay) && (pp->pms.pmsCSetCmd & pmsSync) && (pnstate->pmsLimit != 0)) { /* Is this a synchronous command with a delay? */
234 while(mach_absolute_time() < pp->pms.pmsPop); /* Yes, spin here and wait it out... */
235 }
236
237/*
238 * Gather some statistics
239 */
240
241 dur = tb - pp->pms.pmsStamp; /* Get the amount of time we were in the old step */
242 pp->pms.pmsStamp = tb; /* Set the new timestamp */
243 if(!(pstate == pmsParked)) { /* Only take stats if we were not parked */
244 pcstate = pmsCtls.pmsDefs[pstate]; /* Get the previous step */
245 pmsCtls.pmsStats[cpu][pcstate->pmsStepID].stTime[dir] += dur; /* Accumulate the total time in the old step */
246 pmsCtls.pmsStats[cpu][pcstate->pmsStepID].stCnt[dir] += 1; /* Count transitions */
247 }
248
249/*
250 * See if we are done chaining steps
251 */
252
253 if((pnstate->pmsSetCmd == pmsDelay)
254 || (!(pp->pms.pmsCSetCmd & pmsSync) && (pnstate->pmsLimit != 0))) { /* Is this not syncronous and a non-zero delay or a delayed step? */
255 setTimerReq(); /* Start the timers ticking */
256 break; /* We've stepped as far as we're going to... */
257 }
258
259 nstep = pnstate->pmsNext; /* Chain on to the next */
260 }
261
262 return;
263
264}
265
266/*
267 * Either park the stepper or force the step on a parked stepper for local processor only
268 *
269 */
270
271void pmsRunLocal(uint32_t nstep) {
272
273 struct per_proc_info *pp;
274 uint32_t cstate, ret, lastState;
275 pmsDef *pnstate, *pcstate;
276 uint64_t tb, nt, dur;
277 int cpu, i, j;
278 boolean_t intr;
279
280 if(!pmsInstalled) return; /* Ignore this if no step programs installed... */
281
282 intr = ml_set_interrupts_enabled(FALSE); /* No interruptions in here */
283
284 pp = getPerProc(); /* Get our per_proc */
285
286 if(nstep == pmsStartUp) { /* Should we start up? */
287 pmsCPUInit(); /* Get us up to full with high voltage and park */
288 nstep = pmsNormHigh; /* Change request to transition to normal high */
289 }
290
291 lastState = pp->pms.pmsState; /* Remember if we are parked now */
292
293 pmsSetStep(nstep, 1); /* Step to the new state */
294
295 if((lastState == pmsParked) && (pp->pms.pmsState != pmsParked)) { /* Did we just unpark? */
296 cpu = cpu_number(); /* Get our processor */
297 for(i = 0; i < pmsMaxStates; i++) { /* Step through the steps and clear the statistics since we were parked */
298 pmsCtls.pmsStats[cpu][i].stTime[0] = 0; /* Clear accumulated time - downward */
299 pmsCtls.pmsStats[cpu][i].stTime[1] = 0; /* Clear accumulated time - forward */
300 pmsCtls.pmsStats[cpu][i].stCnt[0] = 0; /* Clear transition count - downward */
301 pmsCtls.pmsStats[cpu][i].stCnt[1] = 0; /* Clear transition count - forward */
302 }
303 }
304
305 (void)ml_set_interrupts_enabled(intr); /* Restore interruptions */
306
307 return;
308
309}
310
311/*
312 * Control the Power Management Stepper.
313 * Called from user state by the superuser via a ppc system call.
314 * Interruptions disabled.
315 *
316 */
317
318int pmsCntrl(struct savearea *save) {
319
320 uint32_t request, nstep, reqsize, result, presult;
321 int ret, cpu;
322 kern_return_t kret;
323 pmsDef *ndefs;
324 struct per_proc_info *pp;
325
326 pp = getPerProc(); /* Get our per_proc */
327 cpu = cpu_number(); /* Get our processor */
328
329 if(!is_suser()) { /* We are better than most, */
330 save->save_r3 = KERN_FAILURE; /* so we will only talk to the superuser. */
331 return 1; /* Turn up our noses, say "harrumph," and walk away... */
332 }
333
334 if(save->save_r3 >= pmsCFree) { /* Can we understand the request? */
335 save->save_r3 = KERN_INVALID_ARGUMENT; /* What language are these guys talking in, anyway? */
336 return 1; /* Cock head like a confused puppy and run away... */
337 }
338
339 request = (int)save->save_r3; /* Remember the request */
340 reqsize = (uint32_t)save->save_r5; /* Get the size of the config table */
341
342 if(request == pmsCQuery) { /* Are we just checking? */
343 result = pmsCPUquery() & pmsCPU; /* Get the processor data and make sure there is no slop */
344 presult = 0; /* Assume nothing */
345 if((uint32_t)pmsQueryFunc) presult = pmsQueryFunc(cpu, pmsPlatformData); /* Go get the platform state */
346 result = result | (presult & (pmsXClk | pmsVoltage | pmsPowerID)); /* Merge the platform state with no slop */
347 save->save_r3 = result; /* Tell 'em... */
348 return 1;
349 }
350
351 if(request == pmsCExperimental) { /* Enter experimental mode? */
352
353 if(pmsInstalled || (pmsExperimental & 1)) { /* Are we already running or in experimental? */
354 save->save_r3 = KERN_FAILURE; /* Fail, since we are already running */
355 return 1;
356 }
357
358 pmsExperimental |= 1; /* Flip us into experimental but don't change other flags */
359
360 pmsCPUConf(); /* Configure for this machine */
361 pmsStart(); /* Start stepping */
362 save->save_r3 = KERN_SUCCESS; /* We are victorious... */
363 return 1;
364
365 }
366
367 if(request == pmsCCnfg) { /* Do some up-front checking before we commit to doing this */
368 if((reqsize > (pmsMaxStates * sizeof(pmsDef))) || (reqsize < (pmsFree * sizeof(pmsDef)))) { /* Check that the size is reasonable */
369 save->save_r3 = KERN_NO_SPACE; /* Tell them that they messed up */
370 return 1; /* l8r... */
371 }
372 }
373
374
375/*
376 * We are committed after here. If there are any errors detected, we shouldn't die, but we
377 * will be stuck in park.
378 *
379 * Also, we can possibly end up on another processor after the broadcast.
380 *
381 */
382
383 if(!hw_compare_and_store(0, 1, &pmsSyncrolator)) { /* Are we already doing this? */
384 save->save_r3 = KERN_RESOURCE_SHORTAGE; /* Tell them that we are already busy and to try again */
385 return 1; /* G'wan away and don't bother me... */
386 }
387 save->save_r3 = KERN_SUCCESS; /* Assume success */
388
389// NOTE: We will block in the following code until everyone has finished the prepare
390
391 pmsRun(pmsPrepCng); /* Get everyone parked and in a proper state for step table changes, including me */
392
393 if(request == pmsCPark) { /* Is all we're supposed to do park? */
394 pmsSyncrolator = 0; /* Free us up */
395 return 1; /* Well, then we're done... */
396 }
397
398 switch(request) { /* Select the routine */
399
400 case pmsCStart: /* Starts normal steppping */
401 nstep = pmsNormHigh; /* Set the request */
402 break;
403
404 case pmsCFLow: /* Forces low power */
405 nstep = pmsLow; /* Set request */
406 break;
407
408 case pmsCFHigh: /* Forces high power */
409 nstep = pmsHigh; /* Set request */
410 break;
411
412 case pmsCCnfg: /* Loads new stepper program */
413
414 if(!(ndefs = (pmsDef *)kalloc(reqsize))) { /* Get memory for the whole thing */
415 save->save_r3 = KERN_INVALID_ADDRESS; /* Return invalid address */
416 pmsSyncrolator = 0; /* Free us up */
417 return 1; /* All done... */
418 }
419
420 ret = copyin((user_addr_t)((unsigned int)(save->save_r4)), (void *)ndefs, reqsize); /* Get the new config table */
421 if(ret) { /* Hmmm, something went wrong with the copyin */
422 save->save_r3 = KERN_INVALID_ADDRESS; /* Return invalid address */
423 kfree((vm_offset_t)ndefs, reqsize); /* Free up the copied in data */
424 pmsSyncrolator = 0; /* Free us up */
425 return 1; /* All done... */
426 }
427
428 kret = pmsBuild(ndefs, reqsize, 0, 0, 0); /* Go build and replace the tables. Make sure we keep the old platform stuff */
429 if(kret) { /* Hmmm, something went wrong with the compilation */
430 save->save_r3 = kret; /* Pass back the passed back return code */
431 kfree((vm_offset_t)ndefs, reqsize); /* Free up the copied in data */
432 pmsSyncrolator = 0; /* Free us up */
433 return 1; /* All done... */
434 }
435
436 nstep = pmsNormHigh; /* Set the request */
437 break;
438
439 default:
440 panic("pmsCntrl: stepper control is so very, very confused = %08X\n", request);
441
442 }
443
444 pmsRun(nstep); /* Get everyone into step */
445 pmsSyncrolator = 0; /* Free us up */
446 return 1; /* All done... */
447
448}
449
450/*
451 * Broadcast a change to all processors including ourselves.
452 * This must transition before broadcasting because we may block and end up on a different processor.
453 *
454 * This will block until all processors have transitioned, so
455 * obviously, this can block.
456 *
457 * Called with interruptions disabled.
458 *
459 */
460
461void pmsRun(uint32_t nstep) {
462
463 pmsRunLocal(nstep); /* If we aren't parking (we are already parked), transition ourselves */
464 (void)cpu_broadcast(&pmsBroadcastWait, pmsRemote, nstep); /* Tell everyone else to do it too */
465
466 return;
467
468}
469
470/*
471 * Receive a broadcast and react.
472 * This is called from the interprocessor signal handler.
473 * We wake up the initiator after we are finished.
474 *
475 */
476
477void pmsRemote(uint32_t nstep) {
478
479 pmsRunLocal(nstep); /* Go set the step */
480 if(!hw_atomic_sub(&pmsBroadcastWait, 1)) { /* Drop the wait count */
481 thread_wakeup((event_t)&pmsBroadcastWait); /* If we were the last, wake up the signaller */
482 }
483 return;
484}
485
486
487/*
488 * Build the tables needed for the stepper. This includes both the step definitions and the step control table.
489 *
490 * We most absolutely need to be parked before this happens because we're gonna change the table.
491 * We're going to have to be pretty complete about checking for errors.
492 * Also, a copy is always made because we don't want to be crippled by not being able to change
493 * the table or description formats.
494 *
495 * We pass in a table of external functions and the new stepper def uses the corresponding
496 * indexes rather than actual function addresses. This is done so that a proper table can be
497 * built with the control syscall. It can't supply addresses, so the index has to do. We
498 * internalize the table so our caller does not need to keep it. Note that passing in a 0
499 * will use the current function table. Also note that entry 0 is reserved and must be 0,
500 * we will check and fail the build.
501 *
502 * The platformData parameter is a 32-bit word of data that is passed unaltered to the set function.
503 *
504 * The queryFunc parameter is the address of a function that will return the current state of the platform.
505 * The format of the data returned is the same as the platform specific portions of pmsSetCmd, i.e., pmsXClk,
506 * pmsVoltage, and any part of pmsPowerID that is maintained by the platform hardware (an example would be
507 * the values of the gpios that correspond to pmsPowerID). The value should be constructed by querying
508 * hardware rather than returning a value cached by software. One of the intents of this function is to
509 * help recover lost or determine initial power states.
510 *
511 */
512
513kern_return_t pmsBuild(pmsDef *pd, uint32_t pdsize, pmsSetFunc_t *functab, uint32_t platformData, pmsQueryFunc_t queryFunc) {
514
515 int steps, newsize, i, cstp, nstps, oldAltSize, xdsply;
516 uint32_t setf;
517 uint64_t nlimit;
518 pmsDef *newpd, *oldAlt;
519 boolean_t intr;
520
521 xdsply = (pmsExperimental & 3) != 0; /* Turn on kprintfs if requested or in experimental mode */
522
523 if(pdsize % sizeof(pmsDef)) return KERN_INVALID_ARGUMENT; /* Length not multiple of definition size */
524
525 steps = pdsize / sizeof(pmsDef); /* Get the number of steps supplied */
526
527 if((steps >= pmsMaxStates) || (steps < pmsFree)) /* Complain if too big or too small */
528 return KERN_INVALID_ARGUMENT; /* Squeak loudly!!! */
529
530 if((uint32_t)functab && (uint32_t)functab[0]) /* Verify that if they supplied a new function table, entry 0 is 0 */
531 return KERN_INVALID_ARGUMENT; /* Fail because they didn't reserve entry 0 */
532
533 if(xdsply) kprintf("\n StepID Down Next HWSel HWfun Limit\n");
534
535 for(i = 0; i < steps; i++) { /* Step through and verify the definitions */
536
537 if(xdsply) kprintf(" %6d %6d %6d %08X %6d %20lld\n", pd[i].pmsStepID, pd[i].pmsDown,
538 pd[i].pmsNext, pd[i].pmsSetCmd,
539 pd[i].sf.pmsSetFuncInd, pd[i].pmsLimit);
540
541 if((pd[i].pmsLimit != 0) && (pd[i].pmsLimit < 100ULL)) {
542 if(xdsply) kprintf("error step %3d: pmsLimit too small/n", i);
543