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 <buserror@gmail.com>
This commit is contained in:
Michel Pollet 2024-02-12 16:58:13 +00:00
parent 59beeb2f68
commit 66937f3858
No known key found for this signature in database
12 changed files with 1042 additions and 1017 deletions

View File

@ -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 <errno.h>
#include <stdbool.h>
#include <stdint.h>
#define _GNU_SOURCE // for asprintf
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#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 <track 0-36> [count]\n");
}
return;
}
}
#include "mish.h"
MISH_CMD_NAMES(d2, "d2");
MISH_CMD_HELP(d2,
"d2: disk ][ internals",
" <default>: dump status"
);
MII_MISH(d2, _mii_mish_d2);

View File

@ -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 <errno.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/mman.h>
#include <assert.h>
#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,
};
}

View File

@ -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,
};

View File

@ -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:

View File

@ -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

View File

@ -1,95 +0,0 @@
/*
* mii_disk_format.c
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <sys/stat.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#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;
}

View File

@ -1,35 +0,0 @@
/*
* mii_disk_format.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
/********** 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)

470
src/format/mii_floppy.c Normal file
View File

@ -0,0 +1,470 @@
/*
* mii_floppy.c
*
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#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;
}

58
src/format/mii_floppy.h Normal file
View File

@ -0,0 +1,58 @@
/*
* mii_floppy.h
*
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
#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 );

94
src/format/mii_woz.h Normal file
View File

@ -0,0 +1,94 @@
/*
* mii_woz.h
*
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <stdint.h>
/*
* 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;

View File

@ -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 <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#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,
};
}

View File

@ -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);