The 96Boards Developerbox (a.k.a. Synquacer E-series) provides a CP2102 debug UART with its GPIO pins hooked up to an SPI NOR FLASH. The circuit is intended to provide emergency de-brick functions without requiring any additional tools (such as a JTAG or SPI programmer). This was *expected* to be very slow (and it *is* very slow) but CP2102 is much cheaper than a full dual channel USB comms chip. It is only really practical as an emergency debrick tool/
This patch provides an early implementation of a programmer driver. It is a true RFC... I'd like to tidy it up to upstream and don't want to waste effort tweaking it if it ends up objectionable to the maintainers. As shown here it appears to work reasonably well: probe works and it is able to reliabily read flash (performance is roughly the same as a 2400 baud modem, or ~70 minutes per megabyte).
Questions are:
1. Currently the driver is structured as a simple Developerbox driver rather than a CP210x driver and configuration for Developerbox. I'm happy to layer it properly (via register_cp210x_bitbang_master() or something similar) but equally am happy to leave that as refactoring for a future adventurer (I'm not sure encouraging the use of CP210x as a programmer would be deemed desirable). Which is better?
2. Take a look at CP2014_FAST... This is clearly a layering violation but the performance boost of combining the SCK and MOSI write is very useful. Are there any objections to refactoring the bitbang interfaces to allow optimized bitbanging?
3. Currently I've copied get_device_by_vid_pid_number() from the dediprog code base. I plan to extend it to check the serial number (instead of using the enumeration order). Is there any other programmer I should look at to refactor this into library code or should I just customise this function for CP210x and move on?
Change-Id: I2547a96c1a2259ad0d52cd4b6ef42261b37cccf3 Signed-off-by: Daniel Thompson daniel.thompson@linaro.org --- Makefile | 24 +++- developerbox_spi.c | 296 +++++++++++++++++++++++++++++++++++++++++++++ flashrom.c | 12 ++ programmer.h | 12 ++ 4 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 developerbox_spi.c
diff --git a/Makefile b/Makefile index 943d88d76f77..17cc8d53f139 100644 --- a/Makefile +++ b/Makefile @@ -153,12 +153,17 @@ UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes else override CONFIG_PONY_SPI = no endif -# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported under DOS (missing USB support). +# Dediprog, Developerbox, USB-Blaster, PICkit2, CH341A and FT2232 are not supported under DOS (missing USB support). ifeq ($(CONFIG_DEDIPROG), yes) UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes else override CONFIG_DEDIPROG = no endif +ifeq ($(CONFIG_DEVELOPERBOX), yes) +UNSUPPORTED_FEATURES += CONFIG_DEVELOPERBOX=yes +else +override CONFIG_DEVELOPERBOX = no +endif ifeq ($(CONFIG_FT2232_SPI), yes) UNSUPPORTED_FEATURES += CONFIG_FT2232_SPI=yes else @@ -310,12 +315,17 @@ UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes else override CONFIG_PONY_SPI = no endif -# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported with libpayload (missing libusb support). +# Dediprog, Developerbox, USB-Blaster, PICkit2, CH341A and FT2232 are not supported with libpayload (missing libusb support). ifeq ($(CONFIG_DEDIPROG), yes) UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes else override CONFIG_DEDIPROG = no endif +ifeq ($(CONFIG_DEVELOPERBOX), yes) +UNSUPPORTED_FEATURES += CONFIG_DEVELOPERBOX=yes +else +override CONFIG_DEVELOPERBOX = no +endif ifeq ($(CONFIG_FT2232_SPI), yes) UNSUPPORTED_FEATURES += CONFIG_FT2232_SPI=yes else @@ -626,6 +636,9 @@ CONFIG_BUSPIRATE_SPI ?= yes # Always enable Dediprog SF100 for now. CONFIG_DEDIPROG ?= yes
+# Always enable Developerbox emergency recovery for now. +CONFIG_DEVELOPERBOX ?= yes + # Always enable Marvell SATA controllers for now. CONFIG_SATAMV ?= yes
@@ -666,6 +679,7 @@ endif ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no) override CONFIG_CH341A_SPI = no override CONFIG_DEDIPROG = no +override CONFIG_DEVELOPERBOX = no endif ifeq ($(CONFIG_ENABLE_LIBPCI_PROGRAMMERS), no) override CONFIG_INTERNAL = no @@ -902,6 +916,12 @@ PROGRAMMER_OBJS += dediprog.o NEED_LIBUSB1 += CONFIG_DEDIPROG endif
+ifeq ($(CONFIG_DEVELOPERBOX), yes) +FEATURE_CFLAGS += -D'CONFIG_DEVELOPERBOX=1' +PROGRAMMER_OBJS += developerbox_spi.o +NEED_LIBUSB1 += CONFIG_DEVELOPERBOX +endif + ifeq ($(CONFIG_SATAMV), yes) FEATURE_CFLAGS += -D'CONFIG_SATAMV=1' PROGRAMMER_OBJS += satamv.o diff --git a/developerbox_spi.c b/developerbox_spi.c new file mode 100644 index 000000000000..5742ed093b1a --- /dev/null +++ b/developerbox_spi.c @@ -0,0 +1,296 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2018 Linaro Limited + * + * 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; version 2 of the License. + * + * 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. + */ + +/* Bit bang driver for the 96Boards Developerbox (a.k.a. Synquacer E-series) + * on-board debug UART. The Developerbox implements its debug UART using a + * CP2102N, a USB to UART bridge will also provides four GPIO pins. On + * Developerbox these can be hooked up to the onboard SPI NOR FLASH and used + * for emergency de-brick without any additional hardware programmer. Bit + * banging over USB is extremely slow compared to a proper SPI programmer so + * this is only practical as a de-brick tool. + * + * Schematic is available here: + * https://www.96boards.org/documentation/enterprise/developerbox/hardware-docs... + */ + +#include "platform.h" + +#include <sys/types.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <errno.h> +#include <libusb.h> +#include "flash.h" +#include "chipdrivers.h" +#include "programmer.h" +#include "spi.h" + +/* LIBUSB_CALL ensures the right calling conventions on libusb callbacks. + * However, the macro is not defined everywhere. m( + */ +#ifndef LIBUSB_CALL +#define LIBUSB_CALL +#endif + +/* To prepare a Developerbox for programming via the debug UART DSW4 must be + * changed from the default 00000000 to 10001000 (DSW4-1 and DSW4-5 should be + * turned on) + */ + +/* Bit positions for each pin. */ +#define DEVELOPERBOX_SPI_SCK 0 +#define DEVELOPERBOX_SPI_CS 1 +#define DEVELOPERBOX_SPI_MISO 2 +#define DEVELOPERBOX_SPI_MOSI 3 + +/* Config request types */ +#define REQTYPE_HOST_TO_DEVICE 0x40 +#define REQTYPE_DEVICE_TO_HOST 0xc0 + +/* Config request codes */ +#define CP210X_VENDOR_SPECIFIC 0xFF + +/* CP210X_VENDOR_SPECIFIC */ +#define CP210X_WRITE_LATCH 0x37E1 +#define CP210X_READ_LATCH 0x00C2 + +const struct dev_entry devs_developerbox[] = { + {0x10C4, 0xEA60, OK, "Silicon Labs", "CP2102N USB to UART Bridge Controller"}, + {0}, +}; + +struct libusb_context *usb_ctx; +static libusb_device_handle *cp210x_handle; + +#define CP210X_FAST +#ifdef CP210X_FAST +static int cp210x_mosi; +static int cp210x_sck; +#endif + +static int cp210x_gpio_get() +{ + int res; + uint8_t gpio; + + res = libusb_control_transfer ( + cp210x_handle, // libusb_device_handle * dev_handle, + REQTYPE_DEVICE_TO_HOST, // uint8_t bmRequestType, + CP210X_VENDOR_SPECIFIC, // uint8_t bRequest, + CP210X_READ_LATCH, // uint16_t wValue, + 0, // uint16_t wIndex, + &gpio, // unsigned char * data, + 1, // uint16_t wLength, + 0); // unsigned int timeout + + if (res < 0) { + msg_perr("Failed to read GPIO pins (%s)\n", libusb_error_name(res)); + return 0; + } + + return gpio; +} + +static void cp210x_gpio_set(uint8_t val, uint8_t mask) +{ + int res; + uint16_t gpio; + + gpio = ((val & 0xf) << 8) | (mask & 0xf); + + /* Set relay state on the card */ + res = libusb_control_transfer ( + cp210x_handle, // libusb_device_handle * dev_handle, + REQTYPE_HOST_TO_DEVICE, // uint8_t bmRequestType, + CP210X_VENDOR_SPECIFIC, // uint8_t bRequest, + CP210X_WRITE_LATCH, // uint16_t wValue, + gpio, // uint16_t wIndex, + NULL, // unsigned char * data, + 0, // uint16_t wLength, + 0); // unsigned int timeout + + if (res < 0) + msg_perr("Failed to read GPIO pins (%s)\n", libusb_error_name(res)); +} + +static void cp210x_bitbang_set_cs(int val) +{ + msg_pspew(">><<: %s(%d)\n", __FUNCTION__, val); + +#ifdef CP210X_FAST + /* evict any cached state before updating chip select */ + cp210x_gpio_set(cp210x_mosi << DEVELOPERBOX_SPI_MOSI | + cp210x_sck << DEVELOPERBOX_SPI_SCK, + 1 << DEVELOPERBOX_SPI_MOSI | 1 << DEVELOPERBOX_SPI_SCK); +#endif + + cp210x_gpio_set((!!val) << DEVELOPERBOX_SPI_CS, 1 << DEVELOPERBOX_SPI_CS); +} + +static void cp210x_bitbang_set_sck(int val) +{ + msg_pspew(">><<: %s(%d)\n", __FUNCTION__, val); +#ifndef CP210X_FAST + cp210x_gpio_set((!!val) << DEVELOPERBOX_SPI_SCK, 1 << DEVELOPERBOX_SPI_SCK); +#else + /* evict any previous falling edge */ + if (!cp210x_sck) + cp210x_gpio_set(cp210x_mosi << DEVELOPERBOX_SPI_MOSI, + 1 << DEVELOPERBOX_SPI_MOSI | 1 << DEVELOPERBOX_SPI_SCK); + + cp210x_sck = !!val; + + /* send a rising edge */ + if (cp210x_sck) + cp210x_gpio_set(cp210x_sck << DEVELOPERBOX_SPI_SCK, + 1 << DEVELOPERBOX_SPI_SCK); +#endif +} + +static void cp210x_bitbang_set_mosi(int val) +{ + msg_pspew(">><<: %s(%d)\n", __FUNCTION__, val); +#ifndef CP210X_FAST + cp210x_gpio_set((!!val) << DEVELOPERBOX_SPI_MOSI, 1 << DEVELOPERBOX_SPI_MOSI); +#else + cp210x_mosi = !!val; +#endif +} + +static int cp210x_bitbang_get_miso(void) +{ + msg_pspew(">><<: %s\n", __FUNCTION__); + return !!(cp210x_gpio_get() & (1 << DEVELOPERBOX_SPI_MISO)); +} + +static const struct bitbang_spi_master bitbang_spi_master_cp210x = { + .type = BITBANG_SPI_MASTER_DEVELOPERBOX, + .set_cs = cp210x_bitbang_set_cs, + .set_sck = cp210x_bitbang_set_sck, + .set_mosi = cp210x_bitbang_set_mosi, + .get_miso = cp210x_bitbang_get_miso, + .half_period = 0, +}; + +/* Might be useful for other USB devices as well. static for now. + * num parameter allows user to specify one device of multiple installed */ +static struct libusb_device_handle *get_device_by_vid_pid_number(uint16_t vid, uint16_t pid, unsigned int num) +{ + struct libusb_device **list; + ssize_t count = libusb_get_device_list(usb_ctx, &list); + if (count < 0) { + msg_perr("Getting the USB device list failed (%s)!\n", libusb_error_name(count)); + return NULL; + } + + struct libusb_device_handle *handle = NULL; + ssize_t i = 0; + for (i = 0; i < count; i++) { + struct libusb_device *dev = list[i]; + struct libusb_device_descriptor desc; + int err = libusb_get_device_descriptor(dev, &desc); + if (err != 0) { + msg_perr("Reading the USB device descriptor failed (%s)!\n", libusb_error_name(err)); + libusb_free_device_list(list, 1); + return NULL; + } + if ((desc.idVendor == vid) && (desc.idProduct == pid)) { + msg_pdbg("Found USB device %04"PRIx16":%04"PRIx16" at address %d-%d.\n", + desc.idVendor, desc.idProduct, + libusb_get_bus_number(dev), libusb_get_device_address(dev)); + if (num == 0) { + err = libusb_open(dev, &handle); + if (err != 0) { + msg_perr("Opening the USB device failed (%s)!\n", + libusb_error_name(err)); + libusb_free_device_list(list, 1); + return NULL; + } + break; + } + num--; + } + } + libusb_free_device_list(list, 1); + + return handle; +} + +static int developerbox_spi_shutdown(void *data) +{ + libusb_close(cp210x_handle); + libusb_exit(usb_ctx); + + return 0; +} + +int developerbox_spi_init() +{ + char *device; + long usedevice = 0; + + device = extract_programmer_param("device"); + if (device) { + char *dev_suffix; + errno = 0; + usedevice = strtol(device, &dev_suffix, 10); + if (errno != 0 || device == dev_suffix) { + msg_perr("Error: Could not convert 'device'.\n"); + free(device); + return 1; + } + if (usedevice < 0 || usedevice > UINT_MAX) { + msg_perr("Error: Value for 'device' is out of range.\n"); + free(device); + return 1; + } + if (strlen(dev_suffix) > 0) { + msg_perr("Error: Garbage following 'device' value.\n"); + free(device); + return 1; + } + msg_pinfo("Using device %li.\n", usedevice); + } + free(device); + + /* Here comes the USB stuff. */ + libusb_init(&usb_ctx); + if (!usb_ctx) { + msg_perr("Could not initialize libusb!\n"); + return 1; + } + + const uint16_t vid = devs_developerbox[0].vendor_id; + const uint16_t pid = devs_developerbox[0].device_id; + cp210x_handle = get_device_by_vid_pid_number(vid, pid, (unsigned int) usedevice); + if (!cp210x_handle) { + msg_perr("Could not find a Developerbox programmer on USB.\n"); + libusb_exit(usb_ctx); + return 1; + } + + if (register_shutdown(developerbox_spi_shutdown, NULL)) + return 1; + + if (register_spi_bitbang_master(&bitbang_spi_master_cp210x)) { + /* This should never happen. */ + msg_perr("Developerbox bitbang SPI master init failed!\n"); + return 1; + } + + return 0; +} diff --git a/flashrom.c b/flashrom.c index f85dbb1f836c..6f8fc740d85b 100644 --- a/flashrom.c +++ b/flashrom.c @@ -255,6 +255,18 @@ const struct programmer_entry programmer_table[] = { }, #endif
+#if CONFIG_DEVELOPERBOX == 1 + { + .name = "developerbox", + .type = USB, + .devs.dev = devs_developerbox, + .init = developerbox_spi_init, + .map_flash_region = fallback_map, + .unmap_flash_region = fallback_unmap, + .delay = internal_delay, + }, +#endif + #if CONFIG_RAYER_SPI == 1 { .name = "rayer_spi", diff --git a/programmer.h b/programmer.h index e49f2621ea1e..e0cf8ca759fe 100644 --- a/programmer.h +++ b/programmer.h @@ -73,6 +73,9 @@ enum programmer { #if CONFIG_DEDIPROG == 1 PROGRAMMER_DEDIPROG, #endif +#if CONFIG_DEVELOPERBOX == 1 + PROGRAMMER_DEVELOPERBOX, +#endif #if CONFIG_RAYER_SPI == 1 PROGRAMMER_RAYER_SPI, #endif @@ -169,6 +172,9 @@ enum bitbang_spi_master_type { #if CONFIG_OGP_SPI == 1 BITBANG_SPI_MASTER_OGP, #endif +#if CONFIG_DEVELOPERBOX == 1 + BITBANG_SPI_MASTER_DEVELOPERBOX, +#endif };
struct bitbang_spi_master { @@ -542,6 +548,12 @@ int dediprog_init(void); extern const struct dev_entry devs_dediprog[]; #endif
+/* developerbox_spi.c */ +#if CONFIG_DEVELOPERBOX == 1 +int developerbox_spi_init(void); +extern const struct dev_entry devs_developerbox[]; +#endif + /* ch341a_spi.c */ #if CONFIG_CH341A_SPI == 1 int ch341a_spi_init(void);