mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-31 11:17:01 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			283 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "lib/core/globals.h"
 | |
| #include "lib/config/config.h"
 | |
| #include "lib/decoders/decoders.h"
 | |
| #include "lib/encoders/encoders.h"
 | |
| #include "arch/ibm/ibm.h"
 | |
| #include "lib/core/crc.h"
 | |
| #include "lib/data/image.h"
 | |
| #include "arch/ibm/ibm.pb.h"
 | |
| #include "lib/encoders/encoders.pb.h"
 | |
| #include "fmt/format.h"
 | |
| #include "lib/config/proto.h"
 | |
| #include "lib/data/layout.h"
 | |
| #include <ctype.h>
 | |
| 
 | |
| /* IAM record separator:
 | |
|  * 0xC2 is:
 | |
|  * data:    1  1  0  0  0  0  1  0  = 0xc2
 | |
|  * mfm:     01 01 00 10 10 10 01 00 = 0x5254
 | |
|  * special: 01 01 00 10 00 10 01 00 = 0x5224
 | |
|  */
 | |
| #define MFM_IAM_SEPARATOR 0x5224
 | |
| 
 | |
| /* FM IAM record:
 | |
|  * flux:   XXXX-XXX-XXXX-X- = 0xf77a
 | |
|  * clock:  X X - X - X X X  = 0xd7
 | |
|  * data:    X X X X X X - - = 0xfc
 | |
|  */
 | |
| #define FM_IAM_RECORD 0xf77a
 | |
| 
 | |
| /* MFM IAM record:
 | |
|  * data:   1  1  1  1  1  1  0  0  = 0xfc
 | |
|  * flux:   01 01 01 01 01 01 00 10 = 0x5552
 | |
|  */
 | |
| #define MFM_IAM_RECORD 0x5552
 | |
| 
 | |
| /* 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.
 | |
|  */
 | |
| #define MFM_RECORD_SEPARATOR 0x4489
 | |
| #define MFM_RECORD_SEPARATOR_BYTE 0xa1
 | |
| 
 | |
| /* MFM IDAM byte:
 | |
|  * data:    1  1  1  1  1  1  1  0  = 0xfe
 | |
|  * mfm:     01 01 01 01 01 01 01 00 = 0x5554
 | |
|  */
 | |
| 
 | |
| /* MFM DAM byte:
 | |
|  * data:    1  1  1  1  1  0  1  1  = 0xfb
 | |
|  * mfm:     01 01 01 01 01 00 01 01 = 0x5545
 | |
|  */
 | |
| 
 | |
| static uint8_t decodeUint16(uint16_t raw)
 | |
| {
 | |
|     Bytes b;
 | |
|     ByteWriter bw(b);
 | |
|     bw.write_be16(raw);
 | |
|     return decodeFmMfm(b.toBits())[0];
 | |
| }
 | |
| 
 | |
| class IbmEncoder : public Encoder
 | |
| {
 | |
| public:
 | |
|     IbmEncoder(const EncoderProto& config):
 | |
|         Encoder(config),
 | |
|         _config(config.ibm())
 | |
|     {
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     void writeRawBits(uint32_t data, int width)
 | |
|     {
 | |
|         _cursor += width;
 | |
|         _lastBit = data & 1;
 | |
|         for (int i = 0; i < width; i++)
 | |
|         {
 | |
|             unsigned pos = _cursor - i - 1;
 | |
|             if (pos < _bits.size())
 | |
|                 _bits[pos] = data & 1;
 | |
|             data >>= 1;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void getEncoderTrackData(IbmEncoderProto::TrackdataProto& trackdata,
 | |
|         unsigned track,
 | |
|         unsigned head)
 | |
|     {
 | |
|         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);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| public:
 | |
|     std::unique_ptr<Fluxmap> encode(std::shared_ptr<const TrackInfo>& trackInfo,
 | |
|         const std::vector<std::shared_ptr<const Sector>>& sectors,
 | |
|         const Image& image) override
 | |
|     {
 | |
|         IbmEncoderProto::TrackdataProto trackdata;
 | |
|         getEncoderTrackData(
 | |
|             trackdata, trackInfo->logicalTrack, trackInfo->logicalSide);
 | |
| 
 | |
|         auto trackLayout = Layout::getLayoutOfTrack(
 | |
|             trackInfo->logicalTrack, trackInfo->logicalSide);
 | |
| 
 | |
|         auto writeBytes = [&](const Bytes& bytes)
 | |
|         {
 | |
|             if (trackdata.use_fm())
 | |
|                 encodeFm(_bits, _cursor, bytes);
 | |
|             else
 | |
|                 encodeMfm(_bits, _cursor, bytes, _lastBit);
 | |
|         };
 | |
| 
 | |
|         auto writeFillerRawBytes = [&](int count, uint16_t byte)
 | |
|         {
 | |
|             for (int i = 0; i < count; i++)
 | |
|                 writeRawBits(byte, 16);
 | |
|         };
 | |
| 
 | |
|         auto writeFillerBytes = [&](int count, uint8_t byte)
 | |
|         {
 | |
|             Bytes b{byte};
 | |
|             for (int i = 0; i < count; i++)
 | |
|                 writeBytes(b);
 | |
|         };
 | |
| 
 | |
|         double clockRateUs = trackdata.target_clock_period_us();
 | |
|         if (!trackdata.use_fm())
 | |
|             clockRateUs /= 2.0;
 | |
|         int bitsPerRevolution =
 | |
|             (trackdata.target_rotational_period_ms() * 1000.0) / clockRateUs;
 | |
|         _bits.resize(bitsPerRevolution);
 | |
|         _cursor = 0;
 | |
| 
 | |
|         uint8_t idamUnencoded = decodeUint16(trackdata.idam_byte());
 | |
|         uint8_t damUnencoded = decodeUint16(trackdata.dam_byte());
 | |
| 
 | |
|         uint8_t sectorSize = 0;
 | |
|         {
 | |
|             int s = trackLayout->sectorSize >> 7;
 | |
|             while (s > 1)
 | |
|             {
 | |
|                 s >>= 1;
 | |
|                 sectorSize += 1;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         uint16_t gapFill = trackdata.gap_fill_byte();
 | |
| 
 | |
|         writeFillerRawBytes(trackdata.gap0(), gapFill);
 | |
|         if (trackdata.emit_iam())
 | |
|         {
 | |
|             writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
 | |
|             if (!trackdata.use_fm())
 | |
|             {
 | |
|                 for (int i = 0; i < 3; i++)
 | |
|                     writeRawBits(MFM_IAM_SEPARATOR, 16);
 | |
|             }
 | |
|             writeRawBits(
 | |
|                 trackdata.use_fm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16);
 | |
|             writeFillerRawBytes(trackdata.gap1(), gapFill);
 | |
|         }
 | |
| 
 | |
|         bool first = true;
 | |
|         for (const auto& sectorData : sectors)
 | |
|         {
 | |
|             if (!first)
 | |
|                 writeFillerRawBytes(trackdata.gap3(), gapFill);
 | |
|             first = false;
 | |
| 
 | |
|             /* Writing the sector and data records are fantastically annoying.
 | |
|              * The CRC is calculated from the *very start* of the record, and
 | |
|              * include the malformed marker bytes. Our encoder doesn't know
 | |
|              * about this, of course, with the result that we have to construct
 | |
|              * the unencoded header, calculate the checksum, and then use the
 | |
|              * same logic to emit the bytes which require special encoding
 | |
|              * before encoding the rest of the header normally. */
 | |
| 
 | |
|             {
 | |
|                 Bytes header;
 | |
|                 ByteWriter bw(header);
 | |
| 
 | |
|                 writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
 | |
|                 if (!trackdata.use_fm())
 | |
|                 {
 | |
|                     for (int i = 0; i < 3; i++)
 | |
|                         bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
 | |
|                 }
 | |
|                 bw.write_8(idamUnencoded);
 | |
|                 bw.write_8(sectorData->logicalTrack);
 | |
|                 bw.write_8(
 | |
|                     sectorData->logicalSide ^ trackdata.invert_side_byte());
 | |
|                 bw.write_8(sectorData->logicalSector);
 | |
|                 bw.write_8(sectorSize);
 | |
|                 uint16_t crc = crc16(CCITT_POLY, header);
 | |
|                 bw.write_be16(crc);
 | |
| 
 | |
|                 int conventionalHeaderStart = 0;
 | |
|                 if (!trackdata.use_fm())
 | |
|                 {
 | |
|                     for (int i = 0; i < 3; i++)
 | |
|                         writeRawBits(MFM_RECORD_SEPARATOR, 16);
 | |
|                     conventionalHeaderStart += 3;
 | |
|                 }
 | |
|                 writeRawBits(trackdata.idam_byte(), 16);
 | |
|                 conventionalHeaderStart += 1;
 | |
| 
 | |
|                 writeBytes(header.slice(conventionalHeaderStart));
 | |
|             }
 | |
| 
 | |
|             writeFillerRawBytes(trackdata.gap2(), gapFill);
 | |
| 
 | |
|             {
 | |
|                 Bytes data;
 | |
|                 ByteWriter bw(data);
 | |
| 
 | |
|                 writeFillerBytes(trackdata.use_fm() ? 6 : 12, 0x00);
 | |
|                 if (!trackdata.use_fm())
 | |
|                 {
 | |
|                     for (int i = 0; i < 3; i++)
 | |
|                         bw.write_8(MFM_RECORD_SEPARATOR_BYTE);
 | |
|                 }
 | |
|                 bw.write_8(damUnencoded);
 | |
| 
 | |
|                 Bytes truncatedData =
 | |
|                     sectorData->data.slice(0, trackLayout->sectorSize);
 | |
|                 bw += truncatedData;
 | |
|                 uint16_t crc = crc16(CCITT_POLY, data);
 | |
|                 bw.write_be16(crc);
 | |
| 
 | |
|                 int conventionalHeaderStart = 0;
 | |
|                 if (!trackdata.use_fm())
 | |
|                 {
 | |
|                     for (int i = 0; i < 3; i++)
 | |
|                         writeRawBits(MFM_RECORD_SEPARATOR, 16);
 | |
|                     conventionalHeaderStart += 3;
 | |
|                 }
 | |
|                 writeRawBits(trackdata.dam_byte(), 16);
 | |
|                 conventionalHeaderStart += 1;
 | |
| 
 | |
|                 writeBytes(data.slice(conventionalHeaderStart));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (_cursor >= _bits.size())
 | |
|             error("track data overrun");
 | |
|         while (_cursor < _bits.size())
 | |
|             writeFillerRawBytes(1, gapFill);
 | |
| 
 | |
|         std::unique_ptr<Fluxmap> fluxmap(new Fluxmap);
 | |
|         fluxmap->appendBits(_bits,
 | |
|             calculatePhysicalClockPeriod(clockRateUs * 1e3,
 | |
|                 trackdata.target_rotational_period_ms() * 1e6));
 | |
|         return fluxmap;
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     const IbmEncoderProto& _config;
 | |
|     std::vector<bool> _bits;
 | |
|     unsigned _cursor;
 | |
|     bool _lastBit;
 | |
| };
 | |
| 
 | |
| std::unique_ptr<Encoder> createIbmEncoder(const EncoderProto& config)
 | |
| {
 | |
|     return std::unique_ptr<Encoder>(new IbmEncoder(config));
 | |
| }
 |