#define PRINT_SPACING 0 #define DUMP_HEADER 0 #define DUMP_DATA 0 // These are the general routines for supporting MFM decoders. // Call mfm_decode_setup once before trying to decode a disk // call mfm_init_sector_status_list before decoding each track // call mfm_update_deltas before decoding each track // call mfm_decode_track to decode the track or // call mfm_wait_all_deltas to just wait for all deltas to be received and // possibly written to a file // call mfm_check_deltas to get number of deltas in the buffer // call mfm_check_header_values to verify header parameters // call mfm_write_metadata to write extra data for each sector // call mfm_write_sector to check sector information and possibly write it to // the extract file // call mfm_decode_done when all tracks have been processed // call mfm_handle_alt_track_ch to add alternate track to list // call mfm_fix_head to adjust head value in header if needed // // TODO: make it use sector number information and checking CRC at data length to write data // for sectors with bad headers. See if resyncing PLL at write boundaries improves performance when // data bits are shifted at write boundaries. // // 02/20/24 DJG Added CONTROLLER_ADAPTEC_4000_18SECTOR_512B // 11/20/23 DJG Added CONTROLLER_NEC_4800 // 11/04/23 DJG Added CONTROLLER_SOUYZ_NEON and printed more accurate bad // sector information when --ignore_seek_errors used. // 10/30/23 DJG Added CONTROLLER_OMTI_20L // 10/18/23 SWE Added David Junior II 210 and 301 // 09/01/23 DJG Added WD_MICROENGINE support // 08/31/23 DJG Added DIMENSION_68000 support // 07/08/23 DJG Added Fujitsu-K-10R format // 03/10/23 DJG Added ES7978 format // 10/01/22 DJG Added CTM9016 format // 03/17/22 DJG Improved error message // 12/19/21 DJG Code cleanups and hunk of commented code for possible future changes // 12/18/21 SWE Added David Junior II format // 10/29/21 DJG Added STRIDE_440 format // 09/19/21 DJG Added TANDY_16B format // 09/03/21 DJG Added SUPERBRAIN format, Fixed message // 08/27/21 DJG Added DSD_5217_512B format // 05/27/21 DJG Added TEKTRONIX_6130 format // 01/07/21 DJG Added RQDX2 format // 11/13/20 DJG Added CONTROLLER_ACORN_A310_PODULE // 10/18/20 DJG Made cylinders & head printed in Mismatch cyl error same order // as other messages. // 10/17/20 DJG Pass correct byte range to ecc correction routine. Error // didn't seem to cause problem. // 10/16/20 DJG Added SHUGART_SA1400 controller // 10/08/20 DJG Added SHUGART_1610 and UNKNOWN2 controllers // 09/21/20 DJG Added controller SM_1810_512B // 08/15/20 DJG If we are ignoring seek errors write sector data if good always // 02/20/20 DJG Improved selection of track to keep for emulator file when // retries done. // Prevent ignore_seek_errors from being set when generating emulator // file since wrong cylinder data will be written. // 11/22/19 DJG Fixed unintended clearing of last_status which caused bad // data to be written to emu file when seek error // 10/25/19 DJG Added PERQ T2 format // 10/05/19 DJG Fixes to detect when CONT_MODEL controller doesn't really // match format // 07/19/19 DJG Variable renamed // 07/07/19 DJG If both bad header and bad data set count as bad header. // 06/19/19 DJG Removed DTC_256B and added SM1040. Improve 3 bit head support. // 02/09/19 DJG Added CONTROLLER_SAGA_FOX // 01/20/18 DJG Make extracted data file always full size. Previously if // tracks at end of disk were unreadable they wouldn't be written. Other // unreadable tracks were zero filled. New names for iSBC 214/215 for ext2emu. // 12/16/18 DJG Added NIXDORF_8870 // 10/12/18 DJG Added IBM_3174 // 09/10/18 DJG Added CONTROLLER_DILOG_DQ604 // 08/26/18 DJG Best sector wasn't always written out. Ignore bad headers at // end of track // 08/05/18 DJG Added IBM_5288 // 07/02/18 DJG Added Convergent AWS SA1000 format // 06/25/18 DJG Fixed calculating emulation file track data size for SA1000 drives // 06/17/18 DJG Added Tandy 8 Meg SA1004, fourth DTC variant, and ROHM_PBX.Changes to support // adapting to header type found. // 05/06/18 DJG Added format Xerox 8010 and Altos. Fixes for error // recovery where sectors that were read correctly weren't always used // to replace previous bad read and don't declare seek error on header // with error. // 03/31/18 DJG Split DTC into three versions for ext2emu // 03/07/18 DJG Added CONTROLLER_DILOG_DQ614 // 02/04/18 DJG Fixed error message and freed alternate track memory. // 12/17/17 DJG Aded EDAX_PV9900 // 09/30/17 DJG Added Wang 2275 // 08/11/17 DJG Added support for Convergent AWS // 05/19/17 DJG Previous fix prevented writing sectors with data error. Back // to writing the best data we have for sector in extracted data file. // 04/21/17 DJG Added better tracking of information during read. When index // fell in a sector the sector_status_list wasn't properly updated. // 03/08/17 DJG Fixed Intel iSBC 215 // 02/12/17 DJG Added support for Data General MV/2000 // 02/09/17 DJG Added support for AT&T 3B2 // 02/07/17 DJG Added support for Altos 586 // 01/17/17 DJG Add flag to ignore seek errors and report missing cylinders // 12/11/16 DJG Added logic for detecting sectors with zero contect that // make polynomial detection ambiguous. // 12/04/16 DJG Added Intel iSBC_215 format. Fixed handling of bad blocks // for Adaptec format. // 11/20/16 DJG Added logic to determine how much emulator track size needs // to be increased by to make data fit. Added logic to pass data size // information. // 11/14/16 DJG Added Telenex Autoscope, Vector4, and Xebec_S1420 formats // 11/02/16 DJG Add ability to write tag/metadata if it exists // 10/28/16 DJG Improved support for LBA format disks // 10/22/16 DJG Added unknown format found on ST-212 disk // 10/16/16 DJG Renamed OLIVETTI to DTC. Added MOTOROLA_VME10 and SOLOSYSTEMS // 05/21/16 DJG Improvements in alternate track handling and fix marking // of header and data location for fixing emulator file with ECC corrections // 04/23/16 DJG Added EC1841 support // 01/24/16 DJG Add MVME320 controller support // 01/13/16 DJG Changes for ext2emu related changes on how drive formats will // be handled. // 01/06/16 DJG Add code to fix extracted data file when alternate tracks are // used. Only a few format know how to determine alternate track // 12/31/15 DJG Changes for ext2emu // 12/24/15 DJG Code cleanup // 11/13/15 DJG Added Seagate ST11M support // 11/07/15 DJG Added Symbolics 3640 support // 11/01/15 DJG Renamed formats and other comment changes // 05/17/15 DJG Added formats MIGHTYFRAME, ADAPTEC, NEWBURYDATA, SYMBOLICS, and // partially implement format RUSSIAN. Also code cleanup amd check for // trumcation of emulation file. // 01/04/15 DJG Added Corvus_H and NorthStar Advantage decoders. // 11/09/14 DJG Changes for note option // 10/06/14 DJG Added new CONTROLLER_MACBOTTOM format // 09/10/14 DJG Added new CONTROLLER_OLIVETTI format // 09/06/14 DJG Made sector number printed for sectors with errors // use drive sector numbering (drives may use 0 or 1 for first sector. // Separated dumping read data from error messages // // MFM disk utilities is free software: you can redistribute it and/or modify // // Copyright 2022 David Gesswein. // This file is part of MFM disk utilities. // // 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 "deltas_read.h" #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) // Hold for sector status and cylinder and head it was for. We save the // data so when the cylinder and head changes we can print the final status. // The same track may be reread. static SECTOR_STATUS last_sector_list[MAX_SECTORS]; static int last_cyl; static int last_head; static int cyl_found[4096]; // Last LBA address processed for detecting bad sectors static int last_lba_addr; static void update_emu_track_sector(DRIVE_PARAMS *drive_params, SECTOR_STATUS *sector_status, int sect_rel0, uint8_t bytes[], int num_bytes, int update); void update_emu_track_words(DRIVE_PARAMS * drive_params, SECTOR_STATUS sector_status_list[], int write_track, int new_track, int cyl, int head); static void print_missing_cyl(DRIVE_PARAMS *drive_params); static void dump_bad(void); // This is used with --ignore_seek_errors to show what sectors were good/bad // for the entire disk. It also makes sure a good sector won't be overwritten with a // bad sector. A disk I was reading you saw multiple cylinders when // reading a track so track at a time error information wasn't accurate. // sector_good 0 = not good, 1 = good, ECC_OFFSET + bits corrected = good // but ECC corrected #define ECC_OFFSET 5 static char sector_good[MAX_CYL][MAX_HEAD][MAX_SECTORS]; // CRC of sector data. This detects if good or ECC corrected data differs between // reads static uint64_t sector_crc[MAX_CYL][MAX_HEAD][MAX_SECTORS]; static int num_cyl, num_head, num_sectors; // These are various PLL constants I was trying. For efficiency the // filter routine is put in the decoders so it can inline and has the // "best" PLL constants I found. #if 0 int filter_type; static inline float filter(float v, float *delay) { float in, out; in = v + *delay; #if 1 out = in * 0.034446428576716f + *delay * -0.034124999994713f; #else switch (filter_type) { case 0: out = in * 0.034446428576716f + *delay * -0.034124999994713f; break; case 1: out = in * 0.114928571449721f + *delay * -0.113642857121708f; // 1, 2e-5 break; case 2: out = in * 0.034928571449721f + *delay * -0.033642857121708f; // .3, 2e-5 break; case 3: out = in * 0.071142857227454f + *delay * -0.065999999915403f; // .3, 1e-5 break; case 4: o1t = in * 0.023114285722745f + *delay * -0.022599999991540f; // .3, 10e-5, /10 break; case 5: out = in * 0.068828571437031f + *delay * -0.068314285705826f; // .3, 10e-5, /10 break; } #endif *delay = in; return out; } #endif // 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; } // Compare two integers for qsort static int cmpint(const void *i1, const void *i2) { if (*(int *) i1 < *(int *)i2) { return -1; } else if (*(int *) i1 == *(int *) i2) { return 0; } else { return 1; } } // Print any errors and ECC correction from the sector_status_list. // // drive_params: Parameters for drive. Stats updated. // sector_status_list: List of sector statuses // cyl, head: Track status if for static void print_sector_list_status(DRIVE_PARAMS *drive_params, SECTOR_STATUS *sector_status_list, int cyl, int head) { int cntr; int ecc_corrections = 0; int hard_errors = 0; int data_errors = 0; int lba_addrs[MAX_SECTORS]; int first_sector_number = drive_params->first_sector_number; int num_sectors = drive_params->num_sectors; // Find if anything needs printing for (cntr = 0; cntr < num_sectors; cntr++) { if (!(sector_status_list[cntr].status & SECT_SPARE_BAD)) { if (UNRECOVERED_ERROR(sector_status_list[cntr].status)) { hard_errors = 1; } if (sector_status_list[cntr].status & SECT_BAD_DATA) { data_errors = 1; } if (sector_status_list[cntr].status & SECT_ECC_RECOVERED) { ecc_corrections = 1; } } } if (mfm_controller_info[drive_params->controller].analyze_type == CINFO_LBA) { int lba_missing = 0; if (data_errors) { msg(MSG_ERR_SUMMARY, "Bad sectors on cylinder %d head %d LBA:",cyl,head); } // Make a list of all LBA addresses found. If SECT_BAD_HEADER set then // LBA address is not valid. We then sort the list and check that the // numbers are consecutive. for (cntr = 0; cntr < num_sectors; cntr++) { if (sector_status_list[cntr].status & (SECT_BAD_HEADER | SECT_BAD_LBA_NUMBER)) { lba_missing++; } else { lba_addrs[cntr - lba_missing] = sector_status_list[cntr].lba_addr; if (sector_status_list[cntr].status & SECT_BAD_DATA && !(sector_status_list[cntr].status & SECT_SPARE_BAD)) { msg(MSG_ERR_SUMMARY, " %d", sector_status_list[cntr].lba_addr); } } } if (data_errors) { msg(MSG_ERR_SUMMARY,"\n"); } if (ecc_corrections) { msg(MSG_ERR_SUMMARY, "ECC Corrections on cylinder %d head %d LBA:",cyl,head); for (cntr = 0; cntr < num_sectors; cntr++) { if (sector_status_list[cntr].status & SECT_ECC_RECOVERED && !(sector_status_list[cntr].status & SECT_SPARE_BAD)) { msg(MSG_ERR_SUMMARY, " %d(", sector_status_list[cntr].lba_addr); if (sector_status_list[cntr].ecc_span_corrected_header) { msg(MSG_ERR_SUMMARY, "%dH%s", sector_status_list[cntr].ecc_span_corrected_header, sector_status_list[cntr].ecc_span_corrected_data != 0 ? "," : ""); } if (sector_status_list[cntr].ecc_span_corrected_data) { msg(MSG_ERR_SUMMARY, "%d", sector_status_list[cntr].ecc_span_corrected_data); } msg(MSG_ERR_SUMMARY,")"); } } msg(MSG_ERR_SUMMARY, "\n"); } qsort(lba_addrs, num_sectors - lba_missing, sizeof(lba_addrs[0]), cmpint); for (cntr = 0; cntr < num_sectors - lba_missing; cntr++) { if (last_lba_addr + 1 != lba_addrs[cntr]) { msg(MSG_ERR, "Missing LBA address %d", last_lba_addr + 1); if (last_lba_addr + 2 != lba_addrs[cntr]) { msg(MSG_ERR, " to %d", lba_addrs[cntr] - 1); } msg(MSG_ERR, "\n"); } last_lba_addr = lba_addrs[cntr]; } } else { // Print CHS errors if (hard_errors) { int last_cyl = cyl; msg(MSG_ERR_SUMMARY, "Bad sectors on cylinder %d", cyl); // Sector status list is indexed by header sector number relative to lowest // sector number for (cntr = 0; cntr < num_sectors; cntr++) { if (!(sector_status_list[cntr].status & SECT_BAD_HEADER)) { if (sector_status_list[cntr].cyl != cyl && sector_status_list[cntr].cyl != last_cyl) { msg(MSG_ERR_SUMMARY, "/%d",sector_status_list[cntr].cyl); last_cyl = sector_status_list[cntr].cyl; } } } msg(MSG_ERR_SUMMARY, " head %d:",head); for (cntr = 0; cntr < num_sectors; cntr++) { if (!(sector_status_list[cntr].status & SECT_SPARE_BAD)) { if (sector_status_list[cntr].status & SECT_BAD_HEADER) { msg(MSG_ERR_SUMMARY, " %dH", cntr + first_sector_number); } if (sector_status_list[cntr].status & SECT_BAD_DATA) { msg(MSG_ERR_SUMMARY, " %d", cntr + first_sector_number); } } } msg(MSG_ERR_SUMMARY,"\n"); } if (ecc_corrections) { msg(MSG_ERR_SUMMARY, "ECC Corrections on cylinder %d head %d:",cyl,head); for (cntr = 0; cntr < num_sectors; cntr++) { if (sector_status_list[cntr].status & SECT_ECC_RECOVERED && !(sector_status_list[cntr].status & SECT_SPARE_BAD)) { msg(MSG_ERR_SUMMARY, " %d(", cntr + first_sector_number); if (sector_status_list[cntr].ecc_span_corrected_header) { msg(MSG_ERR_SUMMARY, "%dH%s", sector_status_list[cntr].ecc_span_corrected_header, sector_status_list[cntr].ecc_span_corrected_data != 0 ? "," : ""); } if (sector_status_list[cntr].ecc_span_corrected_data) { msg(MSG_ERR_SUMMARY, "%d", sector_status_list[cntr].ecc_span_corrected_data); } msg(MSG_ERR_SUMMARY,")"); } } msg(MSG_ERR_SUMMARY, "\n"); } } } // Update statistics for the read so we can print summary at the end. // Since we may be called multiple times for the same track if errors are // retried only update the statistics and print errors when the track // changes. // // drive_params: Parameters for drive. Stats updated. // cyl, head: cylinder and head from header // sector_status_list: status of sectors static void update_stats(DRIVE_PARAMS *drive_params, int cyl, int head, SECTOR_STATUS sector_status_list[]) { STATS *stats = &drive_params->stats; int i; int write_cyl = last_cyl; // If track changed and list has been set (last_cyl != -1) then process if (last_cyl != -1 && (cyl != last_cyl || head != last_head)) { // This was start of trying to enable creating emu file with --ignore_seek_errors // Solved issue by using microstepper so this never finished // Also needed change below // if (sector_status_list[i].status & SECT_ECC_RECOVERED) { // best_weight += 10; // and to allow ignore seek with emu generation #if 0 if (drive_params->ignore_seek_errors) { int cyl_list[drive_params->num_sectors]; int cmpint (const void * a, const void * b) { return ( *(int*)a - *(int*)b ); } int ndx = 0; for (i = 0; i < drive_params->num_sectors; i++) { if (!(last_sector_list[i].status & SECT_BAD_HEADER)) { cyl_list[ndx++] = last_sector_list[i].cyl; } } qsort(cyl_list, ndx, sizeof(cyl_list[0]), cmpint); int count = 1; int last_entry = cyl_list[0]; for (i = 1; i < ndx; i++) { if (cyl_list[i] == last_entry) { count++; // If over half same cylinder use it. } if (cyl_list[i] != last_entry || i == ndx - 1) { if (count > drive_params->num_sectors / 2 + 1 && last_entry != last_cyl) { printf("Writing read cyl %d to actual cyl %d head %d, count %d\n", last_cyl, last_entry, last_head, count); write_cyl = last_entry; } count = 1; last_entry = cyl_list[i]; } } } #endif update_emu_track_words(drive_params, sector_status_list, 1, 1, write_cyl, last_head); for (i = 0; i < drive_params->num_sectors; i++) { if (last_sector_list[i].status & SECT_ECC_RECOVERED && !(last_sector_list[i].status & SECT_SPARE_BAD)) { stats->num_ecc_recovered++; } if (last_sector_list[i].status & SECT_SPARE_BAD) { stats->num_spare_bad++; } else if (last_sector_list[i].status & SECT_BAD_HEADER) { stats->num_bad_header++; } else if (last_sector_list[i].status & SECT_BAD_DATA) { stats->num_bad_data++; } else { stats->num_good_sectors++; } } print_sector_list_status(drive_params, last_sector_list, last_cyl, last_head); } else { update_emu_track_words(drive_params, sector_status_list, 0, last_cyl == -1, cyl, head); } // Save the sector information so we can use it when track changes if (sector_status_list != NULL) { memcpy(last_sector_list, sector_status_list, sizeof(last_sector_list)); } last_cyl = cyl; last_head = head; } // Sets the sector status list to bad header. This is the default error. // If we don't find the header when trying to read so can't update the list // we will then have bad header status // // sector_status_list: status of sectors // num_sectors: number of sectors in list void mfm_init_sector_status_list(SECTOR_STATUS sector_status_list[], int num_sectors) { int i; memset(sector_status_list, 0, sizeof(*sector_status_list) * num_sectors); for (i = 0; i < num_sectors; i++) { sector_status_list[i].status = SECT_BAD_HEADER | SECT_NOT_WRITTEN; } } // Decode a track's worth of deltas to clock and data. Used for // generating emulation file from unknown format disk data // // drive_params: Drive parameters // cyl,head: Physical Track data from // deltas: MFM delta time transition data to analyze // 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 static SECTOR_DECODE_STATUS mfm_decode_track_deltas(DRIVE_PARAMS *drive_params, int cyl, int head, uint16_t deltas[], int *seek_difference, SECTOR_STATUS sector_status_list[]) { // 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; // loop counter int i; // These are variables for the PLL filter. avg_bit_sep_time is the // "VCO" frequency float avg_bit_sep_time = 20; // 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; // PLL filter state. Static works better since next track bit timing // similar to previous though a bad track can put it off enough that // the next track has errors. Retry should fix. TODO: Look at static float filter_state = 0; // Time in track for debugging int track_time = 0; // Number of deltas available so far to process int num_deltas; // Count all the raw bits for emulation file int all_raw_bits_count = 0; num_deltas = deltas_get_count(0); raw_word = 0; i = 1; while (num_deltas >= 0) { // We process what we have then check for more. for (; i < num_deltas; i++) { track_time += deltas[i]; // This is simulating a PLL/VCO clock sampling the data. clock_time += deltas[i]; // 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 avg_bit_sep_time = 20.0 + filter(clock_time, &filter_state); if (drive_params->emulation_filename != NULL && 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; } raw_bit_cntr += int_bit_pos; } num_deltas = deltas_get_count(i); } return SECT_NO_STATUS; } // This routine calls the proper decoder for the drive format // See called routines for parameters and return value SECTOR_DECODE_STATUS mfm_decode_track(DRIVE_PARAMS * drive_params, int cyl, int head, uint16_t deltas[], int *seek_difference, SECTOR_STATUS sector_status_list[]) { int rc; int i; for (i = 0; i < drive_params->num_sectors; i++) { sector_status_list[i].last_status = SECT_BAD_HEADER; } // Change in mfm_process_bytes if this if is changed if (drive_params->controller == CONTROLLER_WD_1006 || drive_params->controller == CONTROLLER_RQDX2 || drive_params->controller == CONTROLLER_SOUYZ_NEON || drive_params->controller == CONTROLLER_NEC_4800 || drive_params->controller == CONTROLLER_ES7978 || drive_params->controller == CONTROLLER_WD_MICROENGINE || drive_params->controller == CONTROLLER_ISBC_214_128B || drive_params->controller == CONTROLLER_ISBC_214_256B || drive_params->controller == CONTROLLER_ISBC_214_512B || drive_params->controller == CONTROLLER_ISBC_214_1024B || drive_params->controller == CONTROLLER_TEKTRONIX_6130 || drive_params->controller == CONTROLLER_NIXDORF_8870 || drive_params->controller == CONTROLLER_TANDY_8MEG || drive_params->controller == CONTROLLER_WD_3B1 || drive_params->controller == CONTROLLER_TANDY_16B || drive_params->controller == CONTROLLER_MOTOROLA_VME10 || drive_params->controller == CONTROLLER_SM_1810_512B || drive_params->controller == CONTROLLER_DSD_5217_512B || drive_params->controller == CONTROLLER_OMTI_5510 || drive_params->controller == CONTROLLER_MORROW_MD11 || drive_params->controller == CONTROLLER_UNKNOWN1 || drive_params->controller == CONTROLLER_UNKNOWN2 || drive_params->controller == CONTROLLER_SHUGART_SA1400 || drive_params->controller == CONTROLLER_DEC_RQDX3 || drive_params->controller == CONTROLLER_DJ_II || drive_params->controller == CONTROLLER_DJ_II_210 || drive_params->controller == CONTROLLER_DJ_II_301 || drive_params->controller == CONTROLLER_MYARC_HFDC || drive_params->controller == CONTROLLER_SHUGART_1610 || drive_params->controller == CONTROLLER_MVME320 || drive_params->controller == CONTROLLER_DTC || drive_params->controller == CONTROLLER_DTC_520_512B || drive_params->controller == CONTROLLER_DTC_520_256B || drive_params->controller == CONTROLLER_MACBOTTOM || drive_params->controller == CONTROLLER_FUJITSU_K_10R || drive_params->controller == CONTROLLER_CTM9016 || drive_params->controller == CONTROLLER_ACORN_A310_PODULE || drive_params->controller == CONTROLLER_MIGHTYFRAME || drive_params->controller == CONTROLLER_DG_MV2000 || drive_params->controller == CONTROLLER_ADAPTEC || drive_params->controller == CONTROLLER_ADAPTEC_4000_18SECTOR_512B || drive_params->controller == CONTROLLER_NEWBURYDATA || drive_params->controller == CONTROLLER_ELEKTRONIKA_85 || drive_params->controller == CONTROLLER_SEAGATE_ST11M || drive_params->controller == CONTROLLER_ALTOS_586 || drive_params->controller == CONTROLLER_ATT_3B2 || drive_params->controller == CONTROLLER_WANG_2275 || drive_params->controller == CONTROLLER_WANG_2275_B || drive_params->controller == CONTROLLER_CALLAN || drive_params->controller == CONTROLLER_IBM_5288 || drive_params->controller == CONTROLLER_IBM_3174 || drive_params->controller == CONTROLLER_EDAX_PV9900 || drive_params->controller == CONTROLLER_ALTOS || drive_params->controller == CONTROLLER_CONVERGENT_AWS || drive_params->controller == CONTROLLER_CONVERGENT_AWS_SA1000 || drive_params->controller == CONTROLLER_ISBC_215_128B || drive_params->controller == CONTROLLER_ISBC_215_256B || drive_params->controller == CONTROLLER_ISBC_215_512B || drive_params->controller == CONTROLLER_ISBC_215_1024B || drive_params->controller == CONTROLLER_DILOG_DQ614 || drive_params->controller == CONTROLLER_DILOG_DQ604 || drive_params->controller == CONTROLLER_DIMENSION_68000 || drive_params->controller == CONTROLLER_ROHM_PBX || drive_params->controller == CONTROLLER_SYMBOLICS_3620 || drive_params->controller == CONTROLLER_OMTI_20L || drive_params->controller == CONTROLLER_SM1040 || drive_params->controller == CONTROLLER_SYMBOLICS_3640) { rc = wd_decode_track(drive_params, cyl, head, deltas, seek_difference, sector_status_list); } else if (drive_params->controller == CONTROLLER_XEROX_6085 || drive_params->controller == CONTROLLER_XEROX_8010 || drive_params->controller == CONTROLLER_TELENEX_AUTOSCOPE) { rc = tagged_decode_track(drive_params, cyl, head, deltas, seek_difference, sector_status_list); } else if (drive_params->controller == CONTROLLER_XEBEC_104786 || drive_params->controller == CONTROLLER_XEBEC_104527_256B || drive_params->controller == CONTROLLER_XEBEC_104527_512B || drive_params->controller == CONTROLLER_XEBEC_S1420 || drive_params->controller == CONTROLLER_EC1841 || drive_params->controller == CONTROLLER_SOLOSYSTEMS) { rc = xebec_decode_track(drive_params, cyl, head, deltas, seek_difference, sector_status_list); } else if (drive_params->controller == CONTROLLER_CORVUS_H || drive_params->controller == CONTROLLER_CROMEMCO || drive_params->controller == CONTROLLER_VECTOR4_ST506 || drive_params->controller == CONTROLLER_VECTOR4 || drive_params->controller == CONTROLLER_STRIDE_440 || drive_params->controller == CONTROLLER_SAGA_FOX) { rc = corvus_decode_track(drive_params, cyl, head, deltas, seek_difference, sector_status_list); } else if (drive_params->controller == CONTROLLER_NORTHSTAR_ADVANTAGE || drive_params->controller == CONTROLLER_ND100_3041 || drive_params->controller == CONTROLLER_SUPERBRAIN) { rc = northstar_decode_track(drive_params, cyl, head, deltas, seek_difference, sector_status_list); } else if (drive_params->controller == CONTROLLER_PERQ_T2) { rc = perq_decode_track(drive_params, cyl, head, deltas, seek_difference, sector_status_list); } else { rc = mfm_decode_track_deltas(drive_params, cyl, head, deltas, seek_difference, sector_status_list); } update_stats(drive_params, cyl, head, sector_status_list); return rc; } // This makes the floating point faster with minor simplifications to the floating point processing // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344k/Chdiihcd.html #ifdef __arm__ static void enable_runfast() { static const unsigned int x = 0x04086060; static const unsigned int y = 0x03000000; int r; __asm volatile ("fmrx %0, fpscr \n\t" //r0 = FPSCR "and %0, %0, %1 \n\t" //r0 = r0 & 0x04086060 "orr %0, %0, %2 \n\t" //r0 = r0 | 0x03000000 "fmxr fpscr, %0 \n\t" //FPSCR = r0 :"=r" (r) :"r"(x), "r"(y) ); } #endif // Setup to start decoding. Pass null pointer for filename if decoded data // shouldn't be written to a file. // // drive_params: Parameters for drive // write_files: 1 if output files shouldn't be written void mfm_decode_setup(DRIVE_PARAMS *drive_params, int write_files) { STATS *stats = &drive_params->stats; // set to value indicating not yet set. last_head = -1; last_cyl = -1; last_lba_addr = -1; memset(cyl_found, 0, sizeof(cyl_found)); num_cyl = drive_params->num_cyl; num_head = drive_params->num_head; num_sectors = drive_params->num_sectors; memset(sector_good, 0, sizeof(sector_good)); memset(sector_crc, 0, sizeof(sector_crc)); if (drive_params->emulation_output && drive_params->ignore_seek_errors) { msg(MSG_ERR, "Ignore seek errors is invalid if generating emulation file. Option turned off\n"); drive_params->ignore_seek_errors = 0; } if (write_files && drive_params->emulation_filename != NULL && drive_params->emulation_output == 1) { // Assume 3600 RPM, 60 RPS. Make round number of words // Set if not set on command line if (drive_params->emu_track_data_bytes == 0) { drive_params->emu_track_data_bytes = ceil(1/ emu_rps(mfm_controller_info[drive_params->controller].clk_rate_hz) * mfm_controller_info[drive_params->controller].clk_rate_hz / 8 / 4)*4; } drive_params->emu_fd = emu_file_write_header(drive_params->emulation_filename, drive_params->num_cyl, drive_params->num_head, drive_params->cmdline, drive_params->note, mfm_controller_info[drive_params->controller].clk_rate_hz, drive_params->start_time_ns, drive_params->emu_track_data_bytes); } drive_params->ext_metadata_fd = -1; drive_params->ext_fd = -1; if (write_files && drive_params->extract_filename != NULL) { drive_params->ext_fd = open(drive_params->extract_filename, O_RDWR | O_CREAT | O_TRUNC, 0664); if (drive_params->ext_fd < 0) { perror("Unable to create output extracted data file"); exit(1); } if (mfm_controller_info[drive_params->controller].metadata_bytes != 0) { char extention[] = ".metadata"; char fn[strlen(drive_params->extract_filename) + strlen(extention) + 1]; strcpy(fn, drive_params->extract_filename); strcat(fn, extention); drive_params->ext_metadata_fd = open(fn, O_RDWR | O_CREAT | O_TRUNC, 0664); if (drive_params->ext_metadata_fd < 0) { perror("Unable to create metadata output file"); exit(1); } } } memset(stats, 0, sizeof(*stats)); stats->min_sect = INT_MAX; stats->min_head = INT_MAX; stats->min_cyl = INT_MAX; #if __arm__ // Speed up floating point on beaglebone enable_runfast(); #endif } static void fix_ext_alt_tracks(DRIVE_PARAMS *drive_params) { ALT_INFO *alt_info = drive_params->alt_llist; void *ptr_hold; if (alt_info != NULL) { msg(MSG_INFO,"Applying alternate sector information\n"); } while (alt_info != NULL) { uint8_t bad_data[alt_info->length]; uint8_t good_data[alt_info->length]; msg(MSG_DEBUG,"Swapping start bad offset %d good offset %d length %d\n", alt_info->bad_offset, alt_info->good_offset, alt_info->length); if (pread(drive_params->ext_fd, bad_data, sizeof(bad_data), alt_info->bad_offset) != sizeof(bad_data)) { msg(MSG_FATAL, "bad alt pread failed\n"); exit(1); } if (pread(drive_params->ext_fd, good_data, sizeof(good_data), alt_info->good_offset) != sizeof(good_data)) { msg(MSG_FATAL, "good alt pread failed\n"); exit(1); } if (pwrite(drive_params->ext_fd, bad_data, sizeof(bad_data), alt_info->good_offset) != sizeof(bad_data)) { msg(MSG_FATAL, "good alt pwrite failed\n"); exit(1); } if (pwrite(drive_params->ext_fd, good_data, sizeof(good_data), alt_info->bad_offset) != sizeof(good_data)) { msg(MSG_FATAL, "bad alt pwrite failed\n"); exit(1); } ptr_hold = alt_info; alt_info = alt_info->next; free(ptr_hold); } } // This cleans up at end of processing data and prints summary information. // Also checks that data agrees with the drive parameters. // // drive_params: Parameters for drive void mfm_decode_done(DRIVE_PARAMS * drive_params) { STATS *stats = &drive_params->stats; // Process last track sector list update_stats(drive_params, -1, -1, NULL); if (drive_params->ext_fd >= 0) { ftruncate(drive_params->ext_fd, drive_params->num_cyl * drive_params->num_head * drive_params->num_sectors * drive_params->sector_size); fix_ext_alt_tracks(drive_params); close(drive_params->ext_fd); } if (stats->min_cyl != INT_MAX) { msg(MSG_STATS, "Found cyl %d to %d, head %d to %d, sector %d to %d\n", stats->min_cyl, stats->max_cyl, stats->min_head, stats->max_head, stats->min_sect, stats->max_sect); if (stats->max_cyl - stats->min_cyl + 1 != drive_params->num_cyl) { msg(MSG_ERR_SUMMARY, "Expected cyls %d doesn't match cyls found %d\n", drive_params->num_cyl, stats->max_cyl - stats->min_cyl + 1); } if (stats->max_head - stats->min_head + 1 != drive_params->num_head) { msg(MSG_ERR_SUMMARY, "Expected heads %d doesn't match heads found %d\n", drive_params->num_head, stats->max_head - stats->min_head + 1); } if (stats->max_sect - stats->min_sect + 1 != drive_params->num_sectors) { msg(MSG_ERR_SUMMARY, "Expected sectors %d doesn't match sectors found %d\n", drive_params->num_sectors, stats->max_sect - stats->min_sect + 1); } if (stats->min_sect != drive_params->first_sector_number) { msg(MSG_ERR_SUMMARY, "Expected first sector number %d doesn't match first sector found %d\n", drive_params->first_sector_number, stats->min_sect); } if (drive_params->ignore_seek_errors) { print_missing_cyl(drive_params); } msg(MSG_STATS, "Expected %d sectors got %d good sectors, %d bad header, %d bad data\n", drive_params->num_cyl * drive_params->num_head * drive_params->num_sectors, stats->num_good_sectors, stats->num_bad_header, stats->num_bad_data); msg(MSG_STATS, "%d sectors marked bad or spare\n", stats->num_spare_bad); msg(MSG_STATS, "%d sectors corrected with ECC. Max bits in burst corrected %d\n", stats->num_ecc_recovered, stats->max_ecc_span); if (stats->max_track_words * 4 > drive_params->emu_track_data_bytes && stats->emu_data_truncated) { msg(MSG_ERR, "*** To create valid emulator file rerun with --track_words %d\n", stats->max_track_words + 2); msg(MSG_ERR, "if decoding emulator file with mfm_util shows errors\n"); } } emu_file_close(drive_params->emu_fd, drive_params->emulation_output); if (drive_params->ignore_seek_errors) { dump_bad(); } } // This checks that the sector header values are reasonable and match the // track and head we thought we were reading. If the cylinder doesn't match // we return the difference between the actual and expected cylinder so // the caller can try seeking again if needed. // // It also updates the sector_status_list with the information gotten from // the sector header. // // exp_cyl, exp_head: Track data was from // sector_index: Counter for sectors starting at 0. With errors may not match // sector number // sector_size: Sector size from header // seek_difference: Return of cylinder difference // sector_status: Status of this sector // drive_params: Parameters for drive void mfm_check_header_values(int exp_cyl, int exp_head, int *sector_index, int sector_size, int *seek_difference, SECTOR_STATUS *sector_status, DRIVE_PARAMS *drive_params, SECTOR_STATUS sector_status_list[]) { if (sector_status->ignore) { return; } if (drive_params->ignore_header_mismatch) { sector_status->logical_sector = *sector_index; (*sector_index)++; return; } if (sector_status->cyl >= 0 && sector_status->cyl < ARRAYSIZE(cyl_found)) { cyl_found[sector_status->cyl] = 1; } // If ignore seek error we will still declare an error if greater than 250 // to make analyze work better. if (!sector_status->is_lba && (sector_status->head != exp_head || (sector_status->cyl != exp_cyl && !drive_params->ignore_seek_errors) || abs(sector_status->cyl - exp_cyl) > 250)) { // Possibly a seek error, mark it if header isn't declared bad. If // drive uses bad CRC with initial value 0 non header data can pass // CRC hopefully will have BAD_HEADER set. // TODO: Should we not do any of these checks with bad header? if (sector_status->cyl != exp_cyl && ! (sector_status->status & SECT_BAD_HEADER)) { msg(MSG_ERR,"Mismatch cyl %d,%d head %d,%d index %d\n", exp_cyl, sector_status->cyl, exp_head, sector_status->head, *sector_index); sector_status->status |= ANALYZE_WRONG_FORMAT; sector_status->status |= SECT_WRONG_CYL; if (seek_difference != NULL) { *seek_difference = exp_cyl - sector_status->cyl; } } sector_status->status |= SECT_BAD_HEADER; } // If we have expected sector ordering information check the sector numbers // TODO: make this handle more complex sector numbering where they vary // between tracks if (drive_params->sector_numbers != NULL) { int orig_sector_index = *sector_index; for (; *sector_index < drive_params->num_sectors; (*sector_index)++) { if (sector_status->sector == drive_params->sector_numbers[*sector_index]) { break; } } if (*sector_index > orig_sector_index+1) { msg(MSG_ERR, "Cyl %d head %d Missed sector between %d(%d) and %d(%d)\n", sector_status->cyl, sector_status->head, drive_params->sector_numbers[orig_sector_index], orig_sector_index, drive_params->sector_numbers[*sector_index], *sector_index); } if (*sector_index >= drive_params->num_sectors) { msg(MSG_ERR_SERIOUS,"Cyl %d head %d Sector %d not found in expected sector list after %d(%d)\n", sector_status->cyl, sector_status->head, sector_status->sector, drive_params->sector_numbers[orig_sector_index], orig_sector_index); sector_status->status |= SECT_BAD_HEADER; sector_status->status |= ANALYZE_WRONG_FORMAT; *sector_index = orig_sector_index; } sector_status->logical_sector = *sector_index; } else { sector_status->logical_sector = *sector_index; (*sector_index)++; } if (sector_size != drive_params->sector_size) { msg(MSG_ERR,"Expected sector size %d header says %d cyl %d head %d sector %d\n", drive_params->sector_size, sector_size, sector_status->cyl, sector_status->head, sector_status->sector); sector_status->status |= ANALYZE_WRONG_FORMAT; } // Code copies from mfm_write_sector // so sectors marked bad will be properly handled. Something cleaner // would be good. int sect_rel0 = sector_status->sector - drive_params->first_sector_number; if (sect_rel0 >= drive_params->num_sectors || sect_rel0 < 0) { msg(MSG_ERR_SERIOUS, "Logical sector %d out of range 0-%d sector %d cyl %d head %d phys sector %d\n", sect_rel0, drive_params->num_sectors-1, sector_status->sector, sector_status->cyl,sector_status->head, *sector_index); sector_status->status |= SECT_BAD_HEADER; sector_status->status |= ANALYZE_WRONG_FORMAT; } else if (sector_status->head > drive_params->num_head) { msg(MSG_ERR_SERIOUS,"Head out of range %d max %d cyl %d sector %d\n", sector_status->head, drive_params->num_head, sector_status->cyl, sector_status->sector); sector_status->status |= SECT_BAD_HEADER; sector_status->status |= ANALYZE_WRONG_FORMAT; } if (sect_rel0 < MAX_SECTORS) { int sector_to_update = sect_rel0; // If < 0 mark bad in last sector for analyze. if (sector_to_update < 0) { sector_to_update = MAX_SECTORS-1; } // If we haven't written the sector update the sector status info. If // we have written we don't need to update here. Updating could change // sector data indicating good sector written to bad if (sector_status_list[sector_to_update].status & SECT_NOT_WRITTEN) { int last_status = sector_status_list[sector_to_update].last_status; sector_status_list[sector_to_update] = *sector_status; // Keep existing last status sector_status_list[sector_to_update].last_status = last_status; // Set to bad data as default. If data found good this will // be changed. Keep not written flag. sector_status_list[sector_to_update].status |= SECT_BAD_DATA | SECT_NOT_WRITTEN; } } } // Dump overall bad sector information for --ignore_seek_error static void dump_bad(void) { int bad_sector_count = 0; int ecc_sector_count = 0; msg(MSG_INFO, "BAD data with E is data was ECC corrected, likely no error\n"); for (int cyl = 0; cyl < num_cyl; cyl++) { for (int head = 0; head < num_head; head++) { int printed = 0; for (int sect = 0; sect < num_sectors; sect++) { if (sector_good[cyl][head][sect] != 1) { if (!printed) { msg(MSG_INFO, "BAD cyl %d head %d: ", cyl, head) ; printed = 1; } if (sector_good[cyl][head][sect] == 2) { msg(MSG_INFO, "%dE(%d) ", sect, sector_good[cyl][head][sect] - ECC_OFFSET); ecc_sector_count++; } else { msg(MSG_INFO, "%d ", sect); bad_sector_count++; } } } if (printed) { msg(MSG_INFO, "\n"); } } } printf("%d BAD sectors, %d sectors corrected with ECC\n", bad_sector_count, ecc_sector_count); } // Write the sector data to file. We only write the best data so if the // caller retries read with error we won't overwrite good data if this // read has an error for this sector but the previous didn't. // Bytes is data to write. // drive_params: specifies the length and other information. // sector_status: is the status of the sector writing // sector_status_list: is the status of the data that would be written to the // file. Status is still updated if drive_params->ext_fd is -1 to prevent // writing. // all_bytes: Includes data header bytes, used for emulator file writing // all_bytes_len: Length of all_bytes // return: -1 if error found, 0 if OK. int mfm_write_sector(uint8_t bytes[], DRIVE_PARAMS * drive_params, SECTOR_STATUS *sector_status, SECTOR_STATUS sector_status_list[], uint8_t all_bytes[], int all_bytes_len) { int rc; STATS *stats = &drive_params->stats; int update; off_t offset; if (sector_status->ignore) { return 0; } // Some disks number sectors starting from 1. We need them starting // from 0. int sect_rel0 = sector_status->sector - drive_params->first_sector_number; // Collect statistics stats->max_sect = MAX(sector_status->sector, stats->max_sect); stats->min_sect = MIN(sector_status->sector, stats->min_sect); stats->max_head = MAX(sector_status->head, stats->max_head); stats->min_head = MIN(sector_status->head, stats->min_head); stats->max_cyl = MAX(sector_status->cyl, stats->max_cyl); stats->min_cyl = MIN(sector_status->cyl, stats->min_cyl); // Check for sector and head in range to prevent bad writes. // We don't check cyl against max since if it exceeds it things // will still work properly. if (sect_rel0 >= drive_params->num_sectors || sect_rel0 < 0) { msg(MSG_ERR_SERIOUS, "Logical sector %d out of range 0-%d sector %d cyl %d head %d\n", sect_rel0, drive_params->num_sectors-1, sector_status->sector, sector_status->cyl,sector_status->head); return -1; } if (sector_status->head > drive_params->num_head) { msg(MSG_ERR_SERIOUS,"Head out of range %d max %d cyl %d sector %d\n", sector_status->head, drive_params->num_head, sector_status->cyl, sector_status->sector); return -1; } sector_status_list[sect_rel0].last_status = sector_status->status; // If not written then write data. Otherwise only write if likely to be // better than sector previously written. Better is if we didn't get a CRC // error, or the ECC correction span is less than the last one. // We assume the header data is correct so we don't check if the header // ECC correction is better. If the header wasn't right we wrote the // data to the wrong spot in the file. update = 0; // If the previous header was bad update if (sector_status_list[sect_rel0].status & SECT_BAD_HEADER) { update = 1; } // If we haven't written the sector yet write it even if bad if (sector_status_list[sect_rel0].status & SECT_NOT_WRITTEN) { sector_status_list[sect_rel0].status &= ~SECT_NOT_WRITTEN; update = 1; } // If current read isn't bad if ( !(sector_status->status & SECT_BAD_DATA)) { // If last was bad then update if (sector_status_list[sect_rel0].status & SECT_BAD_DATA) { update = 1; } // If previous had ECC correction and current correction is less then update if ((sector_status_list[sect_rel0].ecc_span_corrected_data > 0 && (sector_status->ecc_span_corrected_data == 0 || sector_status->ecc_span_corrected_data < sector_status_list[sect_rel0].ecc_span_corrected_data)) || (drive_params->ignore_seek_errors && sector_status->cyl != sector_status_list[sect_rel0].cyl )) { update = 1; } } // If LBA number bad don't update if (sector_status->status & SECT_BAD_LBA_NUMBER) { // Update status that would normally be updated when sector written sector_status_list[sect_rel0] = *sector_status; update = 0; } // Always update errors in emu data in case it ends up being used as // the best data to write update_emu_track_sector(drive_params, sector_status, sect_rel0, all_bytes, all_bytes_len, update); // Only write best sector when in ignore_seek_error mode. Since can get // same sector from reads that are supposed to be from different cylinders // the normal check can't determine which is best. if (update && (drive_params->ignore_seek_errors)) { CRC_INFO crc_info = {0x12345678, 0x140a0445000101ll, 56, 0}; uint64_t crc = crc64(bytes, drive_params->sector_size, &crc_info); offset = (sect_rel0) * drive_params->sector_size + sector_status->head * (drive_params->sector_size * drive_params->num_sectors) + (off_t) sector_status->cyl * (drive_params->sector_size * drive_params->num_sectors * drive_params->num_head); // If good read or previous good read see if data different. Good is CRC // matches with or without ECC correction if (!(sector_status->status & SECT_BAD_DATA) && sector_good[sector_status->cyl][sector_status->head][sect_rel0] != 0) { if (crc != sector_crc[sector_status->cyl][sector_status->head][sect_rel0]) { msg(MSG_INFO, "Miscorrected ECC cyl %d head %d sect %d\n",sector_status->cyl, sector_status->head,sect_rel0); } } // Span is only set if we don't have CRC error after correction if (sector_status_list[sect_rel0].ecc_span_corrected_data > 0) { int new_good = ECC_OFFSET + sector_status_list[sect_rel0].ecc_span_corrected_data; // Update CRC so we can see if we are getting false ECC corrections sector_crc[sector_status->cyl][sector_status->head][sect_rel0] = crc; if (sector_good[sector_status->cyl][sector_status->head][sect_rel0] <= new_good) { //printf("Not replacing better sector with ecc error"); update = 0; } else { sector_good[sector_status->cyl][sector_status->head][sect_rel0] = new_good; } } else { if ( !(sector_status->status & SECT_BAD_DATA)) { sector_good[sector_status->cyl][sector_status->head][sect_rel0] = 1; sector_crc[sector_status->cyl][sector_status->head][sect_rel0] = crc; } else { // If we had a read without error don't overwrite with bad data if (sector_good[sector_status->cyl][sector_status->head][sect_rel0] > 0) { update = 0; } } } } if (update) { if (drive_params->ext_fd >= 0) { if (sector_status->is_lba) { offset = (off_t) sector_status->lba_addr * drive_params->sector_size; } else { offset = (sect_rel0) * drive_params->sector_size + sector_status->head * (drive_params->sector_size * drive_params->num_sectors) + (off_t) sector_status->cyl * (drive_params->sector_size * drive_params->num_sectors * drive_params->num_head); } if (lseek(drive_params->ext_fd, offset, SEEK_SET) < 0) { msg(MSG_FATAL, "Seek failed decoded data: %s\n", strerror(errno)); exit(1); }; if ((rc = write(drive_params->ext_fd, bytes, drive_params->sector_size)) != drive_params->sector_size) { msg(MSG_FATAL, "Write failed, rc %d: %s", rc, strerror(errno)); exit(1); } } sector_status_list[sect_rel0] = *sector_status; } sector_status_list[sect_rel0].last_status = sector_status->status; return 0; } // Write the sector metadata to file. We will write the last sector read // data. TODO: Should we do the keep the best logic? // drive_params: specifies the length and other information. // sector_status: is the status of the sector writing // return: -1 if error found, 0 if OK. int mfm_write_metadata(uint8_t bytes[], DRIVE_PARAMS * drive_params, SECTOR_STATUS *sector_status) { int size = mfm_controller_info[drive_params->controller].metadata_bytes; size_t offset; int sect_rel0 = sector_status->sector - drive_params->first_sector_number; int rc; if (drive_params->ext_metadata_fd >= 0) { if (sector_status->is_lba) { offset = (off_t) sector_status->lba_addr * size; } else { offset = (sect_rel0) * size + sector_status->head * (size * drive_params->num_sectors) + (off_t) sector_status->cyl * (size * drive_params->num_sectors * drive_params->num_head); } if (lseek(drive_params->ext_metadata_fd, offset, SEEK_SET) < 0) { msg(MSG_FATAL, "Seek failed metadata: %s\n", strerror(errno)); exit(1); }; if ((rc = write(drive_params->ext_metadata_fd, bytes, size)) != size) { msg(MSG_FATAL, "Metadata write failed, rc %d: %s", rc, strerror(errno)); exit(1); } } return 0; } // This prints the header or data bytes for decoding new formats void mfm_dump_bytes(uint8_t bytes[], int len, int cyl, int head, int sector_index, int msg_level) { int i; msg(msg_level, "%4d %2d %2d:", cyl, head, sector_index); //for (i = 0; i < MIN(len, 30); i++) { for (i = 0; i < len; i++) { msg(msg_level, "0x%02x,",bytes[i]); if (i % 16 == 15) { msg(msg_level, "\n"); } } msg(msg_level, "\n"); #if 0 for (i = 0; i < len; i++) { // MSG(MSG_INFO, " %02x",bytes[i]); msg(msg_level, "%c",bytes[i]); if (i % 16 == 80) { msg(msg_level, "\n"); } } msg(msg_level, "\n"); #endif } // Perform CRC check of data bytes. // drive_params: Drive parameters // bytes: bytes to process // bytes_crc_len: Length of bytes including CRC // state: Where we are in the decoding process // *crc: Zero if no CRC error // *ecc_span: Number of bits corrected with ECC to fix CRC error // *init_status: Set to SECT_AMBIGUOUS_CRC if zero CRC may be due to zero data // perform_ecc: Non zero if ECC corrections should be performed // SECTOR_DECODE_STATUS mfm_crc_bytes(DRIVE_PARAMS *drive_params, uint8_t bytes[], int bytes_crc_len, int state, uint64_t *crc_ret, int *ecc_span, SECTOR_DECODE_STATUS *init_status, int perform_ecc) { uint64_t crc; // CRC to use to process these bytes CRC_INFO crc_info; // Start byte for CRC decoding int start; SECTOR_DECODE_STATUS status = SECT_NO_STATUS; CHECK_TYPE check_type; if (state == PROCESS_HEADER) { start = mfm_controller_info[drive_params->controller].header_crc_ignore; crc_info = drive_params->header_crc; check_type = mfm_controller_info[drive_params->controller].header_check; } else { start = mfm_controller_info[drive_params->controller].data_crc_ignore; crc_info = drive_params->data_crc; check_type = mfm_controller_info[drive_params->controller].data_check; } if (check_type == CHECK_CHKSUM) { crc = checksum64(&bytes[start], bytes_crc_len-crc_info.length/8-start, &crc_info); if (crc_info.length == 8) { crc = crc & 0xff; if (crc == bytes[bytes_crc_len-1]) { crc = 0; } } else if (crc_info.length == 16) { crc = crc & 0xff; if (crc == bytes[bytes_crc_len-2] && crc == (bytes[bytes_crc_len-1] ^ 0xff)) { crc = 0; } else { msg(MSG_DEBUG, "sum %02llx: %02x, %02x\n", crc, bytes[bytes_crc_len-2], bytes[bytes_crc_len-1]); crc = 1; // Non zero indicates error } } else if (crc_info.length == 32) { crc = crc & 0xffff; uint16_t chksum1, chksum2; chksum1 = ((uint16_t) bytes[bytes_crc_len-4] << 8) | bytes[bytes_crc_len-3]; chksum2 = ((uint16_t) bytes[bytes_crc_len-2] << 8) | bytes[bytes_crc_len-1]; if (crc == chksum1 && crc == (chksum2 ^ 0xffff)) { crc = 0; } else { msg(MSG_DEBUG, "sum %04llx: %04x, %04x\n", crc, chksum1, chksum2); crc = 1; // Non zero indicates error } } else { msg(MSG_FATAL, "Invalid checksum length %d\n",crc_info.length); exit(1); } } else if (check_type == CHECK_NONE) { crc = 0; // TODO: Probably should make these use CRC length field and put in crc_ecc.c } else if (check_type == CHECK_XOR8) { uint8_t x1 = 0; int i; for (i = start; i < bytes_crc_len; i++) { x1 ^= bytes[i]; } crc = x1 ^ 0xff ; } else if (check_type == CHECK_XOR16) { uint8_t x1 = 0, x2 = 0; int i; for (i = start; i < bytes_crc_len - crc_info.length/8; i += 2) { x1 ^= bytes[i]; } for (i = start+1; i < bytes_crc_len - crc_info.length/8; i += 2) { x2 ^= bytes[i]; } crc = (bytes[i] != x2) || (bytes[i+1] != x1) ; } else if (check_type == CHECK_CRC) { int i; crc = crc64(&bytes[start], bytes_crc_len-start, &crc_info); // If all the data and CRC is zero and CRC returns zero // mark it as ambiguous crc since any polynomial will match if (crc == 0) { for (i = start; i < bytes_crc_len; i++) { if (bytes[i] != 0) { break; } } if (i == bytes_crc_len) { *init_status |= SECT_AMBIGUOUS_CRC; } } } else if (check_type == CHECK_PARITY) { crc = eparity64(&bytes[start], bytes_crc_len - start, &drive_params->header_crc) != 1; } else { msg(MSG_FATAL, "Unknown check type %d\n", check_type); exit(1); } // Zero CRC is no error if (crc == 0 && !(*init_status & SECT_AMBIGUOUS_CRC)) { if (state == PROCESS_HEADER) { status |= SECT_ZERO_HEADER_CRC; } else { status |= SECT_ZERO_DATA_CRC; } } if (crc != 0) { // If ECC correction enabled then perform correction up to length // specified if (crc_info.ecc_max_span != 0 && perform_ecc) { *ecc_span = ecc64(&bytes[start], bytes_crc_len-start, crc, &crc_info); // TODO: This includes SECT_SPARE_BAD ECC corrections in the // final value printed. We don't have the info to fix here if (*ecc_span != 0) { drive_params->stats.max_ecc_span = MAX(*ecc_span, drive_params->stats.max_ecc_span); crc = 0; // No longer have CRC error } } } *crc_ret = crc; return status; } // After we have found a valid header/data mark this routine is // used to process the bytes. It checks the CRC and does ECC if needed then // calls wd_process_data to finish the processing. // // drive_params: Drive parameters // bytes: bytes to process // bytes_crc_len: Length of bytes including CRC // state: Where we are in the decoding process // cyl,head: Physical Track data from // sector_index: Sequential sector counter // seek_difference: Return of difference between expected cyl and header // sector_status_list: Return of status of decoded sector // return: Status of sector decoded // TODO: Would be good if data doesn't decode as data to try as header // If data mark missed will process next sector header as data so one // sector of data that should be recoverable will be lost. SECTOR_DECODE_STATUS mfm_process_bytes(DRIVE_PARAMS *drive_params, uint8_t bytes[], int bytes_crc_len, int total_bytes, STATE_TYPE *state, int cyl, int head, int *sector_index, int *seek_difference, SECTOR_STATUS sector_status_list[], SECTOR_DECODE_STATUS init_status) { uint64_t crc; int ecc_span = 0; SECTOR_DECODE_STATUS status = SECT_NO_STATUS; char *name; if (*state == PROCESS_HEADER) { #if DUMP_HEADER static int dump_fd = 0; static int first = 1; if (first) { printf("Dumping for %d bytes crc len %d\n",bytes_crc_len, drive_params->header_crc.length/8); first = 0; } if (dump_fd == 0) { dump_fd = open("dumpheader",O_WRONLY | O_CREAT | O_TRUNC, 0666); } write(dump_fd, bytes, bytes_crc_len); #endif if (msg_get_err_mask() & MSG_DEBUG_DATA) { mfm_dump_bytes(bytes, bytes_crc_len, cyl, head, *sector_index, MSG_DEBUG_DATA); } name = "header"; } else { #if DUMP_DATA static int dump_fd = 0; static int first = 1; if (first) { printf("Dumping for %d bytes crc len %d\n",bytes_crc_len, drive_params->data_crc.length/8); first = 0; } if (dump_fd == 0) { dump_fd = open("dumpdata",O_WRONLY | O_CREAT | O_TRUNC, 0666); } write(dump_fd, bytes, bytes_crc_len); #endif if (msg_get_err_mask() & MSG_DEBUG_DATA) { mfm_dump_bytes(bytes, bytes_crc_len, cyl, head, *sector_index, MSG_DEBUG_DATA); } name = "data"; } status = mfm_crc_bytes(drive_params, bytes, bytes_crc_len, *state, &crc, &ecc_span, &init_status, 1); if (crc != 0 || ecc_span != 0) { msg(MSG_DEBUG,"Bad CRC %s cyl %d head %d sector index %d\n", name, cyl, head, *sector_index); } // If no error process. Only process with errors if data. Without // valid header we don't know what sector we are decoding. if (*state != PROCESS_HEADER || crc == 0 || ecc_span != 0) { // If this is changed change in mfm_decode_track also if (drive_params->controller == CONTROLLER_WD_1006 || drive_params->controller == CONTROLLER_RQDX2 || drive_params->controller == CONTROLLER_SOUYZ_NEON || drive_params->controller == CONTROLLER_NEC_4800 || drive_params->controller == CONTROLLER_ES7978 || drive_params->controller == CONTROLLER_WD_MICROENGINE || drive_params->controller == CONTROLLER_ISBC_214_128B || drive_params->controller == CONTROLLER_ISBC_214_256B || drive_params->controller == CONTROLLER_ISBC_214_512B || drive_params->controller == CONTROLLER_ISBC_214_1024B || drive_params->controller == CONTROLLER_TEKTRONIX_6130 || drive_params->controller == CONTROLLER_NIXDORF_8870 || drive_params->controller == CONTROLLER_TANDY_8MEG || drive_params->controller == CONTROLLER_WD_3B1 || drive_params->controller == CONTROLLER_TANDY_16B || drive_params->controller == CONTROLLER_MOTOROLA_VME10 || drive_params->controller == CONTROLLER_SM_1810_512B || drive_params->controller == CONTROLLER_DSD_5217_512B || drive_params->controller == CONTROLLER_OMTI_5510 || drive_params->controller == CONTROLLER_MORROW_MD11 || drive_params->controller == CONTROLLER_UNKNOWN1 || drive_params->controller == CONTROLLER_UNKNOWN2 || drive_params->controller == CONTROLLER_SHUGART_SA1400 || drive_params->controller == CONTROLLER_DEC_RQDX3 || drive_params->controller == CONTROLLER_DJ_II || drive_params->controller == CONTROLLER_DJ_II_210 || drive_params->controller == CONTROLLER_DJ_II_301 || drive_params->controller == CONTROLLER_MYARC_HFDC || drive_params->controller == CONTROLLER_SHUGART_1610 || drive_params->controller == CONTROLLER_MVME320 || drive_params->controller == CONTROLLER_DTC || drive_params->controller == CONTROLLER_DTC_520_512B || drive_params->controller == CONTROLLER_DTC_520_256B || drive_params->controller == CONTROLLER_MACBOTTOM || drive_params->controller == CONTROLLER_FUJITSU_K_10R || drive_params->controller == CONTROLLER_CTM9016 || drive_params->controller == CONTROLLER_ACORN_A310_PODULE || drive_params->controller == CONTROLLER_MIGHTYFRAME || drive_params->controller == CONTROLLER_DG_MV2000 || drive_params->controller == CONTROLLER_ADAPTEC || drive_params->controller == CONTROLLER_ADAPTEC_4000_18SECTOR_512B || drive_params->controller == CONTROLLER_NEWBURYDATA || drive_params->controller == CONTROLLER_ELEKTRONIKA_85 || drive_params->controller == CONTROLLER_SEAGATE_ST11M || drive_params->controller == CONTROLLER_ALTOS_586 || drive_params->controller == CONTROLLER_ATT_3B2 || drive_params->controller == CONTROLLER_WANG_2275 || drive_params->controller == CONTROLLER_WANG_2275_B || drive_params->controller == CONTROLLER_CALLAN || drive_params->controller == CONTROLLER_IBM_5288 || drive_params->controller == CONTROLLER_IBM_3174 || drive_params->controller == CONTROLLER_EDAX_PV9900 || drive_params->controller == CONTROLLER_ALTOS || drive_params->controller == CONTROLLER_CONVERGENT_AWS || drive_params->controller == CONTROLLER_CONVERGENT_AWS_SA1000 || drive_params->controller == CONTROLLER_ISBC_215_128B || drive_params->controller == CONTROLLER_ISBC_215_256B || drive_params->controller == CONTROLLER_ISBC_215_512B || drive_params->controller == CONTROLLER_ISBC_215_1024B || drive_params->controller == CONTROLLER_DILOG_DQ614 || drive_params->controller == CONTROLLER_DILOG_DQ604 || drive_params->controller == CONTROLLER_DIMENSION_68000 || drive_params->controller == CONTROLLER_ROHM_PBX || drive_params->controller == CONTROLLER_SYMBOLICS_3620 || drive_params->controller == CONTROLLER_OMTI_20L || drive_params->controller == CONTROLLER_SM1040 || drive_params->controller == CONTROLLER_SYMBOLICS_3640) { status |= wd_process_data(state, bytes, total_bytes, crc, cyl, head, sector_index, drive_params, seek_difference, sector_status_list, ecc_span, init_status); } else if (drive_params->controller == CONTROLLER_XEROX_6085 || drive_params->controller == CONTROLLER_XEROX_8010 || drive_params->controller == CONTROLLER_TELENEX_AUTOSCOPE) { status |= tagged_process_data(state, bytes, total_bytes, crc, cyl, head, sector_index, drive_params, seek_difference, sector_status_list, ecc_span, init_status); } else if (drive_params->controller == CONTROLLER_XEBEC_104786 || drive_params->controller == CONTROLLER_XEBEC_104527_256B || drive_params->controller == CONTROLLER_XEBEC_104527_512B || drive_params->controller == CONTROLLER_XEBEC_S1420 || drive_params->controller == CONTROLLER_EC1841 || drive_params->controller == CONTROLLER_SOLOSYSTEMS) { status |= xebec_process_data(state, bytes, total_bytes, crc, cyl, head, sector_index, drive_params, seek_difference, sector_status_list, ecc_span, init_status); } else if (drive_params->controller == CONTROLLER_CORVUS_H || drive_params->controller == CONTROLLER_CROMEMCO || drive_params->controller == CONTROLLER_VECTOR4_ST506 || drive_params->controller == CONTROLLER_VECTOR4 || drive_params->controller == CONTROLLER_STRIDE_440 || drive_params->controller == CONTROLLER_SAGA_FOX) { status |= corvus_process_data(state, bytes, total_bytes, crc, cyl, head, sector_index, drive_params, seek_difference, sector_status_list, ecc_span, init_status); } else if (drive_params->controller == CONTROLLER_NORTHSTAR_ADVANTAGE || drive_params->controller == CONTROLLER_ND100_3041 || drive_params->controller == CONTROLLER_SUPERBRAIN) { status |= northstar_process_data(state, bytes, total_bytes, crc, cyl, head, sector_index, drive_params, seek_difference, sector_status_list, ecc_span, init_status); } else if (drive_params->controller == CONTROLLER_PERQ_T2) { status |= perq_process_data(state, bytes, total_bytes, crc, cyl, head, sector_index, drive_params, seek_difference, sector_status_list, ecc_span, init_status); } else { msg(MSG_FATAL, "Unexpected controller %d\n", drive_params->controller); exit(1); } } else { // Wern't able to process header to mark invalid if we haven't seen all // expected headers. False header detection can occur in the junk at // the end of the track. if (*sector_index < drive_params->num_sectors) { status |= SECT_BAD_HEADER; } // Search for header in case we are out of sync. If we found // data next we can't process it anyway. *state = MARK_ID; } return status; } // TODO: Add ability to convert sector data back to track data to replace this. // This requires determining the gap formats which we don't currently know. // This attempts to piece a good track together out of multiple reads. It does // not always succeed even though we have successfully recovered the sector // data. Various issues with the data can cause the reassembled data to not // make an error free track. // Best_track is the best track read as one read. Best_fixed_track is // the best track by putting together multiple reads uint32_t best_track_words[MAX_TRACK_WORDS]; int best_track_weight; int best_track_num_words; uint32_t best_fixed_track_words[MAX_TRACK_WORDS]; int best_fixed_track_weight; int best_fixed_track_num_words; uint32_t current_track_words[MAX_TRACK_WORDS]; int current_track_words_ndx; int last_sector_start_word; int header_track_word_ndx; int data_bit; int data_word_ndx; // For examining track timing int header_track_tot_bit_count;; int data_tot_bit_count; // Note that last call where data will be written had data for next track. void update_emu_track_words(DRIVE_PARAMS * drive_params, SECTOR_STATUS sector_status_list[], int write_track, int new_track, int cyl, int head) { int i; int last_weight = 0; int best_weight = 0; if (drive_params->emulation_filename == NULL || !drive_params->emulation_output) { return; } // Determine error value for best track and best_fixed track so we can // determine which to use. if (sector_status_list != NULL) { for (i = 0; i < drive_params->num_sectors; i++) { if (!(sector_status_list[i].last_status & SECT_WRONG_CYL)) { // Last status is the one for the last track read if (sector_status_list[i].last_status & SECT_BAD_DATA) { last_weight += 1; } else if (!(sector_status_list[i].last_status & SECT_BAD_HEADER)) { if (sector_status_list[i].last_status & SECT_ECC_RECOVERED) { last_weight += 9; } else { last_weight += 10; } } } if (!(sector_status_list[i].status & SECT_WRONG_CYL)) { // This is the best status for all the reads of the track if (sector_status_list[i].status & SECT_BAD_DATA) { best_weight += 1; } else if (!(sector_status_list[i].status & SECT_BAD_HEADER)) { if (sector_status_list[i].status & SECT_ECC_RECOVERED) { best_weight += 9; } else { best_weight += 10; } } } } } if (write_track) { // If it is at least as good use the track that was from one read // since it is more likely to be ok if (best_track_weight >= best_fixed_track_weight) { emu_file_write_track_bits(drive_params->emu_fd, best_track_words, best_track_num_words, cyl, head, drive_params->emu_track_data_bytes); } else { //printf("Using fixed %d,%d,%d %d %d\n",best_weight, last_weight, // best_track_weight, cyl, head); emu_file_write_track_bits(drive_params->emu_fd, best_fixed_track_words, best_fixed_track_num_words, cyl, head, drive_params->emu_track_data_bytes); } } // Keep best track. Should be last track the way mfm_read works. if (last_weight > best_track_weight || new_track) { best_track_weight = last_weight; memcpy(best_track_words, current_track_words, current_track_words_ndx * sizeof(best_track_words[0])); best_track_num_words = current_track_words_ndx; } // Take the first track for our best fixed track if (new_track && sector_status_list != NULL) { memcpy(best_fixed_track_words, current_track_words, current_track_words_ndx * sizeof(best_track_words[0])); best_fixed_track_num_words = current_track_words_ndx; } best_fixed_track_weight = best_weight; // Clear for next time current_track_words_ndx = 0; } // Temporary storage for last header found. Only one stored at a time static struct { int bit_count; int bit_offset; int tot_bit_count; int word_ndx; } mark_data; // Mark start of header in track data we are building // Bit count is bit location in word // bit_offset is difference between proper header location and bit // count when routine called // tot_bit_count is for finding header separation void mfm_mark_header_location(int bit_count, int bit_offset, int tot_bit_count) { if (bit_count == MARK_STORED) { bit_count = mark_data.bit_count; bit_offset = mark_data.bit_offset; tot_bit_count = mark_data.tot_bit_count; header_track_word_ndx = MAX(mark_data.word_ndx - 1, 0); } else { header_track_word_ndx = MAX(current_track_words_ndx - 1, 0); } #if PRINT_SPACING if (header_track_tot_bit_count != 0 && (tot_bit_count) > header_track_tot_bit_count) { msg(MSG_INFO, "Header to header difference %.1f bytes bit count %d\n", (tot_bit_count - header_track_tot_bit_count) / 16.0, tot_bit_count); msg(MSG_INFO, "Data to header difference %.1f header to data %.1f bytes\n", (tot_bit_count - data_tot_bit_count) / 16.0, (data_tot_bit_count - header_track_tot_bit_count) / 16.0); } else { msg(MSG_INFO, "First Header %.1f bytes\n", tot_bit_count / 16.0); } #endif // Back up 1 word to ensure we copy the header mark pattern // We don't have to be accurate since we can change some of // the gap words. header_track_tot_bit_count = tot_bit_count - bit_offset; } // Mark start of data in track data we are building // Bit count is bit location in word // bit_offset is difference between proper header location and bit // count when routine called // tot_bit_count is for finding header separation void mfm_mark_data_location(int bit_count, int bit_offset, int tot_bit_count) { if (bit_count == MARK_STORED) { bit_count = mark_data.bit_count; bit_offset = mark_data.bit_offset; tot_bit_count = mark_data.tot_bit_count; data_word_ndx = mark_data.word_ndx; } else { // Here we have to be accurate since we need to just replace the data data_word_ndx = current_track_words_ndx; } // Shift to correct bit and word index based on bit_offset data_bit = bit_count - bit_offset; if (data_bit >= 32) { data_bit -= 32; data_word_ndx++; } if (data_bit < 0) { data_bit += 32; data_word_ndx--; } data_tot_bit_count = tot_bit_count - bit_offset; } // This returns the bit count for the start of the last data area // Only valid after mfm_mark_data_location called int mfm_get_data_bit_count() { return data_tot_bit_count; } // Mark start of header type to be determined later // Bit count is bit location in word // bit_offset is difference between proper header location and bit // count when routine called // tot_bit_count is for finding header separation void mfm_mark_location(int bit_count, int bit_offset, int tot_bit_count) { mark_data.word_ndx = current_track_words_ndx; mark_data.bit_count = bit_count; mark_data.bit_offset = bit_offset; mark_data.tot_bit_count = tot_bit_count; } // Mark end of data in track data we are building // Bit count is bit location in work void mfm_mark_end_data(int bit_count, DRIVE_PARAMS *drive_params, int cyl, int head) { if (drive_params->emu_track_data_bytes > 0 && current_track_words_ndx*4 >= drive_params->emu_track_data_bytes) { msg(MSG_ERR, "Warning: Track data truncated writing to emulation file by %d bytes, need %d words cyl %d head %d\n", current_track_words_ndx*4 - drive_params->emu_track_data_bytes+ (bit_count+7)/8, current_track_words_ndx, cyl, head); drive_params->stats.emu_data_truncated = 1; } if (current_track_words_ndx > drive_params->stats.max_track_words) { drive_params->stats.max_track_words = current_track_words_ndx; } } // If we fixed an ECC error we try to put the fixed data bits back into the // sector. Header is not fixed if it has an ECC correction. We copy the // track words into the best track in the area for that sector. static void update_emu_track_sector(DRIVE_PARAMS *drive_params, SECTOR_STATUS *sector_status, int sect_rel0, uint8_t bytes[], int num_bytes, int update) { int i, bit; int word_ndx; int last_bit; int bit_num; uint64_t pat64, mask64, word64; if (sector_status->ecc_span_corrected_data != 0) { #if 0 printf("updating %d %d to %d, %d\n", sect_rel0, header_track_word_ndx, current_track_words_ndx, num_bytes); printf("word ndx %d bit %d\n", data_word_ndx, data_bit); #endif bit_num = 31 - data_bit; word_ndx = data_word_ndx; bit_num += 1; if (bit_num > 31) { bit_num -= 32; word_ndx--; } last_bit = (current_track_words[word_ndx] & (1 << bit_num)) >> bit_num; bit_num -= 2; if (bit_num < 0) { bit_num += 32; word_ndx++; } // This decodes the data bits back into MFM clock and data bits for (i = 0; i < num_bytes; i++) { for (bit = 0x80; bit != 0; bit >>= 1) { if (bytes[i] & bit) { pat64 = 1; } else if (last_bit) { pat64 = 0; } else { pat64 = 2; } last_bit = pat64 & 1; word64 = ((uint64_t) current_track_words[word_ndx-1] << 32) | current_track_words[word_ndx]; mask64 = 0x3ll << bit_num; word64 = (word64 & ~mask64) | (pat64 << bit_num); current_track_words[word_ndx-1] = word64 >> 32; current_track_words[word_ndx] = word64; bit_num -= 2; if (bit_num < 0) { bit_num += 32; word_ndx++; } } } } // If cyl or head changed we are starting a new track so don't copy it // here. update_emu_track_words will copy the entire track. If update // isn't set this data isn't better so don't copy it. if (sector_status->cyl == last_cyl && sector_status->head == last_head && update) { int start = MAX(0, header_track_word_ndx - mfm_controller_info[drive_params->controller].copy_extra); for (i = start; i < current_track_words_ndx; i++) { best_fixed_track_words[i] = current_track_words[i]; } } } // Write the raw decoded clock and data words into the current track word // buffer. This routine only called if bits left in raw_word plus bits // about to be added are >= 32 bits. // // drive_params: Drive parameters // all_raw_bits_count: How many bits left in raw_word to process // int_bit_pos: Number of zeros that are being added before next one // raw_word: bits accululated so far int mfm_save_raw_word(DRIVE_PARAMS *drive_params, int all_raw_bits_count, int int_bit_pos, int raw_word) { uint32_t tmp; // Get shift to move unprocessed bits to MSB int shift = 32 - all_raw_bits_count; // If we aren't generating an emulation output file don't process the // raw words. if (drive_params->emulation_filename == NULL || !drive_params->emulation_output) { return 0; } // Shift unprocessed bits to MSB tmp = raw_word << shift; // If the LSB after shift is where int_bit_pos says a one goes then add it int_bit_pos -= shift; if (int_bit_pos == 0) { tmp |= 1; } // Save word if (current_track_words_ndx < ARRAYSIZE(current_track_words)) { current_track_words[current_track_words_ndx++] = tmp; } else { msg(MSG_FATAL, "Current track words overflow index %d\n", current_track_words_ndx); exit(1); } // Add any more zeros in int_bit_pos until it is less than 32. Those // bits will be added to raw_word by caller. while (int_bit_pos >= 32) { if (current_track_words_ndx < ARRAYSIZE(current_track_words)) { current_track_words[current_track_words_ndx++] = 0; int_bit_pos -= 32; } else { msg(MSG_FATAL, "Current track words overflow\n"); exit(1); } } // This will be all_raw_bits_count on next call return int_bit_pos; } // This adds to alternate track link list the data that needs to be // swapped to put the good data in the proper location in the extracted // data file for replacing entire track. // // drive_params: Drive parameters // bad_cyl: Cylinder that has alterinate track assigned for // bad_head: Head that has alternate track assigned for // good_cyl: The alternate cylinder assigned // good_head: The alternate head assigned. void mfm_handle_alt_track_ch(DRIVE_PARAMS *drive_params, unsigned int bad_cyl, unsigned int bad_head, unsigned int good_cyl, unsigned int good_head) { ALT_INFO *alt_info; // Don't perform alt track processing if analyzing. if (drive_params->analyze_in_progress) { return; } if (bad_cyl >= drive_params->num_cyl) { msg(MSG_ERR, "Bad alternate cylinder %d out of valid range %d to %d\n", bad_cyl, 0, drive_params->num_cyl - 1); return; } if (good_cyl >= drive_params->num_cyl) { msg(MSG_ERR, "Good alternate cylinder %d out of valid range %d to %d\n", good_cyl, 0, drive_params->num_cyl - 1); return; } if (bad_head >= drive_params->num_head) { msg(MSG_ERR, "Bad alternate head %d out of valid range %d to %d\n", bad_head, 0, drive_params->num_head - 1); return; } if (good_head >= drive_params->num_head) { msg(MSG_ERR, "Good alternate head %d out of valid range %d to %d\n", good_head, 0, drive_params->num_head - 1); return; } alt_info = msg_malloc(sizeof(ALT_INFO), "Alt info"); memset(alt_info, 0, sizeof(ALT_INFO)); alt_info->bad_offset = (bad_cyl * drive_params->num_head + bad_head) * drive_params->num_sectors * drive_params->sector_size; alt_info->good_offset = (good_cyl * drive_params->num_head + good_head) * drive_params->num_sectors * drive_params->sector_size; alt_info->length = drive_params->num_sectors * drive_params->sector_size; alt_info->next = drive_params->alt_llist; // Alternate is reported with same information for each // sector. Only add one copy if (drive_params->alt_llist == NULL || alt_info->bad_offset != drive_params->alt_llist->bad_offset || alt_info->good_offset != drive_params->alt_llist->good_offset) { drive_params->alt_llist = alt_info; msg(MSG_INFO,"Alternate track assigned to cyl %d head %d for cyl %d head %d. Extract data fixed\n", good_cyl, good_head, bad_cyl, bad_head); } else { free(alt_info); } } // Print sectors not found during read/decoding // // drive_params: Drive parameters static void print_missing_cyl(DRIVE_PARAMS *drive_params) { int i; int cyl_missing = 0; for (i = 0; i < drive_params->num_cyl; i++) { if (cyl_found[i] == 0) { cyl_missing = 1; break; } } if (cyl_missing) { printf("Cylinders not found\n"); for (i = 0; i < drive_params->num_cyl; i++) { if (cyl_found[i] == 0) { printf(" %d\n",i); } } } } // Perform 3 head bit correction // // drive_params: Drive parameters // head: Head value from header // exp_head: Expected head // return: Corrected head value int mfm_fix_head(DRIVE_PARAMS *drive_params, int exp_head, int head) { // WD 1003 controllers wrote 3 bit head code so head 8 is written as 0. // If requested and head seems correct fix read head value. //printf("3 bit %d, head %d exp %d\n",drive_params->head_3bit, head, exp_head); if (drive_params->head_3bit && head == (exp_head & 0x7)) { return exp_head; } else { return head; } }