Commit | Line | Data |
---|---|---|
196ba1fc PH |
1 | /* ibm1130_disk.c: IBM 1130 disk IO simulator\r |
2 | \r | |
3 | NOTE - there is a problem with this code. The Device Status Word (DSW) is\r | |
4 | computed from current conditions when requested by an XIO load status\r | |
5 | command; the value of DSW available to the simulator's examine & save\r | |
6 | commands may NOT be accurate. This should probably be fixed.\r | |
7 | \r | |
8 | Based on the SIMH package written by Robert M Supnik\r | |
9 | \r | |
10 | * (C) Copyright 2002, Brian Knittel.\r | |
11 | * You may freely use this program, but: it offered strictly on an AS-IS, AT YOUR OWN\r | |
12 | * RISK basis, there is no warranty of fitness for any purpose, and the rest of the\r | |
13 | * usual yada-yada. Please keep this notice and the copyright in any distributions\r | |
14 | * or modifications.\r | |
15 | *\r | |
16 | * Revision History\r | |
17 | * 05-dec-06 Added cgiwritable mode\r | |
18 | *\r | |
19 | * 19-Dec-05 We no longer issue an operation complete interrupt if an INITR, INITW\r | |
20 | * or CONTROL operation is attemped on a drive that is not online. DATA_ERROR\r | |
21 | * is now only indicated in the DSW when \r | |
22 | *\r | |
23 | * 02-Nov-04 Addes -s option to boot to leave switches alone.\r | |
24 | * 15-jun-03 moved actual read on XIO read to end of time interval,\r | |
25 | * as the APL boot card required 2 instructions to run between the\r | |
26 | * time read was initiated and the time the data was read (a jump and a wait)\r | |
27 | *\r | |
28 | * 01-sep-02 corrected treatment of -m and -r flags in dsk_attach\r | |
29 | * in cgi mode, so that file is opened readonly but emulated\r | |
30 | * disk is writable.\r | |
31 | *\r | |
32 | * This is not a supported product, but I welcome bug reports and fixes.\r | |
33 | * Mail to simh@ibm1130.org\r | |
34 | */\r | |
35 | \r | |
36 | #include "ibm1130_defs.h"\r | |
37 | #include "memory.h"\r | |
38 | \r | |
39 | #define TRACE_DMS_IO /* define to enable debug of DMS phase IO */\r | |
40 | \r | |
41 | #ifdef TRACE_DMS_IO\r | |
42 | extern int32 sim_switches;\r | |
43 | extern int32 sim_quiet;\r | |
44 | static int trace_dms = 0;\r | |
45 | static void tracesector (int iswrite, int nwords, int addr, int sector);\r | |
46 | static t_stat where_cmd (int flag, char *ptr);\r | |
47 | static t_stat phdebug_cmd (int flag, char *ptr);\r | |
48 | static t_stat fdump_cmd (int flags, char *cptr);\r | |
49 | static void enable_dms_tracing (int newsetting);\r | |
50 | #endif\r | |
51 | \r | |
52 | /* Constants */\r | |
53 | \r | |
54 | #define DSK_NUMWD 321 /* words/sector */\r | |
55 | #define DSK_NUMSC 4 /* sectors/surface */\r | |
56 | #define DSK_NUMSF 2 /* surfaces/cylinder */\r | |
57 | #define DSK_NUMCY 203 /* cylinders/drive */\r | |
58 | #define DSK_NUMTR (DSK_NUMCY * DSK_NUMSF) /* tracks/drive */\r | |
59 | #define DSK_NUMDR 5 /* drives/controller */\r | |
60 | #define DSK_SIZE (DSK_NUMCY * DSK_NUMSF * DSK_NUMSC * DSK_NUMWD) /* words/drive */\r | |
61 | \r | |
62 | #define UNIT_V_RONLY (UNIT_V_UF + 0) /* hwre write lock */\r | |
63 | #define UNIT_V_OPERR (UNIT_V_UF + 1) /* operation error flag */\r | |
64 | #define UNIT_V_HARDERR (UNIT_V_UF + 2) /* hard error flag (reset on power down) */\r | |
65 | #define UNIT_RONLY (1u << UNIT_V_RONLY)\r | |
66 | #define UNIT_OPERR (1u << UNIT_V_OPERR)\r | |
67 | #define UNIT_HARDERR (1u << UNIT_V_HARDERR)\r | |
68 | \r | |
69 | #define MEM_MAPPED(uptr) (uptr->flags & UNIT_BUF) /* disk buffered in memory */\r | |
70 | \r | |
71 | #define IO_NONE 0 /* last operation, used to ensure fseek between read and write */\r | |
72 | #define IO_READ 1\r | |
73 | #define IO_WRITE 2\r | |
74 | \r | |
75 | #define DSK_DSW_DATA_ERROR 0x8000 /* device status word bits */\r | |
76 | #define DSK_DSW_OP_COMPLETE 0x4000\r | |
77 | #define DSK_DSW_NOT_READY 0x2000\r | |
78 | #define DSK_DSW_DISK_BUSY 0x1000\r | |
79 | #define DSK_DSW_CARRIAGE_HOME 0x0800\r | |
80 | #define DSK_DSW_SECTOR_MASK 0x0003\r | |
81 | \r | |
82 | /* device status words */\r | |
83 | static int16 dsk_dsw[DSK_NUMDR] = {DSK_DSW_NOT_READY, DSK_DSW_NOT_READY, DSK_DSW_NOT_READY, DSK_DSW_NOT_READY, DSK_DSW_NOT_READY};\r | |
84 | static int16 dsk_sec[DSK_NUMDR] = {0}; /* next-sector-up */\r | |
85 | static char dsk_lastio[DSK_NUMDR]; /* last stdio operation: IO_READ or IO_WRITE */\r | |
86 | int32 dsk_swait = 50; /* seek time -- see how short a delay we can get away with */\r | |
87 | int32 dsk_rwait = 50; /* rotate time */\r | |
88 | static t_bool raw_disk_debug = FALSE;\r | |
89 | \r | |
90 | static t_stat dsk_svc (UNIT *uptr);\r | |
91 | static t_stat dsk_reset (DEVICE *dptr);\r | |
92 | static t_stat dsk_attach (UNIT *uptr, char *cptr);\r | |
93 | static t_stat dsk_detach (UNIT *uptr);\r | |
94 | static t_stat dsk_boot (int unitno, DEVICE *dptr);\r | |
95 | \r | |
96 | static void diskfail (UNIT *uptr, int dswflag, int unitflag, t_bool do_interrupt);\r | |
97 | \r | |
98 | /* DSK data structures\r | |
99 | \r | |
100 | dsk_dev disk device descriptor\r | |
101 | dsk_unit unit descriptor\r | |
102 | dsk_reg register list\r | |
103 | */\r | |
104 | \r | |
105 | UNIT dsk_unit[] = {\r | |
106 | { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r | |
107 | { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r | |
108 | { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r | |
109 | { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r | |
110 | { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }\r | |
111 | };\r | |
112 | \r | |
113 | #define IS_ONLINE(u) (((u)->flags & (UNIT_ATT|UNIT_DIS)) == UNIT_ATT)\r | |
114 | \r | |
115 | /* Parameters in the unit descriptor */\r | |
116 | \r | |
117 | #define CYL u3 /* current cylinder */\r | |
118 | #define FUNC u4 /* current function */\r | |
119 | \r | |
120 | REG dsk_reg[] = {\r | |
121 | { HRDATA (DSKDSW0, dsk_dsw[0], 16) },\r | |
122 | { HRDATA (DSKDSW1, dsk_dsw[1], 16) },\r | |
123 | { HRDATA (DSKDSW2, dsk_dsw[2], 16) },\r | |
124 | { HRDATA (DSKDSW3, dsk_dsw[3], 16) },\r | |
125 | { HRDATA (DSKDSW4, dsk_dsw[4], 16) },\r | |
126 | { DRDATA (STIME, dsk_swait, 24), PV_LEFT },\r | |
127 | { DRDATA (RTIME, dsk_rwait, 24), PV_LEFT },\r | |
128 | { NULL } };\r | |
129 | \r | |
130 | MTAB dsk_mod[] = {\r | |
131 | { UNIT_RONLY, 0, "write enabled", "ENABLED", NULL },\r | |
132 | { UNIT_RONLY, UNIT_RONLY, "write locked", "LOCKED", NULL },\r | |
133 | { 0 } };\r | |
134 | \r | |
135 | DEVICE dsk_dev = {\r | |
136 | "DSK", dsk_unit, dsk_reg, dsk_mod,\r | |
137 | DSK_NUMDR, 16, 16, 1, 16, 16,\r | |
138 | NULL, NULL, &dsk_reset,\r | |
139 | dsk_boot, dsk_attach, dsk_detach};\r | |
140 | \r | |
141 | static int32 dsk_ilswbit[DSK_NUMDR] = { /* interrupt level status word bits for the drives */\r | |
142 | ILSW_2_1131_DISK,\r | |
143 | ILSW_2_2310_DRV_1,\r | |
144 | ILSW_2_2310_DRV_2,\r | |
145 | ILSW_2_2310_DRV_3,\r | |
146 | ILSW_2_2310_DRV_4,\r | |
147 | };\r | |
148 | \r | |
149 | static int32 dsk_ilswlevel[DSK_NUMDR] =\r | |
150 | {\r | |
151 | 2, /* interrupt levels for the drives */\r | |
152 | 2, 2, 2, 2\r | |
153 | };\r | |
154 | \r | |
155 | typedef enum {DSK_FUNC_IDLE, DSK_FUNC_READ, DSK_FUNC_VERIFY, DSK_FUNC_WRITE, DSK_FUNC_SEEK, DSK_FUNC_FAILED} DSK_FUNC;\r | |
156 | \r | |
157 | static struct tag_dsk_action { /* stores data needed for pending IO activity */\r | |
158 | int32 io_address;\r | |
159 | uint32 io_filepos;\r | |
160 | int io_nwords;\r | |
161 | int io_sector;\r | |
162 | } dsk_action[DSK_NUMDR];\r | |
163 | \r | |
164 | /* xio_disk - XIO command interpreter for the disk drives */\r | |
165 | /*\r | |
166 | * device status word:\r | |
167 | *\r | |
168 | * 0 data error, occurs when:\r | |
169 | * 1. A modulo 4 error is detected during a read, read-check, or write operation. \r | |
170 | * 2. The disk storage is in a read or write mode at the leading edge of a sector pulse. \r | |
171 | * 3. A seek-incomplete signal is received from the 2311.\r | |
172 | * 4. A write select error has occurred in the disk storage drive. \r | |
173 | * 5. The power unsafe latch is set in the attachment.\r | |
174 | * Conditions 1, 2, and 3 are turned off by a sense device command with modifier bit 15\r | |
175 | * set to 1. Conditions 4 and 5 are turned off by powering the drive off and back on.\r | |
176 | * 1 operation complete\r | |
177 | * 2 not ready, occurs when disk not ready or busy or disabled or off-line or\r | |
178 | * power unsafe latch set. Also included in the disk not ready is the write select error,\r | |
179 | * which can be a result of power unsafe or write select. \r | |
180 | * 3 disk busy\r | |
181 | * 4 carriage home (on cyl 0)\r | |
182 | * 15-16: number of next sector spinning into position.\r | |
183 | */\r | |
184 | \r | |
185 | extern void void_backtrace (int afrom, int ato);\r | |
186 | \r | |
187 | void xio_disk (int32 iocc_addr, int32 func, int32 modify, int drv)\r | |
188 | {\r | |
189 | int i, rev, nsteps, newcyl, sec, nwords;\r | |
190 | uint32 newpos; /* changed from t_addr to uint32 in anticipation of simh 64-bit development */\r | |
191 | char msg[80];\r | |
192 | UNIT *uptr = dsk_unit+drv;\r | |
193 | int16 buf[DSK_NUMWD];\r | |
194 | \r | |
195 | if (! BETWEEN(drv, 0, DSK_NUMDR-1)) { /* hmmm, invalid drive */\r | |
196 | if (func != XIO_SENSE_DEV) { /* tried to use it, too */\r | |
197 | /* just do nothing, as if the controller isn't there. NAMCRA at N0116300 tests for drives by attempting reads\r | |
198 | sprintf(msg, "Op %x on invalid drive number %d", func, drv);\r | |
199 | xio_error(msg);\r | |
200 | */\r | |
201 | }\r | |
202 | return;\r | |
203 | }\r | |
204 | \r | |
205 | CLRBIT(uptr->flags, UNIT_OPERR); /* clear pending error flag from previous op, if any */\r | |
206 | \r | |
207 | switch (func) {\r | |
208 | case XIO_INITR:\r | |
209 | if (! IS_ONLINE(uptr)) { /* disk is offline */\r | |
210 | diskfail(uptr, 0, 0, FALSE);\r | |
211 | break;\r | |
212 | }\r | |
213 | \r | |
214 | sim_cancel(uptr); /* cancel any pending ops */\r | |
215 | dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; /* and mark the disk as busy */\r | |
216 | \r | |
217 | nwords = M[iocc_addr++ & mem_mask]; /* get word count w/o upsetting SAR/SBR */\r | |
218 | \r | |
219 | if (nwords == 0) /* this is bad -- on real 1130, this locks up disk controller ! */\r | |
220 | break;\r | |
221 | \r | |
222 | if (! BETWEEN(nwords, 1, DSK_NUMWD)) { /* count bad */\r | |
223 | SETBIT(uptr->flags, UNIT_OPERR); /* set data error DSW bit when op complete */\r | |
224 | nwords = DSK_NUMWD; /* limit xfer to proper sector size */\r | |
225 | }\r | |
226 | \r | |
227 | sec = modify & 0x07; /* get sector on cylinder */\r | |
228 | \r | |
229 | if ((modify & 0x0080) == 0) { /* it's a real read if it's not a read check */\r | |
230 | /* ah. We have a problem. The APL boot card counts on there being time for at least one\r | |
231 | * more instruction to execute between the XIO read and the time the data starts loading\r | |
232 | * into core. So, we have to defer the actual read operation a bit. Might as well wait\r | |
233 | * until it's time to issue the operation complete interrupt. This means saving the\r | |
234 | * IO information, then performing the actual read in dsk_svc.\r | |
235 | */\r | |
236 | \r | |
237 | newpos = (uptr->CYL*DSK_NUMSC*DSK_NUMSF + sec)*2*DSK_NUMWD;\r | |
238 | \r | |
239 | dsk_action[drv].io_address = iocc_addr;\r | |
240 | dsk_action[drv].io_nwords = nwords;\r | |
241 | dsk_action[drv].io_sector = sec;\r | |
242 | dsk_action[drv].io_filepos = newpos;\r | |
243 | \r | |
244 | uptr->FUNC = DSK_FUNC_READ;\r | |
245 | }\r | |
246 | else {\r | |
247 | trace_io("* DSK%d verify %d.%d (%x)", drv, uptr->CYL, sec, uptr->CYL*8 + sec);\r | |
248 | \r | |
249 | if (raw_disk_debug)\r | |
250 | printf("* DSK%d verify %d.%d (%x)", drv, uptr->CYL, sec, uptr->CYL*8 + sec);\r | |
251 | \r | |
252 | uptr->FUNC = DSK_FUNC_VERIFY;\r | |
253 | }\r | |
254 | \r | |
255 | sim_activate(uptr, dsk_rwait);\r | |
256 | break;\r | |
257 | \r | |
258 | case XIO_INITW:\r | |
259 | if (! IS_ONLINE(uptr)) { /* disk is offline */\r | |
260 | diskfail(uptr, 0, 0, FALSE);\r | |
261 | break;\r | |
262 | }\r | |
263 | \r | |
264 | if (uptr->flags & UNIT_RONLY) { /* oops, write to RO disk? permanent error until disk is powered off/on */\r | |
265 | diskfail(uptr, DSK_DSW_DATA_ERROR, UNIT_HARDERR, FALSE);\r | |
266 | break;\r | |
267 | }\r | |
268 | \r | |
269 | sim_cancel(uptr); /* cancel any pending ops */\r | |
270 | dsk_dsw[drv] |= DSK_DSW_DISK_BUSY; /* and mark drive as busy */\r | |
271 | \r | |
272 | nwords = M[iocc_addr++ & mem_mask]; /* get word count w/o upsetting SAR/SBR */\r | |
273 | \r | |
274 | if (nwords == 0) /* this is bad -- locks up disk controller ! */\r | |
275 | break;\r | |
276 | \r | |
277 | if (! BETWEEN(nwords, 1, DSK_NUMWD)) { /* count bad */\r | |
278 | SETBIT(uptr->flags, UNIT_OPERR); /* set data error DSW bit when op complete */\r | |
279 | nwords = DSK_NUMWD; /* limit xfer to proper sector size */\r | |
280 | }\r | |
281 | \r | |
282 | sec = modify & 0x07; /* get sector on cylinder */\r | |
283 | newpos = (uptr->CYL*DSK_NUMSC*DSK_NUMSF + sec)*2*DSK_NUMWD;\r | |
284 | \r | |
285 | trace_io("* DSK%d wrote %d words from M[%04x-%04x] to %d.%d (%x, %x)", drv, nwords, iocc_addr & mem_mask, (iocc_addr + nwords - 1) & mem_mask, uptr->CYL, sec, uptr->CYL*8 + sec, newpos);\r | |
286 | \r | |
287 | if (raw_disk_debug)\r | |
288 | printf("* DSK%d XIO @ %04x wrote %d words from M[%04x-%04x] to %d.%d (%x, %x)\n", drv, prev_IAR, nwords, iocc_addr & mem_mask, (iocc_addr + nwords - 1) & mem_mask, uptr->CYL, sec, uptr->CYL*8 + sec, newpos);\r | |
289 | \r | |
290 | #ifdef TRACE_DMS_IO\r | |
291 | if (trace_dms)\r | |
292 | tracesector(1, nwords, iocc_addr & mem_mask, uptr->CYL*8 + sec);\r | |
293 | #endif\r | |
294 | for (i = 0; i < nwords; i++)\r | |
295 | buf[i] = M[iocc_addr++ & mem_mask];\r | |
296 | \r | |
297 | for (; i < DSK_NUMWD; i++) /* rest of sector gets zeroed */\r | |
298 | buf[i] = 0;\r | |
299 | \r | |
300 | i = uptr->CYL*8 + sec;\r | |
301 | if (buf[0] != i)\r | |
302 | printf("*DSK writing bad sector#\n");\r | |
303 | \r | |
304 | if (MEM_MAPPED(uptr)) {\r | |
305 | memcpy((char *) uptr->filebuf + newpos, buf, 2*DSK_NUMWD);\r | |
306 | uptr->hwmark = newpos + 2*DSK_NUMWD;\r | |
307 | }\r | |
308 | else {\r | |
309 | if (uptr->pos != newpos || dsk_lastio[drv] != IO_WRITE) {\r | |
310 | fseek(uptr->fileref, newpos, SEEK_SET);\r | |
311 | dsk_lastio[drv] = IO_WRITE;\r | |
312 | }\r | |
313 | \r | |
314 | fxwrite(buf, 2, DSK_NUMWD, uptr->fileref);\r | |
315 | uptr->pos = newpos + 2*DSK_NUMWD;\r | |
316 | }\r | |
317 | \r | |
318 | uptr->FUNC = DSK_FUNC_WRITE;\r | |
319 | sim_activate(uptr, dsk_rwait);\r | |
320 | break;\r | |
321 | \r | |
322 | case XIO_CONTROL: /* step fwd/rev */\r | |
323 | if (! IS_ONLINE(uptr)) {\r | |
324 | diskfail(uptr, 0, 0, FALSE);\r | |
325 | break;\r | |
326 | }\r | |
327 | \r | |
328 | sim_cancel(uptr);\r | |
329 | \r | |
330 | rev = modify & 4;\r | |
331 | nsteps = iocc_addr & 0x00FF;\r | |
332 | if (nsteps == 0) /* 0 steps does not cause op complete interrupt */\r | |
333 | break;\r | |
334 | \r | |
335 | newcyl = uptr->CYL + (rev ? (-nsteps) : nsteps);\r | |
336 | if (newcyl < 0)\r | |
337 | newcyl = 0;\r | |
338 | else if (newcyl >= DSK_NUMCY)\r | |
339 | newcyl = DSK_NUMCY-1;\r | |
340 | \r | |
341 | uptr->FUNC = DSK_FUNC_SEEK;\r | |
342 | uptr->CYL = newcyl;\r | |
343 | sim_activate(uptr, dsk_swait); /* schedule interrupt */\r | |
344 | \r | |
345 | dsk_dsw[drv] |= DSK_DSW_DISK_BUSY;\r | |
346 | trace_io("* DSK%d at cyl %d", drv, newcyl);\r | |
347 | break;\r | |
348 | \r | |
349 | case XIO_SENSE_DEV:\r | |
350 | CLRBIT(dsk_dsw[drv], DSK_DSW_CARRIAGE_HOME|DSK_DSW_NOT_READY);\r | |
351 | \r | |
352 | if ((uptr->flags & UNIT_HARDERR) || (dsk_dsw[drv] & DSK_DSW_DISK_BUSY) || ! IS_ONLINE(uptr))\r | |
353 | SETBIT(dsk_dsw[drv], DSK_DSW_NOT_READY);\r | |
354 | else if (uptr->CYL <= 0) {\r | |
355 | SETBIT(dsk_dsw[drv], DSK_DSW_CARRIAGE_HOME);\r | |
356 | uptr->CYL = 0;\r | |
357 | }\r | |
358 | \r | |
359 | dsk_sec[drv] = (int16) ((dsk_sec[drv] + 1) % 4); /* advance the "next sector" count every time */\r | |
360 | ACC = dsk_dsw[drv] | dsk_sec[drv];\r | |
361 | \r | |
362 | if (modify & 0x01) { /* reset interrupts */\r | |
363 | CLRBIT(dsk_dsw[drv], DSK_DSW_OP_COMPLETE|DSK_DSW_DATA_ERROR);\r | |
364 | CLRBIT(ILSW[dsk_ilswlevel[drv]], dsk_ilswbit[drv]);\r | |
365 | }\r | |
366 | break;\r | |
367 | \r | |
368 | default:\r | |
369 | sprintf(msg, "Invalid disk XIO function %x", func);\r | |
370 | xio_error(msg);\r | |
371 | }\r | |
372 | }\r | |
373 | \r | |
374 | /* diskfail - schedule an operation complete that sets the error bit */\r | |
375 | \r | |
376 | static void diskfail (UNIT *uptr, int dswflag, int unitflag, t_bool do_interrupt)\r | |
377 | {\r | |
378 | int drv = uptr - dsk_unit;\r | |
379 | \r | |
380 | sim_cancel(uptr); /* cancel any pending ops */\r | |
381 | SETBIT(dsk_dsw[drv], dswflag); /* set any specified DSW bits */\r | |
382 | SETBIT(uptr->flags, unitflag); /* set any specified unit flag bits */\r | |
383 | uptr->FUNC = DSK_FUNC_FAILED; /* tell svc routine why it failed */\r | |
384 | \r | |
385 | if (do_interrupt)\r | |
386 | sim_activate(uptr, 1); /* schedule an immediate op complete interrupt */\r | |
387 | }\r | |
388 | \r | |
389 | t_stat dsk_svc (UNIT *uptr)\r | |
390 | {\r | |
391 | int drv = uptr - dsk_unit, i, nwords, sec;\r | |
392 | int16 buf[DSK_NUMWD];\r | |
393 | uint32 newpos; /* changed from t_addr to uint32 in anticipation of simh 64-bit development */\r | |
394 | int32 iocc_addr;\r | |
395 | \r | |
396 | if (uptr->FUNC == DSK_FUNC_IDLE) /* service function called with no activity? not good, but ignore */\r | |
397 | return SCPE_OK;\r | |
398 | \r | |
399 | CLRBIT(dsk_dsw[drv], DSK_DSW_DISK_BUSY); /* activate operation complete interrupt */\r | |
400 | SETBIT(dsk_dsw[drv], DSK_DSW_OP_COMPLETE);\r | |
401 | \r | |
402 | if (uptr->flags & (UNIT_OPERR|UNIT_HARDERR)) { /* word count error or data error */\r | |
403 | SETBIT(dsk_dsw[drv], DSK_DSW_DATA_ERROR);\r | |
404 | CLRBIT(uptr->flags, UNIT_OPERR); /* soft error is one time occurrence; don't clear hard error */\r | |
405 | }\r | |
406 | /* schedule interrupt */\r | |
407 | SETBIT(ILSW[dsk_ilswlevel[drv]], dsk_ilswbit[drv]);\r | |
408 | \r | |
409 | switch (uptr->FUNC) { /* take care of business */\r | |
410 | case DSK_FUNC_IDLE:\r | |
411 | case DSK_FUNC_VERIFY:\r | |
412 | case DSK_FUNC_WRITE:\r | |
413 | case DSK_FUNC_SEEK:\r | |
414 | case DSK_FUNC_FAILED:\r | |
415 | break;\r | |
416 | \r | |
417 | case DSK_FUNC_READ: /* actually read the data into core */\r | |
418 | iocc_addr = dsk_action[drv].io_address; /* recover saved parameters */\r | |
419 | nwords = dsk_action[drv].io_nwords;\r | |
420 | newpos = dsk_action[drv].io_filepos;\r | |
421 | sec = dsk_action[drv].io_sector;\r | |
422 | \r | |
423 | if (MEM_MAPPED(uptr)) {\r | |
424 | memcpy(buf, (char *) uptr->filebuf + newpos, 2*DSK_NUMWD);\r | |
425 | }\r | |
426 | else {\r | |
427 | if (uptr->pos != newpos || dsk_lastio[drv] != IO_READ) {\r | |
428 | fseek(uptr->fileref, newpos, SEEK_SET);\r | |
429 | dsk_lastio[drv] = IO_READ;\r | |
430 | uptr->pos = newpos;\r | |
431 | }\r | |
432 | fxread(buf, 2, DSK_NUMWD, uptr->fileref); /* read whole sector so we're in position for next read */\r | |
433 | uptr->pos = newpos + 2*DSK_NUMWD;\r | |
434 | }\r | |
435 | \r | |
436 | void_backtrace(iocc_addr, iocc_addr + nwords - 1); /* mark prev instruction as altered */\r | |
437 | \r | |
438 | trace_io("* DSK%d read %d words from %d.%d (%x, %x) to M[%04x-%04x]", drv, nwords, uptr->CYL, sec, uptr->CYL*8 + sec, newpos, iocc_addr & mem_mask,\r | |
439 | (iocc_addr + nwords - 1) & mem_mask);\r | |
440 | \r | |
441 | /* this will help debug the monitor by letting me watch phase loading */\r | |
442 | if (raw_disk_debug)\r | |
443 | printf("* DSK%d XIO @ %04x read %d words from %d.%d (%x, %x) to M[%04x-%04x]\n", drv, prev_IAR, nwords, uptr->CYL, sec, uptr->CYL*8 + sec, newpos, iocc_addr & mem_mask,\r | |
444 | (iocc_addr + nwords - 1) & mem_mask);\r | |
445 | \r | |
446 | i = uptr->CYL*8 + sec;\r | |
447 | if (buf[0] != i)\r | |
448 | printf("*DSK read bad sector #\n");\r | |
449 | \r | |
450 | for (i = 0; i < nwords; i++)\r | |
451 | M[(iocc_addr+i) & mem_mask] = buf[i];\r | |
452 | \r | |
453 | #ifdef TRACE_DMS_IO\r | |
454 | if (trace_dms)\r | |
455 | tracesector(0, nwords, iocc_addr & mem_mask, uptr->CYL*8 + sec);\r | |
456 | #endif\r | |
457 | break;\r | |
458 | \r | |
459 | default:\r | |
460 | fprintf(stderr, "Unexpected FUNC %x in dsk_svc(%d)\n", uptr->FUNC, drv);\r | |
461 | break;\r | |
462 | \r | |
463 | }\r | |
464 | \r | |
465 | uptr->FUNC = DSK_FUNC_IDLE; /* we're done with this operation */\r | |
466 | \r | |
467 | return SCPE_OK;\r | |
468 | }\r | |
469 | \r | |
470 | t_stat dsk_reset (DEVICE *dptr)\r | |
471 | {\r | |
472 | int drv;\r | |
473 | UNIT *uptr;\r | |
474 | \r | |
475 | #ifdef TRACE_DMS_IO\r | |
476 | /* add the WHERE command. It finds the phase that was loaded at given address and indicates */\r | |
477 | /* the offset in the phase */\r | |
478 | register_cmd("WHERE", &where_cmd, 0, "w{here} address find phase and offset of an address\n");\r | |
479 | register_cmd("PHDEBUG", &phdebug_cmd, 0, "ph{debug} off|phlo phhi break on phase load\n");\r | |
480 | register_cmd("FDUMP", &fdump_cmd, 0, NULL);\r | |
481 | #endif\r | |
482 | \r | |
483 | for (drv = 0, uptr = dsk_dev.units; drv < DSK_NUMDR; drv++, uptr++) {\r | |
484 | sim_cancel(uptr);\r | |
485 | \r | |
486 | CLRBIT(ILSW[2], dsk_ilswbit[drv]);\r | |
487 | CLRBIT(uptr->flags, UNIT_OPERR|UNIT_HARDERR);\r | |
488 | \r | |
489 | uptr->CYL = 0;\r | |
490 | uptr->FUNC = DSK_FUNC_IDLE;\r | |
491 | dsk_dsw[drv] = (int16) ((uptr->flags & UNIT_ATT) ? DSK_DSW_CARRIAGE_HOME : 0);\r | |
492 | }\r | |
493 | \r | |
494 | calc_ints();\r | |
495 | \r | |
496 | return SCPE_OK;\r | |
497 | }\r | |
498 | \r | |
499 | static t_stat dsk_attach (UNIT *uptr, char *cptr)\r | |
500 | {\r | |
501 | int drv = uptr - dsk_unit;\r | |
502 | t_stat rval;\r | |
503 | \r | |
504 | sim_cancel(uptr); /* cancel current IO */\r | |
505 | dsk_lastio[drv] = IO_NONE;\r | |
506 | \r | |
507 | if (uptr->flags & UNIT_ATT) /* dismount current disk */\r | |
508 | if ((rval = dsk_detach(uptr)) != SCPE_OK)\r | |
509 | return rval;\r | |
510 | \r | |
511 | uptr->CYL = 0; /* reset the device */\r | |
512 | uptr->FUNC = DSK_FUNC_IDLE;\r | |
513 | dsk_dsw[drv] = DSK_DSW_CARRIAGE_HOME;\r | |
514 | \r | |
515 | CLRBIT(uptr->flags, UNIT_RO|UNIT_ROABLE|UNIT_BUFABLE|UNIT_BUF|UNIT_RONLY|UNIT_OPERR|UNIT_HARDERR);\r | |
516 | CLRBIT(ILSW[2], dsk_ilswbit[drv]);\r | |
517 | calc_ints();\r | |
518 | \r | |
519 | if (sim_switches & SWMASK('M')) /* if memory mode (e.g. for CGI), buffer the file */\r | |
520 | SETBIT(uptr->flags, UNIT_BUFABLE|UNIT_MUSTBUF);\r | |
521 | \r | |
522 | if (sim_switches & SWMASK('R')) /* read lock mode */\r | |
523 | SETBIT(uptr->flags, UNIT_RO|UNIT_ROABLE|UNIT_RONLY);\r | |
524 | \r | |
525 | if (cgi && (sim_switches & SWMASK('M')) && ! cgiwritable) { /* if cgi and memory mode, but writable option not specified */\r | |
526 | sim_switches |= SWMASK('R'); /* have attach_unit open file in readonly mode */\r | |
527 | SETBIT(uptr->flags, UNIT_ROABLE); /* but don't set the UNIT_RONLY flag so DMS can write to the buffered image */\r | |
528 | }\r | |
529 | \r | |
530 | if ((rval = attach_unit(uptr, quotefix(cptr))) != SCPE_OK) { /* mount new disk */\r | |
531 | SETBIT(dsk_dsw[drv], DSK_DSW_NOT_READY);\r | |
532 | return rval;\r | |
533 | }\r | |
534 | \r | |
535 | if (drv == 0) {\r | |
536 | disk_ready(TRUE);\r | |
537 | disk_unlocked(FALSE);\r | |
538 | }\r | |
539 | \r | |
540 | enable_dms_tracing(sim_switches & SWMASK('D'));\r | |
541 | raw_disk_debug = sim_switches & SWMASK('G');\r | |
542 | \r | |
543 | return SCPE_OK;\r | |
544 | }\r | |
545 | \r | |
546 | static t_stat dsk_detach (UNIT *uptr)\r | |
547 | {\r | |
548 | t_stat rval;\r | |
549 | int drv = uptr - dsk_unit;\r | |
550 | \r | |
551 | sim_cancel(uptr);\r | |
552 | \r | |
553 | if ((rval = detach_unit(uptr)) != SCPE_OK)\r | |
554 | return rval;\r | |
555 | \r | |
556 | CLRBIT(ILSW[2], dsk_ilswbit[drv]);\r | |
557 | CLRBIT(uptr->flags, UNIT_OPERR|UNIT_HARDERR);\r | |
558 | calc_ints();\r | |
559 | \r | |
560 | uptr->CYL = 0;\r | |
561 | uptr->FUNC = DSK_FUNC_IDLE;\r | |
562 | dsk_dsw[drv] = DSK_DSW_NOT_READY;\r | |
563 | \r | |
564 | if (drv == 0) {\r | |
565 | disk_unlocked(TRUE);\r | |
566 | disk_ready(FALSE);\r | |
567 | }\r | |
568 | \r | |
569 | return SCPE_OK;\r | |
570 | }\r | |
571 | \r | |
572 | /* boot routine - if they type BOOT DSK, load the standard boot card. */\r | |
573 | \r | |
574 | static t_stat dsk_boot (int unitno, DEVICE *dptr)\r | |
575 | {\r | |
576 | t_stat rval;\r | |
577 | \r | |
578 | if ((rval = reset_all(0)) != SCPE_OK)\r | |
579 | return rval;\r | |
580 | \r | |
581 | return load_cr_boot(unitno, sim_switches);\r | |
582 | }\r | |
583 | \r | |
584 | #ifdef TRACE_DMS_IO\r | |
585 | \r | |
586 | static struct {\r | |
587 | int phid;\r | |
588 | char *name;\r | |
589 | } phase[] = {\r | |
590 | # include "dmsr2v12phases.h"\r | |
591 | 0xFFFF, ""\r | |
592 | };\r | |
593 | \r | |
594 | #pragma pack(2)\r | |
595 | #define MAXSLET ((3*320)/4)\r | |
596 | struct tag_slet {\r | |
597 | int16 phid;\r | |
598 | int16 addr;\r | |
599 | int16 nwords;\r | |
600 | int16 sector;\r | |
601 | } slet[MAXSLET] = {\r | |
602 | # include "dmsr2v12slet.h" /* without RPG, use this info until overwritten by actual data from disk */\r | |
603 | };\r | |
604 | \r | |
605 | #pragma pack()\r | |
606 | \r | |
607 | #define MAXMSEG 100\r | |
608 | struct tag_mseg {\r | |
609 | char *name;\r | |
610 | int addr, offset, len, phid;\r | |
611 | } mseg[MAXMSEG];\r | |
612 | int nseg = 0;\r | |
613 | \r | |
614 | static void enable_dms_tracing (int newsetting)\r | |
615 | {\r | |
616 | nseg = 0; /* clear the segment map */\r | |
617 | \r | |
618 | if ((newsetting && trace_dms) || ! (newsetting || trace_dms))\r | |
619 | return;\r | |
620 | \r | |
621 | trace_dms = newsetting;\r | |
622 | if (! sim_quiet)\r | |
623 | printf("DMS disk tracing is now %sabled\n", trace_dms ? "en" : "dis");\r | |
624 | }\r | |
625 | \r | |
626 | char * saywhere (int addr)\r | |
627 | {\r | |
628 | int i;\r | |
629 | static char buf[150];\r | |
630 | \r | |
631 | for (i = 0; i < nseg; i++) {\r | |
632 | if (addr >= mseg[i].addr && addr < (mseg[i].addr+mseg[i].len)) {\r | |
633 | sprintf(buf, "/%04x = /%04x + /%x in ", addr, mseg[i].addr - mseg[i].offset, addr-mseg[i].addr + mseg[i].offset);\r | |
634 | if (mseg[i].phid > 0) \r | |
635 | sprintf(buf+strlen(buf), "phase %02x (%s)", mseg[i].phid, mseg[i].name);\r | |
636 | else\r | |
637 | sprintf(buf+strlen(buf), "%s", mseg[i].name);\r | |
638 | \r | |
639 | return buf;\r | |
640 | }\r | |
641 | }\r | |
642 | return NULL;\r | |
643 | }\r | |
644 | \r | |
645 | static int phdebug_lo = -1, phdebug_hi = -1;\r | |
646 | \r | |
647 | static t_stat phdebug_cmd (int flag, char *ptr)\r | |
648 | {\r | |
649 | int val1, val2;\r | |
650 | \r | |
651 | if (strcmpi(ptr, "off") == 0)\r | |
652 | phdebug_lo = phdebug_hi = -1;\r | |
653 | else {\r | |
654 | switch(sscanf(ptr, "%x%x", &val1, &val2)) {\r | |
655 | case 1:\r | |
656 | phdebug_lo = phdebug_hi = val1;\r | |
657 | enable_dms_tracing(TRUE);\r | |
658 | break;\r | |
659 | \r | |
660 | case 2:\r | |
661 | phdebug_lo = val1;\r | |
662 | phdebug_hi = val2;\r | |
663 | enable_dms_tracing(TRUE);\r | |
664 | break;\r | |
665 | \r | |
666 | default:\r | |
667 | printf("Usage: phdebug off | phdebug phfrom [phto]\n");\r | |
668 | break;\r | |
669 | }\r | |
670 | }\r | |
671 | return SCPE_OK;\r | |
672 | }\r | |
673 | \r | |
674 | static t_stat where_cmd (int flag, char *ptr)\r | |
675 | {\r | |
676 | int addr;\r | |
677 | char *where;\r | |
678 | \r | |
679 | if (! trace_dms) {\r | |
680 | printf("Tracing is disabled. To enable, attach disk with -d switch\n");\r | |
681 | return SCPE_OK;\r | |
682 | }\r | |
683 | \r | |
684 | if (sscanf(ptr, "%x", &addr) != 1)\r | |
685 | return SCPE_ARG;\r | |
686 | \r | |
687 | if ((where = saywhere(addr)) == NULL)\r | |
688 | printf("/%04x not found\n", addr);\r | |
689 | else\r | |
690 | printf("%s\n", where);\r | |
691 | \r | |
692 | return SCPE_OK;\r | |
693 | }\r | |
694 | \r | |
695 | /* savesector - save info on a sector just read. THIS IS NOT YET TESTED */\r | |
696 | \r | |
697 | static void addseg (int i)\r | |
698 | {\r | |
699 | if (! trace_dms)\r | |
700 | return;\r | |
701 | \r | |
702 | if (nseg >= MAXMSEG) {\r | |
703 | printf("(Memory map full, disabling tracing)\n");\r | |
704 | trace_dms = 0;\r | |
705 | nseg = -1;\r | |
706 | return;\r | |
707 | }\r | |
708 | memcpy(mseg+i+1, mseg+i, (nseg-i)*sizeof(mseg[0]));\r | |
709 | nseg++;\r | |
710 | }\r | |
711 | \r | |
712 | static void delseg (int i)\r | |
713 | {\r | |
714 | if (! trace_dms)\r | |
715 | return;\r | |
716 | \r | |
717 | if (nseg > 0) {\r | |
718 | nseg--;\r | |
719 | memcpy(mseg+i, mseg+i+1, (nseg-i)*sizeof(mseg[0]));\r | |
720 | }\r | |
721 | }\r | |
722 | \r | |
723 | static void savesector (int addr, int offset, int len, int phid, char *name)\r | |
724 | {\r | |
725 | int i;\r | |
726 | \r | |
727 | if (! trace_dms)\r | |
728 | return;\r | |
729 | \r | |
730 | addr++; /* first word is sector address, so account for that */\r | |
731 | len--;\r | |
732 | \r | |
733 | for (i = 0; i < nseg; i++) {\r | |
734 | if (addr >= (mseg[i].addr+mseg[i].len)) /* entirely after this entry */\r | |
735 | continue;\r | |
736 | \r | |
737 | if (mseg[i].addr < addr) { /* old one starts before this. split it */\r | |
738 | addseg(i);\r | |
739 | mseg[i].len = addr-mseg[i].addr;\r | |
740 | i++;\r | |
741 | mseg[i].addr = addr;\r | |
742 | mseg[i].len -= mseg[i-1].len;\r | |
743 | }\r | |
744 | \r | |
745 | break;\r | |
746 | }\r | |
747 | \r | |
748 | addseg(i); /* add new segment. Old one ends up after this */\r | |
749 | \r | |
750 | if (i >= MAXMSEG)\r | |
751 | return;\r | |
752 | \r | |
753 | mseg[i].addr = addr;\r | |
754 | mseg[i].offset = offset;\r | |
755 | mseg[i].phid = phid;\r | |
756 | mseg[i].len = len;\r | |
757 | mseg[i].name = name;\r | |
758 | \r | |
759 | i++; /* delete any segments completely covered */\r | |
760 | \r | |
761 | while (i < nseg && (mseg[i].addr+mseg[i].len) <= (addr+len))\r | |
762 | delseg(i);\r | |
763 | \r | |
764 | if (i < nseg && mseg[i].addr < (addr+len)) { /* old one extends past this. Retain the end */\r | |
765 | mseg[i].len = (mseg[i].addr+mseg[i].len) - (addr+len);\r | |
766 | mseg[i].addr = addr+len;\r | |
767 | }\r | |
768 | }\r | |
769 | \r | |
770 | static void tracesector (int iswrite, int nwords, int addr, int sector)\r | |
771 | {\r | |
772 | int i, phid = 0, offset = 0;\r | |
773 | char *name = NULL;\r | |
774 | \r | |
775 | if (nwords < 3 || ! trace_dms)\r | |
776 | return;\r | |
777 | \r | |
778 | switch (sector) { /* explicitly known sector name */\r | |
779 | case 0: name = "ID/COLD START"; break;\r | |
780 | case 1: name = "DCOM"; break;\r | |
781 | case 2: name = "RESIDENT IMAGE"; break;\r | |
782 | case 3:\r | |
783 | case 4:\r | |
784 | case 5: name = "SLET"; /* save just-read or written SLET info */\r | |
785 | memmove(&slet[(320/4)*(sector-3)], &M[addr+1], nwords*2);\r | |
786 | break;\r | |
787 | case 6: name = "RELOAD TABLE"; break;\r | |
788 | case 7: name = "PAGE HEADER"; break;\r | |
789 | }\r | |
790 | \r | |
791 | printf("* %04x: %3d /%04x %c %3d.%d ",\r | |
792 | prev_IAR, nwords, addr, iswrite ? 'W' : 'R', sector/8, sector%8);\r | |
793 | \r | |
794 | if (name == NULL) { /* look up sector in SLET */\r | |
795 | for (i = 0; i < MAXSLET; i++) {\r | |
796 | if (slet[i].phid == 0) /* not found */\r | |
797 | goto done;\r | |
798 | else if (slet[i].sector > sector) {\r | |
799 | if (--i >= 0) {\r | |
800 | if (sector >= slet[i].sector && sector <= (slet[i].sector + slet[i].nwords/320)) {\r | |
801 | phid = slet[i].phid;\r | |
802 | offset = (sector-slet[i].sector)*320;\r | |
803 | break;\r | |
804 | }\r | |
805 | }\r | |
806 | goto done;\r | |
807 | }\r | |
808 | if (slet[i].sector == sector) {\r | |
809 | phid = slet[i].phid; /* we found the starting sector */\r | |
810 | break;\r | |
811 | }\r | |
812 | }\r | |
813 | \r | |
814 | if (i >= MAXSLET) /* was not found */\r | |
815 | goto done;\r | |
816 | \r | |
817 | name = "?";\r | |
818 | for (i = sizeof(phase)/sizeof(phase[0]); --i >= 0; ) {\r | |
819 | if (phase[i].phid == phid) { /* look up name */\r | |
820 | name = phase[i].name;\r | |
821 | break;\r | |
822 | }\r | |
823 | }\r | |
824 | printf("%02x %s", phid, name);\r | |
825 | }\r | |
826 | else\r | |
827 | printf("%s", name);\r | |
828 | \r | |
829 | done:\r | |
830 | putchar('\n');\r | |
831 | \r | |
832 | if (phid >= phdebug_lo && phid <= phdebug_hi && offset == 0)\r | |
833 | break_simulation(STOP_PHASE_BREAK); /* break on read of first sector of indicated phases */\r | |
834 | \r | |
835 | if (name != NULL && *name != '?' && ! iswrite)\r | |
836 | savesector(addr, offset, nwords, phid, name);\r | |
837 | }\r | |
838 | \r | |
839 | static t_stat fdump_cmd (int flags, char *cptr)\r | |
840 | {\r | |
841 | int addr = 0x7a24; /* address of next statement */\r | |
842 | int sofst = 0x7a26, symaddr;\r | |
843 | int cword, nwords, stype, has_stnum, strel = 1, laststno = 0;\r | |
844 | \r | |
845 | addr = M[addr & mem_mask] & mem_mask; /* get address of first statement */\r | |
846 | sofst = M[sofst & mem_mask] & mem_mask ; /* get address of symbol table */\r | |
847 | \r | |
848 | for (;;) {\r | |
849 | cword = M[addr];\r | |
850 | nwords = (cword >> 2) & 0x01FF;\r | |
851 | stype = (cword >> 1) & 0x7C00;\r | |
852 | has_stnum = (cword & 1);\r | |
853 | \r | |
854 | if (has_stnum) {\r | |
855 | laststno++;\r | |
856 | strel = 0;\r | |
857 | }\r | |
858 | \r | |
859 | printf("/%04x [%4d +%3d] %3d - %04x", addr, laststno, strel, nwords, stype);\r | |
860 | \r | |
861 | if (has_stnum) {\r | |
862 | addr++;\r | |
863 | nwords--;\r | |
864 | symaddr = sofst - (M[addr] & 0x7FF)*3 + 3;\r | |
865 | printf(" [%04x %04x %04x]", M[symaddr], M[symaddr+1], M[symaddr+2]);\r | |
866 | }\r | |
867 | \r | |
868 | if (stype == 0x5000) { /* error record */\r | |
869 | printf(" (err %d)", M[addr+1]);\r | |
870 | }\r | |
871 | \r | |
872 | if (stype == 0x0800)\r | |
873 | break;\r | |
874 | \r | |
875 | addr += nwords;\r | |
876 | putchar('\n');\r | |
877 | \r | |
878 | if (nwords == 0) {\r | |
879 | printf("0 words?\n");\r | |
880 | break;\r | |
881 | }\r | |
882 | strel++;\r | |
883 | }\r | |
884 | \r | |
885 | printf("\nEnd found at /%04x, EOFS = /%04x\n", addr, M[0x7a25 & mem_mask]);\r | |
886 | return SCPE_OK;\r | |
887 | }\r | |
888 | \r | |
889 | #endif /* TRACE_DMS_IO */\r |