fwupd/plugins/synaptics-mst/fu-synaptics-mst-connection.c
Richard Hughes 5c9b1fcc81 Only include the start year in the copyright header
The end year is legally and functionally redundant, and more importantly causes
cherry-pick conflicts when trying to maintain old branches. Use git for history.
2021-01-07 14:48:16 +00:00

509 lines
12 KiB
C

/*
* Copyright (C) 2015 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-synaptics-mst-connection.h"
#include "fu-synaptics-mst-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_synaptics_mst_connection, G_TYPE_OBJECT)
static void
fu_synaptics_mst_connection_init (FuSynapticsMstConnection *self)
{
}
static void
fu_synaptics_mst_connection_class_init (FuSynapticsMstConnectionClass *klass)
{
}
static gboolean
fu_synaptics_mst_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_synaptics_mst_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_synaptics_mst_connection_bus_read (FuSynapticsMstConnection *self,
guint32 offset,
guint8 *buf,
guint32 length, GError **error)
{
return fu_synaptics_mst_connection_aux_node_read (self, offset, buf,
length, error);
}
static gboolean
fu_synaptics_mst_connection_bus_write (FuSynapticsMstConnection *self,
guint32 offset,
const guint8 *buf,
guint32 length, GError **error)
{
return fu_synaptics_mst_connection_aux_node_write (self, offset, buf,
length, error);
}
FuSynapticsMstConnection *
fu_synaptics_mst_connection_new (gint fd, guint8 layer, guint rad)
{
FuSynapticsMstConnection *self = g_object_new (FU_TYPE_SYNAPTICS_MST_CONNECTION, NULL);
self->fd = fd;
self->layer = layer;
self->remain_layer = layer;
self->rad = rad;
return self;
}
gboolean
fu_synaptics_mst_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_synaptics_mst_connection_rc_get_command (self,
UPDC_READ_FROM_TX_DPCD + node,
length, offset, (guint8 *)buf,
error);
self->remain_layer++;
return result;
}
return fu_synaptics_mst_connection_bus_read (self, offset, buf, length, error);
}
gboolean
fu_synaptics_mst_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_synaptics_mst_connection_rc_set_command (self,
UPDC_WRITE_TO_TX_DPCD + node,
length, offset, (guint8 *)buf,
error);
self->remain_layer++;
return result;
}
return fu_synaptics_mst_connection_bus_write (self, offset, buf, length, error);
}
gboolean
fu_synaptics_mst_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_synaptics_mst_connection_write (self,
REG_RC_DATA,
buf, cur_length,
error)) {
g_prefix_error (error, "failure writing data register: ");
return FALSE;
}
/* write offset */
if (!fu_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_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_synaptics_mst_connection_read (self,
REG_RC_DATA,
buf, length,
error)) {
g_prefix_error (error, "failed to read length: ");
}
}
return TRUE;
}
gboolean
fu_synaptics_mst_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_synaptics_mst_connection_new (self->fd, i, self->rad);
if (!fu_synaptics_mst_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_synaptics_mst_connection_disable_rc (FuSynapticsMstConnection *self, GError **error)
{
for (gint i = self->layer; i >= 0; i--) {
g_autoptr(FuSynapticsMstConnection) connection_tmp = NULL;
connection_tmp = fu_synaptics_mst_connection_new (self->fd, i, self->rad);
if (!fu_synaptics_mst_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;
}