libqmi: make some qmi stuff more generic

Make a generic qmi_result function to get the result code from a QMI
response, and factor out some common functionality in qmictl.

BUG=chromium-os:27301
TEST=qmidev_unittest

Change-Id: Ia973741c59e135dcc94a2c1c596f04e11f5e8b04
diff --git a/src/Makefile b/src/Makefile
index 17ca8f6..7bbbad4 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -6,8 +6,18 @@
 LIBDIR = /usr/lib
 SBINDIR = /usr/sbin
 
-LIBQMI_SRCS = dev.c file.c mock.c poller.c qmictl.c qmidev.c qmimsg.c util.c
-UNITTESTS = qmidev_unittest
+LIBQMI_SRCS = \
+	dev.c \
+	file.c \
+	mock.c \
+	poller.c \
+	qmi.c \
+	qmictl.c \
+	qmidev.c \
+	qmimsg.c \
+	util.c
+UNITTESTS = \
+	qmidev_unittest
 
 PC_CFLAGS := $(shell pkg-config --cflags glib-2.0)
 CFLAGS += -fpic -I ../include $(PC_CFLAGS)
diff --git a/src/qmi.c b/src/qmi.c
new file mode 100644
index 0000000..f30c872
--- /dev/null
+++ b/src/qmi.c
@@ -0,0 +1,41 @@
+/*
+ * 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 "qmi.h"
+
+#include <assert.h>
+#include <stdint.h>
+
+#include "qmimsg.h"
+
+struct qmi_result {
+  uint16_t status;
+  uint16_t error;
+};
+
+/* 0x02 is the TLV for the result of any request.
+   (grep "Result Code" [Gobi3000API]/Database/QMI/Entity.txt) */
+#define QMI_TLV_RESULT_CODE ((uint8_t)0x02)
+
+int qmi_result(struct qmimsg *message,
+               uint16_t *status_out, uint16_t *error_out)
+{
+  assert(message);
+  assert(status_out);
+  assert(error_out);
+
+  struct qmi_result qmi_result;
+
+  int result = qmimsg_tlv_get(message, QMI_TLV_RESULT_CODE,
+                              sizeof(qmi_result), &qmi_result);
+  if (result)
+    return result;
+
+  *status_out = qmi_result.status;
+  *error_out = qmi_result.error;
+
+  return 0;
+}
diff --git a/src/qmi.h b/src/qmi.h
new file mode 100644
index 0000000..b2ce6a7
--- /dev/null
+++ b/src/qmi.h
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/*
+ * qmi: General QMI functions
+ */
+
+#ifndef LIBQMI_QMI_H
+#define LIBQMI_QMI_H
+
+#include <stdint.h>
+
+struct qmimsg;
+
+/**
+ * Gets the result (status and error) from a TLV message.
+ */
+int qmi_result(struct qmimsg *message,
+               uint16_t *status_out, uint16_t *error_out);
+
+#endif /* LIBQMI_QMIDEV_H */
diff --git a/src/qmictl.c b/src/qmictl.c
index 7524c18..5203858 100644
--- a/src/qmictl.c
+++ b/src/qmictl.c
@@ -17,8 +17,10 @@
 #include "qmictl.h"
 
 #include <assert.h>
+#include <errno.h>
 #include <glib.h>
 
+#include "qmi.h"
 #include "qmidev.h"
 #include "qmimsg.h"
 #include "util.h"
@@ -33,8 +35,20 @@
   QMICTL_MSG_RELEASE_CID = 0x0023
 };
 
-#define QMI_TLV_ID 0x01
-#define QMI_TLV_RESULT 0x02
+#define QMI_TLV_ID ((uint8_t)0x01)
+
+struct qmi_cid {
+  uint8_t service;
+  uint8_t client;
+};
+
+static int ctl_get_id(struct qmimsg *message, struct qmi_cid *id_out)
+{
+  assert(message);
+  assert(id_out);
+
+  return qmimsg_tlv_get(message, QMI_TLV_ID, sizeof(*id_out), id_out);
+}
 
 static struct qmimsg *ctl_msg_new(uint16_t transaction, uint16_t message)
 {
@@ -56,11 +70,6 @@
   criteria->transaction = transaction;
 }
 
-struct qmi_cid {
-  uint8_t service;
-  uint8_t client;
-};
-
 struct get_cid_context {
   qmictl_get_cid_response_fn callback;
   void *context;
@@ -76,27 +85,32 @@
 
   struct get_cid_context *context = data;
   struct qmi_cid id;
-  struct { uint16_t status; uint16_t error; } result;
+  uint16_t status; uint16_t error;
+  int result;
 
-  if (qmimsg_tlv_get(message, QMI_TLV_RESULT, sizeof(result), &result) ||
-      result.status != 0) {
-    goto fail;
+  id.client = 0;
+
+  result = qmi_result(message, &status, &error);
+  if (result)
+    goto out;
+  if (status) {
+    result = status;
+    goto out;
   }
 
-  if (qmimsg_tlv_get(message, QMI_TLV_ID, sizeof(id), &id) ||
-      id.service != context->service) {
-    goto fail;
+  result = ctl_get_id(message, &id);
+  if (result)
+    goto out;
+  if (id.service != context->service) {
+    result = EINVAL;
+    goto out;
   }
 
-  context->callback(qmidev, context->context, 0, id.client);
-  goto out;
-
-fail:
-  context->callback(qmidev, context->context, -1, 0);
+  result = 0;
 
 out:
+  context->callback(qmidev, context->context, result, id.client);
   g_slice_free(struct get_cid_context, context);
-
   return 1;
 }
 
@@ -138,28 +152,31 @@
 
   struct release_cid_context *context = data;
   struct qmi_cid id;
-  struct { uint16_t status; uint16_t error; } result;
+  uint16_t status; uint16_t error;
+  int result;
 
-  if (qmimsg_tlv_get(message, QMI_TLV_RESULT, sizeof(result), &result) ||
-      result.status != 0) {
-    goto fail;
+  result = qmi_result(message, &status, &error);
+  if (result)
+    goto out;
+  if (status) {
+    result = error;
+    goto out;
   }
 
-  if (qmimsg_tlv_get(message, QMI_TLV_ID, sizeof(id), &id) ||
-      id.service != context->id.service ||
+  result = ctl_get_id(message, &id);
+  if (result)
+    goto out;
+  if (id.service != context->id.service ||
       id.client != context->id.client) {
-    goto fail;
+    result = EINVAL;
+    goto out;
   }
 
-  context->callback(qmidev, context->context, 0);
-  goto out;
-
-fail:
-  context->callback(qmidev, context->context, -1);
+  result = 0;
 
 out:
+  context->callback(qmidev, context->context, result);
   g_slice_free(struct release_cid_context, context);
-
   return 1;
 }