/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* Qfu-firmware-update -- Command line tool to update firmware in QFU 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 .
*
* Copyright (C) 2016 Bjørn Mork
* Copyright (C) 2016 Zodiac Inflight Innovations
* Copyright (C) 2016-2017 Aleksander Morgado
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "qfu-log.h"
#include "qfu-qdl-message.h"
#include "qfu-dload-message.h"
#include "qfu-qdl-device.h"
#include "qfu-utils.h"
#include "qfu-enum-types.h"
static void initable_iface_init (GInitableIface *iface);
G_DEFINE_TYPE_EXTENDED (QfuQdlDevice, qfu_qdl_device, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
enum {
PROP_0,
PROP_FILE,
PROP_LAST
};
static GParamSpec *properties[PROP_LAST];
#define SECONDARY_BUFFER_DEFAULT_SIZE 512
#define MAX_PRINTABLE_SIZE 80
struct _QfuQdlDevicePrivate {
GFile *file;
gint fd;
guint qdl_version;
GByteArray *buffer;
GByteArray *secondary_buffer;
};
/******************************************************************************/
/* HDLC */
#define CONTROL 0x7e
#define ESCAPE 0x7d
#define MASK 0x20
static gsize
escape (const guint8 *in,
gsize inlen,
guint8 *out,
gsize outlen)
{
gsize i, j;
for (i = 0, j = 0; i < inlen; i++) {
/* Caller should give a big enough buffer */
g_assert ((j + 1) < outlen);
if (in[i] == CONTROL || in[i] == ESCAPE) {
out[j++] = ESCAPE;
out[j++] = in[i] ^ MASK;
} else
out[j++] = in[i];
}
return j;
}
static gsize
unescape (const guint8 *in,
gsize inlen,
guint8 *out,
gsize outlen)
{
gsize i, j = 0;
gboolean escaping = FALSE;
for (i = 0; i < inlen; i++) {
/* Caller should give a big enough buffer */
g_assert (j < outlen);
if (escaping) {
out[j++] = in[i] ^ MASK;
escaping = FALSE;
} else if (in[i] == ESCAPE) {
escaping = TRUE;
} else {
out[j++] = in[i];
}
}
return j;
}
/* copy a possibly escaped single byte to out */
static gsize
escape_byte (guint8 byte,
guint8 *out,
gsize outlen)
{
gsize j = 0;
if (byte == CONTROL || byte == ESCAPE) {
out[j++] = ESCAPE;
byte ^= MASK;
}
out[j++] = byte;
return j;
}
static gsize
hdlc_max_framed_size (gsize unframed_size)
{
/* 1 header byte, (2 * input size) bytes, 2 crc bytes and 1 trailing byte */
return 4 + (2 * unframed_size);
}
static gsize
hdlc_frame (const guint8 *in,
gsize inlen,
guint8 *out,
gsize outlen)
{
guint16 crc;
gsize j = 0;
out[j++] = CONTROL;
j += escape (in, inlen, &out[j], outlen - j);
crc = qfu_utils_crc16 (in, inlen);
j += escape_byte (crc & 0xff, &out[j], outlen - j);
j += escape_byte (crc >> 8 & 0xff, &out[j], outlen - j);
out[j++] = CONTROL;
return j;
}
static gsize
hdlc_max_unframed_size (gsize framed_size)
{
/* -1 header byte, -2 crc bytes and -1 trailing byte */
g_assert (framed_size > 3);
return framed_size - 3;
}
static gsize
hdlc_unframe (const guint8 *in,
gsize inlen,
guint8 *out,
gsize outlen,
GError **error)
{
guint16 crc;
gsize j, i = inlen;
/* the first control char is optional */
if (*in == CONTROL) {
in++;
i--;
}
if (in[i - 1] == CONTROL)
i--;
j = unescape (in, i, out, outlen);
if (j < 2) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"unescaping failed: too few bytes as output: %" G_GSIZE_FORMAT, j);
return 0;
}
j -= 2; /* remove the crc */
/* verify the crc */
crc = qfu_utils_crc16 (out, j);
if (crc != (out[j] | out[j + 1] << 8)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"crc check failed: 0x%04x != 0x%04x\n", crc, out[j] | out[j + 1] << 8);
return 0;
}
return j;
}
/******************************************************************************/
/* Send */
static gboolean
send_request (QfuQdlDevice *self,
const guint8 *request,
gsize request_size,
GCancellable *cancellable,
GError **error)
{
gssize wlen;
fd_set wr;
gint aux;
struct timeval tv = {
.tv_sec = 2,
.tv_usec = 0,
};
/* Wait for the fd to be writable and don't wait forever */
FD_ZERO (&wr);
FD_SET (self->priv->fd, &wr);
aux = select (self->priv->fd + 1, NULL, &wr, NULL, &tv);
if (g_cancellable_set_error_if_cancelled (cancellable, error))
return FALSE;
if (aux < 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"error waiting to write: %s",
g_strerror (errno));
return FALSE;
}
if (aux == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"timed out waiting to write");
return FALSE;
}
/* Debug output */
if (qfu_log_get_verbose ()) {
gchar *printable;
gsize printable_size = request_size;
gboolean shorted = FALSE;
if (printable_size > MAX_PRINTABLE_SIZE) {
printable_size = MAX_PRINTABLE_SIZE;
shorted = TRUE;
}
printable = qfu_utils_str_hex (request, printable_size, ':');
g_debug ("[qfu-qdl-device] >> %s%s [%" G_GSIZE_FORMAT "]", printable, shorted ? "..." : "", request_size);
g_free (printable);
}
wlen = write (self->priv->fd, request, request_size);
if (wlen < 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"error writting: %s",
g_strerror (errno));
return FALSE;
}
/* We treat EINTR as an error, so we also treat as an error if not all bytes
* were wlen */
if ((gsize)wlen != request_size) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"error writing: only %" G_GSSIZE_FORMAT "/%" G_GSIZE_FORMAT " bytes written",
wlen, request_size);
return FALSE;
}
return TRUE;
}
static gboolean
send_framed_request (QfuQdlDevice *self,
const guint8 *request,
gsize request_size,
GCancellable *cancellable,
GError **error)
{
gsize max_framed_size;
gsize framed_size;
/* Debug output */
if (qfu_log_get_verbose ()) {
gchar *printable;
gsize printable_size = request_size;
gboolean shorted = FALSE;
if (printable_size > MAX_PRINTABLE_SIZE) {
printable_size = MAX_PRINTABLE_SIZE;
shorted = TRUE;
}
printable = qfu_utils_str_hex (request, printable_size, ':');
g_debug ("[qfu-qdl-device] >> %s%s [%" G_GSIZE_FORMAT ", unframed]", printable, shorted ? "..." : "", request_size);
g_free (printable);
}
max_framed_size = hdlc_max_framed_size (request_size);
if (G_UNLIKELY (max_framed_size > self->priv->secondary_buffer->len))
g_byte_array_set_size (self->priv->secondary_buffer, max_framed_size);
/* Pack into an HDLC frame */
framed_size = hdlc_frame (request, request_size, self->priv->secondary_buffer->data, self->priv->secondary_buffer->len);
g_assert (framed_size > 0);
return send_request (self, self->priv->secondary_buffer->data, framed_size, cancellable, error);
}
/******************************************************************************/
/* Receive */
static gssize
receive_response (QfuQdlDevice *self,
guint timeout_secs,
guint8 **response,
GCancellable *cancellable,
GError **error)
{
fd_set rd;
struct timeval tv;
gint aux;
gssize rlen;
guint8 *end;
gssize frame_size;
gsize max_unframed_size;
gsize unframed_size;
/* Use requested timeout */
tv.tv_sec = timeout_secs;
tv.tv_usec = 0;
FD_ZERO (&rd);
FD_SET (self->priv->fd, &rd);
aux = select (self->priv->fd + 1, &rd, NULL, NULL, &tv);
if (g_cancellable_set_error_if_cancelled (cancellable, error))
return -1;
if (aux < 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"error waiting to read response: %s",
g_strerror (errno));
return -1;
}
if (aux == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"timed out waiting for the response");
return -1;
}
/* Receive in the primary buffer */
rlen = read (self->priv->fd, self->priv->buffer->data, self->priv->buffer->len);
if (rlen < 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"couldn't read response: %s",
g_strerror (errno));
return -1;
}
if (rlen == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"couldn't read response: HUP detected");
return -1;
}
/* Debug output */
if (qfu_log_get_verbose ()) {
gchar *printable;
gsize printable_size = rlen;
gboolean shorted = FALSE;
if (printable_size > MAX_PRINTABLE_SIZE) {
printable_size = MAX_PRINTABLE_SIZE;
shorted = TRUE;
}
printable = qfu_utils_str_hex (self->priv->buffer->data, printable_size, ':');
g_debug ("[qfu-qdl-device] << %s%s [%" G_GSIZE_FORMAT "]", printable, shorted ? "..." : "", rlen);
g_free (printable);
}
end = memchr (self->priv->buffer->data + 1, CONTROL, rlen - 1);
if (!end) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"HDLC trailing control character not found");
return -1;
}
frame_size = end - self->priv->buffer->data + 1;
g_assert (frame_size >= 0);
g_assert (frame_size <= rlen);
if (frame_size < 5) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"minimum HDLC frame size not received");
return -1;
}
if (frame_size < rlen)
g_debug ("[qfu-qdl-device] received %" G_GSSIZE_FORMAT " trailing bytes after HDLC frame (ignored)",
rlen - frame_size);
max_unframed_size = hdlc_max_unframed_size (frame_size);
if (G_UNLIKELY (max_unframed_size > self->priv->secondary_buffer->len))
g_byte_array_set_size (self->priv->secondary_buffer, max_unframed_size);
unframed_size = hdlc_unframe (self->priv->buffer->data, (gsize)frame_size, self->priv->secondary_buffer->data, self->priv->secondary_buffer->len, error);
if (unframed_size == 0) {
g_prefix_error (error, "error unframing message: ");
return -1;
}
/* Debug output */
if (qfu_log_get_verbose ()) {
gchar *printable;
gsize printable_size = unframed_size;
gboolean shorted = FALSE;
if (printable_size > MAX_PRINTABLE_SIZE) {
printable_size = MAX_PRINTABLE_SIZE;
shorted = TRUE;
}
printable = qfu_utils_str_hex (self->priv->secondary_buffer->data, printable_size, ':');
g_debug ("[qfu-qdl-device] << %s%s [%" G_GSIZE_FORMAT ", unframed]", printable, shorted ? "..." : "", unframed_size);
g_free (printable);
}
if (response)
*response = self->priv->secondary_buffer->data;
return unframed_size;
}
/******************************************************************************/
/* Send/receive */
static gssize
send_receive (QfuQdlDevice *self,
const guint8 *request,
gsize request_size,
gboolean request_framed,
guint response_timeout_secs,
guint8 **response,
GCancellable *cancellable,
GError **error)
{
gboolean sent;
if (self->priv->fd < 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "device is closed");
return FALSE;
}
if (request_framed)
sent = send_framed_request (self, request, request_size, cancellable, error);
else
sent = send_request (self, request, request_size, cancellable, error);
if (!sent)
return -1;
if (!response)
return 0;
return receive_response (self, response_timeout_secs, response, cancellable, error);
}
/******************************************************************************/
/******************************************************************************/
gboolean
qfu_qdl_device_ufopen (QfuQdlDevice *self,
QfuImage *image,
GCancellable *cancellable,
GError **error)
{
gssize reqlen;
gssize rsplen;
guint8 *rsp = NULL;
reqlen = qfu_qdl_request_ufopen_build (self->priv->buffer->data, self->priv->buffer->len, image, cancellable, error);
if (reqlen < 0)
return FALSE;
rsplen = send_receive (self, self->priv->buffer->data, reqlen, TRUE, 1, &rsp, cancellable, error);
if (rsplen < 0)
return FALSE;
switch (rsp[0]) {
case QFU_QDL_CMD_OPEN_UNFRAMED_RSP:
return qfu_qdl_response_ufopen_parse (rsp, rsplen, error);
case QFU_QDL_CMD_ERROR:
return qfu_qdl_response_error_parse (rsp, rsplen, error);
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"unexpected response received in ufopen: 0x%02x (%s)",
rsp[0], qfu_qdl_cmd_get_string (rsp[0]) ? qfu_qdl_cmd_get_string (rsp[0]) : "unknown");
return FALSE;
}
}
/******************************************************************************/
gboolean
qfu_qdl_device_ufwrite (QfuQdlDevice *self,
QfuImage *image,
guint16 sequence,
GCancellable *cancellable,
GError **error)
{
gssize reqlen;
gssize rsplen;
guint8 *rsp = NULL;
guint16 ack_sequence = 0;
reqlen = qfu_qdl_request_ufwrite_build (self->priv->buffer->data, self->priv->buffer->len, image, sequence, cancellable, error);
if (reqlen < 0)
return FALSE;
/* NOTE: the last chunk will require a long timeout, so just define the
* same one for all chunks */
rsplen = send_receive (self, self->priv->buffer->data, reqlen, FALSE, 120, &rsp, cancellable, error);
if (rsplen < 0)
return FALSE;
switch (rsp[0]) {
case QFU_QDL_CMD_WRITE_UNFRAMED_RSP:
if (!qfu_qdl_response_ufwrite_parse (rsp, rsplen, &ack_sequence, error))
return FALSE;
if (ack_sequence != sequence) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"received ack for chunk #%" G_GUINT16_FORMAT " instead of chunk #%" G_GUINT16_FORMAT,
ack_sequence, sequence);
return FALSE;
}
return TRUE;
case QFU_QDL_CMD_ERROR:
return qfu_qdl_response_error_parse (rsp, rsplen, error);
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"unexpected response received in ufwrite: 0x%02x (%s)",
rsp[0], qfu_qdl_cmd_get_string (rsp[0]) ? qfu_qdl_cmd_get_string (rsp[0]) : "unknown");
return FALSE;
}
}
/******************************************************************************/
gboolean
qfu_qdl_device_ufclose (QfuQdlDevice *self,
GCancellable *cancellable,
GError **error)
{
gsize reqlen;
gssize rsplen;
guint8 *rsp = NULL;
reqlen = qfu_qdl_request_ufclose_build (self->priv->buffer->data, self->priv->buffer->len);
rsplen = send_receive (self, self->priv->buffer->data, reqlen, TRUE, 1, &rsp, cancellable, error);
if (rsplen < 0)
return FALSE;
switch (rsp[0]) {
case QFU_QDL_CMD_CLOSE_UNFRAMED_RSP:
return qfu_qdl_response_ufclose_parse (rsp, rsplen, error);
case QFU_QDL_CMD_ERROR:
return qfu_qdl_response_error_parse (rsp, rsplen, error);
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"unexpected response received in ufclose: 0x%02x (%s)",
rsp[0], qfu_qdl_cmd_get_string (rsp[0]) ? qfu_qdl_cmd_get_string (rsp[0]) : "unknown");
return FALSE;
}
}
/******************************************************************************/
gboolean
qfu_qdl_device_hello (QfuQdlDevice *self,
GCancellable *cancellable,
GError **error)
{
gsize reqlen;
gssize rsplen;
guint8 *rsp = NULL;
g_assert (self->priv->qdl_version > 0);
/* If no error, we assume version is found */
reqlen = qfu_qdl_request_hello_build (self->priv->buffer->data, self->priv->buffer->len, self->priv->qdl_version, self->priv->qdl_version);
rsplen = send_receive (self, self->priv->buffer->data, reqlen, TRUE, 1, &rsp, cancellable, error);
if (rsplen < 0)
return FALSE;
switch (rsp[0]) {
case QFU_QDL_CMD_HELLO_RSP:
return qfu_qdl_response_hello_parse (rsp, rsplen, error);
case QFU_QDL_CMD_ERROR:
return qfu_qdl_response_error_parse (rsp, rsplen, error);
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"unexpected response received in hello: 0x%02x (%s)",
rsp[0], qfu_qdl_cmd_get_string (rsp[0]) ? qfu_qdl_cmd_get_string (rsp[0]) : "unknown");
return FALSE;
}
}
/******************************************************************************/
gboolean
qfu_qdl_device_reset (QfuQdlDevice *self,
GCancellable *cancellable,
GError **error)
{
gsize reqlen;
gssize rsplen;
if (self->priv->fd < 0)
return TRUE;
reqlen = qfu_qdl_request_reset_build (self->priv->buffer->data, self->priv->buffer->len);
rsplen = send_receive (self, self->priv->buffer->data, reqlen, TRUE, 0, NULL, cancellable, error);
/* Close device after a reset, even if we got an error */
close (self->priv->fd);
self->priv->fd = -1;
if (rsplen < 0)
return FALSE;
return TRUE;
}
/******************************************************************************/
static gboolean
qdl_device_dload_sdp (QfuQdlDevice *self,
GCancellable *cancellable,
GError **error)
{
gsize reqlen;
gssize rsplen;
guint8 *rsp = NULL;
reqlen = qfu_dload_request_sdp_build (self->priv->buffer->data, self->priv->buffer->len);
rsplen = send_receive (self, self->priv->buffer->data, reqlen, TRUE, 1, &rsp, cancellable, error);
if (rsplen < 0)
return FALSE;
switch (rsp[0]) {
case QFU_DLOAD_CMD_ACK:
return qfu_dload_response_ack_parse (rsp, rsplen, error);
case QFU_QDL_CMD_ERROR:
return qfu_qdl_response_error_parse (rsp, rsplen, error);
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"unexpected response received in dload sdp: 0x%02x", rsp[0]);
return FALSE;
}
return TRUE;
}
static gboolean
qdl_device_detect_version (QfuQdlDevice *self,
GCancellable *cancellable,
GError **error)
{
guint version;
#define MAX_VALID_VERSION 6
/* Attempt to probe supported protocol version
* Newer modems like Sierra Wireless MC7710 must use '6' for both fields
* Gobi2000 modems like HP un2420 must use '5' for both fields
* Gobi1000 modems must use '4' for both fields
*/
for (version = 4; version <= MAX_VALID_VERSION; version++) {
gsize reqlen;
gssize rsplen;
guint8 *rsp = NULL;
/* If no error, we assume version is found */
reqlen = qfu_qdl_request_hello_build (self->priv->buffer->data, self->priv->buffer->len, version, version);
rsplen = send_receive (self, self->priv->buffer->data, reqlen, TRUE, 1, &rsp, cancellable, error);
if (rsplen < 0)
return FALSE;
/* Break right away on a successful parse, so that we finish with the
* correct version tested */
if (qfu_qdl_response_hello_parse (rsp, rsplen, NULL))
break;
}
if (version > MAX_VALID_VERSION) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"couldn't detect QDL version");
return FALSE;
}
g_debug ("[qfu-qdl-device] QDL version detected: %u", version);
self->priv->qdl_version = version;
return TRUE;
}
static gboolean
initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
QfuQdlDevice *self;
struct termios terminal_data;
gchar *path = NULL;
GError *inner_error = NULL;
self = QFU_QDL_DEVICE (initable);
if (g_cancellable_set_error_if_cancelled (cancellable, &inner_error))
goto out;
path = g_file_get_path (self->priv->file);
g_debug ("[qfu-qdl-device] opening TTY: %s", path);
self->priv->fd = open (path, O_RDWR | O_NOCTTY);
if (self->priv->fd < 0) {
inner_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
"error opening serial device: %s",
g_strerror (errno));
goto out;
}
g_debug ("[qfu-qdl-device] setting terminal in raw mode...");
if (tcgetattr (self->priv->fd, &terminal_data) < 0) {
inner_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
"error getting serial port attributes: %s",
g_strerror (errno));
goto out;
}
cfmakeraw (&terminal_data);
if (tcsetattr (self->priv->fd, TCSANOW, &terminal_data) < 0) {
inner_error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
"error setting serial port attributes: %s",
g_strerror (errno));
goto out;
}
if (!qdl_device_dload_sdp (self, cancellable, &inner_error)) {
if (!g_error_matches (inner_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
goto out;
g_debug ("[qfu-qdl-device] error (ignored): DLOAD SDP not supported");
g_clear_error (&inner_error);
}
if (!qdl_device_detect_version (self, cancellable, &inner_error))
goto out;
out:
g_free (path);
if (inner_error) {
if (!(self->priv->fd < 0)) {
close (self->priv->fd);
self->priv->fd = -1;
}
g_propagate_error (error, inner_error);
return FALSE;
}
return TRUE;
}
/******************************************************************************/
QfuQdlDevice *
qfu_qdl_device_new (GFile *file,
GCancellable *cancellable,
GError **error)
{
g_return_val_if_fail (G_IS_FILE (file), NULL);
return QFU_QDL_DEVICE (g_initable_new (QFU_TYPE_QDL_DEVICE,
cancellable,
error,
"file", file,
NULL));
}
static void
qfu_qdl_device_init (QfuQdlDevice *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_QDL_DEVICE, QfuQdlDevicePrivate);
self->priv->fd = -1;
/* Long buffer for I/O */
self->priv->buffer = g_byte_array_new ();
g_byte_array_set_size (self->priv->buffer, QFU_QDL_MESSAGE_MAX_SIZE);
/* Shorter secondary buffer for framing/unframing */
self->priv->secondary_buffer = g_byte_array_new ();
g_byte_array_set_size (self->priv->secondary_buffer, SECONDARY_BUFFER_DEFAULT_SIZE);
}
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
QfuQdlDevice *self = QFU_QDL_DEVICE (object);
switch (prop_id) {
case PROP_FILE:
self->priv->file = g_value_dup_object (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)
{
QfuQdlDevice *self = QFU_QDL_DEVICE (object);
switch (prop_id) {
case PROP_FILE:
g_value_set_object (value, self->priv->file);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
dispose (GObject *object)
{
QfuQdlDevice *self = QFU_QDL_DEVICE (object);
if (!(self->priv->fd < 0)) {
close (self->priv->fd);
self->priv->fd = -1;
}
g_clear_pointer (&self->priv->buffer, g_byte_array_unref);
g_clear_pointer (&self->priv->secondary_buffer, g_byte_array_unref);
g_clear_object (&self->priv->file);
G_OBJECT_CLASS (qfu_qdl_device_parent_class)->dispose (object);
}
static void
initable_iface_init (GInitableIface *iface)
{
iface->init = initable_init;
}
static void
qfu_qdl_device_class_init (QfuQdlDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (QfuQdlDevicePrivate));
object_class->dispose = dispose;
object_class->get_property = get_property;
object_class->set_property = set_property;
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]);
}