From 74f0fd89b6aa37576b4fe4d8ff2a056acbcf15df Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 6 Jan 2021 22:52:52 +0100 Subject: [PATCH] We can successfully seek on the GreaseWeazle. --- lib/globals.h | 2 + lib/usb/greaseweazle.h | 186 +++++++++++++++++++++++++++++++++ lib/usb/greaseweazleusb.cc | 207 +++++++++++++++++++++++++++++++------ 3 files changed, 362 insertions(+), 33 deletions(-) diff --git a/lib/globals.h b/lib/globals.h index d3b08cc6..201a4496 100644 --- a/lib/globals.h +++ b/lib/globals.h @@ -12,6 +12,8 @@ #include #include +#define packed __attribute((packed)) + typedef double nanoseconds_t; class Bytes; diff --git a/lib/usb/greaseweazle.h b/lib/usb/greaseweazle.h index 7ac872e2..e04686cd 100644 --- a/lib/usb/greaseweazle.h +++ b/lib/usb/greaseweazle.h @@ -4,5 +4,191 @@ #define GREASEWEAZLE_VID 0x1209 #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, . + * Host follows with bytes. + * Bootloader finally returns a status byte, 0 on success. */ +/* [MAIN FIRMWARE] CMD_UPDATE, length=10, , 0xdeafbee3. + * Host follows with 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, */ +#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, */ +#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 diff --git a/lib/usb/greaseweazleusb.cc b/lib/usb/greaseweazleusb.cc index d528cd5d..3cf2bc74 100644 --- a/lib/usb/greaseweazleusb.cc +++ b/lib/usb/greaseweazleusb.cc @@ -5,50 +5,191 @@ #include "bytes.h" #include #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 { + 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 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: - GreaseWeazleUsb(libusb_device_handle* device) {} - ~GreaseWeazleUsb() {} + GreaseWeazleUsb(libusb_device_handle* device) + { + _device = device; - int getVersion() - { Error() << "unsupported operation"; } + /* Configure the device. */ - void recalibrate() - { Error() << "unsupported operation"; } - - void seek(int track) - { Error() << "unsupported operation"; } - - 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) - { Error() << "unsupported operation"; } + int i; + int cfg = -1; + libusb_get_configuration(_device, &cfg); + if (cfg != 1) + { + i = libusb_set_configuration(_device, 1); + if (i < 0) + Error() << "the GreaseWeazle would not accept configuration: " << usberror(i); + } - void measureVoltages(struct voltages_frame* voltages) - { Error() << "unsupported operation"; } + /* Detach the existing kernel serial port driver, if there is one, and claim it ourselves. */ + + 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) { - return new GreaseWeazleUsb(device); + return new GreaseWeazleUsb(device); } +// vim: sw=4 ts=4 et