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