#define GENERATE_HISTOGRAM 0
// This module analyzes the disk format
// Call analyze_disk to perform the analysis
//
// We perform the analysis by trying the various formats we know of until we
// find one that matches and other tests. See the routines for details.
// Copyright 2021 David Gesswein.
// This file is part of MFM disk utilities.
//
// 06/02/23 DJG Fixed write fault error reading NEC drive
// 03/11/23 DJG Improved EC1841 sector number decoding
// 07/20/22 DJG Removed useless lines
// 09/19/21 DJG Fixed indexing of sector_status_list in analyze model.
// Indexing always starts with entry 0 irrespective of first_sector_number
// CONT_MODEL formats that stated with sector 1 were not detected properly.
// 01/17/21 DJG Made match logic for MODEL controllers pick one with closest
// number of sectors to expected. RQDX2 was matching WD_3B1.
// 03/15/20 DJG Give margin in LBA head check
// 03/09/20 DJG Fix finding number of heads for LBA disk where
// selecting for example non existing head 4 gives head 0 data
// 01/25/20 DJG Give more time after seek for track0 to change to prevent
// seek analyze failures with RD54 drive
// 10/05/19 DJG Fixes to detect when CONT_MODEL controller doesn't really
// match format
// 06/19/19 DJG Added missing /n to error message
// 06/08/19 DJG Don't say disk is RLL if secondary period couldn't be
// determined.
// 03/12/19 DJG Fixed detecting format with MODEL using wrong number or
// first sector number
// 11/03/18 DJG Renamed variable
// 09/10/18 DJG Made code not allowing larger sector sizes to match when
// shorter found to allow larger sectors if not many matches for shorter.
// 06/22/18 DJG Fix checking of sector numbers for analyze_model. Detect
// SA1000 bit rate.
// 03/31/18 DJG Added code to analyze drive formats marked as MODEL/
// have track_layout defined.
// 03/09/18 DJG Added error message
// 10/20/17 DJG Fixed error print wording in analyze_seek
// 12/10/16 DJG Added logic to ignore Ambiguous CRC polynomial match on all
// zero data.
// 11/14/16 DJG Make Data CRC max span reasonable for formats not using
// separate data CRC. Doesn't really do anything, just prevents it
// looking funny. Added error for analyze only to handle Telenex Autoscope
// without hurting error recovery on Xerox 6085
// 10/31/16 DJG Redid how Logical block address disks are detected.
// Previous method didn't work when spare/bad sectors marked.
// 10/19/16 DJG Fixed Mightyframe detection to reduce probability of false
// deteection.
// 10/16/16 DJG Added logic to detect RLL disks.
// 10/02/16 DJG Rob Jarratt change to detect recalibrate when finding number
// of cylinders and suggested simplification for determining slow/fast
// seek.
// 10/01/16 DJG Handle tracks with one sector.
// 01/24/16 DJG Fix a couple errors with prints.
// 01/13/16 DJG Changes for ext2emu related changes on how drive formats will
// be handled.
// 11/22/15 DJG Add special logic for ST11M controller detection
// 11/01/15 DJG Analyze header and data together. The Elektronika_85 has
// same header format as other drives but the data area is different.
// Only CHS changed. TODO LBA should be redone to match
// 05/16/15 DJG Changes to support new formats, fix bugs, and allow
// analyzing transition and emulation files.
// 01/04/15 DJG Moved data tables to mfm_decoder.h since mfm_controller
// info needed to reference. Changed loop order analyze_header and
// data to allow poly and initial value to only use valid values
// for controller. NorthStar uses checksum so poly is ignored.
// Set proper sector size for controller in analyze_header which
// Corvus_H needs since it doesn't separate data and header.
// Added setting start_time_ns.
// Fixed detecting non buffered seek for ST506.
// 11/09/14 DJG Interleave will not be checked by default
// 10/01/14 DJG Added new polynomial to analyize and prefixed printing
// of polynomial with 0x
//
// 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 "msg.h"
#include "crc_ecc.h"
#include "emu_tran_file.h"
#include "mfm_decoder.h"
#include "cmd.h"
#include "deltas_read.h"
#include "drive.h"
#include "analyze.h"
#include "parse_cmdline.h"
// Prints CRC information
static void print_crc_info(CRC_INFO *crc_info, int msg_type) {
msg(msg_type, "Polynomial 0x%llx length %d initial value 0x%llx\n", crc_info->poly, crc_info->length,
crc_info->init_value);
}
// Errors we enable from MFM decoding routines while analyzing.
static int decode_errors;
// Clear upper bits to make value only havea length bits
static uint64_t trim_value(uint64_t value, int length) {
if (length < 64) {
value &= (((uint64_t) 1 << length) - 1);
}
return value;
}
// This routine finds the weighted index of the next peak in the histogram
// The bins used in the calculattion are cleared
// histogram: counts for each bin
// length: number of entries in histogram
static double avg_peak(int histogram[], int length)
{
int start = 0;
#define HIST_LIMIT 500
int sum = 0, sum_mult = 0;
int i;
for (i = 0; i < length-1; i++) {
if (histogram[i] > HIST_LIMIT) {
start = i;
break;
}
}
if (start == 0) {
msg(MSG_INFO, "No more peaks in histogram\n");
return 0;
} else {
for (i = start; i < length && histogram[i] > HIST_LIMIT ; i++) {
sum += histogram[i];
sum_mult += histogram[i] * i;
histogram[i] = 0;
}
return (double) sum_mult / sum;
}
}
// Estimate the clock rate used to encode the data. We find the
// difference in average delta for the minimum delta and the next
// time in the histogram. The difference is the clock rate used to
// encode. Estimate is coarse.
static void analyze_rate(DRIVE_PARAMS *drive_params, int cyl, int head,
uint16_t *deltas, int max_deltas)
{
int histogram[100];
int i;
double rate1, rate2;
memset(histogram, 0, sizeof(histogram));
deltas_wait_read_finished();
for (i = 0; i < deltas_get_count(0); i++) {
if (deltas[i] < ARRAYSIZE(histogram)) {
histogram[deltas[i]]++;
}
}
#if GENERATE_HISTOGRAM
for (i = 0; i < ARRAYSIZE(histogram); i++) {
printf("%d, %d\n", i, histogram[i]);
}
#endif
rate1 = avg_peak(histogram, ARRAYSIZE(histogram)) * CLOCKS_TO_NS;
rate2 = avg_peak(histogram, ARRAYSIZE(histogram)) * CLOCKS_TO_NS;
if (fabs(rate1 - 230.4) <= 8.0) {
msg(MSG_ERR, "Primary transition period %.0f ns, hopefully this is a SA1000 type disk\n",
rate1);
} else if (fabs(rate1 - 200) > 8.0) {
msg(MSG_ERR, "Primary transition period %.0f ns, should be around 200\n",
rate1);
} else {
if (rate2 <= 280 && rate2 != 0) {
msg(MSG_ERR, "Secondary transition period %.0f ns, likely RLL\n",
rate2);
msg(MSG_ERR, "RLL is not currently supported\n");
}
}
msg(MSG_DEBUG, "First two transition periods %.0f, %.0f ns\n",
rate1, rate2);
}
// Try to find match for existing fully defined format. Some of the formats
// have all data defined such as CRC and other are just defining the header
// format.
//
// drive_params: Drive parameters determined so far and return what we have determined
// Cyl and head: What track the data was from.
// deltas: MFM delta time transition data to analyze. (filled after read)
// max_deltas: Size of deltas array
// match_count: Number of headers that matched for each format found
// return: Number of matching formats found
static int analyze_model(DRIVE_PARAMS *drive_params, int cyl, int head,
void *deltas, int max_deltas)
{
int i;
int cont;
//int controller_type = -1;
// Variable to restore global error print mask
int msg_mask_hold;
// The status of each sector decoded.
SECTOR_STATUS sector_status_list[MAX_SECTORS];
// Set if format doesn't match
int not_match;
// Number of matching formats and what formats matched
int matches = 0;
int match_list[50];
int best_match = 0;
int best_match_count = 999;
drive_read_track(drive_params, cyl, head, deltas, max_deltas, 0);
analyze_rate(drive_params, cyl, head, deltas, max_deltas);
drive_params->num_head = MAX_HEAD;
// Search all the fully defined formats we know about.
// If LBA format don't try to analyze if cyl and head are zero since CHS
// headers will match
for (cont = 0; mfm_controller_info[cont].name != NULL; cont++) {
int missing_count = 0;
if ((mfm_controller_info[cont].analyze_type == CINFO_LBA &&
cyl == 0 && head == 0) ||
mfm_controller_info[cont].write_data_crc.length == 0) {
continue; // ****
}
//printf("Checking %s\n", mfm_controller_info[cont].name);
parse_set_drive_params_from_controller(drive_params, cont);
mfm_init_sector_status_list(sector_status_list, MAX_SECTORS);
msg_mask_hold = msg_set_err_mask(decode_errors);
// Decode track
mfm_decode_track(drive_params, cyl, head, deltas, NULL,
sector_status_list);
msg_set_err_mask(msg_mask_hold);
not_match = 0;
for (i = 0; i < MAX_SECTORS; i++) {
// If we find a good sector outside the range we expect or are
// missing a sector then don't consider it a match. Read errors
// can cause format match to fail.
if (sector_status_list[i].status & ANALYZE_WRONG_FORMAT) {
not_match = 1;
}
if (sector_status_list[i].status & SECT_BAD_HEADER) {
if (i < drive_params->num_sectors) {
// Allow one missed sector
if (missing_count++ >= 1) {
not_match = 1;
}
}
} else {
if (i >= drive_params->num_sectors) {
not_match = 1;
}
}
}
int good_data_count = 0;
for (i = 0; i < drive_params->num_sectors; i++) {
if (!(sector_status_list[i].status & (SECT_BAD_DATA | SECT_BAD_HEADER))) {
good_data_count++;
}
}
//printf("%s not match %d good %d\n", mfm_controller_info[cont].name, not_match, good_data_count);
if (!not_match && good_data_count >= ceil(drive_params->num_sectors * 2 / 3.0) &&
matches < ARRAYSIZE(match_list)) {
int diff = good_data_count - drive_params->num_sectors;
match_list[matches] = cont;
msg(MSG_INFO, "Found matching format %s: good count difference %d\n",
mfm_controller_info[cont].name, diff);
if (abs(diff) < best_match_count) {
best_match_count = abs(diff);
best_match = matches;
}
matches++;
}
}
// If we found at least one match set drive parameters to first match
if (matches >= 1) {
parse_set_drive_params_from_controller(drive_params, match_list[best_match]);
}
return matches;
}
// Try to find the controller type (header format) and CRC parameters for the
// header portion of sectors.
// The data to analyze has already been read before routine called.
//
// drive_params: Drive parameters determined so far and return what we have determined
// Cyl and head: What track the data was from.
// deltas: MFM delta time transition data to analyze. (filled after read)
// max_deltas: Size of deltas array
// drive_params_list: Data for all formats that match
// drive_params_list_len: Size of drive_params_list array
// match_count: Number of headers that matched for each format found
// return: Number of matching formats found
static int analyze_header(DRIVE_PARAMS *drive_params, int cyl, int head,
void *deltas, int max_deltas, DRIVE_PARAMS drive_params_list[],
int drive_params_list_len, int match_count[])
{
// Loop variables
int poly, init, cont;
int i;
int controller_type = -1;
// Numbers of good sectors found
int good_header_count, previous_good_header_count = 0;
// Variable to restore global error print mask
int msg_mask_hold;
// The status of each sector decoded.
SECTOR_STATUS sector_status_list[MAX_SECTORS];
// And read status
SECTOR_DECODE_STATUS status;
// Index into drive_params_list
int drive_params_list_index = 0;
// Minimum and maximum LBA to verify they make sense
int min_lba_addr, max_lba_addr;
drive_read_track(drive_params, cyl, head, deltas, max_deltas, 0);
analyze_rate(drive_params, cyl, head, deltas, max_deltas);
drive_params->num_sectors = MAX_SECTORS;
drive_params->num_head = MAX_HEAD;
// Don't use ECC while trying to find format
drive_params->header_crc.ecc_max_span = 0;
drive_params->data_crc.ecc_max_span = 0;
// Try an exhaustive search of all the formats we know about. If we get too
// many we may have to try something smarter.
// If LBA format don't try to analyze if cyl and head are zero since CHS
// headers will match
for (cont = 0; mfm_controller_info[cont].name != NULL; cont++) {
if ((mfm_controller_info[cont].analyze_type == CINFO_LBA &&
cyl == 0 && head == 0) ||
mfm_controller_info[cont].analyze_search == CONT_MODEL) {
continue; // ****
}
// Make sure these get set at bottom for final controller picked
// Sector size will be set when we analyze the data header
drive_params->controller = cont;
drive_params->sector_size =
mfm_controller_info[cont].analyze_sector_size;
if (!drive_params->dont_change_start_time) {
drive_params->start_time_ns =
mfm_controller_info[cont].start_time_ns;
}
// TODO: This would be faster if we just searched in mfm_process_bytes
// where we check the CRC instead of decoding the MFM transitions
// each time.
for (poly = mfm_controller_info[cont].header_start_poly;
poly < mfm_controller_info[cont].header_end_poly; poly++) {
drive_params->header_crc.poly = mfm_all_poly[poly].poly;
drive_params->header_crc.length = mfm_all_poly[poly].length;
for (init = mfm_controller_info[cont].start_init;
init < mfm_controller_info[cont].end_init; init++) {
// If not correct size don't try this initial value
if (!(mfm_all_init[init].length == -1 || mfm_all_init[init].length ==
drive_params->data_crc.length)) {
continue;
}
drive_params->header_crc.init_value = trim_value(mfm_all_init[init].value,
drive_params->header_crc.length);
drive_params->data_crc = drive_params->header_crc;
msg(MSG_DEBUG, "Trying controller %s ",
mfm_controller_info[drive_params->controller].name);
print_crc_info(&drive_params->header_crc, MSG_DEBUG);
// After the CRC has gone to zero additional zero bytes will
// not cause CRC errors. If we found a valid header we won't
// check any longer ones to prevent false matches.
if (controller_type != -1 && mfm_controller_info[cont].header_bytes >
mfm_controller_info[controller_type].header_bytes) {
break;
}
mfm_init_sector_status_list(sector_status_list, drive_params->num_sectors);
msg_mask_hold = msg_set_err_mask(decode_errors);
// Decode track
status = mfm_decode_track(drive_params, cyl, head, deltas, NULL,
sector_status_list);
msg_set_err_mask(msg_mask_hold);
if (status & SECT_ZERO_HEADER_CRC) {
msg(MSG_DEBUG, "Found zero CRC header controller %s:\n",
mfm_controller_info[drive_params->controller].name);
print_crc_info(&drive_params->header_crc, MSG_DEBUG);
}
// Now find out how many good sectors we got with these parameters
good_header_count = 0;
min_lba_addr = 0x7fffffff;
max_lba_addr = -1;
for (i = 0; i < drive_params->num_sectors; i++) {
if (!(sector_status_list[i].status & SECT_BAD_HEADER) &&
!(sector_status_list[i].status & SECT_AMBIGUOUS_CRC)) {
good_header_count++;
if (sector_status_list[i].lba_addr < min_lba_addr) {
min_lba_addr = sector_status_list[i].lba_addr;
} else if (sector_status_list[i].lba_addr > max_lba_addr) {
max_lba_addr = sector_status_list[i].lba_addr;
}
}
}
// If LBA drive make sure addresses are somewhat adjacent and
// plausible for the cylinder. If not clear good header count
if (mfm_controller_info[cont].analyze_type == CINFO_LBA &&
(max_lba_addr - min_lba_addr > drive_params->num_sectors ||
max_lba_addr - min_lba_addr + 1 < good_header_count ||
min_lba_addr > cyl * 16 * 34)) {
good_header_count = 0;
}
// If we found at least 2 sectors or 1 if sector is large
// enough to only have one per track
if (good_header_count >= 2 || (good_header_count == 1 &&
drive_params->sector_size > 9000) ) {
// Keep the best
if (good_header_count > previous_good_header_count) {
controller_type = drive_params->controller;
previous_good_header_count = good_header_count;
}
if (drive_params_list_index >= drive_params_list_len) {
msg(MSG_FATAL, "Too many header formats found %d\n",
drive_params_list_index);
exit(1);
}
// Save in list
drive_params_list[drive_params_list_index] =
*drive_params;
drive_params_list[drive_params_list_index].header_crc.ecc_max_span
= mfm_all_poly[poly].ecc_span;
// Set the data CRC span also so drives without seaparate data
// and header ECC will have both fields correct.
drive_params_list[drive_params_list_index].data_crc.ecc_max_span
= mfm_all_poly[poly].ecc_span;
match_count[drive_params_list_index++] = good_header_count;
msg(MSG_DEBUG, "Found %d headers matching:\n", good_header_count);
print_crc_info(&drive_params->header_crc, MSG_DEBUG);
msg(MSG_DEBUG, "Controller type %s\n",
mfm_controller_info[drive_params->controller].name);
}
}
}
}
return drive_params_list_index;
}
// Try to find the sector size and CRC parameters for the data portion of
// sectors.
// The data to analyze has already been read before routine called.
//
// drive_params: Drive parameters determined so far and return what we have determined
// deltas: MFM delta time transition data to analyze
// max_deltas: Number of deltas
// header_match: Number of headers found
// *best_match_count: Return Highest good sectors found for set of parameters
// return: Number of matching formats found.
static int analyze_data(DRIVE_PARAMS *drive_params, int cyl, int head, void *deltas, int max_deltas, int headers_match, int *best_match_count)
{
// Return value
int rc = 0;
// Loop variables
int poly, init, size_ndx;
int i;
// The best match CRC and sector size info so far
CRC_INFO data_crc_info;
// And read status
SECTOR_DECODE_STATUS status;
int sector_size = 0;
// Numbers of good sectors found
int good_data_count, previous_good_data_count = 0;
// Variable to restore global error print mask
int msg_mask_hold;
// The status of each sector decoded.
SECTOR_STATUS sector_status_list[MAX_SECTORS];
*best_match_count = 0;
drive_read_track(drive_params, cyl, head, deltas, max_deltas, 0);
data_crc_info.poly = 0;
// Try an exhaustive search of all the formats we know about. If we get too
// many we may have to try something smarter.
for (poly = mfm_controller_info[drive_params->controller].data_start_poly;
poly < mfm_controller_info[drive_params->controller].data_end_poly; poly++) {
drive_params->data_crc.poly = mfm_all_poly[poly].poly;
drive_params->data_crc.length = mfm_all_poly[poly].length;
// This sometimes gets false corrections when using wrong polynomial
// We put it back when we save the best value.
drive_params->data_crc.ecc_max_span = 0;
for (init = mfm_controller_info[drive_params->controller].start_init;
init < mfm_controller_info[drive_params->controller].end_init; init++) {
// If not correct size don't try this initial value
if (!(mfm_all_init[init].length == -1 || mfm_all_init[init].length ==
drive_params->data_crc.length)) {
continue;
}
drive_params->data_crc.init_value = trim_value(mfm_all_init[init].value,
drive_params->data_crc.length);
for (size_ndx = 0; mfm_all_sector_size[size_ndx] != -1; size_ndx++) {
drive_params->sector_size = mfm_all_sector_size[size_ndx];
// If sector longer than one we already found don't try it. More
// zeros after CRC match will still match. Still try larger
// sector sizes if we didn't match that many sectors
if (sector_size != 0 && drive_params->sector_size > sector_size &&
previous_good_data_count >= .6 * headers_match) {
continue;
}
msg(MSG_DEBUG, "Trying controller data %s len %d ",
mfm_controller_info[drive_params->controller].name,
drive_params->sector_size);
print_crc_info(&drive_params->data_crc, MSG_DEBUG);
mfm_init_sector_status_list(sector_status_list, drive_params->num_sectors);
msg_mask_hold = msg_set_err_mask(decode_errors);
// Decode track
status = mfm_decode_track(drive_params, cyl, head, deltas, NULL, sector_status_list);
msg_set_err_mask(msg_mask_hold);
if (status & SECT_ZERO_DATA_CRC) {
msg(MSG_DEBUG, "Found zero CRC data size %d:\n", drive_params->sector_size);
print_crc_info(&drive_params->data_crc, MSG_DEBUG);
}
// Now find out how many good sectors we got with these parameters
good_data_count = 0;
for (i = 0; i < drive_params->num_sectors; i++) {
if (!UNRECOVERED_ERROR(sector_status_list[i].status) &&
!(sector_status_list[i].status & SECT_ANALYZE_ERROR) &&
!(sector_status_list[i].status & SECT_AMBIGUOUS_CRC)) {
good_data_count++;
}
}
// If we found a good sector
if (good_data_count > 0) {
rc++;
// If we have a previous match print both
if (data_crc_info.poly != 0) {
msg(MSG_ERR_SERIOUS,
"Found multiple matching data CRC parameters. Largest matches will be used:\n");
msg(MSG_ERR_SERIOUS, "Matches %d sector size %d ", good_data_count,
drive_params->sector_size);
print_crc_info(&drive_params->data_crc, MSG_ERR_SERIOUS);
msg(MSG_ERR_SERIOUS, "Matches %d sector size %d ", previous_good_data_count,
sector_size);
print_crc_info(&data_crc_info, MSG_ERR_SERIOUS);
}
// And keep the best
if (good_data_count > previous_good_data_count) {
data_crc_info = drive_params->data_crc;
data_crc_info.ecc_max_span = mfm_all_poly[poly].ecc_span;
sector_size = drive_params->sector_size;
previous_good_data_count = good_data_count;
}
*best_match_count = previous_good_data_count;
}
}
}
}
drive_params->sector_size = sector_size;
drive_params->data_crc = data_crc_info;
return rc;
}
// Determine the number of heads, sectors per track, and interleave pattern
//
// drive_params: Drive parameters determined so far and return what we have determined
// cyl: cylinder the data is from
// deltas: MFM delta time transition data to analyze (filled after read)
// TODO, only handles drives where the interleave pattern is the same on all tracks
// TODO, this won't work when heads >= 8 for WD1003 which truncates head
// number in header to 3 bits.
// TODO, make it detect when sector overlaps index and adjust begin time
static void analyze_sectors(DRIVE_PARAMS *drive_params, int cyl, void *deltas,
int max_deltas) {
int msg_mask_hold;
// We return a pointer to this so it must be static (may be better to malloc)
static uint8_t interleave[MAX_SECTORS];
int unknown_interleave;
int head_mismatch = 0;
SECTOR_STATUS sector_status_list[MAX_SECTORS];
int head;
int found_header;
int unrecovered_error;
int max_sector, min_sector, last_good_head;
int i;
SECTOR_DECODE_STATUS status;
int last_min_lba = 0;
// TODD, this should also detect the WD controllers that support 16 heads but only
// put 0-7 in the header.
max_sector = 0;
min_sector = MAX_SECTORS;
last_good_head = -1;
unrecovered_error = 0;
unknown_interleave = 0;
memset(interleave, 255, sizeof(interleave));
for (head = 0; head < MAX_HEAD && !head_mismatch; head++) {
int found_bad_header;
int err_count = 0;
int min_lba = INT_MAX;
msg_mask_hold = msg_set_err_mask(decode_errors);
// Try to get a good read. sector_status_list will be the best from
// all the reads.
do {
if (drive_read_track(drive_params, cyl, head, deltas, max_deltas, 1)) {
msg(MSG_FORMAT, "Got write fault on head %d, stopping search\n",head);
head_mismatch = 1;
status = 0;
break;
}
mfm_init_sector_status_list(sector_status_list, drive_params->num_sectors);
status = mfm_decode_track(drive_params, cyl, head, deltas, NULL, sector_status_list);
if (UNRECOVERED_ERROR(status) && head == 8 &&
drive_params->controller == CONTROLLER_WD_1006) {
int good_header = 0;
for (i = 0; i < drive_params->num_sectors; i++) {
if ((sector_status_list[i].status & SECT_HEADER_FOUND) &&
!(sector_status_list[i].status & SECT_BAD_HEADER)) {
good_header = 1;
}
}
// Mightyframe encodes head 8-15 differently. If we don't find
// any good headers on head 8 see if its a Mightyframe.
// TODO, Should we stop search after a few bad heads?
if (!good_header && last_good_head == 7 && head == 8) {
int orig_controller = drive_params->controller;
drive_params->controller = CONTROLLER_MIGHTYFRAME;
status = mfm_decode_track(drive_params, cyl, head, deltas,
NULL, sector_status_list);
for (i = 0; i < drive_params->num_sectors; i++) {
if ((sector_status_list[i].status & SECT_HEADER_FOUND) &&
!(sector_status_list[i].status & SECT_BAD_HEADER)) {
good_header = 1;
}
}
if (good_header) {
msg(MSG_FORMAT,"Changed controller type to %s\n",
mfm_controller_info[drive_params->controller].name);
} else {
drive_params->controller = CONTROLLER_DG_MV2000;
status = mfm_decode_track(drive_params, cyl, head, deltas,
NULL, sector_status_list);
for (i = 0; i < drive_params->num_sectors; i++) {
if ((sector_status_list[i].status & SECT_HEADER_FOUND) &&
!(sector_status_list[i].status & SECT_BAD_HEADER)) {
good_header = 1;
}
}
if (good_header) {
msg(MSG_FORMAT,"Changed controller type to %s\n",
mfm_controller_info[drive_params->controller].name);
} else {
drive_params->controller = orig_controller;
status = mfm_decode_track(drive_params, cyl, head, deltas,
NULL, sector_status_list);
}
}
}
}
} while (UNRECOVERED_ERROR(status) && ++err_count < 8);
msg_set_err_mask(msg_mask_hold);
if (UNRECOVERED_ERROR(status)) {
unrecovered_error = 1;
}
found_bad_header = 0;
found_header = 0;
for (i = 0; i < drive_params->num_sectors; i++) {
// If we missed a header after finding one, stop looking at
// interleave since it is likely to be wrong. With
// read errors we still may get confused.
if ((sector_status_list[i].status & SECT_BAD_HEADER) && found_header) {
found_bad_header = 1;
}
if (sector_status_list[i].status & SECT_HEADER_FOUND) {
found_header = 1;
if (!found_bad_header) {
if (interleave[sector_status_list[i].logical_sector] != 255 &&
interleave[sector_status_list[i].logical_sector] !=
sector_status_list[i].sector && !unknown_interleave) {
msg(MSG_ERR, "Interleave mismatch previous entry %d, %d was %d now %d\n",
i, sector_status_list[i].logical_sector,
interleave[sector_status_list[i].logical_sector],
sector_status_list[i].sector);
unknown_interleave = 1;
}
interleave[sector_status_list[i].logical_sector] = sector_status_list[i].sector;
}
max_sector = MAX(max_sector, sector_status_list[i].sector);
min_sector = MIN(min_sector, sector_status_list[i].sector);
if (mfm_controller_info[drive_params->controller].analyze_type == CINFO_LBA) {
if (sector_status_list[i].lba_addr < min_lba) {
min_lba = sector_status_list[i].lba_addr;
}
// If LBA address found is lower than minimum on previous head
// this head doesn't exist. -2 in case of read error so we
// missed some sectors
if (sector_status_list[i].lba_addr >= last_min_lba - 2) {
last_good_head = head;
} else if (!head_mismatch) {
msg(MSG_INFO, "Selected head %d found out of series LBA address, last good head found %d\n",
head, last_good_head);
head_mismatch = 1;
}
} else {
if (sector_status_list[i].head == head) {
last_good_head = head;
} else {
if (!head_mismatch) {
msg(MSG_INFO, "Selected head %d found %d, last good head found %d\n",
head, sector_status_list[i].head, last_good_head);
head_mismatch = 1;
}
}
}
}
}
last_min_lba = min_lba;
}
drive_set_head(0); // Make sure head valid for NEC drives
// If we had a read error but got some good error warn. If nothing readable
// assume we were trying to read an invalid head
if (unrecovered_error && found_header) {
msg(MSG_ERR, "Read errors trying to determine sector numbering, results may be in error\n");
}
if (last_good_head == -1) {
msg(MSG_FATAL, "Unable to determine number of heads\n");
exit(1);
}
// We store number of heads, not maximum head number (starting at 0)
drive_params->num_head = last_good_head+1;
drive_params->num_sectors = max_sector - min_sector + 1;
drive_params->first_sector_number = min_sector;
if (drive_params->controller == CONTROLLER_EC1841) {
drive_params->first_logical_sector = interleave[1];
msg(MSG_INFO, "First logical sector %d\n", drive_params->first_logical_sector);
}
msg(MSG_INFO, "Number of heads %d number of sectors %d first sector %d\n",
drive_params->num_head, drive_params->num_sectors,
drive_params->first_sector_number);
if (unknown_interleave) {
msg(MSG_ERR, "Unable to determine interleave. Interleave value is not required\n");
drive_params->sector_numbers = NULL;
} else {
msg(MSG_INFO, "Interleave (not checked):");
for (i = 0; i < drive_params->num_sectors; i++) {
msg(MSG_INFO, " %d",interleave[i]);
}
msg(MSG_INFO, "\n");
// Too many drives have cylinders with different interleave (spare,
// for testing) that cause confusing errors so checking is disabled
// unless users specified directly
//drive_params->sector_numbers = interleave;
drive_params->sector_numbers = NULL;
}
}
// Test if seek at specified rate works. We
//
// drive_params: Drive parameters determined so far.
// return: 0 if seek at specified speed worked, 1 otherwise
static int analyze_seek(DRIVE_PARAMS *drive_params) {
int seek;
int rc = 0;
int i;
drive_seek_track0();
seek = 30;
// Step down the drive at the specified rate then verify
// it takes the same number of slow steps to return back to
// track 0.
drive_step(drive_params->step_speed, seek,
DRIVE_STEP_UPDATE_CYL, DRIVE_STEP_FATAL_ERR);
// Give time for track0 to change before we check
usleep(1000);
if (drive_at_track0()) {
msg(MSG_INFO, "Drive still at track 0 after seek\n");
rc = 1;
} else {
for (i = 1; i <= seek && rc == 0; i++) {
drive_step(DRIVE_STEP_SLOW, -1,
DRIVE_STEP_UPDATE_CYL, DRIVE_STEP_FATAL_ERR);
if (i == seek) {
// Give time for track0 to change before we check
usleep(1000);
if (!drive_at_track0()) {
msg(MSG_INFO, "Drive didn't reach track 0 testing %s seek\n",
step_speed_text(drive_params->step_speed));
rc = 1;
}
} else {
if (drive_at_track0()) {
msg(MSG_INFO, "Drive prematurely at track 0 after %d of %d steps testing %s seek\n",
i, seek, step_speed_text(drive_params->step_speed));
rc = 1;
}
}
}
drive_seek_track0();
}
return rc;
}
// Find the number of cylinders the disk has. We step down the disk until we
// find unreadable tracks, the cylinder number in header doesn't match, or
// seek times out. Various disks have different behavior. The tracks past the end
// leading to the park zone are normally not formated. Many drives won't let you
// seek past the last cylinder. They either return to track zero or perform a
// recalibrate which may take enough time for the seek to timeout. Really old
// disk will just hit the end stop so can get the same cylinder. Hitting end stop
// probably isn't great.
//
// drive_params: Drive parameters determined so far and return what we have determined
// start_cyl: cylinder head is on at entry
// head: head currently selected
// deltas: MFM delta time transition data to analyze (filled after read)
//
// TODO, this won't deal well with spared cylinders when they have nonsequential
// values
static void analyze_disk_size(DRIVE_PARAMS *drive_params, int start_cyl,
int head, void *deltas, int max_deltas) {
int msg_mask_hold;
int max_cyl;
int not_next_cyl, not_next_cyl_count;
int any_header_found;
int no_header_count;
SECTOR_STATUS sector_status_list[MAX_SECTORS];
int cyl;
int i;
int last_printed_cyl;
int rc;
max_cyl = 0;
no_header_count = 0;
not_next_cyl_count = 0;
for (cyl = start_cyl + 1; cyl < MAX_CYL; cyl++) {
if (cyl % 5 == 0)
msg(MSG_PROGRESS, "At cyl %d\r", cyl);
rc = drive_step(drive_params->step_speed, 1,
DRIVE_STEP_UPDATE_CYL, DRIVE_STEP_RET_ERR);
if (rc == DRIVE_STEP_TIMEOUT) {
msg(MSG_INFO, "Max cylinder set from drive timeout on seek\n");
break;
} else if (rc == DRIVE_STEP_RECAL) {
msg(MSG_INFO, "Stopping end of disk search due to recalibration\n");
break;
}
drive_read_track(drive_params, cyl, head, deltas, max_deltas, 0);
mfm_init_sector_status_list(sector_status_list, drive_params->num_sectors);
msg_mask_hold = msg_set_err_mask(decode_errors);
mfm_decode_track(drive_params, cyl, head, deltas, NULL, sector_status_list);
msg_set_err_mask(msg_mask_hold);
any_header_found = 0;
not_next_cyl = 0;
last_printed_cyl = -1;
for (i = 0; i < drive_params->num_sectors; i++) {
if (sector_status_list[i].status & SECT_HEADER_FOUND) {
any_header_found = 1;
max_cyl = MAX(max_cyl, sector_status_list[i].cyl);
if (cyl != sector_status_list[i].cyl && last_printed_cyl != cyl) {
msg(MSG_INFO, "Found cylinder %d expected %d\n",sector_status_list[i].cyl, cyl);
last_printed_cyl = cyl;
not_next_cyl = 1;
}
}
}
if (!any_header_found) {
if (++no_header_count >= 2) {
msg(MSG_INFO, "Stopping end of disk search due to two unreadable tracks in a row\n");
break;
} else {
msg(MSG_INFO, "No sectors readable from cylinder %d\n",cyl);
}
} else {
no_header_count = 0;
}
if (not_next_cyl) {
if (++not_next_cyl_count >= 2) {
msg(MSG_INFO, "Stopping end of disk search due to mismatching cylinder count\n");
break;
}
} else {
not_next_cyl_count = 0;
}
}
drive_params->num_cyl = max_cyl + 1;
msg(MSG_INFO, "Number of cylinders %d, %.1f MB\n", drive_params->num_cyl,
(double) drive_params->num_cyl * drive_params->num_head *
drive_params->num_sectors * drive_params->sector_size / (1000.0*1000));
// We don't know where head is so return to 0
drive_seek_track0();
}
// Try to identify format of header
// Head will be left at specified cylinder on return
//
// drive_params: Drive parameters determined so far and return what we have determined
// deltas: MFM delta time transition data to analyze (filled after read)
// max_delta: Size of deltas array
// cyl: cylinder to test
// head: head to test
// return: Number of matching formats found
int analyze_headers(DRIVE_PARAMS *drive_params, void *deltas,
int max_deltas, int cyl, int head)
{
int headers_match;
DRIVE_PARAMS drive_params_list[20];
int match_count[ARRAYSIZE(drive_params_list)];
int i;
int max_match = 0, max_match_index = -1;
int format_count = 0;
int data_matches;
headers_match = analyze_header(drive_params, cyl, head, deltas, max_deltas,
drive_params_list, ARRAYSIZE(drive_params_list), match_count);
if (headers_match != 0) {
// If drive has separate data area check it
for (i = 0; i < headers_match; i++) {
if (mfm_controller_info[drive_params_list[i].controller].separate_data) {
if (analyze_data(&drive_params_list[i], cyl, head, deltas,
max_deltas, match_count[i], &data_matches) > 1) {
format_count++;
}
} else {
data_matches = match_count[i];
}
if (data_matches > 0) {
// Pick based on number of header and data matches
data_matches += match_count[i];
format_count++;
if (max_match > 0) {
msg(MSG_ERR_SERIOUS, "Found multiple matching header parameters. Will use largest matches or last if identical\n");
}
msg(MSG_ERR_SERIOUS, "Matches count %d for controller %s\nHeader CRC: ", data_matches,
mfm_controller_info[drive_params_list[i].controller].name);
print_crc_info(&drive_params_list[i].header_crc, MSG_ERR_SERIOUS);
if (mfm_controller_info[drive_params_list[i].controller].separate_data) {
msg(MSG_ERR_SERIOUS, "Sector length %d\nData CRC: ",
drive_params_list[i].sector_size);
print_crc_info(&drive_params_list[i].data_crc, MSG_ERR_SERIOUS);
} else {
msg(MSG_ERR_SERIOUS, "Sector length %d\n",
drive_params_list[i].sector_size);
}
// Use sum of data & header match count
if (data_matches >= max_match) {
max_match = data_matches;
max_match_index = i;
}
}
}
}
if (max_match_index >= 0) {
*drive_params = drive_params_list[max_match_index];
}
return format_count;
}
// Try to identify format of drive
// Head will be left at specified cylinder on return
//
// drive_params: Drive parameters determined so far and return what we have determined
// deltas: MFM delta time transition data to analyze (filled after read)
// max_delta: Size of deltas array
// cyl: cylinder to test
// head: head to test
// return: Number of matching formats found
int analyze_format(DRIVE_PARAMS *drive_params, void *deltas, int max_deltas,
int cyl, int head)
{
int rc;
rc = analyze_model(drive_params, cyl, head, deltas, max_deltas);
// More than one format found, try different track
// TODO: This should be != 0 after model is primary method to detect.
if (rc > 1) {
// If non zero cylinder retry on zero. If drive has stuck heads this
// may allow format to be detected
if (cyl > 0) {
cyl = 0;
} else {
cyl = cyl + 1;
}
head = 1;
msg(MSG_INFO,"Retrying on cylinder %d head %d\n", cyl, head);
rc = analyze_model(drive_params, cyl, head, deltas, max_deltas);
}
if (rc > 1) {
msg(MSG_ERR, "Multiple matching formats found, using best\n");
}
if (rc >= 1) {
DRIVE_PARAMS drive_params_hold;
// FIXME: This may do more than we want. We only need to determine
// number of heads. For now if it doesn't match we fall back to normal
// format detection.
analyze_sectors(drive_params, cyl, deltas, max_deltas);
drive_params_hold = *drive_params;
parse_set_drive_params_from_controller(drive_params, drive_params->controller);
if (drive_params_hold.num_sectors != drive_params->num_sectors ||
drive_params_hold.first_sector_number != drive_params->first_sector_number ||
drive_params_hold.controller != drive_params->controller) {
msg(MSG_INFO, "Sector information detected doesn't match expected format, trying again\n");
} else {
return rc;
}
}
rc = analyze_headers(drive_params, deltas, max_deltas, cyl, head);
// For the ST11M only the first two heads are used on the first
// cylinder. Retry on the next to get proper number of heads.
if (rc != 1 || (drive_params->controller == CONTROLLER_SEAGATE_ST11M &&
cyl == 0)) {
if (cyl != 0 && drive_at_track0()) {
msg(MSG_ERR, "Drive indicating track 0 when should be on cylinder %d\n",
cyl);
} else if (cyl == 0 && !drive_at_track0()) {
msg(MSG_ERR, "Drive not indicating track 0 when should be on cylinder %d\n",
cyl);
}
// If non zero cylinder retry on zero. If drive has stuck heads this
// may allow format to be detected
if (cyl > 0) {
cyl = 0;
} else {
cyl = cyl + 1;
}
head = 1;
msg(MSG_INFO,"Retrying on cylinder %d head %d\n", cyl, head);
// If we either got no valid information or multiple possible values try
// cyl 1 head 1. Cyl 0 head 0 is poor for distinguishing different header
// formats. Also may help if first track has too many read errors.
// Analyzing track 0 is useful for detecting the weird multiple formats
// for the DEC RQDX3 so it is tested first.
rc = analyze_headers(drive_params, deltas, max_deltas, cyl, head);
if (cyl != 0 && drive_at_track0()) {
msg(MSG_ERR, "Drive indicating track 0 when should be on cylinder %d\n",
cyl);
} else if (cyl == 0 && !drive_at_track0()) {
msg(MSG_ERR, "Drive not indicating track 0 when should be on cylinder %d\n",
cyl);
}
}
// Don't check sectors if we didn't figure out format
if (rc != 0) {
analyze_sectors(drive_params, cyl, deltas, max_deltas);
}
return rc;
}
// Routine to determine the various drive parameters. See individual routines
// for details on operation.
//
// drive_params: Return parameters we have determined
// deltas: MFM delta time transition data to analyze (filled after read)
void analyze_disk(DRIVE_PARAMS *drive_params, void *deltas, int max_deltas,
int use_file)
{
int ready = 0;
int i;
int cyl, head;
DRIVE_PARAMS drive_params_hold;
cyl = drive_params->analyze_cyl;
head = drive_params->analyze_head;
// Turn off errors from MFM routines while analyzing. Too many
// messages otherwise.
decode_errors = MSG_FATAL | MSG_FORMAT | (msg_get_err_mask() & MSG_DEBUG);
// Turn on all errors to see why decoding fails
//decode_errors = ~1;
//decode_errors = ~0;
// Save off parameters so we can put back ones we changed as side
// effects of the analysis.
drive_params_hold = *drive_params;
// We don't want to write the analysis information to files
drive_params->extract_filename = NULL;
drive_params->transitions_filename = NULL;
// We will analyze this later
drive_params->sector_numbers = NULL;
// Ignore header errors. Was used during testing.
//drive_params->ignore_header_mismatch = 1;
drive_params->analyze_in_progress = 1;
if (!use_file) {
// Step through all the head selects to find the drive
for (i = 1; i <= 4 && !ready; i++) {
int wait_count = 0;
drive_params->drive = i;
drive_select(drive_params->drive);
// Wait up to 100 ms for drive to be selected
while (wait_count++ < 100 && !ready) {
usleep(1000);
// Bit is active low
ready = !(drive_get_drive_status() & BIT_MASK(R31_DRIVE_SEL));
}
}
if (ready) {
msg(MSG_INFO,"Found drive at select %d\n",drive_params->drive);
} else {
msg(MSG_INFO,"Unable to find drive. If just powered on retry in 30 seconds\n");
exit(1);
}
drive_setup(drive_params);
msg(MSG_INFO, "Drive RPM %.1f\n", drive_rpm());
// We don't want to write transitions to file so pass null
deltas_start_thread(NULL);
}
// For header and field analysis we read one track and reanalyze the same data
mfm_decode_setup(drive_params, 0);
if (analyze_format(drive_params, deltas, max_deltas, cyl, head) == 0) {
msg(MSG_ERR, "Unable to determine drive format\n");
exit(1);
}
if (!use_file) {
// Try a buffered seek and if it doesn't work try an unbuffered seek
drive_params->step_speed = DRIVE_STEP_FAST;
if (analyze_seek(drive_params) != 0) {
drive_params->step_speed = DRIVE_STEP_SLOW;
if (analyze_seek(drive_params) != 0) {
msg(MSG_FATAL, "Drive is not seeking properly\n");
exit(1);
}
}
if (drive_params->step_speed == DRIVE_STEP_FAST) {
msg(MSG_INFO, "Drive supports buffered seeks (ST412)\n");
} else {
msg(MSG_INFO, "Drive doesn't support buffered seeks (ST506)\n");
}
analyze_disk_size(drive_params, cyl, head, deltas, max_deltas);
} else {
if (drive_params->tran_fd != -1) {
drive_params->num_cyl = drive_params->tran_file_info->num_cyl;
} else {
drive_params->num_cyl = drive_params->emu_file_info->num_cyl;
}
}
// And restore items we changed that aren't determined by the analysis
drive_params->ignore_header_mismatch = drive_params_hold.ignore_header_mismatch;
drive_params->extract_filename = drive_params_hold.extract_filename;
drive_params->transitions_filename = drive_params_hold.transitions_filename;
if (!use_file) {
deltas_stop_thread();
}
drive_params->analyze_in_progress = 0;
}