Atari ST Book: BIOS/XBIOS ROM listing

Atari ST ROM fully commented BIOS/XBIOS ROM listing for TOS 2.06, TOS 3.06 and – especially – the ST-Book ROM. I tried to collect the original comments from the Atari developers, when available. Also available on GitHub.

ROM_TOS206  EQU 1                               ; regular TOS 2.06 ROM
ROM_TOS306  EQU 0                               ; regular TOS 2.06 ROM
ROM_STBOOK  EQU 0                               ; ROM for the ST Book (variation of TOS 2.06)

osroml:     bra.s     reseth                    ; os_entry - BRA to reset handler
IF ROM_TOS306
            DC.W      $0306                     ; os_version - TOS version number
ELSE
            DC.W      $0206                     ; os_version - TOS version number
ENDIF
            DC.L      reseth                    ; os_reseth -> reset handler
            DC.L      osroml                    ; os_beg -> base of OS
            DC.L      endos                     ; os_end -> end BIOS/GEMDOS/VDI ram usage
            DC.L      reseth                    ; os_exec/os_rsv1 << unused, reserved >>
            DC.L      gem_mupb                  ; os_magic -> GEM memory usage param. block
IF ROM_STBOOK
            DC.L      $03101992                 ; os_date - Date of system build ($YYYYMMDD)
ELIF ROM_TOS206
            DC.L      $11141991                 ; os_date - Date of system build ($YYYYMMDD)
ELIF ROM_TOS306
            DC.L      $09241991                 ; os_date - Date of system build ($YYYYMMDD)
ENDIF
            DC.W      $0000                     ; os_conf - OS configuration bits
IF ROM_STBOOK
            DC.W      $186a                     ; os_dosdate - DOS-format date of system build
ELIF ROM_TOS206
            DC.W      $176e                     ; os_dosdate - DOS-format date of system build
ELIF ROM_TOS306
            DC.W      $1738                     ; os_dosdate - DOS-format date of system build
ENDIF
            DC.L      ospool_base               ; p_root -> base of OS pool
            DC.L      kb_shift                  ; pkbshift -> keyboard shift state variable
            DC.L      gemdos_pid                ; p_run -> GEMDOS PID of current process
            DC.L      $000000                   ; p_rsv2 << unused, reserved >>

*+
* [ROM based system]
* reseth - System reset handler
*
*  Gains control of the system upon power-up reset,
*  or when the RESET button is pressed,
*  or after a really messy system crash.
*
*-
reseth:     move      #$2700,sr                 ; super mode, no interrupts
IF ROM_TOS306
            move.w    #$100,(fifo).w
            move.w    #0,(fifo).w
ENDIF
            reset                               ; reset hardware

*+
*  [ROM based system]
*  Check for a diagnostic cartridge;
*  if one is inserted, load a return address
*  into A6 and jump into the cat's entry point.
*
*-
            cmpi.l    #$fa52235f,(cartbase).l   ; is the magic number there?
            bne.s     reset1                    ; (no)
            lea       reset1(pc),a6             ; a6 -> return address
            jmp       (cartbase+4).l            ; execute diagnostic cartridge

*+
*  [ROM based system]
*  Is this is a warm reset, setup the memory
*  controller configuration register so that
*  the reset-bailout vector has something to
*  stand on ....
*
*-
reset1:

IF ROM_TOS306
            move.l    #$808,d0
            movec     d0,cacr

            moveq     #0,d0                     ; set vector base to address 0
            movec     d0,vbr
            pmove     ($e35fe4).l,tc            ; 0L - disable PMMU
            pmove     ($e35fe4).l,tt0           ; 0L
            pmove     ($e35fe4).l,tt1           ; 0L
            frestore  ($e35fe4).l               ; 0L - clear FPU

            btst      #0,(scu_gp1).w            ; memconfig valid?
            beq.s     reset3                    ; (no)
ENDIF

            lea       ret_1(pc),a6              ; load return addr
            bra       val_memval                ; check memory configuration validity
ret_1:      bne.s     reset3                    ; (invalid -- don't set anything up)
            move.b    (memcntlr).w,(memconf).w  ; initialize memory controller
reset2:

IF ROM_STBOOK
            move.w    (STConfig).w,d0           ; Configuration on ST Book
            cmp.b     #$fc,d0                   ; power pressed while the ST Book is closed?
            beq.s     reset2b                   ; yes => do not execute reset vector
            move.w    (tt_mcu+4).l,d0           ; ST Book: ???
            and.b     #6,d0                     ; check bit 1 & 2
            bne.s     reset2c
reset2b:    clr.l     (resvalid).l
reset2c:
ENDIF

*+
*  [ROM based system]
*  RESET bailout vector check.
*  Check to make sure we have a clean, well-bred
*  bailout vector. The high byte must be zero,
*  it must be even, and cannot be entirely zero.
*
*-
            cmpi.l    #$31415926,(resvalid).w   ; is the resvalid the magic number?
            bne.s     reset3                    ; (no)
            move.l    (resvector).w,d0          ; d0 = reset bailout vector
IF !ROM_TOS306
            tst.b     (resvector).w             ; bits 24..31 must be zero
            bne.s     reset3                    ; (they aren't, so punt)
ENDIF
            btst      #0,d0                     ; the vector must be even
            bne.s     reset3                    ; (it isn't, so punt)
            movea.l   d0,a0                     ; a0 -> reset handler
            lea       reset2(pc),a6             ; a6 -> return address
            jmp       (a0)                      ; execute reset bailout

*+
*  Initialize PSG output ports.
*  Make port A and B output-only,
*  initialize floppy select lines (so
*  that none are selected)
*-
reset3:     lea       (psgsel).w,a0             ; a0 -> giselect, giwrite-2
            move.b    #7,(a0)                   ; set porta & portb to output
            move.b    #$c0,2(a0)
            move.b    #14,(a0)                  ; deselect disks
            move.b    #7,2(a0)

*+
*  Determine 50hz or 60hz.
*  The hardware RESETs to 60hz. Check a bit in the
*  ROM configuration byte to see if we have to twiddle
*  the hardware into 50hz mode.
*
*-
IF ROM_TOS306
            move.b    #1,(v_shf_mod).w          ; Switch to 640x200x2
ELSE
            btst      #0,(osroml+29).l          ; check bit: configured for 50hz?
            beq.s     notpal                    ; (nope -- we're good ol' NTSC)
            lea       ret_1b(pc),a6
            bra       waitvbl                   ; a short delay for PAL
ret_1b:     move.b    #2,(synmod).w             ; yes -- twiddle to 50hz
notpal:
IF ROM_STBOOK
            move.b    #2,(v_shf_mod).w          ; Switch to 640x400x1
            move.b    #$80,(lcdPowerControl).w  ; LCD display on
            tst.w     (tt_mcu).l                ; ST Book: ???
ENDIF
ENDIF

*+
*  Initialize palette registers to
*  their default values
*
*-
            lea       (palette).w,a1            ; a1 -> hardware reg
            move.w    #15,d0                    ; setup 16 colors
            lea       colors(pc),a0             ; a0 -> table of default colors
sysic1:     move.w    (a0)+,(a1)+               ; copy palette assignment
            dbra      d0,sysic1                 ; (loop for more colors)

*+
*  On a ROM system, put the screen (temporarily)
*  at $10000, so icon-drawing routines won't
*  blow any any system variables.
*-
            move.b    #1,(v_bas_h).w            ; set high ptr
            clr.b     (v_bas_m).w               ; set low ptr

*+
*  [ROM based system]
*  Determine how much memory there is, and initialize
*  the memory controller configuration register
*
*-
IF ROM_TOS306
            btst      #0,(scu_gp1).w            ; memconfig valid?
            beq.s     checkmem                  ; (no)
ENDIF
            move.b    (memcntlr).w,d6           ; d6 = memory controller configuration
IF ROM_TOS306
            move.b    d6,(memconf).w            ; setup memory controller configuration register
ENDIF
            move.l    (phystop).w,d5            ; d5 -> (possible) top of physical mem
            lea       ret_2(pc),a6              ; load return address
            bra       val_memval                ; check if the memory configuration is valid
ret_2:      beq       reset4

IF ROM_STBOOK
*--- init vars + hardware:
            move.b    #%1010,d6                 ; setup controller for 2Mb/2Mb - only valid configuration on a ST Book
            move.b    d6,(memconf).w            ; setup memory controller

*--- write test-pattern to determine memory configuration:
            move.l    #$400000,d5               ; d5 -> physical top of memory (4Mb)
            move.l    #$6161964,d0              ; 16th June 1964...
            move.l    #$3251987,d1              ; 25th March 1987...
            move.l    d0,($300010).l
            move.l    d1,($300014).l
            cmp.l     ($300010).l,d0            ; test if 4MB is actually installed
            bne.s     ret_2a
            cmp.l     ($300014).l,d1
            beq.s     ret_2b
ret_2a:     move.l    #$100000,d5               ; d5 -> physical top of memory (1Mb)

ret_2b:     lea       ($8000).l,sp

ELIF ROM_TOS206
* First we try to configure the memory controller

            clr.w     d6
            move.b    #$a,(memconf).w           ; default: setup controller for 2Mb/2Mb

            movea.w   #$8,a0
            lea       ($200008).l,a1            ; + 2Mb
            clr.w     d0
chkpatloop: move.w    d0,(a0)+                  ; fill 512-8 bytes with a test pattern
            move.w    d0,(a1)+
            add.w     #$fa54,d0
            cmpa.w    #$200,a0
            bne.s     chkpatloop

            move.b    #90,(v_bas_l).w           ; wrote low byte of video address
            tst.b     (v_bas_m).w               ; touch the middle byte (this should reset the low byte)
            move.b    (v_bas_l).w,d0
            cmp.b     #90,d0                    ; low byte not reset?
            bne.s     chkmem1
            clr.b     (v_bas_l).w               ; try a different low byte value
            tst.w     (palette).w               ; touch the color palette
            tst.b     (v_bas_l).w               ; low byte changed?
            bne.s     chkmem1
            move.l    #$40000,d7                ; 256Kb offset
            bra.s     chkmem1b
chkmem1:    move.l    #$200,d7                  ; 512 byte offset
chkmem1b:   move.l    #$200000,d1               ; 2Mb = maximum size per bank

chkmemloop: lsr.w     #2,d6                     ; shift memory configuration down by a bank (bank 1 is in bits 0..1, bank 0 is in bits 2..3)

            movea.l   d7,a0                     ; + 512/256Kb bytes
            addq.l    #8,a0
            lea       chkmem3(pc),a4
            bra       memchk
chkmem3:    beq.s     chkmem7                   ; bank is not working =>

            movea.l   d7,a0
            adda.l    d7,a0                     ; + 1024/512Kb byte
            addq.l    #8,a0
            lea       chkmem4(pc),a4
            bra       memchk
chkmem4:    beq.s     chkmem6                   ; bank has 512Kb of memory =>

            movea.w   #$8,a0                    ; + 0 bytes
            lea       chkmem5(pc),a4
            bra       memchk
chkmem5:    bne.s     chkmem7                   ; bank is empty =>

            addq.w    #4,d6                     ; 4+4 = 1000 2Mb bank size
chkmem6:    addq.w    #4,d6                     ; 4   = 0100 512Kb bank size
chkmem7:    sub.l     #$200000,d1               ; - 2Mb
            beq.s     chkmemloop
            move.b    d6,(memconf).w            ; set memory configuration

ELIF ROM_TOS306
checkmem:
            move.w    #5,d6
            move.b    #$a,(memconf).w
            moveq     #0,d0
            move.l    d0,($1008).w
            move.l    d0,($100c).w
            move.l    #$6161964,d0
            move.l    d0,($0008).w
            cmp.l     ($1008).w,d0
            bne.s     checkmem1
            move.l    #$4251987,d0
            move.l    d0,($000c).w
            cmp.l     ($100c).w,d0
            beq.s     checkmem2
checkmem1:  move.w    #$a,d6
checkmem2:  move.b    d6,(memconf).w
ENDIF

IF ROM_TOS206 || ROM_TOS306
* memory is configured, set the stack pointer
            lea       ($8000).l,sp

* test the memory in 128Mb blocks to find out the maximum installed
* physical memory. A failure (bus error or a memory checksum error)
* terminates the search.
            movea.l   (busexception).w,a4
            lea       chkmemx(pc),a0            ; if a bus error occurs, we are done, too
            move.l    a0,(busexception).w
            move.w    #$fb55,d3
            move.l    #$20000,d7                ; 128Mb is the page size for testing
            movea.l   d7,a0                     ; 128Mb is the minimum physical memory
chkmem8:    movea.l   a0,a1                     ; test just below the current top of memory
            move.w    d0,d2
            moveq     #$2a,d1                   ; test 43 words
chkmem9:    move.w    d2,-(a1)                  ; write test pattern
            add.w     d3,d2
            dbra      d1,chkmem9

            movea.l   a0,a1
            moveq     #$2a,d1                   ; check 43 words
chkmem10:   cmp.w     -(a1),d0                  ; compare the test pattern
            bne.s     chkmemx                   ; test failed => we are done
            clr.w     (a1)                      ; erase memory after testing
            add.w     d3,d0
            dbra      d1,chkmem10

            adda.l    d7,a0                     ; advance to the next page
            bra.s     chkmem8

chkmemx:    suba.l    d7,a0                     ; the last page didn't work
            move.l    a0,d5                     ; end of the physical memory
            move.l    a4,(busexception).w
ENDIF

*+
*  [ROM based system]
*  Clear memory from $400 to 'd5' (phystop)
*-
            movea.w   #etv_timer,a0             ; start at the beginning
            move.l    d5,d4                     ; where to end
            moveq     #0,d0
clm_1:      move.l    d0,(a0)+                  ; initialize all the memory
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            cmpa.l    d4,a0                     ; done?
            bne.s     clm_1                     ; (loop for more bytes)

*+
*  [ROM based system]
*  Indicate that memory has successfully
*  been sized and tested.  Set two variables
*  to magic values ...
*
*-
            move.b    d6,(memcntlr).w           ; save configuration byte
            move.l    d5,(phystop).w            ; save physical top-of-memory
            move.l    #$752019f3,(memvalid).w   ; indicate memory was configured
            move.l    #$237698aa,(memval2).w    ; ditto (paranoia variable)
            move.l    #$5555aaaa,(memval3).w    ; ditto #2 (paranoia variable)
IF ROM_TOS306
            move.l    #fchkmemex,(busexception).w
            move.w    #0,(tt_mcu+224).l
fchkmemex:

* test the memory in 128Mb blocks to find out the maximum installed
* physical fast mem. A failure (bus error or a memory checksum error)
* terminates the search.
            move.l    #fchkmemx,(busexception).w    ; if a bus error occurs, we are done, too
            move.w    #$fb55,d3
            moveq     #0,d0
            move.l    #$20000,d7                ; 128Mb is the page size for testing
            movea.l   #$1020000,a0              ; start address of fast mem area + 128Mb
fchkmem8:   movea.l   a0,a1                     ; test just below the current top of memory
            move.w    d0,d2
            moveq     #$2a,d1                   ; test 43 words
fchkmem9:   move.w    d2,-(a1)                  ; write test pattern
            add.w     d3,d2
            dbra      d1,fchkmem9

            movea.l   a0,a1
            moveq     #$2a,d1                   ; check 43 words
fchkmem10:  cmp.w     -(a1),d0                  ; compare the test pattern
            bne.s     fchkmemx                  ; test failed => we are done
            clr.w     (a1)                      ; erase memory after testing
            add.w     d3,d0
            dbra      d1,fchkmem10

            adda.l    d7,a0                     ; advance to the next page
            bra.s     fchkmem8

fchkmemx:   suba.l    d7,a0                     ; the last page didn't work
            cmpa.l    #$1000000,a0              ; it failed right at the beginning of the fast mem?
            bne.s     fchkmemno                 ; (no)
            suba.l    a0,a0                     ; then we have no fast mem
fchkmemno:  move.l    a0,d5                     ; end of the fast mem
            move.l    d5,(ramtop).w             ; top of fast mem
            move.l    #$1357bd13,(ramvalid).w   ; ramtop is valid (ramvalid == RAMMAGIC)
            bset      #0,(scu_gp1).w            ; memconfig is valid
ELSE
            clr.l     (ramtop).l                ; no FASTRAM available
            move.l    #$1357bd13,(ramvalid).w   ; ramtop is valid (ramvalid == RAMMAGIC)
ENDIF

reset4:     movea.l   #_supstk+2048,sp          ; setup supervisor stack

IF ROM_STBOOK
*+
*  Initialize interrupt vectors
*
*  The exception vectors are pointed to a cold boot (_coldboot)
*  during startup.
*
*  Trap 2 and Divide-by-zero are pointed at an rte
*
*  The HBLANK, VBLANK, line 1001 [someday: line 1111), trap 13, trap 14,
*  and "extended" trap vectors are initialized appropriately.
*
*-
            lea       _rte(pc),a3               ; a3 -> handy RTE
            lea       _rts(pc),a4               ; a4 -> handy RTS

*--- setup 64 vectors:
            lea       (_coldboot).l,a1          ; a1 -> during boot all exception trigger a coldboot, which erases all memory and resets
            lea       (busexception).w,a0       ; a0 -> interrupt RAM
            move.w    #$3f,d0                   ; d0 = count
sei1:       move.l    a1,(a0)+                  ; write vector
            dbra      d0,sei1                   ; (loop to write more vectors)
            move.l    a3,(divzeroexception).w   ; divide-by-zero vector -> rte

            move.l    a3,(Level7IRQ).w          ; level #7 interrupt -> rte (power exception in a ST Book)
            moveq     #6,d0
            lea       (Level1IRQ_TTVME).w,a1
sei2:       move.l    #_rte,(a1)+               ; level #1 ... level #6 to RTE
            dbra      d0,sei2

*--- install OS interrupt vectors:
            move.l    #vbl,(Level4IRQ_VBL).w    ; vblank handler
            move.l    #hbl,(Level2IRQ_HBL).w    ; hblank handler
            move.l    a3,(trap_aesvdi).w        ; (empty) trap#2 handler
            move.l    #trp13h,(trap_bios).w     ; trap #13 handler
            move.l    #trp14h,(trap_xbios).w    ; trap #14 handler
            move.l    #line1010,(lineAexception).w ;line 1010 handler
            move.l    a4,(etv_timer).w          ; default timer-tick vector -> rts
            move.l    #_critich,(etv_critic).w  ; default critical error handler
            move.l    a4,(etv_term).w           ; default terminal vector -> rts

*+
*  Setup the vblank deferred vector list.
*  (This data structor is ugly,
*   but we seem to be stuck with it).
*
*-
            lea       (_vbl_list).w,a0          ; a0 -> default list of vbl locs
            move.l    a0,(_vblqueue).w          ; install ptr to them
            move.w    #7,d0                     ; clear vbl vectors
avbl:       clr.l     (a0)+                     ; one at a time
            dbra      d0,avbl

            lea       (tconstat).l,a0
            movea.w   #xconstat,a1
            moveq     #31,d0
tconl:      move.l    (a0)+,(a1)+
            dbra      d0,tconl

            movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #vmeinit,(busexception).w
            move.b    #$40,(vme_mask).w         ; Enable IRQ6 from VMEBUS/MFP
            move.b    #$14,(sys_mask).w         ; VSYNC & HSYNC enable in the VME Bus System Control Unit
vmeinit:    move.l    a0,(busexception).w
            movea.l   a1,sp
ENDIF

            clr.b     ($aa6).l
IF ROM_STBOOK
            sf        (STEFlag).l               ; ST Book is not an Atari STE
ELSE
            movea.l   sp,a6
            move.l    #mwinitex,(busexception).w
            clr.w     (sndmactl).w
            st        ($a02).l                  ; microwire interface available
            lea       mwinitdata(pc),a0
            move.w    (a0)+,(mwmask).w
            bra.s     mwinit3

mwinitdata: DC.W      $0ffe                     ; LMC 1992 mask
            DC.W      $09d1                     ; 10-011-101000-1 = LCM - Master Volume - 0 db volume (max) - end-bit
            DC.W      $0aa9                     ; 10-101-010100-1 = LCM - Left channel volume - 0 db volume (max) - end-bit
            DC.W      $0a29                     ; 10-100-010100-1 = LCM - Right channel volume - 0 db volume (max) - end-bit
            DC.W      $090d                     ; 10-010-000110-1 = LCM - Trebble control - 0 db (linear) - end-bit
            DC.W      $088d                     ; 10-001-000110-1 = LCM - Bass control - 0 db (linear) - end-bit
            DC.W      $0803                     ; 10-000-000001-1 = LCM - Mixer - DMA + YM2149 - end-bit
            DC.W      $0000                     ; 0 = end of list

mwinit1:    move.w    d0,(mwdata).w
mwinit2:    tst.w     (mwdata).w
            bne.s     mwinit2
mwinit3:    move.w    (a0)+,d0
            bne.s     mwinit1
mwinitex:   movea.l   a6,sp

IF ROM_TOS206
            move.b    #90,(v_bas_l).w           ; write low byte of video address
            tst.b     (v_bas_m).w               ; access the medium byte
            move.b    (v_bas_l).w,d0            ; (which should reset the low byte on an STE!)
            cmp.b     #90,d0                    ; reset?
            bne.s     nostedetect               ; yes => STE detected
            clr.b     (v_bas_l).w               ; clear the low byte again
            tst.w     (palette).w               ; access the color palette
            tst.b     (v_bas_l).w
nostedetect:sne       (STEFlag).l               ; <>0 => no STE hardware available
ELSE
            sf        (STEFlag).l               ; no STE hardware available
ENDIF
ENDIF

*+
*  Clear OS bss (from 'endosbss' to 'ostext')
*-
            movea.l   #ostext,a1                ; a1 -> end
            movea.l   #endosbss,a0              ; a0 -> start

*--- common code to clear memory:
            moveq     #0,d0                     ; quick zero
clrm_1:     move.w    d0,(a0)+                  ; clobber a word
            cmpa.l    a0,a1                     ; at end?
            bne.s     clrm_1                    ; (no -- loop for more words)

IF ROM_TOS306
            bsr       setupPMMU                 ; setup PMMU translation table
ENDIF
*+
*  Setup display base,
*  clear display memory.
*
*-
            movea.l   (phystop).w,a0            ; video_base = phystop - 0x8000
IF !ROM_TOS306
            suba.l    #$8000,a0                 ; screen size = 32kb
            move.w    #$7ff,d1                  ; d1 = # 16-byte chunks to zero
ELSE
            suba.l    #$25900,a0                ; screen size = 154kb
            move.w    #$258f,d1                 ; d1 = # 16-byte chunks to zero
ENDIF
            move.l    a0,(_v_bas_adr).w
            move.b    (_v_bas_adr+1).w,(v_bas_h).w ;load high addr
            move.b    (_v_bas_adr+2).w,(v_bas_m).w ;load low (really, medium) addr
            moveq     #0,d0                     ; quick zero
clrm_2:     move.l    d0,(a0)+                  ; zero a longword
            move.l    d0,(a0)+                  ; zero a longword
            move.l    d0,(a0)+                  ; zero a longword
            move.l    d0,(a0)+                  ; zero a longword
            dbra      d1,clrm_2                 ; (loop for more longwords)

*+
*  Initialize all kinds of OS variables
*
*-

*--- OS parameters:
            movea.l   osroml+20(pc),a0          ; get pointer to magic
            cmpi.l    #$87654321,(a0)           ; is the magic there?
            beq.s     usem
            lea       (osroml+8).l,a0           ; yes -- use numbers there
usem:       move.l    4(a0),(end_os).w          ; init end-of-OS pointer
            move.l    8(a0),(exec_os).w         ; init default-shell pointer

*--- Disk vectors:
            move.l    #_dskinit,(hdv_init).w    ; initialization
            move.l    #_floprw,(hdv_rw).w       ; read/write absolute sectors
            move.l    #_getbpb,(hdv_bpb).w      ; media change inquiry
            move.l    #_mediach,(hdv_mediach).w ; get BIOS parameter block
            move.l    #_boot,(hdv_boot).w       ; boot-from-device

            move.l    #_lstostat,(prv_lsto).w
            move.l    #_lstout,(prv_lst).w
            move.l    #_auxostat,(prv_auxo).w
            move.l    #_auxout,(prv_aux).w
            move.l    #_scrdmp,(scr_dump).w

*--- Randoms:
            move.l    (_v_bas_adr).w,(_memtop).w ;_memtop = _v_bas_ad
            move.l    (end_os).w,(_membot).w    ; set bottom of memory (for DOS)
            move.w    #8,(nvbls).w              ; default number of vbl queue entries
            st        (_fverify).w              ; enable write-verify
            move.w    #3,(seekrate).w           ; set default seek-rate
            move.l    #_diskbuf,(_dskbufp).w    ; set pointer to disk buffer
            move.w    #-1,(_prtcnt).w           ; initialize print-count
            move.l    #osroml,(_sysbase).w      ; -> base of OS
            move.l    #savend,(savptr).w        ; register-save pointer for traps 13&14
            move.l    #_rts,(swv_vec).w         ; ignore monitor changes for now
            clr.l     (_drvbits).w              ; remove all drives
            move.l    #keybellsnd,(bell_hook).w
            move.l    #keyclicksnd,(kcl_hook).w

            bsr       sysbase2ram               ; copy sysbase into RAM and patch os_conf to contain os_dosdate

*--- Cookies:
            lea       (cookieBuffer).l,a0
            move.l    a0,(_p_cookies).w
            move.l    #'_CPU',(a0)+
            moveq     #0,d1                     ; 68000 CPU
            movea.w   #illegalexception,a2
            movea.l   (a2),a3
            movea.l   sp,a1
            move.l    #cookieCPU,(a2)
            move      ccr,d0                    ; first exists in the 68010
            moveq     #10,d1                    ; 68010 CPU
            extb.l    d0                        ; first exists in the 68020
            moveq     #20,d1                    ; 68020 CPU
            movec     cacr,d0
            bset      #9,d0
            movec     d0,cacr
            movec     cacr,d0
            bclr      #9,d0
            beq.s     cookieCPU
            moveq     #30,d1                    ; 68030 CPU
            movec     d0,cacr
cookieCPU:  movea.l   a1,sp
            move.l    a3,(a2)
            move.l    d1,(a0)+
            sne       (_longframe+1).w          ; 68010+ have a longer interrupt stack frame
IF ROM_STBOOK
            move.l    #'_VDO',(a0)+             ; setup VDO cookie: Video hardware
            move.l    #$10001,(a0)+             ; 1,1 => Atari STE, ST-Book
            move.l    #'_MCH',(a0)+             ; setup MCH cookie: Machine type
            move.l    #$10001,(a0)+             ; 1,1 => Atari STE, ST-Book

            move.b    #$7f,d0
            tst.b     (STEFlag).l
            bne.s     cookieSTE
            move.l    #'_SWI',(a0)+             ; setup SWI cookie: DIP configuration switches
            moveq     #0,d0
            move.w    (STConfig).w,d0
            lsr.w     #8,d0
            move.l    d0,(a0)+                  ; all DIP switches as a bit mask 0..7
cookieSTE:  moveq     #3,d1                     ; bit 0: PSG, bit 1: 8-bit DMA
            move.l    #'_SND',(a0)+             ; setup SND cookie: Sound hardware
            btst      #7,d0                     ; DIP switch 7 on?
            bne.s     cookieSND                 ; (punt)
            bclr      #1,d1                     ; no 8-bit DMA sound
cookieSND:  move.l    d1,(a0)+
            btst      #6,d0                     ; DIP switch 6 on?
            bne.s     cookieFDC                 ; (punt - no HD floppy)
            move.b    #8,(dsb0).l               ; select HD density for drive A
            move.l    #'_FDC',(a0)+             ; setup FDC cookie: Floppy disk controller
            move.l    #$1415443,(a0)+           ; 'FDC' | (1 << 24)
cookieFDC:  move.l    #'_FPU',(a0)+             ; Setup FPU cookie: Type of the FPU
            moveq     #0,d7                     ; 0 = no FPU
            suba.w    #$24,sp
            move.l    (lineFexception).w,(sp)
            move.l    (coprocexception).w,4(sp)
            move.l    #cookieFPU,(lineFexception).w
            move.l    #cookieFPU,(coprocexception).w
            lea       8(sp),a1
            movea.w   #ffcp_unorderedcond,a2
            move.l    #cookieFPU2,d0
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            clr.l     -(sp)
            movea.l   sp,a2
            frestore  (sp)
cookieFPU2: move.l    #$20000,d7                ; 0x20000 = 68881 or 68882 as co-processor (Exact type unknown)
cookieFPU:  movea.l   a2,sp
            addq.w    #4,sp
            move.l    (sp)+,(lineFexception).w
            move.l    (sp)+,(coprocexception).w
            move.l    (sp)+,(ffcp_unorderedcond).w
            move.l    (sp)+,(ffcp_inexactresult).w
            move.l    (sp)+,(ffcp_divzero).w
            move.l    (sp)+,(ffcp_underflow).w
            move.l    (sp)+,(ffcp_operanderror).w
            move.l    (sp)+,(ffcp_inexactresult).w
            move.l    (sp)+,(ffcp_divzero).w
            move.l    d7,(a0)+
            movea.l   (busexception).w,a1
            movea.l   sp,a2
            move.l    #cookieFPU3,(busexception).w
            move.w    (FPStat).w,d0
            bset      #0,-3(a0)                 ; SFP004 present
cookieFPU3: move.l    a1,(busexception).w
            movea.l   a2,sp
ELSE

IF ROM_TOS206
            tst.b     (STEFlag).l               ; no STE hardware available?
            beq.s     cookieMCH                 ; (correct)
            move.l    #'_VDO',(a0)+
            clr.l     (a0)+                     ; 0,0 = Atari ST (260 ST, 520 ST, 1040 ST, Mega ST, ...)
            move.l    #'_MCH',(a0)+
            clr.l     (a0)+                     ; 0,0 = Atari ST
            bra.s     cookieSWI

cookieVDO:  move.l    #'_VDO',(a0)+
            move.l    #$10000,(a0)+             ; 1,0 = STE Shifter

            move.l    #$10000,d0                ; x = $00 = regular STE
            movea.l   (busexception).w,a1
            movea.l   sp,a2
            move.l    #cookieMCH1,(busexception).w
            tst.b     (scu_gp1).w
            move.w    #$10,d0                   ; x = $10 = Mega STE (with SCSI)
            bra.s     cookieMCH2
cookieMCH1: clr.w     d0
            movea.l   a2,sp
            move.l    #cookieMCH2,(busexception).w
            tst.b     (ide_stat2).l
            move.w    #8,d0                     ; x = $08 = STE with IDE (unknown machine)
cookieMCH2: move.l    a1,(busexception).w
            movea.l   a2,sp
            move.l    #'_MCH',(a0)+
            move.l    d0,(a0)+                  ; 1,x = STE (520, 1040, 2080, 4160, Mega STE, ST Book)

ELIF ROM_TOS306
            move.l    #'_VDO',(a0)+
            move.l    #$20000,(a0)+             ; 2,0 = TT Shifter
            move.l    #'_MCH',(a0)+
            move.l    #$20000,(a0)+             ; 2,0 = TT
ENDIF

cookieSWI:  move.b    #$7f,d0
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     cookieSND2                ; (correct)
            move.l    #'_SWI',(a0)+             ; setup SWI cookie: DIP configuration switches
            moveq     #0,d0
            move.w    (STConfig).w,d0
            lsr.w     #8,d0
            move.l    d0,(a0)+

cookieSND2: moveq     #3,d1                     ; bit 0: PSG, bit 1: 8-bit DMA
            move.l    #'_SND',(a0)+             ; setup SND cookie: Sound hardware
            btst      #7,d0                     ; DIP switch 7 on?
            bne.s     cookieSND3                ; (punt)
            bclr      #1,d1                     ; no 8-bit DMA sound
cookieSND3: move.l    d1,(a0)+

            btst      #6,d0                     ; DIP switch 6 on?
            bne.s     cookieFDC2                ; (punt - no HD floppy)
            move.b    #8,(dsb0).l               ; select HD density for drive A
            move.l    #'_FDC',(a0)+             ; setup FDC cookie: Floppy disk controller
            move.l    #$1415443,(a0)+           ; 'FDC' | (1 << 24)

cookieFDC2:
            move.l    #'_FPU',(a0)+             ; Setup FPU cookie: Type of the FPU
            movea.l   (lineFexception).w,a1
            movea.l   (coprocexception).w,a2
            movea.l   sp,a3
            move.l    #cookieFPUex,(lineFexception).w
            move.l    #cookieFPUex,(coprocexception).w
            fmove.l   d0,fp0
            move.l    #$20000,(a0)+             ; 6888x present
            bra.s     cookieFPUe2
cookieFPUex:clr.l     (a0)+
cookieFPUe2:move.l    a1,(lineFexception).w
            move.l    a2,(coprocexception).w
            movea.l   a3,sp
            movea.l   (busexception).w,a1
            movea.l   sp,a2
            move.l    #cookieFPUe3,(busexception).w
            move.w    (FPStat).w,d0
            bset      #0,-3(a0)                 ; SFP004 present
cookieFPUe3:move.l    a1,(busexception).w
            movea.l   a2,sp

IF ROM_TOS306
            tst.l     (ramtop).w                ; fast mem installed?
            beq.s     nfrbcookie                ; (no)
            move.l    #'_FRB',(a0)+             ; allocate FRB cookie for DMA buffer
            move.l    (_membot).w,d0
            move.l    d0,(a0)+                  ; buffer adress
            add.l     #$10000,d0                ; 64kb size
            move.l    d0,(_membot).w            ; adjust memory base
            move.l    d0,(end_os).w
nfrbcookie:
ENDIF

ENDIF
            clr.l     (a0)+                     ; terminate cookie list
            move.l    #$10,(a0)+                ; 16 available slots in the cookie store

IF !ROM_STBOOK
*+
*  Initialize interrupt vectors
*
*  The exception vectors are pointed to a cold boot (_coldboot)
*  during startup.
*
*  Trap 2 and Divide-by-zero are pointed at an rte
*
*  The HBLANK, VBLANK, line 1001 [someday: line 1111), trap 13, trap 14,
*  and "extended" trap vectors are initialized appropriately.
*
*-
            lea       _rte(pc),a3               ; a3 -> handy RTE
            lea       _rts(pc),a4               ; a4 -> handy RTS

*--- setup 62 vectors:
            lea       _term(pc),a1              ; _term -> draw number of bombs
IF !ROM_TOS306
            adda.l    #$2000000,a1              ; exception number in top 8 bits of the address
ENDIF
            lea       (busexception).w,a0       ; a0 -> interrupt RAM
            move.w    #$3d,d0                   ; d0 = count
sei1:       move.l    a1,(a0)+                  ; write vector
IF !ROM_TOS306
            adda.l    #$1000000,a1              ; increment the exception number
ENDIF
            dbra      d0,sei1                   ; (loop to write more vectors)
            move.l    a3,(divzeroexception).w   ; divide-by-zero vector -> rte

            move.l    a3,(Level7IRQ).w          ; level #7 interrupt -> rte (power exception in a ST Book)
            moveq     #6,d0
            lea       (Level1IRQ_TTVME).w,a1
sei2:       move.l    #_rte,(a1)+               ; level #1 ... level #6 to RTE
            dbra      d0,sei2

*--- install OS interrupt vectors:
            move.l    #vbl,(Level4IRQ_VBL).w    ; vblank handler
            move.l    #hbl,(Level2IRQ_HBL).w    ; hblank handler
            move.l    a3,(trap_aesvdi).w        ; (empty) trap#2 handler
            move.l    #trp13h,(trap_bios).w     ; trap #13 handler
            move.l    #trp14h,(trap_xbios).w    ; trap #14 handler
            move.l    #line1010,(lineAexception).w ;line 1010 handler
            move.l    a4,(etv_timer).w          ; default timer-tick vector -> rts
            move.l    #_critich,(etv_critic).w  ; default critical error handler
            move.l    a4,(etv_term).w           ; default terminal vector -> rts

*+
*  Setup the vblank deferred vector list.
*  (This data structor is ugly,
*   but we seem to be stuck with it).
*
*-
            lea       (_vbl_list).w,a0          ; a0 -> default list of vbl locs
            move.l    a0,(_vblqueue).w          ; install ptr to them
            move.w    #7,d0                     ; clear vbl vectors
avbl:       clr.l     (a0)+                     ; one at a time
            dbra      d0,avbl

            lea       (tconstat).l,a0
            movea.w   #xconstat,a1
            moveq     #31,d0
tconl:      move.l    (a0)+,(a1)+
            dbra      d0,tconl

            movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #vmeinit,(busexception).w
            move.b    #$40,(vme_mask).w         ; Enable IRQ6 from VMEBUS/MFP
            move.b    #$14,(sys_mask).w         ; VSYNC & HSYNC enable in the VME Bus System Control Unit
vmeinit:    move.l    a0,(busexception).w
            movea.l   a1,sp
ENDIF

*+
*  "The other half" of the BIOS handles character I/O,
*  call its initialization hoook.
*  (It can "never fail". This will get interesting
*  if we ever to a detachable keyboard ....)
*-
            bsr       initmfp

            move.w    #$400,d0                  ; Delay Mode, /50 Prescale, data = 0 (about 10us delay)
            bsr       mfpdelay
            move.l    #setikbd,-(sp)
            move.w    #1,-(sp)
            jsr       (ikbdws).l                ; reset ikbd
            addq.l    #6,sp
            move.w    #$700,d0                  ; Delay Mode, /200 Prescale, data = 0 (about 40us delay)
            move.w    #14,d1                    ; 15 * 40us = 600us delay
ikbddelay:  bsr       mfpdelay
            dbra      d1,ikbddelay

*+
*  Fire up %%2 cartridges
*
*-
            moveq     #2,d0                     ; bit# = 2
            bsr       cartscan                  ; execute cartridge aps
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM

            move.b    #$80,(lcdPowerControl).w  ; LCD display on
            move.b    #$80,(lcdPowerControlShadow).w ; LCD display shadow register
            moveq     #2,d1                     ; Switch to 640x400x1
            lea       setvb1(pc),a6
            bra       waitvbl
setvb1:     move.b    d1,(v_shf_mod).w          ; set rez hardware register

ELIF ROM_TOS206
            moveq     #0,d1                     ; Switch to 320x200x4
            btst      #7,($fffffa01).w          ; monochrome monitor connected?
            bne.s     nomonomon                 ; (no)
            moveq     #2,d1                     ; Switch to 640x400x1
nomonomon:
            lea       setvb1(pc),a6
            bra       waitvbl
setvb1:     move.b    d1,(v_shf_mod).w          ; set rez hardware register

ELIF ROM_TOS306
            moveq     #4,d1                     ; Switch to 640x480x4
            btst      #7,($fffffa01).w          ; monochrome monitor connected?
            bne.s     nomonomon                 ; (no)
            moveq     #6,d1                     ; Switch to 1280x960x1
nomonomon:
            move.b    d1,(shift_tt).w
ENDIF
            move.b    d1,(sshiftmd).w           ; set rez shadow

            bsr       blittest
            jsr       ($e072aa).l               ; linaA blitter/no-blitter table init
            jsr       (esc_init).l              ; clear screen, initialize cursor
            move.l    #reseth,(swv_vec).w       ; RESET system on monitor change
            move.w    #1,(vblsem).w             ; enable vblank processing

*+
*  [1] Fire up %%0 cartridges
*  [2] Enable interrupts
*  [3] Fire up %%1 cartridges
*
*-
            clr.w     d0                        ; magic bit# = 0
            bsr       cartscan                  ; execute cartridge aps
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM
ENDIF
            move      #$2300,sr                 ; Enable interrupts, go to IPL 3

            moveq     #1,d0                     ; magic bit# = 1
            bsr       cartscan                  ; execute cartridge aps
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM

            move.l    (_hz_200).w,d0
            addq.l    #3,d0
resDelayL:  cmp.l     (_hz_200).w,d0            ; a short delay of 15-20ms
            bhi.s     resDelayL
            clr.b     (kb_shift).l              ; reset keyboard shift state
ENDIF

            move.l    #privinstr_exception,(privexception).w ;Emulate the MOVE SR so it works in usermode on a 68020+
            bra       resetcont

*+
* Flush instruction and data cache (68020+)
*-
flushCaches:move      sr,-(sp)
            ori       #$700,sr
            movec     cacr,d0
            or.l      #$808,d0                  ; CI (Clear Instruction Cache), DI (Clear Data Cache)
            movec     d0,cacr
            move      (sp)+,sr
            rts

*+
* Emulate the MOVE SR,<ea> so it works in usermode on 68010+
*-
privinstr_exception:movem.l d0-d2,-(sp)
            move.l    a1,-(sp)
            move.l    a0,-(sp)
            movea.l   $16(sp),a0                ; a0 -> pc
            move.w    (a0),d0                   ; d0 -> current opcode
            move.w    d0,d1                     ; d1 -> save current opcode
            and.w     #$ffc0,d0
            cmp.w     #$40c0,d0                 ; move SR,<ea>?
            bne       privinstr_exception_term  ; (no -- punt)

            move.l    #$30004e71,(endosbss).w   ; move.w d0,d0, nop
            move.l    #'NqNu',(endosbss+2*2).w  ; nop, rts
            move.w    d1,d0
            and.w     #7,d0                     ; <ea> register (bit 0..2)
            lsl.w     #8,d0
            lsl.w     #1,d0                     ; move into bit 9..11 of the destination <ea>
            or.w      d0,(endosbss).w
            move.w    d1,d0
            and.w     #$38,d0                   ; <ea> mode (bit 3..5)
            lsl.w     #3,d0                     ; move into bit 6..8 of the destination <ea>
            or.w      d0,(endosbss).w           ; generate a MOVE D0,<ea> from the MOVE SR,<ea> opcode

            moveq     #2,d2                     ; opcode size = 2
            cmp.w     #$180,d0                  ; <ea> == d8(An,Xn)?
            beq       privinstr_exception_term  ; not supported -> _term
            tst.w     d0                        ; <ea> == Dn?
            beq.s     privinstr_exception_dn
            cmp.w     #$140,d0                  ; <ea> == d16(An)?
            beq.s     privinstr_exception_d16
            cmp.w     #$1c0,d0                  ; <ea> == (xxx).w or (xxx).l?
            bne.s     privinstr_exception_other

* <ea> = (xxx).l or (xxx).w
            and.w     #7,d1                     ; register == 0 => (xxx).w
            beq.s     privinstr_exception_abs16
            addq.w    #2,d2                     ; it is (xxx).l -> opcode size += 2
            move.w    4(a0),(endosbss+2*2).w    ; move lower word of the absolute address
privinstr_exception_abs16:addq.w #2,d2          ; opcode size += 2
            move.w    2(a0),(endosbss+2).w      ; move upper word of the absolute address
            bra.s     privinstr_exception_ea    ; -> regular destination ea

* <ea> = d16(An)
privinstr_exception_d16:addq.w #2,d2            ; opcode size += 2
            move.w    2(a0),(endosbss+2).w      ; move d16 offset
privinstr_exception_other:and.w #7,d1           ; d1 = An from the destination <ea>
            cmp.w     #7,d1                     ; relative to A7?
            bne.s     privinstr_exception_ea    ; no -> regular destination ea

* <ea> = d16(A7) or d8(A7,Dn)
* Special case because A7 has to be the USP, instead of the current SSP (in A7)
            move      usp,a1                    ; have to use the USP, otherwise we wouldn't have gotten the exception
            andi.w    #$f3ff,(endosbss).w       ; convert A7-relative into A1-relative destination <ea>
            add.l     d2,$16(sp)                ; pc += opcode size
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            move.w    $14(sp),d0                ; d0 = SR
            jsr       (endosbss).w              ; execute: MOVE D0,d(A1,Dn); NOP; RTS or MOVE D0,d(A1); NOP; RTS
            move      a1,usp                    ; restore USP, but shouldn't have changed anyway
            movea.l   (sp)+,a0                  ; restore registers
            movea.l   (sp)+,a1
            movem.l   (sp)+,d0-d2
            rte                                 ; continue execution

* <ea> = Dn
privinstr_exception_dn:add.l d2,$16(sp)         ; pc += opcode size
            ori.w     #$10,(endosbss).w         ; source ea = (A0)
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            lea       20(sp),a0                 ; point to SR register to SSP
            movem.l   8(sp),d0-d2               ; restore d0-d2
            jsr       (endosbss).w              ; execute: MOVE (A0),Dn; NOP; NOP; RTS
            movea.l   (sp)+,a0                  ; restore registers
            movea.l   (sp)+,a1
            adda.w    #$c,sp                    ; skip d0-d2, because they have already been restored
            rte                                 ; continue execution

privinstr_exception_ea:add.l d2,$16(sp)         ; pc += opcode size
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            movea.l   (sp)+,a0
            movea.l   (sp)+,a1
            move.w    12(sp),d0                 ; d0 = SR
            jsr       (endosbss).w              ; execute: MOVE D0,<ea>; ...; RTS
            movem.l   (sp)+,d0-d2               ; restore registers
            rte                                 ; continue execution

privinstr_exception_term:movea.l (sp)+,a0       ; restore registers
            movea.l   (sp)+,a1
            movem.l   (sp)+,d0-d2
            jmp       (_term).l                 ; illegal instruction => _term

*+
* Load shell (if _cmdload is nonzero)
* or execute GEM in ROM
*-
resetcont:  jsr       (osi).l                   ; initialize DOS

*--- set the current system time and date
            move.w    (osroml+30).l,(systemDate).l ;use BIOS time as current time
            jsr       (readCurrentTime).l       ; set current time to RTC time
            beq.s     notimechip                ; (no RTC)
            bsr       readIKBDTime              ; read time from the keyboard controller
            swap      d0
            tst.b     d0
            beq.s     notimechip                ; (not valid either)
            move.w    d0,(systemDate).l         ; update system time
            swap      d0
            move.w    d0,(systemTime).l

notimechip: clr.b     (tacr).w
            bclr      #5,(iera).w               ; disable Timer A interrupts

IF ROM_TOS306
            move.l    #$3111,d0
            movec     d0,cacr
ENDIF

            movea.l   #atari_image,a0           ; draw Atari Logo on the screen
            movea.l   (_v_bas_adr).w,a1
            move.b    (sshiftmd).w,d0
            cmp.b     #2,d0                     ; 640 x 400 (bw)
            beq.s     drawLogobw                ; (yes)
            cmp.b     #6,d0                     ; 1280 x 960 (bw)
            beq.s     drawLogobw                ; (yes)
IF ROM_TOS306
            adda.w    #4*320,a1
ELSE
            adda.w    #4*160,a1
ENDIF
            move.w    #86-1,d0                  ; 86 lines
drawLogoc1: moveq     #6-1,d1                   ; 6 words per raster line
drawLogoc2: move.w    (a0)+,d2                  ; draw 4 plane Atari Logo
            move.w    d2,(a1)+
            move.w    d2,(a1)+
            move.w    d2,(a1)+
            move.w    d2,(a1)+
            dbra      d1,drawLogoc2
IF ROM_TOS306
            adda.w    #320-6*8,a1
ELSE
            adda.w    #160-6*8,a1
ENDIF
            dbra      d0,drawLogoc1
            bra.s     drawLogo3b
drawLogobw:
IF ROM_TOS306
            adda.w    #4*160,a1
ELSE
            adda.w    #4*80,a1
ENDIF
            move.w    #86-1,d0                  ; 86 lines
drawLogobw2:moveq     #12-1,d1                  ; 12 bytes per raster line
drawLogobw3:move.b    (a0)+,(a1)+               ; draw 1 plane Atari Logo
            dbra      d1,drawLogobw3
IF ROM_TOS306
            adda.w    #160-12,a1
ELSE
            adda.w    #80-12,a1
ENDIF
            dbra      d0,drawLogobw2

drawLogo3b: moveq     #32+7,d7                   ; y = 7
            tst.b     (sshiftmd).w
            bne.s     drawLogo4
            moveq     #32+12,d7                 ; y = 12
drawLogo4:  move.l    #$30002,d6
            move.w    #$1b,-(sp)
            move.l    d6,-(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #$59,4(sp)
            move.l    d6,(sp)
            trap      #$d                       ; Bconout(2, 'Y')
            move.w    d7,4(sp)
            move.l    d6,(sp)
            trap      #$d                       ; Bconout(2, ' '+0)
            move.w    #$20,4(sp)
            move.l    d6,(sp)
            trap      #$d                       ; Bconout(2, ' '+y)
            addq.w    #6,sp

IF !ROM_TOS306
            cmpi.l    #$3e80,(_hz_200).l        ; system running for >80s?
            bcc       ptch_term                 ; (then no ROM CRC check)
ENDIF

*--- Check the ROM CRC
checkROM:
IF ROM_STBOOK
            move.l    #$3fffe,d7                ; offset to ROM CRC (last 2 bytes)
            move.w    #0,d6                     ; number of banks = 1
ELIF ROM_TOS206
            move.l    #$1fffe,d7                ; offset to ROM CRC (last 2 bytes)
            move.w    #1,d6                     ; number of banks = 2
ELIF ROM_TOS306
            move.l    #$1fffe,d7                ; offset to ROM CRC (last 2 bytes)
            move.w    #3,d6                     ; number of banks = 4
ENDIF
            movea.l   #osroml,a5                ; ROM base address
checkROMl:
IF ROM_STBOOK
            move.w    #1,-(sp)                  ; checksum over every byte
ELIF ROM_TOS206
            move.w    #2,-(sp)                  ; checksum over every other byte
ELIF ROM_TOS306
            move.w    #4,-(sp)                  ; checksum over every 4th byte
ENDIF
            move.l    d7,-(sp)                  ; number of bytes
            move.l    a5,-(sp)                  ; buffer address
            bsr       calccrc
            adda.w    #10,sp
            movea.l   a5,a0
IF ROM_STBOOK
            adda.l    d7,a0
ELIF ROM_TOS206
            adda.l    #$3fffc,a0
ELIF ROM_TOS306
            adda.l    #$7fff8,a0
ENDIF
            move.b    (a0),d1                   ; high byte of CRC
            lsl.w     #8,d1
IF ROM_STBOOK
            move.b    1(a0),d1                  ; low byte of CRC
ELIF ROM_TOS206
            move.b    2(a0),d1                  ; low byte of CRC
ELIF ROM_TOS306
            move.b    4(a0),d1                  ; low byte of CRC
ENDIF
            cmp.w     d1,d0                     ; CRC identical?
            bne.s     checkROMbad               ; (no)
            addq.l    #1,a5                     ; next bank
            dbra      d6,checkROMl
            bra.s     ptch_term

badCRCtext: DC.B      'WARNING: BAD ROM CRC IN CHIP ',0
badCRCtextB:DC.B      '.\r\n',0

checkROMbad:move.l    a5,d5
            pea       (badCRCtext).l
            move.w    #9,-(sp)
            trap      #1
            move.b    #'E',d0                   ; 'E' - even
            btst      #0,d5
            beq.s     checkROMbad2
            move.b    #'O',d0                   ; 'O' - odd
checkROMbad2:move.w   d0,2(sp)
            move.w    #2,(sp)
            trap      #1
IF ROM_TOS306
            move.b    #'E',d0                   ; 'E' - even
            btst      #1,d5
            beq.s     checkROMbad3
            move.b    #'O',d0                   ; 'O' - odd
checkROMbad3:move.w   d0,2(sp)
            move.w    #2,(sp)
            trap      #1
ENDIF
            move.l    #badCRCtextB,2(sp)
            move.w    #9,(sp)
            trap      #1
            addq.w    #6,sp
            addq.l    #1,a5
            dbra      d6,checkROMl

* if no monochrome is active, holding down the alternate key forces
* to 320x200x4 instead of 640x480x4 for ST compatibility
IF ROM_TOS306
            cmpi.b    #6,(sshiftmd).w           ; TT high?
            beq.s     ttlowreschk               ; (yes)
            move.l    #$bffff,-(sp)
            trap      #$d                       ; Kbshift()
            addq.w    #4,sp
            btst      #3,d0                     ; alternate pressed?
            beq.s     ttlowreschk               ; (no)
            clr.w     -(sp)
            pea       ($ffffffff).w
            pea       ($ffffffff).w
            move.w    #5,-(sp)
            trap      #$e                       ; switch to low-res
            adda.w    #$c,sp
            move.l    #$808,d0
            movec     d0,cacr
ttlowreschk:
ENDIF

* During boot till this point any exception triggers a _coldboot, which
* erases the first MB and resets. From now own we point the exceptions
* to _term, which draws the bombs and terminates the currently running app
ptch_term:

IF ROM_STBOOK
            move.l    #$1000000,d1              ; d1 -> exception number
            lea       _term(pc),a0              ; new exception vector
            adda.l    d1,a0                     ; add the exception number into the upper 8 bits (2 = bus error)
            adda.l    d1,a0
            lea       (busexception).w,a1       ; start with the bus error exception
            move.w    #$3f,d0
            move.l    #_coldboot,d2             ; old exception vector
ptch_term1: cmp.l     (a1)+,d2                  ; is it pointing to _coldboot?
            bne.s     ptch_term2                ; no -> ignore It
            move.l    a0,$fffc(a1)              ; point it to _term
ptch_term2: adda.l    d1,a0                     ; increment the exception number in the upper 8 bits
            dbra      d0,ptch_term1             ; next vector ->
ENDIF

            bsr       _dskboot                  ; attempt to boot from disk
            bsr       mtest                     ; memory test and attempt to boot from DMA
            bsr       runresapps                ; execute resident applications in memory

            tst.w     (_cmdload).l              ; load shell from disk?
            beq.s     st_1                      ; (no -- execute GEM in ROM)
            bsr       _auto
            move.l    #osroml,(_sysbase).l      ; -> base of OS
            pea       nullenv(pc)               ; null environment string
            pea       nullenv(pc)               ; null argument string
            pea       cmdname(pc)               ; push shell filename
            clr.w     -(sp)                     ; load-and-go flavor of execute
            bra.s     st_x                      ; exec shell ("never return")

*--- bring up GEM:
st_1:       bsr       _auto                     ; do auto-exec
IF ROM_STBOOK
            bsr       _auto_ROMDISK
ENDIF
            move.l    #osroml,(_sysbase).l      ; -> base of OS

*--- kludge up an enviroment string
            lea       orig_env(pc),a0           ; kludge up an environment string, a0 -> original environment string
            movea.l   #the_env,a1               ; a1 -> place to put it
st_2:       cmpi.b    #'#',(a0)                 ; look for drive# character
            bne.s     st_3                      ; (not it)
            movea.l   a1,a2                     ; a2 -> place to put drive#
st_3:       move.b    (a0)+,(a1)+               ; copy a byte
            bpl.s     st_2                      ; copy while not end-of-string
            move.b    (_bootdev).l,d0           ; compute drive#, and shove it
            add.b     #$41,d0                   ; into the env string at the
            move.b    d0,(a2)                   ; appropriate spot

*--- kludge up an enviroment string:
            pea       (the_env).l               ; push address of environment string
            pea       (nullenv).l               ; no arguments
            pea       nullenv(pc)               ; null shell name (in ROM, after all)
            move.w    #5,-(sp)                  ; createPSP flavor of exec
            move.w    #$4b,-(sp)                ; exec function#
            trap      #1                        ; get pointer to PSP
            adda.w    #14,sp                    ; (clean up cruft)
            movea.l   d0,a0                     ; a0 -> PSP
            move.l    (exec_os).l,8(a0)         ; stuff saddr of GEM in PSP
            pea       (the_env).l               ; our environment string
            move.l    a0,-(sp)                  ; push addr of PSP
            pea       nullenv(pc)               ; null filename
            move.w    #4,-(sp)                  ; just-go
st_x:       move.w    #$4b,-(sp)                ; function = exec
            trap      #1                        ; do it
            adda.w    #14,sp                    ; cleanup stack

*+
* When startup fails (or if the exec returns,
* which "cannot happen") fake a system reset:
*-
            jmp       (reseth).l                ; back to the beginning...

*+
* Default enviroment string
* Cannot be more than 20 chars long without modifying
* the declaration for the_env,
* Any char >= $80 terminates the string (and is included in it)
* The last '#' character is replaced by the boot drive's name (A, B, ...)
*-
orig_env:   DC.B      'PATH=',0                 ; default pathname
            DC.B      '#:\',0                   ; is the boot device
            DC.B      $00                       ; terminate env string
            DC.B      $ff                       ; end of the env string (for our copy)

cmdname:    DC.B      'COMMAND.PRG',0           ; shell name
gemname:    DC.B      'GEM.PRG',0               ; desktop name
            DC.B      0
            DC.B      0

setikbd:    DC.B      $80,$01

*+
* _dskboot - boot (or return diagnostics)
* Passed:       nothing
* Returns:      D0.W = error number (if nonzero)
*-
_dskboot:   moveq     #3,d0                     ; %%3 ap cart
            bsr       cartscan
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM
ENDIF
            movea.l   (hdv_boot).w,a0           ; go through boot vector
            jsr       (a0)
            tst.w     d0                        ; any errors?
            bne.s     dskb1                     ; (yes -- punt)
            movea.l   (_dskbufp).w,a0           ; a0 -> disk buffer
            jsr       (a0)                      ; execute boot sector (it might return)
dskb1:      rts                                 ; return status

mtest:      move.l    #80*200,d7                ; 80s
            cmp.l     (_hz_200).l,d7            ; is the system running for > 80s?
            bcs       dmaBoot                   ; (no memory test)
            movea.w   #0,a5                     ; done status = false
mtestloop:  cmpa.w    #0,a5                     ; memtest done?
            bne       mtest4                    ; (yes)
            bsr       memtest
            movea.w   d0,a5                     ; memtest done?
            beq       mtest5                    ; (no)
            move.w    #$1b,-(sp)
            move.l    #$30002,-(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'p',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'p') Reverse video
            move.w    #$1b,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'w',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'w') Discard end of line
            move.l    #200,d5                   ; 200 = 1s
            move.w    #79,d4                    ; 79
            tst.b     (sshiftmd).l
            bne.s     mtest2
            move.l    #2*200,d5                 ; 2s
            moveq     #39,d4                    ; 39
mtest2:     move.l    d5,d6
            move.l    d4,d3
mtestsp:    move.w    #' ',4(sp)                ; d3 + 1 spaces
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ' ')
            dbra      d4,mtestsp
            move.w    #$d,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 13) CR
            subq.l    #1,d3
mtestsp2:   move.w    #' ',4(sp)                ; d3 spaces
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ' ')
            dbra      d3,mtestsp2
            move.w    #$1b,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'q',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'q') Normal video
            addq.l    #6,sp
mtest4:     cmp.l     (_hz_200).l,d6
            bhi.s     mtest5
            move.w    #$1b,-(sp)
            move.l    #$30002,-(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'K',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'K') Clear to eol
            move.w    #8,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 8) Backspace
            addq.w    #6,sp
            add.l     d5,d6
mtest5:     cmpa.w    #0,a5                     ; memtest done?
            beq.s     mtest6                    ; (no)
            cmp.l     (_hz_200).l,d7
            bls.s     mtest8
mtest6:
IF ROM_STBOOK
            move.w    (tt_mcu+4).l,d0
            not.w     d0
            and.w     #$c,d0                    ; check bit 2 & 3
            bne.s     mtest7
ENDIF
            move.l    #$10002,-(sp)
            trap      #$d                       ; Bconstat(2) - key pressed?
            addq.l    #4,sp
            tst.l     d0
            beq       mtestloop                 ; (no - continue)
            move.l    #$20002,-(sp)
            trap      #$d                       ; Bconin(2) - read key
            addq.l    #4,sp
mtest7:     cmpa.w    #0,a5                     ; memtest done?
            bne.s     mtest8                    ; (yes)
            bsr       memtestabort
            move.l    d7,(_hz_200).l
            bra.s     dmaBoot
mtest8:     move.l    d7,(_hz_200).l
            move.w    #$d,-(sp)
            move.l    #$30002,-(sp)
            trap      #$d                       ; Bconout(2, 13) CR
            move.w    #$1b,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'K',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'K') Clear to eol
            addq.l    #6,sp

*--- boot from DMA device
dmaBoot:
IF ROM_STBOOK
            clr.w     (the_env).l               ; index to the dmaDevList
ELIF ROM_TOS206
            moveq     #$10,d4
ELIF ROM_TOS306
            jsr       (_scsisubb).l

            moveq     #8,d4
            move.b    (scu_gp1).w,d0
            and.w     #$f8,d0
            move.w    d0,(defaultBootDevice).w
            bne.s     dmaBoot2
            pea       (defaultBootDevice).w
            move.w    #2,-(sp)
            clr.l     -(sp)
            jsr       (_NVMaccess).l
            adda.w    #$a,sp
            tst.w     d0
            beq.s     dmaBoot2
ENDIF
            clr.w     (defaultBootDevice).w
dmaBoot2:   move.w    #1,d1                     ; d1 -> 2 tries per device
dmaBoot3:   move.w    d1,-(sp)
IF ROM_STBOOK
            move.w    (the_env).l,d4
            move.b    dmaDevList(pc,d4.w),d4
ENDIF
            move.w    d4,-(sp)                  ; pdev
            move.l    (_dskbufp).w,-(sp)        ; buf = _dskbufp
            move.w    #1,-(sp)                  ; count = 1
            clr.l     -(sp)                     ; sectnum = 0
            jsr       (_dmaread).l              ; read first sector of this device
            adda.w    #12,sp
            move.w    (sp)+,d1
            tst.l     d0                        ; read successful?
            beq.s     dmaBoot4                  ; yes ->
            addq.l    #1,d0                     ; error == time out?
            dbeq      d1,dmaBoot3               ; timeout or another try left? ->
            bra.s     dmaexecl                  ; try next device
dmaBoot4:   movea.l   (_dskbufp).w,a0
            move.w    #$ff,d0                   ; 256 word checksum over the boot sector
            moveq     #0,d1
dmaBoot5:   add.w     (a0)+,d1
            dbra      d0,dmaBoot5
            cmp.w     #$1234,d1                 ; checksum == 0x1234?
            beq.s     dmaexec                   ; execute this valid boot sector ->
dmaexecl:
IF ROM_STBOOK
            move.w    (the_env).l,d4
            addq.w    #1,d4                     ; increment next device
            move.w    d4,(the_env).l
            cmpi.b    #$ff,dmaDevList(pc,d4.w)  ; end of the device list?
            bne.s     dmaBoot2                  ; no -> continue with the next one
ELSE
            addq.w    #1,d4
            move.w    d4,d0
            and.w     #7,d0
            bne.s     dmaBoot2
            cmp.w     #8,d4
            beq.s     dmaBootx
            moveq     #0,d4
            bra.s     dmaBoot2
dmaBootx:
ENDIF
            rts                                 ; no valid boot sector found -> return

IF ROM_STBOOK
dmaDevList: DC.B      $10,$11,$00,$01,$02,$03,$04,$05 ;boot order of DMA devices, $ff terminates the list
            DC.B      $06,$07,$ff
ENDIF

dmaexec:    movea.l   (_dskbufp).w,a0           ; pointer to the boot sector buffer
            move.l    #'DMAr',d3                ; d3 -> 'DMAr' magic
            move.w    d4,d7                     ; d4 -> pdev
            asl.w     #5,d7                     ; d7 = 0
            move.w    (defaultBootDevice).w,d5  ; defaultBootDevice = 0
IF !ROM_STBOOK
            move.l    d4,-(sp)
ENDIF
            move.l    (hdv_rw).l,-(sp)          ; save read sector function pointer
            jsr       (a0)                      ; execute boot sector
            move.l    (sp)+,d0
IF !ROM_STBOOK
            move.l    (sp)+,d4
ENDIF
            cmp.l     (hdv_rw).w,d0             ; did the read sector function change?
            beq.s     dmaexecl                  ; no -> no device driver was loaded -> continue to load boot sectors
            rts                                 ; a new device driver was loaded -> return


*+
* cartscan - scan cartridge memory for runable applications
* Passed:       d0 = bit# to test in application's initialization vector
* Returns:      after all applications have been examined
* Uses:         a0,d0
*-
IF ROM_STBOOK
cartscan_STBOOK_EXTROM:lea (STBOOK_EXTROM).l,a0
            bra.s     cartscan2
ENDIF

cartscan:   lea       (cartbase).l,a0           ; scan cartridge memory for runable applications, a0 -> cartridge memory
cartscan2:  cmpi.l    #$abcdef42,(a0)+          ; correct magic number?
            bne.s     ca_r                      ; (no, so return)
ca_1:       btst      d0,4(a0)                  ; test bit in MSB of INIT address
            beq.s     ca_2                      ; (not set, so don't execute)
            movem.l   d0-d7/a0-a6,-(sp)         ; save everything
            move.l    4(a0),d0                  ; d0 -> initialization address
            and.l     #$ffffff,d0               ; mask out bit# in the top 8 bits of the address
            movea.l   d0,a0
            jsr       (a0)                      ; call cartridge application
            movem.l   (sp)+,d0-d7/a0-a6         ; restore everything
ca_2:       tst.l     (a0)                      ; test link address
            movea.l   (a0),a0                   ; a0 -> next header (or NULL)
            bne.s     ca_1                      ; loop on next header
ca_r:       rts

_rts:       rts


*+
* memchk - check pattern written to memory
*       Passed:         d1.l = offset
*                       a0 = base of pattern ($1f8 bytes long)
*                       a5 -> return address
*
*       Returns:        EQ: the pattern matched
*                       NE: the pattern didn't match
*
*       Uses:           d0.w, a1
*       Called-by:      Coldstart memory-sizing routine.
*-
memchk:     adda.l    d1,a0                     ; a0 -> memory to check
            clr.w     d0                        ; zap pattern seed
            lea       $1f8(a0),a1               ; a1 -> ending address
memchk1:    cmp.w     (a0)+,d0                  ; match?
            bne.s     memchkr                   ; (no -- return NE)
            add.w     #$fa54,d0                 ; yes -- bump pattern
            cmpa.l    a0,a1                     ; matched entire pattern?
            bne.s     memchk1                   ; (no)
memchkr:    jmp       (a4)                      ; "return" to caller


*+
* val_memval - test memory configuration validation
*  Passed:      a6 -> return addressd
*  Returns:     a5 -> 0 (quick zeropage)
*               EQ: memory setup OK
*               NE: memory never configured succesfully
*
*-
val_memval: cmpi.l    #$752019f3,(memvalid).w   ; test memory configuration validation, check first magic number
            bne.s     val_mr                    ; (mismatched -- return NE)
            cmpi.l    #$237698aa,(memval2).w    ; check one more (for paranoia)
            bne.s     val_mr                    ; (mismatched -- return NE)
            cmpi.l    #$5555aaaa,(memval3).w    ; check a third time (for more paranoia)
val_mr:     jmp       (a6)                      ; return EQ/NE


*+
* Default palette assignments.
*  Sort of corresponding to the GSX spec.
*-
colors:     DC.W      $0fff                     ; 0 white
            DC.W      $0f00                     ; 1 red
            DC.W      $00f0                     ; 2 green
            DC.W      $0ff0                     ; 3 yellow
            DC.W      $000f                     ; 4 blue
            DC.W      $0f0f                     ; 5 magenta
            DC.W      $00ff                     ; 6 cyan
            DC.W      $0555                     ; 7 "low white"
            DC.W      $0333                     ; 8 grey
            DC.W      $0f33                     ; 9 light red
            DC.W      $03f3                     ; 10 light green
            DC.W      $0ff3                     ; 11 light yellow
            DC.W      $033f                     ; 12 light blue
            DC.W      $0f3f                     ; 13 light magenta
            DC.W      $03ff                     ; 14 light cyan
            DC.W      $0000                     ; 15 black

*+
* hbl - force caller to IPL
* Oh-well:      "Yeah, it sucks, but it works" (--lt)
*
* Note:         Hacks caller's IPL to 3 (if it was 0). This is
*               a kludge against fascist programs and certain
*               debuggers that insist on starting processes up
*               at IPL 0.
*
*-
hbl:        move.w    d0,-(sp)                  ; save d0
            move.w    2(sp),d0                  ; get pushed SR
            and.w     #$700,d0                  ; strip crufty bits
            bne.s     hbl_r                     ; not IPL 0, so punt
            ori.w     #$300,2(sp)               ; force caller to IPL 3
hbl_r:      move.w    (sp)+,d0                  ; restore d0, back to victim
            rte

*+
* vbl - vertical black interrupt handler
*
*-
vbl:        addq.l    #1,(_frclock).l           ; bump frame clock
            subq.w    #1,(vblsem).l             ; P(vblsem) -- vblank locked?
            bmi       vblret

            movem.l   d0-d7/a0-a6,-(sp)         ; save registers
            addq.l    #1,(_vbclock).l           ; bump unblocked-frame Clock

*------ Call deferred interrupt vectors
            move.w    (nvbls).l,d7              ; d7 = # of deferred vblank vectors
IF ROM_STBOOK
            beq.s     vbl12                     ; (punt if no vectors)
ELSE
            beq       vbl12                     ; (punt if no vectors)
ENDIF
            subq.l    #1,d7                     ; turn into DBRA count
            movea.l   (_vblqueue).w,a0          ; a0 -> vectors
vbl10:      movea.l   (a0)+,a1                  ; a1 -> deferred vector
            cmpa.w    #0,a1                     ; if(a1 == NULL) continue;
            beq.s     vbl11
            movem.l   d7-d7/a0,-(sp)            ; save registers
            jsr       (a1)                      ; call routine
            movem.l   (sp)+,d7-d7/a0            ; restore registers
vbl11:      dbra      d7,vbl10                  ; loop for more vectors

IF ROM_TOS206 || ROM_TOS306
            move.b    (gpip).w,d1
            tst.b     ($a02).l                  ; microwire interface available?
            beq.s     vbl11b                    ; (no)
            move      sr,-(sp)
            ori       #$700,sr
vbl11a:     move.b    (sndmactl+1).w,d0
            move.b    (gpip).w,d1
            btst      #7,d1
            sne       d1
            move.b    (gpip).w,d2
            btst      #7,d2
            sne       d2
            cmp.b     d1,d2
            bne.s     vbl11a
            cmp.b     (sndmactl+1).w,d0
            bne.s     vbl11a
            move      (sp)+,sr
            btst      #0,d0
            beq.s     vbl11b
            not.b     d1
vbl11b:

IF ROM_TOS206
            move.b    (v_shf_mod).w,d0
            and.b     #3,d0
            cmp.b     #2,d0
            bge.s     vbl11c
ELIF ROM_TOS306
            move.b    (shift_tt).w,d0
            and.b     #7,d0
            cmp.b     #6,d0
            beq.s     vbl11c
ENDIF
            btst      #7,d1
            bne.s     vbl11e
            bsr       _wvbl
IF ROM_TOS206
            move.b    #2,d0
ELIF ROM_TOS306
            move.b    #6,d0
ENDIF
            bra.s     vbl11d
vbl11c:     btst      #7,d1
            beq.s     vbl11e
            move.b    (defshiftmd).w,d0
IF ROM_TOS206
            cmp.b     #2,d0
            blt.s     vbl11d
ELIF ROM_TOS306
            cmp.b     #6,d0
            bne.s     vbl11d
ENDIF
            clr.b     d0
vbl11d:
            move.b    d0,(sshiftmd).w
IF ROM_TOS206
            move.b    d0,(v_shf_mod).w
ELIF ROM_TOS306
            move.b    (shift_tt).w,d1
            and.b     #$f8,d1
            or.b      d0,d1
            move.b    d1,(shift_tt).w
ENDIF

            movea.l   (swv_vec).w,a0
            jsr       (a0)
vbl11e:
ENDIF
            jsr       (blink).l                 ; blink cursor

*--- reload color palettes
            tst.l     (colorptr).w              ; reload color palettes, if(colorptr != NULL)....
            beq.s     vbl1
            movea.l   (colorptr).w,a0           ; a0 -> user's color base
            lea       (palette).w,a1            ; a1 -> hardware palette base
            move.w    #7,d1                     ; d0 = count
vbl2:       move.l    (a0)+,(a1)+               ; load a palette
            dbra      d1,vbl2                   ; ...and repeat
            clr.l     (colorptr).w              ; zap colorptr

*--- reload display base register
vbl1:       tst.l     (screenpt).w              ; reload display base register, if(screenpt == NULL) don't
            beq.s     vbl5
            move.l    (screenpt).w,(_v_bas_adr).w ;set OS variable
            move.b    (_v_bas_adr+2).w,(v_bas_m).w ;load "middle" pointer
            move.b    (_v_bas_adr+1).w,(v_bas_h).w ;load "high" pointer
            move.b    (_v_bas_adr+3).w,(v_bas_l).w ;load "low" pointer

*------ Floppy drive-select timeout
vbl5:       bsr       _flopvbl                  ; (no args)

vbl12:      tst.w     (_prtcnt).w               ; monitor screen dump flag - printscreen active?
            bne.s     no_print                  ; no

*+
* printScreen
*
* We re-enable vblanks here, until the printScreen finishes -
*
*-
            bsr       _dumpit                   ; dump screen

no_print:   movem.l   (sp)+,d0-d7/a0-a6         ; restore registers & return (and a handy RTE)
vblret:     addq.w    #1,(vblsem).l             ; V(vblsem) [release vblank]
_rte:       rte


*+
* wvbl - wait for next vblank
* Passed:       nothing
* Returns:      at beginning of next vblank
* Uses:         D0
*-
wvbl:       move      sr,-(sp)                  ; save psw
            andi      #$fbff,sr                 ; enable vbl interrupts
            move.l    (_frclock).l,d0           ; d0 = frame clock
wvbl1:      cmp.l     (_frclock).l,d0           ; wait for clock to change
            beq.s     wvbl1
            move      (sp)+,sr                  ; then restore psw & return
            rts


*+
* _critic - critical error handler binding for C
* Falls-into:   _critich
* (screwy way to save two bytes....)
*
*-
crit_err:   move.l    (etv_critic).l,-(sp)      ; jump through critic vector

*+
* _critich - default critical error handler
* Loads -1 into D0 and returns
*
*-
_critich:   moveq     #-1,d0                    ; default return value = ERROR
            rts                                 ; return to trap invoker


*+
* trp13h - GEMDOS BIOS trap handler (trap 13)
* trp14h - Atari BIOS extensions (trap 14)
* traph  - trap handler
*
* On the stack:
*       From super-             From user
*       visor mode:             mode:
*       -----------             ------------
*       N(sp) args              N(usp) args
*       6(sp) func#             6(usp) func#
*       2(sp) ret               2(ssp) ret
*        (sp) SR                 (ssp) SR
*
* Returns:      anything in D0
* Uses:         d0-d2/a0-a2
* Keeps:        C registers
*
* Notes         BIOS traps are re-entrant to 'nlevels' (declared near the
*               beginning of this file).  Attempts to recurse more than
*               'nlevels' will probably result in a crash.
*
*               BIOS calls may be made from user mode.  (This differs from
*               the current GEMDOS spec, which states that BIOS traps are
*               available from supervisor mode only).
*
*-
trp14h:     lea       trp14tab(pc),a0           ; a0 -> trap14 jump table
            bra.s     traph
trp13h:     lea       trp13tab(pc),a0           ; a0 -> trap13 jump table
traph:      movea.l   (savptr).l,a1             ; a1 - registers save area
            move.w    (sp)+,d0                  ; pop SR and save it
            move.w    d0,-(a1)                  ; (need in D0 for user-mode test)
            move.l    (sp)+,-(a1)               ; save return addr
IF !ROM_TOS306
            tst.w     (_longframe).w
            beq.s     traph2
ENDIF
            tst.w     (sp)+
traph2:     movem.l   d3-d7/a3-a7,-(a1)         ; save C registers + super stack
            move.l    a1,(savptr).l             ; update save-area pointer

* make sure we have the right stack, call function:
            btst      #13,d0                    ; was in user mode?
            bne.s     b_supr                    ; (was in super: use super stack)
            move      usp,sp                    ; use user stack
b_supr:     move.w    (sp)+,d0                  ; get function#
            cmp.w     (a0)+,d0                  ; out of range?
            bge.s     b_exit                    ; (yes, so punt)
            move.w    d0,d1
            lsl.w     #2,d1                     ; turn D0 into longword index
            move.l    (a0,d1.w),d1              ; get pointer to function handler
IF ROM_TOS306
            bclr      #0,d1                     ; clear the indirect but set the old status in Z
            movea.l   d1,a0
            beq.s     b_1                       ; points to code
ELSE
            movea.l   d1,a0                     ; (quick and dirty test-for-negative)
            bpl.s     b_1                       ; points to code
ENDIF
            movea.l   (a0),a0                   ; indirect through RAM...
b_1:        suba.l    a5,a5                     ; a5 -> zero page
            jsr       (a0)                      ; call BIOS function

* restore registers, cleanup stack and return:
b_exit:     movea.l   (savptr).l,a1             ; a1 -> register save area
            movem.l   (a1)+,d3-d7/a3-a7         ; restore C registers + super stack
IF !ROM_TOS306
            tst.w     (_longframe).w
            beq.s     trph3
ENDIF
            clr.w     -(sp)
trph3:      move.l    (a1)+,-(sp)               ; push return address
            move.w    (a1)+,-(sp)               ; push old SR
            move.l    a1,(savptr).l             ; update save-pointer
            rte                                 ; return to caller


*------ jump table for GEMDOS functions:
trp13tab:   DC.W      $000c                     ; number of entries in jump table
            DC.L      _get_mpb
            DC.L      bconstat
            DC.L      bconin
            DC.L      bconout
IF ROM_TOS306
            DC.L      hdv_rw | 1
ELSE
            DC.L      hdv_rw
ENDIF
            DC.L      _setexc
            DC.L      _tickcal
IF ROM_TOS306
            DC.L      hdv_bpb | 1
ELSE
            DC.L      hdv_bpb
ENDIF
            DC.L      bcostat
IF ROM_TOS306
            DC.L      hdv_mediach | 1
ELSE
            DC.L      hdv_mediach
ENDIF
            DC.L      _drvmap
            DC.L      _shift

*------ jump table for Atari BIOS extensions:
trp14tab:
IF ROM_TOS306
            DC.W      $0060                     ; number of entry points
ELSE
            DC.W      $0041                     ; number of entry points
ENDIF
            DC.L      initmous
            DC.L      _rts
            DC.L      _physbase
            DC.L      _logbase
            DC.L      _getrez
            DC.L      _setscreen
            DC.L      _setpalette
            DC.L      _setcolor
            DC.L      _floprd
            DC.L      _flopwr
            DC.L      _flopfmt
            DC.L      _getdsb
            DC.L      midiws
            DC.L      mfpint
            DC.L      iorec
            DC.L      rsconf
            DC.L      keytrans
            DC.L      _rand
            DC.L      _proto_bt
            DC.L      _flopver
            DC.L      _dumpit
            DC.L      _cursconf
            DC.L      settime
            DC.L      gettime
            DC.L      bioskeys
            DC.L      ikbdws
            DC.L      jdisint
            DC.L      jenabint
            DC.L      giaccess
            DC.L      offgibit
            DC.L      ongibit
            DC.L      xbtimer
            DC.L      dosound
            DC.L      setprt
            DC.L      ikbdvecs
            DC.L      kbrate
            DC.L      _prtblk
            DC.L      wvbl
            DC.L      supexec
            DC.L      puntaes
            DC.L      _rts
            DC.L      floprate
            DC.L      _dmaread
            DC.L      _dmawrite
            DC.L      _bconmap
            DC.L      _rts
IF ROM_TOS306
            DC.L      _NVMaccess
ELSE
            DC.L      _rts
ENDIF
IF ROM_STBOOK || ROM_TOS206
            DC.L      _Waketime
ELSE
            DC.L      _rts
ENDIF
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      blitmode
IF ROM_TOS306
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts

            DC.L      _EsetShift
            DC.L      _EgetShift
            DC.L      _EsetBank
            DC.L      _EsetColor
            DC.L      _EsetPalette
            DC.L      _EgetPalette
            DC.L      _EsetGrey
            DC.L      _EsetSmear

            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts

* this routine seems to be unused?
_e00fae:    btst      #0,1(sp)                  ; read?
            bne.s     _hdv_rw_ptc               ; (no) => no patch necessary, write doesn't need to clear the cache
            move.l    #_hdv_rw_ptt,(sp)         ; patch return address
_hdv_rw_ptc:movea.l   (hdv_rw).w,a0
            jmp       (a0)

_hdv_rw_ptt:move.l    d0,-(sp)
            bsr       _flush_cache              ; flush the CPU cache
            move.l    (sp)+,d0
            jmp       (b_exit).l                ; exit trap
ENDIF

*+
* supexec - execute some code in supervisor mode
*
*-
supexec:    movea.l   4(sp),a0                  ; a0 -> code
            jmp       (a0)                      ; execute it


*+
* Character device I/O
*
* No check is made for "bogus" device numbers.  A wierd device
* number will result in a crash.
*
*-
bconstat:   lea       (xconstat).w,a0           ; a0 -> stat table
            moveq     #0,d1
            bra.s     chsw
bconin:     lea       (xconin).w,a0             ; a0 -> input table
            moveq     #4,d1
            bra.s     chsw
bcostat:    lea       (xcostat).w,a0            ; a0 -> ostat table
            moveq     #8,d1
            bra.s     chsw
bconout:    lea       (xconout).w,a0            ; a0 -> output table
            moveq     #$c,d1
chsw:       move.w    4(sp),d0                  ; get device number
            cmp.w     #5,d0
            bls.s     chsw2
            subq.l    #6,d0
            cmp.w     (bdevcount).w,d0
            bcc.s     chsw1
            movea.l   (bdevtabptr).w,a0
            asl.w     #3,d0
            adda.w    d0,a0
            add.w     d0,d0
            adda.w    d0,a0
            movea.l   (a0,d1.w),a0
            jmp       (a0)

chsw1:      moveq     #0,d0
            rts

chsw2:      lsl.w     #2,d0                     ; turn into longword index
            movea.l   (a0,d0.w),a0              ; get address of handler
            jmp       (a0)                      ; jump to it

*+
* Jump tables for
*       0 - lst: (printer)
*       1 - aux: (rs232)
*       2 - con: (screen)
*       3 - Atari midi
*       4 - Atari keyboard (output only)
*       5 - raw console output (bypass vt52 pressure cooker)
*
* No range checking is performed.  If a bogus device number
* is passed to the BIOS' character I/O handler, the system
* will crash to become funky duex.
*
*-
tconstat:   DC.L      _rts
            DC.L      auxistat
            DC.L      constat
            DC.L      midstat
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
tconin:     DC.L      _lstin
            DC.L      auxin
            DC.L      conin
            DC.L      midin
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
tcostat:    DC.L      _lstostat
            DC.L      _auxostat
            DC.L      conoutst
            DC.L      ikbdost
            DC.L      midiost
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
tconout:    DC.L      _lstout
            DC.L      _auxout
            DC.L      conout
            DC.L      midiwc
            DC.L      ikbdwc
            DC.L      _asc_out
            DC.L      _rts
            DC.L      _rts

*+
* _drvmap - return "active drive" bit vector
* Passed:       nothing
* Returns:      D0.L = a bit vector of live (rwabs'able) block device
*
*-
_drvmap:    move.l    (_drvbits).w,d0
            rts


*+
* _shift - get/set keyboard shift state
* Synopsis:     LONG _shift(bits)
*               WORD bits
*
* Returns:      D0.B = shift/alt/ctl/shift' bits
*
* Note:         Since the shift bits are changed at interrupt
*               level, any set from a get of the shift state
*               must be done as a critical section.
*
*-
_shift:     moveq     #0,d0
            move.b    (kb_shift).w,d0
            move.w    4(sp),d1
            bmi.s     shifr
            move.b    d1,(kb_shift).w
shifr:      rts


*+
* _get_mpb - return initial memory parameter block
* Synopsis:     _get_mpb(mpb)
*               MPB *mpb;
*
* Returns:      The properly initialized MPB.
*               The MPB points to an MD somewhere in BSS. The MD /must/
*               be in RAM since DOS will modify it.
*-
_get_mpb:   movea.l   4(sp),a0                  ; a0 -> MPB
            lea       (themd).w,a1              ; a1 -> MD

*--- initialize MPB:
            move.l    a1,(a0)                   ; mp_mfl = &themd;
            clr.l     4(a0)                     ; mp_mal = NULL;
            clr.l     8(a0)                     ; mp_rover = NULL;

*---- initialize MD:
            clr.l     (a1)                      ; m_link = NULL;
            move.l    (_membot).w,4(a1)         ; m_start = _membot;
            move.l    (_memtop).w,d0            ; m_length = _memtop - membot;
            sub.l     (_membot).w,d0
            move.l    d0,8(a1)
            clr.l     $c(a1)                    ; m_own = NULL;

IF ROM_TOS306
            cmpi.l    #$1357bd13,(ramvalid).l   ; fast mem available?
            bne.s     _get_mpbret               ; (no)
            cmpi.l    #$1000000,(ramtop).l      ; fast mem installed?
            bls.s     _get_mpbret               ; (no)
            lea       ($0a02).w,a2              ; themd_tt
            move.l    a2,(a1)                   ; themd.m_link = &themd_tt
            clr.l     (a2)                      ; m_link = NULL
            move.l    #$1000001,4(a2)           ; m_start = base address of fast mem | 1
            move.l    (ramtop).l,d0
            sub.l     #$1000000,d0
            move.l    d0,8(a2)                  ; m_length = size of fast mem
            clr.l     $c(a2)                    ; m_own = NULL
_get_mpbret:
ENDIF
            rts

*+
* _setexc - set exception vector
* Synopsis:     setexc(vecno, addr)
*               If 'addr' < 0, the vector is not set.
*
*               Extended vectors ($100 though $107) are located in the
*               first eight longwords of BSS, at $400. This is for
*               convienience -- they could really be located anywhere.
*
* Returns:      D0.L = original vector value
*
*-
_setexc:    move.w    4(sp),d0                  ; d0 = vector #
            lsl.w     #2,d0                     ; turn into longword index
            suba.l    a0,a0
            lea       (a0,d0.w),a0              ; a0 -> vector
            move.l    (a0),d0                   ; d0 = current vector address
            move.l    6(sp),d1                  ; d1 = what_to_change_it_to
            bmi.s     setex1                    ; punt if (d1 < 0)
            move.l    d1,(a0)                   ; set vector address
setex1:     rts

*+
* _tickcal - return system timer calibration value (in ms)
*
*-
_tickcal:   moveq     #0,d0                     ; cast to unsigned longword
            move.w    (_timr_ms).w,d0           ; get calibration
            rts

*+
* _physbase - get physical display base
*
*-
_physbase:  moveq     #0,d0                     ; cleanup pointer-to-be
            move.b    (v_bas_h).w,d0            ; load and shift bits 16..23
            lsl.w     #8,d0
            move.b    (v_bas_m).w,d0            ; load and shift bits 8..15
            lsl.l     #8,d0
            tst.b     (STEFlag).l
            bne.s     _physbase2
            move.b    (v_bas_l).w,d0
_physbase2: rts                                 ; return pointer in d0

*+
* _logbase -get logical display base
*
*-
_logbase:   move.l    (_v_bas_adr).w,d0         ; set software shadow
            rts

*+
* _getrez - get current screen rez
*-
_getrez:    moveq     #0,d0                     ; cleanup dirty bits
IF ROM_STBOOK
            move.b    (sshiftmd).w,d0           ; get screen rezolution
            and.b     #3,d0                     ; strip garbage bits
ELIF ROM_TOS206
            move.b    (v_shf_mod).w,d0          ; get screen rezolution
            and.b     #3,d0                     ; strip garbage bits
ELIF ROM_TOS306
            move.b    (shift_tt).w,d0
            and.b     #7,d0
ENDIF
            rts                                 ; return rez

*+
* _setscreen - set screen location(s), rez
*       _setscreen(logicalLoc, physicalLoc, rez)
*       LONG logicalLoc, physicalLoc;
*       WORD rez;
*
*-

*--- set logical location:
_setscreen: tst.l     4(sp)                     ; if(logloc < 0) then ignore it
            bmi.s     f5a
            move.l    4(sp),(_v_bas_adr).w      ; set software pointer from logloc

*--- set physical location:
f5a:        tst.l     8(sp)                     ; if(physloc < 0) then ignore it
            bmi.s     f5b
            move.b    9(sp),(v_bas_h).w         ; set bits 16..23 of hardware pointer
            move.b    $a(sp),(v_bas_m).w        ; set bits 8..15 of hardware pointer
            move.b    $b(sp),(v_bas_l).w        ; set bits 0..7 of hardware pointer

*--- set screen resolution (clears the screen, clobbers the cursor):
f5b:        tst.w     $c(sp)                    ; if(rez < 0) then ignore it
            bmi.s     f5r
            bsr       _wvbl                     ; wait for start of vertical-blank
            move.b    $d(sp),(sshiftmd).w       ; set software shadow
IF ROM_TOS306
            move.b    (shift_tt).w,d0
            and.b     #$f8,d0
            or.b      $d(sp),d0
            move.b    d0,(shift_tt).w
ELSE
            move.b    (sshiftmd).w,(v_shf_mod).w ;set hardware location
ENDIF
            clr.w     (vblsem).w                ; disable vblank processing
            jsr       (esc_init).l              ; re-initialize glass tty routines
            move.w    #1,(vblsem).w             ; re-enable vblanks
f5r:        rts

*+
* _setpalette - set palette (on next vblank)
*       _setpalette(LONG palettePtr)
*
*-
_setpalette:move.l    4(sp),(colorptr).w        ; set software pointer
            rts                                 ; (updated by vbl handler)

*+
* _setcolor - set single color, return old color
*       _setcolor(WORD colorNum, WORD colorValue)
*
*-
_setcolor:  move.w    4(sp),d1                  ; get color number
            add.w     d1,d1                     ; turn into word index
            and.w     #$1f,d1                   ; force color range (prevent buserr)
            lea       (palette).w,a0            ; a0 -> base of palette memory
            move.w    (a0,d1.w),d0              ; return old color
            tst.b     (STEFlag).l
            beq.s     _setc0a
            and.w     #$777,d0                  ; mask dirty bits
            bra.s     _setc0b
_setc0a:    and.w     #$fff,d0
_setc0b:    tst.w     6(sp)                     ; if new color is <0, don't set it
            bmi.s     _setc1                    ; (punt)
            move.w    6(sp),(a0,d1.w)           ; set new color
_setc1:     rts

*+
* puntaes - throw-away AES, restart the system
*  Passed:      nothing
*  Uses:        everything
*  Returns:     if AES was already thrown away
*
*-
puntaes:    movea.l   osroml+20(pc),a0          ; get pointer to magic
            cmpi.l    #$87654321,(a0)           ; is the magic still there?
            bne.s     paes1                     ; no -- just return
            cmpa.l    (phystop).w,a0            ; is it in ROM?
            bge.s     paes1                     ; yes -- we can't do anything about it
            clr.l     (a0)                      ; clobber AES!
            bra       reseth                    ; restart the system
paes1:      rts

*+
* _term - terminate current process
* Called-by:    Uncaught traps (bus errors, and so on)
* Saves:        processor state (in a bailout area)
*
*-
_term:
IF !ROM_TOS306
            jsr       (savp_2).l                ; stack PC
savp_2:
ENDIF
            move.l    (sp)+,(proc_pc).w         ; save bogus PC + exception number
            movem.l   d0-d7/a0-a7,(proc_regs).w ; common registers
IF ROM_TOS306
            move.l    2(sp),(proc_pc).w
            move.w    6(sp),d0
            and.w     #$fff,d0
            asr.w     #2,d0
            move.b    d0,(proc_pc).w
ENDIF
            move      usp,a0                    ; save USP
            move.l    a0,(proc_usp).w
            moveq     #15,d0                    ; save 16 words off top of
            lea       (proc_stk).w,a0           ; the stack (enough for
            movea.l   sp,a1                     ; any possible 680000 exception)
savp_1:     move.w    (a1)+,(a0)+               ; save a word
            dbra      d0,savp_1
            move.l    #$12345678,(proc_lives).w ; set magic number (procdump lives)

*--- draw an appropriate number of 'shrooms on the screen:
            moveq     #0,d1
            move.b    (proc_pc).w,d1
            subq.w    #1,d1                     ; 2 for bus error, 3 for address, etc.
            bsr.s     do_shroom
            move.l    #savend,(savptr).w        ; clobber BIOS top level
            move.w    #-1,-(sp)                 ; "error" return condition
            move.w    #$4c,-(sp)                ; GEMDOS function #$4C
            trap      #1                        ; "terminate process"
            bra       reseth                    ; on return, reset system
*+
* do_shroom - draw little mushroom clouds on the screen
*  Passed:      d1.w = #shrooms to draw (DBRA count)
*  Returns:     some shrooms on display
*  Uses:        d0-d7/a0-a2
*
*  Discussion:  The graphics ain't all that great.   And this is silly.
*
*-
do_shroom:  move.b    (sshiftmd).w,d7
IF ROM_TOS306
            and.w     #7,d7
ELSE
            and.w     #3,d7
ENDIF
            add.w     d7,d7                     ; d7 = rez index
            moveq     #0,d0
            move.b    (v_bas_h).w,d0
            lsl.w     #8,d0
            move.b    (v_bas_m).w,d0
            lsl.l     #8,d0
            tst.b     (STEFlag).l
            bne.s     dmste
            move.b    (v_bas_l).w,d0
dmste:      movea.l   d0,a0                     ; a0 -> base of mem to draw at
            cmp.w     #6,d7
            blt.s     dm00
            adda.l    #768*100,a0
            bra.s     dm01
dm00:       adda.w    #160*100,a0
dm01:       lea       (mushroom).l,a1           ; a1 -> source from
            move.w    #15,d6                    ; d6 = scanline count
dm0:        move.w    d1,d2                     ; d3 = # to draw on this line
            movea.l   a0,a2                     ; safe ptr to beg of line
dm1:        move.w    mcount(pc,d7.w),d5        ; d5 = #words to replicate
dm2:        move.w    (a1),(a0)+                ; draw a word
            dbra      d5,dm2                    ; (complete single shroom)
            dbra      d2,dm1                    ; another, on the same line
            addq.w    #2,a1                     ; next source word
            adda.w    mwidth(pc,d7.w),a2        ; next dest line
            movea.l   a2,a0
            dbra      d6,dm0                    ; (loop for next line)
            moveq     #30-1,d7
dm3:        bsr       wvbl
            dbra      d7,dm3
            rts                                 ; byebye

mcount:     DC.W      $0003,$0001,$0000,$0000
            DC.W      $0003,$0000,$0000,$0007
mwidth:     DC.W      $00a0,$00a0,$0050,$0000
            DC.W      $0140,$0000,$00a0,$0140

*+
* _fastcpy - "fast" 512-byte copy
* Synopsis:     fastcpy(src, dest)
*
*               Used by _rwabs to fake disk DMA to odd addresses.  Therefore,
*               disk I/O on odd addresses is very slow.  Lose, lose.
*
*-
_fastcpy:   movea.l   4(sp),a0                  ; a0 -> src
            movea.l   8(sp),a1                  ; a1 -> dest
            move.w    #$3f,d0                   ; d0 = move count (64*8 = 512)
fast1:      move.b    (a0)+,(a1)+               ; copy 8 bytes at a time
            move.b    (a0)+,(a1)+               ; to minimize loop overhead
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            dbra      d0,fast1
            rts

*+
* Go through hard-disk initialization vector
*
*-
_hinit:     move.l    (hdv_init).l,-(sp)
            rts

autopath:   DC.B      '\AUTO\*.PRG',0
            DC.W      $1234,$5678,$9abc,$def0

*+
* _auto - exec auto-startup files in the appropriate subdirectory
* _auto1 - exec (with filename args)
* Passed:       a0 -> full filespec (pathname)
*               a1 -> filename part of filespec
*               _drvbits: bit vector of active drives
*               _bootdev: contains device to exec from
*
* Returns:      nothing
*
* Note:         If _drvbits%%_bootdev is zero, _auto simply quits (since
*               the device isn't active....)
*
* Uses:         everything
*-
_auto:      move.l    #$bffff,-(sp)
            trap      #$d
            addq.l    #4,sp
            btst      #2,d0                     ; control key pressed?
            bne.s     autoq                     ; (then ignore the AUTO folders)
IF !ROM_TOS306
            move.l    (_drvbits).w,d0           ; d0 - active dev vector
            move.w    (_bootdev).l,d1           ; d1 - dev# to exec from
            btst      d1,d0                     ; is the dev alive?
            beq.s     autoq                     ; (no -- so punt)
ENDIF
            lea       autopath(pc),a0           ; -> path
            lea       autopath+6(pc),a1         ; -> filename
_auto1:     move.l    (sp)+,(autoret).l         ; return addr (used by execlr)
            move.l    a0,(pathname).w           ; setup filename/pathname ptrs
            move.l    a1,(filename).w
IF ROM_TOS306
            move.l    (_drvbits).w,d0           ; d0 - active dev vector
            move.w    (_bootdev).l,d1           ; d1 - dev# to exec from
            btst      d1,d0                     ; is the dev alive?
            beq.s     autoq                     ; (no -- so punt)
ENDIF
            lea       nullenv(pc),a0            ; a0 -> \0\0
            move.l    a0,-(sp)                  ; null enviorment
            move.l    a0,-(sp)                  ; null command tail
            move.l    a0,-(sp)                  ; null shell name
            move.w    #5,-(sp)                  ; Create-PSP subfunction
            move.w    #$4b,-(sp)                ; exec function#
            trap      #1                        ; do DOS CALL
            adda.w    #16,sp
            movea.l   d0,a0                     ; a0 -> PSP
            move.l    #fauto,8(a0)              ; initial PC -> autoexec prog
            move.l    a3,-(sp)                  ; null enviroment
            move.l    d0,-(sp)                  ; -> PSP
            move.l    a3,-(sp)                  ; null shell name
            move.w    #4,-(sp)                  ; just-go
            move.w    #$4b,-(sp)                ; function = exec
            trap      #1                        ; do it
            adda.w    #16,sp                    ; cleanup stack & goodbye
            move.l    (autoret).l,-(sp)
autoq:      rts


*+
* ST Book has another ROMDISK in the 2nd 256kb of the ROM
* which is mapped as drive 'P'.
*-
IF ROM_STBOOK
_auto_ROMDISK:lea     autopathROM(pc),a0        ; -> path
            lea       autopathROM+8(pc),a1      ; -> filename
            bra.s     _auto1

autopathROM:DC.B      'P:\AUTO\*.PRG',0
            DC.W      $1234,$5678,$9abc,$def0
ENDIF

*+
* fauto - exec'd by _auto to do autostartup
*
* Passed:       pathname -> path part of filespec
*               filename -> file path of filespec
*
*-
fauto:      clr.l     -(sp)                     ; get into super mode
            move.w    #$20,-(sp)
            trap      #1
            addq.w    #6,sp                     ; cleanup
            movea.l   d0,a4                     ; a4 -> saved super stack

*---- free up some memory
            movea.l   4(sp),a6                  ; a6 -> base page
            lea       $100(a6),sp               ; sp -> new, safer addr
            move.l    #$100,-(sp)               ; keep $100 (just the basepage)
            move.l    a6,-(sp)                  ; -> start of mem to keep
            clr.w     -(sp)                     ; junk word
            move.w    #$4a,-(sp)                ; setblock(...)
            trap      #1
            addq.w    #6,sp
            tst.w     d0
            bne.s     au_dn                     ; punt on error

            move.w    #7,-(sp)                  ; find r/o+hidden+system files
            move.l    (pathname).l,-(sp)        ; -> filename (on input)
            move.w    #$4e,-(sp)                ; searchFirst
            moveq     #8,d7                     ; d7 = cleanup amount
au1:        pea       (autodma).l               ; setup DTA (for search)
            move.w    #$1a,-(sp)
            trap      #1                        ; search first/search next
            addq.w    #6,sp
            trap      #1
            adda.w    d7,sp                     ; cleanup stack
            tst.w     d0                        ; test for match
            bne.s     au_dn                     ; (no match -- quit)

*--- construct filename from path and the name we just found:
            movea.l   (pathname).l,a0           ; copy pathname
            movea.l   (filename).l,a2           ; a2 -> end+1 of pathname
            lea       (autoname).l,a1
au3:        move.b    (a0)+,(a1)+               ; copy path part of name
            cmpa.l    a0,a2                     ; finished?
            bne.s     au3                       ; (no)
            lea       (autodma+30).l,a0         ; copy fname to end of pathname
au2:        move.b    (a0)+,(a1)+
            bne.s     au2
            pea       nullenv(pc)               ; null enviroment
            pea       nullenv(pc)               ; no command tail
            pea       (autoname).l              ; -> file to exec
            clr.w     -(sp)                     ; load-and-go
            move.w    #$4b,-(sp)                ; exec(...)
            trap      #1
            adda.w    #16,sp
            moveq     #2,d7                     ; reset cleanup amount
            move.w    #$4f,-(sp)                ; searchNext
            bra.s     au1

*+
* The first GEMDOS process can never terminate.
* This is not a good feature.
* Kludge around it - re-initialize the stack
* and return to the guy who called us to begin with.
*
*-
au_dn:      lea       (_supstk+2048).l,sp       ; setup supervisor stack
            move.l    (autoret).l,-(sp)         ; get return addr
            rts                                 ; just jump there ...


*+
* _dumpit: dump screen
*
*-
_dumpit:    movea.l   (scr_dump).l,a0
            jsr       (a0)
            move.w    #-1,(_prtcnt).l
            rts


*+
* _scrdmp - printScreen(), font-end to _prtblk()
*  Passed:      nothing
*  Returns:     nothing
*  Uses:        everything
*-
_scrdmp:    move.l    (_v_bas_adr).w,(p_blkptr).w ;-> screen mem
            clr.w     (p_offset).w              ; offset = 0
            clr.w     d0
            move.b    (sshiftmd).w,d0           ; get w & h
            move.w    d0,(p_srcres).w
            add.w     d0,d0
            lea       reztab(pc),a0
            move.w    (a0,d0.w),(p_width).w     ; set display width, height
            move.w    6(a0,d0.w),(p_height).w
            clr.w     (p_left).w                ; left = right = 0
            clr.w     (p_right).w
            move.l    #$ffff8240,(p_colpal).w   ; -> hardware palettes
            clr.w     (p_masks).w               ; default mask ptr

* draft or final mode
            move.w    (pconfig).w,d1            ; p_dstres = pconfig%%3
            lsr.w     #3,d1
            and.w     #1,d1
            move.w    d1,(p_dstres).w

* printer or rs232 port
            move.w    (pconfig).w,d1            ; p_port = pconfig%%4
            move.w    d1,d0
            lsr.w     #4,d0
            and.w     #1,d0
            move.w    d0,(p_port).w

* select printer flavor
            and.w     #7,d1                     ; p_type = ptype[pconfig & 7]
            move.b    ptype(pc,d1.w),d0
            move.w    d0,(p_type).l

* do it
            pea       (p_blkptr).w              ; -> beginning of parameter area
            move.w    #1,(_prtcnt).w
            bsr       _prtblk                   ; print it (finally)
            move.w    #-1,(_prtcnt).l
            addq.w    #4,sp                     ; cleanup stack
            rts                                 ; and return


*---- screen resolution table (pixel) for printScreen
reztab:     DC.W      $0140,$0280,$0280,$00c8
            DC.W      $00c8,$0190

*--- printer flavors (based on low 3 bits of pconfig)
ptype:      DC.B      $00,$02,$01,$ff,$03,$ff,$ff,$ff

*--- what it is:
mushroom:   DC.W      $0600,$2900,$0080,$4840
            DC.W      $11f0,$01f0,$07fc,$0ffe
            DC.W      $0dfe,$1fff,$1fef,$0fee
            DC.W      $0fde,$07fc,$03f8,$00e0

*+
* waitvbl - wait for the beam inside the vertical blank area
*-
IF ROM_TOS306
waitvbl:    clr.b     (tbcr).w
            clr.b     (tbdr).w
            move.b    #8,(tbcr).w
waitvbl2:   tst.b     (tbdr).w
            beq.s     waitvbl2
            jmp       (a6)
ELSE
waitvbl:    lea       (tbdr).w,a0               ; a0 -> timer B data register
            lea       (tbcr).w,a1               ; a1 -> timer B control register
            bclr      #0,(iera).w               ; disable IRQ of timer B
            moveq     #1,d4                     ; wait for the timer to expire
            clr.b     (a1)                      ; stop timer B
            move.b    #$f0,(a0)                 ; event every 240 scan lines
            move.b    #8,(a1)                   ; timer b: event count mode (HBL)
waitvbl2:   cmp.b     (a0),d4                   ; wait for HBL 239 scan lines to pass
            bne.s     waitvbl2
waitvbl3:   move.b    (a0),d4
            move.w    #615,d3                   ; wait till we are inside the vbl area
waitvbl4:   cmp.b     (a0),d4
            bne.s     waitvbl3
            dbra      d3,waitvbl4
            move.b    #$10,(a1)                 ; timer b: reset
            jmp       (a6)
ENDIF

_wvbl:      bra       wvbl

*+
* runresapps - search address space for 512-byte resident code chunks
*
*-
runresapps: movea.l   (phystop).l,a0            ; start at the top of the address space
runresappsl:suba.w    #$200,a0
            cmpa.w    #$400,a0                  ; reach the lower bottom?
            bls.s     runresappsx               ; (bail out)
            cmpi.l    #$12123456,(a0)           ; check for magic
            bne.s     runresappsl               ; (no magic -> next block)
            cmpa.l    4(a0),a0                  ; second long is equal the base address of the block?
            bne.s     runresappsl               ; (no -> next block)
            clr.w     d0
            movea.l   a0,a1
            move.w    #$ff,d1                   ; checksum over 256 words
runresappsc:add.w     (a1)+,d0                  ; checksum over the block
            dbra      d1,runresappsc
            cmp.w     #$5678,d0                 ; magic checksum?
            bne.s     runresappsl               ; (no -> next block)
            move.l    a0,-(sp)                  ; save current address
            jsr       8(a0)                     ; call code block
            movea.l   (sp)+,a0                  ; continue with next block
            bra.s     runresappsl
runresappsx:rts


gettime:    lea       (readRTCTime).l,a3
            lea       (readIKBDTime).l,a4
            bra.s     timecode
settime:    move.w    4(sp),(systemDate).l
            move.w    6(sp),(systemTime).l
            lea       (writeRTCTime).l,a3
            lea       (writeIKBDTime).l,a4
timecode:   bsr       checkRTC
            bcc.s     timecodertc
            movea.l   a4,a3
timecodertc:jmp       (a3)


*+
* sysbase2ram - copy sysbase into memory and patch it
*-
sysbase2ram:lea       osroml(pc),a0
            lea       (ramsysbase).l,a1
            moveq     #$2f,d0
sysbase2raml:move.b   (a0,d0.w),(a1,d0.w)       ; copy 48 bytes into memory
            dbra      d0,sysbase2raml
            move.w    sysbase2ramjmp(pc),$fffa(a1) ;copy JMP instruction just before
            move.l    4(a1),$fffc(a1)           ; copy ROM reseth into this JMP
            move.w    sysbase2rambra(pc),(a1)   ; patch BRA at the beginning of sysbase copy to hit the JMP
            move.w    $1e(a1),$1c(a1)           ; copy os_date into os_conf
            move.l    a1,(_sysbase).l           ; set new sysbase pointer
            rts

sysbase2ramjmp:jmp    ($0000).w                 ; destination address is patched to reseth address

sysbase2rambra:bra.s  sysbase2ramjmp            ; branch onto the JMP

*+
* blitmode - enabled/disable/test the blitter
*
* Returns: d0.w - bit 0 - blitter enabled
*                 bit 1 - blitter available
*-
blitmode:   bsr.s     blittest                  ; test if blitter is installed
            move.w    d0,d4                     ; d4 = blitter status
            move.w    d0,d5
            lsr.w     #1,d5
            or.w      #$fffe,d5                 ; d5 = blitter enable mask
            jsr       ($e0732a).l
            move.w    d0,d3                     ; d3 = blitter active
            move.w    4(sp),d0                  ; new blitter status
            bmi.s     blitmodex                 ; <0 just return the status
            and.w     d5,d0                     ; mask blitter status out
            or.w      d4,d0                     ; or'd blitter available status
            jsr       ($e07302).l               ; disable/enable blitter
blitmodex:  move.w    d3,d0
            rts

*+
* blittest - test if the blitter is installed
*-
blittest:   move      sr,d1
            move.w    #0,d0                     ; d0 = 0 -> blitter not installed
            suba.l    a0,a0
            movea.l   sp,a2
            ori       #$700,sr
            movea.l   8(a0),a1
            move.l    #blittestx,8(a0)
            tst.w     $8a00(a0)
            moveq     #2,d0                     ; d0 = 2 -> blitter installed
blittestx:  move.l    a1,8(a0)
            move      d1,sr
            movea.l   a2,sp
            rts

*+
* delay for a little bit via timer a in the mfp
*-
mfpdelay:   bsr.s     mfpdelaysetup
IF !ROM_TOS306
mfpdelayl:  btst      #5,(ipra).w               ; did timer a fire?
            beq.s     mfpdelayl                 ; wait more if not
            clr.b     (tacr).w                  ; stop timer a
ELSE
mfpdelayl:  btst      #5,(iprb_TT).w            ; did timer a fire?
            beq.s     mfpdelayl                 ; wait more if not
ENDIF
            rts

*+
* setup timer a in the mfp
*-
mfpdelaysetup:
IF !ROM_TOS306
            move    sr,-(sp)
            ori       #$700,sr
            clr.b     (tacr).w                  ; timer A control register
            bclr      #5,(iera).w               ; disable timer A
            move.b    #$df,(ipra).w             ; clear a pending timer A interrupt
            bclr      #5,(imra).w               ; mask the timer A interrupt
            bset      #5,(iera).w               ; enable timer A
            move      (sp)+,sr
            move.b    d0,(tadr).w               ; set timer a data register
            ror.w     #8,d0
            move.b    d0,(tacr).w               ; set timer a control register
            rol.w     #8,d0
ELSE
            movem.w   d0-d1,-(sp)
            move      sr,-(sp)
            ori       #$700,sr
            move.b    (tcdcr_TT).w,d1
            and.b     #$f,d1
            move.b    d1,(tcdcr_TT).w
            bclr      #5,(ierb_TT).w
            move.b    #$df,(iprb_TT).w
            bclr      #5,(imrb_TT).w
            bset      #5,(ierb_TT).w
            move.b    d0,(tcdr_TT).w
            lsr.w     #4,d0
            and.b     #$f0,d0
            or.b      d0,d1
            move.b    d1,(tcdcr_TT).w
            move      (sp)+,sr
            movem.w   (sp)+,d0-d1
            rts
ENDIF
            rts

memclear:   movea.l   4(sp),a0                  ; a0 -> start address of the block
            movea.l   8(sp),a1                  ; a1 -> end address of the block
            movem.l   d3-d7/a3,-(sp)
            moveq     #0,d1
            moveq     #0,d2
            moveq     #0,d3
            moveq     #0,d4
            moveq     #0,d5
            moveq     #0,d6
            moveq     #0,d7
            movea.w   d7,a3
            move.l    a0,d0
            btst      #0,d0
            beq.s     memclear1
            move.b    d1,(a0)+                  ; clear a single byte to even-align
memclear1:  move.l    a1,d0
            sub.l     a0,d0
            and.l     #$ffffff00,d0
            beq.s     memclearb
            lea       (a0,d0.l),a0
            movea.l   a0,a2
            lsr.l     #8,d0
memclear256:movem.l   d1-d7/a3,-(a2)            ; clear 256 bytes
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            subq.l    #1,d0
            bne.s     memclear256
memclearb:  cmpa.l    a0,a1                     ; end address reached?
            beq.s     memcleare
            move.b    d1,(a0)+                  ; clear a single byte
            bra.s     memclearb
memcleare:  movem.l   (sp)+,d3-d7/a3
            rts

*+
* setup PMMU translation table
*-
IF ROM_TOS306
setupPMMU:  lea       ($700).l,a0
            lea       ($e363f0).l,a1            ; translation table
            move.w    #$3f,d0                   ; 64 descriptors
setupPMMUl: move.l    (a1)+,(a0)+
            dbra      d0,setupPMMUl

* PMMU Table:
* $x.......
*   $700: $00000742,$10000001,$20000001,$30000001,$40000001,$50000001,$60000001,$70000001,
*   $720: $80000041,$90000041,$A0000041,$B0000041,$C0000041,$D0000041,$E0000041,$00000782,

* $0.......
*   $740: $000007C2,$01000001,$02000001,$03000001,$04000001,$05000001,$06000001,$07000001,
*   $760: $08000001,$09000001,$0A000001,$0B000001,$0C000001,$0D000001,$0E000001,$0F000001,

* $Fx......
*   $780: $00000041,$01000041,$02000041,$03000041,$04000041,$05000041,$06000041,$07000041,
*   $7a0: $08000041,$09000041,$0A000041,$0B000041,$0C000041,$0D000041,$0E000041,$000007C2,

* $00...... or $FF......
*   $7c0: $00000001,$00100001,$00200001,$00300001,$00400001,$00500001,$00600001,$00700001,
*   $7e0: $00800001,$00900001,$00A00001,$00B00001,$00C00001,$00D00001,$00E00001,$00F00041,

* The purpose of the PMMU is to disable caching in the area of the address space that doesn't contain ram:
* $00F00000..$00FFFFFF
* $80000000..$EFFFFFFF
* $F0000000..$FEFFFFFF
* $FFF00000..$FFFFFFFF

            pmove     ($e364f0).l,crp           ; CPU Root Pointer: $80000002,$00000700: 4 byte descriptor, at $700, lower limit: $00000
            pmove     ($e364f8).l,tc            ; Translation Control Register: $80F04445: MMU enabled, pagesize = 15, bits: 4,4,4,5
            pmove     ($e364fc).l,tt0           ; Transparent Translation Register 0: $017E8107
            pmove     ($e36500).l,tt1           ; Transparent Translation Register 1: $807E8507
            rts
ENDIF

*+
* calccrc - calc a crc
*-
calccrc:    movea.l   4(sp),a0                  ; a0 -> buffer start
            move.l    8(sp),d2                  ; d2 -> number of bytes
            movea.w   $c(sp),a1                 ; a1 -> step offset through the buffer
            clr.w     d0
            clr.w     d1
            clr.w     d3
            lea       (crcTable).l,a2
calccrcl:   move.w    d0,d1
            lsl.w     #8,d0
            lsr.w     #8,d1
            move.b    (a0),d3
            adda.l    a1,a0
            eor.b     d3,d1
            add.w     d1,d1
            move.w    (a2,d1.w),d4
            eor.w     d4,d0
            subq.l    #1,d2
            bne.s     calccrcl
            rts

IF ROM_TOS306
_EsetShift: bsr       _wvbl
            moveq     #0,d0
            move.w    (shift_tt).w,-(sp)
            move.w    6(sp),(shift_tt).w
            move.w    (shift_tt).w,d0
            and.w     #7,d0
            move.b    d0,(sshiftmd).w
            clr.w     (vblsem).w
            jsr       (esc_init).l
            move.w    #1,(vblsem).w
            move.w    (sp)+,d0
            rts

_EgetShift: moveq     #0,d0
            move.w    (shift_tt).w,d0
            rts

_EsetBank:  moveq     #0,d0
            move.w    (shift_tt).w,d0
            and.w     #$f,d0
            tst.w     4(sp)
            bmi.s     _EsetBankx
            move.b    5(sp),(shift_tt+1).w
_EsetBankx: rts

_EsetColor: moveq     #0,d0
            lea       (TT_col).w,a0
            move.w    4(sp),d0
            and.w     #$ff,d0
            add.w     d0,d0
            adda.w    d0,a0
            move.w    (a0),d0
            and.w     #$fff,d0
            move.w    6(sp),d1
            bmi.s     _EsetColorx
            move.w    d1,(a0)
_EsetColorx:rts

_EsetPalette:move.w   4(sp),d0
            and.w     #$ff,d0
            movea.w   d0,a0
            adda.w    a0,a0
            sub.w     #$100,d0
            neg.w     d0
            move.w    6(sp),d1
            cmp.w     d0,d1
            ble.s     _EsetPal2
            move.w    d0,d1
_EsetPal2:  movea.l   8(sp),a1
            lea       $8400(a0),a0
            bra.s     _EsetPal4
_EsetPal3:  move.w    (a1)+,(a0)+
_EsetPal4:  dbra      d1,_EsetPal3
            rts

_EgetPalette:move.w   4(sp),d0
            and.w     #$ff,d0
            movea.w   d0,a0
            adda.w    a0,a0
            sub.w     #$100,d0
            neg.w     d0
            move.w    6(sp),d1
            cmp.w     d0,d1
            ble.s     _EgetPal2
            move.w    d0,d1
_EgetPal2:  movea.l   8(sp),a1
            lea       $8400(a0),a0
            bra.s     _EgetPal4
_EgetPal3:  move.w    (a0)+,(a1)+
_EgetPal4:  dbra      d1,_EgetPal3
            rts

_EsetGrey:  moveq     #0,d0
            move.b    (shift_tt).w,d1
            move.b    d1,d0
            lsr.b     #4,d0
            and.b     #1,d0
            bclr      #4,d1
            tst.w     4(sp)
            beq.s     _EsetGrey2
            bmi.s     _EsetGreyr
            bset      #4,d1
_EsetGrey2: move.b    d1,(shift_tt).w
_EsetGreyr: rts

_EsetSmear: moveq     #0,d0
            move.b    (shift_tt).w,d1
            move.b    d1,d0
            add.b     d0,d0
            subx.w    d0,d0
            neg.w     d0
            bclr      #7,d1
            tst.w     4(sp)
            beq.s     _EsetSmear2
            bmi.s     _EsetSmearr
            bset      #7,d1
_EsetSmear2:move.b    d1,(shift_tt).w
_EsetSmearr:rts
ENDIF

**************************************************************************
* DMAREAD.S from the AHDI source                                         *
**************************************************************************
*+
* dmaread() - read from a DMA device.
* dmawrite() - write to a DMA device.
*
* LONG  sectnum     $4(sp).l
* WORD  count       $8(sp).w
* BYTE  *buf;       $a(sp).l    $b(sp)=high $c(sp)=mid $d(sp)=low
* WORD  pdev;       $e(sp).w
*
* Returns 0 if successful.
* Returns a negative number if failure:
*   EUNDEV (-15L) if unknown device (bad dev number)
*   EREADF (-11L) if read failure
*   EWRITF (-10L) if write failure
*          (-1L)  if timed-out
*
* Comments:
*   ACSI, SCSI and IDE-AT drives are supported.
*-
_dmaread:   move.b    #0,(rwflag).l             ; rwflag = 0 => a read
            move.w    #$08,d1                   ; d1 = opcode for READ
            bra.s     dmarw
_dmawrite:  move.b    #1,(rwflag).l             ; rwflag = 1 => a write
            move.w    #$0a,d1                   ; d1 = opcode for WRITE
dmarw:      cmpi.w    #$10,$e(sp)               ; unit# > IDE-AT unit# 16?
            bhi       undev                     ; if so, return unknow device
            cmpi.w    #$f,$e(sp)                ; an IDE-AT unit?
            bhi.s     ide0                      ; if so, talk IDE-AT
* else talk ACSI or SCSI
            lea       (_cmdblk).l,a0            ; a0 -> beginning of command block
            move.b    d1,(a0)+                  ; byte 0 = opcode
            move.b    5(sp),(a0)+               ; byte 1 = msb of logical block addr
            move.b    6(sp),(a0)+               ; byte 2 = logical block addr
            move.b    7(sp),(a0)+               ; byte 3 = lsb of logical block addr
            move.b    9(sp),(a0)+               ; byte 4 = transfer length (in blocks)
            clr.b     (a0)                      ; byte 5 = control byte
            move.w    $e(sp),d0                 ; d0 = physical unit number
IF ROM_TOS306
            moveq     #0,d1
            move.w    8(sp),d1
            lsl.l     #8,d1
            add.l     d1,d1
ENDIF
            moveq     #6,d2                     ; d2 = length of command
            movea.l   $a(sp),a0                 ; a0 = buffer
            tst.b     (rwflag).l                ; read or write?
            bne.s     rw0                       ; it's a write
            bsr       _dorcmd                   ; send a receive data command
            bra       rw1
rw0:        bsr       wracsi                    ; send a write data command
            bra       rw1                       ; branch will take place for non-IDE drives
*
* Before doing anything with the IDE bus, make sure it's there.
* (The label noide is before at just because it fits better.)
*
noide:      movea.l   a1,sp                     ; got the bus error on IDEASR;
            move.l    a0,(busexception).w       ; restore vector & sp, return EUNDEV
            bra       undev

ide0:       movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #noide,(busexception).w
            tst.b     (ide_stat2).l             ; read a register to probe for IDE bus
            movea.l   a1,sp                     ; hey! no bus error!
            move.l    a0,(busexception).w       ; restore vector & sp and continue.
            move.b    $f(sp),d0                 ; IDE unit#
            bsr       _iderdy                   ; wait for drive to be ready
IF ROM_TOS306
            beq       undev                     ; if drive never gets ready, return with unknown device else
ELSE
            beq.s     undev                     ; if drive never gets ready, return with unknown device else
ENDIF
            moveq     #2,d0                     ; D_IDENTIFY delay required by Conner drives
            add.l     (_hz_200).w,d0            ; between power on and identify()
ide1:       cmp.l     (_hz_200).w,d0
            bcc.s     ide1

            pea       (sbuf).l                  ; scratch buffer for drive parameters
            move.w    $12(sp),-(sp)             ; IDE unit#
            bsr       _identify                 ; identify()
            addq.w    #6,sp                     ; clean up stack
            tst.w     d0                        ; successful?
IF ROM_TOS306
            bmi       rwend                     ; if timed-out, return
ELSE
            bmi.s     rwend                     ; if timed-out, return
ENDIF
            bne.s     rw2                       ; if error, return with error code else can do read or write
            moveq     #2,d0                     ; D_IDENTIFY delay required by Conner drives
            add.l     (_hz_200).w,d0            ; between identify() and r/w
ide4:       cmp.l     (_hz_200).w,d0
            bcc.s     ide4

IF ROM_TOS306
            lea       (sbuf).l,a0
            move.w    $c(a0),-(sp)              ; # of sectors per track
            move.w    6(a0),-(sp)               ; # of heads
            move.w    $12(sp),-(sp)             ; physical unit #
            bsr       _initdevpar
            addq.w    #6,sp
            tst.w     d0
            bmi.s     rwend
            bne.s     rw2
            lea       (sbuf).l,a0
ELSE
            pea       (sbuf).l                  ; beginning of _identify() data
            bsr       _gcparm                   ; get drive current parameters
            addq.w    #4,sp                     ; clean up stack
ENDIF
            move.w    $e(sp),-(sp)              ; physical unit #
            move.l    $c(sp),-(sp)              ; buffer
            move.w    $e(sp),-(sp)              ; count
            move.l    $c(sp),-(sp)              ; logical block address
IF ROM_TOS306
            move.w    $c(a0),-(sp)              ; # of sectors per track
            move.w    6(a0),-(sp)               ; # of heads
ELSE
            move.w    d2,-(sp)                  ; # sectors per track
            move.w    d1,-(sp)                  ; # data heads
ENDIF
            tst.b     (rwflag).l                ; read or write?
            bne.s     ide2                      ; (write)
            bsr       _ideread                  ; read sectors
            bra.s     ide3
ide2:       bsr       _idewrite                 ; write sectors
ide3:       adda.w    #16,sp                    ; clean up stack
rw1:
IF ROM_TOS306
            move      sr,-(sp)
            ori       #$700,sr
            movec     cacr,d1
            ori.w     #$808,d1
            movec     d1,cacr
            move      (sp)+,sr
ENDIF
            tst.w     d0                        ; successful?
            ble.s     rwend                     ; if no error or timed-out, return else
rw2:        moveq     #-11,d0                   ; assume it's a read error EREADF
            tst.b     (rwflag).l                ; read or write?
            beq.s     rwend                     ; if read, done
            moveq     #-10,d0                   ; else, it's a write error EWRITF
rwend:      rts

undev:      moveq     #-15,d0                   ; EUNDEV
            bra.s     rwend

*+
* dorcmd() - send a command which will receive data from the target
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
_dorcmd:
IF ROM_TOS306
            cmp.w     #7,d0
            bls.s     _dorcmd2
            bsr       _rcvscsi
            rts
_dorcmd2:   bsr       _rcvacsi                  ; it's an ACSI device
ELSE
            bsr.s     _rcvacsi                  ; it's an ACSI device
ENDIF
            rts

*+
* dowcmd() - send a command which will write data to the target
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
wracsi:
IF ROM_TOS306
            cmp.w     #7,d0
            bls.s     wracsi2
            bsr       _wrtscsi
            rts
wracsi2:
ENDIF
            bsr       _wrtacsi                  ; it's an ACSI device
            rts

*+
* LONG _qdone() - Wait for command byte handshake
* LONG _fdone() - Wait for operation complete
* Passed:   nothing
*
* Returns:  EQ: no time-out
*       MI: time-out condition
*
* Uses:     D0
*
*-
_fdone:     move.l    #600,d0                   ; ACLTMOUT
            bra.s     qd0

_qdone:     moveq     #20,d0                    ; ACSTMOUT

qd0:
IF ROM_TOS306
            move.l    d0,-(sp)                  ; save timeout value
            moveq     #2,d0                     ; busy-wait delay for slow ACSI
            add.l     (_hz_200).w,d0            ; minimum 20 microsec.
sdelay:     cmp.l     (_hz_200).w,d0
            bge.s     sdelay
            move.l    (sp)+,d0                  ; restore timeout value
ENDIF
            add.l     (_hz_200).w,d0
qd1:        cmp.l     (_hz_200).w,d0            ; time-out?
            bcs.s     qdq                       ; (i give up, return NE)
            btst      #5,(gpip).w               ; interrupt?
            bne.s     qd1                       ; (not yet)
            moveq     #0,d0                     ; return EQ (no time-out)
            rts

qdq:        moveq     #-1,d0
            rts

*+
* Wait for end of SASI command
*
* Passed:   d1 value to be written to wdl
*
* Returns:  EQ: success (error code in D0.W)
*       MI: time-out (-1 in D0.W)
*       NE: failure (SASI error code in D0.W)
*
* Uses:     d0
*-
_endcmd:    bsr.s     _fdone                    ; wait for operation complete
            bmi.s     endce                     ; (timed-out, so complain)
IF ROM_TOS306
            move      sr,-(sp)
            ori       #$700,sr
            movec     cacr,d0
            ori.w     #$800,d0
            movec     d0,cacr
            move      (sp)+,sr
ENDIF
            move.w    d1,(fifo).w
            move.w    (diskctl).w,d0            ; get the result
            and.w     #$ff,d0                   ; (clean it up) if non-0 should do a RequestSense command to learn more
endce:      move.l    (_hz_200).w,(lastacstm).l ; update controller last accessed time
            addq.l    #2,(lastacstm).l          ; lastacstm = _hz_200 + 2;
            rts

*+
*  Handle command time-out;
*  Unlock DMA chip and return completion status;
*-
_hdone:     move.w    #$80,(fifo).w             ; Landon's code seems to presume we put $80 there
IF ROM_TOS306
            clr.w     (flock).w                 ; NOW, signal that we are done
ELSE
            sf        (flock).w                 ; NOW, signal that we are done
ENDIF
            rts

*+
* delay()
*   5 - 10ms kludge delay for message byte sent back by controller.
*-
_delay:     move.l    d0,-(sp)                  ; preserve d0
            move.l    (lastacstm).l,d0          ; d0 = controller last accessed time
wait:       cmp.l     (_hz_200).w,d0            ; while (_hz_200 <= lastacstm)
            bcc.s     wait                      ; wait()
            move.l    (sp)+,d0                  ; restore d0
            rts

*+
* rcvacsi() - send a ACSI command which receives data from target.
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
_rcvacsi:   movea.l   (busexception).w,a1       ; check to see if the ACSI bus actually exists! Needed for prototype PADs
            movea.l   sp,a2
            move.l    #noacsi,(busexception).w
            tst.w     (fifo).w                  ; harmless or bus erorr
            move.l    a1,(busexception).w       ; no bus error - restore & continue
            movea.l   a2,sp
            st        (flock).w                 ; lock FIFO
            bsr.s     _delay                    ; delay if necessary
            movea.w   #diskctl,a1               ; a1 = pointer to DMA chip
            bsr       setadma                   ; set DMA pointer
            move.w    #$190,2(a1)               ; WDL   ; toggle DMA chip to direction
            bsr       rstdelay                  ; delay
            move.w    #$90,2(a1)                ; WDL   ;  for receiving data
            bsr       rstdelay                  ; delay
            bsr       setacnt                   ; set DMA count
            lea       (_cmdblk).l,a0            ; a0 = address of command block
            moveq     #0,d1                     ; direction of DMA is IN
            bsr.s     sblkacsi                  ; send the command block
raend:      bra.s     _hdone                    ; cleanup after IRQ
*+
* wrtacsi() - send an ACSI command which will write data to the target
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
*
_wrtacsi:   movea.l   (busexception).w,a1       ; check to see if the ACSI bus actually exists! Needed for prototype PADs
            movea.l   sp,a2
            move.l    #noacsi,(busexception).w
            tst.w     (diskctl).w               ; harmless or bus erorr
            move.l    a1,(busexception).w       ; no bus error - restore & continue
            movea.l   a2,sp
            st        (flock).w                 ; lock FIFO
            bsr.s     _delay
            movea.w   #diskctl,a1               ; a1 = pointer to DMA chip
            bsr.s     setadma                   ; set DMA pointer
            move.w    #$90,2(a1)                ; WDL   ; toggle DMA chip for "send"
            bsr       rstdelay                  ; delay
            move.w    #$190,2(a1)               ; WDL
IF ROM_TOS306
            bsr       rstdelay                  ; delay
ELSE
            bsr.s     rstdelay                  ; delay
ENDIF
            bsr.s     setacnt                   ; set DMA count
            move.l    #$100,d1                  ; d1 = direction of DMA is OUT
            bsr.s     sblkacsi                  ; send the command block
waend:      bra       _hdone                    ; cleanup after IRQ
* You get to this label if there is a bus error when probing for
* the ACSI bus: it's meant for prototype PADs, which don't
* have an ACSI DMA chip.
noacsi:     moveq     #-15,d0                   ; return EUNDEV
            movea.l   a2,sp
            move.l    a1,(busexception).w
            rts

*+
* sblkacsi() - send command block
*
* Passed:
*   d0.w = physical unit number
*   d1.l = direction of DMA ($0000 for IN or $0100 for OUT)
*   d2.w = command length (NCMD or LCMD)
*   a1.l = pointer to DMA chip
*
* Returns:
*   d0.l =  0 if successful
*   d0.l = -1 if time-out
*
* Trashes:
*   d0, d1, d2, a2
*-
sblkacsi:   move.b    #$88,d1                   ; next byte is the opcode
            move.w    d1,2(a1)                  ; WDL
            move.b    #$8a,d1                   ; following bytes are operands
            lea       (_cmdblk).l,a2            ; a2 = address of command block
* integrate unit # into cmd blk
            lsl.b     #5,d0                     ; shift unit number into place
            or.b      d0,(a2)                   ; first command byte = unit # | opcode
* control byte is sent seperately
            subq.w    #2,d2                     ; and dbra likes one less
sa1:        swap      d1                        ; d1.hw = operand
            move.b    (a2)+,d1                  ; d1.lw = tells controller next byte
            swap      d1                        ; is an operand
            move.l    d1,(a1)                   ; WDCWDL
            bsr       _qdone
            bmi.s     sbaend                    ; if time-out, returns
            dbra      d2,sa1                    ; else send rest of command block
            move.w    d1,2(a1)                  ; WDL - get ready to send control byte
            move.b    #0,d1                     ; signal sending control byte
            swap      d1                        ; d1.hw = control byte
            move.b    (a2),d1                   ; d1.lw = tells controller it's end
            swap      d1                        ; of command
            move.l    d1,(a1)                   ; send it
            move.b    #$8a,d1                   ; d1 = wdl value
            bsr       _endcmd                   ; wait for command completion
sbaend:     rts                                 ; heading home

*+
* setadma() - set the ACSI DMA pointer
*
* Passed:
*   a0.l = buffer address
*-
setadma:    move.l    a0,-(sp)                  ; move it on stack
            move.b    3(sp),(dmalow).w          ; set low-byte of address
            move.b    2(sp),(dmamid).w          ; set mid-byte of address
            move.b    1(sp),(dmahigh).w         ; set high-byte of address
            addq.l    #4,sp                     ; clean up stack
            rts


*+
* setacnt() - set the ACSI DMA counter
*
* Passed:
*   a1.l = pointer to DMA chip
*-
setacnt:
IF ROM_TOS306
            move.l    d1,-(sp)
            lsr.l     #8,d1
            add.l     d1,d1
            move.w    d1,(a1)
            move.l    (sp)+,d1
ELSE
            move.w    #$ff,(a1)                 ; set the ACSI DMA counter to 255 512-byte-blocks
ENDIF
            rts


*   After talking to the DMA chip in a way that may reset it,
* we need a 8 8Mhz clocks (ie. 1 microsec) delay, before we can
* talk to the chip again.
rstdelay:   tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            tst.b     (gpip).w
            rts

IF ROM_TOS306
_rcvscsi:   move.l    a0,-(sp)
            andi.w    #7,d0
            bsr.s     _scsicmd
            movea.l   (sp)+,a0
            tst.w     d0
            bmi.s     _rcvscsir
            movea.l   a0,a1
            movea.w   #s_data,a2
            move.b    #1,(s_tcr).w
            move.b    (s_inircv).w,d0
_rcvscsil:  bsr       _scsidelay250ms
            bsr       _scsiwait
            bmi.s     _rcvscsir
            btst      #3,$a(a2)
            beq       _scsiaction
            move.b    (a2),(a1)+
            bsr       _scsidone
            bra.s     _rcvscsil
_rcvscsir:  rts

_wrtscsi:   andi.w    #7,d0
            move.l    a0,-(sp)
            move.w    d2,-(sp)
            bsr.s     _scsicmd
            move.w    (sp)+,d2
            movea.l   (sp)+,a0
            tst.w     d0
            bmi.s     _wrtscsir
            movea.l   a0,a1
            movea.w   #s_data,a2
            move.b    #0,(s_tcr).w
            move.b    (s_inircv).w,d0
_wrtscsil:  bsr       _scsidelay250ms
            bsr       _scsiwait
            bmi.s     _wrtscsir
            btst      #3,$a(a2)
            beq       _scsiaction
            move.b    (a1)+,(a2)
            bsr       _scsidone
            bra.s     _wrtscsil
_wrtscsir:  rts

_scsicmd:   move.l    d2,-(sp)
            move.w    d0,-(sp)
            bsr.s     _scsido
            addq.w    #2,sp
            move.l    (sp)+,d2
            tst.w     d0
            bmi.s     _scsicmdr
            move.b    #2,(s_tcr).w
            move.b    #1,(s_icr).w
            lea       ($a96).l,a1
            subq.w    #1,d2
            bsr       _scsidelay250ms
_scsicmdl:  move.b    (a1)+,d0
            bsr       _scsisuba
            tst.w     d0
            bmi.s     _scsicmdr
            dbra      d2,_scsicmdl
            moveq     #0,d0
_scsicmdr:  rts

_scsido:    bsr       _scsidelay250ms
_scsidow:   btst      #6,(s_idstat).w
            beq.s     _scsido2
            cmp.l     (a0),d1
            bhi.s     _scsidow
            bra.s     _scsidoerr
_scsido2:   move.b    #0,(s_tcr).w
            move.b    #0,(s_idstat).w
            move.b    #$c,(s_icr).w
            clr.w     d0
            move.w    4(sp),d1
            bset      d1,d0
            move.b    d0,(s_data).w
            move.b    #$d,(s_icr).w
            andi.b    #$fe,(s_mode).w
            andi.b    #$f7,(s_icr).w
            bsr       _scsidelay250ms
_scsidow2:  btst      #6,(s_idstat).w
            bne.s     _scsidook
            cmp.l     (a0),d1
            bhi.s     _scsidow2
_scsidoerr: moveq     #-1,d0
            bra.s     _scsidowr
_scsidook:  clr.w     d0
_scsidowr:  move.b    #0,(s_icr).w
            rts

_scsiaction:bsr       _scsidelay250ms
            move.b    #3,(s_tcr).w
            move.b    (s_inircv).w,d0
            bsr.s     _scsiwait
            bmi.s     _scsiactionr
            moveq     #0,d0
            move.b    (s_data).w,d0
            bsr       _scsidelay250ms
            move.l    d0,-(sp)
            bsr.s     _scsidone
            tst.w     d0
            beq.s     _scsiaction3
_scsiaction2:addq.l    #4,sp
            bra.s     _scsiactionr
_scsiaction3:bsr.s     _scsidelay250ms
            bsr.s     _scsiwait
            bmi.s     _scsiaction2
            move.b    (s_data).w,d0
            bsr.s     _scsidone
            tst.w     d0
            bmi.s     _scsiaction2
            move.l    (sp)+,d0
_scsiactionr:rts

_scsiwait:  btst      #5,(s_idstat).w
            bne.s     _scsiwaitok
            cmp.l     (a0),d1
            bhi.s     _scsiwait
            moveq     #-1,d0
            bra.s     _scsiwaitr
_scsiwaitok:moveq     #0,d0
_scsiwaitr: rts

_scsidone:  ori.b     #$11,(s_icr).w
_scsidonel: btst      #5,(s_idstat).w
            beq.s     _scsidoneok
            cmp.l     (a0),d1
            bhi.s     _scsidonel
            moveq     #-1,d0
            bra.s     _scsidoner
_scsidoneok:moveq     #0,d0
_scsidoner: andi.b    #$ef,(s_icr).w
            rts

_scsisuba:  move.w    d0,-(sp)
            bsr.s     _scsiwait
            bmi.s     _scsisuba2
            move.b    1(sp),(s_data).w
            bsr.s     _scsidone
_scsisuba2: addq.l    #2,sp
            rts

_scsisubb:  move.b    #$80,(s_icr).w
            bsr.s     _scsidelay250ms
_scsisubbl: cmp.l     (a0),d1
            bhi.s     _scsisubbl
            move.b    #0,(s_icr).w
            bsr.s     _scsidelay1s
_scsisubbl2:cmp.l     (a0),d1
            bhi.s     _scsisubbl2
            rts

_scsidelay250ms:
            movea.w   #_hz_200,a0
            moveq     #51,d1
            add.l     (a0),d1
            rts

_scsidelay1s:
            movea.w   #_hz_200,a0
            move.l    #201,d1
            add.l     (a0),d1
            rts
ENDIF

*+
* Wait for status to come back
*-
w4int:      move.l    #10*200,d0                ; d0 = time-out limit (D_WORST)
            add.l     (_hz_200).w,d0            ; d0 = expiration time
wi0:        btst      #5,(gpip).w               ; interrupt?
            beq.s     wi0                       ; if so, out of the loop
            cmp.l     (_hz_200).w,d0            ; time-out?
            bhi.s     wi0                       ; if not, wait some more
            moveq     #-1,d0                    ; else, return time-out
            bra.s     wi3
wi0:        moveq     #0,d0                     ; clear d0
            move.b    (ide_comst).l,d0          ; d0.b = status returned
            btst      #0,d0                     ; any error?
            bne.s     wi2                       ; if yes, return error code
            btst      #3,d0                     ; else DRQ?
            bne.s     wi3                       ; if so, just return
            moveq     #0,d0                     ; else return OK
            bra.s     wi3
wi2:        move.b    (ide_param).l,d0          ; else d0.b = error bits
wi3:        rts                                 ; return status or error code

*+
* ideread() - reads from 1 to 256 sectors as specified in the Task File,
*       beginning at the specified sector.
*      - sector count equal to 0 requests 256 sectors.
*
* ideread(nhd, nspt, sectnum, count, buf, pdev)
* WORD  nhd;        4(sp).w     ; # of data heads on pdev
* WORD  nspt;       6(sp).w     ; # of physical sectors per track
* LONG  sectnum;    8(sp).l     ; logical block address
* WORD  count;      $c(sp).w    ; # of sectors to read
* BYTE  *buf;       $e(sp).l    ; $f(sp)=high $10(sp)=mid $11(sp)=low
* WORD  pdev;       $12(sp).w   ; physical device number
*-
_ideread:   bsr       set_dhcs                  ; set physical address
            movea.l   $e(sp),a0                 ; a0 -> buffer to read into
            move.b    $d(sp),(ide_seccn).l      ; set sector count
            move.w    $c(sp),d1                 ; d1.w = # of sectors to read
            subq.w    #1,d1                     ; dbra likes one less
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$20,(ide_comst).l        ; set command code IDEREAD
ider0:      bsr.s     w4int                     ; wait for interrupt
            tst.w     d0                        ; successful?
            bmi.s     ider1                     ; if timed-out, return
            btst      #3,d0                     ; DRQ?
            beq.s     ider1                     ; if not, return
            bsr       readbuf                   ; fill sector buffer
            dbra      d1,ider0                  ; more to read?
            moveq     #0,d0                     ; everything's fine
ider1:      rts

*+
* idewrite() - writes from 1 to 256 sectors as specified in the Task File,
*       beginning at the specified sector.
*       - sector count equal to 0 requests 256 sectors.
*
* idewrite(nhd, nspt, sectnum, count, buf, pdev)
* WORD  nhd;        4(sp).w     ; # of data heads on pdev
* WORD  nspt;       6(sp).w     ; # of physical sectors per track
* LONG  sectnum;    8(sp).l     ; logical block address
* WORD  count;      $c(sp).w    ; # sectors to read
* BYTE  *buf;       $e(sp).l    ; $f(sp)=high $10(sp)=mid $11(sp)=low
* WORD  pdev;       $12(sp).w   ; physical device number
*-
_idewrite:  bsr.s     set_dhcs                  ; set physical address
            movea.l   $e(sp),a0                 ; a0 -> buffer to write from
            move.b    $d(sp),(ide_seccn).l      ; set sector count
            move.w    $c(sp),d1                 ; d1.w = # of sectors to read
            subq.w    #1,d1                     ; dbra likes one less
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$30,(ide_comst).l        ; set command code IDEWRITE
idew0:      btst      #3,(ide_stat2).l          ; DRQ?
            beq.s     idew0                     ; if not, wait longer
idew1:      bsr       wrtbuf                    ; fill sector buffer
            bsr       w4int                     ; wait for interrupt
            tst.w     d0                        ; successful?
            bmi.s     idew2                     ; if timed-out, return
            btst      #3,d0                     ; DRQ?
            beq.s     idew2                     ; if not, return
            dbra      d1,idew1                  ; else go transfer data
            moveq     #0,d0                     ; everything's fine
idew2:      rts

*+
* set_dhcs() - convert a logical block address into a physical address.
*        - set drive #, head #, cylinder # and sector # in task file.
*
* Passed:
*   8(sp).w = nhd = # of data heads
*   $a(sp).w = nspt = # of physical sectors per track
*   $c(sp).l = logical block address
*   $16(sp).w = physical unit #
*-
set_dhcs:   move.l    $c(sp),d1                 ; d1.l = logical block address
            move.w    8(sp),d2                  ; d2.w = # of data heads
            move.w    $a(sp),d0                 ; d0.w = # of physical sectors per track
            mulu.w    d0,d2                     ; d2.l = # of sectors per cylinder = # heads * # of sectors per track
            divu.w    d2,d1                     ; d1.w = cylinder # = log block addr / #spc
            move.b    d1,(ide_zylow).l          ; set cylinder low
            lsr.l     #8,d1                     ; d1.b = cylinder high
            move.b    d1,(ide_zyhig).l          ; set cylinder high
            lsr.l     #8,d1                     ; d1.l = sector # within the cyl
            divu.w    d0,d1                     ; d1.w = head # = sector # within cyl / #spt
            move.w    $16(sp),d0                ; d0.w = physical unit #
            andi.b    #7,d0                     ; mask off flags from physical unit #
            lsl.b     #4,d0                     ; shift unit # to place
            or.b      d0,d1                     ; or in drive #
            move.b    d1,(ide_headn).l          ; set drive and head #
            swap      d1                        ; d1.w = sector # (base 0)
            addq.w    #1,d1                     ; = sector # + 1 (base 1)
            move.b    d1,(ide_stars).l          ; set sector #
            rts

*+
* identify() - allows the Host to receive parameter information from
*          the drive.
*
* identify(pdev, buf)
* WORD  pdev;   4(sp).w     ; physical unit #
* BYTE  *buf;   6(sp).l     ; buffer to put data
*-
IF ROM_TOS306
_initdevpar:move.w    4(sp),d0                  ; d0 = physical unit #
            andi.b    #7,d0                     ; mask off flags (if any)
            lsl.w     #4,d0                     ; shift unit # to place
            move.b    d0,(ide_headn).l          ; set drive #
            move.w    6(sp),d0
            subq.b    #1,d0
            or.b      d0,(ide_headn).l          ; # of heads - 1
            move.b    9(sp),(ide_seccn).l       ; # of sectors per track
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$91,(ide_comst).l        ; set command code INITIALIZE DEVICE PARAMETERS
            bra       w4int
ENDIF

*+
* identify() - allows the Host to receive parameter information from
*          the drive.
*
* identify(pdev, buf)
* WORD  pdev;   4(sp).w     ; physical unit #
* BYTE  *buf;   6(sp).l     ; buffer to put data
*-
_identify:  move.w    4(sp),d0                  ; d0 = physical unit #
            andi.b    #7,d0                     ; mask off flags (if any)
            lsl.b     #4,d0                     ; shift unit # to place
            move.b    d0,(ide_headn).l          ; set drive #
            movea.l   6(sp),a0                  ; a0 -> buffer
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$ec,(ide_comst).l        ; set command code IDENTIFY
            bsr       w4int                     ; wait for interrupt
            tst.w     d0                        ; successful?
            bmi.s     id0                       ; if timed-out, return
            btst      #3,d0                     ; DRQ?
            beq.s     id0                       ; if not, return with error
            bsr.s     readbuf                   ; read data
            moveq     #0,d0                     ; everything's fine
id0:        rts

*+
* readbuf() - reads 512 bytes (128 longs) of data into sector buffer.
*
* Passed:
*   a0.l = buffer to store data read from sector buffer
*-
readbuf:
IF ROM_TOS306
            moveq     #$7f,d0                   ; d0 = (# of words of data to read) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
rb0:        move.l    (a1),(a0)+                ; read data from bus
            dbra      d0,rb0                    ; repeat until all done
ELSE
            moveq     #$1f,d0                   ; d0 = (# of words of data to read / 8) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
rb0:        move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            dbra      d0,rb0                    ; repeat until all done
ENDIF
            rts

*+
* wrtbuf() - writes 512 bytes (128 longs) of data to sector buffer.
*
* Passed:
*   a0.l = buffer with data to write to sector buffer
*-
wrtbuf:
IF ROM_TOS306
            moveq     #$7f,d0                   ; d0 = (# of words of data to read) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
wb0:        move.l    (a0)+,(a1)                ; write data to bus
            dbra      d0,wb0                    ; repeat until all done
ELSE
            moveq     #$1f,d0                   ; d0 = (# of words of data to read / 8) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
wb0:        move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            dbra      d0,wb0                    ; repeat until all done
ENDIF
            rts

*+
* _iderdy() - test if the IDE drive is ready
*
* Passed:
*   d0.b = IDE drive unit #
*
* Returns: 0 - if drive is NOT ready
*      1 - if drive is ready
*-
_iderdy:    andi.b    #7,d0                     ; mask off flags (if any)
            lsl.b     #4,d0                     ; shift unit # to place
            move.b    d0,(ide_headn).l          ; set drive #
IF ROM_TOS306
            move.l    #5*200,d0                 ; set up timer IDERDY
            add.l     (_hz_200).w,d0
ir0:        btst      #6,(ide_stat2).l
            bne.s     ir1
ELSE
            move.b    #$50,d1                   ; ready status
            move.l    #5*200,d0                 ; set up timer IDERDY
            add.l     (_hz_200).w,d0
ir0:        cmp.b     (ide_stat2).l,d1          ; is drive ready and not busy?
            beq.s     ir1                       ; if so, return with drive ready
ENDIF
            cmp.l     (_hz_200).w,d0            ; time-out yet?
            bcc.s     ir0                       ; if not, wait longer
            moveq     #0,d0                     ; else return drive NOT ready
            rts

ir1:        moveq     #1,d0                     ; else, drive is ready
            rts

*+
* gcparm() - get current drive parameters
*
* gcparm(buf)
* char  *buf;   $4(sp).l    /* -> data returned by identify() */
*
* Returns:
*   d0.w = # of default cylinders
*   d1.w = # of default heads
*   d2.w = # of default sectors per track
*-
IF !ROM_TOS306
_gcparm:    movea.l   4(sp),a0                  ; a0 -> data buffer
            adda.w    #$50,a0                   ; a0 -> where Conner model number is (CONMDL)
            move.l    a0,-(sp)
            pea       (cp2024).l
            move.w    #6,-(sp)
            bsr.s     strcmp                    ; compare model# with "CP2024"
            adda.w    #$a,sp                    ; clean up stack
            tst.w     d0                        ; is unit the CP2024 (Kato 20Mb)?
            bne.s     gcp0                      ; if not, handle the normal way else return default values of CP2024
            move.w    #$267,d0                  ; d0.w = # of cylinders (CP20NCYL)
            move.w    #4,d1                     ; d1.w = # of heads (CP20NHEAD)
            move.w    #$11,d2                   ; d2.w = # of spt (CP20NSPT)
            bra.s     gcpend
gcp0:       movea.l   4(sp),a0
            move.w    2(a0),d0                  ; d0.w = # of cylinders
            move.w    6(a0),d1                  ; d1.w = # of heads
            move.w    $c(a0),d2                 ; d2.w = # of sectors per track
gcpend:     rts

conner:     DC.B      'Conner',0
            DC.B      $00
cp2024:     DC.B      'CP2024',0
            DC.B      $00

*+
* strcmp() - compare two strings
*
* Passed:
*   4(sp).w  = n (# of bytes to compare)
*   6(sp).l  = address of first string
*   10(sp).l = address of second string
*
* Returns:
*   d0.w = 0    if first n bytes of the 2 strings are the same
*        = non-0    otherwise
*-
strcmp:     movem.l   d1/a0-a1,-(sp)            ; save registers d1, a0 and a1
            move.w    $10(sp),d1                ; d1 = byte count
            subq.w    #1,d1                     ; dbra likes one less
            movea.l   $12(sp),a0                ; a0 -> string 1
            movea.l   $16(sp),a1                ; a1 -> string 2
            moveq     #1,d0                     ; assume strings are not the same
str0:       cmpm.b    (a0)+,(a1)+               ; characters the same?
            bne.s     str1                      ; if not, return
            dbra      d1,str0                   ; else compare next character
            moveq     #0,d0                     ; the strings are the same
str1:       movem.l   (sp)+,d1/a0-a1            ; restore registers d1, a0 and a1
            rts
ENDIF

**************************************************************************
*                                                                        *
**************************************************************************
IF ROM_TOS306
readCurrentTime:
            bsr       checkRTC
            bcs.s     readCurrentTime2
            move.b    #$d,(rtcadd).w
            move.b    (rtcdat).w,d0
            btst      #7,d0
            bne.s     readCurrentTime2
            move.l    #$12c80000,-(sp)          ; default date: July 4th, 1989, 12:00am
            bsr       writeRTCTime
            addq.w    #4,sp
readCurrentTime:
            bsr.s     readRTCTime
            cmp.l     #-1,d0
            beq.s     readCurrentTime2
            moveq     #0,d0
readCurrentTime2:
            rts

readRTCTime:bsr       checkRTC
            bcs.s     readCurrentTime2
            move.b    #$d,(rtcadd).w
            btst      #7,(rtcdat).w
            beq       readRTCTimeErr
            move      sr,d2
            move.w    d2,d0
            or.w      #$700,d0
readRTCTime3:move.b    #$a,(rtcadd).w
            btst      #7,(rtcdat).w
            bne.s     readRTCTime3
            moveq     #0,d0
            move.l    d0,d1
            move.b    #0,(rtcadd).w
            move.b    (rtcdat).w,d0
            asr.w     #1,d0
            move.b    #2,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{21:6}
            move.b    #4,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{16:5}
            move.b    #7,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{11:5}
            move.b    #8,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{7:4}
            move.b    #9,(rtcadd).w
            move.b    (rtcdat).w,d1
            sub.b     #12,d1
            bfins     d1,d0{0:7}
            move      d2,sr
            move      sr,d2
            ori       #$700,sr
            move.w    d0,(systemDate).l
            swap      d0
            move.w    d0,(systemTime).l
            swap      d0
            move      d2,sr
            rts

readRTCTimeErr:
            moveq     #-1,d0
            rts

writeRTCTime:bsr       checkRTC
            bcs       readCurrentTime2
            move.l    4(sp),d0
            move.b    #$b,(rtcadd).w
            move.b    #$80,(rtcdat).w
            move.b    #$a,(rtcadd).w
            move.b    #$2a,(rtcdat).w
            move.b    #$b,(rtcadd).w
            move.b    #$8e,(rtcdat).w
            move.b    #0,(rtcadd).w
            bfextu    d0{27:5},d1
            add.b     d1,d1
            move.b    d1,(rtcdat).w
            move.b    #2,(rtcadd).w
            bfextu    d0{21:6},d1
            move.b    d1,(rtcdat).w
            move.b    #4,(rtcadd).w
            bfextu    d0{16:5},d1
            move.b    d1,(rtcdat).w
            move.b    #7,(rtcadd).w
            bfextu    d0{11:5},d1
            move.b    d1,(rtcdat).w
            move.b    #8,(rtcadd).w
            bfextu    d0{7:4},d1
            move.b    d1,(rtcdat).w
            move.b    #9,(rtcadd).w
            bfextu    d0{0:7},d1
            add.b     #$c,d1
            move.b    d1,(rtcdat).w
            move.b    #$b,(rtcadd).w
            move.b    #$e,(rtcdat).w
            rts

checkRTC:   movea.l   sp,a0
            movea.l   (busexception).w,a1
            move.l    #checkRTC2,(busexception).w
            move.b    #0,(rtcadd).w
            move.b    (rtcdat).w,d0
            move.l    a1,(busexception).w
            andi      #$fe,ccr
            rts
checkRTC2:  movea.l   a0,sp
            move.l    a1,(busexception).w
            ori       #1,ccr
            rts



_NVMaccess: moveq     #-5,d0
            move.w    4(sp),d1                  ; op = 0: read NVRAM
            beq.s     _NVMread
            cmp.w     #2,d1                     ; op = 2: initialize NVRAM and update checksum
            beq.s     _NVMinit
            bhi.s     _NVMaccessr               ; (unknown op)

* write into NVRAM and update the checksum
            bsr.s     _NVMvalid                 ; op = 1: write NVRAM
            tst.w     d0
            bne.s     _NVMaccessr
            movea.l   $a(sp),a0                 ; buffer
            bra.s     _NVMwrt2
_NVMwrtl:   move.b    d1,(a1)                   ; select register
            move.b    (a0)+,(a2)                ; write value
            addq.w    #1,d1                     ; next reg
_NVMwrt2:   dbra      d2,_NVMwrtl
            bsr       _NVMchksum                ; calculate the checksum
            move.b    #$3f,(a1)
            move.b    d0,(a2)                   ; checksum into reg 63
            not.b     d0
            move.b    #$3e,(a1)
            move.b    d0,(a2)                   ; inverted checksum into reg 62
            moveq     #0,d0                     ; (ok)
_NVMaccessr:rts

* read from NVRAM and also validate the checksum
_NVMread:   bsr.s     _NVMvalid
            cmp.w     #-5,d0                    ; argument error?
            beq.s     _NVMaccessr               ; (fail)
            movea.l   $a(sp),a0                 ; buffer
            bra.s     _NVMread2
_NVMreadl:  move.b    d1,(a1)                   ; select register
            move.b    (a2),(a0)+                ; read value
            addq.w    #1,d1                     ; next reg
_NVMread2:  dbra      d2,_NVMreadl
            rts

* erase NVRAM and update the checksum
_NVMinit:   lea       (rtcadd).w,a1
            lea       (rtcdat).w,a2
            moveq     #0,d0                     ; fill value = 0
            moveq     #14,d1
            moveq     #50-1,d2                  ; fill 48 bytes plus checksum
_NVMinitl:  move.b    d1,(a1)                   ; select register
            move.b    d0,(a2)                   ; clear
            addq.w    #1,d1                     ; next reg
            dbra      d2,_NVMinitl
            move.b    #$3e,(a1)                 ; inverted checksum of 0 is 0xFF
            move.b    #$ff,(a2)
            rts

* validate checksum in NVRAM and function parameters
_NVMvalid:  bsr.s     _NVMchksum                ; calculate the checksum
            move.b    d0,d1
            moveq     #-12,d0                   ; default: checksum error
            move.b    #$3f,(a1)                 ; register 63
            cmp.b     (a2),d1                   ; contains the checksum
            bne.s     _NVMvalidr                ; (error)
            not.b     d1
            move.b    #$3e,(a1)                 ; register 62
            cmp.b     (a2),d1                   ; contains the inverted checksum
            bne.s     _NVMvalidr                ; (error)
            moveq     #-5,d0                    ; default: argument error
            move.w    $a(sp),d1                 ; count
            cmp.w     #48,d1                    ; more than 48 bytes?
            bcc.s     _NVMvalidr                ; (error)
            move.w    $c(sp),d2                 ; start
            bmi.s     _NVMvalidr                ; negative => error
            add.w     d1,d2                     ; start + count
            cmp.w     #48,d2                    ; >48
            bhi.s     _NVMvalidr                ; (error)
            moveq     #0,d0                     ; no error
_NVMvalidr: move.w    $c(sp),d2                 ; d2: count
            move.w    $a(sp),d1
            add.w     #14,d1                    ; d1: first register
            rts

_NVMchksum: lea       (rtcadd).w,a1
            lea       (rtcdat).w,a2
            moveq     #0,d0                     ; checksum = 0
            moveq     #14,d1                    ; NVRAM starts at register 14
            moveq     #48-1,d2                  ; 48 bytes
_NVMcheckl: move.b    d1,(a1)                   ; select register
            add.b     (a2),d0                   ; sum values to checksum
            addq.w    #1,d1
            dbra      d2,_NVMcheckl
            rts

ELSE

readCurrentTime:bsr.s checkRTC
            bcs.s     readCurrentTime2
            bsr.s     readRTCTime
            cmp.l     #-1,d0
            beq.s     readCurrentTime2
            moveq     #0,d0
readCurrentTime2:rts

IF ROM_STBOOK
checkRTC:
            movea.w   #rtc_sec,a0
            move.b    #$00,$1d(a0)              ; Clear RTC Test register
            move.b    #$0c,$1f(a0)              ; Reset 1 Hz & 16 Hz alarm pulse
            move.b    #$08,$1b(a0)              ; Clock Start, Alarm off, Bank 0
            moveq     #0,d0                     ; The RTC is always available
            rts
ELSE
checkRTC:   movea.w   #rtc_sec,a0
            move.l    (busexception).w,d2
            movea.l   sp,a2
            move.l    #checkRTC2,(busexception).w
            bset      #0,$1b(a0)
            move.l    d2,(busexception).w
            move.w    #$a05,d0
            movep.w   d0,5(a0)
            movep.w   5(a0),d1
            and.w     #$f0f,d1
            cmp.w     d0,d1
            bne.s     checkRTC3
            move.b    #1,1(a0)
            bclr      #0,$1b(a0)
            move.b    #0,$1d(a0)
            rts
checkRTC2:  movea.l   a2,sp
            move.l    d2,(busexception).w
checkRTC3:  ori       #1,ccr                    ; RTC not available
            rts
ENDIF

readRTCTime:bsr.s     checkRTC
            bcs       readRTCTimeErr
            lea       (rtcbufa).w,a1
            lea       (rtcbufb).w,a2
            bsr       readRTCTimeMask
readRTCTime2:exg      a1,a2
            bsr       readRTCTimeMask
            moveq     #$c,d0
readRTCTime3:move.b   (a1,d0.w),d1
            cmp.b     (a2,d0.w),d1
            bne.s     readRTCTime2
            dbra      d0,readRTCTime3
            moveq     #0,d0
            move.b    $b(a1),d0
            mulu.w    #10,d0
            add.b     $c(a1),d0
            asr.w     #1,d0
            move.w    d0,d1
            moveq     #0,d0
            move.b    9(a1),d0
            mulu.w    #10,d0
            add.b     $a(a1),d0
            asl.w     #5,d0
            add.w     d0,d1
            moveq     #0,d0
            move.b    7(a1),d0
            mulu.w    #10,d0
            add.b     8(a1),d0
            asl.w     #8,d0
            asl.w     #3,d0
            add.w     d0,d1
            swap      d1
            moveq     #0,d0
            move.b    4(a1),d0
            mulu.w    #10,d0
            add.b     5(a1),d0
            move.w    d0,d1
            moveq     #0,d0
            move.b    2(a1),d0
            mulu.w    #10,d0
            add.b     3(a1),d0
            asl.w     #5,d0
            add.w     d0,d1
            moveq     #0,d0
            move.b    (a1),d0
            mulu.w    #10,d0
            add.b     1(a1),d0
            asl.w     #8,d0
            asl.w     #1,d0
            add.w     d0,d1
            move      sr,d2
            ori       #$700,sr
            move.w    d1,(systemDate).l
            swap      d1
            move.w    d1,(systemTime).l
            move      d2,sr
            move.l    d1,d0
            rts

readRTCTimeMask:moveq #$c,d0
            moveq     #1,d1
readRTCTimeMask2:move.b (a0,d1.w),d2
            and.b     #$f,d2
            move.b    d2,(a1,d0.w)
            addq.w    #2,d1
            dbra      d0,readRTCTimeMask2
            rts

readRTCTimeErr:moveq  #-1,d0
            rts

writeRTCTime:bsr      checkRTC
            bcs       no_RTC_found
            lea       (rtcbufa).w,a1
            movea.w   4(sp),a2
            bsr       calcRTCWeekday
            move.b    d0,6(a1)
            move.w    6(sp),d1
            move.w    d1,d0
            and.l     #$1f,d0
            add.w     d0,d0
            divu.w    #10,d0
            move.b    d0,$b(a1)
            swap      d0
            move.b    d0,$c(a1)
            move.w    d1,d0
            lsr.w     #5,d0
            and.l     #$3f,d0
            divu.w    #10,d0
            move.b    d0,9(a1)
            swap      d0
            move.b    d0,$a(a1)
            lsr.w     #8,d1
            lsr.w     #3,d1
            ext.l     d1
            divu.w    #10,d1
            move.b    d1,7(a1)
            swap      d1
            move.b    d1,8(a1)
            move.w    4(sp),d1
            move.w    d1,d0
            and.l     #$1f,d0
            divu.w    #10,d0
            move.b    d0,4(a1)
            swap      d0
            move.b    d0,5(a1)
            move.w    d1,d0
            lsr.w     #5,d0
            and.l     #$f,d0
            divu.w    #10,d0
            move.b    d0,2(a1)
            swap      d0
            move.b    d0,3(a1)
            lsr.w     #1,d1
            lsr.w     #8,d1
            ext.l     d1
            move.l    d1,d2
            divu.w    #10,d1
            move.b    d1,(a1)
            swap      d1
            move.b    d1,1(a1)
            divu.w    #4,d2
            swap      d2
            move.b    #$e,$1f(a0)
            bset      #0,$1b(a0)
            move.b    #1,$15(a0)
            move.b    d2,$17(a0)
            bclr      #0,$1b(a0)
            bclr      #3,$1b(a0)
            moveq     #$c,d0
            moveq     #1,d1
writeRTCTime2:move.b  (a1,d0.w),(a0,d1.w)
            addq.w    #2,d1
            dbra      d0,writeRTCTime2
            bset      #3,$1b(a0)
            moveq     #0,d0
            rts

no_RTC_found:moveq    #-1,d0
            rts

calcRTCWeekday:moveq  #2,d2
            move.w    a2,d0
            lsr.w     #8,d0
            lsr.w     #1,d0
            add.w     d0,d2
            move.w    d0,d1
            lsr.w     #2,d1
            add.w     d1,d2
            move.w    a2,d1
            lsr.w     #5,d1
            and.w     #$f,d1
            and.w     #3,d0
            bne.s     calcRTCWeekday2
            cmp.w     #2,d1
            bhi.s     calcRTCWeekday2
            subq.w    #1,d2
calcRTCWeekday2:subq.w #1,d1
            add.w     d1,d1
            add.w     daysOffsetToMonths(pc,d1.w),d2
            move.w    a2,d1
            and.w     #$1f,d1
            add.w     d1,d2
            divu.w    #7,d2
            swap      d2
            moveq     #0,d0
            move.w    d2,d0
            rts

daysOffsetToMonths:DC.W $0000,$001f,$003b,$005a
            DC.W      $0078,$0097,$00b5,$00d4
            DC.W      $00f3,$0111,$0130,$014e

_Waketime:  move.l    4(sp),d0
            beq       clearRTCWaketime
            cmp.l     #-1,d0
            beq       readRTCWaketime
            cmp.l     #1,d0
            beq       enableRTCWaketime
            bsr       readRTCTime
            cmp.l     #-1,d0
            beq       enableRTCWaketimeDone
            move.l    4(sp),d1
            and.b     #$e0,d0
            and.b     #$e0,d1
            cmp.l     d1,d0                     ; is the new wakeup time already in the past?
            bcc.s     _Waketime_ret1
            move.l    (waketimeAlarm).l,d2
            beq.s     _Waketime2
            cmp.l     d2,d0
            bhi.s     _Waketime2
            cmp.l     d2,d1
            bcc.s     _Waketime_ret2
_Waketime2: movea.l   d0,a0
            move.l    d1,d2
            and.l     #$1fffff,d2
            and.l     #$1fffff,d0
            cmp.l     d2,d0                     ; new wakeup time is _now_?
            beq.s     _Waketime3
            move.l    d1,(waketimeAlarm).l
            moveq     #0,d0
            rts

_Waketime3: and.l     #$1f0000,d2
            cmp.l     #$10000,d2
            beq.s     _Waketime_ret5
            and.l     #$ffe0ffff,d1
            or.l      #$10000,d1
            move.l    d1,(waketimeAlarm).l
            moveq     #4,d0
            rts

_Waketime_ret5:move.l a0,d0
            add.l     #$10000,d0
            move.l    d0,(waketimeAlarm).l
            moveq     #5,d0
            rts

_Waketime_ret1:moveq  #1,d0
            rts

_Waketime_ret2:moveq  #2,d0
            rts

readRTCWaketime:move.l (waketimeAlarm).l,d0
            rts

clearRTCWaketime:bsr  checkRTC
            bcs       no_RTC_found
            bclr      #2,$1b(a0)                ; RTC Alarm off
            move.b    #$d,$1f(a0)               ; Reset 1Hz, 16 Hz and Alarm
            clr.l     (waketimeAlarm).l
            moveq     #3,d0
            rts

enableRTCWaketime:bsr readRTCTime
            cmp.l     #-1,d0
            beq       enableRTCWaketimeDone
            move.l    (waketimeAlarm).l,d1
            beq       enableRTCWaketimeDone
            and.b     #$e0,d1
            move.l    d1,d2
            and.b     #$e0,d0
            cmp.l     d1,d0                     ; is the new wakeup time already in the past?
            bhi.s     enableRTCWaketimeDone
            bne.s     enableRTCWaketime2
            and.b     #$1f,d2
            cmp.b     #7,d2
            bcs.s     enableRTCWaketime3
enableRTCWaketime2:movea.w #rtc_sec,a0
            bset      #0,$1b(a0)                ; RTC select Bank 1
            move.l    (waketimeAlarm).l,d1
            move.w    d1,d0
            lsr.w     #5,d0                     ; Minutes
            and.l     #$3f,d0
            divu.w    #10,d0
            move.b    d0,7(a0)                  ; RTC 1-minute alarm register
            swap      d0
            move.b    d0,5(a0)                  ; RTC 10-minute alarm register
            move.w    d1,d0
            lsr.w     #8,d0
            lsr.w     #3,d0                     ; Hours
            ext.l     d0
            divu.w    #10,d0
            move.b    d0,$b(a0)                 ; RTC 1-hour alarm register
            swap      d0
            move.b    d0,9(a0)                  ; RTC 10-hour alarm register
            move.l    d1,d0
            swap      d0
            and.w     #$1f,d0                   ; Days
            ext.l     d0
            divu.w    #10,d0
            move.b    d0,$11(a0)                ; RTC 1-hour alarm register
            swap      d0
            move.b    d0,$f(a0)                 ; RTC 10-day alarm register
            movea.w   (waketimeAlarm).l,a2
            bsr       calcRTCWeekday
            move.b    d0,$d(a0)                 ; RTC day-of-the-week alarm register
            bset      #2,$1b(a0)                ; RTC Alarm on
            bclr      #0,$1b(a0)                ; RTC select Bank 0
enableRTCWaketimeDone:moveq #0,d0
            rts

enableRTCWaketime3:clr.l (waketimeAlarm).l
            moveq     #-1,d0
            rts
ENDIF

**************************************************************************
*                                                                        *
*                cp/m-68k atari rbp bios                                 *
*                basic input/output subsystem                            *
*                copyright 1984, atari corporation                       *
*                all rights reserved.                                    *
*                atari confidential                                      *
*                                                                        *
**************************************************************************
**************************************************************************
*                                                                        *
*        convert ikbd real-time clock format to jdos format              *
*                                                                        *
**************************************************************************
jdostime:   lea       (clkrec).w,a0
            bsr       bcdbin
            subi.b    #80,d0                    ; adjust so that 1980 => 0 for time base
            move.b    d0,d2
            asl.l     #4,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #5,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #5,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #6,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #5,d2

            bsr       bcdbin

            lsr.b     #1,d0                     ; adjust to provide two second increments...
            add.b     d0,d2                     ; ...another @!#%@#$% kludge, thank you !
            move.l    d2,(datetime).w
            move.b    #0,(newtod).w             ; clear handshaking flag
            rts


readIKBDTime:move.b   #$ff,(newtod).w
            move.b    #$1c,d1
            bsr       ikbdput
            movea.l   (_hz_200).w,a0
            adda.w    #200,a0
            moveq     #0,d0
readIKBDTime2:cmpa.l  (_hz_200).w,a0
            bcs.s     readIKBDTimex
            tst.b     (newtod).w
            bne.s     readIKBDTime2
            move.l    (datetime).w,d0
readIKBDTimex:rts

writeIKBDTime:move.l  4(sp),(newtime).w
            lea       (kmbuf).l,a0
            move.l    (newtime).w,d2
            move.b    d2,d0
            andi.b    #$1f,d0
            asl.b     #1,d0
            bsr.s     binbcd
            lsr.l     #5,d2
            move.b    d2,d0
            andi.b    #$3f,d0
            bsr.s     binbcd
            lsr.l     #6,d2
            move.b    d2,d0
            andi.b    #$1f,d0
            bsr.s     binbcd
            lsr.l     #5,d2
            move.b    d2,d0
            andi.b    #$1f,d0
            bsr.s     binbcd
            lsr.l     #5,d2
            move.b    d2,d0
            andi.b    #$f,d0
            bsr.s     binbcd
            lsr.l     #4,d2
            move.b    d2,d0
            andi.b    #$7f,d0
            bsr.s     binbcd
            addi.b    #$80,(a0)
            move.b    #$1b,d1
            bsr       ikbdput
            moveq     #5,d3
            lea       (oclkrec).l,a2
            bsr       ikbdstr
            move.b    #$1c,d1
            bsr       ikbdput
            rts

**************************************************************************
*                                                                        *
*                  convert a byte from binary to bcd format              *
*                                                                        *
*       entry:     d0.l  - value                                         *
*                                                                        *
**************************************************************************
binbcd:     moveq     #0,d1
            move.b    d0,d1
            divs.w    #10,d1
            asl.w     #4,d1
            move.w    d1,d0
            swap      d1
            add.w     d1,d0
            move.b    d0,-(a0)                  ; transfer to output clock buffer
            rts

**************************************************************************
*                                                                        *
*                  convert a byte from bcd format to binary              *
*                                                                        *
*       entry:     a0.l  - pointer to byte                               *
*                                                                        *
**************************************************************************
bcdbin:     move.b    (a0)+,d0                  ; get bcd byte
            move.b    d0,d1
            and.w     #$0f,d0                   ; dump high nibble
            and.w     #$f0,d1                   ; isolate high nibble
            asr.w     #4,d1                     ; dump low nibble
            mulu.w    #10,d1                    ; high nibble * 10
            add.w     d1,d0                     ; + low nibble
            rts

**************************************************************************
*                                                                        *
*                  midi output status                                    *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       word       midiost()                                             *
*                                                                        *
*       returns true/okay to send = -1,  false/not ready = 0             *
*                                                                        *
**************************************************************************
midiost:    moveq     #-1,d0                    ; pre-set to true
            move.b    (midictl).w,d2            ; grab midi status
            btst      #1,d2
            bne.s     midiox                    ; status okay to send
            moveq     #0,d0                     ; status not okay
midiox:     rts

**************************************************************************
*                                                                        *
*                  write char to midi port                               *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void       midiwc(chr)                                           *
*       word       chr                                                   *
*                                                                        *
**************************************************************************
midiwc:     move.w    6(sp),d1
midiput:    lea       (midictl).w,a1            ; point to midi register base
midput1:    move.b    (a1),d2                   ; grab midi status
            btst      #1,d2
            beq.s     midput1
            move.b    d1,2(a1)
            rts                                 ; done for now

**************************************************************************
*                                                                        *
*                  put string to midi routine                            *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void       midiws(size,ptr)                                      *
*       word       size                                                  *
*       long       ptr                                                   *
*                                                                        *
**************************************************************************
midiws:     moveq     #0,d3
            move.w    4(sp),d3                  ; get size of string buffer - 1
            movea.l   6(sp),a2                  ; get string address
midp1:      move.b    (a2)+,d1
            bsr.s     midiput
            dbra      d3,midp1
            rts

**************************************************************************
*                                                                        *
*                  get midi receiver buffer status                       *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       word       midistat()                                            *
*                                                                        *
*       -1 signifies true/okay  0 - signifies false/no characters        *
*                                                                        *
**************************************************************************
midstat:    lea       (mbufrec).w,a0            ; point to midi i/o bufrec
            lea       (midictl).w,a1            ; point to midi register base
            moveq     #-1,d0                    ; set result to true
            lea       6(a0),a2
            lea       8(a0),a3
            cmpm.w    (a3)+,(a2)+               ; atomic buffer empty test
            bne.s     midist1                   ; branch of not, assume d0 is "clr.w"'ed
            moveq     #0,d0                     ; set result to false
midist1:    rts

**************************************************************************
*                                                                        *
*                  getchar routine for midi port                         *
*                                                                        *
*       this routine transfers characters from a input queue that is     *
*       filled by an automatic interrupt routine.  the interrupt         *
*       routine handles the actual transfer of the character from the    *
*       i/o port.                                                        *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       long       midiin()                                              *
*                                                                        *
*       long data returned represents upper three bytes of time stramp   *
*       and least significant byte as data                               *
*                                                                        *
**************************************************************************
* assume that a0/a1 are inited by the midstat call for the rest of
* this routine.

midin:      bsr.s     midstat                   ; see if key pressed
            tst.w     d0
            beq.s     midin                     ; wait until byte comes in
            move      sr,-(sp)                  ; protect this upcoming test
            ori       #$700,sr
            move.w    6(a0),d1                  ; get current head pointer offset from buffer
            cmp.w     8(a0),d1                  ; head=tail?
            beq.s     mwi2                      ; yes

* check for wrap of pointer

            addq.w    #1,d1                     ; i=j+1
            cmp.w     4(a0),d1                  ; ? i>= current bufsiz?
            bcs.s     mwi1                      ; no...
            moveq     #0,d1                     ; wrap pointer
mwi1:       movea.l   (a0),a1                   ; get base address of buffer
            and.l     #$ffff,d1
IF !ROM_TOS306
            moveq     #0,d0
ENDIF
            move.b    (a1,d1.l),d0              ; get character
            move.w    d1,6(a0)                  ; store new head pointer to buffer record
mwi2:       move      (sp)+,sr
            rts

**************************************************************************
*                                                                        *
*                  parallel i/o port service routine                     *
*                                                                        *
*       this set of routines is for general parallel i/o                 *
*                                                                        *
*       entry to listout                                                 *
*                                                                        *
*       entry to listin                                                  *
*                                                                        *
*       exit from listin                                                 *
*                                                                        *
**************************************************************************
_lstout:    btst      #4,(pconfig).w
            bne       _auxout

            move.l    (_hz_200).w,d2            ; d2 = hz_200 - prt_to
            sub.l     (prt_to).w,d2             ; (compute time since last timeout)
            cmpi.l    #5*200,d2                 ; do "fake" timeout if we timed out within
            bcs.s     lperr                     ; the last five seconds
            move.l    (_hz_200).w,d2            ; d2 = starting time for this char
pt0:        bsr       _lstostat                 ; go get parallel port status
            tst.w     d0                        ; ...and check for high (busy)
            bne.s     pt1                       ; port is ready -- print the char

            move.l    (_hz_200).w,d3            ; d3 = hz_200 - d2
            sub.l     d2,d3
            cmpi.l    #$1770,d3                 ; check for 30 second delta
            blt.s     pt0                       ; continue of no timeout

lperr:      moveq     #0,d0                     ; return value of 0 indicates timeout
            move.l    (_hz_200).w,(prt_to).w    ; record time of last timeout
            rts

pt1:        move      sr,d3                     ; save status register
            ori       #$700,sr                  ; protect upcoming switching of the port setting
            moveq     #7,d1                     ; get current io enable register contents
            bsr       gientry
            ori.b     #$80,d0                   ; set port b for output
            move.b    #$87,d1                   ; set to write to io enable
            bsr       gientry
            move      d3,sr                     ; restore status register

            move.w    6(sp),d0                  ; retrieve byte to be sent and...
            move.b    #$8f,d1                   ; write out byte to parallel port
            bsr       gientry

            move      sr,-(sp)
            ori       #$700,sr
            bsr.s     strobeon
            bsr.s     strobeon
            bsr.s     strobeoff
            move      (sp)+,sr
            moveq     #-1,d0                    ; set d0=-1 for good transfer status
            rts


strobeoff:  moveq     #$20,d2                   ; set strobe off
            bra       onbit                     ; go set it!!
strobeon:   move.b    #$df,d2                   ; set strobe on
            bra       offbit                    ; set strobe now...


_lstin:     moveq     #7,d1                     ; get current io enable register contents
            bsr       gientry
            andi.b    #$7f,d0                   ; set port b for input
            move.b    #$87,d1                   ; set to write to io enable
            bsr       gientry

            bsr.s     strobeoff                 ; busy off!
lstibusy:   bsr.s     _lstostat                 ; go get parallel port status
            tst.w     d0                        ; ...and check for high (busy)
            bne.s     lstibusy                  ; loop till high...
            bsr.s     strobeon
            moveq     #$f,d1                    ; init to use gientry routine to read
            bra       gientry                   ; now get the byte from the parallel port
* d0.l contains the byte of data from the port
* the 'bra' is implied rts from this routine

**************************************************************************
*                                                                        *
*                  parallel port status routine                          *
*                                                                        *
**************************************************************************
_lstostat:  lea       (gpip).w,a0               ; point to mfp register base
            moveq     #-1,d0                    ; pre-init to true (parallel port ready)
            btst      #0,(a0)
            beq.s     lst1
            moveq     #0,d0                     ; parallel port busy
lst1:       rts


**************************************************************************
*                                                                        *
*                  auxillary port input status routine                   *
*                                                                        *
**************************************************************************
auxistat:   lea       (rbufrec).w,a0            ; point to rs-232 buffer record
_auxistat:  moveq     #-1,d0                    ; set result to true
            lea       6(a0),a1                  ; head
            lea       8(a0),a0                  ; tail
            cmpm.w    (a0)+,(a1)+               ; atomic buffer empty test
            bne.s     auxist1
            moveq     #0,d0                     ; set result to false
auxist1:    rts

**************************************************************************
*                                                                        *
*                  auxillary input routine                               *
*                                                                        *
**************************************************************************
auxin:      lea       (rbufrec).w,a0            ; point to rs-232 buffer record
            lea       (gpip).w,a2
_auxin:     bsr       rs232get
            move.w    d0,-(sp)
            tst.b     $20(a0)                   ; flow control active?
            beq.s     auxinrts                  ; (no)
            move.w    8(a0),d0                  ; tail
            sub.w     6(a0),d0                  ; - head
            bpl.s     auxin2                    ; underflow?
            add.w     4(a0),d0                  ; + size
auxin2:     cmp.w     $a(a0),d0
            bgt.s     auxinrts
            tst.b     $1e(a0)                   ; high-water flag already set?
            beq.s     auxinrts                  ; no...exit...
            bsr.s     auxinclrhw
auxinrts:   move.w    (sp)+,d0
            rts

auxinclrhw: clr.b     $1e(a0)                   ; clear high-water flag
            btst      #0,$20(a0)                ; is the rs232 mode xon/xoff?
            bne.s     auxinclrhw2               ; (yes)
            bra       rtson
auxinclrhw2:move.b    #$11,$21(a0)              ; "xon"
            bra.s     _auxoutwr

**************************************************************************
*                                                                        *
*                  auxillary port output status routine                  *
*                                                                        *
**************************************************************************
_auxostat:  lea       (rbufrec+14).w,a0         ; point to rs-232 buffer record
__auxostat: move.w    8(a0),d1                  ; tail
            bsr       wrapin
            moveq     #-1,d0                    ; set result to true
            cmp.w     6(a0),d1                  ; head
            bne.s     _auxostatrts
            moveq     #0,d0                     ; set result to false
_auxostatrts:rts

**************************************************************************
*                                                                        *
*                  auxillary output routine                              *
*                                                                        *
**************************************************************************
_auxout:    move.w    6(sp),d0                  ; get data
            lea       (rbufrec+14).w,a0
            bsr       rs232put                  ; exit via rs-232 output routine
            lea       (rbufrec).w,a0
            lea       (gpip).w,a2

_auxoutwr:  tst.b     $2c(a2)                   ; transmitter status
            bpl.s     _auxoutrts                ; not ready
            move      sr,-(sp)
            ori       #$700,sr
            bsr       wr_rs232
            move      (sp)+,sr
_auxoutrts: rts

**************************************************************************
*                                                                        *
*               ikbd output status                                       *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       word    ikbdost()                                                *
*                                                                        *
*       returns true/okay to send = 1,  false/not ready = 0              *
*                                                                        *
**************************************************************************
ikbdost:    moveq     #-1,d0                    ; pre-set to true
            move.b    (keyctl).w,d2             ; grab ikbd status
            btst      #1,d2
            bne.s     ikbdox                    ; status okay to send
            moveq     #0,d0                     ; status not okay
ikbdox:     rts

**************************************************************************
*                                                                        *
*               write char to ikbd port                                  *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void    ikbdwc(chr)                                              *
*       word    chr                                                      *
*                                                                        *
**************************************************************************
ikbdwc:     move.w    6(sp),d1
ikbdput:    lea       (keyctl).w,a1             ; point to ikbd register base
ikput1:     move.b    (a1),d2                   ; grab keyboard status
            btst      #1,d2
            beq.s     ikput1
IF ROM_TOS306
            move.w    #$400,d0
            bsr       mfpdelay
ELSE
            lea       (tcdr).w,a0
            move.w    #$bf,d0
ikput2:     move.b    (a0),d2
ikput3:     cmp.b     (a0),d2
            beq.s     ikput3
            dbra      d0,ikput2
ENDIF
            move.b    d1,2(a1)                  ; write char to the ikbd port
            rts                                 ; done for now

**************************************************************************
*                                                                        *
*               put string to to ikbd routine                            *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void    ikbdws(size,ptr)                                         *
*       word    size                                                     *
*       long    ptr                                                      *
*                                                                        *
**************************************************************************
ikbdws:
IF ROM_TOS306
            moveq     #0,d3
ENDIF
            move.w    4(sp),d3
            movea.l   6(sp),a2
ikbdstr:    move.b    (a2)+,d1
            bsr.s     ikbdput
            dbra      d3,ikbdstr
            rts


constat:    lea       (kbufrec).w,a0            ; point to ikbd buffer record
            moveq     #-1,d0                    ; set result to true
            lea       6(a0),a2                  ; head
            lea       8(a0),a3                  ; tail
            cmpm.w    (a3)+,(a2)+               ; atomic buffer empty test
            bne.s     const1                    ; branch of not, assume d0 is "clr.w"'ed
            moveq     #0,d0                     ; set result to false
const1:     rts


conin:      bsr.s     constat                   ; see if key pressed
            tst.w     d0
            beq.s     conin                     ; wait until key pressed
            move      sr,-(sp)                  ; protect this upcoming test
            ori       #$700,sr
            move.w    6(a0),d1                  ; get current head pointer offset
            cmp.w     8(a0),d1                  ; head=tail?
            beq.s     cwi2                      ; yes

* check for wrap of pointer

            addq.w    #4,d1                     ; i=h+4
            cmp.w     4(a0),d1
            bcs.s     cwi1                      ; no...
            moveq     #0,d1                     ; wrap pointer
cwi1:       movea.l   (a0),a1                   ; get base address of buffer
            and.l     #$ffff,d1
            move.l    (a1,d1.l),d0              ; get character
            move.w    d1,6(a0)                  ; store new head pointer to buffer
cwi2:       move      (sp)+,sr
            rts


conoutst:   moveq     #-1,d0
            rts                                 ; jdos requirement


***********************************************************************************
*                                                                                 *
*                routine to set up the general interrupt port registers           *
*                        (gpip,are,ddr)                                           *
*                                                                                 *
*                algorithm to set up the port                                     *
*                                                                                 *
*                1.  mask off all interrupts via the imrx register,               *
*                2.  clear all enable and pending bits in the ierx and iprx       *
*                         registers;                                              *
*                3.  check the inerrupt in-service registers and loop till        *
*                         clear;                                                  *
*                4.  init the aer register bits as desired (default = 11111111);  *
*                5.  init the ddr register bits as desired (default = 10000000);  *
*                6.  clear the gpip register;                                     *
*                7.  enable all desired interrupt enable bits;                    *
*                8.  mask on all desired interrupt mask bits;                     *
*                                                                                 *
***********************************************************************************
initmfp:    lea       (gpip).w,a0               ; init mfp address pointer
            moveq     #0,d0                     ; init to zero for clearing mfp
            movep.l   d0,0(a0)                  ; clear gpip thru iera
            movep.l   d0,8(a0)                  ; clear ierb thru isrb
            movep.l   d0,$10(a0)                ; clear isrb thru vr

            move.b    #$48,$16(a0)              ; set mfp autovector to $100 and s-bit
            bset      #2,2(a0)                  ; set cts to low to high transition

IF ROM_TOS306
            lea       (gpip_TT).w,a0            ; init mfp #2 address pointer
            moveq     #0,d0                     ; init to zero for clearing mfp
            movep.l   d0,0(a0)                  ; clear gpip thru iera
            movep.l   d0,8(a0)                  ; clear ierb thru isrb
            movep.l   d0,$10(a0)                ; clear isrb thru vr
            move.b    #$58,$16(a0)              ; set mfp autovector to $140 and s-bit
ENDIF

            clr.b     (privexception).w

* init the "c" timer
            move.w    #$1111,(tc_rot).w         ; setup bitstream for /4 on timer c interrupts
            move.w    #$14,(_timr_ms).w         ; set timer calibration value

            moveq     #2,d0                     ; set to timer C
            moveq     #$50,d1                   ; set to /64 for 200 hz tick
            move.w    #$c0,d2                   ; set to 192
            bsr       settimer                  ; setup timer and init interrupt vector.....

            lea       (timercint).l,a2          ; point to the timer C interrupt routine...
            moveq     #5,d0                     ; point the the timer C interrupt number
            bsr       initint

* init the "d" timer
            moveq     #3,d0                     ; select the d timer
            moveq     #1,d1                     ; init for /4 for 9600 baud
            moveq     #2,d2                     ; init for 9600 baud
            bsr       settimer                  ; branch to out timer initialier...

            move.b    #1,(rbufrec+34).w

* now init the 3 rs232 chip registers
            lea       (gpip).w,a0
            move.l    #$880105,d0
            movep.l   d0,$26(a0)                ; inits scr,ucr,rsr,tsr

IF ROM_TOS306
* now init the 3 rs232 chip registers in mfp #2
            lea       (gpip_TT).w,a0
            move.l    #$880105,d0
            movep.l   d0,$26(a0)                ; inits scr,ucr,rsr,tsr
            move.b    #1,(tcdcr_TT).w           ; timer c+d control register: delay 1:4
            move.b    #2,(tddr_TT).w            ; timer d data register
            move.b    #1,($1380).w              ; ???
ENDIF

* check for SCC chip
            tst.b     (STEFlag).l
            bne.s     initmfp2
            bsr       initscc

* initialize the default rs-232 control line settings
initmfp2:   bsr       dtron
            bsr       rtson

* initialize the rs-232 buffer record structure
            lea       (rbufrec).w,a0
            lea       (rs232init).l,a1
            moveq     #$23,d0
            bsr       lbmove                    ; do block move and return

IF ROM_TOS306
            lea       (ttmfp_rbufrec).w,a0
            lea       (ttrs232init).l,a1
            moveq     #$23,d0
            bsr       lbmove

*           ttrs232init:DC.L      iobuf_ttrs232i            ; ibufptr
*                       DC.W      $0100                     ; ibufsiz
*                       DC.W      $0000                     ; ibufhead
*                       DC.W      $0000                     ; ibuftail
*                       DC.W      $0080                     ; ibuflow
*                       DC.W      $00c0                     ; ibufhigh
*                       DC.L      iobuf_ttrs232o            ; obufptr
*                       DC.W      $0100                     ; obufsiz
*                       DC.W      $0000                     ; obufhead
*                       DC.W      $0000                     ; obuftail
*                       DC.W      $0080                     ; obuflow
*                       DC.W      $00c0                     ; obufhigh
*                       DC.B      $00                       ; rsrbyte
*                       DC.B      $00                       ; tsrbyte
*                       DC.B      $00                       ; rxoff
*                       DC.B      $00                       ; txoff
*                       DC.B      $01                       ; rsmode_xon
*                       DC.B      $00                       ; rsmode_filler
*                       DC.W      $01ff

ENDIF

* initialize the midi buffer record structure
            lea       (mbufrec).w,a0
            lea       (minit).l,a1
            moveq     #$d,d0
            bsr       lbmove                    ; do block move and return

            move.w    #-1,(altdigits).l         ; reset alt-numpad ascii entering
            move.l    #aciaexit,d0              ; init ikbd and midi error handler address
            move.l    d0,(vkbderr).w            ; init keyboard error handler address
            move.l    d0,(vmiderr).w            ; init midi error handler address
            move.l    #sysmidi,(midivec).w      ; point to system midi interrupt vector
            move.l    #astatusmidi,(astatusmidip).w
            move.l    #astatuskeyboard,(astatuskeyp).w
            move.l    #itsakey,(itsakeyp).w

* init the midi acia next
            move.b    #$03,(midictl).w          ; init the midi acia via master reset

* init the acia to divide by 16x clock, 8 bit data, 1 stop bit, no parity,
* rts low, transmitting interrupt disabled, receiving interrupt enabled
            move.b    #$95,(midictl).w

* initialize the keyboard acia interrupt vector exception address
            move.b    #7,(conterm).w            ; enabled keyclick, repeat key, bell functions

            move.l    #jdostime,(clkintvec).w
            move.l    #genrts,d0                ; generalized rts for ikbd subsystems
            move.l    d0,(statintvec).w
            move.l    d0,(msintvec).w           ; init user mouse interrupt adr to rts
            move.l    d0,(joyintvec).w
            bsr       initdevstables
*
* Sound routine initialization - uses the pre-init'ed d0.l=0000 !!
*
            moveq     #0,d0                     ; init 'd0' to clear sound variables
            move.l    d0,(cursnd).w             ; clear sound ptr
            move.b    d0,(timer).w              ; clear delay timer
            move.b    d0,(auxd).w               ; clear temp value
            move.l    d0,(prt_to).w             ; init printer timeout to 0

            bsr       strobeoff                 ; init strobe to off (line high!)
            move.b    #15,(cdelay1).w           ; init system default key repeat values
            move.b    #2,(cdelay2).w

* within the mouse relative routine
* initialize the ikbd buffer record structure
            lea       (kbufrec).w,a0
            lea       (kinit).l,a1
            moveq     #$d,d0
            bsr.s     lbmove                    ; do block move and return

            bsr       bioskeys                  ; point key translation address to
* the rom based translation tables

* init the acia next
            move.b    #$03,(keyctl).w           ; init the acia via master Reset

* now that the vector is initialized, we can allow interrupts to occur!
* init the acia do divice by 64 clock, 8 bit data, 1 stop bit, no parity,
* rts low, transmitting interrupt disabled, receiving interrupt enabled

            move.b    #$96,(keyctl).w

            movea.l   #mfpvectr,a3              ; point to initializing array of exception vec's
            moveq     #3,d1                     ; init branch counter/index
st1:        move.l    d1,d2
            move.l    d1,d0                     ; load in interrupt # to setup
            addi.b    #9,d0                     ; add constand to point to proper mfp interrupt
            asl.l     #2,d2
            movea.l   (a3,d2.w),a2
            bsr       initint                   ; go to service routine
            dbra      d1,st1
            lea       (midikey).l,a2
            moveq     #6,d0                     ; load in interrupt # to setup
            bsr       initint                   ; go to service routine

* initializing code which sets the enable
* and mask bits...
            lea       (ctsint).l,a2             ; point to the CTS interrupt routine...
            moveq     #2,d0                     ; point to the CTS interrrupt number
            bsr       initint

IF ROM_TOS306
            movea.l   #$e36704,a3               ; ttmfp_txerr,ttmfp_txrint,ttmfp_rxerr,ttmfp_rcvrint
            movea.w   #mfpTT_SendError,a0       ; $0164
            move.l    (a3)+,(a0)+               ; mfpTT_SendError = ttmfp_txer
            move.l    (a3)+,(a0)+               ; mfpTT_SendBufferEmpty = ttmfp_txrint
            move.l    (a3)+,(a0)+               ; mfpTT_ReceiveError = ttmfp_rxerr
            move.l    (a3)+,(a0)+               ; mfpTT_ReceiveBufferFull = ttmfp_rcvrint
            ori.b     #$1e,(iera_TT).w
            ori.b     #$1e,(imra_TT).w
ENDIF

genrts:     rts

lbmove:     move.b    (a1)+,(a0)+
            dbra      d0,lbmove
            rts

kinit:      DC.L      iobuf_keyb                ; ibufptr
            DC.W      $0100                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0040                     ; ibuflow
            DC.W      $00c0                     ; ibufhigh
minit:      DC.L      iobuf_midi                ; ibufptr
            DC.W      $0080                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0020                     ; ibuflow
            DC.W      $0060                     ; ibufhigh
rs232init:  DC.L      iobuf_rs232i              ; ibufptr
            DC.W      $0100                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0080                     ; ibuflow
            DC.W      $00c0                     ; ibufhigh
            DC.L      iobuf_rs232o              ; obufptr
            DC.W      $0100                     ; obufsiz
            DC.W      $0000                     ; obufhead
            DC.W      $0000                     ; obuftail
            DC.W      $0080                     ; obuflow
            DC.W      $00c0                     ; obufhigh
            DC.B      $00                       ; rsrbyte
            DC.B      $00                       ; tsrbyte
            DC.B      $00                       ; rxoff
            DC.B      $00                       ; txoff
            DC.B      $01                       ; rsmode_xon
            DC.B      $00                       ; rsmode_filler
            DC.W      $01ff

* array of exception vector addresses for the above interrupts, including
* dummy vectors that point to "rte's".

mfpvectr:   DC.L      txerror
            DC.L      txrint
            DC.L      rxerror
            DC.L      rcvrint

***********************************************************************************
*                                                                                 *
*                routine to set up a timer                                        *
*                                                                                 *
*                algorithm to init a timer                                        *
*                                                                                 *
*                1.  determine which timer and set d0.b = to timer's index value  *
*                    as shown below;                                              *
*                2.  disable the associated interrupt;                            *
*                3.  disable the timer itself via it's timer control register;    *
*                4.  initialize the timer's data register                         *
*                5.  repeat step #4 until the data register's contents are        *
*                    verified, per the errata sheet to the 68901 description;     *
*                6.  turn on the timer by using the value that you previously     *
*                    stored in d1;                                                *
*                                                                                 *
*                note:    the interrupt vector for the associated timer           *
*                         is not set in this routine, so it is the user's         *
*                         responsiblity to set it if so desired!                  *
*                                                                                 *
*                                                                                 *
*                registers used:          d0-d3/a0-a3                             *
*                registers saved:         d0-d3/a0-a3                             *
*                entry:                                                           *
*                        d0.l - timer to be set                                   *
*                                0 - timer a                                      *
*                                1 - timer b                                      *
*                                2 - timer c                                      *
*                                3 - timer d                                      *
*                        d1.b - timer's new control setting                       *
*                        d2.b - timer's data register data                        *
*                                                                                 *
*                exit:   no values to pass                                        *
*                                                                                 *
*                        d3   - used and abused by call to mskreg routine         *
*                        a0.l - set to mfp register base                          *
*                        a1.l - temporary location for a3                         *
*                        a2.l - used to pass table address to mskreg routine      *
*                        a3.l - used to pass table address to mskreg routine      *
*                                                                                 *
***********************************************************************************
settimer:   movem.l   d0-d4/a0-a3,-(sp)         ; save all registers to be messed with!!
            movea.w   #gpip,a0                  ; set mfp chip address pointer

            movea.l   #imrt,a3                  ; mask off the timer's interrupt maskable bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #iert,a3                  ; mask off the timer's interrupt enable bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #iprt,a3                  ; mask off the timer's interrupt pending bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #isrt,a3                  ; mask off the timer's interrupt inservice bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #tcrtab,a3                ; mask off the timer's control bits
            movea.l   #tcrmsk,a2
            bsr.s     mskreg

            exg       a3,a1                     ; save address pointer for restoring control

            lea       (tdrtab).l,a3             ; initialize the timer data register
            moveq     #0,d3                     ; to prevent false effective address generation
            move.b    (a3,d0.w),d3
verify:     move.b    d2,(a0,d3.w)
            cmp.b     (a0,d3.w),d2
            bne.s     verify

            exg       a3,a1                     ; grab that register address back
            or.b      d1,(a3)                   ; mask the timer control register value

            movem.l   (sp)+,d0-d4/a0-a3         ; restore all registers that were saved
            rts

***********************************************************************************
*                generalize mask register bit(s) routine                          *
*                                                                                 *
*       entry                                                                     *
*       static  d0 - contains the timer #                                         *
*               d3 - used and abused                                              *
*               d4 - used and abused                                              *
*       static  a0 - mfp register base                                            *
*               a3 - points to table of similar timer registers                   *
*       static  a2 - points to table of similar timer data registers              *
***********************************************************************************

mskreg:     bsr.s     getmask
            move.b    (a2),d3                   ; grab mask now
            and.b     d3,(a3)                   ; and have masked off the desired bit(s)
            rts

getmask:    moveq     #0,d3                     ; to prevent false effective address generation
            adda.w    d0,a3                     ; have got pointer to mfp register now
            move.b    (a3),d3                   ; now have the address offset to mfp
            add.l     a0,d3
            movea.l   d3,a3                     ; now have address pointing to desired mfp reg.
* now we get the mask to turn off interrupt
            adda.w    d0,a2                     ; have got pointer to mask now
            rts


iert:       DC.B      $06,$06,$08,$08
iprt:       DC.B      $0a,$0a,$0c,$0c
isrt:       DC.B      $0e,$0e,$10,$10
imrt:       DC.B      $12,$12,$14,$14
imrmt:      DC.B      $df,$fe,$df,$ef
tcrtab:     DC.B      $18,$1a,$1c,$1c
tcrmsk:     DC.B      $00,$00,$8f,$f8
tdrtab:     DC.B      $1e,$20,$22,$24

***********************************************************************************
*                initialize mfp interrupt via GEMDOS                              *
*                                                                                 *
*                entry                                                            *
*                                                                                 *
*                void    mfpint(numint,intvec)                                    *
*                word    numint                                                   *
*                long    intvec                                                   *
*                                                                                 *
***********************************************************************************
mfpint:     move.w    4(sp),d0
            movea.l   6(sp),a2
            andi.l    #$f,d0                    ; to ensure masking of 0-$f

***********************************************************************************
*                                                                                 *
*                routine to init an mfp associated interrupt vector               *
*                                                                                 *
*                algorithm                                                        *
*                                                                                 *
*                1.  block the interrupt via it's mask bit;                       *
*                2.  disable the interrupt's enable and pending bits;             *
*                3.  check the interrupt's in-service register and loop till      *
*                    clear;                                                       *
*                4.  init the interrupt's associated vector;                      *
*                5.  set the interrupt's enable bit;                              *
*                6.  set the interrupt's mask bit;                                *
*                                                                                 *
*                entry                                                            *
*                         d0 - contains interrupt # to aff                        *
*                         a2 - contains new vector address                        *
***********************************************************************************

initint:    movem.l   d0-d2/a0-a2,-(sp)         ; save affected registers
            bsr.s     disint                    ; disable the interrupts
            move.l    d0,d2                     ; get a copy so as to determine where to...
            asl.w     #2,d2                     ; place the a2 address into the int. vector
            addi.l    #$100,d2                  ; interrupt vector addr = (4 * int) + $000100
            movea.l   d2,a1                     ; transfer the calculated address to a register
            move.l    a2,(a1)                   ; ...that can act upon it thus<--vector init'ed
            bsr.s     enabint                   ; enable interrupts
            movem.l   (sp)+,d0-d2/a0-a2         ; restore affected registers
            rts

*************************************************************************
*                disable an mfp interrupt via GEMDOS                    *
*                                                                       *
*                entry                                                  *
*                                                                       *
*                void    jdisint(numint)                                *
*                word    numint                                         *
*                                                                       *
*************************************************************************

jdisint:    move.w    4(sp),d0
            andi.l    #$f,d0                    ; to ensure masking of 0-$f

*************************************************************************
*                interrupt disable routine                              *
*************************************************************************

disint:     movem.l   d0-d1/a0-a1,-(sp)         ; save affected registers
            lea       (gpip).w,a0               ; set mfp chip address pointer
            lea       $12(a0),a1                ; set a1 to the mskoff routine
            bsr.s     bselect                   ; generate the appropriate bit to clear
            bclr      d1,(a1)                   ; and clear the bit...
            lea       6(a0),a1                  ; set a1 for another mskoff call
            bsr.s     bselect
            bclr      d1,(a1)                   ; and clear the bit...
            lea       $e(a0),a1                 ; now set up to check for interrupts in progress
            bsr.s     bselect                   ; get proper a/b version...
            move.b    #$fe,d0
            rol.b     d1,d0
            move.b    d0,(a1)
            movem.l   (sp)+,d0-d1/a0-a1         ; restore affected registers
            rts

*************************************************************************
*                enable/re-enabled an mfp interrupt via GEMDOS          *
*                                                                       *
*                entry                                                  *
*                                                                       *
*                void    jenabint(numint)                               *
*                word    numint                                         *
*                                                                       *
*************************************************************************

jenabint:   move.w    4(sp),d0
            andi.l    #$f,d0                    ; to ensure masking of 0-$f

*************************************************************************
*                enable interrupt routine                               *
*************************************************************************

enabint:    movem.l   d0-d1/a0-a1,-(sp)         ; save affected registers
            lea       (gpip).w,a0               ; set mfp chip address pointer
            lea       6(a0),a1                  ; set up to enable the interrupt enable bit
            bsr.s     bselect
            bset      d1,(a1)                   ; and set the bit...
            lea       $12(a0),a1                ; set up to enable the interrupt mask bit
            bsr.s     bselect
            bset      d1,(a1)                   ; and set the bit...
            movem.l   (sp)+,d0-d1/a0-a1         ; restore affected registers
            rts


*************************************************************************
*                                                                       *
*       the following routine generates the appropriate bset/bclr #     *
*       for the interrupt # specified in d0.    valid interrupt #'s are *
*       0 --> 15 as shown in the 68901 chip specification.  It also     *
*       selects between the ixra and the ixrb version of the register   *
*       as is appropriate.                                              *
*                                                                       *
*       entry   d0 - contains the interrupt number                      *
*               a1 - contains the pointer to the "ixra" version of      *
*                    the interrupt byte to mask                         *
*       exit            d0 - same as upon entry                         *
*                       d1 - contains the number of the bit             *
*************************************************************************
bselect:    move.b    d0,d1                     ; copy d0 to d1 for scratch work
            cmpi.b    #8,d1                     ; see if desired int # >= 8...
            blt.s     skip0                     ; ...and branch if it ain't...
            subq.w    #8,d1                     ; adjust for using ixrb instead
            rts

skip0:      addq.l    #2,a1                     ; adjust for using ixrb instead
            rts


*************************************************************************
*                                                                       *
*               receiver buffer full interrupt routine                  *
*                                                                       *
*               grabs data from the rs-232 receiver port                *
*                                                                       *
*************************************************************************
rcvrint:    movem.l   d0-d1/a0-a2,-(sp)         ; save affected registers
            lea       (rbufrec).w,a0            ; point to current output buffer record
            lea       (gpip).w,a2               ; set mfp chip address pointer
_rcvrint:   move.b    $2a(a2),$1c(a0)           ; do the required rsr read before the udr read!
            move.b    $2e(a2),d0                ; get incoming data byte

            btst      #0,$20(a0)                ; is the rs232 mode xon/xoff?
            beq.s     ri3                       ; no... so process normally

            cmpi.b    #$13,d0                   ; is the data an "xoff" signal?
            bne.s     ri2                       ; no...now check for xon
            move.b    #$ff,$1f(a0)              ; set to halted transmission to host
            bra.s     ri8

ri2:        cmpi.b    #$11,d0                   ; is the data an "xon" signal?
            bne.s     ri3                       ; neither xon/xoff value, must be normal data...
            clr.b     $1f(a0)                   ; set to normal transmission status
            bra.s     ri7                       ; abnormal exit condition!!

ri3:        move.w    8(a0),d1                  ; get current tail pointer offset
            bsr       wrapin                    ; do wrap of input pointer if needed
            cmp.w     6(a0),d1                  ; head=tail?
            beq.s     ri8                       ; yes...exit...
            bsr       rs232put                  ; store data in ring buffer

* now check for highwater mark triggering of flow-control
            tst.b     $20(a0)                   ; flow control active?
            beq.s     ri8                       ; no...exit...
            move.w    8(a0),d0                  ; current tail pointer
            sub.w     6(a0),d0                  ; - current head
            bpl.s     ri4                       ; wrap around?
            add.w     4(a0),d0                  ; + size to correct the wrap around
ri4:        cmp.w     $c(a0),d0                 ; high-water mark not reached?
            blt.s     ri8                       ; correct...exit...
            tst.b     $1e(a0)                   ; high-water flag already set?
            bne.s     ri8                       ; yes...exit...
            move.b    #$ff,$1e(a0)              ; set high-water flag
            btst      #0,$20(a0)                ; are we using xon/xoff flow control?
            bne.s     ri6                       ; yes...
            bsr       rtsoff                    ; we're ready now for more data...yum! yum!
            bra.s     ri8
ri6:        move.b    #$13,$21(a0)              ; xoff
ri7:        tst.b     $2c(a2)                   ; transmitter status?
            bpl.s     ri8                       ; not ready
            bsr       wr_rs232
ri8:        move.b    #$ef,$e(a2)
            movem.l   (sp)+,d0-d1/a0-a2         ; restore affected registers
            rte

*************************************************************************
*                                                                       *
*               transmit buffer empty interrupt routine                 *
*                                                                       *
*************************************************************************
txrint:     movem.l   d0-d1/a0-a2,-(sp)         ; save affected registers
            lea       (gpip).w,a2               ; set mfp chip address pointer
            lea       (rbufrec).w,a0            ; point to current output buffer record
_txrint:    bsr       wr_rs232
            move.b    #$fb,$e(a2)               ; xmit buffer empty
            movem.l   (sp)+,d0-d1/a0-a2         ; restore affected registers
            rte

*************************************************************************
*                                                                       *
*               Clear-To-Send interrupt routine                         *
*                                                                       *
*************************************************************************
ctsint:     movem.l   d0-d1/a0-a2,-(sp)         ; save affected registers
            lea       (rbufrec).w,a0            ; point to current output buffer record
            lea       (gpip).w,a2               ; set mfp chip address pointer

            btst      #1,$20(a0)                ; are we using CTS/RTS flow control?
            beq.s     ctsexit                   ; no...
            btst      #2,(a2)                   ; CTS already active?
            bne.s     ctsoff                    ; yes...
            clr.b     $1f(a0)                   ; xon_flag = 0
            bset      #2,2(a2)                  ; CTS set
            tst.b     $2c(a2)                   ; transmitter status?
            bpl.s     ctsexit                   ; not empty...
            bsr.s     wr_rs232                  ; send a byte
            bra.s     ctsexit
ctsoff:     move.b    #$ff,$1f(a0)              ; xon_flag = -1
            bclr      #2,2(a2)
ctsexit:    move.b    #$fb,$10(a2)              ; clear CTS
            movem.l   (sp)+,d0-d1/a0-a2         ; restore affected registers
            rte


*************************************************************************
*               routines to handle tx or rx errors                      *
*************************************************************************
rxerror:    movem.l   d0/a0,-(sp)               ; save all registers
            lea       (gpip).w,a0               ; set mfp chip address pointer
_rxerror:   move.b    $2a(a0),(rbufrec+28).l    ; receiver status register
            move.b    $2e(a0),d0                ; dummy read of data register
            move.b    #$f7,$e(a0)
            movem.l   (sp)+,d0/a0               ; restore all registers
            rte

txerror:    move.l    a0,-(sp)
            lea       (gpip).w,a0               ; set mfp chip address pointer
_txerror:   move.b    $2c(a0),(rbufrec+29).l    ; transmitter status register
            move.b    #$fd,$e(a0)
            movea.l   (sp)+,a0                  ; restore all registers
            tst.b     (tsr).w
            rte


wr_rs232:   move.l    a0,-(sp)                  ; save all registers
            move.b    $21(a0),d0                ; send handshake byte pending?
            beq.s     wr_rs2322                 ; no...
            clr.b     $21(a0)                   ; clear handshake byte
            bra.s     wr_rs232o                 ; output the handshake byte
wr_rs2322:  move.b    $20(a0),d0                ; flow control active?
            and.b     $1f(a0),d0                ; and xon-flag
            bne.s     wr_rs2324                 ; yes...
            adda.w    #$e,a0                    ; switch to output ring buffer
            move.w    6(a0),d0                  ; head-index
            cmp.w     8(a0),d0                  ; = tail-index?
            beq.s     wr_rs2324                 ; buffer empty? => out
            bsr.s     rs232get                  ; get next character from the buffer
wr_rs232o:  tst.b     $2c(a2)                   ; transmit buffer empty?
            bpl.s     wr_rs232o                 ; no...wait...
            move.b    $2c(a2),(rbufrec+29).l    ; save transmitter status
            move.b    d0,$2e(a2)                ; output the byte
wr_rs2324:  movea.l   (sp)+,a0                  ; restore all registers
            rts

rs232put:   move.w    8(a0),d1                  ; tail-index
            bsr.s     wrapin
rs232putl:  cmp.w     6(a0),d1                  ; = head-index?
            beq.s     rs232putl                 ; buffer full...wait...
            movea.l   (a0),a1                   ; ptr to send buffer
            and.l     #$ffff,d1
            move.b    d0,(a1,d1.l)              ; byte into the send buffer
            move.w    d1,8(a0)                  ; update tail-index
            rts

rs232get:   move.w    6(a0),d1                  ; head-index
            cmp.w     8(a0),d1                  ; = tail-index?
            beq.s     rs232get                  ; empty? wait...
            bsr.s     wrapin
            movea.l   (a0),a1                   ; ptr to receive buffer
            moveq     #0,d0
            and.l     #$ffff,d1
            move.b    (a1,d1.l),d0              ; byte from the receive buffer
            move.w    d1,6(a0)                  ; update head-index
            rts

rtson:      lea       (psgsel).w,a1
            move      sr,-(sp)
            ori       #$700,sr
            move.b    #14,(a1)
            move.b    (a1),d1
            and.b     #$f7,d1                   ; clear bit 3 in port A
            move.b    d1,2(a1)
            move      (sp)+,sr
            rts

rtsoff:     lea       (psgsel).w,a1
            move      sr,-(sp)
            ori       #$700,sr
            move.b    #14,(a1)
            move.b    (a1),d1
            ori.b     #8,d1                     ; set bit 3 in port A
            move.b    d1,2(a1)
            move      (sp)+,sr
            rts

wrapin:     addq.w    #1,d1                     ; i=h+1
            cmp.w     4(a0),d1                  ; > i => current bufsiz?
            bcs.s     wi1                       ; no...
            moveq     #0,d1                     ; wrap pointer
wi1:        rts

iorec:      move.w    4(sp),d1                  ; device number
            beq.s     iorecaux                  ; rs232? =>
            asl.l     #2,d1
            move.l    devtab(pc,d1.w),d0
            rts

iorecaux:   move.l    (bsrecauxp).w,d0
            rts

devtab:     DC.L      rbufrec                   ; RS232
            DC.L      kbufrec                   ; IKBD
            DC.L      mbufrec                   ; MIDI

rsconf:     movea.l   (bsrsconfp).w,a0
            jmp       (a0)

auxrsconf:  lea       (rbufrec).w,a0            ; point to current output buffer record
            lea       (gpip).w,a2               ; set mfp chip address pointer
_auxrsconf: moveq     #0,d0
            cmpi.w    #-2,4(sp)                 ; -2 = return last baudrate
            bne.s     auxrsconf1
            move.b    $22(a0),d0                ; last baudrate
            rts

auxrsconf1: ori       #$700,sr                  ; no interrupts for now
*
*      first we grab the old ucs,rsr,tsr,scr contents
*
            movep.l   $28(a2),d7
*
*      set flow control mode(s)
*
            move.w    6(sp),d0                  ; if -1 then don't change
            cmp.w     #3,d0
            bhi.s     auxc1                     ; no =>
            bne.s     auxrsconf2
            moveq     #1,d0                     ; set flag for handshake
auxrsconf2: cmp.b     $20(a0),d0                ; state unchanged? => continue
            beq.s     auxc1
            move.w    d0,-(sp)                  ; save new handshake state
            tst.b     $1f(a0)                   ; xon flag set?
            beq.s     auxrsconf3
            clr.b     $1f(a0)                   ; clear xon flag
            bsr       wr_rs232                  ; transmit xon to continue
auxrsconf3: tst.b     $1e(a0)                   ; RTS set?
            beq.s     auxrsconf4                ; no...
            bsr       auxinclrhw                ; reenable RTS
auxrsconf4: move.w    (sp)+,d0                  ; new handshake state
            move.b    d0,$20(a0)                ; set new handshake state
*
*      set timer baud rate
*
auxc1:      tst.w     4(sp)                     ; if -1 then don't change
            bmi.s     auxc2
            bclr      #0,$2a(a2)                ; disable receiver
            bclr      #0,$2c(a2)                ; disable transmitter
            move.w    4(sp),d1                  ; new baudrate
            move.b    d1,$22(a0)                ; store for later
            move.b    baudctrl(pc,d1.w),d0      ; get baudrate control register settings mask
            move.b    bauddata(pc,d1.w),d2      ; get baudrate data register value
            andi.b    #$70,$1c(a2)
            move.b    d2,$24(a2)                ; set tuner D to new baud rate
            or.b      d0,$1c(a2)
            bset      #0,$2a(a2)                ; enable receiver
            bset      #0,$2c(a2)                ; enable transmitter

*      set rs-232 registers

auxc2:      tst.w     8(sp)                     ; if -1 then don't change
            bmi.s     auxc3
            move.b    9(sp),$28(a2)             ; set ucr
auxc3:      tst.w     $a(sp)                    ; if -1 then don't change
            bmi.s     auxc4
            move.b    $b(sp),$2a(a2)            ; set rsr
auxc4:      tst.w     $c(sp)                    ; if -1 then don't change
            bmi.s     auxc5
            move.b    $d(sp),$2c(a2)            ; set tsr
auxc5:      tst.w     $e(sp)                    ; if -1 then don't change
            bmi.s     auxc6
            move.b    $f(sp),$26(a2)            ; set scr
auxc6:      move.l    d7,d0                     ; move old contents of rs-232 registers to d0.l
            rts

* baudrate table - control register setting

baudctrl:   DC.B      $01,$01,$01,$01,$01,$01,$01,$01
            DC.B      $01,$01,$01,$01,$01,$01,$02,$02

* baudrate table - data register setting

bauddata:   DC.B      $01,$02,$04,$05,$08,$0a,$0b,$10
            DC.B      $20,$40,$60,$80,$8f,$af,$40,$60

rs232devs:  DC.L      auxistat                  ; Dev 6: "Modem 1" - ST MFP
            DC.L      auxin
            DC.L      _auxostat
            DC.L      _auxout
            DC.L      auxrsconf
            DC.L      rbufrec

            DC.L      sccbistat                 ; Dev 7: "Modem 2" - SCC Channel B
            DC.L      sccbin
            DC.L      sccbostat
            DC.L      sccbout
            DC.L      sccbrsconf
            DC.L      sccbbufrec

IF TOS_ROM306
            DC.L      ttmfp_auxistat            ; Dev 8: "Seriell 1" - TT MFP
            DC.L      ttmfp_auxin
            DC.L      ttmfp__auxostat
            DC.L      ttmfp__auxout
            DC.L      ttmfp_auxrsconf
            DC.L      ttmfp_rbufrec
ENDIF

            DC.L      sccaistat                 ; Dev 8/9: "Seriell 2" - SCC Channel A
            DC.L      sccain
            DC.L      sccaostat
            DC.L      sccaout
            DC.L      sccarsconf
            DC.L      sccabufrec

_bconmap:   moveq     #0,d0
            move.w    4(sp),d1
            move.w    (bdevmappedAux).w,d0
            cmp.w     #-1,d1
            beq.s     _bconmap_rts
            move.l    #bdevtabptr,d0
            cmp.w     #-2,d1
            beq.s     _bconmap_rts
            moveq     #0,d0
            subq.w    #6,d1
            bmi.s     _bconmap_rts
            cmp.w     (bdevcount).w,d1
            bcc.s     _bconmap_rts
            move.w    (bdevmappedAux).w,d1
            subq.w    #6,d1
            asl.w     #3,d1
            move.w    d1,d2
            add.w     d1,d1
            add.w     d1,d2
            movea.l   (bdevtabptr).w,a0
            adda.w    d2,a0
            lea       (xconstat+4).w,a1
            move.l    (a1),(a0)+
            move.l    $20(a1),(a0)+
            move.l    $40(a1),(a0)+
            move.l    $60(a1),(a0)+
            move.l    (bsrsconfp).l,(a0)+
            move.l    (bsrecauxp).w,(a0)+
            move.w    4(sp),d1
            subq.w    #6,d1
            asl.w     #3,d1
            move.w    d1,d2
            add.w     d1,d1
            add.w     d1,d2
            movea.l   (bdevtabptr).w,a0
            adda.w    d2,a0
            move.l    (a0)+,(a1)
            move.l    (a0)+,$20(a1)
            move.l    (a0)+,$40(a1)
            move.l    (a0)+,$60(a1)
            move.l    (a0)+,(bsrsconfp).w
            move.l    (a0)+,(bsrecauxp).w
            move.w    (bdevmappedAux).w,d0
            move.w    4(sp),(bdevmappedAux).w
_bconmap_rts:rts


initdevstables:lea    (bdevtable).w,a0
            move.l    a0,(bdevtabptr).w
IF TOS_ROM306
            move.w    #4,(bdevcount).w
ELSE
            move.w    #3,(bdevcount).w
            moveq     #1,d0                     ; only MFP RS232 is available
            movea.l   sp,a1
            movea.l   (busexception).w,a2
            move.l    #initdevnoscc,(busexception).w
            tst.b     (scu_gp1).w
            move.w    #3,d0                     ; also 2 SCC RS232 are available
initdevnoscc:movea.l  a1,sp
            move.l    a2,(busexception).w
            move.w    d0,(bdevcount).w
ENDIF
            lea       rs232devs(pc),a1
IF TOS_ROM306
            move.w    #$17,d0                   ; 4 devices
ELSE
            move.w    #$11,d0                   ; 3 devices
ENDIF
initdev1:   move.l    (a1)+,(a0)+
            dbra      d0,initdev1
            move.w    #6,(bdevmappedAux).w      ; Device 6 is mapped into device 1 (Aux)
            lea       (rs232devs).l,a1          ; Map MFP RS232 to dev 1
            lea       (xconstat+4).w,a0
            move.l    (a1)+,(a0)                ; Fix tconstat for dev 1
            move.l    (a1)+,$20(a0)             ; Fix tconin for dev 1
            move.l    (a1)+,$40(a0)             ; Fix tcostat for dev 1
            move.l    (a1)+,$60(a0)             ; Fix tconout for dev 1
            move.l    (a1)+,(bsrsconfp).w
            move.l    (a1)+,(bsrecauxp).w
            rts

IF ROM_TOS306
ttmfp_auxistat:
            lea       (ttmfp_rbufrec).w,a0
            bra       _auxistat
ttmfp_auxin:lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _auxin
ttmfp__auxostat:
            lea       (ttmfp_rbufrec+14).w,a0
            bra       __auxostat
ttmfp__auxout:
            move.w    6(sp),d0
            lea       (ttmfp_rbufrec+14).w,a0
            bsr       rs232put
            lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _auxoutwr
ttmfp_rcvrint:movem.l   d0-d1/a0-a2,-(sp)
            lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _rcvrint
ttmfp_txrint:movem.l   d0-d1/a0-a2,-(sp)
            lea       (gpip_TT).w,a2
            lea       (ttmfp_rbufrec).w,a0
            bra       _txrint
ttmfp_rxerr:movem.l   d0/a0,-(sp)
            lea       (gpip_TT).w,a0
            bra       _rxerror
ttmfp_txerr:move.l    a0,-(sp)
            lea       (gpip_TT).w,a0
            bra       _txerror
ttmfp_auxrsconf:lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _auxrsconf
ENDIF

sccinit:    move.b    (a1)+,d0                  ; SCC register number
            bmi.s     sccina                    ; end of the register list? => exit
            move.b    d0,(a0)                   ; select register
            move.b    (a1)+,(a0)                ; write into register
            bra.s     sccinit
sccina:     rts


* SCC initialization
scctbl:     DC.B      $04,$44                   ; x16, 1 stop, no parity
            DC.B      $01,$04
            DC.B      $02,$60
            DC.B      $03,$c0                   ; rx 8 bit, no crc, rx disabled
            DC.B      $05,$e2                   ; tx 8 bit, no crc, tx disabled, rts,dtr on
            DC.B      $06,$00
            DC.B      $07,$00
            DC.B      $09,$01
            DC.B      $0a,$00                   ; NRZ
            DC.B      $0b,$50                   ; tx & rx clk = brg, trxc=in
            DC.B      $0c,$18                   ; clock divisor for 9600 (pclk)
            DC.B      $0d,$00                   ; divisor high
            DC.B      $0e,$02                   ; BRG = PCLK
            DC.B      $0e,$03                   ; enable BRG
            DC.B      $03,$c1                   ; enable Rx, 8 bits
            DC.B      $05,$ea                   ; enable Tx, 8 bits, rts, dtr on
            DC.B      $0f,$20
            DC.B      $00,$10
            DC.B      $00,$10
            DC.B      $01,$17
            DC.B      $09,$09
            DC.B      $ff                       ; end of the register list
            DC.B      $00

* disconnect LAN from port a
nolan:      move      sr,d1
            ori       #$700,sr
            move.b    #14,(psgsel).w
            move.b    (psgsel).w,d0
            bset      #7,d0
            move.b    #14,(psgsel).w
            move.b    d0,(psgwr).w
            move      d1,sr
            rts

initscc:    movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #initscc_none,(busexception).w
            tst.b     (sccdat_a).w
            move.l    a0,(busexception).w
            lea       (SCC).w,a0
            lea       sccvect(pc),a1
            moveq     #15,d0
initscc2:   move.l    (a1)+,(a0)+
            dbra      d0,initscc2
IF ROM_TOS306
            clr.w     (scdmactl).w
ENDIF
            bsr.s     nolan
            lea       (sccabufrec).w,a0
            lea       sccbufinit(pc),a1
            moveq     #$25,d0
            bsr       lbmove
            lea       (sccbbufrec).w,a0
            lea       sccbufinit(pc),a1
            moveq     #$25,d0
            bsr       lbmove
            move.l    #$1434,(sccbbufrec).w
            move.l    #$1534,(sccbbufrec+14).w
            lea       (sccctl_a).w,a2
            move.b    #9,(a2)
            move.b    #$c0,(a2)
            move.w    #$104,d0                  ; Delay Mode, /4 Prescale, data = 4 (about 6us delay)
            jsr       (mfpdelay).l
            lea       (sccctl_a).w,a0
            lea       scctbl(pc),a1
            bsr       sccinit
            lea       (sccctl_b).w,a0
            lea       scctbl(pc),a1
            bsr       sccinit
            bset      #5,(vme_mask).w           ; Enable IRQ5 from VMEBUS/SCC
            rts

initscc_none:move.l   a0,(busexception).w
            movea.l   a1,sp
            rts


**************************************************************************
*                                                                        *
*                  83C30 SCC interrupt vectors                           *
*                                                                        *
**************************************************************************
sccvec_portbtrnbempty:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).w,a0
            bra       scc23
sccvec_portbextstchg:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).w,a0
            bra       scc24
sccvec_portbrecchr:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).w,a0
            bra.s     scc09
sccvec_portbschrrec:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).l,a0
            bra       scc16

sccvec_portatrnbempty:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).w,a0
            bra       scc23
sccvec_portaextstchg:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).w,a0
            bra       scc24
sccvec_portarecchr:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).w,a0
            bra.s     scc09
sccvec_portaschrrec:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).l,a0
            bra       scc16

scc09:      move.b    #8,(a2)                   ; select scc register #8
            move.b    (a2),d0                   ; read "Receive Data register"
            and.b     $23(a0),d0
            btst      #0,$20(a0)
            beq.s     scc11
            cmp.b     #$13,d0
            bne.s     scc10
            st        $1f(a0)
            bra.s     scc15
scc10:      cmp.b     #$11,d0
            bne.s     scc11
            tst.b     $1f(a0)
            sf        $1f(a0)
            bne.s     scc14
scc11:      move.w    8(a0),d1
            bsr       wrapin
            cmp.w     6(a0),d1
            beq.s     scc15
            bsr       rs232put
            tst.b     $20(a0)
            beq.s     scc15
            tst.b     $1e(a0)
            bne.s     scc15
            move.w    8(a0),d0
            sub.w     6(a0),d0
            bpl.s     scc12
            add.w     4(a0),d0
scc12:      cmp.w     $c(a0),d0
            blt.s     scc15
            st        $1e(a0)
            btst      #0,$20(a0)
            bne.s     scc13
            move.b    $1d(a0),d0
            bclr      #1,d0                     ; clear RTS bit
            move.b    d0,$1d(a0)
            move.b    #5,(a2)                   ; select scc register #5
            move.b    d0,(a2)                   ; write "Transmit Parameters and Controls" register
            bra.s     scc15
scc13:      move.b    #$13,$21(a0)
scc14:      move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #2,d0                     ; Tx Buffer Empty?
            beq.s     scc15
            bsr.s     wr_scc
scc15:      move.b    #0,(a2)                   ; select scc register #0
            move.b    #$38,(a2)                 ; write "Reset Highest IUS Command" - clear pending interrupt
            movem.l   (sp)+,d0-d1/a0-a2
            rte

scc16:      move.b    #1,(a2)                   ; select scc register #1
            move.b    (a2),d0                   ; read special receive condition status bits
            move.b    #8,(a2)                   ; select scc register #8
            move.b    (a2),d0                   ; read "Receive Data register"
            move.b    #0,(a2)                   ; select scc register #0
            move.b    #$30,(a2)                 ; write "Error Reset Command" - clear error condition
            bra.s     scc15

wr_scc:     move.l    a0,-(sp)
            tst.b     $21(a0)
            bne.s     scc19
IF ROM_STBOOK
            btst      #0,$20(a0)
            beq.s     scc18
            tst.b     $1f(a0)
            bne.s     scc22
scc18:      btst      #1,$20(a0)
            beq.s     scc19
            move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #5,d0                     ; CTS?
            beq.s     scc22
ELSE
            move.b    $20(a0),d0
            and.b     $1f(a0),d0
            bne.s     scc2
ENDIF
scc19:      move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #2,d0                     ; Tx Buffer Empty?
            beq.s     scc22
            move.b    $21(a0),d0
            beq.s     scc20
            clr.b     $21(a0)
            bra.s     scc21
scc20:      adda.w    #$e,a0
            move.w    6(a0),d0
            cmp.w     8(a0),d0
            beq.s     scc22
            bsr       rs232get
scc21:      move.b    #8,(a2)                   ; select scc register #8
            move.b    d0,(a2)                   ; write transmit buffer
scc22:      movea.l   (sp)+,a0
            rts

scc23:      move.b    #0,(a2)                   ; select scc register #0
            move.b    #$28,(a2)                 ; write "Reset Tx Int Pending"
            move.b    #0,(a2)                   ; select scc register #0
            move.b    #$38,(a2)                 ; write "Reset Highest IUS Command" - clear pending interrupt
            bsr.s     wr_scc
            movem.l   (sp)+,d0-d1/a0-a2
            rte

scc24:      btst      #1,$20(a0)
            beq.s     scc25
            move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #5,d0                     ; CTS?
            seq       $1f(a0)
            beq.s     scc25
IF ROM_STBOOK
            bsr       wr_scc
ELSE
            bsr.s     wr_scc
ENDIF
scc25:      move.b    #0,(a2)                   ; select scc register #0
            move.b    #$10,(a2)                 ; write "Reset Ext/Status Interrupts"
            move.b    #0,(a2)                   ; select scc register #0
            move.b    #$38,(a2)                 ; write "Reset Highest IUS Command" - clear pending interrupt
            movem.l   (sp)+,d0-d1/a0-a2
            rte


**************************************************************************
*                                                                        *
*                  scc BIOS callbacks                                    *
*                                                                        *
**************************************************************************
sccbistat:  lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra.s     sccistat
sccbin:     lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra.s     sccin
sccbostat:  lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra.s     _sccostat
sccbout:    lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra       _sccout
sccaistat:  lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     sccistat
sccain:     lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     sccin
sccaostat:  lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     _sccostat
sccaout:    lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     _sccout

**************************************************************************
*                                                                        *
*                  scc port input status routine                         *
*                                                                        *
**************************************************************************
sccistat:   moveq     #0,d0                     ; set result to false
            lea       6(a0),a1                  ; head
            lea       8(a0),a0                  ; tail
            cmpm.w    (a0)+,(a1)+               ; atomic buffer empty test
            beq.s     sccist1
            moveq     #-1,d0                    ; set result to true
sccist1:    rts


**************************************************************************
*                                                                        *
*                  scc port output status routine                        *
*                                                                        *
**************************************************************************
_sccostat:  move.w    8(a0),d1                  ; tail
            bsr       wrapin
            cmp.w     6(a0),d1                  ; head
            beq.s     _sccost1
            moveq     #-1,d0                    ; set result to true
            rts

_sccost1:   moveq     #0,d0                     ; set result to false
            rts


**************************************************************************
*                                                                        *
*                  scc input routine                                     *
*                                                                        *
**************************************************************************
sccin:      bsr       rs232get
            move.w    d0,-(sp)
            tst.b     $20(a0)                   ; flow control active?
            beq.s     sccinrts                  ; (no)
            tst.b     $1e(a0)                   ; high-water flag already set?
            beq.s     sccinrts                  ; no...exit...
            move.w    8(a0),d0                  ; tail
            sub.w     6(a0),d0                  ; - head
            bpl.s     sccin2                    ; underflow?
            add.w     4(a0),d0                  ; + size
sccin2:     cmp.w     $a(a0),d0
            bgt.s     sccinrts
            bsr.s     sccinclrhw
sccinrts:   move.w    (sp)+,d0
            rts

sccinclrhw: clr.b     $1e(a0)                   ; clear high-water flag
            btst      #0,$20(a0)                ; is the rs232 mode xon/xoff?
            beq.s     sccinclrhw2
            move.b    #$11,$21(a0)              ; "xon"
            bra.s     _sccoutwr
sccinclrhw2:move.b    $1d(a0),d0
            bset      #1,d0                     ; set RTS
            move.b    d0,$1d(a0)
            move.b    #5,(a2)                   ; select scc register #5
            move.b    d0,(a2)                   ; write "Transmit Parameters and Controls" register
            rts

**************************************************************************
*                                                                        *
*                  scc output routine                                    *
*                                                                        *
**************************************************************************
_sccout:    move.w    6(sp),d0                  ; get data
            adda.w    #$e,a0
            bsr       rs232put                  ; exit via rs-232 output routine
            suba.w    #$e,a0
_sccoutwr:  move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #2,d0                     ; Tx Buffer Empty?
            beq.s     _sccoutrts                ; not ready
            move      sr,-(sp)
            ori       #$700,sr
            bsr       wr_scc
            move      (sp)+,sr
_sccoutrts: rts

**************************************************************************
*                                                                        *
*                  scc rsconf() routine                                  *
*                                                                        *
**************************************************************************
sccarsconf: lea       (sccabufrec).w,a0         ; point to current output buffer record
            lea       (sccctl_a).w,a2
            bra.s     sccrsconf
sccbrsconf: lea       (sccbbufrec).w,a0         ; point to current output buffer record
            lea       (sccctl_b).w,a2

sccrsconf:  moveq     #0,d0
            cmpi.w    #-2,4(sp)                 ; -2 = return last baudrate
            bne.s     sccrsconf2
            move.b    $22(a0),d0                ; last baudrate
            rts

sccrsconf2: ori       #$700,sr                  ; no interrupts for now
*
*      first we grab the old ucs,rsr,tsr,scr contents
*
            moveq     #0,d7
            move.b    $1c(a0),d7
            asl.w     #8,d7
            swap      d7
            move.b    $1d(a0),d7
            lsr.b     #1,d7
            and.b     #4,d7
            asl.w     #8,d7
*
*      set flow control mode(s)
*
            move.w    6(sp),d0                  ; if -1 then don't change
            cmp.w     #3,d0
            bhi.s     sccrsconf6
            bne.s     sccrsconf3
            moveq     #1,d0                     ; set flag for handshake
sccrsconf3: cmp.b     $20(a0),d0                ; state unchanged? => continue
            beq.s     sccrsconf6
            tst.b     $1f(a0)                   ; xon flag set?
            beq.s     sccrsconf4
            clr.b     $1f(a0)                   ; clear xon flag
            bsr       wr_scc
sccrsconf4: tst.b     $1e(a0)                   ; RTS set?
            beq.s     sccrsconf5                ; no...
            move.w    d0,-(sp)                  ; save new handshake state
            bsr       sccinclrhw                ; reenable RTS
            move.w    (sp)+,d0                  ; new handshake state
sccrsconf5: move.b    d0,$20(a0)                ; set new handshake state
*
*      set timer baud rate
*
sccrsconf6: move.w    4(sp),d0                  ; new baudrate
            cmp.w     #$f,d0                    ; if -1 (out of range 0..15) then don't change
            bhi.s     sccrsconf7
            move.b    d0,$22(a0)                ; new baudrate
            asl.w     #1,d0
            lea       (sccbaudtab).l,a1
            move.w    (a1,d0.w),d0
            move.b    #$c,(a2)                  ; select scc register #12
            move.b    d0,(a2)                   ; write "Lower Byte of Baud Rate Generator Time Constant" register
            lsr.w     #8,d0
            move.b    #$d,(a2)                  ; select scc register #13
            move.b    d0,(a2)                   ; write "Upper Byte of Baud Rate Generator Time Constant" register
*      set rs-232 registers

sccrsconf7: move.w    8(sp),d0                  ; if -1 then don't change
            bmi.s     sccrsconf12
            move.b    d0,$1c(a0)
            move.b    d0,d1
            and.b     #$60,d1
            lsr.b     #5,d1
            moveq     #$ff,d2
            lsr.b     d1,d2
            move.b    d2,$23(a0)
            move.b    d0,d1
            and.b     #$60,d1
            beq.s     sccrsconf8
            cmp.b     #$60,d1
            bne.s     sccrsconf9
sccrsconf8: eori.b    #$60,d1
sccrsconf9: move.b    $1d(a0),d2
            and.b     #$9f,d2
            or.b      d1,d2
            move.b    d2,$1d(a0)
            move.b    #5,(a2)                   ; select scc register #5
            move.b    d2,(a2)                   ; write "Transmit Parameters and Controls" register
            asl.b     #1,d1
            or.b      #1,d1                     ; "Rx Enable"
            move.b    #3,(a2)                   ; select scc register #3
            move.b    d1,(a2)                   ; write "Receive Parameters and Control" register
            move.b    d0,d1
            and.b     #$1e,d1
            lsr.b     #1,d1                     ; mask stop bits (bit 2..3) and parity (bit 0..1)
            bclr      #1,d1
            sne       d2
            bclr      #0,d1                     ; disable parity
            bne.s     sccrsconf10
            bclr      #1,d2                     ; "odd parity"
            bra.s     sccrsconf11
sccrsconf10:bset      #1,d2                     ; "even parity"
sccrsconf11:and.b     #3,d2                     ; mask parity bits
            or.b      d2,d1
            or.b      #$40,d1                   ; "X16 Clock Mode"
            move.b    #4,(a2)                   ; select scc register #4
            move.b    d1,(a2)                   ; write "Transmit/Receive Mis- cellaneous Parameters and Modes" register

sccrsconf12:
IF ROM_TOS306
            move.w    $a(sp),d0                 ; if -1 then don't change
ELSE
            move.w    $c(sp),d0                 ; if -1 then don't change
ENDIF
            bmi.s     sccrsconf14
            btst      #3,d0
            beq.s     sccrsconf13
            bset      #4,$1d(a0)                ; set "Tx Enable"
            bne.s     sccrsconf14
            move.b    #5,(a2)                   ; select scc register #5
            move.b    $1d(a0),(a2)              ; write "Transmit Parameters and Controls" register
            bra.s     sccrsconf14
sccrsconf13:bclr      #4,$1d(a0)                ; clear "Tx Enable"
            beq.s     sccrsconf14
            move.b    #5,(a2)                   ; select scc register #5
            move.b    $1d(a0),(a2)              ; write "Transmit Parameters and Controls" register
sccrsconf14:move.l    d7,d0                     ; move old contents of rs-232 registers to d0.l
            rts

sccbaudtab: DC.W      $000b,$0018,$0032,$0044
            DC.W      $0067,$007c,$008a,$00d0
            DC.W      $01a1,$0345,$04e8,$068c
            DC.W      $074d,$08ee,$0d1a,$13a8
sccvect:    DC.L      sccvec_portbtrnbempty
            DC.L      $000000
            DC.L      sccvec_portbextstchg
            DC.L      $000000
            DC.L      sccvec_portbrecchr
            DC.L      $000000
            DC.L      sccvec_portbrecchr
            DC.L      $000000
            DC.L      sccvec_portatrnbempty
            DC.L      $000000
            DC.L      sccvec_portaextstchg
            DC.L      $000000
            DC.L      sccvec_portarecchr
            DC.L      $000000
            DC.L      sccvec_portarecchr
            DC.L      $000000
sccbufinit: DC.L      $001210                   ; ibufptr
            DC.W      $0100                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0080                     ; ibuflow
            DC.W      $00c0                     ; ibufhigh
            DC.L      $001310                   ; obufptr
            DC.W      $0100                     ; obufsiz
            DC.W      $0000                     ; obufhead
            DC.W      $0000                     ; obuftail
            DC.W      $0080                     ; obuflow
            DC.W      $00c0                     ; obufhigh
            DC.B      $08                       ; rsrbyte
            DC.B      $ea                       ; tsrbyte
            DC.B      $00                       ; rxoff
            DC.B      $00                       ; txoff
            DC.B      $01                       ; rsmode_xon
            DC.B      $00                       ; rsmode_filler
            DC.W      $01ff

*************************************************************************
*       this code handles the midi/keyboard interrupt exception         *
*************************************************************************
midikey:    movem.l   d0-d3/a0-a3,-(sp)         ; save all registers
keymidi:    movea.l   (astatusmidip).w,a2
            jsr       (a2)
            movea.l   (astatuskeyp).w,a2
            jsr       (a2)
            btst      #4,(gpip).w               ; check for pending interrupt occurance
            beq.s     keymidi                   ; repeat this interrupt processing
            move.b    #$bf,(isrb).w             ; clear in-service bit
            movem.l   (sp)+,d0-d3/a0-a3         ; restore all registers
            rte                                 ; go back to what was happening!

astatusmidi:lea       (mbufrec).w,a0
            lea       (midictl).w,a1
            movea.l   (vmiderr).w,a2
            bra.s     astatus2
astatuskeyboard:lea   (kbufrec).w,a0
            lea       (keyctl).w,a1
            movea.l   (vkbderr).w,a2
astatus2:   move.b    (a1),d2                   ; grab device status
            btst      #7,d2                     ; make sure it was an interrupt request
            beq.s     aciaexit                  ; nope..it's empty
            btst      #0,d2                     ; see if receiver buffer is full
            beq.s     mk1                       ; nope..it's empty
            movem.l   d2/a0-a2,-(sp)
            bsr.s     arcvrint                  ; yes...get byte
            movem.l   (sp)+,d2/a0-a2
mk1:        andi.b    #$20,d2                   ; mask off bits already tested
            beq.s     aciaexit                  ; see if any other status bits are on
            move.b    2(a1),d0                  ; grab data byte from acia data register
            jmp       (a2)                      ; yes to branch to pre-inited error subroutine

aciaexit:   rts

*************************************************************************
*       acia receiver buffer full interrupt routine                     *
*************************************************************************

arcvrint:   move.b    2(a1),d0                  ; grab data byte from acia data register
            cmpa.l    #kbufrec,a0
            bne       midibyte                  ; don't treat midi acia data as anything other than as pure data...
            tst.b     (kstate).w
            bne.s     ML3
            cmpi.b    #$f6,d0
            bcc.s     itsnotakey                ; is it a ikbd header?
            move.l    (itsakeyp).w,-(sp)        ; jump into ikbd processing function
            rts

itsnotakey: subi.b    #$f6,d0                   ; generate true index into tables now
            andi.l    #$ff,d0                   ; clear high 3 bytes for indexing
            lea       (ikbdev).l,a3             ; point to ikbd device state codes
            move.b    (a3,d0.w),(kstate).w      ; set ikbd state
            lea       (ikbdlen).l,a3            ; point to ikbd device buffer length table
            move.b    (a3,d0.w),(kindex).w      ; set ikbd device index counter
            addi.w    #$f6,d0                   ; re-constitute original value
            cmpi.b    #$f8,d0                   ; mouse position record?
            blt.s     ML8                       ; no
            cmpi.b    #$fb,d0                   ; mouse position record?
            bgt.s     ML8                       ; no
            move.b    d0,(mousebuf).w           ; store mouse byte
            rts

ML8:        cmpi.b    #$fd,d0                   ; joystick record?
            blt.s     ML8b                      ; no
            move.b    d0,(joyrec).w             ; store joystick byte
ML8b:       rts

ikbdev:     DC.B      $01,$02,$03,$03,$03,$03,$04,$05
            DC.B      $06,$07
ikbdlen:    DC.B      $07,$05,$02,$02,$02,$02,$06,$02
            DC.B      $01,$01

ML3:        cmpi.b    #6,(kstate).w
            bcc       ML35                      ; a joystick 0/1 record byte, not both!
            lea       (ikbdparams).l,a2         ; point to ikbd subsystem parameters table
            moveq     #0,d2
            move.b    (kstate).w,d2             ; load to generate longword offset
            subq.b    #1,d2                     ; d2 = (kstate-1) * 12
            asl.w     #1,d2
            add.b     (kstate).w,d2
            subq.b    #1,d2
            asl.w     #2,d2
            movea.l   (a2,d2.w),a0              ; load in subsystem's record pointer
            movea.l   4(a2,d2.w),a1             ; load in subsystem's index base+record pointer
            movea.l   8(a2,d2.w),a2             ; load in subsystem's pointer variable that contains the pointer to the subsystem's interrupt routine
            movea.l   (a2),a2                   ; get the address of the interrupt routine
            moveq     #0,d2                     ; clear out 'd2' for address manipulation
            move.b    (kindex).w,d2
            suba.l    d2,a1
            move.b    d0,(a1)                   ; store byte into the subsystems buffer
            subq.b    #1,(kindex).w             ; decrement the number of bytes in the package
            tst.b     (kindex).w                ; package received?
            bne.s     ML1                       ; not yet...
ikserve:    move.l    a0,-(sp)                  ; stuff buffer pointer to stack
            jsr       (a2)                      ; go service the subsystem interrupt routine
            addq.w    #4,sp                     ; re-adjust stack
            clr.b     (kstate).w                ; reset ikbd state
ML1:        rts

ikbdparams: DC.L      statrec
            DC.L      amrec
            DC.L      statintvec

            DC.L      amrec
            DC.L      mousebuf
            DC.L      msintvec

            DC.L      mousebuf
            DC.L      clkrec
            DC.L      msintvec

            DC.L      clkrec
            DC.L      joyrec
            DC.L      clkintvec

            DC.L      joyrec
            DC.L      joyrec+2
            DC.L      joyintvec

ML35:       move.l    #joyrec+1,d1
            add.b     (kstate).w,d1             ; kstate reflects joy0 or joy1 state
            subq.b    #6,d1
            movea.l   d1,a2                     ; create index to joyrec table for record byte
            move.b    d0,(a2)
            movea.l   (joyintvec).w,a2          ; get user's joystick interrupt routine adr
            lea       (joyrec).w,a0             ; send along address of joystick data
            bra.s     ikserve

itsakey:    move.b    (kb_shift).w,d1           ; load in kbshift for manipulation
* check the special keys
            cmpi.b    #$2a,d0                   ; left shift?
            bne.s     ari2
            bset      #1,d1
            bra       ari10
ari2:       cmpi.b    #$aa,d0
            bne.s     ari3
            bclr      #1,d1
            bra       ari10
ari3:       cmpi.b    #$36,d0                   ; right shift?
            bne.s     ari4
            bset      #0,d1
            bra.s     ari10
ari4:       cmpi.b    #$b6,d0
            bne.s     ari5
            bclr      #0,d1
            bra.s     ari10
ari5:       cmpi.b    #$1d,d0                   ; CTRL
            bne.s     ari6
            bset      #2,d1
            bra.s     ari10
ari6:       cmpi.b    #$9d,d0
            bne.s     ari7
            bclr      #2,d1
            bra.s     ari10
ari7:       cmpi.b    #$38,d0                   ; ALT
            bne.s     ari8
            bset      #3,d1
            bra.s     ari10
ari8:       cmpi.b    #$b8,d0
            bne.s     ari9
            bclr      #3,d1

            tst.w     (altdigits).l             ; ascii code via numpad active?
            bmi.s     ari10                     ; no...
            move.b    d1,(kb_shift).w           ; store shift status
            move.l    a0,-(sp)
            moveq     #0,d1                     ; no scancode
            move.w    d1,d0                     ; clear ascii code reg
            move.b    (altdigits+1).l,d0        ; take entered ascii code
            move.w    #-1,(altdigits).l         ; reset alt-numpad ascii entering
            bra       conin25

ari9:       cmpi.b    #$3a,d0                   ; CAPS LOCK
            bne.s     ari11
            btst      #0,(conterm).w
            beq.s     ari9a                     ; no click please!
            movem.l   d0-d2/a0-a2,-(sp)
            movea.l   (kcl_hook).w,a0
            jsr       (a0)
            movem.l   (sp)+,d0-d2/a0-a2
ari9a:      bchg      #4,d1                     ; toggle CAPS LOCK state
ari10:      move.b    d1,(kb_shift).w           ; restore new kbshift value
            rts                                 ; ignore CAPS LOCK break


ari11:      btst      #7,d0                     ; is it a break code?
            bne.s     ari12                     ; no... a break code!
            move.b    d0,(keyrep).w             ; save for repeat purposes
            move.b    (cdelay1).l,(keydelay1).w
            move.b    (cdelay2).l,(keydelay2).w
            bra.s     ari16
ari12:      move.b    d0,d1
            bclr      #7,d1
            cmp.b     (keyrep).w,d1
            bne.s     ari15
            moveq     #0,d1
            move.b    d1,(keyrep).w
            move.b    d1,(keydelay1).w
            move.b    d1,(keydelay2).w

ari15:      cmpi.b    #$c7,d0                   ; is it a "home" break-code?
            beq.s     ari18a                    ; yes...allow it to pass
            cmpi.b    #$d2,d0                   ; is it an "insert" break-code?
            bne       ari14                     ; no...regular break junk...
ari18a:     btst      #3,(kb_shift).w           ; early "ALT" test to prevent double "nulls"
            beq       ari14                     ; no ALT...so exit now...
ari16:      btst      #0,(conterm).w
            beq.s     ari16a                    ; no click please!
            movem.l   d0-d2/a0-a2,-(sp)
            movea.l   (kcl_hook).w,a1
            jsr       (a1)
            movem.l   (sp)+,d0-d2/a0-a2
ari16a:     move.l    a0,-(sp)                  ; store kbufrec pointer

            moveq     #0,d1
            move.b    d0,d1

default: use regular ascii table:movea.l (skeytran).w,a0
            andi.w    #$7f,d0
            btst      #4,(kb_shift).w           ; caps-lock pressed?
            beq.s     conin21
            movea.l   (skeycl).w,a0             ; use caps-lock table
conin21:    btst      #0,(kb_shift).w
            bne.s     conin22
            btst      #1,(kb_shift).w
            beq.s     conin23
conin22:    cmpi.b    #$3b,d0                   ; see if a possible function key
            bcs.s     conin22a                  ; unsigned less than lowest function scancode
            cmpi.b    #$44,d0                   ; see if a possible function key
            bhi.s     conin22a                  ; unsigned greater than highest function scan
            addi.w    #$19,d1                   ; add to change to GSX standard
            moveq     #0,d0                     ; change to GSX standard
            bra       conin25

conin22a:   movea.l   (skeyshif).w,a0           ; use the shift table
conin23:    move.b    (a0,d0.w),d0
            btst      #2,(kb_shift).w           ; is the control key down?
            beq.s     conin24a
            cmpi.b    #$d,d0                    ; is it a carriage return?
            bne.s     conin23a
            moveq     #$a,d0                    ; change to a linefeed according to GSX spec...
            beq.s     conin24x
conin23a:   cmpi.b    #$47,d1                   ; convert CONTROL-home to gsx standard
            bne.s     conin23b
            addi.w    #$30,d1                   ; by adding #$30...
            bra       conin25
conin23b:   cmpi.b    #$4b,d1                   ; convert CONTROL-left arrow to gsx standard
            bne.s     conin23c
            moveq     #$73,d1                   ; change according to gsx spec
            moveq     #0,d0
            bra       conin25
conin23c:   cmpi.b    #$4d,d1                   ; convert CONTROL-right arrow to gsx standard
            bne.s     conin24x
            moveq     #$74,d1                   ; change according to gsx spec
            moveq     #0,d0
            bra       conin25

conin24x:   cmpi.b    #$32,d0                   ; Control-M?
            bne.s     conin240
            moveq     #0,d0                     ; ascii code 0
            bra       conin25
conin240:   cmpi.b    #$36,d0                   ; Control-Shift-Right?
            bne.s     conin241
            moveq     #$1e,d0                   ; ascii code RS
            bra       conin25
conin241:   cmpi.b    #$2d,d0                   ; Control-X?
            bne.s     conin24a
            moveq     #$1f,d0                   ; ascii code US
            bra       conin25

* this routine allows entering any ascii code by using the numpad
* and holding down ALT during it.
conin24a:   btst      #3,(kb_shift).w           ; is the alt key down?
            beq       conin2x
            cmp.b     #$67,d1                   ; key 0 ... 9 on the number pad?
            bcs.s     outside                   ; no...
            cmp.b     #$70,d1
            bhi.s     outside                   ; no...
            move.w    (altdigits).l,d0          ; already entering an ascii code?
            bpl.s     conin24b                  ; yes
            moveq     #0,d0                     ; start with 0
conin24b:   mulu.w    #10,d0                    ; previous digit * 10
            ext.w     d1
            move.b    (a0,d1.w),d1              ; ascii code of current digit
            sub.b     #'0',d1                   ; convert into number
            add.b     d1,d0                     ; plus old number
            move.w    d0,(altdigits).l          ; store as new number code
            movea.l   (sp)+,a0
            rts

outside:    cmpi.b    #$62,d1                   ; is it an "alt help" signal to dump the screen?
            bne.s     alt15a                    ; no...
            addq.w    #1,(_prtcnt).w            ; yes...switch the signal flag on!...
            movea.l   (sp)+,a0                  ; restore kbufrec pointer
            bra       ari14                     ; ...and exit
*
*       check the alt-insert/alt-home key make/break combinations, first
*
alt15a:     lea       (mousekey1).l,a2          ; get pointer to first alt. mouse scancode table
            moveq     #3,d2                     ; create countdown
mkloop1:    cmp.b     (a2,d2.w),d1              ; is table's scancode value = current value?
            beq       keymaus1                  ; yes...go preprocess it...
            dbra      d2,mkloop1                ; no...loop back to check next table value

            cmpi.b    #$48,d1                   ; is it an up arrow?
            bne.s     alt11
            move.b    #0,d1                     ; x value for up arrow
            move.b    #$f8,d2                   ; y value for up arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #$ff,d2                   ; y value for up arrow
            bra       keymaus
alt11:      cmpi.b    #$4b,d1                   ; is it an left arrow?
            bne.s     alt12
            move.b    #0,d2                     ; x value for left arrow
            move.b    #$f8,d1                   ; y value for left arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #$ff,d1                   ; x value for left arrow
            bra       keymaus
alt12:      cmpi.b    #$4d,d1                   ; is it an right arrow?
            bne.s     alt13
            move.b    #8,d1                     ; x value for right arrow
            move.b    #0,d2                     ; y value for right arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #1,d1                     ; x value for right arrow
            bra       keymaus
alt13:      cmpi.b    #$50,d1                   ; is it an down arrow?
            bne.s     alt14
            move.b    #0,d1                     ; x value for down arrow
            move.b    #8,d2                     ; y value for down arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #1,d2                     ; y value for down arrow
            bra       keymaus
alt14:      btst      #2,(kb_shift).w           ; CTRL key?
            bne.s     conin24                   ; yes
            cmpi.b    #2,d1
            bcs.s     alt1                      ; not >= the '1' key scancode
            cmpi.b    #$d,d1
            bhi.s     alt1                      ; not <= the '=' key scancode
            addi.b    #$76,d1                   ; scancode is a key between '1' key and '=' key
            bra.s     alt2
alt1:       cmpi.b    #$41,d0                   ; is the key an ascii 'A' or greater?
            bcs.s     alt3                      ; no skip to check if 'a'-'z'...
            cmpi.b    #$5a,d0                   ; is the key an ascii 'Z' or less?
            bhi.s     alt3                      ; no skip to check if 'a'-'z'...
alt2:       moveq     #0,d0
            bra.s     conin25
alt3:       cmpi.b    #$61,d0                   ; is the key an ascii 'a' or greater?
            bcs.s     conin25                   ; not...skip to finish normal processing
            cmpi.b    #$7a,d0                   ; is the key an ascii 'z' or less?
            bhi.s     conin25                   ; not...skip to finish normal processing
            bra.s     alt2

conin2x:    btst      #2,(kb_shift).w           ; CTRL key?
            beq.s     conin25                   ; no
conin24:    andi.w    #$1f,d0                   ; yep, so CTRLize the key
conin25:    asl.w     #8,d1                     ; shift the scan code to the word's high byte
            add.w     d1,d0                     ; form the outgoing word

            movea.l   (sp)+,a0                  ; restore kbufrec pointer

            move.w    8(a0),d1                  ; get current tail pointer offset
            addq.w    #4,d1                     ; index = tail + 4
            cmp.w     4(a0),d1                  ; check to see if buffer should wrap
            bcs.s     ari13                     ; no...
            moveq     #0,d1                     ; wrap pointer
ari13:      cmp.w     6(a0),d1                  ; head=tail?
            beq.s     ari14                     ; yes
            movea.l   (a0),a2                   ; get buffer pointer

* move kb_shift into the upper 8 bits of the keycode
            swap      d0
            clr.w     d0
            move.b    (kb_shift).w,d0
            swap      d0
            lsl.l     #8,d0
            lsr.w     #8,d0

            move.l    d0,d2                     ; save keycode for our reset key commands
            bclr      #$1c,d2                   ; clear capslock state
            swap      d2                        ; isolate kb_shift & scancode
            cmp.w     #$c53,d2                  ; control+alt+delete?
            beq       reseth                    ; => reset
            cmp.w     #$d53,d2                  ; control+alt+rshift+delete?
            beq       _coldboot                 ; => cold boot by erasing all memory

            btst      #3,(conterm).w            ; should Bconin() to return the shift status?
            bne.s     ari13b                    ; yes...
            andi.l    #$ffffff,d0               ; no, filter it out (default case)
ari13b:     and.l     #$ffff,d1
            move.l    d0,(a2,d1.l)              ; store the data
            move.w    d1,8(a0)                  ; store the new buftail pointer
ari14:      rts

keyclicksnd:move.l    #gkeyclicksnd,(cursnd).w  ; sound data for key click
            move.b    #0,(timer).w              ; enable sound timer
            rts


midibyte:   movea.l   (midivec).w,a2            ; get contents of midivec for indirect branch
            jmp       (a2)                      ; jump to midi interrupt handler


sysmidi:    move.w    8(a0),d1                  ; get current tail pointer offset
            addq.w    #1,d1                     ; index = tail + 1
            cmp.w     4(a0),d1                  ; check to see if buffer should wrap
            bcs.s     mi13                      ; no...
            moveq     #0,d1                     ; wrap pointer
mi13:       cmp.w     6(a0),d1                  ; head=tail?
            beq.s     mi14                      ; yes
            movea.l   (a0),a2                   ; get buffer pointer
            and.l     #$ffff,d1
            move.b    d0,(a2,d1.l)              ; store the data
            move.w    d1,8(a0)                  ; store the new buftail pointer
mi14:       rts


keymaus1:   moveq     #5,d3                     ; pre-init to "keyboard" right mouse button
            btst      #4,d1                     ; see if it is a left or right button...
            beq.s     keym1                     ; it's a right button ($47/$c7)
            moveq     #6,d3                     ; it's a left button ($52/$d2)
keym1:      btst      #7,d1                     ; see if it is a make or break action
            beq.s     keym2                     ; it's a set button action (make code)
            bclr      d3,(kb_shift).w           ; it's a clear button action (break code)
            bra.s     keym3                     ; go to further pre-init action...
keym2:      bset      d3,(kb_shift).w           ; it's a set button action (set code)
keym3:      moveq     #0,d1
            moveq     #0,d2
*
* finish up at the actual pseudo mouse routine
*

keymaus:    lea       (kmbuf).w,a0              ; point to key-emulating mouse buffer
            movea.l   (msintvec).w,a2           ; grab mouse interrupt vector
            clr.l     d0
            move.b    (kb_shift).w,d0           ; get current button status
            lsr.b     #5,d0                     ; shift right button bit to 'd0'
            addi.b    #$f8,d0                   ; add relative mouse header
            move.b    d0,(a0)                   ; store in first byte of record header
            move.b    d1,1(a0)                  ; store x value in second byte of record buffer
            move.b    d2,2(a0)                  ; store y value in third byte of record buffer
            jsr       (a2)
            movea.l   (sp)+,a0                  ; restore kbufrec pointer
            rts


mousekey1:  DC.B      $47,$c7,$52,$d2

*************************************************************************
*                                                                       *
*************************************************************************
_coldboot:  move      #$2700,sr                 ; disable all IRQSR
IF ROM_STBOOK
            move.l    ($0004).w,(busexception).w ;a bus error triggers a reset
            movea.w   #addrexception,a0         ; start erasing from $c on
            moveq     #0,d0
            move.l    #$3fffc,d1                ; erase 1MB
_coldboot2: move.l    d0,(a0)+
            dbra      d1,_coldboot2
            movea.l   ($0004).w,a0              ; jump into the ROM to reset the system
            jmp       (a0)

ELIF ROM_TOS206
            move.w    #5,d0
            lea       _coldbootr(pc),a0
            movea.w   #addrexception,a1
_coldboot1: move.l    (a0)+,(a1)+               ; copy erase routine to $000c...
            dbra      d0,_coldboot1
            jmp       (addrexception).w

_coldbootr: move.l    ($0004).w,(busexception).w
            lea       _coldbootre(pc),a0
            moveq     #0,d0
_coldbootl: move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            bra.s     _coldbootl
_coldbootre:nop

ELIF ROM_TOS306
            move.l    #$808,d0
            movec     d0,cacr

            moveq     #0,d0
            movec     d0,vbr                    ; set vector base to address 0
            pmove     ($e36738).l,tc            ; 0L - disable PMMU
            pmove     ($e36738).l,tt0           ; 0L
            pmove     ($e36738).l,tt1           ; 0L

            move.w    #11,d0
            lea       _coldbootr(pc),a0
            movea.w   #addrexception,a1
_coldboot1: move.l    (a0)+,(a1)+               ; copy erase routine to $000c...
            dbra      d0,_coldboot1
            jmp       (addrexception).w

_coldbootr: lea       _coldboott(pc),a0
            move.l    a0,(busexception).w
            lea       _coldbootre(pc),a0
            moveq     #0,d0
_coldbootl: move.l    d0,(a0)+                  ; erase ST RAM
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            bra.s     _coldbootl
_coldboott: move.l    ($0004).w,(busexception).w
            lea       ($1000000).l,a0
_coldbootfl:move.l    d0,(a0)+                  ; erase fast RAM
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            bra.s     _coldbootfl
_coldbootre:nop

ENDIF

*************************************************************************
*                                                                       *
*       protocol for accessing a gi sound chip register                 *
*                                                                       *
*       this bios call must be accessed in supervisor state             *
*       because it affects the 'sr' register                            *
*                                                                       *
*       entry                                                           *
*                                                                       *
*       word    giaccess(data,register)                                 *
*       word    data,register                                           *
*                                                                       *
*               data -- data register read/write date                   *
*               register -- chip register to select                     *
*               d1 = #$0000     ;selects read operation of the register *
*               d1 = #$80 .or .xx       ;selects write xx to register   *
*               example write to portb - $80 .or. $0f = $8f             *
*                                                                       *
*       exit                                                            *
*       read operations                                                 *
*       d0.b -- data register contains byte of date                     *
*       write operations                                                *
*       d0.b -- data register contains a verification of written data   *
*                                                                       *
*************************************************************************
giaccess:   move.w    4(sp),d0
            move.w    6(sp),d1
gientry:    move      sr,-(sp)
            ori       #$700,sr
            movem.l   d1-d2/a0,-(sp)            ; save affected registers
            lea       (psgsel).w,a0             ; init desired gi register addr
            move.b    d1,d2                     ; make a copy to test for read or write
            andi.b    #$f,d1                    ; turn off any extraneous bits
            move.b    d1,(a0)                   ; select register
            asl.b     #1,d2                     ; shift once for carry bit detection
            bcc.s     giread                    ; carry clear, so do a read operation
giwrit:     move.b    d0,2(a0)                  ; init the memory location
giread:     moveq     #0,d0                     ; clear our register
            move.b    (a0),d0                   ; grab the data from the gi register
            movem.l   (sp)+,d1-d2/a0            ; restore affected registers
            move      (sp)+,sr
            rts


*********************************************************************
*       routine to turn on the dtr signal                           *
*********************************************************************

dtron:      move.b    #$ef,d2
            bra.s     offbit

*********************************************************************
*                                                                   *
*       routine to set any bit in the gi port a area                *
*                                                                   *
*       entry                                                       *
*                                                                   *
*       word    ongibit(bitnum)                                     *
*       word    bitnum                                              *
*                                                                   *
*               bitnum - byte size mask with desired bit set to "1" *
*                                                                   *
*********************************************************************

ongibit:    moveq     #0,d2
            move.w    4(sp),d2
onbit:      movem.l   d0-d2,-(sp)
            move      sr,-(sp)
            ori       #$700,sr
            moveq     #14,d1                    ; get ready to read in the port a contents
            move.l    d2,-(sp)
            bsr.s     gientry                   ; go get it...
            move.l    (sp)+,d2
            or.b      d2,d0                     ; set bit(s) on
            move.b    #$8e,d1                   ; setup to write to port a
            bsr.s     gientry                   ; go set it and return
            move      (sp)+,sr
            movem.l   (sp)+,d0-d2
            rts


*********************************************************************
*                                                                   *
*       routine to clear any bit in the gi port a area              *
*                                                                   *
*       entry                                                       *
*                                                                   *
*       word    offgibit(bitnum)                                    *
*       word    bitnum                                              *
*                                                                   *
*               bitnum - byte size mask with desired bit set to "0" *
*                                                                   *
*********************************************************************

offgibit:   moveq     #0,d2
            move.w    4(sp),d2
offbit:     movem.l   d0-d2,-(sp)
            move      sr,-(sp)
            ori       #$700,sr
            moveq     #14,d1                    ; get ready to read in the port a contents
            move.l    d2,-(sp)
            bsr.s     gientry                   ; go get it...
            move.l    (sp)+,d2
            and.b     d2,d0                     ; turn bit(s) off
            move.b    #$8e,d1                   ; setup to write to port a
            bsr.s     gientry                   ; go set it and return
            move      (sp)+,sr
            movem.l   (sp)+,d0-d2
            rts


*************************************************************************
*                                                                       *
*               EXTENDED RBP BIOS TIMER INIT CALL                       *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       word    initmous(type,param,intvec)                             *
*       word    type                                                    *
*       long    param,initvec                                           *
*                                                                       *
*               type - key/abs/rel/off  mouse function requested        *
*                       4/  2/  1/  0   value                           *
*               param - address of parameter block                      *
*               intvec - mouse interrupt vector                         *
*                                                                       *
*                                                                       *
*       parameter block definition:                                     *
*                                                                       *
*       byte 0 - y=0 at top/bottom; if non-zero then y=0 at bottom      *
*               otherwise y=0 at top                                    *
*       byte 1 - parameter for set mouse buttons command                *
*       byte 2 - x threshold/scale/delta parameter                      *
*       byte 3 - y threshold/scale/delta parameter                      *
*                                                                       *
*       the following bytes are required for the absolute mode only     *
*                                                                       *
*       byte 4 - xmsb for absolute mouse maximum position               *
*       byte 5 - xlsb for absolute mouse maximum position               *
*       byte 6 - ymsb for absolute mouse maximum position               *
*       byte 7 - ylsb for absolute mouse maximum position               *
*       byte 8 - xmsb for absolute mouse initial position               *
*       byte 9 - xlsb for absolute mouse initial position               *
*       byte a - ymsb for absolute mouse initial position               *
*       byte b - ylsb for absolute mouse initial position               *
*                                                                       *
*************************************************************************

* first we determine if the init is for a absolute, relative or keycode
* mouse action.

initmous:   tst.w     4(sp)                     ; turn mouse off?
            beq.s     im1                       ; yes...disable mouse
            move.l    $a(sp),(msintvec).w       ; init the mouse interrupt vector
            movea.l   6(sp),a3
            cmpi.w    #1,4(sp)                  ; relative mouse request?
            beq.s     im2                       ; yes...
            cmpi.w    #2,4(sp)                  ; absolute mouse request?
            beq.s     im3                       ; yes...
            cmpi.w    #4,4(sp)                  ; keycode mouse request?
            beq.s     im4                       ; yes...
            moveq     #0,d0                     ; error condition returned -- improper request
            rts

im1:        moveq     #$12,d1                   ; disable mouse
            bsr       ikbdput
            move.l    #xbtexit,(msintvec).w     ; re-init the mouse interrupt vector
            bra.s     imexit

im2:        lea       (transbuf).w,a2           ; set transfer buffer pointer
            move.b    #8,(a2)+                  ; set to relative mouse
            move.b    #$b,(a2)+                 ; set relative mouse threshold x,y
            bsr.s     setmouse
            moveq     #6,d3                     ; set length of the string -1 to transfer
            lea       (transbuf).w,a2           ; set transfer buffer pointer
            bsr       ikbdstr                   ; do transfer to ikbd
            bra.s     imexit

im3:        lea       (transbuf).w,a2           ; set transfer buffer pointer
            move.b    #9,(a2)+                  ; set to absolute mouse
            move.b    4(a3),(a2)+               ; set xmsb max
            move.b    5(a3),(a2)+               ; set xlsb max
            move.b    6(a3),(a2)+               ; set ymsb max
            move.b    7(a3),(a2)+               ; set ylsb max
            move.b    #$c,(a2)+                 ; set absolute mouse scale
            bsr.s     setmouse
            move.b    #$e,(a2)+                 ; load initial absolute mouse position
            move.b    #0,(a2)+                  ; filler load
            move.b    8(a3),(a2)+               ; initial xmsb absolute mouse position
            move.b    9(a3),(a2)+               ; initial xlsb absolute mouse position
            move.b    $a(a3),(a2)+              ; initial ymsb absolute mouse position
            move.b    $b(a3),(a2)+              ; initial ylsb absolute mouse position
            moveq     #$10,d3                   ; set length of string -1 to transfer
            lea       (transbuf).w,a2           ; set transfer buffer pointer
            bsr       ikbdstr                   ; do transfer to ikbd
            bra.s     imexit

im4:        lea       (transbuf).w,a2           ; set transfer buffer pointer
            move.b    #$a,(a2)+                 ; set to mouse keycode mode
            bsr.s     setmouse
            moveq     #5,d3                     ; set length of string -1 to transfer
            lea       (transbuf).w,a2           ; set transfer buffer pointer
            bsr       ikbdstr                   ; do transfer to ikbd
imexit:     moveq     #-1,d0                    ; set to true to indicate good init
            rts


setmouse:   move.b    2(a3),(a2)+               ; set x threshold/scale/delta
            move.b    3(a3),(a2)+               ; set y threshold/scale/delta
            moveq     #$10,d1                   ; setup to determine if top/bottom
            sub.b     (a3),d1                   ; set y=0 at ?
            move.b    d1,(a2)+
            move.b    #7,(a2)+                  ; set mouse button action
            move.b    1(a3),(a2)+               ; mouse button parameter
            rts


*************************************************************************
*                                                                       *
*               EXTENDED RBP BIOS TIMER INIT CALL                       *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       void    xbtimer(id,control,data,intvec)                         *
*       word    id,control,data                                         *
*       long    intvec                                                  *
*                                                                       *
*               intvec - timer interrupt vector                         *
*               control - timer's control setting                       *
*               data - timer's data register setting                    *
*               id - timer id   a-0, b-1, c-2, d-3                      *
*                                                                       *
*       Special Note:                                                   *
*                                                                       *
*       In the interest of preserving as many features for the user     *
*       in the future, timer A should be reserved for the end-user      *
*       or independent software vendor's application program.  System   *
*       software or those application needing just a "tick" should      *
*       constrain themselves to timer C, which is adequate for delay    *
*       and other timing uses.  Future hardware may or may not bring    *
*       out the timer A input line out...giving software developers     *
*       another useful aspect of the machine to utilize.                *
*                                                                       *
*       The recommended usage of the timers is as follows:              *
*                                                                       *
*       Timer A - Reserved for end-users and stand-alone applications.  *
*       Timer B - Reserved for screen graphics, primarily.              *
*       Timer C - Reserved for system timing (GSX,GEM,DESKTOP,ET.AL).   *
*       Timer D - Reserved for baud rate control of RS-232 port,        *
*                the interrupt vector is available to anyone.           *
*                                                                       *
*************************************************************************

xbtimer:    moveq     #0,d0
            moveq     #0,d1
            moveq     #0,d2
            move.w    4(sp),d0
            move.w    6(sp),d1
            move.w    8(sp),d2
            bsr       settimer                  ; setup the timer
            tst.l     $a(sp)                    ; if >$7fffffff then skip and exit
            bmi.s     xbtexit
            movea.l   $a(sp),a2                 ; setup for initint call
            moveq     #0,d1                     ; clear long
            lea       xbtim(pc),a1              ; point to timer -> interrupt # translation tab
            andi.l    #$ff,d0                   ; mask off the highest three bytes in register
            move.b    (a1,d0.w),d0              ; setup for initint call
            bsr       initint
xbtexit:    rts

xbtim:      DC.B      $0d,$08,$05,$04

*************************************************************************
*                                                                       *
*               KEYBOARD TRANSLATION TABLE CHANGE CALL                  *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       long    keytrans(unshift,shift,capslock)                        *
*       long    unshift,shift,capslock                                  *
*                                                                       *
*               -1 signifies no change to vector                        *
*                                                                       *
*       exit:                                                           *
*               d0.l - returns pointer to beginning of                  *
*                       key translation address pointers                *
*               order of pointers is:                                   *
*               unshifted,shifted,caps-locked                           *
*               Note:  buffer space for each table should $80!!         *
*                                                                       *
*************************************************************************

keytrans:   tst.l     4(sp)
            bmi.s     kt1
            move.l    4(sp),(skeytran).w
kt1:        tst.l     8(sp)
            bmi.s     kt2
            move.l    8(sp),(skeyshif).w
kt2:        tst.l     $c(sp)
            bmi.s     kt3
            move.l    $c(sp),(skeycl).w
kt3:        move.l    #skeytran,d0
            rts


*************************************************************************
*                                                                       *
*               RESTORE BIOS KEYBOARD TRANSLATION TABLE                 *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       void    bioskeys()                                              *
*                                                                       *
*                                                                       *
*************************************************************************

bioskeys:   move.l    #gskeytran,(skeytran).w
            move.l    #gskeyshif,(skeyshif).w
            move.l    #gskeycl,(skeycl).w
IF !ROM_TOS306
            clr.b     (keyrep).w
ENDIF
            rts

*************************************************************************
*                                                                       *
*               RETURN IKBD SUBSYSTEM INTERRUPT TABLE POINTER           *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       void    dosound(ptr)                                            *
*       long    ptr     ;points to start of sound interpreter table     *
*                                                                       *
*                                                                       *
*************************************************************************

dosound:    move.l    (cursnd).w,d0             ; return current status in D0.L
            move.l    4(sp),d1                  ; if new ptr < 0, then just return
            bmi.s     ds_r                      ; (invalid ptr, so return)
            move.l    d1,(cursnd).w             ; setup new sound ptr
            clr.b     (timer).w                 ; zap sound timer register
ds_r:       rts

*************************************************************************
*                                                                       *
*               SET/RETURN PRINTER CONFIGURATION WORD                   *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       word    setptr(pconfig)                                         *
*       word    pconfig ;sets/gets printer information word             *
*                                                                       *
*                                                                       *
*************************************************************************

setprt:     move.w    (pconfig).w,d0            ; get current config word before we change it
            tst.w     4(sp)                     ; see if we don't change the word
            bmi.s     nosetp                    ; don't set printer word
            move.w    4(sp),(pconfig).w         ; set printer config word
nosetp:     rts

*************************************************************************
*                                                                       *
*               SET/RETURN KEY REPEAT VALUES                            *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       word    kbrate(initial,repeat)                                  *
*       word    initial,repeat                                          *
*                                                                       *
*       initial determines the number of 50 hz cycles to wait before    *
*       a keyrepeat is to commence.  repeat determines the interval     *
*       between keyrepeats after the initial pause.                     *
*                                                                       *
*************************************************************************

kbrate:     move.w    (cdelay1).w,d0            ; get current initial/repeat values
            tst.w     4(sp)                     ; see if we don't change the word
            bmi.s     kbrate1                   ; don't set key repeat values
            move.w    4(sp),d1                  ; set key repeat values
            move.b    d1,(cdelay1).w            ; set initial delay
            tst.w     6(sp)                     ; see if we don't change the word
            bmi.s     kbrate1                   ; don't set key repeat values
            move.w    6(sp),d1                  ; set key repeat values
            move.b    d1,(cdelay2).w            ; set subsequent delay
kbrate1:    rts


*************************************************************************
*                                                                       *
*               RETURN POINTER TO IKBD/MIDI INTERRUPT VECTORS           *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       long    ikbdvecs()                                              *
*               returns a pointer to the midi interrupt vector and      *
*               ikbd subsystem interrupt vector table.  the table       *
*               structure is as follows:                                *
*                                                                       *
*       midivec         ds.l    1       ;midi interrupt handler vector  *
*       vkbderr         ds.l    1       ;keyboard error handler address *
*       vmiderr         ds.l    1       ;midi error handler address     *
*       statintvec      ds.l    1       ;ikbd status interrupt vector   *
*       msintvec        ds.l    1       ;mouse interrupt vector         *
*       clkintvec       ds.l    1       ;realtime clk interrupt vector  *
*       joyintvec       ds.l    1       ;joystick interrupt vector      *
*                                                                       *
*       note:   msintvec is modified via the initmouse system function  *
*               call.  since gem uses this vector, modifying it can be  *
*               fatal while running under gem.  clkintvec is used by    *
*               gemdos.  its pre-inited vector must be restored for     *
*               proper gemdos operation.  Caveat hacker!                *
*                                                                       *
*                                                                       *
*************************************************************************

ikbdvecs:   move.l    #midivec,d0
            rts

*************************************************************************
*                                                                       *
*       C Timer interrupt routine to process the PSG sound table        *
*                                                                       *
*************************************************************************
*+ (lmd)
* timercint - timer c interrupt handler
* divide 200 Hz interrupt frequency to 50 hz, and do:
*       sound handler processing
*       key-repeat processing;
*       control-g bell and keyclick if enabled via sound handler
*       system timer-tick handoff
*       updates:        tc_rot (every tick)
*
*       imports:        etv_timer (timer handoff vector)
*                       _timr_ms (timer calibration value)
*
*-
timercint:  addq.l    #1,(_hz_200).l            ; increment raw tick counter
            rol       (tc_rot).l                ; rotate divisor bits
            bpl.s     t_punt                    ; if not 4th interrupt, then return
            movem.l   d0-d7/a0-a6,-(sp)

            bsr.s     sndirq                    ; process sounds

            btst      #1,(conterm).w            ; check for key repeat enabled
            beq.s     krexit                    ; not enabled

* process for repeat key function first because it can affect the sound
* table if enabled and the user is 'using'...

            tst.b     (keyrep).w
            beq.s     krexit
            tst.b     (keydelay1).w
            beq.s     kr1
            subq.b    #1,(keydelay1).w
            bne.s     krexit
kr1:        subq.b    #1,(keydelay2).w
            bne.s     krexit
            move.b    (cdelay2).w,(keydelay2).w
            move.b    (keyrep).w,d0
            lea       (kbufrec).w,a0
            bsr       ari16                     ; repeat key stroke and stuff into buffer

*+ (lmd)
* Call system timer vector
* (first guy in the system daisy-chain)
*
*-
krexit:     move.w    (_timr_ms).w,-(sp)        ; push #ms/tick
            movea.l   (etv_timer).w,a0          ; get vector
            jsr       (a0)                      ; call it
            addq.w    #2,sp                     ; cleanup stack
tick1:      movem.l   (sp)+,d0-d7/a0-a6
t_punt:     move.b    #$df,(isrb).w             ; clear the interrupt channel
            rte

*********************************************************
*
*  Quick & dirty sound stuff
*
*
*  Programmed by Dave Staugas
*                14 Mar 1985
*
*
*
*
*********************************************************
*
*
*
*
*  To start a sound, load the 32-bit address of the
*                       byte stream for that sound in 32-bit
*                       "cursnd", & zero the 8-bit "timer"
*
*
*
*
*   Sound interrupt routine
*   Called from timer C irq
*
sndirq:     movem.l   d0-d1/a0,-(sp)
            move.l    (cursnd).w,d0             ; get current sound ptr
            beq.s     snd1                      ; br to exit if zero, inactive
            movea.l   d0,a0                     ; ptr to a0
            move.b    (timer).w,d0              ; check delay timer
            beq.s     snd3                      ; br over delay timer update if not on
*
            subq.b    #1,d0                     ; tick off delay timer
            move.b    d0,(timer).w              ; save new
            bra.s     snd1                      ; skip sound update this time

snd3:       move.b    (a0)+,d0                  ; pick up next sound command
            bmi.s     snd2                      ; if minus, go do special
*
            move.b    d0,(psgsel).w             ; else, register load command--select this
            cmpi.b    #7,d0                     ; reg. 7 selected?
            bne.s     sn1                       ; br if no
*
            move.b    (a0)+,d1                  ; get data to write to reg 7
            andi.b    #$3f,d1                   ; always leave i/o port settings alone
            move.b    (psgsel).w,d0             ; get mixer contents
            andi.b    #$c0,d0                   ; mask off non-useful info...
            or.b      d1,d0                     ; generate new setting
            move.b    d0,(psgwr).w              ; write data
            bra.s     snd3                      ; go for next command

sn1:        move.b    (a0)+,(psgwr).w           ; write next byte as data directly to reg
            bra.s     snd3                      ; go for next command
*
*  special case command
*
snd2:       addq.b    #1,d0                     ; was command 255?
            bpl.s     snd5                      ; br if yes--set delay timer
*
            cmpi.b    #$81,d0                   ; was command 128 (before increment)
            bne.s     snd6                      ; br if not
*
*  command 128
*
            move.b    (a0)+,(auxd).w            ; 128--set aux data from next byte in stream
            bra.s     snd3                      ; go for next command
*
*  command > 128
*
snd6:       cmpi.b    #$82,d0                   ; command greater than 129
            bne.s     snd5                      ; br if yes--must be set timer
*
*  command 129
*
            move.b    (a0)+,(psgsel).w          ; 129--select register
            move.b    (a0)+,d0                  ; get increment step (signed)
            add.b     d0,(auxd).w               ; add to aux data
            move.b    (a0)+,d0                  ; terminating value
            move.b    (auxd).w,(psgwr).w        ; load reg from data in auxd
            cmp.b     (auxd).w,d0               ; reached end of cycle?
            beq.s     snd4                      ; br if so
*
*  still within loop, reset sound pointer to iterate for next irq
*
            subq.w    #4,a0                     ; back up sound ptr to repeat this command
            bra.s     snd4                      ; update ptr & exit
*
*  set delay timer
*
snd5:       move.b    (a0)+,(timer).w           ; set delay timer from next byter in stream
            bne.s     snd4                      ; if non-zero real delay here
            movea.w   #0,a0                     ; else, sound terminator--set ptr to null

snd4:       move.l    a0,(cursnd).w             ; update sound ptr
snd1:       movem.l   (sp)+,d0-d1/a0            ; pop stack & exit
            rts


*
* sound data...
*
*
* format:
*
*      sound data usually is found in byte pairs, the first of which is the command
*      and the second is the argument.  However, some commands take on more than
*      1 argument
*
*      cmd     function        argument(s)
*      00      load reg0       data0
*      01      load reg1       data0
*      02      load reg2       data0
*      03      load reg3       data0
*      04      load reg4       data0
*      05      load reg5       data0
*      06      load reg6       data0
*      07      load reg7       data0   note: b7 & b6 forced set for all data to r...
*      08      load reg8       data0
*      09      load reg9       data0
*      0A      load reg10      data0
*      0B      load reg11      data0
*      0C      load reg12      data0
*      0D      load reg13      data0
*
*
*      80      init temp w/    data0
*
*      81      loop defined
*              by 3 args       data0 as register to load using temp
*                              data1 as increment/decrement (signed) of temp
*                              data2 as loop terminator value of temp
*
*      82-FF   set delay
*              timer           data0 is # of counts till next update
*                                      note: if data0 = 0, sound is terminated
*
*
*
*
*********************************************************
* VT52 emulator calls for the bell sound
*********************************************************
ringbel:    btst      #2,(conterm).w            ; console bell enabled?
            beq.s     rgbel                     ; (no sound)
            movea.l   (bell_hook).l,a0
            jsr       (a0)                      ; go through the bell vector
            rts


keybellsnd: move.l    #bellsnd,(cursnd).w       ; sound data for console bell
            move.b    #0,(timer).w              ; enable sound timer
rgbel:      rts


*+
* flopini - initialize floppies
* Passed (on the stack):
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer (unused)
*        $0(sp) return address
*
* Returns:       EQ if initialization succeeded (drive attached).
*                NE if initialization failed (no drive attached).
*-
_flopini:   clr.l     (frbufcookie).l
            lea       (dsb0).l,a1               ; get ptr to correct DSB
            tst.w     $c(sp)
            beq.s     fi_1
            lea       (dsb1).l,a1
fi_1:       move.w    (seekrate).l,6(a1)        ; setup default seek rate
            move.w    #3,4(a1)                  ; default: HD mode
            moveq     #-1,d0                    ; (default error)
            clr.w     2(a1)                     ; fake clean drive
            clr.w     (frbufscnt).l
IF !ROM_TOS306
            move.w    #$ff00,2(a1)              ; default = recal drive (it's dirty)
ENDIF
IF ROM_STBOOK
            bsr.s     checkfdc
            beq.s     fi_ndrv
ENDIF
            bsr       floplock                  ; setup parameters
            bsr       select                    ; select drive an side
IF ROM_TOS306
            move.w    #$ff00,2(a1)              ; default = recal drive (it's dirty)
ENDIF
            bsr       restore                   ; attempt restore
            beq.s     fi_ok                     ; (quick exit if that won)
            moveq     #10,d7                    ; attempt seek to track 10
            bsr       hseek1                    ; (hard seek to 'd7')
            bne.s     fi_nok                    ; (failed: drive unusable)
            bsr       restore                   ; attempt restore after seek
fi_ok:      beq       flopok                    ; return OK (on win)
fi_nok:     bra       flopfail                  ; return failure
IF ROM_STBOOK
fi_ndrv:    move.w    #$0202,(diskmode).w       ; both drives are "unsure"
            moveq     #0,d0
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq.s     fi_ret                    ; (no, return NO_ERROR)
            moveq     #-15,d0                   ; return "unknown device"
fi_ret:     rts

drvnrdy:    moveq     #-2,d0                    ; return DRIVE_NOT_READY
            rts
ENDIF

IF ROM_TOS306
            move.l    a0,(busexception).w
            movea.l   a1,sp
            movem.l   (sp)+,d7/a6
drvnrdy:    moveq     #-15,d0
            rts
ENDIF

*+
* checkfdc - check if the FDC track/sector register are available
* Returns:      EQ, if the FDC is not available
*               NE, if the FDC is available
*-
IF ROM_STBOOK
checkfdc:   addq.w    #1,(flock).w              ; lock floppies (allow 'recursion')
            movem.l   d0-d7/a6,-(sp)
            lea       (fifo).w,a6
            move.w    #$82,(a6)                 ; setup 1770 track register
            moveq     #0,d7
            bsr       wdiskctl
            move.w    #$84,(a6)                 ; load 1770 sector register
            moveq     #-1,d7
            bsr       wdiskctl
            move.w    #$82,(a6)                 ; read 1770 track register
            bsr       rdiskctl
            move.b    d0,d1                     ; not equal to 0?
            bne.s     checkfdc2
            move.w    #$84,(a6)                 ; read 1770 sector register
            bsr       rdiskctl
            subq.w    #1,(flock).w              ; unlock floppies
            tst.b     d1                        ; track register wrong?
            bne.s     checkfdc2
            cmp.b     #$ff,d0                   ; sector register wrong?
checkfdc2:  movem.l   (sp)+,d0-d7/a6
            eori      #4,sr                     ; flip Z bit (return NE if bit is zero)
            rts
ENDIF

*+
* floprd - read sector from floppy
* Passed (on the stack):
*       $14(sp) count
*       $12(sp) sideno
*       $10(sp) trackno
*        $e(sp) sectno
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer
*        $0(sp) return address
*
* Returns       EQ, the read won (on all sectors),
*               NE, the read failed (on some sector).
*-
_floprd:
IF ROM_STBOOK
            bsr.s     checkfdc
            beq.s     drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq.s     drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; test for disk change
            moveq     #-11,d0                   ; set default error#
            bsr       frbcheckrd
            bsr       floplock                  ; lock floppies, setup parameters
frd1:       bsr       select                    ; select drive, setup registers
            bsr       go2track                  ; seek appropriate track
            bcs       flopfail
            bne       frde                      ; retry on seek failure
frd1l:      move.w    #-1,(curr_err).l          ; set general error#
            move.w    #$190,(a6)                ; toggle DMA data direction,
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$90,(a6)                 ; leave hardware in READ state
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #1,(diskctl).w            ; set sector count register
            move.w    #$80,(a6)                 ; startup 1770 "read sector" command
            move.w    #$80,d7                   ; (read single)
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; set timeout timer

*--- Wait for read completion:
frd2:       btst      #5,(gpip).w               ; 1770 done yet?
            beq.s     frd4                      ; (yes)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     frd2                      ; (punt on timeout)

*---- check status after read
            move.w    #-2,(curr_err).w          ; set "timeout" error
            bsr       reset1770                 ; (clobber 1770)
            bra.s     frde                      ; (go retry)

*--- check status after read:
frd4:       move.w    #$90,(a6)                 ; examine DMA status register
            move.w    (a6),d0
            btst      #0,d0                     ; bit zero inidcates DMA ERROR
            beq.s     frde                      ; (when its zero -- retry)

            move.w    #$80,(a6)                 ; exeamine 1770 status register
            bsr       rdiskctl
            and.b     #$1c,d0                   ; check for RNF, checksum, lost-data
            bne.s     frd4                      ; (bail on error)
            move.w    #2,(retrycnt).w           ; reset retry count for next sector
            addq.w    #1,(csect).w              ; advance sector #
            addi.l    #$200,(cdma).w            ; advance buffer by 512 bytes
            subq.w    #1,(ccount).w             ; decrement sector count
            beq       flopok                    ; (done)
            bsr       select1
            bra       frd1l

frd4:       bsr.s     err_bits                  ; set error# from 1770 bits
frde:       cmpi.w    #1,(retrycnt).w           ; are we on the "middlemost" retry?
            bne.s     frd5
frde1:      bsr       reseek                    ; yes, home and reseek the head
frd5:       subq.w    #1,(retrycnt).w           ; drop retry count
            bpl       frd1                      ; (continue of any retries left)
            bra       flopfail                  ; fail when we run out of patience

*+
* err_bits - set "curr_err" according to 1770 error status
* Passed:       d0 = 1770 status
*
* Returns:      curr_err, containing current error number
*
* Uses:         d1
*-
err_bits:   moveq     #-13,d1                   ; write protect?
            btst      #6,d0
            bne.s     eb1
            moveq     #-8,d1                    ; record-not-found?
            btst      #4,d0
            bne.s     eb1
            moveq     #-4,d1                    ; CRC error?
            btst      #3,d0
            bne.s     eb1
            move.w    (def_error).w,d1          ; use default error#
eb1:        move.w    d1,(curr_err).w           ; set current error number & return
            rts

*+
* flopwr - write sector to floppy
* Passed (on the stack):
*       $14(sp) count
*       $12(sp) sideno
*       $10(sp) trackno
*        $e(sp) sectno
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer (unused)
*        $0(sp) return address
*
* Returns:      EQ, the write won (on all sectors),
*               NE, the write failed (on some sectors).
*-
_flopwr:
IF ROM_STBOOK
            bsr       checkfdc
            beq       drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; check for disk swap
            moveq     #-10,d0                   ; set default error number
            bsr       frbcheckwr
            bsr       floplock                  ; lock floppies

*+
* If the boot sector is written to,
* set the media change mode to "unsure".
* (Kludge, kludge, kludge....)
*-
            move.w    (csect).w,d0              ; sector 1
            subq.w    #1,d0
            or.w      (ctrack).w,d0             ; track 0
            or.w      (cside).w,d0              ; side 0
            bne.s     fwr1                      ; if not boot sector, then OK
            moveq     #2,d0                     ; set media change mode to unsure
            bsr       setdmode                  ; (boy, is this /ugly/)

fwr1:       bsr       select                    ; select drive
            bsr       go2track                  ; seek
            bcs       flopfail
            bne       fwre1                     ; (retry on seek failure)
fwr1a:      move.w    #-1,(curr_err).w          ; set general error#
            move.w    #$90,(a6)                 ; toggle DMA chip to clear status
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$190,(a6)                ; leave in WRITE mode
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #1,d7                     ; load sector-count register
            bsr       wdiskctl
            move.w    #$180,(a6)                ; load "WRITE SECTOR" command
            move.w    #$a0,d7                   ; into 1770 cmdreg
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; d7 = timeout timer

fwr2:       btst      #5,(gpip).w               ; done yet?
            beq.s     fwr4                      ; (yes, check status)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     fwr2                      ; (still tickin')
            bsr       reset1770                 ; timed out -- reset 1770
            bra.s     fwre                      ; and retry

fwr4:       move.w    #$180,(a6)                ; get 1770 status
            bsr       rdiskctl
            bsr       err_bits                  ; compute 1770 error bits
            btst      #6,d0                     ; if write protected, don't retry
            bne       flopfail                  ; (can't write, so punt)
            and.b     #$5c,d0                   ; check WriteProt+RecNtFnd+CHKSUM+LostD
            bne.s     fwre                      ; retry on error

            move.w    #2,(retrycnt).w           ; reset retry count
            addq.w    #1,(csect).w              ; bump sector number
            addi.l    #$200,(cdma).w            ; add DMA pointer for next sector
            subq.w    #1,(ccount).w             ; if(!--count) return OK;
            beq       flopok
            bsr       select1                   ; setup sector#, DMA pointer
            bra       fwr1a                     ; write next (no seek)

fwre:       cmpi.w    #1,(retrycnt).w           ; re-seek head in "middle" retry
            bne.s     fwr5                      ; (not middle retry)
fwre1:      bsr       reseek                    ; home head and seek
fwr5:       subq.w    #1,(retrycnt).w           ; decrement retry count
            bpl       fwr1                      ; loop if there's still hope
            bra       flopfail                  ; otherwise return error status
*+
* _flopfmt - format a track
* Passed (on the stack):
*       $1a(sp) initial sector data
*       $16(sp) magic number
*       $14(sp) interleave
*       $12(sp) side
*       $10(sp) track
*        $e(sp) spt
*        $c(sp) devno
*        $8(sp) pointer to state block
*        $4(sp) dma address
*        $0(sp) [return]
*
* Returns:      EQ: track successfully written.  Zero.W-terminated list of
*               bad sectors left in buffer (they might /all/ be bad.)
*
*               NE: could not write track (write-protected, drive failure,
*               or something catastrophic happened).
*-
_flopfmt:   cmpi.l    #$87654321,$16(sp)        ; check for magic# on stack
            bne       flopfail                  ; no magic, so we just saved the world
IF ROM_STBOOK
            bsr       checkfdc
            beq       drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; check for disk flip
            moveq     #-10,d0                   ; set default error number
            bsr       frbcheckfmt
            bsr       floplock
            bsr       select                    ; select drive and side
            move.w    $e(sp),(spt).w            ; save sectors-per-track
            move.w    $14(sp),(interlv).w       ; save interleave factor
            move.w    $1a(sp),(virgin).w        ; save initial sector data
            move.l    8(sp),(fmtstateblk).w

*--- put drive into "changed" mode
            moveq     #2,d0                     ; d0 = "CHANGED"
            bsr       setdmode                  ; set media change mode
            moveq     #3,d0                     ; HD mode
            cmpi.w    #$d,(spt).w               ; 13 sectors or more require HD mode
            bcc.s     flopfmt1
            moveq     #0,d0                     ; SD mode
flopfmt1:
IF ROM_STBOOK
            bsr       hdselect                  ; select SD/HD mode of floppy
ELSE
IF ROM_TOS206
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     flopfmt2                  ; (correct)
ENDIF
            move.w    d0,(fdccs).w
flopfmt2:
ENDIF
            move.w    d0,4(a1)

*--- seek to track (hard seek):
            bsr       hseek                     ; hard seek to 'ctrack'
            bne       flopfail                  ; (return error on seek failure)
            move.w    (ctrack).w,2(a1)          ; record current track#

*--- format track, then verify it:
            move.w    #-1,(curr_err).w          ; vanilla error mode
            bsr.s     fmtrack                   ; format track
            bne       flopfail                  ; (return error on seek failure)
            move.w    (spt).w,(ccount).w        ; set number of sectors to verify
            move.w    #1,(csect).w              ; starting sector# = 1
            bsr       verify1                   ; verify sectors

*--- if there are any bad sectors, return /that/ error...
            movea.l   (cdma).w,a2               ; a2 -> bad sector list
            tst.w     (a2)                      ; any bad sectors?
            beq       flopok                    ; no -- return OK
            move.w    #-16,(curr_err).w         ; set error number
            bra       flopfail                  ; return error

*+
* fmtrack - format a track
* Passed:       variables setup by _flopfmt
* Returns:      NE on failure, EQ on success
* Uses:         almost everything
* Called-by:    _flopfmt
*
*-
fmtrack:    move.w    #-10,(def_error).w        ; set default error number
            movea.l   (cdma).w,a2               ; a2 -> prototyping area
            movea.l   (fmtstateblk).w,a3
            moveq     #120-1,d1                 ; 120 x $4e (track leadin)
            cmpi.w    #$d,(spt).w
            bcc.s     fmtrack1
            moveq     #60-1,d1                  ; 60 x $4e (track leadin)
fmtrack1:   moveq     #$4e,d0
            bsr       wmult

            clr.w     d3                        ; interleave index = table start
            tst.w     (interlv).w               ; interleave < 0
            bmi       fmtrack2                  ; use custom interleave table ->
            moveq     #1,d3                     ; first sector = 1

*---- address mark
ot3:        move.w    d3,d4                     ; d4 = starting sector (this pass)
ot1:        moveq     #12-1,d1                  ; 12 x $00
            clr.b     d0
            bsr       wmult
            moveq     #3-1,d1                   ; 3 x $f5
            moveq     #$f5,d0
            bsr       wmult
            move.b    #$fe,(a2)+                ; $fe -- address mark intro
            move.b    (ctrack+1).w,(a2)+        ; track#
            move.b    (cside+1).w,(a2)+         ; side#
            move.b    d4,(a2)+                  ; sector#
            move.b    #2,(a2)+                  ; sector size (512)
            move.b    #$f7,(a2)+                ; write checksum

*--- gap between AM and data:
            moveq     #22-1,d1                  ; 22 x $4e
            moveq     #$4e,d0
            bsr       wmult
            moveq     #12-1,d1                  ; 12 x $00
            clr.b     d0
            bsr       wmult
            moveq     #3-1,d1                   ; 3 x $f5
            moveq     #$f5,d0
            bsr       wmult

*--- data block:
            move.b    #$fb,(a2)+                ; $fb -- data intro
            move.w    #256-1,d1                 ; 256 x virgin.W (initial sector data)
ot2:        move.b    (virgin).w,(a2)+          ; copy high byte
            move.b    (virgin+1).w,(a2)+        ; copy low byte
            dbra      d1,ot2                    ; fill 512 bytes
            move.b    #$f7,(a2)+                ; $f7 -- write checksum
            moveq     #40-1,d1                  ; 40 x $4e
            moveq     #$4e,d0
            bsr       wmult

            tst.w     (interlv).w               ; interleave < 0
            bmi       fmtrack2                  ; use custom interleave table ->
            add.w     (interlv).w,d4            ; bump sector#
            cmp.w     (spt).w,d4                ; if(d4 <= spt) then_continue;
            ble.s     ot1                       ; proto more sectors this pass
            addq.w    #1,d3                     ; bump pass start count
            cmp.w     (interlv).w,d3            ; if(d3 <= interlv) then_continue;
            ble.s     ot3

*--- end-of-track
ot5:        move.w    #2800,d1                  ; 2801 x $4e -- end of track trailer
            cmpi.w    #$d,(spt).w
            bcc.s     ot4
            move.w    #1400,d1                  ; 1401 x $4e -- end of track trailer
ot4:        moveq     #$4e,d0
            bsr       wmult

*--- setup to write the track:
            move.b    (cdma+3).w,(dmalow).w     ; load dma pointer
            move.b    (cdma+2).w,(dmamid).w
            move.b    (cdma+1).w,(dmahigh).w
            move.w    #$90,(a6)                 ; toggle R/W flag and
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$190,(a6)                ; select sector-count register
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            moveq     #$60,d7                   ; (absurd sector count)
            bsr       wdiskctl
            move.w    #$180,(a6)                ; select 1770 cmd register
            move.w    #$f0,d7                   ; write format_track command
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; d7 = timeout timer

*--- wait for 1770 complete:
otw1:       btst      #5,(gpip).w               ; is 1770 done?
            beq.s     otw2                      ; (yes)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     otw1                      ; (still tickin')
            bsr       reset1770                 ; timed out -- reset 1770
oterr:      moveq     #1,d7                     ; return NE (error status)
            rts

fmtrack2:   cmp.w     (spt).w,d3                ; last sector reached?
            beq       ot5                       ; yes -> end of track
            move.w    d3,d6
            add.w     d6,d6
            move.w    (a3,d6.w),d4              ; pick new sector number from the table
            addq.w    #1,d3                     ; increment interleave index
            bra       ot1

*--- see if the write-track won:
otw2:       move.w    #$190,(a6)                ; check DMA status bit
            move.w    (a6),d0
            btst      #0,d0                     ; if its zero, there was a DMA error
            beq.s     oterr                     ; (so return NE)
            move.w    #$180,(a6)                ; get 1770 status
            bsr       rdiskctl
            bsr       err_bits                  ; set 1770 error bits
            and.b     #$44,d0                   ; check for writeProtect & lostData
            rts                                 ; return NE on 1770 error

*----- write 'D1+1' copies of D0.B into A2, A2+1, ...
wmult:      move.b    d0,(a2)+                  ; record byte in proto buffer
            dbra      d1,wmult                  ; (do it again)
            rts


*+
* _flopver - verify sectors on a track
*       $14(sp) count
*       $12(sp) sideno
*       $10(sp) trackno
*        $e(sp) sectno
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer (at least 1K long)
*        $0(sp) return address
*
* Returns:      NULL.W-terminated list of bad sectors in the buffer if D0 == 0,
*               OR some kind of error (D0 < 0).
*
*-
_flopver:
IF ROM_STBOOK
            bsr       checkfdc
            beq       drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; hack disk change
            moveq     #-11,d0                   ; set default error#
            bsr       frbcheckfmt
            bsr       floplock                  ; lock floppies, setup parameters
            bsr       select                    ; select floppy
            bsr       go2track                  ; go to track
            bne       flopfail                  ; (punt if that fails)
            bsr.s     verify1                   ; verify some sectors
            bra       flopok                    ; return "OK"

*+
* verify1 - verify sectors on a single track
* Passed:       csect = starting sector#
*               ccount = number of sectors to verify
*               cdma -> 1K buffer (at least)
*
* Returns:      NULL.W-terminated list of bad sectors (in the buffer)
*               (buffer+$200..buffer+$3ff used as DMA buffer)
*
* Enviroment:   Head seeked to correct track;
*               Drive and side already selected;
*               Motor should be spinning (go2track and fmttrack do this).
*
* Uses:         Almost everything.
*
* Called-by:    _flopfmt, _flopver
*
*-
verify1:    move.w    #-11,(def_error).w        ; set default error number
            movea.l   (cdma).w,a2               ; a2 -> start of bad sector list
            addi.l    #$200,(cdma).w            ; bump buffer up 512 bytes

*--- setup for (next) sector
tvrlp:      move.w    #2,(retrycnt).w           ; init sector-retry count
            move.w    #$84,(a6)                 ; load 1770 sector register
            move.w    (csect).w,d7              ; with 'csect'
            bsr       wdiskctl

*--- setup for sector read
tvr1:       move.b    (cdma+3).w,(dmalow).w     ; load dma pointer
            move.b    (cdma+2).w,(dmamid).w
            move.b    (cdma+1).w,(dmahigh).w
            move.w    #$190,(a6)                ; toggle R/W (leave in W state)
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$90,(a6)
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #1,d7                     ; set DMA sector count to 1
            bsr       wdiskctl
            move.w    #$80,(a6)                 ; load 1770 command register
            move.w    #$80,d7                   ; with ReadSector command
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; d7 = timeout timer

*--- wait for command completion
tvr2:       btst      #5,(gpip).w               ; test for 1770 done
            beq.s     tvr4                      ; (yes, it completed)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     tvr2                      ; (still tickin')
            bsr       reset1770                 ; reset controller and return error
            bra.s     tvre

*--- got "done" interrupt, check DMA status:
tvr4:       move.w    #$90,(a6)                 ; read DMA error status
            move.w    (a6),d0
            btst      #0,d0                     ; if DMA_ERROR is zero, then retry
            beq.s     tvre

*--- check 1770 completion status (see if it's happy):
            move.w    #$80,(a6)                 ; read 1770 status register
            bsr       rdiskctl
            bsr       err_bits                  ; set error# from 1770 register
            and.b     #$1c,d0                   ; check for record-not-found, crc-error,
            bne.s     tvre                      ; and lost data; return on error

*--- read next sector (or return if done)
tvr6:       addq.w    #1,(csect).w              ; bump sector count
            subq.w    #1,(ccount).w             ; while(--count) read_another;
            bne       tvrlp
            subi.l    #$200,(cdma).w            ; readjust DMA pointer
            clr.w     (a2)                      ; terminate bad sector list
            rts                                 ; and return EQ


*--- read failure: retry or record bad sector
tvre:       cmpi.w    #1,(retrycnt).w           ; re-seek head?
            bne.s     tvr5                      ; (no)
            bsr       reseek                    ; yes: back to home and then back
tvr5:       subq.w    #1,(retrycnt).w           ; to the current track...
            bpl       tvr1
            move.w    (csect).w,(a2)+           ; record bad sector
            bra.s     tvr6                      ; do next sector

*+
* _flopvbl - floppy vblank handler
* Deselects floppies after the motor stops
*-
_flopvbl:
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       fvblr                     ; (no, return "unknown device")
ENDIF
            lea       (fifo).w,a6               ; a6 -> fifo
            st        (_motoron).w              ; assume motor is on
            tst.w     (flock).w                 ; floppies locked?
            bne.s     fvblr                     ; (yes, so don't touch them)
IF ROM_STBOOK
            bsr       checkfdc
            beq.s     fvblr
ENDIF

*--- write-protect monitor:
            move.l    (_frclock).w,d0           ; check a drive every 8 jiffies
            move.b    d0,d1                     ; (save jiffy count)
            and.b     #7,d1                     ; time yet?
            bne.s     fvbl1                     ; (no)
            move.w    #$80,(a6)                 ; select 1770 command/status register

*--- select drive, record it's WP status:
            lsr.b     #3,d0                     ; use bit 4 as drive# to check
            and.w     #1,d0
            lea       (wpstatus).w,a0
            adda.w    d0,a0
            cmp.w     (_nflops).w,d0
            bne.s     fvbl2
            clr.w     d0
fvbl2:      addq.b    #1,d0
            lsl.b     #1,d0                     ; (magic shift left)
            eori.b    #7,d0                     ; invert select bits, select side 0
            bsr       setporta                  ; set port A (d2 = old bits)
            bsr       rdiskctl                  ; get 1770 status
            move.w    d0,d1
            btst      #6,d1                     ; test Write-Protect status bit
            sne       (a0)                      ; set WP status to $00 or $FF.
            move.b    d2,d0                     ; restore old drive-select bits
            bsr       setporta

fvbl1:      move.w    (wpstatus).w,d0           ; or _wpstatus into _wplatch
            or.w      d0,(wplatch).w            ; (catch any WP transitions)

*--- floppy deselect test
            tst.w     (deselflg).w              ; floppies already deselected?
            bne.s     fvblr1                    ; (yes, so don't do it again)
            move.l    (_hz_200).w,d0
            cmp.l     (fltimeout).l,d0          ; did we hit our 5s timeout?
            bcc.s     fvbl2

            bsr       rdiskctl                  ; read 1770 status register
            btst      #7,d0                     ; is the motor still on?
            bne.s     fvblr                     ; (yes, so don't deselect)
fvbl2:      move.b    #7,d0                     ; deselect both drives
            bsr       setporta                  ; (set bits 0..3 in portA of PSG)
            move.w    #1,(deselflg).w           ; indicate floppies deselected
fvblr1:     clr.w     (_motoron).w              ; indicate motor is OFF
fvblr:      rts                                 ; back to vbl


*+
* floplock - lock floppies and setup floppy parameters
*
* Passed (on the stack):
*       $18(sp) - count.W (sector count)
*       $16(sp) - side.W (side#)
*       $14(sp) - track.W (track#)
*       $12(sp) - sect.W (sector#)
*       $10(sp) - dev.W (device#)
*        $c(sp) - obsolte.L
*         8(sp) - dma.L (dma pointer)
*         4(sp) - ret1.L (caller's return address)
*         0(sp) - ret.L (floplock's return address)
*
* Passed:       D0.W = default error number
*-
floplock:   movem.l   d3-d7/a3-a6,($168a).l     ; save C registers

            lea       (fifo).w,a6               ; a6 -> fifo
            st        (_motoron).w              ; kludge motor state = ON
            move.w    d0,(def_error).w          ; set default error number
            move.w    d0,(curr_err).w           ; set current error number
            move.w    #1,(flock).w              ; tell vbl not to touch floppies
            move.l    8(sp),(cdma).w            ; cdma -> /even/ DMA address
            move.w    $10(sp),(cdev).w          ; save devide# (0 . 1)
            move.w    $12(sp),(csect).w         ; save sector# (1 . 9, usually)
            move.w    $14(sp),(ctrack).w        ; save track# (0 . 39 .  79   )
            move.w    $16(sp),(cside).w         ; save side# (0 . 1)
            move.w    $18(sp),(ccount).w        ; save sector count (1..spt)
            move.w    #2,(retrycnt).w           ; setup retry count

*--- pick a DSB
            lea       (dsb0).w,a1
            tst.w     (cdev).w
            beq.s     flock2
            lea       (dsb1).w,a1

*--- recalibrate drive (if it needs it)
flock2:     tst.w     2(a1)                     ; if (curtrack < 0) recalibrate()
            bpl.s     flockr

            bsr       select                    ; select drive & side
            clr.w     2(a1)                     ; we're optimistic -- assume winnage
            bsr       restore                   ; attempt restore
            beq.s     flockr                    ; (it won)
            moveq     #10,d7                    ; attempt seek to track 10
            bsr       hseek1
            bne.s     flock1                    ; (failed)
            bsr       restore                   ; attempt restore again
            beq.s     flockr                    ; (it won)
flock1:     move.w    #$ff00,2(a1)              ; complete failure (what can we do?)
flockr:     rts

*+
* flopfail - unlock floppies and return error.
*
*-
flopfail:   moveq     #1,d0                     ; disk change mode = unsure
            bsr       setdmode                  ; set media change mode
            move.w    (curr_err).w,d0           ; get current error number
            ext.l     d0                        ; extent to long
            bra.s     unlok1                    ; clobber floppy lock & return

*+
* flopok - unlock floppies and return success status:
*
*-
flopok:     clr.l     d0                        ; return 0 (success)
unlok1:     move.l    d0,-(sp)                  ; (save return value)
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            move.w    #$86,(a6)                 ; force WP to real-time mode
            move.w    2(a1),d7                  ; dest-track = current track
            bsr       wdiskctl
            move.w    #$10,d6                   ; cmd = seek w/o verify
            bsr       flopcmds                  ; do it

            move.l    (_hz_200).l,d0
            add.l     #5*200,d0                 ; 5s floppy deselect timeout
            move.l    d0,(fltimeout).l

            move.w    (cdev).w,d0               ; set last-access time for 'cdev'
            lsl.w     #2,d0
            lea       (_acctim).w,a0
            move.l    (_frclock).w,(a0,d0.w)
            cmpi.w    #1,(_nflops).w            ; if (nflops == 1) set other time, too
            bne.s     unlok2
            move.l    (_frclock).w,4(a0)        ; set last-access time for floppy 1

unlok2:     move.l    (sp)+,d0                  ; restore return value
            movem.l   ($168a).w,d3-d7/a3-a6     ; restore C registers
            clr.w     (flock).l                 ; unlock floppies
            bsr       frbbackcopy               ; if necessary copy FRB buffer back
            rts


*+
* hseek  - seek to 'ctrack' without verify
* hseek1 - seek to 'd7' without verify
* hseek2 - seek to 'd7' without verify, keep current error number
*
* Returns:      NE on seek failure ("cannot happen"?)
*               EQ if seek wins
*
* Uses:         d7, d6, ...
* Jumps-to:     flopcmds
* Called-by:    _flopfmt, _flopinit
*
*-
hseek:      move.w    (ctrack).l,d7             ; dest track = 'ctrack'
hseek1:     move.w    #-6,(curr_err).l          ; possible error = "seek error"
            move.w    #$86,(a6)                 ; write destination track# to data reg
            bsr       wdiskctl
            move.w    #$10,d6                   ; execute "seek" command
            bra       flopcmds                  ; (without verify...)

*+
* reseek - home head, then reseek track
* Returns:      EQ/NE on success/failure
* Falls-into:   go2track
*
*-
reseek:     move.w    #-6,(curr_err).l          ; set "seek error"
IF ROM_STBOOK
            bsr.s     restore                   ; restore head
            bne.s     go2trr                    ; (punt if home fails)
ELSE
            bsr       restore                   ; restore head
            bne       go2trr                    ; (punt if home fails)
ENDIF

            clr.w     2(a1)                     ; current track = 0
            move.w    #$82,(a6)                 ; set "current track" reg on 1770
            clr.w     d7
            bsr       wdiskctl

            move.w    #$86,(a6)                 ; seek out to track five
            move.w    #5,d7
            bsr       wdiskctl                  ; dest track = 5
            move.w    #$10,d6
            bsr.s     flopcmds                  ; seek
            bne.s     go2trr                    ; return error on seek failure
            move.w    #5,2(a1)                  ; set current track#

*+
* go2track - seek proper track
* Passed:       Current floppy parameters (ctrack, et al.)
* Returns:      EQ/NE on success/failure
* Calls:        flopcmds
*-
go2track:   move.w    #1,(fseekrtr).l           ; set seek retry count
go2track1:  move.w    #-6,(curr_err).l          ; set "seek error"
            move.w    #$86,(a6)                 ; set destination track# in
            move.w    (ctrack).w,d7             ; 1770's data register
            bsr       wdiskctl                  ; (write track#)
            moveq     #$14,d6                   ; execute 1770 "seek_with_verify"
            bsr.s     flopcmds                  ; (include seek-rate bits)
            bcs.s     go2trr
            bne.s     go2track2                 ; return error on seek failure
            and.b     #$18,d7                   ; check for RNF, CRC_error, lost_data
            beq.s     go2track3                 ; (exit on no error)
go2track2:  move.w    4(a1),d0
            and.w     #3,d0
            eori.w    #3,d0                     ; toggle between SD/HD mode for a second seek try
IF ROM_STBOOK
            move.w    d0,4(a1)
            bsr       hdselect                  ; select SD/HD mode of floppy
ELIF ROM_TOS206
            move.w    d0,4(a1)
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     go2track2b                ; (correct)
            move.w    d0,(fdccs).w
go2track2b:
ELIF ROM_TOS306
            move.w    d0,(fdccs).w
            move.w    d0,4(a1)
ENDIF
            subq.w    #1,(fseekrtr).l           ; --seek retry count
            bne.s     go2trr                    ; (out of retries -- punt)
            bsr.s     restore                   ; move head to track 00 to reset it
            bra.s     go2track1                 ; try again
go2track3:  move.w    (ctrack).w,2(a1)          ; update current track number
            clr.w     d7                        ; set EQ
go2trr:     rts


*+
* restore - home head
* Passed:       nothing
* Returns:      EQ/NE on success/failure
* Falls-into:   flopcmds
*-
restore:    clr.w     d6                        ; $00 = 1770 "restore" command
            bsr.s     flopcmds                  ; do restore
            bne.s     res_r                     ; punt on timeout
            btst      #2,d7                     ; test TRK00 bit
            eori      #4,ccr                    ; flip Z bit (return NE if bit is zero)
            bne.s     res_r                     ; (punt if didn't win)
            clr.w     2(a1)                     ; set current track#
res_r:      rts


*--- seek rate conversion table for HD mode
* HD is using twice the clock speed (16MHz instead of 8MHz) for the FDC,
* so the FDC has to seek "slower"
dseekrt:    DC.B      $01,$01,$00,$00           ; 6ms and 12ms -> 12ms,  2ms and 3ms -> 6ms


*+
* flopcmds - floppy command (on-in seek speed bits from database)
* Passed:       d6.w = 1770 command
* Sets-up:      seek bits (bits 0 and 1) in d6.w
* Falls-into:   flopcmds
* Returns:      EQ/NE on success/failure
*-
flopcmds:   move.w    6(a1),d0                  ; get floppy's seek rate bits
            and.w     #3,d0                     ; OR into command
            tst.w     4(a1)                     ; SD mode?
            beq.s     flopcmds1                 ; (yes)
            lea       (dseekrt).l,a0            ; use a HD-compatible seek rate table
            move.b    (a0,d0.w),d0
flopcmds1:  or.b      d0,d6
            or.b      (a1),d6                   ; (a1) == 8 for HD, with sets the verify bit in the command
IF ROM_STBOOK
            move.w    4(a1),d0                  ; get SD/HD mode
            bsr.s     hdselect                  ; select SD/HD mode of floppy
ELSE
IF ROM_TOS206
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     flopcmds2                 ; (correct)
ENDIF
            move.w    4(a1),(fdccs).w
flopcmds2:
ENDIF

*+
* flopcmd - execute 1770 command (with timeout)
* Passed:       d6.w = 1770 command
*
* Returns:      EQ/NE on success/failure
*               d7 = 1770 status bits
*
*-
flopcmd:    move.l    (_hz_200).w,d7            ; setup timeout (assume short)
            add.l     #300,d7
            move.w    #$80,(a6)                 ; select 1770 command register
            bsr       rdiskctl                  ; read it to clobber READY status
            btst      #7,d0                     ; is motor on?
            bne.s     flopcm                    ; (yes, keep short timeout)
            move.l    (_hz_200).w,d7            ; extra timeout for motor startup
            add.l     #600,d7
flopcm:     bsr       wdiskct6                  ; write command (in d6)
flopc1:     cmp.l     (_hz_200).w,d7            ; timeout?
            bcs.s     flopcto                   ; (yes, reset and return failure)
            btst      #5,(gpip).w               ; 1770 completion?
            bne.s     flopc1                    ; (not yet, so wait some more)
            bsr       rdiskct7                  ; return EQ + 1770 status in d7
            clr.w     d6
            rts

flopcto:    bsr.s     reset1770                 ; bash controller
            moveq     #0,d6
            subq.w    #1,d6                     ; and return NE
            rts


*+
* hdselect - select HD mode for 1.4MB floppies and SD for 720kd floppies
* Passed:          d0.w SD/HD mode
*-
IF ROM_STBOOK
hdselect:   tst.w     d0                        ; if != 0 then select HD
            bne.s     hdselect2
            clr.b     d0                        ; SD mode
            bra.s     hdselect3
hdselect2:  move.b    #$80,d0                   ; HD mode is bit 7 in PORT A on the sound chip
hdselect3:  move      sr,d2                     ; save status register
            move.b    #14,(psgsel).w            ; select port on GI chip
            move.b    (psgsel).w,d1             ; get current bits
            bclr      #7,d1                     ; clear HD bit
            or.b      d0,d1                     ; or-in our new bit
            move.b    #14,(psgsel).w            ; select port on GI chip
            move.b    d1,(psgwr).w              ; and write 'em back out there
            move      d2,sr                     ; restore status register
            rts
ENDIF

*+
* reset1770 - reset disk controller after a catastrophe
* Passed:       nothing
* Returns:      nothing
* Uses:         d7
*-
reset1770:  move.w    #$80,(a6)                 ; execute 1770 "reset" command
            move.w    #$d0,d7
            bsr       wdiskctl
IF ROM_TOS306
            move.w    d0,-(sp)
            move.w    #$114,d0
            bsr       mfpdelay
            move.w    (sp)+,d0
ELSE
            move.w    #2,d0
r1770a:     move.b    (tcdr).w,d1               ; wait for 1770 to stop convulsing
r1770b:     cmp.b     (tcdr).w,d1
            beq.s     r1770b
            dbra      d0,r1770a
ENDIF
            bsr.s     rdiskct7                  ; return 1770 status in d7
            rts


*+
* select - setup drive select, 1770 and DMA registers
* Passed:       cside, cdev
* Returns:      appropriate drive and side select
*-
select:     clr.w     (deselflg).w              ; floppies NOT deselected
            move.w    (cdev).w,d0               ; get device number
            addq.b    #1,d0                     ; add and shift to get select bits
            lsl.b     #1,d0                     ; into bits 1 and 2
            or.w      (cside).w,d0              ; or-in side number (bit 0)
            eori.b    #7,d0                     ; negate bits for funky hardware select
            and.b     #7,d0                     ; strip anything else out there
            bsr.s     setporta                  ; do drive select

            move.w    #$82,(a6)                 ; setup 1770 track register
            move.w    2(a1),d7                  ; for current track number
            bsr.s     wdiskctl

select1:    move.w    #$84,(a6)                 ; setup requested sector_number from
            move.w    (csect).w,d7              ; caller's parameters
            bsr.s     wdiskctl
            move.b    (cdma+3).w,(dmalow).w     ; setup DMA chip's DMA pointer
            move.b    (cdma+2).w,(dmamid).w
            move.b    (cdma+1).w,(dmahigh).w
            rts


*+
* setporta - set floppy select bits in PORT A on the sound chip
* Passed:       d0.b (low three bits)
* Returns:      d1 = value written to port A
*               d2 = old value read from port A
* Uses:         d1
*-
setporta:   move      sr,-(sp)                  ; save our IPL
            ori       #$700,sr                  ; start critical section
            move.b    #14,(psgsel).w            ; select port on GI chip
            move.b    (psgsel).w,d1             ; get current bits
            move.b    d1,d2                     ; save old bits for caller
            and.b     #$f8,d1                   ; strip low three bits there
            or.b      d0,d1                     ; or-in our new bits
            move.b    d1,(psgwr).w              ; and write 'em back out there
            move      (sp)+,sr                  ; restore IPL to terminate CS, return
            rts


*+
* Primitives to read/write 1770 controller chip (DISKCTL register).
*
* The 1770 can't keep up with full-tilt CPU access, so
* we have to surround read and writes with delay loops.
* This is not really as slow as it sounds.
*
*-
wdiskct6:   bsr.s     rwdelay                   ; delay
            move.w    d6,(diskctl).w            ; write d6 to diskctl
            rts

wdiskctl:   bsr.s     rwdelay                   ; delay
            move.w    d7,(diskctl).w            ; write d7 to diskctl
            rts

rdiskct7:   bsr.s     rwdelay                   ; delay
            move.w    (diskctl).w,d7            ; read diskctl into d7
            rts

rdiskctl:   bsr.s     rwdelay                   ; delay
            move.w    (diskctl).w,d0            ; read diskctl into d0
            rts


rwdelay:
IF ROM_TOS306
            move.w    d0,-(sp)
            move.w    #$119,d0
            bsr       mfpdelay
            move.w    (sp)+,d0
ELSE
            movem.l   d0-d1,-(sp)               ; save registers
            move.w    #2,d0
rwdly1:     move.b    (tcdr).w,d1               ; busy-loop: give 1770 time to settle
rwdly2:     cmp.b     (tcdr).w,d1
            beq.s     rwdly2
            dbra      d0,rwdly1
            movem.l   (sp)+,d0-d1               ; restore registers
ENDIF
            rts


*+
* change - check to see if the "right" floppy bas been inserted
* On the stack:
*       $10(sp) - dev.W (device#)
*        $c(sp) - dsb.L (pointer to Device State Block)
*         8(sp) - dma.L (dma pointer)
*         4(sp) - ret1.L (caller's return address)
*         0(sp) - ret.L (change's return address)
*
* Returns:      both media "might have changed" condition
*
* Uses:         C registers
*
*-
change:     cmpi.w    #1,(_nflops).l            ; if there are zero or two floppies
            bne.s     ch_r                      ; then do nothing (return OK)
            move.w    $10(sp),d0                ; if cdev == _curflop
            cmp.w     (_curflop).l,d0           ; (...current disk == current drive?)
            beq.s     ch_ok1                    ; then return OK (but use drive #0)

*--- ask the user to stick in the other floppy (via critical error handler)
push disk# we want inserted:move.w d0,-(sp)
            move.w    #-17,-(sp)                ; push "INSERT_A_DISK" error number
            bsr       crit_err                  ; use critical error handler and
            addq.w    #4,sp                     ; hope somebody handles it
            move.w    #$ffff,(wplatch).l        ; set "might have changed" on both drvs
            lea       (_acctim).l,a0
            clr.l     (a0)+
            clr.l     (a0)
            move.w    $10(sp),(_curflop).l      ; set current disk#
ch_ok1:     clr.w     $10(sp)                   ; use drive 0
ch_r:       rts

*+
* setdmode - set drive-change mode
* Passed:         d0.b = mode to put current drive in (0, 1, 2)
* Uses:           a0
*
*-
setdmode:   lea       (diskmode).w,a0           ; a0 -> disk mode table
            move.b    d0,-(sp)                  ; (save mode)
            move.w    (cdev).w,d0               ; d0.w = drive# (index into table)
            cmpi.w    #1,(_nflops).l
            bne.s     setdmode2
            move.w    (_curflop).l,d0
setdmode2:  move.b    (sp)+,(a0,d0.w)           ; set drive's mode
            rts


*+
* floprate - sets the seek rate of the specified floppy drive
* On the stack:
*         6(sp) - rate.w (0: 6ms, 1: 12ms, 2:2ms, 3:3ms)
*         4(sp) - dev.W (device#)
*         0(sp) - return address
*
* Returns:      prior seek rate for the specified drive
*
*-

*--- pick a DSB
floprate:   lea       (dsb0).l,a1
            tst.w     4(sp)
            beq.s     floprate2
            lea       (dsb1).l,a1
floprate2:  move.w    6(a1),d0                  ; current rate
            move.w    6(sp),d1                  ; new seek rate
            cmp.w     #-1,d1                    ; new seek rate == -1
            beq.s     floprateret               ; (just return the current rate)
            cmp.w     #-2,d1                    ; new seek rate == -2
            beq.s     flopratehd                ; (select HD floppy density)
            cmp.w     #-3,d1                    ; new seek rate == -3
            beq.s     flopratesd                ; (select SD floppy density)
            cmp.w     #-4,d1                    ; new seek rate == -4
            beq.s     floprateretdens           ; (return current density)
            move.w    d1,6(a1)                  ; set new seek rate in dsb
floprateret:ext.l     d0
            rts

flopratehd: move.b    #8,(a1)                   ; set density to HD
            moveq     #0,d0                     ; return no error
            rts

flopratesd: clr.b     (a1)                      ; set density to SD
            moveq     #0,d0                     ; return no error
            rts

floprateretdens:tst.b (a1)                      ; return current density setting
            sne       d0                        ; -1:HD, 0:SD
            ext.w     d0
            ext.l     d0
            rts


*+
* Fast RAM doesn't support DMA transfers, we use a temporary buffer
* for loads/stores from and into Fast RAM. The buffer is in _FRB cookie.
*-
frbbackcopy:move.w    (frbufscnt).l,d1          ; sectors to copy back into memory?
            beq.s     frbcheckrts               ; (no, punt)
IF ROM_TOS306
            move.l    d0,-(sp)
            bsr       flushCaches
            move.l    (sp)+,d0
ENDIF
            clr.w     (frbufscnt).l             ; reset sector flag
            movea.l   (frbufaddress).l,a0       ; requested buffer address in memory
            movea.l   (frbufcookie).l,a1        ; used DMA buffer address
frbcopya1_a0:asl.w    #5,d1
            subq.w    #1,d1
frbcopyl:   move.l    (a1)+,(a0)+
            move.l    (a1)+,(a0)+
            move.l    (a1)+,(a0)+
            move.l    (a1)+,(a0)+
            dbra      d1,frbcopyl
frbcheckrts:rts

frbcheckrd:
IF ROM_TOS306
            cmpi.l    #$a00000,8(sp)            ; beyond the 10MB limit?
ELSE
            cmpi.l    #$400000,8(sp)            ; beyond the 4MB limit?
ENDIF
            bcs.s     frbcheckrts               ; (no, punt)
            bsr.s     findFRBc
            move.l    8(sp),(frbufaddress).l    ; save buffer address
            move.l    (frbufcookie).l,8(sp)     ; inject FRB buffer into the parameter
            move.w    $18(sp),(frbufscnt).l     ; save number of sectors
            rts

frbcheckwr:
IF ROM_TOS306
            cmpi.l    #$a00000,8(sp)            ; beyond the 10MB limit?
ELSE
            cmpi.l    #$400000,8(sp)            ; beyond the 4MB limit?
ENDIF
            bcs.s     frbcheckrts               ; (no, punt)
            bsr.s     findFRBc
            movea.l   8(sp),a1                  ; get the source buffer address
            movea.l   (frbufcookie).l,a0
            move.l    a0,8(sp)                  ; inject FRB buffer into the parameter
            move.w    $18(sp),d1                ; number of sectors
            bra.s     frbcopya1_a0              ; copy D1 sectors from A1 to A0

frbcheckfmt:
IF ROM_TOS306
            cmpi.l    #$a00000,8(sp)            ; beyond the 10MB limit?
ELSE
            cmpi.l    #$400000,8(sp)            ; beyond the 4MB limit?
ENDIF
            bcs.s     frbcheckrts               ; (no, punt)
            bsr.s     findFRBc
            move.l    8(sp),(frbufaddress).l    ; save buffer address
            move.l    (frbufcookie).l,8(sp)     ; inject FRB buffer into the parameter
            move.w    #1,(frbufscnt).l          ; 1 sector
            rts

* get FRB buffer pointer from the cookies
findFRBc:   tst.l     (frbufcookie).l           ; variable already set?
            bne.s     findFRBcrts               ; (yes, return)
            movea.l   (_p_cookies).w,a0
            cmpa.w    #0,a0                     ; no cookie jar?
            beq.s     findFRBcx
findFRBcl:  tst.l     (a0)                      ; end of the cookie jar?
            beq.s     findFRBcx
            cmpi.l    #'_FRB',(a0)+
            beq.s     findFRBcf
            addq.l    #4,a0
            bra.s     findFRBcl
findFRBcf:  move.l    (a0)+,(frbufcookie).l
findFRBcrts:rts

findFRBcx:  addq.l    #8,sp                     ; go 2 return levels up
            moveq     #-12,d0                   ; return -12 if no _FRB cookie
            rts

Atari ST Book: ROM disk listing

Commented assembly listing for the ROM disk in an Atari ST-Book. Also available on GitHub.

***
*  Half the ST Book ROM contains a ROM disk, which is mapped as drive 'P' into GEMDOS
***
$e40000 : abcd ef42                STBOOK_EXTROM:DC.L    $abcdef42

$e40004 : 00e4 0200                CA_NEXT:    DC.L      CA2_NEXT                   ; pointer to next application
$e40008 : 08e4 0026                CA_INIT:    DC.L      ROMDISK_CODE
$e4000c : 00e4 0026                CA_RUN:     DC.L      ROMDISK_CODE
$e40010 : 1776                     CA_TIME:    DC.W      $1776
$e40012 : 69b6                     CA_DATE:    DC.W      $69b6
$e40014 : 0000 016c                CA_SIZE:    DC.L      $0000016c
$e40018 : 424f 4f4b 524f 4d2e 5052+CA_NAME:    DC.B      'BOOKROM.PRG',0
$e40024 : ffff                                 DC.W      $ffff

$e40026 : 601a                     ROMDISK_CODE:bra.s    ROMDISK_INIT              ; PRG_magic
$e40028 : 0000 014c                            DC.L      $0000014c                 ; PRG_tsize
$e4002c : 0000 0000                            DC.L      $00000000                 ; PRG_dsize
$e40030 : 0000 0000                            DC.L      $00000000                 ; PRG_bsize
$e40034 : 0000 0000                            DC.L      $00000000                 ; PRG_ssize
$e40038 : 0000 0000                            DC.L      $00000000                 ; PRG_res1
$e4003c : 0000 0000                            DC.L      $00000000                 ; PRGFLAGS
$e40040 : 0000                                 DC.W      $0000                     ; ABSFLAG

$e40042 : 303c 00fc                ROMDISK_INIT:move.w   #252,d0                   ; 252kb ROMDISK
$e40046 : 31fc 0200 0800                       move.w    #512,(ROMDISK_BPB).w      ; 512 bytes per sector
$e4004c : 31fc 0001 0802                       move.w    #1,(ROMDISK_BPB+2).w      ; 1 sector per cluster
$e40052 : 31fc 0200 0804                       move.w    #512,(ROMDISK_BPB+4).w    ; 512 bytes per cluster
$e40058 : 31fc 0001 0806                       move.w    #1,(ROMDISK_BPB+6).w      ; sector length of root directory
$e4005e : 31fc 0001 0810                       move.w    #1,(ROMDISK_BPB+16).w     ; 16 bit FAT
$e40064 : e340                                 asl.w     #1,d0
$e40066 : 3200                                 move.w    d0,d1
$e40068 : 31c1 080e                            move.w    d1,(ROMDISK_BPB+14).w     ; 504 clusters per disk
$e4006c : d27c 00ff                            add.w     #$ff,d1
$e40070 : e041                                 asr.w     #8,d1
$e40072 : 31c1 0808                            move.w    d1,(ROMDISK_BPB+8).w      ; 2 sectors per FAT
$e40076 : 31c1 080a                            move.w    d1,(ROMDISK_BPB+10).w     ; starting sector of second FAT = 2
$e4007a : 31c1 080c                            move.w    d1,(ROMDISK_BPB+12).w     ; starting sector of data = 2*2+1 = 5
$e4007e : d378 080c                            add.w     d1,(ROMDISK_BPB+12).w
$e40082 : 0678 0001 080c                       addi.w    #1,(ROMDISK_BPB+12).w

$e40088 : 303c 000f                            move.w    #15,d0                    ; ROMDISK is drive P
$e4008c : 7e01                                 moveq     #1,d7
$e4008e : e1af                                 lsl.l     d0,d7
$e40090 : 8fb8 04c2                            or.l      d7,(_drvbits).w           ; enable drive

$e40094 : 21f8 0472 0816                       move.l    (hdv_bpb).w,(ROMDISK_HDV_BPB).w
$e4009a : 21f8 0476 081a                       move.l    (hdv_rw).w,(ROMDISK_HDV_RW).w
$e400a0 : 21f8 047e 081e                       move.l    (hdv_mediach).w,(ROMDISK_HDV_MEDIACH).w

$e400a6 : 41fa 0024                            lea       ROMDISK_BPB(pc),a0
$e400aa : 21c8 0472                            move.l    a0,(hdv_bpb).w
$e400ae : 41fa 002c                            lea       ROMDISK_RW(pc),a0
$e400b2 : 21c8 0476                            move.l    a0,(hdv_rw).w
$e400b6 : 41fa 0034                            lea       ROMDISK_MEDIACH(pc),a0
$e400ba : 21c8 047e                            move.l    a0,(hdv_mediach).w

$e400be : 487a 00ae                            pea       ROMDISK_insttext(pc)
$e400c2 : 3f3c 0009                            move.w    #9,-(sp)
$e400c6 : 4e41                                 trap      #1
$e400c8 : 5c4f                                 addq.w    #6,sp
$e400ca : 4e75                                 rts

$e400cc : 302f 0004                ROMDISK_BPB:move.w    4(sp),d0
$e400d0 : 2078 0816                            movea.l   (ROMDISK_HDV_BPB).w,a0
$e400d4 : 43fa 002e                            lea       ROMDISK_BPB_IMP(pc),a1
$e400d8 : 6000 001e                            bra       ROMDISK_CHKDRV
$e400dc : 302f 000e                ROMDISK_RW: move.w    $e(sp),d0
$e400e0 : 2078 081a                            movea.l   (ROMDISK_HDV_RW).w,a0
$e400e4 : 43fa 0026                            lea       ROMDISK_RW_IMP(pc),a1
$e400e8 : 6000 000e                            bra       ROMDISK_CHKDRV
$e400ec : 302f 0004                ROMDISK_MEDIACH:move.w 4(sp),d0
$e400f0 : 2078 081e                            movea.l   (ROMDISK_HDV_MEDIACH).w,a0
$e400f4 : 43fa 0074                            lea       ROMDISK_MEDIACH_IMP(pc),a1
$e400f8 : b07c 000f                ROMDISK_CHKDRV:cmp.w  #15,d0                    ; ROMDISK is drive P
$e400fc : 6600 0004                            bne       ROMDISK_CHKDRVx
$e40100 : 2049                                 movea.l   a1,a0
$e40102 : 4ed0                     ROMDISK_CHKDRVx:jmp   (a0)

$e40104 : 203c 0000 0800           ROMDISK_BPB_IMP:move.l #ROMDISK_BPB,d0          ; return ROMDISK_BPB
$e4010a : 4e75                                 rts

$e4010c : 207c 00e4 0222           ROMDISK_RW_IMP:movea.l #ROMDISK_BASE,a0
$e40112 : 7000                                 moveq     #0,d0
$e40114 : 302f 000c                            move.w    $c(sp),d0                 ; start sector number
$e40118 : e188                                 lsl.l     #8,d0
$e4011a : e388                                 lsl.l     #1,d0
$e4011c : d1c0                                 adda.l    d0,a0
$e4011e : 7400                                 moveq     #0,d2
$e40120 : 342f 000a                            move.w    $a(sp),d2                 ; number of sectors
$e40124 : e98a                                 lsl.l     #4,d2
$e40126 : 226f 0006                            movea.l   6(sp),a1                  ; destination buffer
$e4012a : 2009                                 move.l    a1,d0
$e4012c : 082f 0000 0005                       btst      #0,5(sp)                  ; write access? That will fail!
$e40132 : 6704                                 beq.s     ROMDISK_RW_IMP2
$e40134 : 70f3                                 moveq     #-13,d0
$e40136 : 4e75                                 rts

$e40138 : 0800 0000                ROMDISK_RW_IMP2:btst  #0,d0                     ; the destination has to be an even address for fast copying
$e4013c : 6618                                 bne.s     ROMDISK_RW_IMP4
$e4013e : 22d8                     ROMDISK_RW_IMP3:move.l (a0)+,(a1)+
$e40140 : 22d8                                 move.l    (a0)+,(a1)+
$e40142 : 22d8                                 move.l    (a0)+,(a1)+
$e40144 : 22d8                                 move.l    (a0)+,(a1)+               ; copy 32 bytes
$e40146 : 22d8                                 move.l    (a0)+,(a1)+
$e40148 : 22d8                                 move.l    (a0)+,(a1)+
$e4014a : 22d8                                 move.l    (a0)+,(a1)+
$e4014c : 22d8                                 move.l    (a0)+,(a1)+
$e4014e : 5382                                 subq.l    #1,d2
$e40150 : 6b14                                 bmi.s     ROMDISK_RW_IMP6
$e40152 : 66ea                                 bne.s     ROMDISK_RW_IMP3
$e40154 : 6010                                 bra.s     ROMDISK_RW_IMP6
$e40156 : e74a                     ROMDISK_RW_IMP4:lsl.w #3,d2
$e40158 : 12d8                     ROMDISK_RW_IMP5:move.b (a0)+,(a1)+
$e4015a : 12d8                                 move.b    (a0)+,(a1)+
$e4015c : 12d8                                 move.b    (a0)+,(a1)+
$e4015e : 12d8                                 move.b    (a0)+,(a1)+
$e40160 : 5382                                 subq.l    #1,d2
$e40162 : 6b02                                 bmi.s     ROMDISK_RW_IMP6
$e40164 : 66f2                                 bne.s     ROMDISK_RW_IMP5
$e40166 : 4280                     ROMDISK_RW_IMP6:clr.l d0                     ; no error
$e40168 : 4e75                                 rts

$e4016a : 4280                     ROMDISK_MEDIACH_IMP:clr.l d0                 ; no error
$e4016c : 4e75                                 rts

$e4016e : 3032 3533 4b20 524f 4d44+ROMDISK_insttext:DC.B '0253K ROMDISK installed as P:\r\n',0
$e4018e : 0000 0000 ffff ffff                  DC.L      0          ; no relocation table
$e40196 : ffff ffff ffff ffff                  DCB.B     110,$ff    ; fill the 512-page to the end with $FF

$e40200 : 0000 0000                CA2_NEXT:   DC.L      $000000
$e40204 : 00e4 0222                CA2_INIT:   DC.L      ROMDISK_BASE
$e40208 : 00e4 0222                CA2_RUN:    DC.L      ROMDISK_BASE
$e4020c : 1869                     CA2_TIME:   DC.W      $1869
$e4020e : 99c6                     CA2_DATE:   DC.W      $99c6
$e40210 : 0003 fa00                CA2_SIZE:   DC.L      509 * 512
$e40214 : 4341 5254 524f 4d2e 494d+CA2_NAME:   DC.B      'CARTROM.IMG',0
$e40220 : ffff                                 DC.W      $ffff

$e40222 : 0000 0000 ffff 0400      ROMDISK_BASE:DC.B     $00,$00,$00,$00,$ff,$ff,$04,$00 ;509 * 512-byte sectors
$e4022a : 0500 0600 0700 0800                  DC.B      $05,$00,$06,$00,$07,$00,$08,$00
$e40232 : 0900 0a00 0b00 ffff                  DC.B      $09,$00,$0a,$00,$0b,$00,$ff,$ff
$e4023a : 0d00 0e00 0f00 1000                  DC.B      $0d,$00,$0e,$00,$0f,$00,$10,$00
$e40242 : 1100 1200 1300 1400                  DC.B      $11,$00,$12,$00,$13,$00,$14,$00
$e4024a : 1500 1600 1700 1800                  DC.B      $15,$00,$16,$00,$17,$00,$18,$00
$e40252 : 1900 1a00 1b00 1c00                  DC.B      $19,$00,$1a,$00,$1b,$00,$1c,$00
$e4025a : 1d00 1e00 1f00 2000                  DC.B      $1d,$00,$1e,$00,$1f,$00,$20,$00
$e40262 : 2100 2200 2300 2400                  DC.B      $21,$00,$22,$00,$23,$00,$24,$00
$e4026a : 2500 2600 2700 2800                  DC.B      $25,$00,$26,$00,$27,$00,$28,$00
...

Atari ST Book: Internal Registers

By looking at the ROM code, available software and the hardware layout I can now document most specialty registers in the ST Book from Atari. I hope it is useful to you. Also available at GitHub.

The main ROM for the ST Book starts at $e0000 and ends at $e3ffff (256kb large). At $e40000 starts the ROM-Disk (also 256kb large), which is automatically activated as drive P.

YM Port A

Bit Description
0 Side Select (S0SEL)
1 Disk 0 Select (D0SEL)
2 Disk 1 Select (D1SEL)
3 MAX241 Pin 20 (TI3) – RS232 RTS (/MFPRTS)
4 MAX241 Pin 6 (TI2) – RS232 DTR (/MFPDTR)
5 Centronics Strobe (STROBE)
6 IDE Reset (IDERESET)
7 FDD Dense Selection (FDD_DENSE_SEL)

Autovector Interrupt Level 7 /POWER FAIL (NMI)

MFP Input Pins

Bit Description
7 Power Alarms (Power Alarms) (POWER_ALARMS-) = (/SRCLOW /RTC_ALARM /POWERON)
6 MAX241 Pin 22 (RD4) – RS232 CD (/MFPRI)
5 XDISKINT
4 KTDK (VBL) (KTOK)
3 XBLTINT
2 MAX241 Pin 26 (RD3) – RS232 CTS (/MFPCTS)
1 MAX241 Pin 5 (RD2) – RS232 DCD (/MFPCD)
0 Centronics Busy (BUSY)

$D0000 – Unknown $D0004 – Unknown

$FF820B

Bit Description
0 0: enable combo -> shadow controller video transfer, 1: disable combo -> shadow controller video transfer

$FF827E

Bit Description
0 0: Shadow Chip OFF
1 0: /SHIFTER_OFF output (Unused in STBook)
2 0: POWER_OFF (Turns off main VCC when high)
3 0: /22ON output (turns off LCD Bias when HIGH)
4 0: REFRESH_MACHINE output (turns on refresh controller)
5 0: RS–232_OFF output (turns off +/- 10 generator)
6 0: (Unused in STBook)
7 0: MTR_POWER_ON (turns on IDE drive motor supply)

$FF9200 (Configuration/Signal register)

Bit Description
0 0: Power Button pressed /(POWER_SWITCH)
1 0: Top is closed /(TOP_CLOSED)
2 0: RTC Alarm triggered /(RTC_ALARM)
3 0: “Common Source” voltage level below 7.2V /(SOURCE_DEAD) – triggers an NMI
4 0: “Common Source” voltage level below 8.8V /(SOURCE_LOW)
5 0: External Modem (Pin 10 on J204) /(MODEM_WAKE)
6 Reserved (always 1)
7 0: Triggered from the Expansion Port /(EXPANSION_WAKE)
8 Reserved
9 Reserved
10 Reserved
11 Reserved
12 Reserved
13 SELF TEST (?)
14 LOW SPEED FLOPPY
15 DMA AVAILABLE

$FF9210 8-bit Common Power Voltage Level in 100mV steps

$FF9214 8-bit Reference Voltage Level in 100mV steps

Atari ST Book: POWER.PRG listing

Commented assembly listing for the POWER.PRG in an Atari ST-Book. Also available on GitHub.

main:       pea       ($0000).w
            move.l    #'_PWR',-(sp)
            jsr       (_getcookie).l            ; _PWR cookie installed?
            addq.l    #8,sp
            tst.w     d0
            beq       install                   ; (no -> install app)
_term:      clr.w     -(sp)
            trap      #1                        ; Pterm0()
            illegal

install:    move.l    #infoText,-(sp)
            move.w    #9,-(sp)
            trap      #1                        ; Cconws()
            addq.w    #6,sp

            pea       (cookiePtr).l
            pea       ($0000).w
            jsr       (_getcookie).l
            addq.l    #8,sp
            addq.l    #8,(cookiePtr).l

            movea.l   4(sp),a0                  ; BASEPAGE
            move.l    (cookiePtr).l,d0
            lsl.l     #3,d0
            move.l    $18(a0),d1                ; Pointer to beginning of BSS segment
            add.l     $1c(a0),d1                ; + BSS segment size
            sub.l     a0,d1                     ; - BASEPAGE address = size of the app memory
            add.l     d1,d0
            move.l    (cookiePtr).l,-(sp)
            move.l    d0,(cookiePtr).l
            move.l    a0,-(sp)
            move.l    #cookie_blankScreenTimer,-(sp)
            move.l    #'_PWR',-(sp)
            jsr       (setCookie).l
            adda.l    #$10,sp
            tst.l     d0
            bmi.s     _term
            bpl       install2
            subi.l    #$40,(cookiePtr).l
            clr.b     (v_opnwk_called).l

install2:   clr.l     -(sp)
            move.w    #$20,-(sp)
            trap      #1                        ; oldval = Super(0L)
            addq.w    #6,sp
            move.l    d0,-(sp)
            move.w    #$20,-(sp)                ; Super(oldval)

            jsr       (setupVectors).l

            move.b    #0,(ahdiDetected).l       ; AHDI check
            movea.l   (pun_ptr).w,a0
            cmpi.l    #'AHDI',$52(a0)           ; P_cookie in PUN_INFO in the AHDI driver
            bne       install3
            lea       $52(a0),a1
            cmpa.l    $56(a0),a1                ; check P_cookptr in the AHDI driver
            bne       install3
            cmpi.w    #$500,$5a(a0)             ; check P_version in the AHDI driver
            blt       install3
            move.b    #1,(ahdiDetected).l
            move.b    $63(a0),(ahdiStandbyTime).l ;idle time limit to spin down IDE unit 0

install3:   move.l    #powerAlarm,(mfp_MonochromeDetect).w ;power alarm
            bclr      #7,(aer).w                ; clear pending "Monochrome Monitor Detect"
            bset      #7,(iera).w               ; enable "Monochrome Monitor Detect" interrupt
            bset      #7,(imra).w               ; enable "Monochrome Monitor Detect" interrupt mask
            move.l    #powerAlarm,(Level7IRQ).w ; power fail

            move.w    #3,(countdownVBLTimer).l  ; reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            move.l    (cookie_blankScreenTimer).l,(blankScreenTimerCounter).l
            bclr      #3,(lcdPowerControlShadow).w ;LCD on
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l
            move.l    (cookie_sleepTimer).l,(sleepTimerCounter).l

            trap      #1                        ; Super(oldval)
            addq.w    #6,sp
            clr.w     -(sp)
            move.l    (cookiePtr).l,-(sp)
            move.w    #$31,-(sp)
            trap      #1                        ; Ptermres()
            illegal

***************************************************************************
*
***************************************************************************
_getcookie: movem.l   d6-d7/a6,-(sp)
            move.l    $10(sp),d6                ; cookie
            movea.l   $14(sp),a6                ; ptr to the cookie value, if found
            clr.l     d7
            move.l    #1,-(sp)
            move.w    #$20,-(sp)
            trap      #1
            tst.l     d0
            bne       _getcookies
            move.l    #0,2(sp)
            move.w    #$20,(sp)
            trap      #1
            move.l    d0,d7
_getcookies:addq.l    #6,sp
            movea.l   (_p_cookies).w,a0
            move.l    a0,d0
            beq       _getcookiee
_getcookiel:move.l    (a0),d0
            cmp.l     d0,d6
            beq       _getcookie2
            tst.l     d0
            beq       _getcookiee
            addq.l    #8,a0
            bra.s     _getcookiel
_getcookie2:move.l    a6,d0
            beq       _getcookie3
            move.l    4(a0),(a6)
_getcookie3:moveq     #1,d0
_getcookiee:move.l    d0,d6
            tst.l     d7
            beq       _getcookiex
            move.l    d7,-(sp)
            move.w    #$20,-(sp)
            trap      #1
            addq.l    #6,sp
_getcookiex:move.l    d6,d0
            movem.l   (sp)+,d6-d7/a6
            rts

***************************************************************************
*
***************************************************************************
setCookie:  link      a6,#0
            move.l    d7,-(sp)
            moveq     #0,d7
            move.l    #1,-(sp)
            move.w    #$20,-(sp)
            trap      #1
            tst.l     d0
            bne       setCookies
            move.l    #0,2(sp)
            move.w    #$20,(sp)
            trap      #1
            move.l    d0,d7
setCookies: addq.l    #6,sp
            movea.l   (_p_cookies).w,a0
            move.l    a0,d0
            beq       setCookiee
            moveq     #0,d0
setCookiel: addq.l    #1,d0
            tst.l     (a0)
            beq       setCookie2
            addq.l    #8,a0
            bra.s     setCookiel
setCookie2: cmp.l     4(a0),d0
            beq       setCookiee2
            move.l    4(a0),$c(a0)
            clr.l     8(a0)
            move.l    8(a6),(a0)
            move.l    $c(a6),4(a0)
            moveq     #0,d0
            bra       setCookiee5
setCookiee: cmpi.l    #2,$14(a6)
            blt       setCookiee4
            move.l    (resvector).w,(oldcookieresvector).l
            move.l    (resvalid).w,(oldcookieresvalid).l
            move.l    #cookieResetVector,(resvector).w
            move.l    #$31415926,(resvalid).w
            movea.l   $10(a6),a0
            move.l    a0,(_p_cookies).w
            move.l    8(a6),(a0)+
            move.l    $c(a6),(a0)+
            clr.l     (a0)+
            move.l    $14(a6),(a0)
            moveq     #1,d0
            bra       setCookiee5
cookieResetVector:clr.l (_p_cookies).w
            move.l    (oldcookieresvalid).l,(resvalid).w
            move.l    (oldcookieresvector).l,(resvector).w
            jmp       (a6)

setCookiee2:cmp.l     $14(a6),d0
            ble       setCookiee4
            move.l    d0,d1
            subq.l    #2,d1
            movea.l   (_p_cookies).w,a0
            movea.l   $10(a6),a1
            move.l    a1,(_p_cookies).w
setCookiee3:move.l    (a0)+,(a1)+
            move.l    (a0)+,(a1)+
            dbra      d1,setCookiee3
            move.l    8(a6),(a1)+
            move.l    $c(a6),(a1)+
            clr.l     (a1)+
            move.l    $14(a6),(a1)
            moveq     #1,d0
            bra       setCookiee5
setCookiee4:moveq     #$ff,d0
setCookiee5:tst.l     d7
            beq       setCookiex
            move.l    d0,8(a6)
            move.l    d7,-(sp)
            move.w    #$20,-(sp)
            trap      #1
            addq.l    #6,sp
            move.l    8(a6),d0
setCookiex: move.l    (sp)+,d7
            unlk      a6
            rts

***************************************************************************
*       reset vector - used to wake up the machine back                   *
***************************************************************************
resetVector:move.l    (oldresvalid).l,(resvalid).w
            move.l    (oldresvector).l,(resvector).w
            bclr      #2,(lcdPowerControlShadow).w ;power is on
            bclr      #3,(lcdPowerControlShadow).w ;LCD on
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l
            movea.l   (oldUSP).l,a0
            move      a0,usp
            movea.l   (oldSSP).l,sp
            clr.w     (saveOrRestoreFlag).l     ; Restore chip registers

            subi.l    #$2e,(savptr).w
            bsr       saveState_video
            bsr       saveState_halftone
            bsr       saveState_MFP
            bsr       saveState_MIDI
            bsr       saveState_PSG
            bsr       saveState_time
            bsr       saveState_Keyboard
            bsr       saveState_AHDI
            move.l    #0,-(sp)
            move.w    #$2f,-(sp)
            trap      #$e                       ; Waketime(0L) - clear Waketime
            addq.w    #6,sp
            addi.l    #$2e,(savptr).w

            bset      #7,(imra).w
            andi.b    #$f,(tcdcr).w
            move.b    #$df,(iprb).w
            movea.l   (etv_timer).w,a6
            subq.l    #2,sp
            move.w    #511-1,d7
resetVector2:clr.w    (sp)
            jsr       (a6)
            dbra      d7,resetVector2
            addq.l    #2,sp
            ori.b     #$50,(tcdcr).w

resetVector3:move.w   #30,d1
resetVector4:move.b   (STConfig+1).l,d0
            cmp.b     #$fe,d0                   ; Power button still pressed?
            beq.s     resetVector3              ; yes -> continue to wait
            cmp.b     #$ff,d0                   ; no pressed?
            bne       resetVector5              ; -> done
            btst      #7,(gpip).w               ; power alarm IRQ pending?
            beq.s     resetVector3              ; -> continue to wait
            btst      #5,(iprb).w
            beq.s     resetVector4
            move.b    #$df,(iprb).w
            dbra      d1,resetVector4

resetVector5:move.b   #$7f,(ipra).w
            movem.l   (saveRegs).l,d0-d7/a0-a6
            rte

***************************************************************************
*       NMI or MFP IRQ #15                                                *
***************************************************************************
powerAlarm: btst      #2,(STConfig+1).l         ; wait for RTC alarm to be off
            bne       powerAlarm2
            bclr      #2,(rtc_mode+1).l         ; disable RTC alarm
            bra.s     powerAlarm

powerAlarm2:movem.l   d0-d2,(saveRegs).l
            move.b    (STConfig+1).l,d0         ; IRQ source
            move.b    d0,d1
            and.b     #$a4,d1
            cmp.b     #$a4,d1                   ; _only_ expansion wake, modem wake or RTC wake?
            bne       powerAlarmExit            ; these are always ignored ->
            btst      #3,d0                     ; power dead?
            beq       gotoSleep                 ; -> force sleep
            btst      #0,d0                     ; power switch?
            beq       gotoSleep                 ; -> force sleep
            btst      #1,d0                     ; top closed?
            beq       gotoSleep                 ; -> force sleep
            cmp.b     #$ff,d0                   ; anything else?
            bne       powerAlarmExit            ; -> ignore it

* we got no IRQ source, wait for a bit to see if that changes
            move.w    #$1e,d1                   ; 31 scan lines
            move      sr,d2
            move      #$2700,sr
powerAlarml:move.b    (STConfig+1).l,d0         ; read the IRQ source
            cmp.b     #$ff,d0                   ; anything changed?
            bne       gotoSleep                 ; -> force sleep
            btst      #5,(iprb).w               ; scan line IRQ?
            beq.s     powerAlarml               ; no -> continue waiting
            move.b    #$df,(iprb).w             ; confirm IRQ
            dbra      d1,powerAlarml            ; wait 31 scan lines ->
            move      d2,sr
powerAlarmExit:movem.l (saveRegs).l,d0-d2
            move.b    #$7f,(isra).w             ; re-enable MFP IRQ #15
            rte                                 ; continue normally

***************************************************************************
*       let the system go to sleep                                        *
***************************************************************************
gotoSleep:  move      #$2700,sr                 ; stop all interrupts
            movem.l   d3-d7/a0-a6,(saveRegs+4*3).l ;save all remaining registers
            btst      #3,(STConfig+1).l         ; power dead?
            beq       shutdownPower             ; -> force shutdown

            subi.l    #$2e,(savptr).w           ; decrement re-entrancy stack for xbios
            move.l    #1,-(sp)
            move.w    #$2f,-(sp)
            trap      #$e                       ; Waketime(1L) - enable Waketime
            addq.w    #6,sp
            addi.l    #$2e,(savptr).w           ; re-increment re-entrancy stack for xbios
            tst.l     d0
            beq       shutdownPower

            move.b    #$c,(ide_seccn).l
            move.b    #$e2,(ide_comst).l        ; IDE standby command
            addi.l    #11*50,(blankScreenTimerCounter).l    ; wait for 11s
            addi.l    #11*50,(sleepTimerCounter).l
            movea.l   (etv_timer).w,a6
            subq.l    #2,sp
            move.w    #511-1,d7
gotoSleep2: clr.w     (sp)                      ; calling etv_timer as ~10s have passed
            jsr       (a6)
            dbra      d7,gotoSleep2
            addq.l    #2,sp

gotoSleep3: move.b    #30,d0
gotoSleep4: btst      #7,(gpip).w
            beq.s     gotoSleep3
            btst      #5,(iprb).w
            beq.s     gotoSleep4
            move.b    #$df,(iprb).w
            dbra      d0,gotoSleep4
            move.b    #$7f,(isra).w
            movem.l   (saveRegs).l,d0-d7/a0-a6
            rte

***************************************************************************
*       shut the system down because of a power failure                   *
***************************************************************************
shutdownPower:move    usp,a0
            move.l    a0,(oldUSP).l
            move.l    sp,(oldSSP).l
            move.w    #1,(saveOrRestoreFlag).l  ; Save chip registers
            bclr      #7,(imra).w
            bset      #3,(lcdPowerControlShadow).w ;LCD off
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l
            subi.l    #$2e,(savptr).w
            bsr       saveState_video
            bsr       saveState_halftone
            bsr       saveState_FDC
            bsr       saveState_bios_rwabs
            bsr       saveState_AHDI
            bsr       saveState_PSG
            bsr       saveState_MFP
            addi.l    #$2e,(savptr).w
            move.l    (resvalid).w,(oldresvalid).l
            move.l    (resvector).w,(oldresvector).l
            move.l    #$31415926,(resvalid).w
            move.l    #resetVector,(resvector).w
            move.b    (lcdPowerControlShadow).w,d0
            bset      #4,d0                     ; REFRESH_MACHINE output
            move.b    d0,(lcdPowerControl).l
            bset      #0,(synmod+1).l           ; disable combo -> shadow controller video transfer
debouncel:  move.w    #100,d0                   ; wait for up to 500ms for the power button to be released
            clr.b     (tacr).w                  ; stop timer a
            bclr      #5,(iera).w               ; disable timer a
            bclr      #5,(imra).w               ; clear pending timer a interrupts
            bset      #5,(iera).w               ; enable timer a
            move.b    #$c0,(tadr).w
            move.b    #5,(tacr).w               ; timer a at 5ms
debouncel2: cmpi.b    #$fe,(STConfig+1).l       ; Power button still pressed?
            beq.s     debouncel
            btst      #5,(ipra).w               ; timer a not triggered
            beq.s     debouncel2
            move.b    #$df,(ipra).w             ; clear timer a interrupt
            dbra      d0,debouncel2             ; continue waiting
            bset      #2,(lcdPowerControlShadow).w ;power off
            move.b    (lcdPowerControlShadow).w,d0
            bset      #4,d0                     ; REFRESH_MACHINE output
            move.b    d0,(lcdPowerControl).l
* The following opcode is pre-fetched, the CPU doesn't need to touch this memory after the previous opcode anymore
endlessLoop:bra.s     endlessLoop               ; wakeup via reset!
            trap      #1                        ; this is useless stuff...
            addq.w    #6,sp
            illegal

***************************************************************************
*   save/restore the XBIOS time
*   Force reading the time from the RTC to update the GEMDOS time after sleep
***************************************************************************
saveState_time:tst.w  (saveOrRestoreFlag).l
            bne       saveState_timee
            move.w    #$17,-(sp)
            trap      #$e                       ; Gettime()
            addq.w    #2,sp
saveState_timee:rts

***************************************************************************
*   save/restore the MFP state
***************************************************************************
saveState_MFP:lea     (sav_MFP).l,a0
            tst.w     (saveOrRestoreFlag).l
            beq       saveState_MFPe
            move.b    (aer).w,(a0)+
            move.b    (ddr).w,(a0)+
            move.b    (iera).w,(a0)+
            move.b    (ierb).w,(a0)+
            move.b    (imra).w,(a0)+
            move.b    (imrb).w,(a0)+
            move.b    (vr).w,(a0)+
            move.b    (tacr).w,(a0)+
            move.b    (tbcr).w,(a0)+
            move.b    (tcdcr).w,(a0)+
            move.b    (scr).w,(a0)+
            move.b    (ucr).w,(a0)+
            move.b    (rsr).w,(a0)+
            move.b    (tsr).w,(a0)+
            move.b    #$c0,(sav_MFP+16).l
            lea       (tadr).w,a0
            lea       (tacr).w,a1
            bsr       saveState_MFPsave
            move.b    d0,(sav_MFP+14).l
            lea       (tbdr).w,a0
            lea       (tbcr).w,a1
            bsr       saveState_MFPsave
            move.b    d0,(sav_MFP+15).l
            lea       (tddr).w,a0
            lea       (tcdcr).w,a1
            bsr       saveState_MFPsave
            move.b    d0,(sav_MFP+17).l
            rts

saveState_MFPe:move.b (a0)+,(aer).w
            move.b    (a0)+,(ddr).w
            move.b    (a0)+,(iera).w
            move.b    (a0)+,(ierb).w
            move.b    (a0)+,(imra).w
            move.b    (a0)+,(imrb).w
            move.b    (a0)+,(vr).w
            move.b    (a0)+,(tacr).w
            move.b    (a0)+,(tbcr).w
            move.b    (a0)+,(tcdcr).w
            move.b    (a0)+,(scr).w
            move.b    (a0)+,(ucr).w
            move.b    (a0)+,(rsr).w
            move.b    (a0)+,(tsr).w
            move.b    (sav_MFP+14).l,(tadr).w
            move.b    (sav_MFP+15).l,(tbdr).w
            move.b    (sav_MFP+16).l,(tcdr).w
            move.b    (sav_MFP+17).l,(tddr).w
            move.b    (gpip).w,d0
            move.b    (aer).w,d1
            eor.b     d1,d0
            btst      #7,d0
            bne       saveState_MFP2
            btst      #7,(ipra).w
            bne       saveState_MFP2
            bchg      #7,(aer).w
            bchg      #7,(aer).w
saveState_MFP2:btst   #6,d0
            bne       saveState_MFP3
            btst      #6,(ipra).w
            bne       saveState_MFP3
            bchg      #6,(aer).w
            bchg      #6,(aer).w
saveState_MFP3:btst   #5,d0
            bne       saveState_MFP4
            btst      #7,(iprb).w
            bne       saveState_MFP4
            bchg      #5,(aer).w
            bchg      #5,(aer).w
saveState_MFP4:btst   #4,d0
            bne       saveState_MFP5
            btst      #6,(iprb).w
            bne       saveState_MFP5
            bchg      #4,(aer).w
            bchg      #4,(aer).w
saveState_MFP5:btst   #3,d0
            bne       saveState_MFP6
            btst      #3,(iprb).w
            bne       saveState_MFP6
            bchg      #3,(aer).w
            bchg      #3,(aer).w
saveState_MFP6:btst   #2,d0
            bne       saveState_MFP7
            btst      #2,(iprb).w
            bne       saveState_MFP7
            bchg      #2,(aer).w
            bchg      #2,(aer).w
saveState_MFP7:btst   #1,d0
            bne       saveState_MFP8
            btst      #1,(iprb).w
            bne       saveState_MFP8
            bchg      #1,(aer).w
            bchg      #1,(aer).w
saveState_MFP8:btst   #0,d0
            bne       saveState_MFP9
            btst      #0,(iprb).w
            bne       saveState_MFP9
            bchg      #0,(aer).w
            bchg      #0,(aer).w
saveState_MFP9:rts

saveState_MFPsave:move.b (tcdcr).w,d0
            and.b     #7,d0
            move.b    d0,(tcdcr).w
            move.b    #4,(tcdr).w
            or.b      #$70,d0
            move.b    (a1),d2
            or.b      #7,d2
            move.b    d2,(a1)
            moveq     #1,d1
            lea       (tcdr).w,a1
            lea       (tcdcr).w,a2
            cmp.b     (a0),d1
            bne.s     $107f8
            move.b    d0,(a2)
saveState_MFPsavel:cmp.b (a0),d1
            bne       saveState_MFPsavee
            cmp.b     (a1),d1
            bne.s     saveState_MFPsavel
saveState_MFPsavee:move.b (a0),d0
            rts

***************************************************************************
*   save/restore the keyboard state
*   Reset IKBD on restore, and reset keyboard tables
***************************************************************************
saveState_Keyboard:tst.w (saveOrRestoreFlag).l
            bne       saveState_Keyboarde
            move.b    #3,(keyctl).w
            move.b    #$96,(keyctl).w
            move.l    #ikbdResetCommand,-(sp)
            move.w    #2,-(sp)
            move.w    #$19,-(sp)
            trap      #$e                       ; Ikbdws(2, ikbdResetCommand)
            addq.w    #8,sp
            move.l    #-1,-(sp)
            move.l    #-1,-(sp)
            move.l    #-1,-(sp)
            move.w    #$10,-(sp)
            trap      #$e                       ; Keytbl(-1L, -1L, -1L)
            adda.w    #$e,sp
            movea.l   d0,a0
            movea.l   (a0)+,a3
            movea.l   (a0)+,a4
            movea.l   (a0)+,a5
            move.w    #$18,-(sp)
            trap      #$e                       ; Bioskeys()
            addq.w    #2,sp
            move.l    a5,-(sp)
            move.l    a4,-(sp)
            move.l    a3,-(sp)
            move.w    #$10,-(sp)
            trap      #$e                       ; Keytbl(,,)
            adda.w    #$e,sp
saveState_Keyboarde:rts

ikbdResetCommand:DC.B $80,$01

***************************************************************************
*   save/restore the MIDI port state
*   Just reset MIDI on restore
***************************************************************************
saveState_MIDI:tst.w  (saveOrRestoreFlag).l
            bne       saveState_MIDIe
            move.b    #3,(midictl).w
            move.b    #$95,(midictl).w
saveState_MIDIe:rts

***************************************************************************
*   save/restore the sound chip state
***************************************************************************
saveState_PSG:movem.l d3-d4/a3,-(sp)
            moveq     #$f,d3
            lea       (save_psg).l,a3
            tst.w     (saveOrRestoreFlag).l
            beq       saveState_PSGe
            moveq     #$ff,d4
saveState_PSGl:addq.w #1,d4
            move.w    d4,-(sp)
            move.w    #0,-(sp)
            move.w    #$1c,-(sp)
            trap      #$e                       ; Giaccess()
            addq.w    #6,sp
            move.b    d0,(a3)+
            dbra      d3,saveState_PSGl
            ori.b     #$37,(save_psg+7).l
            movem.l   (sp)+,d3-d4/a3
            rts

saveState_PSGe:moveq  #$7f,d4
saveState_PSGel:addq.w #1,d4
            move.b    (a3)+,d0
            move.w    d4,-(sp)
            move.w    d0,-(sp)
            move.w    #$1c,-(sp)
            trap      #$e                       ; Giaccess()
            addq.w    #6,sp
            dbra      d3,saveState_PSGel
            movem.l   (sp)+,d3-d4/a3
            rts

***************************************************************************
*   save/restore the shifter state
***************************************************************************
saveState_video:tst.w (saveOrRestoreFlag).l
            beq       saveState_videoe
            move.b    (v_bas_h).w,(sav_v_bas_h).l
            move.b    (v_bas_m).w,(sav_v_bas_m).l
            move.b    (v_bas_l).w,(sav_v_bas_l).l
            move.b    (synmod+1).w,(sav_synmod).l
            move.w    (palette).w,(sav_palette0).l
            move.b    (v_shf_mod+1).w,(sav_v_shf_mod).l
            rts
saveState_videoe:move.b (sav_v_bas_h).l,(v_bas_h).w
            move.b    (sav_v_bas_m).l,(v_bas_m).w
            move.b    (sav_v_bas_l).l,(v_bas_l).w
            move.b    (sav_synmod).l,(synmod+1).w
            move.w    (sav_palette0).l,(palette).w
            move.b    (sav_v_shf_mod).l,(v_shf_mod+1).w
            move.b    #0,(v_pixscroll+1).w
            rts

***************************************************************************
*   save/restore the blitter halftone data
***************************************************************************
saveState_halftone:move.w #$1e,d0
            lea       (Halftone).w,a0
            lea       (sav_halftone).l,a1
            tst.w     (saveOrRestoreFlag).l
            beq       saveState_halftonee
saveState_halftonel1:move.w (a0)+,(a1)+
            dbra      d0,saveState_halftonel1
            rts
saveState_halftonee:move.w (a1)+,(a0)+
            dbra      d0,saveState_halftonee
            rts

***************************************************************************
*   save/restore the BIOS device driver state
*   Read the boot sector to detect disk change and update BIOS disk status
***************************************************************************
saveState_bios_rwabs:tst.w (saveOrRestoreFlag).l
            beq       saveState_bios_rwabsl
            move.w    #0,-(sp)
            move.w    #0,-(sp)
            move.w    #1,-(sp)
            move.l    #0,-(sp)
            move.w    #0,-(sp)
            move.w    #4,-(sp)
            trap      #$d                       ; Rwabs(0, 0L, 1, 0, 0)
            adda.w    #$e,sp
            move.w    #1,-(sp)
            move.w    #0,-(sp)
            move.w    #1,-(sp)
            move.l    #0,-(sp)
            move.w    #0,-(sp)
            move.w    #4,-(sp)
            trap      #$d                       ; Rwabs(0, 0L, 1, 0, 1)
            adda.w    #$e,sp
saveState_bios_rwabsl:rts

***************************************************************************
*   save/restore the FDC state
***************************************************************************
saveState_FDC:tst.w   (saveOrRestoreFlag).l
            beq.s     saveState_bios_rwabsl
            move.w    #$190,(diskctl).w
            move.w    #$90,(diskctl).w
            rts

***************************************************************************
*   save/restore the AHDI state
*   Send harddrive to sleep/wakeup
***************************************************************************
saveState_AHDI:tst.b  (ahdiDetected).l
            bne       saveState_AHDIe
saveState_AHDI6:rts

saveState_AHDIe:moveq #0,d0
            andi.w    #7,d0
            lsl.w     #4,d0
            move.b    d0,(ide_headn).l
            tst.w     (saveOrRestoreFlag).l
            beq       saveState_AHDIe2
            move.b    #$e0,(ide_comst).l        ; IDE standby immediate
saveState_AHDI7:move.l #10*200,d0
            add.l     (_hz_200).w,d0
saveState_AHDIl:btst  #5,(gpip).w
            beq.s     saveState_AHDI3
            btst      #7,(ide_stat2).l
            beq.s     saveState_AHDI3
            cmp.l     (_hz_200).w,d0
            bhi.s     saveState_AHDIl
            moveq     #-1,d0
            bra.s     saveState_AHDIr
saveState_AHDI3:move.b (ide_comst).l,d0         ; IDE status register
            btst      #0,d0
            bne.s     saveState_AHDI5
            btst      #3,d0
            bne.s     saveState_AHDIr
            moveq     #0,d0
            bra.s     saveState_AHDIr
saveState_AHDI5:move.b (ide_param).l,d0
saveState_AHDIr:rts

saveState_AHDIe2:bsr  saveState_AHDIs
            tst.w     d0
            beq.s     saveState_AHDI6
            moveq     #0,d0
            andi.w    #7,d0
            lsl.w     #4,d0
            move.b    d0,(ide_headn).l
            move.b    (ahdiStandbyTime).l,(ide_seccn).l
            move.b    #$e2,(ide_comst).l        ; IDE standby command
            bra.s     saveState_AHDI7
saveState_AHDIs:andi.b #7,d0
            lsl.b     #4,d0
            move.b    d0,(ide_headn).l
            move.l    #5*200,d0
            add.l     (_hz_200).w,d0
            move.b    #$50,d1
saveState_AHDIs2:cmp.b (ide_stat2).l,d1
            beq.s     saveState_AHDIs3
            cmp.l     (_hz_200).w,d0
            bcc.s     saveState_AHDIs2
            moveq     #0,d0
            rts
saveState_AHDIs3:moveq #1,d0
            rts

***************************************************************************
*   patch all vectors to monitor image updates and/or keyboard/mouse input
*   as well as timeouts for sleep
***************************************************************************
setupVectors:
            * patch AES/VDI trap
            moveq    #$ff,d0
            trap      #2
            move.l    d0,(sav_screendrvfuncptr).l
            move.l    (trap_aesvdi).w,(oldtrap_aesvdi).l
            move.l    #new_trap2,(trap_aesvdi).w

            * patch BIOS trap
            move.l    (trap_bios).w,(oldtrap_bios).l
            move.l    #new_bios,(trap_bios).w

            * patch XBIOS trap
            move.l    (trap_xbios).w,(oldtrap_xbios).l
            move.l    #new_xbios,(trap_xbios).w

            * patch line A
            linea     #0                        ; Line-A Initialization
            move.l    d0,(lineA_variables).l
            move.l    a1,(lineA_fontHeaders).l
            move.w    #15,d0
            lea       (sav_lineA).l,a3
setupVectorsl:move.l  (a2)+,(a3)+
            dbra      d0,setupVectorsl
            move.l    (lineAexception).w,(old_lineA).l
            move.l    #new_lineA,(lineAexception).w

            * patch IKBD vectors (keyboard, joystick, MIDI, mouse)
            move.w    #$22,-(sp)
            trap      #$e                       ; Kbdvbase()
            addq.w    #2,sp
            movea.l   d0,a1
            lea       $10(a1),a1
            move.l    a1,(old_mousevecAddr).l
            move.l    (a1),(old_mousevec).l
            move.l    #new_mousevec,(a1)
            movea.l   d0,a1
            lea       -4(a1),a1
            move.l    (a1),(old_ikbd_neg4).l
            move.l    #new_ikbd_neg4,(a1)
            movea.l   d0,a1
            lea       $18(a1),a1
            move.l    (a1),(old_joyvec).l
            move.l    #new_joyvec,(a1)
            movea.l   d0,a1
            lea       $1c(a1),a1
            move.l    (a1),(old_midisysvec).l
            move.l    #new_midisysvec,(a1)

            * patch 50Hz timer
            move.l    (etv_timer).w,(old_evt_timer).l
            move.l    #new_etv_timer,(etv_timer).w

            * insert code into VBL queue
            movea.l   (_vblqueue).w,a0
            addq.l    #4,a0
setupVectorsl2:tst.l  (a0)+
            bne.s     setupVectorsl2
            move.l    #new_vbl,-4(a0)
            rts

***************************************************************************
*   Called 50 times per second
*   - if serial port monitoring is active, re-enable LCD
*   - if screen saver is active and timeout is reached, disable LCD
*   - if sleep timeout is active and reached, sleep machine
***************************************************************************
new_etv_timer:btst    #2,(rsr).w                ; serial port active?
            beq       new_etv_timer2            ; (no)
            move.l    (cookie_sleepTimer).l,(sleepTimerCounter).l
            tst.w     (cookie_watchSerialPortFlag).l ;monitor the RS232?
            beq       new_etv_timer2            ; (no)
            move.l    (cookie_blankScreenTimer).l,(blankScreenTimerCounter).l
            bclr      #3,(lcdPowerControlShadow).w ;LCD on
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l

new_etv_timer2:tst.l  (cookie_blankScreenTimer).l
            beq       new_etv_timer3
            subq.l    #1,(blankScreenTimerCounter).l
            bne       new_etv_timer3
            move.l    (cookie_blankScreenTimer).l,(blankScreenTimerCounter).l
            bset      #3,(lcdPowerControlShadow).w ;LCD off
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l

new_etv_timer3:tst.l  (cookie_sleepTimer).l
            beq       new_etv_timer4
            subq.l    #1,(sleepTimerCounter).l
            bne       new_etv_timer4
            move.l    (cookie_sleepTimer).l,(sleepTimerCounter).l
            pea       new_etv_timer4(pc)
            move      sr,-(sp)
            movem.l   d0-d2,(saveRegs).l
            jmp       (gotoSleep).l

new_etv_timer4:move.l (old_evt_timer).l,-(sp)
            rts

***************************************************************************
*   VBL vector patch
*   - After 3 VBLs disable the combo -> shadow controller video transfer
***************************************************************************
new_vbl:    tst.w     (cookie_videoPowerSaverFlag).l    ; disable the LCD after timeout?
            beq       new_vbl2                  ; (no)
            subq.w    #1,(countdownVBLTimer).l
            bne       new_vbl2
            bset      #4,(lcdPowerControlShadow).w ;REFRESH_MACHINE output
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l
            bset      #0,(synmod+1).l           ; disable combo -> shadow controller video transfer
new_vbl2:   nop
            rts

***************************************************************************
*   mouse vector patch
*   Any mouse activity:
*   - enables the transfer to the shadow controller for at least 3 VBL.
*   - reset screen saver, enable LCD
*   - re-trigger the sleep timer.
***************************************************************************
new_mousevec:move.w   #3,(countdownVBLTimer).l  ; reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            move.l    (cookie_blankScreenTimer).l,(blankScreenTimerCounter).l
            bclr      #3,(lcdPowerControlShadow).w ;LCD on
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l
            move.l    (cookie_sleepTimer).l,(sleepTimerCounter).l

            move.l    (old_mousevec).l,-(sp)
            rts

***************************************************************************
*   joystick vector patch
*   Any joystick activity:
*   - enables the transfer to the shadow controller for at least 3 VBL.
*   - reset screen saver, enable LCD
*   - re-trigger the sleep timer.
***************************************************************************
new_joyvec: move.w    #3,(countdownVBLTimer).l  ; reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            move.l    (cookie_blankScreenTimer).l,(blankScreenTimerCounter).l
            bclr      #3,(lcdPowerControlShadow).w ;LCD on
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l
            move.l    (cookie_sleepTimer).l,(sleepTimerCounter).l

            move.l    (old_joyvec).l,-(sp)
            rts

***************************************************************************
*   keyboard driver patch
*   Any keyboard activity:
*   - enables the transfer to the shadow controller for at least 3 VBL.
*   - reset screen saver, enable LCD
*   - re-trigger the sleep timer.
***************************************************************************
new_ikbd_neg4:move.w  #3,(countdownVBLTimer).l  ; reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            move.l    (cookie_blankScreenTimer).l,(blankScreenTimerCounter).l
            bclr      #3,(lcdPowerControlShadow).w ;LCD on
            move.b    (lcdPowerControlShadow).w,(lcdPowerControl).l
            move.l    (cookie_sleepTimer).l,(sleepTimerCounter).l

            move.l    (old_ikbd_neg4).l,-(sp)
            rts

***************************************************************************
*   MIDI vector patch
*   - any MIDI activity re-triggers the sleep timer.
***************************************************************************
new_midisysvec:move.l (cookie_sleepTimer).l,(sleepTimerCounter).l
            move.l    (old_midisysvec).l,-(sp)
            rts

***************************************************************************
*   AES/VDI patch
*   - #-1 enables the transfer to the shadow controller for at least 3 VBL.
*   - VDI calls except calls waiting for keyboard input also enable the
*     transfer to the shadow controller for at least 3 VBL.
*   - VDI v_opnwk() patches the mouse vector on the first call.
***************************************************************************
new_trap2:  cmp.w     #$73,d0                   ; VDI?
            bne       new_trap2b                ; (no)
            move.l    a0,-(sp)
            movea.l   d1,a0
            movea.l   (a0),a0
            move.w    (a0),(vdi_functionCode).l
            movea.l   (sp)+,a0
            cmpi.w    #$21,(vdi_functionCode).l ; vsin_mode() - Set input mode
            beq       new_trap2out
            cmpi.w    #$1f,(vdi_functionCode).l ; vrq_string() / vsm_string() - keyboard string input
            beq       new_trap2out
            cmpi.w    #$80,(vdi_functionCode).l ; vq_key_s() - Get shift key status
            beq       new_trap2out
            move.l    #trap_vdi_tailPatch,-(sp)
            move.w    4(sp),-(sp)
            ori.w     #$2000,(sp)
            move.l    (oldtrap_aesvdi).l,-(sp)
            rts

new_trap2b: cmp.w     #$ffff,d0
            bne       new_trap2out
            move.l    #trap_aesvdi_m1,d0
            rte

new_trap2out:move.l   (oldtrap_aesvdi).l,-(sp)
            rts

trap_vdi_tailPatch:move.w #3,(countdownVBLTimer).l ;reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            cmpi.w    #1,(vdi_functionCode).l   ; v_opnwk()
            beq       trap_aesvdi_tailPatch2
            rte

trap_aesvdi_tailPatch2:bset #0,(v_opnwk_called).l
            bne       trap_aesvdi_tailPatch3
            move.l    a1,-(sp)
            movea.l   (old_mousevecAddr).l,a1
            move.l    (a1),(old_mousevec).l
            move.l    #new_mousevec,(a1)
            movea.l   (sp)+,a1
trap_aesvdi_tailPatch3:rte

trap_aesvdi_m1:movea.l (sav_screendrvfuncptr).l,a0
            jsr       (a0)
            move.w    #3,(countdownVBLTimer).l  ; reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            rts

***************************************************************************
*   BIOS patch
*   - Bconout() enables the transfer to the shadow controller
*     for at least 3 VBL.
*   - Bconin(), Rwabs() and Getbpb() re-trigger the sleep timer.
***************************************************************************
new_bios:   move      usp,a0                    ; USP stack is active
            btst      #5,(sp)                   ; called from user mode?
            beq       new_bios2                 ; yes ->
            movea.l   sp,a0                     ; SSP stack is active
            addq.l    #6,a0
new_bios2:  move.w    (a0),d0                   ; bios function number
            cmp.w     #3,d0                     ; Bconout()
            beq       new_bios_bconout
            cmp.w     #2,d0                     ; Bconin()
            beq       new_bios_updatetimer
            cmp.w     #4,d0                     ; Rwabs()
            beq       new_bios_updatetimer
            cmp.w     #7,d0                     ; Getbpb()
            beq       new_bios_updatetimer
new_bios_old:movea.l  (oldtrap_bios).l,a0
            jmp       (a0)

new_bios_updatetimer:move.l (cookie_sleepTimer).l,(sleepTimerCounter).l
            bra.s     new_bios_old

new_bios_bconout:move.l (cookie_sleepTimer).l,(sleepTimerCounter).l
            move.l    2(a0),-(sp)
            move.w    #3,-(sp)                  ; Bconout()
            pea       new_bios_tailPatch(pc)
            move.w    $a(sp),-(sp)
            ori.w     #$2000,(sp)
            movea.l   (oldtrap_bios).l,a0
            jmp       (a0)

new_bios_tailPatch:move.w #3,(countdownVBLTimer).l ;reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            addq.l    #6,sp
            rte

***************************************************************************
*   XBIOS patch
*   - Setscreen() and Vsync() enable the transfer to the shadow controller
*     for at least 3 VBL.
*   - Midiws(), Flopver() and DMAread()/DMAwrite() re-trigger the
*     sleep timer.
***************************************************************************
new_xbios:  move      usp,a0                    ; USP stack is active
            btst      #5,(sp)                   ; called from user mode?
            beq       new_xbios2                ; yes ->
            lea       6(sp),a0                  ; SSP stack is active
new_xbios2: move.w    (a0),d0                   ; xbios function number
            cmp.w     #5,d0                     ; Setscreen()
            beq       new_xbios_setscreen
            cmp.w     #$25,d0                   ; Vsync()
            beq       new_xbios4
            cmp.w     #8,d0                     ; Floprd()
            blt       new_xbios_old
            cmp.w     #$c,d0                    ; Midiws()
            ble       new_xbios_updatetimer
            cmp.w     #$13,d0                   ; Flopver()
            beq       new_xbios_updatetimer
            cmp.w     #$2a,d0                   ; DMAread()
            beq       new_xbios_updatetimer
            cmp.w     #$2b,d0                   ; DMAwrite()
            beq       new_xbios_updatetimer
new_xbios_old:movea.l (oldtrap_xbios).l,a0
            jmp       (a0)

new_xbios_updatetimer:move.l (cookie_sleepTimer).l,(sleepTimerCounter).l
            bra.s     new_xbios_old

new_xbios_setscreen:move.w $a(a0),-(sp)         ; mode
            move.l    6(a0),-(sp)               ; physbase
            move.l    2(a0),-(sp)               ; logbase
            move.w    #5,-(sp)                  ; # Setscreen()
            pea       new_xbios_tailPatch(pc)
            move.w    $10(sp),-(sp)
            ori.w     #$2000,(sp)
            movea.l   (oldtrap_xbios).l,a0
            jmp       (a0)

new_xbios_tailPatch:move.w #3,(countdownVBLTimer).l ;reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            adda.l    #$c,sp                    ; remove parameters and function number from the stack
            rte

new_xbios4: move.w    #3,-(sp)                  ; # Logbase()
            pea       new_xbios7(pc)            ; push return address onto stack
            move.w    6(sp),-(sp)               ; push SR onto stack (for a rte)
            ori.w     #$2000,(sp)               ; set supervisor mode bit
            movea.l   (oldtrap_xbios).l,a0
            jmp       (a0)

new_xbios7: move.w    #3,(countdownVBLTimer).l  ; reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            addq.l    #2,sp                     ; remove function number from the stack
            rte

***************************************************************************
*   line A patch
*   After every line A operation the video transfer to the shadow controller
*   is enabled and kept on for at least 3 VBLs. Without this the video
*   changes would not be visible to the user.
***************************************************************************
new_lineA:  movea.l   2(sp),a1                  ; PC which points to the current opcode
            move.w    (a1),d2                   ; actual line-A opcode
            and.w     #$fff,d2                  ; mask our the upper 4 bit ($Axxx)
            addq.l    #2,a1                     ; increment PC by 2 (behind the lineA opcode)
            move.l    a1,2(sp)                  ; write back new PC
            cmp.w     #$f,d2                    ; lineA range 0..15
            bhi       new_lineA2                ; out of range...punt
            lsl.w     #2,d2                     ; pointer into lineA jump table
            movea.l   new_lineA_FuncPtr(pc,d2.w),a1
            movem.l   d3-d7/a3-a5,-(sp)         ; save all registers
            jsr       (a1)                      ; call our lineA handler
            movem.l   (sp)+,d3-d7/a3-a5         ; restore all registers
new_lineA2: rte                                 ; return from interrupt

new_lineA_FuncPtr:
            DC.L lineA_0,lineA_1,lineA_2,lineA_3,lineA_4,lineA_5,lineA_6,lineA_7
            DC.L lineA_8,lineA_9,lineA_a,lineA_b,lineA_c,lineA_d,lineA_e,lineA_f

lineA_0:    movea.l   (lineA_variables).l,a0
            move.l    a0,d0
            movea.l   (lineA_fontHeaders).l,a1
            lea       (new_lineA_FuncPtr).l,a2
            rts

lineA_1:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4).l,-(sp)
            rts

lineA_2:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*2).l,-(sp)
            rts

lineA_3:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*3).l,-(sp)
            rts

lineA_4:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*4).l,-(sp)
            rts

lineA_5:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*5).l,-(sp)
            rts

lineA_6:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*6).l,-(sp)
            rts

lineA_7:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*7).l,-(sp)
            rts

lineA_8:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*8).l,-(sp)
            rts

lineA_9:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*9).l,-(sp)
            rts

lineA_a:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*10).l,-(sp)
            rts

lineA_b:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*11).l,-(sp)
            rts

lineA_c:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*12).l,-(sp)
            rts

lineA_d:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*13).l,-(sp)
            rts

lineA_e:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*14).l,-(sp)
            rts

lineA_f:    pea       lineA_tailPatch(pc)
            move.l    (sav_lineA+4*15).l,-(sp)
            rts

lineA_tailPatch:move.w #3,(countdownVBLTimer).l ; reset VBL timer
            bclr      #0,(synmod+1).l           ; enable combo -> shadow controller video transfer
            rts

***************************************************************************
*
***************************************************************************
infoText:           DC.B '**********************************\r\n'
                    DC.B 'Shutdown   version 1.0   Oct 17 91\r\n'
                    DC.B 'Copyright  1991 Atari Corporation\r\n'
                    DC.B 'All Rights Reserved\r\n'
                    DC.B '**********************************\r\n'
                    DC.B 0
oldcookieresvector: DS.L 1
oldcookieresvalid:  DS.L 1
saveOrRestoreFlag:  DS.B 1
oldresvalid:        DS.L 1
oldresvector:       DS.L 1
sav_v_bas_h:        DS.B 1
sav_v_bas_m:        DS.B 1
sav_v_bas_l:        DS.B 1
sav_synmod:         DS.B 1
sav_palette0:       DS.W 1
sav_v_shf_mod:      DS.B 1
save_psg:           DS.B 16
saveRegs:           DS.L 16
oldSSP:             DS.L 1
oldUSP:             DS.L 1
sav_MFP:            DS.B 18
sav_halftone:       DS.W 31
sav_screendrvfuncptr:DS.L 1
cookiePtr:          DS.L 1
old_mousevec:       DS.L 1
old_mousevecAddr:   DS.L 1
old_joyvec:         DS.L 1
old_ikbd_neg4:      DS.L 1
old_midisysvec:     DS.L 1
lineA_variables:    DS.L 1
lineA_fontHeaders:  DS.L 1
countdownVBLTimer:  DS.W 1
vdi_functionCode:   DS.W 1
ahdiDetected:       DS.B 1
ahdiStandbyTime:    DS.B 1
blankScreenTimerCounter:DS.L 1
sleepTimerCounter:  DS.L 1
cookie_blankScreenTimer:DS.L 1
cookie_sleepTimer:  DS.L 1
cookie_videoPowerSaverFlag:DS.W 1
cookie_watchSerialPortFlag:DS.W 1
old_evt_timer:      DS.L 1
oldtrap_aesvdi:     DS.L 1
oldtrap_bios:       DS.L 1
oldtrap_xbios:      DS.L 1
old_lineA:          DS.L 1
sav_lineA:          DS.L 16
v_opnwk_called:     DS.B 1

Atari ST Protection: Speedball

SpeedBall protection is almost identical in the code to Garfield. Track 79 has to be shorter than normal (e.g. 5979 bytes, about 5% shorter than normal)

02BDA8  48E77F7E            L0434:MOVEM.L   A1-A6/D1-D7,-(A7)
02BDAC  23C80002C1D0              MOVE.L    A0,$0002C1D0         L0465
02BDB2  13FC00000002C1D4          MOVE.B    #0,$0002C1D4         L0466
02BDBA  263C00000001              MOVE.L    #1,D3
02BDC0  23C30002C1D6        L0435:MOVE.L    D3,$0002C1D6         L0467
02BDC6  4EB90002BE38              JSR       EnterSuper
02BDCC  4EB90002C19E              JSR       InitProtBuffer
02BDD2  50F90000043E              ST        $43E.L
02BDD8  4EB90002BE90              JSR       YMPortDeselect
02BDDE  4EB90002BECE              JSR       FDCForceInterrupt
02BDE4  4EB90002BEF2              JSR       FDCSeekTrack79
02BDEA  4EB90002BF48              JSR       FDCReadTrack
02BDF0  4EB90002BF48              JSR       FDCReadTrack
02BDF6  4EB90002BECE              JSR       FDCForceInterrupt
02BDFC  4EB90002C050              JSR       YMDeselectFloppy
02BE02  51F90000043E              SF        $43E.L
02BE08  4EB90002C06A              JSR       ExitSuper
02BE0E  4EB90002C14E              JSR       CheckProtBuffer
02BE14  10390002C1BE              MOVE.B    $0002C1BE,D0         L045C
02BE1A  B03C0000                  CMP.B     #0,D0
02BE1E  67000012                  BEQ       18(PC)               L0436
02BE22  26390002C1D6              MOVE.L    $0002C1D6,D3         L0467
02BE28  51CBFF96                  DBF       D3,-106(PC)          L0435
02BE2C  4EF900025290              JMP       $25290
02BE32  4CDF7EFE            L0436:MOVEM.L   (A7)+,A1-A6/D1-D7
02BE36  4E75                      RTS

EnterSuper
02BE38  2F3C00000001              MOVE.L    #1,-(A7)
02BE3E  3F3C0020                  MOVE.W    #$20,-(A7)
02BE42  4E41                      TRAP      #1
02BE44  DFFC00000006              ADDA.L    #6,A7
02BE4A  4A40                      TST.W     D0
02BE4C  6600002C                  BNE       44(PC)               L0438
02BE50  42A7                      CLR.L     -(A7)
02BE52  3F3C0020                  MOVE.W    #$20,-(A7)
02BE56  4E41                      TRAP      #1
02BE58  DFFC00000006              ADDA.L    #6,A7
02BE5E  23C00002C1C0              MOVE.L    D0,$0002C1C0         L045D
02BE64  12390002C1D4              MOVE.B    $0002C1D4,D1         L0466
02BE6A  4A01                      TST.B     D1
02BE6C  66000020                  BNE       32(PC)               L0439
02BE70  13FC00020002C1D4          MOVE.B    #2,$0002C1D4         L0466
02BE78  4E75                      RTS
02BE7A  12390002C1D4        L0438:MOVE.B    $0002C1D4,D1         L0466
02BE80  4A01                      TST.B     D1
02BE82  6600000A                  BNE       10(PC)               L0439
02BE86  13FC00010002C1D4          MOVE.B    #1,$0002C1D4         L0466
02BE8E  4E75                L0439:RTS

YMPortDeselect
02BE90  4EB90002BE38              JSR       EnterSuper
02BE96  303C0000                  MOVE.W    #0,D0
02BE9A  5200                      ADDQ.B    #1,D0
02BE9C  E308                      LSL.B     #1,D0
02BE9E  00400000                  ORI.W     #0,D0
02BEA2  0A000007                  EORI.B    #7,D0
02BEA6  02000007                  ANDI.B    #7,D0
02BEAA  40E7                L043B:MOVE      SR,-(A7)
02BEAC  007C0700                  ORI.W     #$700,SR
02BEB0  13FC000E00FF8800          MOVE.B    #$E,$FF8800.L
02BEB8  123900FF8800              MOVE.B    $FF8800.L,D1
02BEBE  020100F8                  ANDI.B    #$F8,D1
02BEC2  8200                      OR.B      D0,D1
02BEC4  13C100FF8802              MOVE.B    D1,$FF8802.L
02BECA  46DF                      MOVE      (A7)+,SR
02BECC  4E75                      RTS

FDCForceInterrupt
02BECE  4EB90002BE38              JSR       EnterSuper
02BED4  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
02BEDC  3C3C00D0                  MOVE.W    #$D0,D6
02BEE0  4EB90002C0B2              JSR       FDCWriteReg
02BEE6  3E3C0028                  MOVE.W    #$28,D7
02BEEA  4EB90002C0AC              JSR       DelayD7
02BEF0  4E75                      RTS

FDCSeekTrack79
02BEF2  4EB90002BE38              JSR       EnterSuper
02BEF8  4EB90002C0F0              JSR       $0002C0F0            L044E
02BEFE  33FC008600FF8606          MOVE.W    #$86,$FF8606.L
02BF06  3C3C004F                  MOVE.W    #$4F,D6
02BF0A  4EB90002C0B2              JSR       FDCWriteReg
02BF10  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
02BF18  3C3C001B                  MOVE.W    #$1B,D6
02BF1C  4EB90002C0B2              JSR       FDCWriteReg
02BF22  2E3C00060000              MOVE.L    #$60000,D7
02BF28  5387                L043E:SUBQ.L    #1,D7
02BF2A  67000010                  BEQ       16(PC)               L043F
02BF2E  0839000500FFFA01          BTST      #5,$FFFA01.L
02BF36  6600FFF0                  BNE       -16(PC)              L043E
02BF3A  4E75                      RTS
02BF3C  3F3CFFF9            L043F:MOVE.W    #$FFF9,-(A7)
02BF40  4EB90002C14C              JSR       $0002C14C            L0452
02BF46  4E75                      RTS

FDCReadTrack
02BF48  4EB90002BE38              JSR       EnterSuper
02BF4E  42B90002C1C8              CLR.L     $0002C1C8            L045F
02BF54  40F90002C1B8              MOVE      SR,$0002C1B8         L0459
02BF5A  46FC2700                  MOVE      #$2700,SR
02BF5E  33FC009000FF8606          MOVE.W    #$90,$FF8606.L
02BF66  33FC019000FF8606          MOVE.W    #$190,$FF8606.L
02BF6E  33FC009000FF8606          MOVE.W    #$90,$FF8606.L
02BF76  3C3C0016                  MOVE.W    #$16,D6
02BF7A  343C0200                  MOVE.W    #$200,D2
02BF7E  C4C6                      MULU      D6,D2
02BF80  33C20002C1BA              MOVE.W    D2,$0002C1BA         L045A
02BF86  26390002C1D0              MOVE.L    $0002C1D0,D3         L0465
02BF8C  D483                      ADD.L     D3,D2
02BF8E  23C20002C1C4              MOVE.L    D2,$0002C1C4         L045E
02BF94  4EB90002C0B2              JSR       FDCWriteReg
02BF9A  20390002C1D0              MOVE.L    $0002C1D0,D0         L0465
02BFA0  13C000FF860D              MOVE.B    D0,$FF860D.L
02BFA6  E088                      LSR.L     #8,D0
02BFA8  13C000FF860B              MOVE.B    D0,$FF860B.L
02BFAE  E088                      LSR.L     #8,D0
02BFB0  13C000FF8609              MOVE.B    D0,$FF8609.L
02BFB6  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
02BFBE  3C3C00E8                  MOVE.W    #$E8,D6
02BFC2  4EB90002C0B2              JSR       FDCWriteReg
02BFC8  2E3C00050000              MOVE.L    #$50000,D7
02BFCE  2A790002C1C4              MOVEA.L   $0002C1C4,A5         L045E
02BFD4  303C0200                  MOVE.W    #$200,D0
02BFD8  51C8FFFE            L0441:DBF       D0,-2(PC)            L0441
02BFDC  0839000500FFFA01    L0442:BTST      #5,$FFFA01.L
02BFE4  67000030                  BEQ       48(PC)               L0443
02BFE8  5387                      SUBQ.L    #1,D7
02BFEA  67000060                  BEQ       96(PC)               L0446
02BFEE  13F900FF86090002C1C9      MOVE.B    $FF8609.L,$0002C1C9  L0460
02BFF8  13F900FF860B0002C1CA      MOVE.B    $FF860B.L,$0002C1CA  L0461
02C002  13F900FF860D0002C1CB      MOVE.B    $FF860D.L,$0002C1CB  L0462
02C00C  BBF90002C1C8              CMPA.L    $0002C1C8,A5         L045F
02C012  6E00FFC8                  BGT       -56(PC)              L0442
02C016  33FC009000FF8606    L0443:MOVE.W    #$90,$FF8606.L
02C01E  3A3900FF8606              MOVE.W    $FF8606.L,D5
02C024  33C50002C1BC              MOVE.W    D5,$0002C1BC         L045B
02C02A  08050000                  BTST      #0,D5
02C02E  67000018                  BEQ       24(PC)               L0445
02C032  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
02C03A  4EB90002C12E              JSR       $0002C12E            FDCReadRegB
02C040  46F90002C1B8        L0444:MOVE      $0002C1B8,SR         L0459
02C046  4E75                      RTS
02C048  6000FFF6            L0445:BRA       -10(PC)              L0444
02C04C  6000FFF2            L0446:BRA       -14(PC)              L0444

YMDeselectFloppy
02C050  4EB90002BE38              JSR       EnterSuper
02C056  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
02C05E  103C0007                  MOVE.B    #7,D0
02C062  4EB90002BEAA              JSR       $0002BEAA            L043B
02C068  4E75                      RTS

ExitSuper
02C06A  10390002C1D4              MOVE.B    $0002C1D4,D0         L0466
02C070  5300                      SUBQ.B    #1,D0
02C072  4A00                      TST.B     D0
02C074  6700002C                  BEQ       44(PC)               L0449
02C078  2F3C00000001              MOVE.L    #1,-(A7)
02C07E  3F3C0020                  MOVE.W    #$20,-(A7)
02C082  4E41                      TRAP      #1
02C084  DFFC00000006              ADDA.L    #6,A7
02C08A  4A40                      TST.W     D0
02C08C  67000014                  BEQ       20(PC)               L0449
02C090  2F390002C1C0              MOVE.L    $0002C1C0,-(A7)      L045D
02C096  3F3C0020                  MOVE.W    #$20,-(A7)
02C09A  4E41                      TRAP      #1
02C09C  DFFC00000006              ADDA.L    #6,A7
02C0A2  33FC4E7100029B08    L0449:MOVE.W    #$4E71,$29B08       ;(#"Nq")
02C0AA  4E75                      RTS

DelayD7
02C0AC  51CFFFFE                  DBF       D7,-2(PC)            DelayD7
02C0B0  4E75                      RTS

FDCWriteReg
02C0B2  4EB90002BE38              JSR       EnterSuper
02C0B8  4EB90002C0DE              JSR       fdcDelay
02C0BE  33C600FF8604              MOVE.W    D6,$FF8604.L
02C0C4  4EB90002C0DE              JSR       fdcDelay
02C0CA  4E75                      RTS

FDCReadReg
02C0CC  4EB90002BE38              JSR       EnterSuper
02C0D2  363900FF8604              MOVE.W    $FF8604.L,D3
02C0D8  4EB90002C0DE              JSR       fdcDelay

fdcDelay
02C0DE  40E7                      MOVE      SR,-(A7)
02C0E0  3F07                      MOVE.W    D7,-(A7)
02C0E2  3E3C0028                  MOVE.W    #$28,D7             ;(#"(")
02C0E6  51CFFFFE            L044D:DBF       D7,-2(PC)            L044D
02C0EA  3E1F                      MOVE.W    (A7)+,D7
02C0EC  46DF                      MOVE      (A7)+,SR
02C0EE  4E75                      RTS

02C0F0  3C390002C1CC        L044E:MOVE.W    $0002C1CC,D6         L0463
02C0F6  02460003                  ANDI.W    #3,D6
02C0FA  2E3C00050000              MOVE.L    #$50000,D7
02C100  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
02C108  4EB90002C0B2              JSR       FDCWriteReg
02C10E  5387                L044F:SUBQ.L    #1,D7
02C110  67000010                  BEQ       16(PC)               L0450
02C114  0839000500FFFA01          BTST      #5,$FFFA01.L
02C11C  6600FFF0                  BNE       -16(PC)              L044F
02C120  4E75                      RTS
02C122  3F3CFFF9            L0450:MOVE.W    #$FFF9,-(A7)
02C126  4EB90002C14C              JSR       $0002C14C            L0452
02C12C  4E75                      RTS

FDCReadRegB
02C12E  4EB90002BE38              JSR       EnterSuper
02C134  4EB90002C0DE              JSR       fdcDelay
02C13A  33F900FF86040002C1CE      MOVE.W    $FF8604.L,$0002C1CE  L0464
02C144  4EB90002C0DE              JSR       fdcDelay
02C14A  4E75                      RTS

02C14C  4E75                L0452:RTS

CheckProtBuffer
02C14E  33FC4E750002BDA8          MOVE.W    #$4E75,$0002BDA8    ;(#"Nu") L0434
02C156  22390002C1D0              MOVE.L    $0002C1D0,D1         L0465
02C15C  068100002EE0              ADDI.L    #6000*2,D1
02C162  2041                      MOVEA.L   D1,A0
02C164  263C00000DAC              MOVE.L    #3500,D3
02C16A  243C00000000              MOVE.L    #0,D2
02C170  3A20                      MOVE.W    -(A0),D5
02C172  3020                L0454:MOVE.W    -(A0),D0
02C174  BA40                      CMP.W     D0,D5
02C176  660A                      BNE.S     10(PC)               L0455
02C178  5482                      ADDQ.L    #2,D2
02C17A  51CBFFF6                  DBF       D3,-10(PC)           L0454
02C17E  60000014                  BRA       20(PC)               L0456
02C182  04820000170C        L0455:SUBI.L    #5900,D2
02C188  6B0A                      BMI.S     10(PC)               L0456
02C18A  13FC00000002C1BE          MOVE.B    #0,$0002C1BE         L045C
02C192  4E75                      RTS
02C194  13FC00010002C1BE    L0456:MOVE.B    #1,$0002C1BE         L045C
02C19C  4E75                      RTS

InitProtBuffer
02C19E  243C0000176F              MOVE.L    #5999,D2
02C1A4  20790002C1D0              MOVEA.L   $0002C1D0,A0         L0465
02C1AA  2639000004BA              MOVE.L    $4BA.L,D3
02C1B0  30C3                L0458:MOVE.W    D3,(A0)+
02C1B2  51CAFFFC                  DBF       D2,-4(PC)            L0458
02C1B6  4E75                      RTS

02C1B8  2304                L0459:DC.W      $2304
02C1BA  2C00                L045A:DC.W      $2C00
02C1BC  0000                L045B:DC.W      $0000
02C1BE  0000                L045C:DC.W      $0000
02C1C0  00000000            L045D:DC.L      $00000000
02C1C4  00017EFA            L045E:DC.L      $00017EFA
02C1C8  00                  L045F:DC.B      $00
02C1C9  00                  L0460:DC.B      $00
02C1CA  00                  L0461:DC.B      $00
02C1CB  00                  L0462:DC.B      $00
02C1CC  0003                L0463:DC.W      $0003
02C1CE  0000                L0464:DC.W      $0000
02C1D0  000152FA            L0465:DC.L      $000152FA
02C1D4  0100                L0466:DC.B      $01,$00
02C1D6  00000001            L0467:DC.L      $00000001

Atari ST Protection: Silent Service, F15 Strike Eagle

This is a more interesting protection! For one it decrypts the track testing code only while it is executed (not really a problem with an Atari ST emulator) and encrypts it afterwards.

It reads track 79 and takes the very FIRST byte from the track and searches for the first position where this byte changes. Usually this is the first GAP before the first sector header and without a SYNC it can be shifted by even 1/2 bit by the FDC. However the code has a lookup table for all 16 possible bit-shift combinations and tests if this first byte plus the following 5 are in this table. This way even without a correct SYNC the protection test still succeeds!

But there is more! It skips sector 1..9 with their headers and data blocks and then stores the sector number of the first sector after these 9 sector (e.g. 0x13). This sector extends over the index mark and has to have random data at the end, because read track stops at the index mark. However up to 496 bytes at the beginning have to be identical. If there are no garbage data at the end of the sector, the protection fails, because the sector didn’t extend over the index!

As a last test the sector is read normally via XBIOS Floprd() and this call should return NO error (read sector doesn’t stop at the index mark, if it already started reading before the index mark). The whole 512 byte sector should have identical byte in it.

If all this succeeds, the copy protection test was successfull, too.

A very nice protection indeed!

silentServiceProtection:
    move.l  a6,$148d6
    lea     $1491a,a6
    movem.l d1-7/a0-5,-(a6)
    move.l  a6,$148da

    move.w  #$22,-(a7)
    trap    #14                 ;Kbdvbase()
    addq.w  #2,a7
    movea.l d0,a0
    move.l  16(a0),$148ae       ;->kb_mousevec

    move.l  $148ae,-(a7)
    lea     $13cd4,a0
    move.l  a0,-(a7)
    clr.w   -(a7)
    clr.w   -(a7)
    trap    #14                 ;Initmouse(0,...)
    adda.l  #12,a7

    move    sr,d0
    btst    #13,d0
    bne.s   L0102F4
    clr.l   -(a7)
    move.w  #$20,-(a7)
    trap    #1
    addq.w  #6,a7
    move.l  d0,$148ce
    move.l  #-1,$148c2
    bra.s   L0102FA
L0102F4
    clr.l   $148c2

L0102FA
    move.b  #$ff,$43e           ;set flock

    move    sr,-(a7)
    ori.w   #$700,sr
    clr.l   d0
    clr.l   d7
    move    (a7)+,sr

    move.w  #$19,-(a7)
    trap    #1                  ;Dgetdrv()
    addq.w  #2,a7
    cmpi.b  #1,d0
    bne.s   L01032A
    move.w  $4a6,d5
    cmpi.w  #2,d5
    beq.s   L01032A
    clr.l   d0
L01032A
    move.w  d0,$148ac

    addq.b  #1,d0               ;drive = 1/2
    lsl.b   #1,d0               ;drive = 2/4
    or.w    #0,d0               ;side 1
    eori.b  #7,d0
    and.b   #7,d0
    moveq   #2,d4               ;3 retries
    bsr     fdcSelect

    move.w  #$82,$ff8606        ;Track Register
    bsr     fdcReadD0
    move.w  d0,$148b4           ;save track register

cpRetry
    bsr     fdcRestore
    btst    #0,d6
    bne.l   cpFailed

    bsr     fdcSeek79
    btst    #0,d6
    bne.l   cpFailed

    clr.l   d3
    moveq   #2,d5
cpNextTrack:
    bsr     fdcReadTrack
    btst    #0,d6
    bne.l   cpFailed
    bsr     decryptCode

    lea     $1491e,a0       ;track buffer
    move.l  #$1f0,d0
    move.b  (a0)+,d7
cpSearch
    move.b  (a0)+,d6        ;find first changed byte
    cmp.b   d6,d7
    bne.s   cpNext
    dbra    d0,cpSearch
    bra.l   cpFailed
cpNext
    subq.l  #2,a0           ;back to the last byte from the block
    lea     $13c72,a2

    ;a possible bit shift mask
        $01,$03,$3F,$0F,$FF,$FF,
        $02,$06,$7C,$1F,$FF,$FF,
        $04,$0C,$F8,$3F,$FF,$FF,
        $08,$19,$F0,$7F,$FF,$FF,
        $10,$33,$E0,$FF,$FF,$FF,
        $20,$67,$C1,$FF,$FF,$FF,
        $40,$CF,$83,$FF,$FF,$FF,
        $80,$81,$9F,$07,$FF,$FF,

        $5E,$5C,$40,$B0,$00,$00,
        $BC,$B8,$81,$60,$00,$00,
        $97,$10,$2C,$00,$00,$00,
        $2F,$2E,$20,$58,$00,$00,
        $E5,$C4,$0B,$00,$00,$00,
        $CB,$88,$16,$00,$00,$00,
        $79,$71,$02,$C0,$00,$00,
        $F2,$E2,$05,$80,$00,$00,

        $55,$55

    move.b  (a0)+,d6        ;byte at the track beginning that was repeated
cpLoop
    move.b  (a2)+,d7        ;find the repeated byte
    cmp.b   #$55,d7
    beq.l   cpFailed
    cmp.b   d6,d7
    beq.s   cpNextB
    addq.w  #5,a2
    bra.s   cpLoop
cpNextB

    moveq   #4,d0
cpLoopB
    move.b  (a0)+,d6        ;the following 5 bytes have to be the same
    cmp.b   (a2)+,d6
    bne.l   cpFailed
    dbra    d0,cpLoopB

    ; skip 9 sectors 1..9 in the current track
    clr.b   d0
cpLoopC
    bsr     cpSearchSync    ;search for $A1,$A1
    addq.b  #1,d0           ;sector + 1
    move.b  (a0)+,d6
    cmp.b   #$fe,d6         ;Address Mark Header
    bne.l   cpFailed
    move.b  (a0)+,d6
    cmp.b   #$4f,d6         ;Track 79
    bne.l   cpFailed
    move.b  (a0)+,d6
    cmp.b   #0,d6           ;Side == 0
    bne.l   cpFailed
    move.b  (a0)+,d6
    cmp.b   d0,d6           ;Sector correct?
    bne.l   cpFailed

    bsr     cpSearchSync    ;search for $A1,$A1
    move.b  (a0)+,d6
    cmp.b   #$fb,d6         ;Data Header
    bne.l   cpFailed
    adda.l  #$200,a0        ;Skip Sector
    cmp.b   #9,d0
    bne.s   cpLoopC

    bsr     cpSearchSync    ;search for $A1,$A1
    move.b  (a0)+,d6
    cmp.b   #$fe,d6         ;Addres Mark Header
    bne.s   cpFailed
    move.b  (a0)+,d6
    movea.l #10,a6
    cmp.b   #$4f,d6         ;Track 79
    bne.s   cpFailed
    move.b  (a0)+,d6
    cmp.b   #0,d6           ;Side == 0
    bne.s   cpFailed
    clr.w   d6
    move.b  (a0)+,d6        ;Sector >= 10
    cmp.b   #10,d6
    blt.s   cpFailed
    move.w  d6,$13c6e       ;save found sector number ($0D)

    bsr     cpSearchSync    ;search for $A1,$A1
    move.b  (a0)+,d6
    cmp.b   #$fb,d6         ;Data Header
    bne.s   cpFailed

    clr.w   d0
    clr.w   d6
    move.b  (a0)+,d6
    move.w  d6,$13c70       ;first byte in the sector ($E5)
cpLoopD
    addq.w  #1,d0
    move.b  (a0)+,d6
    cmp.w   $13c70,d6       ;skip same bytes (first byte in the sector ($E5))
    bne.s   cpNextC
    cmp.w   #$1f0,d0        ;too many bytes?
    beq.s   cpFailed
    bra.s   cpLoopD
cpNextC

    cmp.b   #0,d6           ;end of track reached before end of sector?
    beq.s   cpOut

cpFailed
    clr.w   $13c6e          ;save found sector number = illegal

    bsr     encryptCode
    dbra    d5,cpNextTrack
    dbra    d4,cpRetry

    bsr     fdcDone
    move.b  d0,d6
    cmpi.l  #$0,$148c2
    beq.s   L0104A8
    move.l  $148ce,-(a7)
    move.w  #$20,-(a7)
    trap    #1
    addq.w  #6,a7
L0104A8
    bsr     cpCreateReturncode
    bsr     cpRestoreRegister
    rts

cpOut
    bsr     encryptCode
    bsr     fdcDone

    cmpi.l  #$0,$148c2
    beq.s   L0104D4
    move.l  $148ce,-(a7)
    move.w  #$20,-(a7)
    trap    #1
    addq.w  #6,a7

L0104D4
    move.w  #1,-(a7)            ;count
    move.w  #0,-(a7)            ;sideno
    move.w  #$4f,-(a7)          ;trackno
    move.w  $13c6e,-(a7)        ;sectno = saved found sector number ($0D)
    move.w  $148ac,-(a7)        ;devno
    clr.l   -(a7)               ;filler
    lea     $1491e,a0           ;track buffer
    move.l  a0,-(a7)
    move.w  #8,-(a7)
    trap    #14                 ;int16_t Floprd( void *buf, int32_t filler, int16_t devno, int16_t sectno, int16_t trackno, int16_t sideno, int16_t count )
    adda.l  #20,a7
    cmp.l   #0,d0
    beq.s   L010512             ;should be 0! =>
    andi.l  #$ffffff,d0
L010510
    rts

L010512
    ori.l   #$37000000,d0

    ;the sector has to have all the same byte
    move.l  #511,d7
    lea     $1491e,a0           ;track buffer
    clr.w   d6
L010526
    move.b  (a0)+,d6
    cmp.w   $13c70,d6           ;first byte in the sector ($E5)
    bne.s   L010510
    dbra    d7,L010526

    ori.l   #$4f,d0
    bsr     cpCreateReturncode
    bsr     cpRestoreRegister
    rts

fdcReadTrack:
    move.l  d7,$148ca
    lea     $1491e,a0           ;track buffer
    move.l  a0,$148d2
    move.b  $148d5,$ff860d
    move.b  $148d4,$ff860b
    move.b  $148d3,$ff8609
    move.l  #$680,d7
fdcReadTrackLoopA
    clr.l   (a0)+
    dbra    d7,fdcReadTrackLoopA
    move.w  #$90,$ff8606
    move.w  #$190,$ff8606
    move.w  #$90,$ff8606
    move.w  #31,d7
    bsr     fdcWriteD7          ;31 DMA sectors (31*512 bytes)
    move.w  #$80,$ff8606
    move.w  #$e4,d7
    bsr     fdcWriteD7          ;Read Track
    move.l  #$40000,d7
    move.b  #255,d6             ;Failed flag
fdcReadTrackLoopB
    btst    #5,$fffa01
    beq.s   fdcReadTrackDone
    subq.l  #1,d7
    bne.s   fdcReadTrackLoopB
    bsr     fdcForceInterrupt
    rts
fdcReadTrackDone
    move.w  #$90,$ff8606
    move.w  $ff8606,d0
    btst    #0,d0               ;DMA ok?
    beq.s   fdcReadTrackFailed
    clr.b   d6                  ;success
fdcReadTrackFailed
    rts

cpRestoreRegister:
    movea.l $148da,a6
    movem.l (a6)+,d1-7/a0-5
    movea.l $148d6,a6
    rts

cpSearchSync:
    move.l  #$1f0,d7
cpSearchSyncLoop
    move.b  (a0)+,d6
    cmp.b   #$a1,d6
    bne.s   cpSearchSyncCheck
    move.b  (a0)+,d6
    cmp.b   #$a1,d6
    bne.s   cpSearchSyncCheck
    rts
cpSearchSyncCheck
    dbra    d7,cpSearchSyncLoop
    suba.l  a6,a6
    addq.w  #4,a7
    bra.l   cpFailed

cpCreateReturncode:             ;$3700004F
    andi.l  #$ff0000ff,d0
    clr.l   d7
    move.w  $13c70,d7           ;first byte in the sector ($E5)
    rol.w   #8,d7
    or.w    $13c6e,d7           ;save found sector number ($0D)
    rol.w   #4,d7
    rol.l   #8,d7
    or.l    d7,d0               ;== $3750DE4F (Silent Service) / $37512E4F (F15 Strike Eagle)
    rts

Calculation after return:
0 == ($0062EB61 ^ $3750DE4F) - $3732352E


fdcDone
    movea.l $148c6,a5
    move.w  $148b4,d7           ;save track register
    bsr     fdcSeekD7
fdcDoneLoop
    move.w  #$80,$ff8606
    bsr     fdcReadD0           ;wait for the motor off
    btst    #7,d0
    bne.s   fdcDoneLoop
    move.b  d2,d0
    move.b  #$0,$43e            ;reset flock
    bsr     fdcSelect           ;deselect floppy

    move.l  $148ae,-(a7)
    lea     $13cd4,a0
    move.l  a0,-(a7)
    move.w  #1,-(a7)
    clr.w   -(a7)
    trap    #14                 ;Initmouse(1,...)
    adda.l  #12,a7
    rts

CryptRetA0Plus_16
    addq.l  #1,a0
    bsr CryptRetA0Plus_10
CryptRetA0Plus_5
    subq.l  #1,a0
    bsr CryptRetA0Plus_5B
CryptRetA0Plus_1
    subq.l  #1,a0
    bsr CryptRetA0Plus_2
CryptRetA0Plus_0
    rts


fdcWriteD7
    bsr.s   fdcDelay
    move.w  d7,$ff8604

fdcDelay
    move    sr,-(a7)
    move.w  d7,-(a7)
    move.w  #32,d7
fdcDelayLoop
    dbra    d7,fdcDelayLoop
    move.w  (a7)+,d7
    move    (a7)+,sr
    rts

encryptCode:
    bsr.s   CryptBlock
    move    $148b2,sr
    rts

decryptCode:
    move    sr,$148b2
    ori.w   #$700,sr
    bsr.s   CryptBlock
    rts

CryptBlock
    move    sr,d7
    lsr.w   #8,d7
    bset    #6,d7
    bclr    #4,d7
    bclr    #3,d7

    lea     $13ce1,a0
    lea     $1038a,a2
    bsr.s   CryptRetA0Plus_16
    addq.l  #1,a0               ;A0 = $13CF2
    moveq   #8,d0
    bsr.s   CryptBlockSub

    lea     $13ce1,a0
    lea     $103a8,a2
    bsr     CryptRetA0Plus_10   ;A0 = $13CF5
    moveq   #34,d0
    bsr.s   CryptBlockSub

    lea     $13ce1,a0
    lea     $103fe,a2
    bsr     CryptRetA0Plus_5B   ;A0 = $13CE6
    moveq   #26,d0
    bsr.s   CryptBlockSub

    addq.l  #5,a0               ;A0 = $13D06
    lea     $10444,a2
    moveq   #4,d0
    bsr.s   CryptBlockSub

    lea     $13ce1,a6
    lea     $10466,a2
    bsr     CryptRetA0Plus_1
    bsr     CryptRetA0Plus_2    ;A0 = $13D0E
    moveq   #4,d0
    bsr.s   CryptBlockSub
    rts

CryptBlockSub
    clr.l   d6
    move.b  (a0)+,d6
    adda.l  d6,a2
    eor.b   d7,(a2)
    dbra    d0,CryptBlockSub
    rts


fdcSelect:
    move    sr,-(a7)
    ori.w   #$700,sr
    move.b  #$e,$ff8800
    move.b  $ff8800,d1
    move.b  d1,d2
    and.b   #$f8,d1
    or.b    d0,d1
    move.b  d1,$ff8802
    move    (a7)+,sr
    rts


fdcReadD0
    bsr     fdcDelay
    move.w  $ff8604,d0
    bra.l   fdcDelay


fdcRestore
    move.w  #$03,d7
    bsr.s   fdcCommandAndWait
fdcRestoreLoop
    subq.l  #1,d6
    beq.s   fdcRestoreTimeout
    btst    #5,$fffa01
    bne.s   fdcRestoreLoop
    clr.l   d6
    move.w  #$80,$ff8606
    bsr.s   fdcReadD0
    btst    #2,d0           ;Track 0?
    bne.s   fdcRestoreSuccess
fdcRestoreTimeout
    bsr.s   fdcForceInterrupt
    moveq   #1,d6
fdcRestoreSuccess
    rts


fdcForceInterrupt
    move.w  #$80,$ff8606
    move.w  #$d0,d7
    bsr     fdcWriteD7
    bsr     fdcDelay
    rts


fdcSeek79
    move.w  #$4f,d7
fdcSeekD7
    move.w  #$86,$ff8606
    bsr     fdcWriteD7
    move.w  #$13,d7
    bsr.s   fdcCommandAndWait
fdcSeek79Loop
    subq.l  #1,d6
    beq.s   fdcSeek79Done
    btst    #5,$fffa01
    bne.s   fdcSeek79Loop
fdcSeek79Done
    clr.l   d6
    rts


fdcCommandAndWait
    move.l  #$40000,d6
    move.w  #$80,$ff8606
    bsr     fdcReadD0
    btst    #7,d0
    bne.s   fdcCommandAndWait2
    move.l  #$60000,d6
fdcCommandAndWait2
    bsr     fdcWriteD7
    rts


CryptRetA0Plus_10
    bsr     CryptRetA0Plus_5
CryptRetA0Plus_5B
    addq.l  #2,a0
    bsr     CryptRetA0Plus_1
CryptRetA0Plus_2
    addq.l  #2,a0
    bsr     CryptRetA0Plus_0
    rts

Atari ST Protection: Mighty Bombjack

Finally an interesting protection!

The boot sector is executable and simply loads the code into memory plus reading the boot sector (moving the head back to track 0). After loading the code the fun begins (skip further down…)

    TEXT
    BRA.S     L0000

    DC.B      $00,$00,$00,$00,$00,$00,$00,$00
    DC.B      $00,$00,$02,$02,$01,$00,$02,$70
    DC.B      $00,$20,$03,$00,$05,$00,$0A,$00
    DC.B      $01,$00,$00,$00,$00,$00

L0000:MOVE.L    #$230,D0        ;track 56, sector 1
    MOVE.L    #101,D1         ;$CA00 bytes
    MOVEA.L   $432.L,A0       ;_membot = Start of TPA (user memory)
    BSR       LoadSectors
    BNE.S     L0000
    LSL.L     #8,D1
    ADD.L     D1,D1           ;buf += 512 * sectors
    ADDA.L    D1,A0
    MOVEQ     #1,D1           ;$200 bytes
    BSR       LoadSectors     ;Track 1, Sector 1
    MOVE.L    $432.L,-(A7)    ;_membot = Start of TPA (user memory)
    RTS

LoadSectors:
    MOVEM.L   A0-A2/D1-D5,-(A7)
    MOVE.L    D0,D3           ;startsector
    MOVE.L    D1,D4           ;number of sectors
    MOVEQ     #-1,D0
LoadSectorsLoop:
    MOVE.L    D3,D0
    DIVU      #10,D0          ;10 sectors per track
    SWAP      D0
    MOVEQ     #0,D1
    MOVE.W    D0,D1
    ADDQ.W    #1,D1           ;sectno
    SWAP      D0
    ANDI.L    #$FF,D0         ;trackno
    MOVE.L    D0,D5
    MOVEQ     #11,D2
    SUB.L     D1,D2           ;number of sectors
    CMP.L     D2,D4
    BGE.S     LoadSectorsR    ;< remaining sectors
    MOVE.L    D4,D2           ;then read the remaining sectors
LoadSectorsR:
    BSR       Floprd
    BNE.S     LoadSectorsError
    ADD.L     D2,D3           ;sector += read sectors
    SUB.L     D2,D4           ;number of sectors -= read sectors
    LSL.L     #8,D2
    LSL.L     #1,D2           ;buf += read sectors * 512
    ADDA.L    D2,A0
    TST.L     D4              ;remaining sectors left?
    BGT.S     LoadSectorsLoop
    MOVEQ     #0,D0
LoadSectorsError:
    MOVEM.L   (A7)+,A0-A2/D1-D5
    TST.L     D0
    RTS

Floprd:MOVEM.L   A0/D1-D2,-(A7)
    MOVE.W    D2,-(A7)        ;count
    CLR.W     -(A7)           ;sideno
    MOVE.W    D0,-(A7)        ;trackno
    MOVE.W    D1,-(A7)        ;sectno
    CLR.W     -(A7)           ;devno = 'A'
    CLR.L     -(A7)           ;filler
    MOVE.L    A0,-(A7)        ;buf
    MOVE.W    #8,-(A7)  ;FLOPRD
    TRAP      #14             ;Floprd( void *buf, int32_t filler, int16_t devno, int16_t sectno, int16_t trackno, int16_t sideno, int16_t count )
    LEA       20(A7),A7
    TST.L     D0
    MOVEM.L   (A7)+,A0/D1-D2
    RTS

    DS.W      3
    DC.B      'Copylock ST (c)1988-90 Rob Northen Computing, U.K. All Rights Reserved.',0
    DS.W      123
    DC.W      $027F
    END

Ok, the code starts by entering supervisor mode via an illegal instruction, then pushing a trace exception handler onto the stack, which decrypts code on the fly. A *lot* of code. All this code is just there to annoy people trying to trace it by hand. Later it reaches the actual protection, which I'll publish here. It again uses trace to decrypt the next opcode (actually 8 bytes) and encrypting the last one again. As a little bonus it executes a subroutine if the "EXG D7,D7" opcode is executed. Fun, but useless: if you made it that far, this is not doing anything... Another thing: LINE-A, all interrupts and traps have to point to ROM, so does GPI7 and printer code (used to enter the debugger). If not, they point to a memory erase routine and a call into the reset of the ROM.

But now to the actual thing: the protection. 3 Parts:

- Via "read track" the first 450 bytes are read and the first sector address header is searched. It has to be a 512 byte sector on the first side and the first sector has to be equal to "(11 + (track % 5) * -2) % 10". If the routine fails, it returns -1, otherwise the current track. Funny thing: on track 0, sector 1 is just fine and because the boot sector was read at the end of the loading code, that's where the floppy head is located. 3 tries to get it done.
- Via Read Sector it reads sector 5 and sector 6 of track 0. It simply times the time it takes to read the sector and expects sector 6 to be at least >1% slower than sector 5. On the floppy it seems to be more than 3%, so 1% is safe.
- The first 16 bytes of sector 6 have to contain the string "Rob Northen Comp". A checksum over this string plus the following 8 bytes results in the serial number of the disk, which is specific for the application.

Yes, that's it for the protection. It continues with trace decoding and then launches the app. The following code is the heart of the protection, cleaned up and decoded.



018CAE  48E7C0C0                  MOVEM.L   A0-A1/D0-D1,-(A7)
018CB2  43F90000015C              LEA       $15C.L,A1
018CB8  233C00044ED0              MOVE.L    #$44ED0,-(A1)
018CBE  233CFFFA2078              MOVE.L    #$FFFA2078,-(A1)
018CC4  233CFFFF51C8              MOVE.L    #$FFFF51C8,-(A1)
018CCA  233C3FF948E0              MOVE.L    #$3FF948E0,-(A1)
018CD0  233C0000303C              MOVE.L    #$303C,-(A1)
018CD6  233C41F90010              MOVE.L    #$41F90010,-(A1)
018CDC  233C46FC2700              MOVE.L    #$46FC2700,-(A1)

        ;erase all memory and call reset
        000140: 46fc 2700                 MOVE      #$2700,SR
        000144: 41f9 0010 0000            LEA       $100000,A0
        00014a: 303c 3ff9                 MOVE.W    #$3FF9,D0
        00014e: 48e0 ffff                 MOVEM.L   D0-D7/A0-A7,-(A0)
        000152: 51c8 fffa                 DBF       D0,*-$4 [$14E]
        000156: 2078 0004                 MOVEA.L   $4.w,A0
        00015a: 4ed0                      JMP       (A0)

018CE2  23C90000042A              MOVE.L    A1,$42A.L
018CE8  23FC3141592600000426      MOVE.L    #$31415926,$426.L

018CF2  41F900000028              LEA       $28.L,A0            ;Line 1010 Emulator (LineA)
018CF8  61000038                  BSR       56(PC)               resetIfNotInROM
018CFC  41F900000060              LEA       $60.L,A0            ;all interrupts and traps!
018D02  6100002E            L0000:BSR       46(PC)               resetIfNotInROM
018D06  B1FC000000C0              CMPA.L    #$C0,A0
018D0C  66F4                      BNE.S     -12(PC)              L0000
018D0E  41F90000013C              LEA       $13C.L,A0           ;GPI7 - Monochrome Detect
018D14  6100001C                  BSR       28(PC)               resetIfNotInROM
018D18  41F90000050A              LEA       $50A.L,A0           ;prv_lst
018D1E  61000012                  BSR       18(PC)               resetIfNotInROM
018D22  41F900000512              LEA       $512.L,A0           ;prv_aux
018D28  61000008                  BSR       8(PC)                resetIfNotInROM
018D2C  4CDF0303                  MOVEM.L   (A7)+,A0-A1/D0-D1
018D30  6018                      BRA.S     24(PC)               L0003

resetIfNotInROM
018D32  2018                      MOVE.L    (A0)+,D0
018D34  6712                      BEQ.S     18(PC)               L0002
018D36  028000FFFFFF              ANDI.L    #$FFFFFF,D0
018D3C  B0BC00400000              CMP.L     #$400000,D0
018D42  6C04                      BGE.S     4(PC)                L0002
018D44  2149FFFC                  MOVE.L    A1,-4(A0)
018D48  4E75                L0002:RTS

018D4A  7000                L0003:MOVEQ     #0,D0
018D4C  7201                      MOVEQ     #1,D1
018D4E  48E73FF0                  MOVEM.L   A0-A3/D2-D7,-(A7)
018D52  7400                      MOVEQ     #0,D2
018D54  E690                      ROXR.L    #3,D0
018D56  E292                      ROXR.L    #1,D2
018D58  E790                      ROXL.L    #3,D0
018D5A  1400                      MOVE.B    D0,D2
018D5C  2601                      MOVE.L    D1,D3
018D5E  7E01                      MOVEQ     #1,D7
018D60  7C00                      MOVEQ     #0,D6
018D62  47FA03A6                  LEA       934(PC),A3           buffer512Bytes
018D66  3F390000043E              MOVE.W    $43E.L,-(A7)
018D6C  50F90000043E              ST        $43E.L
018D72  610000A0            L0004:BSR       160(PC)              doCheckDiskInterleave
018D76  6B0C                      BMI.S     12(PC)               L0005
018D78  61000036                  BSR       54(PC)               checkSectorsReturnMagic
018D7C  4A86                      TST.L     D6                   ;fail? 2nd try!
018D7E  6604                      BNE.S     4(PC)                L0005
018D80  6100002E                  BSR       46(PC)               checkSectorsReturnMagic
018D84  610000A0            L0005:BSR       160(PC)              seekToPreviousTrack
018D88  4A86                      TST.L     D6
018D8A  660E                      BNE.S     14(PC)               L0006
018D8C  4A82                      TST.L     D2
018D8E  6A0A                      BPL.S     10(PC)               L0006
018D90  5202                      ADDQ.B    #1,D2
018D92  02020001                  ANDI.B    #1,D2
018D96  51CFFFDA                  DBF       D7,-38(PC)           L0004
018D9A  6100005E            L0006:BSR       94(PC)               eraseBuffer
018D9E  33DF0000043E              MOVE.W    (A7)+,$43E.L
018DA4  3202                      MOVE.W    D2,D1               ;d1.w = drive no. key disk was found in
018DA6  2006                      MOVE.L    D6,D0               ;d0.l = serial no. 0=key disk not found
018DA8  4CDF0FFC                  MOVEM.L   (A7)+,A0-A3/D2-D7
018DAC  60000560                  BRA       1376(PC)             L002D

checkSectorsReturnMagic:
018DB0  598F                      SUBQ.L    #4,A7
018DB2  303C0005                  MOVE.W    #5,D0               ;Sector 5 in Track 0 (normal sector)
018DB6  610000AC                  BSR       172(PC)              fdcMeasureSectorTiming
018DBA  2E80                      MOVE.L    D0,(A7)
018DBC  303C0006                  MOVE.W    #6,D0               ;Sector 6 in Track 0 (slower sector, ~3%)
018DC0  610000A2                  BSR       162(PC)              fdcMeasureSectorTiming
018DC4  2217                      MOVE.L    (A7),D1
018DC6  9081                      SUB.L     D1,D0               ;delta between 5 and 6
018DC8  6B2C                      BMI.S     44(PC)               L000A
018DCA  C0FC0064                  MULU      #100,D0
018DCE  80C1                      DIVU      D1,D0               ;convert delta into percent
018DD0  B03C0001                  CMP.B     #1,D0               ;1 percent or less is not good enough!
018DD4  6D20                      BLT.S     32(PC)               L000A

018DD6  7000                      MOVEQ     #0,D0
018DD8  7203                      MOVEQ     #3,D1
018DDA  204B                      MOVEA.L   A3,A0
018DDC  9098                L0008:SUB.L     (A0)+,D0            ;checksum over first 16 bytes of sector 6 = "Rob Northen Comp"
018DDE  51C9FFFC                  DBF       D1,-4(PC)            L0008
018DE2  B0BCB34C4FDC              CMP.L     #$B34C4FDC,D0       ;has to be a specific value!
018DE8  660C                      BNE.S     12(PC)               L000A
018DEA  2C00                      MOVE.L    D0,D6
018DEC  7201                      MOVEQ     #1,D1
018DEE  DC98                L0009:ADD.L     (A0)+,D6            ;secret ID over the next 8 bytes
018DF0  4846                      SWAP      D6
018DF2  51C9FFFA                  DBF       D1,-6(PC)            L0009

018DF6  588F                L000A:ADDQ.L    #4,A7
018DF8  4E75                      RTS

eraseBuffer
018DFA  204B                      MOVEA.L   A3,A0
018DFC  323C00FF                  MOVE.W    #$FF,D1
018E00  203C00D4C742              MOVE.L    #$D4C742,D0
018E06  C0FC0011            L000C:MULU      #$11,D0
018E0A  5280                      ADDQ.L    #1,D0
018E0C  30C0                      MOVE.W    D0,(A0)+
018E0E  51C9FFF6                  DBF       D1,-10(PC)           L000C
018E12  4E75                      RTS

doCheckDiskInterleave:
018E14  61000028                  BSR       40(PC)               selectFloppy
018E18  610000D0                  BSR       208(PC)              checkDiskSectorInterleave
018E1C  3A00                      MOVE.W    D0,D5                => current track number
018E1E  6B04                      BMI.S     4(PC)                L000E
018E20  61000234                  BSR       564(PC)              fdcRestore
018E24  4E75                L000E:RTS

seekToPreviousTrack:
018E26  3005                      MOVE.W    D5,D0                current track number set?
018E28  6B0E                      BMI.S     14(PC)               L0010
018E2A  61000210                  BSR       528(PC)              fdcSeekD0
018E2E  4A86                      TST.L     D6
018E30  6722                      BEQ.S     34(PC)               deselectFloppy
018E32  4A03                      TST.B     D3
018E34  671E                      BEQ.S     30(PC)               deselectFloppy
018E36  4E75                      RTS
018E38  61000250            L0010:BSR       592(PC)              fdcFloppyDeselect
018E3C  4E75                      RTS

selectFloppy:
018E3E  1002                      MOVE.B    D2,D0
018E40  5200                      ADDQ.B    #1,D0
018E42  E308                      LSL.B     #1,D0
018E44  0A000007                  EORI.B    #7,D0
018E48  02000007                  ANDI.B    #7,D0
018E4C  7201                      MOVEQ     #1,D1
018E4E  61000244                  BSR       580(PC)              fdcFloppySelect
018E52  4E75                      RTS

deselectFloppy:
018E54  223C00005949              MOVE.L    #$5949,D1
018E5A  103C0007                  MOVE.B    #7,D0
018E5E  61000234                  BSR       564(PC)              fdcFloppySelect
018E62  4E75                      RTS

fdcMeasureSectorTiming:
018E64  48E72000                  MOVEM.L   D2,-(A7)
018E68  33FC008400FF8606          MOVE.W    #$84,$FF8606.L
018E70  6100027C                  BSR       636(PC)              fdcWriteD0
018E74  610000EE                  BSR       238(PC)              fdcDMAReadAddress
018E78  303C0001                  MOVE.W    #1,D0
018E7C  61000270                  BSR       624(PC)              fdcWriteD0
018E80  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
018E88  43F900FF860B              LEA       $FF860B.L,A1
018E8E  7200                      MOVEQ     #0,D1
018E90  240B                      MOVE.L    A3,D2
018E92  2F3C66F64E75              MOVE.L    #$66F64E75,-(A7)
018E98  2F3C00FFFA01              MOVE.L    #$FFFA01,-(A7)
018E9E  2F3C08390005              MOVE.L    #$8390005,-(A7)
018EA4  2F3CB44066F6              MOVE.L    #$B44066F6,-(A7)
018EAA  2F3C01090000              MOVE.L    #$1090000,-(A7)
018EB0  2F3C02005281              MOVE.L    #$2005281,-(A7)
018EB6  2F3C06820000              MOVE.L    #$6820000,-(A7)
018EBC  2F3CB44067F8              MOVE.L    #$B44067F8,-(A7)
018EC2  2F3C01090000              MOVE.L    #$1090000,-(A7)
018EC8  2F3C00FF8604              MOVE.L    #$FF8604,-(A7)
018ECE  2F3C33FC0080              MOVE.L    #$33FC0080,-(A7)
018ED4  2F3C007C0700              MOVE.L    #$7C0700,-(A7)
018EDA  244F                      MOVEA.L   A7,A2
018EDC  CF47                      EXG       D7,D7

A2 =>
        05BC8C  007C0700                  ORI.W     #$700,SR
        05BC90  33FC008000FF8604          MOVE.W    #$80,$FF8604.L
        05BC98  01090000            L0000:MOVEP.W   0(A1),D0
        05BC9C  B440                      CMP.W     D0,D2
        05BC9E  67F8                      BEQ.S     -8(PC)               L0000
        05BCA0  068200000200              ADDI.L    #$200,D2
        05BCA6  5281                L0001:ADDQ.L    #1,D1
        05BCA8  01090000                  MOVEP.W   0(A1),D0
        05BCAC  B440                      CMP.W     D0,D2
        05BCAE  66F6                      BNE.S     -10(PC)              L0001
        05BCB0  0839000500FFFA01    L0002:BTST      #5,$FFFA01.L
        05BCB8  66F6                      BNE.S     -10(PC)              L0002
        05BCBA  4E75                      RTS

018EDE  4FEF0030                  LEA       48(A7),A7
018EE2  2001                      MOVE.L    D1,D0
018EE4  4CDF0004                  MOVEM.L   (A7)+,D2
018EE8  4E75                      RTS

checkDiskSectorInterleave:
018EEA  61000044                  BSR       68(PC)               L0016
018EEE  6B3C                      BMI.S     60(PC)               L0015
018EF0  B03C0002                  CMP.B     #2,D0               ;512 bytes sector?
018EF4  6636                      BNE.S     54(PC)               L0015 ;no => error
018EF6  2200                      MOVE.L    D0,D1
018EF8  E199                      ROL.L     #8,D1
018EFA  0281000000FF              ANDI.L    #$FF,D1             ;track number
018F00  E088                      LSR.L     #8,D0               ;sector number
018F02  82FC0005                  DIVU      #5,D1
018F06  4241                      CLR.W     D1
018F08  4841                      SWAP      D1
018F0A  C2FCFFFE                  MULU      #$FFFE,D1
018F0E  02810000FFFF              ANDI.L    #$FFFF,D1
018F14  0641000B                  ADDI.W    #11,D1
018F18  82FC000A                  DIVU      #10,D1
018F1C  4841                      SWAP      D1                  ;d1 = (11 + (track % 5) * -2) % 10
018F1E  B200                      CMP.B     D0,D1               ;sector number == track
018F20  660A                      BNE.S     10(PC)               L0015
018F22  E088                      LSR.L     #8,D0               ;side
018F24  4A00                      TST.B     D0
018F26  6604                      BNE.S     4(PC)                L0015 ;Side 2? => error
018F28  E088                      LSR.L     #8,D0               ;return the track number
018F2A  4E75                      RTS
018F2C  70FF                L0015:MOVEQ     #-1,D0
018F2E  4E75                      RTS

; read track 0, load 4 bytes of the address block of first sector into D0 (Track,Side,Sector,Length)
; == 0x00000002
018F30  7202                L0016:MOVEQ     #2,D1               ; 3 tries
018F32  61000062            L0017:BSR       98(PC)               fdcReadTrack
018F36  6B2A                      BMI.S     42(PC)               L001B
018F38  204B                      MOVEA.L   A3,A0
018F3A  303C01C1                  MOVE.W    #450-1,D0
018F3E  0C1800FE            L0018:CMPI.B    #$FE,(A0)+
018F42  6614                      BNE.S     20(PC)               L001A
018F44  0C2800A1FFFE              CMPI.B    #$A1,-2(A0)
018F4A  660C                      BNE.S     12(PC)               L001A
018F4C  7203                      MOVEQ     #3,D1
018F4E  E188                L0019:LSL.L     #8,D0
018F50  1018                      MOVE.B    (A0)+,D0
018F52  51C9FFFA                  DBF       D1,-6(PC)            L0019
018F56  4E75                      RTS
018F58  51C8FFE4            L001A:DBF       D0,-28(PC)           L0018
018F5C  51C9FFD4                  DBF       D1,-44(PC)           L0017
018F60  70FF                      MOVEQ     #-1,D0
018F62  4E75                L001B:RTS

fdcDMAReadAddress:
018F64  200B                      MOVE.L    A3,D0
018F66  13C000FF860D              MOVE.B    D0,$FF860D.L
018F6C  E088                      LSR.L     #8,D0
018F6E  13C000FF860B              MOVE.B    D0,$FF860B.L
018F74  E088                      LSR.L     #8,D0
018F76  13C000FF8609              MOVE.B    D0,$FF8609.L
018F7C  33FC009000FF8606          MOVE.W    #$90,$FF8606.L
018F84  33FC019000FF8606          MOVE.W    #$190,$FF8606.L
018F8C  33FC009000FF8606          MOVE.W    #$90,$FF8606.L
018F94  4E75                      RTS

fdcReadTrack:
018F96  48A74000                  MOVEM.W   D1,-(A7)
018F9A  6100FFC8                  BSR       -56(PC)              fdcDMAReadAddress
018F9E  303C001F                  MOVE.W    #$1F,D0
018FA2  6100014A                  BSR       330(PC)              fdcWriteD0
018FA6  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
018FAE  223C00040000              MOVE.L    #$40000,D1
018FB4  43EB01C2                  LEA       450(A3),A1
018FB8  40E7                      MOVE      SR,-(A7)
018FBA  007C0700                  ORI.W     #$700,SR
018FBE  2F3C584F4E75              MOVE.L    #$584F4E75,-(A7)
018FC4  2F3C00FF8604              MOVE.L    #$FF8604,-(A7)
018FCA  2F3C33FC00D0              MOVE.L    #$33FC00D0,-(A7)
018FD0  2F3C00FF8606              MOVE.L    #$FF8606,-(A7)
018FD6  2F3C33FC0080              MOVE.L    #$33FC0080,-(A7)
018FDC  2F3CB3D76ED6              MOVE.L    #$B3D76ED6,-(A7)
018FE2  2F3C860D0003              MOVE.L    #$860D0003,-(A7)
018FE8  2F3C1F7900FF              MOVE.L    #$1F7900FF,-(A7)
018FEE  2F3C860B0002              MOVE.L    #$860B0002,-(A7)
018FF4  2F3C1F7900FF              MOVE.L    #$1F7900FF,-(A7)
018FFA  2F3C86090001              MOVE.L    #$86090001,-(A7)
019000  2F3C1F7900FF              MOVE.L    #$1F7900FF,-(A7)
019006  2F3C53816B1C              MOVE.L    #$53816B1C,-(A7)
01900C  2F3CFA016720              MOVE.L    #$FA016720,-(A7)
019012  2F3C000500FF              MOVE.L    #$500FF,-(A7)
019018  2F3C42A70839              MOVE.L    #$42A70839,-(A7)
01901E  2F3C00FF8604              MOVE.L    #$FF8604,-(A7)
019024  2F3C33FC00E0              MOVE.L    #$33FC00E0,-(A7)
01902A  244F                      MOVEA.L   A7,A2
01902C  CF47                      EXG       D7,D7

A2 =>
        05BC8C  33FC00E000FF8604          MOVE.W    #$E0,$FF8604.L      ; Read Track
        05BC94  42A7                      CLR.L     -(A7)
        05BC96  0839000500FFFA01    L0000:BTST      #5,$FFFA01.L
        05BC9E  6720                      BEQ.S     32(PC)               L0001
        05BCA0  5381                      SUBQ.L    #1,D1
        05BCA2  6B1C                      BMI.S     28(PC)               L0001
        05BCA4  1F7900FF86090001          MOVE.B    $FF8609.L,1(A7)
        05BCAC  1F7900FF860B0002          MOVE.B    $FF860B.L,2(A7)
        05BCB4  1F7900FF860D0003          MOVE.B    $FF860D.L,3(A7)
        05BCBC  B3D7                      CMPA.L    (A7),A1             ; end address reached?
        05BCBE  6ED6                      BGT.S     -42(PC)              L0000
        05BCC0  33FC008000FF8606    L0001:MOVE.W    #$80,$FF8606.L
        05BCC8  33FC00D000FF8604          MOVE.W    #$D0,$FF8604.L      ; Force Interrupt
        05BCD0  584F                      ADDQ.W    #4,A7
        05BCD2  4E75                      RTS

01902E  4FEF0048                  LEA       72(A7),A7
019032  46DF                      MOVE      (A7)+,SR
019034  2001                      MOVE.L    D1,D0
019036  4C9F0002                  MOVEM.W   (A7)+,D1
01903A  4E75                      RTS

fdcSeekD0:
01903C  33FC008600FF8606          MOVE.W    #$86,$FF8606.L
019044  610000A8                  BSR       168(PC)              fdcWriteD0
019048  303C0014                  MOVE.W    #$14,D0
01904C  6100000A                  BSR       10(PC)               fdcCommand
019050  6B02                      BMI.S     2(PC)                L001F
019052  7000                      MOVEQ     #0,D0
019054  4E75                L001F:RTS

fdcRestore:
019056  7004                      MOVEQ     #4,D0

fdcCommand:
019058  B03C0080                  CMP.B     #$80,D0             ;Type I command?
01905C  6404                      BCC.S     4(PC)                L0022
01905E  00000003                  ORI.B     #3,D0               ;=> seek rate = 3ms
019062  33FC008000FF8606    L0022:MOVE.W    #$80,$FF8606.L
01906A  61000082                  BSR       130(PC)              fdcWriteD0
01906E  203C00004000              MOVE.L    #$4000,D0
019074  0839000500FFFA01    L0023:BTST      #5,$FFFA01.L
01907C  6756                      BEQ.S     86(PC)               fdcReadStatus
01907E  5380                      SUBQ.L    #1,D0
019080  66F2                      BNE.S     -14(PC)              L0023
019082  61000038                  BSR       56(PC)               fdcForceInterrupt
019086  70FF                      MOVEQ     #-1,D0
019088  4E75                      RTS


fdcFloppyDeselect:
01908A  223C0000004B              MOVE.L    #$4B,D1
019090  103C0007                  MOVE.B    #7,D0

fdcFloppySelect:
019094  5381                      SUBQ.L    #1,D1
019096  66FC                      BNE.S     -4(PC)               fdcFloppySelect
019098  40E7                      MOVE      SR,-(A7)
01909A  007C0700                  ORI.W     #$700,SR
01909E  13FC000E00FF8800          MOVE.B    #$E,$FF8800.L
0190A6  123900FF8800              MOVE.B    $FF8800.L,D1
0190AC  020100F8                  ANDI.B    #$F8,D1
0190B0  8200                      OR.B      D0,D1
0190B2  13C100FF8802              MOVE.B    D1,$FF8802.L
0190B8  46DF                      MOVE      (A7)+,SR
0190BA  4E75                      RTS

fdcForceInterrupt:
0190BC  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
0190C4  303C00D0                  MOVE.W    #$D0,D0
0190C8  61000024                  BSR       36(PC)               fdcWriteD0
0190CC  303C000F                  MOVE.W    #$F,D0
0190D0  51C8FFFE            L0027:DBF       D0,-2(PC)            L0027

fdcReadStatus
0190D4  33FC008000FF8606          MOVE.W    #$80,$FF8606.L
0190DC  6100001A                  BSR       26(PC)               fdcDelay
0190E0  303900FF8604              MOVE.W    $FF8604.L,D0
0190E6  02800000001F              ANDI.L    #$1F,D0
0190EC  600A                      BRA.S     10(PC)               fdcDelay

fdcWriteD0
0190EE  61000008                  BSR       8(PC)                fdcDelay
0190F2  33C000FF8604              MOVE.W    D0,$FF8604.L

fdcDelay:
0190F8  40E7                      MOVE      SR,-(A7)
0190FA  3F00                      MOVE.W    D0,-(A7)
0190FC  303C0001                  MOVE.W    #1,D0
019100  51C8FFFE            L002B:DBF       D0,-2(PC)            L002B
019104  301F                      MOVE.W    (A7)+,D0
019106  46DF                      MOVE      (A7)+,SR
019108  4E75                      RTS

buffer512Bytes: DS.B 512

Atari ST Protection: Midi-Maze

This is the actual call into the protection check:

    JSR       init_aes_window
    TST.W     D0
    BGE       aes_ok
    MOVEQ     #-1,D0
    BRA       init_screen_return
aes_ok:JSR       check_copy_protection
    MOVE.W    D0,protection_flag

    CLR.W     (A7)
    MOVE.L    #str_midimaze_d8a,-(A7)
    MOVE.W    #$3D,-(A7)                  ; Fopen("MIDIMAZE.D8A", 0)
    JSR       _gemdos

It is tested later and disables the multi-player game:

check protection in a different place later
    TST.W     protection_flag
    BNE       protOK
    MOVE.W    #-2,machines_online
    MOVE.W    #-2,all_players
protOK:

The protection of Midi Maze is quite simple, but effective: Track 0, Sector 0 on Side 0 is read twice and while the first 6 bytes have to contain ‘100004’, the rest of the sector has to change between these two reads. This is done via “weak” or “fuzzy” bits on the disk. Interestingly a patch of the Floprd() function could defeat the protection easily, however because there is no further checksums or encryption replacing the beginning of check_copy_protection with ST D0; RTS will be more efficient. This was also exactly the patch that was done on a Karstadt Public-Domain disk with an official version of MIDIMAZE on it!

check_copy_protection:
    MOVEM.L   A0-A6/D1-D7,-(A7)
    MOVEQ     #2,D6       ;test drive A and B
    MOVEQ     #2,D5       ;read the sector twice

    MOVE.W    #$19,-(A7)    ;DGETDRV
    TRAP      #1
    ADDQ.W    #2,A7
    MOVE.W    D0,D7
    CMPI.W    #2,D7
    BLT.S     check_copy_protection_rd_loop
    CLR.W     D7          ;if loaded from harddisk, search drive A

check_copy_protection_rd_loop:

    MOVE.W    #1,-(A7)    ;1 sector
    CLR.W     -(A7)       ;Side 0
    CLR.W     -(A7)       ;Track 0
    CLR.W     -(A7)       ;Sector 0
    MOVE.W    D7,-(A7)    ;drive
    CLR.L     -(A7)       ;reserved
    PEA       check_copy_protection_buffer
    MOVE.W    #8,-(A7)  ;FLOPRD
    TRAP      #14
    ADDA.W    #20,A7
    TST.W     D0            ;E_OK
    BEQ.S     check_copy_protection_verify
    CMP.W     #-4,D0        ;E_CRC
    BEQ.S     check_copy_protection_verify

check_copy_protection_rd_retry:
    SUBQ.W    #1,D6     ;out of tries?
    BEQ.S     check_copy_protection_fail    ;=> protection fail
    MOVEQ     #2,D5       ;read the sector twice (again)

    ; try the potentially other floppy drive
    ADDQ.W    #1,D7       ;try reading drive B
    CMPI.W    #2,D7     ;reached C?
    BNE.S     check_copy_protection_rd_loop
    CLR.W     D7          ;then try drive A
    BRA.S     check_copy_protection_rd_loop

check_copy_protection_fail:
    CLR.W     D0          ;fail
    BRA.S     check_copy_protection_return

    DC.B      'RMP  V1.00  31-July-86'

check_copy_protection_verify:
    CLR.W     D0

    ; first: compare the first 6 bytes for identity
    LEA       check_copy_protection_buffer,A0
    LEA       check_copy_protection_magic,A1
    CMPM.L    (A0)+,(A1)+
    BNE.S     check_copy_protection_rd_retry
    CMPM.W    (A0)+,(A1)+
    BNE.S     check_copy_protection_rd_retry

    ; count the number of set bits in the remaining 506 bytes
    MOVE.W    #505,D1
check_copy_protection_byteloop:
    MOVEQ     #7,D2
    MOVE.B    (A0)+,D3
check_copy_protection_bitloop:
    ASR.W     #1,D3
    BCC.S     check_copy_protection_bitloop2
    ADDQ.W    #1,D0
check_copy_protection_bitloop2:
    DBF       D2,check_copy_protection_bitloop
    DBF       D1,check_copy_protection_byteloop

    SUBQ.W    #1,D5       ;already read twice?
    BEQ.S     check_copy_protection_check     ;yes! => compare the bitcounter

    MOVE.W    D0,D4       ; save the number of bits
    BRA       check_copy_protection_rd_loop ;read sector again

check_copy_protection_check:
    SUB.W     D4,D0       ;same number of bits set?
    BEQ.S     check_copy_protection_rd_retry ;=> that is a fail!

    ST        D0          ;bitcounter change => success
check_copy_protection_return:
    MOVEM.L   (A7)+,A0-A6/D1-D7
    RTS


check_copy_protection_buffer:
    DCB.B     512,0

    ALIGN 4
check_copy_protection_magic:
    DC.B      '100004'

Atari ST Protection: Maupiti Island

This game has a custom disk format, which I’ll describe here:

The two disks are 80 tracks, two sided.

Track 1, Side 1: 9 Sectors, no file system. The boot sector loads remaining 8 sectors in the track, which contains the “custom BIOS”. Interestingly the developer did an off-by–1 error and tries to load 9 sectors starting at sector 2. Therefore the Floprd() call actually fails… But this is not part of the protection.

Track 79, Side 2 on Disk B contains also 9 sectors, which are used to save the current game state.

The rest of these disk doesn’t contain any sector. The tracks are read with the read track command and each track has the following format:

  • 24 * $4E
  • 12 * $00
  • 3 * $A1
  • $FE
  • $07
  • track number
  • $07
  • checksum high byte
  • $07
  • checksum low byte
  • $07
  • 5842 data bytes
  • $4E repeated till the index mark

The code just searches for two $A1 at the beginning of the track, followed by $FE. It also tests the track number (it has to match the position of the head, bit 7 is set for side 2). The checksum is an unsigned word, which is calculated by adding all 5842 (unsigned) data bytes together. The checksum is also tested.

Because the data bytes can contain any byte $00..$FF it can’t be written with a standard FDC, which can’t write bytes like $F5 or $F7.

However the FDC has a bug in read track, which tries to sync all the time and certain bytes can trigger the sync, which will destroy the bytes after that. To avoid that a very simple yet efficient method was used: an $07 byte is prefixed any byte that would resync the FDC (Claus Brod wrote a wonderful article about this). This is also the reason to have these bytes in the track header!

Well, that is it. Efficient, very large payload in the track and not possible to copy.

Atari ST Protection: Indiana Jones and the Last Crusade

Another protection that measures the length of track 79, which has to be smaller than 6020 byte. The copy station created them bit a lower bitrate, on a standard floppy these tracks can not be written.

0001885a: 6100 0068                      BSR      fdcSelect
0001885e: 6100 0082                      BSR      fdcRestore

00018862: 303c 004e                      MOVE.W   #$4E,D0
00018866: 6100 0092                      BSR      fdcSeek
0001886a: 6100 0132                      BSR      fdcReadTrack
0001886e: 6100 010c                      BSR      dmaReadByteOffset
00018872: 23c0 0001 89f6                 MOVE.L   D0,$189F6         ;0x186d bytes long (unused?)

00018878: 303c 004f                      MOVE.W   #$4F,D0
0001887c: 6100 007c                      BSR      fdcSeek
00018880: 6100 011c                      BSR      fdcReadTrack
00018884: 6100 0020                      BSR      fdcDone
00018888: 6100 00f2                      BSR      dmaReadByteOffset ;0x175d bytes long
0001888c: b0bc 0000 1784                 CMP.L    #$1784,D0         ;6020 bytes
00018892: 6e00 000a                      BGT      *+$C [$1889E]
00018896: 203c 0000 0000                 MOVE.L   #0,D0             ;success
0001889c: 4e75                           RTS
0001889e: 203c 0000 0001                 MOVE.L   #1,D0             ;fail
000188a4: 4e75                           RTS


fdcDone
000188a6: 33fc 0080 ffff 8606            MOVE.W   #$80,$FFFF8606
000188ae: 3039 ffff 8604                 MOVE.W   $FFFF8604,D0
000188b4: 0800 0000                      BTST     #0,D0
000188b8: 6600 fff4                      BNE      *-$A [$188AE]
000188bc: 7007                           MOVEQ    #7,D0
000188be: 6100 0006                      BSR      *+$8 [$188C6]
000188c2: 4e75                           RTS

fdcSelect
000188c4: 7005                           MOVEQ    #5,D0
000188c6: 13fc 000e ffff 8800            MOVE.B   #$E,$FFFF8800
000188ce: 1239 ffff 8800                 MOVE.B   $FFFF8800,D1
000188d4: 0201 00f8                      ANDI.B   #$F8,D1
000188d8: 8200                           OR.B     D0,D1
000188da: 13c1 ffff 8802                 MOVE.B   D1,$FFFF8802
000188e0: 4e75                           RTS

fdcRestore
000188e2: 33fc 0080 ffff 8606            MOVE.W   #$80,$FFFF8606
000188ea: 6100 0056                      BSR      fdcDelay
000188ee: 33fc 0000 ffff 8604            MOVE.W   #$00,$FFFF8604
000188f6: 6000 005c                      BRA      fdcWait

fdcSeek
000188fa: 33fc 0086 ffff 8606            MOVE.W   #$86,$FFFF8606
00018902: 6100 003e                      BSR      fdcDelay
00018906: 33c0 ffff 8604                 MOVE.W   D0,$FFFF8604
0001890c: 6100 0046                      BSR      fdcWait
00018910: 33fc 0080 ffff 8606            MOVE.W   #$80,$FFFF8606
00018918: 6100 0028                      BSR      fdcDelay
0001891c: 33fc 0015 ffff 8604            MOVE.W   #$15,$FFFF8604
00018924: 6100 002e                      BSR      fdcWait
00018928: 4e75                           RTS

0001892a: 33fc 0080 ffff 8606            MOVE.W   #$80,$FFFF8606
00018932: 6100 000e                      BSR      fdcDelay
00018936: 33fc 0050 ffff 8604            MOVE.W   #$50,$FFFF8604
0001893e: 6000 0014                      BRA      fdcWait

fdcDelay
00018942: 40e7                           MOVE     SR,-(A7)
00018944: 3f07                           MOVE.W   D7,-(A7)
00018946: 3e3c 0020                      MOVE.W   #$20,D7
0001894a: 51cf fffe                      DBF      D7,*-$0 [$1894A]
0001894e: 3e1f                           MOVE.W   (A7)+,D7
00018950: 46df                           MOVE     (A7)+,SR
00018952: 4e75                           RTS

fdcWait
00018954: 0839 0005 ffff fa01            BTST     #5,$FFFFFA01
0001895c: 6600 fff6                      BNE      fdcWait
00018960: 4e75                           RTS

dmaSetAddress
00018962: 2008                           MOVE.L   A0,D0
00018964: 13c0 ffff 860d                 MOVE.B   D0,$FFFF860D
0001896a: e088                           LSR.L    #8,D0
0001896c: 13c0 ffff 860b                 MOVE.B   D0,$FFFF860B
00018972: e088                           LSR.L    #8,D0
00018974: 13c0 ffff 8609                 MOVE.B   D0,$FFFF8609
0001897a: 4e75                           RTS

dmaReadByteOffset
0001897c: 223c 0001 89fa                 MOVE.L   #$189FA,D1
00018982: 4280                           CLR.L    D0
00018984: 1039 00ff 8609                 MOVE.B   $FF8609,D0
0001898a: e188                           LSL.L    #8,D0
0001898c: 1039 00ff 860b                 MOVE.B   $FF860B,D0
00018992: e188                           LSL.L    #8,D0
00018994: 1039 00ff 860d                 MOVE.B   $FF860D,D0
0001899a: 9081                           SUB.L    D1,D0
0001899c: 4e75                           RTS

fdcReadTrack
0001899e: 303c 2710                      MOVE.W   #10000,D0
000189a2: 51c8 fffe                      DBF      D0,*-$0 [$189A2]
000189a6: 41f9 0001 89fa                 LEA      $189FA,A0
000189ac: 6100 ffb4                      BSR      dmaSetAddress
000189b0: 33fc 0090 ffff 8606            MOVE.W   #$90,$FFFF8606
000189b8: 33fc 0190 ffff 8606            MOVE.W   #$190,$FFFF8606
000189c0: 33fc 0090 ffff 8606            MOVE.W   #$90,$FFFF8606
000189c8: 6100 ff78                      BSR      fdcDelay
000189cc: 33fc 001f ffff 8604            MOVE.W   #$1F,$FFFF8604
000189d4: 6100 ff6c                      BSR      fdcDelay
000189d8: 33fc 0080 ffff 8606            MOVE.W   #$80,$FFFF8606
000189e0: 6100 ff60                      BSR      fdcDelay
000189e4: 33fc 00e4 ffff 8604            MOVE.W   #$E4,$FFFF8604
000189ec: 6100 ff54                      BSR      fdcDelay
000189f0: 6100 ff62                      BSR      fdcWait
000189f4: 4e75                           RTS

000189F6: DC.L 0
000189FA: DS.B buffersize