diff options
| author | 2015-08-02 15:48:10 +0200 | |
|---|---|---|
| committer | 2015-08-02 15:50:22 +0200 | |
| commit | be6c3ee4c9b930450da916ea54cf5a78228dc8fe (patch) | |
| tree | cdcfad7526c7eca5fd6b337b4a1b768a40269ab1 /src/lib | |
| parent | 551c9f0788c4311163ad4678ae91b6af870281ae (diff) | |
mfmhd: Introduced format definition, now generally available.
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/formats/mfm_hd.c | 749 | ||||
| -rw-r--r-- | src/lib/formats/mfm_hd.h | 212 |
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 |
