mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-10 17:51:45 +00:00
Add a plugin for the CH341A device
This commit is contained in:
parent
0ebb741c07
commit
91e9f359c3
@ -422,6 +422,7 @@ done
|
|||||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_bcm57xx.so
|
%{_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_cfu.so
|
||||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_ccgx.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_colorhug.so
|
||||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cros_ec.so
|
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cros_ec.so
|
||||||
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cpu.so
|
%{_libdir}/fwupd-plugins-%{fwupdplugin_version}/libfu_plugin_cpu.so
|
||||||
|
@ -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_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
|
static void
|
||||||
fu_cfi_device_init(FuCfiDevice *self)
|
fu_cfi_device_init(FuCfiDevice *self)
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,8 @@ G_DECLARE_DERIVABLE_TYPE(FuCfiDevice, fu_cfi_device, FU, CFI_DEVICE, FuDevice)
|
|||||||
|
|
||||||
struct _FuCfiDeviceClass {
|
struct _FuCfiDeviceClass {
|
||||||
FuDeviceClass parent_class;
|
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);
|
fu_cfi_device_set_block_size(FuCfiDevice *self, guint32 block_size);
|
||||||
gboolean
|
gboolean
|
||||||
fu_cfi_device_get_cmd(FuCfiDevice *self, FuCfiDeviceCmd cmd, guint8 *value, GError **error);
|
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);
|
||||||
|
@ -1020,6 +1020,8 @@ LIBFWUPDPLUGIN_1.7.7 {
|
|||||||
|
|
||||||
LIBFWUPDPLUGIN_1.8.0 {
|
LIBFWUPDPLUGIN_1.8.0 {
|
||||||
global:
|
global:
|
||||||
|
fu_cfi_device_chip_select;
|
||||||
|
fu_cfi_device_chip_select_locker_new;
|
||||||
fu_common_reverse_uint8;
|
fu_common_reverse_uint8;
|
||||||
fu_coswid_firmware_get_type;
|
fu_coswid_firmware_get_type;
|
||||||
fu_coswid_firmware_new;
|
fu_coswid_firmware_new;
|
||||||
|
47
plugins/ch341a/README.md
Normal file
47
plugins/ch341a/README.md
Normal 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`.
|
2
plugins/ch341a/ch341a.quirk
Normal file
2
plugins/ch341a/ch341a.quirk
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[USB\VID_1A86&PID_5512]
|
||||||
|
Plugin = ch341a
|
444
plugins/ch341a/fu-ch341a-cfi-device.c
Normal file
444
plugins/ch341a/fu-ch341a-cfi-device.c
Normal 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;
|
||||||
|
}
|
12
plugins/ch341a/fu-ch341a-cfi-device.h
Normal file
12
plugins/ch341a/fu-ch341a-cfi-device.h
Normal 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)
|
272
plugins/ch341a/fu-ch341a-device.c
Normal file
272
plugins/ch341a/fu-ch341a-device.c
Normal 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;
|
||||||
|
}
|
17
plugins/ch341a/fu-ch341a-device.h
Normal file
17
plugins/ch341a/fu-ch341a-device.h
Normal 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);
|
24
plugins/ch341a/fu-plugin-ch341a.c
Normal file
24
plugins/ch341a/fu-plugin-ch341a.c
Normal 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
68
plugins/ch341a/lsusb.txt
Normal 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)
|
33
plugins/ch341a/meson.build
Normal file
33
plugins/ch341a/meson.build
Normal 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
|
@ -23,6 +23,7 @@ subdir('bcm57xx')
|
|||||||
subdir('bios')
|
subdir('bios')
|
||||||
subdir('ccgx')
|
subdir('ccgx')
|
||||||
subdir('cfu')
|
subdir('cfu')
|
||||||
|
subdir('ch341a')
|
||||||
subdir('colorhug')
|
subdir('colorhug')
|
||||||
subdir('cpu')
|
subdir('cpu')
|
||||||
subdir('cros-ec')
|
subdir('cros-ec')
|
||||||
|
Loading…
Reference in New Issue
Block a user