1160 lines
28 KiB
C
1160 lines
28 KiB
C
/*
|
|
* VARCem Virtual ARchaeological Computer EMulator.
|
|
* An emulator of (mostly) x86-based PC systems and devices,
|
|
* using the ISA,EISA,VLB,MCA and PCI system buses, roughly
|
|
* spanning the era between 1981 and 1995.
|
|
*
|
|
* This file is part of the VARCem Project.
|
|
*
|
|
* Implementation of a generic IDE-XTA disk controller.
|
|
*
|
|
* XTA is the acronym for 'XT-Attached', which was basically
|
|
* the XT-counterpart to what we know now as IDE (which is
|
|
* also named ATA - AT Attachment.) The basic ideas was to
|
|
* put the actual drive controller electronics onto the drive
|
|
* itself, and have the host machine just talk to that using
|
|
* a simpe, standardized I/O path- hence the name IDE, for
|
|
* Integrated Drive Electronics.
|
|
*
|
|
* In the ATA version of IDE, the programming interface of
|
|
* the IBM PC/AT (which used the Western Digitial 1002/1003
|
|
* controllers) was kept, and, so, ATA-IDE assumes a 16bit
|
|
* data path: it reads and writes 16bit words of data. The
|
|
* disk drives for this bus commonly have an 'A' suffix to
|
|
* identify them as 'ATBUS'.
|
|
*
|
|
* In XTA-IDE, which is slightly older, the programming
|
|
* interface of the IBM PC/XT (which used the MFM controller
|
|
* from Xebec) was kept, and, so, it uses an 8bit data path.
|
|
* Disk drives for this bus commonly have the 'X' suffix to
|
|
* mark them as being for this XTBUS variant.
|
|
*
|
|
* So, XTA and ATA try to do the same thing, but they use
|
|
* different ways to achive their goal.
|
|
*
|
|
* Also, XTA is **not** the same as XTIDE. XTIDE is a modern
|
|
* variant of ATA-IDE, but retro-fitted for use on 8bit XT
|
|
* systems: an extra register is used to deal with the extra
|
|
* data byte per transfer. XTIDE uses regular IDE drives,
|
|
* and uses the regular ATA/IDE programming interface, just
|
|
* with the extra register.
|
|
*
|
|
* NOTE: This driver implements both the 'standard' XTA interface,
|
|
* sold by Western Digital as the WDXT-140 (no BIOS) and the
|
|
* WDXT-150 (with BIOS), as well as some variants customized
|
|
* for specific machines.
|
|
*
|
|
* NOTE: The XTA interface is 0-based for sector numbers !!
|
|
*
|
|
* Version: @(#)hdc_ide_xta.c 1.0.7 2018/04/26
|
|
*
|
|
* Author: Fred N. van Kempen, <decwiz@yahoo.com>
|
|
*
|
|
* Based on my earlier HD20 driver for the EuroPC.
|
|
*
|
|
* Copyright 2017,2018 Fred N. van Kempen.
|
|
*
|
|
* Redistribution and use in source and binary forms, with
|
|
* or without modification, are permitted provided that the
|
|
* following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the entire
|
|
* above notice, this list of conditions and the following
|
|
* disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names
|
|
* of its contributors may be used to endorse or promote
|
|
* products derived from this software without specific
|
|
* prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#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 "../86box.h"
|
|
#include "../io.h"
|
|
#include "../dma.h"
|
|
#include "../pic.h"
|
|
#include "../mem.h"
|
|
#include "../rom.h"
|
|
#include "../device.h"
|
|
#include "../timer.h"
|
|
#include "../plat.h"
|
|
#include "../ui.h"
|
|
#include "hdc.h"
|
|
#include "hdd.h"
|
|
|
|
|
|
#define HDC_TIME (50*TIMER_USEC)
|
|
|
|
#define WD_BIOS_FILE L"roms/hdd/xta/idexywd2.bin"
|
|
|
|
|
|
enum {
|
|
STATE_IDLE = 0,
|
|
STATE_RECV,
|
|
STATE_RDATA,
|
|
STATE_RDONE,
|
|
STATE_SEND,
|
|
STATE_SDATA,
|
|
STATE_SDONE,
|
|
STATE_COMPL
|
|
};
|
|
|
|
|
|
/* Command values. */
|
|
#define CMD_TEST_READY 0x00
|
|
#define CMD_RECALIBRATE 0x01
|
|
/* unused 0x02 */
|
|
#define CMD_READ_SENSE 0x03
|
|
#define CMD_FORMAT_DRIVE 0x04
|
|
#define CMD_READ_VERIFY 0x05
|
|
#define CMD_FORMAT_TRACK 0x06
|
|
#define CMD_FORMAT_BAD_TRACK 0x07
|
|
#define CMD_READ_SECTORS 0x08
|
|
/* unused 0x09 */
|
|
#define CMD_WRITE_SECTORS 0x0a
|
|
#define CMD_SEEK 0x0b
|
|
#define CMD_SET_DRIVE_PARAMS 0x0c
|
|
#define CMD_READ_ECC_BURST 0x0d
|
|
#define CMD_READ_SECTOR_BUFFER 0x0e
|
|
#define CMD_WRITE_SECTOR_BUFFER 0x0f
|
|
#define CMD_RAM_DIAGS 0xe0
|
|
/* unused 0xe1 */
|
|
/* unused 0xe2 */
|
|
#define CMD_DRIVE_DIAGS 0xe3
|
|
#define CMD_CTRL_DIAGS 0xe4
|
|
#define CMD_READ_LONG 0xe5
|
|
#define CMD_WRITE_LONG 0xe6
|
|
|
|
/* Status register (reg 1) values. */
|
|
#define STAT_REQ 0x01 /* controller needs data transfer */
|
|
#define STAT_IO 0x02 /* direction of transfer (TO bus) */
|
|
#define STAT_CD 0x04 /* transfer of Command or Data */
|
|
#define STAT_BSY 0x08 /* controller is busy */
|
|
#define STAT_DRQ 0x10 /* DMA requested */
|
|
#define STAT_IRQ 0x20 /* interrupt requested */
|
|
#define STAT_DCB 0x80 /* not seen by driver */
|
|
|
|
/* Sense Error codes. */
|
|
#define ERR_NOERROR 0x00 /* no error detected */
|
|
#define ERR_NOINDEX 0x01 /* drive did not detect IDX pulse */
|
|
#define ERR_NOSEEK 0x02 /* drive did not complete SEEK */
|
|
#define ERR_WRFAULT 0x03 /* write fault during last cmd */
|
|
#define ERR_NOTRDY 0x04 /* drive did not go READY after cmd */
|
|
#define ERR_NOTRK000 0x06 /* drive did not see TRK0 signal */
|
|
#define ERR_LONGSEEK 0x08 /* long seek in progress */
|
|
#define ERR_IDREAD 0x10 /* ECC error during ID field */
|
|
#define ERR_DATA 0x11 /* uncorrectable ECC err in data */
|
|
#define ERR_NOMARK 0x12 /* no address mark detected */
|
|
#define ERR_NOSECT 0x14 /* sector not found */
|
|
#define ERR_SEEK 0x15 /* seek error */
|
|
#define ERR_ECCDATA 0x18 /* ECC corrected data */
|
|
#define ERR_BADTRK 0x19 /* bad track detected */
|
|
#define ERR_ILLCMD 0x20 /* invalid command received */
|
|
#define ERR_ILLADDR 0x21 /* invalid disk address received */
|
|
#define ERR_BADRAM 0x30 /* bad RAM in sector data buffer */
|
|
#define ERR_BADROM 0x31 /* bad checksum in ROM test */
|
|
#define ERR_BADECC 0x32 /* ECC polynomial generator bad */
|
|
|
|
/* Completion Byte fields. */
|
|
#define COMP_DRIVE 0x20
|
|
#define COMP_ERR 0x02
|
|
|
|
#define IRQ_ENA 0x02
|
|
#define DMA_ENA 0x01
|
|
|
|
|
|
/* The device control block (6 bytes) */
|
|
#pragma pack(push,1)
|
|
typedef struct {
|
|
uint8_t cmd; /* [7:5] class, [4:0] opcode */
|
|
|
|
uint8_t head :5, /* [4:0] head number */
|
|
drvsel :1, /* [5] drive select */
|
|
mbz :2; /* [7:6] 00 */
|
|
|
|
uint8_t sector :6, /* [5:0] sector number 0-63 */
|
|
cyl_high :2; /* [7:6] cylinder [9:8] bits */
|
|
|
|
uint8_t cyl_low; /* [7:0] cylinder [7:0] bits */
|
|
|
|
uint8_t count; /* [7:0] blk count / interleave */
|
|
|
|
uint8_t ctrl; /* [7:0] control field */
|
|
} dcb_t;
|
|
#pragma pack(pop)
|
|
|
|
/* The (configured) Drive Parameters. */
|
|
#pragma pack(push,1)
|
|
typedef struct {
|
|
uint8_t cyl_high; /* (MSB) number of cylinders */
|
|
uint8_t cyl_low; /* (LSB) number of cylinders */
|
|
uint8_t heads; /* number of heads per cylinder */
|
|
uint8_t rwc_high; /* (MSB) reduced write current cylinder */
|
|
uint8_t rwc_low; /* (LSB) reduced write current cylinder */
|
|
uint8_t wp_high; /* (MSB) write precompensation cylinder */
|
|
uint8_t wp_low; /* (LSB) write precompensation cylinder */
|
|
uint8_t maxecc; /* max ECC data burst length */
|
|
} dprm_t;
|
|
#pragma pack(pop)
|
|
|
|
/* Define an attached drive. */
|
|
typedef struct {
|
|
int8_t id, /* drive ID on bus */
|
|
present, /* drive is present */
|
|
hdd_num, /* index to global disk table */
|
|
type; /* drive type ID */
|
|
|
|
uint16_t cur_cyl; /* last known position of heads */
|
|
|
|
uint8_t spt, /* active drive parameters */
|
|
hpc;
|
|
uint16_t tracks;
|
|
|
|
uint8_t cfg_spt, /* configured drive parameters */
|
|
cfg_hpc;
|
|
uint16_t cfg_tracks;
|
|
} drive_t;
|
|
|
|
|
|
typedef struct {
|
|
const char *name; /* controller name */
|
|
|
|
uint16_t base; /* controller base I/O address */
|
|
int8_t irq; /* controller IRQ channel */
|
|
int8_t dma; /* controller DMA channel */
|
|
int8_t type; /* controller type ID */
|
|
|
|
uint32_t rom_addr; /* address where ROM is */
|
|
rom_t bios_rom; /* descriptor for the BIOS */
|
|
|
|
/* Controller state. */
|
|
int8_t state; /* controller state */
|
|
uint8_t sense; /* current SENSE ERROR value */
|
|
uint8_t status; /* current operational status */
|
|
uint8_t intr;
|
|
int64_t callback;
|
|
|
|
/* Data transfer. */
|
|
int16_t buf_idx, /* buffer index and pointer */
|
|
buf_len;
|
|
uint8_t *buf_ptr;
|
|
|
|
/* Current operation parameters. */
|
|
dcb_t dcb; /* device control block */
|
|
uint16_t track; /* requested track# */
|
|
uint8_t head, /* requested head# */
|
|
sector, /* requested sector# */
|
|
comp; /* operation completion byte */
|
|
int count; /* requested sector count */
|
|
|
|
drive_t drives[XTA_NUM]; /* the attached drive(s) */
|
|
|
|
uint8_t data[512]; /* data buffer */
|
|
uint8_t sector_buf[512]; /* sector buffer */
|
|
} hdc_t;
|
|
|
|
|
|
#ifdef ENABLE_XTA_LOG
|
|
int xta_do_log = ENABLE_XTA_LOG;
|
|
#endif
|
|
|
|
|
|
static void
|
|
xta_log(const char *fmt, ...)
|
|
{
|
|
#ifdef ENABLE_XTA_LOG
|
|
va_list ap;
|
|
|
|
if (xta_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
static void
|
|
set_intr(hdc_t *dev)
|
|
{
|
|
dev->status = STAT_REQ|STAT_CD|STAT_IO|STAT_BSY;
|
|
dev->state = STATE_COMPL;
|
|
|
|
if (dev->intr & IRQ_ENA) {
|
|
dev->status |= STAT_IRQ;
|
|
picint(1 << dev->irq);
|
|
}
|
|
}
|
|
|
|
|
|
/* Get the logical (block) address of a CHS triplet. */
|
|
static int
|
|
get_sector(hdc_t *dev, drive_t *drive, off64_t *addr)
|
|
{
|
|
if (drive->cur_cyl != dev->track) {
|
|
xta_log("%s: get_sector: wrong cylinder %d/%d\n",
|
|
dev->name, drive->cur_cyl, dev->track);
|
|
dev->sense = ERR_ILLADDR;
|
|
return(1);
|
|
}
|
|
|
|
if (dev->head >= drive->hpc) {
|
|
xta_log("%s: get_sector: past end of heads\n", dev->name);
|
|
dev->sense = ERR_ILLADDR;
|
|
return(1);
|
|
}
|
|
|
|
if (dev->sector >= drive->spt) {
|
|
xta_log("%s: get_sector: past end of sectors\n", dev->name);
|
|
dev->sense = ERR_ILLADDR;
|
|
return(1);
|
|
}
|
|
|
|
/* Calculate logical address (block number) of desired sector. */
|
|
*addr = ((((off64_t) dev->track*drive->hpc) + \
|
|
dev->head)*drive->spt) + dev->sector;
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
static void
|
|
next_sector(hdc_t *dev, drive_t *drive)
|
|
{
|
|
if (++dev->sector >= drive->spt) {
|
|
dev->sector = 0;
|
|
if (++dev->head >= drive->hpc) {
|
|
dev->head = 0;
|
|
dev->track++;
|
|
if (++drive->cur_cyl >= drive->tracks)
|
|
drive->cur_cyl = (drive->tracks - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Perform the seek operation. */
|
|
static void
|
|
do_seek(hdc_t *dev, drive_t *drive, int cyl)
|
|
{
|
|
dev->track = cyl;
|
|
|
|
if (dev->track >= drive->tracks)
|
|
drive->cur_cyl = (drive->tracks - 1);
|
|
else
|
|
drive->cur_cyl = dev->track;
|
|
|
|
if (drive->cur_cyl < 0)
|
|
drive->cur_cyl = 0;
|
|
}
|
|
|
|
|
|
/* Format a track or an entire drive. */
|
|
static void
|
|
do_format(hdc_t *dev, drive_t *drive, dcb_t *dcb)
|
|
{
|
|
int start_cyl, end_cyl;
|
|
int start_hd, end_hd;
|
|
off64_t addr;
|
|
int h, s;
|
|
|
|
/* Get the parameters from the DCB. */
|
|
if (dcb->cmd == CMD_FORMAT_DRIVE) {
|
|
start_cyl = 0;
|
|
start_hd = 0;
|
|
end_cyl = drive->tracks;
|
|
end_hd = drive->hpc;
|
|
} else {
|
|
start_cyl = (dcb->cyl_low | (dcb->cyl_high << 8));
|
|
start_hd = dcb->head;
|
|
end_cyl = start_cyl + 1;
|
|
end_hd = start_hd + 1;
|
|
}
|
|
|
|
switch (dev->state) {
|
|
case STATE_IDLE:
|
|
/* Seek to cylinder. */
|
|
do_seek(dev, drive, start_cyl);
|
|
dev->head = dcb->head;
|
|
dev->sector = 0;
|
|
|
|
/* Activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 1);
|
|
|
|
do_fmt:
|
|
/*
|
|
* For now, we don't use the interleave factor (in
|
|
* dcb->count), although we should one day use an
|
|
* image format that can handle it..
|
|
*
|
|
* That said, we have been given a sector_buf of
|
|
* data to fill the sectors with, so we will use
|
|
* that at least.
|
|
*/
|
|
for (h = start_hd; h < end_hd; h++) {
|
|
for (s = 0; s < drive->spt; s++) {
|
|
/* Set the sector we need to write. */
|
|
dev->head = h;
|
|
dev->sector = s;
|
|
|
|
/* Get address of sector to write. */
|
|
if (get_sector(dev, drive, &addr)) break;
|
|
|
|
/* Write the block to the image. */
|
|
hdd_image_write(drive->hdd_num, addr, 1,
|
|
(uint8_t *)dev->sector_buf);
|
|
}
|
|
}
|
|
|
|
/* One more track done. */
|
|
if (++start_cyl == end_cyl) break;
|
|
|
|
/* This saves us a LOT of code. */
|
|
goto do_fmt;
|
|
}
|
|
|
|
/* De-activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
|
|
}
|
|
|
|
|
|
/* Execute the DCB we just received. */
|
|
static void
|
|
hdc_callback(void *priv)
|
|
{
|
|
hdc_t *dev = (hdc_t *)priv;
|
|
dcb_t *dcb = &dev->dcb;
|
|
drive_t *drive;
|
|
dprm_t *params;
|
|
off64_t addr;
|
|
int no_data = 0;
|
|
int val;
|
|
|
|
/* Cancel timer. */
|
|
dev->callback = 0;
|
|
|
|
drive = &dev->drives[dcb->drvsel];
|
|
dev->comp = (dcb->drvsel) ? COMP_DRIVE : 0x00;
|
|
dev->status |= STAT_DCB;
|
|
|
|
switch (dcb->cmd) {
|
|
case CMD_TEST_READY:
|
|
if (! drive->present) {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_NOTRDY;
|
|
}
|
|
set_intr(dev);
|
|
break;
|
|
|
|
case CMD_RECALIBRATE:
|
|
if (! drive->present) {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_NOTRDY;
|
|
} else {
|
|
dev->track = drive->cur_cyl = 0;
|
|
}
|
|
set_intr(dev);
|
|
break;
|
|
|
|
case CMD_READ_SENSE:
|
|
switch(dev->state) {
|
|
case STATE_IDLE:
|
|
dev->buf_idx = 0;
|
|
dev->buf_len = 4;
|
|
dev->buf_ptr = dev->data;
|
|
dev->buf_ptr[0] = dev->sense;
|
|
dev->buf_ptr[1] = dcb->drvsel ? 0x20 : 0x00;
|
|
dev->buf_ptr[2] = (drive->cur_cyl >> 2) | \
|
|
(dev->sector & 0x3f);
|
|
dev->buf_ptr[3] = (drive->cur_cyl & 0xff);
|
|
dev->sense = ERR_NOERROR;
|
|
dev->status |= (STAT_IO | STAT_REQ);
|
|
dev->state = STATE_SDATA;
|
|
break;
|
|
|
|
case STATE_SDONE:
|
|
set_intr(dev);
|
|
}
|
|
break;
|
|
|
|
case CMD_READ_VERIFY:
|
|
no_data = 1;
|
|
/*FALLTHROUGH*/
|
|
|
|
case CMD_READ_SECTORS:
|
|
if (! drive->present) {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_NOTRDY;
|
|
set_intr(dev);
|
|
break;
|
|
}
|
|
|
|
switch (dev->state) {
|
|
case STATE_IDLE:
|
|
/* Seek to cylinder. */
|
|
do_seek(dev, drive,
|
|
(dcb->cyl_low|(dcb->cyl_high<<8)));
|
|
dev->head = dcb->head;
|
|
dev->sector = dcb->sector;
|
|
|
|
/* Get sector count; count=0 means 256. */
|
|
dev->count = (int)dcb->count;
|
|
if (dev->count == 0)
|
|
dev->count = 256;
|
|
dev->buf_len = 512;
|
|
|
|
dev->state = STATE_SEND;
|
|
/*FALLTHROUGH*/
|
|
|
|
case STATE_SEND:
|
|
/* Activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 1);
|
|
do_send:
|
|
/* Get address of sector to load. */
|
|
if (get_sector(dev, drive, &addr)) {
|
|
/* De-activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
|
|
dev->comp |= COMP_ERR;
|
|
set_intr(dev);
|
|
return;
|
|
}
|
|
|
|
/* Read the block from the image. */
|
|
hdd_image_read(drive->hdd_num, addr, 1,
|
|
(uint8_t *)dev->sector_buf);
|
|
|
|
/* Ready to transfer the data out. */
|
|
dev->state = STATE_SDATA;
|
|
dev->buf_idx = 0;
|
|
if (no_data) {
|
|
/* Delay a bit, no actual transfer. */
|
|
dev->callback = HDC_TIME;
|
|
} else {
|
|
if (dev->intr & DMA_ENA) {
|
|
/* DMA enabled. */
|
|
dev->buf_ptr = dev->sector_buf;
|
|
dev->callback = HDC_TIME;
|
|
} else {
|
|
/* Copy from sector to data. */
|
|
memcpy(dev->data,
|
|
dev->sector_buf,
|
|
dev->buf_len);
|
|
dev->buf_ptr = dev->data;
|
|
|
|
dev->status |= (STAT_IO | STAT_REQ);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_SDATA:
|
|
if (! no_data) {
|
|
/* Perform DMA. */
|
|
while (dev->buf_idx < dev->buf_len) {
|
|
val = dma_channel_write(dev->dma,
|
|
*dev->buf_ptr);
|
|
if (val == DMA_NODATA) {
|
|
xta_log("%s: CMD_READ_SECTORS out of data (idx=%d, len=%d)!\n", dev->name, dev->buf_idx, dev->buf_len);
|
|
|
|
dev->status |= (STAT_CD | STAT_IO| STAT_REQ);
|
|
dev->callback = HDC_TIME;
|
|
return;
|
|
}
|
|
dev->buf_ptr++;
|
|
dev->buf_idx++;
|
|
}
|
|
}
|
|
dev->callback = HDC_TIME;
|
|
dev->state = STATE_SDONE;
|
|
break;
|
|
|
|
case STATE_SDONE:
|
|
dev->buf_idx = 0;
|
|
if (--dev->count == 0) {
|
|
/* De-activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
|
|
|
|
set_intr(dev);
|
|
return;
|
|
}
|
|
|
|
/* Addvance to next sector. */
|
|
next_sector(dev, drive);
|
|
|
|
/* This saves us a LOT of code. */
|
|
dev->state = STATE_SEND;
|
|
goto do_send;
|
|
}
|
|
break;
|
|
|
|
#if 0
|
|
case CMD_WRITE_VERIFY:
|
|
no_data = 1;
|
|
/*FALLTHROUGH*/
|
|
#endif
|
|
|
|
case CMD_WRITE_SECTORS:
|
|
if (! drive->present) {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_NOTRDY;
|
|
set_intr(dev);
|
|
break;
|
|
}
|
|
|
|
switch (dev->state) {
|
|
case STATE_IDLE:
|
|
/* Seek to cylinder. */
|
|
do_seek(dev, drive,
|
|
(dcb->cyl_low|(dcb->cyl_high<<8)));
|
|
dev->head = dcb->head;
|
|
dev->sector = dcb->sector;
|
|
|
|
/* Get sector count; count=0 means 256. */
|
|
dev->count = (int)dev->dcb.count;
|
|
if (dev->count == 0)
|
|
dev->count = 256;
|
|
dev->buf_len = 512;
|
|
|
|
dev->state = STATE_RECV;
|
|
/*FALLTHROUGH*/
|
|
|
|
case STATE_RECV:
|
|
/* Activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 1);
|
|
do_recv:
|
|
/* Ready to transfer the data in. */
|
|
dev->state = STATE_RDATA;
|
|
dev->buf_idx = 0;
|
|
if (no_data) {
|
|
/* Delay a bit, no actual transfer. */
|
|
dev->callback = HDC_TIME;
|
|
} else {
|
|
if (dev->intr & DMA_ENA) {
|
|
/* DMA enabled. */
|
|
dev->buf_ptr = dev->sector_buf;
|
|
dev->callback = HDC_TIME;
|
|
} else {
|
|
/* No DMA, do PIO. */
|
|
dev->buf_ptr = dev->data;
|
|
dev->status |= STAT_REQ;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STATE_RDATA:
|
|
if (! no_data) {
|
|
/* Perform DMA. */
|
|
dev->status = STAT_BSY;
|
|
while (dev->buf_idx < dev->buf_len) {
|
|
val = dma_channel_read(dev->dma);
|
|
if (val == DMA_NODATA) {
|
|
xta_log("%s: CMD_WRITE_SECTORS out of data (idx=%d, len=%d)!\n", dev->name, dev->buf_idx, dev->buf_len);
|
|
|
|
xta_log("%s: CMD_WRITE_SECTORS out of data!\n", dev->name);
|
|
dev->status |= (STAT_CD | STAT_IO | STAT_REQ);
|
|
dev->callback = HDC_TIME;
|
|
return;
|
|
}
|
|
|
|
dev->buf_ptr[dev->buf_idx] = (val & 0xff);
|
|
dev->buf_idx++;
|
|
}
|
|
dev->state = STATE_RDONE;
|
|
dev->callback = HDC_TIME;
|
|
}
|
|
break;
|
|
|
|
case STATE_RDONE:
|
|
/* Copy from data to sector if PIO. */
|
|
if (! (dev->intr & DMA_ENA))
|
|
memcpy(dev->sector_buf, dev->data,
|
|
dev->buf_len);
|
|
|
|
/* Get address of sector to write. */
|
|
if (get_sector(dev, drive, &addr)) {
|
|
/* De-activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
|
|
|
|
dev->comp |= COMP_ERR;
|
|
set_intr(dev);
|
|
return;
|
|
}
|
|
|
|
/* Write the block to the image. */
|
|
hdd_image_write(drive->hdd_num, addr, 1,
|
|
(uint8_t *)dev->sector_buf);
|
|
|
|
dev->buf_idx = 0;
|
|
if (--dev->count == 0) {
|
|
/* De-activate the status icon. */
|
|
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
|
|
|
|
set_intr(dev);
|
|
return;
|
|
}
|
|
|
|
/* Advance to next sector. */
|
|
next_sector(dev, drive);
|
|
|
|
/* This saves us a LOT of code. */
|
|
dev->state = STATE_RECV;
|
|
goto do_recv;
|
|
}
|
|
break;
|
|
|
|
case CMD_FORMAT_DRIVE:
|
|
case CMD_FORMAT_TRACK:
|
|
if (drive->present) {
|
|
do_format(dev, drive, dcb);
|
|
} else {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_NOTRDY;
|
|
}
|
|
set_intr(dev);
|
|
break;
|
|
|
|
case CMD_SEEK:
|
|
/* Seek to cylinder. */
|
|
val = (dcb->cyl_low | (dcb->cyl_high << 8));
|
|
if (drive->present) {
|
|
do_seek(dev, drive, val);
|
|
if (val != drive->cur_cyl) {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_SEEK;
|
|
}
|
|
} else {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_NOTRDY;
|
|
}
|
|
set_intr(dev);
|
|
break;
|
|
|
|
case CMD_SET_DRIVE_PARAMS:
|
|
switch(dev->state) {
|
|
case STATE_IDLE:
|
|
dev->state = STATE_RDATA;
|
|
dev->buf_idx = 0;
|
|
dev->buf_len = sizeof(dprm_t);
|
|
dev->buf_ptr = (uint8_t *)dev->data;
|
|
dev->status |= STAT_REQ;
|
|
break;
|
|
|
|
case STATE_RDONE:
|
|
params = (dprm_t *)dev->data;
|
|
drive->tracks =
|
|
(params->cyl_high << 8) | params->cyl_low;
|
|
drive->hpc = params->heads;
|
|
drive->spt = 17 /*hardcoded*/;
|
|
dev->status &= ~STAT_REQ;
|
|
set_intr(dev);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CMD_WRITE_SECTOR_BUFFER:
|
|
switch (dev->state) {
|
|
case STATE_IDLE:
|
|
dev->buf_idx = 0;
|
|
dev->buf_len = 512;
|
|
dev->state = STATE_RDATA;
|
|
if (dev->intr & DMA_ENA) {
|
|
dev->buf_ptr = dev->sector_buf;
|
|
dev->callback = HDC_TIME;
|
|
} else {
|
|
dev->buf_ptr = dev->data;
|
|
dev->status |= STAT_REQ;
|
|
}
|
|
break;
|
|
|
|
case STATE_RDATA:
|
|
if (dev->intr & DMA_ENA) {
|
|
/* Perform DMA. */
|
|
while (dev->buf_idx < dev->buf_len) {
|
|
val = dma_channel_read(dev->dma);
|
|
if (val == DMA_NODATA) {
|
|
xta_log("%s: CMD_WRITE_BUFFER out of data!\n", dev->name);
|
|
dev->status |= (STAT_CD | STAT_IO | STAT_REQ);
|
|
dev->callback = HDC_TIME;
|
|
return;
|
|
}
|
|
|
|
dev->buf_ptr[dev->buf_idx] = (val & 0xff);
|
|
dev->buf_idx++;
|
|
}
|
|
dev->state = STATE_RDONE;
|
|
dev->callback = HDC_TIME;
|
|
}
|
|
break;
|
|
|
|
case STATE_RDONE:
|
|
if (! (dev->intr & DMA_ENA))
|
|
memcpy(dev->sector_buf,
|
|
dev->data, dev->buf_len);
|
|
set_intr(dev);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CMD_RAM_DIAGS:
|
|
switch(dev->state) {
|
|
case STATE_IDLE:
|
|
dev->state = STATE_RDONE;
|
|
dev->callback = 5*HDC_TIME;
|
|
break;
|
|
|
|
case STATE_RDONE:
|
|
set_intr(dev);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CMD_DRIVE_DIAGS:
|
|
switch(dev->state) {
|
|
case STATE_IDLE:
|
|
if (drive->present) {
|
|
dev->state = STATE_RDONE;
|
|
dev->callback = 5*HDC_TIME;
|
|
} else {
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_NOTRDY;
|
|
set_intr(dev);
|
|
}
|
|
break;
|
|
|
|
case STATE_RDONE:
|
|
set_intr(dev);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case CMD_CTRL_DIAGS:
|
|
switch(dev->state) {
|
|
case STATE_IDLE:
|
|
dev->state = STATE_RDONE;
|
|
dev->callback = 10*HDC_TIME;
|
|
break;
|
|
|
|
case STATE_RDONE:
|
|
set_intr(dev);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
xta_log("%s: unknown command - %02x\n", dev->name, dcb->cmd);
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_ILLCMD;
|
|
set_intr(dev);
|
|
}
|
|
}
|
|
|
|
|
|
/* Read one of the controller registers. */
|
|
static uint8_t
|
|
hdc_read(uint16_t port, void *priv)
|
|
{
|
|
hdc_t *dev = (hdc_t *)priv;
|
|
uint8_t ret = 0xff;
|
|
|
|
switch (port & 7) {
|
|
case 0: /* DATA register */
|
|
dev->status &= ~STAT_IRQ;
|
|
|
|
if (dev->state == STATE_SDATA) {
|
|
if (dev->buf_idx > dev->buf_len) {
|
|
xta_log("%s: read with empty buffer!\n",
|
|
dev->name);
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_ILLCMD;
|
|
break;
|
|
}
|
|
|
|
ret = dev->buf_ptr[dev->buf_idx];
|
|
if (++dev->buf_idx == dev->buf_len) {
|
|
/* All data sent. */
|
|
dev->status &= ~STAT_REQ;
|
|
dev->state = STATE_SDONE;
|
|
dev->callback = HDC_TIME;
|
|
}
|
|
} else if (dev->state == STATE_COMPL) {
|
|
xta_log("DCB=%02X status=%02X comp=%02X\n", dev->dcb.cmd, dev->status, dev->comp);
|
|
ret = dev->comp;
|
|
dev->status = 0x00;
|
|
dev->state = STATE_IDLE;
|
|
}
|
|
break;
|
|
|
|
case 1: /* STATUS register */
|
|
ret = (dev->status & ~STAT_DCB);
|
|
break;
|
|
|
|
case 2: /* "read option jumpers" */
|
|
ret = 0xff; /* all switches off */
|
|
break;
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
|
|
/* Write to one of the controller registers. */
|
|
static void
|
|
hdc_write(uint16_t port, uint8_t val, void *priv)
|
|
{
|
|
hdc_t *dev = (hdc_t *)priv;
|
|
|
|
switch (port & 7) {
|
|
case 0: /* DATA register */
|
|
if (dev->state == STATE_RDATA) {
|
|
if (! (dev->status & STAT_REQ)) {
|
|
xta_log("%s: not ready for command/data!\n", dev->name);
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_ILLCMD;
|
|
break;
|
|
}
|
|
|
|
if (dev->buf_idx >= dev->buf_len) {
|
|
xta_log("%s: write with full buffer!\n", dev->name);
|
|
dev->comp |= COMP_ERR;
|
|
dev->sense = ERR_ILLCMD;
|
|
break;
|
|
}
|
|
|
|
/* Store the data into the buffer. */
|
|
dev->buf_ptr[dev->buf_idx] = val;
|
|
if (++dev->buf_idx == dev->buf_len) {
|
|
/* We got all the data we need. */
|
|
dev->status &= ~STAT_REQ;
|
|
if (dev->status & STAT_DCB)
|
|
dev->state = STATE_RDONE;
|
|
else
|
|
dev->state = STATE_IDLE;
|
|
dev->status &= ~STAT_CD;
|
|
dev->callback = HDC_TIME;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 1: /* RESET register */
|
|
dev->sense = 0x00;
|
|
dev->state = STATE_IDLE;
|
|
break;
|
|
|
|
case 2: /* "controller-select" */
|
|
/* Reset the DCB buffer. */
|
|
dev->buf_idx = 0;
|
|
dev->buf_len = sizeof(dcb_t);
|
|
dev->buf_ptr = (uint8_t *)&dev->dcb;
|
|
dev->state = STATE_RDATA;
|
|
dev->status = (STAT_BSY | STAT_CD | STAT_REQ);
|
|
break;
|
|
|
|
case 3: /* DMA/IRQ intr register */
|
|
//xta_log("%s: WriteMASK(%02X)\n", dev->name, val);
|
|
dev->intr = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void *
|
|
xta_init(const device_t *info)
|
|
{
|
|
drive_t *drive;
|
|
wchar_t *fn = NULL;
|
|
hdc_t *dev;
|
|
int c, i;
|
|
int max = XTA_NUM;
|
|
|
|
/* Allocate and initialize device block. */
|
|
dev = malloc(sizeof(hdc_t));
|
|
memset(dev, 0x00, sizeof(hdc_t));
|
|
dev->type = info->local;
|
|
|
|
/* Do per-controller-type setup. */
|
|
switch(dev->type) {
|
|
case 0: /* WDXT-150, with BIOS */
|
|
dev->name = "WDXT-150";
|
|
dev->base = device_get_config_hex16("base");
|
|
dev->irq = device_get_config_int("irq");
|
|
dev->rom_addr = device_get_config_hex20("bios_addr");
|
|
dev->dma = 3;
|
|
fn = WD_BIOS_FILE;
|
|
max = 1;
|
|
break;
|
|
|
|
case 1: /* EuroPC */
|
|
dev->name = "HD20";
|
|
dev->base = 0x0320;
|
|
dev->irq = 5;
|
|
dev->dma = 3;
|
|
break;
|
|
}
|
|
|
|
xta_log("%s: initializing (I/O=%04X, IRQ=%d, DMA=%d",
|
|
dev->name, dev->base, dev->irq, dev->dma);
|
|
if (dev->rom_addr != 0x000000)
|
|
xta_log(", BIOS=%06X", dev->rom_addr);
|
|
xta_log(")\n");
|
|
|
|
/* Load any disks for this device class. */
|
|
c = 0;
|
|
for (i = 0; i < HDD_NUM; i++) {
|
|
if ((hdd[i].bus == HDD_BUS_XTA) && (hdd[i].xta_channel < max)) {
|
|
drive = &dev->drives[hdd[i].xta_channel];
|
|
|
|
if (! hdd_image_load(i)) {
|
|
drive->present = 0;
|
|
continue;
|
|
}
|
|
drive->id = c;
|
|
drive->hdd_num = i;
|
|
drive->present = 1;
|
|
|
|
/* These are the "hardware" parameters (from the image.) */
|
|
drive->cfg_spt = (uint8_t)(hdd[i].spt & 0xff);
|
|
drive->cfg_hpc = (uint8_t)(hdd[i].hpc & 0xff);
|
|
drive->cfg_tracks = (uint16_t)hdd[i].tracks;
|
|
|
|
/* Use them as "configured" parameters until overwritten. */
|
|
drive->spt = drive->cfg_spt;
|
|
drive->hpc = drive->cfg_hpc;
|
|
drive->tracks = drive->cfg_tracks;
|
|
|
|
xta_log("%s: drive%d (cyl=%d,hd=%d,spt=%d), disk %d\n",
|
|
dev->name, hdd[i].xta_channel, drive->tracks,
|
|
drive->hpc, drive->spt, i);
|
|
|
|
if (++c > max) break;
|
|
}
|
|
}
|
|
|
|
/* Enable the I/O block. */
|
|
io_sethandler(dev->base, 4,
|
|
hdc_read,NULL,NULL, hdc_write,NULL,NULL, dev);
|
|
|
|
/* Load BIOS if it has one. */
|
|
if (dev->rom_addr != 0x000000)
|
|
rom_init(&dev->bios_rom, fn,
|
|
dev->rom_addr, 0x2000, 0x1fff, 0, MEM_MAPPING_EXTERNAL);
|
|
|
|
/* Create a timer for command delays. */
|
|
timer_add(hdc_callback, &dev->callback, &dev->callback, dev);
|
|
|
|
return(dev);
|
|
}
|
|
|
|
|
|
static void
|
|
xta_close(void *priv)
|
|
{
|
|
hdc_t *dev = (hdc_t *)priv;
|
|
drive_t *drive;
|
|
int d;
|
|
|
|
/* Remove the I/O handler. */
|
|
io_removehandler(dev->base, 4,
|
|
hdc_read,NULL,NULL, hdc_write,NULL,NULL, dev);
|
|
|
|
/* Close all disks and their images. */
|
|
for (d = 0; d < XTA_NUM; d++) {
|
|
drive = &dev->drives[d];
|
|
|
|
hdd_image_close(drive->hdd_num);
|
|
}
|
|
|
|
/* Release the device. */
|
|
free(dev);
|
|
}
|
|
|
|
|
|
static const device_config_t wdxt150_config[] = {
|
|
{
|
|
"base", "Address", CONFIG_HEX16, "", 0x0320, /*W2*/
|
|
{
|
|
{
|
|
"320H", 0x0320
|
|
},
|
|
{
|
|
"324H", 0x0324
|
|
},
|
|
{
|
|
""
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"irq", "IRQ", CONFIG_SELECTION, "", 5, /*W3*/
|
|
{
|
|
{
|
|
"IRQ 5", 5
|
|
},
|
|
{
|
|
"IRQ 4", 4
|
|
},
|
|
{
|
|
""
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"bios_addr", "BIOS Address", CONFIG_HEX20, "", 0xc8000, /*W1*/
|
|
{
|
|
{
|
|
"C800H", 0xc8000
|
|
},
|
|
{
|
|
"CA00H", 0xca000
|
|
},
|
|
{
|
|
""
|
|
}
|
|
},
|
|
},
|
|
{
|
|
"", "", -1
|
|
}
|
|
};
|
|
|
|
|
|
const device_t xta_wdxt150_device = {
|
|
"WDXT-150 Fixed Disk Controller",
|
|
DEVICE_ISA,
|
|
0,
|
|
xta_init, xta_close, NULL,
|
|
NULL, NULL, NULL,
|
|
wdxt150_config
|
|
};
|
|
|
|
|
|
const device_t xta_hd20_device = {
|
|
"EuroPC HD20 Fixed Disk Controller",
|
|
DEVICE_ISA,
|
|
1,
|
|
xta_init, xta_close, NULL,
|
|
NULL, NULL, NULL,
|
|
NULL
|
|
};
|