pixart: Check firmware compatibility with hardware before flashing

Also add low-battery detection to ensure the update is safe to apply.
This commit is contained in:
JimmyYu 2021-03-09 17:32:29 +08:00 committed by Ricardo Cañuelo Navarro
parent 7752ffe344
commit 18d79a0625
4 changed files with 171 additions and 27 deletions

View File

@ -26,6 +26,7 @@ Pixart Imaging, Inc and Primax Electronics, Ltd, e.g.
* `HIDRAW\VEN_093A&DEV_2801`
* `HIDRAW\VEN_0461&DEV_4EEF`
* `HIDRAW\VEN_0461&DEV_4EEF&NAME_${NAME}`
* `HIDRAW\VEN_0461&DEV_4EEF&MODEL_${MODEL_NAME}`
Additionaly, a custom GUID values including the name is used, e.g.

View File

@ -30,6 +30,7 @@
#define FU_PXI_DEVICE_CMD_FW_OTA_INIT_NEW 0x27u
#define FU_PXI_DEVICE_CMD_FW_OTA_RETRANSMIT 0x28u
#define FU_PXI_DEVICE_CMD_FW_OTA_DISCONNECT 0x29u
#define FU_PXI_DEVICE_CMD_FW_OTA_GET_MODEL 0x2bu
#define FU_PXI_DEVICE_OBJECT_SIZE_MAX 4096 /* bytes */
#define FU_PXI_DEVICE_OTA_BUF_SZ 512 /* bytes */
@ -52,6 +53,7 @@ enum ota_spec_check_result {
OTA_PROCESS_ILLEGAL = 3, /* Illegal OTA process */
OTA_RECONNECT = 4, /* Inform OTA app do reconnect */
OTA_FW_IMG_VERSION_ERROR = 5, /* FW image file version check error */
OTA_DEVICE_LOW_BATTERY = 6, /* Device is under low battery */
OTA_SPEC_CHECK_MAX_NUM, /* Max number of OTA driver defined error code */
};
@ -72,6 +74,7 @@ struct _FuPxiDevice {
guint16 mtu_size;
guint16 prn_threshold;
guint8 spec_check_result;
gchar *model_name;
};
G_DEFINE_TYPE (FuPxiDevice, fu_pxi_device, FU_TYPE_UDEV_DEVICE)
@ -102,6 +105,8 @@ fu_pxi_device_spec_check_result_to_string (guint8 spec_check_result)
return "reconnect";
if (spec_check_result == OTA_FW_IMG_VERSION_ERROR)
return "fw-img-version-error";
if (spec_check_result == OTA_DEVICE_LOW_BATTERY)
return "device battery is too low";
return NULL;
}
@ -113,6 +118,7 @@ fu_pxi_device_to_string (FuDevice *device, guint idt, GString *str)
/* FuUdevDevice->to_string */
FU_DEVICE_CLASS (fu_pxi_device_parent_class)->to_string (device, idt, str);
fu_common_string_append_kv (str, idt, "ModelName", self->model_name);
fu_common_string_append_kx (str, idt, "Status", self->status);
fu_common_string_append_kx (str, idt, "NewFlow", self->new_flow);
fu_common_string_append_kx (str, idt, "CurrentObjectOffset", self->offset);
@ -130,9 +136,34 @@ fu_pxi_device_prepare_firmware (FuDevice *device,
FwupdInstallFlags flags,
GError **error)
{
FuPxiDevice *self = FU_PXI_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);
}
@ -396,8 +427,8 @@ fu_pxi_device_reset (FuPxiDevice *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);
fu_byte_array_append_uint8 (req, OTA_RESET);
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_device_set_feature (self, req, error)) {
@ -477,7 +508,7 @@ fu_pxi_device_fw_ota_init_new (FuPxiDevice *self, gsize bufsz, GError **error)
g_set_error (error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"FwInitNew spec check fail with %s [0x%02x]",
"FwInitNew spec check fail: %s [0x%02x]",
fu_pxi_device_spec_check_result_to_string (self->spec_check_result),
self->spec_check_result);
return FALSE;
@ -493,7 +524,7 @@ fu_pxi_device_fw_upgrade (FuPxiDevice *self, FuFirmware *firmware, GError **erro
const gchar *version;
const guint8 *buf;
gsize bufsz = 0;
guint8 fw_version[10] = { 0x0 };
guint8 fw_version[5] = { 0x0 };
guint8 opcode = 0;
guint16 checksum;
g_autoptr(GBytes) fw = NULL;
@ -523,7 +554,7 @@ fu_pxi_device_fw_upgrade (FuPxiDevice *self, FuFirmware *firmware, GError **erro
if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL)
fu_common_dump_raw (G_LOG_DOMAIN, "fw upgrade", req->data, req->len);
/* read fw upgrade command result */
/* wait fw upgrade command result */
if (!fu_pxi_device_wait_notify (self, 0x1, &opcode, NULL, error))
return FALSE;
if (opcode != FU_PXI_DEVICE_CMD_FW_UPGRADE) {
@ -601,8 +632,13 @@ fu_pxi_device_fw_get_info (FuPxiDevice *self, GError **error)
if (!fu_pxi_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_device_get_feature (self, res, FU_PXI_DEVICE_FW_INFO_RET_LEN + 3, error))
return FALSE;
if (!fu_common_read_uint8_safe (res, sizeof(res), 0x4, &opcode, error))
@ -628,6 +664,46 @@ fu_pxi_device_fw_get_info (FuPxiDevice *self, GError **error)
return TRUE;
}
static gboolean
fu_pxi_device_get_model_info (FuPxiDevice *self, GError **error)
{
guint8 res[FU_PXI_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_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_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_device_probe (FuDevice *device, GError **error)
{
@ -645,7 +721,7 @@ fu_pxi_device_setup_guid (FuPxiDevice *self, GError **error)
g_autofree gchar *devid = NULL;
g_autoptr(GString) dev_name = NULL;
/* extra GUID */
/* extra GUID with device name */
if (!fu_pxi_device_get_raw_info (self, &hid_raw_info ,error))
return FALSE;
dev_name = g_string_new (fu_device_get_name (FU_DEVICE (self)));
@ -656,6 +732,23 @@ fu_pxi_device_setup_guid (FuPxiDevice *self, GError **error)
(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,
dev_name->str);
fu_device_add_instance_id (FU_DEVICE (self), devid2);
}
/* set logical id */
fu_device_set_logical_id (FU_DEVICE (self), devid);
#endif
return TRUE;
}
@ -665,10 +758,6 @@ fu_pxi_device_setup (FuDevice *device, GError **error)
{
FuPxiDevice *self = FU_PXI_DEVICE (device);
if (!fu_pxi_device_setup_guid (self ,error)) {
g_prefix_error (error, "failed to setup GUID: ");
return FALSE;
}
if (!fu_pxi_device_fw_ota_init (self, error)) {
g_prefix_error (error, "failed to OTA init: ");
return FALSE;
@ -677,6 +766,14 @@ fu_pxi_device_setup (FuDevice *device, GError **error)
g_prefix_error (error, "failed to get info: ");
return FALSE;
}
if (!fu_pxi_device_get_model_info (self ,error)) {
g_prefix_error (error, "failed to get model: ");
return FALSE;
}
if (!fu_pxi_device_setup_guid (self ,error)) {
g_prefix_error (error, "failed to setup GUID: ");
return FALSE;
}
return TRUE;
}
@ -689,10 +786,19 @@ fu_pxi_device_init (FuPxiDevice *self)
fu_device_add_protocol (FU_DEVICE (self), "com.pixart.rf");
}
static void
fu_pxi_device_finalize (GObject *object)
{
FuPxiDevice *self = FU_PXI_DEVICE (object);
g_free (self->model_name);
}
static void
fu_pxi_device_class_init (FuPxiDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
object_class->finalize = fu_pxi_device_finalize;
klass_device->probe = fu_pxi_device_probe;
klass_device->setup = fu_pxi_device_setup;
klass_device->to_string = fu_pxi_device_to_string;

View File

@ -15,10 +15,25 @@
struct _FuPxiFirmware {
FuFirmware parent_instance;
gchar *model_name;
};
G_DEFINE_TYPE (FuPxiFirmware, fu_pxi_firmware, FU_TYPE_FIRMWARE)
const gchar *
fu_pxi_firmware_get_model_name (FuPxiFirmware *self)
{
g_return_val_if_fail (FU_IS_PXI_FIRMWARE (self), NULL);
return self->model_name;
}
static void
fu_pxi_firmware_to_string (FuFirmware *firmware, guint idt, GString *str)
{
FuPxiFirmware *self = FU_PXI_FIRMWARE (firmware);
fu_common_string_append_kv (str, idt, "ModelName", self->model_name);
}
static gboolean
fu_pxi_firmware_parse (FuFirmware *firmware,
GBytes *fw,
@ -27,14 +42,16 @@ fu_pxi_firmware_parse (FuFirmware *firmware,
FwupdInstallFlags flags,
GError **error)
{
FuPxiFirmware *self = FU_PXI_FIRMWARE (firmware);
const guint8 *buf;
const guint8 tag[] = {
0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
};
gboolean header_ok = TRUE;
gsize bufsz = 0;
guint32 version_raw = 0;
guint8 fw_header[PIXART_RF_FW_HEADER_SIZE];
guint8 model_name[FU_PXI_DEVICE_MODEL_NAME_LEN] = { 0x0 };
g_autofree gchar *version = NULL;
/* get buf */
buf = g_bytes_get_data (fw, &bufsz);
@ -46,11 +63,11 @@ fu_pxi_firmware_parse (FuFirmware *firmware,
return FALSE;
}
/* get fw header */
/* get fw header from buf */
if (!fu_memcpy_safe (fw_header, sizeof(fw_header), 0x0,
buf, bufsz, bufsz - sizeof(fw_header),
sizeof(fw_header), error)) {
g_prefix_error (error, "failed to read fw header ");
g_prefix_error (error, "failed to read fw header: ");
return FALSE;
}
if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL) {
@ -66,24 +83,31 @@ fu_pxi_firmware_parse (FuFirmware *firmware,
&tmp, error))
return FALSE;
if (tmp != tag[i]) {
header_ok = FALSE;
break;
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Fw tag is incorrect");
return FALSE;
}
}
/* set the default version if can not find it in fw bin */
if (header_ok) {
g_autofree gchar *version = NULL;
version_raw = (((guint32) (fw_header[0] - '0')) << 16) +
(((guint32) (fw_header[2] - '0')) << 8) +
(guint32) (fw_header[4] - '0');
fu_firmware_set_version_raw (firmware, version_raw);
version = fu_common_version_from_uint32 (version_raw,
FWUPD_VERSION_FORMAT_DELL_BIOS);
fu_firmware_set_version (firmware, version);
} else {
fu_firmware_set_version (firmware, "0.0.0");
/* set fw version */
version_raw = (((guint32) (fw_header[0] - '0')) << 16) +
(((guint32) (fw_header[2] - '0')) << 8) +
(guint32) (fw_header[4] - '0');
fu_firmware_set_version_raw (firmware, version_raw);
version = fu_common_version_from_uint32 (version_raw,
FWUPD_VERSION_FORMAT_DELL_BIOS);
fu_firmware_set_version (firmware, version);
/* set fw model name */
if (!fu_memcpy_safe (model_name, sizeof(model_name), 0x0,
fw_header, sizeof(fw_header), 0x05,
sizeof(model_name), error)) {
g_prefix_error (error, "failed to get fw model name: ");
return FALSE;
}
self->model_name = g_strndup ((gchar *) model_name, sizeof(model_name));
/* success */
fu_firmware_set_bytes (firmware, fw);
@ -140,12 +164,22 @@ fu_pxi_firmware_init (FuPxiFirmware *self)
{
}
static void
fu_pxi_firmware_finalize (GObject *object)
{
FuPxiFirmware *self = FU_PXI_FIRMWARE (object);
g_free (self->model_name);
}
static void
fu_pxi_firmware_class_init (FuPxiFirmwareClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass);
object_class->finalize = fu_pxi_firmware_finalize;
klass_firmware->parse = fu_pxi_firmware_parse;
klass_firmware->write = fu_pxi_firmware_write;
klass_firmware->to_string = fu_pxi_firmware_to_string;
}
FuFirmware *

View File

@ -8,7 +8,10 @@
#include "fu-firmware.h"
#define FU_PXI_DEVICE_MODEL_NAME_LEN 12 /* bytes */
#define FU_TYPE_PXI_FIRMWARE (fu_pxi_firmware_get_type ())
G_DECLARE_FINAL_TYPE (FuPxiFirmware, fu_pxi_firmware, FU, PXI_FIRMWARE, FuFirmware)
FuFirmware *fu_pxi_firmware_new (void);
const gchar *fu_pxi_firmware_get_model_name (FuPxiFirmware *self);