mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-06 12:11:44 +00:00

This makes more sense; we're updating the device, not the plugin itself. This also means we don't need to funnel everything through callbacks like GFileProgressCallback and we can also update the state without adding an explicit callback to each derived device type.
728 lines
21 KiB
C
728 lines
21 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 <string.h>
|
|
#include <appstream-glib.h>
|
|
|
|
#include "fu-ebitdo-common.h"
|
|
#include "fu-ebitdo-device.h"
|
|
|
|
typedef struct
|
|
{
|
|
FuEbitdoDeviceKind kind;
|
|
guint32 serial[9];
|
|
} FuEbitdoDevicePrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (FuEbitdoDevice, fu_ebitdo_device, FU_TYPE_USB_DEVICE)
|
|
|
|
#define GET_PRIVATE(o) (fu_ebitdo_device_get_instance_private (o))
|
|
|
|
/**
|
|
* fu_ebitdo_device_kind_from_string:
|
|
* @kind: the string.
|
|
*
|
|
* Converts the text representation to an enumerated value.
|
|
*
|
|
* Returns: (transfer full): a #FuEbitdoDeviceKind, or %FU_EBITDO_DEVICE_KIND_UNKNOWN for unknown.
|
|
*
|
|
* Since: 0.1.0
|
|
**/
|
|
FuEbitdoDeviceKind
|
|
fu_ebitdo_device_kind_from_string (const gchar *kind)
|
|
{
|
|
if (g_strcmp0 (kind, "BOOTLOADER") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_BOOTLOADER;
|
|
if (g_strcmp0 (kind, "FC30") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_FC30;
|
|
if (g_strcmp0 (kind, "NES30") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_NES30;
|
|
if (g_strcmp0 (kind, "SFC30") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_SFC30;
|
|
if (g_strcmp0 (kind, "SNES30") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_SNES30;
|
|
if (g_strcmp0 (kind, "FC30PRO") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_FC30PRO;
|
|
if (g_strcmp0 (kind, "NES30PRO") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_NES30PRO;
|
|
if (g_strcmp0 (kind, "FC30_ARCADE") == 0)
|
|
return FU_EBITDO_DEVICE_KIND_FC30_ARCADE;
|
|
return FU_EBITDO_DEVICE_KIND_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* fu_ebitdo_device_kind_to_string:
|
|
* @kind: the #FuEbitdoDeviceKind.
|
|
*
|
|
* Converts the enumerated value to an text representation.
|
|
*
|
|
* Returns: string version of @kind
|
|
*
|
|
* Since: 0.1.0
|
|
**/
|
|
const gchar *
|
|
fu_ebitdo_device_kind_to_string (FuEbitdoDeviceKind kind)
|
|
{
|
|
if (kind == FU_EBITDO_DEVICE_KIND_BOOTLOADER)
|
|
return "BOOTLOADER";
|
|
if (kind == FU_EBITDO_DEVICE_KIND_FC30)
|
|
return "FC30";
|
|
if (kind == FU_EBITDO_DEVICE_KIND_NES30)
|
|
return "NES30";
|
|
if (kind == FU_EBITDO_DEVICE_KIND_SFC30)
|
|
return "SFC30";
|
|
if (kind == FU_EBITDO_DEVICE_KIND_SNES30)
|
|
return "SNES30";
|
|
if (kind == FU_EBITDO_DEVICE_KIND_FC30PRO)
|
|
return "FC30PRO";
|
|
if (kind == FU_EBITDO_DEVICE_KIND_NES30PRO)
|
|
return "NES30PRO";
|
|
if (kind == FU_EBITDO_DEVICE_KIND_FC30_ARCADE)
|
|
return "FC30_ARCADE";
|
|
return NULL;
|
|
}
|
|
|
|
FuEbitdoDeviceKind
|
|
fu_ebitdo_device_get_kind (FuEbitdoDevice *device)
|
|
{
|
|
FuEbitdoDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->kind;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ebitdo_device_send (FuEbitdoDevice *device,
|
|
FuEbitdoPktType type,
|
|
FuEbitdoPktCmd subtype,
|
|
FuEbitdoPktCmd cmd,
|
|
const guint8 *in,
|
|
gsize in_len,
|
|
GError **error)
|
|
{
|
|
FuEbitdoDevicePrivate *priv = GET_PRIVATE (device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
guint8 packet[FU_EBITDO_USB_EP_SIZE];
|
|
gsize actual_length;
|
|
guint8 ep_out = FU_EBITDO_USB_RUNTIME_EP_OUT;
|
|
g_autoptr(GError) error_local = NULL;
|
|
FuEbitdoPkt *hdr = (FuEbitdoPkt *) packet;
|
|
|
|
/* different */
|
|
if (priv->kind == FU_EBITDO_DEVICE_KIND_BOOTLOADER)
|
|
ep_out = FU_EBITDO_USB_BOOTLOADER_EP_OUT;
|
|
|
|
/* check size */
|
|
if (in_len > 64 - 8) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"input buffer too large");
|
|
return FALSE;
|
|
}
|
|
|
|
/* packet[0] is the total length of the packet */
|
|
memset (packet, 0x00, sizeof(packet));
|
|
hdr->type = type;
|
|
hdr->subtype = subtype;
|
|
|
|
/* do we have a payload */
|
|
if (in_len > 0) {
|
|
hdr->cmd_len = GUINT16_TO_LE (in_len + 3);
|
|
hdr->cmd = cmd;
|
|
hdr->payload_len = GUINT16_TO_LE (in_len);
|
|
memcpy (packet + 0x08, in, in_len);
|
|
hdr->pkt_len = (guint8) (in_len + 7);
|
|
} else {
|
|
hdr->cmd_len = GUINT16_TO_LE (in_len + 1);
|
|
hdr->cmd = cmd;
|
|
hdr->pkt_len = 5;
|
|
}
|
|
|
|
/* debug */
|
|
if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) {
|
|
fu_ebitdo_dump_raw ("->DEVICE", packet, (gsize) hdr->pkt_len + 1);
|
|
fu_ebitdo_dump_pkt (hdr);
|
|
}
|
|
|
|
/* get data from device */
|
|
if (!g_usb_device_interrupt_transfer (usb_device,
|
|
ep_out,
|
|
packet,
|
|
FU_EBITDO_USB_EP_SIZE,
|
|
&actual_length,
|
|
FU_EBITDO_USB_TIMEOUT,
|
|
NULL, /* cancellable */
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to send to device on ep 0x%02x: %s",
|
|
(guint) FU_EBITDO_USB_BOOTLOADER_EP_OUT,
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ebitdo_device_receive (FuEbitdoDevice *device,
|
|
guint8 *out,
|
|
gsize out_len,
|
|
GError **error)
|
|
{
|
|
FuEbitdoDevicePrivate *priv = GET_PRIVATE (device);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
guint8 packet[FU_EBITDO_USB_EP_SIZE];
|
|
gsize actual_length;
|
|
guint8 ep_in = FU_EBITDO_USB_RUNTIME_EP_IN;
|
|
g_autoptr(GError) error_local = NULL;
|
|
FuEbitdoPkt *hdr = (FuEbitdoPkt *) packet;
|
|
|
|
/* different */
|
|
if (priv->kind == FU_EBITDO_DEVICE_KIND_BOOTLOADER)
|
|
ep_in = FU_EBITDO_USB_BOOTLOADER_EP_IN;
|
|
|
|
/* get data from device */
|
|
memset (packet, 0x0, sizeof(packet));
|
|
if (!g_usb_device_interrupt_transfer (usb_device,
|
|
ep_in,
|
|
packet,
|
|
FU_EBITDO_USB_EP_SIZE,
|
|
&actual_length,
|
|
FU_EBITDO_USB_TIMEOUT,
|
|
NULL, /* cancellable */
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to retrieve from device on ep 0x%02x: %s",
|
|
(guint) FU_EBITDO_USB_BOOTLOADER_EP_IN,
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* debug */
|
|
if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) {
|
|
fu_ebitdo_dump_raw ("<-DEVICE", packet, actual_length);
|
|
fu_ebitdo_dump_pkt (hdr);
|
|
}
|
|
|
|
/* get-version (booloader) */
|
|
if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD &&
|
|
hdr->subtype == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA &&
|
|
hdr->cmd == FU_EBITDO_PKT_CMD_FW_GET_VERSION) {
|
|
if (out != NULL) {
|
|
if (hdr->payload_len != out_len) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"outbuf size wrong, expected %" G_GSIZE_FORMAT " got %u",
|
|
out_len,
|
|
hdr->payload_len);
|
|
return FALSE;
|
|
}
|
|
memcpy (out,
|
|
packet + sizeof(FuEbitdoPkt),
|
|
hdr->payload_len);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* get-version (firmware) -- not a packet, just raw data! */
|
|
if (hdr->pkt_len == FU_EBITDO_PKT_CMD_GET_VERSION_RESPONSE) {
|
|
if (out != NULL) {
|
|
if (out_len != 4) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"outbuf size wrong, expected 4 got %" G_GSIZE_FORMAT,
|
|
out_len);
|
|
return FALSE;
|
|
}
|
|
memcpy (out, packet + 1, 4);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* verification-id response */
|
|
if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD &&
|
|
hdr->subtype == FU_EBITDO_PKT_CMD_VERIFICATION_ID) {
|
|
if (out != NULL) {
|
|
if (hdr->cmd_len != out_len) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"outbuf size wrong, expected %" G_GSIZE_FORMAT " got %i",
|
|
out_len,
|
|
hdr->cmd_len);
|
|
return FALSE;
|
|
}
|
|
memcpy (out,
|
|
packet + sizeof(FuEbitdoPkt) - 3,
|
|
hdr->cmd_len);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* update-firmware-data */
|
|
if (hdr->type == FU_EBITDO_PKT_TYPE_USER_CMD &&
|
|
hdr->subtype == FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA &&
|
|
hdr->payload_len == 0x00) {
|
|
if (hdr->cmd != FU_EBITDO_PKT_CMD_ACK) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"write failed, got %s",
|
|
fu_ebitdo_pkt_cmd_to_string (hdr->cmd));
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* unhandled */
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"unexpected device response");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
fu_ebitdo_device_set_version (FuEbitdoDevice *device, guint32 version)
|
|
{
|
|
g_autofree gchar *tmp = NULL;
|
|
tmp = g_strdup_printf ("%.2f", version / 100.f);
|
|
fu_device_set_version (FU_DEVICE (device), tmp);
|
|
}
|
|
|
|
static gboolean
|
|
fu_ebitdo_device_validate (FuEbitdoDevice *device, GError **error)
|
|
{
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
guint8 idx;
|
|
g_autofree gchar *ven = NULL;
|
|
const gchar *whitelist[] = {
|
|
"8Bitdo",
|
|
"SFC30",
|
|
NULL };
|
|
|
|
/* this is a new, always valid, VID */
|
|
if (g_usb_device_get_vid (usb_device) == 0x2dc8)
|
|
return TRUE;
|
|
|
|
/* verify the vendor prefix against a whitelist */
|
|
idx = g_usb_device_get_manufacturer_index (usb_device);
|
|
ven = g_usb_device_get_string_descriptor (usb_device, idx, error);
|
|
if (ven == NULL) {
|
|
g_prefix_error (error, "could not check vendor descriptor: ");
|
|
return FALSE;
|
|
}
|
|
for (guint i = 0; whitelist[i] != NULL; i++) {
|
|
if (g_str_has_prefix (ven, whitelist[i]))
|
|
return TRUE;
|
|
}
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"vendor '%s' did not match whitelist, "
|
|
"probably not a 8Bitdo device…", ven);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ebitdo_device_open (FuUsbDevice *device, GError **error)
|
|
{
|
|
FuEbitdoDevice *self = FU_EBITDO_DEVICE (device);
|
|
FuEbitdoDevicePrivate *priv = GET_PRIVATE (self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (device);
|
|
gdouble tmp;
|
|
guint32 version_tmp = 0;
|
|
guint32 serial_tmp[9];
|
|
|
|
/* open, then ensure this is actually 8Bitdo hardware */
|
|
g_debug ("opening %s", fu_ebitdo_device_kind_to_string (priv->kind));
|
|
if (!fu_ebitdo_device_validate (self, error))
|
|
return FALSE;
|
|
if (!g_usb_device_claim_interface (usb_device, 0, /* 0 = idx? */
|
|
G_USB_DEVICE_CLAIM_INTERFACE_BIND_KERNEL_DRIVER,
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
|
|
/* in firmware mode */
|
|
if (priv->kind != FU_EBITDO_DEVICE_KIND_BOOTLOADER) {
|
|
if (!fu_ebitdo_device_send (self,
|
|
FU_EBITDO_PKT_TYPE_USER_CMD,
|
|
FU_EBITDO_PKT_CMD_GET_VERSION,
|
|
0,
|
|
NULL, 0, /* in */
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
if (!fu_ebitdo_device_receive (self,
|
|
(guint8 *) &version_tmp,
|
|
sizeof(version_tmp),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
tmp = (gdouble) GUINT32_FROM_LE (version_tmp);
|
|
fu_ebitdo_device_set_version (self, tmp);
|
|
return TRUE;
|
|
}
|
|
|
|
/* get version */
|
|
if (!fu_ebitdo_device_send (self,
|
|
FU_EBITDO_PKT_TYPE_USER_CMD,
|
|
FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA,
|
|
FU_EBITDO_PKT_CMD_FW_GET_VERSION,
|
|
NULL, 0, /* in */
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
if (!fu_ebitdo_device_receive (self,
|
|
(guint8 *) &version_tmp,
|
|
sizeof(version_tmp),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
tmp = (gdouble) GUINT32_FROM_LE (version_tmp);
|
|
fu_ebitdo_device_set_version (self, tmp);
|
|
|
|
/* get verification ID */
|
|
if (!fu_ebitdo_device_send (self,
|
|
FU_EBITDO_PKT_TYPE_USER_CMD,
|
|
FU_EBITDO_PKT_CMD_GET_VERIFICATION_ID,
|
|
0x00, /* cmd */
|
|
NULL, 0,
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
memset (serial_tmp, 0x00, sizeof (serial_tmp));
|
|
if (!fu_ebitdo_device_receive (self,
|
|
(guint8 *) &serial_tmp, sizeof(serial_tmp),
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
for (guint i = 0; i < 9; i++)
|
|
priv->serial[i] = GUINT32_FROM_LE (serial_tmp[i]);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
const guint32 *
|
|
fu_ebitdo_device_get_serial (FuEbitdoDevice *device)
|
|
{
|
|
FuEbitdoDevicePrivate *priv = GET_PRIVATE (device);
|
|
return priv->serial;
|
|
}
|
|
|
|
static void
|
|
fu_ebitdo_device_set_progress (FuEbitdoDevice *device, guint32 current, guint32 total)
|
|
{
|
|
gdouble percentage = -1.f;
|
|
if (total > 0)
|
|
percentage = (100.f * (gdouble) current) / (gdouble) total;
|
|
if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) {
|
|
g_debug ("written %" G_GUINT32_FORMAT "/%" G_GUINT32_FORMAT " bytes [%.1f%%]",
|
|
current, total, percentage);
|
|
}
|
|
fu_device_set_progress (FU_DEVICE (device), (guint) percentage);
|
|
}
|
|
|
|
gboolean
|
|
fu_ebitdo_device_write_firmware (FuEbitdoDevice *device, GBytes *fw, GError **error)
|
|
{
|
|
FuEbitdoDevicePrivate *priv = GET_PRIVATE (device);
|
|
FuEbitdoFirmwareHeader *hdr;
|
|
const guint8 *payload_data;
|
|
const guint chunk_sz = 32;
|
|
guint32 payload_len;
|
|
guint32 serial_new[3];
|
|
g_autoptr(GError) error_local = NULL;
|
|
const guint32 app_key_index[16] = {
|
|
0x186976e5, 0xcac67acd, 0x38f27fee, 0x0a4948f1,
|
|
0xb75b7753, 0x1f8ffa5c, 0xbff8cf43, 0xc4936167,
|
|
0x92bd03f0, 0x5573c6ed, 0x57d8845b, 0x827197ac,
|
|
0xb91901c9, 0x3917edfe, 0xbcd6344f, 0xcf9e23b5
|
|
};
|
|
|
|
/* corrupt */
|
|
if (g_bytes_get_size (fw) < sizeof (FuEbitdoFirmwareHeader)) {
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"firmware too small for header");
|
|
return FALSE;
|
|
}
|
|
|
|
/* print details about the firmware */
|
|
hdr = (FuEbitdoFirmwareHeader *) g_bytes_get_data (fw, NULL);
|
|
fu_ebitdo_dump_firmware_header (hdr);
|
|
|
|
/* check the file size */
|
|
payload_len = (guint32) (g_bytes_get_size (fw) - sizeof (FuEbitdoFirmwareHeader));
|
|
if (payload_len != GUINT32_FROM_LE (hdr->destination_len)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"file size incorrect, expected 0x%04x got 0x%04x",
|
|
(guint) GUINT32_FROM_LE (hdr->destination_len),
|
|
(guint) payload_len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* check if this is firmware */
|
|
for (guint i = 0; i < 4; i++) {
|
|
if (hdr->reserved[i] != 0x0) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"data invalid, reserved[%u] = 0x%04x",
|
|
i, hdr->reserved[i]);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* set up the firmware header */
|
|
if (!fu_ebitdo_device_send (device,
|
|
FU_EBITDO_PKT_TYPE_USER_CMD,
|
|
FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA,
|
|
FU_EBITDO_PKT_CMD_FW_UPDATE_HEADER,
|
|
(const guint8 *) hdr, sizeof(FuEbitdoFirmwareHeader),
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to set up firmware header: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
if (!fu_ebitdo_device_receive (device, NULL, 0, &error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to get ACK for fw update header: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* flash the firmware in 32 byte blocks */
|
|
payload_data = g_bytes_get_data (fw, NULL);
|
|
payload_data += sizeof(FuEbitdoFirmwareHeader);
|
|
for (guint32 offset = 0; offset < payload_len; offset += chunk_sz) {
|
|
if (g_getenv ("FWUPD_EBITDO_VERBOSE") != NULL) {
|
|
g_debug ("writing %u bytes to 0x%04x of 0x%04x",
|
|
chunk_sz, offset, payload_len);
|
|
}
|
|
fu_ebitdo_device_set_progress (device, offset, payload_len);
|
|
if (!fu_ebitdo_device_send (device,
|
|
FU_EBITDO_PKT_TYPE_USER_CMD,
|
|
FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA,
|
|
FU_EBITDO_PKT_CMD_FW_UPDATE_DATA,
|
|
payload_data + offset, chunk_sz,
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to write firmware @0x%04x: %s",
|
|
offset, error_local->message);
|
|
return FALSE;
|
|
}
|
|
if (!fu_ebitdo_device_receive (device, NULL, 0, &error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"Failed to get ACK for write firmware @0x%04x: %s",
|
|
offset, error_local->message);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* mark as complete */
|
|
fu_ebitdo_device_set_progress (device, payload_len, payload_len);
|
|
|
|
/* set the "encode id" which is likely a checksum, bluetooth pairing
|
|
* or maybe just security-through-obscurity -- also note:
|
|
* SET_ENCODE_ID enforces no read for success?! */
|
|
serial_new[0] = priv->serial[0] ^ app_key_index[priv->serial[0] & 0x0f];
|
|
serial_new[1] = priv->serial[1] ^ app_key_index[priv->serial[1] & 0x0f];
|
|
serial_new[2] = priv->serial[2] ^ app_key_index[priv->serial[2] & 0x0f];
|
|
if (!fu_ebitdo_device_send (device,
|
|
FU_EBITDO_PKT_TYPE_USER_CMD,
|
|
FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA,
|
|
FU_EBITDO_PKT_CMD_FW_SET_ENCODE_ID,
|
|
(guint8 *) serial_new,
|
|
sizeof(serial_new),
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to set encoding ID: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* mark flash as successful */
|
|
if (!fu_ebitdo_device_send (device,
|
|
FU_EBITDO_PKT_TYPE_USER_CMD,
|
|
FU_EBITDO_PKT_CMD_UPDATE_FIRMWARE_DATA,
|
|
FU_EBITDO_PKT_CMD_FW_UPDATE_OK,
|
|
NULL, 0,
|
|
&error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to mark firmware as successful: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
if (!fu_ebitdo_device_receive (device, NULL, 0, &error_local)) {
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"failed to get ACK for mark firmware as successful: %s",
|
|
error_local->message);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success! */
|
|
return TRUE;
|
|
}
|
|
|
|
typedef struct {
|
|
guint16 vid;
|
|
guint16 pid;
|
|
FuEbitdoDeviceKind kind;
|
|
} FuEbitdoVidPid;
|
|
|
|
static FuEbitdoDeviceKind
|
|
fu_ebitdo_device_detect_kind (GUsbDevice *usb_device)
|
|
{
|
|
const FuEbitdoVidPid vidpids[] = {
|
|
/* legacy VIDs */
|
|
{ 0x0483, 0x5750, FU_EBITDO_DEVICE_KIND_BOOTLOADER },
|
|
{ 0x1235, 0xab11, FU_EBITDO_DEVICE_KIND_FC30 },
|
|
{ 0x1235, 0xab12, FU_EBITDO_DEVICE_KIND_NES30 },
|
|
{ 0x1235, 0xab21, FU_EBITDO_DEVICE_KIND_SFC30 },
|
|
{ 0x1235, 0xab20, FU_EBITDO_DEVICE_KIND_SNES30 },
|
|
{ 0x1002, 0x9000, FU_EBITDO_DEVICE_KIND_FC30PRO },
|
|
{ 0x2002, 0x9000, FU_EBITDO_DEVICE_KIND_NES30PRO },
|
|
{ 0x8000, 0x1002, FU_EBITDO_DEVICE_KIND_FC30_ARCADE },
|
|
/* new VID */
|
|
{ 0x2dc8, 0x5750, FU_EBITDO_DEVICE_KIND_BOOTLOADER },
|
|
{ 0x2dc8, 0xab11, FU_EBITDO_DEVICE_KIND_FC30 },
|
|
{ 0x2dc8, 0xab12, FU_EBITDO_DEVICE_KIND_NES30 },
|
|
{ 0x2dc8, 0xab21, FU_EBITDO_DEVICE_KIND_SFC30 },
|
|
{ 0x2dc8, 0xab20, FU_EBITDO_DEVICE_KIND_SNES30 },
|
|
{ 0x2dc8, 0x9000, FU_EBITDO_DEVICE_KIND_FC30PRO },
|
|
{ 0x2dc8, 0x9001, FU_EBITDO_DEVICE_KIND_NES30PRO },
|
|
{ 0x2dc8, 0x1002, FU_EBITDO_DEVICE_KIND_FC30_ARCADE },
|
|
{ 0x0000, 0x0000, FU_EBITDO_DEVICE_KIND_UNKNOWN }
|
|
};
|
|
|
|
/* find correct kind */
|
|
for (guint j = 0; vidpids[j].vid != 0x0000; j++) {
|
|
if (g_usb_device_get_vid (usb_device) == vidpids[j].vid &&
|
|
g_usb_device_get_pid (usb_device) == vidpids[j].pid) {
|
|
return vidpids[j].kind;
|
|
}
|
|
}
|
|
return FU_EBITDO_DEVICE_KIND_UNKNOWN;
|
|
}
|
|
|
|
static gboolean
|
|
fu_ebitdo_device_probe (FuUsbDevice *device, GError **error)
|
|
{
|
|
FuEbitdoDevice *self = FU_EBITDO_DEVICE (device);
|
|
FuEbitdoDevicePrivate *priv = GET_PRIVATE (self);
|
|
GUsbDevice *usb_device = fu_usb_device_get_dev (FU_USB_DEVICE (device));
|
|
g_autofree gchar *name = NULL;
|
|
|
|
/* not supported */
|
|
priv->kind = fu_ebitdo_device_detect_kind (usb_device);
|
|
if (priv->kind == FU_EBITDO_DEVICE_KIND_UNKNOWN) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"not supported with this device");
|
|
return FALSE;
|
|
}
|
|
|
|
/* allowed, but requires manual bootloader step */
|
|
fu_device_add_flag (FU_DEVICE (device), FWUPD_DEVICE_FLAG_UPDATABLE);
|
|
fu_device_set_remove_delay (FU_DEVICE (device), FU_DEVICE_REMOVE_DELAY_USER_REPLUG);
|
|
|
|
/* set name and vendor */
|
|
name = g_strdup_printf ("%s Gamepad",
|
|
fu_ebitdo_device_kind_to_string (priv->kind));
|
|
fu_device_set_name (FU_DEVICE (device), name);
|
|
fu_device_set_summary (FU_DEVICE (device),
|
|
"A redesigned classic game controller");
|
|
fu_device_set_vendor (FU_DEVICE (device), "8Bitdo");
|
|
|
|
/* add a hardcoded icon name */
|
|
fu_device_add_icon (FU_DEVICE (device), "input-gaming");
|
|
|
|
/* only the bootloader can do the update */
|
|
if (priv->kind != FU_EBITDO_DEVICE_KIND_BOOTLOADER) {
|
|
fu_device_add_guid (FU_DEVICE (device), "USB\\VID_0483&PID_5750");
|
|
fu_device_add_guid (FU_DEVICE (device), "USB\\VID_2DC8&PID_5750");
|
|
fu_device_add_flag (FU_DEVICE (device),
|
|
FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER);
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_ebitdo_device_init (FuEbitdoDevice *device)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_ebitdo_device_class_init (FuEbitdoDeviceClass *klass)
|
|
{
|
|
FuUsbDeviceClass *klass_usb_device = FU_USB_DEVICE_CLASS (klass);
|
|
klass_usb_device->open = fu_ebitdo_device_open;
|
|
klass_usb_device->probe = fu_ebitdo_device_probe;
|
|
}
|
|
|
|
/**
|
|
* fu_ebitdo_device_new:
|
|
*
|
|
* Creates a new #FuEbitdoDevice.
|
|
*
|
|
* Returns: (transfer full): a #FuEbitdoDevice, or %NULL if not a game pad
|
|
*
|
|
* Since: 0.1.0
|
|
**/
|
|
FuEbitdoDevice *
|
|
fu_ebitdo_device_new (GUsbDevice *usb_device)
|
|
{
|
|
FuEbitdoDevice *device;
|
|
device = g_object_new (FU_TYPE_EBITDO_DEVICE,
|
|
"usb-device", usb_device,
|
|
NULL);
|
|
return FU_EBITDO_DEVICE (device);
|
|
}
|