diff options
Diffstat (limited to 'src/lib/formats/mfm_hd.c')
-rw-r--r-- | src/lib/formats/mfm_hd.c | 750 |
1 files changed, 750 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..ed50e60deb2 --- /dev/null +++ b/src/lib/formats/mfm_hd.c @@ -0,0 +1,750 @@ +// 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 0 + +/* + 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) + if (current_cylinder==0 && current_head==0) 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; +} |