First Commit of my working state
[simh.git] / ALTAIR / altair_dsk.c
CommitLineData
196ba1fc
PH
1/* altair_dsk.c: MITS Altair 88-DISK Simulator\r
2\r
3 Copyright (c) 1997-2005, Charles E. Owen\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 Charles E. Owen 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 Charles E. Owen.\r
25\r
26 The 88_DISK is a 8-inch floppy controller which can control up\r
27 to 16 daisy-chained Pertec FD-400 hard-sectored floppy drives.\r
28 Each diskette has physically 77 tracks of 32 137-byte sectors\r
29 each.\r
30\r
31 The controller is interfaced to the CPU by use of 3 I/O addreses,\r
32 standardly, these are device numbers 10, 11, and 12 (octal).\r
33\r
34 Address Mode Function\r
35 ------- ---- --------\r
36\r
37 10 Out Selects and enables Controller and Drive\r
38 10 In Indicates status of Drive and Controller\r
39 11 Out Controls Disk Function\r
40 11 In Indicates current sector position of disk\r
41 12 Out Write data\r
42 12 In Read data\r
43\r
44 Drive Select Out (Device 10 OUT):\r
45\r
46 +---+---+---+---+---+---+---+---+\r
47 | C | X | X | X | Device |\r
48 +---+---+---+---+---+---+---+---+\r
49\r
50 C = If this bit is 1, the disk controller selected by 'device' is\r
51 cleared. If the bit is zero, 'device' is selected as the\r
52 device being controlled by subsequent I/O operations.\r
53 X = not used\r
54 Device = value zero thru 15, selects drive to be controlled.\r
55\r
56 Drive Status In (Device 10 IN):\r
57\r
58 +---+---+---+---+---+---+---+---+\r
59 | R | Z | I | X | X | H | M | W |\r
60 +---+---+---+---+---+---+---+---+\r
61\r
62 W - When 0, write circuit ready to write another byte.\r
63 M - When 0, head movement is allowed\r
64 H - When 0, indicates head is loaded for read/write\r
65 X - not used (will be 0)\r
66 I - When 0, indicates interrupts enabled (not used this simulator)\r
67 Z - When 0, indicates head is on track 0\r
68 R - When 0, indicates that read circuit has new byte to read\r
69\r
70 Drive Control (Device 11 OUT):\r
71\r
72 +---+---+---+---+---+---+---+---+\r
73 | W | C | D | E | U | H | O | I |\r
74 +---+---+---+---+---+---+---+---+\r
75\r
76 I - When 1, steps head IN one track\r
77 O - When 1, steps head OUT out track\r
78 H - When 1, loads head to drive surface\r
79 U - When 1, unloads head\r
80 E - Enables interrupts (ignored this simulator)\r
81 D - Disables interrupts (ignored this simulator)\r
82 C - When 1 lowers head current (ignored this simulator)\r
83 W - When 1, starts Write Enable sequence: W bit on device 10\r
84 (see above) will go 1 and data will be read from port 12\r
85 until 137 bytes have been read by the controller from\r
86 that port. The W bit will go off then, and the sector data\r
87 will be written to disk. Before you do this, you must have\r
88 stepped the track to the desired number, and waited until\r
89 the right sector number is presented on device 11 IN, then\r
90 set this bit.\r
91\r
92 Sector Position (Device 11 IN):\r
93\r
94 As the sectors pass by the read head, they are counted and the\r
95 number of the current one is available in this register.\r
96\r
97 +---+---+---+---+---+---+---+---+\r
98 | X | X | Sector Number | T |\r
99 +---+---+---+---+---+---+---+---+\r
100\r
101 X = Not used\r
102 Sector number = binary of the sector number currently under the\r
103 head, 0-31.\r
104 T = Sector True, is a 1 when the sector is positioned to read or\r
105 write.\r
106\r
107*/\r
108\r
109#include <stdio.h>\r
110\r
111#include "altair_defs.h"\r
112\r
113#define UNIT_V_ENABLE (UNIT_V_UF + 0) /* Write Enable */\r
114#define UNIT_ENABLE (1 << UNIT_V_ENABLE)\r
115\r
116#define DSK_SECTSIZE 137\r
117#define DSK_SECT 32\r
118#define DSK_TRACSIZE 4384\r
119#define DSK_SURF 1\r
120#define DSK_CYL 77\r
121#define DSK_SIZE (DSK_SECT * DSK_SURF * DSK_CYL * DSK_SECTSIZE)\r
122\r
123t_stat dsk_svc (UNIT *uptr);\r
124t_stat dsk_reset (DEVICE *dptr);\r
125void writebuf();\r
126\r
127extern int32 PCX;\r
128\r
129/* Global data on status */\r
130\r
131int32 cur_disk = 8; /* Currently selected drive */\r
132int32 cur_track[9] = {0, 0, 0, 0, 0, 0, 0, 0, 377};\r
133int32 cur_sect[9] = {0, 0, 0, 0, 0, 0, 0, 0, 377};\r
134int32 cur_byte[9] = {0, 0, 0, 0, 0, 0, 0, 0, 377};\r
135int32 cur_flags[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};\r
136\r
137char dskbuf[137]; /* Data Buffer */\r
138int32 dirty = 0; /* 1 when buffer has unwritten data in it */\r
139UNIT *dptr; /* fileref to write dirty buffer to */\r
140\r
141int32 dsk_rwait = 100; /* rotate latency */\r
142\r
143\r
144/* 88DSK Standard I/O Data Structures */\r
145\r
146UNIT dsk_unit[] = {\r
147 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r
148 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r
149 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r
150 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r
151 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r
152 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r
153 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) },\r
154 { UDATA (&dsk_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_DISABLE, DSK_SIZE) }\r
155};\r
156\r
157REG dsk_reg[] = {\r
158 { ORDATA (DISK, cur_disk, 4) },\r
159 { NULL }\r
160};\r
161\r
162DEVICE dsk_dev = {\r
163 "DSK", dsk_unit, dsk_reg, NULL,\r
164 8, 10, 31, 1, 8, 8,\r
165 NULL, NULL, &dsk_reset,\r
166 NULL, NULL, NULL\r
167};\r
168\r
169/* Service routines to handle simlulator functions */\r
170\r
171/* service routine - actually gets char & places in buffer */\r
172\r
173t_stat dsk_svc (UNIT *uptr)\r
174{\r
175return SCPE_OK;\r
176}\r
177\r
178/* Reset routine */\r
179\r
180t_stat dsk_reset (DEVICE *dptr)\r
181{\r
182cur_disk = 0;\r
183return SCPE_OK;\r
184}\r
185\r
186/* I/O instruction handlers, called from the CPU module when an\r
187 IN or OUT instruction is issued.\r
188\r
189 Each function is passed an 'io' flag, where 0 means a read from\r
190 the port, and 1 means a write to the port. On input, the actual\r
191 input is passed as the return value, on output, 'data' is written\r
192 to the device.\r
193*/\r
194\r
195/* Disk Controller Status/Select */\r
196\r
197/* IMPORTANT: The status flags read by port 8 IN instruction are\r
198 INVERTED, that is, 0 is true and 1 is false. To handle this, the\r
199 simulator keeps it's own status flags as 0=false, 1=true; and\r
200 returns the COMPLEMENT of the status flags when read. This makes\r
201 setting/testing of the flag bits more logical, yet meets the\r
202 simulation requirement that they are reversed in hardware.\r
203*/\r
204\r
205int32 dsk10(int32 io, int32 data)\r
206{\r
207\r
208 if (io == 0) { /* IN: return flags */\r
209 return ((~cur_flags[cur_disk]) & 0xFF); /* Return the COMPLEMENT! */\r
210 }\r
211\r
212 /* OUT: Controller set/reset/enable/disable */\r
213\r
214 if (dirty == 1)\r
215 writebuf();\r
216\r
217 /*printf("\n[%o] OUT 10: %x", PCX, data);*/\r
218 cur_disk = data & 0x0F;\r
219 if (data & 0x80) {\r
220 cur_flags[cur_disk] = 0; /* Disable drive */\r
221 cur_sect[cur_disk = 0377];\r
222 cur_byte[cur_disk = 0377];\r
223 return (0);\r
224 }\r
225 cur_flags[cur_disk] = 0x1A; /* Enable: head move true */\r
226 cur_sect[cur_disk] = 0377; /* reset internal counters */\r
227 cur_byte[cur_disk] = 0377;\r
228 if (cur_track[cur_disk] == 0)\r
229 cur_flags[cur_disk] |= 0x40; /* track 0 if there */\r
230 return (0);\r
231}\r
232\r
233/* Disk Drive Status/Functions */\r
234\r
235int32 dsk11(int32 io, int32 data)\r
236{\r
237 int32 stat;\r
238\r
239 if (io == 0) { /* Read sector position */\r
240 /*printf("\n[%o] IN 11", PCX);*/\r
241 if (dirty == 1)\r
242 writebuf();\r
243 if (cur_flags[cur_disk] & 0x04) { /* head loaded? */\r
244 cur_sect[cur_disk]++;\r
245 if (cur_sect[cur_disk] > 31)\r
246 cur_sect[cur_disk] = 0;\r
247 cur_byte[cur_disk] = 0377;\r
248 stat = cur_sect[cur_disk] << 1;\r
249 stat &= 0x3E; /* return 'sector true' bit = 0 (true) */\r
250 stat |= 0xC0; /* set on 'unused' bits */\r
251 return (stat);\r
252 } else {\r
253 return (0); /* head not loaded - return 0 */\r
254 }\r
255 }\r
256\r
257 /* Drive functions */\r
258\r
259 if (cur_disk > 7)\r
260 return (0); /* no drive selected - can do nothin */\r
261\r
262 /*printf("\n[%o] OUT 11: %x", PCX, data);*/\r
263 if (data & 0x01) { /* Step head in */\r
264 cur_track[cur_disk]++;\r
265 if (cur_track[cur_disk] > 76 )\r
266 cur_track[cur_disk] = 76;\r
267 if (dirty == 1)\r
268 writebuf();\r
269 cur_sect[cur_disk] = 0377;\r
270 cur_byte[cur_disk] = 0377;\r
271 }\r
272\r
273 if (data & 0x02) { /* Step head out */\r
274 cur_track[cur_disk]--;\r
275 if (cur_track[cur_disk] < 0) {\r
276 cur_track[cur_disk] = 0;\r
277 cur_flags[cur_disk] |= 0x40; /* track 0 if there */\r
278 }\r
279 if (dirty == 1)\r
280 writebuf();\r
281 cur_sect[cur_disk] = 0377;\r
282 cur_byte[cur_disk] = 0377;\r
283 }\r
284\r
285 if (dirty == 1)\r
286 writebuf();\r
287\r
288 if (data & 0x04) { /* Head load */\r
289 cur_flags[cur_disk] |= 0x04; /* turn on head loaded bit */\r
290 cur_flags[cur_disk] |= 0x80; /* turn on 'read data available */\r
291 }\r
292\r
293 if (data & 0x08) { /* Head Unload */\r
294 cur_flags[cur_disk] &= 0xFB; /* off on 'head loaded' */\r
295 cur_flags[cur_disk] &= 0x7F; /* off on 'read data avail */\r
296 cur_sect[cur_disk] = 0377;\r
297 cur_byte[cur_disk] = 0377;\r
298 }\r
299\r
300 /* Interrupts & head current are ignored */\r
301\r
302 if (data & 0x80) { /* write sequence start */\r
303 cur_byte[cur_disk] = 0;\r
304 cur_flags[cur_disk] |= 0x01; /* enter new write data on */\r
305 }\r
306 return 0;\r
307}\r
308\r
309/* Disk Data In/Out*/\r
310\r
311int32 dsk12(int32 io, int32 data)\r
312{\r
313 static int32 rtn, i;\r
314 static long pos;\r
315 UNIT *uptr;\r
316\r
317 uptr = dsk_dev.units + cur_disk;\r
318 if (io == 0) {\r
319 if ((i = cur_byte[cur_disk]) < 138) { /* just get from buffer */\r
320 cur_byte[cur_disk]++;\r
321 return (dskbuf[i] & 0xFF);\r
322 }\r
323 /* physically read the sector */\r
324 /*printf("\n[%o] IN 12 (READ) T%d S%d", PCX, cur_track[cur_disk],\r
325 cur_sect[cur_disk]);*/\r
326 pos = DSK_TRACSIZE * cur_track[cur_disk];\r
327 pos += DSK_SECTSIZE * cur_sect[cur_disk];\r
328 rtn = fseek(uptr -> fileref, pos, 0);\r
329 rtn = fread(dskbuf, 137, 1, uptr -> fileref);\r
330 cur_byte[cur_disk] = 1;\r
331 return (dskbuf[0] & 0xFF);\r
332 } else {\r
333 if (cur_byte[cur_disk] > 136) {\r
334 i = cur_byte[cur_disk];\r
335 dskbuf[i] = data & 0xFF;\r
336 writebuf();\r
337 return (0);\r
338 }\r
339 i = cur_byte[cur_disk];\r
340 dirty = 1;\r
341 dptr = uptr;\r
342 dskbuf[i] = data & 0xFF;\r
343 cur_byte[cur_disk]++;\r
344 return (0);\r
345 }\r
346}\r
347\r
348void writebuf()\r
349{\r
350 long pos;\r
351 int32 rtn, i;\r
352\r
353 i = cur_byte[cur_disk]; /* null-fill rest of sector if any */\r
354 while (i < 138) {\r
355 dskbuf[i] = 0;\r
356 i++;\r
357 }\r
358 /*printf("\n[%o] OUT 12 (WRITE) T%d S%d", PCX, cur_track[cur_disk],\r
359 cur_sect[cur_disk]); i = getch(); */\r
360 pos = DSK_TRACSIZE * cur_track[cur_disk]; /* calc file pos */\r
361 pos += DSK_SECTSIZE * cur_sect[cur_disk];\r
362 rtn = fseek(dptr -> fileref, pos, 0);\r
363 rtn = fwrite(dskbuf, 137, 1, dptr -> fileref);\r
364 cur_flags[cur_disk] &= 0xFE; /* ENWD off */\r
365 cur_byte[cur_disk] = 0377;\r
366 dirty = 0;\r
367 return;\r
368}\r