/* * Written by Oron Peled * 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 #include #include #include #include #include #include #include #include #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; }