/*      $NetBSD: iwm.s,v 1.8 2025/06/14 13:54:52 nat Exp $      */

/*
* Copyright (c) 1996-99 Hauke Fath.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
* iwm.s -- low level routines for Sony floppy disk access.
* The present implementation supports the 800K GCR format on non-DMA
* machines.
*
* The IWM and SWIM chips run in polled mode; they are not capable of
* interrupting the CPU. That's why interrupts need only be blocked
* when there is simply no time for interrupt routine processing,
* i.e. during data transfers.
*
* o  The local routines do not block any interrupts.
*
* o  The iwmXXX() routines that set/get IWM or drive settings are not
*    time critical and do not block interrupts.
*
* o  The iwmXXX() routines that are called to perform data transfers
*    block all interrupts because otherwise the current sector data
*    would be lost.
*    The old status register content is stored on the stack.
*
* o  We run at spl4 to give the NMI switch a chance. All currently
*    supported machines have no interrupt sources > 4 (SSC) -- the
*    Q700 interrupt levels can be shifted around in A/UX mode,
*    but we're not there, yet.
*
* o  As a special case iwmReadSectHdr() must run with interrupts disabled
*    (it transfers data). Depending on the needs of the caller, it
*    may be necessary to block interrupts after completion of the routine
*    so interrupt handling is left to the caller.
*
* If we wanted to deal with incoming serial data / serial interrupts,
* we would have to either call zshard(0) {mac68k/dev/zs.c} or
* zsc_intr_hard(0) {sys/dev/ic/z8530sc.c}. Or we would have to roll our
* own as both of the listed function calls look rather expensive compared
* to a 'tst.b REGADDR ; bne NN'.
*/

#include <m68k/asm.h>

#include <mac68k/obio/iwmreg.h>

#define USE_DELAY       0       /* "1" bombs for unknown reasons */


/*
* References to global name space
*/
       .extern _C_LABEL(TimeDBRA)      | in mac68k/macrom.c
       .extern _C_LABEL(Via1Base)      | in mac68k/machdep.c
       .extern _C_LABEL(IWMBase)       | in iwm_fd.c


       .data

diskTo:
       /*
        * Translation table from 'disk bytes' to 6 bit 'nibbles',
        * taken from the .Sony driver.
        * This could be made a loadable table (via ioctls) to read
        * e.g. ProDOS disks (there is a hook for such a table in .Sony).
        */
       .byte   /* 90 */  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01
       .byte   /* 98 */  0xFF, 0xFF, 0x02, 0x03, 0xFF, 0x04, 0x05, 0x06
       .byte   /* A0 */  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x08
       .byte   /* A8 */  0xFF, 0xFF, 0xFF, 0x09, 0x0A, 0x0B, 0x0C, 0x0D
       .byte   /* B0 */  0xFF, 0xFF, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13
       .byte   /* B8 */  0xFF, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A
       .byte   /* C0 */  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
       .byte   /* C8 */  0xFF, 0xFF, 0xFF, 0x1B, 0xFF, 0x1C, 0x1D, 0x1E
       .byte   /* D0 */  0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0x20, 0x21
       .byte   /* D8 */  0xFF, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28
       .byte   /* E0 */  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x29, 0x2A, 0x2B
       .byte   /* E8 */  0xFF, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32
       .byte   /* F0 */  0xFF, 0xFF, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38
       .byte   /* F8 */  0xFF, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F

hdrLeadIn:
       .byte   0xD5, 0xAA, 0x96

hdrLeadOut:
       .byte   0xDE, 0xAA, 0xFF

dataLeadIn:
       .byte   0xD5, 0xAA, 0xAD

dataLeadOut:
       .byte   0xDE, 0xAA, 0xFF, 0xFF


toDisk:
       /*
        * Translation table from 6-bit nibbles [0x00..0x3f] to 'disk bytes'
        */
       .byte   /* 00 */  0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6
       .byte   /* 08 */  0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3
       .byte   /* 10 */  0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC
       .byte   /* 18 */  0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3
       .byte   /* 20 */  0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE
       .byte   /* 28 */  0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC
       .byte   /* 30 */  0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xf5, 0xF6
       .byte   /* 38 */  0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF

syncPattern:
       /*
        * This sync pattern creates 4 sync chars with 10 bits each that look
        * like 0011111111b (i.e. 0x0FF). As the IWM ignores leading zero
        * bits, it locks on 0xFF after the third sync byte.
        * For convenience, the bytes of the sector data lead-in
        * (D5 AA AD) follow.
        */
       .byte   0xFF, 0x3F, 0xCF, 0xF3, 0xFC, 0xFF
       .byte   0xD5, 0xAA, 0xAD



       .text

/*
* Register conventions:
*      %a0     IWM base address
*      %a1     VIA1 base address
*
*      %d0     return value (0 == no error)
*
* Upper bits in data registers that are not cleared give nasty
* (pseudo-) random errors when building an address. Make sure those
*  registers are cleaned with a moveq before use!
*/



/**
**     Export wrappers
**/

/*
* iwmQueryDrvFlags -- export wrapper for driveStat
*
* Parameters:  stack   l       drive selector
*              stack   l       register selector
* Returns:     %d0             flag
*/
ENTRY(iwmQueryDrvFlag)
       link    %a6,#0
       moveml  %d1/%a0-%a1,%sp@-
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       movel   %a6@(8),%d0             | Get drive #
       beq     quDrv00
       cmpl    #1,%d0
       beq     quDrv01

       bra     quDone                  | Invalid drive #

quDrv00:
       tstb    %a0@(intDrive)          | SELECT; choose drive #0
       bra     queryDrv

quDrv01:
       tstb    %a0@(extDrive)          | SELECT; choose drive #1

queryDrv:
       movel   %a6@(12),%d0            | Get register #
       bsr     driveStat

quDone:
       moveml  %sp@+,%d1/%a0-%a1
       unlk    %a6
       rts


/*
* iwmReadSectHdr -- read and decode the next available sector header.
*
* Parameters:  stack   l       Address of sector header struct (I/O)
*                              b       side (0, 1)
*                              b       track (0..79)
*                              b       sector (0..11)
* Returns:     %d0             result code
*/
ENTRY(iwmReadSectHdr)
       link    %a6,#0
       moveml  %d1-%d5/%a0-%a4,%sp@-
       movel   %a6@(0x08),%a4          | Get param block address
       bsr     readSectHdr
       moveml  %sp@+,%d1-%d5/%a0-%a4
       unlk    %a6
       rts



/**
**     Exported functions
**/

/*
* iwmInit -- Initialize IWM chip.
*
* Parameters:  -
* Returns:     %d0             result code
*/
ENTRY(iwmInit)
       link    %a6,#0
       moveml  %d2/%a0,%sp@-
       movel   _C_LABEL(IWMBase),%a0

       /*
        * Reset IWM to known state (clear disk I/O latches)
        */
       tstb    %a0@(ph0L)              | CA0
       tstb    %a0@(ph1L)              | CA1
       tstb    %a0@(ph2L)              | CA2
       tstb    %a0@(ph3L)              | LSTRB

       tstb    %a0@(mtrOff)            | ENABLE; make sure drive is off
       tstb    %a0@(intDrive)          | SELECT; choose drive 1
       moveq   #0x1F,%d0               | XXX was 0x17 -- WHY!?

       /*
        * First do it quick...
        */
       tstb    %a0@(q6H)
       andb    %a0@(q7L),%d0           | status register
       tstb    %a0@(q6L)
       cmpib   #iwmMode,%d0            | all is well??
       beq     initDone

       /*
        * If this doesn't succeed (e.g. drive still running),
        * we do it thoroughly.
        */
       movel   #0x00080000,%d2         | ca. 500,000 retries = 1.5 sec
initLp:
       moveq   #initIWMErr,%d0         | Initialization error
       subql   #1,%d2
       bmi     initErr
       tstb    %a0@(mtrOff)            | disable drive
       tstb    %a0@(q6H)
       moveq   #0x3F,%d0
       andb    %a0@(q7L),%d0
       bclr    #5,%d0                  | Reset bit 5 and set Z flag
                                       | according to previous state
       bne     initLp                  | Loop if drive still on
       cmpib   #iwmMode,%d0
       beq     initDone
       moveb   #iwmMode,%a0@(q7H)      | Init IWM
       tstb    %a0@(q7L)
       bra     initLp

initDone:
       tstb    %a0@(q6L)               | Prepare IWM for data
       moveq   #0,%d0                  | noErr

initErr:
       moveml  %sp@+,%d2/%a0
       unlk    %a6
       rts


/*
* iwmCheckDrive -- Check if given drive is available and return bit vector
*      with capabilities (SS/DS, disk inserted, ...)
*
* Parameters:  stack   l       Drive number (0,1)
* Returns:     %d0     Bit      0 - 0 = Drive is single sided
*                               1 - 0 = Disk inserted
*                               2 - 0 = Motor is running
*                               3 - 0 = Disk is write protected
*                               4 - 0 = Disk is DD
*                              31 - (-1) No drive / invalid drive #
*/
ENTRY(iwmCheckDrive)
       link    %a6,#0
       moveml  %d1/%a0-%a1,%sp@-
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       moveq   #-1,%d1                 | no drive

       movel   %a6@(0x08),%d0          | check drive #
       beq     chkDrv00
       cmpl    #1,%d0
       beq     chkDrv01

       bra     chkDone                 | invalid drive #

chkDrv00:
       tstb    %a0@(intDrive)          | SELECT; choose drive #0
       bra     chkDrive

chkDrv01:
       tstb    %a0@(extDrive)          | SELECT; choose drive #1

chkDrive:
       moveq   #-2,%d1                 | error code
       moveq   #drvInstalled,%d0       | Drive installed?
       bsr     driveStat
       bmi     chkDone                 | no drive

       moveq   #0,%d1                  | Drive found
       tstb    %a0@(mtrOn)             | ENABLE; activate drive
       moveq   #singleSided,%d0        | Drive is single-sided?
       bsr     driveStat
       bpl     chkHasDisk
       /*
        * Drive is double-sided -- this is not really a surprise as the
        * old ss 400k drive needs disk speed control from the Macintosh
        * and we're not doing that here. Anyway - just in case...
        * I am not sure m680x0 Macintoshes (x>0) support 400K drives at all
        * due to their radically different sound support.
        */
       bset    #0,%d1                  | 1 = no.
chkHasDisk:
       moveq   #diskInserted,%d0       | Disk inserted?
       bsr     driveStat
       bpl     chkMotorOn
       bset    #1,%d1                  | 1 = No.
       bra     chkDone
chkMotorOn:
       moveq   #drvMotorState,%d0      | Motor is running?
       bsr     driveStat
       bpl     chkWrtProt
       bset    #2,%d1                  | 1 = No.
chkWrtProt:
       moveq   #writeProtected,%d0     | Disk is write protected?
       bsr     driveStat
       bpl     chkDD_HD
       bset    #3,%d1                  | 1 = No.
chkDD_HD:
       moveq   #diskIsHD,%d0           | Disk is HD? (was "drive installed")
       bsr     driveStat
       bpl     chkDone
       bset    #4,%d1                  | 1 = No.
chkDone:
       movel   %d1,%d0
       moveml  %sp@+,%d1/%a0-%a1
       unlk    %a6
       rts


/*
* iwmDiskEject -- post EJECT command and toggle LSTRB line to give a
* strobe signal.
* IM III says pulse length = 500 ms, but we seem to get away with
* less delay; after all, we spin lock the CPU with it.
*
* Parameters:  stack   l       drive number (0,1)
*              %a0             IWMBase
*              %a1             VIABase
* Returns:     %d0             result code
*/
ENTRY(iwmDiskEject)
       link    %a6,#0
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       movel   %a6@(0x08),%d0          | Get drive #
       beq     ejDrv00
       cmpw    #1,%d0
       beq     ejDrv01

       bra     ejDone                  | Invalid drive #

ejDrv00:
       tstb    %a0@(intDrive)          | SELECT; choose drive #0
       bra     ejDisk

ejDrv01:
       tstb    %a0@(extDrive)          | SELECT; choose drive #1
ejDisk:
       tstb    %a0@(mtrOn)             | ENABLE; activate drive

       moveq   #motorOffCmd,%d0        | Motor off
       bsr     driveCmd

       moveq   #diskInserted,%d0       | Disk inserted?
       bsr     driveStat
       bmi     ejDone

       moveq   #ejectDiskCmd,%d0       | Eject it
       bsr     selDriveReg

       tstb    %a0@(ph3H)              | LSTRB high
#if USE_DELAY
       movel   #1000,%sp@-             | delay 1 ms
       jsr     _C_LABEL(delay)
       addqw   #4,%sp                  | clean up stack
#else
       movew   #1,%d0
       bsr     iwmDelay
#endif
       tstb    %a0@(ph3L)              | LSTRB low
       moveq   #0,%d0                  | All's well...
ejDone:
       unlk    %a6
       rts


/*
* iwmSelectDrive -- select internal (0) / external (1) drive.
*
* Parameters:  stack   l       drive ID (0/1)
* Returns:     %d0             drive #
*/
ENTRY(iwmSelectDrive)
       link    %a6,#0
       moveml  %a0-%a1,%sp@-
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       movel   %a6@(8),%d0             | Get drive #
       bne     extDrv
       tstb    %a0@(intDrive)
       bra     sdDone
extDrv:
       tstb    %a0@(extDrive)
sdDone:
       moveml  %sp@+,%a0-%a1
       unlk    %a6
       rts


/*
* iwmMotor -- switch drive motor on/off
*
* Parameters:  stack   l       drive ID (0/1)
*              stack   l       on(1)/off(0)
* Returns:     %d0             motor cmd
*/
ENTRY(iwmMotor)
       link    %a6,#0
       moveml  %a0-%a1,%sp@-
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       movel   %a6@(8),%d0             | Get drive #
       bne     mtDrv1
       tstb    %a0@(intDrive)
       bra     mtSwitch
mtDrv1:
       tstb    %a0@(extDrive)
mtSwitch:
       movel   #motorOnCmd,%d0         | Motor ON
       tstl    %a6@(12)
       bne     mtON
       movel   #motorOffCmd,%d0
mtON:
       bsr     driveCmd

       moveml  %sp@+,%a0-%a1
       unlk    %a6
       rts


/*
* iwmSelectSide -- select side 0 (lower head) / side 1 (upper head).
*
* This MUST be called immediately before an actual read/write access.
*
* Parameters:  stack   l       side bit (0/1)
* Returns:     -
*/
ENTRY(iwmSelectSide)
       link    %a6,#0
       moveml  %d1/%a0-%a1,%sp@-
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       moveq   #0x0B,%d0               | Drive ready for reading?
       bsr     selDriveReg             | (undocumented)
ss01:
       bsr     dstatus
       bmi     ss01

       moveq   #rdDataFrom0,%d0        | Lower head
       movel   %a6@(0x08),%d1          | Get side #
       beq     ssSide0
       moveq   #rdDataFrom1,%d0        | Upper head
ssSide0:
       bsr     driveStat

       moveml  %sp@+,%d1/%a0-%a1
       unlk    %a6
       rts


/*
* iwmTrack00 -- move head to track 00 for drive calibration.
*
* XXX Drive makes funny noises during restore. Tune delay/retry count?
*
* Parameters:  -
* Returns:     %d0             result code
*/
ENTRY(iwmTrack00)
       link    %a6,#0
       moveml  %d1-%d4/%a0-%a1,%sp@-
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       moveq   #motorOnCmd,%d0         | Switch drive motor on
       bsr     driveCmd

       moveq   #stepOutCmd,%d0         | Step out
       bsr     driveCmd

       movew   #100,%d2                | Max. tries
t0Retry:
       moveq   #atTrack00,%d0          | Already at track 0?
       bsr     driveStat
       bpl     isTrack00               | Track 0 => Bit 7 = 0

       moveq   #doStepCmd,%d0          | otherwise step
       bsr     driveCmd
       movew   #80,%d4                 | Retries
t0Still:
       moveq   #stillStepping,%d0      | Drive is still stepping?
       bsr     driveStat
       dbmi    %d4,t0Still

       cmpiw   #-1,%d4
       bne     t002

       moveq   #cantStepErr,%d0        | Not ready after many retries
       bra     t0Done
t002:

#if USE_DELAY
       movel   #15000,%sp@-
       jsr     _C_LABEL(delay)         | in mac68k/clock.c
       addqw   #4,%sp
#else
       movew   #15,%d0
       bsr     iwmDelay
#endif

       dbra    %d2,t0Retry

       moveq   #tk0BadErr,%d0          | Can't find track 00!!
       bra     t0Done

isTrack00:
       moveq   #0,%d0
t0Done:
       moveml  %sp@+,%d1-%d4/%a0-%a1
       unlk    %a6
       rts


/*
* iwmSeek -- do specified # of steps (positive - in, negative - out).
*
* Parameters:  stack   l       # of steps
* returns:     %d0             result code
*/
ENTRY(iwmSeek)
       link    %a6,#0
       moveml  %d1-%d4/%a0-%a1,%sp@-
       movel   _C_LABEL(IWMBase),%a0
       movel   _C_LABEL(Via1Base),%a1

       moveq   #motorOnCmd,%d0         | Switch drive motor on
       bsr     driveCmd

       moveq   #stepInCmd,%d0          | Set step IN
       movel   %a6@(8),%d2             | Get # of steps from stack
       beq     stDone                  | 0 steps? Nothing to do.
       bpl     stepOut

       moveq   #stepOutCmd,%d0         | Set step OUT
       negl    %d2                     | Make # of steps positive
stepOut:
       subql   #1,%d2                  | Loop exits for -1
       bsr     driveCmd                | Set direction
stLoop:
       moveq   #doStepCmd,%d0
       bsr     driveCmd                | Step one!
       movew   #80,%d4                 | Retries
st01:
       moveq   #stillStepping, %d0     | Drive is still stepping?
       bsr     driveStat
       dbmi    %d4,st01

       cmpiw   #-1,%d4
       bne     st02

       moveq   #cantStepErr,%d2        | Not ready after many retries
       bra     stDone
st02:

#if USE_DELAY
       movel   #30,%sp@-
       jsr     _C_LABEL(delay)         | in mac68k/clock.c
       addqw   #4,%sp
#else
       movew   _C_LABEL(TimeDBRA),%d4  | dbra loops per ms
       lsrw    #5,%d4                  | DIV 32
st03:   dbra    %d4,st03                | makes ca. 30 us
#endif

       dbra    %d2,stLoop

       moveq   #0,%d2                  | All is well
stDone:
       movel   %d2,%d0
       moveml  %sp@+,%d1-%d4/%a0-%a1
       unlk    %a6
       rts


/*
* iwmReadSector -- read and decode the next available sector.
*
* TODO:        Poll SCC as long as interrupts are disabled (see top comment)
*              Add a branch for Verify (compare to buffer)
*              Understand and document the checksum algorithm!
*
*              XXX make "sizeof cylCache_t" a symbolic constant
*
* Parameters:  %fp+08  l       Address of sector data buffer (512 bytes)
*              %fp+12  l       Address of sector header struct (I/O)
*              %fp+16  l       Address of cache buffer ptr array
* Returns:     %d0             result code
* Local:       %fp-2   w       CPU status register
*              %fp-3   b       side,
*              %fp-4   b       track,
*              %fp-5   b       sector wanted
*              %fp-6   b       retry count
*              %fp-7   b       sector read
*/
ENTRY(iwmReadSector)
       link    %a6,#-8
       moveml  %d1-%d7/%a0-%a5,%sp@-

       movel   _C_LABEL(Via1Base),%a1
       movel   %a6@(o_hdr),%a4         | Addr of sector header struct

       moveb   %a4@+,%a6@(-3)          | Save side bit,
       moveb   %a4@+,%a6@(-4)          | track#,
       moveb   %a4@,%a6@(-5)           | sector#
       moveb   #2*maxGCRSectors,%a6@(-6) | Max. retry count

       movew   %sr,%a6@(-2)            | Save CPU status register
       oriw    #0x0600,%sr             | Block all interrupts

rsNextSect:
       movel   %a6@(o_hdr),%a4         | Addr of sector header struct
       bsr     readSectHdr             | Get next available SECTOR header
       bne     rsDone                  | Return if error

       /*
        * Is this the right track & side? If not, return with error
        */
       movel   %a6@(o_hdr),%a4         | Sector header struct

       moveb   %a4@(o_side),%d1        | Get actual side
       lsrb    #3,%d1                  | "Normalize" side bit (to bit 0)
       andb    #1,%d1
       moveb   %a6@(-3),%d2            | Get wanted side
       eorb    %d1,%d2                 | Compare side bits
       bne     rsSeekErr               | Should be equal!

       moveb   %a6@(-4),%d1            | Get track# we want
       cmpb    %a4@(o_track),%d1       | Compare to the header we've read
       beq     rsGetSect

rsSeekErr:
       moveq   #seekErr,%d0            | Wrong track or side found
       bra     rsDone

       /*
        * Check for sector data lead-in 'D5 AA AD'
        * Registers:
        *      %a0 points to data register of IWM as set up by readSectHdr
        *      %a2 points to 'diskTo' translation table
        *      %a4 points to tags buffer
        */
rsGetSect:
       moveb   %a4@(2),%a6@(-7)        | save sector number
       lea     %a4@(3),%a4             | Beginning of tag buffer
       moveq   #50,%d3                 | Max. retries for sector lookup
rsLeadIn:
       lea     dataLeadIn,%a3          | Sector data lead-in
       moveq   #0x03,%d4               | is 3 bytes long
rsLI1:
       moveb   %a0@,%d2                | Get next byte
       bpl     rsLI1
       dbra    %d3,rsLI2
       moveq   #noDtaMkErr,%d0         | Can't find a data mark
       bra     rsDone

rsLI2:
       cmpb    %a3@+,%d2
       bne     rsLeadIn                | If ne restart scan
       subqw   #1,%d4
       bne     rsLI1

       /*
        * We have found the lead-in. Now get the 12 tag bytes.
        * (We leave %a3 pointing to 'dataLeadOut' for later.)
        */
rsTagNyb0:
       moveb   %a0@,%d3                | Get a char,
       bpl     rsTagNyb0
       moveb   %a2@(0,%d3),%a4@+       | remap and store it

       moveq   #0,%d5                  | Clear checksum registers
       moveq   #0,%d6
       moveq   #0,%d7
       moveq   #10,%d4                 | Loop counter
       moveq   #0,%d3                  | Data scratch reg

rsTags:
rsTagNyb1:
       moveb   %a0@,%d3                | Get 2 bit nibbles
       bpl     rsTagNyb1
       moveb   %a2@(0,%d3),%d1         | Remap disk byte
       rolb    #2,%d1
       moveb   %d1,%d2
       andib   #0xC0,%d2               | Get top 2 bits for first byte
rsTagNyb2:
       moveb   %a0@,%d3                | Get first 6 bit nibble
       bpl     rsTagNyb2
       orb     %a2@(0,%d3),%d2         | Remap it and complete first byte

       moveb   %d7,%d3                 | The X flag bit (a copy of the carry
       addb    %d7,%d3                 | flag) is added with the next addx

       rolb    #1,%d7
       eorb    %d7,%d2
       moveb   %d2,%a4@+               | Store tag byte
       addxb   %d2,%d5                 | See above

       rolb    #2,%d1
       moveb   %d1,%d2
       andib   #0xC0,%d2               | Get top 2 bits for second byte
rsTagNyb3:
       moveb   %a0@,%d3                | Get second 6 bit nibble
       bpl     rsTagNyb3
       orb     %a2@(0,%d3),%d2         | remap it and complete byte
       eorb    %d5,%d2
       moveb   %d2,%a4@+               | Store tag byte
       addxb   %d2,%d6

       rolb    #2,%d1
       andib   #0xC0,%d1               | Get top 2 bits for third byte
rsTagNyb4:
       moveb   %a0@,%d3                | Get third 6 bit nibble
       bpl     rsTagNyb4
       orb     %a2@(0,%d3),%d1         | remap it and complete byte
       eorb    %d6,%d1
       moveb   %d1,%a4@+               | Store tag byte
       addxb   %d1,%d7

       subqw   #3,%d4                  | Update byte counter (four 6&2 encoded
       bpl     rsTags                  | disk bytes make three data bytes).

       /*
        * Jetzt sind wir hier...
        * ...und Thomas D. hat noch was zu sagen...
        *
        * We begin to read in the actual sector data.
        * Compare sector # to what we wanted: If it matches, read directly
        * to buffer, else read to track cache.
        */
       movew   #0x01FE,%d4             | Loop counter
       moveq   #0,%d1                  | Clear %d1
       moveb   %a6@(-7),%d1            | Get sector# we have read
       cmpb    %a6@(-5),%d1            | Compare to the sector# we want
       bne     rsToCache
       movel   %a6@(o_buf),%a4         | Sector data buffer
       bra     rsData
rsToCache:
       movel   %a6@(o_rslots),%a4      | Base address of slot array
       lslw    #3,%d1                  | sizeof cylCacheSlot_t is 8 bytes
       movel   #-1,%a4@(o_valid,%d1)
       movel   %a4@(o_secbuf,%d1),%a4  | and get its buffer ptr
rsData:
rsDatNyb1:
       moveb   %a0@,%d3                | Get 2 bit nibbles
       bpl     rsDatNyb1
       moveb   %a2@(0,%d3),%d1         | Remap disk byte
       rolb    #2,%d1
       moveb   %d1,%d2
       andib   #0xC0,%d2               | Get top 2 bits for first byte
rsDatNyb2:
       moveb   %a0@,%d3                | Get first 6 bit nibble
       bpl     rsDatNyb2
       orb     %a2@(0,%d3),%d2         | Remap it and complete first byte

       moveb   %d7,%d3                 | The X flag bit (a copy of the carry
       addb    %d7,%d3                 | flag) is added with the next addx

       rolb    #1,%d7
       eorb    %d7,%d2
       moveb   %d2,%a4@+               | Store data byte
       addxb   %d2,%d5                 | See above

       rolb    #2,%d1
       moveb   %d1,%d2
       andib   #0xC0,%d2               | Get top 2 bits for second byte
rsDatNyb3:
       moveb   %a0@,%d3                | Get second 6 bit nibble
       bpl     rsDatNyb3
       orb     %a2@(0,%d3),%d2         | Remap it and complete byte
       eorb    %d5,%d2
       moveb   %d2,%a4@+               | Store data byte
       addxb   %d2,%d6
       tstw    %d4
       beq     rsCkSum                 | Data read, continue with checksums

       rolb    #2,%d1
       andib   #0xC0,%d1               | Get top 2 bits for third byte
rsDatNyb4:
       moveb   %a0@,%d3                | Get third 6 bit nibble
       bpl     rsDatNyb4
       orb     %a2@(0,%d3),%d1         | Remap it and complete byte
       eorb    %d6,%d1
       moveb   %d1,%a4@+               | Store data byte
       addxb   %d1,%d7
       subqw   #3,%d4                  | Update byte counter
       bra     rsData

       /*
        * Next read checksum bytes
        * While reading the sector data, three separate checksums are
        * maintained in %D5/%D6/%D7 for the 1st/2nd/3rd data byte of
        * each group.
        */
rsCkSum:
rsCkS1:
       moveb   %a0@,%d3                | Get 2 bit nibbles
       bpl     rsCkS1
       moveb   %a2@(0,%d3),%d1         | Remap disk byte
       bmi     rsBadCkSum              | Fault! (Bad read)
       rolb    #2,%d1
       moveb   %d1,%d2
       andib   #0xC0,%d2               | Get top 2 bits for first byte
rsCkS2:
       moveb   %a0@,%d3                | Get first 6 bit nibble
       bpl     rsCkS2
       moveb   %a2@(0,%d3),%d3         | and remap it
       bmi     rsBadCkSum              | Fault! ( > 0x3f is bad read)
       orb     %d3,%d2                 | Merge 6&2
       cmpb    %d2,%d5                 | Compare first checksum to %D5
       bne     rsBadCkSum              | Fault! (Checksum)

       rolb    #2,%d1
       moveb   %d1,%d2
       andib   #0xC0,%d2               | Get top 2 bits for second byte
rsCkS3:
       moveb   %a0@,%d3                | Get second 6 bit nibble
       bpl     rsCkS3
       moveb   %a2@(0,%d3),%d3         | and remap it
       bmi     rsBadCkSum              | Fault! (Bad read)
       orb     %d3,%d2                 | Merge 6&2
       cmpb    %d2,%d6                 | Compare second checksum to %D6
       bne     rsBadCkSum              | Fault! (Checksum)

       rolb    #2,%d1
       andib   #0xC0,%d1               | Get top 2 bits for second byte
rsCkS4:
       moveb   %a0@,%d3                | Get third 6 bit nibble
       bpl     rsCkS4
       moveb   %a2@(0,%d3),%d3         | and remap it
       bmi     rsBadCkSum              | Fault! (Bad read)
       orb     %d3,%d1                 | Merge 6&2
       cmpb    %d1,%d7                 | Compare third checksum to %D7
       beq     rsLdOut                 | Fault! (Checksum)

rsBadCkSum:
       moveq   #badDCkSum,%d0          | Bad data mark checksum
       bra     rsDone

rsBadDBtSlp:
       moveq   #badDBtSlp,%d0          | One of the data mark bit slip
       bra     rsDone                  | nibbles was incorrect

       /*
        * We have gotten the checksums allright, now look for the
        * sector data lead-out 'DE AA'
        * (We have %a3 still pointing to 'dataLeadOut'; this part of the
        * table is used for writing to disk, too.)
        */
rsLdOut:
       moveq   #1,%d4                  | Is two bytes long {1,0}
rsLdOut1:
       moveb   %a0@,%d3                | Get token
       bpl     rsLdOut1
       cmpb    %a3@+,%d3
       bne     rsBadDBtSlp             | Fault!
       dbra    %d4,rsLdOut1
       moveq   #0,%d0                  | OK.

       /*
        * See if we got the sector we wanted. If not, and no error
        * occurred, mark buffer valid. Else ignore the sector.
        * Then, read on.
        */
rsDone:
       movel   %a6@(o_hdr),%a4         | Addr of sector header struct
       moveb   %a4@(o_sector),%d1      | Get # of sector we have just read
       cmpb    %a6@(-5),%d1            | Compare to the sector we want
       beq     rsAllDone

       tstb    %d0                     | Any error? Simply ignore data
       beq     rsBufValid
       lslw    #3,%d1                  | sizeof cylCacheSlot_t is 8 bytes
       movel   %a6@(o_rslots),%a4
       clrl    %a4@(o_valid,%d1)       | Mark buffer content "invalid"

rsBufValid:
       subqb   #1,%a6@(-6)             | max. retries
       bne     rsNextSect
                                       | Sector not found, but
       tstb    %d0                     | don't set error code if we
       bne     rsAllDone               | already have one.
       moveq   #sectNFErr,%d0
rsAllDone:
       movew   %a6@(-2),%sr            | Restore interrupt mask
       moveml  %sp@+,%d1-%d7/%a0-%a5
       unlk    %a6
       rts


/*
* iwmWriteSector -- encode and write data to the specified sector.
*
* TODO:        Poll SCC as long as interrupts are disabled (see top comment)
*              Understand and document the checksum algorithm!
*
*              XXX Use registers more efficiently
*
* Parameters:  %fp+8   l       Address of sector header struct (I/O)
*              %fp+12  l       Address of cache buffer ptr array
* Returns:     %d0             result code
*
* Local:       %fp-2   w       CPU status register
*              %fp-3   b       side,
*              %fp-4   b       track,
*              %fp-5   b       sector wanted
*              %fp-6   b       retry count
*              %fp-10  b       current slot
*/
ENTRY(iwmWriteSector)
       link    %a6,#-10
       moveml  %d1-%d7/%a0-%a5,%sp@-

       movel   _C_LABEL(Via1Base),%a1
       movel   %a6@(o_hdr),%a4         | Addr of sector header struct

       moveb   %a4@+,%a6@(-3)          | Save side bit,
       moveb   %a4@+,%a6@(-4)          | track#,
       moveb   %a4@,%a6@(-5)           | sector#
       moveb   #maxGCRSectors,%a6@(-6) | Max. retry count

       movew   %sr,%a6@(-2)            | Save CPU status register
       oriw    #0x0600,%sr             | Block all interrupts

wsNextSect:
       movel   %a6@(o_hdr),%a4
       bsr     readSectHdr             | Get next available sector header
       bne     wsAllDone               | Return if error

       /*
        * Is this the right track & side? If not, return with error
        */
       movel   %a6@(o_hdr),%a4         | Sector header struct

       moveb   %a4@(o_side),%d1        | Get side#
       lsrb    #3,%d1                  | "Normalize" side bit...
       andb    #1,%d1
       moveb   %a6@(-3),%d2            | Get wanted side
       eorb    %d1,%d2                 | Compare side bits
       bne     wsSeekErr

       moveb   %a6@(-4),%d1            | Get wanted track#
       cmpb    %a4@(o_track),%d1       | Compare to the read header
       beq     wsCompSect

wsSeekErr:
       moveq   #seekErr,%d0            | Wrong track or side
       bra     wsAllDone

       /*
        * Look up the current sector number in the cache.
        * If the buffer is dirty ("valid"), write it to disk. If not,
        * loop over all the slots and return if all of them are clean.
        *
        * Alternatively, we could decrement a "dirty sectors" counter here.
        */
wsCompSect:
       moveq   #0,%d1                  | Clear register
       moveb   %a4@(o_sector),%d1      | get the # of header read
       lslw    #3,%d1                  | sizeof cylCacheSlot_t is 8 bytes
       movel   %a6@(o_wslots),%a4
       tstl    %a4@(o_valid,%d1)       | Sector dirty?
       bne     wsBufDirty

       moveq   #maxGCRSectors-1,%d2    | Any dirty sectors left?
wsChkDty:
       movew   %d2,%d1
       lslw    #3,%d1                  | sizeof cylCacheSlot_t is 8 bytes
       tstl    %a4@(o_valid,%d1)
       bne     wsNextSect              | Sector dirty?
       dbra    %d2,wsChkDty

       bra     wsAllDone               | We are through with this track.


       /*
        * Write sync pattern and sector data lead-in 'D5 AA'. The
        * missing 'AD' is made up by piping 0x0B through the nibble
        * table (toDisk).
        *
        * To set up IWM for writing:
        *
        * access q6H & write first byte to q7H.
        * Then check bit 7 of q6L (status reg) for 'IWM ready'
        * and write subsequent bytes to q6H.
        *
        * Registers:
        *      %a0     IWM base address (later: data register)
        *      %a1     Via1Base
        *      %a2     IWM handshake register
        *      %a3     data (tags buffer, data buffer)
        *      %a4     Sync pattern, 'toDisk' translation table
        */
wsBufDirty:
       movel   _C_LABEL(IWMBase),%a0
       lea     %a4@(0,%d1),%a3
       movel   %a3,%a6@(-10)           | Save ptr to current slot
       tstb    %a0@(q6H)               | Enable writing to disk
       movel   %a6@(o_hdr),%a4         | Sector header struct
       lea     %a4@(o_Tags),%a3        | Point %a3 to tags buffer
       lea     syncPattern,%a4

       moveb   %a4@+,%a0@(q7H)         | Write first sync byte
       lea     %a0@(q6L),%a2           | Point %a2 to handshake register
       lea     %a0@(q6H),%a0           | Point %a0 to IWM data register

       moveq   #6,%d0                  | Loop counter for sync bytes
       moveq   #0,%d2
       moveq   #0,%d3
       movel   #0x02010009,%d4         | Loop counters for tag/sector data

       /*
        * Write 5 sync bytes and first byte of sector data lead-in
        */
wsLeadIn:
       moveb   %a4@+,%d1               | Get next sync byte
wsLI1:
       tstb    %a2@                    | IWM ready?
       bpl     wsLI1
       moveb   %d1,%a0@                | Write it to disk
       subqw   #1,%d0
       bne     wsLeadIn

       moveb   %a4@+,%d1               | Write 2nd byte of sector lead-in
       lea     toDisk,%a4              | Point %a4 to nibble translation table
wsLI2:
       tstb    %a2@                    | IWM ready?
       bpl     wsLI2
       moveb   %d1,%a0@                | Write it to disk

       moveq   #0,%d5                  | Clear checksum registers
       moveq   #0,%d6
       moveq   #0,%d7

       moveq   #0x0B,%d1               | 3rd byte of sector data lead-in
                                       | (Gets translated to 0xAD)
       moveb   %a3@+,%d2               | Get 1st byte from tags buffer
       bra     wsDataEntry

       /*
        * The following loop reads the content of the tags buffer (12 bytes)
        * and the data buffer (512 bytes).
        * Each pass reads out three bytes and
        * a) splits them 6&2 into three 6 bit nibbles and a fourth byte
        *    consisting of the three 2 bit nibbles
        * b) encodes the nibbles with a table to disk bytes (bit 7 set, no
        *    more than two consecutive zero bits) and writes them to disk as
        *
        *    00mmnnoo          fragment 2 bit nibbles
        *    00mmmmmm          6 bit nibble -- first byte
        *    00nnnnnn          6 bit nibble -- second byte
        *    00oooooo          6 bit nibble -- third byte
        *
        * c) adds up three 8 bit checksums, one for each of the bytes written.
        */
wsSD1:
       movel   %a6@(-10),%a3           | Get ptr to current slot
       movel   %a3@(o_secbuf),%a3      | Get start of sector data buffer

wsData:
       addxb   %d2,%d7
       eorb    %d6,%d2
       moveb   %d2,%d3
       lsrw    #6,%d3                  | Put 2 bit nibbles into place
wsRDY01:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY01
       moveb   %a4@(0,%d3),%a0@        | Translate nibble and write
       subqw   #3,%d4                  | Update counter
       moveb   %d7,%d3
       addb    %d7,%d3                 | Set X flag (Why?)
       rolb    #1,%d7
       andib   #0x3F,%d0
wsRDY02:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY02
       moveb   %a4@(0,%d0),%a0@        | Translate nibble and write

       /*
        * We enter with the last byte of the sector data lead-in
        * between our teeth (%D1, that is).
        */
wsDataEntry:
       moveb   %a3@+,%d0               | Get first byte
       addxb   %d0,%d5
       eorb    %d7,%d0
       moveb   %d0,%d3                 | Keep top two bits
       rolw    #2,%d3                  | by shifting them to MSByte
       andib   #0x3F,%d1
wsRDY03:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY03
       moveb   %a4@(0,%d1),%a0@        | Translate nibble and write

       moveb   %a3@+,%d1                       | Get second byte
       addxb   %d1,%d6
       eorb    %d5,%d1
       moveb   %d1,%d3                 | Keep top two bits
       rolw    #2,%d3                  | by shifting them to MSByte
       andib   #0x3F,%d2
wsRDY04:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY04
       moveb   %a4@(0,%d2),%a0@        | Translate nibble and write

       /*
        * XXX We have a classic off-by-one error here: the last access
        * reaches beyond the data buffer which bombs with memory
        * protection. The value read isn't used anyway...
        * Hopefully there is enough time for an additional check
        * (exit the last loop cycle before the buffer access).
        */
       tstl    %d4                     | Last loop cycle?
       beq     wsSDDone                | Then get out while we can.

       moveb   %a3@+,%d2               | Get third byte
       tstw    %d4                     | First write tag buffer,...
       bne     wsData

       swap    %d4                     | ...then write data buffer
       bne     wsSD1

       /*
        * Write nibbles for last 2 bytes, then
        * split checksum bytes in 6&2 and write them to disk
        */
wsSDDone:
       clrb    %d3                     | No 513th byte
       lsrw    #6,%d3                  | Set up 2 bit nibbles
wsRDY05:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY05
       moveb   %a4@(0,%d3),%a0@        | Write fragments
       moveb   %d5,%d3
       rolw    #2,%d3
       moveb   %d6,%d3
       rolw    #2,%d3
       andib   #0x3F,%d0
wsRDY06:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY06
       moveb   %a4@(0,%d0),%a0@        | Write 511th byte
       andib   #0x3F,%d1
wsRDY07:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY07
       moveb   %a4@(0,%d1),%a0@        | write 512th byte
       moveb   %d7,%d3
       lsrw    #6,%d3                  | Get fragments ready
wsRDY08:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY08
       moveb   %a4@(0,%d3),%a0@        | Write fragments
       andib   #0x3F,%d5
wsRDY09:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY09
       moveb   %a4@(0,%d5),%a0@        | Write first checksum byte
       andib   #0x3F,%D6
wsRDY10:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY10
       moveb   %a4@(0,%d6),%a0@        | Write second checksum byte
       andib   #0x3F,%d7
wsRDY11:
       tstb    %a2@                    | IWM ready?
       bpl     wsRDY11
       moveb   %a4@(0,%d7),%a0@        | Write third checksum byte

       /*
        * Write sector data lead-out
        */
       lea     dataLeadOut,%a4         | Sector data lead-out
       moveq   #3,%d2                  | Four bytes long {3,2,1,0}
wsLeadOut:
       moveb   %a2@,%d1                | IWM ready?
       bpl     wsLeadOut
       moveb   %a4@+,%a0@              | Write lead-out
       dbf     %d2,wsLeadOut

       moveq   #0,%d0
       btst    #6,%d1                  | Check IWM underrun bit
       bne     wsNoErr

       moveq   #wrUnderRun,%d0         | Could not write
                                       | fast enough to keep up with IWM
wsNoErr:
       tstb    %a0@(0x0200)            | q7L -- Write OFF

wsDone:
       tstb    %d0                     | Any error? Simply retry
       bne     wsBufInvalid

       movel   %a6@(-10),%a4           | Else, get ptr to current slot
       clrl    %a4@(o_valid)           | Mark current buffer "clean"
       bra     wsNextSect

wsBufInvalid:
       subqb   #1,%a6@(-6)             | retries
       bne     wsNextSect
                                       | Sector not found, but
       tstb    %d0                     | don't set error code if we
       bne     wsAllDone               | already have one.
       moveq   #sectNFErr,%d0

wsAllDone:
       movew   %a6@(-2),%sr            | Restore interrupt mask
       moveml  %sp@+,%d1-%d7/%a0-%a5
       unlk    %a6
       rts



/**
**     Local functions
**/

/*
* iwmDelay
*
* In-kernel calls to delay() in mac68k/clock.c bomb
*
* Parameters:  %d0     delay in milliseconds
* Trashes:     %d0, %d1
* Returns:     -
*/
iwmDelay:
       /* TimeDBRA is ~8K for 040/33 machines, so we need nested loops */
id00:   movew   _C_LABEL(TimeDBRA),%d1  | dbra loops per ms
id01:   dbra    %d1,id01                |
       dbra    %d0,id00
       rts


/*
* selDriveReg -- Select drive status/control register
*
* Parameters:  %d0     register #
*                      (bit 0 - CA2, bit 1 - SEL, bit 2 - CA0, bit 3 - CA1)
*              %a0     IWM base address
*              %a1     VIA base address
* Returns:     %d0     register # (unchanged)
*/
selDriveReg:
       tstb    %a0@(ph0H)              | default CA0 to 1 (says IM III)
       tstb    %a0@(ph1H)              | default CA1 to 1

       btst    #0,%d0                  | bit 0 set => CA2 on
       beq     se00
       tstb    %a0@(ph2H)
       bra     se01
se00:
       tstb    %a0@(ph2L)

se01:
       btst    #1,%d0                  | bit 1 set => SEL on (VIA 1)
       beq     se02
       bset    #vHeadSel,%a1@(vBufA)
       bra     se03
se02:
       bclr    #vHeadSel,%a1@(vBufA)

se03:
       btst    #2,%d0                  | bit 2 set => CA0 on
       bne     se04
       tstb    %a0@(ph0L)

se04:
       btst    #3,%d0                  | bit 3 set => CA1 on
       bne     se05
       tstb    %a0@(ph1L)
se05:
       rts



/*
* dstatus -- check drive status (bit 7 - N flag) wrt. a previously
* set status tag.
*
* Parameters:  %d0     register selector
*              %a0     IWM base address
* Returns:     %d0     status
*/
dstatus:
       tstb    %a0@(q6H)
       moveb   %a0@(q7L),%d0
       tstb    %a0@(q6L)               | leave in "read data reg"
       tstb    %d0                     | state for safety
       rts


/*
* driveStat -- query drive status.
*
* Parameters:  %a0     IWMBase
*              %a1     VIABase
*              %d0     register selector
* Returns:     %d0     status (Bit 7)
*/
driveStat:
       tstb    %a0@(mtrOn)             | ENABLE; turn drive on
       bsr     selDriveReg
       bsr     dstatus
       rts


/*
* dtrigger -- toggle LSTRB line to give drive a strobe signal
* IM III says pulse length = 1 us < t < 1 ms
*
* Parameters:  %a0     IWMBase
*              %a1     VIABase
* Returns:     -
*/
dtrigger:
       tstb    %a0@(ph3H)              | LSTRB high
       moveb   %a1@(vBufA),%a1@(vBufA) | intelligent nop seen in q700 ROM
       tstb    %a0@(ph3L)              | LSTRB low
       rts


/*
* driveCmd -- send command to drive.
*
* Parameters:  %a0     IWMBase
*              %a1     VIABase
*              %d0     Command token
* Returns:     -
*/
driveCmd:
       bsr     selDriveReg
       bsr     dtrigger
       rts


/*
* readSectHdr -- read and decode the next available sector header.
*
* TODO:        Poll SCC as long as interrupts are disabled.
*
* Parameters:  %a4     sectorHdr_t address
* Returns:     %d0     result code
* Uses:        %d0-%d4, %a0, %a2-%a4
*/
readSectHdr:
       moveq   #3,%d4                  | Read 3 chars from IWM for sync
       movew   #1800,%d3               | Retries to sync to disk
       moveq   #0,%d2                  | Clear scratch regs
       moveq   #0,%d1
       moveq   #0,%d0
       movel   _C_LABEL(IWMBase),%a0   | IWM base address

       tstb    %a0@(q7L)
       lea     %a0@(q6L),%a0           | IWM data register
shReadSy:
       moveb   %a0@,%d2                | Read char
       dbra    %d3,shSeekSync

       moveq   #noNybErr,%d0           | Disk is blank?
       bra     shDone

shSeekSync:
       bpl     shReadSy                | No char at IWM, repeat read
       subqw   #1,%d4
       bne     shReadSy
       /*
        * When we get here, the IWM should be in sync with the data
        * stream from disk.
        * Next look for sector header lead-in 'D5 AA 96'
        */
       movew   #1500,%d3               | Retries to seek header
shLeadIn:
       lea     hdrLeadIn,%a3           | Sector header lead-in bytes
       moveq   #0x03,%d4               | is 3 bytes long
shLI1:
       moveb   %a0@,%d2                | Get next byte
       dbra    %d3,shLI2
       moveq   #noAdrMkErr,%d0         | Can't find an address mark
       bra     shDone

shLI2:
       bpl     shLI1                   | No char at IWM, repeat read
       cmpb    %a3@+,%d2
       bne     shLeadIn                | If ne restart scan
       subqw   #1,%d4
       bne     shLI1
       /*
        * We have found the lead-in. Now get the header information.
        * Reg %d4 holds the checksum.
        */
       lea     diskTo-0x90,%a2         | Translate disk bytes -> 6&2
shHdr1:
       moveb   %a0@,%d0                | Get 1st char
       bpl     shHdr1
       moveb   %a2@(0,%d0),%d1         | and remap it
       moveb   %d1,%d4
       rorw    #6,%d1                  | separate 2:6, drop hi bits
shHdr2:
       moveb   %a0@,%d0                | Get 2nd char
       bpl     shHdr2
       moveb   %a2@(0,%d0),%d2         | and remap it
       eorb    %d2,%d4
shHdr3:
       moveb   %a0@,%d0                | Get 3rd char
       bpl     shHdr3
       moveb   %a2@(0,%d0),%d1         | and remap it
       eorb    %d1,%d4
       rolw    #6,%d1                  |
shHdr4:
       moveb   %a0@,%d0                | Get 4th char
       bpl     shHdr4
       moveb   %a2@(0,%d0),%d3         | and remap it
       eorb    %d3,%d4
shHdr5:
       moveb   %a0@,%d0                | Get checksum byte
       bpl     shHdr5
       moveb   %a2@(0,%d0),%d5         | and remap it
       eorb    %d5,%d4
       bne     shCsErr                 | Checksum ok?
       /*
        * We now have in
        * %d1/lsb      track number
        * %d1/msb      bit 3 is side bit
        * %d2          sector number
        * %d3          ???
        * %d5          checksum (=0)
        *
        * Next check for lead-out.
        */
       moveq   #1,%d4                  | is 2 bytes long
shHdr6:
       moveb   %a0@,%d0                | Get token
       bpl     shHdr6
       cmpb    %a3@+,%d0               | Check
       bne     shLOErr                 | Fault!
       dbra    %d4,shHdr6
       movew   %d1,%d0                 | Isolate side bit
       lsrw    #8,%d0
       moveb   %d0,%a4@+               | and store it
       moveb   %d1,%a4@+               | Store track number
       moveb   %d2,%a4@+               | and sector number
       moveq   #0,%d0                  | All is well
       bra     shDone

shCsErr:
       moveq   #badCkSmErr,%d0         | Bad sector header checksum
       bra     shDone
shLOErr:
       moveq   #badBtSlpErr,%d0        | Bad address mark (no lead-out)

shDone:
       tstl    %d0                     | Set flags
       rts