/* * abcdisk.c * * Emulate an ABC80/800 disk controller */ #include #include #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 0 enum 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 0x08 enum controller_types { MOx, MFx, SFx, HDx, CONTROLLER_TYPES }; #if INTERLEAVE # define IL(mask, fac) .ilmsk = (mask), .ilfac = (fac), #else # define IL(mask, fac) #endif static 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 int virt2phys(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); } }