/* * Copyright (C) 2020 Jimmy Yu * Copyright (C) 2020 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #ifdef HAVE_HIDRAW_H #include #include #endif #include #include "fu-pxi-ble-device.h" #include "fu-pxi-common.h" #include "fu-pxi-firmware.h" #define PXI_HID_DEV_OTA_INPUT_REPORT_ID 0x05 #define PXI_HID_DEV_OTA_RETRANSMIT_REPORT_ID 0x06 #define PXI_HID_DEV_OTA_FEATURE_REPORT_ID 0x07 #define PXI_HID_DEV_OTA_REPORT_USAGE_PAGE 0xff02u #define PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE 0xff01u #define ERR_COMMAND_SUCCESS 0x0 #define FU_PXI_DEVICE_OBJECT_SIZE_MAX 4096 /* bytes */ #define FU_PXI_BLE_DEVICE_OTA_BUF_SZ 512 /* bytes */ #define FU_PXI_BLE_DEVICE_NOTIFY_RET_LEN 4 /* bytes */ #define FU_PXI_BLE_DEVICE_FW_INFO_RET_LEN 8 /* bytes */ #define FU_PXI_BLE_DEVICE_NOTIFY_TIMEOUT_MS 5000 #define FU_PXI_BLE_DEVICE_SET_REPORT_RETRIES 10 /* OTA target selection */ enum ota_process_setting { OTA_MAIN_FW, /* Main firmware */ OTA_HELPER_FW, /* Helper firmware */ OTA_EXTERNAL_RESOURCE, /* External resource */ }; struct _FuPxiBleDevice { FuUdevDevice parent_instance; struct ota_fw_state fwstate; guint8 retransmit_id; gchar *model_name; }; G_DEFINE_TYPE(FuPxiBleDevice, fu_pxi_ble_device, FU_TYPE_UDEV_DEVICE) #ifdef HAVE_HIDRAW_H static gboolean fu_pxi_ble_device_get_raw_info(FuPxiBleDevice *self, struct hidraw_devinfo *info, GError **error) { if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRAWINFO, (guint8 *)info, NULL, error)) { return FALSE; } return TRUE; } #endif static void fu_pxi_ble_device_to_string(FuDevice *device, guint idt, GString *str) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); /* FuUdevDevice->to_string */ FU_DEVICE_CLASS(fu_pxi_ble_device_parent_class)->to_string(device, idt, str); fu_common_string_append_kv(str, idt, "ModelName", self->model_name); fu_pxi_ota_fw_state_to_string(&self->fwstate, idt, str); fu_common_string_append_kx(str, idt, "RetransmitID", self->retransmit_id); } static FuFirmware * fu_pxi_ble_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); const gchar *model_name; g_autoptr(FuFirmware) firmware = fu_pxi_firmware_new(); if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; /* check is compatible with hardware */ model_name = fu_pxi_firmware_get_model_name(FU_PXI_FIRMWARE(firmware)); if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0) { if (self->model_name == NULL || model_name == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "legacy device or firmware detected, " "--force required"); return NULL; } if (g_strcmp0(self->model_name, model_name) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "incompatible firmware, got %s, expected %s.", model_name, self->model_name); return NULL; } } return g_steal_pointer(&firmware); } #ifdef HAVE_HIDRAW_H static gboolean fu_pxi_ble_device_set_feature_cb(FuDevice *device, gpointer user_data, GError **error) { GByteArray *req = (GByteArray *)user_data; return fu_udev_device_ioctl(FU_UDEV_DEVICE(device), HIDIOCSFEATURE(req->len), (guint8 *)req->data, NULL, error); } #endif static gboolean fu_pxi_ble_device_set_feature(FuPxiBleDevice *self, GByteArray *req, GError **error) { #ifdef HAVE_HIDRAW_H if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "SetFeature", req->data, req->len); } return fu_device_retry(FU_DEVICE(self), fu_pxi_ble_device_set_feature_cb, FU_PXI_BLE_DEVICE_SET_REPORT_RETRIES, req, error); #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_ble_device_get_feature(FuPxiBleDevice *self, guint8 *buf, guint bufsz, GError **error) { #ifdef HAVE_HIDRAW_H if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGFEATURE(bufsz), buf, NULL, error)) { return FALSE; } if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "GetFeature", buf, bufsz); /* prepend the report-id and cmd for versions of bluez that do not have * https://github.com/bluez/bluez/commit/35a2c50437cca4d26ac6537ce3a964bb509c9b62 */ if (bufsz > 2 && buf[0] != PXI_HID_DEV_OTA_FEATURE_REPORT_ID) { g_debug("doing fixup for old bluez version"); memmove(buf + 2, buf, bufsz - 2); buf[0] = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; buf[1] = 0x0; } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE; #endif } static gboolean fu_pxi_ble_device_search_hid_usage_page(guint8 *report_descriptor, gint size, guint8 *usage_page, guint8 usage_page_sz) { gint pos = 0; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { fu_common_dump_raw(G_LOG_DOMAIN, "target usage_page", usage_page, usage_page_sz); } while (pos < size) { /* HID info define by HID specification */ guint8 item = report_descriptor[pos]; guint8 report_size = item & 0x03; guint8 report_tag = item & 0xF0; guint8 usage_page_tmp[4] = {0x00}; report_size = (report_size == 3) ? 4 : report_size; if (report_tag != 0) { pos += report_size + 1; continue; } memmove(usage_page_tmp, &report_descriptor[pos + 1], report_size); if (memcmp(usage_page, usage_page_tmp, usage_page_sz) == 0) { if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) { g_debug("hit item: %x ", item); fu_common_dump_raw(G_LOG_DOMAIN, "usage_page", usage_page, report_size); g_debug("hit pos %d", pos); } return TRUE; /* finished processing */ } pos += report_size + 1; } return FALSE; /* finished processing */ } static gboolean fu_pxi_ble_device_check_support_report_id(FuPxiBleDevice *self, GError **error) { #ifdef HAVE_HIDRAW_H gint desc_size = 0; g_autoptr(GByteArray) req = g_byte_array_new(); struct hidraw_report_descriptor rpt_desc; /* Get Report Descriptor Size */ if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRDESCSIZE, (guint8 *)&desc_size, NULL, error)) return FALSE; rpt_desc.size = desc_size; if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self), HIDIOCGRDESC, (guint8 *)&rpt_desc, NULL, error)) return FALSE; fu_common_dump_raw(G_LOG_DOMAIN, "HID descriptor", rpt_desc.value, rpt_desc.size); /* check ota retransmit feature report usage page exist or not */ fu_byte_array_append_uint16(req, PXI_HID_DEV_OTA_RETRANSMIT_USAGE_PAGE, G_LITTLE_ENDIAN); if (!fu_pxi_ble_device_search_hid_usage_page(rpt_desc.value, rpt_desc.size, req->data, req->len)) { /* replace retransmit report id with feature report id, if retransmit report id not * found */ self->retransmit_id = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; } return TRUE; #else g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, " not available"); return FALSE #endif } static gboolean fu_pxi_ble_device_fw_ota_check_retransmit(FuPxiBleDevice *self, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); /* write fw ota retransmit command to reset the ota state */ fu_byte_array_append_uint8(req, self->retransmit_id); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_RETRANSMIT); return fu_pxi_ble_device_set_feature(self, req, error); } static gboolean fu_pxi_ble_device_check_support_resume(FuPxiBleDevice *self, FuFirmware *firmware, GError **error) { g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; guint16 checksum_tmp = 0x0; /* get the default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* check offset is invalid or not */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX); if (self->fwstate.offset > chunks->len) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "offset from device is invalid: " "got 0x%x, current maximum 0x%x", self->fwstate.offset, chunks->len); return FALSE; } /* calculate device current checksum */ for (guint i = 0; i < self->fwstate.offset; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); checksum_tmp += fu_pxi_common_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); } /* check current file is different with previous fw bin or not */ if (self->fwstate.checksum != checksum_tmp) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "checksum is different from previous fw: " "got 0x%04x, expected 0x%04x", self->fwstate.checksum, checksum_tmp); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_wait_notify(FuPxiBleDevice *self, goffset port, guint8 *status, guint16 *checksum, GError **error) { g_autoptr(GTimer) timer = g_timer_new(); guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0}; guint8 cmd_status = 0x0; /* skip the wrong report id ,and keep polling until result is correct */ while (g_timer_elapsed(timer, NULL) * 1000.f < FU_PXI_BLE_DEVICE_NOTIFY_TIMEOUT_MS) { if (!fu_udev_device_pread_full(FU_UDEV_DEVICE(self), port, res, (FU_PXI_BLE_DEVICE_NOTIFY_RET_LEN + 1) - port, error)) return FALSE; if (res[0] == PXI_HID_DEV_OTA_INPUT_REPORT_ID) break; } /* timeout */ if (res[0] != PXI_HID_DEV_OTA_INPUT_REPORT_ID) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Timed-out waiting for HID report"); return FALSE; } /* get the opcode if status is not null */ if (status != NULL) { guint8 status_tmp = 0x0; if (!fu_common_read_uint8_safe(res, sizeof(res), 0x1, &status_tmp, error)) return FALSE; /* need check command result if command is fw upgrade */ if (status_tmp == FU_PXI_DEVICE_CMD_FW_UPGRADE) { if (!fu_common_read_uint8_safe(res, sizeof(res), 0x2, &cmd_status, error)) return FALSE; if (cmd_status != ERR_COMMAND_SUCCESS) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "cmd status was 0x%02x", cmd_status); return FALSE; } } /* propagate */ *status = status_tmp; } if (checksum != NULL) { if (!fu_common_read_uint16_safe(res, sizeof(res), 0x3, checksum, G_LITTLE_ENDIAN, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_fw_object_create(FuPxiBleDevice *self, FuChunk *chk, GError **error) { guint8 opcode = 0; g_autoptr(GByteArray) req = g_byte_array_new(); /* request */ fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE); fu_byte_array_append_uint32(req, fu_chunk_get_address(chk), G_LITTLE_ENDIAN); fu_byte_array_append_uint32(req, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* check object create success or not */ if (!fu_pxi_ble_device_wait_notify(self, 0x0, &opcode, NULL, error)) return FALSE; if (opcode != FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwObjectCreate opcode got 0x%02x, expected 0x%02x", opcode, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_write_payload(FuPxiBleDevice *self, FuChunk *chk, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); g_byte_array_append(req, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); return fu_pxi_ble_device_set_feature(self, req, error); } static gboolean fu_pxi_ble_device_write_chunk(FuPxiBleDevice *self, FuChunk *chk, GError **error) { guint32 prn = 0; guint16 checksum; guint16 checksum_device = 0; g_autoptr(GPtrArray) chunks = NULL; /* send create fw object command */ if (!fu_pxi_ble_device_fw_object_create(self, chk, error)) return FALSE; /* write payload */ chunks = fu_chunk_array_new(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), fu_chunk_get_address(chk), 0x0, self->fwstate.mtu_size); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk2 = g_ptr_array_index(chunks, i); if (!fu_pxi_ble_device_write_payload(self, chk2, error)) return FALSE; prn++; /* wait notify from device when PRN over threshold write or * offset reach max object sz or write offset reach fw length */ if (prn >= self->fwstate.prn_threshold || i == chunks->len - 1) { guint8 opcode = 0; if (!fu_pxi_ble_device_wait_notify(self, 0x0, &opcode, &checksum_device, error)) return FALSE; if (opcode != FU_PXI_DEVICE_CMD_FW_WRITE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwWrite opcode invalid 0x%02x", opcode); return FALSE; } prn = 0; } } /* the last chunk */ checksum = fu_pxi_common_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk)); self->fwstate.checksum += checksum; if (checksum_device != self->fwstate.checksum) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "checksum fail, got 0x%04x, expected 0x%04x", checksum_device, checksum); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_reset(FuPxiBleDevice *self, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_MCU_RESET); /* OTA reset command */ fu_byte_array_append_uint8(req, OTA_RESET); /* OTA reset reason */ fu_device_set_status(FU_DEVICE(self), FWUPD_STATUS_DEVICE_RESTART); if (!fu_pxi_ble_device_set_feature(self, req, error)) { g_prefix_error(error, "failed to reset: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_fw_ota_init(FuPxiBleDevice *self, GError **error) { g_autoptr(GByteArray) req = g_byte_array_new(); /* write fw ota init command */ fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_INIT); return fu_pxi_ble_device_set_feature(self, req, error); } static gboolean fu_pxi_ble_device_fw_ota_init_new(FuPxiBleDevice *self, gsize bufsz, GError **error) { guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 fw_version[10] = {0x0}; g_autoptr(GByteArray) req = g_byte_array_new(); /* write fw ota init new command */ fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW); fu_byte_array_append_uint32(req, bufsz, G_LITTLE_ENDIAN); fu_byte_array_append_uint8(req, 0x0); /* OTA setting */ g_byte_array_append(req, fw_version, sizeof(fw_version)); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* delay for BLE device read command */ g_usleep(10 * 1000); /* read fw ota init new command */ res[0] = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; res[1] = FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW; if (!fu_pxi_ble_device_get_feature(self, res, sizeof(res), error)) return FALSE; /* shared state */ if (!fu_pxi_ota_fw_state_parse(&self->fwstate, res, sizeof(res), 0x05, error)) return FALSE; if (self->fwstate.spec_check_result != OTA_SPEC_CHECK_OK) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwInitNew spec check fail: %s [0x%02x]", fu_pxi_spec_check_result_to_string(self->fwstate.spec_check_result), self->fwstate.spec_check_result); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_fw_upgrade(FuPxiBleDevice *self, FuFirmware *firmware, GError **error) { const gchar *version; const guint8 *buf; gsize bufsz = 0; guint8 fw_version[5] = {0x0}; guint8 opcode = 0; guint16 checksum; g_autoptr(GBytes) fw = NULL; g_autoptr(GByteArray) req = g_byte_array_new(); fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; buf = g_bytes_get_data(fw, &bufsz); checksum = fu_pxi_common_sum16(buf, bufsz); fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_UPGRADE); fu_byte_array_append_uint32(req, bufsz, G_LITTLE_ENDIAN); fu_byte_array_append_uint16(req, checksum, G_LITTLE_ENDIAN); version = fu_firmware_get_version(firmware); if (!fu_memcpy_safe(fw_version, sizeof(fw_version), 0x0, /* dst */ (guint8 *)version, strlen(version), 0x0, /* src */ strlen(version), error)) return FALSE; g_byte_array_append(req, fw_version, sizeof(fw_version)); /* send fw upgrade command */ fu_device_set_status(FU_DEVICE(self), FWUPD_STATUS_DEVICE_VERIFY); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL) fu_common_dump_raw(G_LOG_DOMAIN, "fw upgrade", req->data, req->len); /* wait fw upgrade command result */ if (!fu_pxi_ble_device_wait_notify(self, 0x1, &opcode, NULL, error)) { g_prefix_error(error, "FwUpgrade command fail, " "fw-checksum: 0x%04x fw-size: %" G_GSIZE_FORMAT ": ", checksum, bufsz); return FALSE; } if (opcode != FU_PXI_DEVICE_CMD_FW_UPGRADE) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "FwUpgrade opcode invalid 0x%02x", opcode); return FALSE; } /* success */ return TRUE; } static gboolean fu_pxi_ble_device_write_firmware(FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); g_autoptr(GBytes) fw = NULL; g_autoptr(GPtrArray) chunks = NULL; g_autoptr(GError) error_local = NULL; /* get the default image */ fw = fu_firmware_get_bytes(firmware, error); if (fw == NULL) return FALSE; /* send fw ota retransmit command to reset status */ fu_device_set_status(device, FWUPD_STATUS_DEVICE_BUSY); if (!fu_pxi_ble_device_fw_ota_check_retransmit(self, error)) { g_prefix_error(error, "failed to OTA check retransmit: "); return FALSE; } /* send fw ota init command */ if (!fu_pxi_ble_device_fw_ota_init(self, error)) return FALSE; if (!fu_pxi_ble_device_fw_ota_init_new(self, g_bytes_get_size(fw), error)) return FALSE; /* prepare write fw into device */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, FU_PXI_DEVICE_OBJECT_SIZE_MAX); if (!fu_pxi_ble_device_check_support_resume(self, firmware, &error_local)) { g_debug("do not resume: %s", error_local->message); self->fwstate.offset = 0; self->fwstate.checksum = 0; } /* write fw into device */ fu_device_set_status(device, FWUPD_STATUS_DEVICE_WRITE); for (guint i = self->fwstate.offset; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_pxi_ble_device_write_chunk(self, chk, error)) return FALSE; fu_device_set_progress_full(device, (gsize)i, (gsize)chunks->len); } /* fw upgrade command */ if (!fu_pxi_ble_device_fw_upgrade(self, firmware, error)) return FALSE; /* send device reset command */ return fu_pxi_ble_device_reset(self, error); } static gboolean fu_pxi_ble_device_fw_get_info(FuPxiBleDevice *self, GError **error) { guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 opcode = 0x0; guint16 checksum = 0; g_autofree gchar *version_str = NULL; g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_GET_INFO); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* delay for BLE device read command */ g_usleep(10 * 1000); res[0] = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; res[1] = FU_PXI_DEVICE_CMD_FW_GET_INFO; if (!fu_pxi_ble_device_get_feature(self, res, FU_PXI_BLE_DEVICE_FW_INFO_RET_LEN + 3, error)) return FALSE; if (!fu_common_read_uint8_safe(res, sizeof(res), 0x4, &opcode, error)) return FALSE; if (opcode != FU_PXI_DEVICE_CMD_FW_GET_INFO) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "FwGetInfo opcode invalid 0x%02x", opcode); return FALSE; } /* set current version */ version_str = g_strndup((gchar *)res + 0x6, 5); fu_device_set_version(FU_DEVICE(self), version_str); /* add current checksum */ if (!fu_common_read_uint16_safe(res, sizeof(res), 0xb, &checksum, G_LITTLE_ENDIAN, error)) return FALSE; /* success */ return TRUE; } static gboolean fu_pxi_ble_device_get_model_info(FuPxiBleDevice *self, GError **error) { guint8 res[FU_PXI_BLE_DEVICE_OTA_BUF_SZ] = {0x0}; guint8 opcode = 0x0; guint8 model_name[FU_PXI_DEVICE_MODEL_NAME_LEN] = {0x0}; g_autoptr(GByteArray) req = g_byte_array_new(); fu_byte_array_append_uint8(req, PXI_HID_DEV_OTA_FEATURE_REPORT_ID); fu_byte_array_append_uint8(req, FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL); if (!fu_pxi_ble_device_set_feature(self, req, error)) return FALSE; /* delay for BLE device read command */ g_usleep(10 * 1000); res[0] = PXI_HID_DEV_OTA_FEATURE_REPORT_ID; if (!fu_pxi_ble_device_get_feature(self, res, sizeof(res), error)) return FALSE; if (!fu_common_read_uint8_safe(res, sizeof(res), 0x4, &opcode, error)) return FALSE; /* old firmware */ if (opcode != FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL) return TRUE; /* get model from res */ if (!fu_memcpy_safe(model_name, sizeof(model_name), 0x0, /* dst */ (guint8 *)res, sizeof(res), 0x6, /* src */ sizeof(model_name), error)) return FALSE; g_clear_pointer(&self->model_name, g_free); if (model_name[0] != 0x00 && model_name[0] != 0xFF) self->model_name = g_strndup((gchar *)model_name, sizeof(model_name)); /* success */ return TRUE; } static gboolean fu_pxi_ble_device_probe(FuDevice *device, GError **error) { /* set the logical and physical ID */ if (!fu_udev_device_set_logical_id(FU_UDEV_DEVICE(device), "hid", error)) return FALSE; return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "hid", error); } static gboolean fu_pxi_ble_device_setup_guid(FuPxiBleDevice *self, GError **error) { #ifdef HAVE_HIDRAW_H struct hidraw_devinfo hid_raw_info = {0x0}; g_autofree gchar *devid = NULL; g_autoptr(GString) dev_name = NULL; /* extra GUID with device name */ if (!fu_pxi_ble_device_get_raw_info(self, &hid_raw_info, error)) return FALSE; dev_name = g_string_new(fu_device_get_name(FU_DEVICE(self))); g_string_ascii_up(dev_name); fu_common_string_replace(dev_name, " ", "_"); devid = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&NAME_%s", (guint)hid_raw_info.vendor, (guint)hid_raw_info.product, dev_name->str); fu_device_add_instance_id(FU_DEVICE(self), devid); /* extra GUID with model name*/ if (self->model_name != NULL) { g_autofree gchar *devid2 = NULL; g_autoptr(GString) model_name = NULL; model_name = g_string_new(self->model_name); g_string_ascii_up(model_name); fu_common_string_replace(model_name, " ", "_"); devid2 = g_strdup_printf("HIDRAW\\VEN_%04X&DEV_%04X&MODEL_%s", (guint)hid_raw_info.vendor, (guint)hid_raw_info.product, model_name->str); fu_device_add_instance_id(FU_DEVICE(self), devid2); } #endif return TRUE; } static gboolean fu_pxi_ble_device_setup(FuDevice *device, GError **error) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(device); if (!fu_pxi_ble_device_check_support_report_id(self, error)) { g_prefix_error(error, "failed to check report id: "); return FALSE; } if (!fu_pxi_ble_device_fw_ota_check_retransmit(self, error)) { g_prefix_error(error, "failed to OTA check retransmit: "); return FALSE; } if (!fu_pxi_ble_device_fw_ota_init(self, error)) { g_prefix_error(error, "failed to OTA init: "); return FALSE; } if (!fu_pxi_ble_device_fw_get_info(self, error)) { g_prefix_error(error, "failed to get info: "); return FALSE; } if (!fu_pxi_ble_device_get_model_info(self, error)) { g_prefix_error(error, "failed to get model: "); return FALSE; } if (!fu_pxi_ble_device_setup_guid(self, error)) { g_prefix_error(error, "failed to setup GUID: "); return FALSE; } return TRUE; } static void fu_pxi_ble_device_init(FuPxiBleDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_TRIPLET); fu_device_add_vendor_id(FU_DEVICE(self), "USB:0x093A"); fu_device_add_protocol(FU_DEVICE(self), "com.pixart.rf"); fu_device_retry_set_delay(FU_DEVICE(self), 50); self->retransmit_id = PXI_HID_DEV_OTA_RETRANSMIT_REPORT_ID; } static void fu_pxi_ble_device_finalize(GObject *object) { FuPxiBleDevice *self = FU_PXI_BLE_DEVICE(object); g_free(self->model_name); } static void fu_pxi_ble_device_class_init(FuPxiBleDeviceClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); object_class->finalize = fu_pxi_ble_device_finalize; klass_device->probe = fu_pxi_ble_device_probe; klass_device->setup = fu_pxi_ble_device_setup; klass_device->to_string = fu_pxi_ble_device_to_string; klass_device->write_firmware = fu_pxi_ble_device_write_firmware; klass_device->prepare_firmware = fu_pxi_ble_device_prepare_firmware; }