]> git.saurik.com Git - apple/xnu.git/blob - iokit/Kernel/IOPerfControl.cpp
xnu-7195.101.1.tar.gz
[apple/xnu.git] / iokit / Kernel / IOPerfControl.cpp
1 /*
2 * Copyright (c) 2017 Apple Inc. All rights reserved.
3 */
4
5 #include <IOKit/perfcontrol/IOPerfControl.h>
6
7 #include <stdatomic.h>
8
9 #include <kern/thread_group.h>
10
11 #undef super
12 #define super OSObject
13 OSDefineMetaClassAndStructors(IOPerfControlClient, OSObject);
14
15 static IOPerfControlClient::IOPerfControlClientShared *_Atomic gIOPerfControlClientShared;
16
17 bool
18 IOPerfControlClient::init(IOService *driver, uint64_t maxWorkCapacity)
19 {
20 // TODO: Remove this limit and implement dynamic table growth if workloads are found that exceed this
21 if (maxWorkCapacity > kMaxWorkTableNumEntries) {
22 maxWorkCapacity = kMaxWorkTableNumEntries;
23 }
24
25 if (!super::init()) {
26 return false;
27 }
28
29 shared = atomic_load_explicit(&gIOPerfControlClientShared, memory_order_acquire);
30 if (shared == nullptr) {
31 IOPerfControlClient::IOPerfControlClientShared *expected = shared;
32 shared = reinterpret_cast<IOPerfControlClient::IOPerfControlClientShared*>(kalloc(sizeof(IOPerfControlClientShared)));
33 if (!shared) {
34 return false;
35 }
36
37 atomic_init(&shared->maxDriverIndex, 0);
38
39 shared->interface = PerfControllerInterface{
40 .version = 0,
41 .registerDevice =
42 [](IOService *device) {
43 return kIOReturnSuccess;
44 },
45 .unregisterDevice =
46 [](IOService *device) {
47 return kIOReturnSuccess;
48 },
49 .workCanSubmit =
50 [](IOService *device, PerfControllerInterface::WorkState *state, WorkSubmitArgs *args) {
51 return false;
52 },
53 .workSubmit =
54 [](IOService *device, uint64_t token, PerfControllerInterface::WorkState *state, WorkSubmitArgs *args) {
55 },
56 .workBegin =
57 [](IOService *device, uint64_t token, PerfControllerInterface::WorkState *state, WorkBeginArgs *args) {
58 },
59 .workEnd =
60 [](IOService *device, uint64_t token, PerfControllerInterface::WorkState *state, WorkEndArgs *args, bool done) {
61 },
62 };
63
64 shared->interfaceLock = IOLockAlloc();
65 if (!shared->interfaceLock) {
66 goto shared_init_error;
67 }
68
69 shared->deviceRegistrationList = OSSet::withCapacity(4);
70 if (!shared->deviceRegistrationList) {
71 goto shared_init_error;
72 }
73
74 if (!atomic_compare_exchange_strong_explicit(&gIOPerfControlClientShared, &expected, shared, memory_order_acq_rel,
75 memory_order_acquire)) {
76 IOLockFree(shared->interfaceLock);
77 shared->deviceRegistrationList->release();
78 kfree(shared, sizeof(*shared));
79 shared = expected;
80 }
81 }
82
83 // Note: driverIndex is not guaranteed to be unique if maxDriverIndex wraps around. It is intended for debugging only.
84 driverIndex = atomic_fetch_add_explicit(&shared->maxDriverIndex, 1, memory_order_relaxed) + 1;
85
86 // + 1 since index 0 is unused for kIOPerfControlClientWorkUntracked
87 workTableLength = maxWorkCapacity + 1;
88 assertf(workTableLength <= kWorkTableMaxSize, "%zu exceeds max allowed capacity of %zu", workTableLength, kWorkTableMaxSize);
89 if (maxWorkCapacity > 0) {
90 workTable = reinterpret_cast<WorkTableEntry*>(kalloc(workTableLength * sizeof(WorkTableEntry)));
91 if (!workTable) {
92 goto error;
93 }
94 bzero(workTable, workTableLength * sizeof(WorkTableEntry));
95 workTableNextIndex = 1;
96
97 workTableLock = IOSimpleLockAlloc();
98 if (!workTableLock) {
99 goto error;
100 }
101 }
102
103 return true;
104
105 error:
106 if (workTable) {
107 kfree(workTable, maxWorkCapacity * sizeof(WorkTableEntry));
108 }
109 if (workTableLock) {
110 IOSimpleLockFree(workTableLock);
111 }
112 return false;
113 shared_init_error:
114 if (shared) {
115 if (shared->interfaceLock) {
116 IOLockFree(shared->interfaceLock);
117 }
118 if (shared->deviceRegistrationList) {
119 shared->deviceRegistrationList->release();
120 }
121 kfree(shared, sizeof(*shared));
122 shared = nullptr;
123 }
124 return false;
125 }
126
127 IOPerfControlClient *
128 IOPerfControlClient::copyClient(IOService *driver, uint64_t maxWorkCapacity)
129 {
130 IOPerfControlClient *client = new IOPerfControlClient;
131 if (!client || !client->init(driver, maxWorkCapacity)) {
132 panic("could not create IOPerfControlClient");
133 }
134 return client;
135 }
136
137 /* Convert the per driver token into a globally unique token for the performance
138 * controller's consumption. This is achieved by setting the driver's unique
139 * index onto the high order bits. The performance controller is shared between
140 * all drivers and must track all instances separately, while each driver has
141 * its own token table, so this step is needed to avoid token collisions between
142 * drivers.
143 */
144 inline uint64_t
145 IOPerfControlClient::tokenToGlobalUniqueToken(uint64_t token)
146 {
147 return token | (static_cast<uint64_t>(driverIndex) << kWorkTableIndexBits);
148 }
149
150 /* With this implementation, tokens returned to the driver differ from tokens
151 * passed to the performance controller. This implementation has the nice
152 * property that tokens returns to the driver will aways be between 1 and
153 * the value of maxWorkCapacity passed by the driver to copyClient. The tokens
154 * the performance controller sees will match on the lower order bits and have
155 * the driver index set on the high order bits.
156 */
157 uint64_t
158 IOPerfControlClient::allocateToken(thread_group *thread_group)
159 {
160 uint64_t token = kIOPerfControlClientWorkUntracked;
161
162 #if CONFIG_THREAD_GROUPS
163 auto s = IOSimpleLockLockDisableInterrupt(workTableLock);
164
165 uint64_t num_tries = 0;
166 size_t index = workTableNextIndex;
167 // - 1 since entry 0 is for kIOPerfControlClientWorkUntracked
168 while (num_tries < workTableLength - 1) {
169 if (workTable[index].thread_group == nullptr) {
170 thread_group_retain(thread_group);
171 workTable[index].thread_group = thread_group;
172 token = index;
173 // next integer between 1 and workTableLength - 1
174 workTableNextIndex = (index % (workTableLength - 1)) + 1;
175 break;
176 }
177 // next integer between 1 and workTableLength - 1
178 index = (index % (workTableLength - 1)) + 1;
179 num_tries += 1;
180 }
181 #if (DEVELOPMENT || DEBUG)
182 if (token == kIOPerfControlClientWorkUntracked) {
183 /* When investigating a panic here, first check that the driver is not leaking tokens.
184 * If the driver is not leaking tokens and maximum is less than kMaxWorkTableNumEntries,
185 * the driver should be modified to pass a larger value to copyClient.
186 * If the driver is not leaking tokens and maximum is equal to kMaxWorkTableNumEntries,
187 * this code will have to be modified to support dynamic table growth to support larger
188 * numbers of tokens.
189 */
190 panic("Tokens allocated for this device exceeded maximum of %zu.\n",
191 workTableLength - 1); // - 1 since entry 0 is for kIOPerfControlClientWorkUntracked
192 }
193 #endif
194
195 IOSimpleLockUnlockEnableInterrupt(workTableLock, s);
196 #endif
197
198 return token;
199 }
200
201 void
202 IOPerfControlClient::deallocateToken(uint64_t token)
203 {
204 #if CONFIG_THREAD_GROUPS
205 assertf(token != kIOPerfControlClientWorkUntracked, "Attempt to deallocate token kIOPerfControlClientWorkUntracked\n");
206 assertf(token <= workTableLength, "Attempt to deallocate token %llu which is greater than the table size of %zu\n", token, workTableLength);
207 auto s = IOSimpleLockLockDisableInterrupt(workTableLock);
208
209 auto &entry = workTable[token];
210 auto *thread_group = entry.thread_group;
211 bzero(&entry, sizeof(entry));
212 workTableNextIndex = token;
213
214 IOSimpleLockUnlockEnableInterrupt(workTableLock, s);
215
216 // This can call into the performance controller if the last reference is dropped here. Are we sure
217 // the driver isn't holding any locks? If not, we may want to async this to another context.
218 thread_group_release(thread_group);
219 #endif
220 }
221
222 IOPerfControlClient::WorkTableEntry *
223 IOPerfControlClient::getEntryForToken(uint64_t token)
224 {
225 if (token == kIOPerfControlClientWorkUntracked) {
226 return nullptr;
227 }
228
229 if (token >= workTableLength) {
230 panic("Invalid work token (%llu): index out of bounds.", token);
231 }
232
233 WorkTableEntry *entry = &workTable[token];
234 assertf(entry->thread_group, "Invalid work token: %llu", token);
235 return entry;
236 }
237
238 void
239 IOPerfControlClient::markEntryStarted(uint64_t token, bool started)
240 {
241 if (token == kIOPerfControlClientWorkUntracked) {
242 return;
243 }
244
245 if (token >= workTableLength) {
246 panic("Invalid work token (%llu): index out of bounds.", token);
247 }
248
249 workTable[token].started = started;
250 }
251
252 IOReturn
253 IOPerfControlClient::registerDevice(__unused IOService *driver, IOService *device)
254 {
255 IOReturn ret = kIOReturnSuccess;
256
257 IOLockLock(shared->interfaceLock);
258
259 if (shared->interface.version > 0) {
260 ret = shared->interface.registerDevice(device);
261 } else {
262 shared->deviceRegistrationList->setObject(device);
263 }
264
265 IOLockUnlock(shared->interfaceLock);
266
267 return ret;
268 }
269
270 void
271 IOPerfControlClient::unregisterDevice(__unused IOService *driver, IOService *device)
272 {
273 IOLockLock(shared->interfaceLock);
274
275 if (shared->interface.version > 0) {
276 shared->interface.unregisterDevice(device);
277 } else {
278 shared->deviceRegistrationList->removeObject(device);
279 }
280
281 IOLockUnlock(shared->interfaceLock);
282 }
283
284 uint64_t
285 IOPerfControlClient::workSubmit(IOService *device, WorkSubmitArgs *args)
286 {
287 #if CONFIG_THREAD_GROUPS
288 auto *thread_group = thread_group_get(current_thread());
289 if (!thread_group) {
290 return kIOPerfControlClientWorkUntracked;
291 }
292
293 PerfControllerInterface::WorkState state{
294 .thread_group_id = thread_group_get_id(thread_group),
295 .thread_group_data = thread_group_get_machine_data(thread_group),
296 .work_data = nullptr,
297 .work_data_size = 0,
298 .started = false,
299 };
300 if (!shared->interface.workCanSubmit(device, &state, args)) {
301 return kIOPerfControlClientWorkUntracked;
302 }
303
304 uint64_t token = allocateToken(thread_group);
305 if (token != kIOPerfControlClientWorkUntracked) {
306 state.work_data = &workTable[token].perfcontrol_data;
307 state.work_data_size = sizeof(workTable[token].perfcontrol_data);
308 shared->interface.workSubmit(device, tokenToGlobalUniqueToken(token), &state, args);
309 }
310 return token;
311 #else
312 return kIOPerfControlClientWorkUntracked;
313 #endif
314 }
315
316 uint64_t
317 IOPerfControlClient::workSubmitAndBegin(IOService *device, WorkSubmitArgs *submitArgs, WorkBeginArgs *beginArgs)
318 {
319 #if CONFIG_THREAD_GROUPS
320 auto *thread_group = thread_group_get(current_thread());
321 if (!thread_group) {
322 return kIOPerfControlClientWorkUntracked;
323 }
324
325 PerfControllerInterface::WorkState state{
326 .thread_group_id = thread_group_get_id(thread_group),
327 .thread_group_data = thread_group_get_machine_data(thread_group),
328 .work_data = nullptr,
329 .work_data_size = 0,
330 .started = false,
331 };
332 if (!shared->interface.workCanSubmit(device, &state, submitArgs)) {
333 return kIOPerfControlClientWorkUntracked;
334 }
335
336 uint64_t token = allocateToken(thread_group);
337 if (token != kIOPerfControlClientWorkUntracked) {
338 auto &entry = workTable[token];
339 state.work_data = &entry.perfcontrol_data;
340 state.work_data_size = sizeof(workTable[token].perfcontrol_data);
341 shared->interface.workSubmit(device, tokenToGlobalUniqueToken(token), &state, submitArgs);
342 state.started = true;
343 shared->interface.workBegin(device, tokenToGlobalUniqueToken(token), &state, beginArgs);
344 markEntryStarted(token, true);
345 }
346 return token;
347 #else
348 return kIOPerfControlClientWorkUntracked;
349 #endif
350 }
351
352 void
353 IOPerfControlClient::workBegin(IOService *device, uint64_t token, WorkBeginArgs *args)
354 {
355 #if CONFIG_THREAD_GROUPS
356 WorkTableEntry *entry = getEntryForToken(token);
357 if (entry == nullptr) {
358 return;
359 }
360
361 assertf(!entry->started, "Work for token %llu was already started", token);
362
363 PerfControllerInterface::WorkState state{
364 .thread_group_id = thread_group_get_id(entry->thread_group),
365 .thread_group_data = thread_group_get_machine_data(entry->thread_group),
366 .work_data = &entry->perfcontrol_data,
367 .work_data_size = sizeof(entry->perfcontrol_data),
368 .started = true,
369 };
370 shared->interface.workBegin(device, tokenToGlobalUniqueToken(token), &state, args);
371 markEntryStarted(token, true);
372 #endif
373 }
374
375 void
376 IOPerfControlClient::workEnd(IOService *device, uint64_t token, WorkEndArgs *args, bool done)
377 {
378 #if CONFIG_THREAD_GROUPS
379 WorkTableEntry *entry = getEntryForToken(token);
380 if (entry == nullptr) {
381 return;
382 }
383
384 PerfControllerInterface::WorkState state{
385 .thread_group_id = thread_group_get_id(entry->thread_group),
386 .thread_group_data = thread_group_get_machine_data(entry->thread_group),
387 .work_data = &entry->perfcontrol_data,
388 .work_data_size = sizeof(entry->perfcontrol_data),
389 .started = entry->started,
390 };
391 shared->interface.workEnd(device, tokenToGlobalUniqueToken(token), &state, args, done);
392
393 if (done) {
394 deallocateToken(token);
395 } else {
396 markEntryStarted(token, false);
397 }
398 #endif
399 }
400
401 static _Atomic uint64_t unique_work_context_id = 1ull;
402
403 class IOPerfControlWorkContext : public OSObject
404 {
405 OSDeclareDefaultStructors(IOPerfControlWorkContext);
406
407 public:
408 uint64_t id;
409 struct thread_group *thread_group;
410 bool started;
411 uint8_t perfcontrol_data[32];
412
413 bool init() override;
414 void reset();
415 void free() override;
416 };
417
418 OSDefineMetaClassAndStructors(IOPerfControlWorkContext, OSObject);
419
420 bool
421 IOPerfControlWorkContext::init()
422 {
423 if (!super::init()) {
424 return false;
425 }
426 id = atomic_fetch_add_explicit(&unique_work_context_id, 1, memory_order_relaxed) + 1;
427 reset();
428 return true;
429 }
430
431 void
432 IOPerfControlWorkContext::reset()
433 {
434 thread_group = nullptr;
435 started = false;
436 bzero(perfcontrol_data, sizeof(perfcontrol_data));
437 }
438
439 void
440 IOPerfControlWorkContext::free()
441 {
442 assertf(thread_group == nullptr, "IOPerfControlWorkContext ID %llu being released without calling workEnd!\n", id);
443 super::free();
444 }
445
446 OSObject *
447 IOPerfControlClient::copyWorkContext()
448 {
449 IOPerfControlWorkContext *context = new IOPerfControlWorkContext;
450
451 if (context == nullptr) {
452 return nullptr;
453 }
454
455 if (!context->init()) {
456 context->free();
457 return nullptr;
458 }
459
460 return OSDynamicCast(OSObject, context);
461 }
462
463 bool
464 IOPerfControlClient::workSubmitAndBeginWithContext(IOService *device, OSObject *context, WorkSubmitArgs *submitArgs, WorkBeginArgs *beginArgs)
465 {
466 #if CONFIG_THREAD_GROUPS
467
468 if (workSubmitWithContext(device, context, submitArgs) == false) {
469 return false;
470 }
471
472 IOPerfControlWorkContext *work_context = OSDynamicCast(IOPerfControlWorkContext, context);
473
474 PerfControllerInterface::WorkState state{
475 .thread_group_id = thread_group_get_id(work_context->thread_group),
476 .thread_group_data = thread_group_get_machine_data(work_context->thread_group),
477 .work_data = &work_context->perfcontrol_data,
478 .work_data_size = sizeof(work_context->perfcontrol_data),
479 .started = true,
480 };
481
482 shared->interface.workBegin(device, work_context->id, &state, beginArgs);
483
484 work_context->started = true;
485
486 return true;
487 #else
488 return false;
489 #endif
490 }
491
492 bool
493 IOPerfControlClient::workSubmitWithContext(IOService *device, OSObject *context, WorkSubmitArgs *args)
494 {
495 #if CONFIG_THREAD_GROUPS
496 IOPerfControlWorkContext *work_context = OSDynamicCast(IOPerfControlWorkContext, context);
497
498 if (work_context == nullptr) {
499 return false;
500 }
501
502 auto *thread_group = thread_group_get(current_thread());
503 assert(thread_group != nullptr);
504
505 assertf(!work_context->started, "IOPerfControlWorkContext ID %llu was already started", work_context->id);
506 assertf(work_context->thread_group == nullptr, "IOPerfControlWorkContext ID %llu has already taken a refcount on TG 0x%p \n", work_context->id, (void *)(work_context->thread_group));
507
508 PerfControllerInterface::WorkState state{
509 .thread_group_id = thread_group_get_id(thread_group),
510 .thread_group_data = thread_group_get_machine_data(thread_group),
511 .work_data = nullptr,
512 .work_data_size = 0,
513 .started = false,
514 };
515 if (!shared->interface.workCanSubmit(device, &state, args)) {
516 return false;
517 }
518
519 work_context->thread_group = thread_group_retain(thread_group);
520
521 state.work_data = &work_context->perfcontrol_data;
522 state.work_data_size = sizeof(work_context->perfcontrol_data);
523
524 shared->interface.workSubmit(device, work_context->id, &state, args);
525
526 return true;
527 #else
528 return false;
529 #endif
530 }
531
532 void
533 IOPerfControlClient::workBeginWithContext(IOService *device, OSObject *context, WorkBeginArgs *args)
534 {
535 #if CONFIG_THREAD_GROUPS
536 IOPerfControlWorkContext *work_context = OSDynamicCast(IOPerfControlWorkContext, context);
537
538 if (work_context == nullptr) {
539 return;
540 }
541
542 if (work_context->thread_group == nullptr) {
543 // This Work Context has not taken a refcount on a TG
544 return;
545 }
546
547 assertf(!work_context->started, "IOPerfControlWorkContext %llu was already started", work_context->id);
548
549 PerfControllerInterface::WorkState state{
550 .thread_group_id = thread_group_get_id(work_context->thread_group),
551 .thread_group_data = thread_group_get_machine_data(work_context->thread_group),
552 .work_data = &work_context->perfcontrol_data,
553 .work_data_size = sizeof(work_context->perfcontrol_data),
554 .started = true,
555 };
556 shared->interface.workBegin(device, work_context->id, &state, args);
557
558 work_context->started = true;
559 #endif
560 }
561
562 void
563 IOPerfControlClient::workEndWithContext(IOService *device, OSObject *context, WorkEndArgs *args, bool done)
564 {
565 #if CONFIG_THREAD_GROUPS
566 IOPerfControlWorkContext *work_context = OSDynamicCast(IOPerfControlWorkContext, context);
567
568 if (work_context == nullptr) {
569 return;
570 }
571
572 if (work_context->thread_group == nullptr) {
573 return;
574 }
575
576 PerfControllerInterface::WorkState state{
577 .thread_group_id = thread_group_get_id(work_context->thread_group),
578 .thread_group_data = thread_group_get_machine_data(work_context->thread_group),
579 .work_data = &work_context->perfcontrol_data,
580 .work_data_size = sizeof(work_context->perfcontrol_data),
581 .started = work_context->started,
582 };
583
584 shared->interface.workEnd(device, work_context->id, &state, args, done);
585
586 if (done) {
587 thread_group_release(work_context->thread_group);
588 work_context->reset();
589 } else {
590 work_context->started = false;
591 }
592
593 return;
594 #else
595 return;
596 #endif
597 }
598
599 IOReturn
600 IOPerfControlClient::registerPerformanceController(PerfControllerInterface pci)
601 {
602 IOReturn result = kIOReturnError;
603
604 IOLockLock(shared->interfaceLock);
605
606 if (shared->interface.version == 0 && pci.version > 0) {
607 assert(pci.registerDevice && pci.unregisterDevice && pci.workCanSubmit && pci.workSubmit && pci.workBegin && pci.workEnd);
608 result = kIOReturnSuccess;
609
610 OSObject *obj;
611 while ((obj = shared->deviceRegistrationList->getAnyObject())) {
612 IOService *device = OSDynamicCast(IOService, obj);
613 if (device) {
614 pci.registerDevice(device);
615 }
616 shared->deviceRegistrationList->removeObject(obj);
617 }
618
619 shared->interface = pci;
620 }
621
622 IOLockUnlock(shared->interfaceLock);
623
624 return result;
625 }