First Commit of my working state
[simh.git] / PDP8 / pdp8_rk.c
1 /* pdp8_rk.c: RK8E cartridge disk simulator
2
3 Copyright (c) 1993-2005, Robert M Supnik
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 Except as contained in this notice, the name of Robert M Supnik shall not be
23 used in advertising or otherwise to promote the sale, use or other dealings
24 in this Software without prior written authorization from Robert M Supnik.
25
26 rk RK8E/RK05 cartridge disk
27
28 25-Apr-03 RMS Revised for extended file support
29 04-Oct-02 RMS Added DIB, device number support
30 06-Jan-02 RMS Changed enable/disable support
31 30-Nov-01 RMS Added read only unit, extended SET/SHOW support
32 24-Nov-01 RMS Converted FLG to array, made register names consistent
33 25-Apr-01 RMS Added device enable/disable support
34 29-Jun-96 RMS Added unit enable/disable support
35 */
36
37 #include "pdp8_defs.h"
38
39 /* Constants */
40
41 #define RK_NUMSC 16 /* sectors/surface */
42 #define RK_NUMSF 2 /* surfaces/cylinder */
43 #define RK_NUMCY 203 /* cylinders/drive */
44 #define RK_NUMWD 256 /* words/sector */
45 #define RK_SIZE (RK_NUMCY * RK_NUMSF * RK_NUMSC * RK_NUMWD)
46 /* words/drive */
47 #define RK_NUMDR 4 /* drives/controller */
48 #define RK_M_NUMDR 03
49
50 /* Flags in the unit flags word */
51
52 #define UNIT_V_HWLK (UNIT_V_UF + 0) /* hwre write lock */
53 #define UNIT_V_SWLK (UNIT_V_UF + 1) /* swre write lock */
54 #define UNIT_HWLK (1 << UNIT_V_HWLK)
55 #define UNIT_SWLK (1 << UNIT_V_SWLK)
56 #define UNIT_WPRT (UNIT_HWLK|UNIT_SWLK|UNIT_RO) /* write protect */
57
58 /* Parameters in the unit descriptor */
59
60 #define CYL u3 /* current cylinder */
61 #define FUNC u4 /* function */
62
63 /* Status register */
64
65 #define RKS_DONE 04000 /* transfer done */
66 #define RKS_HMOV 02000 /* heads moving */
67 #define RKS_SKFL 00400 /* drive seek fail */
68 #define RKS_NRDY 00200 /* drive not ready */
69 #define RKS_BUSY 00100 /* control busy error */
70 #define RKS_TMO 00040 /* timeout error */
71 #define RKS_WLK 00020 /* write lock error */
72 #define RKS_CRC 00010 /* CRC error */
73 #define RKS_DLT 00004 /* data late error */
74 #define RKS_STAT 00002 /* drive status error */
75 #define RKS_CYL 00001 /* cyl address error */
76 #define RKS_ERR (RKS_BUSY+RKS_TMO+RKS_WLK+RKS_CRC+RKS_DLT+RKS_STAT+RKS_CYL)
77
78 /* Command register */
79
80 #define RKC_M_FUNC 07 /* function */
81 #define RKC_READ 0
82 #define RKC_RALL 1
83 #define RKC_WLK 2
84 #define RKC_SEEK 3
85 #define RKC_WRITE 4
86 #define RKC_WALL 5
87 #define RKC_V_FUNC 9
88 #define RKC_IE 00400 /* interrupt enable */
89 #define RKC_SKDN 00200 /* set done on seek done */
90 #define RKC_HALF 00100 /* 128W sector */
91 #define RKC_MEX 00070 /* memory extension */
92 #define RKC_V_MEX 3
93 #define RKC_M_DRV 03 /* drive select */
94 #define RKC_V_DRV 1
95 #define RKC_CYHI 00001 /* high cylinder addr */
96
97 #define GET_FUNC(x) (((x) >> RKC_V_FUNC) & RKC_M_FUNC)
98 #define GET_DRIVE(x) (((x) >> RKC_V_DRV) & RKC_M_DRV)
99 #define GET_MEX(x) (((x) & RKC_MEX) << (12 - RKC_V_MEX))
100
101 /* Disk address */
102
103 #define RKD_V_SECT 0 /* sector */
104 #define RKD_M_SECT 017
105 #define RKD_V_SUR 4 /* surface */
106 #define RKD_M_SUR 01
107 #define RKD_V_CYL 5 /* cylinder */
108 #define RKD_M_CYL 0177
109 #define GET_CYL(x,y) ((((x) & RKC_CYHI) << (12-RKD_V_CYL)) | \
110 (((y) >> RKD_V_CYL) & RKD_M_CYL))
111 #define GET_DA(x,y) ((((x) & RKC_CYHI) << 12) | y)
112
113 /* Reset commands */
114
115 #define RKX_CLS 0 /* clear status */
116 #define RKX_CLC 1 /* clear control */
117 #define RKX_CLD 2 /* clear drive */
118 #define RKX_CLSA 3 /* clear status alt */
119
120 #define RK_INT_UPDATE if (((rk_sta & (RKS_DONE + RKS_ERR)) != 0) && \
121 ((rk_cmd & RKC_IE) != 0)) \
122 int_req = int_req | INT_RK; \
123 else int_req = int_req & ~INT_RK
124 #define RK_MIN 10
125 #define MAX(x,y) (((x) > (y))? (x): (y))
126
127 extern uint16 M[];
128 extern int32 int_req, stop_inst;
129 extern UNIT cpu_unit;
130
131 int32 rk_busy = 0; /* controller busy */
132 int32 rk_sta = 0; /* status register */
133 int32 rk_cmd = 0; /* command register */
134 int32 rk_da = 0; /* disk address */
135 int32 rk_ma = 0; /* memory address */
136 int32 rk_swait = 10, rk_rwait = 10; /* seek, rotate wait */
137 int32 rk_stopioe = 1; /* stop on error */
138
139 DEVICE rk_dev;
140 int32 rk (int32 IR, int32 AC);
141 t_stat rk_svc (UNIT *uptr);
142 t_stat rk_reset (DEVICE *dptr);
143 t_stat rk_boot (int32 unitno, DEVICE *dptr);
144 void rk_go (int32 function, int32 cylinder);
145
146 /* RK-8E data structures
147
148 rk_dev RK device descriptor
149 rk_unit RK unit list
150 rk_reg RK register list
151 rk_mod RK modifiers list
152 */
153
154 DIB rk_dib = { DEV_RK, 1, { &rk } };
155
156 UNIT rk_unit[] = {
157 { UDATA (&rk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
158 UNIT_ROABLE, RK_SIZE) },
159 { UDATA (&rk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
160 UNIT_ROABLE, RK_SIZE) },
161 { UDATA (&rk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
162 UNIT_ROABLE, RK_SIZE) },
163 { UDATA (&rk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE+
164 UNIT_ROABLE, RK_SIZE) }
165 };
166
167 REG rk_reg[] = {
168 { ORDATA (RKSTA, rk_sta, 12) },
169 { ORDATA (RKCMD, rk_cmd, 12) },
170 { ORDATA (RKDA, rk_da, 12) },
171 { ORDATA (RKMA, rk_ma, 12) },
172 { FLDATA (BUSY, rk_busy, 0) },
173 { FLDATA (INT, int_req, INT_V_RK) },
174 { DRDATA (STIME, rk_swait, 24), PV_LEFT },
175 { DRDATA (RTIME, rk_rwait, 24), PV_LEFT },
176 { FLDATA (STOP_IOE, rk_stopioe, 0) },
177 { ORDATA (DEVNUM, rk_dib.dev, 6), REG_HRO },
178 { NULL }
179 };
180
181 MTAB rk_mod[] = {
182 { UNIT_HWLK, 0, "write enabled", "WRITEENABLED", NULL },
183 { UNIT_HWLK, UNIT_HWLK, "write locked", "LOCKED", NULL },
184 { MTAB_XTD|MTAB_VDV, 0, "DEVNO", "DEVNO",
185 &set_dev, &show_dev, NULL },
186 { 0 }
187 };
188
189 DEVICE rk_dev = {
190 "RK", rk_unit, rk_reg, rk_mod,
191 RK_NUMDR, 8, 24, 1, 8, 12,
192 NULL, NULL, &rk_reset,
193 &rk_boot, NULL, NULL,
194 &rk_dib, DEV_DISABLE
195 };
196
197 /* IOT routine */
198
199 int32 rk (int32 IR, int32 AC)
200 {
201 int32 i;
202 UNIT *uptr;
203
204 switch (IR & 07) { /* decode IR<9:11> */
205
206 case 0: /* unused */
207 return (stop_inst << IOT_V_REASON) + AC;
208
209 case 1: /* DSKP */
210 return (rk_sta & (RKS_DONE + RKS_ERR))? /* skip on done, err */
211 IOT_SKP + AC: AC;
212
213 case 2: /* DCLR */
214 rk_sta = 0; /* clear status */
215 switch (AC & 03) { /* decode AC<10:11> */
216
217 case RKX_CLS: /* clear status */
218 if (rk_busy != 0) rk_sta = rk_sta | RKS_BUSY;
219 case RKX_CLSA: /* clear status alt */
220 break;
221
222 case RKX_CLC: /* clear control */
223 rk_cmd = rk_busy = 0; /* clear registers */
224 rk_ma = rk_da = 0;
225 for (i = 0; i < RK_NUMDR; i++) sim_cancel (&rk_unit[i]);
226 break;
227
228 case RKX_CLD: /* reset drive */
229 if (rk_busy != 0) rk_sta = rk_sta | RKS_BUSY;
230 else rk_go (RKC_SEEK, 0); /* seek to 0 */
231 break;
232 } /* end switch AC */
233 break;
234
235 case 3: /* DLAG */
236 if (rk_busy != 0) rk_sta = rk_sta | RKS_BUSY;
237 else {
238 rk_da = AC; /* load disk addr */
239 rk_go (GET_FUNC (rk_cmd), GET_CYL (rk_cmd, rk_da));
240 }
241 break;
242
243 case 4: /* DLCA */
244 if (rk_busy != 0) rk_sta = rk_sta | RKS_BUSY;
245 else rk_ma = AC; /* load curr addr */
246 break;
247
248 case 5: /* DRST */
249 uptr = rk_dev.units + GET_DRIVE (rk_cmd); /* selected unit */
250 rk_sta = rk_sta & ~(RKS_HMOV + RKS_NRDY); /* clear dynamic */
251 if ((uptr->flags & UNIT_ATT) == 0) rk_sta = rk_sta | RKS_NRDY;
252 if (sim_is_active (uptr)) rk_sta = rk_sta | RKS_HMOV;
253 return rk_sta;
254
255 case 6: /* DLDC */
256 if (rk_busy != 0) rk_sta = rk_sta | RKS_BUSY;
257 else {
258 rk_cmd = AC; /* load command */
259 rk_sta = 0; /* clear status */
260 }
261 break;
262
263 case 7: /* DMAN */
264 break;
265 } /* end case pulse */
266
267 RK_INT_UPDATE; /* update int req */
268 return 0; /* clear AC */
269 }
270
271 /* Initiate new function
272
273 Called with function, cylinder, to allow recalibrate as well as
274 load and go to be processed by this routine.
275
276 Assumes that the controller is idle, and that updating of interrupt
277 request will be done by the caller.
278 */
279
280 void rk_go (int32 func, int32 cyl)
281 {
282 int32 t;
283 UNIT *uptr;
284
285 if (func == RKC_RALL) func = RKC_READ; /* all? use standard */
286 if (func == RKC_WALL) func = RKC_WRITE;
287 uptr = rk_dev.units + GET_DRIVE (rk_cmd); /* selected unit */
288 if ((uptr->flags & UNIT_ATT) == 0) { /* not attached? */
289 rk_sta = rk_sta | RKS_DONE | RKS_NRDY | RKS_STAT;
290 return;
291 }
292 if (sim_is_active (uptr) || (cyl >= RK_NUMCY)) { /* busy or bad cyl? */
293 rk_sta = rk_sta | RKS_DONE | RKS_STAT;
294 return;
295 }
296 if ((func == RKC_WRITE) && (uptr->flags & UNIT_WPRT)) {
297 rk_sta = rk_sta | RKS_DONE | RKS_WLK; /* write and locked? */
298 return;
299 }
300 if (func == RKC_WLK) { /* write lock? */
301 uptr->flags = uptr->flags | UNIT_SWLK;
302 rk_sta = rk_sta | RKS_DONE;
303 return;
304 }
305 t = abs (cyl - uptr->CYL) * rk_swait; /* seek time */
306 if (func == RKC_SEEK) { /* seek? */
307 sim_activate (uptr, MAX (RK_MIN, t)); /* schedule */
308 rk_sta = rk_sta | RKS_DONE; /* set done */
309 }
310 else {
311 sim_activate (uptr, t + rk_rwait); /* schedule */
312 rk_busy = 1; /* set busy */
313 }
314 uptr->FUNC = func; /* save func */
315 uptr->CYL = cyl; /* put on cylinder */
316 return;
317 }
318
319 /* Unit service
320
321 If seek, complete seek command
322 Else complete data transfer command
323
324 The unit control block contains the function and cylinder address for
325 the current command.
326
327 Note that memory addresses wrap around in the current field.
328 */
329
330 static uint16 fill[RK_NUMWD/2] = { 0 };
331 t_stat rk_svc (UNIT *uptr)
332 {
333 int32 err, wc, wc1, awc, swc, pa, da;
334 UNIT *seluptr;
335
336 if (uptr->FUNC == RKC_SEEK) { /* seek? */
337 seluptr = rk_dev.units + GET_DRIVE (rk_cmd); /* see if selected */
338 if ((uptr == seluptr) && ((rk_cmd & RKC_SKDN) != 0)) {
339 rk_sta = rk_sta | RKS_DONE;
340 RK_INT_UPDATE;
341 }
342 return SCPE_OK;
343 }
344
345 if ((uptr->flags & UNIT_ATT) == 0) { /* not att? abort */
346 rk_sta = rk_sta | RKS_DONE | RKS_NRDY | RKS_STAT;
347 rk_busy = 0;
348 RK_INT_UPDATE;
349 return IORETURN (rk_stopioe, SCPE_UNATT);
350 }
351
352 if ((uptr->FUNC == RKC_WRITE) && (uptr->flags & UNIT_WPRT)) {
353 rk_sta = rk_sta | RKS_DONE | RKS_WLK; /* write and locked? */
354 rk_busy = 0;
355 RK_INT_UPDATE;
356 return SCPE_OK;
357 }
358
359 pa = GET_MEX (rk_cmd) | rk_ma; /* phys address */
360 da = GET_DA (rk_cmd, rk_da) * RK_NUMWD * sizeof (int16);/* disk address */
361 swc = wc = (rk_cmd & RKC_HALF)? RK_NUMWD / 2: RK_NUMWD; /* get transfer size */
362 if ((wc1 = ((rk_ma + wc) - 010000)) > 0) wc = wc - wc1; /* if wrap, limit */
363 err = fseek (uptr->fileref, da, SEEK_SET); /* locate sector */
364
365 if ((uptr->FUNC == RKC_READ) && (err == 0) && MEM_ADDR_OK (pa)) { /* read? */
366 awc = fxread (&M[pa], sizeof (int16), wc, uptr->fileref);
367 for ( ; awc < wc; awc++) M[pa + awc] = 0; /* fill if eof */
368 err = ferror (uptr->fileref);
369 if ((wc1 > 0) && (err == 0)) { /* field wraparound? */
370 pa = pa & 070000; /* wrap phys addr */
371 awc = fxread (&M[pa], sizeof (int16), wc1, uptr->fileref);
372 for ( ; awc < wc1; awc++) M[pa + awc] = 0; /* fill if eof */
373 err = ferror (uptr->fileref);
374 }
375 }
376
377 if ((uptr->FUNC == RKC_WRITE) && (err == 0)) { /* write? */
378 fxwrite (&M[pa], sizeof (int16), wc, uptr->fileref);
379 err = ferror (uptr->fileref);
380 if ((wc1 > 0) && (err == 0)) { /* field wraparound? */
381 pa = pa & 070000; /* wrap phys addr */
382 fxwrite (&M[pa], sizeof (int16), wc1, uptr->fileref);
383 err = ferror (uptr->fileref);
384 }
385 if ((rk_cmd & RKC_HALF) && (err == 0)) { /* fill half sector */
386 fxwrite (fill, sizeof (int16), RK_NUMWD/2, uptr->fileref);
387 err = ferror (uptr->fileref);
388 }
389 }
390
391 rk_ma = (rk_ma + swc) & 07777; /* incr mem addr reg */
392 rk_sta = rk_sta | RKS_DONE; /* set done */
393 rk_busy = 0;
394 RK_INT_UPDATE;
395
396 if (err != 0) {
397 perror ("RK I/O error");
398 clearerr (uptr->fileref);
399 return SCPE_IOERR;
400 }
401 return SCPE_OK;
402 }
403
404 /* Reset routine */
405
406 t_stat rk_reset (DEVICE *dptr)
407 {
408 int32 i;
409 UNIT *uptr;
410
411 rk_cmd = rk_ma = rk_da = rk_sta = rk_busy = 0;
412 int_req = int_req & ~INT_RK; /* clear interrupt */
413 for (i = 0; i < RK_NUMDR; i++) { /* stop all units */
414 uptr = rk_dev.units + i;
415 sim_cancel (uptr);
416 uptr->flags = uptr->flags & ~UNIT_SWLK;
417 uptr->CYL = uptr->FUNC = 0;
418 }
419 return SCPE_OK;
420 }
421
422 /* Bootstrap routine */
423
424 #define BOOT_START 023
425 #define BOOT_UNIT 032
426 #define BOOT_LEN (sizeof (boot_rom) / sizeof (int16))
427
428 static const uint16 boot_rom[] = {
429 06007, /* 23, CAF */
430 06744, /* 24, DLCA ; addr = 0 */
431 01032, /* 25, TAD UNIT ; unit no */
432 06746, /* 26, DLDC ; command, unit */
433 06743, /* 27, DLAG ; disk addr, go */
434 01032, /* 30, TAD UNIT ; unit no, for OS */
435 05031, /* 31, JMP . */
436 00000 /* UNIT, 0 ; in bits <9:10> */
437 };
438
439 t_stat rk_boot (int32 unitno, DEVICE *dptr)
440 {
441 int32 i;
442 extern int32 saved_PC;
443
444 if (rk_dib.dev != DEV_RK) return STOP_NOTSTD; /* only std devno */
445 for (i = 0; i < BOOT_LEN; i++) M[BOOT_START + i] = boot_rom[i];
446 M[BOOT_UNIT] = (unitno & RK_M_NUMDR) << 1;
447 saved_PC = BOOT_START;
448 return SCPE_OK;
449 }