home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frozen Fish 1: Amiga
/
FrozenFish-Apr94.iso
/
bbs
/
alib
/
d5xx
/
d520
/
ioboard.lha
/
IOBoard
/
newerser.lzh
/
newser.asm
< prev
next >
Wrap
Assembly Source File
|
1991-07-21
|
69KB
|
2,839 lines
*****************************************************************************
* Program: newser.asm - Copyright © 1990, 1991 by Dan Babcock
*
* Permission is given for PERSONAL use of this code. Commercial
* use is of course forbidden. (Without prior permission etc.)
*
* Function: A "serial.device" compatible driver for the Rockwell 65C52
*
* 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
* [release 1]
* 03/03/91 V1.10 Fixed some bugs, speeded up, & added prefs hook
* 03/08/91 V1.11 Bug fixes
* [release 2]
* 04/18/91 V1.12 Small changes (very small)
* 06/20/91 V2.00 Bug fixes, code clean-up, faster
* [release 3]
* 06/27/91 V2.10 Added ASDG-style DTR/RTS control extension
* (See "SetControlLines" for documentaion)
* Added special time-out to write routine to
* avoid theoretical (and possibly real) problem
* Fixed bug in Break routine
* [release 4]
*
* Feel free to contact me:
*
* [School address] [Permanent address] People/Link:
* Subject to Dan Babcock DANBABCOCK
* change so not P.O. Box 1532 Bix:
* given here Southgate, MI 48195 danbabcock
* U.S.A.
*
* I am also reachable via internet. I read comp.sys.amiga.programming.
*
* 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 differences 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 doubt anyone uses Start and Stop...)
* 2. Newser does not currently send an XOFF or otherwise tell the outside
* world to "shut up" when the driver's input buffer fills.
* Since the buffer is 64K, it is very unlikely to ever fill.
* Both of those capabilities can be added external to the driver if you
* have a custom application that requires them.
*****************************************************************************
MC68010
super ;suppress warnings about supervisor mode
exeobj
objfile 'devs:newser.device'
macfile 'newser.i'
;Set INFO_LEVEL to a high number (e.g. 100000) for full debugging output
;over the internal serial port.
INFO_LEVEL equ 000000
EXEC macro
move.l a1,-(sp)
move.l a6,-(sp)
movea.l (4).w,a6
jsr (_LVO\1,a6)
movea.l (sp)+,a6
movea.l (sp)+,a1
endm
;*************************** Structures *************************
serprefs clrso
prefs_CTLCHAR so.l 1 ;Control char's (order = xON,xOFF,rsvd,rsvd)
prefs_RBUFLEN so.l 1 ;Length in bytes of serial port's read buffer
prefs_EXTFLAGS so.l 1 ;Additional serial flags
prefs_BAUD so.l 1 ;Baud rate requested (true baud)
prefs_BRKTIME so.l 1 ;Duration of break signal in MICROseconds
prefs_TERMARRAY so.b TERMARRAY_SIZE ;Termination character array
prefs_READLEN so.b 1 ;Bits per read char (bit count)
prefs_WRITELEN so.b 1 ;Bits per write char (bit count)
prefs_STOPBITS so.b 1 ;Stopbits for read (count)
prefs_SERFLAGS so.b 1 ;See SERFLAGS bit definitions
serprefs_sizeof soval
MyDev setso LIB_SIZE
md_Flags so.b 1
md_Pad1 so.b 1
md_SysLib so.l 1
md_SegList so.l 1
md_Units so.b MD_NUMUNITS*4
VectorBase so.l 1
MyPreviousAutoVec: so.l 1
ScoreBoard so.w 1
Chip1present so.w 1
Chip2present so.w 1
prefs_unit0 so.b serprefs_sizeof ;The reason why prefs are in this
prefs_unit1 so.b serprefs_sizeof ;structure instead of the unit structure
prefs_unit2 so.b serprefs_sizeof ;is that the pref settings should be saved
prefs_unit3 so.b serprefs_sizeof ;across CloseDevice calls (which usually
MyDev_Sizeof soval ;causes the unit to be dumped).
MyDevUnit setso UNIT_SIZE ;Odd # longwords
mdu_wport so.b MP_SIZE ;MsgPort for write task
MDU_FLAGS so.b 1
IERstate so.b 1 ;Current state of IER used as a mask
mdu_UnitNum so.b 1
frstate so.b 1 ;This var mirrors a write-only register
mdu_SysLib so.l 1 ;Copy of location 4
mdu_Device so.l 1 ;Ptr to main device struct
mdu_rstack so.b MYPROCSTACKSIZE ;For read task
mdu_wstack so.b MYPROCSTACKSIZE ;For write task
mdu_rtcb so.b TC_SIZE ;Task Control Block (TCB) for read task
mdu_wtcb so.b TC_SIZE ;Task Control Block (TCB) for write task
timerport so.b MP_SIZE
timeriorequest so.b IOTV_SIZE
;The -sig longwords must be contiguous!!!
readsig so.l 1 ;Read task signals
readabortsig so.l 1
tdresig so.l 1 ;Write block finished
writeabortsig so.l 1
xonsig so.l 1 ;Comes from read task, and indicates xon received
HeadLong so.w 1 ;Ptr to start of circular buffer (logical)
Head so.w 1
TailLong so.w 1 ;Ptr to end of circular buffer (logical)
Tail so.w 1
StartBuf so.l 1 ;Ptr to physical start of input buffer
ReadRequestPtr so.l 1
WriteRequestPtr so.l 1
xstate so.b 1 ;Zero if 'x-off' received, else $FF
ISRcopy so.b 1 ;Used for read error diagnosis
CSRcopy so.b 1 ;Used for read error diagnosis
Exclusive so.b 1 ;True if someone has exclusive access to this unit
daciabase so.l 1
mdu_prefs so.l 1 ;Ptr to prefs for this unit (in MyDev)
WriteCount so.l 1
WriteBufferPtr so.l 1
MyDevUnit_Sizeof soval
;Note that we have a single unit structure used by both the read and write
;tasks (and the interrupt routine).
* UNIT_FLAG definitions:
BITDEF UNIT,BREAKSENT,2 ;break sent flag for Query
BITDEF UNIT,READIMMEDIATE,3
BITDEF UNIT,NEEDCHAR,4
* Bit definitions for MDU_FLAGS
BITDEF MDU,STOPPED,2 ;State bit for unit stopped
BITDEF MDU,V,3 ;Buffer overflow flag - set in int routine if an overflow occurs
BITDEF MDU,WaitingForChar,4
BITDEF MDU,CharAvailable,5
* Bit definitions for ioflags
BITDEF ioflags,Ignore,4 ;Ignore this IO request
* Equates & bit defs for IERstate
READINT equ $87
READINTMASK equ $7
WRITEINT equ $C0
WRITEINTMASK equ $40
MYIDENT macro
cstr 'newser.device V2.10 (June 27, 1991)',13,10
even
endm
;The first executable location. This should return an error in case someone
;tried to run us as a program (instead of loading us as a device).
FirstAddress:
moveq #-1,d0
rts
;A romtag structure. After your driver is brought in from disk, the
;disk image will be scanned for this structure to discover magic constants
;about you (such as where to start running you from...).
initDDescrip:
dw RTC_MATCHWORD ;UWORD RT_MATCHWORD (Magic cookie)
dl initDDescrip ;APTR RT_MATCHTAG (Back pointer)
dl EndCode ;APTR RT_ENDSKIP (To end of this hunk)
db RTF_AUTOINIT ;UBYTE RT_FLAGS (magic-see "Init")
db VERSION ;UBYTE RT_VERSION
db NT_DEVICE ;UBYTE RT_TYPE (must be correct)
db MYPRI ;BYTE RT_PRI
dl myName ;APTR RT_NAME (exec name)
dl idString ;APTR RT_IDSTRING (text string)
dl Init ;APTR RT_INIT
db 'newser.device - Copyright (c) 1990, 1991 by Dan Babcock. '
db 'Contact me on People/Link, Bix, or Usenet!'
even
;nasty data area (makes this version non-ROMable)
OldVec: dc.l 0
Unit0: dc.l 0
Unit1: dc.l 0
_Unit2: dc.l 0 ;_ used because of conflict with chip reg addr sym
Unit3: dc.l 0
;*********************** Tables (constant) **********************
;Note!! Some of these values are depended on in the InitRoutine prefs
;code...i.e. they may not be changed arbitrarily!
defaultprefs:
dl SER_DEFAULT_CTLCHAR ;prefs_CTLCHAR
dl 64*1024 ;prefs_RBUFLEN
dl 0 ;prefs_EXTFLAGS
dl 9600 ;prefs_BAUD
dl 250000 ;prefs_BRKTIME
dl 0 ;prefs_TERMARRAY
dl 0 ;prefs_TERMARRAY
db 8 ;prefs_READLEN
db 8 ;prefs_WRITELEN
db 1 ;prefs_STOPBITS
;Note: RAD_BOOGIE must NOT be set here
db $88 ;prefs_SERFLAGS
;Table of DACIA base addresses for the 4 units
basetable:
dl ACIA_Base
dl ACIA_Base+UNIT2
dl ACIA_Base+ACIA1
dl ACIA_Base+ACIA1+UNIT2
;List of supported baud rates
baudtable:
dw 50
dw 110
dw 135
dw 150
dw 300
dw 600
dw 1200
dw 1800
dw 2400
dw 3600
dw 4800
dw 7200
dw 9600
dw 19200
dw 38400
dw 31250 ;MIDI (external clock)
timername:
cstr 'timer.device'
ifne INFO_LEVEL ;If any debugging enabled at all
subSysName:
cstr 'newser' ;This name for debugging use
endc
myName: MYDEVNAME ;This is the name that the device will have
;This is an identifier tag to help in supporting the device
;format is 'name version.revision (dd MON yyyy)',<cr>,<lf>,<null>
idString: MYIDENT
;The romtag specified that we were "RTF_AUTOINIT". This means that the
;RT_INIT structure member points to one of these tables below. If the
;AUTOINIT bit was not set then RT_INIT would point to a routine to run.
Init: dl MyDev_Sizeof ;data space size
dl FuncTable ;pointer to function initializers
dl DataTable ;pointer to data initializers
dl InitRoutine ;routine to run
FuncTable:
dw -1 ;this indicates that the following are offsets,
;rather than absolute addresses
dw Open-FuncTable ;standard system routines
dw _Close-FuncTable
dw Expunge-FuncTable
dw Null-FuncTable ;Reserved for future use!
dw BeginIO-FuncTable ;my device definitions
dw AbortIO-FuncTable
dw -1 ;function table end marker
;The data table initializes static data structures. The format is
;specified in exec/InitStruct routine's manual pages. The
;INITBYTE/INITWORD/INITLONG macros are in the file "exec/initializers.i".
;The first argument is the offset from the device base for this
;byte/word/long. The second argument is the value to put in that cell.
;The table is null terminated
DataTable:
INITBYTE LN_TYPE,NT_DEVICE ;Must be LN_TYPE!
INITLONG LN_NAME,myName
INITBYTE LIB_FLAGS,LIBF_SUMUSED+LIBF_CHANGED
INITWORD LIB_VERSION,VERSION
INITWORD LIB_REVISION,REVISION
INITLONG LIB_IDSTRING,idString
dw 0 ;terminate list
;This routine gets called after the device has been allocated. The device
;pointer is in D0. The AmigaDOS segment list is in a0. If it returns the
;device pointer, then the device will be linked into the device list. If it
;returns NULL, then the device will be unloaded.
;
;This call is single-threaded by exec; please read the description for
;"Open" below.
;
; Register Usage
; ==============
; a3 -- Points to temporary RAM
; a4 -- Expansion library base
; a5 -- device pointer
; a6 -- Exec base
InitRoutine:
PUTDEBUG 5,<'%s/Init: called'>
movem.l d1-d7/a0-a6,-(sp) ;Preserve ALL modified registers
movea.l d0,a5
move.l a6,(md_SysLib,a5) ;Save a pointer to exec
move.l a0,(md_SegList,a5) ;Save pointer to our loaded code
;Check for presence/absence of the 2 serial chips
moveq #0,d0
moveq #0,d1
move.l #ACIA_Base,a4
move.b #$83,(FMR,a4)
move.b (CSR,a4),d2
and.b #3,d2
cmp.b #3,d2
bne.b .Chip2
move.b #$80,(FMR,a4)
move.b (CSR,a4),d2
and.b #3,d2
bne.b .Chip2
moveq #-1,d0
.Chip2:
move.l #ACIA_Base+ACIA1,a4
move.b #$83,(FMR,a4)
move.b (CSR,a4),d2
and.b #3,d2
cmp.b #3,d2
bne.b .Done
move.b #$80,(FMR,a4)
move.b (CSR,a4),d2
and.b #3,d2
bne.b .Done
moveq #-1,d1
.Done:
move.w d0,(Chip1present,a5)
move.w d1,(Chip2present,a5)
;Take over level 6 autovector (nasty, but justified)
clr.l (VectorBase,a5)
tst.w (AttnFlags,a6)
beq.b .skipvbr
SYS SuperState
movec vbr,a2
bsr MyUserState
move.l a2,(VectorBase,a5)
.skipvbr:
move.l (VectorBase,a5),a0
move.l ($78,a0),(OldVec)
move.l #Int0000,($78,a0)
move.l #Int0000,(MyPreviousAutoVec,a5)
move.l a5,a6
bsr SetDefaultPrefs
move.l (4).w,a6
;Try to find the prefs in memory
lea (MagicPortName,pc),a1
SYS FindPort
tst.l d0
beq .EndInit ;use hard-coded defaults
move.l d0,a0
lea (MP_SIZE,a0),a3
move.l a5,a6
lea (prefs_unit0,a6),a2
moveq #3,d7 ;4 units
moveq #0,d5
.OuterLoop:
move.l d5,d6
lsl.w #3,d6 ;Convert unit # to index
move.l a3,a1
lea (a1,d6.w),a1 ;UnitPrefs for this unit
.PrefsLoop:
moveq #0,d0
move.w #512,d0
moveq #0,d1
move.w (up_BufSize,a1),d1 ;0-7
lsl.l d1,d0
move.l d0,(prefs_RBUFLEN,a2)
lea (baudtable,pc),a0
move.w (up_BaudRate,a1),d0 ;0-15
add.w d0,d0
moveq #0,d1
move.w (a0,d0.w),d1
move.l d1,(prefs_BAUD,a2)
moveq #5,d1 ;5 bit word
move.b (up_WordLen,a1),d0
beq.b .WordLen ;up_WordLen=0
addq.b #1,d1 ;6 bit word
subq.b #1,d0
beq.b .WordLen ;up_WordLen=1
addq.b #1,d1 ;7 bit word
subq.b #1,d0
beq.b .WordLen ;up_WordLen=2
addq.b #1,d1 ;8 bit word
.WordLen:
move.b d1,(prefs_READLEN,a2)
move.b d1,(prefs_WRITELEN,a2)
move.b (up_StopBits,a1),d0 ;0-1
addq.b #1,d0
move.b d0,(prefs_STOPBITS,a2)
move.b (up_Parity,a1),d0
cmp.b #4,d0 ;none?
beq.b .SkipParity
bset #SERB_PARTY_ON,(prefs_SERFLAGS,a2)
cmp.b #2,d0
bcc.b .MarkSpace
bclr #SERB_PARTY_ODD,(prefs_SERFLAGS,a2) ;set to even
tst.b d0
bne.b .SkipParity
bset #SERB_PARTY_ODD,(prefs_SERFLAGS,a2) ;set to odd
bra.b .SkipParity
.MarkSpace:
bset #SEXTB_MSPON,(3+prefs_EXTFLAGS,a2)
bclr #SEXTB_MARK,(3+prefs_EXTFLAGS,a2) ;set to space
cmp.b #3,d0
beq.b .SkipParity
bset #SEXTB_MARK,(3+prefs_EXTFLAGS,a2)
.SkipParity:
move.b (up_Shake,a1),d0
cmp.b #2,d0
beq.b .SkipShake
cmp.b #1,d0
bne.b .SkipXon
bclr #SERB_XDISABLED,(prefs_SERFLAGS,a2)
bra.b .SkipShake
.SkipXon:
bset #SERB_7WIRE,(prefs_SERFLAGS,a2)
.SkipShake:
addq.l #1,d5
add.w #serprefs_sizeof,a2
dbra d7,.OuterLoop
.EndInit:
move.l a5,d0 ;no error (zero indicates error)
;MUST return device ptr
PUTDEBUG 5,<'%s/Init: finished'>
movem.l (sp)+,d1-d7/a0-a6
rts
MagicPortName: dc.b 'newser_prefs',0
even
MyUserState:
;(The one in 1.2/1.3 ROM is buggy)
;Enter with ExecBase in A6 and SSP in D0
move.l (sp)+,d1
move.l sp,usp
movea.l d0,sp
movea.l a5,a0
lea (.L1,pc),a5
jmp (_LVOSupervisor,a6)
.L1: movea.l a0,a5
move.l d1,(2,sp) ;store return address in stack frame
andi.w #$DFFF,(sp) ;clear supervisor bit
rte
;Enter with device ptr in a6.
SetDefaultPrefs:
movem.l d0/d1/a0/a1,-(sp)
lea (prefs_unit0,a6),a1
moveq #MD_NUMUNITS-1,d1
1$ move.w #serprefs_sizeof-1,d0
lea (defaultprefs,pc),a0
2$ move.b (a0)+,(a1)+
dbra d0,2$
dbra d1,1$
movem.l (sp)+,d0/d1/a0/a1
rts
NewVector:
;Enter with device ptr in a6
;exit with d0=0 if OK, else error!
;uses d0,a0,a1
movem.l a0/a1,-(sp)
move.l (VectorBase,a6),a1
move.l (MyPreviousAutoVec,a6),d0
cmp.l ($78,a1),d0
bne.b .Error
moveq #0,d0
move.b (ScoreBoard,a6),d0
lsl.l #2,d0
lea (IntRoutines,pc),a0
move.l (a0,d0.w),(MyPreviousAutoVec,a6)
move.l (a0,d0.w),($78,a1)
moveq #0,d0
.End:
movem.l (sp)+,a0/a1
rts
.Error:
moveq #1,d0
bra.b .End
;Here begins the system interface commands. When the user calls OpenDevice/
;CloseDevice/RemDevice, this eventually gets translated into a call to the
;following routines (Open/Close/Expunge). Exec has already put our device
;pointer in a6 for us.
;
;Open sets the IO_ERROR field on an error. If it was successful, we should
;also set up the IO_UNIT and LN_TYPE fields. exec takes care of setting up
;IO_DEVICE.
;
;NOTE: We must also copy the current prefs for this unit into the user's
;extended iorequest fields.
;
; Register Usage
; ==============
; d0 -- unitnum
; d1 -- flags
; a1 -- iob
; a6 -- Device ptr
Open:
addq.w #1,(LIB_OPENCNT,a6) ;Fake an opener for duration of call
PUTDEBUG 20,<'%s/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 Open_Range_Error ;Unit number out of range (BHS)
;Check to see if the corresponding chip is installed
lea (Chip1present,a6),a4
tst.b (a4,d0.w)
beq Open_Range_Error
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 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 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
clr.b (IO_ERROR,a2) ;no error
move.b #NT_REPLYMSG,(LN_TYPE,a2) ;Mark IORequest as "complete"
Open_End:
subq.w #1,(LIB_OPENCNT,a6) ;End of expunge protection
movem.l (sp)+,d2/a2-a4
PUTDEBUG 30,<'%s/Open: Finished!'>
rts
Open_Range_Error:
Open_Error:
moveq #IOERR_OPENFAIL,d0
move.b d0,(IO_ERROR,a2)
move.l d0,(IO_DEVICE,a2) ;Trash IO_DEVICE on open failure
PUTDEBUG 2,<'%s/Open: failed'>
bra.b Open_End
;Copy prefs from our device struct to an IOrequest. Enter with IORequest
;in a1 and unit pointer in a3. All registers are preserved.
CopyPrefs:
movem.l d0/a1/a4,-(sp)
movea.l (mdu_prefs,a3),a4
lea (IO_CTLCHAR,a1),a1
move.w #serprefs_sizeof-1,d0
1$: move.b (a4)+,(a1)+
dbra d0,1$
movem.l (sp)+,d0/a1/a4
rts
;Copy prefs from an IOrequest to our device struct Enter with IORequest
;in a1 and unit pointer in a3. All registers are preserved.
SetPrefs:
movem.l d0/a1/a4,-(sp)
movea.l (mdu_prefs,a3),a4
lea (IO_CTLCHAR,a1),a1
move.w #serprefs_sizeof-1,d0
1$: move.b (a1)+,(a4)+
dbra d0,1$
movem.l (sp)+,d0/a1/a4
rts
;There are two different things that might be returned from the Close
;routine. If the device wishes to be unloaded, then Close must return
;the segment list (as given to Init). Otherwise close MUST return NULL.
; ( device:a6, iob:a1 )
_Close:
movem.l a2-a3,-(sp)
PUTDEBUG 20,<'%s/Close: called'>
movea.l a1,a2
movea.l (IO_UNIT,a2),a3
moveq #-1,d0
move.l d0,(IO_UNIT,a2) ;We're closed...
move.l d0,(IO_DEVICE,a2) ;Customers not welcome at this IORequest!
subq.w #1,(UNIT_OPENCNT,a3) ;See if the unit is still in use
bne.b Close_Device
bsr ExpungeUnit
Close_Device:
moveq #0,d0
subq.w #1,(LIB_OPENCNT,a6) ;Mark us as having one fewer openers
bne.b Close_End ;See if there is anyone left with us open
btst #LIBB_DELEXP,(md_Flags,a6) ;See if we have a delayed expunge pending
beq.b Close_End
bsr Expunge ;Do the expunge
Close_End:
movem.l (sp)+,a2-a3
PUTDEBUG 20,<'%s/Close: Finished!'>
rts ;MUST return either zero or the SegList!
;Expunge is called by the memory allocator when the system is low on
;memory.
;
;There are two different things that might be returned from the Expunge
;routine. If the device is no longer open then Expunge may return the
;segment list (as given to Init). Otherwise Expunge may set the
;delayed expunge flag and return NULL.
;
;One other important note: because Expunge is called from the memory
;allocator, it may NEVER Wait() or otherwise take long time to complete.
;
; A6 - library base (scratch)
; D0-D1/A0-A1 - scratch
Expunge:
PUTDEBUG 10,<'%s/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$:
;Important: If it's not possible to restore the old autovector, then don't
;expunge!!
;Restore old autovector if possible
move.l (MyPreviousAutoVec,a5),a0
move.l (VectorBase,a5),a1
cmp.l ($78,a1),a0
beq.b .ok
moveq #0,d0
bra.b Expunge_End
.ok:
move.l (OldVec,pc),($78,a1)
move.l (md_SegList,a5),d2 ;Store our seglist in d2
movea.l a5,a1 ;Unlink from device list
SYS Remove ;Remove first (before FreeMem)
movea.l a5,a1 ;Devicebase
moveq #0,d0
move.w (LIB_NEGSIZE,a5),d0
suba.l d0,a1 ;Calculate base of functions
add.w (LIB_POSSIZE,a5),d0 ;Calculate size of functions + data area
SYS FreeMem
move.l d2,d0 ;Set up our return value
Expunge_End:
movem.l (sp)+,d1/d2/a5/a6
rts
Null: moveq #0,d0 ;The "Null" function MUST return zero.
rts
;This is the main unit initialization routine. It allocates memory for
;the device structure, calls SetUpUnit, then calls InitTask.
; ( d2:unit number, a3:scratch, a6:devptr )
InitUnit:
PUTDEBUG 30,<'%s/InitUnit: called'>
movem.l d0/d1/a4/a2,-(sp)
move.l d2,d1
mulu.w #serprefs_sizeof,d1
lea (prefs_unit0,a6),a4
adda.l d1,a4
;Now A4 is a ptr to the prefs for this unit
move.l #MyDevUnit_Sizeof,d0 ;Allocate unit memory
move.l #MEMF_PUBLIC!MEMF_CLEAR,d1
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS AllocMem
movea.l (sp)+,a6
movea.l d0,a3
tst.l d0
bne continitunit1
;Couldn't init the unit (out of memory).
ReturnVoid:
move.l d2,d0 ;Unit number
lsl.l #2,d0
clr.l (md_Units,a6,d0.l) ;Set unit table
movem.l (sp)+,d0/d1/a4/a2
rts
BadSetup:
bsr KillTask
bra.b ReturnVoid
continitunit1:
moveq #0,d0 ;Don't need to re-zero it
movea.l a3,a2
lea (mdu_Init,pc),a1
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS InitStruct
movea.l (sp)+,a6
move.l a5,-(sp) ;Look up DACIA base address
lea (basetable,pc),a0
moveq #0,d0
move.b d2,d0
lsl.l #2,d0
movea.l (0,a0,d0.w),a5
move.l a5,(daciabase,a3)
move.b #$7f,(IER,a5) ;Disable DACIA interrupts
bset d2,(ScoreBoard,a6)
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
lea (Unit0,pc),a2
move.l a3,(a2,d0.l)
movem.l (sp)+,d0/d1/a4/a2
PUTDEBUG 30,<'%s/InitUnit: finished! End of silence.'>
rts
;This routine does the following:
;
;1. Allocate read buffer memory
;2. Initialize all the special MyDevUnit fields (but NOT the signals -
; they must be allocated in the task's context)
;3. Call InitDACIA [Initialize/set-up DACIA registers (for this unit).]
;4. Install an interrupt server.
;
;Returns failure code in d0 - zero if OK, -1 if out of memory, 1 if
;unable to open timer.device, 2 if invalid parm
;
;SetUpUnit(d2:unit number, a3:unit, a6:devptr)
;AND...ptr to prefs in A4
;uses d1,a0,a1,a2,a6
SetUpUnit:
PUTDEBUG 30,<'%s/SetUpUnit: called'>
movem.l d1/a0/a1/a2,-(sp)
move.l #65536,d0 ;buffer size
move.l d0,(prefs_RBUFLEN,a4)
move.l #MEMF_PUBLIC,d1
EXEC AllocMem
tst.l d0
bne.b continitunit
moveq #-1,d0 ;Failed to allocate input buffer memory - exit.
movem.l (sp)+,d1/a0/a1/a2
rts
;Initialize all the special MyDevUnit fields (but NOT the signals -
;they must be allocated in the task's context)
continitunit:
move.l d0,(StartBuf,a3)
clr.l (HeadLong,a3)
clr.l (TailLong,a3)
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.b #$FF,(xstate,a3)
move.b #$80,(frstate,a3)
bset #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
lea (timername,pc),a0 ;Open the timer device.
moveq #UNIT_MICROHZ,d0
lea (timeriorequest,a3),a1
moveq #0,d1 ;no special flags
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS OpenDevice
movea.l (sp)+,a6
tst.l d0
bne OpenTimerFailed
;Initialize/set-up DACIA registers (for this unit).
;(according to the prefs pointed to by a4)
bsr InitDACIA
tst.l d0
bne InitDACIAfailed
;Install an interrupt server.
bsr NewVector
tst.l d0
bne.b EndSetUpUnit
moveq #0,d0 ;situation under control
movem.l (sp)+,d1/a0/a1/a2
PUTDEBUG 30,<'%s/SetUpUnit: Finished!'>
rts
InitDACIAfailed:
moveq #2,d0
bra.b EndSetUpUnit
OpenTimerFailed:
moveq #1,d0
EndSetUpUnit:
move.l d0,-(sp)
move.l (prefs_RBUFLEN,a4),d0
movea.l (StartBuf,a3),a1
EXEC FreeMem
move.l (sp)+,d0
movem.l (sp)+,d1/a0/a1/a2
rts
;****** end of SetUpUnit ******
;Initialize/set-up DACIA registers (for this unit).
;(according to the prefs pointed to by a4)
;d0.b will mirror CTR, d1.b will mirror FMR
;Returns error code in d0 - zero if successful.
;uses d1,d3,d5,a0
InitDACIA:
PUTDEBUG 30,<'%s/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.
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d3,-(sp)
PUTDEBUG 150,<'%s/InitDACIA: %ldbps requested.'>
addq.l #4,sp
endc
swap d3
tst.w d3
bne InvParm
swap d3
;Test for (and allow) zero baud -- this makes Handshake happy
;(Not a bug in Handshake.) Old RKMs mentioned this behavior, but the
;latest RKMs do not. Argh!
tst.l d3
beq.b SkipBAUD
lea (baudtable,pc),a0
moveq #15,d5
baudloop: cmp.w (a0)+,d3
dbeq d5,baudloop
bne InvParm
not.b d5
and.b #$0F,d5
or.b d5,d0
ifne INFO_LEVEL
clr.l -(sp)
move.b d0,(3,sp)
PUTDEBUG 30,<'%s/InitDACIA: Baud rate = %lx'>
addq.l #4,sp
endc
SkipBAUD:
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)
;set data bits per character
move.b (prefs_READLEN,a4),d3
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
and.b #$80,d1 ;bug fix (clear before OR)
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:
ifne INFO_LEVEL
clr.l -(sp)
move.b d1,(3,sp)
PUTDEBUG 30,<'%s/InitDACIA: FMR = %lx'>
addq.l #4,sp
endc
move.b d1,(frstate,a3)
move.b d1,(FMR,a5)
moveq #0,d0
movem.l (sp)+,d1/d3/d5/a0
PUTDEBUG 30,<'%s/InitDACIA: Finished!'>
rts
InvParm: PUTDEBUG 30,<'%s/InitDACIA: Invalid Parm!'>
moveq #-1,d0
movem.l (sp)+,d1/d3/d5/a0
rts
;****** end of InitDACIA ******
;(this is a subroutine for InitUnit)
;This routine sets up the two tasks (one for reading, one for writing).
;Returns zero in d0.l if an error occured else a ptr to the unit.
;
;uses d1-d4 and a0-a5
InitTask:
PUTDEBUG 30,<'%s/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
bsr.b InitTaskStruct
move.l a3,d0 ;Mark us as ready to go
PUTDEBUG 30,<'%s/InitTask: ok'>
;Return zero in d0.l if an error occured, else a ptr to the unit
movem.l (sp)+,d1-d4/a0-a5
rts
;Start up the unit task. We do a trick here --we set his message port to
;PA_IGNORE until the new task has a change to set it up. We cannot go to
;sleep here: it would be very nasty if someone else tried to open the unit
;(exec's OpenDevice has done a Forbid() for us --we depend on this to become
;single threaded).
;
;Enter with:
;a0 - ptr to low end of stack
;a1 - ptr to tcb (task control block)
;a3 - unit pointer
;a5 - starting address of task
;a2 - ptr to a (uninitialized) message port
;
;uses a0-a3 and d0,d1
InitTaskStruct:
movem.l d0/d1/a0-a3,-(sp)
move.l a0,(TC_SPLOWER,a1) ;Initialize the stack information
lea (MYPROCSTACKSIZE,a0),a0 ;High end of stack
move.l a0,(TC_SPUPPER,a1)
move.l a3,-(a0) ;Argument - unit ptr (send on stack)
move.l a0,(TC_SPREG,a1)
move.l a1,(MP_SIGTASK,a2)
ifge INFO_LEVEL-30
move.l a1,-(sp)
move.l a3,-(sp)
PUTDEBUG 30,<'%s/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
move.w #-1,a3 ;generate address error
;if task ever "returns" (we RemTask() it
;to get rid of it...)
moveq #0,d0
PUTDEBUG 30,<'%s/About to add task'>
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS AddTask
movea.l (sp)+,a6
movem.l (sp)+,d0/d1/a0-a3
rts
;Get rid of the unit's tasks. We know this is safe because the unit has an
;open count of zero, so it is 'guaranteed' not in use.
;
;Kill the two tasks
; ( a3:unitptr, a6:deviceptr )
;
;uses a0,a1 and d0-d2
KillTask:
movem.l a0/a1/d0-d2,-(sp)
lea (mdu_rtcb,a3),a1
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS RemTask
movea.l (sp)+,a6
lea (mdu_wtcb,a3),a1
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS RemTask
movea.l (sp)+,a6
moveq #0,d2
move.b (mdu_UnitNum,a3),d2 ;Save the unit number
bsr FreeUnit ;Free the unit structure
lsl.l #2,d2
clr.l (md_Units,a6,d2.l) ;Clear out the unit vector in the device
movem.l (sp)+,a0/a1/d0-d2
rts
; ( a3:unitptr, a6:deviceptr )
;uses a0,a1,d0,d1
FreeUnit:
movem.l a0/a1/d0/d1,-(sp)
movea.l a3,a1
move.l #MyDevUnit_Sizeof,d0
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS FreeMem
movea.l (sp)+,a6
movem.l (sp)+,a0/a1/d0/d1
rts
; ( a3:unitptr, a6:deviceptr )
;
;uses a0,a1,a5,d0-d2
ExpungeUnit:
movem.l a0/a1/a5/d0-d2,-(sp)
PUTDEBUG 10,<'%s/ExpungeUnit: called'>
movea.l (daciabase,a3),a5
move.b #$7f,(IER,a5) ;shut off all interrupts
move.b #$83,(FMR,a5) ;Deassert DTR* and RTS*
bsr FreeResources
lea (mdu_rtcb,a3),a1
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS RemTask
movea.l (sp)+,a6
lea (mdu_wtcb,a3),a1
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS RemTask
movea.l (sp)+,a6
moveq #0,d2
move.b (mdu_UnitNum,a3),d2 ;Save the unit number
bsr FreeUnit ;Free the unit structure.
bclr d2,(ScoreBoard,a6)
bsr NewVector
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...
;
;Enter with unit ptr in a3 and device ptr in a6
;
;uses a0,a1,a4,d0,d1
FreeResources:
PUTDEBUG 10,<'%s/FreeResources: called'>
movem.l a0/a1/a4/d0/d1,-(sp)
movea.l (StartBuf,a3),a1 ;Free input buffer memory
movea.l (mdu_prefs,a3),a4
move.l (prefs_RBUFLEN,a4),d0
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS FreeMem
movea.l (sp)+,a6
lea (timeriorequest,a3),a1 ;Close timer device
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS CloseDevice
movea.l (sp)+,a6
movem.l (sp)+,a0/a1/a4/d0/d1
PUTDEBUG 10,<'%s/FreeResources: finished!'>
rts
;Here begins the device functions
;
;Cmdtable is used to look up the address of a routine that will
;implement the device command.
cmdtable:
dc.w Invalid-cmdtable ;0 CMD_INVALID
dc.w MyReset-cmdtable ;1 CMD_RESET
dc.w Read-cmdtable ;2 CMD_READ
dc.w Write-cmdtable ;3 CMD_WRITE
dc.w Invalid-cmdtable ;4 CMD_UPDATE (update has no meaning here)
dc.w MyClear-cmdtable ;5 CMD_CLEAR
dc.w MyStop-cmdtable ;6 CMD_STOP
dc.w Start-cmdtable ;7 CMD_START
dc.w Flush-cmdtable ;8 CMD_FLUSH
dc.w Query-cmdtable ;9 SDCMD_QUERY
dc.w Break-cmdtable ;10 SDCMD_BREAK
dc.w SetParams-cmdtable ;11 SDCMD_SETPARAMS
dc.w Invalid-cmdtable ;12 not used
dc.w Invalid-cmdtable ;13 not used
dc.w Invalid-cmdtable ;14 not used
dc.w Invalid-cmdtable ;15 not used
dc.w SetControlLines-cmdtable ;16 - ASDG extension
;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.
;
;Some IO requests do not need single threading. These can be performed
;immediatley.
;The immediate commands are Invalid, Reset, Stop, Start, Flush, Clear,
;Query, SetParams, and SetControlLines.
;
;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 looks at IO_COMMAND to determine which task
;to use. Break and Write requests go to the write
;task. Read requests (only) go to the read task.
;All other requests are performed immediately, i.e. within the caller's
;context.
;
;This routine uses a3,a4,a5 (and a0/a1/d0/d1, but those do not need to be
;preserved).
; ( iob: a1, device:a6 )
;************************** Start of BeginIO ***************************
BeginIO:
movem.l a3-a5,-(sp)
ifge INFO_LEVEL-1
bchg.b #1,($bfe001).l ;Blink the power LED
endc
ifge INFO_LEVEL-3
clr.l -(sp)
move.w (IO_COMMAND,a1),(2,sp) ;Get entire word
PUTDEBUG 3,<'%s/BeginIO -- %ld'>
addq.l #4,sp
endc
move.w (IO_COMMAND,a1),d0
cmp.w #16,d0 ;command in range?
bhi .NoCmd ;no, reject it.
;Do some initial set-up work
andi.b #$f,(IO_FLAGS,a1) ;clear upper nibble
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
movea.l (mdu_prefs,a3),a4
movea.l (daciabase,a3),a5
clr.b (IO_ERROR,a1) ;No error so far
cmp.w #CMD_READ,d0
bne .NotRead
;If the task is busy reading, don't read in the caller's context!
tst.l (ReadRequestPtr,a3)
bne.b .ForgetIt
lea (MP_MSGLIST,a3),a0 ;Read port - anything there?
movea.l (a0),a0
tst.l (a0)
bne.b .ForgetIt
;Check whether there are enough characters in the buffer to do the read
;immediately.
btst #SERB_RAD_BOOGIE,(prefs_SERFLAGS,a4) ;Check for RAD_BOOGIE
beq.b .ForgetIt
bsr GetBytesInReadBuf
cmp.l (IO_LENGTH,a1),d0
blo.b .ForgetIt
bset #UNITB_READIMMEDIATE,(UNIT_FLAGS,a3)
bne.b .ForgetIt
bsr ReadImmediate
bclr #UNITB_READIMMEDIATE,(UNIT_FLAGS,a3)
bra .ReplyEnd
.ForgetIt:
;Send message to read task
move.l a3,a0
bra .SendMessage
.NotRead:
cmp.w #CMD_WRITE,d0
bne .NotWrite
moveq #1,d0
cmp.l (IO_LENGTH,a1),d0
bne .QueueForWriteTask
;If the write task is busy writing, don't write in the caller's context!
tst.l (WriteRequestPtr,a3)
bne .QueueForWriteTask
lea (mdu_wport+MP_MSGLIST,a3),a0 ;Write port - anything there?
movea.l (a0),a0
tst.l (a0)
bne .QueueForWriteTask
btst #SERB_7WIRE,(prefs_SERFLAGS,a4)
bne.b .Handshake
btst #CSRB_CTSL,(CSR,a5)
bne .QueueForWriteTask
.Handshake:
tst.b (xstate,a3)
beq .QueueForWriteTask
Disable
btst #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
beq.b .NeedInt ;Enable, then QueueForWriteTask
move.l (IO_DATA,a1),a0
move.b (a0),(TDR,a5)
bclr #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
Enable
bclr #UNITB_BREAKSENT,(UNIT_FLAGS,a3) ;for Query
move.l d0,(IO_ACTUAL,a1)
bra.b .ReplyEnd
.NotWrite:
cmp.w #SDCMD_BREAK,d0
beq.b .QueueForWriteTask ;Handle just like a write request
;execute immediately
lea (cmdtable,pc),a0
add.w d0,d0
add.w (a0,d0.w),a0
jsr (a0)
;If the quick bit is set then we don't need to reply
.ReplyEnd:
btst #IOB_QUICK,(IO_FLAGS,a1)
bne.b .End
push a6
move.l (4).w,a6
SYS ReplyMsg
pop a6
.End: movem.l (sp)+,a3-a5
rts
.NeedInt:
Enable
.QueueForWriteTask:
;Send message to write task
lea (mdu_wport,a3),a0
.SendMessage:
;Send the IO request as a "message" to either the read or write task
;Enter with port address in A0 and IORequest in A1
bclr #IOB_QUICK,(IO_FLAGS,a1) ;We did NOT complete this quickly
ifge INFO_LEVEL-250
move.l a1,-(sp)
move.l a0,-(sp)
PUTDEBUG 250,<'%s/PutMsg: Port=%lx Message=%lx'>
addq.l #8,sp
endc
push a6
movea.l (md_SysLib,a6),a6
SYS PutMsg ;Port=a0, Message=a1
pop a6
bra .End
.NoCmd:
PUTDEBUG 200,<'%s/BeginIO_NoCmd!'>
move.b #IOERR_NOCMD,(IO_ERROR,a1)
bra .ReplyEnd
;************************* End of BeginIO ****************************
;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 *****************************
Read:
movem.l d0-d6/a0/a2,-(sp)
;Try to do a quick read
btst #SERB_RAD_BOOGIE,(prefs_SERFLAGS,a4) ;Check for RAD_BOOGIE
beq.b .NoBoogie
bsr GetBytesInReadBuf
cmp.l (IO_LENGTH,a1),d0
blo.b .NotEnoughDataInBuf
bsr ReadImmediate
bra .FinalEndRead
.NoBoogie:
.NotEnoughDataInBuf:
moveq #0,d5 ;IO_ACTUAL
move.l (IO_LENGTH,a1),d6
movea.l (IO_DATA,a1),a2
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d6,-(sp)
PUTDEBUG 150,<'%s/Read entered -- %ld bytes requested.'>
addq.l #4,sp
endc
bra .ReadEntry
.ReadLoop:
PUTDEBUG 150,<'%s/Read: Waiting for signal from interrupt code.'>
move.l (readsig,a3),d0
add.l (readabortsig,a3),d0
bset #MDUB_WaitingForChar,(MDU_FLAGS,a3)
EXEC Wait ;Wait for one or more bytes to come in
and.l (readabortsig,a3),d0
bne .ReadAbort
.NoErrorAtAll:
PUTDEBUG 150,<'%s/Read: One or more bytes came in.'>
.ReadEntry:
bsr GetBytesInReadBuf
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
PUTDEBUG 150,<'%s/Read: There are %ld bytes in the buffer.'>
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 ;Dump d0 bytes of the read buffer into the user's buffer
tst.l d0
beq .ReadLoop
bra.b .EndRead ;Go if TermArray caused early exit
;The number of bytes in the read buffer equals or exceeds the number of
;bytes the user wants.
.AllDone:
PUTDEBUG 150,<'%s/Read: All done.'>
move.l d6,d0
bsr DumpReadBuf ;Dump d0 bytes of the read buffer into the user's buffer
.EndRead: PUTDEBUG 150,<'%s/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)
.FinalEndRead:
movem.l (sp)+,d0-d6/a0/a2
rts ;Done with read
;Something exceptional happened. An informative error code should be
;returned. first, check for error conditions... Note that the serial.device
;standard does not provide a way to inform the caller of simultaneous error
;conditions.
.ReadAbort:
PUTDEBUG 150,<'%s/readabort: Something exceptional happened.'>
Disable
move.b (ISRcopy,a3),d2
move.b (CSRcopy,a3),d3
clr.b (ISRcopy,a3)
clr.b (CSRcopy,a3)
Enable
btst #ISRB_PAR,d2
beq.b .notpar
move.b #SerErr_ParityErr,(IO_ERROR,a1) ;parity error
bra .EndRead
.notpar: btst #1,d2 ;Check for frame err, overrun, & break
beq .EndRead ;We infer that an abort has been issued
;This is a check for framing error....it might cause problems for
;some auto-baud algorithms, so it's commented out for this release.
; 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
;****** Subroutines for the read routine ******
GetBytesInReadBuf:
;Return with the number of bytes in the read buffer in d0
move.l d1,-(sp)
movem.l (HeadLong,a3),d0/d1 ;load HeadLong/TailLong
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
PUTDEBUG 150,<'%s/GetBytesInReadBuf: HeadLong=%ld'>
addq.l #4,sp
endc
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d1,-(sp)
PUTDEBUG 150,<'%s/GetBytesInReadBuf: TailLong=%ld'>
addq.l #4,sp
endc
sub.l d0,d1
bpl.b .skipadd
add.l #65536,d1
.skipadd:
move.l d1,d0
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: d0,d5,d6,a2
;and updates the following: d5,a2
;Returns with D0=0 if all is OK, else a TermArray match was found.
;This is a subroutine used only by Read
DumpReadBuf:
movem.l d1/d6/d7/a0/a6,-(sp)
move.l (HeadLong,a3),d1
add.w d0,(Head,a3)
move.l (StartBuf,a3),a0
btst #SERB_RAD_BOOGIE,(prefs_SERFLAGS,a4)
bne.b .Loop
btst #SERB_EOFMODE,(IO_SERFLAGS,a1)
beq.b .XLoop
.EOFDump:
moveq #7,d7
lea (prefs_TERMARRAY,a4),a6
move.b (a0,d1.l),d6 ;index MUST be long (sign extended)!
btst #SERB_XDISABLED,(prefs_SERFLAGS,a4)
bne.b .Skip
bsr xcode
.Skip: cmp.b (a6)+,d6
dbcc d7,.Skip
beq.b .TermRead
move.b d6,(a2)+
addq.l #1,d5
addq.w #1,d1
subq.l #1,d0
bne.b .EOFDump
bra.b .End
.Loop:
move.b (a0,d1.l),(a2)+ ;index MUST be long (sign extended)!
addq.l #1,d5 ;add to IO_ACTUAL
addq.w #1,d1 ;increment 16-bit head ptr
subq.l #1,d0
bne.b .Loop
bra.b .End
.XLoop:
move.b (a0,d1.l),d6 ;index MUST be long (sign extended)!
bsr.b xcode
move.b d6,(a2)+
addq.l #1,d5 ;add to IO_ACTUAL
addq.w #1,d1 ;increment 16-bit head ptr
subq.l #1,d0
bne.b .XLoop
bra.b .End
.TermRead:
moveq #-1,d0
bra.b .End1
.End:
moveq #0,d0
.End1:
movem.l (sp)+,d1/d6/d7/a0/a6
rts
xcode:
;Check for xon/xoff
;Enter with current read character in d6.b
ifne INFO_LEVEL
move.l (prefs_CTLCHAR,a4),-(sp)
PUTDEBUG 5,<'%s/XCODE: prefs_CTLCHAR = %lx'>
addq.l #4,sp
endc
ifne INFO_LEVEL
clr.l -(sp)
move.b d6,(3,sp)
PUTDEBUG 5,<'%s/XCODE: Current char = %lx'>
addq.l #4,sp
endc
cmp.b (prefs_CTLCHAR,a4),d6 ;Xon check
beq.b .XON
cmp.b (prefs_CTLCHAR+1,a4),d6 ;Xoff check
beq.b .XOFF
rts
.XON: PUTDEBUG 5,<'%s/XCODE: XON!'>
tst.b (xstate,a3)
bne.b .End ;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 (mdu_SysLib,a3),a6
SYS Signal
movem.l (sp)+,d0/d1/a0/a1/a6
.End: rts
.XOFF: PUTDEBUG 5,<'%s/XCODE: XOFF!'>
clr.b (xstate,a3)
rts
ReadImmediate:
;Fast read routine
;Requires boogie mode to be set and for there to be a sufficient number
;of bytes in the buffer to completely satisfy the request.
;Uses d0,d1,d2,d4,a0,a1,a2,a4,a6
movem.l d0-d2/d4/a0-a2/a4/a6,-(sp)
push a1
move.l (HeadLong,a3),d2
move.l (IO_LENGTH,a1),d4
add.w d4,(Head,a3)
move.l (IO_DATA,a1),a4
move.l (StartBuf,a3),a2
move.l (mdu_SysLib,a3),a6
move.l #65536,d0
sub.l d2,d0
cmp.l d0,d4
bls.b .UseIOLength
;Need 2 copy operations
sub.l d0,d4
move.l a4,a1
add.l d0,a4
lea (a2,d2.l),a0
SYS CopyMem
move.l d4,d0
move.l a4,a1
move.l a2,a0
SYS CopyMem
bra.b .ReadQuickEnd
.UseIOLength:
move.l d4,d0
move.l a4,a1
lea (a2,d2.l),a0
SYS CopyMem
.ReadQuickEnd:
pop a1
move.l (IO_LENGTH,a1),(IO_ACTUAL,a1)
movem.l (sp)+,d0-d2/d4/a0-a2/a4/a6
rts
;*************************** Write ******************************
Write:
comment |
Potential problem: If a TDRE interrupt happens, and just at that instant
(before the interrupt routine has time to kick into action) CTS is
deasserted, the interrupt routine will miss the TDRE condition and the
write routine will hang. This is unlikely, but possible. V2.10 adds
a time-out feature to avoid this if it ever happens in real life.
Testing the effectiveness of this solution is difficult, because I am
solving a problem that has, to my knowledge, never occured. (hehe)
|
movem.l d0-d5/a0-a2,-(sp)
push a1
bclr #UNITB_BREAKSENT,(UNIT_FLAGS,a3) ;for Query
btst #SERB_7WIRE,(prefs_SERFLAGS,a4)
bne.b .Handshake
btst #CSRB_CTSL,(CSR,a5)
bne .NotConnected
.Handshake:
movea.l (IO_DATA,a1),a2
move.l (IO_LENGTH,a1),d2
beq .End
bpl.b .CheckX
move.l a2,a0 ;handle zero-terminated writes
.L1: tst.b (a0)+
bne.b .L1
sub.l a2,a0
move.l a0,d2
.CheckX:
tst.b (xstate,a3)
bne .DoIt ;go if no XOFF
PUTDEBUG 5,<'%s/Transmit: Waiting for XON!'>
move.l (xonsig,a3),d0
add.l (writeabortsig,a3),d0
EXEC Wait ;Wait for an x-on signal before sending
and.l (writeabortsig,a3),d0
bne .Abort
PUTDEBUG 5,<'%s/Transmit: Received XON!'>
bra .CheckX
.DoIt:
move.l d2,d4
Disable ;quite neccessary!
btst #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
beq.b .NeedInt
bclr #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
move.b (a2)+,(TDR,a5) ;If set, we can load up the transmit reg immediately
subq.l #1,d4
beq .DoneWriting ;does an Enable
.NeedInt:
move.l a2,(WriteBufferPtr,a3)
move.l d4,(WriteCount,a3) ;GO!!!
Enable
lea (timeriorequest,a3),a1
movea.l (MN_REPLYPORT,a1),a0
moveq #0,d0
move.b (MP_SIGBIT,a0),d0
moveq #0,d3
bset d0,d3
.TopWaitBlock:
moveq #0,d5 ;clear danger flag
.WaitBlock:
move.w #TR_ADDREQUEST,(IO_COMMAND,a1)
clr.l (TV_SECS+IO_SIZE,a1)
move.l #100000,(TV_MICRO+IO_SIZE,a1) ;100ms
EXEC SendIO
move.l d3,d0 ;timer signal
add.l (writeabortsig,a3),d0
add.l (tdresig,a3),d0
;Wait for the block write to finish
EXEC Wait ;Wait for write to finish
move.l d0,d1
and.l (tdresig,a3),d1
beq.b .NotTDRE
EXEC AbortIO ;kill timer request
EXEC WaitIO
; PUTDEBUG 150,<'%s/Transmit: Write block finished.'>
.End:
moveq #0,d0 ;value
move.l d3,d1 ;mask
EXEC SetSignal ;clear timer signal just in case
pop a1
move.l d2,(IO_ACTUAL,a1)
movem.l (sp)+,d0-d5/a0-a2
rts
.NotTDRE:
move.l d0,d1
and.l (writeabortsig,a3),d1
beq.b .CheckOut
;Abort signal
EXEC AbortIO ;kill timer request
EXEC WaitIO
bra .Abort
.CheckOut:
;If we end up here then a 100ms timeout has occured - so check a few
;things...
EXEC WaitIO ;remove the message from the replyport
move.l (WriteCount,a3),d0
cmp.l d4,d0 ;compare with previous WriteCount value
beq.b .Equal
move.l d0,d4
bra .TopWaitBlock
.Equal:
btst #CSRB_CTSL,(CSR,a5)
bne .TopWaitBlock ;go if not asserted
tst.l d5
bne.b .Danger
moveq #1,d5 ;set danger flag
bra .WaitBlock
.Danger:
;If we get here we know:
;1. Nothing has been outputted (no TDRE) in the last 100-200+ ms, and
;2. CTS is asserted (presumably at least 100ms -- we assume this)
;So, we assume that we got unlucky and missed a TDRE, and force a byte out:
Disable
bclr #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
move.l (WriteBufferPtr,a3),a0
move.b (a0)+,(TDR,a5)
move.l a0,(WriteBufferPtr,a3)
subq.l #1,(WriteCount,a3)
beq.b .DoneWriting
Enable
bra .TopWaitBlock
.DoneWriting:
Enable
bra .End
.NotConnected:
move.b #SerErr_LineErr,(IO_ERROR,a1)
bra .End
.Abort:
sub.l (WriteCount,a3),d2
clr.l (WriteCount,a3)
bra .End
;******* end of write routine *******
Break:
movem.l d0-d2/a0/a1,-(sp)
PUTDEBUG 5,<'%s/Break: called'>
move.b #2,(ACR,a5) ;start break
bset #UNITB_BREAKSENT,(UNIT_FLAGS,a3)
lea (timeriorequest,a3),a1
move.w #TR_ADDREQUEST,(IO_COMMAND,a1)
clr.l (TV_SECS+IO_SIZE,a1)
move.l (prefs_BRKTIME,a4),d0
ori.b #$FF,d0 ;To avoid the V33/V34 timer bug
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
PUTDEBUG 5,<'%s/Break: TV_MICRO=%ld'>
addq.l #4,sp
endc
move.l d0,(TV_MICRO+IO_SIZE,a1)
movea.l (MN_REPLYPORT,a1),a0
moveq #0,d2
move.b (MP_SIGBIT,a0),d2
EXEC SendIO
moveq #0,d0
bset d2,d0
add.l (writeabortsig,a3),d0
PUTDEBUG 5,<'%s/Break: Waiting...'>
EXEC Wait
and.l (writeabortsig,a3),d0
beq.b .BreakOK
PUTDEBUG 5,<'%s/Break: Aborted!'>
EXEC AbortIO ;The break was aborted. Clean up.
.BreakOK:
EXEC WaitIO
moveq #0,d0 ;value
moveq #0,d1
bset d2,d1 ;mask
EXEC SetSignal ;clear timer signal bit just in case
move.b #0,(ACR,a5) ;stop the break
PUTDEBUG 5,<'%s/Break: Finished!'>
movem.l (sp)+,d0-d2/a0/a1
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.
;
;If sucessful, AbortIO returns IOERR_ABORTED in IO_ERROR and zero in D0
AbortIO:
PUTDEBUG 5,<'%s/AbortIO: called'>
Forbid
movem.l d1/a0/a1/a3,-(sp)
movea.l (IO_UNIT,a1),a3
move.b #IOERR_ABORTED,(IO_ERROR,a1) ;We always say we succeed(ed)
cmpi.b #NT_REPLYMSG,(LN_TYPE,a1) ;Already complete?
beq.b .End
btst #IOB_QUICK,(IO_FLAGS,a1)
bne.b .End
;Check to see whether or not the IORequest is being processed
cmpi.w #CMD_READ,(IO_COMMAND,a1)
bne.b .NotRead
cmp.l (ReadRequestPtr,a3),a1
bne.b .NotActive
move.l (readabortsig,a3),d0
lea (mdu_rtcb,a3),a1
.Signal: EXEC Signal
bra.b .End
.NotRead:
cmpi.w #CMD_WRITE,(IO_COMMAND,a1)
bne.b .NotWrite
.Write: cmp.l (WriteRequestPtr,a3),a1
bne.b .NotActive
move.l (writeabortsig,a3),d0
lea (mdu_wtcb,a3),a1
bra.b .Signal
.NotWrite:
cmpi.w #SDCMD_BREAK,(IO_COMMAND,a1)
beq.b .Write
bra.b .End
.NotActive:
;Set ignore bit in the IORequest
bset #ioflagsB_Ignore,(IO_FLAGS,a1)
.End: Permit
movem.l (sp)+,d1/a0/a1/a3
moveq #0,d0 ;error code (always successful)
PUTDEBUG 5,<'%s/AbortIO: Finished!'>
rts
;**************** End of AbortIO *****************
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:
movem.l d0/d1,-(sp)
moveq #0,d0
moveq #0,d1
movem.l d0/d1,(HeadLong,a3)
movem.l (sp)+,d0/d1
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:
movem.l d0/d7/a1,-(sp)
PUTDEBUG 30,<'%s/MyReset: called'>
Forbid
move.b (IERstate,a3),d7
clr.b (IERstate,a3)
move.b #$7f,(IER,a5) ;Disable ACIA interrupts
bsr Flush ;Flush pending requests
;Abort current IO, if any IO is indeed occuring
move.l a1,-(sp)
movea.l (WriteRequestPtr,a3),a1
move.l a1,d0
beq.b .WriteNotActive
bsr AbortIO
.WriteNotActive:
movea.l (ReadRequestPtr,a3),a1
move.l a1,d0
beq.b .NothingActive
bsr AbortIO
.NothingActive:
movea.l (sp)+,a1
bsr SetDefaultPrefs
bsr CopyPrefs
bsr FreeResources
bsr SetUpUnit
tst.l d0 ;Check for a possible error condition
bmi.b .OutOfMem
subq.l #1,d0
beq.b .TimerError
subq.l #1,d0
beq.b .ParamError
move.b d7,(IERstate,a3) ;Enable DACIA interrupts again
bset #7,d7
move.b d7,(IER,a5)
Permit
clr.l (IO_ACTUAL,a1)
PUTDEBUG 30,<'%s/MyReset: Finished!'>
.ResetEnd:
movem.l (sp)+,d0/d7/a1
rts
.ParamError:
move.b #SerErr_InvParam,(IO_ERROR,a1)
bra.b .MyResetFailed
.TimerError:
move.b #SerErr_TimerErr,(IO_ERROR,a1)
bra.b .MyResetFailed
.OutOfMem:
move.b #SerErr_BufErr,(IO_ERROR,a1)
.MyResetFailed:
Permit
clr.l (IO_ACTUAL,a1)
PUTDEBUG 30,<'%s/MyReset: Error!'>
bra.b .ResetEnd
; 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:
movem.l d0-d1,-(sp)
; PUTDEBUG 30,<'%s/Query: called.'>
bsr GetBytesInReadBuf
move.l d0,(IO_ACTUAL,a1)
ifne INFO_LEVEL ;If any debugging enabled at all
move.l d0,-(sp)
; PUTDEBUG 30,<'%s/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 ;carrier detect
Q4: btst #5,d1
beq.b Q5
bset #4,d0
Q5: btst #UNITB_BREAKSENT,(UNIT_FLAGS,a3)
beq.b NB
bset #9,d0
NB: 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)
movem.l (sp)+,d0-d1
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:
movem.l d0/a0/a2,-(sp)
bclr #SERB_XDISABLED,(prefs_SERFLAGS,a4) ;enable
btst #SERB_XDISABLED,(IO_SERFLAGS,a1)
beq.b .NotDisabled
bset #SERB_XDISABLED,(prefs_SERFLAGS,a4)
st (xstate,a3) ;set to X-ON state
.NotDisabled:
;Now check to see whether the device is busy, i.e. any current or pending requests.
Forbid
tst.l (ReadRequestPtr,a3)
bne .DevBusy
tst.l (WriteRequestPtr,a3)
bne .DevBusy
lea (MP_MSGLIST,a3),a0 ;Read port - anything there?
movea.l (a0),a2
tst.l (a2)
bne.b .DevBusy
lea (mdu_wport+MP_MSGLIST,a3),a0 ;Write port - anything there?
movea.l (a0),a2
tst.l (a2)
bne.b .DevBusy
;Ok, the device is not busy. Set all params.
bsr SetPrefs ;First copy the data
move.l #65536,(prefs_RBUFLEN,a4)
;---> This section of code was supposed to increase compatibility, but
;---> it actually decreases it!! The autodocs fail me again....
;If boogie is set, it implies a few other things...(NOT!!!!)
; 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)
; bra.b .SkipNext
;.SkipBoogie:
;The boogie flag is not set...but maybe it should be...
;Note that this logic is depended on in the Read routine
btst #SERB_XDISABLED,(prefs_SERFLAGS,a4)
beq.b .nochance
btst #SERB_EOFMODE,(prefs_SERFLAGS,a4)
bne.b .nochance
bset #SERB_RAD_BOOGIE,(prefs_SERFLAGS,a4) ;Set RAD_BOOGIE !
.nochance:
.SkipNext:
bsr InitDACIA ;Then set up the chip
tst.l d0
bne.b .Invalid
.End: Permit
movem.l (sp)+,d0/a0/a2
rts
.Invalid: move.b #SerErr_InvParam,(IO_ERROR,a1)
bra.b .End
.DevBusy: move.b #SerErr_DevBusy,(IO_ERROR,a1)
bra.b .End
SetControlLines:
comment |
This routine is an extension specified by ASDG for their Dual Serial
Board. It allows application control of RTS and DTR. It works like this:
IO_COMMAND = 16
IO_OFFSET = mask
IO_LENGTH = value
Bit 0 = RTS
Bit 1 = DTR
|
movem.l d0-d2,-(sp)
Forbid
move.b (frstate,a3),d0
move.l (IO_LENGTH,a1),d1 ;state
move.l (IO_OFFSET,a1),d2 ;mask
and.b d2,d1
not.b d2
and.b d2,d0 ;clear
or.b d1,d0 ;set
move.b d0,(frstate,a3)
move.b d0,(FMR,a5)
Permit
movem.l (sp)+,d0-d2
rts
;The Stop command stop all future io requests from being processed until a
;Start command is received. The Stop command is NOT stackable: e.g. no matter
;how many stops have been issued, it only takes one Start to restart
;processing.
MyStop:
PUTDEBUG 30,<'%s/MyStop: called'>
bset #MDUB_STOPPED,(MDU_FLAGS,a3)
rts
Start:
;[A3=unit A6=device]
movem.l d0-d1/a0-a2,-(sp)
PUTDEBUG 30,<'%s/Start: called'>
movea.l a1,a2
;Turn processing back on
bclr #MDUB_STOPPED,(MDU_FLAGS,a3)
;Kick the tasks to start them moving
move.b (MP_SIGBIT,a3),d1 ;First the read task...
moveq #0,d0
bset d1,d0 ;Prepared signal mask
movea.l (MP_SIGTASK,a3),a1 ;FIXED: marco-task to signal
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS Signal ;FIXED: marco-a6 not a3
movea.l (sp)+,a6
movea.l a2,a1 ;Then the write task
lea (mdu_wport,a3),a0
move.b (MP_SIGBIT,a0),d1
moveq #0,d0
bset d1,d0 ;Prepared signal mask
movea.l (MP_SIGTASK,a0),a1 ;FIXED: marco-task to signal
move.l a6,-(sp)
movea.l (md_SysLib,a6),a6
SYS Signal ;FIXED: marco-a6 not a3
movea.l (sp)+,a6
PUTDEBUG 30,<'%s/Start: Finished!'>
movem.l (sp)+,d0-d1/a0-a2
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.
Flush:
movem.l d0-d1/a0/a1/a6,-(sp)
PUTDEBUG 30,<'%s/Flush: called'>
movea.l (md_SysLib,a6),a6
SYS Forbid
ReadFlush_Loop:
movea.l a3,a0
SYS GetMsg ;Steal messages from task's port
tst.l d0
beq.b WriteFlush_Loop
movea.l d0,a1
move.b #IOERR_ABORTED,(IO_ERROR,a1)
SYS ReplyMsg
bra.b ReadFlush_Loop
WriteFlush_Loop:
lea (mdu_wport,a3),a0
SYS GetMsg ;Steal messages from task's port
tst.l d0
beq.b Flush_End
movea.l d0,a1
move.b #IOERR_ABORTED,(IO_ERROR,a1)
SYS ReplyMsg
bra.b WriteFlush_Loop
Flush_End:
SYS Permit
PUTDEBUG 30,<'%s/Flush: Finished!'>
movem.l (sp)+,d0-d1/a0/a1/a6
rts
;Here begins the task related routines
;
; Register Usage
; ==============
; a2 -- device pointer
; a3 -- unit pointer
; a4 -- prefs pointer
; a5 -- hardware pointer
; a6 -- syslib pointer
;----------------------------------------------------------------------
;
;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...:
ReadTask_Begin:
PUTDEBUG 35,<'%s/ReadTask_Begin'>
movea.l (4).w,a6
;Grab the arguments passed down from our parent
movea.l (4,sp),a3 ;Unit pointer
move.l (mdu_prefs,a3),a4
movea.l (daciabase,a3),a5
lea (readsig,a3),a2
moveq #1,d6 ;Number of signals to allocate-1
ReadSigLoop:
moveq #-1,d0 ;-1 is any signal at all
SYS AllocSignal ;Allocate signals for I/O interrupts
moveq #0,d7 ;Convert bit number signal mask
bset d0,d7
move.l d7,(a2)+ ;Save in unit structure
dbra d6,ReadSigLoop
movea.l (mdu_Device,a3),a2 ;Point to device structure
moveq #-1,d0 ;-1 is any signal at all
SYS AllocSignal ;Allocate a signal
move.b d0,(MP_SIGBIT,a3)
move.b #PA_SIGNAL,(MP_FLAGS,a3) ;Make message port "live"
ifge INFO_LEVEL-40
move.l ($114,a6),-(sp)
move.l (mdu_Device,a3),-(sp)
move.l a3,-(sp)
move.l d0,-(sp)
PUTDEBUG 40,<'%s/ReadTask -- Signal=%ld, Unit=%lx Device=%lx Task=%lx'>
adda.l #4*4,sp
endc
;Enable read-related ACIA interrupts
ori.b #READINTMASK,(IERstate,a3)
move.b #READINT,(IER,a5)
bra ReadTask_NextMessage
;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
;Main loop: wait for a new message
ReadTask_PermitMainLoop:
Permit
ReadTask_MainLoop:
PUTDEBUG 75,<'%s/ReadTask ++Sleep'>
move.l a3,a0
SYS WaitPort
ifge INFO_LEVEL-5
bchg.b #1,($bfe001).l ;Blink the power LED
endc
PUTDEBUG 75,<'%s/ReadTask ++Wakeup'>
btst #MDUB_STOPPED,(MDU_FLAGS,a3) ;See if we are stopped
bne.b ReadTask_MainLoop ;Device is stopped, ignore messages
ReadTask_NextMessage:
movea.l a3,a0
Forbid
SYS GetMsg ;Get the next request
PUTDEBUG 1,<'%s/ReadTask GotMsg'>
tst.l d0
beq ReadTask_PermitMainLoop ;no message?
movea.l d0,a1 ;Do this request
exg a2,a6 ;Put device ptr in right place
btst #ioflagsB_Ignore,(IO_FLAGS,a1)
bne.b Readignorecmd
move.l a1,(ReadRequestPtr,a3)
Permit
bsr Read ;Do it!
clr.l (ReadRequestPtr,a3)
EXEC ReplyMsg
;No longer active - abort has stopped sending signals. Now we can
;(and should) clear the abort signal.
moveq #0,d0
move.l (readabortsig,a3),d1
EXEC SetSignal
bra.b Readconttl
Readignorecmd:
Permit
clr.l (IO_ACTUAL,a1)
EXEC ReplyMsg
Readconttl:
exg a2,a6 ;Get ExecBase back in a6
bra ReadTask_NextMessage
;**** End of read task code ****
;**** Beginning of write task code ****
; Register Usage
; ==============
; a2 -- device pointer
; a3 -- unit pointer
; a4 -- prefs pointer
; a5 -- hardware pointer
; a6 -- syslib pointer
WriteTask_Begin:
PUTDEBUG 35,<'%s/WriteTask_Begin'>
movea.l (SysBase).w,a6
;Grab the arguments passed down from our parent
movea.l (4,sp),a3 ;Unit pointer
move.l (mdu_prefs,a3),a4
movea.l (daciabase,a3),a5
lea (tdresig,a3),a2
moveq #2,d6 ;Number of signals to allocate-1
WriteSigLoop:
moveq #-1,d0 ;-1 is any signal at all
SYS AllocSignal ;Allocate signals for I/O interrupts
moveq #0,d7 ;Convert bit number signal mask
bset d0,d7
move.l d7,(a2)+ ;Save in unit structure
dbra d6,WriteSigLoop
;Allocate a signal for the timer message port
moveq #-1,d0 ;-1 is any signal at all
SYS AllocSignal
lea (timerport,a3),a2
move.b d0,(MP_SIGBIT,a2)
move.l a2,(timeriorequest+MN_REPLYPORT,a3)
suba.l a1,a1
SYS FindTask
move.l d0,(MP_SIGTASK,a2)
move.b #PA_SIGNAL,(MP_FLAGS,a2) ;Make message port "live"
movea.l (mdu_Device,a3),a2 ;Point to device structure
;Allocate a signal for the cmd message port
moveq #-1,d0 ;-1 is any signal at all
SYS AllocSignal
move.b d0,(mdu_wport+MP_SIGBIT,a3)
move.b #PA_SIGNAL,(mdu_wport+MP_FLAGS,a3) ;Make message port "live"
ifge INFO_LEVEL-40
move.l (ThisTask,a6),-(sp)
move.l a5,-(sp)
move.l a3,-(sp)
move.l d0,-(sp)
PUTDEBUG 40,<'%s/WriteTask -- Signal=%ld, Unit=%lx Device=%lx Task=%lx'>
adda.l #4*4,sp
endc
;Enable write-related ACIA interrupts
ori.b #WRITEINTMASK,(IERstate,a3)
move.b #WRITEINT,(IER,a5)
bra WriteTask_NextMessage
;Main loop: wait for a new message
WriteTask_PermitMainLoop:
Permit
WriteTask_MainLoop:
PUTDEBUG 75,<'%s/WriteTask ++Sleep'>
lea (mdu_wport,a3),a0
SYS WaitPort
ifge INFO_LEVEL-5
bchg.b #1,($bfe001).l ;Blink the power LED
endc
PUTDEBUG 75,<'%s/WriteTask ++Wakeup'>
btst #MDUB_STOPPED,(MDU_FLAGS,a3) ;See if we are stopped
bne ReadTask_MainLoop ;Device is stopped, ignore messages
WriteTask_NextMessage:
lea (mdu_wport,a3),a0
Forbid
SYS GetMsg ;Get the next request
PUTDEBUG 1,<'%s/WriteTask GotMsg'>
tst.l d0
beq WriteTask_PermitMainLoop ;No message?
movea.l d0,a1 ;Do this request
exg a2,a6 ;Put device ptr in right place
btst #ioflagsB_Ignore,(IO_FLAGS,a1)
bne.b Writeignorecmd
move.l a1,(WriteRequestPtr,a3)
Permit
cmp.w #CMD_WRITE,(IO_COMMAND,a1)
bne.b .NotWrite
bsr Write
bra.b .Continue
.NotWrite:
bsr Break
.Continue:
clr.l (WriteRequestPtr,a3)
EXEC ReplyMsg
;No longer active - abort has stopped sending signals. Now we can
;(and should) clear the abort signal.
moveq #0,d0
move.l (writeabortsig,a3),d1
EXEC SetSignal
bra.b Writeconttl
Writeignorecmd:
Permit
clr.l (IO_ACTUAL,a1)
EXEC ReplyMsg
Writeconttl:
exg a2,a6 ; get syslib back in a6
bra WriteTask_NextMessage
;********** end of write task code ***********
;Initialize the device
mdu_Init:
;Initialize read task message port/tcb
INITBYTE MP_FLAGS,PA_IGNORE ;Unit starts with read message port
INITBYTE LN_TYPE,NT_MSGPORT
INITLONG LN_NAME,myName
INITLONG mdu_rtcb+LN_NAME,myName
INITBYTE mdu_rtcb+LN_TYPE,NT_TASK
INITBYTE mdu_rtcb+LN_PRI,127
;Initialize write task message port/tcb
INITBYTE mdu_wport+MP_FLAGS,PA_IGNORE ;Write message port
INITBYTE mdu_wport+LN_TYPE,NT_MSGPORT
INITLONG mdu_wport+LN_NAME,myName
INITLONG mdu_wtcb+LN_NAME,myName
INITBYTE mdu_wtcb+LN_TYPE,NT_TASK
INITBYTE mdu_wtcb+LN_PRI,126
;Initialize timer message port
INITBYTE timerport+MP_FLAGS,PA_IGNORE ;Timer message port
INITBYTE timerport+LN_TYPE,NT_MSGPORT
INITLONG timerport+LN_NAME,myName
dc.w 0
;******************** Interrupt code *********************************
;Notes:
;In this version of the driver we use a single interrupt routine for all
;units, and that routine bypasses the usual Exec conventions. Normally
;this would be bad, but the need for speed certainly warrents it in this
;case.
;This is the interrupt routine. It serves three 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 an exceptional condition and signal read task
;uses d0,d7,a0,a3,a5
DoInt macro
movem.l d0/d7/a0/a3/a5,-(sp)
ifge \1-1
move.l (Unit3,pc),a3
move.l (daciabase,a3),a5
move.b (ISR,a5),d7
and.b (IERstate,a3),d7 ;Quick check (note that this masking is very
;important)
bne FastInt ;Service it
endc
ifge \2-1
move.l (_Unit2,pc),a3
move.l (daciabase,a3),a5
move.b (ISR,a5),d7
and.b (IERstate,a3),d7 ;Quick check (note that this masking is very
;important)
bne FastInt ;Service it
endc
ifge \3-1
move.l (Unit1,pc),a3
move.l (daciabase,a3),a5
move.b (ISR,a5),d7
and.b (IERstate,a3),d7 ;Quick check (note that this masking is very
;important)
bne FastInt ;Service it
endc
ifge \4-1
move.l (Unit0,pc),a3
move.l (daciabase,a3),a5
move.b (ISR,a5),d7
and.b (IERstate,a3),d7 ;Quick check (note that this masking is very
;important)
bne FastInt ;Service it
endc
movem.l (sp)+,d0/d7/a0/a3/a5
move.l (OldVec,pc),-(sp)
rts
endm
IntRoutines:
dc.l Int0000 ;dummy entry for 0
dc.l Int0001
dc.l Int0010
dc.l Int0011
dc.l Int0100
dc.l Int0101
dc.l Int0110
dc.l Int0111
dc.l Int1000
dc.l Int1001
dc.l Int1010
dc.l Int1011
dc.l Int1100
dc.l Int1101
dc.l Int1110
dc.l Int1111
;************* Start of fast interrupt routine **************
FastInt:
;****** NOTE/WARNING: Reading the ISR will cause the interrupt line
;****** to be deasserted, although the bits in the ISR (except for read
;****** error bits) will stay set ---- therefore simultaneous
;****** conditions MUST!!!! be checked for!!!!!! There is no
;****** second chance!
;Read error condition?
;(Note: Reading the RDR clears the error flags.)
move.b d7,d0
and.b #6,d0
bne.b ReadErr ;read errors?
btst #ISRB_RDRF,d7 ;Test Receive Data Buffer Full
beq.b DoTDRE
DoRDRF:
; PUTDEBUG 75,<'%s/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*).
move.l (TailLong,a3),d0 ;Read and store
move.l (StartBuf,a3),a0
move.b (RDR,a5),(a0,d0.l)
addq.w #1,(Tail,a3)
;Currently waiting for a character?
btst #MDUB_WaitingForChar,(MDU_FLAGS,a3)
bne.b SignalRead ;yes, signal read task
bset #MDUB_CharAvailable,(MDU_FLAGS,a3)
btst #ISRB_TDRE,d7
bne.b DoTDRE
;Return from int routine
movem.l (sp)+,d0/d7/a0/a3/a5
move.w #$2000,(_custom+intreq)
rte
DoTDRE:
;Continue with int routine...
;We can deduce that the interrupt was caused by TDRE
;Therefore we have something to write...
tst.l (WriteCount,a3)
beq.b SetNeedChar
bclr #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
move.l (WriteBufferPtr,a3),a0
move.b (a0)+,(TDR,a5)
move.l a0,(WriteBufferPtr,a3)
subq.l #1,(WriteCount,a3)
beq.b DoneWriting
EndInt: movem.l (sp)+,d0/d7/a0/a3/a5
move.w #$2000,(_custom+intreq)
rte
SetNeedChar:
bset #UNITB_NEEDCHAR,(UNIT_FLAGS,a3)
bra.b EndInt
ReadErr:
bra.b RealReadErr
SignalRead:
bclr #MDUB_WaitingForChar,(MDU_FLAGS,a3)
bset #MDUB_CharAvailable,(MDU_FLAGS,a3)
movem.l d1/a1/a6,-(sp)
movea.l (mdu_SysLib,a3),a6
move.l (readsig,a3),d0
lea (mdu_rtcb,a3),a1
SYS Signal ;Signal the read task
movem.l (sp)+,d1/a1/a6
btst #ISRB_TDRE,d7
bne DoTDRE
movem.l (sp)+,d0/d7/a0/a3/a5
move.l (OldVec,pc),-(sp)
rts
DoneWriting:
; PUTDEBUG 75,<'%s/Int: Write signal.'>
movem.l d1/a1/a6,-(sp)
move.l (tdresig,a3),d0
lea (mdu_wtcb,a3),a1
movea.l (mdu_SysLib,a3),a6
SYS Signal ;Signal the write task
movem.l (sp)+,d1/a1/a6
movem.l (sp)+,d0/d7/a0/a3/a5
move.l (OldVec,pc),-(sp)
rts
;Exceptional conditions handled here....
RealReadErr:
;An exceptional condition occured - signal the read task
movem.l d1/a1/a6,-(sp)
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
SYS Signal
move.b (RDR,a5),d0 ;This forces a clear of the error bits
movem.l (sp)+,d1/a1/a6
btst #ISRB_RDRF,d7 ;Test Receive Data Buffer Full
bne DoRDRF
btst #ISRB_TDRE,d7
bne DoTDRE
movem.l (sp)+,d0/d7/a0/a3/a5
move.l (OldVec,pc),-(sp)
rts
;********* End of fast interrupt routine *********
Int0000: DoInt 0,0,0,0
Int0001: DoInt 0,0,0,1
Int0010: DoInt 0,0,1,0
Int0011: DoInt 0,0,1,1
Int0100: DoInt 0,1,0,0
Int0101: DoInt 0,1,0,1
Int0110: DoInt 0,1,1,0
Int0111: DoInt 0,1,1,1
Int1000: DoInt 1,0,0,0
Int1001: DoInt 1,0,0,1
Int1010: DoInt 1,0,1,0
Int1011: DoInt 1,0,1,1
Int1100: DoInt 1,1,0,0
Int1101: DoInt 1,1,0,1
Int1110: DoInt 1,1,1,0
Int1111: DoInt 1,1,1,1
ifne INFO_LEVEL ;If any debugging enabled at all
KPutFmt: move.l a2,-(sp)
lea (KPutChar,pc),a2
bsr.b KDoFmt
movea.l (sp)+,a2
rts
KDoFmt: move.l a6,-(sp)
movea.l (SysBase).w,a6
SYS RawDoFmt
movea.l (sp)+,a6
rts
KPutChar: move.l a6,-(sp)
movea.l (SysBase).w,a6
SYS RawPutChar
movea.l (sp)+,a6
rts
endc
EndCode:
end