/* * Copyright (C) 2020 Richard Hughes * Copyright (c) 2020 Synaptics Incorporated. * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include "fu-io-channel.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_common_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_common_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_common_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) { /* FuUdevDevice->probe */ if (!FU_DEVICE_CLASS (fu_synaptics_rmi_ps2_device_parent_class)->probe (device, error)) return FALSE; /* 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, 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, error)) return FALSE; } else if (f34->function_version == 0x2) { if (!fu_synaptics_rmi_v7_device_detach (device, 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, 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_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART); fu_device_sleep_with_progress (device, 2); /* 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_device_sleep_with_progress (device, 5); /* 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; }