diff --git a/Altair8800/altair8800_defs.h b/Altair8800/altair8800_defs.h new file mode 100644 index 00000000..932fa543 --- /dev/null +++ b/Altair8800/altair8800_defs.h @@ -0,0 +1,45 @@ +/* altair8800_defs.h + + Copyright (c) 2025 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: + 11/07/25 Initial version + +*/ + +#ifndef _ALTAIR8800_DEFS_H +#define _ALTAIR8800_DEFS_H + +#include "sim_defs.h" + +#define KB 1024 /* kilo byte */ +#define KBLOG2 10 /* log2 of KB */ + +#define PLURAL(x) (x), (x) == 1 ? "" : "s" + +/* Keyboard Constants */ +#define KBD_BS 0x08 +#define KBD_DEL 0x7f + +#endif diff --git a/Altair8800/altair8800_dsk.c b/Altair8800/altair8800_dsk.c new file mode 100644 index 00000000..3ad89e0a --- /dev/null +++ b/Altair8800/altair8800_dsk.c @@ -0,0 +1,435 @@ +/* altair8800_dsk.c: Soft Sector Disk Library + + Copyright (c) 2025 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: + 13-Nov-2025 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_dsk.h" + +static void calc_offset(DSK_INFO *d); + +/* + * INTERLEAVED disk images are structured as follows: + * + * +------------------+ + * | TRACK 0 / HEAD 0 | + * +------------------+ + * | TRACK 0 / HEAD 1 | + * +------------------+ + * | TRACK 1 / HEAD 0 | + * +------------------+ + * | TRACK 1 / HEAD 1 | + * +------------------+ + * | TRACK n / HEAD 0 | + * +------------------+ + * | TRACK n / HEAD 1 | + * +------------------+ + * + * NON-INTERLEAVED disk images are structured as follows: + * + * +------------------+ + * | TRACK 0 / HEAD 0 | + * +------------------+ + * | TRACK 1 / HEAD 0 | + * +------------------+ + * | TRACK n / HEAD 0 | + * +------------------+ + * | TRACK 0 / HEAD 1 | + * +------------------+ + * | TRACK 1 / HEAD 1 | + * +------------------+ + * | TRACK n / HEAD 1 | + * +------------------+ + * + */ + +t_stat dsk_init(DSK_INFO *d, UNIT *unit, int tracks, int heads, int interleaved) +{ + if (d == NULL) { + return SCPE_ARG; + } + if (tracks < 1 || tracks > DSK_MAX_TRACKS) { + return SCPE_ARG; + } + if (heads < 1 || heads > DSK_MAX_HEADS) { + return SCPE_ARG; + } + + d->unit = unit; + d->fmt.tracks = tracks; + d->fmt.heads = heads; + d->fmt.interleaved = interleaved; + + return SCPE_OK; +} + +t_stat dsk_init_format(DSK_INFO *d, int strack, int etrack, int shead, int ehead, int den, int secs, int secsize, int stsec) +{ + int tr, hd; + + if (d == NULL) { + return SCPE_ARG; + } + if (strack < 0 || strack > d->fmt.tracks - 1) { + return SCPE_ARG; + } + if (shead < 0 || shead > d->fmt.heads - 1) { + return SCPE_ARG; + } + if (strack > etrack || shead > ehead) { + return SCPE_ARG; + } + + if (d->fmt.tracks < (etrack - strack + 1)) { + d->fmt.tracks = (etrack - strack + 1); + } + + if (d->fmt.heads < (ehead - shead + 1)) { + d->fmt.heads = ehead - shead + 1; + } + + if (d->fmt.interleaved && d->fmt.heads > 1) { + for (tr = strack; tr <= etrack; tr++) { + for (hd = shead; hd <= ehead ; hd++) { + d->fmt.track[tr][hd].density = den; + d->fmt.track[tr][hd].sectors = secs; + d->fmt.track[tr][hd].sectorsize = secsize; + d->fmt.track[tr][hd].startsector = stsec; + } + } + } + else { + for (hd = shead; hd <= ehead; hd++) { + for (tr = strack; tr <= etrack; tr++) { + d->fmt.track[tr][hd].density = den; + d->fmt.track[tr][hd].sectors = secs; + d->fmt.track[tr][hd].sectorsize = secsize; + d->fmt.track[tr][hd].startsector = stsec; + } + } + } + + calc_offset(d); + + return SCPE_OK; +} + +static void calc_offset(DSK_INFO *d) { + int t, h, offset = 0; + + if (d->fmt.interleaved && d->fmt.heads > 1) { + for (t = 0; t < d->fmt.tracks; t++) { + for (h = 0; h < d->fmt.heads; h++) { + sim_debug(d->dbg_verbose, d->unit->dptr, "T:%02d H:%d O:%d\n", t, h, offset); + d->fmt.track[t][h].offset = offset; + + /* Set offset to start of next track */ + offset += d->fmt.track[t][h].sectors * d->fmt.track[t][h].sectorsize; + } + } + } + else { + for (h = 0; h < d->fmt.heads; h++) { + for (t = 0; t < d->fmt.tracks; t++) { + sim_debug(d->dbg_verbose, d->unit->dptr, "T:%02d H:%d O:%d\n", t, h, offset); + d->fmt.track[t][h].offset = offset; + + /* Set offset to start of next track */ + offset += d->fmt.track[t][h].sectors * d->fmt.track[t][h].sectorsize; + } + } + } +} + +t_stat dsk_validate(DSK_INFO *d, int track, int head, int sector) +{ + if (track < 0 || track > d->fmt.tracks - 1) { + sim_printf("DSK: ** Invalid track number %d\n", track); + return SCPE_IOERR; + } + if (head < 0 || head > (d->fmt.heads - 1)) { + sim_printf("DSK: ** Invalid head number %d\n", head); + return SCPE_IOERR; + } + if (sector < d->fmt.track[track][head].startsector || sector > (d->fmt.track[track][head].sectors - ((d->fmt.track[track][head].startsector) ? 0 : 1))) { + sim_printf("DSK: ** Invalid sector number. track/head %d/%d has %d sectors. %d requested.\n", track, head, d->fmt.track[track][head].sectors, sector); + return SCPE_IOERR; + } + + return SCPE_OK; +} + +int32 dsk_size(DSK_INFO *d) +{ + if (d != NULL && d->unit != NULL && d->unit->fileref != NULL) { + return sim_fsize(d->unit->fileref); + } + + return 0; +} + +int32 dsk_tracks(DSK_INFO *d) +{ + if (d != NULL) { + return d->fmt.tracks; + } + + return 0; +} + +int32 dsk_track_size(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].sectors * d->fmt.track[track][head].sectorsize; + } + + return 0; +} + +int32 dsk_sectors(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].sectors; + } + + return 0; +} + +int32 dsk_sector_size(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].sectorsize; + } + + return 0; +} + +int32 dsk_start_sector(DSK_INFO *d, int32 track, int32 head) +{ + if (d != NULL) { + return d->fmt.track[track][head].startsector; + } + + return 0; +} + +int32 dsk_sector_offset(DSK_INFO *d, int32 track, int32 head, int32 sector) +{ + if (d != NULL) { + return d->fmt.track[track][head].offset + (dsk_sector_size(d, track, head) * (sector - dsk_start_sector(d, track, head))); + } + + return 0; +} + +t_stat dsk_read_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, uint8 *buf, int32 *bytesread) +{ + int32 b, ssize; + t_stat r = SCPE_OK; + + if (d == NULL || d->unit == NULL || d->unit->fileref == NULL) { + return SCPE_ARG; + } + + if ((r = dsk_validate(d, track, head, sector)) != 0) { + return SCPE_ARG; + } + + ssize = dsk_sector_size(d, track, head); + + fseek(d->unit->fileref, dsk_sector_offset(d, track, head, sector), SEEK_SET); + + if ((b = fread(buf, 1, ssize, d->unit->fileref)) != ssize) { + r = SCPE_IOERR; + } + + if (bytesread != NULL) { + *bytesread = b; + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK RD SEC: T:%d H:%d S:%d SS:%d READ:%d\n", track, head, sector, ssize, *bytesread); + } + + +// dsk_dump_buf(buf, ssize); + + return r; +} + +t_stat dsk_write_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, const uint8 *buf, int32 *byteswritten) +{ + int b, ssize, offset; + int r = SCPE_OK; + + if (d == NULL || d->unit == NULL || d->unit->fileref == NULL) { + return SCPE_ARG; + } + + if ((r = dsk_validate(d, track, head, sector)) != 0) { + return r; + } + + ssize = dsk_sector_size(d, track, head); + offset = dsk_sector_offset(d, track,head, sector); + + fseek(d->unit->fileref, offset, SEEK_SET); + + b = fwrite(buf, 1, ssize, d->unit->fileref); + + if (byteswritten != NULL) { + *byteswritten = b; + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK WR SEC: T:%d H:%d S:%d SS:%d O:%d WRITTEN:%d\n", track, head, sector, ssize, offset, *byteswritten); + } + + +// dsk_dump_buf(buf, ssize); + + return r; +} + +t_stat dsk_read_track(DSK_INFO *d, int32 track, int32 head, uint8 *buf) +{ + if (d == NULL || d->unit == NULL || d->unit->dptr == NULL) { + return SCPE_ARG; + } + + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK RD TRK: T:%d H:%d\n", track, head); + + return SCPE_OK; +} + +t_stat dsk_write_track(DSK_INFO *d, int32 track, int32 head, uint8 fill) +{ + int s, ssize, start; + unsigned char *b; + + if (d == NULL) { + return SCPE_ARG; + } + + ssize = dsk_sector_size(d, track, head); + start = dsk_start_sector(d, track, head); + + if ((b = malloc(ssize)) == NULL) { + return 0; + } + + memset(b, fill, ssize); + + sim_debug(d->dbg_verbose, d->unit->dptr, "DSK WR TRK: T:%d H:%d SS:%d F:%02X\n", track, head, ssize, fill); + + for (s = 0; s < dsk_sectors(d, track, head); s++) { + dsk_write_sector(d, track, head, s + start, b, NULL); + } + + free(b); + + return 0; +} + +t_stat dsk_format(DSK_INFO *d, uint8 fill) +{ + int t, h; + + if (d == NULL) { + return SCPE_ARG; + } + + for (t = 0; t < d->fmt.tracks; t++) { + for (h = 0; h < d->fmt.heads; h++) { + dsk_write_track(d, t, h, fill); + } + } + + return SCPE_OK; +} + +void dsk_dump_buf(const uint8 *b, int32 size) +{ + int i; + + if (b == NULL) { + return; + } + + for (i = 0; i < size; i++) { + if ((i & 0x0f) == 0x00) { + sim_printf("%04X: ", i); + } + sim_printf("%02X%c", b[i], ((i & 0x0f) == 0x0f) ? '\n' : ' '); + } +} + +void dsk_show(DSK_INFO *d) +{ + int t, h; + + if (d != NULL) { + sim_printf("\n"); + sim_printf("fmt.tracks = %d\n", d->fmt.tracks); + sim_printf("fmt.heads = %d\n", d->fmt.heads); + + for (t = 0; t < d->fmt.tracks; t++) { + for (h = 0; h < d->fmt.heads; h++) { + sim_printf("T:%02d H:%d D:%s SECS:%02d SECSIZE:%04d OFFSET:%05X\n", t, h, + d->fmt.track[t][h].density == DSK_DENSITY_SD ? "SD" : "DD", + d->fmt.track[t][h].sectors, + d->fmt.track[t][h].sectorsize, + d->fmt.track[t][h].offset); + } + } + } +} + +void dsk_set_verbose_flag(DSK_INFO *d, uint32 flag) +{ + if (d != NULL) { + d->dbg_verbose = flag; + } +} + +t_stat dsk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + uint32 i; + + for (i=0; i < dptr->numunits; i++) { + if ((dptr->units[i].flags & UNIT_ATTABLE) && + !(dptr->units[i].flags & UNIT_DIS)) { + fprintf (st, " sim> ATTACH {switches} %s diskfile\n", sim_uname(&dptr->units[i])); + } + } + + fprintf (st, "\n%s attach command switches\n", dptr->name); + fprintf (st, " -E Must Exist (if not specified an attempt to create the indicated\n"); + fprintf (st, " disk container will be attempted).\n"); + fprintf (st, " -N New file. Existing file is overwritten.\n"); + fprintf (st, " -R Attach Read Only.\n"); + + fprintf (st, "\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/altair8800_dsk.h b/Altair8800/altair8800_dsk.h new file mode 100644 index 00000000..e4bb2de7 --- /dev/null +++ b/Altair8800/altair8800_dsk.h @@ -0,0 +1,85 @@ +/* altair8800_dsk.h: Soft Sector Disk Library + + Copyright (c) 2025 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: + 13-Nov-2025 Initial version + +*/ + +#ifndef _ALTAIR8800_DSK_H +#define _ALTAIR8800_DSK_H + +#include "sim_defs.h" + +#define DSK_MAX_TRACKS 80 +#define DSK_MAX_HEADS 2 + +#define DSK_DENSITY_SD 0x01 +#define DSK_DENSITY_DD 0x02 + +typedef struct { + int32 density; + int32 sectors; + int32 sectorsize; + int32 startsector; + int32 offset; +} DSK_TRACK; + +typedef struct { + int32 tracks; + int32 heads; + int32 interleaved; + DSK_TRACK track[DSK_MAX_TRACKS][DSK_MAX_HEADS]; +} DSK_FORMAT; + +typedef struct { + UNIT *unit; + DSK_FORMAT fmt; + uint32 dbg_verbose; +} DSK_INFO; + +extern t_stat dsk_init(DSK_INFO *d, UNIT *unit, int32 tracks, int32 heads, int32 interleaved); +extern t_stat dsk_init_format(DSK_INFO *d, int32 strack, int32 etrack, int32 shead, int32 ehead, + int32 den, int32 secs, int32 secsize, int32 stsec); +extern void dsk_set_verbose_flag(DSK_INFO *d, uint32 flag); +extern int32 dsk_size(DSK_INFO *d); +extern int32 dsk_tracks(DSK_INFO *d); +extern int32 dsk_track_size(DSK_INFO *d, int32 track, int32 head); +extern int32 dsk_sectors(DSK_INFO *d, int32 track, int32 head); +extern int32 dsk_sector_size(DSK_INFO *d, int32 track, int32 head); +extern int32 dsk_sector_offset(DSK_INFO *d, int32 track, int32 head, int32 sector); +extern int32 dsk_start_sector(DSK_INFO *d, int32 track, int32 head); +extern t_stat dsk_validate(DSK_INFO *d, int track, int head, int sector); +extern t_stat dsk_write_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, const uint8 *buf, int32 *byteswritten); +extern t_stat dsk_read_sector(DSK_INFO *d, int32 track, int32 head, int32 sector, uint8 *buf, int32 *bytesread); +extern t_stat dsk_read_track(DSK_INFO *d, int32 track, int32 head, uint8 *buf); +extern t_stat dsk_write_track(DSK_INFO *d, int32 track, int32 head, uint8 fill); +extern t_stat dsk_format(DSK_INFO *d, uint8 fill); +extern void dsk_dump_buf(const uint8 *b, int32 size); +extern void dsk_show(DSK_INFO *d); +extern t_stat dsk_attach_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +#endif + diff --git a/Altair8800/altair8800_sys.c b/Altair8800/altair8800_sys.c new file mode 100644 index 00000000..373f1bca --- /dev/null +++ b/Altair8800/altair8800_sys.c @@ -0,0 +1,167 @@ +/* altair8800_sys.c: MITS Altair 8800 SIMH System Interface + + Copyright (c) 2025 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. + + ---------------------------------------------------------- + + This module of the simulator contains the glue between the + Altair8800 simulator and SIMH. + + To add a device, these modules must be modified: + + altair8800_sys.h add external DEVICE declaration + altair8800_sys.c add DEVICE to sim_devices + + ---------------------------------------------------------- + + History: + 11/07/25 Initial version + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" + +#if 0 +static t_bool fprint_stopped(FILE *st, t_stat reason); +#endif + +/* SCP data structures + + sim_name simulator name string + sim_PC pointer to saved PC register descriptor + sim_emax number of words needed for examine + sim_devices array of pointers to simulated devices + sim_stop_messages array of pointers to stop messages + sim_load binary loader +*/ + +char sim_name[] = "Altair 8800 (BUS)"; + +int32 sim_emax = SIM_EMAX; + +DEVICE *sim_devices[] = { + &bus_dev, + &cpu_dev, + &ssw_dev, + &simh_dev, + &ram_dev, + &bram_dev, + &rom_dev, + &po_dev, + &mdsk_dev, + &m2sio0_dev, + &m2sio1_dev, + &sio_dev, + &sbc200_dev, + &tarbell_dev, + &vfii_dev, + NULL +}; + +char memoryAccessMessage[256]; +char instructionMessage[256]; + +const char *sim_stop_messages[SCPE_BASE] = { + "Unknown error", /* 0 is reserved/unknown */ + "Breakpoint", + memoryAccessMessage, + instructionMessage, + "Invalid Opcode", + "HALT instruction" +}; + + +/* find_unit_index find index of a unit + + Inputs: + uptr = pointer to unit + Outputs: + result = index of device +*/ +int32 sys_find_unit_index(UNIT* uptr) +{ + DEVICE *dptr = find_dev_from_unit(uptr); + + if (dptr == NULL) { + return -1; + } + + return (uptr - dptr->units); +} + +#if 0 + +// Need to figure out how to initize in altair8800_sys.c +// +static t_bool fprint_stopped(FILE *st, t_stat reason) +{ + fprintf(st, "Hey, it stopped!\n"); + + return FALSE; +} + +#endif + +char *sys_strupr(const char *str) +{ + static char s[128]; + int i; + + for (i = 0; i < sizeof(s) && str[i] != '\0'; i++) { + s[i] = sim_toupper(str[i]); + } + + s[i] = '\0'; + + return s; +} + + +uint8 sys_floorlog2(unsigned int n) +{ + /* Compute log2(n) */ + uint8 r = 0; + if (n >= 1<<16) { + n >>=16; + r += 16; + } + if (n >= 1<< 8) { + n >>= 8; + r += 8; + } + if (n >= 1<< 4) { + n >>= 4; + r += 4; + } + if (n >= 1<< 2) { + n >>= 2; + r += 2; + } + if (n >= 1<< 1) { + r += 1; + } + return ((n == 0) ? (0xFF) : r); /* 0xFF is error return value */ +} + diff --git a/Altair8800/altair8800_sys.h b/Altair8800/altair8800_sys.h new file mode 100644 index 00000000..34a1b025 --- /dev/null +++ b/Altair8800/altair8800_sys.h @@ -0,0 +1,69 @@ +/* altair8800_sys.h + + Copyright (c) 2025 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: + 11/07/25 Initial version + +*/ + +#ifndef _ALTAIR8800_SYS_H +#define _ALTAIR8800_SYS_H + +#include "sim_defs.h" + +#define SIM_EMAX 6 + +extern DEVICE bus_dev; +extern DEVICE cpu_dev; +extern DEVICE ssw_dev; +extern DEVICE simh_dev; +extern DEVICE z80_dev; +extern DEVICE ram_dev; +extern DEVICE bram_dev; +extern DEVICE rom_dev; +extern DEVICE po_dev; +extern DEVICE mdsk_dev; +extern DEVICE m2sio0_dev; +extern DEVICE m2sio1_dev; +extern DEVICE sio_dev; +extern DEVICE sbc200_dev; +extern DEVICE tarbell_dev; +extern DEVICE vfii_dev; + +extern char memoryAccessMessage[256]; +extern char instructionMessage[256]; + +extern int32 sys_find_unit_index(UNIT* uptr); + +extern void sys_set_cpu_instr(t_stat (*routine)(void)); +extern void sys_set_cpu_pc(REG *reg); +extern void sys_set_cpu_pc_value(t_value (*routine)(void)); +extern void sys_set_cpu_parse_sym(t_stat (*routine)(CONST char *cptr, t_addr addr, UNIT *uptr, t_value *val, int32 sw)); +extern void sys_set_cpu_dasm(int32 (*routine)(char *S, const uint32 *val, const int32 addr)); +extern void sys_set_cpu_is_subroutine_call(t_bool (*routine)(t_addr **ret_addrs)); +extern char *sys_strupr(const char *str); +extern uint8 sys_floorlog2(unsigned int n); + +#endif diff --git a/Altair8800/mits_2sio.c b/Altair8800/mits_2sio.c new file mode 100644 index 00000000..592f8ecc --- /dev/null +++ b/Altair8800/mits_2sio.c @@ -0,0 +1,931 @@ +/* mits_2sio.c: MITS Altair 8800 88-2SIO + + Copyright (c) 2025 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: + 07-Nov-2025 Initial version + + ================================================================== + + The 88-2 Serial Input/Output Board (88-2SIO) is designed around an + Asynchronous Communications Interface Adapter (ACIA). + + The card had up to two physical I/O ports which could be connected + to any serial I/O device that would connect to a current loop, + RS232, or TTY interface. Available baud rates were jumper + selectable for each port from 110 to 9600. + + All I/O is via programmed I/O. Each each port has a status port + and a data port. A write to the status port can select some + options for the device (0x03 will reset the port). A read of the + status port gets the port status: + + +---+---+---+---+---+---+---+---+ + | R P V F C D O I | + +---+---+---+---+---+---+---+---+ + + I - A 1 in this bit position means a character has been received + on the data port and is ready to be read. + O - A 1 in this bit means the port is ready to receive a character + on the data port and transmit it out over the serial line. + D - A 1 in this bit means Data Carrier Detect is present. + C - A 1 in this bit means Clear to Send is present. + F - A 1 in this bit means a Framing Error has occurred. + V - A 1 in this bit means an Overrun has occurred. + P - A 1 in this bit means a Parity Error has occurred. + R - A 1 in this bit means an Interrupt has occurred. + + A read to the data port gets the buffered character, a write + to the data port writes the character to the device. + + The following are excerpts from Computer Notes, Volume 2, Issue 8, + Jan-Feb '77: + + GLITCHES + Q&A from the Repair Department + By Bruce Fowler + + We get many calls on how to interface terminals to the 2SIO. The + problem is that the Asynchronous Communications Interface Adapter's + (ACIA) handshaking signals make interfacing with the 2SIO a + somewhat complicated matter. An explanation of the signals and + their function should make the job easier. The three handshaking + signals--Data Carrier Detect (DCD), Request to Send (RTS) and + Clear to Send (CTS)--permit limited control of a modem or + peripheral. RTS is an output signal, and DCD and CTS are input + signals. + + Data will only leave the ACIA when CTS is active. + + The ACIA will receive data only when DCD is active. DCD is normally + used with modems. As long as DCD is inactive, the ACIA's receiver + section is inhibited and no data can be received by the ACIA. + + Information from the two input signals, CTS and DCD, is present in + the ACIA status register. Bit 2 represents /DCD, and bit 3 + represents /CTS. When bit 2 is high, DCD is inactive. When bit 3 is high, + CTS is inactive. When bit 2 goes low, valid data is sent to the ACIA. + When bit 3 goes low, data can be transmitted. + + / = Active Low + +*/ + +#include "sim_defs.h" +#include "sim_tmxr.h" +#include "altair8800_defs.h" +#include "s100_bus.h" +#include "mits_2sio.h" + +#define M2SIO_NAME "MITS 88-2SIO SERIAL ADAPTER" +#define M2SIO0_SNAME "M2SIO0" +#define M2SIO1_SNAME "M2SIO1" + +#define M2SIO_PORTS 2 + +#define M2SIO_WAIT 250 /* Service Wait Interval */ + +#define M2SIO0_IOBASE 0x10 +#define M2SIO0_IOSIZE 2 +#define M2SIO1_IOBASE 0x12 +#define M2SIO1_IOSIZE 2 + +#define M2SIO_RDRF 0x01 /* Receive Data Register Full */ +#define M2SIO_TDRE 0x02 /* Transmit Data Register Empty */ +#define M2SIO_DCD 0x04 /* Data Carrier Detect */ +#define M2SIO_CTS 0x08 /* Clear to Send */ +#define M2SIO_FE 0x10 /* Framing Error */ +#define M2SIO_OVRN 0x20 /* Overrun */ +#define M2SIO_PE 0x40 /* Parity Error */ +#define M2SIO_IRQ 0x80 /* Interrupt Request */ +#define M2SIO_RESET 0x03 /* Reset */ +#define M2SIO_CLK1 0x00 /* Divide Clock by 1 */ +#define M2SIO_CLK16 0x01 /* Divide Clock by 16 */ +#define M2SIO_CLK64 0x02 /* Divide Clock by 64 */ +#define M2SIO_72E 0x00 /* 7-2-E */ +#define M2SIO_72O 0x04 /* 7-2-O */ +#define M2SIO_71E 0x08 /* 7-1-E */ +#define M2SIO_71O 0x0C /* 7-1-O */ +#define M2SIO_82N 0x10 /* 8-2-N */ +#define M2SIO_81N 0x14 /* 8-1-N */ +#define M2SIO_81E 0x18 /* 8-1-E */ +#define M2SIO_81O 0x1C /* 8-1-O */ +#define M2SIO_FMTMSK 0x1c /* Length, Parity, Stop Mask */ +#define M2SIO_RTSLTID 0x00 /* RTS Low, Xmit Int Disabled */ +#define M2SIO_RTSLTIE 0x20 /* RTS Low, Xmit Int Enabled */ +#define M2SIO_RTSHTID 0x40 /* RTS High, Xmit Int Disabled */ +#define M2SIO_RTSHTBR 0x60 /* RTS High, Xmit Break */ +#define M2SIO_RTSMSK 0x60 /* RTS Bit Mask */ +#define M2SIO_RIE 0x80 /* Receive Int Enabled */ + +#define M2SIO_BAUD 9600 /* Default baud rate */ + +static const char* m2sio_description(DEVICE *dptr); +static t_stat m2sio_svc(UNIT *uptr); +static t_stat m2sio_reset(DEVICE *dptr, int32 (*routine)(const int32, const int32, const int32)); +static t_stat m2sio0_reset(DEVICE *dptr); +static t_stat m2sio1_reset(DEVICE *dptr); +static t_stat m2sio_attach(UNIT *uptr, const char *cptr); +static t_stat m2sio_detach(UNIT *uptr); +static t_stat m2sio_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat m2sio_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat m2sio_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc); +static t_stat m2sio_config_line(UNIT *uptr); +static t_stat m2sio_config_rts(DEVICE *dptr, char rts); +static int32 m2sio0_io(int32 addr, int32 io, int32 data); +static int32 m2sio1_io(int32 addr, int32 io, int32 data); +static int32 m2sio_io(DEVICE *dptr, int32 addr, int32 io, int32 data); +static int32 m2sio_stat(DEVICE *dptr, int32 io, int32 data); +static int32 m2sio_data(DEVICE *dptr, int32 io, int32 data); +static void m2sio_int(UNIT *uptr); +static int32 m2sio_map_kbdchar(UNIT *uptr, int32 ch); +static t_stat m2sio_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +static M2SIO_REG m2sio0_reg; +static M2SIO_REG m2sio1_reg; + +/* Debug Flags */ +#define STATUS_MSG (1 << 0) +#define IRQ_MSG (1 << 1) +#define VERBOSE_MSG (1 << 2) + +/* Debug Table */ +static DEBTAB m2sio_dt[] = { + { "STATUS", STATUS_MSG, "Status messages" }, + { "IRQ", IRQ_MSG, "Interrupt messages" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +/* Terminal multiplexer library descriptors */ + +static TMLN m2sio0_tmln[] = { /* line descriptors */ + { 0 } +}; + +static TMLN m2sio1_tmln[] = { /* line descriptors */ + { 0 } +}; + +static TMXR m2sio0_tmxr = { /* multiplexer descriptor */ + 1, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + m2sio0_tmln, /* line descriptor array */ + NULL, /* line connection order */ + NULL /* multiplexer device (derived internally) */ +}; + +static TMXR m2sio1_tmxr = { /* multiplexer descriptor */ + 1, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + m2sio1_tmln, /* line descriptor array */ + NULL, /* line connection order */ + NULL /* multiplexer device (derived internally) */ +}; + + +static MTAB m2sio_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "Sets MITS 2SIO base I/O address" }, + + { UNIT_M2SIO_MAP, 0, "NOMAP", "NOMAP", NULL, NULL, NULL, + "Do not map any character" }, /* disable character mapping */ + { UNIT_M2SIO_MAP, UNIT_M2SIO_MAP, "MAP", "MAP", NULL, NULL, NULL, + "Enable mapping of characters" }, /* enable all character mapping */ + { UNIT_M2SIO_UPPER, 0, "NOUPPER", "NOUPPER", NULL, NULL, NULL, + "Console input remains unchanged" }, /* do not change case of input characters */ + { UNIT_M2SIO_UPPER, UNIT_M2SIO_UPPER, "UPPER", "UPPER", NULL, NULL, NULL, + "Convert console input to upper case" }, /* change input characters to upper case */ + { UNIT_M2SIO_BS, 0, "BS", "BS", NULL, NULL, NULL, + "Map delete to backspace" }, /* map delete to backspace */ + { UNIT_M2SIO_BS, UNIT_M2SIO_BS, "DEL", "DEL", NULL, NULL, NULL, + "Map backspace to delete" }, /* map backspace to delete */ + { UNIT_M2SIO_DTR, UNIT_M2SIO_DTR, "DTR", "DTR", NULL, NULL, NULL, + "DTR follows RTS" }, + { UNIT_M2SIO_DTR, 0, "NODTR", "NODTR", NULL, NULL, NULL, + "DTR does not follow RTS (default)" }, + { UNIT_M2SIO_DCD, UNIT_M2SIO_DCD, "DCD", "DCD", NULL, NULL, NULL, + "Force DCD active low" }, + { UNIT_M2SIO_DCD, 0, "NODCD", "NODCD", NULL, NULL, NULL, + "DCD follows status line (default)" }, + { UNIT_M2SIO_CTS, UNIT_M2SIO_CTS, "CTS", "CTS", NULL, NULL, NULL, + "Force CTS active low" }, + { UNIT_M2SIO_CTS, 0, "NOCTS", "NOCTS", NULL, NULL, NULL, + "CTS follows status line (default)" }, + + { MTAB_XTD | MTAB_VUN, UNIT_M2SIO_CONSOLE, NULL, "CONSOLE", &m2sio_set_console, NULL, NULL, "Set as CONSOLE" }, + { MTAB_XTD | MTAB_VUN, 0, NULL, "NOCONSOLE", &m2sio_set_console, NULL, NULL, "Remove as CONSOLE" }, + + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "BAUD", "BAUD", &m2sio_set_baud, &m2sio_show_baud, + NULL, "Set baud rate (default=9600)" }, + { 0 } +}; + +static RES m2sio0_res = { M2SIO0_IOBASE, M2SIO0_IOSIZE, 0, 0, &m2sio0_tmxr }; +static RES m2sio1_res = { M2SIO1_IOBASE, M2SIO1_IOSIZE, 0, 0, &m2sio1_tmxr }; + +static UNIT unit0[] = { + { UDATA (&m2sio_svc, UNIT_ATTABLE | UNIT_M2SIO_MAP | UNIT_M2SIO_CONSOLE | UNIT_M2SIO_DCD | UNIT_M2SIO_CTS , 0), M2SIO_WAIT }, +}; + +static UNIT unit1[] = { + { UDATA (&m2sio_svc, UNIT_ATTABLE | UNIT_M2SIO_DCD | UNIT_M2SIO_CTS, 0), M2SIO_WAIT }, +}; + +static REG reg0[] = { + { HRDATAD (M2STA0, m2sio0_reg.stb, 8, "2SIO port 0 status register"), }, + { HRDATAD (M2CTL0, m2sio0_reg.ctb, 8, "2SIO port 0 control register"), }, + { HRDATAD (M2RXD0, m2sio0_reg.rxb, 8, "2SIO port 0 rx data buffer"), }, + { HRDATAD (M2TXD0, m2sio0_reg.txb, 8, "2SIO port 0 tx data buffer"), }, + { FLDATAD (M2TXP0, m2sio0_reg.txp, 0, "2SIO port 0 tx data pending"), }, + { FLDATAD (M2CON0, m2sio0_reg.conn, 0, "2SIO port 0 connection status"), }, + { FLDATAD (M2RIE0, m2sio0_reg.rie, 0, "2SIO port 0 receive interrupt enable"), }, + { FLDATAD (M2TIE0, m2sio0_reg.tie, 0, "2SIO port 0 transmit interrupt enable"), }, + { FLDATAD (M2RTS0, m2sio0_reg.rts, 0, "2SIO port 0 RTS status (active low)"), }, + { FLDATAD (M2RDRF0, m2sio0_reg.stb, 0, "2SIO port 0 RDRF status"), }, + { FLDATAD (M2TDRE0, m2sio0_reg.stb, 1, "2SIO port 0 TDRE status"), }, + { FLDATAD (M2DCD0, m2sio0_reg.stb, 2, "2SIO port 0 DCD status (active low)"), }, + { FLDATAD (M2CTS0, m2sio0_reg.stb, 3, "2SIO port 0 CTS status (active low)"), }, + { FLDATAD (M2OVRN0, m2sio0_reg.stb, 4, "2SIO port 0 OVRN status"), }, + { FLDATAD (DCDL0, m2sio0_reg.dcdl, 0, "2SIO port 0 DCD latch"), }, + { DRDATAD (M2WAIT0, unit0[0].wait, 32, "2SIO port 0 wait cycles"), }, + { FLDATAD (M2INTEN0, m2sio0_reg.intenable, 1, "2SIO port 0 Global vectored interrupt enable"), }, + { DRDATAD (M2VEC0, m2sio0_reg.intvector, 8, "2SIO port 0 interrupt vector"), }, + { HRDATAD (M2DBVAL0, m2sio0_reg.databus, 8, "2SIO port 0 data bus value"), }, + { NULL } +}; +static REG reg1[] = { + { HRDATAD (M2STA1, m2sio1_reg.stb, 8, "2SIO port 1 status buffer"), }, + { HRDATAD (M2CTL1, m2sio1_reg.ctb, 8, "2SIO port 1 control register"), }, + { HRDATAD (M2RXD1, m2sio1_reg.rxb, 8, "2SIO port 1 rx data buffer"), }, + { HRDATAD (M2TXD1, m2sio1_reg.txb, 8, "2SIO port 1 tx data buffer"), }, + { FLDATAD (M2TXP1, m2sio1_reg.txp, 0, "2SIO port 1 tx data pending"), }, + { FLDATAD (M2CON1, m2sio1_reg.conn, 0, "2SIO port 1 connection status"), }, + { FLDATAD (M2RIE1, m2sio1_reg.rie, 0, "2SIO port 1 receive interrupt enable"), }, + { FLDATAD (M2TIE1, m2sio1_reg.tie, 0, "2SIO port 1 transmit interrupt enable"), }, + { FLDATAD (M2RTS1, m2sio1_reg.rts, 0, "2SIO port 1 RTS status (active low)"), }, + { FLDATAD (M2RDRF1, m2sio1_reg.stb, 0, "2SIO port 1 RDRF status"), }, + { FLDATAD (M2TDRE1, m2sio1_reg.stb, 1, "2SIO port 1 TDRE status"), }, + { FLDATAD (M2DCD1, m2sio1_reg.stb, 2, "2SIO port 1 DCD status (active low)"), }, + { FLDATAD (M2CTS1, m2sio1_reg.stb, 3, "2SIO port 1 CTS status (active low)"), }, + { FLDATAD (M2OVRN1, m2sio1_reg.stb, 4, "2SIO port 1 OVRN status"), }, + { FLDATAD (DCDL1, m2sio1_reg.dcdl, 0, "2SIO port 1 DCD latch"), }, + { DRDATAD (M2WAIT1, unit1[0].wait, 32, "2SIO port 1 wait cycles"), }, + { FLDATAD (M2INTEN1, m2sio1_reg.intenable, 1, "2SIO port 1 Global vectored interrupt enable"), }, + { DRDATAD (M2VEC1, m2sio1_reg.intvector, 8, "2SIO port 1 interrupt vector"), }, + { HRDATAD (M2DBVAL1, m2sio1_reg.databus, 8, "2SIO port 1 data bus value"), }, + { NULL } +}; + +DEVICE m2sio0_dev = { + M2SIO0_SNAME, /* name */ + unit0, /* unit */ + reg0, /* registers */ + m2sio_mod, /* modifiers */ + 1, /* # units */ + ADDRRADIX, /* address radix */ + ADDRWIDTH, /* address width */ + 1, /* address increment */ + DATARADIX, /* data radix */ + DATAWIDTH, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &m2sio0_reset, /* reset routine */ + NULL, /* boot routine */ + &m2sio_attach, /* attach routine */ + &m2sio_detach, /* detach routine */ + &m2sio0_res, /* context */ + (DEV_DISABLE | DEV_DEBUG | DEV_MUX), /* flags */ + 0, /* debug control */ + m2sio_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &m2sio_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context for help */ + &m2sio_description /* description */ +}; + +DEVICE m2sio1_dev = { + M2SIO1_SNAME, /* name */ + unit1, /* unit */ + reg1, /* registers */ + m2sio_mod, /* modifiers */ + 1, /* # units */ + 10, /* address radix */ + 31, /* address width */ + 1, /* address increment */ + 8, /* data radix */ + 8, /* data width */ + NULL, /* examine routine */ + NULL, /* deposit routine */ + &m2sio1_reset, /* reset routine */ + NULL, /* boot routine */ + &m2sio_attach, /* attach routine */ + &m2sio_detach, /* detach routine */ + &m2sio1_res, /* context */ + (DEV_DISABLE | DEV_DEBUG | DEV_MUX), /* flags */ + 0, /* debug control */ + m2sio_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &m2sio_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context for help */ + &m2sio_description /* description */ +}; + +static const char* m2sio_description(DEVICE *dptr) +{ + return M2SIO_NAME; +} + +static t_stat m2sio0_reset(DEVICE *dptr) +{ + dptr->units->up8 = &m2sio0_reg; + + return(m2sio_reset(dptr, &m2sio0_io)); +} + +static t_stat m2sio1_reset(DEVICE *dptr) +{ + dptr->units->up8 = &m2sio1_reg; + + return(m2sio_reset(dptr, &m2sio1_io)); +} + +static t_stat m2sio_reset(DEVICE *dptr, int32 (*routine)(const int32, const int32, const int32)) +{ + RES *res; + M2SIO_REG *reg; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + /* Connect/Disconnect I/O Ports at base address */ + if (dptr->flags & DEV_DIS) { /* Device is disabled */ + s100_bus_remio(res->io_base, res->io_size, routine); + s100_bus_noconsole(&dptr->units[0]); + + return SCPE_OK; + } + + /* Device is enabled */ + s100_bus_addio(res->io_base, res->io_size, routine, dptr->name); + + /* Set as CONSOLE unit */ + if (dptr->units[0].flags & UNIT_M2SIO_CONSOLE) { + s100_bus_console(&dptr->units[0]); + } + + /* Set DEVICE for this UNIT */ + dptr->units[0].dptr = dptr; + dptr->units[0].wait = M2SIO_WAIT; + + /* Enable TMXR modem control passthrough */ + tmxr_set_modem_control_passthru(res->tmxr); + + /* Reset status registers */ + reg->stb = M2SIO_CTS | M2SIO_DCD; + reg->txp = FALSE; + reg->dcdl = FALSE; + + if (dptr->units[0].flags & UNIT_ATT) { + m2sio_config_rts(dptr, 1); /* disable RTS */ + } + + /* Start service routine */ + sim_activate(&dptr->units[0], dptr->units[0].wait); + + sim_debug(STATUS_MSG, dptr, "reset adapter.\n"); + + return SCPE_OK; +} + + +static t_stat m2sio_svc(UNIT *uptr) +{ + DEVICE *dptr; + RES *res; + M2SIO_REG *reg; + int32 c,s,stb; + t_stat r; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + /* Check for new incoming connection */ + if (uptr->flags & UNIT_ATT) { + if (tmxr_poll_conn(res->tmxr) >= 0) { /* poll connection */ + + reg->conn = TRUE; /* set connected */ + + sim_debug(STATUS_MSG, uptr->dptr, "new connection.\n"); + } + } + + /* Update incoming modem status bits */ + if (uptr->flags & UNIT_ATT) { + tmxr_set_get_modem_bits(res->tmxr->ldsc, 0, 0, &s); + stb = reg->stb; + reg->stb &= ~M2SIO_CTS; + reg->stb |= ((s & TMXR_MDM_CTS) || (uptr->flags & UNIT_M2SIO_CTS)) ? 0 : M2SIO_CTS; /* Active Low */ + if ((stb ^ reg->stb) & M2SIO_CTS) { + sim_debug(STATUS_MSG, uptr->dptr, "CTS state changed to %s.\n", (reg->stb & M2SIO_CTS) ? "LOW" : "HIGH"); + } + + if (!reg->dcdl) { + reg->stb &= ~M2SIO_DCD; + reg->stb |= ((s & TMXR_MDM_DCD) || (uptr->flags & UNIT_M2SIO_DCD)) ? 0 : M2SIO_DCD; /* Active Low */ + if ((stb ^ reg->stb) & M2SIO_DCD) { + if ((reg->stb & M2SIO_DCD) == M2SIO_DCD) { + reg->dcdl = TRUE; + if (reg->rie) { + m2sio_int(uptr); + } + } + sim_debug(STATUS_MSG, uptr->dptr, "DCD state changed to %s.\n", (reg->stb & M2SIO_DCD) ? "LOW" : "HIGH"); + } + } + + /* Enable receiver if DCD is active low */ + res->tmxr->ldsc->rcve = !(reg->stb & M2SIO_DCD); + } + + /* TX data */ + if (reg->txp) { + if (uptr->flags & UNIT_ATT) { + if (!(reg->stb & M2SIO_CTS)) { /* Active low */ + r = tmxr_putc_ln(res->tmxr->ldsc, reg->txb); + reg->txp = FALSE; /* Reset TX Pending */ + } else { + r = SCPE_STALL; + } + } else { + r = sim_putchar(reg->txb); + reg->txp = FALSE; /* Reset TX Pending */ + } + + if (r == SCPE_LOST) { + reg->conn = FALSE; /* Connection was lost */ + sim_debug(STATUS_MSG, uptr->dptr, "lost connection.\n"); + } + + /* If TX buffer now empty, send interrupt */ + if ((!reg->txp) && (reg->tie)) { + m2sio_int(uptr); + } + + } + + /* Update TDRE if not set and no character pending */ + if (!reg->txp && !(reg->stb & M2SIO_TDRE)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_tx(res->tmxr); + reg->stb |= (tmxr_txdone_ln(res->tmxr->ldsc) && reg->conn) ? M2SIO_TDRE : 0; + } else { + reg->stb |= M2SIO_TDRE; + } + } + + /* Check for Data if RX buffer empty */ + if (!(reg->stb & M2SIO_RDRF)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_rx(res->tmxr); + + c = tmxr_getc_ln(res->tmxr->ldsc); + } else { + c = s100_bus_poll_kbd(uptr); + } + + if (c & (TMXR_VALID | SCPE_KFLAG)) { + reg->rxb = m2sio_map_kbdchar(uptr, c); + reg->stb |= M2SIO_RDRF; + reg->stb &= ~(M2SIO_FE | M2SIO_OVRN | M2SIO_PE); + if (reg->rie) { + m2sio_int(uptr); + } + } + } + + sim_activate_abs(uptr, uptr->wait); + + return SCPE_OK; +} + + +/* Attach routine */ +static t_stat m2sio_attach(UNIT *uptr, const char *cptr) +{ + DEVICE *dptr; + RES *res; + M2SIO_REG *reg; + t_stat r; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + sim_debug(VERBOSE_MSG, uptr->dptr, "attach (%s).\n", cptr); + + if ((r = tmxr_attach(res->tmxr, uptr, cptr)) == SCPE_OK) { + + if (res->tmxr->ldsc->serport) { + r = m2sio_config_rts(uptr->dptr, reg->rts); /* update RTS */ + } + + res->tmxr->ldsc->rcve = 1; + } + + return r; +} + + +/* Detach routine */ +static t_stat m2sio_detach(UNIT *uptr) +{ + DEVICE *dptr; + RES *res; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + + sim_debug(VERBOSE_MSG, uptr->dptr, "detach.\n"); + + if (uptr->flags & UNIT_ATT) { + sim_cancel(uptr); + + return (tmxr_detach(res->tmxr, uptr)); + } + + return SCPE_UNATT; +} + +static t_stat m2sio_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + if (value == UNIT_M2SIO_CONSOLE) { + s100_bus_console(uptr); + } + else { + s100_bus_noconsole(uptr); + } + + return SCPE_OK; +} + +static t_stat m2sio_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + M2SIO_REG *reg; + int32 baud; + t_stat r = SCPE_ARG; + + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + if (!(uptr->flags & UNIT_ATT)) { + return SCPE_UNATT; + } + + if (cptr != NULL) { + if (sscanf(cptr, "%d", &baud)) { + switch (baud) { + case 110: + case 150: + case 300: + case 1200: + case 1800: + case 2400: + case 4800: + case 9600: + case 19200: + reg->baud = baud; + r = m2sio_config_line(uptr); + + return r; + + default: + break; + } + } + } + + return r; +} + +static t_stat m2sio_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc) +{ + M2SIO_REG *reg; + + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + if (uptr->flags & UNIT_ATT) { + fprintf(st, "Baud rate: %d", reg->baud); + } + + return SCPE_OK; +} + +static t_stat m2sio_config_line(UNIT *uptr) +{ + DEVICE *dptr; + RES *res; + M2SIO_REG *reg; + char config[20]; + const char *fmt; + t_stat r = SCPE_IERR; + + if ((dptr = find_dev_from_unit(uptr)) == NULL) + return SCPE_IERR; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return SCPE_IERR; + } + + if (reg != NULL) { + switch (reg->ctb & M2SIO_FMTMSK) { + case M2SIO_72E: + fmt = "7E2"; + break; + case M2SIO_72O: + fmt = "7O2"; + break; + case M2SIO_71E: + fmt = "7E1"; + break; + case M2SIO_71O: + fmt = "7O1"; + break; + case M2SIO_82N: + fmt = "8N2"; + break; + case M2SIO_81E: + fmt = "8E1"; + break; + case M2SIO_81O: + fmt = "8O1"; + break; + case M2SIO_81N: + default: + fmt = "8N1"; + break; + } + + sprintf(config, "%d-%s", reg->baud, fmt); + + r = tmxr_set_config_line(res->tmxr->ldsc, config); + + sim_debug(STATUS_MSG, uptr->dptr, "port configuration set to '%s'.\n", config); + } + + return r; +} + +/* +** RTS is active low +** 0 = RTS active +** 1 = RTS inactive +*/ +static t_stat m2sio_config_rts(DEVICE *dptr, char rts) +{ + RES *res; + M2SIO_REG *reg; + t_stat r = SCPE_OK; + int32 s; + + if ((res = (RES *) dptr->ctxt) == NULL) { + return SCPE_IERR; + } + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + if (dptr->units[0].flags & UNIT_ATT) { + /* RTS Control */ + s = TMXR_MDM_RTS; + if (dptr->units[0].flags & UNIT_M2SIO_DTR) { + s |= TMXR_MDM_DTR; + } + + if (!rts) { + r = tmxr_set_get_modem_bits(res->tmxr->ldsc, s, 0, NULL); + if (reg->rts) { + sim_debug(STATUS_MSG, dptr, "RTS state changed to HIGH.\n"); + } + } else { + r = tmxr_set_get_modem_bits(res->tmxr->ldsc, 0, s, NULL); + if (!reg->rts) { + sim_debug(STATUS_MSG, dptr, "RTS state changed to LOW.\n"); + } + } + } + + reg->rts = rts; /* Active low */ + + return r; +} + +static int32 m2sio0_io(int32 addr, int32 io, int32 data) +{ + return(m2sio_io(&m2sio0_dev, addr, io, data)); +} + +static int32 m2sio1_io(int32 addr, int32 io, int32 data) +{ + return(m2sio_io(&m2sio1_dev, addr, io, data)); +} + +static int32 m2sio_io(DEVICE *dptr, int32 addr, int32 io, int32 data) +{ + int32 r; + + if (addr & 0x01) { + r = m2sio_data(dptr, io, data); + } else { + r = m2sio_stat(dptr, io, data); + } + + return(r); +} + +static int32 m2sio_stat(DEVICE *dptr, int32 io, int32 data) +{ + M2SIO_REG *reg; + int32 r; + + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + if (io == S100_IO_READ) { + r = reg->stb; + } else { + reg->ctb = data & 0xff; /* save control byte */ + + /* Master Reset */ + if ((data & M2SIO_RESET) == M2SIO_RESET) { + sim_debug(STATUS_MSG, dptr, "MC6850 master reset.\n"); + reg->stb &= (M2SIO_CTS | M2SIO_DCD); /* Reset status register */ + reg->rxb = 0x00; + reg->txp = FALSE; + reg->tie = FALSE; + reg->rie = FALSE; + reg->dcdl = FALSE; + m2sio_config_rts(dptr, 1); /* disable RTS */ + } else { + /* Interrupt Enable */ + reg->rie = (data & M2SIO_RIE) == M2SIO_RIE; /* Receive interrupt enable */ + reg->tie = (data & M2SIO_RTSMSK) == M2SIO_RTSLTIE; /* Transmit interrupt enable */ + switch (data & M2SIO_RTSMSK) { + case M2SIO_RTSLTIE: + case M2SIO_RTSLTID: + m2sio_config_rts(dptr, 0); /* enable RTS */ + break; + + case M2SIO_RTSHTID: + case M2SIO_RTSHTBR: + m2sio_config_rts(dptr, 1); /* disable RTS */ + break; + + default: + break; + } + + /* Set data bits, parity and stop bits format */ + m2sio_config_line(&dptr->units[0]); + } + + r = 0x00; + } + + return(r); +} + +static int32 m2sio_data(DEVICE *dptr, int32 io, int32 data) +{ + M2SIO_REG *reg; + int32 r; + + if ((reg = (M2SIO_REG *) dptr->units->up8) == NULL) { + return SCPE_IERR; + } + + if (io == S100_IO_READ) { + r = reg->rxb; + reg->stb &= ~(M2SIO_RDRF | M2SIO_FE | M2SIO_OVRN | M2SIO_PE | M2SIO_IRQ); + reg->dcdl = FALSE; + } else { + reg->txb = data; + reg->stb &= ~(M2SIO_TDRE | M2SIO_IRQ); + reg->txp = TRUE; + r = 0x00; + } + + return r; +} + +static void m2sio_int(UNIT *uptr) +{ + M2SIO_REG *reg; + + if ((reg = (M2SIO_REG *) uptr->up8) == NULL) { + return; + } + + if (reg->intenable) { + s100_bus_int((1 << reg->intvector), reg->databus); /* Generate interrupt on the bus */ + reg->stb |= M2SIO_IRQ; + + sim_debug(IRQ_MSG, uptr->dptr, "%s: IRQ Vector=%d Status=%02X\n", sim_uname(uptr), reg->intvector, reg->stb); + } +} + +static int32 m2sio_map_kbdchar(UNIT *uptr, int32 ch) +{ + ch &= 0xff; + + if (uptr->flags & UNIT_M2SIO_MAP) { + if (uptr->flags & UNIT_M2SIO_BS) { + if (ch == KBD_BS) { + return KBD_DEL; + } + } + else if (ch == KBD_DEL) { + return KBD_BS; + } + + if (uptr->flags & UNIT_M2SIO_UPPER) { + return toupper(ch); + } + } + + return ch; +} + +static t_stat m2sio_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 88-2SIO (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + fprintf(st, "\n\n"); + tmxr_attach_help(st, dptr, uptr, flag, cptr); + + fprintf(st, "----- NOTES -----\n\n"); + fprintf(st, "Only one device may poll the host keyboard for CONSOLE input.\n"); + fprintf(st, "Use SET %s CONSOLE to select this UNIT as the CONSOLE device.\n", sim_dname(dptr)); + fprintf(st, "\nUse SHOW BUS CONSOLE to display the current CONSOLE device.\n\n"); + + return SCPE_OK; +} + diff --git a/Altair8800/mits_2sio.h b/Altair8800/mits_2sio.h new file mode 100644 index 00000000..7be304c9 --- /dev/null +++ b/Altair8800/mits_2sio.h @@ -0,0 +1,67 @@ +/* mits_2sio.h + + Copyright (c) 2025 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: + 11/07/25 Initial version + +*/ + +#ifndef _MITS_2SIO_H +#define _MITS_2SIO_H + +#define UNIT_V_M2SIO_CONSOLE (UNIT_V_UF + 0) /* Port checks console for input */ +#define UNIT_M2SIO_CONSOLE (1 << UNIT_V_M2SIO_CONSOLE) +#define UNIT_V_M2SIO_MAP (UNIT_V_UF + 1) /* map keyboard characters */ +#define UNIT_M2SIO_MAP (1 << UNIT_V_M2SIO_MAP) +#define UNIT_V_M2SIO_BS (UNIT_V_UF + 2) /* map delete to backspace */ +#define UNIT_M2SIO_BS (1 << UNIT_V_M2SIO_BS) +#define UNIT_V_M2SIO_UPPER (UNIT_V_UF + 3) /* map to upper case */ +#define UNIT_M2SIO_UPPER (1 << UNIT_V_M2SIO_UPPER) +#define UNIT_V_M2SIO_DTR (UNIT_V_UF + 4) /* DTR follows RTS */ +#define UNIT_M2SIO_DTR (1 << UNIT_V_M2SIO_DTR) +#define UNIT_V_M2SIO_DCD (UNIT_V_UF + 5) /* Force DCD active low */ +#define UNIT_M2SIO_DCD (1 << UNIT_V_M2SIO_DCD) +#define UNIT_V_M2SIO_CTS (UNIT_V_UF + 6) /* Force CTS active low */ +#define UNIT_M2SIO_CTS (1 << UNIT_V_M2SIO_CTS) + +typedef struct { + int32 port; /* Port 0 or 1 */ + t_bool conn; /* Connected Status */ + int32 baud; /* Baud rate */ + int32 rts; /* RTS Status */ + int32 rxb; /* Receive Buffer */ + int32 txb; /* Transmit Buffer */ + t_bool txp; /* Transmit Pending */ + int32 stb; /* Status Buffer */ + int32 ctb; /* Control Buffer */ + t_bool rie; /* Rx Int Enable */ + t_bool tie; /* Tx Int Enable */ + t_bool dcdl; /* DCD latch */ + uint8 intenable; /* Interrupt Enable */ + uint8 intvector; /* Interrupt Vector */ + uint8 databus; /* Data Bus Value */ +} M2SIO_REG; + +#endif diff --git a/Altair8800/mits_dsk.c b/Altair8800/mits_dsk.c new file mode 100644 index 00000000..cb07b689 --- /dev/null +++ b/Altair8800/mits_dsk.c @@ -0,0 +1,631 @@ +/* mits_dsk.c: MITS Altair 88-DCDD Simulator + + Copyright (c) 2025 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 + Minidisk support added by Mike Douglas + + History: + 07-Nov-2025 Initial version + + ================================================================== + + The 88-DCDD is a 8-inch floppy controller which can control up + to 16 daisy-chained Pertec FD-400 hard-sectored floppy drives. + Each diskette has physically 77 tracks of 32 137-byte sectors + each. + + The controller is interfaced to the CPU by use of 3 I/O addresses, + standardly, these are device numbers 10, 11, and 12 (octal). + + Address Mode Function + ------- ---- -------- + 10 Out Selects and enables Controller and Drive + 10 In Indicates status of Drive and Controller + 11 Out Controls Disk Function + 11 In Indicates current sector position of disk + 12 Out Write data + 12 In Read data + + Drive Select Out (Device 10 OUT): + + +---+---+---+---+---+---+---+---+ + | C | X | X | X | Device | + +---+---+---+---+---+---+---+---+ + + C = If this bit is 1, the disk controller selected by 'device' is + cleared. If the bit is zero, 'device' is selected as the + device being controlled by subsequent I/O operations. + X = not used + Device = value zero thru 15, selects drive to be controlled. + + Drive Status In (Device 10 IN): + + +---+---+---+---+---+---+---+---+ + | R | Z | I | X | X | H | M | W | + +---+---+---+---+---+---+---+---+ + + W - When 0, write circuit ready to write another byte. + M - When 0, head movement is allowed + H - When 0, indicates head is loaded for read/write + X - not used (will be 0) + I - When 0, indicates interrupts enabled (not used by this simulator) + Z - When 0, indicates head is on track 0 + R - When 0, indicates that read circuit has new byte to read + + Drive Control (Device 11 OUT): + + +---+---+---+---+---+---+---+---+ + | W | C | D | E | U | H | O | I | + +---+---+---+---+---+---+---+---+ + + I - When 1, steps head IN one track + O - When 1, steps head OUT one track + H - When 1, loads head to drive surface + U - When 1, unloads head + E - Enables interrupts (ignored by this simulator) + D - Disables interrupts (ignored by this simulator) + C - When 1 lowers head current (ignored by this simulator) + W - When 1, starts Write Enable sequence: W bit on device 10 + (see above) will go 1 and data will be read from port 12 + until 137 bytes have been read by the controller from + that port. The W bit will go off then, and the sector data + will be written to disk. Before you do this, you must have + stepped the track to the desired number, and waited until + the right sector number is presented on device 11 IN, then + set this bit. + + Sector Position (Device 11 IN): + + As the sectors pass by the read head, they are counted and the + number of the current one is available in this register. + + +---+---+---+---+---+---+---+---+ + | X | X | Sector Number | T | + +---+---+---+---+---+---+---+---+ + + X = Not used + Sector number = binary of the sector number currently under the + head, 0-31. + T = Sector True, is a 0 when the sector is positioned to read or + write. + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "altair8800_dsk.h" +#include "s100_bus.h" +#include "mits_dsk.h" + +static int32 poc = TRUE; /* Power On Clear */ + +/* 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 mdsk10(const int32 port, const int32 io, const int32 data); +static int32 mdsk11(const int32 port, const int32 io, const int32 data); +static int32 mdsk12(const int32 port, const int32 io, const int32 data); + +static t_stat mdsk_boot(int32 unitno, DEVICE *dptr); +static t_stat mdsk_reset(DEVICE *dptr); +static t_stat mdsk_attach(UNIT *uptr, CONST char *cptr); +static t_stat mdsk_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* mdsk_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 sectors_per_track [NUM_OF_DSK]; +static int32 current_imageSize [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 int32 warnLevelDSK = 3; +static int32 warnLock [NUM_OF_DSK]; +static int32 warnAttached [NUM_OF_DSK]; +static int32 warnDSK10 = 0; +static int32 warnDSK11 = 0; +static int32 warnDSK12 = 0; +static int8 dskbuf[DSK_SECTSIZE]; /* data Buffer */ +static int32 sector_true = 0; /* sector true flag for sector register read */ + +/* 88DSK Standard I/O Data Structures */ + +static UNIT mdsk_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 mdsk_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { 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 (IMAGESIZE, current_imageSize, 10, 32, NUM_OF_DSK, + "Size of disk image array"), REG_CIRC + REG_RO }, + { 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 }, + { DRDATAD (DSKWL, warnLevelDSK, 32, + "Warn level register") }, + { BRDATAD (WARNLOCK, warnLock, 10, 32, NUM_OF_DSK, + "Count of write to locked register array"), REG_CIRC + REG_RO }, + { BRDATAD (WARNATTACHED, warnAttached, 10, 32, NUM_OF_DSK, + "Count for selection of unattached disk register array"), REG_CIRC + REG_RO }, + { DRDATAD (WARNDSK10, warnDSK10, 4, + "Count of IN(8) on unattached disk register"), REG_RO }, + { DRDATAD (WARNDSK11, warnDSK11, 4, + "Count of IN/OUT(9) on unattached disk register"), REG_RO }, + { DRDATAD (WARNDSK12, warnDSK12, 4, + "Count of IN/OUT(10) on unattached disk register"), REG_RO }, + { BRDATAD (DISKBUFFER, dskbuf, 10, 8, DSK_SECTSIZE, + "Disk data buffer array"), REG_CIRC + REG_RO }, + { NULL } +}; + +#define DSK_NAME "MITS 88-DCDD Floppy Disk Controller" +#define DEV_NAME "DSK" + +static const char* mdsk_description(DEVICE *dptr) { + return DSK_NAME; +} + +static MTAB mdsk_mod[] = { + { UNIT_DSK_WLK, 0, "WRTENB", "WRTENB", NULL, NULL, NULL, + "Enables " DSK_NAME "n for writing" }, + { UNIT_DSK_WLK, UNIT_DSK_WLK, "WRTLCK", "WRTLCK", NULL, NULL, NULL, + "Locks " DSK_NAME "n for writing" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB mdsk_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 mdsk_dev = { + DEV_NAME, mdsk_unit, mdsk_reg, mdsk_mod, + NUM_OF_DSK, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &mdsk_reset, + &mdsk_boot, &mdsk_attach, NULL, + NULL, (DEV_DISABLE | DEV_DEBUG), 0, + mdsk_dt, NULL, NULL, &mdsk_show_help, &dsk_attach_help, NULL, + &mdsk_description +}; + +static const char* selectInOut(const int32 io) { + return io == 0 ? "IN" : "OUT"; +} + +/* service routines to handle simulator functions */ +/* reset routine */ + +static t_stat mdsk_reset(DEVICE *dptr) +{ + int32 i; + + if (dptr->flags & DEV_DIS) { + s100_bus_remio(0x08, 1, &mdsk10); + s100_bus_remio(0x09, 1, &mdsk11); + s100_bus_remio(0x0A, 1, &mdsk12); + + poc = TRUE; + } + else { + if (poc) { + s100_bus_addio(0x08, 1, &mdsk10, dptr->name); + s100_bus_addio(0x09, 1, &mdsk11, dptr->name); + s100_bus_addio(0x0A, 1, &mdsk12, dptr->name); + + for (i = 0; i < NUM_OF_DSK; i++) { + current_imageSize[i] = 0; + sectors_per_track[i] = DSK_SECT; + tracks[i] = MAX_TRACKS; + } + } + } + + for (i = 0; i < NUM_OF_DSK; i++) { + warnLock[i] = 0; + warnAttached[i] = 0; + current_track[i] = 0; + current_sector[i] = 0; + current_byte[i] = 0; + current_flag[i] = 0; + } + + warnDSK10 = 0; + warnDSK11 = 0; + warnDSK12 = 0; + current_disk = NUM_OF_DSK; + in9_count = 0; + in9_message = FALSE; + + return SCPE_OK; +} +/* mdsk_attach - determine type of drive attached based on disk image size */ + +static t_stat mdsk_attach(UNIT *uptr, CONST char *cptr) +{ + int32 thisUnitIndex; + int32 imageSize; + t_stat r; + + sim_switches |= SWMASK ('E'); /* File must exist */ + + 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)); + + /* If the file size is close to the mini-disk image size, set the number of + tracks to 16, otherwise, 32 sectors per track. */ + + imageSize = sim_fsize(uptr -> fileref); + current_imageSize[thisUnitIndex] = imageSize; + sectors_per_track[thisUnitIndex] = (((MINI_DISK_SIZE - MINI_DISK_DELTA < imageSize) && + (imageSize < MINI_DISK_SIZE + MINI_DISK_DELTA)) ? + MINI_DISK_SECT : DSK_SECT); + return SCPE_OK; +} + +static t_stat mdsk_boot(int32 unitno, DEVICE *dptr) +{ + *((int32 *) sim_PC->loc) = 0xff00; + return SCPE_OK; +} + +static int32 dskseek(const UNIT *xptr) +{ + return sim_fseek(xptr -> fileref, DSK_SECTSIZE * sectors_per_track[current_disk] * current_track[current_disk] + + DSK_SECTSIZE * 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 < DSK_SECTSIZE) + dskbuf[i++] = 0; + uptr = mdsk_dev.units + current_disk; + if (((uptr -> flags) & UNIT_DSK_WLK) == 0) { /* write enabled */ + sim_debug(WRITE_MSG, &mdsk_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, &mdsk_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, DSK_SECTSIZE, uptr -> fileref); + if (rtn != DSK_SECTSIZE) { + sim_debug(VERBOSE_MSG, &mdsk_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 if ( (mdsk_dev.dctrl & VERBOSE_MSG) && (warnLock[current_disk] < warnLevelDSK) ) { + /* write locked - print warning message if required */ + warnLock[current_disk]++; + sim_debug(VERBOSE_MSG, &mdsk_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] &= 0xfe; /* ENWD off */ + current_byte[current_disk] = 0xff; + 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 mdsk10(const int32 port, const int32 io, const int32 data) +{ + int32 current_disk_flags; + in9_count = 0; + if (io == 0) { /* IN: return flags */ + if (current_disk >= NUM_OF_DSK) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK10 < warnLevelDSK)) { + warnDSK10++; + sim_debug(VERBOSE_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT + " Attempt of IN 0x08 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, &mdsk_dev, "DSK%i: " ADDRESS_FORMAT " OUT 0x08: %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 = (mdsk_dev.units + current_disk) -> flags; + if ((current_disk_flags & UNIT_ATT) == 0) { /* nothing attached? */ + if ( (mdsk_dev.dctrl & VERBOSE_MSG) && (warnAttached[current_disk] < warnLevelDSK) ) { + warnAttached[current_disk]++; + sim_debug(VERBOSE_MSG, &mdsk_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] = 0xff; + if (data & 0x80) /* disable drive? */ + current_flag[current_disk] = 0; /* yes, clear all flags */ + else { /* enable drive */ + current_flag[current_disk] = 0x1a; /* move head true */ + if (current_track[current_disk] == 0) /* track 0? */ + current_flag[current_disk] |= 0x40; /* yes, set track 0 true as well */ + if (sectors_per_track[current_disk] == MINI_DISK_SECT) /* drive enable loads head for Minidisk */ + current_flag[current_disk] |= 0x84; + } + } + return 0; /* ignored since OUT */ +} + +/* Disk Drive Status/Functions */ + +static int32 mdsk11(const int32 port, const int32 io, const int32 data) +{ + if (current_disk >= NUM_OF_DSK) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK11 < warnLevelDSK)) { + warnDSK11++; + sim_debug(VERBOSE_MSG, &mdsk_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 == 0) { /* read sector position */ + in9_count++; + if ((mdsk_dev.dctrl & SECTOR_STUCK_MSG) && (in9_count > 2 * DSK_SECT) && (!in9_message)) { + in9_message = TRUE; + sim_debug(SECTOR_STUCK_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " Looping on sector find.\n", + current_disk, s100_bus_get_addr()); + } + sim_debug(IN_MSG, &mdsk_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] & 0x04) { /* 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]) + current_sector[current_disk] = 0; + current_byte[current_disk] = 0xff; + } + 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, &mdsk_dev, "DSK%i: " ADDRESS_FORMAT " OUT 0x09: %x\n", current_disk, s100_bus_get_addr(), data); + if (data & 0x01) { /* step head in */ + if (current_track[current_disk] == (tracks[current_disk] - 1)) { + sim_debug(TRACK_STUCK_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " Unnecessary step in.\n", + current_disk, s100_bus_get_addr()); + } + current_track[current_disk]++; + current_flag[current_disk] &= 0xbf; /* 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] = 0xff; + } + + if (data & 0x02) { /* step head out */ + if (current_track[current_disk] == 0) { + sim_debug(TRACK_STUCK_MSG, &mdsk_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] |= 0x40; /* track 0 if there */ + } + if (dirty) /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + current_sector[current_disk] = 0xff; + current_byte[current_disk] = 0xff; + } + + if (dirty) /* implies that current_disk < NUM_OF_DSK */ + writebuf(); + + if (data & 0x04) { /* head load */ + current_flag[current_disk] |= 0x04; /* turn on head loaded bit */ + current_flag[current_disk] |= 0x80; /* turn on 'read data available' */ + } + + if ((data & 0x08) && (sectors_per_track[current_disk] != MINI_DISK_SECT)) { /* head unload */ + current_flag[current_disk] &= 0xfb; /* turn off 'head loaded' bit */ + current_flag[current_disk] &= 0x7f; /* turn off 'read data available' */ + current_sector[current_disk] = 0xff; + current_byte[current_disk] = 0xff; + } + + /* interrupts & head current are ignored */ + + if (data & 0x80) { /* write sequence start */ + current_byte[current_disk] = 0; + current_flag[current_disk] |= 0x01; /* enter new write data on */ + } + return 0; /* ignored since OUT */ +} + +/* Disk Data In/Out */ + +static int32 mdsk12(const int32 port, const int32 io, const int32 data) +{ + int32 i, rtn; + UNIT *uptr; + + if (current_disk >= NUM_OF_DSK) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK12 < warnLevelDSK)) { + warnDSK12++; + sim_debug(VERBOSE_MSG, &mdsk_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 = mdsk_dev.units + current_disk; + if (io == 0) { + if (current_byte[current_disk] >= DSK_SECTSIZE) { + /* physically read the sector */ + sim_debug(READ_MSG, &mdsk_dev, + "DSK%i: " ADDRESS_FORMAT " IN 0x0a (READ) D%d T%d S%d\n", + current_disk, s100_bus_get_addr(), current_disk, + current_track[current_disk], current_sector[current_disk]); + for (i = 0; i < DSK_SECTSIZE; i++) + dskbuf[i] = 0; + if (dskseek(uptr)) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK12 < warnLevelDSK)) { + warnDSK12++; + sim_debug(VERBOSE_MSG, &mdsk_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]); + } + } + rtn = sim_fread(dskbuf, 1, DSK_SECTSIZE, uptr -> fileref); + if (rtn != DSK_SECTSIZE) { + if ((mdsk_dev.dctrl & VERBOSE_MSG) && (warnDSK12 < warnLevelDSK)) { + warnDSK12++; + sim_debug(VERBOSE_MSG, &mdsk_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; + } + return dskbuf[current_byte[current_disk]++] & 0xff; + } else { + if (current_byte[current_disk] >= DSK_SECTSIZE) + 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 0; /* ignored since OUT */ + } +} + +static t_stat mdsk_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 88-DCDD (%s)\n", sim_dname(dptr)); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/mits_dsk.h b/Altair8800/mits_dsk.h new file mode 100644 index 00000000..de843e5e --- /dev/null +++ b/Altair8800/mits_dsk.h @@ -0,0 +1,57 @@ +/* mits_dsk.h + + Copyright (c) 2025 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: + 11/07/25 Initial version + +*/ + +#ifndef _MITS_DSK_H +#define _MITS_DSK_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_SECTSIZE 137 /* size of sector */ +#define DSK_SECT 32 /* sectors per track */ +#define MAX_TRACKS 2048 /* number of tracks, + original Altair has 77 tracks only */ +#define DSK_TRACSIZE (DSK_SECTSIZE * DSK_SECT) +#define MAX_DSK_SIZE (DSK_TRACSIZE * MAX_TRACKS) +#define BOOTROM_SIZE_DSK 256 /* size of boot rom */ + +#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 ALTAIR_DISK_SIZE 337664 /* size of regular Altair disks */ +#define ALTAIR_DISK_DELTA 256 /* threshold for detecting regular Altair disks */ + +#endif + diff --git a/Altair8800/s100_bram.c b/Altair8800/s100_bram.c new file mode 100644 index 00000000..2388b1de --- /dev/null +++ b/Altair8800/s100_bram.c @@ -0,0 +1,484 @@ +/* s100_bram.c: MITS Altair 8800 Banked RAM + + Copyright (c) 2025 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: + 07-Nov-2025 Initial version + +*/ + +#include "s100_bus.h" +#include "s100_bram.h" + +static t_stat bram_reset (DEVICE *dptr); +static t_stat bram_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw); +static t_stat bram_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw); +static int32 bram_io (const int32 addr, const int32 rw, const int32 data); +static int32 bram_memio (const int32 addr, const int32 rw, const int32 data); +static t_stat bram_set_banks (int32 banks); +static t_stat bram_clear_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat bram_enable_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat bram_randomize_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static t_stat bram_banks_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static void bram_addio (int32 type); +static void bram_remio (int32 type); +static t_stat bram_set_type (int32 type); +static t_stat bram_type_command (UNIT *uptr, int32 value, CONST char *cptr, void *desc); +static void bram_clear (void); +static void bram_randomize (void); +static t_stat bram_show_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +static const char* bram_description (DEVICE *dptr); + +static void PutBYTE(register uint32 Addr, const register uint32 Value); +static uint32 GetBYTE(register uint32 Addr); + +static int32 poc = TRUE; /* Power On Clear */ + +static int32 *M = NULL; /* RAM */ +static int32 bram_banks = 0; +static int32 bram_bank = 0; +static int32 bram_type = BRAM_TYPE_NONE; + +static BRAM B[BRAM_TYPE_MAX + 1] = { + { 0x00, 0, 0, "NONE" }, + { 0xff, 1, 8, "ERAM" }, + { 0x40, 1, 8, "VRAM" }, + { 0x40, 1, 7, "CRAM" }, + { 0xc0, 1, MAXBANK, "HRAM" }, + { 0x40, 1, MAXBANK, "B810" }, +}; + +#define DEV_NAME "BRAM" + +static const char* bram_description(DEVICE *dptr) { + return "Banked Random Access Memory"; +} + +static UNIT bram_unit = { + UDATA (NULL, UNIT_FIX | UNIT_BINK, MAXBANKSIZE) +}; + +static REG bram_reg[] = { + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { HRDATAD (BANK, bram_bank, MAXBANKS2LOG, "Selected bank"), }, + { DRDATAD (BANKS, bram_banks, 8, "Number of banks"), }, + { DRDATAD (TYPE, bram_type, 8, "RAM type"), }, + { NULL } +}; + +static MTAB bram_mod[] = { + { UNIT_BRAM_VERBOSE, UNIT_BRAM_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_BRAM_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_B810 , NULL, "B810", &bram_type_command, + NULL, NULL, "Sets the RAM type to Digital Design B810" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_CRAM , NULL, "CRAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to Cromemco" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_ERAM , NULL, "ERAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to SD Systems ExpandoRAM" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_HRAM , NULL, "HRAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to NorthStar" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_VRAM , NULL, "VRAM", &bram_type_command, + NULL, NULL, "Sets the RAM type to Vector" }, + { MTAB_XTD | MTAB_VDV, BRAM_TYPE_NONE , NULL, "NONE", &bram_type_command, + NULL, NULL, "Sets the RAM type to NONE" }, + + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "BANKS={1-16}", &bram_banks_command, + NULL, NULL, "Sets the RAM size" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 1, NULL, "ADDPAGE={PAGE | START-END | ALL}", &bram_enable_command, + NULL, NULL, "Enable RAM page(s)" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "REMPAGE={PAGE | START-END | ALL}", &bram_enable_command, + NULL, NULL, "Disable RAM page(s)" }, + { MTAB_VDV, 0, NULL, "CLEAR", &bram_clear_command, + NULL, NULL, "Sets RAM to 0x00" }, + { MTAB_VDV, 0, NULL, "RANDOMIZE", &bram_randomize_command, + NULL, NULL, "Sets RAM to random values" }, + { 0 } +}; + +/* Debug Flags */ +static DEBTAB bram_dt[] = { + { NULL, 0 } +}; + +DEVICE bram_dev = { + DEV_NAME, /* name */ + &bram_unit, /* units */ + bram_reg, /* registers */ + bram_mod, /* modifiers */ + 1, /* # units */ + ADDRRADIX, /* address radix */ + ADDRWIDTH, /* address width */ + 1, /* addr increment */ + DATARADIX, /* data radix */ + DATAWIDTH, /* data width */ + &bram_ex, /* examine routine */ + &bram_dep, /* deposit routine */ + &bram_reset, /* reset routine */ + NULL, /* boot routine */ + NULL, /* attach routine */ + NULL, /* detach routine */ + NULL, /* context */ + (DEV_DISABLE | DEV_DIS), /* flags */ + 0, /* debug control */ + bram_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &bram_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context available to help routines */ + &bram_description /* device description */ +}; + +static t_stat bram_reset(DEVICE *dptr) +{ + if (dptr->flags & DEV_DIS) { /* Disable Device */ + bram_set_type(BRAM_TYPE_NONE); + + poc = TRUE; + } + else { + if (poc) { + poc = FALSE; + } + else { + bram_bank = 0; + } + } + + return SCPE_OK; +} + +/* memory examine */ +static t_stat bram_ex(t_value *vptr, t_addr addr, UNIT *uptr, int32 sw) +{ + *vptr = GetBYTE(addr & ADDRMASK) & DATAMASK; + + return SCPE_OK; +} + +/* memory deposit */ +static t_stat bram_dep(t_value val, t_addr addr, UNIT *uptr, int32 sw) +{ + PutBYTE(addr & ADDRMASK, val & DATAMASK); + + return SCPE_OK; +} + +static int32 bram_io(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_WRITE) { + + switch (bram_type) { + case BRAM_TYPE_HRAM: + if (data >= 0 && data < B[bram_type].banks) { + bram_bank = data; + } else { + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + } + break; + + case BRAM_TYPE_B810: + if (data >= 0 && data < B[bram_type].banks) { + bram_bank = data; + } else { + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + } + break; + + case BRAM_TYPE_ERAM: + if (data >= 0 && data < B[bram_type].banks) { + bram_bank = data; + if (bram_unit.flags & UNIT_BRAM_VERBOSE) { + sim_printf("%s selecting bank %d\n", B[bram_type].name, data); + } + } else { + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + } + break; + + case BRAM_TYPE_VRAM: + switch(data & 0xFF) { + case 0x01: + case 0x41: // OASIS uses this for some reason? */ + bram_bank = 0; + break; + case 0x02: + case 0x42: // OASIS uses this for some reason? */ + bram_bank = 1; + break; + case 0x04: + bram_bank = 2; + break; + case 0x08: + bram_bank = 3; + break; + case 0x10: + bram_bank = 4; + break; + case 0x20: + bram_bank = 5; + break; + case 0x40: + bram_bank = 6; + break; + case 0x80: + bram_bank = 7; + break; + default: + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + break; + } + break; + + case BRAM_TYPE_CRAM: + switch(data & 0x7F) { + case 0x01: + bram_bank = 0; + break; + case 0x02: + bram_bank = 1; + break; + case 0x04: + bram_bank = 2; + break; + case 0x08: + bram_bank = 3; + break; + case 0x10: + bram_bank = 4; + break; + case 0x20: + bram_bank = 5; + break; + case 0x40: + bram_bank = 6; + break; + default: + sim_printf("Invalid bank select 0x%02x for %s\n", data, B[bram_type].name); + break; + } + break; + + default: + break; + } + } + + return DATAMASK; +} + +static int32 bram_memio(const int32 addr, const int32 rw, const int32 data) +{ + if (rw == S100_IO_READ) { + return GetBYTE(addr); + } + + PutBYTE(addr, data); + + return DATAMASK; +} + +static uint32 GetBYTE(register uint32 Addr) +{ + t_addr bankAddr; + + if (M != NULL) { + Addr &= ADDRMASK; + + bankAddr = Addr + (bram_bank * MAXBANKSIZE); + + return M[bankAddr] & DATAMASK; + } + + return DATAMASK; +} + +static void PutBYTE(register uint32 Addr, const register uint32 Value) +{ + t_addr bankAddr; + + if (M != NULL) { + Addr &= ADDRMASK; + + bankAddr = Addr + (bram_bank * MAXBANKSIZE); + + M[bankAddr] = Value & DATAMASK; + } +} + +static void bram_addio(int32 type) +{ + if (type > BRAM_TYPE_NONE && type <= BRAM_TYPE_MAX) { + if (B[type].size) { + s100_bus_addio_out(B[type].baseport, B[type].size, &bram_io, B[type].name); + } + } +} + +static void bram_remio(int32 type) +{ + if (type > BRAM_TYPE_NONE && type <= BRAM_TYPE_MAX) { + s100_bus_remio_out(B[type].baseport, B[type].size, &bram_io); + } +} + +static t_stat bram_set_type(int32 type) +{ + if (bram_type == type) { /* No change */ + return SCPE_OK; + } + + bram_remio(bram_type); /* Changing type - remove previous IO */ + + bram_type = type; + bram_bank = 0; + + bram_set_banks(B[bram_type].banks); + bram_addio(bram_type); + + return SCPE_OK; +} + +static t_stat bram_type_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + return bram_set_type(value); +} + +static t_stat bram_set_banks(int32 banks) { + if (banks > 0 && banks <= MAXBANK) { + M = realloc(M, banks * MAXBANKSIZE); + } + else if (M != NULL) { + free(M); + + M = NULL; + + s100_bus_remmem(0x0000, MAXBANKSIZE, &bram_memio); /* Remove enabled pages */ + } + + bram_banks = banks; + + return SCPE_OK; +} + +static t_stat bram_banks_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) { + int32 result, banks; + + if (cptr == NULL) { + sim_printf("Banks must be provided as SET %s BANKS=1-%d\n", DEV_NAME, MAXBANK); + return SCPE_ARG | SCPE_NOMESSAGE; + } + + result = sscanf(cptr, "%i", &banks); + + if (result == 1 && banks && banks <= MAXBANK) { + return bram_set_banks(banks); + } + + return SCPE_ARG | SCPE_NOMESSAGE; +} + +static t_stat bram_enable_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) { + int32 size; + t_addr start, end; + + if (cptr == NULL) { + sim_printf("Memory page(s) must be provided as SET %s [ADD|REM]PAGE=E0-EF\n", DEV_NAME); + return SCPE_ARG | SCPE_NOMESSAGE; + } + + if (get_range(NULL, cptr, &start, &end, 16, PAGEMASK, 0) == NULL) { + return SCPE_ARG; + } + + if (start < MAXPAGE) { + start = start << LOG2PAGESIZE; + } + if (end < MAXPAGE) { + end = end << LOG2PAGESIZE; + } + + start &= 0xff00; + end &= 0xff00; + + size = end - start + PAGESIZE; + + if (value) { + s100_bus_addmem(start, size, &bram_memio, DEV_NAME); /* Add pages */ + } + else { + s100_bus_remmem(start, size, &bram_memio); /* Remove pages */ + } + + return SCPE_OK; +} + +static t_stat bram_clear_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + bram_clear(); + + return SCPE_OK; +} + +static t_stat bram_randomize_command(UNIT *uptr, int32 value, CONST char *cptr, void *desc) +{ + bram_randomize(); + + return SCPE_OK; +} + +static void bram_clear() +{ + int32 i; + + for (i = 0; i < MAXBANKSIZE; i++) { + M[i] = 0; + } +} + +static void bram_randomize() +{ + int32 i; + + for (i = 0; i < bram_banks * MAXBANKSIZE; i++) { + if (M != NULL) { + M[i] = sim_rand() & DATAMASK; + } + } +} + +static t_stat bram_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nAltair 8800 Banked RAM (%s)\n", dptr->name); + + fprint_set_help (st, dptr); + fprint_show_help (st, dptr); + fprint_reg_help (st, dptr); + + return SCPE_OK; +} + diff --git a/Altair8800/s100_bram.h b/Altair8800/s100_bram.h new file mode 100644 index 00000000..f25d15a9 --- /dev/null +++ b/Altair8800/s100_bram.h @@ -0,0 +1,57 @@ +/* s100_bram.h + + Copyright (c) 2025 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: + 11/07/25 Initial version + +*/ + +#ifndef _S100_BRAM_H +#define _S100_BRAM_H + +#include "sim_defs.h" + +#define UNIT_BRAM_V_VERBOSE (UNIT_V_UF+0) /* Enable verbose messagesto */ +#define UNIT_BRAM_VERBOSE (1 << UNIT_BRAM_V_VERBOSE) + +/* Supported Memory Boards */ + +#define BRAM_TYPE_NONE 0 /* No type selected */ +#define BRAM_TYPE_ERAM 1 /* SD Systems ExpandoRAM */ +#define BRAM_TYPE_VRAM 2 /* Vector Graphic RAM card */ +#define BRAM_TYPE_CRAM 3 /* Cromemco RAM card */ +#define BRAM_TYPE_HRAM 4 /* North Start Horizon RAM card */ +#define BRAM_TYPE_B810 5 /* AB Digital Design B810 RAM card */ +#define BRAM_TYPE_MAX BRAM_TYPE_B810 /* Maximum type */ + +typedef struct { + int32 baseport; /* Base IO address */ + int32 size; /* Number of addresses */ + int32 banks; /* Number of banks */ + char *name; /* Short name */ +} BRAM; + +#endif + diff --git a/Altair8800/s100_bus.c b/Altair8800/s100_bus.c new file mode 100644 index 00000000..cdbcd1a3 --- /dev/null +++ b/Altair8800/s100_bus.c @@ -0,0 +1,988 @@ +/* s100_bus.c - S100 Bus Simulator + + Copyright (c) 2025, 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 + ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of 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. + +*/ + +#include "sim_defs.h" +#include "altair8800_sys.h" +#include "s100_z80.h" +#include "s100_bus.h" + +static t_stat bus_reset (DEVICE *dptr); +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); +static t_stat bus_show_config (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat bus_show_console (FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat bus_show_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); +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 ChipType chiptype = CHIP_TYPE_Z80; + +static MDEV mdev_table[MAXPAGE]; /* Active memory table */ +static MDEV mdev_dflt; /* Default memory table */ + +static uint32 bus_addr = 0x0000; + +static int32 poc = TRUE; /* Power On Clear */ + +/* Interrupts */ +uint32 nmiInterrupt = 0x00; /* NMI */ +uint32 vectorInterrupt = 0x00; /* Vector Interrupt bits */ +uint8 dataBus[MAX_INT_VECTORS]; /* Data bus value */ + +/* 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 +*/ +IDEV idev_in[MAXPAGE]; +IDEV idev_out[MAXPAGE]; + +int32 nulldev(CONST int32 addr, CONST int32 io, CONST int32 data) { return 0xff; } + +/* Which UNIT is the CONSOLE */ +UNIT *bus_console = NULL; + +static CONST char* bus_description(DEVICE *dptr) { + return "S100 Bus"; +} + +static UNIT bus_unit = { + UDATA (NULL, 0, 0) +}; + +static REG bus_reg[] = { + { HRDATAD (WRU, sim_int_char, 8, "Interrupt character pseudo register"), }, + { FLDATAD (POC, poc, 0x01, "Power on Clear flag"), }, + { HRDATAD(VECINT,vectorInterrupt, 8, "Vector Interrupt pseudo register"), }, + { BRDATAD (DATABUS, dataBus, 16, 8, MAX_INT_VECTORS, "Data bus pseudo register"), REG_RO + REG_CIRC }, + { HRDATAD(NMI, nmiInterrupt, 1, "NMI Interrupt pseudo register"), }, + { NULL } +}; + +static MTAB bus_mod[] = { + { UNIT_BUS_VERBOSE, UNIT_BUS_VERBOSE, "VERBOSE", "VERBOSE", NULL, NULL, + NULL, "Enable verbose messages" }, + { UNIT_BUS_VERBOSE, 0, "QUIET", "QUIET", NULL, NULL, + NULL, "Disable verbose messages" }, + + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "CONFIG", NULL, NULL, &bus_show_config, NULL, "Show BUS configuration" }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "CONSOLE", NULL, NULL, &bus_show_console, NULL, "Show CONSOLE unit" }, + + { 0 } +}; + +static DEBTAB bus_dt[] = { + { NULL, 0 } +}; + +DEVICE bus_dev = { + "BUS", &bus_unit, bus_reg, bus_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + &bus_ex, &bus_dep, &bus_reset, + NULL, NULL, NULL, + NULL, 0, 0, + bus_dt, NULL, NULL, &bus_show_help, NULL, NULL, &bus_description +}; + +/* Simulator-specific commands */ +static CTAB bus_cmd_tbl[] = { + { "REG", &z80_cmd_reg, 0, "REG Display registers\n" }, + { "MEM", &bus_cmd_memory, 0, "MEM
Dump a block of memory\n" }, + { "HEXLOAD", &bus_hexload_command, 0, "HEXLOAD [fname]