mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-06 14:32:56 +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_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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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
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('ccgx')
|
||||
subdir('cfu')
|
||||
subdir('ch341a')
|
||||
subdir('colorhug')
|
||||
subdir('cpu')
|
||||
subdir('cros-ec')
|
||||
|
Loading…
Reference in New Issue
Block a user