From 9e3d414ef2fa95cf7703e4539004389d8e3c4136 Mon Sep 17 00:00:00 2001 From: Patrick Linstruth Date: Mon, 30 Mar 2026 18:29:07 -0400 Subject: [PATCH] Altair8800: New Devices and Bug Fixes This update to the Altair8800 simulator includes: Adds MITS hard disk controller device Adds FarmTek FDC+ disk controller device (1.5MB) Adds iCOM 3712/3812 disk controller device Adds Processor Technology VDM1 video device Fixes boot bug in TARBELL and VFII devices TARBELL returns 0xff for DMA status if 1011 Fixes DAZZLER vertical blank status timing Adds DDT command to control DDT-style output Improves MEMORY dump display --- Altair8800/altair8800_sys.c | 6 +- Altair8800/altair8800_sys.h | 4 + Altair8800/cromemco_dazzler.c | 51 +- Altair8800/cromemco_dazzler.h | 2 - Altair8800/farmtek_fdcplus.c | 785 ++++++++++++ Altair8800/farmtek_fdcplus.h | 67 + Altair8800/icom_fd3x12.c | 1451 ++++++++++++++++++++++ Altair8800/mits_hdsk.c | 575 +++++++++ Altair8800/pt_vdm1.c | 702 +++++++++++ Altair8800/s100_bus.c | 105 +- Altair8800/s100_bus.h | 3 + Altair8800/s100_cpu.c | 37 + Altair8800/s100_cpu.h | 5 + Altair8800/s100_po.c | 11 +- Altair8800/s100_z80.c | 10 +- Altair8800/sds_vfii.c | 9 +- Visual Studio Projects/Altair8800.vcproj | 20 + makefile | 4 + 18 files changed, 3815 insertions(+), 32 deletions(-) create mode 100644 Altair8800/farmtek_fdcplus.c create mode 100644 Altair8800/farmtek_fdcplus.h create mode 100644 Altair8800/icom_fd3x12.c create mode 100755 Altair8800/mits_hdsk.c create mode 100644 Altair8800/pt_vdm1.c 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 \