From 82bd1bead43cdb1cb4c1787250a63997f5bd88a4 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Sat, 3 Oct 2020 13:05:06 -0500 Subject: [PATCH] Add Micropolis decoder Resolves #187 --- arch/micropolis/decoder.cc | 56 ++++++++++++++++++++++++++++++++++++ arch/micropolis/micropolis.h | 18 ++++++++++++ doc/disk-micropolis.md | 49 +++++++++++++++++++++++++++++++ mkninja.sh | 2 ++ src/fe-readmicropolis.cc | 26 +++++++++++++++++ src/fluxengine.cc | 2 ++ 6 files changed, 153 insertions(+) create mode 100644 arch/micropolis/decoder.cc create mode 100644 arch/micropolis/micropolis.h create mode 100644 doc/disk-micropolis.md create mode 100644 src/fe-readmicropolis.cc diff --git a/arch/micropolis/decoder.cc b/arch/micropolis/decoder.cc new file mode 100644 index 00000000..f16d6fbc --- /dev/null +++ b/arch/micropolis/decoder.cc @@ -0,0 +1,56 @@ +#include "globals.h" +#include "fluxmap.h" +#include "decoders/fluxmapreader.h" +#include "decoders/decoders.h" +#include "sector.h" +#include "micropolis.h" +#include "bytes.h" +#include "fmt/format.h" + +/* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. */ +static const FluxPattern SECTOR_SYNC_PATTERN(32, 0xaaaa5555); + +AbstractDecoder::RecordType MicropolisDecoder::advanceToNextRecord() +{ + _fmr->seekToIndexMark(); + const FluxMatcher* matcher = nullptr; + _sector->clock = _fmr->seekToPattern(SECTOR_SYNC_PATTERN, matcher); + if (matcher == &SECTOR_SYNC_PATTERN) { + readRawBits(16); + return SECTOR_RECORD; + } + return UNKNOWN_RECORD; +} + +static uint8_t checksum(const Bytes& bytes) { + ByteReader br(bytes); + uint16_t sum = 0; + while (!br.eof()) { + sum += br.read_8(); + } + return (sum & 0xFF) + (sum >> 8); +} + +void MicropolisDecoder::decodeSectorRecord() +{ + auto rawbits = readRawBits(MICROPOLIS_ENCODED_SECTOR_SIZE*16); + auto bytes = decodeFmMfm(rawbits).slice(0, MICROPOLIS_ENCODED_SECTOR_SIZE); + ByteReader br(bytes); + + br.read_8(); /* sync */ + _sector->logicalTrack = br.read_8(); + _sector->logicalSide = _sector->physicalSide; + _sector->logicalSector = br.read_8(); + if (_sector->logicalSector > 15) + return; + if (_sector->logicalTrack > 77) + return; + + br.read(10); /* OS data or padding */ + _sector->data = br.read(256); + uint8_t wantChecksum = br.read_8(); + uint8_t gotChecksum = checksum(bytes.slice(1, 2+266)); + br.read(5); /* 4 byte ECC and ECC-present flag */ + + _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; +} diff --git a/arch/micropolis/micropolis.h b/arch/micropolis/micropolis.h new file mode 100644 index 00000000..0ecab4fb --- /dev/null +++ b/arch/micropolis/micropolis.h @@ -0,0 +1,18 @@ +#ifndef ZILOGMCZ_H +#define ZILOGMCZ_H + +#define MICROPOLIS_ENCODED_SECTOR_SIZE (1+2+266+6) + +class Sector; +class Fluxmap; + +class MicropolisDecoder : public AbstractDecoder +{ +public: + virtual ~MicropolisDecoder() {} + + RecordType advanceToNextRecord(); + void decodeSectorRecord(); +}; + +#endif diff --git a/doc/disk-micropolis.md b/doc/disk-micropolis.md new file mode 100644 index 00000000..d19daaf5 --- /dev/null +++ b/doc/disk-micropolis.md @@ -0,0 +1,49 @@ +Disk: Micropolis +================ + +Micropolis MetaFloppy disks use MFM and hard sectors. They were 100 TPI and +stored 315k per side. Each of the 16 sectors contains 266 bytes of "user data," +allowing 10 bytes of metadata for use by the operating system. Micropolis DOS +(MDOS) used the metadata bytes, but CP/M did not. + +Some later systems were Micropolis-compatible and so were also 100 TPI, like +the Vector Graphic Dual-Mode Disk Controller which was paired with a Tandon +drive. + +Reading disks +------------- + +Just do: + +``` +fluxengine read micropolis +``` + +You should end up with a `micropolis.img` which is 630784 bytes long (for a +normal DD disk). The image is written in CHS order, but HCS is generally used +by CP/M tools so the image needs to be post-processed. For only half-full disks +or single-sided disks, you can use `-s :s=0` to read only one side of the disk +which works around the problem. + +The [CP/M BIOS](https://www.seasip.info/Cpm/bios.html) defined SELDSK, SETTRK, +and SETSEC, but no function to select the head/side. This caused dual-sided +floppies to be represented as twice the number of tracks with the second side's +tracks logically following the first side (e.g., tracks 77-153). That produces +results mostly indistinguishable from HCS. + +Useful references +----------------- + + - [Micropolis 1040/1050 S-100 Floppy Disk Subsystems User's Manual][micropolis1040/1050]. + Section 6, pages 261-266. Documents pre-ECC sector format. Note that the + entire record, starting at the sync byte, is controlled by software + + - [Vector Graphic Dual-Mode Disk Controller Board Engineering Documentation][vectordualmode]. + Section 1.6.2. Documents ECC sector format + + - [AltairZ80 Simulator Usage Manual][altairz80]. Section 10.6. Documents ECC + sector format and VGI file format + +[micropolis1040/1050]: http://www.bitsavers.org/pdf/micropolis/metafloppy/1084-01_1040_1050_Users_Manual_Apr79.pdf +[vectordualmode]: http://bitsavers.org/pdf/vectorGraphic/hardware/7200-1200-02-1_Dual-Mode_Disk_Controller_Board_Engineering_Documentation_Feb81.pdf +[altairz80]: http://www.bitsavers.org/simh.trailing-edge.com_201206/pdf/altairz80_doc.pdf diff --git a/mkninja.sh b/mkninja.sh index da0d9a33..4a7000ef 100644 --- a/mkninja.sh +++ b/mkninja.sh @@ -171,6 +171,7 @@ buildlibrary libbackend.a \ arch/ibm/decoder.cc \ arch/ibm/encoder.cc \ arch/macintosh/decoder.cc \ + arch/micropolis/decoder.cc \ arch/mx/decoder.cc \ arch/tids990/decoder.cc \ arch/tids990/encoder.cc \ @@ -224,6 +225,7 @@ buildlibrary libfrontend.a \ src/fe-readfb100.cc \ src/fe-readibm.cc \ src/fe-readmac.cc \ + src/fe-readmicropolis.cc \ src/fe-readmx.cc \ src/fe-readtids990.cc \ src/fe-readvictor9k.cc \ diff --git a/src/fe-readmicropolis.cc b/src/fe-readmicropolis.cc new file mode 100644 index 00000000..bd6316e0 --- /dev/null +++ b/src/fe-readmicropolis.cc @@ -0,0 +1,26 @@ +#include "globals.h" +#include "flags.h" +#include "reader.h" +#include "fluxmap.h" +#include "encoders/encoders.h" +#include "decoders/decoders.h" +#include "sector.h" +#include "sectorset.h" +#include "record.h" +#include "micropolis/micropolis.h" +#include "fmt/format.h" + +static FlagGroup flags { &readerFlags }; + +int mainReadMicropolis(int argc, const char* argv[]) +{ + setReaderDefaultSource(":t=0-76"); + setReaderDefaultOutput("micropolis.img"); + setReaderRevolutions(21); /* 17 index holes * 1.25 */ + flags.parseFlags(argc, argv); + + MicropolisDecoder decoder; + readDiskCommand(decoder); + return 0; +} + diff --git a/src/fluxengine.cc b/src/fluxengine.cc index 2074904f..772bfb3d 100644 --- a/src/fluxengine.cc +++ b/src/fluxengine.cc @@ -21,6 +21,7 @@ extern command_cb mainReadF85; extern command_cb mainReadFB100; extern command_cb mainReadIBM; extern command_cb mainReadMac; +extern command_cb mainReadMicropolis; extern command_cb mainReadMx; extern command_cb mainReadTiDs990; extern command_cb mainReadVictor9K; @@ -78,6 +79,7 @@ static std::vector readables = { "fb100", mainReadFB100, "Reads FB100 disks.", }, { "ibm", mainReadIBM, "Reads the ubiquitous IBM format disks.", }, { "mac", mainReadMac, "Reads Apple Macintosh disks.", }, + { "micropolis", mainReadMicropolis, "Reads Micropolis disks.", }, { "mx", mainReadMx, "Reads MX disks.", }, { "tids990", mainReadTiDs990, "Reads Texas Instruments DS990 disks.", }, { "victor9k", mainReadVictor9K, "Reads Victor 9000 disks.", },