mirror of
https://git.proxmox.com/git/fwupd
synced 2025-06-05 10:40:20 +00:00

Now incorporate is fixed to copy across the properties we need in the superclass, we don't need to do the subclass ->probe(). Note, we still need to do the subclassed ->probe() when using FU_UDEV_DEVICE_FLAG_VENDOR_FROM_PARENT or when looking at properties on the parent device. This also removes the spurious 'already set GType to FuVliUsbhubDevice, ignoring FuVliUsbhubDevice' messages when running the daemon.
1022 lines
28 KiB
C
1022 lines
28 KiB
C
/*
|
|
* Copyright (C) 2020 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (c) 2020 Synaptics Incorporated.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <fwupdplugin.h>
|
|
|
|
#include "fu-synaptics-rmi-ps2-device.h"
|
|
#include "fu-synaptics-rmi-v5-device.h"
|
|
#include "fu-synaptics-rmi-v7-device.h"
|
|
|
|
struct _FuSynapticsRmiPs2Device {
|
|
FuSynapticsRmiDevice parent_instance;
|
|
FuIOChannel *io_channel;
|
|
};
|
|
|
|
G_DEFINE_TYPE(FuSynapticsRmiPs2Device, fu_synaptics_rmi_ps2_device, FU_TYPE_SYNAPTICS_RMI_DEVICE)
|
|
|
|
enum EPS2DataPortCommand {
|
|
edpAuxFullRMIBackDoor = 0x7F,
|
|
edpAuxAccessModeByte1 = 0xE0,
|
|
edpAuxAccessModeByte2 = 0xE1,
|
|
edpAuxIBMReadSecondaryID = 0xE1,
|
|
edpAuxSetScaling1To1 = 0xE6,
|
|
edpAuxSetScaling2To1 = 0xE7,
|
|
edpAuxSetResolution = 0xE8,
|
|
edpAuxStatusRequest = 0xE9,
|
|
edpAuxSetStreamMode = 0xEA,
|
|
edpAuxReadData = 0xEB,
|
|
edpAuxResetWrapMode = 0xEC,
|
|
edpAuxSetWrapMode = 0xEE,
|
|
edpAuxSetRemoteMode = 0xF0,
|
|
edpAuxReadDeviceType = 0xF2,
|
|
edpAuxSetSampleRate = 0xF3,
|
|
edpAuxEnable = 0xF4,
|
|
edpAuxDisable = 0xF5,
|
|
edpAuxSetDefault = 0xF6,
|
|
edpAuxResend = 0xFE,
|
|
edpAuxReset = 0xFF,
|
|
};
|
|
|
|
typedef enum {
|
|
esdrTouchPad = 0x47,
|
|
esdrStyk = 0x46,
|
|
esdrControlBar = 0x44,
|
|
esdrRGBControlBar = 0x43,
|
|
} ESynapticsDeviceResponse;
|
|
|
|
enum EStatusRequestSequence {
|
|
esrIdentifySynaptics = 0x00,
|
|
esrReadTouchPadModes = 0x01,
|
|
esrReadModeByte = 0x01,
|
|
esrReadEdgeMargins = 0x02,
|
|
esrReadCapabilities = 0x02,
|
|
esrReadModelID = 0x03,
|
|
esrReadCompilationDate = 0x04,
|
|
esrReadSerialNumberPrefix = 0x06,
|
|
esrReadSerialNumberSuffix = 0x07,
|
|
esrReadResolutions = 0x08,
|
|
esrReadExtraCapabilities1 = 0x09,
|
|
esrReadExtraCapabilities2 = 0x0A,
|
|
esrReadExtraCapabilities3 = 0x0B,
|
|
esrReadExtraCapabilities4 = 0x0C,
|
|
esrReadExtraCapabilities5 = 0x0D,
|
|
esrReadCoordinates = 0x0D,
|
|
esrReadExtraCapabilities6 = 0x0E,
|
|
esrReadExtraCapabilities7 = 0x0F,
|
|
};
|
|
|
|
enum EPS2DataPortStatus {
|
|
edpsAcknowledge = 0xFA,
|
|
edpsError = 0xFC,
|
|
edpsResend = 0xFE,
|
|
edpsTimeOut = 0x100
|
|
};
|
|
|
|
enum ESetSampleRateSequence {
|
|
essrSetModeByte1 = 0x0A,
|
|
essrSetModeByte2 = 0x14,
|
|
essrSetModeByte3 = 0x28,
|
|
essrSetModeByte4 = 0x3C,
|
|
essrSetDeluxeModeByte1 = 0x0A,
|
|
essrSetDeluxeModeByte2 = 0x3C,
|
|
essrSetDeluxeModeByte3 = 0xC8,
|
|
essrFastRecalibrate = 0x50,
|
|
essrPassThroughCommandTunnel = 0x28
|
|
};
|
|
|
|
enum EDeviceType {
|
|
edtUnknown,
|
|
edtTouchPad,
|
|
};
|
|
|
|
enum EStickDeviceType {
|
|
esdtNone = 0,
|
|
esdtIBM,
|
|
esdtJYTSyna = 5,
|
|
esdtSynaptics = 6,
|
|
esdtUnknown = 0xFFFFFFFF
|
|
};
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_read_ack(FuSynapticsRmiPs2Device *self, guint8 *pbuf, GError **error)
|
|
{
|
|
for (guint i = 0; i < 60; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!fu_io_channel_read_raw(self->io_channel,
|
|
pbuf,
|
|
0x1,
|
|
NULL,
|
|
10,
|
|
FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO,
|
|
&error_local)) {
|
|
if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) {
|
|
g_warning("read timed out: %u", i);
|
|
g_usleep(30);
|
|
continue;
|
|
}
|
|
g_propagate_error(error, g_steal_pointer(&error_local));
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "read timed out");
|
|
return FALSE;
|
|
}
|
|
|
|
/* read a single byte from the touchpad */
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_read_byte(FuSynapticsRmiPs2Device *self,
|
|
guint8 *pbuf,
|
|
guint timeout,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail(timeout > 0, FALSE);
|
|
return fu_io_channel_read_raw(self->io_channel,
|
|
pbuf,
|
|
0x1,
|
|
NULL,
|
|
timeout,
|
|
FU_IO_CHANNEL_FLAG_NONE,
|
|
error);
|
|
}
|
|
|
|
/* write a single byte to the touchpad and the read the acknowledge */
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_write_byte(FuSynapticsRmiPs2Device *self,
|
|
guint8 buf,
|
|
guint timeout,
|
|
FuSynapticsRmiDeviceFlags flags,
|
|
GError **error)
|
|
{
|
|
gboolean do_write = TRUE;
|
|
g_return_val_if_fail(timeout > 0, FALSE);
|
|
for (guint i = 0;; i++) {
|
|
guint8 res = 0;
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (do_write) {
|
|
if (!fu_io_channel_write_raw(self->io_channel,
|
|
&buf,
|
|
sizeof(buf),
|
|
timeout,
|
|
FU_IO_CHANNEL_FLAG_FLUSH_INPUT |
|
|
FU_IO_CHANNEL_FLAG_USE_BLOCKING_IO,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
do_write = FALSE;
|
|
|
|
for (;;) {
|
|
/* attempt to read acknowledge... */
|
|
if (!fu_synaptics_rmi_ps2_device_read_ack(self, &res, &error_local)) {
|
|
if (i > 3) {
|
|
g_propagate_prefixed_error(error,
|
|
g_steal_pointer(&error_local),
|
|
"read ack failed: ");
|
|
return FALSE;
|
|
}
|
|
g_warning("read ack failed: %s, retrying", error_local->message);
|
|
break;
|
|
}
|
|
if (res == edpsAcknowledge)
|
|
return TRUE;
|
|
if (res == edpsResend) {
|
|
do_write = TRUE;
|
|
g_debug("resend");
|
|
g_usleep(G_USEC_PER_SEC);
|
|
break;
|
|
}
|
|
if (res == edpsError) {
|
|
do_write = TRUE;
|
|
g_debug("error");
|
|
g_usleep(1000 * 10);
|
|
break;
|
|
}
|
|
g_debug("other response: 0x%x", res);
|
|
g_usleep(1000 * 10);
|
|
}
|
|
if (i >= 3) {
|
|
if (flags & FU_SYNAPTICS_RMI_DEVICE_FLAG_ALLOW_FAILURE) {
|
|
/* just break without error return because FW
|
|
* will not return ACK for commands like RESET */
|
|
break;
|
|
}
|
|
g_set_error_literal(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"cannot write byte after retries");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_set_resolution_sequence(FuSynapticsRmiPs2Device *self,
|
|
guint8 arg,
|
|
gboolean send_e6s,
|
|
GError **error)
|
|
{
|
|
/* send set scaling twice if send_e6s */
|
|
for (gint i = send_e6s ? 2 : 1; i > 0; --i) {
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetScaling1To1,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
for (gint i = 3; i >= 0; --i) {
|
|
guint8 ucTwoBitArg = (arg >> (i * 2)) & 0x3;
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetResolution,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
ucTwoBitArg,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_status_request_sequence(FuSynapticsRmiPs2Device *self,
|
|
guint8 ucArgument,
|
|
guint32 *buf,
|
|
GError **error)
|
|
{
|
|
gboolean success = FALSE;
|
|
|
|
/* allow 3 retries */
|
|
for (guint i = 0; i < 3; ++i) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!fu_synaptics_rmi_ps2_device_set_resolution_sequence(self,
|
|
ucArgument,
|
|
FALSE,
|
|
&error_local)) {
|
|
g_debug("failed set try #%u: %s", i, error_local->message);
|
|
continue;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxStatusRequest,
|
|
10,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
&error_local)) {
|
|
g_debug("failed write try #%u: %s", i, error_local->message);
|
|
continue;
|
|
}
|
|
success = TRUE;
|
|
break;
|
|
}
|
|
if (success == FALSE) {
|
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* read the response from the status request */
|
|
for (gint i = 0; i < 3; ++i) {
|
|
guint8 tmp = 0x0;
|
|
if (!fu_synaptics_rmi_ps2_device_read_byte(self, &tmp, 10, error)) {
|
|
g_prefix_error(error, "failed to read byte: ");
|
|
return FALSE;
|
|
}
|
|
*buf = ((*buf) << 8) | tmp;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_sample_rate_sequence(FuSynapticsRmiPs2Device *self,
|
|
guint8 param,
|
|
guint8 arg,
|
|
gboolean send_e6s,
|
|
GError **error)
|
|
{
|
|
/* allow 3 retries */
|
|
for (guint i = 0;; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (i > 0) {
|
|
/* always send two E6s when retrying */
|
|
send_e6s = TRUE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_set_resolution_sequence(self,
|
|
arg,
|
|
send_e6s,
|
|
&error_local) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetSampleRate,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
&error_local) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
param,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
&error_local)) {
|
|
if (i > 3) {
|
|
g_propagate_error(error, g_steal_pointer(&error_local));
|
|
return FALSE;
|
|
}
|
|
g_warning("failed, will retry: %s", error_local->message);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_detect_synaptics_styk(FuSynapticsRmiPs2Device *self,
|
|
gboolean *result,
|
|
GError **error)
|
|
{
|
|
guint8 buf;
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxIBMReadSecondaryID,
|
|
10,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to write IBMReadSecondaryID(0xE1): ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_read_byte(self, &buf, 10, error)) {
|
|
g_prefix_error(error, "failed to receive IBMReadSecondaryID: ");
|
|
return FALSE;
|
|
}
|
|
if (buf == esdtJYTSyna || buf == esdtSynaptics)
|
|
*result = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_query_build_id(FuSynapticsRmiDevice *rmi_device,
|
|
guint32 *build_id,
|
|
GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device);
|
|
guint32 buf = 0;
|
|
gboolean is_synaptics_styk = FALSE;
|
|
ESynapticsDeviceResponse esdr;
|
|
|
|
if (!fu_synaptics_rmi_ps2_device_status_request_sequence(self,
|
|
esrIdentifySynaptics,
|
|
&buf,
|
|
error)) {
|
|
g_prefix_error(error, "failed to request IdentifySynaptics: ");
|
|
return FALSE;
|
|
}
|
|
g_debug("identify Synaptics response = 0x%x", buf);
|
|
|
|
esdr = (buf & 0xFF00) >> 8;
|
|
if (!fu_synaptics_rmi_ps2_device_detect_synaptics_styk(self, &is_synaptics_styk, error)) {
|
|
g_prefix_error(error, "failed to detect Synaptics styk: ");
|
|
return FALSE;
|
|
}
|
|
fu_synaptics_rmi_device_set_iepmode(rmi_device, FALSE);
|
|
if (esdr == esdrTouchPad || is_synaptics_styk) {
|
|
/* Get the firmware id from the Extra Capabilities 2 Byte
|
|
* The firmware id is located in bits 0 - 23 */
|
|
if (!fu_synaptics_rmi_ps2_device_status_request_sequence(self,
|
|
esrReadExtraCapabilities2,
|
|
build_id,
|
|
error)) {
|
|
g_prefix_error(error, "failed to read extraCapabilities2: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_query_product_sub_id(FuSynapticsRmiDevice *rmi_device,
|
|
guint8 *sub_id,
|
|
GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device);
|
|
guint32 buf = 0;
|
|
if (!fu_synaptics_rmi_ps2_device_status_request_sequence(self,
|
|
esrReadCapabilities,
|
|
&buf,
|
|
error)) {
|
|
g_prefix_error(error,
|
|
"failed to status_request_sequence read esrReadCapabilities: ");
|
|
return FALSE;
|
|
}
|
|
*sub_id = (buf >> 8) & 0xFF;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_enter_iep_mode(FuSynapticsRmiDevice *rmi_device, GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device);
|
|
|
|
/* disable stream */
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxDisable,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to disable stream mode: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* enable RMI mode */
|
|
if (!fu_synaptics_rmi_ps2_device_sample_rate_sequence(self,
|
|
essrSetModeByte2,
|
|
edpAuxFullRMIBackDoor,
|
|
FALSE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to enter RMI mode: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_write_rmi_register(FuSynapticsRmiPs2Device *self,
|
|
guint8 addr,
|
|
const guint8 *buf,
|
|
guint8 buflen,
|
|
guint timeout,
|
|
FuSynapticsRmiDeviceFlags flags,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail(timeout > 0, FALSE);
|
|
|
|
if (!fu_synaptics_rmi_device_enter_iep_mode(FU_SYNAPTICS_RMI_DEVICE(self),
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetScaling2To1,
|
|
timeout,
|
|
flags,
|
|
error)) {
|
|
g_prefix_error(error, "failed to edpAuxSetScaling2To1: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetSampleRate,
|
|
timeout,
|
|
flags,
|
|
error)) {
|
|
g_prefix_error(error, "failed to edpAuxSetSampleRate: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self, addr, timeout, flags, error)) {
|
|
g_prefix_error(error, "failed to write address: ");
|
|
return FALSE;
|
|
}
|
|
for (guint8 i = 0; i < buflen; i++) {
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetSampleRate,
|
|
timeout,
|
|
flags,
|
|
error)) {
|
|
g_prefix_error(error, "failed to set byte %u: ", i);
|
|
return FALSE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self, buf[i], timeout, flags, error)) {
|
|
g_prefix_error(error, "failed to write byte %u: ", i);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
g_usleep(1000 * 20);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_read_rmi_register(FuSynapticsRmiPs2Device *self,
|
|
guint8 addr,
|
|
guint8 *buf,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail(buf != NULL, FALSE);
|
|
|
|
if (!fu_synaptics_rmi_device_enter_iep_mode(FU_SYNAPTICS_RMI_DEVICE(self),
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
for (guint retries = 0;; retries++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetScaling2To1,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetSampleRate,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
addr,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxStatusRequest,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to write command in Read RMI register: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_read_byte(self, buf, 10, &error_local)) {
|
|
if (retries++ > 2) {
|
|
g_propagate_prefixed_error(
|
|
error,
|
|
g_steal_pointer(&error_local),
|
|
"failed to read byte @0x%x after %u retries: ",
|
|
addr,
|
|
retries);
|
|
return FALSE;
|
|
}
|
|
g_debug("failed to read byte @0x%x: %s", addr, error_local->message);
|
|
continue;
|
|
}
|
|
|
|
/* success */
|
|
break;
|
|
}
|
|
|
|
/* success */
|
|
g_usleep(1000 * 20);
|
|
return TRUE;
|
|
}
|
|
|
|
static GByteArray *
|
|
fu_synaptics_rmi_ps2_device_read_rmi_packet_register(FuSynapticsRmiPs2Device *self,
|
|
guint8 addr,
|
|
guint req_sz,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) buf = g_byte_array_new();
|
|
|
|
if (!fu_synaptics_rmi_device_enter_iep_mode(FU_SYNAPTICS_RMI_DEVICE(self),
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error))
|
|
return NULL;
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetScaling2To1,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxSetSampleRate,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
addr,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error) ||
|
|
!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxStatusRequest,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to write command in Read RMI Packet Register: ");
|
|
return NULL;
|
|
}
|
|
for (guint i = 0; i < req_sz; ++i) {
|
|
guint8 tmp = 0;
|
|
if (!fu_synaptics_rmi_ps2_device_read_byte(self, &tmp, 10, error)) {
|
|
g_prefix_error(error, "failed to read byte %u: ", i);
|
|
return NULL;
|
|
}
|
|
fu_byte_array_append_uint8(buf, tmp);
|
|
}
|
|
|
|
g_usleep(1000 * 20);
|
|
return g_steal_pointer(&buf);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_query_status(FuSynapticsRmiDevice *rmi_device, GError **error)
|
|
{
|
|
FuSynapticsRmiFunction *f34;
|
|
g_debug("ps2 query status");
|
|
f34 = fu_synaptics_rmi_device_get_function(rmi_device, 0x34, error);
|
|
if (f34 == NULL)
|
|
return FALSE;
|
|
if (f34->function_version == 0x0 || f34->function_version == 0x1) {
|
|
return fu_synaptics_rmi_v5_device_query_status(rmi_device, error);
|
|
}
|
|
if (f34->function_version == 0x2) {
|
|
return fu_synaptics_rmi_v7_device_query_status(rmi_device, error);
|
|
}
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"f34 function version 0x%02x unsupported",
|
|
f34->function_version);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_set_page(FuSynapticsRmiDevice *rmi_device, guint8 page, GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device);
|
|
if (!fu_synaptics_rmi_ps2_device_write_rmi_register(self,
|
|
RMI_DEVICE_PAGE_SELECT_REGISTER,
|
|
&page,
|
|
1,
|
|
20,
|
|
FALSE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to write page %u: ", page);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static GByteArray *
|
|
fu_synaptics_rmi_ps2_device_read(FuSynapticsRmiDevice *rmi_device,
|
|
guint16 addr,
|
|
gsize req_sz,
|
|
GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device);
|
|
g_autoptr(GByteArray) buf = NULL;
|
|
g_autofree gchar *dump = NULL;
|
|
|
|
if (!fu_synaptics_rmi_device_set_page(rmi_device, addr >> 8, error)) {
|
|
g_prefix_error(error, "failed to set RMI page:");
|
|
return NULL;
|
|
}
|
|
|
|
for (guint retries = 0;; retries++) {
|
|
buf = g_byte_array_new();
|
|
for (guint i = 0; i < req_sz; i++) {
|
|
guint8 tmp = 0x0;
|
|
if (!fu_synaptics_rmi_ps2_device_read_rmi_register(
|
|
self,
|
|
(guint8)((addr & 0x00FF) + i),
|
|
&tmp,
|
|
error)) {
|
|
g_prefix_error(error, "failed register read 0x%x: ", addr + i);
|
|
return NULL;
|
|
}
|
|
fu_byte_array_append_uint8(buf, tmp);
|
|
}
|
|
if (buf->len != req_sz) {
|
|
g_debug("buf->len(%u) != req_sz(%u)", buf->len, (guint)req_sz);
|
|
if (retries++ > 2) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"buffer length did not match: %u vs %u",
|
|
buf->len,
|
|
(guint)req_sz);
|
|
return NULL;
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
dump = g_strdup_printf("R %x", addr);
|
|
if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) {
|
|
fu_dump_full(G_LOG_DOMAIN, dump, buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE);
|
|
}
|
|
return g_steal_pointer(&buf);
|
|
}
|
|
|
|
static GByteArray *
|
|
fu_synaptics_rmi_ps2_device_read_packet_register(FuSynapticsRmiDevice *rmi_device,
|
|
guint16 addr,
|
|
gsize req_sz,
|
|
GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device);
|
|
g_autoptr(GByteArray) buf = NULL;
|
|
|
|
if (!fu_synaptics_rmi_device_set_page(rmi_device, addr >> 8, error)) {
|
|
g_prefix_error(error, "failed to set RMI page:");
|
|
return NULL;
|
|
}
|
|
|
|
buf = fu_synaptics_rmi_ps2_device_read_rmi_packet_register(self, addr, req_sz, error);
|
|
if (buf == NULL) {
|
|
g_prefix_error(error, "failed packet register read %x: ", addr);
|
|
return NULL;
|
|
}
|
|
|
|
if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) {
|
|
g_autofree gchar *dump = g_strdup_printf("R %x", addr);
|
|
fu_dump_full(G_LOG_DOMAIN, dump, buf->data, buf->len, 80, FU_DUMP_FLAGS_NONE);
|
|
}
|
|
return g_steal_pointer(&buf);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_write(FuSynapticsRmiDevice *rmi_device,
|
|
guint16 addr,
|
|
GByteArray *req,
|
|
FuSynapticsRmiDeviceFlags flags,
|
|
GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(rmi_device);
|
|
if (!fu_synaptics_rmi_device_set_page(rmi_device, addr >> 8, error)) {
|
|
g_prefix_error(error, "failed to set RMI page: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_synaptics_rmi_ps2_device_write_rmi_register(self,
|
|
addr & 0x00FF,
|
|
req->data,
|
|
req->len,
|
|
1000, /* timeout */
|
|
flags,
|
|
error)) {
|
|
g_prefix_error(error, "failed to write register %x: ", addr);
|
|
return FALSE;
|
|
}
|
|
if (g_getenv("FWUPD_SYNAPTICS_RMI_VERBOSE") != NULL) {
|
|
g_autofree gchar *str = g_strdup_printf("W %x", addr);
|
|
fu_dump_full(G_LOG_DOMAIN, str, req->data, req->len, 80, FU_DUMP_FLAGS_NONE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_write_bus_select(FuSynapticsRmiDevice *rmi_device,
|
|
guint8 bus,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GByteArray) req = g_byte_array_new();
|
|
fu_byte_array_append_uint8(req, bus);
|
|
if (!fu_synaptics_rmi_ps2_device_write(rmi_device,
|
|
RMI_DEVICE_BUS_SELECT_REGISTER,
|
|
req,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to write rmi register %u: ", bus);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_probe(FuDevice *device, GError **error)
|
|
{
|
|
/* psmouse is the usual mode, but serio is needed for update */
|
|
if (g_strcmp0(fu_udev_device_get_driver(FU_UDEV_DEVICE(device)), "serio_raw") == 0) {
|
|
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
} else {
|
|
fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
|
|
}
|
|
|
|
/* set the physical ID */
|
|
return fu_udev_device_set_physical_id(FU_UDEV_DEVICE(device), "platform", error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_open(FuDevice *device, GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(device);
|
|
guint8 buf[2] = {0x0};
|
|
|
|
/* FuUdevDevice->open */
|
|
if (!FU_DEVICE_CLASS(fu_synaptics_rmi_ps2_device_parent_class)->open(device, error))
|
|
return FALSE;
|
|
|
|
/* create channel */
|
|
self->io_channel = fu_io_channel_unix_new(fu_udev_device_get_fd(FU_UDEV_DEVICE(device)));
|
|
|
|
/* in serio_raw mode */
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
/* clear out any data in the serio_raw queue */
|
|
for (guint i = 0; i < 0xffff; i++) {
|
|
guint8 tmp = 0;
|
|
if (!fu_synaptics_rmi_ps2_device_read_byte(self, &tmp, 20, NULL))
|
|
break;
|
|
}
|
|
|
|
/* send reset -- may take 300-500ms */
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxReset,
|
|
600,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to reset: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* read the 0xAA 0x00 announcing the touchpad is ready */
|
|
if (!fu_synaptics_rmi_ps2_device_read_byte(self, &buf[0], 500, error) ||
|
|
!fu_synaptics_rmi_ps2_device_read_byte(self, &buf[1], 500, error)) {
|
|
g_prefix_error(error, "failed to read 0xAA00: ");
|
|
return FALSE;
|
|
}
|
|
if (buf[0] != 0xAA || buf[1] != 0x00) {
|
|
g_set_error(error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to read 0xAA00, got 0x%02X%02X: ",
|
|
buf[0],
|
|
buf[1]);
|
|
return FALSE;
|
|
}
|
|
|
|
/* disable the device so that it stops reporting finger data */
|
|
if (!fu_synaptics_rmi_ps2_device_write_byte(self,
|
|
edpAuxDisable,
|
|
50,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error)) {
|
|
g_prefix_error(error, "failed to disable stream mode: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_close(FuDevice *device, GError **error)
|
|
{
|
|
FuSynapticsRmiPs2Device *self = FU_SYNAPTICS_RMI_PS2_DEVICE(device);
|
|
fu_udev_device_set_fd(FU_UDEV_DEVICE(device), -1);
|
|
g_clear_object(&self->io_channel);
|
|
|
|
/* FuUdevDevice->close */
|
|
return FU_DEVICE_CLASS(fu_synaptics_rmi_ps2_device_parent_class)->close(device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_detach(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuSynapticsRmiDevice *self = FU_SYNAPTICS_RMI_DEVICE(device);
|
|
FuSynapticsRmiFunction *f34;
|
|
|
|
/* sanity check */
|
|
if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_debug("already in bootloader mode, skipping");
|
|
return TRUE;
|
|
}
|
|
|
|
/* put in serio_raw mode so that we can do register writes */
|
|
if (!fu_udev_device_write_sysfs(FU_UDEV_DEVICE(device), "drvctl", "serio_raw", error)) {
|
|
g_prefix_error(error, "failed to write to drvctl: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* rescan device */
|
|
if (!fu_device_close(device, error))
|
|
return FALSE;
|
|
if (!fu_device_rescan(device, error))
|
|
return FALSE;
|
|
if (!fu_device_open(device, error))
|
|
return FALSE;
|
|
|
|
f34 = fu_synaptics_rmi_device_get_function(self, 0x34, error);
|
|
if (f34 == NULL)
|
|
return FALSE;
|
|
if (f34->function_version == 0x0 || f34->function_version == 0x1) {
|
|
if (!fu_synaptics_rmi_v5_device_detach(device, progress, error))
|
|
return FALSE;
|
|
} else if (f34->function_version == 0x2) {
|
|
if (!fu_synaptics_rmi_v7_device_detach(device, progress, error))
|
|
return FALSE;
|
|
} else {
|
|
g_set_error(error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"f34 function version 0x%02x unsupported",
|
|
f34->function_version);
|
|
return FALSE;
|
|
}
|
|
|
|
/* set iepmode before querying device forcibly because of FW requirement */
|
|
if (!fu_synaptics_rmi_device_enter_iep_mode(self,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_FORCE,
|
|
error))
|
|
return FALSE;
|
|
|
|
if (!fu_synaptics_rmi_ps2_device_query_status(self, error)) {
|
|
g_prefix_error(error, "failed to query status after detach: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_setup(FuDevice *device, GError **error)
|
|
{
|
|
/* we can only scan the PDT in serio_raw mode */
|
|
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER))
|
|
return TRUE;
|
|
return FU_DEVICE_CLASS(fu_synaptics_rmi_ps2_device_parent_class)->setup(device, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_attach(FuDevice *device, FuProgress *progress, GError **error)
|
|
{
|
|
FuSynapticsRmiDevice *rmi_device = FU_SYNAPTICS_RMI_DEVICE(device);
|
|
|
|
/* sanity check */
|
|
if (!fu_device_has_flag(device, FWUPD_DEVICE_FLAG_IS_BOOTLOADER)) {
|
|
g_debug("already in runtime mode, skipping");
|
|
return TRUE;
|
|
}
|
|
|
|
/* set iepmode before reset device forcibly because of FW requirement */
|
|
fu_synaptics_rmi_device_set_iepmode(rmi_device, FALSE);
|
|
|
|
/* delay after writing */
|
|
fu_progress_sleep(progress, 2000);
|
|
|
|
/* reset device */
|
|
if (!fu_synaptics_rmi_device_enter_iep_mode(rmi_device,
|
|
FU_SYNAPTICS_RMI_DEVICE_FLAG_NONE,
|
|
error))
|
|
return FALSE;
|
|
if (!fu_synaptics_rmi_device_reset(rmi_device, error)) {
|
|
g_prefix_error(error, "failed to reset device: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* delay after reset */
|
|
fu_progress_sleep(progress, 5000);
|
|
|
|
/* back to psmouse */
|
|
if (!fu_udev_device_write_sysfs(FU_UDEV_DEVICE(device), "drvctl", "psmouse", error)) {
|
|
g_prefix_error(error, "failed to write to drvctl: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* rescan device */
|
|
return fu_device_rescan(device, error);
|
|
}
|
|
|
|
static void
|
|
fu_synaptics_rmi_ps2_device_init(FuSynapticsRmiPs2Device *self)
|
|
{
|
|
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
|
|
fu_device_set_name(FU_DEVICE(self), "TouchStyk");
|
|
fu_device_set_vendor(FU_DEVICE(self), "Synaptics");
|
|
fu_device_add_vendor_id(FU_DEVICE(self), "HIDRAW:0x06CB");
|
|
fu_synaptics_rmi_device_set_max_page(FU_SYNAPTICS_RMI_DEVICE(self), 0x1);
|
|
fu_udev_device_set_flags(FU_UDEV_DEVICE(self),
|
|
FU_UDEV_DEVICE_FLAG_OPEN_READ | FU_UDEV_DEVICE_FLAG_OPEN_WRITE);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synaptics_rmi_ps2_device_wait_for_attr(FuSynapticsRmiDevice *rmi_device,
|
|
guint8 source_mask,
|
|
guint timeout_ms,
|
|
GError **error)
|
|
{
|
|
g_usleep(1000 * timeout_ms);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_synaptics_rmi_ps2_device_class_init(FuSynapticsRmiPs2DeviceClass *klass)
|
|
{
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
|
|
FuSynapticsRmiDeviceClass *klass_rmi = FU_SYNAPTICS_RMI_DEVICE_CLASS(klass);
|
|
klass_device->attach = fu_synaptics_rmi_ps2_device_attach;
|
|
klass_device->detach = fu_synaptics_rmi_ps2_device_detach;
|
|
klass_device->setup = fu_synaptics_rmi_ps2_device_setup;
|
|
klass_device->probe = fu_synaptics_rmi_ps2_device_probe;
|
|
klass_device->open = fu_synaptics_rmi_ps2_device_open;
|
|
klass_device->close = fu_synaptics_rmi_ps2_device_close;
|
|
klass_rmi->read = fu_synaptics_rmi_ps2_device_read;
|
|
klass_rmi->write = fu_synaptics_rmi_ps2_device_write;
|
|
klass_rmi->set_page = fu_synaptics_rmi_ps2_device_set_page;
|
|
klass_rmi->query_status = fu_synaptics_rmi_ps2_device_query_status;
|
|
klass_rmi->query_build_id = fu_synaptics_rmi_ps2_device_query_build_id;
|
|
klass_rmi->query_product_sub_id = fu_synaptics_rmi_ps2_device_query_product_sub_id;
|
|
klass_rmi->wait_for_attr = fu_synaptics_rmi_ps2_device_wait_for_attr;
|
|
klass_rmi->enter_iep_mode = fu_synaptics_rmi_ps2_device_enter_iep_mode;
|
|
klass_rmi->write_bus_select = fu_synaptics_rmi_ps2_device_write_bus_select;
|
|
klass_rmi->read_packet_register = fu_synaptics_rmi_ps2_device_read_packet_register;
|
|
}
|