(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either expressed or implied.  
The entire risk as to the use of this information is assumed by the user.


       
                       Allocation of Input and 
                       Other System Resources
               
                            by Bob Burns

    
In most system designs there is a trade-off between ease of use and 
performance.  The Amiga is no exception.  Historically, high performance 
applications have resorted to taking over the machine in order to get direct 
access to system resources.  This is something we would like to avoid because 
once the machine is taken over, there is no exit.  The user must reboot. 
The purpose of this article is to present some techniques that let games
and other special applications take over those portions of the system that 
they need, yet gracefully return them when no longer needed.


Input Resources
---------------

The Input Task 

A useful metaphor for describing the input task on the Amiga is the 
"food-chain".  Raw input events are passed through a linked list of handlers.
Each handler processes the input events it receives and returns a new set to 
be "consumed" by the next input handler in the chain.  

The primal input events are designated RAWKEY, RAWMOUSE, TIMER and  DISK.  
The RAWKEY, RAWMOUSE and TIMER input events are started up the food chain by 
the input.device itself in response to its continuing IO requests to the 
keyboard, gameport, and timer devices.  The DISK events are inserted into 
the chain by the DOS.

The two standard consumers in the food chain are Intuition and the console 
device.  Intuition in particular is prolific in the variety of new input 
events it may produce in response to a primal event.  Another consumer often 
found in the food chain is a generic hotkey program.  These insert themselves 
in the food chain before either Intuition or the console.  

The food chain is implemented as a a prioritized list, with Intuition at a 
priority of 50 and the console device at 20.  For more on input events, see 
the devices/inputevents.h header.


Input Device Commands

Let's quickly review the input device commands you can use to control the 
food chain.  Insertion and removal of a new consumer in the input device food 
chain is performed by the input.device commands AddHandler and RemHandler.

The command WriteEvent is used to propagate an event through the input food 
chain without being on it.

The commands SetThresh and SetPeriod are used to modifiy the key repeat rate.

Finally, the commands SetMPort, SetMType, and SetMTrig are used to modify the 
gameport controller that is to be used for mouse input.  The use of these 
commands is illustrated in the example code shown below.

The joymouse.c example uses the commands to move the mouse to the second 
gameport device so that the first gameport can be used with a lightpen.  This 
is useful for owners of the original model A1000 and A2000s.  

The joymouse.c example also shows how to change the first gameport to work 
with a joystick, or remove it from the input food chain entirely so both 
ports can be used for other purposes.

You need only disable the mouse to use the gameports for your special purposes. 
You don't need to take over the whole machine.  The gameport.device can then 
be used to gather data from the port, or the hardware can be read directly.



Gameport Input

The gameport.device contains two units, one for each gameport.  The original 
Amiga design called for game controllers to be allocated to applications as 
follows:

        1.  Let multiple opens of gameport.device occur.

        2.  Follow a high level protocol for determining if the 
            gameport is in use: check via AskCType for the flag
            GPCT_NOCONTROLLER.  If set, then no one else is using it.

There are some problems with this scheme.  First, there is no protection 
between SetCType and AskCType.  So two tasks can AskCType, see NO, and
both SetCType.  Second, because games often go directly to the hardware, 
the gameport.device became just a spoon-feeder to the input.device for mouse 
input.
    
Despite these problems, the gameports can still be shared between applications
in a system-compatible way.  Neither the SetCType nor AskCType commands are 
deferred, nor do they wait and thus break a Forbid.   This means you can use 
the following code to get a gameport:

		Forbid
		AskCType
		if(GPCT_NOCONTROLLER){
		    SetCType
		    owned = TRUE      }
		else
		    owned = FALSE
		endif
		Permit

We normally try to discourage counting on this kind of side effect, but in 
this case, it's the only way.  If the port is being allocated, but not for 
one of the supported controller types, then set the controller to the 
catch-all value GPCT_ALLOCATED.



Joysticks

To read a joystick, there are three choices.  The first is to change the 
mouse port to a joystick port and capture events from the input device food 
chain as a handler.  This is not recommended.
    
The second choice is to read the joystick port directly from the hardware 
registers.  It's fairly easy to do and we cannot fault folks for doing it 
this way - just make sure to allocate the gameport unit so other applications
know you've got the port.  You should also allocate the POTGO resource bits 
to acquire buttons 2 & 3.

The third choice is to use the gameport device.  This polls every vertical 
blank and only satisfies requests when trigger conditions are met.  It's 
advantages are that the polling runs out of the contention-free ROMs, and 
when combined with certain reply port types (e.g. the direct call action), 
can be efficient for your application.



Proportional Controller

Unfortunately, support for proportional controllers in the gameport device 
was removed in one of the early code crunches.  This occurred after support 
for Coleco-style controllers had already been removed.  The example program
propjoy.c shown below demonstrates how to use the potgo resource and some 
vblank interrupt routines to track proportional controllers.



Keyboard Input

The input device IO requests to the gameport can be shut down but IO requests 
to the keyboard cannot.  If an application requests key events from the 
keyboard, it will probably get about half of them: every other one going to 
the input device.  So, to learn about all the key states an application can 
either poll the keyboard with the ReadMatrix command, or insert itself as a 
handler in the food chain.  If you use the ReadMatrix command, note that
io_Length MUST be exactly 13.




Other System Resources
----------------------

Misc

Whenever using serial or parallel port resources, they must be acquired from 
the misc.resource.  The misc.resource oversees usage of the serial data port, 
the serial communication bits, the parallel data and handshake port, and the 
parallel communication bits.  The parallel communication bits double as the 
Commodore serial bus interface bits for those who want to connect a 1541 to 
the Amiga.

The serial and parallel devices both use the misc.resource.  If the Exec 
device is opened and closed, the appropriate two misc resources will be 
allocated then freed.  Unfortunately, if the DOS devices SER: or PAR: have
been opened, they will open the device and thus the corresponding resource 
pair forever.  For more information on this problem see the article on the
parallel and serial resources elsewhere in this issue.


Disk

Whenever using floppy disk resources, they must be acquired from the disk 
resource.  The disk resource provides both a gross and a fine unit allocation
scheme.  AllocUnit and FreeUnit are used to claim a unit for long term use, 
and GetUnit and GiveUnit are used to claim a unit for shorter periods.

The trackdisk.device uses and abides by both allocation schemes.  Because a 
trackdisk unit is never closed for Amiga 3.5" drives, since the file system 
keeps them open, the associated resource units will always be allocated for 
these drives.  GetUnit and GiveUnit can still be used, however, by other 
applications that have not succeeded with AllocUnit.

It is therefore possible to prevent the trackdisk device from using units that
have not been mounted yet by successfully performing an AllocUnit for that 
unit.  It is also possible to starve trackdisk usage by performing a GetUnit.
The appropriate companion routine (FreeUnit or GiveUnit) should be called to 
restore the resource at the end of its use.


CIA

CIA resources not associated with the misc and disk resources are the joystick
fire buttons, which are acquired with the appropriate gameport.device unit, 
the LED/audio filter, which is catch-as-catch-can, the memory overlay bit 
and the keyboard serial interface, which is defined to be allocated by the 
keyboard.device when it is running.  The CIAB timers are available to be 
allocated and are associated with an interrupt.  It is therefore allocated by
successfully adding a vector for that interrupt with AddICRVector.


        Amiga CIA Timer Allocation

        CIAA (int 2)
            timerA      Used for keyboard handshake
            timerB      Used for uSec timer.device
            TOD         Used for 60Hz timer.device

         CIAB (int 6)
            timerA      Commodore serial bus communication, usually not used
            timerB      Not used
            TOD         Used for graphics.library beam counter


By using the techniques outlined above, you can often avoid taking over the 
whole system in your high-performance applications.  Allocate only the 
resources you need and return them when you are done.


  Copyright (c) 1988 Commodore-Amiga, Inc.
 
  Executables based on this information may be used in software
  for Commodore Amiga computers.  All other rights reserved.
 
  This information is provided "as is"; no warranties are made.
  All use is at your own risk, and no liability or responsibility is assumed.


;=============================================================
; makefile - requires Manx 3.6
;=============================================================
;
;joymouse:	joymouse.o32 iefa.o
;	ln joymouse.o32 iefa.o -lc32 -o joymouse
;
;joymouse.o32:	joymouse.c
;	cc +L +C +D +p -B -S -L100 -o joymouse.o32 joymouse.c
;
;iefa.o:		iefa.asm
;	as -o iefa.o iefa.asm
;
;=============================================================
; joymouse.c
;=============================================================
; 
#define	DEBUG
#include	"exec/types.h"
#include	"exec/nodes.h"
#include	"exec/lists.h"
#include	"exec/ports.h"
#include	"exec/interrupts.h"
#include	"devices/gameport.h"
#include	"devices/input.h"
#include	"devices/inputevent.h"
#include	"libraries/dos.h"

struct Task *FindTask();

struct MsgPort iorp = {
    {0, 0, NT_MSGPORT, 0, 0}, 0, SIGF_SINGLE, 0, {0, 0, 0, 0, 0}
};

struct IOStdReq ior = {
    {{0, 0, NT_MESSAGE, 0, 0}, &iorp, 0},
    0, 0, 0, 0, 0, 0, 0, 0, 0
};

struct InputEvent nullEvent = {
    NULL, IECLASS_NULL
};

struct GamePortTrigger joyTrigger = {
    GPTF_DOWNKEYS|GPTF_UPKEYS, 1, 1, 1
};

struct GamePortTrigger mouseTrigger = {
    GPTF_DOWNKEYS|GPTF_UPKEYS, 0, 1, 1
};

extern VOID eventAFunction();

struct Interrupt inputHandler = {
    {0, 0, NT_INTERRUPT, 100, 0}, 0, eventAFunction
};

#define	MAXDELTA	255
#define	DELTASHIFT	4

int deltaX = 0;
int deltaY = 0;

struct InputEvent *
eventFunction(event, data)
struct InputEvent *event;
{
    struct InputEvent *loopevent;

#ifdef	DEBUG
kprintf(".");
#endif

    loopevent = event;
    do {
	if (loopevent->ie_Class == IECLASS_RAWMOUSE) {
	    /* handle X, then Y.  slow down faster than speed up */
	    /* this could use another iteration to get it to "feel" right */
	    if (loopevent->ie_X < 0) {
#ifdef	DEBUG
kprintf("<");
#endif
		if (deltaX <= 0) {
		    if (deltaX > -MAXDELTA) deltaX--;
		}
		else deltaX = deltaX/2;
	    }
	    else if (loopevent->ie_X > 0) {
#ifdef	DEBUG

kprintf(">");
#endif
		if (deltaX >= 0) {
		    if (deltaX < MAXDELTA) deltaX++;
		}
		else deltaX = deltaX/2;
	    }
	    else if (loopevent->ie_Y != 0) {
		if (deltaX != 0) deltaX += (deltaX<0)?1:-1;
	    }
	    else deltaX = deltaX/2;
	    if (deltaX < 0)
		loopevent->ie_X = (deltaX-((1<<DELTASHIFT)+1))>>DELTASHIFT;
	    else
		loopevent->ie_X = (deltaX+((1<<DELTASHIFT)-1))>>DELTASHIFT;

	    if (loopevent->ie_Y < 0) {
#ifdef	DEBUG
kprintf("v");
#endif
		if (deltaY <= 0) {
		    if (deltaY > -MAXDELTA) deltaY--;
		}
		else deltaY = deltaY/2;
	    }
	    else if (loopevent->ie_Y > 0) {
#ifdef	DEBUG
kprintf("^");
#endif
		if (deltaY >= 0) {
		    if (deltaY < MAXDELTA) deltaY++;
		}
		else deltaY = deltaY/2;
	    }
	    else if (loopevent->ie_X != 0) {
		if (deltaY != 0) deltaY += (deltaY<0)?1:-1;
	    }
	    else deltaY = deltaY/2;
	    if (deltaY < 0)
		loopevent->ie_Y = (deltaY-((1<<DELTASHIFT)+1))>>DELTASHIFT;
	    else
		loopevent->ie_Y = (deltaY+((1<<DELTASHIFT)-1))>>DELTASHIFT;
	}
    }
    while ((loopevent = loopevent->ie_NextEvent) != 0);
    return(event);
}


main(argc, argv)
int argc;
char *argv[];
{
    char c;
    int result;

    NewList(&iorp.mp_MsgList);
    iorp.mp_SigTask = (struct Task *) FindTask((char *) NULL);

    if (OpenDevice("input.device", 0, &ior, 0)) exit(20);

    /* free this controller */
    c = GPCT_NOCONTROLLER;
    ior.io_Command = IND_SETMTYPE;
    ior.io_Length = 1;
    ior.io_Data = (APTR) &c;
    DoIO(&ior);

    /* wait for the SETM to take effect with a side effect */
    ior.io_Command = IND_WRITEEVENT;
    ior.io_Length = (LONG) sizeof(nullEvent);
    ior.io_Data = (APTR) &nullEvent;
    DoIO(&ior);

    /* switch to the right game port */
    c = 1;
    ior.io_Command = IND_SETMPORT;
    ior.io_Length = 1;
    ior.io_Data = (APTR) &c;
    DoIO(&ior);

    /* set this controller to a joystick */
    c = GPCT_ABSJOYSTICK;
    ior.io_Command = IND_SETMTYPE;
    ior.io_Length = 1;
    ior.io_Data = (APTR) &c;
    DoIO(&ior);

    /* set the trigger for the joystick */
    ior.io_Command = IND_SETMTRIG;
    ior.io_Length = (LONG) sizeof(joyTrigger);
    ior.io_Data = (APTR) &joyTrigger;
    DoIO(&ior);

    /* insert the accellerator handler into the food chain */
    ior.io_Command = IND_ADDHANDLER;
    ior.io_Data = (APTR) &inputHandler;
    ior.io_Length = (LONG) sizeof(struct Interrupt);
    DoIO(&ior);

    /* wait for this program to terminate */
    Wait(SIGBREAKF_CTRL_C);

    /* free this controller */
    c = GPCT_NOCONTROLLER;
    ior.io_Command = IND_SETMTYPE;
    ior.io_Length = 1;
    ior.io_Data = (APTR) &c;
    DoIO(&ior);

    /* remove the accellerator handler from the food chain */
    ior.io_Command = IND_REMHANDLER;
    ior.io_Data = (APTR) &inputHandler;
    ior.io_Length = (LONG) sizeof(struct Interrupt);
    DoIO(&ior);

    /* switch to the left game port */
    c = 0;
    ior.io_Command = IND_SETMPORT;
    ior.io_Length = 1;
    ior.io_Data = (APTR) &c;
    result = DoIO(&ior);

    /* set the trigger for a mouse */
    ior.io_Command = IND_SETMTRIG;
    ior.io_Length = sizeof(mouseTrigger);
    ior.io_Data = (APTR) &mouseTrigger;
    DoIO(&ior);

    /* set this controller to a mouse */
    c = GPCT_MOUSE;
    ior.io_Command = IND_SETMTYPE;
    ior.io_Length = 1;
    ior.io_Data = (APTR) &c;
    result = DoIO(&ior);
}

;=============================================================
; iefa.asm
;=============================================================
;
; Copyright (c) 1988 Commodore-Amiga, Inc.
;
; Executables based on this information may be used in software
; for Commodore Amiga computers.  All other rights reserved.
;
; This information is provided "as is"; no warranties are made.
; All use is at your own risk, and no liability or responsibility is assumed.
;
	XREF		_eventFunction

	XDEF		_eventAFunction
_eventAFunction:
		MOVEM.L	A0/A1,-(A7)
		JSR	_eventFunction
		ADDQ.L	#8,A7
		RTS

		END


/******************************************************************************
*
*   Source Control
*   --------------
*   $Header: propjoy.c,v 34.1 85/11/24 17:59:44 bart Exp $
*
*   $Locker:  $
*
*   $Log:   propjoy.c,v $
*   Revision 34.1  85/11/24  17:59:44  bart
*   be system compatible
*   
*   Revision 34.0  86/11/21  16:35:51  bart
*   added to rcs for updating
*
* Copyright (c) 1988 Commodore-Amiga, Inc.
* 
* Executables based on this information may be used in software
* for Commodore Amiga computers.  All other rights reserved.
* 
* This information is provided "as is"; no warranties are made.
* All use is at your own risk, and no liability or responsibility is assumed.
*
******************************************************************************/

/* main.c - test program for add/rem tof task - bart - 05.19.86 */
/* propjoy.c modified program for proportional controller - bart - 11.21.86 */

;=============================================================
; propjoy.c
;=============================================================

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/execbase.h>
#include <exec/execname.h>

#include <graphics/gfxbase.h>
#include <graphics/graphint.h>

#include <hardware/cia.h>
#include <hardware/custom.h>
#include <hardware/intbits.h>

#include <resources/potgo.h>

#define V1_POINT_2  33

#define NUM_SERVERS 2

#define MAX_COUNT   (UWORD)~1	    /* do it for a while */

/* use system defined hard addresses */

extern struct Custom custom;

/* use system defined addresses for potgo and pot1dat */

#define POTGO	    &custom.potgo
#define POT0DAT	    &custom.pot0dat
#define POT1DAT	    &custom.pot1dat

/* vertical blank interrupt server priority   */

#define HIGHINTPRI 127L	    /* needs to be replaced with priority relative */
#define LOWINTPRI -127L	    /* needs to be replaced with priority relative */

/* bit number defines for potgo ... */

#define START_B	    0	

#define DATRX_B	    8	
#define DATRY_B	    10

#define DATLX_B	    12	
#define DATLY_B	    14

/* masks ... */

#define START_F	    (1L << START_B)
#define DATRX_F	    (1L << DATRX_B)
#define DATRY_F	    (1L << DATRY_B) 
#define DATLX_F	    (1L << DATLX_B)
#define DATLY_F	    (1L << DATLY_B) 

#define RPOTX	    (START_F | DATRX_F) 
#define RPOTY	    (START_F | DATRY_F) 
#define RPOTXY	    (START_F | DATRX_F | DATRY_F) 

#define LPOTX	    (START_F | DATLX_F) 
#define LPOTY	    (START_F | DATLY_F) 
#define LPOTXY	    (START_F | DATLX_F | DATLY_F) 

struct ExecBase *ExecBase = NULL;
struct GfxBase *GfxBase = NULL;
struct PotgoBase *PotgoBase = NULL;

/* global storage for potdat */

UWORD oldbits = NULL;
UWORD potbits = NULL;
ULONG potdat = NULL;

/* create server-task to read the proportional joysticks,*/ 
/* update potdat, and then poke potgo to start next data read */

/* reserve space for the interrupt servers */
struct Isrvstr server[NUM_SERVERS] = {NULL};

first_server(i)
int i;
{
    /* read previous proportional joystick values */
    potdat = *(ULONG *)POT0DAT;

    /* poke potgo, restore old bits */
    WritePotgo(oldbits,((~1)<<8)|oldbits);

    /* be nice -- let other servers run, too */
    return(NULL);
}

second_server(i)
int i;
{
    /* poke potgo, start prop joystick read  */
    WritePotgo(potbits,((~1)<<8)|potbits);

    /* be nice -- let other servers run, too */
    return(NULL);
}


main()
{
    LONG error = FALSE;
    struct Isrvstr *iserver[NUM_SERVERS];
    LONG i;

    /* set server priorities */ 
    server[0].is_Node.ln_Pri = HIGHINTPRI;  
    server[1].is_Node.ln_Pri = LOWINTPRI;   

    /* set up server pointers */
    iserver[0] = &server[0];
    iserver[1] = &server[1];

    if((ExecBase = (struct ExecBase *)OpenLibrary(EXECNAME,V1_POINT_2)) != NULL)
    {
	if((GfxBase = (struct GfxBase *)
	    OpenLibrary("graphics.library",V1_POINT_2)) != NULL)
	{

	    if((PotgoBase = OpenResource(POTGONAME,V1_POINT_2)) != NULL)
	    {

		/* remember currently used bits */
		oldbits = ~(AllocPotBits(~1));

		/* restore previous state of bit allocation */
		FreePotBits(~oldbits);

		/* now attempt to allocate start potbit */
		potbits = AllocPotBits(START_F);

		Forbid();   

		/* add interrupt servers */

		AddTOF(iserver[0],first_server,0);

		AddTOF(iserver[1],second_server,1);

		Permit();

		/* loop until done */

		for(i=0; i < MAX_COUNT; i++)
		{
		    /* wait for server to update pot values */
		    WaitTOF();

		    /* only output information once every second */
		    if(!(i %  ExecBase->VBlankFrequency))
		    {
		    }
		}

		Forbid();   

		for(i=0; i < NUM_SERVERS; i++)
		{
		    RemTOF(iserver[i]);
		}

		Permit();

		/* free potbits */

		FreePotBits(potbits);

		CloseLibrary(GfxBase);
	    }
	    else
	    {
		error = TRUE;
	    }
	}
	else
	{
		error = TRUE;
	}

	CloseLibrary(ExecBase);

    }
    else
    {
	error = TRUE;
    }

    /* return error code */
    exit(error);
}
