| /* |
| * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "qmidev.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <glib.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <glib.h> |
| |
| #include <libqmi.h> |
| |
| #include "dev.h" |
| #include "error.h" |
| #include "file.h" |
| #include "mock.h" |
| #include "poller.h" |
| #include "qmi.h" |
| #include "qmictl.h" |
| #include "qmidev.h" |
| #include "qmidms.h" |
| #include "qmimsg.h" |
| #include "qmiwds.h" |
| #include "util.h" |
| |
| static const int qmidev_log_unhandled_messages = 0; |
| |
| struct qmidev { |
| void *priv; |
| |
| struct poller *poller; |
| |
| struct dev *dev; |
| struct polled *dev_polled; |
| |
| GList *callbacks; |
| GList *listeners; |
| GList *clients; |
| |
| int connected; |
| }; |
| |
| struct callback { |
| callback_fn func; |
| void *context; |
| }; |
| |
| struct listener { |
| struct client *client; |
| struct listen_criteria criteria; |
| qmidev_listen_callback_fn callback; |
| void *context; |
| }; |
| |
| struct client { |
| struct service *service; |
| |
| uint8_t client_id; |
| uint16_t next_transaction_id; |
| struct listener *event_listener; |
| GList *events; |
| |
| void *priv; |
| }; |
| |
| static int service_is_control(struct service *service) |
| { |
| return !service->service_id; |
| } |
| |
| static int call_listeners(struct qmidev *qmidev, struct qmimsg *message); |
| static void call_callbacks(struct qmidev *qmidev); |
| |
| static struct qmidev *qmidev_new(void) |
| { |
| struct qmidev *qmidev = g_slice_new(struct qmidev); |
| |
| qmidev->priv = NULL; |
| |
| qmidev->poller = poller_new(); |
| qmidev->dev = NULL; |
| qmidev->dev_polled = NULL; |
| |
| qmidev->callbacks = NULL; |
| qmidev->listeners = NULL; |
| qmidev->clients = NULL; |
| |
| qmidev->connected = 0; |
| |
| return qmidev; |
| } |
| |
| struct qmidev *qmidev_new_file(const char *path) |
| { |
| struct qmidev *qmidev; |
| struct dev *dev; |
| |
| assert(path); |
| |
| qmidev = qmidev_new(); |
| dev = file_new(qmidev, path); |
| if (!dev) { |
| free(qmidev); |
| return NULL; |
| } |
| qmidev->dev = dev; |
| |
| return qmidev; |
| } |
| |
| struct qmidev *qmidev_new_mock(void) |
| { |
| struct qmidev *qmidev; |
| struct dev *dev; |
| |
| qmidev = qmidev_new(); |
| dev = mock_new(); |
| qmidev->dev = dev; |
| |
| return qmidev; |
| } |
| |
| void *qmidev_priv(struct qmidev *qmidev) |
| { |
| assert(qmidev); |
| return qmidev->priv; |
| } |
| |
| void qmidev_set_priv(struct qmidev *qmidev, void *priv) |
| { |
| assert(qmidev); |
| qmidev->priv = priv; |
| } |
| |
| int qmidev_fd(struct qmidev *qmidev) |
| { |
| assert(qmidev); |
| return poller_fd(qmidev->poller); |
| } |
| |
| int qmidev_process(struct qmidev *qmidev) |
| { |
| assert(qmidev); |
| |
| call_callbacks(qmidev); |
| return poller_poll(qmidev->poller); |
| } |
| |
| struct listener *qmidev_listen(struct qmidev *qmidev, |
| struct client *client, |
| struct listen_criteria *criteria, |
| qmidev_listen_callback_fn callback, |
| void *context) |
| { |
| struct listener *listener; |
| |
| assert(qmidev); |
| assert(criteria); |
| assert(callback); |
| |
| listener = g_slice_new(struct listener); |
| listener->client = client; |
| listener->criteria = *criteria; |
| listener->callback = callback; |
| listener->context = context; |
| |
| qmidev->listeners = g_list_prepend(qmidev->listeners, listener); |
| |
| return listener; |
| } |
| |
| void qmidev_cancel_listen(struct qmidev *qmidev, struct listener *listener) |
| { |
| assert(qmidev); |
| assert(listener); |
| |
| qmidev->listeners = g_list_remove(qmidev->listeners, listener); |
| g_slice_free(struct listener, listener); |
| } |
| |
| struct call_listener_context { |
| struct qmidev *qmidev; |
| struct qmimsg *message; |
| uint8_t msg_service; |
| uint8_t msg_client; |
| uint8_t msg_qmi_flags; |
| uint16_t msg_transaction; |
| uint16_t msg_message; |
| int calls; |
| }; |
| |
| static void call_listener(void *data, void *user_data) |
| { |
| struct call_listener_context *context = user_data; |
| struct listener *listener = data; |
| struct listen_criteria *crit = &listener->criteria; |
| int flags = crit->flags; |
| int result; |
| |
| if (listener->client->service->service_id != context->msg_service) |
| return; |
| |
| if ((context->msg_client != QMI_CID_BROADCAST) && |
| (listener->client->client_id != context->msg_client)) |
| return; |
| |
| if ((flags & LISTEN_MATCH_QMI_FLAGS) && |
| (crit->qmi_flags != context->msg_qmi_flags)) |
| return; |
| |
| if ((flags & LISTEN_MATCH_TRANSACTION) && |
| (crit->transaction != context->msg_transaction)) |
| return; |
| |
| if ((flags & LISTEN_MATCH_MESSAGE) && |
| (crit->message != context->msg_message)) |
| return; |
| |
| result = listener->callback(context->qmidev, |
| listener->context, |
| context->message); |
| if (result) { |
| /* The glib documentation does not note this, but it is in fact safe to |
| call g_list_remove while called back from g_list_foreach. */ |
| qmidev_cancel_listen(context->qmidev, listener); |
| } |
| |
| context->calls++; |
| } |
| |
| static int call_listeners(struct qmidev *qmidev, struct qmimsg *message) |
| { |
| struct call_listener_context context; |
| context.qmidev = qmidev; |
| context.message = message; |
| qmimsg_get_header(message, |
| &context.msg_service, |
| &context.msg_client, |
| &context.msg_qmi_flags, |
| &context.msg_transaction, |
| &context.msg_message); |
| context.calls = 0; |
| g_list_foreach(qmidev->listeners, &call_listener, &context); |
| return context.calls == 0; |
| } |
| |
| |
| |
| static struct client *make_ctl_client(struct qmidev *qmidev) |
| { |
| struct client *client = g_slice_new(struct client); |
| |
| client->service = &qmictl_service; |
| client->client_id = 0x00; |
| client->next_transaction_id = 0x0001; |
| client->events = NULL; |
| client->priv = NULL; |
| |
| if (client->service->create_fn) |
| client->service->create_fn(qmidev, client); |
| |
| return client; |
| } |
| |
| struct transaction { |
| struct client *client; |
| uint16_t transaction_id; |
| |
| struct listener *response_listener; |
| response_fn callback; |
| void *context; |
| }; |
| |
| struct call_event_listeners_context { |
| struct qmidev *qmidev; |
| struct client *client; |
| uint8_t type; |
| uint16_t length; |
| void *value; |
| int calls; |
| }; |
| |
| static void call_event_listener(void *data, void *user_data) |
| { |
| struct call_event_listeners_context *context = user_data; |
| struct event *event = data; |
| |
| if (event->type != context->type) |
| return; |
| |
| event->callback(context->qmidev, |
| event->user_callback, |
| event->user_context, |
| context->length, |
| context->value); |
| |
| context->calls++; |
| } |
| |
| static void call_event_listeners(void *data, |
| uint8_t type, uint16_t length, void *value) |
| { |
| struct call_event_listeners_context *context = data; |
| |
| context->type = type; |
| context->length = length; |
| context->value = value; |
| context->calls = 0; |
| |
| g_list_foreach(context->client->events, &call_event_listener, context); |
| } |
| |
| static int event_message_received(struct qmidev *qmidev, |
| void *data, |
| struct qmimsg *message) |
| { |
| struct call_event_listeners_context context; |
| |
| context.qmidev = qmidev; |
| context.client = data; |
| qmimsg_tlv_foreach(message, &call_event_listeners, &context); |
| |
| return 0; |
| } |
| |
| struct get_client_context { |
| struct client *client; |
| qmidev_client_fn callback; |
| void *context; |
| }; |
| |
| static void got_cid(struct qmidev *qmidev, |
| void *data, |
| int status, |
| uint8_t cid) |
| { |
| struct get_client_context *context = data; |
| struct client *client = context->client; |
| struct listen_criteria event_criteria; |
| |
| if (status) { |
| g_slice_free(struct client, client); |
| client = NULL; |
| goto out; |
| } |
| |
| client->client_id = cid; |
| if (client->service->create_fn) |
| client->service->create_fn(qmidev, client); |
| |
| event_criteria.flags = LISTEN_MATCH_QMI_FLAGS | |
| LISTEN_MATCH_MESSAGE; |
| if (service_is_control(client->service)) |
| event_criteria.qmi_flags = QMI_FLAG_CTL_INDICATION; |
| else |
| event_criteria.qmi_flags = QMI_FLAG_SVC_INDICATION; |
| event_criteria.message = client->service->event_msg_id; |
| |
| client->event_listener = qmidev_listen(qmidev, client, |
| &event_criteria, |
| &event_message_received, |
| client); |
| |
| out: |
| context->callback(qmidev, context->context, status, client); |
| g_slice_free(struct get_client_context, context); |
| } |
| |
| int qmidev_get_client(struct qmidev *qmidev, |
| struct service *service, |
| qmidev_client_fn caller_callback, |
| void *caller_context) |
| { |
| struct client *client; |
| struct get_client_context *context; |
| int result; |
| |
| client = g_slice_new(struct client); |
| client->service = service; |
| client->next_transaction_id = 0x0001; |
| client->events = NULL; |
| client->priv = NULL; |
| |
| context = g_slice_new(struct get_client_context); |
| context->client = client; |
| context->callback = caller_callback; |
| context->context = caller_context; |
| |
| result = qmictl_get_cid(qmidev, |
| client->service->service_id, |
| &got_cid, |
| context); |
| if (result) { |
| g_slice_free(struct get_client_context, context); |
| g_slice_free(struct client, client); |
| } |
| |
| return result; |
| } |
| |
| struct release_client_context { |
| struct client *client; |
| qmidev_response_fn callback; |
| void *context; |
| }; |
| |
| static void released_cid(struct qmidev *qmidev, |
| void *data, |
| int status) |
| { |
| struct release_client_context *context = data; |
| struct client *client = context->client; |
| |
| if (status) |
| goto out; |
| |
| qmidev_cancel_listen(qmidev, client->event_listener); |
| |
| /* TODO: Free things that reference client (transactions). */ |
| |
| if (context->client->service->destroy_fn) |
| context->client->service->destroy_fn(qmidev, context->client); |
| g_slice_free(struct client, context->client); |
| |
| out: |
| context->callback(qmidev, context->context, status); |
| g_slice_free(struct release_client_context, context); |
| } |
| |
| int qmidev_release_client(struct qmidev *qmidev, |
| struct client *client, |
| qmidev_response_fn caller_callback, |
| void *caller_context) |
| { |
| struct release_client_context *context; |
| int result; |
| |
| context = g_slice_new(struct release_client_context); |
| context->client = client; |
| context->callback = caller_callback; |
| context->context = caller_context; |
| |
| result = qmictl_release_cid(qmidev, |
| client->service->service_id, |
| client->client_id, |
| &released_cid, |
| context); |
| if (result) |
| g_slice_free(struct release_client_context, context); |
| |
| return result; |
| } |
| |
| static gint client_compare_service(gconstpointer a, gconstpointer b) |
| { |
| const struct client *client_a = a; |
| const struct client *client_b = b; |
| |
| return client_a->service - client_b->service; |
| } |
| |
| static struct client *find_client(struct qmidev *qmidev, |
| struct service *service) |
| { |
| struct client wanted; |
| GList *found_list; |
| |
| wanted.service = service; |
| found_list = g_list_find_custom(qmidev->clients, |
| &wanted, |
| &client_compare_service); |
| |
| if (found_list) |
| return (struct client *) found_list->data; |
| else |
| return NULL; |
| } |
| |
| struct client *qmidev_default_client(struct qmidev *qmidev, |
| struct service *service) |
| { |
| return find_client(qmidev, service); |
| } |
| |
| static struct transaction *make_transaction(struct client *client, |
| response_fn callback, |
| void *context) |
| { |
| struct transaction *transaction; |
| |
| transaction = g_slice_new(struct transaction); |
| transaction->client = client; |
| transaction->transaction_id = client->next_transaction_id; |
| transaction->callback = callback; |
| transaction->context = context; |
| |
| return transaction; |
| } |
| |
| static void increment_transaction_id(struct client *client) |
| { |
| static const uint16_t min_transaction_id = 0x0001; |
| uint16_t max_transaction_id; |
| |
| if (client->service->service_id == QMI_SVC_CTL) |
| max_transaction_id = 0x00FF; |
| else |
| max_transaction_id = 0xFFFF; |
| |
| if (client->next_transaction_id < max_transaction_id) |
| client->next_transaction_id++; |
| else |
| client->next_transaction_id = min_transaction_id; |
| } |
| |
| static int response_received(struct qmidev *qmidev, |
| void *data, |
| struct qmimsg *message) |
| { |
| struct transaction *transaction = data; |
| |
| transaction->callback(qmidev, |
| transaction->context, |
| qmi_result(message), |
| message); |
| g_slice_free(struct transaction, transaction); |
| |
| /* remove listener */ |
| return 1; |
| } |
| |
| int qmidev_client_request(struct qmidev *qmidev, |
| struct client *client, |
| uint16_t msg_id, |
| request_fn make_request_fn, |
| void *request_context, |
| response_fn handle_response_fn, |
| void *response_context) |
| { |
| struct transaction *transaction; |
| struct qmimsg *request_message; |
| struct listen_criteria response_criteria; |
| int result; |
| |
| transaction = make_transaction(client, handle_response_fn, response_context); |
| |
| qmimsg_new(0, client->service->service_id, client->client_id, |
| 0, transaction->transaction_id, msg_id, |
| &request_message); |
| if (make_request_fn) { |
| result = make_request_fn(request_context, request_message); |
| if (result) |
| goto fail; |
| } |
| |
| response_criteria.flags = LISTEN_MATCH_TRANSACTION | |
| LISTEN_MATCH_QMI_FLAGS | |
| LISTEN_MATCH_MESSAGE; |
| if (service_is_control(client->service)) |
| response_criteria.qmi_flags = QMI_FLAG_CTL_RESPONSE; |
| else |
| response_criteria.qmi_flags = QMI_FLAG_SVC_RESPONSE; |
| response_criteria.transaction = client->next_transaction_id; |
| response_criteria.message = msg_id; |
| |
| transaction->response_listener = |
| qmidev_listen(qmidev, client, |
| &response_criteria, |
| &response_received, |
| transaction); |
| |
| /* TODO: Timeout. */ |
| |
| result = qmidev_send(qmidev, request_message); |
| |
| increment_transaction_id(client); |
| |
| return 0; |
| |
| fail: |
| g_slice_free(struct transaction, transaction); |
| qmimsg_free(request_message); |
| return result; |
| } |
| |
| int qmidev_request(struct qmidev *qmidev, |
| struct service *service, |
| uint16_t msg_id, |
| request_fn make_request_fn, |
| void *request_context, |
| response_fn handle_response_fn, |
| void *response_context) |
| { |
| struct client *client; |
| |
| client = find_client(qmidev, service); |
| if (!client) |
| return QMI_ERR_SERVICE_UNAVAILABLE; |
| |
| return qmidev_client_request(qmidev, client, msg_id, |
| make_request_fn, request_context, |
| handle_response_fn, response_context); |
| } |
| |
| struct void_context { |
| qmidev_response_fn callback; |
| void *context; |
| }; |
| |
| static void void_response_received(struct qmidev *qmidev, |
| void *data, |
| int result, |
| struct qmimsg *message) |
| { |
| struct void_context *context = data; |
| |
| /* nothing useful in message. */ |
| message = message; |
| |
| context->callback(qmidev, context->context, result); |
| g_slice_free(struct void_context, context); |
| } |
| |
| int qmidev_void_request(struct qmidev *qmidev, |
| struct service *service, |
| uint16_t msg_id, |
| request_fn make_request_fn, |
| void *request_context, |
| qmidev_response_fn handle_response_fn, |
| void *response_context) |
| { |
| struct void_context *context; |
| int result; |
| |
| context = g_slice_new(struct void_context); |
| context->callback = handle_response_fn; |
| context->context = response_context; |
| |
| result = qmidev_request(qmidev, service, msg_id, |
| make_request_fn, request_context, |
| &void_response_received, context); |
| if (result) |
| g_slice_free(struct void_context, context); |
| return result; |
| } |
| |
| void *client_priv(struct client *client) |
| { |
| assert(client); |
| return client->priv; |
| } |
| |
| void client_set_priv(struct client *client, void *priv) |
| { |
| assert(client); |
| client->priv = priv; |
| } |
| |
| void event_init(struct event *event, uint8_t type, event_fn callback) |
| { |
| event->type = type; |
| event->listener = NULL; |
| event->callback = callback; |
| event->user_callback = NULL; |
| event->user_context = NULL; |
| } |
| |
| struct set_callback_context { |
| uint8_t event_type; |
| uint8_t report_event; |
| qmidev_response_fn callback; |
| void *context; |
| }; |
| |
| static void trivial_set_callback_done(struct qmidev *qmidev, |
| void *data) |
| { |
| struct set_callback_context *context = data; |
| context->callback(qmidev, context->context, 0); |
| g_slice_free(struct set_callback_context, context); |
| } |
| |
| int set_event_report_request(void *data, |
| struct qmimsg *message) |
| { |
| struct set_callback_context *context = data; |
| return qmimsg_tlv_add(message, |
| context->event_type, |
| sizeof(context->report_event), |
| &context->report_event); |
| } |
| |
| void set_event_report_response(struct qmidev *qmidev, |
| void *data, |
| int result, |
| struct qmimsg *message) |
| { |
| struct set_callback_context *context = data; |
| |
| /* nothing useful in message */ |
| message = message; |
| |
| context->callback(qmidev, context->context, result); |
| g_slice_free(struct set_callback_context, context); |
| } |
| |
| int qmidev_set_callback(struct qmidev *qmidev, |
| struct client *client, |
| struct event *event, |
| void *user_callback, |
| void *user_context, |
| qmidev_response_fn caller_callback, |
| void *caller_context) |
| { |
| struct set_callback_context *context; |
| int active_before, active_after; |
| int result; |
| |
| active_before = !!event->user_callback; |
| active_after = !!user_callback; |
| |
| context = g_slice_new(struct set_callback_context); |
| context->event_type = event->type; |
| context->report_event = active_after ? 0x01 : 0x00; |
| context->callback = caller_callback; |
| context->context = caller_context; |
| |
| event->user_callback = user_callback; |
| event->user_context = user_context; |
| |
| if (active_before == active_after) { |
| qmidev_call_later(qmidev, &trivial_set_callback_done, context); |
| return 0; |
| } |
| |
| if (active_after) |
| client->events = g_list_prepend(client->events, event); |
| else |
| client->events = g_list_remove(client->events, event); |
| |
| result = qmidev_client_request(qmidev, client, |
| client->service->event_msg_id, |
| &set_event_report_request, context, |
| &set_event_report_response, context); |
| if (result) |
| g_slice_free(struct set_callback_context, context); |
| return result; |
| } |
| |
| |
| struct connect_context { |
| GQueue *pending_services; |
| |
| qmidev_response_fn caller_callback; |
| void *caller_context; |
| }; |
| |
| static void connect_next_service(struct qmidev *qmidev, |
| struct connect_context *context); |
| |
| static void got_client(struct qmidev *qmidev, |
| void *data, |
| int status, |
| struct client *client) |
| { |
| struct connect_context *context = data; |
| |
| if (!status) { |
| qmidev->clients = g_list_prepend(qmidev->clients, client); |
| } |
| |
| connect_next_service(qmidev, context); |
| } |
| |
| static void connect_next_service(struct qmidev *qmidev, |
| struct connect_context *context) |
| { |
| struct service *next_service; |
| int result; |
| |
| assert(qmidev); |
| assert(context); |
| |
| if (!g_queue_is_empty(context->pending_services)) { |
| next_service = g_queue_pop_head(context->pending_services); |
| result = qmidev_get_client(qmidev, next_service, &got_client, context); |
| if (result) |
| context->caller_callback(qmidev, context->caller_context, result); |
| } else { |
| context->caller_callback(qmidev, context->caller_context, 0); |
| g_slice_free(struct connect_context, context); |
| } |
| } |
| |
| int qmidev_connect(struct qmidev *qmidev, |
| qmidev_response_fn caller_callback, |
| void *caller_context) |
| { |
| struct connect_context *context; |
| int result; |
| |
| assert(qmidev); |
| assert(caller_callback); |
| |
| if (qmidev->connected) |
| return QMI_ERR_ALREADY_CONNECTED; |
| |
| result = dev_open(qmidev->dev); |
| /* TODO: Should this go through the callback instead? */ |
| if (result) |
| return result; |
| |
| qmidev->connected = 1; |
| |
| qmidev->clients = g_list_prepend(qmidev->clients, make_ctl_client(qmidev)); |
| |
| context = g_slice_new(struct connect_context); |
| context->caller_callback = caller_callback; |
| context->caller_context = caller_context; |
| context->pending_services = g_queue_new(); |
| |
| g_queue_push_tail(context->pending_services, &qmidms_service); |
| g_queue_push_tail(context->pending_services, &qmiwds_service); |
| |
| connect_next_service(qmidev, context); |
| |
| return 0; |
| } |
| |
| struct disconnect_context { |
| qmidev_response_fn caller_callback; |
| void *caller_context; |
| }; |
| |
| static void disconnected_services(struct qmidev *qmidev, |
| struct disconnect_context *context) |
| { |
| int result = dev_close(qmidev->dev); |
| qmidev->connected = 0; |
| context->caller_callback(qmidev, context->caller_context, result); |
| g_slice_free(struct disconnect_context, context); |
| } |
| |
| static void disconnect_next_service(struct qmidev *qmidev, |
| struct disconnect_context *context); |
| |
| static void released_client(struct qmidev *qmidev, |
| void *data, |
| int status) |
| { |
| if (status) { |
| /* TODO: What do we do if release fails? */ |
| } |
| |
| disconnect_next_service(qmidev, data); |
| } |
| |
| static void disconnect_next_service(struct qmidev *qmidev, |
| struct disconnect_context *context) |
| { |
| struct client *next_client; |
| int result; |
| |
| if (!qmidev->clients) { |
| disconnected_services(qmidev, context); |
| return; |
| } |
| |
| next_client = qmidev->clients->data; |
| qmidev->clients = g_list_remove(qmidev->clients, next_client); |
| if (!next_client->client_id) { |
| /* This is the CTL client. Free it by hand and try the next one. */ |
| g_slice_free(struct client, next_client); |
| disconnect_next_service(qmidev, context); |
| return; |
| } |
| |
| result = qmidev_release_client(qmidev, next_client, |
| &released_client, context); |
| if (result) |
| context->caller_callback(qmidev, context->caller_context, result); |
| } |
| |
| int qmidev_disconnect(struct qmidev *qmidev, |
| qmidev_response_fn caller_callback, |
| void *caller_context) |
| { |
| struct disconnect_context *context; |
| |
| assert(qmidev); |
| assert(caller_callback); |
| |
| if (!qmidev->connected) |
| return QMI_ERR_NOT_CONNECTED; |
| |
| context = g_slice_new(struct disconnect_context); |
| context->caller_callback = caller_callback; |
| context->caller_context = caller_context; |
| |
| disconnect_next_service(qmidev, context); |
| |
| return 0; |
| } |
| |
| static int dev_read_fn(void *ctx, void *buf, size_t len) |
| { |
| struct qmidev *qmidev; |
| assert(ctx); |
| qmidev = ctx; |
| assert(qmidev->dev); |
| |
| return dev_read(qmidev->dev, buf, len); |
| } |
| |
| static void dev_read_cb(struct polled *polled) |
| { |
| struct qmidev *qmidev; |
| struct qmimsg *msg; |
| int result; |
| |
| assert(polled); |
| qmidev = polled_priv(polled); |
| assert(qmidev); |
| |
| result = qmimsg_read(&dev_read_fn, qmidev, &msg); |
| if (result) |
| return; |
| if (call_listeners(qmidev, msg) && qmidev_log_unhandled_messages) { |
| fprintf(stderr, "Unhandled message received:\n"); |
| qmimsg_print(msg); |
| } |
| qmimsg_free(msg); |
| } |
| |
| static void dev_close_cb(struct polled *polled) |
| { |
| struct qmidev *qmidev; |
| |
| assert(polled); |
| qmidev = polled_priv(polled); |
| assert(qmidev); |
| |
| qmidev_clear_dev_fd(qmidev); |
| } |
| |
| int qmidev_set_dev_fd(struct qmidev *qmidev, int fd) |
| { |
| assert(qmidev); |
| assert(fd >= 0); |
| assert(!qmidev->dev_polled); |
| |
| qmidev->dev_polled = poller_add(qmidev->poller, fd); |
| if (!qmidev->dev_polled) |
| return QMI_ERR_INTERNAL; |
| polled_set_priv(qmidev->dev_polled, qmidev); |
| polled_set_read(qmidev->dev_polled, &dev_read_cb); |
| polled_set_close(qmidev->dev_polled, &dev_close_cb); |
| polled_update(qmidev->dev_polled); |
| |
| return 0; |
| } |
| |
| int qmidev_clear_dev_fd(struct qmidev *qmidev) |
| { |
| assert(qmidev); |
| assert(qmidev->dev_polled); |
| |
| /* Make sure we don't get called again. */ |
| polled_set_close(qmidev->dev_polled, NULL); |
| polled_update(qmidev->dev_polled); |
| |
| /* will be freed by the poller when the fd closes. */ |
| qmidev->dev_polled = NULL; |
| |
| return 0; |
| } |
| |
| static int dev_write_fn(void *ctx, const void *buf, size_t len) |
| { |
| struct qmidev *qmidev; |
| |
| assert(ctx); |
| qmidev = ctx; |
| assert(qmidev->dev); |
| |
| return dev_write(qmidev->dev, buf, len); |
| } |
| |
| int qmidev_send(struct qmidev *qmidev, struct qmimsg *msg) |
| { |
| assert(qmidev); |
| assert(qmidev->connected); |
| |
| return qmimsg_write(msg, &dev_write_fn, qmidev); |
| } |
| |
| struct polled *qmidev_poll(struct qmidev *qmidev, int fd) |
| { |
| assert(qmidev); |
| |
| return poller_add(qmidev->poller, fd); |
| } |
| |
| |
| |
| void qmidev_call_later(struct qmidev *qmidev, callback_fn func, void *context) |
| { |
| struct callback *callback; |
| |
| assert(qmidev); |
| assert(func); |
| |
| callback = g_slice_new(struct callback); |
| callback->func = func; |
| callback->context = context; |
| qmidev->callbacks = g_list_append(qmidev->callbacks, callback); |
| } |
| |
| static void call_callback(void *data, void *user_data) |
| { |
| struct qmidev *qmidev = user_data; |
| struct callback *callback = data; |
| |
| callback->func(qmidev, callback->context); |
| g_slice_free(struct callback, callback); |
| } |
| |
| static void call_callbacks(struct qmidev *qmidev) |
| { |
| g_list_foreach(qmidev->callbacks, call_callback, qmidev); |
| g_list_free(qmidev->callbacks); |
| qmidev->callbacks = NULL; |
| } |