[comp.sys.amiga.tech] Interupt handling

billk@pnet01.cts.com (Bill W. Kelly) (01/14/89)

(Roy Forsstrom was asking if anyone knew how to do interrupts on the Amiga)

Yes.   I don't have any code examples handy right now, but Amiga interrupts
are very straightforward.  

First of all, since I assume you are not going to take over the machine,
you do NOT want to mess with the 68000 interrupt autovectors!  (On a 68000,
these are at addresses $64 (level one interrupt) through $7c (level seven
interrupt).  On a 68010 and up, you would also have to add the offset
contained in the VBR (vector base register) to get to where the interrupt
vectors are.)  

Anyway, since you are going to be running under Exec, you can use the Amiga's
Exec_Library routines to add/remove your own interrupt handlers and interrupt
servers.   Your interrupt code never actually gets stuck into the 68000
interrupt autovectors.  Instead, the Amiga's interrupt routines decode the
interrupt a bit and then to a JSR to your interrupt handler (or server).  This
means that when you are through you want to do an RTS and NOT AN RTE!   

While the 68000 has 7 interrupt levels, (7 is currently not used by system)
there are fourteen interrupts on the Amiga that go through autovector's 1-6. 
The Amiga's interrupt handlers find out exactly which interrupt has occured
for you by examining the INTENAR and INTREQR hardware registers.  (Addresses
$dff01c and $dff01e.)   

Anyway, that's kindof beside the point. (Sorry...) 

Let's say you want to add an interrupt server to the server chain for the
"Start of Vertical Blank" interrupt.  That's AMIGA INTERRUPT NUMBER 5 (68000
interrupt level three).

So, you are going to want to call the Exec function:

        AddIntServer(intNumber, interrupt) 

The "intNumber" is 5 for the Amiga vBlank interrupt. 

The "interrupt" is an initialized interrupt structure.

Here's the interrupt structure:

        struct Interrupt {
          struct Node is_Node;
          APTR is_Data;
          VOID (*is_Code)();
        };

The is_Data field should point to any data space that you have allocated
that your interrupt routine will be wanting to use.

The is_Code field should point to your assembley-language interrupt-handling
code.  

The is_Node is an instance of the Node structure.

You need to set the following fields of the node structure:

        is_Node.ln_Type = NT_INTERRUPT;
        is_Node.ln_Pri = -20;            /* or whavever pri. you want */
        is_Node.ln_Name = "Optional";    /* optional name */

If you set it up correctly and do the AddIntServer() you will have installed
your own VBlank interrupt server into the Amiga's VBlank interrupt server
chain.

Come to think of it, there are constants defined for the interrupt numbers. (I
usually just look in the hardware manual.)

So, you should do:  AddIntServer(INTB_VERTB, MyVertBIntr);

To remove it, do:   RemIntServer(INTB_VERTB, MyVertBIntr);

When your interrupt server code is called, the 68000 registers will
be set up as follows: 

        A1 points to the data area you specified in the is_Data field
           of your Interrupt structure.  Actually, it is the address of
           the is_Data field itself.  You must fetch from A1 to get the
           contents of the is_Data field.
        A0 points to the base address of the Amiga custom chips. ($dff000)

The registers you can use as scratch are: d0-d1/a0-a1/a5

NOTE: The above information was for interrupt SERVER CHAINS.  Some Amiga
interrupts have server chains -- others just have one interrupt handler
routine.  Installing an interrupt HANDLER is slightly different from
adding a server to a SERVER CHAIN.

To install an interrupt HANDLER, to the following:

Set up the is_Data and is_Code fields of the interrupt structure the same
as you would do for an interrupt server.  

Exceptions:     is_Node.ln_Pri = 0;  /* handler has no pri. set to NULL */

        Add/remove HANDLER with different Exec call than SERVER:

        PriorInterrupt = SetIntVector(INTB_RBF, MyRBFInterrupt);

        The above code will replace the existing serial Receive Buffer
        Full interrupt handler with your own code.  When you are through
        you can put back the old handler like this:

        SetIntVector(INTB_RBF, PriorInterrupt);

        Also, 68000 register conventions differ slightly for HANDLERS.

        D0 is scratch and contains garbage data.

        D1 is scratch but contains anded INTENAR and INTREQR hardware
           register bits.  This results in a bit mask of the interrupts
           which are both enabled AND active.

        A1 points to base of Amiga custom chips. ($dff000)

        A5 is scratch (and happens to contain the address of your 
           interrupt handler)

        A6 points to ExecBase.  Scratch for HANDLERS.  

So, those are the differences between interrupt servers and interrupt
handlers.  

Oh, I almost forgot.  When you return (RTS) from an interrupt SERVER, you
should have cleared register D0.  (i.e. moveq #0,d0)  If register D0 is
nonzero, you will terminate the server chain. (No servers with a lower
priority than yours will be called.)

Here's a list of which interrupts are HANDLERS and which are SERVER CHAINS:

        Servers:  PORTS, COPER, VERTB, BLIT, EXTER, and NMI.
        Handlers: TBE, DSKBLK, SOFT, AUD0, AUD1, AUD2, AUD3, RBF, DSKSYN

While I'm at it, here's a list of Exec functions you are allowed to 
call from within an interrupt:

        Alert()         Disable()               Cause()
        Enable()        FindName()              FindPort()
        FindTask()      PutMsg()                ReplyMsg()
        Signal()

Also, if you are manipulating YOUR OWN list structures during an
interrupt, it's ok to call:

        AddHead()       AddTail()       Enqueue()
        RemHead()       RemTail()

Also...

For HANDLERS, you should turn off the interrupt request bit for the
interrupt you are handling in the hardware register INTREQ.

Take as an example a handler for the RBF interrupt.  You should:

        move.w #INTF_RBF,intreq(a0)     ;a0 contains addr $dff000
                                        ;which is base addr of 
                                        ;Amiga hardware registers

I don't want to go into why what appears to be setting the bit
INTF_RBF is, in fact, clearing that bit right now except briefly...

There are some hardware registers that are only affected by the bits
that get written to them that are ones.  (INTREQ, INTENA, DMACON, etc.)
The bits that get written as zeros have no effect.  Basically you can think of
the bits you write into the register as being OR'd into it -- but there's a
catch!  Bit 15 is special and controls whether the bits being OR'd get NAND'd
(NOT AND) instead.  If bit 15 ($8000) is set, all the other bits in the word
that are ones get OR'd.  If bit 15 is clear, all the other bits in the word
that are ones get NAND'd.

So, if you write $8001 to INTENAR you will be setting bit one. (Because bit 15
was SET)   (Bit one happens to be TBE -- Serial port Transmit Buffer Empty)

If you write $0001 to INTENA you will be CLEARING bit one. (Because bit 15 was
CLEAR)


I hope this makes sense.

While I'm at it, I may as well go over Software Interrupts.

Like the hardware interrupts, software interrupts use the Interrupt structure.

You set up the interrupt structure almost exactly as you would for an
interrupt SERVER.   Software interrupts, however have only five priority
levels. (SERVERS have 256 priority levels, -128 to 127)

Software Interrupt priorities can be: -32, -16, 0, 16, and 32.

There are no functions like AddIntServer or SetIntVector for software
interrupts.  Rather, you activate them with Cause().

        Cause(MySoftInt);       /* activate a software interrupt */

Software interrupts run at a higher priority than tasks, but at a lower pri.
than hardware interrupts.

A neat thing you can do with software interrupts has to do with messages
arriving at message ports.  Normally a message port's mp_Flags field is set to
PA_SIGNAL and the mp_SigTask field contains a pointer to a Task structure.

So, when a message arrives at a message port, the task at *mp_Task will
be signal()'d with the signal bit number in mp_SigBit.

But here's what you can do with a software interrupt:

        Set mp_Flags to PA_SOFTINT

        Point mp_SigTask at your software interrupt structure instead
        of at a task.

Now when a message arrives at this port, instead of having your task
Signal()'d, your software interrupt will be Cause()'d to run.

----

Well, I guess that about wraps it up for Amiga Interrupts...
Next question, please?  :-)

Seriously, though, I have tried to be as accurate as possible.  All of 
this information is in the RKM's (ROM Kernal Manuals), by the way.
Anyway, if I've made a bunch of typos, I'm sorry.  You should be able to read
around them.

                                Good luck, and happy Amiga-ing,
                                Bill Kelly
--
Bill W. Kelly
{nosc ucsd hplabs!hp-sdd}!crash!pnet01!billk   crash!pnet01!billk@nosc.mil
                                                      billk@pnet01.cts.com