summaryrefslogtreecommitdiff
path: root/kernel/xpp/utils/astribank_usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/xpp/utils/astribank_usb.c')
-rw-r--r--kernel/xpp/utils/astribank_usb.c536
1 files changed, 536 insertions, 0 deletions
diff --git a/kernel/xpp/utils/astribank_usb.c b/kernel/xpp/utils/astribank_usb.c
new file mode 100644
index 0000000..088d360
--- /dev/null
+++ b/kernel/xpp/utils/astribank_usb.c
@@ -0,0 +1,536 @@
+/*
+ * 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 <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;
+ INFO("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)
+{
+ assert(astribank != NULL);
+ 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;
+}
+