*****************************************************************************
* 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
*
* [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.
*****************************************************************************
*
* Also, some of the following is:
*
*****************************************************************************
*
* Copyright (C) 1986, Commodore Amiga Inc.  All rights reserved.
* Permission granted for non-commercial use
*
*****************************************************************************

;Set Tabs           |       |                 |       |

	ifnd	__m68
	fail	'Wrong assembler!'
	endc
	exeobj
	objfile	'ram:newser.device'
	macfile 'Includes:IOexp.i'	;The One & Only include file

INFO_LEVEL	equ	0	;Assembly-time option

;The first executable location.  This should return an error in case someone
;tried to run us as a program (instead of loading us as a device).

FirstAddress	moveq	#-1,d0
	rts

;A romtag structure.  After your driver is brought in from disk, the
;disk image will be scanned for this structure to discover magic constants
;about you (such as where to start running you from...).

initDDescrip	dw	RTC_MATCHWORD	;UWORD	RT_MATCHWORD (Magic cookie)
	dl	initDDescrip	;APTR	RT_MATCHTAG  (Back pointer)
	dl	EndCode	;APTR	RT_ENDSKIP   (To end of this hunk)
	db	RTF_AUTOINIT	;UBYTE	RT_FLAGS     (magic-see "Init")
	db	VERSION	;UBYTE	RT_VERSION
	db	NT_DEVICE	;UBYTE	RT_TYPE      (must be correct)
	db	MYPRI	;BYTE	RT_PRI
	dl	myName	;APTR	RT_NAME      (exec name)
	dl	idString	;APTR	RT_IDSTRING  (text string)
	dl	Init	;APTR	RT_INIT

	db	'newser.device - Copyright (c) 1990,91 by Dan Babcock. '
	db	'Contact me on People/Link or Usenet!'
	even

;*********************** Tables (constant) **********************

defaultprefs	dl	SER_DEFAULT_CTLCHAR ;prefs_CTLCHAR
	dl	16*1024	;prefs_RBUFLEN
	dl	0	;prefs_EXTFLAGS
	dl	1200	;prefs_BAUD
	dl	250000	;prefs_BRKTIME
	dl	0	;prefs_TERMARRAY
	dl	0	;prefs_TERMARRAY
	db	8	;prefs_READLEN
	db	8	;prefs_WRITELEN
	db	1	;prefs_STOPBITS
	db	$88	;prefs_SERFLAGS

;Table of DACIA base addresses for the 4 units

basetable	dl	ACIA_Base
	dl	ACIA_Base+UNIT2
	dl	ACIA_Base+ACIA1
	dl	ACIA_Base+ACIA1+UNIT2

;List of supported baud rates 

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

timername	cstr	'timer.device'

myName	MYDEVNAME	;This is the name that the device will have

;This is an identifier tag to help in supporting the device
;format is 'name version.revision (dd MON yyyy)',<cr>,<lf>,<null>

idString	MYIDENT

;The romtag specified that we were "RTF_AUTOINIT".  This means that the
;RT_INIT structure member points to one of these tables below.  If the
;AUTOINIT bit was not set then RT_INIT would point to a routine to run.

Init	dl	MyDev_Sizeof	;data space size
	dl	funcTable	;pointer to function initializers
	dl	dataTable	;pointer to data initializers
	dl	initRoutine	;routine to run

funcTable	dl	Open	;standard system routines
	dl	_Close
	dl	Expunge
	dl	Null	;Reserved for future use!
	dl	BeginIO	;my device definitions
	dl	AbortIO
	dl	-1	;function table end marker

;The data table initializes static data structures. The format is
;specified in exec/InitStruct routine's manual pages.  The
;INITBYTE/INITWORD/INITLONG macros are in the file "exec/initializers.i".
;The first argument is the offset from the device base for this
;byte/word/long. The second argument is the value to put in that cell.
;The table is null terminated

dataTable	INITBYTE LN_TYPE,NT_DEVICE ;Must be LN_TYPE!
	INITLONG LN_NAME,myName
	INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
	INITWORD LIB_VERSION,VERSION
	INITWORD LIB_REVISION,REVISION
	INITLONG LIB_IDSTRING,idString
	dw	 0	;terminate list

;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

initRoutine	PUTDEBUG 5,<'Init: called'>
	movem.l	d1-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 5,<'Init: finished'>
	movem.l	(sp)+,d1-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	#serprefs_sizeof-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

Open	addq.w	#1,(LIB_OPENCNT,a6) ;Fake an opener for duration of call
	PUTDEBUG 20,<'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 30,<'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 2,<'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	#serprefs_sizeof-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	#serprefs_sizeof-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 )

_Close	movem.l	a2-a3,-(sp)
	PUTDEBUG 20,<'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	Expunge	;Do the expunge
Close_End	movem.l	(sp)+,a2-a3
	PUTDEBUG 20,<'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

Expunge	PUTDEBUG 10,<'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
	SYS	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
	SYS	FreeMem
	move.l	d2,d0	;Set up our return value
Expunge_End	movem.l	(sp)+,d1/d2/a5/a6
	rts

Null	PUTDEBUG 1,<'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 30,<'InitUnit: called'>
	movem.l	d0/d1/a4/a2,-(sp)
	move.l	d2,d1
	mulu.w	#serprefs_sizeof,d1
	lea	(prefs_unit0,a6),a4
	adda.l	d1,a4

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

	move.l	#MyDevUnit_Sizeof,d0 ;Allocate unit memory
	move.l	#MEMF_PUBLIC!MEMF_CLEAR,d1
	move.l	a6,-(sp)
	movea.l	(md_SysLib,a6),a6
	SYS	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
	SYS	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 30,<'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 30,<'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
	EXEC	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
	SYS	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 30,<'At AddIntServer, a3= %lx and a1= %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 30,<'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
	EXEC	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 30,<'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 30,<'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 30,<'InitDACIA: Finished!'>
	rts

InvParm	PUTDEBUG 30,<'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 30,<'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 30,<'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 30,<'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 30,<'About to add task'>
	move.l	a6,-(sp)
	movea.l	(md_SysLib,a6),a6
	SYS	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
	SYS	RemTask
	movea.l	(sp)+,a6

	lea	(mdu_wtcb,a3),a1
	move.l	a6,-(sp)
	movea.l	(md_SysLib,a6),a6
	SYS	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	#MyDevUnit_Sizeof,d0
	move.l	a6,-(sp)
	movea.l	(md_SysLib,a6),a6
	SYS	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 10,<'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
	SYS	RemTask
	movea.l	(sp)+,a6

	lea	(mdu_wtcb,a3),a1
	move.l	a6,-(sp)
	movea.l	(md_SysLib,a6),a6
	SYS	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 10,<'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
	SYS	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
	SYS	FreeMem
	movea.l	(sp)+,a6

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

;Here begins the device functions
;
;Cmdtable is used to look up the address of a routine that will
;implement the device command.

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
cmdtable_end:

;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	%00000000000000000000101111110011
;		 --------========--------========
;		 FEDCBA9876543210FEDCBA9876543210

;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 )

BeginIO	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 3,<'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
	Disable	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)
	Enable	a0

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

	movea.l	a3,a0
	move.l	a6,-(sp)
	movea.l	(md_SysLib,a6),a6
	SYS	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)
	Enable	a0
	lea	(mdu_wport,a3),a0

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

	move.l	a6,-(sp)
	movea.l	(md_SysLib,a6),a6
	SYS	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)
	Forbid
	bset	#ioflagsB_Active,(IO_FLAGS,a1)
	move.l	a1,(breakiorequest,a3)
	Permit
	move.l	(breaksig,a3),d0
	lea	(mdu_wtcb,a3),a1
	EXEC	Signal
	Enable	a0
	bra.b	BeginIO_End 

;Do it on the schedule of the calling process

BeginIO_Immediate	Enable	a0
	bsr	PerformIO

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

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

breakout	Enable	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 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 160,<'TermIO'>
	move.w	(IO_COMMAND,a1),d0
	move.w	#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
	EXEC	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 150,<'Read entered -- %ld bytes requested.'>
	addq.l	#4,sp
	endc
 
	bra.b	ReadEntry

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

ReadEntry	bsr	GetBytesInReadBuf

	ifne	INFO_LEVEL	;If any debugging enabled at all
	move.l	d0,-(sp)
	PUTDEBUG 150,<'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 150,<'Read: AllDone'>
	move.l	d6,d0
	bsr	DumpReadBuf	;Dump d0 bytes of the read buffer into the user's buffer
endread	PUTDEBUG 150,<'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 150,<'readabort: Something exceptional happened.'>
	Disable
	move.b	(ISRcopy,a3),d2
	move.b	(CSRcopy,a3),d3
	Enable
	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 150,<'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
	EXEC	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 150,<'Transmit: Entered.'>
	ori.b	#WRITEINTMASK,(IERstate,a3) 
	move.b	#WRITEINT,(IER,a5)
	move.l	d4,d0
	EXEC	Wait	;Wait for TDRE to be set
	and.l	(writeabortsig,a3),d0
	bne	aborttransmit
	PUTDEBUG 150,<'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 5,<'Transmit: Waiting for XON!'>
	move.l (xonsig,a3),d0
	add.l (writeabortsig,a3),d0
	EXEC	Wait	;Wait for an x-on signal before sending
	and.l (writeabortsig,a3),d0
	bne.b aborttransmit
	bra.b txs

doit	PUTDEBUG 5,<'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)
	Disable
	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	Enable   
	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	Disable
	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)
	Enable
	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 5,<'XCODE: prefs_CTLCHAR = %lx'>
	addq.l	#4,sp
	endc

	ifne	INFO_LEVEL
	clr.l	-(sp)
	move.b	d6,(3,sp)
	PUTDEBUG 5,<'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 5,<'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 5,<'XCODE: XOFF!'>
	clr.b	(xstate,a3)
	rts

eofdump:
eofdumploop:
	moveq	#7,d7
	lea	(prefs_TERMARRAY,a4),a6
	move.b	(a0)+,d6
	bsr	xcode
..	cmp.b	(a6)+,d6
	dbcc	d7,..eofcmploop
	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 5,<'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 5,<'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
	EXEC	SendIO
	moveq	#0,d0
	bset	d2,d0
	add.l	(writeabortsig,a3),d0

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

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

	PUTDEBUG 5,<'Break: Aborted!'>

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

	PUTDEBUG 5,<'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.

AbortIO	PUTDEBUG 5,<'AbortIO: called'>
	Forbid
	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	Permit
	moveq	#0,d0
	PUTDEBUG 5,<'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	EXEC	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	Disable
	move.l	(startbuf,a3),d0
	move.l	d0,(head,a3)
	move.l	d0,(tail,a3)
	Enable
	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 30,<'MyReset: called'>
	Forbid
	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	AbortIO

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

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

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)
	Permit
	clr.l	(IO_ACTUAL,a1)
	PUTDEBUG 30,<'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	Permit
	clr.l	(IO_ACTUAL,a1)
	PUTDEBUG 30,<'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 30,<'Query: called.'>
	bsr	GetBytesInReadBuf
	move.l	d0,(IO_ACTUAL,a1)

	ifne	INFO_LEVEL  ;If any debugging enabled at all
	move.l	d0,-(sp)
	PUTDEBUG 30,<'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	Forbid
	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. *

	Disable
	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
	SYS	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
	SYS	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
	Enable
SPLx	Permit
	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	Permit
	move.b	#SerErr_DevBusy,(IO_ERROR,a1)
	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 30,<'MyStop: called'>
	bset	#MDUB_STOPPED,(MDU_FLAGS,a3)
	rts

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

;[A3=unit A6=device]

InternalStart	PUTDEBUG 30,<'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
	SYS	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
	SYS	Signal	;FIXED: marco-a6 not a3
	movea.l	(sp)+,a6
	movea.l	a2,a1
	PUTDEBUG 30,<'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 30,<'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
	SYS	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)
	SYS	ReplyMsg
	bra.b	ReadFlush_Loop

WriteFlush_Loop	lea	(mdu_wport,a3),a0
	SYS	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)
	SYS	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 30,<'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 35,<'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
	SYS	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
	SYS	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 40,<'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 75,<'ReadTask ++Sleep'>
	move.l	d7,d0
	SYS	Wait
	ifge	INFO_LEVEL-5
	bchg.b	#1,($bfe001).l  ;Blink the power LED
	endc

ReadTask_StartHere	PUTDEBUG 75,<'ReadTask ++Wakeup'>
	btst	#MDUB_STOPPED,(MDU_FLAGS,a3) ;See if we are stopped
	bne.b	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
	SYS	GetMsg	;Get the next request
	PUTDEBUG 1,<'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

	Forbid
	btst	#ioflagsB_Ignore,(IO_FLAGS,a1)
	bne.b	Readignorecmd
	bset	#ioflagsB_Active,(IO_FLAGS,a1)
	move.l	a1,(ReadRequestPtr,a3)
	Permit
	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	(readabortsig,a3),d1
	EXEC	SetSignal
	bra.b	Readconttl

Readignorecmd	Permit
	clr.l	(IO_ACTUAL,a1)
	bsr	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 35,<'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
	SYS	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
	SYS	SetExcept	;Make breaksig an exception-causing signal

;Allocate a signal for the timer message port

	moveq	#-1,d0	;-1 is any signal at all
	SYS	AllocSignal
	lea	(timerport,a3),a2
	move.b	d0,(MP_SIGBIT,a2)
	move.l	a2,(timeriorequest+MN_REPLYPORT,a3)
	suba.l	a1,a1
	SYS	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
	SYS	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 40,<'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 75,<'WriteTask ++Sleep'>
	move.l  d7,d0
	SYS	Wait
	ifge INFO_LEVEL-5
	bchg.b	#1,($bfe001).l  ;Blink the power LED
	endc

WriteTask_StartHere	PUTDEBUG 75,<'WriteTask ++Wakeup'>
	btst    #MDUB_STOPPED,(MDU_FLAGS,a3) ;See if we are stopped
	bne.b   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
	SYS	GetMsg	;Get the next request
	PUTDEBUG 1,<'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

	Forbid
	btst	#ioflagsB_Ignore,(IO_FLAGS,a1)
	bne.b	Writeignorecmd
	bset	#ioflagsB_Active,(IO_FLAGS,a1)
	move.l	a1,(WriteRequestPtr,a3)
	Permit
	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
	EXEC	SetSignal
	bra.b	Writeconttl

Writeignorecmd	Permit
	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 5,<'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 5,<'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 5,<'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 5,<'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 5,<'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,myName
	INITLONG mdu_rtcb+LN_NAME,myName
	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,myName
	INITLONG mdu_wtcb+LN_NAME,myName
	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,myName

;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,myName

	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 75,<'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 75,<'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 75,<'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.b	rdrfempty

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

	PUTDEBUG 75,<'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 75,<'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 75,<'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 75,<'Int: Finished!'>
	move.w	(sp)+,d7
	movea.l	(sp)+,a3
	moveq	#1,d0	;Terminate the chain
	rts

	ISDEBUG	'newser'	;This name for debugging use

EndCode:
	end
