OXYGENHUB
◆ main openmsx / src / fdc / RawTrack.hh
6.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#ifndef RAWTRACK_HH
2
#define RAWTRACK_HH
3
4
#include "narrow.hh"
5
#include "serialize_meta.hh"
6
#include <cstdint>
7
#include <optional>
8
#include <span>
9
#include <vector>
10
11
namespace openmsx {
12
13
class CRC16;
14
15
// This class represents a raw disk track. It contains the logical sector
16
// content, but also address blocks, CRC checksums, sync blocks and the data
17
// in the gaps between these blocks.
18
//
19
// The internal representation is based on the DMK disk image file format. See:
20
//    http://www.trs-80.com/wordpress/dsk-and-dmk-image-utilities/
21
//    (at the bottom of the page)
22
//
23
// Besides the raw track data, this format also stores the positions of the
24
// 'address marks' in the track (this roughly corresponds with the start of a
25
// sector). Of course a real disk doesn't have such a list. In a real disk this
26
// information is stored as 'MFM encodings with missing clock transitions'.
27
//
28
// Normal MFM encoding goes like this: An input bit of '0' is encoded as 'x0'
29
// with x the inverse of the previously encoded bit. A '1' input bit is encoded
30
// as '01'. (More in detail: a '1' encoded bit indicates a magnetic flux
31
// transition, a '0' bit is no flux transition). So for example the input byte
32
// 0xA1 (binary 10100001) is MFM encoded as '01 00 01 00 10 10 10 01'. (Note
33
// that decoding is simply taking every 2nd bit (the data bits), the other bits
34
// (the clock bits) ensure that between encoded '1' bits is always at least 1
35
// and at most 3 zero bits. So no too dense flux transitions and not too far
36
// apart to keep the stream synchronized).
37
//
38
// Now for the missing clock transitions: besides the encodings for the 256
39
// possible input bytes, the FDC can write two other encodings, namely:
40
//   01 00 01 00 10 00 10 01  (0xA1 with missing clock between bit 4 and 5)
41
//   01 01 00 10 00 10 01 00  (0xC2 with missing clock between bit 3 and 4)
42
//
43
// So in principle we should store each of these special 0xA1 or 0xC2 bytes.
44
// Instead we only store the locations of '0xA1 0xA1 0xA1 0xFE' sequences (the
45
// 0xA1 bytes have missing clocks, this sequence indicates the start of an
46
// address mark). So we don't store the location of the special 0xC2 bytes, nor
47
// the location of each special 0xA1 byte. These certainly do occur on a real
48
// disk track, but the WD2793 controller only reacts to the full sequence
49
// above, so for us this is good enough. (The FDC also uses these special
50
// encodings to re-synchronize itself with the input stream, e.g. figure out
51
// which bit is the start bit in a byte, but from a functional emulation point
52
// of view we can ignore this).
53
//
54
// Also note that it's possible to create real disks that have still completely
55
// different magnetic flux patterns than the 256+2 possible MFM patterns
56
// described above. Such disks cannot be described by this class. But for
57
// openMSX that's not a problem because the WD2793 or TC8566AF disk controllers
58
// anyway can't handle such disks (they would always interpret the flux pattern
59
// as one of the 256+2 MFM patterns). Other systems (like Amiga) have disk
60
// controllers that allow more direct access to the disk and could for example
61
// encode the data in a more efficient way than MFM (e.g. GCR6). That's why
62
// Amiga can fit more data on the same disk (even more than simply storing 10
63
// or 11 sectors on a track by making the gaps between the sectors smaller).
64
65
class RawTrack {
66
public:
67
	// Typical track length is 6250 bytes:
68
	//    250kbps, 300rpm -> 6250 bytes per rotation.
69
	// The IBM Disk Format Specification confirms this number.
70
	// Of course this is in ideal circumstances: in reality the rotation
71
	// speed can vary and thus the disk can be formatted with slightly more
72
	// or slightly less raw bytes per track. This class can also represent
73
	// tracks of different lengths.
74
	static constexpr unsigned STANDARD_SIZE = 6250;
75
76
	struct Sector
77
	{
78
		// note: initialize to avoid UMR on savestate
79
		int addrIdx = 0;
80
		int dataIdx = 0;
81
		uint8_t track = 0;
82
		uint8_t head = 0;
83
		uint8_t sector = 0;
84
		uint8_t sizeCode = 0;
85
		bool deleted = false;
86
		bool addrCrcErr = false;
87
		bool dataCrcErr = false;
88
89
		template<typename Archive>
90
		void serialize(Archive& ar, unsigned version);
91
	};
92
93
	/* Construct a (cleared) track. */
94
	explicit RawTrack(unsigned size = STANDARD_SIZE);
95
96
	/** Clear track data. Also sets the track length. */
97
	void clear(unsigned size);
98
99
	/** Get track length. */
100
	[[nodiscard]] unsigned getLength() const { return unsigned(data.size()); }
101
102
	void addIdam(unsigned idx);
103
104
	// In the methods below, 'index' is allowed to be 'out-of-bounds',
105
	// it will wrap like in a circular buffer.
106
107
	[[nodiscard]] uint8_t read(int idx) const { return data[wrapIndex(idx)]; }
108
	void write(int idx, uint8_t val, bool setIdam = false);
109
	[[nodiscard]] int wrapIndex(int idx) const {
110
		// operator% is not a modulo but a remainder operation (makes a
111
		// difference for negative inputs). Hence the extra test.
112
		int size = narrow<int>(data.size());
113
		int tmp = idx % size;
114
		return (tmp >= 0) ? tmp : (tmp + size);
115
	}
116
117
	[[nodiscard]] std::span<      uint8_t> getRawBuffer()       { return data; }
118
	[[nodiscard]] std::span<const uint8_t> getRawBuffer() const { return data; }
119
	[[nodiscard]] const auto& getIdamBuffer() const { return idam; }
120
121
	/** Get info on all sectors in this track. */
122
	[[nodiscard]] std::vector<Sector> decodeAll() const;
123
124
	/** Get the next sector (starting from a certain index). */
125
	[[nodiscard]] std::optional<Sector> decodeNextSector(unsigned startIdx) const;
126
127
	/** Get a sector with a specific number.
128
	  * Note that if a sector with the same number occurs multiple times,
129
	  * this method will always return the same (the first) sector. So
130
	  * don't use it in the implementation of FDC / DiskDrive code.
131
	  */
132
	[[nodiscard]] std::optional<Sector> decodeSector(uint8_t sectorNum) const;
133
134
	/** Like memcpy() but copy from/to circular buffer. */
135
	void readBlock (int idx, std::span<uint8_t> destination) const;
136
	void writeBlock(int idx, std::span<const uint8_t> source);
137
138
	/** Convenience method to calculate CRC for part of this track. */
139
	[[nodiscard]] uint16_t calcCrc(int idx, int size) const;
140
	void updateCrc(CRC16& crc, int idx, int size) const;
141
142
	void applyWd2793ReadTrackQuirk();
143
144
	template<typename Archive>
145
	void serialize(Archive& ar, unsigned version);
146
147
private:
148
	[[nodiscard]] std::optional<Sector> decodeSectorImpl(int idx) const;
149
150
private:
151
	// Index into 'data'-array to positions where an address mark
152
	// starts (it points to the 'FE' byte in the 'A1 A1 A1 FE ..'
153
	// sequence.
154
	std::vector<unsigned> idam;
155
156
	// MFM-decoded raw data, this does NOT include the missing clock
157
	// transitions that can occur in the encodings of the 'A1' and
158
	// 'C2' bytes.
159
	std::vector<uint8_t> data;
160
};
161
SERIALIZE_CLASS_VERSION(RawTrack, 2);
162
163
} // namespace openmsx
164
165
#endif