diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..e9ef8513 --- /dev/null +++ b/.clang-format @@ -0,0 +1,42 @@ +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignArrayOfStructures: Left +AlignEscapedNewlines: Left +AllowAllArgumentsOnNextLine: 'true' +AllowAllConstructorInitializersOnNextLine: 'false' +AllowAllParametersOfDeclarationOnNextLine: 'true' +AllowShortBlocksOnASingleLine: 'true' +AllowShortCaseLabelsOnASingleLine: 'true' +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: None +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'true' +AlwaysBreakTemplateDeclarations: 'Yes' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakConstructorInitializers: 'AfterColon' +BreakBeforeBraces: Allman +BreakInheritanceList: AfterColon +BreakStringLiterals: 'true' +IndentCaseLabels: 'true' +IndentWidth: '4' +ColumnLimit: '80' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' +IncludeBlocks: Preserve +IndentWrappedFunctionNames: 'false' +KeepEmptyLinesAtTheStartOfBlocks: 'true' +PointerAlignment: Left +ReflowComments: 'true' +SortIncludes: 'false' +SortUsingDeclarations: 'true' +SpaceAfterTemplateKeyword: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCtorInitializerColon: 'false' +SpaceBeforeInheritanceColon: 'true' +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: 'false' +... diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 78543494..08f525f4 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -53,3 +53,12 @@ jobs: run: | make + - name: zip + run: | + zip -9 fluxengine.zip fluxengine.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex + + - name: Upload build artifacts + uses: actions/upload-artifact@v2 + with: + name: ${{ github.event.repository.name }}.${{ github.sha }} + path: fluxengine.zip diff --git a/FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex b/FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex index 51b9850d..4f737a9a 100644 --- a/FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex +++ b/FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex @@ -21,7 +21,7 @@ :4005000014BF0A461A46002808BF19468448FFF7D8FE0321084602F031F9264602F04EF994F8643043B1EA6EEB689B1A41F28832934201D9FFF7FCFE00F07EFF18B9794820 :40054000FFF7BFFE04E000F07DFF0028F7D10BE000F072FF10B902F031F9F9E77248FFF7B0FE032001F098F8032000F077FF0128D4D16E48FFF7EEFE6D490320FFF734FE85 :4005800094F876106B48FFF79CFE94F87630023B142B00F2D683DFE813F01500D4031E00D4032400D4035000D4037600D403D900D403C101D4030803D4032C03D40333036C -:4005C000D4034D0303238DF820308DF821300F238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD44 +:4005C000D4034D0303238DF820308DF8213010238DF822302AE394F87800FFF703FF564B21E340F2DC57FFF7DFFE00232375E068227D02F0FF012AB9EB681B1ABB42F7DD43 :400600000B4611E083B10022174696F87810F068277594F814E0BEF1000F02D1EB681B1AF7E701329142F3DA07228DF8202004228DF82120ADF82230F8E20220FFF782FD2F :400640004FF000080DF1200A02F0B8F84FF480790027C9EB0803DA1907F80A200137402FF9D10220FFF76EFD3A465146022000F04DFFB9F10109EBD108F10108B8F1400F58 :40068000E2D12E4B38E04FF0010A4FF000080DF1200B02F093F84FF0000959460120FFF7A3FD08EB090300270493049B1BF807203B44DBB29A4209D08DE80C0041463B4641 @@ -4615,12 +4615,12 @@ :0200000490105A :04000000BC90ACAF55 :0200000490303A -:0200000090CF9F +:0200000090D09E :0200000490402A :4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0 :400040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080 :400080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040 :4000C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 :0200000490501A -:0C00000000012E16106900002E30A138FF +:0C00000000012E16106900002E30A139FE :00000001FF \ No newline at end of file diff --git a/Makefile b/Makefile index 541be0ec..50bb0063 100644 --- a/Makefile +++ b/Makefile @@ -83,3 +83,5 @@ clean: @mkdir -p $(OBJDIR) @sh $< > $@ +compdb: + @ninja -f .obj/build.ninja -t compdb > compile_commands.json diff --git a/README.md b/README.md index ac96472d..5ce44cbd 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ people who've had it work). | [Brother 240kB](doc/disk-brother.md) | 🦄 | 🦄 | | | [Brother FB-100](doc/disk-fb100.md) | 🦖 | | Tandy Model 100, Husky Hunter, knitting machines | | [Macintosh 800kB](doc/disk-macintosh.md) | 🦄 | 🦄 | and probably the 400kB too | +| [NEC PC-98](doc/disk-ibm.md) | 🦄 | 🦄 | trimode drive not required | +| [Sharp X68000](doc/disk-ibm.md) | 🦄 | 🦄 | | | [TRS-80](doc/disk-trs80.md) | 🦖 | 🦖* | a minor variation of the IBM scheme | {: .datatable } @@ -125,9 +127,9 @@ at least, check the CRC so what data's there is probably good. | [DVK MX](doc/disk-mx.md) | 🦖 | | Soviet PDP-11 clone | | [VDS Eco1](doc/disk-eco1.md) | 🦖 | | 8" mixed format | | [Micropolis](doc/disk-micropolis.md) | 🦄 | | Micropolis 100tpi drives | -| [Northstar(doc/disk-northstar.md) | 🦖 | 🦖 | 5.25" hard sectors | +| [Northstar](doc/disk-northstar.md) | 🦖 | 🦖 | 5.25" hard sectors | | [TI DS990 FD1000](doc/disk-tids990.md) | 🦄 | 🦄 | 8" | -| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 8" | +| [Victor 9000](doc/disk-victor9k.md) | 🦖 | | 5.25" GCR encoded | | [Zilog MCZ](doc/disk-zilogmcz.md) | 🦖 | | 8" _and_ hard sectors | {: .datatable } diff --git a/doc/disk-ibm.md b/doc/disk-ibm.md index a2945200..188dd8df 100644 --- a/doc/disk-ibm.md +++ b/doc/disk-ibm.md @@ -14,6 +14,10 @@ metadata. Systems which use IBM scheme disks include but are not limited to: - the TRS-80 - late era Commodore machines (the 1571 and so on) - most CP/M machines + - NEC PC-88 series + - NEC PC-98 series + - Sharp X68000 + - Fujitsu FM Towns - etc FluxEngine supports reading these. However, some variants are more peculiar @@ -93,11 +97,12 @@ correct format to use. Mixed-format disks ------------------ -Some disks, usually those belonging to early CP/M machines, have more than one -format on the disk at once. Typically, the first few tracks will be low-density -FM encoded and will be read by the machine's ROM; those tracks contain new -floppy drive handling code capable of coping with MFM data, and so the rest of -the disk will use that, allowing them to store more data. +Some disks, such as those belonging to early CP/M machines, or N88-Basic disks +(for PC-88 and PC-98), have more than one format on the disk at once. Typically, +the first few tracks will be low-density FM encoded and will be read by the +machine's ROM; those tracks contain new floppy drive handling code capable of +coping with MFM data, and so the rest of the disk will use that, allowing them +to store more data. FluxEngine can read these fine, but it tends to get a bit confused when it sees tracks with differing numbers of sectors --- if track 0 has 32 sectors but @@ -105,6 +110,31 @@ track 1 has 16, it will assume that sectors 16..31 are missing on track 1 and size the image file accordingly. This can be worked around by specifying the size of each track; see the `eco1` read profile for an example. -Writing can be made to work too, but there is currently no example. Please [get -in touch](https://github.com/davidgiven/fluxengine/issues/new) if you have -specific requirements (nothing's come up yet). +N88-Basic format floppies can be written by either specifying the `n88basic` +format, or by using D88 or NFD format images which include explicit sector +layout information. + +Writing other formats can be made to work too, by creating a custom format +specifier, using the `n88basic` format as an example. +Please [get in touch](https://github.com/davidgiven/fluxengine/issues/new) if +you have specific requirements. + +360rpm 3.5" disks +----------------- + +Japanese PCs (NEC PC-98, Sharp X68000, Fujitsu FM Towns) spin their floppy +drives at 360rpm rather than the more typical 300rpm. This was done in order +to be fully backwards compatible with 5.25" disks, while using the exact +same floppy controller. Later models of the PC-9821, as well as most USB floppy +drives, feature "tri-mode" support which in addition to normal 300rpm modes, +can change their speed to read and write 360rpm DD and HD disks. + +Neither the FluxEngine or Greaseweazle hardware can currently command a +tri-mode drive to spin at 360rpm, however an older 360rpm-only drive will work +to read these formats. + +Alternately, the FluxEngine software can resale the flux pulses to enable +reading and writing these formats with a plain 300rpm drive. To do this, +specify the following two additional options: + + --flux_source.rescale=1.2 --flux_sink.rescale=1.2 diff --git a/doc/disk-victor9k.md b/doc/disk-victor9k.md index 01f49699..9bba7dc5 100644 --- a/doc/disk-victor9k.md +++ b/doc/disk-victor9k.md @@ -16,8 +16,8 @@ the speed zone allocation on head 1 differ from head 0... | 1 | 4-15 | 0-7 | 18 | 224.5 | | 2 | 16-26 | 8-18 | 17 | 212.2 | | 3 | 27-37 | 19-29 | 16 | 199.9 | -| 4 | 38-48 | 30-40 | 15 | 187.6 | -| 5 | 49-59 | 41-51 | 14 | 175.3 | +| 4 | 38-47\* | 30-39\* | 15 | 187.6 | +| 5 | 48-59 | 40-51 | 14 | 175.3 | | 6 | 60-70 | 52-62 | 13 | 163.0 | | 7 | 71-79 | 63-74 | 12 | 149.6 | | 8 | | 75-79 | 11 | 144.0 | @@ -26,8 +26,13 @@ the speed zone allocation on head 1 differ from head 0... FluxEngine, the disk always spins at 360 rpm, which corresponds to a rotational period of 166 ms.) -FluxEngine can read and write the single-sided variant of these. (Double-sided -will be trivial to do, it's just not done yet.) +\*The Victor 9000 Hardware Reference Manual has a bug in the documentation +and lists Zone 4 as ending with track 48 on head 0 and track 40 on head 1. +The above table matches observed data on various disks and the assembly +code in the boot loader, which ends Zone 4 with track 47 on head 0 +and track 39 on Head 1. + +FluxEngine can read and write both the single-sided and double-sided variants. Reading discs ------------- @@ -35,10 +40,14 @@ Reading discs Just do: ``` -fluxengine read victor9k-ss +fluxengine read + ``` -You should end up with an `victor9k.img` which is 627200 bytes long. +...where `` can be `victor9k_ss` or `victor9k_ds`. + +For `victor9k_ss` you should end up with an `victor9k.img` which is 627200 bytes long. +For `victor9k_ds` you should end up with an `victor9k.img` which is 1224192 bytes long. **Big warning!** The image is triangular, where each track occupies a different amount of space. Victor disk images are complicated due to the way the tracks @@ -50,7 +59,7 @@ Writing discs Just do: ``` -fluxengine read victor9k-ss -i victor9k.img +fluxengine read victor9k_ss -i victor9k.img ``` **Big warning!** This uses the same triangular disk image that reading uses. diff --git a/doc/greaseweazle.md b/doc/greaseweazle.md index 753dcbda..656715a9 100644 --- a/doc/greaseweazle.md +++ b/doc/greaseweazle.md @@ -16,6 +16,13 @@ FluxEngine makes things complicated when you're not using the FluxEngine client software with a FluxEngine board, but I'm afraid it's too late to change that now. Sorry. +**If you are using GreaseWeazle-compatible hardware** such as the +[adafruit-floppy](https://github.com/adafruit/Adafruit_Floppy) project, then +FluxEngine will still work; however, as the USB VID/PID won't be that of a real +GreaseWeazle, the the FluxEngine client can't autodetect it. Instead, you'll +need to specify the serial port manually with something like +`--usb.greaseweazle.port=/dev/ttyACM0` or `--usb.greaseweazle.port=COM5`. + **If you were using a previous version on Windows** you might have installed the WinUSB driver. That's no longer needed, and will in fact not work. You'll need to use Zadig to restore the old driver; to do this, make sure the left @@ -30,6 +37,8 @@ Supported features with the GreaseWeazle include: - simple reading and writing of disks, seeking etc - erasing disks - determining disk rotation speed + - both Shugart and normal IBM buses (via + `--usb.greaseweazle.bus_type=SHUGART` or `IBMPC`; the default is `IBMPC`) What doesn't work ----------------- diff --git a/doc/using.md b/doc/using.md index 5cecf464..e782f16a 100644 --- a/doc/using.md +++ b/doc/using.md @@ -267,7 +267,8 @@ FluxEngine also supports a number of file system image formats. When using the - `` Read from a [FDI image file](https://www.pc98.org/project/doc/hdi.html), - commonly used by PC-98 emulators. **Read Only.** + commonly used by PC-98 emulators. Supports automatically configuring + the encoder. **Read Only.** - `` @@ -280,16 +281,34 @@ FluxEngine also supports a number of file system image formats. When using the The D88 reader should be used with the `ibm` profile and will override most encoding parameters on a track-by-track basis. + - `` + + Read from a [NFD r0 image file](https://www.pc98.org/project/doc/nfdr0.html), + commonly used by various Japanese PC emulators, including the NEC PC-98. **Read Only.** + + Only r0 version files are currently supported. + + The NFD reader should be used with the `ibm` profile and will override + most encoding parameters on a track-by-track basis. + - `` - Write to a [LDBS generic image - file](https://www.seasip.info/Unix/LibDsk/ldbs.html). **Write only.** - - - `` + Write to a [LDBS generic image + file](https://www.seasip.info/Unix/LibDsk/ldbs.html). **Write only.** - Write to a [D64 image - file](http://unusedino.de/ec64/technical/formats/d64.html), commonly used - by Commodore 64 emulators. **Write only.** + - `` + + Write to a [D64 image + file](http://unusedino.de/ec64/technical/formats/d64.html), commonly used by + Commodore 64 emulators. **Write only.** + + - `` + + Write undecoded data to a raw binary file. **Write only.** This gives you the + underlying MFM, FM or GCR stream, without actually doing the decode into + user-visible bytes. However, the decode is still done in order to check for + correctness. Individual records are separated by three `\\0` bytes and tracks + are seperated by four `\\0` bytes; tracks are emitted in CHS order. ### High density disks @@ -339,6 +358,16 @@ behaviour. has no effect on the _drive_, so it doesn't help with flippy disks, but is useful for using very old drives with FluxEngine itself. If you use this option, then any index marks in the sampled flux are, of course, garbage. + + - `--flux_source.rescale=X, --flux_sink.rescale=X` + + When reading or writing a floppy on a drive that doesn't match the + original drive RPM, the flux periods can be scaled to compensate. + + For example, to read or write a PC-98 1.2MB (360rpm) floppy using a 300rpm + floppy drive: + + `--flux_source.rescale=1.2 --flux_sink.rescale=1.2` ## Visualisation diff --git a/lib/decoders/decoders.cc b/lib/decoders/decoders.cc index 0dee46e4..c206c1f9 100644 --- a/lib/decoders/decoders.cc +++ b/lib/decoders/decoders.cc @@ -132,6 +132,7 @@ void AbstractDecoder::pushRecord(const Fluxmap::Position& start, const Fluxmap:: auto record = std::make_shared(); _trackdata->records.push_back(record); + _sector->records.push_back(record); record->startTime = start.ns(); record->endTime = end.ns(); diff --git a/lib/fluxmap.cc b/lib/fluxmap.cc index d89ecab1..f1b814ae 100644 --- a/lib/fluxmap.cc +++ b/lib/fluxmap.cc @@ -108,3 +108,22 @@ std::vector Fluxmap::split() { maps.push_back(map); return maps; } + +void Fluxmap::rescale(double scale) { + if (scale != 1.0) { + auto bytesOrig = _bytes; + _bytes = Bytes(); + _duration = 0; + _ticks = 0; + int lastEvent = 0; + for (unsigned i=0; i split(); + void rescale(double scale); private: uint8_t& findLastByte(); diff --git a/lib/fluxsink/fluxsink.proto b/lib/fluxsink/fluxsink.proto index af11f568..59ce0ed3 100644 --- a/lib/fluxsink/fluxsink.proto +++ b/lib/fluxsink/fluxsink.proto @@ -29,6 +29,7 @@ message Fl2FluxSinkProto { } message FluxSinkProto { + optional double rescale = 7 [ default = 1.0, (help) = "amount to multiply pulse periods by" ]; oneof dest { string fluxfile = 1 [(help) = "name of destination flux file"]; HardwareFluxSinkProto drive = 2; diff --git a/lib/fluxsource/fl2fluxsource.cc b/lib/fluxsource/fl2fluxsource.cc index 0cbfb1b8..8ffcfe49 100644 --- a/lib/fluxsource/fl2fluxsource.cc +++ b/lib/fluxsource/fl2fluxsource.cc @@ -31,7 +31,7 @@ public: return std::make_unique(track.flux()); } - return std::unique_ptr(); + return std::make_unique(); } void recalibrate() {} diff --git a/lib/fluxsource/fluxsource.proto b/lib/fluxsource/fluxsource.proto index c5ce6243..8019f31f 100644 --- a/lib/fluxsource/fluxsource.proto +++ b/lib/fluxsource/fluxsource.proto @@ -39,6 +39,7 @@ message Fl2FluxSourceProto { } message FluxSourceProto { + optional double rescale = 9 [ default = 1.0, (help) = "amount to divide pulse periods by" ]; oneof source { string fluxfile = 1 [default = "name of source flux file"]; HardwareFluxSourceProto drive = 2; diff --git a/lib/imagereader/d88imagereader.cc b/lib/imagereader/d88imagereader.cc index 0e1af925..cfdaf93d 100644 --- a/lib/imagereader/d88imagereader.cc +++ b/lib/imagereader/d88imagereader.cc @@ -5,7 +5,6 @@ #include "image.h" #include "proto.h" #include "lib/config.pb.h" -#include "imagereader/imagereaderimpl.h" #include "fmt/format.h" #include #include @@ -166,6 +165,21 @@ public: const Geometry& geometry = image->getGeometry(); std::cout << fmt::format("D88: read {} tracks, {} sides\n", geometry.numTracks, geometry.numSides); + + if (!config.has_heads()) + { + auto* heads = config.mutable_heads(); + heads->set_start(0); + heads->set_end(geometry.numSides - 1); + } + + if (!config.has_cylinders()) + { + auto* cylinders = config.mutable_cylinders(); + cylinders->set_start(0); + cylinders->set_end(geometry.numTracks - 1); + } + return image; } diff --git a/lib/imagereader/dimimagereader.cc b/lib/imagereader/dimimagereader.cc index 5c40b86d..161ca225 100644 --- a/lib/imagereader/dimimagereader.cc +++ b/lib/imagereader/dimimagereader.cc @@ -5,7 +5,6 @@ #include "image.h" #include "proto.h" #include "lib/config.pb.h" -#include "imagereader/imagereaderimpl.h" #include "fmt/format.h" #include #include @@ -130,6 +129,8 @@ public: Error() << fmt::format("DIM: unknown media byte 0x%02x, could not determine write profile automatically", mediaByte); break; } + + config.mutable_decoder()->mutable_ibm(); } image->calculateSize(); @@ -137,6 +138,21 @@ public: std::cout << fmt::format("DIM: read {} tracks, {} sides, {} kB total\n", geometry.numTracks, geometry.numSides, ((int)inputFile.tellg() - 256) / 1024); + + if (!config.has_heads()) + { + auto* heads = config.mutable_heads(); + heads->set_start(0); + heads->set_end(geometry.numSides - 1); + } + + if (!config.has_cylinders()) + { + auto* cylinders = config.mutable_cylinders(); + cylinders->set_start(0); + cylinders->set_end(geometry.numTracks - 1); + } + return image; } diff --git a/lib/imagereader/fdiimagereader.cc b/lib/imagereader/fdiimagereader.cc index b16de4f1..3c65dd73 100644 --- a/lib/imagereader/fdiimagereader.cc +++ b/lib/imagereader/fdiimagereader.cc @@ -3,8 +3,8 @@ #include "sector.h" #include "imagereader/imagereader.h" #include "image.h" +#include "proto.h" #include "lib/config.pb.h" -#include "imagereader/imagereaderimpl.h" #include "fmt/format.h" #include #include @@ -76,11 +76,54 @@ public: trackCount++; } + if (config.encoder().format_case() == EncoderProto::FormatCase::FORMAT_NOT_SET) + { + auto ibm = config.mutable_encoder()->mutable_ibm(); + auto trackdata = ibm->add_trackdata(); + trackdata->set_clock_rate_khz(500); + auto sectors = trackdata->mutable_sectors(); + switch (fddType) { + case 0x90: + std::cout << "FDI: automatically setting format to 1.2MB (1024 byte sectors)\n"; + config.mutable_cylinders()->set_end(76); + trackdata->set_track_length_ms(167); + trackdata->set_sector_size(1024); + for (int i = 0; i < 9; i++) + sectors->add_sector(i); + break; + case 0x30: + std::cout << "FDI: automatically setting format to 1.44MB\n"; + trackdata->set_track_length_ms(200); + trackdata->set_sector_size(512); + for (int i = 0; i < 18; i++) + sectors->add_sector(i); + break; + default: + Error() << fmt::format("FDI: unknown fdd type 0x%02x, could not determine write profile automatically", fddType); + break; + } + } + image->calculateSize(); const Geometry& geometry = image->getGeometry(); std::cout << fmt::format("FDI: read {} tracks, {} sides, {} kB total\n", geometry.numTracks, geometry.numSides, ((int)inputFile.tellg() - headerSize) / 1024); + + if (!config.has_heads()) + { + auto* heads = config.mutable_heads(); + heads->set_start(0); + heads->set_end(geometry.numSides - 1); + } + + if (!config.has_cylinders()) + { + auto* cylinders = config.mutable_cylinders(); + cylinders->set_start(0); + cylinders->set_end(geometry.numTracks - 1); + } + return image; } diff --git a/lib/imagereader/imagereader.cc b/lib/imagereader/imagereader.cc index 89fc1e4d..943cd28a 100644 --- a/lib/imagereader/imagereader.cc +++ b/lib/imagereader/imagereader.cc @@ -38,6 +38,9 @@ std::unique_ptr ImageReader::create(const ImageReaderProto& config) case ImageReaderProto::kD64: return ImageReader::createD64ImageReader(config); + case ImageReaderProto::kNfd: + return ImageReader::createNFDImageReader(config); + case ImageReaderProto::kNsi: return ImageReader::createNsiImageReader(config); @@ -65,6 +68,7 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st {".imd", [&]() { proto->mutable_imd(); }}, {".img", [&]() { proto->mutable_img(); }}, {".st", [&]() { proto->mutable_img(); }}, + {".nfd", [&]() { proto->mutable_nfd(); }}, {".nsi", [&]() { proto->mutable_nsi(); }}, {".td0", [&]() { proto->mutable_td0(); }}, {".vgi", [&]() { proto->mutable_img(); }}, @@ -87,20 +91,3 @@ void ImageReader::updateConfigForFilename(ImageReaderProto* proto, const std::st ImageReader::ImageReader(const ImageReaderProto& config): _config(config) {} - -void getTrackFormat(const ImgInputOutputProto& config, - ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side) -{ - trackdata.Clear(); - for (const ImgInputOutputProto::TrackdataProto& f : config.trackdata()) - { - if (f.has_track() && f.has_up_to_track() && ((track < f.track()) || (track > f.up_to_track()))) - continue; - if (f.has_track() && !f.has_up_to_track() && (track != f.track())) - continue; - if (f.has_side() && (f.side() != side)) - continue; - - trackdata.MergeFrom(f); - } -} diff --git a/lib/imagereader/imagereader.h b/lib/imagereader/imagereader.h index e17ac02b..1996f84d 100644 --- a/lib/imagereader/imagereader.h +++ b/lib/imagereader/imagereader.h @@ -27,6 +27,7 @@ public: static std::unique_ptr createDimImageReader(const ImageReaderProto& config); static std::unique_ptr createFdiImageReader(const ImageReaderProto& config); static std::unique_ptr createD88ImageReader(const ImageReaderProto& config); + static std::unique_ptr createNFDImageReader(const ImageReaderProto& config); public: virtual std::unique_ptr readImage() = 0; diff --git a/lib/imagereader/imagereader.proto b/lib/imagereader/imagereader.proto index 88676606..dd5002f8 100644 --- a/lib/imagereader/imagereader.proto +++ b/lib/imagereader/imagereader.proto @@ -3,6 +3,12 @@ syntax = "proto2"; import "lib/common.proto"; message ImgInputOutputProto { + enum Order { + UNDEFINED = 0; + CHS = 1; + HCS = 2; + } + message SectorsProto { repeated int32 sector = 1 [(help) = "sector ID"]; } @@ -30,6 +36,7 @@ message ImgInputOutputProto { optional int32 sides = 6 [default=0, (help) = "number of sides in image"]; optional int32 physical_offset = 7 [default=0, (help) = "logical:physical track offset"]; optional int32 physical_step = 8 [default=1, (help) = "logical:physical track step"]; + optional Order order = 9 [default=CHS, (help) = "the order in which to emit tracks in the image"]; } message DiskCopyInputProto {} @@ -41,6 +48,7 @@ message Td0InputProto {} message DimInputProto {} message FdiInputProto {} message D88InputProto {} +message NFDInputProto {} message ImageReaderProto { optional string filename = 1 [(help) = "filename of input sector image"]; @@ -55,6 +63,7 @@ message ImageReaderProto { DimInputProto dim = 9; FdiInputProto fdi = 10; D88InputProto d88 = 11; + NFDInputProto nfd = 12; } } diff --git a/lib/imagereader/imagereaderimpl.h b/lib/imagereader/imagereaderimpl.h deleted file mode 100644 index ed80cba7..00000000 --- a/lib/imagereader/imagereaderimpl.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef IMAGEREADERIMPL_H -#define IMAGEREADERIMPL_H - -extern void getTrackFormat(const ImgInputOutputProto& config, - ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side); - -#endif - diff --git a/lib/imagereader/imgimagereader.cc b/lib/imagereader/imgimagereader.cc index 61f1ea68..81a525c1 100644 --- a/lib/imagereader/imgimagereader.cc +++ b/lib/imagereader/imgimagereader.cc @@ -4,7 +4,7 @@ #include "imagereader/imagereader.h" #include "image.h" #include "lib/config.pb.h" -#include "imagereader/imagereaderimpl.h" +#include "imginputoutpututils.h" #include "fmt/format.h" #include #include @@ -27,34 +27,31 @@ public: Error() << "IMG: bad configuration; did you remember to set the tracks, sides and trackdata fields?"; std::unique_ptr image(new Image); - int trackCount = 0; - for (int track = 0; track < _config.img().tracks(); track++) - { + for (const auto& p : getTrackOrdering(_config.img())) + { + int track = p.first; + int side = p.second; + if (inputFile.eof()) break; int physicalCylinder = track * _config.img().physical_step() + _config.img().physical_offset(); - for (int side = 0; side < _config.img().sides(); side++) - { - ImgInputOutputProto::TrackdataProto trackdata; - getTrackFormat(_config.img(), trackdata, track, side); + ImgInputOutputProto::TrackdataProto trackdata; + getTrackFormat(_config.img(), trackdata, track, side); - for (int sectorId : getSectors(trackdata)) - { - Bytes data(trackdata.sector_size()); - inputFile.read((char*) data.begin(), data.size()); + for (int sectorId : getSectors(trackdata)) + { + Bytes data(trackdata.sector_size()); + inputFile.read((char*) data.begin(), data.size()); - const auto& sector = image->put(physicalCylinder, side, sectorId); - sector->status = Sector::OK; - sector->logicalTrack = track; - sector->physicalCylinder = physicalCylinder; - sector->logicalSide = sector->physicalHead = side; - sector->logicalSector = sectorId; - sector->data = data; - } - } - - trackCount++; + const auto& sector = image->put(physicalCylinder, side, sectorId); + sector->status = Sector::OK; + sector->logicalTrack = track; + sector->physicalCylinder = physicalCylinder; + sector->logicalSide = sector->physicalHead = side; + sector->logicalSector = sectorId; + sector->data = data; + } } image->calculateSize(); diff --git a/lib/imagereader/nfdimagereader.cc b/lib/imagereader/nfdimagereader.cc new file mode 100644 index 00000000..cd41b350 --- /dev/null +++ b/lib/imagereader/nfdimagereader.cc @@ -0,0 +1,149 @@ +#include "globals.h" +#include "flags.h" +#include "sector.h" +#include "imagereader/imagereader.h" +#include "image.h" +#include "proto.h" +#include "lib/config.pb.h" +#include "fmt/format.h" +#include +#include +#include + +// reader based on this partial documentation of the D88 format: +// https://www.pc98.org/project/doc/d88.html + +class NFDImageReader : public ImageReader +{ +public: + NFDImageReader(const ImageReaderProto& config): + ImageReader(config) + {} + + std::unique_ptr readImage() + { + std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary); + if (!inputFile.is_open()) + Error() << "cannot open input file"; + + Bytes fileId(14); // read first entry of track table as well + inputFile.read((char*) fileId.begin(), fileId.size()); + + if (fileId == Bytes("T98FDDIMAGE.R1")) { + Error() << "NFD: r1 images are not currently supported"; + } + if (fileId != Bytes("T98FDDIMAGE.R0")) { + Error() << "NFD: could not find NFD header"; + } + + Bytes header(0x10a10); + inputFile.seekg( 0, std::ios::beg ); + inputFile.read((char*) header.begin(), header.size()); + + ByteReader headerReader(header); + + char heads = headerReader.seek(0x115).read_8(); + if (heads != 2) { + Error() << "NFD: unsupported number of heads"; + } + + if (config.encoder().format_case() != EncoderProto::FormatCase::FORMAT_NOT_SET) + std::cout << "NFD: overriding configured format"; + + auto ibm = config.mutable_encoder()->mutable_ibm(); + config.mutable_cylinders()->set_end(0); + std::cout << "NFD: HD 1.2MB mode\n"; + if (config.flux_sink().dest_case() == FluxSinkProto::DestCase::kDrive) { + config.mutable_flux_sink()->mutable_drive()->set_high_density(true); + } + + std::unique_ptr image(new Image); + for (int track = 0; track < 163; track++) + { + auto trackdata = ibm->add_trackdata(); + trackdata->set_clock_rate_khz(500); + trackdata->set_track_length_ms(167); + auto sectors = trackdata->mutable_sectors(); + int currentTrackCylinder = -1; + int currentTrackHead = -1; + int trackSectorSize = -1; + + for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++){ + headerReader.seek(0x120 + track * 26 * 16 + sectorInTrack * 16); + int cylinder = headerReader.read_8(); + int head = headerReader.read_8(); + int sectorId = headerReader.read_8(); + int sectorSize = 128 << headerReader.read_8(); + int mfm = headerReader.read_8(); + int ddam = headerReader.read_8(); + int status = headerReader.read_8(); + headerReader.skip(9); // skip ST0, ST1, ST2, PDA, reserved(5) + if (cylinder == 0xFF) + continue; + if (ddam != 0) + Error() << "NFD: nonzero ddam currently unsupported"; + if (status != 0) + Error() << "NFD: nonzero fdd status codes are currently unsupported"; + if (currentTrackCylinder < 0) { + currentTrackCylinder = cylinder; + currentTrackHead = head; + } else if (currentTrackCylinder != cylinder) { + Error() << "NFD: all sectors in a track must belong to the same cylinder"; + } else if (currentTrackHead != head) { + Error() << "NFD: all sectors in a track must belong to the same head"; + } + if (trackSectorSize < 0) { + trackSectorSize = sectorSize; + // this is the first sector we've read, use it settings for per-track data + trackdata->set_cylinder(cylinder); + trackdata->set_head(head); + trackdata->set_sector_size(sectorSize); + trackdata->set_use_fm(!mfm); + if (!mfm) { + trackdata->set_gap_fill_byte(0xffff); + trackdata->set_idam_byte(0xf57e); + trackdata->set_dam_byte(0xf56f); + } + // create timings to approximately match N88-BASIC + if (sectorSize <= 128) { + trackdata->set_gap0(0x1b); + trackdata->set_gap2(0x09); + trackdata->set_gap3(0x1b); + } else if (sectorSize <= 256) { + trackdata->set_gap0(0x36); + trackdata->set_gap3(0x36); + } + } else if (trackSectorSize != sectorSize) { + Error() << "NFD: multiple sector sizes per track are currently unsupported"; + } + Bytes data(sectorSize); + inputFile.read((char*) data.begin(), data.size()); + const auto& sector = image->put(cylinder, head, sectorId); + sector->status = Sector::OK; + sector->logicalTrack = cylinder; + sector->physicalCylinder = cylinder; + sector->logicalSide = sector->physicalHead = head; + sector->logicalSector = sectorId; + sector->data = data; + + sectors->add_sector(sectorId); + if (config.cylinders().end() < cylinder) + config.mutable_cylinders()->set_end(cylinder); + } + } + + image->calculateSize(); + const Geometry& geometry = image->getGeometry(); + std::cout << fmt::format("NFD: read {} tracks, {} sides\n", + geometry.numTracks, geometry.numSides); + return image; + } + +}; + +std::unique_ptr ImageReader::createNFDImageReader( + const ImageReaderProto& config) +{ + return std::unique_ptr(new NFDImageReader(config)); +} + diff --git a/lib/imagewriter/imagewriter.cc b/lib/imagewriter/imagewriter.cc index 2e27e95b..dc0849ed 100644 --- a/lib/imagewriter/imagewriter.cc +++ b/lib/imagewriter/imagewriter.cc @@ -29,6 +29,9 @@ std::unique_ptr ImageWriter::create(const ImageWriterProto& config) case ImageWriterProto::kNsi: return ImageWriter::createNsiImageWriter(config); + case ImageWriterProto::kRaw: + return ImageWriter::createRawImageWriter(config); + default: Error() << "bad output image config"; return std::unique_ptr(); @@ -45,8 +48,9 @@ void ImageWriter::updateConfigForFilename(ImageWriterProto* proto, const std::st {".diskcopy", [&]() { proto->mutable_diskcopy(); }}, {".img", [&]() { proto->mutable_img(); }}, {".ldbs", [&]() { proto->mutable_ldbs(); }}, - {".st", [&]() { proto->mutable_img(); }}, {".nsi", [&]() { proto->mutable_nsi(); }}, + {".raw", [&]() { proto->mutable_raw(); }}, + {".st", [&]() { proto->mutable_img(); }}, {".vgi", [&]() { proto->mutable_img(); }}, {".xdf", [&]() { proto->mutable_img(); }}, }; diff --git a/lib/imagewriter/imagewriter.h b/lib/imagewriter/imagewriter.h index 321b9355..143c4af9 100644 --- a/lib/imagewriter/imagewriter.h +++ b/lib/imagewriter/imagewriter.h @@ -14,16 +14,12 @@ public: static std::unique_ptr create(const ImageWriterProto& config); static void updateConfigForFilename(ImageWriterProto* proto, const std::string& filename); - static std::unique_ptr createImgImageWriter( - const ImageWriterProto& config); - static std::unique_ptr createLDBSImageWriter( - const ImageWriterProto& config); - static std::unique_ptr createD64ImageWriter( - const ImageWriterProto& config); - static std::unique_ptr createDiskCopyImageWriter( - const ImageWriterProto& config); - static std::unique_ptr createNsiImageWriter( - const ImageWriterProto& config); + static std::unique_ptr createD64ImageWriter(const ImageWriterProto& config); + static std::unique_ptr createDiskCopyImageWriter(const ImageWriterProto& config); + static std::unique_ptr createImgImageWriter(const ImageWriterProto& config); + static std::unique_ptr createLDBSImageWriter(const ImageWriterProto& config); + static std::unique_ptr createNsiImageWriter(const ImageWriterProto& config); + static std::unique_ptr createRawImageWriter(const ImageWriterProto& config); public: void printMap(const Image& sectors); diff --git a/lib/imagewriter/imagewriter.proto b/lib/imagewriter/imagewriter.proto index 6585e026..22490fbf 100644 --- a/lib/imagewriter/imagewriter.proto +++ b/lib/imagewriter/imagewriter.proto @@ -29,6 +29,7 @@ message LDBSOutputProto { message DiskCopyOutputProto {} message NsiOutputProto {} +message RawOutputProto {} message ImageWriterProto { optional string filename = 1 [(help) = "filename of output sector image"]; @@ -38,6 +39,7 @@ message ImageWriterProto { LDBSOutputProto ldbs = 4; DiskCopyOutputProto diskcopy = 5; NsiOutputProto nsi = 6; + RawOutputProto raw = 7; } } diff --git a/lib/imagewriter/imgimagewriter.cc b/lib/imagewriter/imgimagewriter.cc index 7f67a433..1bb163f7 100644 --- a/lib/imagewriter/imgimagewriter.cc +++ b/lib/imagewriter/imgimagewriter.cc @@ -4,7 +4,7 @@ #include "imagewriter/imagewriter.h" #include "image.h" #include "lib/config.pb.h" -#include "imagereader/imagereaderimpl.h" +#include "imginputoutpututils.h" #include "fmt/format.h" #include #include @@ -28,31 +28,31 @@ public: if (!outputFile.is_open()) Error() << "cannot open output file"; - for (int track = 0; track < tracks; track++) + for (const auto& p : getTrackOrdering(_config.img(), tracks, sides)) { - for (int side = 0; side < sides; side++) + int track = p.first; + int side = p.second; + + ImgInputOutputProto::TrackdataProto trackdata; + getTrackFormat(_config.img(), trackdata, track, side); + + auto sectors = getSectors(trackdata, geometry.numSectors); + if (sectors.empty()) { - ImgInputOutputProto::TrackdataProto trackdata; - getTrackFormat(_config.img(), trackdata, track, side); + int maxSector = geometry.firstSector + geometry.numSectors - 1; + for (int i=geometry.firstSector; i<=maxSector; i++) + sectors.push_back(i); + } - auto sectors = getSectors(trackdata, geometry.numSectors); - if (sectors.empty()) - { - int maxSector = geometry.firstSector + geometry.numSectors - 1; - for (int i=geometry.firstSector; i<=maxSector; i++) - sectors.push_back(i); - } + int sectorSize = trackdata.has_sector_size() ? trackdata.sector_size() : geometry.sectorSize; - int sectorSize = trackdata.has_sector_size() ? trackdata.sector_size() : geometry.sectorSize; - - for (int sectorId : sectors) - { - const auto& sector = image.get(track, side, sectorId); - if (sector) - sector->data.slice(0, sectorSize).writeTo(outputFile); - else - outputFile.seekp(sectorSize, std::ios::cur); - } + for (int sectorId : sectors) + { + const auto& sector = image.get(track, side, sectorId); + if (sector) + sector->data.slice(0, sectorSize).writeTo(outputFile); + else + outputFile.seekp(sectorSize, std::ios::cur); } } diff --git a/lib/imagewriter/rawimagewriter.cc b/lib/imagewriter/rawimagewriter.cc new file mode 100644 index 00000000..b97512cd --- /dev/null +++ b/lib/imagewriter/rawimagewriter.cc @@ -0,0 +1,73 @@ +#include "globals.h" +#include "flags.h" +#include "sector.h" +#include "imagewriter/imagewriter.h" +#include "fmt/format.h" +#include "decoders/decoders.h" +#include "image.h" +#include "arch/northstar/northstar.h" +#include "lib/imagewriter/imagewriter.pb.h" +#include +#include +#include + +class RawImageWriter : public ImageWriter +{ +public: + RawImageWriter(const ImageWriterProto& config): + ImageWriter(config) + {} + + void writeImage(const Image& image) + { + const Geometry& geometry = image.getGeometry(); + + size_t trackSize = geometry.numSectors * geometry.sectorSize; + + if (geometry.numTracks * trackSize == 0) { + std::cout << "RAW: no sectors in output; skipping image file generation." << std::endl; + return; + } + + std::cout << fmt::format("RAW: writing {} cylinders, {} sides\n", + geometry.numTracks, geometry.numSides); + + std::ofstream outputFile(_config.filename(), std::ios::out | std::ios::binary); + if (!outputFile.is_open()) + Error() << "RAW: cannot open output file"; + + unsigned sectorFileOffset; + for (int track = 0; track < geometry.numTracks * geometry.numSides; track++) + { + int side = (track < geometry.numTracks) ? 0 : 1; + + std::vector> records; + for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++) + { + const auto& sector = image.get(track % geometry.numTracks, side, sectorId); + if (sector) + records.insert(records.end(), sector->records.begin(), sector->records.end()); + } + + std::sort(records.begin(), records.end(), + [&](std::shared_ptr left, std::shared_ptr right) { + return left->startTime < right->startTime; + }); + + for (const auto& record : records) + { + record->rawData.writeTo(outputFile); + Bytes(3).writeTo(outputFile); + } + Bytes(1).writeTo(outputFile); + } + } +}; + + +std::unique_ptr ImageWriter::createRawImageWriter( + const ImageWriterProto& config) +{ + return std::unique_ptr(new RawImageWriter(config)); +} + diff --git a/lib/imginputoutpututils.cc b/lib/imginputoutpututils.cc new file mode 100644 index 00000000..641c7d8f --- /dev/null +++ b/lib/imginputoutpututils.cc @@ -0,0 +1,57 @@ +#include "globals.h" +#include "lib/imagereader/imagereader.pb.h" +#include "imginputoutpututils.h" + +std::vector> getTrackOrdering(const ImgInputOutputProto& config, + unsigned numTracks, unsigned numSides) +{ + int tracks = config.has_tracks() ? config.tracks() : numTracks; + int sides = config.has_sides() ? config.sides() : numSides; + + std::vector> ordering; + switch (config.order()) + { + case ImgInputOutputProto::CHS: + { + for (int track = 0; track < tracks; track++) + { + for (int side = 0; side < sides; side++) + ordering.push_back(std::make_pair(track, side)); + } + break; + } + + case ImgInputOutputProto::HCS: + { + for (int side = 0; side < sides; side++) + { + for (int track = 0; track < tracks; track++) + ordering.push_back(std::make_pair(track, side)); + } + break; + } + + default: + Error() << "IMG: invalid track ordering"; + } + + return ordering; +} + +void getTrackFormat(const ImgInputOutputProto& config, + ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side) +{ + trackdata.Clear(); + for (const ImgInputOutputProto::TrackdataProto& f : config.trackdata()) + { + if (f.has_track() && f.has_up_to_track() && ((track < f.track()) || (track > f.up_to_track()))) + continue; + if (f.has_track() && !f.has_up_to_track() && (track != f.track())) + continue; + if (f.has_side() && (f.side() != side)) + continue; + + trackdata.MergeFrom(f); + } +} + diff --git a/lib/imginputoutpututils.h b/lib/imginputoutpututils.h new file mode 100644 index 00000000..d12a6ac4 --- /dev/null +++ b/lib/imginputoutpututils.h @@ -0,0 +1,12 @@ +#ifndef IMGINPUTOUTPUTUTILS_H +#define IMGINPUTOUTPUTUTILS_H + +extern std::vector> getTrackOrdering(const ImgInputOutputProto& config, + unsigned numTracks = 0, unsigned numSides = 0); + +extern void getTrackFormat(const ImgInputOutputProto& config, + ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side); + +#endif + + diff --git a/lib/reader.cc b/lib/reader.cc index 6ae2167e..e3f29bb4 100644 --- a/lib/reader.cc +++ b/lib/reader.cc @@ -25,6 +25,7 @@ static std::shared_ptr readFluxmap(FluxSource& fluxsource, unsigned cyl { std::cout << fmt::format("{0:>3}.{1}: ", cylinder, head) << std::flush; std::shared_ptr fluxmap = fluxsource.readFlux(cylinder, head); + fluxmap->rescale(1.0/config.flux_source().rescale()); std::cout << fmt::format( "{0:.0} ms in {1} bytes\n", fluxmap->duration()/1e6, @@ -52,7 +53,7 @@ static std::set> collect_sectors(std::set(sectorid), std::get<1>(sectorid), std::get<2>(sectorid)); + std::get<2>(sectorid)); replacing->status = replacement->status = Sector::CONFLICT; } } diff --git a/lib/sector.h b/lib/sector.h index 6ad2862e..1bfd0706 100644 --- a/lib/sector.h +++ b/lib/sector.h @@ -4,6 +4,8 @@ #include "bytes.h" #include "fluxmap.h" +class Record; + /* * Note that sectors here used zero-based numbering throughout (to make the * maths easier); traditionally floppy disk use 0-based track numbering and @@ -32,12 +34,13 @@ public: nanoseconds_t headerEndTime = 0; nanoseconds_t dataStartTime = 0; nanoseconds_t dataEndTime = 0; - int physicalCylinder = 0; - int physicalHead = 0; - int logicalTrack = 0; - int logicalSide = 0; - int logicalSector = 0; + unsigned physicalCylinder = 0; + unsigned physicalHead = 0; + unsigned logicalTrack = 0; + unsigned logicalSide = 0; + unsigned logicalSector = 0; Bytes data; + std::vector> records; std::tuple key() const { return std::make_tuple(logicalTrack, logicalSide, logicalSector, status); } diff --git a/lib/usb/greaseweazleusb.cc b/lib/usb/greaseweazleusb.cc index 9d3efe07..74215f69 100644 --- a/lib/usb/greaseweazleusb.cc +++ b/lib/usb/greaseweazleusb.cc @@ -4,6 +4,7 @@ #include "fluxmap.h" #include "bytes.h" #include "fmt/format.h" +#include "lib/usb/usb.pb.h" #include "greaseweazle.h" #include "serial.h" @@ -61,8 +62,9 @@ private: } public: - GreaseWeazleUsb(const std::string& port): - _serial(SerialPort::openSerialPort(port)) + GreaseWeazleUsb(const std::string& port, const GreaseWeazleProto& config): + _serial(SerialPort::openSerialPort(port)), + _config(config) { int version = getVersion(); if (version >= 29) @@ -79,7 +81,7 @@ public: /* Configure the hardware. */ - do_command({ CMD_SET_BUS_TYPE, 3, BUS_IBMPC }); + do_command({ CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type() }); } int getVersion() @@ -129,7 +131,7 @@ public: .write_8(CMD_READ_FLUX) .write_8(cmd.size()) .write_le32(0) //ticks default value (guessed) - .write_le32(2);//guessed + .write_le16(2);//revolutions do_command(cmd); } } @@ -276,14 +278,13 @@ public: if (hardSectorThreshold != 0) Error() << "hard sectors are currently unsupported on the GreaseWeazel"; - int revolutions = (readTime+_revolutions-1) / _revolutions; - do_command({ CMD_HEAD, 3, (uint8_t)side }); switch (_version) { case V22: { + int revolutions = (readTime+_revolutions-1) / _revolutions; Bytes cmd(4); cmd.writer() .write_8(CMD_READ_FLUX) @@ -300,8 +301,8 @@ public: cmd.writer() .write_8(CMD_READ_FLUX) .write_8(cmd.size()) - .write_le32(0) //ticks default value (guessed) - .write_le32(revolutions + (synced ? 1 : 0)); + .write_le32((readTime + (synced ? _revolutions : 0)) / _clock) + .write_le32(0); do_command(cmd); } } @@ -384,14 +385,15 @@ private: }; std::unique_ptr _serial; + const GreaseWeazleProto& _config; int _version; nanoseconds_t _clock; nanoseconds_t _revolutions; }; -USB* createGreaseWeazleUsb(const std::string& port) +USB* createGreaseWeazleUsb(const std::string& port, const GreaseWeazleProto& config) { - return new GreaseWeazleUsb(port); + return new GreaseWeazleUsb(port, config); } // vim: sw=4 ts=4 et diff --git a/lib/usb/serial.cc b/lib/usb/serial.cc index 80efa83c..1c6bd3ca 100644 --- a/lib/usb/serial.cc +++ b/lib/usb/serial.cc @@ -45,8 +45,16 @@ COMMTIMEOUTS commtimeouts = {0}; commtimeouts.ReadIntervalTimeout = 100; SetCommTimeouts(_handle, &commtimeouts); + + if (!EscapeCommFunction(_handle, CLRDTR)) + Error() << fmt::format("Couldn't clear DTR: {}", + get_last_error_string()); + Sleep(200); + if (!EscapeCommFunction(_handle, SETDTR)) + Error() << fmt::format("Couldn't set DTR: {}", + get_last_error_string()); - PurgeComm(_handle, PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_TXCLEAR); + PurgeComm(_handle, PURGE_RXABORT|PURGE_RXCLEAR|PURGE_TXABORT|PURGE_TXCLEAR); } ~SerialPortImpl() override @@ -113,6 +121,7 @@ #else #include + #include class SerialPortImpl : public SerialPort { @@ -138,6 +147,15 @@ t.c_cc[VMIN] = 1; cfsetspeed(&t, 9600); tcsetattr(_fd, TCSANOW, &t); + + /* Toggle DTR to reset the device. */ + + int flag = TIOCM_DTR; + if (ioctl(_fd, TIOCMBIC, &flag) == -1) + Error() << fmt::format("cannot clear DTR on serial port: {}", strerror(errno)); + usleep(200000); + if (ioctl(_fd, TIOCMBIS, &flag) == -1) + Error() << fmt::format("cannot set DTR on serial port: {}", strerror(errno)); } ~SerialPortImpl() override diff --git a/lib/usb/usb.cc b/lib/usb/usb.cc index 32e6acae..95c4577d 100644 --- a/lib/usb/usb.cc +++ b/lib/usb/usb.cc @@ -13,70 +13,87 @@ static USB* usb = NULL; -USB::~USB() -{} +USB::~USB() {} static std::unique_ptr selectDevice() { - auto candidates = findUsbDevices({ FLUXENGINE_ID, GREASEWEAZLE_ID }); - if (candidates.size() == 0) - Error() << "no devices found (is one plugged in? Do you have the appropriate " - "permissions?"; + auto candidates = findUsbDevices({FLUXENGINE_ID, GREASEWEAZLE_ID}); + if (candidates.size() == 0) + Error() << "no devices found (is one plugged in? Do you have the " + "appropriate permissions?"; - if (config.usb().has_serial()) - { - auto wantedSerial = config.usb().serial(); - for (auto& c : candidates) - { - if (c->serial == wantedSerial) - return std::move(c); - } - Error() << "serial number not found (try without one to list or autodetect devices)"; - } + if (config.usb().has_serial()) + { + auto wantedSerial = config.usb().serial(); + for (auto& c : candidates) + { + if (c->serial == wantedSerial) + return std::move(c); + } + Error() << "serial number not found (try without one to list or " + "autodetect devices)"; + } - if (candidates.size() == 1) - return std::move(candidates[0]); + if (candidates.size() == 1) + return std::move(candidates[0]); - std::cerr << "More than one device detected; use --usb.serial= to select one:\n"; - for (const auto& c : candidates) - { - std::cerr << " "; - switch (c->id) - { - case FLUXENGINE_ID: - std::cerr << fmt::format("FluxEngine: {}\n", c->serial); - break; + std::cerr << "More than one device detected; use --usb.serial= to " + "select one:\n"; + for (const auto& c : candidates) + { + std::cerr << " "; + switch (c->id) + { + case FLUXENGINE_ID: + std::cerr << fmt::format("FluxEngine: {}\n", c->serial); + break; - case GREASEWEAZLE_ID: - std::cerr << fmt::format("GreaseWeazle: {} on {}\n", c->serial, c->serialPort); - break; - } - } - exit(1); + case GREASEWEAZLE_ID: + std::cerr << fmt::format( + "GreaseWeazle: {} on {}\n", c->serial, c->serialPort); + break; + } + } + exit(1); } USB* get_usb_impl() { - auto candidate = selectDevice(); - switch (candidate->id) - { - case FLUXENGINE_ID: - std::cerr << fmt::format("Using FluxEngine {}\n", candidate->serial); - return createFluxengineUsb(candidate->device); + /* Special case for certain configurations. */ - case GREASEWEAZLE_ID: - std::cerr << fmt::format("Using GreaseWeazle {} on {}\n", candidate->serial, candidate->serialPort); - return createGreaseWeazleUsb(candidate->serialPort); + if (config.usb().has_greaseweazle() && + config.usb().greaseweazle().has_port()) + { + const auto& conf = config.usb().greaseweazle(); + std::cerr << fmt::format( + "Using GreaseWeazle on serial port {}\n", conf.port()); + return createGreaseWeazleUsb(conf.port(), conf); + } - default: - Error() << "internal"; - } + /* Otherwise, select a device by USB ID. */ + + auto candidate = selectDevice(); + switch (candidate->id) + { + case FLUXENGINE_ID: + std::cerr << fmt::format( + "Using FluxEngine {}\n", candidate->serial); + return createFluxengineUsb(candidate->device); + + case GREASEWEAZLE_ID: + std::cerr << fmt::format("Using GreaseWeazle {} on {}\n", + candidate->serial, + candidate->serialPort); + return createGreaseWeazleUsb( + candidate->serialPort, config.usb().greaseweazle()); + + default: Error() << "internal"; + } } USB& getUsb() { - if (!usb) - usb = get_usb_impl(); - return *usb; + if (!usb) + usb = get_usb_impl(); + return *usb; } - diff --git a/lib/usb/usb.h b/lib/usb/usb.h index 28605770..f76baad8 100644 --- a/lib/usb/usb.h +++ b/lib/usb/usb.h @@ -5,6 +5,7 @@ #include "flags.h" class Fluxmap; +class GreaseWeazleProto; namespace libusbp { class device; } class USB @@ -33,7 +34,7 @@ protected: extern USB& getUsb(); extern USB* createFluxengineUsb(libusbp::device& device); -extern USB* createGreaseWeazleUsb(const std::string& serialPort); +extern USB* createGreaseWeazleUsb(const std::string& serialPort, const GreaseWeazleProto& config); static inline int usbGetVersion() { return getUsb().getVersion(); } static inline void usbRecalibrate() { getUsb().recalibrate(); } diff --git a/lib/usb/usb.proto b/lib/usb/usb.proto index f78f5fd2..38ab5a9b 100644 --- a/lib/usb/usb.proto +++ b/lib/usb/usb.proto @@ -2,9 +2,26 @@ syntax = "proto2"; import "lib/common.proto"; +message GreaseWeazleProto { + enum BusType { + BUSTYPE_INVALID = 0; + IBMPC = 1; + SHUGART = 2; + }; + + optional string port = 1 + [(help) = "GreaseWeazle serial port to use"]; + optional BusType bus_type = 2 + [(help) = "which FDD bus type is in use", default = IBMPC]; +} + message UsbProto { oneof device { string serial = 1 [(help) = "serial number of FluxEngine or GreaseWeazle device to use"]; } + + oneof config { + GreaseWeazleProto greaseweazle = 2 [(help) = "GreaseWeazle-specific options"]; + } } diff --git a/lib/usb/usbfinder.cc b/lib/usb/usbfinder.cc index 95b74d76..0653b49c 100644 --- a/lib/usb/usbfinder.cc +++ b/lib/usb/usbfinder.cc @@ -9,41 +9,42 @@ static const std::string get_serial_number(const libusbp::device& device) { - try - { - return device.get_serial_number(); - } - catch (const libusbp::error& e) - { - if (e.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER)) - return "n/a"; - throw; - } + try + { + return device.get_serial_number(); + } + catch (const libusbp::error& e) + { + if (e.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER)) + return "n/a"; + throw; + } } -std::vector> findUsbDevices(const std::set& ids) +std::vector> findUsbDevices( + const std::set& ids) { - std::vector> candidates; - for (const auto& it : libusbp::list_connected_devices()) - { - auto candidate = std::make_unique(); - candidate->device = it; + std::vector> candidates; + for (const auto& it : libusbp::list_connected_devices()) + { + auto candidate = std::make_unique(); + candidate->device = it; - uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); - if (ids.contains(id)) - { - candidate->id = id; - candidate->serial = get_serial_number(it); + uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); + if (ids.find(id) != ids.end()) + { + candidate->id = id; + candidate->serial = get_serial_number(it); - if (id == GREASEWEAZLE_ID) - { - libusbp::serial_port port(candidate->device); - candidate->serialPort = port.get_name(); - } + if (id == GREASEWEAZLE_ID) + { + libusbp::serial_port port(candidate->device); + candidate->serialPort = port.get_name(); + } - candidates.push_back(std::move(candidate)); - } - } + candidates.push_back(std::move(candidate)); + } + } - return candidates; + return candidates; } diff --git a/lib/writer.cc b/lib/writer.cc index 3fdc3ae4..e80fb531 100644 --- a/lib/writer.cc +++ b/lib/writer.cc @@ -40,6 +40,7 @@ void writeTracks( } else { + fluxmap->rescale(config.flux_sink().rescale()); /* Precompensation actually seems to make things worse, so let's leave * it disabled for now. */ //fluxmap->precompensate(PRECOMPENSATION_THRESHOLD_TICKS, 2); @@ -77,6 +78,7 @@ void writeTracksAndVerify( } else { + fluxmap->rescale(config.flux_sink().rescale()); std::sort(sectors.begin(), sectors.end(), sectorPointerSortPredicate); for (int retry = 0;; retry++) diff --git a/mkninja.sh b/mkninja.sh index 5e757b71..5dfd4632 100644 --- a/mkninja.sh +++ b/mkninja.sh @@ -469,7 +469,10 @@ buildlibrary libbackend.a \ lib/imagewriter/imagewriter.cc \ lib/imagewriter/imgimagewriter.cc \ lib/imagewriter/ldbsimagewriter.cc \ + lib/imagereader/nfdimagereader.cc \ lib/imagewriter/nsiimagewriter.cc \ + lib/imagewriter/rawimagewriter.cc \ + lib/imginputoutpututils.cc \ lib/ldbs.cc \ lib/proto.cc \ lib/reader.cc \ @@ -528,8 +531,9 @@ FORMATS="\ northstar350 \ northstar87 \ tids990 \ - victor9k_ss \ vgi \ + victor9k_ss \ + victor9k_ds \ zilogmcz \ " @@ -641,6 +645,7 @@ encodedecodetest mac800 scripts/mac800_test.textpb encodedecodetest n88basic encodedecodetest tids990 encodedecodetest victor9k_ss +encodedecodetest victor9k_ds # vim: sw=4 ts=4 et diff --git a/src/fe-write.cc b/src/fe-write.cc index c36efa02..fcc3dab9 100644 --- a/src/fe-write.cc +++ b/src/fe-write.cc @@ -17,6 +17,7 @@ #include static FlagGroup flags; +static bool verify = true; static StringFlag sourceImage( { "--input", "-i" }, @@ -55,6 +56,13 @@ static StringFlag destHeads( setRange(config.mutable_heads(), value); }); +static ActionFlag noVerifyFlag( + { "--no-verify", "-n" }, + "skip verification of write", + []{ + verify = false; + }); + int mainWrite(int argc, const char* argv[]) { if (argc == 1) @@ -68,7 +76,7 @@ int mainWrite(int argc, const char* argv[]) std::unique_ptr fluxSink(FluxSink::create(config.flux_sink())); std::unique_ptr decoder; - if (config.has_decoder()) + if (config.has_decoder() && verify) decoder = AbstractDecoder::create(config.decoder()); std::unique_ptr fluxSource; diff --git a/src/formats/brother120.textpb b/src/formats/brother120.textpb index 65beef8e..a6f10179 100644 --- a/src/formats/brother120.textpb +++ b/src/formats/brother120.textpb @@ -17,7 +17,17 @@ image_reader { image_writer { filename: "brother120.img" - img {} + img { + tracks: 39 + sides: 1 + trackdata { + sector_size: 256 + sector_range { + start_sector: 0 + sector_count: 12 + } + } + } } encoder { diff --git a/src/formats/brother240.textpb b/src/formats/brother240.textpb index 3506476f..fdbcd196 100644 --- a/src/formats/brother240.textpb +++ b/src/formats/brother240.textpb @@ -17,7 +17,17 @@ image_reader { image_writer { filename: "brother240.img" - img {} + img { + tracks: 78 + sides: 1 + trackdata { + sector_size: 256 + sector_range { + start_sector: 0 + sector_count: 12 + } + } + } } encoder { diff --git a/src/formats/victor9k_ds.textpb b/src/formats/victor9k_ds.textpb new file mode 100644 index 00000000..568a9910 --- /dev/null +++ b/src/formats/victor9k_ds.textpb @@ -0,0 +1,459 @@ +comment: 'Victor 9000 / Sirius One 1224kB DSHD GCR variable sector)' + +image_reader { + filename: "victor9k_ds.img" + img { + tracks: 80 + sides: 2 + trackdata { + sector_size: 512 + sector_range { + start_sector: 0 + } + } + trackdata { + side: 0 + track: 0 + up_to_track: 3 + sector_range { + sector_count: 19 + } + } + trackdata { + side: 0 + track: 4 + up_to_track: 15 + sector_range { + sector_count: 18 + } + } + trackdata { + side: 0 + track: 16 + up_to_track: 26 + sector_range { + sector_count: 17 + } + } + trackdata { + side: 0 + track: 27 + up_to_track: 37 + sector_range { + sector_count: 16 + } + } + trackdata { + side: 0 + track: 38 + up_to_track: 47 + sector_range { + sector_count: 15 + } + } + trackdata { + side: 0 + track: 48 + up_to_track: 59 + sector_range { + sector_count: 14 + } + } + trackdata { + side: 0 + track: 60 + up_to_track: 70 + sector_range { + sector_count: 13 + } + } + trackdata { + side: 0 + track: 71 + up_to_track: 79 + sector_range { + sector_count: 12 + } + } + trackdata { + side: 1 + track: 0 + up_to_track: 7 + sector_range { + sector_count: 18 + } + } + trackdata { + side: 1 + track: 8 + up_to_track: 18 + sector_range { + sector_count: 17 + } + } + trackdata { + side: 1 + track: 19 + up_to_track: 29 + sector_range { + sector_count: 16 + } + } + trackdata { + side: 1 + track: 30 + up_to_track: 39 + sector_range { + sector_count: 15 + } + } + trackdata { + side: 1 + track: 40 + up_to_track: 51 + sector_range { + sector_count: 14 + } + } + trackdata { + side: 1 + track: 52 + up_to_track: 62 + sector_range { + sector_count: 13 + } + } + trackdata { + side: 1 + track: 63 + up_to_track: 74 + sector_range { + sector_count: 12 + } + } + trackdata { + side: 1 + track: 75 + up_to_track: 79 + sector_range { + sector_count: 11 + } + } + } +} + +image_writer { + filename: "victor9k_ds.img" + img { + tracks: 80 + sides: 2 + trackdata { + sector_size: 512 + sector_range { + start_sector: 0 + } + } + trackdata { + side: 0 + track: 0 + up_to_track: 3 + sector_range { + sector_count: 19 + } + } + trackdata { + side: 0 + track: 4 + up_to_track: 15 + sector_range { + sector_count: 18 + } + } + trackdata { + side: 0 + track: 16 + up_to_track: 26 + sector_range { + sector_count: 17 + } + } + trackdata { + side: 0 + track: 27 + up_to_track: 37 + sector_range { + sector_count: 16 + } + } + trackdata { + side: 0 + track: 38 + up_to_track: 47 + sector_range { + sector_count: 15 + } + } + trackdata { + side: 0 + track: 48 + up_to_track: 59 + sector_range { + sector_count: 14 + } + } + trackdata { + side: 0 + track: 60 + up_to_track: 70 + sector_range { + sector_count: 13 + } + } + trackdata { + side: 0 + track: 71 + up_to_track: 79 + sector_range { + sector_count: 12 + } + } + trackdata { + side: 1 + track: 0 + up_to_track: 7 + sector_range { + sector_count: 18 + } + } + trackdata { + side: 1 + track: 8 + up_to_track: 18 + sector_range { + sector_count: 17 + } + } + trackdata { + side: 1 + track: 19 + up_to_track: 29 + sector_range { + sector_count: 16 + } + } + trackdata { + side: 1 + track: 30 + up_to_track: 39 + sector_range { + sector_count: 15 + } + } + trackdata { + side: 1 + track: 40 + up_to_track: 51 + sector_range { + sector_count: 14 + } + } + trackdata { + side: 1 + track: 52 + up_to_track: 62 + sector_range { + sector_count: 13 + } + } + trackdata { + side: 1 + track: 63 + up_to_track: 74 + sector_range { + sector_count: 12 + } + } + trackdata { + side: 1 + track: 75 + up_to_track: 79 + sector_range { + sector_count: 11 + } + } + } +} + +encoder { + victor9k { + trackdata { + original_data_rate_khz: 500 + post_index_gap_us: 1000.0 + pre_header_sync_bits: 120 + post_header_gap_bits: 48 + pre_data_sync_bits: 40 + post_data_gap_bits: 200 + } + trackdata { + head: 0 + min_cylinder: 0 + max_cylinder: 3 + original_period_ms: 237.9 + sector_range { + start_sector: 0 + sector_count: 19 + } + } + trackdata { + head: 0 + min_cylinder: 4 + max_cylinder: 15 + original_period_ms: 224.5 + sector_range { + sector_count: 18 + } + } + trackdata { + head: 0 + min_cylinder: 16 + max_cylinder: 26 + original_period_ms: 212.2 + sector_range { + sector_count: 17 + } + } + trackdata { + head: 0 + min_cylinder: 27 + max_cylinder: 37 + original_period_ms: 199.9 + sector_range { + sector_count: 16 + } + } + trackdata { + head: 0 + min_cylinder: 38 + max_cylinder: 47 + original_period_ms: 187.6 + sector_range { + sector_count: 15 + } + } + trackdata { + head: 0 + min_cylinder: 48 + max_cylinder: 59 + original_period_ms: 175.3 + sector_range { + sector_count: 14 + } + } + trackdata { + head: 0 + min_cylinder: 60 + max_cylinder: 70 + original_period_ms: 163.0 + sector_range { + sector_count: 13 + } + } + trackdata { + head: 0 + min_cylinder: 71 + max_cylinder: 79 + original_period_ms: 149.6 + sector_range { + sector_count: 12 + } + } + trackdata { + head: 1 + min_cylinder: 0 + max_cylinder: 7 + original_period_ms: 224.5 + sector_range { + start_sector: 0 + sector_count: 18 + } + } + trackdata { + head: 1 + min_cylinder: 8 + max_cylinder: 18 + original_period_ms: 212.2 + sector_range { + sector_count: 17 + } + } + trackdata { + head: 1 + min_cylinder: 19 + max_cylinder: 29 + original_period_ms: 199.9 + sector_range { + sector_count: 16 + } + } + trackdata { + head: 1 + min_cylinder: 30 + max_cylinder: 39 + original_period_ms: 187.6 + sector_range { + sector_count: 15 + } + } + trackdata { + head: 1 + min_cylinder: 40 + max_cylinder: 51 + original_period_ms: 175.3 + sector_range { + sector_count: 14 + } + } + trackdata { + head: 1 + min_cylinder: 52 + max_cylinder: 62 + original_period_ms: 163.0 + sector_range { + sector_count: 13 + } + } + trackdata { + head: 1 + min_cylinder: 63 + max_cylinder: 74 + original_period_ms: 149.6 + sector_range { + sector_count: 12 + } + } + trackdata { + head: 1 + min_cylinder: 75 + max_cylinder: 79 + original_period_ms: 144.0 + sector_range { + sector_count: 11 + } + } + } +} + +decoder { + victor9k {} +} + +cylinders { + start: 0 + end: 79 +} + +heads { + start: 0 + end: 1 +} + diff --git a/src/formats/victor9k_ss.textpb b/src/formats/victor9k_ss.textpb index 1b72f03c..4feb922a 100644 --- a/src/formats/victor9k_ss.textpb +++ b/src/formats/victor9k_ss.textpb @@ -41,13 +41,13 @@ image_reader { } trackdata { track: 38 - up_to_track: 48 + up_to_track: 47 sector_range { sector_count: 15 } } trackdata { - track: 49 + track: 48 up_to_track: 59 sector_range { sector_count: 14 @@ -111,13 +111,13 @@ image_writer { } trackdata { track: 38 - up_to_track: 48 + up_to_track: 47 sector_range { sector_count: 15 } } trackdata { - track: 49 + track: 48 up_to_track: 59 sector_range { sector_count: 14 @@ -190,7 +190,7 @@ encoder { trackdata { head: 0 min_cylinder: 38 - max_cylinder: 48 + max_cylinder: 47 original_period_ms: 187.6 sector_range { sector_count: 15 @@ -198,7 +198,7 @@ encoder { } trackdata { head: 0 - min_cylinder: 49 + min_cylinder: 48 max_cylinder: 59 original_period_ms: 175.3 sector_range {