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