| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640 | /* * abcdisk.c * * Emulate an ABC80/800 disk controller */#include <string.h>#include <stdio.h>#include "fw.h"#include "io.h"#include "abcio.h"#include "console.h"#include "ff.h"/* Option flags, mostly for debugging */#define NOTTHERE	0#define READONLY	0#define INTERLEAVE	0#define FORMAT_SUPPORT	0enum drive_flags {    DF_MOUNTED,			/* Host file open, drive exported */    DF_READONLY,		/* Drive is readonly */    DF_DISABLED			/* Drive is disabled */};enum pending {    PEND_IO    = 1,    PEND_RESET = 2};/* Per-drive state */struct drive_state {    FIL file;			/* Host file */    char name[4];               /* Drive name */    uint16_t sectors;		/* Total size in 256-byte sectors */#if INTERLEAVE    uint8_t ilmsk, ilfac;       /* Software interleaving parameters */#endif    enum drive_flags flags;    enum drive_flags force;	/* Option to force flags */};/* Fixed parameters for each controller */struct ctl_params {    uint8_t clustshift;		/* log2(clustersize/256) */    uint8_t devsel;		/* I/O device select */    uint16_t maxsectors;	/* Maximum sectors for this controller */    uint8_t c, h, s;		/* Disk geometry */    bool newaddr;		/* "New addressing" */    const char name[4];		/* Name of controller (disk type) */#if INTERLEAVE    uint8_t ilmsk, ilfac;	/* Software interleaving parea */#endif#if FORMAT_SUPPORT    bool fmtdata_i_buf;	/* Use user-provided formatting data */#endif} __attribute__((aligned(4)));/* Per-controller state */struct ctl_state {    struct abc_dev iodev;    const struct ctl_params *params;    uint8_t k[4];		/* Command bytes */    uint8_t drives;		/* Total drives present */    bool initialized;	        /* Controller initialized */    volatile enum pending pending;	/* Need to do I/O */    struct drive_state drv[8];	/* Per-drive state */    uint8_t buf[4][256];	/* 4 host buffers @ 256 bytes */};/* * DOSGEN depends on this value... and different DOSGEN * expect different values. If this value is wrong, DOSGEN * will spin forever on "testing sector..." */#define OUT_OF_RANGE 0x21	/* Status code for an invalid sector */#define DISK_NOT_READY 0x80#define WRITE_PROTECT  0x40#define WRITE_FAULT    0x20#define CRC_ERROR      0x08enum controller_types {    MOx,    MFx,    SFx,    HDx,    CONTROLLER_TYPES};#if INTERLEAVE# define IL(mask, fac) .ilmsk = (mask), .ilfac = (fac),#else# define IL(mask, fac)#endifstatic const struct ctl_params parameters[CONTROLLER_TYPES] = {    /*     * MOx: covers all of these formats:     * SSSD = 40×8×1 (80K, FD2/DD80),     * SSDD = 40×16×1 (160K, FD2D/DD82/ABC830) and     * DSDD = 40×16×2 (320K, FD4D/DD84/DD52)     */    [MOx] = {	.devsel = 45,	.clustshift = 0,	.maxsectors = 40 * 2 * 16,	.c = 40, .h = 2, .s = 16,	.name = "mo",#if FORMAT_SUPPORT	.fmtdata_in_buf = true,#endif	IL(15, 7)    },    /* MFx: DSQD = 80×16x2 (640K, ABC832/834) */    [MFx] = {	.devsel = 44,	.clustshift = 2,	.maxsectors = 80 * 2 * 16,	.c = 80, .h = 2, .s = 16,	.name = "mf"    },    /* SFx: 8" floppy (DD88, ABC838) */    [SFx] = {	.devsel = 46,	.clustshift = 2,	.maxsectors = (77 * 2 - 1) * 26, /* Track 0, side 0 not used */	.c = 77, .h = 2, .s = 26,	.name = "sf"    },    [HDx] = {	.devsel = 36,	.clustshift = 5,	.newaddr = true,            /* Actually irrelevant for clustshift = 5 */	.maxsectors = (239 * 8 - 1) * 32,     /* Maximum supported by UFD-DOS */	.c = 238, .h = 16, .s = 64,	.name = "hd"    }};static struct ctl_state __dram_bss controllers[CONTROLLER_TYPES];static inline bool mounted(const struct drive_state *drv){    return !!(drv->flags & DF_MOUNTED);}static inline struct drive_state *cur_drv_mutable(struct ctl_state *state){    return &state->drv[state->k[1] & 7];}static inline const struct drive_state *cur_drv(const struct ctl_state *state){    return &state->drv[state->k[1] & 7];}static inline unsigned int cur_sector(const struct ctl_state *state){    uint8_t k2 = state->k[2], k3 = state->k[3];    if (state->params->newaddr)        return (k2 << 8) + k3;    else        return (((k2 << 3) + (k3 >> 5)) << state->params->clustshift)	    + (k3 & 31);}/* Get physical sector number, after interleaving */static inline unsigned intvirt2phys(const struct drive_state *drv, unsigned int sector){#if INTERLEAVE    unsigned int ilmsk = drv->ilmsk;    unsigned int ilfac = drv->ilfac;    sector = (sector & ~ilmsk) | ((sector * ilfac) & ilmsk);#endif    return sector;}static inline unsigned int phys_sector(const struct ctl_state *state){    return virt2phys(cur_drv(state), cur_sector(state));}static inline unsigned int file_pos(const struct ctl_state *state){    return phys_sector(state) << 8;}static inline bool file_pos_valid(const struct ctl_state *state){    return phys_sector(state) < cur_drv(state)->sectors;}static inline bool cur_sector_valid(const struct ctl_state *state){    uint8_t k3 = state->k[3];    if (!state->params->newaddr && ((k3 & 31) >> state->params->clustshift))	return false;    return phys_sector(state) < state->params->maxsectors;}static inline uint8_t *cur_buf(struct ctl_state *state){    return state->buf[state->k[1] >> 6];}static void disk_start_command(struct ctl_state *state){    abc_setup_out_queue(&state->iodev, state->k, 4, 0x81);}static void sync_drives(struct ctl_state *state){    for (int i = 0; i < 8; i++)	if (state->drv[i].flags & DF_MOUNTED)	    f_sync(&state->drv[i].file);}static void disk_set_error(struct ctl_state *state, unsigned int error){    abc_set_inp_default(&state->iodev, error);}static void disk_reset_state(struct ctl_state *state){    abc_set_inp_status(&state->iodev, 0);    disk_set_error(state, 0);    sync_drives(state);    disk_start_command(state);}static struct drive_state *name_to_drive(const char *drive){    struct ctl_state *state;    unsigned int ndrive;    /* All drive names are three letters long */    if (strlen(drive) != 3)	return NULL;    ndrive = drive[2] - '0';    if (ndrive > 7)	return NULL;    if (!memcmp("dr", drive, 2)) {	/* DRx alias for MOx (matches "old DOS") */	return &controllers[MOx].drv[ndrive];    }    for (int i = 0; i < CONTROLLER_TYPES; i++) {	struct ctl_state *state = &controllers[i];	if (!memcmp(state->params->name, drive, 2))	    return &state->drv[ndrive];    }    return NULL;		/* No such disk */}bool valid_drive_name(const char *drive){    return name_to_drive(drive) != NULL;}static int mount_drive(struct drive_state *drv, struct ctl_state *state,		       const char *filename){    if (mounted(drv)) {	f_close(&drv->file);	drv->flags &= ~DF_MOUNTED;    }    if (drv->force & DF_DISABLED)	return -1;    drv->flags = drv->force & ~DF_MOUNTED;    if (!filename) {	return 0;		/* Explicit unmount */    } else {	while (1) {	    BYTE mode = FA_OPEN_EXISTING | FA_READ;	    if (!(drv->flags & DF_READONLY))		mode |= FA_WRITE;	    FRESULT rv = f_open(&drv->file, filename, mode);	    if (rv == FR_WRITE_PROTECTED && (mode & FA_WRITE)) {		drv->flags |= DF_READONLY;		continue;	    }	    if (rv != FR_OK)		drv->flags |= DF_DISABLED;	    else		drv->flags |= DF_MOUNTED;	    break;	}	if (!(drv->flags & DF_MOUNTED))	    return -1;	con_printf("abcdisk: %-3s = %s\n", drv->name, filename);    }    /*     * Smaller than the standard disk size?  Treat the sectors     * beyond the end as bad.     */    unsigned int filesec = f_size(&drv->file) >> 8;    drv->sectors = min(filesec, state->params->maxsectors);    /* Interleaving parameters */#if INTERLEAVE	drv->ilfac = state->params->ilfac;	drv->ilmsk = state->params->ilmsk;#endif	return 0;}#define IDLE_CALLBACK_MASK	(1 << 4)static void abcdisk_callback_out(struct abc_dev *dev, uint8_t data){    struct ctl_state *state = container_of(dev, struct ctl_state, iodev);    dev->callback_mask = IDLE_CALLBACK_MASK;    ABC_INP1_DATA = dev->inp_data[1] = 0;    state->pending |= PEND_IO;}static void abcdisk_callback_inp(struct abc_dev *dev){    struct ctl_state *state = container_of(dev, struct ctl_state, iodev);    dev->callback_mask = IDLE_CALLBACK_MASK;    ABC_INP1_DATA = dev->inp_data[1] = 0;    state->pending |= PEND_IO;}static void abcdisk_callback_cmd(struct abc_dev *dev, uint8_t data, uint8_t addr){    struct ctl_state *state = container_of(dev, struct ctl_state, iodev);    if (addr == 4)	state->pending |= PEND_RESET;}static char * const disk_pfx[] = {    "/abcdisk.800/", "/abcdisk/", "/abcdisk.", "/", NULL};static void init_drives(struct ctl_state *state){    static bool disk_initialized;    static int disk_init_status;    uint32_t abc_status = ABC_STATUS;    const char * const *pfx_list;    int i;    if (!disk_initialized) {	disk_init_status = disk_init();	disk_initialized = true;    }    /* .80/ or .800/ for the first entry */    strcpy(disk_pfx[0] + 10 + !!(abc_status & ABC_STATUS_800), "0/");    /* If any of these don't exist we simply report device not ready */    state->drives = 0;    if (!disk_init_status) {	for (i = 0; i < 8; i++) {	    struct drive_state *drv = &state->drv[i];	    unsigned int filesec;	    char * const *pfx;	    snprintf(drv->name, sizeof drv->name, "%-.2s%c",		     state->params->name, i + '0');	    for (pfx = disk_pfx; *pfx; pfx++) {		char filename_buf[64];		snprintf(filename_buf, sizeof filename_buf,			 "%s%s", *pfx, drv->name);		if (!mount_drive(drv, state, filename_buf)) {		    state->drives++;		    break;		}	    }	}    }}static void do_next_command(struct ctl_state *state){    struct drive_state *drv = cur_drv_mutable(state);    uint8_t *buf = cur_buf(state);    if (state->k[0] & 0x01) {        /* READ SECTOR */	if (!(drv->flags & DF_MOUNTED)) {	    disk_set_error(state, DISK_NOT_READY);	} else if (!cur_sector_valid(state)) {	    disk_set_error(state, OUT_OF_RANGE);	} else if (!file_pos_valid(state)) {	    disk_set_error(state, CRC_ERROR);	} else {	    UINT rlen = 0;	    FRESULT rv;	    rv = f_lseek(&drv->file, file_pos(state));	    if (rv == FR_OK)		rv = f_read(&drv->file, buf, 256, &rlen);	    if (rv != FR_OK || rlen != 256) {		disk_set_error(state, CRC_ERROR);	    }	}        state->k[0] &= ~0x01;   /* Command done */    }    if (state->k[0] & 0x02) {        /* SECTOR TO HOST */        state->k[0] &= ~0x02;   /* Command done */	abc_setup_inp_queue(&state->iodev, buf, 256, 0x01);        return;    }    if (state->k[0] & 0x04) {        /* SECTOR FROM HOST */        state->k[0] &= ~0x04;   /* Command done */	abc_setup_out_queue(&state->iodev, buf, 256, 0x01);        return;    }    if (state->k[0] & 0x08) {        /* WRITE SECTOR */	if (!(drv->flags & DF_MOUNTED)) {	    disk_set_error(state, DISK_NOT_READY);	} else if (drv->flags & DF_READONLY) {	    disk_set_error(state, WRITE_PROTECT);	} else if (!cur_sector_valid(state)) {	    disk_set_error(state, OUT_OF_RANGE);	} else if (!file_pos_valid(state)) {	    disk_set_error(state, CRC_ERROR);	} else {	    UINT wlen = 0;	    FRESULT rv;	    rv = f_lseek(&drv->file, file_pos(state));	    if (rv == FR_OK)		rv = f_write(&drv->file, buf, 256, &wlen);	    if (rv != FR_OK || wlen != 256)		disk_set_error(state, WRITE_FAULT);        }	state->k[0] &= ~0x08;   /* Command done */    }#if FORMAT_SUPPORT    /* This code needs additional work */    if (state->k[0] & 0x10 && state->k[1] & 0x08) {	state->out_ptr = 0;	/* FORMAT */	if (!drv->hf) {	    state->error = 0x80;	/* Not ready */	} else if (!file_wrok(hf)) {            state->error = 0x40;	/* Write protect */	} else {	    unsigned int s, c0, c1, s0, s1;	    unsigned int cylsec = state->s * state->h;	    uint8_t data[256];	    unsigned int fmtsec, filesec;	    /* Sector count produced by format */	    fmtsec = state->params->maxsectors;	    /* For non-MO-drives, this seems to be internally generated */	    memset(data, 0x40, 256);	    if (state->fmtdata_in_buf) {		/*		 * MO drives put the sector image in the buffers, for		 * backwards compatibility and to support single density.		 *		 * Right before the F7 header CRC opcode is a density byte;		 * 00 for single, and 01 for double.  The data begins after		 * a byte of FB.		 */		bool single = false;		const uint8_t *p, *ep;		ep = state->buf[1];		for (p = state->buf[0]+1; p < ep; p++) {		    if (*p == 0xf7)			single = (p[-1] == 0);		    if (*p == 0xfb)			break;		}		fmtsec >>= single;		if (*p++ == 0xfb) { /* Data block found */		    if (single) {			/* Really two 128-byte sectors! */			memcpy(data, p, 128);			memcpy(data+128, p, 128);		    } else {			memcpy(data, p, 256);		    }		}	    }	    /*	     * Adjust the size of the accessible device to the smallest	     * of the physical file and the formatted size	     */	    filesec = drv->hf->filesize >> 8;	    drv->sectors = (filesec && filesec < fmtsec) ? filesec : fmtsec;	    /*	     * k2 and k3 contain the first and last cylinder numbers to	     * format, inclusively.  The last cylinder may be partial due	     * to virtual remapping, e.g. for sf floppies.	     */	    c0 = state->k[2];	    s0 = c0 * cylsec;	    c1 = state->k[3] + 1;	    s1 = c1 * cylsec;	    if (tracing(TRACE_DISK)) {		fprintf(tracef, "%s: formatting cyl %u..%u, sectors %u..%u\n",			drv->name, c0, c1-1, s0, s1-1);	    }	    clearerr(hf->f);	    state->error = 0;	    for (s = s0; s < s1; s++) {		unsigned int ps = virt2phys(drv, s);		if (ps >= drv->sectors) {		    state->error |= 0x02; /* Track 0/Lost data? */		    break;		} else if (hf->map) {		    memcpy(hf->map + (ps << 8), data, 256);		} else {		    fseek(hf->f, ps << 8, SEEK_SET);		    fwrite(data, 1, 256, hf->f);		}	    }	    if (ferror(hf->f))		state->error |= 0x20; /* Write fault */	}	state->k[1] &= ~0x08;    }#endif	/* FORMAT support */    if (!(state->k[1] & 0x38))	state->k[0] &= ~0x10;    disk_start_command(state);}#define SYNC_TIME (2*CPU_HZ)/* Called from the main loop */void abcdisk_io_poll(void){    static uint32_t last_sync;    uint32_t now = rdtime();    bool need_sync = (now - last_sync) >= SYNC_TIME;    last_sync = now;    for (int i = 0; i < CONTROLLER_TYPES; i++) {	struct ctl_state *state = &controllers[i];	enum pending pending;	mask_irq(ABC_IRQ);	pending = state->pending;	state->pending = 0;	unmask_irq(ABC_IRQ);	if (!pending)	    continue;	if (pending & PEND_RESET) {	    if (state->initialized)		disk_reset_state(state);	}	if (pending & PEND_IO) {	    if (!state->initialized)		init_drives(state);	    do_next_command(state);	}	if (need_sync)	    sync_drives(state);    }}/* * Called during initialization. Don't initialize the SD card here; * it will take too long and ABC will time out claiming no drives present. */void abcdisk_init(void){    static const struct abc_dev iodev_template = {	.callback_mask = IDLE_CALLBACK_MASK,	.status_first_out_mask = (uint8_t)~0x80,	.status_first_inp_mask = (uint8_t)~0x80,	.callback_out = abcdisk_callback_out,	.callback_inp = abcdisk_callback_inp,	.callback_cmd = abcdisk_callback_cmd    };    for (int i = 0; i < CONTROLLER_TYPES; i++) {	struct ctl_state *state = &controllers[i];	state->params = ¶meters[i];	state->iodev  = iodev_template;	disk_reset_state(state);	abc_register(&state->iodev, state->params->devsel);    }}
 |