1 | /* |
2 | * Copyright (c) 2019 Apple Computer, Inc. All rights reserved. |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
5 | * |
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 |
24 | * limitations under the License. |
25 | * |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
27 | */ |
28 | |
29 | extern "C" { |
30 | #include <kern/debug.h> |
31 | #include <pexpert/pexpert.h> |
32 | #include <pexpert/arm64/board_config.h> |
33 | }; |
34 | |
35 | #include <kern/bits.h> |
36 | #include <kern/processor.h> |
37 | #include <kern/thread.h> |
38 | #include <kperf/kperf.h> |
39 | #include <machine/machine_routines.h> |
40 | #include <libkern/OSAtomic.h> |
41 | #include <libkern/c++/OSCollection.h> |
42 | #include <IOKit/IODeviceTreeSupport.h> |
43 | #include <IOKit/IOLib.h> |
44 | #include <IOKit/IOPlatformActions.h> |
45 | #include <IOKit/IOPMGR.h> |
46 | #include <IOKit/IOReturn.h> |
47 | #include <IOKit/IOService.h> |
48 | #include <IOKit/PassthruInterruptController.h> |
49 | #include <IOKit/pwr_mgt/RootDomain.h> |
50 | #include <IOKit/pwr_mgt/IOPMPrivate.h> |
51 | #include <Kernel/IOKitKernelInternal.h> |
52 | |
53 | #if USE_APPLEARMSMP |
54 | |
55 | // FIXME: These are in <kern/misc_protos.h> but that file has other deps that aren't being resolved |
56 | extern "C" void console_suspend(); |
57 | extern "C" void console_resume(); |
58 | |
59 | static PassthruInterruptController *gCPUIC; |
60 | static IOPMGR *gPMGR; |
61 | static IOInterruptController *gAIC; |
62 | static bool aic_ipis = false; |
63 | static const ml_topology_info *topology_info; |
64 | |
65 | // cpu_id of the boot processor |
66 | static unsigned int boot_cpu; |
67 | |
68 | // array index is a cpu_id (so some elements may be NULL) |
69 | static processor_t *machProcessors; |
70 | |
71 | bool cluster_power_supported = false; |
72 | static uint64_t cpu_power_state_mask; |
73 | static uint64_t all_clusters_mask; |
74 | static uint64_t online_clusters_mask; |
75 | |
76 | static void |
77 | processor_idle_wrapper(cpu_id_t /*cpu_id*/, boolean_t enter, uint64_t *new_timeout_ticks) |
78 | { |
79 | if (enter) { |
80 | gPMGR->enterCPUIdle(newIdleTimeoutTicks: new_timeout_ticks); |
81 | } else { |
82 | gPMGR->exitCPUIdle(newIdleTimeoutTicks: new_timeout_ticks); |
83 | } |
84 | } |
85 | |
86 | static void |
87 | idle_timer_wrapper(void */*refCon*/, uint64_t *new_timeout_ticks) |
88 | { |
89 | gPMGR->updateCPUIdle(newIdleTimeoutTicks: new_timeout_ticks); |
90 | } |
91 | |
92 | static OSDictionary * |
93 | matching_dict_for_cpu_id(unsigned int cpu_id) |
94 | { |
95 | // The cpu-id property in EDT doesn't necessarily match the dynamically |
96 | // assigned logical ID in XNU, so look up the cpu node by the physical |
97 | // (cluster/core) ID instead. |
98 | OSSymbolConstPtr cpuTypeSymbol = OSSymbol::withCString(cString: "cpu" ); |
99 | OSSymbolConstPtr cpuIdSymbol = OSSymbol::withCString(cString: "reg" ); |
100 | OSDataPtr cpuId = OSData::withValue(value: topology_info->cpus[cpu_id].phys_id); |
101 | |
102 | OSDictionary *propMatch = OSDictionary::withCapacity(capacity: 4); |
103 | propMatch->setObject(aKey: gIODTTypeKey, anObject: cpuTypeSymbol); |
104 | propMatch->setObject(aKey: cpuIdSymbol, anObject: cpuId); |
105 | |
106 | OSDictionary *matching = IOService::serviceMatching(className: "IOPlatformDevice" ); |
107 | matching->setObject(aKey: gIOPropertyMatchKey, anObject: propMatch); |
108 | |
109 | propMatch->release(); |
110 | cpuTypeSymbol->release(); |
111 | cpuIdSymbol->release(); |
112 | cpuId->release(); |
113 | |
114 | return matching; |
115 | } |
116 | |
117 | static void |
118 | register_aic_handlers(const ml_topology_cpu *cpu_info, |
119 | ipi_handler_t ipi_handler, |
120 | perfmon_interrupt_handler_func pmi_handler) |
121 | { |
122 | OSDictionary *matching = matching_dict_for_cpu_id(cpu_id: cpu_info->cpu_id); |
123 | IOService *cpu = IOService::waitForMatchingService(matching, UINT64_MAX); |
124 | matching->release(); |
125 | |
126 | OSArray *irqs = (OSArray *) cpu->getProperty(aKey: gIOInterruptSpecifiersKey); |
127 | if (!irqs) { |
128 | panic("Error finding interrupts for CPU %d" , cpu_info->cpu_id); |
129 | } |
130 | |
131 | unsigned int irqcount = irqs->getCount(); |
132 | |
133 | if (irqcount == 3) { |
134 | // Legacy configuration, for !HAS_IPI chips (pre-Skye). |
135 | if (cpu->registerInterrupt(source: 0, NULL, handler: (IOInterruptAction)ipi_handler, NULL) != kIOReturnSuccess || |
136 | cpu->enableInterrupt(source: 0) != kIOReturnSuccess || |
137 | cpu->registerInterrupt(source: 2, NULL, handler: (IOInterruptAction)ipi_handler, NULL) != kIOReturnSuccess || |
138 | cpu->enableInterrupt(source: 2) != kIOReturnSuccess) { |
139 | panic("Error registering IPIs" ); |
140 | } |
141 | #if !defined(HAS_IPI) |
142 | // Ideally this should be decided by EDT, but first we need to update EDT |
143 | // to default to fast IPIs on modern platforms. |
144 | aic_ipis = true; |
145 | #endif |
146 | } |
147 | |
148 | // Conditional, because on Skye and later, we use an FIQ instead of an external IRQ. |
149 | if (pmi_handler && irqcount == 1) { |
150 | if (cpu->registerInterrupt(source: 1, NULL, handler: (IOInterruptAction)(void (*)(void))pmi_handler, NULL) != kIOReturnSuccess || |
151 | cpu->enableInterrupt(source: 1) != kIOReturnSuccess) { |
152 | panic("Error registering PMI" ); |
153 | } |
154 | } |
155 | } |
156 | |
157 | static void |
158 | cpu_boot_thread(void */*unused0*/, wait_result_t /*unused1*/) |
159 | { |
160 | OSDictionary *matching = IOService::serviceMatching(className: "IOPlatformExpert" ); |
161 | IOService::waitForMatchingService(matching, UINT64_MAX); |
162 | matching->release(); |
163 | |
164 | gCPUIC = new PassthruInterruptController; |
165 | if (!gCPUIC || !gCPUIC->init()) { |
166 | panic("Can't initialize PassthruInterruptController" ); |
167 | } |
168 | gAIC = static_cast<IOInterruptController *>(gCPUIC->waitForChildController()); |
169 | |
170 | ml_set_max_cpus(max_cpus: topology_info->max_cpu_id + 1); |
171 | |
172 | #if XNU_CLUSTER_POWER_DOWN |
173 | cluster_power_supported = true; |
174 | /* |
175 | * If a boot-arg is set that allows threads to be bound |
176 | * to a cpu or cluster, cluster_power_supported must |
177 | * default to false. |
178 | */ |
179 | #ifdef CONFIG_XNUPOST |
180 | uint64_t kernel_post = 0; |
181 | PE_parse_boot_argn("kernPOST" , &kernel_post, sizeof(kernel_post)); |
182 | if (kernel_post != 0) { |
183 | cluster_power_supported = false; |
184 | } |
185 | #endif |
186 | if (PE_parse_boot_argn("enable_skstb" , NULL, 0)) { |
187 | cluster_power_supported = false; |
188 | } |
189 | if (PE_parse_boot_argn("enable_skstsct" , NULL, 0)) { |
190 | cluster_power_supported = false; |
191 | } |
192 | #endif |
193 | PE_parse_boot_argn(arg_string: "cluster_power" , arg_ptr: &cluster_power_supported, max_arg: sizeof(cluster_power_supported)); |
194 | |
195 | matching = IOService::serviceMatching(className: "IOPMGR" ); |
196 | gPMGR = OSDynamicCast(IOPMGR, |
197 | IOService::waitForMatchingService(matching, UINT64_MAX)); |
198 | matching->release(); |
199 | |
200 | const size_t array_size = (topology_info->max_cpu_id + 1) * sizeof(*machProcessors); |
201 | machProcessors = static_cast<processor_t *>(zalloc_permanent(array_size, ZALIGN_PTR)); |
202 | |
203 | for (unsigned int cpu = 0; cpu < topology_info->num_cpus; cpu++) { |
204 | const ml_topology_cpu *cpu_info = &topology_info->cpus[cpu]; |
205 | const unsigned int cpu_id = cpu_info->cpu_id; |
206 | ml_processor_info_t this_processor_info; |
207 | ipi_handler_t ipi_handler; |
208 | perfmon_interrupt_handler_func pmi_handler; |
209 | |
210 | memset(s: &this_processor_info, c: 0, n: sizeof(this_processor_info)); |
211 | this_processor_info.cpu_id = reinterpret_cast<cpu_id_t>(cpu_id); |
212 | this_processor_info.phys_id = cpu_info->phys_id; |
213 | this_processor_info.log_id = cpu_id; |
214 | this_processor_info.cluster_id = cpu_info->cluster_id; |
215 | this_processor_info.cluster_type = cpu_info->cluster_type; |
216 | this_processor_info.l2_cache_size = cpu_info->l2_cache_size; |
217 | this_processor_info.l2_cache_id = cpu_info->l2_cache_id; |
218 | this_processor_info.l3_cache_size = cpu_info->l3_cache_size; |
219 | this_processor_info.l3_cache_id = cpu_info->l3_cache_id; |
220 | |
221 | gPMGR->initCPUIdle(info: &this_processor_info); |
222 | this_processor_info.processor_idle = &processor_idle_wrapper; |
223 | this_processor_info.idle_timer = &idle_timer_wrapper; |
224 | |
225 | kern_return_t result = ml_processor_register(ml_processor_info: &this_processor_info, |
226 | processor: &machProcessors[cpu_id], ipi_handler: &ipi_handler, pmi_handler: &pmi_handler); |
227 | if (result == KERN_FAILURE) { |
228 | panic("ml_processor_register failed: %d" , result); |
229 | } |
230 | register_aic_handlers(cpu_info, ipi_handler, pmi_handler); |
231 | |
232 | if (processor_start(processor: machProcessors[cpu_id]) != KERN_SUCCESS) { |
233 | panic("processor_start failed" ); |
234 | } |
235 | } |
236 | ml_cpu_init_completed(); |
237 | IOService::publishResource(key: gIOAllCPUInitializedKey, value: kOSBooleanTrue); |
238 | } |
239 | |
240 | void |
241 | IOCPUInitialize(void) |
242 | { |
243 | topology_info = ml_get_topology_info(); |
244 | boot_cpu = topology_info->boot_cpu->cpu_id; |
245 | |
246 | for (unsigned int i = 0; i < topology_info->num_clusters; i++) { |
247 | bit_set(all_clusters_mask, topology_info->clusters[i].cluster_id); |
248 | } |
249 | // iBoot powers up every cluster (at least for now) |
250 | online_clusters_mask = all_clusters_mask; |
251 | |
252 | thread_t thread; |
253 | kernel_thread_start(continuation: &cpu_boot_thread, NULL, new_thread: &thread); |
254 | thread_set_thread_name(th: thread, name: "cpu_boot_thread" ); |
255 | thread_deallocate(thread); |
256 | } |
257 | |
258 | static unsigned int |
259 | target_to_cpu_id(cpu_id_t in) |
260 | { |
261 | return (unsigned int)(uintptr_t)in; |
262 | } |
263 | |
264 | // Release a secondary CPU from reset. Runs from a different CPU (obviously). |
265 | kern_return_t |
266 | PE_cpu_start(cpu_id_t target, |
267 | vm_offset_t /*start_paddr*/, vm_offset_t /*arg_paddr*/) |
268 | { |
269 | unsigned int cpu_id = target_to_cpu_id(in: target); |
270 | |
271 | if (cpu_id != boot_cpu) { |
272 | #if APPLEVIRTUALPLATFORM |
273 | /* When running virtualized, the reset vector address must be passed to PMGR explicitly */ |
274 | extern unsigned int LowResetVectorBase; |
275 | gPMGR->enableCPUCore(cpu_id, entry_pa: ml_vtophys(vaddr: (vm_offset_t)&LowResetVectorBase)); |
276 | #else |
277 | gPMGR->enableCPUCore(cpu_id, 0); |
278 | #endif |
279 | } |
280 | return KERN_SUCCESS; |
281 | } |
282 | |
283 | // Initialize a CPU when it first comes up. Runs on the target CPU. |
284 | // |bootb| is true on the initial boot, false on S2R resume. |
285 | void |
286 | PE_cpu_machine_init(cpu_id_t target, boolean_t bootb) |
287 | { |
288 | unsigned int cpu_id = target_to_cpu_id(in: target); |
289 | |
290 | if (!bootb && cpu_id == boot_cpu && ml_is_quiescing()) { |
291 | IOCPURunPlatformActiveActions(); |
292 | } |
293 | |
294 | ml_broadcast_cpu_event(event: CPU_BOOTED, cpu_or_cluster: cpu_id); |
295 | |
296 | // Send myself an IPI to clear SIGPdisabled. Hang here if IPIs are broken. |
297 | // (Probably only works on the boot CPU.) |
298 | PE_cpu_signal(source: target, target); |
299 | while (ml_get_interrupts_enabled() && !ml_cpu_signal_is_enabled()) { |
300 | OSMemoryBarrier(); |
301 | } |
302 | } |
303 | |
304 | void |
305 | PE_cpu_halt(cpu_id_t target) |
306 | { |
307 | unsigned int cpu_id = target_to_cpu_id(in: target); |
308 | processor_exit(processor: machProcessors[cpu_id]); |
309 | } |
310 | |
311 | void |
312 | PE_cpu_signal(cpu_id_t /*source*/, cpu_id_t target) |
313 | { |
314 | struct ml_topology_cpu *cpu = &topology_info->cpus[target_to_cpu_id(in: target)]; |
315 | if (aic_ipis) { |
316 | gAIC->sendIPI(cpu_id: cpu->cpu_id, deferred: false); |
317 | } else { |
318 | ml_cpu_signal(cpu_id: cpu->phys_id); |
319 | } |
320 | } |
321 | |
322 | void |
323 | PE_cpu_signal_deferred(cpu_id_t /*source*/, cpu_id_t target) |
324 | { |
325 | struct ml_topology_cpu *cpu = &topology_info->cpus[target_to_cpu_id(in: target)]; |
326 | if (aic_ipis) { |
327 | gAIC->sendIPI(cpu_id: cpu->cpu_id, deferred: true); |
328 | } else { |
329 | ml_cpu_signal_deferred(cpu_id: cpu->phys_id); |
330 | } |
331 | } |
332 | |
333 | void |
334 | PE_cpu_signal_cancel(cpu_id_t /*source*/, cpu_id_t target) |
335 | { |
336 | struct ml_topology_cpu *cpu = &topology_info->cpus[target_to_cpu_id(in: target)]; |
337 | if (aic_ipis) { |
338 | gAIC->cancelDeferredIPI(cpu_id: cpu->cpu_id); |
339 | } else { |
340 | ml_cpu_signal_retract(cpu_id: cpu->phys_id); |
341 | } |
342 | } |
343 | |
344 | // Brings down one CPU core for S2R. Runs on the target CPU. |
345 | void |
346 | PE_cpu_machine_quiesce(cpu_id_t target) |
347 | { |
348 | unsigned int cpu_id = target_to_cpu_id(in: target); |
349 | |
350 | if (cpu_id == boot_cpu) { |
351 | IOCPURunPlatformQuiesceActions(); |
352 | } else { |
353 | gPMGR->disableCPUCore(cpu_id); |
354 | } |
355 | |
356 | ml_broadcast_cpu_event(event: CPU_DOWN, cpu_or_cluster: cpu_id); |
357 | ml_arm_sleep(); |
358 | } |
359 | |
360 | static bool |
361 | is_cluster_powering_down(int cpu_id) |
362 | { |
363 | // Don't kill the cluster power if any other CPUs in this cluster are still awake |
364 | unsigned int target_cluster_id = topology_info->cpus[cpu_id].cluster_id; |
365 | for (int i = 0; i < topology_info->num_cpus; i++) { |
366 | if (topology_info->cpus[i].cluster_id == target_cluster_id && |
367 | cpu_id != i && |
368 | bit_test(cpu_power_state_mask, i)) { |
369 | return false; |
370 | } |
371 | } |
372 | return true; |
373 | } |
374 | |
375 | // Takes one secondary CPU core offline at runtime. Runs on the target CPU. |
376 | // Returns true if the platform code should go into deep sleep WFI, false otherwise. |
377 | bool |
378 | PE_cpu_down(cpu_id_t target) |
379 | { |
380 | unsigned int cpu_id = target_to_cpu_id(in: target); |
381 | assert(cpu_id != boot_cpu); |
382 | gPMGR->disableCPUCore(cpu_id); |
383 | ml_broadcast_cpu_event(event: CPU_DOWN, cpu_or_cluster: cpu_id); |
384 | return cluster_power_supported && is_cluster_powering_down(cpu_id); |
385 | } |
386 | |
387 | void |
388 | PE_handle_ext_interrupt(void) |
389 | { |
390 | gCPUIC->externalInterrupt(); |
391 | } |
392 | |
393 | void |
394 | PE_cpu_power_disable(int cpu_id) |
395 | { |
396 | bit_clear(cpu_power_state_mask, cpu_id); |
397 | if (!cluster_power_supported || cpu_id == boot_cpu) { |
398 | return; |
399 | } |
400 | |
401 | // Don't kill the cluster power if any other CPUs in this cluster are still awake |
402 | unsigned int target_cluster_id = topology_info->cpus[cpu_id].cluster_id; |
403 | if (!is_cluster_powering_down(cpu_id)) { |
404 | return; |
405 | } |
406 | |
407 | if (processor_should_kprintf(processor: machProcessors[cpu_id], starting: false)) { |
408 | kprintf(fmt: "%s>turning off power to cluster %d\n" , __FUNCTION__, target_cluster_id); |
409 | } |
410 | ml_broadcast_cpu_event(event: CLUSTER_EXIT_REQUESTED, cpu_or_cluster: target_cluster_id); |
411 | bit_clear(online_clusters_mask, target_cluster_id); |
412 | gPMGR->disableCPUCluster(cluster_id: target_cluster_id); |
413 | } |
414 | |
415 | bool |
416 | PE_cpu_power_check_kdp(int cpu_id) |
417 | { |
418 | if (!cluster_power_supported) { |
419 | return true; |
420 | } |
421 | |
422 | unsigned int cluster_id = topology_info->cpus[cpu_id].cluster_id; |
423 | return bit_test(online_clusters_mask, cluster_id); |
424 | } |
425 | |
426 | void |
427 | PE_cpu_power_enable(int cpu_id) |
428 | { |
429 | bit_set(cpu_power_state_mask, cpu_id); |
430 | if (!cluster_power_supported || cpu_id == boot_cpu) { |
431 | return; |
432 | } |
433 | |
434 | unsigned int cluster_id = topology_info->cpus[cpu_id].cluster_id; |
435 | if (!bit_test(online_clusters_mask, cluster_id)) { |
436 | if (processor_should_kprintf(processor: machProcessors[cpu_id], starting: true)) { |
437 | kprintf(fmt: "%s>turning on power to cluster %d\n" , __FUNCTION__, cluster_id); |
438 | } |
439 | gPMGR->enableCPUCluster(cluster_id); |
440 | bit_set(online_clusters_mask, cluster_id); |
441 | ml_broadcast_cpu_event(event: CLUSTER_ACTIVE, cpu_or_cluster: cluster_id); |
442 | } |
443 | } |
444 | |
445 | void |
446 | IOCPUSleepKernel(void) |
447 | { |
448 | IOPMrootDomain *rootDomain = IOService::getPMRootDomain(); |
449 | unsigned int i; |
450 | |
451 | printf("IOCPUSleepKernel enter\n" ); |
452 | sched_override_available_cores_for_sleep(); |
453 | |
454 | rootDomain->tracePoint( point: kIOPMTracePointSleepPlatformActions ); |
455 | IOPlatformActionsPreSleep(); |
456 | rootDomain->tracePoint( point: kIOPMTracePointSleepCPUs ); |
457 | |
458 | integer_t old_pri; |
459 | thread_t self = current_thread(); |
460 | |
461 | /* |
462 | * We need to boost this thread's priority to the maximum kernel priority to |
463 | * ensure we can urgently preempt ANY thread currently executing on the |
464 | * target CPU. Note that realtime threads have their own mechanism to eventually |
465 | * demote their priority below MAXPRI_KERNEL if they hog the CPU for too long. |
466 | */ |
467 | old_pri = thread_kern_get_pri(thr: self); |
468 | thread_kern_set_pri(thr: self, pri: thread_kern_get_kernel_maxpri()); |
469 | |
470 | // Sleep the non-boot CPUs. |
471 | ml_set_is_quiescing(true); |
472 | for (i = 0; i < topology_info->num_cpus; i++) { |
473 | unsigned int cpu_id = topology_info->cpus[i].cpu_id; |
474 | if (cpu_id != boot_cpu) { |
475 | processor_exit(processor: machProcessors[cpu_id]); |
476 | } |
477 | } |
478 | |
479 | console_suspend(); |
480 | |
481 | rootDomain->tracePoint( point: kIOPMTracePointSleepPlatformDriver ); |
482 | rootDomain->stop_watchdog_timer(); |
483 | |
484 | /* |
485 | * Now sleep the boot CPU, including calling the kQueueQuiesce actions. |
486 | * The system sleeps here. |
487 | */ |
488 | processor_exit(processor: machProcessors[boot_cpu]); |
489 | |
490 | /* |
491 | * The system is now coming back from sleep on the boot CPU. |
492 | * The kQueueActive actions have already been called. |
493 | * |
494 | * The reconfig engine is programmed to power up all clusters on S2R resume. |
495 | */ |
496 | online_clusters_mask = all_clusters_mask; |
497 | |
498 | /* |
499 | * processor_start() never gets called for the boot CPU, so it needs to |
500 | * be explicitly marked as online here. |
501 | */ |
502 | PE_cpu_power_enable(cpu_id: boot_cpu); |
503 | |
504 | ml_set_is_quiescing(false); |
505 | |
506 | rootDomain->start_watchdog_timer(); |
507 | |
508 | console_resume(); |
509 | |
510 | rootDomain->tracePoint( point: kIOPMTracePointWakeCPUs ); |
511 | |
512 | for (i = 0; i < topology_info->num_cpus; i++) { |
513 | unsigned int cpu_id = topology_info->cpus[i].cpu_id; |
514 | if (cpu_id != boot_cpu) { |
515 | processor_start(processor: machProcessors[cpu_id]); |
516 | } |
517 | } |
518 | |
519 | rootDomain->tracePoint( point: kIOPMTracePointWakePlatformActions ); |
520 | IOPlatformActionsPostResume(); |
521 | |
522 | sched_restore_available_cores_after_sleep(); |
523 | |
524 | thread_kern_set_pri(thr: self, pri: old_pri); |
525 | printf("IOCPUSleepKernel exit\n" ); |
526 | } |
527 | |
528 | #endif /* USE_APPLEARMSMP */ |
529 | |