NetBSD/sys/arch/mac68k/obio/iwm.s
scottr 9da0e1f5c5 First cut at a floppy disk device driver, for IWM and IWM-compatible
controllers.  Supports GCR-encoded disks only (400K and 800K); neither
of the 1.44M formats will work until someone figures out how to drive
the SWIM and its descendants.

This code was written by Hauke Fath, and had only minor touchup (mostly
KNF) by me.
1999-02-18 07:38:26 +00:00

1440 lines
32 KiB
ArmAsm
Raw Blame History

/* $Id: iwm.s,v 1.1 1999/02/18 07:38:26 scottr Exp $ */
/*
* Copyright (c) 1996-98 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 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 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 "iwm_regs.s"
#define USE_DELAY 0 /* "1" bombs for unknown reasons */
/*
* References to global name space
*/
.extern _TimeDBRA | in mac68k/macrom.c
.extern _IWMBase | in mac68k/machdep.c
.extern _VIA1Base |
.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 _IWMBase,a0
movel _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 _IWMBase,a0
movel _Via1Base,a1
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 _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 #0x17,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 #0x17,d0
beq initDone
moveb #0x17,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 _IWMBase,a0
movel _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 _IWMBase,a0
movel _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 _IWMBase,a0
movel _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 _IWMBase,a0
movel _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 _IWMBase,a0
movel _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 resore. Tune delay/retry count?
*
* Parameters: -
* Returns: d0 result code
*/
ENTRY(iwmTrack00)
link a6,#0
moveml d1-d4/a0-a1,sp@-
movel _IWMBase,a0
movel _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 _IWMBase,a0
movel _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 _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!
*
* Parameters: fp+08 l Address of sector data buffer (512 bytes)
* fp+12 l Address of sector header struct (I/O)
* Returns: d0 result code
* Local: fp-2 w CPU status register
* fp-3 b side,
* fp-4 b track,
* fp-5 b sector wanted
*/
ENTRY(iwmReadSector)
link a6,#-6
moveml d1-d7/a0-a5,sp@-
movel _IWMBase,a0
movel _Via1Base,a1
movel a6@(12),a4 | Addr of sector header struct
moveb a4@(0),a6@(-3) | Save side bit,
moveb a4@(1),a6@(-4) | track#,
/* moveb a4@(2),a6@(-5) | sector# */
movew sr,a6@(-2) | Save CPU status register
oriw #0x0700,sr | Block all interrupts
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@(12),a4 | Sector header struct
moveb a4@(0),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 wanted track#
cmpb a4@(1),d1 | Compare to the read header
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
* a2 points to 'diskTo'<EFBFBD>translation table
* a4 points to tags buffer
*/
rsGetSect:
lea a4@(3),a4 | Beginning of tag buffer
moveq #50,d3 | Max. retries to seek
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.
*/
movel a6@(8),a4 | Sector data buffer
movew #0x01FE,d4 | Loop counter
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.
rsDone:
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!
*
* Parameters: fp+8 l Address of sector data buffer (512 bytes)
* fp+12 l Address of sector header struct (I/O)
* Returns: d0 result code
*
* Local: fp-2 w CPU status register
* fp-3 b side,
* fp-4 b track,
* fp-5 b sector wanted
*/
ENTRY(iwmWriteSector)
link a6,#-6
moveml d1-d7/a0-a5,sp@-
movel _IWMBase,a0
movel _Via1Base,a1
movel a6@(12),a4 | Addr of sector header struct
moveb a4@(0),a6@(-3) | Save side bit,
moveb a4@(1),a6@(-4) | track#,
moveb a4@(2),a6@(-5) | sector#
movew sr,a6@(-2) | Save CPU status register
oriw #0x0700,sr | Block all interrupts
bsr readSectHdr | Get next available sector header
bne wsDone | Return if error
/*
* Is this the right track & side? If not, return with error
*/
movel a6@(12),a4 | Sector header struct
moveb a4@(0),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@(1),d1 | Compare to the read header
beq wsCompSect
wsSeekErr:
moveq #seekErr,d0 | Wrong track or side
bra wsDone
/*
* Are we at the right sector? If not, we return with zero flag
* cleared, but d0 = 0.
*/
wsCompSect:
moveq #0,d1 | Clear register
moveb a6@(-5),d1 | Get wanted sector#
cmpb a4@(2),d1 | Compare to the read header
bne wsDone
/*
* 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 Data register of IWM
* a1 Via1Base
* a2 IWM handshake register
* a3 data (tags buffer, data buffer)
* a4 Sync pattern, 'toDisk'<EFBFBD>translation table
*/
movel _IWMBase,a0
tstb a0@(q6H) | Enable writing to disk
lea a4@(3),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@(8),a3 | 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 (??)
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:
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 _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: a0 IWMBase
* a1 VIABase
* a4 sectorHdr_t address
* Returns: d0 result code
*/
readSectHdr:
moveq #3,d4 | Read 3 chars from IWM for sync
movew #600,d3 | Retries to sync to disk
moveq #0,d2 | Clear scratch regs
moveq #0,d1
moveq #0,d0
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
bpl shLI1
dbra d3,shLI2
moveq #noAdrMkErr,d0 | Can't find an address mark
bra shDone
shLI2:
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