ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/interrupt.h

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.