blob: 3a63627972122b48abfb78ae135f0a72652e526a [file] [log] [blame] [raw]
/* swtp_dc4_dsk.c: SWTP DC-4 DISK Simulator
Copyright (c) 2005, William A. Beech
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of William A. Beech shall not
be used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from William A. Beech.
Based on work by Charles E Owen (c) 1997 and Peter Schorn (c) 2002-2005
The DC-4 is a 5-inch floppy controller which can control up
to 4 daisy-chained 5-inch floppy drives. The controller is based on
the Western Digital 1797 Floppy Disk Controller (FDC) chip. This
file only emulates the minimum DC-4 functionality to interface with
the virtual disk file.
The floppy controller is interfaced to the CPU by use of 5 memory
addreses. These are device numbers 5 and 6 (0x8014-0x801B).
Address Mode Function
------- ---- --------
0x8014 Read Returns FDC interrupt status
0x8014 Write Selects the drive/head/motor control
0x8018 Read Returns status of FDC
0x8018 Write FDC command register
0x8019 Read Returns FDC track register
0x8019 Write Set FDC track register
0x801A Read Returns FDC sector register
0x801A Write Set FDC sector register
0x801B Read Read data
0x801B Write Write data
Drive Select Read (0x8014):
+---+---+---+---+---+---+---+---+
| I | D | X | X | X | X | X | X |
+---+---+---+---+---+---+---+---+
I = Set indicates an interrupt request from the FDC pending.
D = DRQ pending - same as bit 1 of FDC status register.
Drive Select Write (0x8014):
+---+---+---+---+---+---+---+---+
| M | S | X | X | X | X | Device|
+---+---+---+---+---+---+---+---+
M = If this bit is 1, the one-shot is triggered/retriggered to
start/keep the motors on.
S = Side select. If set, side one is selected otherwise side zero
is selected.
X = not used
Device = value 0 thru 3, selects drive 0-3 to be controlled.
Drive Status Read (0x8018):
+---+---+---+---+---+---+---+---+
| R | P | H | S | C | L | D | B |
+---+---+---+---+---+---+---+---+
B - When 1, the controller is busy.
D - When 1, index mark detected (type I) or data request - read data
ready/write data empty (type II or III).
H - When 1, track 0 (type I) or lost data (type II or III).
C - When 1, crc error detected.
S - When 1, seek (type I) or RNF (type II or III) error.
H - When 1, head is currently loaded (type I) or record type/
write fault (type II or III).
P - When 1, indicates that diskette is write-protected.
R - When 1, drive is not ready.
Drive Control Write (0x8018) for type I commands:
+---+---+---+---+---+---+---+---+
| 0 | S2| S1| S0| H | V | R1| R0|
+---+---+---+---+---+---+---+---+
R0/R1 - Selects the step rate.
V - When 1, verify on destination track.
H - When 1, loads head to drive surface.
S0/S1/S2 = 000 - home.
001 - seek track in data register.
010 - step without updating track register.
011 - step and update track register.
100 - step in without updating track register.
101 - step in and update track register.
110 - step out without updating track register.
111 - step out and update track register.
Drive Control Write (0x8018) for type II commands:
+---+---+---+---+---+---+---+---+
| 1 | 0 | T | M | S | E | B | A |
+---+---+---+---+---+---+---+---+
A - Zero for read, 1 on write deleted data mark else data mark.
B - When 1, shifts sector length field definitions one place.
E - When, delay operation 15 ms, 0 no delay.
S - When 1, select side 1, 0 select side 0.
M - When 1, multiple records, 0 for single record.
T - When 1, write command, 0 for read.
Drive Control Write (0x8018) for type III commands:
+---+---+---+---+---+---+---+---+
| 1 | 1 | T0| T1| 0 | E | 0 | 0 |
+---+---+---+---+---+---+---+---+
E - When, delay operation 15 ms, 0 no delay.
T0/T1 - 00 - read address command.
10 - read track command.
11 - write track command.
Tracks are numbered from 0 up to one minus the last track in the 1797!
Track Register Read (0x8019):
+---+---+---+---+---+---+---+---+
| Track Number |
+---+---+---+---+---+---+---+---+
Reads the current 8-bit value from the track position.
Track Register Write (0x8019):
+---+---+---+---+---+---+---+---+
| Track Number |
+---+---+---+---+---+---+---+---+
Writes the 8-bit value to the track register.
Sectors are numbers from 1 up to the last sector in the 1797!
Sector Register Read (0x801A):
+---+---+---+---+---+---+---+---+
| Sector Number |
+---+---+---+---+---+---+---+---+
Reads the current 8-bit value from the sector position.
Sector Register Write (0x801A):
+---+---+---+---+---+---+---+---+
| Sector Number |
+---+---+---+---+---+---+---+---+
Writes the 8-bit value to the sector register.
Data Register Read (0x801B):
+---+---+---+---+---+---+---+---+
| Data |
+---+---+---+---+---+---+---+---+
Reads the current 8-bit value from the data register.
Data Register Write (0x801B):
+---+---+---+---+---+---+---+---+
| Data |
+---+---+---+---+---+---+---+---+
Writes the 8-bit value to the data register.
A FLEX disk is defined as follows:
Track Sector Use
0 1 Boot sector
0 2 Boot sector (cont)
0 3 Unused
0 4 System Identity Record (explained below)
0 5 Unused
0 6-last Directory - 10 entries/sector (explained below)
1 1 First available data sector
last-1 last Last available data sector
System Identity Record
Byte Use
0x10 Volume ID (8 bytes)
0x18 ???
0x19 ???
0x1A ???
0x1B Volume number (2 bytes)
0x1D First free sector (2 bytes)
0x1F Last track minus one (byte)
0x20 Last sector (byte)
0x21 Total sectors on disk (2 bytes)
0x23 Month (byte
0x24 Day (byte)
0x25 Year (byte)
0x26 Last track minus one (byte)
0x27 Last sector (byte)
*/
#include <stdio.h>
#include "swtp_defs.h"
#define UNIT_V_ENABLE (UNIT_V_UF + 0) /* Write Enable */
#define UNIT_ENABLE (1 << UNIT_V_ENABLE)
/* emulate a SS FLEX disk with 72 sectors and 80 tracks */
#define NUM_DISK 4 /* standard 1797 maximum */
#define SECT_SIZE 256 /* standard FLEX sector */
#define NUM_SECT 72 /* sectors/track */
#define TRAK_SIZE (SECT_SIZE * NUM_SECT)
#define HEADS 1 /* handle as SS with twice the sectors */
#define NUM_CYL 80 /* maximum tracks */
#define DSK_SIZE (NUM_SECT * HEADS * NUM_CYL * SECT_SIZE)
/* 1797 status bits */
#define BUSY 0x01
#define DRQ 0x02
#define WRPROT 0x40
#define NOTRDY 0x80
/* debug prints */
#define DEBUG 0
/* prototypes */
t_stat dsk_svc (UNIT *uptr);
t_stat dsk_reset (DEVICE *dptr);
int32 fdcdrv(int32 io, int32 data);
int32 fdccmd(int32 io, int32 data);
int32 fdctrk(int32 io, int32 data);
int32 fdcsec(int32 io, int32 data);
int32 fdcdata(int32 io, int32 data);
/* Global data on status */
int32 cur_dsk = NUM_DISK; /* Currently selected drive */
int32 cur_trk[NUM_DISK] = {0, 0, 0, 0};
int32 cur_sec[NUM_DISK] = {0, 0, 0, 0};
int32 cur_byt[NUM_DISK] = {0, 0, 0, 0};
int32 cur_flg[NUM_DISK] = {NOTRDY, NOTRDY, NOTRDY, NOTRDY};
/* Variables */
uint8 dskbuf[SECT_SIZE]; /* Data Buffer */
UNIT *dptr = NULL; /* fileref to write dirty buffer to */
int32 fdcbyte;
int32 intrq = 0; /* interrupt request flag */
/* DC-4 Simh Device Data Structures */
UNIT dsk_unit[] = {
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },
{ UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) } };
REG dsk_reg[] = {
{ HRDATA (DISK, cur_dsk, 4) },
{ NULL } };
MTAB dsk_mod[] = {
{ UNIT_ENABLE, UNIT_ENABLE, "RW", "RW", NULL },
{ UNIT_ENABLE, 0, "RO", "RO", NULL },
{ 0 } };
DEVICE dsk_dev = {
"DSK", dsk_unit, dsk_reg, dsk_mod,
NUM_DISK, 16, 16, 1, 16, 8,
NULL, NULL, &dsk_reset,
NULL, NULL, NULL };
/* service routines to handle simlulator functions */
/* service routine - actually gets char & places in buffer */
t_stat dsk_svc (UNIT *uptr)
{
return SCPE_OK;
}
/* Reset routine */
t_stat dsk_reset (DEVICE *dptr)
{
cur_dsk = 0;
return SCPE_OK;
}
/* I/O instruction handlers, called from the CPU module when an
memory read or write to the proper addresses is issued.
Each function is passed an 'io' flag, where 0 means a read from
the port, and 1 means a write to the port. On input, the actual
input is passed as the return value, on output, 'data' is written
to the device.
*/
/* DC-4 drive select register routine - this register is not part of the 1797
*/
int32 fdcdrv(int32 io, int32 data)
{
/* **** probably need to grab the parameters from the SIR and set the limits */
if (io) { /* write to DC-4 drive register */
cur_dsk = data & 0x03; /* only 2 drive select bits */
#if DEBUG > 0
printf("Drive set to %d\n\r", cur_dsk);
#endif
if ((dsk_unit[cur_dsk].flags & UNIT_ENABLE) == 0)
cur_flg[cur_dsk] |= WRPROT; /* set WPROT */
return 0;
} else { /* read from DC-4 drive register */
#if DEBUG > 0
printf("Drive read as %02X\n\r", intrq);
#endif
return intrq;
}
}
/* WD 1797 FDC command register routine */
int32 fdccmd(int32 io, int32 data)
{
static int32 val = 0, val1 = NOTRDY, i;
static long pos;
UNIT *uptr;
if ((dsk_unit[cur_dsk].flags & UNIT_ATT) == 0) { /* not attached */
cur_flg[cur_dsk] |= NOTRDY; /* set not ready flag */
printf("Drive %d is not attached\n\r", cur_dsk);
return 0;
} else {
cur_flg[cur_dsk] &= ~NOTRDY; /* clear not ready flag */
}
uptr = dsk_dev.units + cur_dsk; /* get virtual drive address */
if (io) { /* write command to fdc */
switch(data) {
case 0x8C: /* read command */
case 0x9C:
#if DEBUG > 0
printf("Read of disk %d, track %d, sector %d\n\r",
cur_dsk, cur_trk[cur_dsk], cur_sec[cur_dsk]);
#endif
pos = TRAK_SIZE * cur_trk[cur_dsk]; /* calculate file offset */
pos += SECT_SIZE * (cur_sec[cur_dsk] - 1);
#if DEBUG > 0
printf("Read pos = %ld ($%04X)\n\r", pos, pos);
#endif
sim_fseek(uptr -> fileref, pos, 0); /* seek to offset */
sim_fread(dskbuf, 256, 1, uptr -> fileref); /* read in buffer */
cur_flg[cur_dsk] |= BUSY | DRQ; /* set DRQ & BUSY */
i = cur_byt[cur_dsk] = 0; /* clear counter */
break;
case 0xAC: /* write command */
#if DEBUG > 0
printf("Write of disk %d, track %d, sector %d\n\r",
cur_dsk, cur_trk[cur_dsk], cur_sec[cur_dsk]);
#endif
if (cur_flg[cur_dsk] & WRPROT) {
printf("Drive %d is write-protected\n\r", cur_dsk);
} else {
pos = TRAK_SIZE * cur_trk[cur_dsk]; /* calculate file offset */
pos += SECT_SIZE * (cur_sec[cur_dsk] - 1);
#if DEBUG > 1
printf("Write pos = %ld ($%04X)\n\r", pos, pos);
#endif
sim_fseek(uptr -> fileref, pos, 0); /* seek to offset */
dptr = uptr; /* save pointer for actual write */
cur_flg[cur_dsk] |= BUSY | DRQ;/* set DRQ & BUSY */
i = cur_byt[cur_dsk] = 0; /* clear counter */
}
break;
case 0x18: /* seek command */
case 0x1B:
cur_trk[cur_dsk] = fdcbyte; /* set track */
cur_flg[cur_dsk] &= ~(BUSY | DRQ); /* clear flags */
#if DEBUG > 0
printf("Seek of disk %d, track %d\n\r", cur_dsk, fdcbyte);
#endif
break;
case 0x0B: /* restore command */
cur_trk[cur_dsk] = 0; /* home the drive */
cur_flg[cur_dsk] &= ~(BUSY | DRQ); /* clear flags */
#if DEBUG > 0
printf("Drive %d homed\n\r", cur_dsk);
#endif
break;
default:
printf("Unknown FDC command %02H\n\r", data);
}
} else { /* read status from fdc */
val = cur_flg[cur_dsk]; /* set return value */
if (val1 == 0 && val == 0x03) /* delay BUSY going high */
val = 0x02; /* set DRQ first */
if (val != val1) { /* now allow BUSY after on read */
val1 = val;
#if DEBUG > 0
printf("Drive %d status=%02X\n\r", cur_dsk, cur_flg[cur_dsk]);
#endif
}
}
return val;
}
/* WD 1797 FDC track register routine */
int32 fdctrk(int32 io, int32 data)
{
if (io) {
cur_trk[cur_dsk] = data & 0xFF;
#if DEBUG > 1
printf("Drive %d track set to %d\n\r", cur_dsk, data);
#endif
} else
;
#if DEBUG > 1
printf("Drive %d track read as %d\n\r", cur_dsk, cur_trk[cur_dsk]);
#endif
return cur_trk[cur_dsk];
}
/* WD 1797 FDC sector register routine */
int32 fdcsec(int32 io, int32 data)
{
if (io) {
cur_sec[cur_dsk] = data & 0xFF;
if (cur_sec[cur_dsk] == 0) /* fix for swtp boot! */
cur_sec[cur_dsk] = 1;
#if DEBUG > 1
printf("Drive %d sector set to %d\n\r", cur_dsk, data);
#endif
} else
;
#if DEBUG > 1
printf("Drive %d sector read as %d\n\r", cur_dsk, cur_sec[cur_dsk]);
#endif
return cur_sec[cur_dsk];
}
/* WD 1797 FDC data register routine */
int32 fdcdata(int32 io, int32 data)
{
int32 i;
if (io) { /* write byte to fdc */
fdcbyte = data; /* save for seek */
if ((i = cur_byt[cur_dsk]) < SECT_SIZE) { /* copy bytes to buffer */
#if DEBUG > 3
printf("Writing byte %d of %02X\n\r", cur_byt[cur_dsk], data);
#endif
cur_byt[cur_dsk]++; /* step counter */
dskbuf[i] = data; /* byte into buffer */
if (cur_byt[cur_dsk] == SECT_SIZE) {
cur_flg[cur_dsk] &= ~(BUSY | DRQ);
if (dptr) { /* if initiated by FDC write command */
sim_fwrite(dskbuf, 256, 1, dptr -> fileref); /* write it */
dptr = NULL;
}
#if DEBUG > 0
printf("Sector write complete\n\r");
#endif
}
}
return 0;
} else { /* read byte from fdc */
if ((i = cur_byt[cur_dsk]) < SECT_SIZE) { /* copy bytes from buffer */
#if DEBUG > 1
printf("Reading byte %d\n\r", cur_byt[cur_dsk]);
#endif
cur_byt[cur_dsk]++; /* step counter */
if (cur_byt[cur_dsk] == SECT_SIZE) { /* done? */
cur_flg[cur_dsk] &= ~(BUSY | DRQ); /* clear flags */
#if DEBUG > 0
printf("Sector read complete\n\r");
#endif
}
return (dskbuf[i] & 0xFF);
} else
return 0;
}
}