1 | /* |
2 | * Copyright (c) 2021 Apple 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 | #define IOKIT_ENABLE_SHARED_PTR |
30 | |
31 | #include <IOKit/IOLib.h> |
32 | #include <IOKit/IOReturn.h> |
33 | |
34 | #include <libkern/c++/OSArray.h> |
35 | #include <libkern/c++/OSDictionary.h> |
36 | #include <libkern/c++/OSNumber.h> |
37 | #include <libkern/c++/OSString.h> |
38 | #include <libkern/c++/OSSymbol.h> |
39 | #include <libkern/c++/OSUnserialize.h> |
40 | #include <libkern/c++/OSSharedPtr.h> |
41 | #include <libkern/c++/OSSerialize.h> |
42 | |
43 | #include <sys/work_interval.h> |
44 | #include <sys/param.h> |
45 | |
46 | #include <kern/thread_group.h> |
47 | #include <kern/work_interval.h> |
48 | #include <kern/workload_config.h> |
49 | |
50 | #if DEVELOPMENT || DEBUG |
51 | #define WLC_LOG(fmt, args...) IOLog("WorkloadConfig: " fmt, ##args) |
52 | #else |
53 | #define WLC_LOG(fmt, args...) |
54 | #endif |
55 | |
56 | /* Limit criticality offsets. */ |
57 | #define MAX_CRITICALITY_OFFSET 16 |
58 | |
59 | |
60 | /* Plist keys/values. */ |
61 | #define kWorkloadIDTableKey "WorkloadIDTable" |
62 | #define kRootKey "Root" |
63 | #define kPhasesKey "Phases" |
64 | #define kWorkIntervalTypeKey "WorkIntervalType" |
65 | #define kWorkloadClassKey "WorkloadClass" |
66 | #define kCriticalityOffsetKey "CriticalityOffset" |
67 | #define kDefaultPhaseKey "DefaultPhase" |
68 | #define kFlagsKey "Flags" |
69 | #define kWorkloadIDConfigurationFlagsKey "WorkloadIDConfigurationFlags" |
70 | |
71 | #define kDisableWorkloadClassThreadPolicyValue "DisableWorkloadClassThreadPolicy" |
72 | #define kWIComplexityAllowedValue "ComplexityAllowed" |
73 | |
74 | #define ARRAY_LEN(x) (sizeof (x) / sizeof (x[0])) |
75 | |
76 | #if !CONFIG_THREAD_GROUPS |
77 | #define THREAD_GROUP_FLAGS_EFFICIENT 0 |
78 | #define THREAD_GROUP_FLAGS_APPLICATION 0 |
79 | #define THREAD_GROUP_FLAGS_CRITICAL 0 |
80 | #define THREAD_GROUP_FLAGS_BEST_EFFORT 0 |
81 | #define THREAD_GROUP_FLAGS_ABSENT 0 |
82 | #endif /* CONFIG_THREAD_GROUPS */ |
83 | |
84 | /* BEGIN IGNORE CODESTYLE */ |
85 | static const struct WorkloadClassData { |
86 | const char *name; |
87 | UInt32 workIntervalFlags; |
88 | UInt32 threadGroupFlags; |
89 | } wlClassData[] = { |
90 | [WI_CLASS_NONE] = |
91 | { |
92 | .name = "NONE" , |
93 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID, |
94 | .threadGroupFlags = THREAD_GROUP_FLAGS_ABSENT, |
95 | }, |
96 | [WI_CLASS_DISCRETIONARY] = |
97 | { |
98 | .name = "DISCRETIONARY" , |
99 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID, |
100 | .threadGroupFlags = THREAD_GROUP_FLAGS_EFFICIENT, |
101 | }, |
102 | [WI_CLASS_BEST_EFFORT] = |
103 | { |
104 | .name = "BEST_EFFORT" , |
105 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID, |
106 | .threadGroupFlags = THREAD_GROUP_FLAGS_BEST_EFFORT, |
107 | }, |
108 | [WI_CLASS_APP_SUPPORT] = |
109 | { |
110 | .name = "APPLICATION_SUPPORT" , |
111 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID, |
112 | .threadGroupFlags = 0, |
113 | }, |
114 | [WI_CLASS_APPLICATION] = |
115 | { |
116 | .name = "APPLICATION" , |
117 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID, |
118 | .threadGroupFlags = THREAD_GROUP_FLAGS_APPLICATION, |
119 | }, |
120 | [WI_CLASS_SYSTEM] = |
121 | { |
122 | .name = "SYSTEM" , |
123 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID, |
124 | .threadGroupFlags = 0, |
125 | }, |
126 | [WI_CLASS_SYSTEM_CRITICAL] = |
127 | { |
128 | .name = "SYSTEM_CRITICAL" , |
129 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID, |
130 | .threadGroupFlags = THREAD_GROUP_FLAGS_CRITICAL, |
131 | }, |
132 | [WI_CLASS_REALTIME] = |
133 | { |
134 | .name = "REALTIME" , |
135 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID | |
136 | WORK_INTERVAL_WORKLOAD_ID_RT_ALLOWED, |
137 | .threadGroupFlags = 0, |
138 | }, |
139 | [WI_CLASS_REALTIME_CRITICAL] = |
140 | { |
141 | .name = "REALTIME_CRITICAL" , |
142 | .workIntervalFlags = WORK_INTERVAL_WORKLOAD_ID_HAS_ID | |
143 | WORK_INTERVAL_WORKLOAD_ID_RT_ALLOWED | |
144 | WORK_INTERVAL_WORKLOAD_ID_RT_CRITICAL, |
145 | .threadGroupFlags = THREAD_GROUP_FLAGS_CRITICAL, |
146 | }, |
147 | }; |
148 | /* END IGNORE CODESTYLE */ |
149 | |
150 | struct FlagMap { |
151 | const char *str; |
152 | UInt32 flags; |
153 | }; |
154 | |
155 | static inline IOReturn |
156 | stringToFlags(const OSString &str, UInt32 &flags, const struct FlagMap *map, |
157 | size_t mapLen) |
158 | { |
159 | for (size_t i = 0; i < mapLen; i++) { |
160 | if (str.isEqualTo(cString: map[i].str)) { |
161 | flags = map[i].flags; |
162 | return kIOReturnSuccess; |
163 | } |
164 | } |
165 | |
166 | return kIOReturnNotFound; |
167 | } |
168 | |
169 | static inline IOReturn |
170 | flagsToString(const UInt32 flags, OSSharedPtr<OSString> &str, const struct FlagMap *map, |
171 | size_t mapLen) |
172 | { |
173 | for (size_t i = 0; i < mapLen; i++) { |
174 | if (flags == map[i].flags) { |
175 | str = OSString::withCStringNoCopy(cString: map[i].str); |
176 | return kIOReturnSuccess; |
177 | } |
178 | } |
179 | |
180 | return kIOReturnNotFound; |
181 | } |
182 | |
183 | /* BEGIN IGNORE CODESTYLE */ |
184 | static const struct FlagMap typeMap[] = { |
185 | { |
186 | .str = "DEFAULT" , |
187 | .flags = WORK_INTERVAL_TYPE_DEFAULT | |
188 | WORK_INTERVAL_FLAG_UNRESTRICTED, |
189 | }, |
190 | { |
191 | .str = "COREAUDIO" , |
192 | .flags = WORK_INTERVAL_TYPE_COREAUDIO | |
193 | WORK_INTERVAL_FLAG_ENABLE_AUTO_JOIN | |
194 | WORK_INTERVAL_FLAG_ENABLE_DEFERRED_FINISH, |
195 | }, |
196 | { |
197 | .str = "COREANIMATION" , |
198 | .flags = WORK_INTERVAL_TYPE_COREANIMATION, |
199 | }, |
200 | { |
201 | .str = "CA_RENDER_SERVER" , |
202 | .flags = WORK_INTERVAL_TYPE_CA_RENDER_SERVER, |
203 | }, |
204 | { |
205 | .str = "FRAME_COMPOSITOR" , |
206 | .flags = WORK_INTERVAL_TYPE_FRAME_COMPOSITOR, |
207 | }, |
208 | { |
209 | .str = "CA_CLIENT" , |
210 | .flags = WORK_INTERVAL_TYPE_CA_CLIENT | |
211 | WORK_INTERVAL_FLAG_UNRESTRICTED, |
212 | }, |
213 | { |
214 | .str = "HID_DELIVERY" , |
215 | .flags = WORK_INTERVAL_TYPE_HID_DELIVERY, |
216 | }, |
217 | { |
218 | .str = "COREMEDIA" , |
219 | .flags = WORK_INTERVAL_TYPE_COREMEDIA, |
220 | }, |
221 | { |
222 | .str = "ARKIT" , |
223 | .flags = WORK_INTERVAL_TYPE_ARKIT | |
224 | WORK_INTERVAL_FLAG_FINISH_AT_DEADLINE, |
225 | }, |
226 | { |
227 | .str = "AUDIO_CLIENT" , |
228 | .flags = WORK_INTERVAL_TYPE_COREAUDIO | |
229 | WORK_INTERVAL_FLAG_UNRESTRICTED | |
230 | WORK_INTERVAL_FLAG_ENABLE_AUTO_JOIN | |
231 | WORK_INTERVAL_FLAG_ENABLE_DEFERRED_FINISH |
232 | }, |
233 | }; |
234 | /* END IGNORE CODESTYLE */ |
235 | |
236 | static IOReturn |
237 | unparseWorkIntervalType(const UInt32 createFlags, OSSharedPtr<OSString> &typeStr) |
238 | { |
239 | IOReturn ret = flagsToString(flags: createFlags, str&: typeStr, map: typeMap, |
240 | ARRAY_LEN(typeMap)); |
241 | if (ret != kIOReturnSuccess) { |
242 | WLC_LOG("unrecognised create flags: 0x%x\n" , createFlags); |
243 | } |
244 | |
245 | return ret; |
246 | } |
247 | |
248 | static IOReturn |
249 | parseWorkIntervalType(const OSSymbol &id, const OSObject *typeObj, UInt32 &createFlags) |
250 | { |
251 | OSSharedPtr<OSString> defaultIntervalType = OSString::withCString(cString: "DEFAULT" ); |
252 | |
253 | const OSString *typeStr = OSDynamicCast(OSString, typeObj); |
254 | if (typeStr == nullptr) { |
255 | typeStr = defaultIntervalType.get(); |
256 | } |
257 | |
258 | IOReturn ret = stringToFlags(str: *typeStr, flags&: createFlags, map: typeMap, |
259 | ARRAY_LEN(typeMap)); |
260 | if (ret != kIOReturnSuccess) { |
261 | WLC_LOG("unrecognised \"" kWorkIntervalTypeKey "\": \"%s\"\n" , |
262 | typeStr->getCStringNoCopy()); |
263 | } |
264 | |
265 | return ret; |
266 | } |
267 | |
268 | static IOReturn |
269 | parseWorkloadClass(const OSSymbol &id, const OSObject *wlClassObj, wi_class_t &wiClass) |
270 | { |
271 | const OSString *wlClass = OSDynamicCast(OSString, wlClassObj); |
272 | if (wlClass == nullptr) { |
273 | wiClass = WI_CLASS_NONE; |
274 | return kIOReturnSuccess; |
275 | } |
276 | |
277 | for (size_t i = 0; i < ARRAY_LEN(wlClassData); i++) { |
278 | if (wlClassData[i].name != nullptr && |
279 | wlClass->isEqualTo(cString: wlClassData[i].name)) { |
280 | wiClass = (wi_class_t)i; |
281 | return kIOReturnSuccess; |
282 | } |
283 | } |
284 | |
285 | WLC_LOG("%s: unknown %s: \"%s\"\n" , id.getCStringNoCopy(), |
286 | kWorkloadClassKey, wlClass->getCStringNoCopy()); |
287 | return kIOReturnError; |
288 | } |
289 | |
290 | static IOReturn |
291 | parseCriticalityOffset(const OSSymbol &id, const wi_class_t wiClass, |
292 | const OSObject *cOffsetObj, uint8_t &criticalityOffset) |
293 | { |
294 | if (wiClass != WI_CLASS_SYSTEM_CRITICAL && |
295 | wiClass != WI_CLASS_REALTIME_CRITICAL && |
296 | wiClass != WI_CLASS_BEST_EFFORT && |
297 | wiClass != WI_CLASS_APP_SUPPORT && |
298 | wiClass != WI_CLASS_SYSTEM) { |
299 | criticalityOffset = 0; |
300 | return kIOReturnSuccess; |
301 | } |
302 | |
303 | const OSNumber *cOffset = OSDynamicCast(OSNumber, cOffsetObj); |
304 | if (cOffset == nullptr) { |
305 | criticalityOffset = 0; |
306 | return kIOReturnSuccess; |
307 | } |
308 | |
309 | UInt64 criticalityOffset64 = cOffset->unsigned64BitValue(); |
310 | const int nBytes = cOffset->numberOfBytes(); |
311 | if (nBytes <= sizeof(criticalityOffset64) && |
312 | criticalityOffset64 < MAX_CRITICALITY_OFFSET) { |
313 | criticalityOffset = (uint8_t)criticalityOffset64; |
314 | return kIOReturnSuccess; |
315 | } |
316 | |
317 | WLC_LOG("%s: criticality offset too large\n" , id.getCStringNoCopy()); |
318 | return kIOReturnError; |
319 | } |
320 | |
321 | static IOReturn |
322 | parseFlags(const OSSymbol &id, const OSObject *flagsObj, UInt32 &threadGroupFlags, |
323 | UInt32 &workIntervalFlags) |
324 | { |
325 | /* Optional, so just carry on if not found. */ |
326 | if (flagsObj == nullptr) { |
327 | return kIOReturnSuccess; |
328 | } |
329 | |
330 | OSArray *flags = OSDynamicCast(OSArray, flagsObj); |
331 | if (flags == nullptr) { |
332 | WLC_LOG("failed to parse \"" kFlagsKey "\"\n" ); |
333 | return kIOReturnError; |
334 | } |
335 | |
336 | /* BEGIN IGNORE CODESTYLE */ |
337 | __block IOReturn ret = kIOReturnSuccess; |
338 | flags->iterateObjects(block: ^bool (OSObject *object) { |
339 | const OSString *flag = OSDynamicCast(OSString, object); |
340 | if (flag == nullptr) { |
341 | WLC_LOG("%s: non-string flag found\n" , id.getCStringNoCopy()); |
342 | ret = kIOReturnError; |
343 | return true; |
344 | |
345 | } |
346 | |
347 | /* Ignore unknown flags. */ |
348 | if (flag->isEqualTo(kWIComplexityAllowedValue)) { |
349 | workIntervalFlags |= WORK_INTERVAL_WORKLOAD_ID_COMPLEXITY_ALLOWED; |
350 | } |
351 | |
352 | return false; |
353 | }); |
354 | /* END IGNORE CODESTYLE */ |
355 | |
356 | return ret; |
357 | } |
358 | |
359 | static |
360 | IOReturn |
361 | parsePhases(workload_config_ctx_t *ctx, const OSSymbol &id, OSObject *phasesObj) |
362 | { |
363 | __block IOReturn ret = kIOReturnError; |
364 | |
365 | OSDictionary *phases = OSDynamicCast(OSDictionary, phasesObj); |
366 | if (phases == nullptr) { |
367 | WLC_LOG("%s: failed to find dictionary for \"" kPhasesKey "\"\n" , |
368 | id.getCStringNoCopy()); |
369 | return kIOReturnError; |
370 | } |
371 | |
372 | /* There should be at least one phase described. */ |
373 | ret = kIOReturnError; |
374 | |
375 | /* BEGIN IGNORE CODESTYLE */ |
376 | phases->iterateObjects(block: ^bool (const OSSymbol *phase, OSObject *value) { |
377 | const OSDictionary *dict = OSDynamicCast(OSDictionary, value); |
378 | if (dict == nullptr) { |
379 | WLC_LOG("%s: failed to find dictionary for \"%s\" phase\n" , |
380 | id.getCStringNoCopy(), phase->getCStringNoCopy()); |
381 | ret = kIOReturnError; |
382 | return true; |
383 | } |
384 | |
385 | UInt32 createFlags = 0; |
386 | ret = parseWorkIntervalType(id, typeObj: dict->getObject(kWorkIntervalTypeKey), |
387 | createFlags); |
388 | if (ret != kIOReturnSuccess) { |
389 | return true; |
390 | } |
391 | |
392 | wi_class_t wiClass = WI_CLASS_NONE; |
393 | ret = parseWorkloadClass(id, wlClassObj: dict->getObject(kWorkloadClassKey), wiClass); |
394 | if (ret != kIOReturnSuccess) { |
395 | return true; |
396 | } |
397 | const struct WorkloadClassData classData = wlClassData[wiClass]; |
398 | |
399 | uint8_t criticalityOffset = 0; |
400 | ret = parseCriticalityOffset(id, wiClass, |
401 | cOffsetObj: dict->getObject(kCriticalityOffsetKey), criticalityOffset); |
402 | if (ret != kIOReturnSuccess) { |
403 | return true; |
404 | } |
405 | |
406 | UInt32 threadGroupFlags = classData.threadGroupFlags; |
407 | UInt32 workIntervalFlags = classData.workIntervalFlags; |
408 | ret = parseFlags(id, flagsObj: dict->getObject(kFlagsKey), threadGroupFlags, workIntervalFlags); |
409 | if (ret != kIOReturnSuccess) { |
410 | return true; |
411 | } |
412 | |
413 | const workload_config_t config = { |
414 | .wc_thread_group_flags = threadGroupFlags, |
415 | .wc_flags = workIntervalFlags, |
416 | .wc_create_flags = createFlags, |
417 | .wc_class_offset = (uint8_t)criticalityOffset, |
418 | .wc_class = wiClass, |
419 | }; |
420 | ret = workload_config_insert(ctx, id: id.getCStringNoCopy(), phase: phase->getCStringNoCopy(), config: &config); |
421 | if (ret != kIOReturnSuccess) { |
422 | WLC_LOG("%s: failed to add \"%s\" phase\n" , |
423 | id.getCStringNoCopy(), phase->getCStringNoCopy()); |
424 | return true; |
425 | } |
426 | |
427 | return false; |
428 | }); |
429 | /* END IGNORE CODESTYLE */ |
430 | |
431 | return ret; |
432 | } |
433 | |
434 | static IOReturn |
435 | parseRoot(const OSSymbol &id, const OSObject *rootDict, OSString *&defaultPhase) |
436 | { |
437 | const OSDictionary *root = OSDynamicCast(OSDictionary, rootDict); |
438 | if (root == nullptr) { |
439 | WLC_LOG("%s: failed to find dictionary for \"" kRootKey "\"\n" , |
440 | id.getCStringNoCopy()); |
441 | return kIOReturnError; |
442 | } |
443 | |
444 | defaultPhase = OSDynamicCast(OSString, root->getObject(kDefaultPhaseKey)); |
445 | if (defaultPhase == nullptr) { |
446 | WLC_LOG("%s: failed to find \"" kDefaultPhaseKey"\" in \"" kRootKey "\" dictionary\n" , |
447 | id.getCStringNoCopy()); |
448 | return kIOReturnError; |
449 | } |
450 | |
451 | if (defaultPhase->getLength() == 0) { |
452 | WLC_LOG("%s: \"" kDefaultPhaseKey" \" is empty in \"" kRootKey "\" dictionary\n" , |
453 | id.getCStringNoCopy()); |
454 | return kIOReturnError; |
455 | } |
456 | |
457 | return kIOReturnSuccess; |
458 | } |
459 | |
460 | static IOReturn |
461 | parseWorkloadIDTable(workload_config_ctx_t *ctx, OSDictionary *IDTable) |
462 | { |
463 | /* |
464 | * At least one valid entry is expected, so start off with error to |
465 | * catch an empty table or one with no valid entries. |
466 | */ |
467 | __block IOReturn ret = kIOReturnError; |
468 | |
469 | /* BEGIN IGNORE CODESTYLE */ |
470 | IDTable->iterateObjects(block: ^bool (const OSSymbol *id, OSObject *value) { |
471 | /* Validate the workload ID. */ |
472 | if (id->getLength() == 0) { |
473 | WLC_LOG("zero length ID in \"" kWorkloadIDTableKey "\"\n" ); |
474 | ret = kIOReturnError; |
475 | return true; |
476 | } |
477 | |
478 | /* Parse its properties. */ |
479 | OSDictionary *idConfig = OSDynamicCast(OSDictionary, value); |
480 | if (idConfig == nullptr) { |
481 | WLC_LOG("failed to find dictionary for \"%s\"\n" , |
482 | id->getCStringNoCopy()); |
483 | ret = kIOReturnError; |
484 | return true; |
485 | } |
486 | |
487 | ret = parsePhases(ctx, id: *id, phasesObj: idConfig->getObject(kPhasesKey)); |
488 | if (ret != kIOReturnSuccess) { |
489 | return true; |
490 | } |
491 | |
492 | OSString *defaultPhase = nullptr; |
493 | ret = parseRoot(id: *id, rootDict: idConfig->getObject(kRootKey), defaultPhase); |
494 | if (ret != kIOReturnSuccess) { |
495 | return true; |
496 | } |
497 | |
498 | /* Fails if the specified phase doesn't exist.. */ |
499 | ret = workload_config_set_default(ctx, id: id->getCStringNoCopy(), |
500 | phase: defaultPhase->getCStringNoCopy()); |
501 | if (ret != kIOReturnSuccess) { |
502 | WLC_LOG("failed to set default phase (%s) for \"%s\"\n" , |
503 | defaultPhase->getCStringNoCopy(), id->getCStringNoCopy()); |
504 | return true; |
505 | } |
506 | |
507 | return false; |
508 | }); |
509 | /* END IGNORE CODESTYLE */ |
510 | |
511 | return ret; |
512 | } |
513 | |
514 | static IOReturn |
515 | parseWorkloadIDConfigurationFlags(workload_config_ctx_t *ctx, const OSObject *idTableFlagsObj) |
516 | { |
517 | /* Optional, so just carry on if not found. */ |
518 | if (idTableFlagsObj == nullptr) { |
519 | return kIOReturnSuccess; |
520 | } |
521 | |
522 | OSArray *idTableFlags = OSDynamicCast(OSArray, idTableFlagsObj); |
523 | if (idTableFlags == nullptr) { |
524 | WLC_LOG("failed to parse \"" |
525 | kWorkloadIDConfigurationFlagsKey "\"\n" ); |
526 | return kIOReturnError; |
527 | } |
528 | |
529 | /* BEGIN IGNORE CODESTYLE */ |
530 | __block IOReturn ret = kIOReturnSuccess; |
531 | idTableFlags->iterateObjects(block: ^bool (OSObject *object) { |
532 | const OSString *flag = OSDynamicCast(OSString, object); |
533 | if (flag == nullptr) { |
534 | WLC_LOG("non-string Workload ID Table flag found\n" ); |
535 | ret = kIOReturnError; |
536 | return true; |
537 | } |
538 | |
539 | if (flag->isEqualTo(kDisableWorkloadClassThreadPolicyValue)) { |
540 | workload_config_clear_flag(ctx, flag: WLC_F_THREAD_POLICY); |
541 | } |
542 | |
543 | return false; |
544 | }); |
545 | /* END IGNORE CODESTYLE */ |
546 | |
547 | return ret; |
548 | } |
549 | |
550 | static IOReturn |
551 | unparseWorkloadIDConfigurationFlags(OSSharedPtr<OSDictionary> &plist) |
552 | { |
553 | workload_config_flags_t flags = WLC_F_NONE; |
554 | |
555 | /* There may be no config at all. That's ok. */ |
556 | if (workload_config_get_flags(flags: &flags) != KERN_SUCCESS) { |
557 | return kIOReturnSuccess; |
558 | } |
559 | |
560 | /* Workload config can change thread policy scheduling - the default. */ |
561 | if ((flags & WLC_F_THREAD_POLICY) != 0) { |
562 | return kIOReturnSuccess; |
563 | } |
564 | |
565 | OSSharedPtr<OSArray> idTableFlags = OSArray::withCapacity(capacity: 1); |
566 | OSSharedPtr<OSString> flag = OSString::withCString(kDisableWorkloadClassThreadPolicyValue); |
567 | if (!idTableFlags->setObject(flag) || |
568 | !plist->setObject(kWorkloadIDConfigurationFlagsKey, anObject: idTableFlags)) { |
569 | return kIOReturnError; |
570 | } |
571 | |
572 | return kIOReturnSuccess; |
573 | } |
574 | |
575 | extern "C" { |
576 | extern IOReturn IOParseWorkloadConfig(workload_config_ctx_t *, const char *, size_t); |
577 | extern IOReturn IOUnparseWorkloadConfig(char *, size_t *); |
578 | } |
579 | |
580 | /* Called locked. */ |
581 | IOReturn |
582 | IOParseWorkloadConfig(workload_config_ctx_t *ctx, const char *buffer, size_t size) |
583 | { |
584 | IOReturn ret = kIOReturnError; |
585 | |
586 | OSSharedPtr<OSString> unserializeErrorString = nullptr; |
587 | OSSharedPtr<OSObject> obj = nullptr; |
588 | OSDictionary *idTable = nullptr; |
589 | OSDictionary *dict = nullptr; |
590 | |
591 | ret = workload_config_init(ctx); |
592 | if (ret != kIOReturnSuccess) { |
593 | WLC_LOG("failed to initialize workload configuration\n" ); |
594 | goto out; |
595 | } |
596 | |
597 | obj = OSUnserializeXML(buffer, errorString&: unserializeErrorString); |
598 | dict = OSDynamicCast(OSDictionary, obj.get()); |
599 | if (dict == nullptr) { |
600 | WLC_LOG("failed to unserialize plist\n" ); |
601 | ret = kIOReturnError; |
602 | goto out; |
603 | } |
604 | |
605 | idTable = OSDynamicCast(OSDictionary, dict->getObject(kWorkloadIDTableKey)); |
606 | if (idTable == nullptr) { |
607 | WLC_LOG("failed to find " kWorkloadIDTableKey "\n" ); |
608 | ret = kIOReturnError; |
609 | goto out; |
610 | } |
611 | |
612 | ret = parseWorkloadIDTable(ctx, IDTable: idTable); |
613 | if (ret != kIOReturnSuccess) { |
614 | goto out; |
615 | } |
616 | |
617 | ret = parseWorkloadIDConfigurationFlags(ctx, idTableFlagsObj: dict->getObject(kWorkloadIDConfigurationFlagsKey)); |
618 | if (ret != kIOReturnSuccess) { |
619 | goto out; |
620 | } |
621 | |
622 | ret = kIOReturnSuccess; |
623 | |
624 | out: |
625 | if (ret != kIOReturnSuccess) { |
626 | workload_config_free(ctx); |
627 | } |
628 | |
629 | return ret; |
630 | } |
631 | |
632 | /* |
633 | * Does the reverse of IOParseWorkloadConfig() - i.e. serializes the internal |
634 | * workload configuration. |
635 | * The serialized workload config is copied to 'buffer' (if non-NULL). |
636 | * size is in/out - it describes the size of buffer and on return the length of |
637 | * the serialized config. |
638 | */ |
639 | IOReturn |
640 | IOUnparseWorkloadConfig(char *buffer, size_t *size) |
641 | { |
642 | assert(size != nullptr); |
643 | |
644 | OSSharedPtr<OSDictionary> dict = nullptr;; |
645 | OSSharedPtr<OSDictionary> idTable = nullptr; |
646 | OSSharedPtr<OSSerialize> serialize = nullptr; |
647 | |
648 | serialize = OSSerialize::withCapacity(capacity: 1); |
649 | if (serialize == nullptr) { |
650 | return kIOReturnNoMemory; |
651 | } |
652 | |
653 | dict = OSDictionary::withCapacity(capacity: 1); |
654 | if (dict == nullptr) { |
655 | return kIOReturnNoMemory; |
656 | } |
657 | |
658 | idTable = OSDictionary::withCapacity(capacity: 1); |
659 | if (idTable == nullptr) { |
660 | return kIOReturnNoMemory; |
661 | } |
662 | |
663 | __block IOReturn ret = kIOReturnSuccess; |
664 | /* BEGIN IGNORE CODESTYLE */ |
665 | workload_config_iterate(cb: ^(const char *id_str, const void *config) { |
666 | OSSharedPtr<OSDictionary> idDict = OSDictionary::withCapacity(capacity: 1); |
667 | if (idDict == nullptr) { |
668 | ret = kIOReturnNoMemory; |
669 | return true; |
670 | } |
671 | |
672 | OSSharedPtr<OSDictionary> phase = OSDictionary::withCapacity(capacity: 1); |
673 | if (phase == nullptr) { |
674 | ret = kIOReturnNoMemory; |
675 | return true; |
676 | } |
677 | |
678 | workload_config_phases_iterate(phases: config, cb: ^(const char *phase_str, |
679 | const bool is_default, const workload_config_t *wc) { |
680 | OSSharedPtr<OSDictionary> phaseData = OSDictionary::withCapacity(capacity: 1); |
681 | if (phaseData == nullptr) { |
682 | ret = kIOReturnNoMemory; |
683 | return true; |
684 | } |
685 | |
686 | if (wc->wc_class != WI_CLASS_NONE) { |
687 | assert3u(wc->wc_class, <, WI_CLASS_COUNT); |
688 | OSSharedPtr<OSString> wClass = OSString::withCString(cString: wlClassData[wc->wc_class].name); |
689 | if (wClass == nullptr || !phaseData->setObject(kWorkloadClassKey, anObject: wClass)) { |
690 | ret = kIOReturnError; |
691 | return true; |
692 | } |
693 | } |
694 | |
695 | if (wc->wc_class_offset > 0) { |
696 | OSSharedPtr<OSNumber> criticalityOffset = OSNumber::withNumber(value: wc->wc_class_offset, numberOfBits: 8); |
697 | if (criticalityOffset == nullptr || |
698 | !phaseData->setObject(kCriticalityOffsetKey, anObject: criticalityOffset)) { |
699 | ret = kIOReturnError; |
700 | return true; |
701 | } |
702 | } |
703 | |
704 | OSSharedPtr<OSString> type = nullptr; |
705 | if (unparseWorkIntervalType(createFlags: wc->wc_create_flags, typeStr&: type) != kIOReturnSuccess || |
706 | !phaseData->setObject(kWorkIntervalTypeKey, anObject: type)) { |
707 | ret = kIOReturnError; |
708 | return true; |
709 | } |
710 | |
711 | |
712 | OSSharedPtr<OSArray> flags = OSArray::withCapacity(capacity: 2); |
713 | if (flags == nullptr) { |
714 | ret = kIOReturnError; |
715 | return true; |
716 | } |
717 | if ((wc->wc_flags & WORK_INTERVAL_WORKLOAD_ID_COMPLEXITY_ALLOWED) != 0) { |
718 | OSSharedPtr<OSString> WIComplexityAllowedStr = |
719 | OSString::withCString(kWIComplexityAllowedValue); |
720 | if (WIComplexityAllowedStr == nullptr || !flags->setObject(WIComplexityAllowedStr)) { |
721 | ret = kIOReturnError; |
722 | return true; |
723 | } |
724 | } |
725 | if (flags->getCount() && !phaseData->setObject(kFlagsKey, anObject: flags)) { |
726 | ret = kIOReturnError; |
727 | return true; |
728 | } |
729 | |
730 | if (!phase->setObject(aKey: phase_str, anObject: phaseData)) { |
731 | ret = kIOReturnError; |
732 | return true; |
733 | } |
734 | |
735 | if (is_default) { |
736 | OSSharedPtr<OSDictionary> root = OSDictionary::withCapacity(capacity: 1); |
737 | OSSharedPtr<OSString> phaseStr = OSString::withCString(cString: phase_str); |
738 | |
739 | if (root == nullptr || phaseStr == nullptr || |
740 | !root->setObject(kDefaultPhaseKey, anObject: phaseStr)) { |
741 | ret = kIOReturnError; |
742 | return true; |
743 | } |
744 | |
745 | if (!idDict->setObject(kRootKey, anObject: root)) { |
746 | ret = kIOReturnError; |
747 | return true; |
748 | } |
749 | } |
750 | |
751 | return false; |
752 | |
753 | }); |
754 | |
755 | if (ret != kIOReturnSuccess) { |
756 | return true; |
757 | } |
758 | |
759 | if (!idDict->setObject(kPhasesKey, anObject: phase)) { |
760 | ret = kIOReturnError; |
761 | return true; |
762 | } |
763 | |
764 | if (!idTable->setObject(aKey: id_str, anObject: idDict)) { |
765 | ret = kIOReturnError; |
766 | return true; |
767 | } |
768 | |
769 | return false; |
770 | }); |
771 | /* END IGNORE CODESTYLE */ |
772 | |
773 | if (ret != kIOReturnSuccess) { |
774 | return ret; |
775 | } |
776 | |
777 | OSSharedPtr<OSDictionary> plist = OSDictionary::withCapacity(capacity: 1); |
778 | if (plist == nullptr) { |
779 | return kIOReturnError; |
780 | } |
781 | |
782 | if (idTable->getCount() > 0 && |
783 | !plist->setObject(kWorkloadIDTableKey, anObject: idTable)) { |
784 | return kIOReturnError; |
785 | } |
786 | |
787 | if (unparseWorkloadIDConfigurationFlags(plist) != kIOReturnSuccess) { |
788 | return kIOReturnError; |
789 | } |
790 | |
791 | if (!plist->serialize(serializer: serialize.get())) { |
792 | return kIOReturnError; |
793 | } |
794 | |
795 | if (buffer != nullptr) { |
796 | (void) strlcpy(dst: buffer, src: serialize->text(), n: *size); |
797 | } |
798 | *size = serialize->getLength(); |
799 | |
800 | return kIOReturnSuccess; |
801 | } |
802 | |