| 1 | /* sds_dsk.c: SDS 940 moving head disk simulator\r |
| 2 | \r |
| 3 | Copyright (c) 2001-2005, Robert M. Supnik\r |
| 4 | \r |
| 5 | Permission is hereby granted, free of charge, to any person obtaining a\r |
| 6 | copy of this software and associated documentation files (the "Software"),\r |
| 7 | to deal in the Software without restriction, including without limitation\r |
| 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense,\r |
| 9 | and/or sell copies of the Software, and to permit persons to whom the\r |
| 10 | Software is furnished to do so, subject to the following conditions:\r |
| 11 | \r |
| 12 | The above copyright notice and this permission notice shall be included in\r |
| 13 | all copies or substantial portions of the Software.\r |
| 14 | \r |
| 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r |
| 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r |
| 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\r |
| 18 | ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r |
| 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r |
| 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r |
| 21 | \r |
| 22 | Except as contained in this notice, the name of Robert M Supnik shall not be\r |
| 23 | used in advertising or otherwise to promote the sale, use or other dealings\r |
| 24 | in this Software without prior written authorization from Robert M Supnik.\r |
| 25 | \r |
| 26 | dsk moving head disk\r |
| 27 | \r |
| 28 | The SDS 9164 disk has a subsector feature, allowing each 64W sector to be\r |
| 29 | viewed as 16W packets. In addition, it has a chaining feature, allowing\r |
| 30 | records to be extended beyond a sector boundary. To accomodate this, the\r |
| 31 | first word of each sector has 3 extra bits:\r |
| 32 | \r |
| 33 | <26> = end of chain flag \r |
| 34 | <25:24> = 4 - number of packets\r |
| 35 | \r |
| 36 | These values were chosen so that 000 = continue chain, full sector.\r |
| 37 | */\r |
| 38 | \r |
| 39 | #include "sds_defs.h"\r |
| 40 | \r |
| 41 | #define UNIT_V_WLK (UNIT_V_UF + 0) /* write locked */\r |
| 42 | #define UNIT_WLK (1 << UNIT_V_WLK)\r |
| 43 | #define UNIT_WPRT (UNIT_WLK | UNIT_RO) /* write protect */\r |
| 44 | \r |
| 45 | #define DSK_PKTWD 16 /* words/packet */\r |
| 46 | #define DSK_NUMPKT 4 /* packets/sector */\r |
| 47 | #define DSK_NUMWD (DSK_PKTWD*DSK_NUMPKT) /* words/sector */\r |
| 48 | #define DSK_N_SC 5 /* sect addr width */\r |
| 49 | #define DSK_V_SC 0 /* position */\r |
| 50 | #define DSK_M_SC ((1 << DSK_N_SC) - 1) /* mask */\r |
| 51 | #define DSK_NUMSC (1 << DSK_N_SC) /* sectors/track */\r |
| 52 | #define DSK_N_TR 8 /* track addr width */\r |
| 53 | #define DSK_V_TR (DSK_N_SC) /* position */\r |
| 54 | #define DSK_M_TR ((1 << DSK_N_TR) - 1) /* mask */\r |
| 55 | #define DSK_NUMTR (1 << DSK_N_TR) /* tracks/surface */\r |
| 56 | #define DSK_N_SF 5 /* surf addr width */\r |
| 57 | #define DSK_V_SF (DSK_N_SC + DSK_N_TR) /* position */\r |
| 58 | #define DSK_M_SF ((1 << DSK_N_SF) - 1) /* mask */\r |
| 59 | #define DSK_NUMSF (1 << DSK_N_SF) /* surfaces/drive */\r |
| 60 | #define DSK_SCSIZE (DSK_NUMSF*DSK_NUMTR*DSK_NUMSC) /* sectors/drive */\r |
| 61 | #define DSK_AMASK (DSK_SCSIZE - 1) /* address mask */\r |
| 62 | #define DSK_SIZE (DSK_SCSIZE * DSK_NUMWD) /* words/drive */\r |
| 63 | #define DSK_GETTR(x) (((x) >> DSK_V_TR) & DSK_M_TR)\r |
| 64 | #define cyl u3 /* curr cylinder */\r |
| 65 | #define DSK_SIP (1 << (DSK_N_TR + 2))\r |
| 66 | #define DSK_V_PKT 24\r |
| 67 | #define DSK_M_PKT 03\r |
| 68 | #define DSK_V_CHN 26\r |
| 69 | #define DSK_GETPKT(x) (4 - (((x) >> DSK_V_PKT) & DSK_M_PKT))\r |
| 70 | #define DSK_ENDCHN(x) ((x) & (1 << DSK_V_CHN)) \r |
| 71 | \r |
| 72 | extern uint32 xfr_req;\r |
| 73 | extern uint32 alert;\r |
| 74 | extern int32 stop_invins, stop_invdev, stop_inviop;\r |
| 75 | int32 dsk_da = 0; /* disk addr */\r |
| 76 | int32 dsk_op = 0; /* operation */\r |
| 77 | int32 dsk_err = 0; /* error flag */\r |
| 78 | uint32 dsk_buf[DSK_NUMWD]; /* sector buf */\r |
| 79 | int32 dsk_bptr = 0; /* byte ptr */\r |
| 80 | int32 dsk_blnt = 0; /* byte lnt */\r |
| 81 | int32 dsk_time = 5; /* time per char */\r |
| 82 | int32 dsk_stime = 200; /* seek time */\r |
| 83 | int32 dsk_stopioe = 1;\r |
| 84 | DSPT dsk_tplt[] = { /* template */\r |
| 85 | { 1, 0 },\r |
| 86 | { 1, DEV_OUT },\r |
| 87 | { 0, 0 }\r |
| 88 | };\r |
| 89 | \r |
| 90 | DEVICE dsk_dev;\r |
| 91 | t_stat dsk_svc (UNIT *uptr);\r |
| 92 | t_stat dsk_reset (DEVICE *dptr);\r |
| 93 | t_stat dsk_fill (uint32 dev);\r |
| 94 | t_stat dsk_read_buf (uint32 dev);\r |
| 95 | t_stat dsk_write_buf (uint32 dev);\r |
| 96 | void dsk_end_op (uint32 fl);\r |
| 97 | t_stat dsk (uint32 fnc, uint32 inst, uint32 *dat);\r |
| 98 | \r |
| 99 | /* DSK data structures\r |
| 100 | \r |
| 101 | dsk_dev device descriptor\r |
| 102 | dsk_unit unit descriptor\r |
| 103 | dsk_reg register list\r |
| 104 | */\r |
| 105 | \r |
| 106 | DIB dsk_dib = { CHAN_F, DEV_DSK, XFR_DSK, dsk_tplt, &dsk };\r |
| 107 | \r |
| 108 | UNIT dsk_unit = { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE, DSK_SIZE) };\r |
| 109 | \r |
| 110 | REG dsk_reg[] = {\r |
| 111 | { BRDATA (BUF, dsk_buf, 8, 24, DSK_NUMWD) },\r |
| 112 | { DRDATA (BPTR, dsk_bptr, 9), PV_LEFT },\r |
| 113 | { DRDATA (BLNT, dsk_bptr, 9), PV_LEFT },\r |
| 114 | { ORDATA (DA, dsk_da, 21) },\r |
| 115 | { ORDATA (INST, dsk_op, 24) },\r |
| 116 | { FLDATA (XFR, xfr_req, XFR_V_DSK) },\r |
| 117 | { FLDATA (ERR, dsk_err, 0) },\r |
| 118 | { DRDATA (WTIME, dsk_time, 24), REG_NZ + PV_LEFT },\r |
| 119 | { DRDATA (STIME, dsk_stime,24), REG_NZ + PV_LEFT },\r |
| 120 | { FLDATA (STOP_IOE, dsk_stopioe, 0) },\r |
| 121 | { NULL }\r |
| 122 | };\r |
| 123 | \r |
| 124 | MTAB dsk_mod[] = {\r |
| 125 | { UNIT_WLK, 0, "write enabled", "WRITEENABLED", NULL },\r |
| 126 | { UNIT_WLK, UNIT_WLK, "write locked", "LOCKED", NULL },\r |
| 127 | { MTAB_XTD|MTAB_VDV, 0, "CHANNEL", "CHANNEL",\r |
| 128 | &set_chan, &show_chan, NULL },\r |
| 129 | { 0 }\r |
| 130 | };\r |
| 131 | \r |
| 132 | DEVICE dsk_dev = {\r |
| 133 | "DSK", &dsk_unit, dsk_reg, dsk_mod,\r |
| 134 | 1, 8, 24, 1, 8, 27,\r |
| 135 | NULL, NULL, &dsk_reset,\r |
| 136 | NULL, NULL, NULL,\r |
| 137 | &dsk_dib, DEV_DISABLE\r |
| 138 | };\r |
| 139 | \r |
| 140 | /* Moving head disk routine\r |
| 141 | \r |
| 142 | conn - inst = EOM0, dat = NULL\r |
| 143 | eom1 - inst = EOM1, dat = NULL\r |
| 144 | sks - inst = SKS, dat = ptr to result\r |
| 145 | disc - inst = device number, dat = NULL\r |
| 146 | wreor - inst = device number, dat = NULL\r |
| 147 | read - inst = device number, dat = ptr to data\r |
| 148 | write - inst = device number, dat = ptr to result\r |
| 149 | */\r |
| 150 | \r |
| 151 | t_stat dsk (uint32 fnc, uint32 inst, uint32 *dat)\r |
| 152 | {\r |
| 153 | int32 i, t, new_ch, dsk_wptr, dsk_byte;\r |
| 154 | t_stat r;\r |
| 155 | \r |
| 156 | switch (fnc) { /* case on function */\r |
| 157 | \r |
| 158 | case IO_CONN: /* connect */\r |
| 159 | new_ch = I_GETEOCH (inst); /* get new chan */\r |
| 160 | if (new_ch != dsk_dib.chan) return SCPE_IERR; /* wrong chan? */\r |
| 161 | dsk_op = inst; /* save instr */\r |
| 162 | dsk_bptr = dsk_blnt = 0; /* init ptrs */\r |
| 163 | for (i = 0; i < DSK_NUMWD; i++) dsk_buf[i] = 0; /* clear buffer */\r |
| 164 | xfr_req = xfr_req & ~XFR_DSK; /* clr xfr flg */\r |
| 165 | sim_activate (&dsk_unit, dsk_stime); /* activate */\r |
| 166 | break;\r |
| 167 | \r |
| 168 | case IO_EOM1: /* EOM mode 1 */\r |
| 169 | new_ch = I_GETEOCH (inst); /* get new chan */\r |
| 170 | if (new_ch != dsk_dib.chan) return SCPE_IERR; /* wrong chan? */\r |
| 171 | if (inst & 07600) CRETIOP; /* inv inst? */\r |
| 172 | alert = POT_DSK; /* alert */\r |
| 173 | break;\r |
| 174 | \r |
| 175 | case IO_DISC: /* disconnect */\r |
| 176 | dsk_end_op (0); /* normal term */\r |
| 177 | if (inst & DEV_OUT) return dsk_fill (inst); /* fill write */\r |
| 178 | break;\r |
| 179 | \r |
| 180 | case IO_WREOR: /* write eor */\r |
| 181 | dsk_end_op (CHF_EOR); /* eor term */\r |
| 182 | return dsk_fill (inst); /* fill write */\r |
| 183 | \r |
| 184 | case IO_SKS: /* SKS */\r |
| 185 | new_ch = I_GETSKCH (inst); /* sks chan */\r |
| 186 | if (new_ch != dsk_dib.chan) return SCPE_IERR; /* wrong chan? */\r |
| 187 | t = I_GETSKCND (inst); /* sks cond */\r |
| 188 | if (((t == 000) && !sim_is_active (&dsk_unit) &&/* 10026: ready */\r |
| 189 | (dsk_unit.flags & UNIT_ATT)) ||\r |
| 190 | ((t == 004) && !dsk_err && /* 11026: !err */\r |
| 191 | (dsk_unit.flags & UNIT_ATT)) ||\r |
| 192 | ((t == 010) && ((dsk_unit.cyl & DSK_SIP) == 0)) || /* 12026: on trk */\r |
| 193 | ((t == 014) && !(dsk_unit.flags & UNIT_WPRT)) || /* 13026: !wrprot */\r |
| 194 | ((t == 001) && (dsk_unit.flags & UNIT_ATT))) /* 10226: online */\r |
| 195 | *dat = 1;\r |
| 196 | break;\r |
| 197 | \r |
| 198 | case IO_READ:\r |
| 199 | xfr_req = xfr_req & ~XFR_DSK; /* clr xfr req */\r |
| 200 | if (dsk_bptr >= dsk_blnt) { /* no more data? */\r |
| 201 | if (r = dsk_read_buf (inst)) return r; /* read sector */\r |
| 202 | }\r |
| 203 | dsk_wptr = dsk_bptr >> 2; /* word pointer */\r |
| 204 | dsk_byte = dsk_bptr & 03; /* byte in word */\r |
| 205 | *dat = (dsk_buf[dsk_wptr] >> ((3 - dsk_byte) * 6)) & 077;\r |
| 206 | dsk_bptr = dsk_bptr + 1; /* incr buf ptr */\r |
| 207 | if ((dsk_bptr >= dsk_blnt) && /* end sector, */\r |
| 208 | ((dsk_op & CHC_BIN) || DSK_ENDCHN (dsk_buf[0])))/* sec mode | eoch? */\r |
| 209 | dsk_end_op (CHF_EOR); /* eor term */\r |
| 210 | break;\r |
| 211 | \r |
| 212 | case IO_WRITE:\r |
| 213 | xfr_req = xfr_req & ~XFR_DSK; /* clr xfr req */\r |
| 214 | if (dsk_bptr >= (DSK_NUMWD * 4)) { /* full? */\r |
| 215 | if (r = dsk_write_buf (inst)) return r; /* write sector */\r |
| 216 | }\r |
| 217 | dsk_wptr = dsk_bptr >> 2; /* word pointer */\r |
| 218 | dsk_buf[dsk_wptr] = ((dsk_buf[dsk_wptr] << 6) | (*dat & 077)) & DMASK;\r |
| 219 | dsk_bptr = dsk_bptr + 1; /* incr buf ptr */\r |
| 220 | break;\r |
| 221 | \r |
| 222 | default:\r |
| 223 | CRETINS;\r |
| 224 | }\r |
| 225 | \r |
| 226 | return SCPE_OK; \r |
| 227 | }\r |
| 228 | \r |
| 229 | /* PIN routine - return disk address */\r |
| 230 | \r |
| 231 | t_stat pin_dsk (uint32 num, uint32 *dat)\r |
| 232 | {\r |
| 233 | *dat = dsk_da; /* ret disk addr */\r |
| 234 | return SCPE_OK;\r |
| 235 | }\r |
| 236 | \r |
| 237 | /* POT routine - start seek */\r |
| 238 | \r |
| 239 | t_stat pot_dsk (uint32 num, uint32 *dat)\r |
| 240 | {\r |
| 241 | int32 st;\r |
| 242 | \r |
| 243 | if (sim_is_active (&dsk_unit)) return STOP_IONRDY; /* busy? wait */\r |
| 244 | dsk_da = (*dat) & DSK_AMASK; /* save dsk addr */\r |
| 245 | st = abs (DSK_GETTR (dsk_da) - /* calc seek time */\r |
| 246 | (dsk_unit.cyl & DSK_M_TR)) * dsk_stime;\r |
| 247 | if (st == 0) st = dsk_stime; /* min time */\r |
| 248 | sim_activate (&dsk_unit, st); /* set timer */\r |
| 249 | dsk_unit.cyl = dsk_unit.cyl | DSK_SIP; /* seeking */\r |
| 250 | return SCPE_OK;\r |
| 251 | }\r |
| 252 | \r |
| 253 | /* Unit service and read/write */\r |
| 254 | \r |
| 255 | t_stat dsk_svc (UNIT *uptr)\r |
| 256 | {\r |
| 257 | if (uptr->cyl & DSK_SIP) { /* end seek? */\r |
| 258 | uptr->cyl = DSK_GETTR (dsk_da); /* on cylinder */\r |
| 259 | if (dsk_op) sim_activate (&dsk_unit, dsk_stime); /* sched r/w */\r |
| 260 | }\r |
| 261 | else {\r |
| 262 | xfr_req = xfr_req | XFR_DSK; /* set xfr req */\r |
| 263 | sim_activate (&dsk_unit, dsk_time); /* activate */\r |
| 264 | }\r |
| 265 | return SCPE_OK;\r |
| 266 | }\r |
| 267 | \r |
| 268 | /* Read sector */\r |
| 269 | \r |
| 270 | t_stat dsk_read_buf (uint32 dev)\r |
| 271 | {\r |
| 272 | int32 da, pkts, awc;\r |
| 273 | \r |
| 274 | if ((dsk_unit.flags & UNIT_ATT) == 0) { /* !attached? */\r |
| 275 | dsk_end_op (CHF_ERR | CHF_EOR); /* disk error */\r |
| 276 | CRETIOE (dsk_stopioe, SCPE_UNATT);\r |
| 277 | }\r |
| 278 | da = dsk_da * DSK_NUMWD * sizeof (uint32);\r |
| 279 | fseek (dsk_unit.fileref, da, SEEK_SET); /* locate sector */\r |
| 280 | awc = fxread (dsk_buf, sizeof (uint32), DSK_NUMWD, dsk_unit.fileref);\r |
| 281 | if (ferror (dsk_unit.fileref)) { /* error? */\r |
| 282 | dsk_end_op (CHF_ERR | CHF_EOR); /* disk error */\r |
| 283 | return SCPE_IOERR;\r |
| 284 | }\r |
| 285 | for ( ; awc < DSK_NUMWD; awc++) dsk_buf[awc] = 0;\r |
| 286 | pkts = DSK_GETPKT (dsk_buf[0]); /* get packets */\r |
| 287 | dsk_blnt = pkts * DSK_PKTWD * 4; /* new buf size */\r |
| 288 | dsk_bptr = 0; /* init bptr */\r |
| 289 | dsk_da = (dsk_da + 1) & DSK_AMASK; /* incr disk addr */\r |
| 290 | return SCPE_OK;\r |
| 291 | }\r |
| 292 | \r |
| 293 | /* Write sector. If this routine is called directly, then the sector\r |
| 294 | buffer is full, and there is at least one more character to write;\r |
| 295 | therefore, there are 4 packets in the sector, and the sector is not\r |
| 296 | the end of the chain.\r |
| 297 | */\r |
| 298 | \r |
| 299 | t_stat dsk_write_buf (uint32 dev)\r |
| 300 | {\r |
| 301 | int32 i, da;\r |
| 302 | \r |
| 303 | if ((dsk_unit.flags & UNIT_ATT) == 0) { /* !attached? */\r |
| 304 | dsk_end_op (CHF_ERR | CHF_EOR); /* disk error */\r |
| 305 | CRETIOE (dsk_stopioe, SCPE_UNATT);\r |
| 306 | }\r |
| 307 | if (dsk_unit.flags & UNIT_WPRT) { /* write prot? */\r |
| 308 | dsk_end_op (CHF_ERR | CHF_EOR); /* disk error */\r |
| 309 | return SCPE_OK;\r |
| 310 | }\r |
| 311 | da = dsk_da * DSK_NUMWD * sizeof (uint32);\r |
| 312 | fseek (dsk_unit.fileref, da, SEEK_SET); /* locate sector */\r |
| 313 | fxwrite (dsk_buf, sizeof (uint32), DSK_NUMWD, dsk_unit.fileref);\r |
| 314 | if (ferror (dsk_unit.fileref)) { /* error? */\r |
| 315 | dsk_end_op (CHF_ERR | CHF_EOR); /* disk error */\r |
| 316 | return SCPE_IOERR;\r |
| 317 | }\r |
| 318 | dsk_bptr = 0; /* init bptr */\r |
| 319 | dsk_da = (dsk_da + 1) & DSK_AMASK; /* incr disk addr */\r |
| 320 | for (i = 0; i < DSK_NUMWD; i++) dsk_buf[i] = 0; /* clear buffer */\r |
| 321 | return SCPE_OK;\r |
| 322 | }\r |
| 323 | \r |
| 324 | /* Fill incomplete sector at end of operation. Calculate the number\r |
| 325 | of packets and set the end of chain flag.\r |
| 326 | */\r |
| 327 | \r |
| 328 | t_stat dsk_fill (uint32 dev)\r |
| 329 | {\r |
| 330 | int32 nochn = (dsk_op & CHC_BIN)? 0: 1; /* chain? */\r |
| 331 | int32 pktend = (dsk_bptr + ((DSK_PKTWD * 4) - 1)) & /* end pkt */\r |
| 332 | ~((DSK_PKTWD * 4) - 1);\r |
| 333 | int32 pkts = pktend / (DSK_PKTWD * 4); /* # packets */\r |
| 334 | \r |
| 335 | if (dsk_bptr == 0) return SCPE_OK; /* no fill? */\r |
| 336 | for ( ; dsk_bptr < pktend; dsk_bptr++) { /* fill packet */\r |
| 337 | int32 dsk_wptr = dsk_bptr >> 2;\r |
| 338 | dsk_buf[dsk_wptr] = (dsk_buf[dsk_wptr] << 6) & DMASK;\r |
| 339 | }\r |
| 340 | dsk_buf[0] = dsk_buf[0] | (nochn << DSK_V_CHN) | /* insert chain, */\r |
| 341 | ((4 - pkts) << DSK_V_PKT); /* num pkts */\r |
| 342 | return dsk_write_buf (dev); /* write sec */\r |
| 343 | }\r |
| 344 | \r |
| 345 | /* Terminate DSK operation */\r |
| 346 | \r |
| 347 | void dsk_end_op (uint32 fl)\r |
| 348 | {\r |
| 349 | if (fl) chan_set_flag (dsk_dib.chan, fl); /* set flags */\r |
| 350 | dsk_op = 0; /* clear op */\r |
| 351 | xfr_req = xfr_req & ~XFR_DSK; /* clear xfr */\r |
| 352 | sim_cancel (&dsk_unit); /* stop */\r |
| 353 | if (fl & CHF_ERR) { /* error? */\r |
| 354 | chan_disc (dsk_dib.chan); /* disconnect */\r |
| 355 | dsk_err = 1; /* set disk err */\r |
| 356 | }\r |
| 357 | return;\r |
| 358 | }\r |
| 359 | \r |
| 360 | /* Reset routine */\r |
| 361 | \r |
| 362 | t_stat dsk_reset (DEVICE *dptr)\r |
| 363 | {\r |
| 364 | int32 i;\r |
| 365 | \r |
| 366 | chan_disc (dsk_dib.chan); /* disconnect */\r |
| 367 | dsk_da = 0; /* clear state */\r |
| 368 | dsk_op = 0;\r |
| 369 | dsk_err = 0;\r |
| 370 | dsk_bptr = dsk_blnt = 0;\r |
| 371 | xfr_req = xfr_req & ~XFR_DSK; /* clr xfr req */\r |
| 372 | sim_cancel (&dsk_unit); /* deactivate */\r |
| 373 | dsk_unit.cyl = 0;\r |
| 374 | for (i = 0; i < DSK_NUMWD; i++) dsk_buf[i] = 0; /* clear buffer */\r |
| 375 | return SCPE_OK;\r |
| 376 | }\r |