452 lines
9.2 KiB
C
452 lines
9.2 KiB
C
/* (C) 2019 by Sylvain Munaut <tnt@246tNt.com>
|
|
* (C) 2019-2020 by Harald Welte <laforge@gnumonks.org>
|
|
* All Rights Reserved
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sched.h>
|
|
#include <errno.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <libusb.h>
|
|
|
|
#include "idt82v2081.h"
|
|
#include "idt82v2081_usb.h"
|
|
#include "idt82v2081_regs.h"
|
|
|
|
|
|
static int g_do_exit = 0;
|
|
|
|
#define USB_VID 0x1d50
|
|
#define USB_PID 0x6151
|
|
#define EP_DATA_IN0 0x81
|
|
#define EP_DATA_IN1 0x82
|
|
|
|
struct e1_streamer;
|
|
struct flow;
|
|
|
|
typedef int (*xfer_cb_t)(struct e1_streamer *e1s, struct flow *flow, uint8_t *buf, int size);
|
|
|
|
struct flow_entry {
|
|
uint8_t *buf;
|
|
struct libusb_transfer *xfr;
|
|
};
|
|
|
|
struct flow {
|
|
struct e1_streamer *parent;
|
|
xfer_cb_t cb;
|
|
|
|
int ep;
|
|
int count;
|
|
int size;
|
|
int ppx;
|
|
struct flow_entry *entries;
|
|
};
|
|
|
|
struct e1_streamer {
|
|
struct libusb_device_handle *devh;
|
|
struct flow data_in[2];
|
|
struct idt82 liu[2];
|
|
FILE *fh;
|
|
};
|
|
|
|
struct e1_chunk_hdr {
|
|
uint32_t magic;
|
|
struct {
|
|
uint64_t sec;
|
|
uint64_t usec;
|
|
} time;
|
|
int16_t len;
|
|
uint8_t ep;
|
|
} __attribute__((packed));
|
|
|
|
|
|
static int
|
|
cb_xfr_data_in(struct e1_streamer *e1s, struct flow *flow, uint8_t *buf, int size)
|
|
{
|
|
struct e1_chunk_hdr hdr;
|
|
struct timeval tv;
|
|
int rc;
|
|
|
|
hdr.magic = 0xe115600d; /* E1 is good */
|
|
|
|
gettimeofday(&tv, NULL);
|
|
hdr.time.sec = tv.tv_sec;
|
|
hdr.time.usec = tv.tv_usec;
|
|
|
|
hdr.ep = flow->ep;
|
|
hdr.len = size;
|
|
|
|
if (size < 0) {
|
|
printf("EP %02x - Err %d: %s\n", flow->ep, size, libusb_strerror(size));
|
|
return 0;
|
|
}
|
|
|
|
if (!e1s->fh)
|
|
return 0;
|
|
|
|
rc = fwrite(&hdr, sizeof(struct e1_chunk_hdr), 1, e1s->fh);
|
|
if (rc != 1) {
|
|
fprintf(stderr, "[!] Short write: %d != %zd", rc, sizeof(struct e1_chunk_hdr));
|
|
if (rc == -1)
|
|
fprintf(stderr, ", %s\n", strerror(errno));
|
|
else
|
|
fprintf(stderr, "\n");
|
|
g_do_exit = 1;
|
|
}
|
|
|
|
if (size > 0) {
|
|
rc = fwrite(buf, size, 1, e1s->fh);
|
|
if (rc != 1) {
|
|
fprintf(stderr, "[!] Short write: %d != %zd", rc, sizeof(struct e1_chunk_hdr));
|
|
if (rc == -1)
|
|
fprintf(stderr, ", %s\n", strerror(errno));
|
|
else
|
|
fprintf(stderr, "\n");
|
|
g_do_exit = 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void LIBUSB_CALL cb_xfr(struct libusb_transfer *xfr)
|
|
{
|
|
struct flow *flow = (struct flow *) xfr->user_data;
|
|
struct e1_streamer *e1s = flow->parent;
|
|
int j, rv, len;
|
|
|
|
#if 0
|
|
fprintf(stderr, "transfer status (%02x) %d [%d %d] [%d %d]\n", flow->ep, xfr->status,
|
|
xfr->iso_packet_desc[0].status,
|
|
xfr->iso_packet_desc[0].actual_length,
|
|
xfr->iso_packet_desc[1].status,
|
|
xfr->iso_packet_desc[1].actual_length
|
|
);
|
|
#endif
|
|
|
|
if (xfr->status != LIBUSB_TRANSFER_COMPLETED) {
|
|
fprintf(stderr, "[!] XFR status != completed (%d)\n", xfr->status);
|
|
g_do_exit = 1;
|
|
}
|
|
|
|
len = 0;
|
|
|
|
if (flow->ep & 0x80) {
|
|
for (j=0; j<flow->ppx; j++) {
|
|
flow->cb(e1s, flow,
|
|
libusb_get_iso_packet_buffer_simple(xfr, j),
|
|
(xfr->iso_packet_desc[j].status == LIBUSB_TRANSFER_COMPLETED) ?
|
|
xfr->iso_packet_desc[j].actual_length : -1
|
|
);
|
|
if (!(xfr->iso_packet_desc[j].status == LIBUSB_TRANSFER_COMPLETED)) {
|
|
fprintf(stderr, "[!] ISO packet status != completed (%d)\n",
|
|
xfr->iso_packet_desc[j].status);
|
|
}
|
|
|
|
len += (xfr->iso_packet_desc[j].length = flow->size);
|
|
}
|
|
} else {
|
|
for (j=0; j<flow->ppx; j++)
|
|
len += (xfr->iso_packet_desc[j].length = flow->cb(e1s, flow, &xfr->buffer[len], flow->size));
|
|
}
|
|
|
|
libusb_fill_iso_transfer(xfr, e1s->devh, flow->ep,
|
|
xfr->buffer, len, flow->ppx,
|
|
cb_xfr, flow, 0
|
|
);
|
|
|
|
rv = libusb_submit_transfer(xfr);
|
|
if (rv) {
|
|
fprintf(stderr, "[!] Error re-submitting buffer (%d): %s\n", rv, libusb_strerror(rv));
|
|
g_do_exit = 1;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
_e1s_flow_fini(struct flow *flow)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<flow->count; i++)
|
|
free(flow->entries[i].buf);
|
|
|
|
free(flow->entries);
|
|
}
|
|
|
|
static void
|
|
_e1s_flow_init(struct e1_streamer *e1s, struct flow *flow, xfer_cb_t cb, int ep, int count, int size, int ppx)
|
|
{
|
|
int i;
|
|
|
|
flow->parent = e1s;
|
|
flow->cb = cb;
|
|
flow->ep = ep;
|
|
flow->count = count;
|
|
flow->size = size;
|
|
flow->ppx = ppx;
|
|
flow->entries = calloc(count, sizeof(struct flow_entry));
|
|
|
|
for (i=0; i<count; i++)
|
|
flow->entries[i].buf = malloc(size * ppx);
|
|
}
|
|
|
|
static int
|
|
_e1s_flow_start(struct e1_streamer *e1s, struct flow *flow)
|
|
{
|
|
struct libusb_transfer *xfr;
|
|
int i, j, rv, len;
|
|
|
|
for (i=0; i<flow->count; i++)
|
|
{
|
|
xfr = libusb_alloc_transfer(flow->ppx);
|
|
if (!xfr)
|
|
return -ENOMEM;
|
|
|
|
len = 0;
|
|
|
|
if (flow->ep & 0x80) {
|
|
for (j=0; j<flow->ppx; j++)
|
|
len += (xfr->iso_packet_desc[j].length = flow->size);
|
|
} else {
|
|
for (j=0; j<flow->ppx; j++)
|
|
len += (xfr->iso_packet_desc[j].length = flow->cb(e1s, flow, &flow->entries[i].buf[len], flow->size));
|
|
}
|
|
|
|
libusb_fill_iso_transfer(xfr, e1s->devh, flow->ep,
|
|
flow->entries[i].buf, len, flow->ppx,
|
|
cb_xfr, flow, 0
|
|
);
|
|
|
|
rv = libusb_submit_transfer(xfr);
|
|
if (rv) {
|
|
return rv;
|
|
}
|
|
|
|
flow->entries[i].xfr = xfr;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
e1s_release(struct e1_streamer *e1s)
|
|
{
|
|
if (!e1s)
|
|
return;
|
|
|
|
_e1s_flow_fini(&e1s->data_in[0]);
|
|
_e1s_flow_fini(&e1s->data_in[1]);
|
|
|
|
if (e1s->devh) {
|
|
libusb_release_interface(e1s->devh, 0);
|
|
libusb_close(e1s->devh);
|
|
}
|
|
|
|
free(e1s);
|
|
}
|
|
|
|
static struct e1_streamer *
|
|
e1s_new(bool monitor, const char *out_file, bool append, int nx, int ppx)
|
|
{
|
|
struct e1_streamer *e1s = NULL;
|
|
int rv;
|
|
|
|
e1s = calloc(1, sizeof(struct e1_streamer));
|
|
if (!e1s)
|
|
return NULL;
|
|
|
|
e1s->devh = libusb_open_device_with_vid_pid(NULL, USB_VID, USB_PID);
|
|
if (!e1s->devh) {
|
|
fprintf(stderr, "Error finding USB device\n");
|
|
goto err;
|
|
}
|
|
|
|
rv = libusb_claim_interface(e1s->devh, 0);
|
|
if (rv < 0) {
|
|
fprintf(stderr, "Error claiming interface: %s\n", libusb_error_name(rv));
|
|
goto err;
|
|
}
|
|
|
|
rv = libusb_set_interface_alt_setting(e1s->devh, 0, 1);
|
|
if (rv < 0) {
|
|
fprintf(stderr, "Error enabling interface: %s\n", libusb_error_name(rv));
|
|
goto err;
|
|
}
|
|
|
|
_e1s_flow_init(e1s, &e1s->data_in[0], cb_xfr_data_in, EP_DATA_IN0, nx, 388, ppx);
|
|
_e1s_flow_init(e1s, &e1s->data_in[1], cb_xfr_data_in, EP_DATA_IN1, nx, 388, ppx);
|
|
|
|
idt82_usb_init(&e1s->liu[0], e1s->devh, EP_DATA_IN0);
|
|
idt82_usb_init(&e1s->liu[1], e1s->devh, EP_DATA_IN1);
|
|
idt82_init(&e1s->liu[0], monitor);
|
|
idt82_init(&e1s->liu[1], monitor);
|
|
|
|
if (out_file) {
|
|
e1s->fh = fopen(out_file, append ? "ab" : "wb");
|
|
if (!e1s->fh)
|
|
fprintf(stderr, "[1] Failed to open recording file\n");
|
|
}
|
|
|
|
return e1s;
|
|
|
|
err:
|
|
e1s_release(e1s);
|
|
return NULL;
|
|
}
|
|
|
|
struct options {
|
|
/* Transfer config */
|
|
int nx;
|
|
int ppx;
|
|
|
|
/* Output */
|
|
const char *out_filename;
|
|
bool out_append;
|
|
|
|
/* PHY */
|
|
bool monitor;
|
|
|
|
/* OS */
|
|
bool realtime;
|
|
};
|
|
|
|
static void
|
|
opts_defaults(struct options *opts)
|
|
{
|
|
memset(opts, 0x00, sizeof(struct options));
|
|
|
|
opts->nx = 2;
|
|
opts->ppx = 4;
|
|
}
|
|
|
|
static void
|
|
opts_help(void)
|
|
{
|
|
fprintf(stderr, " -a Output : append mode\n");
|
|
fprintf(stderr, " -o FILE Output : filename\n");
|
|
fprintf(stderr, " -n NX Xfer : Number of queued transfers (default: 2)\n");
|
|
fprintf(stderr, " -p PPX Xfer : Number of packets per transfer (default: 4)\n");
|
|
fprintf(stderr, " -m PHY : Monitor mode (i.e. high gain)\n");
|
|
fprintf(stderr, " -r OS : Set real-time priority on process\n");
|
|
fprintf(stderr, " -h help\n");
|
|
}
|
|
|
|
static int
|
|
opts_parse(struct options *opts, int argc, char *argv[])
|
|
{
|
|
const char *opts_short = "ao:n:p:mrh";
|
|
int opt;
|
|
|
|
while ((opt = getopt(argc, argv, opts_short)) != -1)
|
|
{
|
|
switch(opt) {
|
|
case 'a':
|
|
opts->out_append = true;
|
|
break;
|
|
|
|
case 'o':
|
|
opts->out_filename = optarg;
|
|
break;
|
|
|
|
case 'n':
|
|
opts->nx = atoi(optarg);
|
|
if (opts->nx <= 0) {
|
|
fprintf(stderr, "[!] Invalid nx value ignored\n");
|
|
opts->nx = 2;
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
opts->ppx = atoi(optarg);
|
|
if (opts->ppx <= 0) {
|
|
fprintf(stderr, "[!] Invalid ppx value ignored\n");
|
|
opts->ppx = 4;
|
|
}
|
|
break;
|
|
|
|
case 'm':
|
|
opts->monitor = true;
|
|
break;
|
|
|
|
case 'r':
|
|
opts->realtime = true;
|
|
break;
|
|
|
|
case 'h':
|
|
default:
|
|
opts_help();
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct e1_streamer *e1s;
|
|
struct sched_param sp;
|
|
struct options opts;
|
|
int rv;
|
|
|
|
opts_defaults(&opts);
|
|
opts_parse(&opts, argc, argv);
|
|
|
|
if (opts.realtime) {
|
|
memset(&sp, 0x00, sizeof(sp));
|
|
sp.sched_priority = 50;
|
|
rv = sched_setscheduler(0, SCHED_RR, &sp);
|
|
printf("%d %d\n", rv, errno);
|
|
perror("sched_setscheduler");
|
|
}
|
|
|
|
rv = libusb_init(NULL);
|
|
if (rv < 0) {
|
|
fprintf(stderr, "Error initializing libusb: %s\n", libusb_error_name(rv));
|
|
return rv;
|
|
}
|
|
|
|
e1s = e1s_new(opts.monitor, opts.out_filename, opts.out_append, opts.nx, opts.ppx);
|
|
if (!e1s)
|
|
goto out;
|
|
|
|
_e1s_flow_start(e1s, &e1s->data_in[0]);
|
|
_e1s_flow_start(e1s, &e1s->data_in[1]);
|
|
|
|
while (!g_do_exit) {
|
|
rv = libusb_handle_events(NULL);
|
|
if (rv != LIBUSB_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
out:
|
|
e1s_release(e1s);
|
|
|
|
libusb_exit(NULL);
|
|
|
|
return 0;
|
|
}
|