Add a plugin for the CH341A device

This commit is contained in:
Richard Hughes 2022-03-28 22:15:40 +01:00
parent 0ebb741c07
commit 91e9f359c3
14 changed files with 988 additions and 1 deletions

View File

@ -422,6 +422,7 @@ done
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_bcm57xx.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cfu.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ccgx.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ch341a.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_colorhug.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cros_ec.so
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cpu.so

View File

@ -467,6 +467,64 @@ fu_cfi_device_to_string(FuDevice *device, guint idt, GString *str)
fu_common_string_append_kx(str, idt, "BlockSize", priv->block_size);
}
/**
* fu_cfi_device_chip_select:
* @self: a #FuCfiDevice
* @value: boolean
* @error: (nullable): optional return location for an error
*
* Sets the chip select value.
*
* Returns: %TRUE on success
*
* Since: 1.8.0
**/
gboolean
fu_cfi_device_chip_select(FuCfiDevice *self, gboolean value, GError **error)
{
FuCfiDeviceClass *klass = FU_CFI_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_CFI_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
if (klass->chip_select == NULL) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not supported");
return FALSE;
}
return klass->chip_select(self, value, error);
}
static gboolean
fu_cfi_device_chip_select_assert(GObject *device, GError **error)
{
return fu_cfi_device_chip_select(FU_CFI_DEVICE(device), TRUE, error);
}
static gboolean
fu_cfi_device_chip_select_deassert(GObject *device, GError **error)
{
return fu_cfi_device_chip_select(FU_CFI_DEVICE(device), FALSE, error);
}
/**
* fu_cfi_device_chip_select_locker_new:
* @self: a #FuCfiDevice
*
* Creates a custom device locker that asserts and deasserts the chip select signal.
*
* Returns: (transfer full): (nullable): a #FuDeviceLocker
*
* Since: 1.8.0
**/
FuDeviceLocker *
fu_cfi_device_chip_select_locker_new(FuCfiDevice *self, GError **error)
{
g_return_val_if_fail(FU_IS_CFI_DEVICE(self), NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
return fu_device_locker_new_full(self,
fu_cfi_device_chip_select_assert,
fu_cfi_device_chip_select_deassert,
error);
}
static void
fu_cfi_device_init(FuCfiDevice *self)
{

View File

@ -15,7 +15,8 @@ G_DECLARE_DERIVABLE_TYPE(FuCfiDevice, fu_cfi_device, FU, CFI_DEVICE, FuDevice)
struct _FuCfiDeviceClass {
FuDeviceClass parent_class;
gpointer __reserved[31];
gboolean (*chip_select)(FuCfiDevice *self, gboolean value, GError **error);
gpointer __reserved[30];
};
/**
@ -70,3 +71,8 @@ void
fu_cfi_device_set_block_size(FuCfiDevice *self, guint32 block_size);
gboolean
fu_cfi_device_get_cmd(FuCfiDevice *self, FuCfiDeviceCmd cmd, guint8 *value, GError **error);
gboolean
fu_cfi_device_chip_select(FuCfiDevice *self, gboolean value, GError **error);
FuDeviceLocker *
fu_cfi_device_chip_select_locker_new(FuCfiDevice *self, GError **error);

View File

@ -1020,6 +1020,8 @@ LIBFWUPDPLUGIN_1.7.7 {
LIBFWUPDPLUGIN_1.8.0 {
global:
fu_cfi_device_chip_select;
fu_cfi_device_chip_select_locker_new;
fu_common_reverse_uint8;
fu_coswid_firmware_get_type;
fu_coswid_firmware_new;

47
plugins/ch341a/README.md Normal file
View File

@ -0,0 +1,47 @@
# CH341A
## Introduction
The CH341A is an affordable SPI programmer.
The assumed map between UIO command bits, pins on CH341A chip and pins on SPI chip:
UIO CH341A SPI CH341A
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 DOUBT
6 D6/21 unused DIN2
7 D7/22 SO/2 DIN
## Firmware Format
The daemon will decompress the cabinet archive and extract a firmware blob of unspecified format.
This plugin supports the following protocol ID:
- com.winchiphead.ch341a
## GUID Generation
These devices use the standard USB DeviceInstanceId values, e.g.
- `USB\VID_1A86&PID_5512&REV_0304`
- `USB\VID_1A86&PID_5512`
## Update Behavior
The device programs devices in raw mode, and can best be used with `fwupdtool`.
To write an image, use `sudo fwupdtool --plugins ch341a install-blob firmware.bin` and to backup
the contents of a SPI device use `sudo fwupdtool --plugins ch341a firmware-dump backup.bin`
## Vendor ID Security
The vendor ID is set from the USB vendor, in this instance set to `USB:0x1A86`
## External Interface Access
This plugin requires read/write access to `/dev/bus/usb`.

View File

@ -0,0 +1,2 @@
[USB\VID_1A86&PID_5512]
Plugin = ch341a

View File

@ -0,0 +1,444 @@
/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-ch341a-cfi-device.h"
#include "fu-ch341a-device.h"
struct _FuCh341aCfiDevice {
FuCfiDevice parent_instance;
};
G_DEFINE_TYPE(FuCh341aCfiDevice, fu_ch341a_cfi_device, FU_TYPE_CFI_DEVICE)
#define CH341A_PAYLOAD_SIZE 0x1A
static gboolean
fu_ch341a_cfi_device_chip_select(FuCfiDevice *self, gboolean value, GError **error)
{
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
return fu_ch341a_device_chip_select(proxy, value, error);
}
typedef struct {
guint8 mask;
guint8 value;
} FuCh341aCfiDeviceHelper;
static gboolean
fu_ch341a_cfi_device_wait_for_status_cb(FuDevice *device, gpointer user_data, GError **error)
{
FuCh341aCfiDeviceHelper *helper = (FuCh341aCfiDeviceHelper *)user_data;
FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device);
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(device));
guint8 buf[2] = {0x0};
g_autoptr(FuDeviceLocker) cslocker = NULL;
/* enable chip */
cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error);
if (cslocker == NULL)
return FALSE;
if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self),
FU_CFI_DEVICE_CMD_READ_STATUS,
&buf[0],
error))
return FALSE;
if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) {
g_prefix_error(error, "failed to want to status: ");
return FALSE;
}
if ((buf[0x1] & helper->mask) != helper->value) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"wanted 0x%x, got 0x%x",
helper->value,
buf[0x1] & helper->mask);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_ch341a_cfi_device_wait_for_status(FuCh341aCfiDevice *self,
guint8 mask,
guint8 value,
guint count,
guint delay,
GError **error)
{
FuCh341aCfiDeviceHelper helper = {.mask = mask, .value = value};
return fu_device_retry_full(FU_DEVICE(self),
fu_ch341a_cfi_device_wait_for_status_cb,
count,
delay,
&helper,
error);
}
static gboolean
fu_ch341a_cfi_device_read_jedec(FuCh341aCfiDevice *self, GError **error)
{
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
guint8 buf[CH341A_PAYLOAD_SIZE] = {0x9F};
g_autoptr(FuDeviceLocker) cslocker = NULL;
g_autoptr(GString) flash_id = g_string_new(NULL);
/* enable chip */
cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error);
if (cslocker == NULL)
return FALSE;
/* read JEDEC ID */
if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error)) {
g_prefix_error(error, "failed to request JEDEC ID: ");
return FALSE;
}
if (buf[1] == 0x0 && buf[2] == 0x0 && buf[3] == 0x0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"flash ID non-valid");
return FALSE;
}
if (buf[1] == 0xFF && buf[2] == 0xFF && buf[3] == 0xFF) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"device not detected");
return FALSE;
}
g_string_append_printf(flash_id, "%02X", buf[1]);
g_string_append_printf(flash_id, "%02X", buf[2]);
g_string_append_printf(flash_id, "%02X", buf[3]);
fu_cfi_device_set_flash_id(FU_CFI_DEVICE(self), flash_id->str);
/* success */
return TRUE;
}
static gboolean
fu_ch341a_cfi_device_setup(FuDevice *device, GError **error)
{
FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device);
/* setup SPI chip */
if (!fu_ch341a_cfi_device_read_jedec(self, error))
return FALSE;
/* this is a generic SPI chip */
fu_device_add_instance_id(device, "SPI");
fu_device_add_vendor_id(device, "SPI:*");
/* FuCfiDevice->setup */
return FU_DEVICE_CLASS(fu_ch341a_cfi_device_parent_class)->setup(device, error);
}
static gboolean
fu_ch341a_cfi_device_write_enable(FuCh341aCfiDevice *self, GError **error)
{
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
guint8 buf[1] = {0x0};
g_autoptr(FuDeviceLocker) cslocker = NULL;
/* write enable */
if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self), FU_CFI_DEVICE_CMD_WRITE_EN, &buf[0], error))
return FALSE;
cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error);
if (cslocker == NULL)
return FALSE;
if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error))
return FALSE;
if (!fu_device_locker_close(cslocker, error))
return FALSE;
/* check that WEL is now set */
return fu_ch341a_cfi_device_wait_for_status(self, 0b10, 0b10, 10, 5, error);
}
static gboolean
fu_ch341a_cfi_device_chip_erase(FuCh341aCfiDevice *self, GError **error)
{
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
guint8 buf[] = {0x0};
g_autoptr(FuDeviceLocker) cslocker = NULL;
/* enable chip */
cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error);
if (cslocker == NULL)
return FALSE;
/* erase */
if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self),
FU_CFI_DEVICE_CMD_CHIP_ERASE,
&buf[0],
error))
return FALSE;
if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error))
return FALSE;
if (!fu_device_locker_close(cslocker, error))
return FALSE;
/* poll Read Status register BUSY */
return fu_ch341a_cfi_device_wait_for_status(self, 0b1, 0b0, 100, 500, error);
}
static gboolean
fu_ch341a_cfi_device_write_page(FuCh341aCfiDevice *self, FuChunk *page, GError **error)
{
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
guint8 buf[4] = {0x0};
g_autoptr(GPtrArray) chunks = NULL;
g_autoptr(FuDeviceLocker) cslocker = NULL;
if (!fu_ch341a_cfi_device_write_enable(self, error))
return FALSE;
cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error);
if (cslocker == NULL)
return FALSE;
/* cmd, then 24 bit starting address */
fu_common_write_uint32(buf, fu_chunk_get_address(page), G_BIG_ENDIAN);
if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self),
FU_CFI_DEVICE_CMD_PAGE_PROG,
&buf[0],
error))
return FALSE;
if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error))
return FALSE;
/* send data */
chunks = fu_chunk_array_new(fu_chunk_get_data(page),
fu_chunk_get_data_sz(page),
0x0,
0x0,
CH341A_PAYLOAD_SIZE);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
guint8 buf2[CH341A_PAYLOAD_SIZE] = {0x0};
if (!fu_memcpy_safe(buf2,
sizeof(buf2),
0x0, /* dst */
fu_chunk_get_data(chk),
fu_chunk_get_data_sz(chk),
0x0, /* src */
fu_chunk_get_data_sz(chk),
error))
return FALSE;
if (!fu_ch341a_device_spi_transfer(proxy, buf2, fu_chunk_get_data_sz(chk), error))
return FALSE;
}
if (!fu_device_locker_close(cslocker, error))
return FALSE;
/* poll Read Status register BUSY */
return fu_ch341a_cfi_device_wait_for_status(self, 0b1, 0b0, 100, 50, error);
}
static gboolean
fu_ch341a_cfi_device_write_pages(FuCh341aCfiDevice *self,
GPtrArray *pages,
FuProgress *progress,
GError **error)
{
/* progress */
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_steps(progress, pages->len);
for (guint i = 0; i < pages->len; i++) {
FuChunk *page = g_ptr_array_index(pages, i);
if (!fu_ch341a_cfi_device_write_page(self, page, error))
return FALSE;
fu_progress_step_done(progress);
}
/* success */
return TRUE;
}
static GBytes *
fu_ch341a_cfi_device_read_firmware(FuCh341aCfiDevice *self,
gsize bufsz,
FuProgress *progress,
GError **error)
{
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
guint8 buf[CH341A_PAYLOAD_SIZE] = {0x0};
g_autoptr(FuDeviceLocker) cslocker = NULL;
g_autoptr(GByteArray) blob = g_byte_array_new();
g_autoptr(GPtrArray) chunks = NULL;
/* enable chip */
cslocker = fu_cfi_device_chip_select_locker_new(FU_CFI_DEVICE(self), error);
if (cslocker == NULL)
return NULL;
/* read each block */
chunks = fu_chunk_array_new(NULL, bufsz + 0x4, 0x0, 0x0, CH341A_PAYLOAD_SIZE);
fu_progress_set_id(progress, G_STRLOC);
fu_progress_set_steps(progress, chunks->len);
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ);
/* cmd, then 24 bit starting address */
fu_common_write_uint32(buf, 0x0, G_BIG_ENDIAN);
if (!fu_cfi_device_get_cmd(FU_CFI_DEVICE(self),
FU_CFI_DEVICE_CMD_READ_DATA,
&buf[0],
error))
return NULL;
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
/* the first package has cmd and address info */
if (!fu_ch341a_device_spi_transfer(proxy, buf, sizeof(buf), error))
return NULL;
if (i == 0) {
g_byte_array_append(blob, buf + 0x4, fu_chunk_get_data_sz(chk) - 0x4);
} else {
g_byte_array_append(blob, buf + 0x0, fu_chunk_get_data_sz(chk));
}
/* done */
fu_progress_step_done(progress);
}
/* success */
return g_byte_array_free_to_bytes(g_steal_pointer(&blob));
}
static gboolean
fu_ch341a_cfi_device_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device);
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
g_autoptr(GBytes) fw = NULL;
g_autoptr(GBytes) fw_verify = NULL;
g_autoptr(GPtrArray) pages = NULL;
g_autoptr(FuDeviceLocker) cslocker = NULL;
g_autoptr(FuDeviceLocker) locker = NULL;
/* open programmer */
locker = fu_device_locker_new(proxy, error);
if (locker == NULL)
return FALSE;
/* progress */
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_flag(progress, FU_PROGRESS_FLAG_GUESSED);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_ERASE, 33);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 44);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 35);
/* get default image */
fw = fu_firmware_get_bytes(firmware, error);
if (fw == NULL)
return FALSE;
/* erase */
if (!fu_ch341a_cfi_device_write_enable(self, error)) {
g_prefix_error(error, "failed to enable writes: ");
return FALSE;
}
if (!fu_ch341a_cfi_device_chip_erase(self, error)) {
g_prefix_error(error, "failed to erase: ");
return FALSE;
}
fu_progress_step_done(progress);
/* write each block */
pages = fu_chunk_array_new_from_bytes(fw,
0x0,
0x0,
fu_cfi_device_get_page_size(FU_CFI_DEVICE(self)));
if (!fu_ch341a_cfi_device_write_pages(self,
pages,
fu_progress_get_child(progress),
error)) {
g_prefix_error(error, "failed to write pages: ");
return FALSE;
}
fu_progress_step_done(progress);
/* verify each block */
fw_verify = fu_ch341a_cfi_device_read_firmware(self,
g_bytes_get_size(fw),
fu_progress_get_child(progress),
error);
if (fw_verify == NULL) {
g_prefix_error(error, "failed to verify blocks: ");
return FALSE;
}
if (!fu_common_bytes_compare(fw, fw_verify, error))
return FALSE;
fu_progress_step_done(progress);
/* success! */
return TRUE;
}
static GBytes *
fu_ch341a_cfi_device_dump_firmware(FuDevice *device, FuProgress *progress, GError **error)
{
FuCh341aCfiDevice *self = FU_CH341A_CFI_DEVICE(device);
FuCh341aDevice *proxy = FU_CH341A_DEVICE(fu_device_get_proxy(FU_DEVICE(self)));
gsize bufsz = fu_device_get_firmware_size_max(device);
g_autoptr(FuDeviceLocker) locker = NULL;
/* open programmer */
locker = fu_device_locker_new(proxy, error);
if (locker == NULL)
return NULL;
/* sanity check */
if (bufsz == 0x0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"device firmware size not set");
return NULL;
}
return fu_ch341a_cfi_device_read_firmware(self, bufsz, progress, error);
}
static void
fu_ch341a_cfi_device_set_progress(FuDevice *self, FuProgress *progress)
{
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* detach */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 100); /* write */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0); /* attach */
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0); /* reload */
}
static void
fu_ch341a_cfi_device_init(FuCh341aCfiDevice *self)
{
fu_device_add_protocol(FU_DEVICE(self), "org.jedec.cfi");
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
}
static void
fu_ch341a_cfi_device_class_init(FuCh341aCfiDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
FuCfiDeviceClass *klass_cfi = FU_CFI_DEVICE_CLASS(klass);
klass_cfi->chip_select = fu_ch341a_cfi_device_chip_select;
klass_device->setup = fu_ch341a_cfi_device_setup;
klass_device->write_firmware = fu_ch341a_cfi_device_write_firmware;
klass_device->dump_firmware = fu_ch341a_cfi_device_dump_firmware;
klass_device->set_progress = fu_ch341a_cfi_device_set_progress;
}

View File

@ -0,0 +1,12 @@
/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_CH341A_CFI_DEVICE (fu_ch341a_cfi_device_get_type())
G_DECLARE_FINAL_TYPE(FuCh341aCfiDevice, fu_ch341a_cfi_device, FU, CH341A_CFI_DEVICE, FuCfiDevice)

View File

@ -0,0 +1,272 @@
/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-ch341a-cfi-device.h"
#include "fu-ch341a-device.h"
struct _FuCh341aDevice {
FuUsbDevice parent_instance;
guint8 speed;
};
G_DEFINE_TYPE(FuCh341aDevice, fu_ch341a_device, FU_TYPE_USB_DEVICE)
#define CH341A_USB_TIMEOUT 1000
#define CH341A_EP_OUT 0x02 /* host to device (write) */
#define CH341A_EP_IN 0x82 /* device to host (read) */
#define CH341A_EP_SIZE 0x20
#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_SET 0x60
#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_SPEED_LOW 0x00
#define CH341A_STM_I2C_SPEED_STANDARD 0x01
#define CH341A_STM_I2C_SPEED_FAST 0x02
#define CH341A_STM_I2C_SPEED_HIGH 0x03
#define CH341A_STM_SPI_MODUS_STANDARD 0x00
#define CH341A_STM_SPI_MODUS_DOUBLE 0x04
#define CH341A_STM_SPI_ENDIAN_BIG 0x0
#define CH341A_STM_SPI_ENDIAN_LITTLE 0x80
static const gchar *
fu_ch341a_device_speed_to_string(guint8 speed)
{
if (speed == CH341A_STM_I2C_SPEED_LOW)
return "20kHz";
if (speed == CH341A_STM_I2C_SPEED_STANDARD)
return "100kHz";
if (speed == CH341A_STM_I2C_SPEED_FAST)
return "400kHz";
if (speed == CH341A_STM_I2C_SPEED_HIGH)
return "750kHz";
if (speed == (CH341A_STM_I2C_SPEED_LOW | CH341A_STM_SPI_MODUS_DOUBLE))
return "2*20kHz";
if (speed == (CH341A_STM_I2C_SPEED_STANDARD | CH341A_STM_SPI_MODUS_DOUBLE))
return "2*100kHz";
if (speed == (CH341A_STM_I2C_SPEED_FAST | CH341A_STM_SPI_MODUS_DOUBLE))
return "2*400kHz";
if (speed == (CH341A_STM_I2C_SPEED_HIGH | CH341A_STM_SPI_MODUS_DOUBLE))
return "2*750kHz";
return NULL;
}
static void
fu_ch341a_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuCh341aDevice *self = FU_CH341A_DEVICE(device);
/* FuUsbDevice->to_string */
FU_DEVICE_CLASS(fu_ch341a_device_parent_class)->to_string(device, idt, str);
fu_common_string_append_kv(str,
idt,
"Speed",
fu_ch341a_device_speed_to_string(self->speed));
}
static gboolean
fu_ch341a_device_write(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
gsize actual_length = 0;
/* debug */
if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "write", buf, bufsz);
if (!g_usb_device_bulk_transfer(usb_device,
CH341A_EP_OUT,
buf,
bufsz,
&actual_length,
CH341A_USB_TIMEOUT,
NULL,
error)) {
g_prefix_error(error, "failed to write 0x%x bytes:", (guint)bufsz);
return FALSE;
}
if (bufsz != actual_length) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"only wrote 0x%x of 0x%x",
(guint)actual_length,
(guint)bufsz);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_ch341a_device_read(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(self));
gsize actual_length = 0;
if (!g_usb_device_bulk_transfer(usb_device,
CH341A_EP_IN,
buf,
bufsz,
&actual_length,
CH341A_USB_TIMEOUT,
NULL,
error)) {
g_prefix_error(error, "failed to read 0x%x bytes: ", (guint)bufsz);
return FALSE;
}
if (bufsz != actual_length) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"only read 0x%x of 0x%x",
(guint)actual_length,
(guint)bufsz);
return FALSE;
}
/* debug */
if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "read", buf, bufsz);
/* success */
return TRUE;
}
gboolean
fu_ch341a_device_spi_transfer(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error)
{
gsize buf2sz = bufsz + 1;
g_autofree guint8 *buf2 = g_malloc0(buf2sz);
/* requires LSB first */
buf2[0] = CH341A_CMD_SPI_STREAM;
for (gsize i = 0; i < bufsz; i++)
buf2[i + 1] = fu_common_reverse_uint8(buf[i]);
/* debug */
if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "SPIwrite", buf, bufsz);
if (!fu_ch341a_device_write(self, buf2, buf2sz, error))
return FALSE;
if (!fu_ch341a_device_read(self, buf, bufsz, error))
return FALSE;
/* requires LSB first */
for (gsize i = 0; i < bufsz; i++)
buf[i] = fu_common_reverse_uint8(buf[i]);
/* debug */
if (g_getenv("FWUPD_CH341A_VERBOSE") != NULL)
fu_common_dump_raw(G_LOG_DOMAIN, "SPIread", buf, bufsz);
/* success */
return TRUE;
}
static gboolean
fu_ch341a_device_configure_stream(FuCh341aDevice *self, GError **error)
{
guint8 buf[] = {CH341A_CMD_I2C_STREAM,
CH341A_CMD_I2C_STM_SET | self->speed,
CH341A_CMD_I2C_STM_END};
if (!fu_ch341a_device_write(self, buf, sizeof(buf), error)) {
g_prefix_error(error, "failed to configure stream: ");
return FALSE;
}
/* success */
return TRUE;
}
gboolean
fu_ch341a_device_chip_select(FuCh341aDevice *self, gboolean val, GError **error)
{
guint8 buf[] = {
CH341A_CMD_UIO_STREAM,
CH341A_CMD_UIO_STM_OUT | (val ? 0x36 : 0x37), /* CS* high, SCK=0, DOUBT*=1 */
CH341A_CMD_UIO_STM_DIR | (val ? 0x3F : 0x00), /* pin direction */
CH341A_CMD_UIO_STM_END,
};
return fu_ch341a_device_write(self, buf, sizeof(buf), error);
}
static gboolean
fu_ch341a_device_setup(FuDevice *device, GError **error)
{
FuCh341aDevice *self = FU_CH341A_DEVICE(device);
g_autoptr(FuCh341aCfiDevice) cfi_device = NULL;
/* FuUsbDevice->setup */
if (!FU_DEVICE_CLASS(fu_ch341a_device_parent_class)->setup(device, error))
return FALSE;
/* set speed */
if (!fu_ch341a_device_configure_stream(self, error))
return FALSE;
/* setup SPI chip */
cfi_device = g_object_new(FU_TYPE_CH341A_CFI_DEVICE,
"context",
fu_device_get_context(FU_DEVICE(self)),
"proxy",
FU_DEVICE(self),
"logical-id",
"SPI",
NULL);
if (!fu_device_setup(FU_DEVICE(cfi_device), error))
return FALSE;
fu_device_add_child(device, FU_DEVICE(cfi_device));
/* success */
return TRUE;
}
static void
fu_ch341a_device_init(FuCh341aDevice *self)
{
self->speed = CH341A_STM_I2C_SPEED_STANDARD;
fu_usb_device_add_interface(FU_USB_DEVICE(self), 0x0);
fu_device_set_name(FU_DEVICE(self), "CH341A");
fu_device_set_vendor(FU_DEVICE(self), "WinChipHead");
}
static void
fu_ch341a_device_class_init(FuCh341aDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->setup = fu_ch341a_device_setup;
klass_device->to_string = fu_ch341a_device_to_string;
}

View File

@ -0,0 +1,17 @@
/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#include <fwupdplugin.h>
#define FU_TYPE_CH341A_DEVICE (fu_ch341a_device_get_type())
G_DECLARE_FINAL_TYPE(FuCh341aDevice, fu_ch341a_device, FU, CH341A_DEVICE, FuUsbDevice)
gboolean
fu_ch341a_device_chip_select(FuCh341aDevice *self, gboolean val, GError **error);
gboolean
fu_ch341a_device_spi_transfer(FuCh341aDevice *self, guint8 *buf, gsize bufsz, GError **error);

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include "fu-ch341a-device.h"
static void
fu_plugin_ch341a_init(FuPlugin *plugin)
{
fu_plugin_add_device_gtype(plugin, FU_TYPE_CH341A_DEVICE);
}
void
fu_plugin_init_vfuncs(FuPluginVfuncs *vfuncs)
{
vfuncs->build_hash = FU_BUILD_HASH;
vfuncs->init = fu_plugin_ch341a_init;
}

68
plugins/ch341a/lsusb.txt Normal file
View File

@ -0,0 +1,68 @@
Bus 001 Device 124: ID 1a86:5512 QinHeng Electronics CH341 in EPP/MEM/I2C mode, EPP/I2C adapter
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 255 Vendor Specific Class
bDeviceSubClass 0
bDeviceProtocol 2
bMaxPacketSize0 8
idVendor 0x1a86 QinHeng Electronics
idProduct 0x5512 CH341 in EPP/MEM/I2C mode, EPP/I2C adapter
bcdDevice 3.04
iManufacturer 0
iProduct 0
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0027
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 96mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 3
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 1
bInterfaceProtocol 2
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 1
Device Status: 0x0000
(Bus Powered)

View File

@ -0,0 +1,33 @@
if gusb.found()
cargs = ['-DG_LOG_DOMAIN="FuPluginCh341a"']
install_data([
'ch341a.quirk',
],
install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
)
shared_module('fu_plugin_ch341a',
fu_hash,
sources : [
'fu-ch341a-cfi-device.c',
'fu-ch341a-device.c',
'fu-plugin-ch341a.c',
],
include_directories : [
root_incdir,
fwupd_incdir,
fwupdplugin_incdir,
],
install : true,
install_dir: plugin_dir,
link_with : [
fwupd,
fwupdplugin,
],
c_args : cargs,
dependencies : [
plugin_deps,
],
)
endif

View File

@ -23,6 +23,7 @@ subdir('bcm57xx')
subdir('bios')
subdir('ccgx')
subdir('cfu')
subdir('ch341a')
subdir('colorhug')
subdir('cpu')
subdir('cros-ec')