1457 lines
63 KiB
C
1457 lines
63 KiB
C
/* pdp11_rr.c: RP11/-C/-E/RP02/RP03 disk pack device
|
|
|
|
Copyright (c) 2022 Tony Lawrence
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a
|
|
copy of this software and associated documentation files (the "Software"),
|
|
to deal in the Software without restriction, including without limitation
|
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
and/or sell copies of the Software, and to permit persons to whom the
|
|
Software is furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
Except as contained in this notice, the name of Robert M Supnik shall not be
|
|
used in advertising or otherwise to promote the sale, use or other dealings
|
|
in this Software without prior written authorization from Robert M Supnik.
|
|
|
|
Inspired by PDP-11's RK/RL implementations by Robert Supnik.
|
|
*/
|
|
|
|
#if defined(VM_PDP11) && !defined(UC15)
|
|
|
|
#include <assert.h>
|
|
|
|
#include "pdp11_defs.h"
|
|
#include "sim_disk.h"
|
|
|
|
/* const_assert() */
|
|
|
|
#define _Cx(a, b) a ## b
|
|
#define _Ax(x) _Cx(_assert, x)
|
|
#define _Sx(n) (((n) << 1) - 1)
|
|
#define const_assert(c) do { static const char _Ax(__LINE__)[-_Sx(!(c))] \
|
|
= {!(c)}; (void) _Ax(__LINE__); } while (0)
|
|
|
|
/* Constants */
|
|
|
|
#define RPCONTR uint16
|
|
#define RPWRDSZ 16
|
|
#define MAP_RDW(a,c,b) (Map_ReadW (a, (c) << 1, b) >> 1)
|
|
#define MAP_WRW(a,c,b) (Map_WriteW(a, (c) << 1, b) >> 1)
|
|
|
|
/* RP02 parameters; RP03 doubles # of cylinders (both total and spare) */
|
|
#define RP_NUMWD 256 /* words/sector */
|
|
#define RP_NUMCY 203 /* cylinders/drive */
|
|
#define RP_SPARE 3 /* of those, spare */
|
|
#define RP_NUMSF 20 /* surfaces/cylinder */
|
|
#define RP_NUMSC 10 /* sectors/track */
|
|
#define RP_NUMTR (RP_NUMCY * RP_NUMSF) /* tracks/drive */
|
|
#define RP_NUMBL (RP_NUMTR * RP_NUMSC) /* blocks/drive */
|
|
#define RP_NUMDR 8 /* drives/controller */
|
|
#define RP_MAXFR (1 << 16) /* max transfer */
|
|
#define RP_SIZE(n) (RP_NUMWD * (n)) /* words in n blocks */
|
|
#define RP_SIZE_RP02 RP_SIZE(RP_NUMBL) /* RP02 capacity, words */
|
|
#define RP_SIZE_RP03 RP_SIZE(RP_NUMBL*2) /* RP03 capacity, words */
|
|
#define RP_ROT_12 125 /* Half rotation, 0.1ms */
|
|
|
|
/* Flags in the unit flags word */
|
|
#define UNIT_NOAUTO DKUF_NOAUTOSIZE /* autosize disabled */
|
|
#define UNIT_V_RP03 (DKUF_V_UF + 0)
|
|
#define UNIT_RP03 (1 << UNIT_V_RP03) /* RP03 vs RP02 (0) */
|
|
#define GET_DTYPE(x) (((x) & UNIT_RP03) >> UNIT_V_RP03)
|
|
|
|
/* Controller / drive types */
|
|
#define RP_RP11 "RP11-C"
|
|
#define RP_RP02 "RP02"
|
|
#define RP_RP03 "RP03"
|
|
|
|
/* Parameters in the unit descriptor */
|
|
#define CYL u3 /* current cylinder */
|
|
#define HEAD u4 /* current track */
|
|
#define FUNC us9 /* current func */
|
|
#define STATUS u5 /* RPDS bits for the unit */
|
|
#define SEEKING u6 /* seek cmd underway */
|
|
|
|
/* 4 dummy UNIBUS registers followed by... */
|
|
#define RP_IOFF 4
|
|
/* 12 real UNIBUS registers, so 16 registers total: 32 addresses */
|
|
#define RP_IOLN 040
|
|
|
|
/* RP02/RP03 particulars */
|
|
static struct drv_typ {
|
|
const char* name; /* device type name */
|
|
int32 cyl; /* cylinders */
|
|
int32 size; /* #blocks */
|
|
int32 spare; /* spare (out of cyl) */
|
|
int32 seek_1; /* one track move, 0.1ms */
|
|
int32 seek_ave; /* average seek, 0.1ms */
|
|
int32 seek_max; /* maximal seek, 0.1ms */
|
|
} drv_tab[] = {
|
|
{ RP_RP02, RP_NUMCY, RP_NUMBL, RP_SPARE, 200, 500, 800 },
|
|
{ RP_RP03, RP_NUMCY*2, RP_NUMBL*2, RP_SPARE*2, 75, 290, 550 }
|
|
};
|
|
|
|
/* RPDS 776710, selected drive status, read-only except for the attention bits */
|
|
static BITFIELD rp_ds_bits[] = {
|
|
#define RPDS_ATTN 0000377 /* attention (read/clear) */
|
|
BITF(ATTN,8),
|
|
#define RPDS_WLK 0000400 /* write locked */
|
|
BIT(WLK),
|
|
#define RPDS_UNSAFE 0001000 /* unsafe */
|
|
BIT(UNSAFE),
|
|
#define RPDS_SEEK 0002000 /* seek underway */
|
|
BIT(SEEK),
|
|
#define RPDS_INC 0004000 /* seek incomplete */
|
|
BIT(INC),
|
|
#define RPDS_HNF 0010000 /* header not found */
|
|
BIT(HNF),
|
|
#define RPDS_RP03 0020000 /* drive is RP03 */
|
|
BIT(RP03=),
|
|
#define RPDS_ONLN 0040000 /* unit online */
|
|
BIT(ONLN),
|
|
#define RPDS_RDY 0100000 /* unit ready */
|
|
BIT(RDY),
|
|
ENDBITS
|
|
};
|
|
#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[] = {
|
|
#define RPER_DRE 0000001 /* drive error (HNF|INC) */
|
|
BIT(DRE),
|
|
#define RPER_EOP 0000002 /* end of pack (overrun) */
|
|
BIT(EOP),
|
|
#define RPER_NXM 0000004 /* nx memory */
|
|
BIT(NXM),
|
|
#define RPER_WCE 0000010 /* write check error */
|
|
BIT(WCE),
|
|
#define RPER_TE 0000020 /* timing error */
|
|
BIT(TE),
|
|
#define RPER_CSE 0000040 /* serial checksum error */
|
|
BIT(CSE),
|
|
#define RPER_WPE 0000100 /* word parity error */
|
|
BIT(WPE),
|
|
#define RPER_LPE 0000200 /* longitudinal parity error */
|
|
BIT(LPE),
|
|
#define RPER_MODE 0000400 /* mode error */
|
|
BIT(MODE),
|
|
#define RPER_FMTE 0001000 /* format error */
|
|
BIT(FMTE),
|
|
#define RPER_PGE 0002000 /* programming error */
|
|
BIT(PGE),
|
|
#define RPER_NXS 0004000 /* nx sector */
|
|
BIT(NXS),
|
|
#define RPER_NXT 0010000 /* nx track */
|
|
BIT(NXT),
|
|
#define RPER_NXC 0020000 /* nx cylinder */
|
|
BIT(NXC),
|
|
#define RPER_FUV 0040000 /* unsafe violation */
|
|
BIT(FUV),
|
|
#define RPER_WPV 0100000 /* write lock violation */
|
|
BIT(WPV),
|
|
ENDBITS
|
|
};
|
|
#define RPER_REAL 0177776
|
|
/* hard errors: drawing 19 */
|
|
#define RPER_HARDERR (RPER_WPV | RPER_FUV | RPER_NXC | RPER_NXT | \
|
|
RPER_NXS | RPER_PGE | RPER_NXM | RPER_DRE | RPER_MODE)
|
|
/* soft errors: drawing 19 */
|
|
#define RPER_SOFTERR (RPER_LPE | RPER_WPE | RPER_CSE | RPER_WCE | \
|
|
RPER_EOP | RPER_TE | RPER_FMTE)
|
|
#define RPER_HARD(x) ((x) & RPER_HARDERR ? (RPCS_ERR | RPCS_HERR) : 0)
|
|
#define RPER_SOFT(x) ((x) & RPER_SOFTERR ? RPCS_ERR : 0)
|
|
|
|
/* RPCS 776714, command/status register */
|
|
static const char* rp_funcs[] = {
|
|
"RESET", "WRITE", "READ", "WCHK", "SEEK", "WRNOSEEK", "HOME", "RDNOSEEK"
|
|
};
|
|
|
|
static BITFIELD rp_cs_bits[] = {
|
|
/* CSR_GO */ /* the GO! bit */
|
|
BIT(GO),
|
|
#define RPCS_V_FUNC 1
|
|
#define RPCS_M_FUNC 7
|
|
#define RPCS_FUNC (RPCS_M_FUNC << RPCS_V_FUNC) /* function */
|
|
#define RPCS_RESET 0
|
|
#define RPCS_WRITE 1
|
|
#define RPCS_READ 2
|
|
#define RPCS_WCHK 3
|
|
#define RPCS_SEEK 4
|
|
#define RPCS_WR_NOSEEK 5
|
|
#define RPCS_HOME 6
|
|
#define RPCS_RD_NOSEEK 7
|
|
BITFNAM(FUNC,3,rp_funcs),
|
|
#define RPCS_V_MEX 4
|
|
#define RPCS_M_MEX 3
|
|
#define RPCS_MEX (RPCS_M_MEX << RPCS_V_MEX) /* memory extension */
|
|
BITF(MEX,2),
|
|
/* CSR_IE */ /* interrupt enable */
|
|
BIT(IE),
|
|
/* CSR_DONE */ /* controller ready */
|
|
BIT(DONE),
|
|
#define RPCS_V_DRV 8
|
|
#define RPCS_M_DRV 7
|
|
#define RPCS_DRV (RPCS_M_DRV << RPCS_V_DRV) /* drive id */
|
|
BITFFMT(DRV,3,%u),
|
|
#define RPCS_HDR 0004000 /* header operation */
|
|
BIT(HDR),
|
|
#define RPCS_MODE 0010000 /* 0=PDP-11; 1=PDP-10/-15 or format */
|
|
BIT(MODE),
|
|
#define RPCS_AIE 0020000 /* attention interrupt enable */
|
|
BIT(AIE),
|
|
#define RPCS_HERR 0040000 /* hard error */
|
|
BIT(HERR),
|
|
#define RPCS_ERR CSR_ERR /* error (hard or soft) */
|
|
BIT(ERR),
|
|
#define RPCS_REAL 0037776 /* bits kept here */
|
|
#define RPCS_RW 0037576 /* read/write */
|
|
#define GET_FUNC(x) (((x) & RPCS_FUNC) >> RPCS_V_FUNC)
|
|
#define GET_DRIVE(x) (((x) & RPCS_DRV) >> RPCS_V_DRV)
|
|
ENDBITS
|
|
};
|
|
|
|
/* RPWC 776716, two's complement word count */
|
|
/* For PDP-11 must be even for data, and in multiples of 3 for format */
|
|
static BITFIELD rp_wc_bits[] = {
|
|
#define RPWC_IMP 0177777 /* implemented */
|
|
BITFFMT(WC,16,%u),
|
|
ENDBITS
|
|
};
|
|
|
|
/* RPBA 776720, bus address */
|
|
static BITFIELD rp_ba_bits[] = {
|
|
#define RPBA_IMP 0177776 /* implemented */
|
|
BITF(BA,16),
|
|
ENDBITS
|
|
};
|
|
|
|
/* RPCA 776722, cylinder address */
|
|
static BITFIELD rp_ca_bits[] = {
|
|
#define RPCA_IMP 0000777 /* implemented */
|
|
BITFFMT(CYL,9,%u),
|
|
ENDBITS
|
|
};
|
|
|
|
/* RPDA 776724, disk address (track/sector) */
|
|
static BITFIELD rp_da_bits[] = {
|
|
#define RPDA_IMPL 0017777 /* implemented */
|
|
#define RPDA_RW 0017417 /* bits here */
|
|
#define RPDA_M_SECT 017
|
|
#define RPDA_SECT RPDA_M_SECT /* sector */
|
|
BITFFMT(SECT,4,%u),
|
|
#define RPDA_V_SOT 4
|
|
#define RPDA_SOT (RPDA_M_SECT << RPDA_V_SOT) /* current sect on track */
|
|
BITFFMT(SOT,4,%u),
|
|
#define RPDA_V_TRACK 8
|
|
#define RPDA_M_TRACK 037
|
|
#define RPDA_TRACK (RPDA_M_TRACK << RPDA_V_TRACK) /* track */
|
|
BITFFMT(SURF,5,%u),
|
|
#define GET_SECT(x) ((x) & RPDA_SECT)
|
|
#define GET_TRACK(x) (((x) & RPDA_TRACK) >> RPDA_V_TRACK)
|
|
#define GET_DA(c,h,s) (((c) * RP_NUMSF + (h)) * RP_NUMSC + (s))
|
|
ENDBITS
|
|
};
|
|
|
|
/* RPM1 776726 maintenance 1, read-only, not implemented */
|
|
|
|
/* RPM2 776730 maintenance 2, read-only, not implemented */
|
|
|
|
/* RPM1 776732 maintenance 3, write-only, not implemented */
|
|
|
|
/* SUCA 776734 selected unit cylinder address, read-only */
|
|
static BITFIELD rp_suca_bits[] = {
|
|
BITFFMT(CYL,9,%u),
|
|
ENDBITS
|
|
};
|
|
|
|
/* SILO 776736 silo memory, not implemented */
|
|
|
|
/* 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 03777
|
|
#define RPWLOA_CYL2 0377 /* cyls locked (x2+1) */
|
|
BITFFMT(CYL2,8,%u),
|
|
#define RPWLOA_V_DRV 8
|
|
#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)
|
|
#define GET_WLOADRV(x) (((x) & RPWLOA_DRV) >> RPWLOA_V_DRV)
|
|
BITNCF(4),
|
|
#define RPWLOA_ON 0100000
|
|
BITFNAM(PROTECT,1,offon),
|
|
ENDBITS
|
|
};
|
|
|
|
/* Data buffer and device registers */
|
|
|
|
static RPCONTR* rpxb = NULL; /* xfer buffer */
|
|
static int32 rpds = 0; /* drive status */
|
|
static int32 rper = 0; /* error status */
|
|
static int32 rpcs = 0; /* control/status */
|
|
static int32 rpwc = 0; /* word count */
|
|
static int32 rpba = 0; /* memory address */
|
|
static int32 rpca = 0; /* cylinder address */
|
|
static int32 rpda = 0; /* disk address */
|
|
static int32 suca = 0; /* current cylinder address */
|
|
static int32 wloa = 0; /* write lockout address */
|
|
static int32 not_impl = 0; /* dummy register value */
|
|
|
|
/* Debug detail levels */
|
|
|
|
#define RRDEB_OPS 001 /* transactions */
|
|
#define RRDEB_RRD 002 /* reg reads */
|
|
#define RRDEB_RWR 004 /* reg writes */
|
|
#define RRDEB_TRC 010 /* trace */
|
|
#define RRDEB_INT 020 /* interrupts */
|
|
#define RRDEB_DAT 0100 /* transfer data */
|
|
|
|
static DEBTAB rr_deb[] = {
|
|
{ "OPS", RRDEB_OPS, "transactions" },
|
|
{ "RRD", RRDEB_RRD, "register reads" },
|
|
{ "RWR", RRDEB_RWR, "register writes" },
|
|
{ "INTERRUPT", RRDEB_INT, "interrupts" },
|
|
{ "TRACE", RRDEB_TRC, "trace" },
|
|
{ "DATA", RRDEB_DAT, "transfer data" },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static struct {
|
|
const char* name;
|
|
int32* valp;
|
|
BITFIELD* bits;
|
|
} rr_regs[] = {
|
|
{ "RPDS", &rpds, rp_ds_bits },
|
|
{ "RPER", &rper, rp_er_bits },
|
|
{ "RPCS", &rpcs, rp_cs_bits },
|
|
{ "RPWC", &rpwc, rp_wc_bits },
|
|
{ "RPBA", &rpba, rp_ba_bits },
|
|
{ "RPCA", &rpca, rp_ca_bits },
|
|
{ "RPDA", &rpda, rp_da_bits },
|
|
{ "RPM1", ¬_impl, NULL },
|
|
{ "RPM2", ¬_impl, NULL },
|
|
{ "RPM3", ¬_impl, NULL },
|
|
{ "SUCA", &suca, rp_suca_bits },
|
|
{ "SILO", ¬_impl, NULL }
|
|
};
|
|
|
|
/* Forward decls */
|
|
|
|
static t_stat rr_rd (int32 *data, int32 PA, int32 access);
|
|
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 (int16 func);
|
|
static void rr_set_done (int32 error);
|
|
static void rr_clr_done (void);
|
|
static t_stat rr_boot (int32 unitno, DEVICE *dptr);
|
|
static t_stat rr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr);
|
|
static t_stat rr_attach (UNIT *uptr, CONST char *cptr);
|
|
static t_stat rr_detach (UNIT *uptr);
|
|
static t_stat rr_set_type (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
static t_stat rr_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
static t_stat rr_set_wloa (UNIT *uptr, int32 val, CONST char *cptr, void *desc);
|
|
static t_stat rr_show_ctrl (FILE *st, UNIT *uptr, int32 val, CONST void *desc);
|
|
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
|
|
rr_dev RR device descriptor
|
|
*/
|
|
|
|
static DIB rr_dib = {
|
|
IOBA_AUTO/*base address*/, RP_IOLN/*addresses used*/,
|
|
rr_rd, rr_wr,
|
|
1/*# of vectors*/, IVCL(RR)/*locator*/, VEC_AUTO,
|
|
{ rr_inta }, RP_IOLN/*addresses per device*/,
|
|
};
|
|
|
|
static REG rr_reg[] = {
|
|
/* registers: CSR first, then in the bus address order */
|
|
{ ORDATADF(RPCS, rpcs, 16, "control/status", rp_cs_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) },
|
|
|
|
/* standard stuff */
|
|
{ FLDATAD (INT, IREQ(RR), INT_V_RR, "interrupt pending flag") },
|
|
{ FLDATAD (ERR, rpcs, CSR_V_ERR, "error flag (CSR<15>)") },
|
|
{ FLDATAD (DONE, rpcs, CSR_V_DONE, "device done flag (CSR<7>)") },
|
|
{ FLDATAD (IE, rpcs, CSR_V_IE, "interrupt enable flag (CSR<6>)") },
|
|
{ ORDATA (DEVADDR, rr_dib.ba, 32), REG_HRO },
|
|
{ ORDATA (DEVVEC, rr_dib.vec, 16), REG_HRO },
|
|
{ NULL }
|
|
};
|
|
|
|
static UNIT rr_unit[RP_NUMDR] = {
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) },
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) },
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) },
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) },
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) },
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) },
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) },
|
|
{ UDATA(rr_svc,
|
|
UNIT_FIX | UNIT_ATTABLE | UNIT_DISABLE | UNIT_ROABLE | UNIT_RP03,
|
|
RP_SIZE_RP03) }
|
|
};
|
|
|
|
static MTAB rr_mod[] = {
|
|
{ MTAB_VDV, 0,
|
|
"TYPE", NULL,
|
|
NULL, rr_show_ctrl, NULL,
|
|
"Display controller type" },
|
|
{ MTAB_VDV | MTAB_VALR, 0,
|
|
NULL, "PROTECT",
|
|
rr_set_wloa, NULL, NULL,
|
|
"Set write lockout mode/address" },
|
|
{ MTAB_VUN, 0,
|
|
"WRITEENABLED", "WRITEENABLED",
|
|
set_writelock, show_writelock, NULL,
|
|
"Write enable disk drive" },
|
|
{ MTAB_VUN, 1,
|
|
NULL, "LOCKED",
|
|
set_writelock, NULL, NULL,
|
|
"Write lock disk drive" },
|
|
{ MTAB_VUN, 0,
|
|
"TYPE", NULL,
|
|
NULL, rr_show_type, NULL,
|
|
"Display device type" },
|
|
{ MTAB_VUN, 0/*RP02*/,
|
|
NULL, RP_RP02,
|
|
rr_set_type, NULL, NULL,
|
|
"Set " RP_RP02 " disk type" },
|
|
{ MTAB_VUN, UNIT_RP03,
|
|
NULL, RP_RP03,
|
|
rr_set_type, NULL, NULL,
|
|
"Set " RP_RP03 " disk type"},
|
|
{ UNIT_NOAUTO, 0,
|
|
"autosize", "AUTOSIZE",
|
|
NULL, NULL, NULL,
|
|
"Set type based on file size at attach" },
|
|
{ UNIT_NOAUTO, UNIT_NOAUTO,
|
|
"noautosize", "NOAUTOSIZE", NULL,
|
|
NULL, NULL,
|
|
"Disable disk autosize on attach" },
|
|
{ MTAB_VUN | MTAB_VALR, 0,
|
|
"FORMAT", "FORMAT={AUTO|SIMH|VHD|RAW}",
|
|
sim_disk_set_fmt, sim_disk_show_fmt, NULL,
|
|
"Set/Display disk format" },
|
|
{ MTAB_VDV | MTAB_VALR, 010,
|
|
"ADDRESS", "ADDRESS",
|
|
set_addr, show_addr, NULL,
|
|
"Bus address" },
|
|
{ MTAB_VDV | MTAB_VALR, 0,
|
|
"VECTOR", "VECTOR",
|
|
set_vec, show_vec, NULL,
|
|
"Interrupt vector" },
|
|
{ 0 }
|
|
};
|
|
|
|
DEVICE rr_dev = {
|
|
"RR", rr_unit, rr_reg, rr_mod, RP_NUMDR,
|
|
DEV_RDX/*address radix*/, 26/*address width*/, 1/*address increment*/,
|
|
DEV_RDX/*data radix*/, RPWRDSZ/*data width*/,
|
|
NULL/*examine()*/, NULL/*deposit()*/,
|
|
rr_reset, rr_boot, rr_attach, rr_detach,
|
|
&rr_dib,
|
|
DEV_DIS | DEV_DISABLE | DEV_UBUS | DEV_Q18 | DEV_DEBUG | DEV_DISK,
|
|
0/*debug control*/, rr_deb,
|
|
NULL/*msize()*/, NULL/*logical name*/,
|
|
rr_help, NULL/*attach_help()*/, NULL/*help_ctx*/,
|
|
rr_description,
|
|
};
|
|
|
|
/* I/O dispatch routine, I/O addresses 17776710 - 17776736
|
|
|
|
17776710 RPDS read-only except for attention bits
|
|
17776712 RPER read-only
|
|
17776714 RPCS read/write
|
|
17776716 RPWC read/write
|
|
17776720 RPBA read/write
|
|
17776722 RPCA read/write
|
|
17776724 RPDA read/write
|
|
17776726 RPM1 read-only, unimplemented
|
|
17776730 RPM2 read-only, unimplemented
|
|
17776732 RPM3 write-only, unimplemented
|
|
17776734 SUCA read-only
|
|
17776736 SILO read/write, unimplemented
|
|
|
|
RP11-C actually responds to the range 17776700 - 17776736 with the first 4 word
|
|
locations unused.
|
|
|
|
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: 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.
|
|
|
|
*/
|
|
|
|
static t_stat rr_rd (int32 *data, int32 PA, int32 access)
|
|
{
|
|
/* offset by base then decode <4:1> */
|
|
int32 rn = (((PA - rr_dib.ba) & (RP_IOLN - 1)) >> 1) - RP_IOFF;
|
|
UNIT* uptr;
|
|
|
|
switch (rn) {
|
|
case 0: /* RPDS */
|
|
case 1: /* RPER */
|
|
case 2: /* RPCS */
|
|
/* RPDS */
|
|
uptr = rr_dev.units + GET_DRIVE(rpcs); /* selected unit */
|
|
rpds &= RPDS_ATTN; /* attention bits */
|
|
if (!(uptr->flags & UNIT_DIS)) { /* not disabled? */
|
|
rpds |= RPDS_ONLN;
|
|
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 (!uptr->FUNC && !(rpds & (RPDS_INC | RPDS_UNSAFE)))
|
|
rpds |= RPDS_RDY; /* ready! */
|
|
} else
|
|
rpds |= uptr->STATUS & (RPDS_DKER | RPDS_UNSAFE);
|
|
}
|
|
|
|
/* RPER */
|
|
rper &= RPER_REAL;
|
|
rper |= RPER_DKER(rpds);
|
|
|
|
/* RPCS */
|
|
rpcs &= RPCS_REAL;
|
|
rpcs |= RPER_HARD(rper) | RPER_SOFT(rper);
|
|
|
|
*data = *rr_regs[rn].valp;
|
|
break;
|
|
|
|
case 3: /* RPWC */
|
|
*data = rpwc;
|
|
break;
|
|
|
|
case 4: /* RPBA */
|
|
*data = rpba;
|
|
break;
|
|
|
|
case 5: /* RPCA */
|
|
*data = rpca;
|
|
break;
|
|
|
|
case 6: /* RPDA */
|
|
rpda &= RPDA_RW;
|
|
rpda |= (rand() % RP_NUMSC) << RPDA_V_SOT; /* inject a random sect */
|
|
*data = rpda;
|
|
break;
|
|
|
|
case 10: /* SUCA */
|
|
*data = suca;
|
|
break;
|
|
|
|
default: /* not implemented */
|
|
*data = 0;
|
|
return SCPE_OK;
|
|
}
|
|
sim_debug(RRDEB_RRD, &rr_dev, ">>RR read: %s=%#o\n", rr_regs[rn].name, *data);
|
|
sim_debug_bits(RRDEB_RRD, &rr_dev, rr_regs[rn].bits, *data, *data, 1);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
#define RR_DATOB(r, d) (PA & 1 ? ((d) << 8) | ((r) & 0377) : ((r) & ~0377) | (d))
|
|
|
|
static t_stat rr_wr (int32 data, int32 PA, int32 access)
|
|
{
|
|
/* offset by base then decode <4:1> */
|
|
int32 rn = (((PA - rr_dib.ba) & (RP_IOLN - 1)) >> 1) - RP_IOFF;
|
|
int32 n, oval = rn < 0 ? 0 : *rr_regs[rn].valp;
|
|
int16 func;
|
|
|
|
if (access == WRITEB && 2 <= rn && rn <= 6)
|
|
data = RR_DATOB(oval, data);
|
|
switch (rn) {
|
|
case 0: /* RPDS */
|
|
if (access != WRITEB || !(PA & 1)) {
|
|
rpds &= ~(data & RPDS_ATTN); /* clr attention bits */
|
|
if (!(rpds & RPDS_ATTN) && (rpcs & RPCS_AIE)
|
|
&& (!(rpcs & CSR_IE) || !(rpcs & CSR_DONE))) {
|
|
sim_debug(RRDEB_INT, &rr_dev, "rr_wr(ATT:CLR_INT)\n");
|
|
CLR_INT(RR); /* clr int request */
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 1: /* RPER: read-only */
|
|
break;
|
|
|
|
case 2: /* RPCS */
|
|
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); /* 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 */
|
|
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 */
|
|
rpwc = data & RPWC_IMP;
|
|
break;
|
|
|
|
case 4: /* RPBA */
|
|
rpba = data & RPBA_IMP;
|
|
break;
|
|
|
|
case 5: /* RPCA */
|
|
rpca = data & RPCA_IMP;
|
|
break;
|
|
|
|
case 6: /* RPDA */
|
|
rpda &= ~RPDA_RW;
|
|
rpda |= data & RPDA_RW;
|
|
break;
|
|
|
|
case 10: /* SUCA: read-only */
|
|
break;
|
|
|
|
default:
|
|
return SCPE_OK;
|
|
}
|
|
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, oval, *rr_regs[rn].valp, 1);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Complete seek initiation */
|
|
|
|
static t_stat rr_seek_init (UNIT *uptr)
|
|
{
|
|
rr_set_done(0); /* set done */
|
|
assert(uptr->SEEKING);
|
|
uptr->action = rr_svc;
|
|
sim_activate(uptr, uptr->SEEKING); /* seek ends then */
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Initiate new function */
|
|
|
|
static void rr_go (int16 func)
|
|
{
|
|
int32 i, type, cyl, head;
|
|
t_bool rd, wr;
|
|
UNIT* uptr;
|
|
|
|
assert(func == GET_FUNC(rpcs));
|
|
|
|
if (func == RPCS_RESET) { /* control reset? */
|
|
rpds = 0;
|
|
rper = 0;
|
|
rpcs = CSR_DONE;
|
|
rpwc = 0;
|
|
rpba = 0;
|
|
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->action = rr_svc;
|
|
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->action == rr_svc);
|
|
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); /* 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));
|
|
|
|
rd = func == RPCS_READ || func == RPCS_RD_NOSEEK || func == RPCS_WCHK;
|
|
wr = func == RPCS_WRITE || func == RPCS_WR_NOSEEK;
|
|
|
|
if (rd | wr) {
|
|
int32 sect = GET_SECT(rpda); /* get sect */
|
|
if (sect >= RP_NUMSC) /* sect out of range? */
|
|
rper |= RPER_NXS;
|
|
}
|
|
type = GET_DTYPE(uptr->flags); /* get drive type */
|
|
if (func == RPCS_HOME) {
|
|
head = 0;
|
|
cyl = 0;
|
|
} 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) /* bad head? */
|
|
rper |= RPER_NXT;
|
|
if (cyl >= drv_tab[type].cyl) /* bad cyl? */
|
|
rper |= RPER_NXC;
|
|
}
|
|
|
|
if (wr && (uptr->flags & UNIT_WPRT)) /* write and locked? */
|
|
rper |= RPER_WPV;
|
|
|
|
if (rper) { /* any errors? */
|
|
rr_set_done(0); /* set done (w/errors) */
|
|
return;
|
|
}
|
|
|
|
/* seek time */
|
|
if (func == RPCS_HOME)
|
|
i = drv_tab[type].seek_ave / 2;
|
|
else if (!(i = abs(cyl - uptr->CYL)))
|
|
i = drv_tab[type].seek_1 / 2;
|
|
else if (i <= 2)
|
|
i *= drv_tab[type].seek_1;
|
|
else if (i <= (3 * drv_tab[type].cyl) / 4)
|
|
i = drv_tab[type].seek_ave;
|
|
else
|
|
i = drv_tab[type].seek_max;
|
|
if (func == RPCS_HOME || func == RPCS_SEEK) { /* seek? */
|
|
uptr->action = rr_seek_init;
|
|
uptr->SEEKING = i; /* drive is seeking */
|
|
i = 10; /* enough for 16us */
|
|
/* XXDP ZRPF-B, Test 0 (cylinder positioning) fails because, even though
|
|
* it initializes INTFLG late (_after_ initiating the SEEK), the delayed
|
|
* CS_DONE here helps avoid the race (and an error) but only for the
|
|
* first iteration (cylinder 0). The next and all subsequent iterations
|
|
* begin with clearing attention bits in RDPS with a BIC instruction,
|
|
* like so: "BIC @#RPDS, @#RPDS", resulting in bare 0 actually sent by
|
|
* ALU to the UNIBUS in DATO cycle to RPDS, and causing no bits reset.
|
|
* That, in turn, causes the interrupt to occur immediately when RPCS is
|
|
* written with the new SEEK/AIE function (the attention bit still set)
|
|
* for the next cylinder, and that causes INTFLG to get set by the ISR
|
|
* just prior to when the test clears it. Thus, the test is unable to
|
|
* confirm the interrupt for all remaining cylinders. ZRPB-E fixes both
|
|
* bugs by initializing INTFLG before firing up SEEK/AIE, and also by
|
|
* using the more correct "MOVB @#RPDS, @#RPDS", instead of BIC. */
|
|
} 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->FUNC = func; /* save new func */
|
|
uptr->HEAD = head; /* save head too */
|
|
uptr->CYL = cyl; /* put on cylinder */
|
|
return;
|
|
}
|
|
|
|
/* Complete seek */
|
|
|
|
static void rr_seek_done (UNIT *uptr, t_bool cancel)
|
|
{
|
|
int32 n = (int32)(uptr - rr_dev.units); /* get unit number */
|
|
|
|
if (n == GET_DRIVE(rpcs))
|
|
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 */
|
|
}
|
|
uptr->STATUS &= ~RPDS_SEEK; /* no longer seeking */
|
|
return;
|
|
}
|
|
|
|
/* Service a unit
|
|
|
|
If seek in progress, complete seek command
|
|
Then if I/O pending, complete data transfer command
|
|
|
|
The unit control block contains the function and disk address for
|
|
the current command.
|
|
|
|
Some registers must be revalidated because they could have been
|
|
modified in between go() and now.
|
|
*/
|
|
|
|
static t_stat rr_svc (UNIT *uptr)
|
|
{
|
|
int32 n, cyl, head, sect, da, wc;
|
|
int16 func = uptr->FUNC;
|
|
t_seccnt todo, done;
|
|
t_stat ioerr;
|
|
uint32 ma;
|
|
t_bool wr;
|
|
|
|
assert(func);
|
|
uptr->FUNC = 0; /* idle */
|
|
|
|
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 */
|
|
|
|
assert(~(rpcs & CSR_DONE));
|
|
|
|
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;
|
|
|
|
n = (int32)(uptr - rr_dev.units); /* get drive no */
|
|
|
|
sect = GET_SECT(rpda); /* get sect */
|
|
if (sect >= RP_NUMSC) /* sect out of range? */
|
|
rper |= RPER_NXS;
|
|
head = uptr->HEAD;
|
|
cyl = uptr->CYL;
|
|
|
|
if (wr) {
|
|
if ((wloa & RPWLOA_ON) && !rper/*valid DA*/ &&
|
|
(n <= 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 (rper) { /* control in error? */
|
|
rr_set_done(0);
|
|
return SCPE_OK;
|
|
}
|
|
/* rper == 0: drive remained selected */
|
|
assert(n == GET_DRIVE(rpcs));
|
|
|
|
wc = 0200000 - rpwc; /* get wd cnt */
|
|
assert(wc <= RP_MAXFR);
|
|
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 */
|
|
|
|
if (rpcs & RPCS_HDR) { /* header ops? */
|
|
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)
|
|
n *= 3; /* 3 wds per sector */
|
|
else /* a typo in doc??? */
|
|
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)
|
|
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 */
|
|
rper |= RPER_PGE;
|
|
#endif
|
|
else
|
|
n = RP_SIZE(n); /* can do this many wrds */
|
|
}
|
|
|
|
if (rper) { /* any new errors? */
|
|
rr_set_done(0); /* set done (w/errors) */
|
|
return SCPE_OK;
|
|
}
|
|
if (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 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
|
|
* word did, then NXM gets reported, and the I/O error gets discarded
|
|
* (because the real controller would have stopped the operation right then
|
|
* 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 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.
|
|
*
|
|
* 3. Disk pack overrun is strictly a run-off of an otherwise successful
|
|
* completion, which has left a residual word counter non-zero, because had
|
|
* an earlier error stopped the disk operation, the overrun situation could
|
|
* not have been experienced (the end of pack would not have been reached).
|
|
*/
|
|
|
|
ma = ((rpcs & RPCS_MEX) << (16 - RPCS_V_MEX)) | rpba; /* get mem addr */
|
|
|
|
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 */
|
|
rpxb[0] = 0; /* BR<35:20> */
|
|
rpxb[1] = (cyl << 6) | (head << 1); /* BR<19:04> */
|
|
rpxb[2] = sect; /* BR<03:00> */
|
|
ioerr = 0;
|
|
done = 1; /* 1 sector done */
|
|
} 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 = 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);
|
|
assert(done <= todo);
|
|
if (done >= todo)
|
|
ioerr = 0; /* good stuff */
|
|
else if (ioerr)
|
|
wc = n; /* short, adj wc */
|
|
else {
|
|
todo -= done; /* to clear ... */
|
|
todo *= RP_SIZE(sizeof(*rpxb)); /* ... bytes */
|
|
memset(rpxb + n, 0, todo);
|
|
}
|
|
}
|
|
if (func == RPCS_WCHK) {
|
|
uint32 a = ma;
|
|
for (n = 0; n < wc; ++n) { /* loop thru buf */
|
|
RPCONTR data;
|
|
if (MAP_RDW(a, 1, &data)) { /* mem wd */
|
|
rper |= RPER_NXM; /* NXM? set flg */
|
|
break;
|
|
}
|
|
a += 2;
|
|
if (rper | ioerr)
|
|
continue;
|
|
if (data != rpxb[n]) /* match to disk? */
|
|
rper |= RPER_WCE; /* no, err */
|
|
}
|
|
n %= wc;
|
|
} else if ((n = MAP_WRW(ma, wc, rpxb))) { /* store buf */
|
|
rper |= RPER_NXM; /* NXM? set flag */
|
|
wc -= n; /* adj wd cnt */
|
|
}
|
|
if (!n && ioerr) { /* all wrds ok but I/O? */
|
|
rper |= RPER_FMTE; /* report as FMTE */
|
|
uptr->STATUS |= RPDS_HNF; /* sector not found */
|
|
if (func == RPCS_WCHK)
|
|
rper |= RPER_WCE; /* write-check err, too */
|
|
}
|
|
} else { /* write: */
|
|
if ((n = MAP_RDW(ma, wc, rpxb))) /* get buf */
|
|
wc -= n; /* adj wd cnt */
|
|
if (wc && !(rpcs & RPCS_HDR)) { /* regular write? */
|
|
DEVICE* dptr = find_dev_from_unit(uptr);
|
|
int32 m = (wc + (RP_NUMWD - 1)) & ~(RP_NUMWD - 1); /* clr to */
|
|
memset(rpxb + wc, 0, (m - wc) * sizeof(*rpxb)); /* end of blk */
|
|
sim_disk_data_trace(uptr, (uint8*) rpxb, da, m * sizeof(*rpxb), "rr_write",
|
|
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);
|
|
assert(done <= todo);
|
|
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; /* 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)
|
|
rper |= RPER_NXM; /* NXM? set flg */
|
|
}
|
|
}
|
|
assert(!ioerr || rper);
|
|
|
|
if (wc) { /* any xfer? */
|
|
assert(done);
|
|
rpwc += wc;
|
|
rpwc &= RPWC_IMP;
|
|
ma += wc << 1;
|
|
rpba = ma & RPBA_IMP;
|
|
rpcs &= ~RPCS_MEX;
|
|
rpcs |= (ma >> (16 - RPCS_V_MEX)) & RPCS_MEX;
|
|
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 cyl */
|
|
rpda = (head << RPDA_V_TRACK) | sect; /* updated head / sect */
|
|
rpca = todo; /* wrapped up cyl */
|
|
suca = cyl; /* updated real cyl */
|
|
} else
|
|
assert(rper);
|
|
|
|
rr_set_done(0); /* all done here */
|
|
|
|
if (ioerr) { /* 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 : "<NULL>", (int) rper, sim_error_text(ioerr),
|
|
errstr && *errstr ? ": " : "", errstr ? errstr : "");
|
|
return SCPE_IOERR;
|
|
}
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Interrupt state change routines
|
|
|
|
rr_clr_done clear done
|
|
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");
|
|
CLR_INT(RR); /* clear int req */
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void rr_set_done (int32 err)
|
|
{
|
|
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");
|
|
SET_INT(RR); /* request int */
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int32 rr_inta (void)
|
|
{
|
|
sim_debug(RRDEB_INT, &rr_dev, "rr_inta()\n");
|
|
assert(((rpcs & RPCS_AIE) && (rpds & RPDS_ATTN)) ||
|
|
((rpcs & CSR_IE) && (rpcs & CSR_DONE)));
|
|
rpcs &= ~RPCS_AIE; /* AIE is one-shot */
|
|
return rr_dib.vec; /* return vector */
|
|
}
|
|
|
|
/* Device reset */
|
|
|
|
static t_stat rr_reset (DEVICE *dptr)
|
|
{
|
|
int32 i;
|
|
|
|
/* compile-time sanity check first */
|
|
const_assert(sizeof(rr_regs)/sizeof(rr_regs[0]) == RP_IOLN/2 - RP_IOFF);
|
|
|
|
/* clear everything now */
|
|
rpds = 0;
|
|
rper = 0;
|
|
rpcs = CSR_DONE;
|
|
rpwc = 0;
|
|
rpba = 0;
|
|
rpca = 0;
|
|
rpda = 0;
|
|
suca = 0;
|
|
for (i = 0; i < RP_NUMDR; ++i) {
|
|
UNIT* uptr = rr_dev.units + i;
|
|
sim_cancel(uptr);
|
|
uptr->action = rr_svc;
|
|
uptr->SEEKING = 0;
|
|
uptr->STATUS = 0;
|
|
uptr->FUNC = 0;
|
|
uptr->HEAD = 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));
|
|
if (rpxb == NULL)
|
|
return SCPE_MEM;
|
|
return auto_config(NULL, 0);
|
|
}
|
|
|
|
/* Attach/detach routines */
|
|
|
|
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);
|
|
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)
|
|
{
|
|
int16 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->action = rr_svc;
|
|
uptr->HEAD = 0;
|
|
uptr->CYL = 0;
|
|
return sim_disk_detach(uptr);
|
|
}
|
|
|
|
/* Set / show drive type */
|
|
|
|
static t_stat rr_set_type (UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
if ((val & ~UNIT_RP03) || cptr)
|
|
return SCPE_ARG;
|
|
if (uptr->flags & UNIT_ATT)
|
|
return SCPE_ALATT;
|
|
uptr->capac = RP_SIZE(drv_tab[GET_DTYPE(val)].size);
|
|
uptr->flags &= ~UNIT_RP03;
|
|
uptr->flags |= val;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static t_stat rr_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fputs(drv_tab[GET_DTYPE(uptr->flags)].name, st);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Set WLOA */
|
|
|
|
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_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;
|
|
}
|
|
if (strncasecmp(cptr, "ON", 2) != 0)
|
|
return SCPE_ARG;
|
|
cptr += 2;
|
|
if (*cptr == ';') {
|
|
char* end;
|
|
long val;
|
|
if (!*++cptr)
|
|
return SCPE_MISVAL;
|
|
errno = 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_ARG;
|
|
wloa |= RPWLOA_ON;
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Device bootstrap */
|
|
|
|
#define BOOT_START 02000 /* start */
|
|
#define BOOT_ENTRY (BOOT_START + 002) /* entry */
|
|
#define BOOT_UNIT (BOOT_START + 010) /* unit number */
|
|
#define BOOT_CSR (BOOT_START + 014) /* CSR + 12 */
|
|
#define BOOT_LEN (sizeof (rr_boot_rom) / sizeof (rr_boot_rom[0]))
|
|
|
|
static const uint16 rr_boot_rom[] = {
|
|
/* EXPECTED M9312 REGISTER USE FOR BOOT PROMS (IN THE BOOTED SOFTWARE): *
|
|
* R0 = UNIT NUMBER *
|
|
* R1 = CONTROLLER CSR *
|
|
* R2, R3 = TEMPORARIES *
|
|
* 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 (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" (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 #<START+20>, 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 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 */
|
|
/* 002044 */ 0010341, /* MOV R3, -(R1) ; DO IT! */
|
|
/* 002046 */ 0005005, /* CLR R5 ; M9312 USES FOR DISPLAY */
|
|
/* 002050 */ 0105711, /* 1$: TSTB (R1) ; READY? */
|
|
/* 002052 */ 0100376, /* BPL 1$ ; BR IF NOT */
|
|
/* 002054 */ 0005711, /* TST (R1) ; ERROR? */
|
|
/* 002056 */ 0100002, /* BPL 2$ ; BR IF NOT */
|
|
/* 002060 */ 0000005, /* RESET */
|
|
/* 002062 */ 0000747, /* BR BOOT ; START OVER */
|
|
/* 002064 */ 0105011, /* 2$: CLRB (R1) ; CLEAR CONTROLLER */
|
|
/* 002066 */ 0005007 /* CLR PC ; JUMP TO BOOTSTRAP */
|
|
/* 002070 .END */
|
|
};
|
|
|
|
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));
|
|
WrMemW(BOOT_CSR, (rr_dib.ba & DMASK) + (014/*CSR*/ + 012));
|
|
cpu_set_boot(BOOT_ENTRY);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Misc */
|
|
|
|
#define RP_DESCRIPTION RP_RP11 "/" RP_RP02 "/" RP_RP03 " disk pack device"
|
|
|
|
static t_stat rr_help (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
size_t i;
|
|
fputs(
|
|
/*567901234567890123456789012345678901234567890123456789012345678901234567890*/
|
|
RP_DESCRIPTION "\n\n"
|
|
"A detailed description of this device can be found in the\n"
|
|
"\"PDP-11 Peripherals Handbook\" (1973 - 1976) and in the technical manual\n"
|
|
"\"RP11-C Disk Pack Drive Controller Maintenance Manual\" (1974)\n"
|
|
"(DEC-11-HRPCA-C-D).\n\n"
|
|
"In default configuration " RP_RP11 " responds to the range 17776700 - 17776736\n"
|
|
"with the first 4 word locations not occupied by any device registers (and\n"
|
|
"so 17776710 is the first used location). Some operating systems want you\n"
|
|
"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 (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]); ++i) {
|
|
uint32 spare = GET_DA(drv_tab[i].spare, RP_NUMSF, RP_NUMSC);
|
|
uint32 total = drv_tab[i].size;
|
|
fprintf(st, "%.6s: %5u %5u %5u %5u"
|
|
" %5.1fMB %5.1fMB %5u.%1u\n", drv_tab[i].name,
|
|
drv_tab[i].cyl, drv_tab[i].spare, RP_NUMSF, RP_NUMSC,
|
|
RP_SIZE(total - spare) / .5e6, RP_SIZE(total) / .5e6,
|
|
(drv_tab[i].seek_ave + RP_ROT_12)/10,
|
|
(drv_tab[i].seek_ave + RP_ROT_12)%10);
|
|
}
|
|
fputs("\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 (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"
|
|
" WLOA: 000407 PROTECT=OFF DRV=1 CYL2=7\n\n"
|
|
"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,
|
|
"\nThe " RP_RP11 " is disabled in a Qbus system with more than 256KB of memory.\n");
|
|
fprint_reg_help (st, dptr);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static const char *rr_description (DEVICE *dptr)
|
|
{
|
|
return RP_DESCRIPTION;
|
|
}
|
|
|
|
/* Show / switch controller type */
|
|
|
|
static t_stat rr_show_ctrl (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fputs(RP_RP11, st);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
#elif !defined(UC15)
|
|
#error "RP11/-C/-E can only be used in PDP-11 configuration"
|
|
#endif /*VM_PDP11 && !UC15*/
|