/* * Copyright (C) 2017 The Contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * A copy of the GNU General Public License can be found in the file * "gpl.txt" in the top directory of this repository. */ #include #define ver(...) _ver(fdd_verbose, 0, __VA_ARGS__) #define ver2(...) _ver(fdd_verbose, 1, __VA_ARGS__) #define ver3(...) _ver(fdd_verbose, 2, __VA_ARGS__) int32_t fdd_verbose = 0; #define N_CYL 80 #define N_SID 2 #define N_SEC 9 #define SZ_SEC 512 #define SZ_DISK (N_CYL * N_SID * N_SEC * SZ_SEC) #define REG_COM_STAT 0 #define REG_TRA 1 #define REG_SEC 2 #define REG_DAT 3 #define COM_REST 0x00 #define COM_SEEK 0x10 #define COM_SEEK_VER 0x14 #define COM_RD_SEC 0x80 #define COM_RD_SEC_MUL 0x90 #define COM_WR_SEC_WP 0xa0 #define COM_WR_SEC 0xa2 #define COM_INT 0xd0 #define COM_WR_TRA 0xf0 #define COM_LAT_CYC 5 #define COM_EXE_CYC 5 typedef enum { STEP_IDLE, STEP_PREP, STEP_EXEC } step_t; typedef struct { int32_t reg_tra; int32_t reg_sec; int32_t reg_dat; int32_t sid; step_t step; int32_t com; int32_t cyc; uint8_t *dat; bool tra_0; } state_t; static state_t state = { .reg_tra = 0, .reg_sec = 0, .reg_dat = 0, .sid = 0, .step = STEP_IDLE, .com = -1, .cyc = 0, .dat = NULL, .tra_0 = false }; static uint8_t image[SZ_DISK]; static const char *com_string(int32_t com) { switch (com) { case COM_REST: return "COM_REST"; case COM_SEEK: return "COM_SEEK"; case COM_SEEK_VER: return "COM_SEEK_VER"; case COM_RD_SEC: return "COM_RD_SEC"; case COM_RD_SEC_MUL: return "COM_RD_SEC_MUL"; case COM_WR_SEC_WP: return "COM_WR_SEC_WP"; case COM_WR_SEC: return "COM_WR_SEC"; case COM_WR_TRA: return "COM_WR_TRA"; default: fail("unknown command 0x%02x", com); } } static void stat_string(int32_t stat, char *buff) { buff[0] = 0; if ((stat & 0x80) != 0) { strcat(buff, " mot_on"); } if ((stat & 0x04) != 0) { strcat(buff, " zero"); } if ((stat & 0x02) != 0) { strcat(buff, " dat_req"); } if ((stat & 0x01) != 0) { strcat(buff, " busy"); } } void fdd_set_side(int32_t sid) { ver2("sid <- %d", sid); state.sid = sid; } void fdd_set_sel(int32_t sel) { ver2("sel <- %d", sel); } void fdd_init(void) { ver("fdd init"); inf("loading disk image file %s", disk); SDL_RWops *ops = SDL_RWFromFile(disk, "rb"); if (ops == NULL) { fail("error while opening disk image file %s", disk); } size_t loaded = 0; while (loaded < SZ_DISK) { size_t n_rd = SDL_RWread(ops, image + loaded, 1, SZ_DISK - loaded); if (n_rd == 0) { fail("error while reading disk image file %s", disk); } loaded += n_rd; } SDL_RWclose(ops); } void fdd_quit(void) { ver("fdd quit"); } bool fdd_exec(void) { ver3("fdd exec"); switch (state.step) { case STEP_IDLE: break; case STEP_PREP: ver3("prep %d", state.cyc); --state.cyc; if (state.cyc == 0) { ver2("exec %s", com_string(state.com)); state.step = STEP_EXEC; state.cyc = COM_EXE_CYC; } break; case STEP_EXEC: ver3("exec %d", state.cyc); --state.cyc; if (state.cyc == 0) { ver2("idle %s", com_string(state.com)); state.step = STEP_IDLE; } break; } return false; } uint32_t fdd_read(uint32_t off, int32_t sz) { ver3("fdd rd %u:%d", off, sz * 8); if (sz != 1 || off > 3) { fail("invalid fdd rd %u:%d", off, sz * 8); } uint32_t rv; switch (off) { case REG_COM_STAT: rv = 0x80; // motor on if (state.step == STEP_EXEC) { rv |= 0x01; // busy switch (state.com) { case COM_RD_SEC: case COM_RD_SEC_MUL: case COM_WR_SEC: case COM_WR_SEC_WP: rv |= 0x02; // data request break; } } if (state.tra_0) { rv |= 0x04; // track zero } char stat[100]; stat_string((int32_t)rv, stat); ver3("stat -> 0x%02x%s", rv, stat); break; case REG_TRA: rv = (uint32_t)state.reg_tra; ver2("tra -> %u", rv); break; case REG_SEC: rv = (uint32_t)state.reg_sec; ver2("sec -> %u", rv); break; case REG_DAT: if (state.step != STEP_EXEC || (state.com != COM_RD_SEC && state.com != COM_RD_SEC_MUL)) { fail("unexpected data register read"); } rv = *state.dat; int32_t addr = (int32_t)(state.dat - image); if ((addr & (SZ_SEC - 1)) == 0) { ver2("addr 0x%06x -> 0x%02x", addr, rv); } else { ver3("addr 0x%06x -> 0x%02x", addr, rv); } ++state.dat; ++addr; if ((addr & (SZ_SEC - 1)) == 0 && state.com == COM_RD_SEC) { state.step = STEP_IDLE; state.cyc = 0; } else { state.cyc = COM_EXE_CYC; } break; default: rv = 0; break; } return rv; } void fdd_write(uint32_t off, int32_t sz, uint32_t val) { ver3("fdd wr %u:%d 0x%0*x", off, sz * 8, sz * 2, val); if (sz != 1 || off > 3) { fail("invalid fdd wr %u:%d", off, sz * 8); } switch (off) { case REG_COM_STAT: ver2("com <- 0x%02x, tra %d, sid %d, sec %d", val, state.reg_tra, state.sid, state.reg_sec); state.com = (int32_t)val; ver2("prep %s", com_string(state.com)); state.step = STEP_PREP; state.cyc = COM_LAT_CYC; switch (val) { case COM_REST: state.reg_tra = 0; state.tra_0 = true; state.dat = NULL; break; case COM_SEEK: case COM_SEEK_VER: state.reg_tra = state.reg_dat; state.tra_0 = state.reg_tra == 0; state.dat = NULL; break; case COM_RD_SEC: case COM_RD_SEC_MUL: case COM_WR_SEC: case COM_WR_SEC_WP: { size_t sec_off = (size_t)(((state.reg_tra * N_SID + state.sid) * N_SEC + state.reg_sec - 1) * SZ_SEC); state.dat = image + sec_off; state.tra_0 = false; break; } case COM_INT: state.step = STEP_IDLE; state.cyc = 0; state.dat = NULL; state.tra_0 = false; break; case COM_WR_TRA: state.tra_0 = false; fail("format not yet supported"); break; } break; case REG_TRA: state.reg_tra = (int32_t)val; ver2("tra <- %u", val); break; case REG_SEC: state.reg_sec = (int32_t)val; ver2("sec <- %u", val); break; case REG_DAT: if (state.step == STEP_EXEC && (state.com == COM_WR_SEC || state.com == COM_WR_SEC_WP)) { *state.dat = (uint8_t)val; int32_t addr = (int32_t)(state.dat - image); if ((addr & (SZ_SEC - 1)) == 0) { ver2("addr 0x%06x <- 0x%02x", addr, val); } else { ver3("addr 0x%06x <- 0x%02x", addr, val); } ++state.dat; ++addr; if ((addr & (SZ_SEC - 1)) == 0) { state.step = STEP_IDLE; state.cyc = 0; } else { state.cyc = COM_EXE_CYC; } } else { state.reg_dat = (int32_t)val; ver2("dat <- 0x%02x", val); } break; default: break; } }