blob: fa0d74d091c8982f3d2f5b01581f2de821a91e9e [file] [log] [blame]
/*
* 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 "protocol.h"
#include <stdint.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <libusb.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include "libsigrok.h"
#include "libsigrok-internal.h"
#define COMMAND_START_ACQUISITION 1
#define COMMAND_ABORT_ACQUISITION_ASYNC 2
#define COMMAND_ABORT_ACQUISITION_SYNC 0x7d
int sniffer_sample_header_size; /* set in api.c, can be 4 or 8 */
static int do_console_command(const struct sr_dev_inst *sdi,
const uint8_t *command, uint8_t cmd_len,
uint8_t *reply, uint8_t reply_len)
{
uint8_t buf[64];
struct sr_usb_dev_inst *usb;
int ret, xfer;
/* Temporary HACK */
return SR_OK;
usb = sdi->conn;
if (cmd_len < 1 || cmd_len > 64 || reply_len > 64 ||
command == NULL || (reply_len > 0 && reply == NULL))
return SR_ERR_ARG;
ret = libusb_bulk_transfer(usb->devhdl, 2, buf, cmd_len, &xfer, 1000);
if (ret != 0) {
sr_dbg("Failed to send console command 0x%02x: %s.",
command[0], libusb_error_name(ret));
return SR_ERR;
}
if (xfer != cmd_len) {
sr_dbg("Failed to send console command 0x%02x: incorrect length "
"%d != %d.", xfer, cmd_len);
return SR_ERR;
}
if (reply_len == 0)
return SR_OK;
ret = libusb_bulk_transfer(usb->devhdl, 0x80 | 1, buf, reply_len,
&xfer, 1000);
if (ret != 0) {
sr_dbg("Failed to receive reply to console command 0x%02x: %s.",
command[0], libusb_error_name(ret));
return SR_ERR;
}
if (xfer != reply_len) {
sr_dbg("Failed to receive reply to console command 0x%02x: "
"incorrect length %d != %d.", xfer, reply_len);
return SR_ERR;
}
return SR_OK;
}
SR_PRIV int twinkie_start_acquisition(const struct sr_dev_inst *sdi)
{
static const uint8_t command[1] = {
COMMAND_START_ACQUISITION,
};
int ret;
if ((ret = do_console_command(sdi, command, 1, NULL, 0)) != SR_OK)
return ret;
return SR_OK;
}
SR_PRIV int twinkie_init_device(const struct sr_dev_inst *sdi)
{
(void)sdi;
return SR_OK;
}
static void finish_acquisition(struct dev_context *devc)
{
struct sr_datafeed_packet packet;
struct sr_dev_inst *sdi = devc->cb_data;
/* Terminate session. */
packet.type = SR_DF_END;
sr_session_send(devc->cb_data, &packet);
/* Remove fds from polling. */
usb_source_remove(sdi->session, devc->ctx);
devc->num_transfers = 0;
g_free(devc->transfers);
g_free(devc->convbuffer);
}
static void free_transfer(struct libusb_transfer *transfer)
{
struct dev_context *devc;
unsigned int i;
devc = transfer->user_data;
g_free(transfer->buffer);
transfer->buffer = NULL;
libusb_free_transfer(transfer);
for (i = 0; i < devc->num_transfers; i++) {
if (devc->transfers[i] == transfer) {
devc->transfers[i] = NULL;
break;
}
}
devc->submitted_transfers--;
if (devc->submitted_transfers == 0)
finish_acquisition(devc);
}
static void export_samples(struct dev_context *devc, int cnt)
{
struct sr_datafeed_packet packet;
struct sr_datafeed_logic logic;
/* export the received data */
packet.type = SR_DF_LOGIC;
packet.payload = &logic;
if (devc->limit_samples &&
cnt > devc->limit_samples - devc->sent_samples)
cnt = devc->limit_samples - devc->sent_samples;
logic.length = cnt * SAMPLE_UNIT_SIZE;
logic.unitsize = SAMPLE_UNIT_SIZE;
logic.data = devc->convbuffer;
sr_session_send(devc->cb_data, &packet);
devc->sent_samples += cnt;
}
static void set_vbus_voltage_bits(uint8_t *vbus_dest, uint16_t vbus_voltage, int16_t offset)
{
/*
* Similar to RS232 encoding method. Encoding Format:
* IDLE: 0
* Start: 1
* Data: Value[15:0], TimeStamp[15:0]
* Stop: 0
*/
int i = 0;
/* the offset passed in is in us, and can be overflowed */
int offset_ms = offset/1000;
if (offset_ms > 0) /* convert overflowed value to the correct one */
offset_ms = -65 + offset_ms;
*vbus_dest |= 1<<2; /* start bit */
for (i = 0; i < 16; i ++)
*(vbus_dest + 1 + i) |= (((vbus_voltage >> i) & 1) << 2);
for (i = 0; i < 16; i ++)
*(vbus_dest + 17 + i) |= (((offset_ms >> i) & 1) << 2);
}
static void set_vbus_current_bits(uint8_t *vbus_dest, int16_t vbus_current, int16_t offset)
{
int i = 0;
int offset_ms = offset/1000;
*vbus_dest |= 1<<3; /* start bit */
if (offset_ms > 0)
offset_ms = -65 + offset_ms;
for (i = 0; i < 16; i ++)
*(vbus_dest + 1 + i) |= (((vbus_current >> i) & 1) << 3);
for (i = 0; i < 16; i ++)
*(vbus_dest + 17 + i) |= (((offset_ms >> i) & 1) << 3);
}
uint16_t vbus_voltage_previous = 0;
int16_t vbus_current_previous = 0;
static void expand_sample_data(struct dev_context *devc,
const uint8_t *src, size_t srccnt)
{
int i, f;
size_t b;
size_t rdy_samples, left_samples;
int frames = srccnt / SNIFFER_SAMPLE_PACKET_SIZE;
int sniffer_sample_payload_size = (SNIFFER_SAMPLE_PACKET_SIZE - sniffer_sample_header_size);
for (f = 0; f < frames; f++) {
int ch = (src[1] >> 4) & 3; /* samples channel number */
int bit = 1 << ch; /* channel bit mask */
struct cc_context *cc = devc->cc + ch;
uint8_t *dest = devc->convbuffer + cc->idx;
uint8_t *vbus_dest;
if (ch >= 2) /* only acquires CCx channels */
continue;
int vbus_voltage_set = 0; /* vbus set or not in this frame? 0: not; 1: set */
int vbus_current_set = 0;
/* TODO: check timestamp, overflow, sequence number */
/* need to calculate first; otherwise, will not get the correct value since src is updated */
uint16_t vbus_voltage = ((src[5] << 8 )| src[4]);
int16_t vbus_current = ((src[5] << 8 )| src[4]); /* may be voltage, may be current. */
int16_t vbus_offset = ((src[7] << 8 )| src[6]); /* may be the offset for voltage, or current.*/
/* skip header, go to edges data */
src+= sniffer_sample_header_size;
for (i = 0; i < sniffer_sample_payload_size; i++,src++)
if (*src == cc->prev_src) {
cc->rollbacks++;
} else {
uint8_t diff = *src - cc->prev_src;
int fixup = cc->rollbacks && (((int)*src < (int)cc->prev_src) || (*src == 0xff));
size_t total = (fixup ? cc->rollbacks - 1 : cc->rollbacks) * 256 + diff;
if (total + cc->idx > devc->convbuffer_size) {
sr_warn("overflow %d+%d/%d\n",
cc->idx, total,
devc->convbuffer_size);
/* reset current decoding */
cc->rollbacks = 0;
break;
}
vbus_dest = dest;
/* insert bits in the buffer */
if (cc->level)
for (b = 0 ; b < total ; b++, dest++)
*dest |= bit;
else
dest += total;
#ifdef CONFIG_USBC_SNIFFER_HEADER_V2
if (ch == 0) { /* cc1 header carries vbus voltage*/
if (!vbus_voltage_set) {
set_vbus_voltage_bits(vbus_dest, vbus_voltage, vbus_offset);
vbus_voltage_set = 1; /* after that, label vbus_voltage_set to true */
vbus_voltage_previous = vbus_voltage;
}
}
else { /* cc2 header carries vbus current*/
if (!vbus_current_set) {
set_vbus_current_bits(vbus_dest, vbus_current, vbus_offset);
vbus_current_set = 1;
vbus_current_previous = vbus_current;
}
}
#endif
cc->idx += total;
/* flip level on the next edge */
cc->level = ~cc->level;
cc->rollbacks = 0;
cc->prev_src = *src;
}
/* expand repeated rollbacks */
if (cc->rollbacks > 1) {
size_t total = 256 * (cc->rollbacks - 1);
if (total + cc->idx > devc->convbuffer_size) {
sr_warn("overflow %d+%d/%d\n",
cc->idx, total, devc->convbuffer_size);
/* reset current decoding */
total = 0;
}
vbus_dest = dest;
/* insert bits in the buffer */
if (cc->level)
for (b = 0 ; b < total ; b++, dest++)
*dest |= bit ;
else
dest += total;
#ifdef CONFIG_USBC_SNIFFER_HEADER_V2
if (ch == 0) {
if (!vbus_voltage_set) {
set_vbus_voltage_bits(vbus_dest, vbus_voltage, vbus_offset);
vbus_voltage_set = 1;
vbus_voltage_previous = vbus_voltage;
}
}
else {
if (!vbus_current_set) {
set_vbus_current_bits(vbus_dest, vbus_current, vbus_offset);
vbus_current_set = 1;
vbus_current_previous = vbus_current;
}
}
#endif
cc->idx += total;
cc->rollbacks = 1;
}
}
/* samples ready to be pushed (with both channels) */
rdy_samples = MIN(devc->cc[0].idx, devc->cc[1].idx);
left_samples = MAX(devc->cc[0].idx, devc->cc[1].idx) - rdy_samples;
/* skip empty transfer */
if (rdy_samples == 0)
return;
export_samples(devc, rdy_samples);
/* clean up what we have sent */
memmove(devc->convbuffer, devc->convbuffer + rdy_samples, left_samples * SAMPLE_UNIT_SIZE);
memset(devc->convbuffer + left_samples, 0, rdy_samples * SAMPLE_UNIT_SIZE);
devc->cc[0].idx -= rdy_samples;
devc->cc[1].idx -= rdy_samples;
}
SR_PRIV void twinkie_receive_transfer(struct libusb_transfer *transfer)
{
gboolean packet_has_error = FALSE;
struct dev_context *devc;
devc = transfer->user_data;
/*
* If acquisition has already ended, just free any queued up
* transfer that come in.
*/
if (devc->sent_samples < 0) {
free_transfer(transfer);
return;
}
if (transfer->status || transfer->actual_length)
sr_info("receive_transfer(): status %d received %d bytes.",
transfer->status, transfer->actual_length);
switch (transfer->status) {
case LIBUSB_TRANSFER_NO_DEVICE:
devc->sent_samples = -2;
free_transfer(transfer);
return;
case LIBUSB_TRANSFER_COMPLETED:
case LIBUSB_TRANSFER_TIMED_OUT: /* We may have received some data though. */
break;
default:
packet_has_error = TRUE;
break;
}
if (transfer->actual_length % SNIFFER_SAMPLE_PACKET_SIZE) {
sr_err("Bad USB packet size.");
packet_has_error = TRUE;
}
if (transfer->actual_length == 0 || packet_has_error)
goto resubmit;
/* decode received edges */
expand_sample_data(devc, transfer->buffer, transfer->actual_length);
if (devc->limit_samples &&
(uint64_t)devc->sent_samples >= devc->limit_samples) {
devc->sent_samples = -2;
free_transfer(transfer);
return;
}
resubmit:
if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS)
free_transfer(transfer);
}