fwupd/plugins/pixart-rf/fu-pxi-ble-device.c
Richard Hughes 090a7c8b9a Add API to wait for a device
This allows us to ignore all the delays when the device is emulated, with the
idea being to do dozens of device emulations in the CI tests.

Also, do not call fu_progress_sleep() when the device is emulated.
2023-02-23 13:04:11 -06:00

928 lines
27 KiB
C

/*
* Copyright (C) 2020 Jimmy Yu <Jimmy_yu@pixart.com>
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#ifdef HAVE_HIDRAW_H
#include <linux/hidraw.h>
#include <linux/input.h>
#endif
#include <fwupdplugin.h>
#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,
FU_PXI_DEVICE_IOCTL_TIMEOUT,
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_string_append(str, idt, "ModelName", self->model_name);
fu_pxi_ota_fw_state_to_string(&self->fwstate, idt, str);
fu_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,
FU_PXI_DEVICE_IOCTL_TIMEOUT,
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_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,
"<linux/hidraw.h> 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,
FU_PXI_DEVICE_IOCTL_TIMEOUT,
error)) {
return FALSE;
}
if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL)
fu_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,
"<linux/hidraw.h> 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_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_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,
FU_PXI_DEVICE_IOCTL_TIMEOUT,
error))
return FALSE;
rpt_desc.size = desc_size;
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
HIDIOCGRDESC,
(guint8 *)&rpt_desc,
NULL,
FU_PXI_DEVICE_IOCTL_TIMEOUT,
error))
return FALSE;
if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL)
fu_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,
"<linux/hidraw.h> 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,
FuProgress *progress,
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_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(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_memread_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_memread_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_memread_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_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 */
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 */
fu_device_sleep(FU_DEVICE(self), 10); /* ms */
/* 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,
FuProgress *progress,
GError **error)
{
const gchar *version;
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;
checksum = fu_sum16_bytes(fw);
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, g_bytes_get_size(fw), 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 */
if (!fu_pxi_ble_device_set_feature(self, req, error))
return FALSE;
if (g_getenv("FWUPD_PIXART_RF_VERBOSE") != NULL)
fu_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,
g_bytes_get_size(fw));
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,
FuProgress *progress,
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;
/* 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, 9, "ota-init");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "check-support-resume");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 90, NULL);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 1, NULL);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, 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 */
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;
fu_progress_step_done(progress);
/* 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,
fu_progress_get_child(progress),
&error_local)) {
g_debug("do not resume: %s", error_local->message);
self->fwstate.offset = 0;
self->fwstate.checksum = 0;
}
fu_progress_step_done(progress);
/* write fw into device */
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_progress_set_percentage_full(fu_progress_get_child(progress),
(gsize)self->fwstate.offset + 1,
(gsize)chunks->len);
}
fu_progress_step_done(progress);
/* fw upgrade command */
if (!fu_pxi_ble_device_fw_upgrade(self, firmware, fu_progress_get_child(progress), error))
return FALSE;
fu_progress_step_done(progress);
/* send device reset command */
if (!fu_pxi_ble_device_reset(self, error))
return FALSE;
fu_progress_step_done(progress);
/* success */
return TRUE;
}
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 */
fu_device_sleep(FU_DEVICE(self), 10); /* ms */
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_memread_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_memread_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 */
fu_device_sleep(FU_DEVICE(self), 10); /* ms */
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_memread_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
FuDevice *device = FU_DEVICE(self);
struct hidraw_devinfo hid_raw_info = {0x0};
g_autoptr(GString) dev_name = NULL;
g_autoptr(GString) model_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(device));
g_string_ascii_up(dev_name);
fu_string_replace(dev_name, " ", "_");
/* extra GUID with model name*/
model_name = g_string_new(self->model_name);
g_string_ascii_up(model_name);
fu_string_replace(model_name, " ", "_");
/* generate IDs */
fu_device_add_instance_u16(device, "VEN", hid_raw_info.vendor);
fu_device_add_instance_u16(device, "DEV", hid_raw_info.product);
fu_device_add_instance_str(device, "NAME", dev_name->str);
fu_device_add_instance_str(device, "MODEL", model_name->str);
if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "NAME", NULL))
return FALSE;
if (!fu_device_build_instance_id(device, error, "HIDRAW", "VEN", "DEV", "MODEL", NULL))
return FALSE;
#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_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, 0, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 98, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 0, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 2, "reload");
}
static void
fu_pxi_ble_device_init(FuPxiBleDevice *self)
{
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
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);
G_OBJECT_CLASS(fu_pxi_ble_device_parent_class)->finalize(object);
}
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;
klass_device->set_progress = fu_pxi_ble_device_set_progress;
}