fwupd/plugins/unifying/lu-device.c
2017-07-03 20:17:50 +01:00

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