From 627e6a6b135261f9dcb46dc1a8665c7fe67d3f7c Mon Sep 17 00:00:00 2001 From: Patrick Linstruth Date: Sun, 18 Jan 2026 08:22:41 -0500 Subject: [PATCH] Altair8800: New DAZZLER, PMMI devices and other features Adds Cromemco DAZZLER video device Adds PMMI MM-103 modem device Adds SET RAM PROT/UNPROT=pages Adds Martin Eberhard's CDBL ROM Corrects file format for cromemco and pmmi header files. --- Altair8800/altair8800_sys.c | 2 + Altair8800/altair8800_sys.h | 2 + Altair8800/cromemco_dazzler.c | 615 ++++++++++++++++++++ Altair8800/cromemco_dazzler.h | 56 ++ Altair8800/pmmi_mm103.c | 705 +++++++++++++++++++++++ Altair8800/pmmi_mm103.h | 93 +++ Altair8800/s100_bus.c | 14 - Altair8800/s100_cpu.c | 5 + Altair8800/s100_cpu.h | 1 + Altair8800/s100_ram.c | 81 ++- Altair8800/s100_rom.c | 16 +- Altair8800/s100_rom.h | 4 +- Altair8800/s100_roms.h | 47 ++ Visual Studio Projects/Altair8800.vcproj | 16 + makefile | 2 + 15 files changed, 1623 insertions(+), 36 deletions(-) create mode 100644 Altair8800/cromemco_dazzler.c create mode 100644 Altair8800/cromemco_dazzler.h create mode 100644 Altair8800/pmmi_mm103.c create mode 100644 Altair8800/pmmi_mm103.h diff --git a/Altair8800/altair8800_sys.c b/Altair8800/altair8800_sys.c index ad314c8a..20ce33a8 100644 --- a/Altair8800/altair8800_sys.c +++ b/Altair8800/altair8800_sys.c @@ -74,6 +74,8 @@ DEVICE *sim_devices[] = { &m2sio0_dev, &m2sio1_dev, &acr_dev, + &daz_dev, + &pmmi_dev, &sio_dev, &sbc200_dev, &tarbell_dev, diff --git a/Altair8800/altair8800_sys.h b/Altair8800/altair8800_sys.h index b5a1a225..0adf69dd 100644 --- a/Altair8800/altair8800_sys.h +++ b/Altair8800/altair8800_sys.h @@ -48,6 +48,8 @@ extern DEVICE mdsk_dev; extern DEVICE m2sio0_dev; extern DEVICE m2sio1_dev; extern DEVICE acr_dev; +extern DEVICE daz_dev; +extern DEVICE pmmi_dev; extern DEVICE sio_dev; extern DEVICE sbc200_dev; extern DEVICE tarbell_dev; diff --git a/Altair8800/cromemco_dazzler.c b/Altair8800/cromemco_dazzler.c new file mode 100644 index 00000000..9a395c44 --- /dev/null +++ b/Altair8800/cromemco_dazzler.c @@ -0,0 +1,615 @@ +/* cromemco_dazzler.c: Cromemco DAZZLER and JS-1 Joystick + + Copyright (c) 2026 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 18-Jan-2026 Initial version + + ================================================================== + + This device simulates the Cromemco Dazzler and D+7A with JS-1 Joystick + Console. + +*/ + +#include "sim_defs.h" +#include "sim_video.h" +#include "altair8800_defs.h" +#include "s100_bus.h" +#include "s100_cpu.h" +#include "cromemco_dazzler.h" + +/* +** Public VID_DISPLAY for other devices that may want +** to access the video display directly, such as joystick +** events. +*/ +VID_DISPLAY *daz_vptr = NULL; + +static t_bool daz_0e = 0x00; +static t_bool daz_0f = 0x80; +static uint32 daz_addr = 0x0000; +static t_bool daz_frame = 0x3f; +static uint8 daz_resolution = 32; +static uint16 daz_pages = 1; +static uint16 daz_window_width = 640; +static uint16 daz_window_height = 640; +static uint16 daz_screen_width = 32; +static uint16 daz_screen_height = 32; +static uint16 daz_screen_pixels = 32 * 32; +static uint8 daz_color = 0; +static uint32 daz_surface[DAZ_PIXELS]; +static uint32 daz_cpalette[16]; +static uint32 daz_gpalette[16]; + +#define DAZ_SHOW_VIDEO(b) (b & DAZ_ON) ? "ON" : "OFF" +#define DAZ_SHOW_RES(b) (b & DAZ_RESX4) ? "X4" : "NORMAL" +#define DAZ_SHOW_MEMSIZE(b) (b & DAZ_2K) ? "2K" : "512" +#define DAZ_SHOW_COLOR(b) (b & DAZ_COLOR) ? "COLOR" : "B/W" + +extern t_stat exdep_cmd(int32 flag, CONST char *cptr); + +static const char *daz_description(DEVICE *dptr); +static t_stat daz_svc(UNIT *uptr); +static t_stat daz_reset(DEVICE *dptr); +static t_stat daz_boot(int32 unitno, DEVICE *dptr); +static void daz_set_0f(uint8 val); +static t_stat daz_set_video(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat daz_show_video(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat daz_set_resolution(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat daz_show_resolution(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat daz_set_memsize(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat daz_show_memsize(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static t_stat daz_set_color(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +static t_stat daz_show_color(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +static int32 daz_io(const int32 port, const int32 io, const int32 data); +static t_stat daz_open_video(void); +static t_stat daz_close_video(void); +static void daz_resize_video(void); +static void daz_refresh(void); +static void daz_render_normal(void); +static void daz_render_x4(void); +static int32 daz_quad_surfacex(int q); +static int32 daz_quad_surfacey(int q); +static t_stat daz_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +/* Debug flags */ +#define VERBOSE_MSG (1 << 0) + +/* + DAZZLER data structures + + daz_dev DAZ device descriptor + daz_unit DAZ unit descriptor + daz_reg DAZ register list +*/ + +static RES daz_res = { DAZ_IO_BASE, DAZ_IO_SIZE, 0 ,0, NULL }; + +static UNIT daz_unit = { + UDATA (&daz_svc, 0, 0), 33000 /* 30 fps */ +}; + +static REG daz_reg[] = { + { NULL } +}; + +static DEBTAB daz_debug[] = { + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { "JOYSTICK", SIM_VID_DBG_JOYSTICK, "Joystick messages" }, + { "VIDEO", SIM_VID_DBG_VIDEO, "Video messages" }, + { 0 } +}; + +static MTAB daz_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "VIDEO", "VIDEO", + &daz_set_video, &daz_show_video, NULL, "DAZZLER Video [ ON | OFF ]" }, + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "DAZZLER Base I/O Address" }, + { MTAB_XTD|MTAB_VDV, 0, "MEMSIZE", "MEMSIZE", + &daz_set_memsize, &daz_show_memsize, NULL, "DAZZLER Memory Size [ 512 | 2K ]" }, + { MTAB_XTD|MTAB_VDV, 0, "RESOLUTION", "RESOLUTION", + &daz_set_resolution, &daz_show_resolution, NULL, "DAZZLER Resolution [ NORMAL | HIGH ]" }, + { MTAB_XTD|MTAB_VDV, 0, "COLOR", "COLOR", + &daz_set_color, &daz_show_color, NULL, "DAZZLER Color [ BW | COLOR ]" }, + { 0 } +}; + +DEVICE daz_dev = { + "DAZZLER", &daz_unit, daz_reg, daz_mod, + 1, ADDRRADIX, ADDRWIDTH, 1, DATARADIX, DATAWIDTH, + NULL, NULL, &daz_reset, + &daz_boot, NULL, NULL, + &daz_res, DEV_DEBUG | DEV_DIS | DEV_DISABLE, 0, + daz_debug, NULL, NULL, &daz_show_help, NULL, NULL, + &daz_description +}; + +/* + DAZZLER routines + + daz_description + daz_svc + daz_reset +*/ +static const char *daz_description (DEVICE *dptr) +{ + return "Cromemco Dazzler"; +} + +static t_stat daz_svc(UNIT *uptr) +{ + daz_refresh(); + + sim_activate_after_abs(uptr, uptr->wait); + + return SCPE_OK; +} + +static t_stat daz_reset(DEVICE *dptr) +{ + t_stat r = SCPE_OK; + + if (dptr->flags & DEV_DIS) { + s100_bus_remio(daz_res.io_base, daz_res.io_size, &daz_io); + + sim_cancel(&daz_unit); + + if (daz_vptr != NULL) { + return daz_close_video(); + } + } else { + s100_bus_addio(daz_res.io_base, daz_res.io_size, &daz_io, "DAZZLER"); + + if (daz_vptr == NULL) { + daz_open_video(); + } else { + sim_activate_after_abs(&daz_unit, daz_unit.wait); + } + } + + return r; +} + +static t_stat daz_boot(int32 unitno, DEVICE *dptr) +{ + ChipType chiptype = cpu_get_chiptype(); + + if (chiptype == CHIP_TYPE_8080) { + exdep_cmd(EX_D, "-m 100 MVI A,01H"); + exdep_cmd(EX_D, "-m 102 ORI 80H"); + exdep_cmd(EX_D, "-m 104 OUT 0EH"); + exdep_cmd(EX_D, "-m 106 MVI A,10H"); + exdep_cmd(EX_D, "-m 108 OUT 0FH"); + exdep_cmd(EX_D, "-m 10A LXI H,200H"); + exdep_cmd(EX_D, "-m 10D MVI C,32"); + exdep_cmd(EX_D, "-m 10F MVI B,16"); + exdep_cmd(EX_D, "-m 111 XRA A"); + exdep_cmd(EX_D, "-m 112 MOV M,A"); + exdep_cmd(EX_D, "-m 113 ADI 11H"); + exdep_cmd(EX_D, "-m 115 INX H"); + exdep_cmd(EX_D, "-m 116 DCR B"); + exdep_cmd(EX_D, "-m 117 JNZ 112H"); + exdep_cmd(EX_D, "-m 11A DCR C"); + exdep_cmd(EX_D, "-m 11B JNZ 10FH"); + exdep_cmd(EX_D, "-m 11E JMP 11EH"); + } else if (chiptype == CHIP_TYPE_Z80) { + exdep_cmd(EX_D, "-m 100 LD A,01H"); + exdep_cmd(EX_D, "-m 102 OR 80H"); + exdep_cmd(EX_D, "-m 104 OUT (0EH),A"); + exdep_cmd(EX_D, "-m 106 LD A,10H"); + exdep_cmd(EX_D, "-m 108 OUT (0FH),A"); + exdep_cmd(EX_D, "-m 10A LD HL,200H"); + exdep_cmd(EX_D, "-m 10D LD C,32"); + exdep_cmd(EX_D, "-m 10F LD B,16"); + exdep_cmd(EX_D, "-m 111 XOR A"); + exdep_cmd(EX_D, "-m 112 LD (HL),A"); + exdep_cmd(EX_D, "-m 113 ADD A,11H"); + exdep_cmd(EX_D, "-m 115 INC HL"); + exdep_cmd(EX_D, "-m 116 DEC B"); + exdep_cmd(EX_D, "-m 117 JP NZ,112H"); + exdep_cmd(EX_D, "-m 11A DEC C"); + exdep_cmd(EX_D, "-m 11B JP NZ,10FH"); + exdep_cmd(EX_D, "-m 11E JP 11EH"); + } + + *((int32 *) sim_PC->loc) = 0x0100; + + return SCPE_OK; +} + +static int32 daz_io(const int32 port, const int32 io, const int32 data) +{ + int32 p = port - daz_res.io_base; + + if (io == 0) { /* IN */ + switch (p) { + case 0x00: /* 0E */ + daz_frame = 0x7f; + if ((sim_os_msec() % 30) > 25) { + daz_frame &= ~DAZ_EOF; + } else { + daz_frame |= (sim_os_msec() & 1) ? 0x00 : DAZ_EVEN; + } + + return daz_frame; + + case 0x01: /* 0F */ + sim_debug(VERBOSE_MSG, &daz_dev, "Unspecified IN 0x%02X\n", port); + break; + } + } else { /* OUT */ + switch (p) { + case 0x00: /* 0E */ + daz_0e = data; + daz_addr = (data & 0x7f) << 9; + + sim_debug(VERBOSE_MSG, &daz_dev, "New video address 0x%04X Video is %s\n", daz_addr, daz_0e & DAZ_ON ? "ON" : "OFF"); + break; + + case 0x01: /* 0F */ + daz_set_0f(data); + break; + } + } + + return 0xff; +} + +static t_stat daz_open_video(void) +{ + t_stat r = SCPE_OK; + int i; + + if (daz_vptr == NULL) { + sim_debug(VERBOSE_MSG, &daz_dev, "Opening new video window w:%d h:%d\n", daz_window_width, daz_window_height); + + r = vid_open_window(&daz_vptr, &daz_dev, "Display", daz_window_width, daz_window_height, SIM_VID_IGNORE_VBAR | SIM_VID_RESIZABLE); /* video buffer size */ + + if (r != SCPE_OK) { + sim_printf("Could not open video window r=%X\n", r); + return r; + } + + daz_resize_video(); + + daz_cpalette[0] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x00); + daz_cpalette[1] = vid_map_rgb_window(daz_vptr, 0x80, 0x00, 0x00); + daz_cpalette[2] = vid_map_rgb_window(daz_vptr, 0x00, 0x80, 0x00); + daz_cpalette[3] = vid_map_rgb_window(daz_vptr, 0x80, 0x80, 0x00); + daz_cpalette[4] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x80); + daz_cpalette[5] = vid_map_rgb_window(daz_vptr, 0x80, 0x00, 0x80); + daz_cpalette[6] = vid_map_rgb_window(daz_vptr, 0x00, 0x80, 0x80); + daz_cpalette[7] = vid_map_rgb_window(daz_vptr, 0x80, 0x80, 0x80); + daz_cpalette[8] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x00); + daz_cpalette[9] = vid_map_rgb_window(daz_vptr, 0xff, 0x00, 0x00); + daz_cpalette[10] = vid_map_rgb_window(daz_vptr, 0x00, 0xff, 0x00); + daz_cpalette[11] = vid_map_rgb_window(daz_vptr, 0xff, 0xff, 0x00); + daz_cpalette[12] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0xff); + daz_cpalette[13] = vid_map_rgb_window(daz_vptr, 0xff, 0x00, 0xff); + daz_cpalette[14] = vid_map_rgb_window(daz_vptr, 0x00, 0xff, 0xff); + daz_cpalette[15] = vid_map_rgb_window(daz_vptr, 0xff, 0xff, 0xff); + daz_gpalette[0] = vid_map_rgb_window(daz_vptr, 0x00, 0x00, 0x00); + daz_gpalette[1] = vid_map_rgb_window(daz_vptr, 0x10, 0x10, 0x10); + daz_gpalette[2] = vid_map_rgb_window(daz_vptr, 0x20, 0x20, 0x20); + daz_gpalette[3] = vid_map_rgb_window(daz_vptr, 0x30, 0x30, 0x30); + daz_gpalette[4] = vid_map_rgb_window(daz_vptr, 0x40, 0x40, 0x40); + daz_gpalette[5] = vid_map_rgb_window(daz_vptr, 0x50, 0x50, 0x50); + daz_gpalette[6] = vid_map_rgb_window(daz_vptr, 0x60, 0x60, 0x60); + daz_gpalette[7] = vid_map_rgb_window(daz_vptr, 0x70, 0x70, 0x70); + daz_gpalette[8] = vid_map_rgb_window(daz_vptr, 0x80, 0x80, 0x80); + daz_gpalette[9] = vid_map_rgb_window(daz_vptr, 0x90, 0x90, 0x90); + daz_gpalette[10] = vid_map_rgb_window(daz_vptr, 0xa0, 0xa0, 0xa0); + daz_gpalette[11] = vid_map_rgb_window(daz_vptr, 0xb0, 0xb0, 0xb0); + daz_gpalette[12] = vid_map_rgb_window(daz_vptr, 0xc0, 0xc0, 0xc0); + daz_gpalette[13] = vid_map_rgb_window(daz_vptr, 0xd0, 0xd0, 0xd0); + daz_gpalette[14] = vid_map_rgb_window(daz_vptr, 0xe0, 0xe0, 0xe0); + daz_gpalette[15] = vid_map_rgb_window(daz_vptr, 0xff, 0xff, 0xff); + + for (i = 0; i < daz_screen_pixels; i++) { + daz_surface[i] = 0; + } + } + + sim_activate_after_abs(&daz_unit, daz_unit.wait); + + return r; +} + +static t_stat daz_close_video(void) +{ + t_stat r; + + sim_debug(VERBOSE_MSG, &daz_dev, "Closing video window\n"); + + if ((r = vid_close_window(daz_vptr)) == SCPE_OK) { + sim_cancel(&daz_unit); + + daz_vptr = NULL; + } + + return r; +} + +static void daz_resize_video(void) +{ + if (daz_vptr != NULL) { + vid_render_set_logical_size(daz_vptr, daz_screen_width, daz_screen_height); + if (!sim_is_running) { + daz_refresh(); + } + } +} + +/* + * Draw and refresh the screen in the video window + */ +static void daz_refresh(void) { + if (daz_vptr != NULL) { + if (daz_0f & DAZ_RESX4) { + daz_render_x4(); + } else { + daz_render_normal(); + } + vid_draw_window(daz_vptr, 0, 0, daz_screen_width, daz_screen_height, daz_surface); + vid_refresh_window(daz_vptr); + } +} + +static void daz_render_normal(void) +{ + int q, x, y; + int32 maddr = daz_addr; + int32 saddr = 0; + + for (q = 0; q < daz_pages; q++) { + for (y = daz_quad_surfacey(q); y < daz_quad_surfacey(q) + 32; y++) { + for (x = daz_quad_surfacex(q); x < daz_quad_surfacex(q) + 32; x+= 2) { + saddr = (y * daz_resolution) + x; + if (!(daz_0e & DAZ_ON)) { + daz_surface[saddr] = 0x00; + daz_surface[saddr+1] = 0x00; + } else if (daz_0f & DAZ_COLOR) { + daz_surface[saddr] = daz_cpalette[s100_bus_memr(maddr) & 0x0f]; + daz_surface[saddr+1] = daz_cpalette[(s100_bus_memr(maddr) & 0xf0) >> 4]; + } else { + daz_surface[saddr] = daz_gpalette[s100_bus_memr(maddr) & 0x0f]; + daz_surface[saddr+1] = daz_gpalette[(s100_bus_memr(maddr) & 0xf0) >> 4]; + } + maddr++; + } + } + } +} + +static void daz_render_x4(void) +{ + int b, q, x, y; + int32 maddr = daz_addr; + int32 saddr = 0; + int32 soffset[] = {0, 1, daz_resolution, daz_resolution + 1, 2, 3, daz_resolution + 2, daz_resolution + 3}; + uint32 color; + + if (daz_0f & DAZ_COLOR) { + color = daz_cpalette[daz_color]; + } else { + color = daz_gpalette[daz_color]; + } + + for (q = 0; q < daz_pages; q++) { + for (y = daz_quad_surfacey(q); y < daz_quad_surfacey(q) + 64; y+=2) { + for (x = daz_quad_surfacex(q); x < daz_quad_surfacex(q) + 64; x += 4) { + saddr = (y * daz_resolution) + x; + for (b = 0; b < 8; b++) { + if (daz_0e & DAZ_ON) { + daz_surface[saddr + soffset[b]] = (s100_bus_memr(maddr) & (1 << b)) ? color : 0; + } else { + daz_surface[saddr + soffset[b]] = 0x00; + } + } + maddr++; + } + } + } +} + +static int32 daz_quad_surfacex(int q) +{ + if (q == 1 || q == 3) { + return daz_resolution / ((daz_0f & DAZ_RESX4) ? 2 : 2); + } + + return 0; +} + +static int32 daz_quad_surfacey(int q) +{ + if (q == 2 || q == 3) { + return daz_resolution / ((daz_0f & DAZ_RESX4) ? 2 : 2); + } + + return 0; +} + +static void daz_set_0f(uint8 val) { + uint8 old = daz_0f; + + /* Update daz_0f register */ + daz_0f = val; + daz_color = daz_0f & 0x0f; + + /* Did resolution change? */ + if ((daz_0f & (DAZ_RESX4 | DAZ_2K)) != (old & (DAZ_RESX4 | DAZ_2K))) { + daz_resolution = 32; + daz_pages = 1; + if (daz_0f & DAZ_RESX4) { + daz_resolution *= 2; + } + if (daz_0f & DAZ_2K) { + daz_pages = 4; + daz_resolution *= 2; + } + + sim_debug(VERBOSE_MSG, &daz_dev, "Setting resolution to %02X %dx%d (%d pages) %s %s\n", + daz_0f, daz_resolution, daz_resolution, daz_pages, DAZ_SHOW_RES(daz_0f), DAZ_SHOW_MEMSIZE(daz_0f)); + + daz_screen_width = daz_resolution; + daz_screen_height = daz_resolution; + daz_screen_pixels = daz_screen_width * daz_screen_height; + + daz_resize_video(); + } + + if (!sim_is_running) { + daz_refresh(); + } +} + +static t_stat daz_set_video(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "OFF", strlen(cptr))) { + daz_0e &= ~DAZ_ON; + } else if (!strncmp(cptr, "ON", strlen(cptr))) { + daz_0e |= DAZ_ON; + } else { + return SCPE_ARG; + } + + if (!sim_is_running) { + daz_refresh(); + } + + return SCPE_OK; +} + +static t_stat daz_show_video(FILE *st, UNIT *uptr, int32 val, CONST void *desc) { + if (!st) return SCPE_IERR; + + fprintf(st, "VIDEO=%s", DAZ_SHOW_VIDEO(daz_0e)); + + return SCPE_OK; +} + +static t_stat daz_set_resolution(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + uint8 old = daz_0f; + + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "NORMAL", strlen(cptr))) { + old &= ~DAZ_RESX4; + } else if (!strncmp(cptr, "HIGH", strlen(cptr))) { + old |= DAZ_RESX4; + } else { + return SCPE_ARG; + } + + daz_set_0f(old); + + return SCPE_OK; +} + +static t_stat daz_show_resolution(FILE *st, UNIT *uptr, int32 val, CONST void *desc) { + if (!st) return SCPE_IERR; + + fprintf(st, "RES=%s", DAZ_SHOW_RES(daz_0f)); + + return SCPE_OK; +} + +static t_stat daz_set_memsize(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + uint8 old = daz_0f; + + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "512", strlen(cptr))) { + old &= ~DAZ_2K; + } else if (!strncmp(cptr, "2K", strlen(cptr))) { + old |= DAZ_2K; + } else { + return SCPE_ARG; + } + + daz_set_0f(old); + + return SCPE_OK; +} + +static t_stat daz_show_memsize(FILE *st, UNIT *uptr, int32 val, CONST void *desc) { + if (!st) return SCPE_IERR; + + fprintf(st, "MEMSIZE=%s @ %04X", DAZ_SHOW_MEMSIZE(daz_0f), daz_addr); + + return SCPE_OK; +} + +static t_stat daz_set_color(UNIT *uptr, int32 val, CONST char *cptr, void *desc) +{ + uint8 old = daz_0f; + + if (!cptr) return SCPE_IERR; + if (!strlen(cptr)) return SCPE_ARG; + + /* this assumes that the parameter has already been upcased */ + if (!strncmp(cptr, "BW", strlen(cptr))) { + old &= ~DAZ_COLOR; + } else if (!strncmp(cptr, "COLOR", strlen(cptr))) { + old |= DAZ_COLOR; + } else { + return SCPE_ARG; + } + + daz_set_0f(old); + + return SCPE_OK; +} + +static t_stat daz_show_color(FILE *st, UNIT *uptr, int32 val, CONST void *desc) { + if (!st) return SCPE_IERR; + + fprintf(st, "%s", DAZ_SHOW_COLOR(daz_0f)); + + return SCPE_OK; +} + +static t_stat daz_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nCROMEMCO DAZZLER (%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/cromemco_dazzler.h b/Altair8800/cromemco_dazzler.h new file mode 100644 index 00000000..3de4ff54 --- /dev/null +++ b/Altair8800/cromemco_dazzler.h @@ -0,0 +1,56 @@ +/* cromemco_dazzler.h + + Copyright (c) 2026 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 01/18/26 Initial version + +*/ + +#ifndef _CROMEMCO_DAZZLER_H +#define _CROMEMCO_DAZZLER_H + + +#define DAZ_PIXELS (128 * 128) /* total number of pixels */ + +#define DAZ_IO_BASE 0x0e +#define DAZ_IO_SIZE 2 +#define DAZ_MEM_SIZE 2048 +#define DAZ_MEM_MASK (2048 - 1) + +#define DAZ_ON 0x80 /* On/Off */ +#define DAZ_RESX4 0x40 /* Resolution X 4 */ +#define DAZ_2K 0x20 /* Picture in 2K bytes of memory */ +#define DAZ_COLOR 0x10 /* Picture in 2K bytes of memory */ +#define DAZ_HIGH 0x08 /* High intensity color */ +#define DAZ_BLUE 0x04 /* Blue */ +#define DAZ_GREEN 0x02 /* Green */ +#define DAZ_RED 0x01 /* Red */ +#define DAZ_EOF 0x40 /* End of Frame */ +#define DAZ_EVEN 0x80 /* Even Line */ + +extern VID_DISPLAY *daz_vptr; + +#endif + diff --git a/Altair8800/pmmi_mm103.c b/Altair8800/pmmi_mm103.c new file mode 100644 index 00000000..eb1fcc3f --- /dev/null +++ b/Altair8800/pmmi_mm103.c @@ -0,0 +1,705 @@ +/* pmmi_mm103: PMMI MM-103 MODEM + + Copyright (c) 2026 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 18-Jan-2026 Initial version + + ================================================================== + + This device emulates a PMMI Communications MM-103 Modem & Communications + adapter. + + The MM-103 uses 4 input and 4 output addresses. This driver defaults to + E0-E3 hex. + + The MM-103 uses the Motorola MC6860L digital modem chip. This device does + not have the ability to emulate the modulation and demodulation functions + or the ability to connect to a phone line. All modem features, such as + switch hook, dial tone detection, and dialing, are emulated in such a way + that most software written for the MM-103 should function in some useful + fashion. + + To provide any useful functionality, this device needs to be attached to + a socket or serial port. Enter "HELP PMMI" at the "simh>" prompt for + additional information. +*/ + +#include "sim_defs.h" +#include "sim_tmxr.h" +#include "altair8800_defs.h" +#include "s100_bus.h" +#include "pmmi_mm103.h" + +#define DEVICE_DESC "PMMI MM-103 MODEM" +#define DEVICE_NAME "PMMI" + +static int32 poc = TRUE; /* Power On Clear */ + +/* Debug flags */ +#define STATUS_MSG (1 << 0) +#define ERROR_MSG (1 << 1) +#define VERBOSE_MSG (1 << 2) + +typedef struct { + int32 conn; /* Connected Status */ + int32 baud; /* Baud rate */ + int32 dtr; /* DTR Status */ + int32 txp; /* Transmit Pending */ + int32 stb; /* Status Buffer */ + int32 ireg0; /* In Register 0 */ + int32 ireg1; /* In Register 1 */ + int32 ireg2; /* In Register 2 */ + int32 ireg3; /* In Register 3 */ + int32 oreg0; /* Out Register 0 */ + int32 oreg1; /* Out Register 1 */ + int32 oreg2; /* Out Register 2 */ + int32 oreg3; /* Out Register 3 */ + int32 intmsk; /* Interrupt Mask */ + uint32 ptimer; /* Next Pulse Timer */ + uint32 dtimer; /* Next DT Timer */ + uint32 flags; /* Original Flags */ +} PMMI_CTX; + +extern t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc); +extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc); +extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type, + int32 (*routine)(const int32, const int32, const int32), const char* name, uint8 unmap); + + +static const char* pmmi_description(DEVICE *dptr); +static t_stat pmmi_svc(UNIT *uptr); +static t_stat pmmi_reset(DEVICE *dptr); +static t_stat pmmi_attach(UNIT *uptr, CONST char *cptr); +static t_stat pmmi_detach(UNIT *uptr); +static t_stat pmmi_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat pmmi_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc); +static t_stat pmmi_config_line(UNIT *uptr); +static int32 pmmi_io(int32 addr, int32 io, int32 data); +static int32 pmmi_reg0(int32 io, int32 data); +static int32 pmmi_reg1(int32 io, int32 data); +static int32 pmmi_reg2(int32 io, int32 data); +static int32 pmmi_reg3(int32 io, int32 data); +static t_stat pmmi_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat pmmi_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); + +/* Debug Flags */ +static DEBTAB pmmi_dt[] = { + { "STATUS", STATUS_MSG, "Status messages" }, + { "ERROR", ERROR_MSG, "Error messages" }, + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, + { NULL, 0 } +}; + +/* Terminal multiplexer library descriptors */ + +static TMLN pmmi_tmln[] = { /* line descriptors */ + { 0 } +}; + + +static TMXR pmmi_tmxr = { /* multiplexer descriptor */ + 1, /* number of terminal lines */ + 0, /* listening port (reserved) */ + 0, /* master socket (reserved) */ + pmmi_tmln, /* line descriptor array */ + NULL, /* line connection order */ + NULL /* multiplexer device (derived internally) */ +}; + +static RES pmmi_res = { PMMI_IOBASE, PMMI_IOSIZE, 0, 0, &pmmi_tmxr }; + +static MTAB pmmi_mod[] = { + { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", + &set_iobase, &show_iobase, NULL, "Sets MITS 2SIO base I/O address" }, + { UNIT_PMMI_RTS, UNIT_PMMI_RTS, "RTS", "RTS", NULL, NULL, NULL, + "RTS follows DTR (default)" }, + { UNIT_PMMI_RTS, 0, "NORTS", "NORTS", NULL, NULL, NULL, + "RTS does not follow DTR" }, + { MTAB_XTD|MTAB_VDV|MTAB_VALR, 0, "BAUD", "BAUD", &pmmi_set_baud, &pmmi_show_baud, + NULL, "Set baud rate (default=300)" }, + + { MTAB_XTD | MTAB_VUN, UNIT_PMMI_CONSOLE, NULL, "CONSOLE", &pmmi_set_console, NULL, NULL, "Set as CONSOLE" }, + { MTAB_XTD | MTAB_VUN, 0, NULL, "NOCONSOLE", &pmmi_set_console, NULL, NULL, "Remove as CONSOLE" }, + + { 0 } +}; + +static PMMI_CTX pmmi_ctx = { 0, PMMI_BAUD, 1 }; + +static UNIT pmmi_unit[] = { + { UDATA (&pmmi_svc, UNIT_ATTABLE | UNIT_DISABLE | UNIT_PMMI_RTS, 0), PMMI_WAIT }, +}; + +static REG pmmi_reg[] = { + { HRDATAD (IREG0, pmmi_ctx.ireg0, 8, "PMMI input register 0"), }, + { HRDATAD (IREG1, pmmi_ctx.ireg1, 8, "PMMI input register 1"), }, + { HRDATAD (IREG2, pmmi_ctx.ireg2, 8, "PMMI input register 2"), }, + { HRDATAD (IREG3, pmmi_ctx.ireg3, 8, "PMMI input register 3"), }, + { HRDATAD (OREG0, pmmi_ctx.oreg0, 8, "PMMI output register 0"), }, + { HRDATAD (OREG1, pmmi_ctx.oreg1, 8, "PMMI output register 1"), }, + { HRDATAD (OREG2, pmmi_ctx.oreg2, 8, "PMMI output register 2"), }, + { HRDATAD (OREG3, pmmi_ctx.oreg3, 8, "PMMI output register 3"), }, + { HRDATAD (TXP, pmmi_ctx.txp, 8, "PMMI tx data pending"), }, + { FLDATAD (CON, pmmi_ctx.conn, 0, "PMMI connection status"), }, + { DRDATAD (BAUD, pmmi_ctx.baud, 8, "PMMI calculated baud rate"), }, + { HRDATAD (INTMSK, pmmi_ctx.intmsk, 8, "PMMI interrupt mask"), }, + { FLDATAD (TBMT, pmmi_ctx.ireg0, 0, "PMMI TBMT status"), }, + { FLDATAD (DAV, pmmi_ctx.ireg0, 1, "PMMI DAV status"), }, + { FLDATAD (OR, pmmi_ctx.ireg0, 4, "PMMI OVRN status"), }, + { FLDATAD (DT, pmmi_ctx.ireg2, 0, "PMMI dial tone status (active low)"), }, + { FLDATAD (RNG, pmmi_ctx.ireg2, 1, "PMMI ringing status (active low)"), }, + { FLDATAD (CTS, pmmi_ctx.ireg2, 2, "PMMI CTS status (active low)"), }, + { FLDATAD (AP, pmmi_ctx.ireg2, 0, "PMMI answer phone status (active low)"), }, + { FLDATAD (PULSE, pmmi_ctx.ireg2, 7, "PMMI timer pulse"), }, + { DRDATAD (TIMER, pmmi_ctx.ptimer, 32, "PMMI timer pulse ms"), }, + { DRDATAD (WAIT, pmmi_unit[0].wait, 32, "PMMI wait cycles"), }, + { NULL } +}; + +DEVICE pmmi_dev = { + DEVICE_NAME, /* name */ + pmmi_unit, /* unit */ + pmmi_reg, /* registers */ + pmmi_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 */ + &pmmi_reset, /* reset routine */ + NULL, /* boot routine */ + &pmmi_attach, /* attach routine */ + &pmmi_detach, /* detach routine */ + &pmmi_res, /* context */ + (DEV_DISABLE | DEV_DIS | DEV_DEBUG | DEV_MUX), /* flags */ + 0, /* debug control */ + pmmi_dt, /* debug flags */ + NULL, /* mem size routine */ + NULL, /* logical name */ + &pmmi_show_help, /* help */ + NULL, /* attach help */ + NULL, /* context for help */ + &pmmi_description /* description */ +}; + +static const char* pmmi_description(DEVICE *dptr) +{ + return DEVICE_DESC; +} + +static t_stat pmmi_reset(DEVICE *dptr) +{ + if (dptr->flags & DEV_DIS) { /* Disable Device */ + s100_bus_remio(pmmi_res.io_base, pmmi_res.io_size, &pmmi_io); + + poc = TRUE; + } + else { + if (poc) { + s100_bus_addio(pmmi_res.io_base, pmmi_res.io_size, &pmmi_io, DEVICE_NAME); + + poc = FALSE; + } + } + + /* Set DEVICE for this UNIT */ + dptr->units[0].dptr = dptr; + + /* Enable TMXR modem control passthrough */ + tmxr_set_modem_control_passthru(pmmi_res.tmxr); + + /* Reset status registers */ + pmmi_ctx.ireg0 = 0; + pmmi_ctx.ireg1 = 0; + pmmi_ctx.ireg2 = PMMI_RNG | PMMI_CTS | PMMI_DT | PMMI_AP; + pmmi_ctx.ireg3 = 0; + pmmi_ctx.oreg0 = 0; + pmmi_ctx.oreg1 = 0; + pmmi_ctx.oreg2 = 0; + pmmi_ctx.oreg3 = 0; + pmmi_ctx.txp = 0; + pmmi_ctx.intmsk = 0; + pmmi_ctx.ptimer = sim_os_msec() + 40; + pmmi_ctx.dtimer = 0; + + if (!(dptr->flags & DEV_DIS)) { + sim_activate(&dptr->units[0], dptr->units[0].wait); + } else { + sim_cancel(&dptr->units[0]); + } + + sim_debug(STATUS_MSG, dptr, "reset adapter.\n"); + + return SCPE_OK; +} + +static t_stat pmmi_svc(UNIT *uptr) +{ + int32 c,s,ireg2; + t_stat r = SCPE_OK; + uint32 ms; + + /* Check for new incoming connection */ + if (uptr->flags & UNIT_ATT) { + if (tmxr_poll_conn(pmmi_res.tmxr) >= 0) { /* poll connection */ + + /* Clear DTR and RTS if serial port */ + if (pmmi_res.tmxr->ldsc->serport) { + s = TMXR_MDM_DTR | ((pmmi_dev.units[0].flags & UNIT_PMMI_RTS) ? TMXR_MDM_RTS : 0); + tmxr_set_get_modem_bits(pmmi_res.tmxr->ldsc, 0, s, NULL); + } + + pmmi_res.tmxr->ldsc->rcve = 1; /* Enable receiver */ + pmmi_ctx.conn = 1; /* 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(pmmi_res.tmxr->ldsc, 0, 0, &s); + + ireg2 = pmmi_ctx.ireg2; + pmmi_ctx.ireg2 &= ~PMMI_CTS; + pmmi_ctx.ireg2 |= (s & TMXR_MDM_CTS) ? 0 : PMMI_CTS; /* Active Low */ + + /* CTS status changed */ + if ((ireg2 ^ pmmi_ctx.ireg2) & PMMI_CTS) { + if (pmmi_ctx.ireg2 & PMMI_CTS) { /* If no CTS, set AP bit */ + pmmi_ctx.ireg2 |= PMMI_AP; /* Answer Phone Bit (active low) */ + } + sim_debug(STATUS_MSG, uptr->dptr, "CTS state changed to %s.\n", (pmmi_ctx.ireg2 & PMMI_CTS) ? "LOW" : "HIGH"); + } + + pmmi_ctx.ireg2 &= ~PMMI_RNG; + pmmi_ctx.ireg2 |= (s & TMXR_MDM_RNG) ? 0 : PMMI_RNG; /* Active Low */ + + /* RNG status changed */ + if ((ireg2 ^ pmmi_ctx.ireg2) & PMMI_RNG) { + /* Answer Phone Bit on RI */ + if (!(pmmi_ctx.ireg2 & PMMI_RNG)) { + pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */ + } + + sim_debug(STATUS_MSG, uptr->dptr, "RNG state changed to %s.\n", (pmmi_ctx.ireg2 & PMMI_RNG) ? "LOW" : "HIGH"); + } + + /* Enable receiver if CTS is active low */ + pmmi_res.tmxr->ldsc->rcve = !(pmmi_ctx.ireg2 & PMMI_CTS); + + /* If socket, connection status follows CTS */ + if (!pmmi_res.tmxr->ldsc->serport) { + pmmi_ctx.conn = !(pmmi_ctx.ireg2 & PMMI_CTS); + } + } + + /* TX data */ + if (pmmi_ctx.txp) { + if (uptr->flags & UNIT_ATT) { + /* + ** If CTS active low, send byte + ** otherwise, toss character + */ + if (!(pmmi_ctx.ireg2 & PMMI_CTS)) { + r = tmxr_putc_ln(pmmi_res.tmxr->ldsc, pmmi_ctx.oreg1); + } + pmmi_ctx.txp = 0; /* Reset TX Pending */ + } else { + r = sim_putchar(pmmi_ctx.oreg1); + pmmi_ctx.txp = 0; /* Reset TX Pending */ + } + + if (r == SCPE_LOST) { + pmmi_ctx.conn = 0; /* Connection was lost */ + sim_debug(STATUS_MSG, uptr->dptr, "lost connection.\n"); + } + } + + /* Update TBMT if not set and no character pending */ + if (!pmmi_ctx.txp && !(pmmi_ctx.ireg0 & PMMI_TBMT)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_tx(pmmi_res.tmxr); + pmmi_ctx.ireg0 |= (tmxr_txdone_ln(pmmi_res.tmxr->ldsc) && pmmi_ctx.conn) ? (PMMI_TBMT | PMMI_TEOC) : 0; + } else { + pmmi_ctx.ireg0 |= (PMMI_TBMT | PMMI_TEOC); + } + } + + /* Check for Data if RX buffer empty */ + if (!(pmmi_ctx.ireg0 & PMMI_DAV)) { + if (uptr->flags & UNIT_ATT) { + tmxr_poll_rx(pmmi_res.tmxr); + + c = tmxr_getc_ln(pmmi_res.tmxr->ldsc); + } else { + c = s100_bus_poll_kbd(uptr); + } + + if (c & (TMXR_VALID | SCPE_KFLAG)) { + pmmi_ctx.ireg1 = c & 0xff; + pmmi_ctx.ireg0 |= PMMI_DAV; + pmmi_ctx.ireg0 &= ~(PMMI_FE | PMMI_OR | PMMI_RPE); + } + } + + /* Timer Pulses */ + ms = sim_os_msec(); + + if (ms > pmmi_ctx.ptimer) { + if (pmmi_ctx.oreg2) { + if (pmmi_ctx.ireg2 & PMMI_TMR) { + pmmi_ctx.ireg2 &= ~PMMI_TMR; + pmmi_ctx.ptimer = sim_os_msec() + 600 / (PMMI_CLOCK / pmmi_ctx.oreg2); /* 60% off */ + } else { + pmmi_ctx.ireg2 |= PMMI_TMR; + pmmi_ctx.ptimer = sim_os_msec() + 400 / (PMMI_CLOCK / pmmi_ctx.oreg2); /* 40% on */ + } + } else { + pmmi_ctx.ptimer = sim_os_msec() + 100; /* default to 100ms if timer rate is 0 */ + } + } + + /* Emulate dial tone */ + if ((ms > pmmi_ctx.dtimer) && (pmmi_ctx.oreg0 & PMMI_SH) && (pmmi_ctx.ireg2 & PMMI_DT)) { + pmmi_ctx.ireg2 &= ~PMMI_DT; + sim_debug(STATUS_MSG, uptr->dptr, "dial tone active.\n"); + } + + /* Don't let TMXR clobber our wait time */ + uptr->wait = PMMI_WAIT; + + sim_activate_abs(uptr, uptr->wait); + + return SCPE_OK; +} + + +/* Attach routine */ +static t_stat pmmi_attach(UNIT *uptr, CONST char *cptr) +{ + t_stat r; + + sim_debug(VERBOSE_MSG, uptr->dptr, "attach (%s).\n", cptr); + + if ((r = tmxr_attach(pmmi_res.tmxr, uptr, cptr)) == SCPE_OK) { + + pmmi_ctx.flags = uptr->flags; /* Save Flags */ + + if (!pmmi_res.tmxr->ldsc->serport) { + uptr->flags |= UNIT_PMMI_RTS; /* Force following DTR on sockets */ + } + + pmmi_res.tmxr->ldsc->rcve = 1; + + sim_activate(uptr, uptr->wait); + + sim_debug(VERBOSE_MSG, uptr->dptr, "activated service.\n"); + } + + return r; +} + +/* Detach routine */ +static t_stat pmmi_detach(UNIT *uptr) +{ + sim_debug(VERBOSE_MSG, uptr->dptr, "detach.\n"); + + if (uptr->flags & UNIT_ATT) { + uptr->flags = pmmi_ctx.flags; /* Restore Flags */ + + sim_cancel(uptr); + + return (tmxr_detach(pmmi_res.tmxr, uptr)); + } + + return SCPE_UNATT; +} + +static t_stat pmmi_set_baud(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + int32 baud; + t_stat r = SCPE_ARG; + + if (!(uptr->flags & UNIT_ATT)) { + return SCPE_UNATT; + } + + if (cptr != NULL) { + if (sscanf(cptr, "%d", &baud)) { + if (baud >= 61 && baud <=600) { + pmmi_ctx.baud = baud; + r = pmmi_config_line(uptr); + } + } + } + + return r; +} + +static t_stat pmmi_show_baud(FILE *st, UNIT *uptr, int32 value, const void *desc) +{ + if (uptr->flags & UNIT_ATT) { + fprintf(st, "Baud rate: %d", pmmi_ctx.baud); + } + + return SCPE_OK; +} + +static t_stat pmmi_config_line(UNIT *uptr) +{ + char config[20]; + char b,p,s; + t_stat r = SCPE_IERR; + + switch (pmmi_ctx.oreg0 & PMMI_BMSK) { + case PMMI_5BIT: + b = '5'; + break; + + case PMMI_6BIT: + b = '6'; + break; + + case PMMI_7BIT: + b = '7'; + break; + + case PMMI_8BIT: + default: + b = '8'; + break; + } + + switch (pmmi_ctx.oreg0 & PMMI_PMSK) { + case PMMI_OPAR: + p = 'O'; + break; + + case PMMI_EPAR: + p = 'E'; + break; + + case PMMI_NPAR: + default: + p = 'N'; + break; + } + + switch (pmmi_ctx.oreg0 & PMMI_SMSK) { + case PMMI_2SB: + s = '2'; + break; + + case PMMI_1SB: + default: + s = '1'; + break; + } + + sprintf(config, "%d-%c%c%c", pmmi_ctx.baud, b,p,s); + + sim_debug(STATUS_MSG, uptr->dptr, "setting port configuration to '%s'.\n", config); + + r = tmxr_set_config_line(pmmi_res.tmxr->ldsc, config); + + return r; +} + +static int32 pmmi_io(int32 addr, int32 io, int32 data) +{ + int32 r = 0; + + addr &= 0xff; + data &= 0xff; + + if (io == S100_IO_WRITE) { + sim_debug(VERBOSE_MSG, &pmmi_dev, "OUT %02X,%02X\n", addr , data); + } else { + sim_debug(VERBOSE_MSG, &pmmi_dev, "IN %02X\n", addr); + } + + switch (addr & 0x03) { + case PMMI_REG0: + r = pmmi_reg0(io, data); + break; + + case PMMI_REG1: + r = pmmi_reg1(io, data); + break; + + case PMMI_REG2: + r = pmmi_reg2(io, data); + break; + + case PMMI_REG3: + r = pmmi_reg3(io, data); + break; + } + + return(r); +} + +static int32 pmmi_reg0(int32 io, int32 data) +{ + int32 r; + + if (io == S100_IO_READ) { + r = pmmi_ctx.ireg0; + } else { pmmi_ctx.oreg0 = data; /* Set UART configuration */ + pmmi_config_line(&pmmi_dev.units[0]); + + if (data & PMMI_SH) { /* If off-hook, clear dial tone bit (active low) */ + pmmi_ctx.dtimer = sim_os_msec() + 500; /* Dial tone in 500ms */ + if (pmmi_ctx.oreg0 & PMMI_SH) { + pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */ + } + } else if (!(pmmi_ctx.ireg2 & PMMI_DT)) { + pmmi_ctx.dtimer = 0; + pmmi_ctx.ireg2 |= PMMI_DT; + sim_debug(STATUS_MSG, &pmmi_dev, "dial tone inactive.\n"); + } + + if (data & PMMI_RI) { /* Go off-hook in answer mode */ + pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */ + } + + r = 0x00; + } + + return(r); +} + +static int32 pmmi_reg1(int32 io, int32 data) +{ + int32 r; + + if (io == S100_IO_READ) { + r = pmmi_ctx.ireg1; + pmmi_ctx.ireg0 &= ~(PMMI_DAV | PMMI_FE | PMMI_OR | PMMI_RPE); + } else { + pmmi_ctx.oreg1 = data; + pmmi_ctx.ireg0 &= ~(PMMI_TBMT | PMMI_TEOC); + pmmi_ctx.txp = 1; + + r = 0x00; + } + + return(r); +} + +static int32 pmmi_reg2(int32 io, int32 data) +{ + int32 r; + + if (io == S100_IO_READ) { + r = pmmi_ctx.ireg2; + } else { + pmmi_ctx.oreg2 = data; + + /* + ** The actual baud rate is determined by the following: + ** Rate = 250,000/(Reg X 16) where Reg = the binary + ** value loaded into the rate generator. + */ + if (data) { + pmmi_ctx.baud = 250000/(data * 16); + + pmmi_config_line(&pmmi_dev.units[0]); + } + + r = 0x00; + } + + return(r); +} + +static int32 pmmi_reg3(int32 io, int32 data) +{ + int32 s; + + if (io == S100_IO_READ) { + pmmi_ctx.intmsk = pmmi_ctx.oreg2; /* Load int mask from rate generator */ + } else { + pmmi_ctx.oreg3 = data; + /* Set/Clear DTR */ + s = TMXR_MDM_DTR | ((pmmi_dev.units[0].flags & UNIT_PMMI_RTS) ? TMXR_MDM_RTS : 0); + if (data & PMMI_DTR) { + sim_debug(STATUS_MSG, &pmmi_dev, "setting DTR HIGH.\n"); + tmxr_set_get_modem_bits(pmmi_res.tmxr->ldsc, s, 0, NULL); + if (pmmi_ctx.oreg0 & PMMI_SH) { + pmmi_ctx.ireg2 &= ~PMMI_AP; /* Answer Phone Bit (active low) */ + } + } else { + sim_debug(STATUS_MSG, &pmmi_dev, "setting DTR LOW.\n"); + tmxr_set_get_modem_bits(pmmi_res.tmxr->ldsc, 0, s, NULL); + pmmi_ctx.ireg2 |= PMMI_AP; + } + } + return 0x00; +} + +static t_stat pmmi_set_console(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + if (value == UNIT_PMMI_CONSOLE) { + s100_bus_console(uptr); + } + else { + s100_bus_noconsole(uptr); + } + + return SCPE_OK; +} + +static t_stat pmmi_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) +{ + fprintf (st, "\nPMMI MM-103 (%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"); + + fprintf(st, "This device may be attached to a serial port on the host computer\n"); + fprintf(st, "with the ATTACH command:\n\n"); + fprintf(st, " sim> ATTACH %s CONNECT=/dev/tty.usbserial-AB0NW409\n\n", sim_dname(dptr)); + + fprintf(st, "This device may also be attached to a TCP/IP port on the host computer\n"); + fprintf(st, "with the ATTACH command. The following will listen for a connection\n"); + fprintf(st, "on port 8800:\n\n"); + fprintf(st, " sim> ATTACH %s 8800\n", sim_dname(dptr)); + + return SCPE_OK; +} + diff --git a/Altair8800/pmmi_mm103.h b/Altair8800/pmmi_mm103.h new file mode 100644 index 00000000..fc141202 --- /dev/null +++ b/Altair8800/pmmi_mm103.h @@ -0,0 +1,93 @@ +/* pmmi_mm103.h + + Copyright (c) 2026 Patrick A. Linstruth + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of Patrick Linstruth shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from Patrick Linstruth. + + History: + 01/18/26 Initial version + +*/ + +#ifndef _PMMI_MM103_H +#define _PMMI_MM103_H + + +#define UNIT_V_PMMI_VERBOSE (UNIT_V_UF + 0) /* Verbose messages */ +#define UNIT_PMMI_VERBOSE (1 << UNIT_V_PMMI_VERBOSE) +#define UNIT_V_PMMI_CONSOLE (UNIT_V_UF + 1) /* Use this device for console */ +#define UNIT_PMMI_CONSOLE (1 << UNIT_V_PMMI_CONSOLE) +#define UNIT_V_PMMI_RTS (UNIT_V_UF + 2) /* RTS follows DTR */ +#define UNIT_PMMI_RTS (1 << UNIT_V_PMMI_RTS) + +#define PMMI_WAIT 500 /* Service Wait Interval */ + +#define PMMI_IOBASE 0xC0 +#define PMMI_IOSIZE 4 + +#define PMMI_REG0 0 /* Relative Address 0 */ +#define PMMI_REG1 1 /* Relative Address 1 */ +#define PMMI_REG2 2 /* Relative Address 2 */ +#define PMMI_REG3 3 /* Relative Address 3 */ + +#define PMMI_TBMT 0x01 /* Transmit Data Register Empty */ +#define PMMI_DAV 0x02 /* Receive Data Register Full */ +#define PMMI_TEOC 0x04 /* Transmit Serializer Empty */ +#define PMMI_RPE 0x08 /* Parity Error */ +#define PMMI_OR 0x10 /* Overrun */ +#define PMMI_FE 0x20 /* Framing Error */ + +#define PMMI_DT 0x01 /* Dial Tone */ +#define PMMI_RNG 0x02 /* Ringing */ +#define PMMI_CTS 0x04 /* Clear to Send */ +#define PMMI_RXBRK 0x08 /* RX Break */ +#define PMMI_AP 0x10 /* Answer Phone */ +#define PMMI_FO 0x20 /* Digital Carrier Signal */ +#define PMMI_MODE 0x40 /* Mode */ +#define PMMI_TMR 0x80 /* Timer Pulses */ + +#define PMMI_ST 0x10 /* Self Test */ +#define PMMI_DTR 0x40 /* DTR */ + +#define PMMI_SH 0x01 /* Switch Hook */ +#define PMMI_RI 0x02 /* Ring Indicator */ +#define PMMI_5BIT 0x00 /* 5 Data Bits */ +#define PMMI_6BIT 0x04 /* 6 Data Bits */ +#define PMMI_7BIT 0x08 /* 7 Data Bits */ +#define PMMI_8BIT 0x0C /* 8 Data Bits */ +#define PMMI_BMSK 0x0C /* Data Bits Bit Mask */ + +#define PMMI_OPAR 0x00 /* Odd Parity */ +#define PMMI_NPAR 0x10 /* No Parity */ +#define PMMI_EPAR 0x20 /* Odd Parity */ +#define PMMI_PMSK 0x30 /* Parity Bit Mask */ + +#define PMMI_1SB 0x00 /* 1 Stop Bit */ +#define PMMI_15SB 0x40 /* 1.5 Stop Bits */ +#define PMMI_2SB 0x40 /* 2 Stop Bits */ +#define PMMI_SMSK 0x40 /* Stop Bits Bit Mask */ + +#define PMMI_CLOCK 2500 /* Rate Generator / 100 */ +#define PMMI_BAUD 300 /* Default baud rate */ + +#endif + diff --git a/Altair8800/s100_bus.c b/Altair8800/s100_bus.c index 9d40ad67..826f69d4 100644 --- a/Altair8800/s100_bus.c +++ b/Altair8800/s100_bus.c @@ -45,8 +45,6 @@ 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 */ @@ -413,18 +411,6 @@ void s100_bus_memw(t_addr addr, int32 data) mdev_table[page].routine(addr, S100_IO_WRITE, data); } -ChipType s100_bus_set_chiptype(ChipType new) -{ - chiptype = new; - - return chiptype; -} - -ChipType s100_bus_get_chiptype(void) -{ - return chiptype; -} - uint32 s100_bus_set_addr(uint32 new) { bus_addr = new; diff --git a/Altair8800/s100_cpu.c b/Altair8800/s100_cpu.c index 7cffa6e8..8443383a 100644 --- a/Altair8800/s100_cpu.c +++ b/Altair8800/s100_cpu.c @@ -159,6 +159,11 @@ void cpu_set_chiptype(ChipType new_type) cpu_reset(&cpu_dev); } +ChipType cpu_get_chiptype() +{ + return cpu_type; +} + char * cpu_get_chipname(ChipType type) { return cpu_chipname[type]; diff --git a/Altair8800/s100_cpu.h b/Altair8800/s100_cpu.h index bbc5b60b..183e41e8 100644 --- a/Altair8800/s100_cpu.h +++ b/Altair8800/s100_cpu.h @@ -56,6 +56,7 @@ typedef struct { } CPU; extern void cpu_set_chiptype(ChipType type); +extern ChipType cpu_get_chiptype(void); extern char * cpu_get_chipname(ChipType type); #endif diff --git a/Altair8800/s100_ram.c b/Altair8800/s100_ram.c index 31026d64..bb699fbd 100644 --- a/Altair8800/s100_ram.c +++ b/Altair8800/s100_ram.c @@ -41,6 +41,7 @@ static t_stat ram_default_dis (UNIT *uptr, int32 value, const char *cptr, static t_stat ram_set_memsize (int32 value); static t_stat ram_clear_command (UNIT *uptr, int32 value, const char *cptr, void *desc); static t_stat ram_enable_command (UNIT *uptr, int32 value, const char *cptr, void *desc); +static t_stat ram_prot_command (UNIT *uptr, int32 value, const char *cptr, void *desc); static t_stat ram_randomize_command (UNIT *uptr, int32 value, const char *cptr, void *desc); static t_stat ram_size_command (UNIT *uptr, int32 value, const char *cptr, void *desc); static void ram_clear (void); @@ -54,7 +55,7 @@ static uint32 GetBYTE(register uint32 Addr); static int32 poc = TRUE; /* Power On Clear */ static int32 M[MAXBANKSIZE]; /* RAM */ -static int32 P[MAXBANKSIZE >> LOG2PAGESIZE]; /* Active pages */ +static int32 P[MAXBANKSIZE >> LOG2PAGESIZE]; /* Protected pages */ static int32 memsize = MAXBANKSIZE; static const char* ram_description(DEVICE *dptr) { @@ -86,6 +87,10 @@ static MTAB ram_mod[] = { NULL, NULL, "Enable RAM page(s)" }, { MTAB_XTD | MTAB_VDV | MTAB_VALR, 0, NULL, "REMRAM={PAGE | START-END | ALL}", &ram_enable_command, NULL, NULL, "Disable RAM page(s)" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, TRUE, NULL, "PROT={PAGE | START-END | ALL}", &ram_prot_command, + NULL, NULL, "Protect RAM page(s)" }, + { MTAB_XTD | MTAB_VDV | MTAB_VALR, FALSE, NULL, "UNPROT={PAGE | START-END | ALL}", &ram_prot_command, + NULL, NULL, "Unprotect RAM page(s)" }, { MTAB_VDV, 0, NULL, "CLEAR", &ram_clear_command, NULL, NULL, "Sets RAM to 0x00" }, { MTAB_VDV, 0, NULL, "RANDOM", &ram_randomize_command, @@ -93,8 +98,12 @@ static MTAB ram_mod[] = { { 0 } }; +/* Debug Flags */ +#define VERBOSE_MSG (1 << 0) + /* Debug Flags */ static DEBTAB ram_dt[] = { + { "VERBOSE", VERBOSE_MSG, "Verbose messages" }, { NULL, 0 } }; @@ -116,7 +125,7 @@ DEVICE ram_dev = { NULL, /* attach routine */ NULL, /* detach routine */ NULL, /* context */ - (DEV_DISABLE), /* flags */ + (DEV_DISABLE | DEV_DEBUG), /* flags */ 0, /* debug control */ ram_dt, /* debug flags */ NULL, /* mem size routine */ @@ -129,6 +138,8 @@ DEVICE ram_dev = { static t_stat ram_reset(DEVICE *dptr) { + t_addr addr; + if (dptr->flags & DEV_DIS) { /* Disable Device */ s100_bus_remmem(0x0000, MAXBANKSIZE, &ram_memio); ram_default_dis(NULL, 0, NULL, NULL); @@ -143,6 +154,14 @@ static t_stat ram_reset(DEVICE *dptr) ram_default_ena(NULL, 0, NULL, NULL); } + for (addr = 0; addr < MAXADDR; addr++) { + M[addr] = 0x00; + + if ((addr & 0xff) == 0x00) { + P[addr >> LOG2PAGESIZE] = FALSE; + } + } + poc = FALSE; } } @@ -175,12 +194,17 @@ static int32 ram_memio(const int32 addr, const int32 rw, const int32 data) return 0x0ff; } -static void PutBYTE(register uint32 Addr, const register uint32 Value) +static void PutBYTE(uint32 Addr, const register uint32 Value) { - M[Addr & ADDRMASK] = Value & DATAMASK; + if (!P[(Addr & ADDRMASK) >> LOG2PAGESIZE]) { + M[Addr & ADDRMASK] = Value & DATAMASK; + } + else { + sim_debug(VERBOSE_MSG, &ram_dev, "RAM: Page %02X is protected\n", Addr >> LOG2PAGESIZE); + } } -static uint32 GetBYTE(register uint32 Addr) +static uint32 GetBYTE(uint32 Addr) { return M[Addr & ADDRMASK] & DATAMASK; /* RAM */ } @@ -200,9 +224,8 @@ static t_stat ram_default_dis(UNIT *uptr, int32 value, const char *cptr, void *d } /* set memory to 'size' kilo byte */ -static t_stat ram_set_memsize(int32 size) { - int32 page; - +static t_stat ram_set_memsize(int32 size) +{ size <<= KBLOG2; if (size < KB) { @@ -218,11 +241,6 @@ static t_stat ram_set_memsize(int32 size) { s100_bus_remmem(0x0000, MAXBANKSIZE, &ram_memio); /* Remove all pages */ s100_bus_addmem(0x0000, memsize, &ram_memio, "RAM"); /* Add memsize pages */ - /* Keep track of active pages for SHOW */ - for (page = 0; page < (MAXBANKSIZE >> LOG2PAGESIZE); page++) { - P[page] = (page << LOG2PAGESIZE) <= memsize; - } - ram_unit.capac = memsize; return SCPE_OK; @@ -246,7 +264,8 @@ static void ram_randomize() } } -static t_stat ram_size_command(UNIT *uptr, int32 value, const char *cptr, void *desc) { +static t_stat ram_size_command(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ int32 size, result; if (cptr == NULL) { @@ -263,12 +282,13 @@ static t_stat ram_size_command(UNIT *uptr, int32 value, const char *cptr, void * return SCPE_ARG | SCPE_NOMESSAGE; } -static t_stat ram_enable_command(UNIT *uptr, int32 value, const char *cptr, void *desc) { +static t_stat ram_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 RAM ENABLE=E0-EF\n"); + sim_printf("Memory page(s) must be provided as SET RAM %s=E0-EF\n", value ? "ADDRAM" : "REMRAM"); return SCPE_ARG | SCPE_NOMESSAGE; } @@ -298,6 +318,35 @@ static t_stat ram_enable_command(UNIT *uptr, int32 value, const char *cptr, void return SCPE_OK; } +static t_stat ram_prot_command(UNIT *uptr, int32 value, const char *cptr, void *desc) +{ + t_addr start, end; + uint16 page; + + if (cptr == NULL) { + sim_printf("Memory page(s) must be provided as SET RAM %s=E0-EF\n", value ? "PROT" : "UNPROT"); + 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; + } + + /* Protect or Unprotect Pages */ + for (page = start; page <= end; page++) { + P[page] = value; + } + + return SCPE_OK; +} + static t_stat ram_clear_command(UNIT *uptr, int32 value, const char *cptr, void *desc) { ram_clear(); diff --git a/Altair8800/s100_rom.c b/Altair8800/s100_rom.c index dfbd4b61..5c71ac19 100644 --- a/Altair8800/s100_rom.c +++ b/Altair8800/s100_rom.c @@ -55,11 +55,12 @@ static t_stat rom_show_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, cons static int32 M[MAXBANKSIZE]; static ROM rom_table[] = { - { UNIT_ROM_ALTMON, rom_altmon, ROM_ALTMON_BASEADDR, ROM_ALTMON_SIZE, ROM_ALTMON_NAME, ROM_ALTMON_DESC }, - { UNIT_ROM_DBL, rom_mits_dbl, ROM_MITS_DBL_BASEADDR, ROM_MITS_DBL_SIZE, ROM_MITS_DBL_NAME, ROM_MITS_DBL_DESC }, - { UNIT_ROM_HDSK, rom_mits_hdsk, ROM_MITS_HDSK_BASEADDR, ROM_MITS_HDSK_SIZE, ROM_MITS_HDSK_NAME, ROM_MITS_HDSK_DESC }, - { UNIT_ROM_TURMON, rom_mits_turmon, ROM_MITS_TURMON_BASEADDR, ROM_MITS_TURMON_SIZE, ROM_MITS_TURMON_NAME, ROM_MITS_TURMON_DESC }, - { UNIT_ROM_AZ80DBL, rom_az80_dbl, ROM_AZ80_DBL_BASEADDR, ROM_AZ80_DBL_SIZE, ROM_AZ80_DBL_NAME, ROM_AZ80_DBL_DESC }, + { UNIT_ROM_ALTMON, rom_altmon, ROM_ALTMON_BASEADDR, ROM_ALTMON_SIZE, ROM_ALTMON_NAME, ROM_ALTMON_DESC }, + { UNIT_ROM_DBL, rom_mits_dbl, ROM_MITS_DBL_BASEADDR, ROM_MITS_DBL_SIZE, ROM_MITS_DBL_NAME, ROM_MITS_DBL_DESC }, + { UNIT_ROM_HDSK, rom_mits_hdsk, ROM_MITS_HDSK_BASEADDR, ROM_MITS_HDSK_SIZE, ROM_MITS_HDSK_NAME, ROM_MITS_HDSK_DESC }, + { UNIT_ROM_TURMON, rom_mits_turmon, ROM_MITS_TURMON_BASEADDR, ROM_MITS_TURMON_SIZE, ROM_MITS_TURMON_NAME, ROM_MITS_TURMON_DESC }, + { UNIT_ROM_CDBL, rom_me_cdbl, ROM_ME_CDBL_BASEADDR, ROM_ME_CDBL_SIZE, ROM_ME_CDBL_NAME, ROM_ME_CDBL_DESC }, + { UNIT_ROM_AZ80DBL, rom_az80_dbl, ROM_AZ80_DBL_BASEADDR, ROM_AZ80_DBL_SIZE, ROM_AZ80_DBL_NAME, ROM_AZ80_DBL_DESC }, { 0, NULL, 0x0000, 0, "", "" } }; @@ -108,6 +109,11 @@ static MTAB rom_mod[] = { { UNIT_ROM_TURMON, 0, "NO" ROM_MITS_TURMON_NAME, "NO" ROM_MITS_TURMON_NAME, &rom_dis_turmon, NULL, NULL, "Disable " ROM_MITS_TURMON_DESC }, + { UNIT_ROM_CDBL, UNIT_ROM_CDBL, ROM_ME_CDBL_NAME, ROM_ME_CDBL_NAME, &rom_ena, NULL, + NULL, "Enable " ROM_ME_CDBL_DESC }, + { UNIT_ROM_CDBL, 0, "NO" ROM_ME_CDBL_NAME, "NO" ROM_ME_CDBL_NAME, &rom_dis_turmon, NULL, + NULL, "Disable " ROM_ME_CDBL_DESC }, + { MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "LIST", NULL, NULL, &rom_show_list, NULL, "Show available ROMs" }, { 0 } diff --git a/Altair8800/s100_rom.h b/Altair8800/s100_rom.h index 6a7981a2..4352288d 100644 --- a/Altair8800/s100_rom.h +++ b/Altair8800/s100_rom.h @@ -43,7 +43,9 @@ #define UNIT_ROM_ALTMON (1 << UNIT_ROM_V_ALTMON ) #define UNIT_ROM_V_TURMON (UNIT_V_UF+4) /* Enable/Disable Turnkey Monitor */ #define UNIT_ROM_TURMON (1 << UNIT_ROM_V_TURMON ) -#define UNIT_ROM_V_AZ80DBL (UNIT_V_UF+5) /* Enable/Disable AltairZ80 Disk Boot Loader */ +#define UNIT_ROM_V_CDBL (UNIT_V_UF+5) /* Enable/Disable Combined Disk Boot Loader */ +#define UNIT_ROM_CDBL (1 << UNIT_ROM_V_CDBL) +#define UNIT_ROM_V_AZ80DBL (UNIT_V_UF+6) /* Enable/Disable AltairZ80 Disk Boot Loader */ #define UNIT_ROM_AZ80DBL (1 << UNIT_ROM_V_AZ80DBL) typedef struct { diff --git a/Altair8800/s100_roms.h b/Altair8800/s100_roms.h index 0474ee20..ae57520f 100644 --- a/Altair8800/s100_roms.h +++ b/Altair8800/s100_roms.h @@ -330,6 +330,53 @@ static int32 rom_altmon[ROM_ALTMON_SIZE] = { 0x9c, 0x23, 0xc0, 0x13, 0xc9, 0xff, 0xff, 0xff }; +/* + * Martin Eberhard Combined Disk Boot Loader + * https://deramp.com/downloads/altair/software/roms/custom_roms/M%20Eberhard%20Improved%20ROMs/CDBL.ASM + */ +#define ROM_ME_CDBL_BASEADDR 0xff00 +#define ROM_ME_CDBL_SIZE 0x0100 +#define ROM_ME_CDBL_NAME "CDBL" +#define ROM_ME_CDBL_DESC "Combined Disk Boot Loader" + +static int32 rom_me_cdbl[ROM_ME_CDBL_SIZE] = { + 0xf3, 0x11, 0xf5, 0xff, 0x21, 0xe1, 0x4c, 0x1b, + 0x2b, 0x1a, 0x77, 0x7d, 0xb7, 0xc2, 0x07, 0xff, + 0x01, 0x82, 0x06, 0xe9, 0xd3, 0x22, 0x2f, 0xd3, + 0x23, 0x3e, 0x2c, 0xd3, 0x22, 0xaf, 0xd3, 0x08, + 0xdb, 0x08, 0xe6, 0x08, 0xc2, 0x09, 0x4c, 0x3e, + 0x04, 0xd3, 0x09, 0x3e, 0x01, 0xd3, 0x09, 0x0b, + 0x78, 0xb1, 0xc2, 0x1b, 0x4c, 0x0c, 0xdb, 0x08, + 0x0f, 0x0f, 0xda, 0x22, 0x4c, 0xe6, 0x10, 0x3e, + 0x02, 0xc2, 0x19, 0x4c, 0xdb, 0x09, 0xe6, 0x3f, + 0xfe, 0x1e, 0xc2, 0x30, 0x4c, 0xdb, 0x09, 0x0f, + 0xd2, 0x39, 0x4c, 0xdb, 0x09, 0x0f, 0xda, 0x3f, + 0x4c, 0xe6, 0x1f, 0xc6, 0x10, 0x4f, 0x65, 0x3e, + 0x03, 0xd3, 0x10, 0x3e, 0x11, 0xd3, 0x10, 0x3e, + 0x10, 0x31, 0x7b, 0x4d, 0xf5, 0xdb, 0x09, 0xe6, + 0x3f, 0x0f, 0xb8, 0xc2, 0x59, 0x4c, 0x11, 0x7b, + 0x4d, 0x7c, 0xaa, 0xe6, 0xfe, 0x3e, 0x4f, 0xca, + 0xc5, 0x4c, 0xe5, 0xc5, 0x01, 0x80, 0x00, 0xdb, + 0x08, 0x07, 0xda, 0x73, 0x4c, 0xdb, 0x0a, 0x12, + 0x1c, 0xc2, 0x73, 0x4c, 0x1e, 0x7e, 0x1a, 0x77, + 0xbe, 0xc2, 0xc3, 0x4c, 0x80, 0x47, 0x13, 0x23, + 0x0d, 0xc2, 0x82, 0x4c, 0xeb, 0x4e, 0x0c, 0x23, + 0xae, 0xb1, 0xc1, 0xc2, 0xb6, 0x4c, 0x2a, 0x7c, + 0x4d, 0xeb, 0x7d, 0x93, 0x7c, 0x9a, 0xd2, 0xc7, + 0x4c, 0x11, 0x53, 0x4c, 0xd5, 0x04, 0x04, 0x78, + 0xb9, 0xd8, 0x06, 0x01, 0xc8, 0x78, 0xd3, 0x09, + 0x05, 0xc9, 0x3e, 0x04, 0xd3, 0x09, 0xe1, 0xf1, + 0x3d, 0xc2, 0x55, 0x4c, 0x3e, 0x43, 0x11, 0x3e, + 0x4d, 0x47, 0x37, 0x3e, 0x80, 0xd3, 0x08, 0xd2, + 0x00, 0x00, 0xfb, 0x22, 0x01, 0x00, 0x78, 0x32, + 0x00, 0x00, 0xd3, 0x01, 0xd3, 0x11, 0xd3, 0x05, + 0xd3, 0x23, 0xc3, 0xd6, 0x4c, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +/* + * AltairZ80 Disk Boot Loader + */ #define ROM_AZ80_DBL_BASEADDR 0xff00 #define ROM_AZ80_DBL_SIZE 0x0100 #define ROM_AZ80_DBL_NAME "AZ80DBL" diff --git a/Visual Studio Projects/Altair8800.vcproj b/Visual Studio Projects/Altair8800.vcproj index 26da4748..4711b744 100644 --- a/Visual Studio Projects/Altair8800.vcproj +++ b/Visual Studio Projects/Altair8800.vcproj @@ -203,6 +203,10 @@ RelativePath="..\Altair8800\altair8800_sys.c" > + + @@ -215,6 +219,10 @@ RelativePath="..\Altair8800\mits_dsk.c" > + + @@ -336,6 +344,10 @@ RelativePath="..\Altair8800\altair8800_sys.h" > + + @@ -348,6 +360,10 @@ RelativePath="..\Altair8800\mits_dsk.h" > + + diff --git a/makefile b/makefile index 282a834e..99237f9c 100644 --- a/makefile +++ b/makefile @@ -2242,9 +2242,11 @@ ALTAIR8800 = \ ${ALTAIR8800D}/s100_ram.c \ ${ALTAIR8800D}/s100_rom.c \ ${ALTAIR8800D}/s100_z80.c \ + ${ALTAIR8800D}/cromemco_dazzler.c \ ${ALTAIR8800D}/mits_2sio.c \ ${ALTAIR8800D}/mits_acr.c \ ${ALTAIR8800D}/mits_dsk.c \ + ${ALTAIR8800D}/pmmi_mm103.c \ ${ALTAIR8800D}/sds_sbc200.c \ ${ALTAIR8800D}/sds_vfii.c \ ${ALTAIR8800D}/tarbell_fdc.c \