// These routines are used to read and write the emulator and transition
// file formats for the MFM disk programs.
//
// emu routines are for emulator file format and tran routines for transition/
// delta file format.
// internal delta format is an array of 16-bit transition delta times for
// easy handling, while the transition file format is a packed version
// defined below. (generally "delta" is internal and "transition"
// external, but this is not guaranteed consistent.)
//
// Call emu_file_read_track_deltas to read next track of data and convert to
// delta format.
// Call emu_file_write_header to open file for writing and write out
// header data. File will be created or truncated.
// Call emu_file_read_header to open file for reading or read/write.
// Call emu_file_write_track_bits to write next track of emulation file data.
// Call emu_file_rewrite_track to update a track in emulation file.
// Call emu_file_read_track_bits to read next track of emulation file data.
// Call emu_file_read_cyl to read a cylinder of emulation file data.
// Call emu_file_write_cyl to write a cylinder of emulation file data.
// Call emu_file_close to close emulation file.
// Call emu_file_seek_track to seek to desired cylinder and head
//
// Call tran_file_close to close transition file.
// Call tran_file_read_header to open file for reading.
// Call tran_file_write_header to open file for writing and write out
// header data. File will be created or truncated.
// Call tran_file_read_track_deltas to read next track of data and convert to
// delta format.
// Call tran_file_write_track_deltas to write next track of data, converting
// from delta format.
// Call tran_file_seek_track to seek to desired cylinder and head
//
// Delta format is count of clocks between ones in 200 MHz clocks.
//
// All files are read and written little endian.
// File version word
// Upper 8 bits the file type.
// 1 = transition, 2 = emulation.
// Next 8 bits major version. This field will be increased if file format
// is changed incompatible with old programs.
// Next 8 bits is minor version. This field will be increased if file format
// changed but older programs should be able to use. Programs should
// handle increases in header size. New fields will be added after
// existing fields.
// Next 8 bits are unused and should be zero.
//
// Emulator file header format. See code for which fields are present
// in which file version.
// uint8_t[8] File id string 0xee 0x4d 0x46 0x4d 0x0d 0x0a 0x1a 0x00
// uint32_t File type and version. Current 0x02020200.
// uint32_t Offset to start of first track header in bytes.
// uint32_t Size of the data portion of each track in bytes.
// uint32_t Size of the header portion of each track in bytes.
// uint32_t Number of cylinders of track data in file.
// uint32_t Number of heads/tracks of data per cylinder.
// uint32_t Bit rate in Hz.
// uint32_t command line length in bytes
// uint8_t[n] Command line used to create emulator file. 0 terminated.
// uint32_t Note length in bytes
// uint8_t[n] Note string. 0 terminated.
// uint32_t Start of track time from index in nanoseconds
// Emulator file track header format
// uint32_t 0x12345678, Value to mark header.
// int32_t Cylinder number of track
// int32_t Head number of track
// Cylinder and head of -1 indicate no more data. No track data will
// follow end of file marker.
// Emulator file track data format
// uint32_t of MFM clock and data bits for length specified in file header.
// Bit 31 is the first bit.
//
// Transition file header format. See code for which fields are present
// in which file version.
// uint8_t[8] File id string 0xee 0x4d 0x46 0x4d 0x0a 0x1a 0x0a 0x0d
// uint32_t File type and version. Current 0x01020200
// uint32_t Offset to start of first track header in bytes.
// uint32_t Size of the header portion of each track in bytes.
// uint32_t Number of cylinders of track data in file.
// uint32_t Number of heads/tracks of data per cylinder.
// uint32_t Transition count rate in Hz. Only 200 MHz currently supported.
// uint32_t Length of command line used when capturing.
// uint8_t Command line used when capturing. Zero terminated.
// uint32_t Length of note option.
// uint8_t Note option text. Zero terminated.
// uint32_t Start time of data from index in nanoseconds.
// uint32_t Checksum of header. Calculated over bytes using crc64 in
// this program suite. Polynomial 0x140a0445 length 32 initial value
// 0xffffffff.
//
// Transition file track header format
// int32_t Cylinder number of track
// int32_t Head number of track
// uint32_t Number of bytes of transition data
// uint32_t Checksum of header and track data. Calculated over bytes
// using crc64 in this program suite. Polynomial 0x140a0445 length
// 32 initial value 0xffffffff.
// Cylinder and head of -1 indicate no more data.
//
// Transitions are count of clocks between one bits stored as bytes.
// A transition of 255 indicates the next 3 bytes are a 24 bit transition
// value. A transition value of 254 indicates the next 2 bytes are a 16
// bit transition value. Otherwise the transition count is a single byte.
// Clock transition count clock frequency is in file header. For 200 MHz
// a count of 40 indicates 5 MHz pulse spacing.
//
// 09/12/23 JST Changes to support 5.10 kernel and --sync option
// 04/14/19 DJG Pick correct RPM for SA1000 with 8.6 MHz clock rate
// 03/12/19 DJG Make tran_file_seek_track return EOF if cylinder or head
// value passed not valid for file
// 02/09/19 DJG Minor cleanup
// 09/04/16 DJG Fixed comment
// 12/31/15 DJG Added error check
// 12/24/15 DJG Cleanup and output valid MFM data if needing to pad track
// 05/16/15 DJG Added routines needed for analyze to work on transition and
// emulation files.
// 01/04/15 Added using sample rate field to emulation file. Added
// start_time_ns to emu file header and incremented file version. Allowed
// emulator track file length to be set.
// Added start_time_ns to transition file header and incremented file
// version.
// 10/19/14 DJG Added and modified functions to allow emulator file writes to
// be buffered
//
// Copyright 2013 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 "emu_tran_file.h"
#include "crc_ecc.h"
// Only for CLOCKS_TO_NS
#include "mfm_decoder.h"
// Maximum number of delta bytes in a track we support. For 60 RPM and
// 10 MHz rate should have 166666 deltas. For future RLL 50 RPM and 15 MHz
// should have 300000. Padded more.
#define MAX_BYTE_DELTAS 400000
// EMU track header marker
#define TRACK_ID_VALUE 0x12345678
// File header marker
uint8_t expected_header_id[] =
{ 0xee, 0x4d, 0x46, 0x4d, 0x0d, 0x0a, 0x1a, 0x00};
// File type 2, major version 1, minor version 2
#define EMU_FILE_VERSION 0x02020200
// File type 1, major version 2, minor version 2
#define TRAN_FILE_VERSION 0x01020200
// Size of end of file marker
#define TRAN_FILE_EOF_SIZE 12
#define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0]))
static CRC_INFO trans_initial_poly =
{ .poly = 0x140a0445,
.length = 32,
.init_value = 0xffffffff,
.ecc_max_span = 0
};
// Internal routines for reading and writing file.
// fd: File descriptor to read/write
// bytes: Data to read/write
// len: length of data in bytes
static void emu_file_write(int fd, void *bytes, int len) {
int rc;
if ((rc = write(fd, bytes, len)) != len) {
msg(MSG_FATAL, "Failed to write bytes to emulation file %d %s\n", rc,
rc == -1 ? strerror(errno): "");
exit(1);
}
}
static void emu_file_read(int fd, void *bytes, int len) {
int rc;
if ((rc = read(fd, bytes, len)) != len) {
msg(MSG_FATAL, "Failed to read bytes from emulation file %d %s\n", rc,
rc == -1 ? strerror(errno) : "");
exit(1);
}
}
// Find the desired cylinder and head in the transition file
// fd: File descriptor to read from
// seek_cyl: Cylinder number to find
// seek_head: head number to find
int emu_file_seek_track(int fd, int seek_cyl, int seek_head,
EMU_FILE_INFO *emu_file_info) {
off_t offset;
int track_size = (emu_file_info->track_data_size_bytes +
emu_file_info->track_header_size_bytes);
// If called with invalid head or cylinder return error
if (seek_head >= emu_file_info->num_head || seek_cyl >=
emu_file_info->num_cyl) {
return 1;
}
offset = (off_t) seek_cyl * track_size * emu_file_info->num_head +
seek_head * track_size + emu_file_info->file_header_size_bytes;
if (lseek(fd, offset , SEEK_SET) == -1) {
msg(MSG_FATAL, "Failed to seek in emulation file %s\n",
strerror(errno));
exit(1);
}
return 0;
}
// This calls bit read routine and then converts the bits into deltas.
// The deltas are returned assuming 200 MHz PRU sample clock for delta time.
//
// fd: File descriptor to read from
// emu_file_info: Information on emulator file format
// deltas: Output delta times between ones
// max_deltas: Size of deltas buffer in words
// cyl: Cylinder number of track read
// head: head number of track read
// start_time_ns: Start this number of nanoseconds into the emu file data
// wrapping around at end
// return: Number of deltas read in words. -1 if end of file found.
//TODO: mfm_read/mfm_emu don't set the PRU clock to the frequency to
// make deltas integer for bit rate like mfm_emu does. This should be
// fixed. This code takes into account the actual delta times vs the
// emu file clock rate such that the deltas match what would be
// captured with the 200 MHz PRU clock
int emu_file_read_track_deltas(int fd, EMU_FILE_INFO *emu_file_info,
uint16_t deltas[], int max_deltas, int *cyl, int *head)
{
uint32_t bits[MAX_TRACK_WORDS]; // Track bits read
int num_words; // Size of buffer and number of bytes read
int num_deltas; // Index of deltas building
int delta_time;
int wc, bc; // Word and bit counter
double bit_time = 0;
int delta;
if ((num_words = emu_file_read_track_bits(fd, emu_file_info, bits,
ARRAYSIZE(bits), cyl, head)) == -1) {
num_deltas = -1;
} else {
num_deltas = 0;
delta_time = 0;
for (wc = 0; wc < num_words; wc++) {
for (bc = 0; bc < 32; bc++) {
delta = rint(bit_time / CLOCKS_TO_NS);
delta_time += delta;
bit_time += 1e9 / emu_file_info->sample_rate_hz - delta * CLOCKS_TO_NS;
if (bits[wc] & 0x80000000) {
deltas[num_deltas++] = delta_time;
if (num_deltas >= max_deltas) {
msg(MSG_FATAL, "Emulation file delta overflow\n");
exit(1);
}
delta_time = 0;
}
bits[wc] <<= 1;
}
}
}
return num_deltas;
}
// Create or truncate file and write emulator file header.
//
// fn: File name to write to
// num_cyl: Number of cylinder file will contain
// num_head: Number of tracks per cylinder
// cmdline: Command line string to store in header
// note: Note to store in header
// sample_rate_hz: The MFM clock and data bit rate in Hertz
// start_time_ns: The time from index the data capture was started in nanosecond
// track_bytes: The size of each track data in bytes
// return: file descriptor of file opened
int emu_file_write_header(char *fn, int num_cyl, int num_head, char *cmdline,
char *note, uint32_t sample_rate_hz, uint32_t start_time_ns,
uint32_t track_bytes) {
uint32_t value;
int fd;
char *lcl_note = "";
int track_start;
// If note null store empty string
if (note != NULL) {
lcl_note = note;
}
fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd < 0) {
msg(MSG_FATAL, "Failed to open emulation file %s: %s\n",
fn, strerror(errno));
exit(1);
}
emu_file_write(fd, expected_header_id, sizeof(expected_header_id));
// File type 2, major version 1, minor version 1
value = EMU_FILE_VERSION;
emu_file_write(fd, &value, sizeof(value));
// Offset of first track header
value = sizeof(expected_header_id) + 4*10 + strlen(cmdline)+1 +
strlen(lcl_note)+1;
track_start = value;
emu_file_write(fd, &value, sizeof(value));
value = track_bytes;
emu_file_write(fd, &value, sizeof(value));
// Offset of track data from start of track
value = 4*3;
emu_file_write(fd, &value, sizeof(value));
value = num_cyl;
emu_file_write(fd, &value, sizeof(value));
value = num_head;
emu_file_write(fd, &value, sizeof(value));
value = sample_rate_hz;
emu_file_write(fd, &value, sizeof(value));
if (cmdline == NULL) {
msg(MSG_FATAL,"emu_file_write_header cmdline not specified\n");
exit(1);
}
// Write length including terminating null
value = strlen(cmdline)+1;
emu_file_write(fd, &value, sizeof(value));
emu_file_write(fd, cmdline, value);
value = strlen(lcl_note)+1;
emu_file_write(fd, &value, sizeof(value));
emu_file_write(fd, lcl_note, value);
value = start_time_ns;
emu_file_write(fd, &value, sizeof(value));
if (track_start != lseek(fd, 0, SEEK_CUR)) {
msg(MSG_FATAL, "Header size wrong, update code\n");
exit(1);
}
return fd;
}
// Read emulator file header.
//
// fn: File name to read header from
// emu_file_info: Information on emulator file format, set by this routine
// rewrite: Open file for reading and writing
// return: file descriptor of file opened
int emu_file_read_header(char *fn, EMU_FILE_INFO *emu_file_info,
int rewrite, int direct)
{
uint8_t header_id[sizeof(expected_header_id)];
uint32_t value;
int fd;
int header_left;
if (rewrite) {
if (direct) {
fd = open(fn, O_RDWR | O_DSYNC);
} else {
fd = open(fn, O_RDWR);
}
} else {
fd = open(fn, O_RDONLY);
}
if (fd < 0) {
msg(MSG_FATAL, "Failed to open emulation file %s: %s\n",
fn, strerror(errno));
exit(1);
}
emu_file_read(fd, header_id, sizeof(header_id));
if (memcmp(header_id, expected_header_id, sizeof(header_id)) != 0) {
msg(MSG_FATAL, "Emulation file doesn't have expected id value\n");
exit(1);
}
emu_file_read(fd, &value, sizeof(value));
emu_file_info->version = value;
if ((value & 0xff000000) != (EMU_FILE_VERSION & 0xff000000) ||
(value & 0xff0000) > (EMU_FILE_VERSION & 0xff0000)) {
msg(MSG_FATAL, "Emulation file incorrect type or higher revision than supported %x %x",
value, EMU_FILE_VERSION);
exit(1);
}
emu_file_read(fd, &value, sizeof(value));
emu_file_info->file_header_size_bytes = value;
emu_file_read(fd, &value, sizeof(value));
emu_file_info->track_data_size_bytes = value;
emu_file_read(fd, &value, sizeof(value));
emu_file_info->track_header_size_bytes = value;
emu_file_read(fd, &value, sizeof(value));
emu_file_info->num_cyl = value;
emu_file_read(fd, &value, sizeof(value));
emu_file_info->num_head = value;
emu_file_read(fd, &value, sizeof(value));
emu_file_info->sample_rate_hz = value;
// These fields only in later versions
emu_file_info->decode_cmdline = NULL;
emu_file_info->note = NULL;
if ((emu_file_info->version & 0xffff0000) >= 0x02020000) {
emu_file_read(fd, &value, sizeof(value));
emu_file_info->decode_cmdline = msg_malloc(value,
"emu_file_read decode_cmdline");
emu_file_read(fd, emu_file_info->decode_cmdline, value);
emu_file_read(fd, &value, sizeof(value));
emu_file_info->note = msg_malloc(value, "emu_file_read note");
emu_file_read(fd, emu_file_info->note, value);
}
emu_file_info->start_time_ns = 0;
if ((emu_file_info->version & 0xffffff00) >= 0x02020200) {
emu_file_read(fd, &value, sizeof(value));
emu_file_info->start_time_ns = value;
}
// If more data in header ignore it. This allows
// minor revisions to add additional fields and old programs still
// can process
header_left = emu_file_info->file_header_size_bytes - lseek(fd, 0, SEEK_CUR);
if (header_left > 0) {
char *ignore = msg_malloc(value, "emu_file_read ignore");
emu_file_read(fd, ignore, header_left);
free(ignore);
}
return fd;
}
// Write track header and bits
//
// fd: File descriptor to write to
// words: Words to write. Array will be filled to track_bytes length
// if shorter so must be large enough to hold track.
// num_words: Number of words to write
// cyl: Cylinder of track. Pass -1 to write end of file marker
// head: Head/track number
// track_bytes: The size of each track data in bytes.
void emu_file_write_track_bits(int fd, uint32_t *words, int num_words,
int cyl, int head, uint32_t track_bytes)
{
uint32_t value;
int i;
if (fd != -1) {
value = TRACK_ID_VALUE;
emu_file_write(fd, &value, sizeof(value));
value = cyl;
emu_file_write(fd, &value, sizeof(value));
value = head;
emu_file_write(fd, &value, sizeof(value));
// Cylinder -1 is end of file marker so don't write data. Otherwise
// pad with fill pattern or truncate if longer than track_bytes.
if (cyl != -1) {
uint32_t fill;
// Fill with valid MFM pattern
// Pick word that won't put two ones in a row
if (num_words == 0 || words[num_words-1] & 1) {
fill = 0x55555555;
} else {
fill = 0xaaaaaaaa;
}
for (i = num_words; i < track_bytes/4; i++) {
words[i] = fill;
}
emu_file_write(fd, words, track_bytes);
}
// Free pages written to disk. Improves write speed on fast USB flash
// but worse with internal flash
// posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
}
}
// Overwrite a track including header with new data
//
// fd: File descriptor to write to
// emu_file_info: Information on emulator file format
// cyl: Cylinder of track. Pass -1 to write end of file marker
// head: Head/track number
// buf: Buffer to write
// buf_size: Buffer size in bytes
void emu_file_rewrite_track(int fd, EMU_FILE_INFO *emu_file_info,
int cyl, int head, void *buf, int buf_size)
{
off_t offset;
int rc;
int track_size = emu_file_info->track_data_size_bytes +
emu_file_info->track_header_size_bytes;
int cyl_size = track_size * emu_file_info->num_head;
if (buf_size != track_size) {
msg(MSG_FATAL, "Emulation track buffer size mismatch %d %d\n",
buf_size, track_size);
exit(1);
}
offset = (off_t) cyl * cyl_size + head * track_size +
emu_file_info->file_header_size_bytes;
if ((rc = pwrite(fd, buf, track_size, offset)) != track_size) {
msg(MSG_FATAL, "Failed to write emulation track rc %d %s\n", rc,
rc == -1 ? strerror(errno): "");
exit(1);
}
}
// Read track header and bits
//
// fd: File descriptor to read from
// emu_file_info: Information on emulator file format
// words: Words to write
// num_words: Size of words buffer in words
// cyl: Returns cylinder of track read.
// head: Returns Head/track number
// return: number of words of track data read if OK, -1 if end of file found
int emu_file_read_track_bits(int fd, EMU_FILE_INFO *emu_file_info,
uint32_t *words, int num_words, int *cyl, int *head)
{
uint32_t value;
emu_file_read(fd, &value, sizeof(value));
if (value != TRACK_ID_VALUE) {
msg(MSG_FATAL, "Emulation file track id value mismatch %x %x\n",
value, TRACK_ID_VALUE);
exit(1);
}
emu_file_read(fd, &value, sizeof(value));
*cyl = value;
emu_file_read(fd, &value, sizeof(value));
*head = value;
if (*cyl == -1 && *head == -1) {
num_words = -1;
} else {
if (emu_file_info->track_data_size_bytes > num_words*4) {
msg(MSG_FATAL, "Emulation file track larger than buffer %d %d\n",
emu_file_info->track_data_size_bytes, num_words);
exit(1);
}
emu_file_read(fd, words, emu_file_info->track_data_size_bytes);
num_words = emu_file_info->track_data_size_bytes/4;
}
return num_words;
}
// Read cylinder of data including headers
//
// fd: File descriptor to read from
// emu_file_info: Information on emulator file format
// cyl: Cylinder to read
// buf: Buffer to read into
// buf_size: Buffer size in bytes
void emu_file_read_cyl(int fd, EMU_FILE_INFO *emu_file_info, int cyl,
void *buf, int buf_size) {
off_t offset;
int rc;
int cyl_size = emu_file_info->num_head *
(emu_file_info->track_data_size_bytes + emu_file_info->track_header_size_bytes);
if (buf_size < cyl_size) {
msg(MSG_FATAL, "Emulation cylinder buffer too small\n");
exit(1);
}
offset = (off_t) cyl * cyl_size + emu_file_info->file_header_size_bytes;
if ((rc = pread(fd, buf, cyl_size, offset)) != cyl_size) {
msg(MSG_FATAL, "Failed to read emulation cylinder rc %d %s\n", rc,
rc == -1 ? strerror(errno): "");
exit(1);
}
}
// Write cylinder of data including headers
//
// fd: File descriptor to write to
// emu_file_info: Information on emulator file format
// cyl: Cylinder to read
// buf: Buffer to write
// buf_size: Buffer size in bytes
void emu_file_write_cyl(int fd, EMU_FILE_INFO *emu_file_info, int cyl,
void *buf, int buf_size) {
off_t offset;
int rc;
int cyl_size = emu_file_info->num_head *
(emu_file_info->track_data_size_bytes + emu_file_info->track_header_size_bytes);
if (buf_size != cyl_size) {
msg(MSG_FATAL, "Emulation cylinder buffer size mismatch\n");
exit(1);
}
offset = (off_t) cyl * cyl_size + emu_file_info->file_header_size_bytes;
if ((rc = pwrite(fd, buf, cyl_size, offset)) != cyl_size) {
msg(MSG_FATAL, "Failed to write emulation cylinder rc %d %s\n", rc,
rc == -1 ? strerror(errno): "");
exit(1);
}
}
// Close emulator files
// fd: File descriptor to close
// write_eof: Write end of file mark before closing file
void emu_file_close(int fd, int write_eof)
{
if (fd != -1) {
if (write_eof) {
emu_file_write_track_bits(fd, NULL, 0, -1, -1, 0);
}
fsync(fd);
close(fd);
}
}
////// Transition file routines
// Internal routines for reading and writing file.
// fd: File descriptor to read/write
// bytes: Data to read/write
// len: length of data in bytes
// poly: Polynomial data for calculating checksum. CRC value is
// input and output in init_value field
static void tran_file_write(int fd, void *bytes, int len, CRC_INFO *poly) {
int rc;
if ((rc = write(fd, bytes, len)) != len) {
msg(MSG_FATAL, "Failed to write word to transition file %d %s\n", rc,
rc == -1 ? strerror(errno): "");
exit(1);
}
poly->init_value = crc64(bytes, len, poly);
}
static void tran_file_read(int fd,void *bytes, int len, CRC_INFO *poly) {
int rc;
if ((rc = read(fd, bytes, len)) != len) {
msg(MSG_FATAL, "Failed to read word from transition file %d %d %s\n", rc,
len, rc == -1 ? strerror(errno) : "");
exit(1);
}
poly->init_value = crc64(bytes, len, poly);
}
// Close transitions files
// fd: File descriptor to close
// write_eof: Write end of file mark before closing file
void tran_file_close(int fd, int write_eof)
{
if (fd != -1) {
if (write_eof) {
tran_file_write_track_deltas(fd, NULL, 0, -1, -1);
fsync(fd);
}
close(fd);
}
}
// Read emulator file header.
//
// fn: File name to read header from
// tran_file_info: Information from header
// return: file descriptor for file opened
int tran_file_read_header(char *fn, TRAN_FILE_INFO *tran_file_info)
{
uint8_t header_id[sizeof(expected_header_id)];
uint32_t value;
CRC_INFO poly = trans_initial_poly;
uint32_t crc;
int fd;
int header_left;
fd = open(fn, O_RDONLY);
if (fd < 0) {
msg(MSG_FATAL, "Failed to open transition file %s: %s\n",
fn, strerror(errno));
exit(1);
}
tran_file_read(fd, header_id, sizeof(header_id), &poly);
if (memcmp(header_id, expected_header_id, sizeof(header_id)) != 0) {
msg(MSG_FATAL, "Transition file doesn't have expected header id value\n");
exit(1);
}
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->version = value;
if ((value & 0xff000000) != (TRAN_FILE_VERSION & 0xff000000) ||
(value & 0xff0000) > (TRAN_FILE_VERSION & 0xff0000)) {
msg(MSG_FATAL, "Transition file incorrect type or higher revision than supported %x %x",
value, TRAN_FILE_VERSION);
exit(1);
}
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->file_header_size_bytes = value;
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->track_header_size_bytes = value;
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->num_cyl = value;
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->num_head = value;
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->sample_rate_hz = value;
if (tran_file_info->sample_rate_hz != 200000000) {
msg(MSG_FATAL, "Only transitions with sample rate of 200 MHz supported, got %d\n",
tran_file_info->sample_rate_hz);
exit(1);
}
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->decode_cmdline = msg_malloc(value,
"tran_file_read decode_cmdline");
tran_file_read(fd, tran_file_info->decode_cmdline, value, &poly);
// This field only in later versions
tran_file_info->note = NULL;
if ((tran_file_info->version & 0xffff0000) >= 0x01020000) {
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->note = msg_malloc(value, "tran_file_read note");
tran_file_read(fd, tran_file_info->note, value, &poly);
}
tran_file_info->start_time_ns = 0;
if ((tran_file_info->version & 0xffffff00) >= 0x01020200) {
tran_file_read(fd, &value, sizeof(value), &poly);
tran_file_info->start_time_ns = value;
}
// If more data than 4 byte checksum in header ignore it. This allows
// minor revisions to add additional fields and old programs still
// can process
header_left = tran_file_info->file_header_size_bytes - lseek(fd, 0, SEEK_CUR);
if (header_left > 4) {
char *ignore = msg_malloc(value, "tran_file_read ignore");
tran_file_read(fd, ignore, header_left, &poly);
free(ignore);
}
crc = poly.init_value;
tran_file_read(fd, &value, sizeof(value), &poly);
// CRC is calculated big endian so final CRC won't be zero so we compare
if (value != crc) {
msg(MSG_FATAL, "Transition file header CRC error %x %x\n", crc, value);
exit(1);
}
return fd;
}
// Create or truncate file and write transition file header.
//
// fn: File name to write to
// num_cyl: Number of cylinder file will contain
// num_head: Number of tracks per cylinder
// cmdline: Command line used for generation to store in file
// return: file descriptor for file opened
int tran_file_write_header(char *fn, int num_cyl, int num_head,
char *cmdline, char *note, uint32_t start_time_ns) {
uint32_t value;
CRC_INFO poly = trans_initial_poly;
int fd;
char *lcl_note = "";
int track_start;
// If note null store empty string
if (note != NULL) {
lcl_note = note;
}
fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd < 0) {
msg(MSG_FATAL, "Failed to open transition file %s: %s\n",
fn, strerror(errno));
exit(1);
}
tran_file_write(fd, expected_header_id, sizeof(expected_header_id), &poly);
value = TRAN_FILE_VERSION;
tran_file_write(fd, &value, sizeof(value), &poly);
// Offset of first track header
value = sizeof(expected_header_id) + 4*10 + strlen(cmdline)+1 +
strlen(lcl_note)+1;
track_start = value;
tran_file_write(fd, &value, sizeof(value), &poly);
// Offset of track data from start of track
value = 4*3;
tran_file_write(fd, &value, sizeof(value), &poly);
value = num_cyl;
tran_file_write(fd, &value, sizeof(value), &poly);
value = num_head;
tran_file_write(fd, &value, sizeof(value), &poly);
value = 200000000; // Sample rate in Hz
tran_file_write(fd, &value, sizeof(value), &poly);
if (cmdline == NULL) {
msg(MSG_FATAL,"tran_file_write_header cmdline not specified\n");
exit(1);
}
// write length including terminating null
value = strlen(cmdline)+1;
tran_file_write(fd, &value, sizeof(value), &poly);
tran_file_write(fd, cmdline, value, &poly);
value = strlen(lcl_note)+1;
tran_file_write(fd, &value, sizeof(value), &poly);
tran_file_write(fd, lcl_note, value, &poly);
value = start_time_ns; // Time offset in nanoseconds
tran_file_write(fd, &value, sizeof(value), &poly);
// Checksum
value = poly.init_value;
tran_file_write(fd, &value, sizeof(value), &poly);
if (track_start != lseek(fd, 0, SEEK_CUR)) {
msg(MSG_FATAL, "Header size wrong, update code\n");
exit(1);
}
return fd;
}
// Find the desired cylinder and head in the transition file
// fd: File descriptor to read from
// seek_cyl: Cylinder number to find
// seek_head: head number to find
// tran_file_info: Information on transition file
// return: 0 if track found else 1
int tran_file_seek_track(int fd, int seek_cyl, int seek_head,
TRAN_FILE_INFO *tran_file_info) {
int done = 0;
uint32_t cyl, head;
uint32_t num_bytes;
CRC_INFO poly = trans_initial_poly;
int rc = 0;
if (seek_head >= tran_file_info->num_head ||
seek_cyl >= tran_file_info->num_cyl) {
lseek(fd, -TRAN_FILE_EOF_SIZE, SEEK_END);
return 1;
} else {
lseek(fd, tran_file_info->file_header_size_bytes, SEEK_SET);
while (!done) {
tran_file_read(fd, &cyl, sizeof(cyl), &poly);
tran_file_read(fd, &head, sizeof(head), &poly);
tran_file_read(fd, &num_bytes, sizeof(num_bytes), &poly);
if (cyl == -1 && head == -1) {
msg(MSG_DEBUG, "Unable to find cylinder %d head %d\n",
seek_cyl, seek_head);
rc = 1;
done = 1;
}
if (cyl == seek_cyl && head == seek_head) {
done = 1;
lseek(fd, -4 * 3, SEEK_CUR);
} else {
if (lseek(fd, num_bytes + 4, SEEK_CUR) == -1) {
msg(MSG_FATAL, "tran_file_seek_track seek failed\n");
exit(1);
}
}
}
return rc;
}
}
// Read transition track and return as deltas
//
// fd: File descriptor to read from
// deltas: Deltas to return
// max_deltas: Size of deltas buffer in words
// cyl: Cylinder number of track read
// head: head number of track read
// return: Number of deltas read in words. -1 if end of file found.
int tran_file_read_track_deltas(int fd, uint16_t deltas[], int max_deltas,
int *cyl, int *head)
{
uint8_t deltas_in[MAX_BYTE_DELTAS];
uint32_t value;
CRC_INFO poly = trans_initial_poly;
int32_t num_bytes;
int rc;
int deltas_ndx = 0;
int i;
uint32_t crc;
tran_file_read(fd, &value, sizeof(value), &poly);
*cyl = value;
tran_file_read(fd, &value, sizeof(value), &poly);
*head = value;
tran_file_read(fd, &num_bytes, sizeof(num_bytes), &poly);
if (*cyl == -1 && *head == -1) {
rc = -1;
} else {
if (num_bytes > sizeof(deltas_in)) {
msg(MSG_FATAL, "Transition file track larger than buffer %d %d\n",
num_bytes, sizeof(deltas_in));
exit(1);
}
tran_file_read(fd, deltas_in, num_bytes, &poly);
i = 0;
while (i < num_bytes) {
if (deltas_in[i] == 255) {
value = deltas_in[i+1] | ((uint32_t) deltas_in[i+2] << 8) |
((uint32_t) deltas_in[i+3] << 16);
i += 4;
} else if (deltas_in[i] == 254) {
value = deltas_in[i+1] | ((uint32_t) deltas_in[i+2] << 8);
i += 3;
} else {
value = deltas_in[i++];
}
if (deltas_ndx >= max_deltas) {
msg(MSG_FATAL, "Transition file deltas overflow %d %d\n",
deltas_ndx, max_deltas);
exit(1);
}
deltas[deltas_ndx++] = value;
}
rc = deltas_ndx;
}
crc = poly.init_value;
tran_file_read(fd, &value, sizeof(value), &poly);
// CRC is calculated big endian so final CRC won't be zero so we compare
if (value != crc) {
msg(MSG_FATAL, "Transition file track CRC error %x %x\n", crc, value);
exit(1);
}
return rc;
}
// Write transition track from deltas
//
// fd: File descriptor to write to
// deltas: Deltas to write
// num_deltas: Number of deltas to write in words
// cyl: Cylinder number of track
// head: head number of track
void tran_file_write_track_deltas(int fd, uint16_t *deltas, int num_deltas, int cyl, int head)
{
uint8_t deltas_out[MAX_BYTE_DELTAS];
int deltas_ndx = 0;
uint32_t value;
int i;
CRC_INFO poly = trans_initial_poly;
if (fd == -1) {
return;
}
value = cyl;
tran_file_write(fd, &value, sizeof(value), &poly);
value = head;
tran_file_write(fd, &value, sizeof(value), &poly);
// Cylinder -1 is end of file marker. If these three fields changed
// TRAN_FILE_EOF_SIZE needs to be updated.
if (cyl == -1) {
value = 0; // Data length is zero
tran_file_write(fd, &value, sizeof(value), &poly);
} else {
for (i = 0; i < num_deltas; i++) {
if (deltas[i] == 0) {
printf("*** WRITING 0 delta at %d of %d\n", i, num_deltas);
}
// If greater than a byte or matches one of the size indicator
// values write it as multiple byte values with size indicator.
if (deltas[i] >= 254) {
if (deltas_ndx + 3 >= sizeof(deltas_out)) {
msg(MSG_FATAL, "Too many transitions %d\n",deltas_ndx);
exit(1);
}
// With 16 bit deltas this isn't possible. For future.
if (deltas[i] > 0xffff) {
deltas_out[deltas_ndx++] = 255;
value = deltas[i];
deltas_out[deltas_ndx++] = value;
deltas_out[deltas_ndx++] = value >> 8;
deltas_out[deltas_ndx++] = value >> 16;
} else {
deltas_out[deltas_ndx++] = 254;
value = deltas[i];
deltas_out[deltas_ndx++] = value;
deltas_out[deltas_ndx++] = value >> 8;
}
} else {
if (deltas_ndx >= sizeof(deltas_out)) {
msg(MSG_FATAL, "Too many transitions %d\n",deltas_ndx);
exit(1);
}
deltas_out[deltas_ndx++] = deltas[i];
}
}
// Write length and data
value = deltas_ndx;
tran_file_write(fd, &value, sizeof(value), &poly);
tran_file_write(fd, deltas_out, deltas_ndx, &poly);
}
value = poly.init_value;
tran_file_write(fd, &value, sizeof(value), &poly);
// Free pages written to disk. Improves write speed on fast USB flash
// but worse with internal flash
// posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
}
// Return rotations per second for emulatored drive.
//
// sample_rate_hz: Samples per second for MFM bits
//
float emu_rps(int sample_rate_hz) {
// If data rate about 8.68 MHz assume it is a SA1000 drive rotating
// at 3125 RPM otherwise 3600 RPM. Wang used 8.6 MHz to allow sufficient
// margin to cover both
if (abs(sample_rate_hz - 8680000) <= 90000) {
// 3125 RPM for Shugart. Quantum Q20x0 is 3000. No easy way to tell
// which. Will need to manually specify --track_words 5425 when
// creating emulation file for Quantum drive.
return 52.0833;
} else {
return 60.0;
}
}