Files
86Box/src/machine/m_ps1_hdc.c
OBattler 0faf6692c9 WARNING: CONFIGS MIGHT PARTIALLY BREAK WHERE DEVICE NAMES HAVE CHANGED.
Changes to device_t struct to accomodate the upcoming PCI IRQ arbitration rewrite;
Added device.c/h API to obtain name from the device_t struct;
Significant changes to win/win_settings.c to clean up the code a bit and fix bugs;
Ported all the CPU and AudioPCI commits from PCem;
Added an API call to allow ACPI soft power off to gracefully stop the emulator;
Removed the Siemens PCD-2L from the Dev branch because it now works;
Removed the Socket 5 HP Vectra from the Dev branch because it now works;
Fixed the Compaq Presario and the Micronics Spitfire;
Give the IBM PC330 its own list of 486 CPU so it can have DX2's with CPUID 0x470;
SMM fixes;
Rewrote the SYSENTER, SYSEXIT, SYSCALL, and SYSRET instructions;
Changed IDE reset period to match the specification, fixes #929;
The keyboard input and output ports are now forced in front of the queue when read, fixes a number of bugs, including the AMI Apollo hanging on soft reset;
Added the Intel AN430TX but Dev branched because it does not work;
The network code no longer drops packets if the emulated network card has failed to receive them (eg. when the buffer is full);
Changes to PCI card adding and renamed some PCI slot types, also added proper AGP bridge slot types;
USB UHCI emulation is no longer a stub (still doesn't fully work, but at least Windows XP chk with Debug no longer ASSERT's on it);
Fixed NVR on the the SMC FDC37C932QF and APM variants;
A number of fixes to Intel 4x0 chipsets, including fixing every register of the 440LX and 440EX;
Some ACPI changes.
2020-11-16 00:01:21 +01:00

1382 lines
36 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 the PS/1 Model 2011 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: We should probably find a nicer way to integrate our Disk
* Type table with the main code, so the user can only select
* items from that list...
*
*
*
* Author: Fred N. van Kempen, <decwiz@yahoo.com>
*
* Based on my earlier HD20 driver for the EuroPC.
* Thanks to Marco Bortolin for the help and feedback !!
*
* Copyright 2017-2019 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 <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/timer.h>
#include <86box/io.h>
#include <86box/dma.h>
#include <86box/pic.h>
#include <86box/device.h>
#include <86box/hdc.h>
#include <86box/hdd.h>
#include <86box/plat.h>
#include <86box/ui.h>
#include <86box/machine.h>
#define HDC_TIME (50*TIMER_USEC)
#define HDC_TYPE_USER 47 /* user drive type */
enum {
STATE_IDLE = 0,
STATE_RECV,
STATE_RDATA,
STATE_RDONE,
STATE_SEND,
STATE_SDATA,
STATE_SDONE,
STATE_FINIT,
STATE_FDONE
};
/* Command values. These deviate from the XTA ones. */
#define CMD_READ_SECTORS 0x01 /* regular read-date */
#define CMD_READ_VERIFY 0x02 /* read for verify, no data */
#define CMD_READ_EXT 0x03 /* read extended (ecc) */
#define CMD_READ_ID 0x05 /* read ID mark on cyl */
#define CMD_RECALIBRATE 0x08 /* recalibrate to track0 */
#define CMD_WRITE_SECTORS 0x09 /* regular write-data */
#define CMD_WRITE_VERIFY 0x0a /* write-data with verify */
#define CMD_WRITE_EXT 0x0b /* write extended (ecc) */
#define CMD_FORMAT_DRIVE 0x0d /* format entire disk */
#define CMD_SEEK 0x0e /* seek */
#define CMD_FORMAT_TRACK 0x0f /* format one track */
/* Attachment Status register (reg 2R) values (IBM PS/1 2011.) */
#define ASR_TX_EN 0x01 /* transfer enable */
#define ASR_INT_REQ 0x02 /* interrupt request */
#define ASR_BUSY 0x04 /* busy */
#define ASR_DIR 0x08 /* direction */
#define ASR_DATA_REQ 0x10 /* data request */
/* Attachment Control register (2W) values (IBM PS/1 2011.) */
#define ACR_DMA_EN 0x01 /* DMA enable */
#define ACR_INT_EN 0x02 /* interrupt enable */
#define ACR_RESET 0x80 /* reset */
/* Interrupt Status register (4R) values (IBM PS/1 2011.) */
#define ISR_EQUIP_CHECK 0x01 /* internal hardware error */
#define ISR_ERP_INVOKED 0x02 /* error recovery invoked */
#define ISR_CMD_REJECT 0x20 /* command reject */
#define ISR_INVALID_CMD 0x40 /* invalid command */
#define ISR_TERMINATION 0x80 /* termination error */
/* Attention register (4W) values (IBM PS/1 2011.) */
#define ATT_DATA 0x10 /* data request */
#define ATT_SSB 0x20 /* sense summary block */
#define ATT_CSB 0x40 /* command specify block */
#define ATT_CCB 0x80 /* command control block */
/*
* Define the Sense Summary Block.
*
* The sense summary block contains the current status of the
* drive. The information in the summary block is updated after
* each command is completed, after an error, or before the
* block is transferred.
*/
#pragma pack(push,1)
typedef struct {
/* Status byte 0. */
uint8_t track_0 :1, /* T0 */
mbz1 :1, /* 0 */
mbz2 :1, /* 0 */
cylinder_err :1, /* CE */
write_fault :1, /* WF */
mbz3 :1, /* 0 */
seek_end :1, /* SE */
not_ready :1; /* NR */
/* Status byte 1. */
uint8_t id_not_found :1, /* ID */
mbz4 :1, /* 0 */
mbz5 :1, /* 0 */
wrong_cyl :1, /* WC */
all_bit_set :1, /* BT */
mark_not_found :1, /* AM */
ecc_crc_err :1, /* ET */
ecc_crc_field :1; /* EF */
/* Status byte 2. */
uint8_t headsel_state :4, /* headsel state[4] */
defective_sector:1, /* DS */
retried_ok :1, /* RG */
need_reset :1, /* RR */
#if 1
valid :1; /* 0 (abused as VALID) */
#else
mbz6 :1; /* 0 */
#endif
/* Most recent ID field seen. */
uint8_t last_cyl_low; /* Cyl_Low[8] */
uint8_t last_head :4, /* HD[4] */
mbz7 :1, /* 0 */
last_cyl_high :2, /* Cyl_high[2] */
last_def_sect :1; /* DS */
uint8_t last_sect; /* Sect[8] */
uint8_t sect_size; /* Size[8] = 02 */
/* Current position. */
uint8_t curr_cyl_high :2, /* Cyl_High_[2] */
mbz8 :1, /* 0 */
mbz9 :1, /* 0 */
curr_head :4; /* HD_2[4] */
uint8_t curr_cyl_low; /* Cyl_Low_2[8] */
uint8_t sect_corr; /* sectors corrected */
uint8_t retries; /* retries */
/*
* This byte shows the progress of the controller through the
* last command. It allows the system to monitor the controller
* and determine if a reset is needed. When the transfer of the
* control block is started, the value is set to hex 00. The
* progress indicated by this byte is:
*
* 1. Set to hex 01 after the control block is successfully
* transferred.
*
* 2. Set to hex 02 when the command is valid and the drive
* is ready.
*
* 3. Set to hex 03 when the head is in the correct track.
* The most-significant four bits (high nibble) are then
* used to indicate the successful stages of the data
* transfer:
*
* Bit 7 A sector was transferred between the system
* and the sector buffer.
*
* Bit 6 A sector was transferred between the controller
* and the sector buffer.
*
* Bit 5 An error was detected and error recovery
* procedures have been started.
*
* Bit 4 The controller has completed the operation
* and is now not busy.
*
* 4. When the transfer is complete, the low nibble equals hex 4
* and the high nibble is unchanged.
*/
uint8_t cmd_syndrome; /* command syndrome */
uint8_t drive_type; /* drive type */
uint8_t rsvd; /* reserved byte */
} ssb_t;
#pragma pack(pop)
/*
* Define the Format Control Block.
*
* The format control block (FCB) specifies the ID data used
* in formatting the track. It is used by the Format Track
* and Format Disk commands and contains five bytes for each
* sector formatted on that track.
*
* When the Format Disk command is used, the control block
* contains the sector information of all sectors for head 0,
* cylinder 0. The drive will use the same block to format
* the rest of the disk and automatically increment the head
* number and cylinder number for the remaining tracks. The
* sector numbers, sector size, and the fill byte will be
* the same for each track.
*
* The drive formats the sector IDs on the disk in the same
* order as they are specified in the control block.
* Therefore, sector interleaving is accomplished by filling
* in the control block with the desired interleave.
*
* For example, when formatting 17 sectors per track with an
* interleave of 2, the control block has the first 5 bytes
* with a sector number of 1, the second with a sector number
* of 10, the third with a sector number of 2, and continuing
* until all 17 sectors for that track are defined.
*
* The format for the format control block is described in
* the following. The five bytes are repeated for each
* sector on the track. The control block must contain an
* even number of bytes. If an odd number of sectors are
* being formatted, an additional byte is sent with all
* bits 0.
*/
#pragma pack(push,1)
typedef struct {
uint8_t cyl_high :2, /* cylinder [9:8] bits */
defective_sector:1, /* DS */
mbz1 :1, /* 0 */
head :4; /* head number */
uint8_t cyl_low; /* cylinder [7:0] bits */
uint8_t sector; /* sector number */
uint8_t mbz2 :1, /* 0 */
mbo :1, /* 1 */
mbz3 :6; /* 000000 */
uint8_t fill; /* filler byte */
} fcb_t;
#pragma pack(pop)
/*
* Define the Command Control Block.
*
* The system specifies the operation by sending the 6-byte
* command control block to the controller. It can be sent
* through a DMA or PIO operation.
*/
#pragma pack(push,1)
typedef struct {
uint8_t ec_p :1, /* EC/P (ecc/park) */
mbz1 :1, /* 0 */
auto_seek :1, /* AS (auto-seek) */
no_data :1, /* ND (no data) */
cmd :4; /* command code[4] */
uint8_t cyl_high :2, /* cylinder [9:8] bits */
mbz2 :2, /* 00 */
head :4; /* head number */
uint8_t cyl_low; /* cylinder [7:0] bits */
uint8_t sector; /* sector number */
uint8_t mbz3 :1, /* 0 */
mbo1 :1, /* 1 */
mbz4 :6; /* 000000 */
uint8_t count; /* blk count/interleave */
} ccb_t;
#pragma pack(pop)
/* Define the hard drive geometry table. */
typedef struct {
uint16_t cyl;
uint8_t hpc;
uint8_t spt;
int16_t wpc;
int16_t lz;
} geom_t;
/* 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 {
uint16_t base; /* controller base I/O address */
int8_t irq; /* controller IRQ channel */
int8_t dma; /* controller DMA channel */
/* Registers. */
uint8_t attn, /* ATTENTION register */
ctrl, /* Control register (ACR) */
status, /* Status register (ASR) */
intstat; /* Interrupt Status register (ISR) */
uint8_t *reg_91; /* handle to system board's register 0x91 */
/* Controller state. */
uint64_t callback;
pc_timer_t timer;
int8_t state, /* controller state */
reset; /* reset state counter */
/* Data transfer. */
int16_t buf_idx, /* buffer index and pointer */
buf_len;
uint8_t *buf_ptr;
/* Current operation parameters. */
ssb_t ssb; /* sense block */
ccb_t ccb; /* command control block */
uint16_t track; /* requested track# */
uint8_t head, /* requested head# */
sector; /* requested sector# */
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;
/*
* IBM hard drive types 1-44.
*
* We need these to translate the selected disk's
* geometry back to a valid type through the SSB.
*
* Cyl. Head Sect. Write Land
* p-comp Zone
*/
static const geom_t ibm_type_table[] = {
{ 0, 0, 0, 0, 0 }, /* 0 (none) */
{ 306, 4, 17, 128, 305 }, /* 1 10 MB */
{ 615, 4, 17, 300, 615 }, /* 2 20 MB */
{ 615, 6, 17, 300, 615 }, /* 3 31 MB */
{ 940, 8, 17, 512, 940 }, /* 4 62 MB */
{ 940, 6, 17, 512, 940 }, /* 5 47 MB */
{ 615, 4, 17, -1, 615 }, /* 6 20 MB */
{ 462, 8, 17, 256, 511 }, /* 7 31 MB */
{ 733, 5, 17, -1, 733 }, /* 8 30 MB */
{ 900, 15, 17, -1, 901 }, /* 9 112 MB */
{ 820, 3, 17, -1, 820 }, /* 10 20 MB */
{ 855, 5, 17, -1, 855 }, /* 11 35 MB */
{ 855, 7, 17, -1, 855 }, /* 12 50 MB */
{ 306, 8, 17, 128, 319 }, /* 13 20 MB */
{ 733, 7, 17, -1, 733 }, /* 14 43 MB */
{ 0, 0, 0, 0, 0 }, /* 15 (rsvd) */
{ 612, 4, 17, 0, 663 }, /* 16 20 MB */
{ 977, 5, 17, 300, 977 }, /* 17 41 MB */
{ 977, 7, 17, -1, 977 }, /* 18 57 MB */
{ 1024, 7, 17, 512, 1023 }, /* 19 59 MB */
{ 733, 5, 17, 300, 732 }, /* 20 30 MB */
{ 733, 7, 17, 300, 732 }, /* 21 43 MB */
{ 733, 5, 17, 300, 733 }, /* 22 30 MB */
{ 306, 4, 17, 0, 336 }, /* 23 10 MB */
{ 612, 4, 17, 305, 663 }, /* 24 20 MB */
{ 306, 4, 17, -1, 340 }, /* 25 10 MB */
{ 612, 4, 17, -1, 670 }, /* 26 20 MB */
{ 698, 7, 17, 300, 732 }, /* 27 41 MB */
{ 976, 5, 17, 488, 977 }, /* 28 40 MB */
{ 306, 4, 17, 0, 340 }, /* 29 10 MB */
{ 611, 4, 17, 306, 663 }, /* 30 20 MB */
{ 732, 7, 17, 300, 732 }, /* 31 43 MB */
{ 1023, 5, 17, -1, 1023 }, /* 32 42 MB */
{ 614, 4, 25, -1, 663 }, /* 33 30 MB */
{ 775, 2, 27, -1, 900 }, /* 34 20 MB */
{ 921, 2, 33, -1, 1000 }, /* 35 30 MB * */
{ 402, 4, 26, -1, 460 }, /* 36 20 MB */
{ 580, 6, 26, -1, 640 }, /* 37 44 MB */
{ 845, 2, 36, -1, 1023 }, /* 38 30 MB * */
{ 769, 3, 36, -1, 1023 }, /* 39 41 MB * */
{ 531, 4, 39, -1, 532 }, /* 40 40 MB */
{ 577, 2, 36, -1, 1023 }, /* 41 20 MB */
{ 654, 2, 32, -1, 674 }, /* 42 20 MB */
{ 923, 5, 36, -1, 1023 }, /* 43 81 MB */
{ 531, 8, 39, -1, 532 } /* 44 81 MB */
};
#ifdef ENABLE_PS1_HDC_LOG
int ps1_hdc_do_log = ENABLE_PS1_HDC_LOG;
static void
ps1_hdc_log(const char *fmt, ...)
{
va_list ap;
if (ps1_hdc_do_log)
{
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
#define ps1_hdc_log(fmt, ...)
#endif
static void
hdc_set_callback(hdc_t *dev, uint64_t callback)
{
if (!dev) {
return;
}
if (callback) {
dev->callback = callback;
timer_set_delay_u64(&dev->timer, dev->callback);
} else {
dev->callback = 0;
timer_disable(&dev->timer);
}
}
/* FIXME: we should use the disk/hdd_table.c code with custom tables! */
static int
ibm_drive_type(drive_t *drive)
{
const geom_t *ptr;
int i;
for (i = 0; i < (sizeof(ibm_type_table) / sizeof(geom_t)); i++) {
ptr = &ibm_type_table[i];
if ((drive->tracks == ptr->cyl) &&
(drive->hpc == ptr->hpc) && (drive->spt == ptr->spt)) return(i);
}
return(HDC_TYPE_USER);
}
static void
set_intr(hdc_t *dev, int raise)
{
if (raise) {
dev->status |= ASR_INT_REQ;
if (dev->ctrl & ACR_INT_EN)
picint(1 << dev->irq);
} else {
dev->status &= ~ASR_INT_REQ;
picintc(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) {
ps1_hdc_log("HDC: get_sector: wrong cylinder %d/%d\n",
drive->cur_cyl, dev->track);
dev->ssb.wrong_cyl = 1;
return(1);
}
if (dev->head >= drive->hpc) {
ps1_hdc_log("HDC: get_sector: past end of heads\n");
dev->ssb.cylinder_err = 1;
return(1);
}
if (dev->sector > drive->spt) {
ps1_hdc_log("HDC: get_sector: past end of sectors\n");
dev->ssb.mark_not_found = 1;
return(1);
}
/* Calculate logical address (block number) of desired sector. */
*addr = ((((off64_t) dev->track*drive->hpc) + \
dev->head)*drive->spt) + dev->sector - 1;
return(0);
}
static void
next_sector(hdc_t *dev, drive_t *drive)
{
if (++dev->sector > drive->spt) {
dev->sector = 1;
if (++dev->head >= drive->hpc) {
dev->head = 0;
dev->track++;
if (++drive->cur_cyl >= drive->tracks) {
drive->cur_cyl = drive->tracks-1;
dev->ssb.cylinder_err = 1;
}
}
}
}
/* Finish up. Repeated all over, so a function it is now. */
static void
do_finish(hdc_t *dev)
{
dev->state = STATE_IDLE;
dev->attn &= ~(ATT_CCB | ATT_DATA);
dev->status = 0x00;
set_intr(dev, 1);
}
/* Seek to a cylinder. */
static int
do_seek(hdc_t *dev, drive_t *drive, uint16_t cyl)
{
if (cyl >= drive->tracks) {
dev->ssb.cylinder_err = 1;
return(1);
}
dev->track = cyl;
drive->cur_cyl = dev->track;
return(0);
}
/* Format a track or an entire drive. */
static void
do_format(hdc_t *dev, drive_t *drive, ccb_t *ccb)
{
int start_cyl, end_cyl;
int intr = 0, val;
off64_t addr;
#if 0
fcb_t *fcb;
#endif
/* Get the parameters from the CCB. */
if (ccb->cmd == CMD_FORMAT_DRIVE) {
start_cyl = 0;
end_cyl = drive->tracks;
} else {
start_cyl = (ccb->cyl_low | (ccb->cyl_high << 8));
end_cyl = start_cyl + 1;
}
switch (dev->state) {
case STATE_IDLE:
/* Ready to transfer the FCB data in. */
dev->state = STATE_RDATA;
dev->buf_idx = 0;
dev->buf_ptr = dev->data;
dev->buf_len = ccb->count * sizeof(fcb_t);
if (dev->buf_len & 1)
dev->buf_len++; /* must be even */
/* Enable for PIO or DMA, as needed. */
#if NOT_USED
if (dev->ctrl & ACR_DMA_EN)
hdc_set_callback(dev, HDC_TIME);
else
#endif
dev->status |= ASR_DATA_REQ;
break;
case STATE_RDATA:
/* Perform DMA. */
while (dev->buf_idx < dev->buf_len) {
val = dma_channel_read(dev->dma);
if (val == DMA_NODATA) {
dev->intstat |= ISR_EQUIP_CHECK;
dev->ssb.need_reset = 1;
intr = 1;
break;
}
dev->buf_ptr[dev->buf_idx] = (val & 0xff);
dev->buf_idx++;
}
dev->state = STATE_RDONE;
hdc_set_callback(dev, HDC_TIME);
break;
case STATE_RDONE:
if (! (dev->ctrl & ACR_DMA_EN))
dev->status &= ~ASR_DATA_REQ;
/* Point to the FCB we got. */
#if 0
fcb = (fcb_t *)dev->data;
#endif
dev->state = STATE_FINIT;
/*FALLTHROUGH*/
case STATE_FINIT:
do_fmt:
/* Activate the status icon. */
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 1);
/* Seek to cylinder. */
if (do_seek(dev, drive, start_cyl)) {
intr = 1;
break;
}
dev->head = ccb->head;
dev->sector = 1;
/* Get address of sector to write. */
if (get_sector(dev, drive, &addr)) {
intr = 1;
break;
}
/*
* For now, we don't use the info from
* the FCB, although we should at least
* use it's "filler byte" value...
*/
#if 0
hdd_image_zero_ex(drive->hdd_num, addr, fcb->fill, drive->spt);
#else
hdd_image_zero(drive->hdd_num, addr, drive->spt);
#endif
/* Done with this track. */
dev->state = STATE_FDONE;
/*FALLTHROUGH*/
case STATE_FDONE:
/* One more track done. */
if (++start_cyl == end_cyl) {
intr = 1;
break;
}
/* De-activate the status icon. */
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
/* This saves us a LOT of code. */
dev->state = STATE_FINIT;
goto do_fmt;
}
/* If we errored out, go back idle. */
if (intr) {
/* De-activate the status icon. */
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
do_finish(dev);
}
}
/* Execute the CCB we just received. */
static void
hdc_callback(void *priv)
{
hdc_t *dev = (hdc_t *)priv;
ccb_t *ccb = &dev->ccb;
drive_t *drive;
off64_t addr;
int no_data = 0;
int val;
/* Cancel timer. */
dev->callback = 0;
/* Clear the SSB error bits. */
dev->ssb.track_0 = 0;
dev->ssb.cylinder_err = 0;
dev->ssb.write_fault = 0;
dev->ssb.seek_end = 0;
dev->ssb.not_ready = 0;
dev->ssb.id_not_found = 0;
dev->ssb.wrong_cyl = 0;
dev->ssb.all_bit_set = 0;
dev->ssb.mark_not_found = 0;
dev->ssb.ecc_crc_err = 0;
dev->ssb.ecc_crc_field = 0;
dev->ssb.valid = 1;
/* We really only support one drive, but ohwell. */
drive = &dev->drives[0];
switch (ccb->cmd) {
case CMD_READ_VERIFY:
no_data = 1;
/*FALLTHROUGH*/
case CMD_READ_SECTORS:
if (! drive->present) {
dev->ssb.not_ready = 1;
do_finish(dev);
return;
}
switch (dev->state) {
case STATE_IDLE:
/* Seek to cylinder if requested. */
if (ccb->auto_seek) {
if (do_seek(dev, drive,
(ccb->cyl_low|(ccb->cyl_high<<8)))) {
do_finish(dev);
return;
}
}
dev->head = ccb->head;
dev->sector = ccb->sector;
/* Get sector count and size. */
dev->count = (int)ccb->count;
dev->buf_len = (128 << dev->ssb.sect_size);
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);
do_finish(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. */
hdc_set_callback(dev, HDC_TIME);
} else {
if (dev->ctrl & ACR_DMA_EN) {
/* DMA enabled. */
dev->buf_ptr = dev->sector_buf;
hdc_set_callback(dev, HDC_TIME);
} else {
/* No DMA, do PIO. */
dev->status |= (ASR_DATA_REQ|ASR_DIR);
/* Copy from sector to data. */
memcpy(dev->data,
dev->sector_buf,
dev->buf_len);
dev->buf_ptr = dev->data;
}
}
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) {
ps1_hdc_log("HDC: CMD_READ_SECTORS out of data (idx=%d, len=%d)!\n", dev->buf_idx, dev->buf_len);
/* De-activate the status icon. */
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
dev->intstat |= ISR_EQUIP_CHECK;
dev->ssb.need_reset = 1;
do_finish(dev);
return;
}
dev->buf_idx++;
}
}
dev->state = STATE_SDONE;
hdc_set_callback(dev, HDC_TIME);
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);
if (! (dev->ctrl & ACR_DMA_EN))
dev->status &= ~(ASR_DATA_REQ|ASR_DIR);
dev->ssb.cmd_syndrome = 0xD4;
do_finish(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;
case CMD_READ_EXT: /* READ_EXT */
case CMD_READ_ID: /* READ_ID */
if (! drive->present) {
dev->ssb.not_ready = 1;
do_finish(dev);
return;
}
dev->intstat |= ISR_INVALID_CMD;
do_finish(dev);
break;
case CMD_RECALIBRATE: /* RECALIBRATE */
if (drive->present) {
dev->track = drive->cur_cyl = 0;
} else {
dev->ssb.not_ready = 1;
dev->intstat |= ISR_TERMINATION;
}
do_finish(dev);
break;
case CMD_WRITE_VERIFY:
no_data = 1;
/*FALLTHROUGH*/
case CMD_WRITE_SECTORS:
if (! drive->present) {
dev->ssb.not_ready = 1;
do_finish(dev);
return;
}
switch (dev->state) {
case STATE_IDLE:
/* Seek to cylinder if requested. */
if (ccb->auto_seek) {
if (do_seek(dev, drive,
(ccb->cyl_low|(ccb->cyl_high<<8)))) {
do_finish(dev);
return;
}
}
dev->head = ccb->head;
dev->sector = ccb->sector;
/* Get sector count and size. */
dev->count = (int)ccb->count;
dev->buf_len = (128 << dev->ssb.sect_size);
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. */
hdc_set_callback(dev, HDC_TIME);
} else {
if (dev->ctrl & ACR_DMA_EN) {
/* DMA enabled. */
dev->buf_ptr = dev->sector_buf;
hdc_set_callback(dev, HDC_TIME);
} else {
/* No DMA, do PIO. */
dev->buf_ptr = dev->data;
dev->status |= ASR_DATA_REQ;
}
}
break;
case STATE_RDATA:
if (! no_data) {
/* Perform DMA. */
while (dev->buf_idx < dev->buf_len) {
val = dma_channel_read(dev->dma);
if (val == DMA_NODATA) {
ps1_hdc_log("HDC: CMD_WRITE_SECTORS out of data (idx=%d, len=%d)!\n", dev->buf_idx, dev->buf_len);
/* De-activate the status icon. */
ui_sb_update_icon(SB_HDD|HDD_BUS_XTA, 0);
dev->intstat |= ISR_EQUIP_CHECK;
dev->ssb.need_reset = 1;
do_finish(dev);
return;
}
dev->buf_ptr[dev->buf_idx] = (val & 0xff);
dev->buf_idx++;
}
}
dev->state = STATE_RDONE;
hdc_set_callback(dev, HDC_TIME);
break;
case STATE_RDONE:
/* Copy from data to sector if PIO. */
if (! (dev->ctrl & ACR_DMA_EN))
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);
do_finish(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);
if (! (dev->ctrl & ACR_DMA_EN))
dev->status &= ~ASR_DATA_REQ;
dev->ssb.cmd_syndrome = 0xD4;
do_finish(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:
do_format(dev, drive, ccb);
break;
case CMD_SEEK:
if (! drive->present) {
dev->ssb.not_ready = 1;
do_finish(dev);
return;
}
if (ccb->ec_p == 1) {
/* Park the heads. */
val = do_seek(dev, drive, drive->tracks-1);
} else {
/* Seek to cylinder. */
val = do_seek(dev, drive,
(ccb->cyl_low|(ccb->cyl_high<<8)));
}
if (! val)
dev->ssb.seek_end = 1;
do_finish(dev);
break;
default:
dev->intstat |= ISR_INVALID_CMD;
do_finish(dev);
}
}
/* Prepare to send the SSB block. */
static void
hdc_send_ssb(hdc_t *dev)
{
drive_t *drive;
/* We only support one drive, really, but ohwell. */
drive = &dev->drives[0];
if (! dev->ssb.valid) {
/* Create a valid SSB. */
memset(&dev->ssb, 0x00, sizeof(dev->ssb));
dev->ssb.sect_size = 0x02; /* 512 bytes */
dev->ssb.drive_type = drive->type;
}
/* Update position fields. */
dev->ssb.track_0 = !!(dev->track == 0);
dev->ssb.last_cyl_low = dev->ssb.curr_cyl_low;
dev->ssb.last_cyl_high = dev->ssb.curr_cyl_high;
dev->ssb.last_head = dev->ssb.curr_head;
dev->ssb.curr_cyl_high = ((dev->track >> 8) & 0x03);
dev->ssb.curr_cyl_low = (dev->track & 0xff);
dev->ssb.curr_head = (dev->head & 0x0f);
dev->ssb.headsel_state = dev->ssb.curr_head;
dev->ssb.last_sect = dev->sector;
/* We abuse an unused MBZ bit, so clear it. */
dev->ssb.valid = 0;
/* Set up the transfer buffer for the SSB. */
dev->buf_idx = 0;
dev->buf_len = sizeof(dev->ssb);
dev->buf_ptr = (uint8_t *)&dev->ssb;
/* Done with the SSB. */
dev->attn &= ~ATT_SSB;
}
/* 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;
/* TRM: tell system board we are alive. */
*dev->reg_91 |= 0x01;
switch (port & 7) {
case 0: /* DATA register */
if (dev->state == STATE_SDATA) {
if (dev->buf_idx > dev->buf_len) {
ps1_hdc_log("HDC: read with empty buffer!\n");
dev->state = STATE_IDLE;
dev->intstat |= ISR_INVALID_CMD;
dev->status &= (ASR_TX_EN|ASR_DATA_REQ|ASR_DIR);
set_intr(dev, 1);
break;
}
ret = dev->buf_ptr[dev->buf_idx];
if (++dev->buf_idx == dev->buf_len) {
/* Data block sent OK. */
dev->status &= ~(ASR_TX_EN|ASR_DATA_REQ|ASR_DIR);
dev->state = STATE_IDLE;
}
}
break;
case 2: /* ASR */
ret = dev->status;
break;
case 4: /* ISR */
ret = dev->intstat;
dev->intstat = 0x00;
break;
}
return(ret);
}
static void
hdc_write(uint16_t port, uint8_t val, void *priv)
{
hdc_t *dev = (hdc_t *)priv;
/* TRM: tell system board we are alive. */
*dev->reg_91 |= 0x01;
switch (port & 7) {
case 0: /* DATA register */
if (dev->state == STATE_RDATA) {
if (dev->buf_idx >= dev->buf_len) {
ps1_hdc_log("HDC: write with full buffer!\n");
dev->intstat |= ISR_INVALID_CMD;
dev->status &= ~ASR_DATA_REQ;
set_intr(dev, 1);
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 &= ~ASR_DATA_REQ;
dev->state = STATE_IDLE;
/* If we were receiving a CCB, execute it. */
if (dev->attn & ATT_CCB) {
/*
* If we were already busy with
* a CCB, then it must have had
* some new data using PIO.
*/
if (dev->status & ASR_BUSY)
dev->state = STATE_RDONE;
else
dev->status |= ASR_BUSY;
/* Schedule command execution. */
hdc_set_callback(dev, HDC_TIME);
}
}
}
break;
case 2: /* ACR */
dev->ctrl = val;
if (val & ACR_INT_EN)
set_intr(dev, 0); /* clear IRQ */
if (dev->reset != 0) {
if (++dev->reset == 3) {
dev->reset = 0;
set_intr(dev, 1);
}
break;
}
if (val & ACR_RESET)
dev->reset = 1;
break;
case 4: /* ATTN */
dev->status &= ~ASR_INT_REQ;
if (val & ATT_DATA) {
/* Dunno. Start PIO/DMA now? */
}
if (val & ATT_SSB) {
if (dev->attn & ATT_CCB) {
/* Hey now, we're still busy for you! */
dev->intstat |= ISR_INVALID_CMD;
set_intr(dev, 1);
break;
}
/* OK, prepare for sending an SSB. */
dev->attn |= ATT_SSB;
/* Grab or initialize an SSB to send. */
hdc_send_ssb(dev);
dev->state = STATE_SDATA;
dev->status |= (ASR_TX_EN|ASR_DATA_REQ|ASR_DIR);
set_intr(dev, 1);
}
if (val & ATT_CCB) {
dev->attn |= ATT_CCB;
/* Set up the transfer buffer for a CCB. */
dev->buf_idx = 0;
dev->buf_len = sizeof(dev->ccb);
dev->buf_ptr = (uint8_t *)&dev->ccb;
dev->state = STATE_RDATA;
dev->status |= ASR_DATA_REQ;
set_intr(dev, 1);
}
break;
}
}
static void *
ps1_hdc_init(const device_t *info)
{
drive_t *drive;
hdc_t *dev;
int c, i;
/* Allocate and initialize device block. */
dev = malloc(sizeof(hdc_t));
memset(dev, 0x00, sizeof(hdc_t));
/* Set up controller parameters for PS/1 2011. */
dev->base = 0x0320;
dev->irq = 14;
dev->dma = 3;
ps1_hdc_log("HDC: initializing (I/O=%04X, IRQ=%d, DMA=%d)\n",
dev->base, dev->irq, dev->dma);
/* 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 < 1)) {
drive = &dev->drives[hdd[i].xta_channel];
if (! hdd_image_load(i)) {
drive->present = 0;
continue;
}
drive->id = c;
/* 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 "active" parameters until overwritten. */
drive->spt = drive->cfg_spt;
drive->hpc = drive->cfg_hpc;
drive->tracks = drive->cfg_tracks;
drive->type = ibm_drive_type(drive);
drive->hdd_num = i;
drive->present = 1;
ps1_hdc_log("HDC: drive%d (type %d: cyl=%d,hd=%d,spt=%d), disk %d\n",
hdd[i].xta_channel, drive->type,
drive->tracks, drive->hpc, drive->spt, i);
if (++c > 1) break;
}
}
/* Sectors are 1-based. */
dev->sector = 1;
/* Enable the I/O block. */
io_sethandler(dev->base, 5,
hdc_read,NULL,NULL, hdc_write,NULL,NULL, dev);
/* Create a timer for command delays. */
timer_add(&dev->timer, hdc_callback, dev, 0);
return(dev);
}
static void
ps1_hdc_close(void *priv)
{
hdc_t *dev = (hdc_t *)priv;
drive_t *drive;
int d;
/* Remove the I/O handler. */
io_removehandler(dev->base, 5,
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];
if (drive->present)
hdd_image_close(drive->hdd_num);
}
/* Release the device. */
free(dev);
}
const device_t ps1_hdc_device = {
"PS/1 2011 Fixed Disk Controller",
DEVICE_ISA | DEVICE_PS2,
0,
ps1_hdc_init, ps1_hdc_close, NULL,
{ NULL }, NULL, NULL,
NULL
};
/*
* Very nasty.
*
* The PS/1 systems employ a feedback system where external
* cards let the system know they were 'addressed' by setting
* their Card Selected Flag (CSF) in register 0x0091. Driver
* software can test this register to see if they are talking
* to hardware or not.
*
* This means, that we must somehow do the same, and yes, I
* agree that the current solution is nasty.
*/
void
ps1_hdc_inform(void *priv, uint8_t *reg_91)
{
hdc_t *dev = (hdc_t *)priv;
dev->reg_91 = reg_91;
}