diff options
author | Peter Zijlstra <a.p.zijlstra@chello.nl> | 2009-03-25 07:30:22 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-04-06 03:30:32 -0400 |
commit | b9cacc7bf193df16532bfa7d7ca77fe50fc3c2e6 (patch) | |
tree | 4a8e4df2506876fe28e18c1e7b6e3ff21d0a9c6a /kernel/perf_counter.c | |
parent | c7138f37f905bb7987b1f9f5a8ee73667db39f25 (diff) |
perf_counter: more elaborate write API
Provide a begin, copy, end interface to the output buffer.
begin() reserves the space,
copy() copies the data over, considering page boundaries,
end() finalizes the event and does the wakeup.
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Mike Galbraith <efault@gmx.de>
Cc: Arjan van de Ven <arjan@infradead.org>
Cc: Wu Fengguang <fengguang.wu@intel.com>
Orig-LKML-Reference: <20090325113316.740550870@chello.nl>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'kernel/perf_counter.c')
-rw-r--r-- | kernel/perf_counter.c | 109 |
1 files changed, 75 insertions, 34 deletions
diff --git a/kernel/perf_counter.c b/kernel/perf_counter.c index affe227d56a0..0422fd9bf627 100644 --- a/kernel/perf_counter.c +++ b/kernel/perf_counter.c | |||
@@ -9,6 +9,7 @@ | |||
9 | */ | 9 | */ |
10 | 10 | ||
11 | #include <linux/fs.h> | 11 | #include <linux/fs.h> |
12 | #include <linux/mm.h> | ||
12 | #include <linux/cpu.h> | 13 | #include <linux/cpu.h> |
13 | #include <linux/smp.h> | 14 | #include <linux/smp.h> |
14 | #include <linux/file.h> | 15 | #include <linux/file.h> |
@@ -16,15 +17,14 @@ | |||
16 | #include <linux/sysfs.h> | 17 | #include <linux/sysfs.h> |
17 | #include <linux/ptrace.h> | 18 | #include <linux/ptrace.h> |
18 | #include <linux/percpu.h> | 19 | #include <linux/percpu.h> |
20 | #include <linux/vmstat.h> | ||
21 | #include <linux/hardirq.h> | ||
22 | #include <linux/rculist.h> | ||
19 | #include <linux/uaccess.h> | 23 | #include <linux/uaccess.h> |
20 | #include <linux/syscalls.h> | 24 | #include <linux/syscalls.h> |
21 | #include <linux/anon_inodes.h> | 25 | #include <linux/anon_inodes.h> |
22 | #include <linux/kernel_stat.h> | 26 | #include <linux/kernel_stat.h> |
23 | #include <linux/perf_counter.h> | 27 | #include <linux/perf_counter.h> |
24 | #include <linux/mm.h> | ||
25 | #include <linux/vmstat.h> | ||
26 | #include <linux/rculist.h> | ||
27 | #include <linux/hardirq.h> | ||
28 | 28 | ||
29 | #include <asm/irq_regs.h> | 29 | #include <asm/irq_regs.h> |
30 | 30 | ||
@@ -1411,16 +1411,20 @@ static const struct file_operations perf_fops = { | |||
1411 | * Output | 1411 | * Output |
1412 | */ | 1412 | */ |
1413 | 1413 | ||
1414 | static int perf_output_write(struct perf_counter *counter, int nmi, | 1414 | struct perf_output_handle { |
1415 | void *buf, ssize_t size) | 1415 | struct perf_counter *counter; |
1416 | struct perf_mmap_data *data; | ||
1417 | unsigned int offset; | ||
1418 | int wakeup; | ||
1419 | }; | ||
1420 | |||
1421 | static int perf_output_begin(struct perf_output_handle *handle, | ||
1422 | struct perf_counter *counter, unsigned int size) | ||
1416 | { | 1423 | { |
1417 | struct perf_mmap_data *data; | 1424 | struct perf_mmap_data *data; |
1418 | unsigned int offset, head, nr; | 1425 | unsigned int offset, head; |
1419 | unsigned int len; | ||
1420 | int ret, wakeup; | ||
1421 | 1426 | ||
1422 | rcu_read_lock(); | 1427 | rcu_read_lock(); |
1423 | ret = -ENOSPC; | ||
1424 | data = rcu_dereference(counter->data); | 1428 | data = rcu_dereference(counter->data); |
1425 | if (!data) | 1429 | if (!data) |
1426 | goto out; | 1430 | goto out; |
@@ -1428,45 +1432,82 @@ static int perf_output_write(struct perf_counter *counter, int nmi, | |||
1428 | if (!data->nr_pages) | 1432 | if (!data->nr_pages) |
1429 | goto out; | 1433 | goto out; |
1430 | 1434 | ||
1431 | ret = -EINVAL; | ||
1432 | if (size > PAGE_SIZE) | ||
1433 | goto out; | ||
1434 | |||
1435 | do { | 1435 | do { |
1436 | offset = head = atomic_read(&data->head); | 1436 | offset = head = atomic_read(&data->head); |
1437 | head += size; | 1437 | head += size; |
1438 | } while (atomic_cmpxchg(&data->head, offset, head) != offset); | 1438 | } while (atomic_cmpxchg(&data->head, offset, head) != offset); |
1439 | 1439 | ||
1440 | wakeup = (offset >> PAGE_SHIFT) != (head >> PAGE_SHIFT); | 1440 | handle->counter = counter; |
1441 | handle->data = data; | ||
1442 | handle->offset = offset; | ||
1443 | handle->wakeup = (offset >> PAGE_SHIFT) != (head >> PAGE_SHIFT); | ||
1441 | 1444 | ||
1442 | nr = (offset >> PAGE_SHIFT) & (data->nr_pages - 1); | 1445 | return 0; |
1443 | offset &= PAGE_SIZE - 1; | ||
1444 | 1446 | ||
1445 | len = min_t(unsigned int, PAGE_SIZE - offset, size); | 1447 | out: |
1446 | memcpy(data->data_pages[nr] + offset, buf, len); | 1448 | rcu_read_unlock(); |
1447 | size -= len; | ||
1448 | 1449 | ||
1449 | if (size) { | 1450 | return -ENOSPC; |
1450 | nr = (nr + 1) & (data->nr_pages - 1); | 1451 | } |
1451 | memcpy(data->data_pages[nr], buf + len, size); | ||
1452 | } | ||
1453 | 1452 | ||
1454 | /* | 1453 | static void perf_output_copy(struct perf_output_handle *handle, |
1455 | * generate a poll() wakeup for every page boundary crossed | 1454 | void *buf, unsigned int len) |
1456 | */ | 1455 | { |
1457 | if (wakeup) { | 1456 | unsigned int pages_mask; |
1458 | atomic_xchg(&data->wakeup, POLL_IN); | 1457 | unsigned int offset; |
1459 | __perf_counter_update_userpage(counter, data); | 1458 | unsigned int size; |
1459 | void **pages; | ||
1460 | |||
1461 | offset = handle->offset; | ||
1462 | pages_mask = handle->data->nr_pages - 1; | ||
1463 | pages = handle->data->data_pages; | ||
1464 | |||
1465 | do { | ||
1466 | unsigned int page_offset; | ||
1467 | int nr; | ||
1468 | |||
1469 | nr = (offset >> PAGE_SHIFT) & pages_mask; | ||
1470 | page_offset = offset & (PAGE_SIZE - 1); | ||
1471 | size = min_t(unsigned int, PAGE_SIZE - page_offset, len); | ||
1472 | |||
1473 | memcpy(pages[nr] + page_offset, buf, size); | ||
1474 | |||
1475 | len -= size; | ||
1476 | buf += size; | ||
1477 | offset += size; | ||
1478 | } while (len); | ||
1479 | |||
1480 | handle->offset = offset; | ||
1481 | } | ||
1482 | |||
1483 | static void perf_output_end(struct perf_output_handle *handle, int nmi) | ||
1484 | { | ||
1485 | if (handle->wakeup) { | ||
1486 | (void)atomic_xchg(&handle->data->wakeup, POLL_IN); | ||
1487 | __perf_counter_update_userpage(handle->counter, handle->data); | ||
1460 | if (nmi) { | 1488 | if (nmi) { |
1461 | counter->wakeup_pending = 1; | 1489 | handle->counter->wakeup_pending = 1; |
1462 | set_perf_counter_pending(); | 1490 | set_perf_counter_pending(); |
1463 | } else | 1491 | } else |
1464 | wake_up(&counter->waitq); | 1492 | wake_up(&handle->counter->waitq); |
1465 | } | 1493 | } |
1466 | ret = 0; | ||
1467 | out: | ||
1468 | rcu_read_unlock(); | 1494 | rcu_read_unlock(); |
1495 | } | ||
1496 | |||
1497 | static int perf_output_write(struct perf_counter *counter, int nmi, | ||
1498 | void *buf, ssize_t size) | ||
1499 | { | ||
1500 | struct perf_output_handle handle; | ||
1501 | int ret; | ||
1469 | 1502 | ||
1503 | ret = perf_output_begin(&handle, counter, size); | ||
1504 | if (ret) | ||
1505 | goto out; | ||
1506 | |||
1507 | perf_output_copy(&handle, buf, size); | ||
1508 | perf_output_end(&handle, nmi); | ||
1509 | |||
1510 | out: | ||
1470 | return ret; | 1511 | return ret; |
1471 | } | 1512 | } |
1472 | 1513 | ||