mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-12 08:21:55 +00:00

This removes the 'two-layer' FuDevice and FuSyanpticsmstDevice model, where a complicated plugin cache was used to add and remove devices to the daemon. By making FuSynapticsmstDevice derive from FuDevice rather than GObject we can also use a lot of the helper functionality like the other plugins, for instance ->prepare_firmware(). The `drm_dp_aux_dev` devices do not emit uevents on unplug/re-plug and so all devices of `drm` class are watched and the actual DP AUX devices rescanned after a small delay. When the AUX devices emit changes from the kernel this workaround can be removed. Also drop force power support for MST controllers in the Dell plugin. Overall this has just led to more problems than it's helped. * Monitor flickers when turned on * Crashing graphics drivers from time to time * Fragile logic that doesn't always represent the device state
509 lines
12 KiB
C
509 lines
12 KiB
C
/*
|
|
* Copyright (C) 2015-2017 Richard Hughes <richard@hughsie.com>
|
|
* Copyright (C) 2016 Mario Limonciello <mario.limonciello@dell.com>
|
|
* Copyright (C) 2017 Peichen Huang <peichenhuang@tw.synaptics.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "fu-synapticsmst-connection.h"
|
|
#include "fu-synapticsmst-common.h"
|
|
|
|
#define UNIT_SIZE 32
|
|
#define MAX_WAIT_TIME 3 /* unit : second */
|
|
|
|
struct _FuSynapticsmstConnection {
|
|
GObject parent_instance;
|
|
gint fd; /* not owned by the connection */
|
|
guint8 layer;
|
|
guint8 remain_layer;
|
|
guint8 rad;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuSynapticsmstConnection, fu_synapticsmst_connection, G_TYPE_OBJECT)
|
|
|
|
static void
|
|
fu_synapticsmst_connection_init (FuSynapticsmstConnection *self)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_synapticsmst_connection_class_init (FuSynapticsmstConnectionClass *klass)
|
|
{
|
|
}
|
|
|
|
static gboolean
|
|
fu_synapticsmst_connection_aux_node_read (FuSynapticsmstConnection *self,
|
|
guint32 offset, guint8 *buf,
|
|
gint length, GError **error)
|
|
{
|
|
if (lseek (self->fd, offset, SEEK_SET) != offset) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to lseek to 0x%x on layer:%u, rad:0x%x",
|
|
offset, self->layer, self->rad);
|
|
return FALSE;
|
|
}
|
|
|
|
if (read (self->fd, buf, length) != length) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to read 0x%x bytes on layer:%u, rad:0x%x",
|
|
(guint) length, self->layer, self->rad);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synapticsmst_connection_aux_node_write (FuSynapticsmstConnection *self,
|
|
guint32 offset, const guint8 *buf,
|
|
gint length, GError **error)
|
|
{
|
|
if (lseek (self->fd, offset, SEEK_SET) != offset) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to lseek to 0x%x on layer:%u, rad:0x%x",
|
|
offset, self->layer, self->rad);
|
|
return FALSE;
|
|
}
|
|
|
|
if (write (self->fd, buf, length) != length) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to write 0x%x bytes on layer:%u, rad:0x%x",
|
|
(guint) length, self->layer, self->rad);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_synapticsmst_connection_bus_read (FuSynapticsmstConnection *self,
|
|
guint32 offset,
|
|
guint8 *buf,
|
|
guint32 length, GError **error)
|
|
{
|
|
return fu_synapticsmst_connection_aux_node_read (self, offset, buf,
|
|
length, error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_synapticsmst_connection_bus_write (FuSynapticsmstConnection *self,
|
|
guint32 offset,
|
|
const guint8 *buf,
|
|
guint32 length, GError **error)
|
|
{
|
|
return fu_synapticsmst_connection_aux_node_write (self, offset, buf,
|
|
length, error);
|
|
}
|
|
|
|
FuSynapticsmstConnection *
|
|
fu_synapticsmst_connection_new (gint fd, guint8 layer, guint rad)
|
|
{
|
|
FuSynapticsmstConnection *self = g_object_new (FU_TYPE_SYNAPTICSMST_CONNECTION, NULL);
|
|
self->fd = fd;
|
|
self->layer = layer;
|
|
self->remain_layer = layer;
|
|
self->rad = rad;
|
|
return self;
|
|
}
|
|
|
|
gboolean
|
|
fu_synapticsmst_connection_read (FuSynapticsmstConnection *self,
|
|
guint32 offset, guint8 *buf,
|
|
guint32 length, GError **error)
|
|
{
|
|
if (self->layer && self->remain_layer) {
|
|
guint8 node;
|
|
gboolean result;
|
|
|
|
self->remain_layer--;
|
|
node = (self->rad >> self->remain_layer * 2) & 0x03;
|
|
result = fu_synapticsmst_connection_rc_get_command (self,
|
|
UPDC_READ_FROM_TX_DPCD + node,
|
|
length, offset, (guint8 *)buf,
|
|
error);
|
|
self->remain_layer++;
|
|
return result;
|
|
}
|
|
|
|
return fu_synapticsmst_connection_bus_read (self, offset, buf, length, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_synapticsmst_connection_write (FuSynapticsmstConnection *self,
|
|
guint32 offset,
|
|
const guint8 *buf,
|
|
guint32 length, GError **error)
|
|
{
|
|
if (self->layer && self->remain_layer) {
|
|
guint8 node;
|
|
gboolean result;
|
|
|
|
self->remain_layer--;
|
|
node = (self->rad >> self->remain_layer * 2) & 0x03;
|
|
result = fu_synapticsmst_connection_rc_set_command (self,
|
|
UPDC_WRITE_TO_TX_DPCD + node,
|
|
length, offset, (guint8 *)buf,
|
|
error);
|
|
self->remain_layer++;
|
|
return result;
|
|
}
|
|
|
|
return fu_synapticsmst_connection_bus_write (self, offset, buf, length, error);
|
|
}
|
|
|
|
gboolean
|
|
fu_synapticsmst_connection_rc_set_command (FuSynapticsmstConnection *self,
|
|
guint32 rc_cmd,
|
|
guint32 length,
|
|
guint32 offset,
|
|
const guint8 *buf,
|
|
GError **error)
|
|
{
|
|
guint32 cur_offset = offset;
|
|
guint32 cur_length;
|
|
gint data_left = length;
|
|
gint cmd;
|
|
gint readData = 0;
|
|
long deadline;
|
|
struct timespec t_spec;
|
|
|
|
do {
|
|
if (data_left > UNIT_SIZE) {
|
|
cur_length = UNIT_SIZE;
|
|
} else {
|
|
cur_length = data_left;
|
|
}
|
|
|
|
if (cur_length) {
|
|
/* write data */
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_DATA,
|
|
buf, cur_length,
|
|
error)) {
|
|
g_prefix_error (error, "failure writing data register: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* write offset */
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_OFFSET,
|
|
(guint8 *)&cur_offset, 4,
|
|
error)) {
|
|
g_prefix_error (error, "failure writing offset register: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* write length */
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_LEN,
|
|
(guint8 *)&cur_length, 4,
|
|
error)) {
|
|
g_prefix_error (error, "failure writing length register: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* send command */
|
|
cmd = 0x80 | rc_cmd;
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_CMD,
|
|
(guint8 *)&cmd, 1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to write command: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* wait command complete */
|
|
clock_gettime (CLOCK_REALTIME, &t_spec);
|
|
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
|
|
|
|
do {
|
|
if (!fu_synapticsmst_connection_read (self,
|
|
REG_RC_CMD,
|
|
(guint8 *)&readData, 2,
|
|
error)) {
|
|
g_prefix_error (error, "failed to read command: ");
|
|
return FALSE;
|
|
}
|
|
clock_gettime (CLOCK_REALTIME, &t_spec);
|
|
if (t_spec.tv_sec > deadline) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"timeout exceeded");
|
|
return FALSE;
|
|
}
|
|
} while (readData & 0x80);
|
|
|
|
if (readData & 0xFF00) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"remote command failed: %d",
|
|
(readData >> 8) & 0xFF);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
buf += cur_length;
|
|
cur_offset += cur_length;
|
|
data_left -= cur_length;
|
|
} while (data_left);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_synapticsmst_connection_rc_get_command (FuSynapticsmstConnection *self,
|
|
guint32 rc_cmd,
|
|
guint32 length,
|
|
guint32 offset,
|
|
guint8 *buf,
|
|
GError **error)
|
|
{
|
|
guint32 cur_offset = offset;
|
|
guint32 cur_length;
|
|
gint data_need = length;
|
|
guint32 cmd;
|
|
guint32 readData = 0;
|
|
long deadline;
|
|
struct timespec t_spec;
|
|
|
|
while (data_need) {
|
|
if (data_need > UNIT_SIZE) {
|
|
cur_length = UNIT_SIZE;
|
|
} else {
|
|
cur_length = data_need;
|
|
}
|
|
|
|
if (cur_length) {
|
|
/* write offset */
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_OFFSET,
|
|
(guint8 *)&cur_offset, 4,
|
|
error)) {
|
|
g_prefix_error (error, "failed to write offset: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* write length */
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_LEN,
|
|
(guint8 *)&cur_length, 4,
|
|
error)) {
|
|
g_prefix_error (error, "failed to write length: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* send command */
|
|
cmd = 0x80 | rc_cmd;
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_CMD,
|
|
(guint8 *)&cmd, 1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to write command: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* wait command complete */
|
|
clock_gettime (CLOCK_REALTIME, &t_spec);
|
|
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
|
|
|
|
do {
|
|
if (!fu_synapticsmst_connection_read (self,
|
|
REG_RC_CMD,
|
|
(guint8 *)&readData, 2,
|
|
error)) {
|
|
g_prefix_error (error, "failed to read command: ");
|
|
return FALSE;
|
|
}
|
|
clock_gettime (CLOCK_REALTIME, &t_spec);
|
|
if (t_spec.tv_sec > deadline) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"timeout exceeded");
|
|
return FALSE;
|
|
}
|
|
} while (readData & 0x80);
|
|
|
|
if (readData & 0xFF00) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"remote command failed: %u",
|
|
(readData >> 8) & 0xFF);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (cur_length) {
|
|
if (!fu_synapticsmst_connection_read (self,
|
|
REG_RC_DATA,
|
|
buf,
|
|
cur_length,
|
|
error)) {
|
|
g_prefix_error (error, "failed to read data: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
buf += cur_length;
|
|
cur_offset += cur_length;
|
|
data_need -= cur_length;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_synapticsmst_connection_rc_special_get_command (FuSynapticsmstConnection *self,
|
|
guint32 rc_cmd,
|
|
guint32 cmd_length,
|
|
guint32 cmd_offset,
|
|
guint8 *cmd_data,
|
|
guint32 length,
|
|
guint8 *buf,
|
|
GError **error)
|
|
{
|
|
guint32 readData = 0;
|
|
guint32 cmd;
|
|
long deadline;
|
|
struct timespec t_spec;
|
|
|
|
if (cmd_length) {
|
|
/* write cmd data */
|
|
if (cmd_data != NULL) {
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_DATA,
|
|
cmd_data,
|
|
cmd_length,
|
|
error)) {
|
|
g_prefix_error (error, "Failed to write command data: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* write offset */
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_OFFSET,
|
|
(guint8 *)&cmd_offset, 4,
|
|
error)) {
|
|
g_prefix_error (error, "failed to write offset: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* write length */
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_LEN,
|
|
(guint8 *)&cmd_length, 4,
|
|
error)) {
|
|
g_prefix_error (error, "failed to write length: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* send command */
|
|
cmd = 0x80 | rc_cmd;
|
|
if (!fu_synapticsmst_connection_write (self,
|
|
REG_RC_CMD,
|
|
(guint8 *)&cmd, 1,
|
|
error)) {
|
|
g_prefix_error (error, "failed to write command: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* wait command complete */
|
|
clock_gettime (CLOCK_REALTIME, &t_spec);
|
|
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
|
|
do {
|
|
if (!fu_synapticsmst_connection_read (self,
|
|
REG_RC_CMD,
|
|
(guint8 *)&readData, 2,
|
|
error)) {
|
|
g_prefix_error (error, "failed to read command: ");
|
|
return FALSE;
|
|
}
|
|
clock_gettime (CLOCK_REALTIME, &t_spec);
|
|
if (t_spec.tv_sec > deadline) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"timeout exceeded");
|
|
return FALSE;
|
|
|
|
}
|
|
} while (readData & 0x80);
|
|
|
|
if (readData & 0xFF00) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"remote command failed: %u",
|
|
(readData >> 8) & 0xFF);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (length) {
|
|
if (!fu_synapticsmst_connection_read (self,
|
|
REG_RC_DATA,
|
|
buf, length,
|
|
error)) {
|
|
g_prefix_error (error, "failed to read length: ");
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_synapticsmst_connection_enable_rc (FuSynapticsmstConnection *self, GError **error)
|
|
{
|
|
const gchar *sc = "PRIUS";
|
|
|
|
for (gint i = 0; i <= self->layer; i++) {
|
|
g_autoptr(FuSynapticsmstConnection) connection_tmp = NULL;
|
|
connection_tmp = fu_synapticsmst_connection_new (self->fd, i, self->rad);
|
|
if (!fu_synapticsmst_connection_rc_set_command (connection_tmp,
|
|
UPDC_ENABLE_RC,
|
|
5, 0, (guint8*)sc,
|
|
error)) {
|
|
g_prefix_error (error, "failed to enable remote control: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_synapticsmst_connection_disable_rc (FuSynapticsmstConnection *self, GError **error)
|
|
{
|
|
for (gint i = self->layer; i >= 0; i--) {
|
|
g_autoptr(FuSynapticsmstConnection) connection_tmp = NULL;
|
|
connection_tmp = fu_synapticsmst_connection_new (self->fd, i, self->rad);
|
|
if (!fu_synapticsmst_connection_rc_set_command (connection_tmp,
|
|
UPDC_DISABLE_RC,
|
|
0, 0, NULL,
|
|
error)) {
|
|
g_prefix_error (error, "failed to disable remote control: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|