diff options
author | Aleksander Morgado <aleksander@aleksander.es> | 2016-11-29 15:08:22 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2017-01-16 11:24:12 +0100 |
commit | f535ed61f62efb17c977a812ef4688db622712b8 (patch) | |
tree | d1ad9b5f26705167a20aca8fa74677982dddc096 | |
parent | 48e069c50a7e7cd7241c6bc412538e627ecf2ce1 (diff) | |
download | external_libqmi-f535ed61f62efb17c977a812ef4688db622712b8.zip external_libqmi-f535ed61f62efb17c977a812ef4688db622712b8.tar.gz external_libqmi-f535ed61f62efb17c977a812ef4688db622712b8.tar.bz2 |
qmi-firmware-update: new QfuImage and QfuImageCwe objects
Implement objects to handle the firmware images.
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | src/qmi-firmware-update/Makefile.am | 32 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-image-cwe.c | 246 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-image-cwe.h | 64 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-image-factory.c | 73 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-image-factory.h | 40 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-image.c | 387 | ||||
-rw-r--r-- | src/qmi-firmware-update/qfu-image.h | 110 |
8 files changed, 954 insertions, 0 deletions
@@ -77,3 +77,5 @@ Makefile.in /utils/qmi-network /utils/swi-update + +/src/qmi-firmware-update/qfu-enum-types.* diff --git a/src/qmi-firmware-update/Makefile.am b/src/qmi-firmware-update/Makefile.am index acbca21..1bbca27 100644 --- a/src/qmi-firmware-update/Makefile.am +++ b/src/qmi-firmware-update/Makefile.am @@ -11,11 +11,39 @@ qmi_firmware_update_CPPFLAGS = \ -I$(top_builddir)/src/libqmi-glib/generated \ $(NULL) +ENUMS = \ + qfu-image.h \ + $(NULL) + +ENUMS_GENERATED = \ + qfu-enum-types.h qfu-enum-types.c \ + $(NULL) + +qfu-enum-types.h: Makefile.am $(ENUMS) $(top_srcdir)/build-aux/templates/qmi-enum-types-template.h + $(AM_V_GEN) $(GLIB_MKENUMS) \ + --fhead "#ifndef QFU_ENUM_TYPES_H\n#define QFU_ENUM_TYPES_H\n#include \"qfu-image.h\"\n" \ + --template $(top_srcdir)/build-aux/templates/qmi-enum-types-template.h \ + --ftail "#endif /* __QFUENUM_TYPES_H__ */\n" \ + $(ENUMS) > $@ + +qfu-enum-types.c: $(ENUMS) qfu-enum-types.h $(top_srcdir)/build-aux/templates/qmi-enum-types-template.c + $(AM_V_GEN) $(GLIB_MKENUMS) \ + --fhead "#include \"qfu-enum-types.h\"\n" \ + --template $(top_srcdir)/build-aux/templates/qmi-enum-types-template.c \ + $(ENUMS) > $@ + +nodist_qmi_firmware_update_SOURCES = \ + $(ENUMS_GENERATED) \ + $(NULL) + qmi_firmware_update_SOURCES = \ qfu-main.c \ qfu-updater.h qfu-updater.c \ qfu-udev-helpers.h qfu-udev-helpers.c \ qfu-download-helpers.h qfu-download-helpers.c \ + qfu-image.h qfu-image.c \ + qfu-image-cwe.h qfu-image-cwe.c \ + qfu-image-factory.h qfu-image-factory.c \ $(NULL) qmi_firmware_update_LDADD = \ @@ -23,3 +51,7 @@ qmi_firmware_update_LDADD = \ $(GLIB_LIBS) \ $(top_builddir)/src/libqmi-glib/libqmi-glib.la \ $(NULL) + +# Request to build enum types before anything else +BUILT_SOURCES = $(ENUMS_GENERATED) +CLEANFILES = $(ENUMS_GENERATED) diff --git a/src/qmi-firmware-update/qfu-image-cwe.c b/src/qmi-firmware-update/qfu-image-cwe.c new file mode 100644 index 0000000..5f61f05 --- /dev/null +++ b/src/qmi-firmware-update/qfu-image-cwe.c @@ -0,0 +1,246 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no> + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include <string.h> +#include "qfu-image-cwe.h" + +static GInitableIface *iface_initable_parent; +static void initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_EXTENDED (QfuImageCwe, qfu_image_cwe, QFU_TYPE_IMAGE, 0, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)) + +/* Sierra Wireless CWE file header + * Note: 32bit numbers are big endian + */ +typedef struct _QfuCweFileHeader QfuCweFileHeader; +struct _QfuCweFileHeader { + gchar reserved1[256]; + guint32 crc; /* 32bit CRC of "reserved1" field */ + guint32 rev; /* header revision */ + guint32 val; /* CRC validity indicator */ + gchar type[4]; /* ASCII - not null terminated */ + gchar product[4]; /* ASCII - not null terminated */ + guint32 imgsize; /* image size */ + guint32 imgcrc; /* 32bit CRC of the image */ + gchar version[84]; /* ASCII - null terminated */ + gchar date[8]; /* ASCII - null terminated */ + guint32 compat; /* backward compatibility */ + gchar reserved2[20]; +} __attribute__ ((packed)); + +struct _QfuImageCwePrivate { + QfuCweFileHeader hdr; + gchar *type; + gchar *product; +}; + +/******************************************************************************/ + +const gchar * +qfu_image_cwe_header_get_type (QfuImageCwe *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL); + + return self->priv->type; +} + +const gchar * +qfu_image_cwe_header_get_product (QfuImageCwe *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL); + + return self->priv->product; +} + +const gchar * +qfu_image_cwe_header_get_version (QfuImageCwe *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL); + + return self->priv->hdr.version; +} + +const gchar * +qfu_image_cwe_header_get_date (QfuImageCwe *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL); + + return self->priv->hdr.date; +} + +/******************************************************************************/ + +static goffset +get_header_size (QfuImage *self) +{ + return (goffset) sizeof (QfuCweFileHeader); +} + +static goffset +get_data_size (QfuImage *self) +{ + return qfu_image_get_size (self) - sizeof (QfuCweFileHeader); +} + +static gssize +read_header (QfuImage *_self, + guint8 *out_buffer, + gsize out_buffer_size, + GCancellable *cancellable, + GError **error) +{ + QfuImageCwe *self = QFU_IMAGE_CWE (_self); + + if (out_buffer_size < sizeof (QfuCweFileHeader)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "buffer too small to read header"); + return -1; + } + + memcpy (out_buffer, &self->priv->hdr, sizeof (QfuCweFileHeader)); + return sizeof (QfuCweFileHeader); +} + +/******************************************************************************/ + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + QfuImageCwe *self; + GInputStream *input_stream = NULL; + gboolean result = FALSE; + gssize n_read; + + self = QFU_IMAGE_CWE (initable); + + /* Run parent initable */ + if (!iface_initable_parent->init (initable, cancellable, error)) + return FALSE; + + g_object_get (self, "input-stream", &input_stream, NULL); + g_assert (G_IS_FILE_INPUT_STREAM (input_stream)); + + /* Preload file header */ + + g_debug ("[qfu-image-cwe] reading file header"); + + if (!g_seekable_seek (G_SEEKABLE (input_stream), 0, G_SEEK_SET, cancellable, error)) { + g_prefix_error (error, "couldn't seek input stream: "); + goto out; + } + + n_read = g_input_stream_read (input_stream, + &self->priv->hdr, + sizeof (QfuCweFileHeader), + cancellable, + error); + if (n_read < 0) { + g_prefix_error (error, "couldn't read file header: "); + goto out; + } + + if (n_read != sizeof (QfuCweFileHeader)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "CWE firmware image file is too short: full header not available"); + goto out; + } + + /* Preload non-NUL terminated strings */ + self->priv->type = g_strndup (self->priv->hdr.type, sizeof (self->priv->hdr.type)); + self->priv->product = g_strndup (self->priv->hdr.product, sizeof (self->priv->hdr.product)); + + g_debug ("[qfu-image-cwe] validating data size..."); + + /* Validate image size as reported in the header */ + if (qfu_image_get_data_size (QFU_IMAGE (self)) != GUINT32_FROM_BE (self->priv->hdr.imgsize)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "CWE firmware image file is too short: full data not available"); + goto out; + } + + /* Success! */ + result = TRUE; + +out: + g_object_unref (input_stream); + return result; +} + +/******************************************************************************/ + +QfuImage * +qfu_image_cwe_new (GFile *file, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + + return QFU_IMAGE (g_initable_new (QFU_TYPE_IMAGE_CWE, + cancellable, + error, + "file", file, + "image-type", QFU_IMAGE_TYPE_CWE, + NULL)); +} + +static void +qfu_image_cwe_init (QfuImageCwe *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_IMAGE_CWE, QfuImageCwePrivate); +} + +static void +finalize (GObject *object) +{ + QfuImageCwe *self = QFU_IMAGE_CWE (object); + + g_free (self->priv->type); + g_free (self->priv->product); + + G_OBJECT_CLASS (qfu_image_cwe_parent_class)->finalize (object); +} + +static void +initable_iface_init (GInitableIface *iface) +{ + iface_initable_parent = g_type_interface_peek_parent (iface); + iface->init = initable_init; +} + +static void +qfu_image_cwe_class_init (QfuImageCweClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + QfuImageClass *image_class = QFU_IMAGE_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (QfuImageCwePrivate)); + + object_class->finalize = finalize; + + image_class->get_header_size = get_header_size; + image_class->get_data_size = get_data_size; + image_class->read_header = read_header; + +} diff --git a/src/qmi-firmware-update/qfu-image-cwe.h b/src/qmi-firmware-update/qfu-image-cwe.h new file mode 100644 index 0000000..06bd424 --- /dev/null +++ b/src/qmi-firmware-update/qfu-image-cwe.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no> + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QFU_IMAGE_CWE_H +#define QFU_IMAGE_CWE_H + +#include <glib-object.h> +#include <gio/gio.h> + +#include "qfu-image.h" + +G_BEGIN_DECLS + +#define QFU_TYPE_IMAGE_CWE (qfu_image_cwe_get_type ()) +#define QFU_IMAGE_CWE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QFU_TYPE_IMAGE_CWE, QfuImageCwe)) +#define QFU_IMAGE_CWE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QFU_TYPE_IMAGE_CWE, QfuImageCweClass)) +#define QFU_IS_IMAGE_CWE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QFU_TYPE_IMAGE_CWE)) +#define QFU_IS_IMAGE_CWE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QFU_TYPE_IMAGE_CWE)) +#define QFU_IMAGE_CWE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QFU_TYPE_IMAGE_CWE, QfuImageCweClass)) + +typedef struct _QfuImageCwe QfuImageCwe; +typedef struct _QfuImageCweClass QfuImageCweClass; +typedef struct _QfuImageCwePrivate QfuImageCwePrivate; + +struct _QfuImageCwe { + QfuImage parent; + QfuImageCwePrivate *priv; +}; + +struct _QfuImageCweClass { + QfuImageClass parent; +}; + +GType qfu_image_cwe_get_type (void); +QfuImage *qfu_image_cwe_new (GFile *file, + GCancellable *cancellable, + GError **error); +const gchar *qfu_image_cwe_header_get_type (QfuImageCwe *self); +const gchar *qfu_image_cwe_header_get_product (QfuImageCwe *self); +const gchar *qfu_image_cwe_header_get_version (QfuImageCwe *self); +const gchar *qfu_image_cwe_header_get_date (QfuImageCwe *self); + +G_END_DECLS + +#endif /* QFU_IMAGE_CWE_H */ diff --git a/src/qmi-firmware-update/qfu-image-factory.c b/src/qmi-firmware-update/qfu-image-factory.c new file mode 100644 index 0000000..1ee7b6d --- /dev/null +++ b/src/qmi-firmware-update/qfu-image-factory.c @@ -0,0 +1,73 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no> + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "qfu-image-factory.h" +#include "qfu-image.h" +#include "qfu-image-cwe.h" + +QfuImage * +qfu_image_factory_build (GFile *file, + GCancellable *cancellable, + GError **error) +{ + gchar *basename; + QfuImageType image_type; + QfuImage *image = NULL; + + g_assert (G_IS_FILE (file)); + basename = g_file_get_basename (file); + + /* guessing image type based on the well known Gobi 1k and 2k + * filenames, and assumes anything else is a CWE image + * + * This is based on the types in gobi-loader's snooped magic strings: + * 0x05 => "amss.mbn" + * 0x06 => "apps.mbn" + * 0x0d => "uqcn.mbn" (Gobi 2000 only) + */ + if (g_ascii_strcasecmp (basename, "amss.mbn") == 0) + image_type = QFU_IMAGE_TYPE_AMSS_MODEM; + else if (g_ascii_strcasecmp (basename, "apps.mbn") == 0) + image_type = QFU_IMAGE_TYPE_AMSS_APPLICATION; + else if (g_ascii_strcasecmp (basename, "uqcn.mbn") == 0) + image_type = QFU_IMAGE_TYPE_AMSS_UQCN; + else + /* Note: should check validity of the image as this is the one used as + * default fallback */ + image_type = QFU_IMAGE_TYPE_CWE; + + switch (image_type) { + case QFU_IMAGE_TYPE_AMSS_MODEM: + case QFU_IMAGE_TYPE_AMSS_APPLICATION: + case QFU_IMAGE_TYPE_AMSS_UQCN: + image = qfu_image_new (file, image_type, cancellable, error); + break; + case QFU_IMAGE_TYPE_CWE: + image = qfu_image_cwe_new (file, cancellable, error); + break; + default: + g_assert_not_reached (); + } + + g_free (basename); + return image; +} diff --git a/src/qmi-firmware-update/qfu-image-factory.h b/src/qmi-firmware-update/qfu-image-factory.h new file mode 100644 index 0000000..6ca1cd8 --- /dev/null +++ b/src/qmi-firmware-update/qfu-image-factory.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no> + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QFU_IMAGE_FACTORY_H +#define QFU_IMAGE_FACTORY_H + +#include <glib-object.h> +#include <gio/gio.h> + +#include "qfu-image.h" +#include "qfu-image-cwe.h" + +G_BEGIN_DECLS + +QfuImage *qfu_image_factory_build (GFile *file, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* QFU_IMAGE_H */ diff --git a/src/qmi-firmware-update/qfu-image.c b/src/qmi-firmware-update/qfu-image.c new file mode 100644 index 0000000..830c945 --- /dev/null +++ b/src/qmi-firmware-update/qfu-image.c @@ -0,0 +1,387 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no> + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#include "qfu-image.h" +#include "qfu-enum-types.h" + +static void initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_EXTENDED (QfuImage, qfu_image, G_TYPE_OBJECT, 0, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)) + +enum { + PROP_0, + PROP_FILE, + PROP_IMAGE_TYPE, + PROP_INPUT_STREAM, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _QfuImagePrivate { + QfuImageType image_type; + GFile *file; + GFileInfo *info; + GInputStream *input_stream; +}; + +/******************************************************************************/ + +gsize +qfu_image_get_data_chunk_size (QfuImage *self, + guint16 chunk_i) +{ + gssize chunk_size = QFU_IMAGE_CHUNK_SIZE; + guint n_chunks; + + n_chunks = qfu_image_get_n_data_chunks (self); + if (chunk_i == (n_chunks - 1)) { + chunk_size = qfu_image_get_data_size (self) - (chunk_i * QFU_IMAGE_CHUNK_SIZE); + g_assert (chunk_size > 0); + } else + chunk_size = QFU_IMAGE_CHUNK_SIZE; + + return chunk_size; +} + +gssize +qfu_image_read_data_chunk (QfuImage *self, + guint16 chunk_i, + guint8 *out_buffer, + gsize out_buffer_size, + GCancellable *cancellable, + GError **error) +{ + gssize chunk_size; + guint n_chunks; + goffset chunk_offset; + gssize n_read; + + g_return_val_if_fail (QFU_IS_IMAGE (self), -1); + + g_debug ("[qfu-image] reading chunk #%u", chunk_i); + + n_chunks = qfu_image_get_n_data_chunks (self); + if (chunk_i >= n_chunks) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "invalid chunk index %u", chunk_i); + return -1; + } + + /* Last chunk may be shorter than the default */ + chunk_size = qfu_image_get_data_chunk_size (self, chunk_i); + g_debug ("[qfu-image] chunk #%u size: %" G_GSSIZE_FORMAT " bytes", chunk_i, chunk_size); + + /* Make sure there's enough room */ + if (out_buffer_size < chunk_size) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "buffer too small to fit chunk size: %" G_GSIZE_FORMAT, chunk_size); + return -1; + } + + /* Compute chunk offset */ + chunk_offset = qfu_image_get_header_size (self) + (chunk_i * QFU_IMAGE_CHUNK_SIZE); + g_debug ("[qfu-image] chunk #%u offset: %" G_GOFFSET_FORMAT " bytes", chunk_i, chunk_offset); + + /* Seek to the correct place: note that this is likely a noop if already in that offset */ + if (!g_seekable_seek (G_SEEKABLE (self->priv->input_stream), chunk_offset, G_SEEK_SET, cancellable, error)) { + g_prefix_error (error, "couldn't seek input stream: "); + return -1; + } + + /* Read full chunk */ + n_read = g_input_stream_read (self->priv->input_stream, + out_buffer, + chunk_size, + cancellable, + error); + if (n_read < 0) { + g_prefix_error (error, "couldn't read chunk %u", chunk_i); + return -1; + } + + if (n_read != chunk_size) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "couldn't read expected chunk %u size: %" G_GSSIZE_FORMAT " (%" G_GSSIZE_FORMAT " bytes read)", + chunk_i, chunk_size, n_read); + return -1; + } + + g_debug ("[qfu-image] chunk #%u successfully read", chunk_i); + + return chunk_size; +} + +/******************************************************************************/ + +QfuImageType +qfu_image_get_image_type (QfuImage *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE (self), QFU_IMAGE_TYPE_UNKNOWN); + + return self->priv->image_type; +} + +const gchar * +qfu_image_get_display_name (QfuImage *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE (self), NULL); + + return g_file_info_get_display_name (self->priv->info); +} + +goffset +qfu_image_get_size (QfuImage *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE (self), 0); + + return g_file_info_get_size (self->priv->info); +} + +goffset +qfu_image_get_header_size (QfuImage *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE (self), 0); + + return QFU_IMAGE_GET_CLASS (self)->get_header_size (self); +} + +gssize +qfu_image_read_header (QfuImage *self, + guint8 *out_buffer, + gsize out_buffer_size, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (QFU_IS_IMAGE (self), 0); + + return (QFU_IMAGE_GET_CLASS (self)->read_header ? + QFU_IMAGE_GET_CLASS (self)->read_header (self, out_buffer, out_buffer_size, cancellable, error) : + 0); +} + +goffset +qfu_image_get_data_size (QfuImage *self) +{ + g_return_val_if_fail (QFU_IS_IMAGE (self), 0); + + return QFU_IMAGE_GET_CLASS (self)->get_data_size (self); +} + +guint16 +qfu_image_get_n_data_chunks (QfuImage *self) +{ + goffset data_size; + + data_size = qfu_image_get_data_size (self); + g_assert (data_size <= ((goffset) G_MAXUINT16 * (goffset) QFU_IMAGE_CHUNK_SIZE)); + + return (guint16) (data_size / QFU_IMAGE_CHUNK_SIZE) + !!(data_size % QFU_IMAGE_CHUNK_SIZE); +} + +/******************************************************************************/ + +static goffset +get_header_size (QfuImage *self) +{ + return 0; +} + +static goffset +get_data_size (QfuImage *self) +{ + goffset file_size; + + file_size = qfu_image_get_size (self); + + /* some image types contain trailing garbage - from gobi-loader */ + if (self->priv->image_type == QFU_IMAGE_TYPE_AMSS_MODEM) + file_size -= 8; + + return file_size; +} + +/******************************************************************************/ + +static gboolean +initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + QfuImage *self; + + self = QFU_IMAGE (initable); + g_assert (self->priv->file); + + /* Load file info */ + g_debug ("[qfu-image] loading file info..."); + self->priv->info = g_file_query_info (self->priv->file, + G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + cancellable, + error); + if (!self->priv->info) + return FALSE; + + /* Check minimum file size */ + if (qfu_image_get_size (self) < qfu_image_get_header_size (self)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "image is too short"); + return FALSE; + } + + /* Open file for reading. Kept open while the input stream reference is valid. */ + g_debug ("[qfu-image] opening file for reading..."); + self->priv->input_stream = G_INPUT_STREAM (g_file_read (self->priv->file, cancellable, error)); + if (!self->priv->input_stream) + return FALSE; + + return TRUE; +} + +/******************************************************************************/ + +QfuImage * +qfu_image_new (GFile *file, + QfuImageType image_type, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (G_IS_FILE (file), NULL); + + return QFU_IMAGE (g_initable_new (QFU_TYPE_IMAGE, + cancellable, + error, + "file", file, + "image-type", image_type, + NULL)); +} + +static void +qfu_image_init (QfuImage *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_IMAGE, QfuImagePrivate); + self->priv->image_type = QFU_IMAGE_TYPE_UNKNOWN; +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + QfuImage *self = QFU_IMAGE (object); + + switch (prop_id) { + case PROP_FILE: + self->priv->file = g_value_dup_object (value); + break; + case PROP_IMAGE_TYPE: + self->priv->image_type = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + QfuImage *self = QFU_IMAGE (object); + + switch (prop_id) { + case PROP_FILE: + g_value_set_object (value, self->priv->file); + break; + case PROP_IMAGE_TYPE: + g_value_set_enum (value, self->priv->image_type); + break; + case PROP_INPUT_STREAM: + g_value_set_object (value, self->priv->input_stream); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + QfuImage *self = QFU_IMAGE (object); + + g_clear_object (&self->priv->input_stream); + g_clear_object (&self->priv->info); + g_clear_object (&self->priv->file); + + G_OBJECT_CLASS (qfu_image_parent_class)->dispose (object); +} + +static void +initable_iface_init (GInitableIface *iface) +{ + iface->init = initable_init; +} + +static void +qfu_image_class_init (QfuImageClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (QfuImagePrivate)); + + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + klass->get_header_size = get_header_size; + klass->get_data_size = get_data_size; + + properties[PROP_FILE] = + g_param_spec_object ("file", + "File", + "File object", + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_FILE, properties[PROP_FILE]); + + properties[PROP_IMAGE_TYPE] = + g_param_spec_enum ("image-type", + "Image type", + "Type of firmware image", + QFU_TYPE_IMAGE_TYPE, + QFU_IMAGE_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_IMAGE_TYPE, properties[PROP_IMAGE_TYPE]); + + properties[PROP_INPUT_STREAM] = + g_param_spec_object ("input-stream", + "Input stream", + "Input stream object", + G_TYPE_FILE_INPUT_STREAM, + G_PARAM_READABLE); + g_object_class_install_property (object_class, PROP_INPUT_STREAM, properties[PROP_INPUT_STREAM]); +} diff --git a/src/qmi-firmware-update/qfu-image.h b/src/qmi-firmware-update/qfu-image.h new file mode 100644 index 0000000..8bd0304 --- /dev/null +++ b/src/qmi-firmware-update/qfu-image.h @@ -0,0 +1,110 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * qmi-firmware-update -- Command line tool to update firmware in QMI devices + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no> + * Copyright (C) 2016 Zodiac Inflight Innovations + * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es> + */ + +#ifndef QFU_IMAGE_H +#define QFU_IMAGE_H + +#include <glib-object.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +/* Most of these origin from GobiAPI_1.0.40/Core/QDLEnum.h + * + * The gobi-loader's snooped magic strings use types + * 0x05 => "amss.mbn" + * 0x06 => "apps.mbn" + * 0x0d => "uqcn.mbn" (Gobi 2000 only) + * with no file header data + * + * The 0x80 type is snooped from the Sierra Wireless firmware + * uploaders, using 400 bytes file header data + */ +typedef enum { + QFU_IMAGE_TYPE_UNKNOWN = 0x00, + QFU_IMAGE_TYPE_AMSS_MODEM = 0x05, + QFU_IMAGE_TYPE_AMSS_APPLICATION = 0x06, + QFU_IMAGE_TYPE_AMSS_UQCN = 0x0d, + QFU_IMAGE_TYPE_DBL = 0x0f, + QFU_IMAGE_TYPE_OSBL = 0x10, + QFU_IMAGE_TYPE_CWE = 0x80, +} QfuImageType; + +/* Default chunk size */ +#define QFU_IMAGE_CHUNK_SIZE (1024 * 1024) + +#define QFU_TYPE_IMAGE (qfu_image_get_type ()) +#define QFU_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QFU_TYPE_IMAGE, QfuImage)) +#define QFU_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QFU_TYPE_IMAGE, QfuImageClass)) +#define QFU_IS_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QFU_TYPE_IMAGE)) +#define QFU_IS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QFU_TYPE_IMAGE)) +#define QFU_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QFU_TYPE_IMAGE, QfuImageClass)) + +typedef struct _QfuImage QfuImage; +typedef struct _QfuImageClass QfuImageClass; +typedef struct _QfuImagePrivate QfuImagePrivate; + +struct _QfuImage { + GObject parent; + QfuImagePrivate *priv; +}; + +struct _QfuImageClass { + GObjectClass parent; + + goffset (* get_header_size) (QfuImage *self); + gssize (* read_header) (QfuImage *self, + guint8 *out_buffer, + gsize out_buffer_size, + GCancellable *cancellable, + GError **error); + goffset (* get_data_size) (QfuImage *self); +}; + +GType qfu_image_get_type (void); +QfuImage *qfu_image_new (GFile *file, + QfuImageType image_type, + GCancellable *cancellable, + GError **error); +QfuImageType qfu_image_get_image_type (QfuImage *self); +const gchar *qfu_image_get_display_name (QfuImage *self); +goffset qfu_image_get_size (QfuImage *self); +goffset qfu_image_get_header_size (QfuImage *self); +gssize qfu_image_read_header (QfuImage *self, + guint8 *out_buffer, + gsize out_buffer_size, + GCancellable *cancellable, + GError **error); +goffset qfu_image_get_data_size (QfuImage *self); +guint16 qfu_image_get_n_data_chunks (QfuImage *self); +gsize qfu_image_get_data_chunk_size (QfuImage *self, + guint16 chunk_i); +gssize qfu_image_read_data_chunk (QfuImage *self, + guint16 chunk_i, + guint8 *out_buffer, + gsize out_buffer_size, + GCancellable *cancellable, + GError **error); + +G_END_DECLS + +#endif /* QFU_IMAGE_H */ |