home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Club Amiga de Montreal - CAM
/
CAM_CD_1.iso
/
files
/
515.lha
/
NewSer
/
newser.a
< prev
next >
Wrap
Text File
|
1991-06-08
|
66KB
|
2,665 lines
*****************************************************************************
* Program: newser.asm - Copyright ©1990,91 by Dan Babcock
* Function: A "serial.device" compatible driver for the Rockwell 65C52
* based on the 1.3 RKM driver example.
*
* Author: Dan Babcock
* History: 08/12/90 V0.50 Created
* 09/06/90 V0.51 Cleaned up "eofdump" code.
* 11/04/90 V0.52 Misc. minor changes
* 11/25/90 V0.53 Changed path and name of include file
* 03/25/91 V1.10 I (Mike Mossman) added code to support the
* ASDG SetCtrlLines command.
*
* [To all: Please don't forget to bump the revision numbers
* if you do *any* modifications at all. -Jeff]
*
* This code is designed to be ROMable (but not position independent).
*
* Feel free to contact me:
*
* [School address] [Permanent address] People/Link:
* Dan Babcock Dan Babcock DANBABCOCK
* Atherton Hall Room 63 P.O. Box 1532
* University Park, PA 16802 Southgate, MI 48195
* Voice: (814)-862-2931
*
* I am also reachable via internet. I read comp.sys.amiga.tech.
*
* General notes
* =============
* The macro "PUTDEBUG" is used to output debugging information via the
* internal serial port at a low level. It may be used anywhere, including
* critical sections, supervisor mode, and interrupt code. If debugging is
* not desired, set the INFO_LEVEL equate to zero. Conversely, if debugging
* is desired, set it to a high value (e.g. 100000)
*
* A consistent (for the most part) usage of registers was employed:
* A1 - pointer to an IORequest structure
* A3 - pointer to a unit structure (defined below)
* A4 - pointer to a serprefs structure (defined below)
* A5 - pointer to the hardware (one channel, that is)
* A6 - pointer to the device structure (defined below)
*
* Known difference(s) between this and serial.device are:
* 1. Start/Stop do not send XON/XOFF characters to the outside world
* as implied in the autodoc (I don't think this is important).
* 2. Reading is never performed in the caller's context.
* 3. Newser does not currently send an XOFF or otherwise tell the outside
* world to "shut up" when the driver's input buffer fills; it simply
* reports a buffer overflow condition and continues as normal.
*
* Possible future enhancements:
* 1. Read the default prefs from memory (set up previously) or a file
* 2. Check to see whether units 3 & 4, if requested, actually exist
*
* Suggestions for additional enhancements are welcome.
*****************************************************************************
VERSION EQU 1
REVISION EQU 1
INFO_LEVEL EQU 0
;This define is used to tell which commands should be handled
;immediately (on the caller's schedule).
;
;The immediate commands are Invalid, Reset, Stop, Start, Flush, Clear,
;Query, and SetParams
;
;Note that this method limits you to just 32 device specific commands,
;which may not be enough.
IMMEDIATES EQU %00000000000000010000101111110011
; --------========--------========
; FEDCBA9876543210FEDCBA9876543210
INCDIR "include:"
INCLUDE "exec/types.i"
INCLUDE "exec/funcdef.i"
INCLUDE "exec/exec.i"
INCLUDE "exec/exec_lib.i"
INCLUDE "exec/ables.i"
INCLUDE "exec/errors.i"
INCLUDE "exec/interrupts.i"
INCLUDE "exec/libraries.i"
INCLUDE "exec/lists.i"
INCLUDE "exec/initializers.i"
INCLUDE "exec/resident.i"
INCLUDE "devices/serial.i"
INCLUDE "devices/timer.i"
INCLUDE "hardware/custom.i"
INCLUDE "hardware/intbits.i"
INCLUDE "libraries/dos.i"
INCLUDE "libraries/dosextens.i"
INCLUDE "ioexp.i"
; Things we define for external reference (for easier debuggging)
XDEF LibStart
XDEF RomTag
XDEF LibName
XDEF LibIDString
XDEF LibInit
XDEF LibOpen
XDEF LibClose
XDEF LibExpunge
XDEF LibExtFunc
XDEF LibBeginIO
XDEF LibAbortIO
XDEF LibEnd
XDEF inittable
XDEF functable
XDEF datatable
XDEF defaultprefs
XDEF basetable
XDEF baudtable
XDEF cmdtable
XDEF mdu_Init
XDEF LibEnd
XDEF LibStart
XDEF mydebug
XDEF InitTask
XDEF InitDACIA
XDEF SetUpUnit
XDEF InitUnit
XDEF LibBeginIO
XDEF BeginIO_QueueMsg
XDEF BeginIO_Immediate
XDEF PerformIO
XDEF BeginIO_End
XDEF BeginIO_NoCmd
XDEF queuewrt
XDEF notread
XDEF ReadTask_Begin
XDEF ReadSigLoop
XDEF ReadTask_MainLoop
XDEF ReadTask_StartHere
XDEF ReadTask_NextMessage
XDEF Readignorecmd
XDEF Readconttl
SECTION CODE
; Supposed start of executable code. This is where execution
; will start if anybody's silly enough to try and run the library
; from the command line. Just return the highest error code we
; conveniently can to tell them they screwed up.
LibStart:
moveq #$7F,d0
rts
; Where the magic begins. OpenLibrary actually looks through the
; library file contents trying to find this table by locating the
; magic RTC_MATCHWORD value followed by its own address. This
; table then tells OpenLibrary where to find other things it needs.
; This needs to be close to the front of your library file to cut
; down on the amount of searching OpenLibrary does; that's why
; this object file should be first in the link list.
RomTag: dc.w RTC_MATCHWORD ; Magic value to search for to find table
dc.l RomTag ; Address of matchword; the two together prevent accidental matches
dc.l LibEnd ; Address of end of library handler code
dc.b RTF_AUTOINIT ; Request system to automatically initialize our library
dc.b VERSION ; Version number of our library (defined below)
dc.b NT_DEVICE ; Node type = Device
dc.b 0 ; Node priority = 0 (normal)
dc.l LibName ; Name of this library file, for debugging info
dc.l LibIDString ; More debugging info
dc.l inittable ; Initialization table used by RTF_AUTOINIT
LibName:
dc.b "newser.device",0
Task1:
dc.b "NewserRead",0
Task2:
dc.b "NewserWrite",0
LibIDString:
dc.b "NewSer 1.1S (21 Mar 1991)",13,10,0
timername
dc.b 'timer.device',0
ds.w 0
inittable:
dc.l md_SIZE ; Size of our library base struct
dc.l functable ; Where our function addresses are
dc.l datatable ; Initialization info for our library base struct
dc.l LibInit ; Library initialization routine address
functable:
dc.l LibOpen ; Addresses of all library functions
dc.l LibClose ; First 4 MUST be provided, in this order
dc.l LibExpunge
dc.l LibExtFunc
dc.l LibBeginIO ; The meat of the library.
dc.l LibAbortIO
dc.l -1
; Things for the system to automatically initialize for us via RTF_AUTOINIT request
datatable:
INITBYTE LN_TYPE,NT_DEVICE
INITLONG LN_NAME,LibName
INITBYTE LIB_FLAGS,LIBF_SUMUSED | LIBF_CHANGED
INITWORD LIB_VERSION,VERSION
INITWORD LIB_REVISION,REVISION
INITLONG LIB_IDSTRING,LibIDString
dc.l 0
;This routine gets called after the device has been allocated. The device
;pointer is in D0. The AmigaDOS segment list is in a0. If it returns the
;device pointer, then the device will be linked into the device list. If it
;returns NULL, then the device will be unloaded.
;
;This call is single-threaded by exec; please read the description for
;"Open" below.
;
; Register Usage
; ==============
; a3 -- Points to temporary RAM
; a4 -- Expansion library base
; a5 -- device pointer
; a6 -- Exec base
LibInit:
PUTDEBUG 100,<'Init: called'>
movem.l d0-d7/a0-a6,-(sp) ;Preserve ALL modified registers
movea.l d0,a5 ;Device ptr
move.l a6,md_SysLib(a5) ;Save a pointer to exec
move.l a0,md_SegList(a5) ;Save pointer to our loaded code
;Now fill in the prefs in MyDev with reasonable defaults. Jeff has suggested
;starting a process and reading them from a file.
movea.l a5,a6
bsr.b SetDefaultPrefs
move.l a5,d0 ;a5 set to 0 above if an error occured
PUTDEBUG 100,<'Init: finished'>
movem.l (sp)+,d0-d7/a0-a6
rts
;Enter with device ptr in a6.
SetDefaultPrefs
movem.l d0/d1/a0/a1,-(sp)
lea prefs_unit0(a6),a1
moveq #MD_NUMUNITS-1,d1
1$
move.w #prefs_SIZE-1,d0
lea defaultprefs(pc),a0
2$
move.b (a0)+,(a1)+
dbra d0,2$
dbra d1,1$
movem.l (sp)+,d0/d1/a0/a1
rts
;Here begins the system interface commands. When the user calls OpenDevice/
;CloseDevice/RemDevice, this eventually gets translated into a call to the
;following routines (Open/Close/Expunge). Exec has already put our device
;pointer in a6 for us.
;
;Open sets the IO_ERROR field on an error. If it was successful, we should
;also set up the IO_UNIT and LN_TYPE fields. exec takes care of setting up
;IO_DEVICE.
;
;NOTE: We must also copy the current prefs for this unit into the user's
;extended iorequest fields.
;
; Register Usage
; ==============
; d0 -- unitnum
; d1 -- flags
; a1 -- iob
; a6 -- Device ptr
LibOpen:
addq.w #1,LIB_OPENCNT(a6) ;Fake an opener for duration of call
PUTDEBUG 100,<'Open: called'>
movem.l d2/a2-a4,-(sp)
movea.l a1,a2 ;Save the iob
cmp.l #MD_NUMUNITS,d0 ;See if the unit number is in range
bcc.w Open_Range_Error ;Unit number out of range (BHS)
move.l d0,d2 ;Save unit number
lsl.l #2,d0 ;See if the unit is already initialized
lea md_Units(a6,d0.l),a4
move.l (a4),d0
bne.b Open_UnitOK
;Try to conjure up a unit
bsr.w InitUnit ;scratch:a3 unitnum:d2 devpoint:a6
move.l (a4),d0 ;See if it initialized OK
beq.w Open_Error
movea.l d0,a3
bra.b Open_UnitOK1
Open_UnitOK
movea.l d0,a3 ;Unit pointer in a3
tst.b Exclusive(a3) ;Check for an exclusive access violation
beq.w Open_Error
Open_UnitOK1
btst #SERB_SHARED,IO_FLAGS(a2)
seq Exclusive(a3)
movea.l mdu_prefs(a3),a4 ;Set the "7WIRE" flag
bclr #SERB_7WIRE,prefs_SERFLAGS(a4)
btst #SERB_7WIRE,IO_SERFLAGS(a2)
beq.b 2$
bset #SERB_7WIRE,prefs_SERFLAGS(a4)
2$
movea.l a2,a1 ;Copy the current internal prefs into the iorequest.
bsr CopyPrefs
move.l d0,IO_UNIT(a2)
addq.w #1,LIB_OPENCNT(a6) ;Mark us as having another opener
addq.w #1,UNIT_OPENCNT(a3) ;Internal bookkeeping
bclr #LIBB_DELEXP,md_Flags(a6) ;Prevent delayed expunges
moveq #0,d0
move.b d0,IO_ERROR(a2)
move.b #NT_REPLYMSG,LN_TYPE(a2) ;Mark IORequest as "complete"
Open_End
subq.w #1,LIB_OPENCNT(a6) ;End of expunge protection
movem.l (sp)+,d2/a2-a4
PUTDEBUG 100,<'Open: Finished!'>
rts
Open_Range_Error:
Open_Error
moveq #IOERR_OPENFAIL,d0
move.b d0,IO_ERROR(a2)
move.l d0,IO_DEVICE(a2) ;Trash IO_DEVICE on open failure
PUTDEBUG 100,<'Open: failed'>
bra.b Open_End
;Copy prefs from our device struct to an IOrequest. Enter with IORequest
;in a1 and unit pointer in a3. All registers are preserved.
CopyPrefs
movem.l d0/a1/a4,-(sp)
movea.l mdu_prefs(a3),a4
lea IO_CTLCHAR(a1),a1
move.w #prefs_SIZE-1,d0
1$
move.b (a4)+,(a1)+
dbra d0,1$
movem.l (sp)+,d0/a1/a4
rts
;Copy prefs from an IOrequest to our device struct Enter with IORequest
;in a1 and unit pointer in a3. All registers are preserved.
SetPrefs
movem.l d0/a1/a4,-(sp)
movea.l mdu_prefs(a3),a4
lea IO_CTLCHAR(a1),a1
move.w #prefs_SIZE-1,d0
1$
move.b (a1)+,(a4)+
dbra d0,1$
movem.l (sp)+,d0/a1/a4
rts
;There are two different things that might be returned from the Close
;routine. If the device wishes to be unloaded, then Close must return
;the segment list (as given to Init). Otherwise close MUST return NULL.
; ( device:a6, iob:a1 )
LibClose:
movem.l a2-a3,-(sp)
PUTDEBUG 100,<'Close: called'>
movea.l a1,a2
movea.l IO_UNIT(a2),a3
moveq #-1,d0
move.l d0,IO_UNIT(a2) ;We're closed...
move.l d0,IO_DEVICE(a2) ;Customers not welcome at this IORequest!
subq.w #1,UNIT_OPENCNT(a3) ;See if the unit is still in use
bne.b Close_Device
bsr ExpungeUnit
Close_Device
moveq #0,d0
subq.w #1,LIB_OPENCNT(a6) ;Mark us as having one fewer openers
bne.b Close_End ;See if there is anyone left with us open
btst #LIBB_DELEXP,md_Flags(a6) ;See if we have a delayed expunge pending
beq.b Close_End
bsr LibExpunge ;Do the expunge
Close_End
movem.l (sp)+,a2-a3
PUTDEBUG 100,<'Close: Finished!'>
rts ;MUST return either zero or the SegList!
;Expunge is called by the memory allocator when the system is low on
;memory.
;
;There are two different things that might be returned from the Expunge
;routine. If the device is no longer open then Expunge may return the
;segment list (as given to Init). Otherwise Expunge may set the
;delayed expunge flag and return NULL.
;
;One other important note: because Expunge is called from the memory
;allocator, it may NEVER Wait() or otherwise take long time to complete.
;
; A6 - library base (scratch)
; D0-D1/A0-A1 - scratch
LibExpunge:
PUTDEBUG 100,<'Expunge: called'>
movem.l d1/d2/a5/a6,-(sp) ;Save ALL modified registers
movea.l a6,a5
movea.l md_SysLib(a5),a6
tst.w LIB_OPENCNT(a5) ;See if anyone has us open
beq.b 1$
bset #LIBB_DELEXP,md_Flags(a5) ;Set the delayed expunge flag
moveq #0,d0
bra.b Expunge_End
1$
move.l md_SegList(a5),d2 ;Store our seglist in d2
movea.l a5,a1 ;Unlink from device list
mySYS Remove ;Remove first (before FreeMem)
movea.l a5,a1 ;Devicebase
moveq #0,d0
move.w LIB_NEGSIZE(a5),d0
suba.l d0,a1 ;Calculate base of functions
add.w LIB_POSSIZE(a5),d0 ;Calculate size of functions + data area
mySYS FreeMem
move.l d2,d0 ;Set up our return value
Expunge_End
movem.l (sp)+,d1/d2/a5/a6
rts
LibExtFunc:
PUTDEBUG 100,<'Null: called'>
moveq #0,d0
rts ;The "Null" function MUST return NULL.
;This is the main unit initialization routine. It allocates memory for
;the device structure, calls SetUpUnit, then calls InitTask.
; ( d2:unit number, a3:scratch, a6:devptr )
InitUnit
PUTDEBUG 100,<'InitUnit: called'>
movem.l d0/d1/a4/a2,-(sp)
move.l d2,d1
mulu.w #prefs_SIZE,d1
lea prefs_unit0(a6),a4
adda.l d1,a4
;Now A4 is a ptr to the prefs for this unit
move.l #mdu_SIZE,d0 ;Allocate unit memory
move.l #MEMF_PUBLIC!MEMF_CLEAR,d1
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS AllocMem
movea.l (sp)+,a6
movea.l d0,a3
tst.l d0
bne continitunit1
;Couldn't init the unit (out of memory).
ReturnVoid
move.l d2,d0 ;Unit number
lsl.l #2,d0
clr.l md_Units(a6,d0.l) ;Set unit table
movem.l (sp)+,d0/d1/a4/a2
rts
BadSetup
bsr KillTask
bra.b ReturnVoid
continitunit1
moveq #0,d0 ;Don't need to re-zero it
movea.l a3,a2
lea mdu_Init(pc),a1
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS InitStruct
movea.l (sp)+,a6
move.l a5,-(sp) ;Look up DACIA base address
lea basetable(pc),a0
moveq #0,d0
move.b d2,d0
lsl.l #2,d0
movea.l 0(a0,d0.w),a5
move.l a5,daciabase(a3)
move.b #$7f,IER(a5) ;Disable DACIA interrupts
bsr SetUpUnit
movea.l (sp)+,a5
tst.l d0
bne.b BadSetup
bsr InitTask ;Set up the two tasks for this unit
tst.l d0
beq.b BadSetup
;Done with InitUnit - fill in the proper md_Unit field in the device struct
;and return.
move.l d2,d0 ;Unit number
lsl.l #2,d0
move.l a3,md_Units(a6,d0.l) ;Set unit table
movem.l (sp)+,d0/d1/a4/a2
PUTDEBUG 100,<'InitUnit: finished! End of silence.'>
rts
;This routine does the following:
;
;1. Allocate read buffer memory
;2. Initialize all the special MyDevUnit fields (but NOT the signals -
; they must be allocated in the task's context)
;3. Call InitDACIA [Initialize/set-up DACIA registers (for this unit).]
;4. Install an interrupt server.
;
;Returns failure code in d0 - zero if OK, -1 if out of memory, 1 if
;unable to open timer.device, 2 if invalid parm
;
;SetUpUnit(d2:unit number, a3:unit, a6:devptr)
;AND...ptr to prefs in A4
;uses d1,a0,a1,a2,a6
SetUpUnit
PUTDEBUG 100,<'SetUpUnit: called'>
movem.l d1/a0/a1/a2,-(sp)
move.l prefs_RBUFLEN(a4),d0
moveq #64,d1
cmp.l d1,d0
bhi.b SUU
move.l d1,d0
move.l d0,prefs_RBUFLEN(a4)
SUU
move.l #MEMF_PUBLIC,d1
myEXEC AllocMem
tst.l d0
bne.b continitunit
moveq #-1,d0 ;Failed to allocate input buffer memory - exit.
movem.l (sp)+,d1/a0/a1/a2
rts
;Initialize all the special MyDevUnit fields (but NOT the signals -
;they must be allocated in the task's context)
continitunit
bsr BufPtrSetup ;Fill in head, tail, startbuf, and endbuf
move.l a4,mdu_prefs(a3)
move.l (SysBase).w,mdu_SysLib(a3)
lea timerport+MP_MSGLIST(a3),a0
NEWLIST a0 ;Init the unit's timer MsgPort's list
move.b d2,mdu_UnitNum(a3) ;Initialize unit number
move.l a6,mdu_Device(a3) ;Initialize device pointer
move.l a3,mdu_is+IS_DATA(a3) ;Pass unit addr to interrupt server
move.b #$FF,xstate(a3)
move.b #$80,frstate(a3)
lea timername(pc),a0 ;Open the timer device.
moveq #0,d0 ;UNIT_MICROHZ
lea timeriorequest(a3),a1
moveq #0,d1 ;no special flags
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS OpenDevice
movea.l (sp)+,a6
tst.l d0
bne OpenTimerFailed
;Initialize/set-up DACIA registers (for this unit).
;(according to the prefs pointed to by a4)
bsr InitDACIA
tst.l d0
bne InitDACIAfailed
;Install an interrupt server.
lea mdu_is(a3),a1
moveq #INTB_EXTER,d0 ;EXTER (level 6) interrupt (=13)
movea.l md_SysLib(a6),a6
ifne INFO_LEVEL ;If any debugging enabled at all
move.l a1,-(sp)
move.l a3,-(sp)
PUTDEBUG 200,<'At AddIntServer, unit= %lx and mdu_is= %lx'>
addq.l #8,sp
endc
jsr _LVOAddIntServer(a6)
movea.l mdu_Device(a3),a6
moveq #0,d0 ;situation under control
movem.l (sp)+,d1/a0/a1/a2
PUTDEBUG 100,<'SetUpUnit: Finished!'>
rts
InitDACIAfailed
moveq #2,d0
bra.b EndSetUpUnit
OpenTimerFailed
moveq #1,d0
EndSetUpUnit
move.l d0,-(sp)
move.l prefs_RBUFLEN(a4),d0
movea.l startbuf(a3),a1
myEXEC FreeMem
move.l (sp)+,d0
movem.l (sp)+,d1/a0/a1/a2
rts
;*** end of SetUpUnit ***
;Initialize/set-up DACIA registers (for this unit).
;(according to the prefs pointed to by a4)
;d0.b will mirror CTR, d1.b will mirror FMR
;Returns error code in d0 - zero if successful.
;uses d1,d3,d5,a0
InitDACIA
PUTDEBUG 100,<'InitDACIA: Called.'>
movem.l d1/d3/d5/a0,-(sp)
;"During power-on initialization, all readable registers should be read to
; assure that the status registers are initialized."
move.b ISR(a5),d0
move.b CSR(a5),d0
move.b RDR(a5),d0
move.b frstate(a3),d0 ;Set DTR* and RTS* low (assert)
and.b #$FC,d0 ;Clear bits zero and one
move.b d0,frstate(a3)
move.b d0,FMR(a5)
moveq #0,d0
bset #6,d0 ;Always access ACR, never CDR
move.b frstate(a3),d1
move.l prefs_BAUD(a4),d3 ;First, set proper baud rate.
swap d3
tst.w d3
bne InvParm
swap d3
lea baudtable(pc),a0
moveq #15,d5
baudloop
cmp.w (a0)+,d3
dbeq d5,baudloop
bne InvParm
not.b d5
and.b #$0F,d5
or.b d5,d0
ifne INFO_LEVEL
clr.l -(sp)
move.b d0,3(sp)
PUTDEBUG 200,<'InitDACIA: Baud rate = %lx'>
addq.l #4,sp
endc
cmpi.b #1,prefs_STOPBITS(a4) ;Next, set number of stop bits per character
beq.b onestop
bset #5,d0
onestop
move.b d0,CTR(a5)
move.b prefs_READLEN(a4),d3 ;Set number of data bits per char
cmp.b prefs_WRITELEN(a4),d3
bne InvParm ;We don't support different read & write char lengths
subq.b #5,d3
bmi InvParm
cmp.b #3,d3
bhi InvParm
lsl.b #5,d3
or.b d3,d1
btst #SEXTB_MSPON,prefs_EXTFLAGS+3(a4) ;Set parity
beq.b EvenOdd
bset #2,d1
btst #SEXTB_MARK,prefs_EXTFLAGS+3(a4)
bne.b UseMark
or.b #24,d1 ;Use space
bra.b NoParity
UseMark
or.b #16,d1
bra.b NoParity
EvenOdd
btst #SERB_PARTY_ON,prefs_SERFLAGS(a4)
beq.b NoParity
bset #2,d1
btst #SERB_PARTY_ODD,prefs_SERFLAGS(a4)
bne.b UseOdd
or.b #8,d1
UseOdd:
NoParity
move.b d1,frstate(a3)
move.b d1,FMR(a5)
moveq #0,d0
movem.l (sp)+,d1/d3/d5/a0
PUTDEBUG 100,<'InitDACIA: Finished!'>
rts
InvParm
PUTDEBUG 100,<'InitDACIA: Invalid Parm!'>
moveq #-1,d0
movem.l (sp)+,d1/d3/d5/a0
rts
;*** end of InitDACIA ***
;(this is a subroutine for InitUnit)
;This routine sets up the two tasks (one for reading, one for writing).
;Returns zero in d0.l if an error occured else a ptr to the unit.
;
;uses d1-d4 and a0-a5
InitTask
PUTDEBUG 100,<'InitTask: called.'>
movem.l d1-d4/a0-a5,-(sp)
lea ReadTask_Begin(pc),a5 ;Set up the read task
lea mdu_rstack(a3),a0
lea mdu_rtcb(a3),a1
movea.l a3,a2 ;Read message port
bsr.b InitTaskStruct
lea WriteTask_Begin(pc),a5 ;Set up the write task
lea mdu_wstack(a3),a0
lea mdu_wtcb(a3),a1
lea mdu_wport(a3),a2
move.l a3,TC_EXCEPTDATA(a1)
lea breakexception(pc),a4
move.l a4,TC_EXCEPTCODE(a1)
bsr.b InitTaskStruct
move.l a3,d0 ;Mark us as ready to go
PUTDEBUG 100,<'InitTask: ok'>
;Return zero in d0.l if an error occured, else a ptr to the unit
movem.l (sp)+,d1-d4/a0-a5
rts
;Start up the unit task. We do a trick here --we set his message port to
;PA_IGNORE until the new task has a change to set it up. We cannot go to
;sleep here: it would be very nasty if someone else tried to open the unit
;(exec's OpenDevice has done a Forbid() for us --we depend on this to become
;single threaded).
;
;Enter with:
;a0 - ptr to low end of stack
;a1 - ptr to tcb (task control block)
;a3 - unit pointer
;a5 - starting address of task
;a2 - ptr to a (uninitialized) message port
;
;uses a0-a3 and d0,d1
InitTaskStruct
movem.l d0/d1/a0-a3,-(sp)
move.l a0,TC_SPLOWER(a1) ;Initialize the stack information
lea MYPROCSTACKSIZE(a0),a0 ;High end of stack
move.l a0,TC_SPUPPER(a1)
move.l a3,-(a0) ;Argument - unit ptr (send on stack)
move.l a0,TC_SPREG(a1)
move.l a1,MP_SIGTASK(a2)
ifge INFO_LEVEL-30
move.l a1,-(sp)
move.l a3,-(sp)
PUTDEBUG 100,<'InitUnit, unit= %lx, task=%lx'>
addq.l #8,sp
endc
lea MP_MSGLIST(a2),a0
NEWLIST a0 ;Init the unit's MsgPort's list
movea.l a5,a2 ;Startup the task
lea (-1).l,a3 ;generate address error
;if task ever "returns" (we RemTask() it
;to get rid of it...)
moveq #0,d0
PUTDEBUG 100,<'About to add task'>
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS AddTask
movea.l (sp)+,a6
movem.l (sp)+,d0/d1/a0-a3
rts
;Get rid of the unit's tasks. We know this is safe because the unit has an
;open count of zero, so it is 'guaranteed' not in use.
;
;Kill the two tasks
; ( a3:unitptr, a6:deviceptr )
;
;uses a0,a1 and d0-d2
KillTask
movem.l a0/a1/d0-d2,-(sp)
lea mdu_rtcb(a3),a1
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS RemTask
movea.l (sp)+,a6
lea mdu_wtcb(a3),a1
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS RemTask
movea.l (sp)+,a6
moveq #0,d2
move.b mdu_UnitNum(a3),d2 ;Save the unit number
bsr FreeUnit ;Free the unit structure
lsl.l #2,d2
clr.l md_Units(a6,d2.l) ;Clear out the unit vector in the device
movem.l (sp)+,a0/a1/d0-d2
rts
; ( a3:unitptr, a6:deviceptr )
;uses a0,a1,d0,d1
FreeUnit
movem.l a0/a1/d0/d1,-(sp)
movea.l a3,a1
move.l #mdu_SIZE,d0
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS FreeMem
movea.l (sp)+,a6
movem.l (sp)+,a0/a1/d0/d1
rts
; ( a3:unitptr, a6:deviceptr )
;
;uses a0,a1,a5,d0-d2
ExpungeUnit
movem.l a0/a1/a5/d0-d2,-(sp)
PUTDEBUG 100,<'ExpungeUnit: called'>
movea.l daciabase(a3),a5
move.b #$7f,IER(a5) ;shut off all interrupts
move.b #$83,FMR(a5) ;Deassert DTR* and RTS*
bsr FreeResources
lea mdu_rtcb(a3),a1
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS RemTask
movea.l (sp)+,a6
lea mdu_wtcb(a3),a1
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS RemTask
movea.l (sp)+,a6
moveq #0,d2
move.b mdu_UnitNum(a3),d2 ;Save the unit number
bsr FreeUnit ;Free the unit structure.
lsl.l #2,d2
clr.l md_Units(a6,d2.l) ;Clear out the unit pointer in the device
movem.l (sp)+,a0/a1/a5/d0-d2
rts
;This routines frees up resources specific to this driver (the other
;resources are taken care of by the skeleton).
;Be careful with this routine, so as not to pull the rug out from
;underneath the driver...
;
;If you can expunge you unit, and each unit has it's own interrupts,
;you must remember to remove its interrupt server
;
;Enter with unit ptr in a3 and device ptr in a6
;
;uses a0,a1,a4,d0,d1
FreeResources
PUTDEBUG 100,<'FreeResources: called'>
movem.l a0/a1/a4/d0/d1,-(sp)
lea mdu_is(a3),a1 ;Point to interrupt structure
moveq #INTB_EXTER,d0 ;Portia interrupt bit 13
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS RemIntServer ;Now remove the interrupt server
movea.l (sp)+,a6
movea.l startbuf(a3),a1 ;Free input buffer memory
movea.l mdu_prefs(a3),a4
move.l prefs_RBUFLEN(a4),d0
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS FreeMem
movea.l (sp)+,a6
lea timeriorequest(a3),a1 ;Close timer device
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS CloseDevice
movea.l (sp)+,a6
movem.l (sp)+,a0/a1/a4/d0/d1
PUTDEBUG 100,<'FreeResources: finished!'>
rts
;BeginIO starts all incoming io. The IO is either queued up for the
;unit task or processed immediately.
;
;BeginIO often is given the responsibility of making devices single
;threaded... so two tasks sending commands at the same time don't cause
;a problem. Once this has been done, the command is dispatched via
;PerformIO.
;
;There are many ways to do the threading. This example uses the
;UNITB_ACTIVE bit. Be sure this is good enough for your device before
;using! Any method is ok. If immediate access can not be obtained, the
;request is queued for later processing.
;
;Some IO requests do not need single threading. These can be performed
;immediatley.
;
;IMPORTANT:
; The exec WaitIO() function uses the IORequest node type (LN_TYPE)
; as a flag. If set to NT_MESSAGE, it assumes the request is
; still pending and will wait. If set to NT_REPLYMSG, it assumes the
; request is finished. It's the responsibility of the device driver
; to set the node type to NT_MESSAGE before returning to the user.
;
;Notes: This routine will look at io_command to determine which task
;to use. Break with QUEUEDBRK and Write requests go to the write
;task. Read requests (only) go to the read task.
;Break requests without QUEUEDBRK initiate a write task exception.
;The actual break job is started, and finished, within that exception.
;At the end of the exception, the write task resumes whatever it was
;doing before the break.
;All other requests are performed immediately, i.e. within the caller's
;context.
;
;This routine uses a3 (and a0/a1/d0/d1, but those do not need to be
;preserved).
; ( iob: a1, device:a6 )
LibBeginIO
ifge INFO_LEVEL-1
bchg.b #1,($bfe001).l ;Blink the power LED
endc
ifge INFO_LEVEL-3
clr.l -(sp)
move.w IO_COMMAND(a1),2(sp) ;Get entire word
PUTDEBUG 100,<'BeginIO -- %ld'>
addq.l #4,sp
endc
move.l a3,-(sp)
andi.b #$f,IO_FLAGS(a1)
move.b #NT_MESSAGE,LN_TYPE(a1) ;So WaitIO() is guaranteed to work
movea.l IO_UNIT(a1),a3 ;Bookkeeping -> what unit to play with
move.w IO_COMMAND(a1),d0
;Do a range check & make sure bad requests are rejected
cmp.w #MYDEV_END,d0 ;Compare all 16 bits
bhi BeginIO_NoCmd ;no, reject it. (bcc=bhs - unsigned)
;Process all immediate commands no matter what
move.l #IMMEDIATES,d1
myDisable a0 ;<-- Ick, nasty stuff, but needed here.
btst d0,d1
bne BeginIO_Immediate
;See if the unit is STOPPED. If so, queue the msg.
btst #MDUB_STOPPED,MDU_FLAGS(a3)
bne BeginIO_QueueMsg
;This is not an immediate command. See if the device is busy. If the device
;is not, do the command on the user schedule. Else fire up the task. This
;type of arbitration is essential for a device to reliably work with shared
;hardware.
;
;REMEMBER: Never Wait() on the user's schedule in BeginIO()! The only
;exception is when the user has indicated it is ok by setting the "quick" bit.
;
;We need to queue the device. mark us as needing task attention. Clear the
;quick flag
;
;Note: We handle read, write, and break requests seperately and differently.
BeginIO_QueueMsg
bclr #IOB_QUICK,IO_FLAGS(a1) ;We did NOT complete this quickly
cmpi.w #CMD_READ,IO_COMMAND(a1)
bne.b notread
bset #UNITB_INREADTASK,UNIT_FLAGS(a3)
myEnable a0
ifge INFO_LEVEL-250
move.l a1,-(sp)
move.l a3,-(sp)
PUTDEBUG 100,<'PutMsg: Port=%lx Message=%lx'>
addq.l #8,sp
endc
movea.l a3,a0
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS PutMsg ;Port=a0, Message=a1
movea.l (sp)+,a6
bra BeginIO_End ;Return to caller before completing
notread
cmpi.w #CMD_WRITE,IO_COMMAND(a1)
bne.b handlebreak
queuewrt
bset #UNITB_INWRITETASK,UNIT_FLAGS(a3)
myEnable a0
lea mdu_wport(a3),a0
ifge INFO_LEVEL-250
move.l a1,-(sp)
move.l a0,-(sp)
PUTDEBUG 100,<'PutMsg: Port=%lx Message=%lx'>
addq.l #8,sp
endc
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS PutMsg ;Port=a0, Message=a1
movea.l (sp)+,a6
bra BeginIO_End ;Return to caller before completing
handlebreak
btst #SERB_QUEUEDBRK,IO_SERFLAGS(a1)
bne.b queuewrt ;Handle just like a write request
;This is the tricky case. We initiate the break immediately. To do this we
;cleverly (?) use the under-documented power of task exceptions.
btst #UNITB_INBREAK,UNIT_FLAGS(a3)
bne breakout
bset #UNITB_BREAKACTIVE,UNIT_FLAGS(a3)
myForbid
bset #ioflagsB_Active,IO_FLAGS(a1)
move.l a1,breakiorequest(a3)
myPermit
move.l breaksig(a3),d0
lea mdu_wtcb(a3),a1
myEXEC Signal
myEnable a0
bra.b BeginIO_End
;Do it on the schedule of the calling process
BeginIO_Immediate
myEnable a0
bsr PerformIO
BeginIO_End
PUTDEBUG 100,<'BeginIO_End'>
movea.l (sp)+,a3
rts
BeginIO_NoCmd
PUTDEBUG 100,<'BeginIO_NoCmd!'>
move.b #IOERR_NOCMD,IO_ERROR(a1)
bra.b BeginIO_End
breakout
myEnable a0
bsr TermIO
bra BeginIO_End
;PerformIO actually dispatches an io request. It might be called from the
;task, or directly from BeginIO (thus on the callers's schedule)
;
;It expects a3 to already have the unit pointer in it. a6 has the device
;pointer (as always). a1 has the io request. Bounds checking has already
;been done on the I/O Request.
;
;This routine itself uses d0,a0,a4,a5. The command it calls may choose
;to use any or all registers.
; ( iob:a1, unitptr:a3, devptr:a6 )
PerformIO
ifge INFO_LEVEL-150
clr.l -(sp)
move.w IO_COMMAND(a1),2(sp) ;Get entire word
PUTDEBUG 100,<'PerformIO -- %ld'>
addq.l #4,sp
endc
movem.l d0/a0/a4/a5,-(sp)
movea.l mdu_prefs(a3),a4
movea.l daciabase(a3),a5
moveq #0,d0
move.b d0,IO_ERROR(a1) ; No error so far
move.b IO_COMMAND+1(a1),d0 ;Look only at low byte
lsl.w #2,d0 ; Multiply by 4 to get table offset
lea cmdtable(pc),a0
movea.l 0(a0,d0.w),a0
move.l a0,d0
cmpi.l #$ffffffff,d0
bne ImmCont
PUTDEBUG 200,<'PerformIO_NoCmd!'>
move.b #IOERR_NOCMD,IO_ERROR(a1)
movem.l (sp)+,d0/a0/a4/a5
rts
ImmCont
movem.l d0-d7/a0-a6,-(sp)
jsr (a0) ;iob:a1 unit:a3 devprt:a6
movem.l (sp)+,d0-d7/a0-a6
bsr.b TermIO
movem.l (sp)+,d0/a0/a4/a5
PUTDEBUG 100,<'PerformIO -- Finished!'>
rts
;PerformIO
; ifge INFO_LEVEL-150
; clr.l -(sp)
; move.w IO_COMMAND(a1),2(sp) ;Get entire word
; PUTDEBUG 150,<'PerformIO -- %ld'>
; addq.l #4,sp
; endc
; movem.l d0/a0/a4/a5,-(sp)
; movea.l mdu_prefs(a3),a4
; movea.l daciabase(a3),a5
; moveq #0,d0
; move.b d0,IO_ERROR(a1) ; No error so far
; move.b IO_COMMAND+1(a1),d0 ;Look only at low byte
; lsl.w #2,d0 ; Multiply by 4 to get table offset
; lea cmdtable(pc),a0
; movea.l 0(a0,d0.w),a0
; movem.l d0-d7/a0-a6,-(sp)
; jsr (a0) ;iob:a1 unit:a3 devprt:a6
; movem.l (sp)+,d0-d7/a0-a6
; bsr.b TermIO
; movem.l (sp)+,d0/a0/a4/a5
; PUTDEBUG 15,<'PerformIO -- Finished!'>
; rts
;TermIO sends the IO request back to the user. It knows not to mark
;the device as inactive if this was an immediate request or if the
;request was started from the server task.
;
; ( iob:a1, unitptr:a3 )
;uses d0/d1/a0/a1
TermIO
movem.l d0/d1/a0/a1,-(sp)
PUTDEBUG 100,<'TermIO'>
move.w IO_COMMAND(a1),d0
move.l #IMMEDIATES,d1
btst d0,d1
bne.b TermIO_Immediate ;IO was immediate, don't do task stuff...
cmpi.w #CMD_READ,IO_COMMAND(a1)
bne.b wrtterm
;We may need to turn the active bit off.
btst #UNITB_INREADTASK,UNIT_FLAGS(a3)
bne.b TermIO_Immediate ;IO came from task, don't clear ACTIVE...
;The task does not have more work to do
bclr #UNITB_READACTIVE,UNIT_FLAGS(a3)
bra.b TermIO_Immediate
;We may need to turn the active bit off.
wrtterm
btst #UNITB_INWRITETASK,UNIT_FLAGS(a3)
bne.b TermIO_Immediate ;IO came from task, don't clear ACTIVE...
;The task does not have more work to do
bclr #UNITB_WRITEACTIVE,UNIT_FLAGS(a3)
;If the quick bit is still set then we don't need to reply
;- msg - just return to the user.
TermIO_Immediate
btst #IOB_QUICK,IO_FLAGS(a1)
bne.b TermIO_End
myEXEC ReplyMsg ;a1-message
;ReplyMsg sets the LN_TYPE to NT_REPLYMSG
TermIO_End
movem.l (sp)+,d0/d1/a0/a1
rts
;Here begins the functions that implement the device commands
;all functions are called with:
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
;
;Commands that conflict with 68000 instructions have a "My" prepended to them.
;Read: The handshaking lines don't serve any purpose when reading (except
;perhaps to say "shut up" if an exceptional condition occurs).
;
;d5 is used to count the number of bytes transferred
Read
moveq #0,d5
move.l IO_LENGTH(a1),d6
movea.l IO_DATA(a1),a2
move.l readsig(a3),d4
add.l readabortsig(a3),d4
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d6,-(sp)
PUTDEBUG 100,<'Read entered -- %ld bytes requested.'>
addq.l #4,sp
endc
bra.b ReadEntry
readloop
move.l d4,d0
myEXEC Wait ;Wait for one or more bytes to come in
and.l readabortsig(a3),d0
bne readabort
NoErrorAtAll
PUTDEBUG 100,<'Read: One or more bytes came in.'>
ReadEntry
bsr GetBytesInReadBuf
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
PUTDEBUG 100,<'Read: In fact, %ld bytes came in.'>
addq.l #4,sp
endc
tst.l d0
beq readloop
cmp.l d0,d6
bls.b AllDone ;Branch if d6 <= d0
sub.l d0,d6
bsr DumpReadBuf ;Get all the bytes we can
beq readloop
bra.b endread ;Go if TermArray caused early exit
;The number of bytes in the read buffer equals or exceeds the number of
;bytes the user wants.
AllDone
PUTDEBUG 100,<'Read: AllDone'>
move.l d6,d0
bsr DumpReadBuf ;Dump d0 bytes of the read buffer into the user's buffer
endread
PUTDEBUG 100,<'Read: Finished!'>
bclr #MDUB_V,MDU_FLAGS(a3)
beq.b NoOverflow
move.b #SerErr_BufOverflow,IO_ERROR(a1)
NoOverflow
move.l d5,IO_ACTUAL(a1)
rts ;Done with read
;Something exceptional happened. An informative error code should be
;returned. first, check for error conditions... Note that the serial.device
;standard does not provide a way to inform the caller of simultaneous error
;conditions.
readabort
PUTDEBUG 100,<'readabort: Something exceptional happened.'>
myDisable
move.b ISRcopy(a3),d2
move.b CSRcopy(a3),d3
myEnable
btst #ISRB_PAR,d2
beq.b notpar
move.b #SerErr_ParityErr,IO_ERROR(a1) ;parity error
bra endread
notpar
btst #1,d2 ;Check for frame err, overrun, & break
beq endread ;We infer that an abort has been issued
btst #CSRB_FE,d3 ;Probe further...
beq.b noframe
move.b #SerErr_LineErr,IO_ERROR(a1) ;Framing Error
bra endread
noframe
btst #CSRB_RBRK,d3
beq NoErrorAtAll
;Note that we ignore receive overrun errors. Since there is no error
;code for it, I assume this is what serial.device does too.
move.b #SerErr_DetectedBreak,IO_ERROR(a1) ;Break
bra endread
;d5 is used to count the number of bytes transferred
Write
moveq #0,d5
move.l IO_LENGTH(a1),d6
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d6,-(sp)
PUTDEBUG 100,<'Write entered -- %ld bytes requested.'>
addq.l #4,sp
endc
movea.l IO_DATA(a1),a2
move.l tdresig(a3),d4
add.l writeabortsig(a3),d4
btst #SERB_7WIRE,prefs_SERFLAGS(a4)
bne.b write.hand
;Safety check: if CTS* is not asserted, return with an error code. This
;usually occurs when the port is not connected to anything.
;Note: This is just a hack - something more robust will be done later
;(hopefully).
;Enter with the number of bytes to transmit in d6
;Enter with a pointer to the user's data in a2
write.nohand
btst #CSRB_CTSL,CSR(a5)
bne.b not_connected
bsr.b transmit
subq.l #1,d6
bne.b write.nohand
endwrite
move.l d5,IO_ACTUAL(a1)
rts
not_connected:
move.b #SerErr_LineErr,IO_ERROR(a1)
bra.b endwrite
;Enter with the number of bytes to transmit in d6
;Enter with a pointer to the user's data in a2
;The handshaking protocol implemented here is based on the flowchart
;on page 5-66 of "An Introduction to Microcomputers" by Adam Osborne.
write.hand:
write.hand.loop
btst #CSRB_DSRL,CSR(a5) ;Test DSR*
beq.b writehandcont ;If low, proceed
move.l dsrsig(a3),d0
add.l writeabortsig(a3),d0
myEXEC Wait
and.l writeabortsig(a3),d0
bne.b aborthand
bra.b write.hand.loop
;The 65C52 halts any transmission until CTS* is asserted, so no code
;is involved. This can also be a curse if you don't desire handshaking.
writehandcont
bsr.b transmit ;Transmit a byte
subq.l #1,d6
bne.b write.hand.loop
aborthand
bra.b endwrite
;*** Subroutine for the write routines ***
;
;Returns 1 in d6 if aborted.
transmit
btst #ISRB_TDRE,ISR(a5) ;test TDRE
bne oktosend ;If set, we can load up the transmit reg immediately
PUTDEBUG 100,<'Transmit: Entered.'>
ori.b #WRITEINTMASK,IERstate(a3)
move.b #WRITEINT,IER(a5)
move.l d4,d0
myEXEC Wait ;Wait for TDRE to be set
and.l writeabortsig(a3),d0
bne aborttransmit
PUTDEBUG 100,<'Transmit: TDRE signal received.'>
bra transmit ;Just to be sure
oktosend
btst #SERB_XDISABLED,prefs_SERFLAGS(a4) ;Handle xoff, if requested
bne.b doit
txs
tst.b xstate(a3)
bne.b doit
PUTDEBUG 100,<'Transmit: Waiting for XON!'>
move.l xonsig(a3),d0
add.l writeabortsig(a3),d0
myEXEC Wait ;Wait for an x-on signal before sending
and.l writeabortsig(a3),d0
bne.b aborttransmit
bra.b txs
doit
PUTDEBUG 100,<'Transmit: Received XON!'>
tst.l d6
bmi.b termzero
doit1
move.b (a2)+,TDR(a5)
addq.l #1,d5
rts
termzero
tst.b (a2)
bne.b doit1
aborttransmit
moveq #1,d6
rts
;*** end of write routines ***
;*** Subroutines for the read routine ***
;
;Return with the number of bytes in the read buffer in d0
GetBytesInReadBuf
move.l d1,-(sp)
myDisable
move.l head(a3),d1
move.l tail(a3),d0
sub.l d1,d0
bcc.b ok.getbytes
add.l prefs_RBUFLEN(a4),d0
ok.getbytes
myEnable
move.l (sp)+,d1
rts
;Enter with number of bytes to read in d0
;Enter with a pointer to a dump buffer in a2
;the pointer in a2 is updated to reflect the current position
;
;This routine uses as scratch: a0,a6,d0,d3,d6,d7
;and updates the following: d5,a2
;Returns with zero flag set if all is OK, or zero cleared if a TermArray
;match was found.
DumpReadBuf
myDisable
movea.l head(a3),a0
move.l a0,-(sp)
adda.l d0,a0
cmpa.l endbuf(a3),a0
bls.b notend.drb
suba.l prefs_RBUFLEN(a4),a0
notend.drb
move.l a0,head(a3)
myEnable
movea.l (sp)+,a0
;Note that only the buffer pointers are protected. The main copy loop
;(below) could, however, return somewhat incoherent data if the buffer
;happens to overflow.
btst #SERB_EOFMODE,IO_SERFLAGS(a1)
bne eofdump
drb.loop
move.b (a0)+,d6
bsr.b xcode
move.b d6,(a2)+
addq.l #1,d5
cmpa.l endbuf(a3),a0
bls.b notend.drb2
movea.l startbuf(a3),a0
notend.drb2
subq.l #1,d0
bne.b drb.loop
rts
;Check for xon/xoff
xcode
ifne INFO_LEVEL
move.l prefs_CTLCHAR(a4),-(sp)
PUTDEBUG 100,<'XCODE: prefs_CTLCHAR = %lx'>
addq.l #4,sp
endc
ifne INFO_LEVEL
clr.l -(sp)
move.b d6,3(sp)
PUTDEBUG 100,<'XCODE: Current char = %lx'>
addq.l #4,sp
endc
cmp.b prefs_CTLCHAR(a4),d6 ;Xon check
beq.b setxon
cmp.b prefs_CTLCHAR+1(a4),d6 ;Xoff check
beq.b setxoff
rts
setxon
PUTDEBUG 100,<'XCODE: XON!'>
tst.b xstate(a3)
bne.b sxoq ;If already on, don't signal!
st xstate(a3)
movem.l d0/d1/a0/a1/a6,-(sp)
move.l xonsig(a3),d0
lea mdu_wtcb(a3),a1
movea.l (SysBase).w,a6
jsr _LVOSignal(a6)
movem.l (sp)+,d0/d1/a0/a1/a6
sxoq
rts
setxoff
PUTDEBUG 100,<'XCODE: XOFF!'>
clr.b xstate(a3)
rts
eofdump:
eofdumploop:
moveq #7,d7
lea prefs_TERMARRAY(a4),a6
move.b (a0)+,d6
bsr xcode
cmploop
cmp.b (a6)+,d6
dbcc d7,cmploop
beq.b termread
move.b d6,(a2)+
addq.l #1,d5
cmpa.l endbuf(a3),a0
bls.b notend.drb1
movea.l startbuf(a3),a0
notend.drb1:
subq.l #1,d0
bne.b eofdumploop
rts
termread
moveq #-1,d0
rts
;This routine handles queued breaks. Non-queued breaks are handled by an
;exception (which uses similar but not identical code).
Break
PUTDEBUG 100,<'Break: called'>
bset #UNITB_INBREAK,UNIT_FLAGS(a3)
move.b #2,ACR(a5) ;start break
lea timeriorequest(a3),a1
move.w #TR_ADDREQUEST,IO_COMMAND(a1)
clr.l TV_SECS(a1)
move.l prefs_BRKTIME(a4),d0
ori.b #$FF,d0 ;To avoid the V33/V34 bug
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
PUTDEBUG 100,<'Break: TV_MICRO=%ld'>
addq.l #4,sp
endc
move.l d0,TV_MICRO(a1)
movea.l MN_REPLYPORT(a1),a0
move.b MP_SIGBIT(a0),d2
myEXEC SendIO
moveq #0,d0
bset d2,d0
add.l writeabortsig(a3),d0
PUTDEBUG 100,<'Break: Waiting...'>
myEXEC Wait
and.l writeabortsig(a3),d0
beq.b breakOK1
PUTDEBUG 100,<'Break: Aborted!'>
myEXEC AbortIO ;The break was aborted. Clean up.
myEXEC WaitIO
breakOK1
move.b #0,ACR(a5) ;stop the break
bclr #UNITB_INBREAK,UNIT_FLAGS(a3)
PUTDEBUG 100,<'Break: Finished!'>
rts
;AbortIO() is a REQUEST to "hurry up" processing of an IORequest.
;If the IORequest was already complete, nothing happens (if an IORequest
;is quick or LN_TYPE=NT_REPLYMSG, the IORequest is complete).
;The message must be replied with ReplyMsg(), as normal.
;
;Note that AbortIO is called directly, not via BeginIO/PerformIO.
;The only other direct functions are Open, Close, Expunge, and of course
;BeginIO.
;
; ( iob: a1, device:a6 )
;returns an error code in d0 - zero if successful.
;If sucessful, AbortIO returns IOERR_ABORTED in IO_ERROR and sets bit
;5 of IO_FLAGS.
;
;This routine uses d0/d1/a0, but they do not need to be saved.
LibAbortIO
PUTDEBUG 100,<'AbortIO: called'>
myForbid
move.b #IOERR_ABORTED,IO_ERROR(a1) ;We always say we succeed(ed)
bset #5,IO_FLAGS(a1)
cmpi.b #NT_REPLYMSG,LN_TYPE(a1) ;Already complete?
beq.b complete
;Check to see whether or not the IORequest is being processed
btst #ioflagsB_Active,IO_FLAGS(a1)
bne.b inprogress
bset #ioflagsB_Ignore,IO_FLAGS(a1)
complete
myPermit
moveq #0,d0
PUTDEBUG 100,<'AbortIO: Finished!'>
rts
inprogress
movea.l IO_UNIT(a1),a3 ;IO is in progress - abort it.
cmpi.w #CMD_READ,IO_COMMAND(a1)
bne ip1
move.l readabortsig(a3),d0
lea mdu_rtcb(a3),a1
ip0
myEXEC Signal
bra.b complete
ip1
cmpi.w #CMD_WRITE,IO_COMMAND(a1)
bne.b ip2
ip3
move.l writeabortsig(a3),d0
lea mdu_wtcb(a3),a1
bra.b ip0
;The break command is handled here.
;We use the same abort signal (as write), so handle just as with write.
ip2
bra.b ip3
Invalid
move.b #IOERR_NOCMD,IO_ERROR(a1)
rts
;Clear invalidates all internal buffers.
;
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
MyClear
myDisable
move.l startbuf(a3),d0
move.l d0,head(a3)
move.l d0,tail(a3)
myEnable
rts
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
MyReset
PUTDEBUG 100,<'MyReset: called'>
myForbid
move.b IERstate(a3),d7
clr.b IERstate(a3)
move.b #$7f,IER(a5) ;Disable ACIA interrupts
bsr Flush ;Flush pending requests
;Abort current IO, if any IO is indeed occuring
move.l a1,-(sp)
btst #UNITB_BREAKACTIVE,UNIT_FLAGS(a3)
beq NoBreakActive
movea.l breakiorequest(a3),a1
bsr LibAbortIO
NoBreakActive
btst #UNITB_WRITEACTIVE,UNIT_FLAGS(a3)
beq WriteNotActive
movea.l WriteRequestPtr(a3),a1
bsr LibAbortIO
WriteNotActive
btst #UNITB_READACTIVE,UNIT_FLAGS(a3)
beq NothingActive
movea.l ReadRequestPtr(a3),a1
bsr LibAbortIO
NothingActive
movea.l (sp)+,a1
bsr SetDefaultPrefs
bsr CopyPrefs
bsr FreeResources
bsr SetUpUnit
tst.l d0 ;Check for a possible error condition
bmi.b OutOfMem
subq.l #1,d0
beq.b TimerError
subq.l #1,d0
beq.b ParamError
move.b d7,IERstate(a3) ;Enable DACIA interrupts again
bset #7,d7
move.b d7,IER(a5)
myPermit
clr.l IO_ACTUAL(a1)
PUTDEBUG 100,<'MyReset: Finished!'>
rts
ParamError
move.b #SerErr_InvParam,IO_ERROR(a1)
bra.b MyResetFailed
TimerError
move.b #SerErr_TimerErr,IO_ERROR(a1)
bra.b MyResetFailed
OutOfMem
move.b #SerErr_BufErr,IO_ERROR(a1)
MyResetFailed
myPermit
clr.l IO_ACTUAL(a1)
PUTDEBUG 100,<'MyReset: Error!'>
rts
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
;
;Return number of chars in buffer in IO_ACTUAL
;Fill in IO_STATUS
Query
PUTDEBUG 100,<'Query: called.'>
bsr GetBytesInReadBuf
move.l d0,IO_ACTUAL(a1)
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
PUTDEBUG 100,<'Query: %ld bytes in buf.'>
move.l (sp)+,d0
endc
moveq #0,d0 ;d0 will mirror IO_STATUS
move.b CSR(a5),d1
btst #0,d1
beq.b Q1
bset #6,d0
Q1
btst #1,d0
beq.b Q2
bset #7,d0
Q2
btst #3,d1
beq.b Q3
bset #3,d0
Q3
btst #4,d1
beq.b Q4
bset #5,d0
Q4
btst #5,d1
beq.b Q5
bset #4,d0
Q5
btst #UNITB_BREAKACTIVE,UNIT_FLAGS(a3)
beq.b NB
bset #9,d0
NB
btst #UNITB_INBREAK,UNIT_FLAGS(a3)
beq.b NB1
bset #9,d0
NB1
btst #2,d1
beq.b NRB
bset #10,d0
NRB
tst.b xstate(a3)
bne.b xIsOn
bset #11,d0
xIsOn
move.w d0,IO_STATUS(a1)
rts
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
SetParams:
bclr #SERB_XDISABLED,prefs_SERFLAGS(a4)
btst #SERB_XDISABLED,IO_SERFLAGS(a1) ;Set the XDISABLED thing
beq.b SetP1
bset #SERB_XDISABLED,prefs_SERFLAGS(a4)
SetP1:
beq.b SetP2
st xstate(a3)
;Now check to see whether the device is busy, i.e. any current or pending requests.
SetP2
myForbid
move.b UNIT_FLAGS(a3),d0
and.b #$3c,d0 ;anything going on at the moment?
bne DevBusy
lea $14(a3),a0 ;Read port - anything there?
movea.l (a0),a2
tst.l (a2)
bne DevBusy
lea mdu_wport+$14(a3),a0 ;Write port - anything there?
movea.l (a0),a2
tst.l (a2)
bne DevBusy
;Ok, the device is not busy. Set all params.
bsr SetPrefs ;First copy the data
;Note that this is for compatability only; it does not speed up this driver.
btst #SERB_RAD_BOOGIE,IO_SERFLAGS(a1) ;Check for RAD_BOOGIE
beq.b SkipBoogie
bclr #SEXTB_MSPON,prefs_EXTFLAGS+3(a4)
bclr #SERB_PARTY_ON,prefs_SERFLAGS(a4)
bset #SERB_XDISABLED,prefs_SERFLAGS(a4)
move.b #8,prefs_READLEN(a4)
move.b #8,prefs_WRITELEN(a4)
SkipBoogie
bsr InitDACIA ;Then set up the chip
tst.l d0
bne PInvP
;* Deallocate the old read buffer and allocate a new one. *
myDisable
move.l a1,-(sp) ;Free input buffer memory
movea.l startbuf(a3),a1
movea.l mdu_prefs(a3),a4
move.l prefs_RBUFLEN(a4),d0
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS FreeMem
movea.l (sp)+,a6
move.l prefs_RBUFLEN(a4),d0 ;Allocate buffer mem
moveq #64,d1
cmp.l d1,d0
bhi.b SPmem
move.l d1,d0
move.l d0,prefs_RBUFLEN(a4)
SPmem
move.l #MEMF_PUBLIC,d1
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS AllocMem
movea.l (sp)+,a6
movea.l (sp)+,a1
tst.l d0
bne.b SPMemOK
move.b #SerErr_BufErr,IO_ERROR(a1)
moveq #64,d0
move.l d0,prefs_RBUFLEN(a4)
lea AltBuf(a3),a0
move.l a0,d0
SPMemOK
bsr.b BufPtrSetup
myEnable
SPLx
myPermit
rts
PInvP
move.b #SerErr_InvParam,IO_ERROR(a1)
bra.b SPLx
BufPtrSetup
move.l d0,head(a3)
move.l d0,tail(a3)
move.l d0,startbuf(a3)
add.l prefs_RBUFLEN(a4),d0
subq.l #1,d0
move.l d0,endbuf(a3)
rts
DevBusy
myPermit
move.b #SerErr_DevBusy,IO_ERROR(a1)
rts
; This is the code that I added to support the ASDG command SetCtrlLines.
; This will change the DTR or the RTS lines without external software
; playing directly with the hardware. This (seems to me) to be a better
; idea then what you have to do on the serial.device. BBS type programs
; need this type of support. Mike Mossman ( March 25, 1991)
; a1 -- a pointer to the io request block
; a3 -- a pointer to the unit
; a4 -- a pointer to prefs
; a5 -- a pointer to the unit hardware
; a6 -- a pointer to the device
SetCtrlLines
move.l IO_OFFSET(a1),d0 ;Get the mask into d0
move.l IO_LENGTH(a1),d1 ;Get the bits for Setting/Clearing into d1
and.l d0,d1 ;And the two, to get the bits that need to be changed
not.l d1 ;not them for 6552 logic
ori.b #%11111100,d1 ;set the hi bit for FR access
move.b frstate(a3),d0 ;Get current state
bset.l #0,d0
bset.l #1,d0
bset.l #7,d0
and.b d1,d0 ;Set DTR and RTS as requested (a low is assert)
move.b d0,frstate(a3) ;Save the bit setting in the unit
move.b d0,FMR(a5) ;Actually set the bits
rts
;The Stop command stop all future io requests from being processed until a
;Start command is received. The Stop command is NOT stackable: e.g. no matter
;how many stops have been issued, it only takes one Start to restart
;processing.
MyStop
PUTDEBUG 100,<'MyStop: called'>
bset #MDUB_STOPPED,MDU_FLAGS(a3)
rts
Start
PUTDEBUG 100,<'Start: called'>
bsr.b InternalStart
rts
;[A3=unit A6=device]
InternalStart
PUTDEBUG 100,<'InternalStart: called'>
movea.l a1,a2
;Turn processing back on
bclr #MDUB_STOPPED,MDU_FLAGS(a3)
;Kick the tasks to start them moving
move.b MP_SIGBIT(a3),d1 ;First the read task...
moveq #0,d0
bset d1,d0 ;Prepared signal mask
movea.l MP_SIGTASK(a3),a1 ;FIXED: marco-task to signal
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS Signal ;FIXED: marco-a6 not a3
movea.l (sp)+,a6
movea.l a2,a1 ;Then the write task
lea mdu_wport(a3),a0
move.b MP_SIGBIT(a0),d1
moveq #0,d0
bset d1,d0 ;Prepared signal mask
movea.l MP_SIGTASK(a0),a1 ;FIXED: marco-task to signal
move.l a6,-(sp)
movea.l md_SysLib(a6),a6
mySYS Signal ;FIXED: marco-a6 not a3
movea.l (sp)+,a6
movea.l a2,a1
PUTDEBUG 100,<'InternalStart: Finished!'>
rts
;Flush pulls all I/O requests off the queue and sends them back. We must be
;careful not to destroy work in progress, and also that we do not let some io
;requests slip by.
;
;Some funny magic goes on with the STOPPED bit in here. Stop is defined as
;not being reentrant. We therefore save the old state of the bit and then
;restore it later. This keeps us from needing to DISABLE in flush. It also
;fails miserably if someone does a start in the middle of a flush. (A
;semaphore might help...)
Flush
PUTDEBUG 100,<'Flush: called'>
movem.l d2/a1/a6,-(sp)
movea.l md_SysLib(a6),a6
bset #MDUB_STOPPED,MDU_FLAGS(a3)
sne d2
ReadFlush_Loop
movea.l a3,a0
mySYS GetMsg ;Steal messages from task's port
tst.l d0
beq.b WriteFlush_Loop
movea.l d0,a1
move.b #IOERR_ABORTED,IO_ERROR(a1)
mySYS ReplyMsg
bra.b ReadFlush_Loop
WriteFlush_Loop
lea mdu_wport(a3),a0
mySYS GetMsg ;Steal messages from task's port
tst.l d0
beq.b Flush_End
movea.l d0,a1
move.b #IOERR_ABORTED,IO_ERROR(a1)
mySYS ReplyMsg
bra.b WriteFlush_Loop
Flush_End
move.l d2,d0
movem.l (sp)+,d2/a1/a6
tst.b d0
bne.b 1$
bsr InternalStart
1$
PUTDEBUG 100,<'Flush: Finished!'>
rts
;Here begins the task related routines
;
;A Task is provided so that queued requests may be processed at
;a later time. This is not very justifiable for a ram disk, but
;is very useful for "real" hardware devices. Take care with
;your arbitration of shared hardware with all the multitasking
;programs that might call you at once.
;
; Register Usage
; ==============
; a3 -- unit pointer
; a6 -- syslib pointer
; a5 -- device pointer
; a4 -- task (NOT process) pointer
; d7 -- wait mask
;----------------------------------------------------------------------
;
;Note: Signals must be allocated within this (the task's) context!
;The task is responsible for enabling the right DACIA interrupts...AFTER
;it has done its setup (like allocating signals).
;
;NOTE: We actually have two tasks, each with their own separate
; code (but shared data). First comes the read task...:
cnop 0,4 ;Long word align
ReadTask_Begin
PUTDEBUG 200,<'ReadTask_Begin'>
movea.l (SysBase).w,a6
;Grab the arguments passed down from our parent
movea.l 4(sp),a3 ;Unit pointer
movea.l mdu_Device(a3),a5 ;Point to device structure
lea readsig(a3),a2
moveq #1,d6 ;Number of signals to allocate-1
ReadSigLoop
moveq #-1,d0 ;-1 is any signal at all
mySYS AllocSignal ;Allocate signals for I/O interrupts
moveq #0,d7 ;Convert bit number signal mask
bset d0,d7
move.l d7,(a2)+ ;Save in unit structure
dbra d6,ReadSigLoop
moveq #-1,d0 ;-1 is any signal at all
mySYS AllocSignal ;Allocate a signal
move.b d0,MP_SIGBIT(a3)
move.b #PA_SIGNAL,MP_FLAGS(a3) ;Make message port "live"
;Change the bit number into a mask, and save in d7
moveq #0,d7 ;Clear D7
bset d0,d7
ifge INFO_LEVEL-40
move.l $114(a6),-(sp)
move.l a5,-(sp)
move.l a3,-(sp)
move.l d0,-(sp)
PUTDEBUG 200,<'ReadTask -- Signal=%ld, Unit=%lx Device=%lx Task=%lx'>
adda.l #4*4,sp
endc
;Enable read-related ACIA interrupts
move.l a5,-(sp)
movea.l daciabase(a3),a5
ori.b #READINTMASK,IERstate(a3)
move.b #READINT,IER(a5)
movea.l (sp)+,a5
bra.b ReadTask_StartHere
;OK, kids, we are done with initialization. We now can start the main loop
;of the driver. It goes like this. Because we had the port marked PA_IGNORE
;for a while (in InitUnit) we jump to the getmsg code on entry. (The first
;message will probably be posted BEFORE our task gets a chance to run).
; wait for a message
; lock the device
; get a message. If no message, unlock device and loop
; dispatch the message
; loop back to get a message
;No more messages. Back ourselves out.
ReadTask_Unlock
andi.b #$ff&(~(UNITF_READACTIVE!UNITF_INREADTASK)),UNIT_FLAGS(a3)
;Main loop: wait for a new message
ReadTask_MainLoop
PUTDEBUG 200,<'ReadTask ++Sleep'>
move.l d7,d0
mySYS Wait
ifge INFO_LEVEL-5
bchg.b #1,($bfe001).l ;Blink the power LED
endc
ReadTask_StartHere
PUTDEBUG 200,<'ReadTask ++Wakeup'>
btst #MDUB_STOPPED,MDU_FLAGS(a3) ;See if we are stopped
bne ReadTask_MainLoop ;Device is stopped, ignore messages
bset #UNITB_READACTIVE,UNIT_FLAGS(a3) ;Lock the device
bne ReadTask_MainLoop ;Device in use (immediate command?)
ReadTask_NextMessage:
movea.l a3,a0
mySYS GetMsg ;Get the next request
PUTDEBUG 200,<'ReadTask GotMsg'>
tst.l d0
beq ReadTask_Unlock ; no message?
movea.l d0,a1 ;Do this request
exg a5,a6 ;Put device ptr in right place
myForbid
btst #ioflagsB_Ignore,IO_FLAGS(a1)
bne.b Readignorecmd
bset #ioflagsB_Active,IO_FLAGS(a1)
move.l a1,ReadRequestPtr(a3)
myPermit
bsr PerformIO ;Do it!
PUTDEBUG 200,<'Read Did PerformIO'>
bclr #ioflagsB_Active,IO_FLAGS(a1)
;No longer active - abort has stopped sending signals. Now we can
;(and should) clear the abort signal.
moveq #0,d0
move.l readabortsig(a3),d1
myEXEC SetSignal
bra.b Readconttl
Readignorecmd
myPermit
clr.l IO_ACTUAL(a1)
bsr TermIO
PUTDEBUG 200,<'Read Did TermIO'>
Readconttl
exg a5,a6 ;Get ExecBase back in a6
bra ReadTask_NextMessage
;**** End of read task code ****
;**** Beginning of write task code ****
cnop 0,4 ;Long word align
; Register Usage
; ==============
; a3 -- unit pointer
; a6 -- syslib pointer
; a5 -- device pointer
; a4 -- task (NOT process) pointer
; d7 -- wait mask
WriteTask_Begin
PUTDEBUG 100,<'WriteTask_Begin'>
movea.l (SysBase).w,a6
;Grab the arguments passed down from our parent
movea.l 4(sp),a3 ;Unit pointer
movea.l mdu_Device(a3),a5 ;Point to device structure
lea tdresig(a3),a2
moveq #4,d6 ;Number of signals to allocate-1
WriteSigLoop
moveq #-1,d0 ;-1 is any signal at all
mySYS AllocSignal ;Allocate signals for I/O interrupts
moveq #0,d7 ;Convert bit number signal mask
bset d0,d7
move.l d7,(a2)+ ;Save in unit structure
dbra d6,WriteSigLoop
move.l breaksig(a3),d0
move.l d0,d1
mySYS SetExcept ;Make breaksig an exception-causing signal
;Allocate a signal for the timer message port
moveq #-1,d0 ;-1 is any signal at all
mySYS AllocSignal
lea timerport(a3),a2
move.b d0,MP_SIGBIT(a2)
move.l a2,timeriorequest+MN_REPLYPORT(a3)
suba.l a1,a1
mySYS FindTask
move.l d0,MP_SIGTASK(a2)
move.b #PA_SIGNAL,MP_FLAGS(a2) ;Make message port "live"
;Allocate a signal for the cmd message port
moveq #-1,d0 ;-1 is any signal at all
mySYS AllocSignal
move.b d0,mdu_wport+MP_SIGBIT(a3)
move.b #PA_SIGNAL,mdu_wport+MP_FLAGS(a3) ;Make message port "live"
;Change the bit number into a mask, and save in d7
moveq #0,d7 ;Clear D7
bset d0,d7
ifge INFO_LEVEL-40
move.l ThisTask(a6),-(sp)
move.l a5,-(sp)
move.l a3,-(sp)
move.l d0,-(sp)
PUTDEBUG 100,<'WriteTask -- Signal=%ld, Unit=%lx Device=%lx Task=%lx'>
adda.l #4*4,sp
endc
;Enable write-related ACIA interrupts
move.l a5,-(sp)
movea.l daciabase(a3),a5
ori.b #HANDINTMASK,IERstate(a3)
move.b #HANDINT,IER(a5)
movea.l (sp)+,a5
bra.b WriteTask_StartHere
;No more messages. Back ourselves out.
WriteTask_Unlock
andi.b #$ff&(~(UNITF_WRITEACTIVE!UNITF_INWRITETASK)),UNIT_FLAGS(a3)
;Main loop: wait for a new message
WriteTask_MainLoop
PUTDEBUG 100,<'WriteTask ++Sleep'>
move.l d7,d0
mySYS Wait
ifge INFO_LEVEL-5
bchg.b #1,($bfe001).l ;Blink the power LED
endc
WriteTask_StartHere
PUTDEBUG 100,<'WriteTask ++Wakeup'>
btst #MDUB_STOPPED,MDU_FLAGS(a3) ;See if we are stopped
bne WriteTask_MainLoop ;Device is stopped, ignore messages
bset #UNITB_WRITEACTIVE,UNIT_FLAGS(a3) ;Lock the device
bne WriteTask_MainLoop ;Device in use (immediate command?)
WriteTask_NextMessage:
lea mdu_wport(a3),a0
mySYS GetMsg ;Get the next request
PUTDEBUG 100,<'WriteTask GotMsg'>
tst.l d0
beq WriteTask_Unlock ;No message?
movea.l d0,a1 ;Do this request
exg a5,a6 ;Put device ptr in right place
myForbid
btst #ioflagsB_Ignore,IO_FLAGS(a1)
bne.b Writeignorecmd
bset #ioflagsB_Active,IO_FLAGS(a1)
move.l a1,WriteRequestPtr(a3)
myPermit
bsr PerformIO ;Do it!
bclr #ioflagsB_Active,IO_FLAGS(a1)
;No longer active - abort has stopped sending signals. Now we can
;(and should) clear the abort signal.
moveq #0,d0
move.l writeabortsig(a3),d1
myEXEC SetSignal
bra.b Writeconttl
Writeignorecmd
myPermit
clr.l IO_ACTUAL(a1)
bsr TermIO
Writeconttl
exg a5,a6 ; get syslib back in a6
bra WriteTask_NextMessage
;***** end of write task code *****
;Exception Handler (performs a break command)
;Note that this code acts as a handler for the write task only; the
;read task has no such handler.
;
;unit ptr in a1, execbase in a6
;All registers are saved/restored by Exec
;Note: Not all the usual newser register conventions are used here
breakexception
PUTDEBUG 100,<'BreakException: called'>
move.l d0,-(sp) ;Save exception bits
movea.l a1,a3
movea.l a1,a2
btst #UNITB_BREAKACTIVE,UNIT_FLAGS(a3) ;Sanity check
beq endbreakex
movea.l daciabase(a3),a5
move.b #2,ACR(a5) ;start break
lea timeriorequest(a3),a1
move.w #TR_ADDREQUEST,IO_COMMAND(a1)
movea.l mdu_prefs(a3),a4
clr.l TV_SECS(a1)
move.l prefs_BRKTIME(a4),d0
ori.b #$FF,d0 ;To avoid the V33/V34 bug
move.l d0,TV_MICRO(a1)
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
PUTDEBUG 100,<'BreakException: TV_MICRO=%ld'>
addq.l #4,sp
endc
movea.l MN_REPLYPORT(a1),a0
move.b MP_SIGBIT(a0),d2
jsr _LVOSendIO(a6)
ifne INFO_LEVEL ;If any debugging enabled at all
clr.l -(sp)
move.b d2,3(sp)
PUTDEBUG 100,<'BreakException: Waiting for signal #%ld'>
addq.l #4,sp
endc
;Take note - waiting within an exception is tricky!
suba.l a1,a1
jsr _LVOFindTask(a6)
movea.l d0,a4 ;ThisTask
moveq #0,d0
bset d2,d0
add.l writeabortsig(a3),d0
move.l TC_SIGWAIT(a4),d3 ;Save this -- very important!!!
jsr _LVOWait(a6)
move.l d3,TC_SIGWAIT(a4) ;Restore -- very important!!!
and.l writeabortsig(a3),d0
beq.b breakOK
PUTDEBUG 100,<'BreakException: Aborted!'>
lea timeriorequest(a3),a1 ;The break was aborted. Clean up.
jsr _LVOAbortIO(a6)
lea timeriorequest(a3),a1
jsr _LVOWaitIO(a6)
breakOK
move.b #0,ACR(a5)
movea.l breakiorequest(a3),a1
clr.b IO_ERROR(a1)
btst #IOB_QUICK,IO_FLAGS(a1)
bne.b endbreakex
jsr _LVOReplyMsg(a6)
endbreakex
move.l (sp)+,d0 ;restore exception bits
PUTDEBUG 100,<'BreakException: Finished!'>
rts
;Initialize the device
mdu_Init ;Initialize read task message port/tcb
INITBYTE MP_FLAGS,PA_IGNORE ;Unit starts with a message port
INITBYTE LN_TYPE,NT_MSGPORT
INITLONG LN_NAME,Task1
INITLONG mdu_rtcb+LN_NAME,Task1
INITBYTE mdu_rtcb+LN_TYPE,NT_TASK
INITBYTE mdu_rtcb+LN_PRI,5
;Initialize write task message port/tcb
INITBYTE mdu_wport+MP_FLAGS,PA_IGNORE ;Unit starts with a message port
INITBYTE mdu_wport+LN_TYPE,NT_MSGPORT
INITLONG mdu_wport+LN_NAME,Task2
INITLONG mdu_wtcb+LN_NAME,Task2
INITBYTE mdu_wtcb+LN_TYPE,NT_TASK
INITBYTE mdu_wtcb+LN_PRI,126
;Initialize interrupt structure (except for is_data of course).
INITBYTE mdu_is+LN_TYPE,NT_INTERRUPT
INITBYTE mdu_is+LN_PRI,-127 ;Int priority 11
INITLONG mdu_is+IS_CODE,int ;Interrupt routine addr
INITLONG mdu_is+LN_NAME,LibName
;Initialize timer message port
INITBYTE timerport+MP_FLAGS,PA_IGNORE ;Unit starts with a message port
INITBYTE timerport+LN_TYPE,NT_MSGPORT
INITLONG timerport+LN_NAME,LibName
dc.w 0
;******************** Interrupt code *********************************
;Notes:
;
;Guru's Guide #1: Interrupts is helpful.
;------ ----- --- ----------
;
;NOTE: I am using one interrupt server for each unit. I think it's
;simpler then using one each for the read and write tasks.
;
;This is the interrupt server code. It serves four purposes:
;1. Read in a byte if available, store it, and signal the read task
;2. Check for TDRE-empty condition and signal write task
;3. Check for a DSR* transition and signal write task IF handshake is wanted
;4. Check for an exceptional condition and signal read task
;
;Enter with data pointer in a1 (unit pointer)
;d0, d1, a0, a1, a5, and a6 are scratch
;
;Returns Z bit clear if the interrupt was not caused by the dacia,
;otherwise return with the Z bit set.
int
move.l a3,-(sp)
move.w d7,-(sp)
movea.l a1,a3
movea.l daciabase(a3),a5
move.b ISR(a5),d7 ;This clears CTST, DCDT, and DSRT ints
and.b IERstate(a3),d7 ;Quick check (note that this masking is very
;important)
bne.b serveint ;Yes, service the dacia
move.w (sp)+,d7 ;Not for us
movea.l (sp)+,a3
PUTDEBUG 200,<'Int: Not for us!'>
moveq #0,d0
rts
serveint
ifne INFO_LEVEL ;If any debugging enabled at all
clr.l -(a7)
move.b d7,3(a7)
PUTDEBUG 200,<'Int: Entered with ISR=%lx'>
addq.l #4,sp
endc
;Read error condition?
;(We must check this first because a read of the RDR clears the error flags.)
move.b d7,d0
and.b #6,d0
beq.b contint ;No errors
PUTDEBUG 200,<'Int: Read error condition.'>
;An exceptional condition occured - signal the read task
move.b d7,ISRcopy(a3) ;Useful info
move.b CSR(a5),CSRcopy(a3) ;More useful info
move.l readabortsig(a3),d0
lea mdu_rtcb(a3),a1
movea.l mdu_SysLib(a3),a6
jsr _LVOSignal(a6)
move.b RDR(a5),d0 ;This forces a clear of the error bits
bra rdrfempty
contint
btst #ISRB_RDRF,d7 ;Test Receive Data Buffer Full
beq.b rdrfempty
PUTDEBUG 200,<'Int: Read.'>
;Store the byte in the circular buffer. Note that this code *can't* be
;interrupted, so we're safe (that's because CIA B, which we're plugging into,
;is connected to INT6*).
movea.l tail(a3),a0 ;Read and store
move.b RDR(a5),(a0)+
cmpa.l endbuf(a3),a0
bls.b notend ;endbuf >= tail
movea.l startbuf(a3),a0 ;Wrap around
notend
move.l a0,tail(a3)
move.l head(a3),d0
cmpa.l d0,a0
bne.b nocross
bset #MDUB_V,MDU_FLAGS(a3) ;set buf overflow flag
addq.l #1,d0
move.l d0,head(a3)
cmp.l endbuf(a3),d0
bls.b nocross
move.l startbuf(a3),head(a3)
nocross
movea.l mdu_SysLib(a3),a6
move.l readsig(a3),d0
lea mdu_rtcb(a3),a1
jsr _LVOSignal(a6) ;Signal the read task
rdrfempty
btst #ISRB_TDRE,d7 ;Check TDRE
beq.b notdre
PUTDEBUG 200,<'Int: Write signal.'>
andi.b #WRITEOFFMASK,IERstate(a3)
move.b #WRITEOFF,IER(a5)
move.l tdresig(a3),d0
lea mdu_wtcb(a3),a1
movea.l mdu_SysLib(a3),a6
jsr _LVOSignal(a6) ;Signal the write task
;see if handshaking ("7 wire") is enabled
notdre
movea.l mdu_prefs(a3),a6
btst #SERB_XDISABLED,prefs_SERFLAGS(a6)
bne.b nohand
btst #ISRB_DSRT,d7 ;Check for DSR* transition
beq.b nohand
PUTDEBUG 200,<'Int: DSR transition.'>
move.l dsrsig(a3),d0
lea mdu_wtcb(a3),a1
movea.l mdu_SysLib(a3),a6
jsr _LVOSignal(a6) ;Signal the write task
nohand:
termchain
PUTDEBUG 200,<'Int: Finished!'>
move.w (sp)+,d7
movea.l (sp)+,a3
moveq #1,d0 ;Terminate the chain
rts
defaultprefs
dc.l SER_DEFAULT_CTLCHAR ;prefs_CTLCHAR
dc.l 16*1024 ;prefs_RBUFLEN
dc.l 0 ;prefs_EXTFLAGS
dc.l 1200 ;prefs_BAUD
dc.l 250000 ;prefs_BRKTIME
dc.l 0 ;prefs_TERMARRAY
dc.l 0 ;prefs_TERMARRAY
dc.b 8 ;prefs_READLEN
dc.b 8 ;prefs_WRITELEN
dc.b 1 ;prefs_STOPBITS
dc.b $88 ;prefs_SERFLAGS
;Table of DACIA base addresses for the 4 units
basetable
dc.l ACIA_Base
dc.l ACIA_Base+UNIT2
dc.l ACIA_Base+ACIA1
dc.l ACIA_Base+ACIA1+UNIT2
;List of supported baud rates
baudtable
dc.w 50
dc.w 110
dc.w 135
dc.w 150
dc.w 300
dc.w 600
dc.w 1200
dc.w 1800
dc.w 2400
dc.w 3600
dc.w 4800
dc.w 7200
dc.w 9600
dc.w 19200
dc.w 38400
dc.w 31250 ;MIDI (external clock)
cmdtable
dc.l Invalid ;$00000001 ;0 CMD_INVALID
dc.l MyReset ;$00000002 ;1 CMD_RESET
dc.l Read ;$00000004 ;2 CMD_READ
dc.l Write ;$00000008 ;3 CMD_WRITE
dc.l Invalid ;$00000010 ;4 CMD_UPDATE (update has no meaning here)
dc.l MyClear ;$00000020 ;5 CMD_CLEAR
dc.l MyStop ;$00000040 ;6 CMD_STOP
dc.l Start ;$00000080 ;7 CMD_START
dc.l Flush ;$00000100 ;8 CMD_FLUSH
dc.l Query ;$00000200 ;9 SDCMD_QUERY
dc.l Break ;$00000400 ;10 SDCMD_BREAK
dc.l SetParams ;$00000800 ;11 SDCMD_SETPARAMS
dc.l $FFFFFFFF ;$00001000 ;12 NULL
dc.l $FFFFFFFF ;$00002000 ;13 NULL
dc.l $FFFFFFFF ;$00004000 ;14 NULL
dc.l $FFFFFFFF ;$00008000 ;15 NULL
dc.l SetCtrlLines ;$00010000 ;16 SIOCMD_SETCTRLLINES
cmdtable_end:
ISDEBUG 'newser' ;This name for debugging use
dc.l 0
LibEnd:
END