This is interrupt.h in view mode; [Download] [Up]
/*
* interrupt.h - Implementation of 6510 interrupts and alarms.
*
* Written by
* Ettore Perazzoli (ettore@comm2000.it)
* André Fachat (fachat@physik.tu-chemnitz.de)
*
* This file is part of VICE, the Versatile Commodore Emulator.
* See README for copyright notice.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA.
*
*/
#ifndef _INTERRUPT_H
#define _INTERRUPT_H
#include "types.h"
#include "vmachine.h"
#include <stdio.h>
#include "6510core.h"
/* This handles the interrupt lines and the CPU alarms (i.e. events that happen
at specified clock ticks during emulation). */
/* For maximum performance (these routines are small but used very often), we
allow use of inlined functions. This can be overridden by defining
NO_INLINE (useful for debugging and profiling). */
#if !defined NO_INLINE
# ifndef INLINE_INTERRUPT_FUNCS
# define INLINE_INTERRUPT_FUNCS
# endif
#else
# undef INLINE_INTERRUPT_FUNCS
#endif
/* If this is defined, the interrupt routines take account of the 2-cycle delay
required by the CPU to detect the NMI/IRQ line transition (otherwise, it
must be handled somewhere else). */
#define HANDLE_INTERRUPT_DELAY
#ifdef DEBUG
extern int debugflg;
#endif
/* ------------------------------------------------------------------------- */
/* Define the number of cycles needed by the CPU to detect the NMI or IRQ. */
#ifdef HANDLE_INTERRUPT_DELAY
# define INTERRUPT_DELAY 2
#else
# define INTERRUPT_DELAY 0
#endif
/* Maximum value of a CLOCK. */
#define CLOCK_MAX (~((CLOCK)0))
/* Define the tick when the clock counter overflow must be prevented and what
value should be subtracted from the counter when the tick is reached. */
#define PREVENT_CLK_OVERFLOW_TICK (CLOCK_MAX - 0x100000)
/* These are the available types of interrupt lines. */
enum cpu_int {
IK_NONE = 0,
IK_NMI = 1 << 0,
IK_IRQ = 1 << 1,
IK_RESET = 1 << 2,
IK_TRAP = 1 << 3,
IK_MONITOR = 1 << 4
};
/* This defines the type for a CPU alarm handler. The passed argument is set
to the number of ticks between the time of call and the time of
scheduling. */
typedef int cpu_alarm_handler_t(long delay);
/* We not care about wasted space here, and make fixed-length large enough
arrays since static allocation can be handled more easily... */
struct cpu_int_status {
/* Number of possible alarms. */
int num_alarms;
/* Number of interrupt lines. */
int num_ints;
/* Next CPU alarm to be served. */
int next_alarm;
/* Clock tick when the next pending CPU alarm has to be served. */
CLOCK next_alarm_clk;
/* This is nonzero if we might have a pending interrupt. */
int should_check_pending_interrupt;
/* Value of clk when each CPU alarm has to be served. ~0 means `not
active'. */
CLOCK alarm_clk[0x100];
/* Define, for each interrupt source, whether it has a pending interrupt
(IK_IRQ, IK_NMI, IK_RESET and IK_TRAP) or not (IK_NONE). */
enum cpu_int pending_int[0x100];
/* CPU alarm handlers. */
cpu_alarm_handler_t *alarm_handler[0x100];
/* Number of active IRQ lines. */
int nirq;
/* Tick when the IRQ was triggered. */
CLOCK irq_clk;
/* Number of active NMI lines. */
int nnmi;
/* Tick when the NMI was triggered. */
CLOCK nmi_clk;
/* If 1, do a RESET. */
int reset;
/* If 1, call the trapping function. */
int trap;
/* Debugging function. */
void (*trap_func)(ADDRESS, void *data);
/* Data to pass to the debugging function when called. */
void *trap_data;
/* Pointer to the last executed opcode information. */
opcode_info_t *last_opcode_info_ptr;
/* Number of cycles we have stolen to the processor last time. */
int num_last_stolen_cycles;
/* Clock tick at which these cycles have been stolen. */
CLOCK last_stolen_cycles_clk;
enum cpu_int global_pending_int;
};
typedef struct cpu_int_status cpu_int_status_t;
/* ------------------------------------------------------------------------- */
/* If we do not want the interrupt functions to be inlined, they are only
compiled once when included in `maincpu.c'. */
#ifdef INLINE_INTERRUPT_FUNCS
# define _INT_FUNC inline static
#else
# define _INT_FUNC
#endif
#if defined INLINE_INTERRUPT_FUNCS || defined _MAINCPU_C
#include <string.h> /* memset() */
/* Initialization. */
_INT_FUNC void cpu_int_status_init(cpu_int_status_t *cs, int num_ints,
int num_alarms,
opcode_info_t *last_opcode_info_ptr)
{
int i;
memset(cs, 0, sizeof(cpu_int_status_t));
cs->num_ints = num_ints;
cs->num_alarms = num_alarms;
for (i = 0; i < 0x100; i++)
cs->alarm_clk[i] = CLOCK_MAX;
cs->next_alarm_clk = CLOCK_MAX;
cs->last_opcode_info_ptr = last_opcode_info_ptr;
cs->num_last_stolen_cycles = 0;
cs->last_stolen_cycles_clk = (CLOCK)0;
cs->global_pending_int = IK_NONE;
}
/* Set the IRQ line state. */
_INT_FUNC void set_irq(cpu_int_status_t *cs, int int_num, int value,
CLOCK clk)
{
if (value) { /* Trigger the IRQ. */
if (!(cs->pending_int[int_num] & IK_IRQ)) {
cs->nirq++;
cs->global_pending_int |= IK_IRQ;
cs->pending_int[int_num] |= IK_IRQ;
#ifdef HANDLE_INTERRUPT_DELAY
/* This makes sure that IRQ delay is correctly emulated when
cycles are stolen from the CPU. */
if (cs->last_stolen_cycles_clk <= clk) {
cs->irq_clk = clk;
} else {
cs->irq_clk = cs->last_stolen_cycles_clk - 1;
}
#endif
}
} else { /* Remove the IRQ condition. */
if (cs->pending_int[int_num] & IK_IRQ) {
if (cs->nirq > 0) {
cs->pending_int[int_num] &= ~IK_IRQ;
if (--cs->nirq == 0)
cs->global_pending_int &= ~IK_IRQ;
} else
printf("set_irq(): wrong nirq!\n");
}
}
}
/* Set the NMI line state. */
_INT_FUNC void set_nmi(cpu_int_status_t *cs, int int_num, int value,
CLOCK clk)
{
if (value) { /* Trigger the NMI. */
if (!(cs->pending_int[int_num] & IK_NMI)) {
if (cs->nnmi == 0 && !(cs->global_pending_int & IK_NMI)) {
cs->global_pending_int |= IK_NMI;
#ifdef HANDLE_INTERRUPT_DELAY
/* This makes sure that NMI delay is correctly emulated when
cycles are stolen from the CPU. */
if (cs->last_stolen_cycles_clk <= clk) {
cs->nmi_clk = clk;
} else {
cs->nmi_clk = cs->last_stolen_cycles_clk - 1;
}
#endif
}
cs->nnmi++;
cs->pending_int[int_num] |= IK_NMI;
}
} else { /* Remove the NMI condition. */
if (cs->pending_int[int_num] & IK_NMI) {
if (cs->nnmi > 0) {
cs->nnmi--;
cs->pending_int[int_num] &= ~IK_NMI;
if (clk == cs->nmi_clk)
cs->global_pending_int &= ~IK_NMI;
} else
printf("set_nmi(): wrong nnmi!\n");
}
}
}
/* Change the interrupt line state: this can be used to change both NMI and IRQ
lines. It is slower than `set_nmi()' and `set_irq()', but is left for
backward compatibility (it works like the old `setirq()'). */
_INT_FUNC void set_int(cpu_int_status_t *cs, int int_num,
enum cpu_int value, CLOCK clk)
{
set_nmi(cs, int_num, value & IK_NMI, clk);
set_irq(cs, int_num, value & IK_IRQ, clk);
}
/* Trigger a RESET. This resets the machine. */
_INT_FUNC void trigger_reset(cpu_int_status_t *cs, CLOCK clk)
{
cs->global_pending_int |= IK_RESET;
}
/* Acknowledge a RESET condition, by removing it. */
_INT_FUNC void ack_reset(cpu_int_status_t *cs)
{
cs->global_pending_int &= ~IK_RESET;
}
/* Trigger a TRAP. This is a special condition that can be used for
debugging. `trap_func' will be called with PC as the argument when this
condition is detected. */
_INT_FUNC void trigger_trap(cpu_int_status_t *cs,
void (*trap_func)(ADDRESS, void *data),
void *data, CLOCK clk)
{
cs->global_pending_int |= IK_TRAP;
cs->trap_func = trap_func;
cs->trap_data = data;
}
/* Dispatch the TRAP condition. */
_INT_FUNC void do_trap(cpu_int_status_t *cs, ADDRESS reg_pc)
{
cs->global_pending_int &= ~IK_TRAP;
cs->trap_func(reg_pc, cs->trap_data);
}
_INT_FUNC void monitor_trap_on(cpu_int_status_t *cs)
{
cs->global_pending_int |= IK_MONITOR;
}
_INT_FUNC void monitor_trap_off(cpu_int_status_t *cs)
{
cs->global_pending_int &= ~IK_MONITOR;
}
/* ------------------------------------------------------------------------- */
/* Find the next pending alarm. */
_INT_FUNC void find_next_alarm(cpu_int_status_t *cs)
{
CLOCK next_alarm_clk = CLOCK_MAX;
int next_alarm = 0;
int i;
for (i = 0; i < cs->num_ints; i++)
if (cs->alarm_clk[i] < next_alarm_clk) {
next_alarm = i;
next_alarm_clk = cs->alarm_clk[i];
}
cs->next_alarm = next_alarm;
cs->next_alarm_clk = next_alarm_clk;
}
/* Return the clock tick for the next alarm. */
_INT_FUNC CLOCK next_alarm_clk(cpu_int_status_t *cs)
{
return cs->next_alarm_clk;
}
/* Schedule one cpu alarm. */
_INT_FUNC void set_alarm_clk(cpu_int_status_t *cs, int alarm,
CLOCK tick)
{
if (tick != 0 && tick < cs->next_alarm_clk) {
cs->next_alarm_clk = cs->alarm_clk[alarm] = tick;
cs->next_alarm = alarm;
} else {
if (tick == 0)
tick = CLOCK_MAX;
cs->alarm_clk[alarm] = tick;
if (alarm == cs->next_alarm)
find_next_alarm(cs);
}
}
/* Unschedule one cpu alarm. */
_INT_FUNC void unset_alarm(cpu_int_status_t *cs, int alarm)
{
cs->alarm_clk[alarm] = CLOCK_MAX;
if (alarm == cs->next_alarm)
find_next_alarm(cs);
}
/* ------------------------------------------------------------------------- */
/* Return the current status of the IRQ, NMI, RESET and TRAP lines. */
_INT_FUNC enum cpu_int check_pending_interrupt(cpu_int_status_t *cs)
{
return cs->global_pending_int;
}
/* Return nonzero if a pending NMI should be dispatched now. This takes
account for the internal delays of the 6510, but does not actually check
the status of the NMI line. */
_INT_FUNC int check_nmi_delay(cpu_int_status_t *cs, CLOCK clk)
{
CLOCK nmi_clk = cs->nmi_clk + INTERRUPT_DELAY;
/* Branch instructions delay IRQs and NMI by one cycle if branch
is taken with no page boundary crossing. */
if (OPINFO_DELAYS_INTERRUPT(*cs->last_opcode_info_ptr))
nmi_clk++;
if (clk >= nmi_clk)
return 1;
return 0;
}
/* Return nonzero if a pending IRQ should be dispatched now. This takes
account for the internal delays of the 6510, but does not actually check
the status of the IRQ line. */
_INT_FUNC int check_irq_delay(cpu_int_status_t *cs, CLOCK clk)
{
CLOCK irq_clk = cs->irq_clk + INTERRUPT_DELAY;
/* Branch instructions delay IRQs and NMI by one cycle if branch
is taken with no page boundary crossing. */
if (OPINFO_DELAYS_INTERRUPT(*cs->last_opcode_info_ptr))
irq_clk++;
/* If an opcode changes the I flag from 1 to 0, the 6510 needs
one more opcode before it triggers the IRQ routine. */
if (clk >= irq_clk && !OPINFO_ENABLES_IRQ(*cs->last_opcode_info_ptr))
return 1;
return 0;
}
/* This function must be called by the CPU emulator when a pending NMI
request is served. */
_INT_FUNC void ack_nmi(cpu_int_status_t *cs)
{
cs->global_pending_int &= ~IK_NMI;
}
/* Serve the next pending alarm and update the struct so that we know what
comes next. If there is a pending interrupt, return its kind. Otherwise,
return IK_NONE. */
_INT_FUNC void serve_next_alarm(cpu_int_status_t *cs, CLOCK clk)
{
long offset = clk - cs->next_alarm_clk;
(cs->alarm_handler[cs->next_alarm])(offset);
}
/* This is used to avoid clock counter overflows. Return the number of
cycles subtracted, which is always a multiple of `baseval'. */
_INT_FUNC CLOCK prevent_clk_overflow(cpu_int_status_t *cs, CLOCK *clk,
CLOCK baseval)
{
if (*clk > PREVENT_CLK_OVERFLOW_TICK) {
int i;
CLOCK prevent_clk_overflow_sub =
(((PREVENT_CLK_OVERFLOW_TICK & ~((CLOCK)0xffff))
/ baseval - 1) * baseval);
*clk -= prevent_clk_overflow_sub;
cs->next_alarm_clk -= prevent_clk_overflow_sub;
cs->irq_clk -= prevent_clk_overflow_sub;
cs->nmi_clk -= prevent_clk_overflow_sub;
if (cs->last_stolen_cycles_clk > prevent_clk_overflow_sub)
cs->last_stolen_cycles_clk -= prevent_clk_overflow_sub;
else
cs->last_stolen_cycles_clk = (CLOCK) 0;
for (i = 0; i < cs->num_alarms; i++) {
if (cs->alarm_clk[i] != CLOCK_MAX)
cs->alarm_clk[i] -= prevent_clk_overflow_sub;
}
return prevent_clk_overflow_sub;
} else
return 0;
}
/* Asynchronously steal `num' cycles from the CPU, starting from cycle
`start_clk'. */
_INT_FUNC void steal_cycles(cpu_int_status_t *cs, CLOCK start_clk,
CLOCK *clk_ptr, int num)
{
if (num == 0)
return;
if (start_clk == cs->last_stolen_cycles_clk)
cs->num_last_stolen_cycles += num;
else
cs->num_last_stolen_cycles = num;
*clk_ptr += num;
cs->last_stolen_cycles_clk = start_clk + num;
cs->irq_clk += num - 1;
cs->nmi_clk += num - 1;
}
#else /* defined INLINE_INTERRUPT_FUNCS || defined _MAINCPU_C */
/* We don't want inline definitions: just provide the prototypes. */
extern void cpu_int_status_init(cpu_int_status_t *cs, int num_ints,
int num_alarms,
opcode_info_t *last_opcode_info_ptr);
extern void set_irq(cpu_int_status_t *cs, int int_num, int value,
CLOCK clk);
extern void set_nmi(cpu_int_status_t *cs, int int_num, int value,
CLOCK clk);
extern void set_int(cpu_int_status_t *cs, int int_num,
enum cpu_int value, CLOCK clk);
extern void trigger_reset(cpu_int_status_t *cs, CLOCK clk);
extern void trigger_trap(cpu_int_status_t *cs,
void (*trap_func)(ADDRESS addr, void *data),
void *data, CLOCK clk);
extern void find_next_alarm(cpu_int_status_t *cs);
extern CLOCK next_alarm_clk(cpu_int_status_t *cs);
extern void set_alarm_clk(cpu_int_status_t *cs, int alarm, CLOCK tick);
extern void unset_alarm(cpu_int_status_t *cs, int alarm);
extern int check_pending_interrupt(cpu_int_status_t *cs);
extern int serve_next_alarm(cpu_int_status_t *cs, CLOCK clk);
extern CLOCK prevent_clk_overflow(cpu_int_status_t *cs, CLOCK *clk,
CLOCK baseval);
extern void steal_cycles(cpu_int_status_t *cs, CLOCK start_clk,
CLOCK *clk_ptr, int num);
extern int check_irq_delay(cpu_int_status_t *cs, CLOCK clk);
extern int check_nmi_delay(cpu_int_status_t *cs, CLOCK clk);
extern void ack_nmi(cpu_int_status_t *cs);
extern void ack_reset(cpu_int_status_t *cs);
extern void do_trap(cpu_int_status_t *cs, ADDRESS reg_pc);
#endif /* defined INLINE_INTERRUPT_FUNCS || defined _MAINCPU_C */
/* ------------------------------------------------------------------------- */
extern cpu_int_status_t maincpu_int_status;
extern CLOCK clk;
extern cpu_int_status_t true1541_int_status;
extern CLOCK true1541_clk;
/* For convenience... */
#define maincpu_set_alarm_clk(alarm, tick) \
set_alarm_clk(&maincpu_int_status, (alarm), (tick))
#define maincpu_set_alarm(alarm, ticks_from_now) \
maincpu_set_alarm_clk((alarm), clk + (ticks_from_now))
#define maincpu_unset_alarm(alarm) \
unset_alarm(&maincpu_int_status, (alarm))
#define maincpu_set_irq(int_num, value) \
set_irq(&maincpu_int_status, (int_num), (value), clk)
#define maincpu_set_irq_clk(int_num, value, clk) \
set_irq(&maincpu_int_status, (int_num), (value), (clk))
#define maincpu_set_nmi(int_num, value) \
set_nmi(&maincpu_int_status, (int_num), (value), clk)
#define maincpu_set_nmi_clk(int_num, value, clk) \
set_nmi(&maincpu_int_status, (int_num), (value), (clk))
#define maincpu_set_int(int_num, value) \
set_int(&maincpu_int_status, (int_num), (value), clk)
#define maincpu_set_int_clk(int_num, value, clk) \
set_int(&maincpu_int_status, (int_num), (value), (clk))
#define maincpu_trigger_reset() \
trigger_reset(&maincpu_int_status, clk)
#define maincpu_trigger_trap(trap_func, data) \
trigger_trap(&maincpu_int_status, (trap_func), (data), clk)
#define maincpu_serve_next_alarm() \
serve_next_alarm(&maincpu_int_status, clk)
#define maincpu_prevent_clk_overflow(rfsh_per_sec) \
prevent_clk_overflow(&maincpu_int_status, &clk, (rfsh_per_sec))
#define maincpu_steal_cycles(start_clk, num) \
steal_cycles(&maincpu_int_status, (start_clk), &clk, (num))
#define true1541_set_alarm_clk(alarm, tick) \
set_alarm_clk(&true1541_int_status, (alarm), (tick))
#define true1541_set_alarm(alarm, ticks_from_now) \
true1541_set_alarm_clk((alarm), true1541_clk + (ticks_from_now))
#define true1541_unset_alarm(alarm) \
unset_alarm(&true1541_int_status, (alarm))
#define true1541_set_irq(int_num, value) \
set_irq(&true1541_int_status, (int_num), (value), true1541_clk)
#define true1541_set_irq_clk(int_num, value, clk) \
set_irq(&true1541_int_status, (int_num), (value), (clk))
#define true1541_set_nmi(int_num, value) \
set_nmi(&true1541_int_status, (int_num), (value), true1541_clk)
#define true1541_set_nmi_clk(int_num, value, clk) \
set_nmi(&true1541_int_status, (int_num), (value), (clk))
#define true1541_set_int(int_num, value) \
set_int(&true1541_int_status, (int_num), (value), true1541_clk)
#define true1541_set_int_clk(int_num, value, clk) \
set_int_clk(&true1541_int_status, (int_num), (value), (clk))
#define true1541_trigger_reset() \
trigger_reset(&true1541_int_status, true1541_clk + 1)
#define true1541_trigger_trap(trap_func, data) \
trigger_trap(&true1541_int_status, (trap_func), (data), true1541_clk + 1)
#define true1541_serve_next_alarm() \
serve_next_alarm(&true1541_int_status, true1541_clk)
#endif /* !_INTERRUPT_H */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.