mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-24 11:11:02 -07:00
252 lines
6.9 KiB
C++
252 lines
6.9 KiB
C++
#include "lib/core/globals.h"
|
|
#include "lib/decoders/decoders.h"
|
|
#include "ibm.h"
|
|
#include "lib/core/crc.h"
|
|
#include "lib/data/fluxmap.h"
|
|
#include "lib/data/fluxmapreader.h"
|
|
#include "lib/data/fluxpattern.h"
|
|
#include "lib/data/sector.h"
|
|
#include "arch/ibm/ibm.pb.h"
|
|
#include "lib/config/proto.h"
|
|
#include "lib/data/layout.h"
|
|
#include <string.h>
|
|
|
|
static_assert(std::is_trivially_copyable<IbmIdam>::value,
|
|
"IbmIdam is not trivially copyable");
|
|
|
|
/*
|
|
* The markers at the beginning of records are special, and have
|
|
* missing clock pulses, allowing them to be found by the logic.
|
|
*
|
|
* IAM record:
|
|
* flux: XXXX-XXX-XXXX-X- = 0xf77a
|
|
* clock: X X - X - X X X = 0xd7
|
|
* data: X X X X X X - - = 0xfc
|
|
*
|
|
* (We just ignore this one --- it's useless and optional.)
|
|
*/
|
|
|
|
/*
|
|
* IDAM record:
|
|
* flux: XXXX-X-X-XXXXXX- = 0xf57e
|
|
* clock: X X - - - X X X = 0xc7
|
|
* data: X X X X X X X - = 0xfe
|
|
*/
|
|
const FluxPattern FM_IDAM_PATTERN(16, 0xf57e);
|
|
|
|
/*
|
|
* DAM1 record:
|
|
* flux: XXXX-X-X-XX-X-X- = 0xf56a
|
|
* clock: X X - - - X X X = 0xc7
|
|
* data: X X X X X - - - = 0xf8
|
|
*/
|
|
const FluxPattern FM_DAM1_PATTERN(16, 0xf56a);
|
|
|
|
/*
|
|
* DAM2 record:
|
|
* flux: XXXX-X-X-XX-XXXX = 0xf56f
|
|
* clock: X X - - - X X X = 0xc7
|
|
* data: X X X X X - X X = 0xfb
|
|
*/
|
|
const FluxPattern FM_DAM2_PATTERN(16, 0xf56f);
|
|
|
|
/*
|
|
* TRS80DAM1 record:
|
|
* flux: XXXX-X-X-XX-X-XX = 0xf56b
|
|
* clock: X X - - - X X X = 0xc7
|
|
* data: X X X X X - - X = 0xf9
|
|
*/
|
|
const FluxPattern FM_TRS80DAM1_PATTERN(16, 0xf56b);
|
|
|
|
/*
|
|
* TRS80DAM2 record:
|
|
* flux: XXXX-X-X-XX-XXX- = 0xf56e
|
|
* clock: X X - - - X X X = 0xc7
|
|
* data: X X X X X - X - = 0xfa
|
|
*/
|
|
const FluxPattern FM_TRS80DAM2_PATTERN(16, 0xf56e);
|
|
|
|
/* MFM record separator:
|
|
* 0xA1 is:
|
|
* data: 1 0 1 0 0 0 0 1 = 0xa1
|
|
* mfm: 01 00 01 00 10 10 10 01 = 0x44a9
|
|
* special: 01 00 01 00 10 00 10 01 = 0x4489
|
|
* ^^^^^
|
|
* When shifted out of phase, the special 0xa1 byte becomes an illegal
|
|
* encoding (you can't do 10 00). So this can't be spoofed by user data.
|
|
*
|
|
* shifted: 10 00 10 01 00 01 00 1
|
|
*
|
|
* It's repeated three times.
|
|
*/
|
|
const FluxPattern MFM_PATTERN(48, 0x448944894489LL);
|
|
|
|
const FluxMatchers ANY_RECORD_PATTERN({
|
|
&MFM_PATTERN,
|
|
&FM_IDAM_PATTERN,
|
|
&FM_DAM1_PATTERN,
|
|
&FM_DAM2_PATTERN,
|
|
&FM_TRS80DAM1_PATTERN,
|
|
&FM_TRS80DAM2_PATTERN,
|
|
});
|
|
|
|
class IbmDecoder : public Decoder
|
|
{
|
|
public:
|
|
IbmDecoder(const DecoderProto& config):
|
|
Decoder(config),
|
|
_config(config.ibm())
|
|
{
|
|
}
|
|
|
|
nanoseconds_t advanceToNextRecord() override
|
|
{
|
|
return seekToPattern(ANY_RECORD_PATTERN);
|
|
}
|
|
|
|
void decodeSectorRecord() override
|
|
{
|
|
/* This is really annoying because the IBM record scheme has a
|
|
* variable-sized header _and_ the checksum covers this header too. So
|
|
* we have to read and decode a byte at a time until we know where the
|
|
* record itself starts, saving the bytes for the checksumming later.
|
|
*/
|
|
|
|
Bytes bytes;
|
|
ByteWriter bw(bytes);
|
|
|
|
auto readByte = [&]()
|
|
{
|
|
auto bits = readRawBits(16);
|
|
auto bytes = decodeFmMfm(bits).slice(0, 1);
|
|
uint8_t byte = bytes[0];
|
|
bw.write_8(byte);
|
|
return byte;
|
|
};
|
|
|
|
uint8_t id = readByte();
|
|
if (id == 0xa1)
|
|
{
|
|
readByte();
|
|
readByte();
|
|
id = readByte();
|
|
}
|
|
if (id != IBM_IDAM)
|
|
return;
|
|
|
|
ByteReader br(bytes);
|
|
br.seek(bw.pos);
|
|
|
|
auto bits = readRawBits(IBM_IDAM_LEN * 16);
|
|
bw += decodeFmMfm(bits).slice(0, IBM_IDAM_LEN);
|
|
|
|
IbmDecoderProto::TrackdataProto trackdata;
|
|
getTrackFormat(
|
|
trackdata, _sector->physicalTrack, _sector->physicalSide);
|
|
|
|
_sector->logicalTrack = br.read_8();
|
|
_sector->logicalSide = br.read_8();
|
|
_sector->logicalSector = br.read_8();
|
|
_currentSectorSize = 1 << (br.read_8() + 7);
|
|
|
|
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
|
|
uint16_t wantCrc = br.read_be16();
|
|
if (wantCrc == gotCrc)
|
|
_sector->status =
|
|
Sector::DATA_MISSING; /* correct but unintuitive */
|
|
|
|
if (trackdata.ignore_side_byte())
|
|
_sector->logicalSide =
|
|
Layout::remapSidePhysicalToLogical(_sector->physicalSide);
|
|
_sector->logicalSide ^= trackdata.invert_side_byte();
|
|
if (trackdata.ignore_track_byte())
|
|
_sector->logicalTrack = _sector->physicalTrack;
|
|
|
|
for (int sector : trackdata.ignore_sector())
|
|
if (_sector->logicalSector == sector)
|
|
{
|
|
_sector->status = Sector::MISSING;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void decodeDataRecord() override
|
|
{
|
|
/* This is the same deal as the sector record. */
|
|
|
|
Bytes bytes;
|
|
ByteWriter bw(bytes);
|
|
|
|
auto readByte = [&]()
|
|
{
|
|
auto bits = readRawBits(16);
|
|
auto bytes = decodeFmMfm(bits).slice(0, 1);
|
|
uint8_t byte = bytes[0];
|
|
bw.write_8(byte);
|
|
return byte;
|
|
};
|
|
|
|
uint8_t id = readByte();
|
|
if (id == 0xa1)
|
|
{
|
|
readByte();
|
|
readByte();
|
|
id = readByte();
|
|
}
|
|
if ((id != IBM_DAM1) && (id != IBM_DAM2) && (id != IBM_TRS80DAM1) &&
|
|
(id != IBM_TRS80DAM2))
|
|
return;
|
|
|
|
ByteReader br(bytes);
|
|
br.seek(bw.pos);
|
|
|
|
auto bits = readRawBits((_currentSectorSize + 2) * 16);
|
|
bw += decodeFmMfm(bits).slice(0, _currentSectorSize + 2);
|
|
|
|
_sector->data = br.read(_currentSectorSize);
|
|
uint16_t gotCrc = crc16(CCITT_POLY, bytes.slice(0, br.pos));
|
|
uint16_t wantCrc = br.read_be16();
|
|
_sector->status =
|
|
(wantCrc == gotCrc) ? Sector::OK : Sector::BAD_CHECKSUM;
|
|
|
|
auto layout = Layout::getLayoutOfTrack(
|
|
_sector->logicalTrack, _sector->logicalSide);
|
|
if (_currentSectorSize != layout->sectorSize)
|
|
std::cerr << fmt::format(
|
|
"Warning: configured sector size for t{}.h{}.s{} is {} bytes "
|
|
"but that seen on disk is {} bytes\n",
|
|
_sector->logicalTrack,
|
|
_sector->logicalSide,
|
|
_sector->logicalSector,
|
|
layout->sectorSize,
|
|
_currentSectorSize);
|
|
}
|
|
|
|
private:
|
|
void getTrackFormat(IbmDecoderProto::TrackdataProto& trackdata,
|
|
unsigned track,
|
|
unsigned head) const
|
|
{
|
|
trackdata.Clear();
|
|
for (const auto& f : _config.trackdata())
|
|
{
|
|
if (f.has_track() && (f.track() != track))
|
|
continue;
|
|
if (f.has_head() && (f.head() != head))
|
|
continue;
|
|
|
|
trackdata.MergeFrom(f);
|
|
}
|
|
}
|
|
|
|
private:
|
|
const IbmDecoderProto& _config;
|
|
unsigned _currentSectorSize;
|
|
unsigned _currentHeaderLength;
|
|
};
|
|
|
|
std::unique_ptr<Decoder> createIbmDecoder(const DecoderProto& config)
|
|
{
|
|
return std::unique_ptr<Decoder>(new IbmDecoder(config));
|
|
}
|