502a0c775c
pt_regs->event was set with artificial values to identify the low level system event (syscall trap / breakpoint trap / exceptions / interrupts) With r8 saving out of the way, the full word can be used to save real ECR (Exception Cause Register) which helps idenify the event naturally, including additional info such as cause code, param. Only for Interrupts, where ECR is not applicable, do we resort to synthetic non ECR values. SAVE_ALL_TRAP/EXCEPTIONS can now be merged as they both use ECR with different runtime values. The ptrace helpers now use the sub-fields of ECR to distinguish the events (e.g. vector 0x25 is trap, param 0 is syscall...) The following benefits will follow: (1) This centralizes the location of where ECR is saved and will allow the cleanup of task->thread.cause_code ECR placeholder which is set in non-uniform way. Then ARC VM code can safely rely on it being there for purpose of finer grained VM_EXEC dcache flush (based on exec fault: I-TLB Miss) (2) Further, ECR being passed around from low level handlers as arg can be eliminated as it is part of standard reg-file in pt_regs Signed-off-by: Vineet Gupta <vgupta@synopsys.com>
207 lines
5.0 KiB
C
207 lines
5.0 KiB
C
/*
|
|
* kgdb support for ARC
|
|
*
|
|
* Copyright (C) 2012 Synopsys, Inc. (www.synopsys.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/kgdb.h>
|
|
#include <linux/sched.h>
|
|
#include <asm/disasm.h>
|
|
#include <asm/cacheflush.h>
|
|
|
|
static void to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs,
|
|
struct callee_regs *cregs)
|
|
{
|
|
int regno;
|
|
|
|
for (regno = 0; regno <= 26; regno++)
|
|
gdb_regs[_R0 + regno] = get_reg(regno, kernel_regs, cregs);
|
|
|
|
for (regno = 27; regno < GDB_MAX_REGS; regno++)
|
|
gdb_regs[regno] = 0;
|
|
|
|
gdb_regs[_FP] = kernel_regs->fp;
|
|
gdb_regs[__SP] = kernel_regs->sp;
|
|
gdb_regs[_BLINK] = kernel_regs->blink;
|
|
gdb_regs[_RET] = kernel_regs->ret;
|
|
gdb_regs[_STATUS32] = kernel_regs->status32;
|
|
gdb_regs[_LP_COUNT] = kernel_regs->lp_count;
|
|
gdb_regs[_LP_END] = kernel_regs->lp_end;
|
|
gdb_regs[_LP_START] = kernel_regs->lp_start;
|
|
gdb_regs[_BTA] = kernel_regs->bta;
|
|
gdb_regs[_STOP_PC] = kernel_regs->ret;
|
|
}
|
|
|
|
static void from_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs,
|
|
struct callee_regs *cregs)
|
|
{
|
|
int regno;
|
|
|
|
for (regno = 0; regno <= 26; regno++)
|
|
set_reg(regno, gdb_regs[regno + _R0], kernel_regs, cregs);
|
|
|
|
kernel_regs->fp = gdb_regs[_FP];
|
|
kernel_regs->sp = gdb_regs[__SP];
|
|
kernel_regs->blink = gdb_regs[_BLINK];
|
|
kernel_regs->ret = gdb_regs[_RET];
|
|
kernel_regs->status32 = gdb_regs[_STATUS32];
|
|
kernel_regs->lp_count = gdb_regs[_LP_COUNT];
|
|
kernel_regs->lp_end = gdb_regs[_LP_END];
|
|
kernel_regs->lp_start = gdb_regs[_LP_START];
|
|
kernel_regs->bta = gdb_regs[_BTA];
|
|
}
|
|
|
|
|
|
void pt_regs_to_gdb_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs)
|
|
{
|
|
to_gdb_regs(gdb_regs, kernel_regs, (struct callee_regs *)
|
|
current->thread.callee_reg);
|
|
}
|
|
|
|
void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *kernel_regs)
|
|
{
|
|
from_gdb_regs(gdb_regs, kernel_regs, (struct callee_regs *)
|
|
current->thread.callee_reg);
|
|
}
|
|
|
|
void sleeping_thread_to_gdb_regs(unsigned long *gdb_regs,
|
|
struct task_struct *task)
|
|
{
|
|
if (task)
|
|
to_gdb_regs(gdb_regs, task_pt_regs(task),
|
|
(struct callee_regs *) task->thread.callee_reg);
|
|
}
|
|
|
|
struct single_step_data_t {
|
|
uint16_t opcode[2];
|
|
unsigned long address[2];
|
|
int is_branch;
|
|
int armed;
|
|
} single_step_data;
|
|
|
|
static void undo_single_step(struct pt_regs *regs)
|
|
{
|
|
if (single_step_data.armed) {
|
|
int i;
|
|
|
|
for (i = 0; i < (single_step_data.is_branch ? 2 : 1); i++) {
|
|
memcpy((void *) single_step_data.address[i],
|
|
&single_step_data.opcode[i],
|
|
BREAK_INSTR_SIZE);
|
|
|
|
flush_icache_range(single_step_data.address[i],
|
|
single_step_data.address[i] +
|
|
BREAK_INSTR_SIZE);
|
|
}
|
|
single_step_data.armed = 0;
|
|
}
|
|
}
|
|
|
|
static void place_trap(unsigned long address, void *save)
|
|
{
|
|
memcpy(save, (void *) address, BREAK_INSTR_SIZE);
|
|
memcpy((void *) address, &arch_kgdb_ops.gdb_bpt_instr,
|
|
BREAK_INSTR_SIZE);
|
|
flush_icache_range(address, address + BREAK_INSTR_SIZE);
|
|
}
|
|
|
|
static void do_single_step(struct pt_regs *regs)
|
|
{
|
|
single_step_data.is_branch = disasm_next_pc((unsigned long)
|
|
regs->ret, regs, (struct callee_regs *)
|
|
current->thread.callee_reg,
|
|
&single_step_data.address[0],
|
|
&single_step_data.address[1]);
|
|
|
|
place_trap(single_step_data.address[0], &single_step_data.opcode[0]);
|
|
|
|
if (single_step_data.is_branch) {
|
|
place_trap(single_step_data.address[1],
|
|
&single_step_data.opcode[1]);
|
|
}
|
|
|
|
single_step_data.armed++;
|
|
}
|
|
|
|
int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
|
|
char *remcomInBuffer, char *remcomOutBuffer,
|
|
struct pt_regs *regs)
|
|
{
|
|
unsigned long addr;
|
|
char *ptr;
|
|
|
|
undo_single_step(regs);
|
|
|
|
switch (remcomInBuffer[0]) {
|
|
case 's':
|
|
case 'c':
|
|
ptr = &remcomInBuffer[1];
|
|
if (kgdb_hex2long(&ptr, &addr))
|
|
regs->ret = addr;
|
|
|
|
case 'D':
|
|
case 'k':
|
|
atomic_set(&kgdb_cpu_doing_single_step, -1);
|
|
|
|
if (remcomInBuffer[0] == 's') {
|
|
do_single_step(regs);
|
|
atomic_set(&kgdb_cpu_doing_single_step,
|
|
smp_processor_id());
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
unsigned long kgdb_arch_pc(int exception, struct pt_regs *regs)
|
|
{
|
|
return instruction_pointer(regs);
|
|
}
|
|
|
|
int kgdb_arch_init(void)
|
|
{
|
|
single_step_data.armed = 0;
|
|
return 0;
|
|
}
|
|
|
|
void kgdb_trap(struct pt_regs *regs, int param)
|
|
{
|
|
/* trap_s 3 is used for breakpoints that overwrite existing
|
|
* instructions, while trap_s 4 is used for compiled breakpoints.
|
|
*
|
|
* with trap_s 3 breakpoints the original instruction needs to be
|
|
* restored and continuation needs to start at the location of the
|
|
* breakpoint.
|
|
*
|
|
* with trap_s 4 (compiled) breakpoints, continuation needs to
|
|
* start after the breakpoint.
|
|
*/
|
|
if (regs->ecr_param == 3)
|
|
instruction_pointer(regs) -= BREAK_INSTR_SIZE;
|
|
|
|
kgdb_handle_exception(1, SIGTRAP, 0, regs);
|
|
}
|
|
|
|
void kgdb_arch_exit(void)
|
|
{
|
|
}
|
|
|
|
void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long ip)
|
|
{
|
|
instruction_pointer(regs) = ip;
|
|
}
|
|
|
|
struct kgdb_arch arch_kgdb_ops = {
|
|
/* breakpoint instruction: TRAP_S 0x3 */
|
|
#ifdef CONFIG_CPU_BIG_ENDIAN
|
|
.gdb_bpt_instr = {0x78, 0x7e},
|
|
#else
|
|
.gdb_bpt_instr = {0x7e, 0x78},
|
|
#endif
|
|
};
|