feef47d0cb
The breakpoint generic layer assumes that archs always know in advance the static number of address registers available to host breakpoints through the HBP_NUM macro. However this is not true for every archs. For example Arm needs to get this information dynamically to handle the compatiblity between different versions. To solve this, this patch proposes to drop the static HBP_NUM macro and let the arch provide the number of available slots through a new hw_breakpoint_slots() function. For archs that have CONFIG_HAVE_MIXED_BREAKPOINTS_REGS selected, it will be called once as the number of registers fits for instruction and data breakpoints together. For the others it will be called first to get the number of instruction breakpoint registers and another time to get the data breakpoint registers, the targeted type is given as a parameter of hw_breakpoint_slots(). Reported-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com> Acked-by: Paul Mundt <lethal@linux-sh.org> Cc: Mahesh Salgaonkar <mahesh@linux.vnet.ibm.com> Cc: K. Prasad <prasad@linux.vnet.ibm.com> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Jason Wessel <jason.wessel@windriver.com> Cc: Ingo Molnar <mingo@elte.hu>
147 lines
4.1 KiB
C
147 lines
4.1 KiB
C
#ifndef _LINUX_HW_BREAKPOINT_H
|
|
#define _LINUX_HW_BREAKPOINT_H
|
|
|
|
enum {
|
|
HW_BREAKPOINT_LEN_1 = 1,
|
|
HW_BREAKPOINT_LEN_2 = 2,
|
|
HW_BREAKPOINT_LEN_4 = 4,
|
|
HW_BREAKPOINT_LEN_8 = 8,
|
|
};
|
|
|
|
enum {
|
|
HW_BREAKPOINT_EMPTY = 0,
|
|
HW_BREAKPOINT_R = 1,
|
|
HW_BREAKPOINT_W = 2,
|
|
HW_BREAKPOINT_RW = HW_BREAKPOINT_R | HW_BREAKPOINT_W,
|
|
HW_BREAKPOINT_X = 4,
|
|
HW_BREAKPOINT_INVALID = HW_BREAKPOINT_RW | HW_BREAKPOINT_X,
|
|
};
|
|
|
|
enum bp_type_idx {
|
|
TYPE_INST = 0,
|
|
#ifdef CONFIG_HAVE_MIXED_BREAKPOINTS_REGS
|
|
TYPE_DATA = 0,
|
|
#else
|
|
TYPE_DATA = 1,
|
|
#endif
|
|
TYPE_MAX
|
|
};
|
|
|
|
#ifdef __KERNEL__
|
|
|
|
#include <linux/perf_event.h>
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
|
|
|
static inline void hw_breakpoint_init(struct perf_event_attr *attr)
|
|
{
|
|
memset(attr, 0, sizeof(*attr));
|
|
|
|
attr->type = PERF_TYPE_BREAKPOINT;
|
|
attr->size = sizeof(*attr);
|
|
/*
|
|
* As it's for in-kernel or ptrace use, we want it to be pinned
|
|
* and to call its callback every hits.
|
|
*/
|
|
attr->pinned = 1;
|
|
attr->sample_period = 1;
|
|
}
|
|
|
|
static inline void ptrace_breakpoint_init(struct perf_event_attr *attr)
|
|
{
|
|
hw_breakpoint_init(attr);
|
|
attr->exclude_kernel = 1;
|
|
}
|
|
|
|
static inline unsigned long hw_breakpoint_addr(struct perf_event *bp)
|
|
{
|
|
return bp->attr.bp_addr;
|
|
}
|
|
|
|
static inline int hw_breakpoint_type(struct perf_event *bp)
|
|
{
|
|
return bp->attr.bp_type;
|
|
}
|
|
|
|
static inline unsigned long hw_breakpoint_len(struct perf_event *bp)
|
|
{
|
|
return bp->attr.bp_len;
|
|
}
|
|
|
|
extern struct perf_event *
|
|
register_user_hw_breakpoint(struct perf_event_attr *attr,
|
|
perf_overflow_handler_t triggered,
|
|
struct task_struct *tsk);
|
|
|
|
/* FIXME: only change from the attr, and don't unregister */
|
|
extern int
|
|
modify_user_hw_breakpoint(struct perf_event *bp, struct perf_event_attr *attr);
|
|
|
|
/*
|
|
* Kernel breakpoints are not associated with any particular thread.
|
|
*/
|
|
extern struct perf_event *
|
|
register_wide_hw_breakpoint_cpu(struct perf_event_attr *attr,
|
|
perf_overflow_handler_t triggered,
|
|
int cpu);
|
|
|
|
extern struct perf_event * __percpu *
|
|
register_wide_hw_breakpoint(struct perf_event_attr *attr,
|
|
perf_overflow_handler_t triggered);
|
|
|
|
extern int register_perf_hw_breakpoint(struct perf_event *bp);
|
|
extern int __register_perf_hw_breakpoint(struct perf_event *bp);
|
|
extern void unregister_hw_breakpoint(struct perf_event *bp);
|
|
extern void unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events);
|
|
|
|
extern int dbg_reserve_bp_slot(struct perf_event *bp);
|
|
extern int dbg_release_bp_slot(struct perf_event *bp);
|
|
extern int reserve_bp_slot(struct perf_event *bp);
|
|
extern void release_bp_slot(struct perf_event *bp);
|
|
|
|
extern void flush_ptrace_hw_breakpoint(struct task_struct *tsk);
|
|
|
|
static inline struct arch_hw_breakpoint *counter_arch_bp(struct perf_event *bp)
|
|
{
|
|
return &bp->hw.info;
|
|
}
|
|
|
|
#else /* !CONFIG_HAVE_HW_BREAKPOINT */
|
|
|
|
static inline struct perf_event *
|
|
register_user_hw_breakpoint(struct perf_event_attr *attr,
|
|
perf_overflow_handler_t triggered,
|
|
struct task_struct *tsk) { return NULL; }
|
|
static inline int
|
|
modify_user_hw_breakpoint(struct perf_event *bp,
|
|
struct perf_event_attr *attr) { return -ENOSYS; }
|
|
static inline struct perf_event *
|
|
register_wide_hw_breakpoint_cpu(struct perf_event_attr *attr,
|
|
perf_overflow_handler_t triggered,
|
|
int cpu) { return NULL; }
|
|
static inline struct perf_event * __percpu *
|
|
register_wide_hw_breakpoint(struct perf_event_attr *attr,
|
|
perf_overflow_handler_t triggered) { return NULL; }
|
|
static inline int
|
|
register_perf_hw_breakpoint(struct perf_event *bp) { return -ENOSYS; }
|
|
static inline int
|
|
__register_perf_hw_breakpoint(struct perf_event *bp) { return -ENOSYS; }
|
|
static inline void unregister_hw_breakpoint(struct perf_event *bp) { }
|
|
static inline void
|
|
unregister_wide_hw_breakpoint(struct perf_event * __percpu *cpu_events) { }
|
|
static inline int
|
|
reserve_bp_slot(struct perf_event *bp) {return -ENOSYS; }
|
|
static inline void release_bp_slot(struct perf_event *bp) { }
|
|
|
|
static inline void flush_ptrace_hw_breakpoint(struct task_struct *tsk) { }
|
|
|
|
static inline struct arch_hw_breakpoint *counter_arch_bp(struct perf_event *bp)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
|
|
#endif /* __KERNEL__ */
|
|
|
|
#endif /* _LINUX_HW_BREAKPOINT_H */
|