370 lines
13 KiB
C
370 lines
13 KiB
C
/* altair_dsk.c: MITS Altair 88-DISK Simulator
|
|
|
|
Copyright (c) 1997-2010, Charles E. Owen
|
|
|
|
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 Charles E. Owen shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Charles E. Owen.
|
|
|
|
The 88_DISK is a 8-inch floppy controller which can control up
|
|
to 16 daisy-chained Pertec FD-400 hard-sectored floppy drives.
|
|
Each diskette has physically 77 tracks of 32 137-byte sectors
|
|
each.
|
|
|
|
The controller is interfaced to the CPU by use of 3 I/O addreses,
|
|
standardly, these are device numbers 10, 11, and 12 (octal).
|
|
|
|
Address Mode Function
|
|
------- ---- --------
|
|
|
|
10 Out Selects and enables Controller and Drive
|
|
10 In Indicates status of Drive and Controller
|
|
11 Out Controls Disk Function
|
|
11 In Indicates current sector position of disk
|
|
12 Out Write data
|
|
12 In Read data
|
|
|
|
Drive Select Out (Device 10 OUT):
|
|
|
|
+---+---+---+---+---+---+---+---+
|
|
| C | X | X | X | Device |
|
|
+---+---+---+---+---+---+---+---+
|
|
|
|
C = If this bit is 1, the disk controller selected by 'device' is
|
|
cleared. If the bit is zero, 'device' is selected as the
|
|
device being controlled by subsequent I/O operations.
|
|
X = not used
|
|
Device = value zero thru 15, selects drive to be controlled.
|
|
|
|
Drive Status In (Device 10 IN):
|
|
|
|
+---+---+---+---+---+---+---+---+
|
|
| R | Z | I | X | X | H | M | W |
|
|
+---+---+---+---+---+---+---+---+
|
|
|
|
W - When 0, write circuit ready to write another byte.
|
|
M - When 0, head movement is allowed
|
|
H - When 0, indicates head is loaded for read/write
|
|
X - not used (will be 0)
|
|
I - When 0, indicates interrupts enabled (not used this simulator)
|
|
Z - When 0, indicates head is on track 0
|
|
R - When 0, indicates that read circuit has new byte to read
|
|
|
|
Drive Control (Device 11 OUT):
|
|
|
|
+---+---+---+---+---+---+---+---+
|
|
| W | C | D | E | U | H | O | I |
|
|
+---+---+---+---+---+---+---+---+
|
|
|
|
I - When 1, steps head IN one track
|
|
O - When 1, steps head OUT out track
|
|
H - When 1, loads head to drive surface
|
|
U - When 1, unloads head
|
|
E - Enables interrupts (ignored this simulator)
|
|
D - Disables interrupts (ignored this simulator)
|
|
C - When 1 lowers head current (ignored this simulator)
|
|
W - When 1, starts Write Enable sequence: W bit on device 10
|
|
(see above) will go 1 and data will be read from port 12
|
|
until 137 bytes have been read by the controller from
|
|
that port. The W bit will go off then, and the sector data
|
|
will be written to disk. Before you do this, you must have
|
|
stepped the track to the desired number, and waited until
|
|
the right sector number is presented on device 11 IN, then
|
|
set this bit.
|
|
|
|
Sector Position (Device 11 IN):
|
|
|
|
As the sectors pass by the read head, they are counted and the
|
|
number of the current one is available in this register.
|
|
|
|
+---+---+---+---+---+---+---+---+
|
|
| X | X | Sector Number | T |
|
|
+---+---+---+---+---+---+---+---+
|
|
|
|
X = Not used
|
|
Sector number = binary of the sector number currently under the
|
|
head, 0-31.
|
|
T = Sector True, is a 1 when the sector is positioned to read or
|
|
write.
|
|
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "altair_defs.h"
|
|
|
|
#define UNIT_V_ENABLE (UNIT_V_UF + 0) /* Write Enable */
|
|
#define UNIT_ENABLE (1 << UNIT_V_ENABLE)
|
|
|
|
#define DSK_SECTSIZE 137
|
|
#define DSK_SECT 32
|
|
#define DSK_TRACSIZE 4384
|
|
#define DSK_SURF 1
|
|
#define DSK_CYL 77
|
|
#define DSK_SIZE (DSK_SECT * DSK_SURF * DSK_CYL * DSK_SECTSIZE)
|
|
|
|
t_stat dsk_svc (UNIT *uptr);
|
|
t_stat dsk_reset (DEVICE *dptr);
|
|
void writebuf();
|
|
|
|
extern int32 PCX;
|
|
|
|
/* Global data on status */
|
|
|
|
int32 cur_disk = 8; /* Currently selected drive */
|
|
int32 cur_track[9] = {0, 0, 0, 0, 0, 0, 0, 0, 377};
|
|
int32 cur_sect[9] = {0, 0, 0, 0, 0, 0, 0, 0, 377};
|
|
int32 cur_byte[9] = {0, 0, 0, 0, 0, 0, 0, 0, 377};
|
|
int32 cur_flags[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
char dskbuf[138]; /* Data Buffer */
|
|
int32 dirty = 0; /* 1 when buffer has unwritten data in it */
|
|
UNIT *dptr; /* fileref to write dirty buffer to */
|
|
|
|
int32 dsk_rwait = 100; /* rotate latency */
|
|
|
|
|
|
/* 88DSK Standard I/O 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) },
|
|
{ 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[] = {
|
|
{ ORDATA (DISK, cur_disk, 4) },
|
|
{ NULL }
|
|
};
|
|
|
|
DEVICE dsk_dev = {
|
|
"DSK", dsk_unit, dsk_reg, NULL,
|
|
8, 10, 31, 1, 8, 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_disk = 0;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* I/O instruction handlers, called from the CPU module when an
|
|
IN or OUT instruction 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.
|
|
*/
|
|
|
|
/* Disk Controller Status/Select */
|
|
|
|
/* IMPORTANT: The status flags read by port 8 IN instruction are
|
|
INVERTED, that is, 0 is true and 1 is false. To handle this, the
|
|
simulator keeps it's own status flags as 0=false, 1=true; and
|
|
returns the COMPLEMENT of the status flags when read. This makes
|
|
setting/testing of the flag bits more logical, yet meets the
|
|
simulation requirement that they are reversed in hardware.
|
|
*/
|
|
|
|
int32 dsk10(int32 io, int32 data)
|
|
{
|
|
|
|
if (io == 0) { /* IN: return flags */
|
|
return ((~cur_flags[cur_disk]) & 0xFF); /* Return the COMPLEMENT! */
|
|
}
|
|
|
|
/* OUT: Controller set/reset/enable/disable */
|
|
|
|
if (dirty == 1)
|
|
writebuf();
|
|
|
|
/*printf("\n[%o] OUT 10: %x", PCX, data);*/
|
|
cur_disk = data & 0x0F;
|
|
if (data & 0x80) {
|
|
cur_flags[cur_disk] = 0; /* Disable drive */
|
|
cur_sect[cur_disk] = 0377;
|
|
cur_byte[cur_disk] = 0377;
|
|
return (0);
|
|
}
|
|
cur_flags[cur_disk] = 0x1A; /* Enable: head move true */
|
|
cur_sect[cur_disk] = 0377; /* reset internal counters */
|
|
cur_byte[cur_disk] = 0377;
|
|
if (cur_track[cur_disk] == 0)
|
|
cur_flags[cur_disk] |= 0x40; /* track 0 if there */
|
|
return (0);
|
|
}
|
|
|
|
/* Disk Drive Status/Functions */
|
|
|
|
int32 dsk11(int32 io, int32 data)
|
|
{
|
|
int32 stat;
|
|
|
|
if (io == 0) { /* Read sector position */
|
|
/*printf("\n[%o] IN 11", PCX);*/
|
|
if (dirty == 1)
|
|
writebuf();
|
|
if (cur_flags[cur_disk] & 0x04) { /* head loaded? */
|
|
cur_sect[cur_disk]++;
|
|
if (cur_sect[cur_disk] > 31)
|
|
cur_sect[cur_disk] = 0;
|
|
cur_byte[cur_disk] = 0377;
|
|
stat = cur_sect[cur_disk] << 1;
|
|
stat &= 0x3E; /* return 'sector true' bit = 0 (true) */
|
|
stat |= 0xC0; /* set on 'unused' bits */
|
|
return (stat);
|
|
} else {
|
|
return (0); /* head not loaded - return 0 */
|
|
}
|
|
}
|
|
|
|
/* Drive functions */
|
|
|
|
if (cur_disk > 7)
|
|
return (0); /* no drive selected - can do nothin */
|
|
|
|
/*printf("\n[%o] OUT 11: %x", PCX, data);*/
|
|
if (data & 0x01) { /* Step head in */
|
|
cur_track[cur_disk]++;
|
|
if (cur_track[cur_disk] > 76 )
|
|
cur_track[cur_disk] = 76;
|
|
if (dirty == 1)
|
|
writebuf();
|
|
cur_sect[cur_disk] = 0377;
|
|
cur_byte[cur_disk] = 0377;
|
|
}
|
|
|
|
if (data & 0x02) { /* Step head out */
|
|
cur_track[cur_disk]--;
|
|
if (cur_track[cur_disk] < 0) {
|
|
cur_track[cur_disk] = 0;
|
|
cur_flags[cur_disk] |= 0x40; /* track 0 if there */
|
|
}
|
|
if (dirty == 1)
|
|
writebuf();
|
|
cur_sect[cur_disk] = 0377;
|
|
cur_byte[cur_disk] = 0377;
|
|
}
|
|
|
|
if (dirty == 1)
|
|
writebuf();
|
|
|
|
if (data & 0x04) { /* Head load */
|
|
cur_flags[cur_disk] |= 0x04; /* turn on head loaded bit */
|
|
cur_flags[cur_disk] |= 0x80; /* turn on 'read data available */
|
|
}
|
|
|
|
if (data & 0x08) { /* Head Unload */
|
|
cur_flags[cur_disk] &= 0xFB; /* off on 'head loaded' */
|
|
cur_flags[cur_disk] &= 0x7F; /* off on 'read data avail */
|
|
cur_sect[cur_disk] = 0377;
|
|
cur_byte[cur_disk] = 0377;
|
|
}
|
|
|
|
/* Interrupts & head current are ignored */
|
|
|
|
if (data & 0x80) { /* write sequence start */
|
|
cur_byte[cur_disk] = 0;
|
|
cur_flags[cur_disk] |= 0x01; /* enter new write data on */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Disk Data In/Out*/
|
|
|
|
int32 dsk12(int32 io, int32 data)
|
|
{
|
|
static int32 rtn, i;
|
|
static long pos;
|
|
UNIT *uptr;
|
|
|
|
uptr = dsk_dev.units + cur_disk;
|
|
if (io == 0) {
|
|
if ((i = cur_byte[cur_disk]) < 138) { /* just get from buffer */
|
|
cur_byte[cur_disk]++;
|
|
return (dskbuf[i] & 0xFF);
|
|
}
|
|
/* physically read the sector */
|
|
/*printf("\n[%o] IN 12 (READ) T%d S%d", PCX, cur_track[cur_disk],
|
|
cur_sect[cur_disk]);*/
|
|
pos = DSK_TRACSIZE * cur_track[cur_disk];
|
|
pos += DSK_SECTSIZE * cur_sect[cur_disk];
|
|
if ((uptr == NULL) || (uptr->fileref == NULL))
|
|
return 0;
|
|
rtn = fseek(uptr -> fileref, pos, 0);
|
|
rtn = fread(dskbuf, 137, 1, uptr -> fileref);
|
|
cur_byte[cur_disk] = 1;
|
|
return (dskbuf[0] & 0xFF);
|
|
} else {
|
|
if (cur_byte[cur_disk] > 136) {
|
|
i = cur_byte[cur_disk];
|
|
dskbuf[i] = data & 0xFF;
|
|
writebuf();
|
|
return (0);
|
|
}
|
|
i = cur_byte[cur_disk];
|
|
dirty = 1;
|
|
dptr = uptr;
|
|
dskbuf[i] = data & 0xFF;
|
|
cur_byte[cur_disk]++;
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
void writebuf()
|
|
{
|
|
long pos;
|
|
int32 rtn, i;
|
|
|
|
i = cur_byte[cur_disk]; /* null-fill rest of sector if any */
|
|
while (i < 138) {
|
|
dskbuf[i] = 0;
|
|
i++;
|
|
}
|
|
/*printf("\n[%o] OUT 12 (WRITE) T%d S%d", PCX, cur_track[cur_disk],
|
|
cur_sect[cur_disk]); i = getch(); */
|
|
pos = DSK_TRACSIZE * cur_track[cur_disk]; /* calc file pos */
|
|
pos += DSK_SECTSIZE * cur_sect[cur_disk];
|
|
rtn = fseek(dptr -> fileref, pos, 0);
|
|
rtn = fwrite(dskbuf, 137, 1, dptr -> fileref);
|
|
cur_flags[cur_disk] &= 0xFE; /* ENWD off */
|
|
cur_byte[cur_disk] = 0377;
|
|
dirty = 0;
|
|
return;
|
|
}
|