mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-17 22:24:21 +00:00
1399 lines
37 KiB
C
1399 lines
37 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2016-2017 Richard Hughes <richard@hughsie.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 "config.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <glib/gstdio.h>
|
|
#include <string.h>
|
|
|
|
#include "lu-common.h"
|
|
#include "lu-device-bootloader-nordic.h"
|
|
#include "lu-device-bootloader-texas.h"
|
|
#include "lu-device.h"
|
|
#include "lu-device-runtime.h"
|
|
#include "lu-hidpp.h"
|
|
|
|
typedef struct
|
|
{
|
|
LuDeviceKind kind;
|
|
GUdevDevice *udev_device;
|
|
gint udev_device_fd;
|
|
GUsbDevice *usb_device;
|
|
gchar *platform_id;
|
|
gchar *product;
|
|
gchar *vendor;
|
|
gchar *version_bl;
|
|
gchar *version_fw;
|
|
gchar *version_hw;
|
|
GPtrArray *guids;
|
|
LuDeviceFlags flags;
|
|
guint8 hidpp_id;
|
|
guint8 battery_level;
|
|
gdouble hidpp_version;
|
|
GPtrArray *feature_index;
|
|
} LuDevicePrivate;
|
|
|
|
typedef struct {
|
|
guint8 idx;
|
|
guint16 feature;
|
|
} LuDeviceHidppMap;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (LuDevice, lu_device, G_TYPE_OBJECT)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_KIND,
|
|
PROP_HIDPP_ID,
|
|
PROP_FLAGS,
|
|
PROP_PLATFORM_ID,
|
|
PROP_UDEV_DEVICE,
|
|
PROP_USB_DEVICE,
|
|
PROP_LAST
|
|
};
|
|
|
|
#define GET_PRIVATE(o) (lu_device_get_instance_private (o))
|
|
|
|
LuDeviceKind
|
|
lu_device_kind_from_string (const gchar *kind)
|
|
{
|
|
if (g_strcmp0 (kind, "runtime") == 0)
|
|
return LU_DEVICE_KIND_RUNTIME;
|
|
if (g_strcmp0 (kind, "bootloader-nordic") == 0)
|
|
return LU_DEVICE_KIND_BOOTLOADER_NORDIC;
|
|
if (g_strcmp0 (kind, "bootloader-texas") == 0)
|
|
return LU_DEVICE_KIND_BOOTLOADER_TEXAS;
|
|
if (g_strcmp0 (kind, "peripheral") == 0)
|
|
return LU_DEVICE_KIND_PERIPHERAL;
|
|
return LU_DEVICE_KIND_UNKNOWN;
|
|
}
|
|
|
|
const gchar *
|
|
lu_device_kind_to_string (LuDeviceKind kind)
|
|
{
|
|
if (kind == LU_DEVICE_KIND_RUNTIME)
|
|
return "runtime";
|
|
if (kind == LU_DEVICE_KIND_BOOTLOADER_NORDIC)
|
|
return "bootloader-nordic";
|
|
if (kind == LU_DEVICE_KIND_BOOTLOADER_TEXAS)
|
|
return "bootloader-texas";
|
|
if (kind == LU_DEVICE_KIND_PERIPHERAL)
|
|
return "peripheral";
|
|
return NULL;
|
|
}
|
|
|
|
static const gchar *
|
|
lu_hidpp_feature_to_string (guint feature)
|
|
{
|
|
if (feature == HIDPP_FEATURE_ROOT)
|
|
return "Root";
|
|
if (feature == HIDPP_FEATURE_I_FIRMWARE_INFO)
|
|
return "IFirmwareInfo";
|
|
if (feature == HIDPP_FEATURE_BATTERY_LEVEL_STATUS)
|
|
return "BatteryLevelStatus";
|
|
if (feature == HIDPP_FEATURE_DFU_CONTROL)
|
|
return "DfuControl";
|
|
if (feature == HIDPP_FEATURE_DFU_CONTROL_SIGNED)
|
|
return "DfuControlSigned";
|
|
if (feature == HIDPP_FEATURE_DFU)
|
|
return "Dfu";
|
|
return NULL;
|
|
}
|
|
|
|
LuDeviceHidppMsg *
|
|
lu_device_hidpp_new (void)
|
|
{
|
|
return g_new0 (LuDeviceHidppMsg, 1);
|
|
}
|
|
|
|
#define HIDPP_REPORT_NOTIFICATION 0x01
|
|
#define HIDPP_REPORT_02 0x02
|
|
#define HIDPP_REPORT_03 0x03
|
|
#define HIDPP_REPORT_04 0x04
|
|
#define HIDPP_REPORT_20 0x20
|
|
|
|
static gchar *
|
|
lu_device_flags_to_string (LuDeviceFlags flags)
|
|
{
|
|
GString *str = g_string_new (NULL);
|
|
if (flags & LU_DEVICE_FLAG_REQUIRES_SIGNED_FIRMWARE)
|
|
g_string_append (str, "signed-firmware,");
|
|
if (flags & LU_DEVICE_FLAG_CAN_FLASH)
|
|
g_string_append (str, "can-flash,");
|
|
if (flags & LU_DEVICE_FLAG_REQUIRES_RESET)
|
|
g_string_append (str, "requires-reset,");
|
|
if (flags & LU_DEVICE_FLAG_ACTIVE)
|
|
g_string_append (str, "active,");
|
|
if (flags & LU_DEVICE_FLAG_IS_OPEN)
|
|
g_string_append (str, "is-open,");
|
|
if (flags & LU_DEVICE_FLAG_REQUIRES_ATTACH)
|
|
g_string_append (str, "requires-attach,");
|
|
if (flags & LU_DEVICE_FLAG_REQUIRES_DETACH)
|
|
g_string_append (str, "requires-detach,");
|
|
if (flags & LU_DEVICE_FLAG_DETACH_WILL_REPLUG)
|
|
g_string_append (str, "detach-will-replug,");
|
|
if (str->len == 0) {
|
|
g_string_append (str, "none");
|
|
} else {
|
|
g_string_truncate (str, str->len - 1);
|
|
}
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
gchar *
|
|
lu_device_to_string (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
GString *str = g_string_new (NULL);
|
|
g_autofree gchar *flags_str = NULL;
|
|
|
|
g_string_append_printf (str, "type:\t\t\t%s\n", lu_device_kind_to_string (priv->kind));
|
|
flags_str = lu_device_flags_to_string (priv->flags);
|
|
g_string_append_printf (str, "flags:\t\t\t%s\n", flags_str);
|
|
g_string_append_printf (str, "hidpp-version:\t\t%.2f\n", priv->hidpp_version);
|
|
if (priv->hidpp_id != HIDPP_DEVICE_ID_UNSET)
|
|
g_string_append_printf (str, "hidpp-id:\t\t0x%02x\n", (guint) priv->hidpp_id);
|
|
if (priv->udev_device_fd > 0)
|
|
g_string_append_printf (str, "udev-device:\t\t%i\n", priv->udev_device_fd);
|
|
if (priv->usb_device != NULL)
|
|
g_string_append_printf (str, "usb-device:\t\t%p\n", priv->usb_device);
|
|
if (priv->platform_id != NULL)
|
|
g_string_append_printf (str, "platform-id:\t\t%s\n", priv->platform_id);
|
|
if (priv->vendor != NULL)
|
|
g_string_append_printf (str, "vendor:\t\t\t%s\n", priv->vendor);
|
|
if (priv->product != NULL)
|
|
g_string_append_printf (str, "product:\t\t%s\n", priv->product);
|
|
if (priv->version_bl != NULL)
|
|
g_string_append_printf (str, "version-bootloader:\t%s\n", priv->version_bl);
|
|
if (priv->version_fw != NULL)
|
|
g_string_append_printf (str, "version-firmware:\t%s\n", priv->version_fw);
|
|
if (priv->version_hw != NULL)
|
|
g_string_append_printf (str, "version-hardware:\t%s\n", priv->version_hw);
|
|
for (guint i = 0; i < priv->guids->len; i++) {
|
|
const gchar *guid = g_ptr_array_index (priv->guids, i);
|
|
g_string_append_printf (str, "guid:\t\t\t%s\n", guid);
|
|
}
|
|
if (priv->battery_level != 0)
|
|
g_string_append_printf (str, "battery-level:\t\t%u\n", priv->battery_level);
|
|
for (guint i = 0; i < priv->feature_index->len; i++) {
|
|
LuDeviceHidppMap *map = g_ptr_array_index (priv->feature_index, i);
|
|
g_string_append_printf (str, "feature%02x:\t\t%s [0x%04x]\n",
|
|
map->idx,
|
|
lu_hidpp_feature_to_string (map->feature),
|
|
map->feature);
|
|
}
|
|
|
|
/* fixme: superclass? */
|
|
if (LU_IS_DEVICE_BOOTLOADER (device)) {
|
|
g_string_append_printf (str, "flash-addr-high:\t0x%04x\n",
|
|
lu_device_bootloader_get_addr_hi (device));
|
|
g_string_append_printf (str, "flash-addr-low:\t0x%04x\n",
|
|
lu_device_bootloader_get_addr_lo (device));
|
|
g_string_append_printf (str, "flash-block-size:\t0x%04x\n",
|
|
lu_device_bootloader_get_blocksize (device));
|
|
}
|
|
return g_string_free (str, FALSE);
|
|
}
|
|
|
|
guint8
|
|
lu_device_hidpp_feature_get_idx (LuDevice *device, guint16 feature)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
for (guint i = 0; i < priv->feature_index->len; i++) {
|
|
LuDeviceHidppMap *map = g_ptr_array_index (priv->feature_index, i);
|
|
if (map->feature == feature)
|
|
return map->idx;
|
|
}
|
|
return 0x00;
|
|
}
|
|
|
|
static guint16
|
|
lu_device_hidpp_feature_find_by_idx (LuDevice *device, guint8 idx)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
for (guint i = 0; i < priv->feature_index->len; i++) {
|
|
LuDeviceHidppMap *map = g_ptr_array_index (priv->feature_index, i);
|
|
if (map->idx == idx)
|
|
return map->feature;
|
|
}
|
|
return 0x0000;
|
|
}
|
|
|
|
static gsize
|
|
lu_device_hidpp_msg_length (LuDeviceHidppMsg *msg)
|
|
{
|
|
if (msg->report_id == HIDPP_REPORT_ID_SHORT)
|
|
return 0x07;
|
|
if (msg->report_id == HIDPP_REPORT_ID_LONG)
|
|
return 0x14;
|
|
if (msg->report_id == HIDPP_REPORT_NOTIFICATION)
|
|
return 0x08;
|
|
if (msg->report_id == HIDPP_REPORT_02)
|
|
return 0x08;
|
|
if (msg->report_id == HIDPP_REPORT_03)
|
|
return 0x05;
|
|
if (msg->report_id == HIDPP_REPORT_04)
|
|
return 0x02;
|
|
if (msg->report_id == HIDPP_REPORT_20)
|
|
return 0x0f;
|
|
g_warning ("report 0x%02x unknown length", msg->report_id);
|
|
return 0x08;
|
|
}
|
|
|
|
static void
|
|
lu_device_hidpp_dump (LuDevice *device, const gchar *title, const guint8 *data, gsize len)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autofree gchar *title_prefixed = NULL;
|
|
if (priv->usb_device != NULL)
|
|
title_prefixed = g_strdup_printf ("[USB] %s", title);
|
|
else if (priv->udev_device != NULL)
|
|
title_prefixed = g_strdup_printf ("[HID] %s", title);
|
|
else
|
|
title_prefixed = g_strdup_printf ("[EMU] %s", title);
|
|
lu_dump_raw (title_prefixed, data, len);
|
|
}
|
|
|
|
gboolean
|
|
lu_device_hidpp_send (LuDevice *device,
|
|
LuDeviceHidppMsg *msg,
|
|
guint timeout,
|
|
GError **error)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
gsize len = lu_device_hidpp_msg_length (msg);
|
|
|
|
/* only for HID++2.0 */
|
|
if (lu_device_get_hidpp_version (device) >= 2.f)
|
|
msg->function_id |= FU_DEVICE_UNIFYING_SW_ID;
|
|
|
|
lu_device_hidpp_dump (device, "host->device", (guint8 *) msg, len);
|
|
|
|
/* USB */
|
|
if (priv->usb_device != NULL) {
|
|
gsize actual_length = 0;
|
|
if (!g_usb_device_control_transfer (priv->usb_device,
|
|
G_USB_DEVICE_DIRECTION_HOST_TO_DEVICE,
|
|
G_USB_DEVICE_REQUEST_TYPE_CLASS,
|
|
G_USB_DEVICE_RECIPIENT_INTERFACE,
|
|
LU_REQUEST_SET_REPORT,
|
|
0x0210, 0x0002,
|
|
(guint8 *) msg, len,
|
|
&actual_length,
|
|
timeout,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error (error, "failed to send data: ");
|
|
return FALSE;
|
|
}
|
|
if (actual_length != len) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to send data: "
|
|
"wrote %" G_GSIZE_FORMAT " of %" G_GSIZE_FORMAT,
|
|
actual_length, len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* HID */
|
|
} else if (priv->udev_device != NULL) {
|
|
if (!lu_nonblock_write (priv->udev_device_fd,
|
|
(guint8 *) msg, len, error)) {
|
|
g_prefix_error (error, "failed to send: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
lu_device_hidpp_receive (LuDevice *device,
|
|
LuDeviceHidppMsg *msg,
|
|
guint timeout,
|
|
GError **error)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
gsize read_size = 0;
|
|
|
|
/* USB */
|
|
if (priv->usb_device != NULL) {
|
|
if (!g_usb_device_interrupt_transfer (priv->usb_device,
|
|
LU_DEVICE_EP3,
|
|
(guint8 *) msg,
|
|
sizeof(LuDeviceHidppMsg),
|
|
&read_size,
|
|
timeout,
|
|
NULL,
|
|
error)) {
|
|
g_prefix_error (error, "failed to get data: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* HID */
|
|
} else if (priv->udev_device != NULL) {
|
|
if (!lu_nonblock_read (priv->udev_device_fd,
|
|
(guint8 *) msg,
|
|
sizeof(LuDeviceHidppMsg),
|
|
&read_size,
|
|
timeout,
|
|
error)) {
|
|
g_prefix_error (error, "failed to receive: ");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* check long enough, but allow returning oversize packets */
|
|
lu_device_hidpp_dump (device, "device->host", (guint8 *) msg, read_size);
|
|
if (read_size < lu_device_hidpp_msg_length (msg)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"message length too small, "
|
|
"got %" G_GSIZE_FORMAT " expected %" G_GSIZE_FORMAT,
|
|
read_size, lu_device_hidpp_msg_length (msg));
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
lu_device_hidpp_msg_is_hidpp_10 (LuDeviceHidppMsg *msg)
|
|
{
|
|
/* filter HID++1.0 messages */
|
|
if (msg->sub_id == 0x40 ||
|
|
msg->sub_id == 0x41 ||
|
|
msg->sub_id == 0x49 ||
|
|
msg->sub_id == 0x4b ||
|
|
msg->sub_id == 0x8f) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
lu_device_hidpp_msg_check_swid (LuDeviceHidppMsg *msg, LuDeviceHidppMsg *msg_tmp)
|
|
{
|
|
if ((msg_tmp->function_id & 0x0f) != FU_DEVICE_UNIFYING_SW_ID)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
lu_device_hidpp_msg_is_error (LuDevice *device,
|
|
LuDeviceHidppMsg *msg,
|
|
LuDeviceHidppMsg *msg_tmp,
|
|
GError **error)
|
|
{
|
|
/* wrong device ID reported */
|
|
if (msg->device_id != HIDPP_DEVICE_ID_UNSET &&
|
|
msg->device_id != msg_tmp->device_id) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"invalid device_id response");
|
|
return FALSE;
|
|
}
|
|
|
|
/* kernel not doing it's thing */
|
|
if (msg_tmp->device_id == HIDPP_DEVICE_ID_UNSET) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"device ID not set");
|
|
return FALSE;
|
|
}
|
|
|
|
/* HID++ 1.0 error */
|
|
if (msg_tmp->sub_id == HIDPP_SUBID_ERROR_MSG) {
|
|
const gchar *tmp;
|
|
guint16 feature;
|
|
switch (msg_tmp->data[1]) {
|
|
case HIDPP_ERR_INVALID_SUBID:
|
|
feature = lu_device_hidpp_feature_find_by_idx (device, msg_tmp->sub_id);
|
|
tmp = lu_hidpp_feature_to_string (feature);
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"invalid SubID %s [0x%02x] or command",
|
|
tmp, msg->sub_id);
|
|
break;
|
|
case HIDPP_ERR_INVALID_ADDRESS:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid address");
|
|
break;
|
|
case HIDPP_ERR_INVALID_VALUE:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"invalid value");
|
|
break;
|
|
case HIDPP_ERR_CONNECT_FAIL:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"connection request failed");
|
|
break;
|
|
case HIDPP_ERR_TOO_MANY_DEVICES:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NO_SPACE,
|
|
"too many devices connected");
|
|
break;
|
|
case HIDPP_ERR_ALREADY_EXISTS:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_EXISTS,
|
|
"already exists");
|
|
break;
|
|
case HIDPP_ERR_BUSY:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_BUSY,
|
|
"busy");
|
|
break;
|
|
case HIDPP_ERR_UNKNOWN_DEVICE:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"unknown device");
|
|
break;
|
|
case HIDPP_ERR_RESOURCE_ERROR:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_HOST_UNREACHABLE,
|
|
"resource error");
|
|
break;
|
|
case HIDPP_ERR_REQUEST_UNAVAILABLE:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_EXISTS,
|
|
"request not valid in current context");
|
|
break;
|
|
case HIDPP_ERR_INVALID_PARAM_VALUE:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"request parameter has unsupported value");
|
|
break;
|
|
case HIDPP_ERR_WRONG_PIN_CODE:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_CONNECTION_REFUSED,
|
|
"the pin code was wrong");
|
|
break;
|
|
default:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"generic failure");
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* HID++ 2.0 error */
|
|
if (msg_tmp->sub_id == HIDPP_SUBID_ERROR_MSG_20) {
|
|
|
|
switch (msg_tmp->data[1]) {
|
|
case HIDPP_ERROR_CODE_INVALID_ARGUMENT:
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"Invalid argument 0x%02x",
|
|
msg_tmp->data[2]);
|
|
break;
|
|
case HIDPP_ERROR_CODE_OUT_OF_RANGE:
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"out of range");
|
|
break;
|
|
case HIDPP_ERROR_CODE_HW_ERROR:
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_BROKEN_PIPE,
|
|
"hardware error");
|
|
break;
|
|
case HIDPP_ERROR_CODE_INVALID_FEATURE_INDEX:
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"invalid feature index");
|
|
break;
|
|
case HIDPP_ERROR_CODE_INVALID_FUNCTION_ID:
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
"invalid function ID");
|
|
break;
|
|
case HIDPP_ERROR_CODE_BUSY:
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_BUSY,
|
|
"busy");
|
|
break;
|
|
case HIDPP_ERROR_CODE_UNSUPPORTED:
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"unsupported");
|
|
break;
|
|
default:
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"generic failure");
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* check the response was valid */
|
|
if (0&&msg->report_id != msg_tmp->report_id) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"invalid report_id response");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
lu_device_hidpp_msg_copy (LuDeviceHidppMsg *msg, LuDeviceHidppMsg *msg_tmp)
|
|
{
|
|
memset (msg->data, 0x00, sizeof(msg->data));
|
|
msg->device_id = msg_tmp->device_id;
|
|
msg->sub_id = msg_tmp->sub_id;
|
|
msg->function_id = msg_tmp->function_id;
|
|
memcpy (msg->data, msg_tmp->data, sizeof(msg->data));
|
|
}
|
|
|
|
gboolean
|
|
lu_device_hidpp_transfer (LuDevice *device, LuDeviceHidppMsg *msg, GError **error)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
guint timeout = LU_DEVICE_TIMEOUT_MS;
|
|
g_autoptr(LuDeviceHidppMsg) msg_tmp = lu_device_hidpp_new ();
|
|
|
|
/* increase timeout for some operations */
|
|
if (msg->flags & LU_DEVICE_HIDPP_MSG_FLAG_LONGER_TIMEOUT)
|
|
timeout *= 10;
|
|
|
|
/* send request */
|
|
if (!lu_device_hidpp_send (device, msg, timeout, error))
|
|
return FALSE;
|
|
|
|
/* keep trying to receive until we get a valid reply */
|
|
while (1) {
|
|
if (!lu_device_hidpp_receive (device, msg_tmp, timeout, error))
|
|
return FALSE;
|
|
|
|
/* to ensure compatibility when an HID++ 2.0 device is
|
|
* connected to an HID++ 1.0 receiver, any feature index
|
|
* corresponding to an HID++ 1.0 sub-identifier which could be
|
|
* sent by the receiver, must be assigned to a dummy feature */
|
|
if (lu_device_get_hidpp_version (device) >= 2.f &&
|
|
lu_device_hidpp_msg_is_hidpp_10 (msg_tmp)) {
|
|
g_debug ("ignoring HID++1.0 reply");
|
|
continue;
|
|
}
|
|
|
|
/* is error */
|
|
if (!lu_device_hidpp_msg_is_error (device, msg, msg_tmp, error))
|
|
return FALSE;
|
|
|
|
/* not us */
|
|
if (lu_device_get_hidpp_version (device) >= 2.f &&
|
|
(msg->flags & LU_DEVICE_HIDPP_MSG_FLAG_IGNORE_SWID) == 0) {
|
|
if (!lu_device_hidpp_msg_check_swid (msg, msg_tmp)) {
|
|
g_debug ("ignoring reply with SwId 0x%02i, expected 0x%02i",
|
|
msg_tmp->function_id & 0x0f,
|
|
FU_DEVICE_UNIFYING_SW_ID);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (msg_tmp->report_id == 0x10 || msg_tmp->report_id == 0x11)
|
|
break;
|
|
g_debug ("ignoring message with report 0x%02x", msg_tmp->report_id);
|
|
};
|
|
|
|
/* if the HID++ ID is unset, grab it from the reply */
|
|
if (priv->hidpp_id == HIDPP_DEVICE_ID_UNSET) {
|
|
priv->hidpp_id = msg_tmp->device_id;
|
|
g_debug ("HID++ ID now %02x", priv->hidpp_id);
|
|
}
|
|
|
|
/* copy over data */
|
|
lu_device_hidpp_msg_copy (msg, msg_tmp);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
lu_device_hidpp_feature_search (LuDevice *device, guint16 feature, GError **error)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
LuDeviceHidppMap *map;
|
|
g_autoptr(LuDeviceHidppMsg) msg = lu_device_hidpp_new ();
|
|
|
|
/* find the idx for the feature */
|
|
msg->report_id = HIDPP_REPORT_ID_SHORT;
|
|
msg->device_id = priv->hidpp_id;
|
|
msg->sub_id = 0x00; /* rootIndex */
|
|
msg->function_id = 0x00 << 4; /* getFeature */
|
|
msg->data[0] = feature >> 8;
|
|
msg->data[1] = feature;
|
|
msg->data[2] = 0x00;
|
|
if (!lu_device_hidpp_transfer (device, msg, error)) {
|
|
g_prefix_error (error,
|
|
"failed to get idx for feature %s [0x%04x]: ",
|
|
lu_hidpp_feature_to_string (feature), feature);
|
|
return FALSE;
|
|
}
|
|
|
|
/* zero index */
|
|
if (msg->data[0] == 0x00) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"feature %s [0x%04x] not found",
|
|
lu_hidpp_feature_to_string (feature), feature);
|
|
return FALSE;
|
|
}
|
|
|
|
/* add to map */
|
|
map = g_new0 (LuDeviceHidppMap, 1);
|
|
map->idx = msg->data[0];
|
|
map->feature = feature;
|
|
g_ptr_array_add (priv->feature_index, map);
|
|
g_debug ("added feature %s [0x%04x] as idx %02x",
|
|
lu_hidpp_feature_to_string (feature), feature, map->idx);
|
|
return TRUE;
|
|
}
|
|
|
|
LuDeviceKind
|
|
lu_device_get_kind (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->kind;
|
|
}
|
|
|
|
guint8
|
|
lu_device_get_hidpp_id (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->hidpp_id;
|
|
}
|
|
|
|
void
|
|
lu_device_set_hidpp_id (LuDevice *device, guint8 hidpp_id)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
priv->hidpp_id = hidpp_id;
|
|
}
|
|
|
|
guint8
|
|
lu_device_get_battery_level (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->battery_level;
|
|
}
|
|
|
|
void
|
|
lu_device_set_battery_level (LuDevice *device, guint8 percentage)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
priv->battery_level = percentage;
|
|
}
|
|
|
|
gdouble
|
|
lu_device_get_hidpp_version (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->hidpp_version;
|
|
}
|
|
|
|
void
|
|
lu_device_set_hidpp_version (LuDevice *device, gdouble hidpp_version)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
priv->hidpp_version = hidpp_version;
|
|
}
|
|
|
|
const gchar *
|
|
lu_device_get_platform_id (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->platform_id;
|
|
}
|
|
|
|
void
|
|
lu_device_set_platform_id (LuDevice *device, const gchar *platform_id)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_free (priv->platform_id);
|
|
priv->platform_id = g_strdup (platform_id);
|
|
}
|
|
|
|
const gchar *
|
|
lu_device_get_product (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->product;
|
|
}
|
|
|
|
void
|
|
lu_device_set_product (LuDevice *device, const gchar *product)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_free (priv->product);
|
|
priv->product = g_strdup (product);
|
|
}
|
|
|
|
const gchar *
|
|
lu_device_get_vendor (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->vendor;
|
|
}
|
|
|
|
void
|
|
lu_device_set_vendor (LuDevice *device, const gchar *vendor)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_free (priv->vendor);
|
|
priv->vendor = g_strdup (vendor);
|
|
}
|
|
|
|
const gchar *
|
|
lu_device_get_version_bl (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->version_bl;
|
|
}
|
|
|
|
void
|
|
lu_device_set_version_bl (LuDevice *device, const gchar *version_bl)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_free (priv->version_bl);
|
|
priv->version_bl = g_strdup (version_bl);
|
|
}
|
|
|
|
const gchar *
|
|
lu_device_get_version_fw (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->version_fw;
|
|
}
|
|
|
|
void
|
|
lu_device_set_version_fw (LuDevice *device, const gchar *version_fw)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_free (priv->version_fw);
|
|
priv->version_fw = g_strdup (version_fw);
|
|
}
|
|
|
|
const gchar *
|
|
lu_device_get_version_hw (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->version_hw;
|
|
}
|
|
|
|
void
|
|
lu_device_set_version_hw (LuDevice *device, const gchar *version_hw)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_free (priv->version_hw);
|
|
priv->version_hw = g_strdup (version_hw);
|
|
}
|
|
|
|
GPtrArray *
|
|
lu_device_get_guids (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->guids;
|
|
}
|
|
|
|
void
|
|
lu_device_add_guid (LuDevice *device, const gchar *guid)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_ptr_array_add (priv->guids, g_strdup (guid));
|
|
}
|
|
|
|
gboolean
|
|
lu_device_has_flag (LuDevice *device, LuDeviceFlags flag)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return (priv->flags & flag) > 0;
|
|
}
|
|
|
|
void
|
|
lu_device_add_flag (LuDevice *device, LuDeviceFlags flag)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
priv->flags |= flag;
|
|
g_object_notify (G_OBJECT (device), "flags");
|
|
}
|
|
|
|
void
|
|
lu_device_remove_flag (LuDevice *device, LuDeviceFlags flag)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
priv->flags &= ~flag;
|
|
g_object_notify (G_OBJECT (device), "flags");
|
|
}
|
|
|
|
LuDeviceFlags
|
|
lu_device_get_flags (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->flags;
|
|
}
|
|
|
|
GUdevDevice *
|
|
lu_device_get_udev_device (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->udev_device;
|
|
}
|
|
|
|
GUsbDevice *
|
|
lu_device_get_usb_device (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->usb_device;
|
|
}
|
|
|
|
gboolean
|
|
lu_device_probe (LuDevice *device, GError **error)
|
|
{
|
|
LuDeviceClass *klass = LU_DEVICE_GET_CLASS (device);
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
|
|
/* clear the feature map (leaving only the root) */
|
|
g_ptr_array_set_size (priv->feature_index, 0);
|
|
|
|
/* probe the hardware */
|
|
if (klass->probe != NULL)
|
|
return klass->probe (device, error);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
lu_device_open (LuDevice *device, GError **error)
|
|
{
|
|
LuDeviceClass *klass = LU_DEVICE_GET_CLASS (device);
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autofree gchar *device_str = NULL;
|
|
|
|
g_return_val_if_fail (LU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* already done */
|
|
if (lu_device_has_flag (device, LU_DEVICE_FLAG_IS_OPEN))
|
|
return TRUE;
|
|
|
|
/* set default vendor */
|
|
lu_device_set_vendor (device, "Logitech");
|
|
|
|
/* open device */
|
|
if (priv->usb_device != NULL) {
|
|
g_debug ("opening unifying device using USB");
|
|
if (!g_usb_device_open (priv->usb_device, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* USB */
|
|
if (priv->usb_device != NULL) {
|
|
g_autofree gchar *devid = NULL;
|
|
guint8 num_interfaces = 0x01;
|
|
if (priv->kind == LU_DEVICE_KIND_RUNTIME)
|
|
num_interfaces = 0x03;
|
|
for (guint i = 0; i < num_interfaces; i++) {
|
|
g_debug ("claiming interface 0x%02x", i);
|
|
if (!g_usb_device_claim_interface (priv->usb_device, i,
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
error)) {
|
|
g_prefix_error (error, "Failed to claim 0x%02x: ", i);
|
|
g_usb_device_close (priv->usb_device, NULL);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* generate GUID */
|
|
devid = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
g_usb_device_get_vid (priv->usb_device),
|
|
g_usb_device_get_pid (priv->usb_device));
|
|
lu_device_add_guid (device, devid);
|
|
|
|
/* HID */
|
|
} else if (priv->udev_device != NULL) {
|
|
const gchar *devpath = g_udev_device_get_device_file (priv->udev_device);
|
|
g_debug ("opening unifying device using %s", devpath);
|
|
priv->udev_device_fd = lu_nonblock_open (devpath, error);
|
|
if (priv->udev_device_fd < 0)
|
|
return FALSE;
|
|
}
|
|
|
|
/* subclassed */
|
|
if (klass->open != NULL) {
|
|
if (!klass->open (device, error)) {
|
|
lu_device_close (device, NULL);
|
|
return FALSE;
|
|
}
|
|
}
|
|
lu_device_add_flag (device, LU_DEVICE_FLAG_IS_OPEN);
|
|
|
|
/* subclassed */
|
|
if (!lu_device_probe (device, error)) {
|
|
lu_device_close (device, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
/* add known root for HID++2.0 */
|
|
if (lu_device_get_hidpp_version (device) >= 2.f) {
|
|
LuDeviceHidppMap *map = g_new0 (LuDeviceHidppMap, 1);
|
|
map->idx = 0x00;
|
|
map->feature = HIDPP_FEATURE_ROOT;
|
|
g_ptr_array_add (priv->feature_index, map);
|
|
}
|
|
|
|
/* show the device */
|
|
device_str = lu_device_to_string (device);
|
|
g_debug ("%s", device_str);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
lu_device_poll (LuDevice *device, GError **error)
|
|
{
|
|
LuDeviceClass *klass = LU_DEVICE_GET_CLASS (device);
|
|
const guint timeout = 1; /* ms */
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_autoptr(LuDeviceHidppMsg) msg = lu_device_hidpp_new ();
|
|
|
|
/* is there any pending data to read */
|
|
if (!lu_device_hidpp_receive (device, msg, timeout, &error_local)) {
|
|
if (g_error_matches (error_local,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_TIMED_OUT)) {
|
|
return TRUE;
|
|
}
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"failed to get pending read: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* unifying receiver notification */
|
|
if (msg->report_id == HIDPP_REPORT_ID_SHORT) {
|
|
switch (msg->sub_id) {
|
|
case HIDPP_SUBID_DEVICE_CONNECTION:
|
|
case HIDPP_SUBID_DEVICE_DISCONNECTION:
|
|
case HIDPP_SUBID_DEVICE_LOCKING_CHANGED:
|
|
g_debug ("device changed");
|
|
if (klass->poll != NULL)
|
|
return klass->poll (device, error);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* lu_device_close:
|
|
* @device: A #LuDevice
|
|
* @error: A #GError, or %NULL
|
|
*
|
|
* Closes the device. If at all unsure about closing a device, don't. The device
|
|
* will be automatically closed when the last reference to it is dropped.
|
|
*
|
|
* Returns: %TRUE for success
|
|
**/
|
|
gboolean
|
|
lu_device_close (LuDevice *device, GError **error)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
LuDeviceClass *klass = LU_DEVICE_GET_CLASS (device);
|
|
|
|
g_return_val_if_fail (LU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* not open */
|
|
if (!lu_device_has_flag (device, LU_DEVICE_FLAG_IS_OPEN))
|
|
return TRUE;
|
|
|
|
/* subclassed */
|
|
g_debug ("closing device");
|
|
if (klass->close != NULL) {
|
|
if (!klass->close (device, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* USB */
|
|
if (priv->usb_device != NULL) {
|
|
guint8 num_interfaces = 0x01;
|
|
if (priv->kind == LU_DEVICE_KIND_RUNTIME)
|
|
num_interfaces = 0x03;
|
|
for (guint i = 0; i < num_interfaces; i++) {
|
|
g_autoptr(GError) error_local = NULL;
|
|
g_debug ("releasing interface 0x%02x", i);
|
|
if (!g_usb_device_release_interface (priv->usb_device, i,
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
&error_local)) {
|
|
if (!g_error_matches (error_local,
|
|
G_USB_DEVICE_ERROR,
|
|
G_USB_DEVICE_ERROR_INTERNAL)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"Failed to release 0x%02x: %s",
|
|
i, error_local->message);
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
if (!g_usb_device_close (priv->usb_device, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* HID */
|
|
if (priv->udev_device != NULL && priv->udev_device_fd > 0) {
|
|
if (!g_close (priv->udev_device_fd, error))
|
|
return FALSE;
|
|
priv->udev_device_fd = 0;
|
|
}
|
|
|
|
/* success */
|
|
lu_device_remove_flag (device, LU_DEVICE_FLAG_IS_OPEN);
|
|
return TRUE;
|
|
}
|
|
gboolean
|
|
lu_device_detach (LuDevice *device, GError **error)
|
|
{
|
|
LuDeviceClass *klass = LU_DEVICE_GET_CLASS (device);
|
|
|
|
g_return_val_if_fail (LU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* subclassed */
|
|
g_debug ("detaching device");
|
|
if (klass->detach != NULL)
|
|
return klass->detach (device, error);
|
|
|
|
/* nothing to do */
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"device detach is not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
lu_device_attach (LuDevice *device, GError **error)
|
|
{
|
|
LuDeviceClass *klass = LU_DEVICE_GET_CLASS (device);
|
|
|
|
g_return_val_if_fail (LU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* check kind */
|
|
if (lu_device_get_kind (device) == LU_DEVICE_KIND_RUNTIME) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"device is not in bootloader state");
|
|
return FALSE;
|
|
}
|
|
|
|
/* subclassed */
|
|
if (klass->attach != NULL)
|
|
return klass->attach (device, error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
lu_device_write_firmware (LuDevice *device,
|
|
GBytes *fw,
|
|
GFileProgressCallback progress_cb,
|
|
gpointer progress_data,
|
|
GError **error)
|
|
{
|
|
LuDeviceClass *klass = LU_DEVICE_GET_CLASS (device);
|
|
|
|
g_return_val_if_fail (LU_IS_DEVICE (device), FALSE);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
|
|
|
/* corrupt */
|
|
if (g_bytes_get_size (fw) < 0x4000) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"firmware is too small");
|
|
return FALSE;
|
|
}
|
|
|
|
/* call device-specific method */
|
|
if (klass->write_firmware == NULL) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
"not supported in %s",
|
|
lu_device_kind_to_string (lu_device_get_kind (device)));
|
|
return FALSE;
|
|
}
|
|
|
|
/* call either nordic or texas vfunc */
|
|
return klass->write_firmware (device, fw, progress_cb, progress_data, error);
|
|
}
|
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevDevice, g_object_unref)
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevClient, g_object_unref)
|
|
|
|
static GUdevDevice *
|
|
lu_device_find_udev_device (GUsbDevice *usb_device)
|
|
{
|
|
g_autoptr(GUdevClient) gudev_client = g_udev_client_new (NULL);
|
|
g_autoptr(GList) devices = NULL;
|
|
|
|
devices = g_udev_client_query_by_subsystem (gudev_client, "usb");
|
|
for (GList *l = devices; l != NULL; l = l->next) {
|
|
guint busnum;
|
|
guint devnum;
|
|
g_autoptr(GUdevDevice) udev_device = G_UDEV_DEVICE (l->data);
|
|
g_autoptr(GUdevDevice) udev_parent = g_udev_device_get_parent (udev_device);
|
|
|
|
busnum = g_udev_device_get_sysfs_attr_as_int (udev_parent, "busnum");
|
|
if (busnum != g_usb_device_get_bus (usb_device))
|
|
continue;
|
|
devnum = g_udev_device_get_sysfs_attr_as_int (udev_parent, "devnum");
|
|
if (devnum != g_usb_device_get_address (usb_device))
|
|
continue;
|
|
|
|
return g_object_ref (udev_parent);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
lu_device_update_platform_id (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
if (priv->usb_device != NULL && priv->udev_device == NULL) {
|
|
g_autoptr(GUdevDevice) udev_device = NULL;
|
|
udev_device = lu_device_find_udev_device (priv->usb_device);
|
|
if (udev_device != NULL) {
|
|
const gchar *tmp = g_udev_device_get_sysfs_path (udev_device);
|
|
lu_device_set_platform_id (device, tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
lu_device_get_property (GObject *object, guint prop_id,
|
|
GValue *value, GParamSpec *pspec)
|
|
{
|
|
LuDevice *device = LU_DEVICE (object);
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
switch (prop_id) {
|
|
case PROP_KIND:
|
|
g_value_set_uint (value, priv->kind);
|
|
break;
|
|
case PROP_HIDPP_ID:
|
|
g_value_set_uint (value, priv->hidpp_id);
|
|
break;
|
|
case PROP_FLAGS:
|
|
g_value_set_uint64 (value, priv->flags);
|
|
break;
|
|
case PROP_PLATFORM_ID:
|
|
g_value_set_string (value, priv->platform_id);
|
|
break;
|
|
case PROP_UDEV_DEVICE:
|
|
g_value_set_object (value, priv->udev_device);
|
|
break;
|
|
case PROP_USB_DEVICE:
|
|
g_value_set_object (value, priv->usb_device);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
lu_device_set_property (GObject *object, guint prop_id,
|
|
const GValue *value, GParamSpec *pspec)
|
|
{
|
|
LuDevice *device = LU_DEVICE (object);
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
switch (prop_id) {
|
|
case PROP_KIND:
|
|
priv->kind = g_value_get_uint (value);
|
|
break;
|
|
case PROP_HIDPP_ID:
|
|
priv->hidpp_id = g_value_get_uint (value);
|
|
break;
|
|
case PROP_FLAGS:
|
|
priv->flags = g_value_get_uint64 (value);
|
|
break;
|
|
case PROP_PLATFORM_ID:
|
|
g_free (priv->platform_id);
|
|
priv->platform_id = g_value_dup_string (value);
|
|
break;
|
|
case PROP_UDEV_DEVICE:
|
|
priv->udev_device = g_value_dup_object (value);
|
|
break;
|
|
case PROP_USB_DEVICE:
|
|
priv->usb_device = g_value_dup_object (value);
|
|
lu_device_update_platform_id (device);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
lu_device_finalize (GObject *object)
|
|
{
|
|
LuDevice *device = LU_DEVICE (object);
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GError) error = NULL;
|
|
|
|
/* autoclose */
|
|
if (!lu_device_close (device, &error))
|
|
g_debug ("failed to close: %s", error->message);
|
|
|
|
if (priv->usb_device != NULL)
|
|
g_object_unref (priv->usb_device);
|
|
if (priv->udev_device != NULL)
|
|
g_object_unref (priv->udev_device);
|
|
g_ptr_array_unref (priv->guids);
|
|
g_ptr_array_unref (priv->feature_index);
|
|
g_free (priv->platform_id);
|
|
g_free (priv->product);
|
|
g_free (priv->vendor);
|
|
g_free (priv->version_fw);
|
|
g_free (priv->version_hw);
|
|
g_free (priv->version_bl);
|
|
|
|
G_OBJECT_CLASS (lu_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
lu_device_init (LuDevice *device)
|
|
{
|
|
LuDevicePrivate *priv = GET_PRIVATE (device);
|
|
priv->hidpp_id = HIDPP_DEVICE_ID_UNSET;
|
|
priv->guids = g_ptr_array_new_with_free_func (g_free);
|
|
priv->feature_index = g_ptr_array_new_with_free_func (g_free);
|
|
}
|
|
|
|
static void
|
|
lu_device_class_init (LuDeviceClass *klass)
|
|
{
|
|
GParamSpec *pspec;
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = lu_device_finalize;
|
|
object_class->get_property = lu_device_get_property;
|
|
object_class->set_property = lu_device_set_property;
|
|
|
|
pspec = g_param_spec_uint ("kind", NULL, NULL,
|
|
LU_DEVICE_KIND_UNKNOWN,
|
|
LU_DEVICE_KIND_LAST,
|
|
LU_DEVICE_KIND_UNKNOWN,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
|
g_object_class_install_property (object_class, PROP_KIND, pspec);
|
|
|
|
pspec = g_param_spec_uint ("hidpp-id", NULL, NULL,
|
|
HIDPP_DEVICE_ID_WIRED,
|
|
HIDPP_DEVICE_ID_RECEIVER,
|
|
HIDPP_DEVICE_ID_UNSET,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
|
g_object_class_install_property (object_class, PROP_HIDPP_ID, pspec);
|
|
|
|
pspec = g_param_spec_uint64 ("flags", NULL, NULL,
|
|
LU_DEVICE_FLAG_NONE,
|
|
0xffff,
|
|
LU_DEVICE_FLAG_NONE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
|
g_object_class_install_property (object_class, PROP_FLAGS, pspec);
|
|
|
|
pspec = g_param_spec_string ("platform-id", NULL, NULL, NULL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
|
g_object_class_install_property (object_class, PROP_PLATFORM_ID, pspec);
|
|
|
|
pspec = g_param_spec_object ("udev-device", NULL, NULL,
|
|
G_UDEV_TYPE_DEVICE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
|
g_object_class_install_property (object_class, PROP_UDEV_DEVICE, pspec);
|
|
|
|
pspec = g_param_spec_object ("usb-device", NULL, NULL,
|
|
G_USB_TYPE_DEVICE,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
|
g_object_class_install_property (object_class, PROP_USB_DEVICE, pspec);
|
|
}
|
|
|
|
LuDevice *
|
|
lu_device_fake_new (LuDeviceKind kind)
|
|
{
|
|
LuDevice *device = NULL;
|
|
switch (kind) {
|
|
case LU_DEVICE_KIND_BOOTLOADER_NORDIC:
|
|
device = g_object_new (LU_TYPE_DEVICE_BOOTLOADER_NORDIC,
|
|
"kind", kind,
|
|
NULL);
|
|
break;
|
|
case LU_DEVICE_KIND_BOOTLOADER_TEXAS:
|
|
device = g_object_new (LU_TYPE_DEVICE_BOOTLOADER_TEXAS,
|
|
"kind", kind,
|
|
NULL);
|
|
break;
|
|
case LU_DEVICE_KIND_RUNTIME:
|
|
device = g_object_new (LU_TYPE_DEVICE_RUNTIME,
|
|
"kind", kind,
|
|
NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return device;
|
|
}
|