mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-31 11:17:01 -07:00
345 lines
9.1 KiB
C++
345 lines
9.1 KiB
C++
#include "lib/core/globals.h"
|
|
#include "protocol.h"
|
|
#include "lib/data/fluxmap.h"
|
|
#include "lib/core/bytes.h"
|
|
#include "lib/usb/usb.pb.h"
|
|
#include "lib/core/utils.h"
|
|
#include "lib/usb/serial.h"
|
|
#include "lib/usb/usb.h"
|
|
#include "lib/data/fluxmapreader.h"
|
|
#include <unistd.h>
|
|
|
|
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<unsigned>& 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("> {}\n", command);
|
|
_serial->writeLine(command);
|
|
auto r = _serial->readLine();
|
|
if (_config.verbose())
|
|
fmt::print("< {}\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("<< {}\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<unsigned> 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<SerialPort> _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
|