aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Zijlstra <a.p.zijlstra@chello.nl>2009-03-30 13:07:03 -0400
committerIngo Molnar <mingo@elte.hu>2009-04-06 03:30:37 -0400
commit38ff667b321b00f5e6830e93fb4ab11a653a2920 (patch)
tree37c24148228d978824a014899f4984072da4e077
parent925d519ab82b6dd7aca9420d809ee83819c08db2 (diff)
perf_counter: fix update_userpage()
It just occured to me it is possible to have multiple contending updates of the userpage (mmap information vs overflow vs counter). This would break the seqlock logic. It appear the arch code uses this from NMI context, so we cannot possibly serialize its use, therefore separate the data_head update from it and let it return to its original use. The arch code needs to make sure there are no contending callers by disabling the counter before using it -- powerpc appears to do this nicely. Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl> Acked-by: Paul Mackerras <paulus@samba.org> Orig-LKML-Reference: <20090330171023.241410660@chello.nl> Signed-off-by: Ingo Molnar <mingo@elte.hu>
-rw-r--r--include/linux/perf_counter.h35
-rw-r--r--kernel/perf_counter.c38
2 files changed, 58 insertions, 15 deletions
diff --git a/include/linux/perf_counter.h b/include/linux/perf_counter.h
index 0d833228eee5..8ac18852dcfe 100644
--- a/include/linux/perf_counter.h
+++ b/include/linux/perf_counter.h
@@ -160,10 +160,45 @@ struct perf_counter_hw_event {
160struct perf_counter_mmap_page { 160struct perf_counter_mmap_page {
161 __u32 version; /* version number of this structure */ 161 __u32 version; /* version number of this structure */
162 __u32 compat_version; /* lowest version this is compat with */ 162 __u32 compat_version; /* lowest version this is compat with */
163
164 /*
165 * Bits needed to read the hw counters in user-space.
166 *
167 * The index and offset should be read atomically using the seqlock:
168 *
169 * __u32 seq, index;
170 * __s64 offset;
171 *
172 * again:
173 * rmb();
174 * seq = pc->lock;
175 *
176 * if (unlikely(seq & 1)) {
177 * cpu_relax();
178 * goto again;
179 * }
180 *
181 * index = pc->index;
182 * offset = pc->offset;
183 *
184 * rmb();
185 * if (pc->lock != seq)
186 * goto again;
187 *
188 * After this, index contains architecture specific counter index + 1,
189 * so that 0 means unavailable, offset contains the value to be added
190 * to the result of the raw timer read to obtain this counter's value.
191 */
163 __u32 lock; /* seqlock for synchronization */ 192 __u32 lock; /* seqlock for synchronization */
164 __u32 index; /* hardware counter identifier */ 193 __u32 index; /* hardware counter identifier */
165 __s64 offset; /* add to hardware counter value */ 194 __s64 offset; /* add to hardware counter value */
166 195
196 /*
197 * Control data for the mmap() data buffer.
198 *
199 * User-space reading this value should issue an rmb(), on SMP capable
200 * platforms, after reading this value -- see perf_counter_wakeup().
201 */
167 __u32 data_head; /* head in the data section */ 202 __u32 data_head; /* head in the data section */
168}; 203};
169 204
diff --git a/kernel/perf_counter.c b/kernel/perf_counter.c
index f70ff80e79d7..c95e92329b97 100644
--- a/kernel/perf_counter.c
+++ b/kernel/perf_counter.c
@@ -1316,10 +1316,22 @@ static long perf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
1316 return err; 1316 return err;
1317} 1317}
1318 1318
1319static void __perf_counter_update_userpage(struct perf_counter *counter, 1319/*
1320 struct perf_mmap_data *data) 1320 * Callers need to ensure there can be no nesting of this function, otherwise
1321 * the seqlock logic goes bad. We can not serialize this because the arch
1322 * code calls this from NMI context.
1323 */
1324void perf_counter_update_userpage(struct perf_counter *counter)
1321{ 1325{
1322 struct perf_counter_mmap_page *userpg = data->user_page; 1326 struct perf_mmap_data *data;
1327 struct perf_counter_mmap_page *userpg;
1328
1329 rcu_read_lock();
1330 data = rcu_dereference(counter->data);
1331 if (!data)
1332 goto unlock;
1333
1334 userpg = data->user_page;
1323 1335
1324 /* 1336 /*
1325 * Disable preemption so as to not let the corresponding user-space 1337 * Disable preemption so as to not let the corresponding user-space
@@ -1333,20 +1345,10 @@ static void __perf_counter_update_userpage(struct perf_counter *counter,
1333 if (counter->state == PERF_COUNTER_STATE_ACTIVE) 1345 if (counter->state == PERF_COUNTER_STATE_ACTIVE)
1334 userpg->offset -= atomic64_read(&counter->hw.prev_count); 1346 userpg->offset -= atomic64_read(&counter->hw.prev_count);
1335 1347
1336 userpg->data_head = atomic_read(&data->head);
1337 smp_wmb(); 1348 smp_wmb();
1338 ++userpg->lock; 1349 ++userpg->lock;
1339 preempt_enable(); 1350 preempt_enable();
1340} 1351unlock:
1341
1342void perf_counter_update_userpage(struct perf_counter *counter)
1343{
1344 struct perf_mmap_data *data;
1345
1346 rcu_read_lock();
1347 data = rcu_dereference(counter->data);
1348 if (data)
1349 __perf_counter_update_userpage(counter, data);
1350 rcu_read_unlock(); 1352 rcu_read_unlock();
1351} 1353}
1352 1354
@@ -1547,7 +1549,13 @@ void perf_counter_wakeup(struct perf_counter *counter)
1547 data = rcu_dereference(counter->data); 1549 data = rcu_dereference(counter->data);
1548 if (data) { 1550 if (data) {
1549 (void)atomic_xchg(&data->wakeup, POLL_IN); 1551 (void)atomic_xchg(&data->wakeup, POLL_IN);
1550 __perf_counter_update_userpage(counter, data); 1552 /*
1553 * Ensure all data writes are issued before updating the
1554 * user-space data head information. The matching rmb()
1555 * will be in userspace after reading this value.
1556 */
1557 smp_wmb();
1558 data->user_page->data_head = atomic_read(&data->head);
1551 } 1559 }
1552 rcu_read_unlock(); 1560 rcu_read_unlock();
1553 1561