1 /*
2  * c64dtvdma.c - C64DTV DMA controller
3  *
4  * Written by
5  *  M.Kiesel <mayne@users.sourceforge.net>
6  *  Hannu Nuotio <hannu.nuotio@tut.fi>
7  *  Daniel Kahlin <daniel@kahlin.net>
8  * Based on code by
9  *  Marco van den Heuvel <blackystardust68@yahoo.com>
10  *
11  * This file is part of VICE, the Versatile Commodore Emulator.
12  * See README for copyright notice.
13  *
14  *  This program is free software; you can redistribute it and/or modify
15  *  it under the terms of the GNU General Public License as published by
16  *  the Free Software Foundation; either version 2 of the License, or
17  *  (at your option) any later version.
18  *
19  *  This program is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  *  GNU General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public License
25  *  along with this program; if not, write to the Free Software
26  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
27  *  02111-1307  USA.
28  *
29  */
30 
31 #include "vice.h"
32 
33 #include "c64mem.h"
34 #include "c64dtvmem.h"
35 #include "c64dtvflash.h"
36 #include "c64dtvdma.h"
37 #include "cmdline.h"
38 #include "debug.h"
39 #include "lib.h"
40 #include "log.h"
41 #include "util.h"
42 #include "resources.h"
43 #include "maincpu.h"
44 #include "interrupt.h"
45 #include "snapshot.h"
46 
47 #ifdef DEBUG
48 static log_t c64dtvdma_log = LOG_ERR;
49 #endif
50 
51 static unsigned int c64dtv_dma_int_num;
52 
53 /* I/O of the DMA engine ($D3XX) */
54 uint8_t c64dtvmem_dma[0x20];
55 
56 int dma_active;
57 int dma_on_irq;
58 
59 static int dma_busy;
60 
61 static int dma_source_off;
62 static int dma_dest_off;
63 static int dma_irq;
64 
65 #ifdef DEBUG
66 static int dma_log_enabled = 0;
67 #endif
68 
69 static uint8_t dma_data;
70 static uint8_t dma_data_swap;
71 static int dma_count;
72 static enum { DMA_IDLE, DMA_READ, DMA_READ_SWAP, DMA_WRITE_SWAP, DMA_WRITE } dma_state;
73 static int source_line_off = 0;
74 static int dest_line_off = 0;
75 static uint8_t source_memtype = 0x00;
76 static uint8_t dest_memtype = 0x00;
77 
78 #define GET_REG24(a) ((c64dtvmem_dma[a + 2] << 16) | (c64dtvmem_dma[a + 1] << 8) | c64dtvmem_dma[a])
79 #define GET_REG16(a) ((c64dtvmem_dma[a + 1] << 8) | c64dtvmem_dma[a])
80 #define GET_REG8(a) (c64dtvmem_dma[a])
81 
82 /* ------------------------------------------------------------------------- */
83 
c64dtvdma_init(void)84 void c64dtvdma_init(void)
85 {
86 #ifdef DEBUG
87     if (c64dtvdma_log == LOG_ERR) {
88         c64dtvdma_log = log_open("C64DTVDMA");
89     }
90 #endif
91 
92     /* init DMA IRQ */
93     c64dtv_dma_int_num = interrupt_cpu_status_int_new(maincpu_int_status, "C64DTVDMA");
94 }
95 
c64dtvdma_shutdown(void)96 void c64dtvdma_shutdown(void)
97 {
98 }
99 
c64dtvdma_reset(void)100 void c64dtvdma_reset(void)
101 {
102     int i;
103 
104 #ifdef DEBUG
105     if (dma_log_enabled) {
106         log_message(c64dtvdma_log, "reset");
107     }
108 #endif
109 
110     /* TODO move register file initialization somewhere else? */
111     for (i = 0; i < 0x20; ++i) {
112         c64dtvmem_dma[i] = 0;
113     }
114 
115     dma_source_off = 0;
116     source_memtype = 0x00;
117     dma_dest_off = 0;
118     dest_memtype = 0x00;
119     dma_busy = 0;
120     dma_irq = 0;
121     dma_on_irq = 0;
122     dma_active = 0;
123 
124     dma_count = 0;
125     dma_state = DMA_IDLE;
126     dma_data = 0x00;
127     source_line_off = 0;
128     dest_line_off = 0;
129 }
130 
131 /* ------------------------------------------------------------------------- */
132 /* DMA transfer state machine */
133 
do_dma_read(int swap)134 static inline void do_dma_read(int swap)
135 {
136     uint8_t data;
137     int offs;
138     int memtype;
139 
140     if (!swap) {
141         offs = dma_source_off;
142         memtype = source_memtype;
143     } else {
144         offs = dma_dest_off;
145         memtype = dest_memtype;
146     }
147     offs &= 0x1fffff;
148 
149     switch (memtype) {
150         case 0x00: /* ROM */
151             data = c64dtvflash_read(offs);
152             break;
153         case 0x40: /* RAM */
154             data = mem_ram[offs];
155             break;
156         case 0x80: /* RAM+registers */
157             if ((offs >= 0xd000) && (offs < 0xe000)) {
158                 data = _mem_read_tab_ptr[offs >> 8]((uint16_t)offs);
159             } else {
160                 data = mem_ram[offs];
161             }
162             break;
163         case 0xc0: /* unknown */
164             data = 0;
165             break;
166         default:
167 #ifdef DEBUG
168             log_message(c64dtvdma_log, "invalid memtype in do_dma_read()");
169 #endif
170             data = 0;
171             break;
172     }
173 
174     if (!swap) {
175         dma_data = data;
176     } else {
177         dma_data_swap = data;
178     }
179 }
180 
do_dma_write(int swap)181 static inline void do_dma_write(int swap)
182 {
183     uint8_t data;
184     int offs;
185     int memtype;
186 
187     if (!swap) {
188         offs = dma_dest_off;
189         memtype = dest_memtype;
190         data = dma_data;
191     } else {
192         offs = dma_source_off;
193         memtype = source_memtype;
194         data = dma_data_swap;
195     }
196     offs &= 0x1fffff;
197 
198     switch (memtype) {
199         case 0x00: /* ROM */
200             c64dtvflash_store(offs, data);
201             break;
202         case 0x40: /* RAM */
203             mem_ram[offs] = data;
204             break;
205         case 0x80: /* RAM+registers */
206             if ((offs >= 0xd000) && (offs < 0xe000)) {
207                 _mem_write_tab_ptr[offs >> 8]((uint16_t)offs, data);
208             } else {
209                 mem_ram[offs] = data;
210             }
211             break;
212         case 0xc0: /* unknown */
213             break;
214         default:
215 #ifdef DEBUG
216             log_message(c64dtvdma_log, "invalid memtype in do_dma_write()");
217 #endif
218             break;
219     }
220 }
221 
update_counters(void)222 static inline void update_counters(void)
223 {
224     int source_step = GET_REG16(0x06);
225     int dest_step = GET_REG16(0x08);
226     int source_modulo = GET_REG16(0x0c);
227     int dest_modulo = GET_REG16(0x0e);
228     int source_line_length = GET_REG16(0x10);
229     int dest_line_length = GET_REG16(0x12);
230     int source_modulo_enable = GET_REG8(0x1e) & 0x01;
231     int dest_modulo_enable = GET_REG8(0x1e) & 0x02;
232     int source_direction = (GET_REG8(0x1f) & 0x04) ? +1 : -1;
233     int dest_direction = (GET_REG8(0x1f) & 0x08) ? +1 : -1;
234 
235     /* update offsets */
236     if (source_modulo_enable && (source_line_off >= source_line_length)) {
237         source_line_off = 0;
238         dma_source_off += source_modulo * source_direction;
239     } else {
240         source_line_off++;
241         dma_source_off += source_step * source_direction;
242     }
243 
244     if (dest_modulo_enable && (dest_line_off >= dest_line_length)) {
245         dest_line_off = 0;
246         dma_dest_off += dest_modulo * dest_direction;
247     } else {
248         dest_line_off++;
249         dma_dest_off += dest_step * dest_direction;
250     }
251 }
252 
perform_dma_cycle(void)253 static inline void perform_dma_cycle(void)
254 {
255     int swap = GET_REG8(0x1f) & 0x02;
256 
257     switch (dma_state) {
258         case DMA_IDLE:
259             break;
260         case DMA_READ:
261             if (dma_count == 0) {
262                 dma_state = DMA_IDLE;
263                 break;
264             }
265             do_dma_read(0);
266             if (swap) {
267                 dma_state = DMA_READ_SWAP;
268             } else {
269                 dma_state = DMA_WRITE;
270             }
271             break;
272         case DMA_READ_SWAP:
273             do_dma_read(1);
274             dma_state = DMA_WRITE_SWAP;
275             break;
276         case DMA_WRITE_SWAP:
277             do_dma_write(1);
278             dma_state = DMA_WRITE;
279             break;
280         case DMA_WRITE:
281             do_dma_write(0);
282             update_counters();
283 
284             dma_count--;
285             if (dma_count == 0) {
286                 dma_state = DMA_IDLE;
287             } else {
288                 dma_state = DMA_READ;
289             }
290             break;
291         default:
292 #ifdef DEBUG
293             log_message(c64dtvdma_log, "invalid state in perform_dma_cycle()");
294 #endif
295             dma_state = DMA_IDLE;
296             break;
297     }
298 }
299 
300 /* ------------------------------------------------------------------------- */
301 
302 /* These are the $D3xx DMA register engine handlers */
303 
c64dtvdma_trigger_dma(void)304 void c64dtvdma_trigger_dma(void)
305 {
306     if (!dma_active) {
307         int source_continue = GET_REG8(0x1d) & 0x02;
308         int dest_continue = GET_REG8(0x1d) & 0x08;
309 
310         if (!source_continue) {
311             dma_source_off = GET_REG24(0x00) & 0x3fffff;
312             source_memtype = GET_REG8(0x02) & 0xc0;
313         }
314         if (!dest_continue) {
315             dma_dest_off = GET_REG24(0x03) & 0x3fffff;
316             dest_memtype = GET_REG8(0x05) & 0xc0;
317         }
318 
319         /* total number of bytes to transfer */
320         dma_count = GET_REG16(0x0a);
321         /* length=0 means 64 Kb */
322         if (dma_count == 0) {
323             dma_count = 0x10000;
324         }
325 
326 #ifdef DEBUG
327         if (dma_log_enabled && (source_continue || dest_continue)) {
328             log_message(c64dtvdma_log, "Source continue %s, dest continue %s", source_continue ? "on" : "off", dest_continue ? "on" : "off");
329         }
330 #endif
331 
332         /* initialize state variables */
333         source_line_off = 0;
334         dest_line_off = 0;
335 
336         dma_state = DMA_READ;
337 
338         if (GET_REG8(0x1f) & 0x80) {
339             dma_irq = 1;
340         } else {
341             dma_irq = 0;
342         }
343 
344         dma_busy = 1;
345         dma_active = 1;
346     }
347 }
348 
c64dtv_dma_done(void)349 static inline void c64dtv_dma_done(void)
350 {
351     if (dma_irq) {
352         maincpu_set_irq(c64dtv_dma_int_num, 1);
353         dma_busy = 2;
354     }
355     dma_busy &= 0xfe;
356     dma_active = 0;
357 }
358 
c64dtv_dma_read(uint16_t addr)359 uint8_t c64dtv_dma_read(uint16_t addr)
360 {
361     if (addr == 0x1f) {
362         return dma_busy;
363         /* the default return value is 0x00 too but I have seen some strangeness
364            here.  I've seen something that looks like DMAed data. - tlr */
365     }
366     return 0x00;
367 }
368 
c64dtv_dma_store(uint16_t addr,uint8_t value)369 void c64dtv_dma_store(uint16_t addr, uint8_t value)
370 {
371     /* Store first, then check whether DMA access has been
372        requested, perform if necessary. */
373     c64dtvmem_dma[addr] = value;
374 
375     dma_on_irq = GET_REG8(0x1f) & 0x70;
376 
377     /* Clear DMA IRQ */
378     if ((GET_REG8(0x1d) & 0x01) && (dma_busy == 2)) {
379 #ifdef DEBUG
380         if (dma_log_enabled) {
381             log_message(c64dtvdma_log, "Clear IRQ");
382         }
383 #endif
384         dma_busy &= 0xfd;
385         c64dtvmem_dma[0x1f] = 0;
386         maincpu_set_irq(c64dtv_dma_int_num, 0);
387         dma_irq = 0;
388         /* reset clear IRQ strobe bit */
389         c64dtvmem_dma[0x1d] &= 0xfe;
390     }
391 
392     if (dma_on_irq && (dma_busy == 0)) {
393         dma_busy = 1;
394 #ifdef DEBUG
395         if (dma_log_enabled) {
396             log_message(c64dtvdma_log, "Scheduled DMA (%02x).",
397                     (unsigned int)dma_on_irq);
398         }
399 #endif
400         return;
401     }
402 
403     /* Force DMA start */
404     if (GET_REG8(0x1f) & 0x01) {
405         c64dtvdma_trigger_dma();
406         /* reset force start strobe bit */
407         c64dtvmem_dma[0x1f] &= 0xfe;
408     }
409 }
410 
411 
c64dtvdma_perform_dma(void)412 void c64dtvdma_perform_dma(void)
413 {
414     /* set maincpu_rmw_flag to 0 during DMA */
415     int dma_maincpu_rmw = maincpu_rmw_flag;
416     maincpu_rmw_flag = 0;
417     perform_dma_cycle();
418     maincpu_rmw_flag = dma_maincpu_rmw;
419 
420 #ifdef DEBUG
421     if (dma_log_enabled && (dma_state == DMA_WRITE)) {
422         log_message(c64dtvdma_log,
423                 "%s from %x (%s) to %x (%s), %d to go",
424                 GET_REG8(0x1f) & 0x02 ? "Swapped" : "Copied",
425                 (unsigned int)dma_source_off,
426                 source_memtype == 0 ? "Flash" : "RAM",
427                 (unsigned int)dma_dest_off,
428                 dest_memtype == 0 ? "Flash" : "RAM",
429                 dma_count - 1);
430     }
431 #endif
432 
433     if (dma_state == DMA_IDLE) {
434         c64dtv_dma_done();
435     }
436 }
437 
438 
439 /* ------------------------------------------------------------------------- */
440 
441 #ifdef DEBUG
set_dma_log(int val,void * param)442 static int set_dma_log(int val, void *param)
443 {
444     dma_log_enabled = val ? 1 : 0;
445 
446     return 0;
447 }
448 #endif
449 
450 static const resource_int_t resources_int[] = {
451 #ifdef DEBUG
452     { "DtvDMALog", 0, RES_EVENT_NO, (resource_value_t)0,
453       &dma_log_enabled, set_dma_log, NULL },
454 #endif
455     RESOURCE_INT_LIST_END
456 };
457 
c64dtvdma_resources_init(void)458 int c64dtvdma_resources_init(void)
459 {
460     return resources_register_int(resources_int);
461 }
462 
c64dtvdma_resources_shutdown(void)463 void c64dtvdma_resources_shutdown(void)
464 {
465 }
466 
467 static const cmdline_option_t cmdline_options[] =
468 {
469 #ifdef DEBUG
470     { "-dtvdmalog", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
471       NULL, NULL, "DtvDMALog", (resource_value_t)1,
472       NULL, "Enable DTV DMA logs." },
473     { "+dtvdmalog", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
474       NULL, NULL, "DtvDMALog", (resource_value_t)0,
475       NULL, "Disable DTV DMA logs." },
476 #endif
477     CMDLINE_LIST_END
478 };
479 
c64dtvdma_cmdline_options_init(void)480 int c64dtvdma_cmdline_options_init(void)
481 {
482     return cmdline_register_options(cmdline_options);
483 }
484 
485 /* ------------------------------------------------------------------------- */
486 
487 /* C64DTVDMA snapshot module format:
488 
489    type  | name            | description
490    -------------------------------------
491    ARRAY | regs            | 32 BYTES of register data
492    DWORD | source off      | source offset
493    DWORD | dest off        | destination offset
494    DWORD | busy            | DMA busy flag
495    DWORD | IRQ             | DMA IRQ state
496    DWORD | on IRQ          | on IRQ
497    DWORD | active          | DMA active flag
498    BYTE  | data            | DMA data
499    BYTE  | data swap       | DMA data swap flag
500    DWORD | count           | DMA counter
501    DWORD | state           | DMA state
502    DWORD | source line off | source line offset
503    DWORD | dest line off   | destination line offset
504    BYTE  | source mem type | source memory type
505    BYTE  | dest mem type   | destination memory type
506  */
507 
508 static const char snap_module_name[] = "C64DTVDMA";
509 #define SNAP_MAJOR 0
510 #define SNAP_MINOR 0
511 
512 /* static log_t c64_snapshot_log = LOG_ERR; */
513 
c64dtvdma_snapshot_write_module(snapshot_t * s)514 int c64dtvdma_snapshot_write_module(snapshot_t *s)
515 {
516     snapshot_module_t *m;
517 
518     /* DMA module.  */
519     m = snapshot_module_create(s, snap_module_name, SNAP_MAJOR, SNAP_MINOR);
520 
521     if (m == NULL) {
522         return -1;
523     }
524 
525     if (0
526         || SMW_BA(m, c64dtvmem_dma, 0x20) < 0
527         || SMW_DW(m, dma_source_off) < 0
528         || SMW_DW(m, dma_dest_off) < 0
529         || SMW_DW(m, dma_busy) < 0
530         || SMW_DW(m, dma_irq) < 0
531         || SMW_DW(m, dma_on_irq) < 0
532         || SMW_DW(m, dma_active) < 0
533         || SMW_B(m, dma_data) < 0
534         || SMW_B(m, dma_data_swap) < 0
535         || SMW_DW(m, dma_count) < 0
536         || SMW_DW(m, dma_state) < 0
537         || SMW_DW(m, source_line_off) < 0
538         || SMW_DW(m, dest_line_off) < 0
539         || SMW_B(m, source_memtype) < 0
540         || SMW_B(m, dest_memtype) < 0) {
541         snapshot_module_close(m);
542         return -1;
543     }
544 
545     return snapshot_module_close(m);
546 }
547 
c64dtvdma_snapshot_read_module(snapshot_t * s)548 int c64dtvdma_snapshot_read_module(snapshot_t *s)
549 {
550     uint8_t major_version, minor_version;
551     snapshot_module_t *m;
552     int temp_dma_state;
553 
554     /* DMA module.  */
555     m = snapshot_module_open(s, snap_module_name, &major_version, &minor_version);
556 
557     if (m == NULL) {
558         return -1;
559     }
560 
561     /* Do not accept versions higher than current */
562     if (snapshot_version_is_bigger(major_version, minor_version, SNAP_MAJOR, SNAP_MINOR)) {
563         snapshot_set_error(SNAPSHOT_MODULE_HIGHER_VERSION);
564         goto fail;
565     }
566 
567     if (0
568         || SMR_BA(m, c64dtvmem_dma, 0x20) < 0
569         || SMR_DW_INT(m, &dma_source_off) < 0
570         || SMR_DW_INT(m, &dma_dest_off) < 0
571         || SMR_DW_INT(m, &dma_busy) < 0
572         || SMR_DW_INT(m, &dma_irq) < 0
573         || SMR_DW_INT(m, &dma_on_irq) < 0
574         || SMR_DW_INT(m, &dma_active) < 0
575         || SMR_B(m, &dma_data) < 0
576         || SMR_B(m, &dma_data_swap) < 0
577         || SMR_DW_INT(m, &dma_count) < 0
578         || SMR_DW_INT(m, &temp_dma_state) < 0
579         || SMR_DW_INT(m, &source_line_off) < 0
580         || SMR_DW_INT(m, &dest_line_off) < 0
581         || SMR_B(m, &source_memtype) < 0
582         || SMR_B(m, &dest_memtype) < 0) {
583         goto fail;
584     }
585 
586     dma_state = temp_dma_state;
587 
588     return snapshot_module_close(m);
589 
590 fail:
591     snapshot_module_close(m);
592     return -1;
593 }
594