// This is a utility program to process existing MFM delta transition data. // Used to extract the sector contents to a file // // 10/09/23 DJG Remove interleave option except from ext2emu so ext2emu // version can be parsed better // 09/12/23 JST Changes to support 5.10 kernel and --sync option // 04/26/23 DJG Really fixed EC1841 ext2emu with new sync pattern bytes. Also // switched marking last sector to physcially last sector. // 04/17/23 DJG Add ext2emu support for EC1841 // 01/17/22 DJG Added ext2emu support for Xebec_104527_256B // 12/19/21 DJG Code cleanup // 01/18/21 DJG Only print valid formats for ext2emu // 10/08/20 DJG Added OP_XOR for sector data. Changed a couple of error messages // 12/31/19 DJG Allow additional special encoded bytes // 08/23/19 DJG Fixed typo and print format. // 07/19/19 DJG Added ext2emu support for Xerox 8010 // 03/12/19 DJG Call parameter change // 02/09/19 DJG Added CONTROLLER_SAGA_FOX support // 01/20/18 DJG Set length of A1 list to MAX_SECTORS*2 to prevent overflow. // Minor code changes. // 10/21/18 DJG Don't allow --begin_time to be changed from value in // input file. It doesn't do anything and makes calculation of // begin_time when last sector truncated wrong. // 12/25/17 DJG Made progress indicator only print same value once // 12/17/17 DJG Added progress indicator // 10/16/16 DJG Fixed spelling error // 01/24/16 DJG Fix for ext2emu when sectors start at 1 // 01/06/16 DJG Rename structure // 01/02/16 DJG Fix ext2emu for Northstar // 01/01/16 DJG Add --mark_bad support // 12/31/15 DJG Added ext2emu functionality // 07/30/15 DJG Added support for revision B board. // 05/17/15 DJG Added ability to analyze transition and emulation files. // Added ability to analyze specific cylinder and head. // // 10/24/14 DJG Changes necessary due to addition of mfm_emu write buffer // using decode options stored in header, and minor reformatting // // Copyright 2021 David Gesswein. // This file is part of MFM disk utilities. // // MFM disk utilities is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // MFM disk utilities is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with MFM disk utilities. If not, see . #include #include #include #include #include #include #include #include #include #include #include #include #include "msg.h" #include "crc_ecc.h" #include "emu_tran_file.h" #define DEF_DATA #include "mfm_decoder.h" #include "parse_cmdline.h" #include "analyze.h" #include "deltas_read.h" // Code is from deltas_read_file.c #include "board.h" #define MAX_DELTAS 131072 // Location that needs special encoding such as A1 marking header typedef struct { int index; uint16_t pattern; } SPECIAL_LIST; void ext2emu(int argc, char *argv[]); // Main routine int main (int argc, char *argv[]) { uint16_t deltas[MAX_DELTAS]; int num_deltas; int cyl, head; int last_cyl = -1, last_head = -1; DRIVE_PARAMS drive_params; // This is in order of physcial sector. First entry is the first // physical sector even if the pysical sectors are numbered from 1. SECTOR_STATUS sector_status_list[MAX_SECTORS]; int seek_difference; //SECTOR_DECODE_STATUS status; int transition_file = 1; EMU_FILE_INFO emu_file_info; TRAN_FILE_INFO tran_file_info; // Handle extracted data to emulator file conversion if (strcmp(basename(argv[0]),"ext2emu") == 0) { ext2emu(argc, argv); return 0; } // If they specified a transitions or emulation file get options that // were stored in it. parse_cmdline(argc, argv, &drive_params, "tm", 1, 1, 1, 0); if (drive_params.transitions_filename != NULL || drive_params.emulation_filename != NULL) { char **tran_argv, **targs; int tran_argc = 0; char *orig_cmdline; char *orig_note; char *cmdline; if (drive_params.transitions_filename != NULL) { drive_params.tran_fd = tran_file_read_header( drive_params.transitions_filename, &tran_file_info); drive_params.start_time_ns = tran_file_info.start_time_ns; drive_params.tran_file_info = &tran_file_info; orig_cmdline = tran_file_info.decode_cmdline; orig_note = tran_file_info.note; } else { drive_params.emu_fd = emu_file_read_header( drive_params.emulation_filename, &emu_file_info, 0, 0); drive_params.start_time_ns = emu_file_info.start_time_ns; drive_params.emu_file_info = &emu_file_info; orig_cmdline = emu_file_info.decode_cmdline; orig_note = emu_file_info.note; } // This can't be changed from command line. Use value set from input // file drive_params.dont_change_start_time = 1; if (orig_cmdline != NULL) { msg(MSG_INFO, "Original decode arguments: %s\n", orig_cmdline); if (orig_note != NULL && strlen(orig_note) != 0) { msg(MSG_INFO, "Note: %s\n", orig_note); } // We need first argument of new argv to be program name argv[0] cmdline = msg_malloc(strlen(orig_cmdline) + strlen(argv[0]) + 1, "main cmdline"); strcpy(cmdline, argv[0]); strcat(cmdline, " "); strcat(cmdline, orig_cmdline); tran_argv = buildargv(cmdline); for (targs = tran_argv; *targs != NULL; targs++) { tran_argc++; } // Allow the arj options from stored command line, we will ignore // them in drive_params. Don't make errors fatal in case bad // option gets in header parse_cmdline(tran_argc, tran_argv, &drive_params, "", 0, 0, 1, 0); free(cmdline); } } // Now parse the full command line. This allows overriding options that // were in the transition file header. parse_cmdline(argc, argv, &drive_params, "Mrdi", 0, 0, 0, 0); // Save final parameters drive_params.cmdline = parse_print_cmdline(&drive_params, 0, 0); // And do some checking parse_validate_options(&drive_params, 0); if (drive_params.transitions_filename == NULL) { if (drive_params.emulation_filename == NULL) { msg(MSG_FATAL, "Transition or emulation filename must be specified\n"); exit(1); } else { transition_file = 0; // Fix up from parse_cmdline. We read emulation file not write it if // no transition file is specified // Open if it we didn't open above in getting saved command arguments drive_params.emulation_output = 0; if (drive_params.emu_fd == -1) { drive_params.emu_fd = emu_file_read_header( drive_params.emulation_filename, &emu_file_info, 0, 0); } } } // If analyze requested get drive parameters. if (drive_params.analyze && (drive_params.transitions_filename != NULL || drive_params.emulation_filename != NULL)) { analyze_disk(&drive_params, deltas, MAX_DELTAS, 1); msg(MSG_INFO,"\n"); // Print analysis results parse_print_cmdline(&drive_params, 1, 0); // If we are also converting data set up for it. Otherwise exit if ((drive_params.transitions_filename != NULL && drive_params.emulation_filename != NULL) || drive_params.extract_filename != NULL) { // Go back to beginning of file if (drive_params.tran_fd != -1) { tran_file_seek_track(drive_params.tran_fd, 0, 0, drive_params.tran_file_info); } else { emu_file_seek_track(drive_params.emu_fd, 0, 0, drive_params.emu_file_info); } } else { exit(1); } } if (drive_params.transitions_filename != NULL && drive_params.emulation_filename == NULL && drive_params.extract_filename == NULL) { msg(MSG_FATAL, "Must specify emulation and or extract file to be generated\n"); exit(1); } if (drive_params.transitions_filename == NULL && drive_params.emulation_filename != NULL && drive_params.extract_filename == NULL) { msg(MSG_FATAL, "Must specify extract file to be generated\n"); exit(1); } // Setup decoding of transitions and possible file to write to mfm_decode_setup(&drive_params, 1); if (transition_file) { num_deltas = tran_file_read_track_deltas(drive_params.tran_fd, deltas, MAX_DELTAS, &cyl, &head); } else { num_deltas = emu_file_read_track_deltas(drive_params.emu_fd, &emu_file_info, deltas, MAX_DELTAS, &cyl, &head); } // Read and process a track at a time until all read while (num_deltas >= 0) { if (cyl % 10 == 0 && head == 0) msg(MSG_PROGRESS, "At cyl %d\r", cyl); // Only clear status if we are moving to the next track. If retries // were done we may have multiple reads of the same track. if (last_cyl != cyl || last_head != head) { mfm_init_sector_status_list(sector_status_list, drive_params.num_sectors); } //printf("Decoding new track %d %d\n",cyl, head); deltas_update_count(num_deltas, 0); // If head & cylinder haven't changed assume it's a retry. mfm_decode_track(&drive_params, cyl, head, deltas, &seek_difference, sector_status_list); #if 0 if (1 || status != SECT_HEADER_FOUND) { if (cyl == 0 && head == 0) { FILE *file; char fn[256]; int i; sprintf(fn, "trk%d-%d.txt",cyl, head); file = fopen(fn, "w"); for (i = 0; i < num_deltas; i++) { fprintf(file, "%d\n",deltas[i]); } fclose(file); } } #endif last_cyl = cyl; last_head = head; if (transition_file) { num_deltas = tran_file_read_track_deltas(drive_params.tran_fd, deltas, MAX_DELTAS, &cyl, &head); while (head >= drive_params.num_head) { static int msg_printed = 0; if (!msg_printed) { msg(MSG_INFO, "Warning, data has more heads than specified. Data for head >= %d ignored\n", head); msg_printed = 1; } num_deltas = tran_file_read_track_deltas(drive_params.tran_fd, deltas, MAX_DELTAS, &cyl, &head); } } else { num_deltas = emu_file_read_track_deltas(drive_params.emu_fd, &emu_file_info, deltas, MAX_DELTAS, &cyl, &head); } } mfm_decode_done(&drive_params); return 0; } // Reverse bit ordering in word // value: Value to reverse // len_bits: Number of bits in value // return: Reversed value uint64_t reverse_bits(uint64_t value, int len_bits) { uint64_t new_value = 0; int i; for (i = 0; i < len_bits; i++) { new_value = (new_value << 1) | (value & 1); value >>= 1; } return new_value; } // Non zero if the sector number has already been used static int *sector_used_list; // Size of sector_used_list in bytes static int list_size_bytes; // Number of sector used in the list static int sector_used_count; // Sector number to start the next track with static int track_start_sector; // Interleave to use on a track, sectors incremented by this value static int sector_interleave; // Increment to sector to use for start sector of each track static int track_interleave; // Current sector static int sector; // Current physical sector static int phys_sector; // Current head and cylinder static int head, cyl; // Set the current head value // // head_i: Head value static void set_head(int head_i) { head = head_i; } // Return the current head value static int get_head() { return head; } // Set the current cylinder value // // cyl_i: Cylinder value static void set_cyl(int cyl_i) { cyl = cyl_i; } // Return the current cylinder value static int get_cyl() { return cyl; } // Set the sector interleave values // // drive_params: Drive parameters // sector_interleave_i: Sector interleave. 1 for consecutive sectors //track_interleave_i: Addition to start sector number for tracks // in a cylinder. 0 for all tracks to start with same sector number static void set_sector_interleave(DRIVE_PARAMS *drive_params, int sector_interleave_i, int track_interleave_i) { list_size_bytes = sizeof(*sector_used_list) * drive_params->num_sectors; sector_interleave = sector_interleave_i; sector_used_list = msg_malloc(list_size_bytes,"sector_used_list"); track_interleave = track_interleave_i; memset(sector_used_list, 0, list_size_bytes); } // Update variables to start a new track. // // drive_params: Drive parameters static void start_new_track(DRIVE_PARAMS *drive_params) { // Set sector to start sector for list and reset used sector list memset(sector_used_list, 0, list_size_bytes); sector_used_count = 0; if (track_interleave == 0) { sector = 0; phys_sector = 0; } else { sector = track_start_sector; phys_sector = 0; track_start_sector = (track_start_sector + track_interleave) % drive_params->num_sectors; } } // Update variables to start a new cylinder. Should be called before // start_new_track. Cylinders always start with sector 0 first. // // drive_params: Drive parameters static void start_new_cyl(DRIVE_PARAMS *drive_params) { track_start_sector = 0; } // Get current sector static int get_sector(DRIVE_PARAMS *drive_params) { return sector + drive_params->first_sector_number; } // Increment sector variable // // drive_params: Drive parameters static void inc_sector(DRIVE_PARAMS *drive_params) { // Mark current sector used and increment by interleave if we // haven't generated all sectors. sector_used_list[sector] = 1; sector_used_count++; if (sector_used_count < drive_params->num_sectors) { sector = (sector + sector_interleave) % drive_params->num_sectors; phys_sector++; // If sector is already used find next unused while (sector_used_list[sector]) { sector = (sector + 1) % drive_params->num_sectors; } } } // Get LBA value for current sector head and cylinder // // drive_params: Drive parameters static int get_lba(DRIVE_PARAMS *drive_params) { return (get_cyl() * drive_params->num_head + get_head()) * drive_params->num_sectors + sector; } // Get check value for end of data // // track: Data to calculate over // length: Number of bytes to over // crc_info: Parameters for check calculation // check_type; Type of check value to calculate static uint64_t get_check_value(uint8_t track[], int length, CRC_INFO *crc_info, CHECK_TYPE check_type) { uint64_t value; if (check_type == CHECK_CRC) { value = crc64(track, length, crc_info); } else if (check_type == CHECK_CHKSUM) { value = checksum64(track, length, crc_info); // The length is twice the actual length due to not the best // implementation decision when Northstar format was added. if (crc_info->length == 16) { value = value & 0xff; } else if (crc_info->length == 32) { value = value & 0xffff; } else { msg(MSG_FATAL, "Unsupported checksum length %d\n",crc_info->length); exit(1); } } else if (check_type == CHECK_PARITY) { value = eparity64(track, length, crc_info); } else if (check_type == CHECK_XOR16) { int i; uint8_t x1 = 0, x2 = 0; for (i = 0; i < length; i += 2) { x1 ^= track[i]; } for (i = 1; i < length; i += 2) { x2 ^= track[i]; } value = (x2 << 8) | x1; } else if (check_type == CHECK_NONE) { value = 0; // Should be ignored } else { msg(MSG_FATAL, "Unknown check_type %d\n",check_type); exit(1); } return value; } // Get the sector data from the extract data file and put it in the track // data array // // drive_params: Drive parameters // track: Location to write data read to // length: Number of bytes in track static void get_data(DRIVE_PARAMS *drive_params, uint8_t track[], int length) { int block; int rc; int sector = get_sector(drive_params); if (drive_params->sector_size > length) { msg(MSG_FATAL, "Track overflow get_data\n"); exit(1); } // This is a stupid format where the sector header sector field is // different than the actual sector. This adjusts for it. if (drive_params->controller == CONTROLLER_EC1841) { sector = (sector + 17 - sector_interleave) % drive_params->num_sectors; } block = (get_cyl() * drive_params->num_head + get_head()) * drive_params->num_sectors + sector - drive_params->first_sector_number; if (lseek(drive_params->ext_fd, block * drive_params->sector_size, SEEK_SET) == -1) { msg(MSG_FATAL, "Failed to seek to sector in extracted data file %s\n", strerror(errno)); exit(1); } if ((rc = read(drive_params->ext_fd, track, drive_params->sector_size)) != drive_params->sector_size) { msg(MSG_FATAL, "Failed to read extracted data file rc %d %s\n", rc, rc == -1 ? strerror(errno): ""); exit(1); }; } // Get the sector tag from the extract data tag file and put it in the track // data array // // drive_params: Drive parameters // track: Location to write data read to // length: Number of bytes in track static void get_metadata(DRIVE_PARAMS *drive_params, uint8_t track[], int length) { int block; int rc; if (drive_params->metadata_bytes > length) { msg(MSG_FATAL, "Track overflow get_data\n"); exit(1); } block = (get_cyl() * drive_params->num_head + get_head()) * drive_params->num_sectors + get_sector(drive_params) - drive_params->first_sector_number; if (lseek(drive_params->ext_metadata_fd, block * drive_params->metadata_bytes, SEEK_SET) == -1) { msg(MSG_FATAL, "Failed to seek to sector in extracted metadata file %s\n", strerror(errno)); exit(1); } if ((rc = read(drive_params->ext_metadata_fd, track, drive_params->metadata_bytes)) != drive_params->metadata_bytes) { msg(MSG_FATAL, "Failed to read extracted metadata file rc %d %s\n", rc, rc == -1 ? strerror(errno): ""); exit(1); }; } // Process field definitions to write the specified data to the track // // drive_params: Drive parameters // full_track: Track data array // start: Offset into track fields are referenced to // length: Number of bytes in track // field_def: The array of field definitions to process // special_list: List of locations where special a1 code is in track // special_list_ndx: Next free index in list // special_list_len: Maximum number of entries in list // static void process_field(DRIVE_PARAMS *drive_params, uint8_t full_track[], int start, int length, FIELD_L field_def[], SPECIAL_LIST special_list[], int *special_list_ndx, int special_list_len) { int ndx = 0; uint64_t value; int i; // set to 1 if handled in case otherwise code after case updates track int data_set; // Start and end to calculate crc over int crc_start = 0; int crc_end = -1; // Maximum location filled in field int field_filled = 0; // Pointer to where we start updating from uint8_t *track = &full_track[start]; //printf("Process field called start %d\n",start); while (field_def[ndx].len_bytes != -1) { data_set = 0; switch (field_def[ndx].type) { // Fill the specified range with the specified value only if last // sector of track case FIELD_FILL_LAST_SECTOR: // Only take action on last sector // This used to be based on logical sector. For other example // I had logical and physical were both the last sector. New // sample they are different and physical last sector got the // flag byte so changing to physical. #if 0 if (get_sector(drive_params) != drive_params->num_sectors - drive_params->first_sector_number - 1) { data_set = 1; break; } #endif // If not last physical sector break to not change value if (phys_sector != drive_params->num_sectors - 1) { data_set = 1; break; } // Intentional fall through // Fill the specified range with the specified value case FIELD_FILL: if (field_def[ndx].byte_offset_bit_len + field_def[ndx].len_bytes > length) { msg(MSG_FATAL, "Track overflow field fill %d, %d, %d\n", field_def[ndx].byte_offset_bit_len, field_def[ndx].len_bytes, length); exit(1); } if (field_def[ndx].op == OP_SET) { memset(&track[field_def[ndx].byte_offset_bit_len], field_def[ndx].value, field_def[ndx].len_bytes); } else if (field_def[ndx].op == OP_XOR || field_def[ndx].op == OP_REVERSE_XOR) { for (i = 0; i < field_def[ndx].len_bytes; i++) { track[field_def[ndx].byte_offset_bit_len + i] ^= field_def[ndx].value; } } else { msg(MSG_FATAL, "op %d not supported for FIELD_FILL\n", field_def[ndx].op); exit(1); } data_set = 1; break; case FIELD_CYL: value = get_cyl(); break; case FIELD_CYL_SEAGATE_ST11M: value = get_cyl(); // Controller first cylinder and first user cylinder are both 0 if (value > 0) { value--; } break; case FIELD_HEAD: value = get_head(); break; case FIELD_HEAD_SEAGATE_ST11M: value = get_head(); if (get_cyl() == 0) { value = 0xff; } break; case FIELD_SECTOR: value = get_sector(drive_params); break; case FIELD_LBA: value = get_lba(drive_params); break; case FIELD_HDR_CRC: // If end of CRC not specified assume it ends with byte before // where CRC goes. if (crc_end == -1) { crc_end = field_def[ndx].byte_offset_bit_len - 1; } value = get_check_value(&track[crc_start], crc_end - crc_start + 1, &drive_params->header_crc, mfm_controller_info[drive_params->controller].header_check); break; case FIELD_DATA_CRC: if (crc_end == -1) { crc_end = field_def[ndx].byte_offset_bit_len - 1; } value = get_check_value(&track[crc_start], crc_end - crc_start + 1, &drive_params->data_crc, mfm_controller_info[drive_params->controller].data_check); if (drive_params->mark_bad_list != NULL) { MARK_BAD_INFO *mark_bad_list = &drive_params->mark_bad_list[drive_params->next_mark_bad]; if (get_cyl() == mark_bad_list->cyl && get_head() == mark_bad_list->head && get_sector(drive_params) == mark_bad_list->sector) { // Invert the check value to invalidiate value = ~value; if (!mark_bad_list->last) { drive_params->next_mark_bad++; } } } break; case FIELD_MARK_CRC_START: crc_start = field_def[ndx].byte_offset_bit_len; data_set = 1; break; case FIELD_MARK_CRC_END: crc_end = field_def[ndx].byte_offset_bit_len; data_set = 1; break; case FIELD_SECTOR_DATA: if (field_def[ndx].len_bytes != drive_params->sector_size) { msg(MSG_FATAL, "Sector length mismatch %d %d\n", field_def[ndx].len_bytes, drive_params->sector_size); exit(1); } if (field_def[ndx].op == OP_XOR) { uint8_t buf[length - field_def[ndx].byte_offset_bit_len]; int start = field_def[ndx].byte_offset_bit_len; get_data(drive_params, buf, length - start); for (i = start; i < start + field_def[ndx].len_bytes; i++) { track[i] ^= buf[i - start]; } } else { get_data(drive_params, &track[field_def[ndx].byte_offset_bit_len], length - field_def[ndx].byte_offset_bit_len); } if (field_def[ndx].op == OP_REVERSE) { int start = field_def[ndx].byte_offset_bit_len; for (i = start; i < start + field_def[ndx].len_bytes; i++) { track[i] = reverse_bits(track[i], 8); } } data_set = 1; break; case FIELD_SECTOR_METADATA: if (field_def[ndx].len_bytes != drive_params->metadata_bytes) { msg(MSG_FATAL, "Sector length mismatch metadata %d %d\n", field_def[ndx].len_bytes, drive_params->metadata_bytes); exit(1); } get_metadata(drive_params, &track[field_def[ndx].byte_offset_bit_len], length - field_def[ndx].byte_offset_bit_len); if (field_def[ndx].op == OP_REVERSE) { int start = field_def[ndx].byte_offset_bit_len; for (i = start; i < start + field_def[ndx].len_bytes; i++) { track[i] = reverse_bits(track[i], 8); } } data_set = 1; break; // Place holder for code to handle marking a sector case FIELD_BAD_SECTOR: value = 0; break; // Special A1 with missing clock. We put a1 in the data and fix the // encoded MFM data curing the conversion case FIELD_A1: if (*special_list_ndx >= special_list_len) { msg(MSG_FATAL, "Special list overflow\n"); exit(1); } special_list[*special_list_ndx].index = start + field_def[ndx].byte_offset_bit_len; special_list[(*special_list_ndx)++].pattern = 0x4489; value = 0xa1; break; // Special 42 with missing clock. We put 42 in the data and fix the // encoded MFM data curing the conversion case FIELD_42: if (*special_list_ndx >= special_list_len) { msg(MSG_FATAL, "Special list overflow\n"); exit(1); } special_list[*special_list_ndx].index = start + field_def[ndx].byte_offset_bit_len; special_list[(*special_list_ndx)++].pattern = 0x1224; value = 0x42; break; // Special 85 with missing clock. We put 85 in the data and fix the // encoded MFM data curing the conversion case FIELD_85: if (*special_list_ndx >= special_list_len) { msg(MSG_FATAL, "Special list overflow\n"); exit(1); } special_list[*special_list_ndx].index = start + field_def[ndx].byte_offset_bit_len; special_list[(*special_list_ndx)++].pattern = 0x4891; value = 0x85; break; // Special 0a with missing clock. We put 0a in the data and fix the // encoded MFM data curing the conversion case FIELD_0A: if (*special_list_ndx >= special_list_len) { msg(MSG_FATAL, "Special list overflow\n"); exit(1); } special_list[*special_list_ndx].index = start + field_def[ndx].byte_offset_bit_len; special_list[(*special_list_ndx)++].pattern = 0x2244; value = 0x0a; break; // Special 10 with missing clock. We put 1f in the data and fix the // encoded MFM data curing the conversion case FIELD_10: if (*special_list_ndx >= special_list_len) { msg(MSG_FATAL, "Special list overflow\n"); exit(1); } special_list[*special_list_ndx].index = start + field_def[ndx].byte_offset_bit_len; special_list[(*special_list_ndx)++].pattern = 0x89aa; value = 0x10; break; // Special E3 with missing clock. We put E3 in the data and fix the // encoded MFM data curing the conversion. case FIELD_C0: if (*special_list_ndx >= special_list_len) { msg(MSG_FATAL, "Special list overflow\n"); exit(1); } special_list[*special_list_ndx].index = start + field_def[ndx].byte_offset_bit_len; special_list[(*special_list_ndx)++].pattern = 0x12aa; value = 0xC0; break; case FIELD_NEXT_SECTOR: inc_sector(drive_params); data_set = 1; break; default: msg(MSG_FATAL, "Unknown field_def type %d\n",field_def[ndx].type); exit(1); } if (data_set) { // Just mark last byte written, track already updated field_filled = MAX(field_filled, field_def[ndx].byte_offset_bit_len + field_def[ndx].len_bytes - 1); // If no bit list update the specified bytes } else if (field_def[ndx].bit_list == NULL) { // For the silly controller that has the bits backward reverse them if (field_def[ndx].op == OP_REVERSE || field_def[ndx].op == OP_REVERSE_XOR) { value = reverse_bits(value, field_def[ndx].len_bytes * 8); } field_filled = MAX(field_filled, field_def[ndx].byte_offset_bit_len + field_def[ndx].len_bytes - 1); if (field_def[ndx].byte_offset_bit_len + field_def[ndx].len_bytes > length) { msg(MSG_FATAL, "Track overflow field update %d %d %d\n", field_def[ndx].byte_offset_bit_len, field_def[ndx].len_bytes, length); exit(1); } // Data goes in MSB first. Shift first byte to top of value // then pull of the bytes and update track value <<= (sizeof(value) - field_def[ndx].len_bytes) * 8; for (i = 0; i < field_def[ndx].len_bytes; i++) { int wbyte = (value >> (sizeof(value)*8 - 8)); if (field_def[ndx].op == OP_XOR || field_def[ndx].op == OP_REVERSE_XOR) { track[field_def[ndx].byte_offset_bit_len + i] ^= wbyte; } else { track[field_def[ndx].byte_offset_bit_len + i] = wbyte; } value <<= 8; } } else { int ndx2 = 0; BIT_L *bit_list = field_def[ndx].bit_list; int byte_offset, bit_offset; uint8_t temp; int bit_count = 0; // For the silly controller that has the bits backward reverse them if (field_def[ndx].op == OP_REVERSE || field_def[ndx].op == OP_REVERSE_XOR) { value = reverse_bits(value, field_def[ndx].byte_offset_bit_len); } // Now pull off starting at the highest bit and put them into // the bits specified. In this encoding the bits are numbered with // the most significant bit of the first byte 0 counting up. // Multiple disjoint bit fields can be specified. // bitl_start -1 is end of list, -2 is discard the bits while (bit_list[ndx2].bitl_start != -1) { for (i = 0; i < bit_list[ndx2].bitl_length; i++) { if (bit_list[ndx2].bitl_start == -2) { bit_count++; // Discard these bits } else { // Find byte and bit to update byte_offset = (bit_list[ndx2].bitl_start + i) / 8; field_filled = MAX(field_filled, byte_offset); bit_offset = (bit_list[ndx2].bitl_start + i) % 8; if (byte_offset >= length) { msg(MSG_FATAL, "Track overflow bit field\n"); exit(1); } // Extract bit and update in track temp = ( (value >> (field_def[ndx].byte_offset_bit_len - bit_count++ - 1)) & 1) << (7 - bit_offset); if (field_def[ndx].op == OP_XOR || field_def[ndx].op == OP_REVERSE_XOR) { track[byte_offset] ^= temp; } else { track[byte_offset] &= ~(1 << (7 - bit_offset)); track[byte_offset] |= temp; } } } ndx2++; } // Verify field size agrees with sum of bit field lengths if (bit_count != field_def[ndx].byte_offset_bit_len) { msg(MSG_FATAL, "Bit field length mismatch %d %d\n", bit_count, field_def[ndx].byte_offset_bit_len); exit(1); } } ndx++; } // Verify that last byte in field was updated if (field_filled != length - 1) { msg(MSG_FATAL, "Incorrect field length %d %d\n",field_filled, length); exit(1); } } // Process track definition list to update track data. // // drive_params: Drive parameters // track: Track data array // start: Offset into track to start writing data to // length: Number of bytes in track // track_def: The array of track definitions to process // special_list: List of locations where special a1 code is in track // special_list_ndx: Next free index in list // special_list_len: Maximum number of entries in list // // return: Next offset into track to write to static int process_track(DRIVE_PARAMS *drive_params, uint8_t track[], int start, int length, TRK_L track_def[], SPECIAL_LIST special_list[], int *special_list_ndx, int special_list_len) { int ndx = 0; int new_start; int i; //printf("Process track called start %d\n",start); while (track_def[ndx].count != -1) { switch (track_def[ndx].type) { case TRK_FILL: if (start + track_def[ndx].count > length) { msg(MSG_FATAL, "Track overflow by %d fill, %d %d %d\n", start + track_def[ndx].count - length, start, track_def[ndx].count, length); exit(1); } memset(&track[start], track_def[ndx].value, track_def[ndx].count); start += track_def[ndx].count; break; case TRK_SUB: // Process a sub list of track definitions the specified number // of times for (i = 0; i < track_def[ndx].count; i++) { start = process_track(drive_params, track, start, length, (TRK_L *) track_def[ndx].list, special_list, special_list_ndx, special_list_len); } break; case TRK_FIELD: new_start = start + track_def[ndx].count; if (new_start >= length) { msg(MSG_FATAL, "Track overflow field %d,%d\n", new_start, length); exit(1); } // Fill the field with the specified value then // process the field definitions memset(&track[start], track_def[ndx].value, track_def[ndx].count); process_field(drive_params, track, start, track_def[ndx].count, (FIELD_L *) track_def[ndx].list, special_list, special_list_ndx, special_list_len); start = new_start; break; default: msg(MSG_FATAL, "Unknown track_def type %d\n",track_def[ndx].type); exit(1); } ndx++; } return start; } // Convert data to MFM encoded data. // // data: Bytes to convert // length: Number of bytes to convert // mfm_data: Destination to write encoded data to // special_list: List of locations special a1 mark pattern should be written // special_list_length; Length of list void mfm_encode(uint8_t data[], int length, uint32_t mfm_data[], int mfm_length, SPECIAL_LIST special_list[], int special_list_length) { // Convert a byte to 16 MFM encoded bits. First index is the MFM // bit immediately preceding. static uint16_t mfm_encode[2][256]; // One on first call static int first_time = 1; // Used to index first subscript in mfm_encode int last_bit = 0; // Counters. int i, lbc; int bit; uint16_t value16; uint32_t value32 = 0; // extracted bit int ext_bit; int special_list_ndx = 0; if (length * 2 / sizeof(mfm_data[0]) > mfm_length) { msg(MSG_FATAL, "MFM data overflow\n"); exit(1); } // Generate table to convert a byte to 16 MFM encoded bits if (first_time) { for (lbc = 0; lbc < 2; lbc++) { for (i = 0; i < 256; i++) { last_bit = lbc; value16 = 0; for (bit = 7; bit >= 0; bit--) { value16 <<= 2; ext_bit = (i >> bit) & 1; value16 |= ((!(last_bit | ext_bit)) << 1) | ext_bit; last_bit = ext_bit; } mfm_encode[lbc][i] = value16; } } first_time = 0; } last_bit = 0; for (i = 0; i < length; i++) { // If at the top location in the special list write the special. // pattern. List is in ascending order. Otherwise encode the byte if (special_list_ndx < special_list_length && i == special_list[special_list_ndx].index) { value16 = special_list[special_list_ndx].pattern; special_list_ndx++; } else { value16 = mfm_encode[last_bit][data[i]]; } // Put in correct half of 32 bit word. if (i & 1) { value32 = value32 << 16 | value16; mfm_data[i/2] = value32; } else { value32 = value16; } last_bit = value16 & 1; } } // Convert an extracted data file to an emulator file // //TODO: Is interleave handling sufficient? // Think about handle DEC_RQDX3 format where tracks vary. Having format // vary between sectors on same track is really annoying. void ext2emu(int argc, char *argv[]) { // Store bytes for track uint8_t *track; int track_length; uint32_t *track_mfm; // Store byte locations in track where special MFM encoding of A1 // mark field needs to be inserted SPECIAL_LIST special_list[MAX_SECTORS*2]; int special_list_ndx = 0; // Number of bytes written to track int track_filled = 0; DRIVE_PARAMS drive_params; struct stat finfo; int calc_size; CONTROLLER *controller; parse_cmdline(argc, argv, &drive_params, "sgjdlu3rabt", 1, 0, 0, 1); parse_validate_options_listed(&drive_params, "hcemf"); if (mfm_controller_info[drive_params.controller].track_layout == NULL) { msg(MSG_FATAL, "Not yet able to process format %s\n", mfm_controller_info[drive_params.controller].name); exit(1); } // Pull various parameters we need for this controller controller = &mfm_controller_info[drive_params.controller]; drive_params.num_sectors = controller->write_num_sectors; drive_params.first_sector_number = controller->write_first_sector_number; drive_params.sector_size = controller->write_sector_size; drive_params.metadata_bytes = controller->metadata_bytes; drive_params.data_crc = controller->write_data_crc; drive_params.header_crc = controller->write_header_crc; drive_params.emu_track_data_bytes = controller->track_words * 4; drive_params.start_time_ns = controller->start_time_ns; // Save final parameters drive_params.cmdline = parse_print_cmdline(&drive_params, 0, 1); drive_params.ext_fd = open(drive_params.extract_filename, O_RDONLY); if (drive_params.ext_fd < 0) { msg(MSG_FATAL, "Unable to open extract file: %s\n", strerror(errno)); exit(1); } if (drive_params.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_RDONLY); if (drive_params.ext_metadata_fd < 0) { msg(MSG_FATAL, "Unable to open extract tag file: %s\n", strerror(errno)); exit(1); } } 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, controller->clk_rate_hz, drive_params.start_time_ns, drive_params.emu_track_data_bytes); fstat(drive_params.ext_fd, &finfo); calc_size = drive_params.sector_size * drive_params.num_sectors * drive_params.num_head * drive_params.num_cyl; // Warn if the extracted data file doesn't match the expected size for // the parameters specified if (calc_size != finfo.st_size) { msg(MSG_INFO, "Calculated extract file size %d bytes, actual size %jd\n", calc_size, (intmax_t) finfo.st_size); } // If interleave values specified set them if (drive_params.sector_numbers != NULL) { set_sector_interleave(&drive_params, drive_params.sector_numbers[0], drive_params.sector_numbers[1]); } else { set_sector_interleave(&drive_params, 1, 0); } memset(special_list, 0, sizeof(special_list)); track_length = drive_params.emu_track_data_bytes / 2; track = msg_malloc(track_length , "Track data"); track_mfm = msg_malloc(drive_params.emu_track_data_bytes, "Track data bits"); // Step through each cylinder and track and process the data for (cyl = 0; cyl < drive_params.num_cyl; cyl++) { if (cyl % 10 == 0) msg(MSG_PROGRESS, "At cyl %d\r", cyl); set_cyl(cyl); start_new_cyl(&drive_params); for (head = 0; head < drive_params.num_head; head++) { set_head(head); start_new_track(&drive_params); memset(track, 0, track_length); // Generate the byte data in track, convert to MFM and write // to emulator file track_filled = process_track(&drive_params, track, 0, track_length, controller->track_layout, special_list, &special_list_ndx, ARRAYSIZE(special_list)); mfm_encode(track, track_length, track_mfm, drive_params.emu_track_data_bytes / sizeof(track_mfm[0]), special_list, special_list_ndx); emu_file_write_track_bits(drive_params.emu_fd, (uint32_t *)track_mfm, drive_params.emu_track_data_bytes/4, cyl, head, drive_params.emu_track_data_bytes); special_list_ndx = 0; } } // Warn if we didn't update all of the track array if (track_filled != track_length) { msg(MSG_INFO, "Not all track filled, %d of %d bytes used\n", track_filled, track_length); } emu_file_close(drive_params.emu_fd, 1); }