qmidev: New request API

Add qmidev_{make,send}_request, which make sending a request to the card
simpler, and fill in real, incrementing transaction IDs.

BUG=chromium-os:27301
TEST=no code calls this yet; next commit will use and test it

Change-Id: I0fd6bbe1ca93d19a63ba742379b1caec250ec98a
diff --git a/src/qmidev.c b/src/qmidev.c
index 6b73646..9e1be6a 100644
--- a/src/qmidev.c
+++ b/src/qmidev.c
@@ -215,6 +215,7 @@
   context->current_service = NULL;
   context->pending_services = g_queue_new();
 
+  qmidev->services = g_list_append(qmidev->services, qmisvc_alloc(QMI_SVC_CTL));
   g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_WDS));
   g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_DMS));
   g_queue_push_tail(context->pending_services, qmisvc_alloc(QMI_SVC_NAS));
@@ -276,9 +277,14 @@
   context->current_service = qmidev->services->data;
   qmidev->services = g_list_remove(qmidev->services, context->current_service);
   /* TODO: Check return */
-  qmictl_release_cid(qmidev, context->current_service->sid,
-                     context->current_service->cid,
-                     &released_cid, context);
+  if (context->current_service->sid != QMI_SVC_CTL) {
+    qmictl_release_cid(qmidev, context->current_service->sid,
+                       context->current_service->cid,
+                       &released_cid, context);
+  } else {
+    qmisvc_free(context->current_service);
+    disconnect_next_service(qmidev, context);
+  }
 }
 
 int qmidev_disconnect(struct qmidev *qmidev,
@@ -299,6 +305,136 @@
   return 0;
 }
 
+gint service_compare_sid(gconstpointer a, gconstpointer b)
+{
+  const struct qmisvc *svc_a = a;
+  const struct qmisvc *svc_b = b;
+
+  return svc_a->sid - svc_b->sid;
+}
+
+struct qmisvc *find_service(struct qmidev *qmidev, uint8_t service_id)
+{
+  struct qmisvc wanted;
+  wanted.sid = service_id;
+
+  GList *found_list = g_list_find_custom(qmidev->services, &wanted,
+                                         &service_compare_sid);
+  if (found_list)
+    return (struct qmisvc *) found_list->data;
+  else
+    return NULL;
+}
+
+uint16_t get_free_tid(struct qmisvc *service)
+{
+  uint16_t next_tid = service->next_tid;
+
+  /* CTL uses 8-bit transaction IDs; other services use 16-bit. */
+  uint16_t max_tid = (service->sid == QMI_SVC_CTL) ? 0x00FF : 0xFFFF;
+  if (service->next_tid < max_tid)
+    service->next_tid++;
+  else
+    service->next_tid = 0x0001;
+
+  return next_tid;
+}
+
+int qmidev_make_request(struct qmidev *qmidev,
+                        uint8_t service_id,
+                        uint16_t req_msg_id,
+                        struct qmimsg **message_out)
+{
+  struct qmisvc *service;
+  uint16_t transaction_id;
+  struct qmimsg *message;
+  int result;
+
+  assert(qmidev);
+
+  service = find_service(qmidev, service_id);
+  if (!service)
+    return ENOENT;
+
+  transaction_id = get_free_tid(service);
+
+  result = qmimsg_new(0, service_id, service->cid,
+                      0, transaction_id, req_msg_id,
+                      &message);
+  if (result)
+    return result;
+
+  *message_out = message;
+
+  return 0;
+}
+
+struct request_context {
+  uint16_t resp_msg_id;
+
+  qmidev_request_fn caller_callback;
+  void *caller_context;
+};
+
+int got_reply(struct qmidev *qmidev, void *data, struct qmimsg *message)
+{
+  struct request_context *context = data;
+  int result;
+  uint16_t status, error;
+
+  /* TODO: Check response message ID. */
+
+  result = qmi_result(message, &status, &error);
+  if (result)
+    goto out;
+  if (status) {
+    result = error;
+    goto out;
+  }
+
+  result = 0;
+
+out:
+  context->caller_callback(qmidev, context->caller_context, result, message);
+  g_slice_free(struct request_context, context);
+  return 1;
+}
+
+int qmidev_send_request(struct qmidev *qmidev,
+                        struct qmimsg *req_msg,
+                        uint16_t resp_msg_id,
+                        qmidev_request_fn caller_callback,
+                        void *caller_context)
+{
+  struct request_context *context = g_slice_new(struct request_context);
+
+  context->caller_callback = caller_callback;
+  context->caller_context = caller_context;
+  context->resp_msg_id = resp_msg_id;
+
+  struct listen_criteria criteria;
+  qmimsg_get_header(req_msg,
+                    &criteria.service,
+                    &criteria.client,
+                    &criteria.transaction);
+  struct listener *listener = qmidev_listen(qmidev, &criteria,
+                                            &got_reply, context);
+
+  /* TODO: Do something with listener?  Currently we don't need it for
+     anything, but once we add timeouts we'll need to be able to cancel
+     it. */
+
+  int result = qmidev_send(qmidev, req_msg);
+  if (result) {
+    qmidev_cancel_listen(qmidev, listener);
+    g_slice_free(struct request_context, context);
+    qmimsg_free(req_msg);
+    return result;
+  }
+
+  return 0;
+}
+
 static int dev_read_fn(void *ctx, void *buf, size_t len)
 {
   assert(ctx);
diff --git a/src/qmidev.h b/src/qmidev.h
index e51209e..f7b73b4 100644
--- a/src/qmidev.h
+++ b/src/qmidev.h
@@ -50,6 +50,47 @@
 struct polled *qmidev_poll(struct qmidev *qmidev, int fd);
 
 /**
+ * Callback type for QMI requests made with qmidev_send_request.
+ *
+ * @resp_msg: The response message received, or NULL if none.
+ */
+typedef void (*qmidev_request_fn)(struct qmidev *qmidev,
+                                  void *context,
+                                  int status,
+                                  struct qmimsg *resp_msg);
+
+/**
+ * Creates a new QMI message for a request.
+ *
+ * The service and message ID fields of the message will be filled from the
+ * respective parameters.  The client ID is set to the client ID allocated
+ * by the qmidev for this service, and the transaction ID is assigned
+ * sequentially within that client.
+ *
+ * @service: The service to which the request will be sent
+ * @req_msg_id: The message ID of the request
+ */
+int qmidev_make_request(struct qmidev *qmidev,
+                        uint8_t service_id,
+                        uint16_t req_msg_id,
+                        struct qmimsg **message_out);
+
+/**
+ * Sends a request message created with qmidev_make_request, and listens for
+ * a reply.
+ *
+ * @req_msg: The message returned from qmidev_make_request
+ * @resp_msg_id: The expected message ID of the response
+ * @callback: The callback to call when a response is received
+ * @context: The context with which to call the callback
+ */
+int qmidev_send_request(struct qmidev *qmidev,
+                        struct qmimsg *req_msg,
+                        uint16_t resp_msg_id,
+                        qmidev_request_fn callback,
+                        void *context);
+
+/**
  * Sends a QMI message.
  * Returns zero on success, errno on failure.
  */