This change updates the 3B2 README.md file, and fixes all line endings on 3B2 source files.
645 lines
19 KiB
C
645 lines
19 KiB
C
/* 3b2_if.c: TMS2797 Integrated Floppy 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.
|
|
*/
|
|
|
|
#include "3b2_if.h"
|
|
|
|
#include "sim_disk.h"
|
|
|
|
#include "3b2_cpu.h"
|
|
#include "3b2_csr.h"
|
|
|
|
/* Static function declarations */
|
|
static SIM_INLINE uint32 if_lba();
|
|
|
|
/*
|
|
* Disk Format:
|
|
* ------------
|
|
*
|
|
* - 80 Tracks
|
|
* - 9 Sectors per track
|
|
* - 2 heads
|
|
* - 512 bytes per sector
|
|
*
|
|
* 80 * 9 * 2 * 512 = 720KB
|
|
*
|
|
*/
|
|
|
|
#define IF_STEP_DELAY 300 /* us */
|
|
#define IF_R_DELAY 6500 /* us */
|
|
#define IF_W_DELAY 7000 /* us */
|
|
#define IF_VERIFY_DELAY 2000 /* us */
|
|
#define IF_HLD_DELAY 6000 /* us */
|
|
#define IF_HSW_DELAY 4000 /* us */
|
|
|
|
#if defined(REV3)
|
|
#define SET_INT CPU_SET_INT(INT_FLOPPY)
|
|
#define CLR_INT CPU_CLR_INT(INT_FLOPPY)
|
|
#else
|
|
#define SET_INT do { \
|
|
CPU_SET_INT(INT_FLOPPY); \
|
|
SET_CSR(CSRDISK); \
|
|
} while(0)
|
|
#define CLR_INT do { \
|
|
CPU_CLR_INT(INT_FLOPPY); \
|
|
CLR_CSR(CSRDISK); \
|
|
} while(0)
|
|
#endif
|
|
|
|
UNIT if_unit = {
|
|
UDATA (&if_svc, UNIT_FIX+UNIT_ATTABLE+UNIT_BINK+UNIT_ROABLE,
|
|
IF_DSK_SIZE_SECS)
|
|
};
|
|
|
|
REG if_reg[] = {
|
|
{ NULL }
|
|
};
|
|
|
|
DEVICE if_dev = {
|
|
"IFLOPPY", &if_unit, if_reg, NULL,
|
|
1, 16, 8, 1, 16, 8,
|
|
NULL, NULL, &if_reset,
|
|
NULL, &if_attach, &if_detach, NULL,
|
|
DEV_DEBUG|DEV_DISK|DEV_SECTORS, 0, sys_deb_tab,
|
|
NULL, NULL, &if_help, NULL, NULL,
|
|
&if_description
|
|
};
|
|
|
|
IF_STATE if_state;
|
|
uint8 if_buf[IF_SEC_SIZE];
|
|
uint32 if_sec_ptr = 0;
|
|
|
|
/* Function implementation */
|
|
|
|
static SIM_INLINE void if_activate(uint32 delay_us)
|
|
{
|
|
sim_activate_after(&if_unit, delay_us);
|
|
}
|
|
|
|
t_stat if_svc(UNIT *uptr)
|
|
{
|
|
uint32 lba; /* Logical block address for write */
|
|
t_seccnt sectswritten;
|
|
|
|
if_state.status &= ~(IF_BUSY);
|
|
|
|
switch(if_state.cmd & 0xf0) {
|
|
case IF_RESTORE:
|
|
if_state.status = (IF_TK_0|IF_HEAD_LOADED);
|
|
break;
|
|
case IF_SEEK:
|
|
if_state.status = IF_HEAD_LOADED;
|
|
if (if_state.track == 0) {
|
|
if_state.status |= IF_TK_0;
|
|
}
|
|
break;
|
|
case IF_WRITE_SEC:
|
|
lba = if_lba();
|
|
|
|
/* If we're read-only, don't actually do anything. */
|
|
if (if_unit.flags & UNIT_RO) {
|
|
break;
|
|
}
|
|
|
|
if (sim_disk_wrsect(&if_unit, lba, if_buf, §swritten, 1) == SCPE_OK) {
|
|
if (sectswritten != 1) {
|
|
sim_debug(EXECUTE_MSG, &if_dev,
|
|
"ERROR: ASKED TO wRITE ONE SECTOR, WROTE %d\n",
|
|
sectswritten);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if_state.cmd = 0;
|
|
|
|
/* Request an interrupt */
|
|
sim_debug(IRQ_MSG, &if_dev, "\tINTR\n");
|
|
SET_INT;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat if_reset(DEVICE *dptr)
|
|
{
|
|
if_state.status = IF_TK_0;
|
|
if_state.track = 0;
|
|
if_state.sector = 1;
|
|
if_sec_ptr = 0;
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
t_stat if_attach(UNIT *uptr, CONST char *cptr)
|
|
{
|
|
return sim_disk_attach(uptr, cptr, 512, 1, TRUE, 0, NULL, 0, 0);
|
|
}
|
|
|
|
t_stat if_detach(UNIT *uptr)
|
|
{
|
|
return sim_disk_detach(uptr);
|
|
}
|
|
|
|
uint32 if_read(uint32 pa, size_t size) {
|
|
uint8 reg, data;
|
|
UNIT *uptr;
|
|
|
|
uptr = &(if_dev.units[0]);
|
|
reg = (uint8)(pa - IFBASE);
|
|
|
|
switch (reg) {
|
|
case IF_STATUS_REG:
|
|
data = if_state.status;
|
|
/* If there's no image attached, we're not ready */
|
|
if ((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) {
|
|
data |= IF_NRDY;
|
|
}
|
|
/* Reading the status register always de-asserts the IRQ line */
|
|
CLR_INT;
|
|
sim_debug(READ_MSG, &if_dev, "\tSTATUS\t%02x\n", data);
|
|
break;
|
|
case IF_TRACK_REG:
|
|
data = if_state.track;
|
|
sim_debug(READ_MSG, &if_dev, "\tTRACK\t%02x\n", data);
|
|
break;
|
|
case IF_SECTOR_REG:
|
|
data = if_state.sector;
|
|
sim_debug(READ_MSG, &if_dev, "\tSECTOR\t%02x\n", data);
|
|
break;
|
|
case IF_DATA_REG:
|
|
if_state.status &= ~IF_DRQ;
|
|
|
|
if (((uptr->flags & (UNIT_ATT|UNIT_BUF)) == 0) ||
|
|
((if_state.cmd & 0xf0) != IF_READ_SEC &&
|
|
(if_state.cmd & 0xf0) != IF_READ_SEC_M)) {
|
|
/* Not attached, or not a read command */
|
|
|
|
switch (if_state.cmd & 0xf0) {
|
|
case IF_READ_ADDR:
|
|
/* Special state machine. */
|
|
switch (if_state.read_addr_ptr++) {
|
|
case 0:
|
|
if_state.data = if_state.track;
|
|
break;
|
|
case 1:
|
|
if_state.data = if_state.side;
|
|
break;
|
|
case 2:
|
|
if_state.data = if_state.sector;
|
|
break;
|
|
case 3:
|
|
if_state.data = 2; /* 512 byte */
|
|
break;
|
|
case 4:
|
|
/* TODO: Checksum */
|
|
if_state.data = 0;
|
|
break;
|
|
case 5:
|
|
/* TODO: Checksum */
|
|
if_state.data = 0;
|
|
if_state.read_addr_ptr = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", if_state.data);
|
|
return if_state.data;
|
|
}
|
|
|
|
data = if_buf[if_sec_ptr++];
|
|
sim_debug(READ_MSG, &if_dev, "\tDATA\t%02x\n", data);
|
|
|
|
if (if_sec_ptr >= IF_SEC_SIZE) {
|
|
if_sec_ptr = 0;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
data = 0xffu; // Compiler warning
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/* Handle the most recently received command */
|
|
void if_handle_command()
|
|
{
|
|
uint32 delay_ms = 0;
|
|
uint32 head_switch_delay = 0;
|
|
uint32 head_load_delay = 0;
|
|
uint32 lba; /* Logical block address */
|
|
t_seccnt sectsread;
|
|
|
|
if_sec_ptr = 0;
|
|
|
|
/* We're starting a new command. */
|
|
if_state.status = IF_BUSY;
|
|
|
|
/* Clear read addr state */
|
|
if_state.read_addr_ptr = 0;
|
|
|
|
switch(if_state.cmd & 0xf0) {
|
|
case IF_RESTORE:
|
|
case IF_SEEK:
|
|
case IF_STEP:
|
|
case IF_STEP_T:
|
|
case IF_STEP_IN:
|
|
case IF_STEP_IN_T:
|
|
case IF_STEP_OUT:
|
|
case IF_STEP_OUT_T:
|
|
if_state.cmd_type = 1;
|
|
if (if_state.cmd & IF_H_FLAG) {
|
|
head_load_delay = IF_HLD_DELAY;
|
|
}
|
|
break;
|
|
|
|
case IF_READ_SEC:
|
|
case IF_READ_SEC_M:
|
|
case IF_WRITE_SEC:
|
|
case IF_WRITE_SEC_M:
|
|
if_state.cmd_type = 2;
|
|
#if defined(REV2)
|
|
if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) {
|
|
head_switch_delay = IF_HSW_DELAY;
|
|
if_state.side = (if_state.cmd & IF_U_FLAG) >> 1;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case IF_READ_ADDR:
|
|
case IF_READ_TRACK:
|
|
case IF_WRITE_TRACK:
|
|
if_state.cmd_type = 3;
|
|
#if defined(REV2)
|
|
if (((if_state.cmd & IF_U_FLAG) >> 1) != if_state.side) {
|
|
head_switch_delay = IF_HSW_DELAY;
|
|
if_state.side = (if_state.cmd & IF_U_FLAG) >> 1;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
switch(if_state.cmd & 0xf0) {
|
|
case IF_RESTORE:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRestore\n", if_state.cmd);
|
|
|
|
/* Reset HLT */
|
|
if_state.status &= ~IF_HEAD_LOADED;
|
|
|
|
if (if_unit.flags & UNIT_RO) {
|
|
if_state.status |= IF_WP;
|
|
}
|
|
|
|
/* If head should be loaded immediately, do so now */
|
|
if (if_state.cmd & IF_H_FLAG) {
|
|
if_state.status |= IF_HEAD_LOADED;
|
|
}
|
|
|
|
if (if_state.track == 0) {
|
|
if_state.status |= IF_TK_0;
|
|
if_state.track = 1; /* Kind of a gross hack */
|
|
}
|
|
|
|
if (if_state.cmd & IF_V_FLAG) {
|
|
delay_ms = (IF_STEP_DELAY * if_state.track) + IF_VERIFY_DELAY;
|
|
} else {
|
|
delay_ms = IF_STEP_DELAY * if_state.track;
|
|
}
|
|
|
|
if_activate(delay_ms);
|
|
|
|
if_state.data = 0;
|
|
if_state.track = 0;
|
|
break;
|
|
|
|
case IF_STEP:
|
|
case IF_STEP_T:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep\n", if_state.cmd);
|
|
if (if_unit.flags & UNIT_RO) {
|
|
if_state.status |= IF_WP;
|
|
}
|
|
if_activate(IF_STEP_DELAY);
|
|
if_state.track = (uint8) MIN(MAX((int) if_state.track + if_state.step_dir, 0), 0x4f);
|
|
break;
|
|
case IF_STEP_IN:
|
|
case IF_STEP_IN_T:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep In\n", if_state.cmd);
|
|
if (if_unit.flags & UNIT_RO) {
|
|
if_state.status |= IF_WP;
|
|
}
|
|
if_state.step_dir = IF_STEP_IN_DIR;
|
|
if_state.track = (uint8) MAX((int) if_state.track + if_state.step_dir, 0);
|
|
if_activate(IF_STEP_DELAY);
|
|
break;
|
|
case IF_STEP_OUT:
|
|
case IF_STEP_OUT_T:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tStep Out\n", if_state.cmd);
|
|
if (if_unit.flags & UNIT_RO) {
|
|
if_state.status |= IF_WP;
|
|
}
|
|
if_state.step_dir = IF_STEP_OUT_DIR;
|
|
if_state.track = (uint8) MIN((int) if_state.track + if_state.step_dir, 0x4f);
|
|
if_activate(IF_STEP_DELAY);
|
|
break;
|
|
case IF_SEEK:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tSeek\n", if_state.cmd);
|
|
|
|
/* Reset HLT */
|
|
if_state.status &= ~IF_HEAD_LOADED;
|
|
|
|
if (if_unit.flags & UNIT_RO) {
|
|
if_state.status |= IF_WP;
|
|
}
|
|
|
|
/* If head should be loaded immediately, do so now */
|
|
if (if_state.cmd & IF_H_FLAG) {
|
|
if_state.status |= IF_HEAD_LOADED;
|
|
}
|
|
|
|
/* Save the direction for stepping */
|
|
if (if_state.data > if_state.track) {
|
|
if_state.step_dir = IF_STEP_IN_DIR;
|
|
} else if (if_state.data < if_state.track) {
|
|
if_state.step_dir = IF_STEP_OUT_DIR;
|
|
}
|
|
|
|
/* The new track is in the data register */
|
|
|
|
if (if_state.data > IF_TRACK_COUNT-1) {
|
|
if_state.data = IF_TRACK_COUNT-1;
|
|
}
|
|
|
|
if (if_state.data == 0) {
|
|
if_state.status |= IF_TK_0;
|
|
} else {
|
|
if_state.status &= ~(IF_TK_0);
|
|
}
|
|
|
|
delay_ms = (uint32) abs(if_state.data - if_state.track);
|
|
|
|
if (delay_ms == 0) {
|
|
delay_ms++;
|
|
}
|
|
|
|
if (if_state.cmd & IF_V_FLAG) {
|
|
if_activate((IF_STEP_DELAY * delay_ms) + IF_VERIFY_DELAY + head_load_delay);
|
|
} else {
|
|
if_activate((IF_STEP_DELAY * delay_ms) + head_load_delay);
|
|
}
|
|
|
|
if_state.track = if_state.data;
|
|
break;
|
|
|
|
case IF_READ_SEC:
|
|
lba = if_lba();
|
|
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Sector %d/%d/%d (lba=%d)\n",
|
|
if_state.cmd, if_state.track, if_state.side, if_state.sector, lba);
|
|
|
|
if (sim_disk_rdsect(&if_unit, lba, if_buf, §sread, 1) == SCPE_OK) {
|
|
if (sectsread != 1) {
|
|
sim_debug(EXECUTE_MSG, &if_dev,
|
|
"ERROR: ASKED TO READ ONE SECTOR, READ %d\n",
|
|
sectsread);
|
|
}
|
|
/* We set DRQ right away to request the transfer. */
|
|
if_state.drq = TRUE;
|
|
if_state.status |= IF_DRQ;
|
|
if (if_state.cmd & IF_E_FLAG) {
|
|
if_activate(IF_R_DELAY + IF_VERIFY_DELAY + head_switch_delay);
|
|
} else {
|
|
if_activate(IF_R_DELAY + head_switch_delay);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case IF_READ_SEC_M:
|
|
/* Not yet implemented. Halt the emulator. */
|
|
sim_debug(EXECUTE_MSG, &if_dev,
|
|
"\tCOMMAND\t%02x\tRead Sector (Multi) - NOT IMPLEMENTED\n",
|
|
if_state.cmd);
|
|
stop_reason = STOP_ERR;
|
|
break;
|
|
case IF_WRITE_SEC:
|
|
lba = if_lba();
|
|
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Sector %d/%d/%d (lba=%d)\n",
|
|
if_state.cmd, if_state.track, if_state.side, if_state.sector, lba);
|
|
|
|
if (if_unit.flags & UNIT_RO) {
|
|
if_state.status |= IF_WP;
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tWON'T WRITE: WRITE PROTECTED.\n");
|
|
/* Still cause an interrupt... */
|
|
if_activate(IF_W_DELAY + head_switch_delay);
|
|
/* But don't set DRQ and ask for a transfer. */
|
|
break;
|
|
}
|
|
|
|
/* We set DRQ right away to request the transfer. Data will
|
|
* be written by the host into our buffer by 512 writes to the
|
|
* data register. When the IF device later activates, the data
|
|
* will actually be written. */
|
|
if_state.drq = TRUE;
|
|
if_state.status |= IF_DRQ;
|
|
if (if_state.cmd & IF_E_FLAG) {
|
|
if_activate(IF_W_DELAY + IF_VERIFY_DELAY + head_switch_delay);
|
|
} else {
|
|
if_activate(IF_W_DELAY + head_switch_delay);
|
|
}
|
|
break;
|
|
case IF_WRITE_SEC_M:
|
|
/* Not yet implemented. Halt the emulator. */
|
|
sim_debug(EXECUTE_MSG, &if_dev,
|
|
"\tCOMMAND\t%02x\tWrite Sector (Multi) - NOT IMPLEMENTED\n",
|
|
if_state.cmd);
|
|
stop_reason = STOP_ERR;
|
|
break;
|
|
case IF_READ_ADDR:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Address\n", if_state.cmd);
|
|
if_state.drq = TRUE;
|
|
if_state.status |= IF_DRQ;
|
|
if_activate(IF_R_DELAY);
|
|
break;
|
|
case IF_READ_TRACK:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tRead Track\n", if_state.cmd);
|
|
/* Not yet implemented. Halt the emulator. */
|
|
stop_reason = STOP_ERR;
|
|
break;
|
|
case IF_WRITE_TRACK:
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tWrite Track\n", if_state.cmd);
|
|
/* Set DRQ */
|
|
if_state.drq = TRUE;
|
|
if_state.status |= IF_DRQ;
|
|
if (if_state.cmd & IF_E_FLAG) {
|
|
if_activate(IF_W_DELAY + IF_VERIFY_DELAY + head_switch_delay);
|
|
} else {
|
|
if_activate(IF_W_DELAY + head_switch_delay);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void if_write(uint32 pa, uint32 val, size_t size)
|
|
{
|
|
UNIT *uptr;
|
|
uint8 reg;
|
|
|
|
val = val & 0xff;
|
|
|
|
uptr = &(if_dev.units[0]);
|
|
reg = (uint8) (pa - IFBASE);
|
|
|
|
switch (reg) {
|
|
case IF_CMD_REG:
|
|
if_state.cmd = (uint8) val;
|
|
/* Writing to the command register always de-asserts the IRQ line */
|
|
CLR_INT;
|
|
|
|
/* If this is a FORCE INTERRUPT, handle it immediately. All
|
|
* other commands require that the unit be attached and a
|
|
* diskette loaded. This one does not. */
|
|
if ((if_state.cmd & 0xf0) == IF_FORCE_INT) {
|
|
sim_debug(EXECUTE_MSG, &if_dev, "\tCOMMAND\t%02x\tForce Interrupt\n", if_state.cmd);
|
|
if_state.status = 0;
|
|
|
|
if ((uptr->flags & UNIT_ATT) && if_state.track == 0) {
|
|
if_state.status |= (IF_TK_0|IF_HEAD_LOADED);
|
|
}
|
|
|
|
if ((if_state.cmd & 0xf) == 0) {
|
|
sim_cancel(&if_unit);
|
|
#if defined(REV2)
|
|
CLR_INT; /* TODO: Confirm this is right */
|
|
#endif
|
|
} else if ((if_state.cmd & 0x8) == 0x8) {
|
|
if_state.status |= IF_DRQ;
|
|
SET_INT;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ((uptr->flags & UNIT_ATT) == 0) {
|
|
/* If not attached, do nothing */
|
|
break;
|
|
}
|
|
|
|
if_handle_command();
|
|
break;
|
|
case IF_TRACK_REG:
|
|
if_state.track = (uint8) val;
|
|
sim_debug(WRITE_MSG, &if_dev, "\tTRACK\t%02x\n", val);
|
|
break;
|
|
case IF_SECTOR_REG:
|
|
if_state.sector = (uint8) val;
|
|
sim_debug(WRITE_MSG, &if_dev, "\tSECTOR\t%02x\n", val);
|
|
break;
|
|
case IF_DATA_REG:
|
|
if_state.data = (uint8) val;
|
|
|
|
sim_debug(WRITE_MSG, &if_dev, "\tDATA\t%02x\n", val);
|
|
|
|
if ((uptr->flags & UNIT_ATT) == 0) {
|
|
/* Not attached */
|
|
break;
|
|
}
|
|
|
|
if ((if_state.cmd & 0xf0) == IF_WRITE_TRACK) {
|
|
/* We intentionally ignore WRITE TRACK data, because
|
|
* This is only used for low-level MFM formatting,
|
|
* which we do not emulate. */
|
|
} else if ((if_state.cmd & 0xf0) == IF_WRITE_SEC ||
|
|
(if_state.cmd & 0xf0) == IF_WRITE_SEC_M) {
|
|
|
|
if_buf[if_sec_ptr++] = (uint8) val;
|
|
|
|
if (if_sec_ptr >= IF_SEC_SIZE) {
|
|
if_sec_ptr = 0;
|
|
}
|
|
}
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined(REV3)
|
|
uint32 if_csr_read(uint32 pa, size_t size)
|
|
{
|
|
return (uint32)(if_state.csr);
|
|
}
|
|
|
|
void if_csr_write(uint32 pa, uint32 val, size_t size)
|
|
{
|
|
if_state.csr = val & 0xff;
|
|
}
|
|
#endif
|
|
|
|
CONST char *if_description(DEVICE *dptr)
|
|
{
|
|
return "Integrated Floppy Disk";
|
|
}
|
|
|
|
t_stat if_help(FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, const char *cptr)
|
|
{
|
|
fprintf(st, "Integrated Floppy Disk (IFLOPPY)\n\n");
|
|
fprintf(st, "The IFLOPPY device implements the integrated 720 KB floppy disk\n");
|
|
fprintf(st, "of the 3B2/400. A single floppy disk is supported on the controller.\n\n");
|
|
fprintf(st, "The format of the diskette media is as follows:\n\n");
|
|
fprintf(st, " Size Sides Tracks/Side Sectors/Track Bytes/Track\n");
|
|
fprintf(st, " ------ ----- ----------- ------------- -----------\n");
|
|
fprintf(st, " 720 KB 2 80 9 512\n\n");
|
|
fprintf(st, "Physical media is Double Sided/Quad Density, 96 tpi, 250kbps MFM encoding.\n");
|
|
|
|
fprint_set_help(st, dptr);
|
|
fprint_show_help(st, dptr);
|
|
fprint_reg_help(st, dptr);
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
/*
|
|
* Compute the offset of the currently selected C/H/S (in # of sectors)
|
|
*/
|
|
static SIM_INLINE uint32 if_lba()
|
|
{
|
|
/* Reminder that sectors are numbered 1-9 instead
|
|
* of being numbered 0-8 */
|
|
return((if_state.track * IF_SEC_COUNT * 2) +
|
|
(if_state.side * IF_SEC_COUNT) +
|
|
(if_state.sector - 1));
|
|
}
|
|
|
|
void if_after_dma()
|
|
{
|
|
if_state.drq = FALSE;
|
|
if_state.status &= ~IF_DRQ;
|
|
}
|