mirror of
https://github.com/elliotnunn/mac-rom.git
synced 2025-01-05 08:30:14 +00:00
4325cdcc78
Resource forks are included only for .rsrc files. These are DeRezzed into their data fork. 'ckid' resources, from the Projector VCS, are not included. The Tools directory, containing mostly junk, is also excluded.
1152 lines
46 KiB
Plaintext
1152 lines
46 KiB
Plaintext
;
|
|
; File: TimeMgr.a
|
|
;
|
|
; Contains: The Macintosh OS Time Manager
|
|
;
|
|
; Written by: Gary G. Davidian
|
|
;
|
|
; Copyright: © 1988-1993 by Apple Computer, Inc., all rights reserved.
|
|
;
|
|
; Change History (most recent first):
|
|
;
|
|
; <SM5> 11/9/93 KW added some eieioSTP macros. Only expands for CygnusX1 ROM
|
|
; <SM4> 11/12/92 PN Get rid of ³ 020 conditionals for ROM builds
|
|
; <3> 9/10/92 AEK Roll in new _microseconds and PrimeTime fix from Quicktime
|
|
; code for _microseconds was already there, so just remove old one
|
|
; <2> 3/27/92 JSM Merge in PhilipÕs change: Export Labels for Patch roll-in.
|
|
; <1> ¥ Pre-SuperMario comments follow ¥
|
|
; <9> 2/12/92 JSM Moved this file to TimeMgr folder, keeping all the old
|
|
; revisions.
|
|
; <8> 11/16/91 DTY Checked in Wayne MeretzkyÕs new version of __MicroSeconds,
|
|
; conditionalized for TheFuture.
|
|
; <7> 9/9/91 JSM Cleanup header.
|
|
; <6> 9/22/90 dba Break some routines into separate PROCs so they can be cut back
|
|
; in the linked patch version. Also change conditionals so the the
|
|
; linked patch version of the Time Mgr. is universal (does not
|
|
; assume 68020 or existence of the Power Mgr. chip). This file is
|
|
; now used as the source both of the Time Mgr. in ROM and of the
|
|
; Time Mgr. patches.
|
|
; <5> 9/19/90 BG Removed <3>. 040s are behaving more reliably now.
|
|
; <4> 7/25/90 GGD Added support for the _MicroSeconds trap which returns a 64 bit
|
|
; Microsecond counter which is usefull for timestamping and
|
|
; timing. Deleted some Eclipse NOPs which would not be assembled
|
|
; for 68040s
|
|
; <3> 6/20/90 CCH Added some NOPs for flaky 68040's.
|
|
; <2> 1/12/90 CCH Added include of ÒHardwarePrivateEqu.aÓ.
|
|
; <1.4> 11/13/89 MSH Pmgr equates now in record format.
|
|
; <1.3> 2/23/89 GGD Stored the underflow retry adjustment constant in RAM to allow
|
|
; better patching flexability. On systems with PowerManagers,
|
|
; switch into fast mode in FreezeTime, and restore mode in
|
|
; ThawTime, to reduce drift in the time critical section, and to
|
|
; prevent a potential loop in the underflow path.
|
|
; <1.2> 1/30/89 GGD Added new features for BigBang and the new ROMs. This includes
|
|
; support for extended time manager tasks, and improves accuracy
|
|
; and reduces drift even more.
|
|
; <1.1> 11/10/88 CCH Fixed Header.
|
|
; <1.0> 11/9/88 CCH Adding to EASE.
|
|
; <1.4> 10/16/88 GGD Changed resolution to 20µs with range of 1 day. Improved long
|
|
; term accuracy, and added initialization code to do runtime
|
|
; timings to make it processor speed independent. Coded around the
|
|
; bug in Rockwell VIA timers. Removed machine specific
|
|
; conditionals in favor of the "Cpu" equate. Improved conversion
|
|
; constants for better accuracy. Went back to having state in the
|
|
; system heap.
|
|
; <¥1.3> 9/23/88 CCH Got rid of inc.sum.d and empty nFiles
|
|
; <1.2> 8/16/88 GGD Added InitTimeMgr initialization routine. Changed usage of
|
|
; TimeVars, is now head of active list, no state in SysHeap.
|
|
; <1.1> 8/5/88 GGD Completely re-written. Lots of bug fixes and new features.
|
|
; 7/26/88 GGD Completely re-wrote the Time Manager, replacing all of the code
|
|
; and comments from the old version. This version fixes numerous
|
|
; bugs, and adds several new features.
|
|
; <1.0> 2/10/88 BBM Adding file for the first time into EASEÉ
|
|
;
|
|
|
|
TITLE 'Time Manager - Macintosh Time Manager'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Macintosh Time Manager.
|
|
;
|
|
; Written by Gary G. Davidian, July 26, 1988.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
print off
|
|
|
|
load 'StandardEqu.d'
|
|
include 'HardwarePrivateEqu.a'
|
|
|
|
print on,nomdir
|
|
|
|
if forROM then
|
|
use68020Opcodes: equ 1
|
|
machine MC68020
|
|
else
|
|
use68020Opcodes: equ 0
|
|
machine MC68020 ; some 020 opcodes in stuff that is run-time determined
|
|
endif
|
|
|
|
; globals in system heap (same RECORD in TimeMgrPatch.a; move it to an equate file if you like)
|
|
|
|
TimeMgrPrivate record 0,increment ; time manager private storage
|
|
ActivePtr ds.l 1 ; pointer to soonest active request
|
|
TimerAdjust ds.b 1 ; number of VIA ticks used loading timer
|
|
TimerLowSave ds.b 1 ; low byte of VIA timer from last FreezeTime <4>
|
|
RetryAdjust ds.w 1 ; number of via ticks for underflow retry
|
|
CurrentTime ds.l 1 ; number of virtual ticks since boot
|
|
BackLog ds.l 1 ; number of virtual ticks of ready tasks
|
|
**** NOTE: The ordering of the following 4 fields must not change (FreezeTime Depends on it) <4>
|
|
HighUSecs ds.l 1 ; high 32 bits of microsecond count <4>
|
|
LowUSecs ds.l 1 ; low 32 bits of microsecond count <4>
|
|
FractUSecs ds.w 1 ; 16 bit fractional microsecond count <4>
|
|
CurTimeThresh ds.w 1 ; CurrentTime threshold for updating µsec count <4>
|
|
**** end of order dependent fields <4>
|
|
PrivateSize equ *-TimeMgrPrivate ; size of this record
|
|
endr
|
|
|
|
USecsInc equ $FFF2E035 ; 65522.8758µsec (16.16 fixed point) <4>
|
|
ThreshInc equ 3208 ; = 3208 internal ticks <4>
|
|
eject
|
|
;_______________________________________________________________________
|
|
;
|
|
; The time manager maintains a linked list of tasks that are waiting for
|
|
; their timer to run out. The list is ordered by expiration time, with
|
|
; ActivePtr pointing to the task with the earliest timeout. The tmCount
|
|
; field of the each task on the list contains the number of virtual ticks
|
|
; a task must wait after the task in front of it expires. For the task
|
|
; at the head of the list, this is the time to wait after the VIA timer
|
|
; expires.
|
|
; The TimerAdjust variable is used to account for the number of VIA ticks
|
|
; that go by while we are trying to load a new timer value. This is
|
|
; computed at runtime, so that it is processor speed independent.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; User visible changes since Inside Macintosh Vol. 4 was published.
|
|
;
|
|
; 1) tmCount field is a LONGINT, not an INTEGER (documentation error).
|
|
;
|
|
; 2) RmvTime, and PrimeTime now correctly return a result code (noErr)
|
|
; in register D0. Numerous other bugs that were not as visible have
|
|
; also been fixed.
|
|
;
|
|
; 3) Time can be represented in microseconds as well as milliseconds.
|
|
; Negative values represent negated microseconds. (Although microseconds
|
|
; can be specified, the actual resolution of the time manager is
|
|
; currently closer to 20 microseconds).
|
|
;
|
|
; 4) The high order bit of qType is now a flag to indicate that the
|
|
; task timer is active. Set by PrimeTime, cleared when time expires
|
|
; or RmvTime is called. Initialized cleared by InsTime.
|
|
;
|
|
; 5) tmAddr may be set to zero to indicate that no completion routine
|
|
; should be called. (The flag mentioned above may be used to determine
|
|
; if the time has expired).
|
|
;
|
|
; 6) The completion routine is now passed a pointer (in register A1) to
|
|
; the TMTask record associated with it. This makes it more usable
|
|
; under Multi-Finder.
|
|
;
|
|
; 7) When RmvTime is called on an active task, the tmCount field will
|
|
; be returned with the amount of remaining time that had not been
|
|
; used (in negative microseconds, or positive milliseconds). If
|
|
; the task had already expired, tmCount will contain zero. This
|
|
; allows the Time Manager to be used to compute elapsed times, which
|
|
; is useful for performance measurments.
|
|
;
|
|
; 8) In order to provide better resolution than 1 millisecond, the maximum
|
|
; delay time was reduced from about 24 days, to currently about 1 day.
|
|
; Larger delay times may still be specified to PrimeTime, but they will
|
|
; be converted to the largest possible time instead.
|
|
;
|
|
; 9) Support for Extended Time Manager Tasks has been added, _InsXTime is
|
|
; used to install them. The new field tmWakeUp is used by the TimeMgr
|
|
; to remember when a _PrimeTime is supposed to expire. When _PrimeTime
|
|
; is called, if tmWakeUp is non-zero, then the new wakeup time will be
|
|
; relative to the old tmWakeUp, instead of relative to the current time.
|
|
; This allows for drift free fixed frequency timing tasks, which is needed
|
|
; by the Sound Manager.
|
|
;
|
|
;_______________________________________________________________________
|
|
TITLE 'Time Manager - Equates'
|
|
|
|
TaskActiveBit equ 7 ; high bit of QType word is active flag
|
|
ExtendedTmTaskBit equ 6 ; indicates an extended TmTask record
|
|
T2IntBit equ 5 ; VIER/VIFR bit num for VIA Timer 2
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Representations of time in the Time Manager.
|
|
;
|
|
; Time is represented externally in two ways, both are stored in a longword,
|
|
; if the value is positive, it represents milliseconds, and if it is
|
|
; negative, it represents negated microseconds. This representation is used
|
|
; as the delay time input to PrimeTime, and as the unused remaining time
|
|
; output by RmvTime.
|
|
;
|
|
; The VIA1 Timer2 is the 16 bit hardware timer used by the time manager.
|
|
; On all current machines, it decrements at a rate of 783360 Hz, and
|
|
; generates an interrupt, and keeps counting, when it counts through zero.
|
|
; This provides resolution of 1.276 µsec, and a range of 83.660 msec.
|
|
;
|
|
; Internally the time manager represents time as a virtual unsigned 36 bit
|
|
; VIA timer, which gives a range of about 1 day. However, since we only
|
|
; have 32 bits to store time in, we drop the low 4 bits of the timer,
|
|
; which reduces the resolution by a factor of 16 to 20.425 µsec.
|
|
;
|
|
; Converting between the external and internal forms of time is done by
|
|
; multiplying by the proper fixed point constants, and shifting the binary
|
|
; point of the 64 bit result to get just the integer portion of the result.
|
|
; The computation of the 32 bit conversion constants requires 64 bit
|
|
; intermediate results, and unfortunatly the assembler only provides 32
|
|
; bit expression evaluation, so the proper constants were computed with
|
|
; a 64 bit hex caculator, and hard coded here (yuck!). These are not
|
|
; "Magic Numbers", the formula for computing them is provided, so that
|
|
; they may be re-computed if any of the parameters ever change.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
TicksPerSec equ 783360 ; VIA Timer clock rate
|
|
TickScale equ 4 ; Internal time is VIA ticks >> TickScale
|
|
|
|
MsToIntFractBits equ 26 ; number of fraction bits in 64 bit result
|
|
*MsToInternal equ ((TicksPerSec<<(MsToIntFractBits-TickScale))\
|
|
+999)/1000
|
|
MsToInternal equ $C3D70A3E ; msec to internal time multiplier
|
|
|
|
UsToIntFractBits equ 32 ; number of fraction bits in 64 bit result
|
|
*UsToInternal equ ((TicksPerSec<<(UsToIntFractBits-TickScale))\
|
|
+999999)/1000000
|
|
UsToInternal equ $0C88A47F ; µsec to internal time multiplier
|
|
|
|
IntToMsFractBits equ 32 ; number of fraction bits in 64 bit result
|
|
*InternalToMs equ ((1000<<(IntToMsFractBits+TickScale))\
|
|
+TicksPerSec-1)/TicksPerSec
|
|
InternalToMs equ $053A8FE6 ; internal time to msec multiplier
|
|
|
|
IntToUsFractBits equ 27 ; number of fraction bits in 64 bit result
|
|
*InternalToUs equ ((1000000<<(IntToUsFractBits+TickScale))\
|
|
+TicksPerSec-1)/TicksPerSec
|
|
InternalToUs equ $A36610BC ; internal time to µsec multiplier
|
|
|
|
|
|
macro ; Macro for interfacing with the MultAndMerge routine.
|
|
Convert &Multiplier,&FractionBits
|
|
bsr.s MultAndMerge ; input/output is D0
|
|
dc.l &Multiplier ; conversion multiplier
|
|
if &eval(&FractionBits)=32 then
|
|
dc.l 0 ; merge mask (low 32 bits all fraction)
|
|
else
|
|
dc.l -1<<&FractionBits ; merge mask (some low bits not fraction)
|
|
rol.l #32-&FractionBits,d0 ; position result after merge
|
|
endif
|
|
endm
|
|
TITLE 'Time Manager - Remove Time Manager Task'
|
|
|
|
TimeMgr proc export
|
|
|
|
export __InsTime ; Install Time Manager Task
|
|
export __RmvTime ; Remove Time Manager Task
|
|
export __PrimeTime ; Initiate Time Manager Task Delay
|
|
import FreezeTime ; Stop timer
|
|
entry ThawTime ; Start timer (used by InitTimeMgr)
|
|
entry Timer2Int ; Interrupt handler (vector set up by InitTimeMgr)
|
|
with TimeMgrPrivate
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: RmvTime
|
|
; Inputs: A0 - pointer to Time Manager Task to remove
|
|
; Outputs: D0 - error code (noErr)
|
|
; Destroys: none
|
|
; Calls: FreezeTime, ThawTime, MultAndMerge
|
|
; Called by: OsTrap dispatcher
|
|
;
|
|
; Function: Removes the specified Time Manager Task from the control
|
|
; of the Time Manager. If PrimeTime had been previously
|
|
; called for this task, and the time has not expired yet,
|
|
; the amount of unused time will be returned in the tmCount
|
|
; field. The unused time will be represented in negated
|
|
; microseconds, if it is not too large to fit within 32 bits
|
|
; otherwise it will be represented in positive milliseconds.
|
|
; Additionally, the TaskActiveBit of the QType field will
|
|
; be cleared to indicate that the timer task is no longer
|
|
; active.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
export AfterFreezeTimeInRmvTime
|
|
__RmvTime ; a0-a2/d1-d2 saved by dispatcher
|
|
move.l d3,-(sp) ; save d3 also
|
|
|
|
jsr FreezeTime ; setup to manipulate time queue
|
|
AfterFreezeTimeInRmvTime
|
|
moveq.l #0,d2 ; D2 : total time remaining
|
|
lea ActivePtr-Qlink(a2),a1 ; A1 : previous := head
|
|
@SearchLoop move.l QLink(a1),d0 ; D0 : next := previous.QLink
|
|
beq.s @NotActive ; if end, not an active task
|
|
exg.l a1,d0 ; A1 : previous := next (save old previous)
|
|
move.l tmCount(a1),d1 ; get delay between previous and next
|
|
add.l d1,d2 ; add to total time remaining
|
|
cmpa.l a0,a1 ; is this the one to remove?
|
|
bne.s @SearchLoop ; loop until it is found
|
|
|
|
movea.l d0,a1 ; A1 : old previous
|
|
move.l QLink(a0),d0 ; get successor to removee
|
|
move.l d0,QLink(a1) ; previous points to successor
|
|
beq.s @Removed ; if no successor, don't adjust time
|
|
movea.l d0,a1 ; get pointer to successor
|
|
add.l d1,tmCount(a1) ; pass removees time to successor
|
|
@Removed move.l d2,d0 ; setup total time remaining in D0
|
|
|
|
@NotActive ; at this point D0 is total time remaining
|
|
sub.l BackLog(a2),d0 ; don't count the backlog
|
|
bhs.s @GotRemaining ; if still time remaining
|
|
moveq.l #0,d0 ; otherwise, all backlog, return zero time
|
|
@GotRemaining
|
|
bsr.w ThawTime ; done manipulating time queue
|
|
|
|
movea.l d0,a1 ; save a copy of internal time
|
|
convert InternalToUs,IntToUsFractBits ; convert internal to µsec
|
|
neg.l d0 ; µsecs are passed negated
|
|
bmi.s @ConvertDone ; if it fits we're done
|
|
|
|
move.l a1,d0 ; restore copy of internal time
|
|
convert InternalToMs,IntToMsFractBits ; convert internal to msec
|
|
@ConvertDone
|
|
move.l d0,tmCount(a0) ; return unused delay time
|
|
|
|
move.l (sp)+,d3 ; restore saved d3
|
|
moveq.l #0,d1 ; clear all of the flags
|
|
; (fall in) bra.s __InsTime ; mark task inactive, return with success
|
|
TITLE 'Time Manager - Install Time Manager Task'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: InsTime
|
|
; Inputs: A0 - pointer to Time Manager Task to install
|
|
; Outputs: D0 - error code (noErr)
|
|
; Destroys: none
|
|
; Calls: none
|
|
; Called by: OsTrap dispatcher, RmvTime (falls into)
|
|
;
|
|
; Function: Initializes the fields of a Time Manager Task, to indicate
|
|
; that it is inactive.
|
|
;
|
|
; NOTE: This routine is documented in Inside Machintosh Vol 4., and
|
|
; was used by the old Time Manager to install the task on the
|
|
; Time Manager queue. In this version of the Time Manager, the
|
|
; queue only contains active tasks, so installation is done by
|
|
; _PrimeTime now.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
__InsTime ; a0-a2/d1-d2 saved by dispatcher
|
|
moveq.l #$1F,d0 ; mask to clear flag bits in QType
|
|
and.b QType(a0),d0 ; clear the flags
|
|
andi.w #$0600,d1 ; isolate 2 flag bits from trap word
|
|
lsr.w #4,d1 ; position the 3 flag bits (high bit zeroed)
|
|
or.b d1,d0 ; merge into QType high byte
|
|
move.b d0,QType(a0) ; save flags, mark the task initially inactive
|
|
moveq.l #noErr,d0 ; return success
|
|
rts ; all done
|
|
TITLE 'Time Manager - Multiply 32 by 32'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: MultAndMerge
|
|
; Inputs: D0 - 32 bit multiplier
|
|
; (return PC) - 32 bit multiplicand
|
|
; (return PC+4) - 32 bit merge mask
|
|
; Outputs: D0 - upper 32 bits of 64 bit product, merged with selected
|
|
; bits of lower 32 bits of 64 bit product as specified by
|
|
; merge mask.
|
|
; Destroys: A2, D1, D2, D3
|
|
; Calls: none
|
|
; Called by: PrimeTime, RmvTime
|
|
;
|
|
; Function: Performs a 32 by 32 bit multiply producing a 64 bit result.
|
|
; Same function as the 68020 MULU.L D0,D0:D1 instruction.
|
|
; The 64 bit product is then merged into a 32 bit result, by
|
|
; using a merge mask which has bits set corresponding to which
|
|
; bits in the low 32 bits of the product are to be merged into
|
|
; the corresponding bit positions of the high 32 bits of the
|
|
; product. If the bits specified in the merge mask an non-zero
|
|
; in the high 32 bits of the product, then a result of all ones
|
|
; will be returned to indicate overflow.
|
|
; This routine is used to perform fixed point multiplication,
|
|
; where the position of the implied binary point may vary.
|
|
;
|
|
; Note: D0 = A B, D1 := C D. Product is derived as follows.
|
|
; B*D
|
|
; + B*C
|
|
; + A*D
|
|
; + A*C
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
macro
|
|
mulud0d168000
|
|
|
|
; result in d0, d1
|
|
; trashes d2, d3
|
|
|
|
move.l d4,-(sp) ; preserve d4
|
|
move.l d1,d2 ; D2 := C D
|
|
move.l d1,d3 ; D3 := C D
|
|
mulu.w d0,d1 ; D1 := B*D
|
|
swap d1 ;
|
|
swap d3 ; D3 := D C
|
|
mulu.w d0,d3 ; D3 := B*C
|
|
swap d0 ; D0 := B A
|
|
ext.l d0 ; D0 := A (sign extended)
|
|
move.w d0,d4 ; D4 := A
|
|
beq.s @ShortMul ; if A is zero, skip next 2 mults
|
|
|
|
mulu.w d2,d4 ; D4 := A*D
|
|
swap d2 ; D2 := D C
|
|
mulu.w d2,d0 ; D0 := A*C
|
|
add.l d4,d3 ; D3 := A*D + B*C
|
|
clr.w d4 ;
|
|
addx.w d4,d4 ; D4 := carry from A*D + B*C
|
|
|
|
@ShortMul add.w d3,d1 ; add middle product to low product
|
|
swap d1 ; D1 := low 32 bits of product
|
|
move.w d4,d3 ; D3 := copy saved carry
|
|
swap d3 ; D3 := high 17 bits of A*D + B*C
|
|
addx.l d3,d0 ; D0 := high 32 bits of product
|
|
move.l (sp)+,d4 ; restore d4
|
|
|
|
endm
|
|
|
|
export MultAndMerge
|
|
MultAndMerge
|
|
movea.l (sp)+,a2 ; pop return address
|
|
move.l (a2)+,d1 ; get multiplicand
|
|
|
|
if forROM then
|
|
mulu.l d0,d0:d1 ; d0:d1 := d0*d1
|
|
else
|
|
|
|
tst.b CPUFlag ; are we on a machine with long multiply?
|
|
bz.s @noLongMultiply
|
|
mulu.l d0,d0:d1 ; d0:d1 := d0*d1
|
|
bra.s @didMultiply
|
|
@noLongMultiply
|
|
mulud0d168000 ; d0:d1 := d0*d1 (trash d2, d3)
|
|
@didMultiply
|
|
|
|
endif
|
|
|
|
move.l (a2)+,d2 ; get merge mask
|
|
beq.s @Done ; if zero, result in d0 is correct
|
|
and.l d2,d1 ; get the non-fraction bits from d1
|
|
add.l d0,d2 ; corresponding bits in d0 should be zero
|
|
subx.l d2,d2 ; d2 = -1 if d0 overflowed, else zero
|
|
or.l d2,d0 ; return d0 = -1 if overflow
|
|
or.l d1,d0 ; merge bits from d1 into d0
|
|
@Done jmp (a2) ; return to code after constant list
|
|
TITLE 'Time Manager - Prime Time Manager Task'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: PrimeTime
|
|
; Inputs: A0 - pointer to Time Manager Task to schedule
|
|
; D0 - [long] if >= 0, Delay Time in positive milliseconds
|
|
; if < 0, Delay Time in negated microseconds
|
|
; Outputs: D0 - error code (noErr)
|
|
; Destroys: none
|
|
; Calls: FreezeTime, ThawTime, MultAndMerge
|
|
; Called by: OsTrap dispatcher
|
|
;
|
|
; Function: Schedules a Time Manager Task to run after the specified
|
|
; Delay Time expires. The Delay Time may be specified in
|
|
; milliseconds, in which case, it must be a positive number,
|
|
; or it can be specified in microseconds, in which case, it
|
|
; must be negated first.
|
|
; Additionally, the TaskActiveBit of the QType field will
|
|
; be set to indicate that the timer task is currently active,
|
|
; and will be cleared when the specified time has expired.
|
|
;
|
|
; NOTE: The Time Manager does not support delay times as long as the
|
|
; maximum number of milliseconds that can be passed to this call.
|
|
; If delay time specified is too large, then the maximum delay
|
|
; time supported will be used instead.
|
|
;
|
|
;_______________________________________________________________________
|
|
export AfterFreezeTimeInPrimeTime
|
|
; a0-a2/d1-d2 saved by dispatcher
|
|
__PrimeTime move.l d3,-(sp) ; save d3 also
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; start of code from Quicktime patch
|
|
; attempts to keep backlog from becoming very large
|
|
;
|
|
|
|
btst.b #ExtendedTmTaskBit, qType(a0)
|
|
beq.s @notExtended
|
|
lea 3+tmReserved(a0), a1
|
|
tst.l tmWakeUp(a0)
|
|
beq.s @startNew
|
|
tst.l d0
|
|
beq.s @checkCount
|
|
@startNew:
|
|
clr.b (a1)
|
|
bra.s @notExtended
|
|
@checkCount:
|
|
addq.b #1, (a1)
|
|
bpl.s @notExtended
|
|
bclr.b #ExtendedTmTaskBit, qType(a0)
|
|
@notExtended:
|
|
|
|
;
|
|
; end of code from Quicktime patch
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
tst.l d0 ; see if +msec or -µsec
|
|
bpl.s @msec ; µsec are negated, msec pos
|
|
|
|
@usec neg.l d0 ; get positive number of µsecs
|
|
convert UsToInternal,UsToIntFractBits ; convert µsec to internal
|
|
bra.s @ConvertDone ; join common code
|
|
|
|
@msec convert MsToInternal,MsToIntFractBits ; convert msec to internal
|
|
@ConvertDone
|
|
jsr FreezeTime ; setup to manipulate time queue
|
|
AfterFreezeTimeInPrimeTime
|
|
bset.b #TaskActiveBit,QType(a0); mark the task as active
|
|
bne.s @AlreadyActive ; if already on the queue don't touch it
|
|
|
|
btst.b #ExtendedTmTaskBit,QType(a0) ; check for extended tmTask
|
|
beq.s @AddToActive ; of standard tmTask, nothing special to do
|
|
|
|
move.l CurrentTime(a2),d2 ; get current time
|
|
move.l tmWakeUp(a0),d1 ; get wake time from last _PrimeTime
|
|
beq.s @SetNewWakeTime ; if not set, delay relative to CurrentTime
|
|
sub.l d2,d1 ; d1 := time since/till last wakeup
|
|
bpl.s @WakeInFuture ; if time till, just add to it
|
|
add.l d1,d0 ; subtract time since from desired delay
|
|
bcs.s @SetNewWakeTime ; if time still positive, use it
|
|
|
|
add.l d0,d2 ; new wakeup := old + delay
|
|
bne.s @WakeupInPast ; zero is special wakeup value, don't allow it
|
|
moveq.l #1,d2 ; use one instead of zero
|
|
@WakeupInPast
|
|
move.l d2,tmWakeUp(a0) ; update wakeup.
|
|
move.l d0,d1 ; remember negated delay
|
|
sub.l d0,BackLog(a2) ; remember how late we are
|
|
moveq.l #0,d0 ; for negative delay, use zero
|
|
lea ActivePtr-Qlink(a2),a1 ; A1 : previous := head
|
|
move.l QLink(a1),d2 ; D2 : next := previous.QLink
|
|
beq.s @Insert ; if empty, make it the head
|
|
exg.l a1,d2 ; A1 : previous := next (save old previous)
|
|
sub.l d1,tmCount(a1) ; add backlog of new task to old first task
|
|
exg.l a1,d2 ; restore prior previous and next ptrs
|
|
bra.s @Insert ; add it to the active list
|
|
|
|
@WakeInFuture
|
|
add.l d1,d0 ; add time till desired delay
|
|
@SetNewWakeTime
|
|
add.l d0,d2 ; new wakeup := old + delay
|
|
bne.s @StoreWakeup ; zero is special wakeup value, don't allow it
|
|
moveq.l #1,d2 ; use one instead of zero
|
|
@StoreWakeup
|
|
move.l d2,tmWakeUp(a0) ; update wakeup.
|
|
|
|
@AddToActive
|
|
add.l BackLog(a2),d0 ; run it after all pending tasks
|
|
lea ActivePtr-Qlink(a2),a1 ; A1 : previous := head
|
|
@SearchLoop move.l QLink(a1),d2 ; D2 : next := previous.QLink
|
|
beq.s @Insert ; if end, insert after previous
|
|
exg.l a1,d2 ; A1 : previous := next (save old previous)
|
|
move.l tmCount(a1),d1 ; get delay between previous and next
|
|
sub.l d1,d0 ; subtract from our delay time
|
|
bhs.s @SearchLoop ; loop until our delay between prev and next
|
|
|
|
add.l d1,d0 ; d0 := delay between previous and new
|
|
sub.l d0,d1 ; d1 := delay between new and next
|
|
move.l d1,tmCount(a1) ; adjust next task delay time
|
|
exg.l a1,d2 ; restore prior previous and next ptrs
|
|
|
|
@Insert move.l d2,QLink(a0) ; new task followed by next task
|
|
move.l d0,tmCount(a0) ; setup new task delay time
|
|
move.l a0,QLink(a1) ; previous task followed by new task
|
|
|
|
@AlreadyActive
|
|
bsr.s ThawTime ; done manipulating time queue
|
|
move.l (sp)+,d3 ; restore saved d3
|
|
moveq.l #noErr,d0 ; return success
|
|
rts ; all done
|
|
TITLE 'Time Manager - Timer 2 Interrupt Handler'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: Timer2Int
|
|
; Inputs: none
|
|
; Outputs: none
|
|
; Destroys: A0, A1, A2, A3, D0, D1, D2, D3
|
|
; Calls: FreezeTime, ThawTime, tmAddr service routine.
|
|
; Called by: VIA 1 system interrupt handler
|
|
;
|
|
; Function: Services the timer interrupt, and calls the service routine
|
|
; if task at the head of the list has expired, or continues
|
|
; it running if more time remaining. If nothing on timer
|
|
; list, just disables the timer.
|
|
;
|
|
; NOTE: The TaskActiveBit in the QType field will be cleared when the
|
|
; task timer expires so that applications can poll the timer for
|
|
; completion. If the tmAddr field is zero, the service routine
|
|
; will not be called. When the service routine is called, A0 points
|
|
; to the service routine itself, and A1 points to the expired
|
|
; Time Manager task.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
export AfterFreezeTimeInTimer2Int
|
|
Timer2Int ; a0-a3/d0-d3 saved by IntHand
|
|
jsr FreezeTime ; stop the timer, adjust time remaining <4>
|
|
AfterFreezeTimeInTimer2Int
|
|
move.l ActivePtr(a2),d0 ; get pointer to first active timer task
|
|
beq.s ThawTime ; if nothing in queue, just exit
|
|
|
|
movea.l d0,a0 ; A0 := pointer to timer task
|
|
tst.l tmCount(a0) ; see if timer expired
|
|
bne.s ThawTime ; if not, let it continue running
|
|
|
|
move.l QLink(a0),ActivePtr(a2) ; remove it from the active list
|
|
bclr.b #TaskActiveBit,QType(a0); mark the task as completed
|
|
bsr.s ThawTime ; start next timer running
|
|
|
|
move.l tmAddr(a0),d0 ; get service routine address
|
|
beq.s @NoHandler ; if no service routine, just exit
|
|
movea.l a0,a1 ; A1 := pointer to queue element
|
|
movea.l d0,a0 ; A0 := address of service routine
|
|
jmp (a0) ; return through the service routine
|
|
@NoHandler rts ; all done
|
|
TITLE 'Time Manager - Thaw Time'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: ThawTime
|
|
; Inputs: A2 - ptr to TimeMgrPrivate
|
|
; D3.hi - saved SR (interrupt priority)
|
|
; D3.lo - value read from the low byte of the VIA timer masked
|
|
; to virtual ticks.
|
|
; Outputs: none
|
|
; Destroys: D1, D2, A1, A2
|
|
; Calls: none
|
|
; Called by: RmvTime, PrimeTime, Timer2Int
|
|
;
|
|
; Function: Starts timer, based upon head of timer list, adjusts
|
|
; time remaining for head of list. Restores interrupt
|
|
; priority level.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
ThawTime move.l #$0000FFFF>>TickScale,d2; max range (internal form) of VIA timer
|
|
add.b TimerAdjust(a2),d3 ; add in the timer loading overhead
|
|
move.l ActivePtr(a2),d1 ; check pointer to first active timer task
|
|
beq.s @StartTimer ; if no tasks, just start timer with max
|
|
|
|
movea.l d1,a1 ; point to head of active list
|
|
move.l tmCount(a1),d1 ; get time remaining for head
|
|
sub.l BackLog(a2),d1 ; remove as much backlog as possible
|
|
bhs.s @NoBacklog ; if tmCount >= BackLog
|
|
neg.l d1 ; BackLog - tmCount
|
|
move.l d1,BackLog(a2) ; update backlog
|
|
moveq.l #0,d1 ; run immediatly
|
|
bra.s @FindMax ; update timer
|
|
@NoBackLog clr.l BackLog(a2) ; no backlog remaining
|
|
@FindMax cmp.l d2,d1 ; check remaining time against max
|
|
bhs.s @UseMax ; if remaining >= max, use the max instead
|
|
move.l d1,d2 ; otherwise, use time remaining
|
|
@UseMax sub.l d2,d1 ; remaining := remaining - timer value
|
|
move.l d1,tmCount(a1) ; update time remaining
|
|
|
|
@StartTimer movea.l VIA,a1 ; get base address of VIA1
|
|
lea vT2C(a1),a1 ; point to low byte of counter for speed
|
|
move.w d3,d1 ; save copy of adjusted original low byte
|
|
@Retry add.l d2,CurrentTime(a2) ; update current time
|
|
lsl.w #TickScale,d2 ; convert internal form to VIA form
|
|
lea vT2CH-vT2C(a1),a2 ; point to high byte for speed
|
|
|
|
; *** Begining of time critical section
|
|
eieioSTP
|
|
sub.b vT2C-vT2C(a1),d3 ; see how many ticks of overhead used
|
|
eieioSTP
|
|
sub.w d3,d2 ; subtract out overhead ticks
|
|
bls.s @Underflow ; if less than 1 tick, fix it up
|
|
|
|
; VIAs from Rockwell (which we have been shipping for several years) and possibly
|
|
; other vendors have a bug. If you load the counter high byte in the same clock
|
|
; as the old counter value is counting through zero, when the new counter value
|
|
; counts through zero the VIA will not generate an interrupt. To work around this
|
|
; "feature", we first load a dummy non-zero value into the high byte of the counter,
|
|
; and then load the value we really wanted. This should allow enough time to
|
|
; guarantee that it will not be counting through zero at the time we load the real
|
|
; counter value. This fixes the "AppleShare server hang problem".
|
|
|
|
eieioSTP
|
|
move.b d1,vT2CH-vT2CH(a2) ; *** ROCKWELL VIA FIX, DON'T REMOVE ***
|
|
eieioSTP
|
|
move.b d2,vT2C-vT2C(a1) ; setup timer low byte latch
|
|
eieioSTP
|
|
if use68020Opcodes then ; 68020 shifting is fast
|
|
lsr.w #8,d2 ; get high byte of time
|
|
eieioSTP
|
|
move.b d2,vT2CH-vT2CH(a2) ; load high byte of timer, start timer
|
|
eieioSTP
|
|
else ; 68000 shifting is slower than memory
|
|
move.w d2,-(sp) ; get high byte of time
|
|
eieioSTP
|
|
move.b (sp)+,vT2CH-vT2CH(a2) ; load high byte of timer, start timer
|
|
eieioSTP
|
|
endif
|
|
; *** End of time critical section
|
|
|
|
if forROM then
|
|
if hasPowerMgr then
|
|
movea.l PMgrBase,a2 ; PMgr gobals.
|
|
cmpi.b #1,PMgrRec.saveSpeedo(a2) ; see if we need to be idle
|
|
bne.s @RemainFast ; if not, remain running at fast speed.
|
|
tst.w Clock1M ; return processor to idle.
|
|
@RemainFast
|
|
endif
|
|
endif
|
|
|
|
swap d3 ; get saved sr from high word
|
|
move.w d3,sr ; restore interrupt priority level
|
|
rts ; all done
|
|
|
|
@Underflow add.w d2,d3 ; recompute original delay value
|
|
sub.b d3,d1 ; adjust initial timer low byte
|
|
neg.w d2 ; make excess time positive
|
|
movea.l TimeVars,a2 ; point to TimeMgrPrivate
|
|
add.w RetryAdjust(a2),d2 ; round up before conversion, add in some extra time
|
|
lsr.w #TickScale,d2 ; convert it to virtual ticks
|
|
add.l d2,BackLog(a2) ; remember how negative we are for next freeze
|
|
move.w d1,d3 ; restore initial timer low byte
|
|
bra.s @Retry ; now go reload the timer
|
|
endproc
|
|
TITLE 'Time Manager - Read 64 bit MicroSecond counter'
|
|
;_______________________________________________________________________ <4>
|
|
;
|
|
; Routine: MicroSeconds
|
|
; Inputs: none
|
|
; Outputs: A0/D0 - 64 bit counter (A0=High, D0=Low)
|
|
; Destroys: none
|
|
; Calls: none
|
|
; Called by: OsTrap dispatcher
|
|
;
|
|
; Function: Returns the value of the 64 bit microsecond counter, which
|
|
; is useful for timestamping.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
__MicroSeconds: proc export ; a0-a2/d1-d2 saved by dispatcher
|
|
|
|
with TimeMgrPrivate
|
|
|
|
movem.l d3-d5,-(sp) ; save some others as well
|
|
movea.l VIA,a1 ; get base address of VIA1
|
|
movea.l TimeVars,a0 ; point to TimeMgrPrivate
|
|
moveq.l #0,d1 ; d1 := 0
|
|
|
|
move.w sr,-(sp)
|
|
ori.w #$0700,sr ; disable interrupts
|
|
|
|
; First we'll read the VIA timer. This is complicated by three
|
|
; bits of trivia. First, if an interrupt is pending then reading
|
|
; lower eight bits would clear that interrupt so we'll avoid that by
|
|
; not reading the actual value in the low eight bits but just using
|
|
; zero instead. In fact, because the interrupt may become pending
|
|
; between reading the upper and lower bytes we won't read the
|
|
; lower byte if the upper byte is a zero even if no interrupt is yet
|
|
; pending. The second problem is that between reading the upper
|
|
; byte and the lower byte, the lower byte could decrement from 00
|
|
; to FF which would mean the upper byte value is wrong. So we'll
|
|
; re-read the upper byte after reading the lower byte and if they're
|
|
; not equal we'll retry the entire read operation. Finally, in
|
|
; the case where the interrupt is pending we must adjust the value
|
|
; to take into account the implied latency.
|
|
;
|
|
; When we're done we'll have a 32-bit signed value in d1 which can
|
|
; be combined with CurrentTime to yield the actual time. Also,
|
|
; we'll have the low eight bits from the VIA in d0.
|
|
|
|
eieioSTP
|
|
move.b vT2CH(a1),d1 ; d1 := MSBs of timer
|
|
eieioSTP
|
|
btst.b #T2IntBit,vIFR(a1) ; is the interrupt pending?
|
|
eieioSTP
|
|
beq.s no_overflow
|
|
|
|
overflow: moveq.l #-1,d1 ; force upper bits to ones
|
|
move.b d1,d0 ; generate fake LSBs
|
|
eieioSTP
|
|
move.b vT2CH(a1),d1 ; re-read timer in case of roll-over
|
|
eieioSTP
|
|
rol.w #8,d1 ; position MSBs; LSBs become 1's
|
|
bra.s done_reading_via
|
|
|
|
no_overflow:tst.b d1
|
|
retry: beq.s dont_read_lsbs ; don't clear the interrupt
|
|
eieioSTP
|
|
move.b vT2C(a1),d0 ; d0 := LSBs of timer
|
|
eieioSTP
|
|
move.b vT2CH(a1),d2 ; d2 := MSBs of timer
|
|
eieioSTP
|
|
cmp.b d2,d1 ; did the MSBs roll?
|
|
beq.s have_both_halves
|
|
move.b d2,d1
|
|
bra.s retry
|
|
|
|
dont_read_lsbs:
|
|
moveq.l #0,d0 ; pretend they're zeros
|
|
|
|
have_both_halves:
|
|
lsl.w #8,d1 ; slide the MSBs into position
|
|
move.b d0,d1 ; insert LSBs
|
|
|
|
done_reading_via:
|
|
|
|
; So now we have a 32-bit signed value in d1 which indicates
|
|
; where time really is with respect to the value in CurrentTime.
|
|
; In d0 we have the LSBits of the via timer. Now we convert
|
|
; these values into microseconds. This is pretty contorted
|
|
; because the unit of VIA ticks is not a rational number.
|
|
|
|
moveq.l #(1<<TickScale)-1,d2 ; d2 := virtual tick rounding factor
|
|
add.l d2,d1 ; round to virtual ticks
|
|
asr.l #TickScale,d1 ; convert to virtual ticks by discading LSBs
|
|
move.l CurrentTime(a0),d2 ; d2 := time of next interrupt
|
|
sub.l d1,d2 ; correct with value in VIA
|
|
|
|
lea HighUSecs(a0),a1 ; form pointer for speed
|
|
move.l (a1)+,d3 ; d3.l := High 32 bits of uSeconds
|
|
move.w (a1)+,d4 ; d4.w := Next 16 bits of uSeconds
|
|
move.l (a1)+,d5 ; d5.l := 16 LSBs of uSec and 16 bit fraction
|
|
swap d0 ; put VIA LSBs in d0.hi
|
|
move.w (a1)+,d0 ; d0.w := threshold
|
|
|
|
move.w (sp)+,sr ; now we can enable interrupts
|
|
|
|
; Recall that MicroSeconds are maintained internally as an 80 bit
|
|
; number with 64 bits of mantissa and 16 bits of fraction.
|
|
;
|
|
; The first part of the conversion is to do a huge division by
|
|
; repeated subtraction. We look at the CurrentTime and the
|
|
; Threshold and increment the Threshold by a value X until it
|
|
; is greater than the CurrentTime. Each time we add X to the
|
|
; Threshold we add Y to the 80 bit microsecond counter. X and Y
|
|
; have been carefully chosen so that X is as close to 2^12 as
|
|
; possible and is such that X * Via_Tick_Units is exactly
|
|
; representable in a 32.16 fixed point value.
|
|
|
|
check_threshold:
|
|
cmp.w d0,d2 ; compare CurrentTime to Threshold
|
|
bmi.s threshold_ok
|
|
addi.w #ThreshInc,d0 ; update threshold
|
|
addi.l #USecsInc,d5 ; update Microseconds
|
|
bcc.s check_threshold
|
|
addq.w #1,d4 ; propagate carry
|
|
bcc.s check_threshold
|
|
addq.l #1,d3 ; propagate carry
|
|
bra.s check_threshold
|
|
|
|
threshold_ok:
|
|
swap d4
|
|
swap d5
|
|
move.w d5,d4 ; d3:d4 := 64 bit microseconds count, nearly
|
|
swap d5 ; d5 := 16 bit fractional part
|
|
|
|
; At this point:
|
|
; D0.Hi => lower 8 bits of the upper word are the low VIA byte
|
|
; D0.Lo => Updated Threshold
|
|
; D2 => Updated CurrentTime
|
|
; D3.L => 32 high order bits of microseconds counter
|
|
; D4.L => 32 low order bits of microseconds counter
|
|
; D5.W => 16 bits of microseconds fraction
|
|
|
|
|
|
adjust_for_residual:
|
|
|
|
; Now we need to use the remaining part of Threshold and the four
|
|
; LSBs of the VIA timer to provide further accuracy.
|
|
|
|
move.l d0,d1 ; d1.hi := VIA LSBs
|
|
swap d1 ; d1.lo := VIA LSBs
|
|
neg.b d1 ; convert it to be additional time
|
|
ror.l #TickScale,d1 ; move it into high byte for insertion
|
|
|
|
sub.w d0,d2 ; CurrentTime := CurrentTime - CurTimeThresh
|
|
addi.w #ThreshInc,d2 ; compute additional time
|
|
move.w d2,d1 ; combine with VIA MSBs
|
|
rol.l #TickScale,d1 ; convert to un-scaled VIA time
|
|
mulu.w #InternalToUs>>16,d1 ; convert to microseconds
|
|
if 32-IntToUsFractBits-TickScale <> 1 then
|
|
lsl.l #32-IntToUsFractBits-TickScale,d1 ; align to form 16.16 fixed point result
|
|
else
|
|
add.l d1,d1 ; align to form 16.16 fixed point result
|
|
endif
|
|
add.w d5,d1 ; add in FractUSecs, set ccr.x to carry out
|
|
clr.w d1 ; clear out fraction bits
|
|
swap d1 ; get additional µsecs
|
|
addx.l d1,d4 ; add additional time to LowUSecs
|
|
subx.l d1,d1 ; -1 if ccr.x = 1, 0 if ccr.x = 0
|
|
sub.l d1,d3 ; propagate carry into HighUSecs
|
|
|
|
movea.l d3,a0 ; put result in result registers
|
|
move.l d4,d0
|
|
movem.l (sp)+,d3-d5 ; restore saved registers
|
|
rts ; all done <4>
|
|
|
|
endproc
|
|
|
|
TITLE 'Time Manager - Freeze Time'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: FreezeTime
|
|
; Inputs: none
|
|
; Outputs: A2 - ptr to TimeMgrPrivate
|
|
; D3.hi - saved SR (interrupt priority)
|
|
; D3.lo - value read from the low byte of the VIA timer masked
|
|
; to virtual ticks.
|
|
; Destroys: D1, D2, A1
|
|
; Calls: none
|
|
; Called by: RmvTime, PrimeTime, Timer2Int
|
|
;
|
|
; Function: Saves current interrupt priority level, disables all interrupts.
|
|
; Reads the VIA timer 2, adjusts tmCount field of all active
|
|
; Time Manager Tasks to reflect actual time remaining.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
FreezeTime proc export
|
|
|
|
with TimeMgrPrivate
|
|
|
|
move.w sr,d3 ; save interrupt priority level
|
|
swap d3 ; sr -> high word, zero -> low word
|
|
move.w #$0100-(1<<TickScale),d3; setup virtual tick mask
|
|
movea.l VIA,a1 ; get base address of VIA1
|
|
lea vT2CH(a1),a1 ; point to timer high byte for speed
|
|
movea.l TimeVars,a2 ; point to TimeMgrPrivate
|
|
ori.w #$0700,sr ; disable all interrupts
|
|
if forROM then
|
|
if hasPowerMgr then
|
|
eieioSTP
|
|
tst.w Clock16M ; force processor out of idle.
|
|
eieioSTP
|
|
endif
|
|
else
|
|
|
|
; NOP this out on machines without Power Mgr. chips
|
|
|
|
export PoundThreeNOPsHereIfNoPowerMgr
|
|
PoundThreeNOPsHereIfNoPowerMgr:
|
|
eieioSTP
|
|
tst.w Clock16M ; force processor out of idle.
|
|
eieioSTP
|
|
|
|
endif
|
|
|
|
; The VIA Timer is constantly decrementing, and after the high byte is read, but
|
|
; before the low byte is read, the low byte may decrement from 00 -> FF, which
|
|
; would mean that the high is off by one, which would be an error of 256 ticks.
|
|
; By reading the high byte twice, we can detect and correct this situation.
|
|
; We also correct for the case where the high byte counted from 00 -> FF, in which
|
|
; case the interrupt pending bit may have been incorrect at the time we read it.
|
|
|
|
eieioSTP
|
|
move.b vT2CH-vT2CH(a1),d2 ; get high byte of counter
|
|
eieioSTP
|
|
moveq.l #1<<T2IntBit,d1 ; setup mask to check int pending
|
|
eieioSTP
|
|
and.b vIFR-vT2CH(a1),d1 ; get interrupt pending bit
|
|
eieioSTP
|
|
neg.l d1 ; d1.hi := -1 if int pending, else 0
|
|
if use68020Opcodes then ; 68020 shifting is fast
|
|
move.b d2,d1 ; insert timer high byte
|
|
lsl.w #8,d1 ; position it in the right place
|
|
else ; 68000 shifting is slower than memory
|
|
move.b d2,-(sp) ; insert timer high byte
|
|
move.w (sp)+,d1 ; position it in the right place
|
|
endif
|
|
eieioSTP
|
|
move.b vT2C-vT2CH(a1),d1 ; insert timer low byte
|
|
eieioSTP
|
|
sub.b vT2CH-vT2CH(a1),d2 ; see if high byte changed
|
|
eieioSTP
|
|
beq.s @TimeOK ; if not, time is correct
|
|
|
|
subx.l d2,d2 ; d2 := -1 if counted through zero
|
|
or.l d2,d1 ; update sign bits
|
|
if use68020Opcodes then ; 68020 shifting is fast
|
|
eieioSTP
|
|
move.b vT2CH-vT2CH(a1),d1 ; re-read timer high byte
|
|
eieioSTP
|
|
lsl.w #8,d1 ; position it in the right place
|
|
else ; 68000 shifting is slower than memory
|
|
eieioSTP
|
|
move.b vT2CH-vT2CH(a1),-(sp) ; re-read timer high byte
|
|
eieioSTP
|
|
move.w (sp)+,d1 ; position it in the right place
|
|
endif
|
|
eieioSTP
|
|
move.b vT2C-vT2CH(a1),d1 ; insert timer low byte
|
|
eieioSTP
|
|
@TimeOK
|
|
eieioSTP
|
|
move.b d1,TimerLowSave(a2) ; save low byte of VIA timer <4>
|
|
moveq.l #(1<<TickScale)-1,d2 ; prepare to round to virtual ticks
|
|
add.l d2,d1 ; if virtual tick not complete, don't count it
|
|
and.b d1,d3 ; save VIA timer truncated to virtual ticks
|
|
asr.l #TickScale,d1 ; convert ticks to internal time form
|
|
sub.l d1,CurrentTime(a2) ; update current time
|
|
|
|
move.w CurrentTime+2(a2),d2 ; get low word of current time <4>
|
|
@chkThresh lea CurTimeThresh(a2),a1 ; point to CurTimeThresh <4>
|
|
cmp.w (a1),d2 ; see if next threshold has been reached <4>
|
|
bmi.s @threshOK ; if not reached yet, don't need to increment <4>
|
|
addi.w #ThreshInc,(a1) ; update the next threshold <4>
|
|
addi.l #USecsInc,-(a1) ; update microsecond counter (low word, and fract) <4>
|
|
bcc.s @chkThresh ; if no carry to prop, see if thresh adjusted <4>
|
|
addq.w #1,-(a1) ; propagate the carry <4>
|
|
bcc.s @chkThresh ; if no carry to prop, see if thresh adjusted <4>
|
|
addq.l #1,-(a1) ; propagate the carry <4>
|
|
bra.s @chkThresh ; see if threshold adjusted <4>
|
|
@threshOK ; <4>
|
|
move.l ActivePtr(a2),d2 ; get pointer to first active timer task
|
|
beq.s @UpdateBacklog ; if no active tasks, no backlog (d2=0)
|
|
|
|
movea.l d2,a1 ; setup pointer to task at head
|
|
moveq.l #0,d2 ; assume new backlog will be zero
|
|
sub.l BackLog(a2),d1 ; adjust the timer, account for backlog
|
|
bpl.s @Early ; extra time, no backlog of any kind
|
|
|
|
add.l tmCount(a1),d1 ; tmCount := tmCount - backlog
|
|
bcs.s @UpdateHead ; if no remaining backlog, use remaining count
|
|
sub.l d1,d2 ; setup remaining backlog
|
|
moveq.l #0,d1 ; no remaining time
|
|
@UpdateHead move.l d1,tmCount(a1) ; setup new time remaining
|
|
@UpdateBacklog
|
|
move.l d2,BackLog(a2) ; update backlog time, if any
|
|
rts ; all done
|
|
|
|
@Early add.l tmCount(a1),d1 ; add excess time to time remaining
|
|
bra.s @UpdateHead ; update the time remaining counter
|
|
endproc
|
|
TITLE 'Time Manager - Initialize Time Manager'
|
|
|
|
;_______________________________________________________________________
|
|
;
|
|
; Routine: InitTimeMgr
|
|
; Inputs: none
|
|
; Outputs: none
|
|
; Destroys: none
|
|
; Calls: none
|
|
; Called by: StartInit
|
|
;
|
|
; Function: Allocates and initializes the Time Manager's global data
|
|
; structures at system boot time. Sets up VIA 1 Timer2
|
|
; interrupt handler. Computes the timer adjustment value
|
|
; at runtime, since it is processor speed dependent.
|
|
;
|
|
;_______________________________________________________________________
|
|
|
|
InitTimeMgr proc export
|
|
|
|
import Timer2Int
|
|
import ThawTime
|
|
with TimeMgrPrivate
|
|
|
|
@SavedRegs reg a0-a4/d0-d5 ; registers to preserve
|
|
movem.l @SavedRegs,-(sp) ; save the registers
|
|
|
|
moveq.l #PrivateSize,d0 ; size to allocate
|
|
_NewPtr ,SYS,CLEAR ; allocate and clear the structure
|
|
move.l a0,TimeVars ; setup pointer to private storage
|
|
movea.l a0,a2 ; setup for ThawTime
|
|
|
|
if forROM then
|
|
move.w #(1<<(TickScale-1))\ ; round up before conversion
|
|
+(1<<TickScale),RetryAdjust(a2) ; add in some extra time
|
|
else
|
|
moveq #(1<<(TickScale-1))\ ; round up before conversion
|
|
+(2<<TickScale),d0 ; add in some extra time (value for 68000)
|
|
tst.b CPUFlag ; what processor do we have?
|
|
bz.s @gotRetryAdjust ; 68000, use value computed above
|
|
moveq #(1<<(TickScale-1))\ ; round up before conversion
|
|
+(1<<TickScale),d0 ; add in some extra time (value for 68020)
|
|
@gotRetryAdjust
|
|
move.w d0,RetryAdjust(a2) ; store the computed value
|
|
endif
|
|
|
|
lea Timer2Int,a1 ; get interrupt handler address
|
|
move.l a1,Lvl1DT+(T2IntBit*4) ; put into interrupt table
|
|
|
|
move.w sr,-(sp) ; save interrupt level
|
|
ori.w #$0700,sr ; disable interrupts
|
|
|
|
movea.l Via,a4 ; get VIA pointer
|
|
lea vT2C(a4),a1 ; point to low byte of timer 2 for speed
|
|
lea vT1C(a4),a3 ; point to low byte of timer 1 for speed
|
|
lea vACR(a4),a4 ; point to AUX control reg for speed
|
|
|
|
moveq.l #-$80+(1<<T2IntBit),d1 ; enable timer 2 interrupts
|
|
eieioSTP
|
|
move.b d1,vIER-vT2C(a1) ; initialize the interrupt enable bit
|
|
eieioSTP
|
|
|
|
moveq.l #~(1<<5),d5 ; mask to set timer 2 into timed interrupt mode
|
|
eieioSTP
|
|
and.b vACR-vACR(a4),d5 ; save old AUX control reg
|
|
eieioSTP
|
|
moveq.l #%00111111,d0 ; force timer 1 into one-shot mode
|
|
and.b d5,d0 ; clear the bits
|
|
eieioSTP
|
|
move.b d0,vACR-vACR(a4) ; setup temporary AUX control reg
|
|
eieioSTP
|
|
moveq.l #0,d3 ; setup timer low byte value
|
|
move.w sr,d3 ; get sr
|
|
swap d3 ; sr in high word
|
|
|
|
eieioSTP
|
|
move.b d1,vT1CH-vT1C(a3) ; load and start timer 1
|
|
eieioSTP
|
|
move.b d1,vT2CH-vT2C(a1) ; load and start timer 2
|
|
eieioSTP
|
|
move.b vT2C-vT2C(a1),d0 ; get timer 2 low byte
|
|
eieioSTP
|
|
sub.b vT1C-vT1C(a3),d0 ; get initial timer skew
|
|
eieioSTP
|
|
jsr ThawTime ; run the critical section, start timer
|
|
eieioSTP
|
|
move.b vT2C-vT2C(a1),d1 ; get timer 2 low byte
|
|
eieioSTP
|
|
sub.b vT1C-vT1C(a3),d1 ; get final timer skew
|
|
eieioSTP
|
|
|
|
sub.b d0,d1 ; don't count initial skew
|
|
subi.w #(($FFFF>>TickScale)<<TickScale),d1 ; subtract out loaded timer value
|
|
move.b d1,TimerAdjust(a0) ; setup the fudge factor
|
|
|
|
eieioSTP
|
|
move.b d5,vACR-vACR(a4) ; restore AUX control reg
|
|
eieioSTP
|
|
st vT2CH-vT2C(a1) ; load and start timer 2
|
|
eieioSTP
|
|
move.w (sp)+,sr ; restore interrupt level
|
|
movem.l (sp)+,@SavedRegs ; restore the registers
|
|
rts ; Time Manager is initialized
|
|
endproc
|
|
end
|
|
|