simh-testsetgenerator/AltairZ80/s100_hdc1001.c

731 lines
28 KiB
C

/*************************************************************************
* *
* Copyright (c) 2007-2020 Howard M. Harte. *
* https://github.com/hharte *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND *
* NONINFRINGEMENT. IN NO EVENT SHALL HOWARD M. HARTE BE LIABLE FOR ANY *
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, *
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE *
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
* *
* Except as contained in this notice, the name of Howard M. Harte shall *
* not be used in advertising or otherwise to promote the sale, use or *
* other dealings in this Software without prior written authorization *
* Howard M. Harte. *
* *
* SIMH Interface based on altairz80_hdsk.c, by Peter Schorn. *
* *
* Module Description: *
* Advanced Digital Corporation (ADC) HDC-1001 Hard Disk Controller *
* module for SIMH. The HDC-1001 controller uses the standard IDE/ATA *
* task-file, so this controller should be compatible with other con- *
* trollers that use IDE, like the GIDE interface. *
* *
*************************************************************************/
#include "altairz80_defs.h"
#include "sim_imd.h"
/* Debug flags */
#define ERROR_MSG (1 << 0)
#define SEEK_MSG (1 << 1)
#define CMD_MSG (1 << 2)
#define RD_DATA_MSG (1 << 3)
#define WR_DATA_MSG (1 << 4)
#define VERBOSE_MSG (1 << 5)
#define HDC1001_MAX_DRIVES 4 /* Maximum number of drives supported */
#define HDC1001_MAX_SECLEN 512 /* Maximum of 512 bytes per sector */
#define HDC1001_FORMAT_FILL_BYTE 0xe5 /* Real controller uses 0, but we
choose 0xe5 so the disk shows
up as blank under CP/M. */
#define HDC1001_MAX_CYLS 1024
#define HDC1001_MAX_HEADS 8
#define HDC1001_MAX_SPT 256
#define DEV_NAME "ADCHD"
/* Task File Register Offsets */
#define TF_DATA 0
#define TF_ERROR 1 /* Read */
#define TF_PRECOMP 1 /* Write */
#define TF_SECNT 2
#define TF_SECNO 3
#define TF_CYLLO 4
#define TF_CYLHI 5
#define TF_SDH 6
#define TF_STATUS 7 /* Read */
#define TF_CMD 7 /* Write */
#define HDC1001_STATUS_BUSY (1 << 7)
#define HDC1001_STATUS_READY (1 << 6)
#define HDC1001_STATUS_WRITE_FAULT (1 << 5)
#define HDC1001_STATUS_SEEK_COMPL (1 << 4)
#define HDC1001_STATUS_DRQ (1 << 3)
#define HDC1001_STATUS_ERROR (1 << 0)
#define HDC1001_ERROR_ID_NOT_FOUND (1 << 4)
#define HDC1001_CMD_RESTORE 0x10
#define HDC1001_CMD_READ_SECT 0x20
#define HDC1001_CMD_WRITE_SECT 0x30
#define HDC1001_CMD_FORMAT_TRK 0x50
#define HDC1001_CMD_SEEK 0x70
#define HDC1001_RWOPT_DMA (1 << 3)
#define HDC1001_RWOPT_MULTI (1 << 2)
#define HDC1001_RWOPT_LONG (1 << 3)
static char* hdc1001_reg_rd_str[] = {
"DATA ",
"ERROR ",
"SECNT ",
"SECNO ",
"CYLLO ",
"CYLHI ",
"SDH ",
"STATUS "
};
static char* hdc1001_reg_wr_str[] = {
"DATA ",
"PRECOMP",
"SECNT ",
"SECNO ",
"CYLLO ",
"CYLHI ",
"SDH ",
"COMMAND"
};
typedef struct {
UNIT *uptr;
uint8 readonly; /* Drive is read-only? */
uint16 sectsize; /* sector size */
uint16 nsectors; /* number of sectors/track */
uint16 nheads; /* number of heads */
uint16 ncyls; /* number of cylinders */
uint16 cur_cyl; /* Current cylinder */
uint8 cur_head; /* Current Head */
uint8 cur_sect; /* current starting sector of transfer */
uint16 cur_sectsize;/* Current sector size in SDH register */
uint16 xfr_nsects; /* Number of sectors to transfer */
uint8 ready; /* Is drive ready? */
} HDC1001_DRIVE_INFO;
typedef struct {
PNP_INFO pnp; /* Plug and Play */
uint8 sel_drive; /* Currently selected drive */
uint8 taskfile[8]; /* ATA Task File Registers */
uint8 status_reg; /* HDC-1001 Status Register */
uint8 error_reg; /* HDC-1001 Status Register */
uint8 retries; /* Number of retries to attempt */
uint8 ndrives; /* Number of drives attached to the controller */
uint8 sectbuf[HDC1001_MAX_SECLEN];
uint16 secbuf_index;
HDC1001_DRIVE_INFO drive[HDC1001_MAX_DRIVES];
} HDC1001_INFO;
static HDC1001_INFO hdc1001_info_data = { { 0x0, 0, 0xE0, 8 } };
static HDC1001_INFO *hdc1001_info = &hdc1001_info_data;
extern uint32 PCX;
extern t_stat set_iobase(UNIT *uptr, int32 val, CONST char *cptr, void *desc);
extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, CONST void *desc);
extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
int32 (*routine)(const int32, const int32, const int32), const char* name, uint8 unmap);
extern int32 find_unit_index(UNIT *uptr);
#define UNIT_V_HDC1001_VERBOSE (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages */
#define UNIT_HDC1001_VERBOSE (1 << UNIT_V_HDC1001_VERBOSE)
#define HDC1001_CAPACITY (512*4*16*512) /* Default Disk Capacity Quantum 2020 */
static t_stat hdc1001_reset(DEVICE *hdc1001_dev);
static t_stat hdc1001_attach(UNIT *uptr, CONST char *cptr);
static t_stat hdc1001_detach(UNIT *uptr);
static t_stat hdc1001_unit_set_geometry(UNIT* uptr, int32 value, CONST char* cptr, void* desc);
static t_stat hdc1001_unit_show_geometry(FILE* st, UNIT* uptr, int32 val, CONST void* desc);
static int32 hdc1001dev(const int32 port, const int32 io, const int32 data);
static uint8 HDC1001_Read(const uint32 Addr);
static uint8 HDC1001_Write(const uint32 Addr, uint8 cData);
static t_stat HDC1001_doCommand(void);
static const char* hdc1001_description(DEVICE *dptr);
static UNIT hdc1001_unit[] = {
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDC1001_CAPACITY) },
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDC1001_CAPACITY) },
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDC1001_CAPACITY) },
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDC1001_CAPACITY) }
};
static REG hdc1001_reg[] = {
{ HRDATAD (TF_ERROR, hdc1001_info_data.error_reg, 8, "Taskfile Error Register"), },
{ HRDATAD (TF_STATUS, hdc1001_info_data.status_reg, 8, "Taskfile Status Register"), },
{ HRDATAD (TF_DATA, hdc1001_info_data.taskfile[TF_DATA], 8, "Taskfile Data Register"), },
{ HRDATAD (TF_PRECOMP, hdc1001_info_data.taskfile[TF_PRECOMP], 8, "Taskfile Precomp Register"), },
{ HRDATAD (TF_SECNT, hdc1001_info_data.taskfile[TF_SECNT], 8, "Taskfile Sector Count Register"), },
{ HRDATAD (TF_SECNO, hdc1001_info_data.taskfile[TF_SECNO], 8, "Taskfile Sector Number Register"), },
{ HRDATAD (TF_CYLLO, hdc1001_info_data.taskfile[TF_CYLLO], 8, "Taskfile Cylinder Low Register"), },
{ HRDATAD (TF_CYLHI, hdc1001_info_data.taskfile[TF_CYLHI], 8, "Taskfile Cylinder High Register"), },
{ HRDATAD (TF_SDH, hdc1001_info_data.taskfile[TF_SDH], 8, "Taskfile SDH Register"), },
{ HRDATAD (TF_CMD, hdc1001_info_data.taskfile[TF_CMD], 8, "Taskfile Command Register"), },
{ NULL }
};
#define HDC1001_NAME "ADC HDC-1001 Hard Disk Controller"
static const char* hdc1001_description(DEVICE *dptr) {
return HDC1001_NAME;
}
static MTAB hdc1001_mod[] = {
{ MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE",
&set_iobase, &show_iobase, NULL, "Sets disk controller I/O base address" },
{ MTAB_XTD|MTAB_VUN|MTAB_VALR, 0, "GEOMETRY", "GEOMETRY",
&hdc1001_unit_set_geometry, &hdc1001_unit_show_geometry, NULL,
"Set disk geometry C:nnnn/H:n/S:nnn/N:nnnn" },
{ 0 }
};
/* Debug Flags */
static DEBTAB hdc1001_dt[] = {
{ "ERROR", ERROR_MSG, "Error messages" },
{ "SEEK", SEEK_MSG, "Seek messages" },
{ "CMD", CMD_MSG, "Command messages" },
{ "READ", RD_DATA_MSG, "Read messages" },
{ "WRITE", WR_DATA_MSG, "Write messages" },
{ "VERBOSE", VERBOSE_MSG, "Verbose messages" },
{ NULL, 0 }
};
DEVICE hdc1001_dev = {
DEV_NAME, hdc1001_unit, hdc1001_reg, hdc1001_mod,
HDC1001_MAX_DRIVES, 10, 31, 1, HDC1001_MAX_DRIVES, HDC1001_MAX_DRIVES,
NULL, NULL, &hdc1001_reset,
NULL, &hdc1001_attach, &hdc1001_detach,
&hdc1001_info_data, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), ERROR_MSG,
hdc1001_dt, NULL, NULL, NULL, NULL, NULL, &hdc1001_description
};
/* Reset routine */
static t_stat hdc1001_reset(DEVICE *dptr)
{
PNP_INFO *pnp = (PNP_INFO *)dptr->ctxt;
if(dptr->flags & DEV_DIS) { /* Disconnect I/O Ports */
sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &hdc1001dev, "hdc1001dev", TRUE);
} else {
/* Connect HDC1001 at base address */
if(sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &hdc1001dev, "hdc1001dev", FALSE) != 0) {
sim_printf("%s: error mapping I/O resource at 0x%04x\n", __FUNCTION__, pnp->io_base);
return SCPE_ARG;
}
}
hdc1001_info->status_reg = 0;
hdc1001_info->error_reg = 0;
hdc1001_info->sel_drive = 0;
return SCPE_OK;
}
/* Attach routine */
static t_stat hdc1001_attach(UNIT *uptr, CONST char *cptr)
{
t_stat r = SCPE_OK;
HDC1001_DRIVE_INFO *pDrive;
int i = 0;
i = find_unit_index(uptr);
if (i == -1) {
return (SCPE_IERR);
}
pDrive = &hdc1001_info->drive[i];
/* Defaults for the Quantum 2020 Drive */
pDrive->ready = 0;
if (pDrive->ncyls == 0) {
/* If geometry was not specified, default to Quantun 2020 */
pDrive->ncyls = 512;
pDrive->nheads = 4;
pDrive->nsectors = 16;
pDrive->sectsize = 512;
}
r = attach_unit(uptr, cptr); /* attach unit */
if ( r != SCPE_OK) /* error? */
return r;
/* Determine length of this disk */
if(sim_fsize(uptr->fileref) != 0) {
uptr->capac = sim_fsize(uptr->fileref);
} else {
uptr->capac = (pDrive->ncyls * pDrive->nsectors * pDrive->nheads * pDrive->sectsize);
}
pDrive->uptr = uptr;
/* Default for new file is DSK */
uptr->u3 = IMAGE_TYPE_DSK;
if(uptr->capac > 0) {
r = assignDiskType(uptr);
if (r != SCPE_OK) {
hdc1001_detach(uptr);
return r;
}
}
sim_debug(VERBOSE_MSG, &hdc1001_dev, DEV_NAME "%d, attached to '%s', type=DSK, len=%d\n",
i, cptr, uptr->capac);
pDrive->readonly = (uptr->flags & UNIT_RO) ? 1 : 0;
hdc1001_info->error_reg = 0;
pDrive->ready = 1;
return SCPE_OK;
}
/* Detach routine */
static t_stat hdc1001_detach(UNIT *uptr)
{
HDC1001_DRIVE_INFO *pDrive;
t_stat r;
int8 i;
i = find_unit_index(uptr);
if (i == -1) {
return (SCPE_IERR);
}
pDrive = &hdc1001_info->drive[i];
pDrive->ready = 0;
sim_debug(VERBOSE_MSG, &hdc1001_dev, "Detach " DEV_NAME "%d\n", i);
r = detach_unit(uptr); /* detach unit */
if ( r != SCPE_OK)
return r;
return SCPE_OK;
}
/* Set geometry of the disk drive */
static t_stat hdc1001_unit_set_geometry(UNIT* uptr, int32 value, CONST char* cptr, void* desc)
{
HDC1001_DRIVE_INFO* pDrive;
int8 i;
int32 result;
uint16 newCyls, newHeads, newSPT, newSecLen;
i = find_unit_index(uptr);
if (i == -1) {
return (SCPE_IERR);
}
pDrive = &hdc1001_info->drive[i];
if (cptr == NULL)
return SCPE_ARG;
result = sscanf(cptr, "C:%hd/H:%hd/S:%hd/N:%hd", &newCyls, &newHeads, &newSPT, &newSecLen);
if (result != 4)
return SCPE_ARG;
/* Validate Cyl, Heads, Sector, Length are valid for the HDC-1001 */
if (newCyls < 1 || newCyls > HDC1001_MAX_CYLS) {
sim_debug(ERROR_MSG, &hdc1001_dev, DEV_NAME "%d: Number of cylinders must be 1-%d.\n",
hdc1001_info->sel_drive, HDC1001_MAX_CYLS);
return SCPE_ARG;
}
if (newHeads < 1 || newHeads > HDC1001_MAX_HEADS) {
sim_debug(ERROR_MSG, &hdc1001_dev, DEV_NAME "%d: Number of heads must be 1-%d.\n",
hdc1001_info->sel_drive, HDC1001_MAX_HEADS);
return SCPE_ARG;
}
if (newSPT < 1 || newSPT > HDC1001_MAX_SPT) {
sim_debug(ERROR_MSG, &hdc1001_dev, DEV_NAME "%d: Number of sectors per track must be 1-%d.\n",
hdc1001_info->sel_drive, HDC1001_MAX_SPT);
return SCPE_ARG;
}
if (newSecLen != 512 && newSecLen != 256 && newSecLen != 128) {
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: Sector length must be 128, 256, or 512.\n",
hdc1001_info->sel_drive);
return SCPE_ARG;
}
pDrive->ncyls = newCyls;
pDrive->nheads = newHeads;
pDrive->nsectors = newSPT;
pDrive->sectsize = newSecLen;
return SCPE_OK;
}
/* Show geometry of the disk drive */
static t_stat hdc1001_unit_show_geometry(FILE* st, UNIT* uptr, int32 val, CONST void* desc)
{
HDC1001_DRIVE_INFO* pDrive;
int8 i;
i = find_unit_index(uptr);
if (i == -1) {
return (SCPE_IERR);
}
pDrive = &hdc1001_info->drive[i];
fprintf(st, "C:%d/H:%d/S:%d/N:%d",
pDrive->ncyls, pDrive->nheads, pDrive->nsectors, pDrive->sectsize);
return SCPE_OK;
}
/* HDC-1001 I/O Dispatch */
static int32 hdc1001dev(const int32 port, const int32 io, const int32 data)
{
if(io) {
HDC1001_Write(port, data);
return 0;
} else {
return(HDC1001_Read(port));
}
}
/* I/O Write to HDC-1001 Task File */
static uint8 HDC1001_Write(const uint32 Addr, uint8 cData)
{
HDC1001_DRIVE_INFO *pDrive;
uint8 cmd;
pDrive = &hdc1001_info->drive[hdc1001_info->sel_drive];
switch(Addr & 0x07) {
case TF_DATA: /* Data FIFO */
hdc1001_info->sectbuf[hdc1001_info->secbuf_index] = cData;
sim_debug(VERBOSE_MSG, &hdc1001_dev,DEV_NAME ": " ADDRESS_FORMAT
" WR TF[DATA 0x%03x]=0x%02x\n", PCX, hdc1001_info->secbuf_index, cData);
hdc1001_info->secbuf_index++;
if (hdc1001_info->secbuf_index == (pDrive->xfr_nsects * pDrive->sectsize)) HDC1001_doCommand();
break;
case TF_SDH:
hdc1001_info->sel_drive = (cData >> 3) & 0x03;
pDrive = &hdc1001_info->drive[hdc1001_info->sel_drive];
switch ((cData >> 5) & 0x03) { /* Sector Size */
case 0:
pDrive->cur_sectsize = 256;
break;
case 1:
pDrive->cur_sectsize = 512;
break;
case 2:
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" Invalid sector size specified in SDH registrer.\n", hdc1001_info->sel_drive, PCX);
pDrive->cur_sectsize = 512;
break;
case 3:
pDrive->cur_sectsize = 128;
break;
}
if (pDrive->sectsize != pDrive->cur_sectsize) {
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" Sector size specified in SDH registrer (0x%x) does not match disk geometry (0x%x.)\n",
hdc1001_info->sel_drive, PCX, pDrive->cur_sectsize, pDrive->sectsize);
}
/* fall through */
case TF_PRECOMP:
case TF_SECNT:
case TF_SECNO:
case TF_CYLLO:
case TF_CYLHI:
hdc1001_info->taskfile[Addr & 0x07] = cData;
sim_debug(VERBOSE_MSG, &hdc1001_dev,DEV_NAME ": " ADDRESS_FORMAT
" WR TF[%s]=0x%02x\n", PCX, hdc1001_reg_wr_str[Addr & 0x7], cData);
break;
case TF_CMD:
{
uint8 rwopts;
hdc1001_info->secbuf_index = 0;
hdc1001_info->taskfile[TF_CMD] = cData;
hdc1001_info->status_reg &= ~HDC1001_STATUS_ERROR; /* Clear error bit in status register. */
pDrive->cur_cyl = hdc1001_info->taskfile[TF_CYLLO] | (hdc1001_info->taskfile[TF_CYLHI] << 8);
cmd = hdc1001_info->taskfile[TF_CMD] & 0x70;
rwopts = hdc1001_info->taskfile[TF_CMD] & 0xE;
if (rwopts & HDC1001_RWOPT_MULTI) {
pDrive->xfr_nsects = hdc1001_info->taskfile[TF_SECNT];
sim_debug(ERROR_MSG, &hdc1001_dev, DEV_NAME "%d: " ADDRESS_FORMAT
" Multi-sector Read/Write have not been verified.\n", hdc1001_info->sel_drive, PCX);
}
else {
pDrive->xfr_nsects = 1;
}
/* Everything except Write commands are executed immediately. */
if (cmd != HDC1001_CMD_WRITE_SECT) {
HDC1001_doCommand();
}
else {
/* Writes will be executed once the proper number of bytes
are written to the DATA FIFO. */
hdc1001_info->secbuf_index = 0;
}
}
}
return 0;
}
/* I/O Read from HDC-1001 Task File */
static uint8 HDC1001_Read(const uint32 Addr)
{
HDC1001_DRIVE_INFO* pDrive;
uint8 cData = 0xFF;
pDrive = &hdc1001_info->drive[hdc1001_info->sel_drive];
cData = hdc1001_info->status_reg |= (pDrive->ready ? HDC1001_STATUS_READY : 0);
switch (Addr & 0x07) {
case TF_DATA: /* Data FIFO */
cData = hdc1001_info->sectbuf[hdc1001_info->secbuf_index];
sim_debug(VERBOSE_MSG, &hdc1001_dev,DEV_NAME ": " ADDRESS_FORMAT
" RD TF[DATA 0x%03x]=0x%02x\n", PCX, hdc1001_info->secbuf_index, cData);
hdc1001_info->secbuf_index++;
if (hdc1001_info->secbuf_index > HDC1001_MAX_SECLEN) hdc1001_info->secbuf_index = 0;
break;
case TF_SDH:
case TF_SECNT:
case TF_SECNO:
case TF_CYLLO:
case TF_CYLHI:
cData = hdc1001_info->taskfile[Addr & 0x07];
sim_debug(VERBOSE_MSG, &hdc1001_dev,DEV_NAME ": " ADDRESS_FORMAT
" RD TF[%s]=0x%02x\n", PCX, hdc1001_reg_rd_str[Addr & 0x7], cData);
break;
case TF_ERROR:
cData = hdc1001_info->error_reg;
sim_debug(VERBOSE_MSG, &hdc1001_dev,DEV_NAME ": " ADDRESS_FORMAT
" RD TF[ERROR]=0x%02x\n", PCX, cData);
break;
case TF_STATUS:
cData = hdc1001_info->status_reg;
sim_debug(VERBOSE_MSG, &hdc1001_dev,DEV_NAME ": " ADDRESS_FORMAT
" RD TF[STATUS]=0x%02x\n", PCX, cData);
break;
default:
break;
}
return (cData);
}
/* Validate that Cyl, Head, Sector, Sector Length are valid for the current
* disk drive geometry.
*/
static int HDC1001_Validate_CHSN(HDC1001_DRIVE_INFO* pDrive)
{
int status = SCPE_OK;
/* Check to make sure we're operating on a valid C/H/S/N. */
if ((pDrive->cur_cyl >= pDrive->ncyls) ||
(pDrive->cur_head >= pDrive->nheads) ||
(pDrive->cur_sect >= pDrive->nsectors) ||
(pDrive->cur_sectsize != pDrive->sectsize))
{
/* Set error bit in status register. */
hdc1001_info->status_reg |= HDC1001_STATUS_ERROR;
/* Set ID_NOT_FOUND bit in error register. */
hdc1001_info->error_reg |= HDC1001_ERROR_ID_NOT_FOUND;
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" ID Not Found (check disk geometry.)\n", hdc1001_info->sel_drive, PCX);
status = SCPE_IOERR;
}
else {
/* Clear ID_NOT_FOUND bit in error register. */
hdc1001_info->error_reg &= ~HDC1001_ERROR_ID_NOT_FOUND;
}
return (status);
}
/* Perform HDC-1001 Command */
static t_stat HDC1001_doCommand(void)
{
HDC1001_DRIVE_INFO* pDrive;
uint8 cmd;
cmd = hdc1001_info->taskfile[TF_CMD] & 0x70;
pDrive = &hdc1001_info->drive[hdc1001_info->sel_drive];
pDrive->cur_head = hdc1001_info->taskfile[TF_SDH] & 0x07;
pDrive->cur_sect = hdc1001_info->taskfile[TF_SECNO];
if(pDrive->ready) {
/* Perform command */
switch(cmd) {
case HDC1001_CMD_RESTORE:
pDrive->cur_cyl = 0;
sim_debug(SEEK_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" RESTORE\n", hdc1001_info->sel_drive, PCX);
hdc1001_info->status_reg |= HDC1001_STATUS_SEEK_COMPL;
break;
case HDC1001_CMD_SEEK:
if (pDrive->cur_cyl >= pDrive->ncyls) {
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" SEEK ERROR %d not found\n", hdc1001_info->sel_drive, PCX, pDrive->cur_cyl);
pDrive->cur_cyl = pDrive->ncyls - 1;
}
else {
sim_debug(SEEK_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" SEEK %d\n", hdc1001_info->sel_drive, PCX, pDrive->cur_cyl);
}
hdc1001_info->status_reg |= HDC1001_STATUS_SEEK_COMPL;
break;
case HDC1001_CMD_WRITE_SECT:
/* If drive is read-only, signal a write fault. */
if (pDrive->readonly) {
hdc1001_info->status_reg |= HDC1001_STATUS_ERROR;
hdc1001_info->status_reg |= HDC1001_STATUS_WRITE_FAULT;
break;
} else {
hdc1001_info->status_reg &= ~HDC1001_STATUS_WRITE_FAULT;
}
/* Fall through */
case HDC1001_CMD_READ_SECT:
{
uint32 xfr_len;
uint32 file_offset;
uint8 rwopts; /* Options specified in the command: DMA, Multi-sector, long. */
/* Abort the read/write operation if C/H/S/N is not valid. */
if (HDC1001_Validate_CHSN(pDrive) != SCPE_OK) break;
/* Calculate file offset */
file_offset = (pDrive->cur_cyl * pDrive->nheads * pDrive->nsectors); /* Full cylinders */
file_offset += (pDrive->cur_head * pDrive->nsectors); /* Add full heads */
file_offset += (pDrive->cur_sect); /* Add sectors for current request */
file_offset *= pDrive->sectsize; /* Convert #sectors to byte offset */
rwopts = hdc1001_info->taskfile[TF_CMD] & 0xE;
if (rwopts & HDC1001_RWOPT_LONG) {
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" LONG Read/Write not supported.\n", hdc1001_info->sel_drive, PCX);
}
xfr_len = pDrive->xfr_nsects * pDrive->sectsize;
sim_fseek((pDrive->uptr)->fileref, file_offset, SEEK_SET);
if(cmd == HDC1001_CMD_READ_SECT) { /* Read */
if (rwopts & HDC1001_RWOPT_DMA) {
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" DMA Read not supported.\n", hdc1001_info->sel_drive, PCX);
}
sim_debug(RD_DATA_MSG, &hdc1001_dev, DEV_NAME "%d: " ADDRESS_FORMAT
" %s SECTOR C:%04d/H:%d/S:%04d/#:%d, offset=%5x, len=%d\n",
hdc1001_info->sel_drive, PCX,
(cmd == HDC1001_CMD_READ_SECT) ? "READ" : "WRITE",
pDrive->cur_cyl, pDrive->cur_head,
pDrive->cur_sect, pDrive->xfr_nsects, file_offset, xfr_len);
sim_fread(hdc1001_info->sectbuf, 1, xfr_len, (pDrive->uptr)->fileref);
} else { /* Write */
sim_debug(WR_DATA_MSG, &hdc1001_dev, DEV_NAME "%d: " ADDRESS_FORMAT
" %s SECTOR C:%04d/H:%d/S:%04d/#:%d, offset=%5x, len=%d\n",
hdc1001_info->sel_drive, PCX,
(cmd == HDC1001_CMD_READ_SECT) ? "READ" : "WRITE",
pDrive->cur_cyl, pDrive->cur_head,
pDrive->cur_sect, pDrive->xfr_nsects, file_offset, xfr_len);
sim_fwrite(hdc1001_info->sectbuf, 1, xfr_len, (pDrive->uptr)->fileref);
}
hdc1001_info->status_reg |= HDC1001_STATUS_DRQ;
break;
}
case HDC1001_CMD_FORMAT_TRK:
{
uint32 data_len;
uint32 file_offset;
uint8 *fmtBuffer;
/* If drive is read-only, signal a write fault. */
if (pDrive->readonly) {
hdc1001_info->status_reg |= HDC1001_STATUS_ERROR;
hdc1001_info->status_reg |= HDC1001_STATUS_WRITE_FAULT;
hdc1001_info->status_reg |= HDC1001_STATUS_DRQ;
break;
}
else {
hdc1001_info->status_reg &= ~HDC1001_STATUS_WRITE_FAULT;
}
data_len = pDrive->nsectors * pDrive->sectsize;
/* Abort the read/write operation if C/H/S/N is not valid. */
if (HDC1001_Validate_CHSN(pDrive) != SCPE_OK) break;
sim_debug(WR_DATA_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" FORMAT TRACK: C:%d/H:%d/Fill=0x%02x/Len=%d\n",
hdc1001_info->sel_drive, PCX, pDrive->cur_cyl,
pDrive->cur_head, HDC1001_FORMAT_FILL_BYTE, data_len);
/* Calculate file offset, formatting always handles a full track at a time. */
file_offset = (pDrive->cur_cyl * pDrive->nheads * pDrive->nsectors); /* Full cylinders */
file_offset += (pDrive->cur_head * pDrive->nsectors); /* Add full heads */
file_offset *= pDrive->sectsize; /* Convert #sectors to byte offset */
fmtBuffer = calloc(data_len, sizeof(uint8));
if (HDC1001_FORMAT_FILL_BYTE != 0) {
memset(fmtBuffer, HDC1001_FORMAT_FILL_BYTE, data_len);
}
sim_fseek((pDrive->uptr)->fileref, file_offset, SEEK_SET);
sim_fwrite(fmtBuffer, 1, data_len, (pDrive->uptr)->fileref);
free(fmtBuffer);
hdc1001_info->status_reg |= HDC1001_STATUS_DRQ;
break;
}
default:
sim_debug(ERROR_MSG, &hdc1001_dev,DEV_NAME "%d: " ADDRESS_FORMAT
" CMD=%x Unsupported\n",
hdc1001_info->sel_drive, PCX, cmd);
break;
}
}
return SCPE_OK;
}