2 * Copyright (c) 2017 Apple Inc. All rights reserved.
5 #include <IOKit/perfcontrol/IOPerfControl.h>
9 #include <kern/thread_group.h>
12 #define super OSObject
13 OSDefineMetaClassAndStructors(IOPerfControlClient
, OSObject
);
15 static IOPerfControlClient::IOPerfControlClientShared
*_Atomic gIOPerfControlClientShared
;
18 IOPerfControlClient::init(IOService
*driver
, uint64_t maxWorkCapacity
)
20 // TODO: Remove this limit and implement dynamic table growth if workloads are found that exceed this
21 if (maxWorkCapacity
> kMaxWorkTableNumEntries
) {
22 maxWorkCapacity
= kMaxWorkTableNumEntries
;
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
)));
37 atomic_init(&shared
->maxDriverIndex
, 0);
39 shared
->interface
= PerfControllerInterface
{
42 [](IOService
*device
) {
43 return kIOReturnSuccess
;
46 [](IOService
*device
) {
47 return kIOReturnSuccess
;
50 [](IOService
*device
, PerfControllerInterface::WorkState
*state
, WorkSubmitArgs
*args
) {
54 [](IOService
*device
, uint64_t token
, PerfControllerInterface::WorkState
*state
, WorkSubmitArgs
*args
) {
57 [](IOService
*device
, uint64_t token
, PerfControllerInterface::WorkState
*state
, WorkBeginArgs
*args
) {
60 [](IOService
*device
, uint64_t token
, PerfControllerInterface::WorkState
*state
, WorkEndArgs
*args
, bool done
) {
64 shared
->interfaceLock
= IOLockAlloc();
65 if (!shared
->interfaceLock
) {
66 goto shared_init_error
;
69 shared
->deviceRegistrationList
= OSSet::withCapacity(4);
70 if (!shared
->deviceRegistrationList
) {
71 goto shared_init_error
;
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
));
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;
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
)));
94 bzero(workTable
, workTableLength
* sizeof(WorkTableEntry
));
95 workTableNextIndex
= 1;
97 workTableLock
= IOSimpleLockAlloc();
107 kfree(workTable
, maxWorkCapacity
* sizeof(WorkTableEntry
));
110 IOSimpleLockFree(workTableLock
);
115 if (shared
->interfaceLock
) {
116 IOLockFree(shared
->interfaceLock
);
118 if (shared
->deviceRegistrationList
) {
119 shared
->deviceRegistrationList
->release();
121 kfree(shared
, sizeof(*shared
));
127 IOPerfControlClient
*
128 IOPerfControlClient::copyClient(IOService
*driver
, uint64_t maxWorkCapacity
)
130 IOPerfControlClient
*client
= new IOPerfControlClient
;
131 if (!client
|| !client
->init(driver
, maxWorkCapacity
)) {
132 panic("could not create IOPerfControlClient");
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
145 IOPerfControlClient::tokenToGlobalUniqueToken(uint64_t token
)
147 return token
| (static_cast<uint64_t>(driverIndex
) << kWorkTableIndexBits
);
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.
158 IOPerfControlClient::allocateToken(thread_group
*thread_group
)
160 uint64_t token
= kIOPerfControlClientWorkUntracked
;
162 #if CONFIG_THREAD_GROUPS
163 auto s
= IOSimpleLockLockDisableInterrupt(workTableLock
);
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
;
173 // next integer between 1 and workTableLength - 1
174 workTableNextIndex
= (index
% (workTableLength
- 1)) + 1;
177 // next integer between 1 and workTableLength - 1
178 index
= (index
% (workTableLength
- 1)) + 1;
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
190 panic("Tokens allocated for this device exceeded maximum of %zu.\n",
191 workTableLength
- 1); // - 1 since entry 0 is for kIOPerfControlClientWorkUntracked
195 IOSimpleLockUnlockEnableInterrupt(workTableLock
, s
);
202 IOPerfControlClient::deallocateToken(uint64_t token
)
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
);
209 auto &entry
= workTable
[token
];
210 auto *thread_group
= entry
.thread_group
;
211 bzero(&entry
, sizeof(entry
));
212 workTableNextIndex
= token
;
214 IOSimpleLockUnlockEnableInterrupt(workTableLock
, s
);
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
);
222 IOPerfControlClient::WorkTableEntry
*
223 IOPerfControlClient::getEntryForToken(uint64_t token
)
225 if (token
== kIOPerfControlClientWorkUntracked
) {
229 if (token
>= workTableLength
) {
230 panic("Invalid work token (%llu): index out of bounds.", token
);
233 WorkTableEntry
*entry
= &workTable
[token
];
234 assertf(entry
->thread_group
, "Invalid work token: %llu", token
);
239 IOPerfControlClient::markEntryStarted(uint64_t token
, bool started
)
241 if (token
== kIOPerfControlClientWorkUntracked
) {
245 if (token
>= workTableLength
) {
246 panic("Invalid work token (%llu): index out of bounds.", token
);
249 workTable
[token
].started
= started
;
253 IOPerfControlClient::registerDevice(__unused IOService
*driver
, IOService
*device
)
255 IOReturn ret
= kIOReturnSuccess
;
257 IOLockLock(shared
->interfaceLock
);
259 if (shared
->interface
.version
> 0) {
260 ret
= shared
->interface
.registerDevice(device
);
262 shared
->deviceRegistrationList
->setObject(device
);
265 IOLockUnlock(shared
->interfaceLock
);
271 IOPerfControlClient::unregisterDevice(__unused IOService
*driver
, IOService
*device
)
273 IOLockLock(shared
->interfaceLock
);
275 if (shared
->interface
.version
> 0) {
276 shared
->interface
.unregisterDevice(device
);
278 shared
->deviceRegistrationList
->removeObject(device
);
281 IOLockUnlock(shared
->interfaceLock
);
285 IOPerfControlClient::workSubmit(IOService
*device
, WorkSubmitArgs
*args
)
287 #if CONFIG_THREAD_GROUPS
288 auto *thread_group
= thread_group_get(current_thread());
290 return kIOPerfControlClientWorkUntracked
;
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,
300 if (!shared
->interface
.workCanSubmit(device
, &state
, args
)) {
301 return kIOPerfControlClientWorkUntracked
;
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
);
312 return kIOPerfControlClientWorkUntracked
;
317 IOPerfControlClient::workSubmitAndBegin(IOService
*device
, WorkSubmitArgs
*submitArgs
, WorkBeginArgs
*beginArgs
)
319 #if CONFIG_THREAD_GROUPS
320 auto *thread_group
= thread_group_get(current_thread());
322 return kIOPerfControlClientWorkUntracked
;
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,
332 if (!shared
->interface
.workCanSubmit(device
, &state
, submitArgs
)) {
333 return kIOPerfControlClientWorkUntracked
;
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);
348 return kIOPerfControlClientWorkUntracked
;
353 IOPerfControlClient::workBegin(IOService
*device
, uint64_t token
, WorkBeginArgs
*args
)
355 #if CONFIG_THREAD_GROUPS
356 WorkTableEntry
*entry
= getEntryForToken(token
);
357 if (entry
== nullptr) {
361 assertf(!entry
->started
, "Work for token %llu was already started", token
);
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
),
370 shared
->interface
.workBegin(device
, tokenToGlobalUniqueToken(token
), &state
, args
);
371 markEntryStarted(token
, true);
376 IOPerfControlClient::workEnd(IOService
*device
, uint64_t token
, WorkEndArgs
*args
, bool done
)
378 #if CONFIG_THREAD_GROUPS
379 WorkTableEntry
*entry
= getEntryForToken(token
);
380 if (entry
== nullptr) {
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
,
391 shared
->interface
.workEnd(device
, tokenToGlobalUniqueToken(token
), &state
, args
, done
);
394 deallocateToken(token
);
396 markEntryStarted(token
, false);
401 static _Atomic
uint64_t unique_work_context_id
= 1ull;
403 class IOPerfControlWorkContext
: public OSObject
405 OSDeclareDefaultStructors(IOPerfControlWorkContext
);
409 struct thread_group
*thread_group
;
411 uint8_t perfcontrol_data
[32];
413 bool init() override
;
415 void free() override
;
418 OSDefineMetaClassAndStructors(IOPerfControlWorkContext
, OSObject
);
421 IOPerfControlWorkContext::init()
423 if (!super::init()) {
426 id
= atomic_fetch_add_explicit(&unique_work_context_id
, 1, memory_order_relaxed
) + 1;
432 IOPerfControlWorkContext::reset()
434 thread_group
= nullptr;
436 bzero(perfcontrol_data
, sizeof(perfcontrol_data
));
440 IOPerfControlWorkContext::free()
442 assertf(thread_group
== nullptr, "IOPerfControlWorkContext ID %llu being released without calling workEnd!\n", id
);
447 IOPerfControlClient::copyWorkContext()
449 IOPerfControlWorkContext
*context
= new IOPerfControlWorkContext
;
451 if (context
== nullptr) {
455 if (!context
->init()) {
460 return OSDynamicCast(OSObject
, context
);
464 IOPerfControlClient::workSubmitAndBeginWithContext(IOService
*device
, OSObject
*context
, WorkSubmitArgs
*submitArgs
, WorkBeginArgs
*beginArgs
)
466 #if CONFIG_THREAD_GROUPS
468 if (workSubmitWithContext(device
, context
, submitArgs
) == false) {
472 IOPerfControlWorkContext
*work_context
= OSDynamicCast(IOPerfControlWorkContext
, context
);
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
),
482 shared
->interface
.workBegin(device
, work_context
->id
, &state
, beginArgs
);
484 work_context
->started
= true;
493 IOPerfControlClient::workSubmitWithContext(IOService
*device
, OSObject
*context
, WorkSubmitArgs
*args
)
495 #if CONFIG_THREAD_GROUPS
496 IOPerfControlWorkContext
*work_context
= OSDynamicCast(IOPerfControlWorkContext
, context
);
498 if (work_context
== nullptr) {
502 auto *thread_group
= thread_group_get(current_thread());
503 assert(thread_group
!= nullptr);
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
));
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,
515 if (!shared
->interface
.workCanSubmit(device
, &state
, args
)) {
519 work_context
->thread_group
= thread_group_retain(thread_group
);
521 state
.work_data
= &work_context
->perfcontrol_data
;
522 state
.work_data_size
= sizeof(work_context
->perfcontrol_data
);
524 shared
->interface
.workSubmit(device
, work_context
->id
, &state
, args
);
533 IOPerfControlClient::workBeginWithContext(IOService
*device
, OSObject
*context
, WorkBeginArgs
*args
)
535 #if CONFIG_THREAD_GROUPS
536 IOPerfControlWorkContext
*work_context
= OSDynamicCast(IOPerfControlWorkContext
, context
);
538 if (work_context
== nullptr) {
542 if (work_context
->thread_group
== nullptr) {
543 // This Work Context has not taken a refcount on a TG
547 assertf(!work_context
->started
, "IOPerfControlWorkContext %llu was already started", work_context
->id
);
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
),
556 shared
->interface
.workBegin(device
, work_context
->id
, &state
, args
);
558 work_context
->started
= true;
563 IOPerfControlClient::workEndWithContext(IOService
*device
, OSObject
*context
, WorkEndArgs
*args
, bool done
)
565 #if CONFIG_THREAD_GROUPS
566 IOPerfControlWorkContext
*work_context
= OSDynamicCast(IOPerfControlWorkContext
, context
);
568 if (work_context
== nullptr) {
572 if (work_context
->thread_group
== nullptr) {
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
,
584 shared
->interface
.workEnd(device
, work_context
->id
, &state
, args
, done
);
587 thread_group_release(work_context
->thread_group
);
588 work_context
->reset();
590 work_context
->started
= false;
600 IOPerfControlClient::registerPerformanceController(PerfControllerInterface pci
)
602 IOReturn result
= kIOReturnError
;
604 IOLockLock(shared
->interfaceLock
);
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
;
611 while ((obj
= shared
->deviceRegistrationList
->getAnyObject())) {
612 IOService
*device
= OSDynamicCast(IOService
, obj
);
614 pci
.registerDevice(device
);
616 shared
->deviceRegistrationList
->removeObject(obj
);
619 shared
->interface
= pci
;
622 IOLockUnlock(shared
->interfaceLock
);