summaryrefslogtreecommitdiff
path: root/libqmi-glib/qmi-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'libqmi-glib/qmi-device.c')
-rw-r--r--libqmi-glib/qmi-device.c1812
1 files changed, 1812 insertions, 0 deletions
diff --git a/libqmi-glib/qmi-device.c b/libqmi-glib/qmi-device.c
new file mode 100644
index 0000000..170795d
--- /dev/null
+++ b/libqmi-glib/qmi-device.c
@@ -0,0 +1,1812 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libqmi-glib -- GLib/GIO based library to control QMI devices
+ *
+ * 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 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.
+ *
+ * Copyright (C) 2012 Aleksander Morgado <aleksander@lanedo.com>
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <gio/gio.h>
+
+#include "qmi-device.h"
+#include "qmi-message.h"
+#include "qmi-ctl.h"
+#include "qmi-dms.h"
+#include "qmi-wds.h"
+#include "qmi-utils.h"
+#include "qmi-error-types.h"
+#include "qmi-enum-types.h"
+
+static void async_initable_iface_init (GAsyncInitableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (QmiDevice, qmi_device, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init))
+
+enum {
+ PROP_0,
+ PROP_FILE,
+ PROP_CLIENT_CTL,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _QmiDevicePrivate {
+ /* File */
+ GFile *file;
+ gchar *path;
+ gchar *path_display;
+
+ /* Implicit CTL client */
+ QmiClientCtl *client_ctl;
+
+ /* Supported services */
+ GArray *supported_services;
+
+ /* I/O channel, set when the file is open */
+ GIOChannel *iochannel;
+ guint watch_id;
+ GByteArray *response;
+
+ /* HT to keep track of ongoing transactions */
+ GHashTable *transactions;
+
+ /* HT of clients that want to get indications */
+ GHashTable *registered_clients;
+};
+
+#define BUFFER_SIZE 2048
+
+/*****************************************************************************/
+/* Message transactions (private) */
+
+typedef struct {
+ QmiMessage *message;
+ GSimpleAsyncResult *result;
+ guint timeout_id;
+} Transaction;
+
+static Transaction *
+transaction_new (QmiDevice *self,
+ QmiMessage *message,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Transaction *tr;
+
+ tr = g_slice_new0 (Transaction);
+ tr->message = qmi_message_ref (message);
+ tr->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ transaction_new);
+
+ return tr;
+}
+
+static void
+transaction_complete_and_free (Transaction *tr,
+ QmiMessage *reply,
+ const GError *error)
+{
+ g_assert (reply != NULL || error != NULL);
+
+ if (tr->timeout_id)
+ g_source_remove (tr->timeout_id);
+
+ if (reply)
+ g_simple_async_result_set_op_res_gpointer (tr->result,
+ qmi_message_ref (reply),
+ (GDestroyNotify)qmi_message_unref);
+ else
+ g_simple_async_result_set_from_error (tr->result, error);
+
+ g_simple_async_result_complete_in_idle (tr->result);
+ g_object_unref (tr->result);
+ qmi_message_unref (tr->message);
+ g_slice_free (Transaction, tr);
+}
+
+static inline gpointer
+build_transaction_key (QmiMessage *message)
+{
+ gpointer key;
+ guint8 service;
+ guint8 client_id;
+ guint16 transaction_id;
+
+ service = (guint8)qmi_message_get_service (message);
+ client_id = qmi_message_get_client_id (message);
+ transaction_id = qmi_message_get_transaction_id (message);
+
+ /* We're putting a 32 bit value into a gpointer */
+ key = GUINT_TO_POINTER ((((service << 8) | client_id) << 16) | transaction_id);
+
+#ifdef MESSAGE_ENABLE_TRACE
+ {
+ gchar *hex;
+
+ hex = qmi_utils_str_hex (&key, sizeof (key), ':');
+ g_debug ("KEY: %s", hex);
+ g_free (hex);
+
+ hex = qmi_utils_str_hex (&service, sizeof (service), ':');
+ g_debug (" Service: %s", hex);
+ g_free (hex);
+
+ hex = qmi_utils_str_hex (&client_id, sizeof (client_id), ':');
+ g_debug (" Client ID: %s", hex);
+ g_free (hex);
+
+ hex = qmi_utils_str_hex (&transaction_id, sizeof (transaction_id), ':');
+ g_debug (" Transaction ID: %s", hex);
+ g_free (hex);
+ }
+#endif /* MESSAGE_ENABLE_TRACE */
+
+ return key;
+}
+
+static Transaction *
+device_release_transaction (QmiDevice *self,
+ gpointer key)
+{
+ Transaction *tr = NULL;
+
+ if (self->priv->transactions) {
+ tr = g_hash_table_lookup (self->priv->transactions, key);
+ if (tr)
+ /* If found, remove it from the HT */
+ g_hash_table_remove (self->priv->transactions, key);
+ }
+
+ return tr;
+}
+
+typedef struct {
+ QmiDevice *self;
+ gpointer key;
+} TransactionTimeoutContext;
+
+static void
+transaction_timeout_context_free (TransactionTimeoutContext *ctx)
+{
+ g_slice_free (TransactionTimeoutContext, ctx);
+}
+
+static gboolean
+transaction_timed_out (TransactionTimeoutContext *ctx)
+{
+ Transaction *tr;
+ GError *error = NULL;
+
+ tr = device_release_transaction (ctx->self, ctx->key);
+ tr->timeout_id = 0;
+
+ /* Complete transaction with a timeout error */
+ error = g_error_new (QMI_CORE_ERROR,
+ QMI_CORE_ERROR_TIMEOUT,
+ "Transaction timed out");
+ transaction_complete_and_free (tr, NULL, error);
+ g_error_free (error);
+
+ return FALSE;
+}
+
+static void
+device_store_transaction (QmiDevice *self,
+ Transaction *tr,
+ guint timeout)
+{
+ TransactionTimeoutContext *timeout_ctx;
+ gpointer key;
+
+ if (G_UNLIKELY (!self->priv->transactions))
+ self->priv->transactions = g_hash_table_new (g_direct_hash,
+ g_direct_equal);
+
+ key = build_transaction_key (tr->message);
+ g_hash_table_insert (self->priv->transactions, key, tr);
+
+ /* Once it gets into the HT, setup the timeout */
+ timeout_ctx = g_slice_new (TransactionTimeoutContext);
+ timeout_ctx->self = self;
+ timeout_ctx->key = key; /* valid as long as the transaction is in the HT */
+ tr->timeout_id = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
+ timeout,
+ (GSourceFunc)transaction_timed_out,
+ timeout_ctx,
+ (GDestroyNotify)transaction_timeout_context_free);
+}
+
+static Transaction *
+device_match_transaction (QmiDevice *self,
+ QmiMessage *message)
+{
+ /* msg can be either the original message or the response */
+ return device_release_transaction (self, build_transaction_key (message));
+}
+
+/*****************************************************************************/
+
+/**
+ * qmi_device_get_file:
+ * @self: a #QmiDevice.
+ *
+ * Get the #GFile associated with this #QmiDevice.
+ *
+ * Returns: a #GFile that must be freed with g_object_unref().
+ */
+GFile *
+qmi_device_get_file (QmiDevice *self)
+{
+ GFile *file = NULL;
+
+ g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
+
+ g_object_get (G_OBJECT (self),
+ QMI_DEVICE_FILE, &file,
+ NULL);
+ return file;
+}
+
+/**
+ * qmi_device_peek_file:
+ * @self: a #QmiDevice.
+ *
+ * Get the #GFile associated with this #QmiDevice, without increasing the reference count
+ * on the returned object.
+ *
+ * Returns: a #GFile. Do not free the returned object, it is owned by @self.
+ */
+GFile *
+qmi_device_peek_file (QmiDevice *self)
+{
+ g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
+
+ return self->priv->file;
+}
+
+/**
+ * qmi_device_get_path:
+ * @self: a #QmiDevice.
+ *
+ * Get the system path of the underlying QMI device.
+ *
+ * Returns: the system path of the device.
+ */
+const gchar *
+qmi_device_get_path (QmiDevice *self)
+{
+ g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
+
+ return self->priv->path;
+}
+
+/**
+ * qmi_device_get_path_display:
+ * @self: a #QmiDevice.
+ *
+ * Get the system path of the underlying QMI device in UTF-8.
+ *
+ * Returns: UTF-8 encoded system path of the device.
+ */
+const gchar *
+qmi_device_get_path_display (QmiDevice *self)
+{
+ g_return_val_if_fail (QMI_IS_DEVICE (self), NULL);
+
+ return self->priv->path_display;
+}
+
+/**
+ * qmi_device_is_open:
+ * @self: a #QmiDevice.
+ *
+ * Checks whether the #QmiDevice is open for I/O.
+ *
+ * Returns: #TRUE if @self is open, #FALSE otherwise.
+ */
+gboolean
+qmi_device_is_open (QmiDevice *self)
+{
+ g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE);
+
+ return !!self->priv->iochannel;
+}
+
+/*****************************************************************************/
+/* Register/Unregister clients that want to receive indications */
+
+static gpointer
+build_registered_client_key (guint8 cid,
+ QmiService service)
+{
+ return GUINT_TO_POINTER (((guint8)service << 8) | cid);
+}
+
+static gboolean
+register_client (QmiDevice *self,
+ QmiClient *client,
+ GError **error)
+{
+ gpointer key;
+
+ key = build_registered_client_key (qmi_client_get_cid (client),
+ qmi_client_get_service (client));
+ /* Only add the new client if not already registered one with the same CID
+ * for the same service */
+ if (g_hash_table_lookup (self->priv->registered_clients, key)) {
+ g_set_error (error,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_FAILED,
+ "A client with CID '%u' and service '%s' is already registered",
+ qmi_client_get_cid (client),
+ qmi_service_get_string (qmi_client_get_service (client)));
+ return FALSE;
+ }
+
+ g_hash_table_insert (self->priv->registered_clients,
+ key,
+ g_object_ref (client));
+ return TRUE;
+}
+
+static void
+unregister_client (QmiDevice *self,
+ QmiClient *client)
+{
+ g_hash_table_remove (self->priv->registered_clients,
+ build_registered_client_key (qmi_client_get_cid (client),
+ qmi_client_get_service (client)));
+}
+
+/*****************************************************************************/
+/* Allocate new client */
+
+typedef struct {
+ QmiDevice *self;
+ GSimpleAsyncResult *result;
+ QmiService service;
+ GType client_type;
+ guint8 cid;
+} AllocateClientContext;
+
+static void
+allocate_client_context_complete_and_free (AllocateClientContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_slice_free (AllocateClientContext, ctx);
+}
+
+/**
+ * qmi_device_allocate_client_finish:
+ * @self: a #QmiDevice.
+ * @res: a #GAsyncResult.
+ * @error: a #GError.
+ *
+ * Finishes an operation started with qmi_device_allocate_client().
+ *
+ * Returns: a newly allocated #QmiClient, or #NULL if @error is set.
+ */
+QmiClient *
+qmi_device_allocate_client_finish (QmiDevice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return NULL;
+
+ return QMI_CLIENT (g_object_ref (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res))));
+}
+
+static void
+build_client_object (AllocateClientContext *ctx)
+{
+ QmiClient *client;
+ GError *error = NULL;
+
+ /* We now have a proper CID for the client, we should be able to create it
+ * right away */
+ client = g_object_new (ctx->client_type,
+ QMI_CLIENT_DEVICE, ctx->self,
+ QMI_CLIENT_SERVICE, ctx->service,
+ QMI_CLIENT_CID, ctx->cid,
+ NULL);
+
+ /* Register the client to get indications */
+ if (!register_client (ctx->self, client, &error)) {
+ g_prefix_error (&error,
+ "Cannot register new client with CID '%u' and service '%s'",
+ ctx->cid,
+ qmi_service_get_string (ctx->service));
+ g_simple_async_result_take_error (ctx->result, error);
+ allocate_client_context_complete_and_free (ctx);
+ g_object_unref (client);
+ return;
+ }
+
+ g_debug ("Registered '%s' client with ID '%u'",
+ qmi_service_get_string (ctx->service),
+ ctx->cid);
+
+ /* Client created and registered, complete successfully */
+ g_simple_async_result_set_op_res_gpointer (ctx->result,
+ client,
+ (GDestroyNotify)g_object_unref);
+ allocate_client_context_complete_and_free (ctx);
+}
+
+static void
+allocate_cid_ready (QmiClientCtl *client_ctl,
+ GAsyncResult *res,
+ AllocateClientContext *ctx)
+{
+ QmiMessageCtlAllocateCidOutput *output;
+ QmiService service;
+ guint8 cid;
+ GError *error = NULL;
+
+ /* Check result of the async operation */
+ output = qmi_client_ctl_allocate_cid_finish (client_ctl, res, &error);
+ if (!output) {
+ g_prefix_error (&error, "CID allocation failed in the CTL client: ");
+ g_simple_async_result_take_error (ctx->result, error);
+ allocate_client_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Check result of the QMI operation */
+ if (!qmi_message_ctl_allocate_cid_output_get_result (output, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ allocate_client_context_complete_and_free (ctx);
+ qmi_message_ctl_allocate_cid_output_unref (output);
+ return;
+ }
+
+ /* Allocation info is mandatory when result is success */
+ g_assert (qmi_message_ctl_allocate_cid_output_get_allocation_info (output, &service, &cid, NULL));
+
+ if (service != ctx->service) {
+ g_simple_async_result_set_error (
+ ctx->result,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_FAILED,
+ "CID allocation failed in the CTL client: "
+ "Service mismatch (requested '%s', got '%s')",
+ qmi_service_get_string (ctx->service),
+ qmi_service_get_string (service));
+ allocate_client_context_complete_and_free (ctx);
+ qmi_message_ctl_allocate_cid_output_unref (output);
+ return;
+ }
+
+ ctx->cid = cid;
+ build_client_object (ctx);
+ qmi_message_ctl_allocate_cid_output_unref (output);
+}
+
+static gboolean
+check_service_supported (QmiDevice *self,
+ QmiService service)
+{
+ guint i;
+
+ /* If we didn't check supported services, just assume it is supported */
+ if (!self->priv->supported_services) {
+ g_debug ("Assuming service '%s' is supported...",
+ qmi_service_get_string (service));
+ return TRUE;
+ }
+
+ for (i = 0; i < self->priv->supported_services->len; i++) {
+ QmiMessageCtlGetVersionInfoOutputServiceListService *info;
+
+ info = &g_array_index (self->priv->supported_services,
+ QmiMessageCtlGetVersionInfoOutputServiceListService,
+ i);
+
+ if (service == info->service)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * qmi_device_allocate_client:
+ * @self: a #QmiDevice.
+ * @service: a valid #QmiService.
+ * @cid: a valid client ID, or #QMI_CID_NONE.
+ * @timeout: maximum time to wait.
+ * @cancellable: optional #GCancellable object, #NULL to ignore.
+ * @callback: a #GAsyncReadyCallback to call when the operation is finished.
+ * @user_data: the data to pass to callback function.
+ *
+ * Asynchronously allocates a new #QmiClient in @self.
+ *
+ * If #QMI_CID_NONE is given in @cid, a new client ID will be allocated;
+ * otherwise a client with the given @cid will be generated.
+ *
+ * When the operation is finished @callback will be called. You can then call
+ * qmi_device_allocate_client_finish() to get the result of the operation.
+ *
+ * Note: Clients for the #QMI_SERVICE_CTL cannot be created with this method;
+ * instead get/peek the implicit one from @self.
+ */
+void
+qmi_device_allocate_client (QmiDevice *self,
+ QmiService service,
+ guint8 cid,
+ guint timeout,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ AllocateClientContext *ctx;
+
+ g_return_if_fail (QMI_IS_DEVICE (self));
+ g_return_if_fail (service != QMI_SERVICE_UNKNOWN);
+
+ ctx = g_slice_new0 (AllocateClientContext);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ qmi_device_allocate_client);
+ ctx->service = service;
+
+ /* Check if the requested service is supported by the device */
+ if (!check_service_supported (self, service)) {
+ g_simple_async_result_set_error (ctx->result,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_UNSUPPORTED,
+ "Service '%s' not supported by the device",
+ qmi_service_get_string (service));
+ allocate_client_context_complete_and_free (ctx);
+ return;
+ }
+
+ switch (service) {
+ case QMI_SERVICE_CTL:
+ g_simple_async_result_set_error (ctx->result,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_INVALID_ARGS,
+ "Cannot create additional clients for the CTL service");
+ allocate_client_context_complete_and_free (ctx);
+ return;
+
+ case QMI_SERVICE_DMS:
+ ctx->client_type = QMI_TYPE_CLIENT_DMS;
+ break;
+
+ case QMI_SERVICE_WDS:
+ ctx->client_type = QMI_TYPE_CLIENT_WDS;
+ break;
+
+ default:
+ g_simple_async_result_set_error (ctx->result,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_INVALID_ARGS,
+ "Clients for service '%s' not yet supported",
+ qmi_service_get_string (service));
+ allocate_client_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Allocate a new CID for the client to be created */
+ if (cid == QMI_CID_NONE) {
+ QmiMessageCtlAllocateCidInput *input;
+
+ input = qmi_message_ctl_allocate_cid_input_new ();
+ qmi_message_ctl_allocate_cid_input_set_service (input, ctx->service, NULL);
+
+ g_debug ("Allocating new client ID...");
+ qmi_client_ctl_allocate_cid (self->priv->client_ctl,
+ input,
+ timeout,
+ cancellable,
+ (GAsyncReadyCallback)allocate_cid_ready,
+ ctx);
+
+ qmi_message_ctl_allocate_cid_input_unref (input);
+ return;
+ }
+
+ /* Reuse the given CID */
+ g_debug ("Reusing client CID '%u'...", cid);
+ ctx->cid = cid;
+ build_client_object (ctx);
+}
+
+/*****************************************************************************/
+/* Release client */
+
+typedef struct {
+ QmiClient *client;
+ GSimpleAsyncResult *result;
+} ReleaseClientContext;
+
+static void
+release_client_context_complete_and_free (ReleaseClientContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->client);
+ g_slice_free (ReleaseClientContext, ctx);
+}
+
+/**
+ * qmi_device_release_client_finish:
+ * @self: a #QmiDevice.
+ * @res: a #GAsyncResult.
+ * @error: a #GError.
+ *
+ * Finishes an operation started with qmi_device_release_client().
+ *
+ * Note that even if the release operation returns an error, the client should
+ * anyway be considered released, and shouldn't be used afterwards.
+ *
+ * Returns: #TRUE if successful, or #NULL if @error is set.
+ */
+gboolean
+qmi_device_release_client_finish (QmiDevice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void
+client_ctl_release_cid_ready (QmiClientCtl *client_ctl,
+ GAsyncResult *res,
+ ReleaseClientContext *ctx)
+{
+ GError *error = NULL;
+ QmiMessageCtlReleaseCidOutput *output;
+
+ /* Note: even if we return an error, the client is to be considered
+ * released! (so shouldn't be used) */
+
+ /* Check result of the async operation */
+ output = qmi_client_ctl_release_cid_finish (client_ctl, res, &error);
+ if (!output) {
+ g_simple_async_result_take_error (ctx->result, error);
+ release_client_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Check result of the QMI operation */
+ if (!qmi_message_ctl_release_cid_output_get_result (output, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ release_client_context_complete_and_free (ctx);
+ qmi_message_ctl_release_cid_output_unref (output);
+ return;
+ }
+
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ release_client_context_complete_and_free (ctx);
+ qmi_message_ctl_release_cid_output_unref (output);
+}
+
+/**
+ * qmi_device_release_client:
+ * @self: a #QmiDevice.
+ * @client: the #QmiClient to release.
+ * @flags: mask of #QmiDeviceReleaseClientFlags specifying how the client should be released.
+ * @timeout: maximum time to wait.
+ * @cancellable: optional #GCancellable object, #NULL to ignore.
+ * @callback: a #GAsyncReadyCallback to call when the operation is finished.
+ * @user_data: the data to pass to callback function.
+ *
+ * Asynchronously releases the #QmiClient from the #QmiDevice.
+ *
+ * Once the #QmiClient has been released, it cannot be used any more to
+ * perform operations.
+ *
+ *
+ * When the operation is finished @callback will be called. You can then call
+ * qmi_device_release_client_finish() to get the result of the operation.
+ */
+void
+qmi_device_release_client (QmiDevice *self,
+ QmiClient *client,
+ QmiDeviceReleaseClientFlags flags,
+ guint timeout,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ ReleaseClientContext *ctx;
+ QmiService service;
+ guint8 cid;
+
+ g_return_if_fail (QMI_IS_DEVICE (self));
+ g_return_if_fail (QMI_IS_CLIENT (client));
+
+ /* The CTL client should not have been created out of the QmiDevice */
+ g_assert (qmi_client_get_service (client) != QMI_SERVICE_CTL);
+
+ /* NOTE! The operation must not take a reference to self, or we won't be
+ * able to use it implicitly from our dispose() */
+
+ ctx = g_slice_new0 (ReleaseClientContext);
+ ctx->client = g_object_ref (client);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ qmi_device_release_client);
+
+ cid = qmi_client_get_cid (client);
+ service = (guint8)qmi_client_get_service (client);
+
+ /* Do not try to release an already released client */
+ if (cid == QMI_CID_NONE) {
+ g_simple_async_result_set_error (ctx->result,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_INVALID_ARGS,
+ "Client is already released");
+ release_client_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Unregister from device */
+ unregister_client (self, client);
+
+ g_debug ("Unregistered '%s' client with ID '%u'",
+ qmi_service_get_string (service),
+ cid);
+
+ /* Reset the contents of the client object, making it unusable */
+ g_object_set (client,
+ QMI_CLIENT_CID, QMI_CID_NONE,
+ QMI_CLIENT_SERVICE, QMI_SERVICE_UNKNOWN,
+ QMI_CLIENT_DEVICE, NULL,
+ NULL);
+
+ if (flags & QMI_DEVICE_RELEASE_CLIENT_FLAGS_RELEASE_CID) {
+ QmiMessageCtlReleaseCidInput *input;
+
+ /* And now, really try to release the CID */
+ input = qmi_message_ctl_release_cid_input_new ();
+ qmi_message_ctl_release_cid_input_set_release_info (input, service,cid, NULL);
+
+ /* And now, really try to release the CID */
+ qmi_client_ctl_release_cid (self->priv->client_ctl,
+ input,
+ timeout,
+ cancellable,
+ (GAsyncReadyCallback)client_ctl_release_cid_ready,
+ ctx);
+
+ qmi_message_ctl_release_cid_input_unref (input);
+ return;
+ }
+
+ /* No need to release the CID, so just done */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ release_client_context_complete_and_free (ctx);
+ return;
+}
+
+/*****************************************************************************/
+/* Open device */
+
+typedef struct {
+ QmiClient *client;
+ QmiMessage *message;
+} IdleIndicationContext;
+
+static gboolean
+process_indication_idle (IdleIndicationContext *ctx)
+{
+ g_assert (ctx->client != NULL);
+ g_assert (ctx->message != NULL);
+
+ qmi_client_process_indication (ctx->client, ctx->message);
+
+ g_object_unref (ctx->client);
+ qmi_message_unref (ctx->message);
+ g_slice_free (IdleIndicationContext, ctx);
+ return FALSE;
+}
+
+static void
+report_indication (QmiClient *client,
+ QmiMessage *message)
+{
+ IdleIndicationContext *ctx;
+
+ /* Setup an idle to Pass the indication down to the client */
+ ctx = g_slice_new (IdleIndicationContext);
+ ctx->client = g_object_ref (client);
+ ctx->message = qmi_message_ref (message);
+ g_idle_add ((GSourceFunc)process_indication_idle, ctx);
+}
+
+static void
+process_message (QmiDevice *self,
+ QmiMessage *message)
+{
+ GError *error = NULL;
+
+ /* Ensure the read message is valid */
+ if (!qmi_message_check (message, &error)) {
+ g_warning ("Invalid QMI message received: %s",
+ error->message);
+ g_error_free (error);
+ return;
+ }
+
+#ifdef MESSAGE_ENABLE_TRACE
+ {
+ gchar *printable;
+
+ printable = qmi_message_get_printable (message, ">>>>>> ");
+ g_debug ("[%s] Received message...\n%s",
+ self->priv->path_display,
+ printable);
+ g_free (printable);
+ }
+#endif /* MESSAGE_ENABLE_TRACE */
+
+ if (qmi_message_is_indication (message)) {
+ if (qmi_message_get_client_id (message) == QMI_CID_BROADCAST) {
+ GHashTableIter iter;
+ gpointer key;
+ QmiClient *client;
+
+ g_hash_table_iter_init (&iter, self->priv->registered_clients);
+ while (g_hash_table_iter_next (&iter, &key, (gpointer *)&client)) {
+ /* For broadcast messages, report them just if the service matches */
+ if (qmi_message_get_service (message) == qmi_client_get_service (client))
+ report_indication (client, message);
+ }
+ } else {
+ QmiClient *client;
+
+ client = g_hash_table_lookup (self->priv->registered_clients,
+ build_registered_client_key (qmi_message_get_client_id (message),
+ qmi_message_get_service (message)));
+ if (client)
+ report_indication (client, message);
+ }
+
+ return;
+ }
+
+ if (qmi_message_is_response (message)) {
+ Transaction *tr;
+
+ tr = device_match_transaction (self, message);
+ if (!tr)
+ g_debug ("[%s] No transaction matched in received message",
+ self->priv->path_display);
+ else
+ /* Report the reply message */
+ transaction_complete_and_free (tr, message, NULL);
+
+ return;
+ }
+
+ g_debug ("[%s] Message received but it is neither an indication nor a response. Skipping it.",
+ self->priv->path_display);
+}
+
+static void
+parse_response (QmiDevice *self)
+{
+ do {
+ QmiMessage *message;
+
+ /* Every message received must start with the QMUX marker.
+ * If it doesn't, we broke framing :-/
+ * If we broke framing, an error should be reported and the device
+ * should get closed */
+ if (self->priv->response->len > 0 &&
+ self->priv->response->data[0] != QMI_MESSAGE_QMUX_MARKER) {
+ /* TODO: Report fatal error */
+ g_warning ("QMI framing error detected");
+ return;
+ }
+
+ message = qmi_message_new_from_raw (self->priv->response->data,
+ self->priv->response->len);
+ if (!message)
+ /* More data we need */
+ return;
+
+ /* Remove the read data from the response buffer */
+ g_byte_array_remove_range (self->priv->response,
+ 0,
+ qmi_message_get_length (message));
+
+ /* Play with the received message */
+ process_message (self, message);
+
+ qmi_message_unref (message);
+ } while (self->priv->response->len > 0);
+}
+
+static gboolean
+data_available (GIOChannel *source,
+ GIOCondition condition,
+ QmiDevice *self)
+{
+ gsize bytes_read;
+ GIOStatus status;
+ gchar buffer[BUFFER_SIZE + 1];
+
+ if (condition & G_IO_HUP) {
+ g_debug ("[%s] unexpected port hangup!",
+ self->priv->path_display);
+
+ if (self->priv->response &&
+ self->priv->response->len)
+ g_byte_array_remove_range (self->priv->response, 0, self->priv->response->len);
+
+ qmi_device_close (self, NULL);
+ return FALSE;
+ }
+
+ if (condition & G_IO_ERR) {
+ if (self->priv->response &&
+ self->priv->response->len)
+ g_byte_array_remove_range (self->priv->response, 0, self->priv->response->len);
+ return TRUE;
+ }
+
+ /* If not ready yet, prepare the response with default initial size. */
+ if (G_UNLIKELY (!self->priv->response))
+ self->priv->response = g_byte_array_sized_new (500);
+
+ do {
+ GError *error = NULL;
+
+ status = g_io_channel_read_chars (source,
+ buffer,
+ BUFFER_SIZE,
+ &bytes_read,
+ &error);
+ if (status == G_IO_STATUS_ERROR) {
+ if (error) {
+ g_warning ("error reading from the IOChannel: '%s'", error->message);
+ g_error_free (error);
+ }
+
+ /* Port is closed; we're done */
+ if (self->priv->watch_id == 0)
+ break;
+ }
+
+ /* If no bytes read, just let g_io_channel wait for more data */
+ if (bytes_read == 0)
+ break;
+
+ if (bytes_read > 0)
+ g_byte_array_append (self->priv->response, (const guint8 *)buffer, bytes_read);
+
+ /* Try to parse what we already got */
+ parse_response (self);
+
+ /* And keep on if we were told to keep on */
+ } while (bytes_read == BUFFER_SIZE || status == G_IO_STATUS_AGAIN);
+
+ return TRUE;
+}
+
+static gboolean
+create_iochannel (QmiDevice *self,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ guint fd;
+
+ if (self->priv->iochannel) {
+ g_set_error (error,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_WRONG_STATE,
+ "Already open");
+ return FALSE;
+ }
+
+ g_assert (self->priv->file);
+ g_assert (self->priv->path);
+
+ errno = 0;
+ fd = open (self->priv->path, O_RDWR | O_EXCL | O_NONBLOCK | O_NOCTTY);
+ if (fd < 0) {
+ g_set_error (error,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_FAILED,
+ "Cannot open device file '%s': %s",
+ self->priv->path_display,
+ strerror (errno));
+ return FALSE;
+ }
+
+ /* Create new GIOChannel */
+ self->priv->iochannel = g_io_channel_unix_new (fd);
+
+ /* We don't want UTF-8 encoding, we're playing with raw binary data */
+ g_io_channel_set_encoding (self->priv->iochannel, NULL, NULL);
+
+ /* We don't want to get the channel buffered */
+ g_io_channel_set_buffered (self->priv->iochannel, FALSE);
+
+ /* Let the GIOChannel own the FD */
+ g_io_channel_set_close_on_unref (self->priv->iochannel, TRUE);
+
+ /* We don't want to get blocked while writing stuff */
+ if (!g_io_channel_set_flags (self->priv->iochannel,
+ G_IO_FLAG_NONBLOCK,
+ &inner_error)) {
+ g_prefix_error (&inner_error, "Cannot set non-blocking channel: ");
+ g_propagate_error (error, inner_error);
+ g_io_channel_shutdown (self->priv->iochannel, FALSE, NULL);
+ g_io_channel_unref (self->priv->iochannel);
+ self->priv->iochannel = NULL;
+ return FALSE;
+ }
+
+ self->priv->watch_id = g_io_add_watch (self->priv->iochannel,
+ G_IO_IN | G_IO_ERR | G_IO_HUP,
+ (GIOFunc)data_available,
+ self);
+
+ return !!self->priv->iochannel;
+}
+
+typedef struct {
+ QmiDevice *self;
+ GSimpleAsyncResult *result;
+ GCancellable *cancellable;
+ QmiDeviceOpenFlags flags;
+ guint timeout;
+ guint version_check_retries;
+} DeviceOpenContext;
+
+static void
+device_open_context_complete_and_free (DeviceOpenContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ g_object_unref (ctx->result);
+ if (ctx->cancellable)
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->self);
+ g_slice_free (DeviceOpenContext, ctx);
+}
+
+/**
+ * qmi_device_open_finish:
+ * @self: a #QmiDevice.
+ * @res: a #GAsyncResult.
+ * @error: a #GError.
+ *
+ * Finishes an asynchronous open operation started with qmi_device_open_async().
+ *
+ * Returns: #TRUE if successful, #FALSE if @error is set.
+ */
+gboolean
+qmi_device_open_finish (QmiDevice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+}
+
+static void process_open_flags (DeviceOpenContext *ctx);
+
+static void
+sync_ready (QmiClientCtl *client_ctl,
+ GAsyncResult *res,
+ DeviceOpenContext *ctx)
+{
+ GError *error = NULL;
+ QmiMessageCtlSyncOutput *output;
+
+ /* Check result of the async operation */
+ output = qmi_client_ctl_sync_finish (client_ctl, res, &error);
+ if(!output) {
+ g_simple_async_result_take_error (ctx->result, error);
+ device_open_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Check result of the QMI operation */
+ if (!qmi_message_ctl_sync_output_get_result (output, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ device_open_context_complete_and_free (ctx);
+ qmi_message_ctl_sync_output_unref (output);
+ return;
+ }
+
+ g_debug ("[%s] Sync operation finished",
+ ctx->self->priv->path_display);
+
+ /* Keep on with next flags */
+ process_open_flags (ctx);
+ qmi_message_ctl_sync_output_unref (output);
+}
+
+static void
+version_info_ready (QmiClientCtl *client_ctl,
+ GAsyncResult *res,
+ DeviceOpenContext *ctx)
+{
+ GArray *service_list;
+ QmiMessageCtlGetVersionInfoOutput *output;
+ GError *error = NULL;
+ guint i;
+
+ /* Check result of the async operation */
+ output = qmi_client_ctl_get_version_info_finish (client_ctl, res, &error);
+ if (!output) {
+ if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TIMEOUT)) {
+ /* Update retries... */
+ ctx->version_check_retries--;
+ /* If retries left, retry */
+ if (ctx->version_check_retries > 0) {
+ qmi_client_ctl_get_version_info (ctx->self->priv->client_ctl,
+ NULL,
+ 1,
+ ctx->cancellable,
+ (GAsyncReadyCallback)version_info_ready,
+ ctx);
+ return;
+ }
+
+ /* Otherwise, propagate the error */
+ }
+
+ g_simple_async_result_take_error (ctx->result, error);
+ device_open_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Check result of the QMI operation */
+ if (!qmi_message_ctl_get_version_info_output_get_result (output, &error)) {
+ g_simple_async_result_take_error (ctx->result, error);
+ device_open_context_complete_and_free (ctx);
+ qmi_message_ctl_get_version_info_output_unref (output);
+ return;
+ }
+
+ /* QMI operation succeeded, we can now get the outputs */
+ service_list = NULL;
+ qmi_message_ctl_get_version_info_output_get_service_list (output,
+ &service_list,
+ NULL);
+ ctx->self->priv->supported_services = g_array_ref (service_list);
+
+ g_debug ("[%s] QMI Device supports %u services:",
+ ctx->self->priv->path_display,
+ ctx->self->priv->supported_services->len);
+ for (i = 0; i < ctx->self->priv->supported_services->len; i++) {
+ QmiMessageCtlGetVersionInfoOutputServiceListService *info;
+
+ info = &g_array_index (ctx->self->priv->supported_services,
+ QmiMessageCtlGetVersionInfoOutputServiceListService,
+ i);
+ g_debug ("[%s] %s (%u.%u)",
+ ctx->self->priv->path_display,
+ qmi_service_get_string (info->service),
+ info->major_version,
+ info->minor_version);
+ }
+
+ /* Keep on with next flags */
+ process_open_flags (ctx);
+ qmi_message_ctl_get_version_info_output_unref (output);
+}
+
+static void
+process_open_flags (DeviceOpenContext *ctx)
+{
+ /* Query version info? */
+ if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_VERSION_INFO) {
+ ctx->flags &= ~QMI_DEVICE_OPEN_FLAGS_VERSION_INFO;
+ /* Setup how many times to retry... We'll retry once per second */
+ ctx->version_check_retries = ctx->timeout > 0 ? ctx->timeout : 1;
+ g_debug ("Checking version info (%u retries)...", ctx->version_check_retries);
+ qmi_client_ctl_get_version_info (ctx->self->priv->client_ctl,
+ NULL,
+ 1,
+ ctx->cancellable,
+ (GAsyncReadyCallback)version_info_ready,
+ ctx);
+ return;
+ }
+
+ /* Sync? */
+ if (ctx->flags & QMI_DEVICE_OPEN_FLAGS_SYNC) {
+ g_debug ("Running sync...");
+ ctx->flags &= ~QMI_DEVICE_OPEN_FLAGS_SYNC;
+ qmi_client_ctl_sync (ctx->self->priv->client_ctl,
+ NULL,
+ ctx->timeout,
+ ctx->cancellable,
+ (GAsyncReadyCallback)sync_ready,
+ ctx);
+ return;
+ }
+
+ /* No more flags to process, done we are */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ device_open_context_complete_and_free (ctx);
+}
+
+/**
+ * qmi_device_open:
+ * @self: a #QmiDevice.
+ * @flags: mask of #QmiDeviceOpenFlags specifying how the device should be opened.
+ * @timeout: maximum time, in seconds, to wait for the device to be opened.
+ * @cancellable: optional #GCancellable object, #NULL to ignore.
+ * @callback: a #GAsyncReadyCallback to call when the operation is finished.
+ * @user_data: the data to pass to callback function.
+ *
+ * Asynchronously opens a #QmiDevice for I/O.
+ *
+ * When the operation is finished @callback will be called. You can then call
+ * qmi_device_open_finish() to get the result of the operation.
+ */
+void
+qmi_device_open (QmiDevice *self,
+ QmiDeviceOpenFlags flags,
+ guint timeout,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ DeviceOpenContext *ctx;
+ GError *error = NULL;
+
+ g_return_if_fail (QMI_IS_DEVICE (self));
+
+ ctx = g_slice_new (DeviceOpenContext);
+ ctx->self = g_object_ref (self);
+ ctx->result = g_simple_async_result_new (G_OBJECT (self),
+ callback,
+ user_data,
+ qmi_device_open);
+ ctx->flags = flags;
+ ctx->timeout = timeout;
+ ctx->cancellable = (cancellable ? g_object_ref (cancellable) : NULL);
+
+ if (!create_iochannel (self, &error)) {
+ g_prefix_error (&error,
+ "Cannot open QMI device: ");
+ g_simple_async_result_take_error (ctx->result, error);
+ device_open_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Process all open flags */
+ process_open_flags (ctx);
+}
+
+/*****************************************************************************/
+/* Close channel */
+
+static gboolean
+destroy_iochannel (QmiDevice *self,
+ GError **error)
+{
+ GError *inner_error = NULL;
+
+ /* Already closed? */
+ if (!self->priv->iochannel)
+ return TRUE;
+
+ g_io_channel_shutdown (self->priv->iochannel, TRUE, &inner_error);
+
+ /* Failures when closing still make the device to get closed */
+ g_io_channel_unref (self->priv->iochannel);
+ self->priv->iochannel = NULL;
+
+ if (self->priv->watch_id) {
+ g_source_remove (self->priv->watch_id);
+ self->priv->watch_id = 0;
+ }
+
+ if (self->priv->response) {
+ g_byte_array_unref (self->priv->response);
+ self->priv->response = NULL;
+ }
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * qmi_device_close:
+ * @self: a #QmiDevice
+ * @error: a #GError
+ *
+ * Synchronously closes a #QmiDevice, preventing any further I/O.
+ *
+ * Closing a #QmiDevice multiple times will not return an error.
+ *
+ * Returns: #TRUE if successful, #FALSE if @error is set.
+ */
+gboolean
+qmi_device_close (QmiDevice *self,
+ GError **error)
+{
+ g_return_val_if_fail (QMI_IS_DEVICE (self), FALSE);
+
+ if (!destroy_iochannel (self, error)) {
+ g_prefix_error (error,
+ "Cannot close QMI device: ");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+/* Command */
+
+QmiMessage *
+qmi_device_command_finish (QmiDevice *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error))
+ return NULL;
+
+ return qmi_message_ref (g_simple_async_result_get_op_res_gpointer (
+ G_SIMPLE_ASYNC_RESULT (res)));
+}
+
+void
+qmi_device_command (QmiDevice *self,
+ QmiMessage *message,
+ guint timeout,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ Transaction *tr;
+ gconstpointer raw_message;
+ gsize raw_message_len;
+ gsize written;
+ GIOStatus write_status;
+
+ g_return_if_fail (QMI_IS_DEVICE (self));
+ g_return_if_fail (message != NULL);
+
+ tr = transaction_new (self, message, callback, user_data);
+
+ /* Device must be open */
+ if (!self->priv->iochannel) {
+ error = g_error_new (QMI_CORE_ERROR,
+ QMI_CORE_ERROR_WRONG_STATE,
+ "Device must be open to send commands");
+ transaction_complete_and_free (tr, NULL, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Non-CTL services should use a proper CID */
+ if (qmi_message_get_service (message) != QMI_SERVICE_CTL &&
+ qmi_message_get_client_id (message) == 0) {
+ error = g_error_new (QMI_CORE_ERROR,
+ QMI_CORE_ERROR_FAILED,
+ "Cannot send message in service '%s' without a CID",
+ qmi_service_get_string (qmi_message_get_service (message)));
+ transaction_complete_and_free (tr, NULL, error);
+ g_error_free (error);
+ return;
+ }
+
+#ifdef MESSAGE_ENABLE_TRACE
+ {
+ gchar *printable;
+
+ printable = qmi_message_get_printable (message, "<<<<<< ");
+ g_debug ("[%s] Sending message...\n%s",
+ self->priv->path_display,
+ printable);
+ g_free (printable);
+ }
+#endif /* MESSAGE_ENABLE_TRACE */
+
+ /* Get raw message */
+ raw_message = qmi_message_get_raw (message, &raw_message_len, &error);
+ if (!raw_message) {
+ g_prefix_error (&error, "Cannot get raw message: ");
+ transaction_complete_and_free (tr, NULL, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Setup context to match response */
+ device_store_transaction (self, tr, timeout);
+
+ written = 0;
+ write_status = G_IO_STATUS_AGAIN;
+ while (write_status == G_IO_STATUS_AGAIN) {
+ write_status = g_io_channel_write_chars (self->priv->iochannel,
+ raw_message,
+ (gssize)raw_message_len,
+ &written,
+ &error);
+ switch (write_status) {
+ case G_IO_STATUS_ERROR:
+ g_prefix_error (&error, "Cannot write message: ");
+
+ /* Match transaction so that we remove it from our tracking table */
+ tr = device_match_transaction (self, message);
+ transaction_complete_and_free (tr, NULL, error);
+ g_error_free (error);
+ return;
+
+ case G_IO_STATUS_EOF:
+ /* We shouldn't get EOF when writing */
+ g_assert_not_reached ();
+ break;
+
+ case G_IO_STATUS_NORMAL:
+ /* All good, we'll exit the loop now */
+ break;
+
+ case G_IO_STATUS_AGAIN:
+ /* We're in a non-blocking channel and therefore we're up to receive
+ * EAGAIN; just retry in this case. TODO: in an idle? */
+ break;
+ }
+ }
+
+ /* Just return, we'll get response asynchronously */
+}
+
+/*****************************************************************************/
+/* New QMI device */
+
+/**
+ * qmi_device_new_finish:
+ * @res: a #GAsyncResult.
+ * @error: a #GError.
+ *
+ * Finishes an operation started with qmi_device_new().
+ *
+ * Returns: A newly created #QmiDevice, or #NULL if @error is set.
+ */
+QmiDevice *
+qmi_device_new_finish (GAsyncResult *res,
+ GError **error)
+{
+ GObject *ret;
+ GObject *source_object;
+
+ source_object = g_async_result_get_source_object (res);
+ ret = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error);
+ g_object_unref (source_object);
+
+ return (ret ? QMI_DEVICE (ret) : NULL);
+}
+
+/**
+ * qmi_device_new:
+ * @file: a #GFile.
+ * @cancellable: optional #GCancellable object, #NULL to ignore.
+ * @callback: a #GAsyncReadyCallback to call when the initialization is finished.
+ * @user_data: the data to pass to callback function.
+ *
+ * Asynchronously creates a #QmiDevice object to manage @file.
+ * When the operation is finished, @callback will be invoked. You can then call
+ * qmi_device_new_finish() to get the result of the operation.
+ */
+void
+qmi_device_new (GFile *file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_async_initable_new_async (QMI_TYPE_DEVICE,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ callback,
+ user_data,
+ QMI_DEVICE_FILE, file,
+ NULL);
+}
+
+/*****************************************************************************/
+/* Async init */
+
+typedef struct {
+ QmiDevice *self;
+ GSimpleAsyncResult *result;
+ GCancellable *cancellable;
+} InitContext;
+
+static void
+init_context_complete_and_free (InitContext *ctx)
+{
+ g_simple_async_result_complete_in_idle (ctx->result);
+ if (ctx->cancellable)
+ g_object_unref (ctx->cancellable);
+ g_object_unref (ctx->result);
+ g_object_unref (ctx->self);
+ g_slice_free (InitContext, ctx);
+}
+
+static gboolean
+initable_init_finish (GAsyncInitable *initable,
+ GAsyncResult *result,
+ GError **error)
+{
+ return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void
+query_info_async_ready (GFile *file,
+ GAsyncResult *res,
+ InitContext *ctx)
+{
+ GError *error = NULL;
+ GFileInfo *info;
+
+ info = g_file_query_info_finish (file, res, &error);
+ if (!info) {
+ g_prefix_error (&error,
+ "Couldn't query file info: ");
+ g_simple_async_result_take_error (ctx->result, error);
+ init_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Our QMI device must be of SPECIAL type */
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_SPECIAL) {
+ g_simple_async_result_set_error (ctx->result,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_FAILED,
+ "Wrong file type");
+ init_context_complete_and_free (ctx);
+ return;
+ }
+ g_object_unref (info);
+
+ /* Create the implicit CTL client */
+ ctx->self->priv->client_ctl = g_object_new (QMI_TYPE_CLIENT_CTL,
+ QMI_CLIENT_DEVICE, ctx->self,
+ QMI_CLIENT_SERVICE, QMI_SERVICE_CTL,
+ QMI_CLIENT_CID, QMI_CID_NONE,
+ NULL);
+
+ /* Register the CTL client to get indications */
+ register_client (ctx->self,
+ QMI_CLIENT (ctx->self->priv->client_ctl),
+ &error);
+ g_assert_no_error (error);
+
+ /* Done we are */
+ g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+ init_context_complete_and_free (ctx);
+}
+
+static void
+initable_init_async (GAsyncInitable *initable,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ InitContext *ctx;
+
+ ctx = g_slice_new0 (InitContext);
+ ctx->self = g_object_ref (initable);
+ if (cancellable)
+ ctx->cancellable = g_object_ref (cancellable);
+ ctx->result = g_simple_async_result_new (G_OBJECT (initable),
+ callback,
+ user_data,
+ initable_init_async);
+
+ /* We need a proper file to initialize */
+ if (!ctx->self->priv->file) {
+ g_simple_async_result_set_error (ctx->result,
+ QMI_CORE_ERROR,
+ QMI_CORE_ERROR_INVALID_ARGS,
+ "Cannot initialize QMI device: No file given");
+ init_context_complete_and_free (ctx);
+ return;
+ }
+
+ /* Check the file type. Note that this is just a quick check to avoid
+ * creating QmiDevices pointing to a location already known not to be a QMI
+ * device. */
+ g_file_query_info_async (ctx->self->priv->file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ ctx->cancellable,
+ (GAsyncReadyCallback)query_info_async_ready,
+ ctx);
+}
+
+/*****************************************************************************/
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ QmiDevice *self = QMI_DEVICE (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ g_assert (self->priv->file == NULL);
+ self->priv->file = g_value_dup_object (value);
+ self->priv->path = g_file_get_path (self->priv->file);
+ self->priv->path_display = g_filename_display_name (self->priv->path);
+ break;
+ case PROP_CLIENT_CTL:
+ /* Not writable */
+ g_assert_not_reached ();
+ 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)
+{
+ QmiDevice *self = QMI_DEVICE (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ g_value_set_object (value, self->priv->file);
+ break;
+ case PROP_CLIENT_CTL:
+ g_value_set_object (value, self->priv->client_ctl);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+qmi_device_init (QmiDevice *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+ QMI_TYPE_DEVICE,
+ QmiDevicePrivate);
+
+ self->priv->registered_clients = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ NULL,
+ g_object_unref);
+}
+
+static gboolean
+foreach_warning (gpointer key,
+ QmiClient *client,
+ QmiDevice *self)
+{
+ g_warning ("QMI client for service '%s' with CID '%u' wasn't released",
+ qmi_service_get_string (qmi_client_get_service (client)),
+ qmi_client_get_cid (client));
+
+ return TRUE;
+}
+
+static void
+dispose (GObject *object)
+{
+ QmiDevice *self = QMI_DEVICE (object);
+
+ g_clear_object (&self->priv->file);
+
+ /* unregister our CTL client */
+ unregister_client (self, QMI_CLIENT (self->priv->client_ctl));
+
+ /* If clients were left unreleased, we'll just warn about it.
+ * There is no point in trying to request CID releases, as the device
+ * itself is being disposed. */
+ g_hash_table_foreach_remove (self->priv->registered_clients,
+ (GHRFunc)foreach_warning,
+ self);
+
+ g_clear_object (&self->priv->client_ctl);
+
+ G_OBJECT_CLASS (qmi_device_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+ QmiDevice *self = QMI_DEVICE (object);
+
+ /* Transactions keep refs to the device, so it's actually
+ * impossible to have any content in the HT */
+ if (self->priv->transactions) {
+ g_assert (g_hash_table_size (self->priv->transactions) == 0);
+ g_hash_table_unref (self->priv->transactions);
+ }
+
+ g_hash_table_unref (self->priv->registered_clients);
+
+ if (self->priv->supported_services)
+ g_array_unref (self->priv->supported_services);
+
+ g_free (self->priv->path);
+ g_free (self->priv->path_display);
+ if (self->priv->response)
+ g_byte_array_unref (self->priv->response);
+ if (self->priv->iochannel)
+ g_io_channel_unref (self->priv->iochannel);
+
+ G_OBJECT_CLASS (qmi_device_parent_class)->finalize (object);
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *iface)
+{
+ iface->init_async = initable_init_async;
+ iface->init_finish = initable_init_finish;
+}
+
+static void
+qmi_device_class_init (QmiDeviceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (QmiDevicePrivate));
+
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+ object_class->finalize = finalize;
+ object_class->dispose = dispose;
+
+ properties[PROP_FILE] =
+ g_param_spec_object (QMI_DEVICE_FILE,
+ "Device file",
+ "File to the underlying QMI device",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_FILE, properties[PROP_FILE]);
+
+ properties[PROP_CLIENT_CTL] =
+ g_param_spec_object (QMI_DEVICE_CLIENT_CTL,
+ "CTL client",
+ "Implicit CTL client",
+ QMI_TYPE_CLIENT_CTL,
+ G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_CLIENT_CTL, properties[PROP_CLIENT_CTL]);
+}