fwupd/plugins/synapticsmst/fu-synapticsmst-connection.c
Richard Hughes d350b2ecd7 synapticsmst: Partially rewrite the plugin (Fixes #1105)
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
2019-08-28 08:50:15 -05:00

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;
}