*****************************************************************************
* Program:  newser.asm - Copyright ©1990,91 by Dan Babcock
* Function: A "serial.device" compatible driver for the Rockwell 65C52
*           based on the 1.3 RKM driver example.
*
* Author:   Dan Babcock
* History:  08/12/90 V0.50 Created
*           09/06/90 V0.51 Cleaned up "eofdump" code.
*           11/04/90 V0.52 Misc. minor changes
*           11/25/90 V0.53 Changed path and name of include file
*	    03/25/91 V1.10 I (Mike Mossman) added code to support the
*			   ASDG SetCtrlLines command.
*
* [To all: Please don't forget to bump the revision numbers
*          if you do *any* modifications at all.  -Jeff]
*
* This code is designed to be ROMable (but not position independent).
*
* Feel free to contact me:
*
* [School address]               [Permanent address]      People/Link:
*  Dan Babcock                    Dan Babcock              DANBABCOCK
*  Atherton Hall Room 63          P.O. Box 1532
*  University Park, PA  16802     Southgate, MI  48195
*  Voice: (814)-862-2931
*
* I am also reachable via internet. I read comp.sys.amiga.tech.
*
* General notes
* =============
* The macro "PUTDEBUG" is used to output debugging information via the
* internal serial port at a low level. It may be used anywhere, including
* critical sections, supervisor mode, and interrupt code. If debugging is
* not desired, set the INFO_LEVEL equate to zero. Conversely, if debugging
* is desired, set it to a high value (e.g. 100000)
*
* A consistent (for the most part) usage of registers was employed:
*  A1 - pointer to an IORequest structure
*  A3 - pointer to a unit structure (defined below)
*  A4 - pointer to a serprefs structure (defined below)
*  A5 - pointer to the hardware (one channel, that is)
*  A6 - pointer to the device structure (defined below)
*
* Known difference(s) between this and serial.device are:
*  1. Start/Stop do not send XON/XOFF characters to the outside world
*     as implied in the autodoc (I don't think this is important).
*  2. Reading is never performed in the caller's context.
*  3. Newser does not currently send an XOFF or otherwise tell the outside
*     world to "shut up" when the driver's input buffer fills; it simply
*     reports a buffer overflow condition and continues as normal.
*
* Possible future enhancements:
*  1. Read the default prefs from memory (set up previously) or a file
*  2. Check to see whether units 3 & 4, if requested, actually exist
*
* Suggestions for additional enhancements are welcome.
*****************************************************************************


VERSION	        EQU	1
REVISION        EQU	1
INFO_LEVEL      EQU     0


;This define is used to tell which commands should be handled
;immediately (on the caller's schedule).
;
;The immediate commands are Invalid, Reset, Stop, Start, Flush, Clear,
;Query, and SetParams
;
;Note that this method limits you to just 32 device specific commands,
;which may not be enough.

IMMEDIATES	EQU	%00000000000000010000101111110011
;		         --------========--------========
;		         FEDCBA9876543210FEDCBA9876543210


        INCDIR "include:"

	INCLUDE	"exec/types.i"
	INCLUDE "exec/funcdef.i"
	INCLUDE "exec/exec.i"
	INCLUDE "exec/exec_lib.i"
	INCLUDE "exec/ables.i"
	INCLUDE "exec/errors.i"
	INCLUDE "exec/interrupts.i"
	INCLUDE	"exec/libraries.i"
	INCLUDE	"exec/lists.i"
	INCLUDE	"exec/initializers.i"
	INCLUDE	"exec/resident.i"
	INCLUDE "devices/serial.i"
	INCLUDE "devices/timer.i"
	INCLUDE "hardware/custom.i"
	INCLUDE "hardware/intbits.i"
	INCLUDE "libraries/dos.i"
	INCLUDE "libraries/dosextens.i"
        INCLUDE "ioexp.i"
	
	
	; Things we define for external reference (for easier debuggging)
	XDEF	LibStart
	XDEF	RomTag
	XDEF	LibName
	XDEF	LibIDString
	XDEF	LibInit
	XDEF	LibOpen
	XDEF	LibClose
	XDEF	LibExpunge
	XDEF	LibExtFunc
	XDEF    LibBeginIO
	XDEF    LibAbortIO
	XDEF	LibEnd
        XDEF    inittable
	XDEF    functable
	XDEF    datatable
	XDEF    defaultprefs
	XDEF    basetable
	XDEF    baudtable
	XDEF    cmdtable
	XDEF    mdu_Init
	XDEF    LibEnd
	XDEF    LibStart
	XDEF    mydebug
	XDEF    InitTask
	XDEF    InitDACIA
	XDEF    SetUpUnit
	XDEF    InitUnit
        XDEF	LibBeginIO
        XDEF    BeginIO_QueueMsg
        XDEF    BeginIO_Immediate
        XDEF    PerformIO
        XDEF    BeginIO_End
        XDEF    BeginIO_NoCmd
        XDEF    queuewrt
        XDEF    notread
        XDEF    ReadTask_Begin
        XDEF    ReadSigLoop
        XDEF    ReadTask_MainLoop
        XDEF    ReadTask_StartHere
        XDEF    ReadTask_NextMessage
        XDEF    Readignorecmd
        XDEF    Readconttl

	SECTION	CODE

	; Supposed start of executable code.  This is where execution
	; will start if anybody's silly enough to try and run the library
	; from the command line.  Just return the highest error code we
	; conveniently can to tell them they screwed up.
LibStart:
	moveq	#$7F,d0
	rts

	; Where the magic begins.  OpenLibrary actually looks through the
	; library file contents trying to find this table by locating the
	; magic RTC_MATCHWORD value followed by its own address.  This
	; table then tells OpenLibrary where to find other things it needs.
	; This needs to be close to the front of your library file to cut
	; down on the amount of searching OpenLibrary does; that's why
	; this object file should be first in the link list.
	
RomTag:	dc.w	RTC_MATCHWORD	; Magic value to search for to find table
	dc.l	RomTag		; Address of matchword; the two together prevent accidental matches
	dc.l	LibEnd		; Address of end of library handler code
	dc.b	RTF_AUTOINIT	; Request system to automatically initialize our library
	dc.b	VERSION		; Version number of our library (defined below)
	dc.b	NT_DEVICE	; Node type = Device
	dc.b	0		; Node priority = 0 (normal)
	dc.l	LibName		; Name of this library file, for debugging info
	dc.l	LibIDString	; More debugging info
	dc.l	inittable	; Initialization table used by RTF_AUTOINIT

LibName:
	dc.b	"newser.device",0
Task1:	
	dc.b    "NewserRead",0
Task2:	
	dc.b    "NewserWrite",0
LibIDString:
	dc.b	"NewSer 1.1S (21 Mar 1991)",13,10,0
timername
	dc.b	'timer.device',0
	
	ds.w	0

inittable:
	dc.l	md_SIZE         	; Size of our library base struct
	dc.l	functable	        ; Where our function addresses are
	dc.l	datatable       	; Initialization info for our library base struct
	dc.l	LibInit	        	; Library initialization routine address

functable:
	dc.l	LibOpen			; Addresses of all library functions
	dc.l	LibClose		; First 4 MUST be provided, in this order
	dc.l	LibExpunge
	dc.l	LibExtFunc
	dc.l	LibBeginIO       	; The meat of the library.
	dc.l	LibAbortIO	
	dc.l	-1		    
		                     
		                     	

	; Things for the system to automatically initialize for us via RTF_AUTOINIT request
datatable:
	INITBYTE	LN_TYPE,NT_DEVICE
	INITLONG	LN_NAME,LibName
	INITBYTE	LIB_FLAGS,LIBF_SUMUSED | LIBF_CHANGED
	INITWORD	LIB_VERSION,VERSION
	INITWORD	LIB_REVISION,REVISION
	INITLONG	LIB_IDSTRING,LibIDString
	dc.l	0


;This routine gets called after the device has been allocated.  The device
;pointer is in D0.  The AmigaDOS segment list is in a0.  If it returns the
;device pointer, then the device will be linked into the device list.  If it
;returns NULL, then the device will be unloaded.
;
;This call is single-threaded by exec; please read the description for
;"Open" below.
;
; Register Usage
; ==============
; a3 -- Points to temporary RAM
; a4 -- Expansion library base
; a5 -- device pointer
; a6 -- Exec base

LibInit:
	PUTDEBUG 100,<'Init: called'>
	movem.l	d0-d7/a0-a6,-(sp)	;Preserve ALL modified registers
	movea.l	d0,a5           	;Device ptr
	move.l	a6,md_SysLib(a5)	;Save a pointer to exec
	move.l	a0,md_SegList(a5)       ;Save pointer to our loaded code

;Now fill in the prefs in MyDev with reasonable defaults. Jeff has suggested
;starting a process and reading them from a file.

	movea.l	a5,a6
	bsr.b	SetDefaultPrefs
	move.l	a5,d0			;a5 set to 0 above if an error occured
	PUTDEBUG 100,<'Init: finished'>
	movem.l	(sp)+,d0-d7/a0-a6
	rts

;Enter with device ptr in a6.

SetDefaultPrefs
	movem.l	d0/d1/a0/a1,-(sp)
	lea	prefs_unit0(a6),a1
	moveq	#MD_NUMUNITS-1,d1
1$
	move.w	#prefs_SIZE-1,d0
	lea	defaultprefs(pc),a0
2$
	move.b	(a0)+,(a1)+
	dbra	d0,2$
	dbra	d1,1$
	movem.l	(sp)+,d0/d1/a0/a1
	rts

;Here begins the system interface commands.  When the user calls OpenDevice/
;CloseDevice/RemDevice, this eventually gets translated into a call to the
;following routines (Open/Close/Expunge).  Exec has already put our device
;pointer in a6 for us.
;
;Open sets the IO_ERROR field on an error. If it was successful, we should
;also set up the IO_UNIT and LN_TYPE fields.  exec takes care of setting up
;IO_DEVICE.
;
;NOTE: We must also copy the current prefs for this unit into the user's
;extended iorequest fields.
;
; Register Usage
; ==============
; d0 -- unitnum
; d1 -- flags
; a1 -- iob
; a6 -- Device ptr

LibOpen:
	addq.w	#1,LIB_OPENCNT(a6)      ;Fake an opener for duration of call
	PUTDEBUG 100,<'Open: called'>
	movem.l	d2/a2-a4,-(sp)
	movea.l	a1,a2                   ;Save the iob
	cmp.l	#MD_NUMUNITS,d0 	;See if the unit number is in range
	bcc.w	Open_Range_Error	;Unit number out of range (BHS)
	move.l	d0,d2           	;Save unit number
	lsl.l	#2,d0           	;See if the unit is already initialized
	lea	md_Units(a6,d0.l),a4
	move.l	(a4),d0
	bne.b	Open_UnitOK
		                	;Try to conjure up a unit
	bsr.w	InitUnit        	;scratch:a3 unitnum:d2 devpoint:a6
	move.l	(a4),d0         	;See if it initialized OK
	beq.w	Open_Error
	movea.l	d0,a3
	bra.b	Open_UnitOK1

Open_UnitOK
	movea.l	d0,a3		;Unit pointer in a3
	tst.b	Exclusive(a3)	;Check for an exclusive access violation
	beq.w	Open_Error
Open_UnitOK1
	btst	#SERB_SHARED,IO_FLAGS(a2)
	seq	Exclusive(a3)
	movea.l	mdu_prefs(a3),a4	;Set the "7WIRE" flag

	bclr	#SERB_7WIRE,prefs_SERFLAGS(a4)
	btst	#SERB_7WIRE,IO_SERFLAGS(a2)
	beq.b	2$
	bset	#SERB_7WIRE,prefs_SERFLAGS(a4)

2$
	movea.l	a2,a1	;Copy the current internal prefs into the iorequest.
	bsr	CopyPrefs
	move.l	d0,IO_UNIT(a2)
	addq.w	#1,LIB_OPENCNT(a6)  ;Mark us as having another opener
	addq.w	#1,UNIT_OPENCNT(a3) ;Internal bookkeeping
	bclr	#LIBB_DELEXP,md_Flags(a6) ;Prevent delayed expunges
	moveq	#0,d0
	move.b	d0,IO_ERROR(a2)
	move.b	#NT_REPLYMSG,LN_TYPE(a2) ;Mark IORequest as "complete"
Open_End
	subq.w	#1,LIB_OPENCNT(a6) ;End of expunge protection
	movem.l	(sp)+,d2/a2-a4
	PUTDEBUG 100,<'Open: Finished!'>
	rts

Open_Range_Error:
Open_Error
	moveq	#IOERR_OPENFAIL,d0
	move.b	d0,IO_ERROR(a2)
	move.l	d0,IO_DEVICE(a2)    ;Trash IO_DEVICE on open failure
	PUTDEBUG 100,<'Open: failed'>
	bra.b	Open_End

;Copy prefs from our device struct to an IOrequest.  Enter with IORequest
;in a1 and unit pointer in a3.  All registers are preserved.

CopyPrefs
	movem.l	d0/a1/a4,-(sp)
	movea.l	mdu_prefs(a3),a4
	lea	IO_CTLCHAR(a1),a1
	move.w	#prefs_SIZE-1,d0
1$
	move.b	(a4)+,(a1)+
	dbra	d0,1$

	movem.l	(sp)+,d0/a1/a4
	rts 

;Copy prefs from an IOrequest to our device struct Enter with IORequest
;in a1 and unit pointer in a3.  All registers are preserved.

SetPrefs
	movem.l	d0/a1/a4,-(sp)
	movea.l	mdu_prefs(a3),a4
	lea	IO_CTLCHAR(a1),a1
	move.w	#prefs_SIZE-1,d0
1$
	move.b	(a1)+,(a4)+
	dbra	d0,1$

	movem.l	(sp)+,d0/a1/a4
	rts 

;There are two different things that might be returned from the Close
;routine.  If the device wishes to be unloaded, then Close must return
;the segment list (as given to Init).  Otherwise close MUST return NULL.
; ( device:a6, iob:a1 )

LibClose:
	movem.l	a2-a3,-(sp)
	PUTDEBUG 100,<'Close: called'>
	movea.l	a1,a2
	movea.l	IO_UNIT(a2),a3
	moveq	#-1,d0
	move.l	d0,IO_UNIT(a2)      ;We're closed...
	move.l	d0,IO_DEVICE(a2)    ;Customers not welcome at this IORequest!
	subq.w	#1,UNIT_OPENCNT(a3) ;See if the unit is still in use
	bne.b	Close_Device
	bsr	ExpungeUnit

Close_Device
	moveq	#0,d0
	subq.w	#1,LIB_OPENCNT(a6) ;Mark us as having one fewer openers
	bne.b	Close_End	 ;See if there is anyone left with us open
	btst	#LIBB_DELEXP,md_Flags(a6) ;See if we have a delayed expunge pending
	beq.b	Close_End
	bsr	LibExpunge	;Do the expunge
Close_End
	movem.l	(sp)+,a2-a3
	PUTDEBUG 100,<'Close: Finished!'>
	rts		;MUST return either zero or the SegList!

;Expunge is called by the memory allocator when the system is low on
;memory.
;
;There are two different things that might be returned from the Expunge
;routine.  If the device is no longer open then Expunge may return the
;segment list (as given to Init).  Otherwise Expunge may set the
;delayed expunge flag and return NULL.
;
;One other important note: because Expunge is called from the memory
;allocator, it may NEVER Wait() or otherwise take long time to complete.
;
; A6          - library base (scratch)
; D0-D1/A0-A1 - scratch

LibExpunge:
	PUTDEBUG 100,<'Expunge: called'>
	movem.l	d1/d2/a5/a6,-(sp)	;Save ALL modified registers
	movea.l	a6,a5
	movea.l	md_SysLib(a5),a6
	tst.w	LIB_OPENCNT(a5)	;See if anyone has us open
	beq.b	1$
	bset	#LIBB_DELEXP,md_Flags(a5)	;Set the delayed expunge flag
	moveq	#0,d0
	bra.b	Expunge_End

1$
	move.l	md_SegList(a5),d2 ;Store our seglist in d2
	movea.l	a5,a1	 ;Unlink from device list
	mySYS	Remove	 ;Remove first (before FreeMem)
	movea.l	a5,a1	 ;Devicebase
	moveq	#0,d0
	move.w	LIB_NEGSIZE(a5),d0
	suba.l	d0,a1	;Calculate base of functions
	add.w	LIB_POSSIZE(a5),d0 ;Calculate size of functions + data area
	mySYS	FreeMem
	move.l	d2,d0	;Set up our return value
	
Expunge_End
	movem.l	(sp)+,d1/d2/a5/a6
	rts

LibExtFunc:
	PUTDEBUG 100,<'Null: called'>
	moveq	#0,d0
	rts		;The "Null" function MUST return NULL.


;This is the main unit initialization routine. It allocates memory for
;the device structure, calls SetUpUnit, then calls InitTask.

; ( d2:unit number, a3:scratch, a6:devptr )

InitUnit
	PUTDEBUG 100,<'InitUnit: called'>
	movem.l	d0/d1/a4/a2,-(sp)
	move.l	d2,d1
	mulu.w	#prefs_SIZE,d1
	lea	prefs_unit0(a6),a4
	adda.l	d1,a4

;Now A4 is a ptr to the prefs for this unit

	move.l	#mdu_SIZE,d0 ;Allocate unit memory
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	AllocMem
	movea.l	(sp)+,a6
	movea.l	d0,a3
	tst.l	d0
	bne	continitunit1

;Couldn't init the unit (out of memory).

ReturnVoid
	move.l	d2,d0	;Unit number
	lsl.l	#2,d0
	clr.l	md_Units(a6,d0.l) ;Set unit table
	movem.l	(sp)+,d0/d1/a4/a2
	rts

BadSetup
	bsr	KillTask
	bra.b	ReturnVoid

continitunit1
	moveq	#0,d0	;Don't need to re-zero it
	movea.l	a3,a2
	lea	mdu_Init(pc),a1
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	InitStruct
	movea.l	(sp)+,a6

	move.l	a5,-(sp)	;Look up DACIA base address
	lea	basetable(pc),a0
	moveq	#0,d0
	move.b	d2,d0
	lsl.l	#2,d0
	movea.l	0(a0,d0.w),a5 
	move.l	a5,daciabase(a3)

	move.b	#$7f,IER(a5)	 ;Disable DACIA interrupts

	bsr	SetUpUnit
	movea.l	(sp)+,a5
	tst.l	d0
	bne.b	BadSetup

	bsr InitTask	;Set up the two tasks for this unit
	tst.l d0
	beq.b BadSetup

;Done with InitUnit - fill in the proper md_Unit field in the device struct
;and return.
	move.l	d2,d0	;Unit number
	lsl.l	#2,d0
	move.l	a3,md_Units(a6,d0.l) ;Set unit table
	movem.l	(sp)+,d0/d1/a4/a2
	PUTDEBUG 100,<'InitUnit: finished! End of silence.'>
	rts

;This routine does the following:
;
;1. Allocate read buffer memory
;2. Initialize all the special MyDevUnit fields (but NOT the signals -
;   they must be allocated in the task's context)
;3. Call InitDACIA [Initialize/set-up DACIA registers (for this unit).]
;4. Install an interrupt server.
;
;Returns failure code in d0 - zero if OK, -1 if out of memory, 1 if
;unable to open timer.device, 2 if invalid parm
;
;SetUpUnit(d2:unit number, a3:unit, a6:devptr)
;AND...ptr to prefs in A4
;uses d1,a0,a1,a2,a6

SetUpUnit
	PUTDEBUG 100,<'SetUpUnit: called'>
	movem.l	d1/a0/a1/a2,-(sp)
	move.l	prefs_RBUFLEN(a4),d0
	moveq	#64,d1
	cmp.l	d1,d0
	bhi.b	SUU
	move.l	d1,d0
	move.l	d0,prefs_RBUFLEN(a4)
SUU
	move.l	#MEMF_PUBLIC,d1
	myEXEC	AllocMem
	tst.l	d0
	bne.b	continitunit
	moveq	#-1,d0	;Failed to allocate input buffer memory - exit.
	movem.l	(sp)+,d1/a0/a1/a2
	rts

;Initialize all the special MyDevUnit fields (but NOT the signals -
;they must be allocated in the task's context)

continitunit
	bsr	BufPtrSetup	;Fill in head, tail, startbuf, and endbuf
	move.l	a4,mdu_prefs(a3)
	move.l	(SysBase).w,mdu_SysLib(a3) 
	lea	timerport+MP_MSGLIST(a3),a0
	NEWLIST	a0	              ;Init the unit's timer MsgPort's list
	move.b	d2,mdu_UnitNum(a3)    ;Initialize unit number
	move.l	a6,mdu_Device(a3)     ;Initialize device pointer
	move.l	a3,mdu_is+IS_DATA(a3) ;Pass unit addr to interrupt server
	move.b	#$FF,xstate(a3)
	move.b	#$80,frstate(a3)

	lea	timername(pc),a0	;Open the timer device.
	moveq	#0,d0 ;UNIT_MICROHZ
	lea	timeriorequest(a3),a1
	moveq	#0,d1 ;no special flags
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	OpenDevice
	movea.l	(sp)+,a6
	tst.l	d0
	bne	OpenTimerFailed

;Initialize/set-up DACIA registers (for this unit).
;(according to the prefs pointed to by a4) 

	bsr	InitDACIA
	tst.l	d0
	bne	InitDACIAfailed 

;Install an interrupt server.

	lea	mdu_is(a3),a1
	moveq	#INTB_EXTER,d0	;EXTER (level 6) interrupt (=13)
	movea.l	md_SysLib(a6),a6

	ifne	INFO_LEVEL	;If any debugging enabled at all
	move.l	a1,-(sp)
	move.l	a3,-(sp)
	PUTDEBUG 200,<'At AddIntServer, unit= %lx and mdu_is= %lx'>
	addq.l	#8,sp
	endc

	jsr	_LVOAddIntServer(a6) 
	movea.l	mdu_Device(a3),a6
	moveq	#0,d0 ;situation under control
	movem.l	(sp)+,d1/a0/a1/a2
	PUTDEBUG 100,<'SetUpUnit: Finished!'>
	rts

InitDACIAfailed
	moveq	#2,d0
	bra.b	EndSetUpUnit

OpenTimerFailed
	moveq	#1,d0
EndSetUpUnit
	move.l	d0,-(sp)
	move.l	prefs_RBUFLEN(a4),d0
	movea.l	startbuf(a3),a1
	myEXEC	FreeMem
	move.l	(sp)+,d0
	movem.l	(sp)+,d1/a0/a1/a2
	rts

;*** end of SetUpUnit ***

;Initialize/set-up DACIA registers (for this unit).
;(according to the prefs pointed to by a4) 
;d0.b will mirror CTR, d1.b will mirror FMR
;Returns error code in d0 - zero if successful.
;uses d1,d3,d5,a0

InitDACIA
	PUTDEBUG 100,<'InitDACIA: Called.'>
	movem.l	d1/d3/d5/a0,-(sp)

;"During power-on initialization, all readable registers should be read to
; assure that the status registers are initialized."

	move.b	ISR(a5),d0
	move.b	CSR(a5),d0
	move.b	RDR(a5),d0

	move.b	frstate(a3),d0	;Set DTR* and RTS* low (assert)
	and.b	#$FC,d0 	;Clear bits zero and one
	move.b	d0,frstate(a3)
	move.b	d0,FMR(a5)

	moveq	#0,d0
	bset	#6,d0	;Always access ACR, never CDR
	move.b	frstate(a3),d1
  
	move.l	prefs_BAUD(a4),d3 ;First, set proper baud rate.
	swap	d3
	tst.w	d3
	bne	InvParm
	swap	d3
	lea	baudtable(pc),a0
	moveq	#15,d5 
baudloop
	cmp.w	(a0)+,d3
	dbeq	d5,baudloop
	bne	InvParm
	not.b	d5
	and.b	#$0F,d5
	or.b	d5,d0    

	ifne	INFO_LEVEL
	clr.l	-(sp)
	move.b	d0,3(sp)
	PUTDEBUG 200,<'InitDACIA: Baud rate = %lx'>
	addq.l	#4,sp
	endc

	cmpi.b	#1,prefs_STOPBITS(a4) ;Next, set number of stop bits per character
	beq.b	onestop
	bset	#5,d0
onestop
	move.b	d0,CTR(a5)
	move.b	prefs_READLEN(a4),d3 ;Set number of data bits per char
	cmp.b	prefs_WRITELEN(a4),d3
	bne	InvParm	;We don't support different read & write char lengths
	subq.b	#5,d3
	bmi	InvParm
	cmp.b	#3,d3
	bhi	InvParm
	lsl.b	#5,d3
	or.b	d3,d1

	btst	#SEXTB_MSPON,prefs_EXTFLAGS+3(a4) ;Set parity
	beq.b	EvenOdd
	bset	#2,d1
	btst	#SEXTB_MARK,prefs_EXTFLAGS+3(a4)
	bne.b	UseMark
	or.b	#24,d1	;Use space
	bra.b	NoParity

UseMark
	or.b	#16,d1
	bra.b	NoParity

EvenOdd
	btst	#SERB_PARTY_ON,prefs_SERFLAGS(a4)
	beq.b	NoParity
	bset	#2,d1

	btst	#SERB_PARTY_ODD,prefs_SERFLAGS(a4)
	bne.b	UseOdd
	or.b	#8,d1
UseOdd:
NoParity
	move.b	d1,frstate(a3)
	move.b	d1,FMR(a5)
	moveq	#0,d0
	movem.l	(sp)+,d1/d3/d5/a0
	PUTDEBUG 100,<'InitDACIA: Finished!'>
	rts

InvParm
	PUTDEBUG 100,<'InitDACIA: Invalid Parm!'>
	moveq	#-1,d0
	movem.l	(sp)+,d1/d3/d5/a0
	rts

;*** end of InitDACIA ***

;(this is a subroutine for InitUnit)
;This routine sets up the two tasks (one for reading, one for writing).
;Returns zero in d0.l if an error occured else a ptr to the unit.
;
;uses d1-d4 and a0-a5

InitTask
	PUTDEBUG 100,<'InitTask: called.'>
	movem.l	d1-d4/a0-a5,-(sp)
	lea	ReadTask_Begin(pc),a5 ;Set up the read task
	lea	mdu_rstack(a3),a0
	lea	mdu_rtcb(a3),a1
	movea.l	a3,a2                 ;Read message port
	bsr.b	InitTaskStruct

	lea	WriteTask_Begin(pc),a5 ;Set up the write task
	lea	mdu_wstack(a3),a0
	lea	mdu_wtcb(a3),a1
	lea	mdu_wport(a3),a2
	move.l	a3,TC_EXCEPTDATA(a1)
	lea	breakexception(pc),a4
	move.l	a4,TC_EXCEPTCODE(a1) 
	bsr.b	InitTaskStruct

	move.l	a3,d0	;Mark us as ready to go
	PUTDEBUG 100,<'InitTask: ok'>

;Return zero in d0.l if an error occured, else a ptr to the unit

	movem.l	(sp)+,d1-d4/a0-a5
	rts

;Start up the unit task.  We do a trick here --we set his message port to
;PA_IGNORE until the new task has a change to set it up.  We cannot go to
;sleep here: it would be very nasty if someone else tried to open the unit
;(exec's OpenDevice has done a Forbid() for us --we depend on this to become
;single threaded).
;
;Enter with:
;a0 - ptr to low end of stack
;a1 - ptr to tcb (task control block)
;a3 - unit pointer
;a5 - starting address of task
;a2 - ptr to a (uninitialized) message port
;
;uses a0-a3 and d0,d1

InitTaskStruct
	movem.l	d0/d1/a0-a3,-(sp)
	move.l	a0,TC_SPLOWER(a1)	;Initialize the stack information
	lea	MYPROCSTACKSIZE(a0),a0  ;High end of stack
	move.l	a0,TC_SPUPPER(a1)
	move.l	a3,-(a0)        	;Argument - unit ptr (send on stack)
	move.l	a0,TC_SPREG(a1)
	move.l	a1,MP_SIGTASK(a2)

	ifge	INFO_LEVEL-30
	move.l	a1,-(sp)
	move.l	a3,-(sp)
	PUTDEBUG 100,<'InitUnit, unit= %lx, task=%lx'>
	addq.l	#8,sp
	endc

	lea	MP_MSGLIST(a2),a0
	NEWLIST	a0      	;Init the unit's MsgPort's list
	movea.l	a5,a2   	;Startup the task
	lea	(-1).l,a3	;generate address error
	         		;if task ever "returns" (we RemTask() it
	        		;to get rid of it...)
	moveq	#0,d0
	PUTDEBUG 100,<'About to add task'>
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	AddTask
	movea.l	(sp)+,a6
	movem.l	(sp)+,d0/d1/a0-a3
	rts

;Get rid of the unit's tasks.  We know this is safe because the unit has an
;open count of zero, so it is 'guaranteed' not in use.
;
;Kill the two tasks
; ( a3:unitptr, a6:deviceptr )
;
;uses a0,a1 and d0-d2

KillTask
	movem.l	a0/a1/d0-d2,-(sp)
	lea	mdu_rtcb(a3),a1
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	RemTask
	movea.l	(sp)+,a6

	lea	mdu_wtcb(a3),a1
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	RemTask
	movea.l	(sp)+,a6

	moveq	#0,d2
	move.b	mdu_UnitNum(a3),d2      ;Save the unit number
	bsr	FreeUnit        	;Free the unit structure
	lsl.l	#2,d2
	clr.l	md_Units(a6,d2.l)	;Clear out the unit vector in the device
	movem.l	(sp)+,a0/a1/d0-d2
	rts

; ( a3:unitptr, a6:deviceptr )
;uses a0,a1,d0,d1

FreeUnit
	movem.l	a0/a1/d0/d1,-(sp)
	movea.l	a3,a1
	move.l	#mdu_SIZE,d0
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	FreeMem
	movea.l	(sp)+,a6
	movem.l	(sp)+,a0/a1/d0/d1
	rts

; ( a3:unitptr, a6:deviceptr )
;
;uses a0,a1,a5,d0-d2

ExpungeUnit
	movem.l	a0/a1/a5/d0-d2,-(sp)
	PUTDEBUG 100,<'ExpungeUnit: called'>

	movea.l	daciabase(a3),a5
	move.b	#$7f,IER(a5)	;shut off all interrupts
	move.b	#$83,FMR(a5)	;Deassert DTR* and RTS*

	bsr	FreeResources

	lea	mdu_rtcb(a3),a1
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	RemTask
	movea.l	(sp)+,a6

	lea	mdu_wtcb(a3),a1
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	RemTask
	movea.l	(sp)+,a6

	moveq	#0,d2
	move.b	mdu_UnitNum(a3),d2 ;Save the unit number
	bsr	FreeUnit	;Free the unit structure.
	lsl.l	#2,d2
	clr.l	md_Units(a6,d2.l) ;Clear out the unit pointer in the device

	movem.l	(sp)+,a0/a1/a5/d0-d2
	rts

;This routines frees up resources specific to this driver (the other
;resources are taken care of by the skeleton).
;Be careful with this routine, so as not to pull the rug out from
;underneath the driver...
;
;If you can expunge you unit, and each unit has it's own interrupts,
;you must remember to remove its interrupt server
;
;Enter with unit ptr in a3 and device ptr in a6
;
;uses a0,a1,a4,d0,d1


FreeResources
	PUTDEBUG 100,<'FreeResources: called'>
	movem.l	a0/a1/a4/d0/d1,-(sp)
	lea	mdu_is(a3),a1	;Point to interrupt structure
	moveq	#INTB_EXTER,d0	;Portia interrupt bit 13
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	RemIntServer	;Now remove the interrupt server
	movea.l	(sp)+,a6

	movea.l	startbuf(a3),a1	;Free input buffer memory
	movea.l	mdu_prefs(a3),a4
	move.l	prefs_RBUFLEN(a4),d0
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	FreeMem
	movea.l	(sp)+,a6

	lea	timeriorequest(a3),a1 ;Close timer device
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	CloseDevice
	movea.l	(sp)+,a6
	movem.l	(sp)+,a0/a1/a4/d0/d1
	PUTDEBUG 100,<'FreeResources: finished!'>
	rts


;BeginIO starts all incoming io.  The IO is either queued up for the
;unit task or processed immediately.
;
;BeginIO often is given the responsibility of making devices single
;threaded... so two tasks sending commands at the same time don't cause
;a problem.  Once this has been done, the command is dispatched via
;PerformIO.
;
;There are many ways to do the threading.  This example uses the
;UNITB_ACTIVE bit.  Be sure this is good enough for your device before
;using!  Any method is ok.  If immediate access can not be obtained, the
;request is queued for later processing.
;
;Some IO requests do not need single threading. These can be performed
;immediatley.
;
;IMPORTANT:
; The exec WaitIO() function uses the IORequest node type (LN_TYPE)
; as a flag.  If set to NT_MESSAGE, it assumes the request is
; still pending and will wait.  If set to NT_REPLYMSG, it assumes the
; request is finished.  It's the responsibility of the device driver
; to set the node type to NT_MESSAGE before returning to the user.
;
;Notes: This routine will look at io_command to determine which task
;to use. Break with QUEUEDBRK and Write requests go to the write
;task. Read requests (only) go to the read task. 
;Break requests without QUEUEDBRK initiate a write task exception.
;The actual break job is started, and finished, within that exception.
;At the end of the exception, the write task resumes whatever it was
;doing before the break.
;All other requests are performed immediately, i.e. within the caller's
;context.
;
;This routine uses a3 (and a0/a1/d0/d1, but those do not need to be
;preserved).
; ( iob: a1, device:a6 )

LibBeginIO
	ifge	INFO_LEVEL-1
	bchg.b	#1,($bfe001).l	;Blink the power LED
	endc

	ifge	INFO_LEVEL-3
	clr.l	-(sp)
	move.w	IO_COMMAND(a1),2(sp) ;Get entire word
	PUTDEBUG 100,<'BeginIO  --  %ld'>
	addq.l	#4,sp
	endc

	move.l	a3,-(sp)
	andi.b	#$f,IO_FLAGS(a1)
	move.b	#NT_MESSAGE,LN_TYPE(a1) ;So WaitIO() is guaranteed to work
	movea.l	IO_UNIT(a1),a3	;Bookkeeping -> what unit to play with
	move.w	IO_COMMAND(a1),d0

;Do a range check & make sure bad requests are rejected

	cmp.w	#MYDEV_END,d0	;Compare all 16 bits
	bhi	BeginIO_NoCmd	;no, reject it.  (bcc=bhs - unsigned)

;Process all immediate commands no matter what

	move.l	#IMMEDIATES,d1
	myDisable	a0	;<-- Ick, nasty stuff, but needed here.
	btst	d0,d1
	bne	BeginIO_Immediate

;See if the unit is STOPPED.  If so, queue the msg.

	btst	#MDUB_STOPPED,MDU_FLAGS(a3)
	bne	BeginIO_QueueMsg

;This is not an immediate command.  See if the device is busy.  If the device
;is not, do the command on the user schedule.  Else fire up the task.  This
;type of arbitration is essential for a device to reliably work with shared
;hardware.
;
;REMEMBER: Never Wait() on the user's schedule in BeginIO()!  The only
;exception is when the user has indicated it is ok by setting the "quick" bit.
;
;We need to queue the device.  mark us as needing task attention.  Clear the
;quick flag
;
;Note: We handle read, write, and break requests seperately and differently.

BeginIO_QueueMsg
	bclr	#IOB_QUICK,IO_FLAGS(a1) ;We did NOT complete this quickly
	cmpi.w	#CMD_READ,IO_COMMAND(a1)
	bne.b	notread

	bset	#UNITB_INREADTASK,UNIT_FLAGS(a3)
	myEnable	a0

	ifge	INFO_LEVEL-250
	move.l	a1,-(sp)
	move.l	a3,-(sp)
	PUTDEBUG 100,<'PutMsg: Port=%lx Message=%lx'>
	addq.l	#8,sp
	endc

	movea.l	a3,a0
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	PutMsg	;Port=a0, Message=a1
	movea.l	(sp)+,a6
	bra	BeginIO_End	;Return to caller before completing


notread
	cmpi.w	#CMD_WRITE,IO_COMMAND(a1)
	bne.b	handlebreak

queuewrt
	bset	#UNITB_INWRITETASK,UNIT_FLAGS(a3)
	myEnable	a0
	lea	mdu_wport(a3),a0


	ifge	INFO_LEVEL-250
	move.l	a1,-(sp)
	move.l	a0,-(sp)
	PUTDEBUG 100,<'PutMsg: Port=%lx Message=%lx'>
	addq.l	#8,sp
	endc

	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	PutMsg	;Port=a0, Message=a1
	movea.l	(sp)+,a6
	bra	BeginIO_End	;Return to caller before completing

handlebreak
	btst	#SERB_QUEUEDBRK,IO_SERFLAGS(a1)
	bne.b	queuewrt	;Handle just like a write request

;This is the tricky case. We initiate the break immediately.  To do this we
;cleverly (?) use the under-documented power of task exceptions.

	btst	#UNITB_INBREAK,UNIT_FLAGS(a3)
	bne	breakout
	bset	#UNITB_BREAKACTIVE,UNIT_FLAGS(a3)
	myForbid
	bset	#ioflagsB_Active,IO_FLAGS(a1)
	move.l	a1,breakiorequest(a3)
	myPermit
	move.l	breaksig(a3),d0
	lea	mdu_wtcb(a3),a1
	myEXEC	Signal
	myEnable	a0
	bra.b	BeginIO_End 

;Do it on the schedule of the calling process

BeginIO_Immediate
	myEnable	a0
	bsr	PerformIO

BeginIO_End
	PUTDEBUG 100,<'BeginIO_End'>
	movea.l	(sp)+,a3
	rts

BeginIO_NoCmd
	PUTDEBUG 100,<'BeginIO_NoCmd!'>
	move.b	#IOERR_NOCMD,IO_ERROR(a1)
	bra.b	BeginIO_End

breakout
	myEnable	a0
	bsr	TermIO
	bra	BeginIO_End

;PerformIO actually dispatches an io request.  It might be called from the
;task, or directly from BeginIO (thus on the callers's schedule)
;
;It expects a3 to already have the unit pointer in it.  a6 has the device
;pointer (as always).  a1 has the io request.  Bounds checking has already
;been done on the I/O Request.
;
;This routine itself uses d0,a0,a4,a5. The command it calls may choose
;to use any or all registers.
; ( iob:a1, unitptr:a3, devptr:a6 )

PerformIO
	ifge	INFO_LEVEL-150
	clr.l	-(sp)
	move.w	IO_COMMAND(a1),2(sp)  ;Get entire word
	PUTDEBUG 100,<'PerformIO -- %ld'>
	addq.l	#4,sp
	endc

	movem.l	d0/a0/a4/a5,-(sp)
	movea.l	mdu_prefs(a3),a4
	movea.l	daciabase(a3),a5
	moveq	#0,d0
	move.b	d0,IO_ERROR(a1)     ; No error so far
	move.b	IO_COMMAND+1(a1),d0 ;Look only at low byte
	lsl.w	#2,d0	            ; Multiply by 4 to get table offset
	lea	cmdtable(pc),a0
	movea.l	0(a0,d0.w),a0
	move.l  a0,d0
	cmpi.l  #$ffffffff,d0
	bne     ImmCont
	PUTDEBUG 200,<'PerformIO_NoCmd!'>
	move.b	#IOERR_NOCMD,IO_ERROR(a1)
	movem.l	(sp)+,d0/a0/a4/a5
	rts 


ImmCont
	movem.l	d0-d7/a0-a6,-(sp)
	jsr	(a0)    ;iob:a1  unit:a3  devprt:a6
	movem.l	(sp)+,d0-d7/a0-a6
	bsr.b	TermIO
	movem.l	(sp)+,d0/a0/a4/a5
	PUTDEBUG 100,<'PerformIO -- Finished!'>
	rts



;PerformIO
;	ifge	INFO_LEVEL-150
;	clr.l	-(sp)
;	move.w	IO_COMMAND(a1),2(sp)  ;Get entire word
;	PUTDEBUG 150,<'PerformIO -- %ld'>
;	addq.l	#4,sp
;	endc

;	movem.l	d0/a0/a4/a5,-(sp)
;	movea.l	mdu_prefs(a3),a4
;	movea.l	daciabase(a3),a5
;	moveq	#0,d0
;	move.b	d0,IO_ERROR(a1)     ; No error so far
;	move.b	IO_COMMAND+1(a1),d0 ;Look only at low byte
;	lsl.w	#2,d0		    ; Multiply by 4 to get table offset
;	lea	cmdtable(pc),a0
;	movea.l	0(a0,d0.w),a0

;	movem.l	d0-d7/a0-a6,-(sp)
;	jsr	(a0) 		   ;iob:a1  unit:a3  devprt:a6
;	movem.l	(sp)+,d0-d7/a0-a6
;	bsr.b	TermIO
;	movem.l	(sp)+,d0/a0/a4/a5
;	PUTDEBUG 15,<'PerformIO -- Finished!'>
;	rts


;TermIO sends the IO request back to the user.  It knows not to mark
;the device as inactive if this was an immediate request or if the
;request was started from the server task.
;
; ( iob:a1, unitptr:a3 )
;uses d0/d1/a0/a1

TermIO
	movem.l	d0/d1/a0/a1,-(sp)
	PUTDEBUG 100,<'TermIO'>
	move.w	IO_COMMAND(a1),d0
	move.l	#IMMEDIATES,d1
	btst	d0,d1
	bne.b	TermIO_Immediate	;IO was immediate, don't do task stuff...

	cmpi.w	#CMD_READ,IO_COMMAND(a1)
	bne.b	wrtterm

;We may need to turn the active bit off.

	btst	#UNITB_INREADTASK,UNIT_FLAGS(a3)
	bne.b	TermIO_Immediate	;IO came from task, don't clear ACTIVE...

;The task does not have more work to do

	bclr	#UNITB_READACTIVE,UNIT_FLAGS(a3)
	bra.b	TermIO_Immediate

;We may need to turn the active bit off.

wrtterm
	btst	#UNITB_INWRITETASK,UNIT_FLAGS(a3)
	bne.b	TermIO_Immediate	;IO came from task, don't clear ACTIVE...

;The task does not have more work to do

	bclr	#UNITB_WRITEACTIVE,UNIT_FLAGS(a3)


;If the quick bit is still set then we don't need to reply
;- msg - just return to the user.

TermIO_Immediate
	btst	#IOB_QUICK,IO_FLAGS(a1)
	bne.b	TermIO_End
	myEXEC	ReplyMsg	;a1-message
		        	;ReplyMsg sets the LN_TYPE to NT_REPLYMSG
TermIO_End
	movem.l	(sp)+,d0/d1/a0/a1
	rts

;Here begins the functions that implement the device commands
;all functions are called with:
;  a1 -- a pointer to the io request block
;  a3 -- a pointer to the unit
;  a4 -- a pointer to prefs
;  a5 -- a pointer to the unit hardware
;  a6 -- a pointer to the device
;
;Commands that conflict with 68000 instructions have a "My" prepended to them.

;Read: The handshaking lines don't serve any purpose when reading (except
;perhaps to say "shut up" if an exceptional condition occurs).
;
;d5 is used to count the number of bytes transferred

Read
	moveq	#0,d5
	move.l	IO_LENGTH(a1),d6
	movea.l	IO_DATA(a1),a2
	move.l	readsig(a3),d4
	add.l	readabortsig(a3),d4

	ifne	INFO_LEVEL	;If any debugging enabled at all
	move.l	d6,-(sp)
	PUTDEBUG 100,<'Read entered -- %ld bytes requested.'>
	addq.l	#4,sp
	endc
 
	bra.b	ReadEntry

readloop
	move.l	d4,d0
	myEXEC	Wait	;Wait for one or more bytes to come in
	and.l	readabortsig(a3),d0
	bne	readabort
NoErrorAtAll
	PUTDEBUG 100,<'Read: One or more bytes came in.'>

ReadEntry
	bsr	GetBytesInReadBuf

	ifne	INFO_LEVEL	;If any debugging enabled at all
	move.l	d0,-(sp)
	PUTDEBUG 100,<'Read: In fact, %ld bytes came in.'>
	addq.l	#4,sp
	endc

	tst.l	d0
	beq	readloop
	cmp.l	d0,d6
	bls.b	AllDone 	;Branch if d6 <= d0
	sub.l	d0,d6
	bsr	DumpReadBuf	;Get all the bytes we can
	beq	readloop  
	bra.b	endread 	;Go if TermArray caused early exit
 
;The number of bytes in the read buffer equals or exceeds the number of
;bytes the user wants.

AllDone
	PUTDEBUG 100,<'Read: AllDone'>
	move.l	d6,d0
	bsr	DumpReadBuf	;Dump d0 bytes of the read buffer into the user's buffer
endread
	PUTDEBUG 100,<'Read: Finished!'>
	bclr	#MDUB_V,MDU_FLAGS(a3)
	beq.b	NoOverflow
	move.b	#SerErr_BufOverflow,IO_ERROR(a1)
NoOverflow
	move.l	d5,IO_ACTUAL(a1)
	rts		;Done with read

;Something exceptional happened.  An informative error code should be
;returned.  first, check for error conditions...  Note that the serial.device
;standard does not provide a way to inform the caller of simultaneous error
;conditions.

readabort
	PUTDEBUG 100,<'readabort: Something exceptional happened.'>
	myDisable
	move.b	ISRcopy(a3),d2
	move.b	CSRcopy(a3),d3
	myEnable
	btst	#ISRB_PAR,d2
	beq.b	notpar

	move.b	#SerErr_ParityErr,IO_ERROR(a1) ;parity error
	bra	endread

notpar
	btst	#1,d2	;Check for frame err, overrun, & break
	beq	endread	;We infer that an abort has been issued

	btst	#CSRB_FE,d3	;Probe further...
	beq.b	noframe

	move.b	#SerErr_LineErr,IO_ERROR(a1) ;Framing Error
	bra	endread

noframe
	btst	#CSRB_RBRK,d3
	beq	NoErrorAtAll

;Note that we ignore receive overrun errors. Since there is no error
;code for it, I assume this is what serial.device does too.

	move.b	#SerErr_DetectedBreak,IO_ERROR(a1) ;Break
	bra	endread 

;d5 is used to count the number of bytes transferred

Write
	moveq	#0,d5
	move.l	IO_LENGTH(a1),d6

	ifne	INFO_LEVEL	;If any debugging enabled at all
	move.l	d6,-(sp)
	PUTDEBUG 100,<'Write entered -- %ld bytes requested.'>
	addq.l	#4,sp
	endc

	movea.l	IO_DATA(a1),a2
	move.l	tdresig(a3),d4
	add.l	writeabortsig(a3),d4
	btst	#SERB_7WIRE,prefs_SERFLAGS(a4)
	bne.b	write.hand
   
;Safety check: if CTS* is not asserted, return with an error code. This
;usually occurs when the port is not connected to anything.
;Note: This is just a hack - something more robust will be done later
;(hopefully).

;Enter with the number of bytes to transmit in d6
;Enter with a pointer to the user's data in a2

write.nohand
	btst	#CSRB_CTSL,CSR(a5)
	bne.b	not_connected

	bsr.b	transmit
	subq.l	#1,d6
	bne.b	write.nohand 
endwrite
	move.l	d5,IO_ACTUAL(a1)
	rts

not_connected:
	move.b	#SerErr_LineErr,IO_ERROR(a1)
	bra.b	endwrite

;Enter with the number of bytes to transmit in d6
;Enter with a pointer to the user's data in a2

;The handshaking protocol implemented here is based on the flowchart
;on page 5-66 of "An Introduction to Microcomputers" by Adam Osborne.

write.hand:
write.hand.loop
	btst	#CSRB_DSRL,CSR(a5)	;Test DSR*
	beq.b	writehandcont		;If low, proceed
	move.l	dsrsig(a3),d0
	add.l	writeabortsig(a3),d0
	myEXEC	Wait
	and.l	writeabortsig(a3),d0
	bne.b	aborthand
	bra.b	write.hand.loop

;The 65C52 halts any transmission until CTS* is asserted, so no code
;is involved. This can also be a curse if you don't desire handshaking.

writehandcont
	bsr.b	transmit	;Transmit a byte
	subq.l	#1,d6
	bne.b	write.hand.loop
aborthand
	bra.b	endwrite

;*** Subroutine for the write routines ***
;
;Returns 1 in d6 if aborted.

transmit
	btst	#ISRB_TDRE,ISR(a5) ;test TDRE
	bne	oktosend	;If set, we can load up the transmit reg immediately
	PUTDEBUG 100,<'Transmit: Entered.'>
	ori.b	#WRITEINTMASK,IERstate(a3) 
	move.b	#WRITEINT,IER(a5)
	move.l	d4,d0
	myEXEC	Wait	;Wait for TDRE to be set
	and.l	writeabortsig(a3),d0
	bne	aborttransmit
	PUTDEBUG 100,<'Transmit: TDRE signal received.'>
	bra	transmit	;Just to be sure

oktosend
	btst	#SERB_XDISABLED,prefs_SERFLAGS(a4) ;Handle xoff, if requested
	bne.b doit
txs
	tst.b xstate(a3)
	bne.b doit

	PUTDEBUG 100,<'Transmit: Waiting for XON!'>
	move.l xonsig(a3),d0
	add.l writeabortsig(a3),d0
	myEXEC	Wait	;Wait for an x-on signal before sending
	and.l writeabortsig(a3),d0
	bne.b aborttransmit
	bra.b txs

doit
	PUTDEBUG 100,<'Transmit: Received XON!'>
	tst.l	d6
	bmi.b	termzero
doit1
	move.b	(a2)+,TDR(a5)
	addq.l	#1,d5
	rts

termzero
	tst.b	(a2)
	bne.b	doit1
aborttransmit
	moveq	#1,d6
	rts
 
;*** end of write routines ***

;*** Subroutines for the read routine ***
;
;Return with the number of bytes in the read buffer in d0

GetBytesInReadBuf
	move.l	d1,-(sp)
	myDisable
	move.l	head(a3),d1
	move.l	tail(a3),d0
	sub.l	d1,d0
	bcc.b	ok.getbytes
	add.l	prefs_RBUFLEN(a4),d0
ok.getbytes
	myEnable
	move.l	(sp)+,d1
	rts


;Enter with number of bytes to read in d0
;Enter with a pointer to a dump buffer in a2
;the pointer in a2 is updated to reflect the current position
;
;This routine uses as scratch: a0,a6,d0,d3,d6,d7
;and updates the following: d5,a2
;Returns with zero flag set if all is OK, or zero cleared if a TermArray
;match was found.

DumpReadBuf
	myDisable
	movea.l	head(a3),a0
	move.l	a0,-(sp)
	adda.l	d0,a0
	cmpa.l	endbuf(a3),a0
	bls.b	notend.drb
	suba.l	prefs_RBUFLEN(a4),a0
notend.drb
	move.l	a0,head(a3)
	myEnable
	movea.l	(sp)+,a0

;Note that only the buffer pointers are protected. The main copy loop
;(below) could, however, return somewhat incoherent data if the buffer
;happens to overflow.

	btst	#SERB_EOFMODE,IO_SERFLAGS(a1)
	bne	eofdump 

drb.loop
	move.b	(a0)+,d6
	bsr.b	xcode
	move.b	d6,(a2)+
	addq.l	#1,d5
	cmpa.l	endbuf(a3),a0
	bls.b	notend.drb2
	movea.l	startbuf(a3),a0
notend.drb2
	subq.l	#1,d0
	bne.b	drb.loop 
	rts


;Check for xon/xoff

xcode
	ifne	INFO_LEVEL
	move.l	prefs_CTLCHAR(a4),-(sp)
	PUTDEBUG 100,<'XCODE: prefs_CTLCHAR = %lx'>
	addq.l	#4,sp
	endc

	ifne	INFO_LEVEL
	clr.l	-(sp)
	move.b	d6,3(sp)
	PUTDEBUG 100,<'XCODE: Current char = %lx'>
	addq.l	#4,sp
	endc

	cmp.b	prefs_CTLCHAR(a4),d6	;Xon check
	beq.b	setxon
	cmp.b	prefs_CTLCHAR+1(a4),d6	;Xoff check
	beq.b	setxoff
	rts

setxon
	PUTDEBUG 100,<'XCODE: XON!'>
	tst.b	xstate(a3)
	bne.b	sxoq	;If already on, don't signal!
	st	xstate(a3)
	movem.l	d0/d1/a0/a1/a6,-(sp)
	move.l	xonsig(a3),d0
	lea	mdu_wtcb(a3),a1
	movea.l	(SysBase).w,a6
	jsr	_LVOSignal(a6)
	movem.l	(sp)+,d0/d1/a0/a1/a6
sxoq
	rts

setxoff
	PUTDEBUG 100,<'XCODE: XOFF!'>
	clr.b	xstate(a3)
	rts

eofdump:
eofdumploop:
	moveq	#7,d7
	lea	prefs_TERMARRAY(a4),a6
	move.b	(a0)+,d6
	bsr	xcode
cmploop
	cmp.b	(a6)+,d6
	dbcc	d7,cmploop
	beq.b	termread    
	move.b	d6,(a2)+
	addq.l	#1,d5
	cmpa.l	endbuf(a3),a0
	bls.b	notend.drb1
	movea.l	startbuf(a3),a0
notend.drb1:   
	subq.l	#1,d0
	bne.b	eofdumploop 
	rts

termread
	moveq	#-1,d0
	rts

;This routine handles queued breaks. Non-queued breaks are handled by an
;exception (which uses similar but not identical code).

Break
	PUTDEBUG 100,<'Break: called'>
	bset	#UNITB_INBREAK,UNIT_FLAGS(a3)
	move.b	#2,ACR(a5) ;start break
	lea	timeriorequest(a3),a1
	move.w	#TR_ADDREQUEST,IO_COMMAND(a1)
	clr.l	TV_SECS(a1)
	move.l	prefs_BRKTIME(a4),d0
	ori.b	#$FF,d0	;To avoid the V33/V34 bug

	ifne	INFO_LEVEL	;If any debugging enabled at all
	move.l	d0,-(sp)
	PUTDEBUG 100,<'Break: TV_MICRO=%ld'>
	addq.l	#4,sp
	endc

	move.l	d0,TV_MICRO(a1)
	movea.l	MN_REPLYPORT(a1),a0
	move.b	MP_SIGBIT(a0),d2
	myEXEC	SendIO
	moveq	#0,d0
	bset	d2,d0
	add.l	writeabortsig(a3),d0

	PUTDEBUG 100,<'Break: Waiting...'>

	myEXEC	Wait
	and.l	writeabortsig(a3),d0
	beq.b	breakOK1

	PUTDEBUG 100,<'Break: Aborted!'>

	myEXEC	AbortIO	;The break was aborted. Clean up.
	myEXEC	WaitIO 
 
breakOK1
	move.b	#0,ACR(a5) ;stop the break
	bclr	#UNITB_INBREAK,UNIT_FLAGS(a3)

	PUTDEBUG 100,<'Break: Finished!'>
	rts

;AbortIO() is a REQUEST to "hurry up" processing of an IORequest.
;If the IORequest was already complete, nothing happens (if an IORequest
;is quick or LN_TYPE=NT_REPLYMSG, the IORequest is complete).
;The message must be replied with ReplyMsg(), as normal.
;
;Note that AbortIO is called directly, not via BeginIO/PerformIO.
;The only other direct functions are Open, Close, Expunge, and of course
;BeginIO.
;
; ( iob: a1, device:a6 )
;returns an error code in d0 - zero if successful.
;If sucessful, AbortIO returns IOERR_ABORTED in IO_ERROR and sets bit
;5 of IO_FLAGS.
;
;This routine uses d0/d1/a0, but they do not need to be saved.

LibAbortIO
	PUTDEBUG 100,<'AbortIO: called'>
	myForbid
	move.b	#IOERR_ABORTED,IO_ERROR(a1) ;We always say we succeed(ed)
	bset	#5,IO_FLAGS(a1)
	cmpi.b	#NT_REPLYMSG,LN_TYPE(a1)	;Already complete?
	beq.b	complete

;Check to see whether or not the IORequest is being processed 

	btst	#ioflagsB_Active,IO_FLAGS(a1)
	bne.b	inprogress
	bset	#ioflagsB_Ignore,IO_FLAGS(a1)

complete
	myPermit
	moveq	#0,d0
	PUTDEBUG 100,<'AbortIO: Finished!'>
	rts

inprogress
	movea.l	IO_UNIT(a1),a3	;IO is in progress - abort it.
	cmpi.w	#CMD_READ,IO_COMMAND(a1)
	bne	ip1
	move.l	readabortsig(a3),d0
	lea	mdu_rtcb(a3),a1
ip0
	myEXEC	Signal
	bra.b	complete

ip1
	cmpi.w	#CMD_WRITE,IO_COMMAND(a1)
	bne.b	ip2
ip3
	move.l	writeabortsig(a3),d0
	lea	mdu_wtcb(a3),a1
	bra.b	ip0

;The break command is handled here.
;We use the same abort signal (as write), so handle just as with write.

ip2
	bra.b ip3

Invalid
	move.b  #IOERR_NOCMD,IO_ERROR(a1)
	rts

;Clear invalidates all internal buffers.
;
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device

MyClear
	myDisable
	move.l	startbuf(a3),d0
	move.l	d0,head(a3)
	move.l	d0,tail(a3)
	myEnable
	rts

; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
 
MyReset
	PUTDEBUG 100,<'MyReset: called'>
	myForbid
	move.b	IERstate(a3),d7
	clr.b	IERstate(a3)
	move.b	#$7f,IER(a5)	;Disable ACIA interrupts
	bsr	Flush   	;Flush pending requests

;Abort current IO, if any IO is indeed occuring

	move.l	a1,-(sp)
	btst	#UNITB_BREAKACTIVE,UNIT_FLAGS(a3)
	beq	NoBreakActive
	movea.l	breakiorequest(a3),a1
	bsr	LibAbortIO

NoBreakActive
	btst	#UNITB_WRITEACTIVE,UNIT_FLAGS(a3)
	beq	WriteNotActive
	movea.l	WriteRequestPtr(a3),a1
	bsr	LibAbortIO

WriteNotActive
	btst	#UNITB_READACTIVE,UNIT_FLAGS(a3)
	beq	NothingActive
	movea.l	ReadRequestPtr(a3),a1
	bsr	LibAbortIO

NothingActive
	movea.l	(sp)+,a1 
	bsr	SetDefaultPrefs
	bsr	CopyPrefs  
	bsr	FreeResources
	bsr	SetUpUnit

	tst.l	d0      	;Check for a possible error condition
	bmi.b	OutOfMem
	subq.l	#1,d0
	beq.b	TimerError
	subq.l	#1,d0
	beq.b	ParamError
 
	move.b	d7,IERstate(a3)	;Enable DACIA interrupts again
	bset	#7,d7
	move.b	d7,IER(a5)
	myPermit
	clr.l	IO_ACTUAL(a1)
	PUTDEBUG 100,<'MyReset: Finished!'>
	rts

ParamError
	move.b	#SerErr_InvParam,IO_ERROR(a1)
	bra.b	MyResetFailed

TimerError
	move.b	#SerErr_TimerErr,IO_ERROR(a1)
	bra.b	MyResetFailed

OutOfMem
	move.b	#SerErr_BufErr,IO_ERROR(a1)
MyResetFailed
	myPermit
	clr.l	IO_ACTUAL(a1)
	PUTDEBUG 100,<'MyReset: Error!'>
	rts

; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
;
;Return number of chars in buffer in IO_ACTUAL
;Fill in IO_STATUS

Query
	PUTDEBUG 100,<'Query: called.'>
	bsr	GetBytesInReadBuf
	move.l	d0,IO_ACTUAL(a1)

	ifne	INFO_LEVEL  ;If any debugging enabled at all
	move.l	d0,-(sp)
	PUTDEBUG 100,<'Query: %ld bytes in buf.'>
	move.l	(sp)+,d0
	endc

	moveq	#0,d0 ;d0 will mirror IO_STATUS
	move.b	CSR(a5),d1
	btst	#0,d1
	beq.b	Q1
	bset	#6,d0
Q1
	btst	#1,d0
	beq.b	Q2
	bset	#7,d0
Q2
	btst	#3,d1
	beq.b	Q3
	bset	#3,d0
Q3
	btst	#4,d1
	beq.b	Q4
	bset	#5,d0
Q4
	btst	#5,d1
	beq.b	Q5
	bset	#4,d0
Q5
	btst	#UNITB_BREAKACTIVE,UNIT_FLAGS(a3)
	beq.b	NB
	bset	#9,d0
NB
	btst	#UNITB_INBREAK,UNIT_FLAGS(a3)
	beq.b	NB1
	bset	#9,d0
NB1
	btst	#2,d1
	beq.b	NRB
	bset	#10,d0
NRB
	tst.b	xstate(a3)
	bne.b	xIsOn
	bset	#11,d0
xIsOn
	move.w	d0,IO_STATUS(a1)
	rts

; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device

SetParams:
	bclr	#SERB_XDISABLED,prefs_SERFLAGS(a4)
	btst	#SERB_XDISABLED,IO_SERFLAGS(a1) ;Set the XDISABLED thing
	beq.b	SetP1
	bset	#SERB_XDISABLED,prefs_SERFLAGS(a4)
SetP1:
	beq.b	SetP2
	st	xstate(a3)

;Now check to see whether the device is busy, i.e. any current or pending requests.

SetP2
	myForbid
	move.b	UNIT_FLAGS(a3),d0
	and.b	#$3c,d0 ;anything going on at the moment?
	bne	DevBusy
	lea	$14(a3),a0	;Read port - anything there?
	movea.l	(a0),a2
	tst.l	(a2)
	bne	DevBusy
	lea	mdu_wport+$14(a3),a0 ;Write port - anything there?
	movea.l	(a0),a2
	tst.l	(a2)
	bne	DevBusy

;Ok, the device is not busy. Set all params.

	bsr	SetPrefs	;First copy the data

;Note that this is for compatability only; it does not speed up this driver.

	btst	#SERB_RAD_BOOGIE,IO_SERFLAGS(a1) ;Check for RAD_BOOGIE
	beq.b	SkipBoogie
	bclr	#SEXTB_MSPON,prefs_EXTFLAGS+3(a4)
	bclr	#SERB_PARTY_ON,prefs_SERFLAGS(a4)
	bset	#SERB_XDISABLED,prefs_SERFLAGS(a4)
	move.b	#8,prefs_READLEN(a4)
	move.b	#8,prefs_WRITELEN(a4)

SkipBoogie
	bsr	InitDACIA	;Then set up the chip 
	tst.l	d0
	bne	PInvP

;* Deallocate the old read buffer and allocate a new one. *

	myDisable
	move.l	a1,-(sp)	;Free input buffer memory
	movea.l	startbuf(a3),a1
	movea.l	mdu_prefs(a3),a4
	move.l	prefs_RBUFLEN(a4),d0
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	FreeMem
	movea.l	(sp)+,a6

	move.l	prefs_RBUFLEN(a4),d0 ;Allocate buffer mem
	moveq	#64,d1
	cmp.l	d1,d0
	bhi.b	SPmem
	move.l	d1,d0
	move.l	d0,prefs_RBUFLEN(a4)
SPmem
	move.l	#MEMF_PUBLIC,d1
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	AllocMem
	movea.l	(sp)+,a6
	movea.l	(sp)+,a1
	tst.l	d0
	bne.b	SPMemOK

	move.b	#SerErr_BufErr,IO_ERROR(a1)
	moveq	#64,d0
	move.l	d0,prefs_RBUFLEN(a4)
	lea	AltBuf(a3),a0
	move.l	a0,d0
SPMemOK
	bsr.b	BufPtrSetup
	myEnable
SPLx
	myPermit
	rts

PInvP
	move.b	#SerErr_InvParam,IO_ERROR(a1)
	bra.b	SPLx

BufPtrSetup
	move.l	d0,head(a3)
	move.l	d0,tail(a3)
	move.l	d0,startbuf(a3)
	add.l	prefs_RBUFLEN(a4),d0
	subq.l	#1,d0
	move.l	d0,endbuf(a3)
	rts

DevBusy
	myPermit
	move.b	#SerErr_DevBusy,IO_ERROR(a1)
	rts


; This is the code that I added to support the ASDG command SetCtrlLines.
; This will change the DTR or the RTS lines without external software
; playing directly with the hardware. This (seems to me) to be a better
; idea then what you have to do on the serial.device. BBS type programs
; need this type of support. Mike Mossman ( March 25, 1991)
 
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device

SetCtrlLines
       	move.l	IO_OFFSET(a1),d0 ;Get the mask into d0
        move.l	IO_LENGTH(a1),d1 ;Get the bits for Setting/Clearing into d1
        and.l   d0,d1            ;And the two, to get the bits that need to be changed
	not.l   d1               ;not them for 6552 logic
	ori.b   #%11111100,d1    ;set the hi bit for FR access
	move.b	frstate(a3),d0	 ;Get current state
	bset.l  #0,d0
	bset.l  #1,d0
	bset.l  #7,d0    
	and.b   d1,d0           ;Set DTR and RTS as requested (a low is assert)
	move.b	d0,frstate(a3)  ;Save the bit setting in the unit
	move.b	d0,FMR(a5)	;Actually set the bits
        rts



;The Stop command stop all future io requests from being processed until a
;Start command is received. The Stop command is NOT stackable: e.g. no matter
;how many stops have been issued, it only takes one Start to restart
;processing.

MyStop
	PUTDEBUG 100,<'MyStop: called'>
	bset	#MDUB_STOPPED,MDU_FLAGS(a3)
	rts

Start
	PUTDEBUG 100,<'Start: called'>
	bsr.b	InternalStart
	rts

;[A3=unit A6=device]

InternalStart
	PUTDEBUG 100,<'InternalStart: called'>
	movea.l	a1,a2
	;Turn processing back on
	bclr	#MDUB_STOPPED,MDU_FLAGS(a3)

;Kick the tasks to start them moving

	move.b	MP_SIGBIT(a3),d1	;First the read task...
	moveq	#0,d0
	bset	d1,d0	;Prepared signal mask
	movea.l	MP_SIGTASK(a3),a1 ;FIXED: marco-task to signal
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	Signal	;FIXED: marco-a6 not a3
	movea.l	(sp)+,a6

	movea.l	a2,a1	;Then the write task
	lea	mdu_wport(a3),a0
	move.b	MP_SIGBIT(a0),d1
	moveq	#0,d0
	bset	d1,d0	;Prepared signal mask
	movea.l	MP_SIGTASK(a0),a1 ;FIXED: marco-task to signal
	move.l	a6,-(sp)
	movea.l	md_SysLib(a6),a6
	mySYS	Signal	;FIXED: marco-a6 not a3
	movea.l	(sp)+,a6
	movea.l	a2,a1
	PUTDEBUG 100,<'InternalStart: Finished!'>
	rts

;Flush pulls all I/O requests off the queue and sends them back.  We must be
;careful not to destroy work in progress, and also that we do not let some io
;requests slip by.
;
;Some funny magic goes on with the STOPPED bit in here.  Stop is defined as
;not being reentrant.  We therefore save the old state of the bit and then
;restore it later.  This keeps us from needing to DISABLE in flush.  It also
;fails miserably if someone does a start in the middle of a flush. (A
;semaphore might help...)


Flush
	PUTDEBUG 100,<'Flush: called'>
	movem.l	d2/a1/a6,-(sp)
	movea.l	md_SysLib(a6),a6
	bset	#MDUB_STOPPED,MDU_FLAGS(a3)
	sne	d2

ReadFlush_Loop
	movea.l	a3,a0
	mySYS	GetMsg	;Steal messages from task's port
	tst.l	d0
	beq.b	WriteFlush_Loop

	movea.l	d0,a1
	move.b	#IOERR_ABORTED,IO_ERROR(a1)
	mySYS	ReplyMsg
	bra.b	ReadFlush_Loop

WriteFlush_Loop
	lea	mdu_wport(a3),a0
	mySYS	GetMsg	;Steal messages from task's port
	tst.l	d0
	beq.b	Flush_End

	movea.l	d0,a1
	move.b	#IOERR_ABORTED,IO_ERROR(a1)
	mySYS	ReplyMsg
	bra.b	WriteFlush_Loop

Flush_End
	move.l	d2,d0
	movem.l	(sp)+,d2/a1/a6
	tst.b	d0
	bne.b	1$
	bsr	InternalStart
1$
	PUTDEBUG 100,<'Flush: Finished!'>
	rts

;Here begins the task related routines
;
;A Task is provided so that queued requests may be processed at
;a later time.  This is not very justifiable for a ram disk, but
;is very useful for "real" hardware devices.  Take care with
;your arbitration of shared hardware with all the multitasking
;programs that might call you at once.
;
; Register Usage
; ==============
; a3 -- unit pointer
; a6 -- syslib pointer
; a5 -- device pointer
; a4 -- task (NOT process) pointer
; d7 -- wait mask
;----------------------------------------------------------------------
;
;Note: Signals must be allocated within this (the task's) context!
;The task is responsible for enabling the right DACIA interrupts...AFTER
;it has done its setup (like allocating signals).
;
;NOTE: We actually have two tasks, each with their own separate
;      code (but shared data). First comes the read task...:


	cnop	0,4     ;Long word align

ReadTask_Begin
	PUTDEBUG 200,<'ReadTask_Begin'>
	movea.l	(SysBase).w,a6

;Grab the arguments passed down from our parent

	movea.l	4(sp),a3		;Unit pointer
	movea.l	mdu_Device(a3),a5	;Point to device structure

	lea	readsig(a3),a2
	moveq	#1,d6			;Number of signals to allocate-1
ReadSigLoop
	moveq	#-1,d0	;-1 is any signal at all
	mySYS	AllocSignal		;Allocate signals for I/O interrupts
	moveq	#0,d7			;Convert bit number signal mask
	bset	d0,d7
	move.l	d7,(a2)+		;Save in unit structure
	dbra	d6,ReadSigLoop

	moveq	#-1,d0			;-1 is any signal at all
	mySYS	AllocSignal		;Allocate a signal
	move.b	d0,MP_SIGBIT(a3)
	move.b	#PA_SIGNAL,MP_FLAGS(a3) ;Make message port "live"

;Change the bit number into a mask, and save in d7

	moveq	#0,d7	;Clear D7
	bset	d0,d7

	ifge	INFO_LEVEL-40
	move.l	$114(a6),-(sp)
	move.l	a5,-(sp)
	move.l	a3,-(sp)
	move.l	d0,-(sp)
	PUTDEBUG 200,<'ReadTask -- Signal=%ld, Unit=%lx Device=%lx Task=%lx'>
	adda.l	#4*4,sp
	endc

;Enable read-related ACIA interrupts

	move.l	a5,-(sp)
	movea.l	daciabase(a3),a5
	ori.b	#READINTMASK,IERstate(a3)
	move.b	#READINT,IER(a5)
	movea.l	(sp)+,a5
	bra.b	ReadTask_StartHere

;OK, kids, we are done with initialization.  We now can start the main loop
;of the driver.  It goes like this.  Because we had the port marked PA_IGNORE
;for a while (in InitUnit) we jump to the getmsg code on entry.  (The first
;message will probably be posted BEFORE our task gets a chance to run).
;  wait for a message
;  lock the device
;  get a message.  If no message, unlock device and loop
;  dispatch the message
;  loop back to get a message

;No more messages.  Back ourselves out.

                 
ReadTask_Unlock
         andi.b	#$ff&(~(UNITF_READACTIVE!UNITF_INREADTASK)),UNIT_FLAGS(a3)

;Main loop: wait for a new message

ReadTask_MainLoop
	PUTDEBUG 200,<'ReadTask ++Sleep'>
	move.l	d7,d0
	mySYS	Wait
	ifge	INFO_LEVEL-5
	bchg.b	#1,($bfe001).l 		 ;Blink the power LED
	endc

ReadTask_StartHere
	PUTDEBUG 200,<'ReadTask ++Wakeup'>
	btst	#MDUB_STOPPED,MDU_FLAGS(a3) ;See if we are stopped
	bne	ReadTask_MainLoop	;Device is stopped, ignore messages
	bset	#UNITB_READACTIVE,UNIT_FLAGS(a3) ;Lock the device
	bne	ReadTask_MainLoop	;Device in use (immediate command?)

ReadTask_NextMessage:
	movea.l	a3,a0
	mySYS	GetMsg			;Get the next request
	PUTDEBUG 200,<'ReadTask GotMsg'>
	tst.l	d0
	beq	ReadTask_Unlock 	; no message?

	movea.l	d0,a1			;Do this request
	exg	a5,a6			;Put device ptr in right place

	myForbid
	btst	#ioflagsB_Ignore,IO_FLAGS(a1)
	bne.b	Readignorecmd
	bset	#ioflagsB_Active,IO_FLAGS(a1)
	move.l	a1,ReadRequestPtr(a3)
	myPermit
	bsr	PerformIO	;Do it!
	PUTDEBUG 200,<'Read Did PerformIO'>
	bclr	#ioflagsB_Active,IO_FLAGS(a1)

;No longer active - abort has stopped sending signals. Now we can
;(and should) clear the abort signal.

	moveq	#0,d0
	move.l	readabortsig(a3),d1
	myEXEC	SetSignal
	bra.b	Readconttl

Readignorecmd
	myPermit
	clr.l	IO_ACTUAL(a1)
	bsr	TermIO
	PUTDEBUG 200,<'Read Did TermIO'>
Readconttl
	exg	a5,a6		;Get ExecBase back in a6
	bra	ReadTask_NextMessage

;**** End of read task code ****

;**** Beginning of write task code ****

	cnop	0,4	;Long word align

; Register Usage
; ==============
; a3 -- unit pointer
; a6 -- syslib pointer
; a5 -- device pointer
; a4 -- task (NOT process) pointer
; d7 -- wait mask

WriteTask_Begin
	PUTDEBUG 100,<'WriteTask_Begin'>
	movea.l	(SysBase).w,a6

;Grab the arguments passed down from our parent

	movea.l	4(sp),a3	;Unit pointer
	movea.l	mdu_Device(a3),a5	;Point to device structure
	lea	tdresig(a3),a2
	moveq	#4,d6		;Number of signals to allocate-1
WriteSigLoop
	moveq	#-1,d0	        ;-1 is any signal at all
	mySYS	AllocSignal	;Allocate signals for I/O interrupts
	moveq	#0,d7		;Convert bit number signal mask
	bset	d0,d7
	move.l	d7,(a2)+	;Save in unit structure
	dbra	d6,WriteSigLoop

	move.l	breaksig(a3),d0
	move.l	d0,d1
	mySYS	SetExcept	;Make breaksig an exception-causing signal

;Allocate a signal for the timer message port

	moveq	#-1,d0		;-1 is any signal at all
	mySYS	AllocSignal
	lea	timerport(a3),a2
	move.b	d0,MP_SIGBIT(a2)
	move.l	a2,timeriorequest+MN_REPLYPORT(a3)
	suba.l	a1,a1
	mySYS	FindTask
	move.l	d0,MP_SIGTASK(a2)    
	move.b	#PA_SIGNAL,MP_FLAGS(a2) ;Make message port "live"

;Allocate a signal for the cmd message port

	moveq	#-1,d0	;-1 is any signal at all
	mySYS	AllocSignal
	move.b	d0,mdu_wport+MP_SIGBIT(a3)
	move.b	#PA_SIGNAL,mdu_wport+MP_FLAGS(a3) ;Make message port "live"

;Change the bit number into a mask, and save in d7

	moveq	#0,d7	;Clear D7
	bset	d0,d7

	ifge	INFO_LEVEL-40
	move.l	ThisTask(a6),-(sp)
	move.l	a5,-(sp)
	move.l	a3,-(sp)
	move.l	d0,-(sp)
	PUTDEBUG 100,<'WriteTask -- Signal=%ld, Unit=%lx Device=%lx Task=%lx'>
	adda.l	#4*4,sp
	endc

;Enable write-related ACIA interrupts

	move.l	a5,-(sp)
	movea.l	daciabase(a3),a5
	ori.b	#HANDINTMASK,IERstate(a3)
	move.b	#HANDINT,IER(a5)
	movea.l	(sp)+,a5
	bra.b	WriteTask_StartHere

;No more messages.  Back ourselves out.

WriteTask_Unlock
	andi.b	#$ff&(~(UNITF_WRITEACTIVE!UNITF_INWRITETASK)),UNIT_FLAGS(a3)

;Main loop: wait for a new message

WriteTask_MainLoop
	PUTDEBUG 100,<'WriteTask ++Sleep'>
	move.l  d7,d0
	mySYS	Wait
	
	
	ifge INFO_LEVEL-5
	bchg.b	#1,($bfe001).l  ;Blink the power LED
	endc

WriteTask_StartHere
	PUTDEBUG 100,<'WriteTask ++Wakeup'>
	btst    #MDUB_STOPPED,MDU_FLAGS(a3) ;See if we are stopped
	bne     WriteTask_MainLoop	;Device is stopped, ignore messages
	bset    #UNITB_WRITEACTIVE,UNIT_FLAGS(a3) ;Lock the device
	bne     WriteTask_MainLoop	;Device in use (immediate command?)

WriteTask_NextMessage:
	lea	mdu_wport(a3),a0
	mySYS	GetMsg			;Get the next request
	PUTDEBUG 100,<'WriteTask GotMsg'>
	tst.l	d0
	beq	WriteTask_Unlock	;No message?
	movea.l	d0,a1			;Do this request
	exg	a5,a6			;Put device ptr in right place

	myForbid
	btst	#ioflagsB_Ignore,IO_FLAGS(a1)
	bne.b	Writeignorecmd
	bset	#ioflagsB_Active,IO_FLAGS(a1)
	move.l	a1,WriteRequestPtr(a3)
	myPermit
	bsr	PerformIO	;Do it!
	bclr	#ioflagsB_Active,IO_FLAGS(a1)

;No longer active - abort has stopped sending signals. Now we can
;(and should) clear the abort signal.

	moveq	#0,d0
	move.l	writeabortsig(a3),d1
	myEXEC	SetSignal
	bra.b	Writeconttl

Writeignorecmd
	myPermit
	clr.l	IO_ACTUAL(a1)
	bsr	TermIO
Writeconttl
	exg	a5,a6			; get syslib back in a6
	bra	WriteTask_NextMessage

;***** end of write task code *****

;Exception Handler (performs a break command)
;Note that this code acts as a handler for the write task only; the
;read task has no such handler.
;
;unit ptr in a1, execbase in a6
;All registers are saved/restored by Exec
;Note: Not all the usual newser register conventions are used here

breakexception
	PUTDEBUG 100,<'BreakException: called'>
	move.l	d0,-(sp)	;Save exception bits
	movea.l	a1,a3
	movea.l	a1,a2
	btst	#UNITB_BREAKACTIVE,UNIT_FLAGS(a3) ;Sanity check
	beq	endbreakex
	movea.l	daciabase(a3),a5
	move.b	#2,ACR(a5) ;start break
	lea	timeriorequest(a3),a1
	move.w	#TR_ADDREQUEST,IO_COMMAND(a1)
	movea.l	mdu_prefs(a3),a4
	clr.l	TV_SECS(a1)
	move.l	prefs_BRKTIME(a4),d0
	ori.b	#$FF,d0	;To avoid the V33/V34 bug
	move.l	d0,TV_MICRO(a1)

	ifne	INFO_LEVEL	;If any debugging enabled at all
	move.l	d0,-(sp)
	PUTDEBUG 100,<'BreakException: TV_MICRO=%ld'>
	addq.l	#4,sp
	endc

	movea.l	MN_REPLYPORT(a1),a0
	move.b	MP_SIGBIT(a0),d2
	jsr	_LVOSendIO(a6)

	ifne	INFO_LEVEL	;If any debugging enabled at all
	clr.l	-(sp)
	move.b	d2,3(sp)
	PUTDEBUG 100,<'BreakException: Waiting for signal #%ld'>
	addq.l	#4,sp
	endc

;Take note - waiting within an exception is tricky!

	suba.l	a1,a1
	jsr	_LVOFindTask(a6)
	movea.l	d0,a4	  	  ;ThisTask
	moveq	#0,d0
	bset	d2,d0
	add.l	writeabortsig(a3),d0
	move.l	TC_SIGWAIT(a4),d3 ;Save this -- very important!!!
	jsr	_LVOWait(a6)
	move.l	d3,TC_SIGWAIT(a4) ;Restore -- very important!!!
	and.l	writeabortsig(a3),d0
	beq.b	breakOK

	PUTDEBUG 100,<'BreakException: Aborted!'>

	lea	timeriorequest(a3),a1 ;The break was aborted. Clean up.
	jsr	_LVOAbortIO(a6)

	lea	timeriorequest(a3),a1
	jsr	_LVOWaitIO(a6)
 
breakOK
	move.b	#0,ACR(a5)
	movea.l	breakiorequest(a3),a1
	clr.b	IO_ERROR(a1)
	btst	#IOB_QUICK,IO_FLAGS(a1)
	bne.b	endbreakex
	jsr	_LVOReplyMsg(a6) 
endbreakex
	move.l	(sp)+,d0 ;restore exception bits
	PUTDEBUG 100,<'BreakException: Finished!'>
	rts

;Initialize the device

mdu_Init			    ;Initialize read task message port/tcb
	INITBYTE MP_FLAGS,PA_IGNORE ;Unit starts with a message port
	INITBYTE LN_TYPE,NT_MSGPORT
	INITLONG LN_NAME,Task1
	INITLONG mdu_rtcb+LN_NAME,Task1
	INITBYTE mdu_rtcb+LN_TYPE,NT_TASK
	INITBYTE  mdu_rtcb+LN_PRI,5

;Initialize write task message port/tcb

	INITBYTE mdu_wport+MP_FLAGS,PA_IGNORE ;Unit starts with a message port
	INITBYTE mdu_wport+LN_TYPE,NT_MSGPORT
	INITLONG mdu_wport+LN_NAME,Task2
	INITLONG mdu_wtcb+LN_NAME,Task2
	INITBYTE mdu_wtcb+LN_TYPE,NT_TASK
	INITBYTE mdu_wtcb+LN_PRI,126

;Initialize interrupt structure (except for is_data of course).

	INITBYTE  mdu_is+LN_TYPE,NT_INTERRUPT
	INITBYTE  mdu_is+LN_PRI,-127	;Int priority 11
	INITLONG  mdu_is+IS_CODE,int	;Interrupt routine addr
	INITLONG  mdu_is+LN_NAME,LibName

;Initialize timer message port

	INITBYTE timerport+MP_FLAGS,PA_IGNORE ;Unit starts with a message port
	INITBYTE timerport+LN_TYPE,NT_MSGPORT
	INITLONG timerport+LN_NAME,LibName

	dc.w	0

;******************** Interrupt code *********************************

;Notes:
;
;Guru's Guide #1: Interrupts is helpful.
;------ ----- --- ----------
;
;NOTE: I am using one interrupt server for each unit. I think it's
;simpler then using one each for the read and write tasks.
;
;This is the interrupt server code. It serves four purposes:
;1. Read in a byte if available, store it, and signal the read task
;2. Check for TDRE-empty condition and signal write task
;3. Check for a DSR* transition and signal write task IF handshake is wanted
;4. Check for an exceptional condition and signal read task
;
;Enter with data pointer in a1 (unit pointer)
;d0, d1, a0, a1, a5, and a6 are scratch
;
;Returns Z bit clear if the interrupt was not caused by the dacia,
;otherwise return with the Z bit set.

int
	move.l	a3,-(sp)
	move.w	d7,-(sp)
	movea.l	a1,a3
	movea.l	daciabase(a3),a5
	move.b	ISR(a5),d7	;This clears CTST, DCDT, and DSRT ints
	and.b	IERstate(a3),d7	;Quick check (note that this masking is very
			        ;important)
	bne.b	serveint	;Yes, service the dacia
	move.w	(sp)+,d7	;Not for us
	movea.l	(sp)+,a3
	PUTDEBUG 200,<'Int: Not for us!'>
	moveq	#0,d0
	rts  

serveint
	ifne	INFO_LEVEL	;If any debugging enabled at all
	clr.l	-(a7)
	move.b	d7,3(a7)
	PUTDEBUG 200,<'Int: Entered with ISR=%lx'>
	addq.l	#4,sp
	endc

;Read error condition?
;(We must check this first because a read of the RDR clears the error flags.)

	move.b	d7,d0
	and.b	#6,d0
	beq.b	contint	;No errors

	PUTDEBUG 200,<'Int: Read error condition.'>

;An exceptional condition occured - signal the read task

	move.b	d7,ISRcopy(a3)	;Useful info
	move.b	CSR(a5),CSRcopy(a3) ;More useful info
	move.l	readabortsig(a3),d0
	lea	mdu_rtcb(a3),a1
	movea.l	mdu_SysLib(a3),a6
	jsr	_LVOSignal(a6)

	move.b	RDR(a5),d0	;This forces a clear of the error bits
	bra	rdrfempty

contint
	btst	#ISRB_RDRF,d7	;Test Receive Data Buffer Full
	beq.b	rdrfempty

	PUTDEBUG 200,<'Int: Read.'>


;Store the byte in the circular buffer.  Note that this code *can't* be
;interrupted, so we're safe (that's because CIA B, which we're plugging into,
;is connected to INT6*).

	movea.l	tail(a3),a0	;Read and store
	move.b	RDR(a5),(a0)+
	cmpa.l	endbuf(a3),a0
	bls.b	notend	;endbuf >= tail
	movea.l	startbuf(a3),a0	;Wrap around
notend
	move.l	a0,tail(a3) 
	move.l	head(a3),d0
	cmpa.l	d0,a0
	bne.b	nocross
	bset	#MDUB_V,MDU_FLAGS(a3) ;set buf overflow flag
	addq.l	#1,d0
	move.l	d0,head(a3)
	cmp.l	endbuf(a3),d0
	bls.b	nocross
	move.l	startbuf(a3),head(a3)  
nocross
	movea.l	mdu_SysLib(a3),a6
	move.l	readsig(a3),d0
	lea	mdu_rtcb(a3),a1
	jsr	_LVOSignal(a6)	;Signal the read task

rdrfempty
	btst	#ISRB_TDRE,d7	;Check TDRE
	beq.b	notdre

	PUTDEBUG 200,<'Int: Write signal.'>
	andi.b	#WRITEOFFMASK,IERstate(a3)
	move.b	#WRITEOFF,IER(a5)
	move.l	tdresig(a3),d0
	lea	mdu_wtcb(a3),a1
	movea.l	mdu_SysLib(a3),a6
	jsr	_LVOSignal(a6)	;Signal the write task

;see if handshaking ("7 wire") is enabled

notdre
	movea.l	mdu_prefs(a3),a6
	btst	#SERB_XDISABLED,prefs_SERFLAGS(a6)
	bne.b	nohand

	btst	#ISRB_DSRT,d7	;Check for DSR* transition
	beq.b	nohand

	PUTDEBUG 200,<'Int: DSR transition.'>

	move.l	dsrsig(a3),d0
	lea	mdu_wtcb(a3),a1
	movea.l	mdu_SysLib(a3),a6
	jsr	_LVOSignal(a6)	;Signal the write task

nohand:
termchain
	PUTDEBUG 200,<'Int: Finished!'>
	move.w	(sp)+,d7
	movea.l	(sp)+,a3
	moveq	#1,d0	;Terminate the chain
	rts


defaultprefs
	dc.l	SER_DEFAULT_CTLCHAR     ;prefs_CTLCHAR
	dc.l	16*1024         	;prefs_RBUFLEN
	dc.l	0                	;prefs_EXTFLAGS
	dc.l	1200            	;prefs_BAUD
	dc.l	250000          	;prefs_BRKTIME
	dc.l	0                	;prefs_TERMARRAY
	dc.l	0               	;prefs_TERMARRAY
	dc.b	8                	;prefs_READLEN
	dc.b	8               	;prefs_WRITELEN
	dc.b	1                	;prefs_STOPBITS
	dc.b	$88             	;prefs_SERFLAGS

;Table of DACIA base addresses for the 4 units

basetable
	dc.l	ACIA_Base
	dc.l	ACIA_Base+UNIT2
	dc.l	ACIA_Base+ACIA1
	dc.l	ACIA_Base+ACIA1+UNIT2

;List of supported baud rates 

baudtable
	dc.w	50
	dc.w	110
	dc.w	135
	dc.w	150
	dc.w	300
	dc.w	600
	dc.w	1200
	dc.w	1800
	dc.w	2400
	dc.w	3600
	dc.w	4800
	dc.w	7200
	dc.w	9600
	dc.w	19200
	dc.w	38400
	dc.w	31250	;MIDI (external clock)


cmdtable
	dc.l   Invalid	    ;$00000001  ;0  CMD_INVALID
	dc.l   MyReset	    ;$00000002  ;1  CMD_RESET
	dc.l   Read 	    ;$00000004  ;2  CMD_READ
	dc.l   Write 	    ;$00000008  ;3  CMD_WRITE
	dc.l   Invalid	    ;$00000010  ;4  CMD_UPDATE (update has no meaning here)
	dc.l   MyClear	    ;$00000020  ;5  CMD_CLEAR
	dc.l   MyStop	    ;$00000040  ;6  CMD_STOP
	dc.l   Start 	    ;$00000080  ;7  CMD_START
	dc.l   Flush 	    ;$00000100  ;8  CMD_FLUSH
	dc.l   Query	    ;$00000200  ;9  SDCMD_QUERY
	dc.l   Break	    ;$00000400  ;10 SDCMD_BREAK
	dc.l   SetParams    ;$00000800  ;11 SDCMD_SETPARAMS
	dc.l   $FFFFFFFF    ;$00001000  ;12 NULL
	dc.l   $FFFFFFFF    ;$00002000  ;13 NULL
	dc.l   $FFFFFFFF    ;$00004000  ;14 NULL
	dc.l   $FFFFFFFF    ;$00008000  ;15 NULL
	dc.l   SetCtrlLines ;$00010000  ;16 SIOCMD_SETCTRLLINES
cmdtable_end:

	ISDEBUG	'newser'	;This name for debugging use
         
	 dc.l  0
	
LibEnd:
	END


