diff --git a/Altair8800/altair8800_sys.c b/Altair8800/altair8800_sys.c index 20ce33a8..3c07364d 100644 --- a/Altair8800/altair8800_sys.c +++ b/Altair8800/altair8800_sys.c @@ -57,7 +57,7 @@ static t_bool fprint_stopped(FILE *st, t_stat reason); sim_load binary loader */ -char sim_name[] = "Altair 8800 (BUS)"; +char sim_name[] = "Altair 8800 (GEN2)"; int32 sim_emax = SIM_EMAX; @@ -74,11 +74,15 @@ DEVICE *sim_devices[] = { &m2sio0_dev, &m2sio1_dev, &acr_dev, + &mhdsk_dev, &daz_dev, + &fdcp_dev, + &icom_dev, &pmmi_dev, &sio_dev, &sbc200_dev, &tarbell_dev, + &vdm1_dev, &vfii_dev, NULL }; diff --git a/Altair8800/altair8800_sys.h b/Altair8800/altair8800_sys.h index 0adf69dd..c3398ec2 100644 --- a/Altair8800/altair8800_sys.h +++ b/Altair8800/altair8800_sys.h @@ -48,11 +48,15 @@ extern DEVICE mdsk_dev; extern DEVICE m2sio0_dev; extern DEVICE m2sio1_dev; extern DEVICE acr_dev; +extern DEVICE mhdsk_dev; extern DEVICE daz_dev; +extern DEVICE fdcp_dev; +extern DEVICE icom_dev; extern DEVICE pmmi_dev; extern DEVICE sio_dev; extern DEVICE sbc200_dev; extern DEVICE tarbell_dev; +extern DEVICE vdm1_dev; extern DEVICE vfii_dev; extern char memoryAccessMessage[256]; diff --git a/Altair8800/cromemco_dazzler.c b/Altair8800/cromemco_dazzler.c index 9a395c44..3aff53bc 100644 --- a/Altair8800/cromemco_dazzler.c +++ b/Altair8800/cromemco_dazzler.c @@ -25,6 +25,7 @@ History: 18-Jan-2026 Initial version + 14-Feb-2026 B Ammerman 0x0E EOF timing fix ================================================================== @@ -41,11 +42,11 @@ #include "cromemco_dazzler.h" /* -** Public VID_DISPLAY for other devices that may want +** VID_DISPLAY for other devices that may want ** to access the video display directly, such as joystick ** events. */ -VID_DISPLAY *daz_vptr = NULL; +static VID_DISPLAY *daz_vptr = NULL; static t_bool daz_0e = 0x00; static t_bool daz_0f = 0x80; @@ -105,10 +106,10 @@ static t_stat daz_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, cons daz_reg DAZ register list */ -static RES daz_res = { DAZ_IO_BASE, DAZ_IO_SIZE, 0 ,0, NULL }; +static RES daz_res = { DAZ_IO_BASE, DAZ_IO_SIZE, 0 ,0 }; static UNIT daz_unit = { - UDATA (&daz_svc, 0, 0), 33000 /* 30 fps */ + UDATA (&daz_svc, 0, 0), 16666 /* 60 fps */ }; static REG daz_reg[] = { @@ -234,7 +235,7 @@ static t_stat daz_boot(int32 unitno, DEVICE *dptr) exdep_cmd(EX_D, "-m 11E JP 11EH"); } - *((int32 *) sim_PC->loc) = 0x0100; + cpu_set_pc_loc(0x0100); return SCPE_OK; } @@ -242,15 +243,39 @@ static t_stat daz_boot(int32 unitno, DEVICE *dptr) static int32 daz_io(const int32 port, const int32 io, const int32 data) { int32 p = port - daz_res.io_base; + uint32 mod50; if (io == 0) { /* IN */ switch (p) { case 0x00: /* 0E */ - daz_frame = 0x7f; - if ((sim_os_msec() % 30) > 25) { + /* + * Fix by BobA: DAZZLER is 60 frames per second. Old code + * was effectively about 33 frames per second. Ideally we + * we want our modulus to be 16.666 msec, but the precision of + * sim_os_msec doesn't allow that. So we do one frame at + * 16 milliseocnds and then two at 17 milliseconds. + * It takes exactly 50 milliseconds for three frames. + */ + mod50 = sim_os_msec() % 50; + + /* + * The first frame is the first 16 milliseconds, we clear the + * bit if the value is in the range [12..15]. The second frame is + * 17 milliseconds, we clear the bit if the value is in the range + * [29..32]. The third frame is 17 milliseocnds, we clear the + * bit if the value is in the range [46..49]. + */ + daz_frame |= DAZ_EOF; + + if ((mod50 >= 12 && mod50 <= 15) || (mod50 >= 29 && mod50 <= 32) || (mod50 >= 46 && mod50 <= 49)) { daz_frame &= ~DAZ_EOF; - } else { - daz_frame |= (sim_os_msec() & 1) ? 0x00 : DAZ_EVEN; + } + + /* The even/odd line bit is independent of the EOF bit */ + daz_frame &= ~DAZ_EVEN; + + if ((mod50 & 1) == 0) { + daz_frame |= DAZ_EVEN; } return daz_frame; @@ -332,7 +357,7 @@ static t_stat daz_open_video(void) } } - sim_activate_after_abs(&daz_unit, daz_unit.wait); + sim_activate_after_abs(&daz_unit, daz_unit.wait); /* start video refresh event */ return r; } @@ -610,6 +635,12 @@ static t_stat daz_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, cons fprint_show_help (st, dptr); fprint_reg_help (st, dptr); + fprintf(st, "\n"); + fprintf(st, "The %s device does not currently provide timing of the 0Eh\n", dptr->name); + fprintf(st, "register Even/Odd bit. This bit is simply toggled during I/O reads.\n"); + fprintf(st, "\n"); + fprintf(st, "BOOT %s will display color bars on the Dazzler's video display.\n", dptr->name); + return SCPE_OK; } diff --git a/Altair8800/cromemco_dazzler.h b/Altair8800/cromemco_dazzler.h index 3de4ff54..fda53e4b 100644 --- a/Altair8800/cromemco_dazzler.h +++ b/Altair8800/cromemco_dazzler.h @@ -50,7 +50,5 @@ #define DAZ_EOF 0x40 /* End of Frame */ #define DAZ_EVEN 0x80 /* Even Line */ -extern VID_DISPLAY *daz_vptr; - #endif diff --git a/Altair8800/farmtek_fdcplus.c b/Altair8800/farmtek_fdcplus.c new file mode 100644 index 00000000..ad4a97d1 --- /dev/null +++ b/Altair8800/farmtek_fdcplus.c @@ -0,0 +1,785 @@ +/* farmtek_fdcplus.c: FarmTek FDC+ Simulator + + Copyright (c) 2026 Patrick A. Linstruth + + 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 + PETER SCHORN 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 Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + Based on work by Charles E Owen (c) 1997 + Based on work by Peter Schorn (c) 2002-2023 + + History: + 29-Mar-2026 Initial version + + ================================================================== + + The Altair FDC+ is an enhanced version of the original MITS 8" + floppy disk controller for the Altair 8800. The FDC+ is a 100% + compatible drop-in replacement for the original two-board + Altair FDC. + + This device supports the following FDC+ drive types: + + 5 - 5.25" drive as an Altair 8" drive or as 1.5Mb drive + 7 - Serial Drive as Altair 8" Drive (coming soon) + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "altair8800_dsk.h" +#include "s100_bus.h" +#include "s100_cpu.h" +#include "farmtek_fdcplus.h" + +#define DEVICE_NAME "FarmTek FDC+ Floppy Disk Controller" +#define DEVICE_DEV "FDCP" + +/* Debug flags */ +#define IN_MSG (1 << 0) +#define OUT_MSG (1 << 1) +#define READ_MSG (1 << 2) +#define WRITE_MSG (1 << 3) +#define SECTOR_STUCK_MSG (1 << 4) +#define TRACK_STUCK_MSG (1 << 5) +#define VERBOSE_MSG (1 << 6) + +static int32 poc = TRUE; + +static int32 fdcp_08h(const int32 port, const int32 io, const int32 data); +static int32 fdcp_09h(const int32 port, const int32 io, const int32 data); +static int32 fdcp_0ah(const int32 port, const int32 io, const int32 data); +static int32 fdcp_0bh(const int32 port, const int32 io, const int32 data); + +static t_stat fdcp_boot(int32 unitno, DEVICE *dptr); +static t_stat fdcp_reset(DEVICE *dptr); +static t_stat fdcp_attach(UNIT *uptr, const char *cptr); +static t_stat fdcp_detach(UNIT *uptr); +static t_stat fdcp_set_type(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat fdcpd_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* fdcp_description(DEVICE *dptr); + +/* global data on status */ + +/* currently selected drive (values are 0 .. NUM_OF_DSK) + current_disk < NUM_OF_DSK implies that the corresponding disk is attached to a file */ +static int32 current_disk = NUM_OF_DSK; + +static int32 current_track [NUM_OF_DSK]; +static int32 current_sector [NUM_OF_DSK]; +static int32 current_byte [NUM_OF_DSK]; +static int32 current_flag [NUM_OF_DSK]; +static int32 read_enable [NUM_OF_DSK]; /* For 1.5 MB Drive Type 5 */ +static int32 dummy_read [NUM_OF_DSK]; /* For 1.5 MB Drive Type 5 */ +static int32 sectors_per_track [NUM_OF_DSK]; +static int32 sector_size [NUM_OF_DSK]; +static int32 tracks [NUM_OF_DSK]; +static int32 in9_count = 0; +static int32 in9_message = FALSE; +static int32 dirty = FALSE; /* TRUE when buffer has unwritten data in it */ +static uint8 dskbuf[FDCP15_DISK_SECTSIZE]; /* data Buffer */ +static int32 sector_true = 0; /* sector true flag for sector register read */ + +static int32 drive_type = 0; +static int32 logical_track = 0; /* For 1.5 MB Drive Type 5 */ +static int32 logical_sector = 0; /* For 1.5 MB Drive Type 5 */ + +/**************************************************************************************** +* +* FDC+ +* +*****************************************************************************************/ + +#define FDCP_SELECT 0x08 /* Out */ +#define FDCP_DRVSTAT 0x08 /* In */ +#define FDCP_CONTROL 0x09 /* Out */ +#define FDCP_SECTOR 0x09 /* In */ +#define FDCP_WDATA 0x0a /* Out */ +#define FDCP_RDATA 0x0a /* In */ +#define FDCP_IOSTAT 0x0b /* Out */ +#define FDCP_TRACK 0x0b /* In */ + +#define FDCP_CLEAR 0x80 /* Clear disk select */ + +#define FDCP_ENWD 0x01 /* Enter new write data */ +#define FDCP_MVHD 0x02 /* Move head */ +#define FDCP_HS 0x04 /* Head status */ +#define FDCP_RDY 0x08 /* Ready */ +#define FDCP_WP 0x10 /* Write protected */ +#define FDCP_INTE 0x20 /* Interrupt enabled */ +#define FDCP_TRK0 0x40 /* Track 0 */ +#define FDCP_NRDA 0x80 /* New read data avail. */ + +#define FDCP_STEPIN 0x01 /* Step in */ +#define FDCP_STEPOUT 0x02 /* Step out */ +#define FDCP_HDLD 0x04 /* Head load */ +#define FDCP_HDUL 0x08 /* Head unload */ +#define FDCP_IE 0x10 /* Interrupt enable */ +#define FDCP_RE 0x10 /* Read enable (Type 5) */ +#define FDCP_ID 0x20 /* Interrupt disable */ +#define FDCP_HCS 0x40 /* Head current switch */ +#define FDCP_WE 0x80 /* Write enable */ + +/**************************************************************************************** +* +* bootSector - HD Floppy boot code in an original Altair 137 byte sector +* The disk format and data interchange between the 8080 and the FDC+ is completely +* different for the HD floppy drive option than for standard Altair drives. This +* means a different boot PROM is required to boot the loader found at the start of +* track zero on an HD floppy drive. As a way to get around this requirement, the +* main sector loop returns the standard Altair 137 byte sector below whenever +* the index pulse is reached (i.e., new sector) AND an HD floppy read is not in +* progress. This means an Altair boot PROM will receive this sector and jump +* to it as if it came from a standard Altair disk. +* +*****************************************************************************************/ +static uint8 bootSector[] = { + 0x80,0x80,0x00,0xf3,0x31,0x00,0x3f,0x3e, + 0x80,0xd3,0x08,0xaf,0xd3,0x08,0xd3,0x0b, + 0x3e,0x04,0xd3,0x09,0xdb,0x08,0xe6,0x02, + 0xc2,0x11,0x00,0xdb,0x08,0xe6,0x40,0xca, + 0x26,0x00,0x3e,0x02,0xd3,0x09,0xc3,0x11, + 0x00,0xdb,0x09,0x1f,0xda,0x26,0x00,0x21, + 0x00,0x40,0x01,0x00,0x14,0x3e,0x10,0xd3, + 0x09,0xdb,0x08,0xb7,0xfa,0x36,0x00,0xdb, + 0x0a,0xdb,0x0a,0x77,0x23,0xdb,0x0a,0x77, + 0x23,0x0b,0x78,0xb1,0xc2,0x3e,0x00,0xdb, + 0x0b,0x32,0xff,0x3f,0xe6,0x7f,0xc2,0x00, + 0x00,0x3e,0x80,0xd3,0x08,0xc3,0x00,0x40, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xa6,0x00,0x00,0x00, + 0x00,0x00 +}; + +/* 88DSK Standard I/O Data Structures */ + +static UNIT fdcp_unit[NUM_OF_DSK] = { + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) } +}; + +static REG fdcp_reg[] = { + { DRDATAD (TYPE, drive_type, 4, + "Selected drive type"), }, + { DRDATAD (DISK, current_disk, 4, + "Selected disk register"), }, + { BRDATAD (CURTRACK, current_track, 10, 32, NUM_OF_DSK, + "Selected track register array"), REG_CIRC + REG_RO }, + { BRDATAD (CURSECTOR, current_sector, 10, 32, NUM_OF_DSK, + "Selected sector register array"), REG_CIRC + REG_RO }, + { BRDATAD (CURBYTE, current_byte, 10, 32, NUM_OF_DSK, + "Current byte register array"), REG_CIRC + REG_RO }, + { BRDATAD (CURFLAG, current_flag, 10, 32, NUM_OF_DSK, + "Current flag register array"), REG_CIRC + REG_RO }, + { BRDATAD (TRACKS, tracks, 10, 32, NUM_OF_DSK, + "Number of tracks register array"), REG_CIRC }, + { BRDATAD (SECTPERTRACK, sectors_per_track, 10, 32, NUM_OF_DSK, + "Number of sectors per track register array"), REG_CIRC }, + { BRDATAD (SECTSIZE, sector_size, 10, 32, NUM_OF_DSK, + "Sector size register array"), REG_CIRC }, + { DRDATAD (IN9COUNT, in9_count, 4, + "Count of IN(9) register"), REG_RO }, + { DRDATAD (IN9MESSAGE, in9_message, 4, + "BOOL for IN(9) message register"), REG_RO }, + { DRDATAD (DIRTY, dirty, 4, + "BOOL for write needed register"), REG_RO }, + { BRDATAD (DISKBUFFER, dskbuf, 10, 8, DSK_SECTSIZE, + "Disk data buffer array"), REG_CIRC + REG_RO }, + { NULL } +}; + +static const char* fdcp_description(DEVICE *dptr) { + return DEVICE_NAME; +} + +static MTAB fdcp_mod[] = { + { MTAB_XTD | MTAB_VDV | MTAB_VALO, 0, NULL, "TYPE={5,7}", &fdcp_set_type, NULL, NULL, "FDC+ Drive Type" }, + { UNIT_DSK_WLK, 0, "WRTENB", "WRTENB", NULL, NULL, NULL, + "Enables " DEVICE_DEV "n for writing" }, + { UNIT_DSK_WLK, UNIT_DSK_WLK, "WRTLCK", "WRTLCK", NULL, NULL, NULL, + "Locks " DEVICE_DEV "n for writing" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB fdcp_dt[] = { + { "IN", IN_MSG, "IN operations" }, + { "OUT", OUT_MSG, "OUT operations" }, + { "READ", READ_MSG, "Read operations" }, + { "WRITE", WRITE_MSG, "Write operations" }, + { "SECTOR_STUCK", SECTOR_STUCK_MSG, "Sector stuck" }, + { "TRACK_STUCK", TRACK_STUCK_MSG, "Track stuck" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +DEVICE fdcp_dev = { + DEVICE_DEV, fdcp_unit, fdcp_reg, fdcp_mod, + NUM_OF_DSK, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &fdcp_reset, + &fdcp_boot, &fdcp_attach, &fdcp_detach, + NULL, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), 0, + fdcp_dt, NULL, NULL, &fdcpd_show_help, &dsk_attach_help, NULL, + &fdcp_description +}; + +static const char* selectInOut(const int32 io) { + return io == S100_IO_READ ? "IN" : "OUT"; +} + +/* service routines to handle simulator functions */ +/* reset routine */ + +static t_stat fdcp_reset(DEVICE *dptr) +{ + int32 i; + + if (dptr->flags & DEV_DIS) { + s100_bus_remio(0x08, 1, &fdcp_08h); + s100_bus_remio(0x09, 1, &fdcp_09h); + s100_bus_remio(0x0A, 1, &fdcp_0ah); + s100_bus_remio(0x0B, 1, &fdcp_0bh); + + poc = TRUE; + } + else { + if (poc) { /* Powerup? */ + s100_bus_addio(0x08, 1, &fdcp_08h, dptr->name); + s100_bus_addio(0x09, 1, &fdcp_09h, dptr->name); + s100_bus_addio(0x0A, 1, &fdcp_0ah, dptr->name); + s100_bus_addio(0x0B, 1, &fdcp_0bh, dptr->name); + + drive_type = 5; + + for (i = 0; i < NUM_OF_DSK; i++) { + sectors_per_track[i] = DSK_SECT; + sector_size[i] = DSK_SECTSIZE; + tracks[i] = MAX_TRACKS; + } + + poc = FALSE; + } + + for (i = 0; i < NUM_OF_DSK; i++) { + current_track[i] = 0; + current_sector[i] = 0; + current_byte[i] = 0; + current_flag[i] = 0; + read_enable[i] = 0; + dummy_read[i] = 0; + } + + current_disk = NUM_OF_DSK; + in9_count = 0; + in9_message = FALSE; + } + + return SCPE_OK; +} +/* fdcp_attach - determine type of drive attached based on disk image size */ + +static t_stat fdcp_attach(UNIT *uptr, const char *cptr) +{ + int32 thisUnitIndex; + t_stat r; + + r = attach_unit(uptr, cptr); /* attach unit */ + + if (r != SCPE_OK) { /* error? */ + return r; + } + + ASSURE(uptr != NULL); + thisUnitIndex = sys_find_unit_index(uptr); + ASSURE((0 <= thisUnitIndex) && (thisUnitIndex < NUM_OF_DSK)); + + uptr->capac = sim_fsize(uptr -> fileref); + + drive_type = 5; + sectors_per_track[thisUnitIndex] = FDCP15_DISK_SECT; + sector_size[thisUnitIndex] = FDCP15_DISK_SECTSIZE; + + sim_printf("DSK%i: " ADDRESS_FORMAT + "Drive type: %i SPT:%i SS:%i\n", thisUnitIndex, s100_bus_get_addr(), + drive_type, sectors_per_track[thisUnitIndex], sector_size[thisUnitIndex]); + + sim_debug(VERBOSE_MSG, &fdcp_dev, "DSK%i: " ADDRESS_FORMAT + "Drive type: %i SPT:%i SS:%i\n", thisUnitIndex, s100_bus_get_addr(), + drive_type, sectors_per_track[thisUnitIndex], sector_size[thisUnitIndex]); + + return SCPE_OK; +} + +static t_stat fdcp_detach(UNIT *uptr) +{ + return detach_unit(uptr); +} + +static t_stat fdcp_boot(int32 unitno, DEVICE *dptr) +{ + cpu_set_pc_loc(0xff00); + + return SCPE_OK; +} + +static int32 dskseek(const UNIT *xptr) +{ + return sim_fseek(xptr -> fileref, sector_size[current_disk] * sectors_per_track[current_disk] * + ((drive_type == 5) ? logical_track : current_track[current_disk]) + + (sector_size[current_disk] * current_sector[current_disk]), SEEK_SET); +} + +/* precondition: current_disk < NUM_OF_DSK */ +static void writebuf(void) +{ + int32 i, rtn; + UNIT *uptr; + + i = current_byte[current_disk]; /* null-fill rest of sector if any */ + + while (i < sector_size[current_disk]) { + dskbuf[i++] = 0; + } + + uptr = fdcp_dev.units + current_disk; + + if (((uptr -> flags) & UNIT_DSK_WLK) == 0) { /* write enabled */ + sim_debug(WRITE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " OUT 0x0a (WRITE) D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + + if (dskseek(uptr)) { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " fseek failed D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + } + + rtn = sim_fwrite(dskbuf, 1, sector_size[current_disk], uptr -> fileref); + + if (rtn != sector_size[current_disk]) { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " sim_fwrite failed T%d S%d Return=%d\n", + current_disk, s100_bus_get_addr(), current_track[current_disk], + current_sector[current_disk], rtn); + } + } + else { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Attempt to write to locked DSK%d - ignored.\n", + current_disk, s100_bus_get_addr(), current_disk); + } + + current_flag[current_disk] &= ~FDCP_ENWD; /* ENWD off */ + current_byte[current_disk] = MAX_SECT_SIZE; + dirty = FALSE; +} + +/* 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. +*/ + +static int32 fdcp_08h(const int32 port, const int32 io, const int32 data) +{ + int32 current_disk_flags; + + in9_count = 0; + + if (io == S100_IO_READ) { /* IN: return flags */ + if (current_disk >= NUM_OF_DSK) { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Attempt of IN DRVSTAT on unattached disk - ignored.\n", + current_disk, s100_bus_get_addr()); + + return 0xff; /* no drive selected - can do nothing */ + } + + return (~current_flag[current_disk]) & 0xff; /* return the COMPLEMENT! */ + } + + /* OUT: Controller set/reset/enable/disable */ + if (dirty) { /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + } + + sim_debug(OUT_MSG, &fdcp_dev, "DSK%i: " ADDRESS_FORMAT " OUT SELECT: %x\n", current_disk, s100_bus_get_addr(), data); + + current_disk = data & NUM_OF_DSK_MASK; /* 0 <= current_disk < NUM_OF_DSK */ + current_disk_flags = (fdcp_dev.units + current_disk)->flags; + + if ((current_disk_flags & UNIT_ATT) == 0) { /* nothing attached? */ + sim_debug(VERBOSE_MSG, &fdcp_dev, "DSK%i: " ADDRESS_FORMAT + " Attempt to select unattached DSK%d - ignored.\n", + current_disk, s100_bus_get_addr(), current_disk); + + current_disk = NUM_OF_DSK; + } else { + current_sector[current_disk] = 0xff; /* reset internal counters */ + current_byte[current_disk] = MAX_SECT_SIZE; + + if (data & FDCP_CLEAR) { /* disable drive? */ + current_flag[current_disk] = 0; /* yes, clear all flags */ + read_enable[current_disk] = 0; + dummy_read[current_disk] = 0; + + sim_debug(READ_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Read Disable (CLEAR)\n", + current_disk, s100_bus_get_addr()); + } + else { /* enable drive */ + current_flag[current_disk] = 0x0a; /* move head true */ + + if (current_track[current_disk] == 0) { /* track 0? */ + current_flag[current_disk] |= FDCP_TRK0; /* yes, set track 0 true as well */ + } + } + } + + return 0xff; /* ignored since OUT */ +} + +/* Disk Drive Status/Functions */ + +static int32 fdcp_09h(const int32 port, const int32 io, const int32 data) +{ + if (current_disk >= NUM_OF_DSK) { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT + " Attempt of %s 0x09 on unattached disk - ignored.\n", + current_disk, s100_bus_get_addr(), selectInOut(io)); + + return 0xff; /* no drive selected - can do nothing */ + } + + /* now current_disk < NUM_OF_DSK */ + if (io == S100_IO_READ) { /* read sector position */ + in9_count++; + + if ((fdcp_dev.dctrl & SECTOR_STUCK_MSG) && (in9_count > 2 * DSK_SECT) && (!in9_message)) { + in9_message = TRUE; + sim_debug(SECTOR_STUCK_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Looping on sector find.\n", + current_disk, s100_bus_get_addr()); + } + + sim_debug(IN_MSG, &fdcp_dev, "DSK%i: " ADDRESS_FORMAT " IN 0x09\n", current_disk, s100_bus_get_addr()); + + if (dirty) { /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + } + + if (current_flag[current_disk] & FDCP_HS) { /* head loaded? */ + sector_true ^= 1; /* return sector true every other entry */ + if (sector_true == 0) { /* true when zero */ + current_sector[current_disk]++; + if (current_sector[current_disk] >= (sectors_per_track[current_disk] < MINI_DISK_SECT) ? DSK_SECT : sectors_per_track[current_disk]) { + current_sector[current_disk] = 0; + } + current_byte[current_disk] = MAX_SECT_SIZE; + } + return (((current_sector[current_disk] << 1) & 0x3e) /* return sector number and...) */ + | 0xc0 | sector_true); /* sector true, and set 'unused' bits */ + } + else { + return 0xff; /* head not loaded - return 0xff */ + } + } + + in9_count = 0; + /* drive functions */ + + sim_debug(OUT_MSG, &fdcp_dev, "DSK%i: " ADDRESS_FORMAT " OUT 0x09: %x\n", current_disk, s100_bus_get_addr(), data); + + if (data & FDCP_STEPIN) { /* step head in */ + if (current_track[current_disk] == (tracks[current_disk] - 1)) { + sim_debug(TRACK_STUCK_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Unnecessary step in.\n", + current_disk, s100_bus_get_addr()); + } + + current_track[current_disk]++; + current_flag[current_disk] &= ~FDCP_TRK0; /* mwd 1/29/13: track zero now false */ + + if (current_track[current_disk] > (tracks[current_disk] - 1)) { + current_track[current_disk] = (tracks[current_disk] - 1); + } + if (dirty) { /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + } + + current_sector[current_disk] = 0xff; + current_byte[current_disk] = MAX_SECT_SIZE; + } + + if (data & FDCP_STEPOUT) { /* step head out */ + if (current_track[current_disk] == 0) { + sim_debug(TRACK_STUCK_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Unnecessary step out.\n", + current_disk, s100_bus_get_addr()); + } + + current_track[current_disk]--; + + if (current_track[current_disk] < 0) { + current_track[current_disk] = 0; + current_flag[current_disk] |= FDCP_TRK0; /* track 0 if there */ + } + if (dirty) { /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + } + + current_sector[current_disk] = 0xff; + current_byte[current_disk] = MAX_SECT_SIZE; + } + + if (dirty) { /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + } + + if (data & FDCP_HDLD) { /* head load */ + current_flag[current_disk] |= FDCP_HS; /* turn on head loaded bit */ + current_flag[current_disk] |= FDCP_NRDA; /* turn on 'read data available' */ + } + + if ((data & FDCP_HDUL) && (drive_type != DSK_ALTAIR_MINIDISK)) { /* head unload */ + current_flag[current_disk] &= ~FDCP_HS; /* turn off 'head loaded' bit */ + current_flag[current_disk] &= ~FDCP_NRDA; /* turn off 'read data available' */ + current_sector[current_disk] = 0xff; + current_byte[current_disk] = MAX_SECT_SIZE; + } + + /* interrupts & head current are ignored */ + + if (data & FDCP_WE) { /* write sequence start */ + current_byte[current_disk] = 0; + current_flag[current_disk] |= FDCP_ENWD; /* enter new write data on */ + } + + if ((data & FDCP_RE) && (drive_type == 5)) { /* Read Enable */ + read_enable[current_disk] = 1; + dummy_read[current_disk] = 1; + + sim_debug(READ_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Read Enable\n", + current_disk, s100_bus_get_addr()); + } + + return 0; /* ignored since OUT */ +} + +/* Disk Data In/Out */ + +static int32 fdcp_0ah(const int32 port, const int32 io, const int32 data) +{ + int32 i, rtn; + UNIT *uptr; + + if (current_disk >= NUM_OF_DSK) { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT + " Attempt of %s 0x0a on unattached disk - ignored.\n", + current_disk, s100_bus_get_addr(), selectInOut(io)); + + return 0; + } + + /* now current_disk < NUM_OF_DSK */ + in9_count = 0; + uptr = fdcp_dev.units + current_disk; + + if (io == S100_IO_READ) { + if (current_byte[current_disk] >= sector_size[current_disk]) { + if (drive_type == 5 && read_enable[current_disk] == 0) { + for (i = 0; i < sizeof(bootSector); i++) { + dskbuf[i] = bootSector[i]; + } + current_byte[current_disk] = 0; /* reset sector buffer index */ + + sim_debug(VERBOSE_MSG, &fdcp_dev, "DSK%i: " ADDRESS_FORMAT "Sending BOOT loader\n", + current_disk, s100_bus_get_addr()); + } + else { + /* physically read the sector */ + sim_debug(READ_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " IN 0x0a (READ) D%d T%d (L%d) S%d SS%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], logical_track, current_sector[current_disk], sector_size[current_disk]); + + /* clear disk buffer */ + for (i = 0; i < sector_size[current_disk]; i++) { + dskbuf[i] = 0x00; + } + + /* seek to track/sector */ + if (dskseek(uptr)) { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " fseek error D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + } + + /* read sector */ + rtn = sim_fread(dskbuf, 1, sector_size[current_disk], uptr -> fileref); + + if (rtn != sector_size[current_disk]) { + sim_debug(VERBOSE_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " sim_fread error D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + } + + current_byte[current_disk] = 0; /* reset sector buffer index */ + dummy_read[current_disk] = 1; + read_enable[current_disk] = 0; + + sim_debug(READ_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " Read Disable (SEC READ)\n", + current_disk, s100_bus_get_addr()); + } + } + + /* + * When an IN DRVDATA is executed, it interrupts the PIC processor on the FDC+ + * which then grabs the next sector byte and writes it to the output register that + * the 8080 sees. However, this cannot be done fast enough to be the data that that + * same IN instruction fetches. So IN instruction "n" returns the data output by + * the PIC in response to IN instruction "n-1". + */ + if (drive_type == 5 && dummy_read[current_disk]) { + dummy_read[current_disk] = 0; + return 0xff; + } + + return dskbuf[current_byte[current_disk]++] & 0xff; + } + else { /* write */ + if (current_byte[current_disk] >= sector_size[current_disk]) { + writebuf(); /* from above we have that current_disk < NUM_OF_DSK */ + } + else { + dirty = TRUE; /* this guarantees for the next call to writebuf that current_disk < NUM_OF_DSK */ + dskbuf[current_byte[current_disk]++] = data & 0xff; + } + + return 0xff; /* ignored since OUT */ + } +} + +/* FDC+ Extended I/O */ + +static int32 fdcp_0bh(const int32 port, const int32 io, const int32 data) +{ + if (io == S100_IO_READ) { + sim_debug(IN_MSG, &fdcp_dev, + "DSK%i: " ADDRESS_FORMAT " IN 0x0b %02X\n", + current_disk, s100_bus_get_addr(), 0x80); + + return 0x80; + } + else { + sim_debug(OUT_MSG, &fdcp_dev, "DSK%i: " ADDRESS_FORMAT " OUT 0x0b: %x\n", current_disk, s100_bus_get_addr(), data); + + if ((data < 0) || (data >= 2 * tracks[current_disk])) { + logical_track = 2 * tracks[current_disk] - 1; + } + else { + logical_track = data; + } + } + + return 0xff; +} + +static t_stat fdcp_set_type(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + int32 i, result, type = 0; + + if (cptr != NULL) { + result = sscanf(cptr, "%d", &type); + + if (type < 0 || type > 7) { + return SCPE_ARG; + } + else if (type != 5 && type != 7) { + sim_printf("Use the DSK device for drive type %d\n", type); + + return SCPE_ARG | SCPE_NOMESSAGE; + } + } + + if (drive_type != type) { /* Detach all disks */ + for (i = 0; i < NUM_OF_DSK; i++) { + fdcp_detach(&fdcp_unit[i]); + } + } + + if (type == 7) { + sim_printf("Drive type 7 not implemented.\n"); + } + else { + drive_type = type; + } + + return SCPE_OK; +} + +static t_stat fdcpd_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nFarmTek FDC+ (%s)\n", sim_dname(dptr)); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + fprintf(st, "\n----- NOTES -----\n\n"); + fprintf(st, "The %s device simulates FarmTek's FDC+ disk controller.\n", DEVICE_DEV); + fprintf(st, "The following drive types are supported:\n\n"); + fprintf(st, " Type 5 - 1.5MB on 5.25\" floppy disk\n"); + fprintf(st, " Type 7 - Serial Drive Server\n\n"); + fprintf(st, "Additional information on the FDC+ can be found at\n"); + fprintf(st, "https://deramp.com/fdc_plus.html\n"); + + fprintf(st, "\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/farmtek_fdcplus.h b/Altair8800/farmtek_fdcplus.h new file mode 100644 index 00000000..fc310c44 --- /dev/null +++ b/Altair8800/farmtek_fdcplus.h @@ -0,0 +1,67 @@ +/* farmtek_fdcplus.h + + Copyright (c) 2026 Patrick A. Linstruth + + 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 + PETER SCHORN 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 Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 03/29/26 Initial version + +*/ + +#ifndef _FARMTEK_FDCPLUS_H +#define _FARMTEK_FDCPLUS_H + +#define UNIT_V_DSK_WLK (UNIT_V_UF + 0) /* write locked */ +#define UNIT_DSK_WLK (1 << UNIT_V_DSK_WLK) + +#define NUM_OF_DSK 4 /* NUM_OF_DSK must be power of two */ +#define NUM_OF_DSK_MASK (NUM_OF_DSK - 1) + +#define DSK_ALTAIR_8IN 1 +#define DSK_ALTAIR_MINIDISK 2 +#define FDCP_TYPE_5 5 + +#define DSK_SECTSIZE 137 /* size of sector */ +#define DSK_SECT 32 /* sectors per track */ +#define MAX_TRACKS 2048 /* number of tracks */ + +#define MINI_DISK_SECT 16 /* mini disk sectors per track */ +#define MINI_DISK_TRACKS 35 /* number of tracks on mini disk */ +#define MINI_DISK_SIZE (MINI_DISK_TRACKS * MINI_DISK_SECT * DSK_SECTSIZE) +#define MINI_DISK_DELTA 4096 /* threshold for detecting mini disks */ + +#define FDCP15_DISK_SECT 1 /* FDC+ 1.5MB Type 5 sectors per track */ +#define FDCP15_DISK_TRACKS 149 /* number of tracks on 1.5MB disk */ +#define FDCP15_DISK_SECTSIZE 10240 /* 1.5MB disk sector size */ +#define FDCP15_DISK_SIZE (FDCP15_DISK_TRACKS * FDCP15_DISK_SECT * FDCP15_DISK_SECTSIZE) +#define FDCP15_DISK_DELTA 4096 /* threshold for detecting 1.5MB disks */ + +#define ALTAIR_DISK_SIZE 337664 /* size of regular Altair disks */ +#define ALTAIR_DISK_DELTA 256 /* threshold for detecting regular Altair disks */ + +#define MAX_SECT_SIZE (FDCP15_DISK_SECTSIZE) +#define MAX_TRK_SIZE (FDCP15_DISK_SECTSIZE) +#define MAX_DSK_SIZE (DSK_SECTSIZE * DSK_SECT * MAX_TRACKS) + +#endif + diff --git a/Altair8800/icom_fd3x12.c b/Altair8800/icom_fd3x12.c new file mode 100644 index 00000000..1058c93b --- /dev/null +++ b/Altair8800/icom_fd3x12.c @@ -0,0 +1,1451 @@ +/* icom_37x2.c: iCOM FD3712/FD3812 Flexible Disk System + + Copyright (c) 2026 Patrick A. Linstruth + + 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 + PETER SCHORN 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 Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 29-Mar-2026 Initial version + + ================================================================== + + These functions support simulated iCOM FD3712 and FD3812 + floppy disk systems. The FD3712 supports IBM Diskette type 1 + single-density and the FD3812 also supports IBM Diskette + type 2D double-density. The density of the media attached + is determined by file size. If a read or write is made + where the media doesn't match the density setting of the + controller, a CRC error status will be set. + + The interface board provides 2 I/O ports: + + Command Register Port C0 Output + Data In Register Port C0 Input + Data Out Register Port C1 Output + + +---------------------------------------------------------+ + | | + | COMMAND SET | + | | + +---------------------------------------------------------+ + | COMMAND | 7 6 5 4 3 2 1 0 | BUSY | HEX CODE | + +---------------------------------------------------------+ + | EXAMINE STATUS | 0 0 0 0 0 0 0 0 | No | 00 | + | READ | 0 0 0 0 0 0 1 1 | Yes | 03 | + | WRITE | 0 0 0 0 0 1 0 1 | Yes | 05 | + | READ CRC | 0 0 0 0 0 1 1 1 | Yes | 07 | + | SEEK | 0 0 0 0 1 0 0 1 | Yes | 09 | + | CLEAR ERROR FLAGS | 0 0 0 0 1 0 1 1 | No | 0B | + | SEEK TRACK ZERO | 0 0 0 0 1 1 0 1 | Yes | 0D | + | WRITE DEL DATA MARK | 0 0 0 0 1 1 1 1 | Yes | 0F | + | LOAD TRACK ADDRESS | 0 0 0 1 0 0 0 1 | No | 11 | + | LOAD UNIT/SECTOR | 0 0 1 0 0 0 0 1 | No | 21 | + | LOAD WRITE BUFFER* | 0 0 1 1 0 0 0 0 | No | 30 | + | LOAD WRITE BUFFER | 0 0 1 1 0 0 0 1 | No | 31 | + | EXAMINE READ BUFFER | 0 1 0 0 0 0 0 0 | No | 40 | + | SHIFT READ BUFFER | 0 1 0 0 0 0 0 1 | No | 41 | + | CLEAR CONTROLLER | 1 0 0 0 0 0 0 1 | No | 81 | + | LOAD CONFIGURATION* | 0 0 0 1 1 0 0 1 | No | 15 | + +---------------------------------------------------------+ + | * FD3812 Only | + +---------------------------------------------------------+ + + Bit 0 of the command byte is actually the clock/strobe bit that activates + processing of a command byte in the controller when it is asserted to one. + The bit must be returned to zero before another command can be recognized. + + With the early interface boards, de-assertion was done in software. The + program had to write a command byte with bit 0 cleared in between each + command. This was typically by writing the Examine Staus or Examine Read + Buffer "commands." + + To speed up the data transfer loop, later interface boards included a + one-shot that drove bit 0 of the command byte to the controller. The one- + shot was triggered by data input and output operations which then executed + the command byte (already present) by temporarily asserting command bit 0. + + The one-shot also automatically asserted and cleared bit 0 to the controller + for any command written that had bit 0 set to 1. This meant the software did + not have to follow every command with an examine command to clear bit 0. + + +---------------------------------------------------------------+ + | | + | DISK STATUS BITS | + | | + +---------------------------------------------------------------+ + | BIT | STATUS SIGNAL | DESCRIPTION | + +---------------------------------------------------------------+ + | 7 | DELETED DATA MARK | The simulator does not implement | + | | | this bit. | + +---------------------------------------------------------------+ + | 6 | MEDIA STATUS | This bit is always set. | + +---------------------------------------------------------------+ + | 5 | DRIVE FAIL | This bit is set if any if a drive | + | | | is not attached using the | + | | | "ATTACH" command or there is a | + | | | problem reading from or writing | + | | | to the attached file. | + +---------------------------------------------------------------+ + | 4 | WRITE PROTECT | This bit is set if the selected | + | | | drive contains a write protected | + | | | diskette. This condition should | + | | | not be tested if the selected | + | | | drive has a "DRIVE FAIL" status. | + | | | Use "SET ICOM WRTPROT" to write | + | | | protect an attached diskette and | + | | | "SET ICOM WRTENB" to enable | + | | | writing. | + +---------------------------------------------------------------+ + | 3 | CRC ERROR | This bit is set when an error has | + | | | occurred during the previous | + | | | command. This bit must be tested | + | | | after all read, write, and seek | + | | | operations. The simulator does | + | | | not implement this bit. | + +---------------------------------------------------------------+ + | 2 | UNIT SELECT MSB | Bits 2 and 1 contain the address | + +---------------------------| of the drive currently being | + | 1 | UNIT SELECT LSB | selected by the controller. | + +---------------------------------------------------------------+ + | 0 | BUSY | This bit is set when a read, | + | | | write, seek command is sent to | + | | | the controller. | + +---------------------------------------------------------------+ + + B = Memory Size - 16K + + 32K: B = 32K - 16K = 16K = 04000H + 48K: B = 48K = 16K = 32K = 08000H + 62K: B = 62K = 16K = 46K = 0B800H + 64K: B = 64K = 16K = 48K = 0C000H + + +----------------------------------------------------------------------+ + | | + | CP/M 1.41 Single Density Disk Layout | + | | + +----------------------------------------------------------------------+ + | Track | Sector | Image Offset | Memory Address | Module | + +----------------------------------------------------------------------+ + | 00 | 01 | 0000-007FH | 0080H | SD Disk Boot Loader | + +----------------------------------------------------------------------+ + | 00 | 02-17 | 0080-087FH | 2900H+B | CCP | + +----------------------------------------------------------------------+ + | 00 | 18-26 | 0880-0CFFH | 3100H+B | BDOS | + | 01 | 01-17 | 0D00-157FH | 3580H+B | BDOS | + +----------------------------------------------------------------------+ + | 01 | 18-21 | 1580-177FH | 3E00H+B | BIOS | + +----------------------------------------------------------------------+ + | 01 | 22-26 | | Not Used | + +----------------------------------------------------------------------+ + + +----------------------------------------------------------------------+ + | | + | CP/M 1.41 Double Density Disk Layout | + | | + +----------------------------------------------------------------------+ + | Track | Sector | Image Offset | Memory Address | Module | + +----------------------------------------------------------------------+ + | 00 | 01 | 0000-007FH | 0080H | DD Disk Boot Loader | + +----------------------------------------------------------------------+ + | 00 | 02-26 | | | Not Used | + +----------------------------------------------------------------------+ + | 01 | 01-09 | 0D00-14FFH | 2900H+B | CCP | + +----------------------------------------------------------------------+ + | 01 | 10-21 | 1500-21FFH | 3100H+B | BDOS | + +----------------------------------------------------------------------+ + | 01 | 22-23 | 2200-23FFH | 3E00H+B | BIOS | + +----------------------------------------------------------------------+ + | 01 | 24-26 | | | Not Used | + +----------------------------------------------------------------------+ + +*/ + +/* #define DBG_MSG */ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "altair8800_dsk.h" +#include "s100_bus.h" +#include "s100_cpu.h" + +#ifdef DBG_MSG +#define DBG_PRINT(args) sim_printf args +#else +#define DBG_PRINT(args) +#endif + +#define ICOM_NUM_DRIVES 4 +#define ICOM_SD_SECTOR_LEN 128 +#define ICOM_DD_SECTOR_LEN 256 +#define ICOM_SPT 26 +#define ICOM_TRACKS 77 +#define ICOM_HEADS 1 +#define ICOM_SD_CAPACITY (256256) /* Default iCOM Single Density Disk Capacity */ +#define ICOM_DD_CAPACITY (509184) /* Default iCOM Double Density Disk Capacity */ + +#define ICOM_IO_BASE 0xc0 +#define ICOM_IO_SIZE 2 + +#define ICOM_PROM_BASE 0xf000 +#define ICOM_PROM_SIZE 1024 +#define ICOM_PROM_MASK (ICOM_PROM_SIZE-1) +#define ICOM_MEM_BASE 0xf400 /* Must be on a page boundary */ +#define ICOM_MEM_SIZE 256 /* Must occupy entire page */ +#define ICOM_MEM_MASK (ICOM_MEM_SIZE-1) + +static int32 poc = TRUE; + +static RES icom_res = { ICOM_IO_BASE, ICOM_IO_SIZE, ICOM_PROM_BASE, ICOM_PROM_SIZE }; +static MDEV mdev = { NULL, NULL }; +static DSK_INFO dsk_info[ICOM_NUM_DRIVES]; + +static uint8 icom_mem[ICOM_MEM_SIZE]; + +/* iCOM PROMs are 1024 bytes */ + +static uint8 icom_3712_prom[ICOM_PROM_SIZE] = { + 0xc3, 0x73, 0xf0, 0x20, 0x41, 0x4c, 0x54, 0x41, + 0x49, 0x52, 0x43, 0x20, 0xc3, 0x85, 0xf0, 0x15, + 0xc3, 0xa6, 0xf0, 0xc3, 0xc7, 0xf0, 0xc3, 0x06, + 0xf4, 0xc3, 0x09, 0xf4, 0xc3, 0x0c, 0xf4, 0xc3, + 0x0f, 0xf4, 0xc3, 0x12, 0xf4, 0xc3, 0x15, 0xf4, + 0xc3, 0x6b, 0xf1, 0xc3, 0x73, 0xf1, 0xc3, 0x6e, + 0xf1, 0xc3, 0x7d, 0xf1, 0xc3, 0x82, 0xf1, 0xc3, + 0x88, 0xf1, 0xc3, 0xc5, 0xf1, 0xc9, 0x00, 0x00, + 0xc3, 0x64, 0xf1, 0xc3, 0x5a, 0xf2, 0x20, 0x33, + 0x37, 0x31, 0x32, 0x2d, 0x56, 0x32, 0x31, 0x20, + 0x28, 0x43, 0x29, 0x20, 0x4c, 0x49, 0x46, 0x45, + 0x42, 0x4f, 0x41, 0x54, 0x20, 0x41, 0x53, 0x53, + 0x4f, 0x43, 0x49, 0x41, 0x54, 0x45, 0x53, 0x20, + 0x31, 0x39, 0x37, 0x39, 0x20, 0x21, 0xe0, 0xf3, + 0xc3, 0x7f, 0xf0, 0x21, 0xf0, 0xf3, 0xc3, 0x7f, + 0xf0, 0x21, 0x68, 0xf3, 0xc3, 0x7f, 0xf0, 0x31, + 0x80, 0x00, 0xcd, 0x8f, 0xf2, 0x31, 0x80, 0x00, + 0xcd, 0x5a, 0xf2, 0x0e, 0x00, 0xcd, 0x6e, 0xf1, + 0x01, 0x80, 0x00, 0xcd, 0x82, 0xf1, 0xcd, 0x88, + 0xf1, 0xc2, 0x88, 0xf0, 0x21, 0x00, 0xf4, 0xeb, + 0x21, 0x10, 0xf0, 0xc3, 0x80, 0x00, 0x22, 0x40, + 0xf4, 0x11, 0xf0, 0xff, 0x19, 0x11, 0x20, 0xf4, + 0x06, 0x10, 0xcd, 0x86, 0xf2, 0x11, 0x80, 0xff, + 0x19, 0xaf, 0x32, 0x48, 0xf4, 0xcd, 0x4f, 0xf1, + 0xaf, 0x32, 0x04, 0x00, 0xc3, 0x28, 0xf1, 0x31, + 0x00, 0x01, 0xcd, 0x5a, 0xf2, 0x0e, 0x00, 0xcd, + 0x6e, 0xf1, 0x2a, 0x40, 0xf4, 0x11, 0x00, 0xeb, + 0x19, 0x24, 0x3e, 0x04, 0xcd, 0xf7, 0xf0, 0x0e, + 0x01, 0xcd, 0x6e, 0xf1, 0x2a, 0x40, 0xf4, 0x11, + 0x00, 0xeb, 0x19, 0x11, 0x80, 0x0c, 0x19, 0x3e, + 0x01, 0xcd, 0xf7, 0xf0, 0xc3, 0x28, 0xf1, 0x32, + 0x32, 0xf4, 0x22, 0x33, 0xf4, 0x3a, 0x41, 0xf4, + 0x3d, 0xbc, 0xda, 0x0b, 0xf1, 0xcd, 0x88, 0xf1, + 0xc2, 0xc7, 0xf0, 0x2a, 0x33, 0xf4, 0x11, 0x80, + 0x01, 0x19, 0x3a, 0x32, 0xf4, 0xc6, 0x03, 0xfe, + 0x1b, 0xda, 0xf7, 0xf0, 0xd6, 0x1a, 0x11, 0x00, + 0xf3, 0x19, 0xfe, 0x01, 0xc2, 0xf7, 0xf0, 0xc9, + 0x01, 0x80, 0x00, 0xcd, 0x82, 0xf1, 0x3e, 0xc3, + 0x32, 0x00, 0x00, 0x32, 0x05, 0x00, 0x2a, 0x40, + 0xf4, 0x23, 0x23, 0x23, 0x22, 0x01, 0x00, 0x11, + 0x03, 0xf3, 0x19, 0x22, 0x06, 0x00, 0x3a, 0x04, + 0x00, 0x4f, 0x11, 0xfa, 0xf7, 0x19, 0xe9, 0x7e, + 0xb7, 0xc8, 0x4e, 0x23, 0xe5, 0xcd, 0x5c, 0xf1, + 0xe1, 0xc3, 0x4f, 0xf1, 0x2a, 0x40, 0xf4, 0x11, + 0x0c, 0x00, 0x19, 0xe9, 0x21, 0x00, 0xf4, 0x06, + 0x00, 0x09, 0xc9, 0xc3, 0x67, 0xf2, 0x79, 0x32, + 0x31, 0xf4, 0xc9, 0x79, 0x32, 0x30, 0xf4, 0x3e, + 0xff, 0x32, 0x27, 0xf4, 0xc9, 0x79, 0x32, 0x32, + 0xf4, 0xc9, 0x60, 0x69, 0x22, 0x33, 0xf4, 0xc9, + 0xcd, 0x0a, 0xf2, 0xc2, 0x06, 0xf2, 0x0e, 0x0a, + 0x3e, 0x03, 0xcd, 0x71, 0xf2, 0xe6, 0x28, 0xca, + 0xa4, 0xf1, 0xcd, 0x7e, 0xf2, 0x0d, 0xc2, 0x90, + 0xf1, 0xc3, 0x06, 0xf2, 0x2a, 0x33, 0xf4, 0x0e, + 0x80, 0x3e, 0x40, 0xd3, 0xc0, 0xdb, 0xc0, 0x77, + 0x23, 0xaf, 0xd3, 0xc0, 0x0d, 0x3e, 0x41, 0xd3, + 0xc0, 0xdb, 0xc0, 0x77, 0x23, 0xaf, 0xd3, 0xc0, + 0x0d, 0xc2, 0xb5, 0xf1, 0xc9, 0xcd, 0x0a, 0xf2, + 0xc2, 0x06, 0xf2, 0x2a, 0x33, 0xf4, 0x0e, 0x80, + 0x7e, 0xd3, 0xc1, 0x3e, 0x31, 0xd3, 0xc0, 0xaf, + 0xd3, 0xc0, 0x23, 0x0d, 0xc2, 0xd0, 0xf1, 0x0e, + 0x0a, 0x3e, 0x05, 0xcd, 0x71, 0xf2, 0xe6, 0x20, + 0xca, 0xf1, 0xf1, 0xcd, 0x7e, 0xf2, 0xc3, 0x06, + 0xf2, 0x3a, 0x2f, 0xf4, 0xe6, 0x40, 0xc8, 0x3e, + 0x07, 0xcd, 0x71, 0xf2, 0xe6, 0x28, 0xc8, 0xcd, + 0x7e, 0xf2, 0x0d, 0xc2, 0xe1, 0xf1, 0x3e, 0x01, + 0xb7, 0xc9, 0xaf, 0xd3, 0xc1, 0x3e, 0x15, 0xcd, + 0x80, 0xf2, 0xcd, 0x19, 0xf2, 0xcd, 0x2d, 0xf2, + 0xc9, 0x3a, 0x30, 0xf4, 0xe6, 0x03, 0x0f, 0x0f, + 0x4f, 0x3a, 0x32, 0xf4, 0xb1, 0xd3, 0xc1, 0x3e, + 0x21, 0xcd, 0x80, 0xf2, 0xc9, 0x0e, 0x02, 0x3a, + 0x31, 0xf4, 0x21, 0x27, 0xf4, 0xbe, 0xc8, 0x77, + 0x3a, 0x31, 0xf4, 0xd3, 0xc1, 0x3e, 0x11, 0xcd, + 0x80, 0xf2, 0x3e, 0x09, 0xcd, 0x71, 0xf2, 0xe6, + 0x28, 0xc8, 0xcd, 0x7e, 0xf2, 0x36, 0xff, 0x0d, + 0xc2, 0x2d, 0xf2, 0xcd, 0x62, 0xf2, 0x3e, 0x02, + 0xb7, 0xc9, 0xaf, 0x32, 0x30, 0xf4, 0x3c, 0x32, + 0x32, 0xf4, 0x3e, 0x81, 0xcd, 0x80, 0xf2, 0xcd, + 0x19, 0xf2, 0x3e, 0xff, 0x32, 0x27, 0xf4, 0x3e, + 0x0d, 0xcd, 0x80, 0xf2, 0xdb, 0xc0, 0xe6, 0x01, + 0xc2, 0x74, 0xf2, 0xdb, 0xc0, 0xc9, 0x3e, 0x0b, + 0xd3, 0xc0, 0xaf, 0xd3, 0xc0, 0xc9, 0x7e, 0x12, + 0x23, 0x13, 0x05, 0xc2, 0x86, 0xf2, 0xc9, 0x11, + 0x00, 0xf4, 0x06, 0x08, 0x3e, 0xc3, 0x12, 0x13, + 0x7e, 0x12, 0x23, 0x13, 0x7e, 0x12, 0x23, 0x13, + 0x05, 0xc2, 0x94, 0xf2, 0xc9, 0x3e, 0x03, 0xd3, + 0x10, 0x3e, 0x11, 0xd3, 0x10, 0xc9, 0xdb, 0x10, + 0xe6, 0x01, 0x3e, 0x00, 0xc8, 0x2f, 0xc9, 0xdb, + 0x10, 0xe6, 0x01, 0xca, 0xb7, 0xf2, 0xdb, 0x11, + 0xe6, 0x7f, 0xca, 0xb7, 0xf2, 0xc9, 0xdb, 0x10, + 0xe6, 0x02, 0xca, 0xc6, 0xf2, 0x79, 0xd3, 0x11, + 0xc9, 0xc9, 0xc9, 0xdb, 0x00, 0xe6, 0x01, 0x3e, + 0x00, 0xc0, 0x2f, 0xc9, 0xdb, 0x00, 0xe6, 0x01, + 0xc2, 0xdc, 0xf2, 0xdb, 0x01, 0xe6, 0x7f, 0xca, + 0xdc, 0xf2, 0xc9, 0xdb, 0x00, 0xe6, 0x80, 0xc2, + 0xeb, 0xf2, 0x79, 0xd3, 0x01, 0xc9, 0x3a, 0x48, + 0xf4, 0xb7, 0xc2, 0x0c, 0xf3, 0x3e, 0x11, 0xd3, + 0x03, 0xaf, 0xd3, 0x02, 0x32, 0x47, 0xf4, 0x3e, + 0x84, 0x32, 0x48, 0xf4, 0x79, 0xfe, 0x0a, 0xc2, + 0x1a, 0xf3, 0x32, 0x49, 0xf4, 0x3a, 0x47, 0xf4, + 0xb7, 0xc8, 0x79, 0xfe, 0x08, 0xca, 0x4f, 0xf3, + 0xfe, 0x09, 0xca, 0x5a, 0xf3, 0xfe, 0x0d, 0xca, + 0x38, 0xf3, 0xd8, 0x3a, 0x47, 0xf4, 0x3c, 0xe5, + 0x21, 0x48, 0xf4, 0xbe, 0xe1, 0xc2, 0x48, 0xf3, + 0x3a, 0x47, 0xf4, 0xb7, 0xc2, 0x47, 0xf3, 0x3a, + 0x49, 0xf4, 0xfe, 0x0d, 0xc8, 0x0e, 0x0a, 0xaf, + 0x32, 0x47, 0xf4, 0x79, 0x32, 0x49, 0xf4, 0xdb, + 0x02, 0xe6, 0x11, 0xca, 0x4f, 0xf3, 0x79, 0xd3, + 0x03, 0xc9, 0x0e, 0x20, 0xcd, 0x0c, 0xf3, 0x3a, + 0x47, 0xf4, 0xe6, 0x07, 0xc2, 0x5a, 0xf3, 0xc9, + 0xa8, 0xf3, 0xd2, 0xf2, 0xa1, 0xf3, 0x78, 0xf3, + 0x8e, 0xf3, 0xf6, 0xf2, 0x8e, 0xf3, 0x78, 0xf3, + 0xcd, 0x84, 0xf3, 0xca, 0x78, 0xf3, 0x7e, 0xe6, + 0x7f, 0x36, 0x00, 0xc9, 0x21, 0x4b, 0xf4, 0x7e, + 0xb7, 0xcc, 0x1f, 0xc0, 0x77, 0xc9, 0x3a, 0x4a, + 0xf4, 0xfe, 0x0d, 0xc2, 0x98, 0xf3, 0xb9, 0xc8, + 0x79, 0x32, 0x4a, 0xf4, 0x41, 0xcd, 0x19, 0xc0, + 0xc9, 0xcd, 0x84, 0xf3, 0xc8, 0x3e, 0xff, 0xc9, + 0x21, 0x00, 0x00, 0x22, 0x4a, 0xf4, 0xc9, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xd1, 0xf2, 0xd2, 0xf2, 0xd3, 0xf2, 0xdc, 0xf2, + 0xeb, 0xf2, 0xf6, 0xf2, 0xeb, 0xf2, 0xdc, 0xf2, + 0xa5, 0xf2, 0xd2, 0xf2, 0xae, 0xf2, 0xb7, 0xf2, + 0xc6, 0xf2, 0xf6, 0xf2, 0xc6, 0xf2, 0xb7, 0xf2, +}; + +static uint8 icom_3812_prom[ICOM_PROM_SIZE] = { + 0xc3, 0x46, 0xf0, 0x06, 0x80, 0x7e, 0x12, 0x23, + 0x13, 0x05, 0xc2, 0x05, 0xf0, 0xc9, 0xff, 0x3a, + 0xc3, 0x6d, 0xf0, 0xc3, 0x8a, 0xf0, 0x79, 0x32, + 0x31, 0xf4, 0xc9, 0x79, 0x32, 0x32, 0xf4, 0xc9, + 0x60, 0x69, 0x22, 0x33, 0xf4, 0xc9, 0xff, 0xff, + 0xc3, 0x08, 0xf1, 0xc3, 0x14, 0xf1, 0xc3, 0x16, + 0xf0, 0xc3, 0x1b, 0xf0, 0xc3, 0x20, 0xf0, 0xc3, + 0x30, 0xf1, 0xc3, 0x7b, 0xf1, 0xc3, 0x21, 0xf1, + 0xc3, 0x61, 0xf3, 0xc3, 0xa4, 0xf3, 0x31, 0x80, + 0x00, 0xcd, 0xa4, 0xf3, 0x21, 0x00, 0x00, 0x22, + 0x30, 0xf4, 0x0e, 0x01, 0xcd, 0x1b, 0xf0, 0x21, + 0x80, 0x00, 0x22, 0x33, 0xf4, 0xcd, 0x30, 0xf1, + 0xc2, 0x46, 0xf0, 0x21, 0x00, 0xf4, 0xeb, 0x21, + 0x10, 0xf0, 0xc3, 0x80, 0x00, 0x22, 0x40, 0xf4, + 0x11, 0xf0, 0xff, 0x19, 0x11, 0x20, 0xf4, 0x06, + 0x10, 0xcd, 0x05, 0xf0, 0x11, 0x80, 0xff, 0x19, + 0xcd, 0xdf, 0xf3, 0xaf, 0x32, 0x04, 0x00, 0xc3, + 0xe1, 0xf0, 0x31, 0x00, 0x01, 0xcd, 0xa4, 0xf3, + 0x21, 0x00, 0x01, 0x22, 0x30, 0xf4, 0x2a, 0x40, + 0xf4, 0x11, 0x00, 0xeb, 0x19, 0x3e, 0x01, 0x4f, + 0xc5, 0x32, 0x32, 0xf4, 0x22, 0x33, 0xf4, 0x7c, + 0x2a, 0x40, 0xf4, 0xbc, 0xd2, 0xb5, 0xf0, 0xcd, + 0x30, 0xf1, 0xc2, 0x8a, 0xf0, 0xc1, 0x79, 0x0f, + 0x79, 0x2a, 0x33, 0xf4, 0xda, 0xc3, 0xf0, 0xc6, + 0x04, 0x24, 0x24, 0x3c, 0x11, 0x80, 0x00, 0x19, + 0xfe, 0x35, 0xda, 0xdc, 0xf0, 0xd6, 0x34, 0xfe, + 0x03, 0x2a, 0x40, 0xf4, 0x11, 0x00, 0xec, 0x19, + 0xca, 0xdc, 0xf0, 0x24, 0xfe, 0x01, 0xc2, 0x9f, + 0xf0, 0x01, 0x80, 0x00, 0xcd, 0x20, 0xf0, 0x3e, + 0xc3, 0x32, 0x00, 0x00, 0x32, 0x05, 0x00, 0x2a, + 0x40, 0xf4, 0x23, 0x23, 0x23, 0x22, 0x01, 0x00, + 0x11, 0x03, 0xf3, 0x19, 0x22, 0x06, 0x00, 0x3a, + 0x04, 0x00, 0x4f, 0x11, 0xfa, 0xf7, 0x19, 0xe9, + 0xcd, 0x21, 0xf1, 0x3a, 0x30, 0xf4, 0x32, 0x3d, + 0xf4, 0xc3, 0xb6, 0xf3, 0x79, 0x32, 0x30, 0xf4, + 0xcd, 0x21, 0xf1, 0x3e, 0xff, 0x32, 0x27, 0xf4, + 0xc9, 0x3a, 0x39, 0xf4, 0x3c, 0xc8, 0xcd, 0x6f, + 0xf2, 0xc5, 0xcd, 0xf2, 0xf1, 0xc1, 0xc9, 0x11, + 0xcd, 0x6f, 0xf2, 0xcd, 0x57, 0xf3, 0xca, 0x5a, + 0xf1, 0x21, 0x30, 0xf4, 0x11, 0x39, 0xf4, 0xcd, + 0x2b, 0xf2, 0xc2, 0x4e, 0xf1, 0x1a, 0xbe, 0xc2, + 0x4e, 0xf1, 0xcd, 0xf2, 0xf1, 0xc0, 0x21, 0x30, + 0xf4, 0x11, 0x35, 0xf4, 0xcd, 0x2b, 0xf2, 0xca, + 0x64, 0xf1, 0x21, 0x30, 0xf4, 0xcd, 0x22, 0xf2, + 0xcd, 0x46, 0xf2, 0xc0, 0xcd, 0x57, 0xf3, 0xca, + 0x71, 0xf1, 0x3a, 0x32, 0xf4, 0x3c, 0x0f, 0xe6, + 0x80, 0x2a, 0x33, 0xf4, 0xeb, 0xcd, 0x9d, 0xf2, + 0xc8, 0xc3, 0x11, 0xcd, 0x6f, 0xf2, 0xcd, 0x57, + 0xf3, 0x2a, 0x33, 0xf4, 0xca, 0xb0, 0xf1, 0x21, + 0x30, 0xf4, 0x11, 0x39, 0xf4, 0xcd, 0x2b, 0xf2, + 0xc2, 0xbf, 0xf1, 0x1a, 0xbe, 0xca, 0xc3, 0xf1, + 0x3e, 0xff, 0x32, 0x39, 0xf4, 0x2a, 0x33, 0xf4, + 0xe5, 0x2a, 0x2c, 0xf4, 0x3a, 0x3b, 0xf4, 0x0f, + 0xda, 0xac, 0xf1, 0xe3, 0xcd, 0xf7, 0xf2, 0xe1, + 0xcd, 0xf7, 0xf2, 0x21, 0x30, 0xf4, 0xcd, 0x22, + 0xf2, 0xcd, 0x63, 0xf2, 0xc9, 0x2f, 0xfe, 0xcd, + 0xf2, 0xf1, 0xc0, 0x21, 0x30, 0xf4, 0x11, 0x39, + 0xf4, 0xcd, 0x25, 0xf2, 0x2a, 0x2c, 0xf4, 0xeb, + 0x2a, 0x33, 0xf4, 0xcd, 0x03, 0xf0, 0x2a, 0x40, + 0xf4, 0x11, 0x09, 0xf5, 0x19, 0x11, 0xf2, 0xf1, + 0xd5, 0x7e, 0xfe, 0x10, 0xc8, 0xfe, 0x13, 0xc8, + 0xfe, 0x16, 0xc8, 0xfe, 0x17, 0xc8, 0xd1, 0xaf, + 0xc9, 0x0e, 0x21, 0x39, 0xf4, 0x7e, 0x3c, 0xc8, + 0xcd, 0x22, 0xf2, 0x3e, 0xff, 0x32, 0x39, 0xf4, + 0xcd, 0x46, 0xf2, 0xc0, 0x3a, 0x3b, 0xf4, 0x0f, + 0xd2, 0x18, 0xf2, 0xcd, 0xf4, 0xf2, 0xcd, 0xb8, + 0xf2, 0xcd, 0x0a, 0xf3, 0xca, 0x1e, 0xf2, 0x11, + 0xcd, 0x0a, 0xf3, 0xcd, 0xf4, 0xf2, 0xcd, 0x63, + 0xf2, 0xc9, 0x11, 0x3d, 0xf4, 0x06, 0x03, 0xc3, + 0x05, 0xf0, 0x06, 0x1a, 0xb7, 0xf8, 0xbe, 0xc0, + 0x23, 0x13, 0x1a, 0xbe, 0xc0, 0x23, 0x13, 0x7e, + 0x3c, 0x0f, 0xe6, 0x7f, 0x4f, 0x1a, 0x3c, 0x0f, + 0xe6, 0x7f, 0xb9, 0xc9, 0xfe, 0x21, 0x3e, 0xff, + 0x32, 0x35, 0xf4, 0xaf, 0x32, 0x38, 0xf4, 0xcd, + 0x82, 0xf2, 0x3e, 0x01, 0xc0, 0x21, 0x3d, 0xf4, + 0x11, 0x35, 0xf4, 0xcd, 0x25, 0xf2, 0x78, 0xc8, + 0xc3, 0x7a, 0xf1, 0x3e, 0xff, 0x32, 0x35, 0xf4, + 0xcd, 0xcf, 0xf2, 0xc8, 0x3e, 0x01, 0xc9, 0xd1, + 0x21, 0x00, 0x00, 0x39, 0x31, 0x80, 0xf4, 0xe5, + 0x21, 0x7e, 0xf2, 0xe5, 0xeb, 0xe9, 0xe1, 0xf9, + 0xc9, 0x21, 0xcd, 0x28, 0xf3, 0xc2, 0x99, 0xf2, + 0x0e, 0x05, 0x3e, 0x03, 0xcd, 0xca, 0xf3, 0xe6, + 0x08, 0xc8, 0xcd, 0xd7, 0xf3, 0x0d, 0xc2, 0x8a, + 0xf2, 0x3e, 0x01, 0xb7, 0xc9, 0x21, 0x38, 0xf4, + 0xbe, 0xc4, 0xb8, 0xf2, 0x06, 0x80, 0x3e, 0x40, + 0xd3, 0xc0, 0xdb, 0xc0, 0x12, 0x13, 0x34, 0x05, + 0xc2, 0xaa, 0xf2, 0xaf, 0xd3, 0xc0, 0xc8, 0x11, + 0x06, 0x80, 0x21, 0x38, 0xf4, 0x3e, 0x40, 0xd3, + 0xc0, 0xdb, 0xc0, 0x34, 0x05, 0xc2, 0xc1, 0xf2, + 0x78, 0xd3, 0xc0, 0xc8, 0xcd, 0x17, 0xf2, 0xcd, + 0x28, 0xf3, 0xc2, 0x99, 0xf2, 0x0e, 0x05, 0x3e, + 0x05, 0xcd, 0xca, 0xf3, 0x3a, 0x2f, 0xf4, 0xe6, + 0x40, 0xc8, 0x3e, 0x07, 0xcd, 0xca, 0xf3, 0xe6, + 0x08, 0xc8, 0xcd, 0xd7, 0xf3, 0x0d, 0xc2, 0xd7, + 0xf2, 0xc3, 0x99, 0xf2, 0x2a, 0x2c, 0xf4, 0x06, + 0x80, 0x3e, 0x30, 0xd3, 0xc0, 0x7e, 0xd3, 0xc1, + 0x23, 0x05, 0xc2, 0xfd, 0xf2, 0x78, 0xd3, 0xc0, + 0xc8, 0x0e, 0x06, 0x80, 0x3e, 0x40, 0xd3, 0xc0, + 0xdb, 0xc0, 0x4f, 0xaf, 0xd3, 0xc0, 0x3e, 0x30, + 0xd3, 0xc0, 0x79, 0xd3, 0xc1, 0xaf, 0xd3, 0xc0, + 0x05, 0xc2, 0x0c, 0xf3, 0xc9, 0xcd, 0xb7, 0xf2, + 0x16, 0x05, 0xcd, 0x3f, 0xf3, 0xd3, 0xc1, 0x3e, + 0x21, 0xcd, 0xd9, 0xf3, 0xcd, 0x6b, 0xf3, 0xc8, + 0x15, 0xc2, 0x2a, 0xf3, 0xc3, 0x99, 0xf2, 0x2a, + 0x3d, 0xf4, 0x7d, 0x0f, 0x0f, 0x5f, 0xcd, 0x5a, + 0xf3, 0x3a, 0x3f, 0xf4, 0xca, 0x53, 0xf3, 0x3c, + 0x0f, 0xe6, 0x3f, 0xb3, 0xc9, 0x06, 0x0b, 0x2a, + 0x30, 0xf4, 0x7c, 0xb7, 0xc8, 0x3e, 0x28, 0x85, + 0x4f, 0x21, 0x00, 0xf4, 0x06, 0x00, 0x09, 0x7e, + 0xe6, 0x02, 0xc9, 0x3a, 0x3e, 0xf4, 0x21, 0x27, + 0xf4, 0xbe, 0xc8, 0x77, 0x5f, 0x2a, 0x3d, 0xf4, + 0xcd, 0x5a, 0xf3, 0xca, 0x80, 0xf3, 0x3e, 0x10, + 0xd3, 0xc1, 0x3e, 0x15, 0xcd, 0xd9, 0xf3, 0x7b, + 0xb7, 0x3e, 0x0d, 0xca, 0x98, 0xf3, 0x7b, 0xd3, + 0xc1, 0x3e, 0x11, 0xcd, 0xd9, 0xf3, 0x3e, 0x09, + 0xcd, 0xca, 0xf3, 0xe6, 0x28, 0xc8, 0xcd, 0xb1, + 0xf3, 0xc3, 0x99, 0xf2, 0x3e, 0xff, 0x32, 0x39, + 0xf4, 0xaf, 0x32, 0x3d, 0xf4, 0x3c, 0x32, 0x3f, + 0xf4, 0x3e, 0x81, 0xcd, 0xd9, 0xf3, 0xcd, 0x3f, + 0xf3, 0xd3, 0xc1, 0x3e, 0x21, 0xcd, 0xd9, 0xf3, + 0x3e, 0xff, 0x32, 0x27, 0xf4, 0x32, 0x35, 0xf4, + 0x3e, 0x0d, 0xcd, 0xd9, 0xf3, 0xdb, 0xc0, 0xe6, + 0x01, 0xc2, 0xcd, 0xf3, 0xdb, 0xc0, 0xc9, 0x3e, + 0x0b, 0xd3, 0xc0, 0xaf, 0xd3, 0xc0, 0xc9, 0x7e, + 0xb7, 0xc8, 0x4e, 0xe5, 0xcd, 0xec, 0xf3, 0xe1, + 0x23, 0xc3, 0xdf, 0xf3, 0x2a, 0x40, 0xf4, 0x2e, + 0x0c, 0xe9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static uint8 *icom_prom = icom_3812_prom; /* default is 3812 */ + +/* +** ICOM Registers and Interface Controls +*/ +typedef struct { + uint8 status; /* Status Register */ + uint8 track; /* Track Register */ + uint8 sector; /* Sector Register */ + uint8 command; /* Command Register */ + uint8 rData; /* Read Data Register */ + uint32 rDataBuf; /* Read buffer index */ + uint8 wData; /* Write Data Register */ + uint32 wDataBuf; /* Write buffer index */ + uint8 formatMode; /* format mode */ + int denMode; /* denMode SD or DD */ +} ICOM_REG; + +/* iCOM Registers */ +#define ICOM_REG_COMMAND 0x00 +#define ICOM_REG_DATAI 0x00 +#define ICOM_REG_DATAO 0x01 + +/* iCOM Commands */ +#define ICOM_CMD_STATUS 0x00 +#define ICOM_CMD_CMDMSK 0x01 +#define ICOM_CMD_READ 0x03 +#define ICOM_CMD_WRITE 0x05 +#define ICOM_CMD_READCRC 0x07 +#define ICOM_CMD_SEEK 0x09 +#define ICOM_CMD_CLRERRFLGS 0x0b +#define ICOM_CMD_TRACK0 0x0d +#define ICOM_CMD_WRITEDDM 0x0f +#define ICOM_CMD_LDTRACK 0x11 +#define ICOM_CMD_LDUNITSEC 0x21 +#define ICOM_CMD_LDWRITEBUFOS 0x30 /* 3812 One-Shot Write */ +#define ICOM_CMD_LDWRITEBUF 0x31 +#define ICOM_CMD_EXREADBUF 0x40 +#define ICOM_CMD_SHREADBUF 0x41 +#define ICOM_CMD_CLEAR 0x81 +#define ICOM_CMD_LDCONF 0x15 /* 3812 Load Configuration */ + +#define ICOM_STAT_BUSY 0x01 +#define ICOM_STAT_UNITMSK 0x06 +#define ICOM_STAT_CRC 0x08 +#define ICOM_STAT_WRITEPROT 0x10 +#define ICOM_STAT_DRVFAIL 0x20 +#define ICOM_STAT_MEDIASTAT 0x40 +#define ICOM_STAT_DDM 0x80 + +#define ICOM_CONF_DD 0x10 /* 3812 Double Density */ +#define ICOM_CONF_FM 0x20 /* 3812 Format Mode */ + +#define ICOM_TYPE_3712 0x00 +#define ICOM_TYPE_3812 0x01 + +static int32 prom_enabled = TRUE; +static int32 board_type = ICOM_TYPE_3812; + +typedef struct { + uint8 rwsMs; /* Read/Write Sector ms */ + uint8 seekMs; /* Seek ms */ + uint8 drvSel; /* Currently selected drive */ + uint8 currentTrack[ICOM_NUM_DRIVES]; + uint8 mediaDen[ICOM_NUM_DRIVES]; + ICOM_REG ICOM; /* ICOM Registers and Data */ +} ICOM_INFO; + +static ICOM_INFO icom_info; + +/* +** Read and Write Data Ring Buffers +*/ +#define DATA_MASK ICOM_DD_SECTOR_LEN-1 + +static uint8 rdata[ICOM_DD_SECTOR_LEN]; +static uint8 wdata[ICOM_DD_SECTOR_LEN]; + +/* Local function prototypes */ +static t_stat icom_reset(DEVICE *icom_dev); +static t_stat icom_svc(UNIT *uptr); +static t_stat icom_attach(UNIT *uptr, const char *cptr); +static t_stat icom_detach(UNIT *uptr); +static t_stat icom_boot(int32 unitno, DEVICE *dptr); +static t_stat icom_set_prom(UNIT *uptr, int32 val, const char *cptr, void *desc); +static t_stat icom_show_prom(FILE *st, UNIT *uptr, int32 val, const void *desc); +static void icom_enable_prom(void); +static void icom_disable_prom(void); +static t_stat icom_set_type(UNIT *uptr, int32 val, const char *cptr, void *desc); +static t_stat icom_show_type(FILE *st, UNIT *uptr, int32 val, const void *desc); +static void icom_set_busy(uint32 msec); +static int icom_set_crc(uint8 drive); +static uint8 icom_io_read(uint32 Addr); +static uint8 icom_io_write(uint32 Addr, int32 data); +static const char * ICOM_CommandString(uint8 command); +static uint8 ICOM_Command(UNIT *uptr, ICOM_REG *pICOM, int32 data); +static int32 ICOM_ReadSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer); +static int32 ICOM_WriteSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer); +static uint32 ICOM_FormatTrack(UNIT *uptr, uint8 track, uint8 *buffer); +static uint8 ICOM_DriveNotReady(UNIT *uptr, ICOM_REG *pICOM); +static const char* icom_description(DEVICE *dptr); +static void showReadSec(void); +static void showWriteSec(void); +static int32 icom_io(int32 Addr, int32 rw, int32 data); +static int32 icom_promio(int32 Addr, int32 rw, int32 data); +static int32 icom_memio(int32 Addr, int32 rw, int32 data); +static t_stat icom_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +static UNIT icom_unit[ICOM_NUM_DRIVES] = { + { UDATA (icom_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, ICOM_DD_CAPACITY), 10000 }, + { UDATA (icom_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, ICOM_DD_CAPACITY), 10000 }, + { UDATA (icom_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, ICOM_DD_CAPACITY), 10000 }, + { UDATA (icom_svc, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, ICOM_DD_CAPACITY), 10000 } +}; + +static REG icom_reg[] = { + { DRDATAD (DRIVE, icom_info.drvSel, 8, "Current drive register"), }, + { HRDATAD (STATUS, icom_info.ICOM.status, 8, "Status register"), }, + { HRDATAD (COMMAND, icom_info.ICOM.command, 8, "Command register"), }, + { HRDATAD (RDATA, icom_info.ICOM.rData, 8, "Read Data register"), }, + { HRDATAD (WDATA, icom_info.ICOM.wData, 8, "Write Data register"), }, + { DRDATAD (TRACK, icom_info.ICOM.track, 8, "Track register"), }, + { DRDATAD (SECTOR, icom_info.ICOM.sector, 8, "Sector register"), }, + { DRDATAD (RBUF, icom_info.ICOM.rDataBuf, 16, "Read data buffer index register"), }, + { DRDATAD (WBUF, icom_info.ICOM.wDataBuf, 16, "Write data buffer index register"), }, + { DRDATAD (FORMAT, icom_info.ICOM.formatMode, 8, "Current format mode register"), }, + { DRDATAD (DENSITY, icom_info.ICOM.denMode, 16, "Current density register"), }, + { FLDATAD (PROM, prom_enabled, 0, "PROM enabled bit"), }, + { DRDATAD (RWSMS, icom_info.rwsMs, 8, "Read/Write sector time (ms)"), }, + { DRDATAD (SEEKMS, icom_info.seekMs, 8, "Seek track to track time (ms)"), }, + { NULL } +}; + +#define ICOM_NAME "iCOM 3712/3812 Floppy Disk System" +#define DEV_NAME "ICOM" + +static const char* icom_description(DEVICE *dptr) { + return ICOM_NAME; +} + +#define UNIT_V_ICOM_WPROTECT (UNIT_V_UF + 1) /* WRTENB / WRTPROT */ +#define UNIT_ICOM_WPROTECT (1 << UNIT_V_ICOM_WPROTECT) + +static MTAB icom_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "Sets interface board I/O base address" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "PROM", "PROM={ENABLE|DISABLE}", + &icom_set_prom, &icom_show_prom, NULL, "Set/Show PROM enabled/disabled status"}, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "TYPE", "TYPE={3712|3812}", + &icom_set_type, &icom_show_type, NULL, "Set/Show the current controller type" }, + { UNIT_ICOM_WPROTECT, 0, "WRTENB", "WRTENB", NULL, NULL, NULL, + "Enables " DEV_NAME "n for writing" }, + { UNIT_ICOM_WPROTECT, UNIT_ICOM_WPROTECT, "WRTPROT", "WRTPROT", NULL, NULL, NULL, + "Protects " DEV_NAME "n from writing" }, + { 0 } +}; + +/* Debug flags */ +#define VERBOSE_MSG (1 << 0) +#define ERROR_MSG (1 << 1) +#define RBUF_MSG (1 << 2) +#define WBUF_MSG (1 << 3) +#define CMD_MSG (1 << 4) +#define RD_DATA_MSG (1 << 5) +#define WR_DATA_MSG (1 << 6) +#define STATUS_MSG (1 << 7) +#define RD_DATA_DETAIL_MSG (1 << 8) +#define WR_DATA_DETAIL_MSG (1 << 9) + +/* Debug Flags */ +static DEBTAB icom_dt[] = { + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { "ERROR", ERROR_MSG, "Error messages" }, + { "CMD", CMD_MSG, "Command messages" }, + { "RBUF", RBUF_MSG, "Read Buffer messages" }, + { "WBUF", WBUF_MSG, "Write Buffer messages" }, + { "READ", RD_DATA_MSG, "Read messages" }, + { "WRITE", WR_DATA_MSG, "Write messages" }, + { "STATUS", STATUS_MSG, "Status messages" }, + { "RDDETAIL", RD_DATA_DETAIL_MSG, "Read detail messages" }, + { "WRDETAIL", WR_DATA_DETAIL_MSG, "Write detail messages" }, + { NULL, 0 } +}; + +DEVICE icom_dev = { + DEV_NAME, /* name */ + icom_unit, /* unit */ + icom_reg, /* registers */ + icom_mod, /* modifiers */ + ICOM_NUM_DRIVES, /* # units */ + ADDRRADIX, /* address radix */ + ADDRWIDTH, /* address width */ + 1, /* addr increment */ + DATARADIX, /* data radix */ + DATAWIDTH, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &icom_reset, /* reset routine */ + &icom_boot, /* boot routine */ + &icom_attach, /* attach routine */ + &icom_detach, /* detach routine */ + &icom_res, /* context */ + (DEV_DISABLE | DEV_DIS | DEV_DEBUG), /* flags */ + ERROR_MSG, /* debug control */ + icom_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &icom_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context for help */ + &icom_description /* description */ +}; + +/* Reset routine */ +static t_stat icom_reset(DEVICE *dptr) +{ + RES *res; + int i; + + if ((res = (RES *) dptr->ctxt) == NULL) { + sim_printf("CTX is NULL!\n"); + return SCPE_IERR; + } + + sim_debug(STATUS_MSG, &icom_dev, "reset controller.\n"); + + if (dptr->flags & DEV_DIS) { /* Remove from bus */ + s100_bus_remio(res->io_base, res->io_size, &icom_io); + s100_bus_remmem(res->mem_base, res->mem_size, &icom_promio); + s100_bus_remmem(ICOM_MEM_BASE, ICOM_MEM_SIZE, &icom_memio); + + poc = TRUE; + } else { + if (poc) { /* Powerup? */ + sim_debug(STATUS_MSG, &icom_dev, "Power on Clear.\n"); + + /* Connect I/O at base address */ + s100_bus_addio(res->io_base, res->io_size, &icom_io, DEV_NAME); + + /* Add memory */ + s100_bus_addmem(ICOM_MEM_BASE, ICOM_MEM_SIZE, &icom_memio, DEV_NAME" (RAM)"); + + if (prom_enabled) { + icom_enable_prom(); + } + + for (i = 0; i < ICOM_NUM_DRIVES; i++) { + icom_unit[i].dptr = dptr; + dsk_init(&dsk_info[i], &icom_unit[i], ICOM_TRACKS, ICOM_HEADS, 0); + dsk_set_verbose_flag(&dsk_info[i], VERBOSE_MSG); + } + + poc = FALSE; + } + + icom_info.drvSel = 0; + icom_info.rwsMs = 0; + icom_info.seekMs = 0; + + icom_info.ICOM.track = 0; + icom_info.ICOM.sector = 1; + icom_info.ICOM.command = 0; + icom_info.ICOM.status = 0; + icom_info.ICOM.rData = 0; + icom_info.ICOM.wData = 0; + icom_info.ICOM.rDataBuf = 0; + icom_info.ICOM.wDataBuf = 0; + icom_info.ICOM.formatMode = 0; + + /* Reset Registers and Interface Controls */ + for (i=0; i < ICOM_NUM_DRIVES; i++) { + icom_info.currentTrack[i] = 0; + } + } + + return SCPE_OK; +} + +/* Service routine */ +static t_stat icom_svc(UNIT *uptr) +{ + icom_info.ICOM.status &= ~ICOM_STAT_BUSY; + + return SCPE_OK; +} + +/* Attach routine */ +static t_stat icom_attach(UNIT *uptr, const char *cptr) +{ + t_stat r; + int d; + + /* Determine drive number */ + d = uptr - &icom_unit[0]; + + if (d < 0 || d >= ICOM_NUM_DRIVES) { + return SCPE_IERR; + } + + if ((r = attach_unit(uptr, cptr)) != SCPE_OK) { /* attach unit */ + sim_printf(DEV_NAME ": ATTACH error=%d\n", r); + return r; + } + + /* Determine length of this disk */ + if ((uptr->capac = sim_fsize(uptr->fileref)) == 0) { + uptr->capac = ICOM_SD_CAPACITY; + } + + /* init format based on file size */ + switch (uptr->capac) { + case ICOM_DD_CAPACITY: + dsk_init_format(&dsk_info[d], 0, 1, 0, 0, DSK_DENSITY_SD, ICOM_SPT, ICOM_SD_SECTOR_LEN, 1); + dsk_init_format(&dsk_info[d], 1, 76, 0, 0, DSK_DENSITY_DD, ICOM_SPT, ICOM_DD_SECTOR_LEN, 1); + break; + + default: + uptr->capac = ICOM_SD_CAPACITY; + dsk_init_format(&dsk_info[d], 0, 76, 0, 0, DSK_DENSITY_SD, ICOM_SPT, ICOM_SD_SECTOR_LEN, 1); + break; + } + + sim_debug(VERBOSE_MSG, uptr->dptr, "unit %d, attached to '%s' size=%d interface=%s\n", + d, cptr, uptr->capac, (board_type == ICOM_TYPE_3712) ? "FD3712" : "FD3812"); + +// dsk_show(&dsk_info[d]); + + return SCPE_OK; +} + + +/* Detach routine */ +static t_stat icom_detach(UNIT *uptr) +{ + t_stat r; + int8 i; + + for (i = 0; i < ICOM_NUM_DRIVES; i++) { + if (icom_dev.units[i].fileref == uptr->fileref) { + break; + } + } + + if (i >= ICOM_NUM_DRIVES) { + return SCPE_ARG; + } + + r = detach_unit(uptr); /* detach unit */ + + if (r != SCPE_OK) { + return r; + } + + icom_dev.units[i].fileref = NULL; + + sim_debug(VERBOSE_MSG, uptr->dptr, "unit %d detached.\n", i); + + return SCPE_OK; +} + +static t_stat icom_set_type(UNIT *uptr, int32 val, const char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + + if (!strcmp(cptr, "3812")) { + board_type = ICOM_TYPE_3812; + icom_info.ICOM.status |= ICOM_STAT_MEDIASTAT; + icom_prom = icom_3812_prom; + } else if (!strcmp(cptr, "3712")) { + board_type = ICOM_TYPE_3712; + icom_info.ICOM.status &= ~ICOM_STAT_MEDIASTAT; + icom_info.ICOM.denMode = ICOM_SD_SECTOR_LEN; + icom_prom = icom_3712_prom; + } else { + return SCPE_ARG; + } + + return SCPE_OK; +} + +static t_stat icom_show_type(FILE *st, UNIT *uptr, int32 val, const void *desc) +{ + fprintf(st, "TYPE=%s", (board_type == ICOM_TYPE_3812) ? "3812" : "3712"); + + return SCPE_OK; +} + +static t_stat icom_boot(int32 unitno, DEVICE *dptr) +{ + sim_debug(STATUS_MSG, &icom_dev, DEV_NAME ": Booting Controller at 0x%04x\n", icom_res.mem_base); + + cpu_set_pc_loc(icom_res.mem_base); + + return SCPE_OK; +} + +static void icom_set_busy(uint32 msec) +{ + icom_info.ICOM.status |= ICOM_STAT_BUSY; + + sim_activate_after_abs(&icom_unit[icom_info.drvSel], (msec * 100) + 100); /* activate timer */ +} + +/* Set CRC flag if trying to read/write wrong density */ +static int icom_set_crc(uint8 drive) +{ + uint8 track; + int32 secsize; + + /* If formatting, CRC is always good */ + if (icom_info.ICOM.formatMode) { + return 0; + } + + track = icom_info.currentTrack[drive]; + secsize = dsk_sector_size(&dsk_info[icom_info.drvSel], track, 0); + + icom_info.ICOM.status &= ~ICOM_STAT_CRC; + + /* If reading double density from track 0, set CRC error */ + /* Else, if reading a density different than attached disk, set CRC error */ + if (track == 0 && (icom_info.ICOM.denMode != secsize || secsize != ICOM_SD_SECTOR_LEN)) { + icom_info.ICOM.status |= ICOM_STAT_CRC; + } + else if (track > 0) { + if (icom_info.ICOM.denMode != secsize) { + icom_info.ICOM.status |= ICOM_STAT_CRC; + } + } + + return (icom_info.ICOM.status & ICOM_STAT_CRC); +} + +static int32 icom_io(int32 Addr, int32 rw, int32 data) +{ + if (rw == S100_IO_READ) { + return(icom_io_read(Addr)); + } else { + return(icom_io_write(Addr, data)); + } +} + +static uint8 icom_io_read(uint32 Addr) +{ + ICOM_REG *pICOM; + UNIT *uptr; + uint8 cData; + + uptr = &icom_unit[icom_info.drvSel]; + pICOM = &icom_info.ICOM; + + switch(Addr & 0x01) { + case ICOM_REG_DATAI: + if (pICOM->command & ICOM_CMD_EXREADBUF) { + pICOM->rData = rdata[pICOM->rDataBuf]; + sim_debug(RBUF_MSG, &icom_dev, "read buffer[%d]=%02x\n", pICOM->rDataBuf, pICOM->rData); + if (board_type == ICOM_TYPE_3812) { + ICOM_Command(uptr, pICOM, ICOM_CMD_SHREADBUF); + } + cData = pICOM->rData; + } + else { + cData = pICOM->status; + } + + break; + + default: + sim_debug(ERROR_MSG, &icom_dev, "READ Invalid I/O Address %02x (%02x)\n", Addr & 0xFF, Addr & 0x01); + cData = 0xff; + break; + } + + return (cData); +} + +static uint8 icom_io_write(uint32 Addr, int32 Data) +{ + uint8 cData; + UNIT *uptr; + ICOM_REG *pICOM; + + cData = 0; + uptr = &icom_unit[icom_info.drvSel]; + pICOM = &icom_info.ICOM; + + switch(Addr & 0x01) { + case ICOM_REG_COMMAND: + cData = ICOM_Command(uptr, pICOM, Data); + break; + + case ICOM_REG_DATAO: + pICOM->wData = Data; + + if (pICOM->command == ICOM_CMD_LDWRITEBUFOS && board_type == ICOM_TYPE_3812) { + ICOM_Command(uptr, pICOM, ICOM_CMD_LDWRITEBUF); + } + break; + + default: + sim_debug(ERROR_MSG, &icom_dev, "WRITE Invalid I/O Address %02x (%02x)\n", Addr & 0xFF, Addr & 0x01); + cData = 0xff; + break; + } + + return(cData); +} + +static void showReadSec(void) +{ + int i; + ICOM_REG *pICOM; + + pICOM = &icom_info.ICOM; + + sim_debug(RD_DATA_DETAIL_MSG, &icom_dev, "rdata unit %d track/sector %02d/%02d:\n", icom_info.drvSel, pICOM->track, pICOM->sector); + + for (i=0; i < dsk_sector_size(&dsk_info[icom_info.drvSel], pICOM->track, 0); i++) { + if (((i) & 0xf) == 0) { + sim_debug(RD_DATA_DETAIL_MSG, &icom_dev, "\t"); + } + sim_debug(RD_DATA_DETAIL_MSG, &icom_dev, "%02X ", rdata[i]); + if (((i+1) & 0xf) == 0) { + sim_debug(RD_DATA_DETAIL_MSG, &icom_dev, "\n"); + } + } +} + +static void showWriteSec(void) +{ + int i; + ICOM_REG *pICOM; + + pICOM = &icom_info.ICOM; + + sim_debug(WR_DATA_DETAIL_MSG, &icom_dev, "wdata unit %d track/sector %02d/%02d:\n", icom_info.drvSel, pICOM->track, pICOM->sector); + + for (i=0; i < dsk_sector_size(&dsk_info[icom_info.drvSel], pICOM->track, 0); i++) { + if (((i) & 0xf) == 0) { + sim_debug(WR_DATA_DETAIL_MSG, &icom_dev, "\t"); + } + sim_debug(WR_DATA_DETAIL_MSG, &icom_dev, "%02X ", wdata[i]); + if (((i+1) & 0xf) == 0) { + sim_debug(WR_DATA_DETAIL_MSG, &icom_dev, "\n"); + } + } +} + +static int32 ICOM_ReadSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer) +{ + int32 bytes; + ICOM_REG *pICOM; + + pICOM = &icom_info.ICOM; + + if (uptr->fileref == NULL) { + sim_debug(ERROR_MSG, &icom_dev, "uptr.fileref is NULL!\n"); + return 0; + } + + sim_debug(RD_DATA_MSG, &icom_dev, "drive %d track %d sector %d\n", icom_info.drvSel, track, sector); + + dsk_read_sector(&dsk_info[icom_info.drvSel], track, 0, sector, buffer, &bytes); + + sim_debug(RD_DATA_MSG, &icom_dev, "read %d bytes\n", bytes); + + return bytes; +} + + +static int32 ICOM_WriteSector(UNIT *uptr, uint8 track, uint8 sector, uint8 *buffer) +{ + int32 bytes; + ICOM_REG *pICOM; + + pICOM = &icom_info.ICOM; + + if (uptr->fileref == NULL) { + sim_debug(ERROR_MSG, &icom_dev, "uptr.fileref is NULL!\n"); + return 0; + } + + sim_debug(WR_DATA_MSG, &icom_dev, "drive %d track %d sector %d bytes %d\n", icom_info.drvSel, track, sector, dsk_sector_size(&dsk_info[icom_info.drvSel], track, 0)); + + dsk_write_sector(&dsk_info[icom_info.drvSel], track, 0, sector, buffer, &bytes); + + sim_debug(WR_DATA_MSG, &icom_dev, "wrote %d bytes\n", bytes); + + return bytes; +} + +/* 3812 Only */ +static uint32 ICOM_FormatTrack(UNIT *uptr, uint8 track, uint8 *buffer) +{ + uint8 sector; + uint32 rtn; + + /* Update format for this track */ + dsk_init_format(&dsk_info[icom_info.drvSel], track, track, 0, 0, (icom_info.ICOM.denMode == ICOM_SD_SECTOR_LEN)? DSK_DENSITY_SD : DSK_DENSITY_DD, ICOM_SPT, icom_info.ICOM.denMode, 1); + +// dsk_show(&dsk_info[d]); + + for (sector = 1; sector <= ICOM_SPT; sector++) { + rtn = ICOM_WriteSector(uptr, track, sector, buffer); + sim_debug(WR_DATA_MSG, &icom_dev, "FORMAT track %d sector %d\n", track, sector); + } + + /* update disk density when formatting */ + icom_info.mediaDen[icom_info.drvSel] = (dsk_sector_size(&dsk_info[icom_info.drvSel], track, 0) == ICOM_DD_SECTOR_LEN); + + return rtn; +} + +static uint8 ICOM_DriveNotReady(UNIT *uptr, ICOM_REG *pICOM) +{ + pICOM->status &= ~ICOM_STAT_DRVFAIL; + + if ((uptr == NULL) || (uptr->fileref == NULL)) { + pICOM->status |= ICOM_STAT_DRVFAIL; + sim_debug(STATUS_MSG, &icom_dev, "Drive: %d not attached.\n", icom_info.drvSel); + } + + return (pICOM->status & ICOM_STAT_DRVFAIL); +} + +static const char * ICOM_CommandString(uint8 command) +{ + switch (command) { + case ICOM_CMD_STATUS: + return "STATUS"; + + case ICOM_CMD_READ: + return "READ"; + + case ICOM_CMD_WRITE: + return "WRITE"; + + case ICOM_CMD_READCRC: + return "READ CRC"; + + case ICOM_CMD_SEEK: + return "SEEK"; + + case ICOM_CMD_CLRERRFLGS: + return "CLR ERR FLAGS"; + + case ICOM_CMD_TRACK0: + return "TRACK 0"; + + case ICOM_CMD_WRITEDDM: + return "WRITE DDM"; + + case ICOM_CMD_LDTRACK: + return "LD TRACK"; + + case ICOM_CMD_LDUNITSEC: + return "LD UNIT/SEC"; + + case ICOM_CMD_LDWRITEBUFOS: + return "LD WR BUF ONE-SHOT"; + + case ICOM_CMD_LDWRITEBUF: + return "LD WR BUF"; + + case ICOM_CMD_EXREADBUF: + return "EX RD BUF"; + + case ICOM_CMD_SHREADBUF: + return "SHFT RD BUF"; + + case ICOM_CMD_CLEAR: + return "CLEAR"; + + case ICOM_CMD_LDCONF: + return "LD CONFIG"; + + default: + break; + } + + return "UNRECOGNIZED COMMAND"; +} + +static uint8 ICOM_Command(UNIT *uptr, ICOM_REG *pICOM, int32 Data) +{ + uint8 cData; + uint8 newTrack; + int32 rtn; + + cData = 0; + + if (uptr == NULL) { + return cData; + } + + pICOM->command = Data; + + ICOM_DriveNotReady(uptr, pICOM); /* Update not ready status */ + + switch(pICOM->command) { + case ICOM_CMD_STATUS: + pICOM->rData = pICOM->status; + break; + + case ICOM_CMD_READ: + if (pICOM->status & ICOM_STAT_DRVFAIL || icom_set_crc(icom_info.drvSel)) { + break; + } + + rtn = ICOM_ReadSector(uptr, pICOM->track, pICOM->sector, rdata); + + if (rtn == dsk_sector_size(&dsk_info[icom_info.drvSel], pICOM->track, 0)) { + showReadSec(); + icom_set_busy(icom_info.rwsMs); + } + else { + sim_debug(ERROR_MSG, &icom_dev, "sim_fread errno=%d\n", errno); + + pICOM->status |= ICOM_STAT_DRVFAIL; + } + + pICOM->rDataBuf = 0; // Reset read buffer address + + break; + + case ICOM_CMD_WRITEDDM: + sim_debug(VERBOSE_MSG, &icom_dev, "DDM writes not supported. Performing standard write.\n"); + + /* fall into ICOM_CMD_WRITE */ + + case ICOM_CMD_WRITE: + if (uptr->flags & UNIT_ICOM_WPROTECT) { + sim_debug(ERROR_MSG, &icom_dev, "Disk '%s' write protected.\n", uptr->filename); + break; + } + + if (pICOM->status & ICOM_STAT_DRVFAIL || icom_set_crc(icom_info.drvSel)) { + break; + } + + /* + ** If format mode, format entire track with wdata + */ + if (pICOM->formatMode) { + rtn = ICOM_FormatTrack(uptr, pICOM->track, wdata); + } + else { + rtn = ICOM_WriteSector(uptr, pICOM->track, pICOM->sector, wdata); + } + + if (rtn == dsk_sector_size(&dsk_info[icom_info.drvSel], pICOM->track, 0)) { + showWriteSec(); + icom_set_busy(icom_info.rwsMs); + } + else { + sim_debug(ERROR_MSG, &icom_dev, "sim_fwrite errno=%d\n", errno); + + pICOM->status |= ICOM_STAT_DRVFAIL; + } + + pICOM->wDataBuf = 0; // Reset write buffer address + break; + + case ICOM_CMD_READCRC: + if (pICOM->status & ICOM_STAT_DRVFAIL) { + break; + } + icom_set_crc(icom_info.drvSel); + icom_set_busy(icom_info.rwsMs); + break; + + case ICOM_CMD_SEEK: + if (pICOM->status & ICOM_STAT_DRVFAIL) { + break; + } + + icom_set_busy(icom_info.seekMs * abs((int8) pICOM->track - (int8) icom_info.currentTrack[icom_info.drvSel])); + icom_info.currentTrack[icom_info.drvSel] = pICOM->track; + + break; + + case ICOM_CMD_CLRERRFLGS: + pICOM->status &= ~ICOM_STAT_BUSY; + pICOM->status &= ~ICOM_STAT_DDM; + break; + + case ICOM_CMD_TRACK0: + if (pICOM->status & ICOM_STAT_DRVFAIL) { + break; + } + + pICOM->track = 0; + icom_set_busy(icom_info.seekMs * abs((int8) pICOM->track - (int8) icom_info.currentTrack[icom_info.drvSel])); + icom_info.currentTrack[icom_info.drvSel] = 0; + + break; + + case ICOM_CMD_LDTRACK: + newTrack = pICOM->wData; + + if (newTrack < ICOM_TRACKS) { + pICOM->track = newTrack; + } + + break; + + case ICOM_CMD_LDUNITSEC: + pICOM->sector = pICOM->wData & 0x1f; + icom_info.drvSel = (pICOM->wData >> 6) & 0x03; + pICOM->status &= ~ICOM_STAT_UNITMSK; + pICOM->status |= icom_info.drvSel << 1; + sim_debug(STATUS_MSG, &icom_dev, "LOAD UNIT: D:%d S:%d [%02X]\n", icom_info.drvSel, pICOM->sector, pICOM->wData); + break; + + case ICOM_CMD_LDWRITEBUFOS: + sim_debug(WBUF_MSG, &icom_dev, "LOAD WRITE BUF ONE-SHOT index=%04x\n", pICOM->wDataBuf); + break; + + case ICOM_CMD_LDWRITEBUF: + sim_debug(WBUF_MSG, &icom_dev, "LOAD WRITE BUF %d=%02x\n", pICOM->wDataBuf, pICOM->wData); + wdata[pICOM->wDataBuf] = pICOM->wData; + pICOM->wDataBuf++; + pICOM->wDataBuf &= DATA_MASK; + break; + + case ICOM_CMD_EXREADBUF: + sim_debug(RBUF_MSG, &icom_dev, "EXAMINE READ BUF index=%04x\n", pICOM->rDataBuf); + break; + + case ICOM_CMD_SHREADBUF: + pICOM->rDataBuf++; + pICOM->rDataBuf &= DATA_MASK; + sim_debug(RBUF_MSG, &icom_dev, "SHIFT READ BUF index=%04x\n", pICOM->rDataBuf); + break; + + case ICOM_CMD_CLEAR: + pICOM->status &= ~ICOM_STAT_BUSY; + pICOM->status &= ~ICOM_STAT_DRVFAIL; + pICOM->status &= ~ICOM_STAT_CRC; + pICOM->status &= ~ICOM_STAT_DDM; + break; + + case ICOM_CMD_LDCONF: /* 3812 Only */ + pICOM->formatMode = (pICOM->wData & ICOM_CONF_FM); + pICOM->denMode = (pICOM->wData & ICOM_CONF_DD) ? ICOM_DD_SECTOR_LEN : ICOM_SD_SECTOR_LEN; + break; + + default: + cData=0xFF; + break; + } + + /* Set WRITE PROTECT bit */ + pICOM->status &= ~ICOM_STAT_WRITEPROT; + pICOM->status |= (uptr->flags & UNIT_ICOM_WPROTECT) ? ICOM_STAT_WRITEPROT : 0; + + /* Set data register to status if command bit 6 is 0 */ + if (!(pICOM->command & 0x40)) { + pICOM->rData = pICOM->status; + } + + /* Clear command bit 0 */ + pICOM->command &= ~ICOM_CMD_CMDMSK; + + sim_debug(CMD_MSG, &icom_dev, + "%-13.13s (%02Xh) unit=%d trk=%02d sec=%02d stat=%02Xh mediaDen=%d density=%d formatMode=%s\n", + ICOM_CommandString(Data), + Data, icom_info.drvSel, + pICOM->track, pICOM->sector, pICOM->status, + icom_info.mediaDen[icom_info.drvSel], + dsk_sector_size(&dsk_info[icom_info.drvSel], pICOM->track, 0), (pICOM->formatMode) ? "TRUE" : "FALSE"); + + return(cData); +} + +static t_stat icom_set_prom(UNIT *uptr, int32 val, const char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "ENABLE", strlen(cptr)) && prom_enabled == FALSE) { + icom_enable_prom(); + } else if (!strncmp(cptr, "DISABLE", strlen(cptr)) && prom_enabled == TRUE) { + icom_disable_prom(); + } else { + return SCPE_ARG; + } + + return SCPE_OK; +} + +static t_stat icom_show_prom(FILE *st, UNIT *uptr, int32 val, const void *desc) +{ + fprintf(st, "%s", (prom_enabled) ? "PROM" : "NOPROM"); + + return SCPE_OK; +} + +static void icom_enable_prom(void) +{ + /* Save existing memory device */ + if (mdev.routine == NULL) { + s100_bus_get_mdev(icom_res.mem_base, &mdev); + } + + /* Add PROM to bus */ + s100_bus_addmem(icom_res.mem_base, icom_res.mem_size, &icom_promio, DEV_NAME" (PROM)"); + + prom_enabled = TRUE; +} + +static void icom_disable_prom(void) +{ + /* Restore memory device */ + if (mdev.routine != NULL) { + s100_bus_addmem(icom_res.mem_base, icom_res.mem_size, mdev.routine, mdev.name); + mdev.routine = NULL; + mdev.name = NULL; + } + else { + s100_bus_remmem(icom_res.mem_base, icom_res.mem_size, &icom_promio); + } + + prom_enabled = FALSE; +} + +static int32 icom_promio(int32 Addr, int32 rw, int32 Data) +{ + /* + ** The iCOM controller PROM occupies 1024 bytes (1K) at + ** location F000H. + */ + if (prom_enabled == TRUE) { + return(icom_prom[Addr & ICOM_PROM_MASK]); + } + + return 0xff; +} + +static int32 icom_memio(int32 Addr, int32 rw, int32 Data) +{ + /* + ** The iCOM controller RAM occupies 256 bytes at + ** location F400H. + */ + + if (rw == S100_IO_WRITE) { + icom_mem[Addr & ICOM_MEM_MASK] = Data & 0xff; + } + + return(icom_mem[Addr & ICOM_MEM_MASK]); +} + +static t_stat icom_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\n%s (%s)\n", icom_description(dptr), dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/mits_hdsk.c b/Altair8800/mits_hdsk.c new file mode 100755 index 00000000..9bca8dbe --- /dev/null +++ b/Altair8800/mits_hdsk.c @@ -0,0 +1,575 @@ +/* mits_hdsk.c: MITS 88-HDSK Hard Disk simulator + + Copyright (c) 2026 Patrick A. Linstruth + + 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 + PETER SCHORN 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 Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + Written by Mike Douglas March, 2014 + Ported to Altair8800 by Patrick Linstruth March, 2026 + + Disk images provided by Martin Eberhard + + ================================================================== + + The 88-HDSK from MITS/Pertec consists of a 5mb removable platter and + a fixed 5mb platter. Each platter is double sided. Head 0 and 1 are the + top and bottom surface of the removable platter and head 2 and 3 are the + top and bottom surface of the fixed platter. Hard disk BASIC treats the + two platters as two separate drives. Each platter has 406 cylinders + with 24 sectors per track and 256 bytes per sector. + + The disk image file starts with head 0, track 0, sector 0 (0,0,0) through + (0,0,23), followed by head 1, track 0, sector 0 (1,0,0) through (1,0,23). + The pattern then repeats starting with (0,1,0). + + The external hard disk is accessed through eight ports of a 4-PIO card + at I/O addresses A0h-A7h. + +*/ + +#include "sim_defs.h" +#include "sim_tmxr.h" +#include "altair8800_defs.h" +#include "s100_bus.h" +#include "s100_cpu.h" + +/** Typedefs & Defines **************************************/ +#define HDSK_SECTOR_SIZE 256 /* size of sector */ +#define HDSK_SECTORS_PER_TRACK 24 /* sectors per track */ +#define HDSK_NUM_HEADS 2 /* heads per disk */ +#define HDSK_NUM_TRACKS 406 /* tracks per surface */ +#define HDSK_TRACK_SIZE (HDSK_SECTOR_SIZE * HDSK_SECTORS_PER_TRACK) +#define HDSK_CYLINDER_SIZE (HDSK_TRACK_SIZE * 2) +#define HDSK_CAPACITY (HDSK_CYLINDER_SIZE * HDSK_NUM_TRACKS) +#define HDSK_NUMBER 8 /* number of hard disks */ +#define IO_IN 0 /* I/O operation is input */ +#define IO_OUT 1 /* I/O operation is output */ +#define UNIT_V_DSK_WLK (UNIT_V_UF + 0) /* write locked */ +#define UNIT_DSK_WLK (1 << UNIT_V_DSK_WLK) + +/* Debug flags */ +#define READ_MSG (1 << 0) +#define WRITE_MSG (1 << 1) +#define VERBOSE_MSG (1 << 2) + +/* boot related */ +#define BOOTROM_SIZE_HDSK 256 +#define HDSK_BOOT_ADDRESS 0xfc00 +static t_stat mhdsk_boot(int32 unitno, DEVICE *dptr); +extern t_stat install_bootrom(const int32 bootrom[], const int32 size, const int32 addr, const int32 makeROM); + +// Disk controller commands are in upper nibble of command high byte. + +#define CMD_SHIFT 4 // shift right 4 places +#define CMD_MASK 0x0f // mask after shifting +#define CMD_SEEK 0 // seek to track +#define CMD_WRITE_SEC 2 // write sector from buf n +#define CMD_READ_SEC 3 // read sector into buf n +#define CMD_WRITE_BUF 4 // load buffer n from CPU +#define CMD_READ_BUF 5 // read buffer n into CPU +#define CMD_READ_STATUS 6 // read controller IV byte +#define CMD_SET_IV_BYTE 8 // set controller IV byte +#define CMD_READ_UNFMT 10 // read unformatted sector +#define CMD_FORMAT 12 +#define CMD_INITIALIZE 14 +#define CMD_MAX (CMD_INITIALIZE + 1) + +static const char* commandMessage[CMD_MAX] = { + "Seek", // CMD_SEEK 0 + "Undefined 1", // 1 + "Write Sector", // CMD_WRITE_SEC 2 + "Read Sector", // CMD_READ_SEC 3 + "Write Buffer", // CMD_WRITE_BUF 4 + "Read Buffer", // CMD_READ_BUF 5 + "Read Status", // CMD_READ_STATUS 6 + "Undefined 7", // 7 + "Set IV Byte", // CMD_SET_IV_BYTE 8 + "Undefined 9", // 9 + "Read Unformatted Sector", // CMD_READ_UNFMT 10 + "Undefined 11", // 11 + "Format", // CMD_FORMAT 12 + "Undefined 13", // 13 + "Initialize", // CMD_INITIALIZE 14 +}; + +// Other disk controller bit fields + +#define UNIT_SHIFT 2 // shift right 2 places +#define UNIT_MASK 0x03 // mask after shifting + +#define BUFFER_MASK 0x03 // mask - no shift needed + +#define TRACK_SHIFTH 8 // shift left 8 places into MSbyte +#define TRACK_MASKH 0x01 // msb of track number +#define TRACK_MASKL 0xff // entire lsb of track number + +#define HEAD_SHIFT 5 // shift right 5 places +#define HEAD_MASK 0x03 // mask after shifting (no heads 4-7) + +#define SECTOR_MASK 0x1f // mask - no shift needed + +// Command status equates + +#define CSTAT_WRITE_PROTECT 0x80 // disk is write protected +#define CSTAT_NOT_READY 0x01 // drive not ready +#define CSTAT_BAD_SECTOR 0x02 // invalid sector number + + +/** Module Globals - Private ********************************/ +static uint32 selectedDisk = 0; // current active disk +static uint32 selectedSector = 0; // current sector +static uint32 selectedTrack = 0; // current track +static uint32 selectedHead = 0; // current head +static uint32 selectedBuffer = 0; // current buffer # in use +static uint32 bufferIdx = 0; // current index into selected buffer +static uint32 maxBufferIdx = 256; // maximum buffer index allowed +static uint32 cmdLowByte = 0; // low byte of command + +// Controller status bytes + +static uint8 cstat = 0; // command status from controller + +// The hard disk controller supports four 256 byte disk buffers */ + +static uint8 diskBuf1[HDSK_SECTOR_SIZE]; +static uint8 diskBuf2[HDSK_SECTOR_SIZE]; +static uint8 diskBuf3[HDSK_SECTOR_SIZE]; +static uint8 diskBuf4[HDSK_SECTOR_SIZE]; +static uint8 *diskBuf[] = { diskBuf1, diskBuf2, diskBuf3, diskBuf4 }; + +/** Forward and external Prototypes **************************************/ + +static int32 hdReturnReady(const int32 port, const int32 io, const int32 data); +static int32 hdCstat(const int32 port, const int32 io, const int32 data); +static int32 hdAcmd(const int32 port, const int32 io, const int32 data); +static int32 hdCdata(const int32 port, const int32 io, const int32 data); +static int32 hdAdata(const int32 port, const int32 io, const int32 data); +static void doRead(const int32 port, const int32 data, const uint32 command); +static void doWrite(const int32 port, const int32 data, const uint32 command); +static t_stat dsk_reset(DEVICE *dptr); +static const char* cmdTranslate(const int32 cmd); +static const char* mhdsk_description(DEVICE *dptr); + +/* 88DSK Standard I/O Data Structures */ + +static UNIT dsk_unit[] = { + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }, + { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }}; + +#define HDSK_NAME "MITS Hard Disk" + +static const char* mhdsk_description(DEVICE *dptr) { + return HDSK_NAME; +} + +static MTAB dsk_mod[] = { + { UNIT_DSK_WLK, 0, "WRTENB", "WRTENB", NULL, NULL, NULL, + "Enables " HDSK_NAME "n for writing" }, + { UNIT_DSK_WLK, UNIT_DSK_WLK, "WRTLCK", "WRTLCK", NULL, NULL, NULL, + "Locks " HDSK_NAME "n for writing" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB mhdsk_dt[] = { + { "READ", READ_MSG, "Read messages" }, + { "WRITE", WRITE_MSG, "Write messages" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +DEVICE mhdsk_dev = { + "HDSK", dsk_unit, NULL, dsk_mod, + HDSK_NUMBER, 10, 31, 1, 8, 8, + NULL, NULL, &dsk_reset, + &mhdsk_boot, NULL, NULL, + NULL, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), 0, + mhdsk_dt, NULL, NULL, NULL, NULL, NULL, &mhdsk_description +}; + +static int32 bootrom_mhdsk[BOOTROM_SIZE_HDSK] = { + 0xf3, 0x31, 0x00, 0xf8, 0x21, 0x1b, 0x41, 0x2b, /* fc00-fc07 */ + 0x7c, 0xb5, 0xc2, 0x07, 0xfc, 0xe5, 0xd3, 0xa0, /* fc08-fc0f */ + 0xd3, 0xa2, 0xd3, 0xa4, 0xd3, 0xa6, 0xd3, 0xa1, /* fc10-fc17 */ + 0xd3, 0xa5, 0x2f, 0xd3, 0xa3, 0xd3, 0xa7, 0x3e, /* fc18-fc1f */ + 0x2c, 0xd3, 0xa0, 0xd3, 0xa4, 0xd3, 0xa6, 0x3e, /* fc20-fc27 */ + 0x24, 0xd3, 0xa2, 0xdb, 0xa1, 0x3e, 0x03, 0xd3, /* fc28-fc2f */ + 0x10, 0x3e, 0x11, 0xd3, 0x10, 0xcd, 0xe5, 0xfc, /* fc30-fc37 */ + 0x0d, 0x0a, 0x48, 0x44, 0x42, 0x4c, 0x20, 0x31, /* fc38-fc3f */ + 0x2e, 0x30, 0xb1, 0xcd, 0x77, 0xfc, 0x11, 0x2c, /* fc40-fc47 */ + 0x00, 0x7a, 0xbb, 0xdb, 0xa5, 0xd2, 0x54, 0xfc, /* fc48-fc4f */ + 0x6c, 0x61, 0x48, 0x47, 0x14, 0xc2, 0x49, 0xfc, /* fc50-fc57 */ + 0xcd, 0xe5, 0xfc, 0x0d, 0x0a, 0x4c, 0x4f, 0x41, /* fc58-fc5f */ + 0x44, 0x49, 0x4e, 0xc7, 0xd1, 0xd5, 0xcd, 0x77, /* fc60-fc67 */ + 0xfc, 0xdb, 0xa5, 0x12, 0x13, 0x05, 0xc2, 0x69, /* fc68-fc6f */ + 0xfc, 0x23, 0x0d, 0xc2, 0x66, 0xfc, 0xc9, 0xe5, /* fc70-fc77 */ + 0xd5, 0xc5, 0x01, 0xd0, 0xff, 0x11, 0xff, 0xff, /* fc78-fc7f */ + 0x13, 0x09, 0xda, 0x80, 0xfc, 0x7d, 0xc6, 0x30, /* fc80-fc87 */ + 0xeb, 0xfe, 0x18, 0xda, 0x90, 0xfc, 0xc6, 0x08, /* fc88-fc8f */ + 0x47, 0xcd, 0xaf, 0xfc, 0x26, 0x30, 0xdb, 0xff, /* fc90-fc97 */ + 0xe6, 0x03, 0x0f, 0x0f, 0xb0, 0xcd, 0xb0, 0xfc, /* fc98-fc9f */ + 0xdb, 0xa5, 0xdb, 0xa3, 0xaf, 0xd3, 0xa7, 0x3e, /* fca0-fca7 */ + 0x50, 0xd3, 0xa3, 0xc1, 0xd1, 0xe1, 0xc9, 0x7d, /* fca8-fcaf */ + 0xd3, 0xa7, 0xdb, 0xa1, 0xdb, 0xa3, 0xdb, 0xff, /* fcb0-fcb7 */ + 0xe6, 0x00, 0xb4, 0xd3, 0xa3, 0xdb, 0xa0, 0x07, /* fcb8-fcbf */ + 0xd2, 0xbd, 0xfc, 0xdb, 0xa1, 0xe6, 0x7f, 0xc8, /* fcc0-fcc7 */ + 0xfb, 0xf5, 0xcd, 0xe5, 0xfc, 0x0d, 0x0a, 0x4c, /* fcc8-fccf */ + 0x4f, 0x41, 0x44, 0x20, 0x45, 0x52, 0x52, 0x4f, /* fcd0-fcd7 */ + 0x52, 0xba, 0x21, 0x00, 0xfd, 0x34, 0xca, 0xde, /* fcd8-fcdf */ + 0xfc, 0xe3, 0xc3, 0xcf, 0xfd, 0xe3, 0xdb, 0x10, /* fce0-fce7 */ + 0xe6, 0x02, 0xca, 0xe6, 0xfc, 0x7e, 0xe6, 0x7f, /* fce8-fcef */ + 0xd3, 0x11, 0xbe, 0x23, 0xca, 0xe6, 0xfc, 0xe3, /* fcf0-fcf7 */ + 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* fcf8-fcff */ +}; + +static t_stat mhdsk_boot(int32 unitno, DEVICE *dptr) { + cpu_set_pc_loc(HDSK_BOOT_ADDRESS); + + return SCPE_OK; +} + +static const char* cmdTranslate(const int32 cmd) { + static char result[128]; + if ((0 <= cmd) && (cmd < CMD_MAX)) + return commandMessage[cmd]; + sprintf(result, "Undefined %i", cmd); + return result; +} + +/*---------------------------------------------------------------------------------- + + dsk_reset - install I/O handlers and initialize variables. + + ----------------------------------------------------------------------------------*/ + +static t_stat dsk_reset(DEVICE *dptr) +{ + if (dptr->flags & DEV_DIS) { + s100_bus_remio(0xA0, 1, &hdReturnReady); + s100_bus_remio(0xA1, 1, &hdCstat); + s100_bus_remio(0xA2, 1, &hdReturnReady); + s100_bus_remio(0xA3, 1, &hdAcmd); + s100_bus_remio(0xA4, 1, &hdReturnReady); + s100_bus_remio(0xA5, 1, &hdCdata); + s100_bus_remio(0xA6, 1, &hdReturnReady); + s100_bus_remio(0xA7, 1, &hdAdata); + } else { + s100_bus_addio(0xA0, 1, &hdReturnReady, "hdReturnReady"); + s100_bus_addio(0xA1, 1, &hdCstat, "hdCstat"); + s100_bus_addio(0xA2, 1, &hdReturnReady, "hdReturnReady"); + s100_bus_addio(0xA3, 1, &hdAcmd, "hdAcmd"); + s100_bus_addio(0xA4, 1, &hdReturnReady, "hdReturnReady"); + s100_bus_addio(0xA5, 1, &hdCdata, "hdCdata"); + s100_bus_addio(0xA6, 1, &hdReturnReady, "hdReturnReady"); + s100_bus_addio(0xA7, 1, &hdAdata, "hdAdata"); + } + + selectedSector = 0; // current sector + selectedTrack = 0; // current track + selectedHead = 0; // current head + selectedBuffer = 0; // current buffer # in use + bufferIdx = 0; // current index into selected buffer + maxBufferIdx = 256; // maximum buffer index allowed + cmdLowByte = 0; // low byte of command + + return SCPE_OK; +} + +/*------------------------------------------------------------------------------------- + hdReturnReady - common I/O handler for several hard disk status ports which set + bit 7 when the corresponding hard disk function is ready. In the emulator, + we're always ready for the next step, so we simply return ready all the time. + + 0xA0 - CREADY register. Accessed through the status/control register of 4-PIO + port 1-A. Returns the "ready for command" status byte. + + 0xA2 - ACSTA register. Accessed through the status/control register of 4-PIO + port 1-B. Returns the "command received" status byte. + + 0xA4 - CDSTA register. Accessed through the status/control register of 4-PIO + port 2-A. Returns the "command data available" status byte. + + 0xA6 - ADSTA register. Accessed through the status/control register of 4-PIO + port 2-B. Returns the "available to write" status byte. + +---------------------------------------------------------------------------------------*/ +static int32 hdReturnReady(const int32 port, const int32 io, const int32 data) +{ + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " IN(%02X = %s) = 0x80.\n", + s100_bus_get_addr(), port, (port == 0xa0 ? "CREADY" : (port == 0xa2 ? "ACSTA" : + (port == 0xa4 ? "CDSTA" : (port == 0xa6 ? "ADSTA" : "?????"))))); + return(0x80); // always indicate ready + + // output operations have no effect +} + +/*------------------------------------------------------------ + hdCstat (0xA1) CSTAT register. Accessed through the + data register of 4-PIO port 1-A. + + Comments: Returns error code byte of the most recent + operation. Reading this byte also clears + the CRDY bit, but this isn't actually done + in the emulation since we're always ready. +-------------------------------------------------------------*/ +static int32 hdCstat(const int32 port, const int32 io, const int32 data) +{ + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " IN(%02X = %s) = %02x.\n", s100_bus_get_addr(), port, (port == 0xa1 ? "CSTAT" : "?????"), cstat); + return(cstat); + + // output operations have no effect +} + +/*------------------------------------------------------------ + hdAcmd (0xA3) ACMD register. Accessed through the + data register of 4-PIO port 1-B. + + Comments: The high byte of a command is written to + this register and initiates the command. + The low byte of a command is assumed to + have already been written and stored in + cmdLowByte; +-------------------------------------------------------------*/ +static int32 hdAcmd(const int32 port, const int32 io, const int32 data) +{ + uint32 command; // command field from command msb + uint32 unit; // unit number from command msb + uint32 buffer; // buffer number from command msb + +// if not an OUT command, exit + + if (io != IO_OUT) + return(0); + +// extract command and possible unit and buffer fields. + + cstat = 0; // assume command success + command = (data >> CMD_SHIFT) & CMD_MASK; + unit = (data >> UNIT_SHIFT) & UNIT_MASK; + buffer = data & BUFFER_MASK; + +// SEEK command. Updated selectedTrack. + + if (command == CMD_SEEK) { + selectedTrack = cmdLowByte + ((data & TRACK_MASKH) << TRACK_SHIFTH); + if (selectedTrack >= HDSK_NUM_TRACKS) + selectedTrack = HDSK_NUM_TRACKS-1; + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. " + "Unit = %i. Buffer = %i. Selected Track = %i.\n", + s100_bus_get_addr(), port, data, cmdTranslate(command), unit, buffer, selectedTrack); + } + +// READ, READ UNFORMATTED or WRITE SECTOR command. + + else if ((command == CMD_WRITE_SEC) || (command == CMD_READ_SEC) || (command == CMD_READ_UNFMT)) { + selectedHead = (cmdLowByte >> HEAD_SHIFT) & HEAD_MASK; + selectedDisk = (selectedHead >> 1) + unit * 2 ; + selectedSector = cmdLowByte & SECTOR_MASK; + selectedBuffer = buffer; + if (mhdsk_dev.units[selectedDisk].fileref == NULL) { // make sure a file is attached + cstat = CSTAT_NOT_READY; + sim_debug(READ_MSG, &mhdsk_dev, "HDSK%i: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. " + "Track = %i. Sector = %i. Head = %i. Buffer = %i. " + "No file attached.\n", selectedDisk, s100_bus_get_addr(), port, data, cmdTranslate(command), + selectedTrack, selectedSector, selectedHead, selectedBuffer); + } else if (command == CMD_WRITE_SEC) + doWrite(port, data, command); + else // CMD_READ_SEC or CMD_READ_UNFMT + doRead(port, data, command); + } + +// READ or WRITE BUFFER command. Initiates reading/loading specified buffer. + + else if ((command == CMD_WRITE_BUF) || (command == CMD_READ_BUF)) { + selectedBuffer = buffer; + maxBufferIdx = cmdLowByte; + if (maxBufferIdx == 0) + maxBufferIdx = 256; + bufferIdx = 0; + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. " + "CMD = %s. Unit = %i. Buffer = %i. Max. Buffer Index = %i.\n", + s100_bus_get_addr(), port, data, cmdTranslate(command), unit, buffer, maxBufferIdx); + } + +// READ STATUS command (read IV byte) + + else if (command == CMD_READ_STATUS) { + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. Unit = %i. Buffer = %i.\n", + s100_bus_get_addr(), port, data, cmdTranslate(command), unit, buffer); + } + +// SET IV byte command + + else if (command == CMD_SET_IV_BYTE) { + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. Unit = %i. Buffer = %i.\n", + s100_bus_get_addr(), port, data, cmdTranslate(command), unit, buffer); + } + +// FORMAT command + + else if (command == CMD_FORMAT) { + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. Unit = %i. Buffer = %i.\n", + s100_bus_get_addr(), port, data, cmdTranslate(command), unit, buffer); + } + +// INITIALIZE command + + else if (command == CMD_INITIALIZE) { + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. MD = %s. Unit = %i. Buffer = %i.\n", + s100_bus_get_addr(), port, data, cmdTranslate(command), unit, buffer); + } else { + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. Unit = %i. Buffer = %i.\n", + s100_bus_get_addr(), port, data, cmdTranslate(command), unit, buffer); + } + + return(0); +} + +/*------------------------------------------------------------ + hdCdata (0xA5) Cdata register. Accessed through the + data register of 4-PIO port 1-B. + + Comments: Returns data from the read buffer +-------------------------------------------------------------*/ +static int32 hdCdata(const int32 port, const int32 io, const int32 data) +{ + if (io == IO_IN) { + if (bufferIdx < maxBufferIdx) { + const int32 result = diskBuf[selectedBuffer][bufferIdx]; + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " IN(%02X = CDATA) = %02x. Buffer = %i. Index = %i.\n", + s100_bus_get_addr(), port, result, selectedBuffer, bufferIdx); + bufferIdx++; + return(result); + } + } + return(0); + +// output operations have no effect +} + + +/*------------------------------------------------------------ + hdAdata (0xA7) ADATA register. Accessed through the + data register of 4-PIO port 2-B. + + Comments: Accepts data into the current buffer + and is also the low byte of a command. +-------------------------------------------------------------*/ +static int32 hdAdata(const int32 port, const int32 io, const int32 data) +{ + if (io == IO_OUT) { + cmdLowByte = data & 0xff; + if (bufferIdx < maxBufferIdx) { + diskBuf[selectedBuffer][bufferIdx] = data; + sim_debug(VERBOSE_MSG, &mhdsk_dev, "HDSK: " ADDRESS_FORMAT + " OUT(%02X = ADATA) = %02x. Buffer = %i. Index = %i.\n", + s100_bus_get_addr(), port, data, selectedBuffer, bufferIdx); + bufferIdx++; + } + } + return(0); +} + +/*-- doRead ------------------------------------------------- + Performs read from MITS Hard Disk image file + + Params: nothing + Uses: selectedTrack, selectedHead, selectedSector + selectedDisk, diskBuf[], mhdsk_dev + Returns: nothing (updates cstat directly) + Comments: +-------------------------------------------------------------*/ +static void doRead(const int32 port, const int32 data, const uint32 command) +{ + UNIT *uptr; + uint32 fileOffset; + + uptr = mhdsk_dev.units + selectedDisk; + fileOffset = HDSK_CYLINDER_SIZE * selectedTrack + + HDSK_TRACK_SIZE * (selectedHead & 0x01) + + HDSK_SECTOR_SIZE * selectedSector; + if (sim_fseek(uptr->fileref, fileOffset, SEEK_SET)) + cstat = CSTAT_NOT_READY; /* seek error */ + else if (sim_fread(diskBuf[selectedBuffer], 1, HDSK_SECTOR_SIZE, uptr->fileref) != HDSK_SECTOR_SIZE) + cstat = CSTAT_NOT_READY; /* write error */ + sim_debug(READ_MSG, &mhdsk_dev, "HDSK%i: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. " + "Track = %i. Sector = %i. Head = %i. Buffer = %i. Status = %i(%s).\n", + selectedDisk, s100_bus_get_addr(), port, data, cmdTranslate(command), + selectedTrack, selectedSector, selectedHead, selectedBuffer, + cstat, (cstat == 0 ? "OK" : (cstat == CSTAT_NOT_READY ? "Not Ready" : "????"))); +} + + +/*-- doWrite ------------------------------------------------ + Performs write to MITS Hard Disk image file + + Params: none + Uses: selectedTrack, selectedHead, selectedSector + selectedDisk, diskBuf[], mhdsk_dev + Returns: nothing (updates cstat directly) + Comments: +-------------------------------------------------------------*/ +static void doWrite(const int32 port, const int32 data, const uint32 command) +{ + UNIT *uptr; + uint32 fileOffset; + + uptr = mhdsk_dev.units + selectedDisk; + if (((uptr->flags) & UNIT_DSK_WLK) == 0) { /* write enabled */ + fileOffset = HDSK_CYLINDER_SIZE * selectedTrack + + HDSK_TRACK_SIZE * (selectedHead & 0x01) + + HDSK_SECTOR_SIZE * selectedSector; + if (sim_fseek(uptr->fileref, fileOffset, SEEK_SET)) + cstat = CSTAT_NOT_READY; /* seek error */ + else if (sim_fwrite(diskBuf[selectedBuffer], 1, HDSK_SECTOR_SIZE, uptr->fileref) != + HDSK_SECTOR_SIZE) + cstat = CSTAT_NOT_READY; /* write error */ + } else + cstat = CSTAT_WRITE_PROTECT; + sim_debug(WRITE_MSG, &mhdsk_dev, "HDSK%i: " ADDRESS_FORMAT + " OUT(%02X = ACMD) = %02x. CMD = %s. " + "Track = %i. Sector = %i. Head = %i. Buffer = %i. Status = %i(%s).\n", + selectedDisk, s100_bus_get_addr(), port, data, cmdTranslate(command), + selectedTrack, selectedSector, selectedHead, selectedBuffer, + cstat, (cstat == 0 ? "OK" : + (cstat == CSTAT_NOT_READY ? "Not Ready" : + (cstat == CSTAT_WRITE_PROTECT ? "Write Protected" : "????")))); +} diff --git a/Altair8800/pt_vdm1.c b/Altair8800/pt_vdm1.c new file mode 100644 index 00000000..c8bbd866 --- /dev/null +++ b/Altair8800/pt_vdm1.c @@ -0,0 +1,702 @@ +/* pt_vdm1.c: Processor Technology VDM-1 + + Copyright (c) 2026 Patrick A. Linstruth + + 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 + PETER SCHORN 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 Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 29-Mar-2026 Initial version + +*/ + +#include "altair8800_defs.h" +#include "s100_bus.h" +#include "s100_cpu.h" +#include "sim_video.h" + +#define DEVICE_NAME "VDM1" + +#define VDM1_MARGIN (5) +#define VDM1_CHAR_XSIZE (9) /* character x size */ +#define VDM1_CHAR_YSIZE (13) /* character y size */ +#define VDM1_COLS (64) /* number of colums */ +#define VDM1_LINES (16) /* number of rows */ +#define VDM1_XSIZE (VDM1_COLS * VDM1_CHAR_XSIZE + VDM1_MARGIN * 2) /* visible width */ +#define VDM1_YSIZE (VDM1_LINES * VDM1_CHAR_YSIZE + VDM1_MARGIN * 2) /* visible height */ +#define VDM1_PIXELS (VDM1_XSIZE * VDM1_YSIZE) /* total number of pixels */ + +#define VDM1_MEM_BASE 0xcc00 +#define VDM1_MEM_SIZE 1024 +#define VDM1_MEM_MASK (1024 - 1) + +#define VDM1_IO_BASE 0xfe +#define VDM1_IO_SIZE 1 + +/* +** PORT ASSIGNMENTS +*/ +#define VDM1_DSTAT_RMSK 0xf0 /* START ROW MASK */ +#define VDM1_DSTAT_CMSK 0x0f /* START COL MASK */ + +/* +** Public VID_DISPLAY for other devices that may want +** to access the video display directly, such as keyboard +** events. +*/ +VID_DISPLAY *vdm1_vptr = NULL; +t_stat (*vdm1_kb_callback)(SIM_KEY_EVENT *kev) = NULL; + +static uint8 vdm1_ram[VDM1_MEM_SIZE]; +static uint8 vdm1_dstat = 0x00; +static t_bool vdm1_dirty = TRUE; +static t_bool vdm1_reverse = FALSE; +static t_bool vdm1_blink = FALSE; +static uint16 vdm1_counter = 0; +static t_bool vdm1_active = FALSE; +static uint32 vdm1_surface[VDM1_PIXELS]; +static uint32 vdm1_palette[2]; + +enum vdm1_switch {VDM1_NONE, + VDM1_NORMAL, VDM1_REVERSE, VDM1_BLINK, VDM1_NOBLINK, + VDM1_MODE1, VDM1_MODE2, VDM1_MODE3, VDM1_MODE4 +}; + +static enum vdm1_switch vdm1_ctrl = VDM1_MODE4; +static enum vdm1_switch vdm1_cursor = VDM1_NOBLINK; +static enum vdm1_switch vdm1_display = VDM1_NORMAL; + +static const uint8 vdm1_charset[128][VDM1_CHAR_YSIZE] = + {{0x00,0x7f,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x7f,0x00,0x00,0x00}, + {0x00,0x7f,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x00}, + {0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x7f,0x00,0x00,0x00}, + {0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x7f,0x00,0x00,0x00}, + {0x00,0x20,0x10,0x08,0x04,0x3e,0x10,0x08,0x04,0x02,0x00,0x00,0x00}, + {0x00,0x7f,0x41,0x63,0x55,0x49,0x55,0x63,0x41,0x7f,0x00,0x00,0x00}, + {0x00,0x00,0x01,0x02,0x04,0x48,0x50,0x60,0x40,0x00,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x41,0x7f,0x14,0x14,0x77,0x00,0x00,0x00}, + {0x00,0x10,0x20,0x7c,0x22,0x11,0x01,0x01,0x01,0x01,0x00,0x00,0x00}, + {0x00,0x00,0x08,0x04,0x02,0x7f,0x02,0x04,0x08,0x00,0x00,0x00,0x00}, + {0x00,0x7f,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x7f,0x00,0x00,0x00}, + {0x00,0x00,0x08,0x08,0x08,0x49,0x2a,0x1c,0x08,0x00,0x00,0x00,0x00}, + {0x00,0x08,0x08,0x2a,0x1c,0x08,0x49,0x2a,0x1c,0x08,0x00,0x00,0x00}, + {0x00,0x00,0x08,0x10,0x20,0x7f,0x20,0x10,0x08,0x00,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x63,0x55,0x49,0x55,0x63,0x22,0x1c,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x49,0x41,0x41,0x22,0x1c,0x00,0x00,0x00}, + {0x00,0x7f,0x41,0x41,0x41,0x7f,0x41,0x41,0x41,0x7f,0x00,0x00,0x00}, + {0x00,0x1c,0x2a,0x49,0x49,0x4f,0x41,0x41,0x22,0x1c,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x4f,0x49,0x49,0x2a,0x1c,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x79,0x49,0x49,0x2a,0x1c,0x00,0x00,0x00}, + {0x00,0x1c,0x2a,0x49,0x49,0x79,0x41,0x41,0x22,0x1c,0x00,0x00,0x00}, + {0x00,0x00,0x11,0x0a,0x04,0x4a,0x51,0x60,0x40,0x00,0x00,0x00,0x00}, + {0x00,0x3e,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x63,0x00,0x00,0x00}, + {0x00,0x01,0x01,0x01,0x01,0x7f,0x01,0x01,0x01,0x01,0x00,0x00,0x00}, + {0x00,0x7f,0x41,0x22,0x14,0x08,0x14,0x22,0x41,0x7f,0x00,0x00,0x00}, + {0x00,0x08,0x08,0x08,0x1c,0x1c,0x08,0x08,0x08,0x08,0x00,0x00,0x00}, + {0x00,0x3c,0x42,0x42,0x40,0x30,0x08,0x08,0x00,0x08,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x7f,0x41,0x41,0x22,0x1c,0x00,0x00,0x00}, + {0x00,0x7f,0x49,0x49,0x49,0x79,0x41,0x41,0x41,0x7f,0x00,0x00,0x00}, + {0x00,0x7f,0x41,0x41,0x41,0x79,0x49,0x49,0x49,0x7f,0x00,0x00,0x00}, + {0x00,0x7f,0x41,0x41,0x41,0x4f,0x49,0x49,0x49,0x7f,0x00,0x00,0x00}, + {0x00,0x7f,0x49,0x49,0x49,0x4f,0x41,0x41,0x41,0x7f,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x08,0x08,0x00,0x00,0x00}, + {0x00,0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x14,0x14,0x14,0x7f,0x14,0x7f,0x14,0x14,0x14,0x00,0x00,0x00}, + {0x00,0x08,0x3f,0x48,0x48,0x3e,0x09,0x09,0x7e,0x08,0x00,0x00,0x00}, + {0x00,0x20,0x51,0x22,0x04,0x08,0x10,0x22,0x45,0x02,0x00,0x00,0x00}, + {0x00,0x38,0x44,0x44,0x28,0x10,0x29,0x46,0x46,0x39,0x00,0x00,0x00}, + {0x00,0x0c,0x0c,0x08,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x04,0x08,0x10,0x10,0x10,0x10,0x10,0x08,0x04,0x00,0x00,0x00}, + {0x00,0x10,0x08,0x04,0x04,0x04,0x04,0x04,0x08,0x10,0x00,0x00,0x00}, + {0x00,0x00,0x08,0x49,0x2a,0x1c,0x2a,0x49,0x08,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x08,0x08,0x08,0x7f,0x08,0x08,0x08,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x10,0x20,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00}, + {0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x00,0x00,0x00,0x00}, + {0x00,0x3e,0x41,0x43,0x45,0x49,0x51,0x61,0x41,0x3e,0x00,0x00,0x00}, + {0x00,0x08,0x18,0x28,0x08,0x08,0x08,0x08,0x08,0x3e,0x00,0x00,0x00}, + {0x00,0x3e,0x41,0x01,0x02,0x1c,0x20,0x40,0x40,0x7f,0x00,0x00,0x00}, + {0x00,0x3e,0x41,0x01,0x01,0x1e,0x01,0x01,0x41,0x3e,0x00,0x00,0x00}, + {0x00,0x02,0x06,0x0a,0x12,0x22,0x42,0x7f,0x02,0x02,0x00,0x00,0x00}, + {0x00,0x7f,0x40,0x40,0x7c,0x02,0x01,0x01,0x42,0x3c,0x00,0x00,0x00}, + {0x00,0x1e,0x20,0x40,0x40,0x7e,0x41,0x41,0x41,0x3e,0x00,0x00,0x00}, + {0x00,0x7f,0x41,0x02,0x04,0x08,0x10,0x10,0x10,0x10,0x00,0x00,0x00}, + {0x00,0x3e,0x41,0x41,0x41,0x3e,0x41,0x41,0x41,0x3e,0x00,0x00,0x00}, + {0x00,0x3e,0x41,0x41,0x41,0x3f,0x01,0x01,0x02,0x3c,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x10,0x20,0x00}, + {0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3e,0x00,0x3e,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x10,0x08,0x04,0x02,0x01,0x02,0x04,0x08,0x10,0x00,0x00,0x00}, + {0x00,0x1e,0x21,0x21,0x01,0x06,0x08,0x08,0x00,0x08,0x00,0x00,0x00}, + {0x00,0x1e,0x21,0x4d,0x55,0x55,0x5e,0x40,0x20,0x1e,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x41,0x7f,0x41,0x41,0x41,0x00,0x00,0x00}, + {0x00,0x7e,0x21,0x21,0x21,0x3e,0x21,0x21,0x21,0x7e,0x00,0x00,0x00}, + {0x00,0x1e,0x21,0x40,0x40,0x40,0x40,0x40,0x21,0x1e,0x00,0x00,0x00}, + {0x00,0x7c,0x22,0x21,0x21,0x21,0x21,0x21,0x22,0x7c,0x00,0x00,0x00}, + {0x00,0x7f,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x7f,0x00,0x00,0x00}, + {0x00,0x7f,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x40,0x00,0x00,0x00}, + {0x00,0x1e,0x21,0x40,0x40,0x40,0x4f,0x41,0x21,0x1e,0x00,0x00,0x00}, + {0x00,0x41,0x41,0x41,0x41,0x7f,0x41,0x41,0x41,0x41,0x00,0x00,0x00}, + {0x00,0x3e,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3e,0x00,0x00,0x00}, + {0x00,0x1f,0x04,0x04,0x04,0x04,0x04,0x04,0x44,0x38,0x00,0x00,0x00}, + {0x00,0x41,0x42,0x44,0x48,0x50,0x68,0x44,0x42,0x41,0x00,0x00,0x00}, + {0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7f,0x00,0x00,0x00}, + {0x00,0x41,0x63,0x55,0x49,0x49,0x41,0x41,0x41,0x41,0x00,0x00,0x00}, + {0x00,0x41,0x61,0x51,0x49,0x45,0x43,0x41,0x41,0x41,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x41,0x41,0x41,0x22,0x1c,0x00,0x00,0x00}, + {0x00,0x7e,0x41,0x41,0x41,0x7e,0x40,0x40,0x40,0x40,0x00,0x00,0x00}, + {0x00,0x1c,0x22,0x41,0x41,0x41,0x49,0x45,0x22,0x1d,0x00,0x00,0x00}, + {0x00,0x7e,0x41,0x41,0x41,0x7e,0x48,0x44,0x42,0x41,0x00,0x00,0x00}, + {0x00,0x3e,0x41,0x40,0x40,0x3e,0x01,0x01,0x41,0x3e,0x00,0x00,0x00}, + {0x00,0x7f,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00}, + {0x00,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x3e,0x00,0x00,0x00}, + {0x00,0x41,0x41,0x41,0x22,0x22,0x14,0x14,0x08,0x08,0x00,0x00,0x00}, + {0x00,0x41,0x41,0x41,0x41,0x49,0x49,0x55,0x63,0x41,0x00,0x00,0x00}, + {0x00,0x41,0x41,0x22,0x14,0x08,0x14,0x22,0x41,0x41,0x00,0x00,0x00}, + {0x00,0x41,0x41,0x22,0x14,0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00}, + {0x00,0x7f,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x7f,0x00,0x00,0x00}, + {0x00,0x3c,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c,0x00,0x00,0x00}, + {0x00,0x00,0x40,0x20,0x10,0x08,0x04,0x02,0x01,0x00,0x00,0x00,0x00}, + {0x00,0x3c,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x3c,0x00,0x00,0x00}, + {0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00,0x00}, + {0x00,0x18,0x18,0x08,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3c,0x02,0x3e,0x42,0x42,0x3d,0x00,0x00,0x00}, + {0x00,0x40,0x40,0x40,0x5c,0x62,0x42,0x42,0x62,0x5c,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3c,0x42,0x40,0x40,0x42,0x3c,0x00,0x00,0x00}, + {0x00,0x02,0x02,0x02,0x3a,0x46,0x42,0x42,0x46,0x3a,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3c,0x42,0x7e,0x40,0x40,0x3c,0x00,0x00,0x00}, + {0x00,0x0c,0x12,0x10,0x10,0x7c,0x10,0x10,0x10,0x10,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3a,0x46,0x42,0x46,0x3a,0x02,0x02,0x42,0x3c}, + {0x00,0x40,0x40,0x40,0x5c,0x62,0x42,0x42,0x42,0x42,0x00,0x00,0x00}, + {0x00,0x00,0x08,0x00,0x18,0x08,0x08,0x08,0x08,0x1c,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x06,0x02,0x02,0x02,0x02,0x02,0x02,0x22,0x1c}, + {0x00,0x40,0x40,0x40,0x44,0x48,0x50,0x68,0x44,0x42,0x00,0x00,0x00}, + {0x00,0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x1c,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x76,0x49,0x49,0x49,0x49,0x49,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x5c,0x62,0x42,0x42,0x42,0x42,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3c,0x42,0x42,0x42,0x42,0x3c,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x5c,0x62,0x42,0x42,0x62,0x5c,0x40,0x40,0x40}, + {0x00,0x00,0x00,0x00,0x3a,0x46,0x42,0x42,0x46,0x3a,0x02,0x02,0x02}, + {0x00,0x00,0x00,0x00,0x5c,0x62,0x40,0x40,0x40,0x40,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x3c,0x42,0x30,0x0c,0x42,0x3c,0x00,0x00,0x00}, + {0x00,0x00,0x10,0x10,0x7c,0x10,0x10,0x10,0x12,0x0c,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x46,0x3a,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x41,0x41,0x41,0x22,0x14,0x08,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x41,0x49,0x49,0x49,0x49,0x36,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x42,0x24,0x18,0x18,0x24,0x42,0x00,0x00,0x00}, + {0x00,0x00,0x00,0x00,0x42,0x42,0x42,0x42,0x46,0x3a,0x02,0x42,0x3c}, + {0x00,0x00,0x00,0x00,0x7e,0x04,0x08,0x10,0x20,0x7e,0x00,0x00,0x00}, + {0x00,0x0e,0x10,0x10,0x10,0x20,0x10,0x10,0x10,0x0e,0x00,0x00,0x00}, + {0x00,0x08,0x08,0x08,0x00,0x00,0x08,0x08,0x08,0x00,0x00,0x00,0x00}, + {0x00,0x18,0x04,0x04,0x04,0x02,0x04,0x04,0x04,0x18,0x00,0x00,0x00}, + {0x00,0x30,0x49,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x24,0x49,0x12,0x24,0x49,0x12,0x24,0x49,0x12,0x00,0x00,0x00}}; + +/* Debugging Bitmaps */ + +#define DBG_REG 0x0001 /* registers */ + +extern t_stat set_membase(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat show_membase(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +extern t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +extern t_stat exdep_cmd(int32 flag, CONST char *cptr); + +static t_stat vdm1_svc(UNIT *uptr); +static t_stat vdm1_reset(DEVICE *dptr); +static t_stat vdm1_boot(int32 unitno, DEVICE *dptr); +static t_stat vdm1_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static int32 vdm1_io(const int32 port, const int32 io, const int32 data); +static int32 vdm1_mem(int32 addr, int32 rw, int32 data); +static const char *vdm1_description(DEVICE *dptr); +static void vdm1_refresh(void); +static void vdm1_render(void); +static void vdm1_render_char(uint8 byte, uint8 x, uint8 y); +static t_stat vdm1_set_ctrl(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat vdm1_show_ctrl(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat vdm1_set_cursor(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat vdm1_show_cursor(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat vdm1_set_display(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat vdm1_show_display(FILE *st, UNIT *uptr, int32 val, CONST void *desc); + +/* VDM1 data structures + + vdm1_dev VDM1 device descriptor + vdm1_unit VDM1 unit descriptor + vdm1_reg VDM1 register list +*/ + +static RES vdm1_res = { VDM1_IO_BASE, VDM1_IO_SIZE, VDM1_MEM_BASE, VDM1_MEM_SIZE }; + +UNIT vdm1_unit = { + UDATA (&vdm1_svc, 0, 0), 16666 /* 60 fps */ +}; + +REG vdm1_reg[] = { + { HRDATAD (DSTAT, vdm1_dstat, 8, "VDM-1 display parameter register"), }, + { HRDATAD (DIRTY, vdm1_dirty, 1, "VDM-1 dirty register"), }, + { HRDATAD (BLINK, vdm1_blink, 1, "VDM-1 blink register"), }, + { NULL } +}; + +DEBTAB vdm1_debug[] = { + { "REG", DBG_REG, "Register activity" }, + { "VIDEO", SIM_VID_DBG_VIDEO, "Video activity" }, + { 0 } +}; + + +MTAB vdm1_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "VDM-1 base I/O address" }, + { MTAB_XTD|MTAB_VDV, 0, "MEMBASE", "MEMBASE", + &set_membase, &show_membase, NULL, "VDM-1 base memory address" }, + { MTAB_XTD|MTAB_VDV, 0, "CTRL", "CTRL", + &vdm1_set_ctrl, &vdm1_show_ctrl, NULL, "VDM-1 control character switches" }, + { MTAB_XTD|MTAB_VDV, 0, "CURSOR", "CURSOR", + &vdm1_set_cursor, &vdm1_show_cursor, NULL, "VDM-1 cursor switches" }, + { MTAB_XTD|MTAB_VDV, 0, "DISPLAY", "DISPLAY", + &vdm1_set_display, &vdm1_show_display, NULL, "VDM-1 display switches" }, + { 0 } +}; +DEVICE vdm1_dev = { + DEVICE_NAME, &vdm1_unit, vdm1_reg, vdm1_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &vdm1_reset, + &vdm1_boot, NULL, NULL, + &vdm1_res, DEV_DEBUG | DEV_DIS | DEV_DISABLE, 0, + vdm1_debug, NULL, NULL, &vdm1_help, NULL, NULL, + &vdm1_description +}; + +/* VDM1 routines + + vdm1_svc process event + vdm1_reset process reset +*/ + +t_stat vdm1_svc(UNIT *uptr) +{ + SIM_KEY_EVENT kev; + + vdm1_counter++; + + /* Handle blink */ + if ((vdm1_counter % 10 == 0) && (vdm1_cursor == VDM1_BLINK)) { + vdm1_blink = !vdm1_blink; + vdm1_dirty = TRUE; + } + + if (vdm1_dirty) { + vdm1_refresh(); + vdm1_dirty = TRUE; + } + + if (vdm1_kb_callback != NULL) { + if (vid_poll_kb(&kev) == SCPE_OK) { + (*vdm1_kb_callback)(&kev); + } + } + + sim_activate_after_abs(uptr, uptr->wait); + + return SCPE_OK; +} + +t_stat vdm1_reset(DEVICE *dptr) +{ + RES *res; + t_stat r; + int i; + + res = (RES *) dptr->ctxt; + + if (dptr->flags & DEV_DIS) { + s100_bus_remmem(res->mem_base, res->mem_size, &vdm1_mem); + s100_bus_remio(res->io_base, res->io_size, &vdm1_io); + + sim_cancel(&vdm1_unit); + + if (vdm1_active) { + vdm1_active = FALSE; + return vid_close(); + } + + return SCPE_OK; + } + + s100_bus_addmem(res->mem_base, res->mem_size, &vdm1_mem, DEVICE_NAME); + s100_bus_addio(res->io_base, res->io_size, &vdm1_io, DEVICE_NAME); + + if (!vdm1_active) { + r = vid_open_window(&vdm1_vptr, &vdm1_dev, "Display", VDM1_XSIZE, VDM1_YSIZE, SIM_VID_IGNORE_VBAR | SIM_VID_RESIZABLE); /* video buffer size */ + + if (r != SCPE_OK) { + return r; + } + + vid_set_window_size(vdm1_vptr, VDM1_XSIZE, VDM1_YSIZE * 2); + + vdm1_palette[0] = vid_map_rgb_window(vdm1_vptr, 0x00, 0x00, 0x00); + vdm1_palette[1] = vid_map_rgb_window(vdm1_vptr, 0x00, 0xFF, 0x30); + + for (i = 0; i < VDM1_PIXELS; i++) { + vdm1_surface[i] = vdm1_palette[0]; + } + + vdm1_active = TRUE; + } + + sim_activate_after_abs(&vdm1_unit, 16666); + + return SCPE_OK; +} + +static t_stat vdm1_boot(int32 unitno, DEVICE *dptr) +{ + exdep_cmd(EX_D, "-m 00 MVI A,0"); + exdep_cmd(EX_D, "-m 02 OUT 0FEH"); + exdep_cmd(EX_D, "-m 04 MVI C,0"); + exdep_cmd(EX_D, "-m 06 MVI B,0"); + exdep_cmd(EX_D, "-m 08 LXI H,0CC00H"); + exdep_cmd(EX_D, "-m 0B DCR B"); + exdep_cmd(EX_D, "-m 0C MOV M,B"); + exdep_cmd(EX_D, "-m 0D INX H"); + exdep_cmd(EX_D, "-m 0E MOV A,H"); + exdep_cmd(EX_D, "-m 0F CPI 0D0H"); + exdep_cmd(EX_D, "-m 11 JNZ 000BH"); + exdep_cmd(EX_D, "-m 14 DCX H"); + exdep_cmd(EX_D, "-m 15 MOV A,H"); + exdep_cmd(EX_D, "-m 16 ORA A"); + exdep_cmd(EX_D, "-m 17 JNZ 0014H"); + exdep_cmd(EX_D, "-m 1A INR C"); + exdep_cmd(EX_D, "-m 1B MOV B,C"); + exdep_cmd(EX_D, "-m 1C JMP 0008H"); + + cpu_set_pc_loc(0x0000); + + return SCPE_OK; +} + +static int32 vdm1_io(const int32 port, const int32 io, const int32 data) { + if (io == 1) { + vdm1_dstat = data & 0xff; + } + return 0xff; +} + +/* + * VDM-1 1K Video Memory (16 x 64 characters) + */ +static int32 vdm1_mem(int32 addr, int32 rw, int32 data) +{ + + if (rw == 0) { + data = vdm1_ram[addr & VDM1_MEM_MASK]; + } + else { + vdm1_ram[addr & VDM1_MEM_MASK] = data; + vdm1_dirty = TRUE; + } + + return data; +} + +t_stat vdm1_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf(st, "\nThe VDM-1 has several switches that control the video display:\n\n"); + + fprintf(st, "SET VDM1 CTRL=MODEx\n"); + fprintf(st, "MODE1 - All control characters suppressed. Only cursor blocks\n"); + fprintf(st, " are displayed. CR and VT enabled.\n"); + fprintf(st, "MODE2 - Control characters blanked. CR and VT enabled.\n"); + fprintf(st, "MODE3 - Control characters displayable. CR and VT enabled.\n"); + fprintf(st, "MODE4 - Control characters displayable. CR and VT disabled. (default)\n\n"); + + fprintf(st, "SET VDM1 CURSOR=NONE,BLINK,NOBLINK\n"); + fprintf(st, "NONE - All cursors suppressed.\n"); + fprintf(st, "BLINK - Blinking cursor.\n"); + fprintf(st, "NOBLINK - Non-blinking cursor. (default)\n\n"); + + fprintf(st, "SET VDM1 DISPLAY=NONE,NORMAL,REVERSE\n"); + fprintf(st, "NONE - No display.\n"); + fprintf(st, "NORMAL - Normal video. (default)\n"); + fprintf(st, "REVERSE - Reverse video.\n\n"); + + fprintf(st, "VDM-1 test program displays all characters on the screen:\n"); + fprintf(st, "BOOT VDM1\n\n"); + + return SCPE_OK; +} + +const char *vdm1_description (DEVICE *dptr) +{ + return "Processor Technology VDM-1 Display"; +} + +/* + * Draw and refresh the screen in the video window + */ +static void vdm1_refresh(void) { + if (vdm1_active) { + vdm1_render(); + vid_draw_window(vdm1_vptr, VDM1_MARGIN, VDM1_MARGIN, VDM1_XSIZE, VDM1_YSIZE, vdm1_surface); + vid_refresh_window(vdm1_vptr); + } +} + +/* + * The VDM-1 display is make up of 16 64-character rows. Each character occupies + * 1 byte in memory from CC00-CFFF. + */ +static void vdm1_render(void) +{ + uint8 x,y,s,c,c1; + int addr = 0; + t_bool eol_blank = FALSE; + t_bool eos_blank = FALSE; + + addr += (vdm1_dstat & VDM1_DSTAT_CMSK) * VDM1_COLS; + s = (vdm1_dstat & VDM1_DSTAT_RMSK) >> 4; /* Shadowing */ + + for (y = 0; y < VDM1_LINES; y++) { + for (x = 0; x < VDM1_COLS; x++) { + + c = vdm1_ram[addr++]; + c1 = c & 0x7f; + + /* EOL and EOS blanking */ + if (c1 == 0x0d && (vdm1_ctrl == VDM1_MODE2 || vdm1_ctrl == VDM1_MODE3)) { // CR + eol_blank = TRUE; + } + else if (c1 == 0x0b && (vdm1_ctrl == VDM1_MODE2 || vdm1_ctrl == VDM1_MODE3)) { // VT + eos_blank = TRUE; + } + + /* Blanking control */ + if (vdm1_display == VDM1_NONE || eol_blank || eos_blank || y < s) { + c = ' '; + } + + /* Control character suppression */ + if ((c1 < 0x20 || c1 == 0x7f) && (vdm1_ctrl == VDM1_MODE1 || vdm1_ctrl == VDM1_MODE2)) { + c = ' '; + } + + vdm1_render_char(c, x, y); + + if (addr == VDM1_MEM_SIZE) { + addr = 0; + } + } + } +} + +/* + * The VDM-1 rendered characters one scan line at a time. + * The simulator renders an entire character at a time by + * rendering each character in a rectangle area in the + * video surface buffer. + */ +static void vdm1_render_char(uint8 byte, uint8 x, uint8 y) +{ + uint8 rx,ry,c; + int start,pixel; + + start = (x * VDM1_CHAR_XSIZE) + (VDM1_XSIZE * VDM1_CHAR_YSIZE * y); + + for (ry = 0; ry < VDM1_CHAR_YSIZE; ry++) { + + pixel = start + (VDM1_XSIZE * ry); + + c = vdm1_charset[byte & 0x7f][ry]; + + if (!vdm1_blink && byte & 0x80) { + c = ~(c & 0xff); + } + + if (vdm1_display == VDM1_REVERSE) { + c = ~(c & 0xff); + } + + for (rx = 0; rx < VDM1_CHAR_XSIZE - 1; rx++) { + vdm1_surface[pixel++] = vdm1_palette[c & (0x80 >> rx) ? !vdm1_reverse : vdm1_reverse]; + } + + vdm1_surface[pixel++] = vdm1_palette[(c & 0x80) ? !vdm1_reverse : vdm1_reverse]; + } +} + +static t_stat vdm1_set_ctrl(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "MODE1", strlen(cptr))) { + vdm1_ctrl = VDM1_MODE1; + } else if (!strncmp(cptr, "MODE2", strlen(cptr))) { + vdm1_ctrl = VDM1_MODE2; + } else if (!strncmp(cptr, "MODE3", strlen(cptr))) { + vdm1_ctrl = VDM1_MODE3; + } else if (!strncmp(cptr, "MODE4", strlen(cptr))) { + vdm1_ctrl = VDM1_MODE4; + } else { + return SCPE_ARG; + } + + vdm1_dirty = TRUE; + + return SCPE_OK; +} + +static t_stat vdm1_show_ctrl(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (!st) return SCPE_IERR; + + fprintf(st, "CTRL="); + + switch (vdm1_ctrl) { + case VDM1_MODE1: + fprintf(st, "MODE1"); + break; + + case VDM1_MODE2: + fprintf(st, "MODE2"); + break; + + case VDM1_MODE3: + fprintf(st, "MODE3"); + break; + + case VDM1_MODE4: + fprintf(st, "MODE4"); + break; + + default: + fprintf(st, "UNKNOWN"); + break; + } + + return SCPE_OK; +} + +static t_stat vdm1_set_cursor(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "NONE", strlen(cptr))) { + vdm1_cursor = VDM1_NONE; + } else if (!strncmp(cptr, "BLINK", strlen(cptr))) { + vdm1_cursor = VDM1_BLINK; + } else if (!strncmp(cptr, "NOBLINK", strlen(cptr))) { + vdm1_cursor = VDM1_NOBLINK; + vdm1_blink = FALSE; + } else { + return SCPE_ARG; + } + + vdm1_dirty = TRUE; + + return SCPE_OK; +} + +static t_stat vdm1_show_cursor(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (!st) return SCPE_IERR; + + fprintf(st, "CURSOR="); + + switch (vdm1_cursor) { + case VDM1_NONE: + fprintf(st, "NONE"); + break; + + case VDM1_BLINK: + fprintf(st, "BLINK"); + break; + + case VDM1_NOBLINK: + fprintf(st, "NOBLINK"); + break; + + default: + fprintf(st, "UNKNOWN"); + break; + } + + return SCPE_OK; +} + +static t_stat vdm1_set_display(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "NONE", strlen(cptr))) { + vdm1_display = VDM1_NONE; + } else if (!strncmp(cptr, "NORMAL", strlen(cptr))) { + vdm1_display = VDM1_NORMAL; + } else if (!strncmp(cptr, "REVERSE", strlen(cptr))) { + vdm1_display = VDM1_REVERSE; + } else { + return SCPE_ARG; + } + + vdm1_dirty = TRUE; + + return SCPE_OK; +} + +static t_stat vdm1_show_display(FILE *st, UNIT *uptr, int32 val, CONST void *desc) +{ + if (!st) return SCPE_IERR; + + fprintf(st, "DISPLAY="); + + switch (vdm1_display) { + case VDM1_NONE: + fprintf(st, "NONE"); + break; + + case VDM1_NORMAL: + fprintf(st, "NORMAL"); + break; + + case VDM1_REVERSE: + fprintf(st, "REVERSE"); + break; + + default: + fprintf(st, "UNKNOWN"); + break; + } + + return SCPE_OK; +} + diff --git a/Altair8800/s100_bus.c b/Altair8800/s100_bus.c index 826f69d4..f988cec7 100644 --- a/Altair8800/s100_bus.c +++ b/Altair8800/s100_bus.c @@ -33,7 +33,10 @@ #include "s100_z80.h" #include "s100_bus.h" +#define CLK_DELAY 5000 /* 100 Hz */ + static t_stat bus_reset (DEVICE *dptr); +static t_stat bus_svc (UNIT *uptr); static t_stat bus_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); static t_stat bus_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); static t_stat bus_cmd_memory (int32 flag, const char *cptr); @@ -44,6 +47,10 @@ static t_stat bus_hexload_command (int32 flag, const char *cptr); static t_stat bus_hexsave_command (int32 flag, const char *cptr); static t_stat hexload (const char *filename, t_addr bias); static t_stat hexsave (FILE *outFile, t_addr start, t_addr end); +static t_stat bus_ddt_command (int32 flag, const char *cptr); + +static int32 bus_clk_tps = 100; /* ticks/second */ +static int32 bus_tmr_poll = 0; /* pgm timer poll */ static MDEV mdev_table[MAXPAGE]; /* Active memory table */ static MDEV mdev_dflt; /* Default memory table */ @@ -74,7 +81,7 @@ static const char* bus_description(DEVICE *dptr) { } static UNIT bus_unit = { - UDATA (NULL, 0, 0) + UDATA (&bus_svc, UNIT_IDLE, 0) }; static REG bus_reg[] = { @@ -117,17 +124,20 @@ static CTAB bus_cmd_tbl[] = { { "MEM", &bus_cmd_memory, 0, "MEM
Dump a block of memory\n" }, { "HEXLOAD", &bus_hexload_command, 0, "HEXLOAD [fname] Load Intel hex file\n" }, { "HEXSAVE", &bus_hexsave_command, 0, "HEXSAVE [fname] [start-end] Save Intel hex file\n" }, + { "DDT", &bus_ddt_command, 0, "DDT Control DDT-style output\n" }, { NULL, NULL, 0, NULL } }; -/* bus reset */ -static t_stat bus_reset(DEVICE *dptr) { +/* BUS reset */ +static t_stat bus_reset(DEVICE *dptr) +{ int i; + int32 actual_tps; - if (poc) { + if (poc) { /* Powerup? */ sim_vm_cmd = bus_cmd_tbl; - /* Clear MEM and IO table */ + /* Clear MEM and IO table */ for (i = 0; i < MAXPAGE; i++) { mdev_table[i].routine = &nulldev; mdev_table[i].name = "nulldev"; @@ -141,9 +151,26 @@ static t_stat bus_reset(DEVICE *dptr) { idev_out[i].name = "nulldev"; } + /* Enable DDT-style output */ + bus_ddt_command(0, "ENABLE"); + poc = FALSE; } + actual_tps = sim_rtcn_init_unit(&bus_unit, CLK_DELAY, S100_CLK_TIMER); + + sim_activate(&bus_unit, actual_tps ? actual_tps : CLK_DELAY); + + return SCPE_OK; +} + +/* Altair8800 100Hz timer */ +static t_stat bus_svc(UNIT *uptr) +{ + bus_tmr_poll = sim_rtcn_calb(bus_clk_tps, S100_CLK_TIMER); /* calibrate 100Hz clock */ + + sim_activate_after(uptr, 1000000 / bus_clk_tps); /* reactivate unit */ + return SCPE_OK; } @@ -486,12 +513,7 @@ static t_stat bus_cmd_memory(int32 flag, const char *cptr) while (disp_addr <= last && disp_addr <= ADDRMASK) { if (!(disp_addr & 0x0f)) { - if (ADDRMASK+1 <= 0x10000) { - sim_printf("%04X ", disp_addr); - } - else { - sim_printf("%02X:%04X ", disp_addr >> 16, disp_addr & 0xffff); - } + sim_printf("%04X: ", disp_addr); } if (disp_addr < lo || disp_addr > hi) { @@ -504,6 +526,10 @@ static t_stat bus_cmd_memory(int32 flag, const char *cptr) abuf[disp_addr & 0x0f] = sim_isprint(byte) ? byte : '.'; } + if ((disp_addr & 0x0007) == 0x0007) { + sim_printf(" "); + } + if ((disp_addr & 0x000f) == 0x000f) { sim_printf("%16.16s\n", abuf); } @@ -983,6 +1009,63 @@ t_stat s100_bus_poll_kbd(UNIT *uptr) return SCPE_OK; } +static t_stat bus_ddt_command(int32 flag, const char *cptr) +{ + char arg[4*CBUFSIZE]; + int saved_sim_switches = sim_switches; + + sim_switches = 0; + + GET_SWITCHES(cptr); /* get switches */ + + if (*cptr == 0) { /* must be more */ + sim_switches = saved_sim_switches; + return SCPE_2FARG; + } + + cptr = get_glyph_quoted(cptr, arg, 0); /* get argument */ + sim_trim_endspc(arg); + + if (toupper(*arg) == 'D') { /* disable DDT-style output */ + set_cmd(0, "NOON"); + on_cmd(0, "1"); + on_cmd(0, "2"); + on_cmd(0, "3"); + on_cmd(0, "4"); + on_cmd(0, "5"); + on_cmd(0, "STEP"); + on_cmd(0, "STOP"); + set_cmd(0, "ON NOINHERIT"); + set_cmd(0, "ENV S=S"); + set_cmd(0, "ENV G=G"); + set_cmd(0, "ENV C=C"); + set_cmd(0, "ENV N=N"); + set_cmd(0, "ENV BOOT=BOOT"); + } else { /* enable DDT-style output */ + set_cmd(0, "ON"); + on_cmd(0, "1 ECHOF -n \"%TSTATUS% \";REG"); + on_cmd(0, "2 ECHOF -n \"%TSTATUS% \";REG"); + on_cmd(0, "3 ECHOF -n \"%TSTATUS% \";REG"); + on_cmd(0, "4 ECHOF -n \"%TSTATUS% \";REG"); + on_cmd(0, "5 ECHOF -n \"%TSTATUS% \";REG"); + on_cmd(0, "STEP ECHOF -n \"%TSTATUS% \";REG"); + on_cmd(0, "STOP ECHOF -n \"\\n%TSTATUS% \";REG"); + set_cmd(0, "ON INHERIT"); + set_cmd(0, "ENV S=S -q"); + set_cmd(0, "ENV G=G -q"); + set_cmd(0, "ENV C=C -q"); + set_cmd(0, "ENV N=N -q"); + set_cmd(0, "ENV BOOT=BOOT -q"); + } + + /* Make "D" execute "DEPOSIT", not "DDT" */ + set_cmd(0, "ENV D=DEP"); + + sim_switches = saved_sim_switches; + + return SCPE_OK; +} + static t_stat bus_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) { fprintf (st, "\nAltair 8800 Bus (%s)\n", dptr->name); diff --git a/Altair8800/s100_bus.h b/Altair8800/s100_bus.h index f99c6025..1ee4a131 100644 --- a/Altair8800/s100_bus.h +++ b/Altair8800/s100_bus.h @@ -63,6 +63,9 @@ #define ADDRESS_FORMAT "[0x%08x]" +/* Altair8800 calibrated timer */ +#define S100_CLK_TIMER 0 + /* This is the I/O configuration table. There are 255 possible device addresses, if a device is plugged to a port it's routine address is here, 'nulldev' means no device is available diff --git a/Altair8800/s100_cpu.c b/Altair8800/s100_cpu.c index 8443383a..cea5b4e7 100644 --- a/Altair8800/s100_cpu.c +++ b/Altair8800/s100_cpu.c @@ -37,6 +37,7 @@ REG *sim_PC; static int32 poc = TRUE; /* Power On Clear */ +static int32 idle = FALSE; /* Idle has been requested */ static ChipType cpu_type = CHIP_TYPE_8080; @@ -169,6 +170,30 @@ char * cpu_get_chipname(ChipType type) return cpu_chipname[type]; } +void cpu_idle() +{ + if (sim_idle_enab && idle) { + sim_idle(S100_CLK_TIMER, FALSE); + + idle = FALSE; + } +} + +void cpu_req_idle() +{ + idle = TRUE; +} + +void cpu_clr_idle() +{ + idle = FALSE; +} + +int cpu_get_idle() +{ + return idle; +} + t_stat sim_instr() { t_stat reason = SCPE_NXDEV; @@ -190,6 +215,18 @@ static void cpu_set_pc(REG *reg) sim_PC = reg; } +t_addr cpu_set_pc_loc(t_addr loc) +{ + t_addr old = 0x0000; + + if (sim_PC != NULL) { + old = *((int32 *) sim_PC->loc); + *((int32 *) sim_PC->loc) = loc & ADDRMASK; + } + + return old; +} + static void cpu_set_pc_value(t_value (*routine)(void)) { sim_vm_pc_value = routine; diff --git a/Altair8800/s100_cpu.h b/Altair8800/s100_cpu.h index 183e41e8..58ab1553 100644 --- a/Altair8800/s100_cpu.h +++ b/Altair8800/s100_cpu.h @@ -58,6 +58,11 @@ typedef struct { extern void cpu_set_chiptype(ChipType type); extern ChipType cpu_get_chiptype(void); extern char * cpu_get_chipname(ChipType type); +extern t_addr cpu_set_pc_loc(t_addr loc); +extern void cpu_idle(); +extern void cpu_req_idle(); +extern void cpu_clr_idle(); +extern int cpu_get_idle(); #endif diff --git a/Altair8800/s100_po.c b/Altair8800/s100_po.c index 7f7a0363..75cd94e9 100644 --- a/Altair8800/s100_po.c +++ b/Altair8800/s100_po.c @@ -102,7 +102,7 @@ static int32 po_io(const int32 addr, const int32 rw, const int32 data) PO = data & DATAMASK; if (po_unit.flags & UNIT_PO_VERBOSE) { - sim_printf("\n[PO %02X]\n", ~data & DATAMASK); /* IMSAI FP is Inverted */ + sim_printf("\n[PO %02X]\n", (~data) & DATAMASK); /* IMSAI FP is Inverted */ } } @@ -117,6 +117,15 @@ static t_stat po_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const fprint_show_help (st, dptr); fprint_reg_help (st, dptr); + fprintf(st, "\n"); + fprintf(st, "The IMSAI front panel has a Programmed Output port (FFh) with LED indicators.\n"); + fprintf(st, "The Altair does not have this feature. The %s device adds this feature to the\n", dptr->name); + fprintf(st, "simulator. When the device is enabled, any OUT instruction to I/O port FF\n"); + fprintf(st, "will display '[PO XX]' on the console. Note that the value displayed will be\n"); + fprintf(st, "inverted to match the functionality of the IMSAI.\n"); + fprintf(st, "\n"); + fprintf(st, "SET PO QUIET will suppress the Programmed Output messages.\n"); + return SCPE_OK; } diff --git a/Altair8800/s100_z80.c b/Altair8800/s100_z80.c index 5d794827..e3656d40 100644 --- a/Altair8800/s100_z80.c +++ b/Altair8800/s100_z80.c @@ -236,6 +236,8 @@ static MTAB z80_mod[] = { NULL, NULL, "Chooses 8080 CPU"}, { MTAB_XTD | MTAB_VDV, CHIP_TYPE_Z80, NULL, "Z80", &z80_set_chiptype, NULL, NULL, "Chooses Z80 CPU" }, + { MTAB_XTD|MTAB_VDV, 0, "IDLE", "IDLE", &sim_set_idle, &sim_show_idle, NULL, "Enable/Display idle detection" }, + { MTAB_XTD|MTAB_VDV, 0, NULL, "NOIDLE", &sim_clr_idle, NULL, NULL, "Disable idle detection" }, { UNIT_Z80_OPSTOP, UNIT_Z80_OPSTOP, "ITRAP", "ITRAP", NULL, &chip_show, NULL, "Stop on illegal instruction" }, { UNIT_Z80_OPSTOP, 0, "NOITRAP", "NOITRAP", NULL, &chip_show, @@ -2364,7 +2366,7 @@ t_stat z80_instr(void) reason = STOP_HALT; goto end_decode; } - sim_interval = 0; + cpu_req_idle(); break; case 0x77: /* LD (HL),A */ @@ -5638,6 +5640,8 @@ t_stat z80_instr(void) PC &= ADDRMASK; /* reestablish invariant */ sim_interval--; + + cpu_idle(); } end_decode: @@ -5682,7 +5686,9 @@ static t_stat z80_reset(DEVICE *dptr) poc = TRUE; } else { - if (poc) { /* First time reset? */ + if (poc) { /* Powerup? */ + sim_set_idle(&z80_unit, 0, "", NULL); + poc = FALSE; } } diff --git a/Altair8800/sds_vfii.c b/Altair8800/sds_vfii.c index 8af1b64d..446de5a9 100644 --- a/Altair8800/sds_vfii.c +++ b/Altair8800/sds_vfii.c @@ -32,6 +32,7 @@ #include "altair8800_sys.h" #include "altair8800_dsk.h" #include "s100_bus.h" +#include "s100_cpu.h" #include "sds_vfii.h" #include "wd_17xx.h" @@ -41,8 +42,7 @@ static WD17XX_INFO *wd17xx = NULL; #define VFII_WD17XX_OFFSET 1 -static int32 poc = TRUE; /* Power On Clear */ - +static int32 poc = TRUE; static uint8 drv_sel = 0; static uint8 vfii_creg = 0; @@ -67,7 +67,6 @@ static UNIT vfii_unit[VFII_NUM_DRIVES] = { }; static REG vfii_reg[] = { - { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, { DRDATAD (DRVSEL, drv_sel, 8, "Drive select"), }, { NULL } }; @@ -139,7 +138,7 @@ static t_stat vfii_reset(DEVICE *dptr) poc = TRUE; } else { - if (poc) { + if (poc) { /* Powerup? */ for (i = 0; i < VFII_NUM_DRIVES; i++) { vfii_unit[i].dptr = dptr; dsk_init(&dsk_info[i], &vfii_unit[i], 77, 1, 0); @@ -182,7 +181,7 @@ static t_stat vfii_boot(int32 unitno, DEVICE *dptr) sim_debug(STATUS_MSG, &vfii_dev, DEV_NAME ": Booting Controller at 0x%04x\n", 0xE000); - s100_bus_set_addr(0xE000); + cpu_set_pc_loc(0xE000); return SCPE_OK; } diff --git a/Visual Studio Projects/Altair8800.vcproj b/Visual Studio Projects/Altair8800.vcproj index 4711b744..ab4cd6c9 100644 --- a/Visual Studio Projects/Altair8800.vcproj +++ b/Visual Studio Projects/Altair8800.vcproj @@ -207,6 +207,14 @@ RelativePath="..\Altair8800\cromemco_dazzler.c" > + + + + @@ -219,10 +227,18 @@ RelativePath="..\Altair8800\mits_dsk.c" > + + + + @@ -348,6 +364,10 @@ RelativePath="..\Altair8800\cromemco_dazzler.h" > + + diff --git a/makefile b/makefile index 93921ec3..1007966c 100644 --- a/makefile +++ b/makefile @@ -2250,10 +2250,14 @@ ALTAIR8800 = \ ${ALTAIR8800D}/s100_rom.c \ ${ALTAIR8800D}/s100_z80.c \ ${ALTAIR8800D}/cromemco_dazzler.c \ + ${ALTAIR8800D}/farmtek_fdcplus.c \ + ${ALTAIR8800D}/icom_fd3x12.c \ ${ALTAIR8800D}/mits_2sio.c \ ${ALTAIR8800D}/mits_acr.c \ ${ALTAIR8800D}/mits_dsk.c \ + ${ALTAIR8800D}/mits_hdsk.c \ ${ALTAIR8800D}/pmmi_mm103.c \ + ${ALTAIR8800D}/pt_vdm1.c \ ${ALTAIR8800D}/sds_sbc200.c \ ${ALTAIR8800D}/sds_vfii.c \ ${ALTAIR8800D}/tarbell_fdc.c \