summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib
diff options
context:
space:
mode:
author Michael Zapf <Michael.Zapf@mizapf.de>2015-08-02 15:48:10 +0200
committer Michael Zapf <Michael.Zapf@mizapf.de>2015-08-02 15:50:22 +0200
commitbe6c3ee4c9b930450da916ea54cf5a78228dc8fe (patch)
treecdcfad7526c7eca5fd6b337b4a1b768a40269ab1 /src/lib
parent551c9f0788c4311163ad4678ae91b6af870281ae (diff)
mfmhd: Introduced format definition, now generally available.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/formats/mfm_hd.c749
-rw-r--r--src/lib/formats/mfm_hd.h212
2 files changed, 961 insertions, 0 deletions
diff --git a/src/lib/formats/mfm_hd.c b/src/lib/formats/mfm_hd.c
new file mode 100644
index 00000000000..bd744f947ce
--- /dev/null
+++ b/src/lib/formats/mfm_hd.c
@@ -0,0 +1,749 @@
+// license:LGPL-2.1+
+// copyright-holders:Michael Zapf
+/*************************************************************************
+
+ Hard disk emulation: Format implementation
+ ------------------------------------------
+
+ This is the format implementation for MFM hard disks, similar to the
+ modular format concept of floppy drives in MAME/MESS.
+
+ The base class is mfmhd_image_format_t; it contains some methods for
+ encoding and decoding MFM. Although MFM hard disks should also be able to
+ manage FM recording, we do not plan for FM recording here.
+
+ The encode/decode methods rely on a parameter "encoding";
+ see imagedev/mfmhd.c for a discussion. Essentially, it determines whether
+ data are read bitwise or bytewise, and whether clock bits are separated
+ or interleaved.
+
+ The base class is abstract; you must create a subclass to use it. This
+ file delivers one subclass called mfmhd_generic_format.
+
+ In order to use this format, you must pass the creator identifier to the
+ macro MCFG_MFM_HARDDISK_CONN_ADD. See emu/bus/ti99_peb/hfdc.c for an
+ example.
+
+
+ Generic MFM format
+ ------------------
+ The heart of this class are the methods load and save. They are designed
+ to read sector data from a CHD file and reconstruct the track image (load),
+ or to take a track image, isolate the sector data, and store them
+ into the CHD (save).
+
+ Rebuilding the track image means to create sector headers, allocate gaps,
+ add sync areas, and CRC values. Also, the sectors must be arranged
+ according to the "interleave" parameter and the "skew" parameters for
+ heads and cylinders. While the skews are commonly set to 0, the interleave
+ is often used to fine-tune the transfer speed between the drive hardware
+ and the host system.
+
+ Also, the format allows for two header setups.
+ a) PC-AT-compatible header: four bytes long (ident, cylinder, head, sector);
+ the sector size is always 512 bytes.
+ b) Custom headers: five bytes long (..., sector size). The custom headers
+ are used in non-PC systems.
+
+ ECC: While floppy drives make use of a CRC field to check the data integrity,
+ hard disks use an ECC (error correcting code). The ECC length is 4 bytes
+ or longer, depending on the desired correction capability. The ECC length
+ can also be specified for this format.
+
+ However, for this version, we do not support ECC computation, but instead
+ we use CRC. This is indicated by setting the "ECC length" parameter to -1.
+
+ Format autodetect
+ -----------------
+ While formatting a hard disk, format parameters are likely to change, so
+ we have to find out about the new layout and store the metadata into the
+ CHD if they were modified.
+
+ This is done in the save method. This method does not only retrieve the
+ sector contents but also counts the gap bytes and sync bytes so that
+ they can be stored in the CHD.
+
+ - Interleave detection: save counts the number of sectors between sector
+ number n and sector number n+1.
+
+ - Skew detection: Skew is determined by three tracks: (cyl,head)=
+ (0,0), (1,0), and (0,1). For this purpose we use the m_secnumber list.
+
+ - Header length is detemined by the first sector on (0,0). This is done
+ by checking the header against the following two CRC bytes. If they
+ match for 4 bytes, we have an AT-style header, else a custom header.
+
+ - Gap and sync lengths are determined by the first track (0,0). They are
+ actually not expected to change, unless they are undefined before first
+ use, or the controller or its driver changes. We assume that track
+ (0,0) is actually rewritten during reformatting.
+
+ Since write precompensation and reduced write current cannot be seen
+ on the track image directly, those two values have to be set by the
+ hard disk device itseltf.
+
+ Inhibit autodetect
+ ------------------
+ In case we do not want the format to detect the layout but want to ensure
+ an immutable format, the save_param method may be overwritten to return
+ false for all or a particular group of parameters. The generic format
+ offers a save_param method which always returns true.
+
+ The effect of inhibiting the autodetection is that the layout parameters
+ as found on the CHD are used if available; otherwise defaults are used.
+
+ Defaults
+ --------
+ The generic format defines a method get_default which returns safe values
+ for layout parameters. It can be overwritten for specific formats.
+
+ Debugging
+ ---------
+ There is a set of debug flags (starting with TRACE_) that can be set to 1;
+ after recompiling you will get additional output. Since this class is not
+ a descendant of device_t we do not have a tag for output; for a better
+ overview in the logfile the hard disk device passes its tag to the base
+ class.
+
+
+ TODO
+ ----
+ - Add ECC computation
+
+
+ Michael Zapf
+ August 2015
+
+**************************************************************************/
+
+#include "emu.h"
+#include "mfm_hd.h"
+#include "imageutl.h"
+
+#define TRACE_RWTRACK 0
+#define TRACE_LAYOUT 0
+#define TRACE_IMAGE 0
+#define TRACE_DETAIL 0
+#define TRACE_FORMAT 1
+
+/*
+ Accept the new layout parameters and reset the sector number fields
+ used for skew calculation.
+*/
+void mfmhd_image_format_t::set_layout_params(mfmhd_layout_params param)
+{
+ m_param = m_param_old = param;
+ m_secnumber[0] = m_secnumber[1] = m_secnumber[2] = -1;
+}
+
+/*
+ Encode some value with data-type clock bits.
+*/
+void mfmhd_image_format_t::mfm_encode(UINT16* trackimage, int& position, UINT8 byte, int count)
+{
+ mfm_encode_mask(trackimage, position, byte, count, 0x00);
+}
+
+/*
+ Encode an A1 value with mark-type clock bits.
+*/
+void mfmhd_image_format_t::mfm_encode_a1(UINT16* trackimage, int& position)
+{
+ m_current_crc = 0xffff;
+ mfm_encode_mask(trackimage, position, 0xa1, 1, 0x04);
+}
+
+/*
+ Encode a byte value with a given clock bit mask. Used by both mfm_encode
+ and mfm_encode_a1 methods.
+*/
+void mfmhd_image_format_t::mfm_encode_mask(UINT16* trackimage, int& position, UINT8 byte, int count, int mask)
+{
+ UINT16 encclock = 0;
+ UINT16 encdata = 0;
+ UINT8 thisbyte = byte;
+ bool mark = (mask != 0x00);
+
+ m_current_crc = ccitt_crc16_one(m_current_crc, byte);
+
+ for (int i=0; i < 8; i++)
+ {
+ encdata <<= 1;
+ encclock <<= 1;
+
+ if (m_param.encoding == MFM_BITS || m_param.encoding == MFM_BYTE)
+ {
+ // skip one position for later interleaving
+ encdata <<= 1;
+ encclock <<= 1;
+ }
+
+ if (thisbyte & 0x80)
+ {
+ // Encoding 1 => 01
+ encdata |= 1;
+ m_lastbit = true;
+ }
+ else
+ {
+ // Encoding 0 => x0
+ // If the bit in the mask is set, suppress the clock bit
+ // Also, if we use the simplified encoding, don't set the clock bits
+ if (m_lastbit == false && m_param.encoding != SEPARATED_SIMPLE && (mask & 0x80) == 0) encclock |= 1;
+ m_lastbit = false;
+ }
+ mask <<= 1;
+ // For simplified encoding, set all clock bits to indicate a mark
+ if (m_param.encoding == SEPARATED_SIMPLE && mark) encclock |= 1;
+ thisbyte <<= 1;
+ }
+
+ if (m_param.encoding == MFM_BITS || m_param.encoding == MFM_BYTE)
+ encclock <<= 1;
+ else
+ encclock <<= 8;
+
+ trackimage[position++] = (encclock | encdata);
+
+ // When we write the byte multiple times, check whether the next encoding
+ // differs from the previous because of the last bit
+
+ if (m_param.encoding == MFM_BITS || m_param.encoding == MFM_BYTE)
+ {
+ encclock &= 0x7fff;
+ if ((byte & 0x80)==0 && m_lastbit==false) encclock |= 0x8000;
+ }
+
+ for (int j=1; j < count; j++)
+ {
+ trackimage[position++] = (encclock | encdata);
+ m_current_crc = ccitt_crc16_one(m_current_crc, byte);
+ }
+}
+
+/*
+ Decode an MFM cell pattern into a byte value.
+ Clock bits and data bits are assumed to be interleaved (cdcdcdcdcdcdcdcd);
+ the 8 data bits are returned.
+*/
+UINT8 mfmhd_image_format_t::mfm_decode(UINT16 raw)
+{
+ unsigned int value = 0;
+
+ for (int i=0; i < 8; i++)
+ {
+ value <<= 1;
+
+ value |= (raw & 0x4000);
+ raw <<= 2;
+ }
+ return (value >> 14) & 0xff;
+}
+
+/*
+ For debugging. Outputs the byte array in a xxd-like way.
+*/
+void mfmhd_image_format_t::showtrack(UINT16* enctrack, int length)
+{
+ for (int i=0; i < length; i+=16)
+ {
+ logerror("%07x: ", i);
+ for (int j=0; j < 16; j++)
+ {
+ logerror("%04x ", enctrack[i+j]);
+ }
+ logerror(" ");
+ logerror("\n");
+ }
+}
+
+// ======================================================================
+// Generic MFM HD format
+// ======================================================================
+
+const mfmhd_format_type MFMHD_GEN_FORMAT = &mfmhd_image_format_creator<mfmhd_generic_format>;
+
+/*
+ Calculate the ident byte from the cylinder. The specification does not
+ define idents beyond cylinder 1023, but formatting programs seem to
+ continue with 0xfd for cylinders between 1024 and 2047.
+*/
+UINT8 mfmhd_generic_format::cylinder_to_ident(int cylinder)
+{
+ if (cylinder < 256) return 0xfe;
+ if (cylinder < 512) return 0xff;
+ if (cylinder < 768) return 0xfc;
+ return 0xfd;
+}
+
+/*
+ Returns the linear sector number, given the CHS data.
+
+ C,H,S
+ | 0,0,0 | 0,0,1 | 0,0,2 | ...
+ | 0,1,0 | 0,1,1 | 0,1,2 | ...
+ ...
+ | 1,0,0 | ...
+ ...
+*/
+int mfmhd_generic_format::chs_to_lba(int cylinder, int head, int sector)
+{
+ if ((cylinder < m_param.cylinders) && (head < m_param.heads) && (sector < m_param.sectors_per_track))
+ {
+ return (cylinder * m_param.heads + head) * m_param.sectors_per_track + sector;
+ }
+ else return -1;
+}
+
+chd_error mfmhd_generic_format::load(chd_file* chdfile, UINT16* trackimage, int tracksize, int cylinder, int head)
+{
+ chd_error state = CHDERR_NONE;
+ UINT8 sector_content[16384];
+
+ int sectorcount = m_param.sectors_per_track;
+ int size = m_param.sector_size;
+ int position = 0; // will be incremented by each encode call
+ int sec_number = 0;
+ int identfield = 0;
+ int cylfield = 0;
+ int headfield = 0;
+ int sizefield = (size >> 7)-1;
+
+ // If we don't have interleave data in the CHD, take a default
+ if (m_param.interleave==0)
+ {
+ m_param.interleave = get_default(MFMHD_IL);
+ m_param.cylskew = get_default(MFMHD_CSKEW);
+ m_param.headskew = get_default(MFMHD_HSKEW);
+ }
+
+ int sec_il_start = (m_param.cylskew * cylinder + m_param.headskew * head) % sectorcount;
+ int delta = (sectorcount + m_param.interleave-1) / m_param.interleave;
+
+ if (TRACE_RWTRACK) logerror("%s: Load track (c=%d,h=%d) from CHD, interleave=%d, cylskew=%d, headskew=%d\n", tag(), cylinder, head, m_param.interleave, m_param.cylskew, m_param.headskew);
+
+ m_lastbit = false;
+
+ if (m_param.sync==0)
+ {
+ m_param.gap1 = get_default(MFMHD_GAP1);
+ m_param.gap2 = get_default(MFMHD_GAP2);
+ m_param.gap3 = get_default(MFMHD_GAP3);
+ m_param.sync = get_default(MFMHD_SYNC);
+ m_param.headerlen = get_default(MFMHD_HLEN);
+ m_param.ecctype = get_default(MFMHD_ECC);
+ }
+
+ // Gap 1
+ mfm_encode(trackimage, position, 0x4e, m_param.gap1);
+
+ if (TRACE_LAYOUT) logerror("%s: cyl=%d head=%d: sector sequence = ", tag(), cylinder, head);
+
+ sec_number = sec_il_start;
+ for (int sector = 0; sector < sectorcount; sector++)
+ {
+ if (TRACE_LAYOUT) logerror("%02d ", sec_number);
+
+ // Sync gap
+ mfm_encode(trackimage, position, 0x00, m_param.sync);
+
+ // Write IDAM
+ mfm_encode_a1(trackimage, position);
+
+ // Write header
+ identfield = cylinder_to_ident(cylinder);
+ cylfield = cylinder & 0xff;
+ headfield = head & 0x0f;
+ if (m_param.headerlen==5)
+ headfield |= ((cylinder & 0x700)>>4);
+
+ mfm_encode(trackimage, position, identfield);
+ mfm_encode(trackimage, position, cylfield);
+ mfm_encode(trackimage, position, headfield);
+ mfm_encode(trackimage, position, sec_number);
+ if (m_param.headerlen==5)
+ mfm_encode(trackimage, position, sizefield);
+
+ // Write CRC for header.
+ int crc = m_current_crc;
+ mfm_encode(trackimage, position, (crc >> 8) & 0xff);
+ mfm_encode(trackimage, position, crc & 0xff);
+
+ // Gap 2
+ mfm_encode(trackimage, position, 0x4e, m_param.gap2);
+
+ // Sync
+ mfm_encode(trackimage, position, 0x00, m_param.sync);
+
+ // Write DAM
+ mfm_encode_a1(trackimage, position);
+ mfm_encode(trackimage, position, 0xfb);
+
+ // Get sector content from CHD
+ int lbaposition = chs_to_lba(cylinder, head, sec_number);
+ if (lbaposition>=0)
+ {
+ chd_error state = chdfile->read_units(lbaposition, sector_content);
+ if (state != CHDERR_NONE) break;
+ }
+ else
+ {
+ logerror("%s: Invalid CHS data (%d,%d,%d); not loading from CHD\n", tag(), cylinder, head, sector);
+ }
+
+ for (int i=0; i < size; i++)
+ mfm_encode(trackimage, position, sector_content[i]);
+
+ // Write CRC for content.
+ crc = m_current_crc;
+ mfm_encode(trackimage, position, (crc >> 8) & 0xff);
+ mfm_encode(trackimage, position, crc & 0xff);
+
+ // Gap 3
+ mfm_encode(trackimage, position, 0x00, 3);
+ mfm_encode(trackimage, position, 0x4e, m_param.gap3-3);
+
+ // Calculate next sector number
+ sec_number += delta;
+ if (sec_number >= sectorcount)
+ {
+ sec_il_start = (sec_il_start+1) % delta;
+ sec_number = sec_il_start;
+ }
+ }
+ if (TRACE_LAYOUT) logerror("\n");
+
+ // Gap 4
+ if (state == CHDERR_NONE)
+ {
+ // Fill the rest with 0x4e
+ mfm_encode(trackimage, position, 0x4e, tracksize-position);
+ if (TRACE_IMAGE) showtrack(trackimage, tracksize);
+ }
+ return state;
+}
+
+/*
+ State names for analyzing the track image.
+*/
+enum
+{
+ SEARCH_A1=0,
+ FOUND_A1,
+ DAM_FOUND,
+ CHECK_CRC
+};
+
+chd_error mfmhd_generic_format::save(chd_file* chdfile, UINT16* trackimage, int tracksize, int current_cylinder, int current_head)
+{
+ if (TRACE_RWTRACK) logerror("%s: write back (c=%d,h=%d) to CHD\n", tag(), current_cylinder, current_head);
+
+ UINT8 buffer[16384]; // for header or sector content
+
+ int bytepos = 0;
+ int state = SEARCH_A1;
+ int count = 0;
+ int pos = 0;
+ UINT16 crc = 0;
+ UINT8 byte;
+ bool search_header = true;
+
+ int ident = 0;
+ int cylinder = 0;
+ int head = 0;
+ int sector = 0;
+ int size = 0;
+
+ int headerpos = 0;
+
+ int interleave = 0;
+ int interleave_prec = -1;
+ bool check_interleave = true;
+ bool check_skew = true;
+
+ int gap1 = 0;
+ int ecctype = 0;
+
+ // if (current_cylinder==0 && current_head==0) showtrack(trackimage, tracksize);
+
+ // If we want to detect gaps, we only do it on cylinder 0, head 0
+ // This makes it safer to detect the header length
+ // (There is indeed some chance that we falsely assume a header length of 4
+ // because the two bytes behind happen to be a valid CRC value)
+ if (save_param(MFMHD_GAP1) && current_cylinder==0 && current_head==0)
+ {
+ m_param.gap1 = 0;
+ m_param.gap2 = 0;
+ m_param.gap3 = 0;
+ m_param.sync = 0;
+ // 4-byte headers are used for the IBM-AT format
+ // 5-byte headers are used in other formats
+ m_param.headerlen = 4;
+ m_param.ecctype = 0;
+ }
+
+ // AT format implies 512 bytes per sector
+ int sector_length = 512;
+
+ // Only check once
+ bool countgap1 = (m_param.gap1==0);
+ bool countgap2 = false;
+ bool countgap3 = false;
+ bool countsync = false;
+
+ chd_error chdstate = CHDERR_NONE;
+
+ if (TRACE_IMAGE)
+ {
+ for (int i=0; i < tracksize; i++)
+ {
+ if ((i % 16)==0) logerror("\n%04x: ", i);
+ logerror("%02x ", (m_param.encoding==MFM_BITS || m_param.encoding==MFM_BYTE)? mfm_decode(trackimage[i]) : (trackimage[i]&0xff));
+ }
+ logerror("\n");
+ }
+
+ // We have to go through the bytes of the track and save a sector as soon as one shows up
+
+ while (bytepos < tracksize)
+ {
+ // Decode the next 16 bits
+ if (m_param.encoding==MFM_BITS || m_param.encoding==MFM_BYTE)
+ {
+ byte = mfm_decode(trackimage[bytepos]);
+ }
+ else byte = (trackimage[bytepos] & 0xff);
+
+ switch (state)
+ {
+ case SEARCH_A1:
+ // Counting gaps and sync
+ if (countgap2)
+ {
+ if (byte == 0x4e) m_param.gap2++;
+ else if (byte == 0) { countsync = true; countgap2 = false; }
+ }
+
+ if (countsync)
+ {
+ if (byte == 0) m_param.sync++;
+ else countsync = false;
+ }
+
+ if (countgap3)
+ {
+ if (byte != 0x00 || m_param.gap3 < 4) m_param.gap3++;
+ else countgap3 = false;
+ }
+
+ if (((m_param.encoding==MFM_BITS || m_param.encoding==MFM_BYTE) && trackimage[bytepos]==0x4489)
+ || (m_param.encoding==SEPARATED && trackimage[bytepos]==0x0aa1)
+ || (m_param.encoding==SEPARATED_SIMPLE && trackimage[bytepos]==0xffa1))
+ {
+ state = FOUND_A1;
+ count = (search_header? m_param.headerlen : (sector_length+1)) + 2;
+ crc = 0x443b; // init value with a1
+ pos = 0;
+ }
+ bytepos++;
+ break;
+
+ case FOUND_A1:
+ crc = ccitt_crc16_one(crc, byte);
+ // logerror("%s: MFM HD: Byte = %02x, CRC=%04x\n", tag(), byte, crc);
+
+ // Put byte into buffer
+ // but not the data mark and the CRC
+ if (search_header || (count > 2 && count < sector_length+3)) buffer[pos++] = byte;
+
+ // Stop counting gap1
+ if (search_header && countgap1)
+ {
+ gap1 = bytepos-1;
+ countgap1 = false;
+ }
+
+ if (--count == 0)
+ {
+ if (crc==0)
+ {
+ if (search_header)
+ {
+ // Found a header
+ ident = buffer[0];
+ cylinder = buffer[1];
+ // For non-PC-AT formats, highest three bits are in the head field
+ if (m_param.headerlen == 5) cylinder |= ((buffer[2]&0x70)<<4);
+ else
+ {
+ logerror("%s: Unexpected header size: %d, cylinder=%d, position=%04x\n", tag(), m_param.headerlen, cylinder, bytepos);
+ showtrack(trackimage, tracksize);
+ }
+
+ head = buffer[2] & 0x0f;
+ sector = buffer[3];
+ int identexp = cylinder_to_ident(cylinder);
+
+ if (identexp != ident)
+ {
+ logerror("%s: Field error; ident = %02x (expected %02x) for sector chs=(%d,%d,%d)\n", tag(), ident, identexp, cylinder, head, sector);
+ }
+
+ if (cylinder != current_cylinder)
+ {
+ logerror("%s: Sector header of sector %d defines cylinder = %02x (should be %02x)\n", tag(), sector, cylinder, current_cylinder);
+ }
+
+ if (head != current_head)
+ {
+ logerror("%s: Sector header of sector %d defines head = %02x (should be %02x)\n", tag(), sector, head, current_head);
+ }
+
+ // Check skew
+ // We compare the beginning of this track with the track on the next head and the track on the next cylinder
+ if (check_skew && cylinder < 2 && head < 2)
+ {
+ m_secnumber[cylinder*2 + head] = sector;
+ check_skew=false;
+ }
+
+ // Count the sectors for the interleave
+ if (check_interleave)
+ {
+ if (interleave_prec == -1) interleave_prec = sector;
+ else
+ {
+ if (sector == interleave_prec+1) check_interleave = false;
+ interleave++;
+ }
+ }
+
+ if (interleave == 0) interleave = sector - buffer[3];
+
+ // When we have 4-byte headers, the sector length is 512 bytes
+ if (m_param.headerlen == 5)
+ {
+ size = buffer[4];
+ sector_length = 128 << (size&0x07);
+ ecctype = (size&0xf0)>>4;
+ }
+
+ search_header = false;
+ if (TRACE_DETAIL) logerror("%s: Found sector chs=(%d,%d,%d)\n", tag(), cylinder, head, sector);
+ headerpos = pos;
+ // Start the GAP2 counter (if not already determined)
+ if (m_param.gap2==0) countgap2 = true;
+ }
+ else
+ {
+ // Sector contents
+ // Write the sectors to the CHD
+ int lbaposition = chs_to_lba(cylinder, head, sector);
+ if (lbaposition>=0)
+ {
+ if (TRACE_DETAIL) logerror("%s: Writing sector chs=(%d,%d,%d) to CHD\n", tag(), current_cylinder, current_head, sector);
+ chdstate = chdfile->write_units(chs_to_lba(current_cylinder, current_head, sector), buffer);
+
+ if (chdstate != CHDERR_NONE)
+ {
+ logerror("%s: Write error while writing sector chs=(%d,%d,%d)\n", tag(), cylinder, head, sector);
+ }
+ }
+ else
+ {
+ logerror("%s: Invalid CHS data in track image: (%d,%d,%d); not saving to CHD\n", tag(), cylinder, head, sector);
+ }
+ if (m_param.gap3==0) countgap3 = true;
+ search_header = true;
+ }
+ }
+ else
+ {
+ // Let's test for a 5-byte header
+ if (search_header && m_param.headerlen==4 && current_cylinder==0 && current_head==0)
+ {
+ if (TRACE_DETAIL) logerror("%s: CRC error for 4-byte header; trying 5 bytes\n", tag());
+ m_param.headerlen=5;
+ count = 1;
+ bytepos++;
+ break;
+ }
+ else
+ {
+ logerror("%s: CRC error in %s of (%d,%d,%d)\n", tag(), search_header? "header" : "data", cylinder, head, sector);
+ search_header = true;
+ }
+ }
+ // search next A1
+ state = SEARCH_A1;
+
+ if (!search_header && (pos - headerpos) > 30)
+ {
+ logerror("%s: Error; missing DAM; searching next header\n", tag());
+ search_header = true;
+ }
+ }
+ bytepos++;
+ break;
+ }
+ }
+
+ if (check_interleave == false && save_param(MFMHD_IL))
+ {
+ // Successfully determined the interleave
+ m_param.interleave = interleave;
+ if (TRACE_FORMAT) logerror("%s: Determined interleave = %d\n", tag(), m_param.interleave);
+ }
+
+ if (check_skew == false)
+ {
+ if (m_secnumber[0] != -1)
+ {
+ if (m_secnumber[1] != -1)
+ {
+ if (save_param(MFMHD_HSKEW)) m_param.headskew = m_secnumber[1]-m_secnumber[0];
+ if (TRACE_FORMAT) logerror("%s: Determined head skew = %d\n", tag(), m_param.headskew);
+ }
+ if (m_secnumber[2] != -1)
+ {
+ if (save_param(MFMHD_CSKEW)) m_param.cylskew = m_secnumber[2]-m_secnumber[0];
+ if (TRACE_FORMAT) logerror("%s: Determined cylinder skew = %d\n", tag(), m_param.cylskew);
+ }
+ }
+ }
+
+ gap1 -= m_param.sync;
+ ecctype = -1; // lock to CRC until we have a support for ECC
+
+ if (current_cylinder==0 && current_head==0)
+ {
+ // If we want to detect gaps, store the new value into the param object
+ // The other gaps have already been written directly to the param object above,
+ // unless save_param returned false (or we were not on cylinder 0, head 0)
+ if (save_param(MFMHD_GAP1)) m_param.gap1 = gap1;
+ if (save_param(MFMHD_ECC)) m_param.ecctype = ecctype;
+ }
+ return chdstate;
+}
+
+/*
+ Deliver default values.
+*/
+int mfmhd_generic_format::get_default(mfmhd_param_t type)
+{
+ switch (type)
+ {
+ case MFMHD_IL: return 4;
+ case MFMHD_HSKEW:
+ case MFMHD_CSKEW: return 0;
+ case MFMHD_WPCOM: // Write precompensation cylinder (-1 = none)
+ case MFMHD_RWC: return -1; // Reduced write current cylinder (-1 = none)
+ case MFMHD_GAP1: return 16;
+ case MFMHD_GAP2: return 3;
+ case MFMHD_GAP3: return 18;
+ case MFMHD_SYNC: return 13;
+ case MFMHD_HLEN: return 5;
+ case MFMHD_ECC: return -1; // -1: use CRC instead of ECC
+ }
+ return -1;
+}
diff --git a/src/lib/formats/mfm_hd.h b/src/lib/formats/mfm_hd.h
new file mode 100644
index 00000000000..b779b40be63
--- /dev/null
+++ b/src/lib/formats/mfm_hd.h
@@ -0,0 +1,212 @@
+// license:LGPL-2.1+
+// copyright-holders:Michael Zapf
+/****************************************************************************
+
+ Hard disk support
+ See mfm_hd.c for documentation
+
+ Michael Zapf
+
+ February 2012: Rewritten as class
+
+*****************************************************************************/
+
+#ifndef __MFMHDFMT__
+#define __MFMHDFMT__
+
+#include "emu.h"
+#include "chd.h"
+
+const chd_metadata_tag MFM_HARD_DISK_METADATA_TAG = CHD_MAKE_TAG('G','D','D','I');
+
+extern const char *MFMHD_REC_METADATA_FORMAT;
+extern const char *MFMHD_GAP_METADATA_FORMAT;
+
+/*
+ Determine how data are passed from the hard disk to the controller. We
+ allow for different degrees of hardware emulation.
+*/
+enum mfmhd_enc_t
+{
+ MFM_BITS, // One bit at a time
+ MFM_BYTE, // One data byte with interleaved clock bits
+ SEPARATED, // 8 clock bits (most sig byte), one data byte (least sig byte)
+ SEPARATED_SIMPLE // MSB: 00/FF (standard / mark) clock, LSB: one data byte
+};
+
+class mfmhd_image_format_t;
+
+// Pointer to its alloc function
+typedef mfmhd_image_format_t *(*mfmhd_format_type)();
+
+template<class _FormatClass>
+mfmhd_image_format_t *mfmhd_image_format_creator()
+{
+ return new _FormatClass();
+}
+
+/*
+ Parameters for the track layout
+*/
+class mfmhd_layout_params
+{
+public:
+ // Geometry params. These are fixed for the device. However, sector sizes
+ // could be changed, but we do not support this (yet). These are defined
+ // in the CHD and must match those of the device. They are stored by the GDDD tag.
+ // The encoding is not stored in the CHD but is also supposed to be immutable.
+ int cylinders;
+ int heads;
+ int sectors_per_track;
+ int sector_size;
+ mfmhd_enc_t encoding;
+
+ // Parameters like interleave, precompensation, write current can be changed
+ // on every write operation. They are stored by the GDDI tag (first record).
+ int interleave;
+ int cylskew;
+ int headskew;
+ int write_precomp_cylinder; // if -1, no wpcom on the disks
+ int reduced_wcurr_cylinder; // if -1, no rwc on the disks
+
+ // Parameters for the track layout that are supposed to be the same for
+ // all tracks and that do not change (until the next reformat).
+ // Also, they do not have any influence on the CHD file.
+ // They are stored by the GDDI tag (second record).
+ int gap1;
+ int gap2;
+ int gap3;
+ int sync;
+ int headerlen;
+ int ecctype; // -1 is CRC
+
+ bool sane_rec()
+ {
+ return ((interleave > 0 && interleave < 32) && (cylskew >= 0 && cylskew < 32) && (headskew >= 0 && headskew < 32)
+ && (write_precomp_cylinder >= -1 && write_precomp_cylinder < 100000)
+ && (reduced_wcurr_cylinder >= -1 && reduced_wcurr_cylinder < 100000));
+ }
+
+ void reset_rec()
+ {
+ interleave = cylskew = headskew = 0;
+ write_precomp_cylinder = reduced_wcurr_cylinder = -1;
+ }
+
+ bool sane_gap()
+ {
+ return ((gap1 >= 1 && gap1 < 1000) && (gap2 >= 1 && gap2 < 20) && (gap3 >= 1 && gap3 < 1000)
+ && (sync >= 10 && sync < 20)
+ && (headerlen >= 4 && headerlen<=5) && (ecctype>=-1 && ecctype < 10));
+ }
+
+ void reset_gap()
+ {
+ gap1 = gap2 = gap3 = sync = headerlen = ecctype = 0;
+ }
+
+ bool equals_rec(mfmhd_layout_params* other)
+ {
+ return ((interleave == other->interleave) &&
+ (cylskew == other->cylskew) &&
+ (headskew == other->headskew) &&
+ (write_precomp_cylinder == other->write_precomp_cylinder) &&
+ (reduced_wcurr_cylinder == other->reduced_wcurr_cylinder));
+ }
+
+ bool equals_gap(mfmhd_layout_params* other)
+ {
+ return ((gap1 == other->gap1) &&
+ (gap2 == other->gap2) &&
+ (gap3 == other->gap3) &&
+ (sync == other->sync) &&
+ (headerlen == other->headerlen) &&
+ (ecctype == other->ecctype));
+ }
+};
+
+enum mfmhd_param_t
+{
+ MFMHD_IL,
+ MFMHD_HSKEW,
+ MFMHD_CSKEW,
+ MFMHD_WPCOM,
+ MFMHD_RWC,
+ MFMHD_GAP1,
+ MFMHD_GAP2,
+ MFMHD_GAP3,
+ MFMHD_SYNC,
+ MFMHD_HLEN,
+ MFMHD_ECC
+};
+
+/*
+ Hard disk format
+*/
+class mfmhd_image_format_t
+{
+public:
+ mfmhd_image_format_t() { m_devtag = "mfmhd_image_format_t"; };
+ virtual ~mfmhd_image_format_t() {};
+
+ // Load the image.
+ virtual chd_error load(chd_file* chdfile, UINT16* trackimage, int tracksize, int cylinder, int head) = 0;
+
+ // Save the image.
+ virtual chd_error save(chd_file* chdfile, UINT16* trackimage, int tracksize, int cylinder, int head) = 0;
+
+ // Return the original parameters of the image
+ mfmhd_layout_params* get_initial_params() { return &m_param_old; }
+
+ // Return the recent parameters of the image
+ mfmhd_layout_params* get_current_params() { return &m_param; }
+
+ // Set the track layout parameters (and reset the skew detection values)
+ void set_layout_params(mfmhd_layout_params param);
+
+ // Concrete format shall decide whether we want to save the retrieved parameters or not.
+ virtual bool save_param(mfmhd_param_t type) =0;
+
+ // Accept a tag for log output, since this is not a device instance
+ void set_tag(const char* tag) { m_devtag = tag; }
+
+protected:
+ bool m_lastbit;
+ int m_current_crc;
+ int m_secnumber[4]; // used to determine the skew values
+ const char* m_devtag;
+
+ mfmhd_layout_params m_param, m_param_old;
+
+ void mfm_encode(UINT16* trackimage, int& position, UINT8 byte, int count=1);
+ void mfm_encode_a1(UINT16* trackimage, int& position);
+ void mfm_encode_mask(UINT16* trackimage, int& position, UINT8 byte, int count, int mask);
+ UINT8 mfm_decode(UINT16 raw);
+
+ // Deliver defaults.
+ virtual int get_default(mfmhd_param_t type) =0;
+
+ // Debugging
+ void showtrack(UINT16* enctrack, int length);
+ const char* tag() { return m_devtag; }
+};
+
+class mfmhd_generic_format : public mfmhd_image_format_t
+{
+public:
+ mfmhd_generic_format() { m_devtag = "mfmhd_generic_format"; };
+ chd_error load(chd_file* chdfile, UINT16* trackimage, int tracksize, int cylinder, int head);
+ chd_error save(chd_file* chdfile, UINT16* trackimage, int tracksize, int cylinder, int head);
+
+ // Yes, we want to save all parameters
+ virtual bool save_param(mfmhd_param_t type) { return true; }
+ virtual int get_default(mfmhd_param_t type);
+
+protected:
+ virtual UINT8 cylinder_to_ident(int cylinder);
+ virtual int chs_to_lba(int cylinder, int head, int sector);
+};
+
+extern const mfmhd_format_type MFMHD_GEN_FORMAT;
+
+#endif