/* ibm1130_disk.c: IBM 1130 disk IO simulator NOTE - there is a problem with this code. The Device Status Word (DSW) is computed from current conditions when requested by an XIO load status command; the value of DSW available to the simulator's examine & save commands may NOT be accurate. This should probably be fixed. Based on the SIMH package written by Robert M Supnik * (C) Copyright 2002, Brian Knittel. * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN * RISK basis, there is no warranty of fitness for any purpose, and the rest of the * usual yada-yada. Please keep this notice and the copyright in any distributions * or modifications. * * Revision History * 05-dec-06 Added cgiwritable mode * * 19-Dec-05 We no longer issue an operation complete interrupt if an INITR, INITW * or CONTROL operation is attemped on a drive that is not online. DATA_ERROR * is now only indicated in the DSW when * * 02-Nov-04 Addes -s option to boot to leave switches alone. * 15-jun-03 moved actual read on XIO read to end of time interval, * as the APL boot card required 2 instructions to run between the * time read was initiated and the time the data was read (a jump and a wait) * * 01-sep-02 corrected treatment of -m and -r flags in dsk_attach * in cgi mode, so that file is opened readonly but emulated * disk is writable. * * This is not a supported product, but I welcome bug reports and fixes. * Mail to simh@ibm1130.org */ #include "ibm1130_defs.h" #include #define TRACE_DMS_IO /* define to enable debug of DMS phase IO */ #ifdef TRACE_DMS_IO static int trace_dms = 0; static void tracesector (int iswrite, int nwords, int addr, int sector); static t_stat where_cmd (int32 flag, CONST char *ptr); static t_stat phdebug_cmd (int32 flag, CONST char *ptr); static t_stat fdump_cmd (int32 flags, CONST char *cptr); static void enable_dms_tracing (int newsetting); #endif /* Constants */ #define DSK_NUMWD 321 /* words/sector */ #define DSK_NUMSC 4 /* sectors/surface */ #define DSK_NUMSF 2 /* surfaces/cylinder */ #define DSK_NUMCY 203 /* cylinders/drive */ #define DSK_NUMTR (DSK_NUMCY * DSK_NUMSF) /* tracks/drive */ #define DSK_NUMDR 5 /* drives/controller */ #define DSK_SIZE (DSK_NUMCY * DSK_NUMSF * DSK_NUMSC * DSK_NUMWD) /* words/drive */ #define UNIT_V_RONLY (UNIT_V_UF + 0) /* hwre write lock */ #define UNIT_V_OPERR (UNIT_V_UF + 1) /* operation error flag */ #define UNIT_V_HARDERR (UNIT_V_UF + 2) /* hard error flag (reset on power down) */ #define UNIT_RONLY (1u << UNIT_V_RONLY) #define UNIT_OPERR (1u << UNIT_V_OPERR) #define UNIT_HARDERR (1u << UNIT_V_HARDERR) #define MEM_MAPPED(uptr) (uptr->flags & UNIT_BUF) /* disk buffered in memory */ #define IO_NONE 0 /* last operation, used to ensure fseek between read and write */ #define IO_READ 1 #define IO_WRITE 2 #define DSK_DSW_DATA_ERROR 0x8000 /* device status word bits */ #define DSK_DSW_OP_COMPLETE 0x4000 #define DSK_DSW_NOT_READY 0x2000 #define DSK_DSW_DISK_BUSY 0x1000 #define DSK_DSW_CARRIAGE_HOME 0x0800 #define DSK_DSW_SECTOR_MASK 0x0003 /* device status words */ static int16 dsk_dsw[DSK_NUMDR] = {DSK_DSW_NOT_READY, DSK_DSW_NOT_READY, DSK_DSW_NOT_READY, DSK_DSW_NOT_READY, DSK_DSW_NOT_READY}; static int16 dsk_sec[DSK_NUMDR] = {0}; /* next-sector-up */ static char dsk_lastio[DSK_NUMDR]; /* last stdio operation: IO_READ or IO_WRITE */ int32 dsk_swait = 50; /* seek time -- see how short a delay we can get away with */ int32 dsk_rwait = 50; /* rotate time */ static t_bool raw_disk_debug = FALSE; static t_stat dsk_svc (UNIT *uptr); static t_stat dsk_reset (DEVICE *dptr); static t_stat dsk_attach (UNIT *uptr, CONST char *cptr); static t_stat dsk_detach (UNIT *uptr); static t_stat dsk_boot (int32 unitno, DEVICE *dptr); static void diskfail (UNIT *uptr, int dswflag, int unitflag, t_bool do_interrupt); /* DSK data structures dsk_dev disk device descriptor dsk_unit unit descriptor dsk_reg register list */ UNIT dsk_unit[] = { { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }, { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) } }; #define IS_ONLINE(u) (((u)->flags & (UNIT_ATT|UNIT_DIS)) == UNIT_ATT) /* Parameters in the unit descriptor */ #define CYL u3 /* current cylinder */ #define FUNC u4 /* current function */ REG dsk_reg[] = { { HRDATA (DSKDSW0, dsk_dsw[0], 16) }, { HRDATA (DSKDSW1, dsk_dsw[1], 16) }, { HRDATA (DSKDSW2, dsk_dsw[2], 16) }, { HRDATA (DSKDSW3, dsk_dsw[3], 16) }, { HRDATA (DSKDSW4, dsk_dsw[4], 16) }, { DRDATA (STIME, dsk_swait, 24), PV_LEFT }, { DRDATA (RTIME, dsk_rwait, 24), PV_LEFT }, { NULL } }; MTAB dsk_mod[] = { { UNIT_RONLY, 0, "write enabled", "ENABLED", NULL }, { UNIT_RONLY, UNIT_RONLY, "write locked", "LOCKED", NULL }, { 0 } }; DEVICE dsk_dev = { "DSK", dsk_unit, dsk_reg, dsk_mod, DSK_NUMDR, 16, 16, 1, 16, 16, NULL, NULL, &dsk_reset, dsk_boot, dsk_attach, dsk_detach}; static int32 dsk_ilswbit[DSK_NUMDR] = { /* interrupt level status word bits for the drives */ ILSW_2_1131_DISK, ILSW_2_2310_DRV_1, ILSW_2_2310_DRV_2, ILSW_2_2310_DRV_3, ILSW_2_2310_DRV_4, }; static int32 dsk_ilswlevel[DSK_NUMDR] = { 2, /* interrupt levels for the drives */ 2, 2, 2, 2 }; typedef enum {DSK_FUNC_IDLE, DSK_FUNC_READ, DSK_FUNC_VERIFY, DSK_FUNC_WRITE, DSK_FUNC_SEEK, DSK_FUNC_FAILED} DSK_FUNC; static struct tag_dsk_action { /* stores data needed for pending IO activity */ int32 io_address; uint32 io_filepos; int io_nwords; int io_sector; } dsk_action[DSK_NUMDR]; /* xio_disk - XIO command interpreter for the disk drives */ /* * device status word: * * 0 data error, occurs when: * 1. A modulo 4 error is detected during a read, read-check, or write operation. * 2. The disk storage is in a read or write mode at the leading edge of a sector pulse. * 3. A seek-incomplete signal is received from the 2311. * 4. A write select error has occurred in the disk storage drive. * 5. The power unsafe latch is set in the attachment. * Conditions 1, 2, and 3 are turned off by a sense device command with modifier bit 15 * set to 1. Conditions 4 and 5 are turned off by powering the drive off and back on. * 1 operation complete * 2 not ready, occurs when disk not ready or busy or disabled or off-line or * power unsafe latch set. Also included in the disk not ready is the write select error, * which can be a result of power unsafe or write select. * 3 disk busy * 4 carriage home (on cyl 0) * 15-16: number of next sector spinning into position. */ extern void void_backtrace (int afrom, int ato); extern int boot_drive; void xio_disk (int32 iocc_addr, int32 func, int32 modify, int drv) { int i, rev, nsteps, newcyl, sec, nwords; uint32 newpos; /* changed from t_addr to uint32 in anticipation of simh 64-bit development */ char msg[80]; UNIT *uptr = dsk_unit+drv; int16 buf[DSK_NUMWD]; if (! BETWEEN(drv, 0, DSK_NUMDR-1)) { /* hmmm, invalid drive */ if (func != XIO_SENSE_DEV) { /* tried to use it, too */ /* just do nothing, as if the controller isn't there. NAMCRA at N0116300 tests for drives by attempting reads sprintf(msg, "Op %x on invalid drive number %d", func, drv); xio_error(msg); */ } return; } CLRBIT(uptr->flags, UNIT_OPERR); /* clear pending error flag from previous op, if any */ switch (func) { case XIO_INITR: if (! IS_ONLINE(uptr)) { /* disk is offline */ diskfail(uptr, 0, 0, FALSE); break; } sim_cancel(uptr); /* cancel any pending ops */ dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; /* and mark the disk as busy */ nwords = M[iocc_addr++ & mem_mask]; /* get word count w/o upsetting SAR/SBR */ if (nwords == 0) /* this is bad -- on real 1130, this locks up disk controller ! */ break; if (! BETWEEN(nwords, 1, DSK_NUMWD)) { /* count bad */ SETBIT(uptr->flags, UNIT_OPERR); /* set data error DSW bit when op complete */ nwords = DSK_NUMWD; /* limit xfer to proper sector size */ } sec = modify & 0x07; /* get sector on cylinder */ if ((modify & 0x0080) == 0) { /* it's a real read if it's not a read check */ /* ah. We have a problem. The APL boot card counts on there being time for at least one * more instruction to execute between the XIO read and the time the data starts loading * into core. So, we have to defer the actual read operation a bit. Might as well wait * until it's time to issue the operation complete interrupt. This means saving the * IO information, then performing the actual read in dsk_svc. */ newpos = (uptr->CYL*DSK_NUMSC*DSK_NUMSF + sec)*2*DSK_NUMWD; dsk_action[drv].io_address = iocc_addr; dsk_action[drv].io_nwords = nwords; dsk_action[drv].io_sector = sec; dsk_action[drv].io_filepos = newpos; uptr->FUNC = DSK_FUNC_READ; } else { trace_io("* DSK%d verify %d.%d (%x)", drv, uptr->CYL, sec, uptr->CYL*8 + sec); if (raw_disk_debug) printf("* DSK%d verify %d.%d (%x)", drv, uptr->CYL, sec, uptr->CYL*8 + sec); uptr->FUNC = DSK_FUNC_VERIFY; } sim_activate(uptr, dsk_rwait); break; case XIO_INITW: if (! IS_ONLINE(uptr)) { /* disk is offline */ diskfail(uptr, 0, 0, FALSE); break; } if (uptr->flags & UNIT_RONLY) { /* oops, write to RO disk? permanent error until disk is powered off/on */ diskfail(uptr, DSK_DSW_DATA_ERROR, UNIT_HARDERR, FALSE); break; } sim_cancel(uptr); /* cancel any pending ops */ dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; /* and mark drive as busy */ nwords = M[iocc_addr++ & mem_mask]; /* get word count w/o upsetting SAR/SBR */ if (nwords == 0) /* this is bad -- locks up disk controller ! */ break; if (! BETWEEN(nwords, 1, DSK_NUMWD)) { /* count bad */ SETBIT(uptr->flags, UNIT_OPERR); /* set data error DSW bit when op complete */ nwords = DSK_NUMWD; /* limit xfer to proper sector size */ } sec = modify & 0x07; /* get sector on cylinder */ newpos = (uptr->CYL*DSK_NUMSC*DSK_NUMSF + sec)*2*DSK_NUMWD; trace_io("* DSK%d wrote %d words from M[%04x-%04x] to %d.%d (%x, %x)", drv, nwords, iocc_addr & mem_mask, (iocc_addr + nwords - 1) & mem_mask, uptr->CYL, sec, uptr->CYL*8 + sec, newpos); if (raw_disk_debug) printf("* DSK%d XIO @ %04x wrote %d words from M[%04x-%04x] to %d.%d (%x, %x)\n", drv, prev_IAR, nwords, iocc_addr & mem_mask, (iocc_addr + nwords - 1) & mem_mask, uptr->CYL, sec, uptr->CYL*8 + sec, newpos); #ifdef TRACE_DMS_IO if (trace_dms) tracesector(1, nwords, iocc_addr & mem_mask, uptr->CYL*8 + sec); #endif for (i = 0; i < nwords; i++) buf[i] = M[iocc_addr++ & mem_mask]; for (; i < DSK_NUMWD; i++) /* rest of sector gets zeroed */ buf[i] = 0; i = uptr->CYL*8 + sec; if (buf[0] != i) printf("*DSK writing bad sector#\n"); if (MEM_MAPPED(uptr)) { memcpy((char *) uptr->filebuf + newpos, buf, 2*DSK_NUMWD); uptr->hwmark = newpos + 2*DSK_NUMWD; } else { if (uptr->pos != newpos || dsk_lastio[drv] != IO_WRITE) { fseek(uptr->fileref, newpos, SEEK_SET); dsk_lastio[drv] = IO_WRITE; } fxwrite(buf, 2, DSK_NUMWD, uptr->fileref); uptr->pos = newpos + 2*DSK_NUMWD; } uptr->FUNC = DSK_FUNC_WRITE; sim_activate(uptr, dsk_rwait); break; case XIO_CONTROL: /* step fwd/rev */ if (! IS_ONLINE(uptr)) { diskfail(uptr, 0, 0, FALSE); break; } sim_cancel(uptr); rev = modify & 4; nsteps = iocc_addr & 0x00FF; if (nsteps == 0) /* 0 steps does not cause op complete interrupt */ break; newcyl = uptr->CYL + (rev ? (-nsteps) : nsteps); if (newcyl < 0) newcyl = 0; else if (newcyl >= DSK_NUMCY) newcyl = DSK_NUMCY-1; uptr->FUNC = DSK_FUNC_SEEK; uptr->CYL = newcyl; sim_activate(uptr, dsk_swait); /* schedule interrupt */ dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; trace_io("* DSK%d at cyl %d", drv, newcyl); break; case XIO_SENSE_DEV: CLRBIT(dsk_dsw[drv], DSK_DSW_CARRIAGE_HOME|DSK_DSW_NOT_READY); if ((uptr->flags & UNIT_HARDERR) || (dsk_dsw[drv] & DSK_DSW_DISK_BUSY) || ! IS_ONLINE(uptr)) SETBIT(dsk_dsw[drv], DSK_DSW_NOT_READY); else if (uptr->CYL <= 0) { SETBIT(dsk_dsw[drv], DSK_DSW_CARRIAGE_HOME); uptr->CYL = 0; } dsk_sec[drv] = (int16) ((dsk_sec[drv] + 1) % 4); /* advance the "next sector" count every time */ ACC = dsk_dsw[drv] | dsk_sec[drv]; if (modify & 0x01) { /* reset interrupts */ CLRBIT(dsk_dsw[drv], DSK_DSW_OP_COMPLETE|DSK_DSW_DATA_ERROR); CLRBIT(ILSW[dsk_ilswlevel[drv]], dsk_ilswbit[drv]); } break; default: sprintf(msg, "Invalid disk XIO function %x", func); xio_error(msg); } } /* diskfail - schedule an operation complete that sets the error bit */ static void diskfail (UNIT *uptr, int dswflag, int unitflag, t_bool do_interrupt) { int drv = uptr - dsk_unit; sim_cancel(uptr); /* cancel any pending ops */ SETBIT(dsk_dsw[drv], dswflag); /* set any specified DSW bits */ SETBIT(uptr->flags, unitflag); /* set any specified unit flag bits */ uptr->FUNC = DSK_FUNC_FAILED; /* tell svc routine why it failed */ if (do_interrupt) sim_activate(uptr, 1); /* schedule an immediate op complete interrupt */ } static t_stat dsk_svc (UNIT *uptr) { int drv = uptr - dsk_unit, i, nwords, sec; int16 buf[DSK_NUMWD]; uint32 newpos; /* changed from t_addr to uint32 in anticipation of simh 64-bit development */ int32 iocc_addr; if (uptr->FUNC == DSK_FUNC_IDLE) /* service function called with no activity? not good, but ignore */ return SCPE_OK; CLRBIT(dsk_dsw[drv], DSK_DSW_DISK_BUSY); /* activate operation complete interrupt */ SETBIT(dsk_dsw[drv], DSK_DSW_OP_COMPLETE); if (uptr->flags & (UNIT_OPERR|UNIT_HARDERR)) { /* word count error or data error */ SETBIT(dsk_dsw[drv], DSK_DSW_DATA_ERROR); CLRBIT(uptr->flags, UNIT_OPERR); /* soft error is one time occurrence; don't clear hard error */ } /* schedule interrupt */ SETBIT(ILSW[dsk_ilswlevel[drv]], dsk_ilswbit[drv]); switch (uptr->FUNC) { /* take care of business */ case DSK_FUNC_IDLE: case DSK_FUNC_VERIFY: case DSK_FUNC_WRITE: case DSK_FUNC_SEEK: case DSK_FUNC_FAILED: break; case DSK_FUNC_READ: /* actually read the data into core */ iocc_addr = dsk_action[drv].io_address; /* recover saved parameters */ nwords = dsk_action[drv].io_nwords; newpos = dsk_action[drv].io_filepos; sec = dsk_action[drv].io_sector; if (MEM_MAPPED(uptr)) { memcpy(buf, (char *) uptr->filebuf + newpos, 2*DSK_NUMWD); } else { if (uptr->pos != newpos || dsk_lastio[drv] != IO_READ) { fseek(uptr->fileref, newpos, SEEK_SET); dsk_lastio[drv] = IO_READ; uptr->pos = newpos; } fxread(buf, 2, DSK_NUMWD, uptr->fileref); /* read whole sector so we're in position for next read */ uptr->pos = newpos + 2*DSK_NUMWD; } void_backtrace(iocc_addr, iocc_addr + nwords - 1); /* mark prev instruction as altered */ trace_io("* DSK%d read %d words from %d.%d (%x, %x) to M[%04x-%04x]", drv, nwords, uptr->CYL, sec, uptr->CYL*8 + sec, newpos, iocc_addr & mem_mask, (iocc_addr + nwords - 1) & mem_mask); /* this will help debug the monitor by letting me watch phase loading */ if (raw_disk_debug) printf("* DSK%d XIO @ %04x read %d words from %d.%d (%x, %x) to M[%04x-%04x]\n", drv, prev_IAR, nwords, uptr->CYL, sec, uptr->CYL*8 + sec, newpos, iocc_addr & mem_mask, (iocc_addr + nwords - 1) & mem_mask); i = uptr->CYL*8 + sec; if (buf[0] != i) printf("*DSK read bad sector #\n"); for (i = 0; i < nwords; i++) M[(iocc_addr+i) & mem_mask] = buf[i]; #ifdef TRACE_DMS_IO if (trace_dms) tracesector(0, nwords, iocc_addr & mem_mask, uptr->CYL*8 + sec); #endif break; default: fprintf(stderr, "Unexpected FUNC %x in dsk_svc(%d)\n", uptr->FUNC, drv); break; } uptr->FUNC = DSK_FUNC_IDLE; /* we're done with this operation */ return SCPE_OK; } static t_stat dsk_reset (DEVICE *dptr) { int drv; UNIT *uptr; #ifdef TRACE_DMS_IO /* add the WHERE command. It finds the phase that was loaded at given address and indicates */ /* the offset in the phase */ register_cmd("WHERE", &where_cmd, 0, "w{here} address find phase and offset of an address\n"); register_cmd("PHDEBUG", &phdebug_cmd, 0, "ph{debug} off|phlo phhi break on phase load\n"); register_cmd("FDUMP", &fdump_cmd, 0, NULL); #endif for (drv = 0, uptr = dsk_dev.units; drv < DSK_NUMDR; drv++, uptr++) { sim_cancel(uptr); CLRBIT(ILSW[2], dsk_ilswbit[drv]); CLRBIT(uptr->flags, UNIT_OPERR|UNIT_HARDERR); uptr->CYL = 0; uptr->FUNC = DSK_FUNC_IDLE; dsk_dsw[drv] = (int16) ((uptr->flags & UNIT_ATT) ? DSK_DSW_CARRIAGE_HOME : 0); } calc_ints(); return SCPE_OK; } static t_stat dsk_attach (UNIT *uptr, CONST char *cptr) { int drv = uptr - dsk_unit; t_stat rval; char gbuf[2*CBUFSIZE]; sim_cancel(uptr); /* cancel current IO */ dsk_lastio[drv] = IO_NONE; if (uptr->flags & UNIT_ATT) /* dismount current disk */ if ((rval = dsk_detach(uptr)) != SCPE_OK) return rval; uptr->CYL = 0; /* reset the device */ uptr->FUNC = DSK_FUNC_IDLE; dsk_dsw[drv] = DSK_DSW_CARRIAGE_HOME; CLRBIT(uptr->flags, UNIT_RO|UNIT_ROABLE|UNIT_BUFABLE|UNIT_BUF|UNIT_RONLY|UNIT_OPERR|UNIT_HARDERR); CLRBIT(ILSW[2], dsk_ilswbit[drv]); calc_ints(); if (sim_switches & SWMASK('M')) /* if memory mode (e.g. for CGI), buffer the file */ SETBIT(uptr->flags, UNIT_BUFABLE|UNIT_MUSTBUF); if (sim_switches & SWMASK('R')) /* read lock mode */ SETBIT(uptr->flags, UNIT_RO|UNIT_ROABLE|UNIT_RONLY); if (cgi && (sim_switches & SWMASK('M')) && ! cgiwritable) { /* if cgi and memory mode, but writable option not specified */ sim_switches |= SWMASK('R'); /* have attach_unit open file in readonly mode */ SETBIT(uptr->flags, UNIT_ROABLE); /* but don't set the UNIT_RONLY flag so DMS can write to the buffered image */ } if ((rval = attach_unit(uptr, quotefix(cptr, gbuf))) != SCPE_OK) { /* mount new disk */ SETBIT(dsk_dsw[drv], DSK_DSW_NOT_READY); return rval; } if ((boot_drive >= 0) && (dsk_unit[boot_drive].flags & UNIT_ATT)) { disk_ready(TRUE); disk_unlocked(FALSE); } enable_dms_tracing(sim_switches & SWMASK('D')); raw_disk_debug = sim_switches & SWMASK('G'); return SCPE_OK; } static t_stat dsk_detach (UNIT *uptr) { t_stat rval; int drv = uptr - dsk_unit; sim_cancel(uptr); if ((rval = detach_unit(uptr)) != SCPE_OK) return rval; CLRBIT(ILSW[2], dsk_ilswbit[drv]); CLRBIT(uptr->flags, UNIT_OPERR|UNIT_HARDERR); calc_ints(); uptr->CYL = 0; uptr->FUNC = DSK_FUNC_IDLE; dsk_dsw[drv] = DSK_DSW_NOT_READY; if ((boot_drive >= 0) && (!(dsk_unit[boot_drive].flags & UNIT_ATT))) { disk_unlocked(TRUE); disk_ready(FALSE); } return SCPE_OK; } /* boot routine - if they type BOOT DSK, load the standard boot card. */ static t_stat dsk_boot (int32 unitno, DEVICE *dptr) { t_stat rval; if ((rval = reset_all(0)) != SCPE_OK) return rval; return load_cr_boot(unitno, sim_switches); } #ifdef TRACE_DMS_IO static struct { int phid; const char *name; } phase[] = { # include "dmsr2v12phases.h" {0xFFFF, ""} }; #pragma pack(2) #define MAXSLET ((3*320)/4) struct tag_slet { int16 phid; int16 addr; int16 nwords; int16 sector; } slet[MAXSLET] = { #include "dmsr2v12slet.h" /* without RPG, use this info until overwritten by actual data from disk */ }; #pragma pack() #define MAXMSEG 100 struct tag_mseg { const char *name; int addr, offset, len, phid; } mseg[MAXMSEG]; int nseg = 0; static void enable_dms_tracing (int newsetting) { nseg = 0; /* clear the segment map */ if ((newsetting && trace_dms) || ! (newsetting || trace_dms)) return; trace_dms = newsetting; if (! sim_quiet) printf("DMS disk tracing is now %sabled\n", trace_dms ? "en" : "dis"); } const char * saywhere (int addr) { int i; static char buf[150]; for (i = 0; i < nseg; i++) { if (addr >= mseg[i].addr && addr < (mseg[i].addr+mseg[i].len)) { sprintf(buf, "/%04x = /%04x + /%x in ", addr, mseg[i].addr - mseg[i].offset, addr-mseg[i].addr + mseg[i].offset); if (mseg[i].phid > 0) sprintf(buf+strlen(buf), "phase %02x (%s)", mseg[i].phid, mseg[i].name); else sprintf(buf+strlen(buf), "%s", mseg[i].name); return buf; } } return NULL; } static int phdebug_lo = -1, phdebug_hi = -1; static t_stat phdebug_cmd (int32 flag, CONST char *ptr) { int val1, val2; if (strcmpi(ptr, "off") == 0) phdebug_lo = phdebug_hi = -1; else { switch(sscanf(ptr, "%x%x", &val1, &val2)) { case 1: phdebug_lo = phdebug_hi = val1; enable_dms_tracing(TRUE); break; case 2: phdebug_lo = val1; phdebug_hi = val2; enable_dms_tracing(TRUE); break; default: printf("Usage: phdebug off | phdebug phfrom [phto]\n"); break; } } return SCPE_OK; } static t_stat where_cmd (int32 flag, CONST char *ptr) { int addr; const char *where; if (! trace_dms) { printf("Tracing is disabled. To enable, attach disk with -d switch\n"); return SCPE_OK; } if (sscanf(ptr, "%x", &addr) != 1) return SCPE_ARG; if ((where = saywhere(addr)) == NULL) printf("/%04x not found\n", addr); else printf("%s\n", where); return SCPE_OK; } /* savesector - save info on a sector just read. THIS IS NOT YET TESTED */ static void addseg (int i) { if (! trace_dms) return; if (nseg >= MAXMSEG) { printf("(Memory map full, disabling tracing)\n"); trace_dms = 0; nseg = -1; return; } memcpy(mseg+i+1, mseg+i, (nseg-i)*sizeof(mseg[0])); nseg++; } static void delseg (int i) { if (! trace_dms) return; if (nseg > 0) { nseg--; memcpy(mseg+i, mseg+i+1, (nseg-i)*sizeof(mseg[0])); } } static void savesector (int addr, int offset, int len, int phid, const char *name) { int i; if (! trace_dms) return; addr++; /* first word is sector address, so account for that */ len--; for (i = 0; i < nseg; i++) { if (addr >= (mseg[i].addr+mseg[i].len)) /* entirely after this entry */ continue; if (mseg[i].addr < addr) { /* old one starts before this. split it */ addseg(i); mseg[i].len = addr-mseg[i].addr; i++; mseg[i].addr = addr; mseg[i].len -= mseg[i-1].len; } break; } addseg(i); /* add new segment. Old one ends up after this */ if (i >= MAXMSEG) return; mseg[i].addr = addr; mseg[i].offset = offset; mseg[i].phid = phid; mseg[i].len = len; mseg[i].name = name; i++; /* delete any segments completely covered */ while (i < nseg && (mseg[i].addr+mseg[i].len) <= (addr+len)) delseg(i); if (i < nseg && mseg[i].addr < (addr+len)) { /* old one extends past this. Retain the end */ mseg[i].len = (mseg[i].addr+mseg[i].len) - (addr+len); mseg[i].addr = addr+len; } } static void tracesector (int iswrite, int nwords, int addr, int sector) { int i, phid = 0, offset = 0; const char *name = NULL; if (nwords < 3 || ! trace_dms) return; switch (sector) { /* explicitly known sector name */ case 0: name = "ID/COLD START"; break; case 1: name = "DCOM"; break; case 2: name = "RESIDENT IMAGE"; break; case 3: case 4: case 5: name = "SLET"; /* save just-read or written SLET info */ memmove(&slet[(320/4)*(sector-3)], &M[addr+1], nwords*2); break; case 6: name = "RELOAD TABLE"; break; case 7: name = "PAGE HEADER"; break; } printf("* %04x: %3d /%04x %c %3d.%d ", prev_IAR, nwords, addr, iswrite ? 'W' : 'R', sector/8, sector%8); if (name == NULL) { /* look up sector in SLET */ for (i = 0; i < MAXSLET; i++) { if (slet[i].phid == 0) /* not found */ goto done; else if (slet[i].sector > sector) { if (--i >= 0) { if (sector >= slet[i].sector && sector <= (slet[i].sector + slet[i].nwords/320)) { phid = slet[i].phid; offset = (sector-slet[i].sector)*320; break; } } goto done; } if (slet[i].sector == sector) { phid = slet[i].phid; /* we found the starting sector */ break; } } if (i >= MAXSLET) /* was not found */ goto done; name = "?"; for (i = sizeof(phase)/sizeof(phase[0]); --i >= 0; ) { if (phase[i].phid == phid) { /* look up name */ name = phase[i].name; break; } } printf("%02x %s", phid, name); } else printf("%s", name); done: putchar('\n'); if (phid >= phdebug_lo && phid <= phdebug_hi && offset == 0) break_simulation(STOP_PHASE_BREAK); /* break on read of first sector of indicated phases */ if (name != NULL && *name != '?' && ! iswrite) savesector(addr, offset, nwords, phid, name); } static t_stat fdump_cmd (int32 flags, CONST char *cptr) { int addr = 0x7a24; /* address of next statement */ int sofst = 0x7a26, symaddr; int cword, nwords, stype, has_stnum, strel = 1, laststno = 0; addr = M[addr & mem_mask] & mem_mask; /* get address of first statement */ sofst = M[sofst & mem_mask] & mem_mask ; /* get address of symbol table */ for (;;) { cword = M[addr]; nwords = (cword >> 2) & 0x01FF; stype = (cword >> 1) & 0x7C00; has_stnum = (cword & 1); if (has_stnum) { laststno++; strel = 0; } printf("/%04x [%4d +%3d] %3d - %04x", addr, laststno, strel, nwords, stype); if (has_stnum) { addr++; nwords--; symaddr = sofst - (M[addr] & 0x7FF)*3 + 3; printf(" [%04x %04x %04x]", M[symaddr], M[symaddr+1], M[symaddr+2]); } if (stype == 0x5000) { /* error record */ printf(" (err %d)", M[addr+1]); } if (stype == 0x0800) break; addr += nwords; putchar('\n'); if (nwords == 0) { printf("0 words?\n"); break; } strel++; } printf("\nEnd found at /%04x, EOFS = /%04x\n", addr, M[0x7a25 & mem_mask]); return SCPE_OK; } #endif /* TRACE_DMS_IO */