diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in index af33441b2..961aa433f 100644 --- a/contrib/fwupd.spec.in +++ b/contrib/fwupd.spec.in @@ -415,6 +415,7 @@ done %{_libdir}/fwupd-plugins-3/libfu_plugin_optionrom.so %{_libdir}/fwupd-plugins-3/libfu_plugin_pci_bcr.so %{_libdir}/fwupd-plugins-3/libfu_plugin_pci_mei.so +%{_libdir}/fwupd-plugins-3/libfu_plugin_pixart_rf.so %{_libdir}/fwupd-plugins-3/libfu_plugin_redfish.so %{_libdir}/fwupd-plugins-3/libfu_plugin_rts54hid.so %{_libdir}/fwupd-plugins-3/libfu_plugin_rts54hub.so diff --git a/meson.build b/meson.build index 628b5b67d..3822eb892 100644 --- a/meson.build +++ b/meson.build @@ -281,6 +281,9 @@ endif if cc.has_header('linux/ethtool.h') conf.set('HAVE_ETHTOOL_H', '1') endif +if cc.has_header('linux/hidraw.h') + conf.set('HAVE_HIDRAW_H', '1') +endif if cc.has_header('sys/mman.h') conf.set('HAVE_MMAN_H', '1') endif diff --git a/plugins/meson.build b/plugins/meson.build index dfcfb1f4b..319267c86 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -32,6 +32,7 @@ subdir('upower') subdir('wacom-usb') subdir('vli') subdir('goodix-moc') +subdir('pixart-rf') if get_option('plugin_msr') subdir('msr') diff --git a/plugins/pixart-rf/README.md b/plugins/pixart-rf/README.md new file mode 100644 index 000000000..4800a6b1b --- /dev/null +++ b/plugins/pixart-rf/README.md @@ -0,0 +1,34 @@ +PixArt RF Devices Support +========================= + +Introduction +------------ + +This plugin allows the user to update any supported Pixart RF Device using a +custom HID-based OTA protocol + +Firmware Format +--------------- + +The daemon will decompress the cabinet archive and extract a firmware blob in +an unspecified binary file format. + +This plugin supports the following protocol ID: + + * com.pixart.rf + +GUID Generation +--------------- + +These devices use the standard USB DeviceInstanceId values, e.g. + + * `HIDRAW\VEN_093A&DEV_2801` + +Vendor ID Security +------------------ + +The vendor ID is set from the USB vendor, in this instance set to `USB:0x093A` + +External interface access +------------------------- +This plugin requires ioctl `HIDIOCSFEATURE` access. diff --git a/plugins/pixart-rf/fu-plugin-pixart-rf.c b/plugins/pixart-rf/fu-plugin-pixart-rf.c new file mode 100644 index 000000000..1acff08a2 --- /dev/null +++ b/plugins/pixart-rf/fu-plugin-pixart-rf.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2020 Jimmy Yu + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-plugin-vfuncs.h" + +#include "fu-hash.h" + +#include "fu-pxi-device.h" +#include "fu-pxi-firmware.h" + +void +fu_plugin_init (FuPlugin *plugin) +{ + fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); + fu_plugin_add_udev_subsystem (plugin, "hidraw"); + fu_plugin_set_device_gtype (plugin, FU_TYPE_PXI_DEVICE); + fu_plugin_add_firmware_gtype (plugin, "pixart", FU_TYPE_PXI_RF_FIRMWARE); +} diff --git a/plugins/pixart-rf/fu-pxi-device.c b/plugins/pixart-rf/fu-pxi-device.c new file mode 100644 index 000000000..73a5ce6e0 --- /dev/null +++ b/plugins/pixart-rf/fu-pxi-device.c @@ -0,0 +1,626 @@ +/* + * 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 "fu-common.h" +#include "fu-chunk.h" + +#include "fu-pxi-device.h" +#include "fu-pxi-firmware.h" + +#define PXI_HID_DEV_OTA_INPUT_REPORT_ID 0x05 +#define PXI_HID_DEV_OTA_OUTPUT_REPORT_ID 0x06 +#define PXI_HID_DEV_OTA_FEATURE_REPORT_ID 0x07 + +#define FU_PXI_DEVICE_CMD_FW_OTA_INIT 0x10u +#define FU_PXI_DEVICE_CMD_FW_WRITE 0x17u +#define FU_PXI_DEVICE_CMD_FW_UPGRADE 0x18u +#define FU_PXI_DEVICE_CMD_FW_MCU_RESET 0x22u +#define FU_PXI_DEVICE_CMD_FW_GET_INFO 0x23u +#define FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE 0x25u +#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_OBJECT_SIZE_MAX 4096 /* bytes */ +#define FU_PXI_DEVICE_OTA_BUF_SZ 32 /* bytes */ +#define FU_PXI_DEVICE_NOTTFY_RET_LEN 4 /* bytes */ +#define FU_PXI_DEVICE_FW_INFO_RET_LEN 8 /* bytes */ + +/* OTA target selection */ +enum ota_process_setting { + OTA_MAIN_FW, /* Main firmware */ + OTA_HELPER_FW, /* Helper firmware */ + OTA_EXTERNAL_RESOURCE, /* External resource */ +}; + +/* OTA spec check result */ +enum ota_spec_check_result { + OTA_SPEC_CHECK_OK = 1, /* Spec check ok */ + OTA_FW_OUT_OF_BOUNDS = 2, /* OTA firmware size out of bound */ + 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_SPEC_CHECK_MAX_NUM, /* Max number of OTA driver defined error code */ +}; + +/* OTA disconnect reason */ +enum ota_disconnect_reason { + OTA_CODE_JUMP = 1, /* OTA code jump */ + OTA_UPDATE_DONE = 2, /* OTA update done */ + OTA_RESET, /* OTA reset */ +}; + +struct _FuPxiDevice { + FuUdevDevice parent_instance; + guint8 status; + guint8 new_flow; + guint16 offset; + guint16 checksum; + guint32 max_object_size; + guint16 mtu_size; + guint16 prn_threshold; + guint8 spec_check_result; +}; + +G_DEFINE_TYPE (FuPxiDevice, fu_pxi_device, FU_TYPE_UDEV_DEVICE) + +static const gchar * +fu_pxi_device_spec_check_result_to_string (guint8 spec_check_result) +{ + if (spec_check_result == OTA_SPEC_CHECK_OK) + return "ok"; + if (spec_check_result == OTA_FW_OUT_OF_BOUNDS) + return "fw-out-of-bounds"; + if (spec_check_result == OTA_PROCESS_ILLEGAL) + return "process-illegal"; + if (spec_check_result == OTA_RECONNECT) + return "reconnect"; + if (spec_check_result == OTA_FW_IMG_VERSION_ERROR) + return "fw-img-version-error"; + return NULL; +} + +static void +fu_pxi_device_to_string (FuDevice *device, guint idt, GString *str) +{ + FuPxiDevice *self = FU_PXI_DEVICE (device); + 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); + fu_common_string_append_kx (str, idt, "CurrentChecksum", self->checksum); + fu_common_string_append_kx (str, idt, "MaxObjectSize", self->max_object_size); + fu_common_string_append_kx (str, idt, "MtuSize", self->mtu_size); + fu_common_string_append_kx (str, idt, "PacketReceiptNotificationThreshold", self->prn_threshold); + fu_common_string_append_kv (str, idt, "SpecCheckResult", + fu_pxi_device_spec_check_result_to_string (self->spec_check_result)); +} + +static FuFirmware * +fu_pxi_device_prepare_firmware (FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + g_autoptr(FuFirmware) firmware = fu_pxi_rf_firmware_new (); + if (!fu_firmware_parse (firmware, fw, flags, error)) + return NULL; + return g_steal_pointer (&firmware); +} + +static gboolean +fu_pxi_device_set_feature (FuPxiDevice *self, const guint8 *buf, guint bufsz, GError **error) +{ +#ifdef HAVE_HIDRAW_H + if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL) + fu_common_dump_raw (G_LOG_DOMAIN, "SetFeature", buf, bufsz); + return fu_udev_device_ioctl (FU_UDEV_DEVICE (self), + HIDIOCSFEATURE(bufsz), (guint8 *) buf, + NULL, error); +#else + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + " not available"); + return FALSE; +#endif +} + +static gboolean +fu_pxi_device_get_feature (FuPxiDevice *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); + return TRUE; +#else + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + " not available"); + return FALSE; +#endif +} + +static guint16 +fu_pxi_device_calculate_checksum (const guint8 *buf, gsize bufsz) +{ + guint16 checksum = 0; + for (gsize idx = 0; idx < bufsz; idx++) + checksum += (guint16) buf[idx]; + return checksum; +} + +static gboolean +fu_pxi_device_check_support_resume (FuPxiDevice *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_image_default_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->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->offset, + chunks->len); + return FALSE; + } + + /* calculate device current checksum */ + for (guint i = 0; i < self->offset; i++) { + FuChunk *chk = g_ptr_array_index (chunks, i); + checksum_tmp += fu_pxi_device_calculate_checksum (chk->data, + chk->data_sz); + } + + /* check current file is different with previous fw bin or not */ + if (self->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->checksum, + checksum_tmp); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_pxi_device_wait_notify (FuPxiDevice *self, + goffset port, + guint8 *status, + guint16 *checksum, + GError **error) +{ + guint8 res[FU_PXI_DEVICE_OTA_BUF_SZ] = { + PXI_HID_DEV_OTA_INPUT_REPORT_ID, + 0x0, + }; + if (!fu_udev_device_pread_full (FU_UDEV_DEVICE (self), + port, res, (FU_PXI_DEVICE_NOTTFY_RET_LEN + 1) - port, + error)) + return FALSE; + if (status != NULL) { + if (!fu_common_read_uint8_safe (res, sizeof(res), 0x1, + status, error)) + return FALSE; + } + 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_device_fw_object_create (FuPxiDevice *self, const 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_OUTPUT_REPORT_ID); + fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_OBJECT_CREATE); + fu_byte_array_append_uint32 (req, chk->address, G_LITTLE_ENDIAN); + fu_byte_array_append_uint32 (req, chk->data_sz, G_LITTLE_ENDIAN); + if (!fu_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0x0, + req->data, req->len, error)) + return FALSE; + + /* check object create success or not */ + if (!fu_pxi_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_device_write_payload (FuPxiDevice *self, const FuChunk *chk, GError **error) +{ + g_autoptr(GByteArray) req = g_byte_array_new (); + fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_OUTPUT_REPORT_ID); + g_byte_array_append (req, chk->data, chk->data_sz); + return fu_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0x0, + req->data, req->len, error); +} + +static gboolean +fu_pxi_device_write_chunk (FuPxiDevice *self, const FuChunk *chk, GError **error) +{ + guint32 prn = 0; + guint16 checksum = fu_pxi_device_calculate_checksum (chk->data, chk->data_sz); + guint16 checksum_device = 0; + g_autoptr(GPtrArray) chunks = NULL; + + /* send create fw object command */ + if (!fu_pxi_device_fw_object_create (self, chk, error)) + return FALSE; + + /* write payload */ + chunks = fu_chunk_array_new (chk->data, chk->data_sz, chk->address, + 0x0, self->mtu_size); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk2 = g_ptr_array_index (chunks, i); + if (!fu_pxi_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->prn_threshold || i == chunks->len - 1) { + guint8 opcode = 0; + if (!fu_pxi_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 */ + self->checksum += checksum; + if (checksum_device != self->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_device_reset (FuPxiDevice *self, GError **error) +{ + guint8 req[FU_PXI_DEVICE_OTA_BUF_SZ] = { + PXI_HID_DEV_OTA_OUTPUT_REPORT_ID, + FU_PXI_DEVICE_CMD_FW_MCU_RESET, + OTA_RESET, + }; + fu_device_set_status (FU_DEVICE (self), FWUPD_STATUS_DEVICE_RESTART); + if (!fu_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0, + req, 0x3, + error)) { + g_prefix_error (error, "failed to reset: "); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_pxi_device_fw_ota_init (FuPxiDevice *self, GError **error) +{ + const guint8 req[] = { + PXI_HID_DEV_OTA_OUTPUT_REPORT_ID, + FU_PXI_DEVICE_CMD_FW_OTA_INIT, + }; + return fu_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0, + req, sizeof(req), error); +} + +static gboolean +fu_pxi_device_fw_ota_init_new (FuPxiDevice *self, gsize bufsz, GError **error) +{ + guint8 res[FU_PXI_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_device_set_feature (self, req->data, req->len, 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_device_get_feature (self, res, sizeof(res), error)) + return FALSE; + + /* shared state */ + if (!fu_common_read_uint8_safe (res, sizeof(res), 0x3, + &self->status, error)) + return FALSE; + if (!fu_common_read_uint8_safe (res, sizeof(res), 0x4, + &self->new_flow, error)) + return FALSE; + if (!fu_common_read_uint16_safe (res, sizeof(res), 0x5, + &self->offset, G_LITTLE_ENDIAN, error)) + return FALSE; + if (!fu_common_read_uint16_safe (res, sizeof(res), 0x7, + &self->checksum, G_LITTLE_ENDIAN, error)) + return FALSE; + if (!fu_common_read_uint32_safe (res, sizeof(res), 0x9, + &self->max_object_size, G_LITTLE_ENDIAN, error)) + return FALSE; + if (!fu_common_read_uint16_safe (res, sizeof(res), 0xd, + &self->mtu_size, G_LITTLE_ENDIAN, error)) + return FALSE; + if (!fu_common_read_uint16_safe (res, sizeof(res), 0xf, + &self->prn_threshold, G_LITTLE_ENDIAN, error)) + return FALSE; + if (!fu_common_read_uint8_safe (res, sizeof(res), 0x11, + &self->spec_check_result, error)) + return FALSE; + + /* sanity check */ + if (self->spec_check_result != OTA_SPEC_CHECK_OK) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_READ, + "FwInitNew spec check fail with %s [0x%02x]", + fu_pxi_device_spec_check_result_to_string (self->spec_check_result), + self->spec_check_result); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_pxi_device_fw_upgrade (FuPxiDevice *self, FuFirmware *firmware, GError **error) +{ + const gchar *version; + const guint8 *buf; + gsize bufsz = 0; + guint8 fw_version[10] = { 0x0 }; + guint8 opcode = 0; + guint16 checksum; + g_autoptr(GBytes) fw = NULL; + g_autoptr(GByteArray) req = g_byte_array_new (); + + fw = fu_firmware_get_image_default_bytes (firmware, error); + if (fw == NULL) + return FALSE; + buf = g_bytes_get_data (fw, &bufsz); + checksum = fu_pxi_device_calculate_checksum (buf, bufsz); + fu_byte_array_append_uint8 (req, PXI_HID_DEV_OTA_OUTPUT_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_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0, + req->data, req->len, error)) + return FALSE; + 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 */ + if (!fu_pxi_device_wait_notify (self, 0x1, &opcode, NULL, error)) + 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_device_write_firmware (FuDevice *device, + FuFirmware *firmware, + FwupdInstallFlags flags, + GError **error) +{ + FuPxiDevice *self = FU_PXI_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_image_default_bytes (firmware, error); + if (fw == NULL) + return FALSE; + + /* send fw ota init command */ + fu_device_set_status (device, FWUPD_STATUS_DEVICE_BUSY); + if (!fu_pxi_device_fw_ota_init (self, error)) + return FALSE; + if (!fu_pxi_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_device_check_support_resume (self, firmware, &error_local)) { + g_debug ("do not resume: %s", error_local->message); + self->offset = 0; + self->checksum = 0; + } + + /* write fw into device */ + fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); + for (guint i = self->offset; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index (chunks, i); + if (!fu_pxi_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_device_fw_upgrade (self, firmware, error)) + return FALSE; + + /* send device reset command */ + return fu_pxi_device_reset (self, error); +} + +static gboolean +fu_pxi_device_fw_get_info (FuPxiDevice *self, GError **error) +{ + guint8 res[FU_PXI_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_OUTPUT_REPORT_ID); + fu_byte_array_append_uint8 (req, FU_PXI_DEVICE_CMD_FW_GET_INFO); + if (!fu_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0, + req->data, req->len, error)) + return FALSE; + + 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 + 1, error)) + return FALSE; + + if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL) + fu_common_dump_raw (G_LOG_DOMAIN, "req", (guint8 *) req, sizeof(req)); + if (!fu_common_read_uint8_safe (res, sizeof(res), 0x2, &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 + 0x4, 5); + fu_device_set_version (FU_DEVICE (self), version_str); + + /* add current checksum */ + if (!fu_common_read_uint16_safe (res, sizeof(res), 0x9, + &checksum, G_LITTLE_ENDIAN, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_pxi_device_probe (FuDevice *device, GError **error) +{ + /* set the physical ID */ + return fu_udev_device_set_physical_id (FU_UDEV_DEVICE (device), "hid", error); +} + +static gboolean +fu_pxi_device_setup (FuDevice *device, GError **error) +{ + FuPxiDevice *self = FU_PXI_DEVICE (device); + if (!fu_pxi_device_fw_ota_init (self, error)) + return FALSE; + if (!fu_pxi_device_fw_get_info (self, error)) + return FALSE; + return TRUE; +} + +static void +fu_pxi_device_init (FuPxiDevice *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_set_vendor_id (FU_DEVICE (self), "USB:0x093A"); + fu_device_set_protocol (FU_DEVICE (self), "com.pixart.rf"); +} + +static void +fu_pxi_device_class_init (FuPxiDeviceClass *klass) +{ + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + klass_device->probe = fu_pxi_device_probe; + klass_device->setup = fu_pxi_device_setup; + klass_device->to_string = fu_pxi_device_to_string; + klass_device->write_firmware = fu_pxi_device_write_firmware; + klass_device->prepare_firmware = fu_pxi_device_prepare_firmware; +} diff --git a/plugins/pixart-rf/fu-pxi-device.h b/plugins/pixart-rf/fu-pxi-device.h new file mode 100644 index 000000000..097757d78 --- /dev/null +++ b/plugins/pixart-rf/fu-pxi-device.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2020 Jimmy Yu + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-udev-device.h" + +#define FU_TYPE_PXI_DEVICE (fu_pxi_device_get_type ()) + +G_DECLARE_FINAL_TYPE (FuPxiDevice, fu_pxi_device, FU, PXI_DEVICE, FuUdevDevice) diff --git a/plugins/pixart-rf/fu-pxi-firmware.c b/plugins/pixart-rf/fu-pxi-firmware.c new file mode 100755 index 000000000..b72f5eb46 --- /dev/null +++ b/plugins/pixart-rf/fu-pxi-firmware.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 Jimmy Yu + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include "config.h" + +#include "fu-common.h" + +#include "fu-pxi-firmware.h" + +struct _FuPxiRfFirmware { + FuFirmware parent_instance; +}; + +G_DEFINE_TYPE (FuPxiRfFirmware, fu_pxi_rf_firmware, FU_TYPE_FIRMWARE) + +static gboolean +fu_pxi_rf_firmware_parse (FuFirmware *firmware, + GBytes *fw, + guint64 addr_start, + guint64 addr_end, + FwupdInstallFlags flags, + GError **error) +{ + gsize bufsz = 0; + guint8 pos = 0; + const guint8 *buf; + g_autoptr(FuFirmwareImage) img = fu_firmware_image_new (fw); + + /* get buf */ + buf = g_bytes_get_data (fw, &bufsz); + if (bufsz < 32) { + g_set_error (error, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "firmware invalid, too small!"); + return FALSE; + } + if (g_getenv ("FWUPD_PIXART_RF_VERBOSE") != NULL) { + fu_common_dump_raw (G_LOG_DOMAIN, "fw last 32 bytes", + &buf[bufsz - 32], 32); + } + + /* find the version tag */ + for (guint32 idx = 0; idx < 32; idx++) { + if (buf[(bufsz - 32) + idx ] == 'v') { + pos = idx; + break; + } + } + + /* set the default version if can not find it in fw bin */ + if (pos < 32 - 6 && buf[(bufsz - 32) + pos + 1] == '_') { + g_autofree gchar *version = NULL; + version = g_strdup_printf ("%c.%c.%c", + buf[(bufsz - 32) + pos + 2], + buf[(bufsz - 32) + pos + 4], + buf[(bufsz - 32) + pos + 6]); + fu_firmware_set_version (firmware, version); + } else { + fu_firmware_set_version (firmware, "1.0.0"); + } + fu_firmware_add_image (firmware, img); + return TRUE; +} + +static void +fu_pxi_rf_firmware_init (FuPxiRfFirmware *self) +{ +} + +static void +fu_pxi_rf_firmware_class_init (FuPxiRfFirmwareClass *klass) +{ + FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); + klass_firmware->parse = fu_pxi_rf_firmware_parse; +} + +FuFirmware * +fu_pxi_rf_firmware_new (void) +{ + return FU_FIRMWARE (g_object_new (FU_TYPE_PXI_RF_FIRMWARE, NULL)); +} diff --git a/plugins/pixart-rf/fu-pxi-firmware.h b/plugins/pixart-rf/fu-pxi-firmware.h new file mode 100644 index 000000000..60d215b86 --- /dev/null +++ b/plugins/pixart-rf/fu-pxi-firmware.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2020 Jimmy Yu + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-firmware.h" + +#define FU_TYPE_PXI_RF_FIRMWARE (fu_pxi_rf_firmware_get_type ()) +G_DECLARE_FINAL_TYPE (FuPxiRfFirmware, fu_pxi_rf_firmware, FU, PXI_RF_FIRMWARE, FuFirmware) + +FuFirmware *fu_pxi_rf_firmware_new (void); diff --git a/plugins/pixart-rf/meson.build b/plugins/pixart-rf/meson.build new file mode 100644 index 000000000..74f236c58 --- /dev/null +++ b/plugins/pixart-rf/meson.build @@ -0,0 +1,25 @@ +cargs = ['-DG_LOG_DOMAIN="FuPluginPixartRf"'] + +shared_module('fu_plugin_pixart_rf', + fu_hash, + sources : [ + 'fu-plugin-pixart-rf.c', + 'fu-pxi-device.c', + 'fu-pxi-firmware.c', + ], + include_directories : [ + root_incdir, + fwupd_incdir, + fwupdplugin_incdir, + ], + install : true, + install_dir: plugin_dir, + c_args : cargs, + dependencies : [ + plugin_deps, + ], + link_with : [ + fwupd, + fwupdplugin, + ], +) diff --git a/plugins/pixart-rf/pixart-rf.quirk b/plugins/pixart-rf/pixart-rf.quirk new file mode 100644 index 000000000..669bf1a19 --- /dev/null +++ b/plugins/pixart-rf/pixart-rf.quirk @@ -0,0 +1,3 @@ +# pxi-hid +[DeviceInstanceId=HIDRAW\VEN_093A&DEV_2801] +Plugin = pixart_rf