/* $NetBSD: iwm.s,v 1.7 2015/01/02 15:50:28 christos 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 #include #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 bpl shLI1 | No char at IWM, repeat read 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