922 lines
22 KiB
C
922 lines
22 KiB
C
/*
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
* running old operating systems and software designed for IBM
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
* system designs based on the PCI bus.
|
|
*
|
|
* This file is part of the 86Box distribution.
|
|
*
|
|
* Driver for the IBM PC-XT Fixed Disk controller.
|
|
*
|
|
* The original controller shipped by IBM was made by Xebec, and
|
|
* several variations had been made:
|
|
*
|
|
* #1 Original, single drive (ST412), 10MB, 2 heads.
|
|
* #2 Update, single drive (ST412) but with option for a
|
|
* switch block that can be used to 'set' the actual
|
|
* drive type. Four switches are define, where switches
|
|
* 1 and 2 define drive0, and switches 3 and 4 drive1.
|
|
*
|
|
* 0 ON ON 306 2 0
|
|
* 1 ON OFF 375 8 0
|
|
* 2 OFF ON 306 6 256
|
|
* 3 OFF OFF 306 4 0
|
|
*
|
|
* The latter option is the default, in use on boards
|
|
* without the switch block option.
|
|
*
|
|
* #3 Another updated board, mostly to accomodate the new
|
|
* 20MB disk now being shipped. The controller can have
|
|
* up to 2 drives, the type of which is set using the
|
|
* switch block:
|
|
*
|
|
* SW1 SW2 CYLS HD SPT WPC
|
|
* 0 ON ON 306 4 17 0
|
|
* 1 ON OFF 612 4 17 0 (type 16)
|
|
* 2 OFF ON 615 4 17 300 (Seagate ST-225, 2)
|
|
* 3 OFF OFF 306 8 17 128 (IBM WD25, 13)
|
|
*
|
|
* Examples of #3 are IBM/Xebec, WD10004A-WX1 and ST11R.
|
|
*
|
|
* Since all controllers (including the ones made by DTC) use
|
|
* (mostly) the same API, we keep them all in this module.
|
|
*
|
|
* Version: @(#)hdd_mfm_xt.c 1.0.9 2017/10/11
|
|
*
|
|
* Authors: Sarah Walker, <http://pcem-emulator.co.uk/>
|
|
* Fred N. van Kempen, <decwiz@yahoo.com>
|
|
*
|
|
* Copyright 2008-2017 Sarah Walker.
|
|
* Copyright 2017 Fred N. van Kempen.
|
|
*/
|
|
#define __USE_LARGEFILE64
|
|
#define _LARGEFILE_SOURCE
|
|
#define _LARGEFILE64_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <wchar.h>
|
|
#include "../ibm.h"
|
|
#include "../device.h"
|
|
#include "../dma.h"
|
|
#include "../io.h"
|
|
#include "../mem.h"
|
|
#include "../pic.h"
|
|
#include "../rom.h"
|
|
#include "../timer.h"
|
|
#include "../ui.h"
|
|
#include "hdc.h"
|
|
#include "hdd.h"
|
|
|
|
|
|
#define MFM_TIME (2000LL*TIMER_USEC)
|
|
#define XEBEC_BIOS_FILE L"roms/hdd/mfm_xebec/ibm_xebec_62x0822_1985.bin"
|
|
#define DTC_BIOS_FILE L"roms/hdd/mfm_xebec/dtc_cxd21a.bin"
|
|
|
|
|
|
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 {
|
|
int spt, hpc;
|
|
int tracks;
|
|
int cfg_spt;
|
|
int cfg_hpc;
|
|
int cfg_cyl;
|
|
int current_cylinder;
|
|
int present;
|
|
int hdd_num;
|
|
} drive_t;
|
|
|
|
typedef struct {
|
|
rom_t bios_rom;
|
|
int64_t 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;
|
|
drive_t drives[2];
|
|
int sector, head, cylinder;
|
|
int sector_count;
|
|
uint8_t switches;
|
|
} mfm_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
|
|
mfm_read(uint16_t port, void *priv)
|
|
{
|
|
mfm_t *mfm = (mfm_t *)priv;
|
|
uint8_t temp = 0xff;
|
|
|
|
switch (port) {
|
|
case 0x320: /*Read data*/
|
|
mfm->status &= ~STAT_IRQ;
|
|
switch (mfm->state) {
|
|
case STATE_COMPLETION_BYTE:
|
|
if ((mfm->status & 0xf) != (STAT_CD | STAT_IO | STAT_REQ | STAT_BSY))
|
|
fatal("Read data STATE_COMPLETION_BYTE, status=%02x\n", mfm->status);
|
|
|
|
temp = mfm->completion_byte;
|
|
mfm->status = 0;
|
|
mfm->state = STATE_IDLE;
|
|
break;
|
|
|
|
case STATE_SEND_DATA:
|
|
if ((mfm->status & 0xf) != (STAT_IO | STAT_REQ | STAT_BSY))
|
|
fatal("Read data STATE_COMPLETION_BYTE, status=%02x\n", mfm->status);
|
|
if (mfm->data_pos >= mfm->data_len)
|
|
fatal("Data write with full data!\n");
|
|
temp = mfm->data[mfm->data_pos++];
|
|
if (mfm->data_pos == mfm->data_len) {
|
|
mfm->status = STAT_BSY;
|
|
mfm->state = STATE_SENT_DATA;
|
|
mfm->callback = MFM_TIME;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fatal("Read data register - %i, %02x\n", mfm->state, mfm->status);
|
|
}
|
|
break;
|
|
|
|
case 0x321: /*Read status*/
|
|
temp = mfm->status;
|
|
break;
|
|
|
|
case 0x322: /*Read option jumpers*/
|
|
temp = mfm->switches;
|
|
break;
|
|
}
|
|
|
|
return(temp);
|
|
}
|
|
|
|
|
|
static void
|
|
mfm_write(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
mfm_t *mfm = (mfm_t *)priv;
|
|
|
|
switch (port) {
|
|
case 0x320: /*Write data*/
|
|
switch (mfm->state) {
|
|
case STATE_RECEIVE_COMMAND:
|
|
if ((mfm->status & 0xf) != (STAT_BSY | STAT_CD | STAT_REQ))
|
|
fatal("Bad write data state - STATE_START_COMMAND, status=%02x\n", mfm->status);
|
|
if (mfm->command_pos >= 6)
|
|
fatal("Command write with full command!\n");
|
|
/*Command data*/
|
|
mfm->command[mfm->command_pos++] = val;
|
|
if (mfm->command_pos == 6) {
|
|
mfm->status = STAT_BSY;
|
|
mfm->state = STATE_START_COMMAND;
|
|
mfm->callback = MFM_TIME;
|
|
}
|
|
break;
|
|
|
|
case STATE_RECEIVE_DATA:
|
|
if ((mfm->status & 0xf) != (STAT_BSY | STAT_REQ))
|
|
fatal("Bad write data state - STATE_RECEIVE_DATA, status=%02x\n", mfm->status);
|
|
if (mfm->data_pos >= mfm->data_len)
|
|
fatal("Data write with full data!\n");
|
|
/*Command data*/
|
|
mfm->data[mfm->data_pos++] = val;
|
|
if (mfm->data_pos == mfm->data_len) {
|
|
mfm->status = STAT_BSY;
|
|
mfm->state = STATE_RECEIVED_DATA;
|
|
mfm->callback = MFM_TIME;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fatal("Write data unknown state - %i %02x\n", mfm->state, mfm->status);
|
|
}
|
|
break;
|
|
|
|
case 0x321: /*Controller reset*/
|
|
mfm->status = 0;
|
|
break;
|
|
|
|
case 0x322: /*Generate controller-select-pulse*/
|
|
mfm->status = STAT_BSY | STAT_CD | STAT_REQ;
|
|
mfm->command_pos = 0;
|
|
mfm->state = STATE_RECEIVE_COMMAND;
|
|
break;
|
|
|
|
case 0x323: /*DMA/IRQ mask register*/
|
|
mfm->irq_dma_mask = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void mfm_complete(mfm_t *mfm)
|
|
{
|
|
mfm->status = STAT_REQ | STAT_CD | STAT_IO | STAT_BSY;
|
|
mfm->state = STATE_COMPLETION_BYTE;
|
|
|
|
if (mfm->irq_dma_mask & IRQ_ENA) {
|
|
mfm->status |= STAT_IRQ;
|
|
picint(1 << 5);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
mfm_error(mfm_t *mfm, uint8_t error)
|
|
{
|
|
mfm->completion_byte |= 0x02;
|
|
mfm->error = error;
|
|
|
|
pclog("mfm_error - %02x\n", mfm->error);
|
|
}
|
|
|
|
|
|
static int
|
|
get_sector(mfm_t *mfm, off64_t *addr)
|
|
{
|
|
drive_t *drive = &mfm->drives[mfm->drive_sel];
|
|
int heads = drive->cfg_hpc;
|
|
|
|
if (drive->current_cylinder != mfm->cylinder) {
|
|
pclog("mfm_get_sector: wrong cylinder\n");
|
|
mfm->error = ERR_ILLEGAL_SECTOR_ADDRESS;
|
|
return(1);
|
|
}
|
|
if (mfm->head > heads) {
|
|
pclog("mfm_get_sector: past end of configured heads\n");
|
|
mfm->error = ERR_ILLEGAL_SECTOR_ADDRESS;
|
|
return(1);
|
|
}
|
|
if (mfm->head > drive->hpc) {
|
|
pclog("mfm_get_sector: past end of heads\n");
|
|
mfm->error = ERR_ILLEGAL_SECTOR_ADDRESS;
|
|
return(1);
|
|
}
|
|
if (mfm->sector >= 17) {
|
|
pclog("mfm_get_sector: past end of sectors\n");
|
|
mfm->error = ERR_ILLEGAL_SECTOR_ADDRESS;
|
|
return(1);
|
|
}
|
|
|
|
*addr = ((((off64_t) mfm->cylinder * heads) + mfm->head) *
|
|
17) + mfm->sector;
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
static void
|
|
next_sector(mfm_t *mfm)
|
|
{
|
|
drive_t *drive = &mfm->drives[mfm->drive_sel];
|
|
|
|
mfm->sector++;
|
|
if (mfm->sector >= 17) {
|
|
mfm->sector = 0;
|
|
mfm->head++;
|
|
if (mfm->head >= drive->cfg_hpc) {
|
|
mfm->head = 0;
|
|
mfm->cylinder++;
|
|
drive->current_cylinder++;
|
|
if (drive->current_cylinder >= drive->cfg_cyl)
|
|
drive->current_cylinder = drive->cfg_cyl-1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
mfm_callback(void *priv)
|
|
{
|
|
mfm_t *mfm = (mfm_t *)priv;
|
|
drive_t *drive;
|
|
off64_t addr;
|
|
|
|
mfm->callback = 0LL;
|
|
|
|
mfm->drive_sel = (mfm->command[1] & 0x20) ? 1 : 0;
|
|
mfm->completion_byte = mfm->drive_sel & 0x20;
|
|
drive = &mfm->drives[mfm->drive_sel];
|
|
|
|
switch (mfm->command[0]) {
|
|
case CMD_TEST_DRIVE_READY:
|
|
if (!drive->present)
|
|
mfm_error(mfm, ERR_NOT_READY);
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
case CMD_RECALIBRATE:
|
|
if (!drive->present)
|
|
mfm_error(mfm, ERR_NOT_READY);
|
|
else {
|
|
mfm->cylinder = 0;
|
|
drive->current_cylinder = 0;
|
|
}
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
case CMD_READ_STATUS:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->state = STATE_SEND_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 4;
|
|
mfm->status = STAT_BSY | STAT_IO | STAT_REQ;
|
|
mfm->data[0] = mfm->error;
|
|
mfm->data[1] = mfm->drive_sel ? 0x20 : 0;
|
|
mfm->data[2] = mfm->data[3] = 0;
|
|
mfm->error = 0;
|
|
break;
|
|
|
|
case STATE_SENT_DATA:
|
|
mfm_complete(mfm);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CMD_VERIFY_SECTORS:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->cylinder = mfm->command[3] | ((mfm->command[2] & 0xc0) << 2);
|
|
drive->current_cylinder = (mfm->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : mfm->cylinder;
|
|
mfm->head = mfm->command[1] & 0x1f;
|
|
mfm->sector = mfm->command[2] & 0x1f;
|
|
mfm->sector_count = mfm->command[4];
|
|
do {
|
|
if (get_sector(mfm, &addr)) {
|
|
pclog("get_sector failed\n");
|
|
mfm_error(mfm, mfm->error);
|
|
mfm_complete(mfm);
|
|
return;
|
|
}
|
|
|
|
next_sector(mfm);
|
|
|
|
mfm->sector_count = (mfm->sector_count-1) & 0xff;
|
|
} while (mfm->sector_count);
|
|
|
|
mfm_complete(mfm);
|
|
|
|
ui_sb_update_icon(SB_HDD | HDD_BUS_MFM, 1);
|
|
break;
|
|
|
|
default:
|
|
fatal("CMD_VERIFY_SECTORS: bad state %i\n", mfm->state);
|
|
}
|
|
break;
|
|
|
|
case CMD_FORMAT_TRACK:
|
|
mfm->cylinder = mfm->command[3] | ((mfm->command[2] & 0xc0) << 2);
|
|
drive->current_cylinder = (mfm->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : mfm->cylinder;
|
|
mfm->head = mfm->command[1] & 0x1f;
|
|
|
|
if (get_sector(mfm, &addr)) {
|
|
pclog("get_sector failed\n");
|
|
mfm_error(mfm, mfm->error);
|
|
mfm_complete(mfm);
|
|
return;
|
|
}
|
|
|
|
hdd_image_zero(drive->hdd_num, addr, 17);
|
|
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
case CMD_READ_SECTORS:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->cylinder = mfm->command[3] | ((mfm->command[2] & 0xc0) << 2);
|
|
drive->current_cylinder = (mfm->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : mfm->cylinder;
|
|
mfm->head = mfm->command[1] & 0x1f;
|
|
mfm->sector = mfm->command[2] & 0x1f;
|
|
mfm->sector_count = mfm->command[4];
|
|
mfm->state = STATE_SEND_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 512;
|
|
|
|
if (get_sector(mfm, &addr)) {
|
|
mfm_error(mfm, mfm->error);
|
|
mfm_complete(mfm);
|
|
return;
|
|
}
|
|
|
|
hdd_image_read(drive->hdd_num, addr, 1,
|
|
(uint8_t *) mfm->sector_buf);
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_MFM, 1);
|
|
|
|
if (mfm->irq_dma_mask & DMA_ENA)
|
|
mfm->callback = MFM_TIME;
|
|
else {
|
|
mfm->status = STAT_BSY | STAT_IO | STAT_REQ;
|
|
memcpy(mfm->data, mfm->sector_buf, 512);
|
|
}
|
|
break;
|
|
|
|
case STATE_SEND_DATA:
|
|
mfm->status = STAT_BSY;
|
|
if (mfm->irq_dma_mask & DMA_ENA) {
|
|
for (; mfm->data_pos < 512; mfm->data_pos++) {
|
|
int val = dma_channel_write(3, mfm->sector_buf[mfm->data_pos]);
|
|
|
|
if (val == DMA_NODATA) {
|
|
pclog("CMD_READ_SECTORS out of data!\n");
|
|
mfm->status = STAT_BSY | STAT_CD | STAT_IO | STAT_REQ;
|
|
mfm->callback = MFM_TIME;
|
|
return;
|
|
}
|
|
}
|
|
mfm->state = STATE_SENT_DATA;
|
|
mfm->callback = MFM_TIME;
|
|
} else
|
|
fatal("Read sectors no DMA! - shouldn't get here\n");
|
|
break;
|
|
|
|
case STATE_SENT_DATA:
|
|
next_sector(mfm);
|
|
|
|
mfm->data_pos = 0;
|
|
|
|
mfm->sector_count = (mfm->sector_count-1) & 0xff;
|
|
|
|
if (mfm->sector_count) {
|
|
if (get_sector(mfm, &addr)) {
|
|
mfm_error(mfm, mfm->error);
|
|
mfm_complete(mfm);
|
|
return;
|
|
}
|
|
|
|
hdd_image_read(drive->hdd_num, addr, 1,
|
|
(uint8_t *) mfm->sector_buf);
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_MFM, 1);
|
|
|
|
mfm->state = STATE_SEND_DATA;
|
|
|
|
if (mfm->irq_dma_mask & DMA_ENA)
|
|
mfm->callback = MFM_TIME;
|
|
else {
|
|
mfm->status = STAT_BSY | STAT_IO | STAT_REQ;
|
|
memcpy(mfm->data, mfm->sector_buf, 512);
|
|
}
|
|
} else {
|
|
mfm_complete(mfm);
|
|
ui_sb_update_icon(SB_HDD | HDD_BUS_MFM, 0);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fatal("CMD_READ_SECTORS: bad state %i\n", mfm->state);
|
|
}
|
|
break;
|
|
|
|
case CMD_WRITE_SECTORS:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->cylinder = mfm->command[3] | ((mfm->command[2] & 0xc0) << 2);
|
|
drive->current_cylinder = (mfm->cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : mfm->cylinder;
|
|
mfm->head = mfm->command[1] & 0x1f;
|
|
mfm->sector = mfm->command[2] & 0x1f;
|
|
mfm->sector_count = mfm->command[4];
|
|
mfm->state = STATE_RECEIVE_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 512;
|
|
if (mfm->irq_dma_mask & DMA_ENA)
|
|
mfm->callback = MFM_TIME;
|
|
else
|
|
mfm->status = STAT_BSY | STAT_REQ;
|
|
break;
|
|
|
|
case STATE_RECEIVE_DATA:
|
|
mfm->status = STAT_BSY;
|
|
if (mfm->irq_dma_mask & DMA_ENA) {
|
|
for (; mfm->data_pos < 512; mfm->data_pos++) {
|
|
int val = dma_channel_read(3);
|
|
|
|
if (val == DMA_NODATA) {
|
|
pclog("CMD_WRITE_SECTORS out of data!\n");
|
|
mfm->status = STAT_BSY | STAT_CD | STAT_IO | STAT_REQ;
|
|
mfm->callback = MFM_TIME;
|
|
return;
|
|
}
|
|
|
|
mfm->sector_buf[mfm->data_pos] = val & 0xff;
|
|
}
|
|
|
|
mfm->state = STATE_RECEIVED_DATA;
|
|
mfm->callback = MFM_TIME;
|
|
} else
|
|
fatal("Write sectors no DMA! - should never get here\n");
|
|
break;
|
|
|
|
case STATE_RECEIVED_DATA:
|
|
if (! (mfm->irq_dma_mask & DMA_ENA))
|
|
memcpy(mfm->sector_buf, mfm->data, 512);
|
|
|
|
if (get_sector(mfm, &addr))
|
|
{
|
|
mfm_error(mfm, mfm->error);
|
|
mfm_complete(mfm);
|
|
return;
|
|
}
|
|
|
|
hdd_image_write(drive->hdd_num, addr, 1,
|
|
(uint8_t *) mfm->sector_buf);
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_MFM, 1);
|
|
|
|
next_sector(mfm);
|
|
mfm->data_pos = 0;
|
|
mfm->sector_count = (mfm->sector_count-1) & 0xff;
|
|
|
|
if (mfm->sector_count) {
|
|
mfm->state = STATE_RECEIVE_DATA;
|
|
if (mfm->irq_dma_mask & DMA_ENA)
|
|
mfm->callback = MFM_TIME;
|
|
else
|
|
mfm->status = STAT_BSY | STAT_REQ;
|
|
} else
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
default:
|
|
fatal("CMD_WRITE_SECTORS: bad state %i\n", mfm->state);
|
|
}
|
|
break;
|
|
|
|
case CMD_SEEK:
|
|
if (! drive->present)
|
|
mfm_error(mfm, ERR_NOT_READY);
|
|
else {
|
|
int cylinder = mfm->command[3] | ((mfm->command[2] & 0xc0) << 2);
|
|
|
|
drive->current_cylinder = (cylinder >= drive->cfg_cyl) ? drive->cfg_cyl-1 : cylinder;
|
|
|
|
if (cylinder != drive->current_cylinder)
|
|
mfm_error(mfm, ERR_SEEK_ERROR);
|
|
}
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
case CMD_INIT_DRIVE_PARAMS:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->state = STATE_RECEIVE_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 8;
|
|
mfm->status = STAT_BSY | STAT_REQ;
|
|
break;
|
|
|
|
case STATE_RECEIVED_DATA:
|
|
drive->cfg_cyl = mfm->data[1] | (mfm->data[0] << 8);
|
|
drive->cfg_hpc = mfm->data[2];
|
|
pclog("Drive %i: cylinders=%i, heads=%i\n", mfm->drive_sel, drive->cfg_cyl, drive->cfg_hpc);
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
default:
|
|
fatal("CMD_INIT_DRIVE_PARAMS bad state %i\n", mfm->state);
|
|
}
|
|
break;
|
|
|
|
case CMD_WRITE_SECTOR_BUFFER:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->state = STATE_RECEIVE_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 512;
|
|
if (mfm->irq_dma_mask & DMA_ENA)
|
|
mfm->callback = MFM_TIME;
|
|
else
|
|
mfm->status = STAT_BSY | STAT_REQ;
|
|
break;
|
|
|
|
case STATE_RECEIVE_DATA:
|
|
if (mfm->irq_dma_mask & DMA_ENA) {
|
|
mfm->status = STAT_BSY;
|
|
|
|
for (; mfm->data_pos < 512; mfm->data_pos++) {
|
|
int val = dma_channel_read(3);
|
|
|
|
if (val == DMA_NODATA) {
|
|
pclog("CMD_WRITE_SECTOR_BUFFER out of data!\n");
|
|
mfm->status = STAT_BSY | STAT_CD | STAT_IO | STAT_REQ;
|
|
mfm->callback = MFM_TIME;
|
|
return;
|
|
}
|
|
|
|
mfm->data[mfm->data_pos] = val & 0xff;
|
|
}
|
|
|
|
mfm->state = STATE_RECEIVED_DATA;
|
|
mfm->callback = MFM_TIME;
|
|
} else
|
|
fatal("CMD_WRITE_SECTOR_BUFFER - should never get here!\n");
|
|
break;
|
|
|
|
case STATE_RECEIVED_DATA:
|
|
memcpy(mfm->sector_buf, mfm->data, 512);
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
default:
|
|
fatal("CMD_WRITE_SECTOR_BUFFER bad state %i\n", mfm->state);
|
|
}
|
|
break;
|
|
|
|
case CMD_BUFFER_DIAGNOSTIC:
|
|
case CMD_CONTROLLER_DIAGNOSTIC:
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
case 0xfa:
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
case CMD_DTC_SET_STEP_RATE:
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
case CMD_DTC_GET_DRIVE_PARAMS:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->state = STATE_SEND_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 4;
|
|
mfm->status = STAT_BSY | STAT_IO | STAT_REQ;
|
|
memset(mfm->data, 0, 4);
|
|
mfm->data[0] = drive->tracks & 0xff;
|
|
mfm->data[1] = 17 | ((drive->tracks >> 2) & 0xc0);
|
|
mfm->data[2] = drive->hpc-1;
|
|
pclog("Get drive params %02x %02x %02x %i\n", mfm->data[0], mfm->data[1], mfm->data[2], drive->tracks);
|
|
break;
|
|
|
|
case STATE_SENT_DATA:
|
|
mfm_complete(mfm);
|
|
break;
|
|
|
|
default:
|
|
fatal("CMD_INIT_DRIVE_PARAMS bad state %i\n", mfm->state);
|
|
}
|
|
break;
|
|
|
|
case CMD_DTC_GET_GEOMETRY:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->state = STATE_SEND_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 16;
|
|
mfm->status = STAT_BSY | STAT_IO | STAT_REQ;
|
|
memset(mfm->data, 0, 16);
|
|
mfm->data[0x4] = drive->tracks & 0xff;
|
|
mfm->data[0x5] = (drive->tracks >> 8) & 0xff;
|
|
mfm->data[0xa] = drive->hpc;
|
|
break;
|
|
|
|
case STATE_SENT_DATA:
|
|
mfm_complete(mfm);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CMD_DTC_SET_GEOMETRY:
|
|
switch (mfm->state) {
|
|
case STATE_START_COMMAND:
|
|
mfm->state = STATE_RECEIVE_DATA;
|
|
mfm->data_pos = 0;
|
|
mfm->data_len = 16;
|
|
mfm->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*/
|
|
mfm_complete(mfm);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
fatal("Unknown Xebec command - %02x %02x %02x %02x %02x %02x\n",
|
|
mfm->command[0], mfm->command[1],
|
|
mfm->command[2], mfm->command[3],
|
|
mfm->command[4], mfm->command[5]);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
loadhd(mfm_t *mfm, int c, int d, const wchar_t *fn)
|
|
{
|
|
drive_t *drive = &mfm->drives[d];
|
|
|
|
if (! hdd_image_load(d)) {
|
|
drive->present = 0;
|
|
return;
|
|
}
|
|
|
|
drive->spt = hdd[c].spt;
|
|
drive->hpc = hdd[c].hpc;
|
|
drive->tracks = hdd[c].tracks;
|
|
drive->hdd_num = c;
|
|
drive->present = 1;
|
|
}
|
|
|
|
|
|
static struct {
|
|
int tracks, hpc;
|
|
} hd_types[4] = {
|
|
{ 306, 4 }, /* Type 0 */
|
|
{ 612, 4 }, /* Type 16 */
|
|
{ 615, 4 }, /* Type 2 */
|
|
{ 306, 8 } /* Type 13 */
|
|
};
|
|
|
|
|
|
static void
|
|
mfm_set_switches(mfm_t *mfm)
|
|
{
|
|
int c, d;
|
|
|
|
mfm->switches = 0;
|
|
|
|
for (d=0; d<2; d++) {
|
|
drive_t *drive = &mfm->drives[d];
|
|
|
|
if (! drive->present) continue;
|
|
|
|
for (c=0; c<4; c++) {
|
|
if (drive->spt == 17 &&
|
|
drive->hpc == hd_types[c].hpc &&
|
|
drive->tracks == hd_types[c].tracks) {
|
|
mfm->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(device_t *info)
|
|
{
|
|
int i, c = 0;
|
|
|
|
mfm_t *xebec = malloc(sizeof(mfm_t));
|
|
memset(xebec, 0x00, sizeof(mfm_t));
|
|
|
|
for (i=0; i<HDD_NUM; i++) {
|
|
if ((hdd[i].bus == HDD_BUS_MFM) && (hdd[i].mfm_channel < MFM_NUM)) {
|
|
loadhd(xebec, i, hdd[i].mfm_channel, hdd[i].fn);
|
|
|
|
if (++c > MFM_NUM) break;
|
|
}
|
|
}
|
|
|
|
mfm_set_switches(xebec);
|
|
|
|
rom_init(&xebec->bios_rom, XEBEC_BIOS_FILE,
|
|
0xc8000, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);
|
|
|
|
io_sethandler(0x0320, 4,
|
|
mfm_read, NULL, NULL, mfm_write, NULL, NULL, xebec);
|
|
|
|
timer_add(mfm_callback, &xebec->callback, &xebec->callback, xebec);
|
|
|
|
return(xebec);
|
|
}
|
|
|
|
|
|
static void
|
|
mfm_close(void *priv)
|
|
{
|
|
mfm_t *mfm = (mfm_t *)priv;
|
|
int d;
|
|
|
|
for (d=0; d<2; d++) {
|
|
drive_t *drive = &mfm->drives[d];
|
|
|
|
hdd_image_close(drive->hdd_num);
|
|
}
|
|
|
|
free(mfm);
|
|
}
|
|
|
|
|
|
static int
|
|
xebec_available(void)
|
|
{
|
|
return(rom_present(XEBEC_BIOS_FILE));
|
|
}
|
|
|
|
|
|
device_t mfm_xt_xebec_device = {
|
|
"IBM PC Fixed Disk Adapter",
|
|
DEVICE_ISA,
|
|
0,
|
|
xebec_init, mfm_close, NULL,
|
|
xebec_available, NULL, NULL, NULL,
|
|
NULL
|
|
};
|
|
|
|
|
|
static void *
|
|
dtc5150x_init(device_t *info)
|
|
{
|
|
int i, c = 0;
|
|
|
|
mfm_t *dtc = malloc(sizeof(mfm_t));
|
|
memset(dtc, 0x00, sizeof(mfm_t));
|
|
|
|
for (i=0; i<HDD_NUM; i++) {
|
|
if ((hdd[i].bus == HDD_BUS_MFM) && (hdd[i].mfm_channel < MFM_NUM)) {
|
|
loadhd(dtc, i, hdd[i].mfm_channel, hdd[i].fn);
|
|
|
|
if (++c > MFM_NUM) break;
|
|
}
|
|
}
|
|
|
|
dtc->switches = 0xff;
|
|
|
|
dtc->drives[0].cfg_cyl = dtc->drives[0].tracks;
|
|
dtc->drives[0].cfg_hpc = dtc->drives[0].hpc;
|
|
dtc->drives[1].cfg_cyl = dtc->drives[1].tracks;
|
|
dtc->drives[1].cfg_hpc = dtc->drives[1].hpc;
|
|
|
|
rom_init(&dtc->bios_rom, DTC_BIOS_FILE,
|
|
0xc8000, 0x4000, 0x3fff, 0, MEM_MAPPING_EXTERNAL);
|
|
|
|
io_sethandler(0x0320, 4,
|
|
mfm_read, NULL, NULL, mfm_write, NULL, NULL, dtc);
|
|
|
|
timer_add(mfm_callback, &dtc->callback, &dtc->callback, dtc);
|
|
|
|
return(dtc);
|
|
}
|
|
|
|
|
|
static int
|
|
dtc5150x_available(void)
|
|
{
|
|
return(rom_present(DTC_BIOS_FILE));
|
|
}
|
|
|
|
|
|
device_t mfm_xt_dtc5150x_device = {
|
|
"DTC 5150X",
|
|
DEVICE_ISA,
|
|
0,
|
|
dtc5150x_init, mfm_close, NULL,
|
|
dtc5150x_available, NULL, NULL, NULL,
|
|
NULL
|
|
};
|