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 */
85static 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
150struct FlagMap {
151 const char *str;
152 UInt32 flags;
153};
154
155static inline IOReturn
156stringToFlags(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
169static inline IOReturn
170flagsToString(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 */
184static 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
236static IOReturn
237unparseWorkIntervalType(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
248static IOReturn
249parseWorkIntervalType(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
268static IOReturn
269parseWorkloadClass(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
290static IOReturn
291parseCriticalityOffset(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
321static IOReturn
322parseFlags(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
359static
360IOReturn
361parsePhases(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
434static IOReturn
435parseRoot(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
460static IOReturn
461parseWorkloadIDTable(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
514static IOReturn
515parseWorkloadIDConfigurationFlags(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
550static IOReturn
551unparseWorkloadIDConfigurationFlags(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
575extern "C" {
576extern IOReturn IOParseWorkloadConfig(workload_config_ctx_t *, const char *, size_t);
577extern IOReturn IOUnparseWorkloadConfig(char *, size_t *);
578}
579
580/* Called locked. */
581IOReturn
582IOParseWorkloadConfig(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
624out:
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 */
639IOReturn
640IOUnparseWorkloadConfig(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