xref: /xnu-11215/iokit/Kernel/IOPerfControl.cpp (revision bb611c8f)
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 }
626