fwupd/plugins/elantp/fu-elantp-hid-haptic-device.c
Richard Hughes 090a7c8b9a Add API to wait for a device
This allows us to ignore all the delays when the device is emulated, with the
idea being to do dozens of device emulations in the CI tests.

Also, do not call fu_progress_sleep() when the device is emulated.
2023-02-23 13:04:11 -06:00

1144 lines
32 KiB
C

/*
* Copyright (C) 2022 Jingle Wu <jingle.wu@emc.com.tw>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <fwupdplugin.h>
#include <linux/hidraw.h>
#include <linux/input.h>
#include "fu-elantp-common.h"
#include "fu-elantp-haptic-firmware.h"
#include "fu-elantp-hid-haptic-device.h"
struct _FuElantpHidHapticDevice {
FuUdevDevice parent_instance;
guint16 ic_page_count;
guint16 iap_type;
guint16 tp_iap_ctrl;
guint16 tp_iap_ver;
guint16 tp_ic_type;
guint16 iap_ctrl;
guint16 iap_password;
guint16 module_id;
guint16 fw_page_size;
guint8 pattern;
gint16 driver_ic;
guint8 iap_ver;
};
G_DEFINE_TYPE(FuElantpHidHapticDevice, fu_elantp_hid_haptic_device, FU_TYPE_UDEV_DEVICE)
#define FU_ELANTP_DEVICE_IOCTL_TIMEOUT 5000 /* ms */
static FuElantpHidDevice *
fu_elantp_haptic_device_get_parent(FuDevice *self, GError **error)
{
FuDevice *parent = fu_device_get_parent(FU_DEVICE(self));
if (parent == NULL) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "no parent set");
return NULL;
}
return FU_ELANTP_HID_DEVICE(FU_UDEV_DEVICE(parent));
}
static gboolean
fu_elantp_hid_haptic_device_detach(FuDevice *device, FuProgress *progress, GError **error);
static void
fu_elantp_hid_haptic_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
fu_string_append_kx(str, idt, "ModuleId", self->module_id);
fu_string_append_kx(str, idt, "Pattern", self->pattern);
fu_string_append_kx(str, idt, "FwPageSize", self->fw_page_size);
fu_string_append_kx(str, idt, "IcPageCount", self->ic_page_count);
fu_string_append_kx(str, idt, "IapType", self->iap_type);
fu_string_append_kx(str, idt, "TpIapCtrl", self->tp_iap_ctrl);
fu_string_append_kx(str, idt, "IapCtrl", self->iap_ctrl);
fu_string_append_kx(str, idt, "DriverIC", self->driver_ic);
fu_string_append_kx(str, idt, "IAPVersion", self->iap_ver);
}
static gboolean
fu_elantp_hid_haptic_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_elantp_hid_haptic_device_send_cmd(FuDevice *self,
const guint8 *tx,
gsize txsz,
guint8 *rx,
gsize rxsz,
GError **error)
{
g_autofree guint8 *buf = NULL;
gsize bufsz = rxsz + 3;
if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "SetReport", tx, txsz);
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
HIDIOCSFEATURE(txsz),
(guint8 *)tx,
NULL,
FU_ELANTP_DEVICE_IOCTL_TIMEOUT,
error))
return FALSE;
if (rxsz == 0)
return TRUE;
/* GetFeature */
buf = g_malloc0(bufsz);
buf[0] = tx[0]; /* report number */
if (!fu_udev_device_ioctl(FU_UDEV_DEVICE(self),
HIDIOCGFEATURE(bufsz),
buf,
NULL,
FU_ELANTP_DEVICE_IOCTL_TIMEOUT,
error))
return FALSE;
if (g_getenv("FWUPD_ELANTP_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "GetReport", buf, bufsz);
/* success */
return fu_memcpy_safe(rx,
rxsz,
0x0, /* dst */
buf,
bufsz,
0x3, /* src */
rxsz,
error);
}
static gboolean
fu_elantp_hid_haptic_device_read_cmd(FuDevice *self,
guint16 reg,
guint8 *buf,
gsize bufz,
GError **error)
{
guint8 tmp[5] = {0x0D, 0x05, 0x03};
fu_memwrite_uint16(tmp + 0x3, reg, G_LITTLE_ENDIAN);
return fu_elantp_hid_haptic_device_send_cmd(self, tmp, sizeof(tmp), buf, bufz, error);
}
static gint
fu_elantp_hid_haptic_device_write_cmd(FuDevice *self, guint16 reg, guint16 cmd, GError **error)
{
guint8 buf[5] = {0x0D};
fu_memwrite_uint16(buf + 0x1, reg, G_LITTLE_ENDIAN);
fu_memwrite_uint16(buf + 0x3, cmd, G_LITTLE_ENDIAN);
return fu_elantp_hid_haptic_device_send_cmd(self, buf, sizeof(buf), NULL, 0, error);
}
static gboolean
fu_elantp_hid_haptic_device_ensure_iap_ctrl(FuDevice *parent,
FuElantpHidHapticDevice *self,
GError **error)
{
guint8 buf[2] = {0x0};
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_IAP_CTRL,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read IAPControl: ");
return FALSE;
}
self->tp_iap_ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
/* in bootloader mode? */
if (self->tp_iap_ver <= 5) {
if ((self->tp_iap_ctrl & ETP_I2C_MAIN_MODE_ON2) == 0)
fu_device_add_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
else
fu_device_remove_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
return TRUE;
}
if ((self->tp_iap_ctrl & ETP_I2C_MAIN_MODE_ON) == 0)
fu_device_add_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
else
fu_device_remove_flag(FU_DEVICE(parent), FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_ensure_eeprom_iap_ctrl(FuDevice *parent,
FuElantpHidHapticDevice *self,
GError **error)
{
guint8 buf[2] = {0x0};
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read IAPControl: ");
return FALSE;
}
self->iap_ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if ((self->iap_ctrl & 0x800) != 0x800) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, "bit11 fail");
return FALSE;
}
if ((self->iap_ctrl & 0x1000) == 0x1000) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_BUSY, "bit12 fail, resend");
return FALSE;
}
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_get_haptic_driver_ic(FuDevice *parent,
FuElantpHidHapticDevice *self,
GError **error)
{
guint8 buf[2] = {0x0};
guint16 value;
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_FORCE_TYPE_ENABLE,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read haptic enable cmd: ");
return FALSE;
}
value = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if (value == 0xFFFF || value == ETP_CMD_I2C_FORCE_TYPE_ENABLE) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"failed to read haptic enable cmd");
return FALSE;
}
if ((buf[0] & ETP_FW_FORCE_TYPE_ENABLE_BIT) == 0 ||
(buf[0] & ETP_FW_EEPROM_ENABLE_BIT) == 0) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"eeprom enable bit unset");
return FALSE;
}
/* success */
self->driver_ic = (buf[0] >> 4) & 0xF;
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_get_version(FuDevice *parent,
FuElantpHidHapticDevice *self,
GError **error)
{
guint16 v_s = 0;
guint16 v_d = 0;
guint16 v_m = 0;
guint16 v_y = 0;
guint8 buf[2] = {0x0};
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_GET_EEPROM_FW_VERSION,
error)) {
g_prefix_error(error, "failed to write haptic version cmd: ");
return FALSE;
}
fu_device_sleep(FU_DEVICE(self), ELANTP_DELAY_RESET);
if (!fu_elantp_hid_haptic_device_read_cmd(parent, 0x0321, buf, sizeof(buf), error)) {
g_prefix_error(error, "failed to read haptic version cmd: ");
return FALSE;
}
v_d = buf[0];
v_m = buf[1] & 0xF;
v_s = (buf[1] & 0xF0) >> 4;
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_GET_EEPROM_IAP_VERSION,
error)) {
g_prefix_error(error, "failed to write haptic iap version cmd: ");
return FALSE;
}
fu_device_sleep(FU_DEVICE(self), ELANTP_DELAY_RESET);
if (!fu_elantp_hid_haptic_device_read_cmd(parent, 0x0321, buf, sizeof(buf), error)) {
g_prefix_error(error, "failed to read haptic iap version cmd: ");
return FALSE;
}
v_y = buf[0];
self->iap_ver = buf[1];
if (v_y == 0xFF && v_d == 0xFF && v_m == 0xF) {
fu_device_set_version(FU_DEVICE(self), "0");
} else {
g_autofree gchar *str = g_strdup_printf("%02d%02d%02d%02d", v_y, v_m, v_d, v_s);
fu_device_set_version(FU_DEVICE(self), str);
}
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_write_fw_password(FuDevice *parent,
guint16 tp_ic_type,
guint16 tp_iap_ver,
GError **error)
{
guint8 buf[2] = {0x0};
guint16 pw = ETP_I2C_IC13_IAPV5_PW;
guint16 value;
if (tp_iap_ver < 0x5 || tp_ic_type != 0x13)
return TRUE;
if (!fu_elantp_hid_haptic_device_write_cmd(parent, ETP_CMD_I2C_FW_PW, pw, error)) {
g_prefix_error(error, "failed to write fw password cmd: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_FW_PW,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read fw password cmd: ");
return FALSE;
}
value = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if (value != pw) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"can't set fw password got:%x",
value);
return FALSE;
}
/* success */
return TRUE;
}
typedef struct {
guint16 checksum;
guint16 iap_password;
guint16 tp_iap_ver;
guint16 tp_ic_type;
} FuElantpHaptictpWaitFlashEEPROMChecksumHelper;
static gboolean
fu_elantp_hid_haptic_device_write_checksum_cb(FuDevice *parent, gpointer user_data, GError **error)
{
guint8 buf[2] = {0x0};
guint16 value;
FuElantpHaptictpWaitFlashEEPROMChecksumHelper *helper = user_data;
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_EEPROM_SETTING,
ETP_CMD_I2C_EEPROM_WRITE_INFORMATION,
error)) {
g_prefix_error(error, "failed to write haptic info: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_EEPROM_SETTING,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read haptic info: ");
return FALSE;
}
value = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if ((value & 0xFFFF) != ETP_CMD_I2C_EEPROM_WRITE_INFORMATION) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"failed to set haptic info (0x%04x): ",
value);
return FALSE;
}
if (!fu_elantp_hid_haptic_device_write_fw_password(parent,
helper->tp_ic_type,
helper->tp_iap_ver,
error))
return FALSE;
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_IAP,
helper->iap_password,
error)) {
g_prefix_error(error, "failed to write iap password: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM,
helper->checksum,
error)) {
g_prefix_error(error, "failed to write eeprom checksum: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_EEPROM_SETTING,
ETP_CMD_I2C_EEPROM_SETTING_INITIAL,
error)) {
g_prefix_error(error, "failed to set haptic initial setting: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_EEPROM_WRITE_CHECKSUM,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read haptic checksum: ");
return FALSE;
}
value = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if ((value & 0xFFFF) != helper->checksum) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"eeprom checksum failed 0x%04x != 0x%04x : ",
value,
helper->checksum);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_wait_calc_checksum_cb(FuDevice *parent,
gpointer user_data,
GError **error)
{
guint16 ctrl;
guint8 buf[2] = {0x0};
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_SET_EEPROM_DATATYPE,
error)) {
g_prefix_error(error, "failed to write eeprom datatype: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read calc haptic cmd: ");
return FALSE;
}
ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if ((ctrl & 0x20) == 0x20) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "ctrl failed 0x%04x", ctrl);
return FALSE;
}
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_get_checksum(FuDevice *parent, guint16 *checksum, GError **error)
{
guint8 buf[2] = {0x0};
g_autoptr(GError) error_local = NULL;
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_CALC_EEPROM_CHECKSUM,
error))
return FALSE;
if (!fu_device_retry_full(parent,
fu_elantp_hid_haptic_device_wait_calc_checksum_cb,
100,
ELANTP_EEPROM_READ_DELAY,
NULL,
&error_local)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"failed to wait calc eeprom checksum (%s)",
error_local->message);
return FALSE;
}
if (!fu_elantp_hid_haptic_device_write_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_READ_EEPROM_CHECKSUM,
error))
return FALSE;
if (!fu_elantp_hid_haptic_device_read_cmd(parent,
ETP_CMD_I2C_SET_EEPROM_CTRL,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read haptic checksum cmd: ");
return FALSE;
}
*checksum = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_setup(FuDevice *device, GError **error)
{
FuElantpHidDevice *parent;
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
FuUdevDevice *udev_parent;
guint8 ic_type;
guint16 tmp;
guint8 buf[2] = {0x0};
g_autofree gchar *version_bl = NULL;
parent = fu_elantp_haptic_device_get_parent(device, error);
if (parent == NULL)
return FALSE;
if (!fu_elantp_hid_haptic_device_get_haptic_driver_ic(FU_DEVICE(parent), self, error)) {
g_prefix_error(error, "this module is not support haptic EEPROM: ");
return FALSE;
}
/* get pattern */
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_GET_HID_ID,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read HID ID: ");
return FALSE;
}
tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
self->pattern = tmp != 0xFFFF ? (tmp & 0xFF00) >> 8 : 0;
if (!fu_elantp_hid_haptic_device_get_version(FU_DEVICE(parent), self, error))
return FALSE;
version_bl = fu_version_from_uint16(self->iap_ver, FWUPD_VERSION_FORMAT_HEX);
fu_device_set_version_bootloader(device, version_bl);
/* get module ID */
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_GET_MODULE_ID,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read module ID: ");
return FALSE;
}
self->module_id = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
/* define the extra instance IDs */
udev_parent = FU_UDEV_DEVICE(parent);
fu_device_add_instance_u16(device, "VEN", fu_udev_device_get_vendor(udev_parent));
fu_device_add_instance_u16(device, "DEV", fu_udev_device_get_model(udev_parent));
fu_device_add_instance_u16(device, "DRIVERIC", self->driver_ic);
fu_device_add_instance_u16(device, "MOD", self->module_id);
if (!fu_device_build_instance_id(device,
error,
"HIDRAW",
"VEN",
"DEV",
"DRIVERIC",
"MOD",
NULL))
return FALSE;
/* get OSM version */
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_OSM_VERSION,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read OSM version: ");
return FALSE;
}
tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) {
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_IAP_ICBODY,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read IC body: ");
return FALSE;
}
ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF;
} else
ic_type = (tmp >> 8) & 0xFF;
/* define the extra instance IDs (ic_type + module_id + driver) */
fu_device_add_instance_u8(device, "ICTYPE", ic_type);
fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", NULL);
fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "", NULL);
fu_device_build_instance_id(device, NULL, "ELANTP", "ICTYPE", "DRIVERIC", "MOD", NULL);
fu_device_add_instance_str(device, "DRIVER", "HID");
fu_device_build_instance_id(device,
NULL,
"ELANTP",
"ICTYPE",
"DRIVERIC",
"MOD",
"DRIVER",
NULL);
/* 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, 32768);
/* find out if in bootloader mode */
if (!fu_elantp_hid_haptic_device_ensure_iap_ctrl(FU_DEVICE(parent), self, error))
return FALSE;
/* success */
return TRUE;
}
static FuFirmware *
fu_elantp_hid_haptic_device_prepare_firmware(FuDevice *device,
GBytes *fw,
FwupdInstallFlags flags,
GError **error)
{
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
guint16 driver_ic;
g_autoptr(FuFirmware) firmware = fu_elantp_haptic_firmware_new();
/* check is compatible with hardware */
if (!fu_firmware_parse(firmware, fw, flags, error))
return NULL;
driver_ic = fu_elantp_haptic_firmware_get_driver_ic(FU_ELANTP_HAPTIC_FIRMWARE(firmware));
if (driver_ic != self->driver_ic) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"driver IC 0x%x != 0x%x",
(guint)driver_ic,
(guint)self->driver_ic);
return NULL;
}
/* success */
return g_steal_pointer(&firmware);
}
typedef struct {
guint16 checksum;
guint idx_page_start;
GBytes *fw; /* noref */
FuProgress *progress; /* noref */
} FuElantpHaptictpWriteHelper;
static gboolean
fu_elantp_hid_haptic_device_write_chunks_cb(FuDevice *device, gpointer user_data, GError **error)
{
FuElantpHaptictpWriteHelper *helper = (FuElantpHaptictpWriteHelper *)user_data;
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
FuElantpHidDevice *parent;
const guint16 eeprom_fw_page_size = 32;
g_autoptr(GPtrArray) chunks = NULL;
/* use parent */
parent = fu_elantp_haptic_device_get_parent(device, error);
if (parent == NULL)
return FALSE;
/* progress */
chunks = fu_chunk_array_new_from_bytes(helper->fw, 0x0, 0x0, eeprom_fw_page_size);
fu_progress_set_id(helper->progress, G_STRLOC);
fu_progress_set_steps(helper->progress, chunks->len - helper->idx_page_start + 1);
for (guint i = helper->idx_page_start; i <= chunks->len; i++) {
FuChunk *chk;
guint16 csum_tmp;
gsize blksz = self->fw_page_size + 3;
g_autofree guint8 *blk = g_malloc0(blksz);
g_autoptr(GError) error_iapctrl = NULL;
if (i == chunks->len)
chk = g_ptr_array_index(chunks, 0);
else
chk = g_ptr_array_index(chunks, i);
/* write block */
blk[0] = 0x0B; /* report ID */
blk[1] = eeprom_fw_page_size + 5;
blk[2] = 0xA2;
fu_memwrite_uint16(blk + 0x3, i * eeprom_fw_page_size, G_BIG_ENDIAN);
if (i == 0) {
guint8 first_page[32] = {0x0};
memset(&first_page[0], 0xFF, sizeof(first_page));
csum_tmp = fu_sum16(first_page, eeprom_fw_page_size);
if (!fu_memcpy_safe(blk,
blksz,
0x5, /* dst */
first_page,
eeprom_fw_page_size,
0x0, /* src */
eeprom_fw_page_size,
error))
return FALSE;
fu_memwrite_uint16(blk + eeprom_fw_page_size + 5, csum_tmp, G_BIG_ENDIAN);
csum_tmp = 0;
} else {
csum_tmp = fu_sum16(fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk));
if (!fu_memcpy_safe(blk,
blksz,
0x5, /* dst */
fu_chunk_get_data(chk),
fu_chunk_get_data_sz(chk),
0x0, /* src */
fu_chunk_get_data_sz(chk),
error))
return FALSE;
fu_memwrite_uint16(blk + fu_chunk_get_data_sz(chk) + 5,
csum_tmp,
G_BIG_ENDIAN);
}
if (!fu_elantp_hid_haptic_device_send_cmd(FU_DEVICE(parent),
blk,
blksz,
NULL,
0,
error))
return FALSE;
fu_device_sleep(device,
self->fw_page_size == 512 ? ELANTP_DELAY_WRITE_BLOCK_512
: ELANTP_DELAY_WRITE_BLOCK);
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_SET_EEPROM_DATATYPE,
error))
return FALSE;
if (!fu_elantp_hid_haptic_device_ensure_eeprom_iap_ctrl(FU_DEVICE(parent),
self,
&error_iapctrl)) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"bootloader reports failed write: 0x%x (%s)",
self->iap_ctrl,
error_iapctrl->message);
return FALSE;
}
/* update progress */
helper->checksum += csum_tmp;
helper->idx_page_start = i + 1;
fu_progress_step_done(helper->progress);
}
/* success */
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_write_firmware(FuDevice *device,
FuFirmware *firmware,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuElantpHidDevice *parent;
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
guint16 checksum_device = 0;
const gchar *fw_ver;
const gchar *fw_ver_device;
g_autoptr(GBytes) fw = NULL;
g_autoptr(GError) error_local = NULL;
FuElantpHaptictpWaitFlashEEPROMChecksumHelper helper = {0x0};
FuElantpHaptictpWriteHelper helper_write = {0x0};
/* progress */
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 5, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 80, NULL);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_VERIFY, 10, NULL);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, NULL);
/* simple image */
fw = fu_firmware_get_bytes(firmware, error);
if (fw == NULL)
return FALSE;
/* use parent */
parent = fu_elantp_haptic_device_get_parent(device, error);
if (parent == NULL)
return FALSE;
/* detach */
if (!fu_elantp_hid_haptic_device_detach(device, fu_progress_get_child(progress), error))
return FALSE;
fu_progress_step_done(progress);
/* write each block */
helper_write.fw = fw;
helper_write.progress = fu_progress_get_child(progress);
if (!fu_device_retry_full(device,
fu_elantp_hid_haptic_device_write_chunks_cb,
3,
100,
&helper_write,
error))
return FALSE;
fu_progress_step_done(progress);
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_EEPROM_SETTING,
ETP_CMD_I2C_EEPROM_SETTING_INITIAL,
error)) {
g_prefix_error(error, "cannot disable EEPROM Long Transmission mode: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_SET_EEPROM_LEAVE_IAP,
error)) {
g_prefix_error(error, "cannot leave EEPROM IAP: ");
return FALSE;
}
fu_device_sleep(device, ELANTP_DELAY_RESET);
if (!fu_elantp_hid_haptic_device_get_checksum(FU_DEVICE(parent), &checksum_device, error)) {
g_prefix_error(error, "read device checksum fail: ");
return FALSE;
}
if (helper_write.checksum != checksum_device) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"checksum failed 0x%04x != 0x%04x",
helper_write.checksum,
checksum_device);
return FALSE;
}
helper.checksum = checksum_device;
helper.iap_password = self->iap_password;
helper.tp_ic_type = self->tp_ic_type;
helper.tp_iap_ver = self->tp_iap_ver;
if (!fu_device_retry_full(FU_DEVICE(parent),
fu_elantp_hid_haptic_device_write_checksum_cb,
3,
ELANTP_DELAY_WRITE_BLOCK,
&helper,
&error_local)) {
g_prefix_error(error, "write device checksum fail (%s): ", error_local->message);
return FALSE;
}
if (!fu_elantp_hid_haptic_device_get_version(FU_DEVICE(parent), self, error))
return FALSE;
fw_ver_device = fu_device_get_version(device);
fw_ver = fu_firmware_get_version(firmware);
if (g_strcmp0(fw_ver_device, fw_ver) != 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_WRITE,
"firmware version failed %s != %s",
fw_ver,
fw_ver_device);
return FALSE;
}
fu_progress_step_done(progress);
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_HAPTIC_RESTART,
error)) {
g_prefix_error(error, "cannot restart haptic DriverIC: ");
return FALSE;
}
fu_progress_step_done(progress);
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_detach(FuDevice *device, FuProgress *progress, GError **error)
{
FuElantpHidDevice *parent;
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
guint8 buf[2] = {0x0};
guint16 ctrl;
guint16 tmp;
/* haptic EEPROM IAP process runs in the TP main code */
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"in touchpad bootloader mode");
return FALSE;
}
if (self->driver_ic != 0x2 || self->iap_ver != 0x1) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"no support for EEPROM IAP 0x%x 0x%x: ",
(guint)self->driver_ic,
(guint)self->iap_ver);
return FALSE;
}
parent = fu_elantp_haptic_device_get_parent(device, error);
if (parent == NULL)
return FALSE;
/* get OSM version */
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_OSM_VERSION,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read OSM version: ");
return FALSE;
}
tmp = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if (tmp == ETP_CMD_I2C_OSM_VERSION || tmp == 0xFFFF) {
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_IAP_ICBODY,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read IC body: ");
return FALSE;
}
self->tp_ic_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN) & 0xFF;
} else
self->tp_ic_type = (tmp >> 8) & 0xFF;
/* get IAP firmware version */
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
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)
self->tp_iap_ver = buf[1];
else
self->tp_iap_ver = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
/* set the page size */
self->fw_page_size = 64;
if (self->tp_ic_type >= 0x10) {
if (self->tp_iap_ver >= 1) {
/* set the IAP type, presumably some kind of ABI */
if (self->tp_iap_ver >= 2 &&
(self->tp_ic_type == 0x14 || self->tp_ic_type == 0x15)) {
self->fw_page_size = 512;
} else {
self->fw_page_size = 128;
}
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_IAP_TYPE,
self->fw_page_size / 2,
error))
return FALSE;
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_IAP_TYPE,
buf,
sizeof(buf),
error)) {
g_prefix_error(error, "failed to read IAP type: ");
return FALSE;
}
self->iap_type = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if (self->iap_type != self->fw_page_size / 2) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"failed to set IAP type");
return FALSE;
}
}
}
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_EEPROM_SETTING,
ETP_CMD_I2C_EEPROM_LONG_TRANS_ENABLE,
error)) {
g_prefix_error(error, "cannot enable EEPROM Long Transmission mode: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_SET_EEPROM_CTRL,
ETP_CMD_I2C_SET_EEPROM_ENTER_IAP,
error)) {
g_prefix_error(error, "cannot enter EEPROM IAP: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_read_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_SET_EEPROM_CTRL,
buf,
sizeof(buf),
error))
return FALSE;
ctrl = fu_memread_uint16(buf, G_LITTLE_ENDIAN);
if ((ctrl & 0x800) == 0x800) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_READ,
"unexpected EEPROM bootloader control %x",
ctrl);
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_attach(FuDevice *device, FuProgress *progress, GError **error)
{
FuElantpHidDevice *parent;
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
parent = fu_elantp_haptic_device_get_parent(device, error);
if (parent == NULL)
return FALSE;
/* reset back to runtime */
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_IAP_RESET,
ETP_I2C_IAP_RESET,
error)) {
g_prefix_error(error, "cannot reset TP: ");
return FALSE;
}
fu_device_sleep(device, ELANTP_DELAY_RESET);
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent),
ETP_CMD_I2C_IAP_RESET,
ETP_I2C_ENABLE_REPORT,
error)) {
g_prefix_error(error, "cannot enable TP report: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_write_cmd(FU_DEVICE(parent), 0x0306, 0x003, error)) {
g_prefix_error(error, "cannot switch to TP PTP mode: ");
return FALSE;
}
if (!fu_elantp_hid_haptic_device_ensure_iap_ctrl(FU_DEVICE(parent), self, error))
return FALSE;
/* sanity check */
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_READ, "in bootloader mode");
return FALSE;
}
/* success */
return TRUE;
}
static gboolean
fu_elantp_hid_haptic_device_set_quirk_kv(FuDevice *device,
const gchar *key,
const gchar *value,
GError **error)
{
FuElantpHidHapticDevice *self = FU_ELANTP_HID_HAPTIC_DEVICE(device);
guint64 tmp = 0;
if (g_strcmp0(key, "ElantpIcPageCount") == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error))
return FALSE;
self->ic_page_count = (guint16)tmp;
return TRUE;
}
if (g_strcmp0(key, "ElantpIapPassword") == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT16, error))
return FALSE;
self->iap_password = (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_hid_haptic_device_set_progress(FuDevice *self, FuProgress *progress)
{
fu_progress_set_id(progress, G_STRLOC);
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 5, "detach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 85, "write");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach");
fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 8, "reload");
}
static void
fu_elantp_hid_haptic_device_init(FuElantpHidHapticDevice *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_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN);
fu_device_add_icon(FU_DEVICE(self), "input-touchpad");
fu_device_add_protocol(FU_DEVICE(self), "tw.com.emc.elantp.haptic");
fu_device_set_name(FU_DEVICE(self), "HapticPad EEPROM");
fu_device_set_logical_id(FU_DEVICE(self), "eeprom");
fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PLAIN);
fu_device_set_priority(FU_DEVICE(self), 1); /* better than i2c */
}
static void
fu_elantp_hid_haptic_device_class_init(FuElantpHidHapticDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
klass_device->to_string = fu_elantp_hid_haptic_device_to_string;
klass_device->attach = fu_elantp_hid_haptic_device_attach;
klass_device->set_quirk_kv = fu_elantp_hid_haptic_device_set_quirk_kv;
klass_device->setup = fu_elantp_hid_haptic_device_setup;
klass_device->reload = fu_elantp_hid_haptic_device_setup;
klass_device->write_firmware = fu_elantp_hid_haptic_device_write_firmware;
klass_device->prepare_firmware = fu_elantp_hid_haptic_device_prepare_firmware;
klass_device->probe = fu_elantp_hid_haptic_device_probe;
klass_device->set_progress = fu_elantp_hid_haptic_device_set_progress;
}
FuElantpHidHapticDevice *
fu_elantp_haptic_device_new(FuDevice *device)
{
FuElantpHidHapticDevice *self;
self = g_object_new(FU_TYPE_ELANTP_HID_HAPTIC_DEVICE, NULL);
return FU_ELANTP_HID_HAPTIC_DEVICE(self);
}