fwupd/plugins/system76-launch/fu-system76-launch-device.c
Jeremy Soller fb3b349f16 system76-launch: Add a plugin for the System76 Launch Configurable Keyboard
This plugin supports reading the keyboard firmware version at runtime and
rebooting the keyboard to USB DFU mode. Tested with firmware uploaded to
embargo-system76.
2021-01-23 20:28:16 +00:00

164 lines
4.5 KiB
C

/*
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
* Copyright (C) 2021 Jeremy Soller <jeremy@system76.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-system76-launch-device.h"
#define SYSTEM76_LAUNCH_CMD_VERSION 3
#define SYSTEM76_LAUNCH_CMD_RESET 6
#define SYSTEM76_LAUNCH_TIMEOUT 1000
struct _FuSystem76LaunchDevice {
FuUsbDevice parent_instance;
};
G_DEFINE_TYPE (FuSystem76LaunchDevice, fu_system76_launch_device, FU_TYPE_USB_DEVICE)
static gboolean
fu_system76_launch_device_setup (FuDevice *device, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
const guint8 ep_in = 0x82;
const guint8 ep_out = 0x03;
guint8 data[32] = { 0 };
gsize actual_len = 0;
g_autofree gchar *version = NULL;
/* send version command */
data[0] = SYSTEM76_LAUNCH_CMD_VERSION;
if (!g_usb_device_interrupt_transfer (usb_device,
ep_out,
data,
sizeof(data),
&actual_len,
SYSTEM76_LAUNCH_TIMEOUT,
NULL,
error)) {
g_prefix_error (error, "failed to send version command: ");
return FALSE;
}
if (actual_len < sizeof(data)) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"version command truncated: sent %" G_GSIZE_FORMAT " bytes",
actual_len);
return FALSE;
}
/* receive version response */
if (!g_usb_device_interrupt_transfer (usb_device,
ep_in,
data,
sizeof(data),
&actual_len,
SYSTEM76_LAUNCH_TIMEOUT,
NULL,
error)) {
g_prefix_error (error, "failed to read version response: ");
return FALSE;
}
if (actual_len < sizeof(data)) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"version response truncated: received %" G_GSIZE_FORMAT " bytes",
actual_len);
return FALSE;
}
version = g_strdup_printf ("%s", &data[2]);
fu_device_set_version (device, version);
return TRUE;
}
static gboolean
fu_system76_launch_device_detach (FuDevice *device, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
const guint8 ep_out = 0x03;
guint8 data[32] = { 0 };
gsize actual_len = 0;
g_autofree gchar *version = NULL;
fu_device_set_status (device, FWUPD_STATUS_DEVICE_RESTART);
/* send reset command, should result in bootloader device appearing */
data[0] = SYSTEM76_LAUNCH_CMD_RESET;
if (!g_usb_device_interrupt_transfer (usb_device,
ep_out,
data,
sizeof(data),
&actual_len,
SYSTEM76_LAUNCH_TIMEOUT,
NULL,
error)) {
g_prefix_error (error, "failed to send reset command: ");
return FALSE;
}
fu_device_add_flag (device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG);
return TRUE;
}
static gboolean
fu_system76_launch_device_open (FuUsbDevice *device, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
const guint8 iface_idx = 0x01;
if (!g_usb_device_claim_interface (usb_device, iface_idx,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error (error, "failed to claim interface: ");
return FALSE;
}
return TRUE;
}
static gboolean
fu_system76_launch_device_close (FuUsbDevice *device, GError **error)
{
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
const guint8 iface_idx = 0x01;
if (!g_usb_device_release_interface (usb_device, iface_idx,
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
error)) {
g_prefix_error (error, "failed to release interface: ");
return FALSE;
}
return TRUE;
}
static void
fu_system76_launch_device_init (FuSystem76LaunchDevice *device)
{
fu_device_set_remove_delay (FU_DEVICE (device), FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE);
fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_ADD_COUNTERPART_GUIDS);
fu_device_set_version_format (FU_DEVICE (device), FWUPD_VERSION_FORMAT_PLAIN);
fu_device_set_protocol (FU_DEVICE (device), "org.usb.dfu");
fu_device_retry_set_delay (FU_DEVICE (device), 100);
}
static void
fu_system76_launch_device_class_init (FuSystem76LaunchDeviceClass *klass)
{
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass);
klass_device->setup = fu_system76_launch_device_setup;
klass_device->detach = fu_system76_launch_device_detach;
klass_usb_device->open = fu_system76_launch_device_open;
klass_usb_device->close = fu_system76_launch_device_close;
}