1/*
2 * Copyright (c) 2011 Apple Computer, 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/* all thread states code */
30#include <mach/mach_types.h>
31#include <IOKit/IOTypes.h>
32#include <IOKit/IOLocks.h>
33#include <sys/errno.h>
34
35#include <chud/chud_xnu.h>
36
37#include <kperf/buffer.h>
38#include <kperf/sample.h>
39#include <kperf/context.h>
40#include <kperf/action.h>
41#include <kperf/filter.h>
42#include <kperf/pet.h>
43#include <kperf/timetrigger.h>
44
45/* timer id to call back on */
46static unsigned pet_timerid = 0;
47
48/* aciton ID to call
49 * We also use this as the sync point for waiting, for no good reason
50 */
51static unsigned pet_actionid = 0;
52
53/* the actual thread pointer */
54static thread_t pet_thread = NULL;
55
56/* Lock on which to synchronise */
57static IOLock *pet_lock = NULL;
58
59/* where to sample data to */
60static struct kperf_sample pet_sample_buf;
61
62/* sample an actual, honest to god thread! */
63static void
64pet_sample_thread( thread_t thread )
65{
66	struct kperf_context ctx;
67	task_t task;
68
69	/* work out the context */
70	ctx.cur_thread = thread;
71	ctx.cur_pid = -1;
72
73	task = chudxnu_task_for_thread(thread);
74	if(task)
75		ctx.cur_pid = chudxnu_pid_for_task(task);
76
77	/* do the actual sample */
78	kperf_sample( &pet_sample_buf, &ctx, pet_actionid, false );
79}
80
81/* given a list of threads, preferably stopped, sample 'em! */
82static void
83pet_sample_thread_list( mach_msg_type_number_t threadc, thread_array_t threadv )
84{
85	unsigned int i;
86
87	for( i = 0; i < threadc; i++ )
88	{
89		thread_t thread = threadv[i];
90
91		if( !thread )
92			/* XXX? */
93			continue;
94
95		pet_sample_thread( thread );
96	}
97}
98
99/* given a task (preferably stopped), sample all the threads in it */
100static void
101pet_sample_task( task_t task )
102{
103	mach_msg_type_number_t threadc;
104	thread_array_t threadv;
105	kern_return_t kr;
106
107	kr = chudxnu_task_threads(task, &threadv, &threadc);
108	if( kr != KERN_SUCCESS )
109	{
110		BUF_INFO2(PERF_PET_ERROR, ERR_THREAD, kr);
111		return;
112	}
113
114	pet_sample_thread_list( threadc, threadv );
115
116	chudxnu_free_thread_list(&threadv, &threadc);
117}
118
119/* given a list of tasks, sample all the threads in 'em */
120static void
121pet_sample_task_list( int taskc, task_array_t taskv  )
122{
123	int i;
124
125	for( i = 0; i < taskc; i++ )
126	{
127		kern_return_t kr;
128		task_t task = taskv[i];
129
130		/* FIXME: necessary? old code did this, our hacky
131		 * filtering code does, too
132		 */
133		if(!task) {
134			continue;
135		}
136
137		/* try and stop any task other than the kernel task */
138		if( task != kernel_task )
139		{
140			kr = task_suspend( task );
141
142			/* try the next task */
143			if( kr != KERN_SUCCESS )
144				continue;
145		}
146
147		/* sample it */
148		pet_sample_task( task );
149
150		/* if it wasn't the kernel, resume it */
151		if( task != kernel_task )
152			task_resume(task);
153	}
154}
155
156static void
157pet_sample_all_tasks(void)
158{
159	task_array_t taskv = NULL;
160	mach_msg_type_number_t taskc = 0;
161	kern_return_t kr;
162
163	kr = chudxnu_all_tasks(&taskv, &taskc);
164
165	if( kr != KERN_SUCCESS )
166	{
167		BUF_INFO2(PERF_PET_ERROR, ERR_TASK, kr);
168		return;
169	}
170
171	pet_sample_task_list( taskc, taskv );
172	chudxnu_free_task_list(&taskv, &taskc);
173}
174
175static void
176pet_sample_pid_filter(void)
177{
178	task_t *taskv = NULL;
179	int *pidv, pidc, i;
180	vm_size_t asize;
181
182	kperf_filter_pid_list( &pidc, &pidv );
183	if( pidc == 0  )
184	{
185		BUF_INFO2(PERF_PET_ERROR, ERR_PID, 0);
186		return;
187	}
188
189	asize = pidc * sizeof(task_t);
190	taskv = kalloc( asize );
191
192	if( taskv == NULL )
193		goto out;
194
195	/* convert the pid list into a task list */
196	for( i = 0; i < pidc; i++ )
197	{
198		int pid = pidv[i];
199		if( pid == -1 )
200			taskv[i] = NULL;
201		else
202			taskv[i] = chudxnu_task_for_pid(pid);
203	}
204
205	/* now sample the task list */
206	pet_sample_task_list( pidc, taskv );
207
208	kfree(taskv, asize);
209
210out:
211	kperf_filter_free_pid_list( &pidc, &pidv );
212}
213
214/* do the pet sample */
215static void
216pet_work_unit(void)
217{
218	int pid_filter;
219
220	/* check if we're filtering on pid  */
221	pid_filter = kperf_filter_on_pid();
222
223	if( pid_filter )
224	{
225		BUF_INFO1(PERF_PET_SAMPLE | DBG_FUNC_START, 1);
226		pet_sample_pid_filter();
227	}
228	else
229	{
230		/* otherwise filter everything */
231		BUF_INFO1(PERF_PET_SAMPLE | DBG_FUNC_START, 0);
232		pet_sample_all_tasks();
233	}
234
235	BUF_INFO1(PERF_PET_SAMPLE | DBG_FUNC_END, 0);
236
237}
238
239/* sleep indefinitely */
240static void
241pet_idle(void)
242{
243	IOLockLock(pet_lock);
244	IOLockSleep(pet_lock, &pet_actionid, THREAD_UNINT);
245	IOLockUnlock(pet_lock);
246}
247
248/* loop between sampling and waiting */
249static void
250pet_thread_loop( __unused void *param, __unused wait_result_t wr )
251{
252	BUF_INFO1(PERF_PET_THREAD, 1);
253
254	while(1)
255	{
256		BUF_INFO1(PERF_PET_IDLE, 0);
257		pet_idle();
258
259		BUF_INFO1(PERF_PET_RUN, 0);
260		pet_work_unit();
261
262		/* re-program the timer */
263		kperf_timer_pet_set( pet_timerid );
264
265		/* FIXME: break here on a condition? */
266	}
267}
268
269/* make sure the thread takes a new period value */
270void
271kperf_pet_timer_config( unsigned timerid, unsigned actionid )
272{
273	/* hold the lock so pet thread doesn't run while we do this */
274	IOLockLock(pet_lock);
275
276	BUF_INFO1(PERF_PET_THREAD, 3);
277
278	/* set values */
279	pet_timerid = timerid;
280	pet_actionid = actionid;
281
282	/* done */
283	IOLockUnlock(pet_lock);
284}
285
286/* make the thread run! */
287void
288kperf_pet_thread_go(void)
289{
290	/* Make the thread go */
291	IOLockWakeup(pet_lock, &pet_actionid, FALSE);
292}
293
294
295/* wait for the pet thread to finish a run */
296void
297kperf_pet_thread_wait(void)
298{
299	/* acquire the lock to ensure the thread is parked. */
300	IOLockLock(pet_lock);
301	IOLockUnlock(pet_lock);
302}
303
304/* keep the pet thread around while we run */
305int
306kperf_pet_init(void)
307{
308	kern_return_t rc;
309	thread_t t;
310
311	if( pet_thread != NULL )
312		return 0;
313
314	/* make the sync poing */
315	pet_lock = IOLockAlloc();
316	if( pet_lock == NULL )
317		return ENOMEM;
318
319	/* create the thread */
320	BUF_INFO1(PERF_PET_THREAD, 0);
321	rc = kernel_thread_start( pet_thread_loop, NULL, &t );
322	if( rc != KERN_SUCCESS )
323	{
324		IOLockFree( pet_lock );
325		pet_lock = NULL;
326		return ENOMEM;
327	}
328
329	/* OK! */
330	return 0;
331}
332