/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include "csr.h" #define USB_TYPE_CLASS (0x01 << 5) #define USB_RECIP_DEVICE 0x00 #define USB_ENDPOINT_IN 0x80 #define USB_ENDPOINT_OUT 0x00 struct usbfs_ctrltransfer { uint8_t bmRequestType; uint8_t bRequest; uint16_t wValue; uint16_t wIndex; uint16_t wLength; uint32_t timeout; /* in milliseconds */ void *data; /* pointer to data */ }; struct usbfs_bulktransfer { unsigned int ep; unsigned int len; unsigned int timeout; /* in milliseconds */ void *data; /* pointer to data */ }; #define USBFS_IOCTL_CONTROL _IOWR('U', 0, struct usbfs_ctrltransfer) #define USBFS_IOCTL_BULK _IOWR('U', 2, struct usbfs_bulktransfer) #define USBFS_IOCTL_CLAIMINTF _IOR('U', 15, unsigned int) #define USBFS_IOCTL_RELEASEINTF _IOR('U', 16, unsigned int) static int read_value(const char *name, const char *attr, const char *format) { char path[PATH_MAX]; FILE *file; int n, value; snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/%s", name, attr); file = fopen(path, "r"); if (!file) return -1; n = fscanf(file, format, &value); if (n != 1) return -1; return value; } static char *check_device(const char *name) { char path[PATH_MAX]; int busnum, devnum, vendor, product; busnum = read_value(name, "busnum", "%d"); if (busnum < 0) return NULL; devnum = read_value(name, "devnum", "%d"); if (devnum < 0) return NULL; snprintf(path, sizeof(path), "/dev/bus/usb/%03u/%03u", busnum, devnum); vendor = read_value(name, "idVendor", "%04x"); if (vendor < 0) return NULL; product = read_value(name, "idProduct", "%04x"); if (product < 0) return NULL; if (vendor != 0x0a12 || product != 0x0001) return NULL; return strdup(path); } static char *find_device(void) { char *path = NULL; DIR *dir; dir = opendir("/sys/bus/usb/devices"); if (!dir) return NULL; while (1) { struct dirent *d; d = readdir(dir); if (!d) break; if ((!isdigit(d->d_name[0]) && strncmp(d->d_name, "usb", 3)) || strchr(d->d_name, ':')) continue; path = check_device(d->d_name); if (path) break; } closedir(dir); return path; } static uint16_t seqnum = 0x0000; static int handle = -1; int csr_open_usb(char *device) { int interface = 0; char *path; path = find_device(); if (!path) { fprintf(stderr, "Device not available\n"); return -1; } handle = open(path, O_RDWR, O_CLOEXEC | O_NONBLOCK); free(path); if (handle < 0) { fprintf(stderr, "Can't open device: %s (%d)\n", strerror(errno), errno); return -1; } if (ioctl(handle, USBFS_IOCTL_CLAIMINTF, &interface) < 0) { fprintf(stderr, "Can't claim interface: %s (%d)\n", strerror(errno), errno); close(handle); handle = -1; return -1; } return 0; } static int control_write(int fd, void *data, unsigned short size) { struct usbfs_ctrltransfer transfer; transfer.bmRequestType = USB_TYPE_CLASS | USB_ENDPOINT_OUT | USB_RECIP_DEVICE; transfer.bRequest = 0; transfer.wValue = 0; transfer.wIndex = 0; transfer.wLength = size, transfer.timeout = 2000; transfer.data = data; if (ioctl(fd, USBFS_IOCTL_CONTROL, &transfer) < 0) { fprintf(stderr, "Control transfer failed: %s (%d)\n", strerror(errno), errno); return -1; } return 0; } static int interrupt_read(int fd, unsigned char endpoint, void *data, unsigned short size) { struct usbfs_bulktransfer transfer; transfer.ep = endpoint; transfer.len = size, transfer.timeout = 20; transfer.data = data; return ioctl(fd, USBFS_IOCTL_BULK, &transfer); } static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length) { unsigned char cp[254], rp[254]; uint8_t cmd[10]; uint16_t size; int len, offset = 0; size = (length < 8) ? 9 : ((length + 1) / 2) + 5; cmd[0] = command & 0xff; cmd[1] = command >> 8; cmd[2] = size & 0xff; cmd[3] = size >> 8; cmd[4] = seqnum & 0xff; cmd[5] = seqnum >> 8; cmd[6] = varid & 0xff; cmd[7] = varid >> 8; cmd[8] = 0x00; cmd[9] = 0x00; memset(cp, 0, sizeof(cp)); cp[0] = 0x00; cp[1] = 0xfc; cp[2] = (size * 2) + 1; cp[3] = 0xc2; memcpy(cp + 4, cmd, sizeof(cmd)); memcpy(cp + 14, value, length); interrupt_read(handle, USB_ENDPOINT_IN | 0x01, rp, sizeof(rp)); control_write(handle, cp, (size * 2) + 4); switch (varid) { case CSR_VARID_COLD_RESET: case CSR_VARID_WARM_RESET: case CSR_VARID_COLD_HALT: case CSR_VARID_WARM_HALT: return 0; } do { len = interrupt_read(handle, USB_ENDPOINT_IN | 0x01, rp + offset, sizeof(rp) - offset); if (len < 0) break; offset += len; } while (len > 0); if (rp[0] != 0xff || rp[2] != 0xc2) { errno = EIO; return -1; } if ((rp[11] + (rp[12] << 8)) != 0) { errno = ENXIO; return -1; } memcpy(value, rp + 13, length); return 0; } int csr_read_usb(uint16_t varid, uint8_t *value, uint16_t length) { return do_command(0x0000, seqnum++, varid, value, length); } int csr_write_usb(uint16_t varid, uint8_t *value, uint16_t length) { return do_command(0x0002, seqnum++, varid, value, length); } void csr_close_usb(void) { int interface = 0; ioctl(handle, USBFS_IOCTL_RELEASEINTF, &interface); close(handle); handle = -1; }