1 /*
2 * KCemu -- The emulator for the KC85 homecomputer series and much more.
3 * Copyright (C) 1997-2010 Torsten Paul
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <ctype.h>
21
22 #include "kc/system.h"
23
24 #include "kc/kc.h"
25 #include "kc/fdc.h"
26 #include "kc/fdc_cmd.h"
27
28 #include "libdbg/dbg.h"
29
FloppyState(byte_t head,byte_t cylinder,byte_t sector,Floppy * floppy)30 FloppyState::FloppyState(byte_t head, byte_t cylinder, byte_t sector, Floppy *floppy)
31 {
32 _head = head;
33 _cylinder = cylinder;
34 _sector = sector;
35 _floppy = floppy;
36 }
37
~FloppyState(void)38 FloppyState::~FloppyState(void)
39 {
40 }
41
SectorDesc(long size,byte_t * buf)42 SectorDesc::SectorDesc(long size, byte_t *buf)
43 {
44 _buf = buf;
45 _size = size;
46 }
47
FDC(void)48 FDC::FDC(void) : InterfaceCircuit("FDC")
49 {
50 _cmds[0x00] = new FDC_CMD_INVALID(this);
51 _cmds[0x01] = new FDC_CMD_INVALID(this);
52 _cmds[0x02] = new FDC_CMD_READ_TRACK(this);
53 _cmds[0x03] = new FDC_CMD_SPECIFY(this);
54 _cmds[0x04] = new FDC_CMD_SENSE_DRIVE_STATUS(this);
55 _cmds[0x05] = new FDC_CMD_WRITE_DATA(this);
56 _cmds[0x06] = new FDC_CMD_READ_DATA(this);
57 _cmds[0x07] = new FDC_CMD_RECALIBRATE(this);
58 _cmds[0x08] = new FDC_CMD_SENSE_INTERRUPT_STATUS(this);
59 _cmds[0x09] = new FDC_CMD_WRITE_DELETED_DATA(this);
60 _cmds[0x0a] = new FDC_CMD_READ_ID(this);
61 _cmds[0x0b] = new FDC_CMD_INVALID(this);
62 _cmds[0x0c] = new FDC_CMD_READ_DELETED_DATA(this);
63 _cmds[0x0d] = new FDC_CMD_FORMAT_A_TRACK(this);
64 _cmds[0x0e] = new FDC_CMD_INVALID(this);
65 _cmds[0x0f] = new FDC_CMD_SEEK(this);
66 _cmds[0x10] = new FDC_CMD_INVALID(this);
67 _cmds[0x11] = new FDC_CMD_SCAN_EQUAL(this);
68 _cmds[0x12] = new FDC_CMD_INVALID(this);
69 _cmds[0x13] = new FDC_CMD_INVALID(this);
70 _cmds[0x14] = new FDC_CMD_INVALID(this);
71 _cmds[0x15] = new FDC_CMD_INVALID(this);
72 _cmds[0x16] = new FDC_CMD_INVALID(this);
73 _cmds[0x17] = new FDC_CMD_INVALID(this);
74 _cmds[0x18] = new FDC_CMD_INVALID(this);
75 _cmds[0x19] = new FDC_CMD_SCAN_LOW_OR_EQUAL(this);
76 _cmds[0x1a] = new FDC_CMD_INVALID(this);
77 _cmds[0x1b] = new FDC_CMD_INVALID(this);
78 _cmds[0x1c] = new FDC_CMD_INVALID(this);
79 _cmds[0x1d] = new FDC_CMD_SCAN_HIGH_OR_EQUAL(this);
80 _cmds[0x1e] = new FDC_CMD_INVALID(this);
81 _cmds[0x1f] = new FDC_CMD_INVALID(this);
82
83 _floppy[0] = new Floppy("attach-1");
84 _floppy[1] = new Floppy("attach-2");
85 _floppy[2] = new Floppy("attach-3");
86 _floppy[3] = new Floppy("attach-4");
87
88 _fstate[0] = new FloppyState(0, 0, 1, _floppy[0]);
89 _fstate[1] = new FloppyState(0, 0, 1, _floppy[1]);
90 _fstate[2] = new FloppyState(0, 0, 1, _floppy[2]);
91 _fstate[3] = new FloppyState(0, 0, 1, _floppy[3]);
92
93 init();
94 }
95
~FDC(void)96 FDC::~FDC(void)
97 {
98 for (int a = 0;a <= 0x1f;a++)
99 delete _cmds[a];
100
101 for (int a = 0;a < 4;a++)
102 {
103 delete _fstate[a];
104 delete _floppy[a];
105 }
106 }
107
108 void
init(void)109 FDC::init(void)
110 {
111 _cur_cmd = 0;
112 _cur_floppy = 0;
113 _read_bytes = 0;
114 _MSR = 0;
115 _ST0 = 0;
116 _ST1 = 0;
117 _ST2 = 0;
118 _ST3 = 0;
119 _INPUT_GATE = 0x60;
120 _selected_unit = 0;
121 _selected_device = 0;
122 _terminal_count = false;
123
124 set_state(FDC_STATE_IDLE);
125 }
126
127 byte_t
in_data(word_t addr)128 FDC::in_data(word_t addr)
129 {
130 byte_t val;
131
132 val = 0xff;
133 switch (_state)
134 {
135 case FDC_STATE_RESULT:
136 val = _cur_cmd->read_result();
137 DBG(2, form("KCemu/FDC/in_data",
138 "FDC::in(): addr = %04x, val = %02x [%c] FDC_STATE_RESULT\n",
139 addr, val, isprint(val) ? val : '.'));
140 break;
141 case FDC_STATE_DATA:
142 val = read_byte();
143 DBG(2, form("KCemu/FDC/in_data",
144 "FDC::in(): addr = %04x, val = %02x [%c] FDC_STATE_DATA\n",
145 addr, val, isprint(val) ? val : '.'));
146 break;
147 default:
148 break;
149 }
150
151 return val;
152 }
153
154 /*
155 * data input from disk
156 */
157 byte_t
read_byte(void)158 FDC::read_byte(void)
159 {
160 if (_cur_cmd)
161 return _cur_cmd->read_byte();
162
163 return 0xff;
164 }
165
166 /*
167 * data output to disk ???
168 */
169 void
write_byte(byte_t val)170 FDC::write_byte(byte_t val)
171 {
172 if (_cur_cmd)
173 _cur_cmd->write_byte(val);
174 }
175
176 /*
177 * command output to floppy controller
178 */
179 void
out_data(word_t addr,byte_t val)180 FDC::out_data(word_t addr, byte_t val)
181 {
182 switch (_state)
183 {
184 case FDC_STATE_IDLE:
185 DBG(2, form("KCemu/FDC/out_data",
186 "FDC::out(): addr = %04x, val = %02x [%c] FDC_STATE_IDLE\n",
187 addr, val, isprint(val) ? val : '.'));
188 _cur_cmd = _cmds[val & 0x1f];
189 _cur_cmd->start(val);
190 break;
191 case FDC_STATE_COMMAND:
192 DBG(2, form("KCemu/FDC/out_data",
193 "FDC::out(): addr = %04x, val = %02x [%c] FDC_STATE_COMMAND\n",
194 addr, val, isprint(val) ? val : '.'));
195 _cur_cmd->write_arg(val);
196 break;
197 case FDC_STATE_DATA:
198 DBG(2, form("KCemu/FDC/out_data",
199 "FDC::out(): addr = %04x, val = %02x [%c] FDC_STATE_DATA\n",
200 addr, val, isprint(val) ? val : '.'));
201 write_byte(val);
202 break;
203 default:
204 break;
205 }
206 }
207
208 void
select_floppy(int floppy_nr)209 FDC::select_floppy(int floppy_nr)
210 {
211 DBG(2, form("KCemu/FDC/select_floppy",
212 "FDC::select_floppy(): selecting floppy %d\n",
213 floppy_nr));
214 _selected_unit = floppy_nr;
215 _cur_floppy = _fstate[floppy_nr];
216 }
217
218 Floppy *
get_floppy(void)219 FDC::get_floppy(void)
220 {
221 if (_cur_floppy == 0)
222 return 0;
223
224 return _cur_floppy->get_floppy();
225 }
226
227 Floppy *
get_floppy(int idx)228 FDC::get_floppy(int idx)
229 {
230 if (idx < 0)
231 return NULL;
232 if (idx > 3)
233 return NULL;
234
235 return _fstate[idx]->get_floppy();
236 }
237
238 int
get_head(void)239 FDC::get_head(void)
240 {
241 if (_cur_floppy == 0)
242 return -1;
243 return _cur_floppy->get_head();
244 }
245
246 int
get_cylinder(void)247 FDC::get_cylinder(void)
248 {
249 if (_cur_floppy == 0)
250 return -1;
251 return _cur_floppy->get_cylinder();
252 }
253
254 int
get_sector(void)255 FDC::get_sector(void)
256 {
257 if (_cur_floppy == 0)
258 return -1;
259 return _cur_floppy->get_sector();
260 }
261
262 void
set_state(fdc_state_t state)263 FDC::set_state(fdc_state_t state)
264 {
265 _state = state;
266
267 byte_t msr = get_msr() & 0x0f;
268 byte_t dio = get_msr() & ST_MAIN_DIO;
269 switch (_state)
270 {
271 case FDC_STATE_IDLE:
272 msr |= ST_MAIN_RQM;
273 DBG(2, form("KCemu/FDC/state",
274 "FDC::set_state(): FDC_STATE_IDLE -> MSR: %02x\n",
275 msr));
276 break;
277 case FDC_STATE_COMMAND:
278 msr |= ST_MAIN_READ_WRITE;
279 msr |= ST_MAIN_RQM;
280 DBG(2, form("KCemu/FDC/state",
281 "FDC::set_state(): FDC_STATE_COMMAND -> MSR: %02x\n",
282 msr));
283 break;
284 case FDC_STATE_EXECUTE:
285 msr |= ST_MAIN_NON_DMA;
286 msr |= ST_MAIN_DIO;
287 DBG(2, form("KCemu/FDC/state",
288 "FDC::set_state(): FDC_STATE_EXECUTE -> MSR: %02x\n",
289 msr));
290 break;
291 case FDC_STATE_DATA:
292 msr |= ST_MAIN_READ_WRITE;
293 msr |= ST_MAIN_NON_DMA;
294 msr |= ST_MAIN_RQM;
295 // don't change the direction register, this is set by the command
296 // (1 while doing read, 0 while doing write command data transfer)
297 msr |= dio;
298 DBG(2, form("KCemu/FDC/state",
299 "FDC::set_state(): FDC_STATE_DATA -> MSR: %02x\n",
300 msr));
301 break;
302 case FDC_STATE_RESULT:
303 msr |= ST_MAIN_READ_WRITE;
304 msr |= ST_MAIN_DIO;
305 msr |= ST_MAIN_RQM;
306 DBG(2, form("KCemu/FDC/state",
307 "FDC::set_state(): FDC_STATE_RESULT -> MSR: %02x\n",
308 msr));
309 break;
310 }
311
312 set_msr(0xf0, msr);
313 }
314
315 void
callback(void * data)316 FDC::callback(void *data)
317 {
318 long val = (long)data;
319 switch (val & CB_MASK)
320 {
321 case CB_TYPE_SEEK:
322 callback_seek(data);
323 break;
324 case CB_TYPE_INDEX:
325 callback_index(data);
326 break;
327 default:
328 DBG(0, form("KCemu/internal_error",
329 "FDC::callback(): unknown callback %08x\n",
330 val));
331 break;
332 }
333 }
334
335 /**
336 * Emulation of the index pulse generated by the floppy drive.
337 * We assume the drive rotates with 300rpm, means 5 rotations
338 * per second.
339 */
340 void
callback_index(void * data)341 FDC::callback_index(void *data)
342 {
343 long index_value = ((long)data) & 1;
344
345 byte_t input_gate = 0;
346 if (_selected_device != 0)
347 {
348 int offset;
349 if (index_value == 0)
350 {
351 offset = 1000;
352 input_gate = 0x10;
353 }
354 else
355 {
356 offset = 350000;
357 }
358
359 add_callback(offset, this, (void *)(CB_TYPE_INDEX | (1 - index_value)));
360 }
361
362 set_input_gate(0x10, input_gate);
363 }
364
365 void
callback_seek(void * data)366 FDC::callback_seek(void *data)
367 {
368 long unit = (long)data;
369 byte_t unit_bit = 1 << (unit & 0xff);
370 bool ok = (unit & 0x0100) == 0;
371
372 DBG(2, form("KCemu/FDC/seek",
373 "FDC::seek(): [%8Ld] finished seek %s, floppy %d\n",
374 get_counter(),
375 ok ? "ok" : "with error",
376 unit & 0xff));
377
378 set_msr(unit_bit, 0);
379
380 if (ok)
381 {
382 // seek ok
383 set_ST0(ST_0_IC_MASK | ST_0_SEEK_END | ST_0_EC,
384 ST_0_IC_NORMAL_TERMINATION | ST_0_SEEK_END);
385 set_ST3(ST_3_READY | ST_3_TRACK_0, ST_3_READY | ST_3_TRACK_0);
386 }
387 else
388 {
389 // seek failed
390 set_ST0(ST_0_IC_MASK | ST_0_SEEK_END | ST_0_EC,
391 ST_0_IC_ABNORMAL_TERMINATION | ST_0_SEEK_END | ST_0_EC);
392 set_ST3(ST_3_READY | ST_3_TRACK_0, 0);
393 }
394
395 set_input_gate(0x40, 0x00);
396 }
397
398 bool
seek_internal(byte_t head,byte_t cylinder,byte_t sector)399 FDC::seek_internal(byte_t head, byte_t cylinder, byte_t sector)
400 {
401 if (_cur_floppy == 0)
402 return false;
403
404 _cur_floppy->set_head(head);
405 _cur_floppy->set_cylinder(cylinder);
406 _cur_floppy->set_sector(sector);
407
408 bool seek_ok = _cur_floppy->seek();
409
410 return seek_ok;
411 }
412
413 bool
seek(byte_t head,byte_t cylinder,byte_t sector)414 FDC::seek(byte_t head, byte_t cylinder, byte_t sector)
415 {
416 if (_cur_floppy == 0)
417 return false;
418
419 int c1 = cylinder;
420 int c2 = _cur_floppy->get_cylinder();
421 int diff = abs(c1 - c2);
422
423 bool seek_ok = seek_internal(head, cylinder, sector);
424 set_ST0(ST_0_SEEK_END, 0);
425 set_input_gate(0x40, 0x40);
426
427 int offset = diff * 1000 + 500;
428
429 if (seek_ok)
430 {
431 DBG(2, form("KCemu/FDC/seek",
432 "FDC::seek(): [%8Ld] starting seek to cylinder %d, diff = %d, floppy %d\n",
433 get_counter(),
434 cylinder,
435 diff,
436 _selected_unit));
437 add_callback(offset, this, (void *)(CB_TYPE_SEEK | _selected_unit));
438 }
439 else
440 {
441 DBG(2, form("KCemu/FDC/seek",
442 "FDC::seek(): [%8Ld] seek failed to cylinder %d, diff = %d, floppy %d\n",
443 get_counter(),
444 cylinder,
445 diff,
446 _selected_unit));
447 add_callback(offset, this, (void *)(CB_TYPE_SEEK | _selected_unit | 0x0100));
448 }
449
450 byte_t unit_bit = 1 << _selected_unit;
451 set_msr(unit_bit, unit_bit);
452
453 return seek_ok;
454 }
455
456 void
drive_select(byte_t val)457 FDC::drive_select(byte_t val)
458 {
459 DBG(2, form("KCemu/FDC/drive_select",
460 "FDC::drive_select(): output to drive select port, value = %02x\n",
461 val));
462
463 val &= 0x0f;
464
465 bool has_disc = false;
466 for (int a = 0;a < 4;a++)
467 {
468 Floppy *floppy = get_floppy(a);
469 if ((val & (1 << a)) && (floppy != NULL) && (floppy->get_sector_size() > 0))
470 has_disc = true;
471 }
472
473 if (has_disc)
474 {
475 DBG(2, form("KCemu/FDC/drive_select",
476 "FDC::drive_select(): enable index-hole pulse generation%s\n",
477 _selected_device == 0 ? "" : " (already running)"));
478
479 if (_selected_device == 0)
480 add_callback(20000, this, (void *)(CB_TYPE_INDEX));
481
482 _selected_device = val;
483 }
484 else
485 {
486 DBG(2, form("KCemu/FDC/drive_select",
487 "FDC::drive_select(): stop index-hole pulse generation\n"));
488
489 _selected_device = 0;
490 }
491
492 set_input_gate(0x10, 0x00);
493 }
494
495 byte_t
get_input_gate(void)496 FDC::get_input_gate(void)
497 {
498 return _INPUT_GATE;
499 }
500
501 void
set_input_gate(byte_t mask,byte_t val)502 FDC::set_input_gate(byte_t mask, byte_t val)
503 {
504 _INPUT_GATE = ((_INPUT_GATE & ~mask) | (val & mask));
505 DBG(2, form("KCemu/FDC/input_gate",
506 "FDC::set_input_gate(): INPUT_GATE: %02x\n",
507 _INPUT_GATE));
508 }
509
510 byte_t
get_msr(void)511 FDC::get_msr(void)
512 {
513 return _MSR;
514 }
515
516 void
set_msr(byte_t mask,byte_t val)517 FDC::set_msr(byte_t mask, byte_t val)
518 {
519 _MSR = ((_MSR & ~mask) | (val & mask));
520 DBG(2, form("KCemu/FDC/MSR",
521 "FDC::set_msr(): MSR: %02x\n",
522 _MSR));
523 }
524
525 /*
526 * handle the terminal count signal (TC)
527 *
528 * when receiving high on this pin the floppy controller
529 * aborts the running command (mostly read/write) and
530 * goes into result phase
531 */
532 void
set_terminal_count(bool val)533 FDC::set_terminal_count(bool val)
534 {
535 if (_terminal_count == val)
536 return;
537
538 _terminal_count = val;
539 if (!_terminal_count)
540 return;
541
542 if (_cur_cmd)
543 _cur_cmd->finish_cmd();
544 else
545 set_state(FDC_STATE_IDLE);
546 }
547
548 byte_t
get_ST0(void)549 FDC::get_ST0(void)
550 {
551 set_ST0(ST_0_HEAD_ADDRESS, get_head() == 1 ? ST_0_HEAD_ADDRESS : 0);
552 set_ST0(ST_0_UNIT_SELECT_MASK, _selected_unit);
553
554 return _ST0;
555 }
556
557 byte_t
get_ST1(void)558 FDC::get_ST1(void)
559 {
560 return _ST1;
561 }
562
563 byte_t
get_ST2(void)564 FDC::get_ST2(void)
565 {
566 return _ST2;
567 }
568
569 byte_t
get_ST3(void)570 FDC::get_ST3(void)
571 {
572 set_ST3(ST_3_READY, ST_3_READY);
573 set_ST3(ST_3_TWO_SIDE, ST_3_TWO_SIDE);
574 set_ST3(ST_3_WRITE_PROTECTED, 0);
575 set_ST3(ST_3_TRACK_0, get_cylinder() == 0 ? ST_3_TRACK_0 : 0);
576 set_ST3(ST_3_HEAD_ADDRESS, get_head() == 1 ? ST_3_HEAD_ADDRESS : 0);
577 set_ST3(ST_3_UNIT_SELECT_MASK, _selected_unit);
578
579 return _ST3;
580 }
581
582 void
set_ST0(byte_t mask,byte_t val)583 FDC::set_ST0(byte_t mask, byte_t val)
584 {
585 _ST0 = (~mask & _ST0) | (mask & val);
586 DBG(2, form("KCemu/FDC/ST0",
587 "FDC::set_ST0(): ST0: %02x\n",
588 _ST0));
589 }
590
591 void
set_ST1(byte_t mask,byte_t val)592 FDC::set_ST1(byte_t mask, byte_t val)
593 {
594 _ST1 = (~mask & _ST1) | (mask & val);
595 DBG(2, form("KCemu/FDC/ST1",
596 "FDC::set_ST1(): ST1: %02x\n",
597 _ST1));
598 }
599
600 void
set_ST2(byte_t mask,byte_t val)601 FDC::set_ST2(byte_t mask, byte_t val)
602 {
603 _ST2 = (~mask & _ST2) | (mask & val);
604 DBG(2, form("KCemu/FDC/ST2",
605 "FDC::set_ST2(): ST2: %02x\n",
606 _ST2));
607 }
608
609 void
set_ST3(byte_t mask,byte_t val)610 FDC::set_ST3(byte_t mask, byte_t val)
611 {
612 _ST3 = (~mask & _ST3) | (mask & val);
613 DBG(2, form("KCemu/FDC/ST3",
614 "FDC::set_ST3(): ST3: %02x\n",
615 _ST3));
616 }
617
618 void
reset(bool power_on)619 FDC::reset(bool power_on)
620 {
621 init();
622 }
623
624 void
reti(void)625 FDC::reti(void)
626 {
627 }
628