[flashrom] [PATCH 3/3] Add support for WCH CH341A as an SPI programmer.

Urja Rannikko urjaman at gmail.com
Wed Jan 27 02:12:11 CET 2016


Hi,

Review-ish... with some only-commentary included too.

On Mon, Jan 18, 2016 at 1:28 AM, Stefan Tauner
<stefan.tauner at alumni.tuwien.ac.at> wrote:
> Signed-off-by: Urja Rannikko <urjaman at gmail.com>
> Signed-off-by: Stefan Tauner <stefan.tauner at alumni.tuwien.ac.at>
> ---
>  Makefile        |  23 ++-
>  ch341a_spi.c    | 531 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  flashrom.8.tmpl |  12 +-
>  flashrom.c      |  12 ++
>  programmer.h    |  13 ++
>  5 files changed, 588 insertions(+), 3 deletions(-)
>  create mode 100644 ch341a_spi.c
>
> diff --git a/Makefile b/Makefile
> index 6862200..676bb66 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -157,7 +157,7 @@ UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes
>  else
>  override CONFIG_PONY_SPI = no
>  endif
> -# Dediprog, USB-Blaster, PICkit2 and FT2232 are not supported under DOS (missing USB support).
> +# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported under DOS (missing USB support).
>  ifeq ($(CONFIG_DEDIPROG), yes)
>  UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes
>  else
> @@ -178,6 +178,11 @@ UNSUPPORTED_FEATURES += CONFIG_PICKIT2_SPI=yes
>  else
>  override CONFIG_PICKIT2_SPI = no
>  endif
> +ifeq ($(CONFIG_CH341A_SPI), yes)
> +UNSUPPORTED_FEATURES += CONFIG_CH341A_SPI=yes
> +else
> +override CONFIG_CH341A_SPI = no
> +endif
>  endif
>
>  # FIXME: Should we check for Cygwin/MSVC as well?
> @@ -301,7 +306,7 @@ UNSUPPORTED_FEATURES += CONFIG_PONY_SPI=yes
>  else
>  override CONFIG_PONY_SPI = no
>  endif
> -# Dediprog, USB-Blaster, PICkit2 and FT2232 are not supported with libpayload (missing libusb support)
> +# Dediprog, USB-Blaster, PICkit2, CH341A and FT2232 are not supported with libpayload (missing libusb support)
>  ifeq ($(CONFIG_DEDIPROG), yes)
>  UNSUPPORTED_FEATURES += CONFIG_DEDIPROG=yes
>  else
> @@ -322,6 +327,11 @@ UNSUPPORTED_FEATURES += CONFIG_PICKIT2_SPI=yes
>  else
>  override CONFIG_PICKIT2_SPI = no
>  endif
> +ifeq ($(CONFIG_CH341A_SPI), yes)
> +UNSUPPORTED_FEATURES += CONFIG_CH341A_SPI=yes
> +else
> +override CONFIG_CH341A_SPI = no
> +endif
>  endif
>
>  ifneq ($(TARGET_OS), Linux)
> @@ -500,6 +510,9 @@ CONFIG_LINUX_SPI ?= yes
>  # Always enable ITE IT8212F PATA controllers for now.
>  CONFIG_IT8212 ?= yes
>
> +# Winchiphead CH341A
> +CONFIG_CH341A_SPI ?= yes
> +
>  # Disable wiki printing by default. It is only useful if you have wiki access.
>  CONFIG_PRINT_WIKI ?= no
>
> @@ -740,6 +753,12 @@ NEED_LINUX_I2C := yes
>  PROGRAMMER_OBJS += mstarddc_spi.o
>  endif
>
> +ifeq ($(CONFIG_CH341A_SPI), yes)
> +FEATURE_CFLAGS += -D'CONFIG_CH341A_SPI=1'
> +PROGRAMMER_OBJS += ch341a_spi.o
> +NEED_LIBUSB1 := yes
> +endif
> +
>  ifeq ($(NEED_SERIAL), yes)
>  LIB_OBJS += serial.o
>  endif
> diff --git a/ch341a_spi.c b/ch341a_spi.c
> new file mode 100644
> index 0000000..36282ed
> --- /dev/null
> +++ b/ch341a_spi.c
> @@ -0,0 +1,531 @@
> +/*
> + * This file is part of the flashrom project.
> + *
> + * Copyright (C) 2011 asbokid <ballymunboy at gmail.com>
> + * Copyright (C) 2014 Pluto Yang <yangyj.ee at gmail.com>
> + * Copyright (C) 2015-2016 Stefan Tauner
> + * Copyright (C) 2015 Urja Rannikko <urjaman at gmail.com>
> + *
> + * 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
> + */
> +
> +#include <string.h>
> +#include <libusb.h>
> +#include "flash.h"
> +#include "programmer.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
> +
> +#define         USB_TIMEOUT            1000    /* 1000 ms is plenty and we have no backup strategy anyway. */
> +#define         WRITE_EP               0x02
> +#define         READ_EP                0x82
> +
> +#define         CH341_PACKET_LENGTH            0x20
> +#define         CH341_MAX_PACKETS              256
> +#define         CH341_MAX_PACKET_LEN           (CH341_PACKET_LENGTH * CH341_MAX_PACKETS)
> +
> +#define         CH341A_CMD_SET_OUTPUT          0xA1
> +#define         CH341A_CMD_IO_ADDR             0xA2
> +#define         CH341A_CMD_PRINT_OUT           0xA3
> +#define         CH341A_CMD_SPI_STREAM          0xA8
> +#define         CH341A_CMD_SIO_STREAM          0xA9
> +#define         CH341A_CMD_I2C_STREAM          0xAA
> +#define         CH341A_CMD_UIO_STREAM          0xAB
> +
> +#define         CH341A_CMD_I2C_STM_START       0x74
> +#define         CH341A_CMD_I2C_STM_STOP        0x75
> +#define         CH341A_CMD_I2C_STM_OUT         0x80
> +#define         CH341A_CMD_I2C_STM_IN          0xC0
> +#define         CH341A_CMD_I2C_STM_MAX         ( min( 0x3F, CH341_PACKET_LENGTH ) )
> +#define         CH341A_CMD_I2C_STM_SET         0x60 // bit 2: SPI with two data pairs D5,D4=out, D7,D6=in
> +#define         CH341A_CMD_I2C_STM_US          0x40
> +#define         CH341A_CMD_I2C_STM_MS          0x50
> +#define         CH341A_CMD_I2C_STM_DLY         0x0F
> +#define         CH341A_CMD_I2C_STM_END         0x00
> +
> +#define         CH341A_CMD_UIO_STM_IN          0x00
> +#define         CH341A_CMD_UIO_STM_DIR         0x40
> +#define         CH341A_CMD_UIO_STM_OUT         0x80
> +#define         CH341A_CMD_UIO_STM_US          0xC0
> +#define         CH341A_CMD_UIO_STM_END         0x20
> +
> +#define         CH341A_STM_I2C_20K             0x00
> +#define         CH341A_STM_I2C_100K            0x01
> +#define         CH341A_STM_I2C_400K            0x02
> +#define         CH341A_STM_I2C_750K            0x03
> +#define         CH341A_STM_SPI_DBL             0x04
> +
> +
> +/* Number of parallel IN transfers. 32 seems to produce the most stable throughput on Windows. */
> +#define USB_IN_TRANSFERS 32
> +
> +/* We need to use many queued IN transfers for any resemblance of performance (especially on Windows)
> + * because USB spec says that transfers end on non-full packets and the device sends the 31 reply
> + * data bytes to each 32-byte packet with command + 31 bytes of data... */
> +static struct libusb_transfer *transfer_out = NULL;
> +static struct libusb_transfer *transfer_ins[USB_IN_TRANSFERS] = {0};
> +
> +/* Accumulate delays to be plucked between CS deassertion and CS assertions. */
> +static unsigned int stored_delay_us = 0;
> +
> +static struct libusb_device_handle *handle = NULL;
> +
> +const struct dev_entry devs_ch341a_spi[] = {
> +       {0x1A86, 0x5512, OK, "Winchiphead (WCH)", "CH341A"},
> +
> +       {0},
> +};
> +
> +enum trans_state {TRANS_ACTIVE = -2, TRANS_ERR = -1, TRANS_IDLE = 0};
> +
> +static void print_hex(const void *buf, size_t len)
> +{
> +       size_t i;
> +       for (i = 0; i < len; i++) {
> +               msg_pspew(" %02x", ((uint8_t *)buf)[i]);
> +               if (i % CH341_PACKET_LENGTH == CH341_PACKET_LENGTH - 1)
> +                       msg_pspew("\n");
> +       }
> +}
> +
> +static void cb_common(const char *func, struct libusb_transfer *transfer)
> +{
> +       int *transfer_cnt = (int*)transfer->user_data;
> +
> +       if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
> +               /* Silently ACK and exit. */
> +               *transfer_cnt = TRANS_IDLE;
> +               return;
> +       }
> +
> +       if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
> +               msg_perr("\n%s: error: %s\n", func, libusb_error_name(transfer->status));
> +               *transfer_cnt = TRANS_ERR;
> +       } else {
> +               *transfer_cnt = transfer->actual_length;
> +       }
> +}
> +
> +/* callback for bulk out async transfer */
> +static void LIBUSB_CALL cb_out(struct libusb_transfer *transfer)
> +{
> +       cb_common(__func__, transfer);
> +}
> +
> +/* callback for bulk in async transfer */
> +static void LIBUSB_CALL cb_in(struct libusb_transfer *transfer)
> +{
> +       cb_common(__func__, transfer);
> +}
> +
> +static int32_t usb_transfer(const char *func, unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr)
> +{
> +       if (handle == NULL)
> +               return -1;
> +
> +       int state_out = TRANS_IDLE;
> +       transfer_out->buffer = (uint8_t*)writearr;
> +       transfer_out->length = writecnt;
> +       transfer_out->user_data = &state_out;
> +
> +       /* Schedule write first */
> +       if (writecnt > 0) {
> +               state_out = TRANS_ACTIVE;
> +               int ret = libusb_submit_transfer(transfer_out);
> +               if (ret) {
> +                       msg_perr("%s: failed to submit OUT transfer: %s\n", func, libusb_error_name(ret));
> +                       state_out = TRANS_ERR;
> +                       goto err;
> +               }
> +       }
> +
> +       /* Handle all asynchronous packets as long as we have stuff to write or read. The write(s) simply need
> +        * to complete but we need to scheduling reads as long as we are not done. */
> +       unsigned int free_idx = 0; /* The IN transfer we expect to be free next. */
> +       unsigned int in_idx = 0; /* The IN transfer we expect to be completed next. */
> +       unsigned int in_done = 0;
> +       unsigned int in_active = 0;
> +       unsigned int out_done = 0;
> +       int state_in[USB_IN_TRANSFERS] = {0};
> +       do {
> +               /* Schedule new reads as long as there are free transfers and unscheduled bytes to read. */
> +               while ((in_done + in_active) < readcnt && state_in[free_idx] == TRANS_IDLE) {
> +                       unsigned int cur_todo = min(CH341_PACKET_LENGTH - 1, readcnt - in_done - in_active);
> +                       transfer_ins[free_idx]->length = cur_todo;
> +                       transfer_ins[free_idx]->buffer = readarr;
> +                       transfer_ins[free_idx]->user_data = &state_in[free_idx];
> +                       int ret = libusb_submit_transfer(transfer_ins[free_idx]);
> +                       if (ret) {
> +                               state_in[free_idx] = TRANS_ERR;
> +                               msg_perr("%s: failed to submit IN transfer: %s\n",
> +                                        func, libusb_error_name(ret));
> +                               goto err;
> +                       }
> +                       readarr += cur_todo;
> +                       in_active += cur_todo;
> +                       state_in[free_idx] = TRANS_ACTIVE;
> +                       free_idx = (free_idx + 1) % USB_IN_TRANSFERS; /* Increment (and wrap around). */
Yeah this obvious construct is something I didnt use because I'm such
an embedded guy that I'd never ever use % in performance related
code... (i think i had some if idx>=USB_IN_TRANSFERS) idx = 0
thing)... thanks for making it saner.

> +               }
> +
> +               /* Actually get some work done. */
> +               libusb_handle_events_timeout(NULL, &(struct timeval){1, 0});
> +
> +               /* Check for the write */
> +               if (out_done < writecnt) {
> +                       if (state_out == TRANS_ERR) {
> +                               goto err;
> +                       } else if (state_out > 0) {
> +                               out_done += state_out;
> +                               state_out = TRANS_IDLE;
> +                       }
> +               }
> +               /* Check for completed transfers. */
> +               while (state_in[in_idx] != TRANS_IDLE && state_in[in_idx] != TRANS_ACTIVE) {
> +                       if (state_in[in_idx] == TRANS_ERR) {
> +                               goto err;
> +                       }
> +                       /* If a transfer is done, record the number of bytes read and reuse it later. */
> +                       in_done += state_in[in_idx];
> +                       in_active -= state_in[in_idx];
> +                       state_in[in_idx] = TRANS_IDLE;
> +                       in_idx = (in_idx + 1) % USB_IN_TRANSFERS; /* Increment (and wrap around). */
> +               }
> +       } while ((out_done < writecnt) || (in_done < readcnt));
> +
> +       if (out_done > 0) {
> +               msg_pspew("Wrote %d bytes:\n", out_done);
> +               print_hex(writearr, out_done);
> +               msg_pspew("\n\n");
> +       }
> +       if (in_done > 0) {
> +               msg_pspew("Read %d bytes:\n", in_done);
> +               print_hex(readarr, in_done);
> +               msg_pspew("\n\n");
> +       }
> +       return 0;
> +err:
> +       /* Clean up on errors. */
> +       msg_perr("%s: Failed to %s %d bytes\n", func, (state_out == TRANS_ERR) ? "write" : "read",
> +                (state_out == TRANS_ERR) ? writecnt : readcnt);
> +       /* First, we must cancel any ongoing requests and wait for them to be canceled. */
> +       if ((writecnt > 0) && (state_out == TRANS_ACTIVE)) {
> +               if (libusb_cancel_transfer(transfer_out) != 0)
> +                       state_out = TRANS_ERR;
> +       }
> +       if (readcnt > 0) {
> +               unsigned int i;
> +               for (i = 0; i < USB_IN_TRANSFERS; i++) {
> +                       if (state_in[i] == TRANS_ACTIVE)
> +                               if (libusb_cancel_transfer(transfer_ins[i]) != 0)
> +                                       state_in[i] = TRANS_ERR;
> +               }
> +       }
> +
> +       /* Wait for cancellations to complete. */
> +       while (1) {
> +               bool finished = true;
> +               if ((writecnt > 0) && (state_out == TRANS_ACTIVE))
> +                       finished = false;
> +               if (readcnt > 0) {
> +                       unsigned int i;
> +                       for (i = 0; i < USB_IN_TRANSFERS; i++) {
> +                               if (state_in[i] == TRANS_ACTIVE)
> +                                       finished = false;
> +                       }
> +               }
> +               if (finished)
> +                       break;
> +               libusb_handle_events_timeout(NULL, &(struct timeval){1, 0});
> +       }
> +       return -1;
> +}
> +
> +/*   Set the I2C bus speed (speed(b1b0): 0 = 20kHz; 1 = 100kHz, 2 = 400kHz, 3 = 750kHz).
> + *   Set the SPI bus data width (speed(b2): 0 = Single, 1 = Double).  */
> +static int32_t config_stream(uint32_t speed)
> +{
> +       if (handle == NULL)
> +               return -1;
> +
> +       uint8_t buf[] = {
> +               CH341A_CMD_I2C_STREAM,
> +               CH341A_CMD_I2C_STM_SET | (speed & 0x7),
> +               CH341A_CMD_I2C_STM_END
> +       };
> +
> +       int32_t ret = usb_transfer(__func__, sizeof(buf), 0, buf, NULL);
> +       if (ret < 0) {
> +               msg_perr("Could not configure stream interface.\n");
> +       }
> +       return ret;
> +}
> +
> +/* ch341 requires LSB first, swap the bit order before send and after receive */
> +static uint8_t swap_byte(uint8_t x)
> +{
> +       x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa);
> +       x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc);
> +       x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0);
> +       return x;
> +}
> +
> +/* The assumed map between UIO command bits, pins on CH341A chip and pins on SPI chip:
> + * UIO CH341A  SPI     CH341A SPI name
> + * 0   D0/15   CS/1    (CS0)
> + * 1   D1/16   unused  (CS1)
> + * 2   D2/17   unused  (CS2)
> + * 3   D3/18   SCK/6   (DCK)
> + * 4   D4/19   unused  (DOUT2)
> + * 5   D5/20   SI/5    (DOUT)
> + * - The UIO stream commands seem to only have 6 bits of output, and D6/D7 are the SPI inputs,
> + *  mapped as follows:
> + *     D6/21   unused  (DIN2)
> + *     D7/22   SO/2    (DIN)
> + */
> +static int32_t enable_pins(bool enable)
> +{
> +       uint8_t buf[] = {
> +               CH341A_CMD_UIO_STREAM,
> +               CH341A_CMD_UIO_STM_OUT | 0x37, // CS high (all of them), SCK=0, DOUT*=1
> +               CH341A_CMD_UIO_STM_DIR | (enable ? 0x3F : 0x00), // Interface output enable / disable
> +               CH341A_CMD_UIO_STM_END,
> +       };
> +
> +       int32_t ret = usb_transfer(__func__, sizeof(buf), 0, buf, NULL);
> +       if (ret < 0) {
> +               msg_perr("Could not %sable output pins.\n", enable ? "en" : "dis");
> +       }
> +       return ret;
> +}
> +
> +/* De-assert and assert CS in one operation. */
> +static void pluck_cs(uint8_t *ptr)
> +{
> +       /* This was measured to give a minumum deassertion time of 2.25 us,
> +        * >20x more than needed for most SPI chips (100ns). */
> +       int delay_cnt = 2;
> +       if (stored_delay_us) {
> +               delay_cnt = (stored_delay_us * 4) / 3;
> +               stored_delay_us = 0;
> +       }
> +       *ptr++ = CH341A_CMD_UIO_STREAM;
> +       *ptr++ = CH341A_CMD_UIO_STM_OUT | 0x37; /* deasserted */
> +       int i;
> +       for (i = 0; i < delay_cnt; i++)
> +               *ptr++ = CH341A_CMD_UIO_STM_OUT | 0x37; /* "delay" */
> +       *ptr++ = CH341A_CMD_UIO_STM_OUT | 0x36; /* asserted */
> +       *ptr++ = CH341A_CMD_UIO_STM_END;
> +}
> +
> +void ch341a_spi_delay(unsigned int usecs)
> +{
> +       /* There is space for 28 bytes instructions of 750 ns each in the CS packet (32 - 4 for the actual CS
> +        * instructions), thus max 21 us, but we avoid getting too near to this boundary and use
> +        * internal_delay() for durations over 20 us. */
> +       if ((usecs + stored_delay_us) > 20) {
> +               unsigned int inc = 20 - stored_delay_us;
> +               internal_delay(usecs - inc);
> +               usecs = inc;
> +       }
> +       stored_delay_us += usecs;
> +}
> +
I had a fancier delay implementation done (all the way upto ms scale
using the I2C commands), but since i didnt get that fully tested, this
one will do, i'll post it as an improvement later.

> +static int ch341a_spi_spi_send_command(struct flashctx *flash, unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr)
> +{
> +       if (handle == NULL)
> +               return -1;
> +
> +       /* How many packets ... */
> +       const size_t packets = (writecnt + readcnt + CH341_PACKET_LENGTH - 2) / (CH341_PACKET_LENGTH - 1);
> +
> +       uint8_t wbuf[packets+1][CH341_PACKET_LENGTH];
> +       uint8_t rbuf[writecnt + readcnt];
> +       memset(wbuf[0], 0, CH341_PACKET_LENGTH); // dont want to write stack random to device...
> +
> +       uint8_t *ptr = wbuf[0];
> +       /* CS usage is optimized by doing both transitions in one packet.
> +        * Final transition to deselected state is in the pin disable. */
> +       pluck_cs(ptr);
> +       unsigned int write_left = writecnt;
> +       unsigned int read_left = readcnt;
> +       unsigned int p;
> +       for (p = 0; p < packets; p++) {
> +               unsigned int write_now = min(CH341_PACKET_LENGTH - 1, write_left);
> +               unsigned int read_now = min ((CH341_PACKET_LENGTH - 1) - write_now, read_left);
> +               ptr = wbuf[p+1];
> +               *ptr++ = CH341A_CMD_SPI_STREAM;
> +               unsigned int i;
> +               for (i = 0; i < write_now; ++i)
> +                       *ptr++ = swap_byte(*writearr++);
> +               if (read_now) {
> +                       memset(ptr, 0xFF, read_now);
> +                       read_left -= read_now;
> +               }
> +               write_left -= write_now;
> +       }
> +
> +       int32_t ret = usb_transfer(__func__, CH341_PACKET_LENGTH + packets + writecnt + readcnt,
> +                                   writecnt + readcnt, wbuf[0], rbuf);
> +       if (ret < 0)
> +               return -1;
> +
> +       unsigned int i;
> +       for (i = 0; i < readcnt; i++) {
> +               /* FIXME: does working on words instead of bytes improve speed? */
... drop the comment? i dont really care, but i think our consensus on
IRC was that a words-wide implementation would have more issues
(alignment and such) than performance.
Or make a comment with that info.
> +               *readarr++ = swap_byte(rbuf[writecnt + i]);
> +       }
> +
> +       return 0;
> +}
> +
> +static const struct spi_master spi_master_ch341a_spi = {
> +       .type           = SPI_CONTROLLER_CH341A_SPI,
> +       /* TODO: flashrom's current maximum is 256 B. Device was tested on Linux to accept atleast 16 kB. */
> +       .max_data_read  = 16 * 1024,
> +       .max_data_write = 16 * 1024,
On linux. Not on windows usb "stack" i think... set these back to 1k?
Or test for the limit (maybe something like 4k?) and change the 1000ms
timeout, since i think the big packet would timeout on windows if
nothing else failed...

> +       .command        = ch341a_spi_spi_send_command,
> +       .multicommand   = default_spi_send_multicommand,
> +       .read           = default_spi_read,
> +       .write_256      = default_spi_write_256,
> +       .write_aai      = default_spi_write_aai,
> +};
> +
> +static int ch341a_spi_shutdown(void *data)
> +{
> +       if (handle == NULL)
> +               return -1;
> +
> +       enable_pins(false);
> +       libusb_free_transfer(transfer_out);
> +       transfer_out = NULL;
> +       int i;
> +       for (i = 0; i < USB_IN_TRANSFERS; i++) {
> +               libusb_free_transfer(transfer_ins[i]);
> +               transfer_ins[i] = NULL;
> +       }
> +       libusb_release_interface(handle, 0);
> +       libusb_close(handle);
> +       libusb_exit(NULL);
> +       handle = NULL;
> +       return 0;
> +}
> +
> +int ch341a_spi_init(void)
> +{
> +       if (handle != NULL) {
> +               msg_cerr("%s: handle already set! Please report a bug at flashrom at flashrom.org\n", __func__);
> +               return -1;
> +       }
> +
> +       int32_t ret = libusb_init(NULL);
> +       if (ret < 0) {
> +               msg_perr("Couldnt initialize libusb!\n");
> +               return -1;
> +       }
> +
> +       libusb_set_debug(NULL, 3); // Enable information, warning and error messages (only).
> +
> +       uint16_t vid = devs_ch341a_spi[0].vendor_id;
> +       uint16_t pid = devs_ch341a_spi[0].device_id;
> +       handle = libusb_open_device_with_vid_pid(NULL, vid, pid);
> +       if (handle == NULL) {
> +               msg_perr("Couldn't open device %04x:%04x.\n", vid, pid);
> +               return -1;
> +       }
> +
> +/* libusb_detach_kernel_driver() and friends basically only work on Linux. We simply try to detach on Linux
> + * without a lot of passion here. If that works fine else we will fail on claiming the interface anyway. */
> +#if IS_LINUX
> +       ret = libusb_detach_kernel_driver(handle, 0);
> +       if (ret == LIBUSB_ERROR_NOT_SUPPORTED) {
> +               msg_pwarn("Detaching kernel drivers is not supported. Further accesses may fail.\n");
> +       } else if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND) {
> +               msg_pwarn("Failed to detach kernel driver: '%s'. Further accesses will probably fail.\n",
> +                         libusb_error_name(ret));
> +       }
> +#endif
> +
> +       ret = libusb_claim_interface(handle, 0);
> +       if (ret != 0) {
> +               msg_perr("Failed to claim interface 0: '%s'\n", libusb_error_name(ret));
> +               goto close_handle;
> +       }
> +
> +       struct libusb_device *dev;
> +       if (!(dev = libusb_get_device(handle))) {
> +               msg_perr("Failed to get device from device handle.\n");
> +               goto close_handle;
> +       }
> +
> +       struct libusb_device_descriptor desc;
> +       ret = libusb_get_device_descriptor(dev, &desc);
> +       if (ret < 0) {
> +               msg_perr("Failed to get device descriptor: '%s'\n", libusb_error_name(ret));
> +               goto release_interface;
> +       }
> +
> +       msg_pdbg("Device revision is %d.%01d.%01d\n",
> +               (desc.bcdDevice >> 8) & 0x00FF,
> +               (desc.bcdDevice >> 4) & 0x000F,
> +               (desc.bcdDevice >> 0) & 0x000F);
> +
> +       /* Allocate and pre-fill transfer structures. */
> +       transfer_out = libusb_alloc_transfer(0);
> +       if (!transfer_out) {
> +               msg_perr("Failed to alloc libusb OUT transfer\n");
> +               goto release_interface;
> +       }
> +       int i;
> +       for (i = 0; i < USB_IN_TRANSFERS; i++) {
> +               transfer_ins[i] = libusb_alloc_transfer(0);
> +               if (transfer_ins[i] == NULL) {
> +                       msg_perr("Failed to alloc libusb IN transfer %d\n", i);
> +                       goto dealloc_transfers;
> +               }
> +       }
> +       /* We use these helpers but dont fill the actual buffer yet. */
> +       libusb_fill_bulk_transfer(transfer_out, handle, WRITE_EP, NULL, 0, cb_out, NULL, USB_TIMEOUT);
> +       for (i = 0; i < USB_IN_TRANSFERS; i++)
> +               libusb_fill_bulk_transfer(transfer_ins[i], handle, READ_EP, NULL, 0, cb_in, NULL, USB_TIMEOUT);
> +
> +       if ((config_stream(CH341A_STM_I2C_100K) < 0) || (enable_pins(true) < 0))
> +               goto dealloc_transfers;
> +
> +       register_shutdown(ch341a_spi_shutdown, NULL);
> +       register_spi_master(&spi_master_ch341a_spi);
> +
> +       return 0;
> +
> +dealloc_transfers:
> +       for (i = 0; i < USB_IN_TRANSFERS; i++) {
> +               if (transfer_ins[i] == NULL)
> +                       break;
> +               libusb_free_transfer(transfer_ins[i]);
> +               transfer_ins[i] = NULL;
> +       }
> +       libusb_free_transfer(transfer_out);
> +       transfer_out = NULL;
> +release_interface:
> +       libusb_release_interface(handle, 0);
> +close_handle:
> +       libusb_close(handle);
> +       handle = NULL;
> +       return -1;
> +}
> diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
> index 7c8c70c..0dd8322 100644
> --- a/flashrom.8.tmpl
> +++ b/flashrom.8.tmpl
> @@ -269,6 +269,8 @@ bitbanging adapter)
>  .sp
>  .BR "* pickit2_spi" " (for SPI flash ROMs accessible via Microchip PICkit2)"
>  .sp
> +.BR "* ch341a_spi" " (for SPI flash ROMs attached to WCH CH341A)"
> +.sp
>  Some programmers have optional or mandatory parameters which are described
>  in detail in the
>  .B PROGRAMMER-SPECIFIC INFORMATION
> @@ -1013,6 +1015,10 @@ an operation), without the
>  parameter, once the flash read/write operation you intended to perform has completed successfully.
>  .sp
>  Please also note that the mstarddc_spi driver only works on Linux.
> +.SS
> +.BR "ch341a_spi " programmer
> +The WCH CH341A programmer does not support any parameters currently. SPI frequency is fixed at 2 MHz, and CS0 is
> +used as per the device.
>  .SH EXAMPLES
>  To back up and update your BIOS, run
>  .sp
> @@ -1072,6 +1078,9 @@ needs userspace access to a serial port.
>  .BR dediprog ", " ft2232_spi ", " usbblaster_spi " and " pickit2_spi
>  need access to the respective USB device via libusb-0.1.
>  .sp
> +.BR ch341a_spi
> +needs access to the respective USB device via libusb-1.0.
> +.sp
>  .B dummy
>  needs no access permissions at all.
>  .sp
> @@ -1079,7 +1088,8 @@ needs no access permissions at all.
>  .BR gfxnvidia ", " drkaiser ", " satasii ", " satamv ", " atahpt" and " atavia
>  have to be run as superuser/root, and need additional raw access permission.
>  .sp
> -.BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi " and " pickit2_spi
> +.BR serprog ", " buspirate_spi ", " dediprog ", " usbblaster_spi ", " ft2232_spi ", " pickit2_spi " and " \
> +ch341a_spi
>  can be run as normal user on most operating systems if appropriate device
>  permissions are set.
>  .sp
> diff --git a/flashrom.c b/flashrom.c
> index 3853e19..0ff088f 100644
> --- a/flashrom.c
> +++ b/flashrom.c
> @@ -380,6 +380,18 @@ const struct programmer_entry programmer_table[] = {
>         },
>  #endif
>
> +#if CONFIG_CH341A_SPI == 1
> +       {
> +               .name                   = "ch341a_spi",
> +               .type                   = USB,
> +               .devs.dev               = devs_ch341a_spi,
> +               .init                   = ch341a_spi_init,
> +               .map_flash_region       = fallback_map,
> +               .unmap_flash_region     = fallback_unmap,
> +               .delay                  = ch341a_spi_delay,
> +       },
> +#endif
> +
>         {0}, /* This entry corresponds to PROGRAMMER_INVALID. */
>  };
>
> diff --git a/programmer.h b/programmer.h
> index 97f0ffa..b9f8708 100644
> --- a/programmer.h
> +++ b/programmer.h
> @@ -105,6 +105,9 @@ enum programmer {
>  #if CONFIG_PICKIT2_SPI == 1
>         PROGRAMMER_PICKIT2_SPI,
>  #endif
> +#if CONFIG_CH341A_SPI == 1
> +       PROGRAMMER_CH341A_SPI,
> +#endif
>         PROGRAMMER_INVALID /* This must always be the last entry. */
>  };
>
> @@ -516,6 +519,13 @@ int linux_spi_init(void);
>  int dediprog_init(void);
>  #endif
>
> +/* ch341a_spi.c */
> +#if CONFIG_CH341A_SPI == 1
> +int ch341a_spi_init(void);
> +void ch341a_spi_delay(unsigned int usecs);
> +extern const struct dev_entry devs_ch341a_spi[];
> +#endif
> +
>  /* flashrom.c */
>  struct decode_sizes {
>         uint32_t parallel;
> @@ -575,6 +585,9 @@ enum spi_controller {
>  #if CONFIG_PICKIT2_SPI == 1
>         SPI_CONTROLLER_PICKIT2,
>  #endif
> +#if CONFIG_CH341A_SPI == 1
> +       SPI_CONTROLLER_CH341A_SPI,
> +#endif
>  };
>
>  #define MAX_DATA_UNSPECIFIED 0
> --
> Kind regards, Stefan Tauner
>

With atleast the limits dropped (or tested),  this is:
Acked-by: Urja Rannikko <urjaman at gmail.com>

-- 
Urja




More information about the flashrom mailing list