/* ka10_ai.c: Systems Concepts DC-10 disk control Copyright (c) 2019, Lars Brinkhoff 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 LARS BRINKHOFF 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. This disk controller was probably only ever used with the MIT AI Lab PDP-10. Since the device name DC is alreay claimed, we call this AI. */ #include "kx10_defs.h" #ifndef NUM_DEVS_AI #define NUM_DEVS_AI 0 #endif #if (NUM_DEVS_AI > 0) /* Disk pack geometry. The track format is software defined. ITS and SALV makes it hold two sectors with 1024 words of regular data and 4 extra words. */ #define SECTOR_SIZE 1024 #define SECTORS 2 #define SURFACES 20 #define MEMOREX_CYLINDERS 203 #define CALCOMP_CYLINDERS (2 * MEMOREX_CYLINDERS) #define CYLINDER_SIZE (SECTOR_SIZE * SECTORS * SURFACES) #define MEMOREX_SIZE (CYLINDER_SIZE * MEMOREX_CYLINDERS) #define CALCOMP_SIZE (CYLINDER_SIZE * CALCOMP_CYLINDERS) /* The real sector size, including 2 header words, 4 extra data words, and 2 checksum words. */ #define SECTOR_REAL_SIZE (SECTOR_SIZE + 8) /* A track actually has some more free space. Cylinder 0, surface 0 has a readin block there. */ #define TRACK_REAL_SIZE ((SECTORS + 1) * SECTOR_REAL_SIZE) #define CYLINDER_REAL_SIZE (SURFACES * TRACK_REAL_SIZE) #define AI_DEVNUM 0610 /* First device number; 614 also used. */ #define AI_NAME "AI" #define NUM_UNITS 16 /* Hardware units, but ITS only supports 8. */ /* All bit definitions are from the ITS file SYSTEM; DC10 DEFS27. */ /* CONI DC0 */ #define DASSGN 0400000000000LL /* ASSIGNED TO PROC (WITH SWITCH) */ #define DPIRQC 0400000 /* PI REQ BEING GENERATED */ #define DSSRQ 0200000 /* SEEK REQUEST */ #define DSDEEB 0010000 /* ENABLE INTERRUPT ON DATA ERROR OR READ/ COMP ERROR */ #define DSSERR 0004000 /* ERROR FLAG */ #define DSSAEB 0002000 /* ATTENTION ENABLE FLAG */ #define DSSATT 0001000 /* ATTENTION FLAG */ #define DSIENB 0000400 /* IDLE FLAG ENABLE */ #define DSSRUN 0000200 /* RUN */ #define DSSACT 0000100 /* ACTIVE */ #define DSSCEB 0000040 /* CHANNEL ENABLE */ #define DSSCHF 0000020 /* CHANNEL FLAG */ #define DSSCFL 0000010 /* CPU FLAG */ /* CONO DC0 */ #define DCSET 0400000 /* SET SELECTED */ #define DCCLR 0200000 /* CLEAR SELECTED */ #define DCCSET 0600000 /* RESET CONTROLLER THEN SET SELECTED */ #define DCDENB 0010000 /* DATA ERROR ENABLE */ #define DCERR 0004000 /* SET ERROR FLAG OR CLEAR ALL ERRORS */ #define DCATEB 0002000 /* ATTENTION ENABLE */ #define DCCATT 0001000 /* CLEAR ATTENTION */ #define DCSSRQ 0001000 /* SET SEEK REQUEST */ #define DCIENB 0000400 /* IDLE ENABLE */ #define DCSTAR 0000200 /* START (SET) */ #define DCSSTP 0000200 /* STOP (CLEAR) */ #define DCSGL 0000100 /* DO SINGLE COMMAND */ #define DCCENB 0000040 /* CHANNEL ENABLE */ #define DCCFLG 0000020 /* CHANNEL FLAG */ #define DCCPUF 0000010 /* CPU FLAG */ /* Bits to set or clear with DCSET or DCCLR. */ #define SET_MASK (DCDENB|DCERR|DCATEB|DCIENB|DCSTAR) #define CLEAR_MASK (DCDENB|DCERR|DCATEB|DCCATT|DCIENB|DCSSTP) /* CONI DC1 */ #define DIPE 04000 /* INTERNAL PARITY ERROR */ #define DRLNER 02000 /* RECORD LENGTH */ #define DRCER 01000 /* READ COMPARE ERROR */ #define DOVRRN 00400 /* OVERRUN */ #define DCKSER 00200 /* CKSUM OR DECODER ERR */ #define DWTHER 00100 /* WATCHDOG TIMER */ #define DFUNSF 00040 /* FILE UNSAFE, SEEK INCOMPLETE OR END OR DSK */ #define DOFFL 00020 /* OFF LINE OR MULT SEL */ #define DPROT 00010 /* WRT KEY OR RD ONLY OR PROTECT */ #define DDOBSY 00004 /* DATAO WHEN BSY */ #define DNXM 00002 /* NON-EX MEM */ #define DCPERR 00001 /* CORE PARITY ERR */ /* Channel commands */ #define DUNENB 0020000000000LL /* ENABLE LOAD UNIT FIELD */ #define DCMD 0740000000000LL #define DCOPY 0040000000000LL /* COPY */ #define DCCOMP 0100000000000LL /* COMPARE */ #define DCSKIP 0140000000000LL /* SKIP */ #define DOPR 0200000000000LL /* BASIC OPR */ #define DSDRST 0240000000000LL /* STORE DRIVE STATUS */ #define DALU 0300000000000LL /* BASIC ALU OP CODE */ #define DRC 0400000000000LL /* READ COMPARE */ #define DWRITE 0440000000000LL /* WRITE */ #define DREAD 0500000000000LL /* READ */ #define DSEEK 0540000000000LL /* SEEK */ #define DRCC 0600000000000LL /* READ COMPARE CONTINUOUS */ #define DWRITC 0640000000000LL /* WRITE CONTINUOUS */ #define DREADC 0700000000000LL /* READ CONTINUOUS */ #define DSPC 0740000000000LL /* Special command. */ #define DHLT 0 /* 0 IN 4.9-4.5 = JUMP AND IN 3.5,3.6 = HALT */ #define DXCT 0000020000000LL /* XCT */ #define DJMP 0000040000000LL /* JUMP */ #define DJSR 0000060000000LL /* JSR */ #define DJMASK 0000060000000LL /* OPR */ #define DOHXFR 0400000000LL /* HALT DURING XFER (SO MB WILL BE SAFE) */ /* Special command, E condition (wait). */ #define DSWIDX 0020000000LL /* WAIT UNTIL INDEX PULSE */ #define DSWSEC 0040000000LL /* WAIT UNTIL SECTOR PULSE */ #define DSWINF 0060000000LL /* NEVER (USE WITH G=3 OR 7) */ /* Special command, F condition (other wait). */ #define DSWNUL 0014000000LL /* NO WAIT */ /* Special command, G operation. */ #define DSCRHD 0200000000LL /* READ HEADER WORDS */ #define DSRCAL 0300000000LL /* (RECALIBRATE) */ #define DSCWIM 0500000000LL /* WRITE IMAGE */ /* ALU */ #define DLCC 010000000LL /* OP FROM CC, STORE IN CC */ #define DLDBWC 030000000LL /* OP A FROM DB, STORE IN WC */ #define WC 0037774000000LL /* Word count. */ #define ADDR 0000003777777LL /* Address field. */ /* Drive status. */ #define DDSWC 040000000LL /* WRITE CURRENT SENSED */ #define DDSUNS 020000000LL /* DRIVE UNSAFE */ #define DDSRDO 010000000LL /* READ ONLY */ #define DDSSIC 004000000LL /* SEEK INCOMPLETE */ #define DDSRDY 002000000LL /* DRIVE READY */ #define DDSONL 001000000LL /* DRIVE ON LINE */ #define DDSSEL 000400000LL /* DRIVE SELECTED */ enum { MODE_ERROR = 0, MODE_WRITE, /* Write sector data. */ MODE_READ, /* Read sector data. */ MODE_READ_HEADERS, /* Read sector headers. */ MODE_COMPARE, /* Compare sector data. */ MODE_IMAGE /* Write raw image. */ }; enum image_state { IMAGE_GAP, /* Empty bits (ones) between sectors. */ IMAGE_PREAMBLE, /* Bit pattern before sector header. */ IMAGE_HEADER, /* Sector header, in FM encoding. */ IMAGE_POSTAMBLE, /* Empty bits (ones). */ IMAGE_POSTAMBLE2, /* A "01" to start the sector data. */ IMAGE_SECTOR, /* Sector data, in FM encoding. */ IMAGE_ERROR, }; static enum image_state image_state = IMAGE_ERROR; static int image_count, image_sector_length; static t_stat ai_devio(uint32 dev, uint64 *data); static t_stat ai_svc(UNIT *); static t_stat ai_reset(DEVICE *); static t_stat ai_attach(UNIT *, CONST char *); static t_stat ai_detach(UNIT *); static t_stat ai_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr); static const char *ai_description (DEVICE *dptr); UNIT ai_unit[] = { { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, { UDATA (&ai_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+ UNIT_ROABLE, CALCOMP_SIZE) }, }; DIB ai_dib = { AI_DEVNUM, 2, &ai_devio, NULL }; MTAB ai_mod[] = { {0} }; DEBTAB ai_debug[] = { {"IRQ", DEBUG_IRQ, "Debug IRQ requests"}, {"CMD", DEBUG_CMD, "Show command execution to devices"}, {"DATA", DEBUG_DATA, "Show data transfers"}, {"DETAIL", DEBUG_DETAIL, "Show details about device"}, {"EXP", DEBUG_EXP, "Show exception information"}, {"CONI", DEBUG_CONI, "Show CONI instructions"}, {"CONO", DEBUG_CONO, "Show CONO instructions"}, {"DATAIO", DEBUG_DATAIO, "Show DATAI and DATAO instructions"}, {0, 0} }; static UNIT *channel_unit = ai_unit; static int latency_unit = 0; static int channel_pc = 0; static int channel_status = 0; static uint64 channel_errors = 0; static int channel_cc = 0; static int channel_wc = 0; static int channel_mode = MODE_ERROR; static int channel_delay; static int channel_default_delay = 1000; static int channel_seek_initial = 25000; /* Milliseconds. */ static int channel_seek_delay = 500; /* Per cylinder travelled. */ static int channel_cylinder = 0; REG ai_reg[] = { {ORDATA(PC, channel_pc, 20)}, {ORDATA(STS, channel_status, 18)}, {ORDATA(ERR, channel_errors, 12)}, {ORDATA(CC, channel_cc, 20)}, {ORDATA(WC, channel_wc, 12)}, {ORDATA(SI, channel_seek_initial, 32)}, {ORDATA(SD, channel_seek_delay, 32)}, {ORDATA(CYL, channel_cylinder, 9)}, {0} }; DEVICE ai_dev = { AI_NAME, ai_unit, ai_reg, ai_mod, NUM_UNITS, 8, 18, 1, 8, 36, NULL, NULL, &ai_reset, NULL, &ai_attach, &ai_detach, &ai_dib, DEV_DISABLE | DEV_DEBUG | DEV_DIS, 0, ai_debug, NULL, NULL, &ai_help, NULL, NULL, &ai_description }; static void clear_interrupt (void) { if ((channel_status & (DSDEEB|DSSERR)) == (DSDEEB|DSSERR) || (channel_status & (DSSAEB|DSSATT)) == (DSSAEB|DSSATT) || (channel_status & (DSIENB|DSSRUN)) == DSIENB) { channel_status |= DPIRQC; sim_debug(DEBUG_IRQ, &ai_dev, "Set interrupt: %06o\n", channel_status); set_interrupt (AI_DEVNUM, channel_status); } else { channel_status &= ~DPIRQC; sim_debug(DEBUG_IRQ, &ai_dev, "Clear interrupt\n"); clr_interrupt (AI_DEVNUM); } } static void channel_error (int errors) { channel_errors |= errors; channel_status |= DSSERR; if (channel_status & DSDEEB) { channel_status |= DPIRQC; sim_debug(DEBUG_IRQ, &ai_dev, "Set error interrupt\n"); set_interrupt (AI_DEVNUM, channel_status); } } static void channel_seek (const char *cmd, uint64 data, int offset) { int cyl, sur, sec, x; int da; if (data & DUNENB) channel_unit = &ai_unit[(data >> 033) & 017]; cyl = (data >> 11) & 0777; sur = (data >> 6) & 037; sec = data & 077; if (cyl >= CALCOMP_CYLINDERS && sur >= SURFACES && sec >= SECTORS) { sim_debug(DEBUG_EXP, &ai_dev, "Seek outside geometry\n"); channel_error (DOVRRN); return; } da = SECTOR_REAL_SIZE * sec; da += TRACK_REAL_SIZE * sur; da += CYLINDER_REAL_SIZE * cyl; da += offset; if (channel_unit->flags & UNIT_ATT) { (void)sim_fseek(channel_unit->fileref, da * sizeof(uint64), SEEK_SET); x = channel_cylinder - cyl; if (x < 0) x = -x; if (x > 0) channel_delay = channel_seek_initial + x * channel_seek_delay; channel_cylinder = cyl; sim_debug(DEBUG_CMD, &ai_dev, "%s: unit %d seek %d (%d,%d,%d)\n", cmd, (int)(channel_unit - ai_unit), channel_delay, cyl, sur, sec); } else { sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n"); channel_error (DOFFL); } } static void channel_special (uint64 data) { if (data & DUNENB) channel_unit = &ai_unit[(data >> 033) & 017]; switch (data & 0700000000LL) { case DSCRHD: channel_mode = MODE_READ_HEADERS; channel_seek ("READ HEADER WORDS", data, 0); break; case DSRCAL: sim_debug(DEBUG_CMD, &ai_dev, "Command: (RECALIBRATE)\n"); channel_status |= DSSATT; channel_errors &= ~(017LL << 036); channel_errors |= (channel_unit - ai_unit) << 036; if (channel_status & DSSAEB) { channel_status |= DPIRQC; sim_debug(DEBUG_IRQ, &ai_dev, "Set attention interrupt\n"); set_interrupt (AI_DEVNUM, channel_status); } break; case DSCWIM: if ((channel_unit->flags & UNIT_ATT) == 0) { sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n"); channel_error (DOFFL); } else if (channel_unit->flags & UNIT_RO) { sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n"); channel_error (DPROT); } else { channel_mode = MODE_IMAGE; image_state = IMAGE_GAP; image_count = 0; channel_seek ("WRITE IMAGE", data, 0); } break; default: sim_debug(DEBUG_CMD, &ai_dev, "(unknown special: %012llo)\n", data); break; } } static void channel_alu (uint64 data) { switch (data & 034000000LL) { case DLCC: channel_cc = data & ADDR; sim_debug(DEBUG_CMD, &ai_dev, "ALU: OP FROM CC, STORE IN CC: %o\n", channel_cc); break; case DLDBWC: channel_wc = data & 07777; sim_debug(DEBUG_CMD, &ai_dev, "ALU: OP A FROM DB, STORE IN WC: %o\n", channel_wc); break; default: sim_debug(DEBUG_CMD, &ai_dev, "ALU: (unkownn)\n"); break; } } static void print_data (uint64 *data, int n) { int i; for (i = 0; i < n; i++) sim_debug(DEBUG_DATA, &ai_dev, "Data %012llo\n", *data++); } static t_stat sim_fcompare (void *x, size_t m, size_t n, FILE *f) { static uint64 buf[10240]; if ((channel_unit->flags & UNIT_ATT) == 0) { sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n"); channel_error (DOFFL); return SCPE_OK; } (void)sim_fread (buf, m, n, f); sim_debug(DEBUG_DATA, &ai_dev, "Memory contents:\n"); print_data ((uint64 *)x, n); sim_debug(DEBUG_DATA, &ai_dev, "Disk contents:\n"); print_data (buf, n); if (memcmp (x, buf, m * n) != 0) { sim_debug(DEBUG_EXP, &ai_dev, "Compare failed.\n"); channel_error (DRCER); } return SCPE_OK; } /* The WRITE IMAGE command writes the sector headers as 56 continuous bits. However, the READ HEADERS command presents them as 28-bit halves, each right aligned in a 36-bit word. The image file stores the first format, so here we need to split the words apart. Also skip over the sector data to get to next header. */ static t_stat sim_freadh (uint64 *x, size_t n, FILE *f) { uint64 buf[2]; size_t i; if ((channel_unit->flags & UNIT_ATT) == 0) { sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n"); channel_error (DOFFL); return SCPE_OK; } for (i = 0; i < n; i++) { if ((i & 1) == 0) { (void)sim_fread (buf, sizeof (uint64), 2, f); (void)sim_fseek(f, (SECTOR_REAL_SIZE-2) * sizeof(uint64), SEEK_CUR); x[i] = buf[0] >> 8; } else { x[i] = (buf[0] & 0377) << 20; x[i] |= buf[1] >> 16; } } return SCPE_OK; } /* The track data fields are in somthing close to FM encoding. Here we decode three bits at a time to yeild two bits of data. When 36 data bits have been decoded, output a word to the image file. */ static void decode_fm (int bit, FILE *f) { static int state = 0; static uint64 word = 0; static int n = 0; static int bits = 1; bits = (bits << 1) + bit; state++; if (state != 3) return; word <<= 2; switch (bits & 017) { case 005: case 007: word |= (bits >> 4) & 2; word |= (bits >> 1) & 1; break; case 012: case 016: break; case 013: case 015: case 017: word |= (bits >> 1) & 3; break; default: sim_debug(DEBUG_EXP, &ai_dev, "Error in FM encoding: %o\n", bits); channel_error (DCKSER); break; } state = 0; n += 2; //sim_debug(DEBUG_DETAIL, &ai_dev, "FM: %o, %d, %012llo\n", // bits, n, word); if (n == 36) { //sim_debug(DEBUG_DETAIL, &ai_dev, "Data: %012llo\n", word); (void)sim_fwrite (&word, sizeof word, 1, f); n = 0; word = 0; } } /* Decode a bit stream from the WRITE IMAGE command. */ static void decode_bit (int bit, FILE *f) { static const int preamble_bits[] = { 1, 0, 1, 0, 1 }; //sim_debug(DEBUG_DETAIL, &ai_dev, "Image: bit %o\n", bit); switch (image_state) { case IMAGE_GAP: if (bit == 0) { sim_debug(DEBUG_DETAIL, &ai_dev, "Image: %d gap bits\n", image_count); image_state = IMAGE_PREAMBLE; image_count = 0; } else { image_count++; } break; case IMAGE_PREAMBLE: if (bit != preamble_bits[image_count % 5]) { sim_debug(DEBUG_DETAIL, &ai_dev, "Image: error in preamble bit %d\n", image_count); image_state = IMAGE_ERROR; break; } image_count++; if (image_count == 5*8) { sim_debug(DEBUG_DETAIL, &ai_dev, "Image: preamble ok\n"); image_state = IMAGE_HEADER; image_count = 0; } break; case IMAGE_HEADER: decode_fm (bit, f); image_count++; if (image_count == 108) { t_offset pos; uint64 header[2]; image_state = IMAGE_POSTAMBLE; image_count = 0; pos = sim_ftell (channel_unit->fileref); (void)sim_fseeko(channel_unit->fileref, pos - 2 * sizeof(uint64), SEEK_SET); (void)sim_fread(header, sizeof(uint64), 2, channel_unit->fileref); (void)sim_fseeko(channel_unit->fileref, pos, SEEK_SET); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: key %03llo\n", (header[0] >> 28) & 0377); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: cylinder %lld\n", (header[0] >> 19) & 0777); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: surface %lld\n", (header[0] >> 14) & 037); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: sector %lld\n", (header[0] >> 8) & 077); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: indirect %llo\n", (header[0] >> 7) & 1); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: software protect %llo\n", (header[0] >> 6) & 1); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: hardware protect %llo\n", (header[0] >> 5) & 1); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: parity %llo\n", header[0] & 3); image_sector_length = 040000 - ((header[1] >> 16) & 037777); sim_debug(DEBUG_DETAIL, &ai_dev, "Header: length %o\n", image_sector_length); if (image_sector_length > 02004) { sim_debug(DEBUG_EXP, &ai_dev, "Record length error\n"); channel_error (DRLNER); image_state = IMAGE_SECTOR; } image_sector_length += 2; /* Checksum */ image_sector_length *= 54; /* 36-bit words, FM coded. */ } break; case IMAGE_POSTAMBLE: image_count++; if (bit == 0) { sim_debug(DEBUG_DETAIL, &ai_dev, "Image: %d gap bits\n", image_count); image_state = IMAGE_POSTAMBLE2; image_count = 0; } break; case IMAGE_POSTAMBLE2: if (bit == 0) { sim_debug(DEBUG_DETAIL, &ai_dev, "Image: error in postamble\n"); image_state = IMAGE_ERROR; } else { image_state = IMAGE_SECTOR; } break; case IMAGE_SECTOR: decode_fm (bit, f); image_count++; if (image_count == image_sector_length) { image_state = IMAGE_GAP; image_count = 0; } break; case IMAGE_ERROR: break; } } static void decode_image (uint64 *data, int n, FILE *f) { int i, j; for (i = 0; i < n; i++) { for (j = 35; j >= 0; j--) decode_bit ((int)(*data >> j) & 1, f); data++; } } static int check_nxm (uint64 data, int *n, uint64 *data2, int *n2) { unsigned int addr = data & ADDR; *data2 = 0; *n2 = 0; if (addr + *n > MEMSIZE) { if (MEMSIZE < (ADDR+1)) { sim_debug(DEBUG_EXP, &ai_dev, "Access outside core memory\n"); *n = MEMSIZE - addr; channel_error (DNXM); return 1; } else { /* Access wraps around 21-bit address. */ *n2 = addr + *n - MEMSIZE; *data2 = 0; *n = MEMSIZE - addr; return 0; } } return 0; } /* Execute one channel instruction. It may come from a channel program in core, or from a DATAO DC0, */ static void channel_command (uint64 data) { struct timespec ts; int latency_timer; int n, n2; uint64 data2; int nxm = 0; if ((data & (DCMD|DUNENB)) == 0) { switch (data & DJMASK) { case DHLT: sim_debug(DEBUG_CMD, &ai_dev, "Command: DHLT\n"); channel_status &= ~(DSSRUN|DSSACT); if (channel_status & DSIENB) { channel_status |= DPIRQC; sim_debug(DEBUG_IRQ, &ai_dev, "Set idle interrupt\n"); set_interrupt (AI_DEVNUM, channel_status); } break; case DXCT: sim_debug(DEBUG_CMD, &ai_dev, "Command: XCT\n"); break; case DJMP: channel_status |= DSSRUN|DSSACT; sim_activate(ai_unit, channel_default_delay); clear_interrupt (); if ((data & 014000000LL) == 004000000LL) { sim_debug(DEBUG_CMD, &ai_dev, "Command: JUMP DAOJNC: %o\n", channel_cc); channel_cc++; if (channel_cc != ADDR+1) channel_pc = data & ADDR; } else { sim_debug(DEBUG_CMD, &ai_dev, "Command: JUMP\n"); channel_pc = data & ADDR; } break; case DJSR: sim_debug(DEBUG_CMD, &ai_dev, "Command: JSR\n"); n = 1; if (check_nxm (data, &n, &data2, &n2)) break; M[data & ADDR] = (channel_pc + (channel_unit - ai_unit)) << 036; channel_pc++; channel_status |= DSSRUN|DSSACT; sim_activate(ai_unit, channel_default_delay); break; } return; } switch (data & DCMD) { case DCOPY: n = (data & WC) >> 20; if (n == 0) n = channel_wc; n = 010000 - n; sim_debug(DEBUG_CMD, &ai_dev, "COPY %d words to/from %012llo.\n", n, data & ADDR); if ((channel_unit->flags & UNIT_ATT) == 0) { sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n"); channel_error (DOFFL); break; } nxm = check_nxm (data, &n, &data2, &n2); switch (channel_mode) { case MODE_READ: (void)sim_fread (&M[data & ADDR], sizeof(uint64), n, channel_unit->fileref); if (nxm) break; (void)sim_fread (&M[data2], sizeof(uint64), n2, channel_unit->fileref); print_data (&M[data & ADDR], n); break; case MODE_READ_HEADERS: (void)sim_freadh (&M[data & ADDR], n, channel_unit->fileref); if (nxm) break; (void)sim_freadh (&M[data2], n2, channel_unit->fileref); break; case MODE_WRITE: if (channel_unit->flags & UNIT_RO) { sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n"); channel_error (DPROT); } else { (void)sim_fwrite (&M[data & ADDR], sizeof(uint64), n, channel_unit->fileref); if (nxm) break; (void)sim_fwrite (&M[data2], sizeof(uint64), n2, channel_unit->fileref); } break; case MODE_COMPARE: (void)sim_fcompare (&M[data & ADDR], sizeof(uint64), n, channel_unit->fileref); if (nxm) break; (void)sim_fcompare (&M[data2], sizeof(uint64), n2, channel_unit->fileref); /* If at the end of the sector, skip to next sector. */ if ((sim_ftell (channel_unit->fileref) / sizeof(uint64)) % SECTOR_REAL_SIZE == 1030) (void)sim_fseek(channel_unit->fileref, 4 * sizeof(uint64), SEEK_CUR); break; case MODE_IMAGE: decode_image (&M[data & ADDR], n, channel_unit->fileref); break; default: break; } break; case DCCOMP: n = (data & WC) >> 20; if (n == 0) n = channel_wc; n = 010000 - n; sim_debug(DEBUG_CMD, &ai_dev, "COMPARE %d words\n", n); if ((channel_unit->flags & UNIT_ATT) == 0) { sim_debug(DEBUG_EXP, &ai_dev, "Drive offline\n"); channel_error (DOFFL); break; } nxm = check_nxm (data, &n, &data2, &n2); (void)sim_fcompare (&M[data & ADDR], sizeof(uint64), n, channel_unit->fileref); if (nxm) break; (void)sim_fcompare (&M[data2], sizeof(uint64), n2, channel_unit->fileref); break; case DCSKIP: n = 010000 - ((data & WC) >> 20); sim_debug(DEBUG_CMD, &ai_dev, "SKIP %o words\n", n); (void)sim_fseek(channel_unit->fileref, n * sizeof(uint64), SEEK_CUR); break; case DOPR: if (data & DOHXFR) sim_debug(DEBUG_CMD, &ai_dev, "OPR: Hang during xfer\n"); else sim_debug(DEBUG_CMD, &ai_dev, "OPR ...\n"); break; case DSDRST: if (data & DUNENB) channel_unit = &ai_unit[(data >> 033) & 017]; sim_debug(DEBUG_CMD, &ai_dev, "DSDRST, store unit %d status in %012llo.\n", (int)(channel_unit - ai_unit), data & ADDR); n = 1; if (check_nxm (data, &n, &data2, &n2)) break; (void)sim_rtcn_get_time (&ts, 0); latency_timer = ts.tv_nsec / 100000; latency_timer %= 254; M[data & ADDR] = latency_timer & 0377; M[data & ADDR] |= channel_cylinder << 8; if (channel_unit->flags & UNIT_ATT) /* Drive online. */ M[data & ADDR] |= DDSONL; if (channel_unit->flags & UNIT_RO) /* Drive read-only. */ M[data & ADDR] |= DDSRDO; break; case DALU: channel_alu (data); break; case DRC: channel_mode = MODE_COMPARE; channel_seek ("READ COMPARE", data, 2); break; case DWRITE: if (channel_unit->flags & UNIT_RO) { sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n"); channel_error (DPROT); channel_mode = MODE_ERROR; } else { channel_mode = MODE_WRITE; channel_seek ("WRITE", data, 2); } break; case DREAD: channel_mode = MODE_READ; channel_seek ("READ", data, 2); break; case DSEEK: channel_seek ("SEEK", data, 2); break; case DRCC: channel_mode = MODE_COMPARE; channel_seek ("READ COMPARE CONTINUOUS", data, 2); break; case DWRITC: if (channel_unit->flags & UNIT_RO) { sim_debug(DEBUG_EXP, &ai_dev, "Drive read only\n"); channel_error (DPROT); channel_mode = MODE_ERROR; } else { channel_mode = MODE_WRITE; channel_seek ("WRITE CONTINUOUS", data, 2); } break; case DREADC: channel_mode = MODE_READ; channel_seek ("READ CONTINUOUS", data, 2); break; case DSPC: channel_special (data); break; default: sim_debug(DEBUG_CMD, &ai_dev, "(unknown command: %012llo)\n", data); break; } } /* Process one channel instruction and update the channel state. */ static void channel_run (void) { uint64 data, data2; int n = 1, n2; if (check_nxm (channel_pc, &n, &data2, &n2)) return; data = M[channel_pc]; //sim_debug(DEBUG_CMD, &ai_dev, "Channel PC=%06o %012llo\n", // channel_pc, data); channel_pc++; channel_command (data); } t_stat ai_devio(uint32 dev, uint64 *data) { struct timespec ts; int latency_timer; switch(dev & 7) { case CONI: *data = channel_status; sim_debug(DEBUG_CONI, &ai_dev, "DC0, PC=%06o %012llo\n", PC, *data); return SCPE_OK; case CONO: sim_debug(DEBUG_CONO, &ai_dev, "DC0, PC=%06o %012llo\n", PC, *data); if ((*data & DCCSET) == DCCSET) { sim_debug(DEBUG_CMD, &ai_dev, "Reset controller then set selected.\n"); ai_reset (&ai_dev); } channel_status &= ~7; channel_status |= *data & 7; if (*data & DCSET) { channel_status |= *data & SET_MASK; if (*data & DCSSRQ) channel_status |= DSSRQ; sim_debug(DEBUG_CMD, &ai_dev, "Set bits: %012llo -> %06o\n", *data & SET_MASK, channel_status); } else if (*data & DCCLR) { channel_status &= ~(*data & CLEAR_MASK); sim_debug(DEBUG_CMD, &ai_dev, "Clear bits: %012llo -> %06o\n", *data & CLEAR_MASK, channel_status); if (*data & DCERR) channel_errors = 0; } clear_interrupt (); return SCPE_OK; case DATAI: *data = 0; sim_debug(DEBUG_DATAIO, &ai_dev, "DATAI DC0, PC=%06o %012llo\n", PC, *data); return SCPE_OK; case DATAO: sim_debug(DEBUG_DATAIO, &ai_dev, "DATAO DC0, PC=%06o %012llo\n", PC, *data); if (channel_status & (DSSRUN|DSSACT)) { sim_debug(DEBUG_EXP, &ai_dev, "DATAO when busy\n"); channel_error (DDOBSY); } else channel_command (*data); return SCPE_OK; case CONI|4: /* Latency timer, timer unit, attention unit. */ (void)sim_rtcn_get_time (&ts, 0); latency_timer = ts.tv_nsec / 100000; latency_timer %= 254; *data = (latency_timer << 022) | (latency_unit << 032) | channel_errors; sim_debug(DEBUG_CONI, &ai_dev, "DC1, PC=%06o %012llo\n", PC, *data); return SCPE_OK; case CONO|4: sim_debug(DEBUG_CONO, &ai_dev, "DC1, PC=%06o %012llo\n", PC, *data); sim_debug(DEBUG_CMD, &ai_dev, "Latency timer set to unit %llo\n", *data); latency_unit = *data & 7; return SCPE_OK; case DATAI|4: *data = 0; sim_debug(DEBUG_DATAIO, &ai_dev, "DATAI DC1, PC=%06o %012llo\n", PC, *data); return SCPE_OK; case DATAO|4: sim_debug(DEBUG_DATAIO, &ai_dev, "DATAO DC1, PC=%06o %012llo\n", PC, *data); return SCPE_OK; } return SCPE_OK; /* Unreached */ } t_stat ai_svc (UNIT *uptr) { int i; channel_delay = channel_default_delay; for (i = 0; (channel_status & DSSRUN) && i < 10; i++) channel_run (); if (channel_status & DSSRUN) sim_activate(uptr, channel_delay); return SCPE_OK; } t_stat ai_reset(DEVICE *dptr) { channel_status = 0; channel_errors = 0; channel_pc = 0; channel_cc = 0; channel_wc = 0; channel_mode = 0; return SCPE_OK; } /* Device attach */ t_stat ai_attach (UNIT *uptr, CONST char *cptr) { t_stat r; DEVICE *rptr; DIB *dib; r = attach_unit (uptr, cptr); if (r != SCPE_OK || (sim_switches & SIM_SW_REST) != 0) return r; rptr = find_dev_from_unit(uptr); if (rptr == 0) return SCPE_OK; dib = (DIB *) rptr->ctxt; set_interrupt(dib->dev_num, 0); return SCPE_OK; } /* Device detach */ t_stat ai_detach (UNIT *uptr) { if (!(uptr->flags & UNIT_ATT)) /* attached? */ return SCPE_OK; if (sim_is_active (uptr)) /* unit active? */ sim_cancel (uptr); /* cancel operation */ return detach_unit (uptr); } t_stat ai_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) { fprintf (st, "Systems Concepts DC-10\n\n"); fprint_set_help (st, dptr); fprint_show_help (st, dptr); fprint_reg_help (st, dptr); return SCPE_OK; } const char *ai_description (DEVICE *dptr) { return "Systems Concepts DC-10 disk controller"; } #endif