mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-04 04:15:11 +00:00

Now incorporate is fixed to copy across the properties we need in the superclass, we don't need to do the subclass ->probe(). Note, we still need to do the subclassed ->probe() when using FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT or when looking at properties on the parent device. This also removes the spurious 'already set GType to FuVliUsbhubDevice, ignoring FuVliUsbhubDevice' messages when running the daemon.
451 lines
12 KiB
C
451 lines
12 KiB
C
/*
|
|
* Copyright (C) 2017 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "fu-dfu-common.h"
|
|
#include "fu-dfu-csr-device.h"
|
|
|
|
/**
|
|
* FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY:
|
|
*
|
|
* Respect the write timeout value when performing actions. This is sometimes
|
|
* set to a huge amount of time, and so is not used by default.
|
|
*
|
|
* Since: 1.0.3
|
|
*/
|
|
#define FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY (1 << 0)
|
|
|
|
struct _FuDfuCsrDevice {
|
|
FuHidDevice parent_instance;
|
|
FuDfuState dfu_state;
|
|
guint32 dnload_timeout;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuDfuCsrDevice, fu_dfu_csr_device, FU_TYPE_HID_DEVICE)
|
|
|
|
#define FU_DFU_CSR_REPORT_ID_COMMAND 0x01
|
|
#define FU_DFU_CSR_REPORT_ID_STATUS 0x02
|
|
#define FU_DFU_CSR_REPORT_ID_CONTROL 0x03
|
|
|
|
#define FU_DFU_CSR_COMMAND_HEADER_SIZE 6 /* bytes */
|
|
#define FU_DFU_CSR_COMMAND_UPGRADE 0x01
|
|
|
|
#define FU_DFU_CSR_STATUS_HEADER_SIZE 7
|
|
|
|
#define FU_DFU_CSR_CONTROL_HEADER_SIZE 2 /* bytes */
|
|
#define FU_DFU_CSR_CONTROL_CLEAR_STATUS 0x04
|
|
#define FU_DFU_CSR_CONTROL_RESET 0xff
|
|
|
|
/* maximum firmware packet, including the command header */
|
|
#define FU_DFU_CSR_PACKET_DATA_SIZE 1023 /* bytes */
|
|
|
|
#define FU_DFU_CSR_DEVICE_TIMEOUT 5000 /* ms */
|
|
|
|
static void
|
|
fu_dfu_csr_device_to_string(FuDevice *device, guint idt, GString *str)
|
|
{
|
|
FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device);
|
|
fu_string_append(str, idt, "State", fu_dfu_state_to_string(self->dfu_state));
|
|
fu_string_append_ku(str, idt, "DownloadTimeout", self->dnload_timeout);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_csr_device_attach(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
guint8 buf[] = {FU_DFU_CSR_REPORT_ID_CONTROL, FU_DFU_CSR_CONTROL_RESET};
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(device),
|
|
FU_DFU_CSR_REPORT_ID_CONTROL,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_DFU_CSR_DEVICE_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_IS_FEATURE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to attach: ");
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_csr_device_get_status(FuDfuCsrDevice *self, GError **error)
|
|
{
|
|
guint8 buf[64] = {0};
|
|
|
|
/* hit hardware */
|
|
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
|
|
FU_DFU_CSR_REPORT_ID_STATUS,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_DFU_CSR_DEVICE_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_ALLOW_TRUNC |
|
|
FU_HID_DEVICE_FLAG_IS_FEATURE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to GetStatus: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check packet */
|
|
if (buf[0] != FU_DFU_CSR_REPORT_ID_STATUS) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"GetStatus packet-id was %i expected %i",
|
|
buf[0],
|
|
FU_DFU_CSR_REPORT_ID_STATUS);
|
|
return FALSE;
|
|
}
|
|
|
|
self->dfu_state = buf[5];
|
|
self->dnload_timeout = fu_memread_uint24(&buf[2], G_LITTLE_ENDIAN);
|
|
g_debug("timeout=%" G_GUINT32_FORMAT, self->dnload_timeout);
|
|
g_debug("state=%s", fu_dfu_state_to_string(self->dfu_state));
|
|
g_debug("status=%s", fu_dfu_status_to_string(buf[6]));
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_csr_device_clear_status(FuDfuCsrDevice *self, GError **error)
|
|
{
|
|
guint8 buf[] = {FU_DFU_CSR_REPORT_ID_CONTROL, FU_DFU_CSR_CONTROL_CLEAR_STATUS};
|
|
|
|
/* only clear the status if the state is error */
|
|
if (!fu_dfu_csr_device_get_status(self, error))
|
|
return FALSE;
|
|
if (self->dfu_state != FU_DFU_STATE_DFU_ERROR)
|
|
return TRUE;
|
|
|
|
/* hit hardware */
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
FU_DFU_CSR_REPORT_ID_CONTROL,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_DFU_CSR_DEVICE_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_IS_FEATURE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to ClearStatus: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check the hardware again */
|
|
return fu_dfu_csr_device_get_status(self, error);
|
|
}
|
|
|
|
static GBytes *
|
|
fu_dfu_csr_device_upload_chunk(FuDfuCsrDevice *self, GError **error)
|
|
{
|
|
guint16 data_sz;
|
|
guint8 buf[64] = {0};
|
|
|
|
/* hit hardware */
|
|
if (!fu_hid_device_get_report(FU_HID_DEVICE(self),
|
|
FU_DFU_CSR_REPORT_ID_COMMAND,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_DFU_CSR_DEVICE_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_ALLOW_TRUNC |
|
|
FU_HID_DEVICE_FLAG_IS_FEATURE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to ReadFirmware: ");
|
|
return NULL;
|
|
}
|
|
|
|
/* check command byte */
|
|
if (buf[0] != FU_DFU_CSR_REPORT_ID_COMMAND) {
|
|
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "wrong report ID %u", buf[0]);
|
|
return NULL;
|
|
}
|
|
|
|
/* check the length */
|
|
data_sz = fu_memread_uint16(&buf[1], G_LITTLE_ENDIAN);
|
|
if (data_sz + FU_DFU_CSR_COMMAND_HEADER_SIZE != (guint16)sizeof(buf)) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"wrong data length %" G_GUINT16_FORMAT,
|
|
data_sz);
|
|
return NULL;
|
|
}
|
|
|
|
/* return as bytes */
|
|
return g_bytes_new(buf + FU_DFU_CSR_COMMAND_HEADER_SIZE,
|
|
sizeof(buf) - FU_DFU_CSR_COMMAND_HEADER_SIZE);
|
|
}
|
|
|
|
static GBytes *
|
|
fu_dfu_csr_device_upload(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device);
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
guint32 total_sz = 0;
|
|
gsize done_sz = 0;
|
|
|
|
/* notify UI */
|
|
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_READ);
|
|
|
|
chunks = g_ptr_array_new_with_free_func((GDestroyNotify)g_bytes_unref);
|
|
for (guint32 i = 0; i < 0x3ffffff; i++) {
|
|
g_autoptr(GBytes) chunk = NULL;
|
|
gsize chunk_sz;
|
|
|
|
/* hit hardware */
|
|
chunk = fu_dfu_csr_device_upload_chunk(self, error);
|
|
if (chunk == NULL)
|
|
return NULL;
|
|
chunk_sz = g_bytes_get_size(chunk);
|
|
|
|
/* get the total size using the DFU_CSR header */
|
|
if (i == 0 && chunk_sz >= 10) {
|
|
const guint8 *buf = g_bytes_get_data(chunk, NULL);
|
|
if (memcmp(buf, "DFU_CSR-dfu", 7) == 0) {
|
|
guint16 hdr_ver;
|
|
guint16 hdr_len;
|
|
if (!fu_memread_uint16_safe(buf,
|
|
chunk_sz,
|
|
8,
|
|
&hdr_ver,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return NULL;
|
|
if (hdr_ver != 0x03) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"DFU_CSR header version is "
|
|
"invalid %" G_GUINT16_FORMAT,
|
|
hdr_ver);
|
|
return NULL;
|
|
}
|
|
if (!fu_memread_uint32_safe(buf,
|
|
chunk_sz,
|
|
10,
|
|
&total_sz,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return NULL;
|
|
if (total_sz == 0) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"DFU_CSR header data length "
|
|
"invalid %" G_GUINT32_FORMAT,
|
|
total_sz);
|
|
return NULL;
|
|
}
|
|
if (!fu_memread_uint16_safe(buf,
|
|
chunk_sz,
|
|
14,
|
|
&hdr_len,
|
|
G_LITTLE_ENDIAN,
|
|
error))
|
|
return NULL;
|
|
g_debug("DFU_CSR header length: %" G_GUINT16_FORMAT, hdr_len);
|
|
}
|
|
}
|
|
|
|
/* add to chunk array */
|
|
done_sz += chunk_sz;
|
|
g_ptr_array_add(chunks, g_steal_pointer(&chunk));
|
|
fu_progress_set_percentage_full(progress, done_sz, (gsize)total_sz);
|
|
|
|
/* we're done */
|
|
if (chunk_sz < 64 - FU_DFU_CSR_COMMAND_HEADER_SIZE)
|
|
break;
|
|
}
|
|
|
|
/* notify UI */
|
|
return fu_dfu_utils_bytes_join_array(chunks);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_csr_device_download_chunk(FuDfuCsrDevice *self, guint16 idx, GBytes *chunk, GError **error)
|
|
{
|
|
const guint8 *chunk_data;
|
|
gsize chunk_sz = 0;
|
|
guint8 buf[FU_DFU_CSR_PACKET_DATA_SIZE] = {0};
|
|
|
|
/* too large? */
|
|
chunk_data = g_bytes_get_data(chunk, &chunk_sz);
|
|
if (chunk_sz + FU_DFU_CSR_COMMAND_HEADER_SIZE > FU_DFU_CSR_PACKET_DATA_SIZE) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"packet was too large: %" G_GSIZE_FORMAT,
|
|
chunk_sz);
|
|
return FALSE;
|
|
}
|
|
g_debug("writing %" G_GSIZE_FORMAT " bytes of data", chunk_sz);
|
|
|
|
/* create packet */
|
|
buf[0] = FU_DFU_CSR_REPORT_ID_COMMAND;
|
|
buf[1] = FU_DFU_CSR_COMMAND_UPGRADE;
|
|
fu_memwrite_uint16(&buf[2], idx, G_LITTLE_ENDIAN);
|
|
fu_memwrite_uint16(&buf[4], chunk_sz, G_LITTLE_ENDIAN);
|
|
if (!fu_memcpy_safe(buf,
|
|
sizeof(buf),
|
|
FU_DFU_CSR_COMMAND_HEADER_SIZE, /* dst */
|
|
chunk_data,
|
|
chunk_sz,
|
|
0x0, /* src */
|
|
chunk_sz,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* hit hardware */
|
|
if (!fu_hid_device_set_report(FU_HID_DEVICE(self),
|
|
FU_DFU_CSR_REPORT_ID_COMMAND,
|
|
buf,
|
|
sizeof(buf),
|
|
FU_DFU_CSR_DEVICE_TIMEOUT,
|
|
FU_HID_DEVICE_FLAG_IS_FEATURE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to Upgrade: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* wait for hardware */
|
|
if (fu_device_has_private_flag(FU_DEVICE(self), FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY)) {
|
|
g_debug("sleeping for %ums", self->dnload_timeout);
|
|
g_usleep(self->dnload_timeout * 1000);
|
|
}
|
|
|
|
/* get status */
|
|
if (!fu_dfu_csr_device_get_status(self, error))
|
|
return FALSE;
|
|
|
|
/* is still busy */
|
|
if (self->dfu_state == FU_DFU_STATE_DFU_DNBUSY) {
|
|
g_debug("busy, so sleeping a bit longer");
|
|
g_usleep(G_USEC_PER_SEC);
|
|
if (!fu_dfu_csr_device_get_status(self, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* not correct */
|
|
if (self->dfu_state != FU_DFU_STATE_DFU_DNLOAD_IDLE &&
|
|
self->dfu_state != FU_DFU_STATE_DFU_IDLE) {
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"device did not return to IDLE");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_csr_device_download(FuDevice *device,
|
|
FuFirmware *firmware,
|
|
FuProgress *progress,
|
|
FwupdInstallFlags flags,
|
|
GError **error)
|
|
{
|
|
FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device);
|
|
guint idx;
|
|
g_autoptr(GBytes) blob_empty = NULL;
|
|
g_autoptr(GBytes) blob = NULL;
|
|
g_autoptr(GPtrArray) chunks = NULL;
|
|
|
|
/* get default image */
|
|
blob = fu_firmware_get_bytes(firmware, error);
|
|
if (blob == NULL)
|
|
return FALSE;
|
|
|
|
/* notify UI */
|
|
fu_progress_set_status(progress, FWUPD_STATUS_DEVICE_WRITE);
|
|
|
|
/* create chunks */
|
|
chunks = fu_chunk_array_new_from_bytes(blob,
|
|
0x0,
|
|
0x0,
|
|
FU_DFU_CSR_PACKET_DATA_SIZE -
|
|
FU_DFU_CSR_COMMAND_HEADER_SIZE);
|
|
if (chunks->len > G_MAXUINT16) {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"too many chunks for hardware: 0x%x",
|
|
chunks->len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* send to hardware */
|
|
fu_progress_set_steps(progress, chunks->len);
|
|
for (idx = 0; idx < chunks->len; idx++) {
|
|
FuChunk *chk = g_ptr_array_index(chunks, idx);
|
|
g_autoptr(GBytes) blob_tmp = fu_chunk_get_bytes(chk);
|
|
|
|
/* send packet */
|
|
if (!fu_dfu_csr_device_download_chunk(self, idx, blob_tmp, error))
|
|
return FALSE;
|
|
|
|
/* update progress */
|
|
fu_progress_step_done(progress);
|
|
}
|
|
|
|
/* all done */
|
|
blob_empty = g_bytes_new(NULL, 0);
|
|
return fu_dfu_csr_device_download_chunk(self, idx, blob_empty, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_dfu_csr_device_setup(FuDevice *device, GError **error)
|
|
{
|
|
FuDfuCsrDevice *self = FU_DFU_CSR_DEVICE(device);
|
|
|
|
/* FuUsbDevice->setup */
|
|
if (!FU_DEVICE_CLASS(fu_dfu_csr_device_parent_class)->setup(device, error))
|
|
return FALSE;
|
|
|
|
if (!fu_dfu_csr_device_clear_status(self, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_dfu_csr_device_set_progress(FuDevice *self, FuProgress *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_RESTART, 2, "detach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 94, "write");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach");
|
|
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload");
|
|
}
|
|
|
|
static void
|
|
fu_dfu_csr_device_init(FuDfuCsrDevice *self)
|
|
{
|
|
fu_device_add_protocol(FU_DEVICE(self), "com.qualcomm.dfu");
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE);
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID);
|
|
fu_device_set_firmware_gtype(FU_DEVICE(self), FU_TYPE_DFU_FIRMWARE);
|
|
fu_device_register_private_flag(FU_DEVICE(self),
|
|
FU_DFU_CSR_DEVICE_FLAG_REQUIRE_DELAY,
|
|
"require-delay");
|
|
}
|
|
|
|
static void
|
|
fu_dfu_csr_device_class_init(FuDfuCsrDeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
klass_device->to_string = fu_dfu_csr_device_to_string;
|
|
klass_device->write_firmware = fu_dfu_csr_device_download;
|
|
klass_device->dump_firmware = fu_dfu_csr_device_upload;
|
|
klass_device->attach = fu_dfu_csr_device_attach;
|
|
klass_device->setup = fu_dfu_csr_device_setup;
|
|
klass_device->set_progress = fu_dfu_csr_device_set_progress;
|
|
}
|