[net.micro.amiga] AmigaDos Port-Handler

jcz@ncsu.UUCP (John A. Toebes, VIII) (04/01/86)

As promised before, here is my version of the Port-Handler for AmigaDos.
It was derived in part from looking at the original Port-Handler in order
to obtain the calling conventions of BCPL and the routines that must be
called in order to do I/O through the BCPL environment, but otherwise
I started from scratch (and an empty file) to code it.

My original goal was to learn out exactly how AmigaDos device handlers are 
constructed and as such I have learned enough from writing this one that I
have also written a PIPE: handler to implement *REAL* UN*X pipes.  It
is not completely tested, so I expect it will be a week or so before I
would consider posting it.

Now on to how this one works:  I have tested it with the majority of the 
AmigaDos commands and have yet to get it to fail, but I have one instance
in a print program where it locks up.  Because of that, this program is NOT
to be taken as a direct replacement for the supplied Port-Handler until I
can track down that bug.  The true value of this code is that it shows what
the BCPL environment looks like from the handler point of view.

I will be posting a tutorial on the BCPL environment in about 2 weeks, but a
quick summary of the conventions:
    Upon entry to a BCPL routine (Including all command programs!) the
registers are set up as:
     A0 - Contains 0
     A1 - Points to the Bottom of the BCPL global stack
     A2 - Points to the internal AmigaDos library!
     A3 - ?unknown - I believe it to be unused
     A4 - Points to the Base of your code - I.E. Entry address
     A5 - Points to a service vector for calling other BCPL routines
     A6 - Points to the service vector to return from a routines
     A7 - Points to the program stack
     D0 - Generally unimportant - contains the size of the caller's global area
     D1-D4 - Contain first 4 (if that many) parameters
     D5-D7 - Unused
To call a BCPL routine you must:
  Load the parameters into D1-D4 respectively
  Load D0 with the size of your global stack
  Load A4 with the address of the routine to call
  JSR (A5)
To exit a BCPL routine you simply
  JSR (A6)
In general, A2 points to an internal AmigaDos library that WILL PROBABLY CHANGE
with the next release of Workbench/AmigaDos, but to what extent is unknown.
So as such, the calls described here must be used with NO SUPPORT even implied
by Commodore (They didn't document them so why should they stay?).  However
on the other side of the coin you would never get along without them.
I have figured out some of them as listed in the include file BCPL.I and will
document them later.  For now, the parameters should be fairly simple to
deduce from the code.

Once you have assembled and linked the code, it is invoked by simply replacing
the system PORT-HANDLER in the L: directory and then accessing any of PAR:
SER: or PRT:.  AmigaDos will automatically load and run the handler.  Note that
because this process is automatic, it is next to impossible to debug.  As such
you see the commented out debug statements in the code to store into the stack
frame and at $CA so that you can look at where the process is through WACK.
It is not an easy task, so expect to poke around a bit.

This code is being released for personal consumption only.  I am not supporting
this version (remember it has a known bug) but it should serve to show exactly
what an AmigaDos handler has to do.  I have not been able to get any
verification of correctness from Commodore or even a condonement/condeming
from them so this could be way off base (But I seriously doubt it).
Good Luck...

----cut here---- file BCPL.I -------cut here-----
BCALL	MACRO
	MOVE.L	#FRAME_SIZE,D0
	MOVE.L	\1(A2),A4
	JSR	(A5)
	ENDM

BEXIT	MACRO
	JMP	(A6)
	ENDM

SetIO		EQU	$18
taskid		EQU	$38
DoIO		EQU	$54
SendIO		EQU	$58
OpenDevice	EQU	$7C
CloseDevice	EQU	$80
taskwait	EQU	$A4
returnpkt	EQU	$C4
getlong		EQU	$1B8
putlong		EQU	$1BC

act_locateobject	EQU	8
act_renamedisk		EQU	9
act_freelock		EQU	15
act_deleteobject	EQU	16
act_renameobject	EQU	17
act_copydir		EQU	19
act_waitchar		EQU	20
act_setprotect		EQU	21
act_createdir		EQU	22
act_examineobject	EQU	23
act_examinenext		EQU	24
act_diskinfo		EQU	25
act_setcomment		EQU	28
act_parent		EQU	29
act_inhibit		EQU	31
act_read		EQU	'R'
act_write		EQU	'W'
act_queueread		EQU	1001
act_queuewrite		EQU	1002
act_findinput		EQU	1005
act_findoutput		EQU	1006
act_end			EQU	1007
act_seek		EQU	1008
----cut here---- file Port-Handler.asm ----cut here---
		TTL	"Port-Handler AmigaDos Device Driver"
* Port-handler AmigaDos Device Driver
* Copyright (c) 1986 John A. Toebes, VIII   All Rights Reserved
*
* Permission is granted for personal usage only.
* This program may not be incorporated in any commercial product or sold
* for any purpose without the permission of the author.
*
		SECTION	CODE
StartModule	DC.L	(EndModule-StartModule)/4
EntryPt		EQU	*
*
	NOLIST
	INCLUDE	"exec/types.i"
	INCLUDE	"exec/libraries.i"
	INCLUDE	"exec/io.i"
	INCLUDE	"libraries/dosextens.i"
	LIST
	INCLUDE "bcpl.i"
***
***	Global constants used
***
FALSE	EQU	0
TRUE	EQU	-1
IOB_size	EQU	30
RAW_NAME	EQU	('R'<<24)!('A'<<16)!('W'<<8)
MARKER1_DATA	EQU	('T'<<24)!('O'<<16)!('P'<<8)!('!')
MARKER2_DATA	EQU	('J'<<24)!('O'<<16)!('H'<<8)!('N')

***
***	This is the global structure that we get a pointer to in A1
***	when our code is executing
***
	STRUCTURE	PORT_HANDLER_GLOBALS,0
	LONG	IO_parm_pkt		*Pointer to our initial packet
*	LONG	MARKER1
*	LONG	EXECLIB
*	LONG	OURCODE
*	LONG	DOSCALL
*	LONG	DOSRET
	LONG	read_pkt		*Pointer to user read request
	LONG	write_pkt		*Pointer to user write request
	BYTE	openin			*device is open for input
	BYTE	openout			*device is open for output
	WORD	FILLER			* to pad to a 4 byte boundary
	BPTR	node			*pointer to our DeviceList node
	BPTR	inpkt			*pointer to read transmission pkt
	STRUCT	inpkt_buf,16		* Buffer for above
	BPTR	outpkt			*pointer to write transmission pkt
	STRUCT	outpkt_buf,16		* Buffer for above
	BPTR	IOB			*IO request for reading from device
	STRUCT	IOB_buf,(IOB_size*4)+8	* Buffer for above
	BPTR	IOBO			*IO request for writing to device
	STRUCT	IOBO_buf,(IOB_size*4)+8	* Buffer for above
	APTR	dataloc			*QUE pointer-data to be written/read
	BPTR	tpkt			*QUE pointer-IO completion msg packet
	BPTR	use_IOB			*QUE pointer-IOB for IO
*	LONG	MARKER2
	BPTR	FILLER1
	LABEL	FRAME_SIZE
*
* This routine is a sample device handler for
*	SER:	The serial port
*	PAR:	The parallel port
*	PRT:	The installed printer
*	PRT:RAW	The installed printer without translating LF to CR
*

* BCPL driver Entry conditions:
*	D1 - DosPacket - immediately converted to a real pointer
*	A0 - 0
*	A1 - Pointer to our global data area
*	A2 - Pointer to BCPL subroutine library
*	A3 - <Unassigned>
*	A4 - Pointer to Base of out code
*	A5 - Pointer to Subroutine transfer vector
*	A6 - Pointer to Code termination vector
*

* Upon initialization, D1 contains a BPTR to a DosPacket with the info:
*	arg1 = BPTR to BCPL string of device name
*		This is should be one of PRT: PRT:RAW SER: PAR:
*	arg2 = Extra info from the DevList entry.
*		0 = SER:	1 = PAR:	2 = PRT:
*	arg3 = BPTR to the DevList entry
*
	LSL.L	#2,D1		*make pointer to DosPacket real

*	Initialize our global structure
*	MOVE.L	A1,$CA
*	MOVE.L	#MARKER1_DATA,MARKER1(A1)	*let us see it in memory
*	MOVE.L	#MARKER2_DATA,MARKER2(A1)
*	MOVE.L	A2,EXECLIB(A1)
*	MOVE.L	A4,OURCODE(A1)
*	MOVE.L	A5,DOSCALL(A1)
*	MOVE.L	A6,DOSRET(A1)
	CLR.L	read_pkt(A1)	*No Read request pending
	CLR.L	write_pkt(A1)	*No write request pending
	CLR.B	openin(A1)	*device not opened for input
	CLR.B	openout(A1)	*device not opened for output
	MOVE.L	dp_Arg3(A0,D1.L),D3
	LSL.L	#2,D3		*make it a real pointer
	MOVE.L	D3,node(A1)	*to our DevList entry

	MOVEQ.L	#inpkt_buf,D3	*locate our default input packet
	ADD.L	A1,D3
	MOVE.L	#act_queueread,dp_Type(A0,D3.L)	*and set it as a read packet
	LSR.L	#2,D3
	MOVE.L	D3,inpkt(A1)

	MOVEQ.L	#outpkt_buf,D4	*locate our default output packet
	ADD.L	A1,D4
	MOVE.L	#act_queuewrite,dp_Type(A0,D4.L) *and set as write packet
	LSR.L	#2,D4
	MOVE.L	D4,outpkt(A1)

	MOVEQ.L	#IOB_buf,D5	*locate our input IO request packet
	ADD.L	A1,D5
	LSR.L	#2,D5
	MOVE.L	D5,IOB(A1)

	MOVE.L	#IOBO_buf,D6	*locate our output IO request packet
	ADD.L	A1,D6
	LSR.L	#2,D6
	MOVE.L	D6,IOBO(A1)

	MOVEQ.L	#IOB_size-1,D5	*Empty our input IOB
	LEA	IOB_buf(A1),A3
CLRIOB	CLR.L	(A3)+
	DBF	D5,CLRIOB

***	Open the device
***	We have to figure out what device we will be opening
	MOVEQ.L	#0,D4		* Flags on the open
	MOVE.L	D4,D3		* Unit number

	TST.L	dp_Arg2(A0,D1.L)	* is it a open on SER:
	BEQ	IS_SER
	MOVEQ.L	#1,D2
	CMP.L	dp_Arg2(A0,D1.L),D2	* is it an open on PAR:
	BEQ	IS_PAR

IS_PRT	LEA	prt_name(A4),A3		PRT: uses printer.device
	BRA	PUTNAME

IS_SER	LEA	ser_name(A4),A3		SER: uses serial.device
	BRA	PUTNAME

IS_PAR	LEA	par_name(A4),A3		PAR: uses parallel.device
PUTNAME	EQU	*
	MOVE.L	A3,D2
	LSR.L	#2,D2		*make the name a BPTR
	MOVE.L	IOB(A1),D1
	BCALL	OpenDevice

	TST.L	D1		*did the open succeed
	BNE	GOTOPEN		*no...

	MOVE.L	#ERROR_OBJECT_IN_USE,D3	*say it is in use
	MOVEQ.L	#FALSE,D2
	MOVE.L	(A1),D1
	BCALL	returnpkt
	BEXIT

GOTOPEN	EQU	*

	MOVEQ.L	#IOB_size-1,D5	*get a second IOB for output
	LEA	IOB_buf(A1),A3
	LEA	IOBO_buf(A1),A0	*Use only for copying - restore to 0 later
COPYIOB	MOVE.L	(A3)+,(A0)+
	DBF	D5,COPYIOB
	MOVEQ.L	#0,D5
	MOVE.L	D5,A0

***	Now see if they asked to put the printer into the RAW mode
	MOVE.L	(A1),D1
	LSL.L	#2,D1
	MOVEQ.L	#2,D5
	CMP.L	dp_Arg2(A0,D1.L),D5	* is it a open on PRT:
	BNE	NOTRAW
	MOVE.L	dp_Arg1(A0,D1.L),D3
	LSL.L	#2,D3
	CMP.L	#RAW_NAME,4(A0,D3.L)	*is the secondpart 'RAW\0'?
	BNE	NOTRAW

***	Build the IOB for outputing the initialization string
	LEA	prt_init_str(A4),A3		*point to the output string
	MOVE.L	A3,D3
	MOVE.L	IOBO(A1),D1
	ASL.L	#2,D1
	MOVE.L	D1,A3
	MOVE.W	#CMD_WRITE,D2
	MOVE.L	D2,IO_COMMAND(A3)	;$1C
	MOVE.L	D3,IO_DATA(A3)		;$28
	MOVEQ.L	#-1,D4
	MOVE.L	D4,IO_LENGTH(A3)	;$24

	MOVE.L	IOBO(A1),D1	*DOIO(IOBO) to output the init string
	BCALL	DoIO

NOTRAW	EQU	*

***	set taskid field to our task so everyone comes to us for the device
	BCALL	taskid
	MOVE.L	node(A1),D2
	MOVE.L	D1,dl_Task(A0,D2.L)

***	We are now setup, return our initilization packet so we can
***	Let the system start sending us IO requests
	MOVEQ.L	#TRUE,D2
	MOVE.L	(A1),D1		IO_parm_pkt
	BCALL	returnpkt

***	Now that all the initialization is done, here is where we do all
***	the work of processing the requests.  Of importance to note is that
***	although we have opened the device, the real request to open the
***	device comes (usually) as the first message to our task.
***
***	Events come to us in the form of a DosMessage with the type of
***	event in the type field of the packet
***	We only need to handle a few of these events and can just send
***	the rest back as invalid requests
***
***	The ones we handle are:
***	'R'	- A request to read from the device
***	'W'	- A request to write to a device
***	1001	- A read WE posted to the device completed
***	1002	- A write WE posted to the device completed
***	1005	- The user wishes to open the device we handle for input
***	1006	- The user wishes to open the device we handle for output
***	1007	- The uses wishes to terminate an open on our device
***
***	Register usage:
***	D1 - BCPL pointer to event message packet
***	D2 - real pointer to event message packet - until changed
***
***	Wait for the next message
LOOP	EQU	*
	BCALL	taskwait
	MOVE.L	D1,D2
	LSL.L	#2,D2

***	Now see what type of message it was
	MOVE.L	dp_Type(A0,D2.L),D3

	MOVEQ.L	#'R',D4
	CMP.L	D4,D3		*read
	BEQ	do_R

	MOVEQ.L	#'W',D4
	CMP.L	D4,D3		*write
	BEQ	do_W

	CMP.L	#act_queueread,D3	*act.read
	BEQ	do_read

	CMP.L	#act_queuewrite,D3	*act.write
	BEQ	do_writ

	CMP.L	#act_findinput,D3	*find input
	BEQ	do_old

	CMP.L	#act_findoutput,D3	*find output
	BEQ	do_new

	CMP.L	#act_end,D3	*end
	BEQ	do_end

	BRA	do_dflt

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Open up our device for input
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_old	TST.B	openin(A1)	*make sure it isn't already open for input
	BNE	IN_USE

	ST	openin(A1)	*Say we have the device for input
	MOVE.L	#MODE_OLDFILE,D3
	BRA	SET_SCB

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Open up our device for output
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_new	TST.B	openout(A1)
	BNE	IN_USE

	ST	openout(A1)	*Say we have the device for output
	MOVE.L	#MODE_NEWFILE,D3

SET_SCB	MOVE.L	dp_Arg1(A0,D2.L),D4
	LSL.L	#2,D4
	MOVE.L	D3,fh_Arg1(A0,D4.L)	*and set the access mode
	MOVEQ.L	#TRUE,D5
	MOVE.L	D5,fh_Interactive(A0,D4.L)	* Mark file as interactive
	BRA	OK_RET

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Close the device for input/output
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_end	CMP.L	#MODE_OLDFILE,dp_Arg1(A0,D2.L)
	BNE	closout
	CLR.B	openin(A1)	*device no longer needed for input
	BRA	doclose
closout	CLR.B	openout(A1)	*device no longer needed for output
doclose	EQU	*

***	If this closed both files then it is time to shut down
	TST.B	openin(A1)
	BNE	OK_RET
	TST.B	openout(A1)
	BNE	OK_RET
	MOVE.L	node(A1),D3	*find our devicelist entry
	CLR.L	dl_Task(A0,D3.L) *remove our process id from the entry
	BRA	OK_RET

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Here we have a read request we posted returning to us
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_read	MOVE.L	D1,inpkt(A1)	*restore our packet for posting reads

	MOVE.L	read_pkt(A1),D1	*the message to return to the user
	LEA	IOB_buf(A1),A3	*the IO request that returned
	BRA	CHK_RET

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Here we have a write request we posted returning to us
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_writ	MOVE.L	D1,outpkt(A1)	*restore out packet for posting writes

	MOVE.L	write_pkt(A1),D1	*the message to return to the user
	LEA	IOBO_buf(A1),A3		*the IO request that returned

CHK_RET	MOVEQ.L	#0,D3
	MOVE.B	IO_ERROR(A3),D3		*get the error code if any
	BNE	BAD_RET			*any error?
	MOVE.L	IO_ACTUAL(A3),D2	*return number of bytes read
	BRA	RET_PKT

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Here we have a request from the user to do a read
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_R	MOVE.L	D1,read_pkt(A1)
	MOVE.L	inpkt(A1),tpkt(A1)
	CLR.L	inpkt(A1)		*Indicate input in progress

	MOVEQ.L	#CMD_READ,D4
	MOVE.L	IOB(A1),D1

	BRA	QUE_PKT

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Here we have a request from the user to do a write
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_W	MOVE.L	D1,write_pkt(A1)
	MOVE.L	outpkt(A1),D3
	CLR.L	outpkt(A1)	*Indicate an output is in progress

	MOVEQ.L	#CMD_WRITE,D4
	MOVE.L	IOBO(A1),D1

***	To queue an IO request we need to have set up:
***	D1 - BCPL pointer to the desired IOB
***	D2 - The real pointer to the input request message
***	D3 - message packet associated with the request
***	D4 - I/O request type to be queued
QUE_PKT	MOVE.L	D1,D5
	ASL.L	#2,D5
	MOVE.L	D5,A3

	MOVE.W	D4,IO_COMMAND(A3)		;$1C
	MOVE.L	dp_Arg2(A0,D2.L),IO_DATA(A3)	;$28
	MOVE.L	dp_Arg3(A0,D2.L),IO_LENGTH(A3)	;$24
	CLR.L	IO_OFFSET(A3)			;$28

	MOVE.L	D3,D2
	BCALL	SendIO
	BRA	LOOP

*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***
***	Here we handle all other requests we do not recognize
***
*** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
do_dflt	TST.B	openin(A1)
	BNE	BAD_ACT
	TST.B	openout(A1)
	BNE	BAD_ACT
	MOVE.L	node(A1),D1	*keep them from bothering us anymore
	CLR.L	dl_Task(A0,D1.L)
BAD_ACT	EQU	*
	MOVE.L	#ERROR_ACTION_NOT_KNOWN,D3
	BRA	BAD_RET

***
***	Here we return the message to the user
***
IN_USE	MOVE.L	#ERROR_OBJECT_IN_USE,D3	*the device is in use
BAD_RET	MOVEQ.L	#FALSE,D2	*Operation had failed
	BRA	RET_PKT
OK_RET	MOVEQ.L	#TRUE,D2	*Operation has succeeded
RET_PKT	BCALL	returnpkt	*D1 already has the packet address in it

LOOPIT	TST.B	openin(A1)	*is there anyone open on us?
	BNE	LOOP
	TST.B	openout(A1)
	BNE	LOOP
	TST.L	outpkt(A1)	*or an output pending?
	BEQ	LOOP
	TST.L	inpkt(A1)	*or an input pending?
	BEQ	LOOP

***
***	Our purpose in life is fullfilled, close the device
***	and quietly fade off into the sunset
***
	MOVE.L	IOB(A1),D1
	BCALL	CloseDevice
	BEXIT

		CNOP	0,4
ser_name	EQU	*-EntryPt
		DC.B	14,'serial.device',0

		CNOP	0,4
par_name	EQU	*-EntryPt
		DC.B	16,'parallel.device',0

		CNOP	0,4
prt_name	EQU	*-EntryPt
		DC.B	15,'printer.device',0

		CNOP	0,4
prt_init_str	EQU	*-EntryPt
		DC.B	$1B,'[20l',0
***
***	Trailer stuff to make this look like a BCPL module
***
		CNOP	0,4
		DC.L	0
		DC.L	1
		DC.L	EntryPt-StartModule
		DC.L	(FRAME_SIZE/4)+2+$44
EndModule	EQU	*
		END
----end of code------------
John A. Toebes, VIII
120 H Northington Place
Cary NC 27511
(919) 469-4210
 ...mcnc!ncsu!jcz   (as a guest there)

Disclaimer: I don't know what I am doing so how can you expect anyone else
to be responsible for what I say and do.