#include #include #include "ibm.h" #include "device.h" #include "dma.h" #include "hdd_image.h" #include "io.h" #include "mem.h" #include "pic.h" #include "rom.h" #include "timer.h" #include "mfm_xebec.h" #define XEBEC_TIME (2000 * TIMER_USEC) enum { STATE_IDLE, STATE_RECEIVE_COMMAND, STATE_START_COMMAND, STATE_RECEIVE_DATA, STATE_RECEIVED_DATA, STATE_SEND_DATA, STATE_SENT_DATA, STATE_COMPLETION_BYTE, STATE_DUNNO }; typedef struct mfm_drive_t { int spt, hpc; int tracks; int cfg_spt; int cfg_hpc; int cfg_cyl; int current_cylinder; int present; int hdc_num; } mfm_drive_t; typedef struct xebec_t { rom_t bios_rom; int callback; int state; uint8_t status; uint8_t command[6]; int command_pos; uint8_t data[512]; int data_pos, data_len; uint8_t sector_buf[512]; uint8_t irq_dma_mask; uint8_t completion_byte; uint8_t error; int drive_sel; mfm_drive_t drives[2]; int sector, head, cylinder; int sector_count; uint8_t switches; } xebec_t; #define STAT_IRQ 0x20 #define STAT_DRQ 0x10 #define STAT_BSY 0x08 #define STAT_CD 0x04 #define STAT_IO 0x02 #define STAT_REQ 0x01 #define IRQ_ENA 0x02 #define DMA_ENA 0x01 #define CMD_TEST_DRIVE_READY 0x00 #define CMD_RECALIBRATE 0x01 #define CMD_READ_STATUS 0x03 #define CMD_VERIFY_SECTORS 0x05 #define CMD_FORMAT_TRACK 0x06 #define CMD_READ_SECTORS 0x08 #define CMD_WRITE_SECTORS 0x0a #define CMD_SEEK 0x0b #define CMD_INIT_DRIVE_PARAMS 0x0c #define CMD_WRITE_SECTOR_BUFFER 0x0f #define CMD_BUFFER_DIAGNOSTIC 0xe0 #define CMD_CONTROLLER_DIAGNOSTIC 0xe4 #define CMD_DTC_GET_DRIVE_PARAMS 0xfb #define CMD_DTC_SET_STEP_RATE 0xfc #define CMD_DTC_SET_GEOMETRY 0xfe #define CMD_DTC_GET_GEOMETRY 0xff #define ERR_NOT_READY 0x04 #define ERR_SEEK_ERROR 0x15 #define ERR_ILLEGAL_SECTOR_ADDRESS 0x21 static uint8_t xebec_read(uint16_t port, void *p) { xebec_t *xebec = (xebec_t *)p; uint8_t temp = 0xff; switch (port) { case 0x320: /*Read data*/ xebec->status &= ~STAT_IRQ; switch (xebec->state) { case STATE_COMPLETION_BYTE: if ((xebec->status & 0xf) != (STAT_CD | STAT_IO | STAT_REQ | STAT_BSY)) fatal("Read data STATE_COMPLETION_BYTE, status=%02x\n", xebec->status); temp = xebec->completion_byte; xebec->status = 0; xebec->state = STATE_IDLE; break; case STATE_SEND_DATA: if ((xebec->status & 0xf) != (STAT_IO | STAT_REQ | STAT_BSY)) fatal("Read data STATE_COMPLETION_BYTE, status=%02x\n", xebec->status); if (xebec->data_pos >= xebec->data_len) fatal("Data write with full data!\n"); temp = xebec->data[xebec->data_pos++]; if (xebec->data_pos == xebec->data_len) { xebec->status = STAT_BSY; xebec->state = STATE_SENT_DATA; xebec->callback = XEBEC_TIME; } break; default: fatal("Read data register - %i, %02x\n", xebec->state, xebec->status); } break; case 0x321: /*Read status*/ temp = xebec->status; break; case 0x322: /*Read option jumpers*/ temp = xebec->switches; break; } return temp; } static void xebec_write(uint16_t port, uint8_t val, void *p) { xebec_t *xebec = (xebec_t *)p; switch (port) { case 0x320: /*Write data*/ switch (xebec->state) { case STATE_RECEIVE_COMMAND: if ((xebec->status & 0xf) != (STAT_BSY | STAT_CD | STAT_REQ)) fatal("Bad write data state - STATE_START_COMMAND, status=%02x\n", xebec->status); if (xebec->command_pos >= 6) fatal("Command write with full command!\n"); /*Command data*/ xebec->command[xebec->command_pos++] = val; if (xebec->command_pos == 6) { xebec->status = STAT_BSY; xebec->state = STATE_START_COMMAND; xebec->callback = XEBEC_TIME; } break; case STATE_RECEIVE_DATA: if ((xebec->status & 0xf) != (STAT_BSY | STAT_REQ)) fatal("Bad write data state - STATE_RECEIVE_DATA, status=%02x\n", xebec->status); if (xebec->data_pos >= xebec->data_len) fatal("Data write with full data!\n"); /*Command data*/ xebec->data[xebec->data_pos++] = val; if (xebec->data_pos == xebec->data_len) { xebec->status = STAT_BSY; xebec->state = STATE_RECEIVED_DATA; xebec->callback = XEBEC_TIME; } break; default: fatal("Write data unknown state - %i %02x\n", xebec->state, xebec->status); } break; case 0x321: /*Controller reset*/ xebec->status = 0; break; case 0x322: /*Generate controller-select-pulse*/ xebec->status = STAT_BSY | STAT_CD | STAT_REQ; xebec->command_pos = 0; xebec->state = STATE_RECEIVE_COMMAND; break; case 0x323: /*DMA/IRQ mask register*/ xebec->irq_dma_mask = val; break; } } static void xebec_complete(xebec_t *xebec) { xebec->status = STAT_REQ | STAT_CD | STAT_IO | STAT_BSY; xebec->state = STATE_COMPLETION_BYTE; if (xebec->irq_dma_mask & IRQ_ENA) { xebec->status |= STAT_IRQ; picint(1 << 5); } } static void xebec_error(xebec_t *xebec, uint8_t error) { xebec->completion_byte |= 0x02; xebec->error = error; pclog("xebec_error - %02x\n", xebec->error); } static int xebec_get_sector(xebec_t *xebec, off64_t *addr) { mfm_drive_t *drive = &xebec->drives[xebec->drive_sel]; int heads = drive->cfg_hpc; if (drive->current_cylinder != xebec->cylinder) { pclog("mfm_get_sector: wrong cylinder\n"); xebec->error = ERR_ILLEGAL_SECTOR_ADDRESS; return 1; } if (xebec->head > heads) { pclog("mfm_get_sector: past end of configured heads\n"); xebec->error = ERR_ILLEGAL_SECTOR_ADDRESS; return 1; } if (xebec->head > drive->hpc) { pclog("mfm_get_sector: past end of heads\n"); xebec->error = ERR_ILLEGAL_SECTOR_ADDRESS; return 1; } if (xebec->sector >= 17) { pclog("mfm_get_sector: past end of sectors\n"); xebec->error = ERR_ILLEGAL_SECTOR_ADDRESS; return 1; } *addr = ((((off64_t) xebec->cylinder * heads) + xebec->head) * 17) + xebec->sector; return 0; } static void xebec_next_sector(xebec_t *xebec) { mfm_drive_t *drive = &xebec->drives[xebec->drive_sel]; xebec->sector++; if (xebec->sector >= 17) { xebec->sector = 0; xebec->head++; if (xebec->head >= drive->cfg_hpc) { xebec->head = 0; xebec->cylinder++; drive->current_cylinder++; if (drive->current_cylinder >= drive->cfg_cyl) drive->current_cylinder = drive->cfg_cyl-1; } } } static void xebec_callback(void *p) { off64_t addr; xebec_t *xebec = (xebec_t *)p; mfm_drive_t *drive; xebec->callback = 0; xebec->drive_sel = (xebec->command[1] & 0x20) ? 1 : 0; xebec->completion_byte = xebec->drive_sel & 0x20; drive = &xebec->drives[xebec->drive_sel]; switch (xebec->command[0]) { case CMD_TEST_DRIVE_READY: if (!drive->present) xebec_error(xebec, ERR_NOT_READY); xebec_complete(xebec); break; case CMD_RECALIBRATE: if (!drive->present) xebec_error(xebec, ERR_NOT_READY); else { xebec->cylinder = 0; drive->current_cylinder = 0; } xebec_complete(xebec); break; case CMD_READ_STATUS: switch (xebec->state) { case STATE_START_COMMAND: xebec->state = STATE_SEND_DATA; xebec->data_pos = 0; xebec->data_len = 4; xebec->status = STAT_BSY | STAT_IO | STAT_REQ; xebec->data[0] = xebec->error; xebec->data[1] = xebec->drive_sel ? 0x20 : 0; xebec->data[2] = xebec->data[3] = 0; xebec->error = 0; break; case STATE_SENT_DATA: xebec_complete(xebec); break; } break; case CMD_VERIFY_SECTORS: switch (xebec->state) { case STATE_START_COMMAND: xebec->cylinder = xebec->command[3] | ((xebec->command[2] & 0xc0) << 2); drive->current_cylinder = (xebec->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : xebec->cylinder; xebec->head = xebec->command[1] & 0x1f; xebec->sector = xebec->command[2] & 0x1f; xebec->sector_count = xebec->command[4]; do { if (xebec_get_sector(xebec, &addr)) { pclog("xebec_get_sector failed\n"); xebec_error(xebec, xebec->error); xebec_complete(xebec); return; } xebec_next_sector(xebec); xebec->sector_count = (xebec->sector_count-1) & 0xff; } while (xebec->sector_count); xebec_complete(xebec); update_status_bar_icon(SB_HDD | HDD_BUS_MFM, 1); break; default: fatal("CMD_VERIFY_SECTORS: bad state %i\n", xebec->state); } break; case CMD_FORMAT_TRACK: { xebec->cylinder = xebec->command[3] | ((xebec->command[2] & 0xc0) << 2); drive->current_cylinder = (xebec->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : xebec->cylinder; xebec->head = xebec->command[1] & 0x1f; if (xebec_get_sector(xebec, &addr)) { pclog("xebec_get_sector failed\n"); xebec_error(xebec, xebec->error); xebec_complete(xebec); return; } hdd_image_zero(drive->hdc_num, addr, 17); xebec_complete(xebec); } break; case CMD_READ_SECTORS: switch (xebec->state) { case STATE_START_COMMAND: xebec->cylinder = xebec->command[3] | ((xebec->command[2] & 0xc0) << 2); drive->current_cylinder = (xebec->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : xebec->cylinder; xebec->head = xebec->command[1] & 0x1f; xebec->sector = xebec->command[2] & 0x1f; xebec->sector_count = xebec->command[4]; xebec->state = STATE_SEND_DATA; xebec->data_pos = 0; xebec->data_len = 512; { if (xebec_get_sector(xebec, &addr)) { xebec_error(xebec, xebec->error); xebec_complete(xebec); return; } hdd_image_read(drive->hdc_num, addr, 1, (uint8_t *) xebec->sector_buf); update_status_bar_icon(SB_HDD | HDD_BUS_MFM, 1); } if (xebec->irq_dma_mask & DMA_ENA) xebec->callback = XEBEC_TIME; else { xebec->status = STAT_BSY | STAT_IO | STAT_REQ; memcpy(xebec->data, xebec->sector_buf, 512); } break; case STATE_SEND_DATA: xebec->status = STAT_BSY; if (xebec->irq_dma_mask & DMA_ENA) { for (; xebec->data_pos < 512; xebec->data_pos++) { int val = dma_channel_write(3, xebec->sector_buf[xebec->data_pos]); if (val == DMA_NODATA) { pclog("CMD_READ_SECTORS out of data!\n"); xebec->status = STAT_BSY | STAT_CD | STAT_IO | STAT_REQ; xebec->callback = XEBEC_TIME; return; } } xebec->state = STATE_SENT_DATA; xebec->callback = XEBEC_TIME; } else fatal("Read sectors no DMA! - shouldn't get here\n"); break; case STATE_SENT_DATA: xebec_next_sector(xebec); xebec->data_pos = 0; xebec->sector_count = (xebec->sector_count-1) & 0xff; if (xebec->sector_count) { if (xebec_get_sector(xebec, &addr)) { xebec_error(xebec, xebec->error); xebec_complete(xebec); return; } hdd_image_read(drive->hdc_num, addr, 1, (uint8_t *) xebec->sector_buf); update_status_bar_icon(SB_HDD | HDD_BUS_MFM, 1); xebec->state = STATE_SEND_DATA; if (xebec->irq_dma_mask & DMA_ENA) xebec->callback = XEBEC_TIME; else { xebec->status = STAT_BSY | STAT_IO | STAT_REQ; memcpy(xebec->data, xebec->sector_buf, 512); } } else { xebec_complete(xebec); update_status_bar_icon(SB_HDD | HDD_BUS_MFM, 0); } break; default: fatal("CMD_READ_SECTORS: bad state %i\n", xebec->state); } break; case CMD_WRITE_SECTORS: switch (xebec->state) { case STATE_START_COMMAND: xebec->cylinder = xebec->command[3] | ((xebec->command[2] & 0xc0) << 2); drive->current_cylinder = (xebec->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : xebec->cylinder; xebec->head = xebec->command[1] & 0x1f; xebec->sector = xebec->command[2] & 0x1f; xebec->sector_count = xebec->command[4]; xebec->state = STATE_RECEIVE_DATA; xebec->data_pos = 0; xebec->data_len = 512; if (xebec->irq_dma_mask & DMA_ENA) xebec->callback = XEBEC_TIME; else xebec->status = STAT_BSY | STAT_REQ; break; case STATE_RECEIVE_DATA: xebec->status = STAT_BSY; if (xebec->irq_dma_mask & DMA_ENA) { for (; xebec->data_pos < 512; xebec->data_pos++) { int val = dma_channel_read(3); if (val == DMA_NODATA) { pclog("CMD_WRITE_SECTORS out of data!\n"); xebec->status = STAT_BSY | STAT_CD | STAT_IO | STAT_REQ; xebec->callback = XEBEC_TIME; return; } xebec->sector_buf[xebec->data_pos] = val & 0xff; } xebec->state = STATE_RECEIVED_DATA; xebec->callback = XEBEC_TIME; } else fatal("Write sectors no DMA! - should never get here\n"); break; case STATE_RECEIVED_DATA: if (!(xebec->irq_dma_mask & DMA_ENA)) memcpy(xebec->sector_buf, xebec->data, 512); { if (xebec_get_sector(xebec, &addr)) { xebec_error(xebec, xebec->error); xebec_complete(xebec); return; } hdd_image_write(drive->hdc_num, addr, 1, (uint8_t *) xebec->sector_buf); } update_status_bar_icon(SB_HDD | HDD_BUS_MFM, 1); xebec_next_sector(xebec); xebec->data_pos = 0; xebec->sector_count = (xebec->sector_count-1) & 0xff; if (xebec->sector_count) { xebec->state = STATE_RECEIVE_DATA; if (xebec->irq_dma_mask & DMA_ENA) xebec->callback = XEBEC_TIME; else xebec->status = STAT_BSY | STAT_REQ; } else xebec_complete(xebec); break; default: fatal("CMD_WRITE_SECTORS: bad state %i\n", xebec->state); } break; case CMD_SEEK: if (!drive->present) xebec_error(xebec, ERR_NOT_READY); else { int cylinder = xebec->command[3] | ((xebec->command[2] & 0xc0) << 2); drive->current_cylinder = (cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : cylinder; if (cylinder != drive->current_cylinder) xebec_error(xebec, ERR_SEEK_ERROR); } xebec_complete(xebec); break; case CMD_INIT_DRIVE_PARAMS: switch (xebec->state) { case STATE_START_COMMAND: xebec->state = STATE_RECEIVE_DATA; xebec->data_pos = 0; xebec->data_len = 8; xebec->status = STAT_BSY | STAT_REQ; break; case STATE_RECEIVED_DATA: drive->cfg_cyl = xebec->data[1] | (xebec->data[0] << 8); drive->cfg_hpc = xebec->data[2]; pclog("Drive %i: cylinders=%i, heads=%i\n", xebec->drive_sel, drive->cfg_cyl, drive->cfg_hpc); xebec_complete(xebec); break; default: fatal("CMD_INIT_DRIVE_PARAMS bad state %i\n", xebec->state); } break; case CMD_WRITE_SECTOR_BUFFER: switch (xebec->state) { case STATE_START_COMMAND: xebec->state = STATE_RECEIVE_DATA; xebec->data_pos = 0; xebec->data_len = 512; if (xebec->irq_dma_mask & DMA_ENA) xebec->callback = XEBEC_TIME; else xebec->status = STAT_BSY | STAT_REQ; break; case STATE_RECEIVE_DATA: if (xebec->irq_dma_mask & DMA_ENA) { xebec->status = STAT_BSY; for (; xebec->data_pos < 512; xebec->data_pos++) { int val = dma_channel_read(3); if (val == DMA_NODATA) { pclog("CMD_WRITE_SECTOR_BUFFER out of data!\n"); xebec->status = STAT_BSY | STAT_CD | STAT_IO | STAT_REQ; xebec->callback = XEBEC_TIME; return; } xebec->data[xebec->data_pos] = val & 0xff; } xebec->state = STATE_RECEIVED_DATA; xebec->callback = XEBEC_TIME; } else fatal("CMD_WRITE_SECTOR_BUFFER - should never get here!\n"); break; case STATE_RECEIVED_DATA: memcpy(xebec->sector_buf, xebec->data, 512); xebec_complete(xebec); break; default: fatal("CMD_WRITE_SECTOR_BUFFER bad state %i\n", xebec->state); } break; case CMD_BUFFER_DIAGNOSTIC: case CMD_CONTROLLER_DIAGNOSTIC: xebec_complete(xebec); break; case 0xfa: xebec_complete(xebec); break; case CMD_DTC_SET_STEP_RATE: xebec_complete(xebec); break; case CMD_DTC_GET_DRIVE_PARAMS: switch (xebec->state) { case STATE_START_COMMAND: xebec->state = STATE_SEND_DATA; xebec->data_pos = 0; xebec->data_len = 4; xebec->status = STAT_BSY | STAT_IO | STAT_REQ; memset(xebec->data, 0, 4); xebec->data[0] = drive->tracks & 0xff; xebec->data[1] = 17 | ((drive->tracks >> 2) & 0xc0); xebec->data[2] = drive->hpc-1; pclog("Get drive params %02x %02x %02x %i\n", xebec->data[0], xebec->data[1], xebec->data[2], drive->tracks); break; case STATE_SENT_DATA: xebec_complete(xebec); break; default: fatal("CMD_INIT_DRIVE_PARAMS bad state %i\n", xebec->state); } break; case CMD_DTC_GET_GEOMETRY: switch (xebec->state) { case STATE_START_COMMAND: xebec->state = STATE_SEND_DATA; xebec->data_pos = 0; xebec->data_len = 16; xebec->status = STAT_BSY | STAT_IO | STAT_REQ; memset(xebec->data, 0, 16); xebec->data[0x4] = drive->tracks & 0xff; xebec->data[0x5] = (drive->tracks >> 8) & 0xff; xebec->data[0xa] = drive->hpc; break; case STATE_SENT_DATA: xebec_complete(xebec); break; } break; case CMD_DTC_SET_GEOMETRY: switch (xebec->state) { case STATE_START_COMMAND: xebec->state = STATE_RECEIVE_DATA; xebec->data_pos = 0; xebec->data_len = 16; xebec->status = STAT_BSY | STAT_REQ; break; case STATE_RECEIVED_DATA: /*Bit of a cheat here - we always report the actual geometry of the drive in use*/ xebec_complete(xebec); break; } break; default: fatal("Unknown Xebec command - %02x %02x %02x %02x %02x %02x\n", xebec->command[0], xebec->command[1], xebec->command[2], xebec->command[3], xebec->command[4], xebec->command[5]); } } static void loadhd(xebec_t *xebec, int c, int d, const wchar_t *fn) { mfm_drive_t *drive = &xebec->drives[d]; int ret = 0; ret = hdd_image_load(d); if (!ret) { drive->present = 0; return; } drive->spt = hdc[c].spt; drive->hpc = hdc[c].hpc; drive->tracks = hdc[c].tracks; drive->hdc_num = c; drive->present = 1; } static struct { int tracks, hpc; } xebec_hd_types[4] = { {306, 4}, /*Type 0*/ {612, 4}, /*Type 16*/ {615, 4}, /*Type 2*/ {306, 8} /*Type 13*/ }; static void xebec_set_switches(xebec_t *xebec) { int c, d; xebec->switches = 0; for (d = 0; d < 2; d++) { mfm_drive_t *drive = &xebec->drives[d]; if (!drive->present) continue; for (c = 0; c < 4; c++) { if (drive->spt == 17 && drive->hpc == xebec_hd_types[c].hpc && drive->tracks == xebec_hd_types[c].tracks) { xebec->switches |= (c << (d ? 0 : 2)); break; } } if (c == 4) pclog("WARNING: Drive %c: has format not supported by Fixed Disk Adapter", d ? 'D' : 'C'); } } static void *xebec_init() { int i = 0; int c = 0; xebec_t *xebec = malloc(sizeof(xebec_t)); memset(xebec, 0, sizeof(xebec_t)); for (i = 0; i < HDC_NUM; i++) { if ((hdc[i].bus == HDD_BUS_MFM) && (hdc[i].mfm_channel < MFM_NUM)) { loadhd(xebec, i, hdc[i].mfm_channel, hdc[i].fn); c++; if (c > MFM_NUM) break; } } xebec_set_switches(xebec); rom_init(&xebec->bios_rom, L"roms/ibm_xebec_62x0822_1985.bin", 0xc8000, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL); io_sethandler(0x0320, 0x0004, xebec_read, NULL, NULL, xebec_write, NULL, NULL, xebec); timer_add(xebec_callback, &xebec->callback, &xebec->callback, xebec); return xebec; } static void xebec_close(void *p) { xebec_t *xebec = (xebec_t *)p; int d; for (d = 0; d < 2; d++) { mfm_drive_t *drive = &xebec->drives[d]; hdd_image_close(drive->hdc_num); } free(xebec); } static int xebec_available() { return rom_present(L"roms/ibm_xebec_62x0822_1985.bin"); } device_t mfm_xebec_device = { "IBM PC Fixed Disk Adapter", 0, xebec_init, xebec_close, xebec_available, NULL, NULL, NULL, NULL }; static void *dtc_5150x_init() { int i = 0; int c = 0; xebec_t *xebec = malloc(sizeof(xebec_t)); memset(xebec, 0, sizeof(xebec_t)); for (i = 0; i < HDC_NUM; i++) { if ((hdc[i].bus == HDD_BUS_MFM) && (hdc[i].mfm_channel < MFM_NUM)) { loadhd(xebec, i, hdc[i].mfm_channel, hdc[i].fn); c++; if (c > MFM_NUM) break; } } xebec->switches = 0xff; xebec->drives[0].cfg_cyl = xebec->drives[0].tracks; xebec->drives[0].cfg_hpc = xebec->drives[0].hpc; xebec->drives[1].cfg_cyl = xebec->drives[1].tracks; xebec->drives[1].cfg_hpc = xebec->drives[1].hpc; rom_init(&xebec->bios_rom, L"roms/dtc_cxd21a.bin", 0xc8000, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL); io_sethandler(0x0320, 0x0004, xebec_read, NULL, NULL, xebec_write, NULL, NULL, xebec); timer_add(xebec_callback, &xebec->callback, &xebec->callback, xebec); return xebec; } static int dtc_5150x_available() { return rom_present(L"roms/dtc_cxd21a.bin"); } device_t dtc_5150x_device = { "DTC 5150X", 0, dtc_5150x_init, xebec_close, dtc_5150x_available, NULL, NULL, NULL, NULL };