mirror of
https://git.proxmox.com/git/fwupd
synced 2025-08-04 07:21:54 +00:00
elaptp: Allow recovery when the HID firmware fails to load
This commit is contained in:
parent
1d633a58fe
commit
ad32b0c17e
@ -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);
|
||||
|
@ -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
|
||||
---------------
|
||||
|
@ -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
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#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);
|
||||
|
@ -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 |
|
||||
|
573
plugins/elantp/fu-elantp-i2c-device.c
Normal file
573
plugins/elantp/fu-elantp-i2c-device.c
Normal file
@ -0,0 +1,573 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1+
|
||||
*/
|
||||
|
||||
#include <linux/i2c-dev.h>
|
||||
|
||||
#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;
|
||||
}
|
12
plugins/elantp/fu-elantp-i2c-device.h
Normal file
12
plugins/elantp/fu-elantp-i2c-device.h
Normal file
@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
||||
*
|
||||
* 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)
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user