1 /* i1620_dp.c: IBM 1311 disk simulator
3 Copyright (c) 2002-2005, Robert M. Supnik
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:
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
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.
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.
28 The 1311 disk pack has 100 cylinders, 10 tracks/cylinder, 20 sectors/track.
29 Each sector contains 105 characters of information:
34 By default, a sector's address field will be '00000', which is interpreted
35 to mean the implied sector number that would be in place if the disk pack
36 had been formatted with sequential sector numbers.
38 18-Oct-02 RMS Fixed bug in error testing (found by Hans Pufal)
41 #include "i1620_defs.h"
43 #define DP_NUMDR 4 /* #drives */
44 #define UNIT_V_WAE (UNIT_V_UF + 0) /* write addr enab */
45 #define UNIT_WAE (1 << UNIT_V_WAE)
49 #define DP_ADDR 5 /* address */
50 #define DP_DATA 100 /* data */
51 #define DP_NUMCH (DP_ADDR + DP_DATA)
53 #define DP_NUMSC 20 /* #sectors */
54 #define DP_NUMSF 10 /* #surfaces */
55 #define DP_NUMCY 100 /* #cylinders */
56 #define DP_TOTSC (DP_NUMCY * DP_NUMSF * DP_NUMSC)
57 #define DP_SIZE (DP_TOTSC * DP_NUMCH)
59 /* Disk control field */
61 #define DCF_DRV 0 /* drive select */
62 #define DCF_SEC 1 /* sector addr */
64 #define DCF_CNT (DCF_SEC + DCF_SEC_LEN) /* sector count */
66 #define DCF_ADR (DCF_CNT + DCF_CNT_LEN) /* buffer address */
68 #define DCF_LEN (DCF_ADR + DCF_ADR_LEN)
72 #define FNC_SEEK 1 /* seek */
73 #define FNC_SEC 0 /* sectors */
74 #define FNC_WCH 1 /* write check */
75 #define FNC_NRL 2 /* no rec lnt chk */
76 #define FNC_TRK 4 /* tracks */
77 #define FNC_WRI 8 /* write offset */
79 #define CYL u3 /* current cylinder */
81 extern uint8 M
[MAXMEMSIZE
]; /* memory */
82 extern uint8 ind
[NUM_IND
];
85 int32 dp_stop
= 1; /* disk err stop */
86 uint32 dp_ba
= 0; /* buffer addr */
88 t_stat
dp_reset (DEVICE
*dptr
);
89 t_stat
dp_rdadr (UNIT
*uptr
, int32 sec
, int32 qnr
, int32 qwc
);
90 t_stat
dp_rdsec (UNIT
*uptr
, int32 sec
, int32 qnr
, int32 qwc
);
91 t_stat
dp_wradr (UNIT
*uptr
, int32 sec
, int32 qnr
);
92 t_stat
dp_wrsec (UNIT
*uptr
, int32 sec
, int32 qnr
);
93 int32
dp_fndsec (UNIT
*uptr
, int32 sec
, t_bool rd
);
94 t_stat
dp_nexsec (UNIT
*uptr
, int32 sec
, int32 psec
, t_bool rd
);
95 t_bool
dp_zeroad (uint8
*ap
);
96 int32
dp_cvt_ad (uint8
*ap
);
97 int32
dp_trkop (int32 drv
, int32 sec
);
98 int32
dp_cvt_bcd (uint32 ad
, int32 len
);
99 void dp_fill (UNIT
*uptr
, uint32 da
, int32 cnt
);
100 t_stat
dp_tstgm (uint32 c
, int32 qnr
);
102 /* DP data structures
104 dp_dev DP device descriptor
106 dp_reg DP register list
107 dp_mod DP modifier list
111 { UDATA (NULL
, UNIT_FIX
+ UNIT_DISABLE
+ UNIT_ATTABLE
+
112 UNIT_BUFABLE
+ UNIT_MUSTBUF
+ UNIT_BCD
, DP_SIZE
) },
113 { UDATA (NULL
, UNIT_FIX
+ UNIT_DISABLE
+ UNIT_ATTABLE
+
114 UNIT_BUFABLE
+ UNIT_MUSTBUF
+ UNIT_BCD
, DP_SIZE
) },
115 { UDATA (NULL
, UNIT_FIX
+ UNIT_DISABLE
+ UNIT_ATTABLE
+
116 UNIT_BUFABLE
+ UNIT_MUSTBUF
+ UNIT_BCD
, DP_SIZE
) },
117 { UDATA (NULL
, UNIT_FIX
+ UNIT_DISABLE
+ UNIT_ATTABLE
+
118 UNIT_BUFABLE
+ UNIT_MUSTBUF
+ UNIT_BCD
, DP_SIZE
) }
122 { FLDATA (ADCHK
, ind
[IN_DACH
], 0) },
123 { FLDATA (WLRC
, ind
[IN_DWLR
], 0) },
124 { FLDATA (CYLO
, ind
[IN_DCYO
], 0) },
125 { FLDATA (ERR
, ind
[IN_DERR
], 0) },
126 { FLDATA (DPSTOP
, dp_stop
, 0) },
127 { URDATA (CYL
, dp_unit
[0].CYL
, 10, 8, 0,
128 DP_NUMDR
, PV_LEFT
+ REG_RO
) },
133 { UNIT_WAE
, 0, "write address disabled", "ADDROFF", NULL
},
134 { UNIT_WAE
, UNIT_WAE
, "write address enabled", "ADDRON", NULL
},
139 "DP", dp_unit
, dp_reg
, dp_mod
,
140 DP_NUMDR
, 10, 21, 1, 16, 5,
141 NULL
, NULL
, &dp_reset
,
145 /* Disk IO routine */
147 t_stat
dp (uint32 op
, uint32 pa
, uint32 f0
, uint32 f1
)
149 int32 drv
, sa
, sec
, psec
, cnt
, qwc
, qnr
, t
;
153 if (pa
& 1) return STOP_INVDCF
; /* dcf must be even */
154 ind
[IN_DACH
] = ind
[IN_DWLR
] = 0; /* clr indicators */
155 ind
[IN_DERR
] = ind
[IN_DCYO
] = 0;
156 sa
= ADDR_A (pa
, DCF_SEC
); /* ptr to sector */
157 if (((dp_unit
[0].flags
& UNIT_DIS
) == 0) && /* only drive 0? */
158 (dp_unit
[1].flags
& UNIT_DIS
) &&
159 (dp_unit
[2].flags
& UNIT_DIS
) &&
160 (dp_unit
[3].flags
& UNIT_DIS
)) drv
= 0; /* ignore drv select */
161 else drv
= (((M
[pa
] & 1)? M
[pa
]: M
[sa
]) & 0xE) >> 1; /* drive # */
162 if (drv
>= DP_NUMDR
) return STOP_INVDRV
; /* invalid? */
163 uptr
= dp_dev
.units
+ drv
; /* get unit ptr */
164 if ((uptr
->flags
& UNIT_ATT
) == 0) { /* attached? */
165 ind
[IN_DERR
] = 1; /* no, error */
166 CRETIOE (dp_stop
, SCPE_UNATT
);
169 sec
= dp_cvt_bcd (sa
, DCF_SEC_LEN
); /* cvt sector */
170 if ((sec
< 0) || (sec
>= (DP_NUMDR
* DP_TOTSC
))) /* bad sector? */
172 if (op
== OP_K
) { /* seek? */
173 if (f1
!= FNC_SEEK
) return STOP_INVFNC
; /* really? */
174 uptr
->CYL
= (sec
/ (DP_NUMSF
* DP_NUMSC
)) % /* set cyl # */
176 return SCPE_OK
; /* done! */
179 cnt
= dp_cvt_bcd (ADDR_A (pa
, DCF_CNT
), DCF_CNT_LEN
); /* get count */
180 t
= dp_cvt_bcd (ADDR_A (pa
, DCF_ADR
), DCF_ADR_LEN
); /* get address */
181 if ((t
< 0) || (t
& 1)) return STOP_INVDBA
; /* bad address? */
182 dp_ba
= t
; /* save addr */
184 if (f1
>= FNC_WRI
) return STOP_INVFNC
; /* invalid func? */
185 if (op
== OP_RN
) qwc
= f1
& FNC_WCH
; /* read? set wch */
186 else if (op
== OP_WN
) { /* write? */
187 if (op
& FNC_WCH
) return STOP_INVFNC
; /* cant check */
188 f1
= f1
+ FNC_WRI
; /* offset fnc */
190 else return STOP_INVFNC
; /* not R or W */
191 qnr
= f1
& FNC_NRL
; /* no rec check? */
193 switch (f1
& ~(FNC_WCH
| FNC_NRL
)) { /* case on function */
195 case FNC_SEC
: /* read sectors */
196 if (cnt
<= 0) return STOP_INVDCN
; /* bad count? */
197 psec
= dp_fndsec (uptr
, sec
, TRUE
); /* find sector */
198 if (psec
< 0) CRETIOE (dp_stop
, STOP_DACERR
); /* error? */
199 do { /* loop on count */
200 if (r
= dp_rdsec (uptr
, psec
, qnr
, qwc
)) /* read sector */
202 sec
++; psec
++; /* next sector */
203 } while ((--cnt
> 0) &&
204 ((r
= dp_nexsec (uptr
, sec
, psec
, TRUE
)) == SCPE_OK
));
205 break; /* done, clean up */
207 case FNC_TRK
: /* read track */
208 psec
= dp_trkop (drv
, sec
); /* start of track */
209 for (cnt
= 0; cnt
< DP_NUMSC
; cnt
++) { /* full track */
210 if (r
= dp_rdadr (uptr
, psec
, qnr
, qwc
)) /* read addr */
212 if (r
= dp_rdsec (uptr
, psec
, qnr
, qwc
)) /* read data */
214 psec
= dp_trkop (drv
, sec
) + ((psec
+ 1) % DP_NUMSC
);
216 break; /* done, clean up */
218 case FNC_SEC
+ FNC_WRI
: /* write */
219 if (cnt
<= 0) return STOP_INVDCN
; /* bad count? */
220 psec
= dp_fndsec (uptr
, sec
, FALSE
); /* find sector */
221 if (psec
< 0) CRETIOE (dp_stop
, STOP_DACERR
); /* error? */
222 do { /* loop on count */
223 if (r
= dp_tstgm (M
[dp_ba
], qnr
)) break; /* start with gm? */
224 if (r
= dp_wrsec (uptr
, psec
, qnr
)) break; /* write data */
225 sec
++; psec
++; /* next sector */
226 } while ((--cnt
> 0) &&
227 ((r
= dp_nexsec (uptr
, sec
, psec
, FALSE
)) == SCPE_OK
));
228 break; /* done, clean up */
230 case FNC_TRK
+ FNC_WRI
: /* write track */
231 if ((uptr
->flags
& UNIT_WAE
) == 0) /* enabled? */
233 psec
= dp_trkop (drv
, sec
); /* start of track */
234 for (cnt
= 0; cnt
< DP_NUMSC
; cnt
++) { /* full track */
235 if (r
= dp_tstgm (M
[dp_ba
], qnr
)) break; /* start with gm? */
236 if (r
= dp_wradr (uptr
, psec
, qnr
)) break; /* write addr */
237 if (r
= dp_wrsec (uptr
, psec
, qnr
)) break; /* write data */
238 psec
= dp_trkop (drv
, sec
) + ((psec
+ 1) % DP_NUMSC
);
240 break; /* done, clean up */
242 default: /* unknown */
246 if ((r
== SCPE_OK
) && !qnr
) { /* eor check? */
247 if ((M
[dp_ba
] & DIGIT
) != GRP_MARK
) { /* GM at end? */
248 ind
[IN_DWLR
] = ind
[IN_DERR
] = 1; /* no, error */
252 if ((r
!= SCPE_OK
) && /* error? */
253 (dp_stop
|| !ind
[IN_DERR
])) return r
; /* iochk or stop? */
254 return SCPE_OK
; /* continue */
257 /* Read or compare address with memory */
259 t_stat
dp_rdadr (UNIT
*uptr
, int32 sec
, int32 qnr
, int32 qwc
)
263 int32 da
= (sec
% DP_TOTSC
) * DP_NUMCH
; /* char number */
264 uint8
*ap
= ((uint8
*) uptr
->filebuf
) + da
; /* buf ptr */
265 t_bool zad
= dp_zeroad (ap
); /* zero address */
266 static const int32 dec_tab
[DP_ADDR
] = { /* powers of 10 */
267 10000, 1000, 100, 10, 1
270 for (i
= 0; i
< DP_ADDR
; i
++) { /* copy/check addr */
271 if (zad
) { /* addr zero? */
272 ad
= sec
/ dec_tab
[i
]; /* get addr digit */
273 sec
= sec
% dec_tab
[i
]; /* get remainder */
275 else ad
= *ap
; /* addr digit */
276 if (qwc
) { /* write check? */
277 if (dp_tstgm (M
[dp_ba
], qnr
)) /* grp mrk in mem? */
278 return STOP_WRLERR
; /* yes, error */
279 if (!zad
&& (M
[dp_ba
] != ad
)) { /* digits equal? */
280 ind
[IN_DACH
] = ind
[IN_DERR
] = 1; /* no, error */
284 else M
[dp_ba
] = ad
& (FLAG
| DIGIT
); /* store digit */
285 if (dp_tstgm (*ap
, qnr
)) return STOP_WRLERR
; /* grp mrk on disk? */
286 ap
++; PP (dp_ba
); /* adv ptrs */
291 /* Read or compare data with memory */
293 t_stat
dp_rdsec (UNIT
*uptr
, int32 sec
, int32 qnr
, int32 qwc
)
296 int32 da
= (sec
% DP_TOTSC
) * DP_NUMCH
; /* char number */
297 uint8
*ap
= ((uint8
*) uptr
->filebuf
) + da
+ DP_ADDR
; /* buf ptr */
299 for (i
= 0; i
< DP_DATA
; i
++) { /* copy data */
300 if (qwc
) { /* write check? */
301 if (dp_tstgm (M
[dp_ba
], qnr
)) /* grp mrk in mem? */
302 return STOP_WRLERR
; /* yes, error */
303 if (M
[dp_ba
] != *ap
) { /* dig+flags equal? */
304 ind
[IN_DACH
] = ind
[IN_DERR
] = 1; /* no, error */
308 else M
[dp_ba
] = *ap
& (FLAG
| DIGIT
); /* flag + digit */
309 if (dp_tstgm (*ap
, qnr
)) return STOP_WRLERR
; /* grp mrk on disk? */
310 ap
++; PP (dp_ba
); /* adv ptrs */
315 /* Write address to disk */
317 t_stat
dp_wradr (UNIT
*uptr
, int32 sec
, int32 qnr
)
320 uint32 da
= (sec
% DP_TOTSC
) * DP_NUMCH
; /* char number */
321 uint8
*ap
= ((uint8
*) uptr
->filebuf
) + da
; /* buf ptr */
323 for (i
= 0; i
< DP_ADDR
; i
++) { /* copy address */
324 *ap
= M
[dp_ba
] & (FLAG
| DIGIT
); /* flag + digit */
325 if (da
>= uptr
->hwmark
) uptr
->hwmark
= da
+ 1;
326 if (dp_tstgm (*ap
, qnr
)) { /* grp mrk fm mem? */
327 dp_fill (uptr
, da
+ 1, DP_NUMCH
- i
- 1); /* fill addr+data */
328 return STOP_WRLERR
; /* error */
330 da
++; ap
++; PP (dp_ba
); /* adv ptrs */
335 /* Write data to disk */
337 t_stat
dp_wrsec (UNIT
*uptr
, int32 sec
, int32 qnr
)
340 uint32 da
= ((sec
% DP_TOTSC
) * DP_NUMCH
) + DP_ADDR
; /* char number */
341 uint8
*ap
= ((uint8
*) uptr
->filebuf
) + da
; /* buf ptr */
343 for (i
= 0; i
< DP_DATA
; i
++) { /* copy data */
344 *ap
= M
[dp_ba
] & (FLAG
| DIGIT
); /* get character */
345 if (da
>= uptr
->hwmark
) uptr
->hwmark
= da
+ 1;
346 if (dp_tstgm (*ap
, qnr
)) { /* grp mrk fm mem? */
347 dp_fill (uptr
, da
+ 1, DP_DATA
- i
- 1); /* fill data */
348 return STOP_WRLERR
; /* error */
350 da
++; ap
++; PP (dp_ba
); /* adv ptrs */
357 int32
dp_fndsec (UNIT
*uptr
, int32 sec
, t_bool rd
)
359 int32 ctrk
= sec
% (DP_NUMSF
* DP_NUMSC
); /* curr trk-sec */
360 int32 psec
= ((uptr
->CYL
) * (DP_NUMSF
* DP_NUMSC
)) + ctrk
;
361 int32 da
= psec
* DP_NUMCH
; /* char number */
362 uint8
*ap
= ((uint8
*) uptr
->filebuf
) + da
; /* buf ptr */
365 if (dp_zeroad (ap
)) return psec
; /* addr zero? ok */
366 dskad
= dp_cvt_ad (ap
); /* cvt addr */
367 if (dskad
== sec
) { /* match? */
368 if (rd
|| ((*ap
& FLAG
) == 0)) return psec
; /* read or !wprot? */
369 ind
[IN_DACH
] = ind
[IN_DERR
] = 1; /* no match */
372 psec
= psec
- (psec
% DP_NUMSC
); /* sector 0 */
373 for (i
= 0; i
< DP_NUMSC
; i
++, psec
++) { /* check track */
374 da
= psec
* DP_NUMCH
; /* char number */
375 ap
= ((uint8
*) uptr
->filebuf
) + da
; /* word pointer */
376 if (dp_zeroad (ap
)) continue; /* no implicit match */
377 dskad
= dp_cvt_ad (ap
); /* cvt addr */
378 if (dskad
== sec
) { /* match? */
379 if (rd
|| ((*ap
& FLAG
) == 0)) return psec
; /* read or !wprot? */
380 ind
[IN_DACH
] = ind
[IN_DERR
] = 1; /* no match */
384 ind
[IN_DACH
] = ind
[IN_DERR
] = 1; /* no match */
388 /* Find next sector - must be sequential, cannot cross cylinder boundary */
390 t_stat
dp_nexsec (UNIT
*uptr
, int32 sec
, int32 psec
, t_bool rd
)
392 int32 ctrk
= psec
% (DP_NUMSF
* DP_NUMSC
); /* curr trk-sec */
393 int32 da
= psec
* DP_NUMCH
; /* word number */
394 uint8
*ap
= ((uint8
*) uptr
->filebuf
) + da
; /* buf ptr */
397 if (ctrk
) { /* not trk zero? */
398 if (dp_zeroad (ap
)) return SCPE_OK
; /* addr zero? ok */
399 dskad
= dp_cvt_ad (ap
); /* cvt addr */
400 if ((dskad
== sec
) && /* match? */
401 (rd
|| ((*ap
& FLAG
) == 0))) return SCPE_OK
; /* read or !wprot? */
402 ind
[IN_DACH
] = ind
[IN_DERR
] = 1; /* no, error */
405 ind
[IN_DCYO
] = ind
[IN_DERR
] = 1; /* cyl overflow */
409 /* Test for zero address */
411 t_bool
dp_zeroad (uint8
*ap
)
415 for (i
= 0; i
< DP_ADDR
; i
++, ap
++) { /* loop thru addr */
416 if (*ap
& DIGIT
) return FALSE
; /* nonzero? lose */
418 return TRUE
; /* all zeroes */
421 /* Test for group mark when enabled */
423 t_stat
dp_tstgm (uint32 c
, int32 qnr
)
425 if (!qnr
&& ((c
& DIGIT
) == GRP_MARK
)) { /* premature GM? */
426 ind
[IN_DWLR
] = ind
[IN_DERR
] = 1; /* error */
432 /* Convert disk address to binary - invalid char force bad address */
434 int32
dp_cvt_ad (uint8
*ap
)
439 for (i
= r
= 0; i
< DP_ADDR
; i
++, ap
++) { /* loop thru addr */
440 c
= *ap
& DIGIT
; /* get digit */
441 if (BAD_DIGIT (c
)) return -1; /* bad digit? */
442 r
= (r
* 10) + c
; /* bcd to binary */
447 /* Track operation setup */
449 int32
dp_trkop (int32 drv
, int32 sec
)
451 int32 ctrk
= (sec
/ DP_NUMSC
) % DP_NUMSF
;
453 return ((drv
* DP_TOTSC
) + (dp_unit
[drv
].CYL
* DP_NUMSF
* DP_NUMSC
) +
457 /* Convert DCF BCD field to binary */
459 int32
dp_cvt_bcd (uint32 ad
, int32 len
)
464 for (r
= 0; len
> 0; len
--) { /* loop thru char */
465 c
= M
[ad
] & DIGIT
; /* get digit */
466 if (BAD_DIGIT (c
)) return -1; /* invalid? */
467 r
= (r
* 10) + c
; /* cvt to bin */
468 PP (ad
); /* next digit */
473 /* Fill sector buffer with zero */
475 void dp_fill (UNIT
*uptr
, uint32 da
, int32 cnt
)
477 while (cnt
-- > 0) { /* fill with zeroes*/
478 *(((uint8
*) uptr
->filebuf
) + da
) = 0;
479 if (da
>= uptr
->hwmark
) uptr
->hwmark
= da
+ 1;
487 t_stat
dp_reset (DEVICE
*dptr
)
491 for (i
= 0; i
< DP_NUMDR
; i
++) dp_unit
[i
].CYL
= 0; /* reset cylinder */
492 ind
[IN_DACH
] = ind
[IN_DWLR
] = 0; /* clr indicators */
493 ind
[IN_DERR
] = ind
[IN_DCYO
] = 0;