From 090ed215de86c6b76c58070c1b853856afb163fd Mon Sep 17 00:00:00 2001 From: Tony Lawrence Date: Thu, 5 Oct 2023 14:20:44 -0400 Subject: [PATCH] PDP11: RP11: Major update after XXDP Having run the device code thru XXDP and some other OS's and scenarios rigorously, a bunch of discrepancies were found, which need to be addressed by this rather extensive patch. 1. Each unit must implement its own "drive status" register, to be able to track per-drive errors / conditions correctly; 2. Fixed INT_SET() / INT_CLR() in RPCS write function (wrong order of the "if" conditions); 3. Some behavior was implemented not exactly how it was expected from the real hardware, such as: a. Post-I/O register values in RPDA and RPCA (including the corner case of pack overflow); b. I/O stacking, which wasn't mentioned in any available documentation, but only XXDP listings; c. RESET/IDLE function must be accepted for a "busy" controller; d. HOME function must always execute, even when "device ready" is not set (e.g. when SEEK error detected); e. SEEK incomplete should not respond with "device ready" (however, the condition can be cleared by HOME, d.); f. WLOA-induced write-lock violation wasn't reflected in "device status". 4. Some timing was off so that the device worked "too fast" -- this was fixed (except for the pathological cases when the races are in the actual test code, and cannot be logically fixed); 5. WLOA setup command bug was fixed; 6. Added more code comments found per the above peculiarities. --- PDP11/pdp11_rr.c | 589 ++++++++++++++++++++++++++++------------------- 1 file changed, 353 insertions(+), 236 deletions(-) diff --git a/PDP11/pdp11_rr.c b/PDP11/pdp11_rr.c index 626b54224..29c6ad5d5 100644 --- a/PDP11/pdp11_rr.c +++ b/PDP11/pdp11_rr.c @@ -68,9 +68,10 @@ /* Parameters in the unit descriptor */ #define CYL u3 /* current cylinder */ -#define FUNC u4 /* function */ -#define HEAD u5 /* current track */ -#define SEEKING u6 /* unit performing a seek */ +#define HEAD u4 /* current track */ +#define FUNC u5 /* current func */ +#define STATUS u6 /* RPDS bits for the unit */ +#define SEEKING us9 /* seek cmd underway */ /* 4 dummy UNIBUS registers followed by... */ #define RP_IOFF 4 @@ -114,7 +115,9 @@ static BITFIELD rp_ds_bits[] = { BIT(RDY), ENDBITS }; -#define RPDS_DKER(x) ((x) & (RPDS_HNF | RPDS_INC) ? RPER_DRE : 0) +#define RPDS_REAL 0017400 /* bits stored */ +#define RPDS_DKER (RPDS_HNF | RPDS_INC) /* drive error */ +#define RPER_DKER(x) ((x) & RPDS_DKER ? RPER_DRE : 0)/* DRE for RPER */ /* RPER 776712, error register, read-only */ static BITFIELD rp_er_bits[] = { @@ -269,11 +272,11 @@ static BITFIELD rp_suca_bits[] = { /* Maintenance Write Lockout Address (LOA) (the switches on the maint. panel) */ static const char* offon[] = { "OFF", "ON" }; static BITFIELD rp_wloa_bits[] = { -#define RPWLOA_IMPL 01777 -#define RPWLOA_CYL2 0377 /* cyls locked (x2) */ +#define RPWLOA_IMPL 03777 +#define RPWLOA_CYL2 0377 /* cyls locked (x2+1) */ BITFFMT(CYL2,8,%u), #define RPWLOA_V_DRV 8 -#define RPWLOA_M_DRV 3 +#define RPWLOA_M_DRV 7 #define RPWLOA_DRV (RPWLOA_M_DRV << RPWLOA_V_DRV) /* drive(s) locked */ BITFFMT(DRV,3,%u), #define GET_WLOACYL(x) ((((x) & RPWLOA_CYL2) << 1) | 1) @@ -343,7 +346,7 @@ static t_stat rr_wr (int32 data, int32 PA, int32 access); static int32 rr_inta (void); static t_stat rr_svc (UNIT *uptr); static t_stat rr_reset (DEVICE *dptr); -static void rr_go (void); +static void rr_go (int32 func); static void rr_set_done (int32 error); static void rr_clr_done (void); static t_stat rr_boot (int32 unitno, DEVICE *dptr); @@ -358,6 +361,7 @@ static const char *rr_description (DEVICE *dptr); /* RP11 data structures + rr_dib RR device context for PDP-11 rr_reg RR register list rr_unit RR unit list rr_mod RR modifier list @@ -372,14 +376,14 @@ static DIB rr_dib = { }; static REG rr_reg[] = { - /* registers */ + /* registers: CSR first, then in the bus address order */ { ORDATADF(RPCS, rpcs, 16, "control/status", rp_cs_bits) }, - { ORDATADF(RPCA, rpca, 16, "cylinder address", rp_ca_bits) }, - { ORDATADF(RPDA, rpda, 16, "disk address", rp_da_bits) }, - { ORDATADF(RPBA, rpba, 16, "memory address", rp_ba_bits) }, - { ORDATADF(RPWC, rpwc, 16, "word count", rp_wc_bits) }, { ORDATADF(RPDS, rpds, 16, "drive status", rp_ds_bits) }, { ORDATADF(RPER, rper, 16, "error status", rp_er_bits) }, + { ORDATADF(RPWC, rpwc, 16, "word count", rp_wc_bits) }, + { ORDATADF(RPBA, rpba, 16, "memory address", rp_ba_bits) }, + { ORDATADF(RPCA, rpca, 16, "cylinder address", rp_ca_bits) }, + { ORDATADF(RPDA, rpda, 16, "disk address", rp_da_bits) }, { ORDATADF(SUCA, suca, 16, "current cylinder", rp_suca_bits) }, { ORDATADF(WLOA, wloa, 16, "write lockout address", rp_wloa_bits) }, @@ -508,13 +512,15 @@ Some operating systems want you to specify the latter range (RSTS/E), but some just want to know where the CSR is located, so they auto-calculate the range. The original RP11 had the following differences: it responded to the address -range 17776710 - 17776746 (3 buffer registers RPB1-RPB3 followed RPM3, then 3 -unused 42-46). RPCA was both the cylinder address in the lower 8 bits <00:07> -(read-write), and the selected unit current cylinder address (a la SUCA in -RP11-C) in the higher 8 bits <08:15> (read-only). The RP11 only supported the -RP02 disk drives, and so it only required 8 bits for cylinder addresses. The -RP03 bit in RPDS was always 0. But programmatically it was compatible with the --C revision (except for the separate SUCA, which was not used in most software). +range 17776710 - 17776746: RPM3 was followed by 3 buffer registers RPB1-RPB3, +then 3 locations (42-46) were unused. RPCA was both the cylinder address in the +lower 8 bits <00:07> (read-write), and the selected unit current cylinder address +(a la SUCA in RP11-C) in the higher 8 bits <08:15> (read-only). Since only the +RP02 disk drives were supported, it only required 8 bits for cylinder addresses. +There was no separate SUCA register (the location was occupied by RPB1). The +RP03 bit in RPDS was always 0. But programmatically it was mostly compatible +with the -C revision (except for the SU current cylinder address location, which +was not used by and/or important to the major part of software, anyways). RP11-E was just a newer version of RP11-C and supported both RP02 and RP03 disk drives on the same controller. @@ -539,18 +545,20 @@ static t_stat rr_rd (int32 *data, int32 PA, int32 access) if (GET_DTYPE(uptr->flags)) rpds |= RPDS_RP03; if (uptr->flags & UNIT_ATT) { /* attached? */ + rpds |= uptr->STATUS & RPDS_REAL; if (uptr->flags & UNIT_WPRT) /* write locked? */ rpds |= RPDS_WLK; if (uptr->SEEKING) /* still seeking? */ rpds |= RPDS_SEEK; - else if (!sim_is_active(uptr)) /* idle? */ - rpds |= RPDS_RDY; - } + else if (!uptr->FUNC && !(rpds & (RPDS_INC | RPDS_UNSAFE))) + rpds |= RPDS_RDY; /* ready! */ + } else + rpds |= uptr->STATUS & (RPDS_DKER | RPDS_UNSAFE); } /* RPER */ rper &= RPER_REAL; - rper |= RPDS_DKER(rpds); + rper |= RPER_DKER(rpds); /* RPCS */ rpcs &= RPCS_REAL; @@ -573,7 +581,7 @@ static t_stat rr_rd (int32 *data, int32 PA, int32 access) case 6: /* RPDA */ rpda &= RPDA_RW; - rpda |= (rand() % RP_NUMSC) << RPDA_V_SOT; /* a random sect */ + rpda |= (rand() % RP_NUMSC) << RPDA_V_SOT; /* inject a random sect */ *data = rpda; break; @@ -596,8 +604,10 @@ static t_stat rr_wr (int32 data, int32 PA, int32 access) { /* offset by base then decode <4:1> */ int32 rn = (((PA - rr_dib.ba) >> 1) & 017) - RP_IOFF; - int32 n, old_val = rn < 0 ? 0 : *rr_regs[rn].valp; + int32 n, func, oval = rn < 0 ? 0 : *rr_regs[rn].valp; + if (access == WRITEB && 2 <= rn && rn <= 6) + data = RR_DATOB(oval, data); switch (rn) { case 0: /* RPDS */ if (access != WRITEB || !(PA & 1)) { @@ -614,55 +624,48 @@ static t_stat rr_wr (int32 data, int32 PA, int32 access) break; case 2: /* RPCS */ - if (access == WRITEB) - data = RR_DATOB(rpcs, data); - if (!(data & (RPCS_AIE | CSR_IE))) { /* int disable? */ - sim_debug(RRDEB_INT, &rr_dev, "rr_wr(CSR:CLR_INT)\n"); - CLR_INT(RR); /* clr int request */ - } else if (((data & CSR_IE) - && (rpcs & (CSR_DONE | CSR_IE)) == CSR_DONE) || - ((data & RPCS_AIE) - && !(rpcs & RPCS_AIE) && (rpds & RPDS_ATTN))) { + if (((data & CSR_IE) + && (rpcs & (CSR_DONE | CSR_IE)) == CSR_DONE) || + ((data & RPCS_AIE) + && !(rpcs & RPCS_AIE) && (rpds & RPDS_ATTN))) { sim_debug(RRDEB_INT, &rr_dev, "rr_wr(CSR:SET_INT)\n"); SET_INT(RR); /* set int request */ + } else if (rpcs & (CSR_IE | RPCS_AIE)) { + sim_debug(RRDEB_INT, &rr_dev, "rr_wr(CSR:CLR_INT)\n"); + CLR_INT(RR); /* clr int request */ } rpcs &= ~RPCS_RW; rpcs |= data & RPCS_RW; - n = GET_DRIVE(rpcs); - if (n != GET_DRIVE(old_val)) { + n = GET_DRIVE(rpcs); /* get drive no */ + if (n != GET_DRIVE(oval)) { UNIT* uptr = rr_dev.units + n; /* new selected unit */ suca = uptr->CYL; n = 1; } else n = 0; /* same old */ - if (!(rpcs & CSR_DONE)) { /* not ready? */ - if ((data & CSR_GO) || n) /* GO or de-selected? */ - rper |= RPER_PGE; - } else if (data & CSR_GO) /* new function? */ - rr_go(); + func = GET_FUNC(rpcs); /* get function */ + if (((rpcs & CSR_DONE) || func == RPCS_RESET) /* ready or reset? */ + && (data & CSR_GO)) { /* ...and GO? */ + rr_go(func); /* new function! */ + } else if (!(rpcs & CSR_DONE) /* not ready? */ + && ((data & CSR_GO) || n)) { /* ...and: GO or desel? */ + rper |= RPER_PGE; /* flag error */ + } break; case 3: /* RPWC */ - if (access == WRITEB) - data = RR_DATOB(rpwc, data); rpwc = data; break; case 4: /* RPBA */ - if (access == WRITEB) - data = RR_DATOB(rpba, data); rpba = data & RPBA_IMP; break; case 5: /* RPCA */ - if (access == WRITEB) - data = RR_DATOB(rpca, data); rpca = data & RPCA_IMP; break; case 6: /* RPDA */ - if (access == WRITEB) - data = RR_DATOB(rpda, data); rpda &= ~RPDA_RW; rpda |= data & RPDA_RW; break; @@ -675,21 +678,20 @@ static t_stat rr_wr (int32 data, int32 PA, int32 access) } sim_debug(RRDEB_RWR, &rr_dev, ">>RR write: %s=%#o\n", rr_regs[rn].name, data); /* note that this is post-op; so e.g. it won't ever show the GO bit as 1 */ - sim_debug_bits(RRDEB_RWR, &rr_dev, rr_regs[rn].bits, old_val, *rr_regs[rn].valp, 1); + sim_debug_bits(RRDEB_RWR, &rr_dev, rr_regs[rn].bits, oval, *rr_regs[rn].valp, 1); return SCPE_OK; } /* Initiate new function */ -static void rr_go (void) +static void rr_go (int32 func) { - int32 i, cyl, head, sect, func, type; + int32 i, type, cyl, head; t_bool rd, wr; UNIT* uptr; - assert(rpcs & CSR_DONE); + assert(func == GET_FUNC(rpcs)); - func = GET_FUNC(rpcs); /* get function */ if (func == RPCS_RESET) { /* control reset? */ rpds = 0; rper = 0; @@ -699,49 +701,60 @@ static void rr_go (void) rpca = 0; rpda = 0; suca = rr_dev.units[0].CYL; + for (i = 0; i < RP_NUMDR; ++i) { + uptr = rr_dev.units + i; + sim_cancel(uptr); + uptr->SEEKING = 0; + uptr->STATUS = 0; + uptr->FUNC = 0; + } sim_debug(RRDEB_INT, &rr_dev, "rr_go(RESET:CLR_INT)\n"); CLR_INT(RR); /* clr int request */ return; } + assert(rpcs & CSR_DONE); + rr_clr_done(); /* clear done */ rper = 0; /* clear errors */ rpcs &= ~(CSR_ERR | RPCS_HERR); /* clear summary */ i = GET_DRIVE(rpcs); /* get drive no */ uptr = rr_dev.units + i; /* selected unit */ + assert(uptr->SEEKING || !uptr->FUNC); /* SEEK underway or idle */ + uptr->STATUS &= ~(RPDS_DKER | RPDS_WLK); /* clear drive errors */ if (!(uptr->flags & UNIT_ATT)) { /* not attached? */ - rr_set_done(RPER_PGE); + rr_set_done(RPER_PGE); /* unit offline */ return; } + if (uptr->STATUS & RPDS_UNSAFE) { /* file unsafe? */ + rr_set_done(RPER_FUV); /* unsafe violation */ + return; + } + + assert(!uptr->FUNC || uptr->FUNC == RPCS_HOME || uptr->FUNC == RPCS_SEEK); + if ((uptr->FUNC == RPCS_HOME && func != RPCS_HOME) || + (uptr->FUNC == RPCS_SEEK && func == RPCS_SEEK)) { + rr_set_done(RPER_PGE); /* no can't do */ + return; + } + /* RP11 allows to stack up an I/O command on top of an ongoing SEEK. The + * I/O gets performed once the SEEK has completed (with updated DA from the + * I/O command). XXDP checks that. + * RPDS_SEEK in the unit's STATUS means that drive is performing a SEEK as + * an initiation for an I/O (which can be a part of the topped SEEK). OTOH, + * SEEKING denotes that either a HOME/SEEK command is in progress (per FUNC) + * or SEEK was in progress before the stacked I/O command (stored in FUNC). */ + assert(!(uptr->STATUS & RPDS_SEEK)); - i = 0; /* errors detected */ rd = func == RPCS_READ || func == RPCS_RD_NOSEEK || func == RPCS_WCHK; wr = func == RPCS_WRITE || func == RPCS_WR_NOSEEK; - if (wr && (uptr->flags & UNIT_WPRT)) /* write and locked? */ - i |= RPER_WPV; - if (uptr->SEEKING || sim_is_active(uptr)) /* still busy? */ - i |= RPER_PGE; - if (rpcs & RPCS_HDR) { /* format and ... */ - if (!(rpcs & RPCS_MODE)) /* ... not 18b? */ - i |= RPER_MODE; - else if (!(rd | wr) || /* ... or not R/W? or ... */ - (rd && -((int16) rpwc) != 3) || /* rd hdr: wc m.b. 3 */ - (wr && -((int16) rpwc) % 3)) { /* wr hdr: wc m.b. mult of 3 */ - i |= RPER_PGE; - } - } else if (rd | wr) { /* regular R/W and ... */ - if (rpcs & RPCS_MODE) /* ... 18b? */ - i |= RPER_MODE; -#if 0 /* per doc, rpwc must be even; but DOS/Batch uses odd wc xfers (?!) */ - else if (rpwc & 1) /* ... or odd wc? */ - i |= RPER_PGE; -#endif + if (rd | wr) { + int32 sect = GET_SECT(rpda); /* get sect */ + if (sect >= RP_NUMSC) /* sect out of range? */ + rper |= RPER_NXS; } - sect = GET_SECT(rpda); - if (sect >= RP_NUMSC) - i |= RPER_NXS; type = GET_DTYPE(uptr->flags); /* get drive type */ if (func == RPCS_HOME) { head = 0; @@ -749,23 +762,44 @@ static void rr_go (void) } else if (func == RPCS_RD_NOSEEK || func == RPCS_WR_NOSEEK) { head = uptr->HEAD; cyl = uptr->CYL; + assert(uptr->CYL < drv_tab[type].cyl && uptr->HEAD < RP_NUMSF); } else { head = GET_TRACK(rpda); cyl = rpca; - if (head >= RP_NUMSF) - i |= RPER_NXT; - if (cyl >= drv_tab[type].cyl) - i |= RPER_NXC; + if (head >= RP_NUMSF) /* bad head? */ + rper |= RPER_NXT; + if (cyl >= drv_tab[type].cyl) /* bad cyl? */ + rper |= RPER_NXC; } - if ((wloa & RPWLOA_ON) && wr - && !(i & (RPER_WPV | RPER_NXC | RPER_NXT | RPER_NXS))) { - if (GET_DRIVE(rpcs) <= GET_WLOADRV(wloa)) /* unit protected? */ - i |= RPER_WPV; - else if (cyl <= GET_WLOACYL(wloa)) /* cyl protected? */ - i |= RPER_WPV; + + if (wr) { + if ((wloa & RPWLOA_ON) && !rper/*valid DA*/ && + (i <= GET_WLOADRV(wloa) || cyl <= GET_WLOACYL(wloa))) { + uptr->STATUS |= RPDS_WLK; /* DA write-locked */ + rper |= RPER_WPV; + } else if (uptr->flags & UNIT_WPRT) /* write and locked? */ + rper |= RPER_WPV; } - if (i) { - rr_set_done(i); /* set done */ + + if (rpcs & RPCS_HDR) { /* format and ... */ + if (!(rpcs & RPCS_MODE)) /* ... not 18b? */ + rper |= RPER_MODE; + else if (!(rd | wr) || /* ... or not R/W? or ... */ + (rd && -((int16) rpwc) != 3) || /* rd hdr: wc m.b. 3 */ + (wr && -((int16) rpwc) % 3)) { /* wr hdr: wc m.b. mult of 3 */ + rper |= RPER_PGE; + } + } else if (rd | wr) { /* regular R/W and ... */ + if (rpcs & RPCS_MODE) /* ... 18b? */ + rper |= RPER_MODE; +#if 0 /* per doc, rpwc must be even; but DOS/Batch uses odd wc xfers (?!) */ + else if (rpwc & 1) /* ... or odd wc? */ + rper |= RPER_PGE; +#endif + } + + if (rper) { /* any errors? */ + rr_set_done(0); /* set done (w/errors) */ return; } @@ -780,44 +814,63 @@ static void rr_go (void) i = drv_tab[type].seek_ave; else i = drv_tab[type].seek_max; - if (func == RPCS_SEEK || func == RPCS_HOME) { /* seek? */ - uptr->SEEKING = 1; /* start seeking */ + if (func == RPCS_HOME || func == RPCS_SEEK) { /* seek? */ + uptr->SEEKING = 1; /* drive is seeking */ rr_set_done(0); /* set done */ - sim_activate(uptr, i / 10); /* schedule */ - } else - sim_activate(uptr, (i + RP_ROT_12) / 10); /* I/O takes longer */ + /* XXDP ZRPF-B test 0 fails because of the data race on INTFLG cleared + * _after_ initiating the SEEK. This code interrupts right away (as so + * could the device) causing INTFLG set by ISR to 1, to be lost. Hence, + * the test always sees INTFLG as 0, and cannot confirm the interrupt. + * INTFLG should have been cleared prior to SEEK. Fixed in ZRPB-E. */ + } else { + if (cyl != uptr->CYL || head != uptr->HEAD) { + assert(func != RPCS_RD_NOSEEK && func != RPCS_WR_NOSEEK); + uptr->STATUS |= RPDS_SEEK; /* drive is seeking */ + } + i += RP_ROT_12; /* I/O takes longer */ + /* XXDP ZRPB-E / ZRPF-B have two data race conditions in test 5 (data + * reliability), which in ZRPB-E can be worked around with the following + * multiplier for all 15 steady patterns, but it does not help eliminate + * the second race in the last (random) pattern test, despite showing no + * actual data discrepancies. */ +#if 0 + if (func == RPCS_READ) + i *= 64; /* to use w/XXDP tests */ +#endif + } + sim_activate(uptr, i); /* schedule */ - uptr->CYL = cyl; /* put on cylinder */ + uptr->FUNC = func; /* save new func */ uptr->HEAD = head; /* save head too */ - uptr->FUNC = func; /* save func */ + uptr->CYL = cyl; /* put on cylinder */ return; } /* Complete seek */ -static t_stat rr_seek_done (UNIT *uptr, t_bool cancel) +static void rr_seek_done (UNIT *uptr, t_bool cancel) { - int32 n; + int32 n = (int32)(uptr - rr_dev.units); /* get unit number */ - assert(uptr->SEEKING); - assert(uptr->FUNC == RPCS_SEEK || uptr->FUNC == RPCS_HOME); - n = (int32)(uptr - rr_dev.units); /* get drv number */ if (n == GET_DRIVE(rpcs)) - suca = cancel ? 0 : uptr->CYL; /* update cyl shown */ - uptr->SEEKING = 0; /* set seek done */ - assert((1 << n) | RPDS_ATTN); - rpds |= 1 << n; /* set attention */ - if (rpcs & RPCS_AIE) { /* att ints enabled? */ - sim_debug(RRDEB_INT, &rr_dev, "rr_seek_done(SET_INT)\n"); - SET_INT(RR); + suca = cancel ? 0 : uptr->CYL; /* update cyl shown */ + if (uptr->SEEKING) { /* was seek cmd pending? */ + assert((1 << n) & RPDS_ATTN); + rpds |= 1 << n; /* set attention */ + if (rpcs & RPCS_AIE) { /* att ints enabled? */ + sim_debug(RRDEB_INT, &rr_dev, "rr_seek_done(SET_INT)\n"); + SET_INT(RR); + } + uptr->SEEKING = 0; /* seek completed */ } - return SCPE_OK; + uptr->STATUS &= ~RPDS_SEEK; /* no longer seeking */ + return; } /* Service a unit If seek in progress, complete seek command - Else complete data transfer command + Then if I/O pending, complete data transfer command The unit control block contains the function and disk address for the current command. @@ -828,69 +881,96 @@ static t_stat rr_seek_done (UNIT *uptr, t_bool cancel) static t_stat rr_svc (UNIT *uptr) { - int32 func, cyl, head, sect, da, err, wc, n; + int32 func = uptr->FUNC, cyl, head, sect, da, wc, n; t_seccnt todo, done; t_stat ioerr; uint32 ma; - t_bool rd; + t_bool wr; + + assert(func); + uptr->FUNC = 0; /* idle */ - if (uptr->SEEKING) /* seek? */ - return rr_seek_done(uptr, 0); + rr_seek_done(uptr, 0); /* complete seek, if any */ + assert(!uptr->SEEKING && !(uptr->STATUS & RPDS_SEEK)); + if (func == RPCS_HOME || func == RPCS_SEEK) + return SCPE_OK; /* all done */ - func = uptr->FUNC; - assert(func && ~(rpcs & CSR_DONE)); + assert(~(rpcs & CSR_DONE)); - if (!(uptr->flags & UNIT_ATT)) { /* attached? */ - rr_set_done(RPER_PGE); + if (!(uptr->flags & UNIT_ATT)) { /* not attached? */ + rr_set_done(RPER_PGE); /* unit offline */ return SCPE_UNATT; } + if (uptr->STATUS & RPDS_UNSAFE) { /* file unsafe? */ + rr_set_done(RPER_FUV); /* unsafe violation */ + return SCPE_OK; + } + + wr = func == RPCS_WRITE || func == RPCS_WR_NOSEEK; + sect = GET_SECT(rpda); /* get sect */ - if (sect >= RP_NUMSC) { /* bad sector? */ - rr_set_done(RPER_NXS); + if (sect >= RP_NUMSC) /* bad sector? */ + rper |= RPER_NXS; + + if (wr && (uptr->flags & UNIT_WPRT)) /* write and locked? */ + rper |= RPER_WPV; + + if (rper) { /* control in error? */ + rr_set_done(0); return SCPE_OK; } + /* rper == 0: drive remained selected */ + assert((int32)(uptr - rr_dev.units) == GET_DRIVE(rpcs)); - rd = func == RPCS_READ || func == RPCS_RD_NOSEEK || func == RPCS_WCHK; wc = 0200000 - rpwc; /* get wd cnt */ - cyl = uptr->CYL; + assert(wc <= RP_MAXFR); head = uptr->HEAD; + cyl = uptr->CYL; n = GET_DTYPE(uptr->flags); /* get drive type */ assert(cyl < drv_tab[n].cyl && head < RP_NUMSF); da = GET_DA(cyl, head, sect); /* form full disk addr */ assert(da < drv_tab[n].size); n = drv_tab[n].size - da; /* sectors available */ - err = 0; /* errors detected */ if (rpcs & RPCS_HDR) { /* header ops? */ - if (!(rpcs & RPCS_MODE)) - err |= RPER_MODE; /* must be in 18b mode */ - else if ((rd && wc != 3) || (!rd && wc % 3)) /* DEC-11-HRPCA-C-D 3.8 */ - err |= RPER_PGE; - else if (rd) /* a typo in doc??? */ - n = 3; /* can only read 3 wds */ - else + if (!(rpcs & RPCS_MODE)) /* yes: 18b mode? */ + rper |= RPER_MODE; /* must be in 18b mode */ + else if ((!wr && wc != 3) || (wr && wc % 3)) /* DEC-11-HRPCA-C-D 3.8 */ + rper |= RPER_PGE; + else if (wr) /* a typo in doc??? */ n *= 3; /* 3 wds per sector */ + else + n = 3; /* can only read 3 wds */ } else { /* no: regular R/W */ + /* RP11 can actually handle the PDP-10/-15 (18b) mode on PDP-11 using 3 + * words per transfer, combined as two 18b words in a disk sector (for + * the bit assignments in the triplet, see rr_svc() for format reading). + * However, a sector written in this mode is marked as such and cannot be + * read back in the PDP-11 (16b) mode (but only in the 18b mode). Since + * the disk containers do not have sector format information, this mode + * cannot be supported (and for all intents and purposes it's not needed + * for any real PDP-11 I/O). XXDP has some test for this, though. */ if (rpcs & RPCS_MODE) - err |= RPER_MODE; /* must be in PDP-11 mode */ + rper |= RPER_MODE; /* must be in PDP-11 mode */ #if 0 /* per doc, wc must be even; but DOS/Batch uses odd wc xfers (?!) */ else if (wc & 1) /* must be even */ - err |= RPER_PGE; + rper |= RPER_PGE; #endif else - n *= RP_NUMWD; /* can do this many */ + n = RP_SIZE(n); /* can do this many wrds */ } - if (err) { - rr_set_done(err); + + if (rper) { /* any new errors? */ + rr_set_done(0); /* set done (w/errors) */ return SCPE_OK; } if (wc > n) - wc = n; + wc = n; /* trim word count */ assert(wc); /* A note on error handling: * RP11 processes data words between drive and memory absolutely sequentially * (not by the sector like the SIMH API provides). Therefore, controller - * errors should be asserted to follow that scenario. + * errors should be asserted to follow this scenario. * * 1. When reading from disk, an I/O error must be deferred until all words * (read from the disk so far) have been verified not to cause NXM. If any @@ -899,7 +979,7 @@ static t_stat rr_svc (UNIT *uptr) * and there, and would not have encountered the (later) I/O condition). * * 2. When writing, I/O errors take precedence, provided that words keep - * passing the address check in extraction from memory. But no NXM should + * passing the address check on extraction from memory. But no NXM should * be reported for any words that reside after the completed I/O boundary in * case of a short write to disk. * @@ -911,7 +991,7 @@ static t_stat rr_svc (UNIT *uptr) ma = ((rpcs & RPCS_MEX) << (16 - RPCS_V_MEX)) | rpba; /* get mem addr */ - if (rd) { /* read */ + if (!wr) { /* read: */ if (rpcs & RPCS_HDR) { /* format? */ /* Sector header is loaded in the 36-bit Buffer Register(BR): 17 0-bits, 9-bit cyl, 5-bit track, a spare bit, 4-bit sect */ @@ -920,11 +1000,11 @@ static t_stat rr_svc (UNIT *uptr) rpxb[2] = sect; /* BR<03:00> */ ioerr = 0; done = 1; /* 1 sector done */ - } else { /* normal read */ + } else { /* normal read: */ DEVICE* dptr = find_dev_from_unit(uptr); todo = (wc + (RP_NUMWD - 1)) / RP_NUMWD; /* sectors to read */ ioerr = sim_disk_rdsect(uptr, da, (uint8*) rpxb, &done, todo); - n = done * RP_NUMWD; /* words read */ + n = RP_SIZE(done); /* words read */ sim_disk_data_trace(uptr, (uint8*) rpxb, da, n * sizeof(*rpxb), "rr_read", RRDEB_DAT & (dptr->dctrl | uptr->dctrl), RRDEB_OPS); if (done >= todo) @@ -933,7 +1013,7 @@ static t_stat rr_svc (UNIT *uptr) wc = n; /* short, adj wc */ else { todo -= done; /* to clear ... */ - todo *= RP_NUMWD * sizeof(*rpxb); /* ... bytes */ + todo *= RP_SIZE(sizeof(*rpxb)); /* ... bytes */ memset(rpxb + n, 0, todo); } } @@ -942,26 +1022,27 @@ static t_stat rr_svc (UNIT *uptr) for (n = 0; n < wc; ++n) { /* loop thru buf */ RPCONTR data; if (MAP_RDW(a, 1, &data)) { /* mem wd */ - err |= RPER_NXM; /* NXM? set flg */ + rper |= RPER_NXM; /* NXM? set flg */ break; } a += 2; - if (ioerr) + if (rper | ioerr) continue; if (data != rpxb[n]) /* match to disk? */ - err |= RPER_WCE; /* no, err */ + rper |= RPER_WCE; /* no, err */ } n %= wc; } else if ((n = MAP_WRW(ma, wc, rpxb))) { /* store buf */ - err |= RPER_NXM; /* NXM? set flag */ + rper |= RPER_NXM; /* NXM? set flag */ wc -= n; /* adj wd cnt */ } if (!n && ioerr) { /* all wrds ok but I/O? */ - err |= RPER_FMTE; /* report as FMTE */ + rper |= RPER_FMTE; /* report as FMTE */ + uptr->STATUS |= RPDS_HNF; /* sector not found */ if (func == RPCS_WCHK) - err |= RPER_WCE; + rper |= RPER_WCE; /* write-check err, too */ } - } else { /* write */ + } else { /* write: */ if ((n = MAP_RDW(ma, wc, rpxb))) /* get buf */ wc -= n; /* adj wd cnt */ if (wc && !(rpcs & RPCS_HDR)) { /* regular write? */ @@ -972,69 +1053,81 @@ static t_stat rr_svc (UNIT *uptr) RRDEB_DAT & (dptr->dctrl | uptr->dctrl), RRDEB_OPS); todo = m / RP_NUMWD; /* sectors to write */ ioerr = sim_disk_wrsect(uptr, da, (uint8*) rpxb, &done, todo); - if (done < todo) { - wc = done * RP_NUMWD; /* words written */ - err |= RPER_FMTE; /* report as FMTE */ + if (done < todo) { /* short write? */ + wc = RP_SIZE(done); /* words written */ + rper |= RPER_FMTE; /* report as FMTE */ + uptr->STATUS |= RPDS_HNF; /* sector not found */ if (!ioerr) - ioerr = 1; /* just in case */ - } else if (n) - err |= RPER_NXM; /* NXM? set flg */ - else + ioerr = 1; /* must be reported! */ + } else if (n) { + rper |= RPER_NXM; /* NXM? set flg */ + ioerr = 0; /* don't care */ + } else ioerr = 0; /* good stuff */ } else { ioerr = 0; /* good stuff */ done = wc / 3; if (n) - err |= RPER_NXM; /* NXM? set flg */ + rper |= RPER_NXM; /* NXM? set flg */ } } + assert(!ioerr || rper); if (wc) { /* any xfer? */ + assert(done); rpwc += wc; rpwc &= 0177777; ma += wc << 1; rpba = ma & RPBA_IMP; rpcs &= ~RPCS_MEX; rpcs |= (ma >> (16 - RPCS_V_MEX)) & RPCS_MEX; - - assert(done); - rd = func == RPCS_RD_NOSEEK || func == RPCS_WR_NOSEEK; - if (!rd || --done) { /* w/SEEK or 2+ sects? */ - da += done; /* update DA */ - n = GET_DTYPE(uptr->flags); /* drive type */ - assert(da <= drv_tab[n].size); - head = da / RP_NUMSC; /* new head (w/cyl) */ - cyl = head / RP_NUMSF; /* new cyl */ - if (cyl == drv_tab[n].cyl) { /* at the end? */ - cyl = drv_tab[n].cyl - 1; /* last cyl and ... */ - head = RP_NUMSF - 1; /* ... head keep on */ - } else - head %= RP_NUMSF; /* wrap up head */ - n = (int32)(uptr - rr_dev.units); /* get drv number */ - if (!rd) /* w/SEEK I/O? */ - uptr->HEAD = head; /* yes: select new head */ - else if (uptr->CYL != cyl || /* no: arm moved or */ - (rpwc && !(err | ioerr))) { /* boundary exceeded? */ - assert((1 << n) & RPDS_ATTN); - rpds |= 1 << n; /* set attention */ - if (rpcs & RPCS_AIE) { /* att ints enabled? */ - sim_debug(RRDEB_INT, &rr_dev, "rr_svc(SET_INT)\n"); - SET_INT(RR); /* request interrupt */ - } + if (rpwc && !(rper | ioerr)) + rper |= RPER_EOP; /* disk pack overrun */ + + da += done; /* update DA */ + n = GET_DTYPE(uptr->flags); /* drive type */ + assert(da <= drv_tab[n].size); + sect = da % RP_NUMSC; /* new sector */ + head = da / RP_NUMSC; /* new head (w/cyl) */ + todo = head / RP_NUMSF; /* new cyl (tentative) */ + if (todo == drv_tab[n].cyl) { /* at the end? */ + cyl = drv_tab[n].cyl - 1; /* keep on last cyl */ + todo = 0; /* wrap cyl for rpda */ + head = 0; /* ...and head, too */ + assert(!sect); + } else { + cyl = todo; /* new cyl */ + head %= RP_NUMSF; /* isolate head */ + } + uptr->HEAD = head; /* update head */ + if ((func == RPCS_RD_NOSEEK || func == RPCS_WR_NOSEEK) /* no SEEK I/O... */ + && (uptr->CYL != cyl /* ...and: arm moved or... */ + || (rper & RPER_EOP))) { /* ...boundary exceeded? */ + n = (int32)(uptr - rr_dev.units); /* get unit number */ + assert((1 << n) & RPDS_ATTN); + rpds |= 1 << n; /* set attention */ + if (rpcs & RPCS_AIE) { /* att ints enabled? */ + sim_debug(RRDEB_INT, &rr_dev, "rr_svc(SET_INT)\n"); + SET_INT(RR); /* request interrupt */ } - uptr->CYL = cyl; /* update new cyl */ - if (n == GET_DRIVE(rpcs)) - suca = uptr->CYL; /* let it out */ } + uptr->CYL = cyl; /* update cyl */ + rpda = (head << RPDA_V_TRACK) | sect; /* updated head / sect */ + rpca = todo; /* wrapped up cyl */ + suca = cyl; /* updated real cyl */ } else - assert(err); + assert(rper); - if (rpwc && !(err | ioerr)) - err |= RPER_EOP; /* disk pack overrun */ - rr_set_done(err); + rr_set_done(0); /* all done here */ if (ioerr) { /* I/O error? */ - sim_perror("RR I/O error"); + const char* name = uptr->uname; + const char* file = uptr->filename; + const char* errstr = errno ? strerror(errno) : ""; + sim_printf("RR%u %s [%s:%s] RPER=%06o I/O error (%s)%s%s", (int) GET_DRIVE(rpcs), + GET_DTYPE(uptr->flags) ? RP_RP03 : RP_RP02, name ? name : "???", + file ? file : "", (int) rper, sim_error_text(ioerr), + errstr && *errstr ? ": " : "", errstr ? errstr : ""); return SCPE_IOERR; } return SCPE_OK; @@ -1043,12 +1136,13 @@ static t_stat rr_svc (UNIT *uptr) /* Interrupt state change routines rr_clr_done clear done - rr_set_done set done and possibly errors + rr_set_done set done (and errors) rr_inta interrupt acknowledge */ static void rr_clr_done (void) { + assert(rpcs & CSR_DONE); rpcs &= ~CSR_DONE; /* clear done */ if ((rpcs & CSR_IE) && (!(rpcs & RPCS_AIE) || !(rpds & RPDS_ATTN))) { sim_debug(RRDEB_INT, &rr_dev, "rr_clr_done(CLR_INT)\n"); @@ -1057,9 +1151,10 @@ static void rr_clr_done (void) return; } -static void rr_set_done (int32 error) +static void rr_set_done (int32 err) { - rper |= error; + assert(~(rpcs & CSR_DONE)); + rper |= err; rpcs |= CSR_DONE; /* set done */ if (rpcs & CSR_IE) { /* int enable? */ sim_debug(RRDEB_INT, &rr_dev, "rr_set_done(SET_INT)\n"); @@ -1083,7 +1178,7 @@ static t_stat rr_reset (DEVICE *dptr) { int32 i; - /* some sanity check first */ + /* compile-time sanity check first */ assert(sizeof(rr_regs)/sizeof(rr_regs[0]) == RP_IOLN/2 - RP_IOFF); /* clear everything now */ @@ -1095,19 +1190,20 @@ static t_stat rr_reset (DEVICE *dptr) rpca = 0; rpda = 0; suca = 0; - assert(dptr == &rr_dev); - sim_debug(RRDEB_INT, &rr_dev, "rr_reset(CLR_INT)\n"); - CLR_INT(RR); for (i = 0; i < RP_NUMDR; ++i) { UNIT* uptr = rr_dev.units + i; sim_cancel(uptr); - uptr->CYL = 0; + uptr->SEEKING = 0; + uptr->STATUS = 0; uptr->FUNC = 0; uptr->HEAD = 0; - uptr->SEEKING = 0; + uptr->CYL = 0; } + assert(dptr == &rr_dev); + sim_debug(RRDEB_INT, dptr, "rr_reset(CLR_INT)\n"); + CLR_INT(RR); if (rpxb == NULL) - rpxb = (RPCONTR*) calloc(RP_MAXFR, sizeof (*rpxb)); + rpxb = (RPCONTR*) calloc(RP_MAXFR, sizeof (*rpxb)); if (rpxb == NULL) return SCPE_MEM; return auto_config(NULL, 0); @@ -1119,22 +1215,34 @@ static t_stat rr_attach (UNIT *uptr, CONST char *cptr) { static const char* rr_types[] = { RP_RP03, RP_RP02, NULL }; int32 type = GET_DTYPE(uptr->flags); - return sim_disk_attach_ex2(uptr, cptr, - RP_NUMWD * sizeof(*rpxb), sizeof (*rpxb), - TRUE, 0, drv_tab[type].name, - 0, 0, rr_types, 0); + t_stat err = sim_disk_attach_ex2(uptr, cptr, + RP_SIZE(sizeof(*rpxb)), sizeof(*rpxb), + TRUE, 0, drv_tab[type].name, + 0, 0, rr_types, 0); + if (err == SCPE_OK && !(uptr->STATUS & RPDS_DKER)) + uptr->STATUS &= ~RPDS_UNSAFE; + return err; } static t_stat rr_detach (UNIT *uptr) { - if (uptr->SEEKING) - rr_seek_done(uptr, 1/*cancel*/); - else if (sim_is_active(uptr)) - rr_set_done(RPER_TE); - sim_cancel(uptr); - uptr->CYL = 0; - uptr->FUNC = 0; + int32 func = uptr->FUNC; + rr_seek_done(uptr, 1/*cancel*/); + if (func) { + uptr->FUNC = 0; /* idle now */ + sim_cancel(uptr); + if (func == RPCS_SEEK) + uptr->STATUS |= RPDS_INC; /* seek incomplete */ + else if (func != RPCS_HOME) { + uptr->STATUS |= RPDS_HNF; /* sector not found */ + rr_set_done(RPER_TE); + } + } + uptr->STATUS |= RPDS_UNSAFE; /* must reset before use */ + assert(!sim_is_active(uptr)); + assert(!uptr->SEEKING); uptr->HEAD = 0; + uptr->CYL = 0; return sim_disk_detach(uptr); } @@ -1164,8 +1272,13 @@ static t_stat rr_set_wloa (UNIT *uptr, int32 val, CONST char *cptr, void *desc) { DEVICE* dptr = find_dev_from_unit(uptr); if (!cptr || !*cptr) - return SCPE_ARG; - if (strcasecmp(cptr, "OFF") == 0) { + return SCPE_2FARG; + if (strncasecmp(cptr, "OFF", 3) == 0) { + cptr += 3; + if (*cptr == ';') + return SCPE_2MARG; + if (*cptr) + return SCPE_ARG; wloa &= ~RPWLOA_ON; return SCPE_OK; } @@ -1175,14 +1288,16 @@ static t_stat rr_set_wloa (UNIT *uptr, int32 val, CONST char *cptr, void *desc) if (*cptr == ';') { char* end; long val; + if (!*++cptr) + return SCPE_MISVAL; errno = 0; - val = strtol(++cptr, &end, 0); + val = strtol(cptr, &end, 0); if (errno || !end || *end || end == cptr || (val & ~RPWLOA_IMPL)) return SCPE_ARG; wloa &= ~RPWLOA_IMPL; wloa |= val; - } else if (!*cptr) - return SCPE_2FARG; + } else if (*cptr) + return SCPE_ARG; wloa |= RPWLOA_ON; return SCPE_OK; } @@ -1200,21 +1315,21 @@ static const uint16 rr_boot_rom[] = { * R0 = UNIT NUMBER * * R1 = CONTROLLER CSR * * R2, R3 = TEMPORARIES * - * R4 = ALWAYS POINTS TO PROM BASE + 20 (HELPS LOCATE BOOTED DEVICE DESIGNATION) * + * R4 = ALWAYS POINTS TO PROM BASE + 20 (HELPS LOCATE THE BOOTED DEVICE DESIGNATION) * * R5 = LAST COMMAND DATA (E.G. LOAD ADDR, EXAM DATA; OTHERWISE, JUNK) * - * R6(SP) = PC OF THE COMMAND START (IN M9312 POINTS TO WHERE THE BOOT COMMAND ORIGINATED FROM) */ + * R6(SP) = PC OF THE COMMAND START (CONTAINS THE ADDRESS WHERE THE BOOT COMMAND ORIGINATED FROM) */ /* .TITLE RP11 BOOT M9312 STYLE - TONY LAWRENCE (C) 2023 */ /* .ASECT */ /* 002000 .=2000 */ -/* 002000 */ 0042120, /* START: .WORD "PD ; "DP" (DEVICE DESIGNATION) */ +/* 002000 */ 0042120, /* START: .WORD "PD ; "DP" (RP DEVICE DESIGNATION) */ /* 002002 */ 0012706, BOOT_ENTRY, /* BOOT: MOV #BOOT, SP ; ENTRY POINT PC */ /* 002006 */ 0112700, 0000000, /* MOVB #0, R0 ; UNIT NUMBER */ /* 002012 */ 0012701, 0176726, /* MOV #176726, R1 ; RPCS + 12 */ -/* 002016 */ 0012704, BOOT_START+020, /* MOV #, R4 ; BACKLINK TO ROM W/OFFSET 20 */ +/* 002016 */ 0012704, BOOT_START+020, /* MOV #, R4 ; BACKLINK TO PROM W/OFFSET 20 */ /* 002022 */ 0005041, /* CLR -(R1) ; DISK ADDRESS */ /* 002024 */ 0005041, /* CLR -(R1) ; CYLINDER ADDRESS */ /* 002026 */ 0005041, /* CLR -(R1) ; MEMORY ADDRESS */ -/* 002030 */ 0012741, 0177000, /* MOV #-512., -(R1) ; WORD COUNT */ +/* 002030 */ 0012741, 0177000, /* MOV #-512., -(R1) ; WORD CT (ALWAYS 2 FULL SECTS)*/ /* 002034 */ 0010003, /* MOV R0, R3 */ /* 002036 */ 0000303, /* SWAB R3 ; MOVE UNIT# INTO POSITION */ /* 002040 */ 0052703, 0000005, /* BIS #5, R3 ; COMBINE READ+GO FUNCTION */ @@ -1234,6 +1349,7 @@ static const uint16 rr_boot_rom[] = { static t_stat rr_boot (int32 unitno, DEVICE *dptr) { size_t i; + assert(dptr == &rr_dev); for (i = 0; i < BOOT_LEN; ++i) WrMemW(BOOT_START + (2 * i), rr_boot_rom[i]); WrMemW(BOOT_UNIT, unitno & (RP_NUMDR - 1)); @@ -1262,8 +1378,8 @@ static t_stat rr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const cha "to specify the extended range (e.g. RSTS/E), but some -- the relevant range\n" "(17776710 - 17776736), yet some just want to know where the CSR is located\n" "(17776714 by default), so they can auto-calculate the range on their own.\n\n" - "Disk drive parameters:\n\n" - " Cylinders Heads Sects/Trk Capacity Average access\n" + "Disk drive parameters (all decimal):\n\n" + " Cylinders Heads Sects/Trk Capacity Average access\n" " Total Spare Nominal Usable time, ms\n", st); for (i = 0; i < sizeof(drv_tab)/sizeof(drv_tab[0]) - 1; ++i) { uint32 spare = GET_DA(drv_tab[i].spare, RP_NUMSF, RP_NUMSC); @@ -1276,21 +1392,22 @@ static t_stat rr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const cha (drv_tab[i].seek_ave + RP_ROT_12)%10); } fputs("\n" - "The implementation does not include any maintenance registers or disk\n" - "formatting operations yet supports the Write Lockout Address (LOA)\n" - "register, which can be set with a PROTECT command:\n\n" - " sim> SET RR PROTECT=ON;0407\n\n" - "to turn the protection on (in this case, the entire units 0 and 1,\n" - "and 7x2=14 first cylinders of unit 2 will become write-locked).\n" + "The implementation does not include any maintenance registers or disk/sector\n" + "formatting operations yet supports the Write Lockout Address (LOA) register,\n" + "which can be set with a PROTECT command:\n\n" + " sim> set RR PROTECT=ON;0407\n\n" + "to turn the protection on (in this case, the entire units 0 and 1, and\n" + "7 x 2 + 1 = 15(10) first cylinders of unit 2 will become write-locked).\n" "The current setting can be obtained by examining the WLOA register in\n" - "the device:\n\n" - " sim> EXAMINE RR WLOA\n" + "the device (the sign bit not present in hardware controls the feature):\n\n" + " sim> examine RR WLOA\n" " WLOA: 100407 PROTECT=ON DRV=1 CYL2=7\n\n" "To remove the lockout:\n\n" - " sim> SET RR PROTECT=OFF\n" - " sim> EXAMINE RR WLOA\n" + " sim> set RR PROTECT=OFF\n" + " sim> examine RR WLOA\n" " WLOA: 000407 PROTECT=OFF DRV=1 CYL2=7\n\n" - "Note that it does not clear the address but turns the feature off.\n", st); + "Note that it does not clear the address but turns the feature off. Also,\n" + "the WLOA register is unaffected by the device RESET.\n", st); fprint_set_help (st, dptr); fprint_show_help(st, dptr); fprintf(st,