mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-24 11:11:02 -07:00
337 lines
9.5 KiB
C++
337 lines
9.5 KiB
C++
#include "globals.h"
|
|
#include "usb.h"
|
|
#include "protocol.h"
|
|
#include "fluxmap.h"
|
|
#include "bytes.h"
|
|
#include "libusbp_config.h"
|
|
#include "libusbp.hpp"
|
|
#include "fmt/format.h"
|
|
|
|
#define MAX_TRANSFER (32*1024)
|
|
|
|
/* Hacky: the board always operates in little-endian mode. */
|
|
static uint16_t read_short_from_usb(uint16_t usb)
|
|
{
|
|
uint8_t* p = (uint8_t*)&usb;
|
|
return p[0] | (p[1] << 8);
|
|
}
|
|
|
|
class FluxEngineUsb : public USB
|
|
{
|
|
private:
|
|
uint8_t _buffer[FRAME_SIZE];
|
|
|
|
void usb_cmd_send(void* ptr, size_t len)
|
|
{
|
|
size_t rlen;
|
|
_handle.write_pipe(FLUXENGINE_CMD_OUT_EP, ptr, len, &rlen);
|
|
}
|
|
|
|
void usb_cmd_recv(void* ptr, size_t len)
|
|
{
|
|
size_t rlen;
|
|
_handle.read_pipe(FLUXENGINE_CMD_IN_EP, ptr, len, &rlen);
|
|
}
|
|
|
|
void usb_data_send(const Bytes& bytes)
|
|
{
|
|
size_t ptr = 0;
|
|
while (ptr < bytes.size())
|
|
{
|
|
size_t rlen = bytes.size() - ptr;
|
|
if (rlen > MAX_TRANSFER)
|
|
rlen = MAX_TRANSFER;
|
|
_handle.write_pipe(FLUXENGINE_DATA_OUT_EP, bytes.cbegin() + ptr, rlen, &rlen);
|
|
ptr += rlen;
|
|
}
|
|
}
|
|
|
|
void usb_data_recv(Bytes& bytes)
|
|
{
|
|
size_t ptr = 0;
|
|
while (ptr < bytes.size())
|
|
{
|
|
size_t rlen = bytes.size() - ptr;
|
|
if (rlen > MAX_TRANSFER)
|
|
rlen = MAX_TRANSFER;
|
|
_handle.read_pipe(FLUXENGINE_DATA_IN_EP, bytes.begin() + ptr, rlen, &rlen);
|
|
ptr += rlen;
|
|
if (rlen < MAX_TRANSFER)
|
|
break;
|
|
}
|
|
|
|
bytes.resize(ptr);
|
|
}
|
|
|
|
|
|
public:
|
|
FluxEngineUsb(libusbp::device& device):
|
|
_device(device),
|
|
_interface(_device, 0, false),
|
|
_handle(_interface)
|
|
{
|
|
int version = getVersion();
|
|
if (version != FLUXENGINE_PROTOCOL_VERSION)
|
|
Error() << "your FluxEngine firmware is at version " << version
|
|
<< " but the client is for version " << FLUXENGINE_PROTOCOL_VERSION
|
|
<< "; please upgrade";
|
|
}
|
|
|
|
private:
|
|
libusbp::device _device;
|
|
libusbp::generic_interface _interface;
|
|
libusbp::generic_handle _handle;
|
|
|
|
private:
|
|
void bad_reply(void)
|
|
{
|
|
struct error_frame* f = (struct error_frame*) _buffer;
|
|
if (f->f.type != F_FRAME_ERROR)
|
|
Error() << fmt::format("bad USB reply 0x{:2x}", f->f.type);
|
|
switch (f->error)
|
|
{
|
|
case F_ERROR_BAD_COMMAND:
|
|
Error() << "device did not understand command";
|
|
|
|
case F_ERROR_UNDERRUN:
|
|
Error() << "USB underrun (not enough bandwidth)";
|
|
|
|
default:
|
|
Error() << fmt::format("unknown device error {}", f->error);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
T* await_reply(int desired)
|
|
{
|
|
for (;;)
|
|
{
|
|
usb_cmd_recv(_buffer, sizeof(_buffer));
|
|
struct any_frame* r = (struct any_frame*) _buffer;
|
|
if (r->f.type == F_FRAME_DEBUG)
|
|
{
|
|
std::cout << "dev: " << ((struct debug_frame*)r)->payload << std::endl;
|
|
continue;
|
|
}
|
|
if (r->f.type != desired)
|
|
bad_reply();
|
|
return (T*) r;
|
|
}
|
|
}
|
|
|
|
public:
|
|
int getVersion()
|
|
{
|
|
struct any_frame f = { .f = {.type = F_FRAME_GET_VERSION_CMD, .size = sizeof(f)} };
|
|
usb_cmd_send(&f, f.f.size);
|
|
auto r = await_reply<struct version_frame>(F_FRAME_GET_VERSION_REPLY);
|
|
return r->version;
|
|
}
|
|
|
|
void seek(int track)
|
|
{
|
|
struct seek_frame f = {
|
|
.f = { .type = F_FRAME_SEEK_CMD, .size = sizeof(f) },
|
|
.track = (uint8_t) track
|
|
};
|
|
usb_cmd_send(&f, f.f.size);
|
|
await_reply<struct any_frame>(F_FRAME_SEEK_REPLY);
|
|
}
|
|
|
|
void recalibrate()
|
|
{
|
|
struct any_frame f = {
|
|
.f = { .type = F_FRAME_RECALIBRATE_CMD, .size = sizeof(f) },
|
|
};
|
|
usb_cmd_send(&f, f.f.size);
|
|
await_reply<struct any_frame>(F_FRAME_RECALIBRATE_REPLY);
|
|
}
|
|
|
|
nanoseconds_t getRotationalPeriod(int hardSectorCount)
|
|
{
|
|
struct measurespeed_frame f = {
|
|
.f = {.type = F_FRAME_MEASURE_SPEED_CMD, .size = sizeof(f)},
|
|
.hard_sector_count = (uint8_t) hardSectorCount,
|
|
};
|
|
usb_cmd_send(&f, f.f.size);
|
|
|
|
auto r = await_reply<struct speed_frame>(F_FRAME_MEASURE_SPEED_REPLY);
|
|
return r->period_ms * 1000000;
|
|
}
|
|
|
|
void testBulkWrite()
|
|
{
|
|
struct any_frame f = { .f = {.type = F_FRAME_BULK_WRITE_TEST_CMD, .size = sizeof(f)} };
|
|
usb_cmd_send(&f, f.f.size);
|
|
|
|
/* These must match the device. */
|
|
const int XSIZE = 64;
|
|
const int YSIZE = 256;
|
|
const int ZSIZE = 64;
|
|
|
|
std::cout << "Reading data: " << std::flush;
|
|
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
|
|
double start_time = getCurrentTime();
|
|
usb_data_recv(bulk_buffer);
|
|
double elapsed_time = getCurrentTime() - start_time;
|
|
|
|
std::cout << "transferred "
|
|
<< bulk_buffer.size()
|
|
<< " bytes from device -> PC in "
|
|
<< int(elapsed_time * 1000.0)
|
|
<< " ms ("
|
|
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
|
|
<< " kB/s)"
|
|
<< std::endl;
|
|
|
|
for (int x=0; x<XSIZE; x++)
|
|
for (int y=0; y<YSIZE; y++)
|
|
for (int z=0; z<ZSIZE; z++)
|
|
{
|
|
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
|
|
if (bulk_buffer[offset] != uint8_t(x+y+z))
|
|
Error() << "data transfer corrupted at 0x"
|
|
<< std::hex << offset << std::dec
|
|
<< " "
|
|
<< x << '.' << y << '.' << z << '.';
|
|
}
|
|
|
|
await_reply<struct any_frame>(F_FRAME_BULK_WRITE_TEST_REPLY);
|
|
}
|
|
|
|
void testBulkRead()
|
|
{
|
|
struct any_frame f = { .f = {.type = F_FRAME_BULK_READ_TEST_CMD, .size = sizeof(f)} };
|
|
usb_cmd_send(&f, f.f.size);
|
|
|
|
/* These must match the device. */
|
|
const int XSIZE = 64;
|
|
const int YSIZE = 256;
|
|
const int ZSIZE = 64;
|
|
|
|
Bytes bulk_buffer(XSIZE*YSIZE*ZSIZE);
|
|
for (int x=0; x<XSIZE; x++)
|
|
for (int y=0; y<YSIZE; y++)
|
|
for (int z=0; z<ZSIZE; z++)
|
|
{
|
|
int offset = x*XSIZE*YSIZE + y*ZSIZE + z;
|
|
bulk_buffer[offset] = uint8_t(x+y+z);
|
|
}
|
|
|
|
std::cout << "Writing data: " << std::flush;
|
|
double start_time = getCurrentTime();
|
|
usb_data_send(bulk_buffer);
|
|
double elapsed_time = getCurrentTime() - start_time;
|
|
|
|
std::cout << "transferred "
|
|
<< bulk_buffer.size()
|
|
<< " bytes from PC -> device in "
|
|
<< int(elapsed_time * 1000.0)
|
|
<< " ms ("
|
|
<< int((bulk_buffer.size() / 1024.0) / elapsed_time)
|
|
<< " kB/s)"
|
|
<< std::endl;
|
|
|
|
await_reply<struct any_frame>(F_FRAME_BULK_READ_TEST_REPLY);
|
|
}
|
|
|
|
Bytes read(int side, bool synced, nanoseconds_t readTime,
|
|
nanoseconds_t hardSectorThreshold)
|
|
{
|
|
struct read_frame f = {
|
|
.f = { .type = F_FRAME_READ_CMD, .size = sizeof(f) },
|
|
.side = (uint8_t) side,
|
|
.synced = (uint8_t) synced,
|
|
};
|
|
f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
|
uint16_t milliseconds = readTime / 1e6;
|
|
((uint8_t*)&f.milliseconds)[0] = milliseconds;
|
|
((uint8_t*)&f.milliseconds)[1] = milliseconds >> 8;
|
|
usb_cmd_send(&f, f.f.size);
|
|
|
|
auto fluxmap = std::unique_ptr<Fluxmap>(new Fluxmap);
|
|
|
|
Bytes buffer(1024*1024);
|
|
usb_data_recv(buffer);
|
|
|
|
await_reply<struct any_frame>(F_FRAME_READ_REPLY);
|
|
return buffer;
|
|
}
|
|
|
|
void write(int side, const Bytes& bytes, nanoseconds_t hardSectorThreshold)
|
|
{
|
|
unsigned safelen = bytes.size() & ~(FRAME_SIZE-1);
|
|
Bytes safeBytes = bytes.slice(0, safelen);
|
|
|
|
struct write_frame f = {
|
|
.f = { .type = F_FRAME_WRITE_CMD, .size = sizeof(f) },
|
|
.side = (uint8_t) side,
|
|
};
|
|
f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
|
((uint8_t*)&f.bytes_to_write)[0] = safelen;
|
|
((uint8_t*)&f.bytes_to_write)[1] = safelen >> 8;
|
|
((uint8_t*)&f.bytes_to_write)[2] = safelen >> 16;
|
|
((uint8_t*)&f.bytes_to_write)[3] = safelen >> 24;
|
|
|
|
usb_cmd_send(&f, f.f.size);
|
|
usb_data_send(safeBytes);
|
|
|
|
await_reply<struct any_frame>(F_FRAME_WRITE_REPLY);
|
|
}
|
|
|
|
void erase(int side, nanoseconds_t hardSectorThreshold)
|
|
{
|
|
struct erase_frame f = {
|
|
.f = { .type = F_FRAME_ERASE_CMD, .size = sizeof(f) },
|
|
.side = (uint8_t) side,
|
|
};
|
|
f.hardsec_threshold_ms = (hardSectorThreshold + 5e5) / 1e6; /* round to nearest ms */
|
|
usb_cmd_send(&f, f.f.size);
|
|
|
|
await_reply<struct any_frame>(F_FRAME_ERASE_REPLY);
|
|
}
|
|
|
|
void setDrive(int drive, bool high_density, int index_mode)
|
|
{
|
|
struct set_drive_frame f = {
|
|
.f = { .type = F_FRAME_SET_DRIVE_CMD, .size = sizeof(f) },
|
|
.drive = (uint8_t) drive,
|
|
.high_density = high_density,
|
|
.index_mode = (uint8_t) index_mode
|
|
};
|
|
usb_cmd_send(&f, f.f.size);
|
|
await_reply<struct any_frame>(F_FRAME_SET_DRIVE_REPLY);
|
|
}
|
|
|
|
void measureVoltages(struct voltages_frame* voltages)
|
|
{
|
|
struct any_frame f = {
|
|
{ .type = F_FRAME_MEASURE_VOLTAGES_CMD, .size = sizeof(f) },
|
|
};
|
|
usb_cmd_send(&f, f.f.size);
|
|
|
|
auto convert_voltages_from_usb = [&](const struct voltages& vin, struct voltages& vout)
|
|
{
|
|
vout.logic0_mv = read_short_from_usb(vin.logic0_mv);
|
|
vout.logic1_mv = read_short_from_usb(vin.logic1_mv);
|
|
};
|
|
|
|
struct voltages_frame* r = await_reply<struct voltages_frame>(F_FRAME_MEASURE_VOLTAGES_REPLY);
|
|
convert_voltages_from_usb(r->input_both_off, voltages->input_both_off);
|
|
convert_voltages_from_usb(r->input_drive_0_selected, voltages->input_drive_0_selected);
|
|
convert_voltages_from_usb(r->input_drive_1_selected, voltages->input_drive_1_selected);
|
|
convert_voltages_from_usb(r->input_drive_0_running, voltages->input_drive_0_running);
|
|
convert_voltages_from_usb(r->input_drive_1_running, voltages->input_drive_1_running);
|
|
convert_voltages_from_usb(r->output_both_off, voltages->output_both_off);
|
|
convert_voltages_from_usb(r->output_drive_0_selected, voltages->output_drive_0_selected);
|
|
convert_voltages_from_usb(r->output_drive_1_selected, voltages->output_drive_1_selected);
|
|
convert_voltages_from_usb(r->output_drive_0_running, voltages->output_drive_0_running);
|
|
convert_voltages_from_usb(r->output_drive_1_running, voltages->output_drive_1_running);
|
|
}
|
|
};
|
|
|
|
USB* createFluxengineUsb(libusbp::device& device)
|
|
{
|
|
return new FluxEngineUsb(device);
|
|
}
|