mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-21 17:32:04 +00:00
879 lines
22 KiB
C
879 lines
22 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 <appstream-glib.h>
|
|
#include <fcntl.h>
|
|
#include <gudev/gudev.h>
|
|
#include <poll.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <errno.h>
|
|
|
|
#include <libdfu/dfu.h>
|
|
|
|
#include "fu-device-altos.h"
|
|
|
|
typedef struct
|
|
{
|
|
FuDeviceAltosKind kind;
|
|
GUsbDevice *usb_device;
|
|
guint32 serial[9];
|
|
gchar *guid;
|
|
gchar *tty;
|
|
gchar *version;
|
|
guint64 addr_base;
|
|
guint64 addr_bound;
|
|
struct termios tty_termios;
|
|
gint tty_fd;
|
|
} FuDeviceAltosPrivate;
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (FuDeviceAltos, fu_device_altos, FU_TYPE_DEVICE)
|
|
|
|
#define GET_PRIVATE(o) (fu_device_altos_get_instance_private (o))
|
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUdevClient, g_object_unref)
|
|
|
|
/**
|
|
* fu_device_altos_kind_from_string:
|
|
* @kind: the string.
|
|
*
|
|
* Converts the text representation to an enumerated value.
|
|
*
|
|
* Returns: (transfer full): a #FuDeviceAltosKind, or %FU_DEVICE_ALTOS_KIND_UNKNOWN for unknown.
|
|
*
|
|
* Since: 0.1.0
|
|
**/
|
|
FuDeviceAltosKind
|
|
fu_device_altos_kind_from_string (const gchar *kind)
|
|
{
|
|
if (g_strcmp0 (kind, "BOOTLOADER") == 0)
|
|
return FU_DEVICE_ALTOS_KIND_BOOTLOADER;
|
|
if (g_strcmp0 (kind, "CHAOSKEY") == 0)
|
|
return FU_DEVICE_ALTOS_KIND_CHAOSKEY;
|
|
return FU_DEVICE_ALTOS_KIND_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* fu_device_altos_kind_to_string:
|
|
* @kind: the #FuDeviceAltosKind.
|
|
*
|
|
* Converts the enumerated value to an text representation.
|
|
*
|
|
* Returns: string version of @kind
|
|
*
|
|
* Since: 0.1.0
|
|
**/
|
|
const gchar *
|
|
fu_device_altos_kind_to_string (FuDeviceAltosKind kind)
|
|
{
|
|
if (kind == FU_DEVICE_ALTOS_KIND_BOOTLOADER)
|
|
return "BOOTLOADER";
|
|
if (kind == FU_DEVICE_ALTOS_KIND_CHAOSKEY)
|
|
return "CHAOSKEY";
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
fu_device_altos_finalize (GObject *object)
|
|
{
|
|
FuDeviceAltos *device = FU_DEVICE_ALTOS (object);
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
|
|
g_free (priv->guid);
|
|
g_free (priv->tty);
|
|
g_free (priv->version);
|
|
if (priv->usb_device != NULL)
|
|
g_object_unref (priv->usb_device);
|
|
|
|
G_OBJECT_CLASS (fu_device_altos_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_device_altos_init (FuDeviceAltos *device)
|
|
{
|
|
}
|
|
|
|
static void
|
|
fu_device_altos_class_init (FuDeviceAltosClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = fu_device_altos_finalize;
|
|
}
|
|
|
|
FuDeviceAltosKind
|
|
fu_device_altos_get_kind (FuDeviceAltos *device)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
return priv->kind;
|
|
}
|
|
|
|
static gboolean
|
|
fu_device_altos_find_tty (FuDeviceAltos *device, GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
g_autoptr(GList) devices = NULL;
|
|
g_autoptr(GUdevClient) gudev_client = g_udev_client_new (NULL);
|
|
|
|
/* find all tty devices */
|
|
devices = g_udev_client_query_by_subsystem (gudev_client, "tty");
|
|
for (GList *l = devices; l != NULL; l = l->next) {
|
|
GUdevDevice *dev = G_UDEV_DEVICE (l->data);
|
|
|
|
/* get the tty device */
|
|
const gchar *dev_file = g_udev_device_get_device_file (dev);
|
|
if (dev_file == NULL)
|
|
continue;
|
|
|
|
/* get grandparent */
|
|
dev = g_udev_device_get_parent (dev);
|
|
if (dev == NULL)
|
|
continue;
|
|
dev = g_udev_device_get_parent (dev);
|
|
if (dev == NULL)
|
|
continue;
|
|
|
|
/* check correct device */
|
|
if (g_udev_device_get_sysfs_attr_as_int (dev, "busnum") !=
|
|
g_usb_device_get_bus (priv->usb_device))
|
|
continue;
|
|
if (g_udev_device_get_sysfs_attr_as_int (dev, "devnum") !=
|
|
g_usb_device_get_address (priv->usb_device))
|
|
continue;
|
|
|
|
/* success */
|
|
priv->tty = g_strdup (dev_file);
|
|
return TRUE;
|
|
}
|
|
|
|
/* failure */
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"failed to find tty for %u:%u",
|
|
g_usb_device_get_bus (priv->usb_device),
|
|
g_usb_device_get_address (priv->usb_device));
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_device_altos_tty_write (FuDeviceAltos *device,
|
|
const gchar *data,
|
|
gssize data_len,
|
|
GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
gint rc;
|
|
gssize idx = 0;
|
|
guint timeout_ms = 500;
|
|
struct pollfd fds;
|
|
|
|
/* lets assume this is text */
|
|
if (data_len < 0)
|
|
data_len = strlen (data);
|
|
|
|
fds.fd = priv->tty_fd;
|
|
fds.events = POLLOUT;
|
|
|
|
g_debug ("write, with timeout %ums", timeout_ms);
|
|
while (idx < data_len) {
|
|
|
|
/* wait for data to be allowed to write without blocking */
|
|
rc = poll (&fds, 1, (gint) timeout_ms);
|
|
if (rc == 0)
|
|
break;
|
|
if (rc < 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"failed to poll %i",
|
|
priv->tty_fd);
|
|
return FALSE;
|
|
}
|
|
|
|
/* we can write data */
|
|
if (fds.revents & POLLOUT) {
|
|
gssize len;
|
|
g_debug ("writing %" G_GSSIZE_FORMAT " bytes: %s", data_len, data);
|
|
len = write (priv->tty_fd, data + idx, data_len - idx);
|
|
if (len < 0) {
|
|
if (errno == EAGAIN) {
|
|
g_debug ("got EAGAIN, trying harder");
|
|
continue;
|
|
}
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to write %" G_GSSIZE_FORMAT
|
|
" bytes to %i: %s" ,
|
|
data_len,
|
|
priv->tty_fd,
|
|
strerror (errno));
|
|
return FALSE;
|
|
}
|
|
g_debug ("wrote %" G_GSSIZE_FORMAT " bytes", len);
|
|
idx += len;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GString *
|
|
fu_device_altos_tty_read (FuDeviceAltos *device,
|
|
guint timeout_ms,
|
|
gssize max_size,
|
|
GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
gint rc;
|
|
struct pollfd fds;
|
|
g_autoptr(GString) str = g_string_new (NULL);
|
|
|
|
fds.fd = priv->tty_fd;
|
|
fds.events = POLLIN;
|
|
|
|
g_debug ("read, with timeout %ums", timeout_ms);
|
|
for (;;) {
|
|
/* wait for data to appear */
|
|
rc = poll (&fds, 1, (gint) timeout_ms);
|
|
if (rc == 0)
|
|
break;
|
|
if (rc < 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"failed to poll %i",
|
|
priv->tty_fd);
|
|
return NULL;
|
|
}
|
|
|
|
/* we have data to read */
|
|
if (fds.revents & POLLIN) {
|
|
guint8 buf[1024];
|
|
gssize len = read (priv->tty_fd, buf, sizeof (buf));
|
|
if (len < 0) {
|
|
if (errno == EAGAIN) {
|
|
g_debug ("got EAGAIN, trying harder");
|
|
continue;
|
|
}
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"failed to read %i: %s",
|
|
priv->tty_fd,
|
|
strerror (errno));
|
|
return NULL;
|
|
}
|
|
if (len > 0) {
|
|
g_debug ("read %" G_GSSIZE_FORMAT " bytes from device", len);
|
|
g_string_append_len (str, (gchar *) buf, len);
|
|
}
|
|
|
|
/* check maximum size */
|
|
if (max_size > 0 && str->len >= (guint) max_size)
|
|
break;
|
|
continue;
|
|
}
|
|
if (fds.revents & POLLERR) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"error condition");
|
|
return NULL;
|
|
}
|
|
if (fds.revents & POLLHUP) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"connection hung up");
|
|
return NULL;
|
|
}
|
|
if (fds.revents & POLLNVAL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"invalid request");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* no data */
|
|
if (str->len == 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_READ,
|
|
"no data recieved from device in %ums",
|
|
timeout_ms);
|
|
return NULL;
|
|
}
|
|
|
|
/* return blob */
|
|
return g_steal_pointer (&str);
|
|
}
|
|
|
|
static gboolean
|
|
fu_device_altos_tty_open (FuDeviceAltos *device, GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
struct termios termios;
|
|
g_autoptr(GString) str = NULL;
|
|
|
|
/* open device */
|
|
priv->tty_fd = open (priv->tty, O_RDWR | O_NONBLOCK);
|
|
if (priv->tty_fd < 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"failed to open %s",
|
|
priv->tty);
|
|
return FALSE;
|
|
}
|
|
|
|
/* get the old termios settings so we can restore later */
|
|
if (tcgetattr (priv->tty_fd, &termios) < 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to get attributes from fd");
|
|
return FALSE;
|
|
}
|
|
priv->tty_termios = termios;
|
|
cfmakeraw (&termios);
|
|
|
|
/* set speed */
|
|
if (cfsetspeed (&termios, B9600) < 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to set terminal speed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* one input byte is enough to return
|
|
* inter-character timer off */
|
|
termios.c_cc[VMIN] = 1;
|
|
termios.c_cc[VTIME] = 0;
|
|
|
|
/* set all new data */
|
|
if (tcsetattr (priv->tty_fd, TCSAFLUSH, &termios) < 0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INTERNAL,
|
|
"failed to set attributes on fd");
|
|
return FALSE;
|
|
}
|
|
|
|
/* dump any pending input */
|
|
str = fu_device_altos_tty_read (device, 50, -1, NULL);
|
|
if (str != NULL)
|
|
g_debug ("dumping pending buffer: %s", str->str);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_device_altos_tty_close (FuDeviceAltos *device, GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
|
|
tcsetattr (priv->tty_fd, TCSAFLUSH, &priv->tty_termios);
|
|
close (priv->tty_fd);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GString *
|
|
fu_device_altos_read_page (FuDeviceAltos *device, guint address, GError **error)
|
|
{
|
|
g_autoptr(GString) str = NULL;
|
|
g_autofree gchar *cmd = g_strdup_printf ("R %x\n", address);
|
|
if (!fu_device_altos_tty_write (device, cmd, -1, error))
|
|
return NULL;
|
|
str = fu_device_altos_tty_read (device, 1500, 256, error);
|
|
if (str == NULL)
|
|
return NULL;
|
|
return g_steal_pointer (&str);
|
|
}
|
|
|
|
static gboolean
|
|
fu_device_altos_write_page (FuDeviceAltos *device,
|
|
guint address,
|
|
const guint8 *data,
|
|
guint data_len,
|
|
GError **error)
|
|
{
|
|
g_autofree gchar *cmd = g_strdup_printf ("W %x\n", address);
|
|
if (!fu_device_altos_tty_write (device, cmd, -1, error))
|
|
return FALSE;
|
|
if (!fu_device_altos_tty_write (device, (const gchar *) data, data_len, error))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
fu_device_check_firmware (FuDeviceAltos *device, DfuFirmware *firmware, GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
DfuElement *dfu_element;
|
|
DfuImage *dfu_image;
|
|
|
|
/* get default image */
|
|
dfu_image = dfu_firmware_get_image_default (firmware);
|
|
if (dfu_image == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"no firmware image");
|
|
return FALSE;
|
|
}
|
|
|
|
/* get default element */
|
|
dfu_element = dfu_image_get_element_default (dfu_image);
|
|
if (dfu_element == NULL) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"no firmware element");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check the start address */
|
|
if (dfu_element_get_address (dfu_element) != priv->addr_base) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"start address not correct %" G_GUINT32_FORMAT ":"
|
|
"%" G_GUINT64_FORMAT,
|
|
dfu_element_get_address (dfu_element),
|
|
priv->addr_base);
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_device_altos_write_firmware (FuDeviceAltos *device,
|
|
GBytes *fw,
|
|
FuDeviceAltosWriteFirmwareFlag flags,
|
|
GFileProgressCallback progress_cb,
|
|
gpointer progress_data,
|
|
GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
const gchar *data;
|
|
const gsize data_len;
|
|
guint flash_len;
|
|
g_autoptr(DfuFirmware) firmware = NULL;
|
|
g_autoptr(GBytes) fw_blob = NULL;
|
|
g_autoptr(GString) buf = g_string_new (NULL);
|
|
|
|
/* check kind */
|
|
if (priv->kind != FU_DEVICE_ALTOS_KIND_BOOTLOADER) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"verification only supported in bootloader");
|
|
return FALSE;
|
|
}
|
|
|
|
/* check sizes */
|
|
if (priv->addr_base == 0x0 || priv->addr_bound == 0x0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address base and bound are unset");
|
|
return FALSE;
|
|
}
|
|
|
|
/* read in blocks of 256 bytes */
|
|
flash_len = priv->addr_bound - priv->addr_base;
|
|
if (flash_len == 0x0 || flash_len > 0x100000) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address range was icorrect");
|
|
return FALSE;
|
|
}
|
|
|
|
/* load ihex blob */
|
|
firmware = dfu_firmware_new ();
|
|
if (!dfu_firmware_parse_data (firmware, fw,
|
|
DFU_FIRMWARE_PARSE_FLAG_NONE,
|
|
error)) {
|
|
return FALSE;
|
|
}
|
|
if (!fu_device_check_firmware (device, firmware, error))
|
|
return FALSE;
|
|
|
|
/* convert from ihex to a blob */
|
|
dfu_firmware_set_format (firmware, DFU_FIRMWARE_FORMAT_RAW);
|
|
fw_blob = dfu_firmware_write_data (firmware, error);
|
|
if (fw_blob == NULL)
|
|
return FALSE;
|
|
|
|
/* check firmware will fit */
|
|
data = g_bytes_get_data (fw_blob, &data_len);
|
|
if (data_len > flash_len) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_INVALID_FILE,
|
|
"firmware too large for device %" G_GSIZE_FORMAT ":%u",
|
|
data_len, flash_len);
|
|
return FALSE;
|
|
}
|
|
|
|
/* open tty for download */
|
|
if (!fu_device_altos_tty_open (device, error))
|
|
return FALSE;
|
|
for (guint i = 0; i < flash_len; i+= 0x100) {
|
|
g_autoptr(GString) str = NULL;
|
|
guint8 buf_tmp[0x100];
|
|
|
|
/* copy remaining data into buf if required */
|
|
memset (buf_tmp, 0xff, sizeof (buf));
|
|
if (i < data_len) {
|
|
gsize chunk_len = 0x100;
|
|
if (i + 0x100 > data_len)
|
|
chunk_len = data_len - i;
|
|
memcpy (buf_tmp, data + i, chunk_len);
|
|
}
|
|
|
|
/* verify data from device */
|
|
if (!fu_device_altos_write_page (device,
|
|
priv->addr_base + i,
|
|
buf_tmp,
|
|
0x100,
|
|
error))
|
|
return FALSE;
|
|
|
|
/* verify data written on device */
|
|
str = fu_device_altos_read_page (device,
|
|
priv->addr_base + i,
|
|
error);
|
|
if (str == NULL)
|
|
return FALSE;
|
|
if (str->len != 0x100) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to verify @%x, "
|
|
"not enough data returned",
|
|
(guint) (priv->addr_base + i));
|
|
return FALSE;
|
|
}
|
|
if (memcmp (str->str, buf_tmp, 0x100) != 0) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_WRITE,
|
|
"failed to verify @%x",
|
|
(guint) (priv->addr_base + i));
|
|
return FALSE;
|
|
}
|
|
|
|
/* progress */
|
|
if (progress_cb != NULL) {
|
|
progress_cb ((goffset) i,
|
|
(goffset) flash_len,
|
|
progress_data);
|
|
}
|
|
g_string_append_len (buf, str->str, str->len);
|
|
}
|
|
|
|
/* go to application mode */
|
|
if (flags & FU_DEVICE_ALTOS_WRITE_FIRMWARE_FLAG_REBOOT) {
|
|
if (!fu_device_altos_tty_write (device, "a\n", -1, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* progress complete */
|
|
if (progress_cb != NULL) {
|
|
progress_cb ((goffset) flash_len,
|
|
(goffset) flash_len,
|
|
progress_data);
|
|
}
|
|
|
|
/* done */
|
|
if (!fu_device_altos_tty_close (device, error))
|
|
return FALSE;
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
GBytes *
|
|
fu_device_altos_read_firmware (FuDeviceAltos *device,
|
|
GFileProgressCallback progress_cb,
|
|
gpointer progress_data,
|
|
GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
guint flash_len;
|
|
g_autoptr(GString) buf = g_string_new (NULL);
|
|
|
|
/* check kind */
|
|
if (priv->kind != FU_DEVICE_ALTOS_KIND_BOOTLOADER) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"verification only supported in bootloader");
|
|
return NULL;
|
|
}
|
|
|
|
/* check sizes */
|
|
if (priv->addr_base == 0x0 || priv->addr_bound == 0x0) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address base and bound are unset");
|
|
return NULL;
|
|
}
|
|
|
|
/* read in blocks of 256 bytes */
|
|
flash_len = priv->addr_bound - priv->addr_base;
|
|
if (flash_len == 0x0 || flash_len > 0x100000) {
|
|
g_set_error_literal (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"address range was icorrect");
|
|
return NULL;
|
|
}
|
|
|
|
/* open tty for download */
|
|
if (!fu_device_altos_tty_open (device, error))
|
|
return NULL;
|
|
for (guint i = priv->addr_base; i < priv->addr_bound; i+= 0x100) {
|
|
g_autoptr(GString) str = NULL;
|
|
|
|
/* request data from device */
|
|
str = fu_device_altos_read_page (device, i, error);
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
/* progress */
|
|
if (progress_cb != NULL) {
|
|
progress_cb ((goffset) (i - priv->addr_base),
|
|
(goffset) (priv->addr_bound - priv->addr_base),
|
|
progress_data);
|
|
}
|
|
g_string_append_len (buf, str->str, str->len);
|
|
}
|
|
|
|
/* done */
|
|
if (!fu_device_altos_tty_close (device, error))
|
|
return NULL;
|
|
|
|
/* success */
|
|
return g_bytes_new (buf->str, buf->len);
|
|
}
|
|
|
|
static gboolean
|
|
fu_device_altos_probe_bootloader (FuDeviceAltos *device, GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
g_auto(GStrv) lines = NULL;
|
|
g_autoptr(GString) str = NULL;
|
|
|
|
/* get tty for upload */
|
|
if (!fu_device_altos_find_tty (device, error))
|
|
return FALSE;
|
|
if (!fu_device_altos_tty_open (device, error))
|
|
return FALSE;
|
|
|
|
/* get the version information */
|
|
if (!fu_device_altos_tty_write (device, "v\n", -1, error))
|
|
return FALSE;
|
|
str = fu_device_altos_tty_read (device, 100, -1, error);
|
|
if (str == NULL) {
|
|
fu_device_altos_tty_close (device, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
/* parse each line */
|
|
lines = g_strsplit_set (str->str, "\n\r", -1);
|
|
for (guint i = 0; lines[i] != NULL; i++) {
|
|
|
|
/* ignore */
|
|
if (lines[i][0] == '\0')
|
|
continue;
|
|
if (g_str_has_prefix (lines[i], "manufacturer "))
|
|
continue;
|
|
if (g_str_has_prefix (lines[i], "product "))
|
|
continue;
|
|
|
|
/* we can flash firmware */
|
|
if (g_strcmp0 (lines[i], "altos-loader") == 0) {
|
|
fu_device_remove_flag (FU_DEVICE (device),
|
|
FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER);
|
|
continue;
|
|
}
|
|
|
|
/* version number */
|
|
if (g_str_has_prefix (lines[i], "software-version ")) {
|
|
fu_device_set_version (FU_DEVICE (device), lines[i] + 17);
|
|
continue;
|
|
}
|
|
|
|
/* address base and bound */
|
|
if (g_str_has_prefix (lines[i], "flash-range ")) {
|
|
g_auto(GStrv) addrs = g_strsplit (lines[i] + 17, " ", -1);
|
|
priv->addr_base = g_ascii_strtoull (addrs[0], NULL, 16);
|
|
priv->addr_bound = g_ascii_strtoull (addrs[1], NULL, 16);
|
|
g_debug ("base: %x, bound: %x",
|
|
(guint) priv->addr_base,
|
|
(guint) priv->addr_bound);
|
|
continue;
|
|
}
|
|
|
|
/* unknown line */
|
|
g_warning ("unknown data: '%s'", lines[i]);
|
|
}
|
|
|
|
/* done */
|
|
if (!fu_device_altos_tty_close (device, error))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
fu_device_altos_probe (FuDeviceAltos *device, GError **error)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
|
|
/* bootloader uses tty */
|
|
if (priv->kind == FU_DEVICE_ALTOS_KIND_BOOTLOADER)
|
|
return fu_device_altos_probe_bootloader (device, error);
|
|
|
|
/* get version */
|
|
if (priv->kind == FU_DEVICE_ALTOS_KIND_CHAOSKEY) {
|
|
const gchar *version_prefix = "ChaosKey-hw-1.0-sw-";
|
|
guint8 version_idx;
|
|
g_autofree gchar *version = NULL;
|
|
|
|
/* open */
|
|
if (!g_usb_device_open (priv->usb_device, error))
|
|
return FALSE;
|
|
|
|
/* get string */
|
|
version_idx = g_usb_device_get_product_index (priv->usb_device);
|
|
version = g_usb_device_get_string_descriptor (priv->usb_device,
|
|
version_idx,
|
|
error);
|
|
if (version == NULL) {
|
|
g_usb_device_close (priv->usb_device, NULL);
|
|
return FALSE;
|
|
}
|
|
if (!g_str_has_prefix (version, version_prefix)) {
|
|
g_set_error (error,
|
|
FWUPD_ERROR,
|
|
FWUPD_ERROR_NOT_SUPPORTED,
|
|
"not a ChaosKey v1.0 device: %s",
|
|
version);
|
|
g_usb_device_close (priv->usb_device, NULL);
|
|
return FALSE;
|
|
}
|
|
fu_device_set_version (FU_DEVICE (device), version + 19);
|
|
if (!g_usb_device_close (priv->usb_device, error))
|
|
return FALSE;
|
|
}
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
/* now with kind and usb_device set */
|
|
static void
|
|
fu_device_altos_init_real (FuDeviceAltos *device)
|
|
{
|
|
FuDeviceAltosPrivate *priv = GET_PRIVATE (device);
|
|
g_autofree gchar *devid1 = NULL;
|
|
|
|
/* allowed, but requires manual bootloader step */
|
|
fu_device_add_flag (FU_DEVICE (device),
|
|
FWUPD_DEVICE_FLAG_ALLOW_ONLINE);
|
|
|
|
/* set default vendor */
|
|
fu_device_set_vendor (FU_DEVICE (device), "altusmetrum.org");
|
|
|
|
/* set name */
|
|
switch (priv->kind) {
|
|
case FU_DEVICE_ALTOS_KIND_BOOTLOADER:
|
|
fu_device_set_name (FU_DEVICE (device), "Altos [bootloader]");
|
|
break;
|
|
case FU_DEVICE_ALTOS_KIND_CHAOSKEY:
|
|
fu_device_set_name (FU_DEVICE (device), "Altos ChaosKey");
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
/* add USB\VID_0000&PID_0000 */
|
|
devid1 = g_strdup_printf ("USB\\VID_%04X&PID_%04X",
|
|
g_usb_device_get_vid (priv->usb_device),
|
|
g_usb_device_get_pid (priv->usb_device));
|
|
fu_device_add_guid (FU_DEVICE (device), devid1);
|
|
g_debug ("saving runtime GUID of %s", devid1);
|
|
|
|
/* only the bootloader can do the update */
|
|
if (priv->kind != FU_DEVICE_ALTOS_KIND_BOOTLOADER) {
|
|
fu_device_add_flag (FU_DEVICE (device),
|
|
FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER);
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
guint16 vid;
|
|
guint16 pid;
|
|
FuDeviceAltosKind kind;
|
|
} FuDeviceAltosVidPid;
|
|
|
|
FuDeviceAltos *
|
|
fu_device_altos_new (GUsbDevice *usb_device)
|
|
{
|
|
FuDeviceAltos *device;
|
|
FuDeviceAltosPrivate *priv;
|
|
const FuDeviceAltosVidPid vidpids[] = {
|
|
{ 0xfffe, 0x000a, FU_DEVICE_ALTOS_KIND_BOOTLOADER },
|
|
{ 0x1d50, 0x60c6, FU_DEVICE_ALTOS_KIND_CHAOSKEY },
|
|
{ 0x0000, 0x0000, FU_DEVICE_ALTOS_KIND_UNKNOWN }
|
|
};
|
|
|
|
/* set kind */
|
|
for (guint j = 0; vidpids[j].vid != 0x0000; j++) {
|
|
if (g_usb_device_get_vid (usb_device) != vidpids[j].vid)
|
|
continue;
|
|
if (g_usb_device_get_pid (usb_device) != vidpids[j].pid)
|
|
continue;
|
|
device = g_object_new (FU_TYPE_DEVICE_ALTOS, NULL);
|
|
priv = GET_PRIVATE (device);
|
|
priv->kind = vidpids[j].kind;
|
|
priv->usb_device = g_object_ref (usb_device);
|
|
fu_device_altos_init_real (device);
|
|
return device;
|
|
}
|
|
return NULL;
|
|
}
|