// This routine decodes "Western Digital" (WD) formated disks with two
// headers on each sector.
//
// TODO Improve data separator and resync better after end of
// write transition area. Also why avg_bit_sep_time is < 20 for all drives
// We probably should be able to do better than just the PLL since we can
// look ahead.
//
// 12/08/22 DJG Changed error message
// 07/20/22 DJG Process sector if bytes decoded exactly matches needed
// 03/17/22 DJG Handle large deltas and improved error message
// 07/05/19 DJG Improved 3 bit head field handling
// 04/22/18 DJG Added support for non 10 MHz bit rate and
// format Xerox 8010
// 04/21/17 DJG Added parameter to mfm_check_header_values and added
// determining --begin_time if needed
// 11/14/16 DJG Added Telenex Autoscope. Same as Xerox but without tags
// so naming problems again
// 11/02/16 DJG Write out tag/metadata also if extract file speccified
// 10/16/16 DJG Fixes for XEROX_6085.
// 10/07/16 DJG Add support for disks with tag header in addition to the
// normal header. Xerox 6085
//
// Copyright 2022 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 "msg.h"
#include "crc_ecc.h"
#include "emu_tran_file.h"
#include "mfm_decoder.h"
#include "deltas_read.h"
// Side of data to skip after header or data area.
#define HEADER_IGNORE_BYTES 10
// For data this is the write splice area where you get corrupted data that
// may look like a sector start byte.
#define DATA_IGNORE_BYTES 10
// Type II PLL. Here so it will inline. Converted from continuous time
// by bilinear transformation. Coefficients adjusted to work best with
// my data. Could use some more work.
static inline float filter(float v, float *delay)
{
float in, out;
in = v + *delay;
out = in * 0.034446428576716f + *delay * -0.034124999994713f;
*delay = in;
return out;
}
// Decode bytes into header or sector data for the various formats we know about.
// The decoded data will be written to a file if one was specified.
// Since processing a header with errors can overwrite other good sectors this routine
// shouldn't be called with header data that has a CRC error.
//
// The general format is first byte of header or data area is an 0xa1 with a
// missing clock bit. This is the first byte in the bytes array though finding and
// validating it are done outside this routine. The format names are arbitrarily
// assigned to the first controller found writing that format.
// The formats are
// CONTROLLER_XEROX_6085
// 6 byte header + 2 byte CRC
// byte 0 0xa1
// byte 1 0xfe
// byte 2 cylinder high
// byte 3 cylinder low
// byte 4 head low four bits, unknown if upper used
// byte 5 sector
// byte 6-7 ECC code
// Tag
// byte 0 0xa1
// byte 1 0xfc
// 20 bytes of data
// ECC code (2 byte)
// Data
// byte 0 0xa1
// byte 1 0xfb
// Sector data for sector size
// (alt cyl first 2 bytes msb first, head third. Also not known
// if correct for this format)
// ECC code (2 byte)
//
// CONTROLLER_TELENEX_AUTOSCOPE
// Same as XEROX_6085 except no tag
//
// CONTROLLER_XEROX_8010. Found on 8" SA1004 drive. Best guess at what
// used with
// Quantum image xerox-1108-chinacat-q2040.trans
// Shugart image sa1004_d.tran
// 6 byte header + 2 byte CRC
// byte 0 0xa1
// byte 1 0x41
// byte 2 cylinder high? Sample only has 256 cylinders.
// byte 3 cylinder low
// byte 4 head
// byte 5 sector number
// bytes 6-7 16 bit CRC 0xffff,0x8005,16
// Tag
// byte 0 0xa1
// byte 1 0x43
// 24 bytes of data
// 16 bit CRC 0xffff,0x8005,16
// Data
// byte 0 0xa1
// byte 1 0x43
// Sector data for sector size
// 16 bit CRC 0xffff,0x8005,16
//
// state: Current state in the decoding
// bytes: bytes to process
// crc: The crc of the bytes
// exp_cyl, exp_head: Track we think we are on
// sector_index: A sequential sector counter that may not match the sector
// numbers
// drive_params: Drive parameters
// seek_difference: Return the difference between expected cylinder and
// cylinder in header
// sector_status_list: The status of each sector (read errors etc)
// ecc_span: The maximum ECC span to correct (0 for no correction)
//
// TODO: Reformat drive with the various sector size/bad block/spare track
// options and verify headers decoded properly.
// Handle spared/alternate tracks for extracted data
SECTOR_DECODE_STATUS tagged_process_data(STATE_TYPE *state, uint8_t bytes[],
int total_bytes,
uint64_t crc, int exp_cyl, int exp_head, int *sector_index,
DRIVE_PARAMS *drive_params, int *seek_difference,
SECTOR_STATUS sector_status_list[], int ecc_span,
SECTOR_DECODE_STATUS init_status)
{
static int sector_size;
// Non zero if sector is a bad block, has alternate track assigned,
// or is an alternate track
static SECTOR_STATUS sector_status;
if (*state == PROCESS_HEADER) {
memset(§or_status, 0, sizeof(sector_status));
sector_status.status |= init_status | SECT_HEADER_FOUND;
sector_status.ecc_span_corrected_header = ecc_span;
if (ecc_span != 0) {
sector_status.status |= SECT_ECC_RECOVERED;
}
if (drive_params->controller == CONTROLLER_XEROX_6085 ||
drive_params->controller == CONTROLLER_TELENEX_AUTOSCOPE) {
sector_status.cyl = bytes[2]<< 8;
sector_status.cyl |= bytes[3];
// More is in here but what is not documented in manual
sector_status.head = mfm_fix_head(drive_params, exp_head, bytes[4] & 0xf);
if ((bytes[4] & 0xf0) != 0) {
msg(MSG_INFO, "byte 4 upper bits not zero: %02x on cyl %d head %d sector %d\n",
bytes[4], sector_status.cyl, sector_status.head, sector_status.sector);
msg(MSG_INFO, "May indicate bad block or alternate sector\n");
// Rest of format matches CONTROLLER_OMTI_5510 so this byte may also
}
sector_size = drive_params->sector_size;
sector_status.sector = bytes[5];
if (bytes[1] != 0xfe) {
msg(MSG_INFO, "Invalid header id byte %02x on cyl %d head %d sector %d\n",
bytes[1], sector_status.cyl, sector_status.head, sector_status.sector);
sector_status.status |= SECT_BAD_HEADER;
}
} else if (drive_params->controller == CONTROLLER_XEROX_8010) {
sector_status.cyl = bytes[3] | ((bytes[2] & 0xf) << 8);
sector_status.head = mfm_fix_head(drive_params, exp_head, bytes[4]);
sector_size = drive_params->sector_size;
sector_status.sector = bytes[5];
if ((bytes[1]) != 0x41) {
msg(MSG_INFO, "Invalid header id byte %02x on cyl %d,%d head %d,%d sector %d\n",
bytes[1], exp_cyl, sector_status.cyl,
exp_head, sector_status.head, sector_status.sector);
sector_status.status |= SECT_BAD_HEADER;
}
} else {
msg(MSG_FATAL,"Unknown controller type %d\n",drive_params->controller);
exit(1);
}
mfm_check_header_values(exp_cyl, exp_head, sector_index, sector_size,
seek_difference, §or_status, drive_params, sector_status_list);
msg(MSG_DEBUG,
"Got exp %d,%d cyl %d head %d sector %d,%d size %d\n",
exp_cyl, exp_head, sector_status.cyl, sector_status.head,
sector_status.sector, *sector_index, sector_size);
if ((drive_params->controller == CONTROLLER_XEROX_6085) ||
(drive_params->controller == CONTROLLER_XEROX_8010)) {
*state = MARK_DATA1;
} else {
*state = MARK_DATA;
}
} else if (*state == PROCESS_HEADER2) {
sector_status.status |= init_status;
if (drive_params->controller == CONTROLLER_XEROX_8010) {
if (bytes[1] != 0x43) {
msg(MSG_INFO, "Invalid tag id byte %02x on cyl %d,%d head %d,%d sector %d\n",
bytes[1], exp_cyl, sector_status.cyl,
exp_head, sector_status.head, sector_status.sector);
sector_status.status |= SECT_BAD_HEADER;
}
} else {
if (bytes[1] != 0xfc) {
msg(MSG_INFO, "Invalid tag id byte %02x on cyl %d,%d head %d,%d sector %d\n",
bytes[1], exp_cyl, sector_status.cyl,
exp_head, sector_status.head, sector_status.sector);
sector_status.status |= SECT_BAD_HEADER;
}
}
if (crc != 0) {
sector_status.status |= SECT_BAD_DATA;
}
if (ecc_span != 0) {
sector_status.status |= SECT_ECC_RECOVERED;
}
sector_status.ecc_span_corrected_data = ecc_span;
*state = MARK_DATA;
if (!(sector_status.status & SECT_BAD_HEADER)) {
mfm_write_metadata(&bytes[2], drive_params, §or_status);
}
} else { // Data
// Value and where to look for header mark byte
int id_byte_expected = 0xfb;
if (drive_params->controller == CONTROLLER_XEROX_8010) {
id_byte_expected = 0x43;
}
int id_byte_index = 1;
if (bytes[id_byte_index] != id_byte_expected && crc == 0) {
msg(MSG_INFO,"Invalid data id byte %02x expected %02x on cyl %d head %d sector %d\n",
bytes[id_byte_index], id_byte_expected,
sector_status.cyl, sector_status.head, sector_status.sector);
sector_status.status |= SECT_BAD_DATA;
}
if (crc != 0) {
sector_status.status |= SECT_BAD_DATA;
}
if (ecc_span != 0) {
sector_status.status |= SECT_ECC_RECOVERED;
}
sector_status.ecc_span_corrected_data = ecc_span;
if (!(sector_status.status & SECT_BAD_HEADER)) {
int dheader_bytes = mfm_controller_info[drive_params->controller].data_header_bytes;
if (mfm_write_sector(&bytes[dheader_bytes], drive_params, §or_status,
sector_status_list, &bytes[1], total_bytes-1) == -1) {
sector_status.status |= SECT_BAD_HEADER;
}
}
*state = MARK_ID;
}
return sector_status.status;
}
// Decode a track's worth of deltas.
//
//
// drive_params: Drive parameters
// cyl,head: Physical Track data from
// deltas: MFM delta data to decode
// seek_difference: Return of difference between expected cyl and header
// sector_status_list: Return of status of decoded sector
// return: Or together of the status of each sector decoded
SECTOR_DECODE_STATUS tagged_decode_track(DRIVE_PARAMS *drive_params, int cyl,
int head, uint16_t deltas[], int *seek_difference,
SECTOR_STATUS sector_status_list[])
{
// This is which MFM clock and data bits are valid codes. So far haven't
// found a good way to use this.
//int valid_code[16] = { 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0 };
// This converts the MFM clock and data bits into data bits.
int code_bits[16] = { 0, 1, 0, 0, 2, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
// This is the raw MFM data decoded with above
unsigned int raw_word = 0;
// Counter to know when to decode the next two bits.
int raw_bit_cntr = 0;
// The decoded bits
unsigned int decoded_word = 0;
// Counter to know when we have a bytes worth
int decoded_bit_cntr = 0;
// loop counter
int i;
// These are variables for the PLL filter. avg_bit_sep_time is the
// "VCO" frequency
float avg_bit_sep_time; // 200 MHz clocks
float nominal_bit_sep_time; // 200 MHz clocks
// Clock time is the clock edge time from the VCO.
float clock_time = 0;
// How many bits the last delta corresponded to
int int_bit_pos;
float filter_state = 0;
// Time in track for debugging
int track_time = 0;
// Counter for debugging
int tot_raw_bit_cntr = 0;
// Where we are in decoding a sector, Start looking for header ID mark
STATE_TYPE state = MARK_ID;
// Status of decoding returned
SECTOR_DECODE_STATUS all_sector_status = SECT_NO_STATUS;
// How many zeros we need to see before we will look for the 0xa1 byte.
// When write turns on and off can cause codes that look like the 0xa1
// so this avoids them. Some drives seem to have small number of
// zeros after sector marked bad in header.
// TODO: look at v170.raw file and see if number of bits since last
// header should be used to help separate good from false ID.
// also ignoring known write splice location should help.
#define MARK_NUM_ZEROS 2
int zero_count = 0;
// Number of deltas available so far to process
int num_deltas;
// And number from last time
int last_deltas = 0;
// If we get too large a delta we need to process it in less than 32 bit
// word number of bits. This holds remaining number to process
int remaining_delta = 0;
// Maximum delta to process in one pass
int max_delta;
// Intermediate value
int tmp_raw_word;
// Collect bytes to further process here
uint8_t bytes[MAX_SECTOR_SIZE + 50];
// How many we need before passing them to the next routine
int bytes_needed = 0;
// Length to perform CRC over
int bytes_crc_len = 0;
// how many we have so far
int byte_cntr = 0;
// Sequential counter for counting sectors
int sector_index = 0;
// Count all the raw bits for emulation file
int all_raw_bits_count = 0;
// Used for analyze to distinguish formats with and without extra
// header
SECTOR_DECODE_STATUS init_status = 0;
// First address mark time in ns
int first_addr_mark_ns = 0;
num_deltas = deltas_get_count(0);
raw_word = 0;
nominal_bit_sep_time = 200e6 /
mfm_controller_info[drive_params->controller].clk_rate_hz;
max_delta = nominal_bit_sep_time * 22;
avg_bit_sep_time = nominal_bit_sep_time;
i = 1;
while (num_deltas >= 0) {
// We process what we have then check for more.
for (; i < num_deltas;) {
int delta_process;
// If no remaining delta process next else finish remaining
if (remaining_delta == 0) {
delta_process = deltas[i++];
remaining_delta = delta_process;
} else {
delta_process = remaining_delta;
}
// Don't overflow our 32 bit word
if (delta_process > max_delta) {
delta_process = max_delta;
}
track_time += delta_process;
// This is simulating a PLL/VCO clock sampling the data.
clock_time += delta_process;
remaining_delta -= delta_process;
// Move the clock in current frequency steps and count how many bits
// the delta time corresponds to
for (int_bit_pos = 0; clock_time > avg_bit_sep_time / 2;
clock_time -= avg_bit_sep_time, int_bit_pos++) {
}
// And then filter based on the time difference between the delta and
// the clock. Don't update PLL if this is a long burst without
// transitions
if (remaining_delta == 0) {
avg_bit_sep_time = nominal_bit_sep_time + filter(clock_time, &filter_state);
}
#if 0
if (cyl == 819 && head == 0) {
printf
(" delta %d %d clock %.2f int bit pos %d avg_bit %.2f time %d\n",
deltas[i], i, clock_time,
int_bit_pos, avg_bit_sep_time, track_time);
}
#endif
if (all_raw_bits_count + int_bit_pos >= 32) {
all_raw_bits_count = mfm_save_raw_word(drive_params,
all_raw_bits_count, int_bit_pos, raw_word);
} else {
all_raw_bits_count += int_bit_pos;
}
// Shift based on number of bit times then put in the 1 from the
// delta. If we had a delta greater than the size of raw word we
// will lose the unprocessed bits in raw_word. This is unlikely
// to matter since this is invalid MFM data so the disk had a long
// drop out so many more bits are lost.
if (int_bit_pos >= sizeof(raw_word)*8) {
raw_word = 1;
} else {
raw_word = (raw_word << int_bit_pos) | 1;
}
tot_raw_bit_cntr += int_bit_pos;
raw_bit_cntr += int_bit_pos;
// Are we looking for a mark code?
if ((state == MARK_ID || state == MARK_DATA || state == MARK_DATA1)) {
// These patterns are MFM encoded all zeros or all ones.
// We are looking for zeros so we assume they are zeros.
if (raw_word == 0x55555555 || raw_word == 0xaaaaaaaa) {
zero_count++;
} else {
if (zero_count < MARK_NUM_ZEROS) {
zero_count = 0;
}
}
// This is the 0x891 missing clock MFM sync pattern for 0xA1
// with all the bits for an 0xa1 so the 16 bit value is 0x4489.
// This sync is used to mark the header and data fields
// We want to see enough zeros to ensure we don't get a false
// match at the boundaries where data is overwritten
#if 0
if (cyl == 0 && head == 1 && tot_raw_bit_cntr > 128070 && tot_raw_bit_cntr < 128110)
printf("%d %x\n", tot_raw_bit_cntr, raw_word);
if ((raw_word & 0xffff) == 0x4489) {
printf("cyl %d head %d sector index %d zero %d byte %d %x tot raw %d\n",
cyl, head, sector_index, zero_count, byte_cntr, raw_word, tot_raw_bit_cntr);
}
if ((raw_word & 0xffff) == 0x4489) {
//printf("mark at %d zero %d\n", tot_raw_bit_cntr, zero_count);
}
#endif
if (((raw_word & 0xffff) == 0x4489)
&& zero_count >= MARK_NUM_ZEROS) {
#if 0
static int last_tot_raw_bit_cntr = 0;
if (tot_raw_bit_cntr < last_tot_raw_bit_cntr)
last_tot_raw_bit_cntr = 0;
printf("header %d at %d bytes %d\n", state, tot_raw_bit_cntr,
(tot_raw_bit_cntr - last_tot_raw_bit_cntr)/8/2);
last_tot_raw_bit_cntr = tot_raw_bit_cntr;
#endif
if (first_addr_mark_ns == 0) {
first_addr_mark_ns = track_time * CLOCKS_TO_NS;
}
zero_count = 0;
bytes[0] = 0xa1;
byte_cntr = 1;
if (state == MARK_ID) {
state = PROCESS_HEADER;
mfm_mark_header_location(all_raw_bits_count, 0, tot_raw_bit_cntr);
// Figure out the length of data we should look for
bytes_crc_len = mfm_controller_info[drive_params->controller].header_bytes +
drive_params->header_crc.length / 8;
bytes_needed = bytes_crc_len + HEADER_IGNORE_BYTES;
} else if (state == MARK_DATA1) {
state = PROCESS_HEADER2;
// Figure out the length of data we should look for
bytes_crc_len = 2 + mfm_controller_info[drive_params->controller].metadata_bytes +
drive_params->header_crc.length / 8;
bytes_needed = bytes_crc_len + HEADER_IGNORE_BYTES;
} else {
state = PROCESS_DATA;
mfm_mark_data_location(all_raw_bits_count, 0, tot_raw_bit_cntr);
// Figure out the length of data we should look for
bytes_crc_len = mfm_controller_info[drive_params->controller].data_header_bytes +
mfm_controller_info[drive_params->controller].data_trailer_bytes +
drive_params->sector_size +
drive_params->data_crc.length / 8;
bytes_needed = DATA_IGNORE_BYTES + bytes_crc_len;
if (bytes_needed >= sizeof(bytes)) {
msg(MSG_FATAL,"Too many bytes needed %d\n", bytes_needed);
exit(1);
}
}
// Resync decoding to the mark
raw_bit_cntr = 0;
decoded_word = 0;
decoded_bit_cntr = 0;
}
} else {
//printf("Rawb %08x tot %d\n",raw_word, tot_raw_bit_cntr);
int entry_state = state;
// If we have enough bits to decode do so. Stop if state changes
while (raw_bit_cntr >= 4 && entry_state == state) {
// If we have more than 4 only process 4 this time
raw_bit_cntr -= 4;
tmp_raw_word = raw_word >> raw_bit_cntr;
#if 0
if (!valid_code[tmp_raw_word & 0xf]) {
printf("invalid code %x at %d bit %d\n", tmp_raw_word, i,
tot_raw_bit_cntr);
}
#endif
decoded_word =
(decoded_word << 2) | code_bits[tmp_raw_word & 0xf];
decoded_bit_cntr += 2;
// And if we have a bytes worth store it
if (decoded_bit_cntr >= 8) {
// Do we have enough to further process?
if (byte_cntr < bytes_needed) {
bytes[byte_cntr++] = decoded_word;
//Ugly hack to handle if we missed a header. TODO
//when switching to table driven format the table and
//track time will indicate what header should be found
if (byte_cntr == 2) {
if (bytes[1] == 0xfe) {
if (state != PROCESS_HEADER) {
// Flag error for analyze
init_status = SECT_ANALYZE_ERROR;
msg(MSG_INFO,"Found sector header out of order (state %d)\n", state);
bytes_crc_len = mfm_controller_info[drive_params->controller].header_bytes +
drive_params->header_crc.length / 8;
bytes_needed = bytes_crc_len + HEADER_IGNORE_BYTES;
state = PROCESS_HEADER;
}
} else if (bytes[1] == 0xfc) {
if (state != PROCESS_HEADER2) {
// Flag error for analyze
init_status = SECT_ANALYZE_ERROR;
msg(MSG_INFO,"Found tag header out of order (state %d)\n", state);
bytes_crc_len = 22 +
drive_params->header_crc.length / 8;
bytes_needed = bytes_crc_len + HEADER_IGNORE_BYTES;
state = PROCESS_HEADER2;
}
} else if (bytes[1] == 0xfb) {
if (state != PROCESS_DATA) {
msg(MSG_INFO,"Found data header out of order (state %d)\n", state);
// Flag error for analyze
init_status = SECT_ANALYZE_ERROR;
bytes_crc_len = mfm_controller_info[drive_params->controller].data_header_bytes +
mfm_controller_info[drive_params->controller].data_trailer_bytes +
drive_params->sector_size +
drive_params->data_crc.length / 8;
bytes_needed = DATA_IGNORE_BYTES + bytes_crc_len;
state = PROCESS_DATA;
}
}
}
}
if (byte_cntr == bytes_needed) {
mfm_mark_end_data(all_raw_bits_count, drive_params, cyl, head);
all_sector_status |= mfm_process_bytes(drive_params, bytes,
bytes_crc_len, bytes_needed, &state, cyl, head,
§or_index, seek_difference, sector_status_list,
init_status);
}
decoded_bit_cntr = 0;
}
}
}
}
// Finished what we had, any more?
// If we didn't get too many last time sleep so delta reader can run.
// Thread priorities might be better.
if (num_deltas - last_deltas <= 2000) {
usleep(500);
}
last_deltas = num_deltas;
num_deltas = deltas_get_count(i);
}
if (state == PROCESS_DATA && sector_index <= drive_params->num_sectors) {
float begin_time =
((bytes_needed - byte_cntr) * 16.0 *
1e9/mfm_controller_info[drive_params->controller].clk_rate_hz
+ first_addr_mark_ns) / 2 + drive_params->start_time_ns;
msg(MSG_ERR, "Ran out of data on sector index %d, try adding --begin_time %.0f to mfm_read command line\n",
sector_index, round(begin_time / 1000.0) * 1000.0);
}
// Force last partial word to be saved
mfm_save_raw_word(drive_params, all_raw_bits_count, 32-all_raw_bits_count,
raw_word);
// If we didn't find anything to decode return header error
if (all_sector_status == SECT_NO_STATUS) {
all_sector_status = SECT_BAD_HEADER;
}
return all_sector_status;
}