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 <kern/queue.h> |
32 | } |
33 | |
34 | #include <kern/sched_prim.h> |
35 | #include <machine/machine_routines.h> |
36 | #include <IOKit/IOLib.h> |
37 | #include <IOKit/IOPlatformExpert.h> |
38 | #include <IOKit/IOKitKeysPrivate.h> |
39 | #include <IOKit/IOPlatformActions.h> |
40 | #include "IOKitKernelInternal.h" |
41 | |
42 | static IOLock *gIOPlatformActionsLock; |
43 | |
44 | typedef kern_return_t (*iocpu_platform_action_t)(void * refcon0, void * refcon1, uint32_t priority, |
45 | void * param1, void * param2, void * param3, |
46 | const char * name, uint64_t platform_action_flags); |
47 | |
48 | struct iocpu_platform_action_entry { |
49 | queue_chain_t link; |
50 | iocpu_platform_action_t action; |
51 | int32_t priority; |
52 | const char * name; |
53 | void * refcon0; |
54 | void * refcon1; |
55 | boolean_t callout_in_progress; |
56 | struct iocpu_platform_action_entry * alloc_list; |
57 | }; |
58 | typedef struct iocpu_platform_action_entry iocpu_platform_action_entry_t; |
59 | |
60 | enum { |
61 | kQueueSleep = 0, |
62 | kQueueWake = 1, |
63 | kQueueQuiesce = 2, |
64 | kQueueActive = 3, |
65 | kQueueHaltRestart = 4, |
66 | kQueuePanic = 5, |
67 | kQueueCount = 6 |
68 | }; |
69 | |
70 | #define PLATFORM_ACTION_FLAGS_ALLOW_NESTED_CALLOUTS 1 |
71 | #define PLATFORM_ACTION_FLAGS_NO_LOGGING 2 |
72 | |
73 | const OSSymbol * gIOPlatformSleepActionKey; |
74 | const OSSymbol * gIOPlatformWakeActionKey; |
75 | const OSSymbol * gIOPlatformQuiesceActionKey; |
76 | const OSSymbol * gIOPlatformActiveActionKey; |
77 | const OSSymbol * gIOPlatformHaltRestartActionKey; |
78 | const OSSymbol * gIOPlatformPanicActionKey; |
79 | |
80 | static queue_head_t gActionQueues[kQueueCount]; |
81 | static const OSSymbol * gActionSymbols[kQueueCount]; |
82 | |
83 | static bool |
84 | IOInstallServicePlatformAction(IOService * service, uint32_t qidx); |
85 | |
86 | static void |
87 | iocpu_add_platform_action(queue_head_t * queue, iocpu_platform_action_entry_t * entry) |
88 | { |
89 | iocpu_platform_action_entry_t * next; |
90 | |
91 | queue_iterate(queue, next, iocpu_platform_action_entry_t *, link) |
92 | { |
93 | if (next->priority > entry->priority) { |
94 | queue_insert_before(queue, entry, next, iocpu_platform_action_entry_t *, link); |
95 | return; |
96 | } |
97 | } |
98 | queue_enter(queue, entry, iocpu_platform_action_entry_t *, link); // at tail |
99 | } |
100 | |
101 | static void |
102 | iocpu_remove_platform_action(iocpu_platform_action_entry_t * entry) |
103 | { |
104 | remque(elt: &entry->link); |
105 | } |
106 | |
107 | static kern_return_t |
108 | iocpu_run_platform_actions(queue_head_t * queue, uint32_t first_priority, uint32_t last_priority, |
109 | void * param1, void * param2, void * param3, uint64_t platform_action_flags) |
110 | { |
111 | kern_return_t ret = KERN_SUCCESS; |
112 | kern_return_t result = KERN_SUCCESS; |
113 | iocpu_platform_action_entry_t * next; |
114 | boolean_t allow_nested_callouts = (platform_action_flags & PLATFORM_ACTION_FLAGS_ALLOW_NESTED_CALLOUTS); |
115 | |
116 | queue_iterate(queue, next, iocpu_platform_action_entry_t *, link) |
117 | { |
118 | uint32_t pri = (next->priority < 0) ? -next->priority : next->priority; |
119 | if ((pri >= first_priority) && (pri <= last_priority)) { |
120 | if (!allow_nested_callouts && !next->callout_in_progress) { |
121 | next->callout_in_progress = TRUE; |
122 | ret = (*next->action)(next->refcon0, next->refcon1, pri, param1, param2, param3, next->name, platform_action_flags); |
123 | next->callout_in_progress = FALSE; |
124 | } else if (allow_nested_callouts) { |
125 | ret = (*next->action)(next->refcon0, next->refcon1, pri, param1, param2, param3, next->name, platform_action_flags); |
126 | } |
127 | } |
128 | if (KERN_SUCCESS == result) { |
129 | result = ret; |
130 | } |
131 | } |
132 | return result; |
133 | } |
134 | |
135 | extern "C" kern_return_t |
136 | IOCPURunPlatformQuiesceActions(void) |
137 | { |
138 | assert(preemption_enabled() == false); |
139 | return iocpu_run_platform_actions(queue: &gActionQueues[kQueueQuiesce], first_priority: 0, last_priority: 0U - 1, |
140 | NULL, NULL, NULL, PLATFORM_ACTION_FLAGS_ALLOW_NESTED_CALLOUTS); |
141 | } |
142 | |
143 | extern "C" kern_return_t |
144 | IOCPURunPlatformActiveActions(void) |
145 | { |
146 | assert(preemption_enabled() == false); |
147 | ml_hibernate_active_pre(); |
148 | kern_return_t result = iocpu_run_platform_actions(queue: &gActionQueues[kQueueActive], first_priority: 0, last_priority: 0U - 1, |
149 | NULL, NULL, NULL, PLATFORM_ACTION_FLAGS_ALLOW_NESTED_CALLOUTS); |
150 | ml_hibernate_active_post(); |
151 | return result; |
152 | } |
153 | |
154 | extern "C" kern_return_t |
155 | IOCPURunPlatformHaltRestartActions(uint32_t message) |
156 | { |
157 | if (!gActionQueues[kQueueHaltRestart].next) { |
158 | return kIOReturnNotReady; |
159 | } |
160 | return iocpu_run_platform_actions(queue: &gActionQueues[kQueueHaltRestart], first_priority: 0, last_priority: 0U - 1, |
161 | param1: (void *)(uintptr_t) message, NULL, NULL, PLATFORM_ACTION_FLAGS_ALLOW_NESTED_CALLOUTS); |
162 | } |
163 | |
164 | extern "C" kern_return_t |
165 | IOCPURunPlatformPanicActions(uint32_t message, uint32_t details) |
166 | { |
167 | // Don't allow nested calls of panic actions |
168 | if (!gActionQueues[kQueuePanic].next) { |
169 | return kIOReturnNotReady; |
170 | } |
171 | uint64_t platform_action_flags = 0; |
172 | |
173 | if (!verbose_panic_flow_logging) { |
174 | platform_action_flags = PLATFORM_ACTION_FLAGS_NO_LOGGING; |
175 | } |
176 | return iocpu_run_platform_actions(queue: &gActionQueues[kQueuePanic], first_priority: 0, last_priority: 0U - 1, |
177 | param1: (void *)(uintptr_t) message, param2: (void *)(uintptr_t) details, NULL, platform_action_flags); |
178 | } |
179 | |
180 | extern "C" kern_return_t |
181 | IOCPURunPlatformPanicSyncAction(void *addr, uint32_t offset, uint32_t len) |
182 | { |
183 | PE_panic_save_context_t context = { |
184 | .psc_buffer = addr, |
185 | .psc_offset = offset, |
186 | .psc_length = len |
187 | }; |
188 | |
189 | // Don't allow nested calls of panic actions |
190 | if (!gActionQueues[kQueuePanic].next) { |
191 | return kIOReturnNotReady; |
192 | } |
193 | return iocpu_run_platform_actions(queue: &gActionQueues[kQueuePanic], first_priority: 0, last_priority: 0U - 1, |
194 | param1: (void *)(uintptr_t)(kPEPanicSync), param2: &context, NULL, FALSE); |
195 | } |
196 | |
197 | void |
198 | IOPlatformActionsPreSleep(void) |
199 | { |
200 | iocpu_run_platform_actions(queue: &gActionQueues[kQueueSleep], first_priority: 0, last_priority: 0U - 1, |
201 | NULL, NULL, NULL, PLATFORM_ACTION_FLAGS_ALLOW_NESTED_CALLOUTS); |
202 | } |
203 | |
204 | void |
205 | IOPlatformActionsPostResume(void) |
206 | { |
207 | iocpu_run_platform_actions(queue: &gActionQueues[kQueueWake], first_priority: 0, last_priority: 0U - 1, |
208 | NULL, NULL, NULL, PLATFORM_ACTION_FLAGS_ALLOW_NESTED_CALLOUTS); |
209 | } |
210 | |
211 | void |
212 | IOPlatformActionsInitialize(void) |
213 | { |
214 | gIOPlatformActionsLock = IOLockAlloc(); |
215 | |
216 | for (uint32_t qidx = kQueueSleep; qidx < kQueueCount; qidx++) { |
217 | queue_init(&gActionQueues[qidx]); |
218 | } |
219 | |
220 | gIOPlatformSleepActionKey = gActionSymbols[kQueueSleep] |
221 | = OSSymbol::withCStringNoCopy(kIOPlatformSleepActionKey); |
222 | gIOPlatformWakeActionKey = gActionSymbols[kQueueWake] |
223 | = OSSymbol::withCStringNoCopy(kIOPlatformWakeActionKey); |
224 | gIOPlatformQuiesceActionKey = gActionSymbols[kQueueQuiesce] |
225 | = OSSymbol::withCStringNoCopy(kIOPlatformQuiesceActionKey); |
226 | gIOPlatformActiveActionKey = gActionSymbols[kQueueActive] |
227 | = OSSymbol::withCStringNoCopy(kIOPlatformActiveActionKey); |
228 | gIOPlatformHaltRestartActionKey = gActionSymbols[kQueueHaltRestart] |
229 | = OSSymbol::withCStringNoCopy(kIOPlatformHaltRestartActionKey); |
230 | gIOPlatformPanicActionKey = gActionSymbols[kQueuePanic] |
231 | = OSSymbol::withCStringNoCopy(kIOPlatformPanicActionKey); |
232 | } |
233 | |
234 | static kern_return_t |
235 | IOServicePlatformAction(void * refcon0, void * refcon1, uint32_t priority, |
236 | void * param1, void * param2, void * param3, |
237 | const char * service_name, uint64_t platform_action_flags) |
238 | { |
239 | IOReturn ret; |
240 | IOService * service = (IOService *) refcon0; |
241 | const OSSymbol * function = (const OSSymbol *) refcon1; |
242 | |
243 | if (!(platform_action_flags & PLATFORM_ACTION_FLAGS_NO_LOGGING)) { |
244 | IOLog(format: "%s -> %s\n" , function->getCStringNoCopy(), service_name); |
245 | } |
246 | |
247 | /* |
248 | * We intentionally don't trace params that are kernel addresses, |
249 | * and truncate 64 bit values to 32 bit, so they all fit into |
250 | * one tracepoint along with IOService registry id. |
251 | */ |
252 | SOCD_TRACE_XNU_START(PLATFORM_ACTION, |
253 | ADDR(function->getCStringNoCopy()), |
254 | ADDR(service->getMetaClass()), |
255 | PACK_2X32(VALUE(param1), VALUE(service->getRegistryEntryID())), |
256 | PACK_2X32(VALUE(param3), VALUE(param2))); |
257 | |
258 | ret = service->callPlatformFunction(functionName: function, waitForFunction: false, |
259 | param1: (void *)(uintptr_t) priority, param2: param1, param3: param2, param4: param3); |
260 | |
261 | SOCD_TRACE_XNU_END(PLATFORM_ACTION, |
262 | ADDR(function->getCStringNoCopy()), |
263 | ADDR(service->getMetaClass()), |
264 | PACK_2X32(VALUE(param1), VALUE(service->getRegistryEntryID())), |
265 | PACK_2X32(VALUE(param3), VALUE(param2))); |
266 | |
267 | return ret; |
268 | } |
269 | |
270 | static bool |
271 | IOInstallServicePlatformAction(IOService * service, uint32_t qidx) |
272 | { |
273 | iocpu_platform_action_entry_t * entry; |
274 | OSNumber * num; |
275 | uint32_t priority; |
276 | const OSSymbol * key = gActionSymbols[qidx]; |
277 | queue_head_t * queue = &gActionQueues[qidx]; |
278 | bool reverse; |
279 | |
280 | num = OSDynamicCast(OSNumber, service->getProperty(key)); |
281 | if (!num) { |
282 | return true; |
283 | } |
284 | |
285 | reverse = false; |
286 | switch (qidx) { |
287 | case kQueueWake: |
288 | case kQueueActive: |
289 | reverse = true; |
290 | break; |
291 | } |
292 | queue_iterate(queue, entry, iocpu_platform_action_entry_t *, link) |
293 | { |
294 | if (service == entry->refcon0) { |
295 | return true; |
296 | } |
297 | } |
298 | |
299 | entry = IOMallocType(iocpu_platform_action_entry_t); |
300 | entry->action = &IOServicePlatformAction; |
301 | entry->name = service->getName(); |
302 | priority = num->unsigned32BitValue(); |
303 | if (reverse) { |
304 | entry->priority = -priority; |
305 | } else { |
306 | entry->priority = priority; |
307 | } |
308 | entry->refcon0 = service; |
309 | entry->refcon1 = (void *) key; |
310 | entry->callout_in_progress = FALSE; |
311 | |
312 | iocpu_add_platform_action(queue, entry); |
313 | return false; |
314 | } |
315 | |
316 | |
317 | IOReturn |
318 | IOInstallServicePlatformActions(IOService * service) |
319 | { |
320 | IOLockLock(gIOPlatformActionsLock); |
321 | |
322 | IOInstallServicePlatformAction(service, qidx: kQueueHaltRestart); |
323 | IOInstallServicePlatformAction(service, qidx: kQueuePanic); |
324 | |
325 | IOLockUnlock(gIOPlatformActionsLock); |
326 | |
327 | return kIOReturnSuccess; |
328 | } |
329 | |
330 | IOReturn |
331 | IOInstallServiceSleepPlatformActions(IOService * service) |
332 | { |
333 | IOLockLock(gIOPlatformActionsLock); |
334 | |
335 | for (uint32_t qidx = kQueueSleep; qidx <= kQueueActive; qidx++) { |
336 | IOInstallServicePlatformAction(service, qidx); |
337 | } |
338 | |
339 | IOLockUnlock(gIOPlatformActionsLock); |
340 | |
341 | return kIOReturnSuccess; |
342 | } |
343 | |
344 | IOReturn |
345 | IORemoveServicePlatformActions(IOService * service) |
346 | { |
347 | iocpu_platform_action_entry_t * entry; |
348 | iocpu_platform_action_entry_t * next; |
349 | |
350 | IOLockLock(gIOPlatformActionsLock); |
351 | |
352 | for (uint32_t qidx = kQueueSleep; qidx < kQueueCount; qidx++) { |
353 | next = (typeof(entry))queue_first(&gActionQueues[qidx]); |
354 | while (!queue_end(&gActionQueues[qidx], &next->link)) { |
355 | entry = next; |
356 | next = (typeof(entry))queue_next(&entry->link); |
357 | if (service == entry->refcon0) { |
358 | iocpu_remove_platform_action(entry); |
359 | IOFreeType(entry, iocpu_platform_action_entry_t); |
360 | } |
361 | } |
362 | } |
363 | |
364 | IOLockUnlock(gIOPlatformActionsLock); |
365 | |
366 | return kIOReturnSuccess; |
367 | } |
368 | |