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.