/* pdp11_rf.c: RF11 fixed head disk simulator | |
Copyright (c) 2006-2012, Robert M Supnik | |
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. | |
rf RF11 fixed head disk | |
19-Mar-12 RMS Fixed bug in updating mem addr extension (Peter Schorn) | |
25-Dec-06 RMS Fixed bug in unit mask (John Dundas) | |
26-Jun-06 RMS Cloned from RF08 simulator | |
The RF11 is a head-per-track disk. To minimize overhead, the entire RF11 | |
is buffered in memory. | |
Two timing parameters are provided: | |
rf_time Interword timing, must be non-zero | |
rf_burst Burst mode, if 0, DMA occurs cycle by cycle; otherwise, | |
DMA occurs in a burst | |
*/ | |
#include "pdp11_defs.h" | |
#include <math.h> | |
#define UNIT_V_AUTO (UNIT_V_UF + 0) /* autosize */ | |
#define UNIT_V_PLAT (UNIT_V_UF + 1) /* #platters - 1 */ | |
#define UNIT_M_PLAT (RF_NUMDK - 1) | |
#define UNIT_GETP(x) ((((x) >> UNIT_V_PLAT) & UNIT_M_PLAT) + 1) | |
#define UNIT_AUTO (1 << UNIT_V_AUTO) | |
#define UNIT_PLAT (UNIT_M_PLAT << UNIT_V_PLAT) | |
/* Constants */ | |
#define RF_NUMWD 2048 /* words/track */ | |
#define RF_NUMTR 128 /* tracks/disk */ | |
#define RF_DKSIZE (RF_NUMTR * RF_NUMWD) /* words/disk */ | |
#define RF_NUMDK 8 /* disks/controller */ | |
#define RF_WMASK (RF_NUMWD - 1) /* word mask */ | |
/* Parameters in the unit descriptor */ | |
#define FUNC u4 /* function */ | |
/* Status register */ | |
#define RFCS_ERR (CSR_ERR) /* error */ | |
#define RFCS_FRZ 0040000 /* error freeze */ | |
#define RFCS_WCHK 0020000 /* write check */ | |
#define RFCS_DPAR 0010000 /* data parity (ni) */ | |
#define RFCS_NED 0004000 /* nx disk */ | |
#define RFCS_WLK 0002000 /* write lock */ | |
#define RFCS_MXFR 0001000 /* missed xfer (ni) */ | |
#define RFCS_CLR 0000400 /* clear */ | |
#define RFCS_DONE (CSR_DONE) | |
#define RFCS_IE (CSR_IE) | |
#define RFCS_M_MEX 0000003 /* memory extension */ | |
#define RFCS_V_MEX 4 | |
#define RFCS_MEX (RFCS_M_MEX << RFCS_V_MEX) | |
#define RFCS_MAINT 0000010 /* maint */ | |
#define RFCS_M_FUNC 0000003 /* function */ | |
#define RFNC_NOP 0 | |
#define RFNC_WRITE 1 | |
#define RFNC_READ 2 | |
#define RFNC_WCHK 3 | |
#define RFCS_V_FUNC 1 | |
#define RFCS_FUNC (RFCS_M_FUNC << RFCS_V_FUNC) | |
#define RFCS_GO 0000001 | |
#define RFCS_ALLERR (RFCS_FRZ|RFCS_WCHK|RFCS_DPAR|RFCS_NED|RFCS_WLK|RFCS_MXFR) | |
#define RFCS_W (RFCS_IE|RFCS_MEX|RFCS_FUNC) | |
/* Current memory address */ | |
#define RFCMA_RW 0177776 | |
/* Address extension */ | |
#define RFDAE_ALLERR 0176000 | |
#define RFDAE_NXM 0002000 | |
#define RFDAE_INH 0000400 /* addr inhibit */ | |
#define RFDAE_RLAT 0000200 /* req late */ | |
#define RFDAE_DAE 0000077 /* extension */ | |
#define RFDAE_R 0176677 | |
#define RFDAE_W 0000677 | |
#define GET_FUNC(x) (((x) >> RFCS_V_FUNC) & RFCS_M_FUNC) | |
#define GET_MEX(x) (((x) & RFCS_MEX) << (16 - RFCS_V_MEX)) | |
#define GET_DEX(x) (((x) & RFDAE_DAE) << 16) | |
#define GET_POS(x) ((int) fmod (sim_gtime() / ((double) (x)), \ | |
((double) RF_NUMWD))) | |
extern uint16 *M; | |
extern int32 int_req[IPL_HLVL]; | |
uint32 rf_cs = 0; /* status register */ | |
uint32 rf_cma = 0; | |
uint32 rf_wc = 0; | |
uint32 rf_da = 0; /* disk address */ | |
uint32 rf_dae = 0; | |
uint32 rf_dbr = 0; | |
uint32 rf_maint = 0; | |
uint32 rf_wlk = 0; /* write lock */ | |
uint32 rf_time = 10; /* inter-word time */ | |
uint32 rf_burst = 1; /* burst mode flag */ | |
uint32 rf_stopioe = 1; /* stop on error */ | |
DEVICE rf_dev; | |
t_stat rf_rd (int32 *data, int32 PA, int32 access); | |
t_stat rf_wr (int32 data, int32 PA, int32 access); | |
int32 rf_inta (void); | |
t_stat rf_svc (UNIT *uptr); | |
t_stat rf_reset (DEVICE *dptr); | |
t_stat rf_boot (int32 unitno, DEVICE *dptr); | |
t_stat rf_attach (UNIT *uptr, char *cptr); | |
t_stat rf_set_size (UNIT *uptr, int32 val, char *cptr, void *desc); | |
uint32 update_rfcs (uint32 newcs, uint32 newdae); | |
/* RF11 data structures | |
rf_dev RF device descriptor | |
rf_unit RF unit descriptor | |
rf_reg RF register list | |
*/ | |
#define IOLN_RF 020 | |
DIB rf_dib = { | |
IOBA_AUTO, IOLN_RF, &rf_rd, &rf_wr, | |
1, IVCL (RF), VEC_AUTO, {NULL}, IOLN_RF, | |
}; | |
UNIT rf_unit = { | |
UDATA (&rf_svc, UNIT_FIX+UNIT_ATTABLE+ | |
UNIT_BUFABLE+UNIT_MUSTBUF, RF_DKSIZE) | |
}; | |
REG rf_reg[] = { | |
{ ORDATA (RFCS, rf_cs, 16) }, | |
{ ORDATA (RFWC, rf_wc, 16) }, | |
{ ORDATA (RFCMA, rf_cma, 16) }, | |
{ ORDATA (RFDA, rf_da, 16) }, | |
{ ORDATA (RFDAE, rf_dae, 16) }, | |
{ ORDATA (RFDBR, rf_dbr, 16) }, | |
{ ORDATA (RFMR, rf_maint, 16) }, | |
{ ORDATA (RFWLK, rf_wlk, 32) }, | |
{ FLDATA (INT, IREQ (RF), INT_V_RF) }, | |
{ FLDATA (ERR, rf_cs, CSR_V_ERR) }, | |
{ FLDATA (DONE, rf_cs, CSR_V_DONE) }, | |
{ FLDATA (IE, rf_cs, CSR_V_IE) }, | |
{ DRDATA (TIME, rf_time, 24), REG_NZ + PV_LEFT }, | |
{ FLDATA (BURST, rf_burst, 0) }, | |
{ FLDATA (STOP_IOE, rf_stopioe, 0) }, | |
{ ORDATA (DEVADDR, rf_dib.ba, 32), REG_HRO }, | |
{ ORDATA (DEVVEC, rf_dib.vec, 16), REG_HRO }, | |
{ NULL } | |
}; | |
MTAB rf_mod[] = { | |
{ UNIT_PLAT, (0 << UNIT_V_PLAT), NULL, "1P", &rf_set_size }, | |
{ UNIT_PLAT, (1 << UNIT_V_PLAT), NULL, "2P", &rf_set_size }, | |
{ UNIT_PLAT, (2 << UNIT_V_PLAT), NULL, "3P", &rf_set_size }, | |
{ UNIT_PLAT, (3 << UNIT_V_PLAT), NULL, "4P", &rf_set_size }, | |
{ UNIT_PLAT, (4 << UNIT_V_PLAT), NULL, "5P", &rf_set_size }, | |
{ UNIT_PLAT, (5 << UNIT_V_PLAT), NULL, "6P", &rf_set_size }, | |
{ UNIT_PLAT, (6 << UNIT_V_PLAT), NULL, "7P", &rf_set_size }, | |
{ UNIT_PLAT, (7 << UNIT_V_PLAT), NULL, "8P", &rf_set_size }, | |
{ UNIT_AUTO, UNIT_AUTO, "autosize", "AUTOSIZE", NULL }, | |
{ MTAB_XTD|MTAB_VDV, 020, "ADDRESS", "ADDRESS", | |
&set_addr, &show_addr, NULL }, | |
{ MTAB_XTD|MTAB_VDV, 0, "VECTOR", "VECTOR", | |
&set_vec, &show_vec, NULL }, | |
{ 0 } | |
}; | |
DEVICE rf_dev = { | |
"RF", &rf_unit, rf_reg, rf_mod, | |
1, 8, 21, 1, 8, 16, | |
NULL, NULL, &rf_reset, | |
&rf_boot, &rf_attach, NULL, | |
&rf_dib, DEV_DISABLE | DEV_DIS | DEV_UBUS | DEV_DEBUG | |
}; | |
/* I/O dispatch routine, I/O addresses 17777460 - 17777476 */ | |
t_stat rf_rd (int32 *data, int32 PA, int32 access) | |
{ | |
switch ((PA >> 1) & 07) { /* decode PA<3:1> */ | |
case 0: /* RFCS */ | |
*data = update_rfcs (0, 0); /* update RFCS */ | |
break; | |
case 1: /* RFWC */ | |
*data = rf_wc; | |
break; | |
case 2: /* RFCMA */ | |
*data = rf_cma & RFCMA_RW; | |
break; | |
case 3: /* RFDA */ | |
*data = rf_da; | |
break; | |
case 4: /* RFDAE */ | |
*data = rf_dae & RFDAE_R; | |
break; | |
case 5: /* RFDBR */ | |
*data = rf_dbr; | |
break; | |
case 6: /* RFMR */ | |
*data = rf_maint; | |
break; | |
case 7: /* RFADS */ | |
*data = GET_POS (rf_time); | |
break; | |
} /* end switch */ | |
return SCPE_OK; | |
} | |
t_stat rf_wr (int32 data, int32 PA, int32 access) | |
{ | |
int32 t, fnc; | |
switch ((PA >> 1) & 07) { /* decode PA<3:1> */ | |
case 0: /* RFCS */ | |
if (access == WRITEB) | |
data = (PA & 1)? (rf_cs & 0377) | (data << 8): (rf_cs & ~0377) | data; | |
if (data & RFCS_CLR) /* clear? */ | |
rf_reset (&rf_dev); | |
if ((data & RFCS_IE) == 0) /* int disable? */ | |
CLR_INT (RF); /* clr int request */ | |
else if ((rf_cs & (RFCS_DONE + RFCS_IE)) == RFCS_DONE) | |
SET_INT (RF); /* set int request */ | |
rf_cs = (rf_cs & ~RFCS_W) | (data & RFCS_W); /* merge */ | |
if ((rf_cs & RFCS_DONE) && (data & RFCS_GO) && /* new function? */ | |
((fnc = GET_FUNC (rf_cs)) != RFNC_NOP)) { | |
rf_unit.FUNC = fnc; /* save function */ | |
t = (rf_da & RF_WMASK) - GET_POS (rf_time); /* delta to new loc */ | |
if (t < 0) /* wrap around? */ | |
t = t + RF_NUMWD; | |
sim_activate (&rf_unit, t * rf_time); /* schedule op */ | |
rf_cs &= ~(RFCS_WCHK|RFCS_DPAR|RFCS_NED|RFCS_WLK|RFCS_MXFR|RFCS_DONE); | |
CLR_INT (RF); | |
if (DEBUG_PRS (rf_dev)) | |
fprintf (sim_deb, ">>RF start: cs = %o, da = %o, ma = %o\n", | |
update_rfcs (0, 0), GET_DEX (rf_dae) | rf_da, GET_MEX (rf_cs) | rf_cma); | |
} | |
break; | |
case 1: /* RFWC */ | |
if (access == WRITEB) | |
data = (PA & 1)? (rf_wc & 0377) | (data << 8): (rf_wc & ~0377) | data; | |
rf_wc = data; | |
break; | |
case 2: /* RFCMA */ | |
if (access == WRITEB) | |
data = (PA & 1)? (rf_cma & 0377) | (data << 8): (rf_cma & ~0377) | data; | |
rf_cma = data & RFCMA_RW; | |
break; | |
case 3: /* RFDA */ | |
if (access == WRITEB) | |
data = (PA & 1)? (rf_da & 0377) | (data << 8): (rf_da & ~0377) | data; | |
rf_da = data; | |
break; | |
case 4: /* RFDAE */ | |
if (access == WRITEB) | |
data = (PA & 1)? (rf_dae & 0377) | (data << 8): (rf_dae & ~0377) | data; | |
rf_dae = (rf_dae & ~RFDAE_W) | (data & RFDAE_W); | |
break; | |
case 5: /* RFDBR */ | |
rf_dbr = data; | |
break; | |
case 6: /* RFMR */ | |
rf_maint = data; | |
break; | |
case 7: /* RFADS */ | |
break; /* read only */ | |
} /* end switch */ | |
update_rfcs (0, 0); | |
return SCPE_OK; | |
} | |
/* Unit service | |
Note that for reads and writes, memory addresses wrap around in the | |
current field. This code assumes the entire disk is buffered. | |
*/ | |
t_stat rf_svc (UNIT *uptr) | |
{ | |
uint32 ma, da, t; | |
uint16 dat; | |
uint16 *fbuf = uptr->filebuf; | |
if ((uptr->flags & UNIT_BUF) == 0) { /* not buf? abort */ | |
update_rfcs (RFCS_NED|RFCS_DONE, 0); /* nx disk */ | |
return IORETURN (rf_stopioe, SCPE_UNATT); | |
} | |
ma = GET_MEX (rf_cs) | rf_cma; /* 18b mem addr */ | |
da = GET_DEX (rf_dae) | rf_da; /* 22b disk addr */ | |
do { | |
if (da >= rf_unit.capac) { /* disk overflow? */ | |
update_rfcs (RFCS_NED, 0); | |
break; | |
} | |
if (uptr->FUNC == RFNC_READ) { /* read? */ | |
dat = fbuf[da]; /* get disk data */ | |
rf_dbr = dat; | |
if (Map_WriteW (ma, 2, &dat)) { /* store mem, nxm? */ | |
update_rfcs (0, RFDAE_NXM); | |
break; | |
} | |
} | |
else if (uptr->FUNC == RFNC_WCHK) { /* write check? */ | |
rf_dbr = fbuf[da]; /* get disk data */ | |
if (Map_ReadW (ma, 2, &dat)) { /* read mem, nxm? */ | |
update_rfcs (0, RFDAE_NXM); | |
break; | |
} | |
if (rf_dbr != dat) { /* miscompare? */ | |
update_rfcs (RFCS_WCHK, 0); | |
break; | |
} | |
} | |
else { /* write */ | |
t = (da >> 15) & 037; | |
if ((rf_wlk >> t) & 1) { /* write locked? */ | |
update_rfcs (RFCS_WLK, 0); | |
break; | |
} | |
else { /* not locked */ | |
if (Map_ReadW (ma, 2, &dat)) { /* read mem, nxm? */ | |
update_rfcs (0, RFDAE_NXM); | |
break; | |
} | |
fbuf[da] = dat; /* write word */ | |
rf_dbr = dat; | |
if (da >= uptr->hwmark) | |
uptr->hwmark = da + 1; | |
} | |
} | |
da = (da + 1) & 017777777; /* incr disk addr */ | |
if ((rf_dae & RFDAE_INH) == 0) /* inhibit clear? */ | |
ma = (ma + 2) & UNIMASK; /* incr mem addr */ | |
rf_wc = (rf_wc + 1) & DMASK; /* incr word count */ | |
} while ((rf_wc != 0) && (rf_burst != 0)); /* brk if wc, no brst */ | |
rf_da = da & DMASK; /* split da */ | |
rf_dae = (rf_dae & ~RFDAE_DAE) | ((rf_da >> 16) & RFDAE_DAE); | |
rf_cma = ma & DMASK; /* split ma */ | |
rf_cs = (rf_cs & ~RFCS_MEX) | ((ma >> (16 - RFCS_V_MEX)) & RFCS_MEX); | |
if ((rf_wc != 0) && ((rf_cs & RFCS_ERR) == 0)) /* more to do? */ | |
sim_activate (&rf_unit, rf_time); /* sched next */ | |
else { | |
update_rfcs (RFCS_DONE, 0); | |
if (DEBUG_PRS (rf_dev)) | |
fprintf (sim_deb, ">>RF done: cs = %o, dae = %o, da = %o, ma = %o, wc = %o\n", | |
rf_cs, rf_dae, rf_da, rf_cma, rf_wc); | |
} | |
return SCPE_OK; | |
} | |
/* Update CS register */ | |
uint32 update_rfcs (uint32 newcs, uint32 newdae) | |
{ | |
uint32 oldcs = rf_cs; | |
uint32 da = GET_DEX (rf_dae) | rf_da; | |
rf_dae |= newdae; /* update DAE */ | |
rf_cs |= newcs; /* update CS */ | |
if (da >= rf_unit.capac) /* update CS<ned> */ | |
rf_cs |= RFCS_NED; | |
else rf_cs &= ~RFCS_NED; | |
if (rf_dae & RFDAE_ALLERR) /* update CS<frz> */ | |
rf_cs |= RFCS_FRZ; | |
else rf_cs &= ~RFCS_FRZ; | |
if (rf_cs & RFCS_ALLERR) /* update CS<err> */ | |
rf_cs |= RFCS_ERR; | |
else rf_cs &= ~RFCS_ERR; | |
if ((rf_cs & RFCS_IE) && /* IE and */ | |
(rf_cs & RFCS_DONE) &&!(oldcs & RFCS_DONE)) /* done 0->1? */ | |
SET_INT (RF); | |
return rf_cs; | |
} | |
/* Reset routine */ | |
t_stat rf_reset (DEVICE *dptr) | |
{ | |
rf_cs = RFCS_DONE; | |
rf_da = rf_dae = 0; | |
rf_dbr = 0; | |
rf_cma = 0; | |
rf_wc = 0; | |
rf_maint = 0; | |
CLR_INT (RF); | |
sim_cancel (&rf_unit); | |
return auto_config (0, 0); | |
} | |
/* Bootstrap routine */ | |
/* Device bootstrap */ | |
#define BOOT_START 02000 /* start */ | |
#define BOOT_ENTRY (BOOT_START + 002) /* entry */ | |
#define BOOT_CSR (BOOT_START + 032) /* CSR */ | |
#define BOOT_LEN (sizeof (boot_rom) / sizeof (uint16)) | |
static const uint16 boot_rom[] = { | |
0043113, /* "FD" */ | |
0012706, BOOT_START, /* MOV #boot_start, SP */ | |
0012701, 0177472, /* MOV #RFDAE+2, R1 ; csr block */ | |
0005041, /* CLR -(R1) ; clear dae */ | |
0005041, /* CLR -(R1), ; clear da */ | |
0005041, /* CLR -(R1), ; clear cma */ | |
0012741, 0177000, /* MOV #-256.*2, -(R1) ; load wc */ | |
0012741, 0000005, /* MOV #READ+GO, -(R1) ; read & go */ | |
0005002, /* CLR R2 */ | |
0005003, /* CLR R3 */ | |
0012704, BOOT_START+020, /* MOV #START+20, R4 */ | |
0005005, /* CLR R5 */ | |
0105711, /* TSTB (R1) */ | |
0100376, /* BPL .-2 */ | |
0105011, /* CLRB (R1) */ | |
0005007 /* CLR PC */ | |
}; | |
t_stat rf_boot (int32 unitno, DEVICE *dptr) | |
{ | |
size_t i; | |
extern int32 saved_PC; | |
for (i = 0; i < BOOT_LEN; i++) | |
M[(BOOT_START >> 1) + i] = boot_rom[i]; | |
M[BOOT_CSR >> 1] = (rf_dib.ba & DMASK) + 012; | |
saved_PC = BOOT_ENTRY; | |
return SCPE_OK; | |
} | |
/* Attach routine */ | |
t_stat rf_attach (UNIT *uptr, char *cptr) | |
{ | |
uint32 sz, p; | |
uint32 ds_bytes = RF_DKSIZE * sizeof (int16); | |
if ((uptr->flags & UNIT_AUTO) && (sz = sim_fsize_name (cptr))) { | |
p = (sz + ds_bytes - 1) / ds_bytes; | |
if (p >= RF_NUMDK) | |
p = RF_NUMDK - 1; | |
uptr->flags = (uptr->flags & ~UNIT_PLAT) | | |
(p << UNIT_V_PLAT); | |
} | |
uptr->capac = UNIT_GETP (uptr->flags) * RF_DKSIZE; | |
return attach_unit (uptr, cptr); | |
} | |
/* Change disk size */ | |
t_stat rf_set_size (UNIT *uptr, int32 val, char *cptr, void *desc) | |
{ | |
if (val < 0) | |
return SCPE_IERR; | |
if (uptr->flags & UNIT_ATT) | |
return SCPE_ALATT; | |
uptr->capac = UNIT_GETP (val) * RF_DKSIZE; | |
uptr->flags = uptr->flags & ~UNIT_AUTO; | |
return SCPE_OK; | |
} |