mirror of
https://github.com/open-simh/simh.git
synced 2026-01-27 04:32:40 +00:00
- Allow 3 MB RAM configuration (previously only 1MB, 2MB, and 4MB configurations were allowed) - Allow SCSI CIO card to be used under 3B2/400 emulation (previously it could only be used under 3B2/700 emulation) - Improved CTC, PORTS, and SCSI diagnostic checks - Fixed a bug in IDISK device that allowed impossible disk configurations The last update is a breaking change that disables the HD161 disk type by default, since real 3B2 hardware does not support it. The disk type will still allowed in backward compatibility mode through use of the "SET IDISK LARGE" command.
999 lines
33 KiB
C
999 lines
33 KiB
C
/* 3b2_d.c: uPD7261 Integrated Disk Controller
|
|
|
|
Copyright (c) 2017-2022, Seth J. Morabito
|
|
|
|
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 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 name of the author 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 author.
|
|
*/
|
|
|
|
/*
|
|
* This file contains the code for the Integrated Disk (ID) controller
|
|
* (based on the uPD7261) and up to two winchester hard disks.
|
|
*
|
|
* Supported winchester drives are:
|
|
*
|
|
* SIMH Name ID Cyl Head Sec Byte/Sec Note
|
|
* --------- -- ---- ---- --- -------- ----------------------
|
|
* HD30 3 697 5 18 512 CDC Wren 94155-36
|
|
* HD72 5 925 9 18 512 CDC Wren II 94156-86
|
|
* HD72C 8 754 11 18 512 Fujitsu M2243AS
|
|
* HD135 11 1224 15 18 512 Maxtor XT1190
|
|
*/
|
|
|
|
#include "3b2_id.h"
|
|
|
|
#include "sim_disk.h"
|
|
|
|
#include "3b2_cpu.h"
|
|
|
|
#define ID_STEP_WAIT 4000 /* Delay per track step */
|
|
#define ID_STEP_MIN 10000 /* Minimum delay for RECAL/SEEK command */
|
|
#define ID_RW_WAIT 5000
|
|
#define ID_SUS_WAIT 1000
|
|
#define ID_SPEC_WAIT 1000
|
|
#define ID_SIS_WAIT 1000
|
|
#define ID_CMD_WAIT 2000
|
|
|
|
#define POLLING(id) ((id_dtlh & ID_DTLH_POLL) == 0)
|
|
|
|
/* Static function declarations */
|
|
static SIM_INLINE t_lba id_lba(uint16 cyl, uint8 head, uint8 sec);
|
|
|
|
/* DMAC request */
|
|
t_bool id_drq = FALSE;
|
|
|
|
struct id_state id_state[ID_NUM_UNITS] = {0};
|
|
|
|
/* Enable support for disks with > 1024 cylinders */
|
|
static t_bool id_large = FALSE;
|
|
/* Data FIFO pointer - Read */
|
|
static uint8 id_dpr = 0;
|
|
/* Data FIFO pointer - Write */
|
|
static uint8 id_dpw = 0;
|
|
/* Controller Status Register */
|
|
static uint8 id_status = 0;
|
|
/* Unit Interrupt Status */
|
|
static uint8 id_int_status = 0;
|
|
/* Last command received */
|
|
static uint8 id_cmd = 0;
|
|
/* 8-byte FIFO */
|
|
static uint8 id_data[ID_FIFO_LEN] = {0};
|
|
/* SRQM bit */
|
|
static t_bool id_srqm = FALSE;
|
|
/* The logical unit number (0-1) */
|
|
static uint8 id_unit_num = 0;
|
|
/* The physical unit number (0-3) */
|
|
static uint8 id_ua = 0;
|
|
/* Whether we are using buffered SEEK/RECAL or not */
|
|
static t_bool id_buffered = FALSE;
|
|
/* Sector buffer */
|
|
static uint8 id_buf[ID_SEC_SIZE];
|
|
/* Buffer pointer */
|
|
static size_t id_buf_ptr = 0;
|
|
|
|
/* SPECIFY parameters */
|
|
static uint8 id_esn;
|
|
static uint8 id_etn;
|
|
static uint8 id_dtlh;
|
|
|
|
static uint8 id_idfield[ID_IDFIELD_LEN];
|
|
static uint8 id_idfield_ptr = 0;
|
|
|
|
struct id_dtype {
|
|
uint8 hd; /* Number of heads */
|
|
uint32 capac; /* Capacity (in sectors) */
|
|
const char *name;
|
|
};
|
|
|
|
static struct id_dtype id_dtab[] = {
|
|
ID_DRV(HD30),
|
|
ID_DRV(HD72),
|
|
ID_DRV(HD72C),
|
|
ID_DRV(HD135),
|
|
ID_DRV(HD161), /* Only enabled if 'id_large' is set */
|
|
{ 0 }
|
|
};
|
|
|
|
UNIT id_unit[] = {
|
|
{ UDATA (&id_unit_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+DKUF_NOAUTOSIZE+
|
|
(ID_HD72_DTYPE << ID_V_DTYPE), ID_DSK_SIZE(HD72)), 0, ID0, 0 },
|
|
{ UDATA (&id_unit_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+DKUF_NOAUTOSIZE+
|
|
(ID_HD72_DTYPE << ID_V_DTYPE), ID_DSK_SIZE(HD72)), 0, ID1, 0 },
|
|
{ UDATA (&id_ctlr_svc, 0, 0) },
|
|
{ NULL }
|
|
};
|
|
|
|
UNIT *id_ctlr_unit = &id_unit[ID_CTLR];
|
|
|
|
/* The currently selected drive number */
|
|
UNIT *id_sel_unit = &id_unit[ID0];
|
|
|
|
REG id_reg[] = {
|
|
{ HRDATAD(CMD, id_cmd, 8, "Command") },
|
|
{ HRDATAD(STAT, id_status, 8, "Status") },
|
|
{ NULL }
|
|
};
|
|
|
|
MTAB id_mod[] = {
|
|
{ MTAB_XTD|MTAB_VDV, 1, NULL, "LARGE",
|
|
&id_set_large, NULL, NULL, "Enable support for disks with > 1024 cylinders" },
|
|
{ MTAB_XTD|MTAB_VDV, 0, NULL, "NOLARGE",
|
|
&id_set_large, NULL, NULL, "Disable support disks with > 1024 cylinders" },
|
|
{ MTAB_XTD|MTAB_VUN, ID_HD30_DTYPE, NULL, "HD30",
|
|
&id_set_type, NULL, NULL, "Set HD30 Disk Type" },
|
|
{ MTAB_XTD|MTAB_VUN, ID_HD72_DTYPE, NULL, "HD72",
|
|
&id_set_type, NULL, NULL, "Set HD72 Disk Type" },
|
|
{ MTAB_XTD|MTAB_VUN, ID_HD72C_DTYPE, NULL, "HD72C",
|
|
&id_set_type, NULL, NULL, "Set HD72C Disk Type" },
|
|
{ MTAB_XTD|MTAB_VUN, ID_HD135_DTYPE, NULL, "HD135",
|
|
&id_set_type, NULL, NULL, "Set HD135 Disk Type" },
|
|
{ MTAB_XTD|MTAB_VUN, ID_HD161_DTYPE, NULL, "HD161",
|
|
&id_set_type, NULL, NULL, "Set HD161 Disk Type" },
|
|
{ MTAB_XTD|MTAB_VUN, 0, "TYPE", NULL,
|
|
NULL, &id_show_type, NULL, "Display device type" },
|
|
{ 0 }
|
|
};
|
|
|
|
DEVICE id_dev = {
|
|
"IDISK", id_unit, id_reg, id_mod,
|
|
ID_NUM_UNITS, 16, 32, 1, 16, 8,
|
|
NULL, NULL, &id_reset,
|
|
NULL, &id_attach, &id_detach, NULL,
|
|
DEV_DEBUG|DEV_DISK|DEV_SECTORS, 0, sys_deb_tab,
|
|
NULL, NULL, &id_help, NULL, NULL,
|
|
&id_description
|
|
};
|
|
|
|
#define UPDATE_INT { \
|
|
if ((id_status & ID_IRQ_MASK) && !id_srqm) { \
|
|
CPU_SET_INT(INT_DISK); \
|
|
} else { \
|
|
CPU_CLR_INT(INT_DISK); \
|
|
} \
|
|
}
|
|
|
|
static SIM_INLINE void id_set_status(uint8 flags)
|
|
{
|
|
id_status |= flags;
|
|
UPDATE_INT;
|
|
}
|
|
|
|
static SIM_INLINE void id_clr_status(uint8 flags)
|
|
{
|
|
id_status &= ~(flags);
|
|
UPDATE_INT;
|
|
}
|
|
|
|
static SIM_INLINE void id_set_srqm(t_bool state)
|
|
{
|
|
id_srqm = state;
|
|
UPDATE_INT;
|
|
}
|
|
|
|
static SIM_INLINE void id_clear_fifo()
|
|
{
|
|
id_dpr = 0;
|
|
id_dpw = 0;
|
|
}
|
|
|
|
static SIM_INLINE void id_activate(UNIT *uptr, int32 delay)
|
|
{
|
|
sim_activate_abs(uptr, delay);
|
|
}
|
|
|
|
/*
|
|
* Service routine for ID controller.
|
|
*
|
|
* The simulated HD controller must service Sense Interrupt Status,
|
|
* Specify, and Detect Error independent of the operation of either ID
|
|
* unit, which may be in the middle of a seek or other operation.
|
|
*/
|
|
t_stat id_ctlr_svc(UNIT *uptr)
|
|
{
|
|
uint8 cmd;
|
|
|
|
cmd = uptr->u4; /* The command that caused the activity */
|
|
|
|
id_set_srqm(FALSE);
|
|
id_clr_status(ID_STAT_CB);
|
|
id_set_status(ID_STAT_CEH);
|
|
uptr->u4 = 0;
|
|
|
|
switch (cmd) {
|
|
case ID_CMD_SIS:
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"INTR\t\tCOMPLETING Sense Interrupt Status.\n");
|
|
id_data[0] = id_int_status;
|
|
id_int_status = 0;
|
|
break;
|
|
default:
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"INTR\t\tCOMPLETING OTHER COMMAND 0x%x (CONTROLLER)\n",
|
|
cmd);
|
|
break;
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
* Service routine for ID0 and ID1 units.
|
|
*/
|
|
t_stat id_unit_svc(UNIT *uptr)
|
|
{
|
|
t_bool recal_error;
|
|
uint8 unit, other, cmd, end_flags;
|
|
|
|
unit = uptr->u3; /* The unit number that needs an interrupt */
|
|
cmd = uptr->u4; /* The command that caused the activity */
|
|
other = unit ^ 1; /* The number of the other unit */
|
|
|
|
recal_error = FALSE;
|
|
|
|
/* If the other unit is active, we cannot interrupt, so we delay
|
|
* here */
|
|
if (id_unit[other].u4 == ID_CMD_RDATA ||
|
|
id_unit[other].u4 == ID_CMD_WDATA) {
|
|
id_activate(uptr, 1000);
|
|
return SCPE_OK;
|
|
}
|
|
|
|
id_set_srqm(FALSE);
|
|
|
|
switch (cmd) {
|
|
case ID_CMD_RECAL:
|
|
/* Recalibrate ends in an error condition if we were never able to
|
|
reach track 0 */
|
|
recal_error = (id_state[unit].cyl != 0);
|
|
/* intentional fall-through */
|
|
case ID_CMD_SEEK:
|
|
id_int_status = 0;
|
|
uptr->u4 = 0;
|
|
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"INTR\t\tCOMPLETING Recal/Seek UNIT %d (polling=%d)\n",
|
|
unit, POLLING(unit));
|
|
|
|
if (recal_error) {
|
|
end_flags = 0;
|
|
} else {
|
|
end_flags = POLLING(unit) ? ID_STAT_SRQ : ID_STAT_CEH;
|
|
}
|
|
|
|
if (uptr->flags & UNIT_ATT) {
|
|
id_set_status(end_flags);
|
|
id_int_status |= (ID_IST_SEN|unit);
|
|
} else {
|
|
id_int_status |= (ID_IST_NR|unit);
|
|
}
|
|
|
|
id_data[0] = id_int_status;
|
|
break;
|
|
case ID_CMD_SUS:
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"INTR\t\tCOMPLETING Sense Unit Status UNIT %d\n",
|
|
unit);
|
|
id_set_status(ID_STAT_CEH);
|
|
uptr->u4 = 0;
|
|
if ((uptr->flags & UNIT_ATT) == 0) {
|
|
/* If no HD is attached, SUS puts 0x00 into the data
|
|
buffer */
|
|
id_data[0] = 0;
|
|
} else {
|
|
/* Put Unit Status into byte 0 */
|
|
id_data[0] = (ID_UST_DSEL|ID_UST_SCL|ID_UST_RDY);
|
|
if (id_state[unit].cyl == 0) {
|
|
id_data[0] |= ID_UST_TK0;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"INTR\t\tCOMPLETING OTHER COMMAND 0x%x UNIT %d\n",
|
|
cmd, unit);
|
|
id_set_status(ID_STAT_CEH);
|
|
uptr->u4 = 0;
|
|
break;
|
|
}
|
|
|
|
/* In all conditions except on a RECALIBRATE failure, CB is cleared */
|
|
if (!recal_error) {
|
|
id_clr_status(ID_STAT_CB);
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat id_set_large(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
id_large = (t_bool)val;
|
|
|
|
if (!id_large && (ID_GET_DTYPE(id_unit[0].flags) > ID_HD135_DTYPE ||
|
|
ID_GET_DTYPE(id_unit[1].flags) > ID_HD135_DTYPE)) {
|
|
return sim_messagef(SCPE_OK, "Large disk support disabled, but at least one attached disk is large!\n");
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat id_set_type(UNIT *uptr, int32 val, CONST char *cptr, void *desc)
|
|
{
|
|
if (val < 0 || val > ID_MAX_DTYPE) {
|
|
return SCPE_ARG;
|
|
}
|
|
|
|
if (uptr->flags & UNIT_ATT) {
|
|
return SCPE_ALATT;
|
|
}
|
|
|
|
uptr->flags = (uptr->flags & ~ID_DTYPE) | (val << ID_V_DTYPE);
|
|
uptr->capac = (t_addr)id_dtab[val].capac;
|
|
|
|
/*
|
|
* Warn the user after setting the type, so we can guard against accidentally
|
|
* attaching to a disk we can't use.
|
|
*/
|
|
if (val == ID_HD161_DTYPE && !id_large) {
|
|
return sim_messagef(SCPE_OK, "HD161 disks can only be used if large disk support is enabled (SET IDISK LARGE)\n");
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat id_show_type (FILE *st, UNIT *uptr, int32 val, CONST void *desc)
|
|
{
|
|
fprintf(st, "%s%s",
|
|
id_dtab[ID_GET_DTYPE(uptr->flags)].name,
|
|
id_large ? " (Large Disk Support Enabled)" : "");
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat id_reset(DEVICE *dptr)
|
|
{
|
|
id_clear_fifo();
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat id_attach(UNIT *uptr, CONST char *cptr)
|
|
{
|
|
|
|
if (ID_GET_DTYPE(uptr->flags) == ID_HD161_DTYPE && !id_large) {
|
|
return sim_messagef(SCPE_ARG, "HD161 disks can only be used if large disk support is enabled (SET IDISK LARGE)\n");
|
|
}
|
|
|
|
return sim_disk_attach_ex(uptr, cptr, 512, 1, TRUE, 0, id_dtab[ID_GET_DTYPE(uptr->flags)].name, 0, 0, NULL);
|
|
}
|
|
|
|
t_stat id_detach(UNIT *uptr)
|
|
{
|
|
return sim_disk_detach(uptr);
|
|
}
|
|
|
|
/* Return the logical block address of the given sector */
|
|
static t_lba id_lba(uint16 cyl, uint8 head, uint8 sec)
|
|
{
|
|
uint8 dtype;
|
|
|
|
dtype = ID_GET_DTYPE(id_sel_unit->flags);
|
|
|
|
return((ID_SEC_CNT * id_dtab[dtype].hd * cyl) +
|
|
(ID_SEC_CNT * head) +
|
|
sec);
|
|
}
|
|
|
|
/* At the end of each sector read or write, we update the FIFO
|
|
* with the correct return parameters. */
|
|
static void SIM_INLINE id_end_rw(uint8 est)
|
|
{
|
|
uint8 id = id_sel_unit->u3;
|
|
|
|
id_clear_fifo();
|
|
id_data[0] = est;
|
|
id_data[1] = id_state[id].phn;
|
|
id_data[2] = ~(id_state[id].lcnh);
|
|
id_data[3] = id_state[id].lcnl;
|
|
id_data[4] = id_state[id].lhn;
|
|
id_data[5] = id_state[id].lsn;
|
|
id_data[6] = id_state[id].scnt;
|
|
}
|
|
|
|
/* The controller wraps id_lsn, id_lhn, and id_lcnl on each sector
|
|
* read, so that they point to the next C/H/S */
|
|
static void SIM_INLINE id_update_chs()
|
|
{
|
|
uint8 id = id_sel_unit->u3;
|
|
|
|
if (id_state[id].lsn++ >= id_esn) {
|
|
id_state[id].lsn = 0;
|
|
if (id_state[id].lhn++ >= id_etn) {
|
|
id_state[id].lhn = 0;
|
|
if (id_state[id].lcnl == 0xff) {
|
|
id_state[id].lcnl = 0;
|
|
id_state[id].lcnh++;
|
|
} else {
|
|
id_state[id].lcnl++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 id_read(uint32 pa, size_t size)
|
|
{
|
|
uint8 reg, id;
|
|
uint16 cyl;
|
|
t_lba lba;
|
|
uint32 data;
|
|
t_seccnt sectsread;
|
|
|
|
id = id_sel_unit->u3;
|
|
reg = (uint8) (pa - IDBASE);
|
|
|
|
switch(reg) {
|
|
case ID_DATA_REG: /* Data Buffer Register */
|
|
/* If we're in a DMA transfer, we need to be reading data from
|
|
* the disk buffer. Otherwise, we're reading from the FIFO. */
|
|
|
|
if (id_drq) {
|
|
/* If the drive isn't attached, there's really nothing we
|
|
can do. */
|
|
if ((id_sel_unit->flags & UNIT_ATT) == 0) {
|
|
id_end_rw(ID_EST_NR);
|
|
return 0;
|
|
}
|
|
|
|
/* We could be in one of these commands:
|
|
* - Read Data
|
|
* - Read ID
|
|
*/
|
|
|
|
if (CMD_NUM == ID_CMD_RDATA) {
|
|
/* If we're still in DRQ but we've read all our sectors,
|
|
* that's an error state. */
|
|
if (id_state[id].scnt == 0) {
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"ERROR\tid_scnt = 0 but still in dma\n");
|
|
id_end_rw(ID_EST_OVR);
|
|
return 0;
|
|
}
|
|
|
|
/* If the disk buffer is empty, fill it. */
|
|
if (id_buf_ptr == 0 || id_buf_ptr >= ID_SEC_SIZE) {
|
|
/* It's time to read a new sector into our sector buf */
|
|
id_buf_ptr = 0;
|
|
cyl = (uint16) (((uint16)id_state[id].lcnh << 8)|(uint16)id_state[id].lcnl);
|
|
id_state[id_unit_num].cyl = cyl;
|
|
lba = id_lba(cyl, id_state[id].lhn, id_state[id].lsn);
|
|
if (sim_disk_rdsect(id_sel_unit, lba, id_buf, §sread, 1) == SCPE_OK) {
|
|
if (sectsread !=1) {
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"ERROR: ASKED TO READ ONE SECTOR, READ: %d\n",
|
|
sectsread);
|
|
}
|
|
id_update_chs();
|
|
} else {
|
|
/* Uh-oh! */
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"RDATA READ ERROR. Failure from sim_disk_rdsect!\n");
|
|
id_end_rw(ID_EST_DER);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
data = id_buf[id_buf_ptr++];
|
|
sim_debug(READ_MSG, &id_dev, "DATA\t%02x\n", data);
|
|
|
|
/* Done with this current sector, update id_scnt */
|
|
if (id_buf_ptr >= ID_SEC_SIZE) {
|
|
if (--id_state[id].scnt == 0) {
|
|
id_end_rw(0);
|
|
}
|
|
}
|
|
} else if (CMD_NUM == ID_CMD_RID) {
|
|
/* We have to return the ID bytes for the current C/H/S */
|
|
if (id_idfield_ptr == 0 || id_idfield_ptr >= ID_IDFIELD_LEN) {
|
|
id_idfield[0] = ~(id_state[id].lcnh);
|
|
id_idfield[1] = id_state[id].lcnl;
|
|
id_idfield[2] = id_state[id].lhn;
|
|
id_idfield[3] = id_state[id].lsn;
|
|
id_idfield_ptr = 0;
|
|
}
|
|
|
|
data = id_idfield[id_idfield_ptr++];
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"ID DATA\t%02x\n",
|
|
data);
|
|
|
|
if (id_idfield_ptr >= ID_IDFIELD_LEN) {
|
|
if (id_state[id].scnt-- > 0) {
|
|
/* Another sector to ID */
|
|
id_idfield_ptr = 0;
|
|
} else {
|
|
/* All done, set return codes */
|
|
id_clear_fifo();
|
|
id_data[0] = 0;
|
|
id_data[1] = id_state[id].scnt;
|
|
}
|
|
}
|
|
} else {
|
|
/* cmd not Read Data or Read ID */
|
|
stop_reason = STOP_ERR;
|
|
return 0;
|
|
}
|
|
|
|
return data;
|
|
} else {
|
|
if (id_dpr < ID_FIFO_LEN) {
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"DATA\t%02x\n",
|
|
id_data[id_dpr]);
|
|
return id_data[id_dpr++];
|
|
} else {
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"ERROR\tFIFO OVERRUN\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
break;
|
|
case ID_CMD_STAT_REG: /* Status Register */
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"STATUS\t%02x\n",
|
|
id_status|id_drq);
|
|
return id_status|(id_drq ? 1u : 0);
|
|
}
|
|
|
|
sim_debug(READ_MSG, &id_dev,
|
|
"Read of unsuported register %x\n",
|
|
id_status);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void id_write(uint32 pa, uint32 val, size_t size)
|
|
{
|
|
uint8 reg, id;
|
|
uint16 cyl;
|
|
t_lba lba;
|
|
t_seccnt sectswritten;
|
|
|
|
reg = (uint8) (pa - IDBASE);
|
|
id = id_sel_unit->u3;
|
|
|
|
switch(reg) {
|
|
case ID_DATA_REG:
|
|
/* If we're in a DMA transfer, we need to be writing data to
|
|
* the disk buffer. Otherwise, we're writing to the FIFO. */
|
|
|
|
if (id_drq) {
|
|
/* If we're still in DRQ but we've written all our sectors,
|
|
* that's an error state. */
|
|
if (id_state[id].scnt == 0) {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"ERROR\tid_scnt = 0 but still in dma\n");
|
|
id_end_rw(ID_EST_OVR);
|
|
return;
|
|
}
|
|
|
|
/* Write to the disk buffer */
|
|
if (id_buf_ptr < ID_SEC_SIZE) {
|
|
id_buf[id_buf_ptr++] = (uint8)(val & 0xff);
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"DATA\t%02x\n",
|
|
(uint8)(val & 0xff));
|
|
} else {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"ERROR\tWDATA OVERRUN\n");
|
|
id_end_rw(ID_EST_OVR);
|
|
return;
|
|
}
|
|
|
|
/* If we've hit the end of a sector, flush it */
|
|
if (id_buf_ptr >= ID_SEC_SIZE) {
|
|
/* It's time to start the next sector, and flush the old. */
|
|
id_buf_ptr = 0;
|
|
cyl = (uint16) (((uint16) id_state[id].lcnh << 8)|(uint16)id_state[id].lcnl);
|
|
id_state[id].cyl = cyl;
|
|
lba = id_lba(cyl, id_state[id].lhn, id_state[id].lsn);
|
|
if (sim_disk_wrsect(id_sel_unit, lba, id_buf, §swritten, 1) == SCPE_OK) {
|
|
if (sectswritten !=1) {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"ERROR: ASKED TO WRITE ONE SECTOR, WROTE: %d\n",
|
|
sectswritten);
|
|
}
|
|
id_update_chs();
|
|
if (--id_state[id].scnt == 0) {
|
|
id_end_rw(0);
|
|
}
|
|
} else {
|
|
/* Uh-oh! */
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"ERROR\tWDATA WRITE ERROR. lba=%04x\n",
|
|
lba);
|
|
id_end_rw(ID_EST_DER);
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"DATA\t%02x\n",
|
|
val);
|
|
if (id_dpw < ID_FIFO_LEN) {
|
|
id_data[id_dpw++] = (uint8) val;
|
|
} else {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"ERROR\tFIFO OVERRUN\n");
|
|
}
|
|
}
|
|
return;
|
|
case ID_CMD_STAT_REG:
|
|
id_handle_command((uint8) val);
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
void id_handle_command(uint8 val)
|
|
{
|
|
uint8 cmd, aux_cmd, sec, pattern, id;
|
|
uint16 cyl;
|
|
uint32 time;
|
|
t_lba lba;
|
|
|
|
/* Reset the FIFO pointer */
|
|
id_clear_fifo();
|
|
|
|
/* Is this an aux command or a full command? */
|
|
if ((val & 0xf0) == 0) {
|
|
aux_cmd = val & 0x0f;
|
|
|
|
if (aux_cmd & ID_AUX_CLCE) {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tAUX:CLCE\n",
|
|
val);
|
|
id_clr_status(ID_STAT_CEH|ID_STAT_CEL);
|
|
}
|
|
|
|
if (aux_cmd & ID_AUX_HSRQ) {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tAUX:HSRQ\n",
|
|
val);
|
|
id_set_srqm(TRUE);
|
|
}
|
|
|
|
if (aux_cmd & ID_AUX_CLB) {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tAUX:CLBUF\n",
|
|
val);
|
|
id_clear_fifo();
|
|
}
|
|
|
|
if (aux_cmd & ID_AUX_RST) {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tAUX:RESET\n",
|
|
val);
|
|
id_clear_fifo();
|
|
sim_cancel(id_sel_unit);
|
|
sim_cancel(id_ctlr_unit);
|
|
id_status = 0;
|
|
id_set_srqm(FALSE);
|
|
}
|
|
|
|
/* Just return early */
|
|
return;
|
|
}
|
|
|
|
/* If the controller is busy and this isn't an AUX command, do
|
|
* nothing */
|
|
if (id_status & ID_STAT_CB) {
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"Controller Busy. Skipping command byte %02x\n",
|
|
val);
|
|
return;
|
|
}
|
|
|
|
/* A full command always resets CEH and CEL */
|
|
id_clr_status(ID_STAT_CEH|ID_STAT_CEL);
|
|
|
|
/* Save the full command byte */
|
|
id_cmd = val;
|
|
cmd = (id_cmd >> 4) & 0xf;
|
|
|
|
if ((id_cmd & 3) != id_ua) {
|
|
id_unit_num = id_cmd & 1;
|
|
id_ua = id_cmd & 3;
|
|
id_sel_unit = &id_unit[id_unit_num];
|
|
}
|
|
|
|
id = id_unit_num;
|
|
|
|
/* TODO: Fix this hack */
|
|
if (cmd == ID_CMD_SIS || cmd == ID_CMD_SPEC || cmd == ID_CMD_DERR) {
|
|
id_ctlr_unit->u4 = cmd;
|
|
} else {
|
|
id_sel_unit->u4 = cmd;
|
|
}
|
|
|
|
id_set_status(ID_STAT_CB);
|
|
|
|
switch(cmd) {
|
|
case ID_CMD_SIS:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tSense Int. Status\n",
|
|
val);
|
|
id_clr_status(ID_STAT_SRQ); /* SIS immediately de-asserts SRQ */
|
|
id_activate(id_ctlr_unit, ID_SIS_WAIT);
|
|
break;
|
|
case ID_CMD_SPEC:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tSpecify - ETN=%02x ESN=%02x\n",
|
|
val, id_data[3], id_data[4]);
|
|
id_dtlh = id_data[1];
|
|
id_etn = id_data[3];
|
|
id_esn = id_data[4];
|
|
id_activate(id_ctlr_unit, ID_SPEC_WAIT);
|
|
break;
|
|
case ID_CMD_SUS:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tSense Unit Status - %d\n",
|
|
val, id_ua);
|
|
id_activate(id_sel_unit, ID_SUS_WAIT);
|
|
break;
|
|
case ID_CMD_DERR:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tDetect Error\n",
|
|
val);
|
|
id_activate(id_ctlr_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_RECAL:
|
|
id_buffered = (val & 0x08) == 0x08;
|
|
time = MAX(ID_STEP_MIN, id_state[id].cyl * ID_STEP_WAIT);
|
|
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tRecalibrate - %d - (buffered=%d, polling=%d, delay=%u)\n",
|
|
val, id_ua, id_buffered, POLLING(id), time);
|
|
|
|
if (POLLING(id)) {
|
|
id_set_status(ID_STAT_CEH);
|
|
}
|
|
|
|
/* If large disk support hasn't been enabled, and the starting cylinder we're
|
|
* recalibrating from is > 1023, we can't reach track 0! */
|
|
if (!id_large && id_state[id].cyl > 1023) {
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"WARNING: RECALIBRATE - Starting cylinder %d is > 1023!\n",
|
|
id_state[id].cyl);
|
|
|
|
id_state[id].cyl -= 1023;
|
|
} else {
|
|
id_state[id].cyl = 0;
|
|
}
|
|
|
|
id_activate(id_sel_unit, time);
|
|
break;
|
|
case ID_CMD_SEEK:
|
|
id_buffered = (val & 0x08) == 0x08;
|
|
id_state[id].lcnh = id_data[0];
|
|
id_state[id].lcnl = id_data[1];
|
|
cyl = ((uint16)id_state[id].lcnh) << 8 | (uint16)id_state[id].lcnl;
|
|
time = MAX(ID_STEP_MIN, ID_STEP_WAIT * (cyl > id_state[id].cyl ? cyl - id_state[id].cyl : id_state[id].cyl - cyl));
|
|
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tSeek - %d - CYL=%u BUF=%d POL=%d DELAY=%u\n",
|
|
val, id_ua, cyl, id_buffered, POLLING(id), time);
|
|
|
|
if (POLLING(id)) {
|
|
id_set_status(ID_STAT_CEH);
|
|
}
|
|
|
|
id_state[id].cyl = cyl;
|
|
id_activate(id_sel_unit, time);
|
|
break;
|
|
case ID_CMD_FMT:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tFormat - %d\n",
|
|
val, id_ua);
|
|
|
|
id_state[id].phn = id_data[0];
|
|
id_state[id].scnt = id_data[1];
|
|
pattern = id_data[2];
|
|
|
|
/* Format scnt sectors with the given pattern, if attached */
|
|
if (id_sel_unit->flags & UNIT_ATT) {
|
|
/* Formatting soft-sectored disks always begins at sector 0 */
|
|
sec = 0;
|
|
|
|
while (id_state[id].scnt-- > 0) {
|
|
/* Write one sector of pattern */
|
|
for (id_buf_ptr = 0; id_buf_ptr < ID_SEC_SIZE; id_buf_ptr++) {
|
|
id_buf[id_buf_ptr] = pattern;
|
|
}
|
|
lba = id_lba(id_state[id].cyl, id_state[id].phn, sec++);
|
|
if (sim_disk_wrsect(id_sel_unit, lba, id_buf, NULL, 1) == SCPE_OK) {
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"FORMAT: PHN=%d SCNT=%d PAT=%02x LBA=%04x\n",
|
|
id_state[id].phn, id_state[id].scnt, pattern, lba);
|
|
} else {
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"FORMAT FAILED! PHN=%d SCNT=%d PAT=%02x LBA=%04x\n",
|
|
id_state[id].phn, id_state[id].scnt, pattern, lba);
|
|
break;
|
|
}
|
|
}
|
|
|
|
id_data[0] = 0;
|
|
} else {
|
|
/* Not attached */
|
|
id_data[0] = ID_EST_NR;
|
|
}
|
|
|
|
id_data[1] = id_state[id].scnt;
|
|
|
|
id_activate(id_sel_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_VID:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tVerify ID - %d\n",
|
|
val, id_ua);
|
|
id_data[0] = 0;
|
|
id_data[1] = 0x05; /* What do we put here? */
|
|
id_activate(id_sel_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_RID:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tRead ID - %d\n",
|
|
val, id_ua);
|
|
if (id_sel_unit->flags & UNIT_ATT) {
|
|
id_drq = TRUE;
|
|
|
|
/* Grab our arguments */
|
|
id_state[id].phn = id_data[0];
|
|
id_state[id].scnt = id_data[1];
|
|
|
|
/* Compute logical values used by ID verification */
|
|
id_state[id]. lhn = id_state[id].phn;
|
|
id_state[id].lsn = 0;
|
|
} else {
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"UNIT %d NOT ATTACHED, CANNOT READ ID.\n",
|
|
id_ua);
|
|
}
|
|
id_activate(id_sel_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_RDIAG:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tRead Diag - %d\n",
|
|
val, id_ua);
|
|
id_activate(id_sel_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_RDATA:
|
|
/* Grab our arguments */
|
|
id_state[id].phn = id_data[0];
|
|
id_state[id].lcnh = ~(id_data[1]);
|
|
id_state[id].lcnl = id_data[2];
|
|
id_state[id].lhn = id_data[3];
|
|
id_state[id].lsn = id_data[4];
|
|
id_state[id].scnt = id_data[5];
|
|
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tRead Data - %d - CYL=%u PH=%u LH=%u SEC=%u SCNT=%u\n",
|
|
val, id_ua,
|
|
(uint16)id_state[id].lcnh << 8 | (uint16)id_state[id].lcnl,
|
|
id_data[0], id_data[3], id_data[4], id_data[5]);
|
|
|
|
if (id_sel_unit->flags & UNIT_ATT) {
|
|
id_drq = TRUE;
|
|
id_buf_ptr = 0;
|
|
} else {
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"UNIT %d NOT ATTACHED, CANNOT READ DATA.\n",
|
|
id_ua);
|
|
}
|
|
|
|
id_activate(id_sel_unit, ID_RW_WAIT);
|
|
break;
|
|
case ID_CMD_CHECK:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tCheck - %d\n",
|
|
val, id_ua);
|
|
id_activate(id_sel_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_SCAN:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tScan - %d\n",
|
|
val, id_ua);
|
|
id_activate(id_sel_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_VDATA:
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tVerify Data - %d\n",
|
|
val, id_ua);
|
|
id_activate(id_sel_unit, ID_CMD_WAIT);
|
|
break;
|
|
case ID_CMD_WDATA:
|
|
/* Grab our arguments */
|
|
id_state[id].phn = id_data[0];
|
|
id_state[id].lcnh = ~(id_data[1]);
|
|
id_state[id].lcnl = id_data[2];
|
|
id_state[id].lhn = id_data[3];
|
|
id_state[id].lsn = id_data[4];
|
|
id_state[id].scnt = id_data[5];
|
|
|
|
sim_debug(WRITE_MSG, &id_dev,
|
|
"COMMAND\t%02x\tWrite Data - %d - CYL=%u PH=%u LH=%u SEC=%u SCNT=%u\n",
|
|
val, id_ua,
|
|
(uint16)id_state[id].lcnh << 8 | (uint16)id_state[id].lcnl,
|
|
id_data[0], id_data[3], id_data[4], id_data[5]);
|
|
|
|
if (id_sel_unit->flags & UNIT_ATT) {
|
|
id_drq = TRUE;
|
|
id_buf_ptr = 0;
|
|
} else {
|
|
sim_debug(EXECUTE_MSG, &id_dev,
|
|
"UNIT %d NOT ATTACHED, CANNOT WRITE.\n",
|
|
id_ua);
|
|
}
|
|
id_activate(id_sel_unit, ID_RW_WAIT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void id_after_dma()
|
|
{
|
|
id_clr_status(ID_STAT_DRQ);
|
|
id_drq = FALSE;
|
|
}
|
|
|
|
CONST char *id_description(DEVICE *dptr)
|
|
{
|
|
return "Integrated Hard Disk";
|
|
}
|
|
|
|
t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
fprintf(st, "Integrated Hard Disk (IDISK)\n\n");
|
|
fprintf(st, "The IDISK device implements the integrated MFM hard disk of the\n");
|
|
fprintf(st, "3B2/400. Up to two drives are supported on a single controller.\n\n");
|
|
fprintf(st, "Supported device types are:\n\n");
|
|
fprintf(st, " Name Size ID Cyl Head Sec Byte/Sec Description\n");
|
|
fprintf(st, " ---- -------- -- ---- ---- --- -------- ----------------------\n");
|
|
fprintf(st, " HD30 30.6 MB 3 697 5 18 512 CDC Wren 94155-36\n");
|
|
fprintf(st, " HD72 73.2 MB 5 925 9 18 512 CDC Wren II 94156-86\n");
|
|
fprintf(st, " HD72C 72.9 MB 8 754 11 18 512 Fujitsu M2243AS\n");
|
|
fprintf(st, " HD135 135.0 MB 11 1024 15 18 512 Maxtor XT-2190\n");
|
|
if (id_large) {
|
|
fprintf(st, " HD161 161.4 MB 11 1224 15 18 512 Maxtor XT-2190\n\n");
|
|
}
|
|
fprintf(st, "The drive ID and geometry values are used when low-level formatting a\n");
|
|
fprintf(st, "drive using the AT&T 'idtools' utility.\n");
|
|
|
|
fprint_set_help(st, dptr);
|
|
fprint_show_help(st, dptr);
|
|
fprint_reg_help(st, dptr);
|
|
|
|
return SCPE_OK;
|
|
}
|