Edward O'Callaghan has uploaded this change for review. ( https://review.coreboot.org/c/flashrom/+/38209 )
Change subject: raiden_debug: Upstream ChromiumOS servo debug board prog ......................................................................
raiden_debug: Upstream ChromiumOS servo debug board prog
Initial check-in of the Raiden debugger programmer.
Squash in, usb_device: needed for raiden_debug prog
BUG=b:143389556 BRANCH=none TEST=builds
Change-Id: Ifad273a708acea4de797a0808be58960635a8864 Signed-off-by: Edward O'Callaghan quasisec@google.com --- M Makefile M flashrom.c M meson.build M meson_options.txt M programmer.h A raiden_debug_spi.c A usb_device.c A usb_device.h 8 files changed, 977 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/09/38209/1
diff --git a/Makefile b/Makefile index 7242b09..e8ca7a8 100644 --- a/Makefile +++ b/Makefile @@ -226,6 +226,11 @@ else override CONFIG_RAYER_SPI = no endif +ifeq ($(CONFIG_RAIDEN), yes) +UNSUPPORTED_FEATURES += CONFIG_RAIDEN=yes +else +override CONFIG_RAIDEN = no +endif ifeq ($(CONFIG_NIC3COM), yes) UNSUPPORTED_FEATURES += CONFIG_NIC3COM=yes else @@ -607,6 +612,9 @@ # RayeR SPIPGM hardware support CONFIG_RAYER_SPI ?= yes
+# ChromiumOS servo DUT debug board hardware support +CONFIG_RAIDEN ?= yes + # PonyProg2000 SPI hardware support CONFIG_PONY_SPI ?= yes
@@ -817,6 +825,11 @@ NEED_RAW_ACCESS += CONFIG_RAYER_SPI endif
+ifeq ($(CONFIG_RAIDEN), yes) +FEATURE_CFLAGS += -D'CONFIG_RAIDEN=1' +PROGRAMMER_OBJS += raiden_debug_spi.o usb_device.o +endif + ifeq ($(CONFIG_PONY_SPI), yes) FEATURE_CFLAGS += -D'CONFIG_PONY_SPI=1' PROGRAMMER_OBJS += pony_spi.o diff --git a/flashrom.c b/flashrom.c index e540027..e541b68 100644 --- a/flashrom.c +++ b/flashrom.c @@ -133,6 +133,18 @@ }, #endif
+#if CONFIG_RAIDEN == 1 + { + .name = "raiden_debug", + .type = USB, + .devs.note = "All programmer devices speaking the raiden protocol\n", + .init = raiden_debug_spi_init, + .map_flash_region = fallback_map, + .unmap_flash_region = fallback_unmap, + .delay = internal_delay, + }, +#endif + #if CONFIG_DRKAISER == 1 { .name = "drkaiser", diff --git a/meson.build b/meson.build index 375089c..88b14cf 100644 --- a/meson.build +++ b/meson.build @@ -42,6 +42,7 @@ config_dummy = get_option('config_dummy') config_ft2232_spi = get_option('config_ft2232_spi') config_gfxnvidia = get_option('config_gfxnvidia') +config_raiden = get_option('config_raiden') config_internal = get_option('config_internal') config_it8212 = get_option('config_it8212') config_linux_mtd = get_option('config_linux_mtd') @@ -104,6 +105,7 @@ config_atavia = false config_drkaiser = false config_gfxnvidia = false + config_raiden = false config_internal = false config_it8212 = false config_nic3com = false @@ -170,6 +172,11 @@ srcs += 'gfxnvidia.c' cargs += '-DCONFIG_GFXNVIDIA=1' endif +if config_raiden + srcs += 'raiden_debug_spi.c' + srcs += 'usb_device.c' + cargs += '-DCONFIG_RAIDEN=1' +endif if config_internal srcs += 'board_enable.c' srcs += 'cbtable.c' diff --git a/meson_options.txt b/meson_options.txt index b7633d0..4334814 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -13,6 +13,7 @@ option('config_dummy', type : 'boolean', value : true, description : 'dummy tracing') option('config_ft2232_spi', type : 'boolean', value : true, description : 'FT2232 SPI dongles') option('config_gfxnvidia', type : 'boolean', value : true, description : 'NVIDIA graphics cards') +option('config_raiden', type : 'boolean', value : true, description : 'ChromiumOS Servo DUT debug board') option('config_internal', type : 'boolean', value : true, description : 'internal/onboard') option('config_internal_dmi', type : 'boolean', value : true, description : 'Use internal DMI parser') option('config_it8212', type : 'boolean', value : true, description : 'ITE IT8212F PATA') diff --git a/programmer.h b/programmer.h index 3cf53b9..08500c6 100644 --- a/programmer.h +++ b/programmer.h @@ -43,6 +43,9 @@ #if CONFIG_GFXNVIDIA == 1 PROGRAMMER_GFXNVIDIA, #endif +#if CONFIG_RAIDEN == 1 + PROGRAMMER_RAIDEN, +#endif #if CONFIG_DRKAISER == 1 PROGRAMMER_DRKAISER, #endif @@ -401,6 +404,11 @@ extern const struct dev_entry gfx_nvidia[]; #endif
+/* raiden_debug_spi.c */ +#if CONFIG_RAIDEN == 1 +int raiden_debug_spi_init(void); +#endif + /* drkaiser.c */ #if CONFIG_DRKAISER == 1 int drkaiser_init(void); diff --git a/raiden_debug_spi.c b/raiden_debug_spi.c new file mode 100644 index 0000000..9af0f7d --- /dev/null +++ b/raiden_debug_spi.c @@ -0,0 +1,369 @@ +/* + * This file is part of the flashrom project. + * + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + */ + +/* + * This SPI flash programming interface is designed to talk to a Chromium OS + * device over a Raiden USB connection. The USB connection is routed to a + * microcontroller running an image compiled from: + * + * https://chromium.googlesource.com/chromiumos/platform/ec + * + * The protocol for the USB-SPI bridge is documented in the following file in + * that respository: + * + * chip/stm32/usb_spi.c + */ + +#include "programmer.h" +#include "spi.h" +#include "usb_device.h" + +#include <libusb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define GOOGLE_VID 0x18D1 +#define GOOGLE_RAIDEN_SPI_SUBCLASS 0x51 +#define GOOGLE_RAIDEN_SPI_PROTOCOL 0x01 + +enum raiden_debug_spi_request { + RAIDEN_DEBUG_SPI_REQ_ENABLE = 0x0000, + RAIDEN_DEBUG_SPI_REQ_DISABLE = 0x0001, + RAIDEN_DEBUG_SPI_REQ_ENABLE_AP = 0x0002, + RAIDEN_DEBUG_SPI_REQ_ENABLE_EC = 0x0003, +}; + +#define PACKET_HEADER_SIZE 2 +#define MAX_PACKET_SIZE 64 + +/* + * This timeout is so large because the Raiden SPI timeout is 800ms. + */ +#define TRANSFER_TIMEOUT_MS 1000 + +struct usb_device *device = NULL; +uint8_t in_endpoint = 0; +uint8_t out_endpoint = 0; + +static int send_command(struct flashctx *flash, + unsigned int write_count, + unsigned int read_count, + const unsigned char *write_buffer, + unsigned char *read_buffer) +{ + uint8_t buffer[MAX_PACKET_SIZE]; + int transferred; + + if (write_count > MAX_PACKET_SIZE - PACKET_HEADER_SIZE) { + msg_perr("Raiden: invalid write_count of %d\n", write_count); + return SPI_INVALID_LENGTH; + } + + if (read_count > MAX_PACKET_SIZE - PACKET_HEADER_SIZE) { + msg_perr("Raiden: invalid read_count of %d\n", read_count); + return SPI_INVALID_LENGTH; + } + + buffer[0] = write_count; + buffer[1] = read_count; + + memcpy(buffer + PACKET_HEADER_SIZE, write_buffer, write_count); + + CHECK(LIBUSB(libusb_bulk_transfer(device->handle, + out_endpoint, + buffer, + write_count + PACKET_HEADER_SIZE, + &transferred, + TRANSFER_TIMEOUT_MS)), + "Raiden: OUT transfer failed\n" + " write_count = %d\n" + " read_count = %d\n", + write_count, + read_count); + + if ((unsigned) transferred != write_count + PACKET_HEADER_SIZE) { + msg_perr("Raiden: Write failure (wrote %d, expected %d)\n", + transferred, write_count + PACKET_HEADER_SIZE); + return 0x10001; + } + + CHECK(LIBUSB(libusb_bulk_transfer(device->handle, + in_endpoint, + buffer, + read_count + PACKET_HEADER_SIZE, + &transferred, + TRANSFER_TIMEOUT_MS)), + "Raiden: IN transfer failed\n" + " write_count = %d\n" + " read_count = %d\n", + write_count, + read_count); + + if ((unsigned) transferred != read_count + PACKET_HEADER_SIZE) { + msg_perr("Raiden: Read failure (read %d, expected %d)\n", + transferred, read_count + PACKET_HEADER_SIZE); + return 0x10002; + } + + memcpy(read_buffer, buffer + PACKET_HEADER_SIZE, read_count); + + return buffer[0] | (buffer[1] << 8); +} + +/* + * Unfortunately there doesn't seem to be a way to specify the maximum number + * of bytes that your SPI device can read/write, these values are the maximum + * data chunk size that flashrom will package up with an additional five bytes + * of command for the flash device, resulting in a 62 byte packet, that we then + * add two bytes to in either direction, making our way up to the 64 byte + * maximum USB packet size for the device. + * + * The largest command that flashrom generates is the byte program command, so + * we use that command header maximum size here. + */ +#define MAX_DATA_SIZE (MAX_PACKET_SIZE - \ + PACKET_HEADER_SIZE - \ + JEDEC_BYTE_PROGRAM_OUTSIZE) + +static const struct spi_master spi_master_raiden_debug = { + .features = SPI_MASTER_4BA, + .max_data_read = MAX_DATA_SIZE, + .max_data_write = MAX_DATA_SIZE, + .command = send_command, + .multicommand = default_spi_send_multicommand, + .read = default_spi_read, + .write_256 = default_spi_write_256, +}; + +static int match_endpoint(struct libusb_endpoint_descriptor const *descriptor, + enum libusb_endpoint_direction direction) +{ + return (((descriptor->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == + direction) && + ((descriptor->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == + LIBUSB_TRANSFER_TYPE_BULK)); +} + +static int find_endpoints(struct usb_device *dev, uint8_t *in_ep, uint8_t *out_ep) +{ + int i; + int in_count = 0; + int out_count = 0; + + for (i = 0; i < dev->interface_descriptor->bNumEndpoints; i++) { + struct libusb_endpoint_descriptor const *endpoint = + &dev->interface_descriptor->endpoint[i]; + + if (match_endpoint(endpoint, LIBUSB_ENDPOINT_IN)) { + in_count++; + *in_ep = endpoint->bEndpointAddress; + } else if (match_endpoint(endpoint, LIBUSB_ENDPOINT_OUT)) { + out_count++; + *out_ep = endpoint->bEndpointAddress; + } + } + + if (in_count != 1 || out_count != 1) { + msg_perr("Raiden: Failed to find one IN and one OUT endpoint\n" + " found %d IN and %d OUT endpoints\n", + in_count, + out_count); + return 1; + } + + msg_pdbg("Raiden: Found IN endpoint = 0x%02x\n", *in_ep); + msg_pdbg("Raiden: Found OUT endpoint = 0x%02x\n", *out_ep); + + return 0; +} + +static int shutdown(void * data) +{ + CHECK(LIBUSB(libusb_control_transfer( + device->handle, + LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_INTERFACE, + RAIDEN_DEBUG_SPI_REQ_DISABLE, + 0, + device->interface_descriptor->bInterfaceNumber, + NULL, + 0, + TRANSFER_TIMEOUT_MS)), + "Raiden: Failed to disable SPI bridge\n"); + + usb_device_free(device); + + device = NULL; + libusb_exit(NULL); + + return 0; +} + +static int get_target() +{ + int request_enable = RAIDEN_DEBUG_SPI_REQ_ENABLE; + + char *target_str = extract_programmer_param("target"); + if (target_str) { + if (!strcasecmp(target_str, "ap")) + request_enable = RAIDEN_DEBUG_SPI_REQ_ENABLE_AP; + else if (!strcasecmp(target_str, "ec")) + request_enable = RAIDEN_DEBUG_SPI_REQ_ENABLE_EC; + else { + msg_perr("Invalid target: %s\n", target_str); + request_enable = -1; + } + } + free(target_str); + + return request_enable; +} + +static void free_dev_list(struct usb_device **dev_lst) +{ + struct usb_device *dev = *dev_lst; + /* free devices we don't care about */ + dev = dev->next; + while (dev) + dev = usb_device_free(dev); +} + +int raiden_debug_spi_init(void) +{ + struct usb_match match; + char *serial = extract_programmer_param("serial"); + struct usb_device *current; + int found = 0; + + int request_enable = get_target(); + if (request_enable < 0) + return 1; + + usb_match_init(&match); + + usb_match_value_default(&match.vid, GOOGLE_VID); + usb_match_value_default(&match.class, LIBUSB_CLASS_VENDOR_SPEC); + usb_match_value_default(&match.subclass, GOOGLE_RAIDEN_SPI_SUBCLASS); + usb_match_value_default(&match.protocol, GOOGLE_RAIDEN_SPI_PROTOCOL); + + CHECK(LIBUSB(libusb_init(NULL)), "Raiden: libusb_init failed\n"); + + CHECK(usb_device_find(&match, ¤t), + "Raiden: Failed to find devices\n"); + + while (current) { + device = current; + + if (find_endpoints(device, &in_endpoint, &out_endpoint)) { + msg_pdbg("Raiden: Failed to find valid endpoints on device"); + usb_device_show(" ", current); + goto loop_end; + } + + if (usb_device_claim(device)) { + msg_pdbg("Raiden: Failed to claim USB device"); + usb_device_show(" ", current); + goto loop_end; + } + + if (!serial) { + found = 1; + goto loop_end; + } else { + unsigned char dev_serial[32]; + struct libusb_device_descriptor descriptor; + int rc; + + memset(dev_serial, 0, sizeof(dev_serial)); + + if (libusb_get_device_descriptor(device->device, &descriptor)) { + msg_pdbg("USB: Failed to get device descriptor.\n"); + goto loop_end; + } + + rc = libusb_get_string_descriptor_ascii(device->handle, + descriptor.iSerialNumber, + dev_serial, + sizeof(dev_serial)); + if (rc < 0) { + LIBUSB(rc); + } else { + if (strcmp(serial, (char *)dev_serial)) { + msg_pdbg("Raiden: Serial number %s did not match device", serial); + usb_device_show(" ", current); + } else { + msg_pinfo("Raiden: Serial number %s matched device", serial); + usb_device_show(" ", current); + found = 1; + } + } + } + +loop_end: + if (found) + break; + else + current = usb_device_free(current); + } + + if (!device || !found) { + msg_perr("Raiden: No usable device found.\n"); + return 1; + } + + free_dev_list(¤t); + + CHECK(LIBUSB(libusb_control_transfer( + device->handle, + LIBUSB_ENDPOINT_OUT | + LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_INTERFACE, + request_enable, + 0, + device->interface_descriptor->bInterfaceNumber, + NULL, + 0, + TRANSFER_TIMEOUT_MS)), + "Raiden: Failed to enable SPI bridge\n"); + + register_spi_master(&spi_master_raiden_debug); + register_shutdown(shutdown, NULL); + + return 0; +} diff --git a/usb_device.c b/usb_device.c new file mode 100644 index 0000000..0909694 --- /dev/null +++ b/usb_device.c @@ -0,0 +1,366 @@ +/* + * This file is part of the flashrom project. + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + */ + +#include "programmer.h" +#include "spi.h" +#include "usb_device.h" + +#include <assert.h> +#include <libusb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * Possibly extract a programmer parameter and use it to initialize the given + * match value structure. + */ +static void usb_match_value_init(struct usb_match_value *match, + char const *parameter) +{ + char *string = extract_programmer_param(parameter); + + match->name = parameter; + + if (string) { + match->set = 1; + match->value = strtol(string, NULL, 0); + } else { + match->set = 0; + } + + free(string); +} + +#define USB_MATCH_VALUE_INIT(NAME) \ + usb_match_value_init(&match->NAME, #NAME) + +void usb_match_init(struct usb_match *match) +{ + USB_MATCH_VALUE_INIT(vid); + USB_MATCH_VALUE_INIT(pid); + USB_MATCH_VALUE_INIT(bus); + USB_MATCH_VALUE_INIT(address); + USB_MATCH_VALUE_INIT(config); + USB_MATCH_VALUE_INIT(interface); + USB_MATCH_VALUE_INIT(altsetting); + USB_MATCH_VALUE_INIT(class); + USB_MATCH_VALUE_INIT(subclass); + USB_MATCH_VALUE_INIT(protocol); +} + +void usb_match_value_default(struct usb_match_value *value, + long int default_value) +{ + if (value->set) + return; + + value->set = 1; + value->value = default_value; +} + +/* + * Match the value against a possible user supplied parameter. + * + * Return: + * 0: The user supplied the given parameter and it did not match the value. + * 1: Either the user didn't supply the parameter, or they did and it + * matches the given value. + */ +static int check_match(struct usb_match_value const *match_value, int value) +{ + int reject = match_value->set && (match_value->value != value); + + if (reject) + msg_pdbg("USB: Rejecting device because %s = %d != %d\n", + match_value->name, + value, + match_value->value); + + return !reject; +} + +/* + * Allocate a copy of device and add it to the head of the devices list. + */ +static void add_device(struct usb_device *device, + struct usb_device **devices) +{ + struct usb_device *copy = malloc(sizeof(struct usb_device)); + + assert(copy != NULL); + + *copy = *device; + + copy->next = *devices; + *devices = copy; + + libusb_ref_device(copy->device); +} + +/* + * Look through the interfaces of the current device config for a match. Stop + * looking after the first valid match is found. + * + * Return: + * 0: No matching interface was found. + * 1: A complete match was found and added to the devices list. + */ +static int find_interface(struct usb_match const *match, + struct usb_device *current, + struct usb_device **devices) +{ + int i, j; + + for (i = 0; i < current->config_descriptor->bNumInterfaces; ++i) { + struct libusb_interface const *interface; + + interface = ¤t->config_descriptor->interface[i]; + + for (j = 0; j < interface->num_altsetting; ++j) { + struct libusb_interface_descriptor const *descriptor; + + descriptor = &interface->altsetting[j]; + + if (check_match(&match->interface, + descriptor->bInterfaceNumber) && + check_match(&match->altsetting, + descriptor->bAlternateSetting) && + check_match(&match->class, + descriptor->bInterfaceClass) && + check_match(&match->subclass, + descriptor->bInterfaceSubClass) && + check_match(&match->protocol, + descriptor->bInterfaceProtocol)) { + current->interface_descriptor = descriptor; + add_device(current, devices); + msg_pdbg("USB: Found matching device\n"); + return 1; + } + } + } + + return 0; +} + +/* + * Look through the configs of the current device for a match. Stop looking + * after the first valid match is found. + * + * Return: + * 0: All configurations successfully checked, one may have been added to + * the list. + * non-zero: There was a failure while checking for a match. + */ +static int find_config(struct usb_match const *match, + struct usb_device *current, + struct libusb_device_descriptor const *device_descriptor, + struct usb_device **devices) +{ + int i; + + for (i = 0; i < device_descriptor->bNumConfigurations; ++i) { + CHECK(LIBUSB(libusb_get_config_descriptor( + current->device, + i, + ¤t->config_descriptor)), + "USB: Failed to get config descriptor"); + + if (check_match(&match->config, + current->config_descriptor-> + bConfigurationValue) && + find_interface(match, current, devices)) + break; + + libusb_free_config_descriptor(current->config_descriptor); + } + + return 0; +} + +int usb_device_find(struct usb_match const *match, struct usb_device **devices) +{ + libusb_device **list; + size_t count; + size_t i; + + *devices = NULL; + + CHECK(LIBUSB(count = libusb_get_device_list(NULL, &list)), + "USB: Failed to get device list"); + + for (i = 0; i < count; ++i) { + struct libusb_device_descriptor descriptor; + struct usb_device current = { + .device = list[i], + .handle = NULL, + .next = NULL, + }; + + uint8_t bus = libusb_get_bus_number(list[i]); + uint8_t address = libusb_get_device_address(list[i]); + + msg_pdbg("USB: Inspecting device (Bus %d, Address %d)\n", + bus, + address); + + CHECK(LIBUSB(libusb_get_device_descriptor(list[i], + &descriptor)), + "USB: Failed to get device descriptor"); + + if (check_match(&match->vid, descriptor.idVendor) && + check_match(&match->pid, descriptor.idProduct) && + check_match(&match->bus, bus) && + check_match(&match->address, address)) + CHECK(find_config(match, + ¤t, + &descriptor, + devices), + "USB: Failed to find config"); + } + + libusb_free_device_list(list, 1); + + return (*devices == NULL); +} + +/* + * If the underlying libusb device is not open, open it. + * + * Return: + * 0: The device was already open or was successfully opened. + * non-zero: There was a failure while opening the device. + */ +static int usb_device_open(struct usb_device *device) +{ + if (device->handle == NULL) + CHECK(LIBUSB(libusb_open(device->device, &device->handle)), + "USB: Failed to open device\n"); + + return 0; +} + +int usb_device_show(char const *prefix, struct usb_device *device) +{ + struct libusb_device_descriptor descriptor; + unsigned char product[256]; + + CHECK(usb_device_open(device), "USB: Failed to open device\n"); + + CHECK(LIBUSB(libusb_get_device_descriptor(device->device, &descriptor)), + "USB: Failed to get device descriptor\n"); + + CHECK(LIBUSB(libusb_get_string_descriptor_ascii( + device->handle, + descriptor.iProduct, + product, + sizeof(product))), + "USB: Failed to get device product string\n"); + + product[255] = '\0'; + + msg_perr("%sbus=0x%02x,address=0x%02x | %s\n", + prefix, + libusb_get_bus_number(device->device), + libusb_get_device_address(device->device), + product); + + return 0; +} + +int usb_device_claim(struct usb_device *device) +{ + int current_config; + + CHECK(usb_device_open(device), "USB: Failed to open device\n"); + + CHECK(LIBUSB(libusb_get_configuration(device->handle, + ¤t_config)), + "USB: Failed to get current device configuration\n"); + + if (current_config != device->config_descriptor->bConfigurationValue) + CHECK(LIBUSB(libusb_set_configuration( + device->handle, + device-> + config_descriptor-> + bConfigurationValue)), + "USB: Failed to set new configuration from %d to %d\n", + current_config, + device->config_descriptor->bConfigurationValue); + + CHECK(LIBUSB(libusb_set_auto_detach_kernel_driver(device->handle, 1)), + "USB: Failed to enable auto kernel driver detach\n"); + + CHECK(LIBUSB(libusb_claim_interface(device->handle, + device-> + interface_descriptor-> + bInterfaceNumber)), + "USB: Could not claim device interface %d\n", + device->interface_descriptor->bInterfaceNumber); + + if (device->interface_descriptor->bAlternateSetting != 0) + CHECK(LIBUSB(libusb_set_interface_alt_setting( + device->handle, + device-> + interface_descriptor-> + bInterfaceNumber, + device-> + interface_descriptor-> + bAlternateSetting)), + "USB: Failed to set alternate setting %d\n", + device->interface_descriptor->bAlternateSetting); + + return 0; +} + +struct usb_device *usb_device_free(struct usb_device *device) +{ + struct usb_device *next = device->next; + + if (device->handle != NULL) + libusb_close(device->handle); + + /* + * This unref balances the ref added in the add_device function. + */ + libusb_unref_device(device->device); + libusb_free_config_descriptor(device->config_descriptor); + + free(device); + + return next; +} diff --git a/usb_device.h b/usb_device.h new file mode 100644 index 0000000..46d2458 --- /dev/null +++ b/usb_device.h @@ -0,0 +1,201 @@ +/* + * This file is part of the flashrom project. + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + */ + +/* + * USB device matching framework + * + * This can be used to match a USB device by a number of different parameters. + * The parameters can be passed on the command line and defaults can be set + * by the programmer. + */ + +#include <libusb.h> +#include <stdint.h> + +/* + * Check Macro + * + * The check macro simplifies common return error code checking logic. If the + * expression does not evaluate to zero then the string error is printed and + * the expressions result is imediately returned, else the result is available + * as the value of the CHECK expression statement. + */ + +#define CHECK(expression, string...) \ + ({ \ + int error__ = (expression); \ + \ + if (error__ != 0) { \ + msg_perr(string); \ + return error__; \ + } \ + \ + error__; \ + }) + +/* + * The LIBUSB macro converts a libusb failure code into an error code that + * flashrom recognizes. It also displays additional libusb specific + * information about the failure. + */ +#define LIBUSB(expression) \ + ({ \ + int libusb_error__ = (expression); \ + \ + if (libusb_error__ < 0) { \ + msg_perr("libusb error: %s:%d %s\n", \ + __FILE__, \ + __LINE__, \ + libusb_error_name(libusb_error__)); \ + libusb_error__ = 0x20000 | -libusb_error__; \ + } else { \ + libusb_error__ = 0; \ + } \ + \ + libusb_error__; \ + }) + +/* + * A USB match and associated value struct are used to encode the information + * about a device against which we wish to match. If the value of a + * usb_match_value has been set then a device must match that value. The name + * of the usb_match_value is used to fetch the programmer parameter from the + * flashrom command line and is the same as the name of the corresponding + * field in usb_match. + */ +struct usb_match_value { + char const *name; + int value; + int set; +}; + +struct usb_match { + struct usb_match_value bus; + struct usb_match_value address; + struct usb_match_value vid; + struct usb_match_value pid; + struct usb_match_value serial; + struct usb_match_value config; + struct usb_match_value interface; + struct usb_match_value altsetting; + struct usb_match_value class; + struct usb_match_value subclass; + struct usb_match_value protocol; +}; + +/* + * Initialize a usb_match structure so that each value's name matches the + * values name in the usb_match structure (so bus.name == "bus"...), and + * look for each value in the flashrom command line via + * extract_programmer_param. If the value is found convert it to an integer + * using strtol, accepting hex, decimal and octal encoding. + */ +void usb_match_init(struct usb_match *match); + +/* + * Add a default value to a usb_match_value. This must be done after calling + * usb_match_init. If usb_match_init already set the value of a usb_match_value + * we do nothing, otherwise set the value to default_value. This ensures that + * parameters passed on the command line override defaults. + */ +void usb_match_value_default(struct usb_match_value *match, + long int default_value); + +/* + * The usb_device structure is an entry in a linked list of devices that were + * matched by usb_device_find. + */ +struct usb_device { + struct libusb_device *device; + struct libusb_config_descriptor *config_descriptor; + struct libusb_interface_descriptor const *interface_descriptor; + + /* + * Initially NULL, the libusb_device_handle is only valid once the + * usb_device has been successfully passed to usb_device_show or + * usb_device_claim. + */ + struct libusb_device_handle *handle; + + /* + * Link to next device, or NULL + */ + struct usb_device *next; +}; + +/* + * Find and return a list of all compatible devices. Each device is added to + * the list with its first valid configuration and interface. If an alternate + * configuration (config, interface, altsetting...) is desired the specifics + * can be supplied as programmer parameters. + * + * Return: + * 0: At least one matching device was found. + * 1: No matching devices were found. + */ +int usb_device_find(struct usb_match const *match, struct usb_device **devices); + +/* + * Display the devices bus and address as well as its product string. The + * underlying libusb device is opened if it is not already open. + * + * Return: + * 0: The device information was displayed. + * non-zero: There was a failure while displaying the device information. + */ +int usb_device_show(char const *prefix, struct usb_device *device); + +/* + * Open the underlying libusb device, set its config, claim the interface and + * select the correct alternate interface. + * + * Return: + * 0: The device was successfully claimed. + * non-zero: There was a failure while trying to claim the device. + */ +int usb_device_claim(struct usb_device *device); + +/* + * Free a usb_device structure. + * + * This ensures that the libusb device is closed and that all allocated + * handles and descriptors are freed. + * + * Return: + * The next device in the device list. + */ +struct usb_device *usb_device_free(struct usb_device *device);