/* 3b2_cpu.h: AT&T 3B2 Model 400 Hard Disk (uPD7261) Implementation | |
Copyright (c) 2017, 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. | |
*/ | |
/* | |
* The larger of the two hard drive options shipped with the AT&T 3B2 | |
* was a 72MB Wren II ST-506 MFM hard disk. That's what we emulate | |
* here, as a start. | |
* | |
* Formatted Capacity: 76,723,200 Bytes | |
* | |
* Cylinders: 925 | |
* Sectors/Track: 18 | |
* Heads: 9 | |
* Bytes/Sector: 512 | |
* Avg. seek: 28 ms | |
* Seek/track: 5 ms | |
* | |
* Drive Information from the 3B2 FAQ: | |
* | |
* Drive type drv id cyls trk/cyl sec/trk byte/cyl abbrev | |
* --------------- ------ ---- ------- ------- -------- ------ | |
* Wren II 30MB 3 697 5 18 512 HD30 | |
* Wren II 72MB 5 925 9 18 512 HD72 | |
* Fujitsu M2243AS 8 754 11 18 512 HD72C | |
* Micropolis 1325 5 1024 8 18 512 HD72 | |
* Maxtor 1140 4* 918= 15 18 512 HD120 | |
* Maxtor 1190 11 1224+ 15 18 512 HD135 | |
* | |
*/ | |
#include <assert.h> | |
#include "3b2_id.h" | |
/* Wait times, in CPU steps, for various actions */ | |
/* Each step is 50 us in buffered mode */ | |
#define ID_SEEK_WAIT 100 /* us */ | |
#define ID_SEEK_BASE 700 /* us */ | |
#define ID_RECAL_WAIT 6000 /* us */ | |
/* Reading data takes about 8ms per sector, plus time to seek if not | |
on cylinder */ | |
#define ID_RW_WAIT 8000 /* us */ | |
/* Sense Unit Status completes in about 200 us */ | |
#define ID_SUS_WAIT 200 /* us */ | |
/* Specify takes a bit longer, 1.25 ms */ | |
#define ID_SPEC_WAIT 1250 /* us */ | |
/* Sense Interrupt Status is about 142 us */ | |
#define ID_SIS_WAIT 142 /* us */ | |
/* The catch-all command wait time is about 140 us */ | |
#define ID_CMD_WAIT 140 /* us */ | |
/* State. The DP7261 supports four MFM (winchester) disks connected | |
simultaneously. There is only one set of registers, however, so | |
commands must be completed for one unit before they can begin on | |
another unit. */ | |
/* Data FIFO pointer - Read */ | |
uint8 id_dpr = 0; | |
/* Data FIFO pointer - Write */ | |
uint8 id_dpw = 0; | |
/* Selected unit */ | |
uint8 id_sel = 0; | |
/* Controller Status Register */ | |
uint8 id_status = 0; | |
/* Unit Interrupt Status */ | |
uint8 id_int_status; | |
/* Last command received */ | |
uint8 id_cmd = 0; | |
/* DMAC request */ | |
t_bool id_drq = FALSE; | |
/* 8-byte FIFO */ | |
uint8 id_data[ID_FIFO_LEN] = {0}; | |
/* INT output pin */ | |
t_bool id_irq = FALSE; | |
/* Special flag for seek end SIS */ | |
t_bool id_seek_sis = FALSE; | |
/* State of each drive */ | |
/* Cylinder the drive is positioned on */ | |
uint16 id_cyl[ID_NUM_UNITS] = {0}; | |
/* DTLH byte for each drive */ | |
uint8 id_dtlh[ID_NUM_UNITS] = {0}; | |
/* Arguments of last READ, WRITE, VERIFY ID, or READ ID command */ | |
/* Ending Track Number (from Specify) */ | |
uint8 id_etn = 0; | |
/* Ending Sector Number (from Specify) */ | |
uint8 id_esn = 0; | |
/* Physical sector number */ | |
uint8 id_psn = 0; | |
/* Physical head number */ | |
uint8 id_phn = 0; | |
/* Logical cylinder number, high byte */ | |
uint8 id_lcnh = 0; | |
/* Logical cylinder number, low byte */ | |
uint8 id_lcnl = 0; | |
/* Logical head number */ | |
uint8 id_lhn = 0; | |
/* Logical sector number */ | |
uint8 id_lsn = 0; | |
/* Number of sectors to transfer, decremented after each sector */ | |
uint8 id_scnt = 0; | |
/* Sector buffer */ | |
uint8 id_buf[ID_SEC_SIZE]; | |
/* Buffer pointer */ | |
size_t id_buf_ptr = 0; | |
uint8 id_idfield[ID_IDFIELD_LEN]; | |
uint8 id_idfield_ptr = 0; | |
/* | |
* TODO: Macros used for debugging timers. Remove when debugging is complete. | |
*/ | |
double id_start_time; | |
#define ID_START_TIME() { id_start_time = sim_gtime(); } | |
#define ID_DIFF_MS() ((sim_gtime() - id_start_time) / INST_PER_MS) | |
UNIT id_unit[] = { | |
{UDATA (&id_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK, ID_DSK_SIZE), 0, 0 }, | |
{UDATA (&id_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK, ID_DSK_SIZE), 0, 1 }, | |
{ NULL } | |
}; | |
/* The currently selected drive number */ | |
UNIT *id_sel_unit = &id_unit[0]; | |
REG id_reg[] = { | |
{ HRDATAD(CMD, id_cmd, 8, "Command") }, | |
{ HRDATAD(STAT, id_status, 8, "Status") }, | |
{ BRDATAD(CYL, id_cyl, 8, 8, ID_NUM_UNITS, "Track") }, | |
{ NULL } | |
}; | |
DEVICE id_dev = { | |
"ID", id_unit, id_reg, NULL, | |
ID_NUM_UNITS, 16, 32, 1, 16, 8, | |
NULL, NULL, &id_reset, | |
NULL, &id_attach, &id_detach, NULL, | |
DEV_DEBUG|DEV_SECTORS, 0, sys_deb_tab, | |
NULL, NULL, &id_help, NULL, NULL, | |
&id_description | |
}; | |
/* Function implementation */ | |
static SIM_INLINE void id_activate(uint32 delay) | |
{ | |
ID_START_TIME(); | |
sim_activate_abs(id_sel_unit, (int32) delay); | |
} | |
static SIM_INLINE void id_clear_fifo() | |
{ | |
id_dpr = 0; | |
id_dpw = 0; | |
} | |
t_stat id_svc(UNIT *uptr) | |
{ | |
/* Complete the last command */ | |
id_status = ID_STAT_CEH; | |
switch (CMD_NUM) { | |
case ID_CMD_SEEK: /* fall-through */ | |
case ID_CMD_RECAL: | |
/* SRQ is only set in polling mode (POL bit is 0) */ | |
if ((id_dtlh[UNIT_NUM] & ID_DTLH_POLL) == 0) { | |
id_status |= ID_STAT_SRQ; | |
} | |
if (uptr->flags & UNIT_ATT) { | |
id_int_status = ID_IST_SEN|(uint8)uptr->ID_UNIT_NUM; | |
} else { | |
id_int_status = ID_IST_NR|(uint8)uptr->ID_UNIT_NUM; | |
} | |
break; | |
case ID_CMD_SIS: | |
if (!id_seek_sis) { | |
id_status = ID_STAT_CEL; | |
} | |
id_seek_sis = FALSE; | |
id_data[0] = id_int_status; | |
id_status &= ~ID_STAT_SRQ; | |
break; | |
case ID_CMD_SUS: | |
if ((id_sel_unit->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_cyl[UNIT_NUM] == 0) { | |
id_data[0] |= ID_UST_TK0; | |
} | |
} | |
break; | |
default: | |
break; | |
} | |
sim_debug(EXECUTE_MSG, &id_dev, | |
"[%08x] \tINTR\t\tDELTA=%f ms\n", | |
R[NUM_PC], ID_DIFF_MS()); | |
id_irq = TRUE; | |
return SCPE_OK; | |
} | |
t_stat id_reset(DEVICE *dptr) | |
{ | |
id_clear_fifo(); | |
return SCPE_OK; | |
} | |
t_stat id_attach(UNIT *uptr, CONST char *cptr) | |
{ | |
return sim_disk_attach(uptr, cptr, 512, 1, TRUE, 0, "HD72", 0, 0); | |
} | |
t_stat id_detach(UNIT *uptr) | |
{ | |
return sim_disk_detach(uptr); | |
} | |
/* Return the logical block address of the given sector */ | |
static SIM_INLINE t_lba id_lba(uint16 cyl, uint8 head, uint8 sec) | |
{ | |
return((ID_SEC_CNT * ID_HEADS * 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) { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
">>> ending R/W with status: %02x\n", | |
est); | |
id_dpr = 0; | |
id_dpw = 0; | |
id_data[0] = est; | |
id_data[1] = id_phn; | |
id_data[2] = ~(id_lcnh); | |
id_data[3] = id_lcnl; | |
id_data[4] = id_lhn; | |
id_data[5] = id_lsn; | |
id_data[6] = 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() { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
">>> id_update_chs(): id_esn=%02x id_etn=%02x\n", | |
id_esn, id_etn); | |
if (id_lsn++ >= id_esn) { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
">>> id_update_chs(): id_lsn reset to 0. id_lhn is %02x\n", | |
id_lhn); | |
id_lsn = 0; | |
if (id_lhn++ >= id_etn) { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
">>> id_update_chs(): id_lhn reset to 0. id_lcnl is %02x\n", | |
id_lcnl); | |
id_lhn = 0; | |
if (id_lcnl == 0xff) { | |
id_lcnl = 0; | |
id_lcnh++; | |
} else { | |
id_lcnl++; | |
} | |
} | |
} | |
} | |
uint32 id_read(uint32 pa, size_t size) { | |
uint8 reg; | |
uint16 cyl; | |
t_lba lba; | |
uint32 data; | |
t_seccnt sectsread; | |
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_scnt == 0) { | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x] ERROR\tid_scnt = 0 but still in dma\n", | |
R[NUM_PC]); | |
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_lcnh << 8)|(uint16)id_lcnl); | |
id_cyl[UNIT_NUM] = cyl; | |
lba = id_lba(cyl, id_lhn, 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, | |
"[%08x]\tERROR: ASKED TO READ ONE SECTOR, READ: %d\n", | |
R[NUM_PC], sectsread); | |
} | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x] \tRDATA\tCYL=%d PHN=%d LCNH=%02x " | |
"LCNL=%02x LHN=%d LSN=%d SCNT=%d LBA=%04x\n", | |
R[NUM_PC], cyl, id_phn, id_lcnh, id_lcnl, | |
id_lhn, id_lsn, id_scnt-1, lba); | |
id_update_chs(); | |
} else { | |
/* Uh-oh! */ | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x]\tRDATA READ ERROR. Failure from sim_disk_rdsect!\n", | |
R[NUM_PC]); | |
id_end_rw(ID_EST_DER); | |
return 0; | |
} | |
} | |
data = id_buf[id_buf_ptr++]; | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x]\tSECTOR DATA\t%02x\t(%c)\n", | |
R[NUM_PC], data, (data >= 0x20 && data < 0x7f) ? data : '.'); | |
/* Done with this current sector, update id_scnt */ | |
if (id_buf_ptr >= ID_SEC_SIZE) { | |
if (--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_lcnh); | |
id_idfield[1] = id_lcnl; | |
id_idfield[2] = id_lhn; | |
id_idfield[3] = id_lsn; | |
id_idfield_ptr = 0; | |
} | |
data = id_idfield[id_idfield_ptr++]; | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x]\tID DATA\t%02x\n", | |
R[NUM_PC], data); | |
if (id_idfield_ptr >= ID_IDFIELD_LEN) { | |
if (id_scnt-- > 0) { | |
/* Another sector to ID */ | |
id_idfield_ptr = 0; | |
} else { | |
/* All done, set return codes */ | |
id_dpr = 0; | |
id_dpw = 0; | |
id_data[0] = 0; | |
id_data[1] = id_scnt; | |
} | |
} | |
} else { | |
assert(0); // cmd not Read Data or Read ID | |
} | |
return data; | |
} else { | |
if (id_dpr < ID_FIFO_LEN) { | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x]\tDATA\t%02x\n", | |
R[NUM_PC], id_data[id_dpr]); | |
return id_data[id_dpr++]; | |
} else { | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x] ERROR\tFIFO OVERRUN\n", | |
R[NUM_PC]); | |
return 0; | |
} | |
} | |
break; | |
case ID_CMD_STAT_REG: /* Status Register */ | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x]\tSTATUS\t%02x\n", | |
R[NUM_PC], id_status|id_drq); | |
return id_status|(id_drq ? 1u : 0); | |
} | |
sim_debug(READ_MSG, &id_dev, | |
"[%08x] Read of unsuported register %x\n", | |
R[NUM_PC], id_status); | |
return 0; | |
} | |
void id_write(uint32 pa, uint32 val, size_t size) | |
{ | |
uint8 reg; | |
uint16 cyl; | |
t_lba lba; | |
t_seccnt sectswritten; | |
reg = (uint8) (pa - IDBASE); | |
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_scnt == 0) { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x] ERROR\tid_scnt = 0 but still in dma\n", | |
R[NUM_PC]); | |
id_end_rw(ID_EST_OVR); | |
return; | |
} | |
/* Write to the disk buffer */ | |
if (id_buf_ptr < ID_SEC_SIZE) { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tSECTOR DATA\t%02x\t(%c)\n", | |
R[NUM_PC], val, (val >= 0x20 && val < 0x7f) ? val : '.'); | |
id_buf[id_buf_ptr++] = (uint8)(val & 0xff); | |
} else { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x] ERROR\tWDATA OVERRUN\n", | |
R[NUM_PC]); | |
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_lcnh << 8)|(uint16)id_lcnl); | |
id_cyl[UNIT_NUM] = cyl; | |
lba = id_lba(cyl, id_lhn, 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, | |
"[%08x]\tERROR: ASKED TO WRITE ONE SECTOR, WROTE: %d\n", | |
R[NUM_PC], sectswritten); | |
} | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tWDATA\tCYL=%d PHN=%d LCNH=%02x " | |
"LCNL=%02x LHN=%d LSN=%d SCNT=%d LBA=%04x\n", | |
R[NUM_PC], cyl, id_phn, id_lcnh, id_lcnl, | |
id_lhn, id_lsn, id_scnt, lba); | |
id_update_chs(); | |
if (--id_scnt == 0) { | |
id_end_rw(0); | |
} | |
} else { | |
/* Uh-oh! */ | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x] ERROR\tWDATA WRITE ERROR. lba=%04x\n", | |
R[NUM_PC], lba); | |
id_end_rw(ID_EST_DER); | |
return; | |
} | |
} | |
return; | |
} else { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tDATA\t%02x\n", | |
R[NUM_PC], val); | |
if (id_dpw < ID_FIFO_LEN) { | |
id_data[id_dpw++] = (uint8) val; | |
} else { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x] ERROR\tFIFO OVERRUN\n", | |
R[NUM_PC]); | |
} | |
} | |
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; | |
uint16 cyl; | |
uint32 time; | |
t_lba lba; | |
/* Save the full command byte */ | |
id_cmd = val; | |
/* Reset the FIFO pointer */ | |
id_dpr = 0; | |
id_dpw = 0; | |
/* Writing a command always de-asserts INT output, UNLESS | |
the SRQ bit is set. */ | |
if ((id_status & ID_STAT_SRQ) != ID_STAT_SRQ) { | |
id_irq = FALSE; | |
} | |
/* Is this an aux command or a full command? */ | |
if ((val & 0xf0) == 0) { | |
aux_cmd = val & 0x0f; | |
id_status &= ~(ID_STAT_CB); | |
if (aux_cmd & ID_AUX_CLCE) { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x] \tCOMMAND\t%02x\tAUX:CLCE\n", | |
R[NUM_PC], val); | |
id_status &= ~(ID_STAT_CEL|ID_STAT_CEH); | |
sim_cancel(id_sel_unit); | |
} | |
if (aux_cmd & ID_AUX_HSRQ) { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x] \tCOMMAND\t%02x\tAUX:HSRQ\n", | |
R[NUM_PC], val); | |
id_status &= ~ID_STAT_SRQ; | |
sim_cancel(id_sel_unit); | |
} | |
if (aux_cmd & ID_AUX_CLB) { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tAUX:CLBUF\n", | |
R[NUM_PC], val); | |
id_clear_fifo(); | |
} | |
if (aux_cmd & ID_AUX_RST) { | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tAUX:RESET\n", | |
R[NUM_PC], val); | |
sim_cancel(id_sel_unit); | |
id_clear_fifo(); | |
} | |
/* Just return early */ | |
return; | |
} | |
/* Now that we know it's not an aux command, get the unit number | |
this command is for */ | |
id_sel_unit = &id_unit[UNIT_NUM]; | |
cmd = (id_cmd >> 4) & 0xf; | |
/* If this command is anything BUT a sense interrupt status, set | |
* the seek flag to false. | |
*/ | |
if (cmd != ID_CMD_SIS) { | |
id_seek_sis = FALSE; | |
} | |
id_status = ID_STAT_CB; | |
switch(cmd) { | |
case ID_CMD_SIS: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tSense Int. Status - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_activate(DELAY_US(ID_SIS_WAIT)); | |
break; | |
case ID_CMD_SPEC: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tSpecify - %d - ETN=%02x ESN=%02x\n", | |
R[NUM_PC], val, UNIT_NUM, id_data[3], id_data[4]); | |
id_dtlh[UNIT_NUM] = id_data[1]; | |
id_etn = id_data[3]; | |
id_esn = id_data[4]; | |
id_activate(DELAY_US(ID_SPEC_WAIT)); | |
break; | |
case ID_CMD_SUS: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tSense Unit Status - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_activate(DELAY_US(ID_SUS_WAIT)); | |
break; | |
case ID_CMD_DERR: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tDetect Error - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_status |= ID_STAT_CEH; | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_RECAL: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tRecalibrate - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_cyl[UNIT_NUM] = 0; | |
time = id_cyl[UNIT_NUM]; | |
id_activate(DELAY_US(ID_RECAL_WAIT + (time * ID_SEEK_WAIT))); | |
id_seek_sis = TRUE; | |
break; | |
case ID_CMD_SEEK: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tSeek - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_lcnh = id_data[0]; | |
id_lcnl = id_data[1]; | |
cyl = id_lcnh << 8 | id_lcnl; | |
time = (uint32) abs(id_cyl[UNIT_NUM] - cyl); | |
id_activate(DELAY_US(ID_SEEK_BASE + (ID_SEEK_WAIT * time))); | |
id_cyl[UNIT_NUM] = cyl; | |
id_seek_sis = TRUE; | |
break; | |
case ID_CMD_FMT: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tFormat - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_phn = id_data[0]; | |
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_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_cyl[UNIT_NUM], id_phn, sec++); | |
if (sim_disk_wrsect(id_sel_unit, lba, id_buf, NULL, 1) == SCPE_OK) { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
"[%08x]\tFORMAT: PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", | |
R[NUM_PC], id_phn, id_scnt, pattern, lba); | |
} else { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
"[%08x]\tFORMAT FAILED! PHN=%d SCNT=%d PAT=%02x LBA=%04x\n", | |
R[NUM_PC], id_phn, id_scnt, pattern, lba); | |
break; | |
} | |
} | |
id_data[0] = 0; | |
} else { | |
/* Not attached */ | |
id_data[0] = ID_EST_NR; | |
} | |
id_data[1] = id_scnt; | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_VID: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tVerify ID - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_data[0] = 0; | |
id_data[1] = 0x05; /* What do we put here? */ | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_RID: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tRead ID - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
if (id_sel_unit->flags & UNIT_ATT) { | |
id_drq = TRUE; | |
/* Grab our arguments */ | |
id_phn = id_data[0]; | |
id_scnt = id_data[1]; | |
/* Compute logical values used by ID verification */ | |
id_lhn = id_phn; | |
id_lsn = 0; | |
} else { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ ID.\n", | |
R[NUM_PC], UNIT_NUM); | |
} | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_RDIAG: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tRead Diag - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_RDATA: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tRead Data - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
if (id_sel_unit->flags & UNIT_ATT) { | |
id_drq = TRUE; | |
id_buf_ptr = 0; | |
/* Grab our arguments */ | |
id_phn = id_data[0]; | |
id_lcnh = ~(id_data[1]); | |
id_lcnl = id_data[2]; | |
id_lhn = id_data[3]; | |
id_lsn = id_data[4]; | |
id_scnt = id_data[5]; | |
} else { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT READ DATA.\n", | |
R[NUM_PC], UNIT_NUM); | |
} | |
time = (uint32) abs(id_cyl[UNIT_NUM] - ((id_lcnh<<8)|id_lcnl)); | |
if (time == 0) { | |
time++; | |
} | |
time = time * ID_SEEK_WAIT; | |
id_activate(DELAY_US(time + ID_RW_WAIT)); | |
break; | |
case ID_CMD_CHECK: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tCheck - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_SCAN: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tScan - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_VDATA: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tVerify Data - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
id_activate(DELAY_US(ID_CMD_WAIT)); | |
break; | |
case ID_CMD_WDATA: | |
sim_debug(WRITE_MSG, &id_dev, | |
"[%08x]\tCOMMAND\t%02x\tWrite Data - %d\n", | |
R[NUM_PC], val, UNIT_NUM); | |
if (id_sel_unit->flags & UNIT_ATT) { | |
id_drq = TRUE; | |
id_buf_ptr = 0; | |
/* Grab our arguments */ | |
id_phn = id_data[0]; | |
id_lcnh = ~(id_data[1]); | |
id_lcnl = id_data[2]; | |
id_lhn = id_data[3]; | |
id_lsn = id_data[4]; | |
id_scnt = id_data[5]; | |
} else { | |
sim_debug(EXECUTE_MSG, &id_dev, | |
"[%08x]\tUNIT %d NOT ATTACHED, CANNOT WRITE.\n", | |
R[NUM_PC], UNIT_NUM); | |
} | |
time = (uint32) abs(id_cyl[UNIT_NUM] - ((id_lcnh<<8)|id_lcnl)); | |
if (time == 0) { | |
time++; | |
} | |
time = time * ID_SEEK_WAIT; | |
id_activate(DELAY_US(time + ID_RW_WAIT)); | |
break; | |
} | |
} | |
void id_drq_handled() | |
{ | |
id_status &= ~ID_STAT_DRQ; | |
id_drq = FALSE; | |
} | |
CONST char *id_description(DEVICE *dptr) | |
{ | |
return "72MB MFM Hard Disk"; | |
} | |
t_stat id_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr) | |
{ | |
fprintf(st, "71MB MFM Integrated Hard Disk (ID)\n\n"); | |
fprintf(st, | |
"The ID controller implements the integrated MFM hard disk controller\n" | |
"of the 3B2/400. Up to four drives are supported on a single controller.\n"); | |
return SCPE_OK; | |
} |