********************************************************************************
**									      **
**  Name       : 8n1_040.s						      **
**  Copyright  : © Copyright 96-97					      **
**  Author     : Iain Barclay						      **
**  Created    : 21 Oct 97						      **
**  Version    : 43.5							      **
**									      **
********************************************************************************
	 ;
	 ;	  SNMA specific options
	 ;
	 IFD	  SNMA
	 ;
	 CPU	  M68040
	 ;
	 SNMAOPT  Q,A,M,T,E-,P,R,B
	 ;
	 ELSE
	 IFD PHXASS
	 machine 68040
	 ENDC
	 ENDC
	 ;
	 ;
NEWCODE  SET	  1
SETDSR	 SET	  0
NCOMM	 SET	  0
FASTVBR  SET	  1
DANGER	 SET	  0
NEGCHECK SET	  1
EOFCODE  SET	  1
	 ;
	 ;
	 ;
	 SECTION  text,CODE
	 ;
	 ;
	 ;
DEVICES_SERIAL_I_OBSOLETE  EQU	    1
	 ;
	 ;
	 ;
	 INCLUDE  "exec/lists.i"
	 INCLUDE  "exec/memory.i"
	 INCLUDE  "exec/resident.i"
	 INCLUDE  "exec/devices.i"
	 INCLUDE  "exec/execbase.i"
	 INCLUDE  "exec/io.i"
	 INCLUDE  "exec/ports.i"
	 INCLUDE  "exec/errors.i"
	 INCLUDE  "exec/initializers.i"
	 INCLUDE  "intuition/preferences.i"
	 INCLUDE  "devices/timer.i"
	 INCLUDE  "devices/serial.i"
	 INCLUDE  "hardware/custom.i"
	 INCLUDE  "hardware/cia.i"
	 INCLUDE  "hardware/intbits.i"
	 INCLUDE  "hardware/adkbits.i"
	 INCLUDE  "resources/misc.i"
	 INCLUDE  "exec/alerts.i"
	 INCLUDE  "exec/macros.i"
	 INCLUDE  "8n1.device_rev.i"
	 ;
	 ;	  Define hardware references
	 ;
	 XREF	  _custom
	 XREF	  _intena,_intenar,_intreq,_intreqr
	 XREF	  _ciab,_ciabpra
	 XREF	  _serper,_serdat,_serdatr
	 XREF	  _adkcon,_adkconr
	 ;
	 ;	  Exec Functions
	 ;
	 XREF	  _LVORemDevice,_LVOOpenDevice,_LVOCloseDevice
	 XREF	  _LVOSupervisor
	 XREF	  _LVOAllocMem,_LVOFreeMem,_LVOCopyMem,_LVOTypeOfMem
	 XREF	  _LVOReplyMsg
	 XREF	  _LVOSendIO,_LVOAbortIO
	 XREF	  _LVODisable,_LVOEnable
	 XREF	  _LVOFindName
	 XREF	  _LVOOpenResource
	 XREF	  _LVOAddIntServer,_LVORemIntServer
	 XREF	  _LVOAlert
	 XREF	  _LVOOpenLibrary,_LVOCloseLibrary
	 XREF	  _LVOCacheClearU
	 ;
	 ;	  Misc Resource Functions
	 ;
	 XREF	  _LVOAllocMiscResource,_LVOFreeMiscResource
	 ;
	 ;	  Intuition Functions
	 ;
	 XREF	  _LVOGetPrefs
	 ;
	 ;	  Mask used to get rid of the printer bits.
	 ;
PRTMASK  EQU	  (CIAF_PRTRPOUT|CIAF_PRTRBUSY)
	 ;
	 ;	  Autovector offsets
	 ;
LVL1VEC  EQU	  (1-1)*4+$64
LVL5VEC  EQU	  (5-1)*4+$64
VBR_SIZE EQU	  $400
custom	 EQU	  $dff000
ciab	 EQU	  $bfd000
	 ;
	 ;  New Style device stuff (not the best way but it will have to do for now)
	 ;  If u don't have New Style stuff defined elsewhere remove these ;
;NSCMD_DEVICEQUERY   EQU $4000
; STRUCTURE  NSDEVICEQUERYRESULT,0
;    ULONG   DEVQUERYFORMAT
;    ULONG   SIZEAVAILABLE
;    UWORD   DEVICETYPE
;    UWORD   DEVICESUBTYPE
;    APTR    SUPPORTEDCOMMANDS
;    LABEL   NSDEVICEQUERYRESULT_SIZE
;NSDEVTYPE_SERIAL    EQU 11
	 ;
	 ;	  Device base
	 ;
	 STRUCTURE Base8n1,LIB_SIZE
	 UBYTE	  vb_pad1
	 UBYTE	  vb_pad2
	 ULONG	  vb_SaveDDRA
	 ULONG	  vb_SavePRA
	 APTR	  vb_MiscBase
	 APTR	  vb_OldLevel1
	 APTR	  vb_OldLevel5
	 ULONG	  vb_SegList
	 ULONG	  vb_DefBaud
	 ULONG	  vb_DefRBufLen
	 ULONG	  vb_CurRBuf
	 ULONG	  vb_CurRBufLen
	 ULONG	  vb_CurBaud
	 ULONG	  vb_SerFlags
	 ULONG	  vb_Initialized
	 LABEL	  sizeof_Base8n1
	 ;
	 ;
	 ;
Start:
	 moveq	  #-1,d0			      ; set return code
	 rts					      ; return
	 ;
	 ;	  RamLib looks for this romtag
	 ;
ROMTag	 DC.W	  RTC_MATCHWORD 		      ; RT_MATCHWORD
	 DC.L	  ROMTag			      ; RT_MATCHTAG
	 DC.L	  ENDTag			      ; RT_ENDSKIP
	 DC.B	  RTF_AUTOINIT			      ; RT_FLAGS
	 DC.B	  VERSION			      ; RT_VERSION
	 DC.B	  NT_DEVICE			      ; RT_TYPE
	 DC.B	  0				      ; RT_PRI
	 DC.L	  Name				      ; RT_NAME
	 DC.L	  IdString			      ; RT_IDSTRING
	 DC.L	  Init				      ; RT_INIT
	 ;
	 ;	  Perform device initialization
	 ;
	cnop 0,8    ; align for 040+
InitRoutine:
	 exg	  d0,a0 			      ; swap seglist and base
	 move.l   d0,vb_SegList(a0)		      ; store seglist in base
	 move.l   a6,SysBase			      ; store in global storage
	 exg	  a0,d0 			      ; swap them back
	 rts					      ; return
	 ;
	 ;
	 ;
	cnop 0,8 ; align for 040+
dev_Open:   ; (a1 iorequest, a5 will be Base8n1, a6 sysbase )
	 addq.w   #1,LIB_OPENCNT(a6)		      ; fake open so device is not expunged
	 move.l   a5,-(sp)			      ; save registers
	 move.l   a6,a5 			      ; save base
	 ;
	 cmpi.w   #IOEXTSER_SIZE,MN_LENGTH(a1)	      ; IORequest size >= IOEXTSER_SIZE
	 bcs.w	  50$				      ; nope, finish
	 ;
	 tst.l	  d0				      ; unit 0 specified?
	 bne.w	  50$				      ; nope, error (who cares?)
	 ;
	 cmpi.w   #1,LIB_OPENCNT(a5)		      ; currently open?
	 bne.w	  10$				      ; yep, go process
	 ;
	 move.b   IO_SERFLAGS(a1),vb_SerFlags(a5)     ; save flags
	 ;
	 tst.b	  vb_Initialized(a5)		      ; already initialized?
	 bne.w	  40$				      ; yep, skip initialization
	 ;
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 move.l   a1,-(sp)			      ; save register
	 ;
	 lea	  miscresource(pc),a1		      ; ptr to resource name
	 jsr	  _LVOOpenResource(a6)		      ; go open it
	 move.l   d0,vb_MiscBase(a5)		      ; save base
	 ;
	 moveq	  #MR_SERIALPORT,d0		      ; set unit number
	 bsr.w	  allocResource 		      ; go allocate it
	 tst.l	  d0				      ; did we get it?
	 bne.b	  20$				      ; nope, error
	 ;
	 moveq	  #MR_SERIALBITS,d0		      ; set unit number
	 bsr.w	  allocResource 		      ; go allocate it
	 tst.l	  d0				      ; did we get it?
	 bne.b	  5$				      ; nope, error
	 ;
	 ;
	 ;	  Get system preferences
	 ;
	 lea	  intuitlib(pc),a1		      ; ptr to library name
	 moveq	  #0,d0 			      ; can't use OldOpenLibrary as this will not be supported.
	 jsr	  _LVOOpenLibrary(a6)		      ; go open it (any version)
	 move.l   d0,a6 			      ; get intuition base
	 ;
	 move.l   #(pf_SerParShk+3)&$fffffffc,d0      ; size we need (aligned)
	 suba.l   d0,sp 			      ; reserve space
	 ;
	 move.l   sp,a0 			      ; set data area ptr
	 jsr	  _LVOGetPrefs(a6)		      ; get preferences
	 ;
	 move.l   a6,a1 			      ; get intuition base
	 move.l   SysBase(pc),a6		      ; restore ExecBase
	 jsr	  _LVOCloseLibrary(a6)		      ; close it
	 ;
	IFNE NEWCODE
	 moveq	  #$0f,d0			      ; set mask
	 and.b	  pf_SerStopBuf(sp),d0		      ; get bufsize index
	 move.l   #512,d1
	 lsl.l	  d0,d1 			      ; get default bufsize
	 move.l   d1,vb_DefRBufLen(a5)		      ; and store
	 moveq	  #0,d1 			      ; clear upper half
	 move.w   pf_BaudRate(sp),d1		      ; get baud rate
	ELSE
	 moveq	  #$0f,d0			      ; set mask
	 and.b	  pf_SerStopBuf(sp),d0		      ; get bufsize index
	 moveq	  #0,d1 			      ; clear upper half
	 addq.l   #8,d0 			      ; calc shift value
	 move.w   pf_BaudRate(sp),d1		      ; get baud rate
	 lsl.l	  #2,d0 			      ; get default bufsize
	 move.l   d0,vb_DefRBufLen(a5)		      ; and store
	ENDC
	 move.l   .baudTable(pc,d1.l*4),d1	      ; get baud rate
	 ;
	 move.l   d1,vb_DefBaud(a5)		      ; and store
	 ;
	 bsr	  internalReset 		      ; go init baud and buffer
	 ;
	 lea	  (pf_SerParShk+3)&$fffffffc(sp),sp   ; restore stack
	 tst.l	  d0				      ; got 'em?
	 beq.b	  30$				      ; yep, branch
	 ;
	 moveq	  #MR_SERIALBITS,d0		      ; set unit
	 move.l   vb_MiscBase(a5),a6		      ; get MiscBase
	 jsr	  _LVOFreeMiscResource(a6)	      ; release the resource
	 ;
5$	 moveq	  #MR_SERIALPORT,d0		      ; set unit
	 move.l   vb_MiscBase(a5),a6		      ; get MiscBase
	 jsr	  _LVOFreeMiscResource(a6)	      ; release the resource
	 ;
20$	 moveq	  #SerErr_DevBusy,d0		      ; set error status
	 move.l   (sp)+,a1			      ; restore register
	 bra	  50$				      ; go return
	 ;
	 ;	  Preferences baud lookup table
	 ;
	 cnop 0,8 ; align for 040+
.baudTable
	 dc.l	  112,300,1200,2400,4800,9600,19200,31250,38400,57600,62400,64800
	 dc.l	  76800,115200
	 ;
	 cnop 0,8 ; align for 040+
30$	 moveq	  #0,d1 			      ; clear flags
	 lea	  timerReq(pc),a1		      ; ptr to timer request
	 moveq	  #UNIT_VBLANK,d0		      ; set unit
	 lea	  timerdevice(pc),a0		      ; ptr to device name
	 jsr	  _LVOOpenDevice(a6)		      ; go open it
	 ;
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 ;
	 move.l   #ciab,a1			      ; get ptr to ciab
	 move.b   ciaddra(a1),d0
	 move.b   d0,vb_SaveDDRA(a5)		      ; save DDR value
	IFNE NEWCODE
	 move.b   (a1),d1			      ; ciapra(a1)
	 move.b   d1,vb_SavePRA(a5)		      ; save PR value
	 andi.b   #CIAF_COMDTR|CIAF_COMRTS|PRTMASK,d0 ; turn on DTR/RTS
	 move.b   d0,ciaddra(a1)
	 andi.b   #CIAF_COMCTS|CIAF_COMDSR|PRTMASK,d1 ; make CTS/DSR input
	 move.b   d1,(a1)			      ; ciapra(a1)
	ELSE
	 andi.b   #PRTMASK,d0			      ; make serial bits input
	 move.b   (a1),d1			      ; ciapra(a1)
	 ori.b	  #CIAF_COMDTR|CIAF_COMRTS,d0	      ; make DTR/RTS output
	 move.b   d1,vb_SavePRA(a5)		      ; save PR value
	 andi.b   #CIAF_COMDTR|CIAF_COMRTS|PRTMASK,d0 ; turn on DTR/RTS
	 move.b   d0,ciaddra(a1)
	 andi.b   #CIAF_COMCTS|CIAF_COMDSR|PRTMASK,d1 ; make CTS/DSR input
	 move.b   d1,(a1)			      ; ciapra(a1)
	ENDC
	 ;
	 moveq	  #INTB_PORTS,d0		      ; get interrupt number
	 lea	  VBInterrupt(pc),a1		      ; get interrupt ptr
	 jsr	  _LVOAddIntServer(a6)		      ; add it to the list
	 ;
	 bsr	  getVBR			      ; get vector base (in A0)
	 ;
	IFNE FASTVBR
	 move.l   a5,-(sp)			      ; save a5
	 move.l   a0,-(sp)			      ; save VBR
	 move.l   a0,a1
	 jsr	  _LVOTypeOfMem(a6)
	 btst	  #MEMB_FAST,d0 		      ; VBR in fast?
	 bne.b	  nofast$			      ; yep, branch
	 move.l   #VBR_SIZE,d0
	 moveq	  #MEMF_PUBLIC|MEMF_FAST,d1
	 jsr	  _LVOAllocMem(a6)		      ; Alloc fast-ram for VBR
	 tst.l	  d0
	 beq.b	  nofast$			      ; no fast, branch
	 move.l   (sp),a0			      ; get VBR
	 move.l   d0,a1
	 move.l   d0,-(sp)			      ; save new VBR
	 move.l   #VBR_SIZE,d0
	 jsr	  _LVOCopyMem(a6)
	 move.l   (sp)+,a0			      ; get new VBR
	 lea	  setVBR$(pc),a5
	 jsr	  _LVOSupervisor(a6)		      ; set new VBR
	 jsr	  _LVOCacheClearU(a6)		      ; clear cache
	 move.l   (sp)+,d0			      ; was old VBR zero?
	 beq.b	  waszero$			      ; yep, branch (don't free)
	 move.l   d0,a1
	 move.l   #VBR_SIZE,d0
	 jsr	  _LVOFreeMem(a6)
waszero$
	 bsr	  getVBR
	 bra.b	  vbend$
	 cnop	  0,8 ; align for 040+
setVBR$
	 movec	  a0,vbr			      ; set VBR
	 rte
	 cnop	  0,8 ; align for 040+
nofast$
	 move.l   (sp)+,a0			      ; restore VBR
vbend$
	 move.l   (sp)+,a5			      ; restore a5
	ENDC
	 move.l   LVL1VEC(a0),vb_OldLevel1(a5)	      ; save original vector
	 lea	  level1(pc),a1 		      ; get new vector ptr
	 move.l   a1,LVL1VEC(a0)		      ; set new vector
	 ;
	 move.l   LVL5VEC(a0),vb_OldLevel5(a5)	      ; save original vector
	 lea	  level5(pc),a1 		      ; get new vector ptr
	 move.l   a1,LVL5VEC(a0)		      ; set new vector
	 ;
	 move.l   #custom+intena,a1		      ; get ptr to custom chips
	 move.w   #INTF_RBF|INTF_TBE,intreq-intena(a1); clear pending interrupts
	 move.w   #INTF_SETCLR|INTF_RBF|INTF_TBE,(a1) ; enable RBF & TBE
	 ;
	 addq.b   #1,vb_Initialized(a5) 	      ; set flag
	 ;
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$C000,(a1)			      ; enable interrupts
enable$  move.l   (sp)+,a1			      ; restore register
	 bra.b	  40$				      ; go exit
	ELSE
	 move.l   (sp)+,a1			      ; restore register
	 ;
	 pea	  40$(pc)
	 jmp	  _LVOEnable(a6)		      ; enable interrupts
	 ; go exit
	ENDC
	 ;
	 cnop 0,8 ; align for 040+
10$	 moveq	  #SerErr_DevBusy,d0		      ; preset error status
	 btst	  #SERB_SHARED,vb_SerFlags(a5)	      ; opened shared?
	 beq.b	  50$				      ; nope, error
	 btst	  #SERB_SHARED,IO_SERFLAGS(a1)	      ; requesting shared?
	 beq.b	  50$				      ; nope, error
	 ;
	 ;	  Initialize I/O request
	 ;
40$	 move.b   #8,IO_READLEN(a1)		      ; set read length
	 move.b   #8,IO_WRITELEN(a1)		      ; set write length
	 move.b   #1,IO_STOPBITS(a1)		      ; set stop bits
	 move.b   IO_SERFLAGS(a1),d0
	 ori.b	  #SERF_XDISABLED|SERF_RAD_BOOGIE|SERF_QUEUEDBRK|SERF_7WIRE,d0 ;flags
	 andi.b   #~(SERF_PARTY_ODD|SERF_PARTY_ON),d0 ; not used
	 move.b   d0,IO_SERFLAGS(a1)
	 move.l   vb_CurBaud(a5),IO_BAUD(a1)	      ; set baud
	 moveq	  #0,d0 			      ; no error
	 move.l   vb_CurRBufLen(a5),IO_RBUFLEN(a1)    ; set read buffer length
	 ;
	 addq.w   #1,LIB_OPENCNT(a5)		      ; incr open count
	 andi.b   #~(1<<LIBB_DELEXP),LIB_FLAGS(a5)    ; clear expunge bit
	 ;
50$	 move.b   d0,IO_ERROR(a1)		      ; store error code
	 move.l   a5,a6 			      ; restore base
	 move.l   (sp)+,a5			      ; restore registers
	 move.l   (sp)+,a0
	 subq.w   #1,LIB_OPENCNT(a6)		      ; remove fake open
	 jmp	  (a0)				      ; return
	 ;
	 ;	  Attempt to allocate one of the serial resources.
	 ;
	 cnop 0,8 ; align for 040+
allocResource:
	 move.l   a6,-(sp)			      ; save base pointer
	 move.l   d0,-(sp)			      ; save unit
	 lea	  Name(pc),a1			      ; get lock name
	 move.l   vb_MiscBase(a5),a6		      ; get MiscBase
	 jsr	  _LVOAllocMiscResource(a6)	      ; go allocate it
	 tst.l	  d0				      ; did we get it?
	 beq.b	  20$				      ; yep, branch
	 ;
	 ;	  It's in use, so we try to locate the device using the string
	 ;	  returned and attempt to remove it.
	 ;
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 ;
	 move.l   d0,a1 			      ; get ptr to serial name
	 lea	  DeviceList(a6),a0		      ; get ptr to device list
	 jsr	  _LVOFindName(a6)		      ; go find it
	 tst.l	  d0				      ; found?
	 beq.b	  10$				      ; nope, branch
	 ;
	 move.l   d0,a1 			      ; xfer device ptr
	 jsr	  _LVORemDevice(a6)		      ; remove it
	 ;
	 ;	  We then retry the allocate.
	 ;
10$	 move.l   (sp),d0			      ; get unit
	 lea	  Name(pc),a1			      ; get lock name
	 move.l   vb_MiscBase(a5),a6		      ; get MiscBase
	 jsr	  _LVOAllocMiscResource(a6)	      ; go allocate it
	 ;
20$	 addq.l   #4,sp 			      ; restore stack ptr
	 move.l   (sp)+,a6			      ; restore base ptr
	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return
	 ;
	 ;	  Set exception vectors
	 ;
	 cnop 0,8 ; align for 040+
getVBR:
	 move.l   a5,-(sp)			      ; save registers
	 lea	  20$(pc),a5			      ; ptr to routine
	 jsr	  _LVOSupervisor(a6)		      ; get into supervisor state
10$	 move.l   (sp)+,a5			      ; restore register
	 move.l   (sp)+,a1			      ; return
	 jmp	  (a1)
	 cnop 0,8 ; align for 040+
20$	 movec.l  vbr,a0			      ; get vector base
	 rte					      ; return
	 ;
	 ;	  Device Close routine
	 ;
	 cnop 0,8 ; align for 040+
dev_Close:
	 ;
	 move.l   #-1,IO_DEVICE(a1)		      ; invalidate device
	 ;
	 tst.w	  LIB_OPENCNT(a6)
	 beq.b	  5$
	 subq.w   #1,LIB_OPENCNT(a6)		      ; decr open count
	 bne	  dev_Null			      ; still open? yep, branch
	 ;
5$	 move.l   a5,-(sp)			      ; save registers
	 move.l   a6,a5 			      ; save base
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 ;
	 move.l   a1,-(sp)			      ; save registers
	 ;
	 bsr.b	  getVBR			      ; get vector base (in A0)
	 ;
	 moveq	  #1,d0 			      ; set not restored code
	 ;
	 lea	  level1(pc),a1 		      ; get our vector ptr
	 cmpa.l   LVL1VEC(a0),a1		      ; do they match?
	 bne	  10$				      ; nope, can't restore
	 ;
	 lea	  level5(pc),a1 		      ; get our vector ptr
	 cmpa.l   LVL5VEC(a0),a1		      ; do they match?
	 bne	  10$				      ; nope, can't restore
	 ;
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 ;
	 move.l   vb_OldLevel1(a5),LVL1VEC(a0)	      ; restore original vector
	 move.l   vb_OldLevel5(a5),LVL5VEC(a0)	      ; restore original vector
	 ;
	 moveq	  #INTB_PORTS,d0		      ; get interrupt number
	 lea	  VBInterrupt(pc),a1		      ; get interrupt ptr
	 jsr	  _LVORemIntServer(a6)		      ; remove it from the list
	 ;
	 move.l   #custom+intena,a0		      ; get custom base
	 move.w   #INTF_RBF|INTF_TBE,d0 	      ; indicate serial interrupts
	 move.w   d0,(a0)			      ; disable interrupts
	 move.w   d0,intreq-intena(a0)		      ; clear pending interrupts
	 ;
	 bsr	  freeBuf			      ; free allocated buffers
	 ;
	 move.l   #ciab,a0			      ; get pointer to ciab
	 move.b   ciaddra(a0),d0		      ; get DDR value
	 andi.b   #PRTMASK,d0			      ; save printer bits
	 ori.b	  #~PRTMASK,d0			      ; set serial bits to output
	 move.b   d0,ciaddra(a0)		      ; store value
	 ;
	 move.b   (a0),d0			      ; get PR value ( ciapra(a0) )
	 andi.b   #PRTMASK,d0			      ; mask out serial bits
	 move.b   vb_SavePRA(a5),d1		      ; get saved value
	 andi.b   #~PRTMASK,d1			      ; mask out printer bits
	 or.b	  d1,d0 			      ; combine the two
	 move.b   d0,(a0)			      ; and store ( ciapra(a0) )
	 ;
	 move.b   ciaddra(a0),d0		      ; get DDR value
	 andi.b   #PRTMASK,d0			      ; mask out serial bits
	 move.b   vb_SaveDDRA(a5),d1		      ; get saved value
	 andi.b   #~PRTMASK,d1			      ; mask out printer bits
	 or.b	  d1,d0 			      ; combine the two
	 move.b   d0,ciaddra(a0)		      ; and store
	 ;
	 lea	  timerReq(pc),a1		      ; get ptr to timer request
	 jsr	  _LVOCloseDevice(a6)		      ; go close it
	 ;
	 move.l   vb_MiscBase(a5),a6		      ; get MiscBase
	 moveq	  #MR_SERIALBITS,d0		      ; set unit
	 jsr	  _LVOFreeMiscResource(a6)	      ; release the resource
	 ;
	 moveq	  #MR_SERIALPORT,d0		      ; set unit
	 jsr	  _LVOFreeMiscResource(a6)	      ; release the resource
	 move.l   SysBase(pc),a6		      ; restore ExecBase
	 ;
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$0C000,_intena		      ; enable interrupts
enable$
	ELSE
	 jsr	  _LVOEnable(a6)		      ; enable interrupts
	ENDC
	 ;
	 subq.b   #1,vb_Initialized(a5) 	      ; clear flag
	 moveq	  #0,d0 			      ; free up everything
	 ;
10$	 move.l   (sp)+,a1			      ; restore registers
	 ;
	 move.l   a5,a6 			      ; restore base
	 move.l   (sp)+,a5			      ; restore registers
	 ;
	 tst.l	  d0				      ; freed?
	 bne.b	  dev_Null			      ; nope, can't expunge, exit
	 ;
	 clr.b	  vb_SerFlags(a6)		      ; clear flags
	 ;
	 btst	  #LIBB_DELEXP,LIB_FLAGS(a6)	      ; delayed expunge set?
	 beq.b	  dev_Null			      ; nope, go exit
	 ;
	 ;	  Device Expunge routine (also fall through from dev_Close)
	 ;
dev_Expunge:
	 ori.b	  #1<<LIBB_DELEXP,LIB_FLAGS(a6)       ; Set expunge flag
	 tst.w	  LIB_OPENCNT(a6)		      ; currently open?
	 bne.b	  dev_Null			      ; yep, so just exit
	 ;
	 move.l   vb_SegList(a6),-(sp)		      ; get seglist ptr
	 ;
	 move.l   a6,a1 			      ; get base
	 ; This is the REMOVE macro
	 move.l   (a1)+,a0
	 move.l   (a1),a1
	 move.l   a0,(a1)
	 move.l   a1,LN_PRED(a0)
	 ;
	 move.l   a6,a1 			      ; get base
	 moveq	  #0,d0 			      ; clear work
	 move.w   LIB_NEGSIZE(a6),d0		      ; calculate
	 suba.l   d0,a1 			      ;   memory address
	 add.w	  LIB_POSSIZE(a6),d0		      ;     and size
	 move.l   a6,-(sp)			      ; save registers
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 jsr	  _LVOFreeMem(a6)		      ; free it
	 ;
	 move.l   (sp)+,a6			      ; restore registers
	 move.l   (sp)+,d0			      ; restore registers
	 rts					      ; return (seglist in D0!)
	 ;
	 ;	  Device "ExtFunc" routine
	 ;
	 cnop 0,8 ; align for 040+
dev_Null:
	 moveq	  #0,d0 			      ; set return code
	 rts					      ; return
	 ;
	 ;
	 cnop 0,8 ; align for 040+
cmdTable dc.l	  cmd_Invalid-cmdTable		      ; CMD_INVALID
	 dc.l	  cmd_Reset-cmdTable		      ; CMD_RESET
	 dc.l	  cmd_Read-cmdTable		      ; CMD_READ
	 dc.l	  cmd_Write-cmdTable		      ; CMD_WRITE
	 dc.l	  cmd_Invalid-cmdTable		      ; CMD_UPDATE
	 dc.l	  cmd_Clear-cmdTable		      ; CMD_CLEAR
	 dc.l	  cmd_Invalid-cmdTable		      ; CMD_STOP
	 dc.l	  cmd_Invalid-cmdTable		      ; CMD_START
	 dc.l	  cmd_Flush-cmdTable		      ; CMD_FLUSH
	 dc.l	  sdcmd_Query-cmdTable		      ; SDCMD_QUERY
	 dc.l	  sdcmd_Break-cmdTable		      ; SDCMD_BREAK
	 dc.l	  sdcmd_SetParams-cmdTable	      ; SDCMD_SETPARAMS
endTable
	 dc.l	  nscmd_DeviceQuery-cmdTable	      ; NSCMD_DEVICEQUERY
nsendTable
	 ;
	 ;	  Device BeginIO routine
	 ;
	 cnop 0,8 ; align for 040+
dev_BeginIO:
	 move.l   a5,-(sp)			      ; save register
	 move.l   a6,a5 			      ; save base
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 ;
	 move.b   #NT_MESSAGE,LN_TYPE(a1)	      ; set type
	 clr.b	  IO_ERROR(a1)			      ; clear error
	 ;
	 andi.b   #~(IOSERF_QUEUED|IOSERF_ACTIVE),IO_FLAGS(a1) ; clear flags
	 ;
	 moveq	  #0,d0
	 move.w   IO_COMMAND(a1),d0		      ; get command
	 lsl.l	  #2,d0 			      ; multiply by 4
	 cmp.l	  #endTable-cmdTable,d0 	      ; in range?
	 bcs.b	  5$				      ; yep, branch
	 cmp.l	  #(4*NSCMD_DEVICEQUERY),d0
	 bne.b	  30$
	 moveq	  #endTable-cmdTable,d0
	 ;
5$	 move.l   cmdTable(pc,d0.l),d0		      ; get routine offset
	 jsr	  cmdTable(pc,d0.l)		      ; go do it
	 tst.l	  d0				      ; I/O completed?
	 bne.b	  19$				      ; nope, go return
	 ;
10$
	 btst	  #IOB_QUICK,IO_FLAGS(a1)	      ; need to reply?
	 bne.b	  20$				      ; nope, branch
15$	 jsr	  _LVOReplyMsg(a6)		      ; send it back
	 bra.b	  20$
	 cnop	  0,8
	 ;
19$	 andi.b   #~(1<<IOB_QUICK),IO_FLAGS(a1)       ; clear quick bit
20$	 move.l   a5,a6 			      ; restore base
	 move.l   (sp)+,a5			      ; restore register
	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return
	 cnop 0,8 ; align for 040+
	 ;
30$	 move.b   #IOERR_NOCMD,IO_ERROR(a1)	      ; invalid command
	 bra.b	  10$				      ; branch
	 ;
	 ;	  Device AbortIO routine
	 ;
	 cnop 0,8 ; align for 040+
dev_AbortIO:
	 move.l   a5,-(sp)			      ; save registers
	 move.l   a6,a5 			      ; save base
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 ;
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 ;
	 move.l   IO_COMMAND(a1),d1		      ; get flags
	 btst	  #IOSERB_QUEUED+8,d1		      ; queued request?
	 bne.b	  40$				      ; yep, branch
	 ;
	 btst	  #IOSERB_ACTIVE+8,d1		      ; active request?
	 beq.b	  10$				      ; nope, just exit
	 ;
	 move.l   d1,d0 			      ; get command
	 swap	  d0
	 subq.w   #CMD_READ,d0			      ; was it a read?
	 beq.b	  20$				      ; yep, go process
	 ;
	 subq.w   #CMD_WRITE-CMD_READ,d0	      ; was it a write?
	 beq.b	  30$				      ; yep, go process
	 ;
	 subq.w   #SDCMD_BREAK-CMD_WRITE,d0	      ; was it a break?
	 beq.b	  30$				      ; yep, go process
	 ;
	 ;	  Fall through or enter from below
	 ;
	IFNE DANGER
10$	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$C000,_intena		      ; enable interrupts
enable$
	ELSE
10$	 jsr	  _LVOEnable(a6)		      ; enable ints and return
	ENDC
15$	 move.l   a5,a6 			      ; restore base
	 move.l   (sp)+,a5			      ; restore registers
	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return
	 ;
	 ;	  Abort an active read request
	 ;
	 cnop 0,8 ; align for 040+
20$	 clr.l	  cr_IOReq			      ; no longer active
	 bra.b	  50$				      ; go set flags
	 ;
	 ;	  Abort an active write request
	 ;
	 cnop 0,8 ; align for 040+
30$	 clr.l	  cw_Length			      ; no longer active
	 clr.l	  cw_IOReq			      ; no longer active
	 move.l   cw_Buffer(pc),d0		      ; get buffer ptr
	 sub.l	  IO_DATA(a1),d0		      ; calc number of bytes xfer'd
	 move.l   d0,IO_ACTUAL(a1)		      ; store
	 ;
	 ;	  Force a TBE interrupt to get the next write going.
	 ;
	 move.w   #INTF_SETCLR|INTF_TBE,_intreq       ; make TBE pending
	 bra.b	  50$				      ; go set flags
	 ;
	 ;	  Remove I/O from queue.
	 ;
	 cnop 0,8 ; align for 040+
40$	 move.l   a1,-(sp)			      ; save ptr
	 ; This is the REMOVE macro
	 move.l   (a1)+,a0
	 move.l   (a1),a1
	 move.l   a0,(a1)
	 move.l   a1,LN_PRED(a0)
	 ;
	 move.l   (sp)+,a1			      ; restore ptr
	 ;
	 ;	  Set error and return I/O
	 ;
50$	 move.b   #IOERR_ABORTED,IO_ERROR(a1)	      ; set error code
	 move.b   IO_FLAGS(a1),d1		      ; get flags
	 ori.b	  #1<<IOSERB_ABORT,d1		      ; set abort flag
	 andi.b   #~(IOSERF_QUEUED|IOSERF_ACTIVE),d1  ; clear flags
	 move.b   d1,IO_FLAGS(a1)		      ; store flags
	 btst	  #IOB_QUICK,d1 		      ; need to reply?
	 bne.b	  10$				      ; nope, branch
	 pea	  10$(pc)
	 jmp	  _LVOReplyMsg(a6)		      ; send it back
	 ;
	 ;	  Abort all active/queued commands and reset internal state
	 ;
	 cnop 0,8 ; align for 040+
cmd_Reset:
	 move.l   a1,-(sp)			      ; save I/O request
	 bsr.b	  cmd_Flush			      ; go abort queued requests
	 ;
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 ;
	 ;	  This must follow the DISABLE
	 ;
	 exg	  a5,a6 			      ; exchange base and ExecBase
	 ;
	 move.l   cr_IOReq(pc),d0		      ; active read?
	 beq.b	  10$				      ; nope, branch
	 move.l   d0,a1 			      ; get I/O request
	 bsr	  dev_AbortIO			      ; go abort it
	 ;
	 ;
	 ;
10$	 move.l   cw_IOReq(pc),d0		      ; active write?
	 beq.b	  20$				      ; nope, branch
	 move.l   d0,a1 			      ; get I/O request
	 bsr	  dev_AbortIO			      ; go abort it
	 ;
20$	 exg	  a5,a6 			      ; restore base and ExecBase
	 move.l   (sp)+,a1			      ; restore I/O request
	 move.b   #8,IO_READLEN(a1)		      ; set read length
	 move.b   #8,IO_WRITELEN(a1)		      ; set write length
	 move.b   #1,IO_STOPBITS(a1)		      ; set stop bits
	 move.l   vb_DefBaud(a5),IO_BAUD(a1)	      ; set to default baud
	 move.l   vb_DefRBufLen(a5),IO_RBUFLEN(a1)    ; set to default buflen
	 bsr	  internalReset 		      ; go set/verify parameters
	 move.b   d0,IO_ERROR(a1)		      ; set error code
	 ;
	 ;	  Set RC
	 ;
	 moveq	  #0,d0 			      ; I/O complete
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$0C000,_intena		      ; enable interrupts
enable$  move.l   (sp)+,a0			      ; return
	 jmp	  (a0)
	ELSE
	 jmp	  _LVOEnable(a6)		      ; enable interrupts / return
	ENDC
	 ;
	 ;	  Abort all "queued" requests, leaving all active alone.
	 ;
	 cnop 0,8 ; align for 040+
cmd_Flush:
	 move.l   a2,-(sp)			      ; save registers
	 move.l   a1,-(sp)			      ; save registers
	 ;
	 ;	  Abort all queued read requests.
	 ;
	 lea	  readQ(pc),a2			      ; get ptr to read queue
	 bsr.b	  20$				      ; branch to abort
	 ;
	 ;	  Abort all queued write requests.
	 ;
	 lea	  writeQ(pc),a2 		      ; get ptr to write queue
	 bsr.b	  20$				      ; branch to abort
	 ;
	 ;	  restore, and return to caller
	 ;
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,a2			      ; restore registers
	 ;
	 ;	  Set RC and return
	 ;
	 moveq	  #0,d0 			      ; I/O complete
	 ;
	 ;	  !!!NOTE!!!  In a mad attempt to save 2 bytes, this RTS is used
	 ;	  by the subroutine below.  Why waste 'em?  B-)
	 ;
10$	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return ( used below too!! )
	 ;
	 ;	  Subroutine to remove and reply each I/O request.
	 ;
	 cnop 0,8 ; align for 040+
	IFNE DANGER
20$	 move.l   #custom+intena,a0
	 move.w   #$04000,(a0)
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
20$	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 move.l   a2,8(a2)			      ; clear list
	 move.l   (a2)+,a1			      ; and get ptr to first I/O request
	 move.l   a2,-(a2)			      ;
	 move.l   a1,a2 			      ; save I/O request in a2
	 ;
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$0C000,(a0)			      ; enable interrupts
enable$
	ELSE
	 jsr	  _LVOEnable(a6)		      ; enable interrupts
	ENDC
	 ;
25$	 move.l   (a2),d0			      ; get next I/O request
	 beq.b	  10$				      ; end of list? yep, branch to return
	 ;
	 move.l   a2,a1 			      ; move I/O request to a1
	 move.l   d0,a2 			      ; save next I/O request in a2
	 andi.b   #~(1<<IOSERB_QUEUED),IO_FLAGS(a1)   ; no longer queued
	 move.b   #IOERR_ABORTED,IO_ERROR(a1)	      ; store status
	 ;
	 pea	  25$(pc)			      ; push return address
	 jmp	  _LVOReplyMsg(a6)		      ; send it back / continue with next
	 ;
	 ;	  Process a CMD_READ request.
	 ;
	 cnop 0,8 ; align for 040+
cmd_Read:
	 ;
	 ;	  Zero length requests just get returned.
	 ;
	 clr.l	  IO_ACTUAL(a1) 		      ; clear bytes read
	 move.l   IO_LENGTH(a1),d0		      ; get length and test
	 beq.b	  20$				      ; yep, leave
	 ;
	 ;	  This can be used to circumvent a bug in NComm 3.0 which
	 ;	  references the buffer even when there was nothing read.
	 ;
	IFNE	 NCOMM
	 move.l   IO_DATA(A1),a0		      ; get data pointer
	 clr.b	  (a0)				      ; clear first byte in buffer
	ENDC
	 ;
	 ;	  The disable counter works just like exec's TDNestCnt field.  It's
	 ;	  initialized to -1.  After incrementing, if it is 0, then we
	 ;	  can attempt to process this request immediately.  If it's > 0,
	 ;	  then we're already disabled and we must queue this request.
	 ;
	 addq.l   #1,disableRead		      ; incr disable count
	 bgt.b	  50$				      ; >0, already disabled
	 ;
	 ;	  If we're already processing an request, this one has to wait
	 ;	  until that one is done, so go queue it.
	 ;
	 tst.l	  cr_IOReq(pc)			      ; have an active request?
	 bne.b	  50$				      ; yep, go queue this one
	 ;
	 ;	  If we don't have enough bytes to satisfy this request then go
	 ;	  queue it.
	 ;
	 cmp.l	  i_InCnt(pc),d0		      ; length > current bytes?
	 bgt.b	  50$				      ; yep, go queue it
	 ;
	 ;	  Setup fields and go copy the data
	 ;
	IFNE NCOMM
	 move.l   a0,cr_OutPtr			      ; get/set output ptr
	ELSE
	 move.l   IO_DATA(a1),cr_OutPtr 	      ; get/set output ptr
	ENDC
	 move.l   d0,cr_Length			      ; get/set output count
	 bsr	  copyData			      ; go copy 'em
	 moveq	  #0,d0 			      ; I/O complete
	 ;
	 ;	  We're done, so back off the disable counter.
	 ;
10$	 subq.l   #1,disableRead		      ; decr disable count
	 ;
	 ;	  Return to caller
	 ;
20$	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return
	 ;
	 ;	  Just set flags and queue.  The read interrupt will handle it.
	 ;
	 cnop 0,8 ; align for 040+
50$	 ori.b	  #1<<IOSERB_QUEUED,IO_FLAGS(a1)      ; indicate queued
	 ;
	 ;	  Add this request to the end.
	 ;
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 lea	  readQ(pc),a0			      ; get pointer to read queue
	 ; This is the ADDTAIL macro
	 addq.l   #LH_TAIL,a0
	 move.l   LN_PRED(a0),d0
	 move.l   a1,LN_PRED(a0)
	 move.l   a0,(a1)
	 move.l   d0,4(a1)
	 move.l   d0,a0
	 move.l   a1,(a0)
	 ;
	 ;	  Indicate that this request was not handled immediatly.
	 ;
	 moveq	  #1,d0 			      ; I/O not complete
	 ;
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  10$
	 move.w   #$C000,_intena		      ; enable interrupts
	 bra.b	  10$				      ; branch to return
	ELSE
	 pea	  10$(pc)			      ; branch to return
	 jmp	  _LVOEnable(a6)		      ; enable interrupts
	ENDC
	 ;
	 ;	  Process a CMD_WRITE request.
	 ;
	 cnop 0,8 ; align for 040
cmd_Write:
	 clr.l	  IO_ACTUAL(a1) 		      ; clear bytes written
	 move.l   IO_LENGTH(a1),d0		      ; get length
	 bne.b	  10$				      ; yep, proccess
5$	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return
	 cnop 0,8 ; align for 040
10$
	IFNE EOFCODE
	 btst	  #SERB_EOFMODE,IO_SERFLAGS(a1)       ; EOFMODE requested?
	 beq.b	  20$				      ; nope, branch
	 move.b   #SerErr_InvParam,IO_ERROR(a1)       ; Set error
	 moveq	  #0,d0 			      ; I/O Complete
	 bra.b	  5$
	 cnop 0,8 ; align for 040
20$
	ENDC
	IFNE NEGCHECK
	 ;
	 ; Check for -1 length
	 ;
	 cmp.l	  #-1,d0			      ; length = -1?
	 bne.b	  sdcmd_Break			      ; nope, skip scan
	 ;
	 ;	  Scan the data for null to calc the length
	 ;
	 move.l   a0,d1 			      ; save data ptr
30$	 tst.b	  (a0)+ 			      ; does it equal 0? (first byte)
	 beq.b	  40$				      ; yep, finish
	 tst.b	  (a0)+ 			      ; does it equal 0? (second byte)
	 beq.b	  40$				      ; yep, finish
	 tst.b	  (a0)+ 			      ; does it equal 0? (third byte)
	 beq.b	  40$				      ; yep, finish
	 tst.b	  (a0)+ 			      ; does it equal 0? (fourth byte)
	 bne.b	  30$				      ; nope, loop
40$	 move.l   a0,d0
	 sub.l	  d1,d0 			      ; calc # of bytes
	 move.l   d0,IO_LENGTH(a1)		      ; save length
	 bra.b	  sdcmd_Break
	 nop					      ; fix possable compiler bug
	ENDC
	 ;
	 ;	  Entry point for Break command and fall through from cmd_Write.
	 ;
	 cnop 0,8 ; align for 040
sdcmd_Break:
	 ;
	 ;	  Just set flags and queue. The TBE interrupt will handle it.
	 ;
	 ori.b	  #1<<IOSERB_QUEUED,IO_FLAGS(a1)      ; indicate queued
	 ;
	 ;	  Protect.
	 ;
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 ;
	 ;	  Add request to end of queue.
	 ;
	 lea	  writeQ(pc),a0 		      ; get queue list ptr
	 ; This is the ADDTAIL macro
	 addq.l   #LH_TAIL,a0
	 move.l   LN_PRED(a0),d0
	 move.l   a1,LN_PRED(a0)
	 move.l   a0,(a1)
	 move.l   d0,4(a1)
	 move.l   d0,a0
	 move.l   a1,(a0)
	 ;
	 ;	  If we have an active request, don't force interrupt.
	 ;
	 tst.l	  cw_IOReq(pc)			      ; have an active request?
	 bne.b	  10$				      ; yep, branch
	 ;
	 ;	  Force a TBE interrupt to get the writes going.
	 ;
	 move.w   #INTF_SETCLR|INTF_TBE,_intreq       ; make TBE pending
	 ;
	 ;	  Enable, set RC and return to caller.
	 ;
10$	 moveq	  #1,d0 			      ; I/O not complete
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$C000,_intena		      ; enable interrupts
enable$  move.l   (sp)+,a0			      ; return
	 jmp	  (a0)
	ELSE
	 jmp	  _LVOEnable(a6)		      ; enable interrupts
						      ; return
	ENDC
	 ;
	 ;	  Resets serial read buffer
	 ;
	 ;	  Since this routine is called internally, it must NOT reference
	 ;	  the I/O request.
	 ;
	 cnop 0,8 ; align for 040+
cmd_Clear:
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 ;
	 ;	  Load registers
	 ;
	 move.l   vb_CurRBuf(a5),d0		      ; get internal buffer ptr
	 move.l   vb_CurRBufLen(a5),d1		      ; and internal buffer len
	 lea	  i_BufPtr(pc),a0		      ; get ptr internal control
	 ;
	 ;	  Initialize global buffer variables
	 ;
	 move.l   d0,(a0)+			      ; store buffer ptr
	 move.l   d0,(a0)+			      ; set current input ptr
	 move.l   d0,(a0)+			      ; set current output ptr
	 add.l	  d1,d0 			      ; add buffer length
	 move.l   d0,(a0)+			      ; store ptr to end of buffer
	 clr.l	  (a0)+ 			      ; clear byte cnt
	 move.l   vb_CurBaud(a5),d0		      ; get internal baud
	 lsr.l	  #4,d0 			      ; divide by 16
	 sub.l	  d0,d1 			      ; subtract from length
	 move.l   d1,(a0)			      ; set threshold
	 ;
	 ;	  Enable, set RC and return.
	 ;
	 moveq	  #0,d0 			      ; I/O complete
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$0C000,_intena		      ; enable interrupts
enable$  move.l   (sp)+,a0			      ; return
	 jmp	  (a0)
	ELSE
	 jmp	  _LVOEnable(a6)		      ; enable interrupts
	ENDC
	 ;
	 ;
	 ;
	 cnop 0,8 ; align for 040+
cmd_Invalid
	 move.b   #IOERR_NOCMD,IO_ERROR(a1)	      ; set bad status
	 moveq	  #0,d0 			      ; I/O complete
	 rts					      ; return
	 ;
	 ;	  Returns number of bytes currently in internal buffer and
	 ;	  current serial port status.
	 ;
	 ;	  NOTE:  Not completely compatible with standard serial.device
	 ;	  since it doesn't return the upper byte of IO_STATUS.
	 ;
	 cnop 0,8 ; align for 040+
sdcmd_Query:
	IFNE DANGER
	 move.l   #custom+intena,a0
	 move.w   #$04000,(a0)
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 moveq	  #0,d0 			      ; clear d0
	 move.b   _ciabpra,d0			      ; get PR register
	 andi.b   #~PRTMASK,d0			      ; zap printer bits
	 ;
	 ;	  To use this Set SETDSR to one.  This was done
	 ;	  for a user whose DSR pin did not function.
	 ;
	IFNE SETDSR
	 andi.b   #~(1<<CIAB_COMDSR),d0 		    ; set DSR
	ENDC
	 ;
	 ;
	 ;
	 move.w   d0,IO_STATUS(a1)		      ; store status
	 move.l   i_InCnt(pc),IO_ACTUAL(a1)	      ; byte left in buffer
	 ;
	 ;	  Enable, set RC and return.
	 ;
	 moveq	  #0,d0 			      ; I/O complete
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$0C000,(a0)			      ; enable interrupts
enable$  move.l   (sp)+,a0			      ; return
	 jmp	  (a0)
	ELSE
	 jmp	  _LVOEnable(a6)		      ; enable interrupts
						      ; return
	ENDC
	 ;
	 ;
	 ;
	 cnop 0,8 ; align for 040+
sdcmd_SetParams:
	 ;
	 ;	  Validate the read, write, and stop bit lengths.
	 ;
	 cmp.b	  #8,IO_READLEN(a1)		      ; 8 bit chars for read?
	 bne.b	  40$				      ; nope, branch
	 cmp.b	  #8,IO_WRITELEN(a1)		      ; 8 bit chars for write?
	 bne.b	  40$				      ; nope, branch
	 cmp.b	  #1,IO_STOPBITS(a1)		      ; 1 stop bit?
	 bne.b	  40$				      ; nope, branch
	 ;
	 ;	  Get and validate the baud rate.
	 ;
	 move.l   IO_BAUD(a1),d1		      ; get baud from I/O req
	 bne.b	  20$				      ; specified?
	 move.l   vb_CurBaud(a5),d1		      ; get current baud from base
	 bne.b	  20$				      ; specified?
	 move.l   vb_DefBaud(a5),d1		      ; get default baud from base
20$	 cmp.l	  #110,d1			      ; too low?
	 blt.b	  40$				      ; error
	 cmp.l	  #292000,d1			      ; too high?
	 bgt.b	  40$				      ; error
	 ;
	 ;	  Get and validate the buffer length.
	 ;
	 move.l   IO_RBUFLEN(a1),d0		      ; get buffer length
	 bne.b	  30$				      ; specified?
	 move.l   vb_CurRBufLen(a5),d0		      ; get current from base
	 bne.b	  30$				      ; specified?
	 move.l   vb_DefRBufLen(a5),d0		      ; get default from base
	 ;
30$	 bsr.b	  internalReset 		      ; go init baud and buffer
35$	 move.b   d0,IO_ERROR(a1)		      ; set error code
	 bne.b	  39$
	 ;
	 ;	  If the 7wire bit is not on, we will only use 3-wire protocol
	 ;
	 moveq	  #0,d0 			      ; clear flag
	 btst	  #SERB_7WIRE,IO_SERFLAGS(a1)	      ; use 7wire handshaking?
	 beq.b	  36$				      ; nope, branch
	 moveq	  #1,d0 			      ; set flag
36$	 move.l   d0,Handshake			      ; store flag
	 ;
	 ;	  Set RC and return.
	 ;
39$
	 move.l   (sp)+,a0
	 moveq	  #0,d0 			      ; I/O complete
	 jmp	  (a0)				      ; return
	 ;
	 ;	  Invalid parm detected.
	 ;
	 cnop 0,8 ; align for 040+
40$	 moveq	  #SerErr_InvParam,d0		      ; set error
	 bra.b	  35$				      ; go return
	 ;
	 cnop 0,8 ; align for 040+
nscmd_DeviceQuery:
	 move.l   IO_DATA(a1),d0
	 beq.b	  10$
	 move.l   d0,a0
	 move.l   #NSDEVICEQUERYRESULT_SIZE,SIZEAVAILABLE(a0)
	 move.l   #NSDEVICEQUERYRESULT_SIZE,IO_ACTUAL(a1)
	 move.w   #NSDEVTYPE_SERIAL,DEVICETYPE(a0)
	 move.l   a1,-(sp)
	 lea	  cmdlist(pc),a1
	 move.l   a1,SUPPORTEDCOMMANDS(a0)
	 move.l   (sp)+,a1
10$
	 move.l   (sp)+,a0
	 moveq	  #0,d0
	 jmp	  (a0)
	 ;
	 ;	  Reset the buffer and baud rate
	 ;
	 ;	  Registers:  D0 = Buffer length
	 ;		      D1 = Baud rate
	 ;
	 cnop 0,8 ; align for 040+
internalReset:
	 ;
	 ;	  Disable interrupts.
	 ;
	IFNE DANGER
	 move.w   #$04000,_intena
	 addq.b   #1,IDNestCnt(a6)		      ; disable interrupts
	ELSE
	 jsr	  _LVODisable(a6)		      ; disable interrupts
	ENDC
	 ;
	 ;	  Save buffer length and go set serper.
	 ;
	 move.l   d0,-(sp)			      ; save D0
	 move.l   d1,d0 			      ; get baud rate
	 ;
	 ;	  Set serial period register
	 ;
	 cmp.l	  vb_CurBaud(a5),d0		      ; current baud = new baud?
	 beq.b	  40$				      ; yep, just exit
	 move.l   d0,vb_CurBaud(a5)		      ; save new baud
	 move.l   d0,d1 			      ; save again
	 lsl.l	  #3,d0 			      ; baud *= 8
	 sub.l	  d1,d0 			      ; baud -= saved baud
	 move.l   #25000000,d1			      ; get NTSC base
	 cmpi.b   #50,PowerSupplyFrequency(a6)	      ; PAL machine?
	 bne.b	  5$				      ; nope, branch
	 move.l   #24772416,d1			      ; get PAL base
5$	 cmp.l	  #$FFFF,d0			      ; Divide
	 ble.b	  20$				      ;
	 lsr.l	  #5,d0 			      ;
	IFNE NEWCODE
	 divu.l   d0,d1 			      ;
	ELSE
	 divu.w   d0,d1 			      ;
	 andi.l   #$FFFF,d1			      ;
	ENDC
	 lsr.l	  #5,d1 			      ;
	 bra.b	  30$				      ;
	 cnop 0,8 ; align for 040
20$	 divu.w   d0,d1 			      ;
30$	 move.w   d1,_serper			      ; set period value
40$	 move.l   (sp)+,d0			      ; restore D0
	 ;
	 ;	  Determine if the buffer length is adequate for the selected CPS.
	 ;	  If not, use 64K for the length.
	 ;
	 move.l   vb_CurBaud(a5),d1		      ; get current baud
	 lsr.l	  #2,d1 			      ; divide by 4
	 cmp.l	  d1,d0 			      ; buflen > CPS
	 bhi.b	  10$				      ; yep, branch
	 move.l   #65536,d0			      ; else use 64K
	 ;
	 ;	  Allocate new internal buffer
	 ;
10$	 cmp.l	  vb_CurRBufLen(a5),d0		      ; len same as previous?
	 beq.b	  11$				      ; yep, so no need to alloc
	 move.l   d0,d1 			      ; save length
	 move.l   a1,-(sp)			      ; save registers
	 move.l   d1,-(sp)			      ; save registers
	 moveq	  #MEMF_PUBLIC|MEMF_FAST,d1	      ; public fast memory
	 jsr	  _LVOAllocMem(a6)		      ; go allocate it
	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a1			      ; restore registers
	 tst.l	  d0				      ; did we get it?
	 beq.b	  21$				      ; if zero, error
	 move.l   d0,-(sp)			      ; save d0
	 bsr.b	  freeBuf			      ; go free previous buffer
	 move.l   (sp)+,vb_CurRBuf(a5)		      ; store new ptr
	 move.l   d1,vb_CurRBufLen(a5)		      ; and length
	 bsr	  cmd_Clear			      ; go setup buffer
11$	 moveq	  #0,d0 			      ; success
	 bra.b	  22$				      ; return
	 cnop 0,8 ; align for 040+
21$	 moveq	  #SerErr_BufErr,d0		      ; set error status
22$	 ;
	 ;	  Enable and return to caller.
	 ;
	IFNE DANGER
	 subq.b   #1,IDNestCnt(a6)
	 bge.b	  enable$
	 move.w   #$0C000,_intena		      ; enable interrupts
enable$  move.l   (sp)+,a0			      ; return
	 jmp	  (a0)
	ELSE
	 jmp	  _LVOEnable(a6)		      ; enable interrupts
	ENDC
	 ;
	 ;	  Free internal buffer
	 ;
	 cnop 0,8 ; align for 040+
freeBuf:
	 move.l   vb_CurRBuf(a5),d0		      ; is one there?
	 beq.b	  10$				      ; no so branch
	 move.l   a1,-(sp)			      ; save registers
	 move.l   d1,-(sp)			      ; save registers
	 move.l   d0,a1 			      ; get ptr
	 move.l   vb_CurRBufLen(a5),d0		      ; get length
	 clr.l	  vb_CurRBuf(a5)		      ; clear
	 clr.l	  vb_CurRBufLen(a5)		      ; clear
	 jsr	  _LVOFreeMem(a6)		      ; free it
	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a1			      ; restore registers
10$	 move.l   (sp)+,a0			      ; return
	 jmp	  (a0)
	 ;
	 ;	  Checks CTS status and if clear generates a TBE interrupt or
	 ;	  requeues the timer request.
	 ;
	 ;	  Entered from Exec using the MsgPort callback.
	 ;
	 ;	  Input:   a6 = ExecBase
	 ;	  Output:  none
	 ;
	 ;	  No need to preserve d0/d1/a0/a1
	 ;
	 cnop 0,8 ; align for 040+
timerRtn:
	 lea	  timerReq(pc),a1		      ; get ptr to timer request
	 ; This is the REMOVE macro
	 move.l   (a1)+,a0
	 move.l   (a1),a1
	 move.l   a0,(a1)
	 move.l   a1,LN_PRED(a0)
	 ;
	 ;	  If we were breaking, reset adkcon.
	 ;
	 move.l   #custom+adkconr,a0		      ; get ptr to custom regs
	 btst	  #ADKB_UARTBRK&7,(a0)		      ; were we breaking?
	 beq.b	  10$				      ; nope, skip reset
	 ;
	 move.w   #ADKF_UARTBRK,adkcon-adkconr(a0)    ; stop breaking
	 ;
	 ;
	 ;
10$	 tst.l	  Handshake(pc) 		      ; are we handshaking?
	 beq.b	  20$				      ; nope, generate interrupt
	 btst	  #CIAB_COMCTS,_ciabpra 	      ; clear to send?
	 beq.b	  20$				      ; yep, go start writing
	 ;
	 lea	  timerReq(pc),a1		      ; get ptr to timer request
	 move.l   #1000,IOTV_TIME+TV_MICRO(a1)	      ; wait for .001 seconds
	 jmp	  _LVOSendIO(a6)		      ; go queue it / return
	 ;
	 ;	  CTS is clear so generate TBE interrupt to restart writing
	 ;
	 cnop 0,8 ; align for 040+
20$	 move.w   #INTF_SETCLR|INTF_TBE,intreq-adkconr(a0)  ; set TBE interrupt
	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return
	 ;
	 ;	Non serial interrupt
	 ;	Handles:
	 ;	    INTB_DSKBLK
	 ;	    INTB_SOFTINT
	 ;
	 ;  a0 - custom chips base
	 ;  a1 - is_Data
	 ;
	 cnop 0,8 ; align for 040+
level1n:
	 move.l   a6,-(sp)			      ; save registers
	 move.l   a5,-(sp)			      ; save registers
	 move.l   a1,-(sp)			      ; save registers
	 move.l   d1,-(sp)			      ; save registers
	 move.l   d0,d1 			      ; get enabled interrupts
	 swap	  d1
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 and.l	  d0,d1 			      ; and in requested interrupts
	 ;
	 move.l   #custom,a0			      ; get ptr to custom regs
	 btst	  #INTB_DSKBLK,d1		      ; Disk block done?
	 beq.b	  10$				      ; nope, branch
	 ;
	 move.l   IVDSKBLK(a6),a1		      ; get data and code ptrs
	 move.l   IVDSKBLK+4(a6),a5		      ; get data and code ptrs
	 pea	  20$(pc)			      ; push return address
	 jmp	  (a5)				      ; jump to routine
	 ;
	 cnop 0,8 ; align for 040+
10$	 move.l   IVSOFTINT(a6),a1		      ; get data and code ptrs
	 move.l   IVSOFTINT+4(a6),a5		      ; get data and code ptrs
	 jsr	  (a5)				      ; jump to routine
	 ;
20$	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,a5			      ; restore registers
	 move.l   (sp)+,a6			      ; restore registers
	 move.l   (sp)+,d0			      ; restore registers
	 move.l   (sp)+,a0			      ; restore registers
	 rte					      ; return
	 ;
	 ;	Level 1 interrupt handler
	 ;	Handles:
	 ;	    INTB_TBE
	 ;
	 ;  a1 - is_Data
	 ;
	 cnop 0,8 ; align for 040+
level1:
	 move.l   a0,-(sp)			      ; save A0 (faster than MOVEM)
	 move.l   d0,-(sp)			      ; save D0 (faster than MOVEM)
	 move.l   #custom+intenar,a0		      ; get ptr to custom regs
	 move.l   (a0),d0			      ; get intenar & intreqr
	 btst	  #INTB_INTEN+16,d0		      ; interrupts enabled?
	 beq.b	  30$				      ; nope, ignore
	 btst	  #INTB_TBE,d0			      ; xmit buffer empty?
	 beq.b	  level1n			      ; nope, invoke old handler
	 ;
	 ;	  Handle "Transmit Buffer Empty" interrupt (write)
	 ;
	 move.w   #INTF_TBE,intreq-intenar(a0)	      ; clear interrupt
	 ;
	 ;	  If we're not handshaking, bypass it.
	 ;
10$	 tst.l	  Handshake(pc) 		      ; are we handshaking?
	 beq.b	  20$				      ; nope, skip CTS test
	 btst	  #CIAB_COMCTS,_ciabpra 	      ; clear to send?
	 bne.b	  40$				      ; nope, branch
	 ;
	 ;	  If cw_Length goes negative here, we are either done with a
	 ;	  request or we were called as a result of a fake interrupt
	 ;	  to force us to get the next request going.
	 ;
20$	 subq.l   #1,cw_Length			      ; decr write length
	 blt.b	  60$				      ; < zero, done, branch
	 ;
	 ;	  Currently processing a request.
	 ;
	 move.l   cw_Buffer(pc),a0		      ; get buffer ptr
	 move.w   #256,d0			      ; set stop bit
	 move.b   (a0)+,d0			      ; get next byte
	 move.l   a0,cw_Buffer			      ; store buffer ptr
	 move.w   d0,_serdat			      ; store in serdat reg
	 ;
30$	 move.l   (sp)+,d0			      ; restore registers
	 move.l   (sp)+,a0			      ; restore registers
	 rte					      ; return
	 ;
	 ;	  Queue a timer request to recheck CTS status
	 ;
40$	 move.l   a6,-(sp)			      ; save registers
	 move.l   d1,-(sp)			      ; save registers
	 move.l   a1,-(sp)			      ; save registers
	 lea	  timerReq(pc),a1		      ; get ptr to timer request
	 move.l   #1000,IOTV_TIME+TV_MICRO(a1)	      ; wait for .001 seconds
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 jsr	  _LVOSendIO(a6)		      ; queue the request
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a6			      ; restore registers
	 bra.b	  30$				      ; go return
	 ;
	 ;	  There aren't anymore requests, so clear and exit
	 ;
	 cnop 0,8 ; align for 040+
50$	 clr.l	  cw_Length-Start(a6)		      ; clear length
	 clr.l	  cw_IOReq-Start(a6)		      ; clear
	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,a6			      ; restore registers
	 bra.b	  30$				      ; go return
	 ;
	 ;	  Write request completed
	 ;
	 cnop 0,8 ; align for 040+
60$	 move.l   a6,-(sp)			      ; save registers
	 move.l   a1,-(sp)			      ; save registers
	 move.l   d1,-(sp)			      ; save registers
	 ;
	 move.l   cw_IOReq(pc),d0		      ; active I/O request?
	 beq.b	  70$				      ; nope, branch
	 ;
	 ;	  Reply it and setup for next
	 ;
	 move.l   d0,a1 			      ; get I/O request
	 andi.b   #~(1<<IOSERB_ACTIVE),IO_FLAGS(a1)   ; no longer active
	 clr.b	  IO_ERROR(a1)			      ; no error
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 jsr	  _LVOReplyMsg(a6)		      ; return I/O request
	 ;
70$	 lea	  Start(pc),a6			      ; get section base
	 ;
	 lea	  writeQ(pc),a1 		      ; get ptr to write queue
	 move.l   (a1),a0			      ; get head of list
	 move.l   (a0),d0			      ; get successor
	 beq.b	  50$				      ; end of list? yep, branch
	 ;
	 ;	  Remove the node from the list
	 ;
	 move.l   d0,(a1)			      ; make new head
	 exg.l	  d0,a0 			      ; swap nodes
	 move.l   a1,LN_PRED(a0)		      ; store predecessor
	 ;
	 move.l   d0,a1 			      ; get I/O request
	 ;
	 ;	  If it's a BREAK, then branch to process as such.
	 ;
	 cmpi.w   #SDCMD_BREAK,IO_COMMAND(a1)	      ; BREAK command?
	 beq.b	  100$				      ; yep, branch
	 ;
	 ;	  Check for absolute length
	 ;
	 move.l   IO_LENGTH(a1),IO_ACTUAL(a1)	      ; get length and set it
	 move.b   IO_FLAGS(a1),d1
	 move.l   IO_LENGTH(a1),cw_Length-Start(a6)   ; store length
	 andi.b   #~(1<<IOSERB_QUEUED),d1	      ; no longer queued
	 move.l   IO_DATA(a1),cw_Buffer-Start(a6)     ; store buffer ptr
	 ori.b	  #1<<IOSERB_ACTIVE,d1		      ; make it active
	 move.l   a1,cw_IOReq-Start(a6) 	      ; store I/O request ptr
	 move.b   d1,IO_FLAGS(a1)
	 ;
	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,a6			      ; restore registers
	 bra	  10$				      ; go start request
	 ;
	 ;	  Start the BREAK.
	 ;
	 cnop 0,8 ; align for 040+
100$	 move.b   IO_FLAGS(a1),d1
	 clr.l	  cw_Length-Start(a6)		      ; clear length
	 andi.b   #~(1<<IOSERB_QUEUED),d1	      ; no longer queued
	 move.l   a1,cw_IOReq-Start(a6) 	      ; store I/O request ptr
	 ori.b	  #1<<IOSERB_ACTIVE,d1		      ; make it active
	 move.w   #ADKF_SETCLR|ADKF_UARTBRK,_adkcon   ; start break
	 move.b   d1,IO_FLAGS(a1)
	 move.l   IO_BRKTIME(a1),d0		      ; get break time
	 lea	  timerReq(pc),a1		      ; get ptr to timer request
	 move.l   d0,IOTV_TIME+TV_MICRO(a1)	      ; set the timeout
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 jsr	  _LVOSendIO(a6)		      ; queue the request
	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,a6			      ; restore registers
	 bra	  30$				      ; go exit
	 ;
	 ;	Non serial interrupt
	 ;	Handles:
	 ;	    INTB_DSKSYNC
	 ;
	 ;  a0 - custom chips base
	 ;
	 cnop 0,8 ; align for 040+
level5n:
	 move.l   a6,-(sp)			      ; save registers
	 move.l   a5,-(sp)			      ; save registers
	 move.l   d1,-(sp)			      ; save registers
	 move.l   a1,-(sp)			      ; save registers
	 move.l   d0,d1
	 move.l   SysBase(pc),a6		      ; get ExecBase
	 swap	  d1
	 move.l   IVDSKSYNC(A6),a1		      ; get data and code ptrs
	 and.l	  d0,d1
	 move.l   #custom,a0			      ; get ptr to custom regs
	 move.l   IVDSKSYNC+4(A6),a5		      ; get data and code ptrs
	 jsr	  (a5)				      ; branch to routine
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,d1			      ; restore registers
	 move.l   (sp)+,a5			      ; restore registers
	 move.l   (sp)+,a6			      ; restore registers
	 move.l   (sp)+,a0			      ; restore registers
	 move.l   (sp)+,d0			      ; restore registers
	 rte					      ; return
	 ;
	 ;	Default Level 5 handler
	 ;	Handles:
	 ;	    INTB_RBF
	 ;
	 ;  a1 - custom chips base
	 ;
	 cnop 0,8 ; align for 040+
level5:
	 move.l   d0,-(sp)			      ; save registers
	 move.l   a0,-(sp)			      ; save registers
	 move.l   #custom+serdatr,a0		      ; get ptr to custom regs
	 ;
	 move.l   intenar-serdatr(a0),d0	      ; get intenar & intreqr
	 btst	  #INTB_INTEN+16,d0		      ; interrupts enabled?
	 beq.b	  40$				      ; nope, ignore
	 btst	  #INTB_RBF,d0			      ; receive buffer full?
	 beq.b	  level5n			      ; nope, invoke old handler
	 ;
	 move.w   (a0),d0			      ; Overrun?
	 bpl.b	  20$				      ; nope, branch
	 ;
	 ;	  We've missed some data, so set overrun flag.
	 ;
	 addq.l   #1,Overrun			      ; set overrun flag
20$	 move.w   #INTF_RBF,intreq-serdatr(a0)	      ; clear RBF interrupt
	 move.l   i_BufIn(pc),a0		      ; get current ptr
	 move.b   d0,(a0)+			      ; store received byte
	 addq.l   #1,i_InCnt			      ; incr bytes in buffer
	 ;
	 cmpa.l   i_BufEnd(pc),a0		      ; hit end of buffer?
	 bne.b	  30$				      ; nope, branch
	 ;
	 ;	  Hit physical end of buffer, so wrap to the start of the buffer.
	 ;
	 move.l   i_BufPtr(pc),a0		      ; get buffer ptr
30$	 move.l   a0,i_BufIn			      ; store input ptr
	 ;
	 subq.l   #1,i_Thresh			      ; close to full buffer?
	 bne.b	  40$				      ; nope, branch
	 ;
	 ;	  Hit buffer threshold, so tell other end not to send any more
	 ;	  data.
	 ;
	 tst.l	  Handshake(pc) 		      ; are we handshaking?
	 beq.b	  40$				      ; nope, skip RTS
	 ori.b	  #1<<CIAB_COMRTS,_ciabpra	      ; block further input
	 ;
40$	 move.l   (sp)+,a0			      ; restore registers
	 move.l   (sp)+,d0			      ; restore registers
	 rte					      ; return
	 ;
	 ;
	 ;  a1 - IS_DATA
	 ;  a5 - jump vector register
	 ;
	 cnop 0,8 ; align for 040+
level2:
	 ;
	 ;	  If there's nothing in the buffer, there's no point in going
	 ;	  any further.
	 ;
	 tst.l	  i_InCnt(pc)			      ; anything in the buffer?
	 beq.b	  9$				      ; nope, branch
	 ;
	 ;	  If we've been "disabled" then get out.
	 ;
20$	 tst.l	  disableRead(pc)		      ; internally disabled?
	 bge.b	  10$				      ; yep, get out of here
	 lea	  Start(pc),a5			      ; get base
	 ;
	 ;	  If we have an active request, branch down and try to fulfill it.
	 ;
	 move.l   cr_IOReq(pc),d0		      ; get and test active I/O
	 bne.b	  30$				      ; nzero, active, branch
	 ;
	 ;	  Get first node in list and test if empty.
	 ;
	 move.l   (a1),a0			      ; get head of list
	 move.l   (a0),d0			      ; get successor
	 beq.b	  9$				      ; end of list? yep, branch
	 ;
	 ;	  Remove the node from the list
	 ;
	 move.l   d0,(a1)			      ; make new head
	 exg.l	  d0,a0 			      ; swap nodes
	 move.l   a1,LN_PRED(a0)		      ; store predecessor
	 ;
	 ;	  Setup fields for processing a read request
	 ;
	 move.l   d0,a1 			      ; get I/O request
	 move.b   IO_FLAGS(a1),d1
	 andi.b   #~(1<<IOSERB_QUEUED),d1	      ; no longer queued
	 move.l   a1,cr_IOReq-Start(a5) 	      ; store I/O request
	 ori.b	  #1<<IOSERB_ACTIVE,d1		      ; make it active
	 move.l   IO_DATA(a1),cr_OutPtr-Start(a5)     ; get/set output ptr
	 move.b   d1,IO_FLAGS(a1)
	 move.l   IO_LENGTH(a1),cr_Length-Start(a5)   ; get/set output count
	 beq.b	  35$
	 ;
	 ;	  Process an active I/O request
	 ;
30$	 move.l   SysBase(pc),a6		      ; get ExecBase
	 move.l   d0,a1 			      ; get I/O request
	 bsr.b	  copyData			      ; go copy 'em
	 tst.l	  d0				      ; done with request?
	 beq.b	  9$				      ; nope, branch
	 ;
	 ;	  the request has been satisfied, so return it.
	 ;
35$	 clr.l	  cr_IOReq-Start(a5)		      ; clear request
	 andi.b   #~(1<<IOSERB_ACTIVE),IO_FLAGS(a1)   ; no longer active
	 jsr	  _LVOReplyMsg(a6)		      ; return I/O
10$	 moveq	  #0,d0 			      ; set Z flag
9$	 move.l   (sp)+,a0
	 jmp	  (a0)				      ; return
	 ;
	 ;	  Registers:
	 ;	  Entry:   A1	    Ptr to I/O Request
	 ;		   A6	    ExecBase
	 ;	  Exit:    D0	    ZERO - request not done, do not reply it
	 ;			    NZERO - request done, reply it
	 ;
	 cnop 0,8 ; align for 040+
copyData:
	 ;
	 ;	  Process an active I/O request (or fall through from above)
	 ;
	 move.l   a5,-(sp)			      ; save registers
	 move.l   d5,-(sp)			      ; save registers
	 move.l   a4,-(sp)			      ; save registers
	 move.l   d4,-(sp)			      ; save registers
	 move.l   a3,-(sp)			      ; save registers
	 move.l   d3,-(sp)			      ; save registers
	 move.l   a2,-(sp)			      ; save registers
	 move.l   a1,-(sp)			      ; save registers
	 move.l   d2,-(sp)			      ; save registers
	 lea	  Start(pc),a5			      ; get base
	 move.l   a1,a4 			      ; get I/O request
	 ;
	 move.l   i_BufOut(pc),a2		      ; get current bufout ptr
	 move.l   cr_OutPtr(pc),a3		      ; get current output ptr
	 move.l   cr_Length(pc),d2		      ; get current bytes needed
	 move.l   i_InCnt(pc),d3		      ; get current bytes in buffer
	 ;
	 ;	  If we don't have enough bytes to satisfy the request,
	 ;	  set the length to the number of bytes we do have.
	 ;
	 cmp.l	  d2,d3 			      ; enuf to satisfy request?
	 bge.b	  15$				      ; yep, so branch
	 move.l   d3,d2 			      ; # to copy = # in buffer
	 bra.b	  16$				      ; branch to loop entry
	 ;
	 ;	  Start of copy loop.
	 ;
	 cnop 0,8 ; align for 040+
10$	 move.l   i_BufPtr(pc),a2		      ; reset bufout to start
	 ;
	 ;	  Entry point of copy loop.
	 ;
15$	 move.l   d2,d3 			      ; xfer # of bytes to copy
16$
	 ;
	 ;	  If the copy will extend past the end of the buffer, we can
	 ;	  only copy the number of bytes to the end this go around.
	 ;
	 move.l   i_BufEnd(pc),d0		      ; get ptr to end of buf
	 sub.l	  a2,d0 			      ; calc # of bytes to end
	 cmp.l	  d0,d3 			      ; # to copy < # to end?
	 blt.b	  20$				      ; yep, branch
	 move.l   d0,d3 			      ; get bytes to end
	 ;
	 ;	  Registers:
	 ;
	 ;	  A2 = pointer from which data will be copied
	 ;	  A3 = pointer to which data will be copied
	 ;	  D2 = number of bytes that need to be copied
	 ;	  D3 = number of bytes to copy this iteration
	 ;
20$	 move.l   d3,d0 			      ; get length
	IFNE EOFCODE
	 btst	  #SERB_EOFMODE,IO_SERFLAGS(a4)       ; EOFMODE requested?
	 beq.b	  30$				      ; nope, just go copy
	 ;
	 ;	  EOFMODE was specified so copy characters 1 at a time until
	 ;	  we hit an EOF character, the output butter has filled, or
	 ;	  the input buffer has drained.
	 ;
	 move.l   IO_TERMARRAY(a4),d4		      ; get termarry (part 1)
	 rol.l	  #8,d4
	 move.l   IO_TERMARRAY(a4),d5		      ; get termarry (part 2)
	 move.l   d4,a1 			      ; save d4
	 rol.l	  #8,d5
	 ;
21$	 move.b   (a2)+,d1			      ; get byte
	 ; This way the term char IS copied (if term char is not copied eofmode will fail)
	 move.b   d1,(a3)+			      ; put in output buffer
	 ;
	 cmp.b	  d4,d1 			      ; found term char?
	 bge.b	  22$				      ; possibly, branch
	 rol.l	  #8,d4
	 cmp.b	  d4,d1 			      ; found term char?
	 bge.b	  22$				      ; possibly, branch
	 rol.l	  #8,d4
	 cmp.b	  d4,d1 			      ; found term char?
	 bge.b	  22$				      ; possibly, branch
	 rol.l	  #8,d4
	 cmp.b	  d4,d1 			      ; found term char?
	 bge.b	  22$				      ; possibly, branch
	 move.l   d5,d4 			      ; get termarry (part 2)
	 cmp.b	  d4,d1 			      ; found term char?
	 bge.b	  22$				      ; possibly, branch
	 rol.l	  #8,d4
	 cmp.b	  d4,d1 			      ; found term char?
	 bge.b	  22$				      ; possibly, branch
	 rol.l	  #8,d4
	 cmp.b	  d4,d1 			      ; found term char?
	 bge.b	  22$				      ; possibly, branch
	 rol.l	  #8,d4
	 cmp.b	  d4,d1 			      ; found term char?
22$	 beq.b	  24$				      ; term char found?
	 ;
	 ;	  Didn't find a term character, so continue with the copy loop
	 ;
23$	 move.l   a1,d4 			      ; get termarry (part 1)
	 subq.l   #1,d0 			      ; decr length counter
	 bne.b	  21$				      ; continue if more
	 bra.b	  40$				      ; done with copy, branch
	 ;
	 ;	  We've found a termination character.
	 ;
	 cnop 0,8 ; align for 040+
24$	 clr.l	  cr_Length-Start(a5)		      ; done with request
	 bra.b	  50$				      ; branch
	 cnop 0,8 ; align for 040+
	 ENDC
	 ;
	 ;	  EOFMODE not specified, so just do a bulk copy.
	 ;
	 ;	  XXX POSSIBLE SPEEDUP XXX
	 ;
	 ;	  For short copies it would be quicker to have a simple inline
	 ;	  loop, but what's short???  It would be different by CPU.
	 ;
30$	 move.l   a3,a1 			      ; where to put it
	 move.l   a2,a0 			      ; where to get it
; NB: buffer size is multiple of 64bytes (not contents size)
	 jsr	 _LVOCopyMem(a6)		      ; we use a CopyMem() patch
	 adda.l   d3,a2 			      ; update bufout
	 adda.l   d3,a3 			      ; update outptr
	 ;
	 ;	  Fall through and entered from EOFMODE loop
	 ;
	 ;	  If the following calculation results in a value greater than
	 ;	  zero, then we have a buffer wrap and need to process the
	 ;	  remaining bytes at the beginning of the buffer.
	 ;
40$	 sub.l	  d3,d2 			      ; calc bytes left to copy
	 bgt.b	  10$				      ; >0, more to copy, branch
	 ;
	 ;
	 ;
50$	 move.l   a3,d1 			      ; get outptr
	 sub.l	  cr_OutPtr(pc),d1		      ; calc length
	 move.l   a3,cr_OutPtr-Start(a5)	      ; update outptr
	 ;
	 ;	  Update output ptr and I/O request
	 ;
	 move.l   a2,i_BufOut-Start(a5) 	      ; store bufout ptr
	 add.l	  d1,IO_ACTUAL(a4)		      ; update I/O request
	 ;
	 ;	  Update number of bytes left in the buffer.
	 ;
	 sub.l	  d1,i_InCnt-Start(a5)		      ; calc bytes left in buffer
	 ;
	 ;	  If the threshold becomes positive here, then, if requested,
	 ;	  tell the other end that it's okay to start sending more data.
	 ;
	 add.l	  d1,i_Thresh-Start(a5) 	      ; calc thresh and test
	 ble.b	  60$				      ; <= 0, need more data, skip
	 tst.l	  Handshake(pc) 		      ; are we handshaking?
	 beq.b	  60$				      ; nope, skip RTS
	 andi.b   #~(1<<CIAB_COMRTS),_ciabpra	      ; ready to recieve more data
	 ;
	 ;	  Set error if we've had any overruns.
	 ;
60$	 tst.l	  Overrun(pc)			      ; did overrun occur?
	 beq.b	  70$				      ; nope, branch
	 clr.l	  Overrun-Start(a5)		      ; reset overrun flag
	 move.b   #SerErr_LineErr,IO_ERROR(a4)	      ; set error code
	 ;
	 ;	  Calc number of bytes left to copy.  If the result is greater
	 ;	  than zero, then we have more to copy so return an incomplete
	 ;	  status.
	 ;
70$	 moveq	  #0,d0 			      ; assume I/O incomplete
	 sub.l	  d1,cr_Length-Start(a5)	      ; update length and test
	 bgt.b	  90$				      ; >0, more to do, branch
	 ;
	 ;	  We've completed the I/O request either by copying the requested
	 ;	  of bytes or by finding an EOFMODE character, so return a "reply"
	 ;	  status.
	 ;
	 moveq	  #1,d0 			      ; indicate reply
	 ;
	 ;	  Restore and exit
	 ;
90$	 move.l   (sp)+,d2			      ; restore registers
	 move.l   (sp)+,a1			      ; restore registers
	 move.l   (sp)+,a2			      ; restore registers
	 move.l   (sp)+,d3			      ; restore registers
	 move.l   (sp)+,a3			      ; restore registers
	 move.l   (sp)+,d4			      ; restore registers
	 move.l   (sp)+,a4			      ; restore registers
	 move.l   (sp)+,d5			      ; restore registers
	 move.l   (sp)+,a5			      ; restore registers
	 move.l   (sp)+,a0			      ; return (status in D0)
	 jmp	  (a0)
	 ;
	 ;	  Align data
	 ;
	 CNOP	  0,8
	 ;
	 ;
	 ;
Init:
	 DC.L	  sizeof_Base8n1
	 DC.L	  funcTab
	 DC.L	  dataTab
	 DC.L	  InitRoutine
	 ;
	 ;
	 ;
funcTab:
	 DC.W	  -1
	 DC.W	  dev_Open-funcTab
	 DC.W	  dev_Close-funcTab
	 DC.W	  dev_Expunge-funcTab
	 DC.W	  dev_Null-funcTab
	 DC.W	  dev_BeginIO-funcTab
	 DC.W	  dev_AbortIO-funcTab
	 DC.W	  -1
	 ;
	 ;
	 ;
dataTab:
	 INITBYTE LN_TYPE,NT_DEVICE
	 INITLONG LN_NAME,Name
	 INITBYTE LIB_FLAGS,LIBF_SUMUSED|LIBF_CHANGED
	 INITWORD LIB_VERSION,VERSION
	 INITWORD LIB_REVISION,REVISION
	 INITLONG LIB_IDSTRING,IdString
	 DC.W	  0
	 ;
	 ;	  String Constants
	 ;
miscresource:
	 DC.B	  "misc.resource",0
timerdevice:
	 DC.B	  "timer.device",0
intuitlib:
	 DC.B	  "intuition.library",0
Name:
	 DC.B	  "8n1.device",0
IdString:
	 VSTRING
	 ;
	 ;	  End of checksummed area.  (Realigns data too!)
	 ;
ENDTag:
	 CNOP	  0,8
	 ;
	 ;	  Global SysBase (Use instead of AbsExecBase for speed)
	 ;
SysBase:
	 DC.L	  0
	 ;
	 ;	  Internal buffer tracking (DO NOT CHANGE THE ORDER!!!!)
	 ;
i_BufPtr:
	 DC.L	  0
i_BufIn:
	 DC.L	  0
i_BufOut:
	 DC.L	  0
i_BufEnd:
	 DC.L	  0
i_InCnt:
	 DC.L	  0
i_Thresh:
	 DC.L	  0
	 ;
	 ;	  Used while processing a read request.
	 ;
cr_IOReq:
	 DC.L	  0
cr_OutPtr:
	 DC.L	  0
cr_Length:
	 DC.L	  0
	 ;
	 ;	  List head for read requests
	 ;
readQ:
	 DC.L	  readQ+MLH_TAIL
	 DC.L	  0
	 DC.L	  readQ
	 ;
	 ;	  Write control.
	 ;
cw_Length:
	 DC.L	  0
cw_Buffer:
	 DC.L	  0
cw_IOReq:
	 DC.L	  0
	 ;
	 ;	  List head for write requests
	 ;
writeQ:
	 DC.L	  writeQ+MLH_TAIL
	 DC.L	  0
	 DC.L	  writeQ
	 ;
	 ;
	 ;
timerPort:
	 DC.L	  0				      ; LN_SUCC
	 DC.L	  0				      ; LN_PRED
	 DC.B	  NT_MSGPORT			      ; LN_TYPE
	 DC.B	  0				      ; LN_PRI
	 DC.L	  0				      ; LN_NAME
	 DC.B	  3				      ; MP_FLAGS (undoc'ed)
	 DC.B	  0				      ; MP_SIGBIT
	 DC.L	  timerRtn			      ; MP_SIGTASK
	 DC.L	  timerPort+MP_MSGLIST+LH_TAIL	      ; LH_HEAD
	 DC.L	  0				      ; LH_TAIL
	 DC.L	  timerPort+MP_MSGLIST		      ; LH_TAILPRED
	 DC.B	  0				      ; LH_TYPE
	 DC.B	  0				      ; LH_pad
	 DC.W	  0				      ; long align
	 ;
	 ;
	 ;
timerReq:
	 DC.L	  0				      ; LN_SUCC
	 DC.L	  0				      ; LN_PRED
	 DC.B	  NT_MESSAGE			      ; LN_TYPE
	 DC.B	  0				      ; LN_PRI
	 DC.L	  0				      ; LN_NAME
	 DC.L	  timerPort			      ; MN_REPLYPORT
	 DC.W	  IOTV_SIZE			      ; MN_LENGTH
	 DC.L	  0				      ; IO_DEVICE
	 DC.L	  0				      ; IO_UNIT
	 DC.W	  TR_ADDREQUEST 		      ; IO_COMMAND
	 DC.B	  0				      ; IO_FLAGS
	 DC.B	  0				      ; IO_ERROR
	 DC.L	  0				      ; TV_SECS
	 DC.L	  0				      ; TV_MICROS
	 ;
	 ;
	 ;
VBInterrupt:
	 DC.L	  0				      ; LN_SUCC
	 DC.L	  0				      ; LN_PRED
	 DC.B	  NT_INTERRUPT			      ; LN_TYPE
	 DC.B	  0				      ; LN_PRI
	 DC.L	  Name				      ; LN_NAME
	 DC.L	  readQ 			      ; IS_DATA
	 DC.L	  level2			      ; IS_CODE
	 ;
	 ;	  Global flags
	 ;
	 CNOP	  0,4
Overrun:
	 DC.L	  0
Handshake:
	 DC.L	  1
disableRead:
	 DC.L	  -1
cmdlist:
	 dc.w	  CMD_RESET
	 dc.w	  CMD_READ
	 dc.w	  CMD_WRITE
	 dc.w	  CMD_CLEAR
	 dc.w	  CMD_FLUSH
	 dc.w	  SDCMD_QUERY
	 dc.w	  SDCMD_BREAK
	 dc.w	  SDCMD_SETPARAMS
	 dc.w	  NSCMD_DEVICEQUERY
	 dc.w	  0
	 ;
	 ;
	 ;
	 END

