diff --git a/AltairZ80/altairz80_sys.c b/AltairZ80/altairz80_sys.c index 17205ac9..fb68586b 100644 --- a/AltairZ80/altairz80_sys.c +++ b/AltairZ80/altairz80_sys.c @@ -82,6 +82,7 @@ extern DEVICE n8vem_dev; extern DEVICE wdi2_dev; extern DEVICE scp300f_dev; +extern DEVICE djhdc_dev; extern long disasm (unsigned char *data, char *output, int segsize, long offset); extern t_stat parse_sym_m68k(char* c, t_addr a, UNIT* u, t_value* val, int32 sw); @@ -133,8 +134,9 @@ DEVICE *sim_devices[] = { &tdd_dev, /* iCOM Devices */ &icom_dev, - /* Disk Jockey 2D Devices */ + /* Morrow Devices */ &dj2d_dev, + &djhdc_dev, /* MITS 88-2SIO */ &m2sio0_dev, &m2sio1_dev, diff --git a/AltairZ80/s100_djhdc.c b/AltairZ80/s100_djhdc.c new file mode 100644 index 00000000..08207a2d --- /dev/null +++ b/AltairZ80/s100_djhdc.c @@ -0,0 +1,860 @@ +/************************************************************************* + * * + * 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 + +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); +static void raise_djhdc_interrupt(void); +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 remaineder 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); +} + +#define VI1_INT 1 /* DJHDC interrupts tied to VI1 */ + +static void raise_djhdc_interrupt(void) +{ + sim_debug(IRQ_MSG, &djhdc_dev, DEV_NAME ": " ADDRESS_FORMAT " Interrupt\n", PCX); + + raise_scp300f_interrupt(VI1_INT); + +}