/* * abcdisk.c * * Emulate an ABC80/800 disk controller */ #include #include #include "common.h" #include "config.h" #include "io.h" #include "abcio.h" #include "console.h" #include "sdcard.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 = 1, /* Host file open, drive exported */ DF_READONLY = 2, /* Drive is readonly */ DF_DISABLED = 4, /* Drive is disabled */ DF_DIRTY = 8 /* Disk has been written */ }; enum pending { PEND_IO = 1, /* I/O transfer complete */ PEND_STARTCMD = 2, /* C1# = go to command state */ PEND_RESET = 4 /* C3# or RST# = local/global reset */ }; /* * Status bits in INP(1); bits 5 & 2 are unknown, bit 4 is apparently * always 0 on at least card 66 81046-02 (55 21046-x1)... * "fast DMA controller". */ enum abc_status { AS_CMD = 0x80, /* Start of command */ AS_WRITE = 0x40, /* Data direction OUT(0) (to controller) */ AS_ERROR = 0x08, /* INP(0) contains error info */ AS_READING = 0x04, /* Waiting for sector load */ AS_READY = 0x01 /* Ready for input */ }; /* Number of fragments (extents) we can memoize */ #define MAX_FAST_FRAGMENTS 16 #define CLTBL_SIZE ((MAX_FAST_FRAGMENTS+1)*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 */ DWORD cltbl[CLTBL_SIZE]; /* Extent map */ }; /* Fixed parameters for each controller */ struct ctl_params { enum sysvar_enum enable; /* Enable configuration variable */ enum sysvar_enum devsel; /* Device select code variable */ uint8_t clustshift; /* log2(clustersize/256) */ 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 */ uint8_t error; /* Error status */ 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 */ }; /* * Error codes for INP(0), derived from FD1791 status codes * * DOSGEN depends on OUT_OF_RANGE having 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, XDx, 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] = { .enable = config_abc_io_mo_enable, .devsel = config_abc_io_mo_devsel, .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] = { .enable = config_abc_io_mf_enable, .devsel = config_abc_io_mf_devsel, .clustshift = 2, .maxsectors = 80 * 2 * 16, .c = 80, .h = 2, .s = 16, .name = "mf" }, /* SFx: 8" floppy (DD88, ABC838) */ [SFx] = { .enable = config_abc_io_sf_enable, .devsel = config_abc_io_sf_devsel, .clustshift = 2, .maxsectors = (77 * 2 - 1) * 26, /* Track 0, side 0 not used */ .c = 77, .h = 2, .s = 26, .name = "sf" }, [HDx] = { .enable = config_abc_io_hd_enable, .devsel = config_abc_io_hd_devsel, .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" }, /* Second harddisk */ [XDx] = { .enable = config_abc_io_xd_enable, .devsel = config_abc_io_xd_devsel, .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 = "xd" }, }; 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) { #if 0 con_printf("%-2s: start command\n", state->params->name); #endif memset(&state->k, 0xee, sizeof state->k); state->iodev.callback_mask |= 1 << 0; abc_setup_out_queue(&state->iodev, state->k, 4, AS_CMD|AS_READY|AS_WRITE| (state->error ? AS_ERROR : 0)); } static void sync_drives(struct ctl_state *state) { for (int i = 0; i < 8; i++) { struct drive_state *drv = &state->drv[i]; if (drv->flags & DF_DIRTY) { f_sync(&state->drv[i].file); drv->flags &= ~DF_DIRTY; } } } static void disk_set_error(struct ctl_state *state, unsigned int error) { abc_set_inp_default(&state->iodev, state->error = error); state->k[0] = 0; /* Abort remaining command sequence */ } static void disk_reset_state(struct ctl_state *state) { abc_set_inp_status(&state->iodev, 0); disk_set_error(state, 0); disk_start_command(state); } static struct drive_state * name_to_drive(const char *drive) { 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 * const 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) { FRESULT rv; if (mounted(drv)) { if (!(sdc.fsstatus & STA_NOINIT)) f_close(&drv->file); drv->flags &= ~DF_MOUNTED; state->drives--; } 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; rv = f_open(&drv->file, filename, mode); if (rv == FR_WRITE_PROTECTED && (mode & FA_WRITE)) { drv->flags |= DF_READONLY; continue; } drv->flags |= (rv == FR_OK) ? DF_MOUNTED : DF_DISABLED; break; } if (!(drv->flags & DF_MOUNTED)) return -1; } con_printf("abcdisk: %-3s = %s\n", drv->name, filename); /* Try to memoize extents for fast seek */ drv->cltbl[0] = CLTBL_SIZE; drv->file.cltbl = drv->cltbl; rv = f_lseek(&drv->file, CREATE_LINKMAP); if (rv != FR_OK) { con_printf("abcdisk: %-3s ! file too fragmented, will be slow\n", drv->name); } /* * 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 state->drives++; return 0; } static void unmount_drives(struct ctl_state *state) { int i; for (int i = 0; i < 8; i++) { struct drive_state *drv = &state->drv[i]; if (drv->flags & DF_MOUNTED) mount_drive(drv, state, NULL); } } /* RST# = global reset, C3# = local reset, C1# = goto command start */ #define IDLE_CALLBACK_MASK ((1 << 7)|(1 << 4)|(1 << 2)) static ABC_CALLBACK(abcdisk_callback_out_inp) { struct ctl_state * const state = container_of(dev, struct ctl_state, iodev); dev->callback_mask = IDLE_CALLBACK_MASK; __abc_set_inp_status(dev, 0); state->error = 0; state->pending |= PEND_IO; } static ABC_CALLBACK(abcdisk_callback_restart) { struct ctl_state * const state = container_of(dev, struct ctl_state, iodev); state->pending |= PEND_STARTCMD; } static ABC_CALLBACK(abcdisk_callback_rst) { struct ctl_state * const state = container_of(dev, struct ctl_state, iodev); state->pending |= PEND_RESET; } static char abcdisk_80_800[] = "/abcdisk.800/"; static const char * const disk_pfx[] = { abcdisk_80_800, "/abcdisk/", "/abcdisk.", "/", NULL }; static void init_drives(struct ctl_state *state) { char *p80; int i; /* .80/ or .800/ for the first entry */ p80 = abcdisk_80_800 + 10 + is_abc800(); p80[0] = '0'; p80[1] = '/'; p80[2] = '\0'; for (i = 0; i < 8; i++) { struct drive_state *drv = &state->drv[i]; unsigned int filesec; const 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)) break; } } state->initialized = true; } static void do_next_command(struct ctl_state *state) { struct drive_state * const drv = cur_drv_mutable(state); uint8_t *buf = cur_buf(state); #if 0 con_printf("%-3s: cmd %02x %02x %02x %02x\n", drv->name, state->k[0], state->k[1], state->k[2], state->k[3]); #endif 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 { UINT rlen = 0; FRESULT rv; set_led(LED_DISKIO, true); abc_set_inp_status(&state->iodev, AS_READING|AS_READY); 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 */ state->iodev.callback_mask |= 1 << 8; abc_setup_inp_queue(&state->iodev, buf, 256, AS_READY); return; } if (state->k[0] & 0x04) { /* SECTOR FROM HOST */ state->k[0] &= ~0x04; /* Command done */ state->iodev.callback_mask |= 1 << 0; abc_setup_out_queue(&state->iodev, buf, 256, AS_WRITE|AS_READY); 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 { UINT wlen = 0; FRESULT rv; set_led(LED_DISKIO, true); rv = f_lseek(&drv->file, file_pos(state)); if (rv == FR_OK) { drv->flags |= DF_DIRTY; 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; } if (!(state->k[1] & 0x38)) state->k[0] &= ~0x10; #else /* No FORMAT support */ state->k[0] &= ~0x10; #endif state->k[0] &= ~0xe0; /* Unimplemented commands */ disk_start_command(state); } static FATFS sd_fs; static bool sd_mounted; static int mount_disk(void) { FRESULT rv; char label[128]; uint32_t volid, freeclust; FATFS *fs; set_led(LED_DISKIO, true); rv = f_mount(&sd_fs, "", 1); if (rv != FR_OK) { con_printf("sdcard: no volume found\n"); set_led(LED_DISKIO, false); return -1; } sd_mounted = true; label[0] = '\0'; volid = 0; f_getlabel("", label, &volid); con_printf("sdcard: volume found, label \"%s\", volid %08x\n", label, volid); freeclust = 0; f_getfree("", &freeclust, &fs); con_printf("sdcard: %u/%u clusters free, clusters = %u bytes\n", freeclust, fs->n_fatent - 2, fs->csize << 9); /* Check for firmware update image and install if it exists */ rom_flash_from_sdcard(); return 0; } #define SYNC_TIME (1*TIMER_HZ) /* 1 s */ /* Called from the main loop */ void __hot abcdisk_io_poll(void) { static uint32_t last_sync; static uint32_t last_timer = -1U; static uint32_t prev_abc_status = -1U; uint32_t abc_status_change; uint32_t now = timer_count(); bool need_sync = false; uint32_t abc_status = ABC_STATUS & ABC_STATUS_LIVE; bool unmount_all = false; bool reset_all = false; if (now != last_timer) { /* * Periodically try to initialize the SD card if we are waiting * for it. The disk cache, however, will be initialized when fatfs * tries to mount the device. */ if (sdc.status & STA_NOINIT) { if (!(sdcard_init() & STA_NOINIT)) mount_disk(); unmount_all = true; } else if (sdcard_present_poll() & STA_NOINIT) { unmount_all = true; } else { if ((now - last_sync) >= SYNC_TIME) { need_sync = true; last_sync = now; } } last_timer = now; } /* Allow configuration to override */ if (is_abc800()) abc_status |= ABC_STATUS_800; abc_status_change = abc_status ^ prev_abc_status; if (unlikely(abc_status_change)) { const char *host; prev_abc_status = abc_status; set_led(LED_ABCBUS, !!(abc_status & ABC_STATUS_LIVE)); con_puts("ABC-bus host: "); con_puts(abc_status & ABC_STATUS_800 ? "ABC800" : "ABC80"); con_puts(abc_status & ABC_STATUS_LIVE ? " (online)\n" : " (offline)\n"); unmount_all = !!(abc_status_change & ABC_STATUS_800); reset_all = true; need_sync = true; } for (int i = 0; i < CONTROLLER_TYPES; i++) { struct ctl_state * const state = &controllers[i]; enum pending pending = 0; if (unmount_all && state->initialized) { unmount_drives(state); state->initialized = false; } else if (!state->initialized && !(sdc.status & STA_NOINIT)) { init_drives(state); } if (reset_all) disk_reset_state(state); if (abc_status & ABC_STATUS_LIVE) { mask_irq(ABC_IRQ); pending = state->pending; state->pending = 0; unmask_irq(ABC_IRQ); if (pending & (PEND_RESET|PEND_STARTCMD)) disk_reset_state(state); else if (pending & PEND_IO) do_next_command(state); } if (state->initialized && (need_sync || (pending & PEND_RESET))) sync_drives(state); } if (need_sync) { /* Did we sync drives? Clear LED. */ set_led(LED_DISKIO, false); } } /* * 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, .inp_en = 3, .status_first_out_mask = (uint8_t)~0x80, .status_first_inp_mask = (uint8_t)~0x80, .callback_out[0] = abcdisk_callback_out_inp, .callback_inp[0] = abcdisk_callback_out_inp, .callback_out[2] = abcdisk_callback_restart, .callback_out[4] = abcdisk_callback_rst, /* C3# = local reset */ .callback_rst = abcdisk_callback_rst }; for (size_t i = 0; i < CONTROLLER_TYPES; i++) { struct ctl_state * const state = &controllers[i]; state->params = ¶meters[i]; state->iodev = iodev_template; state->iodev.name = state->params->name; disk_reset_state(state); } abcdisk_config(); } /* * Called during initialization; can also be called on reconfig. */ void abcdisk_config(void) { for (size_t i = 0; i < CONTROLLER_TYPES; i++) { struct ctl_state * const state = &controllers[i]; unsigned int devsel = getvar_uint(state->params->devsel); if (!getvar_bool(state->params->enable)) devsel = DEVSEL_NONE; abc_register(&state->iodev, devsel); } } /* * Shut down the disk subsystem for reboot */ void abcdisk_shutdown(void) { if (!sd_mounted) return; /* Nothing to do */ for (int i = 0; i < CONTROLLER_TYPES; i++) { struct ctl_state * const state = &controllers[i]; if (state->initialized) unmount_drives(state); } /* Unmount the SD card filesystem */ f_unmount(""); /* Check one last time to see if anything needs to be written out */ disk_ioctl(0, CTRL_SYNC, NULL); sd_mounted = false; }