mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-29 14:34:09 +00:00

The CustomFlags feature is a bit of a hack where we just join the flags and store in the device metadata section as a string. This makes it inefficient to check if just one flag exists as we have to split the string to a temporary array each time. Rather than adding to the hack by splitting, appending (if not exists) then joining again, store the flags in the plugin privdata directly. This allows us to support negating custom properties (e.g. ~hint) and also allows quirks to append custom values without duplicating them on each GUID match, e.g. [USB\VID_17EF&PID_307F] Plugin = customflag1 [USB\VID_17EF&PID_307F&HUB_0002] Flags = customflag2 ...would result in customflag1,customflag2 which is the same as you'd get from an enumerated device flag doing the same thing.
683 lines
20 KiB
C
683 lines
20 KiB
C
/*
|
|
* Copyright (C) 2020 Cypress Semiconductor Corporation.
|
|
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include "fu-ccgx-common.h"
|
|
#include "fu-ccgx-dmc-common.h"
|
|
#include "fu-ccgx-dmc-device.h"
|
|
#include "fu-ccgx-dmc-firmware.h"
|
|
|
|
#define DMC_FW_WRITE_STATUS_RETRY_COUNT 3
|
|
#define DMC_FW_WRITE_STATUS_RETRY_DELAY_MS 30
|
|
|
|
struct _FuCcgxDmcDevice {
|
|
FuUsbDevice parent_instance;
|
|
FWImageType fw_image_type;
|
|
DmcDockIdentity dock_id;
|
|
guint8 ep_intr_in;
|
|
guint8 ep_bulk_out;
|
|
DmcUpdateModel update_model;
|
|
};
|
|
|
|
|
|
/**
|
|
* FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG:
|
|
*
|
|
* Needs a manual replug from the end-user.
|
|
*/
|
|
#define FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG (1 << 0)
|
|
|
|
G_DEFINE_TYPE (FuCcgxDmcDevice, fu_ccgx_dmc_device, FU_TYPE_USB_DEVICE)
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_get_dock_id (FuCcgxDmcDevice *self,
|
|
DmcDockIdentity *dock_id,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (dock_id != NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_DOCK_IDENTITY, /* request */
|
|
0, /* value */
|
|
0, /* index */
|
|
(guint8 *) dock_id, /* data */
|
|
sizeof (DmcDockIdentity), /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "get_dock_id error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_get_dock_status (FuCcgxDmcDevice *self,
|
|
DmcDockStatus *dock_status,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (dock_status != NULL, FALSE);
|
|
|
|
/* read minimum status length */
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_DOCK_STATUS, /* request */
|
|
0, /* value */
|
|
0, /* index */
|
|
(guint8 *) dock_status, /* data */
|
|
DMC_GET_STATUS_MIN_LEN, /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "get_dock_status min size error: ");
|
|
return FALSE;
|
|
}
|
|
if (dock_status->status_length <= sizeof(DmcDockStatus)) {
|
|
/* read full status length */
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_DOCK_STATUS, /* request */
|
|
0, /* value */
|
|
0, /* index */
|
|
(guint8 *) dock_status, /* data */
|
|
sizeof(DmcDockStatus), /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "get_dock_status actual size error: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_send_reset_state_machine (FuCcgxDmcDevice *self, GError **error)
|
|
{
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_RESET_STATE_MACHINE, /* request */
|
|
0, /* value */
|
|
0, /* index */
|
|
0, /* data */
|
|
0, /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "send reset state machine error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_send_sort_reset (FuCcgxDmcDevice *self,
|
|
gboolean reset_later,
|
|
GError **error)
|
|
{
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_SOFT_RESET, /* request */
|
|
reset_later, /* value */
|
|
0, /* index */
|
|
0, /* data */
|
|
0, /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "send reset error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_send_start_upgrade (FuCcgxDmcDevice *self,
|
|
const guint8 *custom_meta_data,
|
|
guint16 custom_meta_bufsz,
|
|
GError **error)
|
|
{
|
|
guint16 value = 0;
|
|
if (custom_meta_bufsz > 0)
|
|
value = 1;
|
|
|
|
if (custom_meta_bufsz > 0 && custom_meta_data == NULL) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid metadata, buffer is NULL but size = %d",custom_meta_bufsz);
|
|
return FALSE;
|
|
}
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_UPGRADE_START, /* request */
|
|
value, /* value */
|
|
1, /* index, forced update for Adicora only, other dock will ignore it */
|
|
(guint8 *)custom_meta_data, /* data */
|
|
custom_meta_bufsz, /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "send reset error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_send_download_trigger (FuCcgxDmcDevice *self,
|
|
DmcTriggerCode trigger,
|
|
GError **error)
|
|
{
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_TRIGGER, /* request */
|
|
trigger, /* value */
|
|
0, /* index */
|
|
0, /* data */
|
|
0, /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "send download trigger error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_send_fwct (FuCcgxDmcDevice *self,
|
|
const guint8 *fwct_buf,
|
|
guint16 fwct_sz,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (fwct_buf != NULL, FALSE);
|
|
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_FWCT_WRITE, /* request */
|
|
0, /* value */
|
|
0, /* index */
|
|
(guint8 *) fwct_buf, /* data */
|
|
fwct_sz, /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "send fwct error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_read_intr_req (FuCcgxDmcDevice *self,
|
|
DmcIntRqt *intr_rqt,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (intr_rqt != NULL, FALSE);
|
|
|
|
if (!g_usb_device_interrupt_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
self->ep_intr_in,
|
|
(guint8 *) intr_rqt,
|
|
sizeof(DmcIntRqt),
|
|
NULL,
|
|
DMC_GET_REQUEST_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "read intr rqt error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_send_write_command (FuCcgxDmcDevice *self,
|
|
guint16 start_row,
|
|
guint16 num_of_row,
|
|
GError **error)
|
|
{
|
|
if (!g_usb_device_control_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_VENDOR,
|
|
G_USB_DEVICE_RECIPIENT_DEVICE,
|
|
DMC_RQT_CODE_IMG_WRITE, /* request */
|
|
start_row, /* value */
|
|
num_of_row, /* index */
|
|
0, /* data */
|
|
0, /* length */
|
|
NULL, /* actual length */
|
|
DMC_CONTROL_TRANSFER_DEFAULT_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "send fwct error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_send_row_data (FuCcgxDmcDevice *self,
|
|
const guint8 *row_buffer,
|
|
guint16 row_size,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (row_buffer != NULL, FALSE);
|
|
|
|
if (!g_usb_device_bulk_transfer (fu_usb_device_get_dev (FU_USB_DEVICE (self)),
|
|
self->ep_bulk_out,
|
|
(guint8 *)row_buffer, row_size, NULL,
|
|
DMC_BULK_OUT_PIPE_TIMEOUT,
|
|
NULL, error)) {
|
|
g_prefix_error (error, "write row data error: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
fu_ccgx_dmc_device_to_string (FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
fu_common_string_append_kv (str, idt, "UpdateModel",
|
|
fu_ccgx_dmc_update_model_type_to_string (self->update_model));
|
|
fu_common_string_append_kv (str, idt, "FwImageType",
|
|
fu_ccgx_fw_image_type_to_string (self->fw_image_type));
|
|
fu_common_string_append_kx (str, idt, "EpBulkOut", self->ep_bulk_out);
|
|
fu_common_string_append_kx (str, idt, "EpIntrIn", self->ep_intr_in);
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_get_image_write_status_cb (FuDevice *device, gpointer user_data, GError **error)
|
|
{
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
DmcIntRqt dmc_int_req = {0};
|
|
|
|
/* get interrupt request */
|
|
if (!fu_ccgx_dmc_device_read_intr_req (self, &dmc_int_req, error)) {
|
|
g_prefix_error (error, "read intr req error in image write status: ");
|
|
return FALSE;
|
|
}
|
|
/* check opcode for fw write */
|
|
if (dmc_int_req.opcode != DMC_INT_OPCODE_IMG_WRITE_STATUS) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid dmc intr req opcode in image write status = %d",
|
|
dmc_int_req.opcode);
|
|
return FALSE;
|
|
}
|
|
|
|
/* retry if data[0] is 1 otherwise error */
|
|
if (dmc_int_req.data[0] != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid dmc intr req data in image write status = %d",
|
|
dmc_int_req.data[0]);
|
|
g_usleep (DMC_FW_WRITE_STATUS_RETRY_DELAY_MS * 1000);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_write_firmware_image (FuDevice *device,
|
|
FuCcgxDmcFirmwareRecord *img_rcd,
|
|
gsize *fw_data_written,
|
|
const gsize fw_data_size,
|
|
GError **error)
|
|
{
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
GPtrArray *seg_records;
|
|
|
|
g_return_val_if_fail (img_rcd != NULL, FALSE);
|
|
g_return_val_if_fail (fw_data_written != NULL, FALSE);
|
|
|
|
/* get segment records */
|
|
seg_records = img_rcd->seg_records;
|
|
for (guint32 seg_index = 0; seg_index < seg_records->len; seg_index++) {
|
|
GPtrArray *data_records = NULL;
|
|
FuCcgxDmcFirmwareSegmentRecord *seg_rcd = g_ptr_array_index (seg_records, seg_index);
|
|
|
|
/* write start row and number of rows to a device */
|
|
if (!fu_ccgx_dmc_device_send_write_command (self,
|
|
seg_rcd->start_row,
|
|
seg_rcd->num_rows,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* get data records */
|
|
data_records = seg_rcd->data_records;
|
|
for (guint32 data_index = 0; data_index < data_records->len; data_index++) {
|
|
GBytes *data_rcd = g_ptr_array_index (data_records, data_index);
|
|
const guint8 *row_buffer = NULL;
|
|
gsize row_size = 0;
|
|
|
|
/* write row data */
|
|
row_buffer = g_bytes_get_data (data_rcd, &row_size);
|
|
if (!fu_ccgx_dmc_device_send_row_data (self,
|
|
row_buffer,
|
|
(guint16) row_size,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* increase fw written size */
|
|
*fw_data_written += row_size;
|
|
fu_device_set_progress_full (device, *fw_data_written, fw_data_size);
|
|
|
|
/* get status */
|
|
if (!fu_device_retry (FU_DEVICE (self),
|
|
fu_ccgx_dmc_get_image_write_status_cb,
|
|
DMC_FW_WRITE_STATUS_RETRY_COUNT,
|
|
NULL, error))
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_write_firmware (FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
FuCcgxDmcFirmwareRecord *img_rcd = NULL;
|
|
DmcIntRqt dmc_int_rqt = {0};
|
|
GBytes *custom_meta_blob;
|
|
GBytes *fwct_blob;
|
|
GPtrArray *image_records;
|
|
const guint8 *custom_meta_data = NULL;
|
|
const guint8 *fwct_buf = NULL;
|
|
gsize custom_meta_bufsz = 0;
|
|
gsize fwct_sz = 0;
|
|
gsize fw_data_size = 0;
|
|
gsize fw_data_written = 0;
|
|
guint8 img_index = 0;
|
|
|
|
/* get fwct record */
|
|
fwct_blob = fu_ccgx_dmc_firmware_get_fwct_record (FU_CCGX_DMC_FIRMWARE (firmware));
|
|
fwct_buf = g_bytes_get_data (fwct_blob, &fwct_sz);
|
|
if (fwct_buf == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid fwct data");
|
|
return FALSE;
|
|
}
|
|
|
|
/* get custom meta record */
|
|
custom_meta_blob = fu_ccgx_dmc_firmware_get_custom_meta_record (FU_CCGX_DMC_FIRMWARE (firmware));
|
|
if (custom_meta_blob != NULL)
|
|
custom_meta_data = g_bytes_get_data (custom_meta_blob, &custom_meta_bufsz);
|
|
|
|
/* reset */
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY);
|
|
if (!fu_ccgx_dmc_device_send_reset_state_machine (self, error))
|
|
return FALSE;
|
|
|
|
/* start fw upgrade with custom metadata */
|
|
if (!fu_ccgx_dmc_device_send_start_upgrade (self, custom_meta_data, custom_meta_bufsz, error))
|
|
return FALSE;
|
|
|
|
/* send fwct data */
|
|
if (!fu_ccgx_dmc_device_send_fwct (self, fwct_buf, fwct_sz, error))
|
|
return FALSE;
|
|
|
|
/* get total fw size */
|
|
image_records = fu_ccgx_dmc_firmware_get_image_records (FU_CCGX_DMC_FIRMWARE (firmware));
|
|
fw_data_size = fu_ccgx_dmc_firmware_get_fw_data_size (FU_CCGX_DMC_FIRMWARE (firmware));
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE);
|
|
while (1) {
|
|
|
|
/* get interrupt request */
|
|
if (!fu_ccgx_dmc_device_read_intr_req (self, &dmc_int_rqt, error))
|
|
return FALSE;
|
|
|
|
/* fw upgrade request */
|
|
if (dmc_int_rqt.opcode != DMC_INT_OPCODE_FW_UPGRADE_RQT)
|
|
break;
|
|
|
|
img_index = dmc_int_rqt.data[0];
|
|
if (img_index >= image_records->len) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid image index %d, expected less than %u",
|
|
img_index, image_records->len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* write image */
|
|
img_rcd = g_ptr_array_index (image_records, img_index);
|
|
if (!fu_ccgx_dmc_write_firmware_image (device, img_rcd, &fw_data_written,
|
|
fw_data_size, error))
|
|
return FALSE;
|
|
}
|
|
|
|
if (dmc_int_rqt.opcode != DMC_INT_OPCODE_FW_UPGRADE_STATUS) {
|
|
if (dmc_int_rqt.opcode == DMC_INT_OPCODE_FWCT_ANALYSIS_STATUS) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"fwct analysis failed with status = %d",
|
|
dmc_int_rqt.data[0]);
|
|
return FALSE;
|
|
}
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid dmc intr req opcode = %d with status = %d",
|
|
dmc_int_rqt.opcode, dmc_int_rqt.data[0]);
|
|
return FALSE;
|
|
}
|
|
|
|
if (dmc_int_rqt.data[0] == DMC_DEVICE_STATUS_UPDATE_PHASE_1_COMPLETE) {
|
|
self->update_model = DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER;
|
|
} else if (dmc_int_rqt.data[0] == DMC_DEVICE_STATUS_FW_DOWNLOADED_UPDATE_PEND) {
|
|
self->update_model = DMC_UPDATE_MODEL_PENDING_RESET;
|
|
} else {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid status code = %u",
|
|
dmc_int_rqt.data[0]);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static FuFirmware *
|
|
fu_ccgx_dmc_device_prepare_firmware (FuDevice *device,
|
|
GBytes *fw,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
g_autoptr(FuFirmware) firmware = fu_ccgx_dmc_firmware_new ();
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
GBytes* custom_meta_blob = NULL;
|
|
gboolean custom_meta_exist = FALSE;
|
|
|
|
/* parse all images */
|
|
if (!fu_firmware_parse (firmware, fw, flags, error))
|
|
return NULL;
|
|
|
|
/* get custom meta record */
|
|
custom_meta_blob = fu_ccgx_dmc_firmware_get_custom_meta_record (FU_CCGX_DMC_FIRMWARE (firmware));
|
|
if (custom_meta_blob)
|
|
if (g_bytes_get_size (custom_meta_blob) > 0)
|
|
custom_meta_exist = TRUE;
|
|
|
|
/* check custom meta flag */
|
|
if (self->dock_id.custom_meta_data_flag != custom_meta_exist) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"custom metadata mismatch");
|
|
return NULL;
|
|
}
|
|
return g_steal_pointer (&firmware);
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_attach (FuDevice *device, GError **error)
|
|
{
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
gboolean manual_replug;
|
|
|
|
manual_replug = fu_device_has_private_flag (device, FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG);
|
|
|
|
if (fu_device_get_update_state (self) != FWUPD_UPDATE_STATE_SUCCESS)
|
|
return TRUE;
|
|
|
|
if (self->update_model == DMC_UPDATE_MODEL_DOWNLOAD_TRIGGER) {
|
|
DmcTriggerCode trigger_code = DMC_TRIGGER_CODE_UPDATE_NOW;
|
|
|
|
if (manual_replug)
|
|
trigger_code = DMC_TRIGGER_CODE_UPDATE_ON_DISCONNECT;
|
|
|
|
if (!fu_ccgx_dmc_device_send_download_trigger (self,
|
|
trigger_code,
|
|
error)) {
|
|
g_prefix_error (error, "download trigger error: ");
|
|
return FALSE;
|
|
}
|
|
} else if (self->update_model == DMC_UPDATE_MODEL_PENDING_RESET) {
|
|
if (!fu_ccgx_dmc_device_send_sort_reset (self,
|
|
manual_replug,
|
|
error)) {
|
|
g_prefix_error (error, "soft reset error: ");
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"invalid update model = %u",
|
|
self->update_model);
|
|
return FALSE;
|
|
}
|
|
|
|
if (manual_replug)
|
|
return TRUE;
|
|
|
|
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
|
|
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
DmcDockStatus dock_status = {0};
|
|
DmcDockIdentity dock_id = {0};
|
|
guint32 version_raw = 0;
|
|
g_autofree gchar *version = NULL;
|
|
|
|
/* FuUsbDevice->setup */
|
|
if (!FU_DEVICE_CLASS (fu_ccgx_dmc_device_parent_class)->setup (device, error))
|
|
return FALSE;
|
|
|
|
/* get dock identity */
|
|
if (!fu_ccgx_dmc_device_get_dock_id (self, &dock_id, error))
|
|
return FALSE;
|
|
|
|
/* store dock identity */
|
|
if (!fu_memcpy_safe ((guint8 *) &self->dock_id, sizeof(DmcDockIdentity), 0x0, /* dst */
|
|
(guint8 *) &dock_id, sizeof(DmcDockIdentity), 0, /* src */
|
|
sizeof(DmcDockIdentity), error))
|
|
return FALSE;
|
|
|
|
/* get dock status */
|
|
if (!fu_ccgx_dmc_device_get_dock_status (self, &dock_status, error))
|
|
return FALSE;
|
|
|
|
/* set composite version */
|
|
version_raw = dock_status.composite_version;
|
|
version = fu_common_version_from_uint32 (version_raw, FWUPD_VERSION_FORMAT_QUAD);
|
|
fu_device_set_version (FU_DEVICE (self), version);
|
|
fu_device_set_version_raw (FU_DEVICE (self), version_raw);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ccgx_dmc_device_set_quirk_kv (FuDevice *device,
|
|
const gchar *key,
|
|
const gchar *value,
|
|
GError **error)
|
|
{
|
|
FuCcgxDmcDevice *self = FU_CCGX_DMC_DEVICE (device);
|
|
|
|
if (g_strcmp0 (key, "CcgxImageKind") == 0) {
|
|
self->fw_image_type = fu_ccgx_fw_image_type_from_string (value);
|
|
if (self->fw_image_type != FW_IMAGE_TYPE_UNKNOWN)
|
|
return TRUE;
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid CcgxImageKind");
|
|
return FALSE;
|
|
}
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"no supported");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_ccgx_dmc_device_init (FuCcgxDmcDevice *self)
|
|
{
|
|
self->ep_intr_in = DMC_INTERRUPT_PIPE_ID;
|
|
self->ep_bulk_out = DMC_BULK_PIPE_ID;
|
|
fu_device_add_protocol (FU_DEVICE (self), "com.cypress.ccgx.dmc");
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_QUAD);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_REQUIRE_AC);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_DUAL_IMAGE);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_SELF_RECOVERY);
|
|
fu_device_add_internal_flag (FU_DEVICE (self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
|
|
fu_device_register_private_flag (FU_DEVICE (self),
|
|
FU_CCGX_DMC_DEVICE_FLAG_HAS_MANUAL_REPLUG,
|
|
"has-manual-replug");
|
|
}
|
|
|
|
static void
|
|
fu_ccgx_dmc_device_class_init (FuCcgxDmcDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
klass_device->to_string = fu_ccgx_dmc_device_to_string;
|
|
klass_device->write_firmware = fu_ccgx_dmc_write_firmware;
|
|
klass_device->prepare_firmware = fu_ccgx_dmc_device_prepare_firmware;
|
|
klass_device->attach = fu_ccgx_dmc_device_attach;
|
|
klass_device->setup = fu_ccgx_dmc_device_setup;
|
|
klass_device->set_quirk_kv = fu_ccgx_dmc_device_set_quirk_kv;
|
|
}
|