Improve WD2010 emulation

Better feedback on cylinder positions. Almost all checks of the winchester test pass (option 16 @ RX50DIAG.TD0 diskette), except low level tests expecting manufacturer or diagnostic tracks (write test #1).
This commit is contained in:
Bavarese 2017-07-16 12:27:16 +02:00 committed by GitHub
parent b46091aaa5
commit dfa9e1a22f

View File

@ -4,23 +4,25 @@
Western Digital WD2010 Winchester Disk Controller
Portions (2015) : Karl-Ludwig Deisenhofer
Portions (2015, 2017) : Karl-Ludwig Deisenhofer
**********************************************************************
Implements WD2010 / WD1010 controller basics.
Implements WD2010 / WD1010 controller basics for a single hard disk.
Provides IRQ / (B)DRQ signals needed for early MFM cards.
Honors DRIVE_READY and WRITE FAULT (DRDY / WF).
Single sector read / write (format) confirmed to work with
Rainbow-100 controller (WD1010, largely compatible to WD2010, see **)
Rainbow-100 controller (WD1010, quite compatible to WD2010, see **)
LIST OF UNIMPLEMENTED FEATURES :
- MULTI SECTOR TRANSFERS (M = 1); MULTIPLE DRIVES
- AUTO_SCAN_ID / SEEK + INDEX TIMERS / ID NOT FOUND
- IMPLIED SEEKS / IMPLIED WRITES / RETRIES
- EDGE or LEVEL TRIGGERED SEEK_COMPLETE (SC)
- SET_PARAMETER / COMPUTE_CORRECTION (DWC flag!)
UNIMPLEMENTED FEATURES :
- more than 1 drive (untested)
- multi sector transfers (M = 1)
- seek and index timers / ID not found.
- implied seeks / implied writes / retries
- edge or level triggered seek complete (SC)
- set_parameter / compute_correction
(the DWC flag is not usable in this context).
Pseudo code (from datasheet) left in to illustrate
the intended instruction flow. Some loops were omitted!
@ -447,7 +449,7 @@ void wd2010_device::restore(uint8_t data)
// seek -
//-------------------------------------------------
// FIXME : step rate, drive change (!)
// FIXME : step rate, drive change (untested)
// NOT IMPLEMENTED: IMPLIED SEEK ("wait until rising edge of SC signal")
void wd2010_device::seek(uint8_t data)
@ -459,7 +461,6 @@ void wd2010_device::seek(uint8_t data)
m_status = STATUS_BSY | STATUS_CIP;
// TODO : store STEP RATE.
auto_scan_id(data); // has drive number changed?
int direction; // 0 = towards 0
@ -525,7 +526,11 @@ void wd2010_device::seek(uint8_t data)
// UPDATE INTERNAL CYLINDER POSITION REGISTER (from WD1010 spec -> "SEEK COMMAND")
m_present_cylinder = cylinder_registers;
logerror("SEEK (END) - m_present_cylinder = %u\n", m_present_cylinder);
// ...update CYLINDER registers with cylinder found -
m_task_file[TASK_FILE_CYLINDER_HIGH] = (m_present_cylinder >> 8) & 0xff;
m_task_file[TASK_FILE_CYLINDER_LOW] = (m_present_cylinder - ((m_task_file[TASK_FILE_CYLINDER_HIGH] << 8) )) & 0xff;
logerror("SEEK (END) - m_present_cylinder = %u SDH CYL L/H %02x / %02x\n", m_present_cylinder,m_task_file[TASK_FILE_CYLINDER_LOW],m_task_file[TASK_FILE_CYLINDER_HIGH]);
cmd_timer->adjust(attotime::from_msec(35), newstatus); // 35 msecs makes "SEEK_TIMING" test happy.
}
@ -585,6 +590,7 @@ void wd2010_device::read_sector(uint8_t data)
}
// LOOP OVER 10 INDEXES : SCAN_ID / GET CYL.# (not implemented: ID NOT FOUND)
m_present_cylinder = CYLINDER;
// CYL / HEAD / SEC.SIZE MATCH ? => (ID FOUND)
//
@ -654,6 +660,13 @@ void wd2010_device::write_sector(uint8_t data)
m_status = STATUS_BSY | STATUS_CIP; // Assert BUSY + CIP
// (When drive changed) : SCAN_ID / GET CYL#
auto_scan_id(data); // has drive number changed?
// Assume YES : CYL.register + internal CYL.register SAME? (if NO => SEEK!)
// Assume : SEEK_COMPLETE = YES
m_present_cylinder = CYLINDER;
m_status |= STATUS_DRQ; // Assert BDRQ + DRQ (= status bit 3)
m_out_bdrq_cb(1);
@ -672,12 +685,6 @@ void wd2010_device::complete_write_sector(uint8_t data)
m_out_bdrq_cb(0); // DE-Assert BDRQ (...and DRQ !)
m_status &= ~(STATUS_DRQ);
// (When drive changed) : SCAN_ID / GET CYL#
auto_scan_id(data); // has drive number changed? (*** UNIMPLEMENTED ***)
// Assume YES : CYL.register + internal CYL.register SAME? (if NO => SEEK!)
// Assume : SEEK_COMPLETE = YES
if (!m_in_drdy_cb() || m_in_wf_cb()) // DRIVE IS READY / NO WF?
{
m_error = ERROR_AC; // ABORTED_COMMAND
@ -726,41 +733,35 @@ void wd2010_device::complete_write_sector(uint8_t data)
// ******************************************************
// AUTO SCAN-ID (whenever DRIVE # changes):
// * does nothing right now *
// ******************************************************
void wd2010_device::auto_scan_id(uint8_t data)
{
static int last_drive;
if (DRIVE != last_drive)
{
printf("\n(WD2010) : UNSUPPORTED DRIVE CHANGE !\n");
logerror("\n(WD2010) : UNSUPPORTED DRIVE CHANGE !\n");
// FIXME: geometry of disk not available here. Assume sector size already set (?)
update_sdh( SECTOR_SIZE, 0, 0, 1 ); // new sector_size, head, cylinder, sector
//update_sdh(new_sector_size, new_head, new_cylinder, new_sectornr);
logerror("\n(WD2010) : UNSUPPORTED DRIVE CHANGE (old = %02x, new = %02x) Sector size assumed: %d !\n", last_drive, DRIVE, SECTOR_SIZES[SECTOR_SIZE]);
}
last_drive = DRIVE;
return; // AUTO-SCAN CURRENTLY DISABLED (see NOTES)
return; // (see NOTES)
}
// ******************************************************
// What to do here (just update present_cylinder with CYLINDER)...?
// Update SDH register / update present_cylinder.
void wd2010_device::update_sdh(uint8_t new_sector_size, uint8_t new_head, uint16_t new_cylinder, uint8_t new_sectornr)
{
// "Update SDH"
/*
// Update SECTOR_SIZE, HEAD in SDH with the ID found -
m_task_file[TASK_FILE_SDH_REGISTER] = ???
m_task_file[TASK_FILE_SDH_REGISTER] &= 0x98; // mask 10011000 (size | head)
m_task_file[TASK_FILE_SDH_REGISTER] = ((new_sector_size & 3) << 5) | (new_head & 7);
// ...update CYLINDER registers with cylinder found -
m_task_file[TASK_FILE_CYLINDER_LOW] = (new_cylinder >> 4) & 0x0f;
m_task_file[TASK_FILE_CYLINDER_HIGH] = (new_cylinder - ((new_cylinder >> 4) << 4)) & 0x0f;
// ...update CYLINDER registers with cylinder given -
m_task_file[TASK_FILE_CYLINDER_HIGH] = (new_cylinder >> 8) & 0xff;
m_task_file[TASK_FILE_CYLINDER_LOW] = (new_cylinder - ((m_task_file[TASK_FILE_CYLINDER_HIGH] << 8) )) & 0xff;
// ...update SECTOR_NUMBER with sector nr. found -
// ...update SECTOR_NUMBER with sector nr. given -
m_task_file[TASK_FILE_SECTOR_NUMBER] = new_sectornr;
*/
m_present_cylinder = CYLINDER;
logerror("UPDATE_SDH - m_present_cylinder = %u\n", m_present_cylinder);
@ -770,8 +771,7 @@ void wd2010_device::update_sdh(uint8_t new_sector_size, uint8_t new_head, uint16
// scan_id -
//-------------------------------------------------
// Reads the cylinder number from the track on which the heads are PRESENTLY located,
// and writes this into the Present Cylinder Position Register.
// Reads the cylinder number from the track on which the heads are presently located
// FIXME: NO ID HANDLING (ID FOUND / NOT FOUND), NO BAD BLOCK; NO CRC
void wd2010_device::scan_id(uint8_t data)
@ -786,7 +786,8 @@ void wd2010_device::scan_id(uint8_t data)
// < TODO: Search for ANY ID FIELD. >
// Assume ID FOUND :
update_sdh( 32, 0, 0, 1 ); // (NEW:) SECTOR_SIZE, HEAD, CYLINDER, SECTOR_NR
m_task_file[TASK_FILE_CYLINDER_HIGH] = (m_present_cylinder >> 8) & 0xff;
m_task_file[TASK_FILE_CYLINDER_LOW] = (m_present_cylinder - ((m_task_file[TASK_FILE_CYLINDER_HIGH] << 8) )) & 0xff;
// NO BAD BLOCK.
// NO CRC ERROR.
@ -798,10 +799,10 @@ void wd2010_device::scan_id(uint8_t data)
// FORMAT ENTIRE TRACK using the task file + sector buffer
// On real hardware, data fields are filled with FF.
// Sector buffer is used for track layout (see datasheet).
// Sector buffer is used for track layout (- datasheet).
// Routine simulates one single write on each track
// - just enough to keep formatter programs happy -
// This routine does just enough to keep formatter
// programs happy (no need to low level format a CHD).
// < UNIMPLEMENTED: (IMPLIED) SEEKs, INDEX, CRC and GAPs >
//--------------------------------------------------------
@ -851,36 +852,14 @@ void wd2010_device::format(uint8_t data)
m_out_wg_cb(1); // Have Index, activate WRITE GATE
// Check for WRITE FAULT (WF)
if (m_in_wf_cb())
if (m_in_wf_cb()) // Check for WRITE FAULT (WF)
{
m_error = ERROR_AC; // ABORTED_COMMAND
complete_cmd(newstatus | STATUS_ERR);
return;
}
// uint8_t format_sector_count = m_task_file[TASK_FILE_SECTOR_COUNT];
// do
// {
// < WRITE GAP 1 or GAP 3 >
// < Wait for SEEK_COMPLETE=1 (extend GAP if SEEK_COMPLETE = 0) >
// < Assume SEEK COMPLETE >
// format_sector_count--;
// if (format_sector_count != 0)
{
// The Rainbow 100 driver does ignore multiple sector
// transfers so WRITE FORMAT does not actually write -
m_out_wg_cb(0); // (transition from WG 1 -> 0)
// NOTE: decrementing TASK_FILE_SECTOR_COUNT does * NOT WORK *
}
// else
// { // < Write 4Es until INDEX (*** UNIMPLEMENTED ****) >
// }
// } while (format_sector_count > 0);
m_out_wg_cb(0); // (transition from WG 1 -> 0). Actual write.
// ** DELAY INTRQ UNTIL WRITE IS COMPLETE :
complete_write_when_buffer_ready_high->adjust(attotime::from_usec(1), newstatus | STATUS_DRQ); // 1 USECs