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