mirror of
https://github.com/holub/mame
synced 2025-06-30 16:00:01 +03:00
mfmhd: Introduced format definition, now generally available.
This commit is contained in:
parent
551c9f0788
commit
be6c3ee4c9
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -2560,18 +2560,6 @@ if (MACHINES["HDC9234"]~=null) then
|
||||
}
|
||||
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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
||||
|
@ -31,7 +31,6 @@
|
||||
#include "peribox.h"
|
||||
#include "machine/ataintf.h"
|
||||
#include "tn_ide.h"
|
||||
#include "machine/ti99_hd.h"
|
||||
|
||||
#define CRU_BASE 0x1000
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -2,130 +2,24 @@
|
||||
// copyright-holders:Michael Zapf
|
||||
/****************************************************************************
|
||||
|
||||
Hard disk support
|
||||
See mfm_hd.c for documentation
|
||||
MFM hard disk emulation
|
||||
|
||||
See mfmhd.c for documentation
|
||||
|
||||
Michael Zapf
|
||||
|
||||
February 2012: Rewritten as class
|
||||
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
|
@ -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;
|
||||
|
||||
|
749
src/lib/formats/mfm_hd.c
Normal file
749
src/lib/formats/mfm_hd.c
Normal file
@ -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;
|
||||
}
|
212
src/lib/formats/mfm_hd.h
Normal file
212
src/lib/formats/mfm_hd.h
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user