| /* |
| * This file is part of the libsigrok project. |
| * |
| * Copyright 2014 Google, Inc |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <glib.h> |
| #include <libusb.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <math.h> |
| #include "libsigrok.h" |
| #include "libsigrok-internal.h" |
| #include "protocol.h" |
| |
| #define TWINKIE_VID 0x18d1 |
| #define TWINKIE_PID 0x500a |
| |
| #define USB_INTERFACE 1 |
| #define USB_CONFIGURATION 1 |
| |
| #define MAX_RENUM_DELAY_MS 3000 |
| #define NUM_SIMUL_TRANSFERS 32 |
| |
| #define SAMPLE_RATE SR_KHZ(2400) |
| |
| SR_PRIV struct sr_dev_driver chromium_twinkie_driver_info; |
| static struct sr_dev_driver *di = &chromium_twinkie_driver_info; |
| |
| extern int sniffer_sample_header_size; |
| |
| static const int32_t hwopts[] = { |
| SR_CONF_CONN, |
| }; |
| |
| static const int32_t hwcaps[] = { |
| SR_CONF_LOGIC_ANALYZER, |
| SR_CONF_SAMPLERATE, |
| |
| SR_CONF_LIMIT_SAMPLES, |
| SR_CONF_CONTINUOUS, |
| }; |
| |
| static const int32_t soft_trigger_matches[] = { |
| SR_TRIGGER_ZERO, |
| SR_TRIGGER_ONE, |
| SR_TRIGGER_RISING, |
| SR_TRIGGER_FALLING, |
| SR_TRIGGER_EDGE, |
| }; |
| |
| static const char *channel_names[] = { |
| "CC1", "CC2", "VBus Voltage", "VBus Current", |
| NULL, |
| }; |
| |
| static int init(struct sr_context *sr_ctx) |
| { |
| return std_init(sr_ctx, di, LOG_PREFIX); |
| } |
| |
| static GSList *scan(GSList *options) |
| { |
| struct drv_context *drvc; |
| struct dev_context *devc; |
| struct sr_dev_inst *sdi; |
| struct sr_usb_dev_inst *usb; |
| struct sr_channel *ch; |
| struct sr_config *src; |
| GSList *l, *devices, *conn_devices; |
| struct libusb_device_descriptor des; |
| libusb_device **devlist; |
| int ret, i, j; |
| const char *conn; |
| char connection_id[64]; |
| |
| drvc = di->priv; |
| |
| conn = 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; |
| } |
| } |
| if (conn) |
| conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn); |
| else |
| conn_devices = NULL; |
| |
| /* Find all Twinkie devices */ |
| devices = NULL; |
| libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); |
| for (i = 0; devlist[i]; i++) { |
| if (conn) { |
| usb = NULL; |
| for (l = conn_devices; l; l = l->next) { |
| usb = l->data; |
| if (usb->bus == libusb_get_bus_number(devlist[i]) |
| && usb->address == libusb_get_device_address(devlist[i])) |
| break; |
| } |
| if (!l) |
| /* This device matched none of the ones that |
| * matched the conn specification. */ |
| continue; |
| } |
| |
| if ((ret = libusb_get_device_descriptor(devlist[i], &des)) != 0) { |
| sr_warn("Failed to get device descriptor: %s.", |
| libusb_error_name(ret)); |
| continue; |
| } |
| |
| if (des.idVendor != TWINKIE_VID || des.idProduct != TWINKIE_PID) |
| continue; |
| |
| #ifdef CONFIG_USBC_SNIFFER_HEADER_V1 |
| sniffer_sample_header_size = SNIFFER_SAMPLE_HEADER_SIZE_WITHOUT_VBUS; |
| #else /* CONFIG_USBC_SNIFFER_HEADER_V2 */ |
| sniffer_sample_header_size = SNIFFER_SAMPLE_HEADER_SIZE_WITH_VBUS; |
| #endif |
| usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)); |
| |
| sdi = sr_dev_inst_new(SR_ST_INITIALIZING, |
| "Chromium", "Twinkie", NULL); |
| if (!sdi) |
| return NULL; |
| sdi->driver = di; |
| sdi->connection_id = g_strdup(connection_id); |
| |
| for (j = 0; channel_names[j]; j++) { |
| if (!(ch = sr_channel_new(j, SR_CHANNEL_LOGIC, TRUE, |
| channel_names[j]))) |
| return NULL; |
| sdi->channels = g_slist_append(sdi->channels, ch); |
| } |
| |
| if (!(devc = g_try_malloc0(sizeof(struct dev_context)))) |
| return NULL; |
| sdi->priv = devc; |
| drvc->instances = g_slist_append(drvc->instances, sdi); |
| devices = g_slist_append(devices, sdi); |
| |
| sr_dbg("Found a Twinkie dongle."); |
| sdi->status = SR_ST_INACTIVE; |
| sdi->inst_type = SR_INST_USB; |
| sdi->conn = sr_usb_dev_inst_new( |
| libusb_get_bus_number(devlist[i]), |
| libusb_get_device_address(devlist[i]), NULL); |
| } |
| libusb_free_device_list(devlist, 1); |
| g_slist_free_full(conn_devices, (GDestroyNotify)sr_usb_dev_inst_free); |
| |
| return devices; |
| } |
| |
| static GSList *dev_list(void) |
| { |
| return ((struct drv_context *)(di->priv))->instances; |
| } |
| |
| static int twinkie_dev_open(struct sr_dev_inst *sdi) |
| { |
| libusb_device **devlist; |
| struct sr_usb_dev_inst *usb; |
| struct libusb_device_descriptor des; |
| struct drv_context *drvc; |
| int ret, i, device_count; |
| char connection_id[64]; |
| |
| drvc = di->priv; |
| usb = sdi->conn; |
| |
| if (sdi->status == SR_ST_ACTIVE) |
| /* Device is already in use. */ |
| return SR_ERR; |
| |
| device_count = libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); |
| if (device_count < 0) { |
| sr_err("Failed to get device list: %s.", |
| libusb_error_name(device_count)); |
| return SR_ERR; |
| } |
| |
| for (i = 0; i < device_count; i++) { |
| if ((ret = libusb_get_device_descriptor(devlist[i], &des))) { |
| sr_err("Failed to get device descriptor: %s.", |
| libusb_error_name(ret)); |
| continue; |
| } |
| |
| if (des.idVendor != TWINKIE_VID || des.idProduct != TWINKIE_PID) |
| continue; |
| |
| if ((sdi->status == SR_ST_INITIALIZING) || |
| (sdi->status == SR_ST_INACTIVE)) { |
| /* |
| * Check device by its physical USB bus/port address. |
| */ |
| usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)); |
| if (strcmp(sdi->connection_id, connection_id)) |
| /* This is not the one. */ |
| continue; |
| } |
| |
| if (!(ret = libusb_open(devlist[i], &usb->devhdl))) { |
| if (usb->address == 0xff) |
| /* |
| * First time we touch this device after FW |
| * upload, so we don't know the address yet. |
| */ |
| usb->address = libusb_get_device_address(devlist[i]); |
| } else { |
| sr_err("Failed to open device: %s.", |
| libusb_error_name(ret)); |
| break; |
| } |
| |
| ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE); |
| if (ret == LIBUSB_ERROR_BUSY) { |
| sr_err("Unable to claim USB interface. Another " |
| "program or driver has already claimed it."); |
| break; |
| } else if (ret == LIBUSB_ERROR_NO_DEVICE) { |
| sr_err("Device has been disconnected."); |
| break; |
| } else if (ret != 0) { |
| sr_err("Unable to claim interface: %s.", |
| libusb_error_name(ret)); |
| break; |
| } |
| |
| if ((ret = twinkie_init_device(sdi)) != SR_OK) { |
| sr_err("Failed to init device."); |
| break; |
| } |
| |
| sdi->status = SR_ST_ACTIVE; |
| sr_info("Opened device %d.%d, interface %d.", |
| usb->bus, usb->address, USB_INTERFACE); |
| |
| break; |
| } |
| libusb_free_device_list(devlist, 1); |
| |
| if (sdi->status != SR_ST_ACTIVE) { |
| if (usb->devhdl) { |
| libusb_release_interface(usb->devhdl, USB_INTERFACE); |
| libusb_close(usb->devhdl); |
| usb->devhdl = NULL; |
| } |
| return SR_ERR; |
| } |
| |
| return SR_OK; |
| } |
| |
| static int dev_open(struct sr_dev_inst *sdi) |
| { |
| int ret; |
| |
| ret = twinkie_dev_open(sdi); |
| if (ret != SR_OK) { |
| sr_err("Unable to open device."); |
| return SR_ERR; |
| } |
| |
| return SR_OK; |
| } |
| |
| static int dev_close(struct sr_dev_inst *sdi) |
| { |
| struct sr_usb_dev_inst *usb; |
| |
| usb = sdi->conn; |
| if (usb->devhdl == NULL) |
| return SR_ERR; |
| |
| sr_info("Closing device %d.%d interface %d.", |
| usb->bus, usb->address, USB_INTERFACE); |
| libusb_release_interface(usb->devhdl, USB_INTERFACE); |
| libusb_close(usb->devhdl); |
| usb->devhdl = NULL; |
| sdi->status = SR_ST_INACTIVE; |
| |
| return SR_OK; |
| } |
| |
| static int cleanup(void) |
| { |
| int ret; |
| struct drv_context *drvc; |
| |
| if (!(drvc = di->priv)) |
| /* Can get called on an unused driver, doesn't matter. */ |
| return SR_OK; |
| |
| |
| ret = std_dev_clear(di, NULL); |
| g_free(drvc); |
| di->priv = NULL; |
| |
| return ret; |
| } |
| |
| static int config_get(uint32_t key, GVariant **data, |
| const struct sr_dev_inst *sdi, |
| const struct sr_channel_group *cg) |
| { |
| struct sr_usb_dev_inst *usb; |
| char str[128]; |
| int ret; |
| |
| (void)cg; |
| |
| ret = SR_OK; |
| switch (key) { |
| case SR_CONF_CONN: |
| if (!sdi || !sdi->conn) |
| return SR_ERR_ARG; |
| usb = sdi->conn; |
| if (usb->address == 255) |
| /* Device still needs to re-enumerate after firmware |
| * upload, so we don't know its (future) address. */ |
| return SR_ERR; |
| snprintf(str, 128, "%d.%d", usb->bus, usb->address); |
| *data = g_variant_new_string(str); |
| break; |
| case SR_CONF_SAMPLERATE: |
| *data = g_variant_new_uint64(SAMPLE_RATE); |
| break; |
| 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) |
| { |
| struct dev_context *devc; |
| int ret; |
| |
| (void)cg; |
| |
| if (sdi->status != SR_ST_ACTIVE) |
| return SR_ERR_DEV_CLOSED; |
| |
| devc = sdi->priv; |
| |
| ret = SR_OK; |
| switch (key) { |
| case SR_CONF_LIMIT_SAMPLES: |
| devc->limit_samples = g_variant_get_uint64(data); |
| break; |
| default: |
| ret = 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)cg; |
| |
| ret = SR_OK; |
| switch (key) { |
| case SR_CONF_SCAN_OPTIONS: |
| *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32, |
| hwopts, ARRAY_SIZE(hwopts), sizeof(int32_t)); |
| break; |
| case SR_CONF_DEVICE_OPTIONS: |
| *data = g_variant_new_fixed_array(G_VARIANT_TYPE_INT32, |
| hwcaps, ARRAY_SIZE(hwcaps), sizeof(int32_t)); |
| break; |
| default: |
| return SR_ERR_NA; |
| } |
| |
| return ret; |
| } |
| |
| static void abort_acquisition(struct dev_context *devc) |
| { |
| int i; |
| |
| devc->sent_samples = -1; |
| |
| for (i = devc->num_transfers - 1; i >= 0; i--) { |
| if (devc->transfers[i]) |
| libusb_cancel_transfer(devc->transfers[i]); |
| } |
| } |
| |
| static int receive_data(int fd, int revents, void *cb_data) |
| { |
| struct timeval tv; |
| struct dev_context *devc; |
| struct drv_context *drvc; |
| const struct sr_dev_inst *sdi; |
| |
| (void)fd; |
| (void)revents; |
| |
| sdi = cb_data; |
| drvc = di->priv; |
| devc = sdi->priv; |
| |
| tv.tv_sec = tv.tv_usec = 0; |
| libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv); |
| if (devc->sent_samples == -2) { |
| abort_acquisition(devc); |
| } |
| |
| return TRUE; |
| } |
| |
| static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data) |
| { |
| struct dev_context *devc; |
| struct drv_context *drvc; |
| struct sr_usb_dev_inst *usb; |
| struct libusb_transfer *transfer; |
| unsigned int i, timeout, num_transfers; |
| int ret; |
| unsigned char *buf; |
| size_t size, convsize; |
| |
| if (sdi->status != SR_ST_ACTIVE) |
| return SR_ERR_DEV_CLOSED; |
| |
| drvc = di->priv; |
| devc = sdi->priv; |
| usb = sdi->conn; |
| |
| devc->cb_data = cb_data; |
| devc->sent_samples = 0; |
| /* reset per-CC context */ |
| memset(devc->cc, 0, sizeof(devc->cc)); |
| |
| timeout = 1000; |
| num_transfers = 10; |
| size = 10*1024; |
| convsize = size * 8 * 256 /* largest size : only rollbacks/no edges */; |
| devc->submitted_transfers = 0; |
| |
| devc->convbuffer_size = convsize; |
| if (!(devc->convbuffer = g_try_malloc(convsize * SAMPLE_UNIT_SIZE))) { |
| sr_err("Conversion buffer malloc failed."); |
| return SR_ERR_MALLOC; |
| } |
| memset(devc->convbuffer, 0, devc->convbuffer_size * SAMPLE_UNIT_SIZE); |
| |
| devc->transfers = g_try_malloc0(sizeof(*devc->transfers) * num_transfers); |
| if (!devc->transfers) { |
| sr_err("USB transfers malloc failed."); |
| g_free(devc->convbuffer); |
| return SR_ERR_MALLOC; |
| } |
| |
| devc->num_transfers = num_transfers; |
| for (i = 0; i < num_transfers; i++) { |
| if (!(buf = g_try_malloc(size))) { |
| sr_err("USB transfer buffer malloc failed."); |
| if (devc->submitted_transfers) |
| abort_acquisition(devc); |
| else { |
| g_free(devc->transfers); |
| g_free(devc->convbuffer); |
| } |
| return SR_ERR_MALLOC; |
| } |
| transfer = libusb_alloc_transfer(0); |
| libusb_fill_bulk_transfer(transfer, usb->devhdl, |
| 3 | LIBUSB_ENDPOINT_IN, buf, size, |
| twinkie_receive_transfer, devc, timeout); |
| if ((ret = libusb_submit_transfer(transfer)) != 0) { |
| sr_err("Failed to submit transfer: %s.", |
| libusb_error_name(ret)); |
| libusb_free_transfer(transfer); |
| g_free(buf); |
| abort_acquisition(devc); |
| return SR_ERR; |
| } |
| devc->transfers[i] = transfer; |
| devc->submitted_transfers++; |
| } |
| |
| devc->ctx = drvc->sr_ctx; |
| |
| usb_source_add(sdi->session, devc->ctx, timeout, receive_data, |
| (void *)sdi); |
| |
| /* Send header packet to the session bus. */ |
| std_session_send_df_header(cb_data, LOG_PREFIX); |
| |
| if ((ret = twinkie_start_acquisition(sdi)) != SR_OK) { |
| abort_acquisition(devc); |
| return ret; |
| } |
| |
| return SR_OK; |
| } |
| |
| static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data) |
| { |
| (void)cb_data; |
| |
| if (sdi->status != SR_ST_ACTIVE) |
| return SR_ERR_DEV_CLOSED; |
| |
| abort_acquisition(sdi->priv); |
| |
| return SR_OK; |
| } |
| |
| SR_PRIV struct sr_dev_driver chromium_twinkie_driver_info = { |
| .name = "chromium-twinkie", |
| .longname = "Chromium Twinkie", |
| .api_version = 1, |
| .init = init, |
| .cleanup = cleanup, |
| .scan = scan, |
| .dev_list = dev_list, |
| .dev_clear = NULL, |
| .config_get = config_get, |
| .config_set = config_set, |
| .config_list = config_list, |
| .dev_open = dev_open, |
| .dev_close = dev_close, |
| .dev_acquisition_start = dev_acquisition_start, |
| .dev_acquisition_stop = dev_acquisition_stop, |
| .priv = NULL, |
| }; |