diff options
-rw-r--r-- | scripts/src/emu.lua | 28 | ||||
-rw-r--r-- | scripts/src/lib.lua | 2 | ||||
-rw-r--r-- | scripts/src/machine.lua | 12 | ||||
-rw-r--r-- | src/emu/bus/ti99_peb/hfdc.c | 4 | ||||
-rw-r--r-- | src/emu/bus/ti99_peb/hfdc.h | 3 | ||||
-rw-r--r-- | src/emu/bus/ti99_peb/tn_ide.c | 1 | ||||
-rw-r--r-- | src/emu/imagedev/mfmhd.c (renamed from src/emu/machine/ti99_hd.c) | 901 | ||||
-rw-r--r-- | src/emu/imagedev/mfmhd.h (renamed from src/emu/machine/ti99_hd.h) | 206 | ||||
-rw-r--r-- | src/emu/machine/hdc9234.h | 2 | ||||
-rw-r--r-- | src/lib/formats/mfm_hd.c | 749 | ||||
-rw-r--r-- | src/lib/formats/mfm_hd.h | 212 |
11 files changed, 1261 insertions, 859 deletions
diff --git a/scripts/src/emu.lua b/scripts/src/emu.lua index 26c5f9edd28..38ce3c40a16 100644 --- a/scripts/src/emu.lua +++ b/scripts/src/emu.lua @@ -144,7 +144,7 @@ files { MAME_DIR .. "src/emu/output.c", MAME_DIR .. "src/emu/output.h", MAME_DIR .. "src/emu/render.c", - MAME_DIR .. "src/emu/render.h", + MAME_DIR .. "src/emu/render.h", MAME_DIR .. "src/emu/rendfont.c", MAME_DIR .. "src/emu/rendfont.h", MAME_DIR .. "src/emu/rendlay.c", @@ -216,7 +216,7 @@ files { MAME_DIR .. "src/emu/validity.h", MAME_DIR .. "src/emu/video.c", MAME_DIR .. "src/emu/video.h", - MAME_DIR .. "src/emu/rendersw.inc", + MAME_DIR .. "src/emu/rendersw.inc", MAME_DIR .. "src/emu/debug/debugcmd.c", MAME_DIR .. "src/emu/debug/debugcmd.h", MAME_DIR .. "src/emu/debug/debugcon.c", @@ -296,6 +296,8 @@ files { MAME_DIR .. "src/emu/imagedev/floppy.h", MAME_DIR .. "src/emu/imagedev/harddriv.c", MAME_DIR .. "src/emu/imagedev/harddriv.h", + MAME_DIR .. "src/emu/imagedev/mfmhd.c", + MAME_DIR .. "src/emu/imagedev/mfmhd.h", MAME_DIR .. "src/emu/imagedev/midiin.c", MAME_DIR .. "src/emu/imagedev/midiin.h", MAME_DIR .. "src/emu/imagedev/midiout.c", @@ -340,12 +342,12 @@ dependency { { MAME_DIR .. "src/emu/rendlay.c", GEN_DIR .. "emu/layout/noscreens.lh" }, { MAME_DIR .. "src/emu/video.c", GEN_DIR .. "emu/layout/snap.lh" }, - + } custombuildtask { { MAME_DIR .. "src/emu/uismall.png" , GEN_DIR .. "emu/uismall.fh", { MAME_DIR.. "src/build/png2bdc.py", MAME_DIR .. "src/build/file2str.py" }, {"@echo Converting uismall.png...", PYTHON .. " $(1) $(<) temp.bdc", PYTHON .. " $(2) temp.bdc $(@) font_uismall UINT8" }}, - + layoutbuildtask("emu/layout", "dualhovu"), layoutbuildtask("emu/layout", "dualhsxs"), layoutbuildtask("emu/layout", "dualhuov"), @@ -400,17 +402,17 @@ function emuProject(_target, _subtarget) MAME_DIR .. "3rdparty/lua/src", } end - + dofile(path.join("src", "cpu.lua")) dofile(path.join("src", "sound.lua")) - - + + dofile(path.join("src", "video.lua")) dofile(path.join("src", "machine.lua")) -if (_OPTIONS["DRIVERS"] == nil) then +if (_OPTIONS["DRIVERS"] == nil) then project ("bus") uuid ("5d782c89-cf7e-4cfe-8f9f-0d4bfc16c91d") kind (LIBTYPE) @@ -451,11 +453,11 @@ if (_OPTIONS["DRIVERS"] == nil) then else dofile(path.join("src", "bus.lua")) end - - -- netlist now defines a project + + -- netlist now defines a project dofile(path.join("src", "netlist.lua")) - + project ("dasm") uuid ("f2d28b0a-6da5-4f78-b629-d834aa00429d") kind (LIBTYPE) @@ -487,10 +489,10 @@ end MAME_DIR .. "3rdparty/lua/src", } end - + files { disasm_files - } + } if #disasm_dependency > 0 then dependency { diff --git a/scripts/src/lib.lua b/scripts/src/lib.lua index b445f71a994..7d21cf3e5a2 100644 --- a/scripts/src/lib.lua +++ b/scripts/src/lib.lua @@ -296,6 +296,8 @@ project "formats" MAME_DIR .. "src/lib/formats/msx_dsk.h", MAME_DIR .. "src/lib/formats/mfi_dsk.c", MAME_DIR .. "src/lib/formats/mfi_dsk.h", + MAME_DIR .. "src/lib/formats/mfm_hd.c", + MAME_DIR .. "src/lib/formats/mfm_hd.h", MAME_DIR .. "src/lib/formats/mz_cas.c", MAME_DIR .. "src/lib/formats/mz_cas.h", MAME_DIR .. "src/lib/formats/nanos_dsk.c", diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 55467f69264..e040fb0781a 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -2562,18 +2562,6 @@ end --------------------------------------------------- -- ---@src/emu/machine/ti99_hd.h,MACHINES["TI99_HD"] = true ---------------------------------------------------- - -if (MACHINES["TI99_HD"]~=null) then - files { - MAME_DIR .. "src/emu/machine/ti99_hd.c", - MAME_DIR .. "src/emu/machine/ti99_hd.h", - } -end - ---------------------------------------------------- --- --@src/emu/machine/strata.h,MACHINES["STRATA"] = true --------------------------------------------------- diff --git a/src/emu/bus/ti99_peb/hfdc.c b/src/emu/bus/ti99_peb/hfdc.c index 9ad1d2d11e0..3ade598a448 100644 --- a/src/emu/bus/ti99_peb/hfdc.c +++ b/src/emu/bus/ti99_peb/hfdc.c @@ -57,7 +57,7 @@ #include "emu.h" #include "peribox.h" #include "hfdc.h" -#include "machine/ti99_hd.h" +#include "formats/mfm_hd.h" #include "formats/ti99_dsk.h" // Format #define BUFFER "ram" @@ -1011,7 +1011,7 @@ static SLOT_INTERFACE_START( hfdc_floppies ) SLOT_INTERFACE_END static SLOT_INTERFACE_START( hfdc_harddisks ) - SLOT_INTERFACE( "generic", MFMHD_GENERIC ) // Generic high-level emulation + SLOT_INTERFACE( "generic", MFMHD_GENERIC ) // Generic hard disk (self-adapting to image) SLOT_INTERFACE( "st213", MFMHD_ST213 ) // Seagate ST-213 (10 MB) SLOT_INTERFACE( "st225", MFMHD_ST225 ) // Seagate ST-225 (20 MB) SLOT_INTERFACE( "st251", MFMHD_ST251 ) // Seagate ST-251 (40 MB) diff --git a/src/emu/bus/ti99_peb/hfdc.h b/src/emu/bus/ti99_peb/hfdc.h index d27a9f4c8bc..062a2617070 100644 --- a/src/emu/bus/ti99_peb/hfdc.h +++ b/src/emu/bus/ti99_peb/hfdc.h @@ -17,9 +17,10 @@ #define __HFDC__ #include "imagedev/floppy.h" +#include "imagedev/mfmhd.h" + #include "machine/mm58274c.h" #include "machine/hdc9234.h" -#include "machine/ti99_hd.h" extern const device_type TI99_HFDC; diff --git a/src/emu/bus/ti99_peb/tn_ide.c b/src/emu/bus/ti99_peb/tn_ide.c index c073f3443a7..10e69ace518 100644 --- a/src/emu/bus/ti99_peb/tn_ide.c +++ b/src/emu/bus/ti99_peb/tn_ide.c @@ -31,7 +31,6 @@ #include "peribox.h" #include "machine/ataintf.h" #include "tn_ide.h" -#include "machine/ti99_hd.h" #define CRU_BASE 0x1000 diff --git a/src/emu/machine/ti99_hd.c b/src/emu/imagedev/mfmhd.c index bd82309fa51..e852e011482 100644 --- a/src/emu/machine/ti99_hd.c +++ b/src/emu/imagedev/mfmhd.c @@ -2,40 +2,288 @@ // copyright-holders:Michael Zapf /************************************************************************* - Hard disk emulation + MFM Hard disk emulation + ----------------------- + + This is a low-level emulation of a hard disk drive. Unlike high-level + emulations which just deliver the data bytes, this implementation + considers all bytes on a track, including gaps, CRC, interleave, and more. + + The actual data are stored on a CHD file, however, only as a sequence + of sector contents. The other metadata (like gap information, interleave) + are stored as metadata information in the CHD. + + To provide the desired low-level emulation grade, the tracks must be + reconstructed from the sector contents in the CHD. This is done in + the call_load method. + + Usually, more than one sector of a track is read, so when a track is + reconstructed, the track image is retained for later accesses. + + This implementation also features a LRU cache for the track images, + implemented by the class mfmhd_trackimage_cache, also contained in this + source file. The LRU cache stores the most recently accessed track images. + When lines must be evicted, they are stored back into the CHD file. This + is done in the call_unload method. Beside the sector contents, call_unload + also saves track layout metadata which have been detected during usage. + + The architecture can be imagined like this: + + [host system] ---- [controller] --- [harddisk] --- [track cache] + | + [format] ---- [CHD] + + Encodings + --------- + The goal of this implementation is to provide an emulation that is very + close to the original, similar to the grade achieved for the floppy + emulation. This means that the track image does not contain a byte + sequence but a sequence of MFM cell values. Unlike the floppy emulation, + we do not define the cells by time intervals but simply by a sequence of + bits which represent the MFM cell contents; this is also due to the fact + that the cell rate is more than 10 times higher than with floppy media. + + There are four options for encodings which differ by overhead and + emulation precision. + + - MFM_BITS: MFM cells are transferred bit by bit for reading and writing + - MFM_BYTE: MFM cells are transferred in clusters of 16 cells, thus + encoding a full data byte (with 8 clock bits interleaved) + - SEPARATED: 16 bits are transferred in one go, with the 8 clock bits in the + first byte, and the 8 data bits in the second byte + - SEPARATED_SIMPLE: Similar to SEPARATED, but instead of the clock bits, + 0x00 is used for normal data, and 0xff is used for marks. + + Following the specification of MFM, the data byte 0x67 is encoded as follows: + + MFM_BITS / MFM_BYTE: 1001010010010101 = 0x9495 + SEPARATED: 10001000 01100111 = 0x8867 + SEPARATED_SIMPLE: 00000000 01100111 = 0x0067 + + The ID Address Mark (0xA1) is encoded this way: + + MFM_BITS / MFM_BYTE: 0100010010001001 = 0x4489 + SEPARATED: 00001010 10100001 = 0x0AA1 + SEPARATED_SIMPLE: 11111111 10100001 = 0xFFA1 + + If the CPU load by the emulation is already very high, the + SEPARATED(_SIMPLE) options are recommended. The MFM_BITS option is closest + to the real processing, but causes a high load. MFM_BYTE is a good + compromise between speed and precision. + + + Drive definition + ---------------- + Hard disk drives are defined by subclassing mfm_harddisk_device, as can + be seen for the offered Seagate drive implementations. + + The following parameters must be set in the constructor of the subclass: + + - Number of physical cylinders. These can be more than the number of + cylinders that are used for data. Some drives have cylinders near the + spindle that are used as park positions. + + - Number of cylinders used for data. This is the number of the highest + cylinder plus 1 (counting from 0). + + - Landing zone: Cylinder number where the head is parked. Should be higher + than the number of usable cylinders. + + - Heads: Number of heads. + + - Time for one cylinder seek step in milliseconds: This is the time the + drive heads need to step one cylinder inwards or outwards. This time + includes the settling time. + + - Maximum seek time in milliseconds: This is the time the drive needs to + seek from cylinder 0 to the maximum cylinder. This time includes the + settling time and is typically far less than the one cylinder time + multiplied by the number of cylinders, because the settling time only + occurs once. These delay values are calculated in call_load. + + If the number of physical cylinders is set to 0, the cylinder and head + count in taken from the metadata of the mounted CHD file. This allows for + using all kinds of CHD images that can be handled by the controller, + without having to define a proper drive for them. + + The predefined drives are + + ST-213: Seagate hard disk drive, 10 MB capacity + ST-225: Seagate hard disk drive, 20 MB capacity + ST-251: Seagate hard disk drive, 40 MB capacity + + generic: Hard disk with 0 physical cylinders, which can be used for + all CHDs that can be handled by the controller. + + The ST-xxx drives require to mount a CHD that exactly matches their + geometry. + + + Track image cache + ----------------- + Since the reconstruction of the track takes some time, and we don't want + to create unnecessary effort, track images (sector contents plus all + preambles and gaps encoded as selected) are kept in a cache. Whenever a + track shall be read, the cache is consulted first to retrieve a copy. + If no recent copy is available, the track is loaded from the CHD, set up + by the format implementation (see lib/formats/mfm_hd.c). The least + recently used track is evicted from the cache and written back to the CHD + (also by means of the format implementation). + + When the emulation is stopped, all cache lines are evicted and written back. + If the emulation is killed before, cache contents may possibly not be + written back, so changes may be lost. To alleviate this issue, the cache + writes back one line every 5 seconds so that changes are automatically + committed after some time. + + This cache is not related to caches on real hard drives. It is a pure + emulation artifact, intended to keep conversion efforts as low as possible. + + + Interface + --------- + There are three outgoing lines, used as callbacks to the controller: + - READY: asserted when the drive has completed its spinup. + - INDEX: asserted when the index hole passes by. Unlike the floppy + implementation, this hard disk implementation produces a + zero-length pulse (assert/clear). This must be considered for + the controller emulation. + - SEEK COMPLETE: asserted when the read/write heads have settled over the + target cylinder. This line is important for controller that want + to employ buffered steps. + + There are two data transfer methods: + + - read(attotime &from_when, const attotime &limit, UINT16 &data) + + Delivers the MFM cells at the given point in time. The cells are returned + in the data parameter. The behavior depends on the chosen encoding: + + MFM_BITS: data contains 0x0000 or 0x0001 + MFM_BYTE: data contains a set of 16 consecutive cells at the given time + SEPARATED: data contains the clock bits in the MSB, the data bits in the LSB + SEPARATED_SIMPLE: data contains 0x00 or 0xFF in the MSB (normal or mark) + and the data bits in the LSB. + + When the limit is exceeded, the method returns true, otherwise false. + + - write(attotime &from_when, const attotime &limit, UINT16 cdata, bool wpcom=false, bool reduced_wc=false) + + Writes the MFM cells at the given point in time. cdata contains the + cells according to the encoding (see above). The controller also has + to set wpcom to true to indicate write precompensation, and reduced_wc + to true to indicate a reduced write current; otherwise, these settings + are assumed to be false. The wpcom and rwc settings do not affect the + recording of the data bytes in this emulation, but the drive will store + the cylinder with the lowest number where wpcom (or rwc) occured and + store this in the CHD. + + These methods are used to move and select the heads: + + - step_w(line_state line) + - direction_in_w(line_state line) + - headsel_w(int head) + + Some status lines: + + - ready_r + - seek_complete_r + - trk00_r + + These reflect the values that are also passed by the callback routines + listed above and can be used for a polling scheme. Track00 is not available + as a callback. It indicates whether track 0 has been reached. + + + Configuration + ------------- + For a working example please refer to emu/bus/ti99_peb/hfdc.c. + + According to the MAME/MESS concept of slot devices, the settings are + passed over the slot to the slot device, in this case, the hard disk drive. + + This means that when we add a slot (the connector), we also have to + pass the desired parameters for the drive. + + MCFG_MFM_HARDDISK_CONN_ADD(_tag, _slot_intf, _def_slot, _enc, _spinupms, _cache, _format) + + Specific parameters: + + _enc: Select an encoding from the values as listed above. + _spinupms: Number of milliseconds until the drive announces READY. Many + drives like the included Seagate drives require a pretty long + powerup time (10-20 seconds). In some computer systems, the + user is therefore asked to turn on the drive first so that + on first access by the system, the drive may have completed + its powerup. In MAME/MESS we cannot turn on components earlier, + thus we do not define the spinup time inside the drive + implementation but at this point. + _cache: Number of tracks to be stored in the LRU track cache + _format: Format to be used for the drive. Must be a subclass of + mfmhd_image_format_t, for example, mfmhd_generic_format. The format + is specified by its format creator identifier (e.g. MFMHD_GEN_FORMAT). + + Metadata + -------- + We have three sets of metadata information. The first one is the + declaration of cylinders, heads, sectors, and sector size. It is stored + by the tag GDDD in the CHD. + The second is the declaration of interleave, skew, write precompensation, + and reduced write current. + + Write precompensation is a modification of the timing used for the inner + cylinders. Although write precompensation (wpcom for short) can be applied + at every write operation, it is usually only used starting from some + cylinder, going towards the spindle, applied to the whole tracks. + The value defined here is the first cylinder where wpcom is applied. + Reduced write current (rwc) is a modification of the electrical current + used for writing data. The value defined here is the first cylinder where + rwc is applied. + + Both wpcom and rwc have an effect on the physical device, but this is not + emulated. For that reason we store the information as additional metadata + inside the CHD. It is not relevant for the functionality of the emulated + hard disk. The write operations + + When wpcom or rwc are not used, their value is defined to be -1. + + Interleave affects the order how sectors are arranged on the track; skew + is the number of sectors that the sector sequence is shifted on the + next cylinder (cylinder skew) or head (head skew). These values are used + to compensate for the delay that occurs when the read/write heads are moved + from one cylinder to the next, or switched from one head to the next. + + These parameters are stored by the tag GDDI on the CHD. + + The third set refers to the specification of gaps and sync fields on the + track. These values may change only on first use (when undefined) or when + the hard disk is reformatted with a different controller or driver. These + parameters are also stored by the GDDI tag as a second record. + Michael Zapf - April 2010 - February 2012: Rewritten as class - April 2015: Rewritten with deeper emulation detail + August 2015 References: [1] ST225 OEM Manual, Seagate **************************************************************************/ -// TODO: Define format by a file - #include "emu.h" #include "formats/imageutl.h" #include "harddisk.h" - -#include "ti99_hd.h" +#include "mfmhd.h" #define TRACE_STEPS 0 #define TRACE_SIGNALS 0 #define TRACE_READ 0 #define TRACE_WRITE 0 #define TRACE_CACHE 0 -#define TRACE_RWTRACK 0 #define TRACE_BITS 0 #define TRACE_DETAIL 0 #define TRACE_TIMING 0 -#define TRACE_IMAGE 0 #define TRACE_STATE 1 #define TRACE_CONFIG 1 -#define TRACE_LAYOUT 0 -#define TRACE_FORMAT 1 enum { @@ -52,10 +300,6 @@ enum STEP_SETTLE }; -#define TRACKSLOTS 5 - -#define OFFLIMIT -1 - std::string mfm_harddisk_device::tts(const attotime &t) { char buf[256]; @@ -69,7 +313,7 @@ mfm_harddisk_device::mfm_harddisk_device(const machine_config &mconfig, device_t device_slot_card_interface(mconfig, *this) { m_spinupms = 10000; - m_cachelines = TRACKSLOTS; + m_cachelines = 5; m_max_cylinders = 0; m_phys_cylinders = 0; // We will get this value for generic drives from the image m_max_heads = 0; @@ -132,6 +376,12 @@ void mfm_harddisk_device::device_stop() bool mfm_harddisk_device::call_load() { bool loaded = harddisk_image_device::call_load(); + + std::string devtag(tag()); + devtag += ":format"; + + m_format->set_tag(devtag.c_str()); + if (loaded==IMAGE_INIT_PASS) { std::string metadata; @@ -924,620 +1174,3 @@ void mfm_harddisk_connector::device_config_complete() } const device_type MFM_HD_CONNECTOR = &device_creator<mfm_harddisk_connector>; - -// ================================================================ - -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; -} - -void mfmhd_image_format_t::mfm_encode(UINT16* trackimage, int& position, UINT8 byte, int count) -{ - mfm_encode_mask(trackimage, position, byte, count, 0x00); -} - -void mfmhd_image_format_t::mfm_encode_a1(UINT16* trackimage, int& position) -{ - m_current_crc = 0xffff; - mfm_encode_mask(trackimage, position, 0xa1, 1, 0x04); - // 0x443b; CRC for A1 -} - -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); - } -} - -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"); - } -} - -// ====================================================================== -// General MFM HD format -// ====================================================================== - - // According to MDM5 formatting: - // gap0=16 gap1=16 gap2=3 gap3=22 sync=13 count=32 size=2 - - // HFDC manual: When using the hard disk format, the values for GAP0 and GAP1 must - // both be set to the same number and loaded in the appropriate registers. - - -/* - Concerning pre-comp and reduced wc: - The MDM5 formatter allows for specifying the cylinders for precompensation and reduced write current - Default: red_wc = precomp_cyl = bottom(cylinders / 21) * 16 - - Cylinder = 0 ... precomp_cyl-1: Format command = 0x62, Write command = 0xc0 - Cylinder = precomp_cyl ... end: Format command = 0x62, Write command = 0xca - - (format command unaffected; could be a bug) - - C0: no current reduction, disable EARLY/LATE - CA: reduced write current, enable EARLY/LATE -*/ - -const mfmhd_format_type MFMHD_GEN_FORMAT = &mfmhd_image_format_creator<gen_mfmhd_format>; - -enum -{ - SEARCH_A1=0, - FOUND_A1, - DAM_FOUND, - CHECK_CRC -}; - -/* - 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 gen_mfmhd_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 gen_mfmhd_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 gen_mfmhd_format::load(chd_file* chdfile, UINT16* trackimage, int tracksize, int cylinder, int head) -{ - chd_error state = CHDERR_NONE; - UINT8 sector_content[1024]; // TODO: should be prepared for max 16K - - 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("gen_mfmhd_format: MFM HD cache: load track (c=%d,h=%d) from CHD, interleave=%d, cylskew=%d, headskew=%d\n", 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("gen_mfmhd_format: cyl=%d head=%d: sector sequence = ", 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("gen_mfmhd_format: Invalid CHS data (%d,%d,%d); not loading from CHD\n", 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; -} - -chd_error gen_mfmhd_format::save(chd_file* chdfile, UINT16* trackimage, int tracksize, int current_cylinder, int current_head) -{ - if (TRACE_CACHE) logerror("gen_mfmhd_format: write back (c=%d,h=%d) to CHD\n", current_cylinder, current_head); - - UINT8 buffer[1024]; // 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("gen_mfmhd_format: Unexpected header size: %d, cylinder=%d, position=%04x\n", 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("gen_mfmhd_format: Field error; ident = %02x (expected %02x) for sector chs=(%d,%d,%d)\n", ident, identexp, cylinder, head, sector); - } - - if (cylinder != current_cylinder) - { - logerror("gen_mfmhd_format: Sector header of sector %d defines cylinder = %02x (should be %02x)\n", sector, cylinder, current_cylinder); - } - - if (head != current_head) - { - logerror("gen_mfmhd_format: Sector header of sector %d defines head = %02x (should be %02x)\n", 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("gen_mfmhd_format: Found sector chs=(%d,%d,%d)\n", 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("gen_mfmhd_format: Writing sector chs=(%d,%d,%d) to CHD\n", current_cylinder, current_head, sector); - chdstate = chdfile->write_units(chs_to_lba(current_cylinder, current_head, sector), buffer); - - if (chdstate != CHDERR_NONE) - { - logerror("gen_mfmhd_format: Write error while writing sector chs=(%d,%d,%d)\n", cylinder, head, sector); - } - } - else - { - logerror("gen_mfmhd_format: Invalid CHS data in track image: (%d,%d,%d); not saving to CHD\n", 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("gen_mfmhd_format: CRC error for 4-byte header; trying 5 bytes\n"); - m_param.headerlen=5; - count = 1; - bytepos++; - break; - } - else - { - logerror("gen_mfmhd_format: CRC error in %s of (%d,%d,%d)\n", search_header? "header" : "data", cylinder, head, sector); - search_header = true; - } - } - // search next A1 - state = SEARCH_A1; - - if (!search_header && (pos - headerpos) > 30) - { - logerror("gen_mfmhd_format: Error; missing DAM; searching next header\n"); - search_header = true; - } - } - bytepos++; - break; - } - } - - if (check_interleave == false && save_param(MFMHD_IL)) - { - // Successfully determined the interleave - m_param.interleave = 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("gen_mfmhd_format: Determined head skew = %d\n", 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("gen_mfmhd_format: Determined cylinder skew = %d\n", 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 (save_param(MFMHD_GAP1)) m_param.gap1 = gap1; - if (save_param(MFMHD_ECC)) m_param.ecctype = ecctype; - } - return chdstate; -} - -int gen_mfmhd_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: - case MFMHD_RWC: return -1; - 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; - } - return -1; -} diff --git a/src/emu/machine/ti99_hd.h b/src/emu/imagedev/mfmhd.h index 200c53e4bde..c15b892423d 100644 --- a/src/emu/machine/ti99_hd.h +++ b/src/emu/imagedev/mfmhd.h @@ -2,130 +2,24 @@ // copyright-holders:Michael Zapf /**************************************************************************** - Hard disk support - See mfm_hd.c for documentation + MFM hard disk emulation - Michael Zapf + See mfmhd.c for documentation - February 2012: Rewritten as class + Michael Zapf + August 2015 *****************************************************************************/ -#ifndef __TI99_HD__ -#define __TI99_HD__ +#ifndef __MFMHD__ +#define __MFMHD__ #include "emu.h" #include "imagedev/harddriv.h" +#include "formats/mfm_hd.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; class mfm_harddisk_device; -// 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)); - } -}; - class mfmhd_trackimage { public: @@ -150,7 +44,6 @@ public: private: mfm_harddisk_device* m_mfmhd; mfmhd_trackimage* m_tracks; - void showtrack(UINT16* enctrack, int length); }; class mfm_harddisk_device : public harddisk_image_device, @@ -181,9 +74,6 @@ public: line_state seek_complete_r() { return m_seek_complete? ASSERT_LINE : CLEAR_LINE; } ; line_state trk00_r() { return m_current_cylinder==0? ASSERT_LINE : CLEAR_LINE; } - // Common routine for read/write - bool find_position(attotime &from_when, const attotime &limit, int &bytepos, int &bitpos); - // Data output towards controller bool read(attotime &from_when, const attotime &limit, UINT16 &data); @@ -200,7 +90,7 @@ public: bool call_load(); void call_unload(); - // Tells us the time when the track ends (next index pulse) + // Tells us the time when the track ends (next index pulse). Needed by the controller. attotime track_end_time(); // Access the tracks on the image. Used as a callback from the cache. @@ -266,6 +156,9 @@ private: void prepare_track(int cylinder, int head); void head_move(); void recalibrate(); + + // Common routine for read/write + bool find_position(attotime &from_when, const attotime &limit, int &bytepos, int &bitpos); }; /* @@ -354,81 +247,4 @@ extern const device_type MFM_HD_CONNECTOR; static_cast<mfm_harddisk_connector *>(device)->configure(_enc, _spinupms, _cache, _format); -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() {}; - 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; - -protected: - bool m_lastbit; - int m_current_crc; - int m_secnumber[4]; // used to determine the skew values - - 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); - void showtrack(UINT16* enctrack, int length); - - // Deliver defaults. - virtual int get_default(mfmhd_param_t type) =0; -}; - -class gen_mfmhd_format : public mfmhd_image_format_t -{ -public: - gen_mfmhd_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); - -private: - UINT8 cylinder_to_ident(int cylinder); - int chs_to_lba(int cylinder, int head, int sector); -}; - -extern const mfmhd_format_type MFMHD_GEN_FORMAT; - #endif diff --git a/src/emu/machine/hdc9234.h b/src/emu/machine/hdc9234.h index e10293af006..bedd98e3a9b 100644 --- a/src/emu/machine/hdc9234.h +++ b/src/emu/machine/hdc9234.h @@ -9,8 +9,8 @@ #include "emu.h" #include "imagedev/floppy.h" +#include "imagedev/mfmhd.h" #include "fdc_pll.h" -#include "ti99_hd.h" extern const device_type HDC9234; 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 |