The RSP server initialization,
rsp_init ()
is called from the main
simulator initialization, sim_init ()
in
toplevel-support.c
.
The main simulation initialization is also modified to start the processor stalled on a TRAP exception if RSP debugging is enabled. This ensures that the handler will be called initially.
The main loop of Or1ksim, called after initialization, is in the
function exec_main ()
in
cpu/or32/execute.c
.
If RSP debugging is enabled in the Or1ksim configuration, the
code to interact with the RSP client
(handle_rsp ()
) is called at the start of
each iteration, but only if the processor is
stalled. The handler is called repeatedly until an
interaction with the client unstalls the processor (i.e. a
step or continue function.
void exec_main () { long long time_start; while (1) { time_start = runtime.sim.cycles; if (config.debug.enabled) { while (runtime.cpu.stalled) { if (config.debug.rsp_enabled) { handle_rsp (); } ...
Since interaction with the client can only occur when the processor is stalled, BREAK signals (i.e. ctrl-C) cannot be intercepted.
It would be possible to poll the connection on every instruction iteration, but the performance overhead on the simulator would be unacceptable.
An implementation to pick up BREAK signals should use event driven
I/O - i.e. with a signal handler for SIGIO
. An
alternative is to poll the interface less frequently when the CPU is
not stalled. Since Or1ksim executes at several MIPS, polling every
100,000 cycles would mean a response to ctrl-C of less than 100ms,
while adding no significant overhead.
The RSP interface will only pick up those exceptions which cause the processor to stall. These are the exceptions routed to the debug interface, rather than through their exception vectors, and are specified in the Debug Stop Register (set during initialization). In the present implementation, only TRAP exceptions are picked up this way, allowing the debugger to process memory based breakpoints. However an alternative implementation could allow the debugger to see all exceptions.
Exceptions will be processed at the start of each iteration by
handle_rsp ()
. However the handler needs
to know which signal caused the exception. This is achieved by
modifying the main debug unit exception handling function
(debug_ignore_exception ()
in
debug/debug-unit.c
) to call
rsp_exception ()
if RSP is enabled for
any exception handled by the debug unit. This function stores the
exception (translated to a GDB target signal) in
rsp.sigval
.
int debug_ignore_exception (unsigned long except) { int result = 0; unsigned long dsr = cpu_state.sprs[SPR_DSR]; switch (except) { case EXCEPT_RESET: result = (dsr & SPR_DSR_RSTE); break; case EXCEPT_BUSERR: result = (dsr & SPR_DSR_BUSEE); break; ... cpu_state.sprs[SPR_DRR] |= result; set_stall_state (result != 0); if (config.debug.rsp_enabled && (0 != result)) { rsp_exception (except); } return (result != 0); } /* debug_ignore_exception () */
For almost all exceptions, this approach is suitable. However TRAP exceptions due to single stepping are taken at the end of each instruction execution and do not use the standard exception handling mechanism.
The exec_main ()
function already
includes code to handle this towards the end of the main
loop. This is extended with a call to
rsp_exception ()
if RSP debugging is
enabled.
if (config.debug.enabled) { if (cpu_state.sprs[SPR_DMR1] & SPR_DMR1_ST) { set_stall_state (1); if (config.debug.rsp_enabled) { rsp_exception (EXCEPT_TRAP); } } }