mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-06 06:21:20 +00:00
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:
parent
7752ffe344
commit
18d79a0625
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 *
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user