Merge branch 'tracing/hw-branch-tracing' into tracing/core
Merge reason: this topic is ready for upstream now. It passed Oleg's review and Andrew had no further mm/* objections/observations either. Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
commit
0ad5d703c6
|
@ -506,7 +506,6 @@ config X86_PTRACE_BTS
|
||||||
bool "Branch Trace Store"
|
bool "Branch Trace Store"
|
||||||
default y
|
default y
|
||||||
depends on X86_DEBUGCTLMSR
|
depends on X86_DEBUGCTLMSR
|
||||||
depends on BROKEN
|
|
||||||
---help---
|
---help---
|
||||||
This adds a ptrace interface to the hardware's branch trace store.
|
This adds a ptrace interface to the hardware's branch trace store.
|
||||||
|
|
||||||
|
|
|
@ -167,6 +167,15 @@ config IOMMU_LEAK
|
||||||
Add a simple leak tracer to the IOMMU code. This is useful when you
|
Add a simple leak tracer to the IOMMU code. This is useful when you
|
||||||
are debugging a buggy device driver that leaks IOMMU mappings.
|
are debugging a buggy device driver that leaks IOMMU mappings.
|
||||||
|
|
||||||
|
config X86_DS_SELFTEST
|
||||||
|
bool "DS selftest"
|
||||||
|
default y
|
||||||
|
depends on DEBUG_KERNEL
|
||||||
|
depends on X86_DS
|
||||||
|
---help---
|
||||||
|
Perform Debug Store selftests at boot time.
|
||||||
|
If in doubt, say "N".
|
||||||
|
|
||||||
config HAVE_MMIOTRACE_SUPPORT
|
config HAVE_MMIOTRACE_SUPPORT
|
||||||
def_bool y
|
def_bool y
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
* - buffer allocation (memory accounting)
|
* - buffer allocation (memory accounting)
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* Copyright (C) 2007-2008 Intel Corporation.
|
* Copyright (C) 2007-2009 Intel Corporation.
|
||||||
* Markus Metzger <markus.t.metzger@intel.com>, 2007-2008
|
* Markus Metzger <markus.t.metzger@intel.com>, 2007-2009
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _ASM_X86_DS_H
|
#ifndef _ASM_X86_DS_H
|
||||||
|
@ -83,8 +83,10 @@ enum ds_feature {
|
||||||
* The interrupt threshold is independent from the overflow callback
|
* The interrupt threshold is independent from the overflow callback
|
||||||
* to allow users to use their own overflow interrupt handling mechanism.
|
* to allow users to use their own overflow interrupt handling mechanism.
|
||||||
*
|
*
|
||||||
* task: the task to request recording for;
|
* The function might sleep.
|
||||||
* NULL for per-cpu recording on the current cpu
|
*
|
||||||
|
* task: the task to request recording for
|
||||||
|
* cpu: the cpu to request recording for
|
||||||
* base: the base pointer for the (non-pageable) buffer;
|
* base: the base pointer for the (non-pageable) buffer;
|
||||||
* size: the size of the provided buffer in bytes
|
* size: the size of the provided buffer in bytes
|
||||||
* ovfl: pointer to a function to be called on buffer overflow;
|
* ovfl: pointer to a function to be called on buffer overflow;
|
||||||
|
@ -93,19 +95,28 @@ enum ds_feature {
|
||||||
* -1 if no interrupt threshold is requested.
|
* -1 if no interrupt threshold is requested.
|
||||||
* flags: a bit-mask of the above flags
|
* flags: a bit-mask of the above flags
|
||||||
*/
|
*/
|
||||||
extern struct bts_tracer *ds_request_bts(struct task_struct *task,
|
extern struct bts_tracer *ds_request_bts_task(struct task_struct *task,
|
||||||
void *base, size_t size,
|
void *base, size_t size,
|
||||||
bts_ovfl_callback_t ovfl,
|
bts_ovfl_callback_t ovfl,
|
||||||
size_t th, unsigned int flags);
|
size_t th, unsigned int flags);
|
||||||
extern struct pebs_tracer *ds_request_pebs(struct task_struct *task,
|
extern struct bts_tracer *ds_request_bts_cpu(int cpu, void *base, size_t size,
|
||||||
void *base, size_t size,
|
bts_ovfl_callback_t ovfl,
|
||||||
pebs_ovfl_callback_t ovfl,
|
size_t th, unsigned int flags);
|
||||||
size_t th, unsigned int flags);
|
extern struct pebs_tracer *ds_request_pebs_task(struct task_struct *task,
|
||||||
|
void *base, size_t size,
|
||||||
|
pebs_ovfl_callback_t ovfl,
|
||||||
|
size_t th, unsigned int flags);
|
||||||
|
extern struct pebs_tracer *ds_request_pebs_cpu(int cpu,
|
||||||
|
void *base, size_t size,
|
||||||
|
pebs_ovfl_callback_t ovfl,
|
||||||
|
size_t th, unsigned int flags);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Release BTS or PEBS resources
|
* Release BTS or PEBS resources
|
||||||
* Suspend and resume BTS or PEBS tracing
|
* Suspend and resume BTS or PEBS tracing
|
||||||
*
|
*
|
||||||
|
* Must be called with irq's enabled.
|
||||||
|
*
|
||||||
* tracer: the tracer handle returned from ds_request_~()
|
* tracer: the tracer handle returned from ds_request_~()
|
||||||
*/
|
*/
|
||||||
extern void ds_release_bts(struct bts_tracer *tracer);
|
extern void ds_release_bts(struct bts_tracer *tracer);
|
||||||
|
@ -115,6 +126,28 @@ extern void ds_release_pebs(struct pebs_tracer *tracer);
|
||||||
extern void ds_suspend_pebs(struct pebs_tracer *tracer);
|
extern void ds_suspend_pebs(struct pebs_tracer *tracer);
|
||||||
extern void ds_resume_pebs(struct pebs_tracer *tracer);
|
extern void ds_resume_pebs(struct pebs_tracer *tracer);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Release BTS or PEBS resources
|
||||||
|
* Suspend and resume BTS or PEBS tracing
|
||||||
|
*
|
||||||
|
* Cpu tracers must call this on the traced cpu.
|
||||||
|
* Task tracers must call ds_release_~_noirq() for themselves.
|
||||||
|
*
|
||||||
|
* May be called with irq's disabled.
|
||||||
|
*
|
||||||
|
* Returns 0 if successful;
|
||||||
|
* -EPERM if the cpu tracer does not trace the current cpu.
|
||||||
|
* -EPERM if the task tracer does not trace itself.
|
||||||
|
*
|
||||||
|
* tracer: the tracer handle returned from ds_request_~()
|
||||||
|
*/
|
||||||
|
extern int ds_release_bts_noirq(struct bts_tracer *tracer);
|
||||||
|
extern int ds_suspend_bts_noirq(struct bts_tracer *tracer);
|
||||||
|
extern int ds_resume_bts_noirq(struct bts_tracer *tracer);
|
||||||
|
extern int ds_release_pebs_noirq(struct pebs_tracer *tracer);
|
||||||
|
extern int ds_suspend_pebs_noirq(struct pebs_tracer *tracer);
|
||||||
|
extern int ds_resume_pebs_noirq(struct pebs_tracer *tracer);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The raw DS buffer state as it is used for BTS and PEBS recording.
|
* The raw DS buffer state as it is used for BTS and PEBS recording.
|
||||||
|
@ -170,9 +203,9 @@ struct bts_struct {
|
||||||
} lbr;
|
} lbr;
|
||||||
/* BTS_TASK_ARRIVES or BTS_TASK_DEPARTS */
|
/* BTS_TASK_ARRIVES or BTS_TASK_DEPARTS */
|
||||||
struct {
|
struct {
|
||||||
__u64 jiffies;
|
__u64 clock;
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
} timestamp;
|
} event;
|
||||||
} variant;
|
} variant;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -201,8 +234,12 @@ struct bts_trace {
|
||||||
struct pebs_trace {
|
struct pebs_trace {
|
||||||
struct ds_trace ds;
|
struct ds_trace ds;
|
||||||
|
|
||||||
/* the PEBS reset value */
|
/* the number of valid counters in the below array */
|
||||||
unsigned long long reset_value;
|
unsigned int counters;
|
||||||
|
|
||||||
|
#define MAX_PEBS_COUNTERS 4
|
||||||
|
/* the counter reset value */
|
||||||
|
unsigned long long counter_reset[MAX_PEBS_COUNTERS];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -237,9 +274,11 @@ extern int ds_reset_pebs(struct pebs_tracer *tracer);
|
||||||
* Returns 0 on success; -Eerrno on error
|
* Returns 0 on success; -Eerrno on error
|
||||||
*
|
*
|
||||||
* tracer: the tracer handle returned from ds_request_pebs()
|
* tracer: the tracer handle returned from ds_request_pebs()
|
||||||
|
* counter: the index of the counter
|
||||||
* value: the new counter reset value
|
* value: the new counter reset value
|
||||||
*/
|
*/
|
||||||
extern int ds_set_pebs_reset(struct pebs_tracer *tracer, u64 value);
|
extern int ds_set_pebs_reset(struct pebs_tracer *tracer,
|
||||||
|
unsigned int counter, u64 value);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialization
|
* Initialization
|
||||||
|
@ -252,21 +291,12 @@ extern void __cpuinit ds_init_intel(struct cpuinfo_x86 *);
|
||||||
*/
|
*/
|
||||||
extern void ds_switch_to(struct task_struct *prev, struct task_struct *next);
|
extern void ds_switch_to(struct task_struct *prev, struct task_struct *next);
|
||||||
|
|
||||||
/*
|
|
||||||
* Task clone/init and cleanup work
|
|
||||||
*/
|
|
||||||
extern void ds_copy_thread(struct task_struct *tsk, struct task_struct *father);
|
|
||||||
extern void ds_exit_thread(struct task_struct *tsk);
|
|
||||||
|
|
||||||
#else /* CONFIG_X86_DS */
|
#else /* CONFIG_X86_DS */
|
||||||
|
|
||||||
struct cpuinfo_x86;
|
struct cpuinfo_x86;
|
||||||
static inline void __cpuinit ds_init_intel(struct cpuinfo_x86 *ignored) {}
|
static inline void __cpuinit ds_init_intel(struct cpuinfo_x86 *ignored) {}
|
||||||
static inline void ds_switch_to(struct task_struct *prev,
|
static inline void ds_switch_to(struct task_struct *prev,
|
||||||
struct task_struct *next) {}
|
struct task_struct *next) {}
|
||||||
static inline void ds_copy_thread(struct task_struct *tsk,
|
|
||||||
struct task_struct *father) {}
|
|
||||||
static inline void ds_exit_thread(struct task_struct *tsk) {}
|
|
||||||
|
|
||||||
#endif /* CONFIG_X86_DS */
|
#endif /* CONFIG_X86_DS */
|
||||||
#endif /* _ASM_X86_DS_H */
|
#endif /* _ASM_X86_DS_H */
|
||||||
|
|
|
@ -460,14 +460,8 @@ struct thread_struct {
|
||||||
unsigned io_bitmap_max;
|
unsigned io_bitmap_max;
|
||||||
/* MSR_IA32_DEBUGCTLMSR value to switch in if TIF_DEBUGCTLMSR is set. */
|
/* MSR_IA32_DEBUGCTLMSR value to switch in if TIF_DEBUGCTLMSR is set. */
|
||||||
unsigned long debugctlmsr;
|
unsigned long debugctlmsr;
|
||||||
#ifdef CONFIG_X86_DS
|
/* Debug Store context; see asm/ds.h */
|
||||||
/* Debug Store context; see include/asm-x86/ds.h; goes into MSR_IA32_DS_AREA */
|
|
||||||
struct ds_context *ds_ctx;
|
struct ds_context *ds_ctx;
|
||||||
#endif /* CONFIG_X86_DS */
|
|
||||||
#ifdef CONFIG_X86_PTRACE_BTS
|
|
||||||
/* the signal to send on a bts buffer overflow */
|
|
||||||
unsigned int bts_ovfl_signal;
|
|
||||||
#endif /* CONFIG_X86_PTRACE_BTS */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline unsigned long native_get_debugreg(int regno)
|
static inline unsigned long native_get_debugreg(int regno)
|
||||||
|
@ -795,6 +789,21 @@ static inline unsigned long get_debugctlmsr(void)
|
||||||
return debugctlmsr;
|
return debugctlmsr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline unsigned long get_debugctlmsr_on_cpu(int cpu)
|
||||||
|
{
|
||||||
|
u64 debugctlmsr = 0;
|
||||||
|
u32 val1, val2;
|
||||||
|
|
||||||
|
#ifndef CONFIG_X86_DEBUGCTLMSR
|
||||||
|
if (boot_cpu_data.x86 < 6)
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
rdmsr_on_cpu(cpu, MSR_IA32_DEBUGCTLMSR, &val1, &val2);
|
||||||
|
debugctlmsr = val1 | ((u64)val2 << 32);
|
||||||
|
|
||||||
|
return debugctlmsr;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void update_debugctlmsr(unsigned long debugctlmsr)
|
static inline void update_debugctlmsr(unsigned long debugctlmsr)
|
||||||
{
|
{
|
||||||
#ifndef CONFIG_X86_DEBUGCTLMSR
|
#ifndef CONFIG_X86_DEBUGCTLMSR
|
||||||
|
@ -804,6 +813,18 @@ static inline void update_debugctlmsr(unsigned long debugctlmsr)
|
||||||
wrmsrl(MSR_IA32_DEBUGCTLMSR, debugctlmsr);
|
wrmsrl(MSR_IA32_DEBUGCTLMSR, debugctlmsr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void update_debugctlmsr_on_cpu(int cpu,
|
||||||
|
unsigned long debugctlmsr)
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_X86_DEBUGCTLMSR
|
||||||
|
if (boot_cpu_data.x86 < 6)
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
wrmsr_on_cpu(cpu, MSR_IA32_DEBUGCTLMSR,
|
||||||
|
(u32)((u64)debugctlmsr),
|
||||||
|
(u32)((u64)debugctlmsr >> 32));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* from system description table in BIOS. Mostly for MCA use, but
|
* from system description table in BIOS. Mostly for MCA use, but
|
||||||
* others may find it useful:
|
* others may find it useful:
|
||||||
|
|
|
@ -235,12 +235,11 @@ extern int do_get_thread_area(struct task_struct *p, int idx,
|
||||||
extern int do_set_thread_area(struct task_struct *p, int idx,
|
extern int do_set_thread_area(struct task_struct *p, int idx,
|
||||||
struct user_desc __user *info, int can_allocate);
|
struct user_desc __user *info, int can_allocate);
|
||||||
|
|
||||||
extern void x86_ptrace_untrace(struct task_struct *);
|
#ifdef CONFIG_X86_PTRACE_BTS
|
||||||
extern void x86_ptrace_fork(struct task_struct *child,
|
extern void ptrace_bts_untrace(struct task_struct *tsk);
|
||||||
unsigned long clone_flags);
|
|
||||||
|
|
||||||
#define arch_ptrace_untrace(tsk) x86_ptrace_untrace(tsk)
|
#define arch_ptrace_untrace(tsk) ptrace_bts_untrace(tsk)
|
||||||
#define arch_ptrace_fork(child, flags) x86_ptrace_fork(child, flags)
|
#endif /* CONFIG_X86_PTRACE_BTS */
|
||||||
|
|
||||||
#endif /* __KERNEL__ */
|
#endif /* __KERNEL__ */
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ obj-y += process.o
|
||||||
obj-y += i387.o xsave.o
|
obj-y += i387.o xsave.o
|
||||||
obj-y += ptrace.o
|
obj-y += ptrace.o
|
||||||
obj-$(CONFIG_X86_DS) += ds.o
|
obj-$(CONFIG_X86_DS) += ds.o
|
||||||
|
obj-$(CONFIG_X86_DS_SELFTEST) += ds_selftest.o
|
||||||
obj-$(CONFIG_X86_32) += tls.o
|
obj-$(CONFIG_X86_32) += tls.o
|
||||||
obj-$(CONFIG_IA32_EMULATION) += tls.o
|
obj-$(CONFIG_IA32_EMULATION) += tls.o
|
||||||
obj-y += step.o
|
obj-y += step.o
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,408 @@
|
||||||
|
/*
|
||||||
|
* Debug Store support - selftest
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 Intel Corporation.
|
||||||
|
* Markus Metzger <markus.t.metzger@intel.com>, 2009
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ds_selftest.h"
|
||||||
|
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <linux/smp.h>
|
||||||
|
#include <linux/cpu.h>
|
||||||
|
|
||||||
|
#include <asm/ds.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define BUFFER_SIZE 521 /* Intentionally chose an odd size. */
|
||||||
|
#define SMALL_BUFFER_SIZE 24 /* A single bts entry. */
|
||||||
|
|
||||||
|
struct ds_selftest_bts_conf {
|
||||||
|
struct bts_tracer *tracer;
|
||||||
|
int error;
|
||||||
|
int (*suspend)(struct bts_tracer *);
|
||||||
|
int (*resume)(struct bts_tracer *);
|
||||||
|
};
|
||||||
|
|
||||||
|
static int ds_selftest_bts_consistency(const struct bts_trace *trace)
|
||||||
|
{
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
if (!trace) {
|
||||||
|
printk(KERN_CONT "failed to access trace...");
|
||||||
|
/* Bail out. Other tests are pointless. */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trace->read) {
|
||||||
|
printk(KERN_CONT "bts read not available...");
|
||||||
|
error = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Do some sanity checks on the trace configuration. */
|
||||||
|
if (!trace->ds.n) {
|
||||||
|
printk(KERN_CONT "empty bts buffer...");
|
||||||
|
error = -1;
|
||||||
|
}
|
||||||
|
if (!trace->ds.size) {
|
||||||
|
printk(KERN_CONT "bad bts trace setup...");
|
||||||
|
error = -1;
|
||||||
|
}
|
||||||
|
if (trace->ds.end !=
|
||||||
|
(char *)trace->ds.begin + (trace->ds.n * trace->ds.size)) {
|
||||||
|
printk(KERN_CONT "bad bts buffer setup...");
|
||||||
|
error = -1;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* We allow top in [begin; end], since its not clear when the
|
||||||
|
* overflow adjustment happens: after the increment or before the
|
||||||
|
* write.
|
||||||
|
*/
|
||||||
|
if ((trace->ds.top < trace->ds.begin) ||
|
||||||
|
(trace->ds.end < trace->ds.top)) {
|
||||||
|
printk(KERN_CONT "bts top out of bounds...");
|
||||||
|
error = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ds_selftest_bts_read(struct bts_tracer *tracer,
|
||||||
|
const struct bts_trace *trace,
|
||||||
|
const void *from, const void *to)
|
||||||
|
{
|
||||||
|
const unsigned char *at;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check a few things which do not belong to this test.
|
||||||
|
* They should be covered by other tests.
|
||||||
|
*/
|
||||||
|
if (!trace)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!trace->read)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (to < from)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (from < trace->ds.begin)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (trace->ds.end < to)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (!trace->ds.size)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* Now to the test itself. */
|
||||||
|
for (at = from; (void *)at < to; at += trace->ds.size) {
|
||||||
|
struct bts_struct bts;
|
||||||
|
unsigned long index;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
if (((void *)at - trace->ds.begin) % trace->ds.size) {
|
||||||
|
printk(KERN_CONT
|
||||||
|
"read from non-integer index...");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
index = ((void *)at - trace->ds.begin) / trace->ds.size;
|
||||||
|
|
||||||
|
memset(&bts, 0, sizeof(bts));
|
||||||
|
error = trace->read(tracer, at, &bts);
|
||||||
|
if (error < 0) {
|
||||||
|
printk(KERN_CONT
|
||||||
|
"error reading bts trace at [%lu] (0x%p)...",
|
||||||
|
index, at);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (bts.qualifier) {
|
||||||
|
case BTS_BRANCH:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printk(KERN_CONT
|
||||||
|
"unexpected bts entry %llu at [%lu] (0x%p)...",
|
||||||
|
bts.qualifier, index, at);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ds_selftest_bts_cpu(void *arg)
|
||||||
|
{
|
||||||
|
struct ds_selftest_bts_conf *conf = arg;
|
||||||
|
const struct bts_trace *trace;
|
||||||
|
void *top;
|
||||||
|
|
||||||
|
if (IS_ERR(conf->tracer)) {
|
||||||
|
conf->error = PTR_ERR(conf->tracer);
|
||||||
|
conf->tracer = NULL;
|
||||||
|
|
||||||
|
printk(KERN_CONT
|
||||||
|
"initialization failed (err: %d)...", conf->error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We should meanwhile have enough trace. */
|
||||||
|
conf->error = conf->suspend(conf->tracer);
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Let's see if we can access the trace. */
|
||||||
|
trace = ds_read_bts(conf->tracer);
|
||||||
|
|
||||||
|
conf->error = ds_selftest_bts_consistency(trace);
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* If everything went well, we should have a few trace entries. */
|
||||||
|
if (trace->ds.top == trace->ds.begin) {
|
||||||
|
/*
|
||||||
|
* It is possible but highly unlikely that we got a
|
||||||
|
* buffer overflow and end up at exactly the same
|
||||||
|
* position we started from.
|
||||||
|
* Let's issue a warning, but continue.
|
||||||
|
*/
|
||||||
|
printk(KERN_CONT "no trace/overflow...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let's try to read the trace we collected. */
|
||||||
|
conf->error =
|
||||||
|
ds_selftest_bts_read(conf->tracer, trace,
|
||||||
|
trace->ds.begin, trace->ds.top);
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Let's read the trace again.
|
||||||
|
* Since we suspended tracing, we should get the same result.
|
||||||
|
*/
|
||||||
|
top = trace->ds.top;
|
||||||
|
|
||||||
|
trace = ds_read_bts(conf->tracer);
|
||||||
|
conf->error = ds_selftest_bts_consistency(trace);
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (top != trace->ds.top) {
|
||||||
|
printk(KERN_CONT "suspend not working...");
|
||||||
|
conf->error = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let's collect some more trace - see if resume is working. */
|
||||||
|
conf->error = conf->resume(conf->tracer);
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conf->error = conf->suspend(conf->tracer);
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
trace = ds_read_bts(conf->tracer);
|
||||||
|
|
||||||
|
conf->error = ds_selftest_bts_consistency(trace);
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (trace->ds.top == top) {
|
||||||
|
/*
|
||||||
|
* It is possible but highly unlikely that we got a
|
||||||
|
* buffer overflow and end up at exactly the same
|
||||||
|
* position we started from.
|
||||||
|
* Let's issue a warning and check the full trace.
|
||||||
|
*/
|
||||||
|
printk(KERN_CONT
|
||||||
|
"no resume progress/overflow...");
|
||||||
|
|
||||||
|
conf->error =
|
||||||
|
ds_selftest_bts_read(conf->tracer, trace,
|
||||||
|
trace->ds.begin, trace->ds.end);
|
||||||
|
} else if (trace->ds.top < top) {
|
||||||
|
/*
|
||||||
|
* We had a buffer overflow - the entire buffer should
|
||||||
|
* contain trace records.
|
||||||
|
*/
|
||||||
|
conf->error =
|
||||||
|
ds_selftest_bts_read(conf->tracer, trace,
|
||||||
|
trace->ds.begin, trace->ds.end);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* It is quite likely that the buffer did not overflow.
|
||||||
|
* Let's just check the delta trace.
|
||||||
|
*/
|
||||||
|
conf->error =
|
||||||
|
ds_selftest_bts_read(conf->tracer, trace, top,
|
||||||
|
trace->ds.top);
|
||||||
|
}
|
||||||
|
if (conf->error < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
conf->error = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ds_suspend_bts_wrap(struct bts_tracer *tracer)
|
||||||
|
{
|
||||||
|
ds_suspend_bts(tracer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ds_resume_bts_wrap(struct bts_tracer *tracer)
|
||||||
|
{
|
||||||
|
ds_resume_bts(tracer);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ds_release_bts_noirq_wrap(void *tracer)
|
||||||
|
{
|
||||||
|
(void)ds_release_bts_noirq(tracer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ds_selftest_bts_bad_release_noirq(int cpu,
|
||||||
|
struct bts_tracer *tracer)
|
||||||
|
{
|
||||||
|
int error = -EPERM;
|
||||||
|
|
||||||
|
/* Try to release the tracer on the wrong cpu. */
|
||||||
|
get_cpu();
|
||||||
|
if (cpu != smp_processor_id()) {
|
||||||
|
error = ds_release_bts_noirq(tracer);
|
||||||
|
if (error != -EPERM)
|
||||||
|
printk(KERN_CONT "release on wrong cpu...");
|
||||||
|
}
|
||||||
|
put_cpu();
|
||||||
|
|
||||||
|
return error ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ds_selftest_bts_bad_request_cpu(int cpu, void *buffer)
|
||||||
|
{
|
||||||
|
struct bts_tracer *tracer;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
/* Try to request cpu tracing while task tracing is active. */
|
||||||
|
tracer = ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE, NULL,
|
||||||
|
(size_t)-1, BTS_KERNEL);
|
||||||
|
error = PTR_ERR(tracer);
|
||||||
|
if (!IS_ERR(tracer)) {
|
||||||
|
ds_release_bts(tracer);
|
||||||
|
error = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != -EPERM)
|
||||||
|
printk(KERN_CONT "cpu/task tracing overlap...");
|
||||||
|
|
||||||
|
return error ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ds_selftest_bts_bad_request_task(void *buffer)
|
||||||
|
{
|
||||||
|
struct bts_tracer *tracer;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
/* Try to request cpu tracing while task tracing is active. */
|
||||||
|
tracer = ds_request_bts_task(current, buffer, BUFFER_SIZE, NULL,
|
||||||
|
(size_t)-1, BTS_KERNEL);
|
||||||
|
error = PTR_ERR(tracer);
|
||||||
|
if (!IS_ERR(tracer)) {
|
||||||
|
error = 0;
|
||||||
|
ds_release_bts(tracer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != -EPERM)
|
||||||
|
printk(KERN_CONT "task/cpu tracing overlap...");
|
||||||
|
|
||||||
|
return error ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ds_selftest_bts(void)
|
||||||
|
{
|
||||||
|
struct ds_selftest_bts_conf conf;
|
||||||
|
unsigned char buffer[BUFFER_SIZE], *small_buffer;
|
||||||
|
unsigned long irq;
|
||||||
|
int cpu;
|
||||||
|
|
||||||
|
printk(KERN_INFO "[ds] bts selftest...");
|
||||||
|
conf.error = 0;
|
||||||
|
|
||||||
|
small_buffer = (unsigned char *)ALIGN((unsigned long)buffer, 8) + 8;
|
||||||
|
|
||||||
|
get_online_cpus();
|
||||||
|
for_each_online_cpu(cpu) {
|
||||||
|
conf.suspend = ds_suspend_bts_wrap;
|
||||||
|
conf.resume = ds_resume_bts_wrap;
|
||||||
|
conf.tracer =
|
||||||
|
ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE,
|
||||||
|
NULL, (size_t)-1, BTS_KERNEL);
|
||||||
|
ds_selftest_bts_cpu(&conf);
|
||||||
|
if (conf.error >= 0)
|
||||||
|
conf.error = ds_selftest_bts_bad_request_task(buffer);
|
||||||
|
ds_release_bts(conf.tracer);
|
||||||
|
if (conf.error < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
conf.suspend = ds_suspend_bts_noirq;
|
||||||
|
conf.resume = ds_resume_bts_noirq;
|
||||||
|
conf.tracer =
|
||||||
|
ds_request_bts_cpu(cpu, buffer, BUFFER_SIZE,
|
||||||
|
NULL, (size_t)-1, BTS_KERNEL);
|
||||||
|
smp_call_function_single(cpu, ds_selftest_bts_cpu, &conf, 1);
|
||||||
|
if (conf.error >= 0) {
|
||||||
|
conf.error =
|
||||||
|
ds_selftest_bts_bad_release_noirq(cpu,
|
||||||
|
conf.tracer);
|
||||||
|
/* We must not release the tracer twice. */
|
||||||
|
if (conf.error < 0)
|
||||||
|
conf.tracer = NULL;
|
||||||
|
}
|
||||||
|
if (conf.error >= 0)
|
||||||
|
conf.error = ds_selftest_bts_bad_request_task(buffer);
|
||||||
|
smp_call_function_single(cpu, ds_release_bts_noirq_wrap,
|
||||||
|
conf.tracer, 1);
|
||||||
|
if (conf.error < 0)
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.suspend = ds_suspend_bts_wrap;
|
||||||
|
conf.resume = ds_resume_bts_wrap;
|
||||||
|
conf.tracer =
|
||||||
|
ds_request_bts_task(current, buffer, BUFFER_SIZE,
|
||||||
|
NULL, (size_t)-1, BTS_KERNEL);
|
||||||
|
ds_selftest_bts_cpu(&conf);
|
||||||
|
if (conf.error >= 0)
|
||||||
|
conf.error = ds_selftest_bts_bad_request_cpu(0, buffer);
|
||||||
|
ds_release_bts(conf.tracer);
|
||||||
|
if (conf.error < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
conf.suspend = ds_suspend_bts_noirq;
|
||||||
|
conf.resume = ds_resume_bts_noirq;
|
||||||
|
conf.tracer =
|
||||||
|
ds_request_bts_task(current, small_buffer, SMALL_BUFFER_SIZE,
|
||||||
|
NULL, (size_t)-1, BTS_KERNEL);
|
||||||
|
local_irq_save(irq);
|
||||||
|
ds_selftest_bts_cpu(&conf);
|
||||||
|
if (conf.error >= 0)
|
||||||
|
conf.error = ds_selftest_bts_bad_request_cpu(0, buffer);
|
||||||
|
ds_release_bts_noirq(conf.tracer);
|
||||||
|
local_irq_restore(irq);
|
||||||
|
if (conf.error < 0)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
conf.error = 0;
|
||||||
|
out:
|
||||||
|
put_online_cpus();
|
||||||
|
printk(KERN_CONT "%s.\n", (conf.error ? "failed" : "passed"));
|
||||||
|
|
||||||
|
return conf.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ds_selftest_pebs(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Debug Store support - selftest
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright (C) 2009 Intel Corporation.
|
||||||
|
* Markus Metzger <markus.t.metzger@intel.com>, 2009
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef CONFIG_X86_DS_SELFTEST
|
||||||
|
extern int ds_selftest_bts(void);
|
||||||
|
extern int ds_selftest_pebs(void);
|
||||||
|
#else
|
||||||
|
static inline int ds_selftest_bts(void) { return 0; }
|
||||||
|
static inline int ds_selftest_pebs(void) { return 0; }
|
||||||
|
#endif
|
|
@ -14,6 +14,7 @@
|
||||||
#include <asm/idle.h>
|
#include <asm/idle.h>
|
||||||
#include <asm/uaccess.h>
|
#include <asm/uaccess.h>
|
||||||
#include <asm/i387.h>
|
#include <asm/i387.h>
|
||||||
|
#include <asm/ds.h>
|
||||||
|
|
||||||
unsigned long idle_halt;
|
unsigned long idle_halt;
|
||||||
EXPORT_SYMBOL(idle_halt);
|
EXPORT_SYMBOL(idle_halt);
|
||||||
|
@ -45,6 +46,8 @@ void free_thread_xstate(struct task_struct *tsk)
|
||||||
kmem_cache_free(task_xstate_cachep, tsk->thread.xstate);
|
kmem_cache_free(task_xstate_cachep, tsk->thread.xstate);
|
||||||
tsk->thread.xstate = NULL;
|
tsk->thread.xstate = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WARN(tsk->thread.ds_ctx, "leaking DS context\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_thread_info(struct thread_info *ti)
|
void free_thread_info(struct thread_info *ti)
|
||||||
|
@ -83,8 +86,6 @@ void exit_thread(void)
|
||||||
put_cpu();
|
put_cpu();
|
||||||
kfree(bp);
|
kfree(bp);
|
||||||
}
|
}
|
||||||
|
|
||||||
ds_exit_thread(current);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void flush_thread(void)
|
void flush_thread(void)
|
||||||
|
|
|
@ -290,7 +290,8 @@ int copy_thread(unsigned long clone_flags, unsigned long sp,
|
||||||
p->thread.io_bitmap_max = 0;
|
p->thread.io_bitmap_max = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ds_copy_thread(p, current);
|
clear_tsk_thread_flag(p, TIF_DS_AREA_MSR);
|
||||||
|
p->thread.ds_ctx = NULL;
|
||||||
|
|
||||||
clear_tsk_thread_flag(p, TIF_DEBUGCTLMSR);
|
clear_tsk_thread_flag(p, TIF_DEBUGCTLMSR);
|
||||||
p->thread.debugctlmsr = 0;
|
p->thread.debugctlmsr = 0;
|
||||||
|
|
|
@ -335,7 +335,8 @@ int copy_thread(unsigned long clone_flags, unsigned long sp,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ds_copy_thread(p, me);
|
clear_tsk_thread_flag(p, TIF_DS_AREA_MSR);
|
||||||
|
p->thread.ds_ctx = NULL;
|
||||||
|
|
||||||
clear_tsk_thread_flag(p, TIF_DEBUGCTLMSR);
|
clear_tsk_thread_flag(p, TIF_DEBUGCTLMSR);
|
||||||
p->thread.debugctlmsr = 0;
|
p->thread.debugctlmsr = 0;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <linux/audit.h>
|
#include <linux/audit.h>
|
||||||
#include <linux/seccomp.h>
|
#include <linux/seccomp.h>
|
||||||
#include <linux/signal.h>
|
#include <linux/signal.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
|
||||||
#include <asm/uaccess.h>
|
#include <asm/uaccess.h>
|
||||||
#include <asm/pgtable.h>
|
#include <asm/pgtable.h>
|
||||||
|
@ -578,17 +579,130 @@ static int ioperm_get(struct task_struct *target,
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_X86_PTRACE_BTS
|
#ifdef CONFIG_X86_PTRACE_BTS
|
||||||
|
/*
|
||||||
|
* A branch trace store context.
|
||||||
|
*
|
||||||
|
* Contexts may only be installed by ptrace_bts_config() and only for
|
||||||
|
* ptraced tasks.
|
||||||
|
*
|
||||||
|
* Contexts are destroyed when the tracee is detached from the tracer.
|
||||||
|
* The actual destruction work requires interrupts enabled, so the
|
||||||
|
* work is deferred and will be scheduled during __ptrace_unlink().
|
||||||
|
*
|
||||||
|
* Contexts hold an additional task_struct reference on the traced
|
||||||
|
* task, as well as a reference on the tracer's mm.
|
||||||
|
*
|
||||||
|
* Ptrace already holds a task_struct for the duration of ptrace operations,
|
||||||
|
* but since destruction is deferred, it may be executed after both
|
||||||
|
* tracer and tracee exited.
|
||||||
|
*/
|
||||||
|
struct bts_context {
|
||||||
|
/* The branch trace handle. */
|
||||||
|
struct bts_tracer *tracer;
|
||||||
|
|
||||||
|
/* The buffer used to store the branch trace and its size. */
|
||||||
|
void *buffer;
|
||||||
|
unsigned int size;
|
||||||
|
|
||||||
|
/* The mm that paid for the above buffer. */
|
||||||
|
struct mm_struct *mm;
|
||||||
|
|
||||||
|
/* The task this context belongs to. */
|
||||||
|
struct task_struct *task;
|
||||||
|
|
||||||
|
/* The signal to send on a bts buffer overflow. */
|
||||||
|
unsigned int bts_ovfl_signal;
|
||||||
|
|
||||||
|
/* The work struct to destroy a context. */
|
||||||
|
struct work_struct work;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int alloc_bts_buffer(struct bts_context *context, unsigned int size)
|
||||||
|
{
|
||||||
|
void *buffer = NULL;
|
||||||
|
int err = -ENOMEM;
|
||||||
|
|
||||||
|
err = account_locked_memory(current->mm, current->signal->rlim, size);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
buffer = kzalloc(size, GFP_KERNEL);
|
||||||
|
if (!buffer)
|
||||||
|
goto out_refund;
|
||||||
|
|
||||||
|
context->buffer = buffer;
|
||||||
|
context->size = size;
|
||||||
|
context->mm = get_task_mm(current);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_refund:
|
||||||
|
refund_locked_memory(current->mm, size);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void free_bts_buffer(struct bts_context *context)
|
||||||
|
{
|
||||||
|
if (!context->buffer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
kfree(context->buffer);
|
||||||
|
context->buffer = NULL;
|
||||||
|
|
||||||
|
refund_locked_memory(context->mm, context->size);
|
||||||
|
context->size = 0;
|
||||||
|
|
||||||
|
mmput(context->mm);
|
||||||
|
context->mm = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_bts_context_work(struct work_struct *w)
|
||||||
|
{
|
||||||
|
struct bts_context *context;
|
||||||
|
|
||||||
|
context = container_of(w, struct bts_context, work);
|
||||||
|
|
||||||
|
ds_release_bts(context->tracer);
|
||||||
|
put_task_struct(context->task);
|
||||||
|
free_bts_buffer(context);
|
||||||
|
kfree(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void free_bts_context(struct bts_context *context)
|
||||||
|
{
|
||||||
|
INIT_WORK(&context->work, free_bts_context_work);
|
||||||
|
schedule_work(&context->work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct bts_context *alloc_bts_context(struct task_struct *task)
|
||||||
|
{
|
||||||
|
struct bts_context *context = kzalloc(sizeof(*context), GFP_KERNEL);
|
||||||
|
if (context) {
|
||||||
|
context->task = task;
|
||||||
|
task->bts = context;
|
||||||
|
|
||||||
|
get_task_struct(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
static int ptrace_bts_read_record(struct task_struct *child, size_t index,
|
static int ptrace_bts_read_record(struct task_struct *child, size_t index,
|
||||||
struct bts_struct __user *out)
|
struct bts_struct __user *out)
|
||||||
{
|
{
|
||||||
|
struct bts_context *context;
|
||||||
const struct bts_trace *trace;
|
const struct bts_trace *trace;
|
||||||
struct bts_struct bts;
|
struct bts_struct bts;
|
||||||
const unsigned char *at;
|
const unsigned char *at;
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
trace = ds_read_bts(child->bts);
|
context = child->bts;
|
||||||
|
if (!context)
|
||||||
|
return -ESRCH;
|
||||||
|
|
||||||
|
trace = ds_read_bts(context->tracer);
|
||||||
if (!trace)
|
if (!trace)
|
||||||
return -EPERM;
|
return -ESRCH;
|
||||||
|
|
||||||
at = trace->ds.top - ((index + 1) * trace->ds.size);
|
at = trace->ds.top - ((index + 1) * trace->ds.size);
|
||||||
if ((void *)at < trace->ds.begin)
|
if ((void *)at < trace->ds.begin)
|
||||||
|
@ -597,7 +711,7 @@ static int ptrace_bts_read_record(struct task_struct *child, size_t index,
|
||||||
if (!trace->read)
|
if (!trace->read)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
|
||||||
error = trace->read(child->bts, at, &bts);
|
error = trace->read(context->tracer, at, &bts);
|
||||||
if (error < 0)
|
if (error < 0)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
|
@ -611,13 +725,18 @@ static int ptrace_bts_drain(struct task_struct *child,
|
||||||
long size,
|
long size,
|
||||||
struct bts_struct __user *out)
|
struct bts_struct __user *out)
|
||||||
{
|
{
|
||||||
|
struct bts_context *context;
|
||||||
const struct bts_trace *trace;
|
const struct bts_trace *trace;
|
||||||
const unsigned char *at;
|
const unsigned char *at;
|
||||||
int error, drained = 0;
|
int error, drained = 0;
|
||||||
|
|
||||||
trace = ds_read_bts(child->bts);
|
context = child->bts;
|
||||||
|
if (!context)
|
||||||
|
return -ESRCH;
|
||||||
|
|
||||||
|
trace = ds_read_bts(context->tracer);
|
||||||
if (!trace)
|
if (!trace)
|
||||||
return -EPERM;
|
return -ESRCH;
|
||||||
|
|
||||||
if (!trace->read)
|
if (!trace->read)
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
@ -628,9 +747,8 @@ static int ptrace_bts_drain(struct task_struct *child,
|
||||||
for (at = trace->ds.begin; (void *)at < trace->ds.top;
|
for (at = trace->ds.begin; (void *)at < trace->ds.top;
|
||||||
out++, drained++, at += trace->ds.size) {
|
out++, drained++, at += trace->ds.size) {
|
||||||
struct bts_struct bts;
|
struct bts_struct bts;
|
||||||
int error;
|
|
||||||
|
|
||||||
error = trace->read(child->bts, at, &bts);
|
error = trace->read(context->tracer, at, &bts);
|
||||||
if (error < 0)
|
if (error < 0)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
|
@ -640,35 +758,18 @@ static int ptrace_bts_drain(struct task_struct *child,
|
||||||
|
|
||||||
memset(trace->ds.begin, 0, trace->ds.n * trace->ds.size);
|
memset(trace->ds.begin, 0, trace->ds.n * trace->ds.size);
|
||||||
|
|
||||||
error = ds_reset_bts(child->bts);
|
error = ds_reset_bts(context->tracer);
|
||||||
if (error < 0)
|
if (error < 0)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
return drained;
|
return drained;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ptrace_bts_allocate_buffer(struct task_struct *child, size_t size)
|
|
||||||
{
|
|
||||||
child->bts_buffer = alloc_locked_buffer(size);
|
|
||||||
if (!child->bts_buffer)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
child->bts_size = size;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ptrace_bts_free_buffer(struct task_struct *child)
|
|
||||||
{
|
|
||||||
free_locked_buffer(child->bts_buffer, child->bts_size);
|
|
||||||
child->bts_buffer = NULL;
|
|
||||||
child->bts_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ptrace_bts_config(struct task_struct *child,
|
static int ptrace_bts_config(struct task_struct *child,
|
||||||
long cfg_size,
|
long cfg_size,
|
||||||
const struct ptrace_bts_config __user *ucfg)
|
const struct ptrace_bts_config __user *ucfg)
|
||||||
{
|
{
|
||||||
|
struct bts_context *context;
|
||||||
struct ptrace_bts_config cfg;
|
struct ptrace_bts_config cfg;
|
||||||
unsigned int flags = 0;
|
unsigned int flags = 0;
|
||||||
|
|
||||||
|
@ -678,28 +779,33 @@ static int ptrace_bts_config(struct task_struct *child,
|
||||||
if (copy_from_user(&cfg, ucfg, sizeof(cfg)))
|
if (copy_from_user(&cfg, ucfg, sizeof(cfg)))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
if (child->bts) {
|
context = child->bts;
|
||||||
ds_release_bts(child->bts);
|
if (!context)
|
||||||
child->bts = NULL;
|
context = alloc_bts_context(child);
|
||||||
}
|
if (!context)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
if (cfg.flags & PTRACE_BTS_O_SIGNAL) {
|
if (cfg.flags & PTRACE_BTS_O_SIGNAL) {
|
||||||
if (!cfg.signal)
|
if (!cfg.signal)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
child->thread.bts_ovfl_signal = cfg.signal;
|
|
||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
|
context->bts_ovfl_signal = cfg.signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((cfg.flags & PTRACE_BTS_O_ALLOC) &&
|
ds_release_bts(context->tracer);
|
||||||
(cfg.size != child->bts_size)) {
|
context->tracer = NULL;
|
||||||
int error;
|
|
||||||
|
|
||||||
ptrace_bts_free_buffer(child);
|
if ((cfg.flags & PTRACE_BTS_O_ALLOC) && (cfg.size != context->size)) {
|
||||||
|
int err;
|
||||||
|
|
||||||
error = ptrace_bts_allocate_buffer(child, cfg.size);
|
free_bts_buffer(context);
|
||||||
if (error < 0)
|
if (!cfg.size)
|
||||||
return error;
|
return 0;
|
||||||
|
|
||||||
|
err = alloc_bts_buffer(context, cfg.size);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.flags & PTRACE_BTS_O_TRACE)
|
if (cfg.flags & PTRACE_BTS_O_TRACE)
|
||||||
|
@ -708,15 +814,14 @@ static int ptrace_bts_config(struct task_struct *child,
|
||||||
if (cfg.flags & PTRACE_BTS_O_SCHED)
|
if (cfg.flags & PTRACE_BTS_O_SCHED)
|
||||||
flags |= BTS_TIMESTAMPS;
|
flags |= BTS_TIMESTAMPS;
|
||||||
|
|
||||||
child->bts = ds_request_bts(child, child->bts_buffer, child->bts_size,
|
context->tracer =
|
||||||
/* ovfl = */ NULL, /* th = */ (size_t)-1,
|
ds_request_bts_task(child, context->buffer, context->size,
|
||||||
flags);
|
NULL, (size_t)-1, flags);
|
||||||
if (IS_ERR(child->bts)) {
|
if (unlikely(IS_ERR(context->tracer))) {
|
||||||
int error = PTR_ERR(child->bts);
|
int error = PTR_ERR(context->tracer);
|
||||||
|
|
||||||
ptrace_bts_free_buffer(child);
|
|
||||||
child->bts = NULL;
|
|
||||||
|
|
||||||
|
free_bts_buffer(context);
|
||||||
|
context->tracer = NULL;
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -727,20 +832,25 @@ static int ptrace_bts_status(struct task_struct *child,
|
||||||
long cfg_size,
|
long cfg_size,
|
||||||
struct ptrace_bts_config __user *ucfg)
|
struct ptrace_bts_config __user *ucfg)
|
||||||
{
|
{
|
||||||
|
struct bts_context *context;
|
||||||
const struct bts_trace *trace;
|
const struct bts_trace *trace;
|
||||||
struct ptrace_bts_config cfg;
|
struct ptrace_bts_config cfg;
|
||||||
|
|
||||||
|
context = child->bts;
|
||||||
|
if (!context)
|
||||||
|
return -ESRCH;
|
||||||
|
|
||||||
if (cfg_size < sizeof(cfg))
|
if (cfg_size < sizeof(cfg))
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
|
||||||
trace = ds_read_bts(child->bts);
|
trace = ds_read_bts(context->tracer);
|
||||||
if (!trace)
|
if (!trace)
|
||||||
return -EPERM;
|
return -ESRCH;
|
||||||
|
|
||||||
memset(&cfg, 0, sizeof(cfg));
|
memset(&cfg, 0, sizeof(cfg));
|
||||||
cfg.size = trace->ds.end - trace->ds.begin;
|
cfg.size = trace->ds.end - trace->ds.begin;
|
||||||
cfg.signal = child->thread.bts_ovfl_signal;
|
cfg.signal = context->bts_ovfl_signal;
|
||||||
cfg.bts_size = sizeof(struct bts_struct);
|
cfg.bts_size = sizeof(struct bts_struct);
|
||||||
|
|
||||||
if (cfg.signal)
|
if (cfg.signal)
|
||||||
cfg.flags |= PTRACE_BTS_O_SIGNAL;
|
cfg.flags |= PTRACE_BTS_O_SIGNAL;
|
||||||
|
@ -759,80 +869,51 @@ static int ptrace_bts_status(struct task_struct *child,
|
||||||
|
|
||||||
static int ptrace_bts_clear(struct task_struct *child)
|
static int ptrace_bts_clear(struct task_struct *child)
|
||||||
{
|
{
|
||||||
|
struct bts_context *context;
|
||||||
const struct bts_trace *trace;
|
const struct bts_trace *trace;
|
||||||
|
|
||||||
trace = ds_read_bts(child->bts);
|
context = child->bts;
|
||||||
|
if (!context)
|
||||||
|
return -ESRCH;
|
||||||
|
|
||||||
|
trace = ds_read_bts(context->tracer);
|
||||||
if (!trace)
|
if (!trace)
|
||||||
return -EPERM;
|
return -ESRCH;
|
||||||
|
|
||||||
memset(trace->ds.begin, 0, trace->ds.n * trace->ds.size);
|
memset(trace->ds.begin, 0, trace->ds.n * trace->ds.size);
|
||||||
|
|
||||||
return ds_reset_bts(child->bts);
|
return ds_reset_bts(context->tracer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ptrace_bts_size(struct task_struct *child)
|
static int ptrace_bts_size(struct task_struct *child)
|
||||||
{
|
{
|
||||||
|
struct bts_context *context;
|
||||||
const struct bts_trace *trace;
|
const struct bts_trace *trace;
|
||||||
|
|
||||||
trace = ds_read_bts(child->bts);
|
context = child->bts;
|
||||||
|
if (!context)
|
||||||
|
return -ESRCH;
|
||||||
|
|
||||||
|
trace = ds_read_bts(context->tracer);
|
||||||
if (!trace)
|
if (!trace)
|
||||||
return -EPERM;
|
return -ESRCH;
|
||||||
|
|
||||||
return (trace->ds.top - trace->ds.begin) / trace->ds.size;
|
return (trace->ds.top - trace->ds.begin) / trace->ds.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ptrace_bts_fork(struct task_struct *tsk)
|
/*
|
||||||
{
|
* Called from __ptrace_unlink() after the child has been moved back
|
||||||
tsk->bts = NULL;
|
* to its original parent.
|
||||||
tsk->bts_buffer = NULL;
|
*/
|
||||||
tsk->bts_size = 0;
|
void ptrace_bts_untrace(struct task_struct *child)
|
||||||
tsk->thread.bts_ovfl_signal = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ptrace_bts_untrace(struct task_struct *child)
|
|
||||||
{
|
{
|
||||||
if (unlikely(child->bts)) {
|
if (unlikely(child->bts)) {
|
||||||
ds_release_bts(child->bts);
|
free_bts_context(child->bts);
|
||||||
child->bts = NULL;
|
child->bts = NULL;
|
||||||
|
|
||||||
/* We cannot update total_vm and locked_vm since
|
|
||||||
child's mm is already gone. But we can reclaim the
|
|
||||||
memory. */
|
|
||||||
kfree(child->bts_buffer);
|
|
||||||
child->bts_buffer = NULL;
|
|
||||||
child->bts_size = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ptrace_bts_detach(struct task_struct *child)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Ptrace_detach() races with ptrace_untrace() in case
|
|
||||||
* the child dies and is reaped by another thread.
|
|
||||||
*
|
|
||||||
* We only do the memory accounting at this point and
|
|
||||||
* leave the buffer deallocation and the bts tracer
|
|
||||||
* release to ptrace_bts_untrace() which will be called
|
|
||||||
* later on with tasklist_lock held.
|
|
||||||
*/
|
|
||||||
release_locked_buffer(child->bts_buffer, child->bts_size);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static inline void ptrace_bts_fork(struct task_struct *tsk) {}
|
|
||||||
static inline void ptrace_bts_detach(struct task_struct *child) {}
|
|
||||||
static inline void ptrace_bts_untrace(struct task_struct *child) {}
|
|
||||||
#endif /* CONFIG_X86_PTRACE_BTS */
|
#endif /* CONFIG_X86_PTRACE_BTS */
|
||||||
|
|
||||||
void x86_ptrace_fork(struct task_struct *child, unsigned long clone_flags)
|
|
||||||
{
|
|
||||||
ptrace_bts_fork(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
void x86_ptrace_untrace(struct task_struct *child)
|
|
||||||
{
|
|
||||||
ptrace_bts_untrace(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called by kernel/ptrace.c when detaching..
|
* Called by kernel/ptrace.c when detaching..
|
||||||
*
|
*
|
||||||
|
@ -844,7 +925,6 @@ void ptrace_disable(struct task_struct *child)
|
||||||
#ifdef TIF_SYSCALL_EMU
|
#ifdef TIF_SYSCALL_EMU
|
||||||
clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
|
clear_tsk_thread_flag(child, TIF_SYSCALL_EMU);
|
||||||
#endif
|
#endif
|
||||||
ptrace_bts_detach(child);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined CONFIG_X86_32 || defined CONFIG_IA32_EMULATION
|
#if defined CONFIG_X86_32 || defined CONFIG_IA32_EMULATION
|
||||||
|
|
|
@ -19,6 +19,7 @@ struct anon_vma;
|
||||||
struct file_ra_state;
|
struct file_ra_state;
|
||||||
struct user_struct;
|
struct user_struct;
|
||||||
struct writeback_control;
|
struct writeback_control;
|
||||||
|
struct rlimit;
|
||||||
|
|
||||||
#ifndef CONFIG_DISCONTIGMEM /* Don't use mapnrs, do it properly */
|
#ifndef CONFIG_DISCONTIGMEM /* Don't use mapnrs, do it properly */
|
||||||
extern unsigned long max_mapnr;
|
extern unsigned long max_mapnr;
|
||||||
|
@ -1319,8 +1320,8 @@ int vmemmap_populate_basepages(struct page *start_page,
|
||||||
int vmemmap_populate(struct page *start_page, unsigned long pages, int node);
|
int vmemmap_populate(struct page *start_page, unsigned long pages, int node);
|
||||||
void vmemmap_populate_print_last(void);
|
void vmemmap_populate_print_last(void);
|
||||||
|
|
||||||
extern void *alloc_locked_buffer(size_t size);
|
extern int account_locked_memory(struct mm_struct *mm, struct rlimit *rlim,
|
||||||
extern void free_locked_buffer(void *buffer, size_t size);
|
size_t size);
|
||||||
extern void release_locked_buffer(void *buffer, size_t size);
|
extern void refund_locked_memory(struct mm_struct *mm, size_t size);
|
||||||
#endif /* __KERNEL__ */
|
#endif /* __KERNEL__ */
|
||||||
#endif /* _LINUX_MM_H */
|
#endif /* _LINUX_MM_H */
|
||||||
|
|
|
@ -95,7 +95,6 @@ extern void __ptrace_link(struct task_struct *child,
|
||||||
struct task_struct *new_parent);
|
struct task_struct *new_parent);
|
||||||
extern void __ptrace_unlink(struct task_struct *child);
|
extern void __ptrace_unlink(struct task_struct *child);
|
||||||
extern void exit_ptrace(struct task_struct *tracer);
|
extern void exit_ptrace(struct task_struct *tracer);
|
||||||
extern void ptrace_fork(struct task_struct *task, unsigned long clone_flags);
|
|
||||||
#define PTRACE_MODE_READ 1
|
#define PTRACE_MODE_READ 1
|
||||||
#define PTRACE_MODE_ATTACH 2
|
#define PTRACE_MODE_ATTACH 2
|
||||||
/* Returns 0 on success, -errno on denial. */
|
/* Returns 0 on success, -errno on denial. */
|
||||||
|
@ -327,15 +326,6 @@ static inline void user_enable_block_step(struct task_struct *task)
|
||||||
#define arch_ptrace_untrace(task) do { } while (0)
|
#define arch_ptrace_untrace(task) do { } while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef arch_ptrace_fork
|
|
||||||
/*
|
|
||||||
* Do machine-specific work to initialize a new task.
|
|
||||||
*
|
|
||||||
* This is called from copy_process().
|
|
||||||
*/
|
|
||||||
#define arch_ptrace_fork(child, clone_flags) do { } while (0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern int task_current_syscall(struct task_struct *target, long *callno,
|
extern int task_current_syscall(struct task_struct *target, long *callno,
|
||||||
unsigned long args[6], unsigned int maxargs,
|
unsigned long args[6], unsigned int maxargs,
|
||||||
unsigned long *sp, unsigned long *pc);
|
unsigned long *sp, unsigned long *pc);
|
||||||
|
|
|
@ -96,8 +96,8 @@ struct exec_domain;
|
||||||
struct futex_pi_state;
|
struct futex_pi_state;
|
||||||
struct robust_list_head;
|
struct robust_list_head;
|
||||||
struct bio;
|
struct bio;
|
||||||
struct bts_tracer;
|
|
||||||
struct fs_struct;
|
struct fs_struct;
|
||||||
|
struct bts_context;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* List of flags we want to share for kernel threads,
|
* List of flags we want to share for kernel threads,
|
||||||
|
@ -1209,18 +1209,11 @@ struct task_struct {
|
||||||
struct list_head ptraced;
|
struct list_head ptraced;
|
||||||
struct list_head ptrace_entry;
|
struct list_head ptrace_entry;
|
||||||
|
|
||||||
#ifdef CONFIG_X86_PTRACE_BTS
|
|
||||||
/*
|
/*
|
||||||
* This is the tracer handle for the ptrace BTS extension.
|
* This is the tracer handle for the ptrace BTS extension.
|
||||||
* This field actually belongs to the ptracer task.
|
* This field actually belongs to the ptracer task.
|
||||||
*/
|
*/
|
||||||
struct bts_tracer *bts;
|
struct bts_context *bts;
|
||||||
/*
|
|
||||||
* The buffer to hold the BTS data.
|
|
||||||
*/
|
|
||||||
void *bts_buffer;
|
|
||||||
size_t bts_size;
|
|
||||||
#endif /* CONFIG_X86_PTRACE_BTS */
|
|
||||||
|
|
||||||
/* PID/PID hash table linkage. */
|
/* PID/PID hash table linkage. */
|
||||||
struct pid_link pids[PIDTYPE_MAX];
|
struct pid_link pids[PIDTYPE_MAX];
|
||||||
|
@ -2003,8 +1996,10 @@ extern void set_task_comm(struct task_struct *tsk, char *from);
|
||||||
extern char *get_task_comm(char *to, struct task_struct *tsk);
|
extern char *get_task_comm(char *to, struct task_struct *tsk);
|
||||||
|
|
||||||
#ifdef CONFIG_SMP
|
#ifdef CONFIG_SMP
|
||||||
|
extern void wait_task_context_switch(struct task_struct *p);
|
||||||
extern unsigned long wait_task_inactive(struct task_struct *, long match_state);
|
extern unsigned long wait_task_inactive(struct task_struct *, long match_state);
|
||||||
#else
|
#else
|
||||||
|
static inline void wait_task_context_switch(struct task_struct *p) {}
|
||||||
static inline unsigned long wait_task_inactive(struct task_struct *p,
|
static inline unsigned long wait_task_inactive(struct task_struct *p,
|
||||||
long match_state)
|
long match_state)
|
||||||
{
|
{
|
||||||
|
|
|
@ -93,6 +93,7 @@ obj-$(CONFIG_LATENCYTOP) += latencytop.o
|
||||||
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o
|
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o
|
||||||
obj-$(CONFIG_FUNCTION_TRACER) += trace/
|
obj-$(CONFIG_FUNCTION_TRACER) += trace/
|
||||||
obj-$(CONFIG_TRACING) += trace/
|
obj-$(CONFIG_TRACING) += trace/
|
||||||
|
obj-$(CONFIG_X86_DS) += trace/
|
||||||
obj-$(CONFIG_SMP) += sched_cpupri.o
|
obj-$(CONFIG_SMP) += sched_cpupri.o
|
||||||
obj-$(CONFIG_SLOW_WORK) += slow-work.o
|
obj-$(CONFIG_SLOW_WORK) += slow-work.o
|
||||||
|
|
||||||
|
|
|
@ -1088,8 +1088,8 @@ static struct task_struct *copy_process(unsigned long clone_flags,
|
||||||
#ifdef CONFIG_DEBUG_MUTEXES
|
#ifdef CONFIG_DEBUG_MUTEXES
|
||||||
p->blocked_on = NULL; /* not blocked yet */
|
p->blocked_on = NULL; /* not blocked yet */
|
||||||
#endif
|
#endif
|
||||||
if (unlikely(current->ptrace))
|
|
||||||
ptrace_fork(p, clone_flags);
|
p->bts = NULL;
|
||||||
|
|
||||||
/* Perform scheduler related setup. Assign this task to a CPU. */
|
/* Perform scheduler related setup. Assign this task to a CPU. */
|
||||||
sched_fork(p, clone_flags);
|
sched_fork(p, clone_flags);
|
||||||
|
|
|
@ -24,16 +24,6 @@
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize a new task whose father had been ptraced.
|
|
||||||
*
|
|
||||||
* Called from copy_process().
|
|
||||||
*/
|
|
||||||
void ptrace_fork(struct task_struct *child, unsigned long clone_flags)
|
|
||||||
{
|
|
||||||
arch_ptrace_fork(child, clone_flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ptrace a task: make the debugger its new parent and
|
* ptrace a task: make the debugger its new parent and
|
||||||
* move it to the ptrace list.
|
* move it to the ptrace list.
|
||||||
|
|
|
@ -2010,6 +2010,49 @@ migrate_task(struct task_struct *p, int dest_cpu, struct migration_req *req)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* wait_task_context_switch - wait for a thread to complete at least one
|
||||||
|
* context switch.
|
||||||
|
*
|
||||||
|
* @p must not be current.
|
||||||
|
*/
|
||||||
|
void wait_task_context_switch(struct task_struct *p)
|
||||||
|
{
|
||||||
|
unsigned long nvcsw, nivcsw, flags;
|
||||||
|
int running;
|
||||||
|
struct rq *rq;
|
||||||
|
|
||||||
|
nvcsw = p->nvcsw;
|
||||||
|
nivcsw = p->nivcsw;
|
||||||
|
for (;;) {
|
||||||
|
/*
|
||||||
|
* The runqueue is assigned before the actual context
|
||||||
|
* switch. We need to take the runqueue lock.
|
||||||
|
*
|
||||||
|
* We could check initially without the lock but it is
|
||||||
|
* very likely that we need to take the lock in every
|
||||||
|
* iteration.
|
||||||
|
*/
|
||||||
|
rq = task_rq_lock(p, &flags);
|
||||||
|
running = task_running(rq, p);
|
||||||
|
task_rq_unlock(rq, &flags);
|
||||||
|
|
||||||
|
if (likely(!running))
|
||||||
|
break;
|
||||||
|
/*
|
||||||
|
* The switch count is incremented before the actual
|
||||||
|
* context switch. We thus wait for two switches to be
|
||||||
|
* sure at least one completed.
|
||||||
|
*/
|
||||||
|
if ((p->nvcsw - nvcsw) > 1)
|
||||||
|
break;
|
||||||
|
if ((p->nivcsw - nivcsw) > 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
cpu_relax();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* wait_task_inactive - wait for a thread to unschedule.
|
* wait_task_inactive - wait for a thread to unschedule.
|
||||||
*
|
*
|
||||||
|
|
|
@ -15,12 +15,17 @@ ifdef CONFIG_TRACING_BRANCHES
|
||||||
KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING
|
KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make the trace clocks available generally: it's infrastructure
|
||||||
|
# relied on by ptrace for example:
|
||||||
|
#
|
||||||
|
obj-y += trace_clock.o
|
||||||
|
|
||||||
obj-$(CONFIG_FUNCTION_TRACER) += libftrace.o
|
obj-$(CONFIG_FUNCTION_TRACER) += libftrace.o
|
||||||
obj-$(CONFIG_RING_BUFFER) += ring_buffer.o
|
obj-$(CONFIG_RING_BUFFER) += ring_buffer.o
|
||||||
obj-$(CONFIG_RING_BUFFER_BENCHMARK) += ring_buffer_benchmark.o
|
obj-$(CONFIG_RING_BUFFER_BENCHMARK) += ring_buffer_benchmark.o
|
||||||
|
|
||||||
obj-$(CONFIG_TRACING) += trace.o
|
obj-$(CONFIG_TRACING) += trace.o
|
||||||
obj-$(CONFIG_TRACING) += trace_clock.o
|
|
||||||
obj-$(CONFIG_TRACING) += trace_output.o
|
obj-$(CONFIG_TRACING) += trace_output.o
|
||||||
obj-$(CONFIG_TRACING) += trace_stat.o
|
obj-$(CONFIG_TRACING) += trace_stat.o
|
||||||
obj-$(CONFIG_TRACING) += trace_printk.o
|
obj-$(CONFIG_TRACING) += trace_printk.o
|
||||||
|
|
|
@ -538,6 +538,8 @@ extern int trace_selftest_startup_sysprof(struct tracer *trace,
|
||||||
struct trace_array *tr);
|
struct trace_array *tr);
|
||||||
extern int trace_selftest_startup_branch(struct tracer *trace,
|
extern int trace_selftest_startup_branch(struct tracer *trace,
|
||||||
struct trace_array *tr);
|
struct trace_array *tr);
|
||||||
|
extern int trace_selftest_startup_hw_branches(struct tracer *trace,
|
||||||
|
struct trace_array *tr);
|
||||||
#endif /* CONFIG_FTRACE_STARTUP_TEST */
|
#endif /* CONFIG_FTRACE_STARTUP_TEST */
|
||||||
|
|
||||||
extern void *head_page(struct trace_array_cpu *data);
|
extern void *head_page(struct trace_array_cpu *data);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* h/w branch tracer for x86 based on bts
|
* h/w branch tracer for x86 based on BTS
|
||||||
*
|
*
|
||||||
* Copyright (C) 2008-2009 Intel Corporation.
|
* Copyright (C) 2008-2009 Intel Corporation.
|
||||||
* Markus Metzger <markus.t.metzger@gmail.com>, 2008-2009
|
* Markus Metzger <markus.t.metzger@gmail.com>, 2008-2009
|
||||||
*/
|
*/
|
||||||
#include <linux/spinlock.h>
|
|
||||||
#include <linux/kallsyms.h>
|
#include <linux/kallsyms.h>
|
||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
#include <linux/ftrace.h>
|
#include <linux/ftrace.h>
|
||||||
|
@ -15,110 +14,119 @@
|
||||||
|
|
||||||
#include <asm/ds.h>
|
#include <asm/ds.h>
|
||||||
|
|
||||||
#include "trace.h"
|
|
||||||
#include "trace_output.h"
|
#include "trace_output.h"
|
||||||
|
#include "trace.h"
|
||||||
|
|
||||||
|
|
||||||
#define SIZEOF_BTS (1 << 13)
|
#define BTS_BUFFER_SIZE (1 << 13)
|
||||||
|
|
||||||
/*
|
|
||||||
* The tracer lock protects the below per-cpu tracer array.
|
|
||||||
* It needs to be held to:
|
|
||||||
* - start tracing on all cpus
|
|
||||||
* - stop tracing on all cpus
|
|
||||||
* - start tracing on a single hotplug cpu
|
|
||||||
* - stop tracing on a single hotplug cpu
|
|
||||||
* - read the trace from all cpus
|
|
||||||
* - read the trace from a single cpu
|
|
||||||
*/
|
|
||||||
static DEFINE_SPINLOCK(bts_tracer_lock);
|
|
||||||
static DEFINE_PER_CPU(struct bts_tracer *, tracer);
|
static DEFINE_PER_CPU(struct bts_tracer *, tracer);
|
||||||
static DEFINE_PER_CPU(unsigned char[SIZEOF_BTS], buffer);
|
static DEFINE_PER_CPU(unsigned char[BTS_BUFFER_SIZE], buffer);
|
||||||
|
|
||||||
#define this_tracer per_cpu(tracer, smp_processor_id())
|
#define this_tracer per_cpu(tracer, smp_processor_id())
|
||||||
#define this_buffer per_cpu(buffer, smp_processor_id())
|
|
||||||
|
|
||||||
static int __read_mostly trace_hw_branches_enabled;
|
static int trace_hw_branches_enabled __read_mostly;
|
||||||
|
static int trace_hw_branches_suspended __read_mostly;
|
||||||
static struct trace_array *hw_branch_trace __read_mostly;
|
static struct trace_array *hw_branch_trace __read_mostly;
|
||||||
|
|
||||||
|
|
||||||
/*
|
static void bts_trace_init_cpu(int cpu)
|
||||||
* Start tracing on the current cpu.
|
|
||||||
* The argument is ignored.
|
|
||||||
*
|
|
||||||
* pre: bts_tracer_lock must be locked.
|
|
||||||
*/
|
|
||||||
static void bts_trace_start_cpu(void *arg)
|
|
||||||
{
|
{
|
||||||
if (this_tracer)
|
per_cpu(tracer, cpu) =
|
||||||
ds_release_bts(this_tracer);
|
ds_request_bts_cpu(cpu, per_cpu(buffer, cpu), BTS_BUFFER_SIZE,
|
||||||
|
NULL, (size_t)-1, BTS_KERNEL);
|
||||||
|
|
||||||
this_tracer =
|
if (IS_ERR(per_cpu(tracer, cpu)))
|
||||||
ds_request_bts(/* task = */ NULL, this_buffer, SIZEOF_BTS,
|
per_cpu(tracer, cpu) = NULL;
|
||||||
/* ovfl = */ NULL, /* th = */ (size_t)-1,
|
}
|
||||||
BTS_KERNEL);
|
|
||||||
if (IS_ERR(this_tracer)) {
|
static int bts_trace_init(struct trace_array *tr)
|
||||||
this_tracer = NULL;
|
{
|
||||||
return;
|
int cpu;
|
||||||
|
|
||||||
|
hw_branch_trace = tr;
|
||||||
|
trace_hw_branches_enabled = 0;
|
||||||
|
|
||||||
|
get_online_cpus();
|
||||||
|
for_each_online_cpu(cpu) {
|
||||||
|
bts_trace_init_cpu(cpu);
|
||||||
|
|
||||||
|
if (likely(per_cpu(tracer, cpu)))
|
||||||
|
trace_hw_branches_enabled = 1;
|
||||||
}
|
}
|
||||||
|
trace_hw_branches_suspended = 0;
|
||||||
|
put_online_cpus();
|
||||||
|
|
||||||
|
/* If we could not enable tracing on a single cpu, we fail. */
|
||||||
|
return trace_hw_branches_enabled ? 0 : -EOPNOTSUPP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bts_trace_reset(struct trace_array *tr)
|
||||||
|
{
|
||||||
|
int cpu;
|
||||||
|
|
||||||
|
get_online_cpus();
|
||||||
|
for_each_online_cpu(cpu) {
|
||||||
|
if (likely(per_cpu(tracer, cpu))) {
|
||||||
|
ds_release_bts(per_cpu(tracer, cpu));
|
||||||
|
per_cpu(tracer, cpu) = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trace_hw_branches_enabled = 0;
|
||||||
|
trace_hw_branches_suspended = 0;
|
||||||
|
put_online_cpus();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bts_trace_start(struct trace_array *tr)
|
static void bts_trace_start(struct trace_array *tr)
|
||||||
{
|
{
|
||||||
spin_lock(&bts_tracer_lock);
|
int cpu;
|
||||||
|
|
||||||
on_each_cpu(bts_trace_start_cpu, NULL, 1);
|
get_online_cpus();
|
||||||
trace_hw_branches_enabled = 1;
|
for_each_online_cpu(cpu)
|
||||||
|
if (likely(per_cpu(tracer, cpu)))
|
||||||
spin_unlock(&bts_tracer_lock);
|
ds_resume_bts(per_cpu(tracer, cpu));
|
||||||
}
|
trace_hw_branches_suspended = 0;
|
||||||
|
put_online_cpus();
|
||||||
/*
|
|
||||||
* Stop tracing on the current cpu.
|
|
||||||
* The argument is ignored.
|
|
||||||
*
|
|
||||||
* pre: bts_tracer_lock must be locked.
|
|
||||||
*/
|
|
||||||
static void bts_trace_stop_cpu(void *arg)
|
|
||||||
{
|
|
||||||
if (this_tracer) {
|
|
||||||
ds_release_bts(this_tracer);
|
|
||||||
this_tracer = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bts_trace_stop(struct trace_array *tr)
|
static void bts_trace_stop(struct trace_array *tr)
|
||||||
{
|
{
|
||||||
spin_lock(&bts_tracer_lock);
|
int cpu;
|
||||||
|
|
||||||
trace_hw_branches_enabled = 0;
|
get_online_cpus();
|
||||||
on_each_cpu(bts_trace_stop_cpu, NULL, 1);
|
for_each_online_cpu(cpu)
|
||||||
|
if (likely(per_cpu(tracer, cpu)))
|
||||||
spin_unlock(&bts_tracer_lock);
|
ds_suspend_bts(per_cpu(tracer, cpu));
|
||||||
|
trace_hw_branches_suspended = 1;
|
||||||
|
put_online_cpus();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __cpuinit bts_hotcpu_handler(struct notifier_block *nfb,
|
static int __cpuinit bts_hotcpu_handler(struct notifier_block *nfb,
|
||||||
unsigned long action, void *hcpu)
|
unsigned long action, void *hcpu)
|
||||||
{
|
{
|
||||||
unsigned int cpu = (unsigned long)hcpu;
|
int cpu = (long)hcpu;
|
||||||
|
|
||||||
spin_lock(&bts_tracer_lock);
|
|
||||||
|
|
||||||
if (!trace_hw_branches_enabled)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case CPU_ONLINE:
|
case CPU_ONLINE:
|
||||||
case CPU_DOWN_FAILED:
|
case CPU_DOWN_FAILED:
|
||||||
smp_call_function_single(cpu, bts_trace_start_cpu, NULL, 1);
|
/* The notification is sent with interrupts enabled. */
|
||||||
|
if (trace_hw_branches_enabled) {
|
||||||
|
bts_trace_init_cpu(cpu);
|
||||||
|
|
||||||
|
if (trace_hw_branches_suspended &&
|
||||||
|
likely(per_cpu(tracer, cpu)))
|
||||||
|
ds_suspend_bts(per_cpu(tracer, cpu));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CPU_DOWN_PREPARE:
|
case CPU_DOWN_PREPARE:
|
||||||
smp_call_function_single(cpu, bts_trace_stop_cpu, NULL, 1);
|
/* The notification is sent with interrupts enabled. */
|
||||||
break;
|
if (likely(per_cpu(tracer, cpu))) {
|
||||||
|
ds_release_bts(per_cpu(tracer, cpu));
|
||||||
|
per_cpu(tracer, cpu) = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
|
||||||
spin_unlock(&bts_tracer_lock);
|
|
||||||
return NOTIFY_DONE;
|
return NOTIFY_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,20 +134,6 @@ static struct notifier_block bts_hotcpu_notifier __cpuinitdata = {
|
||||||
.notifier_call = bts_hotcpu_handler
|
.notifier_call = bts_hotcpu_handler
|
||||||
};
|
};
|
||||||
|
|
||||||
static int bts_trace_init(struct trace_array *tr)
|
|
||||||
{
|
|
||||||
hw_branch_trace = tr;
|
|
||||||
|
|
||||||
bts_trace_start(tr);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bts_trace_reset(struct trace_array *tr)
|
|
||||||
{
|
|
||||||
bts_trace_stop(tr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bts_trace_print_header(struct seq_file *m)
|
static void bts_trace_print_header(struct seq_file *m)
|
||||||
{
|
{
|
||||||
seq_puts(m, "# CPU# TO <- FROM\n");
|
seq_puts(m, "# CPU# TO <- FROM\n");
|
||||||
|
@ -147,10 +141,10 @@ static void bts_trace_print_header(struct seq_file *m)
|
||||||
|
|
||||||
static enum print_line_t bts_trace_print_line(struct trace_iterator *iter)
|
static enum print_line_t bts_trace_print_line(struct trace_iterator *iter)
|
||||||
{
|
{
|
||||||
|
unsigned long symflags = TRACE_ITER_SYM_OFFSET;
|
||||||
struct trace_entry *entry = iter->ent;
|
struct trace_entry *entry = iter->ent;
|
||||||
struct trace_seq *seq = &iter->seq;
|
struct trace_seq *seq = &iter->seq;
|
||||||
struct hw_branch_entry *it;
|
struct hw_branch_entry *it;
|
||||||
unsigned long symflags = TRACE_ITER_SYM_OFFSET;
|
|
||||||
|
|
||||||
trace_assign_type(it, entry);
|
trace_assign_type(it, entry);
|
||||||
|
|
||||||
|
@ -226,11 +220,11 @@ static void trace_bts_at(const struct bts_trace *trace, void *at)
|
||||||
/*
|
/*
|
||||||
* Collect the trace on the current cpu and write it into the ftrace buffer.
|
* Collect the trace on the current cpu and write it into the ftrace buffer.
|
||||||
*
|
*
|
||||||
* pre: bts_tracer_lock must be locked
|
* pre: tracing must be suspended on the current cpu
|
||||||
*/
|
*/
|
||||||
static void trace_bts_cpu(void *arg)
|
static void trace_bts_cpu(void *arg)
|
||||||
{
|
{
|
||||||
struct trace_array *tr = (struct trace_array *) arg;
|
struct trace_array *tr = (struct trace_array *)arg;
|
||||||
const struct bts_trace *trace;
|
const struct bts_trace *trace;
|
||||||
unsigned char *at;
|
unsigned char *at;
|
||||||
|
|
||||||
|
@ -243,10 +237,9 @@ static void trace_bts_cpu(void *arg)
|
||||||
if (unlikely(!this_tracer))
|
if (unlikely(!this_tracer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ds_suspend_bts(this_tracer);
|
|
||||||
trace = ds_read_bts(this_tracer);
|
trace = ds_read_bts(this_tracer);
|
||||||
if (!trace)
|
if (!trace)
|
||||||
goto out;
|
return;
|
||||||
|
|
||||||
for (at = trace->ds.top; (void *)at < trace->ds.end;
|
for (at = trace->ds.top; (void *)at < trace->ds.end;
|
||||||
at += trace->ds.size)
|
at += trace->ds.size)
|
||||||
|
@ -255,18 +248,27 @@ static void trace_bts_cpu(void *arg)
|
||||||
for (at = trace->ds.begin; (void *)at < trace->ds.top;
|
for (at = trace->ds.begin; (void *)at < trace->ds.top;
|
||||||
at += trace->ds.size)
|
at += trace->ds.size)
|
||||||
trace_bts_at(trace, at);
|
trace_bts_at(trace, at);
|
||||||
|
|
||||||
out:
|
|
||||||
ds_resume_bts(this_tracer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void trace_bts_prepare(struct trace_iterator *iter)
|
static void trace_bts_prepare(struct trace_iterator *iter)
|
||||||
{
|
{
|
||||||
spin_lock(&bts_tracer_lock);
|
int cpu;
|
||||||
|
|
||||||
|
get_online_cpus();
|
||||||
|
for_each_online_cpu(cpu)
|
||||||
|
if (likely(per_cpu(tracer, cpu)))
|
||||||
|
ds_suspend_bts(per_cpu(tracer, cpu));
|
||||||
|
/*
|
||||||
|
* We need to collect the trace on the respective cpu since ftrace
|
||||||
|
* implicitly adds the record for the current cpu.
|
||||||
|
* Once that is more flexible, we could collect the data from any cpu.
|
||||||
|
*/
|
||||||
on_each_cpu(trace_bts_cpu, iter->tr, 1);
|
on_each_cpu(trace_bts_cpu, iter->tr, 1);
|
||||||
|
|
||||||
spin_unlock(&bts_tracer_lock);
|
for_each_online_cpu(cpu)
|
||||||
|
if (likely(per_cpu(tracer, cpu)))
|
||||||
|
ds_resume_bts(per_cpu(tracer, cpu));
|
||||||
|
put_online_cpus();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void trace_bts_close(struct trace_iterator *iter)
|
static void trace_bts_close(struct trace_iterator *iter)
|
||||||
|
@ -276,11 +278,11 @@ static void trace_bts_close(struct trace_iterator *iter)
|
||||||
|
|
||||||
void trace_hw_branch_oops(void)
|
void trace_hw_branch_oops(void)
|
||||||
{
|
{
|
||||||
spin_lock(&bts_tracer_lock);
|
if (this_tracer) {
|
||||||
|
ds_suspend_bts_noirq(this_tracer);
|
||||||
trace_bts_cpu(hw_branch_trace);
|
trace_bts_cpu(hw_branch_trace);
|
||||||
|
ds_resume_bts_noirq(this_tracer);
|
||||||
spin_unlock(&bts_tracer_lock);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct tracer bts_tracer __read_mostly =
|
struct tracer bts_tracer __read_mostly =
|
||||||
|
@ -293,7 +295,10 @@ struct tracer bts_tracer __read_mostly =
|
||||||
.start = bts_trace_start,
|
.start = bts_trace_start,
|
||||||
.stop = bts_trace_stop,
|
.stop = bts_trace_stop,
|
||||||
.open = trace_bts_prepare,
|
.open = trace_bts_prepare,
|
||||||
.close = trace_bts_close
|
.close = trace_bts_close,
|
||||||
|
#ifdef CONFIG_FTRACE_SELFTEST
|
||||||
|
.selftest = trace_selftest_startup_hw_branches,
|
||||||
|
#endif /* CONFIG_FTRACE_SELFTEST */
|
||||||
};
|
};
|
||||||
|
|
||||||
__init static int init_bts_trace(void)
|
__init static int init_bts_trace(void)
|
||||||
|
|
|
@ -16,6 +16,7 @@ static inline int trace_valid_entry(struct trace_entry *entry)
|
||||||
case TRACE_BRANCH:
|
case TRACE_BRANCH:
|
||||||
case TRACE_GRAPH_ENT:
|
case TRACE_GRAPH_ENT:
|
||||||
case TRACE_GRAPH_RET:
|
case TRACE_GRAPH_RET:
|
||||||
|
case TRACE_HW_BRANCHES:
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -188,6 +189,7 @@ int trace_selftest_startup_dynamic_tracing(struct tracer *trace,
|
||||||
#else
|
#else
|
||||||
# define trace_selftest_startup_dynamic_tracing(trace, tr, func) ({ 0; })
|
# define trace_selftest_startup_dynamic_tracing(trace, tr, func) ({ 0; })
|
||||||
#endif /* CONFIG_DYNAMIC_FTRACE */
|
#endif /* CONFIG_DYNAMIC_FTRACE */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Simple verification test of ftrace function tracer.
|
* Simple verification test of ftrace function tracer.
|
||||||
* Enable ftrace, sleep 1/10 second, and then read the trace
|
* Enable ftrace, sleep 1/10 second, and then read the trace
|
||||||
|
@ -749,3 +751,59 @@ trace_selftest_startup_branch(struct tracer *trace, struct trace_array *tr)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_BRANCH_TRACER */
|
#endif /* CONFIG_BRANCH_TRACER */
|
||||||
|
|
||||||
|
#ifdef CONFIG_HW_BRANCH_TRACER
|
||||||
|
int
|
||||||
|
trace_selftest_startup_hw_branches(struct tracer *trace,
|
||||||
|
struct trace_array *tr)
|
||||||
|
{
|
||||||
|
struct trace_iterator *iter;
|
||||||
|
struct tracer tracer;
|
||||||
|
unsigned long count;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!trace->open) {
|
||||||
|
printk(KERN_CONT "missing open function...");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = tracer_init(trace, tr);
|
||||||
|
if (ret) {
|
||||||
|
warn_failed_init_tracer(trace, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The hw-branch tracer needs to collect the trace from the various
|
||||||
|
* cpu trace buffers - before tracing is stopped.
|
||||||
|
*/
|
||||||
|
iter = kzalloc(sizeof(*iter), GFP_KERNEL);
|
||||||
|
if (!iter)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
memcpy(&tracer, trace, sizeof(tracer));
|
||||||
|
|
||||||
|
iter->trace = &tracer;
|
||||||
|
iter->tr = tr;
|
||||||
|
iter->pos = -1;
|
||||||
|
mutex_init(&iter->mutex);
|
||||||
|
|
||||||
|
trace->open(iter);
|
||||||
|
|
||||||
|
mutex_destroy(&iter->mutex);
|
||||||
|
kfree(iter);
|
||||||
|
|
||||||
|
tracing_stop();
|
||||||
|
|
||||||
|
ret = trace_test_buffer(tr, &count);
|
||||||
|
trace->reset(tr);
|
||||||
|
tracing_start();
|
||||||
|
|
||||||
|
if (!ret && !count) {
|
||||||
|
printk(KERN_CONT "no entries found..");
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_HW_BRANCH_TRACER */
|
||||||
|
|
51
mm/mlock.c
51
mm/mlock.c
|
@ -629,52 +629,43 @@ void user_shm_unlock(size_t size, struct user_struct *user)
|
||||||
free_uid(user);
|
free_uid(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *alloc_locked_buffer(size_t size)
|
int account_locked_memory(struct mm_struct *mm, struct rlimit *rlim,
|
||||||
|
size_t size)
|
||||||
{
|
{
|
||||||
unsigned long rlim, vm, pgsz;
|
unsigned long lim, vm, pgsz;
|
||||||
void *buffer = NULL;
|
int error = -ENOMEM;
|
||||||
|
|
||||||
pgsz = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
pgsz = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
||||||
|
|
||||||
down_write(¤t->mm->mmap_sem);
|
down_write(&mm->mmap_sem);
|
||||||
|
|
||||||
rlim = current->signal->rlim[RLIMIT_AS].rlim_cur >> PAGE_SHIFT;
|
lim = rlim[RLIMIT_AS].rlim_cur >> PAGE_SHIFT;
|
||||||
vm = current->mm->total_vm + pgsz;
|
vm = mm->total_vm + pgsz;
|
||||||
if (rlim < vm)
|
if (lim < vm)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
rlim = current->signal->rlim[RLIMIT_MEMLOCK].rlim_cur >> PAGE_SHIFT;
|
lim = rlim[RLIMIT_MEMLOCK].rlim_cur >> PAGE_SHIFT;
|
||||||
vm = current->mm->locked_vm + pgsz;
|
vm = mm->locked_vm + pgsz;
|
||||||
if (rlim < vm)
|
if (lim < vm)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
buffer = kzalloc(size, GFP_KERNEL);
|
mm->total_vm += pgsz;
|
||||||
if (!buffer)
|
mm->locked_vm += pgsz;
|
||||||
goto out;
|
|
||||||
|
|
||||||
current->mm->total_vm += pgsz;
|
|
||||||
current->mm->locked_vm += pgsz;
|
|
||||||
|
|
||||||
|
error = 0;
|
||||||
out:
|
out:
|
||||||
up_write(¤t->mm->mmap_sem);
|
up_write(&mm->mmap_sem);
|
||||||
return buffer;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
void release_locked_buffer(void *buffer, size_t size)
|
void refund_locked_memory(struct mm_struct *mm, size_t size)
|
||||||
{
|
{
|
||||||
unsigned long pgsz = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
unsigned long pgsz = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
||||||
|
|
||||||
down_write(¤t->mm->mmap_sem);
|
down_write(&mm->mmap_sem);
|
||||||
|
|
||||||
current->mm->total_vm -= pgsz;
|
mm->total_vm -= pgsz;
|
||||||
current->mm->locked_vm -= pgsz;
|
mm->locked_vm -= pgsz;
|
||||||
|
|
||||||
up_write(¤t->mm->mmap_sem);
|
up_write(&mm->mmap_sem);
|
||||||
}
|
|
||||||
|
|
||||||
void free_locked_buffer(void *buffer, size_t size)
|
|
||||||
{
|
|
||||||
release_locked_buffer(buffer, size);
|
|
||||||
|
|
||||||
kfree(buffer);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue