// This module is routines for reading the disk drive.
// drive_setup sets up the drive for reading.
// drive_read_disk reads the disk drive.
// drive_read_track read a track of deltas from a drive. It steps the
// head if necessary.
//
// The drive must be at track 0 on startup or drive_seek_track0 called.
//
// 06/02/2023 DJG Fixed write fault error reading NEC drive
// 07/05/2019 DJG Added support for using recovery signal
// 03/09/2018 DJG Added logic to not retry when requested to read more
// cylinders or heads than analyze determined
// 11/05/2016 DJG Made retry seek progression more like it was before change below
// 10/16/2016 DJG Added control over seek on retry
// 10/02/2016 DJG Rob Jarratt change to clean up confusing code.
//
// Copyright 2023 David Gesswein.
// This file is part of MFM disk utilities.
//
// MFM disk utilities is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MFM disk utilities is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with MFM disk utilities. If not, see .
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "msg.h"
#include "crc_ecc.h"
#include "emu_tran_file.h"
#include "mfm_decoder.h"
#include "cmd.h"
#include "cmd_write.h"
#include "deltas_read.h"
#include "pru_setup.h"
#include "drive.h"
#include "board.h"
// Read the disk.
// drive params specifies the information needed to decode the drive and what
// files should be written from the data read
//
// drive_params: Drive parameters
// deltas: Memory array containing the raw MFM delta time transition data
void drive_read_disk(DRIVE_PARAMS *drive_params, void *deltas, int max_deltas)
{
// Loop variable
int cyl, head;
int cntr;
// Retry counter, counts up
int err_cnt;
// No seek retry counter, counts down
int no_seek_count;
// Read status
SECTOR_DECODE_STATUS sector_status = 0;
// The status of each sector
SECTOR_STATUS sector_status_list[MAX_SECTORS];
// We use this to summarize the list above has any errors
int sect_err;
// For retry we do various length seeks. This is the maximum length we will do
int seek_len;
// Cylinder difference when a seek error occurs
int seek_difference;
// Timing stuff for testing
#ifdef CLOCK_MONOTONIC_RAW
#define CLOCK CLOCK_MONOTONIC_RAW
#else
#define CLOCK CLOCK_MONOTONIC
#endif
struct timespec tv_start;
double start, last = 0;
double min = 9e9, max = 0, tot = 0;
int count = 0;
int recovery_active = 0;
// Start up delta reader
deltas_start_thread(drive_params);
mfm_decode_setup(drive_params, 1);
for (cyl = 0; cyl < drive_params->num_cyl; cyl++) {
if (cyl % 5 == 0)
msg(MSG_PROGRESS, "At cyl %d\r", cyl);
for (head = 0; head < drive_params->num_head; head++) {
int recovered = 0;
int retries;
err_cnt = 0;
no_seek_count = drive_params->no_seek_retries;
seek_len = 1;
// Clear sector status here so we can see if different sectors
// successfully read on different reads.
mfm_init_sector_status_list(sector_status_list, drive_params->num_sectors);
if (cyl >= drive_params->noretry_cyl ||
head >= drive_params->noretry_head) {
retries = 0;
} else {
retries = drive_params->retries;
}
do {
// First error retry without seek, second with third without etc
// Hopefully by moving head it will settle slightly differently
// allowing data to be read
if (no_seek_count <= 0) {
int clip_seek_len = seek_len;
// In recovery mode the drive will perform a microstep instead
// of a full cylinder step. The sequency of microsteps positions
// will repeat after a drive dependent number of steps
if (drive_params->recovery) {
if (!recovery_active) {
drive_enable_recovery(1);
recovery_active = 1;
}
drive_step(drive_params->step_speed, 1,
DRIVE_STEP_NO_UPDATE_CYL, DRIVE_STEP_FATAL_ERR);
} else {
if (cyl + seek_len >= drive_params->num_cyl) {
clip_seek_len = drive_params->num_cyl - cyl - 1;
}
if (cyl + seek_len < 0) {
clip_seek_len = -cyl;
}
//printf("seeking %d %d %d\n",err_cnt, seek_len, seek_len + cyl);
if (clip_seek_len != 0) {
drive_step(drive_params->step_speed, clip_seek_len,
DRIVE_STEP_UPDATE_CYL, DRIVE_STEP_FATAL_ERR);
drive_step(drive_params->step_speed, -clip_seek_len,
DRIVE_STEP_UPDATE_CYL, DRIVE_STEP_FATAL_ERR);
}
if (seek_len < 0) {
seek_len = seek_len * 2;
if (seek_len <= -drive_params->num_cyl) {
seek_len = -1;
}
}
seek_len = -seek_len;
if (seek_len == 0) {
seek_len = 1;
}
}
no_seek_count = drive_params->no_seek_retries;
}
no_seek_count--;
clock_gettime(CLOCK, &tv_start);
start = tv_start.tv_sec + tv_start.tv_nsec / 1e9;
if (last != 0) {
#if 0
if (start-last > 40e-3)
printf("gettime delta ms %f\n", (start-last) * 1e3);
#endif
if (start-last > max)
max = start-last;
if (start-last < min)
min = start-last;
tot += start-last;
count++;
}
last = start;
drive_read_track(drive_params, cyl, head, deltas, max_deltas, 0);
sector_status = mfm_decode_track(drive_params, cyl, head,
deltas, &seek_difference, sector_status_list);
// See if sector list shows any with errors. The sector list
// contains the information on the best read for each sector so
// even if the last read had errors we may have recovered all the
// data without errors.
sect_err = 0;
for (cntr = 0; cntr < drive_params->num_sectors; cntr++) {
if (UNRECOVERED_ERROR(sector_status_list[cntr].status)) {
sect_err = 1;
}
}
// Looks like a seek error. Sector headers which don't have the
// expected cylinder such as when tracks are spared can trigger
// this also
if (sector_status & SECT_WRONG_CYL) {
if (err_cnt < retries) {
msg(MSG_ERR, "Retrying seek cyl %d, cyl off by %d\n", cyl,
seek_difference);
drive_step(drive_params->step_speed, seek_difference,
DRIVE_STEP_NO_UPDATE_CYL, DRIVE_STEP_FATAL_ERR);
}
}
if (UNRECOVERED_ERROR(sector_status) && !sect_err) {
recovered = 1;
}
// repeat until we get all the data or run out of retries
} while (sect_err && err_cnt++ < retries);
if (recovery_active) {
drive_enable_recovery(0);
// My ST225 doesn't follow the manual behavior of inactivating seek
// complete after recovery goes inactive while it repositions the
// heads. The heads seem to be moving since I get data from several
// cylinders. If a step is done the drive gets confused and needs
// to be power cylcled. This delay seems to make it work.
usleep(25000);
// Wait for seek complete
if (pru_exec_cmd(CMD_CHECK_READY, 0)) {
drive_print_drive_status(MSG_FATAL, drive_get_drive_status());
exit(1);
}
recovery_active = 0;
}
if (err_cnt > 0) {
if (err_cnt == retries + 1) {
msg(MSG_ERR, "Retries failed cyl %d head %d\n", cyl, head);
} else {
msg(MSG_INFO, "All sectors recovered %safter %d retries cyl %d head %d\n",
recovered ? "from multiple reads " : "", err_cnt, cyl, head);
}
}
}
}
mfm_decode_done(drive_params);
printf("Track read time in ms min %f max %f avg %f\n", min * 1e3,
max * 1e3, tot * 1e3 / count);
deltas_stop_thread();
return;
}
// Read a track of deltas from a drive
//
// drive_params: Drive parameters
// cyl, head: Cylinder and head reading. Head is stepped to cylinder
// and head selected
// deltas: Memory array containing the raw MFM delta time transition data
// max_deltas: Size of deltas array
// return_write_fault: Non zero if function should return if write fault error present
// otherwise write fault is fatal error.
//
// return non zero if write fault present and return_write_fault true.
int drive_read_track(DRIVE_PARAMS *drive_params, int cyl, int head,
void *deltas, int max_deltas, int return_write_fault) {
if (cyl != drive_current_cyl()) {
drive_step(drive_params->step_speed, cyl - drive_current_cyl(),
DRIVE_STEP_UPDATE_CYL, DRIVE_STEP_FATAL_ERR);
}
// Analyze can change so set it every time
pru_write_word(MEM_PRU0_DATA, PRU0_START_TIME_CLOCKS,
drive_params->start_time_ns / CLOCKS_TO_NS);
drive_set_head(head);
if (pru_exec_cmd(CMD_READ_TRACK, 0) != 0) {
drive_print_drive_status(MSG_FATAL, drive_get_drive_status());
if (drive_has_write_fault() && return_write_fault) {
return 1;
} else {
exit(1);
}
} else {
// OK to start reading deltas
deltas_start_read(cyl, head);
}
return 0;
}