From mboxrd@z Thu Jan 1 00:00:00 1970 Content-Type: multipart/mixed; boundary="===============1100404991909182086==" MIME-Version: 1.0 From: Giacinto Cifelli Subject: [RFC PATCH] new gemalto plugin Date: Fri, 26 Oct 2018 08:10:55 +0200 Message-ID: <20181026061055.18669-1-gciofono@gmail.com> List-Id: To: ofono@ofono.org --===============1100404991909182086== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable I would like to submit to your attention the new version of the Gemalto plugin. It is not ready, but would already benefit from some feedback. The purpose of this new plugin is to address most of the Gemalto modules (excluding perhaps some LTE CatM and CatNB), interfaced either via USB or RS232 interface. I have included the totality of file plugins/gemalto.c because it is quite different from the current one. I would appreciate a generic comment on how to split it in commits this file. I have added an include/gemalto.h file, as suggested by Jonas Bonn. There isn't much in it yet, but some additional code should be moved into it. The gprs and gprs-context's are perhaps not finished. In udevng there are 3 additional commits, please just comment if you like and find them useful and I will submit separately. There are some review comments, in // format, not part of the code. --- include/gemalto.h | 27 + plugins/gemalto.c | 2715 +++++++++++++++++++++++++++++++++++++++++++++ plugins/udevng.c | 208 ++-- 3 files changed, 2885 insertions(+), 65 deletions(-) create mode 100644 include/gemalto.h create mode 100644 plugins/gemalto.c diff --git a/include/gemalto.h b/include/gemalto.h new file mode 100644 index 00000000..26165eb1 --- /dev/null +++ b/include/gemalto.h @@ -0,0 +1,27 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2018 Gemalto M2M + * + * 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 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 = USA + * + */ + +enum auth_option { + GEMALTO_AUTH_DEFAULTS =3D 0, + GEMALTO_AUTH_USE_SGAUTH =3D 1<<0, + GEMALTO_AUTH_ORDER_PWD_USR =3D 1<<1, + GEMALTO_AUTH_ALWAYS_ALL_PARAMS =3D 1<<2, +}; diff --git a/plugins/gemalto.c b/plugins/gemalto.c new file mode 100644 index 00000000..6b208572 --- /dev/null +++ b/plugins/gemalto.c @@ -0,0 +1,2715 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2017 Vincent Cesson. All rights reserved. + * Copyright (C) 2018 Gemalto M2M + * + * 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 + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 = USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ofono.h" +#define OFONO_API_SUBJECT_TO_CHANGE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ELL +#include +#include +#include +#include +#endif // some models can use MBIM, but if the option is not included, // they fall back to PPP + +#include +#include +#include "gemalto.h" + // the next part will not be in the official commit +/* debug utilities - begin */ + +#define REDCOLOR "\x1b\x5b\x30\x31\x3b\x33\x31\x6d" +#define NOCOLOR "\x1b\x5b\x30\x30\x6d" + +#include +#include +#include +#include + +void print_trace(); + +void print_trace() { + char pid_buf[30]; + char name_buf[512]; + int child_pid; + sprintf(pid_buf, "%d", getpid()); + name_buf[readlink("/proc/self/exe", name_buf, 511)]=3D0; + child_pid =3D fork(); + if (!child_pid) { + dup2(2,1); // redirect output to stderr + fprintf(stdout,"stack trace for %s pid=3D%s\n",name_buf,pid_buf); + execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt"= , name_buf, pid_buf, NULL); + abort(); /* If gdb failed to start */ + } else { + waitpid(child_pid,NULL,0); + } +} + +/* debug utilities - end */ + +enum gemalto_connection_type { + GEMALTO_CONNECTION_SERIAL =3D 1, + GEMALTO_CONNECTION_USB =3D 2, +}; + +enum gemalto_device_state { + STATE_ABSENT =3D 0, + STATE_PROBE =3D 1, + STATE_PRESENT =3D 2, +}; + +enum gprs_option { + NO_GPRS =3D 0, + USE_SWWAN =3D 1, + USE_CTX17 =3D 2, + USE_CTX3 =3D 3, + USE_PPP =3D 4, + USE_SWWAN_INV =3D 5, /* inverted syntax idx,act */ + USE_CTX_INV =3D 6, /* inverted syntax idx,act */ +}; + +static const char *none_prefix[] =3D { NULL }; +static const char *cfun_prefix[] =3D { "+CFUN:", NULL }; +static const char *sctm_prefix[] =3D { "^SCTM:", NULL }; +static const char *sbv_prefix[] =3D { "^SBV:", NULL }; +static const char *sqport_prefix[] =3D { "^SQPORT:", NULL }; +static const char *sgpsc_prefix[] =3D { "^SGPSC:", NULL }; + +typedef void (*OpenResultFunc)(gboolean success, struct ofono_modem *modem= ); + +struct gemalto_data { + gboolean init_done; + GIOChannel *channel; + GAtChat *tmp_chat; + OpenResultFunc open_cb; + guint read_src; + GAtChat *app; + GAtChat *mdm; + int cfun; + + struct ofono_sim *sim; + gboolean have_sim; + struct at_util_sim_state_query *sim_state_query; + guint modem_ready_id; + + char modelstr[32]; + char sqport[32]; + + guint model; + guint probing_timer; + guint init_waiting_time; + guint waiting_time; + + enum gemalto_connection_type conn; + enum gemalto_device_state mbim; + enum gemalto_device_state qmi; + enum gemalto_device_state ecmncm; + enum gemalto_device_state gina; + gboolean inverse_enum; + gboolean use_mdm_for_app; + gboolean voice_avail; + enum auth_option auth_syntax; + enum gprs_option gprs_opt; + gboolean has_lte; + gboolean autoattach; + gboolean autoconfig; + gboolean autoactivation; + gboolean vts_with_quotes; + + void *device; /* struct mbim_device* or struct qmi_device* */ + + /* mbim data */ + uint16_t max_segment; + uint8_t max_outstanding; + uint8_t max_sessions; + + /* hardware monitor variables */ + DBusMessage *hm_msg; + int32_t temperature; + int32_t voltage; + /* gnss variables */ + DBusMessage *gnss_msg; + /* hardware control variables */ + DBusMessage *hc_msg; + gboolean powersave; +}; + +/*************************************************************************= ****** + * Generic functions + *************************************************************************= *****/ + +static void gemalto_debug(const char *str, void *user_data) +{ + const char *prefix =3D user_data; + + ofono_info("%s%s", prefix, str); +} + +static const char *gemalto_get_string(struct ofono_modem *modem, const cha= r *k) +{ + const char *v; + + if (!modem || !k || !*k) + return NULL; + + v =3D ofono_modem_get_string(modem, k); + + if (!v || !*v) + return NULL; + + return v; +} + +static void gemalto_signal(const char *iface, const char *name, + const char *value, struct ofono_modem *modem) +{ + DBusMessageIter sub_iter,iter; + const char *path =3D ofono_modem_get_path(modem); + DBusConnection *conn =3D ofono_dbus_get_connection(); + + DBusMessage *signal =3D dbus_message_new_signal(path, + iface, + name); + + DBG(""); + + if (signal =3D=3D NULL) { + DBG("Cannot create new signal message"); + return; + } + + dbus_message_iter_init_append(signal, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + "s", &sub_iter); + if (!dbus_message_iter_append_basic(&sub_iter, + DBUS_TYPE_STRING, &value)) { + DBG("Out of memory!"); + return; + } + + dbus_message_iter_close_container(&iter, &sub_iter); + g_dbus_send_message(conn, signal); +} + +static void executeWithPrompt(GAtChat *port, const char *command, + const char *prompt, const char *argument, void *cb, + void *cbd, void *freecall) +{ + char *buf; + const char *expected_array[2] =3D {0,0}; + + buf =3D g_strdup_printf("%s\r%s", command, argument); + + if (strlen(argument)>=3D2 && g_str_equal(argument+strlen(argument)-2, + "^Z")) + sprintf(buf+strlen(buf)-2,"\x1a"); + + if (strlen(argument)>=3D2 && g_str_equal(argument+strlen(argument)-2, + "\\r")) + sprintf(buf+strlen(buf)-2,"\r"); + + expected_array[0]=3Dprompt; + g_at_chat_send_and_expect_short_prompt(port, buf, expected_array, + cb, cbd, freecall); + free(buf); +} + +static void gemalto_exec_stored_cmd(struct ofono_modem *modem, + const char *filename) +{ + struct gemalto_data *data =3D ofono_modem_get_data(modem); + const char *vid =3D gemalto_get_string(modem, "Vendor"); + const char *pid =3D gemalto_get_string(modem, "Model"); + char store[64]; + int index; + char *command, *prompt, *argument; + char key[32]; + GKeyFile *f; + + sprintf(store,"%s-%s/%s", vid, pid, filename); + f =3D storage_open(NULL, store); + + if (!f) + return; + + for (index =3D 0; ; index++) { + sprintf(key, "command_%d", index); + command =3D g_key_file_get_string(f, "Simple", key, NULL); + + if (!command) + break; + + DBG(REDCOLOR"executing stored command simple: %s"NOCOLOR, command); + g_at_chat_send(data->app, command, NULL, NULL, NULL, NULL); + } + + for (index =3D 0; ; index++) { + sprintf(key, "command_%d", index); + command =3D g_key_file_get_string(f, "WithPrompt", key, NULL); + sprintf(key, "prompt_%d", index); + prompt =3D g_key_file_get_string(f, "WithPrompt", key, NULL); + sprintf(key, "argument_%d", index); + argument =3D g_key_file_get_string(f, "WithPrompt", key, NULL); + + if (!command || !prompt || !argument) + break; + + DBG("executing stored command with prompt: %s", command); + executeWithPrompt(data->app, command, prompt, argument, + NULL, NULL, NULL); + } + + storage_close(NULL, store, f, FALSE); +} + +/*************************************************************************= ****** + * Hardware monitor interface + *************************************************************************= *****/ + +#define HARDWARE_MONITOR_INTERFACE OFONO_SERVICE ".gemalto.HardwareMonitor" +#define CINTERION_LEGACY_HWMON_INTERFACE OFONO_SERVICE ".cinterion.Hardwar= eMonitor" + +static void gemalto_sctmb_notify(GAtResult *result, gpointer user_data) +{ + GAtResultIter iter; + gint value; + char *val; + + g_at_result_iter_init(&iter, result); + g_at_result_iter_next(&iter, "^SCTM_B:"); + g_at_result_iter_next_number(&iter, &value); + + switch(value) { + case -1: + val=3D"Below low temperature alert limit"; + break; + case 0: + val=3D"Normal operating temperature"; + break; + case 1: + val=3D"Above upper temperature alert limit"; + break; + case 2: + val=3D"Above uppermost temperature limit"; + break; + default: /* unvalid value, do not output signal*/ + return; + } + + gemalto_signal(HARDWARE_MONITOR_INTERFACE, "CriticalTemperature", val, + user_data); +} + +static void gemalto_sbc_notify(GAtResult *result, gpointer user_data) +{ + GAtResultIter iter; + const char *value; + + g_at_result_iter_init(&iter, result); + g_at_result_iter_next(&iter, "^SBC:"); + g_at_result_iter_next_unquoted_string(&iter, &value); + gemalto_signal(HARDWARE_MONITOR_INTERFACE, "CriticalVoltage", value, + user_data); +} + +static void gemalto_sctm_cb(gboolean ok, GAtResult *result, gpointer user_= data) +{ + struct gemalto_data *data =3D user_data; + DBusMessage *reply; + GAtResultIter iter; + DBusMessageIter dbus_iter; + DBusMessageIter dbus_dict; + + if (data->hm_msg =3D=3D NULL) + return; + + if (!ok) + goto error; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "^SCTM:")) + goto error; + + if (!g_at_result_iter_skip_next(&iter)) + goto error; + + if (!g_at_result_iter_skip_next(&iter)) + goto error; + + if (!g_at_result_iter_next_number(&iter, &data->temperature)) + goto error; + + reply =3D dbus_message_new_method_return(data->hm_msg); + + dbus_message_iter_init_append(reply, &dbus_iter); + + dbus_message_iter_open_container(&dbus_iter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dbus_dict); + + ofono_dbus_dict_append(&dbus_dict, "Temperature", + DBUS_TYPE_INT32, &data->temperature); + + ofono_dbus_dict_append(&dbus_dict, "Voltage", + DBUS_TYPE_UINT32, &data->voltage); + + dbus_message_iter_close_container(&dbus_iter, &dbus_dict); + + __ofono_dbus_pending_reply(&data->hm_msg, reply); + + return; + +error: + __ofono_dbus_pending_reply(&data->hm_msg, + __ofono_error_failed(data->hm_msg)); +} + +static void gemalto_sbv_cb(gboolean ok, GAtResult *result, gpointer user_d= ata) +{ + struct gemalto_data *data =3D user_data; + GAtResultIter iter; + + if (!ok) + goto error; + + g_at_result_iter_init(&iter, result); + + if (!g_at_result_iter_next(&iter, "^SBV:")) + goto error; + + if (!g_at_result_iter_next_number(&iter, &data->voltage)) + goto error; + + if (g_at_chat_send(data->app, "AT^SCTM?", sctm_prefix, gemalto_sctm_cb, + data, NULL) > 0) + return; + +error: + __ofono_dbus_pending_reply(&data->hm_msg, + __ofono_error_failed(data->hm_msg)); +} + +static DBusMessage *hardware_monitor_get_statistics(DBusConnection *conn, + DBusMessage *msg, + void *modem) +{ + struct gemalto_data *data =3D ofono_modem_get_data(modem); + + DBG(""); + + if (data->hm_msg !=3D NULL) + return __ofono_error_busy(msg); + + if (!g_at_chat_send(data->app, "AT^SBV", sbv_prefix, gemalto_sbv_cb, + data, NULL)) + return __ofono_error_failed(msg); + + data->hm_msg =3D dbus_message_ref(msg); + + return NULL; +} + +static const GDBusMethodTable hardware_monitor_methods[] =3D { + { GDBUS_ASYNC_METHOD("GetStatistics", + NULL, GDBUS_ARGS({ "Statistics", "a{sv}" }), + hardware_monitor_get_statistics) }, + {} +}; + +static const GDBusSignalTable hardware_monitor_signals[] =3D { + { GDBUS_SIGNAL("CriticalTemperature", + GDBUS_ARGS({ "temperature", "a{sv}" }) )}, + { GDBUS_SIGNAL("CriticalVoltage", + GDBUS_ARGS({ "voltage", "a{sv}" }) )}, + {} +}; + +static void gemalto_hardware_monitor_enable(struct ofono_modem *modem) +{ + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusConnection *conn =3D ofono_dbus_get_connection(); + const char *path =3D ofono_modem_get_path(modem); + + /* Listen to over/undertemperature URCs (activated with AT^SCTM) */ + g_at_chat_register(data->app, "^SCTM_B:", + gemalto_sctmb_notify, FALSE, NULL, NULL); + /* Listen to over/under voltage URCs (automatic URC) */ + g_at_chat_register(data->app, "^SBC:", + gemalto_sbc_notify, FALSE, NULL, NULL); + /* Enable temperature URC and value output */ + g_at_chat_send(data->app, "AT^SCTM=3D1,1", none_prefix, NULL, NULL, NULL); + + if (!g_dbus_register_interface(conn, path, HARDWARE_MONITOR_INTERFACE, + hardware_monitor_methods, + hardware_monitor_signals, + NULL, + modem, + NULL)) { + ofono_error("Could not register %s interface under %s", + HARDWARE_MONITOR_INTERFACE, path); + return; + } + + ofono_modem_add_interface(modem, HARDWARE_MONITOR_INTERFACE); + + if (!g_dbus_register_interface(conn, path, + CINTERION_LEGACY_HWMON_INTERFACE, + hardware_monitor_methods, + NULL, + NULL, + modem, + NULL)) { + ofono_error("Could not register %s interface under %s", + CINTERION_LEGACY_HWMON_INTERFACE, path); + return; + } + + ofono_modem_add_interface(modem, CINTERION_LEGACY_HWMON_INTERFACE); +} + +/*************************************************************************= ****** + * Time services interface + *************************************************************************= *****/ + +#define GEMALTO_NITZ_TIME_INTERFACE OFONO_SERVICE ".gemalto.TimeServices" + +static DBusMessage *set_modem_datetime(DBusConnection *conn, + DBusMessage *msg, + void *modem) +{ + struct gemalto_data *data =3D ofono_modem_get_data(modem); + time_t t =3D time(NULL); + struct tm tm; + gchar cclk_cmd[32]; + + /* Set date and time */ + tm =3D *localtime(&t); + strftime(cclk_cmd, 32, "AT+CCLK=3D\"%y/%m/%d,%T\"", &tm); + g_at_chat_send(data->app, cclk_cmd, none_prefix, NULL, NULL, NULL); + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable gsmTime_methods[] =3D { + { GDBUS_ASYNC_METHOD("SetModemDatetime", + NULL, NULL, set_modem_datetime) }, + {} +}; + +static const GDBusSignalTable gsmTime_signals[] =3D { + { GDBUS_SIGNAL("NitzUpdated", + GDBUS_ARGS({ "time", "a{sv}" }) )}, + {} +}; + +static void gemalto_time_enable(struct ofono_modem *modem) +{ + DBusConnection *conn =3D ofono_dbus_get_connection(); + const char *path =3D ofono_modem_get_path(modem); + + if (!g_dbus_register_interface(conn, path, + GEMALTO_NITZ_TIME_INTERFACE, + gsmTime_methods, + gsmTime_signals, + NULL, + modem, + NULL)) { + ofono_error("Could not register %s interface under %s", + GEMALTO_NITZ_TIME_INTERFACE, path); + return; + } + + ofono_modem_add_interface(modem, GEMALTO_NITZ_TIME_INTERFACE); +} + +/*************************************************************************= ****** + * Command passtrhough interface + *************************************************************************= *****/ + +#define COMMAND_PASSTHROUGH_INTERFACE OFONO_SERVICE ".gemalto.CommandPasst= hrough" + +static int command_passthrough_signal_answer(const char *answer, + gpointer user_data) +{ + struct ofono_modem *modem =3D user_data; + DBusConnection *conn =3D ofono_dbus_get_connection(); + const char *path =3D ofono_modem_get_path(modem); + DBusMessage *signal; + DBusMessageIter iter; + + if (!conn || !path) + return -1; + + signal =3D dbus_message_new_signal(path, COMMAND_PASSTHROUGH_INTERFACE, + "Answer"); + if (!signal) { + ofono_error("Unable to allocate new %s.PropertyChanged signal", + COMMAND_PASSTHROUGH_INTERFACE); + return -1; + } + + dbus_message_iter_init_append(signal, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &answer); + + DBG(""); + + return g_dbus_send_message(conn, signal); +} + +static void command_passthrough_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + GAtResultIter iter; + guint len =3D 0; + char *answer; + + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, NULL)) { + len +=3D strlen(g_at_result_iter_raw_line(&iter))+2; + } + + len +=3D strlen(g_at_result_final_response(result))+3; + answer =3D g_new0(char, len); + g_at_result_iter_init(&iter, result); + + while (g_at_result_iter_next(&iter, NULL)) { + sprintf(answer+strlen(answer),"%s\r\n", + g_at_result_iter_raw_line(&iter)); + } + + sprintf(answer+strlen(answer),"%s\r\n", + g_at_result_final_response(result)); + + DBG("answer_len: %u, answer_string: %s", len, answer); + command_passthrough_signal_answer(answer, user_data); + + g_free(answer); +} + +static DBusMessage *command_passthrough_simple(DBusConnection *conn, + DBusMessage *msg, + void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessageIter iter; + const char *command; + + if (!dbus_message_iter_init(msg, &iter)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "No arguments given"); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_STRING) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid argument type: '%c'", + dbus_message_iter_get_arg_type(&iter)); + + dbus_message_iter_get_basic(&iter, &command); + + g_at_chat_send(data->app, command, NULL, command_passthrough_cb, + modem, NULL); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *command_passthrough_with_prompt(DBusConnection *conn, + DBusMessage *msg, + void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessageIter iter; + const char *command, *prompt, *argument; + + if (!dbus_message_iter_init(msg, &iter)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "No arguments given"); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_STRING) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid argument type: '%c'", + dbus_message_iter_get_arg_type(&iter)); + + dbus_message_iter_get_basic(&iter, &command); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_STRING) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid argument type: '%c'", + dbus_message_iter_get_arg_type(&iter)); + + dbus_message_iter_get_basic(&iter, &prompt); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_STRING) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid argument type: '%c'", + dbus_message_iter_get_arg_type(&iter)); + + dbus_message_iter_get_basic(&iter, &argument); + + executeWithPrompt(data->app, command, prompt, argument, + command_passthrough_cb, modem, NULL); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *command_passthrough_send_break(DBusConnection *conn, + DBusMessage *msg, + void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + GIOChannel *channel =3D g_at_chat_get_channel(data->app); + + g_io_channel_write_chars(channel, "\r", 1, NULL, NULL); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable command_passthrough_methods[] =3D { + { GDBUS_ASYNC_METHOD("Simple", + GDBUS_ARGS({ "command", "s" }), + NULL, + command_passthrough_simple) }, + { GDBUS_ASYNC_METHOD("WithPrompt", + GDBUS_ARGS({ "command", "s" }, { "prompt", "s" }, + { "argument", "s" }), + NULL, + command_passthrough_with_prompt) }, + { GDBUS_ASYNC_METHOD("SendBreak", + NULL, + NULL, + command_passthrough_send_break) }, + {} +}; + +static const GDBusSignalTable command_passthrough_signals[] =3D { + { GDBUS_SIGNAL("Answer", + GDBUS_ARGS({ "answer", "s" })) }, + { } +}; + +static void gemalto_command_passthrough_enable(struct ofono_modem *modem) +{ + DBusConnection *conn =3D ofono_dbus_get_connection(); + const char *path =3D ofono_modem_get_path(modem); + + /* Create Command Passthrough DBus interface */ + if (!g_dbus_register_interface(conn, path, COMMAND_PASSTHROUGH_INTERFACE, + command_passthrough_methods, + command_passthrough_signals, + NULL, + modem, + NULL)) { + ofono_error("Could not register %s interface under %s", + COMMAND_PASSTHROUGH_INTERFACE, path); + return; + } + + ofono_modem_add_interface(modem, COMMAND_PASSTHROUGH_INTERFACE); +} + +/*************************************************************************= ****** + * GNSS interface + *************************************************************************= *****/ + +#define GNSS_INTERFACE OFONO_SERVICE ".gemalto.GNSS" + +static void gnss_get_properties_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + const char *port =3D ofono_modem_get_string(modem, "GNSS"); + GAtResultIter iter; + DBusMessage *reply; + DBusMessageIter dbusiter; + DBusMessageIter dict; + + if (data->gnss_msg =3D=3D NULL) + return; + + if (!ok) + goto error; + + reply =3D dbus_message_new_method_return(data->gnss_msg); + dbus_message_iter_init_append(reply, &dbusiter); + dbus_message_iter_open_container(&dbusiter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dict); + g_at_result_iter_init(&iter, result); + + /* supported format: ^SGPSC: "Nmea/Output","off" */ + while (g_at_result_iter_next(&iter, "^SGPSC:")) { + const char *name =3D ""; + const char *val =3D ""; + + if (!g_at_result_iter_next_string(&iter, &name)) + continue; + + /* + * skip the "Info" property: + * different line format and different usage + */ + if (g_str_equal(name,"Info")) + continue; + + if (!g_at_result_iter_next_string(&iter, &val)) + continue; + + ofono_dbus_dict_append(&dict, name, DBUS_TYPE_STRING, &val); + } + + ofono_dbus_dict_append(&dict, "Port", DBUS_TYPE_STRING, &port); + dbus_message_iter_close_container(&dbusiter, &dict); + __ofono_dbus_pending_reply(&data->gnss_msg, reply); + return; + +error: + __ofono_dbus_pending_reply(&data->gnss_msg, + __ofono_error_failed(data->gnss_msg)); +} + +static DBusMessage *gnss_get_properties(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + + if (data->gnss_msg !=3D NULL) + return __ofono_error_busy(msg); + + if (!g_at_chat_send(data->app, "AT^SGPSC?", sgpsc_prefix, + gnss_get_properties_cb, modem, NULL)) + return __ofono_error_failed(msg); + + data->gnss_msg =3D dbus_message_ref(msg); + + return NULL; +} + +static void gnss_set_properties_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessage *reply; + + if (data->gnss_msg =3D=3D NULL) + return; + + if (!ok) { + __ofono_dbus_pending_reply(&data->gnss_msg, + __ofono_error_failed(data->gnss_msg)); + return; + } + + reply =3D dbus_message_new_method_return(data->gnss_msg); + __ofono_dbus_pending_reply(&data->gnss_msg, reply); +} + +static DBusMessage *gnss_set_property(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessageIter iter, var; + const char *name; + char *value; + char buf[256]; + + if (data->gnss_msg !=3D NULL) + return __ofono_error_busy(msg); + + if (dbus_message_iter_init(msg, &iter) =3D=3D FALSE) + return __ofono_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &name); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_VARIANT) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &var); + + if (dbus_message_iter_get_arg_type(&var) !=3D + DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &value); + + snprintf(buf, sizeof(buf), "AT^SGPSC=3D\"%s\",\"%s\"", name, value); + + if (!g_at_chat_send(data->app, buf, sgpsc_prefix, + gnss_set_properties_cb, modem, NULL)) + return __ofono_error_failed(msg); + + data->gnss_msg =3D dbus_message_ref(msg); + return NULL; +} + +static const GDBusMethodTable gnss_methods[] =3D { + { GDBUS_ASYNC_METHOD("GetProperties", + NULL, GDBUS_ARGS({ "properties", "a{sv}" }), + gnss_get_properties) }, + { GDBUS_ASYNC_METHOD("SetProperty", + GDBUS_ARGS({ "property", "s" }, { "value", "v" }), + NULL, gnss_set_property) }, + { } +}; + +static void gnss_exec_stored_param(struct ofono_modem *modem, + const char *filename) { + struct gemalto_data *data =3D ofono_modem_get_data(modem); + const char *vid =3D ofono_modem_get_string(modem, "Vendor"); + const char *pid =3D ofono_modem_get_string(modem, "Model"); + char store[64]; + int index; + char *property, *value; + char key[32]; + GKeyFile *f; + char *command; + + sprintf(store,"%s-%s/%s", vid, pid, filename); + f =3D storage_open(NULL, store); + + if (!f) + return; + + for (index=3D0;;index++) { + sprintf(key, "property_%d", index); + property =3D g_key_file_get_string(f, "Properties", key, NULL); + + sprintf(key, "value_%d", index); + value =3D g_key_file_get_string(f, "Properties", key, NULL); + + if(!property || !value) + break; + + command =3D g_strdup_printf("AT^SGPSC=3D%s,%s", property, value); + DBG(REDCOLOR"setting GNSS property: %sNOCOLOR", command); + g_at_chat_send(data->app, command, NULL, NULL, NULL, NULL); + free(command); + } + + storage_close(NULL, store, f, FALSE); +} + +static void gemalto_gnss_enable_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_modem *modem =3D user_data; + DBusConnection *conn =3D ofono_dbus_get_connection(); + const char *path =3D ofono_modem_get_path(modem); + + if (!ok) + return; /* the module does not support GNSS */ + + gnss_exec_stored_param(modem, "gnss_startup"); + + /* Create GNSS DBus interface */ + if (!g_dbus_register_interface(conn, path, GNSS_INTERFACE, + gnss_methods, + NULL, + NULL, + modem, + NULL)) { + ofono_error("Could not register %s interface under %s", + GNSS_INTERFACE, path); + return; + } + + ofono_modem_add_interface(modem, GNSS_INTERFACE); +} + +static void gemalto_gnss_enable(struct ofono_modem *modem) +{ + struct gemalto_data *data =3D ofono_modem_get_data(modem); + + g_at_chat_send(data->app, "AT^SGPSC?", sgpsc_prefix, + gemalto_gnss_enable_cb, modem, NULL); +} + +/*************************************************************************= ****** + * Hardware control interface + *************************************************************************= *****/ + +#define HARDWARE_CONTROL_INTERFACE OFONO_SERVICE ".gemalto.HardwareControl" + +static DBusMessage *hc_get_properties(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessage *reply; + DBusMessageIter dbusiter; + DBusMessageIter dict; + + reply =3D dbus_message_new_method_return(msg); + dbus_message_iter_init_append(reply, &dbusiter); + dbus_message_iter_open_container(&dbusiter, DBUS_TYPE_ARRAY, + OFONO_PROPERTIES_ARRAY_SIGNATURE, + &dict); + + ofono_dbus_dict_append(&dict, "Powersave", DBUS_TYPE_BOOLEAN, + &data->powersave); + dbus_message_iter_close_container(&dbusiter, &dict); + + return reply; +} + +/* + * powersave for older modules: + * command_0=3DAT+CFUN=3D7 + * return: + * command_0=3DAT+CFUN=3D1 + * + * powersave example for modules with GNSS (could also only stop the outpu= t): + * command_0=3DAT+CREG=3D0 + * command_1=3DAT+CGREG=3D0 + * command_2=3DAT+CEREG=3D0 + * command_3=3DAT^SGPSC=3D"Engine","0" + * command_4=3DAT^SGPSC=3D"Power/Antenna","off" + * return: + * command_0=3DAT+CREG=3D2 + * command_1=3DAT+CGREG=3D2 + * command_2=3DAT+CEREG=3D2 + * command_4=3DAT^SGPSC=3D"Power/Antenna","on" + * command_3=3DAT^SGPSC=3D"Engine","1" + */ + +static void gemalto_powersave_cb(gboolean ok, GAtResult *result, + gpointer user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessage *reply; + + /* flip the state in any case */ + data->powersave =3D !data->powersave; + + if (data->hc_msg =3D=3D NULL) + return; + + reply =3D dbus_message_new_method_return(data->hc_msg); + __ofono_dbus_pending_reply(&data->hc_msg, reply); +} + +static DBusMessage *hc_set_property(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessageIter iter, var; + const char *name; + gboolean enable; + + if (data->hc_msg !=3D NULL) + return __ofono_error_busy(msg); + + if (dbus_message_iter_init(msg, &iter) =3D=3D FALSE) + return __ofono_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_STRING) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &name); + + if (!g_str_equal(name, "Powersave")) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) !=3D DBUS_TYPE_VARIANT) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &var); + + if (dbus_message_iter_get_arg_type(&var) !=3D DBUS_TYPE_BOOLEAN) + return __ofono_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &enable); + + if (data->powersave =3D=3D enable) + return dbus_message_new_method_return(msg); + + gemalto_exec_stored_cmd(modem, enable ? "power_mode_powersave" : + "power_mode_normal"); + + gnss_exec_stored_param(modem, enable ? "gnss_powersave" : + "gnss_normal"); + + if (!g_at_chat_send(data->app, "AT", none_prefix, + gemalto_powersave_cb, modem, NULL)) + return __ofono_error_failed(msg); + + data->hc_msg =3D dbus_message_ref(msg); + return NULL; +} + +static void gemalto_smso_cb(gboolean ok, GAtResult *result, gpointer user_= data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessage *reply; + + if (data->hc_msg =3D=3D NULL) + return; + + if (data->conn !=3D GEMALTO_CONNECTION_SERIAL) + goto finished; + + if (data->mdm) + g_at_chat_unref(data->mdm); + data->mdm =3D NULL; + + if (data->app) + g_at_chat_unref(data->app); + data->app =3D NULL; + + if (ok) + ofono_modem_set_powered(modem, FALSE); + +finished: + reply =3D dbus_message_new_method_return(data->hc_msg); + __ofono_dbus_pending_reply(&data->hc_msg, reply); +} + +static DBusMessage *hc_shutdown(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + + if (data->hc_msg !=3D NULL) + return __ofono_error_busy(msg); + + if (!g_at_chat_send(data->app, "AT^SMSO", none_prefix, + gemalto_smso_cb, modem, NULL)) + return __ofono_error_failed(msg); + + data->hc_msg =3D dbus_message_ref(msg); + return NULL; +} + +static void gemalto_detect_sysstart(GAtResult *result, gpointer user_data); + +static void gemalto_reset_cb(gboolean ok, GAtResult *result, gpointer user= _data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + DBusMessage *reply; + + if (data->hc_msg =3D=3D NULL) + return; + + if (data->conn !=3D GEMALTO_CONNECTION_SERIAL) + goto finished; + + data->modem_ready_id =3D g_at_chat_register(data->app, + "^SYSSTART", gemalto_detect_sysstart, FALSE, + modem, NULL); + +finished: + reply =3D dbus_message_new_method_return(data->hc_msg); + __ofono_dbus_pending_reply(&data->hc_msg, reply); +} + +static DBusMessage *hc_reset(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + + if (data->hc_msg !=3D NULL) + return __ofono_error_busy(msg); + + if (!g_at_chat_send(data->app, "AT+CFUN=3D1,1", none_prefix, + gemalto_reset_cb, modem, NULL)) + return __ofono_error_failed(msg); + + data->hc_msg =3D dbus_message_ref(msg); + return NULL; +} + +static const GDBusMethodTable hardware_control_methods[] =3D { + { GDBUS_ASYNC_METHOD("GetProperties", + NULL, GDBUS_ARGS({ "properties", "a{sv}" }), + hc_get_properties) }, + { GDBUS_ASYNC_METHOD("SetProperty", + GDBUS_ARGS({ "property", "s" }, { "value", "v" }), + NULL, hc_set_property) }, + { GDBUS_ASYNC_METHOD("Shutdown", + NULL, NULL, hc_shutdown) }, + { GDBUS_ASYNC_METHOD("Reset", + NULL, NULL, hc_reset) }, + { } +}; + +static void gemalto_hardware_control_enable(struct ofono_modem *modem) +{ + DBusConnection *conn =3D ofono_dbus_get_connection(); + const char *path =3D ofono_modem_get_path(modem); + + /* Create Hardware Control DBus interface */ + if (!g_dbus_register_interface(conn, path, HARDWARE_CONTROL_INTERFACE, + hardware_control_methods, + NULL, + NULL, + modem, + NULL)) { + ofono_error("Could not register %s interface under %s", + HARDWARE_CONTROL_INTERFACE, path); + return; + } + + ofono_modem_add_interface(modem, HARDWARE_CONTROL_INTERFACE); +} + +/*************************************************************************= ****** + * modem plugin + *************************************************************************= *****/ + +#ifdef HAVE_ELL +static int mbim_parse_descriptors(struct gemalto_data *md, const char *fil= e) +{ + void *data; + size_t len; + const struct mbim_desc *desc =3D NULL; + const struct mbim_extended_desc *ext_desc =3D NULL; + + data =3D l_file_get_contents(file, &len); + if (!data) + return -EIO; + + if (!mbim_find_descriptors(data, len, &desc, &ext_desc)) { + l_free(data); + return -ENOENT; + } + + if (desc) + md->max_segment =3D L_LE16_TO_CPU(desc->wMaxControlMessage); + + if (ext_desc) + md->max_outstanding =3D ext_desc->bMaxOutstandingCommandMessages; + + l_free(data); + return 0; +} + +static int mbim_probe(struct ofono_modem *modem, struct gemalto_data *data) +{ + const char *descriptors; + int err; + + descriptors =3D gemalto_get_string(modem, "DescriptorFile"); + + if (!descriptors) + return -EINVAL; + + data->max_outstanding =3D 1; + + err =3D mbim_parse_descriptors(data, descriptors); + if (err < 0) { + DBG("Warning, unable to load descriptors, setting defaults"); + data->max_segment =3D 512; + } + + DBG("MaxSegment: %d, MaxOutstanding: %d", + data->max_segment, data->max_outstanding); + + return 0; +} +#endif + +static int gemalto_probe(struct ofono_modem *modem) +{ + struct gemalto_data *data; + + data =3D g_try_new0(struct gemalto_data, 1); + if (data =3D=3D NULL) + return -ENOMEM; + +#ifdef HAVE_ELL + mbim_probe(modem, data); +#endif + + ofono_modem_set_data(modem, data); + + return 0; +} + +static void gemalto_remove(struct ofono_modem *modem) +{ + struct gemalto_data *data; + DBusConnection *conn =3D ofono_dbus_get_connection(); + const char *path; + + if (!modem) + return; + + data =3D ofono_modem_get_data(modem); + path =3D ofono_modem_get_path(modem); + + if (!data) + return; + +#ifdef HAVE_ELL + if (data->mbim =3D=3D STATE_PRESENT) { + mbim_device_shutdown(data->device); + } +#endif + + if (data->qmi =3D=3D STATE_PRESENT) { + qmi_device_unref(data->device); + } + + if (data->app) { + /* Cleanup potential SIM state polling */ + at_util_sim_state_query_free(data->sim_state_query); + data->sim_state_query =3D NULL; + + g_at_chat_cancel_all(data->app); + g_at_chat_unregister_all(data->app); + g_at_chat_unref(data->app); + data->app =3D NULL; + } + + if (data->mdm) { + g_at_chat_cancel_all(data->app); + g_at_chat_unregister_all(data->mdm); + g_at_chat_unref(data->mdm); + data->mdm =3D NULL; + } + + if (conn && path) { + if (g_dbus_unregister_interface(conn, path, + HARDWARE_MONITOR_INTERFACE)) + ofono_modem_remove_interface(modem, + HARDWARE_MONITOR_INTERFACE); + + if (g_dbus_unregister_interface(conn, path, + CINTERION_LEGACY_HWMON_INTERFACE)) + ofono_modem_remove_interface(modem, + CINTERION_LEGACY_HWMON_INTERFACE); + + if (g_dbus_unregister_interface(conn, path, + GEMALTO_NITZ_TIME_INTERFACE)) + ofono_modem_remove_interface(modem, + GEMALTO_NITZ_TIME_INTERFACE); + + if (g_dbus_unregister_interface(conn, path, + COMMAND_PASSTHROUGH_INTERFACE)) + ofono_modem_remove_interface(modem, + COMMAND_PASSTHROUGH_INTERFACE); + + if (g_dbus_unregister_interface(conn, path, + GNSS_INTERFACE)) + ofono_modem_remove_interface(modem, + GNSS_INTERFACE); + + if (g_dbus_unregister_interface(conn, path, + HARDWARE_CONTROL_INTERFACE)) + ofono_modem_remove_interface(modem, + HARDWARE_CONTROL_INTERFACE); + } + + //if (data->conn =3D=3D GEMALTO_CONNECTION_SERIAL) + // return; + + ofono_modem_set_data(modem, NULL); + g_free(data); +} + +static void sim_ready_cb(gboolean present, gpointer user_data) +{ + struct ofono_modem *modem =3D user_data; + struct gemalto_data *data =3D ofono_modem_get_data(modem); + struct ofono_sim *sim =3D data->sim; + + at_util_sim_state_query_free(data->sim_state_query); + data->sim_state_query =3D NULL; + + DBG("sim present: %d", present); + + ofono_sim_inserted_notify(sim, present); +} + +static void gemalto_ciev_simstatus_notify(GAtResultIter *iter, + struct ofono_modem *modem) +{ + struct gemalto_data *data =3D ofono_modem_get_data(modem); + struct ofono_sim *sim =3D data->sim; + int status; + + DBG("sim status %d", status); + + if (!g_at_result_iter_next_number(iter, &status)) + return; + + switch (status) { + /* SIM is removed from the holder */ + case 0: + ofono_sim_inserted_notify(sim, FALSE); + break; + + /* SIM is inserted inside the holder */ + case 1: + /* The SIM won't be ready yet */ + data->sim_state_query =3D at_util_sim_state_query_new(data->app, + 1, 20, sim_ready_cb, modem, + NULL); + break; + + /* USIM initialization completed. UE has finished reading USIM data. */ + case 5: + ofono_sim_initialized_notify(sim); + break; + + default: + break; + } +} + +static void gemalto_ciev_nitz_notify(GAtResultIter *iter, + struct ofono_modem *modem) +{ + struct gemalto_data *data =3D ofono_modem_get_data(modem); + const char *nitz_data; + char buf[32]; + + /* Example: +CIEV: nitz,