summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Michael Zapf <Michael.Zapf@mizapf.de>2015-08-02 15:48:10 +0200
committer Michael Zapf <Michael.Zapf@mizapf.de>2015-08-02 15:50:22 +0200
commitbe6c3ee4c9b930450da916ea54cf5a78228dc8fe (patch)
treecdcfad7526c7eca5fd6b337b4a1b768a40269ab1
parent551c9f0788c4311163ad4678ae91b6af870281ae (diff)
mfmhd: Introduced format definition, now generally available.
-rw-r--r--scripts/src/emu.lua28
-rw-r--r--scripts/src/lib.lua2
-rw-r--r--scripts/src/machine.lua12
-rw-r--r--src/emu/bus/ti99_peb/hfdc.c4
-rw-r--r--src/emu/bus/ti99_peb/hfdc.h3
-rw-r--r--src/emu/bus/ti99_peb/tn_ide.c1
-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.h2
-rw-r--r--src/lib/formats/mfm_hd.c749
-rw-r--r--src/lib/formats/mfm_hd.h212
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