From ad32b0c17e177153d55c9aa28e4c678d2f3871f5 Mon Sep 17 00:00:00 2001 From: Richard Hughes Date: Mon, 7 Sep 2020 21:03:08 +0100 Subject: [PATCH] elaptp: Allow recovery when the HID firmware fails to load --- libfwupdplugin/fu-udev-device.c | 23 ++ plugins/elantp/README.md | 2 +- plugins/elantp/elantp.quirk | 6 + plugins/elantp/fu-elantp-common.h | 8 + plugins/elantp/fu-elantp-hid-device.c | 7 +- plugins/elantp/fu-elantp-i2c-device.c | 573 ++++++++++++++++++++++++++ plugins/elantp/fu-elantp-i2c-device.h | 12 + plugins/elantp/fu-plugin-elantp.c | 3 + plugins/elantp/meson.build | 1 + 9 files changed, 628 insertions(+), 7 deletions(-) create mode 100644 plugins/elantp/fu-elantp-i2c-device.c create mode 100644 plugins/elantp/fu-elantp-i2c-device.h diff --git a/libfwupdplugin/fu-udev-device.c b/libfwupdplugin/fu-udev-device.c index 39fdb5d7a..e8f6a4587 100644 --- a/libfwupdplugin/fu-udev-device.c +++ b/libfwupdplugin/fu-udev-device.c @@ -211,6 +211,23 @@ fu_udev_device_get_vendor_fallback (GUdevDevice *udev_device) return NULL; } +#ifdef HAVE_GUDEV +static gboolean +fu_udev_device_probe_i2c_dev (FuUdevDevice *self, GError **error) +{ + FuUdevDevicePrivate *priv = GET_PRIVATE (self); + const gchar *name = g_udev_device_get_sysfs_attr (priv->udev_device, "name"); + if (name != NULL) { + g_autofree gchar *devid = NULL; + g_autofree gchar *name_safe = g_strdup (name); + g_strdelimit (name_safe, " /\\\"", '-'); + devid = g_strdup_printf ("I2C\\NAME_%s", name_safe); + fu_device_add_instance_id (FU_DEVICE (self), devid); + } + return TRUE; +} +#endif + static gboolean fu_udev_device_probe (FuDevice *device, GError **error) { @@ -401,6 +418,12 @@ fu_udev_device_probe (FuDevice *device, GError **error) FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS); } + /* i2c devices all expose a name */ + if (g_strcmp0 (g_udev_device_get_subsystem (priv->udev_device), "i2c-dev") == 0) { + if (!fu_udev_device_probe_i2c_dev (self, error)) + return FALSE; + } + /* determine if we're wired internally */ parent_i2c = g_udev_device_get_parent_with_subsystem (priv->udev_device, "i2c", NULL); diff --git a/plugins/elantp/README.md b/plugins/elantp/README.md index cd1bbc37d..3f867e89e 100644 --- a/plugins/elantp/README.md +++ b/plugins/elantp/README.md @@ -5,7 +5,7 @@ Introduction ------------ This plugin allows updating Touchpad devices from Elan. Devices are enumerated -using HID. +using HID and raw I²C nodes. The I²C mode is used for firmware recovery. Firmware Format --------------- diff --git a/plugins/elantp/elantp.quirk b/plugins/elantp/elantp.quirk index 4760d6489..7d9c07802 100644 --- a/plugins/elantp/elantp.quirk +++ b/plugins/elantp/elantp.quirk @@ -6,6 +6,12 @@ GType = FuElantpHidDevice Plugin = elantp GType = FuElantpHidDevice +# recovery device +[DeviceInstanceId=I2C\NAME_Synopsys-DesignWare-I2C-adapter] +Plugin = elantp +GType = FuElantpI2cDevice +ElantpI2cTargetAddress = 0x15 + [DeviceInstanceId=ELANTP\ICTYPE_00] ElantpIcPageCount = 0x200 ElantpIapPassword = 0x1EA5 diff --git a/plugins/elantp/fu-elantp-common.h b/plugins/elantp/fu-elantp-common.h index a607be532..05d7ed818 100644 --- a/plugins/elantp/fu-elantp-common.h +++ b/plugins/elantp/fu-elantp-common.h @@ -8,6 +8,7 @@ #include +#define ETP_CMD_GET_HID_DESCRIPTOR 0x0001 #define ETP_CMD_GET_HARDWARE_ID 0x0100 #define ETP_CMD_GET_MODULE_ID 0x0101 #define ETP_CMD_I2C_FW_CHECKSUM 0x030F @@ -38,5 +39,12 @@ #define ETP_FW_IAP_CHECK_PW (1 << 7) #define ETP_FW_IAP_LAST_FIT (1 << 9) + +#define ELANTP_DELAY_COMPLETE 1200 /* ms */ +#define ELANTP_DELAY_RESET 30 /* ms */ +#define ELANTP_DELAY_UNLOCK 100 /* ms */ +#define ELANTP_DELAY_WRITE_BLOCK 35 /* ms */ +#define ELANTP_DELAY_WRITE_BLOCK_512 50 /* ms */ + guint16 fu_elantp_calc_checksum (const guint8 *data, gsize length); diff --git a/plugins/elantp/fu-elantp-hid-device.c b/plugins/elantp/fu-elantp-hid-device.c index 62fc7b64c..5e0f52646 100644 --- a/plugins/elantp/fu-elantp-hid-device.c +++ b/plugins/elantp/fu-elantp-hid-device.c @@ -26,12 +26,6 @@ struct _FuElantpHidDevice { guint8 pattern; }; -#define ELANTP_DELAY_COMPLETE 1200 /* ms */ -#define ELANTP_DELAY_RESET 30 /* ms */ -#define ELANTP_DELAY_UNLOCK 100 /* ms */ -#define ELANTP_DELAY_WRITE_BLOCK 35 /* ms */ -#define ELANTP_DELAY_WRITE_BLOCK_512 50 /* ms */ - G_DEFINE_TYPE (FuElantpHidDevice, fu_elantp_hid_device, FU_TYPE_UDEV_DEVICE) static void @@ -498,6 +492,7 @@ fu_elantp_hid_device_init (FuElantpHidDevice *self) fu_device_add_icon (FU_DEVICE (self), "input-touchpad"); fu_device_set_protocol (FU_DEVICE (self), "tw.com.emc.elantp"); fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_HEX); + fu_device_set_priority (FU_DEVICE (self), 1); /* better than i2c */ fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE | diff --git a/plugins/elantp/fu-elantp-i2c-device.c b/plugins/elantp/fu-elantp-i2c-device.c new file mode 100644 index 000000000..1e9cc2ddb --- /dev/null +++ b/plugins/elantp/fu-elantp-i2c-device.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#include + +#include "config.h" + +#include "fu-elantp-common.h" +#include "fu-elantp-firmware.h" +#include "fu-elantp-i2c-device.h" +#include "fu-chunk.h" + +struct _FuElantpI2cDevice { + FuUdevDevice parent_instance; + guint16 i2c_addr; + guint16 ic_page_count; + guint16 iap_type; + guint16 iap_ctrl; + guint16 iap_password; + guint16 module_id; + guint16 fw_page_size; + guint8 pattern; +}; + +G_DEFINE_TYPE (FuElantpI2cDevice, fu_elantp_i2c_device, FU_TYPE_UDEV_DEVICE) + +static void +fu_elantp_i2c_device_to_string (FuDevice *device, guint idt, GString *str) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + fu_common_string_append_kx (str, idt, "I2cAddr", self->i2c_addr); + fu_common_string_append_kx (str, idt, "ModuleId", self->module_id); + fu_common_string_append_kx (str, idt, "Pattern", self->pattern); + fu_common_string_append_kx (str, idt, "FwPageSize", self->fw_page_size); + fu_common_string_append_kx (str, idt, "IcPageCount", self->ic_page_count); + fu_common_string_append_kx (str, idt, "IapType", self->iap_type); + fu_common_string_append_kx (str, idt, "IapCtrl", self->iap_ctrl); +} + +static gboolean +fu_elantp_i2c_device_probe (FuUdevDevice *device, GError **error) +{ + /* check is valid */ + if (g_strcmp0 (fu_udev_device_get_subsystem (device), "i2c-dev") != 0) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "is not correct subsystem=%s, expected i2c-dev", + fu_udev_device_get_subsystem (device)); + return FALSE; + } + if (fu_udev_device_get_device_file (device) == NULL) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "no device file"); + return FALSE; + } + + /* set the physical ID */ + return fu_udev_device_set_physical_id (device, "i2c", error); +} + +static gboolean +fu_elantp_i2c_device_send_cmd (FuElantpI2cDevice *self, + guint8 *tx, gssize txsz, + guint8 *rx, gssize rxsz, + GError **error) +{ + if (g_getenv ("FWUPD_ELANTP_VERBOSE") != NULL) + fu_common_dump_raw (G_LOG_DOMAIN, "Write", tx, txsz); + if (!fu_udev_device_pwrite_full (FU_UDEV_DEVICE (self), 0, tx, txsz, error)) + return FALSE; + if (rxsz == 0) + return TRUE; + if (!fu_udev_device_pread_full (FU_UDEV_DEVICE (self), 0, rx, rxsz, error)) + return FALSE; + if (g_getenv ("FWUPD_ELANTP_VERBOSE") != NULL) + fu_common_dump_raw (G_LOG_DOMAIN, "Read", rx, rxsz); + return TRUE; +} + +static gboolean +fu_elantp_i2c_device_write_cmd (FuElantpI2cDevice *self, guint16 reg, guint16 cmd, GError **error) +{ + guint8 buf[4]; + fu_common_write_uint16 (buf + 0x0, reg, G_LITTLE_ENDIAN); + fu_common_write_uint16 (buf + 0x2, cmd, G_LITTLE_ENDIAN); + return fu_elantp_i2c_device_send_cmd (self, buf, sizeof(buf), NULL, 0, error); +} + +static gboolean +fu_elantp_i2c_device_read_cmd (FuElantpI2cDevice *self, guint16 reg, + guint8 *rx, gsize rxsz, GError **error) +{ + guint8 buf[2]; + fu_common_write_uint16 (buf + 0x0, reg, G_LITTLE_ENDIAN); + return fu_elantp_i2c_device_send_cmd (self, buf, sizeof(buf), + rx, rxsz, error); +} + +static gboolean +fu_elantp_i2c_device_ensure_iap_ctrl (FuElantpI2cDevice *self, GError **error) +{ + guint8 buf[2] = { 0x0 }; + if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_CTRL, buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read IAPControl: "); + return FALSE; + } + self->iap_ctrl = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN); + + /* in bootloader mode? */ + if ((self->iap_ctrl & ETP_I2C_MAIN_MODE_ON) == 0) + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); + else + fu_device_remove_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_IS_BOOTLOADER); + + return TRUE; +} + +static gboolean +fu_elantp_i2c_device_setup (FuDevice *device, GError **error) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + guint16 fwver; + guint16 iap_ver; + guint16 tmp; + guint32 pid; + guint32 vid; + guint8 buf[30] = { 0x0 }; + guint8 ic_type; + g_autofree gchar *instance_id1 = NULL; + g_autofree gchar *instance_id_ic_type = NULL; + g_autofree gchar *version_bl = NULL; + g_autofree gchar *version = NULL; + + /* read the I2C descriptor */ + if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_GET_HID_DESCRIPTOR, + buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to get HID descriptor: "); + return FALSE; + } + vid = fu_common_read_uint16 (buf + 20, G_LITTLE_ENDIAN); + pid = fu_common_read_uint16 (buf + 22, G_LITTLE_ENDIAN); + + /* set the vendor ID */ + if (vid != 0x0000) { + g_autofree gchar *vendor_id = NULL; + vendor_id = g_strdup_printf ("HIDRAW:0x%04X", vid); + fu_device_set_vendor_id (device, vendor_id); + } + + /* add GUIDs in order of priority */ + if (vid != 0x0 && pid != 0x0) { + g_autofree gchar *devid = NULL; + devid = g_strdup_printf ("HIDRAW\\VID_%04X&PID_%04X", + vid, pid); + fu_device_add_instance_id (device, devid); + } + + /* get pattern */ + if (!fu_elantp_i2c_device_read_cmd (self, + ETP_CMD_I2C_GET_HID_ID, + buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read I2C ID: "); + return FALSE; + } + tmp = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN); + self->pattern = tmp != 0xffff ? (tmp & 0xff00) >> 8 : 0; + + /* get current firmware version */ + if (!fu_elantp_i2c_device_read_cmd (self, + ETP_CMD_I2C_FW_VERSION, + buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read fw version: "); + return FALSE; + } + fwver = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN); + version = fu_common_version_from_uint16 (fwver, FWUPD_VERSION_FORMAT_HEX); + fu_device_set_version (device, version); + + /* get IAP firmware version */ + if (!fu_elantp_i2c_device_read_cmd (self, + self->pattern == 0 ? ETP_CMD_I2C_IAP_VERSION : ETP_CMD_I2C_IAP_VERSION_2, + buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read bootloader version: "); + return FALSE; + } + if (self->pattern >= 1) { + iap_ver = buf[1]; + } else { + iap_ver = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN); + } + version_bl = fu_common_version_from_uint16 (iap_ver, FWUPD_VERSION_FORMAT_HEX); + fu_device_set_version_bootloader (device, version_bl); + + /* get module ID */ + if (!fu_elantp_i2c_device_read_cmd (self, + ETP_CMD_GET_MODULE_ID, + buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read module ID: "); + return FALSE; + } + self->module_id = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN); + + /* define the extra instance IDs */ + instance_id1 = g_strdup_printf ("HIDRAW\\VEN_%04X&DEV_%04X&MOD_%04X", + vid, pid, self->module_id); + fu_device_add_instance_id (device, instance_id1); + + /* get OSM version */ + if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_OSM_VERSION, buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read OSM version: "); + return FALSE; + } + tmp = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN); + if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) { + if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_ICBODY, buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read IC body: "); + return FALSE; + } + ic_type = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN) & 0xFF; + } else { + ic_type = (tmp >> 8) & 0xFF; + } + instance_id_ic_type = g_strdup_printf ("ELANTP\\ICTYPE_%02X", ic_type); + fu_device_add_instance_id (device, instance_id_ic_type); + + /* set the page size */ + self->fw_page_size = 64; + if (ic_type >= 0x10) { + if (iap_ver >= 1) { + /* set the IAP type, presumably some kind of ABI */ + if (!fu_elantp_i2c_device_write_cmd (self, + ETP_CMD_I2C_IAP_TYPE, + ETP_I2C_IAP_TYPE_REG, + error)) + return FALSE; + if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_TYPE, + buf, sizeof(buf), error)) { + g_prefix_error (error, "failed to read IAP type: "); + return FALSE; + } + self->iap_type = fu_common_read_uint16 (buf, G_LITTLE_ENDIAN); + if (self->iap_type != ETP_I2C_IAP_TYPE_REG) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "failed to set IAP type"); + return FALSE; + } + if (iap_ver >= 2 && (ic_type == 0x14 || ic_type==0x15)) { + self->fw_page_size = 512; + } else { + self->fw_page_size = 128; + } + } + } + + /* no quirk entry */ + if (self->ic_page_count == 0x0) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_NOT_SUPPORTED, + "no page count for ELANTP\\ICTYPE_%02X", + ic_type); + return FALSE; + } + fu_device_set_firmware_size (device, (guint64) self->ic_page_count * (guint64) self->fw_page_size); + + /* is in bootloader mode */ + if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_elantp_i2c_device_open (FuUdevDevice *device, GError **error) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + gint addr = self->i2c_addr; + guint8 tx_buf[] = { 0x02, 0x01 }; + + /* set target address */ + if (!fu_udev_device_ioctl (device, I2C_SLAVE, + GINT_TO_POINTER (addr), NULL, NULL)) { + if (!fu_udev_device_ioctl (device, I2C_SLAVE_FORCE, + GINT_TO_POINTER (addr), NULL, error)) { + g_prefix_error (error, + "failed to set target address to 0x%x: ", + self->i2c_addr); + return FALSE; + } + } + + /* read i2c device */ + return fu_udev_device_pwrite_full (device, 0x0, tx_buf, sizeof(tx_buf), error); +} + +static FuFirmware * +fu_elantp_i2c_device_prepare_firmware (FuDevice *device, + GBytes *fw, + FwupdInstallFlags flags, + GError **error) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + guint16 module_id; + g_autoptr(FuFirmware) firmware = fu_elantp_firmware_new (); + + /* check is compatible with hardware */ + if (!fu_firmware_parse (firmware, fw, flags, error)) + return NULL; + module_id = fu_elantp_firmware_get_module_id (FU_ELANTP_FIRMWARE (firmware)); + if (self->module_id != module_id) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_INVALID_FILE, + "firmware incompatible, got 0x%04x, expected 0x%04x", + module_id, self->module_id); + return NULL; + } + + /* success */ + return g_steal_pointer (&firmware); +} + +static gboolean +fu_elantp_i2c_device_write_firmware (FuDevice *device, + FuFirmware *firmware, + FwupdInstallFlags flags, + GError **error) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + FuElantpFirmware *firmware_elantp = FU_ELANTP_FIRMWARE (firmware); + gsize bufsz = 0; + guint16 checksum = 0; + guint16 checksum_device = 0; + guint16 iap_addr; + const guint8 *buf; + guint8 csum_buf[2] = { 0x0 }; + g_autoptr(GBytes) fw = NULL; + g_autoptr(GPtrArray) chunks = NULL; + + /* simple image */ + fw = fu_firmware_get_image_default_bytes (firmware, error); + if (fw == NULL) + return FALSE; + + /* write each block */ + fu_device_set_status (device, FWUPD_STATUS_DEVICE_WRITE); + buf = g_bytes_get_data (fw, &bufsz); + iap_addr = fu_elantp_firmware_get_iap_addr (firmware_elantp); + chunks = fu_chunk_array_new (buf + iap_addr, bufsz - iap_addr, 0x0, 0x0, self->fw_page_size); + for (guint i = 0; i < chunks->len; i++) { + FuChunk *chk = g_ptr_array_index (chunks, i); + guint16 csum_tmp = fu_elantp_calc_checksum (chk->data, chk->data_sz); + gsize blksz = self->fw_page_size + 3; + g_autofree guint8 *blk = g_malloc0 (blksz); + + /* write block */ + blk[0] = 0x0B; /* report ID */ + memcpy (blk + 1, chk->data, chk->data_sz); + fu_common_write_uint16 (blk + chk->data_sz + 1, csum_tmp, G_LITTLE_ENDIAN); + + if (!fu_elantp_i2c_device_send_cmd (self, blk, blksz, NULL, 0, error)) + return FALSE; + g_usleep (self->fw_page_size == 512 ? + ELANTP_DELAY_WRITE_BLOCK_512 * 1000 : + ELANTP_DELAY_WRITE_BLOCK * 1000); + + if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error)) + return FALSE; + if (self->iap_ctrl & (ETP_FW_IAP_PAGE_ERR | ETP_FW_IAP_INTF_ERR)) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "bootloader reports failed write: 0x%x", + self->iap_ctrl); + return FALSE; + } + + /* update progress */ + checksum += csum_tmp; + fu_device_set_progress_full (device, (gsize) i, (gsize) chunks->len); + } + + /* verify the written checksum */ + fu_device_set_status (device, FWUPD_STATUS_DEVICE_VERIFY); + if (!fu_elantp_i2c_device_read_cmd (self, ETP_CMD_I2C_IAP_CHECKSUM, + csum_buf, sizeof(csum_buf), error)) + return FALSE; + checksum_device = fu_common_read_uint16 (csum_buf, G_LITTLE_ENDIAN); + if (checksum != checksum_device) { + g_set_error (error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "checksum failed 0x%04x != 0x%04x", + checksum, checksum_device); + return FALSE; + } + + /* wait for a reset */ + fu_device_set_progress (device, 0); + fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); + g_usleep (ELANTP_DELAY_COMPLETE * 1000); + return TRUE; +} + +static gboolean +fu_elantp_i2c_device_detach (FuDevice *device, GError **error) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + + /* sanity check */ + if (fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { + g_debug ("already in bootloader mode, skipping"); + return TRUE; + } + + g_debug ("in bootloader mode, reset IC"); + fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); + if (!fu_elantp_i2c_device_write_cmd (self, + ETP_CMD_I2C_IAP_RESET, + ETP_I2C_IAP_RESET, + error)) + return FALSE; + g_usleep (ELANTP_DELAY_RESET * 1000); + if (!fu_elantp_i2c_device_write_cmd (self, + ETP_CMD_I2C_IAP, + self->iap_password, + error)) + return FALSE; + g_usleep (ELANTP_DELAY_UNLOCK * 1000); + if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error)) + return FALSE; + if ((self->iap_ctrl & ETP_FW_IAP_CHECK_PW) == 0) { + g_set_error_literal (error, + FWUPD_ERROR, + FWUPD_ERROR_WRITE, + "unexpected bootloader password"); + return FALSE; + } + + /* success */ + return TRUE; +} + +static gboolean +fu_elantp_i2c_device_attach (FuDevice *device, GError **error) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + + /* sanity check */ + if (!fu_device_has_flag (device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) { + g_debug ("already in runtime mode, skipping"); + return TRUE; + } + + /* reset back to runtime */ + fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); + if (!fu_elantp_i2c_device_write_cmd (self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_IAP_RESET, error)) + return FALSE; + g_usleep (ELANTP_DELAY_RESET * 1000); + if (!fu_elantp_i2c_device_write_cmd (self, ETP_CMD_I2C_IAP_RESET, ETP_I2C_ENABLE_REPORT, error)) { + g_prefix_error (error, "cannot enable TP report: "); + return FALSE; + } + if (!fu_elantp_i2c_device_write_cmd (self, 0x0306, 0x003, error)) { + g_prefix_error (error, "cannot switch to TP PTP mode: "); + return FALSE; + } + if (!fu_elantp_i2c_device_ensure_iap_ctrl (self, error)) + return FALSE; + + /* success */ + return TRUE; +} + +static gboolean +fu_elantp_i2c_device_set_quirk_kv (FuDevice *device, + const gchar *key, + const gchar *value, + GError **error) +{ + FuElantpI2cDevice *self = FU_ELANTP_I2C_DEVICE (device); + if (g_strcmp0 (key, "ElantpIcPageCount") == 0) { + guint64 tmp = fu_common_strtoull (value); + if (tmp > 0xffff) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "ElantpIcPageCount only supports " + "values <= 0xffff"); + return FALSE; + } + self->ic_page_count = (guint16) tmp; + return TRUE; + } + if (g_strcmp0 (key, "ElantpIapPassword") == 0) { + guint64 tmp = fu_common_strtoull (value); + if (tmp > 0xffff) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "ElantpIapPassword only supports " + "values <= 0xffff"); + return FALSE; + } + self->iap_password = (guint16) tmp; + return TRUE; + } + if (g_strcmp0 (key, "ElantpI2cTargetAddress") == 0) { + guint64 tmp = fu_common_strtoull (value); + if (tmp > 0xffff) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "ElantpI2cTargetAddress only supports " + "values <= 0xffff"); + return FALSE; + } + self->i2c_addr = (guint16) tmp; + return TRUE; + } + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "quirk key not supported"); + return FALSE; +} + +static void +fu_elantp_i2c_device_init (FuElantpI2cDevice *self) +{ + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL); + fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_UPDATABLE); + fu_device_set_summary (FU_DEVICE (self), "Elan Touchpad (I²C Recovery)"); + fu_device_add_icon (FU_DEVICE (self), "input-touchpad"); + fu_device_set_protocol (FU_DEVICE (self), "tw.com.emc.elantp"); + fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_HEX); + fu_udev_device_set_flags (FU_UDEV_DEVICE (self), + FU_UDEV_DEVICE_FLAG_OPEN_READ | + FU_UDEV_DEVICE_FLAG_OPEN_WRITE); +} + +static void +fu_elantp_i2c_device_finalize (GObject *object) +{ + G_OBJECT_CLASS (fu_elantp_i2c_device_parent_class)->finalize (object); +} + +static void +fu_elantp_i2c_device_class_init (FuElantpI2cDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass); + FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass); + object_class->finalize = fu_elantp_i2c_device_finalize; + klass_device->to_string = fu_elantp_i2c_device_to_string; + klass_device->attach = fu_elantp_i2c_device_attach; + klass_device->detach = fu_elantp_i2c_device_detach; + klass_device->set_quirk_kv = fu_elantp_i2c_device_set_quirk_kv; + klass_device->setup = fu_elantp_i2c_device_setup; + klass_device->write_firmware = fu_elantp_i2c_device_write_firmware; + klass_device->prepare_firmware = fu_elantp_i2c_device_prepare_firmware; + klass_udev_device->probe = fu_elantp_i2c_device_probe; + klass_udev_device->open = fu_elantp_i2c_device_open; +} diff --git a/plugins/elantp/fu-elantp-i2c-device.h b/plugins/elantp/fu-elantp-i2c-device.h new file mode 100644 index 000000000..0d2147a94 --- /dev/null +++ b/plugins/elantp/fu-elantp-i2c-device.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2020 Richard Hughes + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include "fu-plugin.h" + +#define FU_TYPE_ELANTP_I2C_DEVICE (fu_elantp_i2c_device_get_type ()) +G_DECLARE_FINAL_TYPE (FuElantpI2cDevice, fu_elantp_i2c_device, FU, ELANTP_I2C_DEVICE, FuUdevDevice) diff --git a/plugins/elantp/fu-plugin-elantp.c b/plugins/elantp/fu-plugin-elantp.c index 5bd05ed37..bc1c97d8e 100644 --- a/plugins/elantp/fu-plugin-elantp.c +++ b/plugins/elantp/fu-plugin-elantp.c @@ -11,12 +11,15 @@ #include "fu-elantp-firmware.h" #include "fu-elantp-hid-device.h" +#include "fu-elantp-i2c-device.h" void fu_plugin_init (FuPlugin *plugin) { fu_plugin_set_build_hash (plugin, FU_BUILD_HASH); + fu_plugin_add_udev_subsystem (plugin, "i2c-dev"); fu_plugin_add_udev_subsystem (plugin, "hidraw"); fu_plugin_add_firmware_gtype (plugin, "elantp", FU_TYPE_ELANTP_FIRMWARE); + fu_plugin_set_device_gtype (plugin, FU_TYPE_ELANTP_I2C_DEVICE); fu_plugin_set_device_gtype (plugin, FU_TYPE_ELANTP_HID_DEVICE); } diff --git a/plugins/elantp/meson.build b/plugins/elantp/meson.build index 97527e9d9..c982ede1b 100644 --- a/plugins/elantp/meson.build +++ b/plugins/elantp/meson.build @@ -13,6 +13,7 @@ shared_module('fu_plugin_elantp', 'fu-elantp-common.c', 'fu-elantp-firmware.c', 'fu-elantp-hid-device.c', + 'fu-elantp-i2c-device.c', ], include_directories : [ root_incdir,