| 1 | /* altairz80_dsk.c: MITS Altair 88-DISK Simulator\r |
| 2 | \r |
| 3 | Copyright (c) 2002-2008, Peter Schorn\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 | PETER SCHORN 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 Peter Schorn shall not\r |
| 23 | be used in advertising or otherwise to promote the sale, use or other dealings\r |
| 24 | in this Software without prior written authorization from Peter Schorn.\r |
| 25 | \r |
| 26 | Based on work by Charles E Owen (c) 1997\r |
| 27 | \r |
| 28 | The 88_DISK is a 8-inch floppy controller which can control up\r |
| 29 | to 16 daisy-chained Pertec FD-400 hard-sectored floppy drives.\r |
| 30 | Each diskette has physically 77 tracks of 32 137-byte sectors\r |
| 31 | each.\r |
| 32 | \r |
| 33 | The controller is interfaced to the CPU by use of 3 I/O addresses,\r |
| 34 | standardly, these are device numbers 10, 11, and 12 (octal).\r |
| 35 | \r |
| 36 | Address Mode Function\r |
| 37 | ------- ---- --------\r |
| 38 | \r |
| 39 | 10 Out Selects and enables Controller and Drive\r |
| 40 | 10 In Indicates status of Drive and Controller\r |
| 41 | 11 Out Controls Disk Function\r |
| 42 | 11 In Indicates current sector position of disk\r |
| 43 | 12 Out Write data\r |
| 44 | 12 In Read data\r |
| 45 | \r |
| 46 | Drive Select Out (Device 10 OUT):\r |
| 47 | \r |
| 48 | +---+---+---+---+---+---+---+---+\r |
| 49 | | C | X | X | X | Device |\r |
| 50 | +---+---+---+---+---+---+---+---+\r |
| 51 | \r |
| 52 | C = If this bit is 1, the disk controller selected by 'device' is\r |
| 53 | cleared. If the bit is zero, 'device' is selected as the\r |
| 54 | device being controlled by subsequent I/O operations.\r |
| 55 | X = not used\r |
| 56 | Device = value zero thru 15, selects drive to be controlled.\r |
| 57 | \r |
| 58 | Drive Status In (Device 10 IN):\r |
| 59 | \r |
| 60 | +---+---+---+---+---+---+---+---+\r |
| 61 | | R | Z | I | X | X | H | M | W |\r |
| 62 | +---+---+---+---+---+---+---+---+\r |
| 63 | \r |
| 64 | W - When 0, write circuit ready to write another byte.\r |
| 65 | M - When 0, head movement is allowed\r |
| 66 | H - When 0, indicates head is loaded for read/write\r |
| 67 | X - not used (will be 0)\r |
| 68 | I - When 0, indicates interrupts enabled (not used by this simulator)\r |
| 69 | Z - When 0, indicates head is on track 0\r |
| 70 | R - When 0, indicates that read circuit has new byte to read\r |
| 71 | \r |
| 72 | Drive Control (Device 11 OUT):\r |
| 73 | \r |
| 74 | +---+---+---+---+---+---+---+---+\r |
| 75 | | W | C | D | E | U | H | O | I |\r |
| 76 | +---+---+---+---+---+---+---+---+\r |
| 77 | \r |
| 78 | I - When 1, steps head IN one track\r |
| 79 | O - When 1, steps head OUT one track\r |
| 80 | H - When 1, loads head to drive surface\r |
| 81 | U - When 1, unloads head\r |
| 82 | E - Enables interrupts (ignored by this simulator)\r |
| 83 | D - Disables interrupts (ignored by this simulator)\r |
| 84 | C - When 1 lowers head current (ignored by this simulator)\r |
| 85 | W - When 1, starts Write Enable sequence: W bit on device 10\r |
| 86 | (see above) will go 1 and data will be read from port 12\r |
| 87 | until 137 bytes have been read by the controller from\r |
| 88 | that port. The W bit will go off then, and the sector data\r |
| 89 | will be written to disk. Before you do this, you must have\r |
| 90 | stepped the track to the desired number, and waited until\r |
| 91 | the right sector number is presented on device 11 IN, then\r |
| 92 | set this bit.\r |
| 93 | \r |
| 94 | Sector Position (Device 11 IN):\r |
| 95 | \r |
| 96 | As the sectors pass by the read head, they are counted and the\r |
| 97 | number of the current one is available in this register.\r |
| 98 | \r |
| 99 | +---+---+---+---+---+---+---+---+\r |
| 100 | | X | X | Sector Number | T |\r |
| 101 | +---+---+---+---+---+---+---+---+\r |
| 102 | \r |
| 103 | X = Not used\r |
| 104 | Sector number = binary of the sector number currently under the\r |
| 105 | head, 0-31.\r |
| 106 | T = Sector True, is a 1 when the sector is positioned to read or\r |
| 107 | write.\r |
| 108 | \r |
| 109 | */\r |
| 110 | \r |
| 111 | #include "altairz80_defs.h"\r |
| 112 | #include <assert.h>\r |
| 113 | \r |
| 114 | #define UNIT_V_DSK_WLK (UNIT_V_UF + 0) /* write locked */\r |
| 115 | #define UNIT_DSK_WLK (1 << UNIT_V_DSK_WLK)\r |
| 116 | #define UNIT_V_DSK_VERBOSE (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages */\r |
| 117 | #define UNIT_DSK_VERBOSE (1 << UNIT_V_DSK_VERBOSE)\r |
| 118 | #define DSK_SECTSIZE 137 /* size of sector */\r |
| 119 | #define DSK_SECT 32 /* sectors per track */\r |
| 120 | #define MAX_TRACKS 254 /* number of tracks,\r |
| 121 | original Altair has 77 tracks only */\r |
| 122 | #define DSK_TRACSIZE (DSK_SECTSIZE * DSK_SECT)\r |
| 123 | #define MAX_DSK_SIZE (DSK_TRACSIZE * MAX_TRACKS)\r |
| 124 | #define TRACE_IN_OUT 1\r |
| 125 | #define TRACE_READ_WRITE 2\r |
| 126 | #define TRACE_SECTOR_STUCK 4\r |
| 127 | #define TRACE_TRACK_STUCK 8\r |
| 128 | #define NUM_OF_DSK_MASK (NUM_OF_DSK - 1)\r |
| 129 | #define BOOTROM_SIZE_DSK 256 /* size of boot rom */\r |
| 130 | \r |
| 131 | \r |
| 132 | int32 dsk10(const int32 port, const int32 io, const int32 data);\r |
| 133 | int32 dsk11(const int32 port, const int32 io, const int32 data);\r |
| 134 | int32 dsk12(const int32 port, const int32 io, const int32 data);\r |
| 135 | static t_stat dsk_boot(int32 unitno, DEVICE *dptr);\r |
| 136 | static t_stat dsk_reset(DEVICE *dptr);\r |
| 137 | static t_stat dsk_set_verbose(UNIT *uptr, int32 value, char *cptr, void *desc);\r |
| 138 | \r |
| 139 | extern REG *sim_PC;\r |
| 140 | extern UNIT cpu_unit;\r |
| 141 | extern char messageBuffer[];\r |
| 142 | extern uint32 PCX;\r |
| 143 | \r |
| 144 | extern void printMessage(void);\r |
| 145 | extern t_stat install_bootrom(int32 bootrom[], int32 size, int32 addr, int32 makeROM);\r |
| 146 | void install_ALTAIRbootROM(void);\r |
| 147 | \r |
| 148 | /* global data on status */\r |
| 149 | \r |
| 150 | /* currently selected drive (values are 0 .. NUM_OF_DSK)\r |
| 151 | current_disk < NUM_OF_DSK implies that the corresponding disk is attached to a file */\r |
| 152 | static int32 current_disk = NUM_OF_DSK;\r |
| 153 | static int32 current_track [NUM_OF_DSK] = {0, 0, 0, 0, 0, 0, 0, 0};\r |
| 154 | static int32 current_sector [NUM_OF_DSK] = {0, 0, 0, 0, 0, 0, 0, 0};\r |
| 155 | static int32 current_byte [NUM_OF_DSK] = {0, 0, 0, 0, 0, 0, 0, 0};\r |
| 156 | static int32 current_flag [NUM_OF_DSK] = {0, 0, 0, 0, 0, 0, 0, 0};\r |
| 157 | static uint8 tracks [NUM_OF_DSK] = { MAX_TRACKS, MAX_TRACKS, MAX_TRACKS, MAX_TRACKS,\r |
| 158 | MAX_TRACKS, MAX_TRACKS, MAX_TRACKS, MAX_TRACKS };\r |
| 159 | static int32 trace_level = 0;\r |
| 160 | static int32 in9_count = 0;\r |
| 161 | static int32 in9_message = FALSE;\r |
| 162 | static int32 dirty = FALSE; /* TRUE when buffer has unwritten data in it */\r |
| 163 | static int32 warnLevelDSK = 3;\r |
| 164 | static int32 warnLock [NUM_OF_DSK] = {0, 0, 0, 0, 0, 0, 0, 0};\r |
| 165 | static int32 warnAttached [NUM_OF_DSK] = {0, 0, 0, 0, 0, 0, 0, 0};\r |
| 166 | static int32 warnDSK10 = 0;\r |
| 167 | static int32 warnDSK11 = 0;\r |
| 168 | static int32 warnDSK12 = 0;\r |
| 169 | static int8 dskbuf[DSK_SECTSIZE]; /* data Buffer */\r |
| 170 | \r |
| 171 | /* Altair MITS modified BOOT EPROM, fits in upper 256 byte of memory */\r |
| 172 | int32 bootrom_dsk[BOOTROM_SIZE_DSK] = {\r |
| 173 | 0xf3, 0x06, 0x80, 0x3e, 0x0e, 0xd3, 0xfe, 0x05, /* ff00-ff07 */\r |
| 174 | 0xc2, 0x05, 0xff, 0x3e, 0x16, 0xd3, 0xfe, 0x3e, /* ff08-ff0f */\r |
| 175 | 0x12, 0xd3, 0xfe, 0xdb, 0xfe, 0xb7, 0xca, 0x20, /* ff10-ff17 */\r |
| 176 | 0xff, 0x3e, 0x0c, 0xd3, 0xfe, 0xaf, 0xd3, 0xfe, /* ff18-ff1f */\r |
| 177 | 0x21, 0x00, 0x5c, 0x11, 0x33, 0xff, 0x0e, 0x88, /* ff20-ff27 */\r |
| 178 | 0x1a, 0x77, 0x13, 0x23, 0x0d, 0xc2, 0x28, 0xff, /* ff28-ff2f */\r |
| 179 | 0xc3, 0x00, 0x5c, 0x31, 0x21, 0x5d, 0x3e, 0x00, /* ff30-ff37 */\r |
| 180 | 0xd3, 0x08, 0x3e, 0x04, 0xd3, 0x09, 0xc3, 0x19, /* ff38-ff3f */\r |
| 181 | 0x5c, 0xdb, 0x08, 0xe6, 0x02, 0xc2, 0x0e, 0x5c, /* ff40-ff47 */\r |
| 182 | 0x3e, 0x02, 0xd3, 0x09, 0xdb, 0x08, 0xe6, 0x40, /* ff48-ff4f */\r |
| 183 | 0xc2, 0x0e, 0x5c, 0x11, 0x00, 0x00, 0x06, 0x08, /* ff50-ff57 */\r |
| 184 | 0xc5, 0xd5, 0x11, 0x86, 0x80, 0x21, 0x88, 0x5c, /* ff58-ff5f */\r |
| 185 | 0xdb, 0x09, 0x1f, 0xda, 0x2d, 0x5c, 0xe6, 0x1f, /* ff60-ff67 */\r |
| 186 | 0xb8, 0xc2, 0x2d, 0x5c, 0xdb, 0x08, 0xb7, 0xfa, /* ff68-ff6f */\r |
| 187 | 0x39, 0x5c, 0xdb, 0x0a, 0x77, 0x23, 0x1d, 0xc2, /* ff70-ff77 */\r |
| 188 | 0x39, 0x5c, 0xd1, 0x21, 0x8b, 0x5c, 0x06, 0x80, /* ff78-ff7f */\r |
| 189 | 0x7e, 0x12, 0x23, 0x13, 0x05, 0xc2, 0x4d, 0x5c, /* ff80-ff87 */\r |
| 190 | 0xc1, 0x21, 0x00, 0x5c, 0x7a, 0xbc, 0xc2, 0x60, /* ff88-ff8f */\r |
| 191 | 0x5c, 0x7b, 0xbd, 0xd2, 0x80, 0x5c, 0x04, 0x04, /* ff90-ff97 */\r |
| 192 | 0x78, 0xfe, 0x20, 0xda, 0x25, 0x5c, 0x06, 0x01, /* ff98-ff9f */\r |
| 193 | 0xca, 0x25, 0x5c, 0xdb, 0x08, 0xe6, 0x02, 0xc2, /* ffa0-ffa7 */\r |
| 194 | 0x70, 0x5c, 0x3e, 0x01, 0xd3, 0x09, 0x06, 0x00, /* ffa8-ffaf */\r |
| 195 | 0xc3, 0x25, 0x5c, 0x3e, 0x80, 0xd3, 0x08, 0xfb, /* ffb0-ffb7 */\r |
| 196 | 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffb8-ffbf */\r |
| 197 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffc0-ffc7 */\r |
| 198 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffc8-ffcf */\r |
| 199 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffd0-ffd7 */\r |
| 200 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffd8-ffdf */\r |
| 201 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffe0-ffe7 */\r |
| 202 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* ffe8-ffef */\r |
| 203 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* fff0-fff7 */\r |
| 204 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* fff8-ffff */\r |
| 205 | };\r |
| 206 | \r |
| 207 | /* 88DSK Standard I/O Data Structures */\r |
| 208 | \r |
| 209 | static UNIT dsk_unit[] = {\r |
| 210 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) },\r |
| 211 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) },\r |
| 212 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) },\r |
| 213 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) },\r |
| 214 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) },\r |
| 215 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) },\r |
| 216 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) },\r |
| 217 | { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, MAX_DSK_SIZE) }\r |
| 218 | };\r |
| 219 | \r |
| 220 | static REG dsk_reg[] = {\r |
| 221 | { DRDATA (DISK, current_disk, 4) },\r |
| 222 | { BRDATA (CURTRACK, current_track, 10, 32, NUM_OF_DSK), REG_CIRC + REG_RO },\r |
| 223 | { BRDATA (CURSECTOR, current_sector, 10, 32, NUM_OF_DSK), REG_CIRC + REG_RO },\r |
| 224 | { BRDATA (CURBYTE, current_byte, 10, 32, NUM_OF_DSK), REG_CIRC + REG_RO },\r |
| 225 | { BRDATA (CURFLAG, current_flag, 10, 32, NUM_OF_DSK), REG_CIRC + REG_RO },\r |
| 226 | { BRDATA (TRACKS, tracks, 10, 8, NUM_OF_DSK), REG_CIRC },\r |
| 227 | { HRDATA (TRACELEVEL, trace_level, 16) },\r |
| 228 | { DRDATA (IN9COUNT, in9_count, 4), REG_RO },\r |
| 229 | { DRDATA (IN9MESSAGE, in9_message, 4), REG_RO },\r |
| 230 | { DRDATA (DIRTY, dirty, 4), REG_RO },\r |
| 231 | { DRDATA (DSKWL, warnLevelDSK, 32) },\r |
| 232 | { BRDATA (WARNLOCK, warnLock, 10, 32, NUM_OF_DSK), REG_CIRC + REG_RO },\r |
| 233 | { BRDATA (WARNATTACHED, warnAttached, 10, 32, NUM_OF_DSK), REG_CIRC + REG_RO },\r |
| 234 | { DRDATA (WARNDSK10, warnDSK10, 4), REG_RO },\r |
| 235 | { DRDATA (WARNDSK11, warnDSK11, 4), REG_RO },\r |
| 236 | { DRDATA (WARNDSK12, warnDSK12, 4), REG_RO },\r |
| 237 | { BRDATA (DISKBUFFER, dskbuf, 10, 8, DSK_SECTSIZE), REG_CIRC + REG_RO },\r |
| 238 | { NULL }\r |
| 239 | };\r |
| 240 | \r |
| 241 | static MTAB dsk_mod[] = {\r |
| 242 | { UNIT_DSK_WLK, 0, "WRTENB", "WRTENB", NULL },\r |
| 243 | { UNIT_DSK_WLK, UNIT_DSK_WLK, "WRTLCK", "WRTLCK", NULL },\r |
| 244 | /* quiet, no warning messages */\r |
| 245 | { UNIT_DSK_VERBOSE, 0, "QUIET", "QUIET", NULL },\r |
| 246 | /* verbose, show warning messages */\r |
| 247 | { UNIT_DSK_VERBOSE, UNIT_DSK_VERBOSE, "VERBOSE", "VERBOSE", &dsk_set_verbose },\r |
| 248 | { 0 }\r |
| 249 | };\r |
| 250 | \r |
| 251 | DEVICE dsk_dev = {\r |
| 252 | "DSK", dsk_unit, dsk_reg, dsk_mod,\r |
| 253 | 8, 10, 31, 1, 8, 8,\r |
| 254 | NULL, NULL, &dsk_reset,\r |
| 255 | &dsk_boot, NULL, NULL,\r |
| 256 | NULL, (DEV_DISABLE), 0,\r |
| 257 | NULL, NULL, NULL\r |
| 258 | };\r |
| 259 | \r |
| 260 | static void resetDSKWarningFlags(void) {\r |
| 261 | int32 i;\r |
| 262 | for (i = 0; i < NUM_OF_DSK; i++) {\r |
| 263 | warnLock[i] = 0;\r |
| 264 | warnAttached[i] = 0;\r |
| 265 | }\r |
| 266 | warnDSK10 = 0;\r |
| 267 | warnDSK11 = 0;\r |
| 268 | warnDSK12 = 0;\r |
| 269 | }\r |
| 270 | \r |
| 271 | static t_stat dsk_set_verbose(UNIT *uptr, int32 value, char *cptr, void *desc) {\r |
| 272 | resetDSKWarningFlags();\r |
| 273 | return SCPE_OK;\r |
| 274 | }\r |
| 275 | \r |
| 276 | /* returns TRUE iff there exists a disk with VERBOSE */\r |
| 277 | static int32 hasVerbose(void) {\r |
| 278 | int32 i;\r |
| 279 | for (i = 0; i < NUM_OF_DSK; i++)\r |
| 280 | if (((dsk_dev.units + i) -> flags) & UNIT_DSK_VERBOSE) return TRUE;\r |
| 281 | return FALSE;\r |
| 282 | }\r |
| 283 | \r |
| 284 | static char* selectInOut(const int32 io) {\r |
| 285 | return io == 0 ? "IN" : "OUT";\r |
| 286 | }\r |
| 287 | \r |
| 288 | /* service routines to handle simulator functions */\r |
| 289 | /* reset routine */\r |
| 290 | \r |
| 291 | static t_stat dsk_reset(DEVICE *dptr) {\r |
| 292 | resetDSKWarningFlags();\r |
| 293 | current_disk = NUM_OF_DSK;\r |
| 294 | trace_level = 0;\r |
| 295 | in9_count = 0;\r |
| 296 | in9_message = FALSE;\r |
| 297 | return SCPE_OK;\r |
| 298 | }\r |
| 299 | \r |
| 300 | void install_ALTAIRbootROM(void) {\r |
| 301 | assert(install_bootrom(bootrom_dsk, BOOTROM_SIZE_DSK, ALTAIR_ROM_LOW, TRUE) == SCPE_OK);\r |
| 302 | }\r |
| 303 | \r |
| 304 | /* The boot routine modifies the boot ROM in such a way that subsequently\r |
| 305 | the specified disk is used for boot purposes.\r |
| 306 | */\r |
| 307 | static t_stat dsk_boot(int32 unitno, DEVICE *dptr) {\r |
| 308 | if (cpu_unit.flags & (UNIT_CPU_ALTAIRROM | UNIT_CPU_BANKED)) {\r |
| 309 | /* check whether we are really modifying an LD A,<> instruction */\r |
| 310 | if ((bootrom_dsk[UNIT_NO_OFFSET_1 - 1] == LDA_INSTRUCTION) && (bootrom_dsk[UNIT_NO_OFFSET_2 - 1] == LDA_INSTRUCTION)) {\r |
| 311 | bootrom_dsk[UNIT_NO_OFFSET_1] = unitno & 0xff; /* LD A,<unitno> */\r |
| 312 | bootrom_dsk[UNIT_NO_OFFSET_2] = 0x80 | (unitno & 0xff); /* LD a,80h | <unitno> */\r |
| 313 | }\r |
| 314 | else { /* Attempt to modify non LD A,<> instructions is refused. */\r |
| 315 | printf("Incorrect boot ROM offsets detected.\n");\r |
| 316 | return SCPE_IERR;\r |
| 317 | }\r |
| 318 | install_ALTAIRbootROM(); /* install modified ROM */\r |
| 319 | }\r |
| 320 | *((int32 *) sim_PC->loc) = ALTAIR_ROM_LOW;\r |
| 321 | return SCPE_OK;\r |
| 322 | }\r |
| 323 | \r |
| 324 | static int32 dskseek(const UNIT *xptr) {\r |
| 325 | return fseek(xptr -> fileref, DSK_TRACSIZE * current_track[current_disk] +\r |
| 326 | DSK_SECTSIZE * current_sector[current_disk], SEEK_SET);\r |
| 327 | }\r |
| 328 | \r |
| 329 | /* precondition: current_disk < NUM_OF_DSK */\r |
| 330 | static void writebuf(void) {\r |
| 331 | int32 i, rtn;\r |
| 332 | UNIT *uptr;\r |
| 333 | i = current_byte[current_disk]; /* null-fill rest of sector if any */\r |
| 334 | while (i < DSK_SECTSIZE)\r |
| 335 | dskbuf[i++] = 0;\r |
| 336 | uptr = dsk_dev.units + current_disk;\r |
| 337 | if (((uptr -> flags) & UNIT_DSK_WLK) == 0) { /* write enabled */\r |
| 338 | if (trace_level & TRACE_READ_WRITE) {\r |
| 339 | MESSAGE_4("OUT 0x0a (WRITE) D%d T%d S%d", current_disk, current_track[current_disk], current_sector[current_disk]);\r |
| 340 | }\r |
| 341 | if (dskseek(uptr)) {\r |
| 342 | MESSAGE_4("fseek failed D%d T%d S%d", current_disk, current_track[current_disk], current_sector[current_disk]);\r |
| 343 | }\r |
| 344 | rtn = fwrite(dskbuf, DSK_SECTSIZE, 1, uptr -> fileref);\r |
| 345 | if (rtn != 1) {\r |
| 346 | MESSAGE_4("fwrite failed T%d S%d Return=%d", current_track[current_disk], current_sector[current_disk], rtn);\r |
| 347 | }\r |
| 348 | }\r |
| 349 | else if ( ((uptr -> flags) & UNIT_DSK_VERBOSE) && (warnLock[current_disk] < warnLevelDSK) ) {\r |
| 350 | /* write locked - print warning message if required */\r |
| 351 | warnLock[current_disk]++;\r |
| 352 | /*05*/ MESSAGE_2("Attempt to write to locked DSK%d - ignored.", current_disk);\r |
| 353 | }\r |
| 354 | current_flag[current_disk] &= 0xfe; /* ENWD off */\r |
| 355 | current_byte[current_disk] = 0xff;\r |
| 356 | dirty = FALSE;\r |
| 357 | }\r |
| 358 | \r |
| 359 | /* I/O instruction handlers, called from the CPU module when an\r |
| 360 | IN or OUT instruction is issued.\r |
| 361 | \r |
| 362 | Each function is passed an 'io' flag, where 0 means a read from\r |
| 363 | the port, and 1 means a write to the port. On input, the actual\r |
| 364 | input is passed as the return value, on output, 'data' is written\r |
| 365 | to the device.\r |
| 366 | */\r |
| 367 | \r |
| 368 | /* Disk Controller Status/Select */\r |
| 369 | \r |
| 370 | /* IMPORTANT: The status flags read by port 8 IN instruction are\r |
| 371 | INVERTED, that is, 0 is true and 1 is false. To handle this, the\r |
| 372 | simulator keeps it's own status flags as 0=false, 1=true; and\r |
| 373 | returns the COMPLEMENT of the status flags when read. This makes\r |
| 374 | setting/testing of the flag bits more logical, yet meets the\r |
| 375 | simulation requirement that they are reversed in hardware.\r |
| 376 | */\r |
| 377 | \r |
| 378 | int32 dsk10(const int32 port, const int32 io, const int32 data) {\r |
| 379 | int32 current_disk_flags;\r |
| 380 | in9_count = 0;\r |
| 381 | if (io == 0) { /* IN: return flags */\r |
| 382 | if (current_disk >= NUM_OF_DSK) {\r |
| 383 | if (hasVerbose() && (warnDSK10 < warnLevelDSK)) {\r |
| 384 | warnDSK10++;\r |
| 385 | /*01*/ MESSAGE_1("Attempt of IN 0x08 on unattached disk - ignored.");\r |
| 386 | }\r |
| 387 | return 0xff; /* no drive selected - can do nothing */\r |
| 388 | }\r |
| 389 | return (~current_flag[current_disk]) & 0xff; /* return the COMPLEMENT! */\r |
| 390 | }\r |
| 391 | \r |
| 392 | /* OUT: Controller set/reset/enable/disable */\r |
| 393 | if (dirty) /* implies that current_disk < NUM_OF_DSK */\r |
| 394 | writebuf();\r |
| 395 | if (trace_level & TRACE_IN_OUT) {\r |
| 396 | MESSAGE_2("OUT 0x08: %x", data);\r |
| 397 | }\r |
| 398 | current_disk = data & NUM_OF_DSK_MASK; /* 0 <= current_disk < NUM_OF_DSK */\r |
| 399 | current_disk_flags = (dsk_dev.units + current_disk) -> flags;\r |
| 400 | if ((current_disk_flags & UNIT_ATT) == 0) { /* nothing attached? */\r |
| 401 | if ( (current_disk_flags & UNIT_DSK_VERBOSE) && (warnAttached[current_disk] < warnLevelDSK) ) {\r |
| 402 | warnAttached[current_disk]++;\r |
| 403 | /*02*/ MESSAGE_2("Attempt to select unattached DSK%d - ignored.", current_disk);\r |
| 404 | }\r |
| 405 | current_disk = NUM_OF_DSK;\r |
| 406 | }\r |
| 407 | else {\r |
| 408 | current_sector[current_disk] = 0xff; /* reset internal counters */\r |
| 409 | current_byte[current_disk] = 0xff;\r |
| 410 | current_flag[current_disk] = data & 0x80 ? 0 /* disable drive */ :\r |
| 411 | (current_track[current_disk] == 0 ? 0x5a /* enable: head move true, track 0 if there */ :\r |
| 412 | 0x1a); /* enable: head move true */\r |
| 413 | }\r |
| 414 | return 0; /* ignored since OUT */\r |
| 415 | }\r |
| 416 | \r |
| 417 | /* Disk Drive Status/Functions */\r |
| 418 | \r |
| 419 | int32 dsk11(const int32 port, const int32 io, const int32 data) {\r |
| 420 | if (current_disk >= NUM_OF_DSK) {\r |
| 421 | if (hasVerbose() && (warnDSK11 < warnLevelDSK)) {\r |
| 422 | warnDSK11++;\r |
| 423 | /*03*/ MESSAGE_2("Attempt of %s 0x09 on unattached disk - ignored.", selectInOut(io));\r |
| 424 | }\r |
| 425 | return 0; /* no drive selected - can do nothing */\r |
| 426 | }\r |
| 427 | \r |
| 428 | /* now current_disk < NUM_OF_DSK */\r |
| 429 | if (io == 0) { /* read sector position */\r |
| 430 | in9_count++;\r |
| 431 | if ((trace_level & TRACE_SECTOR_STUCK) && (in9_count > 2 * DSK_SECT) && (!in9_message)) {\r |
| 432 | in9_message = TRUE;\r |
| 433 | MESSAGE_2("Looping on sector find %d.", current_disk);\r |
| 434 | }\r |
| 435 | if (trace_level & TRACE_IN_OUT) {\r |
| 436 | MESSAGE_1("IN 0x09");\r |
| 437 | }\r |
| 438 | if (dirty) /* implies that current_disk < NUM_OF_DSK */\r |
| 439 | writebuf();\r |
| 440 | if (current_flag[current_disk] & 0x04) { /* head loaded? */\r |
| 441 | current_sector[current_disk]++;\r |
| 442 | if (current_sector[current_disk] >= DSK_SECT)\r |
| 443 | current_sector[current_disk] = 0;\r |
| 444 | current_byte[current_disk] = 0xff;\r |
| 445 | return (((current_sector[current_disk] << 1) & 0x3e) /* return 'sector true' bit = 0 (true) */\r |
| 446 | | 0xc0); /* set on 'unused' bits */\r |
| 447 | } else return 0; /* head not loaded - return 0 */\r |
| 448 | }\r |
| 449 | \r |
| 450 | in9_count = 0;\r |
| 451 | /* drive functions */\r |
| 452 | \r |
| 453 | if (trace_level & TRACE_IN_OUT) {\r |
| 454 | MESSAGE_2("OUT 0x09: %x", data);\r |
| 455 | }\r |
| 456 | if (data & 0x01) { /* step head in */\r |
| 457 | if ((trace_level & TRACE_TRACK_STUCK) && (current_track[current_disk] == (tracks[current_disk] - 1))) {\r |
| 458 | MESSAGE_2("Unnecessary step in for disk %d", current_disk);\r |
| 459 | }\r |
| 460 | current_track[current_disk]++;\r |
| 461 | if (current_track[current_disk] > (tracks[current_disk] - 1))\r |
| 462 | current_track[current_disk] = (tracks[current_disk] - 1);\r |
| 463 | if (dirty) /* implies that current_disk < NUM_OF_DSK */\r |
| 464 | writebuf();\r |
| 465 | current_sector[current_disk] = 0xff;\r |
| 466 | current_byte[current_disk] = 0xff;\r |
| 467 | }\r |
| 468 | \r |
| 469 | if (data & 0x02) { /* step head out */\r |
| 470 | if ((trace_level & TRACE_TRACK_STUCK) && (current_track[current_disk] == 0)) {\r |
| 471 | MESSAGE_2("Unnecessary step out for disk %d", current_disk);\r |
| 472 | }\r |
| 473 | current_track[current_disk]--;\r |
| 474 | if (current_track[current_disk] < 0) {\r |
| 475 | current_track[current_disk] = 0;\r |
| 476 | current_flag[current_disk] |= 0x40; /* track 0 if there */\r |
| 477 | }\r |
| 478 | if (dirty) /* implies that current_disk < NUM_OF_DSK */\r |
| 479 | writebuf();\r |
| 480 | current_sector[current_disk] = 0xff;\r |
| 481 | current_byte[current_disk] = 0xff;\r |
| 482 | }\r |
| 483 | \r |
| 484 | if (dirty) /* implies that current_disk < NUM_OF_DSK */\r |
| 485 | writebuf();\r |
| 486 | \r |
| 487 | if (data & 0x04) { /* head load */\r |
| 488 | current_flag[current_disk] |= 0x04; /* turn on head loaded bit */\r |
| 489 | current_flag[current_disk] |= 0x80; /* turn on 'read data available' */\r |
| 490 | }\r |
| 491 | \r |
| 492 | if (data & 0x08) { /* head unload */\r |
| 493 | current_flag[current_disk] &= 0xfb; /* turn off 'head loaded' bit */\r |
| 494 | current_flag[current_disk] &= 0x7f; /* turn off 'read data available' */\r |
| 495 | current_sector[current_disk] = 0xff;\r |
| 496 | current_byte[current_disk] = 0xff;\r |
| 497 | }\r |
| 498 | \r |
| 499 | /* interrupts & head current are ignored */\r |
| 500 | \r |
| 501 | if (data & 0x80) { /* write sequence start */\r |
| 502 | current_byte[current_disk] = 0;\r |
| 503 | current_flag[current_disk] |= 0x01; /* enter new write data on */\r |
| 504 | }\r |
| 505 | return 0; /* ignored since OUT */\r |
| 506 | }\r |
| 507 | \r |
| 508 | /* Disk Data In/Out */\r |
| 509 | \r |
| 510 | int32 dsk12(const int32 port, const int32 io, const int32 data) {\r |
| 511 | int32 i;\r |
| 512 | UNIT *uptr;\r |
| 513 | \r |
| 514 | if (current_disk >= NUM_OF_DSK) {\r |
| 515 | if (hasVerbose() && (warnDSK12 < warnLevelDSK)) {\r |
| 516 | warnDSK12++;\r |
| 517 | /*04*/ MESSAGE_2("Attempt of %s 0x0a on unattached disk - ignored.", selectInOut(io));\r |
| 518 | }\r |
| 519 | return 0;\r |
| 520 | }\r |
| 521 | \r |
| 522 | /* now current_disk < NUM_OF_DSK */\r |
| 523 | in9_count = 0;\r |
| 524 | uptr = dsk_dev.units + current_disk;\r |
| 525 | if (io == 0) {\r |
| 526 | if (current_byte[current_disk] >= DSK_SECTSIZE) {\r |
| 527 | /* physically read the sector */\r |
| 528 | if (trace_level & TRACE_READ_WRITE) {\r |
| 529 | MESSAGE_4("IN 0x0a (READ) D%d T%d S%d", current_disk, current_track[current_disk], current_sector[current_disk]);\r |
| 530 | }\r |
| 531 | for (i = 0; i < DSK_SECTSIZE; i++)\r |
| 532 | dskbuf[i] = 0;\r |
| 533 | dskseek(uptr);\r |
| 534 | fread(dskbuf, DSK_SECTSIZE, 1, uptr -> fileref);\r |
| 535 | current_byte[current_disk] = 0;\r |
| 536 | }\r |
| 537 | return dskbuf[current_byte[current_disk]++] & 0xff;\r |
| 538 | }\r |
| 539 | else {\r |
| 540 | if (current_byte[current_disk] >= DSK_SECTSIZE)\r |
| 541 | writebuf(); /* from above we have that current_disk < NUM_OF_DSK */\r |
| 542 | else {\r |
| 543 | dirty = TRUE; /* this guarantees for the next call to writebuf that current_disk < NUM_OF_DSK */\r |
| 544 | dskbuf[current_byte[current_disk]++] = data & 0xff;\r |
| 545 | }\r |
| 546 | return 0; /* ignored since OUT */\r |
| 547 | }\r |
| 548 | }\r |