|
@@ -5,20 +5,14 @@
|
|
|
*/
|
|
|
|
|
|
#include "fw.h"
|
|
|
+#include "abcio.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 */
|
|
|
-};
|
|
|
+#define FORMAT_SUPPORT 0
|
|
|
|
|
|
enum drive_flags {
|
|
|
DF_MOUNTED, /* Host file open, drive exported */
|
|
@@ -26,19 +20,26 @@ enum drive_flags {
|
|
|
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 */
|
|
|
};
|
|
|
|
|
|
/* Per-controller state */
|
|
|
struct ctl_state {
|
|
|
- enum controller_state state;
|
|
|
+ struct abc_dev *iodev;
|
|
|
uint8_t k[4]; /* Command bytes */
|
|
|
uint8_t clustshift; /* log2(clustersize/256) */
|
|
|
uint8_t devsel; /* I/O device select */
|
|
@@ -46,10 +47,15 @@ struct ctl_state {
|
|
|
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) */
|
|
|
+ bool initialized; /* Controller initialized */
|
|
|
+#if INTERLEAVE
|
|
|
uint8_t ilmsk, ilfac; /* Software interleaving parea */
|
|
|
- uint8_t error; /* Error code status */
|
|
|
+#endif
|
|
|
+#if FORMAT_SUPPORT
|
|
|
+ bool fmtdata_i_buf; /* Use user-provided formatting data */
|
|
|
+#endif
|
|
|
+ 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 */
|
|
|
};
|
|
@@ -61,6 +67,11 @@ struct ctl_state {
|
|
|
*/
|
|
|
#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,
|
|
@@ -69,6 +80,12 @@ enum controller_types {
|
|
|
CONTROLLER_TYPES
|
|
|
};
|
|
|
|
|
|
+#if INTERLEAVE
|
|
|
+# define IL(mask, fac) .ilmsk = (mask), .ilfac = (fac),
|
|
|
+#else
|
|
|
+# define IL(mask, fac)
|
|
|
+#endif
|
|
|
+
|
|
|
static struct ctl_state controllers[CONTROLLER_TYPES] = {
|
|
|
/*
|
|
|
* MOx: covers all of these formats:
|
|
@@ -81,10 +98,11 @@ static struct ctl_state controllers[CONTROLLER_TYPES] = {
|
|
|
.clustshift = 0,
|
|
|
.maxsectors = 40 * 2 * 16,
|
|
|
.c = 40, .h = 2, .s = 16,
|
|
|
+ .name = "mo",
|
|
|
+#if FORMAT_SUPPORT
|
|
|
.fmtdata_in_buf = true,
|
|
|
- .ilmsk = 15,
|
|
|
- .ilfac = 7,
|
|
|
- .name = "mo"
|
|
|
+#endif
|
|
|
+ IL(15, 7)
|
|
|
},
|
|
|
|
|
|
/* MFx: DSQD = 80×16x2 (640K, ABC832/834) */
|
|
@@ -144,10 +162,12 @@ static inline unsigned int cur_sector(const struct ctl_state *state)
|
|
|
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;
|
|
|
}
|
|
|
|
|
@@ -181,18 +201,32 @@ 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_reset_state(struct ctl_state *state)
|
|
|
{
|
|
|
- int i;
|
|
|
+ abc_set_inp_status(&state->iodev, 0);
|
|
|
+ disk_set_error(state, 0);
|
|
|
|
|
|
- state->state = disk_k0;
|
|
|
- state->error = 0;
|
|
|
- state->in_ptr = -1;
|
|
|
- state->out_ptr = 0;
|
|
|
- state->notready_ctr = NOT_READY;
|
|
|
+ sync_drives(state);
|
|
|
|
|
|
- for (i = 0; i < 8; i++)
|
|
|
- f_sync(&state->drv[i].file);
|
|
|
+ disk_start_command(state);
|
|
|
+}
|
|
|
+
|
|
|
+static void disk_set_error(struct ctl_state *state, unsigned int error)
|
|
|
+{
|
|
|
+ state->error = error;
|
|
|
+ abc_set_inp_default(&state->iodev, error);
|
|
|
}
|
|
|
|
|
|
static struct drive_state *
|
|
@@ -283,57 +317,80 @@ static int mount_drive(struct drive_state *drv, const char *filename)
|
|
|
#if INTERLEAVE
|
|
|
drv->ilfac = state->ilfac;
|
|
|
drv->ilmsk = state->ilmsk;
|
|
|
-#else
|
|
|
- drv->ilfac = drv->ilmsk = 0;
|
|
|
#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 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)
|
|
|
+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 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 (!disk_initialized) {
|
|
|
+ disk_init_status = disk_init();
|
|
|
+ disk_initialized = true;
|
|
|
+ }
|
|
|
|
|
|
+ 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;
|
|
|
+ if (!disk_init_status) {
|
|
|
+ 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);
|
|
@@ -341,12 +398,12 @@ static void do_next_command(struct ctl_state *state)
|
|
|
|
|
|
if (state->k[0] & 0x01) {
|
|
|
/* READ SECTOR */
|
|
|
- if (!) {
|
|
|
- state->error = 0x80; /* Device not ready */
|
|
|
+ if (!(drv->flags & DF_MOUNTED)) {
|
|
|
+ disk_set_error(DISK_NOT_READY);
|
|
|
} else if (!cur_sector_valid(state)) {
|
|
|
- state->error = OUT_OF_RANGE;
|
|
|
+ disk_set_error(OUT_OF_RANGE);
|
|
|
} else if (!file_pos_valid(state)) {
|
|
|
- state->error = 0x08; /* CRC error(?) */
|
|
|
+ disk_set_error(CRC_ERROR);
|
|
|
} else {
|
|
|
UINT rlen = 0;
|
|
|
FRESULT rv;
|
|
@@ -355,55 +412,50 @@ static void do_next_command(struct ctl_state *state)
|
|
|
if (rv == FR_OK)
|
|
|
rv = f_read(&drv->file, buf, 256, &rlen);
|
|
|
if (rv != FR_OK || rlen != 256) {
|
|
|
- state->error = 0x08; /* CRC error */
|
|
|
+ disk_set_error(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 */
|
|
|
+ abc_setup_inp_queue(&state->iodev, buf, 256, 0x01);
|
|
|
return;
|
|
|
}
|
|
|
if (state->k[0] & 0x04) {
|
|
|
/* SECTOR FROM HOST */
|
|
|
- state->state = disk_upload;
|
|
|
- state->out_ptr = 0;
|
|
|
state->k[0] &= ~0x04; /* Command done */
|
|
|
+ abc_setup_out_queue(&state->iodev, buf, 256, 0x01);
|
|
|
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 */
|
|
|
+ if (!(drv->flags & DF_MOUNTED)) {
|
|
|
+ disk_set_error(DISK_NOT_READY);
|
|
|
+ } else if (df->flags & DF_READONLY) {
|
|
|
+ disk_set_error(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);
|
|
|
- }
|
|
|
+ disk_set_error(OUT_OF_RANGE);
|
|
|
} else if (!file_pos_valid(state)) {
|
|
|
- state->error = 0x08; /* CRC error(?) */
|
|
|
+ disk_set_error(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 */
|
|
|
+ UINT wlen = 0;
|
|
|
+ FRESULT rv;
|
|
|
+
|
|
|
+ rv = f_lseek(&state->file, file_pos(state));
|
|
|
+ if (rv == FR_OK)
|
|
|
+ rv = f_write(&state->file, buf, 256, &wlen);
|
|
|
+ if (rv != FR_OK || wlen != 256)
|
|
|
+ disk_set_error(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 */
|
|
@@ -499,24 +551,72 @@ static void do_next_command(struct ctl_state *state)
|
|
|
}
|
|
|
state->k[1] &= ~0x08;
|
|
|
}
|
|
|
+#endif /* FORMAT support */
|
|
|
|
|
|
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;
|
|
|
+ disk_start_command(state);
|
|
|
}
|
|
|
|
|
|
-static void disk_reset(uint8_t sel)
|
|
|
+#define SYNC_TIME (2*CPU_HZ)
|
|
|
+
|
|
|
+/* Called from the main loop */
|
|
|
+void abcdisk_io_poll(void)
|
|
|
{
|
|
|
- struct ctl_state *state = sel_to_state[sel];
|
|
|
+ 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;
|
|
|
|
|
|
- if (state && state->state != disk_need_init)
|
|
|
- disk_reset_state(state);
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ for (int i = 0; i < CONTROLLER_TYPES; i++) {
|
|
|
+ struct state *state = &controllers[i];
|
|
|
+
|
|
|
+ /* Set up the bus device */
|
|
|
+ memset(&state->iodev, 0, sizeof state->iodev);
|
|
|
+ state->iodev.callback_mask = IDLE_CALLBACK_MASK;
|
|
|
+ state->iodev.state_first_out_mask = ~0x80;
|
|
|
+ state->iodev.state_first_inp_mask = ~0x80;
|
|
|
+ state->callback_out = abcdisk_callback_out;
|
|
|
+ state->callback_inp = abcdisk_callback_inp;
|
|
|
+ state->callback_cmd = abcdisk_callback_cmd;
|
|
|
+
|
|
|
+ disk_reset_state(state);
|
|
|
+ abcio_register(&state->iodev, state->devsel);
|
|
|
+ }
|
|
|
+}
|