]> sigrok.org Git - libsigrok.git/commitdiff
greatfet: first operational GreatFET One driver implementation
authorGerhard Sittig <redacted>
Fri, 6 Oct 2023 16:09:58 +0000 (18:09 +0200)
committerGerhard Sittig <redacted>
Sun, 29 Oct 2023 08:22:54 +0000 (09:22 +0100)
This sigrok driver for the GreatFET One device as a logic analyzer is
based on https://github.com/Qyriad/libsigrok and was massaged to use
common infrastructure where appropriate, and adjust coding style to
match the sigrok.org project's code base.

This implementation supports the acquisition of 8 channels at rates up
to 40.8MHz, and higher rates up to 204MHz for reduced channel counts.
Soft triggers are not supported in this implementation, ideally common
support would cover this more transparently in the future.

Tested with v2021.2.1 firmware as available via 'pip install greatfet'.

Support for 16 channels is prepared but disabled by default. Requires
firmware support which is not officially available.

configure.ac
src/hardware/greatfet/api.c
src/hardware/greatfet/protocol.c
src/hardware/greatfet/protocol.h

index aee92a9014c1d2c449c9cc784409fdbbfc4566e3..5c30a8165f383484c07c0eab6210d085b7a376a7 100644 (file)
@@ -327,7 +327,7 @@ SR_DRIVER([Fluke DMM], [fluke-dmm], [serial_comm])
 SR_DRIVER([FTDI LA], [ftdi-la], [libusb libftdi])
 SR_DRIVER([fx2lafw], [fx2lafw], [libusb])
 SR_DRIVER([GMC MH 1x/2x], [gmc-mh-1x-2x], [serial_comm])
-SR_DRIVER([GreatFET], [greatfet])
+SR_DRIVER([Great Scott Gadgets GreatFET One], [greatfet], [libusb])
 SR_DRIVER([GW Instek GDS-800], [gwinstek-gds-800], [serial_comm])
 SR_DRIVER([GW Instek GPD], [gwinstek-gpd], [serial_comm])
 SR_DRIVER([Hameg HMO], [hameg-hmo])
index 7de40a3540cc602e0f157c260e36e593e689ade8..0c451e7878e67a34fea10fe0c559f96477bac0c9 100644 (file)
@@ -1,6 +1,8 @@
 /*
  * This file is part of the libsigrok project.
  *
+ * Copyright (C) 2019 Katherine J. Temkin <k@ktemkin.com>
+ * Copyright (C) 2019 Mikaela Szekely <qyriad@gmail.com>
  * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
  *
  * This program is free software: you can redistribute it and/or modify
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
+
 #include "protocol.h"
 
-static struct sr_dev_driver greatfet_driver_info;
+#define DEFAULT_CONN           "1d50.60e6"
+#define CONTROL_INTERFACE      0
+#define SAMPLES_INTERFACE      1
+
+#define VENDOR_TEXT            "Great Scott Gadgets"
+#define MODEL_TEXT             "GreatFET"
+
+#define BUFFER_SIZE            (4 * 1024 * 1024)
+
+#define DEFAULT_SAMPLERATE     SR_KHZ(34000)
+#define BANDWIDTH_THRESHOLD    (SR_MHZ(42) * 8)
+
+#define WITH_16CHAN_SUPPORT    0
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+       SR_CONF_PROBE_NAMES,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_LOGIC_ANALYZER,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONTINUOUS | SR_CONF_GET,
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint32_t devopts_cg[] = {
+       /* EMPTY */
+};
+
+static const char *channel_names[] = {
+       "SGPIO0", "SGPIO1", "SGPIO2", "SGPIO3",
+       "SGPIO4", "SGPIO5", "SGPIO6", "SGPIO7",
+#if WITH_16CHAN_SUPPORT
+       "SGPIO8", "SGPIO9", "SGPIO10", "SGPIO11",
+       "SGPIO12", "SGPIO13", "SGPIO14", "SGPIO15",
+#endif
+};
+
+/*
+ * The seemingly odd samplerates result from the 204MHz base clock and
+ * a 12bit integer divider. Theoretical minimum could be 50kHz but we
+ * don't bother to provide so low a selection item here.
+ *
+ * When users specify different samplerates, device firmware will pick
+ * the minimum rate which satisfies the user's request.
+ */
+static const uint64_t samplerates[] = {
+       SR_KHZ(1000),   /*   1.0MHz */
+       SR_KHZ(2000),   /*   2.0MHz */
+       SR_KHZ(4000),   /*   4.0MHz */
+       SR_KHZ(8500),   /*   8.5MHz */
+       SR_KHZ(10200),  /*  10.2MHz */
+       SR_KHZ(12000),  /*  12.0MHz */
+       SR_KHZ(17000),  /*  17.0MHz */
+       SR_KHZ(20400),  /*  20.4MHz, the maximum for 16 channels */
+       SR_KHZ(25500),  /*  25.5MHz */
+       SR_KHZ(34000),  /*  34.0MHz */
+       SR_KHZ(40800),  /*  40.8MHz, the maximum for 8 channels */
+       SR_KHZ(51000),  /*  51.0MHz */
+       SR_KHZ(68000),  /*  68.0MHz, the maximum for 4 channels */
+       SR_KHZ(102000), /* 102.0MHz, the maximum for 2 channels */
+       SR_KHZ(204000), /* 204.0MHz, the maximum for 1 channel */
+};
+
+static void greatfet_free_devc(struct dev_context *devc)
+{
+
+       if (!devc)
+               return;
+
+       if (devc->sdi)
+               devc->sdi->priv = NULL;
+
+       g_string_free(devc->usb_comm_buffer, TRUE);
+       g_free(devc->firmware_version);
+       g_free(devc->serial_number);
+       sr_free_probe_names(devc->channel_names);
+       feed_queue_logic_free(devc->acquisition.feed_queue);
+       g_free(devc->transfers.transfers);
+       g_free(devc->transfers.transfer_buffer);
+       /*
+        * USB transfers should not have been allocated when we get here
+        * during device probe/scan, or during shutdown after acquisition
+        * has terminated.
+        */
+
+       g_free(devc);
+}
+
+static void greatfet_free_sdi(struct sr_dev_inst *sdi)
+{
+       struct sr_usb_dev_inst *usb;
+       struct dev_context *devc;
+
+       if (!sdi)
+               return;
+
+       usb = sdi->conn;
+       sdi->conn = NULL;
+       if (usb && usb->devhdl)
+               sr_usb_close(usb);
+       sr_usb_dev_inst_free(usb);
+
+       devc = sdi->priv;
+       sdi->priv = NULL;
+       greatfet_free_devc(devc);
+
+       sr_dev_inst_free(sdi);
+}
 
 static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
        struct drv_context *drvc;
+       struct sr_context *ctx;
        GSList *devices;
+       const char *conn, *probe_names;
+       const char *want_snr;
+       struct sr_config *src;
+       GSList *conn_devices, *l;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_usb_dev_inst *usb;
+       gboolean skip_device;
+       struct libusb_device *dev;
+       struct libusb_device_descriptor des;
+       char *match;
+       char serno_txt[64], conn_id[64];
+       int ret;
+       size_t ch_off, ch_max, ch_idx;
+       gboolean enabled;
+       struct sr_channel *ch;
+       struct sr_channel_group *cg;
 
-       (void)options;
+       drvc = di->context;
+       ctx = drvc->sr_ctx;
 
        devices = NULL;
-       drvc = di->context;
-       drvc->instances = NULL;
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+       /* Accept user specs for conn= and probe names. */
+       conn = DEFAULT_CONN;
+       probe_names = NULL;
+       for (l = options; l; l = l->next) {
+               src = l->data;
+               switch (src->key) {
+               case SR_CONF_CONN:
+                       conn = g_variant_get_string(src->data, NULL);
+                       break;
+               case SR_CONF_PROBE_NAMES:
+                       probe_names = g_variant_get_string(src->data, NULL);
+                       break;
+               }
+       }
+
+       /*
+        * By default search for all devices with the expected VID/PID.
+        * Accept external specs in either "bus.addr" or "vid.pid" form.
+        * As an alternative accept "sn=..." specs and keep using the
+        * default VID/PID in that case. This should result in maximum
+        * usability while still using a maximum amount of common code.
+        */
+       want_snr = NULL;
+       if (g_str_has_prefix(conn, "sn=")) {
+               want_snr = conn + strlen("sn=");
+               conn = DEFAULT_CONN;
+               sr_info("Searching default %s and serial number %s.",
+                       conn, want_snr);
+       }
+       conn_devices = sr_usb_find(ctx->libusb_ctx, conn);
+       if (!conn_devices)
+               return devices;
+
+       /*
+        * Iterate over all devices that have the matching VID/PID.
+        * Skip those which we cannot open. Skip those which don't
+        * match additional serial number conditions. Allocate the
+        * structs for found devices "early", to re-use common code
+        * for communication to the firmware. Release these structs
+        * when identification fails or the device does not match.
+        *
+        * Notice that the scan for devices uses the USB string for
+        * the serial number, and does a weak check (partial match).
+        * This allows users to either use lsusb(8) or gf(1) output
+        * as well as match lazily when only part of the serial nr is
+        * known and becomes unique. Matching against serial nr and
+        * finding multiple devices is as acceptable, just might be a
+        * rare use case. Failure in this stage is silent, there are
+        * legal reasons why we cannot access a device during scan.
+        *
+        * Once a device was found usable, we get its serial number
+        * and version details by means of firmware communication.
+        * To verify that the firmware is operational and that the
+        * protocol works to a minimum degree. And to present data
+        * in --scan output which matches the vendor's gf(1) utility.
+        * This version detail is _not_ checked against conn= specs
+        * because users may specify the longer text string with
+        * more leading digits from lsusb(8) output. That test would
+        * fail when executed against the shorter firmware output.
+        */
+       for (l = conn_devices; l; l = l->next) {
+               usb = l->data;
+
+               ret = sr_usb_open(ctx->libusb_ctx, usb);
+               if (ret != SR_OK)
+                       continue;
+
+               skip_device = FALSE;
+               if (want_snr) do {
+                       dev = libusb_get_device(usb->devhdl);
+                       ret = libusb_get_device_descriptor(dev, &des);
+                       if (ret != 0 || !des.iSerialNumber) {
+                               skip_device = TRUE;
+                               break;
+                       }
+                       ret = libusb_get_string_descriptor_ascii(usb->devhdl,
+                               des.iSerialNumber,
+                               (uint8_t *)serno_txt, sizeof(serno_txt));
+                       if (ret < 0) {
+                               skip_device = TRUE;
+                               break;
+                       }
+                       match = strstr(serno_txt, want_snr);
+                       skip_device = !match;
+                       sr_dbg("got serno %s, checking %s, match %d",
+                               serno_txt, want_snr, !!match);
+               } while (0);
+               if (skip_device) {
+                       sr_usb_close(usb);
+                       continue;
+               }
+
+               sdi = g_malloc0(sizeof(*sdi));
+               sdi->conn = usb;
+               sdi->inst_type = SR_INST_USB;
+               sdi->status = SR_ST_INACTIVE;
+               devc = g_malloc0(sizeof(*devc));
+               sdi->priv = devc;
+               devc->sdi = sdi;
+               devc->usb_comm_buffer = NULL;
+
+               /*
+                * Get the serial number by way of device communication.
+                * Get the firmware version. Failure is fatal.
+                */
+               ret = greatfet_get_serial_number(sdi);
+               if (ret != SR_OK || !devc->serial_number) {
+                       sr_err("Cannot get serial number.");
+                       greatfet_free_sdi(sdi);
+                       continue;
+               }
+               ret = greatfet_get_version_number(sdi);
+               if (ret != SR_OK || !devc->firmware_version) {
+                       sr_err("Cannot get firmware version.");
+                       greatfet_free_sdi(sdi);
+                       continue;
+               }
+
+               /* Continue filling in sdi and devc. */
+               snprintf(conn_id, sizeof(conn_id), "%u.%u",
+                       usb->bus, usb->address);
+               sdi->connection_id = g_strdup(conn_id);
+               sr_usb_close(usb);
+
+               sdi->vendor = g_strdup(VENDOR_TEXT);
+               sdi->model = g_strdup(MODEL_TEXT);
+               sdi->version = g_strdup(devc->firmware_version);
+               sdi->serial_num = g_strdup(devc->serial_number);
+
+               /* Create the "Logic" channel group. */
+               ch_off = 0;
+               ch_max = ARRAY_SIZE(channel_names);
+               devc->channel_names = sr_parse_probe_names(probe_names,
+                       channel_names, ch_max, ch_max, &ch_max);
+               devc->channel_count = ch_max;
+               cg = sr_channel_group_new(sdi, "Logic", NULL);
+               for (ch_idx = 0; ch_idx < ch_max; ch_idx++) {
+                       enabled = ch_idx < 8;
+                       ch = sr_channel_new(sdi, ch_off,
+                               SR_CHANNEL_LOGIC, enabled,
+                               devc->channel_names[ch_idx]);
+                       ch_off++;
+                       cg->channels = g_slist_append(cg->channels, ch);
+               }
+
+               sr_sw_limits_init(&devc->sw_limits);
+               devc->samplerate = DEFAULT_SAMPLERATE;
+               devc->acquisition.bandwidth_threshold = BANDWIDTH_THRESHOLD;
+               devc->acquisition.control_interface = CONTROL_INTERFACE;
+               devc->acquisition.samples_interface = SAMPLES_INTERFACE;
+               devc->acquisition.acquisition_state = ACQ_IDLE;
+
+               devices = g_slist_append(devices, sdi);
+       }
+       g_slist_free(conn_devices);
 
-       return devices;
+       return std_scan_complete(di, devices);
 }
 
 static int dev_open(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct sr_dev_driver *di;
+       struct drv_context *drvc;
+       struct sr_context *ctx;
+       struct sr_usb_dev_inst *usb;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       di = sdi->driver;
+       drvc = di->context;
+       ctx = drvc->sr_ctx;
+       usb = sdi->conn;
 
-       return SR_OK;
+       return sr_usb_open(ctx->libusb_ctx, usb);
 }
 
 static int dev_close(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
-
-       /* TODO: get handle from sdi->conn and close it. */
+       struct sr_usb_dev_inst *usb;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       usb = sdi->conn;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       greatfet_release_resources(sdi);
+
+       if (!usb->devhdl)
+               return SR_ERR_BUG;
+
+       sr_info("Closing device on %s interface %d.",
+               sdi->connection_id, acq->control_interface);
+       if (acq->control_interface_claimed) {
+               libusb_release_interface(usb->devhdl, acq->control_interface);
+               acq->control_interface_claimed = FALSE;
+       }
+       sr_usb_close(usb);
 
        return SR_OK;
 }
 
+static void clear_helper(struct dev_context *devc)
+{
+       greatfet_free_devc(devc);
+}
+
+static int dev_clear(const struct sr_dev_driver *driver)
+{
+       return std_dev_clear_with_callback(driver,
+               (std_dev_clear_callback)clear_helper);
+}
+
 static int config_get(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
-
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       struct dev_context *devc;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+
+       /* Handle requests for the "Logic" channel group. */
+       if (cg) {
+               switch (key) {
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       ret = SR_OK;
+       /* Handle global options for the device. */
        switch (key) {
-       /* TODO */
+       case SR_CONF_CONN:
+               if (!sdi->connection_id)
+                       return SR_ERR_NA;
+               *data = g_variant_new_string(sdi->connection_id);
+               return SR_OK;
+       case SR_CONF_CONTINUOUS:
+               *data = g_variant_new_boolean(TRUE);
+               return SR_OK;
+       case SR_CONF_SAMPLERATE:
+               if (!devc)
+                       return SR_ERR_NA;
+               *data = g_variant_new_uint64(devc->samplerate);
+               return SR_OK;
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_MSEC:
+               if (!devc)
+                       return SR_ERR_NA;
+               return sr_sw_limits_config_get(&devc->sw_limits, key, data);
        default:
                return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int config_set(uint32_t key, GVariant *data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
+       struct dev_context *devc;
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       devc = sdi->priv;
 
-       ret = SR_OK;
+       /* Handle requests for the "Logic" channel group. */
+       if (cg) {
+               switch (key) {
+               default:
+                       return SR_ERR_NA;
+               }
+       }
+
+       /* Handle global options for the device. */
        switch (key) {
-       /* TODO */
+       case SR_CONF_SAMPLERATE:
+               if (!devc)
+                       return SR_ERR_NA;
+               devc->samplerate = g_variant_get_uint64(data);
+               return SR_OK;
+       case SR_CONF_LIMIT_SAMPLES:
+       case SR_CONF_LIMIT_MSEC:
+               if (!devc)
+                       return SR_ERR_NA;
+               return sr_sw_limits_config_set(&devc->sw_limits, key, data);
        default:
-               ret = SR_ERR_NA;
+               return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int config_list(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       /* Handle requests for the "Logic" channel group. */
+       if (cg) {
+               switch (key) {
+               case SR_CONF_DEVICE_OPTIONS:
+                       if (ARRAY_SIZE(devopts_cg) == 0)
+                               return SR_ERR_NA;
+                       *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                               ARRAY_AND_SIZE(devopts_cg),
+                               sizeof(devopts_cg[0]));
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       ret = SR_OK;
+       /* Handle global options for the device. */
        switch (key) {
-       /* TODO */
+       case SR_CONF_SCAN_OPTIONS:
+       case SR_CONF_DEVICE_OPTIONS:
+               return STD_CONFIG_LIST(key, data, sdi, cg,
+                       scanopts, drvopts, devopts);
+       case SR_CONF_SAMPLERATE:
+               *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates));
+               return SR_OK;
        default:
                return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int dev_acquisition_start(const struct sr_dev_inst *sdi)
 {
-       /* TODO: configure hardware, reset acquisition state, set up
-        * callbacks and send header packet. */
+       struct sr_dev_driver *di;
+       struct drv_context *drvc;
+       struct sr_context *ctx;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       int ret;
 
-       (void)sdi;
+       if (!sdi || !sdi->driver || !sdi->priv)
+               return SR_ERR_ARG;
+       di = sdi->driver;
+       drvc = di->context;
+       ctx = drvc->sr_ctx;
+       devc = sdi->priv;
+       acq = &devc->acquisition;
+
+       acq->acquisition_state = ACQ_PREPARE;
+
+       ret = greatfet_setup_acquisition(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       if (!acq->feed_queue) {
+               acq->feed_queue = feed_queue_logic_alloc(sdi,
+                       BUFFER_SIZE, acq->unit_size);
+               if (!acq->feed_queue) {
+                       sr_err("Cannot allocate session feed buffer.");
+                       return SR_ERR_MALLOC;
+               }
+       }
+
+       sr_sw_limits_acquisition_start(&devc->sw_limits);
+
+       ret = greatfet_start_acquisition(sdi);
+       acq->start_req_sent = ret == SR_OK;
+       if (ret != SR_OK) {
+               greatfet_abort_acquisition(sdi);
+               feed_queue_logic_free(acq->feed_queue);
+               acq->feed_queue = NULL;
+               return ret;
+       }
+       acq->acquisition_state = ACQ_RECEIVE;
+
+       usb_source_add(sdi->session, ctx, 50,
+               greatfet_receive_data, (void *)sdi);
+
+       ret = std_session_send_df_header(sdi);
+       acq->frame_begin_sent = ret == SR_OK;
+       (void)sr_session_send_meta(sdi, SR_CONF_SAMPLERATE,
+               g_variant_new_uint64(acq->capture_samplerate));
 
        return SR_OK;
 }
 
 static int dev_acquisition_stop(struct sr_dev_inst *sdi)
 {
-       /* TODO: stop acquisition. */
-
-       (void)sdi;
-
+       greatfet_abort_acquisition(sdi);
        return SR_OK;
 }
 
 static struct sr_dev_driver greatfet_driver_info = {
        .name = "greatfet",
-       .longname = "GreatFET",
+       .longname = "Great Scott Gadgets GreatFET One",
        .api_version = 1,
        .init = std_init,
        .cleanup = std_cleanup,
        .scan = scan,
        .dev_list = std_dev_list,
-       .dev_clear = std_dev_clear,
+       .dev_clear = dev_clear,
        .config_get = config_get,
        .config_set = config_set,
        .config_list = config_list,
index e7751bc66f9d54aedf43fb1ee743b61d5d85e646..913f981c1f01e33cfba06159f5d228c68ff0a408 100644 (file)
@@ -1,6 +1,8 @@
 /*
  * This file is part of the libsigrok project.
  *
+ * Copyright (C) 2019 Katherine J. Temkin <k@ktemkin.com>
+ * Copyright (C) 2019 Mikaela Szekely <qyriad@gmail.com>
  * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
  *
  * This program is free software: you can redistribute it and/or modify
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+
 #include "protocol.h"
 
+/*
+ * Communicate to GreatFET firmware, especially its Logic Analyzer mode.
+ *
+ * Firmware communication is done by two means: Control transfers to
+ * EP0 for command execution. Bulk transfer from EP1 for sample data.
+ * The sample data endpoint number is also provided by firmware in
+ * responses to LA configuration requests.
+ *
+ * Control transfers have a fixed layout: 2x u32 class and verb numbers,
+ * and u8[] payload data up to 512 bytes length. Payload layout depends
+ * on commands and the verb's parameters. Binary data is represented in
+ * LE format (firmware executes on Cortex-M). Strings are limited to a
+ * maximum of 128 bytes.
+ *
+ * The set of commands used by this sigrok driver is minimal:
+ * - Get the GreatFET's firmware version and serial number.
+ *   - String queries, a core verb, individual verb codes for the
+ *     version and for the serial number.
+ * - Configure Logic Analyzer mode, start and stop captures.
+ *   - Configure takes a u32 samplerate and u8 channel count. Yields
+ *     u32 samplerate, u32 buffer size, u8 endpoint number.
+ *   - Start takes a u32 samplerate (does it? depending on firmware
+ *     version?). Empty/no response.
+ *   - Stop has empty/no request and response payloads.
+ *
+ * Firmware implementation details, observed during sigrok driver
+ * creation.
+ * - Serial number "strings" in responses may carry binary data and
+ *   not a text presentation of the serial number. It's uncertain
+ *   whether that is by design or an oversight. This sigrok driver
+ *   copes when it happens. (Remainder from another request which
+ *   provided the part number as well?)
+ * - The GreatFET firmware is designed for exploration by host apps.
+ *   The embedded classes, their methods, their in/out parameters,
+ *   including builtin help texts, can get enumerated. This driver
+ *   does not use this discovery approach, assumes a given protocol.
+ * - The NXP LPC4330 chip has 16 SGPIO pins. It's assumed that the
+ *   GreatFET firmware currently does not support more than 8 logic
+ *   channels due to constraints on bitbang machinery synchronization
+ *   which is under construction (IIUC, it's about pin banks that
+ *   run independently). When firmware versions get identified which
+ *   transparently (from the host's perspective) support more than
+ *   8 channels, this host driver may need a little adjustment.
+ * - The device can sample and stream 8 channels to the host at a
+ *   continuous rate of 40.8MHz. Higher rates are possible assuming
+ *   that fewer pins get sampled. The firmware then provides sample
+ *   memory where data taken at several sample points reside in the
+ *   same byte of sample memory. It helps that power-of-two bitness
+ *   is applied, IOW that there are either 1, 2, 4, or 8 bits per
+ *   sample point. Even when say 3 or 5 channels are enabled. The
+ *   device firmware may assume that a "dense" list of channels gets
+ *   enabled, the sigrok driver supports when some disabled channels
+ *   preceed other enabled channels. The device is then asked to get
+ *   as many channels as are needed to cover all enabled channels,
+ *   including potentially disabled channels before them.
+ * - The LA configure request returns a samplerate that is supported
+ *   by the hardware/firmware combination and will be used during
+ *   acquisition. This returned rate is at least as high as the
+ *   requested samplerate. But might exceed the USB bandwidth which
+ *   the firmware is capable to sustain. Users may not expect that
+ *   since numbers add up differently from their perspective. In the
+ *   example of 3 enabled channels and a requested 72MHz samplerate,
+ *   the firmware will derive that it needs to sample 4 channels at
+ *   a 102MHz rate. Which exceeds its capabilities while users may
+ *   not be aware of these constraints. This sigrok driver attempts
+ *   to detect the condition, and not start an acquisition. And also
+ *   emits diagnostics (at info level which is silent by default).
+ *   It's assumed that users increase verbosity when diagnosing
+ *   issues they may experience.
+ */
+
+/*
+ * Assign a symbolic name to endpoint 0 which is used for USB control
+ * transfers. Although those "or 0" phrases don't take effect from the
+ * compiler's perspective, they hopefully increase readability of the
+ * USB related incantations.
+ *
+ * Endpoint 1 for sample data reception is not declared here. Its value
+ * is taken from logic analyzer configure response. Which remains more
+ * portable across firmware versions and supported device models.
+ */
+#define CONTROL_ENDPOINT       0
+
+/* Header fields for USB control requests. */
+#define LIBGREAT_REQUEST_NUMBER        0x65
+#define LIBGREAT_VALUE_EXECUTE 0
+#define LIBGREAT_FLAG_SKIP_RSP (1UL << 0)
+
+/* Classes and their verbs for core and logic analyzer. */
+#define GREATFET_CLASS_CORE    0x000
+#define CORE_VERB_READ_VERSION 0x1
+#define CORE_VERB_READ_SERIAL  0x3
+
+#define GREATFET_CLASS_LA      0x10d
+#define LA_VERB_CONFIGURE      0x0
+#define LA_VERB_START_CAPTURE  0x3
+#define LA_VERB_STOP_CAPTURE   0x4
+
+/* Maximum text string and binary payload sizes for control requests. */
+#define CORE_MAX_STRING_LENGTH 128
+#define LOGIC_MAX_PAYLOAD_DATA 512
+
+/* USB communication parameters, pool dimensions. */
+#define LOGIC_DEFAULT_TIMEOUT  1000
+#define TRANSFER_POOL_SIZE     16
+#define TRANSFER_BUFFER_SIZE   (256 * 1024)
+
+static int greatfet_process_receive_data(const struct sr_dev_inst *sdi,
+       const uint8_t *data, size_t dlen);
+static int greatfet_cancel_transfers(const struct sr_dev_inst *sdi);
+
+/* Communicate a GreatFET request to EP0, and get its response. */
+static int greatfet_ctrl_out_in(const struct sr_dev_inst *sdi,
+       const uint8_t *tx_data, size_t tx_size,
+       uint8_t *rx_data, size_t rx_size, unsigned int timeout_ms)
+{
+       struct sr_usb_dev_inst *usb;
+       uint16_t flags;
+       int ret;
+       size_t sent, rcvd;
+
+       usb = sdi->conn;
+       if (!usb)
+               return SR_ERR_ARG;
+
+       /* Caller can request to skip transmission of a response. */
+       flags = 0;
+       if (!rx_size)
+               flags |= LIBGREAT_FLAG_SKIP_RSP;
+
+       /* Send USB Control OUT request. */
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *dump = sr_hexdump_new(tx_data, tx_size);
+               sr_spew("USB out data: %s", dump->str);
+               sr_hexdump_free(dump);
+       }
+       ret = libusb_control_transfer(usb->devhdl,
+               LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_ENDPOINT |
+               LIBUSB_ENDPOINT_OUT | CONTROL_ENDPOINT,
+               LIBGREAT_REQUEST_NUMBER, LIBGREAT_VALUE_EXECUTE,
+               flags, (void *)tx_data, tx_size, timeout_ms);
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               const char *msg;
+               msg = ret ? libusb_error_name(ret) : "-";
+               sr_spew("USB out, rc %d, %s", ret, msg);
+       }
+       if (ret < 0) {
+               /* Rate limit error messages. Skip "please retry" kinds. */
+               if (ret != LIBUSB_ERROR_BUSY) {
+                       sr_err("USB out transfer failed: %s (%d)",
+                               libusb_error_name(ret), ret);
+               }
+               return SR_ERR_IO;
+       }
+       sent = (size_t)ret;
+       if (sent != tx_size) {
+               sr_err("Short USB write: want %zu, got %zu: %s.",
+                       tx_size, sent, libusb_error_name(ret));
+               return SR_ERR_IO;
+       }
+
+       /* Get the USB Control IN response. */
+       if (!rx_size)
+               return SR_OK;
+       ret = libusb_control_transfer(usb->devhdl,
+               LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_ENDPOINT |
+               LIBUSB_ENDPOINT_IN | CONTROL_ENDPOINT,
+               LIBGREAT_REQUEST_NUMBER, LIBGREAT_VALUE_EXECUTE,
+               0, rx_data, rx_size, timeout_ms);
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               const char *msg;
+               msg = ret ? libusb_error_name(ret) : "-";
+               sr_spew("USB in, rc %d, %s", ret, msg);
+       }
+       if (ret < 0) {
+               sr_err("USB in transfer failed: %s (%d)",
+                       libusb_error_name(ret), ret);
+               return SR_ERR_IO;
+       }
+       rcvd = (size_t)ret;
+       if (sr_log_loglevel_get() >= SR_LOG_SPEW) {
+               GString *dump = sr_hexdump_new(rx_data, rcvd);
+               sr_spew("USB in data: %s", dump->str);
+               sr_hexdump_free(dump);
+       }
+       /* Short read, including zero length, is not fatal. */
+
+       return rcvd;
+}
+
+/*
+ * Use a string buffer in devc for USB transfers. This simplifies
+ * resource management in error paths.
+ */
+static int greatfet_prep_usb_buffer(const struct sr_dev_inst *sdi,
+       uint8_t **tx_buff, size_t *tx_size, uint8_t **rx_buff, size_t *rx_size)
+{
+       struct dev_context *devc;
+       size_t want_len;
+       GString *s;
+
+       if (tx_buff)
+               *tx_buff = NULL;
+       if (tx_size)
+               *tx_size = 0;
+       if (rx_buff)
+               *rx_buff = NULL;
+       if (rx_size)
+               *rx_size = 0;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /*
+        * Allocate the string buffer unless previously done.
+        * Ensure sufficient allocated space for request/response use.
+        * Assume that glib GString is suitable to hold uint8_t[] data.
+        */
+       if (!devc->usb_comm_buffer) {
+               want_len = 2 * sizeof(uint32_t) + LOGIC_MAX_PAYLOAD_DATA;
+               devc->usb_comm_buffer = g_string_sized_new(want_len);
+               if (!devc->usb_comm_buffer)
+                       return SR_ERR_MALLOC;
+       }
+
+       /* Pass buffer start and size to the caller if requested. */
+       s = devc->usb_comm_buffer;
+       if (tx_buff)
+               *tx_buff = (uint8_t *)s->str;
+       if (tx_size)
+               *tx_size = s->allocated_len;
+       if (rx_buff)
+               *rx_buff = (uint8_t *)s->str;
+       if (rx_size)
+               *rx_size = s->allocated_len;
+
+       return SR_OK;
+}
+
+/* Retrieve a string by executing a core service. */
+static int greatfet_get_string(const struct sr_dev_inst *sdi,
+       uint32_t verb, char **value)
+{
+       uint8_t *req, *rsp;
+       size_t rsp_size;
+       uint8_t *wrptr;
+       size_t wrlen, rcvd;
+       const char *text;
+       int ret;
+
+       if (value)
+               *value = NULL;
+       if (!sdi)
+               return SR_ERR_ARG;
+       ret = greatfet_prep_usb_buffer(sdi, &req, NULL, &rsp, &rsp_size);
+       if (ret != SR_OK)
+               return ret;
+
+       wrptr = req;
+       write_u32le_inc(&wrptr, GREATFET_CLASS_CORE);
+       write_u32le_inc(&wrptr, verb);
+       wrlen = wrptr - req;
+       ret = greatfet_ctrl_out_in(sdi, req, wrlen,
+               rsp, rsp_size, LOGIC_DEFAULT_TIMEOUT);
+       if (ret < 0) {
+               sr_err("Cannot get core string.");
+               return ret;
+       }
+       rcvd = (size_t)ret;
+
+       rsp[rcvd] = '\0';
+       text = (const char *)rsp;
+       sr_dbg("got string, verb %u, text (%zu) %s", verb, rcvd, text);
+       if (value && *text) {
+               *value = g_strndup(text, rcvd);
+       } else if (value) {
+               /*
+                * g_strndup(3) does _not_ copy 'n' bytes. Instead it
+                * truncates the result at the first NUL character seen.
+                * That's why we need extra logic to pass binary data
+                * to callers, to not violate API layers and confuse
+                * USB readers with firmware implementation details
+                * (that may be version dependent).
+                * The very condition to determine whether text or some
+                * binary data was received is a simple check for NUL
+                * in the first position, implemented above. This is
+                * GoodEnough(TM) to handle the firmware version case.
+                */
+               *value = g_malloc0(rcvd + 1);
+               memcpy(*value, text, rcvd);
+       }
+
+       return rcvd;
+}
+
+SR_PRIV int greatfet_get_serial_number(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       char *text;
+       int ret;
+       const uint8_t *rdptr;
+       size_t rdlen;
+       GString *snr;
+       uint32_t chunk;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       ret = greatfet_get_string(sdi, CORE_VERB_READ_SERIAL, &text);
+       if (ret < 0)
+               return ret;
+       if (!text)
+               return SR_ERR_DATA;
+
+       /*
+        * The simple case, we got a text string. The 2019 K.Temkin
+        * implementation took the received string as is. So there
+        * are firmware versions which provide this presentation.
+        */
+       if (*text) {
+               devc->serial_number = text;
+               return SR_OK;
+       }
+
+       /*
+        * The complex case. The received "string" looks binary. Local
+        * setups with v2018.12.1 and v2021.2.1 firmware versions yield
+        * response data that does not look like a text string. Instead
+        * it looks like four u32 fields which carry a binary value and
+        * leading padding. Try that interpreation as well. Construct a
+        * twenty character text presentation from that binary content.
+        *
+        * Implementation detail: Is the "leader" the part number which
+        * a different firmware request may yield? Are there other verbs
+        * which reliably yield the serial number in text format?
+        */
+       rdptr = (const uint8_t *)text;
+       rdlen = (size_t)ret;
+       sr_dbg("trying to read serial nr \"text\" as binary");
+       if (rdlen != 4 * sizeof(uint32_t)) {
+               g_free(text);
+               return SR_ERR_DATA;
+       }
+       snr = g_string_sized_new(20 + 1);
+       chunk = read_u32le_inc(&rdptr);
+       if (chunk) {
+               g_free(text);
+               return SR_ERR_DATA;
+       }
+       chunk = read_u32le_inc(&rdptr);
+       if (chunk) {
+               g_free(text);
+               return SR_ERR_DATA;
+       }
+       g_string_append_printf(snr, "%04" PRIx32, chunk);
+       chunk = read_u32le_inc(&rdptr);
+       g_string_append_printf(snr, "%08" PRIx32, chunk);
+       chunk = read_u32le_inc(&rdptr);
+       g_string_append_printf(snr, "%08" PRIx32, chunk);
+       sr_dbg("got serial number text %s", snr->str);
+       g_free(text);
+       text = g_string_free(snr, FALSE);
+       devc->serial_number = text;
+       return SR_OK;
+}
+
+SR_PRIV int greatfet_get_version_number(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       char *text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       ret = greatfet_get_string(sdi, CORE_VERB_READ_VERSION, &text);
+       if (ret < SR_OK)
+               return ret;
+
+       devc->firmware_version = text;
+       return SR_OK;
+}
+
+/*
+ * Transmit a parameter-less request that wants no response. Or a
+ * request with just a few bytes worth of parameter values, still
+ * not expecting a response.
+ */
+static int greatfet_trivial_request(const struct sr_dev_inst *sdi,
+       uint32_t cls, uint32_t verb, const uint8_t *tx_data, size_t tx_dlen)
+{
+       struct dev_context *devc;
+       uint8_t *req;
+       uint8_t *wrptr;
+       size_t wrlen;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       ret = greatfet_prep_usb_buffer(sdi, &req, NULL, NULL, NULL);
+       if (ret != SR_OK)
+               return ret;
+
+       wrptr = req;
+       write_u32le_inc(&wrptr, cls);
+       write_u32le_inc(&wrptr, verb);
+       while (tx_dlen--)
+               write_u8_inc(&wrptr, *tx_data++);
+       wrlen = wrptr - req;
+       return greatfet_ctrl_out_in(sdi, req, wrlen,
+               NULL, 0, LOGIC_DEFAULT_TIMEOUT);
+}
+
+/*
+ * Transmit a "configure logic analyzer" request. Gets the resulting
+ * samplerate (which can differ from requested values) and endpoint
+ * (which is very useful for compatibility across devices/versions).
+ * Also gets the device firmware's buffer size, which is only used
+ * for information, while the host assumes a fixed larger buffer size
+ * for its own purposes.
+ */
+static int greatfet_logic_config(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct sr_usb_dev_inst *usb;
+       uint8_t *req, *rsp;
+       size_t rsp_size;
+       uint8_t *wrptr;
+       size_t wrlen, rcvd, want_len;
+       const uint8_t *rdptr;
+       uint64_t rate, bw;
+       size_t bufsize;
+       uint8_t ep;
+       char *print_bw;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       usb = sdi->conn;
+       if (!devc || !usb)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       ret = greatfet_prep_usb_buffer(sdi, &req, NULL, &rsp, &rsp_size);
+       if (ret != SR_OK)
+               return ret;
+
+       /*
+        * Prepare to get a specific amount of receive data. The logic
+        * analyzer configure response is strictly binary, in contrast
+        * to variable length string responses elsewhere.
+        */
+       want_len = 2 * sizeof(uint32_t) + sizeof(uint8_t);
+       if (rsp_size < want_len)
+               return SR_ERR_BUG;
+       rsp_size = want_len;
+
+       sr_dbg("about to config LA, rate %" PRIu64 ", chans %zu",
+               devc->samplerate, acq->capture_channels);
+       wrptr = req;
+       write_u32le_inc(&wrptr, GREATFET_CLASS_LA);
+       write_u32le_inc(&wrptr, LA_VERB_CONFIGURE);
+       write_u32le_inc(&wrptr, devc->samplerate);
+       write_u8_inc(&wrptr, acq->capture_channels);
+       wrlen = wrptr - req;
+       ret = greatfet_ctrl_out_in(sdi, req, wrlen,
+               rsp, rsp_size, LOGIC_DEFAULT_TIMEOUT);
+       if (ret < 0) {
+               sr_err("Cannot configure logic analyzer mode.");
+               return ret;
+       }
+       rcvd = (size_t)ret;
+       if (rcvd != want_len) {
+               sr_warn("Unexpected LA configuration response length.");
+               return SR_ERR_DATA;
+       }
+
+       rdptr = rsp;
+       rate = read_u32le_inc(&rdptr);
+       bufsize = read_u32le_inc(&rdptr);
+       ep = read_u8_inc(&rdptr);
+       sr_dbg("LA configured, rate %" PRIu64 ", buf %zu, ep %" PRIu8,
+               rate, bufsize, ep);
+       if (rate != devc->samplerate) {
+               sr_info("Configuration feedback, want rate %" PRIu64 ", got rate %." PRIu64,
+                       devc->samplerate, rate);
+               devc->samplerate = rate;
+       }
+       acq->capture_samplerate = rate;
+       acq->firmware_bufsize = bufsize;
+       acq->samples_endpoint = ep;
+
+       /*
+        * The firmware does not reject requests that would exceed
+        * its capabilities. Yet the device becomes unaccessible when
+        * START is sent in that situation. (Observed with v2021.2.1
+        * firmware.)
+        *
+        * Assume a maximum USB bandwidth that we don't want to exceed.
+        * It's protecting the GreatFET's firmware. It's not a statement
+        * on the host's capability of keeping up with the GreatFET's
+        * firmware capabilities. :)
+        */
+       print_bw = sr_samplerate_string(acq->capture_samplerate);
+       sr_info("Capture configuration: %zu channels, samplerate %s.",
+               acq->capture_channels, print_bw);
+       g_free(print_bw);
+       bw = acq->capture_samplerate * 8 / acq->points_per_byte;
+       print_bw = sr_si_string_u64(bw, "bps");
+       sr_info("Resulting USB bandwidth: %s.", print_bw);
+       g_free(print_bw);
+       if (acq->bandwidth_threshold && bw > acq->bandwidth_threshold) {
+               sr_err("Configuration exceeds bandwidth limit. Aborting.");
+               return SR_ERR_SAMPLERATE;
+       }
+
+       return SR_OK;
+}
+
+/* Transmit "start logic capture" request. */
+static int greatfet_logic_start(const struct sr_dev_inst *sdi)
+{
+       int ret;
+
+       ret = greatfet_trivial_request(sdi,
+               GREATFET_CLASS_LA, LA_VERB_START_CAPTURE, NULL, 0);
+       sr_dbg("LA start, USB out, rc %d", ret);
+       if (ret != SR_OK)
+               sr_err("Cannot start logic analyzer capture.");
+
+       return ret;
+}
+
+/* Transmit "stop logic capture" request. */
+static int greatfet_logic_stop(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       int ret;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       /* Only send STOP when START was sent before. */
+       if (!acq->start_req_sent)
+               return SR_OK;
+
+       ret = greatfet_trivial_request(sdi,
+               GREATFET_CLASS_LA, LA_VERB_STOP_CAPTURE, NULL, 0);
+       sr_dbg("LA stop, USB out, rc %d", ret);
+       if (ret == SR_OK)
+               acq->start_req_sent = FALSE;
+       else
+               sr_warn("Cannot stop logic analyzer capture in the device.");
+
+       return ret;
+}
+
+/*
+ * Determine how many channels the device firmware needs to sample.
+ * So that resulting capture data will cover all those logic channels
+ * which currently are enabled on the sigrok side. We (have to) accept
+ * when the sequence of enabled channels "has gaps" in them. Disabling
+ * channels in the middle of the pin groups is a user's choice that we
+ * need to obey. The count of enabled channels is not good enough for
+ * the purpose of acquisition, it must be "a maximum index" or a total
+ * to-get-sampled count.
+ */
+static int greatfet_calc_capture_chans(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       GSList *l;
+       struct sr_channel *ch;
+       int last_used_idx;
+       size_t logic_ch_count, en_ch_count, fw_ch_count;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       last_used_idx = -1;
+       logic_ch_count = 0;
+       for (l = sdi->channels; l; l = l->next) {
+               ch = l->data;
+               if (ch->type != SR_CHANNEL_LOGIC)
+                       continue;
+               logic_ch_count++;
+               if (!ch->enabled)
+                       continue;
+               if (last_used_idx < ch->index)
+                       last_used_idx = ch->index;
+       }
+       en_ch_count = last_used_idx + 1;
+       sr_dbg("channel count, logic %zu, highest enabled idx %d -> count %zu",
+               logic_ch_count, last_used_idx, en_ch_count);
+       if (!en_ch_count)
+               return SR_ERR_ARG;
+
+       acq->capture_channels = en_ch_count;
+       ret = sr_next_power_of_two(last_used_idx, NULL, &fw_ch_count);
+       if (ret != SR_OK)
+               return ret;
+       if (!fw_ch_count)
+               return SR_ERR_ARG;
+       if (fw_ch_count > 8) {
+               acq->unit_size = sizeof(uint16_t);
+               acq->points_per_byte = 1;
+       } else {
+               acq->unit_size = sizeof(uint8_t);
+               acq->points_per_byte = 8 / fw_ch_count;
+       }
+       acq->channel_shift = fw_ch_count % 8;
+       sr_dbg("unit %zu, dense %d -> shift %zu, points %zu",
+               acq->unit_size, !!acq->channel_shift,
+               acq->channel_shift, acq->points_per_byte);
+
+       return SR_OK;
+}
+
+/*
+ * This is an opportunity to adapt the host's USB transfer size to
+ * the value which the device firmware has provided in the LA config
+ * response.
+ *
+ * We let the opportunity pass. Always use a fixed value for the host
+ * configuration. BULK transfers will adopt, which reduces the number
+ * of transfer completion events for the host.
+ *
+ * Notice that transfer size adjustment is _not_ a means to get user
+ * feedback earlier at low samplerates. This may be done in other
+ * drivers but does not take effect here. Because a buffer is used to
+ * submit sample values to the session. When in doubt, the feed queue
+ * needs flushing.
+ *
+ * TODO Consider whether sample data needs flushing when sample rates
+ * are low and buffers are deep. Ideally use common feed queue support
+ * if that becomes available in the future. Translate low samplerates
+ * (and channel counts) to the amount of samples after which the queue
+ * should get flushed.
+ *
+ * This implementation assumes that samplerates start at 1MHz, and
+ * flushing is not necessary.
+ */
+static int greatfet_calc_submit_size(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       dxfer = &devc->transfers;
+
+       dxfer->capture_bufsize = dxfer->transfer_bufsize;
+       return SR_OK;
+}
+
+/*
+ * This routine is local to protocol.c and does mere data manipulation
+ * and a single attempt at sending "logic analyzer stop" to the device.
+ * This routine gets invoked from USB transfer completion callbacks as
+ * well as periodic timer or data availability callbacks. It is essential
+ * to not spend extended periods of time here.
+ */
+static void greatfet_abort_acquisition_quick(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+
+       if (!sdi)
+               return;
+       devc = sdi->priv;
+       if (!devc)
+               return;
+       acq = &devc->acquisition;
+
+       if (acq->acquisition_state == ACQ_RECEIVE)
+               acq->acquisition_state = ACQ_SHUTDOWN;
+
+       (void)greatfet_logic_stop(sdi);
+       greatfet_cancel_transfers(sdi);
+
+       feed_queue_logic_flush(acq->feed_queue);
+}
+
+/* Allocate USB transfers and associated receive buffers. */
+static int greatfet_allocate_transfers(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+       size_t alloc_size, idx;
+       struct libusb_transfer *xfer;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       dxfer = &devc->transfers;
+
+       dxfer->transfer_bufsize = TRANSFER_BUFFER_SIZE;
+       dxfer->transfers_count = TRANSFER_POOL_SIZE;
+
+       alloc_size = dxfer->transfers_count * dxfer->transfer_bufsize;
+       dxfer->transfer_buffer = g_malloc0(alloc_size);
+       if (!dxfer->transfer_buffer)
+               return SR_ERR_MALLOC;
+
+       alloc_size = dxfer->transfers_count;
+       alloc_size *= sizeof(dxfer->transfers[0]);
+       dxfer->transfers = g_malloc0(alloc_size);
+       if (!dxfer->transfers)
+               return SR_ERR_MALLOC;
+
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               xfer = libusb_alloc_transfer(0);
+               if (!xfer)
+                       return SR_ERR_MALLOC;
+               dxfer->transfers[idx] = xfer;
+       }
+
+       return SR_OK;
+}
+
+/* Submit USB transfers for reception, registers the data callback. */
+static int greatfet_prepare_transfers(const struct sr_dev_inst *sdi,
+       libusb_transfer_cb_fn callback)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct dev_transfers_t *dxfer;
+       struct sr_usb_dev_inst *conn;
+       uint8_t ep;
+       size_t submit_length;
+       size_t off, idx;
+       struct libusb_transfer *xfer;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       conn = sdi->conn;
+       if (!devc || !conn)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+       dxfer = &devc->transfers;
+
+       ep = acq->samples_endpoint;
+       ret = greatfet_calc_submit_size(sdi);
+       if (ret != SR_OK)
+               return ret;
+       submit_length = dxfer->capture_bufsize;
+       if (submit_length > dxfer->transfer_bufsize)
+               submit_length = dxfer->transfer_bufsize;
+       sr_dbg("prep xfer, ep %u (%u), len %zu",
+               ep, ep & ~LIBUSB_ENDPOINT_IN, submit_length);
+
+       dxfer->active_transfers = 0;
+       off = 0;
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               xfer = dxfer->transfers[idx];
+               libusb_fill_bulk_transfer(xfer, conn->devhdl, ep,
+                       &dxfer->transfer_buffer[off], submit_length,
+                       callback, (void *)sdi, 0);
+               if (!xfer->buffer)
+                       return SR_ERR_MALLOC;
+               ret = libusb_submit_transfer(xfer);
+               if (ret != 0) {
+                       sr_spew("submit bulk xfer failed, idx %zu, %d: %s",
+                               idx, ret, libusb_error_name(ret));
+                       return SR_ERR_IO;
+               }
+               dxfer->active_transfers++;
+               off += submit_length;
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Initiate the termination of an acquisition. Cancel all USB transfers.
+ * Their completion will drive further progress including resource
+ * release.
+ */
+static int greatfet_cancel_transfers(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+       size_t idx;
+       struct libusb_transfer *xfer;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       dxfer = &devc->transfers;
+
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               xfer = dxfer->transfers[idx];
+               if (!xfer)
+                       continue;
+               (void)libusb_cancel_transfer(xfer);
+               /*
+                * Cancelled transfers will cause acquisitions to abort
+                * in their callback. Keep the "active" count as is.
+                */
+       }
+
+       return SR_OK;
+}
+
+/*
+ * Free an individual transfer during its callback's execution.
+ * Releasing the last USB transfer also happens to drive more of
+ * the shutdown path.
+ */
+static void greatfet_free_transfer(const struct sr_dev_inst *sdi,
+       struct libusb_transfer *xfer)
+{
+       struct drv_context *drvc;
+       struct sr_usb_dev_inst *usb;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct dev_transfers_t *dxfer;
+       size_t idx;
+
+       if (!sdi || !sdi->driver)
+               return;
+       drvc = sdi->driver->context;
+       usb = sdi->conn;
+       devc = sdi->priv;
+       if (!drvc || !usb || !devc)
+               return;
+       acq = &devc->acquisition;
+       dxfer = &devc->transfers;
+
+       /* Void the transfer in the driver's list of transfers. */
+       for (idx = 0; idx < dxfer->transfers_count; idx++) {
+               if (xfer != dxfer->transfers[idx])
+                       continue;
+               dxfer->transfers[idx] = NULL;
+               dxfer->active_transfers--;
+               break;
+       }
+
+       /* Release the transfer from libusb use. */
+       libusb_free_transfer(xfer);
+
+       /* Done here when more transfers are still pending. */
+       if (!dxfer->active_transfers)
+               return;
+
+       /*
+        * The last USB transfer has been freed after completion.
+        * Post process the previous acquisition's execution.
+        */
+       (void)greatfet_stop_acquisition(sdi);
+       if (acq->frame_begin_sent) {
+               std_session_send_df_end(sdi);
+               acq->frame_begin_sent = FALSE;
+       }
+       usb_source_remove(sdi->session, drvc->sr_ctx);
+       if (acq->samples_interface_claimed) {
+               libusb_release_interface(usb->devhdl, acq->samples_interface);
+               acq->samples_interface_claimed = FALSE;
+       }
+       feed_queue_logic_free(acq->feed_queue);
+       acq->feed_queue = NULL;
+       acq->acquisition_state = ACQ_IDLE;
+}
+
+/*
+ * Callback for the completion of previously submitted USB transfers.
+ * Processes received sample memory content. Initiates termination of
+ * the current acquisition in case of failed processing or failed
+ * communication to the acquisition device. Also initiates termination
+ * when previously configured acquisition limits were reached.
+ */
+static void LIBUSB_CALL xfer_complete_cb(struct libusb_transfer *xfer)
+{
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       const uint8_t *data;
+       size_t dlen;
+       gboolean was_completed, was_cancelled;
+       gboolean has_timedout, device_gone, is_stalled;
+       int level;
+       gboolean shall_abort;
+       int ret;
+
+       sdi = xfer ? xfer->user_data : NULL;
+       devc = sdi ? sdi->priv : NULL;
+       if (!sdi || !devc) {
+               /* ShouldNotHappen(TM) */
+               sr_warn("Completion of unregistered USB transfer.");
+               libusb_free_transfer(xfer);
+               return;
+       }
+       acq = &devc->acquisition;
+
+       /*
+        * Outside of an acquisition? Or in its shutdown path?
+        * Just release the USB transfer, don't process its data.
+        */
+       if (acq->acquisition_state != ACQ_RECEIVE) {
+               greatfet_free_transfer(sdi, xfer);
+               return;
+       }
+
+       /*
+        * Avoid the unfortunate libusb identifiers and data types.
+        * Simplify USB transfer status checks for later code paths.
+        * Optionally log the USB transfers' completion.
+        */
+       data = xfer->buffer;
+       dlen = xfer->actual_length;
+       was_completed = xfer->status == LIBUSB_TRANSFER_COMPLETED;
+       has_timedout = xfer->status == LIBUSB_TRANSFER_TIMED_OUT;
+       was_cancelled = xfer->status == LIBUSB_TRANSFER_CANCELLED;
+       device_gone = xfer->status == LIBUSB_TRANSFER_NO_DEVICE;
+       is_stalled = xfer->status == LIBUSB_TRANSFER_STALL;
+       level = sr_log_loglevel_get();
+       if (level >= SR_LOG_SPEW) {
+               sr_spew("USB transfer, status %s, byte count %zu.",
+                       libusb_error_name(xfer->status), dlen);
+       } else if (level >= SR_LOG_DBG && !was_completed) {
+               sr_dbg("USB transfer, status %s, byte count %zu.",
+                       libusb_error_name(xfer->status), dlen);
+       }
+
+       /*
+        * Timed out transfers may contain a little data. Warn but accept.
+        * Typical case will be completed transfers. Cancelled transfers
+        * are seen in shutdown paths, their data need not get processed.
+        * Terminate acquisition in case of communication or processing
+        * failure, or when limits were reached.
+        */
+       shall_abort = FALSE;
+       if (has_timedout)
+               sr_warn("USB transfer timed out. Using available data.");
+       if (was_completed || has_timedout) {
+               ret = greatfet_process_receive_data(sdi, data, dlen);
+               if (ret != SR_OK) {
+                       sr_err("Error processing sample data. Aborting.");
+                       shall_abort = TRUE;
+               }
+               if (acq->acquisition_state != ACQ_RECEIVE) {
+                       sr_dbg("Sample data processing ends acquisition.");
+                       feed_queue_logic_flush(acq->feed_queue);
+                       shall_abort = TRUE;
+               }
+       } else if (device_gone) {
+               sr_err("Device gone during USB transfer. Aborting.");
+               shall_abort = TRUE;
+       } else if (was_cancelled) {
+               sr_dbg("Cancelled USB transfer. Terminating acquisition.");
+               shall_abort = TRUE;
+       } else if (is_stalled) {
+               sr_err("Device firmware is stalled on USB transfer. Aborting.");
+               shall_abort = TRUE;
+       } else {
+               sr_err("USB transfer failed (%s). Aborting.",
+                       libusb_error_name(xfer->status));
+               shall_abort = TRUE;
+       }
+
+       /*
+        * Resubmit the USB transfer for continued reception of sample
+        * data. Or release the transfer when acquisition terminates
+        * after errors were seen, or limits were reached, or the end
+        * was requested in other regular ways.
+        *
+        * In the case of error or other terminating conditions cancel
+        * the currently executing acquisition, end all USB transfers.
+        */
+       if (!shall_abort) {
+               ret = libusb_submit_transfer(xfer);
+               if (ret < 0) {
+                       sr_err("Cannot resubmit USB transfer. Aborting.");
+                       shall_abort = TRUE;
+               }
+       }
+       if (shall_abort) {
+               greatfet_free_transfer(sdi, xfer);
+               greatfet_abort_acquisition_quick(sdi);
+       }
+}
+
+/* The public protocol.c API to start/stop acquisitions. */
+
+SR_PRIV int greatfet_setup_acquisition(const struct sr_dev_inst *sdi)
+{
+       int ret;
+
+       ret = greatfet_allocate_transfers(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = greatfet_calc_capture_chans(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int greatfet_start_acquisition(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct sr_usb_dev_inst *usb;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       usb = sdi->conn;
+       if (!devc || !usb)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+
+       /*
+        * Configure the logic analyzer. Claim the USB interface. This
+        * part of the sequence is not time critical.
+        */
+       ret = greatfet_logic_config(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = libusb_claim_interface(usb->devhdl, acq->samples_interface);
+       acq->samples_interface_claimed = ret == 0;
+
+       /*
+        * Ideally we could submit USB transfers before sending the
+        * logic analyzer start request. Experience suggests that this
+        * results in libusb IO errors. That's why we need to accept the
+        * window of blindness between sending the LA start request and
+        * initiating USB data reception.
+        */
+       ret = greatfet_logic_start(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = greatfet_prepare_transfers(sdi, xfer_complete_cb);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * The public acquisition abort routine, invoked by api.c logic. Could
+ * optionally spend more time than the _quick() routine.
+ */
+SR_PRIV void greatfet_abort_acquisition(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+
+       if (!sdi)
+               return;
+       devc = sdi->priv;
+       if (!devc)
+               return;
+
+       (void)greatfet_logic_stop(sdi);
+       greatfet_abort_acquisition_quick(sdi);
+}
+
+SR_PRIV int greatfet_stop_acquisition(const struct sr_dev_inst *sdi)
+{
+       struct sr_usb_dev_inst *usb;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       usb = sdi->conn;
+       if (!usb)
+               return SR_ERR_ARG;
+
+       ret = greatfet_logic_stop(sdi);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV void greatfet_release_resources(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct dev_transfers_t *dxfer;
+
+       if (!sdi)
+               return;
+       devc = sdi->priv;
+       if (!devc)
+               return;
+       dxfer = &devc->transfers;
+
+       /*
+        * Is there something that needs to be done here? Transfers'
+        * cancellation gets initiated and then happens as they keep
+        * completing. The completion handler releases their libusb
+        * resources. The last release also unregisters the periodic
+        * glib main loop callback.
+        *
+        * Can something be done here? The receive buffer still is
+        * allocated. As is the feed queue. Can we synchronize to the
+        * last release of the USB resources? Need we keep invoking
+        * the receive callback until the USB transfers pool has been
+        * released? Need we wait for the active transfers counter to
+        * drop to zero, is more checking involved?
+        */
+       if (dxfer->active_transfers)
+               sr_warn("Got active USB transfers in release code path.");
+}
+
+/*
+ * Process received sample date. There are two essential modes:
+ * - The straight forward case. The device provides 8 bits per sample
+ *   point. Forward each byte as is to the sigrok session. It matches
+ *   the sizeof(uint8_t) feed queue allocation parameter.
+ * - The compact presentation where a smaller number of channels is
+ *   active, and their data spans only part of a byte per sample point.
+ *   Multiple samples' data is sharing bytes, and bytes will carry data
+ *   that was taken at different times. This requires some untangling
+ *   before forwarding byte sized sample data to the sigrok session.
+ *
+ * Implementation details:
+ * - Samples taken first are found in the least significant bits of a
+ *   byte. Samples taken next are found in upper bits of the byte. For
+ *   example a byte containing 4x 2bit sample data is seen as 33221100.
+ * - Depending on the number of enabled channels there could be up to
+ *   eight samples in one byte of sample memory. This implementation
+ *   tries to accumulate one input byte's content, but not more. To
+ *   simplify the implementation. Performance can get tuned later as
+ *   the need gets identified. Sampling at 204MHz results in some 3%
+ *   CPU load with Pulseview on the local workstation.
+ * - Samples for 16 channels transparently are handled by the simple
+ *   8 channel case above. All logic data of an individual samplepoint
+ *   occupies full bytes, endianess of sample data as provided by the
+ *   device firmware and the sigrok session are the same. No conversion
+ *   is required.
+ */
+static int greatfet_process_receive_data(const struct sr_dev_inst *sdi,
+       const uint8_t *data, size_t dlen)
+{
+       static int diag_shown;
+
+       struct dev_context *devc;
+       struct dev_acquisition_t *acq;
+       struct feed_queue_logic *q;
+       uint64_t samples_remain;
+       gboolean exceeded;
+       size_t samples_rcvd;
+       uint8_t raw_mask;
+       size_t points_per_byte, points_count;
+       uint8_t raw_data;
+       uint8_t accum[8];
+       const uint8_t *rdptr;
+       uint8_t *wrptr;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       acq = &devc->acquisition;
+       q = acq->feed_queue;
+
+       /*
+        * Check whether acquisition limits apply, and whether they
+        * were reached or exceeded before. Constrain the submission
+        * of more sample values to what's still within the limits of
+        * the current acquisition.
+        */
+       ret = sr_sw_limits_get_remain(&devc->sw_limits,
+               &samples_remain, NULL, NULL, &exceeded);
+       if (ret != SR_OK)
+               return ret;
+       if (exceeded)
+               return SR_OK;
+
+       /*
+        * Check for the simple case first. Where bytes carry samples
+        * of exactly one sample point. Pass memory in verbatim form.
+        * Notice that 16bit sample quantities happen to work here too.
+        */
+       if (!acq->channel_shift) {
+               samples_rcvd = dlen / acq->unit_size;
+               if (samples_remain && samples_rcvd > samples_remain)
+                       samples_rcvd = samples_remain;
+               ret = feed_queue_logic_submit_many(q, data, samples_rcvd);
+               if (ret != SR_OK)
+                       return ret;
+               sr_sw_limits_update_samples_read(&devc->sw_limits, samples_rcvd);
+               return SR_OK;
+       }
+
+       /*
+        * Handle the complex case by means of some naive logic. To
+        * simplify the implementation for now and see if the approach
+        * works out. It helps that the firmware provides sample data
+        * in units of power-of-two bit counts per sample point. This
+        * eliminates fragments which could span several transfers.
+        *
+        * Notice that dense sample memory only happens for channel
+        * counts under 8. That's why we read bytes here and need not
+        * dispatch based on unit size.
+        */
+       raw_mask = (1UL << acq->channel_shift) - 1;
+       points_per_byte = 8 / acq->channel_shift;
+       if (!diag_shown++) {
+               sr_dbg("sample memory: ch count %zu, ch shift %zu, mask 0x%x, points %zu",
+                       acq->capture_channels, acq->channel_shift,
+                       raw_mask, points_per_byte);
+       }
+       samples_rcvd = dlen * points_per_byte;
+       if (samples_remain && samples_rcvd > samples_remain) {
+               samples_rcvd = samples_remain;
+               dlen = samples_rcvd;
+               dlen += points_per_byte - 1;
+               dlen /= points_per_byte;
+       }
+       rdptr = data;
+       while (dlen--) {
+               raw_data = read_u8_inc(&rdptr);
+               wrptr = accum;
+               points_count = points_per_byte;
+               while (points_count--) {
+                       write_u8_inc(&wrptr, raw_data & raw_mask);
+                       raw_data >>= acq->channel_shift;
+               }
+               points_count = points_per_byte;
+               ret = feed_queue_logic_submit_many(q, accum, points_count);
+               if (ret != SR_OK)
+                       return ret;
+               sr_sw_limits_update_samples_read(&devc->sw_limits, points_count);
+       }
+       return SR_OK;
+}
+
+/* Receive callback, invoked when data is available, or periodically. */
 SR_PRIV int greatfet_receive_data(int fd, int revents, void *cb_data)
 {
-       const struct sr_dev_inst *sdi;
+       struct sr_dev_inst *sdi;
        struct dev_context *devc;
+       struct drv_context *drvc;
+       libusb_context *ctx;
+       struct timeval tv;
 
        (void)fd;
+       (void)revents;
 
        sdi = cb_data;
-       if (!sdi)
+       if (!sdi || !sdi->priv || !sdi->driver)
                return TRUE;
-
        devc = sdi->priv;
        if (!devc)
                return TRUE;
+       drvc = sdi->driver->context;
+       if (!drvc || !drvc->sr_ctx)
+               return TRUE;
+       ctx = drvc->sr_ctx->libusb_ctx;
+
+       /*
+        * Handle those USB transfers which have completed so far
+        * in a regular fashion. These carry desired sample values.
+        */
+       tv.tv_sec = tv.tv_usec = 0;
+       libusb_handle_events_timeout(ctx, &tv);
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       /*
+        * End the current acquisition when limites were reached.
+        * Process USB transfers again here before returning, because
+        * acquisition termination will unregister the receive callback,
+        * and cancel previously submitted transfers. Reap those here.
+        */
+       if (sr_sw_limits_check(&devc->sw_limits)) {
+               greatfet_abort_acquisition_quick(sdi);
+               tv.tv_sec = tv.tv_usec = 0;
+               libusb_handle_events_timeout(ctx, &tv);
        }
 
        return TRUE;
index fc61b6800916c76b3921e8c51239220bdd841805..e5a4b8add55d09fa79c1466e920b91522590366d 100644 (file)
@@ -1,6 +1,8 @@
 /*
  * This file is part of the libsigrok project.
  *
+ * Copyright (C) 2019 Katherine J. Temkin <k@ktemkin.com>
+ * Copyright (C) 2019 Mikaela Szekely <qyriad@gmail.com>
  * Copyright (C) 2023 Gerhard Sittig <gerhard.sittig@gmx.net>
  *
  * This program is free software: you can redistribute it and/or modify
 #ifndef LIBSIGROK_HARDWARE_GREATFET_PROTOCOL_H
 #define LIBSIGROK_HARDWARE_GREATFET_PROTOCOL_H
 
-#include <stdint.h>
 #include <glib.h>
 #include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "greatfet"
 
 struct dev_context {
+       struct sr_dev_inst *sdi;
+       GString *usb_comm_buffer;
+       char *firmware_version;
+       char *serial_number;
+       size_t channel_count;
+       char **channel_names;
+       struct sr_sw_limits sw_limits;
+       uint64_t samplerate;
+       struct dev_acquisition_t {
+               uint64_t bandwidth_threshold;
+               size_t unit_size;
+               struct feed_queue_logic *feed_queue;
+               size_t capture_channels;
+               size_t channel_shift;
+               size_t points_per_byte;
+               uint64_t capture_samplerate;
+               size_t firmware_bufsize;
+               uint8_t samples_endpoint;
+               uint8_t control_interface;
+               uint8_t samples_interface;
+               enum {
+                       ACQ_IDLE,
+                       ACQ_PREPARE,
+                       ACQ_RECEIVE,
+                       ACQ_SHUTDOWN,
+               } acquisition_state;
+               gboolean frame_begin_sent;
+               gboolean control_interface_claimed;
+               gboolean samples_interface_claimed;
+               gboolean start_req_sent;
+       } acquisition;
+       struct dev_transfers_t {
+               size_t transfer_bufsize;
+               size_t transfers_count;
+               uint8_t *transfer_buffer;
+               struct libusb_transfer **transfers;
+               size_t active_transfers;
+               size_t capture_bufsize;
+       } transfers;
 };
 
+SR_PRIV int greatfet_get_serial_number(const struct sr_dev_inst *sdi);
+SR_PRIV int greatfet_get_version_number(const struct sr_dev_inst *sdi);
+
+SR_PRIV int greatfet_setup_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV int greatfet_start_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV void greatfet_abort_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV int greatfet_stop_acquisition(const struct sr_dev_inst *sdi);
+SR_PRIV void greatfet_release_resources(const struct sr_dev_inst *sdi);
+
 SR_PRIV int greatfet_receive_data(int fd, int revents, void *cb_data);
 
 #endif