We can successfully seek on the GreaseWeazle.

This commit is contained in:
David Given
2021-01-06 22:52:52 +01:00
parent 09f9bea7a2
commit 74f0fd89b6
3 changed files with 362 additions and 33 deletions

View File

@@ -12,6 +12,8 @@
#include <set> #include <set>
#include <cassert> #include <cassert>
#define packed __attribute((packed))
typedef double nanoseconds_t; typedef double nanoseconds_t;
class Bytes; class Bytes;

View File

@@ -4,5 +4,191 @@
#define GREASEWEAZLE_VID 0x1209 #define GREASEWEAZLE_VID 0x1209
#define GREASEWEAZLE_PID 0x4d69 #define GREASEWEAZLE_PID 0x4d69
#define EP_OUT 0x02
#define EP_IN 0x83
#define GREASEWEAZLE_VERSION 20
/* Copied from https://github.com/keirf/Greaseweazle/blob/master/inc/cdc_acm_protocol.h. */
/*
* GREASEWEAZLE COMMAND SET
*/
/* CMD_GET_INFO, length=3, idx. Returns 32 bytes after ACK. */
#define CMD_GET_INFO 0
/* [BOOTLOADER] CMD_UPDATE, length=6, <update_len>.
* Host follows with <update_len> bytes.
* Bootloader finally returns a status byte, 0 on success. */
/* [MAIN FIRMWARE] CMD_UPDATE, length=10, <update_len>, 0xdeafbee3.
* Host follows with <update_len> bytes.
* Main firmware finally returns a status byte, 0 on success. */
#define CMD_UPDATE 1
/* CMD_SEEK, length=3, cyl#. Seek to cyl# on selected drive. */
#define CMD_SEEK 2
/* CMD_HEAD, length=3, head# (0=bottom) */
#define CMD_HEAD 3
/* CMD_SET_PARAMS, length=3+nr, idx, <nr bytes> */
#define CMD_SET_PARAMS 4
/* CMD_GET_PARAMS, length=4, idx, nr_bytes. Returns nr_bytes after ACK. */
#define CMD_GET_PARAMS 5
/* CMD_MOTOR, length=4, drive#, on/off. Turn on/off a drive motor. */
#define CMD_MOTOR 6
/* CMD_READ_FLUX, length=2-8. Argument is gw_read_flux.
* Returns flux readings until EOStream. */
#define CMD_READ_FLUX 7
/* CMD_WRITE_FLUX, length=2-4. Argument is gw_write_flux.
* Host follows with flux readings until EOStream. */
#define CMD_WRITE_FLUX 8
/* CMD_GET_FLUX_STATUS, length=2. Last read/write status returned in ACK. */
#define CMD_GET_FLUX_STATUS 9
/* CMD_SWITCH_FW_MODE, length=3, <mode> */
#define CMD_SWITCH_FW_MODE 11
/* CMD_SELECT, length=3, drive#. Select drive# as current unit. */
#define CMD_SELECT 12
/* CMD_DESELECT, length=2. Deselect current unit (if any). */
#define CMD_DESELECT 13
/* CMD_SET_BUS_TYPE, length=3, bus_type. Set the bus type. */
#define CMD_SET_BUS_TYPE 14
/* CMD_SET_PIN, length=4, pin#, level. */
#define CMD_SET_PIN 15
/* CMD_RESET, length=2. Reset all state to initial (power on) values. */
#define CMD_RESET 16
/* CMD_ERASE_FLUX, length=6. Argument is gw_erase_flux. */
#define CMD_ERASE_FLUX 17
/* CMD_SOURCE_BYTES, length=6. Argument is gw_sink_source_bytes. */
#define CMD_SOURCE_BYTES 18
/* CMD_SINK_BYTES, length=6. Argument is gw_sink_source_bytes. */
#define CMD_SINK_BYTES 19
#define CMD_MAX 19
/*
* CMD_SET_BUS CODES
*/
#define BUS_NONE 0
#define BUS_IBMPC 1
#define BUS_SHUGART 2
/*
* ACK RETURN CODES
*/
#define ACK_OKAY 0
#define ACK_BAD_COMMAND 1
#define ACK_NO_INDEX 2
#define ACK_NO_TRK0 3
#define ACK_FLUX_OVERFLOW 4
#define ACK_FLUX_UNDERFLOW 5
#define ACK_WRPROT 6
#define ACK_NO_UNIT 7
#define ACK_NO_BUS 8
#define ACK_BAD_UNIT 9
#define ACK_BAD_PIN 10
#define ACK_BAD_CYLINDER 11
/*
* CONTROL-CHANNEL COMMAND SET:
* We abuse SET_LINE_CODING requests over endpoint 0, stashing a command
* in the baud-rate field.
*/
#define BAUD_NORMAL 9600
#define BAUD_CLEAR_COMMS 10000
/*
* Flux stream opcodes. Preceded by 0xFF byte.
*
* Argument types:
* N28: 28-bit non-negative integer N, encoded as 4 bytes b0,b1,b2,b3:
* b0 = (uint8_t)(1 | (N << 1))
* b1 = (uint8_t)(1 | (N >> 6))
* b2 = (uint8_t)(1 | (N >> 13))
* b3 = (uint8_t)(1 | (N >> 20))
*/
/* FLUXOP_INDEX [CMD_READ_FLUX]
* Args:
* +4 [N28]: ticks to index, relative to sample cursor.
* Signals an index pulse in the read stream. Sample cursor is unaffected. */
#define FLUXOP_INDEX 1
/* FLUXOP_SPACE [CMD_READ_FLUX, CMD_WRITE_FLUX]
* Args:
* +4 [N28]: ticks to increment the sample cursor.
* Increments the sample cursor with no intervening flux transitions. */
#define FLUXOP_SPACE 2
/* FLUXOP_ASTABLE [CMD_WRITE_FLUX]
* Args:
* +4 [N28]: astable period.
* Generate regular flux transitions at specified astable period.
* Duration is specified by immediately preceding FLUXOP_SPACE opcode(s). */
#define FLUXOP_ASTABLE 3
/*
* COMMAND PACKETS
*/
/* CMD_GET_INFO, index 0 */
#define GETINFO_FIRMWARE 0
struct packed gw_info {
uint8_t fw_major;
uint8_t fw_minor;
uint8_t is_main_firmware; /* == 0 -> update bootloader */
uint8_t max_cmd;
uint32_t sample_freq;
uint8_t hw_model, hw_submodel;
uint8_t usb_speed;
};
extern struct gw_info gw_info;
/* CMD_GET_INFO, index 1 */
#define GETINFO_BW_STATS 1
struct packed gw_bw_stats {
struct packed {
uint32_t bytes;
uint32_t usecs;
} min_bw, max_bw;
};
/* CMD_READ_FLUX */
struct packed gw_read_flux {
/* Maximum ticks to read for (or 0, for no limit). */
uint32_t ticks;
/* Maximum index pulses to read (or 0, for no limit). */
uint16_t max_index;
};
/* CMD_WRITE_FLUX */
struct packed gw_write_flux {
/* If non-zero, start the write at the index pulse. */
uint8_t cue_at_index;
/* If non-zero, terminate the write at the next index pulse. */
uint8_t terminate_at_index;
};
/* CMD_ERASE_FLUX */
struct packed gw_erase_flux {
uint32_t ticks;
};
/* CMD_SINK_SOURCE_BYTES */
struct packed gw_sink_source_bytes {
uint32_t nr_bytes;
};
/* CMD_{GET,SET}_PARAMS, index 0 */
#define PARAMS_DELAYS 0
struct packed gw_delay {
uint16_t select_delay; /* usec */
uint16_t step_delay; /* usec */
uint16_t seek_settle; /* msec */
uint16_t motor_delay; /* msec */
uint16_t auto_off; /* msec */
};
/* CMD_SWITCH_FW_MODE */
#define FW_MODE_BOOTLOADER 0
#define FW_MODE_NORMAL 1
#endif #endif

View File

@@ -5,50 +5,191 @@
#include "bytes.h" #include "bytes.h"
#include <libusb.h> #include <libusb.h>
#include "fmt/format.h" #include "fmt/format.h"
#include "greaseweazle.h"
#define TIMEOUT 5000
static const char* gw_error(int e)
{
switch (e)
{
case ACK_OKAY: return "OK";
case ACK_BAD_COMMAND: return "Bad command";
case ACK_NO_INDEX: return "No index";
case ACK_NO_TRK0: return "No track 0";
case ACK_FLUX_OVERFLOW: return "Overflow";
case ACK_FLUX_UNDERFLOW: return "Underflow";
case ACK_WRPROT: return "Write protected";
case ACK_NO_UNIT: return "No unit";
case ACK_NO_BUS: return "No bus";
case ACK_BAD_UNIT: return "Invalid unit";
case ACK_BAD_PIN: return "Invalid pin";
case ACK_BAD_CYLINDER: return "Invalid cylinder";
default: return "Unknown error";
}
}
class GreaseWeazleUsb : public USB class GreaseWeazleUsb : public USB
{ {
uint8_t _readbuffer[4096];
int _readbuffer_ptr = 0;
int _readbuffer_fill = 0;
void read_bytes(uint8_t* buffer, int len)
{
while (len > 0)
{
if (_readbuffer_ptr < _readbuffer_fill)
{
int buffered = std::min(len, _readbuffer_fill - _readbuffer_ptr);
memcpy(buffer, _readbuffer + _readbuffer_ptr, buffered);
_readbuffer_ptr += buffered;
buffer += buffered;
len -= buffered;
}
if (len == 0)
break;
int actual;
int rc = libusb_bulk_transfer(_device, EP_IN,
_readbuffer, sizeof(_readbuffer),
&actual, TIMEOUT);
if (rc < 0)
Error() << "failed to receive command reply: " << usberror(rc);
_readbuffer_fill = actual;
_readbuffer_ptr = 0;
}
}
void read_bytes(Bytes& bytes)
{
read_bytes(bytes.begin(), bytes.size());
}
Bytes read_bytes(unsigned len)
{
Bytes b(len);
read_bytes(b);
return b;
}
void write_bytes(const uint8_t* buffer, int len)
{
while (len > 0)
{
int actual;
int rc = libusb_bulk_transfer(_device, EP_OUT, (uint8_t*)buffer, len, &actual, 0);
if (rc < 0)
Error() << "failed to send command: " << usberror(rc);
buffer += actual;
len -= actual;
}
}
void do_command(std::initializer_list<uint8_t> data)
{
int cmd = *data.begin();
write_bytes(data.begin(), data.end() - data.begin());
uint8_t buffer[2];
read_bytes(buffer, sizeof(buffer));
if (buffer[0] != cmd)
Error() << fmt::format("command returned garbage (0x{:x} != 0x{:x} with status 0x{:x})", buffer[0], cmd, buffer[1]);
if (buffer[1])
Error() << fmt::format("GreaseWeazle error: {}", gw_error(buffer[1]));
}
public: public:
GreaseWeazleUsb(libusb_device_handle* device) {} GreaseWeazleUsb(libusb_device_handle* device)
~GreaseWeazleUsb() {} {
_device = device;
int getVersion() /* Configure the device. */
{ Error() << "unsupported operation"; }
void recalibrate() int i;
{ Error() << "unsupported operation"; } int cfg = -1;
libusb_get_configuration(_device, &cfg);
void seek(int track) if (cfg != 1)
{ Error() << "unsupported operation"; } {
i = libusb_set_configuration(_device, 1);
nanoseconds_t getRotationalPeriod() if (i < 0)
{ Error() << "unsupported operation"; } Error() << "the GreaseWeazle would not accept configuration: " << usberror(i);
}
void testBulkWrite()
{ Error() << "unsupported operation"; }
void testBulkRead()
{ Error() << "unsupported operation"; }
Bytes read(int side, bool synced, nanoseconds_t readTime)
{ Error() << "unsupported operation"; }
void write(int side, const Bytes& bytes)
{ Error() << "unsupported operation"; }
void erase(int side)
{ Error() << "unsupported operation"; }
void setDrive(int drive, bool high_density, int index_mode)
{ Error() << "unsupported operation"; }
void measureVoltages(struct voltages_frame* voltages) /* Detach the existing kernel serial port driver, if there is one, and claim it ourselves. */
{ Error() << "unsupported operation"; }
for (int i = 0; i < 2; i++)
{
if (libusb_kernel_driver_active(_device, i))
libusb_detach_kernel_driver(_device, i);
int rc = libusb_claim_interface(_device, i);
if (rc < 0)
Error() << "unable to claim interface: " << libusb_error_name(rc);
}
int version = getVersion();
if (version != GREASEWEAZLE_VERSION)
Error() << "your GreaseWeazle firmware is at version " << version
<< " but the client is for version " << GREASEWEAZLE_VERSION
<< "; please upgrade";
/* Configure the hardware. */
do_command({ CMD_SET_BUS_TYPE, 3, BUS_IBMPC });
}
int getVersion()
{
do_command({ CMD_GET_INFO, 3, GETINFO_FIRMWARE });
Bytes response = read_bytes(32);
ByteReader br(response);
return br.read_be16();
}
void recalibrate()
{ Error() << "unsupported operation"; }
void seek(int track)
{
do_command({ CMD_SEEK, 3, (uint8_t)track });
}
nanoseconds_t getRotationalPeriod()
{ Error() << "unsupported operation"; }
void testBulkWrite()
{ Error() << "unsupported operation"; }
void testBulkRead()
{ Error() << "unsupported operation"; }
Bytes read(int side, bool synced, nanoseconds_t readTime)
{ Error() << "unsupported operation"; }
void write(int side, const Bytes& bytes)
{ Error() << "unsupported operation"; }
void erase(int side)
{ Error() << "unsupported operation"; }
void setDrive(int drive, bool high_density, int index_mode)
{
do_command({ CMD_SELECT, 3, (uint8_t)drive });
}
void measureVoltages(struct voltages_frame* voltages)
{ Error() << "unsupported operation"; }
}; };
USB* createGreaseWeazleUsb(libusb_device_handle* device) USB* createGreaseWeazleUsb(libusb_device_handle* device)
{ {
return new GreaseWeazleUsb(device); return new GreaseWeazleUsb(device);
} }
// vim: sw=4 ts=4 et