diff --git a/README.md b/README.md index f02797ec..73665387 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,8 @@ FluxEngine (If you're reading this on GitHub, the formatting's a bit messed up. [Try the version on cowlark.com instead.](http://cowlark.com/fluxengine/)) -**Breaking news!** As of 2022-09-09, there's new [filesystem -support](doc/filesystem.md). Read (and sometimes write) files directly from -(and to) your disks, with eight different file systems! It works in the GUI, -too, which is available for Linux (and other Unix clones), Windows and OSX. See -the details below. +**Breaking news!** As of 2024-10-01, the FluxEngine client software works +(to a point) with [Applesauce](doc/applesauce.md) hardware.
screenshot of the GUI in action @@ -35,12 +32,14 @@ Don't believe me? Watch the demo reel!
**New!** The FluxEngine client software now works with -[Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) hardware. So, if you -can't find a PSoC5 development kit, or don't want to use the Cypress Windows -tools for programming it, you can use one of these instead. Very nearly all -FluxEngine features are available with the Greaseweazle and it works out-of-the -box. See the [dedicated Greaseweazle documentation page](doc/greaseweazle.md) -for more information. +[Greaseweazle](https://github.com/keirf/Greaseweazle/wiki) and +[Applesauce](https://applesaucefdc.com/) hardware. So, if you can't find a PSoC5 +development kit, or don't want to use the Cypress Windows tools for programming +it, you can use one of these instead. Very nearly all FluxEngine features are +available with the Greaseweazle and it works out-of-the box; the Applesauce is a +bit less supported but still works. See the [dedicated Greaseweazle +documentation page](doc/greaseweazle.md) or the [Applesauce +page](doc/applesauce.md) for more information. Where? ------ diff --git a/build.py b/build.py index ed97ddec..309d2c28 100644 --- a/build.py +++ b/build.py @@ -90,6 +90,8 @@ "./lib/proto.cc", "./lib/readerwriter.cc", "./lib/sector.cc", + "./lib/usb/applesauce.cc", + "./lib/usb/applesauceusb.cc", "./lib/usb/fluxengineusb.cc", "./lib/usb/greaseweazle.cc", "./lib/usb/greaseweazleusb.cc", @@ -209,6 +211,7 @@ "lib/proto.h": "./lib/proto.h", "lib/readerwriter.h": "./lib/readerwriter.h", "lib/sector.h": "./lib/sector.h", + "lib/usb/applesauce.h": "./lib/usb/applesauce.h", "lib/usb/greaseweazle.h": "./lib/usb/greaseweazle.h", "lib/usb/usb.h": "./lib/usb/usb.h", "lib/usb/usbfinder.h": "./lib/usb/usbfinder.h", diff --git a/doc/applesauce.md b/doc/applesauce.md new file mode 100644 index 00000000..3f7b55a7 --- /dev/null +++ b/doc/applesauce.md @@ -0,0 +1,72 @@ +Using the FluxEngine client software with Applesauce hardware +=============================================================== + +The FluxEngine isn't the only project which does this; another one is the +[Applesauce](https://applesaucefdc.com/), a proprietary but feature-rich +off-the-shelf imaging device. Its native client (which is a lot better than +FluxEngine) only works on OSX, so if you want to use it anywhere else, +the FluxEngine client works. + +The Applesauce works rather differently to the FluxEngine hardware or the +[Greaseweazle](greaseweazle.md), so there are some caveats. + + - Rather than streaming the flux data from the device to the PC, the Applesauce + has a fixed buffer in RAM used to capture a complete image of a track. This is + then downloaded later. The advantage is that USB bandwidth isn't an issue; the + downside is that the buffer can only hold so much data. In fact, the Applesauce + can only capture 1.25 revolutions or 2.25 revolutions, nothing else. When used + with the FluxEngine the capture time will be ignored apart from used to + determine whether you want a 'long' or 'short' capture. + + - The current (v2) firmware only supports reading, not writing (via clients + other than the official one, of course). The new (v3) firmware will support + writing, but it's not out yet, so for the time being the FluxEngine client is + read only. + + - You can only do synchronous reads, i.e., reads starting from the index mark. + +Other than this, the FluxEngine software supports the Applesauce almost +out-of-the-box --- just plug it in and nearly everything should work. The +FluxEngine software will autodetect it. If you have more than one device plugged +in, use `--usb.serial=` to specify which one you want to use. + +I am aware that having _software_ called FluxEngine and _hardware_ called +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. + +What works +---------- + +Supported features with the Greaseweazle include: + + - simple reading of disks, seeking etc + - erasing disks + - hard sectored disks + - determining disk rotation speed + - normal IBM buses + +I don't know what happens if you try to use an Apple Superdrive or a Apple II +disk with FluxEngine. If you've got one, [please get in +touch](https://github.com/davidgiven/fluxengine/issues/new)! + +What doesn't work +----------------- + + - voltage measurement + - writing + +Who to contact +-------------- + +I want to make it clear that the FluxEngine code is _not_ supported by the +Applesauce team. If you have any problems, please [contact +me](https://github.com/davidgiven/fluxengine/issues/new) and not them. + +In addition, the Applesauce release cycle is not synchronised to the +FluxEngine release cycle, so it's possible you'll have a version of the +Applesauce firmware which is not supported by FluxEngine. Hopefully, it'll +detect this and complain. Again, [file an +issue](https://github.com/davidgiven/fluxengine/issues/new) and I'll look into +it. + diff --git a/doc/using.md b/doc/using.md index 6008cec9..646b8676 100644 --- a/doc/using.md +++ b/doc/using.md @@ -50,9 +50,9 @@ file while changing the decoder options, to save disk wear. It's also much faste ### Connecting it up -To use, simply plug your FluxEngine (or [Greaseweazle](greaseweazle.md)) into -your computer and run the client. If a single device is plugged in, it will be -automatically detected and used. +To use, simply plug your FluxEngine (or [Greaseweazle](greaseweazle.md) or +[Applesauce](applesauce.md)) into your computer and run the client. If a single +device is plugged in, it will be automatically detected and used. If _more_ than one device is plugged in, you need to specify which one to use with the `--usb.serial` parameter, which takes the device serial number as a diff --git a/lib/decoders/fluxmapreader.cc b/lib/decoders/fluxmapreader.cc index 4ce8f7c8..3fbfb89c 100644 --- a/lib/decoders/fluxmapreader.cc +++ b/lib/decoders/fluxmapreader.cc @@ -47,7 +47,7 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks) { ticks = 0; - for (;;) + while (!eof()) { unsigned thisTicks; int thisEvent; @@ -57,11 +57,11 @@ bool FluxmapReader::findEvent(int event, unsigned& ticks) if (thisEvent == F_EOF) return false; - if (eof()) - return false; if ((event == thisEvent) || (event & thisEvent)) return true; } + + return false; } unsigned FluxmapReader::readInterval(nanoseconds_t clock) diff --git a/lib/flags.cc b/lib/flags.cc index 6f3a543f..a83ccdb3 100644 --- a/lib/flags.cc +++ b/lib/flags.cc @@ -156,7 +156,7 @@ std::vector FlagGroup::parseFlagsWithFilenames(int argc, globalConfig().applyOption(path); } else - error("unrecognised flag; try --help"); + error("unrecognised flag '-{}'; try --help", key); } else { diff --git a/lib/fluxsource/fluxsource.proto b/lib/fluxsource/fluxsource.proto index 7564717c..ab62bb65 100644 --- a/lib/fluxsource/fluxsource.proto +++ b/lib/fluxsource/fluxsource.proto @@ -6,7 +6,7 @@ message HardwareFluxSourceProto {} message TestPatternFluxSourceProto { optional double interval_us = 1 [default = 4.0, (help) = "interval between pulses"]; - optional double sequence_length_us = 2 [default = 200.0, (help) = "length of test sequence"]; + optional double sequence_length_ms = 2 [default = 166.0, (help) = "length of test sequence"]; } message EraseFluxSourceProto {} diff --git a/lib/fluxsource/testpatternfluxsource.cc b/lib/fluxsource/testpatternfluxsource.cc index b5a85c45..3b7cce88 100644 --- a/lib/fluxsource/testpatternfluxsource.cc +++ b/lib/fluxsource/testpatternfluxsource.cc @@ -18,9 +18,9 @@ class TestPatternFluxSource : public TrivialFluxSource { auto fluxmap = std::make_unique(); - while (fluxmap->duration() < (_config.sequence_length_us() * 1000000.0)) + while (fluxmap->duration() < (_config.sequence_length_ms() * 1e6)) { - fluxmap->appendInterval(_config.interval_us()); + fluxmap->appendInterval(_config.interval_us() * TICKS_PER_US); fluxmap->appendPulse(); } diff --git a/lib/globals.cc b/lib/globals.cc index bd28af65..85749805 100644 --- a/lib/globals.cc +++ b/lib/globals.cc @@ -14,5 +14,4 @@ double getCurrentTime(void) void warning(const std::string msg) { log(msg); - fmt::print("Warning: {}\n", msg); } diff --git a/lib/usb/applesauce.cc b/lib/usb/applesauce.cc new file mode 100644 index 00000000..0bcce8c4 --- /dev/null +++ b/lib/usb/applesauce.cc @@ -0,0 +1,64 @@ +#include "lib/globals.h" +#include "usb.h" +#include "protocol.h" +#include "lib/bytes.h" +#include "greaseweazle.h" +#include "lib/fluxmap.h" +#include "lib/decoders/fluxmapreader.h" +#include "lib/a2r.h" + +static double a2r_to_ticks(double a2rticks) +{ + return a2rticks * A2R_NS_PER_TICK / NS_PER_TICK; +} + +static double ticks_to_a2r(double flticks) +{ + return flticks * NS_PER_TICK / A2R_NS_PER_TICK; +} + +Bytes fluxEngineToApplesauce(const Bytes& fldata) +{ + Fluxmap fluxmap(fldata); + FluxmapReader fmr(fluxmap); + Bytes asdata; + ByteWriter bw(asdata); + + while (!fmr.eof()) + { + unsigned ticks; + if (!fmr.findEvent(F_BIT_PULSE, ticks)) + break; + + uint32_t applesauceTicks = ticks_to_a2r(ticks); + while (applesauceTicks >= 255) + { + bw.write_8(255); + applesauceTicks -= 255; + } + bw.write_8(applesauceTicks); + } + + return asdata; +} + +Bytes applesauceToFluxEngine(const Bytes& asdata) +{ + ByteReader br(asdata); + Fluxmap fluxmap; + + unsigned a2rTicksSinceLastPulse = 0; + while (!br.eof()) + { + uint8_t b = br.read_8(); + a2rTicksSinceLastPulse += b; + if (b != 255) + { + fluxmap.appendInterval(a2r_to_ticks(a2rTicksSinceLastPulse)); + fluxmap.appendPulse(); + a2rTicksSinceLastPulse = 0; + } + } + + return fluxmap.rawBytes(); +} diff --git a/lib/usb/applesauce.h b/lib/usb/applesauce.h new file mode 100644 index 00000000..2cef0151 --- /dev/null +++ b/lib/usb/applesauce.h @@ -0,0 +1,9 @@ +#pragma once + +#define APPLESAUCE_VID 0x16c0 +#define APPLESAUCE_PID 0x0483 + +#define APPLESAUCE_ID ((APPLESAUCE_VID << 16) | APPLESAUCE_PID) + +extern Bytes applesauceToFluxEngine(const Bytes& asdata); +extern Bytes fluxEngineToApplesauce(const Bytes& fldata); diff --git a/lib/usb/applesauceusb.cc b/lib/usb/applesauceusb.cc new file mode 100644 index 00000000..5c8cf897 --- /dev/null +++ b/lib/usb/applesauceusb.cc @@ -0,0 +1,345 @@ +#include "lib/globals.h" +#include "protocol.h" +#include "lib/fluxmap.h" +#include "lib/bytes.h" +#include "lib/usb/usb.pb.h" +#include "lib/utils.h" +#include "applesauce.h" +#include "serial.h" +#include "usb.h" +#include "lib/decoders/fluxmapreader.h" +#include + +class ApplesauceUsb; +static ApplesauceUsb* instance; + +static uint32_t ss_rand_next(uint32_t x) +{ + return (x & 1) ? (x >> 1) ^ 0x80000062 : x >> 1; +} + +static Bytes applesauceReadDataToFluxEngine(const Bytes& asdata, + nanoseconds_t clock, + const std::vector& indexMarks) +{ + ByteReader br(asdata); + Fluxmap fluxmap; + auto indexIt = indexMarks.begin(); + fluxmap.appendIndex(); + + unsigned totalTicks = 0; + while (!br.eof()) + { + uint8_t b = br.read_8(); + fluxmap.appendInterval(b * clock / NS_PER_TICK); + if (b != 255) + fluxmap.appendPulse(); + + totalTicks += b; + if ((indexIt != indexMarks.end()) && (totalTicks > *indexIt)) + { + fluxmap.appendIndex(); + indexIt++; + } + } + + return fluxmap.rawBytes(); +} + +static Bytes fluxEngineToApplesauceWriteData(const Bytes& fldata) +{ + Fluxmap fluxmap(fldata); + FluxmapReader fmr(fluxmap); + Bytes asdata; + ByteWriter bw(asdata); + + while (!fmr.eof()) + { + unsigned ticks; + if (!fmr.findEvent(F_BIT_PULSE, ticks)) + break; + + uint32_t applesauceTicks = (double)ticks * NS_PER_TICK; + while (applesauceTicks >= 0xffff) + { + bw.write_le16(0xffff); + applesauceTicks -= 0xffff; + } + if (applesauceTicks == 0) + error("bad data!"); + bw.write_le16(applesauceTicks); + } + + bw.write_le16(0); + return asdata; +} + +class ApplesauceException : public ErrorException +{ +public: + ApplesauceException(const std::string& s): ErrorException(s) {} +}; + +class ApplesauceUsb : public USB +{ +public: + ApplesauceUsb(const std::string& port, const ApplesauceProto& config): + _serial(SerialPort::openSerialPort(port)), + _config(config) + { + std::string s = sendrecv("?"); + if (s != "Applesauce") + error( + "Applesauce device not responding (expected 'Applesauce', got " + "'{}')", + s); + + doCommand("client:v2"); + + atexit( + []() + { + delete instance; + }); + } + + ~ApplesauceUsb() + { + sendrecv("disconnect"); + } + +private: + std::string sendrecv(const std::string& command) + { + if (_config.verbose()) + fmt::print(fmt::format("> {}\n", command)); + _serial->writeLine(command); + auto r = _serial->readLine(); + if (_config.verbose()) + fmt::print(fmt::format("< {}\n", r)); + return r; + } + + void checkCommandResult(const std::string& result) + { + if (result != ".") + throw ApplesauceException( + fmt::format("low-level Applesauce error: '{}'", result)); + } + + void doCommand(const std::string& command) + { + checkCommandResult(sendrecv(command)); + } + + std::string doCommandX(const std::string& command) + { + doCommand(command); + std::string r = _serial->readLine(); + if (_config.verbose()) + fmt::print(fmt::format("<< {}\n", r)); + return r; + } + + void connect() + { + if (!_connected) + { + try + { + doCommand("connect"); + doCommand("drive:enable"); + doCommand("motor:on"); + doCommand("head:zero"); + _connected = true; + } + catch (const ApplesauceException& e) + { + error("Applesauce could not connect to a drive"); + } + } + } + +public: + void seek(int track) override + { + if (track == 0) + doCommand("head:zero"); + else + doCommand(fmt::format("head:track{}", track)); + } + + nanoseconds_t getRotationalPeriod(int hardSectorCount) override + { + if (hardSectorCount != 0) + error("hard sectors are currently unsupported on the Applesauce"); + + connect(); + try + { + double period_us = std::stod(doCommandX("sync:?speed")); + _serial->writeByte('X'); + std::string r = _serial->readLine(); + if (_config.verbose()) + fmt::print("<< {}\n", r); + return period_us * 1e3; + } + catch (const ApplesauceException& e) + { + return 0; + } + } + + void testBulkWrite() override + { + int max = std::stoi(sendrecv("data:?max")); + fmt::print("Writing data: "); + + doCommand(fmt::format("data:>{}", max)); + + Bytes junk(max); + uint32_t seed = 0; + for (int i = 0; i < max; i++) + { + junk[i] = seed; + seed = ss_rand_next(seed); + } + double start_time = getCurrentTime(); + _serial->write(junk); + _serial->readLine(); + double elapsed_time = getCurrentTime() - start_time; + + std::cout << fmt::format( + "transferred {} bytes from PC -> device in {} ms ({} kb/s)\n", + max, + int(elapsed_time * 1000.0), + int((max / 1024.0) / elapsed_time)); + } + + void testBulkRead() override + { + int max = std::stoi(sendrecv("data:?max")); + fmt::print("Reading data: "); + + doCommand(fmt::format("data:<{}", max)); + + double start_time = getCurrentTime(); + _serial->readBytes(max); + double elapsed_time = getCurrentTime() - start_time; + + std::cout << fmt::format( + "transferred {} bytes from device -> PC in {} ms ({} kb/s)\n", + max, + int(elapsed_time * 1000.0), + int((max / 1024.0) / elapsed_time)); + } + + Bytes read(int side, + bool synced, + nanoseconds_t readTime, + nanoseconds_t hardSectorThreshold) override + { + if (hardSectorThreshold != 0) + error("hard sectors are currently unsupported on the Applesauce"); + bool shortRead = readTime < 400e6; + warning( + "applesauce: timed reads not supported; using read of {} " + "revolutions", + shortRead ? "1.25" : "2.25"); + + connect(); + doCommand(fmt::format("head:side{}", side)); + doCommand("sync:on"); + doCommand("data:clear"); + std::string r = doCommandX(shortRead ? "disk:read" : "disk:readx"); + auto rsplit = split(r, '|'); + if (rsplit.size() < 2) + error("unrecognised Applesauce response to disk:read: '{}'", r); + + int bufferSize = std::stoi(rsplit[0]); + nanoseconds_t tickSize = std::stod(rsplit[1]) / 1e3; + + std::vector indexMarks; + for (auto i = rsplit.begin() + 2; i != rsplit.end(); i++) + indexMarks.push_back(std::stoi(*i)); + + doCommand(fmt::format("data:<{}", bufferSize)); + + Bytes rawData = _serial->readBytes(bufferSize); + Bytes b = applesauceReadDataToFluxEngine(rawData, tickSize, indexMarks); + return b; + } + +private: + void checkWritable() + { + if (sendrecv("disk:?write") == "-") + error("cannot write --- disk is write protected"); + if (sendrecv("?safe") == "+") + error("cannot write --- Applesauce 'safe' switch is on"); + if (sendrecv("?vers") < "0300") + error("cannot write --- need Applesauce firmware 2.0 or above"); + } + +public: + void write(int side, + const Bytes& fldata, + nanoseconds_t hardSectorThreshold) override + { + if (hardSectorThreshold != 0) + error("hard sectors are currently unsupported on the Applesauce"); + checkWritable(); + + connect(); + doCommand(fmt::format("head:side{}", side)); + doCommand("sync:on"); + doCommand("disk:wipe"); + doCommand("data:clear"); + doCommand("disk:wclear"); + + Bytes asdata = fluxEngineToApplesauceWriteData(fldata); + doCommand(fmt::format("data:>{}", asdata.size())); + _serial->write(asdata); + checkCommandResult(_serial->readLine()); + doCommand("disk:wcmd0,0"); + doCommand("disk:write"); + } + + void erase(int side, nanoseconds_t hardSectorThreshold) override + { + if (hardSectorThreshold != 0) + error("hard sectors are currently unsupported on the Applesauce"); + checkWritable(); + + connect(); + doCommand(fmt::format("disk:side{}", side)); + doCommand("disk:wipe"); + } + + void setDrive(int drive, bool high_density, int index_mode) override + { + if (drive != 0) + error("the Applesauce only supports drive 0"); + + connect(); + doCommand(fmt::format("dpc:density{}", high_density ? '+' : '-')); + } + + void measureVoltages(struct voltages_frame* voltages) override + { + error("unsupported operation on the Greaseweazle"); + } + +private: + std::unique_ptr _serial; + const ApplesauceProto& _config; + bool _connected = false; +}; + +USB* createApplesauceUsb(const std::string& port, const ApplesauceProto& config) +{ + instance = new ApplesauceUsb(port, config); + return instance; +} + +// vim: sw=4 ts=4 et diff --git a/lib/usb/fluxengineusb.cc b/lib/usb/fluxengineusb.cc index d9b9c42a..e7ec6c76 100644 --- a/lib/usb/fluxengineusb.cc +++ b/lib/usb/fluxengineusb.cc @@ -122,8 +122,7 @@ class FluxEngineUsb : public USB } } -public: - int getVersion() override + int getVersion() { struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} @@ -133,6 +132,7 @@ class FluxEngineUsb : public USB return r->version; } +public: void seek(int track) override { struct seek_frame f = { diff --git a/lib/usb/greaseweazle.cc b/lib/usb/greaseweazle.cc index 8b418e4f..b0313555 100644 --- a/lib/usb/greaseweazle.cc +++ b/lib/usb/greaseweazle.cc @@ -53,7 +53,7 @@ Bytes fluxEngineToGreaseweazle(const Bytes& fldata, nanoseconds_t clock) return gwdata; } -Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock) +Bytes greaseweazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock) { Bytes fldata; ByteReader br(gwdata); diff --git a/lib/usb/greaseweazle.h b/lib/usb/greaseweazle.h index 27381630..a6063d68 100644 --- a/lib/usb/greaseweazle.h +++ b/lib/usb/greaseweazle.h @@ -10,7 +10,7 @@ #define EP_IN 0x83 extern Bytes fluxEngineToGreaseweazle(const Bytes& fldata, nanoseconds_t clock); -extern Bytes greaseWeazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock); +extern Bytes greaseweazleToFluxEngine(const Bytes& gwdata, nanoseconds_t clock); extern Bytes stripPartialRotation(const Bytes& fldata); /* Copied from diff --git a/lib/usb/greaseweazleusb.cc b/lib/usb/greaseweazleusb.cc index 4496adc2..69be470f 100644 --- a/lib/usb/greaseweazleusb.cc +++ b/lib/usb/greaseweazleusb.cc @@ -109,7 +109,8 @@ class GreaseweazleUsb : public USB do_command({CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type()}); } - int getVersion() override +private: + int getVersion() { do_command({CMD_GET_INFO, 3, GETINFO_FIRMWARE}); @@ -124,11 +125,7 @@ class GreaseweazleUsb : public USB return br.read_be16(); } - void recalibrate() override - { - seek(0); - } - +public: void seek(int track) override { do_command({CMD_SEEK, 3, (uint8_t)track}); @@ -356,7 +353,7 @@ class GreaseweazleUsb : public USB do_command({CMD_GET_FLUX_STATUS, 2}); - Bytes fldata = greaseWeazleToFluxEngine(buffer, _clock); + Bytes fldata = greaseweazleToFluxEngine(buffer, _clock); if (synced) fldata = stripPartialRotation(fldata); return fldata; diff --git a/lib/usb/serial.cc b/lib/usb/serial.cc index f1d8aea6..4101072b 100644 --- a/lib/usb/serial.cc +++ b/lib/usb/serial.cc @@ -254,6 +254,11 @@ uint8_t SerialPort::readByte() return b; } +void SerialPort::writeByte(uint8_t b) +{ + this->write(&b, 1); +} + void SerialPort::write(const Bytes& bytes) { int ptr = 0; @@ -264,6 +269,28 @@ void SerialPort::write(const Bytes& bytes) } } +void SerialPort::writeLine(const std::string& chars) +{ + this->write((const uint8_t*)&chars[0], chars.size()); + writeByte('\n'); +} + +std::string SerialPort::readLine() +{ + std::string s; + + for (;;) + { + uint8_t b = readByte(); + if (b == '\r') + continue; + if (b == '\n') + return s; + + s += b; + } +} + std::unique_ptr SerialPort::openSerialPort(const std::string& path) { return std::make_unique(path); diff --git a/lib/usb/serial.h b/lib/usb/serial.h index 7f594102..0fd7416a 100644 --- a/lib/usb/serial.h +++ b/lib/usb/serial.h @@ -17,8 +17,12 @@ class SerialPort void read(Bytes& bytes); Bytes readBytes(size_t count); uint8_t readByte(); + void writeByte(uint8_t b); void write(const Bytes& bytes); + void writeLine(const std::string& chars); + std::string readLine(); + private: uint8_t _readbuffer[4096]; size_t _readbuffer_ptr = 0; diff --git a/lib/usb/usb.cc b/lib/usb/usb.cc index a6654796..201c3192 100644 --- a/lib/usb/usb.cc +++ b/lib/usb/usb.cc @@ -9,6 +9,7 @@ #include "lib/proto.h" #include "usbfinder.h" #include "lib/logger.h" +#include "applesauce.h" #include "greaseweazle.h" static USB* usb = NULL; @@ -54,6 +55,11 @@ static std::shared_ptr selectDevice() std::cerr << fmt::format( "Greaseweazle: {} on {}\n", c->serial, c->serialPort); break; + + case APPLESAUCE_ID: + std::cerr << fmt::format( + "Applesauce: {} on {}\n", c->serial, c->serialPort); + break; } } exit(1); @@ -71,6 +77,14 @@ USB* get_usb_impl() return createGreaseweazleUsb(conf.port(), conf); } + if (globalConfig()->usb().has_applesauce() && + globalConfig()->usb().applesauce().has_port()) + { + const auto& conf = globalConfig()->usb().applesauce(); + log("Using Applesauce on serial port {}", conf.port()); + return createApplesauceUsb(conf.port(), conf); + } + /* Otherwise, select a device by USB ID. */ auto candidate = selectDevice(); @@ -87,6 +101,13 @@ USB* get_usb_impl() return createGreaseweazleUsb( candidate->serialPort, globalConfig()->usb().greaseweazle()); + case APPLESAUCE_ID: + log("Using Applesauce {} on {}", + candidate->serial, + candidate->serialPort); + return createApplesauceUsb( + candidate->serialPort, globalConfig()->usb().applesauce()); + default: error("internal"); } diff --git a/lib/usb/usb.h b/lib/usb/usb.h index 7686ad07..d9a612c8 100644 --- a/lib/usb/usb.h +++ b/lib/usb/usb.h @@ -6,6 +6,7 @@ class Fluxmap; class GreaseweazleProto; +class ApplesauceProto; namespace libusbp { class device; @@ -16,8 +17,10 @@ class USB public: virtual ~USB(); - virtual int getVersion() = 0; - virtual void recalibrate() = 0; + virtual void recalibrate() + { + seek(0); + }; virtual void seek(int track) = 0; virtual nanoseconds_t getRotationalPeriod(int hardSectorCount) = 0; virtual void testBulkWrite() = 0; @@ -41,11 +44,9 @@ extern USB& getUsb(); extern USB* createFluxengineUsb(libusbp::device& device); extern USB* createGreaseweazleUsb( const std::string& serialPort, const GreaseweazleProto& config); +extern USB* createApplesauceUsb( + const std::string& serialPort, const ApplesauceProto& 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 2c61a91a..14f865fc 100644 --- a/lib/usb/usb.proto +++ b/lib/usb/usb.proto @@ -16,9 +16,17 @@ message GreaseweazleProto { [(help) = "which FDD bus type is in use", default = IBMPC]; } +message ApplesauceProto { + optional string port = 1 + [(help) = "Applesauce serial port to use"]; + optional bool verbose = 2 + [(help) = "Enable verbose protocol logging", default = false]; +} + message UsbProto { optional string serial = 1 [(help) = "serial number of FluxEngine or Greaseweazle device to use"]; optional GreaseweazleProto greaseweazle = 2 [(help) = "Greaseweazle-specific options"]; + optional ApplesauceProto applesauce = 3 [(help) = "Applesauce-specific options"]; } diff --git a/lib/usb/usbfinder.cc b/lib/usb/usbfinder.cc index d3a87003..1d98174f 100644 --- a/lib/usb/usbfinder.cc +++ b/lib/usb/usbfinder.cc @@ -3,12 +3,13 @@ #include "usb.h" #include "lib/bytes.h" #include "usbfinder.h" +#include "applesauce.h" #include "greaseweazle.h" #include "protocol.h" #include "libusbp.hpp" static const std::set VALID_DEVICES = { - GREASEWEAZLE_ID, FLUXENGINE_ID}; + GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID}; static const std::string get_serial_number(const libusbp::device& device) { @@ -41,13 +42,17 @@ std::vector> findUsbDevices() candidate->serial = get_serial_number(it); if (id == GREASEWEAZLE_ID) + candidate->type = DEVICE_GREASEWEAZLE; + else if (id == APPLESAUCE_ID) + candidate->type = DEVICE_APPLESAUCE; + else if (id == FLUXENGINE_ID) + candidate->type = DEVICE_FLUXENGINE; + + if ((id == GREASEWEAZLE_ID) || (id == APPLESAUCE_ID)) { libusbp::serial_port port(candidate->device); candidate->serialPort = port.get_name(); - candidate->type = DEVICE_GREASEWEAZLE; } - else if (id == FLUXENGINE_ID) - candidate->type = DEVICE_FLUXENGINE; candidates.push_back(std::move(candidate)); } @@ -71,6 +76,9 @@ std::string getDeviceName(DeviceType type) case DEVICE_FLUXENGINE: return "FluxEngine"; + case DEVICE_APPLESAUCE: + return "Applesauce"; + default: return "unknown"; } diff --git a/lib/usb/usbfinder.h b/lib/usb/usbfinder.h index 2034a7d8..435c7e3b 100644 --- a/lib/usb/usbfinder.h +++ b/lib/usb/usbfinder.h @@ -7,7 +7,8 @@ enum DeviceType { DEVICE_FLUXENGINE, - DEVICE_GREASEWEAZLE + DEVICE_GREASEWEAZLE, + DEVICE_APPLESAUCE, }; extern std::string getDeviceName(DeviceType type); diff --git a/tests/applesauce.cc b/tests/applesauce.cc new file mode 100644 index 00000000..93841174 --- /dev/null +++ b/tests/applesauce.cc @@ -0,0 +1,74 @@ +#include +#include +#include +#include "lib/globals.h" +#include "lib/fluxmap.h" +#include "lib/usb/applesauce.h" + +static void test_convert(const Bytes& asbytes, const Bytes& flbytes) +{ + Bytes astoflbytes = applesauceToFluxEngine(asbytes); + Bytes fltoasbytes = fluxEngineToApplesauce(flbytes); + + if (astoflbytes != flbytes) + { + std::cout << "Applesauce to FluxEngine conversion failed.\n"; + std::cout << "Applesauce bytes:" << std::endl; + hexdump(std::cout, asbytes); + std::cout << std::endl << "Produced this:" << std::endl; + hexdump(std::cout, astoflbytes); + std::cout << std::endl << "Expected this:" << std::endl; + hexdump(std::cout, flbytes); + abort(); + } + + if (fltoasbytes != asbytes) + { + std::cout << "FluxEngine to Applesauce conversion failed.\n"; + std::cout << "FluxEngine bytes:" << std::endl; + hexdump(std::cout, flbytes); + std::cout << std::endl << "Produced this:" << std::endl; + hexdump(std::cout, fltoasbytes); + std::cout << std::endl << "Expected this:" << std::endl; + hexdump(std::cout, asbytes); + abort(); + } +} + +static void test_conversions() +{ + /* Simple one-byte intervals. */ + + test_convert(Bytes{0x20, 0x20, 0x20, 0x20}, Bytes{0xb0, 0xb0, 0xb0, 0xb0}); + + /* Long, multibyte intervals. */ + + test_convert(Bytes{0xff, 0x1f, 0x20, 0xff, 0xff, 0x20}, + Bytes{0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0xb3, + 0xb0, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0x3f, + 0xb9}); +} + +int main(int argc, const char* argv[]) +{ + test_conversions(); + return 0; +} diff --git a/tests/build.py b/tests/build.py index 0a951375..08eb68e1 100644 --- a/tests/build.py +++ b/tests/build.py @@ -18,6 +18,7 @@ "agg", "amiga", "applesingle", + "applesauce", "bitaccumulator", "bytes", "compression", diff --git a/tests/greaseweazle.cc b/tests/greaseweazle.cc index 8ddbc8dd..e0b9c23b 100644 --- a/tests/greaseweazle.cc +++ b/tests/greaseweazle.cc @@ -11,7 +11,7 @@ static void test_convert(const Bytes& gwbytes, const Bytes& flbytes) { - Bytes gwtoflbytes = greaseWeazleToFluxEngine(gwbytes, 2 * NS_PER_TICK); + Bytes gwtoflbytes = greaseweazleToFluxEngine(gwbytes, 2 * NS_PER_TICK); Bytes fltogwbytes = fluxEngineToGreaseweazle(flbytes, 2 * NS_PER_TICK); if (gwtoflbytes != flbytes)