;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                                                                   ;
;  DSKTIM.M68 - Disk timing program (combines REDALL,RNDRED,REDEND) ;
;                                                                   ;
;    Usage:  DSKTIM dev0: reads /S/R/L/E /D:n /B:n /P:n /N          ;
;                                                                   ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; (c) Bob Fowler, 08/01/86, runs on any AMOSL 1.0 or later.
; Permission is given to copy and use this program, but not for profit.
; The references to REDEND refer to REDEND.M68, published in SF/AMUS.

; Explanation of options:
;   /S - is the fastest read possible (sequential reads, like REDALL)
;   /R - is a typical worst case (random reads, like RNDRED)
;   /L - same as /R, but use the AMOSL method if possible (<65536 blocks)
;   /E - is the slowest possible case (read first/last blocks, REDEND)
; Other options:
;   /D:n   - simulates a physical device with n logical devices
;   /B:n   - simulates a logical device with n blocks
;   /B:a-b - simulates device from blocks a to b (>0)
;   /P:n   - displays progress reports every n reads (beware, slows timing)
;   /N     - no displays except for final timings
; Both /B and /D allow read ranges beyond one logical drive.
; If neither /B nor /D appear, the read range defaults to the given drive.

; A respresentative set of timings for each device is as follows:
;   DSKTIM  2000 dev0: /S/N     (only one short timing necessary)
;   DSKTIM  2000 dev0: /E/N/D:n (several short timings necessary)
;   DSKTIM 10000 dev0: /R/N/D:n (several longer timings necessary)
; where a separate timing is done for each of n = 1,2,4,8,16,32,max,min
; (max and min being the maximum and minimum "allowed" logical devices).

; If you do timings on any interesting disks (eg, non-Alpha Micro drives),
; please phone the results to Bob Fowler at 415-527-7631,
; and I will ad them to my growing list (goes back to Persci).

; Current liberal limitations:
;   (a) a timing longer than 23.8 hours will calculate wrong average time
;   (b) only 65536 drives on one device; DSK65536: will not work.

; Notes on usage
;   (a) Timings are only in seconds, at least 100 seconds is recommended.
;   (b) On random reads, at least 10000 reads are suggested.
;       To monitor the convergence behavior of /R and /L timings,
;       do a test run with /P:100, then do a final run without /P.
;   (c) Note that /B:1 can be used to read the same block repeatedly.
;       /B:68 can be used to read the same track (on an AM-1000 10MB).
;       /E/B:69 or /E/B:136 can be used to always seek one track.
;       /B:a-b can be used to randomly read one file, or one area of a disk.
;   (d) Note that /D:1/R simulates random access of all devices.

; Notes on random read methods.
;   Two random read methods are available, via switches /L and /R.
;   (a) /L uses the same method as AMOSL RNDRED.LIT.
;          This method is limited to 16-bit block ranges (65536 or less).
;          It uses AMOSL.MON to generate random numbers as follows.
;             Start X as garbage, then repeat the following ad infinitum:
;             MOD ( 2*X + monitor word 1 + monitor word 2 , 65536) / blocks
;             divides to a quotient Q and remainder R, read block R,
;             set X = Q + R, and repeat loop.
;   (b) /R uses a method based on Knuth (Semi-Numerical Algorithms, p1-24).
;          It generates 32-bit pseudo-random numbers via the expression:
;          X = ( (X + monitor word) x (1 + 2^2 + 2^14 + 2^23) + 1) MOD 2^32
;          The monitor word can be left out, but is currently included.
;   The tradeoffs: /R is more versatile, but has more CPU overhead.

; CPU overhead (ie, all operations other than the READ monitor calls):
;   /S =   .044 ms/read
;   /E =   .029 ms/read
;   /L =   .071 ms/read
;   /R =   .534 ms/read (/B:1) , .397 (/B:1000) , .337 (/B:8000000)
;   /P = 16.6   ms per line displayed (one disk rotation) on the AM-1000
; Note the relatively large overhead for /R (because of 32-bit divisions).
; Any difference between /R and /L is undetectable in /B:1 and /B:68 ranges.
; Wide ranges are difficult to test definitively, due to randomness,
; but in several 4-hour timing comparisons of /L and /R in the /D:2 range,
; /R was consistently about 1.6% (1.3 ms/read) slower than /L.
; This is either due to /R overhead, or defects in one/both random algorithms.
; The overhead for /P does affect timings, but in a very regular fashion.
; Each /P line on the AM-1000 adds 16.6 ms (one disk rotation) to the timing.
; Hence, /P should not be used during a definitive timing run.


; Use of registers:
;   A0 = base of impure work area
;   A1 = device driver address
;   A2 = [user input command string]
;   A3,A4, D0,D1,D2,D3 = [work areas]
;   D4 = random generator (during random read)
;      = blocks on logical device (sequential read)


       COPY    SYS
       SEARCH  SYSSYM

DSKTIM: BYP                             ; bypass blanks
       LIN                             ; empty?
       JEQ     USAGE                   ;   yes - missing device

;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                         ;
;  #1 - process filespec  ;
;                         ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;

GETROM: GETIMP  MEMLTH,A0               ; get impure user work area
       MOV     #-1,D6                  ; get only device code (devn:)
       FSPEC   DDB(A0)                 ; process devn: input
       INIT    DDB(A0)                 ; get I/O buffer
       MOV     DDB+D.DVR(A0),A1        ; device driver address
       MOV     DD.DSZ(A1),LOGBLK(A0)   ; blocks on drive (undocumented)
       MOV     A3,AMRLIM(A0)           ; A3 used in AMOSL RNDRED algorithm

; For non-winchesters, physical blocks = logical blocks
       MOVW    #1,TOTLOG(A0)           ; default to 1 logical device
       MOV     LOGBLK(A0),PHYBLK(A0)   ; default physical to logical blocks
       CLRW    HCS(A0)                 ; Head/Cylinder/Sector not used
       CMPW    70(A1),#170707          ; Winchester?
       BEQ     GETPHY                  ;   yes - get physical blocks
; The following is used by Xebec drivers
       CMPW    120(A1),#70707          ; Winchester?
       BEQ     GETPHY                  ;   yes - get physical blocks
       BR      GOTPHY                  ;   no - already have physical blocks

; For winchesters, physical blocks = heads x cylinders x sectors
GETPHY: MOVW    110(A1),TOTLOG(A0)      ; save total # of logical drives
       CLR     D0                      ; clear upper word
       MOVW    74(A1),D0               ; D0 = heads
       CLR     D1                      ; clear upper word
       MOVW    100(A1),D1              ; sectors
       MULU    D0,D1                   ; D0 = heads * sectors
       MOVW    76(A1),D1               ; D1 = cylinders
       MULU    D0,D1                   ; D0 = heads * sectors * cylinders
       MOV     D0,PHYBLK(A0)           ; Total physical blocks
       MOVW    #1,HCS(A0)              ; Head/Cylinder/Sector used
GOTPHY: MOVW    TOTLOG(A0),USRLOG(A0)   ; User logicals defaults to total log

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                           ;
;  #2 - process read count  ;
;                           ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Get number of reads from user input (defaults to 2^32-1)
       MOV     #-1,D1                  ; default lasts 414+ days ...
       BYP                             ; bypass blanks
       LIN                             ; anything there ?
       BEQ     GOTRDS                  ;   no - use default
       CMPB    @A2,#'/                 ; is next character "/" ?
       BEQ     GOTRDS                  ;   yes - use default
       GTDEC                           ; get user input
GOTRDS: MOV     D1,REQRDS(A0)           ; save

;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                         ;
;  #3 - process switches  ;
;                         ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Initialize default switch values
       CLRW    REDALL(A0)              ; don't do REDALL
       CLRW    RNDRED(A0)              ; don't do RNDRED
       CLRW    REDEND(A0)              ; don't do REDEND
       MOVW    #1,DSPFLG(A0)           ; do displays
       MOV     #-1,DSPRAT(A0)          ; don't do progress displays
       MOV     DD.DSZ(A1),REQRNG(A0)   ; range defaults to logical drive
       MOV     #1,BOTBLK(A0)           ; default first block is one
       MOV     DD.DSZ(A1),TOPBLK(A0)   ; default last block is logical size

SWLOOP: BYP                             ; bypass blanks
       LIN                             ; anything there?
       JEQ     SWEND                   ;   no - user input complete
       CMPB    (A2)+,#'/               ; slash?
       JNE     USAGE                   ;   no - invalid input

       CMPB    @A2,#'S                 ; /S ?
       BNE     SWR                     ;   no - try another
       MOVW    #1,REDALL(A0)           ; do REDALL
SWBYP:  ADD     #1,A2                   ; pass over /S
       BR      SWLOOP                  ; look for another switch

SWR:    CMPB    @A2,#'R                 ; /R ?
       BNE     SWL                     ;   no - try another
       MOVW    #1,RNDRED(A0)           ; do RNDRED
       BR      SWBYP                   ; look for another switch

SWL:    CMPB    @A2,#'L                 ; /L ?
       BNE     SWE                     ;   no - try another
       MOVW    #1,RNDRED(A0)           ; do RNDRED
       MOVW    #1,AMOSL(A0)            ; use AMOSL method
       BR      SWBYP                   ; look for another switch

SWE:    CMPB    @A2,#'E                 ; /E ?
       BNE     SWN                     ;   no - try another
       MOVW    #1,REDEND(A0)           ; do REDEND
       BR      SWBYP                   ; look for another switch

SWN:    CMPB    @A2,#'N                 ; /N ?
       BNE     SWD                     ;   no - try another
       CLRW    DSPFLG(A0)              ; don't do displays
       BR      SWBYP                   ; look for another switch

; /D:n switch - emulate any number of logical devices
SWD:    CMPB    @A2,#'D                 ; /D ?
       BNE     SWB                     ;   no - try another
       ADD     #1,A2                   ; skip past "D"
       CMPB    (A2)+,#':               ; ":" ?
       JNE     USAGE                   ;   no - invalid format
       GTDEC                           ; emulated number of logical devices
       MOVW    D1,USRLOG(A0)           ; save (from longword to word)
       JEQ     USAGE                   ; zero logical devices illegal
       MOV     PHYBLK(A0),D0           ; recall total physical blocks
       CALL    DIVIDE                  ; extended divide routine (D2=D0/D1)
       MOV     D2,REQRNG(A0)           ; quotient is requested block range
       MOV     BOTBLK(A0),D0           ; first block in requested range
       ADD     D2,D0                   ; + requested range
       SUB     #1,D0                   ; - 1
       MOV     D0,TOPBLK(A0)           ; = last block in requested range
       JMP     SWLOOP                  ; look for another switch

; /B:n - range of blocks to read
SWB:    CMPB    @A2,#'B                 ; /B ?
       BNE     SWP                     ;   no - try another
       ADD     #1,A2                   ; skip past "B"
       CMPB    (A2)+,#':               ; ":" ?
       JNE     USAGE                   ;   no - invalid format
       GTDEC                           ; input first block range number
       TST     D1                      ; zero?
       JEQ     USAGE                   ;   yes - not allowed
; one input number n indicates range 1 to n
       MOV     #1,BOTBLK(A0)           ; first block defaults to 1
       MOV     D1,TOPBLK(A0)           ; last block is user input
       CMPB    @A2,#'-                 ; "-" ?
       BNE     FINSWB                  ;   no - done
; two input numbers a-b indicate range a to b
       MOV     D1,BOTBLK(A0)           ; first block in requested range
       ADD     #1,A2                   ; skip past "-"
       GTDEC                           ; input second block range number
       MOV     D1,TOPBLK(A0)           ; last block in requested range
FINSWB: SUB     BOTBLK(A0),D1           ; range = last - first
       ADD     #1,D1                   ; + 1
       MOV     D1,REQRNG(A0)           ; range of requested blocks
       JMP     SWLOOP                  ; look for another switch

; /P:n switch - display progress reports
SWP:    CMPB    @A2,#'P                 ; /P ?
       BNE     SWZ                     ;   no - try another
       ADD     #1,A2                   ; skip past "P"
       CMPB    (A2)+,#':               ; ":" ?
       JNE     USAGE                   ;   no - invalid format
       GTDEC                           ; reads between displays
       MOV     D1,DSPRAT(A0)           ; save
       JMP     SWLOOP                  ; look for another switch

; invalid switch
SWZ:    JMP     USAGE                   ; none of the above ...

SWEND:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                          ;
;  #4 - display disk data  ;
;                          ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Display physical device parameters
       TSTW    DSPFLG(A0)              ; do display?
       JEQ     ENDSP1                  ;   no - skip displays
       TYPE    <Physical device is>
       MOV     PHYBLK(A0),D1           ; physical blocks
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPE    <blocks>
       TSTW    HCS(A0)                 ; Head/Cylinder/Sector used?
       BEQ     FINHCS                  ;   no - skip rest of line
       TYPE    < (>
       CLR     D1                      ; clear upper word
       MOVW    74(A1),D1               ; heads
       DCVT    0,OT$TRM!OT$TSP
       TYPE    <heads x>
       MOVW    76(A1),D1               ; cylinders
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPE    <cylinders x>
       MOVW    100(A1),D1              ; sectors
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPE    <sectors)>
FINHCS: CRLF

; Display logical device parameters
       TYPE    <Logical device is>
       MOV     LOGBLK(A0),D1           ; logical blocks
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPE    <blocks>
       TSTW    HCS(A0)                 ; Head/Cylinder/Sector used?
       BEQ     FINLOG                  ;   no - skip rest of line
       TYPE    < (>
       MOV     PHYBLK(A0),D1           ; physical blocks
       DCVT    0,OT$TRM!OT$TSP
       MOVB    #'/,D1                  ; can't put into TYPE
       TTY
       CLR     D1                      ; clear upper word
       MOVW    TOTLOG(A0),D1           ; logical devices
       DCVT    0,OT$TRM!OT$LSP
       TYPE    <)>
FINLOG: CRLF

ENDSP1:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                          ;
;  #5 - set up read range  ;
;                          ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Range may span more than 1 logical device - allow for this
       MOVW    DDB+D.DRV(A0),BOTDRV(A0); bottom drive defaults to input
       MOVW    DDB+D.DRV(A0),TOPDRV(A0); top drive defaults to input
       MOV     REQRNG(A0),D1           ; final block range
       MOV     D1,ACTRNG(A0)           ; actual range (default value)
       MOV     BOTBLK(A0),D0           ; first block in requested range
       SUB     #1,D0                   ; blocks go from 0 to X-1
       MOV     D0,BOTREC(A0)           ; bottom record of actual range
       ADD     D0,D1                   ; + requested range
       SUB     #1,D1                   ; - 1 = last block in actual range
       MOV     D1,TOPREC(A0)           ; top record of actual range
       CLR     NOTAVL(A0)              ; default to 0 blocks not available

CHKLOG: CMP     D1,LOGBLK(A0)           ; range beyond this logical device?
       BLO     GOTRNG                  ;   no - range calculated
       CLR     D0                      ; clear upper word
       MOVW    TOPDRV(A0),D0           ; pick up current drive
       ADD     #1,D0                   ; convert to range starting with 1
       CMPW    D0,TOTLOG(A0)           ; it is beyond last drive?
       BHIS    OVRFLO                  ;   yes - user request not possible
       ADDW    #1,TOPDRV(A0)           ; top drive goes to next logical
       SUB     LOGBLK(A0),D1           ; reduce block number
       MOV     D1,TOPREC(A0)           ; save record number
       BR      CHKLOG                  ; check next logical drive

; Too many blocks requested.  This can even happen on a simple /D:n request.
; If (eg) physical drive has 3002 blocks, in 3 logical devices,
; each logical device has 1000 blocks, and last 2 blocks are "unavailable".
OVRFLO: SUB     LOGBLK(A0),D1           ; overflow block count
       ADD     #1,D1                   ; starts at 0
       MOV     LOGBLK(A0),TOPREC(A0)   ; top record number
       SUB     #1,TOPREC(A0)           ; blocks go from 0 to X-1
       SUB     D1,ACTRNG(A0)           ; subtract overflow from actual range
       MOV     D1,NOTAVL(A0)           ; save overflow count
GOTRNG:

; AMOSL method is possible if first and last device are same
       TSTW    AMOSL(A0)               ; is /L selected?
       BEQ     FINAML                  ;   no - no problem
;       CLR     D0                      ; clear upper word
       MOVW    BOTDRV(A0),D0           ; pick up first drive number
       CMPW    D0,TOPDRV(A0)           ; same as last drive?
       BEQ     FINAML                  ;   yes - no problem
       CLRW    AMOSL(A0)               ;   no - can't use AMOSL method
       TYPECR  <AMOSL method not possible>
FINAML:

; Displays
       TSTW    DSPFLG(A0)              ; do display?
       JEQ     ENDSP2                  ;   no - skip displays
; Block range requested
       MOV     REQRNG(A0),D1           ; block range requested
       DCVT    0,OT$TRM
       TYPECR  <-block range requested>
; Range overflow
       MOV     NOTAVL(A0),D1           ; get overflow count
       BEQ     FINAVL                  ; zero - no display
       TYPE    <Last>                  ; tell user what happened
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPECR  <blocks not available>
FINAVL:
; Actual test range
       TYPE    <Test range is>         ; display range used
       MOV     ACTRNG(A0),D1           ; final block range
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPE    <blocks (>
       PUSH    A1
       LEA     A1,DDB+D.DEV(A0)        ; location of RAD50 device name
       LEA     A2,PRTDEV(A0)           ; location of ASCII output area
       UNPACK                          ; device name in ASCII (dev)
       CLRB    @A2                     ; following by a null
       POP     A1
       SUB     #3,A2                   ; go back to beginning of string
       TTYL    @A2
       CLR     D1                      ; clear upper word
       MOVW    BOTDRV(A0),D1           ; bottom drive #
       DCVT    0,OT$TRM
       TYPE    <:>
       MOV     BOTREC(A0),D1           ; bottom record #
       OCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPE    <to >
       TTYL    @A2
       CLR     D1                      ; clear upper word
       MOVW    TOPDRV(A0),D1           ; top drive #
       DCVT    0,OT$TRM
       TYPE    <:>
       MOV     TOPREC(A0),D1           ; top record #
       OCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPECR  <octal)>

ENDSP2:

;;;;;;;;;;;;;;;;;;;
;                 ;
;  #6 - do reads  ;
;                 ;
;;;;;;;;;;;;;;;;;;;

; REDALL routine - preserve D4
       TSTW    REDALL(A0)              ; do a REDALL?
       JEQ     ENDALL                  ;   no - skip to end
       CALL    DOCRLF
       TYPE    <REDALL - >
       MOV     ACTRNG(A0),D1           ; actual range
       MOV     REQRDS(A0),D0           ; requested reads
       CMP     D0,#-1                  ; request number entered?
       BEQ     BEGALL                  ;   no - use actual range
       CMP     D0,D1                   ; requested reads too high?
       BHI     BEGALL                  ;   yes - use actual range
       MOV     D0,D1                   ;   no - use requested reads
BEGALL: CALL    BEFORE
       MOVW    BOTDRV(A0),DDB+D.DRV(A0); bottom drive number
       MOV     BOTREC(A0),DDB+D.REC(A0); bottom block number
       MOV     LOGBLK(A0),D4           ; logical blocks on drive
NXTALL: CTRLC   FINALL                  ; check for impatient user ...
       READ    DDB(A0)                 ; read!
       ADD     #1,CURRDS(A0)           ; one more read done
       SUB     #1,REMRDS(A0)           ; one less read to do
       BNE     CNTALL                  ; skip display if countdown not 0
       CALL    AFTER                   ; display progress
       BEQ     ENDALL                  ; if done, don't continue
CNTALL:
       ADD     #1,DDB+D.REC(A0)        ; go to next block
       CMP     D4,DDB+D.REC(A0)        ; block beyond this drive?

BHI     NXTALL                  ;   no - keep going
       ADDW    #1,DDB+D.DRV(A0)        ;   yes - go to next logical drive
       CLR     DDB+D.REC(A0)           ; block number starts at zero
       BR      NXTALL                  ; do another read
FINALL: CALL    LSTDSP                  ; display timings
ENDALL:

; REDEND routine - preserve no regs
       TSTW    REDEND(A0)              ; do a REDEND?
       JEQ     ENDEND                  ;   no - skip to end
       CALL    DOCRLF
       TYPE    <REDEND - >
       MOV     REQRDS(A0),D1           ; requested reads
       CALL    BEFORE
NXTEND: CTRLC   FINEND                  ; check for impatient user ...
       MOVW    BOTDRV(A0),DDB+D.DRV(A0); bottom drive number
       MOV     BOTREC(A0),DDB+D.REC(A0); bottom block number
       READ    DDB(A0)                 ; read!
       ADD     #1,CURRDS(A0)           ; one more read done
       SUB     #1,REMRDS(A0)           ; one less read to do
       BNE     CNTEND                  ; skip display if countdown not 0
       CALL    AFTER                   ; display progress
       BEQ     ENDEND                  ; if done, don't continue
CNTEND:
       MOVW    TOPDRV(A0),DDB+D.DRV(A0); top drive number
       MOV     TOPREC(A0),DDB+D.REC(A0); top block number
       READ    DDB(A0)                 ; read!
       ADD     #1,CURRDS(A0)           ; one more read done
       SUB     #1,REMRDS(A0)           ; one less read to do
       BNE     NXTEND                  ; skip display if countdown not 0
       CALL    AFTER                   ; display progress
       BNE     NXTEND                  ; if done, don't continue
       BR      ENDEND                  ; don't display again
FINEND: CALL    LSTDSP                  ; display timings
ENDEND:

; RNDRED routine - preserve A3,A4,D4
       TSTW    RNDRED(A0)              ; do a RNDRED?
       JEQ     ENDRND                  ;   no - skip to end
       CALL    DOCRLF
       TSTW    AMOSL(A0)               ; use AMOSL method?
       JNE     AMRND                   ;   yes - go to it

       TYPE    <RBF RNDRED - >
       MOV     REQRDS(A0),D1           ; requested reads
       CALL    BEFORE
       MOV     MEMBAS,A4               ; use monitor up to first user
       CLR     D4                      ; start random seed
CYCLE1: MOV     #SYSTEM,A3              ; base of AMOSL.MON
NXTRND: CTRLC   FINRND                  ; check for impatient user ...
       MOV     D4,D2                   ; D4 x 2^0
; following computes D4 = (D4 x (1 + 2^2 + 2^14 + 2^23) + 1) MOD 2^32
       ASL     D2,#2                   ; D4 x 2^2
       ADD     D2,D4
       ASL     D2,#6
       ASL     D2,#6                   ; D4 x 2^14
       ADD     D2,D4
       ASL     D2,#3
       ASL     D2,#6                   ; D4 x 2^23
       ADD     D2,D4
       ADD     #1,D4                   ; +1
; 32-bit number / physical blocks ===> remainder is random block
       MOV     D4,D0                   ; random number
       MOV     ACTRNG(A0),D1           ; / block range
       CALL    DIVIDE                  ; D0 / D1 ===> D2 rem D0
; block / logical blocks ===> quotient (drive #) rem (logical block)
       ADD     BOTREC(A0),D0           ; may not start at block 0
       MOV     LOGBLK(A0),D1           ; logical blocks
       CALL    DIVIDE                  ; D0 / D1 ===> D2 rem D0
       ADDW    BOTDRV(A0),D2           ; may not start at drive 0
       MOVW    D2,DDB+D.DRV(A0)        ; longword to word
       MOV     D0,DDB+D.REC(A0)
       READ    DDB(A0)
       ADD     #1,CURRDS(A0)           ; one more read done
       SUB     #1,REMRDS(A0)           ; one less read to do
       BNE     CNTRND                  ; skip display if countdown not 0
       CALL    AFTER                   ; display progress
       BEQ     ENDRND                  ; if done, don't continue
CNTRND:
; To remove AMOSL.MON from random calculation, comment out next 3 lines
       ADD     (A3)+,D4                ; add in AMOSL.MON longword
       CMP     A4,A3                   ; here yet?
       BLO     CYCLE1                  ;   yes - cycle through again
       BR      NXTRND                  ;   no - just do other stuff
FINRND: CALL    LSTDSP                  ; display timings
ENDRND:
       JMP     END

; AMOSL RNDRED routine - preserve A3,A4,D4,D5
AMRND:  TYPE    <AMOSL RNDRED - >
       MOV     REQRDS(A0),D1           ; requested reads
       CALL    BEFORE
       MOV     AMRLIM(A0),A4           ; use monitor up to first user
       MOV     ACTRNG(A0),D5           ; logical blocks
CYCLE2: MOV     #SYSTEM,A3              ; start at beginning of monitor
NXTAMR: CTRLC   FINRND                  ; check for control-C exit
       ASL     D4                      ; random number x 2
       ADDW    (A3)+,D4                ;   + first monitor word
       ADDW    (A3)+,D4                ;   + second monitor word
       AND     #177777,D4              ; use only lower order word
       DIV     D4,D5                   ;   / blocks
       MOV     D4,D1                   ; get remainder (high order word)
       SWAP    D1
       AND     #177777,D1
       ADD     D1,D4                   ;   + quotient is next random #
       ADD     BOTREC(A0),D1           ; may not start at block 0
       MOV     D1,DDB+D.REC(A0)        ; next block to read
       READ    DDB(A0)                 ; read this block
       ADD     #1,CURRDS(A0)           ; one more read done
       SUB     #1,REMRDS(A0)           ; one less read to do
       BNE     CNTAMR                  ; skip display if countdown not 0
       CALL    AFTER                   ; display progress
       BEQ     ENDRND                  ; if done, don't continue
CNTAMR:
       CMP     A4,A3                   ; are we at end of used memory ?
       BLO     CYCLE2                  ;   yes - recycle through monitor
       BR      NXTAMR                  ;   no - get another word from memory

END:    EXIT

;;;;;;;;;;;;;;;;;;;
;                 ;
;  #7 - routines  ;
;                 ;
;;;;;;;;;;;;;;;;;;;

DOCRLF: TSTW    DSPFLG(A0)              ; do display?
       BEQ     CRLF1                   ;   no - skip displays
       CRLF
CRLF1:  RTN



BEFORE:
       TSTW    DSPFLG(A0)              ; do display?
       BEQ     BEFOR1                  ;   no - skip following
       TYPE    <Doing>
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPECR  <reads (control-C allowed) ...>
BEFOR1: JOBIDX  A1                      ; get address of job status word
       ANDW    #^CJ.CCC,JOBSTS(A1)     ; clear any pending control-C
       CLR     CURRDS(A0)              ; clear read counter
       MOV     D1,ACTRDS(A0)           ; save actual number of reads
       MOV     DSPRAT(A0),D0           ; pick up display rate
       CMP     D0,D1                   ; is it higher?
       BHI     BEGCNT                  ;   yes - use actual number of reads
       MOV     D0,D1                   ;   no - use display rate instead
BEGCNT: MOV     D1,REMRDS(A0)           ; start progress display countdown
       GTIMEI  D0                      ; get starting time (seconds)
       MOV     D0,BEGTIM(A0)           ; save it
       GDATEI  D0                      ; get starting date (days)
       MOV     D0,BEGDAT(A0)           ; save it
       RTN



LSTDSP: MOV     CURRDS(A0),ACTRDS(A0)   ; Control-C abort - end reads
AFTER:  MOV     CURRDS(A0),D1           ; total number of reads
       CMP     D1,ACTRDS(A0)           ; is this the last display?
       BHIS    AFTER1                  ;   yes - must display results
       TSTW    DSPFLG(A0)              ; do displays? (/N)
       JEQ     FINDSP                  ;   no - skip displays this time
AFTER1: TST     D1                      ; did zero blocks get read?
       BNE     GOTRED                  ;   no - no problem
       TYPECR  <no reads>              ;   yes - avoid division by zero
       JMP     FINDSP                  ; bypass all further display
GOTRED: DCVT    0,OT$TRM!OT$TSP
       TYPE    <disk reads in>
       GDATEI  D0                      ; get current date (days)
       SUB     BEGDAT(A0),D0           ; subtract starting date
       MULU    D0,#43200.              ; convert to seconds
       ADD     D0,D0                   ; cannot multiply by 86400 (> 65536)
       GTIMEI  D1                      ; get current time (seconds)
       ADD     D1,D0                   ; add to total
       SUB     BEGTIM(A0),D0           ; subtract beginning time (seconds)
       MOV     D0,D1
       DCVT    0,OT$TRM!OT$LSP!OT$TSP
       TYPE    <seconds >
       TST     D1                      ; positive?
       BGT     TIMEOK                  ;   yes - calculate average
       TYPECR  <(time not valid)>      ;   no - end
       BR      FINDSP
TIMEOK: TYPE    <=>
; following overflows if timings go longer than 84000 seconds (.9 days)
       MULU    D0,#50000.              ; get .02 ms units (must be < 86400)
       MOV     CURRDS(A0),D1           ; total number of reads
       CALL    DIVIDE                  ; big fat divide ...
       DIVU    D2,#50.                 ; quotient is ms, remainder is .01 ms
       CLR     D1                      ; clear upper word
       MOVW    D2,D1                   ; ms part
       DCVT    0,OT$TRM!OT$LSP         ; "nnnn"
       TYPE    <.>                     ; "."
       SWAP    D2                      ; get remainder
       MOVW    D2,D1                   ; centi-ms part
       ADD     D1,D1                   ; convert from n/50 to n/100
       DCVT    2,OT$TRM!OT$TSP         ; "nn"
       TYPECR  <ms per read>
FINDSP: MOV     ACTRDS(A0),D1           ; total reads planned
       SUB     CURRDS(A0),D1           ; - reads accomplished
       BEQ     ENDDSP                  ; done (Z bit still set on return)
       MOV     DSPRAT(A0),D0           ; pick up display rate
       CMP     D0,D1                   ; is it higher?
       BHI     SETDSP                  ;   yes - use actual number of reads
       MOV     D0,D1                   ;   no - use display rate instead
SETDSP: MOV     D1,REMRDS(A0)           ; start progress display countdown
ENDDSP: RTN                             ; read some more (Z bit is returned)



USAGE:  LEA     A2,HELP                 ; give 'em some assistance
       TTYL    @A2
       EXIT                            ; and leave

HELP:   ASCII   |Usage: DSKTIM dev0: reads /S/R/L/E /D:n/B:n/P:n/N|
       BYTE    15
       ASCII   |       /S      does a sequential read (REDALL)|
       BYTE    15
       ASCII   |       /R      does a random read (RNDRED)|
       BYTE    15
       ASCII   |       /L      does a random read using AMOSL method|
       BYTE    15
       ASCII   |       /E      does a first-last read (REDEND)|
       BYTE    15
       ASCII   |       /D:n    simulates n logical devices|
       BYTE    15
       ASCII   |       /B:n    simulates device with n decimal blocks|
       BYTE    15
       ASCII   |       /B:a-b  simulates device from blocks a to b (>0)|
       BYTE    15
       ASCII   |       /P:n    display progress every n reads (slows timing!)|
       BYTE    15
       ASCII   |       /N      no display except final timings|
       BYTE    15
       BYTE    0
       EVEN



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                               ;
;  Extended Divide Routine      ;
;                               ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; The 68000 can only divide 32 bits / 16 bits ===> 16-bit quot , 16-bit rem
; The routine below divides 32 bits / 32 bits ===> 32-bit quot , 32-bit rem
; This can be done in one instruction on the 68020

; Reg   Input          Output
; ---   -----------    ---------
; D0    Dividend       Remainder
; D1    Divisor        Divisor
; D2    [garbage]      Quotient
; D3    [garbage]      [zero]

DIVIDE:

; Following is the 68000 routine
       CLR     D2                      ; D2 = quotient (clear first)
       MOV     #1,D3                   ; D3 = current quotient bit
SHIFT1: CMP     D0,D1                   ; can we subtract?
       BLO     TEST                    ;   no - divisor is shifted enough
       ASL     D1                      ; shift again
       BCS     BACK                    ; we went too far!
       ASL     D3                      ; quotient bit similiarly shifted
       BR      SHIFT1                  ; keep going
BACK:   ROXR    D1                      ; undo last shift
TEST:   CMP     D0,D1                   ; can we subtract?
       BLO     SHIFT2                  ;   no - go shift
       SUB     D1,D0                   ; subtract divisor multiple
       ADD     D3,D2                   ; add bit to quotient
SHIFT2: LSR     D3                      ; quotient bit halved
       BEQ     ENDDIV                  ; if zero, we are done
       LSR     D1                      ; divisor multiple is halved
       BR      TEST                    ; test next quotient subtract
ENDDIV: RTN

; Following is the equivalent 68020 routine
;       DIVUL   D2:D0,D1
;       EXG     D0,D2
;       RTN



       ASECT
       .=0
; Symbols for impure work.buffer area - MUST BE PUT HERE
; (if put at beginning, FIX uses these labels instead of the PSECT labels !!!)

AMRLIM: BLKL    1               ; contents of A3 upon entering program
HCS:    BLKW    1               ; Head/Cylinder/Sector used flag
REDALL: BLKW    1               ; REDALL requested flag (/S)
RNDRED: BLKW    1               ; RNDRED requested flag (/R)
REDEND: BLKW    1               ; REDEND requested flag (/E)
AMOSL:  BLKW    1               ; Use AMOSL method for random read (/L)
DSPRAT: BLKL    1               ; Reads between each progress display (/P)
DSPFLG: BLKL    1               ; Display flag (/N)
REQRDS: BLKL    1               ; Requested number of reads (user)
ACTRDS: BLKL    1               ; Actual number of reads planned
REMRDS: BLKL    1               ; Remaining number of reads (until display)
TOTLOG: BLKW    1               ; Total logical devices in physical drive
USRLOG: BLKW    1               ; User (emulated) logical devices
LOGBLK: BLKL    1               ; Blocks on logical device (1+)
PHYBLK: BLKL    1               ; Blocks on physical device (1+)
BOTBLK: BLKL    1               ; First block in requested range (1+)
TOPBLK: BLKL    1               ; Last block in requested range (1+)
REQRNG: BLKL    1               ; Block range of reads - requested
ACTRNG: BLKL    1               ; Block range of reads - actual
CURRDS: BLKL    1               ; Current number of reads done so far
BOTDRV: BLKW    1               ; Bottom Drive Number
BOTREC: BLKL    1               ; Bottom Block Number
TOPDRV: BLKW    1               ; Top Drive Number
TOPREC: BLKL    1               ; Top Block Number
BEGTIM: BLKL    1               ; Beginning time (seconds)
BEGDAT: BLKL    1               ; Beginning date (days)
PRTDEV: BLKB    3               ; For RAD50 unpacking (ASCII)
NULL:   BLKB    1               ; For RAD50 unpacking (null)
NOTAVL: BLKL    1               ; Not available for reading
DDB:    BLKB    D.DDB           ; DDB for reading
MEMLTH:                         ; Memory used

       END