gobi: Use QMI support to drive this modem
authorMarcel Holtmann <marcel@holtmann.org>
Thu, 21 Jun 2012 23:09:57 +0000 (16:09 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Thu, 21 Jun 2012 23:09:57 +0000 (16:09 -0700)
plugins/gobi.c

index 08e8aa6..c0879b0 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  oFono - Open Source Telephony
  *
- *  Copyright (C) 2008-2011  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2008-2012  Intel Corporation. All rights reserved.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
 #endif
 
 #include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
 #include <stdlib.h>
 
-#include <glib.h>
-#include <gatchat.h>
-#include <gattty.h>
-
 #define OFONO_API_SUBJECT_TO_CHANGE
 #include <ofono/plugin.h>
-#include <ofono/log.h>
 #include <ofono/modem.h>
 #include <ofono/devinfo.h>
 #include <ofono/netreg.h>
+#include <ofono/phonebook.h>
 #include <ofono/sim.h>
+#include <ofono/stk.h>
+#include <ofono/sms.h>
 #include <ofono/gprs.h>
 #include <ofono/gprs-context.h>
+#include <ofono/location-reporting.h>
+#include <ofono/log.h>
 
-#include <drivers/atmodem/atutil.h>
-#include <drivers/atmodem/vendor.h>
+#include <drivers/qmimodem/qmi.h>
+#include <drivers/qmimodem/dms.h>
+#include <drivers/qmimodem/util.h>
 
-static const char *none_prefix[] = { NULL };
+#define GOBI_DMS       (1 << 0)
+#define GOBI_NAS       (1 << 1)
+#define GOBI_WMS       (1 << 2)
+#define GOBI_WDS       (1 << 3)
+#define GOBI_PDS       (1 << 4)
+#define GOBI_PBM       (1 << 5)
+#define GOBI_UIM       (1 << 6)
+#define GOBI_CAT       (1 << 7)
+#define GOBI_CAT_OLD   (1 << 8)
 
 struct gobi_data {
-       GAtChat *chat;
-       struct ofono_sim *sim;
-       gboolean have_sim;
-       guint cfun_timeout;
+       struct qmi_device *device;
+       struct qmi_service *dms;
+       unsigned long features;
+       unsigned int discover_attempts;
+       uint8_t oper_mode;
 };
 
 static void gobi_debug(const char *str, void *user_data)
@@ -66,7 +78,7 @@ static int gobi_probe(struct ofono_modem *modem)
        DBG("%p", modem);
 
        data = g_try_new0(struct gobi_data, 1);
-       if (data == NULL)
+       if (!data)
                return -ENOMEM;
 
        ofono_modem_set_data(modem, data);
@@ -82,191 +94,279 @@ static void gobi_remove(struct ofono_modem *modem)
 
        ofono_modem_set_data(modem, NULL);
 
-       /* Cleanup after hot-unplug */
-       g_at_chat_unref(data->chat);
+       qmi_service_unref(data->dms);
+
+       qmi_device_unref(data->device);
 
        g_free(data);
 }
 
-static GAtChat *open_device(struct ofono_modem *modem,
-                               const char *key, char *debug)
+static void shutdown_cb(void *user_data)
 {
-       const char *device;
-       GIOChannel *channel;
-       GAtSyntax *syntax;
-       GAtChat *chat;
+       struct ofono_modem *modem = user_data;
+       struct gobi_data *data = ofono_modem_get_data(modem);
 
-       device = ofono_modem_get_string(modem, key);
-       if (device == NULL)
-               return NULL;
+       DBG("");
 
-       DBG("%s %s", key, device);
+       data->discover_attempts = 0;
 
-       channel = g_at_tty_open(device, NULL);
-       if (channel == NULL)
-               return NULL;
+       qmi_device_unref(data->device);
+       data->device = NULL;
 
-       syntax = g_at_syntax_new_gsm_permissive();
-       chat = g_at_chat_new(channel, syntax);
-       g_at_syntax_unref(syntax);
+       ofono_modem_set_powered(modem, FALSE);
+}
 
-       g_io_channel_unref(channel);
+static void shutdown_device(struct ofono_modem *modem)
+{
+       struct gobi_data *data = ofono_modem_get_data(modem);
 
-       if (chat == NULL)
-               return NULL;
+       DBG("%p", modem);
 
-       if (getenv("OFONO_AT_DEBUG"))
-               g_at_chat_set_debug(chat, gobi_debug, debug);
+       qmi_service_unref(data->dms);
+       data->dms = NULL;
 
-       return chat;
+       qmi_device_shutdown(data->device, shutdown_cb, modem, NULL);
 }
 
-static void simstat_notify(GAtResult *result, gpointer user_data)
+static void power_reset_cb(struct qmi_result *result, void *user_data)
 {
        struct ofono_modem *modem = user_data;
-       struct gobi_data *data = ofono_modem_get_data(modem);
-       GAtResultIter iter;
-       const char *state, *tmp;
 
-       if (data->sim == NULL)
+       DBG("");
+
+       if (qmi_result_set_error(result, NULL)) {
+               shutdown_device(modem);
                return;
+       }
 
-       g_at_result_iter_init(&iter, result);
+       ofono_modem_set_powered(modem, TRUE);
+}
 
-       if (!g_at_result_iter_next(&iter, "$QCSIMSTAT:"))
-               return;
+static void get_oper_mode_cb(struct qmi_result *result, void *user_data)
+{
+       struct ofono_modem *modem = user_data;
+       struct gobi_data *data = ofono_modem_get_data(modem);
+       struct qmi_param *param;
+       uint8_t mode;
 
-       if (!g_at_result_iter_next_unquoted_string(&iter, &tmp))
+       DBG("");
+
+       if (qmi_result_set_error(result, NULL)) {
+               shutdown_device(modem);
                return;
+       }
 
-       /*
-        * When receiving an unsolicited notification, the comma
-        * is missing ($QCSIMSTAT: 1 SIM INIT COMPLETED). Handle
-        * this gracefully.
-        */
-       if (g_str_has_prefix(tmp, "1 ") == TRUE)
-               state = tmp + 2;
-       else if (!g_at_result_iter_next_unquoted_string(&iter, &state))
+       if (!qmi_result_get_uint8(result, QMI_DMS_RESULT_OPER_MODE, &mode)) {
+               shutdown_device(modem);
                return;
+       }
 
-       DBG("state %s", state);
+       data->oper_mode = mode;
 
-       if (g_str_equal(state, "SIM INIT COMPLETED") == TRUE) {
-               if (data->have_sim == FALSE) {
-                       ofono_sim_inserted_notify(data->sim, TRUE);
-                       data->have_sim = TRUE;
+       switch (data->oper_mode) {
+       case QMI_DMS_OPER_MODE_ONLINE:
+               param = qmi_param_new_uint8(QMI_DMS_PARAM_OPER_MODE,
+                                       QMI_DMS_OPER_MODE_PERSIST_LOW_POWER);
+               if (!param) {
+                       shutdown_device(modem);
+                       return;
                }
+
+               if (qmi_service_send(data->dms, QMI_DMS_SET_OPER_MODE, param,
+                                       power_reset_cb, modem, NULL) > 0)
+                       return;
+
+               shutdown_device(modem);
+               break;
+       default:
+               ofono_modem_set_powered(modem, TRUE);
+               break;
        }
 }
 
-static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data)
+static void get_caps_cb(struct qmi_result *result, void *user_data)
 {
        struct ofono_modem *modem = user_data;
        struct gobi_data *data = ofono_modem_get_data(modem);
+       const struct qmi_dms_device_caps *caps;
+       uint16_t len;
+       uint8_t i;
 
        DBG("");
 
-       if (data->cfun_timeout > 0) {
-               g_source_remove(data->cfun_timeout);
-               data->cfun_timeout = 0;
-       }
+       if (qmi_result_set_error(result, NULL))
+               goto error;
+
+       caps = qmi_result_get(result, QMI_DMS_RESULT_DEVICE_CAPS, &len);
+       if (!caps)
+               goto error;
 
-       if (!ok) {
-               g_at_chat_unref(data->chat);
-               data->chat = NULL;
+        DBG("service capabilities %d", caps->data_capa);
+        DBG("sim supported %d", caps->sim_supported);
 
-               ofono_modem_set_powered(modem, FALSE);
+        for (i = 0; i < caps->radio_if_count; i++)
+                DBG("radio = %d", caps->radio_if[i]);
+
+       if (qmi_service_send(data->dms, QMI_DMS_GET_OPER_MODE, NULL,
+                                       get_oper_mode_cb, modem, NULL) > 0)
                return;
-       }
 
-       data->have_sim = FALSE;
+error:
+       shutdown_device(modem);
+}
 
-       g_at_chat_register(data->chat, "$QCSIMSTAT:", simstat_notify,
-                                               FALSE, modem, NULL);
+static void create_dms_cb(struct qmi_service *service, void *user_data)
+{
+       struct ofono_modem *modem = user_data;
+       struct gobi_data *data = ofono_modem_get_data(modem);
 
-       g_at_chat_send(data->chat, "AT$QCSIMSTAT=1", none_prefix,
-                                               NULL, NULL, NULL);
+       DBG("");
 
-       g_at_chat_send(data->chat, "AT$QCSIMSTAT?", none_prefix,
-                                               NULL, NULL, NULL);
+       if (!service)
+               goto error;
 
-       ofono_modem_set_powered(modem, TRUE);
+       data->dms = qmi_service_ref(service);
+
+       if (qmi_service_send(data->dms, QMI_DMS_GET_CAPS, NULL,
+                                       get_caps_cb, modem, NULL) > 0)
+               return;
+
+error:
+       shutdown_device(modem);
 }
 
-static gboolean cfun_timeout(gpointer user_data)
+static void discover_cb(uint8_t count, const struct qmi_version *list,
+                                                       void *user_data)
 {
        struct ofono_modem *modem = user_data;
        struct gobi_data *data = ofono_modem_get_data(modem);
+       uint8_t i;
 
-       ofono_error("Modem enabling timeout, RFKILL enabled?");
+       DBG("");
 
-       data->cfun_timeout = 0;
+       for (i = 0; i < count; i++) {
+               DBG("%s %d.%d", list[i].name, list[i].major, list[i].minor);
+
+               switch (list[i].type) {
+               case QMI_SERVICE_DMS:
+                       data->features |= GOBI_DMS;
+                       break;
+               case QMI_SERVICE_NAS:
+                       data->features |= GOBI_NAS;
+                       break;
+               case QMI_SERVICE_WMS:
+                       data->features |= GOBI_WMS;
+                       break;
+               case QMI_SERVICE_WDS:
+                       data->features |= GOBI_WDS;
+                       break;
+               case QMI_SERVICE_PDS:
+                       data->features |= GOBI_PDS;
+                       break;
+               case QMI_SERVICE_PBM:
+                       data->features |= GOBI_PBM;
+                       break;
+               case QMI_SERVICE_UIM:
+                       data->features |= GOBI_UIM;
+                       break;
+               case QMI_SERVICE_CAT:
+                       data->features |= GOBI_CAT;
+                       break;
+               case QMI_SERVICE_CAT_OLD:
+                       if (list[i].major > 0)
+                               data->features |= GOBI_CAT_OLD;
+                       break;
+               }
+       }
 
-       g_at_chat_unref(data->chat);
-       data->chat = NULL;
+       if (!(data->features & GOBI_DMS)) {
+               if (++data->discover_attempts < 3) {
+                       qmi_device_discover(data->device, discover_cb,
+                                                               modem, NULL);
+                       return;
+               }
 
-       ofono_modem_set_powered(modem, FALSE);
+               shutdown_device(modem);
+               return;
+       }
 
-       return FALSE;
+       qmi_service_create(data->device, QMI_SERVICE_DMS,
+                                               create_dms_cb, modem, NULL);
 }
 
 static int gobi_enable(struct ofono_modem *modem)
 {
        struct gobi_data *data = ofono_modem_get_data(modem);
+       const char *device;
+       int fd;
 
        DBG("%p", modem);
 
-       data->chat = open_device(modem, "Modem", "Modem: ");
-       if (data->chat == NULL)
+       device = ofono_modem_get_string(modem, "Device");
+       if (!device)
                return -EINVAL;
 
-       g_at_chat_send(data->chat, "ATE0 +CMEE=1", NULL, NULL, NULL, NULL);
+       fd = open(device, O_RDWR | O_NONBLOCK | O_CLOEXEC);
+       if (fd < 0)
+               return -EIO;
 
-       g_at_chat_send(data->chat, "AT+CFUN=4", none_prefix,
-                                       cfun_enable, modem, NULL);
+       data->device = qmi_device_new(fd);
+       if (!data->device) {
+               close(fd);
+               return -ENOMEM;
+       }
+
+       if (getenv("OFONO_QMI_DEBUG"))
+               qmi_device_set_debug(data->device, gobi_debug, "QMI: ");
 
-       data->cfun_timeout = g_timeout_add_seconds(5, cfun_timeout, modem);
+       qmi_device_set_close_on_unref(data->device, true);
+
+       qmi_device_discover(data->device, discover_cb, modem, NULL);
 
        return -EINPROGRESS;
 }
 
-static void cfun_disable(gboolean ok, GAtResult *result, gpointer user_data)
+static void power_disable_cb(struct qmi_result *result, void *user_data)
 {
        struct ofono_modem *modem = user_data;
-       struct gobi_data *data = ofono_modem_get_data(modem);
 
        DBG("");
 
-       g_at_chat_unref(data->chat);
-       data->chat = NULL;
-
-       if (ok)
-               ofono_modem_set_powered(modem, FALSE);
+       shutdown_device(modem);
 }
 
 static int gobi_disable(struct ofono_modem *modem)
 {
        struct gobi_data *data = ofono_modem_get_data(modem);
+       struct qmi_param *param;
 
        DBG("%p", modem);
 
-       g_at_chat_cancel_all(data->chat);
-       g_at_chat_unregister_all(data->chat);
+       param = qmi_param_new_uint8(QMI_DMS_PARAM_OPER_MODE,
+                                       QMI_DMS_OPER_MODE_PERSIST_LOW_POWER);
+       if (!param)
+               return -ENOMEM;
+
+       if (qmi_service_send(data->dms, QMI_DMS_SET_OPER_MODE, param,
+                                       power_disable_cb, modem, NULL) > 0)
+               return -EINPROGRESS;
 
-       g_at_chat_send(data->chat, "AT+CFUN=0", none_prefix,
-                                       cfun_disable, modem, NULL);
+       shutdown_device(modem);
 
        return -EINPROGRESS;
 }
 
-static void set_online_cb(gboolean ok, GAtResult *result, gpointer user_data)
+static void set_online_cb(struct qmi_result *result, void *user_data)
 {
        struct cb_data *cbd = user_data;
        ofono_modem_online_cb_t cb = cbd->cb;
-       struct ofono_error error;
 
-       decode_at_error(&error, g_at_result_final_response(result));
-       cb(&error, cbd->data);
+       DBG("");
+
+       if (qmi_result_set_error(result, NULL))
+               CALLBACK_WITH_FAILURE(cb, cbd->data);
+       else
+               CALLBACK_WITH_SUCCESS(cb, cbd->data);
 }
 
 static void gobi_set_online(struct ofono_modem *modem, ofono_bool_t online,
@@ -274,14 +374,27 @@ static void gobi_set_online(struct ofono_modem *modem, ofono_bool_t online,
 {
        struct gobi_data *data = ofono_modem_get_data(modem);
        struct cb_data *cbd = cb_data_new(cb, user_data);
-       char const *command = online ? "AT+CFUN=1" : "AT+CFUN=4";
+       struct qmi_param *param;
+       uint8_t mode;
 
-       DBG("modem %p %s", modem, online ? "online" : "offline");
+       DBG("%p %s", modem, online ? "online" : "offline");
 
-       if (g_at_chat_send(data->chat, command, none_prefix,
+       if (online)
+               mode = QMI_DMS_OPER_MODE_ONLINE;
+       else
+               mode = QMI_DMS_OPER_MODE_LOW_POWER;
+
+       param = qmi_param_new_uint8(QMI_DMS_PARAM_OPER_MODE, mode);
+       if (!param)
+               goto error;
+
+       if (qmi_service_send(data->dms, QMI_DMS_SET_OPER_MODE, param,
                                        set_online_cb, cbd, g_free) > 0)
                return;
 
+       qmi_param_free(param);
+
+error:
        CALLBACK_WITH_FAILURE(cb, cbd->data);
 
        g_free(cbd);
@@ -293,33 +406,55 @@ static void gobi_pre_sim(struct ofono_modem *modem)
 
        DBG("%p", modem);
 
-       ofono_devinfo_create(modem, 0, "atmodem", data->chat);
-       data->sim = ofono_sim_create(modem, 0, "atmodem", data->chat);
+       ofono_devinfo_create(modem, 0, "qmimodem", data->device);
+
+       if (data->features & GOBI_UIM)
+               ofono_sim_create(modem, 0, "qmimodem", data->device);
+       else if (data->features & GOBI_DMS)
+               ofono_sim_create(modem, 0, "qmimodem-legacy", data->device);
 }
 
 static void gobi_post_sim(struct ofono_modem *modem)
 {
        struct gobi_data *data = ofono_modem_get_data(modem);
-       struct ofono_gprs *gprs;
-       struct ofono_gprs_context *gc;
 
        DBG("%p", modem);
 
-       gprs = ofono_gprs_create(modem, OFONO_VENDOR_GOBI,
-                                               "atmodem", data->chat);
-       gc = ofono_gprs_context_create(modem, 0, "atmodem", data->chat);
+       if (data->features & GOBI_CAT)
+               ofono_stk_create(modem, 0, "qmimodem", data->device);
+       else if (data->features & GOBI_CAT_OLD)
+               ofono_stk_create(modem, 1, "qmimodem", data->device);
+
+       if (data->features & GOBI_PBM)
+               ofono_phonebook_create(modem, 0, "qmimodem", data->device);
 
-       if (gprs && gc)
-               ofono_gprs_add_context(gprs, gc);
+       if (data->features & GOBI_WMS)
+               ofono_sms_create(modem, 0, "qmimodem", data->device);
+
+       if (data->features & GOBI_PDS)
+               ofono_location_reporting_create(modem, 0, "qmimodem",
+                                                       data->device);
 }
 
 static void gobi_post_online(struct ofono_modem *modem)
 {
        struct gobi_data *data = ofono_modem_get_data(modem);
+       struct ofono_gprs *gprs;
+       struct ofono_gprs_context *gc;
 
        DBG("%p", modem);
 
-       ofono_netreg_create(modem, 0, "dunmodem", data->chat);
+       if (data->features & GOBI_NAS)
+               ofono_netreg_create(modem, 0, "qmimodem", data->device);
+
+       if (data->features & GOBI_WDS) {
+               gprs = ofono_gprs_create(modem, 0, "qmimodem", data->device);
+               gc = ofono_gprs_context_create(modem, 0, "qmimodem",
+                                                       data->device);
+
+               if (gprs && gc)
+                       ofono_gprs_add_context(gprs, gc);
+       }
 }
 
 static struct ofono_modem_driver gobi_driver = {