dahdi-tools/xpp/astribank_usb.c

556 lines
15 KiB
C

/*
* Written by Oron Peled <oron@actcom.co.il>
* Copyright (C) 2008, Xorcom
*
* All rights reserved.
*
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#define _GNU_SOURCE /* for memrchr() */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <syslog.h>
#include <arpa/inet.h>
#include "astribank_usb.h"
#include "debug.h"
static const char rcsid[] = "$Id$";
#define DBG_MASK 0x01
#define TIMEOUT 500
#define TYPE_ENTRY(t,ni,n,ne,out,in,...) \
[t] = { \
.type_code = (t), \
.num_interfaces = (ni), \
.my_interface_num = (n), \
.num_endpoints = (ne), \
.my_ep_in = (in), \
.my_ep_out = (out), \
.name = #t, \
.endpoints = { __VA_ARGS__ }, \
}
static const struct interface_type interface_types[] = {
TYPE_ENTRY(USB_11xx, 1, 0, 4, MP_EP_OUT, MP_EP_IN,
XPP_EP_OUT,
MP_EP_OUT,
XPP_EP_IN,
MP_EP_IN),
TYPE_ENTRY(USB_FIRMWARE_II, 2, 1, 2, MP_EP_OUT, MP_EP_IN,
MP_EP_OUT,
MP_EP_IN),
TYPE_ENTRY(USB_PIC, 2, 0, 2, XPP_EP_OUT, XPP_EP_IN,
XPP_EP_OUT,
XPP_EP_IN),
};
#undef TYPE_ENTRY
//static int verbose = LOG_DEBUG;
/*
* USB handling
*/
/* return 1 if:
* - str has a number
* - It is larger than 0
* - It equals num
*/
static int num_matches(int num, const char* str) {
int str_val = atoi(str);
if (str_val <= 0)
return 0;
return (str_val == num);
}
struct usb_device *dev_of_path(const char *path)
{
struct usb_bus *bus;
struct usb_device *dev;
char dirname[PATH_MAX];
char filename[PATH_MAX];
const char *p;
int bnum;
int dnum;
int ret;
assert(path != NULL);
if(access(path, F_OK) < 0) {
perror(path);
return NULL;
}
/* Find last '/' */
if((p = memrchr(path, '/', strlen(path))) == NULL) {
ERR("Missing a '/' in %s\n", path);
return NULL;
}
/* Get the device number */
ret = sscanf(p + 1, "%d", &dnum);
if(ret != 1) {
ERR("Path tail is not a device number: '%s'\n", p);
return NULL;
}
/* Search for a '/' before that */
p = memrchr(path, '/', p - path);
if(p == NULL)
p = path; /* Relative path */
else
p++; /* skip '/' */
/* Get the bus number */
ret = sscanf(p, "%d", &bnum);
if(ret != 1) {
ERR("Path tail is not a bus number: '%s'\n", p);
return NULL;
}
sprintf(dirname, "%03d", bnum);
sprintf(filename, "%03d", dnum);
for (bus = usb_busses; bus; bus = bus->next) {
if (! num_matches(bnum, bus->dirname))
//if(strcmp(bus->dirname, dirname) != 0)
continue;
for (dev = bus->devices; dev; dev = dev->next) {
//if(strcmp(dev->filename, filename) == 0)
if (num_matches(dnum, dev->filename))
return dev;
}
}
ERR("no usb device match '%s'\n", path);
return NULL;
}
int get_usb_string(struct astribank_device *astribank, uint8_t item, char *buf, unsigned int len)
{
char tmp[BUFSIZ];
int ret;
assert(astribank->handle);
if (!item)
return 0;
ret = usb_get_string_simple(astribank->handle, item, tmp, BUFSIZ);
if (ret <= 0)
return ret;
return snprintf(buf, len, "%s", tmp);
}
static int match_interface(const struct astribank_device *astribank,
const struct interface_type *itype)
{
struct usb_interface *interface;
struct usb_interface_descriptor *iface_desc;
struct usb_config_descriptor *config_desc;
int i = itype - interface_types;
int inum;
int num_altsetting;
DBG("Checking[%d]: interfaces=%d interface num=%d endpoints=%d: \"%s\"\n",
i,
itype->num_interfaces,
itype->my_interface_num,
itype->num_endpoints,
itype->name);
config_desc = astribank->dev->config;
if (!config_desc) {
ERR("No configuration descriptor: strange USB1 controller?\n");
return 0;
}
if(config_desc->bNumInterfaces <= itype->my_interface_num) {
DBG("Too little interfaces: have %d need %d\n",
config_desc->bNumInterfaces, itype->my_interface_num + 1);
return 0;
}
if(astribank->my_interface_num != itype->my_interface_num) {
DBG("Wrong match -- not my interface num (wanted %d)\n", astribank->my_interface_num);
return 0;
}
inum = itype->my_interface_num;
interface = &config_desc->interface[inum];
assert(interface != NULL);
iface_desc = interface->altsetting;
num_altsetting = interface->num_altsetting;
assert(num_altsetting != 0);
assert(iface_desc != NULL);
if(iface_desc->bInterfaceClass != 0xFF) {
DBG("Bad interface class 0x%X\n", iface_desc->bInterfaceClass);
return 0;
}
if(iface_desc->bInterfaceNumber != itype->my_interface_num) {
DBG("Bad interface number %d\n", iface_desc->bInterfaceNumber);
return 0;
}
if(iface_desc->bNumEndpoints != itype->num_endpoints) {
DBG("Different number of endpoints %d\n", iface_desc->bNumEndpoints);
return 0;
}
return 1;
}
static int astribank_init(struct astribank_device *astribank)
{
struct usb_device_descriptor *dev_desc;
struct usb_config_descriptor *config_desc;
struct usb_interface *interface;
struct usb_interface_descriptor *iface_desc;
struct usb_endpoint_descriptor *endpoint;
const struct interface_type *fwtype;
int i;
assert(astribank);
astribank->handle = usb_open(astribank->dev);
if(!astribank->handle) {
ERR("Failed to open usb device '%s/%s': %s\n",
astribank->dev->bus->dirname, astribank->dev->filename, usb_strerror());
return 0;
}
fwtype = astribank->fwtype;
if(usb_claim_interface(astribank->handle, fwtype->my_interface_num) != 0) {
ERR("usb_claim_interface: %s\n", usb_strerror());
return 0;
}
dev_desc = &astribank->dev->descriptor;
config_desc = astribank->dev->config;
if (!config_desc) {
ERR("usb interface without a configuration\n");
return 0;
}
DBG("Got config_desc. Looking for interface %d\n", fwtype->my_interface_num);
interface = &config_desc->interface[fwtype->my_interface_num];
iface_desc = interface->altsetting;
endpoint = iface_desc->endpoint;
astribank->is_usb2 = (endpoint->wMaxPacketSize == 512);
for(i = 0; i < iface_desc->bNumEndpoints; i++, endpoint++) {
DBG("Validating endpoint @ %d (interface %d)\n", i, fwtype->my_interface_num);
if(endpoint->bEndpointAddress != fwtype->endpoints[i]) {
ERR("Wrong endpoint 0x%X != 0x%X (at index %d)\n",
endpoint->bEndpointAddress,
fwtype->endpoints[i],
i);
return 0;
}
if(endpoint->bEndpointAddress == MP_EP_OUT || endpoint->bEndpointAddress == MP_EP_IN) {
if(endpoint->wMaxPacketSize > PACKET_SIZE) {
ERR("Endpoint #%d wMaxPacketSize too large (%d)\n", i, endpoint->wMaxPacketSize);
return 0;
}
}
}
astribank->my_ep_in = fwtype->my_ep_in;
astribank->my_ep_out = fwtype->my_ep_out;
if(get_usb_string(astribank, dev_desc->iManufacturer, astribank->iManufacturer, BUFSIZ) < 0)
return 0;
if(get_usb_string(astribank, dev_desc->iProduct, astribank->iProduct, BUFSIZ) < 0)
return 0;
if(get_usb_string(astribank, dev_desc->iSerialNumber, astribank->iSerialNumber, BUFSIZ) < 0)
return 0;
if(get_usb_string(astribank, iface_desc->iInterface, astribank->iInterface, BUFSIZ) < 0)
return 0;
DBG("ID=%04X:%04X Manufacturer=[%s] Product=[%s] SerialNumber=[%s] Interface=[%s]\n",
dev_desc->idVendor,
dev_desc->idProduct,
astribank->iManufacturer,
astribank->iProduct,
astribank->iSerialNumber,
astribank->iInterface);
if(usb_clear_halt(astribank->handle, astribank->my_ep_out) != 0) {
ERR("Clearing output endpoint: %s\n", usb_strerror());
return 0;
}
if(usb_clear_halt(astribank->handle, astribank->my_ep_in) != 0) {
ERR("Clearing input endpoint: %s\n", usb_strerror());
return 0;
}
if((i = flush_read(astribank)) < 0) {
ERR("flush_read failed: %d\n", i);
return 0;
}
return 1;
}
struct astribank_device *astribank_open(const char devpath[], int iface_num)
{
struct astribank_device *astribank;
int i;
DBG("devpath='%s' iface_num=%d\n", devpath, iface_num);
if((astribank = malloc(sizeof(*astribank))) == NULL) {
ERR("Out of memory");
return NULL;
}
memset(astribank, 0, sizeof(*astribank));
astribank->my_interface_num = iface_num;
usb_init();
usb_find_busses();
usb_find_devices();
astribank->dev = dev_of_path(devpath);
if(!astribank->dev) {
ERR("Bailing out\n");
goto fail;
}
DBG("Scan interface types (astribank has %d interfaces)\n", astribank->dev->config->bNumInterfaces);
for(i = 0; i < sizeof(interface_types)/sizeof(interface_types[0]); i++) {
if(match_interface(astribank, &interface_types[i])) {
DBG("Identified[%d]: interfaces=%d endpoints=%d: \"%s\"\n",
i,
interface_types[i].num_interfaces,
interface_types[i].num_endpoints,
interface_types[i].name);
astribank->fwtype = &interface_types[i];
goto found;
}
}
ERR("Didn't find suitable device\n");
fail:
free(astribank);
return NULL;
found:
if(!astribank_init(astribank))
goto fail;
astribank->tx_sequenceno = 1;
return astribank;
}
/*
* MP device handling
*/
void show_astribank_info(const struct astribank_device *astribank)
{
struct usb_device_descriptor *dev_desc;
struct usb_device *dev;
assert(astribank != NULL);
dev = astribank->dev;
dev_desc = &dev->descriptor;
if(verbose <= LOG_INFO) {
INFO("usb:%s/%s: ID=%04X:%04X [%s / %s / %s]\n",
dev->bus->dirname,
dev->filename,
dev_desc->idVendor,
dev_desc->idProduct,
astribank->iManufacturer,
astribank->iProduct,
astribank->iSerialNumber);
} else {
printf("USB Bus/Device: [%s/%s]\n", dev->bus->dirname, dev->filename);
printf("USB Firmware Type: [%s]\n", astribank->fwtype->name);
printf("USB iManufacturer: [%s]\n", astribank->iManufacturer);
printf("USB iProduct: [%s]\n", astribank->iProduct);
printf("USB iSerialNumber: [%s]\n", astribank->iSerialNumber);
}
}
void astribank_close(struct astribank_device *astribank, int disconnected)
{
assert(astribank != NULL);
if(!astribank->handle)
return; /* Nothing to do */
if(!disconnected) {
if(usb_release_interface(astribank->handle, astribank->fwtype->my_interface_num) != 0) {
ERR("Releasing interface: usb: %s\n", usb_strerror());
}
}
if(usb_close(astribank->handle) != 0) {
ERR("Closing device: usb: %s\n", usb_strerror());
}
astribank->tx_sequenceno = 0;
astribank->handle = NULL;
}
int send_usb(struct astribank_device *astribank, char *buf, int len, int timeout)
{
int ret;
dump_packet(LOG_DEBUG, __FUNCTION__, buf, len);
if(astribank->my_ep_out & USB_ENDPOINT_IN) {
ERR("send_usb called with an input endpoint 0x%x\n", astribank->my_ep_out);
return -EINVAL;
}
ret = usb_bulk_write(astribank->handle, astribank->my_ep_out, buf, len, timeout);
if(ret < 0) {
/*
* If the device was gone, it may be the
* result of renumeration. Ignore it.
*/
if(ret != -ENODEV) {
ERR("bulk_write to endpoint 0x%x failed: (%d) %s\n",
astribank->my_ep_out, ret, usb_strerror());
dump_packet(LOG_ERR, "send_usb[ERR]", buf, len);
exit(2);
} else {
DBG("bulk_write to endpoint 0x%x got ENODEV\n", astribank->my_ep_out);
astribank_close(astribank, 1);
}
return ret;
} else if(ret != len) {
ERR("bulk_write to endpoint 0x%x short write: (%d) %s\n",
astribank->my_ep_out, ret, usb_strerror());
dump_packet(LOG_ERR, "send_usb[ERR]", buf, len);
return -EFAULT;
}
return ret;
}
int recv_usb(struct astribank_device *astribank, char *buf, size_t len, int timeout)
{
int ret;
if(astribank->my_ep_in & USB_ENDPOINT_OUT) {
ERR("recv_usb called with an output endpoint 0x%x\n", astribank->my_ep_in);
return -EINVAL;
}
ret = usb_bulk_read(astribank->handle, astribank->my_ep_in, buf, len, timeout);
if(ret < 0) {
DBG("bulk_read from endpoint 0x%x failed: (%d) %s\n",
astribank->my_ep_in, ret, usb_strerror());
memset(buf, 0, len);
return ret;
}
dump_packet(LOG_DEBUG, __FUNCTION__, buf, ret);
return ret;
}
int flush_read(struct astribank_device *astribank)
{
char tmpbuf[BUFSIZ];
int ret;
DBG("starting...\n");
memset(tmpbuf, 0, BUFSIZ);
ret = recv_usb(astribank, tmpbuf, BUFSIZ, 1);
if(ret < 0 && ret != -ETIMEDOUT) {
ERR("ret=%d\n", ret);
return ret;
} else if(ret > 0) {
DBG("Got %d bytes:\n", ret);
dump_packet(LOG_DEBUG, __FUNCTION__, tmpbuf, ret);
}
return 0;
}
int release_isvalid(uint16_t release)
{
uint8_t rmajor = (release >> 8) & 0xFF;
uint8_t rminor = release & 0xFF;
return (rmajor > 0) &&
(rmajor < 10) &&
(rminor > 0) &&
(rminor < 10);
}
int label_isvalid(const char *label)
{
int len;
int goodlen;
const char GOOD_CHARS[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789"
"-_.";
len = strlen(label);
goodlen = strspn(label, GOOD_CHARS);
if(len > LABEL_SIZE) {
ERR("Label too long (%d > %d)\n", len, LABEL_SIZE);
return 0;
}
if(goodlen != len) {
ERR("Bad character in label (pos=%d)\n", goodlen);
return 0;
}
return 1;
}
int eeprom_fill(struct eeprom_table *eprm,
const char *vendor,
const char *product,
const char *release,
const char *label)
{
uint16_t val;
eprm->source = 0xC0;
eprm->config_byte = 0;
if(vendor) {
val = strtoul(vendor, NULL, 0);
if(!val) {
ERR("Invalid vendor '%s'\n",
vendor);
return -EINVAL;
}
eprm->vendor = val;
}
if(product) {
val = strtoul(product, NULL, 0);
if(!val) {
ERR("Invalid product '%s'\n",
product);
return -EINVAL;
}
eprm->product = val;
}
if(release) {
int release_major = 0;
int release_minor = 0;
uint16_t value;
if(sscanf(release, "%d.%d", &release_major, &release_minor) != 2) {
ERR("Failed to parse release number '%s'\n", release);
return -EINVAL;
}
value = (release_major << 8) | release_minor;
DBG("Parsed release(%d): major=%d, minor=%d\n",
value, release_major, release_minor);
if(!release_isvalid(value)) {
ERR("Invalid release number 0x%X\n", value);
return -EINVAL;
}
eprm->release = value;
}
if(label) {
/* padding */
if(!label_isvalid(label)) {
ERR("Invalid label '%s'\n", label);
return -EINVAL;
}
memset(eprm->label, 0, LABEL_SIZE);
memcpy(eprm->label, label, strlen(label));
}
return 0;
}
int astribank_has_twinstar(struct astribank_device *astribank)
{
struct usb_device_descriptor *dev_desc;
uint16_t product_series;
assert(astribank != NULL);
dev_desc = &astribank->dev->descriptor;
product_series = dev_desc->idProduct;
product_series &= 0xFFF0;
if(product_series == 0x1160) /* New boards */
return 1;
return 0;
}