1 /*
2  * apple2_disk.c
3  *
4  * Disk ][ card emulation
5  */
6 
7 /* $Id: apple2_disk.c,v 1.11 2000/08/21 01:13:32 nyef Exp $ */
8 
9 #include <stdlib.h>
10 #include <string.h>
11 
12 #include "apple2_cards.h"
13 #include "types.h"
14 #include "tool.h"
15 #include "ui.h"
16 #include "menu.h"
17 
18 
19 /* manifest constants */
20 
21 #define DISK_2_ROM_FILE "disk_ii.rom"
22 
23 #define DSK_FILE_TRACK_LENGTH 0x1000
24 #define NIB_FILE_TRACK_LENGTH 0x1a00
25 
26 #define BYTES_PER_NIB_SECTOR 0x1a0
27 
28 #define TRACKS_PER_DISK 35 /* 0x23 */
29 
30 #define DISK_SEEK_IN 0 /* higher tracks */
31 #define DISK_SEEK_OUT 1 /* lower tracks */
32 
33 
34 /* data structures */
35 
36 struct a2_disk {
37     int is_inserted;
38     int is_readonly;
39 
40     int last_phase;
41 
42     int cur_track;
43 
44     u8 *disk_data;
45 
46     u8 *track_data;
47     int track_data_index;
48 
49     struct ui_menu *menu;
50 };
51 
52 struct a2_disk_card {
53     struct apple2_card api;
54 
55     struct a2_disk disks[2];
56 
57     int motor_on;
58     int drive_select;
59 
60     u8 write_latch;
61 
62     int qlines;
63 };
64 
65 
66 /* drive emulation */
67 
a2_disk_seek(struct a2_disk * disk,int direction)68 void a2_disk_seek(struct a2_disk *disk, int direction)
69 {
70     int new_track;
71 
72     new_track = disk->cur_track;
73 
74     if (direction == DISK_SEEK_OUT) {
75 	if (new_track == 0) { /* already at beginning */
76 	    return;
77 	}
78 	new_track--;
79     } else if (direction == DISK_SEEK_IN) {
80 	new_track++;
81 	if (new_track >= TRACKS_PER_DISK) { /* already at end */
82 	    return;
83 	}
84     } else {
85 	deb_printf("a2_disk: BUG: bogus direction parameter to seek.\n");
86 	return;
87     }
88 
89     disk->cur_track = new_track;
90     disk->track_data = disk->disk_data + (new_track * NIB_FILE_TRACK_LENGTH);
91 }
92 
a2_line_toggle(struct a2_disk_card * card,u16 address)93 void a2_line_toggle(struct a2_disk_card *card, u16 address)
94 {
95     int addr;
96 
97     addr = address & 0xf;
98 
99     switch (addr) {
100     case 0x0: case 0x1: case 0x2: case 0x3:
101     case 0x4: case 0x5: case 0x6: case 0x7:
102 	if (!(addr & 1)) {
103 	    return; /* stepper phase off, ingore it */
104 	}
105 	if (addr & 2) {
106 	    card->disks[card->drive_select].last_phase = addr;
107 	    return; /* odd stepper phase off, set phase in disk struct */
108 	}
109 	if ((addr & 4) == (card->disks[card->drive_select].last_phase & 4)) {
110 	    /* outward bound */
111 	    a2_disk_seek(&card->disks[card->drive_select], DISK_SEEK_OUT);
112 	} else {
113 	    /* inward bound */
114 	    a2_disk_seek(&card->disks[card->drive_select], DISK_SEEK_IN);
115 	}
116 	return;
117 
118     case 0x8:
119 	card->motor_on = 0;
120 	return;
121     case 0x9:
122 	card->motor_on = 1;
123 	return;
124 
125     case 0xa:
126 	card->drive_select = 0;
127 	return;
128     case 0xb:
129 	card->drive_select = 1;
130 	return;
131 
132     case 0xc: /* Q6L */
133 	card->qlines &= ~1;
134 	return;
135     case 0xd: /* Q6H */
136 	card->qlines |= 1;
137 	return;
138     case 0xe: /* Q7L */
139 	card->qlines &= ~2;
140 	return;
141     case 0xf: /* Q7H */
142 	card->qlines |= 2;
143 	return;
144     }
145 }
146 
a2_disk_read(struct a2_disk_card * card,u16 address)147 u8 a2_disk_read(struct a2_disk_card *card, u16 address)
148 {
149     struct a2_disk *cur_disk;
150 
151     a2_line_toggle(card, address);
152 
153     cur_disk = &card->disks[card->drive_select];
154 
155     switch (card->qlines & 3) {
156     case 0: /* read data */
157 	if (card->motor_on && cur_disk->is_inserted) {
158 	    u8 retval;
159 
160 	    retval = cur_disk->track_data[cur_disk->track_data_index];
161 	    cur_disk->track_data_index++;
162 	    cur_disk->track_data_index %= NIB_FILE_TRACK_LENGTH;
163 
164 	    return retval;
165 	}
166 	break;
167     case 1: /* sense write protect */
168 	return cur_disk->is_readonly << 7;
169     case 2: /* write byte */
170 	if (card->motor_on && cur_disk->is_inserted && (!cur_disk->is_readonly)) {
171 	    cur_disk->track_data[cur_disk->track_data_index] = card->write_latch;
172 	    cur_disk->track_data_index++;
173 	    cur_disk->track_data_index %= NIB_FILE_TRACK_LENGTH;
174 	}
175 	break;
176     case 3: /* load write latch, SHOULD NOT HAPPEN */
177 	deb_printf("a2_disk: load write latch during read.\n");
178 	break;
179     }
180 
181     return 0x00;
182 }
183 
a2_disk_write(struct a2_disk_card * card,u16 address,u8 data)184 void a2_disk_write(struct a2_disk_card *card, u16 address, u8 data)
185 {
186     a2_line_toggle(card, address);
187 
188     switch (card->qlines & 3) {
189     case 0: /* read data */
190 	break;
191     case 1: /* sense write protect */
192 	break;
193     case 2: /* write byte */
194 	break;
195     case 3: /* load write latch */
196 	card->write_latch = data;
197 	break;
198     }
199 }
200 
201 
202 /* disk image conversion */
203 
204 u8 a2_disk_6_2_write_table[64] = {
205     0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
206     0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
207     0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
208     0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
209     0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
210     0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
211     0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
212     0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
213 };
214 
disk2_nibblize_sector_6_2(u8 * sector_data,u8 * output_buffer)215 int disk2_nibblize_sector_6_2(u8 *sector_data, u8 *output_buffer)
216 {
217     u8 aux_data_buffer[86]; /* 0x56 bytes */
218     const u8 bit_reverse_table[4] = {0, 2, 1, 3,};
219     int i;
220     int j;
221     u8 checksum;
222     u8 last_byte;
223 
224     /* set up aux_data_buffer[] */
225     memset(aux_data_buffer, 0, 86);
226 #if 1
227     j = 85;
228     for (i = 0; i < 0x100; i++) {
229 	aux_data_buffer[j] >>= 2;
230 	aux_data_buffer[j] |= bit_reverse_table[sector_data[i] & 3] << 4;
231 	if (!j--) {
232 	    j = 85;
233 	}
234     }
235 #endif
236     aux_data_buffer[0] >>= 2;
237     aux_data_buffer[1] >>= 2;
238 
239     /* output data to disk buffer */
240     checksum = 0;
241     j = 0;
242     for (i = 85; i >= 0; i--) {
243 	last_byte = aux_data_buffer[i];
244 	output_buffer[j++] = a2_disk_6_2_write_table[checksum ^ last_byte];
245 	checksum = last_byte;
246     }
247     for (i = 0; i < 0x100; i++) {
248 	last_byte = sector_data[i] >> 2;
249 	output_buffer[j++] = a2_disk_6_2_write_table[checksum ^ last_byte];
250 	checksum = last_byte;
251     }
252     output_buffer[j++] = a2_disk_6_2_write_table[checksum];
253 
254     return j;
255 }
256 
disk2_write_address_prologue(u8 * output_buffer)257 int disk2_write_address_prologue(u8 *output_buffer)
258 {
259     output_buffer[0] = 0xd5;
260     output_buffer[1] = 0xaa;
261     output_buffer[2] = 0x96;
262     return 3;
263 }
264 
disk2_write_data_prologue(u8 * output_buffer)265 int disk2_write_data_prologue(u8 *output_buffer)
266 {
267     output_buffer[0] = 0xd5;
268     output_buffer[1] = 0xaa;
269     output_buffer[2] = 0xad;
270     return 3;
271 }
272 
disk2_write_epilogue(u8 * output_buffer)273 int disk2_write_epilogue(u8 *output_buffer)
274 {
275     output_buffer[0] = 0xde;
276     output_buffer[1] = 0xaa;
277     output_buffer[2] = 0xeb;
278     return 3;
279 }
280 
disk2_write_data_field(u8 * sector_data,u8 * output_buffer)281 int disk2_write_data_field(u8 *sector_data, u8 *output_buffer)
282 {
283     int length;
284 
285     length = disk2_write_data_prologue(output_buffer);
286     length += disk2_nibblize_sector_6_2(sector_data, output_buffer + length);
287     length += disk2_write_epilogue(output_buffer + length);
288 
289     return length;
290 }
291 
disk2_write_address_field(u8 volume,u8 track,u8 sector,u8 * output_buffer)292 int disk2_write_address_field(u8 volume, u8 track, u8 sector, u8 *output_buffer)
293 {
294     int length;
295     u8 checksum;
296 
297     length = disk2_write_address_prologue(output_buffer);
298 
299     output_buffer[length++] = (volume >> 1) | 0xaa;
300     output_buffer[length++] = volume | 0xaa;
301 
302     output_buffer[length++] = (track >> 1) | 0xaa;
303     output_buffer[length++] = track | 0xaa;
304 
305     output_buffer[length++] = (sector >> 1) | 0xaa;
306     output_buffer[length++] = sector | 0xaa;
307 
308     checksum = volume ^ track ^ sector;
309 
310     output_buffer[length++] = (checksum >> 1) | 0xaa;
311     output_buffer[length++] = checksum | 0xaa;
312 
313     length += disk2_write_epilogue(output_buffer + length);
314 
315     return length;
316 }
317 
disk2_write_sector(u8 volume,u8 track,u8 sector,u8 * sector_data,u8 * output_buffer)318 int disk2_write_sector(u8 volume, u8 track, u8 sector, u8 *sector_data, u8 *output_buffer)
319 {
320     int length;
321     int i;
322 
323     for (i = 0; i < 0x25; i++) {
324 	output_buffer[i] = 0xff;
325     }
326 
327     length = i;
328 
329     length += disk2_write_address_field(volume, track, sector, output_buffer + length);
330     for (i = 0; i < 0x10; i++) {
331 	output_buffer[length++] = 0xff;
332     }
333 
334     length += disk2_write_data_field(sector_data, output_buffer + length);
335 
336     if (length != BYTES_PER_NIB_SECTOR) {
337 	deb_printf("disk2_write_sector(): BUG: wrote 0x%x bytes, expected 0x%x bytes.\n", length, BYTES_PER_NIB_SECTOR);
338     }
339     return length;
340 }
341 
disk2_convert_track(u8 volume,u8 track,u8 * input_buffer,u8 * output_buffer,u8 * translate_table)342 void disk2_convert_track(u8 volume, u8 track, u8 *input_buffer, u8 *output_buffer, u8 *translate_table)
343 {
344     int i;
345 
346     for (i = 0; i < 16; i++) {
347 	disk2_write_sector(volume, track, i,
348 			   input_buffer + (translate_table[i] * 0x100),
349 			   output_buffer + (i * BYTES_PER_NIB_SECTOR));
350     }
351 }
352 
disk2_convert_disk(u8 * input_buffer,u8 * translate_table)353 u8 *disk2_convert_disk(u8 *input_buffer, u8 *translate_table)
354 {
355     u8 *retval;
356     int i;
357 
358     retval = malloc(NIB_FILE_TRACK_LENGTH * TRACKS_PER_DISK);
359 
360     if (!retval) {
361 	return NULL;
362     }
363 
364     for (i = 0; i < TRACKS_PER_DISK; i++) {
365 	disk2_convert_track(254, i, input_buffer + (i * DSK_FILE_TRACK_LENGTH),
366 			    retval + (i * NIB_FILE_TRACK_LENGTH),
367 			    translate_table);
368     }
369 
370     return retval;
371 }
372 
373 u8 disk2_po_translate_table[0x10] = {
374     0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b,
375     0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f,
376 };
377 
378 u8 disk2_do_translate_table[0x10] = {
379     0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04,
380     0x0b, 0x03, 0x0a, 0x02, 0x09, 0x01, 0x08, 0x0f,
381 };
382 
a2_disk_load_disk(struct a2_disk_card * card)383 void a2_disk_load_disk(struct a2_disk_card *card)
384 {
385     rom_file romfile;
386 
387 /*      romfile = read_romimage("../roms/apple2/dc/Dos_3.3.dsk"); */
388 /*      romfile = read_romimage("../roms/apple2/dc/DOS33.po"); */
389 /*      romfile = read_romimage("../roms/apple2/wizardry/dc/Wizardry_I_boot.dsk"); */
390     romfile = read_romimage("../roms/apple2/sysroms/tarnover/dc/2400Game.do");
391 /*      romfile = read_romimage("../roms/apple2/rockys_boots.dsk"); */
392 /*      romfile = read_romimage("../roms/apple2/autodul1.dsk"); */
393 /*      romfile = read_romimage("../roms/apple2/acs/AdvConstSet1of6.dsk"); */
394 /*      romfile = read_romimage("../roms/apple2/ultima_1/U1Boot.dsk"); */
395 /*      romfile = read_romimage("../roms/apple2/Conan1.dsk"); */
396 /*      romfile = read_romimage("../roms/apple2/softporn.dsk"); */
397 /*      romfile = read_romimage("../roms/apple2/ProDOS.do"); */
398     card->disks[0].disk_data = disk2_convert_disk(romfile->data, disk2_do_translate_table);
399     card->disks[0].track_data = card->disks[0].disk_data;
400     card->disks[0].is_inserted = 1;
401 
402     romfile = read_romimage("../roms/apple2/sysroms/tarnover/dc/2400Plyr.do");
403     card->disks[1].disk_data = disk2_convert_disk(romfile->data, disk2_do_translate_table);
404     card->disks[1].track_data = card->disks[1].disk_data;
405     card->disks[1].is_inserted = 1;
406     card->disks[1].is_readonly = 0;
407 }
408 
409 void disk2_remove_disk(struct a2_disk *disk);
410 
disk2_open_callback(struct a2_disk * disk,char * filename)411 void disk2_open_callback(struct a2_disk *disk, char *filename)
412 {
413     rom_file romfile;
414 
415     /* FIXME: is limited to DOS order only */
416     /* FIXME: leaks memory */
417     /* FIXME: is always readonly */
418     romfile = read_romimage(filename);
419     disk->disk_data = disk2_convert_disk(romfile->data, disk2_do_translate_table);
420     disk->track_data = disk[0].disk_data;
421     disk->cur_track = 0;
422     disk->is_inserted = 1;
423     disk->is_readonly = 1;
424 
425     disk->menu[1].callback = (ui_menu_callback)disk2_remove_disk;
426     disk->menu[1].name = "Remove disk";
427     menu_rename_item(&disk->menu[1]);
428 }
429 
430 #define DISK2_FILE_FILTER \
431     "All Supported Files\0*.dsk;*.do;*.po\0" \
432     "Disk images (*.DSK)\0*.dsk\0" \
433     "ProDOS-order images (*.PO)\0*.po" \
434     "DOS-order images (*.DO)\0*.do\0\0"
435 
disk2_insert_disk(struct a2_disk * disk)436 void disk2_insert_disk(struct a2_disk *disk)
437 {
438     menu_file_open_box((ui_open_callback)disk2_open_callback,
439 		       disk, DISK2_FILE_FILTER);
440 }
441 
disk2_remove_disk(struct a2_disk * disk)442 void disk2_remove_disk(struct a2_disk *disk)
443 {
444     /* FIXME: leaks memory */
445     /* FIXME: should sync modified images to disk */
446     disk->is_inserted = 0;
447 
448     disk->menu[1].callback = (ui_menu_callback)disk2_insert_disk;
449     disk->menu[1].name = "Insert disk";
450     menu_rename_item(&disk->menu[1]);
451 }
452 
453 /* initialization */
454 
a2_disk_load_rom(struct a2_disk_card * card)455 void a2_disk_load_rom(struct a2_disk_card *card)
456 {
457     rom_file romfile;
458 
459     romfile = read_romimage(DISK_2_ROM_FILE);
460 
461     if (!romfile) {
462 	/* rom not found */
463 	deb_printf("unable to load \"" DISK_2_ROM_FILE "\", the disk rom file.\n");
464 	return;
465     }
466 
467     card->api.rom = romfile->data;
468 }
469 
470 struct ui_menu disk2_menu[] = {
471     {"disk",        MF_NONE}, /* menu title (maybe) */
472     {"Insert disk", MF_NONE, (ui_menu_callback)disk2_insert_disk},
473     {NULL, 0, NULL}, /* must be last entry */
474 };
475 
disk2_init_menu(struct a2_disk * disk,int disk_num)476 void disk2_init_menu(struct a2_disk *disk, int disk_num)
477 {
478     struct ui_menu *menu;
479 
480     menu = malloc(sizeof(disk2_menu));
481     memcpy(menu, disk2_menu, sizeof(disk2_menu));
482 
483     menu[1].callback_data = disk;
484     disk->menu = menu;
485 
486     apple2_set_child_menu(menu, disk_num);
487 }
488 
apple2_disk_init(struct apple2_mainboard * apple2)489 struct apple2_card *apple2_disk_init(struct apple2_mainboard *apple2)
490 {
491     struct a2_disk_card *retval;
492 
493     retval = malloc(sizeof(*retval));
494 
495     if (!retval) {
496 	return NULL;
497     }
498 
499     a2_disk_load_rom(retval);
500 
501     disk2_init_menu(&retval->disks[0], 0);
502     disk2_init_menu(&retval->disks[1], 1);
503 
504     retval->api.read = (a2_card_read)a2_disk_read;
505     retval->api.write = (a2_card_write)a2_disk_write;
506 
507     retval->motor_on = 0;
508     retval->drive_select = 0;
509     retval->qlines = 0;
510 
511     retval->disks[0].is_inserted = 0;
512     retval->disks[0].is_readonly = 1;
513     retval->disks[0].cur_track = 0;
514 
515     retval->disks[1].is_inserted = 0;
516     retval->disks[1].is_readonly = 1;
517     retval->disks[1].cur_track = 0;
518 
519     return (struct apple2_card *)retval;
520 }
521 
522 /*
523  * $Log: apple2_disk.c,v $
524  * Revision 1.11  2000/08/21 01:13:32  nyef
525  * added preliminary support for file open boxes
526  *
527  * Revision 1.10  2000/08/21 00:33:46  nyef
528  * fixed to initialize the per-disk menus (oops)
529  *
530  * Revision 1.9  2000/08/05 15:42:45  nyef
531  * really fixed warning in disk2_nibblize_sector_6_2()
532  *
533  * Revision 1.8  2000/07/01 15:43:32  nyef
534  * started adding preliminary per-disk menus
535  *
536  * Revision 1.7  2000/05/06 22:35:27  nyef
537  * fixed warning in disk2_nibblize_sector_6_2()
538  *
539  * Revision 1.6  2000/03/13 02:18:05  nyef
540  * fixed track seeks (I hope)
541  *
542  * Revision 1.5  2000/03/12 00:13:59  nyef
543  * fixed the DO skewing table
544  *
545  * Revision 1.4  2000/03/11 22:55:00  nyef
546  * fixed the PO skewing table
547  * added a DO skewing table
548  *
549  * Revision 1.3  2000/03/11 22:13:00  nyef
550  * added code to convert PO image files to NIB format (used internally)
551  *
552  * Revision 1.2  2000/03/11 16:32:00  nyef
553  * added preliminary track seeks, disk reading, and disk writing
554  *
555  * Revision 1.1  2000/03/11 02:29:58  nyef
556  * Initial revision
557  *
558  */
559