/* * 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); }