fwupd/plugins/synapticsmst/synapticsmst-common.c
Richard Hughes 67a756c93f synapticsmst: Refactor away the global state
The synapticsmst-common.c file had some global state so that cascade devices
could use the device fd. This made the control flow error prone, and it meant
that the fd could be leaked trivially on any error path.

Moving the fd ownership to the device is the logical place for the control, and
means we can create "connections" to access the main device and the cascade
devices.

This fixes the warnings detected by Coverity.
2017-01-19 13:41:04 +00:00

403 lines
9.2 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* 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>
*
* Licensed under the GNU Lesser General Public License Version 2.1
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <glib-object.h>
#include "synapticsmst-common.h"
#include "synapticsmst-device.h"
#define UNIT_SIZE 32
#define MAX_WAIT_TIME 3 /* unit : second */
struct _SynapticsMSTConnection {
gint fd; /* not owned by the connection */
guint8 layer;
guint8 remain_layer;
guint8 rad;
};
guint8
synapticsmst_common_aux_node_read (SynapticsMSTConnection *connection,
gint offset, gint *buf, gint length)
{
if (lseek (connection->fd, offset, SEEK_SET) != offset)
return DPCD_SEEK_FAIL;
if (read (connection->fd, buf, length) != length)
return DPCD_ACCESS_FAIL;
return DPCD_SUCCESS;
}
static guint8
synapticsmst_common_aux_node_write (SynapticsMSTConnection *connection,
gint offset, gint *buf, gint length)
{
if (lseek (connection->fd, offset, SEEK_SET) != offset)
return DPCD_SEEK_FAIL;
if (write (connection->fd, buf, length) != length)
return DPCD_ACCESS_FAIL;
return DPCD_SUCCESS;
}
void
synapticsmst_common_free (SynapticsMSTConnection *connection)
{
g_free (connection);
}
SynapticsMSTConnection *
synapticsmst_common_new (gint fd, guint8 layer, guint rad)
{
SynapticsMSTConnection *connection = g_new0 (SynapticsMSTConnection, 1);
connection->fd = fd;
connection->layer = layer;
connection->remain_layer = layer;
connection->rad = rad;
return connection;
}
guint8
synapticsmst_common_read_dpcd (SynapticsMSTConnection *connection,
gint offset, gint *buf, gint length)
{
if (connection->layer && connection->remain_layer) {
guint8 rc, node;
connection->remain_layer--;
node = (connection->rad >> connection->remain_layer * 2) & 0x03;
rc = synapticsmst_common_rc_get_command (connection,
UPDC_READ_FROM_TX_DPCD + node,
length, offset, (guint8 *)buf);
connection->remain_layer++;
return rc;
}
return synapticsmst_common_aux_node_read (connection, offset, buf, length);
}
guint8
synapticsmst_common_write_dpcd (SynapticsMSTConnection *connection,
gint offset,
gint *buf,
gint length)
{
if (connection->layer && connection->remain_layer) {
guint8 rc, node;
connection->remain_layer--;
node = (connection->rad >> connection->remain_layer * 2) & 0x03;
rc = synapticsmst_common_rc_set_command (connection,
UPDC_WRITE_TO_TX_DPCD + node,
length, offset, (guint8 *)buf);
connection->remain_layer++;
return rc;
}
return synapticsmst_common_aux_node_write (connection, offset, buf, length);
}
guint8
synapticsmst_common_rc_set_command (SynapticsMSTConnection *connection,
gint rc_cmd,
gint length,
gint offset,
guint8 *buf)
{
guint8 rc = 0;
gint cur_offset = offset;
gint 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 */
rc = synapticsmst_common_write_dpcd (connection, REG_RC_DATA, (gint *)buf, cur_length);
if (rc)
break;
/* write offset */
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_OFFSET,
&cur_offset, 4);
if (rc)
break;
/* write length */
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_LEN,
&cur_length, 4);
if (rc)
break;
}
/* send command */
cmd = 0x80 | rc_cmd;
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_CMD,
&cmd, 1);
if (rc)
break;
/* wait command complete */
clock_gettime (CLOCK_REALTIME, &t_spec);
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
do {
rc = synapticsmst_common_read_dpcd (connection,
REG_RC_CMD,
&readData, 2);
clock_gettime (CLOCK_REALTIME, &t_spec);
if (t_spec.tv_sec > deadline) {
rc = -1;
}
} while (rc == 0 && readData & 0x80);
if (rc)
break;
else if (readData & 0xFF00) {
rc = (readData >> 8) & 0xFF;
break;
}
buf += cur_length;
cur_offset += cur_length;
data_left -= cur_length;
} while (data_left);
return rc;
}
guint8
synapticsmst_common_rc_get_command (SynapticsMSTConnection *connection,
gint rc_cmd,
gint length,
gint offset,
guint8 *buf)
{
guint8 rc = 0;
gint cur_offset = offset;
gint cur_length;
gint data_need = length;
gint cmd;
gint 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 */
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_OFFSET,
&cur_offset, 4);
if (rc)
break;
/* write length */
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_LEN,
&cur_length, 4);
if (rc)
break;
}
/* send command */
cmd = 0x80 | rc_cmd;
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_CMD,
&cmd, 1);
if (rc)
break;
/* wait command complete */
clock_gettime (CLOCK_REALTIME, &t_spec);
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
do {
rc = synapticsmst_common_read_dpcd (connection,
REG_RC_CMD,
&readData, 2);
clock_gettime (CLOCK_REALTIME, &t_spec);
if (t_spec.tv_sec > deadline) {
rc = -1;
}
} while (rc == 0 && readData & 0x80);
if (rc)
break;
else if (readData & 0xFF00) {
rc = (readData >> 8) & 0xFF;
break;
}
if (cur_length) {
rc = synapticsmst_common_read_dpcd (connection,
REG_RC_DATA,
(gint *)buf,
cur_length);
if (rc)
break;
}
buf += cur_length;
cur_offset += cur_length;
data_need -= cur_length;
}
return rc;
}
guint8
synapticsmst_common_rc_special_get_command (SynapticsMSTConnection *connection,
gint rc_cmd,
gint cmd_length,
gint cmd_offset,
guint8 *cmd_data,
gint length,
guint8 *buf)
{
guint8 rc = 0;
gint readData = 0;
gint cmd;
long deadline;
struct timespec t_spec;
if (cmd_length) {
/* write cmd data */
if (cmd_data != NULL) {
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_DATA,
(gint *)cmd_data,
cmd_length);
if (rc)
return rc;
}
/* write offset */
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_OFFSET,
&cmd_offset, 4);
if (rc)
return rc;
/* write length */
rc = synapticsmst_common_write_dpcd (connection,
REG_RC_LEN,
&cmd_length, 4);
if (rc)
return rc;
}
/* send command */
cmd = 0x80 | rc_cmd;
rc = synapticsmst_common_write_dpcd (connection, REG_RC_CMD, &cmd, 1);
if (rc)
return rc;
/* wait command complete */
clock_gettime (CLOCK_REALTIME, &t_spec);
deadline = t_spec.tv_sec + MAX_WAIT_TIME;
do {
rc = synapticsmst_common_read_dpcd (connection,
REG_RC_CMD,
&readData, 2);
clock_gettime (CLOCK_REALTIME, &t_spec);
if (t_spec.tv_sec > deadline)
return -1;
} while (readData & 0x80);
if (rc)
return rc;
else if (readData & 0xFF00) {
rc = (readData >> 8) & 0xFF;
return rc;
}
if (length) {
rc = synapticsmst_common_read_dpcd (connection,
REG_RC_DATA,
(gint *)buf, length);
if (rc)
return rc;
}
return rc;
}
guint8
synapticsmst_common_enable_remote_control (SynapticsMSTConnection *connection)
{
const gchar *sc = "PRIUS";
guint8 rc = 0;
for (gint i = 0; i <= connection->layer; i++) {
g_autoptr(SynapticsMSTConnection) connection_tmp = NULL;
connection_tmp = synapticsmst_common_new (connection->fd, i, connection->rad);
rc = synapticsmst_common_rc_set_command (connection_tmp,
UPDC_ENABLE_RC,
5, 0, (guint8*)sc);
if (rc)
break;
}
return rc;
}
guint8
synapticsmst_common_disable_remote_control (SynapticsMSTConnection *connection)
{
guint8 rc = 0;
for (gint i = connection->layer; i >= 0; i--) {
g_autoptr(SynapticsMSTConnection) connection_tmp = NULL;
connection_tmp = synapticsmst_common_new (connection->fd, i, connection->rad);
rc = synapticsmst_common_rc_set_command (connection_tmp,
UPDC_DISABLE_RC,
0, 0, NULL);
if (rc)
break;
}
return rc;
}