Merge branch 'master' into micropolis-275

This commit is contained in:
Eric Anderson
2022-01-29 10:09:35 -07:00
46 changed files with 1281 additions and 220 deletions

42
.clang-format Normal file
View File

@@ -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'
...

View File

@@ -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

View File

@@ -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

View File

@@ -83,3 +83,5 @@ clean:
@mkdir -p $(OBJDIR)
@sh $< > $@
compdb:
@ninja -f .obj/build.ninja -t compdb > compile_commands.json

View File

@@ -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 }

View File

@@ -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

View File

@@ -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 <format>
```
You should end up with an `victor9k.img` which is 627200 bytes long.
...where `<format>` 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.

View File

@@ -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
-----------------

View File

@@ -267,7 +267,8 @@ FluxEngine also supports a number of file system image formats. When using the
- `<filename.fdi>`
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.**
- `<filename.d88>`
@@ -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.
- `<filename.nfd>`
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.
- `<filename.ldbs>`
Write to a [LDBS generic image
file](https://www.seasip.info/Unix/LibDsk/ldbs.html). **Write only.**
- `<filename.d64>`
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.**
- `<filename.d64>`
Write to a [D64 image
file](http://unusedino.de/ec64/technical/formats/d64.html), commonly used by
Commodore 64 emulators. **Write only.**
- `<filename.raw>`
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

View File

@@ -132,6 +132,7 @@ void AbstractDecoder::pushRecord(const Fluxmap::Position& start, const Fluxmap::
auto record = std::make_shared<Record>();
_trackdata->records.push_back(record);
_sector->records.push_back(record);
record->startTime = start.ns();
record->endTime = end.ns();

View File

@@ -108,3 +108,22 @@ std::vector<Fluxmap> 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<bytesOrig.size(); i++)
{
lastEvent += bytesOrig[i] & 0x3f;
if (bytesOrig[i] & 0xc0) {
appendInterval(lastEvent * scale + 0.5);
findLastByte() |= bytesOrig[i] & 0xc0;
lastEvent = 0;
}
}
}
}

View File

@@ -65,6 +65,7 @@ public:
void precompensate(int threshold_ticks, int amount_ticks);
std::vector<Fluxmap> split();
void rescale(double scale);
private:
uint8_t& findLastByte();

View File

@@ -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;

View File

@@ -31,7 +31,7 @@ public:
return std::make_unique<Fluxmap>(track.flux());
}
return std::unique_ptr<Fluxmap>();
return std::make_unique<Fluxmap>();
}
void recalibrate() {}

View File

@@ -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;

View File

@@ -5,7 +5,6 @@
#include "image.h"
#include "proto.h"
#include "lib/config.pb.h"
#include "imagereader/imagereaderimpl.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
@@ -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;
}

View File

@@ -5,7 +5,6 @@
#include "image.h"
#include "proto.h"
#include "lib/config.pb.h"
#include "imagereader/imagereaderimpl.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
@@ -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;
}

View File

@@ -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 <algorithm>
#include <iostream>
@@ -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;
}

View File

@@ -38,6 +38,9 @@ std::unique_ptr<ImageReader> 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);
}
}

View File

@@ -27,6 +27,7 @@ public:
static std::unique_ptr<ImageReader> createDimImageReader(const ImageReaderProto& config);
static std::unique_ptr<ImageReader> createFdiImageReader(const ImageReaderProto& config);
static std::unique_ptr<ImageReader> createD88ImageReader(const ImageReaderProto& config);
static std::unique_ptr<ImageReader> createNFDImageReader(const ImageReaderProto& config);
public:
virtual std::unique_ptr<Image> readImage() = 0;

View File

@@ -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;
}
}

View File

@@ -1,8 +0,0 @@
#ifndef IMAGEREADERIMPL_H
#define IMAGEREADERIMPL_H
extern void getTrackFormat(const ImgInputOutputProto& config,
ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side);
#endif

View File

@@ -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 <algorithm>
#include <iostream>
@@ -27,34 +27,31 @@ public:
Error() << "IMG: bad configuration; did you remember to set the tracks, sides and trackdata fields?";
std::unique_ptr<Image> 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();

View File

@@ -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 <algorithm>
#include <iostream>
#include <fstream>
// 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<Image> 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> 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> ImageReader::createNFDImageReader(
const ImageReaderProto& config)
{
return std::unique_ptr<ImageReader>(new NFDImageReader(config));
}

View File

@@ -29,6 +29,9 @@ std::unique_ptr<ImageWriter> 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<ImageWriter>();
@@ -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(); }},
};

View File

@@ -14,16 +14,12 @@ public:
static std::unique_ptr<ImageWriter> create(const ImageWriterProto& config);
static void updateConfigForFilename(ImageWriterProto* proto, const std::string& filename);
static std::unique_ptr<ImageWriter> createImgImageWriter(
const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createLDBSImageWriter(
const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createD64ImageWriter(
const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createDiskCopyImageWriter(
const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createNsiImageWriter(
const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createD64ImageWriter(const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createDiskCopyImageWriter(const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createImgImageWriter(const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createLDBSImageWriter(const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createNsiImageWriter(const ImageWriterProto& config);
static std::unique_ptr<ImageWriter> createRawImageWriter(const ImageWriterProto& config);
public:
void printMap(const Image& sectors);

View File

@@ -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;
}
}

View File

@@ -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 <algorithm>
#include <iostream>
@@ -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);
}
}

View File

@@ -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 <algorithm>
#include <iostream>
#include <fstream>
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<std::shared_ptr<Record>> 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<Record> left, std::shared_ptr<Record> 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> ImageWriter::createRawImageWriter(
const ImageWriterProto& config)
{
return std::unique_ptr<ImageWriter>(new RawImageWriter(config));
}

View File

@@ -0,0 +1,57 @@
#include "globals.h"
#include "lib/imagereader/imagereader.pb.h"
#include "imginputoutpututils.h"
std::vector<std::pair<int, int>> 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<std::pair<int, int>> 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);
}
}

12
lib/imginputoutpututils.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef IMGINPUTOUTPUTUTILS_H
#define IMGINPUTOUTPUTUTILS_H
extern std::vector<std::pair<int, int>> getTrackOrdering(const ImgInputOutputProto& config,
unsigned numTracks = 0, unsigned numSides = 0);
extern void getTrackFormat(const ImgInputOutputProto& config,
ImgInputOutputProto::TrackdataProto& trackdata, unsigned track, unsigned side);
#endif

View File

@@ -25,6 +25,7 @@ static std::shared_ptr<Fluxmap> readFluxmap(FluxSource& fluxsource, unsigned cyl
{
std::cout << fmt::format("{0:>3}.{1}: ", cylinder, head) << std::flush;
std::shared_ptr<Fluxmap> 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<std::shared_ptr<Sector>> collect_sectors(std::set<std::shared_pt
{
std::cout << fmt::format(
"\n multiple conflicting copies of sector {} seen; ",
std::get<0>(sectorid), std::get<1>(sectorid), std::get<2>(sectorid));
std::get<2>(sectorid));
replacing->status = replacement->status = Sector::CONFLICT;
}
}

View File

@@ -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<std::shared_ptr<Record>> records;
std::tuple<int, int, int, Status> key() const
{ return std::make_tuple(logicalTrack, logicalSide, logicalSector, status); }

View File

@@ -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<SerialPort> _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

View File

@@ -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 <termios.h>
#include <sys/ioctl.h>
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

View File

@@ -13,70 +13,87 @@
static USB* usb = NULL;
USB::~USB()
{}
USB::~USB() {}
static std::unique_ptr<CandidateDevice> 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=<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=<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;
}

View File

@@ -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(); }

View File

@@ -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"];
}
}

View File

@@ -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<std::unique_ptr<CandidateDevice>> findUsbDevices(const std::set<uint32_t>& ids)
std::vector<std::unique_ptr<CandidateDevice>> findUsbDevices(
const std::set<uint32_t>& ids)
{
std::vector<std::unique_ptr<CandidateDevice>> candidates;
for (const auto& it : libusbp::list_connected_devices())
{
auto candidate = std::make_unique<CandidateDevice>();
candidate->device = it;
std::vector<std::unique_ptr<CandidateDevice>> candidates;
for (const auto& it : libusbp::list_connected_devices())
{
auto candidate = std::make_unique<CandidateDevice>();
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;
}

View File

@@ -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++)

View File

@@ -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

View File

@@ -17,6 +17,7 @@
#include <fstream>
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(FluxSink::create(config.flux_sink()));
std::unique_ptr<AbstractDecoder> decoder;
if (config.has_decoder())
if (config.has_decoder() && verify)
decoder = AbstractDecoder::create(config.decoder());
std::unique_ptr<FluxSource> fluxSource;

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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 {