diff options
author | Aleksander Morgado <aleksander@lanedo.com> | 2012-04-26 12:57:23 +0200 |
---|---|---|
committer | Aleksander Morgado <aleksander@lanedo.com> | 2012-07-03 16:08:49 +0200 |
commit | 08c76d411ce356d22dcaf0622d4c77b385c71385 (patch) | |
tree | f5a882ce1fce4d26f5c519a0f61789e3aca6355b | |
parent | c689b969e1964c48ba0893c04f54ad3d882f9c04 (diff) |
core: let the `QmiDevice' control the creation and handling of `QmiClient' objects
Instead of treating the QmiClient objects as being independent to the device,
we'll make them instead owned by the QmiDevice. This means that the QmiDevice is
reponsible for allocationg and releasing the CIDs, and that a QmiClient doesn't
need to keep a reference to the QmiDevice around.
This simplification allows us to treat the `QmiClientCtl' as completely internal
to the library. Uses of the library shouldn't need to interface directly with
this object.
-rw-r--r-- | cli/Makefile.am | 1 | ||||
-rw-r--r-- | cli/qmicli-ctl.c | 189 | ||||
-rw-r--r-- | cli/qmicli-dms.c | 34 | ||||
-rw-r--r-- | cli/qmicli.c | 7 | ||||
-rw-r--r-- | cli/qmicli.h | 6 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/qmi-client-dms.c | 42 | ||||
-rw-r--r-- | src/qmi-client-dms.h | 8 | ||||
-rw-r--r-- | src/qmi-client.c | 298 | ||||
-rw-r--r-- | src/qmi-client.h | 11 | ||||
-rw-r--r-- | src/qmi-device.c | 445 | ||||
-rw-r--r-- | src/qmi-device.h | 33 | ||||
-rw-r--r-- | src/qmi-enums.h | 3 |
13 files changed, 394 insertions, 686 deletions
diff --git a/cli/Makefile.am b/cli/Makefile.am index a0f156e..355d51e 100644 --- a/cli/Makefile.am +++ b/cli/Makefile.am @@ -9,7 +9,6 @@ qmicli_CPPFLAGS = \ qmicli_SOURCES = \ qmicli.c \ qmicli.h \ - qmicli-ctl.c \ qmicli-dms.c qmicli_LDADD = \ diff --git a/cli/qmicli-ctl.c b/cli/qmicli-ctl.c deleted file mode 100644 index dfb1279..0000000 --- a/cli/qmicli-ctl.c +++ /dev/null @@ -1,189 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* - * qmicli -- Command line interface to control 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 3 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) 2012 Aleksander Morgado <aleksander@gnu.org> - */ - -#include "config.h" - -#include <stdio.h> -#include <stdlib.h> -#include <locale.h> -#include <string.h> - -#include <glib.h> -#include <gio/gio.h> - -#include <libqmi-glib.h> - -#include "qmicli.h" - -/* Context */ -typedef struct { - QmiDevice *device; - QmiClientCtl *client; - GCancellable *cancellable; -} Context; -static Context *ctx; - -/* Options */ -static gboolean get_version_info_flag; - -static GOptionEntry entries[] = { - { "ctl-get-version-info", 0, 0, G_OPTION_ARG_NONE, &get_version_info_flag, - "Get QMI version info", - NULL - }, - { NULL } -}; - -GOptionGroup * -qmicli_ctl_get_option_group (void) -{ - GOptionGroup *group; - - group = g_option_group_new ("ctl", - "CTL options", - "Show Control options", - NULL, - NULL); - g_option_group_add_entries (group, entries); - - return group; -} - -gboolean -qmicli_ctl_options_enabled (void) -{ - static guint n_actions = 0; - static gboolean checked = FALSE; - - if (checked) - return !!n_actions; - - n_actions = (get_version_info_flag); - - if (n_actions > 1) { - g_printerr ("error: too many CTL actions requested\n"); - exit (EXIT_FAILURE); - } - - checked = TRUE; - return !!n_actions; -} - -static void -context_free (Context *ctx) -{ - if (!ctx) - return; - - if (ctx->cancellable) - g_object_unref (ctx->cancellable); - if (ctx->device) - g_object_unref (ctx->device); - if (ctx->client) - g_object_unref (ctx->client); - g_slice_free (Context, ctx); -} - -static void -client_release_ready (QmiClient *client, - GAsyncResult *res) -{ - GError *error = NULL; - - if (!qmi_client_release_finish (client, res, &error)) { - g_printerr ("error: couldn't release client CID: %s", error->message); - exit (EXIT_FAILURE); - } - - g_debug ("Client CID released"); - - /* Cleanup context and finish async operation */ - context_free (ctx); - qmicli_async_operation_done (); -} - -static void -shutdown (void) -{ - /* NOTE: we don't really need to run explicit release for the CTL client */ - qmi_client_release (QMI_CLIENT (ctx->client), - 10, - (GAsyncReadyCallback)client_release_ready, - NULL); -} - -static void -get_version_info_ready (QmiClientCtl *client, - GAsyncResult *res) -{ - GError *error = NULL; - GPtrArray *result; - guint i; - - result = qmi_client_ctl_get_version_info_finish (client, res, &error); - if (!result) { - g_printerr ("error: couldn't get version info: %s\n", - error->message); - exit (EXIT_FAILURE); - } - - g_print ("[%s] Supported services:\n", - qmi_device_get_path_display (ctx->device)); - for (i = 0; i < result->len; i++) { - QmiCtlVersionInfo *info; - - info = g_ptr_array_index (result, i); - g_print ("\t%s (%u.%u)\n", - qmi_service_get_string (qmi_ctl_version_info_get_service (info)), - qmi_ctl_version_info_get_major_version (info), - qmi_ctl_version_info_get_minor_version (info)); - } - - g_ptr_array_unref (result); - - shutdown (); -} - -void -qmicli_ctl_run (QmiDevice *device, - GCancellable *cancellable) -{ - /* Initialize context */ - ctx = g_slice_new (Context); - ctx->device = g_object_ref (device); - if (cancellable) - ctx->cancellable = g_object_ref (cancellable); - - /* The CTL client should be directly retrievable from the QmiDevice */ - ctx->client = qmi_device_get_client_ctl (device); - - /* Request to get version info? */ - if (get_version_info_flag) { - g_debug ("Asynchronously getting version info..."); - qmi_client_ctl_get_version_info (ctx->client, - 10, - ctx->cancellable, - (GAsyncReadyCallback)get_version_info_ready, - NULL); - return; - } - - g_warn_if_reached (); -} diff --git a/cli/qmicli-dms.c b/cli/qmicli-dms.c index 6445dfb..f1d0e7b 100644 --- a/cli/qmicli-dms.c +++ b/cli/qmicli-dms.c @@ -102,17 +102,17 @@ context_free (Context *ctx) } static void -client_release_ready (QmiClient *client, +release_client_ready (QmiDevice *device, GAsyncResult *res) { GError *error = NULL; - if (!qmi_client_release_finish (client, res, &error)) { - g_printerr ("error: couldn't release client CID: %s", error->message); + if (!qmi_device_release_client_finish (device, res, &error)) { + g_printerr ("error: couldn't release client: %s", error->message); exit (EXIT_FAILURE); } - g_debug ("Client CID released"); + g_debug ("Client released"); /* Cleanup context and finish async operation */ context_free (ctx); @@ -122,10 +122,12 @@ client_release_ready (QmiClient *client, static void shutdown (void) { - qmi_client_release (QMI_CLIENT (ctx->client), - 10, - (GAsyncReadyCallback)client_release_ready, - NULL); + qmi_device_release_client (ctx->device, + QMI_CLIENT (ctx->client), + 10, + NULL, + (GAsyncReadyCallback)release_client_ready, + NULL); } static void @@ -160,12 +162,12 @@ get_ids_ready (QmiClientDms *client, } static void -client_dms_new_ready (GObject *unused, - GAsyncResult *res) +allocate_client_ready (QmiDevice *device, + GAsyncResult *res) { GError *error = NULL; - ctx->client = qmi_client_dms_new_finish (res, &error); + ctx->client = (QmiClientDms *)qmi_device_allocate_client_finish (device, res, &error); if (!ctx->client) { g_printerr ("error: couldn't create DMS client: %s\n", error->message); @@ -197,8 +199,10 @@ qmicli_dms_run (QmiDevice *device, ctx->cancellable = g_object_ref (cancellable); /* Create a new DMS client */ - qmi_client_dms_new (device, - cancellable, - (GAsyncReadyCallback)client_dms_new_ready, - NULL); + qmi_device_allocate_client (device, + QMI_SERVICE_DMS, + 10, + cancellable, + (GAsyncReadyCallback)allocate_client_ready, + NULL); } diff --git a/cli/qmicli.c b/cli/qmicli.c index 6c4ab1b..3796b3c 100644 --- a/cli/qmicli.c +++ b/cli/qmicli.c @@ -161,11 +161,8 @@ device_open_ready (QmiDevice *device, /* As soon as we get the QmiDevice, launch the specific action requested */ - /* CTL options? */ - if (qmicli_ctl_options_enabled ()) - qmicli_ctl_run (device, cancellable); /* DMS options? */ - else if (qmicli_dms_options_enabled ()) + if (qmicli_dms_options_enabled ()) qmicli_dms_run (device, cancellable); /* No options? */ else { @@ -210,8 +207,6 @@ int main (int argc, char **argv) /* Setup option context, process it and destroy it */ context = g_option_context_new ("- Control QMI devices"); g_option_context_add_group (context, - qmicli_ctl_get_option_group ()); - g_option_context_add_group (context, qmicli_dms_get_option_group ()); g_option_context_add_main_entries (context, main_entries, NULL); g_option_context_parse (context, &argc, &argv, NULL); diff --git a/cli/qmicli.h b/cli/qmicli.h index 779e58b..d21d1a0 100644 --- a/cli/qmicli.h +++ b/cli/qmicli.h @@ -26,12 +26,6 @@ /* Common */ void qmicli_async_operation_done (void); -/* CTL group */ -GOptionGroup *qmicli_ctl_get_option_group (void); -gboolean qmicli_ctl_options_enabled (void); -void qmicli_ctl_run (QmiDevice *device, - GCancellable *cancellable); - /* DMS group */ GOptionGroup *qmicli_dms_get_option_group (void); gboolean qmicli_dms_options_enabled (void); diff --git a/src/Makefile.am b/src/Makefile.am index 3a99a6d..aec5d77 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -38,7 +38,7 @@ qmi-enum-types.c: $(ENUMS) qmi-enum-types.h $(top_srcdir)/build-aux/qmi-enum-typ $(ENUMS) > $@ # Additional dependencies -qmi-device.c: qmi-error-types.h +qmi-device.c: qmi-error-types.h qmi-enum-types.h qmi-client.c: qmi-error-types.h qmi-enum-types.h qmi-client-ctl.c: qmi-error-types.h qmi-enum-types.h qmi-message.c: qmi-error-types.h qmi-enum-types.h @@ -68,5 +68,4 @@ include_HEADERS = \ qmi-enums.h qmi-enum-types.h \ qmi-device.h \ qmi-client.h \ - qmi-ctl.h qmi-client-ctl.h \ qmi-dms.h qmi-client-dms.h diff --git a/src/qmi-client-dms.c b/src/qmi-client-dms.c index 0ab6402..3c13deb 100644 --- a/src/qmi-client-dms.c +++ b/src/qmi-client-dms.c @@ -100,48 +100,6 @@ qmi_client_dms_get_ids (QmiClientDms *self, } /*****************************************************************************/ -/* New DMS client */ - -/** - * 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. - */ -QmiClientDms * -qmi_client_dms_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_CLIENT_DMS (ret) : NULL); -} - -void -qmi_client_dms_new (QmiDevice *device, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_async_initable_new_async (QMI_TYPE_CLIENT_DMS, - G_PRIORITY_DEFAULT, - cancellable, - callback, - user_data, - QMI_CLIENT_DEVICE, device, - QMI_CLIENT_SERVICE, QMI_SERVICE_DMS, - NULL); -} - -/*****************************************************************************/ static void qmi_client_dms_init (QmiClientDms *self) diff --git a/src/qmi-client-dms.h b/src/qmi-client-dms.h index 60021b2..2d4dd06 100644 --- a/src/qmi-client-dms.h +++ b/src/qmi-client-dms.h @@ -51,14 +51,6 @@ struct _QmiClientDmsClass { GType qmi_client_dms_get_type (void); -/* New DMS client */ -void qmi_client_dms_new (QmiDevice *device, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -QmiClientDms *qmi_client_dms_new_finish (GAsyncResult *res, - GError **error); - /* Get IDs */ void qmi_client_dms_get_ids (QmiClientDms *self, guint timeout, diff --git a/src/qmi-client.c b/src/qmi-client.c index e504d83..abf1ed5 100644 --- a/src/qmi-client.c +++ b/src/qmi-client.c @@ -27,10 +27,7 @@ #include "qmi-client.h" #include "qmi-client-ctl.h" -static void async_initable_iface_init (GAsyncInitableIface *iface); - -G_DEFINE_TYPE_EXTENDED (QmiClient, qmi_client, G_TYPE_OBJECT, G_TYPE_FLAG_ABSTRACT, - G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)); +G_DEFINE_ABSTRACT_TYPE (QmiClient, qmi_client, G_TYPE_OBJECT); enum { PROP_0, @@ -154,113 +151,6 @@ qmi_client_get_next_transaction_id (QmiClient *self) } /*****************************************************************************/ -/* Explicitly release the CID */ - -/** - * qmi_client_release_finish: - * @self: a #QmiClient. - * @res: a #GAsyncResult. - * @error: a #GError. - * - * Finishes an asynchronous CID release operation started with qmi_client_release(). - * - * Returns: #TRUE if successful, #FALSE if @error is set. - */ -gboolean -qmi_client_release_finish (QmiClient *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, - GSimpleAsyncResult *simple) -{ - GError *error = NULL; - - if (!qmi_client_ctl_release_cid_finish (client_ctl, res, &error)) - g_simple_async_result_take_error (simple, error); - else { - QmiClient *self; - - /* Recover self and reset the CID */ - self = QMI_CLIENT (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); - self->priv->cid = QMI_CID_NONE; - g_object_unref (self); - - g_simple_async_result_set_op_res_gboolean (simple, TRUE); - } - g_simple_async_result_complete (simple); - g_object_unref (simple); -} - -/** - * qmi_client_release: - * @self: a #QmiClient. - * @timeout: maximum time, in seconds, to wait for the CID to be released. - * - * Asynchronously releases the client ID of a #QmiClient. - * - * Once the #QmiClient has released its CID, it cannot be used any more to - * perform operations. Therefore, it is only meaningful to explicitly release - * the CID, when the user needs to know whether it gets properly released. If - * this information is not important, the user can rely on the implicit - * release performed when the last reference of #QmiClient is destroyed. - * - * When the operation is finished @callback will be called. You can then call - * qmi_client_release_finish() to get the result of the operation. - */ -void -qmi_client_release (QmiClient *self, - guint timeout, - GAsyncReadyCallback callback, - gpointer user_data) -{ - GError *error = NULL; - GSimpleAsyncResult *result; - - result = g_simple_async_result_new (G_OBJECT (self), - callback, - user_data, - qmi_client_release); - - /* Unregister client from the device */ - if (!qmi_device_unregister_client (self->priv->device, self, &error)) { - g_simple_async_result_take_error (result, error); - g_simple_async_result_complete_in_idle (result); - g_object_unref (result); - return; - } - - /* The CTL service client uses the implicit client (CID == 0), - * no need to explicitly release it */ - if (self->priv->service == QMI_SERVICE_CTL) { - g_simple_async_result_set_op_res_gboolean (result, TRUE); - g_simple_async_result_complete_in_idle (result); - g_object_unref (result); - return; - } - - /* The CID may already be released */ - if (self->priv->cid == QMI_CID_NONE) { - g_simple_async_result_set_op_res_gboolean (result, TRUE); - g_simple_async_result_complete_in_idle (result); - g_object_unref (result); - return; - } - - qmi_client_ctl_release_cid (qmi_device_peek_client_ctl (self->priv->device), - self->priv->service, - self->priv->cid, - timeout, - NULL, - (GAsyncReadyCallback)client_ctl_release_cid_ready, - result); -} -/*****************************************************************************/ void qmi_client_process_indication (QmiClient *self, @@ -271,155 +161,6 @@ qmi_client_process_indication (QmiClient *self, } /*****************************************************************************/ -/* Init */ - -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 -allocate_cid_ready (QmiClientCtl *client_ctl, - GAsyncResult *res, - GSimpleAsyncResult *simple) -{ - QmiClient *self; - GError *error = NULL; - guint8 cid; - - cid = qmi_client_ctl_allocate_cid_finish (client_ctl, res, &error); - if (!cid) { - g_prefix_error (&error, "CID allocation failed in the CTL client: "); - g_simple_async_result_take_error (simple, error); - g_simple_async_result_complete (simple); - g_object_unref (simple); - return; - } - - /* Recover the QmiClient */ - self = QMI_CLIENT (g_async_result_get_source_object (G_ASYNC_RESULT (simple))); - - /* Store the CID */ - self->priv->cid = cid; - - /* Register the client in the device in order to get notifications */ - if (!qmi_device_register_client (self->priv->device, self, &error)) { - g_prefix_error (&error, "Couldn't register client in device: "); - g_simple_async_result_take_error (simple, error); - } else - g_simple_async_result_set_op_res_gboolean (simple, TRUE); - - g_simple_async_result_complete (simple); - g_object_unref (simple); - g_object_unref (self); -} - -static void -initable_init_async (GAsyncInitable *initable, - int io_priority, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - QmiClient *self = QMI_CLIENT (initable); - GSimpleAsyncResult *result; - GError *error = NULL; - - result = g_simple_async_result_new (G_OBJECT (initable), - callback, - user_data, - initable_init_async); - - /* We need a proper service set to initialize */ - g_assert (self->priv->service != QMI_SERVICE_UNKNOWN); - /* We need a proper device to initialize */ - g_assert (QMI_IS_DEVICE (self->priv->device)); - - if (self->priv->service != QMI_SERVICE_CTL) { - /* NOTE that we shouldn't allocate a CID for the QmiClientCtl, - * as it is the one actually used to allocate CIDs, and must - * exist already in the QmiDevice. */ - qmi_client_ctl_allocate_cid (qmi_device_peek_client_ctl (self->priv->device), - self->priv->service, - 5, /* wait up to 5s to get a CID */ - cancellable, - (GAsyncReadyCallback)allocate_cid_ready, - result); - return; - } - - /* For the QmiClientCtl, just done we are */ - - /* Register the client in the device in order to get notifications */ - if (!qmi_device_register_client (self->priv->device, self, &error)) { - g_prefix_error (&error, "Couldn't register client in device: "); - g_simple_async_result_take_error (result, error); - } else - g_simple_async_result_set_op_res_gboolean (result, TRUE); - - g_simple_async_result_complete_in_idle (result); - g_object_unref (result); -} - -/*****************************************************************************/ -/* Implicit release on dispose */ - -typedef struct { - guint8 cid; - QmiService service; -} ReleaseCidContext; - -static void -client_ctl_release_cid_on_dispose_ready (QmiClientCtl *client_ctl, - GAsyncResult *res, - ReleaseCidContext *ctx) -{ - GError *error = NULL; - - if (!qmi_client_ctl_release_cid_finish (client_ctl, res, &error)) { - g_debug ("[%s] Release of CID %u from service '%s' failed", - qmi_device_get_path_display (qmi_client_peek_device (QMI_CLIENT (client_ctl))), - ctx->cid, - qmi_service_get_string (ctx->service)); - } - - g_slice_free (ReleaseCidContext, ctx); -} - -static void -client_release_cid_on_dispose (QmiClient *self) -{ - ReleaseCidContext *ctx; - - /* The CTL service client uses the implicit client (CID == 0), - * no need to explicitly release it */ - if (self->priv->service == QMI_SERVICE_CTL) - return; - - /* The CID may already be released */ - if (self->priv->cid == QMI_CID_NONE) - return; - - /* NOTE! we cannot use qmi_client_release(), as that ensures that a - * reference to self is available while the operation is ongoing (which - * means it adds a new reference to self). We don't want this, as we're - * already disposing the object. */ - ctx = g_slice_new (ReleaseCidContext); - ctx->service = self->priv->service; - ctx->cid = self->priv->cid; - qmi_client_ctl_release_cid (qmi_device_peek_client_ctl (self->priv->device), - self->priv->service, - self->priv->cid, - 3, - NULL, - (GAsyncReadyCallback)client_ctl_release_cid_on_dispose_ready, - ctx); -} - -/*****************************************************************************/ static void set_property (GObject *object, @@ -431,15 +172,15 @@ set_property (GObject *object, switch (prop_id) { case PROP_DEVICE: - g_assert (self->priv->device == NULL); - self->priv->device = g_value_dup_object (value); + /* NOTE!! We do NOT keep a reference to the device here. + * Clients are OWNED by the device */ + self->priv->device = g_value_get_object (value); break; case PROP_SERVICE: self->priv->service = g_value_get_enum (value); break; case PROP_CID: - /* Not writable */ - g_assert_not_reached (); + self->priv->cid = (guint8)g_value_get_uint (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -485,28 +226,6 @@ qmi_client_init (QmiClient *self) } static void -dispose (GObject *object) -{ - QmiClient *self = QMI_CLIENT (object); - - /* This will launch an async operation to release the CID. - * We won't be able to use the client in the callback of the async - * operation, as it is being disposed just here. */ - client_release_cid_on_dispose (self); - - g_clear_object (&self->priv->device); - - G_OBJECT_CLASS (qmi_client_parent_class)->dispose (object); -} - -static void -async_initable_iface_init (GAsyncInitableIface *iface) -{ - iface->init_async = initable_init_async; - iface->init_finish = initable_init_finish; -} - -static void qmi_client_class_init (QmiClientClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); @@ -515,14 +234,13 @@ qmi_client_class_init (QmiClientClass *klass) object_class->get_property = get_property; object_class->set_property = set_property; - object_class->dispose = dispose; properties[PROP_DEVICE] = g_param_spec_object (QMI_CLIENT_DEVICE, "Device", "The QMI device", QMI_TYPE_DEVICE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_DEVICE, properties[PROP_DEVICE]); properties[PROP_SERVICE] = @@ -531,7 +249,7 @@ qmi_client_class_init (QmiClientClass *klass) "QMI service this client is using", QMI_TYPE_SERVICE, QMI_SERVICE_UNKNOWN, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_SERVICE, properties[PROP_SERVICE]); properties[PROP_CID] = @@ -541,6 +259,6 @@ qmi_client_class_init (QmiClientClass *klass) 0, G_MAXUINT8, QMI_CID_NONE, - G_PARAM_READABLE); + G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CID, properties[PROP_CID]); } diff --git a/src/qmi-client.h b/src/qmi-client.h index b89e3e3..8feed3f 100644 --- a/src/qmi-client.h +++ b/src/qmi-client.h @@ -30,6 +30,9 @@ G_BEGIN_DECLS +#define QMI_CID_NONE 0x00 +#define QMI_CID_BROADCAST 0xFF + #define QMI_TYPE_CLIENT (qmi_client_get_type ()) #define QMI_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QMI_TYPE_CLIENT, QmiClient)) #define QMI_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QMI_TYPE_CLIENT, QmiClientClass)) @@ -67,14 +70,6 @@ guint8 qmi_client_get_cid (QmiClient *self); guint16 qmi_client_get_next_transaction_id (QmiClient *self); -void qmi_client_release (QmiClient *self, - guint timeout, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean qmi_client_release_finish (QmiClient *self, - GAsyncResult *res, - GError **error); - /* not part of the public API */ void qmi_client_process_indication (QmiClient *self, QmiMessage *message); diff --git a/src/qmi-device.c b/src/qmi-device.c index 410a60d..7217a06 100644 --- a/src/qmi-device.c +++ b/src/qmi-device.c @@ -30,6 +30,7 @@ #include "qmi-device.h" #include "qmi-message.h" #include "qmi-client-ctl.h" +#include "qmi-client-dms.h" #include "qmi-utils.h" #include "qmi-error-types.h" #include "qmi-enum-types.h" @@ -257,44 +258,6 @@ qmi_device_get_file (QmiDevice *self) } /** - * qmi_device_peek_client_ctl: - * @self: a #QmiDevice. - * - * Get the #QmiClientCtl handled by 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. - */ -QmiClientCtl * -qmi_device_peek_client_ctl (QmiDevice *self) -{ - g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); - - return self->priv->client_ctl; -} - -/** - * qmi_device_get_client_ctl: - * @self: a #QmiDevice. - * - * Get the #QmiClientCtl handled by this #QmiDevice. - * - * Returns: a #QmiClientCtl that must be freed with g_object_unref(). - */ -QmiClientCtl * -qmi_device_get_client_ctl (QmiDevice *self) -{ - QmiClientCtl *client_ctl = NULL; - - g_return_val_if_fail (QMI_IS_DEVICE (self), NULL); - - g_object_get (G_OBJECT (self), - QMI_DEVICE_CLIENT_CTL, &client_ctl, - NULL); - return client_ctl; -} - -/** * qmi_device_peek_file: * @self: a #QmiDevice. * @@ -360,7 +323,7 @@ qmi_device_is_open (QmiDevice *self) } /*****************************************************************************/ -/* Register clients that want to receive indications */ +/* Register/Unregister clients that want to receive indications */ static gpointer build_registered_client_key (guint8 cid, @@ -369,19 +332,13 @@ build_registered_client_key (guint8 cid, return GUINT_TO_POINTER (((guint8)service << 8) | cid); } -gboolean -qmi_device_register_client (QmiDevice *self, - QmiClient *client, - GError **error) +static gboolean +register_client (QmiDevice *self, + QmiClient *client, + GError **error) { gpointer key; - if (G_UNLIKELY (!self->priv->registered_clients)) - self->priv->registered_clients = g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, - g_object_unref); - 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 @@ -402,28 +359,302 @@ qmi_device_register_client (QmiDevice *self, return TRUE; } -gboolean -qmi_device_unregister_client (QmiDevice *self, - QmiClient *client, - GError **error) +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; +} 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 +allocate_cid_ready (QmiClientCtl *client_ctl, + GAsyncResult *res, + AllocateClientContext *ctx) +{ + QmiClient *client; + GError *error = NULL; + guint8 cid; gpointer key; - key = build_registered_client_key (qmi_client_get_cid (client), - qmi_client_get_service (client)); - if (!g_hash_table_remove (self->priv->registered_clients, key)) { - g_set_error (error, - QMI_CORE_ERROR, - QMI_CORE_ERROR_FAILED, - "Cannot unregister client with CID '%u' and service '%s': Not found", - qmi_client_get_cid (client), - qmi_service_get_string (qmi_client_get_service (client))); - return FALSE; + cid = qmi_client_ctl_allocate_cid_finish (client_ctl, res, &error); + if (!cid) { + 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; } - return TRUE; + /* 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, 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'", + 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; + } + + /* 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); } +/** + * qmi_device_allocate_client: + * @self: a #QmiDevice. + * @service: a valid #QmiService. + * @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. + * + * 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. + */ +void +qmi_device_allocate_client (QmiDevice *self, + QmiService service, + 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; + + 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; + + 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 */ + qmi_client_ctl_allocate_cid (self->priv->client_ctl, + ctx->service, + timeout, + cancellable, + (GAsyncReadyCallback)allocate_cid_ready, + 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; + + /* Note: even if we return an error, the client is to be considered + * released! (so shouldn't be used) */ + + if (!qmi_client_ctl_release_cid_finish (client_ctl, res, &error)) + g_simple_async_result_take_error (ctx->result, error); + else + g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); + + release_client_context_complete_and_free (ctx); +} + +/** + * qmi_device_release_client: + * @self: a #QmiDevice. + * @client: the #QmiClient to release. + * @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, + guint timeout, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + ReleaseClientContext *ctx; + guint8 cid; + QmiService service; + + 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 = 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); + + /* 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); + + /* And now, really try to release the CID */ + qmi_client_ctl_release_cid (self->priv->client_ctl, + service, + cid, + timeout, + cancellable, + (GAsyncReadyCallback)client_ctl_release_cid_ready, + ctx); +} + + /*****************************************************************************/ /* Open device */ @@ -1044,31 +1275,6 @@ initable_init_finish (GAsyncInitable *initable, } static void -client_ctl_ready (GAsyncInitable *initable, - GAsyncResult *res, - InitContext *ctx) -{ - GError *error = NULL; - GObject *obj; - - obj = g_async_initable_new_finish (initable, res, &error); - if (!obj) { - g_prefix_error (&error, - "Couldn't create CTL client: "); - g_simple_async_result_take_error (ctx->result, error); - init_context_complete_and_free (ctx); - return; - } - - /* Keep the client */ - ctx->self->priv->client_ctl = QMI_CLIENT_CTL (obj); - - /* Done we are */ - g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE); - init_context_complete_and_free (ctx); -} - -static void query_info_async_ready (GFile *file, GAsyncResult *res, InitContext *ctx) @@ -1096,16 +1302,22 @@ query_info_async_ready (GFile *file, } g_object_unref (info); - /* Create the implicit CTL client - * We are giving already the CID of the CTL client, the default one */ - g_async_initable_new_async (QMI_TYPE_CLIENT_CTL, - G_PRIORITY_DEFAULT, - ctx->cancellable, - (GAsyncReadyCallback)client_ctl_ready, - ctx, - QMI_CLIENT_DEVICE, ctx->self, - QMI_CLIENT_SERVICE, QMI_SERVICE_CTL, - NULL); + /* 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 @@ -1202,6 +1414,23 @@ 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 @@ -1210,6 +1439,17 @@ 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); @@ -1227,12 +1467,7 @@ finalize (GObject *object) g_hash_table_unref (self->priv->transactions); } - /* Registered clients keep refs to the device, so it's actually - * impossible to have any content in the HT */ - if (self->priv->registered_clients) { - g_assert (g_hash_table_size (self->priv->registered_clients) == 0); - g_hash_table_unref (self->priv->registered_clients); - } + g_hash_table_unref (self->priv->registered_clients); g_free (self->priv->path); g_free (self->priv->path_display); diff --git a/src/qmi-device.h b/src/qmi-device.h index 5289cda..ee5b0ce 100644 --- a/src/qmi-device.h +++ b/src/qmi-device.h @@ -25,6 +25,8 @@ #include <glib-object.h> +#include "qmi-enums.h" + G_BEGIN_DECLS /* Forward reference of the QMI message, we don't want to include qmi-message.h @@ -32,7 +34,6 @@ G_BEGIN_DECLS typedef struct _QmiMessage QmiMessage; typedef struct _QmiClient QmiClient; -typedef struct _QmiClientCtl QmiClientCtl; #define QMI_TYPE_DEVICE (qmi_device_get_type ()) #define QMI_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QMI_TYPE_DEVICE, QmiDevice)) @@ -68,8 +69,6 @@ QmiDevice *qmi_device_new_finish (GAsyncResult *res, GFile *qmi_device_get_file (QmiDevice *self); GFile *qmi_device_peek_file (QmiDevice *self); -QmiClientCtl *qmi_device_get_client_ctl (QmiDevice *self); -QmiClientCtl *qmi_device_peek_client_ctl (QmiDevice *self); const gchar *qmi_device_get_path (QmiDevice *self); const gchar *qmi_device_get_path_display (QmiDevice *self); gboolean qmi_device_is_open (QmiDevice *self); @@ -86,6 +85,26 @@ gboolean qmi_device_open_finish (QmiDevice *self, gboolean qmi_device_close (QmiDevice *self, GError **error); +void qmi_device_allocate_client (QmiDevice *self, + QmiService service, + guint timeout, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +QmiClient *qmi_device_allocate_client_finish (QmiDevice *self, + GAsyncResult *res, + GError **error); + +void qmi_device_release_client (QmiDevice *self, + QmiClient *client, + guint timeout, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean qmi_device_release_client_finish (QmiDevice *self, + GAsyncResult *res, + GError **error); + void qmi_device_command (QmiDevice *self, QmiMessage *message, guint timeout, @@ -96,14 +115,6 @@ QmiMessage *qmi_device_command_finish (QmiDevice *self, GAsyncResult *res, GError **error); -/* not part of the public API */ -gboolean qmi_device_register_client (QmiDevice *self, - QmiClient *client, - GError **error); -gboolean qmi_device_unregister_client (QmiDevice *self, - QmiClient *client, - GError **error); - G_END_DECLS #endif /* _LIBQMI_GLIB_QMI_DEVICE_H_ */ diff --git a/src/qmi-enums.h b/src/qmi-enums.h index 6f2d55d..cf6d9aa 100644 --- a/src/qmi-enums.h +++ b/src/qmi-enums.h @@ -81,7 +81,4 @@ typedef enum { QMI_SERVICE_FLAG_INDICATION = 1 << 2, } QmiServiceFlag; -#define QMI_CID_NONE 0x00 -#define QMI_CID_BROADCAST 0xFF - #endif /* _LIBQMI_GLIB_QMI_ENUMS_H_ */ |