mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-02 16:45:45 +00:00
701 lines
20 KiB
C
701 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;
|
|
}
|