/* * Copyright (C) 2016 Richard Hughes * Copyright (C) 2020 boger wang * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-chunk.h" #include "fu-goodixmoc-common.h" #include "fu-goodixmoc-device.h" struct _FuGoodixMocDevice { FuUsbDevice parent_instance; guint8 dummy_seq; }; G_DEFINE_TYPE (FuGoodixMocDevice, fu_goodixmoc_device, FU_TYPE_USB_DEVICE) #define GX_USB_BULK_EP_IN (3 | 0x80) #define GX_USB_BULK_EP_OUT (1 | 0x00) #define GX_USB_INTERFACE 0 #define GX_USB_DATAIN_TIMEOUT 2000 /* ms */ #define GX_USB_DATAOUT_TIMEOUT 200 /* ms */ #define GX_FLASH_TRANSFER_BLOCK_SIZE 1000 /* 1000 */ static gboolean goodixmoc_device_cmd_send (FuGoodixMocDevice *self, guint8 cmd0, guint8 cmd1, GxPkgType type, GByteArray *req, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint32 crc_all = 0; guint32 crc_hdr = 0; gsize actual_len = 0; g_autoptr(GByteArray) buf = g_byte_array_new (); /* build header */ fu_byte_array_append_uint8 (buf, cmd0); fu_byte_array_append_uint8 (buf, cmd1); fu_byte_array_append_uint8 (buf, type); /* pkg_flag */ fu_byte_array_append_uint8 (buf, self->dummy_seq++); /* reserved */ fu_byte_array_append_uint16 (buf, req->len + GX_SIZE_CRC32, G_LITTLE_ENDIAN); crc_hdr = fu_common_crc8 (buf->data, buf->len); fu_byte_array_append_uint8 (buf, crc_hdr); fu_byte_array_append_uint8 (buf, ~crc_hdr); g_byte_array_append (buf, req->data, req->len); crc_all = fu_common_crc32 (buf->data, buf->len); fu_byte_array_append_uint32 (buf, crc_all, G_LITTLE_ENDIAN); /* send zero length package */ if (!g_usb_device_bulk_transfer (usb_device, GX_USB_BULK_EP_OUT, NULL, 0, NULL, GX_USB_DATAOUT_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to req: "); return FALSE; } if (g_getenv ("FWUPD_GOODIXFP_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "REQST", buf->data, buf->len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); } /* send data */ if (!g_usb_device_bulk_transfer (usb_device, GX_USB_BULK_EP_OUT, buf->data, buf->len, &actual_len, GX_USB_DATAOUT_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to req: "); return FALSE; } if (actual_len != buf->len) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid length"); return FALSE; } /* success */ return TRUE; } static gboolean goodixmoc_device_cmd_recv (FuGoodixMocDevice *self, GxfpCmdResp *presponse, gboolean data_reply, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (self)); guint32 crc_actual = 0; guint32 crc_calculated = 0; gsize actual_len = 0; gsize offset = 0; g_return_val_if_fail (presponse != NULL, FALSE); /* * package format * | zlp | ack | zlp | data | */ while (1) { guint16 header_len = 0x0; guint8 header_cmd0 = 0x0; g_autoptr(GByteArray) reply = g_byte_array_new (); fu_byte_array_set_size (reply, GX_FLASH_TRANSFER_BLOCK_SIZE); if (!g_usb_device_bulk_transfer (usb_device, GX_USB_BULK_EP_IN, reply->data, reply->len, &actual_len, /* allowed to return short read */ GX_USB_DATAIN_TIMEOUT, NULL, error)) { g_prefix_error (error, "failed to reply: "); return FALSE; } /* receive zero length package */ if (actual_len == 0) continue; if (g_getenv ("FWUPD_GOODIXFP_VERBOSE") != NULL) { fu_common_dump_full (G_LOG_DOMAIN, "REPLY", reply->data, actual_len, 16, FU_DUMP_FLAGS_SHOW_ADDRESSES); } /* parse package header */ if (!fu_common_read_uint8_safe (reply->data, reply->len, 0x0, &header_cmd0, error)) return FALSE; if (!fu_common_read_uint16_safe (reply->data, reply->len, 0x4, &header_len, G_LITTLE_ENDIAN, error)) return FALSE; offset = sizeof(GxfpPkgHeader) + header_len - GX_SIZE_CRC32; crc_actual = fu_common_crc32 (reply->data, offset); if (!fu_common_read_uint32_safe (reply->data, reply->len, offset, &crc_calculated, G_LITTLE_ENDIAN, error)) return FALSE; if (crc_actual != crc_calculated) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid checksum, got 0x%x, expected 0x%x", crc_calculated, crc_actual); return FALSE; } /* parse package data */ if (!fu_common_read_uint8_safe (reply->data, reply->len, sizeof(GxfpPkgHeader) + 0x00, &presponse->result, error)) return FALSE; if (header_cmd0 == GX_CMD_ACK) { if (header_len == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid bufsz"); return FALSE; } if (!fu_common_read_uint8_safe (reply->data, reply->len, sizeof(GxfpPkgHeader) + 0x01, &presponse->ack_msg.cmd, error)) return FALSE; } else if (header_cmd0 == GX_CMD_VERSION) { if (!fu_memcpy_safe ((guint8 *) &presponse->version_info, sizeof(presponse->version_info), 0x0, /* dst */ reply->data, reply->len, sizeof(GxfpPkgHeader) + 0x01, /* src */ sizeof(GxfpVersionInfo), error)) return FALSE; } /* continue after ack received */ if (header_cmd0 == GX_CMD_ACK && data_reply) continue; break; } /* success */ return TRUE; } static gboolean fu_goodixmoc_device_cmd_xfer (FuGoodixMocDevice *device, guint8 cmd0, guint8 cmd1, GxPkgType type, GByteArray *req, GxfpCmdResp *presponse, gboolean data_reply, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device); if (!goodixmoc_device_cmd_send (self, cmd0, cmd1, type, req, error)) return FALSE; return goodixmoc_device_cmd_recv (self, presponse, data_reply, error); } static gboolean fu_goodixmoc_device_setup_version (FuGoodixMocDevice *self, GError **error) { GxfpCmdResp rsp = { 0 }; g_autofree gchar *version = NULL; g_autoptr(GByteArray) req = g_byte_array_new (); fu_byte_array_append_uint8 (req, 0); /* dummy */ if (!fu_goodixmoc_device_cmd_xfer (self, GX_CMD_VERSION, GX_CMD1_DEFAULT, GX_PKG_TYPE_EOP, req, &rsp, TRUE, error)) return FALSE; version = g_strndup ((const gchar *) rsp.version_info.fwversion, sizeof(rsp.version_info.fwversion)); fu_device_set_version (FU_DEVICE (self), version); return TRUE; } static gboolean fu_goodixmoc_device_update_init (FuGoodixMocDevice *self, GError **error) { GxfpCmdResp rsp = { 0 }; g_autoptr(GByteArray) req = g_byte_array_new (); /* update initial */ if (!fu_goodixmoc_device_cmd_xfer (self, GX_CMD_UPGRADE, GX_CMD_UPGRADE_INIT, GX_PKG_TYPE_EOP, req, &rsp, TRUE, error)) { g_prefix_error (error, "failed to send initial update: "); return FALSE; } /* check result */ if (rsp.result != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "initial update failed [0x%x]", rsp.result); return FALSE; } return TRUE; } static gboolean fu_goodixmoc_device_attach (FuDevice *device, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device); GxfpCmdResp rsp = { 0 }; g_autoptr(GByteArray) req = g_byte_array_new (); /* reset device */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); if (!fu_goodixmoc_device_cmd_xfer (self, GX_CMD_RESET, 0x03, GX_PKG_TYPE_EOP, req, &rsp, FALSE, error)) { g_prefix_error (error, "failed to send reset device: "); return FALSE; } /* check result */ if (rsp.result != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to reset device [0x%x]", rsp.result); return FALSE; } fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); return TRUE; } static gboolean fu_goodixmoc_device_open (FuDevice *device, GError **error) { GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device)); /* FuUsbDevice->open */ if (!FU_DEVICE_CLASS (fu_goodixmoc_device_parent_class)->open (device, error)) return FALSE; return g_usb_device_claim_interface (usb_device, GX_USB_INTERFACE, G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER, error); } static gboolean fu_goodixmoc_device_setup (FuDevice *device, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE (device); /* ensure version */ if (!fu_goodixmoc_device_setup_version (self, error)) { g_prefix_error (error, "failed to get firmware version: "); return FALSE; } /* success */ return TRUE; } static gboolean fu_goodixmoc_device_write_firmware (FuDevice *device, FuFirmware *firmware, FwupdInstallFlags flags, GError **error) { FuGoodixMocDevice *self = FU_GOODIXMOC_DEVICE(device); GxPkgType pkg_eop = GX_PKG_TYPE_NORMAL; GxfpCmdResp rsp = { 0 }; gboolean wait_data_reply = FALSE; g_autoptr(GBytes) fw = NULL; g_autoptr(GError) error_local = NULL; g_autoptr(GPtrArray) chunks = NULL; /* get default image */ fw = fu_firmware_get_bytes (firmware, error); if (fw == NULL) return FALSE; /* build packets */ chunks = fu_chunk_array_new_from_bytes (fw, 0x00, 0x00, /* page_sz */ GX_FLASH_TRANSFER_BLOCK_SIZE); /* don't auto-boot firmware */ fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); if (!fu_goodixmoc_device_update_init (self, &error_local)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to initial update: %s", error_local->message); return FALSE; } /* write each block */ for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index (chunks, i); g_autoptr(GByteArray) req = g_byte_array_new (); g_autoptr(GError) error_block = NULL; g_byte_array_append (req, fu_chunk_get_data (chk), fu_chunk_get_data_sz (chk)); /* the last chunk */ if (i == chunks->len - 1) { wait_data_reply = TRUE; pkg_eop = GX_PKG_TYPE_EOP; } if (!fu_goodixmoc_device_cmd_xfer (self, GX_CMD_UPGRADE, GX_CMD_UPGRADE_DATA, pkg_eop, req, &rsp, wait_data_reply, &error_block)) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to write: %s", error_block->message); return FALSE; } /* check update status */ if (wait_data_reply && rsp.result != 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_WRITE, "failed to verify firmware [0x%x]", rsp.result); return FALSE; } /* update progress */ fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len); } /* success! */ return TRUE; } static void fu_goodixmoc_device_init (FuGoodixMocDevice *self) { fu_device_add_flag (FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (FU_DEVICE(self), FWUPD_DEVICE_FLAG_SELF_RECOVERY); fu_device_add_flag (FU_DEVICE(self), FWUPD_DEVICE_FLAG_USE_RUNTIME_VERSION); fu_device_set_version_format (FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN); fu_device_set_remove_delay (FU_DEVICE(self), 5000); fu_device_add_protocol (FU_DEVICE (self), "com.goodix.goodixmoc"); fu_device_set_name (FU_DEVICE(self), "Fingerprint Sensor"); fu_device_set_summary (FU_DEVICE(self), "Match-On-Chip Fingerprint Sensor"); fu_device_set_vendor (FU_DEVICE(self), "Goodix"); fu_device_set_install_duration (FU_DEVICE(self), 10); fu_device_set_firmware_size_min (FU_DEVICE(self), 0x20000); fu_device_set_firmware_size_max (FU_DEVICE(self), 0x30000); } static void fu_goodixmoc_device_class_init(FuGoodixMocDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); klass_device->write_firmware = fu_goodixmoc_device_write_firmware; klass_device->setup = fu_goodixmoc_device_setup; klass_device->attach = fu_goodixmoc_device_attach; klass_device->open = fu_goodixmoc_device_open; }