/* altairZ80_hdsk.c: simulated hard disk device to increase capacity | |
Copyright (c) 2002, Peter Schorn | |
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 Peter Schorn shall not | |
be used in advertising or otherwise to promote the sale, use or other dealings | |
in this Software without prior written authorization from Peter Schorn. | |
*/ | |
#include <stdio.h> | |
#include "altairz80_defs.h" | |
#define UNIT_V_HDSKWLK (UNIT_V_UF + 0) /* write locked */ | |
#define UNIT_HDSKWLK (1 << UNIT_V_HDSKWLK) | |
#define UNIT_V_HDSK_VERBOSE (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages */ | |
#define UNIT_HDSK_VERBOSE (1 << UNIT_V_HDSK_VERBOSE) | |
#define HDSK_SECTOR_SIZE 128 /* size of sector */ | |
#define HDSK_SECTORS_PER_TRACK 32 /* sectors per track */ | |
#define HDS_MAX_TRACKS 2048 /* number of tracks */ | |
#define HDSK_TRACK_SIZE (HDSK_SECTOR_SIZE * HDSK_SECTORS_PER_TRACK) | |
#define HDSK_CAPACITY (HDSK_TRACK_SIZE * HDS_MAX_TRACKS) | |
#define HDSK_NUMBER 8 /* number of hard disks */ | |
#define CPM_OK 0 /* indicates to CP/M everything ok */ | |
#define CPM_ERROR 1 /* indicates to CP/M an error condition */ | |
#define CPM_EMPTY 0xe5 /* default value for non-existing bytes */ | |
#define hdsk_none 0 | |
#define hdsk_reset 1 | |
#define hdsk_read 2 | |
#define hdsk_write 3 | |
#define hdsk_boot_address 0x5c00 | |
extern char messageBuffer[]; | |
extern int32 PCX; | |
extern UNIT cpu_unit; | |
extern uint8 M[MAXMEMSIZE][MAXBANKS]; | |
extern int32 saved_PC; | |
extern int32 install_bootrom(void); | |
extern void printMessage(void); | |
extern void PutBYTEWrapper(register uint32 Addr, register uint32 Value); | |
extern void protect(int32 l, int32 h); | |
extern uint8 GetBYTEWrapper(register uint32 Addr); | |
extern int32 bootrom[bootrom_size]; | |
t_stat hdsk_svc(UNIT *uptr); | |
t_stat hdsk_boot(int32 unitno, DEVICE *dptr); | |
int32 hdsk_hasVerbose(void); | |
int32 hdsk_io(int32 port, int32 io, int32 data); | |
int32 hdsk_in(void); | |
int32 hdsk_out(int32 data); | |
int32 checkParameters(void); | |
int32 doSeek(void); | |
int32 doRead(void); | |
int32 doWrite(void); | |
uint8 hdskbuf[HDSK_SECTOR_SIZE]; /* Data Buffer */ | |
int32 hdskLastCommand = hdsk_none; | |
int32 hdskCommandPosition = 0; | |
int32 selectedDisk; | |
int32 selectedSector; | |
int32 selectedTrack; | |
int32 selectedDMA; | |
int32 hdskTrace; | |
UNIT hdsk_unit[] = { | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, | |
{ UDATA (&hdsk_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) } }; | |
REG hdsk_reg[] = { | |
{ DRDATA (HDCMD, hdskLastCommand, 32), REG_RO }, | |
{ DRDATA (HDPOS, hdskCommandPosition, 32), REG_RO }, | |
{ DRDATA (HDDSK, selectedDisk, 32), REG_RO }, | |
{ DRDATA (HDSEC, selectedSector, 32), REG_RO }, | |
{ DRDATA (HDTRK, selectedTrack, 32), REG_RO }, | |
{ DRDATA (HDDMA, selectedDMA, 32), REG_RO }, | |
{ DRDATA (HDTRACE, hdskTrace, 8), }, | |
{ NULL } }; | |
MTAB hdsk_mod[] = { | |
{ UNIT_HDSKWLK, 0, "write enabled", "WRITEENABLED", NULL }, | |
{ UNIT_HDSKWLK, UNIT_HDSKWLK, "write locked", "LOCKED", NULL }, | |
/* quiet, no warning messages */ | |
{ UNIT_HDSK_VERBOSE, 0, "QUIET", "QUIET", NULL }, | |
/* verbose, show warning messages */ | |
{ UNIT_HDSK_VERBOSE, UNIT_HDSK_VERBOSE, "VERBOSE", "VERBOSE", NULL }, | |
{ 0 } }; | |
DEVICE hdsk_dev = { | |
"HDSK", hdsk_unit, hdsk_reg, hdsk_mod, | |
8, 10, 31, 1, 8, 8, | |
NULL, NULL, NULL, | |
&hdsk_boot, NULL, NULL, NULL, 0 }; | |
t_stat hdsk_svc(UNIT *uptr) { | |
return SCPE_OK; | |
} | |
int32 hdskBoot[bootrom_size] = { | |
0xf3, 0x06, 0x80, 0x3e, 0x0e, 0xd3, 0xfe, 0x05, /* 5c00-5c07 */ | |
0xc2, 0x05, 0x5c, 0x3e, 0x16, 0xd3, 0xfe, 0x3e, /* 5c08-5c0f */ | |
0x12, 0xd3, 0xfe, 0xdb, 0xfe, 0xb7, 0xca, 0x20, /* 5c10-5c17 */ | |
0x5c, 0x3e, 0x0c, 0xd3, 0xfe, 0xaf, 0xd3, 0xfe, /* 5c18-5c1f */ | |
0x06, 0x20, 0x3e, 0x01, 0xd3, 0xfd, 0x05, 0xc2, /* 5c20-5c27 */ | |
0x24, 0x5c, 0x11, 0x08, 0x00, 0x21, 0x00, 0x00, /* 5c28-5c2f */ | |
0x0e, 0xb8, 0x3e, 0x02, 0xd3, 0xfd, 0x3a, 0x37, /* 5c30-5c37 */ | |
0xff, 0xd6, 0x08, 0xd3, 0xfd, 0x7b, 0xd3, 0xfd, /* 5c38-5c3f */ | |
0x7a, 0xd3, 0xfd, 0xaf, 0xd3, 0xfd, 0x7d, 0xd3, /* 5c40-5c47 */ | |
0xfd, 0x7c, 0xd3, 0xfd, 0xdb, 0xfd, 0xb7, 0xca, /* 5c48-5c4f */ | |
0x53, 0x5c, 0x76, 0x79, 0x0e, 0x80, 0x09, 0x4f, /* 5c50-5c57 */ | |
0x0d, 0xc2, 0x60, 0x5c, 0xfb, 0xc3, 0x00, 0x00, /* 5c58-5c5f */ | |
0x1c, 0x1c, 0x7b, 0xfe, 0x20, 0xca, 0x73, 0x5c, /* 5c60-5c67 */ | |
0xfe, 0x21, 0xc2, 0x32, 0x5c, 0x1e, 0x00, 0x14, /* 5c68-5c6f */ | |
0xc3, 0x32, 0x5c, 0x1e, 0x01, 0xc3, 0x32, 0x5c, /* 5c70-5c77 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c78-5c7f */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c80-5c87 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c88-5c8f */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c90-5c97 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c98-5c9f */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca0-5ca7 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca8-5caf */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb0-5cb7 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb8-5cbf */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc0-5cc7 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc8-5ccf */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd0-5cd7 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd8-5cdf */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce0-5ce7 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce8-5cef */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf0-5cf7 */ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf8-5cff */ | |
}; | |
t_stat hdsk_boot(int32 unitno, DEVICE *dptr) { | |
int32 i; | |
if (MEMSIZE < 24*KB) { | |
printf("Need at least 24KB RAM to boot from hard disk.\n"); | |
return SCPE_ARG; | |
} | |
if (cpu_unit.flags & (UNIT_ALTAIRROM | UNIT_BANKED)) { | |
if (install_bootrom()) { | |
printf("ALTAIR boot ROM installed.\n"); | |
} | |
/* check whether we are really modifying an LD A,<> instruction */ | |
if (bootrom[unitNoOffset1 - 1] == LDAInstruction) { | |
bootrom[unitNoOffset1] = (unitno+NUM_OF_DSK) & 0xff; /* LD A,<unitno> */ | |
} | |
else { /* Attempt to modify non LD A,<> instructions is refused. */ | |
printf("Incorrect boot ROM offset detected.\n"); | |
return SCPE_IERR; | |
} | |
} | |
for (i = 0; i < bootrom_size; i++) { | |
M[i + hdsk_boot_address][0] = hdskBoot[i] & 0xff; | |
} | |
saved_PC = hdsk_boot_address; | |
protect(hdsk_boot_address, hdsk_boot_address + bootrom_size - 1); | |
return SCPE_OK; | |
} | |
/* returns TRUE iff there exists a disk with VERBOSE */ | |
int32 hdsk_hasVerbose(void) { | |
int32 i; | |
for (i = 0; i < HDSK_NUMBER; i++) { | |
if (((hdsk_dev.units + i) -> flags) & UNIT_HDSK_VERBOSE) { | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
/* The hard disk port is 0xfd. It understands the following commands. | |
1. reset | |
ld b,32 | |
ld a,hdsk_reset | |
l out (0fdh),a | |
dec b | |
jp nz,l | |
2. read / write | |
; parameter block | |
cmd: db hdsk_read or hdsk_write | |
hd: db 0 ; 0 .. 7, defines hard disk to be used | |
sector: db 0 ; 0 .. 31, defines sector | |
track: dw 0 ; 0 .. 2047, defines track | |
dma: dw 0 ; defines where result is placed in memory | |
; routine to execute | |
ld b,7 ; size of parameter block | |
ld hl,cmd ; start address of parameter block | |
l ld a,(hl) ; get byte of parameter block | |
out (0fdh),a ; send it to port | |
inc hl ; point to next byte | |
dec b ; decrement counter | |
jp nz,l ; again, if not done | |
in a,(0fdh) ; get result code | |
*/ | |
/* check the parameters and return TRUE iff parameters are correct or have been repaired */ | |
int32 checkParameters(void) { | |
int32 currentFlag; | |
if ((selectedDisk < 0) || (selectedDisk >= HDSK_NUMBER)) { | |
if (hdsk_hasVerbose()) { | |
message2("HDSK%d does not exist, will use HDSK0 instead.\n", selectedDisk); | |
} | |
selectedDisk = 0; | |
} | |
currentFlag = (hdsk_dev.units + selectedDisk) -> flags; | |
if ((currentFlag & UNIT_ATT) == 0) { | |
if (currentFlag & UNIT_HDSK_VERBOSE) { | |
message2("HDSK%d is not attached.\n", selectedDisk); | |
} | |
return FALSE; /* cannot read or write */ | |
} | |
if ((selectedSector < 0) || (selectedSector >= HDSK_SECTORS_PER_TRACK)) { | |
if (currentFlag & UNIT_HDSK_VERBOSE) { | |
message4("HDSK%d: 0 <= Sector=%02d < %d violated, will use 0 instead.\n", | |
selectedDisk, selectedSector, HDSK_SECTORS_PER_TRACK); | |
} | |
selectedSector = 0; | |
} | |
if ((selectedTrack < 0) || (selectedTrack >= HDS_MAX_TRACKS)) { | |
if (currentFlag & UNIT_HDSK_VERBOSE) { | |
message4("HDSK%d: 0 <= Track=%04d < %04d violated, will use 0 instead.\n", | |
selectedDisk, selectedTrack, HDS_MAX_TRACKS); | |
} | |
selectedTrack = 0; | |
} | |
selectedDMA &= ADDRMASK; | |
if (hdskTrace) { | |
message6("%s HDSK%d Sector=%02d Track=%04d DMA=%04x\n", | |
(hdskLastCommand == hdsk_read) ? "Read" : "Write", | |
selectedDisk, selectedSector, selectedTrack, selectedDMA); | |
} | |
return TRUE; | |
} | |
int32 doSeek(void) { | |
UNIT *uptr = hdsk_dev.units + selectedDisk; | |
if (fseek(uptr -> fileref, | |
HDSK_TRACK_SIZE * selectedTrack + HDSK_SECTOR_SIZE * selectedSector, SEEK_SET)) { | |
if ((uptr -> flags) & UNIT_HDSK_VERBOSE) { | |
message4("Could not access HDSK%d Sector=%02d Track=%04d.\n", | |
selectedDisk, selectedSector, selectedTrack); | |
} | |
return CPM_ERROR; | |
} | |
else { | |
return CPM_OK; | |
} | |
} | |
int32 doRead(void) { | |
int32 i; | |
UNIT *uptr = hdsk_dev.units + selectedDisk; | |
if (doSeek()) { | |
return CPM_ERROR; | |
} | |
if (fread(hdskbuf, HDSK_SECTOR_SIZE, 1, uptr -> fileref) != 1) { | |
for (i = 0; i < HDSK_SECTOR_SIZE; i++) { | |
hdskbuf[i] = CPM_EMPTY; | |
} | |
if ((uptr -> flags) & UNIT_HDSK_VERBOSE) { | |
message4("Could not read HDSK%d Sector=%02d Track=%04d.\n", | |
selectedDisk, selectedSector, selectedTrack); | |
} | |
return CPM_OK; /* allows the creation of empty hard disks */ | |
} | |
for (i = 0; i < HDSK_SECTOR_SIZE; i++) { | |
PutBYTEWrapper(selectedDMA + i, hdskbuf[i]); | |
} | |
return CPM_OK; | |
} | |
int32 doWrite(void) { | |
int32 i; | |
UNIT *uptr = hdsk_dev.units + selectedDisk; | |
if (((uptr -> flags) & UNIT_HDSKWLK) == 0) { /* write enabled */ | |
if (doSeek()) { | |
return CPM_ERROR; | |
} | |
for (i = 0; i < HDSK_SECTOR_SIZE; i++) { | |
hdskbuf[i] = GetBYTEWrapper(selectedDMA + i); | |
} | |
if (fwrite(hdskbuf, HDSK_SECTOR_SIZE, 1, uptr -> fileref) != 1) { | |
if ((uptr -> flags) & UNIT_HDSK_VERBOSE) { | |
message4("Could not write HDSK%d Sector=%02d Track=%04d.\n", | |
selectedDisk, selectedSector, selectedTrack); | |
} | |
return CPM_ERROR; | |
} | |
} | |
else { | |
if ((uptr -> flags) & UNIT_HDSK_VERBOSE) { | |
message4("Could not write to locked HDSK%d Sector=%02d Track=%04d.\n", | |
selectedDisk, selectedSector, selectedTrack); | |
} | |
return CPM_ERROR; | |
} | |
return CPM_OK; | |
} | |
int32 hdsk_in(void) { | |
int32 result; | |
if ((hdskCommandPosition == 6) && ((hdskLastCommand == hdsk_read) || (hdskLastCommand == hdsk_write))) { | |
result = checkParameters() ? ((hdskLastCommand == hdsk_read) ? doRead() : doWrite()) : CPM_ERROR; | |
hdskLastCommand = hdsk_none; | |
hdskCommandPosition = 0; | |
return result; | |
} | |
else if (hdsk_hasVerbose()) { | |
message3("Illegal IN command detected (cmd=%d, pos=%d).\n", hdskLastCommand, hdskCommandPosition); | |
} | |
return CPM_OK; | |
} | |
int32 hdsk_out(int32 data) { | |
switch(hdskLastCommand) { | |
case hdsk_read: | |
case hdsk_write: | |
switch(hdskCommandPosition) { | |
case 0: | |
selectedDisk = data; | |
hdskCommandPosition++; | |
break; | |
case 1: | |
selectedSector = data; | |
hdskCommandPosition++; | |
break; | |
case 2: | |
selectedTrack = data; | |
hdskCommandPosition++; | |
break; | |
case 3: | |
selectedTrack += (data << 8); | |
hdskCommandPosition++; | |
break; | |
case 4: | |
selectedDMA = data; | |
hdskCommandPosition++; | |
break; | |
case 5: | |
selectedDMA += (data << 8); | |
hdskCommandPosition++; | |
break; | |
default: | |
hdskLastCommand = hdsk_none; | |
hdskCommandPosition = 0; | |
} | |
break; | |
default: | |
hdskLastCommand = data; | |
hdskCommandPosition = 0; | |
} | |
return 0; /* ignored, since OUT */ | |
} | |
int32 hdsk_io(int32 port, int32 io, int32 data) { | |
return io == 0 ? hdsk_in() : hdsk_out(data); | |
} |