trunk/src/mess/machine/990_tap.c
| r26090 | r26091 | |
| 1 | | /* |
| 2 | | 990_tap.c: emulation of a generic ti990 tape controller, for use with |
| 3 | | TILINE-based TI990 systems (TI990/10, /12, /12LR, /10A, Business system 300 |
| 4 | | and 300A). |
| 5 | | |
| 6 | | This core will emulate the common feature set found in every tape controller. |
| 7 | | Most controllers support additional features, but are still compatible with |
| 8 | | the basic feature set. I have a little documentation on two specific |
| 9 | | tape controllers (MT3200 and WD800/WD800A), but I have not tried to emulate |
| 10 | | controller-specific features. |
| 11 | | |
| 12 | | |
| 13 | | Long description: see 2234398-9701 and 2306140-9701. |
| 14 | | |
| 15 | | |
| 16 | | Raphael Nabet 2002 |
| 17 | | */ |
| 18 | | /* |
| 19 | | Image encoding: |
| 20 | | |
| 21 | | |
| 22 | | 2 bytes: record len - little-endian |
| 23 | | 2 bytes: always 0s (length MSBs?) |
| 24 | | len bytes: data |
| 25 | | 2 bytes: record len - little-endian |
| 26 | | 2 bytes: always 0s (length MSBs?) |
| 27 | | |
| 28 | | 4 0s: EOF mark |
| 29 | | */ |
| 30 | | |
| 31 | | #include "emu.h" |
| 32 | | #include "990_tap.h" |
| 33 | | #include "devlegcy.h" |
| 34 | | |
| 35 | | |
| 36 | | static void update_interrupt(device_t *device); |
| 37 | | |
| 38 | | #define MAX_TAPE_UNIT 4 |
| 39 | | |
| 40 | | struct tape_unit_t |
| 41 | | { |
| 42 | | device_image_interface *img; /* image descriptor */ |
| 43 | | unsigned int bot : 1; /* TRUE if we are at the beginning of tape */ |
| 44 | | unsigned int eot : 1; /* TRUE if we are at the end of tape */ |
| 45 | | unsigned int wp : 1; /* TRUE if tape is write-protected */ |
| 46 | | }; |
| 47 | | |
| 48 | | struct tap_990_t |
| 49 | | { |
| 50 | | UINT16 w[8]; |
| 51 | | |
| 52 | | const ti990_tpc_interface *intf; |
| 53 | | |
| 54 | | tape_unit_t t[MAX_TAPE_UNIT]; |
| 55 | | }; |
| 56 | | |
| 57 | | struct ti990_tape_t |
| 58 | | { |
| 59 | | int dummy; |
| 60 | | }; |
| 61 | | |
| 62 | | enum |
| 63 | | { |
| 64 | | w0_offline = 0x8000, |
| 65 | | w0_BOT = 0x4000, |
| 66 | | w0_EOR = 0x2000, |
| 67 | | w0_EOF = 0x1000, |
| 68 | | w0_EOT = 0x0800, |
| 69 | | w0_write_ring = 0x0400, |
| 70 | | w0_tape_rewinding = 0x0200, |
| 71 | | w0_command_timeout = 0x0100, |
| 72 | | |
| 73 | | w0_rewind_status = 0x00f0, |
| 74 | | w0_rewind_mask = 0x000f, |
| 75 | | |
| 76 | | w6_unit0_sel = 0x8000, |
| 77 | | w6_unit1_sel = 0x4000, |
| 78 | | w6_unit2_sel = 0x2000, |
| 79 | | w6_unit3_sel = 0x1000, |
| 80 | | w6_command = 0x0f00, |
| 81 | | |
| 82 | | w7_idle = 0x8000, |
| 83 | | w7_complete = 0x4000, |
| 84 | | w7_error = 0x2000, |
| 85 | | w7_int_enable = 0x1000, |
| 86 | | w7_PE_format = 0x0200, |
| 87 | | w7_abnormal_completion = 0x0100, |
| 88 | | w7_interface_parity_err = 0x0080, |
| 89 | | w7_err_correction_enabled = 0x0040, |
| 90 | | w7_hard_error = 0x0020, |
| 91 | | w7_tiline_parity_err = 0x0010, |
| 92 | | w7_tiline_timing_err = 0x0008, |
| 93 | | w7_tiline_timeout_err = 0x0004, |
| 94 | | /*w7_format_error = 0x0002,*/ |
| 95 | | w7_tape_error = 0x0001 |
| 96 | | }; |
| 97 | | |
| 98 | | static const UINT16 w_mask[8] = |
| 99 | | { |
| 100 | | 0x000f, /* Controllers should prevent overwriting of w0 status bits, and I know |
| 101 | | that some controllers do so. */ |
| 102 | | 0xffff, |
| 103 | | 0xffff, |
| 104 | | 0xffff, |
| 105 | | 0xffff, |
| 106 | | 0xffff, |
| 107 | | 0xffff, |
| 108 | | 0xf3ff /* Don't overwrite reserved bits */ |
| 109 | | }; |
| 110 | | |
| 111 | | static int tape_get_id(device_t *image) |
| 112 | | { |
| 113 | | int drive =0; |
| 114 | | if (strcmp(image->tag(), ":tape0") == 0) drive = 0; |
| 115 | | if (strcmp(image->tag(), ":tape1") == 0) drive = 1; |
| 116 | | if (strcmp(image->tag(), ":tape2") == 0) drive = 2; |
| 117 | | if (strcmp(image->tag(), ":tape3") == 0) drive = 3; |
| 118 | | return drive; |
| 119 | | } |
| 120 | | |
| 121 | | /***************************************************************************** |
| 122 | | INLINE FUNCTIONS |
| 123 | | *****************************************************************************/ |
| 124 | | INLINE tap_990_t *get_safe_token(device_t *device) |
| 125 | | { |
| 126 | | assert(device != NULL); |
| 127 | | assert(device->type() == TI990_TAPE_CTRL); |
| 128 | | |
| 129 | | return (tap_990_t *)downcast<tap_990_device *>(device)->token(); |
| 130 | | } |
| 131 | | |
| 132 | | |
| 133 | | /* |
| 134 | | Parse the tape select lines, and return the corresponding tape unit. |
| 135 | | (-1 if none) |
| 136 | | */ |
| 137 | | static int cur_tape_unit(device_t *device) |
| 138 | | { |
| 139 | | int reply; |
| 140 | | tap_990_t *tpc = get_safe_token(device); |
| 141 | | |
| 142 | | if (tpc->w[6] & w6_unit0_sel) |
| 143 | | reply = 0; |
| 144 | | else if (tpc->w[6] & w6_unit1_sel) |
| 145 | | reply = 1; |
| 146 | | else if (tpc->w[6] & w6_unit2_sel) |
| 147 | | reply = 2; |
| 148 | | else if (tpc->w[6] & w6_unit3_sel) |
| 149 | | reply = 3; |
| 150 | | else |
| 151 | | reply = -1; |
| 152 | | |
| 153 | | if (reply >= MAX_TAPE_UNIT) |
| 154 | | reply = -1; |
| 155 | | |
| 156 | | return reply; |
| 157 | | } |
| 158 | | |
| 159 | | /* |
| 160 | | Update interrupt state |
| 161 | | */ |
| 162 | | static void update_interrupt(device_t *device) |
| 163 | | { |
| 164 | | tap_990_t *tpc = get_safe_token(device); |
| 165 | | if (tpc->intf->interrupt_callback) |
| 166 | | (*tpc->intf->interrupt_callback)(device->machine(), (tpc->w[7] & w7_idle) |
| 167 | | && (((tpc->w[7] & w7_int_enable) && (tpc->w[7] & (w7_complete | w7_error))) |
| 168 | | || ((tpc->w[0] & ~(tpc->w[0] >> 4)) & w0_rewind_mask))); |
| 169 | | } |
| 170 | | |
| 171 | | /* |
| 172 | | Handle the read binary forward command: read the next record on tape. |
| 173 | | */ |
| 174 | | static void cmd_read_binary_forward(device_t *device) |
| 175 | | { |
| 176 | | UINT8 buffer[256]; |
| 177 | | int reclen; |
| 178 | | |
| 179 | | int dma_address; |
| 180 | | int char_count; |
| 181 | | int read_offset; |
| 182 | | |
| 183 | | int rec_count = 0; |
| 184 | | int chunk_len; |
| 185 | | int bytes_to_read; |
| 186 | | int bytes_read; |
| 187 | | int i; |
| 188 | | tap_990_t *tpc = get_safe_token(device); |
| 189 | | int tap_sel = cur_tape_unit(device); |
| 190 | | |
| 191 | | if (tap_sel == -1) |
| 192 | | { |
| 193 | | /* No idea what to report... */ |
| 194 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 195 | | update_interrupt(device); |
| 196 | | return; |
| 197 | | } |
| 198 | | else if (! tpc->t[tap_sel].img->exists()) |
| 199 | | { /* offline */ |
| 200 | | tpc->w[0] |= w0_offline; |
| 201 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 202 | | update_interrupt(device); |
| 203 | | return; |
| 204 | | } |
| 205 | | #if 0 |
| 206 | | else if (0) |
| 207 | | { /* rewind in progress */ |
| 208 | | tpc->w[0] |= 0x80 >> tap_sel; |
| 209 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 210 | | update_interrupt(device); |
| 211 | | return; |
| 212 | | } |
| 213 | | #endif |
| 214 | | |
| 215 | | tpc->t[tap_sel].bot = 0; |
| 216 | | |
| 217 | | dma_address = ((((int) tpc->w[6]) << 16) | tpc->w[5]) & 0x1ffffe; |
| 218 | | char_count = tpc->w[4]; |
| 219 | | read_offset = tpc->w[3]; |
| 220 | | |
| 221 | | bytes_read = tpc->t[tap_sel].img->fread(buffer, 4); |
| 222 | | if (bytes_read != 4) |
| 223 | | { |
| 224 | | if (bytes_read == 0) |
| 225 | | { /* legitimate EOF */ |
| 226 | | tpc->t[tap_sel].eot = 1; |
| 227 | | tpc->w[0] |= w0_EOT; /* or should it be w0_command_timeout? */ |
| 228 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 229 | | update_interrupt(device); |
| 230 | | goto update_registers; |
| 231 | | } |
| 232 | | else |
| 233 | | { /* illegitimate EOF */ |
| 234 | | /* No idea what to report... */ |
| 235 | | /* eject tape to avoid catastrophes */ |
| 236 | | logerror("Tape error\n"); |
| 237 | | tpc->t[tap_sel].img->unload(); |
| 238 | | tpc->w[0] |= w0_offline; |
| 239 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 240 | | update_interrupt(device); |
| 241 | | goto update_registers; |
| 242 | | } |
| 243 | | } |
| 244 | | reclen = (((int) buffer[1]) << 8) | buffer[0]; |
| 245 | | if (buffer[2] || buffer[3]) |
| 246 | | { /* no idea what these bytes mean */ |
| 247 | | logerror("Tape error\n"); |
| 248 | | logerror("Tape format looks gooofy\n"); |
| 249 | | /* eject tape to avoid catastrophes */ |
| 250 | | tpc->t[tap_sel].img->unload(); |
| 251 | | tpc->w[0] |= w0_offline; |
| 252 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 253 | | update_interrupt(device); |
| 254 | | goto update_registers; |
| 255 | | } |
| 256 | | |
| 257 | | /* test for EOF mark */ |
| 258 | | if (reclen == 0) |
| 259 | | { |
| 260 | | logerror("read binary forward: found EOF, requested %d\n", char_count); |
| 261 | | tpc->w[0] |= w0_EOF; |
| 262 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 263 | | update_interrupt(device); |
| 264 | | goto update_registers; |
| 265 | | } |
| 266 | | |
| 267 | | logerror("read binary forward: rec length %d, requested %d\n", reclen, char_count); |
| 268 | | |
| 269 | | rec_count = reclen; |
| 270 | | |
| 271 | | /* skip up to read_offset bytes */ |
| 272 | | chunk_len = (read_offset > rec_count) ? rec_count : read_offset; |
| 273 | | |
| 274 | | if (tpc->t[tap_sel].img->fseek(chunk_len, SEEK_CUR)) |
| 275 | | { /* eject tape */ |
| 276 | | logerror("Tape error\n"); |
| 277 | | tpc->t[tap_sel].img->unload(); |
| 278 | | tpc->w[0] |= w0_offline; |
| 279 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 280 | | update_interrupt(device); |
| 281 | | goto update_registers; |
| 282 | | } |
| 283 | | |
| 284 | | rec_count -= chunk_len; |
| 285 | | read_offset -= chunk_len; |
| 286 | | if (read_offset) |
| 287 | | { |
| 288 | | tpc->w[0] |= w0_EOR; |
| 289 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 290 | | update_interrupt(device); |
| 291 | | goto skip_trailer; |
| 292 | | } |
| 293 | | |
| 294 | | /* read up to char_count bytes */ |
| 295 | | chunk_len = (char_count > rec_count) ? rec_count : char_count; |
| 296 | | |
| 297 | | for (; chunk_len>0; ) |
| 298 | | { |
| 299 | | bytes_to_read = (chunk_len < sizeof(buffer)) ? chunk_len : sizeof(buffer); |
| 300 | | bytes_read = tpc->t[tap_sel].img->fread(buffer, bytes_to_read); |
| 301 | | |
| 302 | | if (bytes_read & 1) |
| 303 | | { |
| 304 | | buffer[bytes_read] = 0xff; |
| 305 | | } |
| 306 | | |
| 307 | | /* DMA */ |
| 308 | | for (i=0; i<bytes_read; i+=2) |
| 309 | | { |
| 310 | | device->machine().device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, (((int) buffer[i]) << 8) | buffer[i+1]); |
| 311 | | dma_address = (dma_address + 2) & 0x1ffffe; |
| 312 | | } |
| 313 | | |
| 314 | | rec_count -= bytes_read; |
| 315 | | char_count -= bytes_read; |
| 316 | | chunk_len -= bytes_read; |
| 317 | | |
| 318 | | if (bytes_read != bytes_to_read) |
| 319 | | { /* eject tape */ |
| 320 | | logerror("Tape error\n"); |
| 321 | | tpc->t[tap_sel].img->unload(); |
| 322 | | tpc->w[0] |= w0_offline; |
| 323 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 324 | | update_interrupt(device); |
| 325 | | goto update_registers; |
| 326 | | } |
| 327 | | } |
| 328 | | |
| 329 | | if (char_count) |
| 330 | | { |
| 331 | | tpc->w[0] |= w0_EOR; |
| 332 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 333 | | update_interrupt(device); |
| 334 | | goto skip_trailer; |
| 335 | | } |
| 336 | | |
| 337 | | if (rec_count) |
| 338 | | { /* skip end of record */ |
| 339 | | if (tpc->t[tap_sel].img->fseek(rec_count, SEEK_CUR)) |
| 340 | | { /* eject tape */ |
| 341 | | logerror("Tape error\n"); |
| 342 | | tpc->t[tap_sel].img->unload(); |
| 343 | | tpc->w[0] |= w0_offline; |
| 344 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 345 | | update_interrupt(device); |
| 346 | | goto update_registers; |
| 347 | | } |
| 348 | | } |
| 349 | | |
| 350 | | skip_trailer: |
| 351 | | if (tpc->t[tap_sel].img->fread(buffer, 4) != 4) |
| 352 | | { /* eject tape */ |
| 353 | | logerror("Tape error\n"); |
| 354 | | tpc->t[tap_sel].img->unload(); |
| 355 | | tpc->w[0] |= w0_offline; |
| 356 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 357 | | update_interrupt(device); |
| 358 | | goto update_registers; |
| 359 | | } |
| 360 | | |
| 361 | | if (reclen != ((((int) buffer[1]) << 8) | buffer[0])) |
| 362 | | { /* eject tape */ |
| 363 | | logerror("Tape error\n"); |
| 364 | | tpc->t[tap_sel].img->unload(); |
| 365 | | tpc->w[0] |= w0_offline; |
| 366 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 367 | | update_interrupt(device); |
| 368 | | goto update_registers; |
| 369 | | } |
| 370 | | if (buffer[2] || buffer[3]) |
| 371 | | { /* no idea what these bytes mean */ |
| 372 | | logerror("Tape error\n"); |
| 373 | | logerror("Tape format looks gooofy\n"); |
| 374 | | /* eject tape to avoid catastrophes */ |
| 375 | | tpc->t[tap_sel].img->unload(); |
| 376 | | tpc->w[0] |= w0_offline; |
| 377 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 378 | | update_interrupt(device); |
| 379 | | goto update_registers; |
| 380 | | } |
| 381 | | |
| 382 | | if (! (tpc->w[7] & w7_error)) |
| 383 | | { |
| 384 | | tpc->w[7] |= w7_idle | w7_complete; |
| 385 | | update_interrupt(device); |
| 386 | | } |
| 387 | | |
| 388 | | update_registers: |
| 389 | | tpc->w[1] = rec_count & 0xffff; |
| 390 | | tpc->w[2] = (rec_count >> 8) & 0xff; |
| 391 | | tpc->w[3] = read_offset; |
| 392 | | tpc->w[4] = char_count; |
| 393 | | tpc->w[5] = (dma_address >> 1) & 0xffff; |
| 394 | | tpc->w[6] = (tpc->w[6] & 0xffe0) | ((dma_address >> 17) & 0xf); |
| 395 | | } |
| 396 | | |
| 397 | | /* |
| 398 | | Handle the record skip forward command: skip a specified number of records. |
| 399 | | */ |
| 400 | | static void cmd_record_skip_forward(device_t *device) |
| 401 | | { |
| 402 | | UINT8 buffer[4]; |
| 403 | | int reclen; |
| 404 | | |
| 405 | | int record_count; |
| 406 | | int bytes_read; |
| 407 | | tap_990_t *tpc = get_safe_token(device); |
| 408 | | int tap_sel = cur_tape_unit(device); |
| 409 | | |
| 410 | | if (tap_sel == -1) |
| 411 | | { |
| 412 | | /* No idea what to report... */ |
| 413 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 414 | | update_interrupt(device); |
| 415 | | return; |
| 416 | | } |
| 417 | | else if (! tpc->t[tap_sel].img->exists()) |
| 418 | | { /* offline */ |
| 419 | | tpc->w[0] |= w0_offline; |
| 420 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 421 | | update_interrupt(device); |
| 422 | | return; |
| 423 | | } |
| 424 | | #if 0 |
| 425 | | else if (0) |
| 426 | | { /* rewind in progress */ |
| 427 | | tpc->w[0] |= 0x80 >> tap_sel; |
| 428 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 429 | | update_interrupt(device); |
| 430 | | return; |
| 431 | | } |
| 432 | | #endif |
| 433 | | |
| 434 | | record_count = tpc->w[4]; |
| 435 | | |
| 436 | | if (record_count) |
| 437 | | tpc->t[tap_sel].bot = 0; |
| 438 | | |
| 439 | | while (record_count > 0) |
| 440 | | { |
| 441 | | bytes_read = tpc->t[tap_sel].img->fread(buffer, 4); |
| 442 | | if (bytes_read != 4) |
| 443 | | { |
| 444 | | if (bytes_read == 0) |
| 445 | | { /* legitimate EOF */ |
| 446 | | tpc->t[tap_sel].eot = 1; |
| 447 | | tpc->w[0] |= w0_EOT; /* or should it be w0_command_timeout? */ |
| 448 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 449 | | update_interrupt(device); |
| 450 | | goto update_registers; |
| 451 | | } |
| 452 | | else |
| 453 | | { /* illegitimate EOF */ |
| 454 | | /* No idea what to report... */ |
| 455 | | /* eject tape to avoid catastrophes */ |
| 456 | | tpc->t[tap_sel].img->unload(); |
| 457 | | tpc->w[0] |= w0_offline; |
| 458 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 459 | | update_interrupt(device); |
| 460 | | goto update_registers; |
| 461 | | } |
| 462 | | } |
| 463 | | reclen = (((int) buffer[1]) << 8) | buffer[0]; |
| 464 | | if (buffer[2] || buffer[3]) |
| 465 | | { /* no idea what these bytes mean */ |
| 466 | | logerror("Tape format looks gooofy\n"); |
| 467 | | /* eject tape to avoid catastrophes */ |
| 468 | | tpc->t[tap_sel].img->unload(); |
| 469 | | tpc->w[0] |= w0_offline; |
| 470 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 471 | | update_interrupt(device); |
| 472 | | goto update_registers; |
| 473 | | } |
| 474 | | |
| 475 | | /* test for EOF mark */ |
| 476 | | if (reclen == 0) |
| 477 | | { |
| 478 | | logerror("record skip forward: found EOF\n"); |
| 479 | | tpc->w[0] |= w0_EOF; |
| 480 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 481 | | update_interrupt(device); |
| 482 | | goto update_registers; |
| 483 | | } |
| 484 | | |
| 485 | | /* skip record data */ |
| 486 | | if (tpc->t[tap_sel].img->fseek(reclen, SEEK_CUR)) |
| 487 | | { /* eject tape */ |
| 488 | | tpc->t[tap_sel].img->unload(); |
| 489 | | tpc->w[0] |= w0_offline; |
| 490 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 491 | | update_interrupt(device); |
| 492 | | goto update_registers; |
| 493 | | } |
| 494 | | |
| 495 | | if (tpc->t[tap_sel].img->fread(buffer, 4) != 4) |
| 496 | | { /* eject tape */ |
| 497 | | tpc->t[tap_sel].img->unload(); |
| 498 | | tpc->w[0] |= w0_offline; |
| 499 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 500 | | update_interrupt(device); |
| 501 | | goto update_registers; |
| 502 | | } |
| 503 | | |
| 504 | | if (reclen != ((((int) buffer[1]) << 8) | buffer[0])) |
| 505 | | { /* eject tape */ |
| 506 | | tpc->t[tap_sel].img->unload(); |
| 507 | | tpc->w[0] |= w0_offline; |
| 508 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 509 | | update_interrupt(device); |
| 510 | | goto update_registers; |
| 511 | | } |
| 512 | | if (buffer[2] || buffer[3]) |
| 513 | | { /* no idea what these bytes mean */ |
| 514 | | logerror("Tape format looks gooofy\n"); |
| 515 | | /* eject tape to avoid catastrophes */ |
| 516 | | tpc->t[tap_sel].img->unload(); |
| 517 | | tpc->w[0] |= w0_offline; |
| 518 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 519 | | update_interrupt(device); |
| 520 | | goto update_registers; |
| 521 | | } |
| 522 | | |
| 523 | | record_count--; |
| 524 | | } |
| 525 | | |
| 526 | | tpc->w[7] |= w7_idle | w7_complete; |
| 527 | | update_interrupt(device); |
| 528 | | |
| 529 | | update_registers: |
| 530 | | tpc->w[4] = record_count; |
| 531 | | } |
| 532 | | |
| 533 | | /* |
| 534 | | Handle the record skip reverse command: skip a specified number of records backwards. |
| 535 | | */ |
| 536 | | static void cmd_record_skip_reverse(device_t *device) |
| 537 | | { |
| 538 | | UINT8 buffer[4]; |
| 539 | | int reclen; |
| 540 | | |
| 541 | | int record_count; |
| 542 | | |
| 543 | | int bytes_read; |
| 544 | | tap_990_t *tpc = get_safe_token(device); |
| 545 | | int tap_sel = cur_tape_unit(device); |
| 546 | | |
| 547 | | if (tap_sel == -1) |
| 548 | | { |
| 549 | | /* No idea what to report... */ |
| 550 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 551 | | update_interrupt(device); |
| 552 | | return; |
| 553 | | } |
| 554 | | else if (! tpc->t[tap_sel].img->exists()) |
| 555 | | { /* offline */ |
| 556 | | tpc->w[0] |= w0_offline; |
| 557 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 558 | | update_interrupt(device); |
| 559 | | return; |
| 560 | | } |
| 561 | | #if 0 |
| 562 | | else if (0) |
| 563 | | { /* rewind in progress */ |
| 564 | | tpc->w[0] |= 0x80 >> tap_sel; |
| 565 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 566 | | update_interrupt(device); |
| 567 | | return; |
| 568 | | } |
| 569 | | #endif |
| 570 | | |
| 571 | | record_count = tpc->w[4]; |
| 572 | | |
| 573 | | if (record_count) |
| 574 | | tpc->t[tap_sel].eot = 0; |
| 575 | | |
| 576 | | while (record_count > 0) |
| 577 | | { |
| 578 | | if (tpc->t[tap_sel].img->ftell() == 0) |
| 579 | | { /* bot */ |
| 580 | | tpc->t[tap_sel].bot = 1; |
| 581 | | tpc->w[0] |= w0_BOT; |
| 582 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 583 | | update_interrupt(device); |
| 584 | | goto update_registers; |
| 585 | | } |
| 586 | | if (tpc->t[tap_sel].img->fseek(-4, SEEK_CUR)) |
| 587 | | { /* eject tape */ |
| 588 | | tpc->t[tap_sel].img->unload(); |
| 589 | | tpc->w[0] |= w0_offline; |
| 590 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 591 | | update_interrupt(device); |
| 592 | | goto update_registers; |
| 593 | | } |
| 594 | | bytes_read = tpc->t[tap_sel].img->fread(buffer, 4); |
| 595 | | if (bytes_read != 4) |
| 596 | | { |
| 597 | | /* illegitimate EOF */ |
| 598 | | /* No idea what to report... */ |
| 599 | | /* eject tape to avoid catastrophes */ |
| 600 | | tpc->t[tap_sel].img->unload(); |
| 601 | | tpc->w[0] |= w0_offline; |
| 602 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 603 | | update_interrupt(device); |
| 604 | | goto update_registers; |
| 605 | | } |
| 606 | | reclen = (((int) buffer[1]) << 8) | buffer[0]; |
| 607 | | if (buffer[2] || buffer[3]) |
| 608 | | { /* no idea what these bytes mean */ |
| 609 | | logerror("Tape format looks gooofy\n"); |
| 610 | | /* eject tape to avoid catastrophes */ |
| 611 | | tpc->t[tap_sel].img->unload(); |
| 612 | | tpc->w[0] |= w0_offline; |
| 613 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 614 | | update_interrupt(device); |
| 615 | | goto update_registers; |
| 616 | | } |
| 617 | | |
| 618 | | /* look for EOF mark */ |
| 619 | | if (reclen == 0) |
| 620 | | { |
| 621 | | logerror("record skip reverse: found EOF\n"); |
| 622 | | if (tpc->t[tap_sel].img->fseek(-4, SEEK_CUR)) |
| 623 | | { /* eject tape */ |
| 624 | | tpc->t[tap_sel].img->unload(); |
| 625 | | tpc->w[0] |= w0_offline; |
| 626 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 627 | | update_interrupt(device); |
| 628 | | goto update_registers; |
| 629 | | } |
| 630 | | tpc->w[0] |= w0_EOF; |
| 631 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 632 | | update_interrupt(device); |
| 633 | | goto update_registers; |
| 634 | | } |
| 635 | | |
| 636 | | if (tpc->t[tap_sel].img->fseek(-reclen-8, SEEK_CUR)) |
| 637 | | { /* eject tape */ |
| 638 | | tpc->t[tap_sel].img->unload(); |
| 639 | | tpc->w[0] |= w0_offline; |
| 640 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 641 | | update_interrupt(device); |
| 642 | | goto update_registers; |
| 643 | | } |
| 644 | | |
| 645 | | if (tpc->t[tap_sel].img->fread(buffer, 4) != 4) |
| 646 | | { /* eject tape */ |
| 647 | | tpc->t[tap_sel].img->unload(); |
| 648 | | tpc->w[0] |= w0_offline; |
| 649 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 650 | | update_interrupt(device); |
| 651 | | goto update_registers; |
| 652 | | } |
| 653 | | if (reclen != ((((int) buffer[1]) << 8) | buffer[0])) |
| 654 | | { /* eject tape */ |
| 655 | | tpc->t[tap_sel].img->unload(); |
| 656 | | tpc->w[0] |= w0_offline; |
| 657 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 658 | | update_interrupt(device); |
| 659 | | goto update_registers; |
| 660 | | } |
| 661 | | if (buffer[2] || buffer[3]) |
| 662 | | { /* no idea what these bytes mean */ |
| 663 | | logerror("Tape format looks gooofy\n"); |
| 664 | | /* eject tape to avoid catastrophes */ |
| 665 | | tpc->t[tap_sel].img->unload(); |
| 666 | | tpc->w[0] |= w0_offline; |
| 667 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 668 | | update_interrupt(device); |
| 669 | | goto update_registers; |
| 670 | | } |
| 671 | | |
| 672 | | if (tpc->t[tap_sel].img->fseek(-4, SEEK_CUR)) |
| 673 | | { /* eject tape */ |
| 674 | | tpc->t[tap_sel].img->unload(); |
| 675 | | tpc->w[0] |= w0_offline; |
| 676 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 677 | | update_interrupt(device); |
| 678 | | goto update_registers; |
| 679 | | } |
| 680 | | |
| 681 | | record_count--; |
| 682 | | } |
| 683 | | |
| 684 | | tpc->w[7] |= w7_idle | w7_complete; |
| 685 | | update_interrupt(device); |
| 686 | | |
| 687 | | update_registers: |
| 688 | | tpc->w[4] = record_count; |
| 689 | | } |
| 690 | | |
| 691 | | /* |
| 692 | | Handle the rewind command: rewind to BOT. |
| 693 | | */ |
| 694 | | static void cmd_rewind(device_t *device) |
| 695 | | { |
| 696 | | tap_990_t *tpc = get_safe_token(device); |
| 697 | | int tap_sel = cur_tape_unit(device); |
| 698 | | |
| 699 | | if (tap_sel == -1) |
| 700 | | { |
| 701 | | /* No idea what to report... */ |
| 702 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 703 | | update_interrupt(device); |
| 704 | | return; |
| 705 | | } |
| 706 | | else if (! tpc->t[tap_sel].img->exists()) |
| 707 | | { /* offline */ |
| 708 | | tpc->w[0] |= w0_offline; |
| 709 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 710 | | update_interrupt(device); |
| 711 | | return; |
| 712 | | } |
| 713 | | #if 0 |
| 714 | | else if (0) |
| 715 | | { /* rewind in progress */ |
| 716 | | tpc->w[0] |= 0x80 >> tap_sel; |
| 717 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 718 | | update_interrupt(device); |
| 719 | | return; |
| 720 | | } |
| 721 | | #endif |
| 722 | | |
| 723 | | tpc->t[tap_sel].eot = 0; |
| 724 | | |
| 725 | | if (tpc->t[tap_sel].img->fseek(0, SEEK_SET)) |
| 726 | | { /* eject tape */ |
| 727 | | tpc->t[tap_sel].img->unload(); |
| 728 | | tpc->w[0] |= w0_offline; |
| 729 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 730 | | update_interrupt(device); |
| 731 | | return; |
| 732 | | } |
| 733 | | tpc->t[tap_sel].bot = 1; |
| 734 | | |
| 735 | | tpc->w[7] |= w7_idle | w7_complete; |
| 736 | | update_interrupt(device); |
| 737 | | } |
| 738 | | |
| 739 | | /* |
| 740 | | Handle the rewind and offline command: disable the tape unit. |
| 741 | | */ |
| 742 | | static void cmd_rewind_and_offline(device_t *device) |
| 743 | | { |
| 744 | | tap_990_t *tpc = get_safe_token(device); |
| 745 | | int tap_sel = cur_tape_unit(device); |
| 746 | | |
| 747 | | if (tap_sel == -1) |
| 748 | | { |
| 749 | | /* No idea what to report... */ |
| 750 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 751 | | update_interrupt(device); |
| 752 | | return; |
| 753 | | } |
| 754 | | else if (! tpc->t[tap_sel].img->exists()) |
| 755 | | { /* offline */ |
| 756 | | tpc->w[0] |= w0_offline; |
| 757 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 758 | | update_interrupt(device); |
| 759 | | return; |
| 760 | | } |
| 761 | | #if 0 |
| 762 | | else if (0) |
| 763 | | { /* rewind in progress */ |
| 764 | | tpc->w[0] |= 0x80 >> tap_sel; |
| 765 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 766 | | update_interrupt(device); |
| 767 | | return; |
| 768 | | } |
| 769 | | #endif |
| 770 | | |
| 771 | | /* eject tape */ |
| 772 | | tpc->t[tap_sel].img->unload(); |
| 773 | | |
| 774 | | tpc->w[7] |= w7_idle | w7_complete; |
| 775 | | update_interrupt(device); |
| 776 | | } |
| 777 | | |
| 778 | | /* |
| 779 | | Handle the read transport status command: return the current tape status. |
| 780 | | */ |
| 781 | | static void read_transport_status(device_t *device) |
| 782 | | { |
| 783 | | tap_990_t *tpc = get_safe_token(device); |
| 784 | | int tap_sel = cur_tape_unit(device); |
| 785 | | |
| 786 | | if (tap_sel == -1) |
| 787 | | { |
| 788 | | /* No idea what to report... */ |
| 789 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 790 | | update_interrupt(device); |
| 791 | | } |
| 792 | | else if (! tpc->t[tap_sel].img->exists()) |
| 793 | | { /* offline */ |
| 794 | | tpc->w[0] |= w0_offline; |
| 795 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 796 | | update_interrupt(device); |
| 797 | | } |
| 798 | | #if 0 |
| 799 | | else if (0) |
| 800 | | { /* rewind in progress */ |
| 801 | | tpc->w[0] |= /*...*/; |
| 802 | | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 803 | | update_interrupt(device); |
| 804 | | } |
| 805 | | #endif |
| 806 | | else |
| 807 | | { /* no particular error condition */ |
| 808 | | if (tpc->t[tap_sel].bot) |
| 809 | | tpc->w[0] |= w0_BOT; |
| 810 | | if (tpc->t[tap_sel].eot) |
| 811 | | tpc->w[0] |= w0_EOT; |
| 812 | | if (tpc->t[tap_sel].wp) |
| 813 | | tpc->w[0] |= w0_write_ring; |
| 814 | | tpc->w[7] |= w7_idle | w7_complete; |
| 815 | | update_interrupt(device); |
| 816 | | } |
| 817 | | } |
| 818 | | |
| 819 | | /* |
| 820 | | Parse command code and execute the command. |
| 821 | | */ |
| 822 | | static void execute_command(device_t *device) |
| 823 | | { |
| 824 | | /* hack */ |
| 825 | | tap_990_t *tpc = get_safe_token(device); |
| 826 | | tpc->w[0] &= 0xff; |
| 827 | | |
| 828 | | switch ((tpc->w[6] & w6_command) >> 8) |
| 829 | | { |
| 830 | | case 0x00: |
| 831 | | case 0x0C: |
| 832 | | case 0x0E: |
| 833 | | /* NOP */ |
| 834 | | logerror("NOP\n"); |
| 835 | | tpc->w[7] |= w7_idle | w7_complete; |
| 836 | | update_interrupt(device); |
| 837 | | break; |
| 838 | | case 0x01: |
| 839 | | /* buffer sync: means nothing under emulation */ |
| 840 | | logerror("buffer sync\n"); |
| 841 | | tpc->w[7] |= w7_idle | w7_complete; |
| 842 | | update_interrupt(device); |
| 843 | | break; |
| 844 | | case 0x02: |
| 845 | | /* write EOF - not emulated */ |
| 846 | | logerror("write EOF\n"); |
| 847 | | /* ... */ |
| 848 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 849 | | update_interrupt(device); |
| 850 | | break; |
| 851 | | case 0x03: |
| 852 | | /* record skip reverse - not fully tested */ |
| 853 | | logerror("record skip reverse\n"); |
| 854 | | cmd_record_skip_reverse(device); |
| 855 | | break; |
| 856 | | case 0x04: |
| 857 | | /* read binary forward */ |
| 858 | | logerror("read binary forward\n"); |
| 859 | | cmd_read_binary_forward(device); |
| 860 | | break; |
| 861 | | case 0x05: |
| 862 | | /* record skip forward - not tested */ |
| 863 | | logerror("record skip forward\n"); |
| 864 | | cmd_record_skip_forward(device); |
| 865 | | break; |
| 866 | | case 0x06: |
| 867 | | /* write binary forward - not emulated */ |
| 868 | | logerror("write binary forward\n"); |
| 869 | | /* ... */ |
| 870 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 871 | | update_interrupt(device); |
| 872 | | break; |
| 873 | | case 0x07: |
| 874 | | /* erase - not emulated */ |
| 875 | | logerror("erase\n"); |
| 876 | | /* ... */ |
| 877 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 878 | | update_interrupt(device); |
| 879 | | break; |
| 880 | | case 0x08: |
| 881 | | case 0x09: |
| 882 | | /* read transport status */ |
| 883 | | logerror("read transport status\n"); |
| 884 | | read_transport_status(device); |
| 885 | | break; |
| 886 | | case 0x0A: |
| 887 | | /* rewind - not tested */ |
| 888 | | logerror("rewind\n"); |
| 889 | | cmd_rewind(device); |
| 890 | | break; |
| 891 | | case 0x0B: |
| 892 | | /* rewind and offline - not tested */ |
| 893 | | logerror("rewind and offline\n"); |
| 894 | | cmd_rewind_and_offline(device); |
| 895 | | break; |
| 896 | | case 0x0F: |
| 897 | | /* extended control and status - not emulated */ |
| 898 | | logerror("extended control and status\n"); |
| 899 | | /* ... */ |
| 900 | | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 901 | | update_interrupt(device); |
| 902 | | break; |
| 903 | | } |
| 904 | | } |
| 905 | | |
| 906 | | |
| 907 | | /* |
| 908 | | Read one register in TPCS space |
| 909 | | */ |
| 910 | | READ16_DEVICE_HANDLER(ti990_tpc_r) |
| 911 | | { |
| 912 | | tap_990_t *tpc = get_safe_token(device); |
| 913 | | if (offset < 8) |
| 914 | | return tpc->w[offset]; |
| 915 | | else |
| 916 | | return 0; |
| 917 | | } |
| 918 | | |
| 919 | | /* |
| 920 | | Write one register in TPCS space. Execute command if w7_idle is cleared. |
| 921 | | */ |
| 922 | | WRITE16_DEVICE_HANDLER(ti990_tpc_w) |
| 923 | | { |
| 924 | | tap_990_t *tpc = get_safe_token(device); |
| 925 | | if (offset < 8) |
| 926 | | { |
| 927 | | /* write protect if a command is in progress */ |
| 928 | | if (tpc->w[7] & w7_idle) |
| 929 | | { |
| 930 | | UINT16 old_data = tpc->w[offset]; |
| 931 | | |
| 932 | | /* Only write writable bits AND honor byte accesses (ha!) */ |
| 933 | | tpc->w[offset] = (tpc->w[offset] & ((~w_mask[offset]) | mem_mask)) | (data & w_mask[offset] & ~mem_mask); |
| 934 | | |
| 935 | | if ((offset == 0) || (offset == 7)) |
| 936 | | update_interrupt(device); |
| 937 | | |
| 938 | | if ((offset == 7) && (old_data & w7_idle) && ! (data & w7_idle)) |
| 939 | | { /* idle has been cleared: start command execution */ |
| 940 | | execute_command(device); |
| 941 | | } |
| 942 | | } |
| 943 | | } |
| 944 | | } |
| 945 | | |
| 946 | | class ti990_tape_image_device : public device_t, |
| 947 | | public device_image_interface |
| 948 | | { |
| 949 | | public: |
| 950 | | // construction/destruction |
| 951 | | ti990_tape_image_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); |
| 952 | | |
| 953 | | // image-level overrides |
| 954 | | virtual iodevice_t image_type() const { return IO_MAGTAPE; } |
| 955 | | |
| 956 | | virtual bool is_readable() const { return 1; } |
| 957 | | virtual bool is_writeable() const { return 1; } |
| 958 | | virtual bool is_creatable() const { return 1; } |
| 959 | | virtual bool must_be_loaded() const { return 0; } |
| 960 | | virtual bool is_reset_on_load() const { return 0; } |
| 961 | | virtual const char *image_interface() const { return NULL; } |
| 962 | | virtual const char *file_extensions() const { return "tap"; } |
| 963 | | virtual const option_guide *create_option_guide() const { return NULL; } |
| 964 | | |
| 965 | | virtual bool call_load(); |
| 966 | | virtual void call_unload(); |
| 967 | | protected: |
| 968 | | // device-level overrides |
| 969 | | virtual void device_config_complete(); |
| 970 | | virtual void device_start(); |
| 971 | | }; |
| 972 | | |
| 973 | | const device_type TI990_TAPE = &device_creator<ti990_tape_image_device>; |
| 974 | | |
| 975 | | ti990_tape_image_device::ti990_tape_image_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 976 | | : device_t(mconfig, TI990_TAPE, "TI990 Magnetic Tape", tag, owner, clock, "ti990_tape_image", __FILE__), |
| 977 | | device_image_interface(mconfig, *this) |
| 978 | | { |
| 979 | | } |
| 980 | | |
| 981 | | void ti990_tape_image_device::device_config_complete() |
| 982 | | { |
| 983 | | update_names(); |
| 984 | | } |
| 985 | | |
| 986 | | void ti990_tape_image_device::device_start() |
| 987 | | { |
| 988 | | tape_unit_t *t; |
| 989 | | tap_990_t *tpc = get_safe_token(owner()); |
| 990 | | int id = tape_get_id(this); |
| 991 | | |
| 992 | | t = &tpc->t[id]; |
| 993 | | memset(t, 0, sizeof(*t)); |
| 994 | | |
| 995 | | t->img = this; |
| 996 | | t->wp = 1; |
| 997 | | t->bot = 0; |
| 998 | | t->eot = 0; |
| 999 | | } |
| 1000 | | |
| 1001 | | /* |
| 1002 | | Open a tape image |
| 1003 | | */ |
| 1004 | | bool ti990_tape_image_device::call_load() |
| 1005 | | { |
| 1006 | | tape_unit_t *t; |
| 1007 | | tap_990_t *tpc = get_safe_token(owner()); |
| 1008 | | int id = tape_get_id(this); |
| 1009 | | |
| 1010 | | t = &tpc->t[id]; |
| 1011 | | memset(t, 0, sizeof(*t)); |
| 1012 | | |
| 1013 | | /* tell whether the image is writable */ |
| 1014 | | t->wp = is_readonly(); |
| 1015 | | |
| 1016 | | t->bot = 1; |
| 1017 | | |
| 1018 | | return IMAGE_INIT_PASS; |
| 1019 | | } |
| 1020 | | |
| 1021 | | /* |
| 1022 | | Close a tape image |
| 1023 | | */ |
| 1024 | | void ti990_tape_image_device::call_unload() |
| 1025 | | { |
| 1026 | | tape_unit_t *t; |
| 1027 | | tap_990_t *tpc = get_safe_token(owner()); |
| 1028 | | int id = tape_get_id(this); |
| 1029 | | |
| 1030 | | t = &tpc->t[id]; |
| 1031 | | t->wp = 1; |
| 1032 | | t->bot = 0; |
| 1033 | | t->eot = 0; |
| 1034 | | } |
| 1035 | | |
| 1036 | | #define MCFG_TI990_TAPE_ADD(_tag) \ |
| 1037 | | MCFG_DEVICE_ADD((_tag), TI990_TAPE, 0) |
| 1038 | | |
| 1039 | | |
| 1040 | | static MACHINE_CONFIG_FRAGMENT( tap_990 ) |
| 1041 | | MCFG_TI990_TAPE_ADD("tape0") |
| 1042 | | MCFG_TI990_TAPE_ADD("tape1") |
| 1043 | | MCFG_TI990_TAPE_ADD("tape2") |
| 1044 | | MCFG_TI990_TAPE_ADD("tape3") |
| 1045 | | MACHINE_CONFIG_END |
| 1046 | | |
| 1047 | | /* |
| 1048 | | Init the tape controller core |
| 1049 | | */ |
| 1050 | | static DEVICE_START(tap_990) |
| 1051 | | { |
| 1052 | | tap_990_t *tpc = get_safe_token(device); |
| 1053 | | /* verify that we have an interface assigned */ |
| 1054 | | assert(device->static_config() != NULL); |
| 1055 | | |
| 1056 | | /* copy interface pointer */ |
| 1057 | | tpc->intf = (const ti990_tpc_interface*)device->static_config(); |
| 1058 | | |
| 1059 | | memset(tpc->w, 0, sizeof(tpc->w)); |
| 1060 | | /* The PE bit is always set for the MT3200 (but not MT1600) */ |
| 1061 | | /* According to MT3200 manual, w7 bit #4 (reserved) is always set */ |
| 1062 | | tpc->w[7] = w7_idle /*| w7_PE_format*/ | 0x0800; |
| 1063 | | |
| 1064 | | update_interrupt(device); |
| 1065 | | } |
| 1066 | | |
| 1067 | | |
| 1068 | | const device_type TI990_TAPE_CTRL = &device_creator<tap_990_device>; |
| 1069 | | |
| 1070 | | tap_990_device::tap_990_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 1071 | | : device_t(mconfig, TI990_TAPE_CTRL, "Generic TI990 Tape Controller", tag, owner, clock, "tap_990", __FILE__) |
| 1072 | | { |
| 1073 | | m_token = global_alloc_clear(tap_990_t); |
| 1074 | | } |
| 1075 | | |
| 1076 | | //------------------------------------------------- |
| 1077 | | // device_config_complete - perform any |
| 1078 | | // operations now that the configuration is |
| 1079 | | // complete |
| 1080 | | //------------------------------------------------- |
| 1081 | | |
| 1082 | | void tap_990_device::device_config_complete() |
| 1083 | | { |
| 1084 | | } |
| 1085 | | |
| 1086 | | //------------------------------------------------- |
| 1087 | | // device_start - device-specific startup |
| 1088 | | //------------------------------------------------- |
| 1089 | | |
| 1090 | | void tap_990_device::device_start() |
| 1091 | | { |
| 1092 | | DEVICE_START_NAME( tap_990 )(this); |
| 1093 | | } |
| 1094 | | |
| 1095 | | //------------------------------------------------- |
| 1096 | | // device_mconfig_additions - return a pointer to |
| 1097 | | // the device's machine fragment |
| 1098 | | //------------------------------------------------- |
| 1099 | | |
| 1100 | | machine_config_constructor tap_990_device::device_mconfig_additions() const |
| 1101 | | { |
| 1102 | | return MACHINE_CONFIG_NAME( tap_990 ); |
| 1103 | | } |
trunk/src/mess/machine/990_hd.c
| r26090 | r26091 | |
| 1 | | /* |
| 2 | | 990_hd.c: emulation of a generic ti990 hard disk controller, for use with |
| 3 | | TILINE-based TI990 systems (TI990/10, /12, /12LR, /10A, Business system 300 |
| 4 | | and 300A). |
| 5 | | |
| 6 | | This core will emulate the common feature set found in every disk controller. |
| 7 | | Most controllers support additional features, but are still compatible with |
| 8 | | the basic feature set. I have a little documentation on two specific |
| 9 | | disk controllers (WD900 and WD800/WD800A), but I have not tried to emulate |
| 10 | | controller-specific features. |
| 11 | | |
| 12 | | |
| 13 | | Long description: see 2234398-9701 and 2306140-9701. |
| 14 | | |
| 15 | | |
| 16 | | Raphael Nabet 2002-2003 |
| 17 | | */ |
| 18 | | |
| 19 | | #include "emu.h" |
| 20 | | |
| 21 | | #include "990_hd.h" |
| 22 | | |
| 23 | | #include "harddisk.h" |
| 24 | | #include "imagedev/harddriv.h" |
| 25 | | |
| 26 | | static void update_interrupt(running_machine &machine); |
| 27 | | |
| 28 | | /* max disk units per controller: 4 is the protocol limit, but it may be |
| 29 | | overriden if more than one controller is used */ |
| 30 | | #define MAX_DISK_UNIT 4 |
| 31 | | |
| 32 | | /* Max sector length is bytes. Generally 256, except for a few older disk |
| 33 | | units which use 288-byte-long sectors, and SCSI units which generally use |
| 34 | | standard 512-byte-long sectors. */ |
| 35 | | /* I chose a limit of 512. No need to use more until someone writes CD-ROMs |
| 36 | | for TI990. */ |
| 37 | | #define MAX_SECTOR_SIZE 512 |
| 38 | | |
| 39 | | /* Description of custom format */ |
| 40 | | /* We can use MAME's harddisk.c image format instead. */ |
| 41 | | |
| 42 | | /* machine-independent big-endian 32-bit integer */ |
| 43 | | struct UINT32BE |
| 44 | | { |
| 45 | | UINT8 bytes[4]; |
| 46 | | }; |
| 47 | | |
| 48 | | INLINE UINT32 get_UINT32BE(UINT32BE word) |
| 49 | | { |
| 50 | | return (word.bytes[0] << 24) | (word.bytes[1] << 16) | (word.bytes[2] << 8) | word.bytes[3]; |
| 51 | | } |
| 52 | | |
| 53 | | #ifdef UNUSED_FUNCTION |
| 54 | | INLINE void set_UINT32BE(UINT32BE *word, UINT32 data) |
| 55 | | { |
| 56 | | word->bytes[0] = (data >> 24) & 0xff; |
| 57 | | word->bytes[1] = (data >> 16) & 0xff; |
| 58 | | word->bytes[2] = (data >> 8) & 0xff; |
| 59 | | word->bytes[3] = data & 0xff; |
| 60 | | } |
| 61 | | #endif |
| 62 | | |
| 63 | | /* disk image header */ |
| 64 | | struct disk_image_header |
| 65 | | { |
| 66 | | UINT32BE cylinders; /* number of cylinders on hard disk (big-endian) */ |
| 67 | | UINT32BE heads; /* number of heads on hard disk (big-endian) */ |
| 68 | | UINT32BE sectors_per_track; /* number of sectors per track on hard disk (big-endian) */ |
| 69 | | UINT32BE bytes_per_sector; /* number of bytes of data per sector on hard disk (big-endian) */ |
| 70 | | }; |
| 71 | | |
| 72 | | enum |
| 73 | | { |
| 74 | | header_len = sizeof(disk_image_header) |
| 75 | | }; |
| 76 | | |
| 77 | | enum format_t |
| 78 | | { |
| 79 | | format_mame, |
| 80 | | format_old |
| 81 | | }; |
| 82 | | |
| 83 | | /* disk drive unit descriptor */ |
| 84 | | struct hd_unit_t |
| 85 | | { |
| 86 | | device_image_interface *img; /* image descriptor */ |
| 87 | | format_t format; |
| 88 | | hard_disk_file *hd_handle; /* mame hard disk descriptor - only if format == format_mame */ |
| 89 | | unsigned int wp : 1; /* TRUE if disk is write-protected */ |
| 90 | | unsigned int unsafe : 1; /* TRUE when a disk has just been connected */ |
| 91 | | |
| 92 | | /* disk geometry */ |
| 93 | | unsigned int cylinders, heads, sectors_per_track, bytes_per_sector; |
| 94 | | }; |
| 95 | | |
| 96 | | /* disk controller */ |
| 97 | | struct hdc_t |
| 98 | | { |
| 99 | | UINT16 w[8]; |
| 100 | | |
| 101 | | void (*interrupt_callback)(running_machine &machine, int state); |
| 102 | | |
| 103 | | hd_unit_t d[MAX_DISK_UNIT]; |
| 104 | | }; |
| 105 | | |
| 106 | | /* masks for individual bits controller registers */ |
| 107 | | enum |
| 108 | | { |
| 109 | | w0_offline = 0x8000, |
| 110 | | w0_not_ready = 0x4000, |
| 111 | | w0_write_protect = 0x2000, |
| 112 | | w0_unsafe = 0x1000, |
| 113 | | w0_end_of_cylinder = 0x0800, |
| 114 | | w0_seek_incomplete = 0x0400, |
| 115 | | /*w0_offset_active = 0x0200,*/ |
| 116 | | w0_pack_change = 0x0100, |
| 117 | | |
| 118 | | w0_attn_lines = 0x00f0, |
| 119 | | w0_attn_mask = 0x000f, |
| 120 | | |
| 121 | | w1_extended_command = 0xc000, |
| 122 | | /*w1_strobe_early = 0x2000, |
| 123 | | w1_strobe_late = 0x1000,*/ |
| 124 | | w1_transfer_inhibit = 0x0800, |
| 125 | | w1_command = 0x0700, |
| 126 | | w1_offset = 0x0080, |
| 127 | | w1_offset_forward = 0x0040, |
| 128 | | w1_head_address = 0x003f, |
| 129 | | |
| 130 | | w6_unit0_sel = 0x0800, |
| 131 | | w6_unit1_sel = 0x0400, |
| 132 | | w6_unit2_sel = 0x0200, |
| 133 | | w6_unit3_sel = 0x0100, |
| 134 | | |
| 135 | | w7_idle = 0x8000, |
| 136 | | w7_complete = 0x4000, |
| 137 | | w7_error = 0x2000, |
| 138 | | w7_int_enable = 0x1000, |
| 139 | | /*w7_lock_out = 0x0800,*/ |
| 140 | | w7_retry = 0x0400, |
| 141 | | w7_ecc = 0x0200, |
| 142 | | w7_abnormal_completion = 0x0100, |
| 143 | | w7_memory_error = 0x0080, |
| 144 | | w7_data_error = 0x0040, |
| 145 | | w7_tiline_timeout_err = 0x0020, |
| 146 | | w7_header_err = 0x0010, |
| 147 | | w7_rate_err = 0x0008, |
| 148 | | w7_command_time_out_err = 0x0004, |
| 149 | | w7_search_err = 0x0002, |
| 150 | | w7_unit_err = 0x0001 |
| 151 | | }; |
| 152 | | |
| 153 | | /* masks for computer-controlled bit in each controller register */ |
| 154 | | static const UINT16 w_mask[8] = |
| 155 | | { |
| 156 | | 0x000f, /* Controllers should prevent overwriting of w0 status bits, and I know |
| 157 | | that some controllers do so. */ |
| 158 | | 0xffff, |
| 159 | | 0xffff, |
| 160 | | 0xffff, |
| 161 | | 0xffff, |
| 162 | | 0xffff, |
| 163 | | 0xffff, |
| 164 | | 0xf7ff /* Don't overwrite reserved bits */ |
| 165 | | }; |
| 166 | | |
| 167 | | static hdc_t hdc; |
| 168 | | |
| 169 | | |
| 170 | | static int get_id_from_device( device_t *device ) |
| 171 | | { |
| 172 | | int id = -1; |
| 173 | | |
| 174 | | if ( ! strcmp( ":harddisk1", device->tag() ) ) |
| 175 | | { |
| 176 | | id = 0; |
| 177 | | } |
| 178 | | else if ( ! strcmp( ":harddisk2", device->tag() ) ) |
| 179 | | { |
| 180 | | id = 1; |
| 181 | | } |
| 182 | | else if ( ! strcmp( ":harddisk3", device->tag() ) ) |
| 183 | | { |
| 184 | | id = 2; |
| 185 | | } |
| 186 | | else if ( ! strcmp( ":harddisk4", device->tag() ) ) |
| 187 | | { |
| 188 | | id = 3; |
| 189 | | } |
| 190 | | assert( id >= 0 ); |
| 191 | | |
| 192 | | return id; |
| 193 | | } |
| 194 | | |
| 195 | | |
| 196 | | /* |
| 197 | | Initialize hard disk unit and open a hard disk image |
| 198 | | */ |
| 199 | | static DEVICE_IMAGE_LOAD_LEGACY( ti990_hd ) |
| 200 | | { |
| 201 | | int id = get_id_from_device( &image.device() ); |
| 202 | | hd_unit_t *d; |
| 203 | | hard_disk_file *hd_file; |
| 204 | | |
| 205 | | d = &hdc.d[id]; |
| 206 | | d->img = ℑ |
| 207 | | |
| 208 | | hd_file = dynamic_cast<harddisk_image_device *>(&image)->get_hard_disk_file(); |
| 209 | | |
| 210 | | if ( hd_file ) |
| 211 | | { |
| 212 | | const hard_disk_info *standard_header; |
| 213 | | |
| 214 | | d->format = format_mame; |
| 215 | | d->hd_handle = hd_file; |
| 216 | | |
| 217 | | /* use standard hard disk image header. */ |
| 218 | | standard_header = hard_disk_get_info(d->hd_handle); |
| 219 | | |
| 220 | | d->cylinders = standard_header->cylinders; |
| 221 | | d->heads = standard_header->heads; |
| 222 | | d->sectors_per_track = standard_header->sectors; |
| 223 | | d->bytes_per_sector = standard_header->sectorbytes; |
| 224 | | } |
| 225 | | else |
| 226 | | { |
| 227 | | /* older, custom format */ |
| 228 | | disk_image_header custom_header; |
| 229 | | int bytes_read; |
| 230 | | |
| 231 | | /* set file descriptor */ |
| 232 | | d->format = format_old; |
| 233 | | d->hd_handle = NULL; |
| 234 | | |
| 235 | | /* use custom image header. */ |
| 236 | | /* to convert old header-less images to this format, insert a 16-byte |
| 237 | | header as follow: 00 00 03 8f 00 00 00 05 00 00 00 21 00 00 01 00 */ |
| 238 | | d->img->fseek(0, SEEK_SET); |
| 239 | | bytes_read = d->img->fread(&custom_header, sizeof(custom_header)); |
| 240 | | if (bytes_read != sizeof(custom_header)) |
| 241 | | { |
| 242 | | d->format = format_mame; /* don't care */ |
| 243 | | d->wp = 1; |
| 244 | | d->unsafe = 1; |
| 245 | | return IMAGE_INIT_FAIL; |
| 246 | | } |
| 247 | | |
| 248 | | d->cylinders = get_UINT32BE(custom_header.cylinders); |
| 249 | | d->heads = get_UINT32BE(custom_header.heads); |
| 250 | | d->sectors_per_track = get_UINT32BE(custom_header.sectors_per_track); |
| 251 | | d->bytes_per_sector = get_UINT32BE(custom_header.bytes_per_sector); |
| 252 | | } |
| 253 | | |
| 254 | | if (d->bytes_per_sector > MAX_SECTOR_SIZE) |
| 255 | | { |
| 256 | | d->format = format_mame; |
| 257 | | d->hd_handle = NULL; |
| 258 | | d->wp = 1; |
| 259 | | d->unsafe = 1; |
| 260 | | return IMAGE_INIT_FAIL; |
| 261 | | } |
| 262 | | |
| 263 | | /* tell whether the image is writable */ |
| 264 | | d->wp = image.is_readonly(); |
| 265 | | |
| 266 | | d->unsafe = 1; |
| 267 | | /* set attention line */ |
| 268 | | hdc.w[0] |= (0x80 >> id); |
| 269 | | |
| 270 | | return IMAGE_INIT_PASS; |
| 271 | | } |
| 272 | | |
| 273 | | /* |
| 274 | | close a hard disk image |
| 275 | | */ |
| 276 | | static DEVICE_IMAGE_UNLOAD_LEGACY( ti990_hd ) |
| 277 | | { |
| 278 | | int id = get_id_from_device( image ); |
| 279 | | hd_unit_t *d; |
| 280 | | |
| 281 | | d = &hdc.d[id]; |
| 282 | | |
| 283 | | d->format = format_mame; /* don't care */ |
| 284 | | d->hd_handle = NULL; |
| 285 | | d->wp = 1; |
| 286 | | d->unsafe = 1; |
| 287 | | |
| 288 | | /* clear attention line */ |
| 289 | | hdc.w[0] &= ~ (0x80 >> id); |
| 290 | | } |
| 291 | | |
| 292 | | /* |
| 293 | | Return true if a HD image has been loaded |
| 294 | | */ |
| 295 | | INLINE int is_unit_loaded(int unit) |
| 296 | | { |
| 297 | | int reply = 0; |
| 298 | | |
| 299 | | switch (hdc.d[unit].format) |
| 300 | | { |
| 301 | | case format_mame: |
| 302 | | reply = (hdc.d[unit].hd_handle != NULL); |
| 303 | | break; |
| 304 | | |
| 305 | | case format_old: |
| 306 | | reply = (hdc.d[unit].img->exists() ? 1 : 0); |
| 307 | | break; |
| 308 | | } |
| 309 | | |
| 310 | | return reply; |
| 311 | | } |
| 312 | | |
| 313 | | /* |
| 314 | | Init the hdc core |
| 315 | | */ |
| 316 | | MACHINE_START(ti990_hdc) |
| 317 | | { |
| 318 | | int i; |
| 319 | | |
| 320 | | /* initialize harddisk information */ |
| 321 | | /* attention lines will be set by DEVICE_IMAGE_LOD */ |
| 322 | | for (i=0; i<MAX_DISK_UNIT; i++) |
| 323 | | { |
| 324 | | hdc.d[i].format = format_mame; |
| 325 | | hdc.d[i].hd_handle = NULL; |
| 326 | | hdc.d[i].wp = 1; |
| 327 | | hdc.d[i].unsafe = 1; |
| 328 | | } |
| 329 | | } |
| 330 | | |
| 331 | | |
| 332 | | void ti990_hdc_init(running_machine &machine, void (*interrupt_callback)(running_machine &machine, int state)) |
| 333 | | { |
| 334 | | memset(hdc.w, 0, sizeof(hdc.w)); |
| 335 | | hdc.w[7] = w7_idle; |
| 336 | | |
| 337 | | /* get references to harddisk devices */ |
| 338 | | hdc.d[0].img = dynamic_cast<device_image_interface *>(machine.device("harddisk1")); |
| 339 | | hdc.d[1].img = dynamic_cast<device_image_interface *>(machine.device("harddisk2")); |
| 340 | | hdc.d[2].img = dynamic_cast<device_image_interface *>(machine.device("harddisk3")); |
| 341 | | hdc.d[3].img = dynamic_cast<device_image_interface *>(machine.device("harddisk4")); |
| 342 | | |
| 343 | | hdc.interrupt_callback = interrupt_callback; |
| 344 | | |
| 345 | | update_interrupt(machine); |
| 346 | | } |
| 347 | | |
| 348 | | |
| 349 | | /* |
| 350 | | Parse the disk select lines, and return the corresponding tape unit. |
| 351 | | (-1 if none) |
| 352 | | */ |
| 353 | | static int cur_disk_unit(void) |
| 354 | | { |
| 355 | | int reply; |
| 356 | | |
| 357 | | |
| 358 | | if (hdc.w[6] & w6_unit0_sel) |
| 359 | | reply = 0; |
| 360 | | else if (hdc.w[6] & w6_unit1_sel) |
| 361 | | reply = 1; |
| 362 | | else if (hdc.w[6] & w6_unit2_sel) |
| 363 | | reply = 2; |
| 364 | | else if (hdc.w[6] & w6_unit3_sel) |
| 365 | | reply = 3; |
| 366 | | else |
| 367 | | reply = -1; |
| 368 | | |
| 369 | | if (reply >= MAX_DISK_UNIT) |
| 370 | | reply = -1; |
| 371 | | |
| 372 | | return reply; |
| 373 | | } |
| 374 | | |
| 375 | | /* |
| 376 | | Update interrupt state |
| 377 | | */ |
| 378 | | static void update_interrupt(running_machine &machine) |
| 379 | | { |
| 380 | | if (hdc.interrupt_callback) |
| 381 | | (*hdc.interrupt_callback)(machine, (hdc.w[7] & w7_idle) |
| 382 | | && (((hdc.w[7] & w7_int_enable) && (hdc.w[7] & (w7_complete | w7_error))) |
| 383 | | || ((hdc.w[0] & (hdc.w[0] >> 4)) & w0_attn_mask))); |
| 384 | | } |
| 385 | | |
| 386 | | /* |
| 387 | | Check that a sector address is valid. |
| 388 | | |
| 389 | | Terminate current command and return non-zero if the address is invalid. |
| 390 | | */ |
| 391 | | static int check_sector_address(running_machine &machine, int unit, unsigned int cylinder, unsigned int head, unsigned int sector) |
| 392 | | { |
| 393 | | if ((cylinder > hdc.d[unit].cylinders) || (head > hdc.d[unit].heads) || (sector > hdc.d[unit].sectors_per_track)) |
| 394 | | { /* invalid address */ |
| 395 | | if (cylinder > hdc.d[unit].cylinders) |
| 396 | | { |
| 397 | | hdc.w[0] |= w0_seek_incomplete; |
| 398 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 399 | | } |
| 400 | | else if (head > hdc.d[unit].heads) |
| 401 | | { |
| 402 | | hdc.w[0] |= w0_end_of_cylinder; |
| 403 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 404 | | } |
| 405 | | else if (sector > hdc.d[unit].sectors_per_track) |
| 406 | | hdc.w[7] |= w7_idle | w7_error | w7_command_time_out_err; |
| 407 | | update_interrupt(machine); |
| 408 | | return 1; |
| 409 | | } |
| 410 | | |
| 411 | | return 0; |
| 412 | | } |
| 413 | | |
| 414 | | /* |
| 415 | | Seek to sector whose address is given |
| 416 | | */ |
| 417 | | static int sector_to_lba(running_machine &machine, int unit, unsigned int cylinder, unsigned int head, unsigned int sector, unsigned int *lba) |
| 418 | | { |
| 419 | | if (check_sector_address(machine, unit, cylinder, head, sector)) |
| 420 | | return 1; |
| 421 | | |
| 422 | | * lba = (cylinder*hdc.d[unit].heads + head)*hdc.d[unit].sectors_per_track + sector; |
| 423 | | |
| 424 | | return 0; |
| 425 | | } |
| 426 | | |
| 427 | | /* |
| 428 | | Read one given sector |
| 429 | | */ |
| 430 | | static int read_sector(int unit, unsigned int lba, void *buffer, unsigned int bytes_to_read) |
| 431 | | { |
| 432 | | unsigned long byte_position; |
| 433 | | unsigned int bytes_read; |
| 434 | | |
| 435 | | switch (hdc.d[unit].format) |
| 436 | | { |
| 437 | | case format_mame: |
| 438 | | bytes_read = hdc.d[unit].bytes_per_sector * hard_disk_read(hdc.d[unit].hd_handle, lba, buffer); |
| 439 | | if (bytes_read > bytes_to_read) |
| 440 | | bytes_read = bytes_to_read; |
| 441 | | break; |
| 442 | | |
| 443 | | case format_old: |
| 444 | | byte_position = lba*hdc.d[unit].bytes_per_sector + header_len; |
| 445 | | hdc.d[unit].img->fseek(byte_position, SEEK_SET); |
| 446 | | bytes_read = hdc.d[unit].img->fread(buffer, bytes_to_read); |
| 447 | | break; |
| 448 | | |
| 449 | | default: |
| 450 | | bytes_read = 0; |
| 451 | | break; |
| 452 | | } |
| 453 | | |
| 454 | | return bytes_read; |
| 455 | | } |
| 456 | | |
| 457 | | /* |
| 458 | | Write one given sector |
| 459 | | */ |
| 460 | | static int write_sector(int unit, unsigned int lba, const void *buffer, unsigned int bytes_to_write) |
| 461 | | { |
| 462 | | unsigned long byte_position; |
| 463 | | unsigned int bytes_written; |
| 464 | | |
| 465 | | switch (hdc.d[unit].format) |
| 466 | | { |
| 467 | | case format_mame: |
| 468 | | bytes_written = hdc.d[unit].bytes_per_sector * hard_disk_write(hdc.d[unit].hd_handle, lba, buffer); |
| 469 | | if (bytes_written > bytes_to_write) |
| 470 | | bytes_written = bytes_to_write; |
| 471 | | break; |
| 472 | | |
| 473 | | case format_old: |
| 474 | | byte_position = lba*hdc.d[unit].bytes_per_sector + header_len; |
| 475 | | hdc.d[unit].img->fseek(byte_position, SEEK_SET); |
| 476 | | bytes_written = hdc.d[unit].img->fwrite(buffer, bytes_to_write); |
| 477 | | break; |
| 478 | | |
| 479 | | default: |
| 480 | | bytes_written = 0; |
| 481 | | break; |
| 482 | | } |
| 483 | | |
| 484 | | return bytes_written; |
| 485 | | } |
| 486 | | |
| 487 | | /* |
| 488 | | Handle the store registers command: read the drive geometry. |
| 489 | | */ |
| 490 | | static void store_registers(running_machine &machine) |
| 491 | | { |
| 492 | | int dma_address; |
| 493 | | int byte_count; |
| 494 | | |
| 495 | | UINT16 buffer[3]; |
| 496 | | int i, real_word_count; |
| 497 | | |
| 498 | | int dsk_sel = cur_disk_unit(); |
| 499 | | |
| 500 | | |
| 501 | | if (dsk_sel == -1) |
| 502 | | { |
| 503 | | /* No idea what to report... */ |
| 504 | | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 505 | | update_interrupt(machine); |
| 506 | | return; |
| 507 | | } |
| 508 | | else if (! is_unit_loaded(dsk_sel)) |
| 509 | | { /* offline */ |
| 510 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 511 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 512 | | update_interrupt(machine); |
| 513 | | return; |
| 514 | | } |
| 515 | | |
| 516 | | hdc.d[dsk_sel].unsafe = 0; /* I think */ |
| 517 | | |
| 518 | | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 519 | | byte_count = hdc.w[4] & 0xfffe; |
| 520 | | |
| 521 | | /* formatted words per track */ |
| 522 | | buffer[0] = (hdc.d[dsk_sel].sectors_per_track*hdc.d[dsk_sel].bytes_per_sector) >> 1; |
| 523 | | /* MSByte: sectors per track; LSByte: bytes of overhead per sector */ |
| 524 | | buffer[1] = (hdc.d[dsk_sel].sectors_per_track << 8) | 0; |
| 525 | | /* bits 0-4: heads; bits 5-15: cylinders */ |
| 526 | | buffer[2] = (hdc.d[dsk_sel].heads << 11) | hdc.d[dsk_sel].cylinders; |
| 527 | | |
| 528 | | real_word_count = byte_count >> 1; |
| 529 | | if (real_word_count > 3) |
| 530 | | real_word_count = 3; |
| 531 | | |
| 532 | | /* DMA */ |
| 533 | | if (! (hdc.w[1] & w1_transfer_inhibit)) |
| 534 | | for (i=0; i<real_word_count; i++) |
| 535 | | { |
| 536 | | machine.device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, buffer[i]); |
| 537 | | dma_address = (dma_address + 2) & 0x1ffffe; |
| 538 | | } |
| 539 | | |
| 540 | | hdc.w[7] |= w7_idle | w7_complete; |
| 541 | | update_interrupt(machine); |
| 542 | | } |
| 543 | | |
| 544 | | /* |
| 545 | | Handle the write format command: format a complete track on disk. |
| 546 | | |
| 547 | | The emulation just clears the track data in the disk image. |
| 548 | | */ |
| 549 | | static void write_format(running_machine &machine) |
| 550 | | { |
| 551 | | unsigned int cylinder, head, sector; |
| 552 | | unsigned int lba; |
| 553 | | |
| 554 | | UINT8 buffer[MAX_SECTOR_SIZE]; |
| 555 | | int bytes_written; |
| 556 | | |
| 557 | | int dsk_sel = cur_disk_unit(); |
| 558 | | |
| 559 | | |
| 560 | | if (dsk_sel == -1) |
| 561 | | { |
| 562 | | /* No idea what to report... */ |
| 563 | | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 564 | | update_interrupt(machine); |
| 565 | | return; |
| 566 | | } |
| 567 | | else if (! is_unit_loaded(dsk_sel)) |
| 568 | | { /* offline */ |
| 569 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 570 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 571 | | update_interrupt(machine); |
| 572 | | return; |
| 573 | | } |
| 574 | | else if (hdc.d[dsk_sel].unsafe) |
| 575 | | { /* disk in unsafe condition */ |
| 576 | | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 577 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 578 | | update_interrupt(machine); |
| 579 | | return; |
| 580 | | } |
| 581 | | else if (hdc.d[dsk_sel].wp) |
| 582 | | { /* disk write-protected */ |
| 583 | | hdc.w[0] |= w0_write_protect; |
| 584 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 585 | | update_interrupt(machine); |
| 586 | | return; |
| 587 | | } |
| 588 | | |
| 589 | | cylinder = hdc.w[3]; |
| 590 | | head = hdc.w[1] & w1_head_address; |
| 591 | | |
| 592 | | if (sector_to_lba(machine, dsk_sel, cylinder, head, 0, &lba)) |
| 593 | | return; |
| 594 | | |
| 595 | | memset(buffer, 0, hdc.d[dsk_sel].bytes_per_sector); |
| 596 | | |
| 597 | | for (sector=0; sector<hdc.d[dsk_sel].sectors_per_track; sector++) |
| 598 | | { |
| 599 | | bytes_written = write_sector(dsk_sel, lba, buffer, hdc.d[dsk_sel].bytes_per_sector); |
| 600 | | |
| 601 | | if (bytes_written != hdc.d[dsk_sel].bytes_per_sector) |
| 602 | | { |
| 603 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 604 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 605 | | update_interrupt(machine); |
| 606 | | return; |
| 607 | | } |
| 608 | | |
| 609 | | lba++; |
| 610 | | } |
| 611 | | |
| 612 | | hdc.w[7] |= w7_idle | w7_complete; |
| 613 | | update_interrupt(machine); |
| 614 | | } |
| 615 | | |
| 616 | | /* |
| 617 | | Handle the read data command: read a variable number of sectors from disk. |
| 618 | | */ |
| 619 | | static void read_data(running_machine &machine) |
| 620 | | { |
| 621 | | int dma_address; |
| 622 | | int byte_count; |
| 623 | | |
| 624 | | unsigned int cylinder, head, sector; |
| 625 | | unsigned int lba; |
| 626 | | |
| 627 | | UINT8 buffer[MAX_SECTOR_SIZE]; |
| 628 | | int bytes_to_read; |
| 629 | | int bytes_read; |
| 630 | | int i; |
| 631 | | |
| 632 | | int dsk_sel = cur_disk_unit(); |
| 633 | | |
| 634 | | |
| 635 | | if (dsk_sel == -1) |
| 636 | | { |
| 637 | | /* No idea what to report... */ |
| 638 | | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 639 | | update_interrupt(machine); |
| 640 | | return; |
| 641 | | } |
| 642 | | else if (! is_unit_loaded(dsk_sel)) |
| 643 | | { /* offline */ |
| 644 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 645 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 646 | | update_interrupt(machine); |
| 647 | | return; |
| 648 | | } |
| 649 | | else if (hdc.d[dsk_sel].unsafe) |
| 650 | | { /* disk in unsafe condition */ |
| 651 | | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 652 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 653 | | update_interrupt(machine); |
| 654 | | return; |
| 655 | | } |
| 656 | | |
| 657 | | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 658 | | byte_count = hdc.w[4] & 0xfffe; |
| 659 | | |
| 660 | | cylinder = hdc.w[3]; |
| 661 | | head = hdc.w[1] & w1_head_address; |
| 662 | | sector = hdc.w[2] & 0xff; |
| 663 | | |
| 664 | | if (sector_to_lba(machine, dsk_sel, cylinder, head, sector, &lba)) |
| 665 | | return; |
| 666 | | |
| 667 | | while (byte_count) |
| 668 | | { /* read data sector per sector */ |
| 669 | | if (cylinder > hdc.d[dsk_sel].cylinders) |
| 670 | | { |
| 671 | | hdc.w[0] |= w0_seek_incomplete; |
| 672 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 673 | | update_interrupt(machine); |
| 674 | | return; |
| 675 | | } |
| 676 | | |
| 677 | | bytes_to_read = (byte_count < hdc.d[dsk_sel].bytes_per_sector) ? byte_count : hdc.d[dsk_sel].bytes_per_sector; |
| 678 | | bytes_read = read_sector(dsk_sel, lba, buffer, bytes_to_read); |
| 679 | | |
| 680 | | if (bytes_read != bytes_to_read) |
| 681 | | { /* behave as if the controller could not found the sector ID mark */ |
| 682 | | hdc.w[7] |= w7_idle | w7_error | w7_command_time_out_err; |
| 683 | | update_interrupt(machine); |
| 684 | | return; |
| 685 | | } |
| 686 | | |
| 687 | | /* DMA */ |
| 688 | | if (! (hdc.w[1] & w1_transfer_inhibit)) |
| 689 | | for (i=0; i<bytes_read; i+=2) |
| 690 | | { |
| 691 | | machine.device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, (((int) buffer[i]) << 8) | buffer[i+1]); |
| 692 | | dma_address = (dma_address + 2) & 0x1ffffe; |
| 693 | | } |
| 694 | | |
| 695 | | byte_count -= bytes_read; |
| 696 | | |
| 697 | | /* update sector address to point to next sector */ |
| 698 | | lba++; |
| 699 | | sector++; |
| 700 | | if (sector == hdc.d[dsk_sel].sectors_per_track) |
| 701 | | { |
| 702 | | sector = 0; |
| 703 | | head++; |
| 704 | | if (head == hdc.d[dsk_sel].heads) |
| 705 | | { |
| 706 | | head = 0; |
| 707 | | cylinder++; |
| 708 | | } |
| 709 | | } |
| 710 | | } |
| 711 | | |
| 712 | | hdc.w[7] |= w7_idle | w7_complete; |
| 713 | | update_interrupt(machine); |
| 714 | | } |
| 715 | | |
| 716 | | /* |
| 717 | | Handle the write data command: write a variable number of sectors from disk. |
| 718 | | */ |
| 719 | | static void write_data(running_machine &machine) |
| 720 | | { |
| 721 | | int dma_address; |
| 722 | | int byte_count; |
| 723 | | |
| 724 | | unsigned int cylinder, head, sector; |
| 725 | | unsigned int lba; |
| 726 | | |
| 727 | | UINT8 buffer[MAX_SECTOR_SIZE]; |
| 728 | | UINT16 word; |
| 729 | | int bytes_written; |
| 730 | | int i; |
| 731 | | |
| 732 | | int dsk_sel = cur_disk_unit(); |
| 733 | | |
| 734 | | |
| 735 | | if (dsk_sel == -1) |
| 736 | | { |
| 737 | | /* No idea what to report... */ |
| 738 | | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 739 | | update_interrupt(machine); |
| 740 | | return; |
| 741 | | } |
| 742 | | else if (! is_unit_loaded(dsk_sel)) |
| 743 | | { /* offline */ |
| 744 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 745 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 746 | | update_interrupt(machine); |
| 747 | | return; |
| 748 | | } |
| 749 | | else if (hdc.d[dsk_sel].unsafe) |
| 750 | | { /* disk in unsafe condition */ |
| 751 | | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 752 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 753 | | update_interrupt(machine); |
| 754 | | return; |
| 755 | | } |
| 756 | | else if (hdc.d[dsk_sel].wp) |
| 757 | | { /* disk write-protected */ |
| 758 | | hdc.w[0] |= w0_write_protect; |
| 759 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 760 | | update_interrupt(machine); |
| 761 | | return; |
| 762 | | } |
| 763 | | |
| 764 | | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 765 | | byte_count = hdc.w[4] & 0xfffe; |
| 766 | | |
| 767 | | cylinder = hdc.w[3]; |
| 768 | | head = hdc.w[1] & w1_head_address; |
| 769 | | sector = hdc.w[2] & 0xff; |
| 770 | | |
| 771 | | if (sector_to_lba(machine, dsk_sel, cylinder, head, sector, &lba)) |
| 772 | | return; |
| 773 | | |
| 774 | | while (byte_count > 0) |
| 775 | | { /* write data sector per sector */ |
| 776 | | if (cylinder > hdc.d[dsk_sel].cylinders) |
| 777 | | { |
| 778 | | hdc.w[0] |= w0_seek_incomplete; |
| 779 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 780 | | update_interrupt(machine); |
| 781 | | return; |
| 782 | | } |
| 783 | | |
| 784 | | /* DMA */ |
| 785 | | for (i=0; (i<byte_count) && (i<hdc.d[dsk_sel].bytes_per_sector); i+=2) |
| 786 | | { |
| 787 | | word = machine.device("maincpu")->memory().space(AS_PROGRAM).read_word(dma_address); |
| 788 | | buffer[i] = word >> 8; |
| 789 | | buffer[i+1] = word & 0xff; |
| 790 | | |
| 791 | | dma_address = (dma_address + 2) & 0x1ffffe; |
| 792 | | } |
| 793 | | /* fill with 0s if we did not reach sector end */ |
| 794 | | for (; i<hdc.d[dsk_sel].bytes_per_sector; i+=2) |
| 795 | | buffer[i] = buffer[i+1] = 0; |
| 796 | | |
| 797 | | bytes_written = write_sector(dsk_sel, lba, buffer, hdc.d[dsk_sel].bytes_per_sector); |
| 798 | | |
| 799 | | if (bytes_written != hdc.d[dsk_sel].bytes_per_sector) |
| 800 | | { |
| 801 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 802 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 803 | | update_interrupt(machine); |
| 804 | | return; |
| 805 | | } |
| 806 | | |
| 807 | | byte_count -= bytes_written; |
| 808 | | |
| 809 | | /* update sector address to point to next sector */ |
| 810 | | lba++; |
| 811 | | sector++; |
| 812 | | if (sector == hdc.d[dsk_sel].sectors_per_track) |
| 813 | | { |
| 814 | | sector = 0; |
| 815 | | head++; |
| 816 | | if (head == hdc.d[dsk_sel].heads) |
| 817 | | { |
| 818 | | head = 0; |
| 819 | | cylinder++; |
| 820 | | } |
| 821 | | } |
| 822 | | } |
| 823 | | |
| 824 | | hdc.w[7] |= w7_idle | w7_complete; |
| 825 | | update_interrupt(machine); |
| 826 | | } |
| 827 | | |
| 828 | | /* |
| 829 | | Handle the unformatted read command: read drive geometry information. |
| 830 | | */ |
| 831 | | static void unformatted_read(running_machine &machine) |
| 832 | | { |
| 833 | | int dma_address; |
| 834 | | int byte_count; |
| 835 | | |
| 836 | | unsigned int cylinder, head, sector; |
| 837 | | |
| 838 | | UINT16 buffer[3]; |
| 839 | | int i, real_word_count; |
| 840 | | |
| 841 | | int dsk_sel = cur_disk_unit(); |
| 842 | | |
| 843 | | |
| 844 | | if (dsk_sel == -1) |
| 845 | | { |
| 846 | | /* No idea what to report... */ |
| 847 | | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 848 | | update_interrupt(machine); |
| 849 | | return; |
| 850 | | } |
| 851 | | else if (! is_unit_loaded(dsk_sel)) |
| 852 | | { /* offline */ |
| 853 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 854 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 855 | | update_interrupt(machine); |
| 856 | | return; |
| 857 | | } |
| 858 | | else if (hdc.d[dsk_sel].unsafe) |
| 859 | | { /* disk in unsafe condition */ |
| 860 | | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 861 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 862 | | update_interrupt(machine); |
| 863 | | return; |
| 864 | | } |
| 865 | | |
| 866 | | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 867 | | byte_count = hdc.w[4] & 0xfffe; |
| 868 | | |
| 869 | | cylinder = hdc.w[3]; |
| 870 | | head = hdc.w[1] & w1_head_address; |
| 871 | | sector = hdc.w[2] & 0xff; |
| 872 | | |
| 873 | | if (check_sector_address(machine, dsk_sel, cylinder, head, sector)) |
| 874 | | return; |
| 875 | | |
| 876 | | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 877 | | byte_count = hdc.w[4] & 0xfffe; |
| 878 | | |
| 879 | | /* bits 0-4: head address; bits 5-15: cylinder address */ |
| 880 | | buffer[0] = (head << 11) | cylinder; |
| 881 | | /* MSByte: sectors per record (1); LSByte: sector address */ |
| 882 | | buffer[1] = (1 << 8) | sector; |
| 883 | | /* formatted words per record */ |
| 884 | | buffer[2] = hdc.d[dsk_sel].bytes_per_sector >> 1; |
| 885 | | |
| 886 | | real_word_count = byte_count >> 1; |
| 887 | | if (real_word_count > 3) |
| 888 | | real_word_count = 3; |
| 889 | | |
| 890 | | /* DMA */ |
| 891 | | if (! (hdc.w[1] & w1_transfer_inhibit)) |
| 892 | | for (i=0; i<real_word_count; i++) |
| 893 | | { |
| 894 | | machine.device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, buffer[i]); |
| 895 | | dma_address = (dma_address + 2) & 0x1ffffe; |
| 896 | | } |
| 897 | | |
| 898 | | hdc.w[7] |= w7_idle | w7_complete; |
| 899 | | update_interrupt(machine); |
| 900 | | } |
| 901 | | |
| 902 | | /* |
| 903 | | Handle the restore command: return to track 0. |
| 904 | | */ |
| 905 | | static void restore(running_machine &machine) |
| 906 | | { |
| 907 | | int dsk_sel = cur_disk_unit(); |
| 908 | | |
| 909 | | |
| 910 | | if (dsk_sel == -1) |
| 911 | | { |
| 912 | | /* No idea what to report... */ |
| 913 | | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 914 | | update_interrupt(machine); |
| 915 | | return; |
| 916 | | } |
| 917 | | else if (! is_unit_loaded(dsk_sel)) |
| 918 | | { /* offline */ |
| 919 | | hdc.w[0] |= w0_offline | w0_not_ready; |
| 920 | | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 921 | | update_interrupt(machine); |
| 922 | | return; |
| 923 | | } |
| 924 | | |
| 925 | | hdc.d[dsk_sel].unsafe = 0; /* I think */ |
| 926 | | |
| 927 | | /*if (seek_to_sector(dsk_sel, 0, 0, 0)) |
| 928 | | return;*/ |
| 929 | | |
| 930 | | hdc.w[7] |= w7_idle | w7_complete; |
| 931 | | update_interrupt(machine); |
| 932 | | } |
| 933 | | |
| 934 | | /* |
| 935 | | Parse command code and execute the command. |
| 936 | | */ |
| 937 | | static void execute_command(running_machine &machine) |
| 938 | | { |
| 939 | | /* hack */ |
| 940 | | hdc.w[0] &= 0xff; |
| 941 | | |
| 942 | | if (hdc.w[1] & w1_extended_command) |
| 943 | | logerror("extended commands not supported\n"); |
| 944 | | |
| 945 | | switch (/*((hdc.w[1] & w1_extended_command) >> 11) |*/ ((hdc.w[1] & w1_command) >> 8)) |
| 946 | | { |
| 947 | | case 0x00: //0b000: |
| 948 | | /* store registers */ |
| 949 | | logerror("store registers\n"); |
| 950 | | store_registers(machine); |
| 951 | | break; |
| 952 | | case 0x01: //0b001: |
| 953 | | /* write format */ |
| 954 | | logerror("write format\n"); |
| 955 | | write_format(machine); |
| 956 | | break; |
| 957 | | case 0x02: //0b010: |
| 958 | | /* read data */ |
| 959 | | logerror("read data\n"); |
| 960 | | read_data(machine); |
| 961 | | break; |
| 962 | | case 0x03: //0b011: |
| 963 | | /* write data */ |
| 964 | | logerror("write data\n"); |
| 965 | | write_data(machine); |
| 966 | | break; |
| 967 | | case 0x04: //0b100: |
| 968 | | /* unformatted read */ |
| 969 | | logerror("unformatted read\n"); |
| 970 | | unformatted_read(machine); |
| 971 | | break; |
| 972 | | case 0x05: //0b101: |
| 973 | | /* unformatted write */ |
| 974 | | logerror("unformatted write\n"); |
| 975 | | /* ... */ |
| 976 | | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 977 | | update_interrupt(machine); |
| 978 | | break; |
| 979 | | case 0x06: //0b110: |
| 980 | | /* seek */ |
| 981 | | logerror("seek\n"); |
| 982 | | /* This command can (almost) safely be ignored */ |
| 983 | | hdc.w[7] |= w7_idle | w7_complete; |
| 984 | | update_interrupt(machine); |
| 985 | | break; |
| 986 | | case 0x07: //0b111: |
| 987 | | /* restore */ |
| 988 | | logerror("restore\n"); |
| 989 | | restore(machine); |
| 990 | | break; |
| 991 | | } |
| 992 | | } |
| 993 | | |
| 994 | | /* |
| 995 | | Read one register in TPCS space |
| 996 | | */ |
| 997 | | READ16_HANDLER(ti990_hdc_r) |
| 998 | | { |
| 999 | | if (offset < 8) |
| 1000 | | return hdc.w[offset]; |
| 1001 | | else |
| 1002 | | return 0; |
| 1003 | | } |
| 1004 | | |
| 1005 | | /* |
| 1006 | | Write one register in TPCS space. Execute command if w7_idle is cleared. |
| 1007 | | */ |
| 1008 | | WRITE16_HANDLER(ti990_hdc_w) |
| 1009 | | { |
| 1010 | | if (offset < 8) |
| 1011 | | { |
| 1012 | | /* write protect if a command is in progress */ |
| 1013 | | if (hdc.w[7] & w7_idle) |
| 1014 | | { |
| 1015 | | UINT16 old_data = hdc.w[offset]; |
| 1016 | | |
| 1017 | | /* Only write writable bits AND honor byte accesses (ha!) */ |
| 1018 | | hdc.w[offset] = (hdc.w[offset] & ((~w_mask[offset]) | mem_mask)) | (data & w_mask[offset] & ~mem_mask); |
| 1019 | | |
| 1020 | | if ((offset == 0) || (offset == 7)) |
| 1021 | | update_interrupt(space.machine()); |
| 1022 | | |
| 1023 | | if ((offset == 7) && (old_data & w7_idle) && ! (data & w7_idle)) |
| 1024 | | { /* idle has been cleared: start command execution */ |
| 1025 | | execute_command(space.machine()); |
| 1026 | | } |
| 1027 | | } |
| 1028 | | } |
| 1029 | | } |
| 1030 | | |
| 1031 | | |
| 1032 | | static const struct harddisk_interface ti990_harddisk_config = |
| 1033 | | { |
| 1034 | | DEVICE_IMAGE_LOAD_NAME_LEGACY( ti990_hd ), |
| 1035 | | DEVICE_IMAGE_UNLOAD_NAME_LEGACY( ti990_hd ), |
| 1036 | | NULL, |
| 1037 | | NULL |
| 1038 | | }; |
| 1039 | | |
| 1040 | | MACHINE_CONFIG_FRAGMENT( ti990_hdc ) |
| 1041 | | MCFG_HARDDISK_CONFIG_ADD( "harddisk1", ti990_harddisk_config ) |
| 1042 | | MCFG_HARDDISK_CONFIG_ADD( "harddisk2", ti990_harddisk_config ) |
| 1043 | | MCFG_HARDDISK_CONFIG_ADD( "harddisk3", ti990_harddisk_config ) |
| 1044 | | MCFG_HARDDISK_CONFIG_ADD( "harddisk4", ti990_harddisk_config ) |
| 1045 | | MACHINE_CONFIG_END |
trunk/src/mess/machine/990_dk.c
| r26090 | r26091 | |
| 1 | | /* |
| 2 | | 990_dk.c: emulation of a TI FD800 'Diablo' floppy disk controller |
| 3 | | controller, for use with any TI990 system (and possibly any system which |
| 4 | | implements the CRU bus). |
| 5 | | |
| 6 | | This floppy disk controller supports IBM-format 8" SSSD and DSSD floppies. |
| 7 | | |
| 8 | | Raphael Nabet 2003 |
| 9 | | */ |
| 10 | | |
| 11 | | #include "emu.h" |
| 12 | | |
| 13 | | #include "formats/basicdsk.h" |
| 14 | | #include "imagedev/flopdrv.h" |
| 15 | | #include "990_dk.h" |
| 16 | | |
| 17 | | #define MAX_FLOPPIES 4 |
| 18 | | |
| 19 | | enum buf_mode_t { |
| 20 | | bm_off, bm_read, bm_write |
| 21 | | }; |
| 22 | | static struct |
| 23 | | { |
| 24 | | running_machine *machine; |
| 25 | | UINT16 recv_buf; |
| 26 | | UINT16 stat_reg; |
| 27 | | UINT16 xmit_buf; |
| 28 | | UINT16 cmd_reg; |
| 29 | | |
| 30 | | int interrupt_f_f; |
| 31 | | void (*interrupt_callback)(running_machine &, int state); |
| 32 | | |
| 33 | | UINT8 buf[128]; |
| 34 | | int buf_pos; |
| 35 | | buf_mode_t buf_mode; |
| 36 | | int unit; |
| 37 | | int head; |
| 38 | | int sector; |
| 39 | | /*int non_seq_mode;*/ |
| 40 | | int ddam; |
| 41 | | |
| 42 | | struct |
| 43 | | { |
| 44 | | device_image_interface *img; |
| 45 | | int phys_cylinder; |
| 46 | | int log_cylinder[2]; |
| 47 | | int seclen; |
| 48 | | } drv[MAX_FLOPPIES]; |
| 49 | | } fd800; |
| 50 | | |
| 51 | | /* status bits */ |
| 52 | | enum |
| 53 | | { |
| 54 | | status_OP_complete = 1 << 0, |
| 55 | | status_XFER_ready = 1 << 1, |
| 56 | | status_drv_not_ready= 1 << 2, |
| 57 | | status_dat_chk_err = 1 << 3, |
| 58 | | status_seek_err = 1 << 4, |
| 59 | | status_invalid_cmd = 1 << 5, |
| 60 | | status_no_addr_mark = 1 << 6, |
| 61 | | status_equ_chk_err = 1 << 7, |
| 62 | | status_ID_chk_err = 1 << 8, |
| 63 | | status_ID_not_found = 1 << 9, |
| 64 | | status_ctlr_busy = 1 << 10, |
| 65 | | status_write_prot = 1 << 11, |
| 66 | | status_del_sector = 1 << 12, |
| 67 | | status_interrupt = 1 << 15, |
| 68 | | |
| 69 | | status_unit_shift = 13 |
| 70 | | }; |
| 71 | | |
| 72 | | LEGACY_FLOPPY_OPTIONS_START(fd800) |
| 73 | | #if 1 |
| 74 | | /* SSSD 8" */ |
| 75 | | LEGACY_FLOPPY_OPTION(fd800, "dsk", "TI990 8\" SSSD disk image", basicdsk_identify_default, basicdsk_construct_default, NULL, |
| 76 | | HEADS([1]) |
| 77 | | TRACKS([77]) |
| 78 | | SECTORS([26]) |
| 79 | | SECTOR_LENGTH([128]) |
| 80 | | FIRST_SECTOR_ID([1])) |
| 81 | | #elif 0 |
| 82 | | /* DSSD 8" */ |
| 83 | | LEGACY_FLOPPY_OPTION(fd800, "dsk", "TI990 8\" DSSD disk image", basicdsk_identify_default, basicdsk_construct_default, NULL, |
| 84 | | HEADS([2]) |
| 85 | | TRACKS([77]) |
| 86 | | SECTORS([26]) |
| 87 | | SECTOR_LENGTH([128]) |
| 88 | | FIRST_SECTOR_ID([1])) |
| 89 | | #endif |
| 90 | | LEGACY_FLOPPY_OPTIONS_END |
| 91 | | |
| 92 | | static void fd800_field_interrupt(void) |
| 93 | | { |
| 94 | | if (fd800.interrupt_callback) |
| 95 | | (*fd800.interrupt_callback)(*fd800.machine, (fd800.stat_reg & status_interrupt) && ! fd800.interrupt_f_f); |
| 96 | | } |
| 97 | | |
| 98 | | static void fd800_unload_proc(device_image_interface &image) |
| 99 | | { |
| 100 | | int unit = floppy_get_drive(&image.device()); |
| 101 | | |
| 102 | | fd800.drv[unit].log_cylinder[0] = fd800.drv[unit].log_cylinder[1] = -1; |
| 103 | | } |
| 104 | | |
| 105 | | void fd800_machine_init(running_machine &machine, void (*interrupt_callback)(running_machine &machine, int state)) |
| 106 | | { |
| 107 | | int i; |
| 108 | | |
| 109 | | fd800.machine = &machine; |
| 110 | | fd800.interrupt_callback = interrupt_callback; |
| 111 | | |
| 112 | | fd800.stat_reg = 0; |
| 113 | | fd800.interrupt_f_f = 1; |
| 114 | | |
| 115 | | fd800.buf_pos = 0; |
| 116 | | fd800.buf_mode = bm_off; |
| 117 | | |
| 118 | | for (i=0; i<MAX_FLOPPIES; i++) |
| 119 | | { |
| 120 | | fd800.drv[i].img = dynamic_cast<device_image_interface *>(floppy_get_device(machine, i)); |
| 121 | | fd800.drv[i].phys_cylinder = -1; |
| 122 | | fd800.drv[i].log_cylinder[0] = fd800.drv[i].log_cylinder[1] = -1; |
| 123 | | fd800.drv[i].seclen = 64; |
| 124 | | floppy_install_unload_proc(&fd800.drv[i].img->device(), fd800_unload_proc); |
| 125 | | } |
| 126 | | |
| 127 | | fd800_field_interrupt(); |
| 128 | | } |
| 129 | | |
| 130 | | /* |
| 131 | | Read the first id field that can be found on the floppy disk. |
| 132 | | |
| 133 | | unit: floppy drive index |
| 134 | | head: selected head |
| 135 | | cylinder_id: cylinder ID read |
| 136 | | sector_id: sector ID read |
| 137 | | |
| 138 | | Return TRUE if an ID was found |
| 139 | | */ |
| 140 | | static int fd800_read_id(int unit, int head, int *cylinder_id, int *sector_id) |
| 141 | | { |
| 142 | | /*UINT8 revolution_count;*/ |
| 143 | | chrn_id id; |
| 144 | | |
| 145 | | /*revolution_count = 0;*/ |
| 146 | | |
| 147 | | /*while (revolution_count < 2)*/ |
| 148 | | /*{*/ |
| 149 | | if (floppy_drive_get_next_id(&fd800.drv[unit].img->device(), head, &id)) |
| 150 | | { |
| 151 | | if (cylinder_id) |
| 152 | | *cylinder_id = id.C; |
| 153 | | if (sector_id) |
| 154 | | *sector_id = id.R; |
| 155 | | return TRUE; |
| 156 | | } |
| 157 | | /*}*/ |
| 158 | | |
| 159 | | return FALSE; |
| 160 | | } |
| 161 | | |
| 162 | | /* |
| 163 | | Find a sector by id. |
| 164 | | |
| 165 | | unit: floppy drive index |
| 166 | | head: selected head |
| 167 | | sector: sector ID to search |
| 168 | | data_id: data ID to be used when calling sector read/write functions |
| 169 | | |
| 170 | | Return TRUE if the given sector ID was found |
| 171 | | */ |
| 172 | | static int fd800_find_sector(int unit, int head, int sector, int *data_id) |
| 173 | | { |
| 174 | | UINT8 revolution_count; |
| 175 | | chrn_id id; |
| 176 | | |
| 177 | | revolution_count = 0; |
| 178 | | |
| 179 | | while (revolution_count < 2) |
| 180 | | { |
| 181 | | if (floppy_drive_get_next_id(&fd800.drv[unit].img->device(), head, &id)) |
| 182 | | { |
| 183 | | /* compare id */ |
| 184 | | if ((id.R == sector) && (id.N == 0)) |
| 185 | | { |
| 186 | | *data_id = id.data_id; |
| 187 | | /* get ddam status */ |
| 188 | | /*w->ddam = id.flags & ID_FLAG_DELETED_DATA;*/ |
| 189 | | return TRUE; |
| 190 | | } |
| 191 | | } |
| 192 | | } |
| 193 | | |
| 194 | | return FALSE; |
| 195 | | } |
| 196 | | |
| 197 | | /* |
| 198 | | Perform seek command |
| 199 | | |
| 200 | | unit: floppy drive index |
| 201 | | cylinder: track to seek for |
| 202 | | head: head for which the seek is performed |
| 203 | | |
| 204 | | Return FALSE if the seek was successful |
| 205 | | */ |
| 206 | | static int fd800_do_seek(int unit, int cylinder, int head) |
| 207 | | { |
| 208 | | int retries; |
| 209 | | |
| 210 | | if (cylinder > 76) |
| 211 | | { |
| 212 | | fd800.stat_reg |= status_invalid_cmd; |
| 213 | | return TRUE; |
| 214 | | } |
| 215 | | |
| 216 | | if (!fd800.drv[unit].img->exists()) |
| 217 | | { |
| 218 | | fd800.stat_reg |= status_drv_not_ready; /* right??? */ |
| 219 | | return TRUE; |
| 220 | | } |
| 221 | | |
| 222 | | if (fd800.drv[unit].log_cylinder[head] == -1) |
| 223 | | { /* current track ID is unknown: read it */ |
| 224 | | if (!fd800_read_id(unit, head, &fd800.drv[unit].log_cylinder[head], NULL)) |
| 225 | | { |
| 226 | | fd800.stat_reg |= status_ID_not_found; |
| 227 | | return TRUE; |
| 228 | | } |
| 229 | | } |
| 230 | | /* exit if we are already at the requested track */ |
| 231 | | if (fd800.drv[unit].log_cylinder[head] == cylinder) |
| 232 | | { |
| 233 | | /*fd800.stat_reg |= status_OP_complete;*/ |
| 234 | | return FALSE; |
| 235 | | } |
| 236 | | for (retries=0; retries<10; retries++) |
| 237 | | { /* seek to requested track */ |
| 238 | | floppy_drive_seek(&fd800.drv[unit].img->device(), cylinder-fd800.drv[unit].log_cylinder[head]); |
| 239 | | /* update physical track position */ |
| 240 | | if (fd800.drv[unit].phys_cylinder != -1) |
| 241 | | fd800.drv[unit].phys_cylinder += cylinder-fd800.drv[unit].log_cylinder[head]; |
| 242 | | /* read new track ID */ |
| 243 | | if (!fd800_read_id(unit, head, &fd800.drv[unit].log_cylinder[head], NULL)) |
| 244 | | { |
| 245 | | fd800.drv[unit].log_cylinder[head] = -1; |
| 246 | | fd800.stat_reg |= status_ID_not_found; |
| 247 | | return TRUE; |
| 248 | | } |
| 249 | | /* exit if we have reached the requested track */ |
| 250 | | if (fd800.drv[unit].log_cylinder[head] == cylinder) |
| 251 | | { |
| 252 | | /*fd800.stat_reg |= status_OP_complete;*/ |
| 253 | | return FALSE; |
| 254 | | } |
| 255 | | } |
| 256 | | /* track not found */ |
| 257 | | fd800.stat_reg |= status_seek_err; |
| 258 | | return TRUE; |
| 259 | | } |
| 260 | | |
| 261 | | /* |
| 262 | | Perform restore command |
| 263 | | |
| 264 | | unit: floppy drive index |
| 265 | | |
| 266 | | Return FALSE if the restore was successful |
| 267 | | */ |
| 268 | | static int fd800_do_restore(int unit) |
| 269 | | { |
| 270 | | int seek_count = 0; |
| 271 | | int seek_complete; |
| 272 | | |
| 273 | | if (!fd800.drv[unit].img->exists()) |
| 274 | | { |
| 275 | | fd800.stat_reg |= status_drv_not_ready; /* right??? */ |
| 276 | | return TRUE; |
| 277 | | } |
| 278 | | |
| 279 | | /* limit iterations to 76 to prevent an endless loop if the disc is locked */ |
| 280 | | while (!(seek_complete = !floppy_tk00_r(&fd800.drv[unit].img->device())) && (seek_count < 76)) |
| 281 | | { |
| 282 | | floppy_drive_seek(&fd800.drv[unit].img->device(), -1); |
| 283 | | seek_count++; |
| 284 | | } |
| 285 | | if (! seek_complete) |
| 286 | | { |
| 287 | | fd800.drv[unit].phys_cylinder = -1; |
| 288 | | fd800.stat_reg |= status_seek_err; |
| 289 | | } |
| 290 | | else |
| 291 | | { |
| 292 | | fd800.drv[unit].phys_cylinder = 0; |
| 293 | | /*fd800.stat_reg |= status_OP_complete;*/ |
| 294 | | } |
| 295 | | |
| 296 | | return ! seek_complete; |
| 297 | | } |
| 298 | | |
| 299 | | /* |
| 300 | | Perform a read operation for one sector |
| 301 | | */ |
| 302 | | static void fd800_do_read(void) |
| 303 | | { |
| 304 | | int data_id; |
| 305 | | |
| 306 | | if ((fd800.sector == 0) || (fd800.sector > 26)) |
| 307 | | { |
| 308 | | fd800.stat_reg |= status_invalid_cmd; |
| 309 | | return; |
| 310 | | } |
| 311 | | |
| 312 | | if (!fd800_find_sector(fd800.unit, fd800.head, fd800.sector, &data_id)) |
| 313 | | { |
| 314 | | fd800.stat_reg |= status_ID_not_found; |
| 315 | | return; |
| 316 | | } |
| 317 | | |
| 318 | | floppy_drive_read_sector_data(&fd800.drv[fd800.unit].img->device(), fd800.head, data_id, fd800.buf, 128); |
| 319 | | fd800.buf_pos = 0; |
| 320 | | fd800.buf_mode = bm_read; |
| 321 | | fd800.recv_buf = (fd800.buf[fd800.buf_pos<<1] << 8) | fd800.buf[(fd800.buf_pos<<1)+1]; |
| 322 | | |
| 323 | | fd800.stat_reg |= status_XFER_ready; |
| 324 | | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 325 | | } |
| 326 | | |
| 327 | | /* |
| 328 | | Perform a write operation for one sector |
| 329 | | */ |
| 330 | | static void fd800_do_write(void) |
| 331 | | { |
| 332 | | int data_id; |
| 333 | | |
| 334 | | if (fd800.drv[fd800.unit].seclen < 64) |
| 335 | | /* fill with 0s */ |
| 336 | | memset(fd800.buf+(fd800.drv[fd800.unit].seclen<<1), 0, (64-fd800.drv[fd800.unit].seclen)<<1); |
| 337 | | |
| 338 | | if (!fd800_find_sector(fd800.unit, fd800.head, fd800.sector, &data_id)) |
| 339 | | { |
| 340 | | fd800.stat_reg |= status_ID_not_found; |
| 341 | | return; |
| 342 | | } |
| 343 | | |
| 344 | | floppy_drive_write_sector_data(&fd800.drv[fd800.unit].img->device(), fd800.head, data_id, fd800.buf, 128, fd800.ddam); |
| 345 | | fd800.buf_pos = 0; |
| 346 | | fd800.buf_mode = bm_write; |
| 347 | | |
| 348 | | fd800.stat_reg |= status_XFER_ready; |
| 349 | | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 350 | | } |
| 351 | | |
| 352 | | /* |
| 353 | | Execute a fdc command |
| 354 | | */ |
| 355 | | static void fd800_do_cmd(void) |
| 356 | | { |
| 357 | | int unit; |
| 358 | | int cylinder; |
| 359 | | int head; |
| 360 | | int seclen; |
| 361 | | int sector; |
| 362 | | |
| 363 | | |
| 364 | | if (fd800.buf_mode != bm_off) |
| 365 | | { /* All commands in the midst of read or write are interpreted as Stop */ |
| 366 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 367 | | |
| 368 | | /* reset status */ |
| 369 | | fd800.stat_reg = unit << status_unit_shift; |
| 370 | | |
| 371 | | fd800.buf_pos = 0; |
| 372 | | fd800.buf_mode = bm_off; |
| 373 | | |
| 374 | | fd800.stat_reg |= status_OP_complete; |
| 375 | | |
| 376 | | fd800.stat_reg |= status_interrupt; |
| 377 | | fd800_field_interrupt(); |
| 378 | | |
| 379 | | return; |
| 380 | | } |
| 381 | | |
| 382 | | switch (fd800.cmd_reg >> 12) |
| 383 | | { |
| 384 | | case 0: /* select |
| 385 | | bits 16-25: 0s |
| 386 | | bits 26-27: unit number (0-3) */ |
| 387 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 388 | | |
| 389 | | /* reset status */ |
| 390 | | fd800.stat_reg = unit << status_unit_shift; |
| 391 | | |
| 392 | | if (!fd800.drv[unit].img->exists()) |
| 393 | | fd800.stat_reg |= status_drv_not_ready; /* right??? */ |
| 394 | | else if (fd800.drv[unit].img->is_readonly()) |
| 395 | | fd800.stat_reg |= status_write_prot; |
| 396 | | else |
| 397 | | fd800.stat_reg |= status_OP_complete; |
| 398 | | |
| 399 | | fd800.stat_reg |= status_interrupt; |
| 400 | | fd800_field_interrupt(); |
| 401 | | break; |
| 402 | | |
| 403 | | case 1: /* seek |
| 404 | | bits 16-22: cylinder number (0-76) |
| 405 | | bits 23-24: 0s |
| 406 | | bits 25: head number (1=upper) |
| 407 | | bits 26-27: unit number (0-3) */ |
| 408 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 409 | | head = (fd800.cmd_reg >> 9) & 1; |
| 410 | | cylinder = fd800.cmd_reg & 0x7f; |
| 411 | | |
| 412 | | /* reset status */ |
| 413 | | fd800.stat_reg = unit << status_unit_shift; |
| 414 | | |
| 415 | | if (!fd800_do_seek(unit, cylinder, head)) |
| 416 | | fd800.stat_reg |= status_OP_complete; |
| 417 | | |
| 418 | | fd800.stat_reg |= status_interrupt; |
| 419 | | fd800_field_interrupt(); |
| 420 | | break; |
| 421 | | |
| 422 | | case 2: /* restore |
| 423 | | bits 16-25: 0s |
| 424 | | bits 26-27: unit number (0-3) */ |
| 425 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 426 | | |
| 427 | | /* reset status */ |
| 428 | | fd800.stat_reg = unit << status_unit_shift; |
| 429 | | |
| 430 | | if (!fd800_do_restore(unit)) |
| 431 | | fd800.stat_reg |= status_OP_complete; |
| 432 | | |
| 433 | | fd800.stat_reg |= status_interrupt; |
| 434 | | fd800_field_interrupt(); |
| 435 | | break; |
| 436 | | |
| 437 | | case 3: /* sector length |
| 438 | | bits 16-22: sector word count (0-64) |
| 439 | | bits 23-25: 0s |
| 440 | | bits 26-27: unit number (0-3) */ |
| 441 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 442 | | seclen = fd800.cmd_reg & 0x7f; |
| 443 | | |
| 444 | | /* reset status */ |
| 445 | | fd800.stat_reg = unit << status_unit_shift; |
| 446 | | |
| 447 | | if ((seclen > 64) || (seclen == 0)) |
| 448 | | { |
| 449 | | fd800.stat_reg |= status_invalid_cmd; |
| 450 | | } |
| 451 | | else |
| 452 | | { |
| 453 | | fd800.drv[unit].seclen = seclen; |
| 454 | | fd800.stat_reg |= status_OP_complete; |
| 455 | | } |
| 456 | | |
| 457 | | fd800.stat_reg |= status_interrupt; |
| 458 | | fd800_field_interrupt(); |
| 459 | | break; |
| 460 | | |
| 461 | | case 4: /* read |
| 462 | | bits 16-20: sector number (1-26) |
| 463 | | bits 21-23: 0s |
| 464 | | bit 24: no sequential sectoring (1=active) |
| 465 | | bit 25: head number (1=upper) |
| 466 | | bits 26-27: unit number (0-3) */ |
| 467 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 468 | | head = (fd800.cmd_reg >> 9) & 1; |
| 469 | | /*non_seq_mode = (fd800.cmd_reg >> 8) & 1;*/ |
| 470 | | sector = fd800.cmd_reg & 0x1f; |
| 471 | | |
| 472 | | fd800.unit = unit; |
| 473 | | fd800.head = head; |
| 474 | | fd800.sector = sector; |
| 475 | | /*fd800.non_seq_mode = non_seq_mode;*/ |
| 476 | | |
| 477 | | /* reset status */ |
| 478 | | fd800.stat_reg = unit << status_unit_shift; |
| 479 | | |
| 480 | | fd800_do_read(); |
| 481 | | |
| 482 | | fd800.stat_reg |= status_interrupt; |
| 483 | | fd800_field_interrupt(); |
| 484 | | break; |
| 485 | | |
| 486 | | case 5: /* read ID |
| 487 | | bits 16-24: 0s |
| 488 | | bit 25: head number (1=upper) |
| 489 | | bits 26-27: unit number (0-3) */ |
| 490 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 491 | | head = (fd800.cmd_reg >> 9) & 1; |
| 492 | | |
| 493 | | /* reset status */ |
| 494 | | fd800.stat_reg = unit << status_unit_shift; |
| 495 | | |
| 496 | | if (!fd800_read_id(unit, head, &cylinder, §or)) |
| 497 | | { |
| 498 | | fd800.stat_reg |= status_ID_not_found; |
| 499 | | } |
| 500 | | else |
| 501 | | { |
| 502 | | fd800.recv_buf = (cylinder << 8) | sector; |
| 503 | | fd800.stat_reg |= status_OP_complete; |
| 504 | | } |
| 505 | | |
| 506 | | fd800.stat_reg |= status_interrupt; |
| 507 | | fd800_field_interrupt(); |
| 508 | | break; |
| 509 | | |
| 510 | | case 6: /* read unformatted |
| 511 | | bits 16-20: sector number (1-26) |
| 512 | | bits 21-24: 0s |
| 513 | | bit 25: head number (1=upper) |
| 514 | | bits 26-27: unit number (0-3) */ |
| 515 | | /* ... */ |
| 516 | | break; |
| 517 | | |
| 518 | | case 7: /* write |
| 519 | | bits 16-20: sector number (1-26) |
| 520 | | bits 21-24: 0s |
| 521 | | bit 25: head number (1=upper) |
| 522 | | bits 26-27: unit number (0-3) */ |
| 523 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 524 | | head = (fd800.cmd_reg >> 9) & 1; |
| 525 | | sector = fd800.cmd_reg & 0x1f; |
| 526 | | |
| 527 | | /* reset status */ |
| 528 | | fd800.stat_reg = unit << status_unit_shift; |
| 529 | | |
| 530 | | if ((fd800.sector == 0) || (fd800.sector > 26)) |
| 531 | | { |
| 532 | | fd800.stat_reg |= status_invalid_cmd; |
| 533 | | } |
| 534 | | else |
| 535 | | { |
| 536 | | fd800.unit = unit; |
| 537 | | fd800.head = head; |
| 538 | | fd800.sector = sector; |
| 539 | | fd800.ddam = 0; |
| 540 | | |
| 541 | | fd800.buf_pos = 0; |
| 542 | | fd800.buf_mode = bm_write; |
| 543 | | fd800.stat_reg |= status_XFER_ready; |
| 544 | | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 545 | | } |
| 546 | | |
| 547 | | fd800.stat_reg |= status_interrupt; |
| 548 | | fd800_field_interrupt(); |
| 549 | | break; |
| 550 | | |
| 551 | | case 8: /* write delete |
| 552 | | bits 16-20: sector number (1-26) |
| 553 | | bits 21-24: 0s |
| 554 | | bit 25: head number (1=upper) |
| 555 | | bits 26-27: unit number (0-3) */ |
| 556 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 557 | | head = (fd800.cmd_reg >> 9) & 1; |
| 558 | | sector = fd800.cmd_reg & 0x1f; |
| 559 | | |
| 560 | | /* reset status */ |
| 561 | | fd800.stat_reg = unit << status_unit_shift; |
| 562 | | |
| 563 | | if ((fd800.sector == 0) || (fd800.sector > 26)) |
| 564 | | { |
| 565 | | fd800.stat_reg |= status_invalid_cmd; |
| 566 | | } |
| 567 | | else |
| 568 | | { |
| 569 | | fd800.unit = unit; |
| 570 | | fd800.head = head; |
| 571 | | fd800.sector = sector; |
| 572 | | fd800.ddam = 1; |
| 573 | | |
| 574 | | fd800.buf_pos = 0; |
| 575 | | fd800.buf_mode = bm_write; |
| 576 | | fd800.stat_reg |= status_XFER_ready; |
| 577 | | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 578 | | } |
| 579 | | |
| 580 | | fd800.stat_reg |= status_interrupt; |
| 581 | | fd800_field_interrupt(); |
| 582 | | break; |
| 583 | | |
| 584 | | case 9: /* format track |
| 585 | | bits 16-23: track ID (0-255, normally current cylinder index, or 255 for bad track) |
| 586 | | bit 24: verify only (1 - verify, 0 - format & verify) |
| 587 | | bit 25: head number (1=upper) |
| 588 | | bits 26-27: unit number (0-3) */ |
| 589 | | /* ... */ |
| 590 | | break; |
| 591 | | |
| 592 | | case 10: /* load int mask |
| 593 | | bit 16: bad mask for interrupt (0 = unmask or enable interrupt) |
| 594 | | bits 17-27: 0s */ |
| 595 | | fd800.interrupt_f_f = fd800.cmd_reg & 1; |
| 596 | | fd800_field_interrupt(); |
| 597 | | break; |
| 598 | | |
| 599 | | case 11: /* stop |
| 600 | | bits 16-25: 0s |
| 601 | | bits 26-27: unit number (0-3) */ |
| 602 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 603 | | |
| 604 | | /* reset status */ |
| 605 | | fd800.stat_reg = unit << status_unit_shift; |
| 606 | | |
| 607 | | fd800.stat_reg |= status_OP_complete; |
| 608 | | |
| 609 | | fd800.stat_reg |= status_interrupt; |
| 610 | | fd800_field_interrupt(); |
| 611 | | break; |
| 612 | | |
| 613 | | case 12: /* step head |
| 614 | | bits 16-22: track number (0-76) |
| 615 | | bits 23-25: 0s |
| 616 | | bits 26-27: unit number (0-3) */ |
| 617 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 618 | | cylinder = fd800.cmd_reg & 0x7f; |
| 619 | | |
| 620 | | if (cylinder > 76) |
| 621 | | { |
| 622 | | fd800.stat_reg |= status_invalid_cmd; |
| 623 | | } |
| 624 | | else if ((fd800.drv[unit].phys_cylinder != -1) || (!fd800_do_restore(unit))) |
| 625 | | { |
| 626 | | floppy_drive_seek(&fd800.drv[unit].img->device(), cylinder-fd800.drv[unit].phys_cylinder); |
| 627 | | fd800.stat_reg |= status_OP_complete; |
| 628 | | } |
| 629 | | |
| 630 | | fd800.stat_reg |= status_interrupt; |
| 631 | | fd800_field_interrupt(); |
| 632 | | break; |
| 633 | | |
| 634 | | case 13: /* maintenance commands |
| 635 | | bits 16-23: according to extended command code |
| 636 | | bits 24-27: extended command code (0-7) */ |
| 637 | | switch ((fd800.cmd_reg >> 8) & 15) |
| 638 | | { |
| 639 | | case 0: /* reset |
| 640 | | bits 16-23: 0s */ |
| 641 | | /* ... */ |
| 642 | | break; |
| 643 | | case 1: /* retry inhibit |
| 644 | | bits 16-23: 0s */ |
| 645 | | /* ... */ |
| 646 | | break; |
| 647 | | case 2: /* LED test |
| 648 | | bit 16: 1 |
| 649 | | bits 17-19: 0s |
| 650 | | bit 20: LED #2 enable |
| 651 | | bit 21: LED #3 enable |
| 652 | | bit 22: LED #4 enable |
| 653 | | bit 23: enable LEDs */ |
| 654 | | /* ... */ |
| 655 | | break; |
| 656 | | case 3: /* program error (a.k.a. invalid command) |
| 657 | | bits 16-23: 0s */ |
| 658 | | /* ... */ |
| 659 | | break; |
| 660 | | case 4: /* memory read |
| 661 | | bits 16-20: controller memory address (shifted left by 8 to generate 9900 address) |
| 662 | | bits 21-23: 0s */ |
| 663 | | /* ... */ |
| 664 | | break; |
| 665 | | case 5: /* RAM load |
| 666 | | bit 16: 0 |
| 667 | | bits 17-23: RAM offset (shifted left by 1 and offset by >1800 to generate 9900 address) */ |
| 668 | | /* ... */ |
| 669 | | break; |
| 670 | | case 6: /* RAM run |
| 671 | | bit 16: 0 |
| 672 | | bits 17-23: RAM offset (shifted left by 1 and offset by >1800 to generate 9900 address) */ |
| 673 | | /* ... */ |
| 674 | | break; |
| 675 | | case 7: /* power up simulation |
| 676 | | bits 16-23: 0s */ |
| 677 | | /* ... */ |
| 678 | | break; |
| 679 | | } |
| 680 | | /* ... */ |
| 681 | | break; |
| 682 | | |
| 683 | | case 14: /* IPL |
| 684 | | bits 16-22: track number (0-76) |
| 685 | | bit 23: 0 |
| 686 | | bit 24: no sequential sectoring (1=active) |
| 687 | | bit 25: head number (1=upper) |
| 688 | | bits 26-27: unit number (0-3) */ |
| 689 | | unit = (fd800.cmd_reg >> 10) & 3; |
| 690 | | head = (fd800.cmd_reg >> 9) & 1; |
| 691 | | /*non_seq_mode = (fd800.cmd_reg >> 8) & 1;*/ |
| 692 | | cylinder = fd800.cmd_reg & 0x7f; |
| 693 | | |
| 694 | | if (!fd800_do_seek(unit, cylinder, head)) |
| 695 | | { |
| 696 | | fd800.unit = unit; |
| 697 | | fd800.head = head; |
| 698 | | fd800.sector = 1; |
| 699 | | /*fd800.non_seq_mode = non_seq_mode;*/ |
| 700 | | |
| 701 | | fd800_do_read(); |
| 702 | | } |
| 703 | | |
| 704 | | fd800.stat_reg |= status_interrupt; |
| 705 | | fd800_field_interrupt(); |
| 706 | | break; |
| 707 | | |
| 708 | | case 15: /* Clear Status port |
| 709 | | bits 16-27: 0s */ |
| 710 | | fd800.stat_reg = 0; |
| 711 | | fd800_field_interrupt(); |
| 712 | | break; |
| 713 | | } |
| 714 | | } |
| 715 | | |
| 716 | | /* |
| 717 | | read one CRU bit |
| 718 | | |
| 719 | | 0-15: receive buffer |
| 720 | | 16-31: status: |
| 721 | | 16: OP complete (1 -> complete???) |
| 722 | | 17: Xfer ready (XFER) (1 -> ready???) |
| 723 | | 18: drive not ready |
| 724 | | 19: data check error |
| 725 | | 20: seek error/?????? |
| 726 | | 21 invalid command/?????? |
| 727 | | 22: no address mark found/?????? |
| 728 | | 23: equipment check error/?????? |
| 729 | | 24: ID check error |
| 730 | | 25: ID not found |
| 731 | | 26: Controller busy (CTLBSY) (0 -> controller is ready) |
| 732 | | 27: write protect |
| 733 | | 28: deleted sector detected |
| 734 | | 29: unit LSB |
| 735 | | 30: unit MSB |
| 736 | | 31: Interrupt (CBUSY???) (1 -> controller is ready) |
| 737 | | */ |
| 738 | | READ8_HANDLER(fd800_cru_r) |
| 739 | | { |
| 740 | | int reply = 0; |
| 741 | | |
| 742 | | switch (offset) |
| 743 | | { |
| 744 | | case 0: |
| 745 | | case 1: |
| 746 | | /* receive buffer */ |
| 747 | | reply = fd800.recv_buf >> (offset*8); |
| 748 | | break; |
| 749 | | |
| 750 | | case 2: |
| 751 | | case 3: |
| 752 | | /* status register */ |
| 753 | | reply = fd800.stat_reg >> ((offset-2)*8); |
| 754 | | break; |
| 755 | | } |
| 756 | | |
| 757 | | return reply; |
| 758 | | } |
| 759 | | |
| 760 | | /* |
| 761 | | write one CRU bit |
| 762 | | |
| 763 | | 0-15: controller data word (transmit buffer) |
| 764 | | 16-31: controller command word (command register) |
| 765 | | 16-23: parameter value |
| 766 | | 24: flag bit/extended command code |
| 767 | | 25: head select/extended command code |
| 768 | | 26: FD unit number LSB/extended command code |
| 769 | | 27: FD unit number MSB/extended command code |
| 770 | | 28-31: command code |
| 771 | | */ |
| 772 | | WRITE8_HANDLER(fd800_cru_w) |
| 773 | | { |
| 774 | | switch (offset) |
| 775 | | { |
| 776 | | case 0: |
| 777 | | case 1: |
| 778 | | case 2: |
| 779 | | case 3: |
| 780 | | case 4: |
| 781 | | case 5: |
| 782 | | case 6: |
| 783 | | case 7: |
| 784 | | case 8: |
| 785 | | case 9: |
| 786 | | case 10: |
| 787 | | case 11: |
| 788 | | case 12: |
| 789 | | case 13: |
| 790 | | case 14: |
| 791 | | case 15: |
| 792 | | /* transmit buffer */ |
| 793 | | if (data) |
| 794 | | fd800.xmit_buf |= 1 << offset; |
| 795 | | else |
| 796 | | fd800.xmit_buf &= ~(1 << offset); |
| 797 | | if (offset == 15) |
| 798 | | { |
| 799 | | switch (fd800.buf_mode) |
| 800 | | { |
| 801 | | case bm_off: |
| 802 | | break; |
| 803 | | case bm_read: |
| 804 | | fd800.buf_pos++; |
| 805 | | if (fd800.buf_pos == fd800.drv[fd800.unit].seclen) |
| 806 | | { /* end of sector */ |
| 807 | | if (fd800.sector == 26) |
| 808 | | { /* end of track -> end command (right???) */ |
| 809 | | fd800.stat_reg &= ~status_XFER_ready; |
| 810 | | fd800.stat_reg |= status_OP_complete; |
| 811 | | fd800.stat_reg |= status_interrupt; |
| 812 | | fd800.buf_mode = bm_off; |
| 813 | | fd800_field_interrupt(); |
| 814 | | } |
| 815 | | else |
| 816 | | { /* read next sector */ |
| 817 | | fd800.sector++; |
| 818 | | fd800.stat_reg &= ~status_XFER_ready | status_OP_complete | status_interrupt; |
| 819 | | fd800_do_read(); |
| 820 | | fd800.stat_reg |= status_interrupt; |
| 821 | | fd800_field_interrupt(); |
| 822 | | } |
| 823 | | } |
| 824 | | else |
| 825 | | fd800.recv_buf = (fd800.buf[fd800.buf_pos<<1] << 8) | fd800.buf[(fd800.buf_pos<<1)+1]; |
| 826 | | break; |
| 827 | | |
| 828 | | case bm_write: |
| 829 | | fd800.buf[fd800.buf_pos<<1] = fd800.xmit_buf >> 8; |
| 830 | | fd800.buf[(fd800.buf_pos<<1)+1] = fd800.xmit_buf & 0xff; |
| 831 | | fd800.buf_pos++; |
| 832 | | if (fd800.buf_pos == fd800.drv[fd800.unit].seclen) |
| 833 | | { /* end of sector */ |
| 834 | | fd800_do_write(); |
| 835 | | if (fd800.sector == 26) |
| 836 | | { |
| 837 | | /* end of track -> end command (right???) */ |
| 838 | | fd800.stat_reg &= ~status_XFER_ready; |
| 839 | | fd800.stat_reg |= status_OP_complete; |
| 840 | | fd800.stat_reg |= status_interrupt; |
| 841 | | fd800.buf_mode = bm_off; |
| 842 | | fd800_field_interrupt(); |
| 843 | | } |
| 844 | | else |
| 845 | | { /* increment to next sector */ |
| 846 | | fd800.sector++; |
| 847 | | fd800.stat_reg |= status_interrupt; |
| 848 | | fd800_field_interrupt(); |
| 849 | | } |
| 850 | | } |
| 851 | | break; |
| 852 | | } |
| 853 | | } |
| 854 | | break; |
| 855 | | |
| 856 | | case 16: |
| 857 | | case 17: |
| 858 | | case 18: |
| 859 | | case 19: |
| 860 | | case 20: |
| 861 | | case 21: |
| 862 | | case 22: |
| 863 | | case 23: |
| 864 | | case 24: |
| 865 | | case 25: |
| 866 | | case 26: |
| 867 | | case 27: |
| 868 | | case 28: |
| 869 | | case 29: |
| 870 | | case 30: |
| 871 | | case 31: |
| 872 | | /* command register */ |
| 873 | | if (data) |
| 874 | | fd800.cmd_reg |= 1 << (offset-16); |
| 875 | | else |
| 876 | | fd800.cmd_reg &= ~(1 << (offset-16)); |
| 877 | | if (offset == 31) |
| 878 | | fd800_do_cmd(); |
| 879 | | break; |
| 880 | | } |
| 881 | | } |
trunk/src/mess/machine/ti99/990_hd.c
| r0 | r26091 | |
| 1 | /* |
| 2 | 990_hd.c: emulation of a generic ti990 hard disk controller, for use with |
| 3 | TILINE-based TI990 systems (TI990/10, /12, /12LR, /10A, Business system 300 |
| 4 | and 300A). |
| 5 | |
| 6 | This core will emulate the common feature set found in every disk controller. |
| 7 | Most controllers support additional features, but are still compatible with |
| 8 | the basic feature set. I have a little documentation on two specific |
| 9 | disk controllers (WD900 and WD800/WD800A), but I have not tried to emulate |
| 10 | controller-specific features. |
| 11 | |
| 12 | |
| 13 | Long description: see 2234398-9701 and 2306140-9701. |
| 14 | |
| 15 | |
| 16 | Raphael Nabet 2002-2003 |
| 17 | */ |
| 18 | |
| 19 | #include "emu.h" |
| 20 | |
| 21 | #include "990_hd.h" |
| 22 | |
| 23 | #include "harddisk.h" |
| 24 | #include "imagedev/harddriv.h" |
| 25 | |
| 26 | static void update_interrupt(running_machine &machine); |
| 27 | |
| 28 | /* max disk units per controller: 4 is the protocol limit, but it may be |
| 29 | overriden if more than one controller is used */ |
| 30 | #define MAX_DISK_UNIT 4 |
| 31 | |
| 32 | /* Max sector length is bytes. Generally 256, except for a few older disk |
| 33 | units which use 288-byte-long sectors, and SCSI units which generally use |
| 34 | standard 512-byte-long sectors. */ |
| 35 | /* I chose a limit of 512. No need to use more until someone writes CD-ROMs |
| 36 | for TI990. */ |
| 37 | #define MAX_SECTOR_SIZE 512 |
| 38 | |
| 39 | /* Description of custom format */ |
| 40 | /* We can use MAME's harddisk.c image format instead. */ |
| 41 | |
| 42 | /* machine-independent big-endian 32-bit integer */ |
| 43 | struct UINT32BE |
| 44 | { |
| 45 | UINT8 bytes[4]; |
| 46 | }; |
| 47 | |
| 48 | INLINE UINT32 get_UINT32BE(UINT32BE word) |
| 49 | { |
| 50 | return (word.bytes[0] << 24) | (word.bytes[1] << 16) | (word.bytes[2] << 8) | word.bytes[3]; |
| 51 | } |
| 52 | |
| 53 | #ifdef UNUSED_FUNCTION |
| 54 | INLINE void set_UINT32BE(UINT32BE *word, UINT32 data) |
| 55 | { |
| 56 | word->bytes[0] = (data >> 24) & 0xff; |
| 57 | word->bytes[1] = (data >> 16) & 0xff; |
| 58 | word->bytes[2] = (data >> 8) & 0xff; |
| 59 | word->bytes[3] = data & 0xff; |
| 60 | } |
| 61 | #endif |
| 62 | |
| 63 | /* disk image header */ |
| 64 | struct disk_image_header |
| 65 | { |
| 66 | UINT32BE cylinders; /* number of cylinders on hard disk (big-endian) */ |
| 67 | UINT32BE heads; /* number of heads on hard disk (big-endian) */ |
| 68 | UINT32BE sectors_per_track; /* number of sectors per track on hard disk (big-endian) */ |
| 69 | UINT32BE bytes_per_sector; /* number of bytes of data per sector on hard disk (big-endian) */ |
| 70 | }; |
| 71 | |
| 72 | enum |
| 73 | { |
| 74 | header_len = sizeof(disk_image_header) |
| 75 | }; |
| 76 | |
| 77 | enum format_t |
| 78 | { |
| 79 | format_mame, |
| 80 | format_old |
| 81 | }; |
| 82 | |
| 83 | /* disk drive unit descriptor */ |
| 84 | struct hd_unit_t |
| 85 | { |
| 86 | device_image_interface *img; /* image descriptor */ |
| 87 | format_t format; |
| 88 | hard_disk_file *hd_handle; /* mame hard disk descriptor - only if format == format_mame */ |
| 89 | unsigned int wp : 1; /* TRUE if disk is write-protected */ |
| 90 | unsigned int unsafe : 1; /* TRUE when a disk has just been connected */ |
| 91 | |
| 92 | /* disk geometry */ |
| 93 | unsigned int cylinders, heads, sectors_per_track, bytes_per_sector; |
| 94 | }; |
| 95 | |
| 96 | /* disk controller */ |
| 97 | struct hdc_t |
| 98 | { |
| 99 | UINT16 w[8]; |
| 100 | |
| 101 | void (*interrupt_callback)(running_machine &machine, int state); |
| 102 | |
| 103 | hd_unit_t d[MAX_DISK_UNIT]; |
| 104 | }; |
| 105 | |
| 106 | /* masks for individual bits controller registers */ |
| 107 | enum |
| 108 | { |
| 109 | w0_offline = 0x8000, |
| 110 | w0_not_ready = 0x4000, |
| 111 | w0_write_protect = 0x2000, |
| 112 | w0_unsafe = 0x1000, |
| 113 | w0_end_of_cylinder = 0x0800, |
| 114 | w0_seek_incomplete = 0x0400, |
| 115 | /*w0_offset_active = 0x0200,*/ |
| 116 | w0_pack_change = 0x0100, |
| 117 | |
| 118 | w0_attn_lines = 0x00f0, |
| 119 | w0_attn_mask = 0x000f, |
| 120 | |
| 121 | w1_extended_command = 0xc000, |
| 122 | /*w1_strobe_early = 0x2000, |
| 123 | w1_strobe_late = 0x1000,*/ |
| 124 | w1_transfer_inhibit = 0x0800, |
| 125 | w1_command = 0x0700, |
| 126 | w1_offset = 0x0080, |
| 127 | w1_offset_forward = 0x0040, |
| 128 | w1_head_address = 0x003f, |
| 129 | |
| 130 | w6_unit0_sel = 0x0800, |
| 131 | w6_unit1_sel = 0x0400, |
| 132 | w6_unit2_sel = 0x0200, |
| 133 | w6_unit3_sel = 0x0100, |
| 134 | |
| 135 | w7_idle = 0x8000, |
| 136 | w7_complete = 0x4000, |
| 137 | w7_error = 0x2000, |
| 138 | w7_int_enable = 0x1000, |
| 139 | /*w7_lock_out = 0x0800,*/ |
| 140 | w7_retry = 0x0400, |
| 141 | w7_ecc = 0x0200, |
| 142 | w7_abnormal_completion = 0x0100, |
| 143 | w7_memory_error = 0x0080, |
| 144 | w7_data_error = 0x0040, |
| 145 | w7_tiline_timeout_err = 0x0020, |
| 146 | w7_header_err = 0x0010, |
| 147 | w7_rate_err = 0x0008, |
| 148 | w7_command_time_out_err = 0x0004, |
| 149 | w7_search_err = 0x0002, |
| 150 | w7_unit_err = 0x0001 |
| 151 | }; |
| 152 | |
| 153 | /* masks for computer-controlled bit in each controller register */ |
| 154 | static const UINT16 w_mask[8] = |
| 155 | { |
| 156 | 0x000f, /* Controllers should prevent overwriting of w0 status bits, and I know |
| 157 | that some controllers do so. */ |
| 158 | 0xffff, |
| 159 | 0xffff, |
| 160 | 0xffff, |
| 161 | 0xffff, |
| 162 | 0xffff, |
| 163 | 0xffff, |
| 164 | 0xf7ff /* Don't overwrite reserved bits */ |
| 165 | }; |
| 166 | |
| 167 | static hdc_t hdc; |
| 168 | |
| 169 | |
| 170 | static int get_id_from_device( device_t *device ) |
| 171 | { |
| 172 | int id = -1; |
| 173 | |
| 174 | if ( ! strcmp( ":harddisk1", device->tag() ) ) |
| 175 | { |
| 176 | id = 0; |
| 177 | } |
| 178 | else if ( ! strcmp( ":harddisk2", device->tag() ) ) |
| 179 | { |
| 180 | id = 1; |
| 181 | } |
| 182 | else if ( ! strcmp( ":harddisk3", device->tag() ) ) |
| 183 | { |
| 184 | id = 2; |
| 185 | } |
| 186 | else if ( ! strcmp( ":harddisk4", device->tag() ) ) |
| 187 | { |
| 188 | id = 3; |
| 189 | } |
| 190 | assert( id >= 0 ); |
| 191 | |
| 192 | return id; |
| 193 | } |
| 194 | |
| 195 | |
| 196 | /* |
| 197 | Initialize hard disk unit and open a hard disk image |
| 198 | */ |
| 199 | static DEVICE_IMAGE_LOAD_LEGACY( ti990_hd ) |
| 200 | { |
| 201 | int id = get_id_from_device( &image.device() ); |
| 202 | hd_unit_t *d; |
| 203 | hard_disk_file *hd_file; |
| 204 | |
| 205 | d = &hdc.d[id]; |
| 206 | d->img = ℑ |
| 207 | |
| 208 | hd_file = dynamic_cast<harddisk_image_device *>(&image)->get_hard_disk_file(); |
| 209 | |
| 210 | if ( hd_file ) |
| 211 | { |
| 212 | const hard_disk_info *standard_header; |
| 213 | |
| 214 | d->format = format_mame; |
| 215 | d->hd_handle = hd_file; |
| 216 | |
| 217 | /* use standard hard disk image header. */ |
| 218 | standard_header = hard_disk_get_info(d->hd_handle); |
| 219 | |
| 220 | d->cylinders = standard_header->cylinders; |
| 221 | d->heads = standard_header->heads; |
| 222 | d->sectors_per_track = standard_header->sectors; |
| 223 | d->bytes_per_sector = standard_header->sectorbytes; |
| 224 | } |
| 225 | else |
| 226 | { |
| 227 | /* older, custom format */ |
| 228 | disk_image_header custom_header; |
| 229 | int bytes_read; |
| 230 | |
| 231 | /* set file descriptor */ |
| 232 | d->format = format_old; |
| 233 | d->hd_handle = NULL; |
| 234 | |
| 235 | /* use custom image header. */ |
| 236 | /* to convert old header-less images to this format, insert a 16-byte |
| 237 | header as follow: 00 00 03 8f 00 00 00 05 00 00 00 21 00 00 01 00 */ |
| 238 | d->img->fseek(0, SEEK_SET); |
| 239 | bytes_read = d->img->fread(&custom_header, sizeof(custom_header)); |
| 240 | if (bytes_read != sizeof(custom_header)) |
| 241 | { |
| 242 | d->format = format_mame; /* don't care */ |
| 243 | d->wp = 1; |
| 244 | d->unsafe = 1; |
| 245 | return IMAGE_INIT_FAIL; |
| 246 | } |
| 247 | |
| 248 | d->cylinders = get_UINT32BE(custom_header.cylinders); |
| 249 | d->heads = get_UINT32BE(custom_header.heads); |
| 250 | d->sectors_per_track = get_UINT32BE(custom_header.sectors_per_track); |
| 251 | d->bytes_per_sector = get_UINT32BE(custom_header.bytes_per_sector); |
| 252 | } |
| 253 | |
| 254 | if (d->bytes_per_sector > MAX_SECTOR_SIZE) |
| 255 | { |
| 256 | d->format = format_mame; |
| 257 | d->hd_handle = NULL; |
| 258 | d->wp = 1; |
| 259 | d->unsafe = 1; |
| 260 | return IMAGE_INIT_FAIL; |
| 261 | } |
| 262 | |
| 263 | /* tell whether the image is writable */ |
| 264 | d->wp = image.is_readonly(); |
| 265 | |
| 266 | d->unsafe = 1; |
| 267 | /* set attention line */ |
| 268 | hdc.w[0] |= (0x80 >> id); |
| 269 | |
| 270 | return IMAGE_INIT_PASS; |
| 271 | } |
| 272 | |
| 273 | /* |
| 274 | close a hard disk image |
| 275 | */ |
| 276 | static DEVICE_IMAGE_UNLOAD_LEGACY( ti990_hd ) |
| 277 | { |
| 278 | int id = get_id_from_device( image ); |
| 279 | hd_unit_t *d; |
| 280 | |
| 281 | d = &hdc.d[id]; |
| 282 | |
| 283 | d->format = format_mame; /* don't care */ |
| 284 | d->hd_handle = NULL; |
| 285 | d->wp = 1; |
| 286 | d->unsafe = 1; |
| 287 | |
| 288 | /* clear attention line */ |
| 289 | hdc.w[0] &= ~ (0x80 >> id); |
| 290 | } |
| 291 | |
| 292 | /* |
| 293 | Return true if a HD image has been loaded |
| 294 | */ |
| 295 | INLINE int is_unit_loaded(int unit) |
| 296 | { |
| 297 | int reply = 0; |
| 298 | |
| 299 | switch (hdc.d[unit].format) |
| 300 | { |
| 301 | case format_mame: |
| 302 | reply = (hdc.d[unit].hd_handle != NULL); |
| 303 | break; |
| 304 | |
| 305 | case format_old: |
| 306 | reply = (hdc.d[unit].img->exists() ? 1 : 0); |
| 307 | break; |
| 308 | } |
| 309 | |
| 310 | return reply; |
| 311 | } |
| 312 | |
| 313 | /* |
| 314 | Init the hdc core |
| 315 | */ |
| 316 | MACHINE_START(ti990_hdc) |
| 317 | { |
| 318 | int i; |
| 319 | |
| 320 | /* initialize harddisk information */ |
| 321 | /* attention lines will be set by DEVICE_IMAGE_LOD */ |
| 322 | for (i=0; i<MAX_DISK_UNIT; i++) |
| 323 | { |
| 324 | hdc.d[i].format = format_mame; |
| 325 | hdc.d[i].hd_handle = NULL; |
| 326 | hdc.d[i].wp = 1; |
| 327 | hdc.d[i].unsafe = 1; |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | |
| 332 | void ti990_hdc_init(running_machine &machine, void (*interrupt_callback)(running_machine &machine, int state)) |
| 333 | { |
| 334 | memset(hdc.w, 0, sizeof(hdc.w)); |
| 335 | hdc.w[7] = w7_idle; |
| 336 | |
| 337 | /* get references to harddisk devices */ |
| 338 | hdc.d[0].img = dynamic_cast<device_image_interface *>(machine.device("harddisk1")); |
| 339 | hdc.d[1].img = dynamic_cast<device_image_interface *>(machine.device("harddisk2")); |
| 340 | hdc.d[2].img = dynamic_cast<device_image_interface *>(machine.device("harddisk3")); |
| 341 | hdc.d[3].img = dynamic_cast<device_image_interface *>(machine.device("harddisk4")); |
| 342 | |
| 343 | hdc.interrupt_callback = interrupt_callback; |
| 344 | |
| 345 | update_interrupt(machine); |
| 346 | } |
| 347 | |
| 348 | |
| 349 | /* |
| 350 | Parse the disk select lines, and return the corresponding tape unit. |
| 351 | (-1 if none) |
| 352 | */ |
| 353 | static int cur_disk_unit(void) |
| 354 | { |
| 355 | int reply; |
| 356 | |
| 357 | |
| 358 | if (hdc.w[6] & w6_unit0_sel) |
| 359 | reply = 0; |
| 360 | else if (hdc.w[6] & w6_unit1_sel) |
| 361 | reply = 1; |
| 362 | else if (hdc.w[6] & w6_unit2_sel) |
| 363 | reply = 2; |
| 364 | else if (hdc.w[6] & w6_unit3_sel) |
| 365 | reply = 3; |
| 366 | else |
| 367 | reply = -1; |
| 368 | |
| 369 | if (reply >= MAX_DISK_UNIT) |
| 370 | reply = -1; |
| 371 | |
| 372 | return reply; |
| 373 | } |
| 374 | |
| 375 | /* |
| 376 | Update interrupt state |
| 377 | */ |
| 378 | static void update_interrupt(running_machine &machine) |
| 379 | { |
| 380 | if (hdc.interrupt_callback) |
| 381 | (*hdc.interrupt_callback)(machine, (hdc.w[7] & w7_idle) |
| 382 | && (((hdc.w[7] & w7_int_enable) && (hdc.w[7] & (w7_complete | w7_error))) |
| 383 | || ((hdc.w[0] & (hdc.w[0] >> 4)) & w0_attn_mask))); |
| 384 | } |
| 385 | |
| 386 | /* |
| 387 | Check that a sector address is valid. |
| 388 | |
| 389 | Terminate current command and return non-zero if the address is invalid. |
| 390 | */ |
| 391 | static int check_sector_address(running_machine &machine, int unit, unsigned int cylinder, unsigned int head, unsigned int sector) |
| 392 | { |
| 393 | if ((cylinder > hdc.d[unit].cylinders) || (head > hdc.d[unit].heads) || (sector > hdc.d[unit].sectors_per_track)) |
| 394 | { /* invalid address */ |
| 395 | if (cylinder > hdc.d[unit].cylinders) |
| 396 | { |
| 397 | hdc.w[0] |= w0_seek_incomplete; |
| 398 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 399 | } |
| 400 | else if (head > hdc.d[unit].heads) |
| 401 | { |
| 402 | hdc.w[0] |= w0_end_of_cylinder; |
| 403 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 404 | } |
| 405 | else if (sector > hdc.d[unit].sectors_per_track) |
| 406 | hdc.w[7] |= w7_idle | w7_error | w7_command_time_out_err; |
| 407 | update_interrupt(machine); |
| 408 | return 1; |
| 409 | } |
| 410 | |
| 411 | return 0; |
| 412 | } |
| 413 | |
| 414 | /* |
| 415 | Seek to sector whose address is given |
| 416 | */ |
| 417 | static int sector_to_lba(running_machine &machine, int unit, unsigned int cylinder, unsigned int head, unsigned int sector, unsigned int *lba) |
| 418 | { |
| 419 | if (check_sector_address(machine, unit, cylinder, head, sector)) |
| 420 | return 1; |
| 421 | |
| 422 | * lba = (cylinder*hdc.d[unit].heads + head)*hdc.d[unit].sectors_per_track + sector; |
| 423 | |
| 424 | return 0; |
| 425 | } |
| 426 | |
| 427 | /* |
| 428 | Read one given sector |
| 429 | */ |
| 430 | static int read_sector(int unit, unsigned int lba, void *buffer, unsigned int bytes_to_read) |
| 431 | { |
| 432 | unsigned long byte_position; |
| 433 | unsigned int bytes_read; |
| 434 | |
| 435 | switch (hdc.d[unit].format) |
| 436 | { |
| 437 | case format_mame: |
| 438 | bytes_read = hdc.d[unit].bytes_per_sector * hard_disk_read(hdc.d[unit].hd_handle, lba, buffer); |
| 439 | if (bytes_read > bytes_to_read) |
| 440 | bytes_read = bytes_to_read; |
| 441 | break; |
| 442 | |
| 443 | case format_old: |
| 444 | byte_position = lba*hdc.d[unit].bytes_per_sector + header_len; |
| 445 | hdc.d[unit].img->fseek(byte_position, SEEK_SET); |
| 446 | bytes_read = hdc.d[unit].img->fread(buffer, bytes_to_read); |
| 447 | break; |
| 448 | |
| 449 | default: |
| 450 | bytes_read = 0; |
| 451 | break; |
| 452 | } |
| 453 | |
| 454 | return bytes_read; |
| 455 | } |
| 456 | |
| 457 | /* |
| 458 | Write one given sector |
| 459 | */ |
| 460 | static int write_sector(int unit, unsigned int lba, const void *buffer, unsigned int bytes_to_write) |
| 461 | { |
| 462 | unsigned long byte_position; |
| 463 | unsigned int bytes_written; |
| 464 | |
| 465 | switch (hdc.d[unit].format) |
| 466 | { |
| 467 | case format_mame: |
| 468 | bytes_written = hdc.d[unit].bytes_per_sector * hard_disk_write(hdc.d[unit].hd_handle, lba, buffer); |
| 469 | if (bytes_written > bytes_to_write) |
| 470 | bytes_written = bytes_to_write; |
| 471 | break; |
| 472 | |
| 473 | case format_old: |
| 474 | byte_position = lba*hdc.d[unit].bytes_per_sector + header_len; |
| 475 | hdc.d[unit].img->fseek(byte_position, SEEK_SET); |
| 476 | bytes_written = hdc.d[unit].img->fwrite(buffer, bytes_to_write); |
| 477 | break; |
| 478 | |
| 479 | default: |
| 480 | bytes_written = 0; |
| 481 | break; |
| 482 | } |
| 483 | |
| 484 | return bytes_written; |
| 485 | } |
| 486 | |
| 487 | /* |
| 488 | Handle the store registers command: read the drive geometry. |
| 489 | */ |
| 490 | static void store_registers(running_machine &machine) |
| 491 | { |
| 492 | int dma_address; |
| 493 | int byte_count; |
| 494 | |
| 495 | UINT16 buffer[3]; |
| 496 | int i, real_word_count; |
| 497 | |
| 498 | int dsk_sel = cur_disk_unit(); |
| 499 | |
| 500 | |
| 501 | if (dsk_sel == -1) |
| 502 | { |
| 503 | /* No idea what to report... */ |
| 504 | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 505 | update_interrupt(machine); |
| 506 | return; |
| 507 | } |
| 508 | else if (! is_unit_loaded(dsk_sel)) |
| 509 | { /* offline */ |
| 510 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 511 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 512 | update_interrupt(machine); |
| 513 | return; |
| 514 | } |
| 515 | |
| 516 | hdc.d[dsk_sel].unsafe = 0; /* I think */ |
| 517 | |
| 518 | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 519 | byte_count = hdc.w[4] & 0xfffe; |
| 520 | |
| 521 | /* formatted words per track */ |
| 522 | buffer[0] = (hdc.d[dsk_sel].sectors_per_track*hdc.d[dsk_sel].bytes_per_sector) >> 1; |
| 523 | /* MSByte: sectors per track; LSByte: bytes of overhead per sector */ |
| 524 | buffer[1] = (hdc.d[dsk_sel].sectors_per_track << 8) | 0; |
| 525 | /* bits 0-4: heads; bits 5-15: cylinders */ |
| 526 | buffer[2] = (hdc.d[dsk_sel].heads << 11) | hdc.d[dsk_sel].cylinders; |
| 527 | |
| 528 | real_word_count = byte_count >> 1; |
| 529 | if (real_word_count > 3) |
| 530 | real_word_count = 3; |
| 531 | |
| 532 | /* DMA */ |
| 533 | if (! (hdc.w[1] & w1_transfer_inhibit)) |
| 534 | for (i=0; i<real_word_count; i++) |
| 535 | { |
| 536 | machine.device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, buffer[i]); |
| 537 | dma_address = (dma_address + 2) & 0x1ffffe; |
| 538 | } |
| 539 | |
| 540 | hdc.w[7] |= w7_idle | w7_complete; |
| 541 | update_interrupt(machine); |
| 542 | } |
| 543 | |
| 544 | /* |
| 545 | Handle the write format command: format a complete track on disk. |
| 546 | |
| 547 | The emulation just clears the track data in the disk image. |
| 548 | */ |
| 549 | static void write_format(running_machine &machine) |
| 550 | { |
| 551 | unsigned int cylinder, head, sector; |
| 552 | unsigned int lba; |
| 553 | |
| 554 | UINT8 buffer[MAX_SECTOR_SIZE]; |
| 555 | int bytes_written; |
| 556 | |
| 557 | int dsk_sel = cur_disk_unit(); |
| 558 | |
| 559 | |
| 560 | if (dsk_sel == -1) |
| 561 | { |
| 562 | /* No idea what to report... */ |
| 563 | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 564 | update_interrupt(machine); |
| 565 | return; |
| 566 | } |
| 567 | else if (! is_unit_loaded(dsk_sel)) |
| 568 | { /* offline */ |
| 569 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 570 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 571 | update_interrupt(machine); |
| 572 | return; |
| 573 | } |
| 574 | else if (hdc.d[dsk_sel].unsafe) |
| 575 | { /* disk in unsafe condition */ |
| 576 | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 577 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 578 | update_interrupt(machine); |
| 579 | return; |
| 580 | } |
| 581 | else if (hdc.d[dsk_sel].wp) |
| 582 | { /* disk write-protected */ |
| 583 | hdc.w[0] |= w0_write_protect; |
| 584 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 585 | update_interrupt(machine); |
| 586 | return; |
| 587 | } |
| 588 | |
| 589 | cylinder = hdc.w[3]; |
| 590 | head = hdc.w[1] & w1_head_address; |
| 591 | |
| 592 | if (sector_to_lba(machine, dsk_sel, cylinder, head, 0, &lba)) |
| 593 | return; |
| 594 | |
| 595 | memset(buffer, 0, hdc.d[dsk_sel].bytes_per_sector); |
| 596 | |
| 597 | for (sector=0; sector<hdc.d[dsk_sel].sectors_per_track; sector++) |
| 598 | { |
| 599 | bytes_written = write_sector(dsk_sel, lba, buffer, hdc.d[dsk_sel].bytes_per_sector); |
| 600 | |
| 601 | if (bytes_written != hdc.d[dsk_sel].bytes_per_sector) |
| 602 | { |
| 603 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 604 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 605 | update_interrupt(machine); |
| 606 | return; |
| 607 | } |
| 608 | |
| 609 | lba++; |
| 610 | } |
| 611 | |
| 612 | hdc.w[7] |= w7_idle | w7_complete; |
| 613 | update_interrupt(machine); |
| 614 | } |
| 615 | |
| 616 | /* |
| 617 | Handle the read data command: read a variable number of sectors from disk. |
| 618 | */ |
| 619 | static void read_data(running_machine &machine) |
| 620 | { |
| 621 | int dma_address; |
| 622 | int byte_count; |
| 623 | |
| 624 | unsigned int cylinder, head, sector; |
| 625 | unsigned int lba; |
| 626 | |
| 627 | UINT8 buffer[MAX_SECTOR_SIZE]; |
| 628 | int bytes_to_read; |
| 629 | int bytes_read; |
| 630 | int i; |
| 631 | |
| 632 | int dsk_sel = cur_disk_unit(); |
| 633 | |
| 634 | |
| 635 | if (dsk_sel == -1) |
| 636 | { |
| 637 | /* No idea what to report... */ |
| 638 | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 639 | update_interrupt(machine); |
| 640 | return; |
| 641 | } |
| 642 | else if (! is_unit_loaded(dsk_sel)) |
| 643 | { /* offline */ |
| 644 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 645 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 646 | update_interrupt(machine); |
| 647 | return; |
| 648 | } |
| 649 | else if (hdc.d[dsk_sel].unsafe) |
| 650 | { /* disk in unsafe condition */ |
| 651 | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 652 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 653 | update_interrupt(machine); |
| 654 | return; |
| 655 | } |
| 656 | |
| 657 | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 658 | byte_count = hdc.w[4] & 0xfffe; |
| 659 | |
| 660 | cylinder = hdc.w[3]; |
| 661 | head = hdc.w[1] & w1_head_address; |
| 662 | sector = hdc.w[2] & 0xff; |
| 663 | |
| 664 | if (sector_to_lba(machine, dsk_sel, cylinder, head, sector, &lba)) |
| 665 | return; |
| 666 | |
| 667 | while (byte_count) |
| 668 | { /* read data sector per sector */ |
| 669 | if (cylinder > hdc.d[dsk_sel].cylinders) |
| 670 | { |
| 671 | hdc.w[0] |= w0_seek_incomplete; |
| 672 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 673 | update_interrupt(machine); |
| 674 | return; |
| 675 | } |
| 676 | |
| 677 | bytes_to_read = (byte_count < hdc.d[dsk_sel].bytes_per_sector) ? byte_count : hdc.d[dsk_sel].bytes_per_sector; |
| 678 | bytes_read = read_sector(dsk_sel, lba, buffer, bytes_to_read); |
| 679 | |
| 680 | if (bytes_read != bytes_to_read) |
| 681 | { /* behave as if the controller could not found the sector ID mark */ |
| 682 | hdc.w[7] |= w7_idle | w7_error | w7_command_time_out_err; |
| 683 | update_interrupt(machine); |
| 684 | return; |
| 685 | } |
| 686 | |
| 687 | /* DMA */ |
| 688 | if (! (hdc.w[1] & w1_transfer_inhibit)) |
| 689 | for (i=0; i<bytes_read; i+=2) |
| 690 | { |
| 691 | machine.device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, (((int) buffer[i]) << 8) | buffer[i+1]); |
| 692 | dma_address = (dma_address + 2) & 0x1ffffe; |
| 693 | } |
| 694 | |
| 695 | byte_count -= bytes_read; |
| 696 | |
| 697 | /* update sector address to point to next sector */ |
| 698 | lba++; |
| 699 | sector++; |
| 700 | if (sector == hdc.d[dsk_sel].sectors_per_track) |
| 701 | { |
| 702 | sector = 0; |
| 703 | head++; |
| 704 | if (head == hdc.d[dsk_sel].heads) |
| 705 | { |
| 706 | head = 0; |
| 707 | cylinder++; |
| 708 | } |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | hdc.w[7] |= w7_idle | w7_complete; |
| 713 | update_interrupt(machine); |
| 714 | } |
| 715 | |
| 716 | /* |
| 717 | Handle the write data command: write a variable number of sectors from disk. |
| 718 | */ |
| 719 | static void write_data(running_machine &machine) |
| 720 | { |
| 721 | int dma_address; |
| 722 | int byte_count; |
| 723 | |
| 724 | unsigned int cylinder, head, sector; |
| 725 | unsigned int lba; |
| 726 | |
| 727 | UINT8 buffer[MAX_SECTOR_SIZE]; |
| 728 | UINT16 word; |
| 729 | int bytes_written; |
| 730 | int i; |
| 731 | |
| 732 | int dsk_sel = cur_disk_unit(); |
| 733 | |
| 734 | |
| 735 | if (dsk_sel == -1) |
| 736 | { |
| 737 | /* No idea what to report... */ |
| 738 | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 739 | update_interrupt(machine); |
| 740 | return; |
| 741 | } |
| 742 | else if (! is_unit_loaded(dsk_sel)) |
| 743 | { /* offline */ |
| 744 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 745 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 746 | update_interrupt(machine); |
| 747 | return; |
| 748 | } |
| 749 | else if (hdc.d[dsk_sel].unsafe) |
| 750 | { /* disk in unsafe condition */ |
| 751 | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 752 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 753 | update_interrupt(machine); |
| 754 | return; |
| 755 | } |
| 756 | else if (hdc.d[dsk_sel].wp) |
| 757 | { /* disk write-protected */ |
| 758 | hdc.w[0] |= w0_write_protect; |
| 759 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 760 | update_interrupt(machine); |
| 761 | return; |
| 762 | } |
| 763 | |
| 764 | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 765 | byte_count = hdc.w[4] & 0xfffe; |
| 766 | |
| 767 | cylinder = hdc.w[3]; |
| 768 | head = hdc.w[1] & w1_head_address; |
| 769 | sector = hdc.w[2] & 0xff; |
| 770 | |
| 771 | if (sector_to_lba(machine, dsk_sel, cylinder, head, sector, &lba)) |
| 772 | return; |
| 773 | |
| 774 | while (byte_count > 0) |
| 775 | { /* write data sector per sector */ |
| 776 | if (cylinder > hdc.d[dsk_sel].cylinders) |
| 777 | { |
| 778 | hdc.w[0] |= w0_seek_incomplete; |
| 779 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 780 | update_interrupt(machine); |
| 781 | return; |
| 782 | } |
| 783 | |
| 784 | /* DMA */ |
| 785 | for (i=0; (i<byte_count) && (i<hdc.d[dsk_sel].bytes_per_sector); i+=2) |
| 786 | { |
| 787 | word = machine.device("maincpu")->memory().space(AS_PROGRAM).read_word(dma_address); |
| 788 | buffer[i] = word >> 8; |
| 789 | buffer[i+1] = word & 0xff; |
| 790 | |
| 791 | dma_address = (dma_address + 2) & 0x1ffffe; |
| 792 | } |
| 793 | /* fill with 0s if we did not reach sector end */ |
| 794 | for (; i<hdc.d[dsk_sel].bytes_per_sector; i+=2) |
| 795 | buffer[i] = buffer[i+1] = 0; |
| 796 | |
| 797 | bytes_written = write_sector(dsk_sel, lba, buffer, hdc.d[dsk_sel].bytes_per_sector); |
| 798 | |
| 799 | if (bytes_written != hdc.d[dsk_sel].bytes_per_sector) |
| 800 | { |
| 801 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 802 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 803 | update_interrupt(machine); |
| 804 | return; |
| 805 | } |
| 806 | |
| 807 | byte_count -= bytes_written; |
| 808 | |
| 809 | /* update sector address to point to next sector */ |
| 810 | lba++; |
| 811 | sector++; |
| 812 | if (sector == hdc.d[dsk_sel].sectors_per_track) |
| 813 | { |
| 814 | sector = 0; |
| 815 | head++; |
| 816 | if (head == hdc.d[dsk_sel].heads) |
| 817 | { |
| 818 | head = 0; |
| 819 | cylinder++; |
| 820 | } |
| 821 | } |
| 822 | } |
| 823 | |
| 824 | hdc.w[7] |= w7_idle | w7_complete; |
| 825 | update_interrupt(machine); |
| 826 | } |
| 827 | |
| 828 | /* |
| 829 | Handle the unformatted read command: read drive geometry information. |
| 830 | */ |
| 831 | static void unformatted_read(running_machine &machine) |
| 832 | { |
| 833 | int dma_address; |
| 834 | int byte_count; |
| 835 | |
| 836 | unsigned int cylinder, head, sector; |
| 837 | |
| 838 | UINT16 buffer[3]; |
| 839 | int i, real_word_count; |
| 840 | |
| 841 | int dsk_sel = cur_disk_unit(); |
| 842 | |
| 843 | |
| 844 | if (dsk_sel == -1) |
| 845 | { |
| 846 | /* No idea what to report... */ |
| 847 | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 848 | update_interrupt(machine); |
| 849 | return; |
| 850 | } |
| 851 | else if (! is_unit_loaded(dsk_sel)) |
| 852 | { /* offline */ |
| 853 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 854 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 855 | update_interrupt(machine); |
| 856 | return; |
| 857 | } |
| 858 | else if (hdc.d[dsk_sel].unsafe) |
| 859 | { /* disk in unsafe condition */ |
| 860 | hdc.w[0] |= w0_unsafe | w0_pack_change; |
| 861 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 862 | update_interrupt(machine); |
| 863 | return; |
| 864 | } |
| 865 | |
| 866 | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 867 | byte_count = hdc.w[4] & 0xfffe; |
| 868 | |
| 869 | cylinder = hdc.w[3]; |
| 870 | head = hdc.w[1] & w1_head_address; |
| 871 | sector = hdc.w[2] & 0xff; |
| 872 | |
| 873 | if (check_sector_address(machine, dsk_sel, cylinder, head, sector)) |
| 874 | return; |
| 875 | |
| 876 | dma_address = ((((int) hdc.w[6]) << 16) | hdc.w[5]) & 0x1ffffe; |
| 877 | byte_count = hdc.w[4] & 0xfffe; |
| 878 | |
| 879 | /* bits 0-4: head address; bits 5-15: cylinder address */ |
| 880 | buffer[0] = (head << 11) | cylinder; |
| 881 | /* MSByte: sectors per record (1); LSByte: sector address */ |
| 882 | buffer[1] = (1 << 8) | sector; |
| 883 | /* formatted words per record */ |
| 884 | buffer[2] = hdc.d[dsk_sel].bytes_per_sector >> 1; |
| 885 | |
| 886 | real_word_count = byte_count >> 1; |
| 887 | if (real_word_count > 3) |
| 888 | real_word_count = 3; |
| 889 | |
| 890 | /* DMA */ |
| 891 | if (! (hdc.w[1] & w1_transfer_inhibit)) |
| 892 | for (i=0; i<real_word_count; i++) |
| 893 | { |
| 894 | machine.device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, buffer[i]); |
| 895 | dma_address = (dma_address + 2) & 0x1ffffe; |
| 896 | } |
| 897 | |
| 898 | hdc.w[7] |= w7_idle | w7_complete; |
| 899 | update_interrupt(machine); |
| 900 | } |
| 901 | |
| 902 | /* |
| 903 | Handle the restore command: return to track 0. |
| 904 | */ |
| 905 | static void restore(running_machine &machine) |
| 906 | { |
| 907 | int dsk_sel = cur_disk_unit(); |
| 908 | |
| 909 | |
| 910 | if (dsk_sel == -1) |
| 911 | { |
| 912 | /* No idea what to report... */ |
| 913 | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 914 | update_interrupt(machine); |
| 915 | return; |
| 916 | } |
| 917 | else if (! is_unit_loaded(dsk_sel)) |
| 918 | { /* offline */ |
| 919 | hdc.w[0] |= w0_offline | w0_not_ready; |
| 920 | hdc.w[7] |= w7_idle | w7_error | w7_unit_err; |
| 921 | update_interrupt(machine); |
| 922 | return; |
| 923 | } |
| 924 | |
| 925 | hdc.d[dsk_sel].unsafe = 0; /* I think */ |
| 926 | |
| 927 | /*if (seek_to_sector(dsk_sel, 0, 0, 0)) |
| 928 | return;*/ |
| 929 | |
| 930 | hdc.w[7] |= w7_idle | w7_complete; |
| 931 | update_interrupt(machine); |
| 932 | } |
| 933 | |
| 934 | /* |
| 935 | Parse command code and execute the command. |
| 936 | */ |
| 937 | static void execute_command(running_machine &machine) |
| 938 | { |
| 939 | /* hack */ |
| 940 | hdc.w[0] &= 0xff; |
| 941 | |
| 942 | if (hdc.w[1] & w1_extended_command) |
| 943 | logerror("extended commands not supported\n"); |
| 944 | |
| 945 | switch (/*((hdc.w[1] & w1_extended_command) >> 11) |*/ ((hdc.w[1] & w1_command) >> 8)) |
| 946 | { |
| 947 | case 0x00: //0b000: |
| 948 | /* store registers */ |
| 949 | logerror("store registers\n"); |
| 950 | store_registers(machine); |
| 951 | break; |
| 952 | case 0x01: //0b001: |
| 953 | /* write format */ |
| 954 | logerror("write format\n"); |
| 955 | write_format(machine); |
| 956 | break; |
| 957 | case 0x02: //0b010: |
| 958 | /* read data */ |
| 959 | logerror("read data\n"); |
| 960 | read_data(machine); |
| 961 | break; |
| 962 | case 0x03: //0b011: |
| 963 | /* write data */ |
| 964 | logerror("write data\n"); |
| 965 | write_data(machine); |
| 966 | break; |
| 967 | case 0x04: //0b100: |
| 968 | /* unformatted read */ |
| 969 | logerror("unformatted read\n"); |
| 970 | unformatted_read(machine); |
| 971 | break; |
| 972 | case 0x05: //0b101: |
| 973 | /* unformatted write */ |
| 974 | logerror("unformatted write\n"); |
| 975 | /* ... */ |
| 976 | hdc.w[7] |= w7_idle | w7_error | w7_abnormal_completion; |
| 977 | update_interrupt(machine); |
| 978 | break; |
| 979 | case 0x06: //0b110: |
| 980 | /* seek */ |
| 981 | logerror("seek\n"); |
| 982 | /* This command can (almost) safely be ignored */ |
| 983 | hdc.w[7] |= w7_idle | w7_complete; |
| 984 | update_interrupt(machine); |
| 985 | break; |
| 986 | case 0x07: //0b111: |
| 987 | /* restore */ |
| 988 | logerror("restore\n"); |
| 989 | restore(machine); |
| 990 | break; |
| 991 | } |
| 992 | } |
| 993 | |
| 994 | /* |
| 995 | Read one register in TPCS space |
| 996 | */ |
| 997 | READ16_HANDLER(ti990_hdc_r) |
| 998 | { |
| 999 | if (offset < 8) |
| 1000 | return hdc.w[offset]; |
| 1001 | else |
| 1002 | return 0; |
| 1003 | } |
| 1004 | |
| 1005 | /* |
| 1006 | Write one register in TPCS space. Execute command if w7_idle is cleared. |
| 1007 | */ |
| 1008 | WRITE16_HANDLER(ti990_hdc_w) |
| 1009 | { |
| 1010 | if (offset < 8) |
| 1011 | { |
| 1012 | /* write protect if a command is in progress */ |
| 1013 | if (hdc.w[7] & w7_idle) |
| 1014 | { |
| 1015 | UINT16 old_data = hdc.w[offset]; |
| 1016 | |
| 1017 | /* Only write writable bits AND honor byte accesses (ha!) */ |
| 1018 | hdc.w[offset] = (hdc.w[offset] & ((~w_mask[offset]) | mem_mask)) | (data & w_mask[offset] & ~mem_mask); |
| 1019 | |
| 1020 | if ((offset == 0) || (offset == 7)) |
| 1021 | update_interrupt(space.machine()); |
| 1022 | |
| 1023 | if ((offset == 7) && (old_data & w7_idle) && ! (data & w7_idle)) |
| 1024 | { /* idle has been cleared: start command execution */ |
| 1025 | execute_command(space.machine()); |
| 1026 | } |
| 1027 | } |
| 1028 | } |
| 1029 | } |
| 1030 | |
| 1031 | |
| 1032 | static const struct harddisk_interface ti990_harddisk_config = |
| 1033 | { |
| 1034 | DEVICE_IMAGE_LOAD_NAME_LEGACY( ti990_hd ), |
| 1035 | DEVICE_IMAGE_UNLOAD_NAME_LEGACY( ti990_hd ), |
| 1036 | NULL, |
| 1037 | NULL |
| 1038 | }; |
| 1039 | |
| 1040 | MACHINE_CONFIG_FRAGMENT( ti990_hdc ) |
| 1041 | MCFG_HARDDISK_CONFIG_ADD( "harddisk1", ti990_harddisk_config ) |
| 1042 | MCFG_HARDDISK_CONFIG_ADD( "harddisk2", ti990_harddisk_config ) |
| 1043 | MCFG_HARDDISK_CONFIG_ADD( "harddisk3", ti990_harddisk_config ) |
| 1044 | MCFG_HARDDISK_CONFIG_ADD( "harddisk4", ti990_harddisk_config ) |
| 1045 | MACHINE_CONFIG_END |
trunk/src/mess/machine/ti99/990_dk.c
| r0 | r26091 | |
| 1 | /* |
| 2 | 990_dk.c: emulation of a TI FD800 'Diablo' floppy disk controller |
| 3 | controller, for use with any TI990 system (and possibly any system which |
| 4 | implements the CRU bus). |
| 5 | |
| 6 | This floppy disk controller supports IBM-format 8" SSSD and DSSD floppies. |
| 7 | |
| 8 | Raphael Nabet 2003 |
| 9 | */ |
| 10 | |
| 11 | #include "emu.h" |
| 12 | |
| 13 | #include "formats/basicdsk.h" |
| 14 | #include "imagedev/flopdrv.h" |
| 15 | #include "990_dk.h" |
| 16 | |
| 17 | #define MAX_FLOPPIES 4 |
| 18 | |
| 19 | enum buf_mode_t { |
| 20 | bm_off, bm_read, bm_write |
| 21 | }; |
| 22 | static struct |
| 23 | { |
| 24 | running_machine *machine; |
| 25 | UINT16 recv_buf; |
| 26 | UINT16 stat_reg; |
| 27 | UINT16 xmit_buf; |
| 28 | UINT16 cmd_reg; |
| 29 | |
| 30 | int interrupt_f_f; |
| 31 | void (*interrupt_callback)(running_machine &, int state); |
| 32 | |
| 33 | UINT8 buf[128]; |
| 34 | int buf_pos; |
| 35 | buf_mode_t buf_mode; |
| 36 | int unit; |
| 37 | int head; |
| 38 | int sector; |
| 39 | /*int non_seq_mode;*/ |
| 40 | int ddam; |
| 41 | |
| 42 | struct |
| 43 | { |
| 44 | device_image_interface *img; |
| 45 | int phys_cylinder; |
| 46 | int log_cylinder[2]; |
| 47 | int seclen; |
| 48 | } drv[MAX_FLOPPIES]; |
| 49 | } fd800; |
| 50 | |
| 51 | /* status bits */ |
| 52 | enum |
| 53 | { |
| 54 | status_OP_complete = 1 << 0, |
| 55 | status_XFER_ready = 1 << 1, |
| 56 | status_drv_not_ready= 1 << 2, |
| 57 | status_dat_chk_err = 1 << 3, |
| 58 | status_seek_err = 1 << 4, |
| 59 | status_invalid_cmd = 1 << 5, |
| 60 | status_no_addr_mark = 1 << 6, |
| 61 | status_equ_chk_err = 1 << 7, |
| 62 | status_ID_chk_err = 1 << 8, |
| 63 | status_ID_not_found = 1 << 9, |
| 64 | status_ctlr_busy = 1 << 10, |
| 65 | status_write_prot = 1 << 11, |
| 66 | status_del_sector = 1 << 12, |
| 67 | status_interrupt = 1 << 15, |
| 68 | |
| 69 | status_unit_shift = 13 |
| 70 | }; |
| 71 | |
| 72 | LEGACY_FLOPPY_OPTIONS_START(fd800) |
| 73 | #if 1 |
| 74 | /* SSSD 8" */ |
| 75 | LEGACY_FLOPPY_OPTION(fd800, "dsk", "TI990 8\" SSSD disk image", basicdsk_identify_default, basicdsk_construct_default, NULL, |
| 76 | HEADS([1]) |
| 77 | TRACKS([77]) |
| 78 | SECTORS([26]) |
| 79 | SECTOR_LENGTH([128]) |
| 80 | FIRST_SECTOR_ID([1])) |
| 81 | #elif 0 |
| 82 | /* DSSD 8" */ |
| 83 | LEGACY_FLOPPY_OPTION(fd800, "dsk", "TI990 8\" DSSD disk image", basicdsk_identify_default, basicdsk_construct_default, NULL, |
| 84 | HEADS([2]) |
| 85 | TRACKS([77]) |
| 86 | SECTORS([26]) |
| 87 | SECTOR_LENGTH([128]) |
| 88 | FIRST_SECTOR_ID([1])) |
| 89 | #endif |
| 90 | LEGACY_FLOPPY_OPTIONS_END |
| 91 | |
| 92 | static void fd800_field_interrupt(void) |
| 93 | { |
| 94 | if (fd800.interrupt_callback) |
| 95 | (*fd800.interrupt_callback)(*fd800.machine, (fd800.stat_reg & status_interrupt) && ! fd800.interrupt_f_f); |
| 96 | } |
| 97 | |
| 98 | static void fd800_unload_proc(device_image_interface &image) |
| 99 | { |
| 100 | int unit = floppy_get_drive(&image.device()); |
| 101 | |
| 102 | fd800.drv[unit].log_cylinder[0] = fd800.drv[unit].log_cylinder[1] = -1; |
| 103 | } |
| 104 | |
| 105 | void fd800_machine_init(running_machine &machine, void (*interrupt_callback)(running_machine &machine, int state)) |
| 106 | { |
| 107 | int i; |
| 108 | |
| 109 | fd800.machine = &machine; |
| 110 | fd800.interrupt_callback = interrupt_callback; |
| 111 | |
| 112 | fd800.stat_reg = 0; |
| 113 | fd800.interrupt_f_f = 1; |
| 114 | |
| 115 | fd800.buf_pos = 0; |
| 116 | fd800.buf_mode = bm_off; |
| 117 | |
| 118 | for (i=0; i<MAX_FLOPPIES; i++) |
| 119 | { |
| 120 | fd800.drv[i].img = dynamic_cast<device_image_interface *>(floppy_get_device(machine, i)); |
| 121 | fd800.drv[i].phys_cylinder = -1; |
| 122 | fd800.drv[i].log_cylinder[0] = fd800.drv[i].log_cylinder[1] = -1; |
| 123 | fd800.drv[i].seclen = 64; |
| 124 | floppy_install_unload_proc(&fd800.drv[i].img->device(), fd800_unload_proc); |
| 125 | } |
| 126 | |
| 127 | fd800_field_interrupt(); |
| 128 | } |
| 129 | |
| 130 | /* |
| 131 | Read the first id field that can be found on the floppy disk. |
| 132 | |
| 133 | unit: floppy drive index |
| 134 | head: selected head |
| 135 | cylinder_id: cylinder ID read |
| 136 | sector_id: sector ID read |
| 137 | |
| 138 | Return TRUE if an ID was found |
| 139 | */ |
| 140 | static int fd800_read_id(int unit, int head, int *cylinder_id, int *sector_id) |
| 141 | { |
| 142 | /*UINT8 revolution_count;*/ |
| 143 | chrn_id id; |
| 144 | |
| 145 | /*revolution_count = 0;*/ |
| 146 | |
| 147 | /*while (revolution_count < 2)*/ |
| 148 | /*{*/ |
| 149 | if (floppy_drive_get_next_id(&fd800.drv[unit].img->device(), head, &id)) |
| 150 | { |
| 151 | if (cylinder_id) |
| 152 | *cylinder_id = id.C; |
| 153 | if (sector_id) |
| 154 | *sector_id = id.R; |
| 155 | return TRUE; |
| 156 | } |
| 157 | /*}*/ |
| 158 | |
| 159 | return FALSE; |
| 160 | } |
| 161 | |
| 162 | /* |
| 163 | Find a sector by id. |
| 164 | |
| 165 | unit: floppy drive index |
| 166 | head: selected head |
| 167 | sector: sector ID to search |
| 168 | data_id: data ID to be used when calling sector read/write functions |
| 169 | |
| 170 | Return TRUE if the given sector ID was found |
| 171 | */ |
| 172 | static int fd800_find_sector(int unit, int head, int sector, int *data_id) |
| 173 | { |
| 174 | UINT8 revolution_count; |
| 175 | chrn_id id; |
| 176 | |
| 177 | revolution_count = 0; |
| 178 | |
| 179 | while (revolution_count < 2) |
| 180 | { |
| 181 | if (floppy_drive_get_next_id(&fd800.drv[unit].img->device(), head, &id)) |
| 182 | { |
| 183 | /* compare id */ |
| 184 | if ((id.R == sector) && (id.N == 0)) |
| 185 | { |
| 186 | *data_id = id.data_id; |
| 187 | /* get ddam status */ |
| 188 | /*w->ddam = id.flags & ID_FLAG_DELETED_DATA;*/ |
| 189 | return TRUE; |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | return FALSE; |
| 195 | } |
| 196 | |
| 197 | /* |
| 198 | Perform seek command |
| 199 | |
| 200 | unit: floppy drive index |
| 201 | cylinder: track to seek for |
| 202 | head: head for which the seek is performed |
| 203 | |
| 204 | Return FALSE if the seek was successful |
| 205 | */ |
| 206 | static int fd800_do_seek(int unit, int cylinder, int head) |
| 207 | { |
| 208 | int retries; |
| 209 | |
| 210 | if (cylinder > 76) |
| 211 | { |
| 212 | fd800.stat_reg |= status_invalid_cmd; |
| 213 | return TRUE; |
| 214 | } |
| 215 | |
| 216 | if (!fd800.drv[unit].img->exists()) |
| 217 | { |
| 218 | fd800.stat_reg |= status_drv_not_ready; /* right??? */ |
| 219 | return TRUE; |
| 220 | } |
| 221 | |
| 222 | if (fd800.drv[unit].log_cylinder[head] == -1) |
| 223 | { /* current track ID is unknown: read it */ |
| 224 | if (!fd800_read_id(unit, head, &fd800.drv[unit].log_cylinder[head], NULL)) |
| 225 | { |
| 226 | fd800.stat_reg |= status_ID_not_found; |
| 227 | return TRUE; |
| 228 | } |
| 229 | } |
| 230 | /* exit if we are already at the requested track */ |
| 231 | if (fd800.drv[unit].log_cylinder[head] == cylinder) |
| 232 | { |
| 233 | /*fd800.stat_reg |= status_OP_complete;*/ |
| 234 | return FALSE; |
| 235 | } |
| 236 | for (retries=0; retries<10; retries++) |
| 237 | { /* seek to requested track */ |
| 238 | floppy_drive_seek(&fd800.drv[unit].img->device(), cylinder-fd800.drv[unit].log_cylinder[head]); |
| 239 | /* update physical track position */ |
| 240 | if (fd800.drv[unit].phys_cylinder != -1) |
| 241 | fd800.drv[unit].phys_cylinder += cylinder-fd800.drv[unit].log_cylinder[head]; |
| 242 | /* read new track ID */ |
| 243 | if (!fd800_read_id(unit, head, &fd800.drv[unit].log_cylinder[head], NULL)) |
| 244 | { |
| 245 | fd800.drv[unit].log_cylinder[head] = -1; |
| 246 | fd800.stat_reg |= status_ID_not_found; |
| 247 | return TRUE; |
| 248 | } |
| 249 | /* exit if we have reached the requested track */ |
| 250 | if (fd800.drv[unit].log_cylinder[head] == cylinder) |
| 251 | { |
| 252 | /*fd800.stat_reg |= status_OP_complete;*/ |
| 253 | return FALSE; |
| 254 | } |
| 255 | } |
| 256 | /* track not found */ |
| 257 | fd800.stat_reg |= status_seek_err; |
| 258 | return TRUE; |
| 259 | } |
| 260 | |
| 261 | /* |
| 262 | Perform restore command |
| 263 | |
| 264 | unit: floppy drive index |
| 265 | |
| 266 | Return FALSE if the restore was successful |
| 267 | */ |
| 268 | static int fd800_do_restore(int unit) |
| 269 | { |
| 270 | int seek_count = 0; |
| 271 | int seek_complete; |
| 272 | |
| 273 | if (!fd800.drv[unit].img->exists()) |
| 274 | { |
| 275 | fd800.stat_reg |= status_drv_not_ready; /* right??? */ |
| 276 | return TRUE; |
| 277 | } |
| 278 | |
| 279 | /* limit iterations to 76 to prevent an endless loop if the disc is locked */ |
| 280 | while (!(seek_complete = !floppy_tk00_r(&fd800.drv[unit].img->device())) && (seek_count < 76)) |
| 281 | { |
| 282 | floppy_drive_seek(&fd800.drv[unit].img->device(), -1); |
| 283 | seek_count++; |
| 284 | } |
| 285 | if (! seek_complete) |
| 286 | { |
| 287 | fd800.drv[unit].phys_cylinder = -1; |
| 288 | fd800.stat_reg |= status_seek_err; |
| 289 | } |
| 290 | else |
| 291 | { |
| 292 | fd800.drv[unit].phys_cylinder = 0; |
| 293 | /*fd800.stat_reg |= status_OP_complete;*/ |
| 294 | } |
| 295 | |
| 296 | return ! seek_complete; |
| 297 | } |
| 298 | |
| 299 | /* |
| 300 | Perform a read operation for one sector |
| 301 | */ |
| 302 | static void fd800_do_read(void) |
| 303 | { |
| 304 | int data_id; |
| 305 | |
| 306 | if ((fd800.sector == 0) || (fd800.sector > 26)) |
| 307 | { |
| 308 | fd800.stat_reg |= status_invalid_cmd; |
| 309 | return; |
| 310 | } |
| 311 | |
| 312 | if (!fd800_find_sector(fd800.unit, fd800.head, fd800.sector, &data_id)) |
| 313 | { |
| 314 | fd800.stat_reg |= status_ID_not_found; |
| 315 | return; |
| 316 | } |
| 317 | |
| 318 | floppy_drive_read_sector_data(&fd800.drv[fd800.unit].img->device(), fd800.head, data_id, fd800.buf, 128); |
| 319 | fd800.buf_pos = 0; |
| 320 | fd800.buf_mode = bm_read; |
| 321 | fd800.recv_buf = (fd800.buf[fd800.buf_pos<<1] << 8) | fd800.buf[(fd800.buf_pos<<1)+1]; |
| 322 | |
| 323 | fd800.stat_reg |= status_XFER_ready; |
| 324 | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 325 | } |
| 326 | |
| 327 | /* |
| 328 | Perform a write operation for one sector |
| 329 | */ |
| 330 | static void fd800_do_write(void) |
| 331 | { |
| 332 | int data_id; |
| 333 | |
| 334 | if (fd800.drv[fd800.unit].seclen < 64) |
| 335 | /* fill with 0s */ |
| 336 | memset(fd800.buf+(fd800.drv[fd800.unit].seclen<<1), 0, (64-fd800.drv[fd800.unit].seclen)<<1); |
| 337 | |
| 338 | if (!fd800_find_sector(fd800.unit, fd800.head, fd800.sector, &data_id)) |
| 339 | { |
| 340 | fd800.stat_reg |= status_ID_not_found; |
| 341 | return; |
| 342 | } |
| 343 | |
| 344 | floppy_drive_write_sector_data(&fd800.drv[fd800.unit].img->device(), fd800.head, data_id, fd800.buf, 128, fd800.ddam); |
| 345 | fd800.buf_pos = 0; |
| 346 | fd800.buf_mode = bm_write; |
| 347 | |
| 348 | fd800.stat_reg |= status_XFER_ready; |
| 349 | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 350 | } |
| 351 | |
| 352 | /* |
| 353 | Execute a fdc command |
| 354 | */ |
| 355 | static void fd800_do_cmd(void) |
| 356 | { |
| 357 | int unit; |
| 358 | int cylinder; |
| 359 | int head; |
| 360 | int seclen; |
| 361 | int sector; |
| 362 | |
| 363 | |
| 364 | if (fd800.buf_mode != bm_off) |
| 365 | { /* All commands in the midst of read or write are interpreted as Stop */ |
| 366 | unit = (fd800.cmd_reg >> 10) & 3; |
| 367 | |
| 368 | /* reset status */ |
| 369 | fd800.stat_reg = unit << status_unit_shift; |
| 370 | |
| 371 | fd800.buf_pos = 0; |
| 372 | fd800.buf_mode = bm_off; |
| 373 | |
| 374 | fd800.stat_reg |= status_OP_complete; |
| 375 | |
| 376 | fd800.stat_reg |= status_interrupt; |
| 377 | fd800_field_interrupt(); |
| 378 | |
| 379 | return; |
| 380 | } |
| 381 | |
| 382 | switch (fd800.cmd_reg >> 12) |
| 383 | { |
| 384 | case 0: /* select |
| 385 | bits 16-25: 0s |
| 386 | bits 26-27: unit number (0-3) */ |
| 387 | unit = (fd800.cmd_reg >> 10) & 3; |
| 388 | |
| 389 | /* reset status */ |
| 390 | fd800.stat_reg = unit << status_unit_shift; |
| 391 | |
| 392 | if (!fd800.drv[unit].img->exists()) |
| 393 | fd800.stat_reg |= status_drv_not_ready; /* right??? */ |
| 394 | else if (fd800.drv[unit].img->is_readonly()) |
| 395 | fd800.stat_reg |= status_write_prot; |
| 396 | else |
| 397 | fd800.stat_reg |= status_OP_complete; |
| 398 | |
| 399 | fd800.stat_reg |= status_interrupt; |
| 400 | fd800_field_interrupt(); |
| 401 | break; |
| 402 | |
| 403 | case 1: /* seek |
| 404 | bits 16-22: cylinder number (0-76) |
| 405 | bits 23-24: 0s |
| 406 | bits 25: head number (1=upper) |
| 407 | bits 26-27: unit number (0-3) */ |
| 408 | unit = (fd800.cmd_reg >> 10) & 3; |
| 409 | head = (fd800.cmd_reg >> 9) & 1; |
| 410 | cylinder = fd800.cmd_reg & 0x7f; |
| 411 | |
| 412 | /* reset status */ |
| 413 | fd800.stat_reg = unit << status_unit_shift; |
| 414 | |
| 415 | if (!fd800_do_seek(unit, cylinder, head)) |
| 416 | fd800.stat_reg |= status_OP_complete; |
| 417 | |
| 418 | fd800.stat_reg |= status_interrupt; |
| 419 | fd800_field_interrupt(); |
| 420 | break; |
| 421 | |
| 422 | case 2: /* restore |
| 423 | bits 16-25: 0s |
| 424 | bits 26-27: unit number (0-3) */ |
| 425 | unit = (fd800.cmd_reg >> 10) & 3; |
| 426 | |
| 427 | /* reset status */ |
| 428 | fd800.stat_reg = unit << status_unit_shift; |
| 429 | |
| 430 | if (!fd800_do_restore(unit)) |
| 431 | fd800.stat_reg |= status_OP_complete; |
| 432 | |
| 433 | fd800.stat_reg |= status_interrupt; |
| 434 | fd800_field_interrupt(); |
| 435 | break; |
| 436 | |
| 437 | case 3: /* sector length |
| 438 | bits 16-22: sector word count (0-64) |
| 439 | bits 23-25: 0s |
| 440 | bits 26-27: unit number (0-3) */ |
| 441 | unit = (fd800.cmd_reg >> 10) & 3; |
| 442 | seclen = fd800.cmd_reg & 0x7f; |
| 443 | |
| 444 | /* reset status */ |
| 445 | fd800.stat_reg = unit << status_unit_shift; |
| 446 | |
| 447 | if ((seclen > 64) || (seclen == 0)) |
| 448 | { |
| 449 | fd800.stat_reg |= status_invalid_cmd; |
| 450 | } |
| 451 | else |
| 452 | { |
| 453 | fd800.drv[unit].seclen = seclen; |
| 454 | fd800.stat_reg |= status_OP_complete; |
| 455 | } |
| 456 | |
| 457 | fd800.stat_reg |= status_interrupt; |
| 458 | fd800_field_interrupt(); |
| 459 | break; |
| 460 | |
| 461 | case 4: /* read |
| 462 | bits 16-20: sector number (1-26) |
| 463 | bits 21-23: 0s |
| 464 | bit 24: no sequential sectoring (1=active) |
| 465 | bit 25: head number (1=upper) |
| 466 | bits 26-27: unit number (0-3) */ |
| 467 | unit = (fd800.cmd_reg >> 10) & 3; |
| 468 | head = (fd800.cmd_reg >> 9) & 1; |
| 469 | /*non_seq_mode = (fd800.cmd_reg >> 8) & 1;*/ |
| 470 | sector = fd800.cmd_reg & 0x1f; |
| 471 | |
| 472 | fd800.unit = unit; |
| 473 | fd800.head = head; |
| 474 | fd800.sector = sector; |
| 475 | /*fd800.non_seq_mode = non_seq_mode;*/ |
| 476 | |
| 477 | /* reset status */ |
| 478 | fd800.stat_reg = unit << status_unit_shift; |
| 479 | |
| 480 | fd800_do_read(); |
| 481 | |
| 482 | fd800.stat_reg |= status_interrupt; |
| 483 | fd800_field_interrupt(); |
| 484 | break; |
| 485 | |
| 486 | case 5: /* read ID |
| 487 | bits 16-24: 0s |
| 488 | bit 25: head number (1=upper) |
| 489 | bits 26-27: unit number (0-3) */ |
| 490 | unit = (fd800.cmd_reg >> 10) & 3; |
| 491 | head = (fd800.cmd_reg >> 9) & 1; |
| 492 | |
| 493 | /* reset status */ |
| 494 | fd800.stat_reg = unit << status_unit_shift; |
| 495 | |
| 496 | if (!fd800_read_id(unit, head, &cylinder, §or)) |
| 497 | { |
| 498 | fd800.stat_reg |= status_ID_not_found; |
| 499 | } |
| 500 | else |
| 501 | { |
| 502 | fd800.recv_buf = (cylinder << 8) | sector; |
| 503 | fd800.stat_reg |= status_OP_complete; |
| 504 | } |
| 505 | |
| 506 | fd800.stat_reg |= status_interrupt; |
| 507 | fd800_field_interrupt(); |
| 508 | break; |
| 509 | |
| 510 | case 6: /* read unformatted |
| 511 | bits 16-20: sector number (1-26) |
| 512 | bits 21-24: 0s |
| 513 | bit 25: head number (1=upper) |
| 514 | bits 26-27: unit number (0-3) */ |
| 515 | /* ... */ |
| 516 | break; |
| 517 | |
| 518 | case 7: /* write |
| 519 | bits 16-20: sector number (1-26) |
| 520 | bits 21-24: 0s |
| 521 | bit 25: head number (1=upper) |
| 522 | bits 26-27: unit number (0-3) */ |
| 523 | unit = (fd800.cmd_reg >> 10) & 3; |
| 524 | head = (fd800.cmd_reg >> 9) & 1; |
| 525 | sector = fd800.cmd_reg & 0x1f; |
| 526 | |
| 527 | /* reset status */ |
| 528 | fd800.stat_reg = unit << status_unit_shift; |
| 529 | |
| 530 | if ((fd800.sector == 0) || (fd800.sector > 26)) |
| 531 | { |
| 532 | fd800.stat_reg |= status_invalid_cmd; |
| 533 | } |
| 534 | else |
| 535 | { |
| 536 | fd800.unit = unit; |
| 537 | fd800.head = head; |
| 538 | fd800.sector = sector; |
| 539 | fd800.ddam = 0; |
| 540 | |
| 541 | fd800.buf_pos = 0; |
| 542 | fd800.buf_mode = bm_write; |
| 543 | fd800.stat_reg |= status_XFER_ready; |
| 544 | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 545 | } |
| 546 | |
| 547 | fd800.stat_reg |= status_interrupt; |
| 548 | fd800_field_interrupt(); |
| 549 | break; |
| 550 | |
| 551 | case 8: /* write delete |
| 552 | bits 16-20: sector number (1-26) |
| 553 | bits 21-24: 0s |
| 554 | bit 25: head number (1=upper) |
| 555 | bits 26-27: unit number (0-3) */ |
| 556 | unit = (fd800.cmd_reg >> 10) & 3; |
| 557 | head = (fd800.cmd_reg >> 9) & 1; |
| 558 | sector = fd800.cmd_reg & 0x1f; |
| 559 | |
| 560 | /* reset status */ |
| 561 | fd800.stat_reg = unit << status_unit_shift; |
| 562 | |
| 563 | if ((fd800.sector == 0) || (fd800.sector > 26)) |
| 564 | { |
| 565 | fd800.stat_reg |= status_invalid_cmd; |
| 566 | } |
| 567 | else |
| 568 | { |
| 569 | fd800.unit = unit; |
| 570 | fd800.head = head; |
| 571 | fd800.sector = sector; |
| 572 | fd800.ddam = 1; |
| 573 | |
| 574 | fd800.buf_pos = 0; |
| 575 | fd800.buf_mode = bm_write; |
| 576 | fd800.stat_reg |= status_XFER_ready; |
| 577 | fd800.stat_reg |= status_OP_complete; /* right??? */ |
| 578 | } |
| 579 | |
| 580 | fd800.stat_reg |= status_interrupt; |
| 581 | fd800_field_interrupt(); |
| 582 | break; |
| 583 | |
| 584 | case 9: /* format track |
| 585 | bits 16-23: track ID (0-255, normally current cylinder index, or 255 for bad track) |
| 586 | bit 24: verify only (1 - verify, 0 - format & verify) |
| 587 | bit 25: head number (1=upper) |
| 588 | bits 26-27: unit number (0-3) */ |
| 589 | /* ... */ |
| 590 | break; |
| 591 | |
| 592 | case 10: /* load int mask |
| 593 | bit 16: bad mask for interrupt (0 = unmask or enable interrupt) |
| 594 | bits 17-27: 0s */ |
| 595 | fd800.interrupt_f_f = fd800.cmd_reg & 1; |
| 596 | fd800_field_interrupt(); |
| 597 | break; |
| 598 | |
| 599 | case 11: /* stop |
| 600 | bits 16-25: 0s |
| 601 | bits 26-27: unit number (0-3) */ |
| 602 | unit = (fd800.cmd_reg >> 10) & 3; |
| 603 | |
| 604 | /* reset status */ |
| 605 | fd800.stat_reg = unit << status_unit_shift; |
| 606 | |
| 607 | fd800.stat_reg |= status_OP_complete; |
| 608 | |
| 609 | fd800.stat_reg |= status_interrupt; |
| 610 | fd800_field_interrupt(); |
| 611 | break; |
| 612 | |
| 613 | case 12: /* step head |
| 614 | bits 16-22: track number (0-76) |
| 615 | bits 23-25: 0s |
| 616 | bits 26-27: unit number (0-3) */ |
| 617 | unit = (fd800.cmd_reg >> 10) & 3; |
| 618 | cylinder = fd800.cmd_reg & 0x7f; |
| 619 | |
| 620 | if (cylinder > 76) |
| 621 | { |
| 622 | fd800.stat_reg |= status_invalid_cmd; |
| 623 | } |
| 624 | else if ((fd800.drv[unit].phys_cylinder != -1) || (!fd800_do_restore(unit))) |
| 625 | { |
| 626 | floppy_drive_seek(&fd800.drv[unit].img->device(), cylinder-fd800.drv[unit].phys_cylinder); |
| 627 | fd800.stat_reg |= status_OP_complete; |
| 628 | } |
| 629 | |
| 630 | fd800.stat_reg |= status_interrupt; |
| 631 | fd800_field_interrupt(); |
| 632 | break; |
| 633 | |
| 634 | case 13: /* maintenance commands |
| 635 | bits 16-23: according to extended command code |
| 636 | bits 24-27: extended command code (0-7) */ |
| 637 | switch ((fd800.cmd_reg >> 8) & 15) |
| 638 | { |
| 639 | case 0: /* reset |
| 640 | bits 16-23: 0s */ |
| 641 | /* ... */ |
| 642 | break; |
| 643 | case 1: /* retry inhibit |
| 644 | bits 16-23: 0s */ |
| 645 | /* ... */ |
| 646 | break; |
| 647 | case 2: /* LED test |
| 648 | bit 16: 1 |
| 649 | bits 17-19: 0s |
| 650 | bit 20: LED #2 enable |
| 651 | bit 21: LED #3 enable |
| 652 | bit 22: LED #4 enable |
| 653 | bit 23: enable LEDs */ |
| 654 | /* ... */ |
| 655 | break; |
| 656 | case 3: /* program error (a.k.a. invalid command) |
| 657 | bits 16-23: 0s */ |
| 658 | /* ... */ |
| 659 | break; |
| 660 | case 4: /* memory read |
| 661 | bits 16-20: controller memory address (shifted left by 8 to generate 9900 address) |
| 662 | bits 21-23: 0s */ |
| 663 | /* ... */ |
| 664 | break; |
| 665 | case 5: /* RAM load |
| 666 | bit 16: 0 |
| 667 | bits 17-23: RAM offset (shifted left by 1 and offset by >1800 to generate 9900 address) */ |
| 668 | /* ... */ |
| 669 | break; |
| 670 | case 6: /* RAM run |
| 671 | bit 16: 0 |
| 672 | bits 17-23: RAM offset (shifted left by 1 and offset by >1800 to generate 9900 address) */ |
| 673 | /* ... */ |
| 674 | break; |
| 675 | case 7: /* power up simulation |
| 676 | bits 16-23: 0s */ |
| 677 | /* ... */ |
| 678 | break; |
| 679 | } |
| 680 | /* ... */ |
| 681 | break; |
| 682 | |
| 683 | case 14: /* IPL |
| 684 | bits 16-22: track number (0-76) |
| 685 | bit 23: 0 |
| 686 | bit 24: no sequential sectoring (1=active) |
| 687 | bit 25: head number (1=upper) |
| 688 | bits 26-27: unit number (0-3) */ |
| 689 | unit = (fd800.cmd_reg >> 10) & 3; |
| 690 | head = (fd800.cmd_reg >> 9) & 1; |
| 691 | /*non_seq_mode = (fd800.cmd_reg >> 8) & 1;*/ |
| 692 | cylinder = fd800.cmd_reg & 0x7f; |
| 693 | |
| 694 | if (!fd800_do_seek(unit, cylinder, head)) |
| 695 | { |
| 696 | fd800.unit = unit; |
| 697 | fd800.head = head; |
| 698 | fd800.sector = 1; |
| 699 | /*fd800.non_seq_mode = non_seq_mode;*/ |
| 700 | |
| 701 | fd800_do_read(); |
| 702 | } |
| 703 | |
| 704 | fd800.stat_reg |= status_interrupt; |
| 705 | fd800_field_interrupt(); |
| 706 | break; |
| 707 | |
| 708 | case 15: /* Clear Status port |
| 709 | bits 16-27: 0s */ |
| 710 | fd800.stat_reg = 0; |
| 711 | fd800_field_interrupt(); |
| 712 | break; |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | /* |
| 717 | read one CRU bit |
| 718 | |
| 719 | 0-15: receive buffer |
| 720 | 16-31: status: |
| 721 | 16: OP complete (1 -> complete???) |
| 722 | 17: Xfer ready (XFER) (1 -> ready???) |
| 723 | 18: drive not ready |
| 724 | 19: data check error |
| 725 | 20: seek error/?????? |
| 726 | 21 invalid command/?????? |
| 727 | 22: no address mark found/?????? |
| 728 | 23: equipment check error/?????? |
| 729 | 24: ID check error |
| 730 | 25: ID not found |
| 731 | 26: Controller busy (CTLBSY) (0 -> controller is ready) |
| 732 | 27: write protect |
| 733 | 28: deleted sector detected |
| 734 | 29: unit LSB |
| 735 | 30: unit MSB |
| 736 | 31: Interrupt (CBUSY???) (1 -> controller is ready) |
| 737 | */ |
| 738 | READ8_HANDLER(fd800_cru_r) |
| 739 | { |
| 740 | int reply = 0; |
| 741 | |
| 742 | switch (offset) |
| 743 | { |
| 744 | case 0: |
| 745 | case 1: |
| 746 | /* receive buffer */ |
| 747 | reply = fd800.recv_buf >> (offset*8); |
| 748 | break; |
| 749 | |
| 750 | case 2: |
| 751 | case 3: |
| 752 | /* status register */ |
| 753 | reply = fd800.stat_reg >> ((offset-2)*8); |
| 754 | break; |
| 755 | } |
| 756 | |
| 757 | return reply; |
| 758 | } |
| 759 | |
| 760 | /* |
| 761 | write one CRU bit |
| 762 | |
| 763 | 0-15: controller data word (transmit buffer) |
| 764 | 16-31: controller command word (command register) |
| 765 | 16-23: parameter value |
| 766 | 24: flag bit/extended command code |
| 767 | 25: head select/extended command code |
| 768 | 26: FD unit number LSB/extended command code |
| 769 | 27: FD unit number MSB/extended command code |
| 770 | 28-31: command code |
| 771 | */ |
| 772 | WRITE8_HANDLER(fd800_cru_w) |
| 773 | { |
| 774 | switch (offset) |
| 775 | { |
| 776 | case 0: |
| 777 | case 1: |
| 778 | case 2: |
| 779 | case 3: |
| 780 | case 4: |
| 781 | case 5: |
| 782 | case 6: |
| 783 | case 7: |
| 784 | case 8: |
| 785 | case 9: |
| 786 | case 10: |
| 787 | case 11: |
| 788 | case 12: |
| 789 | case 13: |
| 790 | case 14: |
| 791 | case 15: |
| 792 | /* transmit buffer */ |
| 793 | if (data) |
| 794 | fd800.xmit_buf |= 1 << offset; |
| 795 | else |
| 796 | fd800.xmit_buf &= ~(1 << offset); |
| 797 | if (offset == 15) |
| 798 | { |
| 799 | switch (fd800.buf_mode) |
| 800 | { |
| 801 | case bm_off: |
| 802 | break; |
| 803 | case bm_read: |
| 804 | fd800.buf_pos++; |
| 805 | if (fd800.buf_pos == fd800.drv[fd800.unit].seclen) |
| 806 | { /* end of sector */ |
| 807 | if (fd800.sector == 26) |
| 808 | { /* end of track -> end command (right???) */ |
| 809 | fd800.stat_reg &= ~status_XFER_ready; |
| 810 | fd800.stat_reg |= status_OP_complete; |
| 811 | fd800.stat_reg |= status_interrupt; |
| 812 | fd800.buf_mode = bm_off; |
| 813 | fd800_field_interrupt(); |
| 814 | } |
| 815 | else |
| 816 | { /* read next sector */ |
| 817 | fd800.sector++; |
| 818 | fd800.stat_reg &= ~status_XFER_ready | status_OP_complete | status_interrupt; |
| 819 | fd800_do_read(); |
| 820 | fd800.stat_reg |= status_interrupt; |
| 821 | fd800_field_interrupt(); |
| 822 | } |
| 823 | } |
| 824 | else |
| 825 | fd800.recv_buf = (fd800.buf[fd800.buf_pos<<1] << 8) | fd800.buf[(fd800.buf_pos<<1)+1]; |
| 826 | break; |
| 827 | |
| 828 | case bm_write: |
| 829 | fd800.buf[fd800.buf_pos<<1] = fd800.xmit_buf >> 8; |
| 830 | fd800.buf[(fd800.buf_pos<<1)+1] = fd800.xmit_buf & 0xff; |
| 831 | fd800.buf_pos++; |
| 832 | if (fd800.buf_pos == fd800.drv[fd800.unit].seclen) |
| 833 | { /* end of sector */ |
| 834 | fd800_do_write(); |
| 835 | if (fd800.sector == 26) |
| 836 | { |
| 837 | /* end of track -> end command (right???) */ |
| 838 | fd800.stat_reg &= ~status_XFER_ready; |
| 839 | fd800.stat_reg |= status_OP_complete; |
| 840 | fd800.stat_reg |= status_interrupt; |
| 841 | fd800.buf_mode = bm_off; |
| 842 | fd800_field_interrupt(); |
| 843 | } |
| 844 | else |
| 845 | { /* increment to next sector */ |
| 846 | fd800.sector++; |
| 847 | fd800.stat_reg |= status_interrupt; |
| 848 | fd800_field_interrupt(); |
| 849 | } |
| 850 | } |
| 851 | break; |
| 852 | } |
| 853 | } |
| 854 | break; |
| 855 | |
| 856 | case 16: |
| 857 | case 17: |
| 858 | case 18: |
| 859 | case 19: |
| 860 | case 20: |
| 861 | case 21: |
| 862 | case 22: |
| 863 | case 23: |
| 864 | case 24: |
| 865 | case 25: |
| 866 | case 26: |
| 867 | case 27: |
| 868 | case 28: |
| 869 | case 29: |
| 870 | case 30: |
| 871 | case 31: |
| 872 | /* command register */ |
| 873 | if (data) |
| 874 | fd800.cmd_reg |= 1 << (offset-16); |
| 875 | else |
| 876 | fd800.cmd_reg &= ~(1 << (offset-16)); |
| 877 | if (offset == 31) |
| 878 | fd800_do_cmd(); |
| 879 | break; |
| 880 | } |
| 881 | } |
trunk/src/mess/machine/ti99/990_tap.c
| r0 | r26091 | |
| 1 | /* |
| 2 | 990_tap.c: emulation of a generic ti990 tape controller, for use with |
| 3 | TILINE-based TI990 systems (TI990/10, /12, /12LR, /10A, Business system 300 |
| 4 | and 300A). |
| 5 | |
| 6 | This core will emulate the common feature set found in every tape controller. |
| 7 | Most controllers support additional features, but are still compatible with |
| 8 | the basic feature set. I have a little documentation on two specific |
| 9 | tape controllers (MT3200 and WD800/WD800A), but I have not tried to emulate |
| 10 | controller-specific features. |
| 11 | |
| 12 | |
| 13 | Long description: see 2234398-9701 and 2306140-9701. |
| 14 | |
| 15 | |
| 16 | Raphael Nabet 2002 |
| 17 | */ |
| 18 | /* |
| 19 | Image encoding: |
| 20 | |
| 21 | |
| 22 | 2 bytes: record len - little-endian |
| 23 | 2 bytes: always 0s (length MSBs?) |
| 24 | len bytes: data |
| 25 | 2 bytes: record len - little-endian |
| 26 | 2 bytes: always 0s (length MSBs?) |
| 27 | |
| 28 | 4 0s: EOF mark |
| 29 | */ |
| 30 | |
| 31 | #include "emu.h" |
| 32 | #include "990_tap.h" |
| 33 | #include "devlegcy.h" |
| 34 | |
| 35 | |
| 36 | static void update_interrupt(device_t *device); |
| 37 | |
| 38 | #define MAX_TAPE_UNIT 4 |
| 39 | |
| 40 | struct tape_unit_t |
| 41 | { |
| 42 | device_image_interface *img; /* image descriptor */ |
| 43 | unsigned int bot : 1; /* TRUE if we are at the beginning of tape */ |
| 44 | unsigned int eot : 1; /* TRUE if we are at the end of tape */ |
| 45 | unsigned int wp : 1; /* TRUE if tape is write-protected */ |
| 46 | }; |
| 47 | |
| 48 | struct tap_990_t |
| 49 | { |
| 50 | UINT16 w[8]; |
| 51 | |
| 52 | const ti990_tpc_interface *intf; |
| 53 | |
| 54 | tape_unit_t t[MAX_TAPE_UNIT]; |
| 55 | }; |
| 56 | |
| 57 | struct ti990_tape_t |
| 58 | { |
| 59 | int dummy; |
| 60 | }; |
| 61 | |
| 62 | enum |
| 63 | { |
| 64 | w0_offline = 0x8000, |
| 65 | w0_BOT = 0x4000, |
| 66 | w0_EOR = 0x2000, |
| 67 | w0_EOF = 0x1000, |
| 68 | w0_EOT = 0x0800, |
| 69 | w0_write_ring = 0x0400, |
| 70 | w0_tape_rewinding = 0x0200, |
| 71 | w0_command_timeout = 0x0100, |
| 72 | |
| 73 | w0_rewind_status = 0x00f0, |
| 74 | w0_rewind_mask = 0x000f, |
| 75 | |
| 76 | w6_unit0_sel = 0x8000, |
| 77 | w6_unit1_sel = 0x4000, |
| 78 | w6_unit2_sel = 0x2000, |
| 79 | w6_unit3_sel = 0x1000, |
| 80 | w6_command = 0x0f00, |
| 81 | |
| 82 | w7_idle = 0x8000, |
| 83 | w7_complete = 0x4000, |
| 84 | w7_error = 0x2000, |
| 85 | w7_int_enable = 0x1000, |
| 86 | w7_PE_format = 0x0200, |
| 87 | w7_abnormal_completion = 0x0100, |
| 88 | w7_interface_parity_err = 0x0080, |
| 89 | w7_err_correction_enabled = 0x0040, |
| 90 | w7_hard_error = 0x0020, |
| 91 | w7_tiline_parity_err = 0x0010, |
| 92 | w7_tiline_timing_err = 0x0008, |
| 93 | w7_tiline_timeout_err = 0x0004, |
| 94 | /*w7_format_error = 0x0002,*/ |
| 95 | w7_tape_error = 0x0001 |
| 96 | }; |
| 97 | |
| 98 | static const UINT16 w_mask[8] = |
| 99 | { |
| 100 | 0x000f, /* Controllers should prevent overwriting of w0 status bits, and I know |
| 101 | that some controllers do so. */ |
| 102 | 0xffff, |
| 103 | 0xffff, |
| 104 | 0xffff, |
| 105 | 0xffff, |
| 106 | 0xffff, |
| 107 | 0xffff, |
| 108 | 0xf3ff /* Don't overwrite reserved bits */ |
| 109 | }; |
| 110 | |
| 111 | static int tape_get_id(device_t *image) |
| 112 | { |
| 113 | int drive =0; |
| 114 | if (strcmp(image->tag(), ":tape0") == 0) drive = 0; |
| 115 | if (strcmp(image->tag(), ":tape1") == 0) drive = 1; |
| 116 | if (strcmp(image->tag(), ":tape2") == 0) drive = 2; |
| 117 | if (strcmp(image->tag(), ":tape3") == 0) drive = 3; |
| 118 | return drive; |
| 119 | } |
| 120 | |
| 121 | /***************************************************************************** |
| 122 | INLINE FUNCTIONS |
| 123 | *****************************************************************************/ |
| 124 | INLINE tap_990_t *get_safe_token(device_t *device) |
| 125 | { |
| 126 | assert(device != NULL); |
| 127 | assert(device->type() == TI990_TAPE_CTRL); |
| 128 | |
| 129 | return (tap_990_t *)downcast<tap_990_device *>(device)->token(); |
| 130 | } |
| 131 | |
| 132 | |
| 133 | /* |
| 134 | Parse the tape select lines, and return the corresponding tape unit. |
| 135 | (-1 if none) |
| 136 | */ |
| 137 | static int cur_tape_unit(device_t *device) |
| 138 | { |
| 139 | int reply; |
| 140 | tap_990_t *tpc = get_safe_token(device); |
| 141 | |
| 142 | if (tpc->w[6] & w6_unit0_sel) |
| 143 | reply = 0; |
| 144 | else if (tpc->w[6] & w6_unit1_sel) |
| 145 | reply = 1; |
| 146 | else if (tpc->w[6] & w6_unit2_sel) |
| 147 | reply = 2; |
| 148 | else if (tpc->w[6] & w6_unit3_sel) |
| 149 | reply = 3; |
| 150 | else |
| 151 | reply = -1; |
| 152 | |
| 153 | if (reply >= MAX_TAPE_UNIT) |
| 154 | reply = -1; |
| 155 | |
| 156 | return reply; |
| 157 | } |
| 158 | |
| 159 | /* |
| 160 | Update interrupt state |
| 161 | */ |
| 162 | static void update_interrupt(device_t *device) |
| 163 | { |
| 164 | tap_990_t *tpc = get_safe_token(device); |
| 165 | if (tpc->intf->interrupt_callback) |
| 166 | (*tpc->intf->interrupt_callback)(device->machine(), (tpc->w[7] & w7_idle) |
| 167 | && (((tpc->w[7] & w7_int_enable) && (tpc->w[7] & (w7_complete | w7_error))) |
| 168 | || ((tpc->w[0] & ~(tpc->w[0] >> 4)) & w0_rewind_mask))); |
| 169 | } |
| 170 | |
| 171 | /* |
| 172 | Handle the read binary forward command: read the next record on tape. |
| 173 | */ |
| 174 | static void cmd_read_binary_forward(device_t *device) |
| 175 | { |
| 176 | UINT8 buffer[256]; |
| 177 | int reclen; |
| 178 | |
| 179 | int dma_address; |
| 180 | int char_count; |
| 181 | int read_offset; |
| 182 | |
| 183 | int rec_count = 0; |
| 184 | int chunk_len; |
| 185 | int bytes_to_read; |
| 186 | int bytes_read; |
| 187 | int i; |
| 188 | tap_990_t *tpc = get_safe_token(device); |
| 189 | int tap_sel = cur_tape_unit(device); |
| 190 | |
| 191 | if (tap_sel == -1) |
| 192 | { |
| 193 | /* No idea what to report... */ |
| 194 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 195 | update_interrupt(device); |
| 196 | return; |
| 197 | } |
| 198 | else if (! tpc->t[tap_sel].img->exists()) |
| 199 | { /* offline */ |
| 200 | tpc->w[0] |= w0_offline; |
| 201 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 202 | update_interrupt(device); |
| 203 | return; |
| 204 | } |
| 205 | #if 0 |
| 206 | else if (0) |
| 207 | { /* rewind in progress */ |
| 208 | tpc->w[0] |= 0x80 >> tap_sel; |
| 209 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 210 | update_interrupt(device); |
| 211 | return; |
| 212 | } |
| 213 | #endif |
| 214 | |
| 215 | tpc->t[tap_sel].bot = 0; |
| 216 | |
| 217 | dma_address = ((((int) tpc->w[6]) << 16) | tpc->w[5]) & 0x1ffffe; |
| 218 | char_count = tpc->w[4]; |
| 219 | read_offset = tpc->w[3]; |
| 220 | |
| 221 | bytes_read = tpc->t[tap_sel].img->fread(buffer, 4); |
| 222 | if (bytes_read != 4) |
| 223 | { |
| 224 | if (bytes_read == 0) |
| 225 | { /* legitimate EOF */ |
| 226 | tpc->t[tap_sel].eot = 1; |
| 227 | tpc->w[0] |= w0_EOT; /* or should it be w0_command_timeout? */ |
| 228 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 229 | update_interrupt(device); |
| 230 | goto update_registers; |
| 231 | } |
| 232 | else |
| 233 | { /* illegitimate EOF */ |
| 234 | /* No idea what to report... */ |
| 235 | /* eject tape to avoid catastrophes */ |
| 236 | logerror("Tape error\n"); |
| 237 | tpc->t[tap_sel].img->unload(); |
| 238 | tpc->w[0] |= w0_offline; |
| 239 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 240 | update_interrupt(device); |
| 241 | goto update_registers; |
| 242 | } |
| 243 | } |
| 244 | reclen = (((int) buffer[1]) << 8) | buffer[0]; |
| 245 | if (buffer[2] || buffer[3]) |
| 246 | { /* no idea what these bytes mean */ |
| 247 | logerror("Tape error\n"); |
| 248 | logerror("Tape format looks gooofy\n"); |
| 249 | /* eject tape to avoid catastrophes */ |
| 250 | tpc->t[tap_sel].img->unload(); |
| 251 | tpc->w[0] |= w0_offline; |
| 252 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 253 | update_interrupt(device); |
| 254 | goto update_registers; |
| 255 | } |
| 256 | |
| 257 | /* test for EOF mark */ |
| 258 | if (reclen == 0) |
| 259 | { |
| 260 | logerror("read binary forward: found EOF, requested %d\n", char_count); |
| 261 | tpc->w[0] |= w0_EOF; |
| 262 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 263 | update_interrupt(device); |
| 264 | goto update_registers; |
| 265 | } |
| 266 | |
| 267 | logerror("read binary forward: rec length %d, requested %d\n", reclen, char_count); |
| 268 | |
| 269 | rec_count = reclen; |
| 270 | |
| 271 | /* skip up to read_offset bytes */ |
| 272 | chunk_len = (read_offset > rec_count) ? rec_count : read_offset; |
| 273 | |
| 274 | if (tpc->t[tap_sel].img->fseek(chunk_len, SEEK_CUR)) |
| 275 | { /* eject tape */ |
| 276 | logerror("Tape error\n"); |
| 277 | tpc->t[tap_sel].img->unload(); |
| 278 | tpc->w[0] |= w0_offline; |
| 279 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 280 | update_interrupt(device); |
| 281 | goto update_registers; |
| 282 | } |
| 283 | |
| 284 | rec_count -= chunk_len; |
| 285 | read_offset -= chunk_len; |
| 286 | if (read_offset) |
| 287 | { |
| 288 | tpc->w[0] |= w0_EOR; |
| 289 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 290 | update_interrupt(device); |
| 291 | goto skip_trailer; |
| 292 | } |
| 293 | |
| 294 | /* read up to char_count bytes */ |
| 295 | chunk_len = (char_count > rec_count) ? rec_count : char_count; |
| 296 | |
| 297 | for (; chunk_len>0; ) |
| 298 | { |
| 299 | bytes_to_read = (chunk_len < sizeof(buffer)) ? chunk_len : sizeof(buffer); |
| 300 | bytes_read = tpc->t[tap_sel].img->fread(buffer, bytes_to_read); |
| 301 | |
| 302 | if (bytes_read & 1) |
| 303 | { |
| 304 | buffer[bytes_read] = 0xff; |
| 305 | } |
| 306 | |
| 307 | /* DMA */ |
| 308 | for (i=0; i<bytes_read; i+=2) |
| 309 | { |
| 310 | device->machine().device("maincpu")->memory().space(AS_PROGRAM).write_word(dma_address, (((int) buffer[i]) << 8) | buffer[i+1]); |
| 311 | dma_address = (dma_address + 2) & 0x1ffffe; |
| 312 | } |
| 313 | |
| 314 | rec_count -= bytes_read; |
| 315 | char_count -= bytes_read; |
| 316 | chunk_len -= bytes_read; |
| 317 | |
| 318 | if (bytes_read != bytes_to_read) |
| 319 | { /* eject tape */ |
| 320 | logerror("Tape error\n"); |
| 321 | tpc->t[tap_sel].img->unload(); |
| 322 | tpc->w[0] |= w0_offline; |
| 323 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 324 | update_interrupt(device); |
| 325 | goto update_registers; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | if (char_count) |
| 330 | { |
| 331 | tpc->w[0] |= w0_EOR; |
| 332 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 333 | update_interrupt(device); |
| 334 | goto skip_trailer; |
| 335 | } |
| 336 | |
| 337 | if (rec_count) |
| 338 | { /* skip end of record */ |
| 339 | if (tpc->t[tap_sel].img->fseek(rec_count, SEEK_CUR)) |
| 340 | { /* eject tape */ |
| 341 | logerror("Tape error\n"); |
| 342 | tpc->t[tap_sel].img->unload(); |
| 343 | tpc->w[0] |= w0_offline; |
| 344 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 345 | update_interrupt(device); |
| 346 | goto update_registers; |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | skip_trailer: |
| 351 | if (tpc->t[tap_sel].img->fread(buffer, 4) != 4) |
| 352 | { /* eject tape */ |
| 353 | logerror("Tape error\n"); |
| 354 | tpc->t[tap_sel].img->unload(); |
| 355 | tpc->w[0] |= w0_offline; |
| 356 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 357 | update_interrupt(device); |
| 358 | goto update_registers; |
| 359 | } |
| 360 | |
| 361 | if (reclen != ((((int) buffer[1]) << 8) | buffer[0])) |
| 362 | { /* eject tape */ |
| 363 | logerror("Tape error\n"); |
| 364 | tpc->t[tap_sel].img->unload(); |
| 365 | tpc->w[0] |= w0_offline; |
| 366 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 367 | update_interrupt(device); |
| 368 | goto update_registers; |
| 369 | } |
| 370 | if (buffer[2] || buffer[3]) |
| 371 | { /* no idea what these bytes mean */ |
| 372 | logerror("Tape error\n"); |
| 373 | logerror("Tape format looks gooofy\n"); |
| 374 | /* eject tape to avoid catastrophes */ |
| 375 | tpc->t[tap_sel].img->unload(); |
| 376 | tpc->w[0] |= w0_offline; |
| 377 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 378 | update_interrupt(device); |
| 379 | goto update_registers; |
| 380 | } |
| 381 | |
| 382 | if (! (tpc->w[7] & w7_error)) |
| 383 | { |
| 384 | tpc->w[7] |= w7_idle | w7_complete; |
| 385 | update_interrupt(device); |
| 386 | } |
| 387 | |
| 388 | update_registers: |
| 389 | tpc->w[1] = rec_count & 0xffff; |
| 390 | tpc->w[2] = (rec_count >> 8) & 0xff; |
| 391 | tpc->w[3] = read_offset; |
| 392 | tpc->w[4] = char_count; |
| 393 | tpc->w[5] = (dma_address >> 1) & 0xffff; |
| 394 | tpc->w[6] = (tpc->w[6] & 0xffe0) | ((dma_address >> 17) & 0xf); |
| 395 | } |
| 396 | |
| 397 | /* |
| 398 | Handle the record skip forward command: skip a specified number of records. |
| 399 | */ |
| 400 | static void cmd_record_skip_forward(device_t *device) |
| 401 | { |
| 402 | UINT8 buffer[4]; |
| 403 | int reclen; |
| 404 | |
| 405 | int record_count; |
| 406 | int bytes_read; |
| 407 | tap_990_t *tpc = get_safe_token(device); |
| 408 | int tap_sel = cur_tape_unit(device); |
| 409 | |
| 410 | if (tap_sel == -1) |
| 411 | { |
| 412 | /* No idea what to report... */ |
| 413 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 414 | update_interrupt(device); |
| 415 | return; |
| 416 | } |
| 417 | else if (! tpc->t[tap_sel].img->exists()) |
| 418 | { /* offline */ |
| 419 | tpc->w[0] |= w0_offline; |
| 420 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 421 | update_interrupt(device); |
| 422 | return; |
| 423 | } |
| 424 | #if 0 |
| 425 | else if (0) |
| 426 | { /* rewind in progress */ |
| 427 | tpc->w[0] |= 0x80 >> tap_sel; |
| 428 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 429 | update_interrupt(device); |
| 430 | return; |
| 431 | } |
| 432 | #endif |
| 433 | |
| 434 | record_count = tpc->w[4]; |
| 435 | |
| 436 | if (record_count) |
| 437 | tpc->t[tap_sel].bot = 0; |
| 438 | |
| 439 | while (record_count > 0) |
| 440 | { |
| 441 | bytes_read = tpc->t[tap_sel].img->fread(buffer, 4); |
| 442 | if (bytes_read != 4) |
| 443 | { |
| 444 | if (bytes_read == 0) |
| 445 | { /* legitimate EOF */ |
| 446 | tpc->t[tap_sel].eot = 1; |
| 447 | tpc->w[0] |= w0_EOT; /* or should it be w0_command_timeout? */ |
| 448 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 449 | update_interrupt(device); |
| 450 | goto update_registers; |
| 451 | } |
| 452 | else |
| 453 | { /* illegitimate EOF */ |
| 454 | /* No idea what to report... */ |
| 455 | /* eject tape to avoid catastrophes */ |
| 456 | tpc->t[tap_sel].img->unload(); |
| 457 | tpc->w[0] |= w0_offline; |
| 458 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 459 | update_interrupt(device); |
| 460 | goto update_registers; |
| 461 | } |
| 462 | } |
| 463 | reclen = (((int) buffer[1]) << 8) | buffer[0]; |
| 464 | if (buffer[2] || buffer[3]) |
| 465 | { /* no idea what these bytes mean */ |
| 466 | logerror("Tape format looks gooofy\n"); |
| 467 | /* eject tape to avoid catastrophes */ |
| 468 | tpc->t[tap_sel].img->unload(); |
| 469 | tpc->w[0] |= w0_offline; |
| 470 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 471 | update_interrupt(device); |
| 472 | goto update_registers; |
| 473 | } |
| 474 | |
| 475 | /* test for EOF mark */ |
| 476 | if (reclen == 0) |
| 477 | { |
| 478 | logerror("record skip forward: found EOF\n"); |
| 479 | tpc->w[0] |= w0_EOF; |
| 480 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 481 | update_interrupt(device); |
| 482 | goto update_registers; |
| 483 | } |
| 484 | |
| 485 | /* skip record data */ |
| 486 | if (tpc->t[tap_sel].img->fseek(reclen, SEEK_CUR)) |
| 487 | { /* eject tape */ |
| 488 | tpc->t[tap_sel].img->unload(); |
| 489 | tpc->w[0] |= w0_offline; |
| 490 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 491 | update_interrupt(device); |
| 492 | goto update_registers; |
| 493 | } |
| 494 | |
| 495 | if (tpc->t[tap_sel].img->fread(buffer, 4) != 4) |
| 496 | { /* eject tape */ |
| 497 | tpc->t[tap_sel].img->unload(); |
| 498 | tpc->w[0] |= w0_offline; |
| 499 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 500 | update_interrupt(device); |
| 501 | goto update_registers; |
| 502 | } |
| 503 | |
| 504 | if (reclen != ((((int) buffer[1]) << 8) | buffer[0])) |
| 505 | { /* eject tape */ |
| 506 | tpc->t[tap_sel].img->unload(); |
| 507 | tpc->w[0] |= w0_offline; |
| 508 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 509 | update_interrupt(device); |
| 510 | goto update_registers; |
| 511 | } |
| 512 | if (buffer[2] || buffer[3]) |
| 513 | { /* no idea what these bytes mean */ |
| 514 | logerror("Tape format looks gooofy\n"); |
| 515 | /* eject tape to avoid catastrophes */ |
| 516 | tpc->t[tap_sel].img->unload(); |
| 517 | tpc->w[0] |= w0_offline; |
| 518 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 519 | update_interrupt(device); |
| 520 | goto update_registers; |
| 521 | } |
| 522 | |
| 523 | record_count--; |
| 524 | } |
| 525 | |
| 526 | tpc->w[7] |= w7_idle | w7_complete; |
| 527 | update_interrupt(device); |
| 528 | |
| 529 | update_registers: |
| 530 | tpc->w[4] = record_count; |
| 531 | } |
| 532 | |
| 533 | /* |
| 534 | Handle the record skip reverse command: skip a specified number of records backwards. |
| 535 | */ |
| 536 | static void cmd_record_skip_reverse(device_t *device) |
| 537 | { |
| 538 | UINT8 buffer[4]; |
| 539 | int reclen; |
| 540 | |
| 541 | int record_count; |
| 542 | |
| 543 | int bytes_read; |
| 544 | tap_990_t *tpc = get_safe_token(device); |
| 545 | int tap_sel = cur_tape_unit(device); |
| 546 | |
| 547 | if (tap_sel == -1) |
| 548 | { |
| 549 | /* No idea what to report... */ |
| 550 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 551 | update_interrupt(device); |
| 552 | return; |
| 553 | } |
| 554 | else if (! tpc->t[tap_sel].img->exists()) |
| 555 | { /* offline */ |
| 556 | tpc->w[0] |= w0_offline; |
| 557 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 558 | update_interrupt(device); |
| 559 | return; |
| 560 | } |
| 561 | #if 0 |
| 562 | else if (0) |
| 563 | { /* rewind in progress */ |
| 564 | tpc->w[0] |= 0x80 >> tap_sel; |
| 565 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 566 | update_interrupt(device); |
| 567 | return; |
| 568 | } |
| 569 | #endif |
| 570 | |
| 571 | record_count = tpc->w[4]; |
| 572 | |
| 573 | if (record_count) |
| 574 | tpc->t[tap_sel].eot = 0; |
| 575 | |
| 576 | while (record_count > 0) |
| 577 | { |
| 578 | if (tpc->t[tap_sel].img->ftell() == 0) |
| 579 | { /* bot */ |
| 580 | tpc->t[tap_sel].bot = 1; |
| 581 | tpc->w[0] |= w0_BOT; |
| 582 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 583 | update_interrupt(device); |
| 584 | goto update_registers; |
| 585 | } |
| 586 | if (tpc->t[tap_sel].img->fseek(-4, SEEK_CUR)) |
| 587 | { /* eject tape */ |
| 588 | tpc->t[tap_sel].img->unload(); |
| 589 | tpc->w[0] |= w0_offline; |
| 590 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 591 | update_interrupt(device); |
| 592 | goto update_registers; |
| 593 | } |
| 594 | bytes_read = tpc->t[tap_sel].img->fread(buffer, 4); |
| 595 | if (bytes_read != 4) |
| 596 | { |
| 597 | /* illegitimate EOF */ |
| 598 | /* No idea what to report... */ |
| 599 | /* eject tape to avoid catastrophes */ |
| 600 | tpc->t[tap_sel].img->unload(); |
| 601 | tpc->w[0] |= w0_offline; |
| 602 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 603 | update_interrupt(device); |
| 604 | goto update_registers; |
| 605 | } |
| 606 | reclen = (((int) buffer[1]) << 8) | buffer[0]; |
| 607 | if (buffer[2] || buffer[3]) |
| 608 | { /* no idea what these bytes mean */ |
| 609 | logerror("Tape format looks gooofy\n"); |
| 610 | /* eject tape to avoid catastrophes */ |
| 611 | tpc->t[tap_sel].img->unload(); |
| 612 | tpc->w[0] |= w0_offline; |
| 613 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 614 | update_interrupt(device); |
| 615 | goto update_registers; |
| 616 | } |
| 617 | |
| 618 | /* look for EOF mark */ |
| 619 | if (reclen == 0) |
| 620 | { |
| 621 | logerror("record skip reverse: found EOF\n"); |
| 622 | if (tpc->t[tap_sel].img->fseek(-4, SEEK_CUR)) |
| 623 | { /* eject tape */ |
| 624 | tpc->t[tap_sel].img->unload(); |
| 625 | tpc->w[0] |= w0_offline; |
| 626 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 627 | update_interrupt(device); |
| 628 | goto update_registers; |
| 629 | } |
| 630 | tpc->w[0] |= w0_EOF; |
| 631 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 632 | update_interrupt(device); |
| 633 | goto update_registers; |
| 634 | } |
| 635 | |
| 636 | if (tpc->t[tap_sel].img->fseek(-reclen-8, SEEK_CUR)) |
| 637 | { /* eject tape */ |
| 638 | tpc->t[tap_sel].img->unload(); |
| 639 | tpc->w[0] |= w0_offline; |
| 640 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 641 | update_interrupt(device); |
| 642 | goto update_registers; |
| 643 | } |
| 644 | |
| 645 | if (tpc->t[tap_sel].img->fread(buffer, 4) != 4) |
| 646 | { /* eject tape */ |
| 647 | tpc->t[tap_sel].img->unload(); |
| 648 | tpc->w[0] |= w0_offline; |
| 649 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 650 | update_interrupt(device); |
| 651 | goto update_registers; |
| 652 | } |
| 653 | if (reclen != ((((int) buffer[1]) << 8) | buffer[0])) |
| 654 | { /* eject tape */ |
| 655 | tpc->t[tap_sel].img->unload(); |
| 656 | tpc->w[0] |= w0_offline; |
| 657 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 658 | update_interrupt(device); |
| 659 | goto update_registers; |
| 660 | } |
| 661 | if (buffer[2] || buffer[3]) |
| 662 | { /* no idea what these bytes mean */ |
| 663 | logerror("Tape format looks gooofy\n"); |
| 664 | /* eject tape to avoid catastrophes */ |
| 665 | tpc->t[tap_sel].img->unload(); |
| 666 | tpc->w[0] |= w0_offline; |
| 667 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 668 | update_interrupt(device); |
| 669 | goto update_registers; |
| 670 | } |
| 671 | |
| 672 | if (tpc->t[tap_sel].img->fseek(-4, SEEK_CUR)) |
| 673 | { /* eject tape */ |
| 674 | tpc->t[tap_sel].img->unload(); |
| 675 | tpc->w[0] |= w0_offline; |
| 676 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 677 | update_interrupt(device); |
| 678 | goto update_registers; |
| 679 | } |
| 680 | |
| 681 | record_count--; |
| 682 | } |
| 683 | |
| 684 | tpc->w[7] |= w7_idle | w7_complete; |
| 685 | update_interrupt(device); |
| 686 | |
| 687 | update_registers: |
| 688 | tpc->w[4] = record_count; |
| 689 | } |
| 690 | |
| 691 | /* |
| 692 | Handle the rewind command: rewind to BOT. |
| 693 | */ |
| 694 | static void cmd_rewind(device_t *device) |
| 695 | { |
| 696 | tap_990_t *tpc = get_safe_token(device); |
| 697 | int tap_sel = cur_tape_unit(device); |
| 698 | |
| 699 | if (tap_sel == -1) |
| 700 | { |
| 701 | /* No idea what to report... */ |
| 702 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 703 | update_interrupt(device); |
| 704 | return; |
| 705 | } |
| 706 | else if (! tpc->t[tap_sel].img->exists()) |
| 707 | { /* offline */ |
| 708 | tpc->w[0] |= w0_offline; |
| 709 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 710 | update_interrupt(device); |
| 711 | return; |
| 712 | } |
| 713 | #if 0 |
| 714 | else if (0) |
| 715 | { /* rewind in progress */ |
| 716 | tpc->w[0] |= 0x80 >> tap_sel; |
| 717 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 718 | update_interrupt(device); |
| 719 | return; |
| 720 | } |
| 721 | #endif |
| 722 | |
| 723 | tpc->t[tap_sel].eot = 0; |
| 724 | |
| 725 | if (tpc->t[tap_sel].img->fseek(0, SEEK_SET)) |
| 726 | { /* eject tape */ |
| 727 | tpc->t[tap_sel].img->unload(); |
| 728 | tpc->w[0] |= w0_offline; |
| 729 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 730 | update_interrupt(device); |
| 731 | return; |
| 732 | } |
| 733 | tpc->t[tap_sel].bot = 1; |
| 734 | |
| 735 | tpc->w[7] |= w7_idle | w7_complete; |
| 736 | update_interrupt(device); |
| 737 | } |
| 738 | |
| 739 | /* |
| 740 | Handle the rewind and offline command: disable the tape unit. |
| 741 | */ |
| 742 | static void cmd_rewind_and_offline(device_t *device) |
| 743 | { |
| 744 | tap_990_t *tpc = get_safe_token(device); |
| 745 | int tap_sel = cur_tape_unit(device); |
| 746 | |
| 747 | if (tap_sel == -1) |
| 748 | { |
| 749 | /* No idea what to report... */ |
| 750 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 751 | update_interrupt(device); |
| 752 | return; |
| 753 | } |
| 754 | else if (! tpc->t[tap_sel].img->exists()) |
| 755 | { /* offline */ |
| 756 | tpc->w[0] |= w0_offline; |
| 757 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 758 | update_interrupt(device); |
| 759 | return; |
| 760 | } |
| 761 | #if 0 |
| 762 | else if (0) |
| 763 | { /* rewind in progress */ |
| 764 | tpc->w[0] |= 0x80 >> tap_sel; |
| 765 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 766 | update_interrupt(device); |
| 767 | return; |
| 768 | } |
| 769 | #endif |
| 770 | |
| 771 | /* eject tape */ |
| 772 | tpc->t[tap_sel].img->unload(); |
| 773 | |
| 774 | tpc->w[7] |= w7_idle | w7_complete; |
| 775 | update_interrupt(device); |
| 776 | } |
| 777 | |
| 778 | /* |
| 779 | Handle the read transport status command: return the current tape status. |
| 780 | */ |
| 781 | static void read_transport_status(device_t *device) |
| 782 | { |
| 783 | tap_990_t *tpc = get_safe_token(device); |
| 784 | int tap_sel = cur_tape_unit(device); |
| 785 | |
| 786 | if (tap_sel == -1) |
| 787 | { |
| 788 | /* No idea what to report... */ |
| 789 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 790 | update_interrupt(device); |
| 791 | } |
| 792 | else if (! tpc->t[tap_sel].img->exists()) |
| 793 | { /* offline */ |
| 794 | tpc->w[0] |= w0_offline; |
| 795 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 796 | update_interrupt(device); |
| 797 | } |
| 798 | #if 0 |
| 799 | else if (0) |
| 800 | { /* rewind in progress */ |
| 801 | tpc->w[0] |= /*...*/; |
| 802 | tpc->w[7] |= w7_idle | w7_error | w7_tape_error; |
| 803 | update_interrupt(device); |
| 804 | } |
| 805 | #endif |
| 806 | else |
| 807 | { /* no particular error condition */ |
| 808 | if (tpc->t[tap_sel].bot) |
| 809 | tpc->w[0] |= w0_BOT; |
| 810 | if (tpc->t[tap_sel].eot) |
| 811 | tpc->w[0] |= w0_EOT; |
| 812 | if (tpc->t[tap_sel].wp) |
| 813 | tpc->w[0] |= w0_write_ring; |
| 814 | tpc->w[7] |= w7_idle | w7_complete; |
| 815 | update_interrupt(device); |
| 816 | } |
| 817 | } |
| 818 | |
| 819 | /* |
| 820 | Parse command code and execute the command. |
| 821 | */ |
| 822 | static void execute_command(device_t *device) |
| 823 | { |
| 824 | /* hack */ |
| 825 | tap_990_t *tpc = get_safe_token(device); |
| 826 | tpc->w[0] &= 0xff; |
| 827 | |
| 828 | switch ((tpc->w[6] & w6_command) >> 8) |
| 829 | { |
| 830 | case 0x00: |
| 831 | case 0x0C: |
| 832 | case 0x0E: |
| 833 | /* NOP */ |
| 834 | logerror("NOP\n"); |
| 835 | tpc->w[7] |= w7_idle | w7_complete; |
| 836 | update_interrupt(device); |
| 837 | break; |
| 838 | case 0x01: |
| 839 | /* buffer sync: means nothing under emulation */ |
| 840 | logerror("buffer sync\n"); |
| 841 | tpc->w[7] |= w7_idle | w7_complete; |
| 842 | update_interrupt(device); |
| 843 | break; |
| 844 | case 0x02: |
| 845 | /* write EOF - not emulated */ |
| 846 | logerror("write EOF\n"); |
| 847 | /* ... */ |
| 848 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 849 | update_interrupt(device); |
| 850 | break; |
| 851 | case 0x03: |
| 852 | /* record skip reverse - not fully tested */ |
| 853 | logerror("record skip reverse\n"); |
| 854 | cmd_record_skip_reverse(device); |
| 855 | break; |
| 856 | case 0x04: |
| 857 | /* read binary forward */ |
| 858 | logerror("read binary forward\n"); |
| 859 | cmd_read_binary_forward(device); |
| 860 | break; |
| 861 | case 0x05: |
| 862 | /* record skip forward - not tested */ |
| 863 | logerror("record skip forward\n"); |
| 864 | cmd_record_skip_forward(device); |
| 865 | break; |
| 866 | case 0x06: |
| 867 | /* write binary forward - not emulated */ |
| 868 | logerror("write binary forward\n"); |
| 869 | /* ... */ |
| 870 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 871 | update_interrupt(device); |
| 872 | break; |
| 873 | case 0x07: |
| 874 | /* erase - not emulated */ |
| 875 | logerror("erase\n"); |
| 876 | /* ... */ |
| 877 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 878 | update_interrupt(device); |
| 879 | break; |
| 880 | case 0x08: |
| 881 | case 0x09: |
| 882 | /* read transport status */ |
| 883 | logerror("read transport status\n"); |
| 884 | read_transport_status(device); |
| 885 | break; |
| 886 | case 0x0A: |
| 887 | /* rewind - not tested */ |
| 888 | logerror("rewind\n"); |
| 889 | cmd_rewind(device); |
| 890 | break; |
| 891 | case 0x0B: |
| 892 | /* rewind and offline - not tested */ |
| 893 | logerror("rewind and offline\n"); |
| 894 | cmd_rewind_and_offline(device); |
| 895 | break; |
| 896 | case 0x0F: |
| 897 | /* extended control and status - not emulated */ |
| 898 | logerror("extended control and status\n"); |
| 899 | /* ... */ |
| 900 | tpc->w[7] |= w7_idle | w7_error | w7_hard_error; |
| 901 | update_interrupt(device); |
| 902 | break; |
| 903 | } |
| 904 | } |
| 905 | |
| 906 | |
| 907 | /* |
| 908 | Read one register in TPCS space |
| 909 | */ |
| 910 | READ16_DEVICE_HANDLER(ti990_tpc_r) |
| 911 | { |
| 912 | tap_990_t *tpc = get_safe_token(device); |
| 913 | if (offset < 8) |
| 914 | return tpc->w[offset]; |
| 915 | else |
| 916 | return 0; |
| 917 | } |
| 918 | |
| 919 | /* |
| 920 | Write one register in TPCS space. Execute command if w7_idle is cleared. |
| 921 | */ |
| 922 | WRITE16_DEVICE_HANDLER(ti990_tpc_w) |
| 923 | { |
| 924 | tap_990_t *tpc = get_safe_token(device); |
| 925 | if (offset < 8) |
| 926 | { |
| 927 | /* write protect if a command is in progress */ |
| 928 | if (tpc->w[7] & w7_idle) |
| 929 | { |
| 930 | UINT16 old_data = tpc->w[offset]; |
| 931 | |
| 932 | /* Only write writable bits AND honor byte accesses (ha!) */ |
| 933 | tpc->w[offset] = (tpc->w[offset] & ((~w_mask[offset]) | mem_mask)) | (data & w_mask[offset] & ~mem_mask); |
| 934 | |
| 935 | if ((offset == 0) || (offset == 7)) |
| 936 | update_interrupt(device); |
| 937 | |
| 938 | if ((offset == 7) && (old_data & w7_idle) && ! (data & w7_idle)) |
| 939 | { /* idle has been cleared: start command execution */ |
| 940 | execute_command(device); |
| 941 | } |
| 942 | } |
| 943 | } |
| 944 | } |
| 945 | |
| 946 | class ti990_tape_image_device : public device_t, |
| 947 | public device_image_interface |
| 948 | { |
| 949 | public: |
| 950 | // construction/destruction |
| 951 | ti990_tape_image_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock); |
| 952 | |
| 953 | // image-level overrides |
| 954 | virtual iodevice_t image_type() const { return IO_MAGTAPE; } |
| 955 | |
| 956 | virtual bool is_readable() const { return 1; } |
| 957 | virtual bool is_writeable() const { return 1; } |
| 958 | virtual bool is_creatable() const { return 1; } |
| 959 | virtual bool must_be_loaded() const { return 0; } |
| 960 | virtual bool is_reset_on_load() const { return 0; } |
| 961 | virtual const char *image_interface() const { return NULL; } |
| 962 | virtual const char *file_extensions() const { return "tap"; } |
| 963 | virtual const option_guide *create_option_guide() const { return NULL; } |
| 964 | |
| 965 | virtual bool call_load(); |
| 966 | virtual void call_unload(); |
| 967 | protected: |
| 968 | // device-level overrides |
| 969 | virtual void device_config_complete(); |
| 970 | virtual void device_start(); |
| 971 | }; |
| 972 | |
| 973 | const device_type TI990_TAPE = &device_creator<ti990_tape_image_device>; |
| 974 | |
| 975 | ti990_tape_image_device::ti990_tape_image_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 976 | : device_t(mconfig, TI990_TAPE, "TI990 Magnetic Tape", tag, owner, clock, "ti990_tape_image", __FILE__), |
| 977 | device_image_interface(mconfig, *this) |
| 978 | { |
| 979 | } |
| 980 | |
| 981 | void ti990_tape_image_device::device_config_complete() |
| 982 | { |
| 983 | update_names(); |
| 984 | } |
| 985 | |
| 986 | void ti990_tape_image_device::device_start() |
| 987 | { |
| 988 | tape_unit_t *t; |
| 989 | tap_990_t *tpc = get_safe_token(owner()); |
| 990 | int id = tape_get_id(this); |
| 991 | |
| 992 | t = &tpc->t[id]; |
| 993 | memset(t, 0, sizeof(*t)); |
| 994 | |
| 995 | t->img = this; |
| 996 | t->wp = 1; |
| 997 | t->bot = 0; |
| 998 | t->eot = 0; |
| 999 | } |
| 1000 | |
| 1001 | /* |
| 1002 | Open a tape image |
| 1003 | */ |
| 1004 | bool ti990_tape_image_device::call_load() |
| 1005 | { |
| 1006 | tape_unit_t *t; |
| 1007 | tap_990_t *tpc = get_safe_token(owner()); |
| 1008 | int id = tape_get_id(this); |
| 1009 | |
| 1010 | t = &tpc->t[id]; |
| 1011 | memset(t, 0, sizeof(*t)); |
| 1012 | |
| 1013 | /* tell whether the image is writable */ |
| 1014 | t->wp = is_readonly(); |
| 1015 | |
| 1016 | t->bot = 1; |
| 1017 | |
| 1018 | return IMAGE_INIT_PASS; |
| 1019 | } |
| 1020 | |
| 1021 | /* |
| 1022 | Close a tape image |
| 1023 | */ |
| 1024 | void ti990_tape_image_device::call_unload() |
| 1025 | { |
| 1026 | tape_unit_t *t; |
| 1027 | tap_990_t *tpc = get_safe_token(owner()); |
| 1028 | int id = tape_get_id(this); |
| 1029 | |
| 1030 | t = &tpc->t[id]; |
| 1031 | t->wp = 1; |
| 1032 | t->bot = 0; |
| 1033 | t->eot = 0; |
| 1034 | } |
| 1035 | |
| 1036 | #define MCFG_TI990_TAPE_ADD(_tag) \ |
| 1037 | MCFG_DEVICE_ADD((_tag), TI990_TAPE, 0) |
| 1038 | |
| 1039 | |
| 1040 | static MACHINE_CONFIG_FRAGMENT( tap_990 ) |
| 1041 | MCFG_TI990_TAPE_ADD("tape0") |
| 1042 | MCFG_TI990_TAPE_ADD("tape1") |
| 1043 | MCFG_TI990_TAPE_ADD("tape2") |
| 1044 | MCFG_TI990_TAPE_ADD("tape3") |
| 1045 | MACHINE_CONFIG_END |
| 1046 | |
| 1047 | /* |
| 1048 | Init the tape controller core |
| 1049 | */ |
| 1050 | static DEVICE_START(tap_990) |
| 1051 | { |
| 1052 | tap_990_t *tpc = get_safe_token(device); |
| 1053 | /* verify that we have an interface assigned */ |
| 1054 | assert(device->static_config() != NULL); |
| 1055 | |
| 1056 | /* copy interface pointer */ |
| 1057 | tpc->intf = (const ti990_tpc_interface*)device->static_config(); |
| 1058 | |
| 1059 | memset(tpc->w, 0, sizeof(tpc->w)); |
| 1060 | /* The PE bit is always set for the MT3200 (but not MT1600) */ |
| 1061 | /* According to MT3200 manual, w7 bit #4 (reserved) is always set */ |
| 1062 | tpc->w[7] = w7_idle /*| w7_PE_format*/ | 0x0800; |
| 1063 | |
| 1064 | update_interrupt(device); |
| 1065 | } |
| 1066 | |
| 1067 | |
| 1068 | const device_type TI990_TAPE_CTRL = &device_creator<tap_990_device>; |
| 1069 | |
| 1070 | tap_990_device::tap_990_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 1071 | : device_t(mconfig, TI990_TAPE_CTRL, "Generic TI990 Tape Controller", tag, owner, clock, "tap_990", __FILE__) |
| 1072 | { |
| 1073 | m_token = global_alloc_clear(tap_990_t); |
| 1074 | } |
| 1075 | |
| 1076 | //------------------------------------------------- |
| 1077 | // device_config_complete - perform any |
| 1078 | // operations now that the configuration is |
| 1079 | // complete |
| 1080 | //------------------------------------------------- |
| 1081 | |
| 1082 | void tap_990_device::device_config_complete() |
| 1083 | { |
| 1084 | } |
| 1085 | |
| 1086 | //------------------------------------------------- |
| 1087 | // device_start - device-specific startup |
| 1088 | //------------------------------------------------- |
| 1089 | |
| 1090 | void tap_990_device::device_start() |
| 1091 | { |
| 1092 | DEVICE_START_NAME( tap_990 )(this); |
| 1093 | } |
| 1094 | |
| 1095 | //------------------------------------------------- |
| 1096 | // device_mconfig_additions - return a pointer to |
| 1097 | // the device's machine fragment |
| 1098 | //------------------------------------------------- |
| 1099 | |
| 1100 | machine_config_constructor tap_990_device::device_mconfig_additions() const |
| 1101 | { |
| 1102 | return MACHINE_CONFIG_NAME( tap_990 ); |
| 1103 | } |