From 66937f3858c1dcc8fee7ba51b83c4d2ffb16c991 Mon Sep 17 00:00:00 2001 From: Michel Pollet Date: Mon, 12 Feb 2024 16:58:13 +0000 Subject: [PATCH] New DiskII Driver. Woz here we come Complete replacement from the previous version, see readme for details... Short story is that theres support for WOZ 1/2 read/write disks, as now emulate the low level LSS state machine. Signed-off-by: Michel Pollet --- src/drivers/mii_disk2.c | 661 ++++++++++++++++++++--------------- src/format/dsk.c | 472 ------------------------- src/format/empty.c | 36 -- src/format/mii_dd.c | 31 +- src/format/mii_dd.h | 5 + src/format/mii_disk_format.c | 95 ----- src/format/mii_disk_format.h | 35 -- src/format/mii_floppy.c | 470 +++++++++++++++++++++++++ src/format/mii_floppy.h | 58 +++ src/format/mii_woz.h | 94 +++++ src/format/nib.c | 100 ------ ui_gl/mii_mui_2dsk.c | 2 +- 12 files changed, 1042 insertions(+), 1017 deletions(-) delete mode 100644 src/format/dsk.c delete mode 100644 src/format/empty.c delete mode 100644 src/format/mii_disk_format.c delete mode 100644 src/format/mii_disk_format.h create mode 100644 src/format/mii_floppy.c create mode 100644 src/format/mii_floppy.h create mode 100644 src/format/mii_woz.h delete mode 100644 src/format/nib.c diff --git a/src/drivers/mii_disk2.c b/src/drivers/mii_disk2.c index 36328db..5c35e6b 100644 --- a/src/drivers/mii_disk2.c +++ b/src/drivers/mii_disk2.c @@ -6,201 +6,120 @@ * SPDX-License-Identifier: MIT */ -// Shamelesly lifted from periph/disk2.c -// -// Copyright (c) 2023 Micah John Cowan. -// This code is licensed under the MIT license. -// See the accompanying LICENSE file for details. - -#include -#include -#include +#define _GNU_SOURCE // for asprintf #include #include - +#include #include -#include -#include - #include "mii.h" #include "mii_bank.h" #include "mii_disk2.h" #include "mii_rom_disk2.h" -#include "mii_disk_format.h" +#include "mii_woz.h" +#include "mii_floppy.h" -//#define DISK_DEBUG -#ifdef DISK_DEBUG -# define D2DBG(...) do { \ - if (c->pr_count == 1) \ - fprintf(stderr, __VA_ARGS__); \ - } while(0) -#else -# define D2DBG(...) -#endif +enum { + // bits used to address the LSS ROM using lss_mode + WRITE_BIT = 0, + LOAD_BIT = 1, + QA_BIT = 2, + RP_BIT = 3, +}; typedef struct mii_card_disk2_t { - uint32_t timer; - bool motor_on; - bool drive_two; - bool write_mode; - union { - struct { - DiskFormatDesc disk1; - DiskFormatDesc disk2; - }; - DiskFormatDesc disk[2]; - }; - uint8_t data_register; // only used for write - bool steppers[4]; - int cog1; - int cog2; -// int pr_count; // debug print + mii_dd_t drive[2]; + mii_floppy_t floppy[2]; + uint8_t selected; + + uint8_t timer_off; + uint8_t timer_lss; + + uint8_t write_register; + uint8_t head : 4; // bits are shifted in there + uint8_t clock : 3; // LSS clock cycles, read a bit when 0 + uint8_t lss_state : 4, // Sequence state + lss_mode : 4; // WRITE/LOAD/SHIFT/QA/RP etc + uint8_t lss_prev_state; // for write bit + uint8_t lss_skip; + uint8_t data_register; } mii_card_disk2_t; -//static const size_t dsk_disksz = 143360; -int disk2_debug = 0; +static void +_mii_disk2_lss_tick( + mii_card_disk2_t *c ); -static DiskFormatDesc * -active_disk_obj( - mii_card_disk2_t *c) +// debug, used for mish, only supports one card tho (yet) +static mii_card_disk2_t *_mish_d2 = NULL; + +/* + * This timer is used to turn off the motor after a second + */ +static uint64_t +_mii_floppy_motor_off_cb( + mii_t * mii, + void * param ) { - return c->drive_two ? &c->disk2 : &c->disk1; -} - -#if 0 -bool -drive_spinning( - mii_card_disk2_t *c) -{ - return c->motor_on; -} - -int -active_disk( - mii_card_disk2_t *c) -{ - return c->drive_two ? 2 : 1; -} -int -eject_disk( - mii_card_disk2_t *c, - int drive) -{ - if (c->motor_on && active_disk(c) == drive) { - return -1; - } - init(c); // to make sure - - if (drive == 1) { - c->disk1.eject(&c->disk1); - c->disk1 = disk_format_load(NULL); - } else if (drive == 2) { - c->disk2.eject(&c->disk2); - c->disk2 = disk_format_load(NULL); - } + mii_card_disk2_t *c = param; + mii_floppy_t *f = &c->floppy[c->selected]; + printf("%s drive %d off\n", __func__, c->selected); + if (c->drive[c->selected].file && f->tracks_dirty) + mii_floppy_update_tracks(f, c->drive[c->selected].file); + f->motor = 0; return 0; } -int -insert_disk( +/* + * This (tries) to be called every cycle, it never happends in practice, + * as all the instructions can use multiple cycles by CPU runs. + * But I can retreive the overshoot cycles and call the LSS as many times + * as needed to 'catch up' + */ +static uint64_t +_mii_floppy_lss_cb( + mii_t * mii, + void * param ) +{ + mii_card_disk2_t *c = param; + mii_floppy_t *f = &c->floppy[c->selected]; + if (!f->motor) + return 0; // stop timer, motor is now off + // delta is ALWAYS negative, or zero here + int32_t delta = mii_timer_get(mii, c->timer_lss); + uint64_t ret = -delta + 1; + do { + _mii_disk2_lss_tick(c); + _mii_disk2_lss_tick(c); + } while (delta++ < 0); + return ret; +} + +static uint8_t +_mii_disk2_switch_track( + mii_t *mii, mii_card_disk2_t *c, - int drive, - const char *path) + int delta) { - int err = eject_disk(c, drive); - if (err != 0) return err; + mii_floppy_t *f = &c->floppy[c->selected]; + int qtrack = f->qtrack + delta; + if (qtrack < 0) qtrack = 0; + if (qtrack >= 35 * 4) qtrack = (35 * 4) -1; - // Make sure we're inserted to slot 6 - // XXX should check for error/distinguish if we're already in that slot - (void) periph_slot_reg(6, &disk2card); + if (qtrack == f->qtrack) + return f->qtrack; - if (err) { - return -1; // XXX should be a distinguishable err code - } - if (drive == 1) { - c->disk1 = disk_format_load(path); - } else if (drive == 2) { - c->disk2 = disk_format_load(path); - } + uint8_t track_id = f->track_id[f->qtrack]; + if (track_id != MII_FLOPPY_RANDOM_TRACK_ID) + printf("NEW TRACK D%d: %d\n", c->selected, track_id); + uint8_t track_id_new = f->track_id[qtrack]; - return 0; -} -#endif - -// NOTE: cog "left" and "right" refers only to the number line, -// and not the physical cog or track head movement. -static bool -cogleft( - mii_card_disk2_t *c, - int *cog) -{ - int cl = ((*cog) + 3) % 4; // position to the "left" of cog - return (!c->steppers[(*cog)] && c->steppers[cl]); -} - -// NOTE: cog "left" and "right" refers only to the number line, -// and not the physical cog or track head movement. -static bool -cogright( - mii_card_disk2_t *c, - int *cog) -{ - int cr = ((*cog) + 1) % 4; // position to the "right" of cog - return (!c->steppers[(*cog)] && c->steppers[cr]); -} - -static int * -active_cog( - mii_card_disk2_t *c) -{ - return c->drive_two? &c->cog2 : &c->cog1; -} - -static void -adjust_track( - mii_card_disk2_t *c) -{ - DiskFormatDesc *disk = active_disk_obj(c); - int *cog = active_cog(c); - D2DBG("halftrack: "); - if (cogleft(c, cog)) { - *cog = ((*cog) + 3) % 4; - if (disk->halftrack > 0) --disk->halftrack; - D2DBG("dec to %d", disk->halftrack); - } else if (cogright(c, cog)) { - *cog = ((*cog) + 1) % 4; - if (disk->halftrack < 69) ++disk->halftrack; - D2DBG("inc to %d", disk->halftrack); - } else { - D2DBG("no change (%d)", disk->halftrack); - } -} - -static void -stepper_motor( - mii_card_disk2_t *c, - uint8_t psw) -{ - uint8_t phase = psw >> 1; - bool onoff = (psw & 1) != 0; - - D2DBG("phase %d %s ", (int)phase, onoff? "on, " : "off,"); - c->steppers[phase] = onoff; - adjust_track(c); -} - -static inline uint8_t encode4x4(mii_card_disk2_t *c, uint8_t orig) -{ - return (orig | 0xAA); -} - -static void turn_off_motor(mii_card_disk2_t *c) -{ - c->motor_on = false; - DiskFormatDesc *disk = active_disk_obj(c); - disk->spin(disk, false); -// event_fire_disk_active(0); + /* adapt the bit position from one track to the others, from WOZ specs */ + uint32_t track_size = f->tracks[track_id].bit_count; + uint32_t new_size = f->tracks[track_id_new].bit_count; + uint32_t new_pos = f->bit_position * new_size / track_size; + f->bit_position = new_pos; + f->qtrack = qtrack; + return f->qtrack; } static int @@ -209,30 +128,34 @@ _mii_disk2_init( struct mii_slot_t *slot ) { mii_card_disk2_t *c = calloc(1, sizeof(*c)); - slot->drv_priv = c; - c->disk1 = disk_format_load(NULL); - c->disk2 = disk_format_load(NULL); + printf("%s loading in slot %d\n", __func__, slot->id + 1); uint16_t addr = 0xc100 + (slot->id * 0x100); mii_bank_write( &mii->bank[MII_BANK_CARD_ROM], addr, mii_rom_disk2, 256); - return 0; -} - -static void -_mii_disk2_run( - mii_t * mii, - struct mii_slot_t *slot) -{ - mii_card_disk2_t *c = slot->drv_priv; - if (c->timer && c->timer <= mii->video.frame_count) { - printf("%s turning off motor\n", __func__); - c->timer = 0; - turn_off_motor(c); + for (int i = 0; i < 2; i++) { + mii_dd_t *dd = &c->drive[i]; + dd->slot_id = slot->id + 1; + dd->drive = i + 1; + dd->slot = slot; + asprintf((char **)&dd->name, "Disk ][ S:%d D:%d", + dd->slot_id, dd->drive); + mii_floppy_init(&c->floppy[i]); + c->floppy[i].id = i; } + mii_dd_register_drives(&mii->dd, c->drive, 2); + + c->timer_off = mii_timer_register(mii, + _mii_floppy_motor_off_cb, c, 0, + "Disk ][ motor off"); + c->timer_lss = mii_timer_register(mii, + _mii_floppy_lss_cb, c, 0, + "Disk ][ LSS"); + _mish_d2 = c; + return 0; } static uint8_t @@ -241,99 +164,64 @@ _mii_disk2_access( uint16_t addr, uint8_t byte, bool write) { mii_card_disk2_t *c = slot->drv_priv; + mii_floppy_t * f = &c->floppy[c->selected]; uint8_t ret = 0; + if (write) { + // printf("WRITE PC:%04x %4.4x: %2.2x\n", mii->cpu.PC, addr, byte); + c->write_register = byte; + } int psw = addr & 0x0F; - - if (c->timer) - c->timer = mii->video.frame_count + 60; - uint8_t last = -1; - if (disk2_debug && last != psw + (write? 0x10 : 0)) - printf("disk sw $%02X, PC = $%04X\n", psw, mii->cpu.PC); - last = psw + (write? 0x10 : 0); - if (write) - c->data_register = byte; // ANY write sets data register - if (psw < 8) { - stepper_motor(c, psw); - } else switch (psw) { - case 0x08: - if (c->motor_on) { - c->timer = mii->video.frame_count + 60; - //frame_timer(60, turn_off_motor); + int p = psw >> 1, on = psw & 1; + switch (psw) { + case 0x00 ... 0x07: { + if (on) { + if ((f->stepper + 3) % 4 == p) + _mii_disk2_switch_track(mii, c, -2); + else if ((f->stepper + 1) % 4 == p) + _mii_disk2_switch_track(mii, c, 2); + f->stepper = p; } - break; + } break; + case 0x08: case 0x09: { -// frame_timer_cancel(turn_off_motor); - c->timer = 0; - c->motor_on = true; - DiskFormatDesc *disk = active_disk_obj(c); - disk->spin(disk, true); - if (disk2_debug) - printf("%s turning on motor %d\n", __func__, c->drive_two); - // event_fire_disk_active(drive_two? 2 : 1); + // motor on/off + if (on) { + mii_timer_set(mii, c->timer_off, 0); + mii_timer_set(mii, c->timer_lss, 1); + f->motor = 1; + } else { + if (!mii_timer_get(mii, c->timer_off)) { + mii_timer_set(mii, c->timer_off, 1000000); // one second + } + } } break; case 0x0A: - if (c->motor_on && c->drive_two) { - c->disk2.spin(&c->disk2, false); - c->disk1.spin(&c->disk1, true); - } - c->drive_two = false; - if (c->motor_on) { - // event_fire_disk_active(1); - } - break; - case 0x0B: - if (c->motor_on && !c->drive_two) { - c->disk1.spin(&c->disk1, false); - c->disk2.spin(&c->disk2, true); - } - c->drive_two = true; - if (c->motor_on) { - // event_fire_disk_active(2); - } - break; - case 0x0C: { - DiskFormatDesc *disk = active_disk_obj(c); - if (!c->motor_on) { - // do nothing - } else if (c->write_mode) { - // XXX ignores timing - disk->write_byte(disk, c->data_register); - c->data_register = 0; // "shifted out". - } else { - // XXX any even-numbered switch can be used - // to read a byte. But for now we do so only - // through the sanctioned switch for that purpose. - ret = c->data_register = disk->read_byte(disk); - // printf("read byte %02X\n", ret); + case 0x0B: { + if (on != c->selected) { + c->selected = on; + printf("SELECTED DRIVE: %d\n", c->selected); + c->floppy[on].motor = f->motor; + f->motor = 0; } } break; + case 0x0C: case 0x0D: -#if 0 - if (!motor_on || drive_two) { - // do nothing - } else if (write_mode) { - data_register = (val == -1? 0: val); - } else { - // XXX should return whether disk is write-protected... - } -#endif + c->lss_mode = (c->lss_mode & ~(1 << LOAD_BIT)) | (!!on << LOAD_BIT); break; case 0x0E: - c->write_mode = false; - if (disk2_debug) - printf("%s write mode off\n", __func__); - break; case 0x0F: - c->write_mode = true; - if (disk2_debug) - printf("%s write mode on\n", __func__); + c->lss_mode = (c->lss_mode & ~(1 << WRITE_BIT)) | (!!on << WRITE_BIT); break; - default: - ; } - - D2DBG("\n"); + /* + * Here we run one LSS cycle ahead of 'schedule', just because it allows + * the write protect bit to be loaded if needed, it *has* to run before + * we return this value, so we marked a skip and run it here. + */ + _mii_disk2_lss_tick(c); + c->lss_skip++; + ret = on ? byte : c->data_register; return ret; } @@ -353,14 +241,15 @@ _mii_disk2_command( break; case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + 2 - 1: int drive = cmd - MII_SLOT_DRIVE_LOAD; - if (c->disk[drive].privdat) { - c->disk[drive].eject(&c->disk[drive]); - } const char *filename = param; - printf("%s: drive %d loading %s\n", __func__, drive, - filename); - c->disk[drive] = disk_format_load( - filename && *filename ? filename : NULL); + mii_dd_file_t *file = NULL; + if (filename && *filename) { + file = mii_dd_file_load(&mii->dd, filename, O_RDWR); + if (!file) + return -1; + } + mii_dd_drive_load(&c->drive[drive], file); + mii_floppy_load(&c->floppy[drive], file); break; } return 0; @@ -371,7 +260,231 @@ static mii_slot_drv_t _driver = { .desc = "Apple Disk ][", .init = _mii_disk2_init, .access = _mii_disk2_access, - .run = _mii_disk2_run, .command = _mii_disk2_command, }; MI_DRIVER_REGISTER(_driver); + + +/* Sather Infamous Table, pretty much verbatim + WRITE + -----------READ PULSE--------- -—------NO READ PULSE-------- + ----SHIFT----- -----LOAD----- ----SHIFT----- -----LOAD----- +SEQ --QA'- --QA-- --QA'- --QA-- --QA'- --QA-- --QA'- --QA-- +0- 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP +1- 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP +2- 39-SL0 39-SL0 3B-LD 3B-LD 39-SL0 39-SL0 3B-LD 3B-LD +3- 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP +4- 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP +5- 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP +6- 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP +7- 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP +8- 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP +9- A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP +A- B9-SL0 B9-SL0 BB-LD BB-LD B9-SL0 B9-SL0 BB-LD BB-LD +B- C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP +C- D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP +D- E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP +E- F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP +F- 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP + READ + ------------SHIFT------------ -------------LOAD------------ + -----QA'------ ------QA------ -----QA'------ -----QA------ + --RP-- -NORP- --RP-- -NORP- --RP-- -NORP- --RP-- -NORP- +0- 18-NOP 18-NOP 18-NOP 18-NOP 0A-SR 0A-SR 0A-SR 0A-SR +1- 2D-SL1 2D-SL1 38-NOP 38-NOP 0A-SR 0A-SR 0A-SR 0A-SR +2- D8-NOP 38-NOP 08-NOP 28-NOP 0A-SR 0A-SR 0A-SR 0A-SR +3- D8-NOP 48-NOP 48-NOP 48-NOP 0A-SR 0A-SR 0A-SR 0A-SR +4- D8-NOP 58-NOP D8-NOP 58-NOP 0A-SR 0A-SR 0A-SR 0A-SR +5- D8-NOP 68-NOP D8-NOP 68-NOP 0A-SR 0A-SR 0A-SR 0A-SR +6- D8-NOP 78-NOP D8-NOP 78-NOP 0A-SR 0A-SR 0A-SR 0A-SR +7- D8-NOP 88-NOP D8-NOP 88-NOP 0A-SR 0A-SR 0A-SR 0A-SR +8- D8-NOP 98-NOP D8-NOP 98-NOP 0A-SR 0A-SR 0A-SR 0A-SR +9- D8-NOP 29-SL0 D8-NOP A8-NOP 0A-SR 0A-SR 0A-SR 0A-SR +A- CD-SL1 BD-SL1 D8-NOP B8-NOP 0A-SR 0A-SR 0A-SR 0A-SR +B- D9-SL0 59-SL0 D8-NOP C8-NOP 0A-SR 0A-SR 0A-SR 0A-SR +C- D9-SL0 D9-SL0 D8-NOP A0-CLR 0A-SR 0A-SR 0A-SR 0A-SR +D- D8-NOP 08-NOP E8-NOP E8-NOP 0A-SR 0A-SR 0A-SR 0A-SR +E- FD-SL1 FD-SL1 F8-NOP F8-NOP 0A-SR 0A-SR 0A-SR 0A-SR +F- DD-SL1 4D-SL1 E0-CLR E0-CLR 0A-SR 0A-SR 0A-SR 0A-SR +*/ +enum { + WRITE = (1 << WRITE_BIT), + LOAD = (1 << LOAD_BIT), + QA1 = (1 << QA_BIT), + RP1 = (1 << RP_BIT), + /* This keeps the transposed table more readable, as it looks like the book */ + READ = 0, SHIFT = 0, QA0 = 0, RP0 = 0, +}; +// And this is the same Sather table (Figure 9.11, page 9-20) but transposed +static const uint8_t lss_rom16s[16][16] = { +[WRITE|RP0|SHIFT|QA0]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x88 }, +[WRITE|RP0|SHIFT|QA1]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x08 }, +[WRITE|RP0|LOAD|QA0]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x88 }, +[WRITE|RP0|LOAD|QA1]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x08 }, +[WRITE|RP1|SHIFT|QA0]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x88 }, +[WRITE|RP1|SHIFT|QA1]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x08 }, +[WRITE|RP1|LOAD|QA0]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x88 }, +[WRITE|RP1|LOAD|QA1]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x08 }, +[READ|SHIFT|QA0|RP1]={ 0x18,0x2D,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xCD,0xD9,0xD9,0xD8,0xFD,0xDD }, +[READ|SHIFT|QA0|RP0]={ 0x18,0x2D,0x38,0x48,0x58,0x68,0x78,0x88,0x98,0x29,0xBD,0x59,0xD9,0x08,0xFD,0x4D }, +[READ|SHIFT|QA1|RP1]={ 0x18,0x38,0x08,0x48,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xE8,0xF8,0xE0 }, +[READ|SHIFT|QA1|RP0]={ 0x18,0x38,0x28,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB8,0xC8,0xA0,0xE8,0xF8,0xE0 }, +[READ|LOAD|QA0|RP1]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A }, +[READ|LOAD|QA0|RP0]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A }, +[READ|LOAD|QA1|RP1]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A }, +[READ|LOAD|QA1|RP0]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A }, +}; + +static void +_mii_disk2_lss_tick( + mii_card_disk2_t *c ) +{ + if (c->lss_skip) { + c->lss_skip--; + return; + } + mii_floppy_t *f = &c->floppy[c->selected]; + + c->lss_mode = (c->lss_mode & ~(1 << QA_BIT)) | + (!!(c->data_register & 0x80) << QA_BIT); + if (c->clock++ == 0) { // clock is clipped to 3 bits + uint8_t track_id = f->track_id[f->qtrack]; + + uint32_t byte_index = f->bit_position >> 3; + uint8_t bit_index = 7 - (f->bit_position & 7); + if (!(c->lss_mode & (1 << WRITE_BIT))) { + uint8_t bit = f->tracks[track_id].data[byte_index]; + bit = (bit >> bit_index) & 1; + c->head = (c->head << 1) | bit; + // see WOZ spec for how we do this here + if ((c->head & 0xf) == 0) { + bit = f->tracks[MII_FLOPPY_RANDOM_TRACK_ID].data[byte_index]; + bit = (bit >> bit_index) & 1; + } else { + bit = (c->head >> 1) & 1; + } + c->lss_mode = (c->lss_mode & ~(1 << RP_BIT)) | (bit << RP_BIT); + } + if ((c->lss_mode & (1 << WRITE_BIT))) { + uint8_t msb = c->data_register >> 7; + + if (!f->tracks[track_id].dirty) { + printf("DIRTY TRACK %2d \n", track_id); + f->tracks[track_id].dirty = 1; + f->tracks_dirty = 1; + } + f->tracks[track_id].data[byte_index] &= ~(1 << bit_index); + f->tracks[track_id].data[byte_index] |= (msb << bit_index); + } + f->bit_position = (f->bit_position + 1) % f->tracks[track_id].bit_count; + } + const uint8_t *rom = lss_rom16s[c->lss_mode]; + uint8_t cmd = rom[c->lss_state]; + uint8_t next = cmd >> 4; + uint8_t action = cmd & 0xF; + + if (action & 0b1000) { // Table 9.3 in Sather's book + switch (action & 0b0011) { + case 1: // SL0/1 + c->data_register <<= 1; + c->data_register |= !!(action & 0b0100); + break; + case 2: // SR + c->data_register = (c->data_register >> 1) | + (!!f->write_protected << 7); + break; + case 3: // LD + c->data_register = c->write_register; + break; + } + } else { // CLR + c->data_register = 0; + } + c->lss_state = next; + // read pulse only last one cycle.. + c->lss_mode &= ~(1 << RP_BIT); +} + + +static void +_mii_mish_d2( + void * param, + int argc, + const char * argv[]) +{ +// mii_t * mii = param; + if (!_mish_d2) { + printf("No Disk ][ card installed\n"); + return; + } + static int sel = 0; + if (!argv[1] || !strcmp(argv[1], "list")) { + mii_card_disk2_t *c = _mish_d2; + for (int i = 0; i < 2; i++) { + mii_floppy_t *f = &c->floppy[i]; + printf("Drive %d %s\n", f->id, f->write_protected ? "WP" : "RW"); + printf("\tMotor: %3s qtrack:%d Bit %6d\n", + f->motor ? "ON" : "OFF", f->qtrack, f->bit_position); + } + return; + } + if (!strcmp(argv[1], "sel")) { + if (argv[2]) { + sel = atoi(argv[2]); + } + printf("Selected drive: %d\n", sel); + return; + } + if (!strcmp(argv[1], "wp")) { + if (argv[2]) { + int wp = atoi(argv[2]); + mii_card_disk2_t *c = _mish_d2; + mii_floppy_t *f = &c->floppy[sel]; + f->write_protected = wp; + } + printf("Drive %d Write protected: %d\n", sel, + _mish_d2->floppy[sel].write_protected); + return; + } + // dump a track, specify track number and number of bytes + if (!strcmp(argv[1], "track")) { + if (argv[2]) { + int track = atoi(argv[2]); + int count = 256; + if (argv[3]) + count = atoi(argv[3]); + mii_card_disk2_t *c = _mish_d2; + mii_floppy_t *f = &c->floppy[sel]; + uint8_t *data = f->tracks[track].data; + + for (int i = 0; i < count; i += 8) { + uint8_t *line = data + i; + #if 0 + for (int bi = 0; bi < 8; bi++) { + uint8_t b = line[bi]; + for (int bbi = 0; bbi < 8; bbi++) { + printf("%c", (b & 0x80) ? '1' : '0'); + b <<= 1; + } + } + printf("\n"); + #endif + for (int bi = 0; bi < 8; bi++) + printf("%8x", line[bi]); + printf("\n"); + } + } else { + printf("track [count]\n"); + } + return; + } +} + +#include "mish.h" + +MISH_CMD_NAMES(d2, "d2"); +MISH_CMD_HELP(d2, + "d2: disk ][ internals", + " : dump status" + ); +MII_MISH(d2, _mii_mish_d2); diff --git a/src/format/dsk.c b/src/format/dsk.c deleted file mode 100644 index 95089f5..0000000 --- a/src/format/dsk.c +++ /dev/null @@ -1,472 +0,0 @@ -// format/dsk.c -// -// Copyright (c) 2023 Micah John Cowan. -// This code is licensed under the MIT license. -// See the accompanying LICENSE file for details. - -#include -#include -#include -#include -#include -#include - -#include "mii_disk_format.h" - -#define NIBBLE_SECTOR_SIZE 416 -#define NIBBLE_TRACK_SIZE 6656 -#define DSK_SECTOR_SIZE 256 -#define MAX_SECTORS 16 -#define VOLUME_NUMBER 254 -#define DSK_TRACK_SIZE (DSK_SECTOR_SIZE * MAX_SECTORS) - -#define byte uint8_t - -struct dskprivdat { - const char *path; - byte *realbuf; - byte *buf; - const byte *secmap; - int bytenum; - uint64_t dirty_tracks; -}; -static const struct dskprivdat datinit = { 0 }; - -static const size_t nib_disksz = 232960; -static const size_t dsk_disksz = 143360; - -// DOS 3.3 Physical sector order (index is physical sector, -// value is DOS sector) -const byte DO[] = { - 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, - 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF -}; - -// ProDOS Physical sector order (index is physical sector, -// value is ProDOS sector). -const byte PO[] = { - 0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb, - 0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf -}; - -const byte TRANS62[] = { - 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, - 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, - 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, - 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, - 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, - 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, - 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, - 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff -}; - -static const int DETRANS62[] = { - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 0x00, 0x01, - -1, -1, 0x02, 0x03, -1, 0x04, 0x05, 0x06, - -1, -1, -1, -1, -1, -1, 0x07, 0x08, - -1, -1, -1, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, - -1, -1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, - -1, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, - -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, 0x1B, -1, 0x1C, 0x1D, 0x1E, - -1, -1, -1, 0x1F, -1, -1, 0x20, 0x21, - -1, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - -1, -1, -1, -1, -1, 0x29, 0x2A, 0x2B, - -1, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, - -1, -1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - -1, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F -}; - -static void realign_tracks(DiskFormatDesc *desc) -{ - /* - When we unpack a .dsk into nibblized form, it's - automatically aligned neatly within our track-storage - boundaries within the disk buffer, because that's how - we laid 'em out. - - When a sector is written or updated, it will - tend to stay aligned, because the program had to - find the existing sector to write to, to keep things - in order. - - However, in the event of reformatting, there are no - guarantees about the track being aligned conveniently. - The formatting program can start at any old position - and just start writing, because no existing data is going - to be preserved. - - To deal with this, we could either: - 1) Be prepared to loop back around our track's buffer, - mid-sector - 2) Re-align the track at our convenience, to start - somewhere that we know for sure can't be the middle of - sector data. - - I've opted for option (2). It would be a bad option if - we're reading and writing a .nib file, because we'd be - unnecessarily altering the file's structure (in cases - where it had NOT been reformatted), but it's perfectly - fine when we'll be discarding the nibblized format anyway. - - We handle this by seeking forward to the first sector-field - start boundary (D5 AA 96) that we can find, and make that - the new "start" of our track. It doesn't matter if it's a - false-start that doesn't start a real sector-field, because one - thing we know for *sure* is that first D5 can't be found - in the middle of a legitimate header or data field. - */ - - struct dskprivdat *dat = desc->privdat; - byte *buf = dat->buf; - byte *secbuf = malloc(NIBBLE_TRACK_SIZE); - for (int t=0; t != NUM_TRACKS; ++t) { - byte *tstart = buf + (t * NIBBLE_TRACK_SIZE); - byte *tend = buf + ((t + 1) * NIBBLE_TRACK_SIZE); - byte *talign; - for (talign = tstart; talign <= (tend - 3); ++talign) { - if (talign[0] == 0xD5 && talign[1] == 0xAA && talign[2] == 0x96) { - if (talign == tstart) { - // Nothing to do, already aligned. - } else { - size_t rollsz = talign - tstart; - memcpy(secbuf, tstart, rollsz); - memmove(tstart, talign, tend-talign); - memcpy(tend - rollsz, secbuf, rollsz); - } - break; // handle next track - } - } - } - free(secbuf); -} - -static void implodeDo(DiskFormatDesc *desc) -{ - struct dskprivdat *dat = desc->privdat; - - realign_tracks(desc); - - const byte *rd = dat->buf; // nibble buf - const byte *end = dat->buf + nib_disksz; - - bool warned = false; - - for (;;) { - // Scan forward for a sector header - const int sector_hdr_sz = 11; // counts prologue, but not epilogue - for (;;) { - // This is the only place we're "allowed" to end processing. - if (rd >= (end - sector_hdr_sz)) goto done; - if (rd[0] == 0xD5 && rd[1] == 0xAA && rd[2] == 0x96) break; - ++rd; - } - -header: - rd += 3; - int v = ((rd[0] << 1) | 0x1) & rd[1]; - rd += 2; - int t = ((rd[0] << 1) | 0x1) & rd[1]; - rd += 2; - int s = ((rd[0] << 1) | 0x1) & rd[1]; - rd += 2; - int checkSum = ((rd[0] << 1) | 0x1) & rd[1]; - rd += 2; - - if (checkSum != (v ^ t ^ s)) { - WARN("Sector header checksum failed, t=%d s=%d" - " at nibblized byte %zu.\n", t, s, rd - dat->buf); - WARN("Probable disk corruption for %s\n", dat->path); - } - - byte truet = (rd - dat->buf)/NIBBLE_TRACK_SIZE; - if (t != truet) { - WARN("Sector header lying about track number" - " at nibblized pos %zu\n", (size_t)(rd - dat->buf)); - WARN(" (says %d but we're on track %d). Skipping sector.\n", - (int)t, (int)truet); - continue; - } - if (s >= MAX_SECTORS) { - WARN("Sector header sec# too high (%d; max is %d).\n", - (int)s, (int)MAX_SECTORS); - WARN(" Skipping sector.\n"); - continue; - } - - // const int data_field_sz = 0x15A; //counts prologue, not epilogue - for (;;) { - if (rd >= (end - 0x15A)) goto bail; - if (rd[0] == 0xD5 && rd[1] == 0xAA) { - if (rd[2] == 0x96) goto header; - if (rd[2] == 0xAD) break; - } - ++rd; - } - - rd += 3; - - // Translate sector data field - { - int s_ = dat->secmap[s]; - byte *data = dat->realbuf + (t * DSK_TRACK_SIZE) - + (s_ * DSK_SECTOR_SIZE); - byte data2[0x56]; - byte last = 0; - int val; - - if (0) { - WARN("Translating track %d, phys sec %d (image sec %d)\n", - t, s, s_); - WARN(" starting at dsk pos %zu\n", (size_t)(data - dat->realbuf)); - } - - for (int j = 0x55; j >= 0; --j) { - val = DETRANS62[*rd - 0x80]; - if (val == -1 && !warned) { - warned = true; - WARN("Untranslatable nibble at (nibblized) pos %zu," - " disk %s.\n", (size_t)(rd - dat->buf), dat->path); - if (rd <= end - 4) { - WARN("%02X %02X %02X %02X [%02X] %02X %02X %02X\n", - rd[-4], rd[-3], rd[-2], rd[-1], - rd[0], rd[1], rd[2], rd[3]); - } - WARN("CORRUPT DISK SAVE TO %s\n", dat->path); - } - val ^= last; - data2[j] = val; - last = val; - ++rd; - } - - for (int j = 0; j < 0x100; ++j) { - val = DETRANS62[*rd - 0x80]; - if (val == -1 && !warned) { - warned = true; - WARN("Untranslatable nibble at t=%d, phys s=%d," - " disk %s.\n", t, s, dat->path); - WARN("CORRUPT DISK SAVE TO %s\n", dat->path); - } - val ^= last; - data[j] = val; - last = val; - ++rd; - } - - int checkSum = DETRANS62[*rd++ - 0x80]; - if (checkSum != -1) checkSum ^= last; - if (checkSum != 0) { - WARN("Bad sector data checksum at t=%d, phys s=%d," - " disk %s.\n", t, s, dat->path); - } - - for (int k = 0, j = 0x55; k < 0x100; ++k) { - data[k] <<= 1; - if ((data2[j] & 0x1) != 0) { - data[k] |= 0x01; - } - data2[j] >>= 1; - - data[k] <<= 1; - if ((data2[j] & 0x01) != 0) { - data[k] |= 0x01; - } - data2[j] >>= 0x01; - - if (--j < 0) j = 0x55; - } - } - } - -bail: - WARN("Error translating to dsk: ended mid-sector!\n"); - WARN("Probable disk corruption for %s\n", dat->path); -done: - return; -} - -static void spin(DiskFormatDesc *desc, bool b) -{ - struct dskprivdat *dat = desc->privdat; - if (!b && dat->dirty_tracks != 0) { - implodeDo(desc); - // For now, sync the entire disk - errno = 0; - int err = msync(dat->realbuf, dsk_disksz, MS_SYNC); - if (err < 0) { - DIE(1,"Couldn't sync to disk file %s: %s\n", - dat->path, strerror(errno)); - } - dat->dirty_tracks = 0; - } -} - -static byte read_byte(DiskFormatDesc *desc) -{ - struct dskprivdat *dat = desc->privdat; - size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE; - pos += (dat->bytenum % NIBBLE_TRACK_SIZE); - byte val = dat->buf[pos]; - dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE; - return val; -} - -static void write_byte(DiskFormatDesc *desc, byte val) -{ - struct dskprivdat *dat = desc->privdat; - if ((val & 0x80) == 0) { - // D2DBG("dodged write $%02X", val); - return; // must have high bit - } - dat->dirty_tracks |= 1 << (desc->halftrack/2); - size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE; - pos += (dat->bytenum % NIBBLE_TRACK_SIZE); - - //D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos); - - dat->buf[pos] = val; - dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE; -} - -static void eject(DiskFormatDesc *desc) -{ - // free dat->path and dat, and unmap disk image - struct dskprivdat *dat = desc->privdat; - (void) munmap(dat->buf, dsk_disksz); - free((void*)dat->path); - free(dat); -} - -// This function is derived from Scullin Steel Co.'s apple2js code -// https://github.com/whscullin/apple2js/blob/e280c3d/js/formats/format_utils.ts#L140 -static void explodeSector(byte vol, byte track, byte sector, - byte **nibSec, const byte *data) -{ - byte *wr = *nibSec; - unsigned int gap; - - // Gap 1/3 (40/0x28 bytes) - - if (sector == 0) // Gap 1 - gap = 0x80; - else { // Gap 3 - gap = track == 0? 0x28 : 0x26; - } - - for (uint8_t i = 0; i != gap; ++i) { - *wr++ = 0xFF; - } - - // Address Field - const byte checksum = vol ^ track ^ sector; - *wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0x96; // Address Prolog D5 AA 96 - *wr++ = (vol >> 1) | 0xAA; *wr++ = vol | 0xAA; - *wr++ = (track >> 1) | 0xAA; *wr++ = track | 0xAA; - *wr++ = (sector >> 1) | 0xAA; *wr++ = sector | 0xAA; - *wr++ = (checksum >> 1) | 0xAA; *wr++ = checksum | 0xAA; - *wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB - - // Gap 2 (5 bytes) - for (int i = 0; i != 5; ++i) { - *wr++ = 0xFF; - } - - // Data Field - *wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0xAD; // Data Prolog D5 AA AD - - byte *nibbles = wr; - const unsigned ptr2 = 0; - const unsigned ptr6 = 0x56; - - for (int i = 0; i != 0x156; ++i) { - nibbles[i] = 0; - } - - int i2 = 0x55; - for (int i6 = 0x101; i6 >= 0; --i6) { - byte val6 = data[i6 % 0x100]; - byte val2 = nibbles[ptr2 + i2]; - - val2 = (val2 << 1) | (val6 & 1); - val6 >>= 1; - val2 = (val2 << 1) | (val6 & 1); - val6 >>= 1; - - nibbles[ptr6 + i6] = val6; - nibbles[ptr2 + i2] = val2; - - if (--i2 < 0) - i2 = 0x55; - } - - byte last = 0; - for (int i = 0; i != 0x156; ++i) { - const byte val = nibbles[i]; - nibbles[i] = TRANS62[last ^ val]; - last = val; - } - wr += 0x156; // advance write-pointer - *wr++ = TRANS62[last]; - - *wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB - - // Gap 3 - *wr++ = 0xFF; - - *nibSec = wr; -} - -static void explodeDsk(byte *nibbleBuf, byte *dskBuf, const byte *secmap) -{ - for (int t = 0; t < NUM_TRACKS; ++t) { - byte *writePtr = nibbleBuf; - for (int phys_sector = 0; phys_sector < MAX_SECTORS; ++phys_sector) { - const byte dos_sector = secmap[phys_sector]; - const size_t off = ((MAX_SECTORS * t + dos_sector) - * DSK_SECTOR_SIZE); - explodeSector(VOLUME_NUMBER, t, phys_sector, - &writePtr, &dskBuf[off]); - } - assert(writePtr - nibbleBuf <= NIBBLE_TRACK_SIZE); - for (; writePtr != (nibbleBuf + NIBBLE_TRACK_SIZE); ++writePtr) { - *writePtr = 0xFF; - } - nibbleBuf += NIBBLE_TRACK_SIZE; - } -} - -DiskFormatDesc dsk_insert(const char *path, byte *buf, size_t sz) -{ - if (sz != dsk_disksz) { - DIE(0,"Wrong disk image size for %s:\n", path); - DIE(1," Expected %zu, got %zu.\n", dsk_disksz, sz); - } - - struct dskprivdat *dat = malloc(sizeof *dat); - *dat = datinit; - dat->realbuf = buf; - dat->path = strdup(path); - dat->buf = calloc(1, nib_disksz); - - const char *ext = rindex(path, '.'); - ext = ext ? ext+1 : ""; - if (!strcasecmp(ext, "PO")) { - INFO("Opening %s as PO.\n", dat->path); - dat->secmap = PO; - } else { - INFO("Opening %s as DO.\n", dat->path); - dat->secmap = DO; - } - explodeDsk(dat->buf, dat->realbuf, dat->secmap); - - return (DiskFormatDesc){ - .privdat = dat, - .spin = spin, - .read_byte = read_byte, - .write_byte = write_byte, - .eject = eject, - }; -} diff --git a/src/format/empty.c b/src/format/empty.c deleted file mode 100644 index 6643fe3..0000000 --- a/src/format/empty.c +++ /dev/null @@ -1,36 +0,0 @@ -// format/empty.c -// -// Copyright (c) 2023 Micah John Cowan. -// This code is licensed under the MIT license. -// See the accompanying LICENSE file for details. - -#include "mii_disk_format.h" - - -#define byte uint8_t - -void spin(DiskFormatDesc *d, bool b) -{ -} - -byte read_byte(DiskFormatDesc *d) -{ - // A real disk can never send a low byte. - // But an empty disk must never send a legitimate byte. - return 0x00; -} - -void write_byte(DiskFormatDesc *d, byte b) -{ -} - -void eject(DiskFormatDesc *d) -{ -} - -DiskFormatDesc empty_disk_desc = { - .spin = spin, - .read_byte = read_byte, - .write_byte = write_byte, - .eject = eject, -}; diff --git a/src/format/mii_dd.c b/src/format/mii_dd.c index 17f22e0..6d59ec0 100644 --- a/src/format/mii_dd.c +++ b/src/format/mii_dd.c @@ -141,10 +141,21 @@ mii_dd_file_load( { if (!flags) flags = O_RDONLY; + if (flags & O_WRONLY) + flags |= O_RDWR; int err; int fd = open(pathname, flags); if (fd < 0) { - perror(pathname); + printf("%s: %s: Retrying Read only\n", __func__, pathname); + if (flags & (O_RDWR | O_WRONLY)) { + flags &= ~(O_RDWR | O_WRONLY); + flags |= O_RDONLY; + fd = open(pathname, flags, 0666); + } + } + if (fd < 0) { + printf("%s: %s: Failed to open: %s\n", + __func__, pathname, strerror(errno)); return NULL; } struct stat st; @@ -174,10 +185,22 @@ mii_dd_file_load( res->dd = NULL; res->next = dd->file; dd->file = res; + res->read_only = (flags & O_RDWR) == 0; char *suffix = strrchr(pathname, '.'); - if (suffix && !strcasecmp(suffix, ".2mg")) { - res->format = MII_DD_FILE_2MG; - res->map += 64; + if (suffix) { + if (!strcasecmp(suffix, ".dsk")) { + res->format = MII_DD_FILE_DSK; + } else if (!strcasecmp(suffix, ".po") || !strcasecmp(suffix, ".hdv")) { + res->format = MII_DD_FILE_PO; + } else if (!strcasecmp(suffix, ".nib")) { + res->format = MII_DD_FILE_NIB; + } else if (!strcasecmp(suffix, ".woz")) { + res->format = MII_DD_FILE_WOZ; + } else if (!strcasecmp(suffix, ".2mg")) { + res->format = MII_DD_FILE_2MG; + res->map += 64; + } + printf("%s: suffix %s, format %d\n", __func__, suffix, res->format); } return res; bail: diff --git a/src/format/mii_dd.h b/src/format/mii_dd.h index f49e9dc..5779a67 100644 --- a/src/format/mii_dd.h +++ b/src/format/mii_dd.h @@ -16,7 +16,11 @@ enum { // MII_DD_FILE_OVERLAY = 1, MII_DD_FILE_RAM, MII_DD_FILE_ROM, + MII_DD_FILE_PO, MII_DD_FILE_2MG = 5, + MII_DD_FILE_DSK, + MII_DD_FILE_NIB, + MII_DD_FILE_WOZ }; // a disk image file (or chunck of ram, if ramdisk is used) @@ -24,6 +28,7 @@ typedef struct mii_dd_file_t { struct mii_dd_file_t *next; char * pathname; uint8_t format; + uint8_t read_only; uint8_t * start; // start of the file uint8_t * map; // start of the blocks diff --git a/src/format/mii_disk_format.c b/src/format/mii_disk_format.c deleted file mode 100644 index af99174..0000000 --- a/src/format/mii_disk_format.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * mii_disk_format.c - * - * Copyright (C) 2023 Michel Pollet - * - * SPDX-License-Identifier: MIT - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mii_disk_format.h" - - -#define byte uint8_t - -static const size_t nib_disksz = 232960; -static const size_t dsk_disksz = 143360; - -extern DiskFormatDesc nib_insert(const char*, byte *, size_t); -extern DiskFormatDesc dsk_insert(const char *, byte *, size_t); -extern DiskFormatDesc empty_disk_desc; - -int mmapfile(const char *fname, byte **buf, size_t *sz, int flags); - -DiskFormatDesc disk_format_load(const char *path) -{ - if (path == NULL) { - return empty_disk_desc; - } - byte *buf; - size_t sz; - int err = mmapfile(path, &buf, &sz, O_RDWR); - if (buf == NULL) { - DIE(1,"Couldn't load/mmap disk %s: %s\n", - path, strerror(err)); - } - printf("%s loaded %s dz = %d\n", __func__, path, (int)sz); - if (sz == nib_disksz) { - return nib_insert(path, buf, sz); - } else if (sz == dsk_disksz) { - return dsk_insert(path, buf, sz); - } else { - DIE(2,"Unrecognized disk format for %s.\n", path); - } -} - -int mmapfile(const char *fname, byte **buf, size_t *sz, int flags) -{ - int err; - int fd; - - *buf = NULL; - - errno = 0; - fd = open(fname, flags); - if (fd < 0) { - return errno; - } - - struct stat st; - errno = 0; - err = fstat(fd, &st); - if (err < 0) { - goto bail; - } - - errno = 0; - int protect = PROT_READ; - int mflags = MAP_PRIVATE; - if (flags & O_RDWR || flags & O_WRONLY) { - protect |= PROT_WRITE; - mflags = MAP_SHARED; - } - *buf = mmap(NULL, st.st_size, protect, mflags, fd, 0); - if (*buf == NULL) { - err = errno; - goto bail; - } - close(fd); // safe to close now. - - *sz = st.st_size; - return 0; -bail: - close(fd); - return err; -} diff --git a/src/format/mii_disk_format.h b/src/format/mii_disk_format.h deleted file mode 100644 index e157189..0000000 --- a/src/format/mii_disk_format.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * mii_disk_format.h - * - * Copyright (C) 2023 Michel Pollet - * - * SPDX-License-Identifier: MIT - */ -#pragma once - -#include -#include -#include - -/********** FORMATS **********/ -#define NUM_TRACKS 35 -#define SECTOR_SIZE 256 -typedef struct DiskFormatDesc DiskFormatDesc; -typedef struct DiskFormatDesc { - void *privdat; - bool writeprot; - unsigned int halftrack; - void (*spin)(DiskFormatDesc *, bool); - uint8_t (*read_byte)(DiskFormatDesc *); - void (*write_byte)(DiskFormatDesc *, uint8_t); - void (*eject)(DiskFormatDesc *); -} DiskFormatDesc; - -DiskFormatDesc disk_format_load(const char *path); - -#define WARN(...) fprintf(stderr, __VA_ARGS__) -#define INFO(...) fprintf(stderr, __VA_ARGS__) -#define DIE(code, ...) do { \ - WARN(__VA_ARGS__); \ - if (code) exit(code); \ - } while(0) diff --git a/src/format/mii_floppy.c b/src/format/mii_floppy.c new file mode 100644 index 0000000..8e79fa3 --- /dev/null +++ b/src/format/mii_floppy.c @@ -0,0 +1,470 @@ +/* + * mii_floppy.c + * + * Copyright (C) 2024 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include "mii_floppy.h" +#include "mii_woz.h" + +void +mii_floppy_init( + mii_floppy_t *f) +{ + f->motor = 0; + f->stepper = 0; + f->qtrack = 15; // just to see something at seek time + f->bit_position = 0; + f->write_protected &= ~MII_FLOPPY_WP_MANUAL;// keep the manual WP bit + /* this will look like this; ie half tracks are 'random' + 0: 0 1: 0 2:35 3: 1 + 4: 1 5: 1 6:35 7: 2 + 8: 2 9: 2 10:35 11: 3 + */ + for (int i = 0; i < (int)sizeof(f->track_id); i++) + f->track_id[i] = ((i + 1) % 4) == 3 ? + MII_FLOPPY_RANDOM_TRACK_ID : ((i + 2) / 4); + /* generate a buffer with about 30% one bits */ + uint8_t *random = f->tracks[MII_FLOPPY_RANDOM_TRACK_ID].data; + memset(random, 0, 256); + uint32_t bits = 256 * 8; + uint32_t ones = bits * 0.3; // 30% ones + // set 'ones' random bits in that random track + while (ones) { + uint32_t bit = rand() % bits; + if (random[bit >> 3] & (1 << (bit & 7))) + continue; + random[bit >> 3] |= (1 << (bit & 7)); + ones--; + } + // we also fill up the last (ramdom) track with copies of the first + // 256 uint8_ts of itself, to make it look like a real track + int rbi = 0; + for (int i = 0; i < 36; i++) { + f->tracks[i].dirty = 0; + f->tracks[i].bit_count = MII_FLOPPY_DEFAULT_TRACK_SIZE * 8; + // fill the whole array up to the end.. + for (int bi = 0; bi < (int)sizeof(f->tracks[i].data); bi++) + f->tracks[i].data[bi] = random[rbi++ % 256]; + } +} + +static void +mii_track_write_bits( + mii_floppy_track_t * dst, + uint8_t bits, + uint8_t count ) +{ + while (count--) { + uint32_t uint8_t_index = dst->bit_count >> 3; + uint8_t bit_index = 7 - (dst->bit_count & 7); + dst->data[uint8_t_index] &= ~(1 << bit_index); + dst->data[uint8_t_index] |= (!!(bits >> 7) << bit_index); + dst->bit_count++; + bits <<= 1; + } +} + +/* + * NIB isn't ideal to use with our bitstream, as it's lacking the sync + * bits. It was made to use in something like our previous emulator that + * was just iterating uint8_ts. + * Anyway, We can recreate the proper bitstream by finding sectors headers, + * filling up a few 'correct' 10 bits sync uint8_ts, then plonk said sector + * as is. + */ + +static uint8_t _de44(uint8_t a, uint8_t b) { + return ((a & 0x55) << 1) | (b & 0x55); +} + +static void +mii_nib_rebit_track( + uint8_t *track, + mii_floppy_track_t * dst) +{ + dst->bit_count = 0; + uint32_t window = 0; + int srci = 0; + int seccount = 0; + int state = 0; // look for address field + do { + window = (window << 8) | track[srci++]; + switch (state) { + case 0: { + if (window != 0xffd5aa96) + break; + for (int i = 0; i < (seccount == 0 ? 40 : 20); i++) + mii_track_write_bits(dst, 0xff, 10); + uint8_t * h = track + srci - 4; // incs first 0xff + int tid = _de44(h[6], h[7]); + int sid = _de44(h[8], h[9]); + printf("Track %2d sector %2d\n", tid, sid); + memcpy(dst->data + (dst->bit_count >> 3), track + srci - 4, 15); + dst->bit_count += 15 * 8; + srci += 11; + state = 1; + } break; + case 1: { + if (window != 0xffd5aaad) + break; + for (int i = 0; i < 4; i++) + mii_track_write_bits(dst, 0xff, 10); + uint8_t *h = track + srci - 4; + memcpy(dst->data + (dst->bit_count >> 3), h, 4 + 342 + 4); + dst->bit_count += (4 + 342 + 4) * 8; + srci += 4 + 342; + seccount++; + state = 0; + } break; + } + } while (srci < 6656); +} + +static int +mii_floppy_load_nib( + mii_floppy_t *f, + mii_dd_file_t *file ) +{ + const char *filename = basename(file->pathname); + printf("%s: loading NIB %s\n", __func__, filename); + for (int i = 0; i < 35; i++) { + uint8_t *track = file->map + (i * 6656); + mii_nib_rebit_track(track, &f->tracks[i]); + if (f->tracks[i].bit_count < 100) { + printf("%s: %s: Invalid track %d has zero bits!\n", __func__, + filename, i); + return -1; + } + // printf("Track %d converted to %d bits\n", i, f->tracks[i].bit_count); + f->tracks[i].dirty = 0; + } + return 0; +} + +static int +mii_floppy_write_track_woz( + mii_floppy_t *f, + mii_dd_file_t *file, + int track_id ) +{ + mii_woz_header_t *header = (mii_woz_header_t *)file->map; + + int version = !strncmp((char*)header, "WOZ", 3); + if (!version) { + printf("%s: not a WOZ file %4.4s\n", __func__, (char*)&header->magic_le); + return 0; + } + version += !strncmp((char*)header, "WOZ2", 4); + + mii_woz_tmap_t *tmap = NULL; + if (version == 1) { + mii_woz1_info_t *info = (mii_woz1_info_t *)(header + 1); + tmap = (mii_woz_tmap_t *)((uint8_t *)info + + le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t)); + mii_woz1_trks_t *trks = (mii_woz1_trks_t *)((uint8_t *)tmap + + le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t)); + + trks->track[track_id].bit_count_le = htole32(f->tracks[track_id].bit_count); + uint32_t byte_count = (le32toh(trks->track[track_id].bit_count_le) + 7) >> 3; + memcpy(trks->track[track_id].bits, f->tracks[track_id].data, byte_count); + trks->track[track_id].byte_count_le = htole16(byte_count); + } else { + mii_woz2_info_t *info = (mii_woz2_info_t *)(header + 1); + tmap = (mii_woz_tmap_t *)((uint8_t *)info + + le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t)); + mii_woz2_trks_t *trks = (mii_woz2_trks_t *)((uint8_t *)tmap + + le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t)); + + uint8_t *track = file->map + + (le16toh(trks->track[track_id].start_block_le) << 9); + + trks->track[track_id].bit_count_le = htole32(f->tracks[track_id].bit_count); + uint32_t byte_count = (le32toh(trks->track[track_id].bit_count_le) + 7) >> 3; + memcpy(track, f->tracks[track_id].data, byte_count); + } + f->tracks[track_id].dirty = 0; + return 0; +} + +static int +mii_floppy_load_woz( + mii_floppy_t *f, + mii_dd_file_t *file ) +{ + const char *filename = basename(file->pathname); + printf("%s: loading WOZ %s\n", __func__, filename); + mii_woz_header_t *header = (mii_woz_header_t *)file->map; + + int version = !strncmp((char*)header, "WOZ", 3); + if (!version) { + printf("%s: not a WOZ file %4.4s\n", __func__, (char*)&header->magic_le); + return 0; + } + version += !strncmp((char*)header, "WOZ2", 4); + mii_woz_tmap_t *tmap = NULL; + if (version == 1) { + mii_woz1_info_t *info = (mii_woz1_info_t *)(header + 1); + tmap = (mii_woz_tmap_t *)((uint8_t *)info + + le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t)); + mii_woz1_trks_t *trks = (mii_woz1_trks_t *)((uint8_t *)tmap + + le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t)); +#if 1 + printf("WOZ: version %d, type %d\n", + info->version, info->disk_type ); + printf("WOZ: creator '%s'\n", info->creator); + printf("WOZ: track map %4.4s size %d\n", + (char*)&tmap->chunk.id_le, + le32toh(tmap->chunk.size_le)); + printf("WOZ: Track chunk %4.4s size %d\n", + (char*)&trks->chunk.id_le, le32toh(trks->chunk.size_le)); +#endif + for (int i = 0; i < 35; i++) { + uint8_t *track = trks->track[i].bits; + memcpy(f->tracks[i].data, track, le16toh(trks->track[i].byte_count_le)); + f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le); + f->tracks[i].dirty = 0; + } + } else { + mii_woz2_info_t *info = (mii_woz2_info_t *)(header + 1); + tmap = (mii_woz_tmap_t *)((uint8_t *)info + + le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t)); + mii_woz2_trks_t *trks = (mii_woz2_trks_t *)((uint8_t *)tmap + + le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t)); +#if 1 + printf("WOZ: version %d, type %d, sides %d, largest track %d\n", + info->version, info->disk_type, info->sides, + le16toh(info->largest_track_le) * 512); + printf("WOZ: creator '%s'\n", info->creator); + printf("WOZ: track map %4.4s size %d\n", + (char*)&tmap->chunk.id_le, + le32toh(tmap->chunk.size_le)); + printf("WOZ: Track chunk %4.4s size %d\n", + (char*)&trks->chunk.id_le, le32toh(trks->chunk.size_le)); +#endif + for (int i = 0; i < 35; i++) { + uint8_t *track = file->map + + (le16toh(trks->track[i].start_block_le) << 9); + uint32_t byte_count = (le32toh(trks->track[i].bit_count_le) + 7) >> 3; + memcpy(f->tracks[i].data, track, byte_count); + f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le); + f->tracks[i].dirty = 0; + } + } + #if 0 + // copy the track map from the file to the floppy + for (int ti = 0; ti < (int)sizeof(f->track_id); ti++) { + f->track_id[ti] = tmap->track_id[ti] == 0xff ? + MII_FLOPPY_RANDOM_TRACK_ID : tmap->track_id[ti]; + if (f->tracks[f->track_id[ti]].bit_count == 0) { + printf("%s Invalid qtrack %d (points to track %d) has zero bits!\n", + __func__, ti, f->track_id[ti]); + } + } + #endif + return version; +} + +#define NIBBLE_SECTOR_SIZE 416 +#define NIBBLE_TRACK_SIZE 6656 +#define DSK_SECTOR_SIZE 256 +#define MAX_SECTORS 16 +#define VOLUME_NUMBER 254 +#define DSK_TRACK_SIZE (DSK_SECTOR_SIZE * MAX_SECTORS) + +//static const size_t nib_disksz = 232960; +//static const size_t dsk_disksz = 143360; + +// DOS 3.3 Physical sector order (index is physical sector, +// value is DOS sector) +static const uint8_t DO[] = { + 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, + 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF +}; +// ProDOS Physical sector order (index is physical sector, +// value is ProDOS sector). +static const uint8_t PO[] = { + 0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb, + 0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf +}; +static const uint8_t TRANS62[] = { + 0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, + 0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +// This function is derived from Scullin Steel Co.'s apple2js code +// https://github.com/whscullin/apple2js/blob/e280c3d/js/formats/format_utils.ts#L140 + +/* Further recycled for MII .DSK decoding + * We use this function to convert the sector from byte to nibble (8 bits), then + * we pass that track to the mii_nib_rebit_track() to add 10 bit headers and + * such. It could be done in one pass, but really, it's easier to reuse it as is. + */ +static void +mii_floppy_nibblize_sector( + uint8_t vol, uint8_t track, uint8_t sector, + uint8_t **nibSec, const uint8_t *data) +{ + uint8_t *wr = *nibSec; + unsigned int gap; + + // Gap 1/3 (40/0x28 uint8_ts) + if (sector == 0) // Gap 1 + gap = 0x80; + else { // Gap 3 + gap = track == 0? 0x28 : 0x26; + } + for (uint8_t i = 0; i != gap; ++i) + *wr++ = 0xFF; + // Address Field + const uint8_t checksum = vol ^ track ^ sector; + *wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0x96; // Address Prolog D5 AA 96 + *wr++ = (vol >> 1) | 0xAA; *wr++ = vol | 0xAA; + *wr++ = (track >> 1) | 0xAA; *wr++ = track | 0xAA; + *wr++ = (sector >> 1) | 0xAA; *wr++ = sector | 0xAA; + *wr++ = (checksum >> 1) | 0xAA; *wr++ = checksum | 0xAA; + *wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB + // Gap 2 (5 uint8_ts) + for (int i = 0; i != 5; ++i) + *wr++ = 0xFF; + // Data Field + *wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0xAD; // Data Prolog D5 AA AD + + uint8_t *nibbles = wr; + const unsigned ptr2 = 0; + const unsigned ptr6 = 0x56; + + for (int i = 0; i != 0x156; ++i) + nibbles[i] = 0; + + int i2 = 0x55; + for (int i6 = 0x101; i6 >= 0; --i6) { + uint8_t val6 = data[i6 % 0x100]; + uint8_t val2 = nibbles[ptr2 + i2]; + val2 = (val2 << 1) | (val6 & 1); val6 >>= 1; + val2 = (val2 << 1) | (val6 & 1); val6 >>= 1; + nibbles[ptr6 + i6] = val6; + nibbles[ptr2 + i2] = val2; + if (--i2 < 0) + i2 = 0x55; + } + uint8_t last = 0; + for (int i = 0; i != 0x156; ++i) { + const uint8_t val = nibbles[i]; + nibbles[i] = TRANS62[last ^ val]; + last = val; + } + wr += 0x156; // advance write-pointer + *wr++ = TRANS62[last]; + *wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB + // Gap 3 + *wr++ = 0xFF; + *nibSec = wr; +} + +static int +mii_floppy_load_dsk( + mii_floppy_t *f, + mii_dd_file_t *file ) +{ + uint8_t *nibbleBuf = malloc(NIBBLE_TRACK_SIZE); + const char *filename = basename(file->pathname); + + const char *ext = rindex(filename, '.'); + ext = ext ? ext+1 : ""; + const uint8_t * secmap = DO; + if (!strcasecmp(ext, "PO")) { + printf("%s Opening %s as PO.\n", __func__, filename); + secmap = PO; + } else { + printf("%s Opening %s as DO.\n", __func__, filename); + } + for (int i = 0; i < 35; ++i) { + memset(nibbleBuf, 0xff, NIBBLE_TRACK_SIZE); + uint8_t *writePtr = nibbleBuf; + for (int phys_sector = 0; phys_sector < MAX_SECTORS; ++phys_sector) { + const uint8_t dos_sector = secmap[phys_sector]; + uint32_t off = ((MAX_SECTORS * i + dos_sector) * DSK_SECTOR_SIZE); + uint8_t *track = file->map + off; + mii_floppy_nibblize_sector(VOLUME_NUMBER, i, phys_sector, + &writePtr, track); + } + mii_nib_rebit_track(nibbleBuf, &f->tracks[i]); + } + free(nibbleBuf); + // DSK is read only + f->write_protected |= MII_FLOPPY_WP_RO_FORMAT; + + return 0; +} + +int +mii_floppy_update_tracks( + mii_floppy_t *f, + mii_dd_file_t *file ) +{ + if (f->write_protected & MII_FLOPPY_WP_RO_FORMAT) + return -1; + if (f->write_protected & MII_FLOPPY_WP_RO_FILE) + return -1; + if (!f->tracks_dirty) + return 0; + for (int i = 0; i < 35; i++) { + if (!f->tracks[i].dirty) + continue; + printf("%s: track %d is dirty, saving\n", __func__, i); + switch (file->format) { + case MII_DD_FILE_NIB: + break; + case MII_DD_FILE_WOZ: + mii_floppy_write_track_woz(f, file, i); + printf("%s: WOZ track %d updated\n", __func__, i); + break; + } + f->tracks[i].dirty = 0; + } + f->tracks_dirty = 0; + return 0; +} + +int +mii_floppy_load( + mii_floppy_t *f, + mii_dd_file_t *file ) +{ + if (!file) + return -1; + int res = -1; + switch (file->format) { + case MII_DD_FILE_NIB: + res = mii_floppy_load_nib(f, file); + break; + case MII_DD_FILE_WOZ: + res = mii_floppy_load_woz(f, file); + break; + case MII_DD_FILE_DSK: + res = mii_floppy_load_dsk(f, file); + break; + default: + printf("%s: unsupported format %d\n", __func__, file->format); + } + // update write protection in case file is opened read only + if (file->read_only) + f->write_protected |= MII_FLOPPY_WP_RO_FILE; + else + f->write_protected &= ~MII_FLOPPY_WP_RO_FILE; + return res; +} diff --git a/src/format/mii_floppy.h b/src/format/mii_floppy.h new file mode 100644 index 0000000..dbc99b2 --- /dev/null +++ b/src/format/mii_floppy.h @@ -0,0 +1,58 @@ +/* + * mii_floppy.h + * + * Copyright (C) 2024 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include +#include "mii_dd.h" + +// for NIB and others. can be bigger on .WOZ +#define MII_FLOPPY_DEFAULT_TRACK_SIZE 6656 +// track containing random bits +#define MII_FLOPPY_RANDOM_TRACK_ID 35 + +enum { + MII_FLOPPY_WP_MANUAL = (1 << 0), // write protect by the user + MII_FLOPPY_WP_RO_FILE = (1 << 1), // file is read only + MII_FLOPPY_WP_RO_FORMAT = (1 << 2), // File format doesn't do writes +}; + +typedef struct mii_floppy_track_t { + uint8_t dirty : 1; // track has been written to + uint32_t bit_count; + uint8_t data[6680]; // max suggested by WOZ spec +} mii_floppy_track_t; + +typedef struct mii_floppy_t { + uint8_t write_protected : 3, id : 2; + uint8_t motor; // motor is on + uint8_t stepper; // last step we did... + uint8_t qtrack; // quarter track we are on + uint32_t bit_position; + uint8_t tracks_dirty; // needs saving + uint8_t track_id[35 * 4]; + mii_floppy_track_t tracks[36]; +} mii_floppy_t; + +/* + * Initialize a floppy structure with random data. It is not formatted, just + * ready to use for loading a disk image, or formatting as a 'virgin' disk. + */ +void +mii_floppy_init( + mii_floppy_t *f); + +int +mii_floppy_load( + mii_floppy_t *f, + mii_dd_file_t *file ); + +int +mii_floppy_update_tracks( + mii_floppy_t *f, + mii_dd_file_t *file ); diff --git a/src/format/mii_woz.h b/src/format/mii_woz.h new file mode 100644 index 0000000..3c010b9 --- /dev/null +++ b/src/format/mii_woz.h @@ -0,0 +1,94 @@ +/* + * mii_woz.h + * + * Copyright (C) 2024 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include + +/* + * Woz format parser. 'le' fields are little-endian in the file. + * Use the appropriate macros like le32toh() to convert to host-endian as needed. + */ +typedef struct mii_woz_header_t { + uint32_t magic_le; // 'WOZ2' + uint8_t padding[4]; + uint32_t crc_le; +} __attribute__((packed)) mii_woz_header_t; + +typedef struct mii_woz_chunk_t { + uint32_t id_le; + uint32_t size_le; +} __attribute__((packed)) mii_woz_chunk_t; + +// offset 12 in the file, 'version' at offset 20 +typedef struct mii_woz2_info_t { + mii_woz_chunk_t chunk; // 'INFO' + uint8_t version; // version 3 + uint8_t disk_type; // 1=5.25" 2=3.5" + uint8_t write_protected; + uint8_t sync; // 1=cross track sync was used when imaged + uint8_t cleaned; // 1=MC3470 fake bits have been cleaned + uint8_t creator[32]; + uint8_t sides; // 1 or 2 (3.5") + uint8_t boot_format; // boot sector format 1:16,2:13,3:both + uint8_t optimal_bit_timing; + uint16_t compatible_hardware_le; + uint16_t required_ram_le; // apple II ram required (48, 64 etc) + uint16_t largest_track_le; // in units of 512 bytes + uint16_t flux_block_le; + uint16_t flux_largest_track_le; +} __attribute__((packed)) mii_woz2_info_t; + +// offset 80 in the file -- same for WOZ1 and WOZ2 +typedef struct mii_woz_tmap_t { + mii_woz_chunk_t chunk; // 'TMAP' + // 140 bytes for 35*4 tracks; or 160 bytes for 80 tracks*2 sides + uint8_t track_id[160]; // 'TRKS' id for each quarter track +} __attribute__((packed)) mii_woz_tmap_t; + +// offset 248 in the file +typedef struct mii_woz2_trks_t { + mii_woz_chunk_t chunk; // 'TRKS' + // offset 256 in the file + struct { + /* First block of BITS data. This value is relative to the start + of the file, so the first possible starting block is 3. + Multiply this value by 512 (x << 9) to get the starting byte + of the BITS data. */ + uint16_t start_block_le; // starting block number + uint16_t block_count_le; // number of 512-byte blocks + uint32_t bit_count_le; // number of bits in the track + } track[160]; + uint8_t bits[]; // the actual bits +} __attribute__((packed)) mii_woz2_trks_t; + +/* Same info, tmap and trks for WOZ 1 files */ +typedef struct mii_woz1_info_t { + mii_woz_chunk_t chunk; // 'INFO' + uint8_t version; // version 1 + uint8_t disk_type; // 1=5.25" 2=3.5" + uint8_t write_protected; + uint8_t sync; // 1=cross track sync was used when imaged + uint8_t cleaned; // 1=MC3470 fake bits have been cleaned + uint8_t creator[32]; +} __attribute__((packed)) mii_woz1_info_t; + +// offset 248 in the file +typedef struct mii_woz1_trks_t { + mii_woz_chunk_t chunk; // 'TRKS' + // offset 256 in the file + struct { + uint8_t bits[6646]; + uint16_t byte_count_le; // size in bytes + uint16_t bit_count_le; + uint16_t splice_point_le; + uint8_t splice_nibble; + uint8_t splice_bit_count; + uint16_t reserved; + } track[35]; +} __attribute__((packed)) mii_woz1_trks_t; diff --git a/src/format/nib.c b/src/format/nib.c deleted file mode 100644 index d2be1db..0000000 --- a/src/format/nib.c +++ /dev/null @@ -1,100 +0,0 @@ -// format/nib.c -// -// Copyright (c) 2023 Micah John Cowan. -// This code is licensed under the MIT license. -// See the accompanying LICENSE file for details. - -#include "mii_disk_format.h" - -#include -#include -#include -#include - -#define byte uint8_t - -#define NIBBLE_TRACK_SIZE 6656 -#define NIBBLE_SECTOR_SIZE 416 -#define MAX_SECTORS 16 - -struct nibprivdat { - const char *path; - byte *buf; - int bytenum; - uint64_t dirty_tracks; -}; -static const struct nibprivdat datinit = { 0 }; - -static const size_t nib_disksz = 232960; - -static void spin(DiskFormatDesc *desc, bool b) -{ - struct nibprivdat *dat = desc->privdat; - if (!b && dat->dirty_tracks != 0) { - // For now, sync the entire disk - errno = 0; - int err = msync(dat->buf, nib_disksz, MS_SYNC); - if (err < 0) { - DIE(1,"Couldn't sync to disk file %s: %s\n", - dat->path, strerror(errno)); - } - dat->dirty_tracks = 0; - } -} - -static byte read_byte(DiskFormatDesc *desc) -{ - struct nibprivdat *dat = desc->privdat; - size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE; - pos += (dat->bytenum % NIBBLE_TRACK_SIZE); - byte val = dat->buf[pos]; - dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE; - return val; -} - -static void write_byte(DiskFormatDesc *desc, byte val) -{ - struct nibprivdat *dat = desc->privdat; - if ((val & 0x80) == 0) { - // D2DBG("dodged write $%02X", val); - return; // must have high bit - } - dat->dirty_tracks |= 1 << (desc->halftrack/2); - size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE; - pos += (dat->bytenum % NIBBLE_TRACK_SIZE); - - //D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos); - - dat->buf[pos] = val; - dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE; -} - -static void eject(DiskFormatDesc *desc) -{ - // free dat->path and dat, and unmap disk image - struct nibprivdat *dat = desc->privdat; - (void) munmap(dat->buf, nib_disksz); - free((void*)dat->path); - free(dat); -} - -DiskFormatDesc nib_insert(const char *path, byte *buf, size_t sz) -{ - if (sz != nib_disksz) { - DIE(0,"Wrong disk image size for %s:\n", path); - DIE(1," Expected %zu, got %zu.\n", nib_disksz, sz); - } - - struct nibprivdat *dat = malloc(sizeof *dat); - *dat = datinit; - dat->buf = buf; - dat->path = strdup(path); - - return (DiskFormatDesc){ - .privdat = dat, - .spin = spin, - .read_byte = read_byte, - .write_byte = write_byte, - .eject = eject, - }; -} diff --git a/ui_gl/mii_mui_2dsk.c b/ui_gl/mii_mui_2dsk.c index 781f700..fecb414 100644 --- a/ui_gl/mii_mui_2dsk.c +++ b/ui_gl/mii_mui_2dsk.c @@ -142,7 +142,7 @@ _mii_2dsk_action_cb( "Select DSK file to load", m->drive_kind == MII_2DSK_SMARTPORT ? "\\.(po|hdv|2mg)$" : - "\\.(dsk)$", + "\\.(woz|nib|dsk)$", getenv("HOME"), MUI_STDF_FLAG_REGEXP); mui_window_set_action(w, _mii_2dsk_stdfile_cb, m);