mirror of
https://github.com/j-core/j-core-ice40.git
synced 2026-02-28 01:16:29 +00:00
558 lines
13 KiB
C
558 lines
13 KiB
C
/*
|
|
Copyright (c) 2001 by William A. Gatliff
|
|
All rights reserved. bgat@billgatliff.com
|
|
|
|
See the file COPYING for details.
|
|
|
|
This file is provided "as-is", and without any express
|
|
or implied warranties, including, without limitation,
|
|
the implied warranties of merchantability and fitness
|
|
for a particular purpose.
|
|
|
|
The author welcomes feedback regarding this file.
|
|
*/
|
|
|
|
/* $Id$ */
|
|
|
|
|
|
/*
|
|
This is code for the Hitachi SH-2 processor family. Stepping is
|
|
done via code disassembly and replacement of TRAP opcodes, which
|
|
means that you can't step code that lives in flash.
|
|
*/
|
|
|
|
#include "gdb.h"
|
|
#include "sh2.h"
|
|
|
|
|
|
typedef enum {
|
|
R0 = 0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15,
|
|
PC, PR, GBR, VBR, MACH, MACL, SR
|
|
} register_id_E;
|
|
|
|
typedef struct {
|
|
unsigned long pr;
|
|
unsigned long gbr;
|
|
unsigned long *vbr;
|
|
unsigned long mach;
|
|
unsigned long macl;
|
|
unsigned long r[16];
|
|
unsigned long pc;
|
|
unsigned long sr;
|
|
} register_file_S;
|
|
|
|
static register_file_S register_file;
|
|
short gdb_sh2_stepped_opcode;
|
|
|
|
|
|
/* stuff for stepi */
|
|
#define OPCODE_BT(op) (((op) & 0xff00) == 0x8900)
|
|
#define OPCODE_BF(op) (((op) & 0xff00) == 0x8b00)
|
|
#define OPCODE_BTF_DISP(op) \
|
|
(((op) & 0x80) ? (((op) | 0xffffff80) << 1) : (((op) & 0x7f ) << 1))
|
|
#define OPCODE_BFS(op) (((op) & 0xff00) == 0x8f00)
|
|
#define OPCODE_BTS(op) (((op) & 0xff00) == 0x8d00)
|
|
#define OPCODE_BRA(op) (((op) & 0xf000) == 0xa000)
|
|
#define OPCODE_BRA_DISP(op) \
|
|
(((op) & 0x800) ? (((op) | 0xfffff800) << 1) : (((op) & 0x7ff) << 1))
|
|
#define OPCODE_BRAF(op) (((op) & 0xf0ff) == 0x0023)
|
|
#define OPCODE_BRAF_REG(op) (((op) & 0x0f00) >> 8)
|
|
#define OPCODE_BSR(op) (((op) & 0xf000) == 0xb000)
|
|
#define OPCODE_BSR_DISP(op) \
|
|
(((op) & 0x800) ? (((op) | 0xfffff800) << 1) : (((op) & 0x7ff) << 1))
|
|
#define OPCODE_BSRF(op) (((op) & 0xf0ff) == 0x0003)
|
|
#define OPCODE_BSRF_REG(op) (((op) >> 8) & 0xf)
|
|
#define OPCODE_JMP(op) (((op) & 0xf0ff) == 0x402b)
|
|
#define OPCODE_JMP_REG(op) (((op) >> 8) & 0xf)
|
|
#define OPCODE_JSR(op) (((op) & 0xf0ff) == 0x400b)
|
|
#define OPCODE_JSR_REG(op) (((op) >> 8) & 0xf)
|
|
#define OPCODE_RTS(op) ((op) == 0xb)
|
|
#define OPCODE_RTE(op) ((op) == 0x2b)
|
|
#define OPCODE_TRAPA(op) (((op) & 0xff00) == 0xc300)
|
|
#define OPCODE_TRAPA_DISP(op) ((op) & 0x00ff)
|
|
|
|
|
|
#define SR_T_BIT_MASK 0x1
|
|
|
|
#define STEP_OPCODE 0xc320
|
|
|
|
|
|
/*
|
|
Analyzes the next instruction, to see where the program
|
|
will go to when it runs. Returns the destination address.
|
|
*/
|
|
static long get_stepi_dest (void)
|
|
{
|
|
short op = *(short*)register_file.pc;
|
|
long addr = register_file.pc + 2;
|
|
|
|
|
|
/* BT, BT/S (untested!), BF and BF/S (untested!)
|
|
TODO: test delay-slot branches */
|
|
if (((OPCODE_BT(op) || OPCODE_BTS(op))
|
|
&& (register_file.sr & SR_T_BIT_MASK))
|
|
|| ((OPCODE_BF(op) || OPCODE_BFS(op))
|
|
&& !(register_file.sr & SR_T_BIT_MASK)))
|
|
{
|
|
|
|
/* we're taking the branch */
|
|
|
|
/* per 6.12 of the SH1/SH2 programming manual,
|
|
PC+disp is address of the second instruction
|
|
after the branch instruction, so we have to add 4 */
|
|
/* TODO: spend more time understanding this magic */
|
|
addr = register_file.pc + 4 + OPCODE_BTF_DISP(op);
|
|
}
|
|
|
|
/* BRA */
|
|
else if (OPCODE_BRA(op))
|
|
addr = register_file.pc + 4 + OPCODE_BRA_DISP(op);
|
|
|
|
/* BRAF */
|
|
else if (OPCODE_BRAF(op))
|
|
addr = register_file.pc + 4
|
|
+ register_file.r[OPCODE_BRAF_REG(op)];
|
|
|
|
/* BSR */
|
|
else if (OPCODE_BSR(op))
|
|
addr = register_file.pc + 4 + OPCODE_BSR_DISP(op);
|
|
|
|
/* BSRF */
|
|
else if (OPCODE_BSRF(op))
|
|
addr = register_file.pc + 4
|
|
+ register_file.r[OPCODE_BSRF_REG(op)];
|
|
|
|
/* JMP */
|
|
else if (OPCODE_JMP(op))
|
|
addr = register_file.r[OPCODE_JMP_REG(op)];
|
|
|
|
/* JSR */
|
|
else if (OPCODE_JSR(op))
|
|
addr = register_file.r[OPCODE_JSR_REG(op)];
|
|
|
|
/* RTS */
|
|
else if (OPCODE_RTS(op))
|
|
addr = register_file.pr;
|
|
|
|
/* RTE */
|
|
else if (OPCODE_RTE(op))
|
|
addr = *(unsigned long*)(register_file.r[15]);
|
|
|
|
/* TRAPA */
|
|
else if (OPCODE_TRAPA(op))
|
|
addr = register_file.vbr[OPCODE_TRAPA_DISP(op)];
|
|
|
|
return addr;
|
|
}
|
|
|
|
|
|
/*
|
|
Uses a TRAP to generate an exception
|
|
after we run the next instruction.
|
|
*/
|
|
void gdb_step (long addr)
|
|
{
|
|
long dest_addr;
|
|
|
|
if (addr)
|
|
register_file.pc = addr;
|
|
|
|
/* determine where the step will take us */
|
|
dest_addr = get_stepi_dest();
|
|
|
|
/* save the target opcode, replace with STEP_OPCODE */
|
|
gdb_sh2_stepped_opcode = *(short*)dest_addr;
|
|
*(short*)dest_addr = STEP_OPCODE;
|
|
|
|
gdb_return_from_exception();
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
Retrieves a register value from gdb_register_file. Returns the size
|
|
of the register, in bytes, or zero if an invalid id is specified
|
|
(which *will* happen--- gdb.c uses this functionality to tell how
|
|
many registers we actually have).
|
|
*/
|
|
int gdb_peek_register_file (int id, long* val)
|
|
{
|
|
/* all our registers are longs */
|
|
int retval = sizeof(long);
|
|
|
|
switch (id)
|
|
{
|
|
case R0: case R1: case R2: case R3:
|
|
case R4: case R5: case R6: case R7:
|
|
case R8: case R9: case R10: case R11:
|
|
case R12: case R13: case R14: case R15:
|
|
*val = register_file.r[id];
|
|
break;
|
|
|
|
case PC: *val = register_file.pc; break;
|
|
case PR: *val = register_file.pr; break;
|
|
case GBR: *val = register_file.gbr; break;
|
|
case VBR: *val = (long)register_file.vbr; break;
|
|
case MACH: *val = register_file.mach; break;
|
|
case MACL: *val = register_file.macl; break;
|
|
case SR: *val = register_file.sr; break;
|
|
default: retval = 0;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
#define PORT (*(volatile unsigned long *)0xabcd0000)
|
|
|
|
/*
|
|
Stuffs a register value into gdb_register_file. Returns the size of
|
|
the register, in bytes, or zero if an invalid id is specified.
|
|
*/
|
|
int gdb_poke_register_file (int id, long val)
|
|
{
|
|
/* all our registers are longs */
|
|
int retval = sizeof(long);
|
|
|
|
switch( id )
|
|
{
|
|
case R0: case R1: case R2: case R3:
|
|
case R4: case R5: case R6: case R7:
|
|
case R8: case R9: case R10: case R11:
|
|
case R12: case R13: case R14: case R15:
|
|
register_file.r[id] = val;
|
|
break;
|
|
|
|
case PC: register_file.pc = val; break;
|
|
case PR: register_file.pr = val; break;
|
|
case GBR: register_file.gbr = val; break;
|
|
case VBR: register_file.vbr = (void *)val; break;
|
|
case MACH: register_file.mach = val; break;
|
|
case MACL: register_file.macl = val; break;
|
|
case SR: register_file.sr = val; break;
|
|
default: retval = 0;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*
|
|
Releases the application to run.
|
|
*/
|
|
void gdb_continue (long addr)
|
|
{
|
|
if (addr) register_file.pc = addr;
|
|
gdb_return_from_exception();
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
The stub calls this before dropping into the monitor, to give us a
|
|
chance to clean things like software stepping up.
|
|
*/
|
|
void gdb_monitor_onentry (void)
|
|
{
|
|
/* if we're stepping, then undo the step */
|
|
if (gdb_sh2_stepped_opcode)
|
|
{
|
|
*(short*)register_file.pc = gdb_sh2_stepped_opcode;
|
|
gdb_sh2_stepped_opcode = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
Catches TRAPA #34 calls from newlib and other runtime library
|
|
stubs. Currently only handles SYS_write.
|
|
TODO: fix magic numbers.
|
|
*/
|
|
int gdb_trapa34 (int syscall, int arg1, int arg2, int arg3)
|
|
{
|
|
return gdb_file_io(syscall, arg1, arg2, arg3);
|
|
}
|
|
|
|
|
|
static int i_cnt = 50; /* toggle LED 2Hz */
|
|
static int led = 0;
|
|
void gdb_pit ()
|
|
{
|
|
if (!(i_cnt--)) {
|
|
i_cnt = 50;
|
|
if (!led) PORT = led = 0x088;
|
|
else PORT = led = 0x000;
|
|
}
|
|
}
|
|
|
|
void gdb_flush_cache (void *start, int len) { return; }
|
|
|
|
|
|
__asm__(
|
|
|
|
".section .text\n"
|
|
"save_registers_handle_exception:\n"
|
|
|
|
/*
|
|
Generic code to save processor context.
|
|
Assumes the stack looks like this:
|
|
|
|
sigval<-r15
|
|
r1
|
|
r0
|
|
pc
|
|
sr
|
|
*/
|
|
|
|
/* find end of register_file */
|
|
" mov.l register_file_end, r0\n"
|
|
|
|
/* copy sr to register file */
|
|
" mov.l @(16, r15), r1\n"
|
|
" mov.l r1, @r0\n"
|
|
|
|
/* copy pc to register file */
|
|
" mov.l @(12, r15), r1\n"
|
|
" mov.l r1, @-r0\n"
|
|
|
|
/* sigval, r1, r0, pc, sr are already on the stack, */
|
|
/* so r15 isn't the same as it was immediately before */
|
|
/* we took the current exception. We have to adjust */
|
|
/* r15 in the register file so that gdb gets the right */
|
|
/* stack pointer value */
|
|
" mov r15, r1\n"
|
|
" add #20, r1\n"
|
|
" mov.l r1, @-r0\n"
|
|
|
|
/* save r14-r2 */
|
|
" mov.l r14, @-r0\n"
|
|
" mov.l r13, @-r0\n"
|
|
" mov.l r12, @-r0\n"
|
|
" mov.l r11, @-r0\n"
|
|
" mov.l r10, @-r0\n"
|
|
" mov.l r9, @-r0\n"
|
|
" mov.l r8, @-r0\n"
|
|
" mov.l r7, @-r0\n"
|
|
" mov.l r6, @-r0\n"
|
|
" mov.l r5, @-r0\n"
|
|
" mov.l r4, @-r0\n"
|
|
" mov.l r3, @-r0\n"
|
|
" mov.l r2, @-r0\n"
|
|
|
|
/* copy r1 to register file */
|
|
" mov.l @(4, r15), r1\n"
|
|
" mov.l r1, @-r0\n"
|
|
|
|
/* copy r0 to register file */
|
|
" mov.l @(8, r15), r1\n"
|
|
" mov.l r1, @-r0\n"
|
|
|
|
/* save macl, mach, vbr, gbr, pr in register file */
|
|
" sts.l macl, @-r0\n"
|
|
" sts.l mach, @-r0\n"
|
|
" stc.l vbr, @-r0\n"
|
|
" stc.l gbr, @-r0\n"
|
|
" sts.l pr, @-r0\n"
|
|
|
|
/* call gdb_handle_exception */
|
|
" mov.l handle_exception, r0\n"
|
|
" mov.l @r15, r4\n"
|
|
" jsr @r0\n"
|
|
" nop\n"
|
|
|
|
" .align 2\n"
|
|
" handle_exception: .long _gdb_handle_exception\n"
|
|
" register_file_end: .long _register_file+88\n"
|
|
|
|
|
|
/*
|
|
TRAPA #32 (breakpoint) isr.
|
|
Sends a SIGTRAP to gdb_handle_exception().
|
|
|
|
Because we always subtract 2 from the pc
|
|
stacked during exception processing, this
|
|
function won't permit compiled-in breakpoints.
|
|
If you compile a TRAPA #32 into the code, we'll
|
|
loop on it indefinitely. Use TRAPA #33 instead.
|
|
*/
|
|
".section .text\n"
|
|
".global _gdb_trapa32_isr\n"
|
|
"_gdb_trapa32_isr:\n"
|
|
|
|
/* put r0, r1 on the stack */
|
|
" mov.l r0, @-r15\n"
|
|
" mov.l r1, @-r15\n"
|
|
|
|
/* disable interrupts */
|
|
" mov #0xf0, r0\n"
|
|
" ldc r0, sr\n"
|
|
|
|
/* put SIGTRAP on stack */
|
|
" mov #5, r0\n"
|
|
" mov.l r0, @-r15\n"
|
|
|
|
/* fudge pc, so we re-execute the instruction replaced
|
|
by the trap; this breaks compiled-in breakpoints! */
|
|
" mov.l @(12, r15), r0\n"
|
|
" add #-2, r0\n"
|
|
" mov.l r0, @(12, r15)\n"
|
|
|
|
/* save registers, call gdb_handle_exception */
|
|
" bra save_registers_handle_exception\n"
|
|
" nop\n"
|
|
|
|
|
|
".section .text\n"
|
|
".global _gdb_trapa33_isr\n"
|
|
"_gdb_trapa33_isr:\n"
|
|
" mov.l r0, @-r15\n"
|
|
" mov #0xf0, r0\n"
|
|
" ldc r0, sr\n"
|
|
" mov.l r1, @-r15\n"
|
|
" mov #5, r0\n"
|
|
" mov.l r0, @-r15\n"
|
|
" bra save_registers_handle_exception\n"
|
|
" nop\n"
|
|
|
|
/*
|
|
PIT
|
|
*/
|
|
".section .text\n"
|
|
".global _gdb_my_isr\n"
|
|
"_gdb_my_isr:\n"
|
|
" sts.l pr,@-r15\n"
|
|
" bsr _gdb_pit\n"
|
|
" nop\n"
|
|
" lds.l @r15+, pr\n"
|
|
" rte\n"
|
|
" nop\n"
|
|
|
|
/*
|
|
TRAPA #34 handler. Used by newlib et al for system calls. We
|
|
include it here so that printf() and family get automagically bound
|
|
to gdb_console_write().
|
|
*/
|
|
".section .text\n"
|
|
".global _gdb_trapa34_isr\n"
|
|
"_gdb_trapa34_isr:\n"
|
|
" sts.l pr,@-r15\n"
|
|
" bsr _gdb_trapa34\n"
|
|
" nop\n"
|
|
" lds.l @r15+, pr\n"
|
|
" rte\n"
|
|
" nop\n"
|
|
|
|
".section .text\n"
|
|
".global _gdb_unhandled_isr\n"
|
|
"_gdb_unhandled_isr:\n"
|
|
" mov.l r0, @-r15\n"
|
|
" mov #0xf0, r0\n"
|
|
" ldc r0, sr\n"
|
|
" mov.l r1, @-r15\n"
|
|
" mov #30, r0\n"
|
|
" mov.l r0, @-r15\n"
|
|
" bra save_registers_handle_exception\n"
|
|
" nop\n"
|
|
|
|
".section .text\n"
|
|
".global _gdb_nmi_isr\n"
|
|
"_gdb_nmi_isr:\n"
|
|
" mov.l r0, @-r15\n"
|
|
" mov #0xf0, r0\n"
|
|
" ldc r0, sr\n"
|
|
" mov.l r1, @-r15\n"
|
|
" mov #2, r0\n"
|
|
" mov.l r0, @-r15\n"
|
|
" bra save_registers_handle_exception\n"
|
|
" nop\n"
|
|
|
|
".section .text\n"
|
|
".global _gdb_illegalinst_isr\n"
|
|
"_gdb_illegalinst_isr:\n"
|
|
" mov.l r0, @-r15\n"
|
|
" mov #0xf0, r0\n"
|
|
" ldc r0, sr\n"
|
|
" mov.l r1, @-r15\n"
|
|
" mov #4, r0\n"
|
|
" mov.l r0, @-r15\n"
|
|
" bra save_registers_handle_exception\n"
|
|
" nop\n"
|
|
|
|
".section .text\n"
|
|
".global _gdb_addresserr_isr\n"
|
|
"_gdb_addresserr_isr:\n"
|
|
" mov.l r0, @-r15\n"
|
|
" mov #0xf0, r0\n"
|
|
" ldc r0, sr\n"
|
|
" mov.l r1, @-r15\n"
|
|
" mov #11, r0\n"
|
|
" mov.l r0, @-r15\n"
|
|
" bra save_registers_handle_exception\n"
|
|
" nop\n"
|
|
|
|
|
|
/* Restores registers to the values specified in register_file. */
|
|
".section .text\n"
|
|
".global _gdb_return_from_exception\n"
|
|
"_gdb_return_from_exception:\n"
|
|
|
|
/* find register_file */
|
|
" mov.l register_file, r0\n"
|
|
" lds.l @r0+, pr\n"
|
|
" ldc.l @r0+, gbr\n"
|
|
" ldc.l @r0+, vbr\n"
|
|
" lds.l @r0+, mach\n"
|
|
" lds.l @r0+, macl\n"
|
|
|
|
/* skip r0 and r1 for now,
|
|
since we're using them */
|
|
" add #8, r0\n"
|
|
|
|
" mov.l @r0+, r2\n"
|
|
" mov.l @r0+, r3\n"
|
|
" mov.l @r0+, r4\n"
|
|
" mov.l @r0+, r5\n"
|
|
" mov.l @r0+, r6\n"
|
|
" mov.l @r0+, r7\n"
|
|
" mov.l @r0+, r8\n"
|
|
" mov.l @r0+, r9\n"
|
|
" mov.l @r0+, r10\n"
|
|
" mov.l @r0+, r11\n"
|
|
" mov.l @r0+, r12\n"
|
|
" mov.l @r0+, r13\n"
|
|
" mov.l @r0+, r14\n"
|
|
" mov.l @r0+, r15\n"
|
|
|
|
/* put sr onto stack */
|
|
" mov.l @(4,r0), r1\n"
|
|
" mov.l r1, @-r15\n"
|
|
|
|
/* put pc onto stack */
|
|
" mov.l @r0, r1\n"
|
|
" mov.l r1, @-r15\n"
|
|
|
|
/* restore r1, r0 */
|
|
" add #-64, r0\n"
|
|
" mov.l @(4,r0), r1\n"
|
|
" mov.l @r0, r0\n"
|
|
|
|
" rte\n"
|
|
" nop\n"
|
|
".align 2\n"
|
|
" register_file: .long _register_file\n"
|
|
|
|
|
|
/* "kill" and "detach" try to simulate a reset */
|
|
".section .text\n"
|
|
".global _gdb_kill\n"
|
|
".global _gdb_detach\n"
|
|
"_gdb_kill:\n"
|
|
"_gdb_detach:\n"
|
|
" mov #4, r15\n"
|
|
" mov.l @r15, r15\n"
|
|
" mov #0, r0\n"
|
|
" mov.l @r0, r0\n"
|
|
" jmp @r0\n"
|
|
" nop\n"
|
|
|
|
);
|