diff options
author | Aleksander Morgado <aleksander@lanedo.com> | 2012-04-24 16:59:01 +0200 |
---|---|---|
committer | Aleksander Morgado <aleksander@lanedo.com> | 2012-07-03 16:08:48 +0200 |
commit | 3acf5ce303eb101ce937e3bae2622faace3eefd9 (patch) | |
tree | fb99d25642cae8d461645f27eebb3c85a1ed38f1 | |
parent | d3d9360d4998a1d5ba96e5f8d93229afd7941915 (diff) |
device: allow clients to get registered if they want to receive indication messages
-rw-r--r-- | src/qmi-client.c | 35 | ||||
-rw-r--r-- | src/qmi-client.h | 8 | ||||
-rw-r--r-- | src/qmi-device.c | 232 | ||||
-rw-r--r-- | src/qmi-device.h | 9 |
4 files changed, 239 insertions, 45 deletions
diff --git a/src/qmi-client.c b/src/qmi-client.c index 45570ba..e504d83 100644 --- a/src/qmi-client.c +++ b/src/qmi-client.c @@ -219,6 +219,7 @@ qmi_client_release (QmiClient *self, GAsyncReadyCallback callback, gpointer user_data) { + GError *error = NULL; GSimpleAsyncResult *result; result = g_simple_async_result_new (G_OBJECT (self), @@ -226,6 +227,14 @@ qmi_client_release (QmiClient *self, 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) { @@ -251,6 +260,15 @@ qmi_client_release (QmiClient *self, (GAsyncReadyCallback)client_ctl_release_cid_ready, result); } +/*****************************************************************************/ + +void +qmi_client_process_indication (QmiClient *self, + QmiMessage *message) +{ + if (QMI_CLIENT_GET_CLASS (self)->process_indication) + QMI_CLIENT_GET_CLASS (self)->process_indication (self, message); +} /*****************************************************************************/ /* Init */ @@ -287,6 +305,13 @@ allocate_cid_ready (QmiClientCtl *client_ctl, /* 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); @@ -327,7 +352,14 @@ initable_init_async (GAsyncInitable *initable, } /* For the QmiClientCtl, just done we are */ - g_simple_async_result_set_op_res_gboolean (result, TRUE); + + /* 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); } @@ -387,7 +419,6 @@ client_release_cid_on_dispose (QmiClient *self) ctx); } - /*****************************************************************************/ static void diff --git a/src/qmi-client.h b/src/qmi-client.h index 637bed7..b89e3e3 100644 --- a/src/qmi-client.h +++ b/src/qmi-client.h @@ -52,6 +52,10 @@ struct _QmiClient { struct _QmiClientClass { GObjectClass parent; + + /* Virtual method to get indications processed */ + void (* process_indication) (QmiClient *self, + QmiMessage *message); }; GType qmi_client_get_type (void); @@ -71,6 +75,10 @@ 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); + G_END_DECLS #endif /* _LIBQMI_GLIB_QMI_CLIENT_H_ */ diff --git a/src/qmi-device.c b/src/qmi-device.c index 5d103e3..09185f5 100644 --- a/src/qmi-device.c +++ b/src/qmi-device.c @@ -59,6 +59,9 @@ struct _QmiDevicePrivate { /* HT to keep track of ongoing transactions */ GHashTable *transactions; + + /* HT of clients that want to get indications */ + GHashTable *registered_clients; }; #define BUFFER_SIZE 2048 @@ -352,8 +355,181 @@ qmi_device_is_open (QmiDevice *self) } /*****************************************************************************/ +/* Register clients that want to receive indications */ + +static gpointer +build_registered_client_key (guint8 cid, + QmiService service) +{ + return GUINT_TO_POINTER (((guint8)service << 8) | cid); +} + +gboolean +qmi_device_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 + * 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; +} + +gboolean +qmi_device_unregister_client (QmiDevice *self, + QmiClient *client, + GError **error) +{ + 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; + } + + return TRUE; +} + +/*****************************************************************************/ /* 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 { + /* Received message is a response in a transaction; handle QMI protocol + * errors */ + if (!qmi_message_get_response_result (message, &error)) { + transaction_complete_and_free (tr, NULL, error); + g_error_free (error); + } 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) { @@ -383,47 +559,8 @@ parse_response (QmiDevice *self) 0, qmi_message_get_length (message)); - /* 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); - } else if (qmi_message_get_client_id (message) == QMI_CID_BROADCAST) { - g_debug ("Broadcast QMI message received"); - /* TODO: notify to clients */ - } else { - Transaction *tr; - - /* Got a valid QMI message, do whatever we need to do with it now */ - -#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 */ - - tr = device_match_transaction (self, message); - if (!tr) - g_debug ("[%s] No transaction matched in received message", - self->priv->path_display); - else { - /* Received message is a response in a transaction; handle QMI protocol - * errors */ - if (!qmi_message_get_response_result (message, &error)) { - transaction_complete_and_free (tr, NULL, error); - g_error_free (error); - } else { - /* Report the reply message */ - transaction_complete_and_free (tr, message, NULL); - } - } - } + /* Play with the received message */ + process_message (self, message); qmi_message_unref (message); } while (self->priv->response->len > 0); @@ -1064,8 +1201,17 @@ finalize (GObject *object) /* Transactions keep refs to the device, so it's actually * impossible to have any content in the HT */ - g_assert (g_hash_table_size (self->priv->transactions) == 0); - g_hash_table_unref (self->priv->transactions); + if (self->priv->transactions) { + g_assert (g_hash_table_size (self->priv->transactions) == 0); + 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_free (self->priv->path); g_free (self->priv->path_display); diff --git a/src/qmi-device.h b/src/qmi-device.h index 8d20489..5289cda 100644 --- a/src/qmi-device.h +++ b/src/qmi-device.h @@ -31,6 +31,7 @@ G_BEGIN_DECLS * as it is not installable */ typedef struct _QmiMessage QmiMessage; +typedef struct _QmiClient QmiClient; typedef struct _QmiClientCtl QmiClientCtl; #define QMI_TYPE_DEVICE (qmi_device_get_type ()) @@ -95,6 +96,14 @@ 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_ */ |