diff --git a/build.py b/build.py index ed97ddec..42403bc0 100644 --- a/build.py +++ b/build.py @@ -90,6 +90,7 @@ "./lib/proto.cc", "./lib/readerwriter.cc", "./lib/sector.cc", + "./lib/usb/applesauceusb.cc", "./lib/usb/fluxengineusb.cc", "./lib/usb/greaseweazle.cc", "./lib/usb/greaseweazleusb.cc", diff --git a/lib/usb/applesauceusb.cc b/lib/usb/applesauceusb.cc new file mode 100644 index 00000000..95a6a985 --- /dev/null +++ b/lib/usb/applesauceusb.cc @@ -0,0 +1,335 @@ +#include "lib/globals.h" +#include "protocol.h" +#include "lib/fluxmap.h" +#include "lib/bytes.h" +#include "lib/usb/usb.pb.h" +#include "applesauce.h" +#include "serial.h" +#include "usb.h" +#include + +static uint32_t ss_rand_next(uint32_t x) +{ + return (x & 1) ? (x >> 1) ^ 0x80000062 : x >> 1; +} + +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); + } + + ~ApplesauceUsb() + { + sendrecv("disconnect"); + } + +private: + std::string sendrecv(const std::string& command) + { + _serial->writeLine(command); + return _serial->readLine(); + } + + void connect() + { + if (!_connected) + { + std::string probe = sendrecv("connect"); + if (probe != ".") + error("Applesauce could not find any drives"); + _connected = true; + } + } + +public: + int getVersion() override + { + // do_command({CMD_GET_INFO, 3, GETINFO_FIRMWARE}); + // + // Bytes response = _serial->readBytes(32); + // ByteReader br(response); + // + // br.seek(4); + // nanoseconds_t freq = br.read_le32(); + // _clock = 1000000000 / freq; + // + // br.seek(0); + // return br.read_be16(); + error("unsupported operation getVersion on the Greaseweazle"); + } + + void seek(int track) override + { + // do_command({CMD_SEEK, 3, (uint8_t)track}); + error("unsupported operation seek on the Greaseweazle"); + } + + nanoseconds_t getRotationalPeriod(int hardSectorCount) override + { + // if (hardSectorCount != 0) + // error("hard sectors are currently unsupported on the + // Greaseweazle"); + + // /* The Greaseweazle doesn't have a command to fetch the period + // directly, + // * so we have to do a flux read. */ + + // switch (_version) + // { + // case V22: + // do_command({CMD_READ_FLUX, 2}); + // break; + + // case V24: + // case V29: + // { + // Bytes cmd(8); + // cmd.writer() + // .write_8(CMD_READ_FLUX) + // .write_8(cmd.size()) + // .write_le32(0) // ticks default value (guessed) + // .write_le16(2); // revolutions + // do_command(cmd); + // } + // } + + // uint32_t ticks_gw = 0; + // uint32_t firstindex = ~0; + // uint32_t secondindex = ~0; + // for (;;) + // { + // uint8_t b = _serial->readByte(); + // if (!b) + // break; + + // if (b == 255) + // { + // switch (_serial->readByte()) + // { + // case FLUXOP_INDEX: + // { + // uint32_t index = read_28() + ticks_gw; + // if (firstindex == ~0) + // firstindex = index; + // else if (secondindex == ~0) + // secondindex = index; + // break; + // } + + // case FLUXOP_SPACE: + // ticks_gw += read_28(); + // break; + + // default: + // error("bad opcode in Greaseweazle stream"); + // } + // } + // else + // { + // if (b < 250) + // ticks_gw += b; + // else + // { + // int delta = 250 + (b - 250) * 255 + _serial->readByte() - + // 1; ticks_gw += delta; + // } + // } + // } + + // if (secondindex == ~0) + // error( + // "unable to determine disk rotational period (is a disk in the + // " "drive?)"); + // do_command({CMD_GET_FLUX_STATUS, 2}); + + // _revolutions = (nanoseconds_t)(secondindex - firstindex) * _clock; + // return _revolutions; + error("unsupported operation getRotationalPeriod on the Greaseweazle"); + } + + void testBulkWrite() override + { + int max = std::stoi(sendrecv("data:?max")); + fmt::print("Writing data: "); + + if (sendrecv(fmt::format("data:>{}", max)) != ".") + error("Cannot write to Applesauce"); + + 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: "); + + if (sendrecv(fmt::format("data:<{}", max)) != ".") + error("Cannot read from Applesauce"); + + 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 Greaseweazle"); + // + // 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) + // .write_8(cmd.size()) + // .write_le32(revolutions + (synced ? 1 : 0)); + // do_command(cmd); + // break; + // } + // + // case V24: + // case V29: + // { + // Bytes cmd(8); + // cmd.writer() + // .write_8(CMD_READ_FLUX) + // .write_8(cmd.size()) + // .write_le32( + // (readTime + (synced ? _revolutions : 0)) / _clock) + // .write_le16(0); + // do_command(cmd); + // } + // } + // + // Bytes buffer; + // ByteWriter bw(buffer); + // for (;;) + // { + // uint8_t b = _serial->readByte(); + // if (!b) + // break; + // bw.write_8(b); + // } + // + // do_command({CMD_GET_FLUX_STATUS, 2}); + // + // Bytes fldata = greaseWeazleToFluxEngine(buffer, _clock); + // if (synced) + // fldata = stripPartialRotation(fldata); + // return fldata; + error("unsupported operation read on the Greaseweazle"); + } + + void write(int side, + const Bytes& fldata, + nanoseconds_t hardSectorThreshold) override + { + // if (hardSectorThreshold != 0) + // error("hard sectors are currently unsupported on the Greaseweazle"); + // + // do_command({CMD_HEAD, 3, (uint8_t)side}); + // switch (_version) + // { + // case V22: + // do_command({CMD_WRITE_FLUX, 3, 1}); + // break; + // + // case V24: + // case V29: + // do_command({CMD_WRITE_FLUX, 4, 1, 1}); + // break; + // } + // _serial->write(fluxEngineToGreaseweazle(fldata, _clock)); + // _serial->readByte(); /* synchronise */ + // + // do_command({CMD_GET_FLUX_STATUS, 2}); + error("unsupported operation write on the Greaseweazle"); + } + + void erase(int side, nanoseconds_t hardSectorThreshold) override + { + // if (hardSectorThreshold != 0) + // error("hard sectors are currently unsupported on the Greaseweazle"); + // + // do_command({CMD_HEAD, 3, (uint8_t)side}); + // + // Bytes cmd(6); + // ByteWriter bw(cmd); + // bw.write_8(CMD_ERASE_FLUX); + // bw.write_8(cmd.size()); + // bw.write_le32(200e6 / _clock); + // do_command(cmd); + // _serial->readByte(); /* synchronise */ + // + // do_command({CMD_GET_FLUX_STATUS, 2}); + error("unsupported operation erase on the Greaseweazle"); + } + + void setDrive(int drive, bool high_density, int index_mode) override + { + if (drive != 0) + error("the Applesauce only supports drive 0"); + + connect(); + sendrecv("drive:enable"); + sendrecv("motor:on"); + sendrecv(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) +{ + return new ApplesauceUsb(port, config); +} + +// vim: sw=4 ts=4 et diff --git a/lib/usb/greaseweazleusb.cc b/lib/usb/greaseweazleusb.cc index 4496adc2..b3192bd6 100644 --- a/lib/usb/greaseweazleusb.cc +++ b/lib/usb/greaseweazleusb.cc @@ -124,11 +124,6 @@ class GreaseweazleUsb : public USB return br.read_be16(); } - void recalibrate() override - { - seek(0); - } - void seek(int track) override { do_command({CMD_SEEK, 3, (uint8_t)track}); 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 b79e880f..201c3192 100644 --- a/lib/usb/usb.cc +++ b/lib/usb/usb.cc @@ -77,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(); @@ -94,7 +102,11 @@ USB* get_usb_impl() candidate->serialPort, globalConfig()->usb().greaseweazle()); case APPLESAUCE_ID: - error("Applesauce not supported yet"); + 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..bc0cd910 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; @@ -17,7 +18,10 @@ class USB 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,6 +45,8 @@ 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() { diff --git a/lib/usb/usb.proto b/lib/usb/usb.proto index 2c61a91a..15439b7e 100644 --- a/lib/usb/usb.proto +++ b/lib/usb/usb.proto @@ -16,9 +16,15 @@ message GreaseweazleProto { [(help) = "which FDD bus type is in use", default = IBMPC]; } +message ApplesauceProto { + optional string port = 1 + [(help) = "Applesauce serial port to use"]; +} + 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 20b546a3..1d98174f 100644 --- a/lib/usb/usbfinder.cc +++ b/lib/usb/usbfinder.cc @@ -42,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)); }