864 lines
33 KiB
C
864 lines
33 KiB
C
/*************************************************************************
|
|
* *
|
|
* Copyright (c) 2022 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 NON- *
|
|
* INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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 names of The Authors shall *
|
|
* not be used in advertising or otherwise to promote the sale, use or *
|
|
* other dealings in this Software without prior written authorization *
|
|
* from the Authors. *
|
|
* *
|
|
* Based on s100_disk3.c *
|
|
* *
|
|
* Module Description: *
|
|
* Morrow Disk Jockey HDC-DMA Hard Disk Controller module for SIMH. *
|
|
* Reference: *
|
|
* http://www.bitsavers.org/pdf/morrow/boards/HDC_DMA_Technical_Manual_1983.pdf *
|
|
* *
|
|
*************************************************************************/
|
|
|
|
#include "altairz80_defs.h"
|
|
#include "sim_imd.h"
|
|
|
|
#define DEV_NAME "DJHDC"
|
|
|
|
#define DJHDC_MAX_CYLS 1024
|
|
#define DJHDC_MAX_HEADS 8
|
|
#define DJHDC_MAX_SPT 256
|
|
|
|
/* Debug flags */
|
|
#define ERROR_MSG (1 << 0)
|
|
#define SEEK_MSG (1 << 1)
|
|
#define OPCODE_MSG (1 << 2)
|
|
#define RD_DATA_MSG (1 << 3)
|
|
#define WR_DATA_MSG (1 << 4)
|
|
#define IRQ_MSG (1 << 5)
|
|
#define VERBOSE_MSG (1 << 6)
|
|
#define FORMAT_MSG (1 << 7)
|
|
|
|
#define DJHDC_MAX_DRIVES 4
|
|
|
|
/* DJHDC I/O Ports */
|
|
#define DJHDC_RESET 0 /* Reset */
|
|
#define DJHDC_START 1 /* Start */
|
|
|
|
#define DJHDC_LINK_ADDR 0x000050 /* Default link address in RAM */
|
|
|
|
#define DJHDC_STEP_DIR 0x10 /* Bit 4, Step OUT */
|
|
|
|
#define DJHDC_IRQ_EN_MASK 0x80 /* Interrupt enable mask */
|
|
|
|
#define DJHDC_OPCODE_READ_DATA 0x00
|
|
#define DJHDC_OPCODE_WRITE_DATA 0x01
|
|
#define DJHDC_OPCODE_READ_HEADER 0x02
|
|
#define DJHDC_OPCODE_FORMAT_TRACK 0x03
|
|
#define DJHDC_OPCODE_LOAD_CONSTANTS 0x04
|
|
#define DJHDC_OPCODE_SENSE_STATUS 0x05
|
|
#define DJHDC_OPCODE_NOOP 0x06
|
|
|
|
#define DJHDC_STATUS_BUSY 0x00 /* Busy */
|
|
#define DJHDC_STATUS_NOT_READY 0x01 /* Drive not ready */
|
|
#define DJHDC_STATUS_HEADER_NOT_FOUND 0x04 /* Sector header not found */
|
|
#define DJHDC_STATUS_DATA_NOT_FOUND 0x05 /* Sector data not found */
|
|
#define DJHDC_STATUS_DATA_OVERRUN 0x06 /* Data overrun(channel error) */
|
|
#define DJHDC_STATUS_DATA_CRC_ERROR 0x07 /* Data CRC error */
|
|
#define DJHDC_STATUS_WRITE_FAULT 0x08 /* Write fault */
|
|
#define DJHDC_STATUS_HEADER_CRC_ERROR 0x09 /* Sector header CRC error */
|
|
#define DJHDC_STATUS_ILLEGAL_COMMAND 0xA0 /* Illegal command */
|
|
#define DJHDC_STATUS_COMPLETE 0xFF /* Successful completion */
|
|
|
|
#define DJHDC_TRACK_0_DETECT (1 << 0) /* 0 = track 0. */
|
|
#define DJHDC_WRITE_FAULT_SIGNAL (1 << 1) /* Drive Write fault (0). */
|
|
#define DJHDC_DRIVE_READY_SIGNAL (1 << 2) /* Drive is up to speed (0). */
|
|
|
|
#define DJHDC_OPCODE_MASK 0x07
|
|
|
|
#define DJHDC_IOPB_LEN 16
|
|
|
|
#define DJHDC_IOPB_SELDRV 0
|
|
#define DJHDC_IOPB_STEP_L 1
|
|
#define DJHDC_IOPB_STEP_H 2
|
|
#define DJHDC_IOPB_SEL_HD 3
|
|
#define DJHDC_IOPB_DMA_L 4
|
|
#define DJHDC_IOPB_DMA_H 5
|
|
#define DJHDC_IOPB_DMA_E 6
|
|
#define DJHDC_IOPB_ARG0 7
|
|
#define DJHDC_IOPB_ARG1 8
|
|
#define DJHDC_IOPB_ARG2 9
|
|
#define DJHDC_IOPB_ARG3 10
|
|
#define DJHDC_IOPB_OPCODE 11
|
|
#define DJHDC_IOPB_STATUS 12
|
|
#define DJHDC_IOPB_LINK 13
|
|
#define DJHDC_IOPB_LINK_H 14
|
|
#define DJHDC_IOPB_LINK_E 15
|
|
|
|
#define DJHDC_INT 1 /* DJHDC interrupts tied to VI1 */
|
|
|
|
typedef struct {
|
|
UNIT *uptr;
|
|
DISK_INFO *imd;
|
|
uint16 sectsize; /* sector size, not including pre/postamble */
|
|
uint16 nsectors; /* number of sectors/track */
|
|
uint16 nheads; /* number of heads */
|
|
uint16 ntracks; /* number of tracks */
|
|
uint16 res_tracks; /* Number of reserved tracks on drive. */
|
|
uint16 track; /* Current Track */
|
|
|
|
uint16 cur_sect; /* current starting sector of transfer */
|
|
uint16 cur_cyl; /* Current Track */
|
|
uint16 cur_head; /* Number of sectors to transfer */
|
|
uint16 cur_sectsize;/* Current sector size */
|
|
uint8 ready; /* Is drive ready? */
|
|
} DJHDC_DRIVE_INFO;
|
|
|
|
typedef struct {
|
|
PNP_INFO pnp; /* Plug and Play */
|
|
uint8 sel_drive; /* Currently selected drive */
|
|
uint8 mode; /* mode (0xFF=absolute, 0x00=logical) */
|
|
uint8 ndrives; /* Number of drives attached to the controller */
|
|
|
|
uint32 link_addr; /* Link Address for next IOPB */
|
|
uint32 dma_addr; /* DMA Address for the current IOPB */
|
|
|
|
uint16 steps; /* Step count */
|
|
uint8 step_dir; /* Step direction, 1 = out. */
|
|
uint8 irq_enable;
|
|
uint8 step_delay;
|
|
uint8 head_settle_time;
|
|
uint8 sector_size_code;
|
|
|
|
DJHDC_DRIVE_INFO drive[DJHDC_MAX_DRIVES];
|
|
uint8 iopb[16];
|
|
} DJHDC_INFO;
|
|
|
|
static DJHDC_INFO djhdc_info_data = { { 0x0, 0, 0x54, 2 } };
|
|
static DJHDC_INFO *djhdc_info = &djhdc_info_data;
|
|
|
|
/* Disk geometries:
|
|
* IMI SCRIBE
|
|
* Sectsize: 1024 1024
|
|
* Sectors: 8 8
|
|
* Heads: 6 4
|
|
* Tracks: 306 480
|
|
*/
|
|
|
|
/* Default geometry for a 15MB hard disk. */
|
|
#define SCRIBE_SECTSIZE 1024
|
|
#define SCRIBE_NSECTORS 8
|
|
#define SCRIBE_NHEADS 4
|
|
#define SCRIBE_NTRACKS 480
|
|
|
|
static const char* djhdc_opcode_str[] = {
|
|
"Read Data ",
|
|
"Write Data ",
|
|
"Read Header ",
|
|
"Format Track ",
|
|
"Load Constants",
|
|
"Sense Status ",
|
|
"No Operation ",
|
|
"Invalid "
|
|
};
|
|
|
|
static int32 ntracks = SCRIBE_NTRACKS;
|
|
static int32 nheads = SCRIBE_NHEADS;
|
|
static int32 nsectors = SCRIBE_NSECTORS;
|
|
static int32 sectsize = SCRIBE_SECTSIZE;
|
|
|
|
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);
|
|
extern void raise_scp300f_interrupt(uint8 intnum);
|
|
|
|
/* These are needed for DMA. */
|
|
extern void PutByteDMA(const uint32 Addr, const uint32 Value);
|
|
extern uint8 GetByteDMA(const uint32 Addr);
|
|
|
|
#define UNIT_V_DJHDC_VERBOSE (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages */
|
|
#define UNIT_DJHDC_VERBOSE (1 << UNIT_V_DJHDC_VERBOSE)
|
|
#define DJHDC_CAPACITY (SCRIBE_NTRACKS * SCRIBE_NHEADS * \
|
|
SCRIBE_NSECTORS * SCRIBE_SECTSIZE) /* Default Disk Capacity */
|
|
|
|
static t_stat djhdc_reset(DEVICE *djhdc_dev);
|
|
static t_stat djhdc_attach(UNIT *uptr, CONST char *cptr);
|
|
static t_stat djhdc_detach(UNIT *uptr);
|
|
static t_stat djhdc_unit_set_geometry(UNIT* uptr, int32 value, CONST char* cptr, void* desc);
|
|
static t_stat djhdc_unit_show_geometry(FILE* st, UNIT* uptr, int32 value, CONST void* desc);
|
|
static int DJHDC_Validate_CHSN(DJHDC_DRIVE_INFO* pDrive);
|
|
#ifdef DJHDC_INTERRUPTS
|
|
static void raise_djhdc_interrupt(void);
|
|
#endif /* DJHDC_INTERRUPTS */
|
|
|
|
static const char* djhdc_description(DEVICE *dptr);
|
|
|
|
static int32 djhdcdev(const int32 port, const int32 io, const int32 data);
|
|
|
|
/* static uint8 DJHDC_Read(const uint32 Addr); */
|
|
static uint8 DJHDC_Write(const uint32 Addr, uint8 cData);
|
|
|
|
static UNIT djhdc_unit[] = {
|
|
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DJHDC_CAPACITY) },
|
|
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DJHDC_CAPACITY) },
|
|
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DJHDC_CAPACITY) },
|
|
{ UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, DJHDC_CAPACITY) }
|
|
};
|
|
|
|
static REG djhdc_reg[] = {
|
|
{ DRDATAD (NTRACKS, ntracks, 10,
|
|
"Number of tracks"), },
|
|
{ DRDATAD (NHEADS, nheads, 8,
|
|
"Number of heads"), },
|
|
{ DRDATAD (NSECTORS, nsectors, 8,
|
|
"Number of sectors per track"), },
|
|
{ DRDATAD (SECTSIZE, sectsize, 11,
|
|
"Sector size not including pre/postamble"), },
|
|
{ HRDATAD (SEL_DRIVE, djhdc_info_data.sel_drive, 3,
|
|
"Currently selected drive"), },
|
|
{ HRDATAD (MODE, djhdc_info_data.mode, 8,
|
|
"Mode (0xFF=absolute, 0x00=logical)"), },
|
|
{ HRDATAD (NDRIVES, djhdc_info_data.ndrives, 8,
|
|
"Number of drives attached to the controller"), },
|
|
{ HRDATAD (LINK_ADDR, djhdc_info_data.link_addr, 32,
|
|
"Link address for next IOPB"), },
|
|
{ HRDATAD (DMA_ADDR, djhdc_info_data.dma_addr, 32,
|
|
"DMA address for the current IOPB"), },
|
|
{ BRDATAD (IOPB, djhdc_info_data.iopb, 16, 8, 16,
|
|
"IOPB command register"), },
|
|
{ NULL }
|
|
};
|
|
|
|
#define DJHDC_NAME "Morrow HDC/DMA Hard Disk Controller"
|
|
|
|
static const char* djhdc_description(DEVICE *dptr) {
|
|
if (dptr == NULL) {
|
|
return NULL;
|
|
}
|
|
return DJHDC_NAME;
|
|
}
|
|
|
|
static MTAB djhdc_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",
|
|
&djhdc_unit_set_geometry, &djhdc_unit_show_geometry, NULL,
|
|
"Set disk geometry C:nnnn/H:n/S:nnn/N:nnnn" },
|
|
{ 0 }
|
|
};
|
|
|
|
/* Debug Flags */
|
|
static DEBTAB djhdc_dt[] = {
|
|
{ "ERROR", ERROR_MSG, "Error messages" },
|
|
{ "SEEK", SEEK_MSG, "Seek messages" },
|
|
{ "OPCODE", OPCODE_MSG, "Opcode messages" },
|
|
{ "READ", RD_DATA_MSG, "Read messages" },
|
|
{ "WRITE", WR_DATA_MSG, "Write messages" },
|
|
{ "IRQ", IRQ_MSG, "IRQ messages" },
|
|
{ "VERBOSE", VERBOSE_MSG, "Verbose messages" },
|
|
{ "FORMAT", FORMAT_MSG, "Format messages" },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
DEVICE djhdc_dev = {
|
|
DEV_NAME, djhdc_unit, djhdc_reg, djhdc_mod,
|
|
DJHDC_MAX_DRIVES, 10, 31, 1, DJHDC_MAX_DRIVES, DJHDC_MAX_DRIVES,
|
|
NULL, NULL, &djhdc_reset,
|
|
NULL, &djhdc_attach, &djhdc_detach,
|
|
&djhdc_info_data, (DEV_DISABLE | DEV_DIS | DEV_DEBUG), ERROR_MSG,
|
|
djhdc_dt, NULL, NULL, NULL, NULL, NULL, &djhdc_description
|
|
};
|
|
|
|
/* Reset routine */
|
|
static t_stat djhdc_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, &djhdcdev, "djhdcdev", TRUE);
|
|
} else {
|
|
/* Connect DJHDC at base address */
|
|
if(sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &djhdcdev, "djhdcdev", FALSE) != 0) {
|
|
sim_printf("%s: error mapping I/O resource at 0x%04x\n", __FUNCTION__, pnp->io_base);
|
|
return SCPE_ARG;
|
|
}
|
|
}
|
|
|
|
djhdc_info->link_addr = DJHDC_LINK_ADDR; /* After RESET, the link pointer is at 0x000050. */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Attach routine */
|
|
static t_stat djhdc_attach(UNIT *uptr, CONST char *cptr)
|
|
{
|
|
t_stat r = SCPE_OK;
|
|
DJHDC_DRIVE_INFO *pDrive;
|
|
int i = 0;
|
|
|
|
i = find_unit_index(uptr);
|
|
if (i == -1) {
|
|
return (SCPE_IERR);
|
|
}
|
|
pDrive = &djhdc_info->drive[i];
|
|
|
|
pDrive->ready = 1;
|
|
pDrive->track = 5;
|
|
|
|
if (pDrive->ntracks == 0) {
|
|
/* If geometry was not specified, default to Miniscribe 15MB */
|
|
pDrive->ntracks = SCRIBE_NTRACKS;
|
|
pDrive->nheads = SCRIBE_NHEADS;
|
|
pDrive->nsectors = SCRIBE_NSECTORS;
|
|
pDrive->sectsize = SCRIBE_SECTSIZE;
|
|
}
|
|
|
|
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->ntracks * 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) {
|
|
djhdc_detach(uptr);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
if (uptr->flags & UNIT_DJHDC_VERBOSE)
|
|
sim_printf("DJHDC%d, attached to '%s', type=%s, len=%d\n", i, cptr,
|
|
uptr->u3 == IMAGE_TYPE_IMD ? "IMD" : uptr->u3 == IMAGE_TYPE_CPT ? "CPT" : "DSK",
|
|
uptr->capac);
|
|
|
|
if(uptr->u3 == IMAGE_TYPE_IMD) {
|
|
if(uptr->capac < 318000) {
|
|
sim_printf("Cannot create IMD files with SIMH.\nCopy an existing file and format it with CP/M.\n");
|
|
djhdc_detach(uptr);
|
|
return SCPE_OPENERR;
|
|
}
|
|
|
|
if (uptr->flags & UNIT_DJHDC_VERBOSE)
|
|
sim_printf("--------------------------------------------------------\n");
|
|
djhdc_info->drive[i].imd = diskOpenEx((uptr->fileref), (uptr->flags & UNIT_DJHDC_VERBOSE),
|
|
&djhdc_dev, VERBOSE_MSG, VERBOSE_MSG);
|
|
if (uptr->flags & UNIT_DJHDC_VERBOSE)
|
|
sim_printf("\n");
|
|
} else {
|
|
djhdc_info->drive[i].imd = NULL;
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Detach routine */
|
|
static t_stat djhdc_detach(UNIT *uptr)
|
|
{
|
|
DJHDC_DRIVE_INFO *pDrive;
|
|
t_stat r;
|
|
int32 i;
|
|
|
|
i = find_unit_index(uptr);
|
|
|
|
if (i == -1) {
|
|
return (SCPE_IERR);
|
|
}
|
|
|
|
pDrive = &djhdc_info->drive[i];
|
|
|
|
pDrive->ready = 0;
|
|
|
|
if (uptr->flags & UNIT_DJHDC_VERBOSE)
|
|
sim_printf("Detach DJHDC%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 djhdc_unit_set_geometry(UNIT* uptr, int32 value, CONST char* cptr, void* desc)
|
|
{
|
|
DJHDC_DRIVE_INFO* pDrive;
|
|
int32 i;
|
|
int32 result;
|
|
uint16 newCyls, newHeads, newSPT, newSecLen;
|
|
|
|
i = find_unit_index(uptr);
|
|
|
|
if (i == -1) {
|
|
return (SCPE_IERR);
|
|
}
|
|
|
|
pDrive = &djhdc_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 > DJHDC_MAX_CYLS) {
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "%d: Number of cylinders must be 1-%d.\n",
|
|
djhdc_info->sel_drive, DJHDC_MAX_CYLS);
|
|
return SCPE_ARG;
|
|
}
|
|
if (newHeads < 1 || newHeads > DJHDC_MAX_HEADS) {
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "%d: Number of heads must be 1-%d.\n",
|
|
djhdc_info->sel_drive, DJHDC_MAX_HEADS);
|
|
return SCPE_ARG;
|
|
}
|
|
if (newSPT < 1 || newSPT > DJHDC_MAX_SPT) {
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "%d: Number of sectors per track must be 1-%d.\n",
|
|
djhdc_info->sel_drive, DJHDC_MAX_SPT);
|
|
return SCPE_ARG;
|
|
}
|
|
if (newSecLen != 2048 && newSecLen != 1024 && newSecLen != 512 && newSecLen != 256 && newSecLen != 128) {
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "%d: Sector length must be 128, 256, 512, 1024, or 2048.\n",
|
|
djhdc_info->sel_drive);
|
|
return SCPE_ARG;
|
|
}
|
|
|
|
pDrive->ntracks = newCyls;
|
|
pDrive->nheads = newHeads;
|
|
pDrive->nsectors = newSPT;
|
|
pDrive->sectsize = newSecLen;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/* Show geometry of the disk drive */
|
|
static t_stat djhdc_unit_show_geometry(FILE* st, UNIT* uptr, int32 value, CONST void* desc)
|
|
{
|
|
DJHDC_DRIVE_INFO* pDrive;
|
|
int32 i;
|
|
|
|
i = find_unit_index(uptr);
|
|
|
|
if (i == -1) {
|
|
return (SCPE_IERR);
|
|
}
|
|
|
|
pDrive = &djhdc_info->drive[i];
|
|
|
|
fprintf(st, "C:%d/H:%d/S:%d/N:%d",
|
|
pDrive->ntracks, pDrive->nheads, pDrive->nsectors, pDrive->sectsize);
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
static int32 djhdcdev(const int32 port, const int32 io, const int32 data)
|
|
{
|
|
sim_debug(VERBOSE_MSG, &djhdc_dev, DEV_NAME ": " ADDRESS_FORMAT
|
|
" IO %s, Port %02x\n", PCX, io ? "WR" : "RD", port);
|
|
if(io) {
|
|
DJHDC_Write(port, (uint8)data);
|
|
return 0;
|
|
} else {
|
|
return(0xFF);
|
|
}
|
|
}
|
|
|
|
static uint8 DJHDC_Write(const uint32 Addr, uint8 cData)
|
|
{
|
|
uint32 next_link;
|
|
uint8 result = DJHDC_STATUS_COMPLETE;
|
|
uint8 i;
|
|
uint8 opcode;
|
|
|
|
DJHDC_DRIVE_INFO *pDrive;
|
|
|
|
/* RESET */
|
|
if ((Addr & 1) == DJHDC_RESET) {
|
|
djhdc_info->link_addr = DJHDC_LINK_ADDR - DJHDC_IOPB_LINK;
|
|
|
|
sim_debug(VERBOSE_MSG, &djhdc_dev, DEV_NAME "[%d]: RESET\n",
|
|
djhdc_info->sel_drive);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* START */
|
|
|
|
/* Read first three bytes of IOPB (link address) */
|
|
for (i = DJHDC_IOPB_LINK; i < DJHDC_IOPB_LEN; i++) {
|
|
djhdc_info->iopb[i] = GetByteDMA((djhdc_info->link_addr) + i);
|
|
}
|
|
|
|
next_link = djhdc_info->iopb[DJHDC_IOPB_LINK + 0];
|
|
next_link |= djhdc_info->iopb[DJHDC_IOPB_LINK+1] << 8;
|
|
next_link |= djhdc_info->iopb[DJHDC_IOPB_LINK+2] << 16;
|
|
|
|
/* Point IOPB to new link */
|
|
djhdc_info->link_addr = next_link;
|
|
|
|
/* Read remainder of IOPB */
|
|
for(i = 0; i < DJHDC_IOPB_LEN-3; i++) {
|
|
djhdc_info->iopb[i] = GetByteDMA((djhdc_info->link_addr) + i);
|
|
}
|
|
|
|
/* Process the IOPB */
|
|
djhdc_info->iopb[DJHDC_IOPB_OPCODE] = djhdc_info->iopb[DJHDC_IOPB_OPCODE] & DJHDC_OPCODE_MASK;
|
|
|
|
opcode = djhdc_info->iopb[DJHDC_IOPB_OPCODE];
|
|
djhdc_info->sel_drive = djhdc_info->iopb[DJHDC_IOPB_SELDRV] & 0x03;
|
|
djhdc_info->step_dir = (djhdc_info->iopb[DJHDC_IOPB_SELDRV] & DJHDC_STEP_DIR) ? 1 : 0;
|
|
|
|
djhdc_info->steps = djhdc_info->iopb[DJHDC_IOPB_STEP_L];
|
|
djhdc_info->steps |= djhdc_info->iopb[DJHDC_IOPB_STEP_H] << 8;
|
|
|
|
djhdc_info->dma_addr = djhdc_info->iopb[DJHDC_IOPB_DMA_L];
|
|
djhdc_info->dma_addr |= djhdc_info->iopb[DJHDC_IOPB_DMA_H] << 8;
|
|
djhdc_info->dma_addr |= djhdc_info->iopb[DJHDC_IOPB_DMA_E] << 16;
|
|
|
|
sim_debug(VERBOSE_MSG, &djhdc_dev, DEV_NAME "[%d]: SEEK=%d %s, LINK=0x%05x, OPCODE=%x, %s DMA@0x%05x\n",
|
|
djhdc_info->sel_drive,
|
|
djhdc_info->steps,
|
|
djhdc_info->step_dir ? "OUT" : "IN",
|
|
djhdc_info->link_addr,
|
|
djhdc_info->iopb[DJHDC_IOPB_OPCODE],
|
|
djhdc_opcode_str[djhdc_info->iopb[DJHDC_IOPB_OPCODE]],
|
|
djhdc_info->dma_addr);
|
|
|
|
pDrive = &djhdc_info->drive[djhdc_info->sel_drive];
|
|
|
|
if(pDrive->ready) {
|
|
/* Seek phase */
|
|
if (djhdc_info->step_dir) {
|
|
/* Step Out */
|
|
if (djhdc_info->steps >= pDrive->cur_cyl) {
|
|
pDrive->cur_cyl = 0;
|
|
sim_debug(SEEK_MSG, &djhdc_dev, DEV_NAME "[%d]: HOME\n",
|
|
djhdc_info->sel_drive);
|
|
}
|
|
else {
|
|
pDrive->cur_cyl -= djhdc_info->steps;
|
|
}
|
|
}
|
|
else {
|
|
/* Step In */
|
|
pDrive->cur_cyl += djhdc_info->steps;
|
|
}
|
|
|
|
sim_debug(SEEK_MSG, &djhdc_dev, DEV_NAME "[%d]: Current track: %d\n",
|
|
djhdc_info->sel_drive,
|
|
pDrive->cur_cyl);
|
|
|
|
|
|
/* Perform command */
|
|
switch(opcode) {
|
|
case DJHDC_OPCODE_READ_DATA:
|
|
case DJHDC_OPCODE_WRITE_DATA:
|
|
{
|
|
uint32 track_len;
|
|
uint32 xfr_len;
|
|
uint32 file_offset;
|
|
uint32 xfr_count = 0;
|
|
uint8* dataBuffer;
|
|
size_t rtn;
|
|
|
|
pDrive->cur_cyl = djhdc_info->iopb[DJHDC_IOPB_ARG0] | (djhdc_info->iopb[DJHDC_IOPB_ARG1] << 8);
|
|
pDrive->cur_head = djhdc_info->iopb[DJHDC_IOPB_ARG2];
|
|
pDrive->cur_sect = djhdc_info->iopb[DJHDC_IOPB_ARG3] - 1;
|
|
|
|
if (DJHDC_Validate_CHSN(pDrive) != SCPE_OK) {
|
|
result = DJHDC_STATUS_HEADER_NOT_FOUND;
|
|
break;
|
|
}
|
|
|
|
track_len = pDrive->nsectors * pDrive->nheads * pDrive->sectsize;
|
|
|
|
file_offset = (pDrive->cur_cyl * track_len); /* Calculate offset based on current track */
|
|
file_offset += pDrive->nsectors * pDrive->cur_head * pDrive->sectsize;
|
|
file_offset += pDrive->cur_sect * pDrive->sectsize;
|
|
|
|
xfr_len = pDrive->sectsize;
|
|
|
|
dataBuffer = (uint8*)malloc(xfr_len);
|
|
if (dataBuffer == NULL) {
|
|
sim_printf("%s: error allocating memory\n", __FUNCTION__);
|
|
return (0);
|
|
}
|
|
|
|
if (sim_fseek((pDrive->uptr)->fileref, file_offset, SEEK_SET) == 0) {
|
|
|
|
if (opcode == DJHDC_OPCODE_READ_DATA) { /* Read */
|
|
rtn = sim_fread(dataBuffer, 1, xfr_len, (pDrive->uptr)->fileref);
|
|
|
|
sim_debug(RD_DATA_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT
|
|
" READ @0x%05x C:%04d/H:%d/S:%04d len=%d, file_offset=%d, %s\n",
|
|
djhdc_info->sel_drive,
|
|
PCX,
|
|
djhdc_info->dma_addr,
|
|
pDrive->cur_cyl,
|
|
pDrive->cur_head,
|
|
pDrive->cur_sect,
|
|
xfr_len,
|
|
file_offset,
|
|
rtn == (size_t)xfr_len ? "OK" : "NOK");
|
|
|
|
|
|
/* Perform DMA Transfer */
|
|
for (xfr_count = 0; xfr_count < xfr_len; xfr_count++) {
|
|
PutByteDMA(djhdc_info->dma_addr + xfr_count, dataBuffer[xfr_count]);
|
|
}
|
|
}
|
|
else { /* Write */
|
|
sim_debug(WR_DATA_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT
|
|
" WRITE @0x%05x C:%04d/H:%d/S:%04d file_offset=%d, len=%d\n",
|
|
djhdc_info->sel_drive,
|
|
PCX, djhdc_info->dma_addr,
|
|
pDrive->cur_cyl,
|
|
pDrive->cur_head,
|
|
pDrive->cur_sect,
|
|
file_offset,
|
|
xfr_len);
|
|
|
|
/* Perform DMA Transfer */
|
|
for (xfr_count = 0; xfr_count < xfr_len; xfr_count++) {
|
|
dataBuffer[xfr_count] = GetByteDMA(djhdc_info->dma_addr + xfr_count);
|
|
}
|
|
|
|
sim_fwrite(dataBuffer, 1, xfr_len, (pDrive->uptr)->fileref);
|
|
}
|
|
}
|
|
else {
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT " READWRITE: sim_fseek error.\n", djhdc_info->sel_drive, PCX);
|
|
}
|
|
|
|
free(dataBuffer);
|
|
|
|
break;
|
|
}
|
|
case DJHDC_OPCODE_READ_HEADER:
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT " READ_HEADER: not implemented.\n", djhdc_info->sel_drive, PCX);
|
|
result = DJHDC_STATUS_HEADER_NOT_FOUND;
|
|
break;
|
|
case DJHDC_OPCODE_FORMAT_TRACK:
|
|
{
|
|
uint32 track_len;
|
|
uint32 file_offset;
|
|
uint8* fmtBuffer;
|
|
uint8 head;
|
|
uint8 gap;
|
|
uint8 sector_count;
|
|
uint8 sector_size_code;
|
|
uint8 fill_byte;
|
|
|
|
head = ~(djhdc_info->iopb[DJHDC_IOPB_SEL_HD] >> 2) & 7;
|
|
gap = djhdc_info->iopb[DJHDC_IOPB_ARG0];
|
|
sector_count = 255 - djhdc_info->iopb[DJHDC_IOPB_ARG1];
|
|
sector_size_code = djhdc_info->iopb[DJHDC_IOPB_ARG2];
|
|
fill_byte = djhdc_info->iopb[DJHDC_IOPB_ARG3];
|
|
|
|
switch (sector_size_code) {
|
|
case 0xFF:
|
|
pDrive->cur_sectsize = 128;
|
|
break;
|
|
case 0xFE:
|
|
pDrive->cur_sectsize = 256;
|
|
break;
|
|
case 0xFC:
|
|
pDrive->cur_sectsize = 512;
|
|
break;
|
|
case 0xF8:
|
|
pDrive->cur_sectsize = 1024;
|
|
break;
|
|
case 0xF0:
|
|
pDrive->cur_sectsize = 2048;
|
|
break;
|
|
default:
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME ": Invalid sector size code: 0x%02x.\n",
|
|
djhdc_info->sector_size_code);
|
|
pDrive->cur_sectsize = 0;
|
|
result = DJHDC_STATUS_ILLEGAL_COMMAND;
|
|
break;
|
|
}
|
|
|
|
if (DJHDC_Validate_CHSN(pDrive) != SCPE_OK) {
|
|
result = DJHDC_STATUS_HEADER_NOT_FOUND;
|
|
break;
|
|
}
|
|
|
|
track_len = pDrive->nheads * sector_count * pDrive->sectsize;
|
|
|
|
file_offset = pDrive->cur_cyl * track_len; /* Calculate offset based on current track */
|
|
file_offset += head * sector_count * pDrive->sectsize;
|
|
|
|
sim_debug(FORMAT_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT
|
|
" FORMAT C:%d/H:%d, Gap=%d, Fill=0x%02x, Count=%d, Sector Size:=%d, file offset: 0x%08x\n",
|
|
djhdc_info->sel_drive,
|
|
PCX,
|
|
pDrive->cur_cyl,
|
|
head,
|
|
gap,
|
|
fill_byte,
|
|
sector_count,
|
|
pDrive->sectsize,
|
|
file_offset);
|
|
|
|
fmtBuffer = (uint8*)malloc(track_len);
|
|
|
|
if (fmtBuffer == NULL) {
|
|
sim_printf("%s: error allocating memory\n", __FUNCTION__);
|
|
return (0);
|
|
}
|
|
|
|
memset(fmtBuffer, fill_byte, track_len);
|
|
|
|
if (sim_fseek((pDrive->uptr)->fileref, file_offset, SEEK_SET) == 0) {
|
|
sim_fwrite(fmtBuffer, 1, track_len, (pDrive->uptr)->fileref);
|
|
}
|
|
else {
|
|
sim_debug(WR_DATA_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT " FORMAT: sim_fseek error.\n", djhdc_info->sel_drive, PCX);
|
|
result = DJHDC_STATUS_WRITE_FAULT;
|
|
}
|
|
|
|
free(fmtBuffer);
|
|
|
|
break;
|
|
}
|
|
case DJHDC_OPCODE_LOAD_CONSTANTS:
|
|
djhdc_info->irq_enable = (djhdc_info->iopb[DJHDC_IOPB_ARG1] & DJHDC_IRQ_EN_MASK) ? 1 : 0;
|
|
djhdc_info->step_delay = djhdc_info->iopb[DJHDC_IOPB_ARG1] & ~DJHDC_IRQ_EN_MASK;
|
|
djhdc_info->head_settle_time = djhdc_info->iopb[DJHDC_IOPB_ARG2];
|
|
djhdc_info->sector_size_code = djhdc_info->iopb[DJHDC_IOPB_ARG3];
|
|
|
|
switch (djhdc_info->sector_size_code) {
|
|
case 0x00:
|
|
pDrive->cur_sectsize = 128;
|
|
break;
|
|
case 0x01:
|
|
pDrive->cur_sectsize = 256;
|
|
break;
|
|
case 0x03:
|
|
pDrive->cur_sectsize = 512;
|
|
break;
|
|
case 0x07:
|
|
pDrive->cur_sectsize = 1024;
|
|
break;
|
|
case 0x0F:
|
|
pDrive->cur_sectsize = 2048;
|
|
break;
|
|
default:
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME ": Invalid sector size code: 0x%02x.\n",
|
|
djhdc_info->sector_size_code);
|
|
pDrive->cur_sectsize = 0;
|
|
result = DJHDC_STATUS_ILLEGAL_COMMAND;
|
|
break;
|
|
}
|
|
|
|
sim_debug(VERBOSE_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT
|
|
" Load Constants: Interrupt Enable: %d, step delay: %d, head settle time: %d, sector size %d (code: 0x%02x)\n",
|
|
djhdc_info->sel_drive, PCX,
|
|
djhdc_info->irq_enable,
|
|
djhdc_info->step_delay,
|
|
djhdc_info->head_settle_time,
|
|
pDrive->sectsize,
|
|
djhdc_info->sector_size_code);
|
|
break;
|
|
case DJHDC_OPCODE_SENSE_STATUS:
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT " SENSE_STATUS: not implemented.\n", djhdc_info->sel_drive, PCX);
|
|
result = DJHDC_DRIVE_READY_SIGNAL;
|
|
if (pDrive->cur_cyl != 0) result = DJHDC_TRACK_0_DETECT;
|
|
break;
|
|
case DJHDC_OPCODE_NOOP:
|
|
sim_debug(VERBOSE_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT
|
|
" NOOP\n", djhdc_info->sel_drive, PCX);
|
|
break;
|
|
|
|
default:
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "[%d]: " ADDRESS_FORMAT
|
|
" OPCODE=%x Unsupported\n",
|
|
djhdc_info->sel_drive,
|
|
PCX,
|
|
opcode & DJHDC_OPCODE_MASK);
|
|
result = DJHDC_STATUS_ILLEGAL_COMMAND;
|
|
break;
|
|
}
|
|
} else { /* Drive not ready */
|
|
result = DJHDC_STATUS_NOT_READY;
|
|
}
|
|
/* Return status */
|
|
djhdc_info->iopb[DJHDC_IOPB_STATUS] = result;
|
|
|
|
/* Update IOPB in host memory */
|
|
PutByteDMA(djhdc_info->link_addr + DJHDC_IOPB_STATUS, djhdc_info->iopb[DJHDC_IOPB_STATUS]);
|
|
|
|
#ifdef DJHDC_INTERRUPTS
|
|
if(djhdc_info->irq_enable) {
|
|
raise_djhdc_interrupt();
|
|
}
|
|
#endif /* DJHDC_INTERRUPTS */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Validate that Cyl, Head, Sector, Sector Length are valid for the current
|
|
* disk drive geometry.
|
|
*/
|
|
static int DJHDC_Validate_CHSN(DJHDC_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->ntracks) ||
|
|
(pDrive->cur_head >= pDrive->nheads) ||
|
|
(pDrive->cur_sect >= pDrive->nsectors) ||
|
|
(pDrive->cur_sectsize != pDrive->sectsize))
|
|
{
|
|
|
|
sim_debug(ERROR_MSG, &djhdc_dev, DEV_NAME "%d: " ADDRESS_FORMAT
|
|
" ID Not Found (check disk geometry.)\n", djhdc_info->sel_drive, PCX);
|
|
|
|
status = SCPE_IOERR;
|
|
}
|
|
|
|
return (status);
|
|
}
|
|
|
|
#ifdef DJHDC_INTERRUPTS
|
|
static void raise_djhdc_interrupt(void)
|
|
{
|
|
sim_debug(IRQ_MSG, &djhdc_dev, DEV_NAME ": " ADDRESS_FORMAT " Interrupt\n", PCX);
|
|
|
|
raise_scp300f_interrupt(DJHDC_INT);
|
|
}
|
|
#endif /* DJHDC_INTERRUPTS */
|