diff options
author | Markus Metzger <markus.t.metzger@intel.com> | 2008-11-25 03:01:25 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-11-25 11:31:11 -0500 |
commit | ca0002a179bfa532d009a9272d619732872c49bd (patch) | |
tree | 9a9ca02164dfb2c13afaa38ab67f3f15d8dd5ce8 /arch/x86/kernel/ds.c | |
parent | 7d55718b0c19ba611241c330f688ee824e9bab79 (diff) |
x86, bts: base in-kernel ds interface on handles
Impact: generalize the DS code to shared buffers
Change the in-kernel ds.h interface to identify the tracer via a
handle returned on ds_request_~().
Tracers used to be identified via their task_struct.
The changes are required to allow DS to be shared between different
tasks, which is needed for perfmon2 and for ftrace.
For ptrace, the handle is stored in the traced task's task_struct.
This should probably go into a (arch-specific) ptrace context some
time.
Signed-off-by: Markus Metzger <markus.t.metzger@intel.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'arch/x86/kernel/ds.c')
-rw-r--r-- | arch/x86/kernel/ds.c | 679 |
1 files changed, 340 insertions, 339 deletions
diff --git a/arch/x86/kernel/ds.c b/arch/x86/kernel/ds.c index d6938d9351cf..96768e9cce99 100644 --- a/arch/x86/kernel/ds.c +++ b/arch/x86/kernel/ds.c | |||
@@ -28,6 +28,7 @@ | |||
28 | #include <linux/slab.h> | 28 | #include <linux/slab.h> |
29 | #include <linux/sched.h> | 29 | #include <linux/sched.h> |
30 | #include <linux/mm.h> | 30 | #include <linux/mm.h> |
31 | #include <linux/kernel.h> | ||
31 | 32 | ||
32 | 33 | ||
33 | /* | 34 | /* |
@@ -44,6 +45,35 @@ struct ds_configuration { | |||
44 | }; | 45 | }; |
45 | static struct ds_configuration ds_cfg; | 46 | static struct ds_configuration ds_cfg; |
46 | 47 | ||
48 | /* | ||
49 | * A BTS or PEBS tracer. | ||
50 | * | ||
51 | * This holds the configuration of the tracer and serves as a handle | ||
52 | * to identify tracers. | ||
53 | */ | ||
54 | struct ds_tracer { | ||
55 | /* the DS context (partially) owned by this tracer */ | ||
56 | struct ds_context *context; | ||
57 | /* the buffer provided on ds_request() and its size in bytes */ | ||
58 | void *buffer; | ||
59 | size_t size; | ||
60 | /* the number of allocated pages for on-request allocated buffers */ | ||
61 | unsigned int pages; | ||
62 | }; | ||
63 | |||
64 | struct bts_tracer { | ||
65 | /* the common DS part */ | ||
66 | struct ds_tracer ds; | ||
67 | /* buffer overflow notification function */ | ||
68 | bts_ovfl_callback_t ovfl; | ||
69 | }; | ||
70 | |||
71 | struct pebs_tracer { | ||
72 | /* the common DS part */ | ||
73 | struct ds_tracer ds; | ||
74 | /* buffer overflow notification function */ | ||
75 | pebs_ovfl_callback_t ovfl; | ||
76 | }; | ||
47 | 77 | ||
48 | /* | 78 | /* |
49 | * Debug Store (DS) save area configuration (see Intel64 and IA32 | 79 | * Debug Store (DS) save area configuration (see Intel64 and IA32 |
@@ -107,35 +137,15 @@ static inline void ds_set(unsigned char *base, enum ds_qualifier qual, | |||
107 | (*(unsigned long *)base) = value; | 137 | (*(unsigned long *)base) = value; |
108 | } | 138 | } |
109 | 139 | ||
140 | #define DS_ALIGNMENT (1 << 3) /* BTS and PEBS buffer alignment */ | ||
141 | |||
110 | 142 | ||
111 | /* | 143 | /* |
112 | * Locking is done only for allocating BTS or PEBS resources and for | 144 | * Locking is done only for allocating BTS or PEBS resources and for |
113 | * guarding context and buffer memory allocation. | 145 | * guarding context and buffer memory allocation. |
114 | * | ||
115 | * Most functions require the current task to own the ds context part | ||
116 | * they are going to access. All the locking is done when validating | ||
117 | * access to the context. | ||
118 | */ | 146 | */ |
119 | static spinlock_t ds_lock = __SPIN_LOCK_UNLOCKED(ds_lock); | 147 | static spinlock_t ds_lock = __SPIN_LOCK_UNLOCKED(ds_lock); |
120 | 148 | ||
121 | /* | ||
122 | * Validate that the current task is allowed to access the BTS/PEBS | ||
123 | * buffer of the parameter task. | ||
124 | * | ||
125 | * Returns 0, if access is granted; -Eerrno, otherwise. | ||
126 | */ | ||
127 | static inline int ds_validate_access(struct ds_context *context, | ||
128 | enum ds_qualifier qual) | ||
129 | { | ||
130 | if (!context) | ||
131 | return -EPERM; | ||
132 | |||
133 | if (context->owner[qual] == current) | ||
134 | return 0; | ||
135 | |||
136 | return -EPERM; | ||
137 | } | ||
138 | |||
139 | 149 | ||
140 | /* | 150 | /* |
141 | * We either support (system-wide) per-cpu or per-thread allocation. | 151 | * We either support (system-wide) per-cpu or per-thread allocation. |
@@ -183,51 +193,13 @@ static inline int check_tracer(struct task_struct *task) | |||
183 | * | 193 | * |
184 | * Contexts are use-counted. They are allocated on first access and | 194 | * Contexts are use-counted. They are allocated on first access and |
185 | * deallocated when the last user puts the context. | 195 | * deallocated when the last user puts the context. |
186 | * | ||
187 | * We distinguish between an allocating and a non-allocating get of a | ||
188 | * context: | ||
189 | * - the allocating get is used for requesting BTS/PEBS resources. It | ||
190 | * requires the caller to hold the global ds_lock. | ||
191 | * - the non-allocating get is used for all other cases. A | ||
192 | * non-existing context indicates an error. It acquires and releases | ||
193 | * the ds_lock itself for obtaining the context. | ||
194 | * | ||
195 | * A context and its DS configuration are allocated and deallocated | ||
196 | * together. A context always has a DS configuration of the | ||
197 | * appropriate size. | ||
198 | */ | 196 | */ |
199 | static DEFINE_PER_CPU(struct ds_context *, system_context); | 197 | static DEFINE_PER_CPU(struct ds_context *, system_context); |
200 | 198 | ||
201 | #define this_system_context per_cpu(system_context, smp_processor_id()) | 199 | #define this_system_context per_cpu(system_context, smp_processor_id()) |
202 | 200 | ||
203 | /* | ||
204 | * Returns the pointer to the parameter task's context or to the | ||
205 | * system-wide context, if task is NULL. | ||
206 | * | ||
207 | * Increases the use count of the returned context, if not NULL. | ||
208 | */ | ||
209 | static inline struct ds_context *ds_get_context(struct task_struct *task) | 201 | static inline struct ds_context *ds_get_context(struct task_struct *task) |
210 | { | 202 | { |
211 | struct ds_context *context; | ||
212 | unsigned long irq; | ||
213 | |||
214 | spin_lock_irqsave(&ds_lock, irq); | ||
215 | |||
216 | context = (task ? task->thread.ds_ctx : this_system_context); | ||
217 | if (context) | ||
218 | context->count++; | ||
219 | |||
220 | spin_unlock_irqrestore(&ds_lock, irq); | ||
221 | |||
222 | return context; | ||
223 | } | ||
224 | |||
225 | /* | ||
226 | * Same as ds_get_context, but allocates the context and it's DS | ||
227 | * structure, if necessary; returns NULL; if out of memory. | ||
228 | */ | ||
229 | static inline struct ds_context *ds_alloc_context(struct task_struct *task) | ||
230 | { | ||
231 | struct ds_context **p_context = | 203 | struct ds_context **p_context = |
232 | (task ? &task->thread.ds_ctx : &this_system_context); | 204 | (task ? &task->thread.ds_ctx : &this_system_context); |
233 | struct ds_context *context = *p_context; | 205 | struct ds_context *context = *p_context; |
@@ -238,16 +210,9 @@ static inline struct ds_context *ds_alloc_context(struct task_struct *task) | |||
238 | if (!context) | 210 | if (!context) |
239 | return NULL; | 211 | return NULL; |
240 | 212 | ||
241 | context->ds = kzalloc(ds_cfg.sizeof_ds, GFP_KERNEL); | ||
242 | if (!context->ds) { | ||
243 | kfree(context); | ||
244 | return NULL; | ||
245 | } | ||
246 | |||
247 | spin_lock_irqsave(&ds_lock, irq); | 213 | spin_lock_irqsave(&ds_lock, irq); |
248 | 214 | ||
249 | if (*p_context) { | 215 | if (*p_context) { |
250 | kfree(context->ds); | ||
251 | kfree(context); | 216 | kfree(context); |
252 | 217 | ||
253 | context = *p_context; | 218 | context = *p_context; |
@@ -272,10 +237,6 @@ static inline struct ds_context *ds_alloc_context(struct task_struct *task) | |||
272 | return context; | 237 | return context; |
273 | } | 238 | } |
274 | 239 | ||
275 | /* | ||
276 | * Decreases the use count of the parameter context, if not NULL. | ||
277 | * Deallocates the context, if the use count reaches zero. | ||
278 | */ | ||
279 | static inline void ds_put_context(struct ds_context *context) | 240 | static inline void ds_put_context(struct ds_context *context) |
280 | { | 241 | { |
281 | unsigned long irq; | 242 | unsigned long irq; |
@@ -296,13 +257,6 @@ static inline void ds_put_context(struct ds_context *context) | |||
296 | if (!context->task || (context->task == current)) | 257 | if (!context->task || (context->task == current)) |
297 | wrmsrl(MSR_IA32_DS_AREA, 0); | 258 | wrmsrl(MSR_IA32_DS_AREA, 0); |
298 | 259 | ||
299 | put_tracer(context->task); | ||
300 | |||
301 | /* free any leftover buffers from tracers that did not | ||
302 | * deallocate them properly. */ | ||
303 | kfree(context->buffer[ds_bts]); | ||
304 | kfree(context->buffer[ds_pebs]); | ||
305 | kfree(context->ds); | ||
306 | kfree(context); | 260 | kfree(context); |
307 | out: | 261 | out: |
308 | spin_unlock_irqrestore(&ds_lock, irq); | 262 | spin_unlock_irqrestore(&ds_lock, irq); |
@@ -312,21 +266,29 @@ static inline void ds_put_context(struct ds_context *context) | |||
312 | /* | 266 | /* |
313 | * Handle a buffer overflow | 267 | * Handle a buffer overflow |
314 | * | 268 | * |
315 | * task: the task whose buffers are overflowing; | ||
316 | * NULL for a buffer overflow on the current cpu | ||
317 | * context: the ds context | 269 | * context: the ds context |
318 | * qual: the buffer type | 270 | * qual: the buffer type |
319 | */ | 271 | */ |
320 | static void ds_overflow(struct task_struct *task, struct ds_context *context, | 272 | static void ds_overflow(struct ds_context *context, enum ds_qualifier qual) |
321 | enum ds_qualifier qual) | 273 | { |
322 | { | 274 | switch (qual) { |
323 | if (!context) | 275 | case ds_bts: { |
324 | return; | 276 | struct bts_tracer *tracer = |
325 | 277 | container_of(context->owner[qual], | |
326 | if (context->callback[qual]) | 278 | struct bts_tracer, ds); |
327 | (*context->callback[qual])(task); | 279 | if (tracer->ovfl) |
328 | 280 | tracer->ovfl(tracer); | |
329 | /* todo: do some more overflow handling */ | 281 | } |
282 | break; | ||
283 | case ds_pebs: { | ||
284 | struct pebs_tracer *tracer = | ||
285 | container_of(context->owner[qual], | ||
286 | struct pebs_tracer, ds); | ||
287 | if (tracer->ovfl) | ||
288 | tracer->ovfl(tracer); | ||
289 | } | ||
290 | break; | ||
291 | } | ||
330 | } | 292 | } |
331 | 293 | ||
332 | 294 | ||
@@ -343,23 +305,25 @@ static void ds_overflow(struct task_struct *task, struct ds_context *context, | |||
343 | static inline void *ds_allocate_buffer(size_t size, unsigned int *pages) | 305 | static inline void *ds_allocate_buffer(size_t size, unsigned int *pages) |
344 | { | 306 | { |
345 | unsigned long rlim, vm, pgsz; | 307 | unsigned long rlim, vm, pgsz; |
346 | void *buffer; | 308 | void *buffer = NULL; |
347 | 309 | ||
348 | pgsz = PAGE_ALIGN(size) >> PAGE_SHIFT; | 310 | pgsz = PAGE_ALIGN(size) >> PAGE_SHIFT; |
349 | 311 | ||
312 | down_write(¤t->mm->mmap_sem); | ||
313 | |||
350 | rlim = current->signal->rlim[RLIMIT_AS].rlim_cur >> PAGE_SHIFT; | 314 | rlim = current->signal->rlim[RLIMIT_AS].rlim_cur >> PAGE_SHIFT; |
351 | vm = current->mm->total_vm + pgsz; | 315 | vm = current->mm->total_vm + pgsz; |
352 | if (rlim < vm) | 316 | if (rlim < vm) |
353 | return NULL; | 317 | goto out; |
354 | 318 | ||
355 | rlim = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur >> PAGE_SHIFT; | 319 | rlim = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur >> PAGE_SHIFT; |
356 | vm = current->mm->locked_vm + pgsz; | 320 | vm = current->mm->locked_vm + pgsz; |
357 | if (rlim < vm) | 321 | if (rlim < vm) |
358 | return NULL; | 322 | goto out; |
359 | 323 | ||
360 | buffer = kzalloc(size, GFP_KERNEL); | 324 | buffer = kzalloc(size, GFP_KERNEL); |
361 | if (!buffer) | 325 | if (!buffer) |
362 | return NULL; | 326 | goto out; |
363 | 327 | ||
364 | current->mm->total_vm += pgsz; | 328 | current->mm->total_vm += pgsz; |
365 | current->mm->locked_vm += pgsz; | 329 | current->mm->locked_vm += pgsz; |
@@ -367,290 +331,337 @@ static inline void *ds_allocate_buffer(size_t size, unsigned int *pages) | |||
367 | if (pages) | 331 | if (pages) |
368 | *pages = pgsz; | 332 | *pages = pgsz; |
369 | 333 | ||
334 | out: | ||
335 | up_write(¤t->mm->mmap_sem); | ||
370 | return buffer; | 336 | return buffer; |
371 | } | 337 | } |
372 | 338 | ||
373 | static int ds_request(struct task_struct *task, void *base, size_t size, | 339 | static void ds_install_ds_config(struct ds_context *context, |
374 | ds_ovfl_callback_t ovfl, enum ds_qualifier qual) | 340 | enum ds_qualifier qual, |
341 | void *base, size_t size, size_t ith) | ||
375 | { | 342 | { |
376 | struct ds_context *context; | ||
377 | unsigned long buffer, adj; | 343 | unsigned long buffer, adj; |
378 | const unsigned long alignment = (1 << 3); | 344 | |
345 | /* adjust the buffer address and size to meet alignment | ||
346 | * constraints: | ||
347 | * - buffer is double-word aligned | ||
348 | * - size is multiple of record size | ||
349 | * | ||
350 | * We checked the size at the very beginning; we have enough | ||
351 | * space to do the adjustment. | ||
352 | */ | ||
353 | buffer = (unsigned long)base; | ||
354 | |||
355 | adj = ALIGN(buffer, DS_ALIGNMENT) - buffer; | ||
356 | buffer += adj; | ||
357 | size -= adj; | ||
358 | |||
359 | size /= ds_cfg.sizeof_rec[qual]; | ||
360 | size *= ds_cfg.sizeof_rec[qual]; | ||
361 | |||
362 | ds_set(context->ds, qual, ds_buffer_base, buffer); | ||
363 | ds_set(context->ds, qual, ds_index, buffer); | ||
364 | ds_set(context->ds, qual, ds_absolute_maximum, buffer + size); | ||
365 | |||
366 | /* The value for 'no threshold' is -1, which will set the | ||
367 | * threshold outside of the buffer, just like we want it. | ||
368 | */ | ||
369 | ds_set(context->ds, qual, | ||
370 | ds_interrupt_threshold, buffer + size - ith); | ||
371 | } | ||
372 | |||
373 | static int ds_request(struct ds_tracer *tracer, enum ds_qualifier qual, | ||
374 | struct task_struct *task, | ||
375 | void *base, size_t size, size_t th) | ||
376 | { | ||
377 | struct ds_context *context; | ||
379 | unsigned long irq; | 378 | unsigned long irq; |
380 | int error = 0; | 379 | int error; |
381 | 380 | ||
381 | error = -EOPNOTSUPP; | ||
382 | if (!ds_cfg.sizeof_ds) | 382 | if (!ds_cfg.sizeof_ds) |
383 | return -EOPNOTSUPP; | 383 | goto out; |
384 | 384 | ||
385 | /* we require some space to do alignment adjustments below */ | 385 | /* we require some space to do alignment adjustments below */ |
386 | if (size < (alignment + ds_cfg.sizeof_rec[qual])) | 386 | error = -EINVAL; |
387 | return -EINVAL; | 387 | if (size < (DS_ALIGNMENT + ds_cfg.sizeof_rec[qual])) |
388 | goto out; | ||
388 | 389 | ||
389 | /* buffer overflow notification is not yet implemented */ | 390 | if (th != (size_t)-1) { |
390 | if (ovfl) | 391 | th *= ds_cfg.sizeof_rec[qual]; |
391 | return -EOPNOTSUPP; | 392 | |
393 | error = -EINVAL; | ||
394 | if (size <= th) | ||
395 | goto out; | ||
396 | } | ||
397 | |||
398 | error = -ENOMEM; | ||
399 | if (!base) { | ||
400 | base = ds_allocate_buffer(size, &tracer->pages); | ||
401 | if (!base) | ||
402 | goto out; | ||
403 | } | ||
392 | 404 | ||
405 | tracer->buffer = base; | ||
406 | tracer->size = size; | ||
393 | 407 | ||
394 | context = ds_alloc_context(task); | 408 | error = -ENOMEM; |
409 | context = ds_get_context(task); | ||
395 | if (!context) | 410 | if (!context) |
396 | return -ENOMEM; | 411 | goto out; |
412 | tracer->context = context; | ||
413 | |||
397 | 414 | ||
398 | spin_lock_irqsave(&ds_lock, irq); | 415 | spin_lock_irqsave(&ds_lock, irq); |
399 | 416 | ||
400 | error = -EPERM; | 417 | error = -EPERM; |
401 | if (!check_tracer(task)) | 418 | if (!check_tracer(task)) |
402 | goto out_unlock; | 419 | goto out_unlock; |
403 | |||
404 | get_tracer(task); | 420 | get_tracer(task); |
405 | 421 | ||
406 | error = -EALREADY; | ||
407 | if (context->owner[qual] == current) | ||
408 | goto out_put_tracer; | ||
409 | error = -EPERM; | 422 | error = -EPERM; |
410 | if (context->owner[qual] != NULL) | 423 | if (context->owner[qual]) |
411 | goto out_put_tracer; | 424 | goto out_put_tracer; |
412 | context->owner[qual] = current; | 425 | context->owner[qual] = tracer; |
413 | 426 | ||
414 | spin_unlock_irqrestore(&ds_lock, irq); | 427 | spin_unlock_irqrestore(&ds_lock, irq); |
415 | 428 | ||
416 | 429 | ||
417 | error = -ENOMEM; | 430 | ds_install_ds_config(context, qual, base, size, th); |
418 | if (!base) { | ||
419 | base = ds_allocate_buffer(size, &context->pages[qual]); | ||
420 | if (!base) | ||
421 | goto out_release; | ||
422 | |||
423 | context->buffer[qual] = base; | ||
424 | } | ||
425 | error = 0; | ||
426 | 431 | ||
427 | context->callback[qual] = ovfl; | 432 | return 0; |
428 | |||
429 | /* adjust the buffer address and size to meet alignment | ||
430 | * constraints: | ||
431 | * - buffer is double-word aligned | ||
432 | * - size is multiple of record size | ||
433 | * | ||
434 | * We checked the size at the very beginning; we have enough | ||
435 | * space to do the adjustment. | ||
436 | */ | ||
437 | buffer = (unsigned long)base; | ||
438 | |||
439 | adj = ALIGN(buffer, alignment) - buffer; | ||
440 | buffer += adj; | ||
441 | size -= adj; | ||
442 | |||
443 | size /= ds_cfg.sizeof_rec[qual]; | ||
444 | size *= ds_cfg.sizeof_rec[qual]; | ||
445 | |||
446 | ds_set(context->ds, qual, ds_buffer_base, buffer); | ||
447 | ds_set(context->ds, qual, ds_index, buffer); | ||
448 | ds_set(context->ds, qual, ds_absolute_maximum, buffer + size); | ||
449 | |||
450 | if (ovfl) { | ||
451 | /* todo: select a suitable interrupt threshold */ | ||
452 | } else | ||
453 | ds_set(context->ds, qual, | ||
454 | ds_interrupt_threshold, buffer + size + 1); | ||
455 | |||
456 | /* we keep the context until ds_release */ | ||
457 | return error; | ||
458 | |||
459 | out_release: | ||
460 | context->owner[qual] = NULL; | ||
461 | ds_put_context(context); | ||
462 | put_tracer(task); | ||
463 | return error; | ||
464 | 433 | ||
465 | out_put_tracer: | 434 | out_put_tracer: |
466 | spin_unlock_irqrestore(&ds_lock, irq); | ||
467 | ds_put_context(context); | ||
468 | put_tracer(task); | 435 | put_tracer(task); |
469 | return error; | ||
470 | |||
471 | out_unlock: | 436 | out_unlock: |
472 | spin_unlock_irqrestore(&ds_lock, irq); | 437 | spin_unlock_irqrestore(&ds_lock, irq); |
473 | ds_put_context(context); | 438 | ds_put_context(context); |
439 | tracer->context = NULL; | ||
440 | out: | ||
474 | return error; | 441 | return error; |
475 | } | 442 | } |
476 | 443 | ||
477 | int ds_request_bts(struct task_struct *task, void *base, size_t size, | 444 | struct bts_tracer *ds_request_bts(struct task_struct *task, |
478 | ds_ovfl_callback_t ovfl) | 445 | void *base, size_t size, |
446 | bts_ovfl_callback_t ovfl, size_t th) | ||
479 | { | 447 | { |
480 | return ds_request(task, base, size, ovfl, ds_bts); | 448 | struct bts_tracer *tracer; |
481 | } | 449 | int error; |
482 | 450 | ||
483 | int ds_request_pebs(struct task_struct *task, void *base, size_t size, | 451 | /* buffer overflow notification is not yet implemented */ |
484 | ds_ovfl_callback_t ovfl) | 452 | error = -EOPNOTSUPP; |
485 | { | 453 | if (ovfl) |
486 | return ds_request(task, base, size, ovfl, ds_pebs); | 454 | goto out; |
455 | |||
456 | error = -ENOMEM; | ||
457 | tracer = kzalloc(sizeof(*tracer), GFP_KERNEL); | ||
458 | if (!tracer) | ||
459 | goto out; | ||
460 | tracer->ovfl = ovfl; | ||
461 | |||
462 | error = ds_request(&tracer->ds, ds_bts, task, base, size, th); | ||
463 | if (error < 0) | ||
464 | goto out_tracer; | ||
465 | |||
466 | return tracer; | ||
467 | |||
468 | out_tracer: | ||
469 | (void)ds_release_bts(tracer); | ||
470 | out: | ||
471 | return ERR_PTR(error); | ||
487 | } | 472 | } |
488 | 473 | ||
489 | static int ds_release(struct task_struct *task, enum ds_qualifier qual) | 474 | struct pebs_tracer *ds_request_pebs(struct task_struct *task, |
475 | void *base, size_t size, | ||
476 | pebs_ovfl_callback_t ovfl, size_t th) | ||
490 | { | 477 | { |
491 | struct ds_context *context; | 478 | struct pebs_tracer *tracer; |
492 | int error; | 479 | int error; |
493 | 480 | ||
494 | context = ds_get_context(task); | 481 | /* buffer overflow notification is not yet implemented */ |
495 | error = ds_validate_access(context, qual); | 482 | error = -EOPNOTSUPP; |
496 | if (error < 0) | 483 | if (ovfl) |
497 | goto out; | 484 | goto out; |
498 | 485 | ||
499 | kfree(context->buffer[qual]); | 486 | error = -ENOMEM; |
500 | context->buffer[qual] = NULL; | 487 | tracer = kzalloc(sizeof(*tracer), GFP_KERNEL); |
488 | if (!tracer) | ||
489 | goto out; | ||
490 | tracer->ovfl = ovfl; | ||
501 | 491 | ||
502 | current->mm->total_vm -= context->pages[qual]; | 492 | error = ds_request(&tracer->ds, ds_pebs, task, base, size, th); |
503 | current->mm->locked_vm -= context->pages[qual]; | 493 | if (error < 0) |
504 | context->pages[qual] = 0; | 494 | goto out_tracer; |
505 | context->owner[qual] = NULL; | ||
506 | 495 | ||
507 | /* | 496 | return tracer; |
508 | * we put the context twice: | 497 | |
509 | * once for the ds_get_context | 498 | out_tracer: |
510 | * once for the corresponding ds_request | 499 | (void)ds_release_pebs(tracer); |
511 | */ | ||
512 | ds_put_context(context); | ||
513 | out: | 500 | out: |
514 | ds_put_context(context); | 501 | return ERR_PTR(error); |
515 | return error; | 502 | } |
503 | |||
504 | static void ds_release(struct ds_tracer *tracer, enum ds_qualifier qual) | ||
505 | { | ||
506 | if (tracer->context) { | ||
507 | BUG_ON(tracer->context->owner[qual] != tracer); | ||
508 | tracer->context->owner[qual] = NULL; | ||
509 | |||
510 | put_tracer(tracer->context->task); | ||
511 | ds_put_context(tracer->context); | ||
512 | } | ||
513 | |||
514 | if (tracer->pages) { | ||
515 | kfree(tracer->buffer); | ||
516 | |||
517 | down_write(¤t->mm->mmap_sem); | ||
518 | |||
519 | current->mm->total_vm -= tracer->pages; | ||
520 | current->mm->locked_vm -= tracer->pages; | ||
521 | |||
522 | up_write(¤t->mm->mmap_sem); | ||
523 | } | ||
516 | } | 524 | } |
517 | 525 | ||
518 | int ds_release_bts(struct task_struct *task) | 526 | int ds_release_bts(struct bts_tracer *tracer) |
519 | { | 527 | { |
520 | return ds_release(task, ds_bts); | 528 | if (!tracer) |
529 | return -EINVAL; | ||
530 | |||
531 | ds_release(&tracer->ds, ds_bts); | ||
532 | kfree(tracer); | ||
533 | |||
534 | return 0; | ||
521 | } | 535 | } |
522 | 536 | ||
523 | int ds_release_pebs(struct task_struct *task) | 537 | int ds_release_pebs(struct pebs_tracer *tracer) |
524 | { | 538 | { |
525 | return ds_release(task, ds_pebs); | 539 | if (!tracer) |
540 | return -EINVAL; | ||
541 | |||
542 | ds_release(&tracer->ds, ds_pebs); | ||
543 | kfree(tracer); | ||
544 | |||
545 | return 0; | ||
526 | } | 546 | } |
527 | 547 | ||
528 | static int ds_get_index(struct task_struct *task, size_t *pos, | 548 | static size_t ds_get_index(struct ds_context *context, enum ds_qualifier qual) |
529 | enum ds_qualifier qual) | ||
530 | { | 549 | { |
531 | struct ds_context *context; | ||
532 | unsigned long base, index; | 550 | unsigned long base, index; |
533 | int error; | ||
534 | |||
535 | context = ds_get_context(task); | ||
536 | error = ds_validate_access(context, qual); | ||
537 | if (error < 0) | ||
538 | goto out; | ||
539 | 551 | ||
540 | base = ds_get(context->ds, qual, ds_buffer_base); | 552 | base = ds_get(context->ds, qual, ds_buffer_base); |
541 | index = ds_get(context->ds, qual, ds_index); | 553 | index = ds_get(context->ds, qual, ds_index); |
542 | 554 | ||
543 | error = ((index - base) / ds_cfg.sizeof_rec[qual]); | 555 | return (index - base) / ds_cfg.sizeof_rec[qual]; |
544 | if (pos) | ||
545 | *pos = error; | ||
546 | out: | ||
547 | ds_put_context(context); | ||
548 | return error; | ||
549 | } | 556 | } |
550 | 557 | ||
551 | int ds_get_bts_index(struct task_struct *task, size_t *pos) | 558 | int ds_get_bts_index(struct bts_tracer *tracer, size_t *pos) |
552 | { | 559 | { |
553 | return ds_get_index(task, pos, ds_bts); | 560 | if (!tracer) |
561 | return -EINVAL; | ||
562 | |||
563 | if (!pos) | ||
564 | return -EINVAL; | ||
565 | |||
566 | *pos = ds_get_index(tracer->ds.context, ds_bts); | ||
567 | |||
568 | return 0; | ||
554 | } | 569 | } |
555 | 570 | ||
556 | int ds_get_pebs_index(struct task_struct *task, size_t *pos) | 571 | int ds_get_pebs_index(struct pebs_tracer *tracer, size_t *pos) |
557 | { | 572 | { |
558 | return ds_get_index(task, pos, ds_pebs); | 573 | if (!tracer) |
574 | return -EINVAL; | ||
575 | |||
576 | if (!pos) | ||
577 | return -EINVAL; | ||
578 | |||
579 | *pos = ds_get_index(tracer->ds.context, ds_pebs); | ||
580 | |||
581 | return 0; | ||
559 | } | 582 | } |
560 | 583 | ||
561 | static int ds_get_end(struct task_struct *task, size_t *pos, | 584 | static size_t ds_get_end(struct ds_context *context, enum ds_qualifier qual) |
562 | enum ds_qualifier qual) | ||
563 | { | 585 | { |
564 | struct ds_context *context; | 586 | unsigned long base, max; |
565 | unsigned long base, end; | ||
566 | int error; | ||
567 | |||
568 | context = ds_get_context(task); | ||
569 | error = ds_validate_access(context, qual); | ||
570 | if (error < 0) | ||
571 | goto out; | ||
572 | 587 | ||
573 | base = ds_get(context->ds, qual, ds_buffer_base); | 588 | base = ds_get(context->ds, qual, ds_buffer_base); |
574 | end = ds_get(context->ds, qual, ds_absolute_maximum); | 589 | max = ds_get(context->ds, qual, ds_absolute_maximum); |
575 | 590 | ||
576 | error = ((end - base) / ds_cfg.sizeof_rec[qual]); | 591 | return (max - base) / ds_cfg.sizeof_rec[qual]; |
577 | if (pos) | ||
578 | *pos = error; | ||
579 | out: | ||
580 | ds_put_context(context); | ||
581 | return error; | ||
582 | } | 592 | } |
583 | 593 | ||
584 | int ds_get_bts_end(struct task_struct *task, size_t *pos) | 594 | int ds_get_bts_end(struct bts_tracer *tracer, size_t *pos) |
585 | { | 595 | { |
586 | return ds_get_end(task, pos, ds_bts); | 596 | if (!tracer) |
597 | return -EINVAL; | ||
598 | |||
599 | if (!pos) | ||
600 | return -EINVAL; | ||
601 | |||
602 | *pos = ds_get_end(tracer->ds.context, ds_bts); | ||
603 | |||
604 | return 0; | ||
587 | } | 605 | } |
588 | 606 | ||
589 | int ds_get_pebs_end(struct task_struct *task, size_t *pos) | 607 | int ds_get_pebs_end(struct pebs_tracer *tracer, size_t *pos) |
590 | { | 608 | { |
591 | return ds_get_end(task, pos, ds_pebs); | 609 | if (!tracer) |
610 | return -EINVAL; | ||
611 | |||
612 | if (!pos) | ||
613 | return -EINVAL; | ||
614 | |||
615 | *pos = ds_get_end(tracer->ds.context, ds_pebs); | ||
616 | |||
617 | return 0; | ||
592 | } | 618 | } |
593 | 619 | ||
594 | static int ds_access(struct task_struct *task, size_t index, | 620 | static int ds_access(struct ds_context *context, enum ds_qualifier qual, |
595 | const void **record, enum ds_qualifier qual) | 621 | size_t index, const void **record) |
596 | { | 622 | { |
597 | struct ds_context *context; | ||
598 | unsigned long base, idx; | 623 | unsigned long base, idx; |
599 | int error; | ||
600 | 624 | ||
601 | if (!record) | 625 | if (!record) |
602 | return -EINVAL; | 626 | return -EINVAL; |
603 | 627 | ||
604 | context = ds_get_context(task); | ||
605 | error = ds_validate_access(context, qual); | ||
606 | if (error < 0) | ||
607 | goto out; | ||
608 | |||
609 | base = ds_get(context->ds, qual, ds_buffer_base); | 628 | base = ds_get(context->ds, qual, ds_buffer_base); |
610 | idx = base + (index * ds_cfg.sizeof_rec[qual]); | 629 | idx = base + (index * ds_cfg.sizeof_rec[qual]); |
611 | 630 | ||
612 | error = -EINVAL; | ||
613 | if (idx > ds_get(context->ds, qual, ds_absolute_maximum)) | 631 | if (idx > ds_get(context->ds, qual, ds_absolute_maximum)) |
614 | goto out; | 632 | return -EINVAL; |
615 | 633 | ||
616 | *record = (const void *)idx; | 634 | *record = (const void *)idx; |
617 | error = ds_cfg.sizeof_rec[qual]; | 635 | |
618 | out: | 636 | return ds_cfg.sizeof_rec[qual]; |
619 | ds_put_context(context); | ||
620 | return error; | ||
621 | } | 637 | } |
622 | 638 | ||
623 | int ds_access_bts(struct task_struct *task, size_t index, const void **record) | 639 | int ds_access_bts(struct bts_tracer *tracer, size_t index, |
640 | const void **record) | ||
624 | { | 641 | { |
625 | return ds_access(task, index, record, ds_bts); | 642 | if (!tracer) |
643 | return -EINVAL; | ||
644 | |||
645 | return ds_access(tracer->ds.context, ds_bts, index, record); | ||
626 | } | 646 | } |
627 | 647 | ||
628 | int ds_access_pebs(struct task_struct *task, size_t index, const void **record) | 648 | int ds_access_pebs(struct pebs_tracer *tracer, size_t index, |
649 | const void **record) | ||
629 | { | 650 | { |
630 | return ds_access(task, index, record, ds_pebs); | 651 | if (!tracer) |
652 | return -EINVAL; | ||
653 | |||
654 | return ds_access(tracer->ds.context, ds_pebs, index, record); | ||
631 | } | 655 | } |
632 | 656 | ||
633 | static int ds_write(struct task_struct *task, const void *record, size_t size, | 657 | static int ds_write(struct ds_context *context, enum ds_qualifier qual, |
634 | enum ds_qualifier qual, int force) | 658 | const void *record, size_t size) |
635 | { | 659 | { |
636 | struct ds_context *context; | 660 | int bytes_written = 0; |
637 | int error; | ||
638 | 661 | ||
639 | if (!record) | 662 | if (!record) |
640 | return -EINVAL; | 663 | return -EINVAL; |
641 | 664 | ||
642 | error = -EPERM; | ||
643 | context = ds_get_context(task); | ||
644 | if (!context) | ||
645 | goto out; | ||
646 | |||
647 | if (!force) { | ||
648 | error = ds_validate_access(context, qual); | ||
649 | if (error < 0) | ||
650 | goto out; | ||
651 | } | ||
652 | |||
653 | error = 0; | ||
654 | while (size) { | 665 | while (size) { |
655 | unsigned long base, index, end, write_end, int_th; | 666 | unsigned long base, index, end, write_end, int_th; |
656 | unsigned long write_size, adj_write_size; | 667 | unsigned long write_size, adj_write_size; |
@@ -678,14 +689,14 @@ static int ds_write(struct task_struct *task, const void *record, size_t size, | |||
678 | write_end = end; | 689 | write_end = end; |
679 | 690 | ||
680 | if (write_end <= index) | 691 | if (write_end <= index) |
681 | goto out; | 692 | break; |
682 | 693 | ||
683 | write_size = min((unsigned long) size, write_end - index); | 694 | write_size = min((unsigned long) size, write_end - index); |
684 | memcpy((void *)index, record, write_size); | 695 | memcpy((void *)index, record, write_size); |
685 | 696 | ||
686 | record = (const char *)record + write_size; | 697 | record = (const char *)record + write_size; |
687 | size -= write_size; | 698 | size -= write_size; |
688 | error += write_size; | 699 | bytes_written += write_size; |
689 | 700 | ||
690 | adj_write_size = write_size / ds_cfg.sizeof_rec[qual]; | 701 | adj_write_size = write_size / ds_cfg.sizeof_rec[qual]; |
691 | adj_write_size *= ds_cfg.sizeof_rec[qual]; | 702 | adj_write_size *= ds_cfg.sizeof_rec[qual]; |
@@ -700,47 +711,32 @@ static int ds_write(struct task_struct *task, const void *record, size_t size, | |||
700 | ds_set(context->ds, qual, ds_index, index); | 711 | ds_set(context->ds, qual, ds_index, index); |
701 | 712 | ||
702 | if (index >= int_th) | 713 | if (index >= int_th) |
703 | ds_overflow(task, context, qual); | 714 | ds_overflow(context, qual); |
704 | } | 715 | } |
705 | 716 | ||
706 | out: | 717 | return bytes_written; |
707 | ds_put_context(context); | ||
708 | return error; | ||
709 | } | 718 | } |
710 | 719 | ||
711 | int ds_write_bts(struct task_struct *task, const void *record, size_t size) | 720 | int ds_write_bts(struct bts_tracer *tracer, const void *record, size_t size) |
712 | { | 721 | { |
713 | return ds_write(task, record, size, ds_bts, /* force = */ 0); | 722 | if (!tracer) |
714 | } | 723 | return -EINVAL; |
715 | 724 | ||
716 | int ds_write_pebs(struct task_struct *task, const void *record, size_t size) | 725 | return ds_write(tracer->ds.context, ds_bts, record, size); |
717 | { | ||
718 | return ds_write(task, record, size, ds_pebs, /* force = */ 0); | ||
719 | } | 726 | } |
720 | 727 | ||
721 | int ds_unchecked_write_bts(struct task_struct *task, | 728 | int ds_write_pebs(struct pebs_tracer *tracer, const void *record, size_t size) |
722 | const void *record, size_t size) | ||
723 | { | 729 | { |
724 | return ds_write(task, record, size, ds_bts, /* force = */ 1); | 730 | if (!tracer) |
725 | } | 731 | return -EINVAL; |
726 | 732 | ||
727 | int ds_unchecked_write_pebs(struct task_struct *task, | 733 | return ds_write(tracer->ds.context, ds_pebs, record, size); |
728 | const void *record, size_t size) | ||
729 | { | ||
730 | return ds_write(task, record, size, ds_pebs, /* force = */ 1); | ||
731 | } | 734 | } |
732 | 735 | ||
733 | static int ds_reset_or_clear(struct task_struct *task, | 736 | static void ds_reset_or_clear(struct ds_context *context, |
734 | enum ds_qualifier qual, int clear) | 737 | enum ds_qualifier qual, int clear) |
735 | { | 738 | { |
736 | struct ds_context *context; | ||
737 | unsigned long base, end; | 739 | unsigned long base, end; |
738 | int error; | ||
739 | |||
740 | context = ds_get_context(task); | ||
741 | error = ds_validate_access(context, qual); | ||
742 | if (error < 0) | ||
743 | goto out; | ||
744 | 740 | ||
745 | base = ds_get(context->ds, qual, ds_buffer_base); | 741 | base = ds_get(context->ds, qual, ds_buffer_base); |
746 | end = ds_get(context->ds, qual, ds_absolute_maximum); | 742 | end = ds_get(context->ds, qual, ds_absolute_maximum); |
@@ -749,70 +745,69 @@ static int ds_reset_or_clear(struct task_struct *task, | |||
749 | memset((void *)base, 0, end - base); | 745 | memset((void *)base, 0, end - base); |
750 | 746 | ||
751 | ds_set(context->ds, qual, ds_index, base); | 747 | ds_set(context->ds, qual, ds_index, base); |
752 | |||
753 | error = 0; | ||
754 | out: | ||
755 | ds_put_context(context); | ||
756 | return error; | ||
757 | } | 748 | } |
758 | 749 | ||
759 | int ds_reset_bts(struct task_struct *task) | 750 | int ds_reset_bts(struct bts_tracer *tracer) |
760 | { | 751 | { |
761 | return ds_reset_or_clear(task, ds_bts, /* clear = */ 0); | 752 | if (!tracer) |
753 | return -EINVAL; | ||
754 | |||
755 | ds_reset_or_clear(tracer->ds.context, ds_bts, /* clear = */ 0); | ||
756 | |||
757 | return 0; | ||
762 | } | 758 | } |
763 | 759 | ||
764 | int ds_reset_pebs(struct task_struct *task) | 760 | int ds_reset_pebs(struct pebs_tracer *tracer) |
765 | { | 761 | { |
766 | return ds_reset_or_clear(task, ds_pebs, /* clear = */ 0); | 762 | if (!tracer) |
763 | return -EINVAL; | ||
764 | |||
765 | ds_reset_or_clear(tracer->ds.context, ds_pebs, /* clear = */ 0); | ||
766 | |||
767 | return 0; | ||
767 | } | 768 | } |
768 | 769 | ||
769 | int ds_clear_bts(struct task_struct *task) | 770 | int ds_clear_bts(struct bts_tracer *tracer) |
770 | { | 771 | { |
771 | return ds_reset_or_clear(task, ds_bts, /* clear = */ 1); | 772 | if (!tracer) |
773 | return -EINVAL; | ||
774 | |||
775 | ds_reset_or_clear(tracer->ds.context, ds_bts, /* clear = */ 1); | ||
776 | |||
777 | return 0; | ||
772 | } | 778 | } |
773 | 779 | ||
774 | int ds_clear_pebs(struct task_struct *task) | 780 | int ds_clear_pebs(struct pebs_tracer *tracer) |
775 | { | 781 | { |
776 | return ds_reset_or_clear(task, ds_pebs, /* clear = */ 1); | 782 | if (!tracer) |
783 | return -EINVAL; | ||
784 | |||
785 | ds_reset_or_clear(tracer->ds.context, ds_pebs, /* clear = */ 1); | ||
786 | |||
787 | return 0; | ||
777 | } | 788 | } |
778 | 789 | ||
779 | int ds_get_pebs_reset(struct task_struct *task, u64 *value) | 790 | int ds_get_pebs_reset(struct pebs_tracer *tracer, u64 *value) |
780 | { | 791 | { |
781 | struct ds_context *context; | 792 | if (!tracer) |
782 | int error; | 793 | return -EINVAL; |
783 | 794 | ||
784 | if (!value) | 795 | if (!value) |
785 | return -EINVAL; | 796 | return -EINVAL; |
786 | 797 | ||
787 | context = ds_get_context(task); | 798 | *value = *(u64 *)(tracer->ds.context->ds + (ds_cfg.sizeof_field * 8)); |
788 | error = ds_validate_access(context, ds_pebs); | ||
789 | if (error < 0) | ||
790 | goto out; | ||
791 | |||
792 | *value = *(u64 *)(context->ds + (ds_cfg.sizeof_field * 8)); | ||
793 | 799 | ||
794 | error = 0; | 800 | return 0; |
795 | out: | ||
796 | ds_put_context(context); | ||
797 | return error; | ||
798 | } | 801 | } |
799 | 802 | ||
800 | int ds_set_pebs_reset(struct task_struct *task, u64 value) | 803 | int ds_set_pebs_reset(struct pebs_tracer *tracer, u64 value) |
801 | { | 804 | { |
802 | struct ds_context *context; | 805 | if (!tracer) |
803 | int error; | 806 | return -EINVAL; |
804 | |||
805 | context = ds_get_context(task); | ||
806 | error = ds_validate_access(context, ds_pebs); | ||
807 | if (error < 0) | ||
808 | goto out; | ||
809 | 807 | ||
810 | *(u64 *)(context->ds + (ds_cfg.sizeof_field * 8)) = value; | 808 | *(u64 *)(tracer->ds.context->ds + (ds_cfg.sizeof_field * 8)) = value; |
811 | 809 | ||
812 | error = 0; | 810 | return 0; |
813 | out: | ||
814 | ds_put_context(context); | ||
815 | return error; | ||
816 | } | 811 | } |
817 | 812 | ||
818 | static const struct ds_configuration ds_cfg_var = { | 813 | static const struct ds_configuration ds_cfg_var = { |
@@ -840,6 +835,10 @@ static inline void | |||
840 | ds_configure(const struct ds_configuration *cfg) | 835 | ds_configure(const struct ds_configuration *cfg) |
841 | { | 836 | { |
842 | ds_cfg = *cfg; | 837 | ds_cfg = *cfg; |
838 | |||
839 | printk(KERN_INFO "DS available\n"); | ||
840 | |||
841 | BUG_ON(MAX_SIZEOF_DS < ds_cfg.sizeof_ds); | ||
843 | } | 842 | } |
844 | 843 | ||
845 | void __cpuinit ds_init_intel(struct cpuinfo_x86 *c) | 844 | void __cpuinit ds_init_intel(struct cpuinfo_x86 *c) |
@@ -883,6 +882,8 @@ void ds_free(struct ds_context *context) | |||
883 | * is dying. There should not be any user of that context left | 882 | * is dying. There should not be any user of that context left |
884 | * to disturb us, anymore. */ | 883 | * to disturb us, anymore. */ |
885 | unsigned long leftovers = context->count; | 884 | unsigned long leftovers = context->count; |
886 | while (leftovers--) | 885 | while (leftovers--) { |
886 | put_tracer(context->task); | ||
887 | ds_put_context(context); | 887 | ds_put_context(context); |
888 | } | ||
888 | } | 889 | } |