Преглед изворни кода

WIP: ABC disk controller emulation

H. Peter Anvin пре 3 година
родитељ
комит
5eed62e710
2 измењених фајлова са 538 додато и 0 уклоњено
  1. 522 0
      fw/abcdisk.c
  2. 16 0
      fw/abcdma.c

+ 522 - 0
fw/abcdisk.c

@@ -0,0 +1,522 @@
+/*
+ * abcdisk.c
+ *
+ * Emulate an ABC80/800 disk controller
+ */
+
+#include "fw.h"
+#include "ff.h"
+
+/* Option flags, mostly for debugging */
+#define NOTTHERE	0
+#define READONLY	0
+#define INTERLEAVE	0
+
+/* Disk controller I/O state machine */
+enum controller_state {
+    ctl_uninit,			/* Disks not initialized */
+    ctl_command,		/* Expecting command */
+    ctl_upload,			/* Expecting data block upload */
+    ctl_download		/* Expecting data block download */
+};
+
+enum drive_flags {
+    DF_MOUNTED,			/* Host file open, drive exported */
+    DF_READONLY,		/* Drive is readonly */
+    DF_DISABLED			/* Drive is disabled */
+};
+
+/* Per-drive state */
+struct drive_state {
+    FIL file;			/* Host file */
+    char name[4];               /* Drive name */
+    uint16_t sectors;		/* Total size in 256-byte sectors */
+    uint8_t ilmsk, ilfac;       /* Software interleaving parameters */
+    enum drive_flags flags;
+    enum drive_flags force;	/* Option to force flags */
+};
+
+/* Per-controller state */
+struct ctl_state {
+    enum controller_state state;
+    uint8_t k[4];		/* Command bytes */
+    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 */
+    uint8_t drives;		/* Total drives present */
+    bool newaddr;		/* "New addressing" */
+    bool fmtdata_in_buf;	/* Use user-provided formatting data */
+    const char name[4];		/* Name of controller (disk type) */
+    uint8_t ilmsk, ilfac;	/* Software interleaving parea */
+    uint8_t error;		/* Error code status */
+    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 */
+
+enum controller_types {
+    MOx,
+    MFx,
+    SFx,
+    HDx,
+    CONTROLLER_TYPES
+};
+
+static struct ctl_state controllers[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,
+	.fmtdata_in_buf = true,
+	.ilmsk = 15,
+	.ilfac = 7,
+	.name = "mo"
+    },
+
+    /* 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 * 32 - 1) * 32,     /* Maximum supported by UFD-DOS */
+	.c = 238, .h = 16, .s = 64,
+	.name = "hd"
+    }
+};
+
+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->newaddr)
+        return (k2 << 8) + k3;
+    else
+        return (((k2 << 3) + (k3 >> 5)) << state->clustshift) + (k3 & 31);
+}
+
+/* Get physical sector number, after interleaving */
+static inline unsigned int
+virt2phys(const struct drive_state *drv, unsigned int sector)
+{
+    unsigned int ilmsk = drv->ilmsk;
+    unsigned int ilfac = drv->ilfac;
+
+    sector = (sector & ~ilmsk) | ((sector * ilfac) & ilmsk);
+    return sector;
+}
+
+static inline unsigned int phys_sector(const struct ctl_state *state)
+{
+    return virt2phys(cur_drv(state), cur_sector(state));
+}
+
+static inline off_t file_pos(const struct ctl_state *state)
+{
+    return phys_sector(state) << 8;
+}
+
+static inline bool cur_sector_valid(const struct ctl_state *state)
+{
+    uint8_t k3 = state->k[3];
+
+    if (!state->newaddr && ((k3 & 31) >> state->clustshift))
+	return false;
+
+    return cur_sector(state) < cur_drv(state)->sectors;
+}
+
+static inline bool file_pos_valid(const struct ctl_state *state)
+{
+    return file_pos(state) < cur_drv(state)->hf->filesize - 255;
+}
+
+static inline uint8_t *cur_buf(struct ctl_state *state)
+{
+    return state->buf[state->k[1] >> 6];
+}
+
+static void disk_reset_state(struct ctl_state *state)
+{
+    int i;
+
+    state->state = disk_k0;
+    state->error = 0;
+    state->in_ptr = -1;
+    state->out_ptr = 0;
+    state->notready_ctr = NOT_READY;
+
+    for (i = 0; i < 8; i++)
+        f_sync(&state->drv[i].file);
+}
+
+static struct drive_state *
+name_to_drive(const char *drive)
+{
+    int sel;
+    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 &mo_state.drv[ndrive];
+    }
+
+    for (sel = 0; sel < 64; sel++) {
+	state = sel_to_state[sel];
+	if (!state)
+	    continue;
+	if (!memcmp(state->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, 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;
+    }
+
+    /*
+     * Smaller than the standard disk size?  Treat the sectors
+     * beyond the end as bad.
+     */
+    unsigned int filesec = f_size(drv->file) >> 8;
+    drv->sectors = (filesec && filesec < state->maxsectors)
+            ? filesec : state->maxsectors;
+
+    /* Interleaving parameters */
+#if INTERLEAVE
+	drv->ilfac = state->ilfac;
+	drv->ilmsk = state->ilmsk;
+#else
+	drv->ilfac = drv->ilmsk = 0;
+#endif
+
+	return 0;
+}
+
+static const char * const disk_pfx_abc80[]  = { "/abcdisk.80/",  "/abcdisk/", "/", NULL };
+static const char * const disk_pfx_abc800[] = { "/abcdisk.800/", "/abcdisk/", "/", NULL };
+
+static void disk_init(struct ctl_state *state)
+{
+    uint32_t abc_status = ABC_STATUS;
+    const char * const *pfx_list;
+    int i;
+
+    /* If no ABC, don't initialize disks; should in fact unmount them all */
+    if (!(abc_status & ABC_STATUS_LIVE))
+	return;
+
+    pfx_list = (abc_status & ABC_STATUS_800) ? disk_pfx_abc800 : disk_pfx_abc80;
+    
+    /* If any of these don't exist we simply report device not ready */
+    state->drives = 0;
+    
+    for (i = 0; i < 8; i++) {
+        struct drive_state *drv = &state->drv[i];
+        unsigned int filesec;
+	const char **pfx;
+
+        snprintf(drv->name, sizeof drv->name, "%-.2s%c", state->name, i + '0');
+
+	for (pfx = pfx_list; *pfx; pfx++) {
+	    char filename_buf[64];
+	    snprintf(filename_buf, sizeof filename_buf, "%s%s", *pfx, drv->name);
+
+	    if (!mount_drive(drv, filename_buf)) {
+		state->drives++;
+		break;
+	    }
+	}
+    }
+
+    disk_reset_state(state);
+}
+
+static void setup_dma(struct drive_state *drv, unsigned int len, uint8_t devsel, bool out_dir)
+{
+    
+    
+
+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 (!) {
+	    state->error = 0x80; /* Device not ready */
+	} else if (!cur_sector_valid(state)) {
+	    state->error = OUT_OF_RANGE;
+	} else if (!file_pos_valid(state)) {
+	    state->error = 0x08; /* 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) {
+		state->error = 0x08; /* CRC error */
+	    }
+	}
+        state->k[0] &= ~0x01;   /* Command done */
+    }
+    if (state->k[0] & 0x02) {
+        /* SECTOR TO HOST */
+	
+	state->in_ptr = 0;
+        state->state = disk_download;
+        state->k[0] &= ~0x02;   /* Command done */
+        return;
+    }
+    if (state->k[0] & 0x04) {
+        /* SECTOR FROM HOST */
+        state->state = disk_upload;
+        state->out_ptr = 0;
+        state->k[0] &= ~0x04;   /* Command done */
+        return;
+    }
+    if (state->k[0] & 0x08) {
+        /* WRITE SECTOR */
+	if (!hf) {
+	    state->error = 0x80; /* Not ready */
+	} else if (!file_wrok(hf)) {
+	    state->error = 0x40; /* Write protect */
+	} else if (!cur_sector_valid(state)) {
+	    state->error = OUT_OF_RANGE;
+	    if (tracing(TRACE_DISK)) {
+		fprintf(tracef, "%s: write: disk sector out of range: %u/%u (cluster %u/%u)\n",
+			drv->name, cur_sector(state), drv->sectors,
+			cur_sector(state) >> state->clustshift,
+			drv->sectors >> state->clustshift);
+	    }
+	} else if (!file_pos_valid(state)) {
+	    state->error = 0x08; /* CRC error(?) */
+	} else {
+	    if (hf->map) {
+		memcpy(hf->map + file_pos(state), buf, 256);
+	    } else {
+		clearerr(hf->f);
+		fseek(hf->f, file_pos(state), SEEK_SET);
+		fwrite(buf, 1, 256, hf->f);
+		if (ferror(hf->f))
+		    state->error = 0x20; /* Write fault */
+	    }
+        }
+        state->k[0] &= ~0x08;   /* Command done */
+    }
+    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->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;
+    }
+
+    if (!(state->k[1] & 0x38))
+	state->k[0] &= ~0x10;
+
+    if (tracing(TRACE_DISK)) {
+      if (state->trace_dump) {
+	trace_dump(TRACE_DISK, drv->name, buf, 256);
+	state->trace_dump = false;
+      }
+    }
+    state->state = disk_k0;
+}
+
+static void disk_reset(uint8_t sel)
+{
+    struct ctl_state *state = sel_to_state[sel];
+
+    if (state && state->state != disk_need_init)
+	disk_reset_state(state);
+}
+

+ 16 - 0
fw/abcdma.c

@@ -0,0 +1,16 @@
+#include "fw.h"
+#include "io.h"
+
+void setup_abc_dma(void *buf, unsigned int len, uint8_t devsel,
+		  bool dir_in, unsigned long status)
+{
+    devsel &= 63;
+
+    volatile uint32_t *portctl    = &ABCMEM_WRPORT(devsel)  + dir_in;
+    volatile uint32_t *portcount  = &ABCMEM_WRCOUNT(devsel) + dir_in;
+    volatile uint32_t *portstatus = &ABCMEMMAP_STATUS(devsel);
+
+    *portctl = ((size_t)buf & 0x01ffffff) | (ABCMEMMAP_WR << dir_in);
+    *portcount = len;
+    *portstatus = status;
+}