diff --git a/README.md b/README.md index 8f2f3320..484b5935 100644 --- a/README.md +++ b/README.md @@ -103,45 +103,46 @@ particular filesystem and can read (and sometimes write, support varies) files directly from disks, flux files or disk images. Some formats have multiple choices because they can store multiple types of file system. - - - -| Profile | Format | Read? | Write? | Filesystem? | -|:--------|:-------|:-----:|:------:|:------------| -| [`acornadfs`](doc/disk-acornadfs.md) | Acorn ADFS: BBC Micro, Archimedes | 🦖 | | | -| [`acorndfs`](doc/disk-acorndfs.md) | Acorn DFS: Acorn Atom, BBC Micro series | 🦄 | | ACORNDFS | -| [`aeslanier`](doc/disk-aeslanier.md) | AES Lanier "No Problem": 616kB 5.25" 77-track SSDD hard sectored | 🦖 | | | -| [`agat`](doc/disk-agat.md) | Agat: 840kB 5.25" 80-track DS | 🦖 | 🦖 | | -| [`amiga`](doc/disk-amiga.md) | Amiga: 880kB 3.5" DSDD | 🦄 | 🦄 | AMIGAFFS | -| [`ampro`](doc/disk-ampro.md) | Ampro Little Board: CP/M | 🦖 | | CPMFS | -| [`apple2`](doc/disk-apple2.md) | Apple II: Prodos, Appledos, and CP/M | 🦄 | 🦄 | APPLEDOS CPMFS PRODOS | -| [`atarist`](doc/disk-atarist.md) | Atari ST: Almost PC compatible | 🦄 | 🦄 | | -| [`bk`](doc/disk-bk.md) | BK: 800kB 5.25"/3.5" 80-track 10-sector DSDD | 🦖 | 🦖 | | -| [`brother`](doc/disk-brother.md) | Brother word processors: GCR family | 🦄 | 🦄 | BROTHER120 FATFS | -| [`commodore`](doc/disk-commodore.md) | Commodore: 1541, 1581, 8050 and variations | 🦄 | 🦄 | CBMFS | -| [`eco1`](doc/disk-eco1.md) | VDS Eco1: CP/M; 1210kB 77-track mixed format DSHD | 🦖 | | CPMFS | -| [`epsonpf10`](doc/disk-epsonpf10.md) | Epson PF-10: CP/M; 3.5" 40-track DSDD | 🦖 | | CPMFS | -| [`f85`](doc/disk-f85.md) | Durango F85: 461kB 5.25" 77-track SS | 🦖 | | | -| [`fb100`](doc/disk-fb100.md) | Brother FB-100: 100kB 3.5" 40-track SSSD | 🦖 | | | -| [`hplif`](doc/disk-hplif.md) | Hewlett-Packard LIF: a variety of disk formats used by HP | 🦄 | 🦄 | LIF | -| [`ibm`](doc/disk-ibm.md) | IBM PC: Generic PC 3.5"/5.25" disks | 🦄 | 🦄 | FATFS | -| [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 | | CPMFS | -| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS | -| [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 | | -| [`ms2000`](doc/disk-ms2000.md) | : MS2000 Microdisk Development System | | | MICRODOS | -| [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 | | | -| [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 | | -| [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 | | -| [`psos`](doc/disk-psos.md) | pSOS: 800kB DSDD with PHILE | 🦄 | 🦄 | PHILE | -| [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦄 | 🦖 | ROLAND | -| [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 | | -| [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 | | SMAKY6 | -| [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 | | -| [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M | | | CPMFS | -| [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 | | -| [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 | | ZDOS | -{: .datatable } - + + + +| Profile | Format | Read? | Write? | Filesystem? | +|:--------|:-------|:-----:|:------:|:------------| +| [`acornadfs`](doc/disk-acornadfs.md) | Acorn ADFS: BBC Micro, Archimedes | 🦖 | | | +| [`acorndfs`](doc/disk-acorndfs.md) | Acorn DFS: Acorn Atom, BBC Micro series | 🦄 | | ACORNDFS | +| [`aeslanier`](doc/disk-aeslanier.md) | AES Lanier "No Problem": 616kB 5.25" 77-track SSDD hard sectored | 🦖 | | | +| [`agat`](doc/disk-agat.md) | Agat: 840kB 5.25" 80-track DS | 🦖 | 🦖 | | +| [`amiga`](doc/disk-amiga.md) | Amiga: 880kB 3.5" DSDD | 🦄 | 🦄 | AMIGAFFS | +| [`ampro`](doc/disk-ampro.md) | Ampro Little Board: CP/M | 🦖 | | CPMFS | +| [`apple2`](doc/disk-apple2.md) | Apple II: Prodos, Appledos, and CP/M | 🦄 | 🦄 | APPLEDOS CPMFS PRODOS | +| [`atarist`](doc/disk-atarist.md) | Atari ST: Almost PC compatible | 🦄 | 🦄 | | +| [`bk`](doc/disk-bk.md) | BK: 800kB 5.25"/3.5" 80-track 10-sector DSDD | 🦖 | 🦖 | | +| [`brother`](doc/disk-brother.md) | Brother word processors: GCR family | 🦄 | 🦄 | BROTHER120 FATFS | +| [`commodore`](doc/disk-commodore.md) | Commodore: 1541, 1581, 8050 and variations | 🦄 | 🦄 | CBMFS | +| [`eco1`](doc/disk-eco1.md) | VDS Eco1: CP/M; 1210kB 77-track mixed format DSHD | 🦖 | | CPMFS | +| [`epsonpf10`](doc/disk-epsonpf10.md) | Epson PF-10: CP/M; 3.5" 40-track DSDD | 🦖 | | CPMFS | +| [`f85`](doc/disk-f85.md) | Durango F85: 461kB 5.25" 77-track SS | 🦖 | | | +| [`fb100`](doc/disk-fb100.md) | Brother FB-100: 100kB 3.5" 40-track SSSD | 🦖 | | | +| [`hplif`](doc/disk-hplif.md) | Hewlett-Packard LIF: a variety of disk formats used by HP | 🦄 | 🦄 | LIF | +| [`ibm`](doc/disk-ibm.md) | IBM PC: Generic PC 3.5"/5.25" disks | 🦄 | 🦄 | FATFS | +| [`icl30`](doc/disk-icl30.md) | ICL Model 30: CP/M; 263kB 35-track DSSD | 🦖 | | CPMFS | +| [`mac`](doc/disk-mac.md) | Macintosh: 400kB/800kB 3.5" GCR | 🦄 | 🦄 | MACHFS | +| [`micropolis`](doc/disk-micropolis.md) | Micropolis: 100tpi MetaFloppy disks | 🦄 | 🦄 | | +| [`ms2000`](doc/disk-ms2000.md) | : MS2000 Microdisk Development System | | | MICRODOS | +| [`mx`](doc/disk-mx.md) | DVK MX: Soviet-era PDP-11 clone | 🦖 | | | +| [`n88basic`](doc/disk-n88basic.md) | N88-BASIC: PC8800/PC98 5.25" 77-track 26-sector DSHD | 🦄 | 🦄 | | +| [`northstar`](doc/disk-northstar.md) | Northstar: 5.25" hard sectored | 🦄 | 🦄 | | +| [`psos`](doc/disk-psos.md) | pSOS: 800kB DSDD with PHILE | 🦄 | 🦄 | PHILE | +| [`rolandd20`](doc/disk-rolandd20.md) | Roland D20: 3.5" electronic synthesiser disks | 🦄 | 🦖 | ROLAND | +| [`rx50`](doc/disk-rx50.md) | Digital RX50: 400kB 5.25" 80-track 10-sector SSDD | 🦖 | 🦖 | | +| [`smaky6`](doc/disk-smaky6.md) | Smaky 6: 308kB 5.25" 77-track 16-sector SSDD, hard sectored | 🦖 | | SMAKY6 | +| [`tartu`](doc/disk-tartu.md) | Tartu: The Palivere and variations | 🦄 | | CPMFS | +| [`tids990`](doc/disk-tids990.md) | Texas Instruments DS990: 1126kB 8" DSSD | 🦖 | 🦖 | | +| [`tiki`](doc/disk-tiki.md) | Tiki 100: CP/M | | | CPMFS | +| [`victor9k`](doc/disk-victor9k.md) | Victor 9000 / Sirius One: 1224kB 5.25" DSDD GCR | 🦖 | 🦖 | | +| [`zilogmcz`](doc/disk-zilogmcz.md) | Zilog MCZ: 320kB 8" 77-track SSSD hard-sectored | 🦖 | | ZDOS | +{: .datatable } + ### Notes diff --git a/arch/build.py b/arch/build.py index 5e901433..09370e2d 100644 --- a/arch/build.py +++ b/arch/build.py @@ -19,6 +19,7 @@ proto( "./northstar/northstar.proto", "./rolandd20/rolandd20.proto", "./smaky6/smaky6.proto", + "./tartu/tartu.proto", "./tids990/tids990.proto", "./victor9k/victor9k.proto", "./zilogmcz/zilogmcz.proto", diff --git a/arch/smaky6/smaky6.proto b/arch/smaky6/smaky6.proto index badf4bc1..6a0bfed1 100644 --- a/arch/smaky6/smaky6.proto +++ b/arch/smaky6/smaky6.proto @@ -1,6 +1,4 @@ syntax = "proto2"; -import "lib/common.proto"; - message Smaky6DecoderProto {} diff --git a/arch/tartu/decoder.cc b/arch/tartu/decoder.cc new file mode 100644 index 00000000..7590a86f --- /dev/null +++ b/arch/tartu/decoder.cc @@ -0,0 +1,84 @@ +#include "lib/globals.h" +#include "lib/decoders/decoders.h" +#include "arch/tartu/tartu.h" +#include "lib/crc.h" +#include "lib/fluxmap.h" +#include "lib/decoders/fluxmapreader.h" +#include "lib/sector.h" +#include + +constexpr uint64_t HEADER_BITS = 0xaaaaaaaa44895554LL; +constexpr uint64_t DATA_BITS = 0xaaaaaaaa44895545LL; + +static const FluxPattern HEADER_PATTERN(64, HEADER_BITS); +static const FluxPattern DATA_PATTERN(64, DATA_BITS); + +const FluxMatchers ANY_RECORD_PATTERN { + &HEADER_PATTERN, + &DATA_PATTERN +}; + +class TartuDecoder : public Decoder +{ +public: + TartuDecoder(const DecoderProto& config): + Decoder(config), + _config(config.tartu()) + { + } + + void beginTrack() override + { + } + + nanoseconds_t advanceToNextRecord() override + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + void decodeSectorRecord() override + { + if (readRaw64() != HEADER_BITS) + return; + + auto bits = readRawBits(16 * 8); + auto bytes = decodeFmMfm(bits).slice(0, 8); + + ByteReader br(bytes); + uint8_t track = br.read_8(); + _sector->logicalTrack = track >> 1; + _sector->logicalSide = track & 1; + br.skip(1); /* seems always to be 1 */ + _sector->logicalSector = br.read_8(); + uint8_t wantChecksum = br.read_8(); + uint8_t gotChecksum = ~sumBytes(bytes.slice(0, 3)); + + if (wantChecksum == gotChecksum) + _sector->status = Sector::DATA_MISSING; + + _sector->status = Sector::DATA_MISSING; + } + + void decodeDataRecord() override + { + if (readRaw64() != DATA_BITS) + return; + + const auto& bits = readRawBits(129 * 16); + const auto& bytes = decodeFmMfm(bits).slice(0, 129); + _sector->data = bytes.slice(0, 128); + + uint8_t wantChecksum = bytes.reader().seek(128).read_8(); + uint8_t gotChecksum = ~sumBytes(_sector->data); + _sector->status = (wantChecksum == gotChecksum) ? Sector::OK : Sector::BAD_CHECKSUM; + } + +private: + const TartuDecoderProto& _config; +}; + +std::unique_ptr createTartuDecoder(const DecoderProto& config) +{ + return std::unique_ptr(new TartuDecoder(config)); +} + diff --git a/arch/tartu/tartu.h b/arch/tartu/tartu.h new file mode 100644 index 00000000..51877b66 --- /dev/null +++ b/arch/tartu/tartu.h @@ -0,0 +1,7 @@ +#ifndef TARTU_H +#define TARTU_H + +extern std::unique_ptr createTartuDecoder(const DecoderProto& config); + +#endif + diff --git a/arch/tartu/tartu.proto b/arch/tartu/tartu.proto new file mode 100644 index 00000000..397060f4 --- /dev/null +++ b/arch/tartu/tartu.proto @@ -0,0 +1,4 @@ +syntax = "proto2"; + +message TartuDecoderProto {} + diff --git a/build.py b/build.py index 6b19314c..097ce8f8 100644 --- a/build.py +++ b/build.py @@ -143,6 +143,7 @@ cxxlibrary( "./arch/northstar/encoder.cc", "./arch/rolandd20/decoder.cc", "./arch/smaky6/decoder.cc", + "./arch/tartu/decoder.cc", "./arch/tids990/decoder.cc", "./arch/tids990/encoder.cc", "./arch/victor9k/decoder.cc", @@ -175,6 +176,7 @@ cxxlibrary( "arch/micropolis/micropolis.h": "./arch/micropolis/micropolis.h", "arch/c64/data_gcr.h": "./arch/c64/data_gcr.h", "arch/c64/c64.h": "./arch/c64/c64.h", + "arch/tartu/tartu.h": "./arch/tartu/tartu.h", "lib/a2r.h": "./lib/a2r.h", "lib/bitmap.h": "./lib/bitmap.h", "lib/bytes.h": "./lib/bytes.h", diff --git a/doc/disk-tartu.md b/doc/disk-tartu.md new file mode 100644 index 00000000..34bcf48a --- /dev/null +++ b/doc/disk-tartu.md @@ -0,0 +1,43 @@ +tartu +==== +## The Palivere and variations + + +The Tartu Palivere is a 1988 Z80-based computer from Estonia. It is a CP/M +machine with 64kB of RAM, running off a 2MHz ꃣ0e30 +clone; it operated off punched tape, cassette, external hard drive or floppy, and was notable as being the first ever computer with an Estonian keyboard. + +
+The Tartu computer's developer Leo Humal working with one. +
+ +From a floppy disk perspective, it is interesting because the floppy drive +interface is almost entirely handled in software --- necessary at the time as +the usual floppy disk interface chip at the time, the ⎲fba5 +of the WD1793), was hard to find. Instead, the floppy controller board was +implemented entirely using TTL logic. Despite this, the encoding is fairly high +density, using MFM and with up to 780kB on a double-sided 80 track disk. + +
+The Tartu FDC with Soviet TTL logic chips. +
+ +FluxEngine supports reading Tartu disks with CP/M filesystem access. + +## Options + + - Format variants: + - `390`: 390kB 5.25" 40-track DSDD + - `780`: 780kB 5.25" 80-track DSDD + +## Examples + +To read: + + - `fluxengine read tartu --390 -s drive:0 -o tartu.img` + - `fluxengine read tartu --780 -s drive:0 -o tartu.img` + +## References + + - [The Estonia Museum of Electronics](https://www.elektroonikamuuseum.ee/tartu_arvuti_lugu.html) + diff --git a/doc/tartu-fdc.jpg b/doc/tartu-fdc.jpg new file mode 100644 index 00000000..dc5a9dc4 Binary files /dev/null and b/doc/tartu-fdc.jpg differ diff --git a/doc/tartu.jpg b/doc/tartu.jpg new file mode 100644 index 00000000..b0d0ddb3 Binary files /dev/null and b/doc/tartu.jpg differ diff --git a/lib/decoders/decoders.cc b/lib/decoders/decoders.cc index 446f438c..ab2b9994 100644 --- a/lib/decoders/decoders.cc +++ b/lib/decoders/decoders.cc @@ -18,6 +18,7 @@ #include "arch/northstar/northstar.h" #include "arch/rolandd20/rolandd20.h" #include "arch/smaky6/smaky6.h" +#include "arch/tartu/tartu.h" #include "arch/tids990/tids990.h" #include "arch/victor9k/victor9k.h" #include "arch/zilogmcz/zilogmcz.h" @@ -51,6 +52,7 @@ std::unique_ptr Decoder::create(const DecoderProto& config) {DecoderProto::kNorthstar, createNorthstarDecoder }, {DecoderProto::kRolandd20, createRolandD20Decoder }, {DecoderProto::kSmaky6, createSmaky6Decoder }, + {DecoderProto::kTartu, createTartuDecoder }, {DecoderProto::kTids990, createTids990Decoder }, {DecoderProto::kVictor9K, createVictor9kDecoder }, {DecoderProto::kZilogmcz, createZilogMczDecoder }, @@ -89,7 +91,7 @@ std::shared_ptr Decoder::decodeToSectors( Fluxmap::Position recordStart = fmr.tell(); _sector->clock = advanceToNextRecord(); if (fmr.eof() || !_sector->clock) - return _trackdata; + break; /* Read the sector record. */ @@ -108,28 +110,26 @@ std::shared_ptr Decoder::decodeToSectors( { /* The data is in a separate record. */ - for (;;) + _sector->headerStartTime = before.ns(); + _sector->headerEndTime = after.ns(); + + _sector->clock = advanceToNextRecord(); + if (fmr.eof() || !_sector->clock) + break; + + before = fmr.tell(); + decodeDataRecord(); + after = fmr.tell(); + + if (_sector->status != Sector::DATA_MISSING) + { + _sector->position = before.bytes; + _sector->dataStartTime = before.ns(); + _sector->dataEndTime = after.ns(); + pushRecord(before, after); + } + else { - _sector->headerStartTime = before.ns(); - _sector->headerEndTime = after.ns(); - - _sector->clock = advanceToNextRecord(); - if (fmr.eof() || !_sector->clock) - break; - - before = fmr.tell(); - decodeDataRecord(); - after = fmr.tell(); - - if (_sector->status != Sector::DATA_MISSING) - { - _sector->position = before.bytes; - _sector->dataStartTime = before.ns(); - _sector->dataEndTime = after.ns(); - pushRecord(before, after); - break; - } - fmr.skipToEvent(F_BIT_PULSE); resetFluxDecoder(); } @@ -142,6 +142,8 @@ std::shared_ptr Decoder::decodeToSectors( _trackdata->sectors.push_back(_sector); } } + + return _trackdata; } void Decoder::pushRecord( diff --git a/lib/decoders/decoders.proto b/lib/decoders/decoders.proto index d861b4eb..f3103981 100644 --- a/lib/decoders/decoders.proto +++ b/lib/decoders/decoders.proto @@ -15,13 +15,14 @@ import "arch/mx/mx.proto"; import "arch/northstar/northstar.proto"; import "arch/rolandd20/rolandd20.proto"; import "arch/smaky6/smaky6.proto"; +import "arch/tartu/tartu.proto"; import "arch/tids990/tids990.proto"; import "arch/victor9k/victor9k.proto"; import "arch/zilogmcz/zilogmcz.proto"; import "lib/fluxsink/fluxsink.proto"; import "lib/common.proto"; -//NEXT: 32 +//NEXT: 33 message DecoderProto { optional double pulse_debounce_threshold = 1 [default = 0.30, (help) = "ignore pulses with intervals shorter than this, in fractions of a clock"]; @@ -50,6 +51,7 @@ message DecoderProto { NorthstarDecoderProto northstar = 24; RolandD20DecoderProto rolandd20 = 31; Smaky6DecoderProto smaky6 = 30; + TartuDecoderProto tartu = 32; Tids990DecoderProto tids990 = 16; Victor9kDecoderProto victor9k = 17; ZilogMczDecoderProto zilogmcz = 18; diff --git a/src/formats/build.py b/src/formats/build.py index 7331080c..02412d6a 100644 --- a/src/formats/build.py +++ b/src/formats/build.py @@ -34,6 +34,7 @@ formats = [ "rx50", "shugart_drive", "smaky6", + "tartu", "tids990", "tiki", "victor9k", diff --git a/src/formats/tartu.textpb b/src/formats/tartu.textpb new file mode 100644 index 00000000..9b2baa9c --- /dev/null +++ b/src/formats/tartu.textpb @@ -0,0 +1,100 @@ +shortname: 'Tartu' +comment: 'The Palivere and variations' +read_support_status: UNICORN + +documentation: +<<< +The Tartu Palivere is a 1988 Z80-based computer from Estonia. It is a CP/M +machine with 64kB of RAM, running off a 2MHz КP580ВМ80А, a Soviet Union 8080 +clone; it operated off punched tape, cassette, external hard drive or floppy, and was notable as being the first ever computer with an Estonian keyboard. + +
+The Tartu computer's developer Leo Humal working with one. +
+ +From a floppy disk perspective, it is interesting because the floppy drive +interface is almost entirely handled in software --- necessary at the time as +the usual floppy disk interface chip at the time, the КR1818VG93 (a Soviet clone +of the WD1793), was hard to find. Instead, the floppy controller board was +implemented entirely using TTL logic. Despite this, the encoding is fairly high +density, using MFM and with up to 780kB on a double-sided 80 track disk. + +
+The Tartu FDC with Soviet TTL logic chips. +
+ +FluxEngine supports reading Tartu disks with CP/M filesystem access. +>>> + +documentation: +<<< +## References + + - [The Estonia Museum of Electronics](https://www.elektroonikamuuseum.ee/tartu_arvuti_lugu.html) +>>> + +image_writer { + filename: "tartu.img" + type: IMAGETYPE_IMG +} + +layout { + layoutdata { + sector_size: 128 + physical { + start_sector: 1 + count: 39 + } + + filesystem { + start_sector: 1 + count: 39 + skew: 3 + } + } +} + +decoder { + tartu {} +} + +option_group { + comment: "$formats" + + option { + name: "390" + comment: '390kB 5.25" 40-track DSDD' + + config { + layout { + format_type: FORMATTYPE_40TRACK + tracks: 40 + sides: 2 + } + } + } + + option { + name: "780" + comment: '780kB 5.25" 80-track DSDD' + + config { + layout { + format_type: FORMATTYPE_80TRACK + tracks: 80 + sides: 2 + } + } + } +} + +filesystem { + type: CPMFS + cpmfs { + filesystem_start { + track: 1 + } + block_size: 2048 + dir_entries: 128 + } +}