/* * Copyright (C) 2021 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-cfu-device.h" #include "fu-cfu-module.h" struct _FuCfuDevice { FuHidDevice parent_instance; guint8 protocol_version; }; G_DEFINE_TYPE(FuCfuDevice, fu_cfu_device, FU_TYPE_HID_DEVICE) #define FU_CFU_DEVICE_TIMEOUT 5000 /* ms */ #define FU_CFU_FEATURE_SIZE 60 /* bytes */ #define FU_CFU_CMD_GET_FIRMWARE_VERSION 0x00 #define FU_CFU_CMD_SEND_OFFER 0x00 // TODO static void fu_cfu_device_to_string(FuDevice *device, guint idt, GString *str) { FuCfuDevice *self = FU_CFU_DEVICE(device); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_cfu_device_parent_class)->to_string(device, idt, str); fu_common_string_append_kx(str, idt, "ProtocolVersion", self->protocol_version); } static gboolean fu_cfu_device_write_offer(FuCfuDevice *self, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { const guint8 *buf; gsize bufsz = 0; guint8 buf2[FU_CFU_FEATURE_SIZE] = {0}; g_autofree guint8 *buf_tmp = NULL; g_autoptr(GBytes) blob = NULL; /* generate a offer blob */ if (flags & FWUPD_INSTALL_FLAG_FORCE) fu_cfu_offer_set_force_ignore_version(FU_CFU_OFFER(firmware), TRUE); blob = fu_firmware_write(firmware, error); if (blob == NULL) return FALSE; /* send it to the hardware */ buf = g_bytes_get_data(blob, &bufsz); buf_tmp = fu_memdup_safe(buf, bufsz, error); if (buf_tmp == NULL) return FALSE; if (!fu_hid_device_set_report(FU_HID_DEVICE(self), FU_CFU_CMD_SEND_OFFER, buf_tmp, bufsz, FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { g_prefix_error(error, "failed to send offer: "); return FALSE; } if (!fu_hid_device_get_report(FU_HID_DEVICE(self), FU_CFU_CMD_SEND_OFFER, buf2, sizeof(buf2), FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { return FALSE; } g_debug("status:%s reject:%s", fu_cfu_device_offer_to_string(buf2[13]), fu_cfu_device_reject_to_string(buf2[9])); if (buf2[13] != FU_CFU_DEVICE_OFFER_ACCEPT) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not supported: %s", fu_cfu_device_offer_to_string(buf2[13])); return FALSE; } /* success */ return TRUE; } static gboolean fu_cfu_device_write_payload(FuCfuDevice *self, FuFirmware *firmware, FuProgress *progress, GError **error) { g_autoptr(GPtrArray) chunks = NULL; /* write each chunk */ chunks = fu_firmware_get_chunks(firmware, error); if (chunks == NULL) return FALSE; fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); guint8 databuf[60] = {0}; guint8 buf2[60] = {0}; /* flags */ if (i == 0) databuf[0] = FU_CFU_DEVICE_FLAG_FIRST_BLOCK; else if (i == chunks->len - 1) databuf[0] = FU_CFU_DEVICE_FLAG_LAST_BLOCK; /* length */ databuf[1] = fu_chunk_get_data_sz(chk); /* sequence number */ if (!fu_common_write_uint16_safe(databuf, sizeof(databuf), 0x2, i + 1, G_LITTLE_ENDIAN, error)) return FALSE; /* address */ if (!fu_common_write_uint32_safe(databuf, sizeof(databuf), 0x4, fu_chunk_get_address(chk), G_LITTLE_ENDIAN, error)) return FALSE; /* data */ if (!fu_memcpy_safe(databuf, sizeof(databuf), 0x8, /* dst */ fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), 0x0, /* src */ fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "memory copy for payload fail: "); return FALSE; } // send // revc if (buf2[5] != FU_CFU_DEVICE_STATUS_SUCCESS) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "failed to send chunk %u: %s", i + 1, fu_cfu_device_status_to_string(buf2[5])); return FALSE; } fu_progress_step_done(progress); } /* success */ return TRUE; } static gboolean fu_cfu_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuCfuDevice *self = FU_CFU_DEVICE(device); g_autoptr(FuFirmware) fw_offer = NULL; g_autoptr(FuFirmware) fw_payload = NULL; /* 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_BUSY, 2); /* offer */ fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98); /* payload */ /* send offer */ fw_offer = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_HEADER, error); if (fw_offer == NULL) return FALSE; if (!fu_cfu_device_write_offer(self, fw_offer, fu_progress_get_child(progress), flags, error)) return FALSE; fu_progress_step_done(progress); /* send payload */ fw_payload = fu_firmware_get_image_by_id(firmware, FU_FIRMWARE_ID_PAYLOAD, error); if (fw_payload == NULL) return FALSE; if (!fu_cfu_device_write_payload(self, fw_payload, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_cfu_device_setup(FuDevice *device, GError **error) { FuCfuDevice *self = FU_CFU_DEVICE(device); guint8 buf[FU_CFU_FEATURE_SIZE] = {0}; guint8 component_cnt = 0; guint8 tmp = 0; gsize offset = 0; g_autoptr(GHashTable) modules_by_cid = NULL; /* FuHidDevice->setup */ if (!FU_DEVICE_CLASS(fu_cfu_device_parent_class)->setup(device, error)) return FALSE; /* get version */ if (!fu_hid_device_get_report(FU_HID_DEVICE(device), FU_CFU_CMD_GET_FIRMWARE_VERSION, buf, sizeof(buf), FU_CFU_DEVICE_TIMEOUT, FU_HID_DEVICE_FLAG_IS_FEATURE, error)) { return FALSE; } if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x0, &component_cnt, error)) return FALSE; if (!fu_common_read_uint8_safe(buf, sizeof(buf), 0x3, &tmp, error)) return FALSE; self->protocol_version = tmp & 0b1111; /* keep track of all modules so we can work out which are dual bank */ modules_by_cid = g_hash_table_new(g_int_hash, g_int_equal); /* read each component module version */ offset += 4; for (guint i = 0; i < component_cnt; i++) { g_autoptr(FuCfuModule) module = fu_cfu_module_new(device); FuCfuModule *module_tmp; if (!fu_cfu_module_setup(module, buf, sizeof(buf), offset, error)) return FALSE; fu_device_add_child(device, FU_DEVICE(module)); /* same module already exists, so mark both as being dual bank */ module_tmp = g_hash_table_lookup(modules_by_cid, GINT_TO_POINTER(fu_cfu_module_get_component_id(module))); if (module_tmp != NULL) { fu_device_add_flag(FU_DEVICE(module), FWUPD_DEVICE_FLAG_DUAL_IMAGE); fu_device_add_flag(FU_DEVICE(module_tmp), FWUPD_DEVICE_FLAG_DUAL_IMAGE); } else { g_hash_table_insert(modules_by_cid, GINT_TO_POINTER(fu_cfu_module_get_component_id(module)), module); } /* done */ offset += 0x8; } /* success */ return TRUE; } static void fu_cfu_device_init(FuCfuDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE); } static void fu_cfu_device_class_init(FuCfuDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->setup = fu_cfu_device_setup; klass_device->to_string = fu_cfu_device_to_string; klass_device->write_firmware = fu_cfu_device_write_firmware; }