diff --git a/ofono/Makefile.am b/ofono/Makefile.am index 0089390b..9f452eb1 100644 --- a/ofono/Makefile.am +++ b/ofono/Makefile.am @@ -119,6 +119,32 @@ builtin_sources += plugins/udevng.c endif if RILMODEM +if JOLLA_RILMODEM +builtin_modules += ril +builtin_sources += drivers/ril/ril_call_barring.c \ + drivers/ril/ril_call_forward.c \ + drivers/ril/ril_call_settings.c \ + drivers/ril/ril_call_volume.c \ + drivers/ril/ril_cbs.c \ + drivers/ril/ril_devinfo.c \ + drivers/ril/ril_gprs.c \ + drivers/ril/ril_gprs_context.c \ + drivers/ril/ril_mce.c \ + drivers/ril/ril_modem.c \ + drivers/ril/ril_netreg.c \ + drivers/ril/ril_oem_raw.c \ + drivers/ril/ril_phonebook.c \ + drivers/ril/ril_plugin.c \ + drivers/ril/ril_plugin_dbus.c \ + drivers/ril/ril_radio_settings.c \ + drivers/ril/ril_sim.c \ + drivers/ril/ril_sim_dbus.c \ + drivers/ril/ril_sms.c \ + drivers/ril/ril_stk.c \ + drivers/ril/ril_ussd.c \ + drivers/ril/ril_util.c \ + drivers/ril/ril_voicecall.c +else builtin_sources += $(gril_sources) builtin_modules += rildev @@ -149,7 +175,7 @@ builtin_sources += drivers/rilmodem/rilmodem.h \ drivers/rilmodem/oemraw-messages.c \ drivers/rilmodem/call-barring.c \ drivers/rilmodem/stk.c - +endif endif if ISIMODEM diff --git a/ofono/configure.ac b/ofono/configure.ac index 6c6f39b2..5fcf03dd 100644 --- a/ofono/configure.ac +++ b/ofono/configure.ac @@ -42,6 +42,7 @@ AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], if (test "${enableval}" = "yes" && test "${ac_cv_prog_cc_g}" = "yes"); then CFLAGS="$CFLAGS -g" + CPPFLAGS="$CPPFLAGS -DDEBUG" fi ]) @@ -166,6 +167,20 @@ AC_ARG_ENABLE(rilmodem, AC_HELP_STRING([--disable-rilmodem], [enable_rilmodem=${enableval}]) AM_CONDITIONAL(RILMODEM, test "${enable_rilmodem}" != "no") +AC_ARG_ENABLE(jolla-rilmodem, + AC_HELP_STRING([--enable-jolla-rilmodem], [enable Jolla RIL modem]), + [enable_jolla_rilmodem=${enableval}], [enable_jolla_rilmodem="no"]) +AM_CONDITIONAL(JOLLA_RILMODEM, test "${enable_jolla_rilmodem}" != "no") + +if (test "${enable_jolla_rilmodem}" = "yes"); then + PKG_CHECK_MODULES(GRILIO, libgrilio, dummy=yes, + AC_MSG_ERROR(libgrilio is required)) + PKG_CHECK_MODULES(GLIBUTIL, libglibutil, dummy=yes, + AC_MSG_ERROR(libglibutil is required)) + CFLAGS="$CFLAGS $GRILIO_CFLAGS $GLIBUTIL_CFLAGS" + LIBS="$LIBS $GRILIO_LIBS $GLIBUTIL_LIBS" +fi + AC_ARG_ENABLE(qmimodem, AC_HELP_STRING([--disable-qmimodem], [disable Qualcomm QMI modem support]), [enable_qmimodem=${enableval}]) diff --git a/ofono/drivers/ril/ril_call_barring.c b/ofono/drivers/ril/ril_call_barring.c new file mode 100644 index 00000000..0ccd9655 --- /dev/null +++ b/ofono/drivers/ril/ril_call_barring.c @@ -0,0 +1,284 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "common.h" + +/* See 3GPP 27.007 7.4 for possible values */ +#define RIL_MAX_SERVICE_LENGTH 3 + +/* + * ril.h does not state that string count must be given, but that is + * still expected by the modem + */ +#define RIL_QUERY_STRING_COUNT 4 +#define RIL_SET_STRING_COUNT 5 +#define RIL_SET_PW_STRING_COUNT 3 + +struct ril_call_barring { + GRilIoQueue *q; + guint timer_id; +}; + +struct ril_call_barring_cbd { + struct ril_call_barring *bd; + union _ofono_call_barring_cb { + ofono_call_barring_query_cb_t query; + ofono_call_barring_set_cb_t set; + gpointer ptr; + } cb; + gpointer data; +}; + +#define ril_call_barring_cbd_free g_free + +static inline struct ril_call_barring *ril_call_barring_get_data( + struct ofono_call_barring *b) +{ + return ofono_call_barring_get_data(b); +} + +static struct ril_call_barring_cbd *ril_call_barring_cbd_new( + struct ril_call_barring *bd, void *cb, void *data) +{ + struct ril_call_barring_cbd *cbd; + + cbd = g_new0(struct ril_call_barring_cbd, 1); + cbd->bd = bd; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static inline void ril_call_barring_submit_request(struct ril_call_barring *bd, + GRilIoRequest* req, guint code, GRilIoChannelResponseFunc response, + void *cb, void *data) +{ + grilio_queue_send_request_full(bd->q, req, code, response, + ril_call_barring_cbd_free, + ril_call_barring_cbd_new(bd, cb, data)); +} + +static void ril_call_barring_query_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_barring_cbd *cbd = user_data; + ofono_call_barring_query_cb_t cb = cbd->cb.query; + + if (status == RIL_E_SUCCESS) { + int bearer_class = 0; + GRilIoParser rilp; + + /* + * Services for which the specified barring facility is active. + * "0" means "disabled for all, -1 if unknown" + */ + grilio_parser_init(&rilp, data, len); + grilio_parser_get_int32(&rilp, NULL); /* count */ + grilio_parser_get_int32(&rilp, &bearer_class); + DBG("Active services: %d", bearer_class); + cb(ril_error_ok(&error), bearer_class, cbd->data); + } else { + ofono_error("Call Barring query error %d", status); + cb(ril_error_failure(&error), 0, cbd->data); + } +} + +static void ril_call_barring_query(struct ofono_call_barring *b, + const char *lock, int cls, + ofono_call_barring_query_cb_t cb, void *data) +{ + struct ril_call_barring *bd = ofono_call_barring_get_data(b); + char cls_textual[RIL_MAX_SERVICE_LENGTH]; + GRilIoRequest *req = grilio_request_new(); + + DBG("lock: %s, services to query: %d", lock, cls); + + /* + * RIL modems do not support 7 as default bearer class. According to + * the 22.030 Annex C: When service code is not given it corresponds to + * "All tele and bearer services" + */ + if (cls == BEARER_CLASS_DEFAULT) { + cls = SERVICE_CLASS_NONE; + } + + sprintf(cls_textual, "%d", cls); + + /* + * See 3GPP 27.007 7.4 for parameter descriptions. + * According to ril.h password should be empty string "" when not + * needed, but in reality we only need to give string length as 0 + */ + grilio_request_append_int32(req, RIL_QUERY_STRING_COUNT); + grilio_request_append_utf8(req, lock); /* Facility code */ + grilio_request_append_int32(req, 0); /* Password length */ + grilio_request_append_utf8(req, cls_textual); + grilio_request_append_utf8(req, NULL); /* AID (not yet supported) */ + + ril_call_barring_submit_request(bd, req, + RIL_REQUEST_QUERY_FACILITY_LOCK, + ril_call_barring_query_cb, cb, data); + grilio_request_unref(req); +} + +static void ril_call_barring_set_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_barring_cbd *cbd = user_data; + ofono_call_barring_set_cb_t cb = cbd->cb.set; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("Call Barring Set error %d", status); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_call_barring_set(struct ofono_call_barring *b, + const char *lock, int enable, const char *passwd, int cls, + ofono_call_barring_set_cb_t cb, void *data) +{ + struct ril_call_barring *bd = ofono_call_barring_get_data(b); + char cls_textual[RIL_MAX_SERVICE_LENGTH]; + GRilIoRequest *req = grilio_request_new(); + + DBG("lock: %s, enable: %i, bearer class: %i", lock, enable, cls); + + /* + * RIL modem does not support 7 as default bearer class. According to + * the 22.030 Annex C: When service code is not given it corresponds to + * "All tele and bearer services" + */ + if (cls == BEARER_CLASS_DEFAULT) { + cls = SERVICE_CLASS_NONE; + } + + sprintf(cls_textual, "%d", cls); + + /* See 3GPP 27.007 7.4 for parameter descriptions */ + grilio_request_append_int32(req, RIL_SET_STRING_COUNT); + grilio_request_append_utf8(req, lock); /* Facility code */ + grilio_request_append_utf8(req, enable ? + RIL_FACILITY_LOCK : + RIL_FACILITY_UNLOCK); + grilio_request_append_utf8(req, passwd); + grilio_request_append_utf8(req, cls_textual); + grilio_request_append_utf8(req, NULL); /* AID (not yet supported) */ + + ril_call_barring_submit_request(bd, req, + RIL_REQUEST_SET_FACILITY_LOCK, + ril_call_barring_set_cb, cb, data); + grilio_request_unref(req); +} + +static void ril_call_barring_set_passwd_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_barring_cbd *cbd = user_data; + ofono_call_barring_set_cb_t cb = cbd->cb.set; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("Call Barring Set PW error %d", status); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_call_barring_set_passwd(struct ofono_call_barring *b, + const char *lock, const char *old_passwd, + const char *new_passwd, ofono_call_barring_set_cb_t cb, + void *data) +{ + struct ril_call_barring *bd = ofono_call_barring_get_data(b); + GRilIoRequest *req = grilio_request_new(); + + DBG(""); + grilio_request_append_int32(req, RIL_SET_PW_STRING_COUNT); + grilio_request_append_utf8(req, lock); /* Facility code */ + grilio_request_append_utf8(req, old_passwd); + grilio_request_append_utf8(req, new_passwd); + + ril_call_barring_submit_request(bd, req, + RIL_REQUEST_CHANGE_BARRING_PASSWORD, + ril_call_barring_set_passwd_cb, cb, data); + grilio_request_unref(req); +} + +static gboolean ril_call_barring_register(gpointer user_data) +{ + struct ofono_call_barring *b = user_data; + struct ril_call_barring *bd = ril_call_barring_get_data(b); + + GASSERT(bd->timer_id); + bd->timer_id = 0; + ofono_call_barring_register(b); + return FALSE; +} + +static int ril_call_barring_probe(struct ofono_call_barring *b, + unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_call_barring *bd = g_new0(struct ril_call_barring, 1); + + DBG(""); + bd->q = grilio_queue_new(ril_modem_io(modem)); + bd->timer_id = g_idle_add(ril_call_barring_register, b); + ofono_call_barring_set_data(b, bd); + return 0; +} + +static void ril_call_barring_remove(struct ofono_call_barring *b) +{ + struct ril_call_barring *bd = ril_call_barring_get_data(b); + + DBG(""); + ofono_call_barring_set_data(b, NULL); + + if (bd->timer_id > 0) { + g_source_remove(bd->timer_id); + } + + grilio_queue_cancel_all(bd->q, FALSE); + grilio_queue_unref(bd->q); + g_free(bd); +} + +const struct ofono_call_barring_driver ril_call_barring_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_call_barring_probe, + .remove = ril_call_barring_remove, + .query = ril_call_barring_query, + .set = ril_call_barring_set, + .set_passwd = ril_call_barring_set_passwd +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_call_forward.c b/ofono/drivers/ril/ril_call_forward.c new file mode 100644 index 00000000..dd4c30e1 --- /dev/null +++ b/ofono/drivers/ril/ril_call_forward.c @@ -0,0 +1,315 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "common.h" + +struct ril_call_forward { + GRilIoQueue *q; + guint timer_id; +}; + +enum ril_call_forward_cmd { + CF_ACTION_DISABLE, + CF_ACTION_ENABLE, + CF_ACTION_UNUSED, + CF_ACTION_REGISTRATION, + CF_ACTION_ERASURE +}; + +struct ril_call_forward_cbd { + struct ril_call_forward *fd; + union _ofono_call_forward_cb { + ofono_call_forwarding_query_cb_t query; + ofono_call_forwarding_set_cb_t set; + gpointer ptr; + } cb; + gpointer data; +}; + +#define ril_call_forward_cbd_free g_free + +static inline struct ril_call_forward *ril_call_forward_get_data( + struct ofono_call_forwarding *cf) +{ + return ofono_call_forwarding_get_data(cf); +} + +static struct ril_call_forward_cbd *ril_call_forward_cbd_new(void *cb, + void *data) +{ + struct ril_call_forward_cbd *cbd; + + cbd = g_new0(struct ril_call_forward_cbd, 1); + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static inline void ril_call_forward_submit_request(struct ril_call_forward *fd, + GRilIoRequest* req, guint code, GRilIoChannelResponseFunc response, + void *cb, void *data) +{ + grilio_queue_send_request_full(fd->q, req, code, response, + ril_call_forward_cbd_free, + ril_call_forward_cbd_new(cb, data)); +} + +static void ril_forward_set_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_forward_cbd *cbd = user_data; + ofono_call_forwarding_set_cb_t cb = cbd->cb.set; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("CF setting failed"); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_call_forward_registration(struct ofono_call_forwarding *cf, + int type, int cls, const struct ofono_phone_number *number, + int time, ofono_call_forwarding_set_cb_t cb, void *data) +{ + struct ril_call_forward *fd = ril_call_forward_get_data(cf); + GRilIoRequest *req = grilio_request_new(); + + ofono_info("cf registration"); + grilio_request_append_int32(req, CF_ACTION_REGISTRATION); + grilio_request_append_int32(req, type); + + /* + * Modem seems to respond with error to all queries + * or settings made with bearer class + * BEARER_CLASS_DEFAULT. Design decision: If given + * class is BEARER_CLASS_DEFAULT let's map it to + * BEARER_CLASS_VOICE as per RIL design. + */ + if (cls == BEARER_CLASS_DEFAULT) { + cls = BEARER_CLASS_VOICE; + } + + grilio_request_append_int32(req, cls); + grilio_request_append_int32(req, number->type); + grilio_request_append_utf8(req, number->number); + grilio_request_append_int32(req, time); + + ril_call_forward_submit_request(fd, req, RIL_REQUEST_SET_CALL_FORWARD, + ril_forward_set_cb, cb, data); + grilio_request_unref(req); +} + +static void ril_call_forward_send_cmd(struct ofono_call_forwarding *cf, + int type, int cls, ofono_call_forwarding_set_cb_t cb, + void *data, int action) +{ + struct ril_call_forward *fd = ril_call_forward_get_data(cf); + GRilIoRequest *req = grilio_request_new(); + + grilio_request_append_int32(req, action); + grilio_request_append_int32(req, type); + + /* + * Modem seems to respond with error to all queries + * or settings made with bearer class + * BEARER_CLASS_DEFAULT. Design decision: If given + * class is BEARER_CLASS_DEFAULT let's map it to + * BEARER_CLASS_VOICE as per RIL design. + */ + if (cls == BEARER_CLASS_DEFAULT) { + cls = BEARER_CLASS_VOICE; + } + + grilio_request_append_int32(req, cls); /* Service class */ + + /* Following 3 values have no real meaning in erasure + * but apparently RIL expects them so fields need to + * be filled. Otherwise there is no response + */ + grilio_request_append_int32(req, 0x81); /* TOA unknown */ + grilio_request_append_utf8(req, "1234567890"); + grilio_request_append_int32(req, 60); + + ril_call_forward_submit_request(fd, req, RIL_REQUEST_SET_CALL_FORWARD, + ril_forward_set_cb, cb, data); + grilio_request_unref(req); +} + +static void ril_call_forward_erasure(struct ofono_call_forwarding *cf, + int type, int cls, ofono_call_forwarding_set_cb_t cb, void *data) +{ + ofono_info("CF_ACTION_ERASURE"); + ril_call_forward_send_cmd(cf, type, cls, cb, data, CF_ACTION_ERASURE); +} + +static void ril_call_forward_deactivate(struct ofono_call_forwarding *cf, + int type, int cls, ofono_call_forwarding_set_cb_t cb, void *data) +{ + ofono_info("CF_ACTION_DISABLE"); + ril_call_forward_send_cmd(cf, type, cls, cb, data, CF_ACTION_DISABLE); +} + +static void ril_call_forward_activate(struct ofono_call_forwarding *cf, + int type, int cls, ofono_call_forwarding_set_cb_t cb, void *data) +{ + ofono_info("CF_ACTION_ENABLE"); + ril_call_forward_send_cmd(cf, type, cls, cb, data, CF_ACTION_ENABLE); +} + +static void ril_call_forward_query_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_forward_cbd *cbd = user_data; + ofono_call_forwarding_query_cb_t cb = cbd->cb.query; + + if (status == RIL_E_SUCCESS) { + struct ofono_call_forwarding_condition *list = NULL; + GRilIoParser rilp; + int count = 0; + int i; + + grilio_parser_init(&rilp, data, len); + grilio_parser_get_int32(&rilp, &count); + + list = g_new0(struct ofono_call_forwarding_condition, count); + for (i = 0; i < count; i++) { + struct ofono_call_forwarding_condition *fw = list + i; + char *str; + + grilio_parser_get_int32(&rilp, &fw->status); + grilio_parser_get_int32(&rilp, NULL); + grilio_parser_get_int32(&rilp, &fw->cls); + grilio_parser_get_int32(&rilp, &fw->phone_number.type); + str = grilio_parser_get_utf8(&rilp); + if (str) { + strncpy(fw->phone_number.number, str, + OFONO_MAX_PHONE_NUMBER_LENGTH); + fw->phone_number.number[ + OFONO_MAX_PHONE_NUMBER_LENGTH] = 0; + g_free(str); + } + grilio_parser_get_int32(&rilp, &fw->time); + } + + cb(ril_error_ok(&error), count, list, cbd->data); + g_free(list); + } else { + ofono_error("CF query failed"); + cb(ril_error_failure(&error), 0, NULL, cbd->data); + } +} + +static void ril_call_forward_query(struct ofono_call_forwarding *cf, int type, + int cls, ofono_call_forwarding_query_cb_t cb, void *data) +{ + struct ril_call_forward *fd = ril_call_forward_get_data(cf); + GRilIoRequest *req = grilio_request_new(); + + ofono_info("cf query"); + grilio_request_append_int32(req, 2); + grilio_request_append_int32(req, type); + + /* + * Modem seems to respond with error to all queries + * or settings made with bearer class + * BEARER_CLASS_DEFAULT. Design decision: If given + * class is BEARER_CLASS_DEFAULT let's map it to + * SERVICE_CLASS_NONE as per RIL design. + */ + if (cls == BEARER_CLASS_DEFAULT) { + cls = SERVICE_CLASS_NONE; + } + + grilio_request_append_int32(req, cls); + + /* Following 3 values have no real meaning in query + * but apparently RIL expects them so fields need to + * be filled. Otherwise there is no response + */ + grilio_request_append_int32(req, 0x81); /* TOA unknown */ + grilio_request_append_utf8(req, "1234567890"); + grilio_request_append_int32(req, 0); + + ril_call_forward_submit_request(fd, req, + RIL_REQUEST_QUERY_CALL_FORWARD_STATUS, + ril_call_forward_query_cb, cb, data); + grilio_request_unref(req); +} + +static gboolean ril_call_forward_register(gpointer user_data) +{ + struct ofono_call_forwarding *cf = user_data; + struct ril_call_forward *fd = ril_call_forward_get_data(cf); + + fd->timer_id = 0; + ofono_call_forwarding_register(cf); + return FALSE; +} + +static int ril_call_forward_probe(struct ofono_call_forwarding *cf, + unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_call_forward *fd = g_try_new0(struct ril_call_forward, 1); + + DBG(""); + fd->q = grilio_queue_new(ril_modem_io(modem)); + fd->timer_id = g_idle_add(ril_call_forward_register, cf); + ofono_call_forwarding_set_data(cf, fd); + return 0; +} + +static void ril_call_forward_remove(struct ofono_call_forwarding *cf) +{ + struct ril_call_forward *fd = ril_call_forward_get_data(cf); + + DBG(""); + ofono_call_forwarding_set_data(cf, NULL); + + if (fd->timer_id > 0) { + g_source_remove(fd->timer_id); + } + + grilio_queue_cancel_all(fd->q, FALSE); + grilio_queue_unref(fd->q); + g_free(fd); +} + +const struct ofono_call_forwarding_driver ril_call_forwarding_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_call_forward_probe, + .remove = ril_call_forward_remove, + .erasure = ril_call_forward_erasure, + .deactivation = ril_call_forward_deactivate, + .query = ril_call_forward_query, + .registration = ril_call_forward_registration, + .activation = ril_call_forward_activate +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_call_settings.c b/ofono/drivers/ril/ril_call_settings.c new file mode 100644 index 00000000..c0578d4e --- /dev/null +++ b/ofono/drivers/ril/ril_call_settings.c @@ -0,0 +1,309 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "common.h" + +struct ril_call_settings { + GRilIoQueue *q; + guint timer_id; +}; + +struct ril_call_settings_cbd { + union _ofono_call_settings_cb { + ofono_call_settings_status_cb_t status; + ofono_call_settings_set_cb_t set; + ofono_call_settings_clir_cb_t clir; + gpointer ptr; + } cb; + gpointer data; +}; + +#define ril_call_settings_cbd_free g_free + +static inline struct ril_call_settings *ril_call_settings_get_data( + struct ofono_call_settings *b) +{ + return ofono_call_settings_get_data(b); +} + +static struct ril_call_settings_cbd *ril_call_settings_cbd_new(void *cb, + void *data) +{ + struct ril_call_settings_cbd *cbd; + + cbd = g_new0(struct ril_call_settings_cbd, 1); + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static inline void ril_call_settings_submit_req(struct ril_call_settings *sd, + GRilIoRequest* req, guint code, GRilIoChannelResponseFunc response, + void *cb, void *data) +{ + grilio_queue_send_request_full(sd->q, req, code, response, + ril_call_settings_cbd_free, + ril_call_settings_cbd_new(cb, data)); +} + +static void ril_call_settings_clip_query_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_settings_cbd *cbd = user_data; + ofono_call_settings_status_cb_t cb = cbd->cb.status; + + if (status == RIL_E_SUCCESS) { + GRilIoParser rilp; + int res = 0; + + /* data length of the response */ + grilio_parser_init(&rilp, data, len); + if (grilio_parser_get_int32(&rilp, &res) && res > 0) { + grilio_parser_get_int32(&rilp, &res); + } + + cb(ril_error_ok(&error), res, cbd->data); + } else { + cb(ril_error_failure(&error), -1, cbd->data); + } +} + +static void ril_call_settings_set_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_settings_cbd *cbd = user_data; + ofono_call_settings_set_cb_t cb = cbd->cb.set; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_call_settings_cw_set(struct ofono_call_settings *cs, int mode, + int cls, ofono_call_settings_set_cb_t cb, void *data) +{ + struct ril_call_settings *sd = ril_call_settings_get_data(cs); + GRilIoRequest *req = grilio_request_sized_new(12); + + grilio_request_append_int32(req, 2); /* Number of params */ + grilio_request_append_int32(req, mode); /* on/off */ + + /* Modem seems to respond with error to all queries + * or settings made with bearer class + * BEARER_CLASS_DEFAULT. Design decision: If given + * class is BEARER_CLASS_DEFAULT let's map it to + * SERVICE_CLASS_VOICE effectively making it the + * default bearer. This in line with API which is + * contains only voice anyways. + */ + if (cls == BEARER_CLASS_DEFAULT) { + cls = BEARER_CLASS_VOICE; + } + + grilio_request_append_int32(req, cls); /* Service class */ + + ril_call_settings_submit_req(sd, req, RIL_REQUEST_SET_CALL_WAITING, + ril_call_settings_set_cb, cb, data); + grilio_request_unref(req); +} + +static void ril_call_settings_cw_query_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_settings_cbd *cbd = user_data; + ofono_call_settings_status_cb_t cb = cbd->cb.status; + + if (status == RIL_E_SUCCESS) { + GRilIoParser rilp; + int res = 0; + int sv = 0; + + grilio_parser_init(&rilp, data, len); + + /* first value in int[] is len so let's skip that */ + grilio_parser_get_int32(&rilp, NULL); + + /* status of call waiting service, disabled is returned only if + * service is not active for any service class */ + grilio_parser_get_int32(&rilp, &res); + DBG("CW enabled/disabled: %d", res); + + if (res > 0) { + /* services for which call waiting is enabled, + 27.007 7.12 */ + grilio_parser_get_int32(&rilp, &sv); + DBG("CW enabled for: %d", sv); + } + + cb(ril_error_ok(&error), sv, cbd->data); + } else { + cb(ril_error_failure(&error), -1, cbd->data); + } +} + +static void ril_call_settings_cw_query(struct ofono_call_settings *cs, int cls, + ofono_call_settings_status_cb_t cb, void *data) +{ + struct ril_call_settings *sd = ril_call_settings_get_data(cs); + GRilIoRequest *req = grilio_request_sized_new(8); + + grilio_request_append_int32(req, 1); /* Number of params */ + + /* + * RILD expects service class to be 0 as certain carriers can reject + * the query with specific service class + */ + grilio_request_append_int32(req, 0); + + ril_call_settings_submit_req(sd, req, RIL_REQUEST_QUERY_CALL_WAITING, + ril_call_settings_cw_query_cb, cb, data); + grilio_request_unref(req); +} + +static void ril_call_settings_clip_query(struct ofono_call_settings *cs, + ofono_call_settings_status_cb_t cb, void *data) +{ + struct ril_call_settings *sd = ril_call_settings_get_data(cs); + + ril_call_settings_submit_req(sd, NULL, RIL_REQUEST_QUERY_CLIP, + ril_call_settings_clip_query_cb, cb, data); +} + +static void ril_call_settings_clir_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_settings_cbd *cbd = user_data; + ofono_call_settings_clir_cb_t cb = cbd->cb.clir; + + if (status == RIL_E_SUCCESS) { + GRilIoParser rilp; + int override = -1, network = -1; + + grilio_parser_init(&rilp, data, len); + /*first value in int[] is len so let's skip that*/ + grilio_parser_get_int32(&rilp, NULL); + /* Set HideCallerId property from network */ + grilio_parser_get_int32(&rilp, &override); + /* CallingLineRestriction indicates the state of + the CLIR supplementary service in the network */ + grilio_parser_get_int32(&rilp, &network); + + cb(ril_error_ok(&error), override, network, cbd->data); + } else { + cb(ril_error_failure(&error), -1, -1, cbd->data); + } +} + +static void ril_call_settings_clir_query(struct ofono_call_settings *cs, + ofono_call_settings_clir_cb_t cb, void *data) +{ + struct ril_call_settings *sd = ril_call_settings_get_data(cs); + + ril_call_settings_submit_req(sd, NULL, RIL_REQUEST_GET_CLIR, + ril_call_settings_clir_cb, cb, data); +} + +static void ril_call_settings_clir_set(struct ofono_call_settings *cs, + int mode, ofono_call_settings_set_cb_t cb, void *data) +{ + struct ril_call_settings *sd = ril_call_settings_get_data(cs); + GRilIoRequest *req = grilio_request_sized_new(8); + + grilio_request_append_int32(req, 1); /* Number of params */ + grilio_request_append_int32(req, mode); /* for outgoing calls */ + + ril_call_settings_submit_req(sd, req, RIL_REQUEST_SET_CLIR, + ril_call_settings_set_cb, cb, data); + grilio_request_unref(req); +} + +static gboolean ril_call_settings_register(gpointer user_data) +{ + struct ofono_call_settings *cs = user_data; + struct ril_call_settings *sd = ril_call_settings_get_data(cs); + + DBG(""); + GASSERT(sd->timer_id); + sd->timer_id = 0; + ofono_call_settings_register(cs); + + /* Single-shot */ + return FALSE; +} + +static int ril_call_settings_probe(struct ofono_call_settings *cs, + unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_call_settings *sd = g_try_new0(struct ril_call_settings, 1); + + DBG(""); + sd->q = grilio_queue_new(ril_modem_io(modem)); + sd->timer_id = g_idle_add(ril_call_settings_register, cs); + ofono_call_settings_set_data(cs, sd); + return 0; +} + +static void ril_call_settings_remove(struct ofono_call_settings *cs) +{ + struct ril_call_settings *sd = ril_call_settings_get_data(cs); + + DBG(""); + ofono_call_settings_set_data(cs, NULL); + + if (sd->timer_id > 0) { + g_source_remove(sd->timer_id); + } + + grilio_queue_cancel_all(sd->q, FALSE); + grilio_queue_unref(sd->q); + g_free(sd); +} + +const struct ofono_call_settings_driver ril_call_settings_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_call_settings_probe, + .remove = ril_call_settings_remove, + .clip_query = ril_call_settings_clip_query, + .cw_query = ril_call_settings_cw_query, + .cw_set = ril_call_settings_cw_set, + .clir_query = ril_call_settings_clir_query, + .clir_set = ril_call_settings_clir_set + + /* + * Not supported in RIL API + * .colp_query = ril_call_settings_colp_query, + * .colr_query = ril_call_settings_colr_query + */ +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_call_volume.c b/ofono/drivers/ril/ril_call_volume.c new file mode 100644 index 00000000..1fd4bc23 --- /dev/null +++ b/ofono/drivers/ril/ril_call_volume.c @@ -0,0 +1,151 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +struct ril_call_volume { + struct ofono_call_volume *v; + GRilIoQueue *q; + guint timer_id; +}; + +struct ril_call_volume_req { + ofono_call_volume_cb_t cb; + gpointer data; +}; + +static inline struct ril_call_volume *ril_call_volume_get_data( + struct ofono_call_volume *v) +{ + return ofono_call_volume_get_data(v); +} + +static void ril_call_volume_mute_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_call_volume_req *cbd = user_data; + ofono_call_volume_cb_t cb = cbd->cb; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("Could not set the ril mute state"); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_call_volume_mute(struct ofono_call_volume *v, int muted, + ofono_call_volume_cb_t cb, void *data) +{ + struct ril_call_volume *vd = ril_call_volume_get_data(v); + struct ril_call_volume_req *cbd; + GRilIoRequest *req = grilio_request_sized_new(8); + + cbd = g_new(struct ril_call_volume_req, 1); + cbd->cb = cb; + cbd->data = data; + + DBG("%d", muted); + grilio_request_append_int32(req, 1); + grilio_request_append_int32(req, muted); + grilio_queue_send_request_full(vd->q, req, RIL_REQUEST_SET_MUTE, + ril_call_volume_mute_cb, g_free, cbd); + grilio_request_unref(req); +} + +static void ril_call_volume_query_mute_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_call_volume *vd = user_data; + + if (status == RIL_E_SUCCESS) { + int muted = 0; + GRilIoParser rilp; + + grilio_parser_init(&rilp, data, len); + grilio_parser_get_int32(&rilp, NULL); /* Array length */ + grilio_parser_get_int32(&rilp, &muted); + DBG("{%d}", muted); + ofono_call_volume_set_muted(vd->v, muted); + } else { + ofono_error("Could not retrive the ril mute state"); + } +} + +static gboolean ril_call_volume_register(gpointer user_data) +{ + struct ril_call_volume *vd = user_data; + + DBG(""); + GASSERT(vd->timer_id); + vd->timer_id = 0; + ofono_call_volume_register(vd->v); + + /* Probe the mute state */ + grilio_queue_send_request_full(vd->q, NULL, + RIL_REQUEST_GET_MUTE, ril_call_volume_query_mute_cb, NULL, vd); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +static int ril_call_volume_probe(struct ofono_call_volume *v, + unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_call_volume *vd = g_new0(struct ril_call_volume, 1); + + DBG(""); + vd->v = v; + vd->q = grilio_queue_new(ril_modem_io(modem)); + vd->timer_id = g_idle_add(ril_call_volume_register, vd); + ofono_call_volume_set_data(v, vd); + return 0; +} + +static void ril_call_volume_remove(struct ofono_call_volume *v) +{ + struct ril_call_volume *vd = ril_call_volume_get_data(v); + + DBG(""); + ofono_call_volume_set_data(v, NULL); + + if (vd->timer_id) { + g_source_remove(vd->timer_id); + } + + grilio_queue_cancel_all(vd->q, FALSE); + grilio_queue_unref(vd->q); + g_free(vd); +} + +const struct ofono_call_volume_driver ril_call_volume_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_call_volume_probe, + .remove = ril_call_volume_remove, + .mute = ril_call_volume_mute, +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_cbs.c b/ofono/drivers/ril/ril_cbs.c new file mode 100644 index 00000000..45c5837b --- /dev/null +++ b/ofono/drivers/ril/ril_cbs.c @@ -0,0 +1,119 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +struct ril_cbs { + struct ofono_cbs *cbs; + GRilIoChannel *io; + guint timer_id; + gulong event_id; +}; + +static void ril_set_topics(struct ofono_cbs *cbs, const char *topics, + ofono_cbs_set_cb_t cb, void *data) +{ + struct ofono_error error; + cb(ril_error_ok(&error), data); +} + +static void ril_clear_topics(struct ofono_cbs *cbs, + ofono_cbs_set_cb_t cb, void *data) +{ + struct ofono_error error; + cb(ril_error_ok(&error), data); +} + +static void ril_cbs_notify(GRilIoChannel *io, guint code, + const void *data, guint len, void *user_data) +{ + struct ril_cbs *cd = user_data; + GRilIoParser rilp; + char* pdu; + + GASSERT(code == RIL_UNSOL_ON_USSD); + grilio_parser_init(&rilp, data, len); + pdu = grilio_parser_get_utf8(&rilp); + DBG("%s", pdu); + if (pdu) { + ofono_cbs_notify(cd->cbs, (const guchar *)pdu, strlen(pdu)); + g_free(pdu); + } +} + +static gboolean ril_cbs_register(gpointer user_data) +{ + struct ril_cbs *cd = user_data; + + DBG(""); + GASSERT(cd->timer_id); + cd->timer_id = 0; + ofono_cbs_register(cd->cbs); + + cd->event_id = grilio_channel_add_unsol_event_handler(cd->io, + ril_cbs_notify, RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS, cd); + + /* Single-shot */ + return FALSE; +} + +static int ril_cbs_probe(struct ofono_cbs *cbs, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_cbs *cd = g_try_new0(struct ril_cbs, 1); + + DBG(""); + cd->cbs = cbs; + cd->io = grilio_channel_ref(ril_modem_io(modem)); + cd->timer_id = g_idle_add(ril_cbs_register, cd); + ofono_cbs_set_data(cbs, cd); + return 0; +} + +static void ril_cbs_remove(struct ofono_cbs *cbs) +{ + struct ril_cbs *cd = ofono_cbs_get_data(cbs); + + DBG(""); + ofono_cbs_set_data(cbs, NULL); + + if (cd->timer_id > 0) { + g_source_remove(cd->timer_id); + } + + grilio_channel_remove_handler(cd->io, cd->event_id); + grilio_channel_unref(cd->io); + g_free(cd); +} + +const struct ofono_cbs_driver ril_cbs_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_cbs_probe, + .remove = ril_cbs_remove, + .set_topics = ril_set_topics, + .clear_topics = ril_clear_topics +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_constants.h b/ofono/drivers/ril/ril_constants.h new file mode 100644 index 00000000..dfe744b3 --- /dev/null +++ b/ofono/drivers/ril/ril_constants.h @@ -0,0 +1,408 @@ +/* + * + * RIL constants adopted from AOSP's header: + * + * /hardware/ril/reference_ril/ril.h + * + * Copyright (C) 2013 Canonical Ltd. + * Copyright (C) 2013-2015 Jolla Ltd. + * + * 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. + */ + +#ifndef __RIL_CONSTANTS_H +#define __RIL_CONSTANTS_H 1 + +/* Error Codes */ +#define RIL_E_SUCCESS 0 +#define RIL_E_RADIO_NOT_AVAILABLE 1 +#define RIL_E_GENERIC_FAILURE 2 +#define RIL_E_PASSWORD_INCORRECT 3 +#define RIL_E_SIM_PIN2 4 +#define RIL_E_SIM_PUK2 5 +#define RIL_E_REQUEST_NOT_SUPPORTED 6 +#define RIL_E_CANCELLED 7 +#define RIL_E_OP_NOT_ALLOWED_DURING_VOICE_CALL 8 +#define RIL_E_OP_NOT_ALLOWED_BEFORE_REG_TO_NW 9 +#define RIL_E_SMS_SEND_FAIL_RETRY 10 +#define RIL_E_SIM_ABSENT 11 +#define RIL_E_SUBSCRIPTION_NOT_AVAILABLE 12 +#define RIL_E_MODE_NOT_SUPPORTED 13 +#define RIL_E_FDN_CHECK_FAILURE 14 +#define RIL_E_ILLEGAL_SIM_OR_ME 15 +#define RIL_E_UNUSED 16 +#define RIL_E_DIAL_MODIFIED_TO_USSD 17 +#define RIL_E_DIAL_MODIFIED_TO_SS 18 +#define RIL_E_DIAL_MODIFIED_TO_DIAL 19 +#define RIL_E_USSD_MODIFIED_TO_DIAL 20 +#define RIL_E_USSD_MODIFIED_TO_SS 21 +#define RIL_E_USSD_MODIFIED_TO_USSD 22 +#define RIL_E_SS_MODIFIED_TO_DIAL 23 +#define RIL_E_SS_MODIFIED_TO_USSD 24 +#define RIL_E_SS_MODIFIED_TO_SS 25 +#define RIL_E_SUBSCRIPTION_NOT_SUPPORTED 26 +#define RIL_E_MISSING_RESOURCE 27 +#define RIL_E_NO_SUCH_ELEMENT 28 +#define RIL_E_INVALID_PARAMETER 29 + +/* call states */ +enum ril_call_state { + RIL_CALL_ACTIVE, + RIL_CALL_HOLDING, + RIL_CALL_DIALING, + RIL_CALL_ALERTING, + RIL_CALL_INCOMING, + RIL_CALL_WAITING +}; + +/* Radio state */ +enum ril_radio_state { + RADIO_STATE_OFF, + RADIO_STATE_UNAVAILABLE, + RADIO_STATE_SIM_NOT_READY, + RADIO_STATE_SIM_LOCKED_OR_ABSENT, + RADIO_STATE_SIM_READY, + RADIO_STATE_RUIM_NOT_READY, + RADIO_STATE_RUIM_READY, + RADIO_STATE_RUIM_LOCKED_OR_ABSENT, + RADIO_STATE_NV_NOT_READY, + RADIO_STATE_NV_READY, + RADIO_STATE_ON +}; + +/* Preferred network types */ +enum ril_pref_net_type { + PREF_NET_TYPE_GSM_WCDMA, + PREF_NET_TYPE_GSM_ONLY, + PREF_NET_TYPE_WCDMA, + PREF_NET_TYPE_GSM_WCDMA_AUTO, + PREF_NET_TYPE_CDMA_EVDO_AUTO, + PREF_NET_TYPE_CDMA_ONLY, + PREF_NET_TYPE_EVDO_ONLY, + PREF_NET_TYPE_GSM_WCDMA_CDMA_EVDO_AUTO, + PREF_NET_TYPE_LTE_CDMA_EVDO, + PREF_NET_TYPE_LTE_GSM_WCDMA, + PREF_NET_TYPE_LTE_CMDA_EVDO_GSM_WCDMA, + PREF_NET_TYPE_LTE_ONLY, + PREF_NET_TYPE_LTE_WCDMA +}; + +/* Radio technologies */ +enum ril_radio_tech { + RADIO_TECH_UNKNOWN, + RADIO_TECH_GPRS, + RADIO_TECH_EDGE, + RADIO_TECH_UMTS, + RADIO_TECH_IS95A, + RADIO_TECH_IS95B, + RADIO_TECH_1xRTT , + RADIO_TECH_EVDO_0, + RADIO_TECH_EVDO_A, + RADIO_TECH_HSDPA, + RADIO_TECH_HSUPA , + RADIO_TECH_HSPA, + RADIO_TECH_EVDO_B, + RADIO_TECH_EHRPD, + RADIO_TECH_LTE, + RADIO_TECH_HSPAP, + RADIO_TECH_GSM, + RADIO_TECH_TD_SCDMA, + RADIO_TECH_DC_HSDPA +}; + +/* See RIL_REQUEST_LAST_CALL_FAIL_CAUSE */ +#define CALL_FAIL_UNOBTAINABLE_NUMBER 1 +#define CALL_FAIL_NORMAL 16 +#define CALL_FAIL_BUSY 17 +#define CALL_FAIL_CONGESTION 34 +#define CALL_FAIL_ACM_LIMIT_EXCEEDED 68 +#define CALL_FAIL_CALL_BARRED 240 +#define CALL_FAIL_FDN_BLOCKED 241 +#define CALL_FAIL_IMSI_UNKNOWN_IN_VLR 242 +#define CALL_FAIL_IMEI_NOT_ACCEPTED 243 +#define CALL_FAIL_DIAL_MODIFIED_TO_USSD 244 +#define CALL_FAIL_DIAL_MODIFIED_TO_SS 245 +#define CALL_FAIL_DIAL_MODIFIED_TO_DIAL 246 +#define CALL_FAIL_CDMA_LOCKED_UNTIL_POWER_CYCLE 1000 +#define CALL_FAIL_CDMA_DROP 1001 +#define CALL_FAIL_CDMA_INTERCEPT 1002 +#define CALL_FAIL_CDMA_REORDER 1003 +#define CALL_FAIL_CDMA_SO_REJECT 1004 +#define CALL_FAIL_CDMA_RETRY_ORDER 1005 +#define CALL_FAIL_CDMA_ACCESS_FAILURE 1006 +#define CALL_FAIL_CDMA_PREEMPTED 1007 +#define CALL_FAIL_CDMA_NOT_EMERGENCY 1008 +#define CALL_FAIL_CDMA_ACCESS_BLOCKED 1009 +#define CALL_FAIL_ERROR_UNSPECIFIED 0xffff + +/* see RIL_REQUEST_DEACTIVATE_DATA_CALL parameter*/ +#define RIL_DEACTIVATE_DATA_CALL_NO_REASON 0 +#define RIL_DEACTIVATE_DATA_CALL_RADIO_SHUTDOWN 1 + +/* See RIL_REQUEST_SETUP_DATA_CALL */ + +#define RIL_DATA_PROFILE_DEFAULT 0 +#define RIL_DATA_PROFILE_TETHERED 1 +#define RIL_DATA_PROFILE_IMS 2 +#define RIL_DATA_PROFILE_FOTA 3 /* FOTA = Firmware Over the Air */ +#define RIL_DATA_PROFILE_CBS 4 +#define RIL_DATA_PROFILE_OEM_BASE 1000 /* Start of OEM-specific profiles */ + +#define RIL_AUTH_NONE 0 +#define RIL_AUTH_PAP 1 +#define RIL_AUTH_CHAP 2 +#define RIL_AUTH_BOTH 3 + +/* SIM card states */ +#define RIL_CARDSTATE_ABSENT 0 +#define RIL_CARDSTATE_PRESENT 1 +#define RIL_CARDSTATE_ERROR 2 + +/* SIM personalization substates */ +#define RIL_PERSOSUBSTATE_UNKNOWN 0 +#define RIL_PERSOSUBSTATE_IN_PROGRESS 1 +#define RIL_PERSOSUBSTATE_READY 2 +#define RIL_PERSOSUBSTATE_SIM_NETWORK 3 +#define RIL_PERSOSUBSTATE_SIM_NETWORK_SUBSET 4 +#define RIL_PERSOSUBSTATE_SIM_CORPORATE 5 +#define RIL_PERSOSUBSTATE_SIM_SERVICE_PROVIDER 6 +#define RIL_PERSOSUBSTATE_SIM_SIM 7 +#define RIL_PERSOSUBSTATE_SIM_NETWORK_PUK 8 +#define RIL_PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK 9 +#define RIL_PERSOSUBSTATE_SIM_CORPORATE_PUK 10 +#define RIL_PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK 11 +#define RIL_PERSOSUBSTATE_SIM_SIM_PUK 12 +#define RIL_PERSOSUBSTATE_RUIM_NETWORK1 13 +#define RIL_PERSOSUBSTATE_RUIM_NETWORK2 14 +#define RIL_PERSOSUBSTATE_RUIM_HRPD 15 +#define RIL_PERSOSUBSTATE_RUIM_CORPORATE 16 +#define RIL_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER 17 +#define RIL_PERSOSUBSTATE_RUIM_RUIM 18 +#define RIL_PERSOSUBSTATE_RUIM_NETWORK1_PUK 19 +#define RIL_PERSOSUBSTATE_RUIM_NETWORK2_PUK 20 +#define RIL_PERSOSUBSTATE_RUIM_HRPD_PUK 21 +#define RIL_PERSOSUBSTATE_RUIM_CORPORATE_PUK 22 +#define RIL_PERSOSUBSTATE_RUIM_SERVICE_PROVIDER_PUK 23 +#define RIL_PERSOSUBSTATE_RUIM_RUIM_PUK 24 + +/* SIM - App states */ +#define RIL_APPSTATE_ILLEGAL -1 +#define RIL_APPSTATE_UNKNOWN 0 +#define RIL_APPSTATE_DETECTED 1 +#define RIL_APPSTATE_PIN 2 +#define RIL_APPSTATE_PUK 3 +#define RIL_APPSTATE_SUBSCRIPTION_PERSO 4 +#define RIL_APPSTATE_READY 5 + +/* SIM - PIN states */ +#define RIL_PINSTATE_UNKNOWN 0 +#define RIL_PINSTATE_ENABLED_NOT_VERIFIED 1 +#define RIL_PINSTATE_ENABLED_VERIFIED 2 +#define RIL_PINSTATE_DISABLED 3 +#define RIL_PINSTATE_ENABLED_BLOCKED 4 +#define RIL_PINSTATE_ENABLED_PERM_BLOCKED 5 + +/* SIM - App types */ +#define RIL_APPTYPE_UNKNOWN 0 +#define RIL_APPTYPE_SIM 1 +#define RIL_APPTYPE_USIM 2 +#define RIL_APPTYPE_RUIM 3 +#define RIL_APPTYPE_CSIM 4 +#define RIL_APPTYPE_ISIM 5 + +/* RIL Request Messages */ +#define RIL_REQUEST_GET_SIM_STATUS 1 +#define RIL_REQUEST_ENTER_SIM_PIN 2 +#define RIL_REQUEST_ENTER_SIM_PUK 3 +#define RIL_REQUEST_ENTER_SIM_PIN2 4 +#define RIL_REQUEST_ENTER_SIM_PUK2 5 +#define RIL_REQUEST_CHANGE_SIM_PIN 6 +#define RIL_REQUEST_CHANGE_SIM_PIN2 7 +#define RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION 8 +#define RIL_REQUEST_GET_CURRENT_CALLS 9 +#define RIL_REQUEST_DIAL 10 +#define RIL_REQUEST_GET_IMSI 11 +#define RIL_REQUEST_HANGUP 12 +#define RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND 13 +#define RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND 14 +#define RIL_REQUEST_SWITCH_HOLDING_AND_ACTIVE 15 +#define RIL_REQUEST_CONFERENCE 16 +#define RIL_REQUEST_UDUB 17 +#define RIL_REQUEST_LAST_CALL_FAIL_CAUSE 18 +#define RIL_REQUEST_SIGNAL_STRENGTH 19 +#define RIL_REQUEST_VOICE_REGISTRATION_STATE 20 +#define RIL_REQUEST_DATA_REGISTRATION_STATE 21 +#define RIL_REQUEST_OPERATOR 22 +#define RIL_REQUEST_RADIO_POWER 23 +#define RIL_REQUEST_DTMF 24 +#define RIL_REQUEST_SEND_SMS 25 +#define RIL_REQUEST_SEND_SMS_EXPECT_MORE 26 +#define RIL_REQUEST_SETUP_DATA_CALL 27 +#define RIL_REQUEST_SIM_IO 28 +#define RIL_REQUEST_SEND_USSD 29 +#define RIL_REQUEST_CANCEL_USSD 30 +#define RIL_REQUEST_GET_CLIR 31 +#define RIL_REQUEST_SET_CLIR 32 +#define RIL_REQUEST_QUERY_CALL_FORWARD_STATUS 33 +#define RIL_REQUEST_SET_CALL_FORWARD 34 +#define RIL_REQUEST_QUERY_CALL_WAITING 35 +#define RIL_REQUEST_SET_CALL_WAITING 36 +#define RIL_REQUEST_SMS_ACKNOWLEDGE 37 +#define RIL_REQUEST_GET_IMEI 38 +#define RIL_REQUEST_GET_IMEISV 39 +#define RIL_REQUEST_ANSWER 40 +#define RIL_REQUEST_DEACTIVATE_DATA_CALL 41 +#define RIL_REQUEST_QUERY_FACILITY_LOCK 42 +#define RIL_REQUEST_SET_FACILITY_LOCK 43 +#define RIL_REQUEST_CHANGE_BARRING_PASSWORD 44 +#define RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE 45 +#define RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC 46 +#define RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL 47 +#define RIL_REQUEST_QUERY_AVAILABLE_NETWORKS 48 +#define RIL_REQUEST_DTMF_START 49 +#define RIL_REQUEST_DTMF_STOP 50 +#define RIL_REQUEST_BASEBAND_VERSION 51 +#define RIL_REQUEST_SEPARATE_CONNECTION 52 +#define RIL_REQUEST_SET_MUTE 53 +#define RIL_REQUEST_GET_MUTE 54 +#define RIL_REQUEST_QUERY_CLIP 55 +#define RIL_REQUEST_LAST_DATA_CALL_FAIL_CAUSE 56 +#define RIL_REQUEST_DATA_CALL_LIST 57 +#define RIL_REQUEST_RESET_RADIO 58 +#define RIL_REQUEST_OEM_HOOK_RAW 59 +#define RIL_REQUEST_OEM_HOOK_STRINGS 60 +#define RIL_REQUEST_SCREEN_STATE 61 +#define RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION 62 +#define RIL_REQUEST_WRITE_SMS_TO_SIM 63 +#define RIL_REQUEST_DELETE_SMS_ON_SIM 64 +#define RIL_REQUEST_SET_BAND_MODE 65 +#define RIL_REQUEST_QUERY_AVAILABLE_BAND_MODE 66 +#define RIL_REQUEST_STK_GET_PROFILE 67 +#define RIL_REQUEST_STK_SET_PROFILE 68 +#define RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND 69 +#define RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE 70 +#define RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM 71 +#define RIL_REQUEST_EXPLICIT_CALL_TRANSFER 72 +#define RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE 73 +#define RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE 74 +#define RIL_REQUEST_GET_NEIGHBORING_CELL_IDS 75 +#define RIL_REQUEST_SET_LOCATION_UPDATES 76 +#define RIL_REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE 77 +#define RIL_REQUEST_CDMA_SET_ROAMING_PREFERENCE 78 +#define RIL_REQUEST_CDMA_QUERY_ROAMING_PREFERENCE 79 +#define RIL_REQUEST_SET_TTY_MODE 80 +#define RIL_REQUEST_QUERY_TTY_MODE 81 +#define RIL_REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE 82 +#define RIL_REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE 83 +#define RIL_REQUEST_CDMA_FLASH 84 +#define RIL_REQUEST_CDMA_BURST_DTMF 85 +#define RIL_REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY 86 +#define RIL_REQUEST_CDMA_SEND_SMS 87 +#define RIL_REQUEST_CDMA_SMS_ACKNOWLEDGE 88 +#define RIL_REQUEST_GSM_GET_BROADCAST_SMS_CONFIG 89 +#define RIL_REQUEST_GSM_SET_BROADCAST_SMS_CONFIG 90 +#define RIL_REQUEST_GSM_SMS_BROADCAST_ACTIVATION 91 +#define RIL_REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG 92 +#define RIL_REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG 93 +#define RIL_REQUEST_CDMA_SMS_BROADCAST_ACTIVATION 94 +#define RIL_REQUEST_CDMA_SUBSCRIPTION 95 +#define RIL_REQUEST_CDMA_WRITE_SMS_TO_RUIM 96 +#define RIL_REQUEST_CDMA_DELETE_SMS_ON_RUIM 97 +#define RIL_REQUEST_DEVICE_IDENTITY 98 +#define RIL_REQUEST_EXIT_EMERGENCY_CALLBACK_MODE 99 +#define RIL_REQUEST_GET_SMSC_ADDRESS 100 +#define RIL_REQUEST_SET_SMSC_ADDRESS 101 +#define RIL_REQUEST_REPORT_SMS_MEMORY_STATUS 102 +#define RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING 103 +#define RIL_REQUEST_CDMA_GET_SUBSCRIPTION_SOURCE 104 +#define RIL_REQUEST_ISIM_AUTHENTICATION 105 +#define RIL_REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU 106 +#define RIL_REQUEST_STK_SEND_ENVELOPE_WITH_STATUS 107 +#define RIL_REQUEST_VOICE_RADIO_TECH 108 +#define RIL_REQUEST_GET_CELL_INFO_LIST 109 +#define RIL_REQUEST_SET_UNSOL_CELL_INFO_LIST_RATE 110 +#define RIL_REQUEST_SET_INITIAL_ATTACH_APN 111 +#define RIL_REQUEST_IMS_REGISTRATION_STATE 112 +#define RIL_REQUEST_IMS_SEND_SMS 113 +#define RIL_REQUEST_SIM_TRANSMIT_APDU_BASIC 114 +#define RIL_REQUEST_SIM_OPEN_CHANNEL 115 +#define RIL_REQUEST_SIM_CLOSE_CHANNEL 116 +#define RIL_REQUEST_SIM_TRANSMIT_APDU_CHANNEL 117 +#define RIL_REQUEST_NV_READ_ITEM 118 +#define RIL_REQUEST_NV_WRITE_ITEM 119 +#define RIL_REQUEST_NV_WRITE_CDMA_PRL 120 +#define RIL_REQUEST_NV_RESET_CONFIG 121 +#define RIL_REQUEST_SET_UICC_SUBSCRIPTION 122 +#define RIL_REQUEST_ALLOW_DATA 123 +#define RIL_REQUEST_GET_HARDWARE_CONFIG 124 +#define RIL_REQUEST_SIM_AUTHENTICATION 125 +#define RIL_REQUEST_GET_DC_RT_INFO 126 +#define RIL_REQUEST_SET_DC_RT_INFO_RATE 127 +#define RIL_REQUEST_SET_DATA_PROFILE 128 +#define RIL_REQUEST_SHUTDOWN 129 +#define RIL_REQUEST_GET_RADIO_CAPABILITY 130 +#define RIL_REQUEST_SET_RADIO_CAPABILITY 131 + +/* RIL Unsolicited Messages */ +#define RIL_UNSOL_RESPONSE_BASE 1000 +#define RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED 1000 +#define RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED 1001 +#define RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED 1002 +#define RIL_UNSOL_RESPONSE_NEW_SMS 1003 +#define RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT 1004 +#define RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM 1005 +#define RIL_UNSOL_ON_USSD 1006 +#define RIL_UNSOL_ON_USSD_REQUEST 1007 +#define RIL_UNSOL_NITZ_TIME_RECEIVED 1008 +#define RIL_UNSOL_SIGNAL_STRENGTH 1009 +#define RIL_UNSOL_DATA_CALL_LIST_CHANGED 1010 +#define RIL_UNSOL_SUPP_SVC_NOTIFICATION 1011 +#define RIL_UNSOL_STK_SESSION_END 1012 +#define RIL_UNSOL_STK_PROACTIVE_COMMAND 1013 +#define RIL_UNSOL_STK_EVENT_NOTIFY 1014 +#define RIL_UNSOL_STK_CALL_SETUP 1015 +#define RIL_UNSOL_SIM_SMS_STORAGE_FULL 1016 +#define RIL_UNSOL_SIM_REFRESH 1017 +#define RIL_UNSOL_CALL_RING 1018 +#define RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED 1019 +#define RIL_UNSOL_RESPONSE_CDMA_NEW_SMS 1020 +#define RIL_UNSOL_RESPONSE_NEW_BROADCAST_SMS 1021 +#define RIL_UNSOL_CDMA_RUIM_SMS_STORAGE_FULL 1022 +#define RIL_UNSOL_RESTRICTED_STATE_CHANGED 1023 +#define RIL_UNSOL_ENTER_EMERGENCY_CALLBACK_MODE 1024 +#define RIL_UNSOL_CDMA_CALL_WAITING 1025 +#define RIL_UNSOL_CDMA_OTA_PROVISION_STATUS 1026 +#define RIL_UNSOL_CDMA_INFO_REC 1027 +#define RIL_UNSOL_OEM_HOOK_RAW 1028 +#define RIL_UNSOL_RINGBACK_TONE 1029 +#define RIL_UNSOL_RESEND_INCALL_MUTE 1030 +#define RIL_UNSOL_CDMA_SUBSCRIPTION_SOURCE_CHANGED 1031 +#define RIL_UNSOL_CDMA_PRL_CHANGED 1032 +#define RIL_UNSOL_EXIT_EMERGENCY_CALLBACK_MODE 1033 +#define RIL_UNSOL_RIL_CONNECTED 1034 +#define RIL_UNSOL_VOICE_RADIO_TECH_CHANGED 1035 +#define RIL_UNSOL_CELL_INFO_LIST 1036 +#define RIL_UNSOL_RESPONSE_IMS_NETWORK_STATE_CHANGED 1037 +#define RIL_UNSOL_UICC_SUBSCRIPTION_STATUS_CHANGED 1038 +#define RIL_UNSOL_SRVCC_STATE_NOTIFY 1039 +#define RIL_UNSOL_HARDWARE_CONFIG_CHANGED 1040 +#define RIL_UNSOL_DC_RT_INFO_CHANGED 1041 +#define RIL_UNSOL_RADIO_CAPABILITY 1042 +#define RIL_UNSOL_ON_SS 1043 +#define RIL_UNSOL_STK_CC_ALPHA_NOTIFY 1044 + +/* Suplementary services Service class*/ +#define SERVICE_CLASS_NONE 0 + +/* RIL_FACILITY_LOCK parameters */ +#define RIL_FACILITY_UNLOCK "0" +#define RIL_FACILITY_LOCK "1" + +#endif /*__RIL_CONSTANTS_H */ diff --git a/ofono/drivers/ril/ril_devinfo.c b/ofono/drivers/ril/ril_devinfo.c new file mode 100644 index 00000000..cbe014fd --- /dev/null +++ b/ofono/drivers/ril/ril_devinfo.c @@ -0,0 +1,173 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_constants.h" +#include "ril_util.h" +#include "ril_log.h" + +/* + * TODO: No public RIL api to query manufacturer or model. + * Check where to get, could /system/build.prop be updated to have good values? + */ + +struct ril_devinfo { + struct ofono_devinfo *info; + GRilIoQueue *q; + guint timer_id; +}; + +struct ril_devinfo_req { + ofono_devinfo_query_cb_t cb; + gpointer data; +}; + +#define ril_devinfo_req_free g_free + +static inline struct ril_devinfo *ril_devinfo_get_data( + struct ofono_devinfo *info) +{ + return ofono_devinfo_get_data(info); +} + +struct ril_devinfo_req *ril_devinfo_req_new(ofono_devinfo_query_cb_t cb, + void *data) +{ + struct ril_devinfo_req *cbd = g_new0(struct ril_devinfo_req, 1); + + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static void ril_devinfo_query_unsupported(struct ofono_devinfo *info, + ofono_devinfo_query_cb_t cb, void *data) +{ + struct ofono_error error; + cb(ril_error_failure(&error), "", data); +} + +static void ril_devinfo_query_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_devinfo_req *cbd = user_data; + + if (status == RIL_E_SUCCESS) { + gchar *res; + GRilIoParser rilp; + grilio_parser_init(&rilp, data, len); + res = grilio_parser_get_utf8(&rilp); + DBG("%s", res); + cbd->cb(ril_error_ok(&error), res ? res : "", cbd->data); + g_free(res); + } else { + cbd->cb(ril_error_failure(&error), NULL, cbd->data); + } +} + +static void ril_devinfo_query(struct ofono_devinfo *info, guint cmd, + ofono_devinfo_query_cb_t cb, void *data) +{ + struct ril_devinfo *di = ril_devinfo_get_data(info); + + /* See comment in ril_devinfo_remove */ + if (di->q) { + grilio_queue_send_request_full(di->q, NULL, cmd, + ril_devinfo_query_cb, ril_devinfo_req_free, + ril_devinfo_req_new(cb, data)); + } else { + struct ofono_error error; + cb(ril_error_failure(&error), NULL, data); + } +} + +static void ril_devinfo_query_revision(struct ofono_devinfo *info, + ofono_devinfo_query_cb_t cb, void *data) +{ + DBG(""); + ril_devinfo_query(info, RIL_REQUEST_BASEBAND_VERSION, cb, data); +} + +static void ril_devinfo_query_serial(struct ofono_devinfo *info, + ofono_devinfo_query_cb_t cb, + void *data) +{ + /* TODO: make it support both RIL_REQUEST_GET_IMEI (deprecated) and + * RIL_REQUEST_DEVICE_IDENTITY depending on the rild version used */ + DBG(""); + ril_devinfo_query(info, RIL_REQUEST_GET_IMEI, cb, data); +} + +static gboolean ril_devinfo_register(gpointer user_data) +{ + struct ril_devinfo *di = user_data; + + DBG(""); + di->timer_id = 0; + ofono_devinfo_register(di->info); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +static int ril_devinfo_probe(struct ofono_devinfo *info, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_devinfo *di = g_new0(struct ril_devinfo, 1); + + DBG(""); + di->q = grilio_queue_new(ril_modem_io(modem)); + di->info = info; + + di->timer_id = g_idle_add(ril_devinfo_register, di); + ofono_devinfo_set_data(info, di); + return 0; +} + +static void ril_devinfo_remove(struct ofono_devinfo *info) +{ + struct ril_devinfo *di = ril_devinfo_get_data(info); + + DBG(""); + ofono_devinfo_set_data(info, NULL); + + if (di->timer_id > 0) { + g_source_remove(di->timer_id); + } + + grilio_queue_cancel_all(di->q, FALSE); + grilio_queue_unref(di->q); + g_free(di); +} + +const struct ofono_devinfo_driver ril_devinfo_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_devinfo_probe, + .remove = ril_devinfo_remove, + .query_manufacturer = ril_devinfo_query_unsupported, + .query_model = ril_devinfo_query_unsupported, + .query_revision = ril_devinfo_query_revision, + .query_serial = ril_devinfo_query_serial +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_gprs.c b/ofono/drivers/ril/ril_gprs.c new file mode 100644 index 00000000..0fbd8e74 --- /dev/null +++ b/ofono/drivers/ril/ril_gprs.c @@ -0,0 +1,389 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_constants.h" +#include "ril_util.h" +#include "ril_log.h" + +#include "common.h" + +/* + * This module is the ofono_gprs_driver implementation for rilmodem. + * + * Notes: + * + * 1. ofono_gprs_suspend/resume() are not used by this module, as + * the concept of suspended GPRS is not exposed by RILD. + * + * 2. ofono_gprs_bearer_notify() is never called as RILD does not + * expose an unsolicited event equivalent to +CPSB ( see 27.007 + * 7.29 ), and the tech values returned by REQUEST_DATA/VOICE + * _REGISTRATION requests do not match the values defined for + * in the +CPSB definition. Note, the values returned by + * the *REGISTRATION commands are aligned with those defined by + * +CREG ( see 27.003 7.2 ). + */ + +struct ril_gprs { + struct ofono_gprs *gprs; + struct ril_modem *md; + GRilIoChannel *io; + GRilIoQueue *q; + gboolean ofono_attached; + gboolean ofono_registered; + int max_cids; + int last_status; + int ril_data_tech; + gulong event_id; + guint poll_id; + guint timer_id; +}; + +struct ril_gprs_cbd { + struct ril_gprs *gd; + union _ofono_gprs_cb { + ofono_gprs_status_cb_t status; + ofono_gprs_cb_t cb; + gpointer ptr; + } cb; + gpointer data; +}; + +#define ril_gprs_cbd_free g_free + +static void ril_gprs_poll_data_reg_state_cb(GRilIoChannel *io, int ril_status, + const void *data, guint len, void *user_data); + +static inline struct ril_gprs *ril_gprs_get_data(struct ofono_gprs *b) +{ + return ofono_gprs_get_data(b); +} + +static struct ril_gprs_cbd *ril_gprs_cbd_new(struct ril_gprs *gd, void *cb, + void *data) +{ + struct ril_gprs_cbd *cbd = g_new0(struct ril_gprs_cbd, 1); + + cbd->gd = gd; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +int ril_gprs_ril_data_tech(struct ofono_gprs *gprs) +{ + struct ril_gprs *gd = ril_gprs_get_data(gprs); + return gd ? gd->ril_data_tech : -1; +} + +static void ril_gprs_poll_data_reg_state(struct ril_gprs *gd) +{ + if (!gd->poll_id) { + DBG(""); + gd->poll_id = grilio_queue_send_request_full(gd->q, NULL, + RIL_REQUEST_DATA_REGISTRATION_STATE, + ril_gprs_poll_data_reg_state_cb, NULL, gd); + } +} + +static void ril_gprs_state_changed(GRilIoChannel *io, guint code, + const void *data, guint len, void *user_data) +{ + struct ril_gprs *gd = user_data; + + DBG("%s", ril_modem_get_path(gd->md)); + ril_gprs_poll_data_reg_state(gd); +} + +static gboolean ril_gprs_set_attached_callback(gpointer user_data) +{ + struct ofono_error error; + struct ril_gprs_cbd *cbd = user_data; + + DBG("%s", ril_modem_get_path(cbd->gd->md)); + cbd->gd->timer_id = 0; + cbd->cb.cb(ril_error_ok(&error), cbd->data); + ril_gprs_cbd_free(cbd); + + /* Single shot */ + return FALSE; +} + +static void ril_gprs_set_attached(struct ofono_gprs *gprs, int attached, + ofono_gprs_cb_t cb, void *data) +{ + struct ril_gprs *gd = ril_gprs_get_data(gprs); + + DBG("%s attached: %d", ril_modem_get_path(gd->md), attached); + /* + * As RIL offers no actual control over the GPRS 'attached' + * state, we save the desired state, and use it to override + * the actual modem's state in the 'attached_status' function. + * This is similar to the way the core ofono gprs code handles + * data roaming ( see src/gprs.c gprs_netreg_update(). + * + * The core gprs code calls driver->set_attached() when a netreg + * notification is received and any configured roaming conditions + * are met. + */ + + gd->ofono_attached = attached; + + /* + * However we cannot respond immediately, since core sets the + * value of driver_attached after calling set_attached and that + * leads to comparison failure in gprs_attached_update in + * connection drop phase + */ + gd->timer_id = g_idle_add(ril_gprs_set_attached_callback, + ril_gprs_cbd_new(gd, cb, data)); +} + +static int ril_gprs_parse_data_reg_state(struct ril_gprs *gd, + const void *data, guint len) +{ + struct ofono_gprs *gprs = gd->gprs; + struct ril_reg_data reg; + + if (!ril_util_parse_reg(data, len, ®)) { + ofono_error("Failure parsing data registration response."); + gd->ril_data_tech = -1; + return NETWORK_REGISTRATION_STATUS_UNKNOWN; + } else { + const int rawstatus = reg.status; + + if (gd->ril_data_tech != reg.ril_tech) { + gd->ril_data_tech = reg.ril_tech; + DBG("ril data tech %d", reg.ril_tech); + } + + if (!gd->ofono_registered) { + ofono_gprs_register(gprs); + gd->ofono_registered = TRUE; + } + + if (reg.max_calls > gd->max_cids) { + DBG("Setting max cids to %d", reg.max_calls); + gd->max_cids = reg.max_calls; + ofono_gprs_set_cid_range(gprs, 1, reg.max_calls); + } + + if (reg.status == NETWORK_REGISTRATION_STATUS_ROAMING) { + reg.status = ril_netreg_check_if_really_roaming( + ril_modem_ofono_netreg(gd->md), reg.status); + } + + if (rawstatus != reg.status) { + ofono_info("data registration modified %d => %d", + rawstatus, reg.status); + } + + return reg.status; + } +} + +static void ril_gprs_registration_status_cb(GRilIoChannel *io, int ril_status, + const void *data, guint len, void *user_data) +{ + struct ril_gprs_cbd *cbd = user_data; + ofono_gprs_status_cb_t cb = cbd->cb.status; + struct ril_gprs *gd = cbd->gd; + struct ofono_gprs *gprs = gd->gprs; + struct ofono_error error; + int status = -1; + + DBG("%s", ril_modem_get_path(gd->md)); + if (gd && ril_status == RIL_E_SUCCESS) { + ril_error_init_ok(&error); + } else { + ofono_error("ril_gprs_data_reg_cb: reply failure: %s", + ril_error_to_string(ril_status)); + ril_error_init_failure(&error); + goto cb_out; + } + + status = ril_gprs_parse_data_reg_state(gd, data, len); + if (status == NETWORK_REGISTRATION_STATUS_UNKNOWN) { + ril_error_init_failure(&error); + goto cb_out; + } + + /* Let's minimize logging */ + if (status != gd->last_status) { + ofono_info("data reg changes %d (%d), attached %d", + status, gd->last_status, gd->ofono_attached); + } + + /* Must be attached if registered or roaming */ + if (gd->last_status != NETWORK_REGISTRATION_STATUS_REGISTERED && + gd->last_status != NETWORK_REGISTRATION_STATUS_ROAMING) { + if (status == NETWORK_REGISTRATION_STATUS_REGISTERED) { + gd->ofono_attached = TRUE; + } else if ((status == NETWORK_REGISTRATION_STATUS_ROAMING) && + ofono_gprs_get_roaming_allowed(gd->gprs)) { + gd->ofono_attached = TRUE; + } + } + + if (!ofono_modem_get_online(ofono_gprs_get_modem(gprs))) + gd->ofono_attached = FALSE; + + /* if unsolicitated and no state change let's not notify core */ + if ((status == gd->last_status) && gd->ofono_attached) { + goto cb_out; + } + + if (!gd->ofono_attached) { + if (!cb) { + if (status == NETWORK_REGISTRATION_STATUS_ROAMING) { + if (!ofono_gprs_get_roaming_allowed(gd->gprs)) { + ofono_gprs_detached_notify(gprs); + } + + /* + * This prevents core ending + * into eternal loop with driver + */ + ril_error_init_failure(&error); + } + + ofono_gprs_status_notify(gprs, status); + + } else { + /* + * This prevents core ending + * into eternal loop with driver + */ + ril_error_init_failure(&error); + } + + gd->last_status = status; + goto exit; + } + + if (!cb) { + ofono_gprs_status_notify(gprs, status); + } + + gd->last_status = status; + +exit: + DBG("data reg status %d, last status %d, attached %d", + status, gd->last_status, gd->ofono_attached); +cb_out: + if (cb) { + cb(&error, status, cbd->data); + } +} + +static void ril_gprs_poll_data_reg_state_cb(GRilIoChannel *io, int ril_status, + const void *data, guint len, void *user_data) +{ + struct ril_gprs *gd = user_data; + int status; + + DBG("%s", ril_modem_get_path(gd->md)); + GASSERT(gd->poll_id); + gd->poll_id = 0; + + if (ril_status != RIL_E_SUCCESS) { + ofono_error("ril_gprs_data_probe_reg_cb: reply failure: %s", + ril_error_to_string(ril_status)); + status = NETWORK_REGISTRATION_STATUS_UNKNOWN; + } else { + status = ril_gprs_parse_data_reg_state(gd, data, len); + ofono_info("data reg status probed %d", status); + } + + if (status != gd->last_status) { + ofono_info("data reg changes %d (%d), attached %d", + status, gd->last_status, gd->ofono_attached); + gd->last_status = status; + if (gd->ofono_attached) { + ofono_gprs_status_notify(gd->gprs, status); + } + } +} + +static void ril_gprs_registration_status(struct ofono_gprs *gprs, + ofono_gprs_status_cb_t cb, void *data) +{ + struct ril_gprs *gd = ril_gprs_get_data(gprs); + + DBG(""); + if (gd) { + grilio_queue_send_request_full(gd->q, NULL, + RIL_REQUEST_DATA_REGISTRATION_STATE, + ril_gprs_registration_status_cb, ril_gprs_cbd_free, + ril_gprs_cbd_new(gd, cb, data)); + } +} + +static int ril_gprs_probe(struct ofono_gprs *gprs, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_gprs *gd = g_new0(struct ril_gprs, 1); + + DBG("%s", ril_modem_get_path(modem)); + gd->md = modem; + gd->io = grilio_channel_ref(ril_modem_io(modem)); + gd->q = grilio_queue_new(gd->io); + gd->last_status = -1; + gd->ril_data_tech = -1; + gd->gprs = gprs; + + ofono_gprs_set_data(gprs, gd); + ril_gprs_poll_data_reg_state(gd); + gd->event_id = grilio_channel_add_unsol_event_handler(gd->io, + ril_gprs_state_changed, + RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, gd); + return 0; +} + +static void ril_gprs_remove(struct ofono_gprs *gprs) +{ + struct ril_gprs *gd = ril_gprs_get_data(gprs); + + DBG("%s", ril_modem_get_path(gd->md)); + ofono_gprs_set_data(gprs, NULL); + + if (gd->timer_id > 0) { + g_source_remove(gd->timer_id); + } + + grilio_channel_remove_handler(gd->io, gd->event_id); + grilio_channel_unref(gd->io); + grilio_queue_cancel_all(gd->q, FALSE); + grilio_queue_unref(gd->q); + g_free(gd); +} + +const struct ofono_gprs_driver ril_gprs_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_gprs_probe, + .remove = ril_gprs_remove, + .set_attached = ril_gprs_set_attached, + .attached_status = ril_gprs_registration_status, +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_gprs_context.c b/ofono/drivers/ril/ril_gprs_context.c new file mode 100644 index 00000000..d3c4a784 --- /dev/null +++ b/ofono/drivers/ril/ril_gprs_context.c @@ -0,0 +1,900 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_constants.h" +#include "ril_util.h" +#include "ril_log.h" + +#include + +#include +#include +#include + +#include "common.h" + +#define PROTO_IP_STR "IP" +#define PROTO_IPV6_STR "IPV6" +#define PROTO_IPV4V6_STR "IPV4V6" + +#define MIN_DATA_CALL_LIST_SIZE 8 +#define MIN_DATA_CALL_REPLY_SIZE 36 + +#define SETUP_DATA_CALL_PARAMS 7 +#define DATA_PROFILE_DEFAULT_STR "0" +#define DEACTIVATE_DATA_CALL_PARAMS 2 + +#define CTX_ID_NONE ((unsigned int)(-1)) + +enum data_call_state { + DATA_CALL_INACTIVE, + DATA_CALL_LINK_DOWN, + DATA_CALL_ACTIVE, +}; + +enum ril_gprs_context_state { + STATE_IDLE, + STATE_ACTIVATING, + STATE_DEACTIVATING, + STATE_ACTIVE, +}; + +struct ril_gprs_context { + struct ofono_gprs_context *gc; + struct ril_modem *modem; + GRilIoChannel *io; + GRilIoQueue *q; + guint active_ctx_cid; + enum ril_gprs_context_state state; + gulong regid; + struct ril_gprs_context_data_call *active_call; +}; + +struct ril_gprs_context_data_call { + guint status; + gint cid; + guint active; + int retry_time; + int prot; + gint mtu; + gchar *ifname; + gchar **dnses; + gchar **gateways; + gchar **addresses; +}; + +struct ril_gprs_context_data_call_list { + guint version; + guint num; + GSList *calls; +}; + +struct ril_gprs_context_cbd { + struct ril_gprs_context *gcd; + ofono_gprs_context_cb_t cb; + gpointer data; +}; + +#define ril_gprs_context_cbd_free g_free + +static inline struct ril_gprs_context *ril_gprs_context_get_data( + struct ofono_gprs_context *gprs) +{ + return ofono_gprs_context_get_data(gprs); +} + +static struct ril_gprs_context_cbd *ril_gprs_context_cbd_new( + struct ril_gprs_context *gcd, ofono_gprs_context_cb_t cb, void *data) +{ + struct ril_gprs_context_cbd *cbd = + g_new0(struct ril_gprs_context_cbd, 1); + + cbd->gcd = gcd; + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static char *ril_gprs_context_netmask(const char *address) +{ + if (address) { + const char *suffix = strchr(address, '/'); + if (suffix) { + int nbits = atoi(suffix + 1); + if (nbits > 0 && nbits < 33) { + const char* str; + struct in_addr in; + in.s_addr = htonl((nbits == 32) ? 0xffffffff : + ((1 << nbits)-1) << (32-nbits)); + str = inet_ntoa(in); + if (str) { + return g_strdup(str); + } + } + } + } + return g_strdup("255.255.255.0"); +} + +static const char *ril_gprs_ofono_protocol_to_ril(guint protocol) +{ + switch (protocol) { + case OFONO_GPRS_PROTO_IPV6: + return PROTO_IPV6_STR; + case OFONO_GPRS_PROTO_IPV4V6: + return PROTO_IPV4V6_STR; + case OFONO_GPRS_PROTO_IP: + return PROTO_IP_STR; + default: + return NULL; + } +} + +static int ril_gprs_protocol_to_ofono(gchar *protocol_str) +{ + if (protocol_str) { + if (!strcmp(protocol_str, PROTO_IPV6_STR)) { + return OFONO_GPRS_PROTO_IPV6; + } else if (!strcmp(protocol_str, PROTO_IPV4V6_STR)) { + return OFONO_GPRS_PROTO_IPV4V6; + } else if (!strcmp(protocol_str, PROTO_IP_STR)) { + return OFONO_GPRS_PROTO_IP; + } + } + return -1; +} + +static void ril_gprs_context_data_call_free( + struct ril_gprs_context_data_call *call) +{ + if (call) { + g_free(call->ifname); + g_strfreev(call->dnses); + g_strfreev(call->addresses); + g_strfreev(call->gateways); + g_free(call); + } +} + +static void ril_gprs_context_set_disconnected(struct ril_gprs_context *gcd) +{ + gcd->state = STATE_IDLE; + if (gcd->active_call) { + ril_gprs_context_data_call_free(gcd->active_call); + gcd->active_call = NULL; + } + if (gcd->active_ctx_cid != CTX_ID_NONE) { + guint id = gcd->active_ctx_cid; + gcd->active_ctx_cid = CTX_ID_NONE; + ofono_gprs_context_deactivated(gcd->gc, id); + } +} + +static void ril_gprs_split_ip_by_protocol(char **ip_array, + char ***split_ip_addr, + char ***split_ipv6_addr) +{ + const int n = gutil_strv_length(ip_array); + int i; + + *split_ipv6_addr = *split_ip_addr = NULL; + for (i = 0; i < n && (!*split_ipv6_addr || !*split_ip_addr); i++) { + const char *addr = ip_array[i]; + switch (ril_address_family(addr)) { + case AF_INET: + if (!*split_ip_addr) { + char *mask = ril_gprs_context_netmask(addr); + *split_ip_addr = g_strsplit(addr, "/", 2); + if (gutil_strv_length(*split_ip_addr) == 2) { + g_free((*split_ip_addr)[1]); + (*split_ip_addr)[1] = mask; + } else { + /* This is rather unlikely to happen */ + *split_ip_addr = + gutil_strv_add(*split_ip_addr, + mask); + g_free(mask); + } + } + break; + case AF_INET6: + if (!*split_ipv6_addr) { + *split_ipv6_addr = g_strsplit(addr, "/", 2); + } + } + } +} + +static void ril_gprs_split_gw_by_protocol(char **gw_array, char **ip_gw, + char **ipv6_gw) +{ + const int n = gutil_strv_length(gw_array); + int i; + + *ip_gw = *ipv6_gw = NULL; + for (i = 0; i < n && (!*ipv6_gw || !*ip_gw); i++) { + const char *gw_addr = gw_array[i]; + switch (ril_address_family(gw_addr)) { + case AF_INET: + if (!*ip_gw) *ip_gw = g_strdup(gw_addr); + break; + case AF_INET6: + if (!*ipv6_gw) *ipv6_gw = g_strdup(gw_addr); + break; + } + } +} + +static void ril_gprs_split_dns_by_protocol(char **dns_array, char ***dns_addr, + char ***dns_ipv6_addr) +{ + const int n = gutil_strv_length(dns_array); + int i; + + *dns_ipv6_addr = *dns_addr = 0; + for (i = 0; i < n; i++) { + const char *addr = dns_array[i]; + switch (ril_address_family(addr)) { + case AF_INET: + *dns_addr = gutil_strv_add(*dns_addr, addr); + break; + case AF_INET6: + *dns_ipv6_addr = gutil_strv_add(*dns_ipv6_addr, addr); + break; + } + } +} + +static gint ril_gprs_context_parse_data_call_compare(gconstpointer a, + gconstpointer b) +{ + const struct ril_gprs_context_data_call *ca = a; + const struct ril_gprs_context_data_call *cb = b; + + if (ca->cid < cb->cid) { + return -1; + } else if (ca->cid > cb->cid) { + return 1; + } else { + return 0; + } +} + +static void ril_gprs_context_data_call_free1(gpointer data) +{ + ril_gprs_context_data_call_free(data); +} + +static void ril_gprs_context_data_call_list_free( + struct ril_gprs_context_data_call_list *list) +{ + if (list) { + g_slist_free_full(list->calls, ril_gprs_context_data_call_free1); + g_free(list); + } +} + +static struct ril_gprs_context_data_call *ril_gprs_context_data_call_find( + struct ril_gprs_context_data_call_list *list, gint cid) +{ + if (list) { + GSList *entry; + + for (entry = list->calls; entry; entry = entry->next) { + struct ril_gprs_context_data_call *call = entry->data; + + if (call->cid == cid) { + return call; + } + } + } + + return NULL; +} + +/* Only compares the stuff that's important to us */ +static gboolean ril_gprs_context_data_call_equal( + const struct ril_gprs_context_data_call *c1, + const struct ril_gprs_context_data_call *c2) +{ + if (!c1 && !c2) { + return TRUE; + } else if (c1 && c2) { + return c1->cid == c2->cid && + c1->active == c2->active && c1->prot == c2->prot && + !g_strcmp0(c1->ifname, c2->ifname) && + gutil_strv_equal(c1->dnses, c2->dnses) && + gutil_strv_equal(c1->gateways, c2->gateways) && + gutil_strv_equal(c1->addresses, c2->addresses); + } else { + return FALSE; + } +} + +static struct ril_gprs_context_data_call * + ril_gprs_context_parse_data_call(int version, GRilIoParser *rilp) +{ + char *prot; + struct ril_gprs_context_data_call *call = + g_new0(struct ril_gprs_context_data_call, 1); + + grilio_parser_get_uint32(rilp, &call->status); + grilio_parser_get_int32(rilp, &call->retry_time); + grilio_parser_get_int32(rilp, &call->cid); + grilio_parser_get_uint32(rilp, &call->active); + prot = grilio_parser_get_utf8(rilp); + call->ifname = grilio_parser_get_utf8(rilp); + call->addresses = grilio_parser_split_utf8(rilp, " "); + call->dnses = grilio_parser_split_utf8(rilp, " "); + call->gateways = grilio_parser_split_utf8(rilp, " "); + + call->prot = ril_gprs_protocol_to_ofono(prot); + if (call->prot < 0) { + ofono_error("Invalid type(protocol) specified: %s", prot); + } + + g_free(prot); + + if (version >= 9) { + /* PCSCF */ + grilio_parser_skip_string(rilp); + if (version >= 11) { + /* MTU */ + grilio_parser_get_int32(rilp, &call->mtu); + } + } + + return call; +} + +static struct ril_gprs_context_data_call_list * + ril_gprs_context_parse_data_call_list(const void *data, guint len) +{ + struct ril_gprs_context_data_call_list *reply = + g_new0(struct ril_gprs_context_data_call_list, 1); + GRilIoParser rilp; + unsigned int i, n; + + grilio_parser_init(&rilp, data, len); + grilio_parser_get_uint32(&rilp, &reply->version); + grilio_parser_get_uint32(&rilp, &n); + DBG("version=%d,num=%d", reply->version, n); + + for (i = 0; i < n && !grilio_parser_at_end(&rilp); i++) { + struct ril_gprs_context_data_call *call = + ril_gprs_context_parse_data_call(reply->version, &rilp); + + DBG("%d [status=%d,retry=%d,cid=%d," + "active=%d,type=%s,ifname=%s,mtu=%d," + "address=%s, dns=%s %s,gateways=%s]", + i, call->status, call->retry_time, + call->cid, call->active, + ril_gprs_ofono_protocol_to_ril(call->prot), + call->ifname, call->mtu, call->addresses[0], + call->dnses[0], + (call->dnses[0] && call->dnses[1]) ? + call->dnses[1] : "", + call->gateways[0]); + + reply->num++; + reply->calls = g_slist_insert_sorted(reply->calls, call, + ril_gprs_context_parse_data_call_compare); + } + + return reply; +} + +static void ril_gprs_context_call_list_changed(GRilIoChannel *io, guint event, + const void *data, guint len, void *user_data) +{ + struct ril_gprs_context *gcd = user_data; + struct ofono_gprs_context *gc = gcd->gc; + struct ril_gprs_context_data_call *call = NULL; + struct ril_gprs_context_data_call *prev_call; + struct ril_gprs_context_data_call_list *unsol = + ril_gprs_context_parse_data_call_list(data, len); + + if (gcd->active_call) { + /* Find our call */ + call = ril_gprs_context_data_call_find(unsol, + gcd->active_call->cid); + if (call) { + /* Check if the call have been disconnected */ + if (call->active == DATA_CALL_INACTIVE) { + ofono_error("Clearing active context"); + ril_gprs_context_set_disconnected(gcd); + + /* Compare it agains the last known state */ + } else if (ril_gprs_context_data_call_equal(call, + gcd->active_call)) { + DBG("call %u didn't change", call->cid); + call = NULL; + } else { + /* Steal it from the list */ + DBG("call %u changed", call->cid); + unsol->calls = g_slist_remove(unsol->calls, + call); + } + } else { + ofono_error("Clearing active context"); + ril_gprs_context_set_disconnected(gcd); + } + } + + /* We don't need the rest of the list anymore */ + ril_gprs_context_data_call_list_free(unsol); + + if (!call) { + /* We are not interested */ + return; + } + + /* Store the updated call data */ + prev_call = gcd->active_call; + gcd->active_call = call; + + if (call->status != 0) { + ofono_info("data call status: %d", call->status); + } + + if (call->active == DATA_CALL_ACTIVE) { + gboolean signal = FALSE; + + if (call->ifname && g_strcmp0(call->ifname, prev_call->ifname)) { + DBG("interface changed"); + signal = TRUE; + ofono_gprs_context_set_interface(gc, call->ifname); + } + + if (!gutil_strv_equal(call->addresses, prev_call->addresses)) { + char **split_ip_addr = NULL; + char **split_ipv6_addr = NULL; + + DBG("address changed"); + signal = TRUE; + + /* Pick 1 address of each protocol */ + ril_gprs_split_ip_by_protocol(call->addresses, + &split_ip_addr, &split_ipv6_addr); + + if ((call->prot == OFONO_GPRS_PROTO_IPV4V6 || + call->prot == OFONO_GPRS_PROTO_IPV6) && + split_ipv6_addr) { + ofono_gprs_context_set_ipv6_address(gc, + split_ipv6_addr[0]); + } + + if ((call->prot == OFONO_GPRS_PROTO_IPV4V6 || + call->prot == OFONO_GPRS_PROTO_IP) && + split_ip_addr) { + ofono_gprs_context_set_ipv4_netmask(gc, + split_ip_addr[1]); + ofono_gprs_context_set_ipv4_address(gc, + split_ip_addr[0], TRUE); + } + + g_strfreev(split_ip_addr); + g_strfreev(split_ipv6_addr); + } + + if (!gutil_strv_equal(call->gateways, prev_call->gateways)){ + char *ip_gw = NULL; + char *ipv6_gw = NULL; + + DBG("gateway changed"); + signal = TRUE; + + /* Pick 1 gw for each protocol*/ + ril_gprs_split_gw_by_protocol(call->gateways, + &ip_gw, &ipv6_gw); + + if ((call->prot == OFONO_GPRS_PROTO_IPV4V6 || + call->prot == OFONO_GPRS_PROTO_IPV6) && + ipv6_gw) { + ofono_gprs_context_set_ipv6_gateway(gc, ipv6_gw); + } + + if ((call->prot == OFONO_GPRS_PROTO_IPV4V6 || + call->prot == OFONO_GPRS_PROTO_IP) && + ip_gw) { + ofono_gprs_context_set_ipv4_gateway(gc, ip_gw); + } + + g_free(ip_gw); + g_free(ipv6_gw); + } + + if (!gutil_strv_equal(call->dnses, prev_call->dnses)){ + char **dns_ip = NULL; + char **dns_ipv6 = NULL; + + DBG("name server(s) changed"); + signal = TRUE; + + /* split based on protocol*/ + ril_gprs_split_dns_by_protocol(call->dnses, + &dns_ip, &dns_ipv6); + + if ((call->prot == OFONO_GPRS_PROTO_IPV4V6 || + call->prot == OFONO_GPRS_PROTO_IPV6) && + dns_ipv6) { + ofono_gprs_context_set_ipv6_dns_servers(gc, + (const char **) dns_ipv6); + } + + if ((call->prot == OFONO_GPRS_PROTO_IPV4V6 || + call->prot == OFONO_GPRS_PROTO_IP) && dns_ip) { + ofono_gprs_context_set_ipv4_dns_servers(gc, + (const char**)dns_ip); + } + + g_strfreev(dns_ip); + g_strfreev(dns_ipv6); + } + + if (signal) { + ofono_gprs_context_signal_change(gc, call->cid); + } + } + + ril_gprs_context_data_call_free(prev_call); +} + +static void ril_gprs_context_activate_primary_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_gprs_context_cbd *cbd = user_data; + ofono_gprs_context_cb_t cb = cbd->cb; + struct ril_gprs_context *gcd = cbd->gcd; + struct ofono_gprs_context *gc = gcd->gc; + struct ofono_error error; + struct ril_gprs_context_data_call_list *reply = NULL; + struct ril_gprs_context_data_call *call; + char **split_ip_addr = NULL; + char **split_ipv6_addr = NULL; + char* ip_gw = NULL; + char* ipv6_gw = NULL; + char** dns_addr = NULL; + char** dns_ipv6_addr = NULL; + + ofono_info("setting up data call"); + + ril_error_init_ok(&error); + if (status != RIL_E_SUCCESS) { + ofono_error("GPRS context: Reply failure: %s", + ril_error_to_string(status)); + error.type = OFONO_ERROR_TYPE_FAILURE; + error.error = status; + ril_gprs_context_set_disconnected(gcd); + goto done; + } + + reply = ril_gprs_context_parse_data_call_list(data, len); + if (reply->num != 1) { + ofono_error("Number of data calls: %u", reply->num); + ril_error_init_failure(&error); + ril_gprs_context_set_disconnected(gcd); + goto done; + } + + call = reply->calls->data; + + if (call->status != 0) { + ofono_error("Unexpected data call status %d", call->status); + error.type = OFONO_ERROR_TYPE_FAILURE; + error.error = call->status; + goto done; + } + + /* Must have interface */ + if (!call->ifname) { + ofono_error("GPRS context: No interface"); + error.type = OFONO_ERROR_TYPE_FAILURE; + error.error = EINVAL; + ril_gprs_context_set_disconnected(gcd); + goto done; + } + + /* Check the ip address */ + ril_gprs_split_ip_by_protocol(call->addresses, &split_ip_addr, + &split_ipv6_addr); + if (!split_ip_addr && !split_ipv6_addr) { + ofono_error("GPRS context: No IP address"); + error.type = OFONO_ERROR_TYPE_FAILURE; + error.error = EINVAL; + ril_gprs_context_set_disconnected(gcd); + goto done; + } + + /* Steal the call data from the list */ + g_slist_free(reply->calls); + reply->calls = NULL; + ril_gprs_context_data_call_free(gcd->active_call); + gcd->active_call = call; + gcd->state = STATE_ACTIVE; + + ofono_gprs_context_set_interface(gc, call->ifname); + ril_gprs_split_gw_by_protocol(call->gateways, &ip_gw, &ipv6_gw); + ril_gprs_split_dns_by_protocol(call->dnses, &dns_addr, &dns_ipv6_addr); + + /* TODO: + * RILD can return multiple addresses; oFono only supports setting + * a single IPv4 and single IPV6 address. At this time, we only use + * the first address. It's possible that a RIL may just specify + * the end-points of the point-to-point connection, in which case this + * code will need to changed to handle such a device. + */ + + if (split_ipv6_addr && + (call->prot == OFONO_GPRS_PROTO_IPV6 || + call->prot == OFONO_GPRS_PROTO_IPV4V6)) { + + ofono_gprs_context_set_ipv6_address(gc, split_ipv6_addr[0]); + ofono_gprs_context_set_ipv6_gateway(gc, ipv6_gw); + ofono_gprs_context_set_ipv6_dns_servers(gc, + (const char **) dns_ipv6_addr); + } + + if (split_ip_addr && + (call->prot == OFONO_GPRS_PROTO_IP || + call->prot == OFONO_GPRS_PROTO_IPV4V6)) { + ofono_gprs_context_set_ipv4_netmask(gc, split_ip_addr[1]); + ofono_gprs_context_set_ipv4_address(gc, split_ip_addr[0], TRUE); + ofono_gprs_context_set_ipv4_gateway(gc, ip_gw); + ofono_gprs_context_set_ipv4_dns_servers(gc, + (const char **) dns_addr); + } + +done: + ril_gprs_context_data_call_list_free(reply); + g_strfreev(split_ip_addr); + g_strfreev(split_ipv6_addr); + g_strfreev(dns_addr); + g_strfreev(dns_ipv6_addr); + g_free(ip_gw); + g_free(ipv6_gw); + + cb(&error, cbd->data); +} + +static void ril_gprs_context_activate_primary(struct ofono_gprs_context *gc, + const struct ofono_gprs_primary_context *ctx, + ofono_gprs_context_cb_t cb, void *data) +{ + struct ril_gprs_context *gcd = ril_gprs_context_get_data(gc); + struct ofono_netreg *netreg = ril_modem_ofono_netreg(gcd->modem); + struct ofono_gprs *gprs = ril_modem_ofono_gprs(gcd->modem); + const int rs = ofono_netreg_get_status(netreg); + const gchar *protocol_str; + GRilIoRequest* req; + int tech, auth; + + /* Let's make sure that we aren't connecting when roaming not allowed */ + if (rs == NETWORK_REGISTRATION_STATUS_ROAMING) { + if (!ofono_gprs_get_roaming_allowed(gprs) && + ril_netreg_check_if_really_roaming(netreg, rs) == + NETWORK_REGISTRATION_STATUS_ROAMING) { + struct ofono_error error; + ofono_info("Can't activate context %d (roaming)", + ctx->cid); + cb(ril_error_failure(&error), data); + return; + } + } + + ofono_info("Activating context: %d", ctx->cid); + protocol_str = ril_gprs_ofono_protocol_to_ril(ctx->proto); + GASSERT(protocol_str); + + /* ril.h has this to say about the radio tech parameter: + * + * ((const char **)data)[0] Radio technology to use: 0-CDMA, + * 1-GSM/UMTS, 2... for values above 2 + * this is RIL_RadioTechnology + 2. + * + * Makes little sense but it is what it is. + */ + tech = ril_gprs_ril_data_tech(gprs); + if (tech > 2) { + tech += 2; + } else { + /* + * This value used to be hardcoded, let's keep using it + * as the default. + */ + tech = RADIO_TECH_HSPA; + } + + /* + * We do the same as in $AOSP/frameworks/opt/telephony/src/java/com/ + * android/internal/telephony/dataconnection/DataConnection.java, + * onConnect(), and use authentication or not depending on whether + * the user field is empty or not. + */ + auth = (ctx->username && ctx->username[0]) ? + RIL_AUTH_BOTH : RIL_AUTH_NONE; + + /* + * TODO: add comments about tethering, other non-public + * profiles... + */ + req = grilio_request_new(); + grilio_request_append_int32(req, SETUP_DATA_CALL_PARAMS); + grilio_request_append_format(req, "%d", tech); + grilio_request_append_utf8(req, DATA_PROFILE_DEFAULT_STR); + grilio_request_append_utf8(req, ctx->apn); + grilio_request_append_utf8(req, ctx->username); + grilio_request_append_utf8(req, ctx->password); + grilio_request_append_format(req, "%d", auth); + grilio_request_append_utf8(req, protocol_str); + + GASSERT(ctx->cid != CTX_ID_NONE); + gcd->active_ctx_cid = ctx->cid; + gcd->state = STATE_ACTIVATING; + + grilio_queue_send_request_full(gcd->q, req, RIL_REQUEST_SETUP_DATA_CALL, + ril_gprs_context_activate_primary_cb, ril_gprs_context_cbd_free, + ril_gprs_context_cbd_new(gcd, cb, data)); + grilio_request_unref(req); +} + +static void ril_gprs_context_deactivate_primary_cb(GRilIoChannel *io, int err, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_gprs_context_cbd *cbd = user_data; + struct ril_gprs_context *gcd = cbd->gcd; + ofono_gprs_context_cb_t cb = cbd->cb; + + /* Reply has no data... */ + if (err == RIL_E_SUCCESS) { + ofono_info("Deactivated data call"); + if (gcd->state == STATE_DEACTIVATING) { + gcd->state = STATE_IDLE; + } + + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("Deactivate failure: %s", ril_error_to_string(err)); + cb(ril_error_failure(&error), cbd->data); + } +} + +static GRilIoRequest *ril_gprs_context_deactivate_req(unsigned int cid) +{ + /* + * TODO: airplane-mode; change reason to '1', + * which means "radio power off". + */ + GRilIoRequest *req = grilio_request_new(); + grilio_request_append_int32(req, DEACTIVATE_DATA_CALL_PARAMS); + grilio_request_append_format(req, "%d", cid); + grilio_request_append_format(req, "%d", + RIL_DEACTIVATE_DATA_CALL_NO_REASON); + return req; +} + +static void ril_gprs_context_deactivate_primary(struct ofono_gprs_context *gc, + unsigned int id, ofono_gprs_context_cb_t cb, void *data) +{ + struct ril_gprs_context *gcd = ril_gprs_context_get_data(gc); + + ofono_info("Deactivate primary"); + + GASSERT(cb); + if (gcd->active_call >= 0) { + GRilIoRequest* req; + + GASSERT(gcd->active_ctx_cid == id); + req = ril_gprs_context_deactivate_req(gcd->active_call->cid); + grilio_queue_send_request_full(gcd->q, req, + RIL_REQUEST_DEACTIVATE_DATA_CALL, + ril_gprs_context_deactivate_primary_cb, + ril_gprs_context_cbd_free, + ril_gprs_context_cbd_new(gcd, cb, data)); + grilio_request_unref(req); + gcd->state = STATE_DEACTIVATING; + } else { + struct ofono_error error; + cb(ril_error_ok(&error), data); + } +} + +static void ril_gprs_context_shutdown(struct ril_gprs_context *gcd) +{ + if (gcd->active_call && gcd->state == STATE_ACTIVE) { + GRilIoRequest* req; + + DBG("cid: %d", gcd->active_call->cid); + + /* + * Send it to GRilIoChannel so that it doesn't get cancelled + * by ril_gprs_context_remove() + */ + req = ril_gprs_context_deactivate_req(gcd->active_call->cid); + grilio_channel_send_request(gcd->io, req, + RIL_REQUEST_DEACTIVATE_DATA_CALL); + grilio_request_unref(req); + + /* + * When disconnect actually completes, we will receive + * RIL_UNSOL_DATA_CALL_LIST_CHANGED event + */ + gcd->state = STATE_DEACTIVATING; + } +} + +static void ril_gprs_context_detach_shutdown(struct ofono_gprs_context *gc, + unsigned int id) +{ + struct ril_gprs_context *gcd = ril_gprs_context_get_data(gc); + + DBG("%d", id); + GASSERT(gcd->active_ctx_cid == id); + ril_gprs_context_shutdown(gcd); +} + +static int ril_gprs_context_probe(struct ofono_gprs_context *gc, + unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_gprs_context *gcd = g_new0(struct ril_gprs_context, 1); + + DBG(""); + gcd->gc = gc; + gcd->modem = modem; + gcd->io = grilio_channel_ref(ril_modem_io(modem)); + gcd->q = grilio_queue_new(gcd->io); + gcd->regid = grilio_channel_add_unsol_event_handler(gcd->io, + ril_gprs_context_call_list_changed, + RIL_UNSOL_DATA_CALL_LIST_CHANGED, gcd); + ril_gprs_context_set_disconnected(gcd); + ofono_gprs_context_set_data(gc, gcd); + return 0; +} + +static void ril_gprs_context_remove(struct ofono_gprs_context *gc) +{ + struct ril_gprs_context *gcd = ril_gprs_context_get_data(gc); + + DBG(""); + ril_gprs_context_shutdown(gcd); + ofono_gprs_context_set_data(gc, NULL); + + grilio_channel_remove_handler(gcd->io, gcd->regid); + grilio_channel_unref(gcd->io); + grilio_queue_cancel_all(gcd->q, FALSE); + grilio_queue_unref(gcd->q); + ril_gprs_context_data_call_free(gcd->active_call); + g_free(gcd); +} + +const struct ofono_gprs_context_driver ril_gprs_context_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_gprs_context_probe, + .remove = ril_gprs_context_remove, + .activate_primary = ril_gprs_context_activate_primary, + .deactivate_primary = ril_gprs_context_deactivate_primary, + .detach_shutdown = ril_gprs_context_detach_shutdown, +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_log.h b/ofono/drivers/ril/ril_log.h new file mode 100644 index 00000000..710a42d6 --- /dev/null +++ b/ofono/drivers/ril/ril_log.h @@ -0,0 +1,31 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#ifndef RIL_LOG_H +#define RIL_LOG_H + +#define GLOG_MODULE_NAME ril_log +#include +#include + +#endif /* RIL_LOG_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_mce.c b/ofono/drivers/ril/ril_mce.c new file mode 100644 index 00000000..dfcc0b69 --- /dev/null +++ b/ofono/drivers/ril/ril_mce.c @@ -0,0 +1,133 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_mce.h" +#include "ril_constants.h" + +#include +#include + +#include +#include + +#include +#include + +#include + +struct ril_mce { + GRilIoChannel *io; + DBusConnection *conn; + int screen_state; + guint daemon_watch; + guint signal_watch; +}; + +static void ril_mce_send_screen_state(struct ril_mce *mce, gboolean on) +{ + GRilIoRequest *req = grilio_request_sized_new(8); + + grilio_request_append_int32(req, 1); /* Number of params */ + grilio_request_append_int32(req, on); /* screen on/off */ + + grilio_channel_send_request(mce->io, req, RIL_REQUEST_SCREEN_STATE); + grilio_request_unref(req); +} + +static gboolean ril_mce_display_changed(DBusConnection *conn, + DBusMessage *message, void *user_data) +{ + DBusMessageIter iter; + + if (dbus_message_iter_init(message, &iter) && + dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { + struct ril_mce *mce = user_data; + const char *value = NULL; + int state; + + dbus_message_iter_get_basic(&iter, &value); + DBG(" %s", value); + + /* It is on if it's not off */ + state = (g_strcmp0(value, MCE_DISPLAY_OFF_STRING) != 0); + if (mce->screen_state != state) { + mce->screen_state = state; + ril_mce_send_screen_state(mce, state); + } + } else { + DBG(""); + } + + return TRUE; +} + +static void ril_mce_connect(DBusConnection *conn, void *user_data) +{ + struct ril_mce *mce = user_data; + + DBG(""); + if (!mce->signal_watch) { + mce->signal_watch = g_dbus_add_signal_watch(conn, + MCE_SERVICE, NULL, MCE_SIGNAL_IF, MCE_DISPLAY_SIG, + ril_mce_display_changed, mce, NULL); + } +} + +static void ril_mce_disconnect(DBusConnection *conn, void *user_data) +{ + struct ril_mce *mce = user_data; + + DBG(""); + if (mce->signal_watch) { + g_dbus_remove_watch(conn, mce->signal_watch); + mce->signal_watch = 0; + } +} + +struct ril_mce *ril_mce_new(GRilIoChannel *io) +{ + struct ril_mce *mce = g_new0(struct ril_mce, 1); + + mce->conn = dbus_connection_ref(ofono_dbus_get_connection()); + mce->io = grilio_channel_ref(io); + mce->screen_state = -1; + mce->daemon_watch = g_dbus_add_service_watch(mce->conn, MCE_SERVICE, + ril_mce_connect, ril_mce_disconnect, mce, NULL); + + return mce; +} + +void ril_mce_free(struct ril_mce *mce) +{ + if (mce) { + if (mce->signal_watch) { + g_dbus_remove_watch(mce->conn, mce->signal_watch); + } + if (mce->daemon_watch) { + g_dbus_remove_watch(mce->conn, mce->daemon_watch); + } + dbus_connection_unref(mce->conn); + grilio_channel_unref(mce->io); + g_free(mce); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_mce.h b/ofono/drivers/ril/ril_mce.h new file mode 100644 index 00000000..8c9e483f --- /dev/null +++ b/ofono/drivers/ril/ril_mce.h @@ -0,0 +1,32 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#ifndef RIL_MCE_H +#define RIL_MCE_H + +#include "ril_types.h" + +struct ril_mce *ril_mce_new(GRilIoChannel *io); +void ril_mce_free(struct ril_mce *mce); + +#endif /* RIL_MCE_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_modem.c b/ofono/drivers/ril/ril_modem.c new file mode 100644 index 00000000..e34c2a02 --- /dev/null +++ b/ofono/drivers/ril/ril_modem.c @@ -0,0 +1,560 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_constants.h" +#include "ril_util.h" +#include "ril_log.h" + +#include "ofono.h" + +#define MAX_PDP_CONTEXTS (2) + +enum ril_modem_power_state { + POWERED_OFF, + POWERING_ON, + POWERED_ON, + POWERING_OFF +}; + +enum ril_modem_online_state { + OFFLINE, + GOING_ONLINE, + ONLINE, + GOING_OFFLINE +}; + +enum ril_modem_events { + MODEM_EVENT_CONNECTED, + MODEM_EVENT_RADIO_STATE_CHANGED, + MODEM_EVENT_ERROR, + MODEM_EVENT_COUNT +}; + +struct ril_modem_online_request { + ofono_modem_online_cb_t cb; + void *data; + guint id; +}; + +struct ril_modem { + GRilIoChannel *io; + GRilIoQueue *q; + struct ofono_modem *modem; + struct ofono_radio_settings *radio_settings; + struct ril_modem_config config; + char *default_name; + + enum ril_radio_state radio_state; + enum ril_modem_power_state power_state; + gulong event_id[MODEM_EVENT_COUNT]; + + ril_modem_cb_t error_cb; + void *error_cb_data; + + ril_modem_cb_t removed_cb; + void *removed_cb_data; + + struct ril_modem_online_request set_online; + struct ril_modem_online_request set_offline; +}; + +static guint ril_modem_request_power(struct ril_modem *md, gboolean on, + GRilIoChannelResponseFunc cb); + +static inline struct ril_modem *ril_modem_from_ofono(struct ofono_modem *modem) +{ + return ofono_modem_get_data(modem); +} + +GRilIoChannel *ril_modem_io(struct ril_modem *md) +{ + return md ? md->io : NULL; +} + +const struct ril_modem_config *ril_modem_config(struct ril_modem *md) +{ + return md ? &md->config : NULL; +} + +struct ofono_modem *ril_modem_ofono_modem(struct ril_modem *md) +{ + return md ? md->modem : NULL; +} + +struct ofono_sim *ril_modem_ofono_sim(struct ril_modem *md) +{ + return (md && md->modem) ? + __ofono_atom_find(OFONO_ATOM_TYPE_SIM, md->modem) : + NULL; +} + +struct ofono_gprs *ril_modem_ofono_gprs(struct ril_modem *md) +{ + return (md && md->modem) ? + __ofono_atom_find(OFONO_ATOM_TYPE_GPRS, md->modem) : + NULL; +} + +struct ofono_netreg *ril_modem_ofono_netreg(struct ril_modem *md) +{ + return (md && md->modem) ? + __ofono_atom_find(OFONO_ATOM_TYPE_NETREG, md->modem) : + NULL; +} + +void ril_modem_delete(struct ril_modem *md) +{ + if (md && md->modem) { + ofono_modem_remove(md->modem); + } +} + +void ril_modem_set_error_cb(struct ril_modem *md, ril_modem_cb_t cb, + void *data) +{ + md->error_cb = cb; + md->error_cb_data = data; +} + +void ril_modem_set_removed_cb(struct ril_modem *md, ril_modem_cb_t cb, + void *data) +{ + md->removed_cb = cb; + md->removed_cb_data = data; +} + +void ril_modem_allow_data(struct ril_modem *md) +{ + if (md && md->modem) { + GRilIoRequest *req = grilio_request_sized_new(8); + + DBG("%s", ofono_modem_get_path(md->modem)); + grilio_request_append_int32(req, 1); + grilio_request_append_int32(req, TRUE); + grilio_queue_send_request(md->q, req, RIL_REQUEST_ALLOW_DATA); + grilio_request_unref(req); + } +} + +static void ril_modem_signal_error(struct ril_modem *md) +{ + if (md->modem && md->error_cb) { + ril_modem_cb_t cb = md->error_cb; + void *data = md->error_cb_data; + + md->error_cb = NULL; + md->error_cb_data = NULL; + cb(md, data); + } +} + +static void ril_modem_online_request_ok(GRilIoChannel* io, + struct ril_modem_online_request *req) +{ + if (req->id) { + grilio_channel_cancel_request(io, req->id, FALSE); + req->id = 0; + } + + if (req->cb) { + struct ofono_error error; + ofono_modem_online_cb_t cb = req->cb; + void *data = req->data; + + req->cb = NULL; + req->data = NULL; + cb(ril_error_ok(&error), data); + } +} + +static void ril_modem_update_online_state(struct ril_modem *md) +{ + switch (md->radio_state) { + case RADIO_STATE_ON: + ril_modem_online_request_ok(md->io, &md->set_online); + break; + + case RADIO_STATE_OFF: + case RADIO_STATE_UNAVAILABLE: + ril_modem_online_request_ok(md->io, &md->set_offline); + break; + + default: + break; + } + + if (!md->set_offline.id && !md->set_online.id && + md->power_state == POWERING_OFF) { + md->power_state = POWERED_OFF; + ofono_modem_set_powered(md->modem, FALSE); + } +} + +static void ril_modem_online_request_done(struct ril_modem *md, + struct ril_modem_online_request *req, int ril_status) +{ + GASSERT(req->id); + GASSERT(req->cb); + GASSERT(req->data); + req->id = 0; + + /* If this request has completed successfully, we will + * invoke the callback and notify ofono core when we get + * RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, i.e. the power + * state has actually changed */ + if (ril_status != RIL_E_SUCCESS) { + struct ofono_error error; + ofono_modem_online_cb_t cb = req->cb; + void *data = req->data; + + req->cb = NULL; + req->data = NULL; + cb(ril_error_failure(&error), data); + } + + ril_modem_update_online_state(md); +} + +static void ril_modem_set_online_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_modem *md = user_data; + + DBG("Power on status %s", ril_error_to_string(status)); + ril_modem_online_request_done(md, &md->set_online, status); +} + +static void ril_modem_set_offline_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_modem *md = user_data; + + DBG("Power on status %s", ril_error_to_string(status)); + ril_modem_online_request_done(md, &md->set_offline, status); +} + +static GRilIoRequest *ril_modem_request_radio_power(gboolean on) +{ + GRilIoRequest *req = grilio_request_sized_new(8); + + grilio_request_append_int32(req, 1); + grilio_request_append_int32(req, on); /* Radio ON=1, OFF=0 */ + return req; +} + +static guint ril_modem_request_power(struct ril_modem *md, gboolean on, + GRilIoChannelResponseFunc cb) +{ + guint id = 0; + + if (md->q) { + GRilIoRequest *req = ril_modem_request_radio_power(on); + + DBG("[%u] %s", md->config.slot, on ? "ON" : "OFF"); + id = grilio_queue_send_request_full(md->q, req, + RIL_REQUEST_RADIO_POWER, cb, NULL, md); + grilio_request_unref(req); + } + + return id; +} + +static void ril_modem_connected(struct ril_modem *md) +{ + ofono_debug("RIL version %u", md->io->ril_version); + ril_modem_request_power(md, FALSE, NULL); + if (md->power_state == POWERING_ON) { + md->power_state = POWERED_ON; + ofono_modem_set_powered(md->modem, TRUE); + } +} + +static void ril_modem_connected_cb(GRilIoChannel *io, void *user_data) +{ + ril_modem_connected((struct ril_modem *)user_data); +} + +static void ril_modem_pre_sim(struct ofono_modem *modem) +{ + struct ril_modem *md = ril_modem_from_ofono(modem); + + DBG(""); + ofono_devinfo_create(modem, 0, RILMODEM_DRIVER, md); + ofono_sim_create(modem, 0, RILMODEM_DRIVER, md); + ofono_voicecall_create(modem, 0, RILMODEM_DRIVER, md); +} + +static void ril_modem_post_sim(struct ofono_modem *modem) +{ + struct ril_modem *md = ril_modem_from_ofono(modem); + struct ofono_gprs *gprs; + struct ofono_gprs_context *gc; + int i; + + DBG(""); + ofono_sms_create(modem, 0, RILMODEM_DRIVER, md); + gprs = ofono_gprs_create(modem, 0, RILMODEM_DRIVER, md); + if (gprs) { + for (i = 0; i < MAX_PDP_CONTEXTS; i++) { + gc = ofono_gprs_context_create(modem, 0, + RILMODEM_DRIVER, md); + if (gc == NULL) + break; + + ofono_gprs_add_context(gprs, gc); + } + } + + ofono_phonebook_create(modem, 0, RILMODEM_DRIVER, md); + ofono_call_forwarding_create(modem, 0, RILMODEM_DRIVER, md); + ofono_call_barring_create(modem, 0, RILMODEM_DRIVER, md); + ofono_stk_create(modem, 0, RILMODEM_DRIVER, md); + ofono_message_waiting_register(ofono_message_waiting_create(modem)); +} + +static void ril_modem_post_online(struct ofono_modem *modem) +{ + struct ril_modem *md = ril_modem_from_ofono(modem); + + DBG(""); + ofono_call_volume_create(modem, 0, RILMODEM_DRIVER, md); + ofono_netreg_create(modem, 0, RILMODEM_DRIVER, md); + ofono_ussd_create(modem, 0, RILMODEM_DRIVER, md); + ofono_call_settings_create(modem, 0, RILMODEM_DRIVER, md); + ofono_oem_raw_create(modem, 0, RILMODEM_DRIVER, md); +} + +static void ril_modem_set_online(struct ofono_modem *modem, ofono_bool_t online, + ofono_modem_online_cb_t cb, void *data) +{ + struct ril_modem *md = ril_modem_from_ofono(modem); + struct ril_modem_online_request *req; + + DBG("%s going %sline", ofono_modem_get_path(modem), + online ? "on" : "off"); + + GASSERT(md->power_state == POWERED_ON); + if (online) { + req = &md->set_online; + GASSERT(!req->id); + req->id = ril_modem_request_power(md, TRUE, + ril_modem_set_online_cb); + } else { + req = &md->set_offline; + GASSERT(!req->id); + req->id = ril_modem_request_power(md, FALSE, + ril_modem_set_offline_cb); + } + + if (req->id) { + req->cb = cb; + req->data = data; + } else { + struct ofono_error error; + cb(ril_error_failure(&error), data); + } +} + +static int ril_modem_enable(struct ofono_modem *modem) +{ + struct ril_modem *md = ril_modem_from_ofono(modem); + + DBG("%s", ofono_modem_get_path(modem)); + if (md->io->connected) { + md->power_state = POWERED_ON; + return 0; + } else { + DBG("Waiting for RIL_UNSOL_RIL_CONNECTED"); + md->power_state = POWERING_ON; + return -EINPROGRESS; + } +} + +static int ril_modem_disable(struct ofono_modem *modem) +{ + struct ril_modem *md = ril_modem_from_ofono(modem); + + DBG("%s", ofono_modem_get_path(modem)); + if (md->set_online.id || md->set_offline.id) { + md->power_state = POWERING_OFF; + return -EINPROGRESS; + } else { + md->power_state = POWERED_OFF; + return 0; + } +} + +static void ril_modem_error(GRilIoChannel *io, const GError *error, + void *user_data) +{ + struct ril_modem *md = user_data; + + ofono_error("%s", error->message); + grilio_channel_remove_handler(io, md->event_id[MODEM_EVENT_ERROR]); + md->event_id[MODEM_EVENT_ERROR] = 0; + ril_modem_signal_error(md); +} + +static int ril_modem_probe(struct ofono_modem *modem) +{ + DBG("%s", ofono_modem_get_path(modem)); + return 0; +} + +static void ril_modem_remove(struct ofono_modem *modem) +{ + struct ril_modem *md = ril_modem_from_ofono(modem); + int i; + + DBG("%s", ofono_modem_get_path(modem)); + GASSERT(md->modem); + + if (md->radio_state > RADIO_STATE_UNAVAILABLE) { + GRilIoRequest *req = ril_modem_request_radio_power(FALSE); + grilio_channel_send_request(md->io, req, + RIL_REQUEST_RADIO_POWER); + grilio_request_unref(req); + } + + if (md->removed_cb) { + ril_modem_cb_t cb = md->removed_cb; + void *data = md->removed_cb_data; + + md->removed_cb = NULL; + md->removed_cb_data = NULL; + cb(md, data); + } + + ofono_modem_set_data(modem, NULL); + + for (i=0; ievent_id); i++) { + grilio_channel_remove_handler(md->io, md->event_id[i]); + } + + grilio_channel_unref(md->io); + grilio_queue_cancel_all(md->q, FALSE); + grilio_queue_unref(md->q); + g_free(md->default_name); + g_free(md); +} + +static void ril_modem_radio_state_changed(GRilIoChannel *io, guint ril_event, + const void *data, guint len, void *user_data) +{ + struct ril_modem *md = user_data; + GRilIoParser rilp; + int radio_state; + + GASSERT(ril_event == RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED); + grilio_parser_init(&rilp, data, len); + if (grilio_parser_get_int32(&rilp, &radio_state) && + grilio_parser_at_end(&rilp)) { + DBG("%s %s", ofono_modem_get_path(md->modem), + ril_radio_state_to_string(radio_state)); + md->radio_state = radio_state; + if (radio_state == RADIO_STATE_ON && !md->radio_settings) { + DBG("Initializing radio settings interface"); + md->radio_settings = + ofono_radio_settings_create(md->modem, 0, + RILMODEM_DRIVER, md); + } + + ril_modem_update_online_state(md); + } else { + ofono_error("Error parsing RADIO_STATE_CHANGED"); + } +} + +struct ril_modem *ril_modem_create(GRilIoChannel *io, const char *dev, + const struct ril_modem_config *config) +{ + struct ofono_modem *modem = ofono_modem_create(dev, RILMODEM_DRIVER); + + if (modem) { + int err; + struct ril_modem *md = g_new0(struct ril_modem, 1); + + /* Copy config */ + md->config = *config; + if (config->default_name && config->default_name[0]) { + md->default_name = g_strdup(config->default_name); + } else { + md->default_name = g_strdup_printf("SIM%u", + config->slot + 1); + } + md->config.default_name = md->default_name; + + md->modem = modem; + md->io = grilio_channel_ref(io); + md->q = grilio_queue_new(io); + ofono_modem_set_data(modem, md); + err = ofono_modem_register(modem); + if (!err) { + md->event_id[MODEM_EVENT_ERROR] = + grilio_channel_add_error_handler(io, + ril_modem_error, md); + md->event_id[MODEM_EVENT_RADIO_STATE_CHANGED] = + grilio_channel_add_unsol_event_handler(io, + ril_modem_radio_state_changed, + RIL_UNSOL_RESPONSE_RADIO_STATE_CHANGED, + md); + if (io->connected) { + ril_modem_connected(md); + } else { + DBG("[%u] waiting for RIL_UNSOL_RIL_CONNECTED", + config->slot); + md->event_id[MODEM_EVENT_CONNECTED] = + grilio_channel_add_connected_handler( + io, ril_modem_connected_cb, md); + } + + /* + * ofono_modem_reset sets Powered to TRUE without + * issuing PropertyChange signal. + */ + ofono_modem_set_powered(md->modem, FALSE); + ofono_modem_set_powered(md->modem, TRUE); + return md; + } else { + ofono_error("Error %d registering %s", + err, RILMODEM_DRIVER); + } + + ofono_modem_remove(modem); + } + + return NULL; +} + +const struct ofono_modem_driver ril_modem_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_modem_probe, + .remove = ril_modem_remove, + .enable = ril_modem_enable, + .disable = ril_modem_disable, + .pre_sim = ril_modem_pre_sim, + .post_sim = ril_modem_post_sim, + .post_online = ril_modem_post_online, + .set_online = ril_modem_set_online +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_netreg.c b/ofono/drivers/ril/ril_netreg.c new file mode 100644 index 00000000..b953e095 --- /dev/null +++ b/ofono/drivers/ril/ril_netreg.c @@ -0,0 +1,652 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "common.h" +#include "simutil.h" + +#include + +enum ril_netreg_events { + NETREG_EVENT_VOICE_NETWORK_STATE_CHANGED, + NETREG_EVENT_NITZ_TIME_RECEIVED, + NETREG_EVENT_SIGNAL_STRENGTH, + NETREG_EVENT_COUNT +}; + +struct ril_netreg { + GRilIoChannel *io; + GRilIoQueue *q; + struct ofono_netreg *netreg; + char mcc[OFONO_MAX_MCC_LENGTH + 1]; + char mnc[OFONO_MAX_MNC_LENGTH + 1]; + int tech; + struct ofono_network_time time; + guint timer_id; + int corestatus; /* Registration status previously reported to core */ + gulong event_id[NETREG_EVENT_COUNT]; +}; + +/* 27.007 Section 7.3 */ +enum operator_status { + OPERATOR_STATUS_UNKNOWN = 0, + OPERATOR_STATUS_AVAILABLE = 1, + OPERATOR_STATUS_CURRENT = 2, + OPERATOR_STATUS_FORBIDDEN = 3, +}; + +struct ril_netreg_cbd { + struct ril_netreg *nd; + union { + ofono_netreg_status_cb_t status; + ofono_netreg_operator_cb_t operator; + ofono_netreg_operator_list_cb_t operator_list; + ofono_netreg_register_cb_t reg; + ofono_netreg_strength_cb_t strength; + gpointer ptr; + } cb; + gpointer data; +}; + +#define ril_netreg_cbd_free g_free + +static inline struct ril_netreg *ril_netreg_get_data(struct ofono_netreg *nr) +{ + return ofono_netreg_get_data(nr); +} + +static struct ril_netreg_cbd *ril_netreg_cbd_new(struct ril_netreg *nd, + void *cb, void *data) +{ + struct ril_netreg_cbd *cbd = g_new0(struct ril_netreg_cbd, 1); + + cbd->nd = nd; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static gboolean ril_netreg_extract_mcc_mnc(const char *str, + struct ofono_network_operator *op) +{ + if (str) { + int i; + const char *ptr = str; + + /* Three digit country code */ + for (i = 0; + i < OFONO_MAX_MCC_LENGTH && *ptr && isdigit(*ptr); + i++) { + op->mcc[i] = *ptr++; + } + op->mcc[i] = 0; + + if (i == OFONO_MAX_MCC_LENGTH) { + /* Usually a 2 but sometimes 3 digit network code */ + for (i=0; + imnc[i] = *ptr++; + } + op->mnc[i] = 0; + + if (i > 0) { + + /* + * Sometimes MCC/MNC are followed by + and + * what looks like the technology code. This + * is of course completely undocumented. + */ + if (*ptr == '+') { + int tech = ril_parse_tech(ptr+1, NULL); + if (tech >= 0) { + op->tech = tech; + } + } + + return TRUE; + } + } + } + return FALSE; +} + +static void ril_netreg_state_cb(GRilIoChannel *io, int call_status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_netreg_cbd *cbd = user_data; + ofono_netreg_status_cb_t cb = cbd->cb.status; + struct ril_netreg *nd = cbd->nd; + struct ril_reg_data reg; + int rawstatus; + + DBG(""); + if (call_status != RIL_E_SUCCESS || !nd->netreg) { + ofono_error("voice registration status query fail"); + nd->corestatus = -1; + cb(ril_error_failure(&error), -1, -1, -1, -1, cbd->data); + return; + } + + if (!ril_util_parse_reg(data, len, ®)) { + DBG("voice registration status parsing fail"); + nd->corestatus = -1; + cb(ril_error_failure(&error), -1, -1, -1, -1, cbd->data); + return; + } + + rawstatus = reg.status; + if (reg.status == NETWORK_REGISTRATION_STATUS_ROAMING) { + reg.status = ril_netreg_check_if_really_roaming(nd->netreg, + reg.status); + } + + if (rawstatus != reg.status) { + ofono_info("voice registration modified %d => %d", + rawstatus, reg.status); + } + + DBG("status:%d corestatus:%d", reg.status, nd->corestatus); + + if (nd->corestatus != reg.status) { + ofono_info("voice registration changes %d (%d)", + reg.status, nd->corestatus); + } + + nd->corestatus = reg.status; + nd->tech = reg.access_tech; + cb(ril_error_ok(&error), reg.status, reg.lac, reg.ci, reg.access_tech, + cbd->data); +} + +static void ril_netreg_status_notify(struct ofono_error *error, int status, + int lac, int ci, int tech, gpointer user_data) +{ + struct ril_netreg *nd = user_data; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + DBG("Error during status notification"); + } else if (nd->netreg) { + ofono_netreg_status_notify(nd->netreg, status, lac, ci, tech); + } +} + +static void ril_netreg_network_state_change(GRilIoChannel *io, + guint ril_event, const void *data, guint len, void *user_data) +{ + struct ril_netreg *nd = user_data; + + GASSERT(ril_event == RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED); + grilio_queue_send_request_full(nd->q, NULL, + RIL_REQUEST_VOICE_REGISTRATION_STATE, + ril_netreg_state_cb, ril_netreg_cbd_free, + ril_netreg_cbd_new(nd, ril_netreg_status_notify, nd)); +} + +static void ril_netreg_registration_status(struct ofono_netreg *netreg, + ofono_netreg_status_cb_t cb, void *data) +{ + struct ril_netreg *nd = ril_netreg_get_data(netreg); + + grilio_queue_send_request_full(nd->q, NULL, + RIL_REQUEST_VOICE_REGISTRATION_STATE, ril_netreg_state_cb, + ril_netreg_cbd_free, ril_netreg_cbd_new(nd, cb, data)); +} + +static void ril_netreg_current_operator_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_netreg_cbd *cbd = user_data; + struct ril_netreg *nd = cbd->nd; + struct ofono_error error; + struct ofono_network_operator op; + struct ofono_network_operator *result = NULL; + gchar *lalpha = NULL, *salpha = NULL, *numeric = NULL; + int tmp; + GRilIoParser rilp; + + ril_error_init_failure(&error); + if (status != RIL_E_SUCCESS) { + ofono_error("Failed to retrive the current operator: %s", + ril_error_to_string(status)); + goto done; + } + + grilio_parser_init(&rilp, data, len); + if (!grilio_parser_get_int32(&rilp, &tmp) || !tmp) { + goto done; + } + + lalpha = grilio_parser_get_utf8(&rilp); + salpha = grilio_parser_get_utf8(&rilp); + numeric = grilio_parser_get_utf8(&rilp); + + /* Try to use long by default */ + if (lalpha) { + strncpy(op.name, lalpha, OFONO_MAX_OPERATOR_NAME_LENGTH); + } else if (salpha) { + strncpy(op.name, salpha, OFONO_MAX_OPERATOR_NAME_LENGTH); + } else { + goto done; + } + + if (!ril_netreg_extract_mcc_mnc(numeric, &op)) { + goto done; + } + + /* Set to current */ + op.status = OPERATOR_STATUS_CURRENT; + op.tech = nd->tech; + result = &op; + ril_error_init_ok(&error); + + DBG("lalpha=%s, salpha=%s, numeric=%s, %s, mcc=%s, mnc=%s, %s", + lalpha, salpha, numeric, op.name, op.mcc, op.mnc, + registration_tech_to_string(op.tech)); + +done: + cbd->cb.operator(&error, result, cbd->data); + g_free(lalpha); + g_free(salpha); + g_free(numeric); +} + +static void ril_netreg_current_operator(struct ofono_netreg *netreg, + ofono_netreg_operator_cb_t cb, void *data) +{ + struct ril_netreg *nd = ril_netreg_get_data(netreg); + + grilio_queue_send_request_full(nd->q, NULL, RIL_REQUEST_OPERATOR, + ril_netreg_current_operator_cb, ril_netreg_cbd_free, + ril_netreg_cbd_new(nd, cb, data)); +} + +static void ril_netreg_list_operators_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_netreg_cbd *cbd = user_data; + ofono_netreg_operator_list_cb_t cb = cbd->cb.operator_list; + struct ofono_network_operator *list; + struct ofono_error error; + int noperators = 0, i; + GRilIoParser rilp; + gboolean ok = TRUE; + + if (status != RIL_E_SUCCESS) { + ofono_error("Failed to retrive the list of operators: %s", + ril_error_to_string(status)); + cb(ril_error_failure(&error), 0, NULL, cbd->data); + return; + } + + grilio_parser_init(&rilp, data, len); + + /* Number of operators at the list (4 strings for every operator) */ + grilio_parser_get_int32(&rilp, &noperators); + GASSERT(!(noperators % 4)); + noperators /= 4; + ofono_info("noperators = %d", noperators); + + list = g_new0(struct ofono_network_operator, noperators); + for (i = 0; i < noperators && ok; i++) { + struct ofono_network_operator *op = list + i; + char *lalpha = grilio_parser_get_utf8(&rilp); + char *salpha = grilio_parser_get_utf8(&rilp); + char *numeric = grilio_parser_get_utf8(&rilp); + char *status = grilio_parser_get_utf8(&rilp); + + /* Try to use long by default */ + if (lalpha) { + strncpy(op->name, lalpha, + OFONO_MAX_OPERATOR_NAME_LENGTH); + } else if (salpha) { + strncpy(op->name, salpha, + OFONO_MAX_OPERATOR_NAME_LENGTH); + } else { + op->name[0] = 0; + } + + /* Set the proper status */ + if (!strcmp(status, "available")) { + list[i].status = OPERATOR_STATUS_AVAILABLE; + } else if (!strcmp(status, "current")) { + list[i].status = OPERATOR_STATUS_CURRENT; + } else if (!strcmp(status, "forbidden")) { + list[i].status = OPERATOR_STATUS_FORBIDDEN; + } else { + list[i].status = OPERATOR_STATUS_UNKNOWN; + } + + op->tech = ACCESS_TECHNOLOGY_GSM; + ok = ril_netreg_extract_mcc_mnc(numeric, op); + if (ok) { + DBG("[operator=%s, %s, %s, status: %s]", op->name, + op->mcc, op->mnc, status); + } + + g_free(lalpha); + g_free(salpha); + g_free(numeric); + g_free(status); + } + + if (ok) { + cb(ril_error_ok(&error), noperators, list, cbd->data); + } else { + cb(ril_error_failure(&error), 0, NULL, cbd->data); + } + + g_free(list); +} + +static void ril_netreg_list_operators(struct ofono_netreg *netreg, + ofono_netreg_operator_list_cb_t cb, void *data) +{ + struct ril_netreg *nd = ril_netreg_get_data(netreg); + + grilio_queue_send_request_full(nd->q, NULL, + RIL_REQUEST_QUERY_AVAILABLE_NETWORKS, + ril_netreg_list_operators_cb, ril_netreg_cbd_free, + ril_netreg_cbd_new(nd, cb, data)); +} + +static void ril_netreg_register_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_netreg_cbd *cbd = user_data; + ofono_netreg_register_cb_t cb = cbd->cb.reg; + struct ofono_error error; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("registration failed, ril result %d", status); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_netreg_register_auto(struct ofono_netreg *netreg, + ofono_netreg_register_cb_t cb, void *data) +{ + struct ril_netreg *nd = ril_netreg_get_data(netreg); + + ofono_info("nw select automatic"); + grilio_queue_send_request_full(nd->q, NULL, + RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, + ril_netreg_register_cb, ril_netreg_cbd_free, + ril_netreg_cbd_new(nd, cb, data)); +} + +static void ril_netreg_register_manual(struct ofono_netreg *netreg, + const char *mcc, const char *mnc, + ofono_netreg_register_cb_t cb, void *data) +{ + struct ril_netreg *nd = ril_netreg_get_data(netreg); + char buf[OFONO_MAX_MCC_LENGTH + OFONO_MAX_MNC_LENGTH + 1]; + int len = snprintf(buf, sizeof(buf), "%s%s", mcc, mnc); + GRilIoRequest *req = grilio_request_new(); + + ofono_info("nw select manual: %s%s", mcc, mnc); + grilio_request_append_utf8_chars(req, buf, len); + grilio_queue_send_request_full(nd->q, req, + RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, + ril_netreg_register_cb, ril_netreg_cbd_free, + ril_netreg_cbd_new(nd, cb, data)); + grilio_request_unref(req); +} + +static int ril_netreg_get_signal_strength(const void *data, guint len) +{ + GRilIoParser rilp; + int gw_signal = 0, cdma_dbm = 0, evdo_dbm = 0, lte_signal = 0; + + grilio_parser_init(&rilp, data, len); + + /* RIL_SignalStrength_v6 */ + /* GW_SignalStrength */ + grilio_parser_get_int32(&rilp, &gw_signal); + grilio_parser_get_int32(&rilp, NULL); /* bitErrorRate */ + + /* CDMA_SignalStrength */ + grilio_parser_get_int32(&rilp, &cdma_dbm); + grilio_parser_get_int32(&rilp, NULL); /* ecio */ + + /* EVDO_SignalStrength */ + grilio_parser_get_int32(&rilp, &evdo_dbm); + grilio_parser_get_int32(&rilp, NULL); /* ecio */ + grilio_parser_get_int32(&rilp, NULL); /* signalNoiseRatio */ + + /* LTE_SignalStrength */ + grilio_parser_get_int32(&rilp, <e_signal); + grilio_parser_get_int32(&rilp, NULL); /* rsrp */ + grilio_parser_get_int32(&rilp, NULL); /* rsrq */ + grilio_parser_get_int32(&rilp, NULL); /* rssnr */ + grilio_parser_get_int32(&rilp, NULL); /* cqi */ + + DBG("gw: %d, cdma: %d, evdo: %d, lte: %d", gw_signal, cdma_dbm, + evdo_dbm, lte_signal); + + /* Return the first valid one */ + if (gw_signal != 99 && gw_signal != -1) { + return (gw_signal * 100) / 31; + } + + if (lte_signal != 99 && lte_signal != -1) { + return (lte_signal * 100) / 31; + } + + /* In case of dbm, return the value directly */ + if (cdma_dbm != -1) { + return MIN(cdma_dbm, 100); + } + + if (evdo_dbm != -1) { + return MIN(evdo_dbm, 100); + } + + return -1; +} + +static void ril_netreg_strength_notify(GRilIoChannel *io, guint ril_event, + const void *data, guint len, void *user_data) +{ + struct ril_netreg *nd = user_data; + int strength; + + GASSERT(ril_event == RIL_UNSOL_SIGNAL_STRENGTH); + strength = ril_netreg_get_signal_strength(data, len); + DBG("%d", strength); + ofono_netreg_strength_notify(nd->netreg, strength); +} + +static void ril_netreg_strength_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_netreg_cbd *cbd = user_data; + ofono_netreg_strength_cb_t cb = cbd->cb.strength; + struct ofono_error error; + + if (status == RIL_E_SUCCESS) { + int strength = ril_netreg_get_signal_strength(data, len); + cb(ril_error_ok(&error), strength, cbd->data); + } else { + ofono_error("Failed to retrive the signal strength: %s", + ril_error_to_string(status)); + cb(ril_error_failure(&error), -1, cbd->data); + } +} + +static void ril_netreg_strength(struct ofono_netreg *netreg, + ofono_netreg_strength_cb_t cb, void *data) +{ + struct ril_netreg *nd = ril_netreg_get_data(netreg); + + grilio_queue_send_request_full(nd->q, NULL, + RIL_REQUEST_SIGNAL_STRENGTH, ril_netreg_strength_cb, + ril_netreg_cbd_free, ril_netreg_cbd_new(nd, cb, data)); +} + +static void ril_netreg_nitz_notify(GRilIoChannel *io, guint ril_event, + const void *data, guint len, void *user_data) +{ + struct ril_netreg *nd = user_data; + GRilIoParser rilp; + int year, mon, mday, hour, min, sec, dst, tzi; + char tzs, tz[4]; + gchar *nitz; + + GASSERT(ril_event == RIL_UNSOL_NITZ_TIME_RECEIVED); + + grilio_parser_init(&rilp, data, len); + nitz = grilio_parser_get_utf8(&rilp); + + DBG("%s", nitz); + sscanf(nitz, "%u/%u/%u,%u:%u:%u%c%u,%u", &year, &mon, &mday, + &hour, &min, &sec, &tzs, &tzi, &dst); + snprintf(tz, sizeof(tz), "%c%d", tzs, tzi); + + nd->time.utcoff = atoi(tz) * 15 * 60; + nd->time.dst = dst; + nd->time.sec = sec; + nd->time.min = min; + nd->time.hour = hour; + nd->time.mday = mday; + nd->time.mon = mon; + nd->time.year = 2000 + year; + + ofono_netreg_time_notify(nd->netreg, &nd->time); + g_free(nitz); +} + +int ril_netreg_check_if_really_roaming(struct ofono_netreg *netreg, + gint status) +{ + /* These functions tolerate NULL argument */ + const char *net_mcc = ofono_netreg_get_mcc(netreg); + const char *net_mnc = ofono_netreg_get_mnc(netreg); + struct sim_spdi *spdi = ofono_netreg_get_spdi(netreg); + + if (spdi && net_mcc && net_mnc) { + if (sim_spdi_lookup(spdi, net_mcc, net_mnc)) { + ofono_info("voice reg: not roaming based on spdi"); + return NETWORK_REGISTRATION_STATUS_REGISTERED; + } + } + + return status; +} + +static gboolean ril_netreg_register(gpointer user_data) +{ + struct ril_netreg *nd = user_data; + + GASSERT(nd->timer_id); + nd->timer_id = 0; + ofono_netreg_register(nd->netreg); + + /* Register for network state changes */ + nd->event_id[NETREG_EVENT_VOICE_NETWORK_STATE_CHANGED] = + grilio_channel_add_unsol_event_handler(nd->io, + ril_netreg_network_state_change, + RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, nd); + + /* Register for network time update reports */ + nd->event_id[NETREG_EVENT_NITZ_TIME_RECEIVED] = + grilio_channel_add_unsol_event_handler(nd->io, + ril_netreg_nitz_notify, + RIL_UNSOL_NITZ_TIME_RECEIVED, nd); + + /* Register for signal strength changes */ + nd->event_id[NETREG_EVENT_SIGNAL_STRENGTH] = + grilio_channel_add_unsol_event_handler(nd->io, + ril_netreg_strength_notify, + RIL_UNSOL_SIGNAL_STRENGTH, nd); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +static int ril_netreg_probe(struct ofono_netreg *netreg, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_netreg *nd = g_new0(struct ril_netreg, 1); + + DBG("[%u] %p", ril_modem_slot(modem), netreg); + nd->io = grilio_channel_ref(ril_modem_io(modem)); + nd->q = grilio_queue_new(nd->io); + nd->netreg = netreg; + nd->tech = -1; + nd->time.sec = -1; + nd->time.min = -1; + nd->time.hour = -1; + nd->time.mday = -1; + nd->time.mon = -1; + nd->time.year = -1; + nd->time.dst = 0; + nd->time.utcoff = 0; + nd->corestatus = -1; + + ofono_netreg_set_data(netreg, nd); + nd->timer_id = g_idle_add(ril_netreg_register, nd); + return 0; +} + +static void ril_netreg_remove(struct ofono_netreg *netreg) +{ + int i; + struct ril_netreg *nd = ril_netreg_get_data(netreg); + + DBG("%p", netreg); + grilio_queue_cancel_all(nd->q, FALSE); + ofono_netreg_set_data(netreg, NULL); + + for (i=0; ievent_id); i++) { + grilio_channel_remove_handler(nd->io, nd->event_id[i]); + } + + if (nd->timer_id > 0) { + g_source_remove(nd->timer_id); + } + + grilio_channel_unref(nd->io); + grilio_queue_unref(nd->q); + g_free(nd); +} + +const struct ofono_netreg_driver ril_netreg_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_netreg_probe, + .remove = ril_netreg_remove, + .registration_status = ril_netreg_registration_status, + .current_operator = ril_netreg_current_operator, + .list_operators = ril_netreg_list_operators, + .register_auto = ril_netreg_register_auto, + .register_manual = ril_netreg_register_manual, + .strength = ril_netreg_strength +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_oem_raw.c b/ofono/drivers/ril/ril_oem_raw.c new file mode 100644 index 00000000..1ee6f994 --- /dev/null +++ b/ofono/drivers/ril/ril_oem_raw.c @@ -0,0 +1,137 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +struct ril_oem_raw { + GRilIoQueue *q; + guint timer_id; +}; + +struct ril_oem_raw_cbd { + ofono_oem_raw_query_cb_t cb; + gpointer data; +}; + +#define ril_oem_raw_cbd_free g_free + +static inline struct ril_oem_raw *ril_oem_raw_get_data( + struct ofono_oem_raw *raw) +{ + return ofono_oem_raw_get_data(raw); +} + +static struct ril_oem_raw_cbd *ril_oem_raw_cbd_new(ofono_oem_raw_query_cb_t cb, + void *data) +{ + struct ril_oem_raw_cbd *cbd = g_new0(struct ril_oem_raw_cbd, 1); + + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static void ril_oem_raw_request_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_oem_raw_cbd *cbd = user_data; + + if (status == RIL_E_SUCCESS) { + struct ofono_oem_raw_results result; + + result.data = (void *)data; + result.length = len; + cbd->cb(ril_error_ok(&error), &result, cbd->data); + } else { + DBG("error:%d len:%d ", status, len); + cbd->cb(ril_error_failure(&error), NULL, cbd->data); + } +} + +static void ril_oem_raw_request(struct ofono_oem_raw *raw, + const struct ofono_oem_raw_request *request, + ofono_oem_raw_query_cb_t cb, void *data) +{ + struct ril_oem_raw *od = ril_oem_raw_get_data(raw); + GRilIoRequest *req = grilio_request_sized_new(request->length); + + grilio_request_append_bytes(req, request->data, request->length); + grilio_queue_send_request_full(od->q, req, RIL_REQUEST_OEM_HOOK_RAW, + ril_oem_raw_request_cb, ril_oem_raw_cbd_free, + ril_oem_raw_cbd_new(cb, data)); + grilio_request_unref(req); +} + +static gboolean ril_oem_raw_register(gpointer user_data) +{ + struct ofono_oem_raw *raw = user_data; + struct ril_oem_raw *od = ril_oem_raw_get_data(raw); + + DBG(""); + GASSERT(od->timer_id); + od->timer_id = 0; + ofono_oem_raw_dbus_register(raw); + + /* Single-shot */ + return FALSE; +} + +static int ril_oem_raw_probe(struct ofono_oem_raw *raw, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_oem_raw *od = g_new0(struct ril_oem_raw, 1); + + DBG(""); + od->q = grilio_queue_new(ril_modem_io(modem)); + od->timer_id = g_idle_add(ril_oem_raw_register, raw); + ofono_oem_raw_set_data(raw, od); + return 0; +} + +static void ril_oem_raw_remove(struct ofono_oem_raw *raw) +{ + struct ril_oem_raw *od = ril_oem_raw_get_data(raw); + + DBG(""); + grilio_queue_cancel_all(od->q, TRUE); + ofono_oem_raw_set_data(raw, NULL); + + if (od->timer_id) { + g_source_remove(od->timer_id); + } + + grilio_queue_unref(od->q); + g_free(od); +} + +/* const */ struct ofono_oem_raw_driver ril_oem_raw_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_oem_raw_probe, + .remove = ril_oem_raw_remove, + .request = ril_oem_raw_request, +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_phonebook.c b/ofono/drivers/ril/ril_phonebook.c new file mode 100644 index 00000000..9da529f9 --- /dev/null +++ b/ofono/drivers/ril/ril_phonebook.c @@ -0,0 +1,1026 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "util.h" +#include "simutil.h" + +/* File info parameters */ +#define FCP_TEMPLATE 0x62 +#define FCP_FILE_SIZE 0x80 +#define FCP_FILE_DESC 0x82 +#define FCP_FILE_ID 0x83 +#define FCP_FILE_LIFECYCLE 0x8A +#define FCP_FILE_SECURITY_ARR 0x8B +#define FCP_FILE_SECURITY_COMPACT 0x8C +#define FCP_FILE_SECURITY_EXPANDED 0xAB + +#define SIM_EFPBR_FILEID 0x4F30 + +#define UNUSED 0xff + +#define EXT1_CP_SUBADDRESS 1 +#define EXT1_ADDITIONAL_DATA 2 + +#define NAME_SIZE 64 +#define NUMBER_SIZE 256 +#define EMAIL_SIZE 128 +#define EXT_NUMBER_SIZE 24 +#define SNE_SIZE 64 + +/* TON (Type Of Number) See TS 24.008 */ +#define TON_MASK 0x70 +#define TON_INTERNATIONAL 0x10 + +enum constructed_tag { + TYPE_1_TAG = 0xA8, + TYPE_2_TAG = 0xA9, + TYPE_3_TAG = 0xAA +}; + +enum file_type_tag { + TYPE_ADN = 0xC0, + TYPE_IAD = 0xC1, + TYPE_EXT1 = 0xC2, + TYPE_SNE = 0xC3, + TYPE_ANR = 0xC4, + TYPE_PBC = 0xC5, + TYPE_GPR = 0xC6, + TYPE_AAS = 0xC7, + TYPE_GAS = 0xC8, + TYPE_UID = 0xC9, + TYPE_EMAIL = 0xCA, + TYPE_CCP1 = 0xCB +}; + +struct ril_phonebook_file { + int file_id; + guchar file_type; + guchar structure; + int file_length; + int record_length; + int record; + gboolean handled; +}; + +struct ril_phonebook_entry { + int entry; + char *name; + char *number; + char *email; + char *anr; + char *sne; +}; + +struct ril_phonebook { + int refcount; + GRilIoQueue *q; + struct ril_modem *modem; + struct ofono_phonebook *pb; + guint timer_id; +}; + +struct ril_phonebook_export { + struct ril_phonebook *pbd; + int app_type; + ofono_phonebook_cb_t cb; + gpointer data; + gint pb_entry; + struct ril_phonebook_file ref_file_info; + struct ril_phonebook_file *ext_file_info; + struct ril_phonebook_file *extension_file_info; /* NEEDED? */ + struct ril_phonebook_file *current_file_info; + GSList *pb_files; + GSList *pb_next; + GSList *pb_entries; + guchar ext1_to_type; + guchar ext1_to_entry; +}; + +static const guchar sim_path[4] = {0x3F, 0x00, 0x7F, 0x10}; +static const guchar usim_path[6] = {0x3F, 0x00, 0x7F, 0x10, 0x5F, 0x3A}; + +static void ril_phonebook_content_data_read(struct ril_phonebook_export *exp, + struct ril_phonebook_file *file_info); + +static inline struct ril_phonebook *ril_phonebook_get_data( + struct ofono_phonebook *pb) +{ + return ofono_phonebook_get_data(pb); +} + +static void ril_phonebook_cancel_io(struct ril_phonebook *pbd) +{ + if (pbd->timer_id) { + g_source_remove(pbd->timer_id); + pbd->timer_id = 0; + } + grilio_queue_cancel_all(pbd->q, FALSE); +} + +static void ril_phonebook_free(struct ril_phonebook *pbd) +{ + ril_phonebook_cancel_io(pbd); + grilio_queue_unref(pbd->q); + g_free(pbd); +} + +static inline struct ril_phonebook *ril_phonebook_ref(struct ril_phonebook *pbd) +{ + GASSERT(pbd->refcount > 0); + pbd->refcount++; + return pbd; +} + +static inline void ril_phonebook_unref(struct ril_phonebook *pbd) +{ + GASSERT(pbd); + GASSERT(pbd->refcount > 0); + if (!(--pbd->refcount)) { + ril_phonebook_free(pbd); + } +} + +static struct ril_phonebook_export *ril_phonebook_export_new( + struct ril_phonebook *pbd, int app_type, + ofono_phonebook_cb_t cb, void *data) +{ + struct ril_phonebook_export *exp = + g_new0(struct ril_phonebook_export, 1); + + exp->pbd = ril_phonebook_ref(pbd); + exp->app_type = app_type; + exp->cb = cb; + exp->data = data; + return exp; +} + +static void ril_phonebook_entry_free(gpointer data) +{ + struct ril_phonebook_entry *entry = data; + + g_free(entry->number); + g_free(entry->name); + g_free(entry->anr); + g_free(entry->sne); + g_free(entry->email); + g_free(entry); +} + +static void ril_phonebook_export_done(struct ril_phonebook_export *exp, + int type) +{ + /* Don't invoke completion callback if phonebook is already gone */ + if (exp->cb && exp->pbd->pb) { + struct ofono_error error; + error.error = 0; + error.type = type; + exp->cb(&error, exp->data); + } + + g_free(exp->extension_file_info); + g_free(exp->current_file_info); + g_slist_free_full(exp->pb_files, g_free); + g_slist_free_full(exp->pb_entries, ril_phonebook_entry_free); + ril_phonebook_unref(exp->pbd); + g_free(exp); +} + +static inline void ril_phonebook_export_ok(struct ril_phonebook_export *exp) +{ + DBG(""); + ril_phonebook_export_done(exp, OFONO_ERROR_TYPE_NO_ERROR); +} + +static inline void ril_phonebook_export_error(struct ril_phonebook_export *exp) +{ + DBG(""); + ril_phonebook_export_done(exp, OFONO_ERROR_TYPE_FAILURE); +} + +/* + * BCD to utf8 conversion. See table 4.4 in TS 31.102. + * BCD 0x0C indicates pause before sending following digits as DTMF tones. + * BCD 0x0D is a wildcard that means "any digit" + * BCD 0x0E is reserved, we convert it to 'e' (why not?). + */ +static void ril_phonebook_bcd_to_utf8(char *utf8, const guchar *bcd, guint len) +{ + static const char digit_to_utf8[] = "0123456789*#pwe\0"; + guint i; + + for (i = 0; i < len; i++) { + utf8[2*i] = digit_to_utf8[bcd[i] & 0x0f]; + utf8[2*i + 1] = digit_to_utf8[(bcd[i] >> 4) & 0x0f]; + } + + utf8[2*i] = 0; +} + +static void ril_phonebook_create_entry(gpointer data, gpointer user_data) +{ + struct ril_phonebook_entry *pbe = data; + struct ril_phonebook *pbd = user_data; + + if (pbd->pb) { + if ((pbe->name && pbe->name[0]) || + (pbe->number && pbe->number[0]) || + (pbe->email && pbe->email[0]) || + (pbe->anr && pbe->anr[0]) || + (pbe->sne && pbe->sne[0])) { + DBG("vCard: name=%s number=%s email=%s anr=%s sne=%s", + pbe->name, pbe->number, pbe->email, + pbe->anr, pbe->sne); + ofono_phonebook_entry(pbd->pb, -1, pbe->number, -1, + pbe->name, -1, NULL, pbe->anr, -1, + pbe->sne, pbe->email, NULL, NULL); + } + } +} + +static void ril_phonebook_create_entries(struct ril_phonebook_export *exp) +{ + DBG("All data requested, start vCard creation"); + g_slist_foreach(exp->pb_entries, ril_phonebook_create_entry, exp->pbd); + DBG("Finally all PB data read"); +} + +static void ril_phonebook_handle_adn(struct ril_phonebook_export *exp, + const guchar *msg, size_t len) +{ + guchar name_length; + guchar number_start; + guchar number_length = 0; + guchar extension_record = UNUSED; + guchar prefix; + char *number = NULL; + char *name; + + if (len < 14) { + return; + } + + name_length = len - 14; + number_start = name_length; + + name = sim_string_to_utf8(msg, name_length); + /* Length contains also TON&NPI */ + number_length = msg[number_start]; + + if (number_length != UNUSED && number_length != 0) { + number = g_malloc(NUMBER_SIZE); + number_length--; + prefix = 0; + + if ((msg[number_start + 1] & TON_MASK) == TON_INTERNATIONAL) { + number[0] = '+'; + prefix = 1; + } + + ril_phonebook_bcd_to_utf8(number + prefix, + msg + number_start + 2, + number_length); + extension_record = msg[len - 1]; + } + + DBG("ADN name %s, number %s ", name, number); + DBG("length %d extension_record %d", number_length, extension_record); + + /* THE PURPOSE OF THIS CODE WAS UNCLEAR + if (extension_record != UNUSED) { + next_file = g_try_new0(struct ril_phonebook_file, 1); + if (next_file) { + if (pbd->extension_file_info) { + memmove(next_file, + pbd-> + extension_file_info, + sizeof(struct + pb_file_info)); + } else { + next_file->file_type = + TYPE_EXT1; + next_file->file_id = + SIM_EFEXT1_FILEID; + } + + next_file->record = extension_record; + pbd->ext1_to_type = TYPE_ADN; + pbd->ext1_to_entry = pbd->pb_entry; + } + } + */ + + if (name || number) { + struct ril_phonebook_entry *new_entry = + g_new0(struct ril_phonebook_entry, 1); + + new_entry->name = name; + new_entry->number = number; + + DBG("Creating PB entry %d with name %s number %s", + exp->pb_entry, new_entry->name, new_entry->number); + + exp->pb_entries = g_slist_append(exp->pb_entries, new_entry); + exp->pb_entry++; + } +} + +static void ril_phonebook_handle_sne(struct ril_phonebook_export *exp, + const guchar *msg, size_t len) +{ + guchar sne_length; + guchar entry_nbr; + char *sne; + + DBG("SNE"); + + if (len < 2) { + return; + } + + sne_length = len - 2; + entry_nbr = msg[len - 1]; + + sne = sim_string_to_utf8(msg, sne_length); + + if (sne) { + /* GSlist nth counts from 0, PB entries from 1 */ + GSList *list_entry = g_slist_nth(exp->pb_entries, entry_nbr-1); + DBG("SNE \'%s\' to PB entry %d", sne, entry_nbr); + + if (list_entry) { + struct ril_phonebook_entry *entry = + list_entry->data; + + DBG("Adding SNE to entry %d", entry_nbr); + DBG("name %s", entry->name); + + g_free(entry->sne); + entry->sne = sne; + } else { + g_free(sne); + } + } +} + +static void ril_phonebook_handle_anr(struct ril_phonebook_export *exp, + const guchar *msg, size_t len) +{ + guchar number_length = 0; + guchar extension_record = UNUSED; + guchar aas_record = UNUSED; + guchar prefix; + guchar entry_nbr; + char* anr = NULL; + + DBG("ANR"); + + if (len < 1 || msg[0] == UNUSED) { + return; + } + + entry_nbr = msg[len - 1]; + aas_record = msg[0]; + /* Length contains also TON&NPI */ + number_length = msg[1]; + + if (number_length) { + number_length--; + anr = g_malloc0(NUMBER_SIZE); + prefix = 0; + + if ((msg[2] & TON_MASK) == TON_INTERNATIONAL) { + anr[0] = '+'; + prefix = 1; + } + + ril_phonebook_bcd_to_utf8(anr + prefix, msg + 3, number_length); + extension_record = msg[len - 3]; + } + + DBG("ANR to entry %d number %s number length %d", entry_nbr, anr, + number_length); + DBG("extension_record %d aas %d", extension_record, aas_record); + + /* THE PURPOSE OF THIS CODE WAS UNCLEAR + if (extension_record != UNUSED) { + next_file = g_new0(struct ril_phonebook_file, 1); + + if (pbd->extension_file_info) { + memmove(next_file, pbd-> extension_file_info, + sizeof(struct ril_phonebook_file)); + } else { + next_file->file_type = TYPE_EXT1; + next_file->file_id = SIM_EFEXT1_FILEID; + } + + next_file->record = extension_record; + pbd->ext1_to_type = TYPE_ANR; + pbd->ext1_to_entry = phonebook_entry_nbr; + } + */ + + if (anr) { + /* GSlist nth counts from 0, PB entries from 1 */ + GSList *list_entry = g_slist_nth(exp->pb_entries, entry_nbr-1); + + if (list_entry) { + struct ril_phonebook_entry *entry = list_entry->data; + if (entry) { + /* if one already exists, delete it */ + g_free(entry->anr); + DBG("Adding ANR to entry %d, name %s", + entry_nbr, entry->name); + entry->anr = anr; + } + } else { + g_free(anr); + } + } +} + +static void ril_phonebook_handle_email(struct ril_phonebook_export *exp, + const guchar *msg, size_t len) +{ + char *email; + guchar entry_nbr; + + if (len < 1) + return; + + entry_nbr = msg[len - 1]; + email = sim_string_to_utf8(msg, len - 2); + + if (email) { + /* GSlist nth counts from 0, PB entries from 1 */ + GSList *list_entry = g_slist_nth(exp->pb_entries, entry_nbr-1); + + DBG("Email \'%s\' to PB entry %d", email, entry_nbr); + if (list_entry) { + struct ril_phonebook_entry *entry = list_entry->data; + + /* if one already exists, delete it */ + if (entry) { + g_free(entry->email); + DBG("Adding email to entry %d", entry_nbr); + DBG("name %s", entry->name); + entry->email = email; + } + } else { + g_free(email); + } + } +} + +static void ril_phonebook_handle_ext1(struct ril_phonebook_export *exp, + const unsigned char *msg) +{ + char *ext_number = g_malloc0(EXT_NUMBER_SIZE); + guchar next_extension_record, number_length = msg[1]; + + ril_phonebook_bcd_to_utf8(ext_number, msg, number_length); + next_extension_record = msg[number_length + 2]; + + DBG("Number extension %s", ext_number); + DBG("number length %d", number_length); + DBG("extension_record %d", next_extension_record); + + /* pb_entry is already incremented & g_slist_nth counts from 0 */ + if (exp->ext1_to_type == TYPE_ADN) { + GSList *entry = g_slist_nth(exp->pb_entries, + exp->ext1_to_entry-1); + DBG("Looking for ADN entry %d", exp->ext1_to_entry); + if (entry) { + struct ril_phonebook_entry *pb_entry = entry->data; + if (pb_entry) { + strcat(pb_entry->number, ext_number); + } + } + } else if (exp->ext1_to_type == TYPE_ANR) { + GSList *entry = g_slist_nth(exp->pb_entries, + exp->ext1_to_entry-1); + DBG("Looking for ANR entry %d", exp->ext1_to_entry); + if (entry) { + struct ril_phonebook_entry *pb_entry = entry->data; + if (pb_entry) { + strcat(pb_entry->anr, ext_number); + } + } + } + + g_free(ext_number); + + /* THE PURPOSE OF THIS CODE WAS UNCLEAR + if (next_extension_record != UNUSED) { + next_file = g_new0(struct ril_phonebook_file, 1); + if (exp->ext_file_info) { + *next_file = *exp->ext_file_info; + } else { + next_file->file_type = TYPE_EXT1; + next_file->file_id = SIM_EFEXT1_FILEID; + } + next_file->record = next_extension_record; + } + */ +} + +static void ril_phonebook_decode_response(struct ril_phonebook_export *exp, + guchar file_type, const guchar *msg, size_t len) +{ + switch (file_type) { + case TYPE_ADN: + ril_phonebook_handle_adn(exp, msg, len); + break; + case TYPE_SNE: + ril_phonebook_handle_sne(exp, msg, len); + break; + case TYPE_ANR: + ril_phonebook_handle_anr(exp, msg, len); + break; + case TYPE_AAS: + DBG("AAS"); + break; + case TYPE_EMAIL: + ril_phonebook_handle_email(exp, msg, len); + break; + case TYPE_EXT1: + DBG("EXT1 to type=%02X, entry=%d", exp->ext1_to_type, + exp->ext1_to_entry); + if (msg[0] == EXT1_ADDITIONAL_DATA) { + ril_phonebook_handle_ext1(exp, msg); + } + break; + default: + DBG("Skipping type %02X", file_type); + break; + } +} + +static void pb_adn_sim_data_cb(const struct ofono_error *error, + const unsigned char *sdata, int length, void *data) +{ + struct ril_phonebook_export *exp = data; + struct ofono_sim *sim = ril_modem_ofono_sim(exp->pbd->modem); + struct ril_phonebook_file *file_info = exp->current_file_info; + + DBG(""); + GASSERT(file_info); + if (error->type != OFONO_ERROR_TYPE_NO_ERROR || !exp->pbd->pb || + !sim || !file_info) { + ril_phonebook_export_error(exp); + return; + } + + ril_phonebook_decode_response(exp, exp->extension_file_info ? + exp->extension_file_info->file_type : file_info->file_type, + sdata, length); + + /* APPARENTLY THIS CODE NEVER WORKED + if (file_info) { + DBG("Reading extension file %04X, record %d", + file_info->file_id, file_info->record); + ril_sim_read_file_linear(sim, file_info->file_id, + file_info->record, + file_info->record_length, + sim_path, sizeof(sim_path), + pb_adn_sim_data_cb, cbd_outer); + + g_free(extension_file_info); + extension_file_info = file_info; + return; + } + */ + + g_free(exp->extension_file_info); + exp->extension_file_info = NULL; + + if (file_info->record < + (file_info->file_length / file_info->record_length)) { + + file_info->record++; + DBG("Same file, next record %d", file_info->record); + ril_sim_read_file_linear(sim, file_info->file_id, + file_info->record, file_info->record_length, + sim_path, sizeof(sim_path), + pb_adn_sim_data_cb, exp); + } else { + ril_phonebook_create_entries(exp); + ril_phonebook_export_ok(exp); + } +} + +static void ril_phonebook_adn_sim_info_cb(const struct ofono_error *error, + int filelength, enum ofono_sim_file_structure structure, + int recordlength, const unsigned char access[3], + unsigned char file_status, void *data) +{ + struct ril_phonebook_export *exp = data; + struct ofono_sim *sim = ril_modem_ofono_sim(exp->pbd->modem); + int records; + + DBG(""); + if (error->type == OFONO_ERROR_TYPE_NO_ERROR && + structure == OFONO_SIM_FILE_STRUCTURE_FIXED && + exp->pbd->pb && sim && recordlength && + (records = filelength / recordlength) > 0) { + struct ril_phonebook_file *info; + + if (!exp->current_file_info) { + exp->current_file_info = + g_new0(struct ril_phonebook_file, 1); + } + + info = exp->current_file_info; + info->file_id = SIM_EFADN_FILEID; + info->file_type = TYPE_ADN; + info->structure = structure; + info->file_length = filelength; + info->record_length = recordlength; + info->record = 1; + + ril_sim_read_file_linear(sim, info->file_id, + info->record, info->record_length, + sim_path, sizeof(sim_path), + pb_adn_sim_data_cb, exp); + } else { + ril_phonebook_export_error(exp); + } +} + +static gboolean ril_phonebook_file_supported( + const struct ril_phonebook_file *file) +{ + if (file) { + switch (file->file_type) { + case TYPE_ADN: + case TYPE_EMAIL: + case TYPE_SNE: + case TYPE_ANR: + return TRUE; + default: + return FALSE; + } + } + return FALSE; +} + +static void ril_phonebook_content_data_cb(const struct ofono_error *error, + const unsigned char *sdata, + int length, void *data) +{ + struct ril_phonebook_export *exp = data; + struct ril_phonebook_file *file_info = exp->pb_next->data; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR || !exp->pbd->pb) { + ril_phonebook_export_error(exp); + return; + } + + ril_phonebook_decode_response(exp, exp->extension_file_info ? + exp->extension_file_info->file_type : file_info->file_type, + sdata, length); + + /* APPARENTLY THIS CODE NEVER WORKED + if (file_info) { + DBG("Reading extension file %04X, record %d, structure %d", + file_info->file_id, file_info->record, + file_info->structure); + ril_phonebook_content_data_read(exp, file_info); + g_free(extension_file_info); + extension_file_info = file_info; + return; + } + */ + + g_free(exp->extension_file_info); + exp->extension_file_info = NULL; + + if (((file_info->structure == OFONO_SIM_FILE_STRUCTURE_FIXED) || + (file_info->structure == OFONO_SIM_FILE_STRUCTURE_CYCLIC)) && + (file_info->record < + (file_info->file_length / file_info->record_length))) { + file_info->record++; + DBG("Same file, next record %d", file_info->record); + } else { + DBG("Next file in list"); + if ((exp->pb_next = g_slist_next(exp->pb_next)) != NULL && + !ril_phonebook_file_supported(exp->pb_next->data)) { + file_info = exp->pb_next->data; + DBG("Skipping file type %02X", file_info->file_type); + exp->pb_next = g_slist_next(exp->pb_next); + } + + if (!exp->pb_next) { + ril_phonebook_create_entries(exp); + ril_phonebook_export_ok(exp); + return; + } + + file_info = exp->pb_next->data; + } + + ril_phonebook_content_data_read(exp, file_info); +} + +static void ril_phonebook_content_data_read(struct ril_phonebook_export *exp, + struct ril_phonebook_file *file) +{ + struct ofono_sim* sim = ril_modem_ofono_sim(exp->pbd->modem); + + if (exp->pbd->pb && sim) { + DBG("Reading content type=%02X, file ID=%04X, structure=%d", + file->file_type, file->file_id, + file->structure); + + switch (file->structure) { + case OFONO_SIM_FILE_STRUCTURE_FIXED: + ril_sim_read_file_linear(sim, file->file_id, + file->record, file->record_length, + usim_path, sizeof(usim_path), + ril_phonebook_content_data_cb, exp); + return; + case OFONO_SIM_FILE_STRUCTURE_CYCLIC: + ril_sim_read_file_cyclic(sim, file->file_id, + file->record, file->record_length, NULL, 0, + ril_phonebook_content_data_cb, exp); + return; + case OFONO_SIM_FILE_STRUCTURE_TRANSPARENT: + ril_sim_read_file_transparent(sim, file->file_id, 0, + file->file_length, usim_path, sizeof(usim_path), + ril_phonebook_content_data_cb, exp); + return; + } + } + + ril_phonebook_export_error(exp); +} + +static void ril_phonebook_content_info_cb(const struct ofono_error *error, + int filelength, enum ofono_sim_file_structure structure, + int recordlength, const unsigned char access[3], + unsigned char file_status, void *data) +{ + struct ril_phonebook_export *exp = data; + struct ofono_sim* sim = ril_modem_ofono_sim(exp->pbd->modem); + struct ril_phonebook_file *file; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR || !exp->pbd->pb || !sim) { + ril_phonebook_export_error(exp); + return; + } + + file = exp->pb_next->data; + file->structure = structure; + file->file_length = filelength; + file->record_length = recordlength; + file->record = 1; + + DBG("File type=%02X, File ID=%04X, Struct=%d, File len=%d, Rec len=%d", + file->file_type, file->file_id, file->structure, + file->file_length, file->record_length); + + if (file->file_type == TYPE_EXT1) { + exp->ext_file_info = file; + } + + exp->pb_next = g_slist_next(exp->pb_next); + if (exp->pb_next) { + file = exp->pb_next->data; + DBG("Reading next content info %04X", file->file_id); + ril_sim_read_file_info(sim, file->file_id, + usim_path, sizeof(usim_path), + ril_phonebook_content_info_cb, exp); + } else { + DBG("All info requested, start content reading"); + + /* Re-start from beginning */ + exp->pb_next = exp->pb_files; + file = exp->pb_next->data; + + DBG("content_data_read type=%02X", file->file_type); + ril_phonebook_content_data_read(exp, file); + } +} + +static void ril_phonebook_reference_data_cb(const struct ofono_error *error, + const unsigned char *sdata, int length, void *data) +{ + struct ril_phonebook_export *exp = data; + struct ril_phonebook_file* ref = &exp->ref_file_info; + struct ofono_sim* sim = ril_modem_ofono_sim(exp->pbd->modem); + const guchar *ptr = sdata; + gboolean finished = FALSE; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR || !exp->pbd->pb || !sim) { + ril_phonebook_export_error(exp); + return; + } + + while ((ptr < sdata + length) && !finished) { + int i, typelen; + switch (ptr[0]) { + case TYPE_1_TAG: + case TYPE_2_TAG: + case TYPE_3_TAG: + typelen = ptr[1]; + DBG("File type=%02X, len=%d", ptr[0], typelen); + ptr += 2; + for (i = 0; i < typelen; i += ptr[i+1] + 2) { + struct ril_phonebook_file *file = + g_new0(struct ril_phonebook_file, 1); + + file->file_type = ptr[i]; + file->file_id = (ptr[i+2] << 8) + ptr[i+3]; + DBG("Creating file info type=%02X id=%04X", + file->file_type, file->file_id); + exp->pb_files = g_slist_append(exp->pb_files, + file); + } + ptr += typelen; + break; + + default: + DBG("All handled %02x", *ptr); + finished = TRUE; + break; + } + } + + if (ref->record < (ref->file_length/ref->record_length)) { + ref->record++; + DBG("Next EFpbr record %d", ref->record); + switch (exp->app_type) { + case RIL_APPTYPE_SIM: + ril_sim_read_file_linear(sim, ref->file_id, + ref->record, ref->record_length, + sim_path, sizeof(sim_path), + ril_phonebook_reference_data_cb, exp); + return; + case RIL_APPTYPE_USIM: + ril_sim_read_file_linear(sim, ref->file_id, + ref->record, ref->record_length, + usim_path, sizeof(usim_path), + ril_phonebook_reference_data_cb, exp); + return; + default: + break; + } + } else { + DBG("All EFpbr records read"); + exp->pb_next = exp->pb_files; + if (exp->pb_next) { + struct ril_phonebook_file *file = exp->pb_next->data; + ril_sim_read_file_info(sim, file->file_id, + usim_path, sizeof(usim_path), + ril_phonebook_content_info_cb, exp); + return; + } else { + ril_phonebook_export_ok(exp); + } + } + + ril_phonebook_export_error(exp); +} + +static void ril_phonebook_reference_info_cb(const struct ofono_error *error, + int filelength, enum ofono_sim_file_structure structure, + int recordlength, const unsigned char access[3], + unsigned char file_status, void *data) +{ + + struct ril_phonebook_export *exp = data; + struct ofono_sim* sim = ril_modem_ofono_sim(exp->pbd->modem); + int records; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR || + structure != OFONO_SIM_FILE_STRUCTURE_FIXED || + !exp->pbd->pb || !sim || !recordlength) { + ril_phonebook_export_error(exp); + return; + } + + records = filelength / recordlength; + if (records) { + struct ril_phonebook_file* ref = &exp->ref_file_info; + + DBG("EFpbr size %d, record length %d, records %d", + filelength, recordlength, records); + ref->file_id = SIM_EFPBR_FILEID; + ref->file_length = filelength; + ref->record_length = recordlength; + ref->record = 1; /* Current record, not amount */ + ref->structure = OFONO_SIM_FILE_STRUCTURE_FIXED; + ril_sim_read_file_linear(sim, SIM_EFPBR_FILEID, + 1, recordlength, usim_path, sizeof(usim_path), + ril_phonebook_reference_data_cb, exp); + } else { + ril_phonebook_export_error(exp); + } + +} + +static void ril_phonebook_export_entries(struct ofono_phonebook *pb, + const char *storage, ofono_phonebook_cb_t cb, void *data) +{ + struct ril_phonebook *pbd = ril_phonebook_get_data(pb); + struct ofono_sim *sim = ril_modem_ofono_sim(pbd->modem); + struct ofono_error error; + + DBG("Storage %s", storage); + + /* Only for SIM memory */ + if (!strcmp(storage, "SM")) { + const int type = ril_sim_app_type(sim); + switch (type) { + case RIL_APPTYPE_SIM: + DBG("SIM application"); + ril_sim_read_file_info(sim, SIM_EFADN_FILEID, + sim_path, sizeof(sim_path), + ril_phonebook_adn_sim_info_cb, + ril_phonebook_export_new(pbd, type, cb, data)); + return; + case RIL_APPTYPE_USIM: + DBG("USIM application"); + ril_sim_read_file_info(sim, SIM_EFPBR_FILEID, + usim_path, sizeof(usim_path), + ril_phonebook_reference_info_cb, + ril_phonebook_export_new(pbd, type, cb, data)); + return; + default: + DBG("Unsupported UICC application type %d", type); + break; + } + } + + cb(ril_error_failure(&error), data); +} + +static gboolean ril_phonebook_register(gpointer user_data) +{ + struct ril_phonebook *pbd = user_data; + + pbd->timer_id = 0; + ofono_phonebook_register(pbd->pb); + + /* Single shot */ + return FALSE; +} + +static int ril_phonebook_probe(struct ofono_phonebook *pb, + unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_phonebook *pbd = g_new0(struct ril_phonebook, 1); + + DBG(""); + pbd->refcount = 1; + pbd->modem = modem; + pbd->pb = pb; + pbd->q = grilio_queue_new(ril_modem_io(modem)); + + pbd->timer_id = g_idle_add(ril_phonebook_register, pbd); + ofono_phonebook_set_data(pb, pbd); + return 0; +} + +static void ril_phonebook_remove(struct ofono_phonebook *pb) +{ + struct ril_phonebook *pbd = ril_phonebook_get_data(pb); + DBG(""); + ril_phonebook_cancel_io(pbd); + pbd->modem = NULL; + pbd->pb = NULL; + ofono_phonebook_set_data(pb, NULL); + ril_phonebook_unref(pbd); +} + +const struct ofono_phonebook_driver ril_phonebook_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_phonebook_probe, + .remove = ril_phonebook_remove, + .export_entries = ril_phonebook_export_entries +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_plugin.c b/ofono/drivers/ril/ril_plugin.c new file mode 100644 index 00000000..5b64dab2 --- /dev/null +++ b/ofono/drivers/ril/ril_plugin.c @@ -0,0 +1,1063 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_mce.h" +#include "ril_constants.h" +#include "ril_util.h" +#include "ril_log.h" + +#include +#include +#include +#include +#include +#include + +#define OFONO_API_SUBJECT_TO_CHANGE +#include +#include "ofono.h" +#include "storage.h" + +#define RADIO_GID 1001 +#define RADIO_UID 1001 +#define RIL_SUB_SIZE 4 + +#define RILMODEM_CONF_FILE "/etc/ofono/ril_subscription.conf" +#define RILMODEM_DEFAULT_SOCK "/dev/socket/rild" +#define RILMODEM_DEFAULT_SOCK2 "/dev/socket/rild2" +#define RILMODEM_DEFAULT_SUB "SUB1" +#define RILMODEM_DEFAULT_4G TRUE /* 4G is on by default */ +#define RILMODEM_DEFAULT_SLOT 0xffffffff +#define RILMODEM_DEFAULT_TIMEOUT 0 /* No timeout */ + +#define RILCONF_DEV_PREFIX "ril_" +#define RILCONF_PATH_PREFIX "/" RILCONF_DEV_PREFIX +#define RILCONF_NAME "name" +#define RILCONF_SOCKET "socket" +#define RILCONF_SLOT "slot" +#define RILCONF_SUB "sub" +#define RILCONF_TIMEOUT "timeout" +#define RILCONF_4G "enable4G" + +#define RIL_STORE "ril" +#define RIL_STORE_GROUP "Settings" +#define RIL_STORE_ENABLED_SLOTS "EnabledSlots" +#define RIL_STORE_DEFAULT_VOICE_SIM "DefaultVoiceSim" +#define RIL_STORE_DEFAULT_DATA_SIM "DefaultDataSim" +#define RIL_STORE_SLOTS_SEP "," + +struct ril_plugin_priv { + struct ril_plugin pub; + struct ril_plugin_dbus *dbus; + GSList *slots; + struct ril_modem *data_modem; + char *default_voice_imsi; + char *default_data_imsi; + char *default_voice_path; + char *default_data_path; + GKeyFile *storage; +}; + +struct ril_slot { + char *path; + char *name; + char *sockpath; + char *sub; + gint timeout; /* RIL timeout, in seconds */ + struct ril_modem_config config; + + struct ril_plugin_priv *plugin; + struct ril_sim_dbus *sim_dbus; + struct ril_modem *modem; + struct ril_mce *mce; + struct ofono_sim *sim; + GRilIoChannel *io; + guint trace_id; + guint dump_id; + guint error_id; + guint eof_id; + guint retry_id; + guint sim_watch_id; + guint sim_state_watch_id; + enum ofono_sim_state sim_state; +}; + +static void ril_debug_trace_notify(struct ofono_debug_desc *desc); +static void ril_debug_dump_notify(struct ofono_debug_desc *desc); +static void ril_plugin_create_modem(struct ril_slot *slot); + +GLOG_MODULE_DEFINE("rilmodem"); + +static struct ofono_debug_desc ril_debug_trace OFONO_DEBUG_ATTR = { + .name = "ril_trace", + .flags = OFONO_DEBUG_FLAG_DEFAULT, + .notify = ril_debug_trace_notify +}; +static struct ofono_debug_desc ril_debug_dump OFONO_DEBUG_ATTR = { + .name = "ril_dump", + .flags = OFONO_DEBUG_FLAG_DEFAULT, + .notify = ril_debug_dump_notify +}; + +static inline struct ril_plugin_priv *ril_plugin_cast(struct ril_plugin *pub) +{ + return G_CAST(pub, struct ril_plugin_priv, pub); +} + +static void ril_plugin_foreach_slot_proc(gpointer data, gpointer user_data) +{ + void (*fn)(struct ril_slot *) = user_data; + fn((struct ril_slot *)data); +} + +static void ril_plugin_foreach_slot(struct ril_plugin_priv *plugin, + void (*fn)(struct ril_slot *)) +{ + g_slist_foreach(plugin->slots, ril_plugin_foreach_slot_proc, fn); +} + +static gboolean ril_plugin_retry(gpointer data) +{ + struct ril_slot *slot = data; + + slot->retry_id = 0; + ril_plugin_create_modem(slot); + return FALSE; +} + +static void ril_plugin_shutdown_slot(struct ril_slot *slot, gboolean kill_io) +{ + if (slot->sim) { + if (slot->sim_state_watch_id) { + ofono_sim_remove_state_watch(slot->sim, + slot->sim_state_watch_id); + } + slot->sim = NULL; + } + + if (slot->modem) { + struct ofono_modem *m = ril_modem_ofono_modem(slot->modem); + + if (m && slot->sim_watch_id) { + __ofono_modem_remove_atom_watch(m, slot->sim_watch_id); + } + + ril_modem_delete(slot->modem); + /* The above call is expected to result in + * ril_plugin_modem_removed getting called + * which will set slot->modem to NULL */ + GASSERT(!slot->modem); + } + + /* All watches have to be unregistered by now */ + GASSERT(!slot->sim_state_watch_id); + GASSERT(!slot->sim_watch_id); + + if (kill_io) { + if (slot->mce) { + ril_mce_free(slot->mce); + slot->mce = NULL; + } + + if (slot->io) { + grilio_channel_remove_logger(slot->io, slot->trace_id); + grilio_channel_remove_logger(slot->io, slot->dump_id); + slot->trace_id = 0; + slot->dump_id = 0; + + grilio_channel_remove_handler(slot->io, slot->error_id); + grilio_channel_remove_handler(slot->io, slot->eof_id); + slot->error_id = 0; + slot->eof_id = 0; + + grilio_channel_shutdown(slot->io, FALSE); + grilio_channel_unref(slot->io); + slot->io = NULL; + } + } +} + +static void ril_pligin_set_config_string(struct ril_plugin_priv *plugin, + const char *key, const char *value, gboolean sync) +{ + if (value) { + g_key_file_set_string(plugin->storage, RIL_STORE_GROUP, key, + value); + } else { + g_key_file_remove_key(plugin->storage, RIL_STORE_GROUP, key, + NULL); + } + if (sync) { + storage_sync(NULL, RIL_STORE, plugin->storage); + } +} + +static struct ril_slot *ril_plugin_find_slot_imsi(GSList *slots, + const char *imsi) +{ + struct ril_slot *default_slot = NULL; + + while (slots) { + struct ril_slot *slot = slots->data; + const char *slot_imsi = ofono_sim_get_imsi(slot->sim); + if (slot_imsi) { + if (imsi) { + /* We are looking for the specific sim */ + if (!strcmp(imsi, slot_imsi)) { + return slot; + } + } else { + /* We are looking for any slot with a sim */ + if (!default_slot) { + default_slot = slot; + } + } + } + slots = slots->next; + } + + return default_slot; +} + +static struct ril_slot *ril_plugin_find_slot_number(GSList *slots, guint number) +{ + while (slots) { + struct ril_slot *slot = slots->data; + if (slot->config.slot == number) { + return slot; + } + slots = slots->next; + } + + return NULL; +} + +/* Returns the event mask to be passed to ril_plugin_dbus_signal. + * The caller has a chance to OR it with other bits */ +static int ril_plugin_update_modem_paths(struct ril_plugin_priv *plugin) +{ + int mask = 0; + struct ril_slot *voice = ril_plugin_find_slot_imsi(plugin->slots, + plugin->default_voice_imsi); + struct ril_slot *data = ril_plugin_find_slot_imsi(plugin->slots, + plugin->default_data_imsi); + + if (!voice) { + /* If there's no default voice SIM, find any SIM instead. + * One should always be able to make and receive a phone call + * if there's a working SIM in the phone. However if the + * previously selected voice SIM is inserted, we will switch + * back to it. */ + voice = ril_plugin_find_slot_imsi(plugin->slots, NULL); + } + + if (voice) { + if (g_strcmp0(plugin->default_voice_path, voice->path)) { + DBG("Default voice SIM at %s", voice->path); + g_free(plugin->default_voice_path); + plugin->default_voice_path = g_strdup(voice->path); + mask |= RIL_PLUGIN_SIGNAL_VOICE_PATH; + } + } else if (plugin->default_voice_path) { + DBG("No default voice SIM"); + g_free(plugin->default_voice_path); + plugin->default_voice_path = NULL; + mask |= RIL_PLUGIN_SIGNAL_VOICE_PATH; + } + + if (data) { + if (g_strcmp0(plugin->default_data_path, data->path)) { + DBG("Default data SIM at %s", data->path); + g_free(plugin->default_data_path); + plugin->default_data_path = g_strdup(data->path); + mask |= RIL_PLUGIN_SIGNAL_DATA_PATH; + } + if (plugin->data_modem != data->modem) { + plugin->data_modem = data->modem; + ril_modem_allow_data(data->modem); + } + } else if (plugin->default_data_path) { + DBG("No default data SIM"); + g_free(plugin->default_data_path); + plugin->default_data_path = NULL; + mask |= RIL_PLUGIN_SIGNAL_DATA_PATH; + } + + plugin->pub.default_voice_path = plugin->default_voice_path; + plugin->pub.default_data_path = plugin->default_data_path; + return mask; +} + +static void ril_plugin_check_sim_state(struct ril_slot *slot) +{ + const char *slot_imsi = ofono_sim_get_imsi(slot->sim); + const char *dbus_imsi = ril_sim_dbus_imsi(slot->sim_dbus); + + if (!slot_imsi) { + if (slot->sim_dbus) { + ril_sim_dbus_free(slot->sim_dbus); + slot->sim_dbus = NULL; + } + } else if (g_strcmp0(slot_imsi, dbus_imsi)) { + ril_sim_dbus_free(slot->sim_dbus); + slot->sim_dbus = ril_sim_dbus_new(slot->modem); + } +} + +static void ril_plugin_sim_watch_done(void *data) +{ + struct ril_slot *slot = data; + + slot->sim_watch_id = 0; +} + +static void ril_plugin_sim_state_watch_done(void *data) +{ + struct ril_slot *slot = data; + + slot->sim_state_watch_id = 0; +} + +static void ril_plugin_sim_state_watch(enum ofono_sim_state new_state, + void *data) +{ + struct ril_slot *slot = data; + + DBG("%s sim state %d", slot->path + 1, new_state); + slot->sim_state = new_state; + ril_plugin_check_sim_state(slot); + ril_plugin_dbus_signal(slot->plugin->dbus, + ril_plugin_update_modem_paths(slot->plugin)); +} + +static void ril_plugin_register_sim(struct ril_slot *slot, struct ofono_sim *sim) +{ + GASSERT(sim); + GASSERT(!slot->sim); + GASSERT(slot->sim_watch_id); + GASSERT(!slot->sim_state_watch_id); + slot->sim = sim; + slot->sim_state = ofono_sim_get_state(sim); + slot->sim_state_watch_id = ofono_sim_add_state_watch(sim, + ril_plugin_sim_state_watch, slot, + ril_plugin_sim_state_watch_done); +} + +static void ril_plugin_sim_watch(struct ofono_atom *atom, + enum ofono_atom_watch_condition cond, void *data) +{ + struct ril_slot *slot = data; + + if (cond == OFONO_ATOM_WATCH_CONDITION_REGISTERED) { + DBG("%s sim registered", slot->path + 1); + ril_plugin_register_sim(slot, __ofono_atom_get_data(atom)); + } else if (cond == OFONO_ATOM_WATCH_CONDITION_UNREGISTERED) { + DBG("%s sim unregistered", slot->path + 1); + slot->sim = NULL; + } + + ril_plugin_check_sim_state(slot); + ril_plugin_dbus_signal(slot->plugin->dbus, + ril_plugin_update_modem_paths(slot->plugin)); +} + +static void ril_plugin_handle_error(struct ril_slot *slot) +{ + ril_plugin_shutdown_slot(slot, TRUE); + GASSERT(!slot->retry_id); + slot->retry_id = g_timeout_add_seconds(RIL_RETRY_SECS, + ril_plugin_retry, slot); +} + +static void ril_plugin_error(GRilIoChannel *io, const GError *error, void *data) +{ + ril_plugin_handle_error((struct ril_slot *)data); +} + +static void ril_plugin_disconnect(GRilIoChannel *io, void *data) +{ + ril_plugin_handle_error((struct ril_slot *)data); +} + +static void ril_plugin_modem_error(struct ril_modem *modem, void *data) +{ + ril_plugin_handle_error((struct ril_slot *)data); +} + +static void ril_plugin_modem_removed(struct ril_modem *modem, void *data) +{ + struct ril_slot *slot = data; + + DBG(""); + GASSERT(slot->modem); + GASSERT(slot->modem == modem); + + if (slot->sim_dbus) { + ril_sim_dbus_free(slot->sim_dbus); + slot->sim_dbus = NULL; + } + + slot->modem = NULL; + if (slot->plugin->data_modem == modem) { + slot->plugin->data_modem = NULL; + } +} + +static void ril_plugin_trace(GRilIoChannel *io, GRILIO_PACKET_TYPE type, + guint id, guint code, const void *data, guint data_len, void *user_data) +{ + /* Use log sub-module to turn prefix off */ + static GLOG_MODULE_DEFINE2_(log_module, NULL, GLOG_MODULE_NAME); + const char *prefix = io->name ? io->name : ""; + const char dir = (type == GRILIO_PACKET_REQ) ? '<' : '>'; + switch (type) { + case GRILIO_PACKET_REQ: + gutil_log(&log_module, GLOG_LEVEL_INFO, "%s%c [%08x] %s", + prefix, dir, id, ril_request_to_string(code)); + break; + case GRILIO_PACKET_RESP: + gutil_log(&log_module, GLOG_LEVEL_INFO, "%s%c [%08x] %s", + prefix, dir, id, ril_error_to_string(code)); + break; + case GRILIO_PACKET_UNSOL: + gutil_log(&log_module, GLOG_LEVEL_INFO, "%s%c %s", + prefix, dir, ril_unsol_event_to_string(code)); + break; + } +} + +static void ril_debug_dump_update_slot(struct ril_slot *slot) +{ + if (slot->io) { + if (ril_debug_dump.flags & OFONO_DEBUG_FLAG_PRINT) { + if (!slot->dump_id) { + slot->dump_id = + grilio_channel_add_default_logger( + slot->io, GLOG_LEVEL_INFO); + } + } else if (slot->dump_id) { + grilio_channel_remove_logger(slot->io, slot->dump_id); + slot->dump_id = 0; + } + } +} + +static void ril_debug_trace_update_slot(struct ril_slot *slot) +{ + if (slot->io) { + if (ril_debug_trace.flags & OFONO_DEBUG_FLAG_PRINT) { + if (!slot->trace_id) { + slot->trace_id = + grilio_channel_add_logger(slot->io, + ril_plugin_trace, slot); + /* + * Loggers are invoked in the order they have + * been registered. Make sure that dump logger + * is invoked after ril_plugin_trace. + */ + if (slot->dump_id) { + grilio_channel_remove_logger(slot->io, + slot->dump_id); + slot->dump_id = 0; + } + ril_debug_dump_update_slot(slot); + } + } else if (slot->trace_id) { + grilio_channel_remove_logger(slot->io, slot->trace_id); + slot->trace_id = 0; + } + } +} + +static void ril_plugin_create_modem(struct ril_slot *slot) +{ + DBG("%s %s", slot->sockpath, slot->sub); + if (!slot->io) { + slot->io = grilio_channel_new_socket(slot->sockpath, slot->sub); + if (slot->io) { + ril_debug_trace_update_slot(slot); + ril_debug_dump_update_slot(slot); + + if (slot->name) { + grilio_channel_set_name(slot->io, slot->name); + } + + grilio_channel_set_timeout(slot->io, slot->timeout); + slot->error_id = + grilio_channel_add_error_handler(slot->io, + ril_plugin_error, slot); + slot->eof_id = + grilio_channel_add_disconnected_handler(slot->io, + ril_plugin_disconnect, slot); + } + } + + if (slot->io) { + GASSERT(!slot->modem); + slot->modem = ril_modem_create(slot->io, slot->path + 1, + &slot->config); + if (slot->modem) { + struct ofono_sim *sim = ril_modem_ofono_sim(slot->modem); + + slot->sim_watch_id = __ofono_modem_add_atom_watch( + ril_modem_ofono_modem(slot->modem), + OFONO_ATOM_TYPE_SIM, ril_plugin_sim_watch, + slot, ril_plugin_sim_watch_done); + if (sim) { + ril_plugin_register_sim(slot, sim); + } + ril_modem_set_error_cb(slot->modem, + ril_plugin_modem_error, slot); + ril_modem_set_removed_cb(slot->modem, + ril_plugin_modem_removed, slot); + if (!slot->mce) { + GASSERT(!slot->mce); + slot->mce = ril_mce_new(slot->io); + } + } else { + ril_plugin_shutdown_slot(slot, TRUE); + } + } + + if (!slot->modem) { + GASSERT(!slot->retry_id); + slot->retry_id = g_timeout_add_seconds(RIL_RETRY_SECS, + ril_plugin_retry, slot); + } +} + +static GSList *ril_plugin_create_default_config() +{ + GSList *list = NULL; + + if (g_file_test(RILMODEM_DEFAULT_SOCK, G_FILE_TEST_EXISTS)) { + struct ril_slot *slot; + + if (g_file_test(RILMODEM_DEFAULT_SOCK2, G_FILE_TEST_EXISTS)) { + DBG("Falling back to default 2-SIM config"); + + slot = g_new0(struct ril_slot, 1); + slot->path = g_strdup(RILCONF_PATH_PREFIX "0"); + slot->sockpath = g_strdup(RILMODEM_DEFAULT_SOCK); + slot->name = g_strdup("RIL1"); + slot->config.enable_4g = RILMODEM_DEFAULT_4G; + slot->timeout = RILMODEM_DEFAULT_TIMEOUT; + list = g_slist_append(list, slot); + + slot = g_new0(struct ril_slot, 1); + slot->path = g_strdup(RILCONF_PATH_PREFIX "1"); + slot->sockpath = g_strdup(RILMODEM_DEFAULT_SOCK2); + slot->name = g_strdup("RIL2"); + slot->config.enable_4g = RILMODEM_DEFAULT_4G; + slot->timeout = RILMODEM_DEFAULT_TIMEOUT; + slot->config.slot = 1; + list = g_slist_append(list, slot); + } else { + DBG("Falling back to default Jolla1 config"); + + slot = g_new0(struct ril_slot, 1); + slot->path = g_strdup(RILCONF_PATH_PREFIX "0"); + slot->sockpath = g_strdup(RILMODEM_DEFAULT_SOCK); + slot->sub = g_strdup(RILMODEM_DEFAULT_SUB); + slot->name = g_strdup(""); + slot->config.enable_4g = RILMODEM_DEFAULT_4G; + slot->timeout = RILMODEM_DEFAULT_TIMEOUT; + list = g_slist_append(list, slot); + } + } else { + DBG("No default config"); + } + + return list; +} + +static struct ril_slot *ril_plugin_parse_config_group(GKeyFile *file, + const char *group) +{ + struct ril_slot *slot = NULL; + char *sock = g_key_file_get_string(file, group, RILCONF_SOCKET, NULL); + if (sock) { + int value; + GError *err = NULL; + char *sub = g_key_file_get_string(file, group, RILCONF_SUB, + NULL); + + slot = g_new0(struct ril_slot, 1); + slot->sockpath = sock; + slot->path = g_strconcat("/", group, NULL); + slot->name = g_key_file_get_string(file, group, RILCONF_NAME, + NULL); + + if (sub && strlen(sub) == RIL_SUB_SIZE) { + DBG("%s: %s:%s", group, sock, sub); + slot->sub = sub; + } else { + DBG("%s: %s", group, sock); + g_free(sub); + } + + value = g_key_file_get_integer(file, group, RILCONF_SLOT, &err); + if (!err && value >= 0) { + slot->config.slot = value; + DBG("%s: slot %u", group, slot->config.slot); + } else { + slot->config.slot = RILMODEM_DEFAULT_SLOT; + if (err) { + g_error_free(err); + err = NULL; + } + } + + value = g_key_file_get_integer(file, group, RILCONF_TIMEOUT, + &err); + if (!err) { + slot->timeout = value; + DBG("%s: timeout %d", group, slot->timeout); + } else { + slot->timeout = RILMODEM_DEFAULT_TIMEOUT; + if (err) { + g_error_free(err); + err = NULL; + } + } + + slot->config.enable_4g = g_key_file_get_boolean(file, group, + RILCONF_4G, &err); + if (err) { + /* Set to default */ + slot->config.enable_4g = RILMODEM_DEFAULT_4G; + g_error_free(err); + err = NULL; + } + DBG("%s: 4G %s", group, slot->config.enable_4g ? "on" : "off"); + } else { + DBG("no socket path in %s", group); + } + + return slot; +} + +static void ril_plugin_delete_slot(struct ril_slot *slot) +{ + ril_plugin_shutdown_slot(slot, TRUE); + g_free(slot->path); + g_free(slot->name); + g_free(slot->sockpath); + g_free(slot->sub); + g_free(slot); +} + +static GSList *ril_plugin_add_slot(GSList *slots, struct ril_slot *new_slot) +{ + GSList *link = slots; + + /* Slot numbers and paths must be unique */ + while (link) { + GSList *next = link->next; + struct ril_slot *slot = link->data; + gboolean delete_this_slot = FALSE; + + if (!strcmp(slot->path, new_slot->path)) { + ofono_error("Duplicate modem path '%s'", slot->path); + delete_this_slot = TRUE; + } else if (slot->config.slot != RILMODEM_DEFAULT_SLOT && + slot->config.slot == new_slot->config.slot) { + ofono_error("Duplicate RIL slot %u", slot->config.slot); + delete_this_slot = TRUE; + } + + if (delete_this_slot) { + slots = g_slist_delete_link(slots, link); + ril_plugin_delete_slot(slot); + } + + link = next; + } + + return g_slist_append(slots, new_slot); +} + +static guint ril_plugin_find_unused_slot(GSList *slots) +{ + guint number; + + for (number = 0; ril_plugin_find_slot_number(slots, number); number++); + return number; +} + +static GSList *ril_plugin_parse_config_file(GKeyFile *file) +{ + GSList *list = NULL; + GSList *link; + gsize i, n = 0; + gchar **groups = g_key_file_get_groups(file, &n); + + for (i=0; idata; + if (slot->config.slot == RILMODEM_DEFAULT_SLOT) { + slot->config.slot = ril_plugin_find_unused_slot(list); + } + link = link->next; + } + + g_strfreev(groups); + return list; +} + +static GSList *ril_plugin_load_config(const char *path) +{ + GError *err = NULL; + GSList *list = NULL; + GKeyFile *file = g_key_file_new(); + + if (g_key_file_load_from_file(file, path, 0, &err)) { + DBG("loading %s", path); + list = ril_plugin_parse_config_file(file); + } else { + DBG("conf load result: %s", err->message); + g_error_free(err); + } + + if (!list) { + list = ril_plugin_create_default_config(); + } + + g_key_file_free(file); + return list; +} + +static void ril_plugin_destroy_slot(gpointer data) +{ + ril_plugin_delete_slot((struct ril_slot *)data); +} + +static void ril_plugin_init_slot(gpointer data, gpointer user_data) +{ + struct ril_plugin_priv *plugin = user_data; + struct ril_slot *slot = data; + + slot->plugin = plugin; + GASSERT(!gutil_strv_contains(plugin->pub.available_slots, slot->path)); + plugin->pub.available_slots = gutil_strv_add(plugin->pub.available_slots, + slot->path); +} + +/* RIL expects user radio */ +static void ril_plugin_switch_user() +{ + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) { + ofono_error("prctl(PR_SET_KEEPCAPS) failed: %s", + strerror(errno)); + } else if (setgid(RADIO_GID) < 0) { + ofono_error("setgid(%d) failed: %s", RADIO_GID, + strerror(errno)); + } else if (setuid(RADIO_UID) < 0) { + ofono_error("setuid(%d) failed: %s", RADIO_UID, + strerror(errno)); + } else { + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + + memset(&header, 0, sizeof(header)); + memset(&cap, 0, sizeof(cap)); + + header.version = _LINUX_CAPABILITY_VERSION; + cap.effective = cap.permitted = (1 << CAP_NET_ADMIN) | + (1 << CAP_NET_RAW); + + if (syscall(SYS_capset, &header, &cap) < 0) { + ofono_error("syscall(SYS_capset) failed: %s", + strerror(errno)); + } + } +} + +/* Removes unavailable and duplicate slots from the string array, + * reorders them to match the order of available slots */ +static char **ril_plugin_clean_slots(struct ril_plugin *plugin, char **slots) +{ + guint i; + const guint len1 = gutil_strv_length(plugin->available_slots); + const guint len2 = gutil_strv_length(slots); + const guint len = MIN(len1, len2); + char **clean_slots = g_new0(char*, len + 1); + char **ptr = clean_slots; + + *ptr = NULL; + for (i = 0; i < len1; i++) { + const char *slot = plugin->available_slots[i]; + if (gutil_strv_contains(slots, slot)) { + GASSERT(!gutil_strv_contains(clean_slots, slot)); + *ptr++ = g_strdup(slot); + *ptr = NULL; + } + } + + GASSERT(gutil_strv_length(clean_slots) <= len); + return clean_slots; +} + +static gboolean ril_plugin_slot_enabled(struct ril_slot *slot) +{ + char **enabled = slot->plugin->pub.enabled_slots; + + if (enabled) { + while (*enabled) { + if (!strcmp(*enabled, slot->path)) { + return TRUE; + } + enabled++; + } + } + + return FALSE; +} + +static void ril_plugin_update_slot(struct ril_slot *slot) +{ + if (ril_plugin_slot_enabled(slot)) { + DBG("%s enabled", slot->path + 1); + if (!slot->modem) { + ril_plugin_create_modem(slot); + } + } else { + DBG("%s disabled", slot->path + 1); + ril_plugin_shutdown_slot(slot, FALSE); + } +} + +static void ril_plugin_update_slots(struct ril_plugin_priv *plugin) +{ + ril_plugin_foreach_slot(plugin, ril_plugin_update_slot); + ril_plugin_dbus_signal(plugin->dbus, + ril_plugin_update_modem_paths(plugin)); +} + +void ril_plugin_set_enabled_slots(struct ril_plugin *pub, gchar **slots) +{ + char **new_slots = ril_plugin_clean_slots(pub, slots); + + if (!gutil_strv_equal(pub->enabled_slots, new_slots)) { + struct ril_plugin_priv *plugin = ril_plugin_cast(pub); + + /* Save the new config value. If it exactly matches the list + * of available modems, delete the setting because that's the + * default behavior. */ + if (gutil_strv_equal(pub->enabled_slots, new_slots)) { + ril_pligin_set_config_string(plugin, + RIL_STORE_ENABLED_SLOTS, NULL, TRUE); + } else { + char *value = g_strjoinv(RIL_STORE_SLOTS_SEP, new_slots); + ril_pligin_set_config_string(plugin, + RIL_STORE_ENABLED_SLOTS, value, TRUE); + g_free(value); + } + g_strfreev(pub->enabled_slots); + pub->enabled_slots = new_slots; + ril_plugin_dbus_signal(plugin->dbus, + RIL_PLUGIN_SIGNAL_ENABLED_SLOTS); + + /* Add and remove modems */ + ril_plugin_update_slots(plugin); + } else { + g_strfreev(new_slots); + } +} + +void ril_plugin_set_default_voice_imsi(struct ril_plugin *pub, const char *imsi) +{ + struct ril_plugin_priv *plugin = ril_plugin_cast(pub); + + if (g_strcmp0(plugin->default_voice_imsi, imsi)) { + DBG("Default voice sim set to %s", imsi ? imsi : "(auto)"); + g_free(plugin->default_voice_imsi); + pub->default_voice_imsi = + plugin->default_voice_imsi = g_strdup(imsi); + ril_pligin_set_config_string(plugin, RIL_STORE_DEFAULT_VOICE_SIM, + imsi, TRUE); + ril_plugin_dbus_signal(plugin->dbus, + RIL_PLUGIN_SIGNAL_VOICE_IMSI | + ril_plugin_update_modem_paths(plugin)); + } +} + +void ril_plugin_set_default_data_imsi(struct ril_plugin *pub, const char *imsi) +{ + struct ril_plugin_priv *plugin = ril_plugin_cast(pub); + + if (g_strcmp0(plugin->default_data_imsi, imsi)) { + DBG("Default data sim set to %s", imsi ? imsi : "(auto)"); + g_free(plugin->default_data_imsi); + pub->default_data_imsi = + plugin->default_data_imsi = g_strdup(imsi); + ril_pligin_set_config_string(plugin, RIL_STORE_DEFAULT_DATA_SIM, + imsi, TRUE); + ril_plugin_dbus_signal(plugin->dbus, + RIL_PLUGIN_SIGNAL_DATA_IMSI | + ril_plugin_update_modem_paths(plugin)); + } +} + +struct ril_plugin_priv *ril_plugin = NULL; + +static void ril_debug_trace_notify(struct ofono_debug_desc *desc) +{ + if (ril_plugin) { + ril_plugin_foreach_slot(ril_plugin, ril_debug_trace_update_slot); + } +} + +static void ril_debug_dump_notify(struct ofono_debug_desc *desc) +{ + if (ril_plugin) { + ril_plugin_foreach_slot(ril_plugin, ril_debug_dump_update_slot); + } +} + +static int ril_plugin_init(void) +{ + char *enabled_slots; + + DBG(""); + GASSERT(!ril_plugin); + + gutil_log_func = gutil_log_syslog; + + ril_plugin_switch_user(); + + ril_plugin = g_new0(struct ril_plugin_priv, 1); + ril_plugin->slots = ril_plugin_load_config(RILMODEM_CONF_FILE); + g_slist_foreach(ril_plugin->slots, ril_plugin_init_slot, ril_plugin); + ril_plugin->dbus = ril_plugin_dbus_new(&ril_plugin->pub); + + /* Load settings */ + ril_plugin->storage = storage_open(NULL, RIL_STORE); + enabled_slots = g_key_file_get_string(ril_plugin->storage, + RIL_STORE_GROUP, RIL_STORE_ENABLED_SLOTS, NULL); + if (enabled_slots) { + char **strv = g_strsplit(enabled_slots, RIL_STORE_SLOTS_SEP, 0); + + DBG("Enabled slots: %s", enabled_slots); + ril_plugin->pub.enabled_slots = + ril_plugin_clean_slots(&ril_plugin->pub, strv); + g_strfreev(strv); + g_free(enabled_slots); + } else { + /* Let all slots be enabled by default */ + ril_plugin->pub.enabled_slots = + g_strdupv(ril_plugin->pub.available_slots); + } + + ril_plugin->pub.default_voice_imsi = + ril_plugin->default_voice_imsi = + g_key_file_get_string(ril_plugin->storage, RIL_STORE_GROUP, + RIL_STORE_DEFAULT_VOICE_SIM, NULL); + ril_plugin->pub.default_data_imsi = + ril_plugin->default_data_imsi = + g_key_file_get_string(ril_plugin->storage, RIL_STORE_GROUP, + RIL_STORE_DEFAULT_DATA_SIM, NULL); + + DBG("Default voice sim is %s", ril_plugin->default_voice_imsi ? + ril_plugin->default_voice_imsi : "(auto)"); + DBG("Default data sim is %s", ril_plugin->default_data_imsi ? + ril_plugin->default_data_imsi : "(auto)"); + + ofono_modem_driver_register(&ril_modem_driver); + ofono_sim_driver_register(&ril_sim_driver); + ofono_sms_driver_register(&ril_sms_driver); + ofono_netreg_driver_register(&ril_netreg_driver); + ofono_devinfo_driver_register(&ril_devinfo_driver); + ofono_voicecall_driver_register(&ril_voicecall_driver); + ofono_call_barring_driver_register(&ril_call_barring_driver); + ofono_call_forwarding_driver_register(&ril_call_forwarding_driver); + ofono_call_settings_driver_register(&ril_call_settings_driver); + ofono_call_volume_driver_register(&ril_call_volume_driver); + ofono_radio_settings_driver_register(&ril_radio_settings_driver); + ofono_gprs_driver_register(&ril_gprs_driver); + ofono_gprs_context_driver_register(&ril_gprs_context_driver); + ofono_phonebook_driver_register(&ril_phonebook_driver); + ofono_ussd_driver_register(&ril_ussd_driver); + ofono_cbs_driver_register(&ril_cbs_driver); + ofono_oem_raw_driver_register(&ril_oem_raw_driver); + ofono_stk_driver_register(&ril_stk_driver); + + /* This will create the modems (those that are enabled) */ + ril_plugin_update_slots(ril_plugin); + return 0; +} + +static void ril_plugin_exit(void) +{ + DBG(""); + GASSERT(ril_plugin); + + ofono_modem_driver_unregister(&ril_modem_driver); + ofono_sim_driver_unregister(&ril_sim_driver); + ofono_sms_driver_unregister(&ril_sms_driver); + ofono_devinfo_driver_unregister(&ril_devinfo_driver); + ofono_netreg_driver_unregister(&ril_netreg_driver); + ofono_voicecall_driver_unregister(&ril_voicecall_driver); + ofono_call_barring_driver_unregister(&ril_call_barring_driver); + ofono_call_forwarding_driver_unregister(&ril_call_forwarding_driver); + ofono_call_settings_driver_unregister(&ril_call_settings_driver); + ofono_call_volume_driver_unregister(&ril_call_volume_driver); + ofono_radio_settings_driver_unregister(&ril_radio_settings_driver); + ofono_gprs_driver_unregister(&ril_gprs_driver); + ofono_gprs_context_driver_unregister(&ril_gprs_context_driver); + ofono_phonebook_driver_unregister(&ril_phonebook_driver); + ofono_ussd_driver_unregister(&ril_ussd_driver); + ofono_cbs_driver_unregister(&ril_cbs_driver); + ofono_oem_raw_driver_unregister(&ril_oem_raw_driver); + ofono_stk_driver_unregister(&ril_stk_driver); + + if (ril_plugin) { + g_slist_free_full(ril_plugin->slots, ril_plugin_destroy_slot); + ril_plugin_dbus_free(ril_plugin->dbus); + g_key_file_free(ril_plugin->storage); + g_strfreev(ril_plugin->pub.available_slots); + g_strfreev(ril_plugin->pub.enabled_slots); + g_free(ril_plugin->default_voice_imsi); + g_free(ril_plugin->default_data_imsi); + g_free(ril_plugin->default_voice_path); + g_free(ril_plugin->default_data_path); + g_free(ril_plugin); + ril_plugin = NULL; + } +} + +OFONO_PLUGIN_DEFINE(ril, "RIL driver", VERSION, + OFONO_PLUGIN_PRIORITY_DEFAULT, ril_plugin_init, ril_plugin_exit) + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_plugin.h b/ofono/drivers/ril/ril_plugin.h new file mode 100644 index 00000000..72553064 --- /dev/null +++ b/ofono/drivers/ril/ril_plugin.h @@ -0,0 +1,151 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#ifndef RIL_PLUGIN_H +#define RIL_PLUGIN_H + +#include "ril_types.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define RILMODEM_DRIVER "ril" +#define RIL_RETRY_SECS (2) +#define MAX_SIM_STATUS_RETRIES (15) + +struct ril_plugin { + const char *default_voice_imsi; + const char *default_data_imsi; + const char *default_voice_path; + const char *default_data_path; + char **available_slots; + char **enabled_slots; +}; + +struct ril_modem_config { + guint slot; + gboolean enable_4g; + const char *default_name; +}; + +#define RIL_PLUGIN_SIGNAL_VOICE_IMSI (0x01) +#define RIL_PLUGIN_SIGNAL_DATA_IMSI (0x02) +#define RIL_PLUGIN_SIGNAL_VOICE_PATH (0x04) +#define RIL_PLUGIN_SIGNAL_DATA_PATH (0x10) +#define RIL_PLUGIN_SIGNAL_ENABLED_SLOTS (0x20) + +struct ril_modem; +struct ril_plugin_dbus; +typedef void (*ril_modem_cb_t)(struct ril_modem *modem, void *data); + +void ril_plugin_set_enabled_slots(struct ril_plugin *plugin, char **slots); +void ril_plugin_set_default_voice_imsi(struct ril_plugin *plugin, + const char *imsi); +void ril_plugin_set_default_data_imsi(struct ril_plugin *plugin, + const char *imsi); + +struct ril_sim_dbus *ril_sim_dbus_new(struct ril_modem *modem); +const char *ril_sim_dbus_imsi(struct ril_sim_dbus *dbus); +void ril_sim_dbus_free(struct ril_sim_dbus *dbus); + +struct ril_plugin_dbus *ril_plugin_dbus_new(struct ril_plugin *plugin); +void ril_plugin_dbus_free(struct ril_plugin_dbus *dbus); +void ril_plugin_dbus_signal(struct ril_plugin_dbus *dbus, int mask); + +struct ril_modem *ril_modem_create(GRilIoChannel *io, const char *dev, + const struct ril_modem_config *config); +void ril_modem_delete(struct ril_modem *modem); +void ril_modem_allow_data(struct ril_modem *modem); +GRilIoChannel *ril_modem_io(struct ril_modem *modem); +const struct ril_modem_config *ril_modem_config(struct ril_modem *modem); +struct ofono_sim *ril_modem_ofono_sim(struct ril_modem *modem); +struct ofono_gprs *ril_modem_ofono_gprs(struct ril_modem *modem); +struct ofono_netreg *ril_modem_ofono_netreg(struct ril_modem *modem); +struct ofono_modem *ril_modem_ofono_modem(struct ril_modem *modem); +void ril_modem_set_error_cb(struct ril_modem *modem, ril_modem_cb_t cb, + void *data); +void ril_modem_set_removed_cb(struct ril_modem *modem, ril_modem_cb_t cb, + void *data); + +#define ril_modem_slot(md) (ril_modem_config(modem)->slot) +#define ril_modem_4g_enabled(md) (ril_modem_config(modem)->enable_4g) +#define ril_modem_get_path(md) ofono_modem_get_path(ril_modem_ofono_modem(md)) + +void ril_sim_read_file_linear(struct ofono_sim *sim, int fileid, + int record, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data); +void ril_sim_read_file_cyclic(struct ofono_sim *sim, int fileid, + int record, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data); +void ril_sim_read_file_transparent(struct ofono_sim *sim, int fileid, + int start, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data); +void ril_sim_read_file_info(struct ofono_sim *sim, int fileid, + const unsigned char *path, unsigned int path_len, + ofono_sim_file_info_cb_t cb, void *data); + +int ril_sim_app_type(struct ofono_sim *sim); +int ril_gprs_ril_data_tech(struct ofono_gprs *gprs); +int ril_netreg_check_if_really_roaming(struct ofono_netreg *netreg, + gint status); + +extern const struct ofono_call_barring_driver ril_call_barring_driver; +extern const struct ofono_call_forwarding_driver ril_call_forwarding_driver; +extern const struct ofono_call_settings_driver ril_call_settings_driver; +extern const struct ofono_call_volume_driver ril_call_volume_driver; +extern const struct ofono_cbs_driver ril_cbs_driver; +extern const struct ofono_devinfo_driver ril_devinfo_driver; +extern const struct ofono_gprs_context_driver ril_gprs_context_driver; +extern const struct ofono_gprs_driver ril_gprs_driver; +extern const struct ofono_modem_driver ril_modem_driver; +extern const struct ofono_netreg_driver ril_netreg_driver; +extern /* const */ struct ofono_oem_raw_driver ril_oem_raw_driver; +extern const struct ofono_phonebook_driver ril_phonebook_driver; +extern const struct ofono_radio_settings_driver ril_radio_settings_driver; +extern const struct ofono_sim_driver ril_sim_driver; +extern const struct ofono_sms_driver ril_sms_driver; +extern const struct ofono_stk_driver ril_stk_driver; +extern const struct ofono_ussd_driver ril_ussd_driver; +extern const struct ofono_voicecall_driver ril_voicecall_driver; + +#endif /* RIL_PLUGIN_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_plugin_dbus.c b/ofono/drivers/ril/ril_plugin_dbus.c new file mode 100644 index 00000000..e092db9a --- /dev/null +++ b/ofono/drivers/ril/ril_plugin_dbus.c @@ -0,0 +1,416 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" + +#include +#include + +#include +#include +#include + +#include "ofono.h" + +struct ril_plugin_dbus { + struct ril_plugin *plugin; + DBusConnection *conn; +}; + +#define RIL_DBUS_PATH "/" +#define RIL_DBUS_INTERFACE "org.nemomobile.ofono.ModemManager" +#define RIL_DBUS_INTERFACE_VERSION (1) + +#define RIL_DBUS_ENABLED_MODEM_CHANGED_SIGNAL "EnabledModemsChanged" +#define RIL_DBUS_DEFAULT_VOICE_SIM_CHANGED_SIGNAL "DefaultVoiceSimChanged" +#define RIL_DBUS_DEFAULT_DATA_SIM_CHANGED_SIGNAL "DefaultDataSimChanged" +#define RIL_DBUS_DEFAULT_VOICE_MODEM_CHANGED_SIGNAL "DefaultVoiceModemChanged" +#define RIL_DBUS_DEFAULT_DATA_MODEM_CHANGED_SIGNAL "DefaultDataModemChanged" +#define RIL_DBUS_IMSI_AUTO "auto" + +static void ril_plugin_dbus_append_path_array(DBusMessageIter *it, char **paths) +{ + DBusMessageIter array; + + dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, &array); + + if (paths) { + while (*paths) { + const char *path = *paths++; + dbus_message_iter_append_basic(&array, + DBUS_TYPE_OBJECT_PATH, &path); + } + } + + dbus_message_iter_close_container(it, &array); +} + +static void ril_plugin_dbus_append_imsi(DBusMessageIter *it, const char *imsi) +{ + if (!imsi) imsi = RIL_DBUS_IMSI_AUTO; + dbus_message_iter_append_basic(it, DBUS_TYPE_STRING, &imsi); +} + +static void ril_plugin_dbus_append_path(DBusMessageIter *it, const char *path) +{ + if (!path) path = ""; + /* It's DBUS_TYPE_STRING because DBUS_TYPE_OBJECT_PATH can't be empty */ + dbus_message_iter_append_basic(it, DBUS_TYPE_STRING, &path); +} + +static void ril_plugin_dbus_message_append_path_array(DBusMessage *msg, + char **paths) +{ + DBusMessageIter iter; + + dbus_message_iter_init_append(msg, &iter); + ril_plugin_dbus_append_path_array(&iter, paths); +} + +static void ril_plugin_dbus_signal_path_array(struct ril_plugin_dbus *dbus, + const char *name, char **paths) +{ + DBusMessage *signal = dbus_message_new_signal(RIL_DBUS_PATH, + RIL_DBUS_INTERFACE, name); + + ril_plugin_dbus_message_append_path_array(signal, paths); + g_dbus_send_message(dbus->conn, signal); +} + +static inline void ril_plugin_dbus_signal_imsi(struct ril_plugin_dbus *dbus, + const char *name, const char *imsi) +{ + if (!imsi) imsi = RIL_DBUS_IMSI_AUTO; + g_dbus_emit_signal(dbus->conn, RIL_DBUS_PATH, RIL_DBUS_INTERFACE, + name, DBUS_TYPE_STRING, &imsi, DBUS_TYPE_INVALID); +} + +static inline void ril_plugin_dbus_signal_path(struct ril_plugin_dbus *dbus, + const char *name, const char *path) +{ + if (!path) path = ""; + g_dbus_emit_signal(dbus->conn, RIL_DBUS_PATH, RIL_DBUS_INTERFACE, + name, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID); +} + +void ril_plugin_dbus_signal(struct ril_plugin_dbus *dbus, int mask) +{ + if (dbus) { + if (mask & RIL_PLUGIN_SIGNAL_VOICE_IMSI) { + ril_plugin_dbus_signal_imsi(dbus, + RIL_DBUS_DEFAULT_VOICE_SIM_CHANGED_SIGNAL, + dbus->plugin->default_voice_imsi); + } + if (mask & RIL_PLUGIN_SIGNAL_DATA_IMSI) { + ril_plugin_dbus_signal_imsi(dbus, + RIL_DBUS_DEFAULT_DATA_SIM_CHANGED_SIGNAL, + dbus->plugin->default_data_imsi); + } + if (mask & RIL_PLUGIN_SIGNAL_ENABLED_SLOTS) { + ril_plugin_dbus_signal_path_array(dbus, + RIL_DBUS_ENABLED_MODEM_CHANGED_SIGNAL, + dbus->plugin->enabled_slots); + } + if (mask & RIL_PLUGIN_SIGNAL_VOICE_PATH) { + ril_plugin_dbus_signal_path(dbus, + RIL_DBUS_DEFAULT_VOICE_MODEM_CHANGED_SIGNAL, + dbus->plugin->default_voice_path); + } + if (mask & RIL_PLUGIN_SIGNAL_DATA_PATH) { + ril_plugin_dbus_signal_path(dbus, + RIL_DBUS_DEFAULT_DATA_MODEM_CHANGED_SIGNAL, + dbus->plugin->default_data_path); + } + } +} + +static DBusMessage *ril_plugin_dbus_reply_with_path_array(DBusConnection *conn, + DBusMessage *msg, char **paths) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + + ril_plugin_dbus_message_append_path_array(reply, paths); + return reply; +} + +static DBusMessage *ril_plugin_dbus_get_all(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_int32_t version = RIL_DBUS_INTERFACE_VERSION; + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &version); + ril_plugin_dbus_append_path_array(&iter, dbus->plugin->available_slots); + ril_plugin_dbus_append_path_array(&iter, dbus->plugin->enabled_slots); + ril_plugin_dbus_append_imsi(&iter, dbus->plugin->default_data_imsi); + ril_plugin_dbus_append_imsi(&iter, dbus->plugin->default_voice_imsi); + ril_plugin_dbus_append_path(&iter, dbus->plugin->default_data_path); + ril_plugin_dbus_append_path(&iter, dbus->plugin->default_voice_path); + return reply; +} + +static DBusMessage *ril_plugin_dbus_get_interface_version(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_int32_t version = RIL_DBUS_INTERFACE_VERSION; + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &version); + return reply; +} + +static DBusMessage *ril_plugin_dbus_get_available_modems(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + return ril_plugin_dbus_reply_with_path_array(conn, msg, + dbus->plugin->available_slots); +} + +static DBusMessage *ril_plugin_dbus_get_enabled_modems(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + return ril_plugin_dbus_reply_with_path_array(conn, msg, + dbus->plugin->enabled_slots); +} + +static DBusMessage *ril_plugin_dbus_reply_with_imsi(DBusConnection *conn, + DBusMessage *msg, const char *imsi) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + ril_plugin_dbus_append_imsi(&iter, imsi); + return reply; +} + +static DBusMessage *ril_plugin_dbus_get_default_data_sim(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + return ril_plugin_dbus_reply_with_imsi(conn, msg, + dbus->plugin->default_data_imsi); +} + +static DBusMessage *ril_plugin_dbus_get_default_voice_sim(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + return ril_plugin_dbus_reply_with_imsi(conn, msg, + dbus->plugin->default_voice_imsi); +} + +static DBusMessage *ril_plugin_dbus_reply_with_path(DBusConnection *conn, + DBusMessage *msg, const char *path) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + ril_plugin_dbus_append_path(&iter, path); + return reply; +} + +static DBusMessage *ril_plugin_dbus_get_default_data_modem(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + return ril_plugin_dbus_reply_with_path(conn, msg, + dbus->plugin->default_data_path); +} + +static DBusMessage *ril_plugin_dbus_get_default_voice_modem(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + return ril_plugin_dbus_reply_with_path(conn, msg, + dbus->plugin->default_voice_path); +} + +static DBusMessage *ril_plugin_dbus_set_enabled_modems(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + DBusMessageIter iter; + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) { + char **paths = NULL; + DBusMessageIter array; + + dbus_message_iter_recurse(&iter, &array); + while (dbus_message_iter_get_arg_type(&array) == + DBUS_TYPE_OBJECT_PATH) { + DBusBasicValue value; + + dbus_message_iter_get_basic(&array, &value); + paths = gutil_strv_add(paths, value.str); + dbus_message_iter_next(&array); + } + + ril_plugin_set_enabled_slots(dbus->plugin, paths); + g_strfreev(paths); + return dbus_message_new_method_return(msg); + } else { + return __ofono_error_invalid_args(msg); + } +} + +static DBusMessage *ril_plugin_dbus_set_imsi(struct ril_plugin_dbus *dbus, + DBusMessage *msg, void (*apply)(struct ril_plugin *plugin, + const char *imsi)) +{ + DBusMessageIter iter; + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { + DBusBasicValue value; + const char *imsi; + + dbus_message_iter_get_basic(&iter, &value); + imsi = value.str; + if (!g_strcmp0(imsi, RIL_DBUS_IMSI_AUTO)) imsi = NULL; + apply(dbus->plugin, imsi); + return dbus_message_new_method_return(msg); + } else { + return __ofono_error_invalid_args(msg); + } +} + +static DBusMessage *ril_plugin_dbus_set_default_voice_sim(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + GASSERT(conn == dbus->conn); + return ril_plugin_dbus_set_imsi(dbus, msg, + ril_plugin_set_default_voice_imsi); +} + +static DBusMessage *ril_plugin_dbus_set_default_data_sim(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_plugin_dbus *dbus = data; + + GASSERT(conn == dbus->conn); + return ril_plugin_dbus_set_imsi(dbus, msg, + ril_plugin_set_default_data_imsi); +} + +static const GDBusMethodTable ril_plugin_dbus_methods[] = { + { GDBUS_METHOD("GetAll", NULL, + GDBUS_ARGS({"version", "i" }, + {"availableModems", "ao" }, + {"enabledModems", "ao" }, + {"defaultDataSim", "s" }, + {"defaultVoiceSim", "s" }, + {"defaultDataModem", "s" }, + {"defaultVoiceModem" , "s"}), + ril_plugin_dbus_get_all) }, + { GDBUS_METHOD("GetInterfaceVersion", + NULL, GDBUS_ARGS({ "version", "i" }), + ril_plugin_dbus_get_interface_version) }, + { GDBUS_METHOD("GetAvailableModems", + NULL, GDBUS_ARGS({ "modems", "ao" }), + ril_plugin_dbus_get_available_modems) }, + { GDBUS_METHOD("GetEnabledModems", + NULL, GDBUS_ARGS({ "modems", "ao" }), + ril_plugin_dbus_get_enabled_modems) }, + { GDBUS_METHOD("GetDefaultDataSim", + NULL, GDBUS_ARGS({ "imsi", "s" }), + ril_plugin_dbus_get_default_data_sim) }, + { GDBUS_METHOD("GetDefaultVoiceSim", + NULL, GDBUS_ARGS({ "imsi", "s" }), + ril_plugin_dbus_get_default_voice_sim) }, + { GDBUS_METHOD("GetDefaultDataModem", + NULL, GDBUS_ARGS({ "path", "s" }), + ril_plugin_dbus_get_default_data_modem) }, + { GDBUS_METHOD("GetDefaultVoiceModem", + NULL, GDBUS_ARGS({ "path", "s" }), + ril_plugin_dbus_get_default_voice_modem) }, + { GDBUS_METHOD("SetEnabledModems", + GDBUS_ARGS({ "modems", "ao" }), NULL, + ril_plugin_dbus_set_enabled_modems) }, + { GDBUS_METHOD("SetDefaultDataSim", + GDBUS_ARGS({ "imsi", "s" }), NULL, + ril_plugin_dbus_set_default_data_sim) }, + { GDBUS_METHOD("SetDefaultVoiceSim", + GDBUS_ARGS({ "imsi", "s" }), NULL, + ril_plugin_dbus_set_default_voice_sim) }, + { } +}; + +static const GDBusSignalTable ril_plugin_dbus_signals[] = { + { GDBUS_SIGNAL(RIL_DBUS_ENABLED_MODEM_CHANGED_SIGNAL, + GDBUS_ARGS({ "modems", "ao" })) }, + { GDBUS_SIGNAL(RIL_DBUS_DEFAULT_DATA_SIM_CHANGED_SIGNAL, + GDBUS_ARGS({ "imsi", "s" })) }, + { GDBUS_SIGNAL(RIL_DBUS_DEFAULT_VOICE_SIM_CHANGED_SIGNAL, + GDBUS_ARGS({ "imsi", "s" })) }, + { GDBUS_SIGNAL(RIL_DBUS_DEFAULT_DATA_MODEM_CHANGED_SIGNAL, + GDBUS_ARGS({ "path", "s" })) }, + { GDBUS_SIGNAL(RIL_DBUS_DEFAULT_VOICE_MODEM_CHANGED_SIGNAL, + GDBUS_ARGS({ "path", "s" })) }, + { } +}; + +struct ril_plugin_dbus *ril_plugin_dbus_new(struct ril_plugin *plugin) +{ + struct ril_plugin_dbus *dbus = g_new0(struct ril_plugin_dbus, 1); + + dbus->conn = dbus_connection_ref(ofono_dbus_get_connection()); + dbus->plugin = plugin; + if (g_dbus_register_interface(dbus->conn, RIL_DBUS_PATH, + RIL_DBUS_INTERFACE, ril_plugin_dbus_methods, + ril_plugin_dbus_signals, NULL, dbus, NULL)) { + return dbus; + } else { + ofono_error("RIL D-Bus register failed"); + ril_plugin_dbus_free(dbus); + return NULL; + } +} + +void ril_plugin_dbus_free(struct ril_plugin_dbus *dbus) +{ + if (dbus) { + g_dbus_unregister_interface(dbus->conn, RIL_DBUS_PATH, + RIL_DBUS_INTERFACE); + dbus_connection_unref(dbus->conn); + g_free(dbus); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_radio_settings.c b/ofono/drivers/ril/ril_radio_settings.c new file mode 100644 index 00000000..d4ea8167 --- /dev/null +++ b/ofono/drivers/ril/ril_radio_settings.c @@ -0,0 +1,297 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "storage.h" + +struct ril_radio_settings { + GRilIoQueue *q; + int ratmode; + guint timer_id; +}; + +struct ril_radio_settings_cbd { + struct ril_radio_settings *rsd; + union _ofono_radio_settings_cb { + ofono_radio_settings_rat_mode_set_cb_t rat_mode_set; + ofono_radio_settings_rat_mode_query_cb_t rat_mode_query; + gpointer ptr; + } cb; + gpointer data; +}; + +#define RIL_STORE "rilmodem" +#define LTE_FLAG "4gOn" + +#define ril_radio_settings_cbd_free g_free + +static inline struct ril_radio_settings *ril_radio_settings_get_data( + struct ofono_radio_settings *rs) +{ + return ofono_radio_settings_get_data(rs); +} + +static struct ril_radio_settings_cbd *ril_radio_settings_cbd_new( + struct ril_radio_settings *rsd, void *cb, void *data) +{ + struct ril_radio_settings_cbd *cbd; + + cbd = g_new0(struct ril_radio_settings_cbd, 1); + cbd->rsd = rsd; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static void ril_radio_settings_submit_request(struct ril_radio_settings *rsd, + GRilIoRequest* req, guint code, GRilIoChannelResponseFunc response, + void *cb, void *data) +{ + grilio_queue_send_request_full(rsd->q, req, code, response, + ril_radio_settings_cbd_free, + ril_radio_settings_cbd_new(rsd, cb, data)); +} + +static void ril_radio_settings_set_rat_mode_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_radio_settings_cbd *cbd = user_data; + ofono_radio_settings_rat_mode_set_cb_t cb = cbd->cb.rat_mode_set; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("rat mode setting failed"); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_radio_settings_set_rat_mode(struct ofono_radio_settings *rs, + enum ofono_radio_access_mode mode, + ofono_radio_settings_rat_mode_set_cb_t cb, void *data) +{ + struct ril_radio_settings *rsd = ril_radio_settings_get_data(rs); + GRilIoRequest *req = grilio_request_sized_new(8); + int pref = rsd->ratmode; + + ofono_info("rat mode set %d", mode); + switch (mode) { + case OFONO_RADIO_ACCESS_MODE_GSM: + pref = PREF_NET_TYPE_GSM_ONLY; + break; + case OFONO_RADIO_ACCESS_MODE_UMTS: + pref = PREF_NET_TYPE_GSM_WCDMA_AUTO; /* per UI design */ + break; + case OFONO_RADIO_ACCESS_MODE_LTE: + pref = PREF_NET_TYPE_LTE_ONLY; + default: + break; + } + + grilio_request_append_int32(req, 1); /* Number of params */ + grilio_request_append_int32(req, pref); + ril_radio_settings_submit_request(rsd, req, + RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, + ril_radio_settings_set_rat_mode_cb, cb, data); + grilio_request_unref(req); +} + +static void ril_radio_settings_force_rat(struct ril_radio_settings *rsd, + int pref) +{ + if (pref != rsd->ratmode) { + GRilIoRequest *req = grilio_request_sized_new(8); + DBG("pref ril rat mode %d, ril current %d", pref, rsd->ratmode); + + grilio_request_append_int32(req, 1); + grilio_request_append_int32(req, rsd->ratmode); + grilio_queue_send_request(rsd->q, req, + RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE); + grilio_request_unref(req); + } +} + +static void ril_radio_settings_query_rat_mode_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_radio_settings_cbd *cbd = user_data; + ofono_radio_settings_rat_mode_query_cb_t cb = cbd->cb.rat_mode_query; + + DBG(""); + if (status == RIL_E_SUCCESS) { + GRilIoParser rilp; + int mode = OFONO_RADIO_ACCESS_MODE_ANY; + int pref = -1; + + grilio_parser_init(&rilp, data, len); + grilio_parser_get_int32(&rilp, NULL); + grilio_parser_get_int32(&rilp, &pref); + + switch (pref) { + case PREF_NET_TYPE_LTE_ONLY: + mode = OFONO_RADIO_ACCESS_MODE_LTE; + case PREF_NET_TYPE_GSM_ONLY: + mode = OFONO_RADIO_ACCESS_MODE_GSM; + break; + case PREF_NET_TYPE_GSM_WCDMA_AUTO:/* according to UI design */ + if (!cb) { + ril_radio_settings_force_rat(cbd->rsd, pref); + } + case PREF_NET_TYPE_WCDMA: + case PREF_NET_TYPE_GSM_WCDMA: /* according to UI design */ + mode = OFONO_RADIO_ACCESS_MODE_UMTS; + break; + case PREF_NET_TYPE_LTE_CDMA_EVDO: + case PREF_NET_TYPE_LTE_GSM_WCDMA: + case PREF_NET_TYPE_LTE_CMDA_EVDO_GSM_WCDMA: + if (!cb) { + ril_radio_settings_force_rat(cbd->rsd, pref); + } + break; + case PREF_NET_TYPE_CDMA_EVDO_AUTO: + case PREF_NET_TYPE_CDMA_ONLY: + case PREF_NET_TYPE_EVDO_ONLY: + case PREF_NET_TYPE_GSM_WCDMA_CDMA_EVDO_AUTO: + default: + break; + } + + ofono_info("rat mode %d (ril %d)", mode, pref); + if (cb) { + cb(ril_error_ok(&error), mode, cbd->data); + } + } else { + ofono_error("rat mode query failed"); + if (cb) { + cb(ril_error_failure(&error), -1, cbd->data); + } + } +} + +static void ril_radio_settings_query_rat_mode(struct ofono_radio_settings *rs, + ofono_radio_settings_rat_mode_query_cb_t cb, void *data) +{ + struct ril_radio_settings *rsd = ril_radio_settings_get_data(rs); + + ofono_info("rat mode query"); + ril_radio_settings_submit_request(rsd, NULL, + RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, + ril_radio_settings_query_rat_mode_cb, cb, data); +} + +static gboolean ril_radio_settings_get_config(struct ril_radio_settings *rsd) +{ + gboolean needsconfig = FALSE; + gboolean value = FALSE; + + /* Hmm... One file shared by all modems... Why?? */ + + GKeyFile *keyfile = storage_open(NULL, RIL_STORE); + char **alreadyset = g_key_file_get_groups(keyfile, NULL); + + if (alreadyset[0]) + value = g_key_file_get_boolean( + keyfile, alreadyset[0], LTE_FLAG, NULL); + else if (rsd->ratmode == PREF_NET_TYPE_GSM_WCDMA_AUTO) + value = TRUE; + + if (!value && rsd->ratmode == PREF_NET_TYPE_LTE_GSM_WCDMA) { + g_key_file_set_boolean(keyfile, + LTE_FLAG, LTE_FLAG, TRUE); + needsconfig = TRUE; + } else if (value && rsd->ratmode == PREF_NET_TYPE_GSM_WCDMA_AUTO) { + g_key_file_set_boolean(keyfile, + LTE_FLAG, LTE_FLAG, FALSE); + needsconfig = TRUE; + } + + g_strfreev(alreadyset); + storage_close(NULL, RIL_STORE, keyfile, TRUE); + + DBG("needsconfig %d, rat mode %d", needsconfig, rsd->ratmode); + return needsconfig; +} + +static gboolean ril_radio_settings_register(gpointer user_data) +{ + struct ofono_radio_settings *rs = user_data; + struct ril_radio_settings *rsd = ril_radio_settings_get_data(rs); + + rsd->timer_id = 0; + ofono_radio_settings_register(rs); + + if (ril_radio_settings_get_config(rsd)) { + ril_radio_settings_submit_request(rsd, NULL, + RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, + ril_radio_settings_query_rat_mode_cb, NULL, NULL); + } + + /* Single shot */ + return FALSE; +} + +static int ril_radio_settings_probe(struct ofono_radio_settings *rs, + unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_radio_settings *rsd = g_new0(struct ril_radio_settings, 1); + + DBG(""); + rsd->q = grilio_queue_new(ril_modem_io(modem)); + rsd->ratmode = ril_modem_4g_enabled(modem) ? + PREF_NET_TYPE_LTE_GSM_WCDMA : + PREF_NET_TYPE_GSM_WCDMA_AUTO; + + rsd->timer_id = g_idle_add(ril_radio_settings_register, rs); + ofono_radio_settings_set_data(rs, rsd); + return 0; +} + +static void ril_radio_settings_remove(struct ofono_radio_settings *rs) +{ + struct ril_radio_settings *rd = ril_radio_settings_get_data(rs); + + DBG(""); + ofono_radio_settings_set_data(rs, NULL); + if (rd->timer_id > 0) { + g_source_remove(rd->timer_id); + } + + grilio_queue_cancel_all(rd->q, FALSE); + grilio_queue_unref(rd->q); + g_free(rd); +} + +const struct ofono_radio_settings_driver ril_radio_settings_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_radio_settings_probe, + .remove = ril_radio_settings_remove, + .query_rat_mode = ril_radio_settings_query_rat_mode, + .set_rat_mode = ril_radio_settings_set_rat_mode, +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_sim.c b/ofono/drivers/ril/ril_sim.c new file mode 100644 index 00000000..e31bb3b3 --- /dev/null +++ b/ofono/drivers/ril/ril_sim.c @@ -0,0 +1,1230 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_constants.h" +#include "ril_log.h" + +#include "simutil.h" +#include "util.h" +#include "ofono.h" + +#define SIM_STATUS_RETRY_SECS (2) + +/* + * TODO: + * 1. Define constants for hex literals + * 2. Document P1-P3 usage (+CSRM) + */ + +#define EF_STATUS_INVALIDATED 0 +#define EF_STATUS_VALID 1 + +/* Commands defined for TS 27.007 +CRSM */ +#define CMD_READ_BINARY 176 /* 0xB0 */ +#define CMD_READ_RECORD 178 /* 0xB2 */ +#define CMD_GET_RESPONSE 192 /* 0xC0 */ +#define CMD_UPDATE_BINARY 214 /* 0xD6 */ +#define CMD_UPDATE_RECORD 220 /* 0xDC */ +#define CMD_STATUS 242 /* 0xF2 */ +#define CMD_RETRIEVE_DATA 203 /* 0xCB */ +#define CMD_SET_DATA 219 /* 0xDB */ + +/* FID/path of SIM/USIM root directory */ +#define ROOTMF "3F00" + +/* RIL_Request* parameter counts */ +#define GET_IMSI_NUM_PARAMS 1 +#define ENTER_SIM_PIN_PARAMS 2 +#define SET_FACILITY_LOCK_PARAMS 5 +#define ENTER_SIM_PUK_PARAMS 3 +#define CHANGE_SIM_PIN_PARAMS 3 + +#define MAX_UICC_APPS 16 + +struct ril_sim_status { + guint card_state; + guint pin_state; + guint gsm_umts_index; + guint cdma_index; + guint ims_index; + guint num_apps; +}; + +struct ril_sim_app { + guint app_type; + guint app_state; + guint perso_substate; + char *aid_str; + char *app_str; + guint pin_replaced; + guint pin1_state; + guint pin2_state; +}; + +/* + * TODO: CDMA/IMS + * + * This code currently only grabs the AID/application ID from + * the gsm_umts application on the SIM card. This code will + * need to be modified for CDMA support, and possibly IMS-based + * applications. In this case, app_id should be changed to an + * array or HashTable of app_status structures. + * + * The same applies to the app_type. + */ +struct ril_sim { + GRilIoChannel *io; + GRilIoQueue *q; + GRilIoQueue *q2; + struct ofono_sim *sim; + guint slot; + gchar *aid_str; + int app_type; + gchar *app_str; + guint app_index; + enum ofono_sim_password_type passwd_type; + int retries[OFONO_SIM_PASSWORD_INVALID]; + enum ofono_sim_password_type passwd_state; + gboolean initialized; + gboolean removed; + guint retry_status_timer_id; + guint idle_id; + guint status_req_id; + gulong event_id; +}; + +struct ril_sim_cbd { + struct ril_sim *sd; + union _ofono_sim_cb { + ofono_sim_file_info_cb_t file_info; + ofono_sim_read_cb_t read; + ofono_sim_imsi_cb_t imsi; + ofono_sim_passwd_cb_t passwd; + ofono_sim_lock_unlock_cb_t lock_unlock; + gpointer ptr; + } cb; + gpointer data; +}; + +#define ril_sim_cbd_free g_free + +static void ril_sim_request_status(struct ril_sim *sd); + +static inline struct ril_sim *ril_sim_get_data(struct ofono_sim *sim) +{ + return ofono_sim_get_data(sim); +} + +static struct ril_sim_cbd *ril_sim_cbd_new(struct ril_sim *sd, void *cb, + void *data) +{ + struct ril_sim_cbd *cbd = g_new0(struct ril_sim_cbd, 1); + + cbd->sd = sd; + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +int ril_sim_app_type(struct ofono_sim *sim) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + return sd ? sd->app_type : RIL_APPTYPE_UNKNOWN; +} + +static void ril_sim_append_path(struct ril_sim *sd, GRilIoRequest *req, + const int fileid, const guchar *path, const guint path_len) +{ + guchar db_path[6] = { 0x00 }; + char *hex_path = NULL; + int len = 0; + + DBG(""); + + if (path_len > 0 && path_len < 7) { + memcpy(db_path, path, path_len); + len = path_len; + } else if (sd->app_type == RIL_APPTYPE_USIM) { + len = sim_ef_db_get_path_3g(fileid, db_path); + } else if (sd->app_type == RIL_APPTYPE_SIM) { + len = sim_ef_db_get_path_2g(fileid, db_path); + } else { + ofono_error("Unsupported app_type: 0x%x", sd->app_type); + } + + if (len > 0) { + hex_path = encode_hex(db_path, len, 0); + grilio_request_append_utf8(req, hex_path); + DBG("%s", hex_path); + g_free(hex_path); + } else if (fileid == SIM_EF_ICCID_FILEID || fileid == SIM_EFPL_FILEID) { + /* + * Special catch-all for EF_ICCID (unique card ID) + * and EF_PL files which exist in the root directory. + * As the sim_info_cb function may not have yet + * recorded the app_type for the SIM, and the path + * for both files is the same for 2g|3g, just hard-code. + * + * See 'struct ef_db' in: + * ../../src/simutil.c for more details. + */ + DBG("%s", ROOTMF); + grilio_request_append_utf8(req, ROOTMF); + } else { + /* + * The only known case of this is EFPHASE_FILED (0x6FAE). + * The ef_db table ( see /src/simutil.c ) entry for + * EFPHASE contains a value of 0x0000 for it's + * 'parent3g' member. This causes a NULL path to + * be returned. + */ + + DBG("db_get_path*() returned empty path."); + grilio_request_append_utf8(req, NULL); + } +} + +static guchar *ril_sim_parse_io_response(const void *data, guint len, + int *sw1, int *sw2, int *hex_len) +{ + GRilIoParser rilp; + char *response = NULL; + guchar *hex_response = NULL; + + /* Minimum length of SIM_IO_Response is 12: + * sw1 (int32) + * sw2 (int32) + * simResponse (string) + */ + if (len < 12) { + ofono_error("SIM IO reply too small (< 12): %d", len); + return NULL; + } + + DBG("length is: %d", len); + grilio_parser_init(&rilp, data, len); + grilio_parser_get_int32(&rilp, sw1); + grilio_parser_get_int32(&rilp, sw2); + + response = grilio_parser_get_utf8(&rilp); + if (response) { + long items_written = 0; + const long response_len = strlen(response); + DBG("response is set; len is: %ld", response_len); + hex_response = decode_hex(response, response_len, + &items_written, -1); + *hex_len = items_written; + } + + DBG("sw1=0x%.2X,sw2=0x%.2X,%s", *sw1, *sw2, response); + g_free(response); + return hex_response; +} + +static void ril_sim_file_info_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_sim_cbd *cbd = user_data; + ofono_sim_file_info_cb_t cb = cbd->cb.file_info; + struct ril_sim *sd = cbd->sd; + struct ofono_error error; + gboolean ok = FALSE; + int sw1 = 0, sw2 = 0, response_len = 0; + int flen = 0, rlen = 0, str = 0; + guchar *response = NULL; + guchar access[3] = { 0x00, 0x00, 0x00 }; + guchar file_status = EF_STATUS_VALID; + + DBG(""); + + /* In case sim card has been removed prior to this callback has been + * called we must not call the core call back method as otherwise the + * core will crash. + */ + if (sd->removed == TRUE) { + ofono_error("RIL_CARDSTATE_ABSENT"); + return; + } + + error.error = 0; + error.type = OFONO_ERROR_TYPE_FAILURE; + if (status != RIL_E_SUCCESS) { + DBG("Reply failure: %s", ril_error_to_string(status)); + goto error; + } + + if ((response = ril_sim_parse_io_response(data, len, + &sw1, &sw2, &response_len)) == NULL) { + ofono_error("Can't parse SIM IO response"); + goto error; + } + + if ((sw1 != 0x90 && sw1 != 0x91 && sw1 != 0x92 && sw1 != 0x9f) || + (sw1 == 0x90 && sw2 != 0x00)) { + ofono_error("Invalid values: sw1: %02x sw2: %02x", sw1, sw2); + error.type = OFONO_ERROR_TYPE_SIM; + error.error = (sw1 << 8) | sw2; + goto error; + } + + if (response_len) { + if (response[0] == 0x62) { + ok = sim_parse_3g_get_response( + response, response_len, + &flen, &rlen, &str, access, NULL); + } else { + ok = sim_parse_2g_get_response( + response, response_len, + &flen, &rlen, &str, access, &file_status); + } + } + + if (!ok) { + ofono_error("%s parse response failed", __func__); + goto error; + } + + error.type = OFONO_ERROR_TYPE_NO_ERROR; + cb(&error, flen, str, rlen, access, file_status, cbd->data); + g_free(response); + return; + +error: + cb(&error, -1, -1, -1, NULL, EF_STATUS_INVALIDATED, cbd->data); + g_free(response); +} + +static guint ril_sim_request_io(struct ril_sim *sd, GRilIoQueue *q, int fileid, + guint cmd, guint p1, guint p2, guint p3, const guchar *path, + unsigned int path_len, GRilIoChannelResponseFunc cb, + struct ril_sim_cbd *cbd) +{ + guint id; + GRilIoRequest *req = grilio_request_sized_new(80); + + DBG("cmd=0x%.2X,efid=0x%.4X,%d,%d,%d,(null),pin2=(null),aid=%s", + cmd, fileid, p1, p2, p3, sd->aid_str); + + grilio_request_append_int32(req, cmd); + grilio_request_append_int32(req, fileid); + ril_sim_append_path(sd, req, fileid, path, path_len); + grilio_request_append_int32(req, p1); /* P1 */ + grilio_request_append_int32(req, p2); /* P2 */ + grilio_request_append_int32(req, p3); /* P3 */ + grilio_request_append_utf8(req, NULL); /* data; only for writes */ + grilio_request_append_utf8(req, NULL); /* pin2; only for writes */ + grilio_request_append_utf8(req, sd->aid_str); + + id = grilio_queue_send_request_full(q, req, RIL_REQUEST_SIM_IO, + cb, ril_sim_cbd_free, cbd); + grilio_request_unref(req); + return id; +} + +static void ril_sim_internal_read_file_info(struct ril_sim *sd, GRilIoQueue *q, + int fileid, const unsigned char *path, unsigned int path_len, + ofono_sim_file_info_cb_t cb, void *data) +{ + if (!sd || !ril_sim_request_io(sd, q, fileid, CMD_GET_RESPONSE, + 0, 0, 15, path, path_len, ril_sim_file_info_cb, + ril_sim_cbd_new(sd, cb, data))) { + struct ofono_error error; + cb(ril_error_failure(&error), -1, -1, -1, NULL, + EF_STATUS_INVALIDATED, data); + } +} + +static void ril_sim_ofono_read_file_info(struct ofono_sim *sim, int fileid, + const unsigned char *path, unsigned int len, + ofono_sim_file_info_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + ril_sim_internal_read_file_info(sd, sd->q, fileid, path, len, cb, data); +} + +void ril_sim_read_file_info(struct ofono_sim *sim, int fileid, + const unsigned char *path, unsigned int path_len, + ofono_sim_file_info_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + ril_sim_internal_read_file_info(sd, sd->q2, fileid, path, path_len, + cb, data); +} + +static void ril_sim_read_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_sim_cbd *cbd = user_data; + ofono_sim_read_cb_t cb = cbd->cb.read; + struct ofono_error error; + int sw1 = 0, sw2 = 0, response_len = 0; + guchar *response = NULL; + + DBG(""); + if (status != RIL_E_SUCCESS) { + ofono_error("Error: %s", ril_error_to_string(status)); + goto error; + } + + if ((response = ril_sim_parse_io_response(data, len, + &sw1, &sw2, &response_len)) == NULL) { + ofono_error("Error parsing IO response"); + goto error; + } + + cb(ril_error_ok(&error), response, response_len, cbd->data); + g_free(response); + return; + +error: + cb(ril_error_failure(&error), NULL, 0, cbd->data); +} + +static void ril_sim_read(struct ril_sim *sd, GRilIoQueue *q, int fileid, + guint cmd, guint p1, guint p2, guint p3, const guchar *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data) +{ + if (!sd || !ril_sim_request_io(sd, q, fileid, cmd, p1, p2, p3, path, + path_len, ril_sim_read_cb, ril_sim_cbd_new(sd, cb, data))) { + struct ofono_error error; + cb(ril_error_failure(&error), NULL, 0, data); + } +} + +static inline void ril_sim_internal_read_file_transparent(struct ril_sim *sd, + GRilIoQueue *q, int fileid, int start, int length, + const unsigned char *path, unsigned int path_len, + ofono_sim_read_cb_t cb, void *data) +{ + ril_sim_read(sd, q, fileid, CMD_READ_BINARY, (start >> 8), + (start & 0xff), length, path, path_len, cb, data); +} + +static void ril_sim_ofono_read_file_transparent(struct ofono_sim *sim, + int fileid, int start, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + ril_sim_internal_read_file_transparent(sd, sd->q, fileid, start, length, + path, path_len, cb, data); +} + +void ril_sim_read_file_transparent(struct ofono_sim *sim, int fileid, + int start, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + ril_sim_internal_read_file_transparent(sd, sd->q2, fileid, start, + length, path, path_len, cb, data); +} + +static inline void ril_sim_internal_read_file_linear(struct ril_sim *sd, + GRilIoQueue *q, int fileid, int record, int length, + const unsigned char *path, unsigned int path_len, + ofono_sim_read_cb_t cb, void *data) +{ + ril_sim_read(sd, q, fileid, CMD_READ_RECORD, record, 4, length, + path, path_len, cb, data); +} + +static void ril_sim_ofono_read_file_linear(struct ofono_sim *sim, int fileid, + int record, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + ril_sim_internal_read_file_linear(sd, sd->q, fileid, record, length, + path, path_len, cb, data); +} + +void ril_sim_read_file_linear(struct ofono_sim *sim, int fileid, + int record, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + ril_sim_internal_read_file_linear(sd, sd->q2, fileid, record, length, + path, path_len, cb, data); +} + +void ril_sim_read_file_cyclic(struct ofono_sim *sim, int fileid, + int rec, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data) +{ + /* Hmmm... Is this right? */ + ril_sim_read_file_linear(sim, fileid, rec, length, path, path_len, + cb, data); +} + +static void ril_sim_ofono_read_file_cyclic(struct ofono_sim *sim, int fileid, + int rec, int length, const unsigned char *path, + unsigned int path_len, ofono_sim_read_cb_t cb, void *data) +{ + /* Hmmm... Is this right? */ + ril_sim_ofono_read_file_linear(sim, fileid, rec, length, path, path_len, + cb, data); +} + +static void ril_sim_get_imsi_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_sim_cbd *cbd = user_data; + ofono_sim_imsi_cb_t cb = cbd->cb.imsi; + struct ofono_error error; + + if (status == RIL_E_SUCCESS) { + gchar *imsi; + GRilIoParser rilp; + grilio_parser_init(&rilp, data, len); + imsi = grilio_parser_get_utf8(&rilp); + DBG("%s", imsi); + if (imsi) { + /* 15 is the max length of IMSI */ + GASSERT(strlen(imsi) == 15); + cb(ril_error_ok(&error), imsi, cbd->data); + g_free(imsi); + return; + } + } else { + ofono_error("Reply failure: %s", ril_error_to_string(status)); + } + + cb(ril_error_failure(&error), NULL, cbd->data); +} + +static void ril_sim_read_imsi(struct ofono_sim *sim, ofono_sim_imsi_cb_t cb, + void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + GRilIoRequest *req = grilio_request_sized_new(60); + + DBG("%s", sd->aid_str); + grilio_request_append_int32(req, GET_IMSI_NUM_PARAMS); + grilio_request_append_utf8(req, sd->aid_str); + grilio_queue_send_request_full(sd->q, req, RIL_REQUEST_GET_IMSI, + ril_sim_get_imsi_cb, ril_sim_cbd_free, + ril_sim_cbd_new(sd, cb, data)); + grilio_request_unref(req); +} + +static void ril_sim_configure_app(struct ril_sim *sd, + struct ril_sim_app **apps, guint index) +{ + const struct ril_sim_app *app = apps[index]; + + sd->app_type = app->app_type; + + g_free(sd->aid_str); + sd->aid_str = g_strdup(app->aid_str); + + g_free(sd->app_str); + sd->app_str = g_strdup(app->app_str); + + sd->app_index = index; + + DBG("setting aid_str (AID) to: %s", sd->aid_str); + switch (app->app_state) { + case RIL_APPSTATE_PIN: + sd->passwd_state = OFONO_SIM_PASSWORD_SIM_PIN; + break; + case RIL_APPSTATE_PUK: + sd->passwd_state = OFONO_SIM_PASSWORD_SIM_PUK; + break; + case RIL_APPSTATE_SUBSCRIPTION_PERSO: + switch (app->perso_substate) { + case RIL_PERSOSUBSTATE_SIM_NETWORK: + sd->passwd_state = OFONO_SIM_PASSWORD_PHNET_PIN; + break; + case RIL_PERSOSUBSTATE_SIM_NETWORK_SUBSET: + sd->passwd_state = OFONO_SIM_PASSWORD_PHNETSUB_PIN; + break; + case RIL_PERSOSUBSTATE_SIM_CORPORATE: + sd->passwd_state = OFONO_SIM_PASSWORD_PHCORP_PIN; + break; + case RIL_PERSOSUBSTATE_SIM_SERVICE_PROVIDER: + sd->passwd_state = OFONO_SIM_PASSWORD_PHSP_PIN; + break; + case RIL_PERSOSUBSTATE_SIM_SIM: + sd->passwd_state = OFONO_SIM_PASSWORD_PHSIM_PIN; + break; + case RIL_PERSOSUBSTATE_SIM_NETWORK_PUK: + sd->passwd_state = OFONO_SIM_PASSWORD_PHNET_PUK; + break; + case RIL_PERSOSUBSTATE_SIM_NETWORK_SUBSET_PUK: + sd->passwd_state = OFONO_SIM_PASSWORD_PHNETSUB_PUK; + break; + case RIL_PERSOSUBSTATE_SIM_CORPORATE_PUK: + sd->passwd_state = OFONO_SIM_PASSWORD_PHCORP_PUK; + break; + case RIL_PERSOSUBSTATE_SIM_SERVICE_PROVIDER_PUK: + sd->passwd_state = OFONO_SIM_PASSWORD_PHSP_PUK; + break; + case RIL_PERSOSUBSTATE_SIM_SIM_PUK: + sd->passwd_state = OFONO_SIM_PASSWORD_PHFSIM_PUK; + break; + default: + sd->passwd_state = OFONO_SIM_PASSWORD_NONE; + break; + }; + break; + case RIL_APPSTATE_READY: + sd->passwd_state = OFONO_SIM_PASSWORD_NONE; + break; + case RIL_APPSTATE_UNKNOWN: + case RIL_APPSTATE_DETECTED: + default: + sd->passwd_state = OFONO_SIM_PASSWORD_INVALID; + break; + } +} + +static void ril_sim_set_uicc_subscription(struct ril_sim *sd, + int app_index, int sub_status) +{ + GRilIoRequest *req = grilio_request_sized_new(16); + const guint sub_id = sd->slot; + + DBG("%d,%d,%d,%d", sd->slot, app_index, sub_id, sub_status); + grilio_request_append_int32(req, sd->slot); + grilio_request_append_int32(req, app_index); + grilio_request_append_int32(req, sub_id); + grilio_request_append_int32(req, sub_status); + grilio_queue_send_request(sd->q, req, + RIL_REQUEST_SET_UICC_SUBSCRIPTION); + grilio_request_unref(req); +} + +static int ril_sim_select_uicc_subscription(struct ril_sim *sim, + struct ril_sim_app **apps, guint num_apps) +{ + int selected_app = -1; + guint i; + + for (i = 0; i < num_apps; i++) { + const int type = apps[i]->app_type; + if (type == RIL_APPTYPE_USIM || type == RIL_APPTYPE_RUIM) { + selected_app = i; + break; + } else if (type != RIL_APPTYPE_UNKNOWN && selected_app == -1) { + selected_app = i; + } + } + + DBG("Select app %d for subscription.", selected_app); + if (selected_app != -1) { + /* Number 1 means activate that app */ + ril_sim_set_uicc_subscription(sim, selected_app, 1); + } + + return selected_app; +} + +static gboolean ril_sim_parse_status_response(const void *data, guint len, + struct ril_sim_status *status, struct ril_sim_app **apps) +{ + GRilIoParser rilp; + int i; + + grilio_parser_init(&rilp, data, len); + + /* + * FIXME: Need to come up with a common scheme for verifying the + * size of RIL message and properly reacting to bad messages. + * This could be a runtime assertion, disconnect, drop/ignore + * the message, ... + * + * 20 is the min length of RIL_CardStatus_v6 as the AppState + * array can be 0-length. + */ + if (len < 20) { + ofono_error("SIM_STATUS reply too small: %d bytes", len); + status->card_state = RIL_CARDSTATE_ERROR; + return FALSE; + } + + grilio_parser_get_uint32(&rilp, &status->card_state); + + /* + * NOTE: + * + * The global pin_status is used for multi-application + * UICC cards. For example, there are SIM cards that + * can be used in both GSM and CDMA phones. Instead + * of managed PINs for both applications, a global PIN + * is set instead. It's not clear at this point if + * such SIM cards are supported by ofono or RILD. + */ + grilio_parser_get_uint32(&rilp, &status->pin_state); + grilio_parser_get_uint32(&rilp, &status->gsm_umts_index); + grilio_parser_get_uint32(&rilp, &status->cdma_index); + grilio_parser_get_uint32(&rilp, &status->ims_index); + grilio_parser_get_uint32(&rilp, &status->num_apps); + + DBG("card_state=%d, universal_pin_state=%d, gsm_umts_index=%d, " + "cdma_index=%d, ims_index=%d", status->card_state, + status->pin_state, status->gsm_umts_index, + status->cdma_index, status->ims_index); + + if (status->card_state != RIL_CARDSTATE_PRESENT) { + return FALSE; + } + + DBG("sim num_apps: %d", status->num_apps); + if (status->num_apps > MAX_UICC_APPS) { + ofono_error("SIM error; too many apps: %d", status->num_apps); + status->num_apps = MAX_UICC_APPS; + } + + for (i = 0; i < status->num_apps; i++) { + apps[i] = g_try_new0(struct ril_sim_app, 1); + grilio_parser_get_uint32(&rilp, &apps[i]->app_type); + grilio_parser_get_uint32(&rilp, &apps[i]->app_state); + + /* + * Consider RIL_APPSTATE_ILLEGAL also READY. Even if app state + * is RIL_APPSTATE_ILLEGAL (-1), ICC operations must be + * permitted. Network access requests will anyway be rejected + * and ME will be in limited service. + */ + if (apps[i]->app_state == RIL_APPSTATE_ILLEGAL) { + DBG("RIL_APPSTATE_ILLEGAL => RIL_APPSTATE_READY"); + apps[i]->app_state = RIL_APPSTATE_READY; + } + + grilio_parser_get_uint32(&rilp, &apps[i]->perso_substate); + + /* TODO: we need a way to instruct parcel to skip + * a string, without allocating memory... + */ + apps[i]->aid_str = grilio_parser_get_utf8(&rilp); /* app ID */ + apps[i]->app_str = grilio_parser_get_utf8(&rilp); /* label */ + + grilio_parser_get_uint32(&rilp, &apps[i]->pin_replaced); + grilio_parser_get_uint32(&rilp, &apps[i]->pin1_state); + grilio_parser_get_uint32(&rilp, &apps[i]->pin2_state); + + DBG("app[%d]: type=%d, state=%d, perso_substate=%d, " + "aid_ptr=%s, app_label_ptr=%s, pin1_replaced=%d, " + "pin1=%d, pin2=%d", i, apps[i]->app_type, + apps[i]->app_state, apps[i]->perso_substate, + apps[i]->aid_str, apps[i]->app_str, + apps[i]->pin_replaced, apps[i]->pin1_state, + apps[i]->pin2_state); + } + + return TRUE; +} + +static void ril_sim_free_apps(struct ril_sim_app **apps, guint num_apps) +{ + guint i; + + for (i = 0; i < num_apps; i++) { + g_free(apps[i]->aid_str); + g_free(apps[i]->app_str); + g_free(apps[i]); + } +} + +static gboolean ril_sim_status_retry(gpointer user_data) +{ + struct ril_sim *sd = user_data; + + DBG("[%u]", sd->slot); + GASSERT(sd->retry_status_timer_id); + sd->retry_status_timer_id = 0; + ril_sim_request_status(sd); + return FALSE; +} + +static void ril_sim_status_cb(GRilIoChannel *io, int ril_status, + const void *data, guint len, void *user_data) +{ + struct ril_sim *sd = user_data; + struct ril_sim_app *apps[MAX_UICC_APPS]; + struct ril_sim_status status; + + DBG("[%u]", sd->slot); + sd->status_req_id = 0; + + if (ril_status != RIL_E_SUCCESS) { + ofono_error("SIM status request failed: %s", + ril_error_to_string(ril_status)); + if (!sd->retry_status_timer_id) { + sd->retry_status_timer_id = + g_timeout_add_seconds(SIM_STATUS_RETRY_SECS, + ril_sim_status_retry, sd); + + } + } else if (ril_sim_parse_status_response(data, len, &status, apps) && + status.num_apps) { + + int app_index = status.gsm_umts_index; + + if (app_index < 0) { + app_index = ril_sim_select_uicc_subscription(sd, + apps, status.num_apps); + } + if (app_index >= 0 && app_index < (int)status.num_apps && + apps[app_index]->app_type != RIL_APPTYPE_UNKNOWN) { + ril_sim_configure_app(sd, apps, app_index); + } + + sd->removed = FALSE; + + if (sd->passwd_state != OFONO_SIM_PASSWORD_INVALID) { + /* + * ril_sim_parse_status_response returns true only when + * card status is RIL_CARDSTATE_PRESENT, + * ofono_sim_inserted_notify returns if status doesn't + * change. So can notify core always in this branch. + */ + ofono_sim_inserted_notify(sd->sim, TRUE); + + /* TODO: There doesn't seem to be any other + * way to force the core SIM code to + * recheck the PIN. + * Wouldn't __ofono_sim_refresh be + * more appropriate call here?? + * __ofono_sim_refresh(sim, NULL, TRUE, TRUE); + */ + __ofono_sim_recheck_pin(sd->sim); + } + + ril_sim_free_apps(apps, status.num_apps); + } else if (status.card_state == RIL_CARDSTATE_ABSENT) { + guint i; + ofono_info("RIL_CARDSTATE_ABSENT"); + + sd->passwd_state = OFONO_SIM_PASSWORD_INVALID; + for (i = 0; i < OFONO_SIM_PASSWORD_INVALID; i++) { + sd->retries[i] = -1; + } + + sd->removed = TRUE; + sd->initialized = FALSE; + + ofono_sim_inserted_notify(sd->sim, FALSE); + } +} + +static void ril_sim_request_status(struct ril_sim *sd) +{ + if (!sd->status_req_id) { + sd->status_req_id = grilio_queue_send_request_full(sd->q, + NULL, RIL_REQUEST_GET_SIM_STATUS, + ril_sim_status_cb, NULL, sd); + } +} + +static void ril_sim_status_changed(GRilIoChannel *io, guint code, + const void *data, guint len, void *user_data) +{ + struct ril_sim *sd = user_data; + + GASSERT(code == RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED); + ril_sim_request_status(sd); +} + +static void ril_sim_query_pin_retries(struct ofono_sim *sim, + ofono_sim_pin_retries_cb_t cb, + void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + struct ofono_error error; + + cb(ril_error_ok(&error), sd->retries, data); +} + +static void ril_sim_query_passwd_state_cb(GRilIoChannel *io, int err, + const void *data, guint len, void *user_data) +{ + struct ril_sim_cbd *cbd = user_data; + struct ril_sim *sd = cbd->sd; + ofono_sim_passwd_cb_t cb = cbd->cb.passwd; + struct ril_sim_app *apps[MAX_UICC_APPS]; + struct ril_sim_status status; + const gint state = ofono_sim_get_state(sd->sim); + + if (ril_sim_parse_status_response(data, len, &status, apps) && + status.num_apps) { + const int app_index = status.gsm_umts_index; + + if (app_index >= 0 && app_index < (int)status.num_apps && + apps[app_index]->app_type != RIL_APPTYPE_UNKNOWN) { + ril_sim_configure_app(sd, apps, app_index); + } + + ril_sim_free_apps(apps, status.num_apps); + } + + DBG("passwd_state %u", sd->passwd_state); + + /* if pin code required cannot be initialized yet*/ + if (sd->passwd_state == OFONO_SIM_PASSWORD_SIM_PIN) { + sd->initialized = FALSE; + } + + /* + * To prevent double call to sim_initialize_after_pin from + * sim_pin_query_cb we must prevent calling sim_pin_query_cb + * when !OFONO_SIM_STATE_READY && OFONO_SIM_PASSWORD_NONE + */ + if ((state == OFONO_SIM_STATE_READY) || (sd->initialized == FALSE) || + (sd->passwd_state != OFONO_SIM_PASSWORD_NONE)){ + struct ofono_error error; + + if (sd->passwd_state == OFONO_SIM_PASSWORD_NONE) { + sd->initialized = TRUE; + } + + if (state == OFONO_SIM_STATE_LOCKED_OUT) { + sd->initialized = FALSE; + } + + if (sd->passwd_state == OFONO_SIM_PASSWORD_INVALID) { + cb(ril_error_failure(&error), -1, cbd->data); + } else { + cb(ril_error_ok(&error), sd->passwd_state, cbd->data); + } + } +} + +static void ril_sim_query_passwd_state(struct ofono_sim *sim, + ofono_sim_passwd_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + grilio_queue_send_request_full(sd->q, NULL, + RIL_REQUEST_GET_SIM_STATUS, ril_sim_query_passwd_state_cb, + ril_sim_cbd_free, ril_sim_cbd_new(sd, cb, data)); +} + +static void ril_sim_pin_change_state_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_sim_cbd *cbd = user_data; + ofono_sim_lock_unlock_cb_t cb = cbd->cb.lock_unlock; + struct ril_sim *sd = cbd->sd; + struct ofono_error error; + GRilIoParser rilp; + int retry_count = 0; + int passwd_type = sd->passwd_type; + int i; + + /* There is no reason to ask SIM status until + * unsolicited sim status change indication + * Looks like state does not change before that. + */ + + grilio_parser_init(&rilp, data, len); + grilio_parser_get_int32(&rilp, NULL); + grilio_parser_get_int32(&rilp, &retry_count); + + for (i = 0; i < OFONO_SIM_PASSWORD_INVALID; i++) { + sd->retries[i] = -1; + } + + sd->retries[passwd_type] = retry_count; + + DBG("result=%d passwd_type=%d retry_count=%d", + status, passwd_type, retry_count); + + error.error = 0; + error.type = (status == RIL_E_SUCCESS) ? + OFONO_ERROR_TYPE_NO_ERROR : + OFONO_ERROR_TYPE_FAILURE; + + cb(&error, cbd->data); +} + +static void ril_sim_pin_send(struct ofono_sim *sim, const char *passwd, + ofono_sim_lock_unlock_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + GRilIoRequest *req = grilio_request_sized_new(60); + + /* Should passwd_type be stored in cbd? */ + sd->passwd_type = OFONO_SIM_PASSWORD_SIM_PIN; + grilio_request_append_int32(req, ENTER_SIM_PIN_PARAMS); + grilio_request_append_utf8(req, passwd); + grilio_request_append_utf8(req, sd->aid_str); + + DBG("%s,aid=%s", passwd, sd->aid_str); + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_ENTER_SIM_PIN, ril_sim_pin_change_state_cb, + ril_sim_cbd_free, ril_sim_cbd_new(sd, cb, data)); + grilio_request_unref(req); +} + +static guint ril_perso_change_state(struct ofono_sim *sim, + enum ofono_sim_password_type passwd_type, int enable, + const char *passwd, ofono_sim_lock_unlock_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + GRilIoRequest *req = NULL; + int code = 0; + guint id = 0; + + switch (passwd_type) { + case OFONO_SIM_PASSWORD_PHNET_PIN: + if (!enable) { + code = RIL_REQUEST_ENTER_NETWORK_DEPERSONALIZATION; + req = grilio_request_sized_new(12); + grilio_request_append_int32(req, + RIL_PERSOSUBSTATE_SIM_NETWORK); + grilio_request_append_utf8(req, passwd); + } else { + DBG("Not supported, enable=%d", enable); + } + break; + default: + DBG("Not supported, type=%d", passwd_type); + break; + } + + if (req) { + sd->passwd_type = passwd_type; + id = grilio_queue_send_request_full(sd->q, req, code, + ril_sim_pin_change_state_cb, ril_sim_cbd_free, + ril_sim_cbd_new(sd, cb, data)); + grilio_request_unref(req); + } + + return id; +} + +static void ril_sim_pin_change_state(struct ofono_sim *sim, + enum ofono_sim_password_type passwd_type, int enable, + const char *passwd, ofono_sim_lock_unlock_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + struct ofono_error error; + const char *type_str = NULL; + guint id = 0; + + switch (passwd_type) { + case OFONO_SIM_PASSWORD_SIM_PIN: + type_str = "SC"; + break; + case OFONO_SIM_PASSWORD_PHSIM_PIN: + type_str = "PS"; + break; + case OFONO_SIM_PASSWORD_PHFSIM_PIN: + type_str = "PF"; + break; + case OFONO_SIM_PASSWORD_SIM_PIN2: + type_str = "P2"; + break; + case OFONO_SIM_PASSWORD_PHNET_PIN: + id = ril_perso_change_state(sim, passwd_type, enable, passwd, + cb, data); + break; + case OFONO_SIM_PASSWORD_PHNETSUB_PIN: + type_str = "PU"; + break; + case OFONO_SIM_PASSWORD_PHSP_PIN: + type_str = "PP"; + break; + case OFONO_SIM_PASSWORD_PHCORP_PIN: + type_str = "PC"; + break; + default: + break; + } + + DBG("%d,%s,%d,%s,0,aid=%s", passwd_type, type_str, enable, + passwd, sd->aid_str); + + if (type_str) { + GRilIoRequest *req = grilio_request_sized_new(60); + grilio_request_append_int32(req, SET_FACILITY_LOCK_PARAMS); + grilio_request_append_utf8(req, type_str); + grilio_request_append_utf8(req, enable ? + RIL_FACILITY_LOCK : RIL_FACILITY_UNLOCK); + grilio_request_append_utf8(req, passwd); + grilio_request_append_utf8(req, "0"); /* class */ + grilio_request_append_utf8(req, sd->aid_str); + + sd->passwd_type = passwd_type; + id = grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_SET_FACILITY_LOCK, + ril_sim_pin_change_state_cb, ril_sim_cbd_free, + ril_sim_cbd_new(sd, cb, data)); + grilio_request_unref(req); + } + + if (!id) { + cb(ril_error_failure(&error), data); + } +} + +static void ril_sim_pin_send_puk(struct ofono_sim *sim, + const char *puk, const char *passwd, + ofono_sim_lock_unlock_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + GRilIoRequest *req = grilio_request_sized_new(60); + + grilio_request_append_int32(req, ENTER_SIM_PUK_PARAMS); + grilio_request_append_utf8(req, puk); + grilio_request_append_utf8(req, passwd); + grilio_request_append_utf8(req, sd->aid_str); + + DBG("puk=%s,pin=%s,aid=%s", puk, passwd, sd->aid_str); + sd->passwd_type = OFONO_SIM_PASSWORD_SIM_PUK; + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_ENTER_SIM_PUK, ril_sim_pin_change_state_cb, + ril_sim_cbd_free, ril_sim_cbd_new(sd, cb, data)); + grilio_request_unref(req); +} + +static void ril_sim_change_passwd(struct ofono_sim *sim, + enum ofono_sim_password_type passwd_type, + const char *old_passwd, const char *new_passwd, + ofono_sim_lock_unlock_cb_t cb, void *data) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + GRilIoRequest *req = grilio_request_sized_new(60); + + grilio_request_append_int32(req, CHANGE_SIM_PIN_PARAMS); + grilio_request_append_utf8(req, old_passwd); + grilio_request_append_utf8(req, new_passwd); + grilio_request_append_utf8(req, sd->aid_str); + + DBG("old=%s,new=%s,aid=%s", old_passwd, new_passwd, sd->aid_str); + sd->passwd_type = passwd_type; + grilio_queue_send_request_full(sd->q, req, + (passwd_type == OFONO_SIM_PASSWORD_SIM_PIN2) ? + RIL_REQUEST_CHANGE_SIM_PIN2 : RIL_REQUEST_CHANGE_SIM_PIN, + ril_sim_pin_change_state_cb, ril_sim_cbd_free, + ril_sim_cbd_new(sd, cb, data)); +} + +static gboolean ril_sim_register(gpointer user) +{ + struct ril_sim *sd = user; + + DBG("[%u]", sd->slot); + GASSERT(sd->idle_id); + sd->idle_id = 0; + + ril_sim_request_status(sd); + ofono_sim_register(sd->sim); + sd->event_id = grilio_channel_add_unsol_event_handler(sd->io, + ril_sim_status_changed, RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED, + sd); + + return FALSE; +} + +static int ril_sim_probe(struct ofono_sim *sim, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_sim *sd = g_new0(struct ril_sim, 1); + int i; + + sd->sim = sim; + sd->slot = ril_modem_slot(modem); + sd->io = grilio_channel_ref(ril_modem_io(modem)); + + /* NB: One queue is used for the requests generated by the ofono + * code, and the second one for the requests initiated internally + * by the RIL code. + * + * The difference is that when SIM card is removed, ofono requests + * are cancelled without invoking they completion callbacks (otherwise + * ofono would crash) while our completion callbacks have to be + * notified in this case (otherwise we would leak memory) + */ + sd->q = grilio_queue_new(sd->io); + sd->q2 = grilio_queue_new(sd->io); + + DBG("[%u]", sd->slot); + + for (i = 0; i < OFONO_SIM_PASSWORD_INVALID; i++) { + sd->retries[i] = -1; + } + + sd->idle_id = g_idle_add(ril_sim_register, sd); + ofono_sim_set_data(sim, sd); + return 0; +} + +static void ril_sim_remove(struct ofono_sim *sim) +{ + struct ril_sim *sd = ril_sim_get_data(sim); + + DBG("[%u]", sd->slot); + grilio_queue_cancel_all(sd->q, FALSE); + grilio_queue_cancel_all(sd->q2, TRUE); + ofono_sim_set_data(sim, NULL); + + if (sd->idle_id) { + g_source_remove(sd->idle_id); + } + + if (sd->retry_status_timer_id) { + g_source_remove(sd->retry_status_timer_id); + } + + grilio_channel_remove_handler(sd->io, sd->event_id); + grilio_channel_unref(sd->io); + grilio_queue_unref(sd->q); + grilio_queue_unref(sd->q2); + g_free(sd->aid_str); + g_free(sd->app_str); + g_free(sd); +} + +const struct ofono_sim_driver ril_sim_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_sim_probe, + .remove = ril_sim_remove, + .read_file_info = ril_sim_ofono_read_file_info, + .read_file_transparent = ril_sim_ofono_read_file_transparent, + .read_file_linear = ril_sim_ofono_read_file_linear, + .read_file_cyclic = ril_sim_ofono_read_file_cyclic, + .read_imsi = ril_sim_read_imsi, + .query_passwd_state = ril_sim_query_passwd_state, + .send_passwd = ril_sim_pin_send, + .lock = ril_sim_pin_change_state, + .reset_passwd = ril_sim_pin_send_puk, + .change_passwd = ril_sim_change_passwd, + .query_pin_retries = ril_sim_query_pin_retries +/* + * TODO: Implementing SIM write file IO support requires + * the following functions to be defined. + * + * .write_file_transparent = ril_sim_update_binary, + * .write_file_linear = ril_sim_update_record, + * .write_file_cyclic = ril_sim_update_cyclic, + */ +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_sim_dbus.c b/ofono/drivers/ril/ril_sim_dbus.c new file mode 100644 index 00000000..f7d53fe6 --- /dev/null +++ b/ofono/drivers/ril/ril_sim_dbus.c @@ -0,0 +1,243 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_log.h" + +#include +#include + +#include + +#include "ofono.h" +#include "storage.h" + +struct ril_sim_dbus { + char *path; + char *imsi; + char *name; + char *default_name; + gboolean enable_4g; + GKeyFile *storage; + DBusConnection *conn; + struct ril_modem *md; +}; + +#define RIL_SIM_STORE "ril" +#define RIL_SIM_STORE_GROUP "Settings" +#define RIL_SIM_STORE_ENABLE_4G "Enable4G" +#define RIL_SIM_STORE_DISPLAY_NAME "DisplayName" + +#define RIL_SIM_DBUS_INTERFACE "org.nemomobile.ofono.SimSettings" +#define RIL_SIM_DBUS_INTERFACE_VERSION (1) + +#define RIL_SIM_DBUS_DISPLAY_NAME_CHANGED_SIGNAL "DisplayNameChanged" +#define RIL_SIM_DBUS_ENABLE_4G_CHANGED_SIGNAL "Enable4GChanged" + +static DBusMessage *ril_sim_dbus_get_all(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_sim_dbus *dbus = data; + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_int32_t version = RIL_SIM_DBUS_INTERFACE_VERSION; + dbus_bool_t enable_4g = dbus->enable_4g; + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &version); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &enable_4g); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &dbus->name); + return reply; +} + +static DBusMessage *ril_sim_dbus_get_interface_version(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_int32_t version = RIL_SIM_DBUS_INTERFACE_VERSION; + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &version); + return reply; +} + +static DBusMessage *ril_sim_dbus_get_enable_4g(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_sim_dbus *dbus = data; + DBusMessage *reply = dbus_message_new_method_return(msg); + dbus_bool_t enable_4g = dbus->enable_4g; + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &enable_4g); + return reply; +} + +static DBusMessage *ril_sim_dbus_get_display_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct ril_sim_dbus *dbus = data; + DBusMessage *reply = dbus_message_new_method_return(msg); + DBusMessageIter iter; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &dbus->name); + return reply; +} + +static void ril_sim_dbus_update_display_name(struct ril_sim_dbus *dbus, + const char *name) +{ + if (g_strcmp0(dbus->name, name)) { + g_free(dbus->name); + dbus->name = g_strdup(name); + g_key_file_set_string(dbus->storage, RIL_SIM_STORE_GROUP, + RIL_SIM_STORE_DISPLAY_NAME, name); + storage_sync(dbus->imsi, RIL_SIM_STORE, dbus->storage); + g_dbus_emit_signal(dbus->conn, dbus->path, + RIL_SIM_DBUS_INTERFACE, + RIL_SIM_DBUS_DISPLAY_NAME_CHANGED_SIGNAL, + DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); + } +} + +static DBusMessage *ril_sim_dbus_set_display_name(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { + struct ril_sim_dbus *dbus = data; + DBusBasicValue value; + const char *name; + + dbus_message_iter_get_basic(&iter, &value); + name = value.str; + if (!name || !name[0]) name = dbus->default_name; + ril_sim_dbus_update_display_name(dbus, name); + return dbus_message_new_method_return(msg); + } else { + return __ofono_error_invalid_args(msg); + } +} + +static const GDBusMethodTable ril_sim_dbus_methods[] = { + { GDBUS_METHOD("GetAll", + NULL, GDBUS_ARGS({ "settings", "ibs" }), + ril_sim_dbus_get_all) }, + { GDBUS_METHOD("GetInterfaceVersion", + NULL, GDBUS_ARGS({ "version", "i" }), + ril_sim_dbus_get_interface_version) }, + { GDBUS_METHOD("GetEnable4G", + NULL, GDBUS_ARGS({ "enable", "b" }), + ril_sim_dbus_get_enable_4g) }, + { GDBUS_METHOD("GetDisplayName", + NULL, GDBUS_ARGS({ "name", "s" }), + ril_sim_dbus_get_display_name) }, + { GDBUS_METHOD("SetDisplayName", + GDBUS_ARGS({ "name", "s" }), NULL, + ril_sim_dbus_set_display_name) }, + { } +}; + +static const GDBusSignalTable ril_sim_dbus_signals[] = { + { GDBUS_SIGNAL(RIL_SIM_DBUS_DISPLAY_NAME_CHANGED_SIGNAL, + GDBUS_ARGS({ "name", "s" })) }, + { GDBUS_SIGNAL(RIL_SIM_DBUS_ENABLE_4G_CHANGED_SIGNAL, + GDBUS_ARGS({ "enabled", "b" })) }, + { } +}; + +const char *ril_sim_dbus_imsi(struct ril_sim_dbus *dbus) +{ + return dbus ? dbus->imsi : NULL; +} + +struct ril_sim_dbus *ril_sim_dbus_new(struct ril_modem *md) +{ + const char *imsi = ofono_sim_get_imsi(ril_modem_ofono_sim(md)); + + if (imsi) { + GError *error = NULL; + const struct ril_modem_config *config= ril_modem_config(md); + struct ril_sim_dbus *dbus = g_new0(struct ril_sim_dbus, 1); + + DBG("%s", ril_modem_get_path(md)); + dbus->md = md; + dbus->conn = dbus_connection_ref(ofono_dbus_get_connection()); + dbus->path = g_strdup(ril_modem_get_path(md)); + dbus->imsi = g_strdup(imsi); + dbus->default_name = g_strdup(config->default_name); + + /* Load settings */ + dbus->storage = storage_open(imsi, RIL_SIM_STORE); + dbus->enable_4g = g_key_file_get_boolean(dbus->storage, + RIL_SIM_STORE_GROUP, RIL_SIM_STORE_ENABLE_4G, &error); + if (error) { + dbus->enable_4g = config->enable_4g; + g_error_free(error); + error = NULL; + } + dbus->name = g_key_file_get_string(dbus->storage, + RIL_SIM_STORE_GROUP, RIL_SIM_STORE_DISPLAY_NAME, NULL); + if (!dbus->name) { + dbus->name = g_strdup(config->default_name); + GASSERT(dbus->name); + } + + /* Register D-Bus interface */ + if (g_dbus_register_interface(dbus->conn, dbus->path, + RIL_SIM_DBUS_INTERFACE, ril_sim_dbus_methods, + ril_sim_dbus_signals, NULL, dbus, NULL)) { + ofono_modem_add_interface(ril_modem_ofono_modem(md), + RIL_SIM_DBUS_INTERFACE); + return dbus; + } else { + ofono_error("RIL D-Bus register failed"); + ril_sim_dbus_free(dbus); + } + } + + return NULL; +} + +void ril_sim_dbus_free(struct ril_sim_dbus *dbus) +{ + if (dbus) { + DBG("%s", dbus->path); + g_dbus_unregister_interface(dbus->conn, dbus->path, + RIL_SIM_DBUS_INTERFACE); + ofono_modem_remove_interface(ril_modem_ofono_modem(dbus->md), + RIL_SIM_DBUS_INTERFACE); + dbus_connection_unref(dbus->conn); + g_key_file_free(dbus->storage); + g_free(dbus->path); + g_free(dbus->imsi); + g_free(dbus->name); + g_free(dbus->default_name); + g_free(dbus); + } +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_sms.c b/ofono/drivers/ril/ril_sms.c new file mode 100644 index 00000000..94e033f5 --- /dev/null +++ b/ofono/drivers/ril/ril_sms.c @@ -0,0 +1,498 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_constants.h" +#include "ril_util.h" +#include "ril_log.h" + +#include "smsutil.h" +#include "util.h" +#include "simutil.h" + +#define SIM_EFSMS_FILEID 0x6F3C +#define EFSMS_LENGTH 176 + +#define TYPE_LOCAL 129 +#define TYPE_INTERNATIONAL 145 + +static unsigned char path[4] = {0x3F, 0x00, 0x7F, 0x10}; + +enum ril_sms_events { + SMS_EVENT_NEW_SMS, + SMS_EVENT_NEW_STATUS_REPORT, + SMS_EVENT_NEW_SMS_ON_SIM, + SMS_EVENT_COUNT +}; + +struct ril_sms { + GRilIoChannel *io; + GRilIoQueue *q; + struct ril_modem *modem; + struct ofono_sms *sms; + gulong event_id[SMS_EVENT_COUNT]; + guint timer_id; +}; + +struct ril_sms_cbd { + union _ofono_sms_cb { + ofono_sms_sca_set_cb_t sca_set; + ofono_sms_sca_query_cb_t sca_query; + ofono_sms_submit_cb_t submit; + gpointer ptr; + } cb; + gpointer data; +}; + +struct ril_sms_on_sim_req { + struct ril_sms *sd; + int record; +}; + +#define ril_sms_cbd_free g_free +#define ril_sms_on_sim_req_free g_free + +static inline struct ril_sms *ril_sms_get_data(struct ofono_sms *sms) +{ + return ofono_sms_get_data(sms); +} + +struct ril_sms_cbd *ril_sms_cbd_new(struct ril_sms *sd, void *cb, void *data) +{ + struct ril_sms_cbd *cbd = g_new0(struct ril_sms_cbd, 1); + + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +struct ril_sms_on_sim_req *ril_sms_on_sim_req_new(struct ril_sms *sd, int rec) +{ + struct ril_sms_on_sim_req *cbd = g_new0(struct ril_sms_on_sim_req, 1); + + cbd->sd = sd; + cbd->record = rec; + return cbd; +} + +static void ril_sms_sca_set_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_sms_cbd *cbd = user_data; + ofono_sms_sca_set_cb_t cb = cbd->cb.sca_set; + + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("csca setting failed"); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_sms_sca_set(struct ofono_sms *sms, + const struct ofono_phone_number *sca, + ofono_sms_sca_set_cb_t cb, void *data) +{ + struct ril_sms *sd = ril_sms_get_data(sms); + GRilIoRequest *req = grilio_request_new(); + char number[OFONO_MAX_PHONE_NUMBER_LENGTH + 4]; + + if (sca->type == TYPE_LOCAL) { + snprintf(number, sizeof(number), "\"%s\"", sca->number); + } else { + snprintf(number, sizeof(number), "\"+%s\"", sca->number); + } + + DBG("Setting sca: %s", number); + grilio_request_append_utf8(req, number); + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_SET_SMSC_ADDRESS, ril_sms_sca_set_cb, + ril_sms_cbd_free, ril_sms_cbd_new(sd, cb, data)); + grilio_request_unref(req); +} + +static void ril_sms_sca_query_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_sms_cbd *cbd = user_data; + ofono_sms_sca_query_cb_t cb = cbd->cb.sca_query; + struct ofono_error error; + GRilIoParser rilp; + gchar *temp_buf; + + if (status != RIL_E_SUCCESS) { + ofono_error("csca query failed"); + cb(ril_error_failure(&error), NULL, cbd->data); + return; + } + + grilio_parser_init(&rilp, data, len); + temp_buf = grilio_parser_get_utf8(&rilp); + + if (temp_buf) { + /* RIL gives address in quotes */ + gchar *number = strtok(temp_buf, "\""); + struct ofono_phone_number sca; + + strncpy(sca.number, number, OFONO_MAX_PHONE_NUMBER_LENGTH); + sca.number[OFONO_MAX_PHONE_NUMBER_LENGTH] = '\0'; + if (sca.number[0] == '+') { + number = number + 1; + sca.type = TYPE_INTERNATIONAL; + } else { + sca.type = TYPE_LOCAL; + } + + DBG("csca_query_cb: %s, %d", sca.number, sca.type); + cb(ril_error_ok(&error), &sca, cbd->data); + g_free(temp_buf); + } else { + ofono_error("return value invalid"); + cb(ril_error_failure(&error), NULL, cbd->data); + } +} + +static void ril_sms_sca_query(struct ofono_sms *sms, + ofono_sms_sca_query_cb_t cb, void *data) +{ + struct ril_sms *sd = ril_sms_get_data(sms); + + DBG("Sending csca_query"); + grilio_queue_send_request_full(sd->q, NULL, + RIL_REQUEST_GET_SMSC_ADDRESS, ril_sms_sca_query_cb, + ril_sms_cbd_free, ril_sms_cbd_new(sd, cb, data)); +} + +static void ril_sms_submit_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_sms_cbd *cbd = user_data; + ofono_sms_submit_cb_t cb = cbd->cb.submit; + struct ofono_error error; + int mr = 0; + + if (status == RIL_E_SUCCESS) { + GRilIoParser rilp; + int err = -1; + + grilio_parser_init(&rilp, data, len); + + /* TP-Message-Reference for GSM/ + * BearerData MessageId for CDMA + */ + grilio_parser_get_int32(&rilp, &mr); + grilio_parser_skip_string(&rilp); + + /* error: 3GPP 27.005, 3.2.5, -1 if unknown or not applicable */ + grilio_parser_get_int32(&rilp, &err); + DBG("sms msg ref: %d, error: %d", mr, err); + ril_error_init_ok(&error); + } else if (status == RIL_E_GENERIC_FAILURE) { + ofono_info("not allowed by MO SMS control, do not retry"); + error.type = OFONO_ERROR_TYPE_CMS; + error.error = 500; + } else { + ofono_error("sms sending failed, retry"); + ril_error_init_failure(&error); + } + + cb(&error, mr, cbd->data); +} + +static void ril_sms_submit(struct ofono_sms *sms, const unsigned char *pdu, + int pdu_len, int tpdu_len, int mms, + ofono_sms_submit_cb_t cb, void *data) +{ + struct ril_sms *sd = ril_sms_get_data(sms); + GRilIoRequest *req = grilio_request_new(); + int smsc_len; + char *tpdu; + + DBG("pdu_len: %d, tpdu_len: %d mms: %d", pdu_len, tpdu_len, mms); + + grilio_request_append_int32(req, 2); /* Number of strings */ + + /* SMSC address: + * + * smsc_len == 1, then zero-length SMSC was spec'd + * RILD expects a NULL string in this case instead + * of a zero-length string. + */ + smsc_len = pdu_len - tpdu_len; + if (smsc_len > 1) { + /* TODO: encode SMSC & write to parcel */ + DBG("SMSC address specified (smsc_len %d); NOT-IMPLEMENTED", + smsc_len); + } + + grilio_request_append_utf8(req, NULL); /* default SMSC address */ + + /* TPDU: + * + * 'pdu' is a raw hexadecimal string + * encode_hex() turns it into an ASCII/hex UTF8 buffer + * grilio_request_append_utf8() encodes utf8 -> utf16 + */ + tpdu = encode_hex(pdu + smsc_len, tpdu_len, 0); + grilio_request_append_utf8(req, tpdu); + + DBG("%s", tpdu); + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_SEND_SMS, ril_sms_submit_cb, + ril_sms_cbd_free, ril_sms_cbd_new(sd, cb, data)); + grilio_request_unref(req); + g_free(tpdu); +} + +static void ril_ack_delivery_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + if (status != RIL_E_SUCCESS) { + ofono_error("SMS acknowledgement failed: " + "Further SMS reception is not guaranteed"); + } +} + +static void ril_ack_delivery(struct ril_sms *sd, gboolean error) +{ + GRilIoRequest *req = grilio_request_sized_new(12); + const int code = (error ? 0 : 0xff); + + DBG("(%d,%d)", error, code); + grilio_request_append_int32(req, 2); /* Array size*/ + grilio_request_append_int32(req, error); /* Success (1)/Failure (0) */ + grilio_request_append_int32(req, code); /* error code */ + + /* ACK the incoming NEW_SMS */ + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_SMS_ACKNOWLEDGE, ril_ack_delivery_cb, NULL, NULL); + grilio_request_unref(req); +} + +static void ril_sms_notify(GRilIoChannel *io, guint ril_event, + const void *data, guint len, void *user_data) +{ + struct ril_sms *sd = user_data; + GRilIoParser rilp; + char *ril_pdu; + int ril_pdu_len; + unsigned int smsc_len; + long ril_buf_len; + guchar *ril_data; + + ril_pdu = NULL; + ril_data = NULL; + + DBG("event: %d; data_len: %d", ril_event, len); + + grilio_parser_init(&rilp, data, len); + ril_pdu = grilio_parser_get_utf8(&rilp); + if (ril_pdu == NULL) + goto error; + + ril_pdu_len = strlen(ril_pdu); + + DBG("ril_pdu_len is %d", ril_pdu_len); + ril_data = decode_hex(ril_pdu, ril_pdu_len, &ril_buf_len, -1); + if (ril_data == NULL) + goto error; + + /* The first octect in the pdu contains the SMSC address length + * which is the X following octects it reads. We add 1 octet to + * the read length to take into account this read octet in order + * to calculate the proper tpdu length. + */ + smsc_len = ril_data[0] + 1; + ofono_info("sms received, smsc_len is %d", smsc_len); + DBG("(%s)", ril_pdu); + + if (ril_event == RIL_UNSOL_RESPONSE_NEW_SMS) { + /* Last parameter is 'tpdu_len' ( substract SMSC length ) */ + ofono_sms_deliver_notify(sd->sms, ril_data, ril_buf_len, + ril_buf_len - smsc_len); + } else { + GASSERT(ril_event == RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT); + ofono_sms_status_notify(sd->sms, ril_data, ril_buf_len, + ril_buf_len - smsc_len); + } + + g_free(ril_pdu); + g_free(ril_data); + ril_ack_delivery(sd, TRUE); + return; + +error: + g_free(ril_pdu); + g_free(ril_data); + ril_ack_delivery(sd, FALSE); + ofono_error("Unable to parse NEW_SMS notification"); +} + +static void ril_new_sms_on_sim_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + DBG("%d", status); + if (status == RIL_E_SUCCESS) { + ofono_info("sms deleted from sim"); + } else { + ofono_error("deleting sms from sim failed"); + } +} + +static void ril_request_delete_sms_om_sim(struct ril_sms *sd, int record) +{ + GRilIoRequest *req = grilio_request_sized_new(8); + + DBG("Deleting record: %d", record); + + grilio_request_append_int32(req, 1); /* Array length */ + grilio_request_append_int32(req, record); + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_DELETE_SMS_ON_SIM, + ril_new_sms_on_sim_cb, NULL, NULL); + grilio_request_unref(req); +} + +static void ril_sms_on_sim_cb(const struct ofono_error *error, + const unsigned char *sdata, + int length, void *data) +{ + struct ril_sms_on_sim_req *cbd = data; + struct ril_sms *sd = cbd->sd; + + /* + * It seems when reading EFsms RIL returns the whole record including + * the first status byte therefore we ignore that as we are only + * interested of the following pdu + */ + /* The first octect in the pdu contains the SMSC address length + * which is the X following octects it reads. We add 1 octet to + * the read length to take into account this read octet in order + * to calculate the proper tpdu length. + */ + if (error->type == OFONO_ERROR_TYPE_NO_ERROR) { + unsigned int smsc_len = sdata[1] + 1; + ofono_sms_deliver_notify(sd->sms, sdata + 1, length - 1, + length - smsc_len - 1); + ril_request_delete_sms_om_sim(sd, cbd->record); + } else { + ofono_error("cannot read sms from sim"); + } + + ril_sms_on_sim_req_free(cbd); +} + +static void ril_sms_on_sim(GRilIoChannel *io, guint ril_event, + const void *data, guint len, void *user_data) +{ + struct ril_sms *sd = user_data; + struct ofono_sim *sim = ril_modem_ofono_sim(sd->modem); + int data_len = 0, rec = 0; + GRilIoParser rilp; + + ofono_info("new sms on sim"); + grilio_parser_init(&rilp, data, len); + if (sim && + grilio_parser_get_int32(&rilp, &data_len) && data_len > 0 && + grilio_parser_get_int32(&rilp, &rec)) { + DBG("rec %d", rec); + ril_sim_read_file_linear(sim, SIM_EFSMS_FILEID, rec, + EFSMS_LENGTH, path, sizeof(path), + ril_sms_on_sim_cb, + ril_sms_on_sim_req_new(sd,rec)); + } +} + +static gboolean ril_sms_register(gpointer user_data) +{ + struct ril_sms *sd = user_data; + + DBG(""); + GASSERT(sd->timer_id); + sd->timer_id = 0; + ofono_sms_register(sd->sms); + + /* Register event handlers */ + sd->event_id[SMS_EVENT_NEW_SMS] = + grilio_channel_add_unsol_event_handler(sd->io, ril_sms_notify, + RIL_UNSOL_RESPONSE_NEW_SMS, sd); + sd->event_id[SMS_EVENT_NEW_STATUS_REPORT] = + grilio_channel_add_unsol_event_handler(sd->io, ril_sms_notify, + RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT, sd); + sd->event_id[SMS_EVENT_NEW_SMS_ON_SIM] = + grilio_channel_add_unsol_event_handler(sd->io, ril_sms_on_sim, + RIL_UNSOL_RESPONSE_NEW_SMS_ON_SIM, sd); + + /* Single-shot */ + return FALSE; +} + +static int ril_sms_probe(struct ofono_sms *sms, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_sms *sd = g_new0(struct ril_sms, 1); + + sd->modem = modem; + sd->sms = sms; + sd->io = grilio_channel_ref(ril_modem_io(modem)); + sd->q = grilio_queue_new(sd->io); + sd->timer_id = g_idle_add(ril_sms_register, sd); + ofono_sms_set_data(sms, sd); + return 0; +} + +static void ril_sms_remove(struct ofono_sms *sms) +{ + int i; + struct ril_sms *sd = ril_sms_get_data(sms); + + DBG(""); + ofono_sms_set_data(sms, NULL); + + for (i=0; ievent_id); i++) { + grilio_channel_remove_handler(sd->io, sd->event_id[i]); + + } + + if (sd->timer_id > 0) { + g_source_remove(sd->timer_id); + } + + grilio_channel_unref(sd->io); + grilio_queue_cancel_all(sd->q, FALSE); + grilio_queue_unref(sd->q); + g_free(sd); +} + +const struct ofono_sms_driver ril_sms_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_sms_probe, + .remove = ril_sms_remove, + .sca_query = ril_sms_sca_query, + .sca_set = ril_sms_sca_set, + .submit = ril_sms_submit, + .bearer_query = NULL, /* FIXME: needs investigation. */ + .bearer_set = NULL +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_stk.c b/ofono/drivers/ril/ril_stk.c new file mode 100644 index 00000000..bba7789a --- /dev/null +++ b/ofono/drivers/ril/ril_stk.c @@ -0,0 +1,303 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "util.h" + +#ifndef UI_LANG +# define UI_LANG "/var/lib/environment/nemo/locale.conf" +#endif + +enum ril_stk_events { + STK_EVENT_PROACTIVE_COMMAND, + STK_EVENT_SESSION_END, + STK_EVENT_NOTIFY, + STK_EVENT_COUNT +}; + +struct ril_stk { + struct ofono_stk *stk; + GRilIoChannel *io; + GRilIoQueue *q; + gulong event_id[STK_EVENT_COUNT]; +}; + +struct ril_stk_cbd { + union _ofono_stk_cb { + ofono_stk_envelope_cb_t envelope; + ofono_stk_generic_cb_t generic; + gpointer ptr; + } cb; + gpointer data; +}; + +#define ril_stk_cbd_free g_free + +static inline struct ril_stk *ril_stk_get_data(struct ofono_stk *stk) +{ + return ofono_stk_get_data(stk); +} + +struct ril_stk_cbd *ril_stk_cbd_new(void *cb, void *data) +{ + struct ril_stk_cbd *cbd = g_new0(struct ril_stk_cbd, 1); + + cbd->cb.ptr = cb; + cbd->data = data; + return cbd; +} + +static void ril_stk_envelope_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_stk_cbd *cbd = user_data; + ofono_stk_envelope_cb_t cb = cbd->cb.envelope; + + if (status == RIL_E_SUCCESS) { + DBG("%u bytes(s)", len); + cb(ril_error_ok(&error), NULL, 0, cbd->data); + } else { + DBG("Envelope reply failure: %s", ril_error_to_string(status)); + cb(ril_error_failure(&error), NULL, 0, cbd->data); + } +} + +static void ril_stk_envelope(struct ofono_stk *stk, int length, + const unsigned char *cmd, ofono_stk_envelope_cb_t cb, void *data) +{ + struct ril_stk *sd = ril_stk_get_data(stk); + GRilIoRequest *req = grilio_request_new(); + char *hex_envelope = encode_hex(cmd, length, 0); + + DBG("%s", hex_envelope); + grilio_request_append_utf8(req, hex_envelope); + g_free(hex_envelope); + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_STK_SEND_ENVELOPE_COMMAND, + ril_stk_envelope_cb, ril_stk_cbd_free, + ril_stk_cbd_new(cb, data)); + grilio_request_unref(req); +} + +static void ril_stk_terminal_response_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_stk_cbd *cbd = user_data; + ofono_stk_generic_cb_t cb = cbd->cb.generic; + + DBG(""); + if (status == RIL_E_SUCCESS) { + cb(ril_error_ok(&error), cbd->data); + } else { + ofono_error("Error in sending terminal response"); + cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_stk_terminal_response(struct ofono_stk *stk, int length, + const unsigned char *resp, + ofono_stk_generic_cb_t cb, void *data) +{ + struct ril_stk *sd = ril_stk_get_data(stk); + GRilIoRequest *req = grilio_request_new(); + char *hex_tr = encode_hex(resp, length, 0); + + DBG("rilmodem terminal response: %s", hex_tr); + grilio_request_append_utf8(req, hex_tr); + g_free(hex_tr); + grilio_queue_send_request_full(sd->q, req, + RIL_REQUEST_STK_SEND_TERMINAL_RESPONSE, + ril_stk_terminal_response_cb, + ril_stk_cbd_free, ril_stk_cbd_new(cb, data)); + grilio_request_unref(req); +} + +static void ril_stk_user_confirmation(struct ofono_stk *stk, + ofono_bool_t confirm) +{ + struct ril_stk *sd = ril_stk_get_data(stk); + GRilIoRequest *req = grilio_request_sized_new(8); + + DBG("%d", confirm); + grilio_request_append_int32(req, 1); /* size of array */ + grilio_request_append_int32(req, confirm); /* yes/no */ + + grilio_queue_send_request(sd->q, req, + RIL_REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM); + grilio_request_unref(req); +} + +static void ril_stk_pcmd_notify(GRilIoChannel *io, guint code, + const void *data, guint data_len, void *user_data) +{ + struct ril_stk *sd = user_data; + GRilIoParser rilp; + char *pcmd; + guchar *pdu; + long len = 0; + + GASSERT(code == RIL_UNSOL_STK_PROACTIVE_COMMAND); + grilio_parser_init(&rilp, data, data_len); + pcmd = grilio_parser_get_utf8(&rilp); + DBG("pcmd: %s", pcmd); + + pdu = decode_hex(pcmd, strlen(pcmd), &len, -1); + g_free(pcmd); + + ofono_stk_proactive_command_notify(sd->stk, len, pdu); + g_free(pdu); +} + +static void ril_stk_event_notify(GRilIoChannel *io, guint code, + const void *data, guint data_len, void *user_data) +{ + struct ril_stk *sd = user_data; + GRilIoParser rilp; + char *pcmd = NULL; + guchar *pdu = NULL; + long len; + + /* Proactive command has been handled by the modem. */ + GASSERT(code == RIL_UNSOL_STK_EVENT_NOTIFY); + grilio_parser_init(&rilp, data, data_len); + pcmd = grilio_parser_get_utf8(&rilp); + DBG("pcmd: %s", pcmd); + pdu = decode_hex(pcmd, strlen(pcmd), &len, -1); + g_free(pcmd); + + ofono_stk_proactive_command_handled_notify(sd->stk, len, pdu); + g_free(pdu); +} + +static void ril_stk_session_end_notify(GRilIoChannel *io, guint code, + const void *data, guint len, void *user_data) +{ + struct ril_stk *sd = user_data; + + DBG(""); + GASSERT(code == RIL_UNSOL_STK_SESSION_END); + ofono_stk_proactive_session_end_notify(sd->stk); +} + +static void ril_stk_agent_ready(struct ofono_stk *stk) +{ + struct ril_stk *sd = ril_stk_get_data(stk); + + DBG(""); + if (!sd->event_id[STK_EVENT_PROACTIVE_COMMAND]) { + DBG("Subscribing notifications"); + sd->event_id[STK_EVENT_PROACTIVE_COMMAND] = + grilio_channel_add_unsol_event_handler(sd->io, + ril_stk_pcmd_notify, + RIL_UNSOL_STK_PROACTIVE_COMMAND, sd); + + GASSERT(!sd->event_id[STK_EVENT_SESSION_END]); + sd->event_id[STK_EVENT_SESSION_END] = + grilio_channel_add_unsol_event_handler(sd->io, + ril_stk_session_end_notify, + RIL_UNSOL_STK_SESSION_END, sd); + + GASSERT(!sd->event_id[STK_EVENT_NOTIFY]); + sd->event_id[STK_EVENT_NOTIFY] = + grilio_channel_add_unsol_event_handler(sd->io, + ril_stk_event_notify, + RIL_UNSOL_STK_EVENT_NOTIFY, sd); + + grilio_queue_send_request(sd->q, NULL, + RIL_REQUEST_REPORT_STK_SERVICE_IS_RUNNING); + } +} + +static void ril_stk_set_lang() +{ + GError *error = NULL; + GIOChannel* chan = g_io_channel_new_file(UI_LANG, "r", &error); + if (chan) { + GString* buf = g_string_new(NULL); + gsize term; + while (g_io_channel_read_line_string(chan, buf, &term, NULL) == + G_IO_STATUS_NORMAL) { + char* lang; + g_string_set_size(buf, term); + lang = strstr(buf->str, "LANG="); + if (lang) { + setenv("LANG", lang + 5, TRUE); + } + } + g_string_free(buf, TRUE); + g_io_channel_unref(chan); + } else { + DBG("%s: %s", UI_LANG, error->message); + g_error_free(error); + } +} + +static int ril_stk_probe(struct ofono_stk *stk, unsigned int vendor, void *data) +{ + struct ril_modem *modem = data; + struct ril_stk *sd = g_new0(struct ril_stk, 1); + + DBG(""); + sd->stk = stk; + sd->io = grilio_channel_ref(ril_modem_io(modem)); + sd->q = grilio_queue_new(sd->io); + + ofono_stk_set_data(stk, sd); + ofono_stk_register(stk); + ril_stk_set_lang(); + return 0; +} + +static void ril_stk_remove(struct ofono_stk *stk) +{ + struct ril_stk *sd = ril_stk_get_data(stk); + int i; + + DBG(""); + ofono_stk_set_data(stk, NULL); + + for (i=0; ievent_id); i++) { + grilio_channel_remove_handler(sd->io, sd->event_id[i]); + } + + grilio_channel_unref(sd->io); + grilio_queue_cancel_all(sd->q, FALSE); + grilio_queue_unref(sd->q); + g_free(sd); +} + +const struct ofono_stk_driver ril_stk_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_stk_probe, + .remove = ril_stk_remove, + .envelope = ril_stk_envelope, + .terminal_response = ril_stk_terminal_response, + .user_confirmation = ril_stk_user_confirmation, + .ready = ril_stk_agent_ready +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_types.h b/ofono/drivers/ril/ril_types.h new file mode 100644 index 00000000..4e40c481 --- /dev/null +++ b/ofono/drivers/ril/ril_types.h @@ -0,0 +1,41 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#ifndef RIL_TYPES_H +#define RIL_TYPES_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#endif /* RIL_TYPES_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_ussd.c b/ofono/drivers/ril/ril_ussd.c new file mode 100644 index 00000000..2bd51d68 --- /dev/null +++ b/ofono/drivers/ril/ril_ussd.c @@ -0,0 +1,224 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include "smsutil.h" +#include "util.h" + +struct ril_ussd { + struct ofono_ussd *ussd; + GRilIoChannel *io; + GRilIoQueue *q; + guint timer_id; + gulong event_id; +}; + +struct ril_ussd_cbd { + ofono_ussd_cb_t cb; + gpointer data; +}; + +#define ril_ussd_cbd_free g_free + +static inline struct ril_ussd *ril_ussd_get_data(struct ofono_ussd *ussd) +{ + return ofono_ussd_get_data(ussd); +} + +static struct ril_ussd_cbd *ril_ussd_cbd_new(ofono_ussd_cb_t cb, void *data) +{ + struct ril_ussd_cbd *cbd = g_new0(struct ril_ussd_cbd, 1); + + cbd->cb = cb; + cbd->data = data; + return cbd; +} + +static void ril_ussd_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ofono_error error; + struct ril_ussd_cbd *cbd = user_data; + + if (status == RIL_E_SUCCESS) { + cbd->cb(ril_error_ok(&error), cbd->data); + } else { + cbd->cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_ussd_request(struct ofono_ussd *ussd, int dcs, + const unsigned char *pdu, int len, ofono_ussd_cb_t cb, void *data) +{ + struct ofono_error error; + enum sms_charset charset; + struct ril_ussd *ud = ril_ussd_get_data(ussd); + + ofono_info("send ussd, len:%d", len); + if (cbs_dcs_decode(dcs, NULL, NULL, &charset, NULL, NULL, NULL)) { + if (charset == SMS_CHARSET_7BIT) { + unsigned char unpacked_buf[182]; + long written = 0; + + unpack_7bit_own_buf(pdu, len, 0, TRUE, + sizeof(unpacked_buf)-1, &written, 0, + unpacked_buf); + + unpacked_buf[written] = 0; + if (written >= 1) { + /* + * When USSD was packed, additional CR + * might have been added (according to + * 23.038 6.1.2.3.1). So if the last + * character is CR, it should be removed + * here. + * + * Over 2 characters long USSD string must + * end with # (checked in valid_ussd_string), + * so it should be safe to remove extra CR. + */ + GRilIoRequest *req = grilio_request_new(); + int length = strlen((char *)unpacked_buf); + while (length > 2 && + unpacked_buf[length-1] == '\r') { + unpacked_buf[--length] = 0; + } + grilio_request_append_utf8_chars(req, (char*) + unpacked_buf, length); + grilio_queue_send_request_full(ud->q, req, + RIL_REQUEST_SEND_USSD, ril_ussd_cb, + ril_ussd_cbd_free, + ril_ussd_cbd_new(cb, data)); + grilio_request_unref(req); + return; + } + } + } + + cb(ril_error_failure(&error), data); +} + +static void ril_ussd_cancel(struct ofono_ussd *ussd, + ofono_ussd_cb_t cb, void *data) +{ + struct ril_ussd *ud = ril_ussd_get_data(ussd); + + ofono_info("send ussd cancel"); + grilio_queue_send_request_full(ud->q, NULL, RIL_REQUEST_CANCEL_USSD, + ril_ussd_cb, ril_ussd_cbd_free, ril_ussd_cbd_new(cb, data)); +} + +static void ril_ussd_notify(GRilIoChannel *io, guint code, + const void *data, guint len, void *user_data) +{ + struct ril_ussd *ud = user_data; + GRilIoParser rilp; + char *ussd_from_network = NULL; + char *type = NULL; + int ussdtype = 0; + + ofono_info("ussd_received"); + + GASSERT(code == RIL_UNSOL_ON_USSD); + grilio_parser_init(&rilp, data, len); + grilio_parser_get_uint32(&rilp, NULL); + type = grilio_parser_get_utf8(&rilp); + ussd_from_network = grilio_parser_get_utf8(&rilp); + + ussdtype = g_ascii_xdigit_value(*type); + + if (ussd_from_network) { + const int data_len = strlen(ussd_from_network); + DBG("ussd_received, length %d", data_len); + ofono_ussd_notify(ud->ussd, ussdtype, 0xFF, + (const unsigned char *) ussd_from_network, data_len); + } else { + ofono_ussd_notify(ud->ussd, ussdtype, 0, NULL, 0); + } + + /* ussd_from_network not freed because core does that if dcs is 0xFF */ + g_free(type); + return; +} + +static gboolean ril_ussd_register(gpointer user_data) +{ + struct ril_ussd *ud = user_data; + + DBG(""); + GASSERT(ud->timer_id); + ud->timer_id = 0; + ofono_ussd_register(ud->ussd); + + /* Register for USSD events */ + ud->event_id = grilio_channel_add_unsol_event_handler(ud->io, + ril_ussd_notify, RIL_UNSOL_ON_USSD, ud); + + /* Single-shot */ + return FALSE; +} + +static int ril_ussd_probe(struct ofono_ussd *ussd, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_ussd *ud = g_try_new0(struct ril_ussd, 1); + + DBG(""); + ud->ussd = ussd; + ud->io = grilio_channel_ref(ril_modem_io(modem)); + ud->q = grilio_queue_new(ud->io); + ud->timer_id = g_idle_add(ril_ussd_register, ud); + ofono_ussd_set_data(ussd, ud); + return 0; +} + +static void ril_ussd_remove(struct ofono_ussd *ussd) +{ + struct ril_ussd *ud = ril_ussd_get_data(ussd); + + DBG(""); + ofono_ussd_set_data(ussd, NULL); + + if (ud->timer_id > 0) { + g_source_remove(ud->timer_id); + } + + grilio_channel_remove_handler(ud->io, ud->event_id); + grilio_channel_unref(ud->io); + grilio_queue_cancel_all(ud->q, FALSE); + grilio_queue_unref(ud->q); + g_free(ud); +} + +const struct ofono_ussd_driver ril_ussd_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_ussd_probe, + .remove = ril_ussd_remove, + .request = ril_ussd_request, + .cancel = ril_ussd_cancel +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_util.c b/ofono/drivers/ril/ril_util.c new file mode 100644 index 00000000..06f3a798 --- /dev/null +++ b/ofono/drivers/ril/ril_util.c @@ -0,0 +1,434 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_util.h" +#include "ril_log.h" +#include "ril_constants.h" + +#include +#include + +#include + +#include "common.h" +#include "util.h" + +const char *ril_error_to_string(int error) +{ +#define RIL_E_(name) case RIL_E_##name: return #name +#define GRILIO_E_(name) case GRILIO_STATUS_##name: return "GRILIO_" #name + static char unknown[12]; + switch (error) { + case RIL_E_SUCCESS: return "OK"; + GRILIO_E_(TIMEOUT); + GRILIO_E_(CANCELLED); + RIL_E_(RADIO_NOT_AVAILABLE); + RIL_E_(GENERIC_FAILURE); + RIL_E_(PASSWORD_INCORRECT); + RIL_E_(SIM_PIN2); + RIL_E_(SIM_PUK2); + RIL_E_(REQUEST_NOT_SUPPORTED); + RIL_E_(CANCELLED); + RIL_E_(OP_NOT_ALLOWED_DURING_VOICE_CALL); + RIL_E_(OP_NOT_ALLOWED_BEFORE_REG_TO_NW); + RIL_E_(SMS_SEND_FAIL_RETRY); + RIL_E_(SIM_ABSENT); + RIL_E_(SUBSCRIPTION_NOT_AVAILABLE); + RIL_E_(MODE_NOT_SUPPORTED); + RIL_E_(FDN_CHECK_FAILURE); + RIL_E_(ILLEGAL_SIM_OR_ME); + RIL_E_(UNUSED); + RIL_E_(DIAL_MODIFIED_TO_USSD); + RIL_E_(DIAL_MODIFIED_TO_SS); + RIL_E_(DIAL_MODIFIED_TO_DIAL); + RIL_E_(USSD_MODIFIED_TO_DIAL); + RIL_E_(USSD_MODIFIED_TO_SS); + RIL_E_(USSD_MODIFIED_TO_USSD); + RIL_E_(SS_MODIFIED_TO_DIAL); + RIL_E_(SS_MODIFIED_TO_USSD); + RIL_E_(SS_MODIFIED_TO_SS); + RIL_E_(SUBSCRIPTION_NOT_SUPPORTED); + RIL_E_(MISSING_RESOURCE); + RIL_E_(NO_SUCH_ELEMENT); + RIL_E_(INVALID_PARAMETER); + default: + snprintf(unknown, sizeof(unknown), "%d", error); + return unknown; + } +} + +const char *ril_request_to_string(guint request) +{ +#define RIL_REQUEST_(name) case RIL_REQUEST_##name: return #name + static char unknown[24]; + switch (request) { + RIL_REQUEST_(GET_SIM_STATUS); + RIL_REQUEST_(ENTER_SIM_PIN); + RIL_REQUEST_(ENTER_SIM_PUK); + RIL_REQUEST_(ENTER_SIM_PIN2); + RIL_REQUEST_(ENTER_SIM_PUK2); + RIL_REQUEST_(CHANGE_SIM_PIN); + RIL_REQUEST_(CHANGE_SIM_PIN2); + RIL_REQUEST_(ENTER_NETWORK_DEPERSONALIZATION); + RIL_REQUEST_(GET_CURRENT_CALLS); + RIL_REQUEST_(DIAL); + RIL_REQUEST_(GET_IMSI); + RIL_REQUEST_(HANGUP); + RIL_REQUEST_(HANGUP_WAITING_OR_BACKGROUND); + RIL_REQUEST_(HANGUP_FOREGROUND_RESUME_BACKGROUND); + RIL_REQUEST_(SWITCH_HOLDING_AND_ACTIVE); + RIL_REQUEST_(CONFERENCE); + RIL_REQUEST_(UDUB); + RIL_REQUEST_(LAST_CALL_FAIL_CAUSE); + RIL_REQUEST_(SIGNAL_STRENGTH); + RIL_REQUEST_(VOICE_REGISTRATION_STATE); + RIL_REQUEST_(DATA_REGISTRATION_STATE); + RIL_REQUEST_(OPERATOR); + RIL_REQUEST_(RADIO_POWER); + RIL_REQUEST_(DTMF); + RIL_REQUEST_(SEND_SMS); + RIL_REQUEST_(SEND_SMS_EXPECT_MORE); + RIL_REQUEST_(SETUP_DATA_CALL); + RIL_REQUEST_(SIM_IO); + RIL_REQUEST_(SEND_USSD); + RIL_REQUEST_(CANCEL_USSD); + RIL_REQUEST_(GET_CLIR); + RIL_REQUEST_(SET_CLIR); + RIL_REQUEST_(QUERY_CALL_FORWARD_STATUS); + RIL_REQUEST_(SET_CALL_FORWARD); + RIL_REQUEST_(QUERY_CALL_WAITING); + RIL_REQUEST_(SET_CALL_WAITING); + RIL_REQUEST_(SMS_ACKNOWLEDGE); + RIL_REQUEST_(GET_IMEI); + RIL_REQUEST_(GET_IMEISV); + RIL_REQUEST_(ANSWER); + RIL_REQUEST_(DEACTIVATE_DATA_CALL); + RIL_REQUEST_(QUERY_FACILITY_LOCK); + RIL_REQUEST_(SET_FACILITY_LOCK); + RIL_REQUEST_(CHANGE_BARRING_PASSWORD); + RIL_REQUEST_(QUERY_NETWORK_SELECTION_MODE); + RIL_REQUEST_(SET_NETWORK_SELECTION_AUTOMATIC); + RIL_REQUEST_(SET_NETWORK_SELECTION_MANUAL); + RIL_REQUEST_(QUERY_AVAILABLE_NETWORKS); + RIL_REQUEST_(DTMF_START); + RIL_REQUEST_(DTMF_STOP); + RIL_REQUEST_(BASEBAND_VERSION); + RIL_REQUEST_(SEPARATE_CONNECTION); + RIL_REQUEST_(SET_MUTE); + RIL_REQUEST_(GET_MUTE); + RIL_REQUEST_(QUERY_CLIP); + RIL_REQUEST_(LAST_DATA_CALL_FAIL_CAUSE); + RIL_REQUEST_(DATA_CALL_LIST); + RIL_REQUEST_(RESET_RADIO); + RIL_REQUEST_(OEM_HOOK_RAW); + RIL_REQUEST_(OEM_HOOK_STRINGS); + RIL_REQUEST_(SCREEN_STATE); + RIL_REQUEST_(SET_SUPP_SVC_NOTIFICATION); + RIL_REQUEST_(WRITE_SMS_TO_SIM); + RIL_REQUEST_(DELETE_SMS_ON_SIM); + RIL_REQUEST_(SET_BAND_MODE); + RIL_REQUEST_(QUERY_AVAILABLE_BAND_MODE); + RIL_REQUEST_(STK_GET_PROFILE); + RIL_REQUEST_(STK_SET_PROFILE); + RIL_REQUEST_(STK_SEND_ENVELOPE_COMMAND); + RIL_REQUEST_(STK_SEND_TERMINAL_RESPONSE); + RIL_REQUEST_(STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM); + RIL_REQUEST_(EXPLICIT_CALL_TRANSFER); + RIL_REQUEST_(SET_PREFERRED_NETWORK_TYPE); + RIL_REQUEST_(GET_PREFERRED_NETWORK_TYPE); + RIL_REQUEST_(GET_NEIGHBORING_CELL_IDS); + RIL_REQUEST_(SET_LOCATION_UPDATES); + RIL_REQUEST_(CDMA_SET_SUBSCRIPTION_SOURCE); + RIL_REQUEST_(CDMA_SET_ROAMING_PREFERENCE); + RIL_REQUEST_(CDMA_QUERY_ROAMING_PREFERENCE); + RIL_REQUEST_(SET_TTY_MODE); + RIL_REQUEST_(QUERY_TTY_MODE); + RIL_REQUEST_(CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE); + RIL_REQUEST_(CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE); + RIL_REQUEST_(CDMA_FLASH); + RIL_REQUEST_(CDMA_BURST_DTMF); + RIL_REQUEST_(CDMA_VALIDATE_AND_WRITE_AKEY); + RIL_REQUEST_(CDMA_SEND_SMS); + RIL_REQUEST_(CDMA_SMS_ACKNOWLEDGE); + RIL_REQUEST_(GSM_GET_BROADCAST_SMS_CONFIG); + RIL_REQUEST_(GSM_SET_BROADCAST_SMS_CONFIG); + RIL_REQUEST_(GSM_SMS_BROADCAST_ACTIVATION); + RIL_REQUEST_(CDMA_GET_BROADCAST_SMS_CONFIG); + RIL_REQUEST_(CDMA_SET_BROADCAST_SMS_CONFIG); + RIL_REQUEST_(CDMA_SMS_BROADCAST_ACTIVATION); + RIL_REQUEST_(CDMA_SUBSCRIPTION); + RIL_REQUEST_(CDMA_WRITE_SMS_TO_RUIM); + RIL_REQUEST_(CDMA_DELETE_SMS_ON_RUIM); + RIL_REQUEST_(DEVICE_IDENTITY); + RIL_REQUEST_(EXIT_EMERGENCY_CALLBACK_MODE); + RIL_REQUEST_(GET_SMSC_ADDRESS); + RIL_REQUEST_(SET_SMSC_ADDRESS); + RIL_REQUEST_(REPORT_SMS_MEMORY_STATUS); + RIL_REQUEST_(REPORT_STK_SERVICE_IS_RUNNING); + RIL_REQUEST_(CDMA_GET_SUBSCRIPTION_SOURCE); + RIL_REQUEST_(ISIM_AUTHENTICATION); + RIL_REQUEST_(ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU); + RIL_REQUEST_(STK_SEND_ENVELOPE_WITH_STATUS); + RIL_REQUEST_(VOICE_RADIO_TECH); + RIL_REQUEST_(GET_CELL_INFO_LIST); + RIL_REQUEST_(SET_UNSOL_CELL_INFO_LIST_RATE); + RIL_REQUEST_(SET_INITIAL_ATTACH_APN); + RIL_REQUEST_(IMS_REGISTRATION_STATE); + RIL_REQUEST_(IMS_SEND_SMS); + RIL_REQUEST_(SIM_TRANSMIT_APDU_BASIC); + RIL_REQUEST_(SIM_OPEN_CHANNEL); + RIL_REQUEST_(SIM_CLOSE_CHANNEL); + RIL_REQUEST_(SIM_TRANSMIT_APDU_CHANNEL); + RIL_REQUEST_(NV_READ_ITEM); + RIL_REQUEST_(NV_WRITE_ITEM); + RIL_REQUEST_(NV_WRITE_CDMA_PRL); + RIL_REQUEST_(NV_RESET_CONFIG); + RIL_REQUEST_(SET_UICC_SUBSCRIPTION); + RIL_REQUEST_(ALLOW_DATA); + RIL_REQUEST_(GET_HARDWARE_CONFIG); + RIL_REQUEST_(SIM_AUTHENTICATION); + RIL_REQUEST_(GET_DC_RT_INFO); + RIL_REQUEST_(SET_DC_RT_INFO_RATE); + RIL_REQUEST_(SET_DATA_PROFILE); + RIL_REQUEST_(SHUTDOWN); + RIL_REQUEST_(GET_RADIO_CAPABILITY); + RIL_REQUEST_(SET_RADIO_CAPABILITY); + default: + snprintf(unknown, sizeof(unknown), "RIL_REQUEST_%d", request); + return unknown; + } +} + +const char *ril_unsol_event_to_string(guint event) +{ +#define RIL_UNSOL_(name) case RIL_UNSOL_##name: return #name + static char unknown[24]; + switch (event) { + RIL_UNSOL_(RESPONSE_RADIO_STATE_CHANGED); + RIL_UNSOL_(RESPONSE_CALL_STATE_CHANGED); + RIL_UNSOL_(RESPONSE_VOICE_NETWORK_STATE_CHANGED); + RIL_UNSOL_(RESPONSE_NEW_SMS); + RIL_UNSOL_(RESPONSE_NEW_SMS_STATUS_REPORT); + RIL_UNSOL_(RESPONSE_NEW_SMS_ON_SIM); + RIL_UNSOL_(ON_USSD); + RIL_UNSOL_(ON_USSD_REQUEST); + RIL_UNSOL_(NITZ_TIME_RECEIVED); + RIL_UNSOL_(SIGNAL_STRENGTH); + RIL_UNSOL_(DATA_CALL_LIST_CHANGED); + RIL_UNSOL_(SUPP_SVC_NOTIFICATION); + RIL_UNSOL_(STK_SESSION_END); + RIL_UNSOL_(STK_PROACTIVE_COMMAND); + RIL_UNSOL_(STK_EVENT_NOTIFY); + RIL_UNSOL_(STK_CALL_SETUP); + RIL_UNSOL_(SIM_SMS_STORAGE_FULL); + RIL_UNSOL_(SIM_REFRESH); + RIL_UNSOL_(CALL_RING); + RIL_UNSOL_(RESPONSE_SIM_STATUS_CHANGED); + RIL_UNSOL_(RESPONSE_CDMA_NEW_SMS); + RIL_UNSOL_(RESPONSE_NEW_BROADCAST_SMS); + RIL_UNSOL_(CDMA_RUIM_SMS_STORAGE_FULL); + RIL_UNSOL_(RESTRICTED_STATE_CHANGED); + RIL_UNSOL_(ENTER_EMERGENCY_CALLBACK_MODE); + RIL_UNSOL_(CDMA_CALL_WAITING); + RIL_UNSOL_(CDMA_OTA_PROVISION_STATUS); + RIL_UNSOL_(CDMA_INFO_REC); + RIL_UNSOL_(OEM_HOOK_RAW); + RIL_UNSOL_(RINGBACK_TONE); + RIL_UNSOL_(RESEND_INCALL_MUTE); + RIL_UNSOL_(CDMA_SUBSCRIPTION_SOURCE_CHANGED); + RIL_UNSOL_(CDMA_PRL_CHANGED); + RIL_UNSOL_(EXIT_EMERGENCY_CALLBACK_MODE); + RIL_UNSOL_(RIL_CONNECTED); + RIL_UNSOL_(VOICE_RADIO_TECH_CHANGED); + RIL_UNSOL_(CELL_INFO_LIST); + RIL_UNSOL_(RESPONSE_IMS_NETWORK_STATE_CHANGED); + RIL_UNSOL_(UICC_SUBSCRIPTION_STATUS_CHANGED); + RIL_UNSOL_(SRVCC_STATE_NOTIFY); + RIL_UNSOL_(HARDWARE_CONFIG_CHANGED); + RIL_UNSOL_(DC_RT_INFO_CHANGED); + RIL_UNSOL_(RADIO_CAPABILITY); + RIL_UNSOL_(ON_SS); + RIL_UNSOL_(STK_CC_ALPHA_NOTIFY); + default: + snprintf(unknown, sizeof(unknown), "RIL_UNSOL_%d", event); + return unknown; + } +} + +const char *ril_radio_state_to_string(int radio_state) +{ +#define RADIO_STATE_(name) case RADIO_STATE_##name: return #name + static char unknown[16]; + switch (radio_state) { + RADIO_STATE_(OFF); + RADIO_STATE_(UNAVAILABLE); + RADIO_STATE_(SIM_NOT_READY); + RADIO_STATE_(SIM_LOCKED_OR_ABSENT); + RADIO_STATE_(SIM_READY); + RADIO_STATE_(RUIM_NOT_READY); + RADIO_STATE_(RUIM_READY); + RADIO_STATE_(RUIM_LOCKED_OR_ABSENT); + RADIO_STATE_(NV_NOT_READY); + RADIO_STATE_(NV_READY); + RADIO_STATE_(ON); + default: + snprintf(unknown, sizeof(unknown), "%d (?)", radio_state); + return unknown; + } +} + +int ril_address_family(const char *addr) +{ + if (strchr(addr, ':')) { + return AF_INET6; + } else if (strchr(addr, '.')) { + return AF_INET; + } else { + return AF_UNSPEC; + } +} + +gboolean ril_util_parse_reg(const void *data, guint len, + struct ril_reg_data *reg) +{ + GRilIoParser rilp; + int nparams; + gchar *sstatus = NULL, *slac = NULL, *sci = NULL; + gchar *stech = NULL, *sreason = NULL, *smax = NULL; + + memset(reg, 0, sizeof(*reg)); + + /* Size of response string array + * + * Should be: + * >= 4 for VOICE_REG reply + * >= 5 for DATA_REG reply + */ + grilio_parser_init(&rilp, data, len); + if (!grilio_parser_get_int32(&rilp, &nparams) || nparams < 4) { + DBG("broken response"); + return FALSE; + } + + sstatus = grilio_parser_get_utf8(&rilp); + if (!sstatus) { + DBG("No sstatus value returned!"); + return FALSE; + } + + slac = grilio_parser_get_utf8(&rilp); + sci = grilio_parser_get_utf8(&rilp); + stech = grilio_parser_get_utf8(&rilp); + nparams -= 4; + + reg->ril_status = atoi(sstatus); + if (reg->ril_status > 10) { + reg->status = reg->status - 10; + } else { + reg->status = reg->ril_status; + } + + /* FIXME: need to review VOICE_REGISTRATION response + * as it returns ~15 parameters ( vs. 6 for DATA ). + * + * The first four parameters are the same for both + * responses ( although status includes values for + * emergency calls for VOICE response ). + * + * Parameters 5 & 6 have different meanings for + * voice & data response. + */ + if (nparams--) { + /* TODO: different use for CDMA */ + sreason = grilio_parser_get_utf8(&rilp); + if (nparams--) { + /* TODO: different use for CDMA */ + smax = grilio_parser_get_utf8(&rilp); + if (smax) { + reg->max_calls = atoi(smax); + } + } + } + + reg->lac = slac ? strtol(slac, NULL, 16) : -1; + reg->ci = sci ? strtol(sci, NULL, 16) : -1; + reg->access_tech = ril_parse_tech(stech, ®->ril_tech); + + DBG("%s,%s,%s,%d,%s,%s,%s", registration_status_to_string(reg->status), + slac, sci, reg->ril_tech, + registration_tech_to_string(reg->access_tech), + sreason, smax); + + g_free(sstatus); + g_free(slac); + g_free(sci); + g_free(stech); + g_free(sreason); + g_free(smax); + return TRUE; +} + +/* Returns enum access_technology or -1 on failure. */ +int ril_parse_tech(const char *stech, int *ril_tech) +{ + int access_tech = -1; + int tech = -1; + if (stech && stech[0]) { + tech = atoi(stech); + switch (tech) { + case RADIO_TECH_GPRS: + case RADIO_TECH_GSM: + access_tech = ACCESS_TECHNOLOGY_GSM; + break; + case RADIO_TECH_EDGE: + access_tech = ACCESS_TECHNOLOGY_GSM_EGPRS; + break; + case RADIO_TECH_UMTS: + access_tech = ACCESS_TECHNOLOGY_UTRAN; + break; + case RADIO_TECH_HSDPA: + access_tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA; + break; + case RADIO_TECH_HSUPA: + access_tech = ACCESS_TECHNOLOGY_UTRAN_HSUPA; + break; + case RADIO_TECH_HSPA: + case RADIO_TECH_HSPAP: + case RADIO_TECH_DC_HSDPA: + access_tech = ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; + break; + case RADIO_TECH_LTE: + access_tech = ACCESS_TECHNOLOGY_EUTRAN; + break; + default: + DBG("Unknown RIL tech %s", stech); + tech = -1; + break; + } + + } + if (ril_tech) { + *ril_tech = tech; + } + return access_tech; +} + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_util.h b/ofono/drivers/ril/ril_util.h new file mode 100644 index 00000000..14009cf2 --- /dev/null +++ b/ofono/drivers/ril/ril_util.h @@ -0,0 +1,58 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#ifndef RIL_UTIL_H +#define RIL_UTIL_H + +#include "ril_types.h" + +#include + +struct ril_reg_data { + int ril_status; + int ril_tech; + int status; /* enum network_registration_status or -1 if none */ + int access_tech; /* enum access_technology or -1 if none */ + int lac; + int ci; + int max_calls; +}; + +const char *ril_error_to_string(int error); +const char *ril_request_to_string(guint request); +const char *ril_unsol_event_to_string(guint event); +const char *ril_radio_state_to_string(int radio_state); +int ril_parse_tech(const char *stech, int *ril_tech); +int ril_address_family(const char *addr); +gboolean ril_util_parse_reg(const void *data, guint len, + struct ril_reg_data *parsed); + +#define ril_error_init_ok(err) \ + ((err)->error = 0, (err)->type = OFONO_ERROR_TYPE_NO_ERROR) +#define ril_error_init_failure(err) \ + ((err)->error = 0, (err)->type = OFONO_ERROR_TYPE_FAILURE) + +#define ril_error_ok(err) (ril_error_init_ok(err), err) +#define ril_error_failure(err) (ril_error_init_failure(err), err) + +#endif /* RIL_UTIL_H */ + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/ofono/drivers/ril/ril_voicecall.c b/ofono/drivers/ril/ril_voicecall.c new file mode 100644 index 00000000..ed00f6f8 --- /dev/null +++ b/ofono/drivers/ril/ril_voicecall.c @@ -0,0 +1,824 @@ +/* + * oFono - Open Source Telephony - RIL-based devices + * + * Copyright (C) 2015 Jolla Ltd. + * + * 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. + */ + +#include "ril_plugin.h" +#include "ril_constants.h" +#include "ril_util.h" +#include "ril_log.h" + +#include "common.h" + +/* Amount of ms we wait between CLCC calls */ +#define FLAG_NEED_CLIP 1 +#define MAX_DTMF_BUFFER 32 + +enum ril_voicecall_events { + VOICECALL_EVENT_CALL_STATE_CHANGED, + VOICECALL_EVENT_SUPP_SVC_NOTIFICATION, + VOICECALL_EVENT_RINGBACK_TONE, + VOICECALL_EVENT_COUNT, +}; + +struct ril_voicecall { + GSList *calls; + GRilIoChannel *io; + GRilIoQueue *q; + struct ofono_voicecall *vc; + unsigned int local_release; + unsigned char flags; + ofono_voicecall_cb_t cb; + void *data; + guint timer_id; + gchar *tone_queue; + guint send_dtmf_id; + guint clcc_poll_id; + gulong event_id[VOICECALL_EVENT_COUNT]; + gulong supp_svc_notification_id; + gulong ringback_tone_event_id; +}; + +struct release_id_req { + struct ofono_voicecall *vc; + ofono_voicecall_cb_t cb; + gpointer data; + int id; +}; + +struct ril_voicecall_change_state_req { + struct ofono_voicecall *vc; + ofono_voicecall_cb_t cb; + gpointer data; + int affected_types; +}; + +struct lastcause_req { + struct ofono_voicecall *vc; + int id; +}; + +struct ril_voicecall_req { + struct ofono_voicecall *vc; + ofono_voicecall_cb_t cb; + gpointer data; +}; + +static void ril_voicecall_send_one_dtmf(struct ril_voicecall *vd); +static void ril_voicecall_clear_dtmf_queue(struct ril_voicecall *vd); + +/* + * structs ofono_voicecall and voicecall are fully defined + * in src/voicecall.c; we need (read) access to the + * call objects, so partially redefine them here. + */ +struct ofono_voicecall { + GSList *call_list; + /* ... */ +}; + +struct voicecall { + struct ofono_call *call; + /* ... */ +}; + +static inline struct ril_voicecall *ril_voicecall_get_data( + struct ofono_voicecall *vc) +{ + return ofono_voicecall_get_data(vc); +} + +static gint ril_voicecall_compare(gconstpointer a, gconstpointer b) +{ + const struct ofono_call *ca = a; + const struct ofono_call *cb = b; + + if (ca->id < cb->id) + return -1; + + if (ca->id > cb->id) + return 1; + + return 0; +} + +static GSList *ril_voicecall_parse_clcc(const void *data, guint len) +{ + GRilIoParser rilp; + GSList *l = NULL; + int num = 0, i; + gchar *number, *name; + + grilio_parser_init(&rilp, data, len); + + /* Number of RIL_Call structs */ + + grilio_parser_get_int32(&rilp, &num); + for (i = 0; i < num; i++) { + struct ofono_call *call = g_new(struct ofono_call, 1); + gint tmp; + + ofono_call_init(call); + grilio_parser_get_int32(&rilp, &call->status); + grilio_parser_get_uint32(&rilp, &call->id); + grilio_parser_get_int32(&rilp, &call->phone_number.type); + grilio_parser_get_int32(&rilp, NULL); /* isMpty */ + + tmp = 0; + grilio_parser_get_int32(&rilp, &tmp); + call->direction = (tmp ? /* isMT */ + CALL_DIRECTION_MOBILE_TERMINATED : + CALL_DIRECTION_MOBILE_ORIGINATED); + + grilio_parser_get_int32(&rilp, NULL); /* als */ + grilio_parser_get_int32(&rilp, &call->type); /* isVoice */ + grilio_parser_get_int32(&rilp, NULL); /* isVoicePrivacy */ + number = grilio_parser_get_utf8(&rilp); + if (number) { + strncpy(call->phone_number.number, number, + OFONO_MAX_PHONE_NUMBER_LENGTH); + g_free(number); + } + grilio_parser_get_int32(&rilp, NULL); /* numberPresentation */ + name = grilio_parser_get_utf8(&rilp); + if (name) { + strncpy(call->name, name, OFONO_MAX_CALLER_NAME_LENGTH); + g_free(name); + } + grilio_parser_get_int32(&rilp, NULL); /* namePresentation */ + grilio_parser_get_int32(&rilp, &tmp); /* uusInfo */ + GASSERT(!tmp); + + if (strlen(call->phone_number.number) > 0) { + call->clip_validity = 0; + } else { + call->clip_validity = 2; + } + + DBG("[id=%d,status=%d,type=%d,number=%s,name=%s]", + call->id, call->status, call->type, + call->phone_number.number, call->name); + + l = g_slist_insert_sorted(l, call, ril_voicecall_compare); + } + + return l; +} + +static void ril_voicecall_lastcause_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct lastcause_req *reqdata = user_data; + struct ofono_voicecall *vc = reqdata->vc; + int tmp; + int id = reqdata->id; + + enum ofono_disconnect_reason reason = OFONO_DISCONNECT_REASON_ERROR; + int last_cause = CALL_FAIL_ERROR_UNSPECIFIED; + GRilIoParser rilp; + grilio_parser_init(&rilp, data, len); + if (grilio_parser_get_int32(&rilp, &tmp) && tmp > 0) { + grilio_parser_get_int32(&rilp, &last_cause); + } + + /* + * Not all call control cause values specified in 3GPP TS 24.008 + * "Mobile radio interface Layer 3 specification; Core network + * protocols", Annex H, are properly reflected in the RIL API. + * For example, cause #21 "call rejected" is mapped to + * CALL_FAIL_ERROR_UNSPECIFIED, and thus indistinguishable + * from a network failure. We signal disconnect reason "remote" + * for cause values + * - #16 "normal call clearing" + * - #17 "user busy" + * - UNSPECIFIED for MO calls that are not yet connected + * , and disconnect reason "network" otherwise. + */ + ofono_info("Call %d ended with RIL cause %d", id, last_cause); + if (last_cause == CALL_FAIL_NORMAL || last_cause == CALL_FAIL_BUSY) { + reason = OFONO_DISCONNECT_REASON_REMOTE_HANGUP; + } + + if (last_cause == CALL_FAIL_ERROR_UNSPECIFIED) { + GSList *l; + struct voicecall *v; + for (l = vc->call_list; l; l = l->next) { + v = l->data; + if (v->call->id == id) { + if (v->call->status == CALL_STATUS_DIALING || + v->call->status == CALL_STATUS_ALERTING) { + reason = OFONO_DISCONNECT_REASON_REMOTE_HANGUP; + } + break; + } + } + } + + ofono_voicecall_disconnected(vc, id, reason, NULL); +} + +static void ril_voicecall_clcc_poll_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_voicecall *vd = user_data; + GSList *calls; + GSList *n, *o; + struct ofono_error error; + + GASSERT(vd->clcc_poll_id); + vd->clcc_poll_id = 0; + + if (status != RIL_E_SUCCESS) { + ofono_error("We are polling CLCC and received an error"); + ofono_error("All bets are off for call management"); + return; + } + + calls = ril_voicecall_parse_clcc(data, len); + + n = calls; + o = vd->calls; + + while (n || o) { + struct ofono_call *nc = n ? n->data : NULL; + struct ofono_call *oc = o ? o->data : NULL; + + if (oc && (nc == NULL || (nc->id > oc->id))) { + if (vd->local_release & (1 << oc->id)) { + ofono_voicecall_disconnected(vd->vc, oc->id, + OFONO_DISCONNECT_REASON_LOCAL_HANGUP, + NULL); + } else { + /* Get disconnect cause before informing + * oFono core */ + struct lastcause_req *reqdata = + g_new0(struct lastcause_req, 1); + + reqdata->vc = vd->vc; + reqdata->id = oc->id; + grilio_queue_send_request_full(vd->q, NULL, + RIL_REQUEST_LAST_CALL_FAIL_CAUSE, + ril_voicecall_lastcause_cb, + g_free, reqdata); + } + + ril_voicecall_clear_dtmf_queue(vd); + o = o->next; + + } else if (nc && (oc == NULL || (nc->id < oc->id))) { + /* new call, signal it */ + if (nc->type) { + ofono_voicecall_notify(vd->vc, nc); + if (vd->cb) { + ofono_voicecall_cb_t cb = vd->cb; + cb(ril_error_ok(&error), vd->data); + vd->cb = NULL; + vd->data = NULL; + } + } + + n = n->next; + + } else { + /* + * Always use the clip_validity from old call + * the only place this is truly told to us is + * in the CLIP notify, the rest are fudged + * anyway. Useful when RING, CLIP is used, + * and we're forced to use CLCC and clip_validity + * is 1 + */ + if (oc->clip_validity == 1) { + nc->clip_validity = oc->clip_validity; + } + + nc->cnap_validity = oc->cnap_validity; + + /* + * CDIP doesn't arrive as part of CLCC, always + * re-use from the old call + */ + memcpy(&nc->called_number, &oc->called_number, + sizeof(oc->called_number)); + + /* + * If the CLIP is not provided and the CLIP never + * arrives, or RING is used, then signal the call + * here + */ + if (nc->status == CALL_STATUS_INCOMING && + (vd->flags & FLAG_NEED_CLIP)) { + if (nc->type) { + ofono_voicecall_notify(vd->vc, nc); + } + + vd->flags &= ~FLAG_NEED_CLIP; + } else if (memcmp(nc, oc, sizeof(*nc)) && nc->type) { + ofono_voicecall_notify(vd->vc, nc); + } + + n = n->next; + o = o->next; + } + } + + g_slist_free_full(vd->calls, g_free); + + vd->calls = calls; + vd->local_release = 0; +} + +static void ril_voicecall_clcc_poll(struct ril_voicecall *vd) +{ + GASSERT(vd); + if (!vd->clcc_poll_id) { + vd->clcc_poll_id = grilio_queue_send_request_full(vd->q, + NULL, RIL_REQUEST_GET_CURRENT_CALLS, + ril_voicecall_clcc_poll_cb, NULL, vd); + } +} + +static void ril_voicecall_request_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_voicecall_change_state_req *req = user_data; + struct ril_voicecall *vd = ril_voicecall_get_data(req->vc); + struct ofono_error error; + + if (status == RIL_E_SUCCESS) { + GSList *l; + + if (req->affected_types) { + for (l = vd->calls; l; l = l->next) { + struct ofono_call *call = l->data; + + if (req->affected_types & (1 << call->status)) { + vd->local_release |= (1 << call->id); + } + } + } + + ril_error_init_ok(&error); + } else { + ofono_error("generic fail"); + ril_error_init_failure(&error); + } + + ril_voicecall_clcc_poll(vd); + + /* We have to callback after we schedule a poll if required */ + if (req->cb) { + req->cb(&error, req->data); + } +} + +static void ril_voicecall_request(const guint rreq, struct ofono_voicecall *vc, + unsigned int affected_types, GRilIoRequest *ioreq, + ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + struct ril_voicecall_change_state_req *req; + + req = g_new0(struct ril_voicecall_change_state_req, 1); + req->vc = vc; + req->cb = cb; + req->data = data; + req->affected_types = affected_types; + + grilio_queue_send_request_full(vd->q, ioreq, rreq, + ril_voicecall_request_cb, g_free, req); +} + +static void ril_voicecall_dial_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_voicecall_req *cbd = user_data; + struct ofono_voicecall *vc = cbd->vc; + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + + if (status == RIL_E_SUCCESS) { + if (vd->cb) { + /* CLCC will update the oFono call list with + * proper ids if it's not done yet */ + ril_voicecall_clcc_poll(vd); + } + } else { + struct ofono_error error; + ofono_error("call failed."); + vd->cb = cbd->cb; + vd->data = cbd->data; + cbd->cb(ril_error_failure(&error), cbd->data); + } +} + +static void ril_voicecall_dial(struct ofono_voicecall *vc, + const struct ofono_phone_number *ph, + enum ofono_clir_option clir, ofono_voicecall_cb_t cb, + void *data) +{ + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + struct ril_voicecall_req *cbd = g_new(struct ril_voicecall_req, 1); + const char *phstr = phone_number_to_string(ph); + GRilIoRequest *req = grilio_request_new(); + + ofono_info("dialing \"%s\"", phstr); + + DBG("%s,%d,0", phstr, clir); + cbd->vc = vc; + cbd->cb = cb; + cbd->data = data; + + GASSERT(!vd->cb); + vd->cb = cbd->cb; + vd->data = cbd->data; + + grilio_request_append_utf8(req, phstr); /* Number to dial */ + grilio_request_append_int32(req, clir); /* CLIR mode */ + grilio_request_append_int32(req, 0); /* UUS information (absent) */ + + grilio_queue_send_request_full(vd->q, req, RIL_REQUEST_DIAL, + ril_voicecall_dial_cb, g_free, cbd); + grilio_request_unref(req); +} + +static void ril_voicecall_hangup_all(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + struct ofono_error error; + GSList *l; + + for (l = vd->calls; l; l = l->next) { + struct ofono_call *call = l->data; + GRilIoRequest *req = grilio_request_sized_new(8); + + /* TODO: Hangup just the active ones once we have call + * state tracking (otherwise it can't handle ringing) */ + DBG("Hanging up call with id %d", call->id); + grilio_request_append_int32(req, 1); /* Always 1 - AT+CHLD=1x */ + grilio_request_append_int32(req, call->id); + + /* Send request to RIL */ + ril_voicecall_request(RIL_REQUEST_HANGUP, vc, 0x3f, req, + NULL, NULL); + grilio_request_unref(req); + } + + /* TODO: Deal in case of an error at hungup */ + cb(ril_error_ok(&error), data); +} + +static void ril_voicecall_hangup_specific(struct ofono_voicecall *vc, + int id, ofono_voicecall_cb_t cb, void *data) +{ + GRilIoRequest *req = grilio_request_sized_new(8); + struct ofono_error error; + + DBG("Hanging up call with id %d", id); + grilio_request_append_int32(req, 1); /* Always 1 - AT+CHLD=1x */ + grilio_request_append_int32(req, id); + + /* Send request to RIL */ + ril_voicecall_request(RIL_REQUEST_HANGUP, vc, 0x3f, req, NULL, NULL); + grilio_request_unref(req); + cb(ril_error_ok(&error), data); +} + +static void ril_voicecall_call_state_changed_event(GRilIoChannel *io, + guint ril_event, const void *data, guint len, void *user_data) +{ + struct ril_voicecall *vd = user_data; + + GASSERT(ril_event == RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED); + + /* Just need to request the call list again */ + ril_voicecall_clcc_poll(vd); +} + +static void ril_voicecall_supp_svc_notification_event(GRilIoChannel *io, + guint ril_event, const void *data, guint len, void *user_data) +{ + GRilIoParser rilp; + struct ril_voicecall *vd = user_data; + struct ofono_phone_number phone; + int type = 0, code = 0, index = 0; + char *tmp = NULL; + + GASSERT(ril_event == RIL_UNSOL_SUPP_SVC_NOTIFICATION); + + grilio_parser_init(&rilp, data, len); + grilio_parser_get_int32(&rilp, &type); + grilio_parser_get_int32(&rilp, &code); + grilio_parser_get_int32(&rilp, &index); + grilio_parser_get_int32(&rilp, NULL); + tmp = grilio_parser_get_utf8(&rilp); + + if (tmp) { + strncpy(phone.number, tmp, OFONO_MAX_PHONE_NUMBER_LENGTH); + phone.number[OFONO_MAX_PHONE_NUMBER_LENGTH] = 0; + g_free(tmp); + } else { + phone.number[0] = 0; + } + + DBG("RIL data: MT/MO: %i, code: %i, index: %i", type, code, index); + + /* 0 stands for MO intermediate (support TBD), 1 for MT unsolicited */ + if (type == 1) { + ofono_voicecall_ssn_mt_notify(vd->vc, 0, code, index, &phone); + } else { + ofono_error("Unknown SS notification"); + } +} + +static void ril_voicecall_answer(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + /* Send request to RIL */ + DBG("Answering current call"); + ril_voicecall_request(RIL_REQUEST_ANSWER, vc, 0, NULL, cb, data); +} + +static void ril_voicecall_send_dtmf_cb(GRilIoChannel *io, int status, + const void *data, guint len, void *user_data) +{ + struct ril_voicecall *vd = user_data; + + GASSERT(vd->send_dtmf_id); + vd->send_dtmf_id = 0; + + if (status == RIL_E_SUCCESS) { + /* Remove sent DTMF character from queue */ + gchar *tmp = g_strdup(vd->tone_queue + 1); + g_free(vd->tone_queue); + vd->tone_queue = tmp; + + /* Send the next one */ + ril_voicecall_send_one_dtmf(vd); + } else { + DBG("error=%d", status); + ril_voicecall_clear_dtmf_queue(vd); + } +} + +static void ril_voicecall_send_one_dtmf(struct ril_voicecall *vd) +{ + if (!vd->send_dtmf_id && vd->tone_queue && vd->tone_queue[0]) { + GRilIoRequest *req = grilio_request_sized_new(4); + + /* RIL wants just one character */ + DBG("%c", vd->tone_queue[0]); + grilio_request_append_utf8_chars(req, vd->tone_queue, 1); + vd->send_dtmf_id = grilio_queue_send_request_full(vd->q, req, + RIL_REQUEST_DTMF, ril_voicecall_send_dtmf_cb, NULL, vd); + grilio_request_unref(req); + } +} + +static void ril_voicecall_send_dtmf(struct ofono_voicecall *vc, + const char *dtmf, ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + struct ofono_error error; + + DBG("Queue '%s'",dtmf); + + /* + * Queue any incoming DTMF (up to MAX_DTMF_BUFFER characters), + * send them to RIL one-by-one, immediately call back + * core with no error + */ + g_strlcat(vd->tone_queue, dtmf, MAX_DTMF_BUFFER); + ril_voicecall_send_one_dtmf(vd); + + cb(ril_error_ok(&error), data); +} + +static void ril_voicecall_clear_dtmf_queue(struct ril_voicecall *vd) +{ + g_free(vd->tone_queue); + vd->tone_queue = g_strnfill(MAX_DTMF_BUFFER + 1, '\0'); + if (vd->send_dtmf_id) { + grilio_channel_cancel_request(vd->io, vd->send_dtmf_id, FALSE); + vd->send_dtmf_id = 0; + } +} + +static void ril_voicecall_clcc_poll_on_success(GRilIoChannel *io, + int status, const void *data, guint len, void *user_data) +{ + if (status == RIL_E_SUCCESS) { + ril_voicecall_clcc_poll((struct ril_voicecall *)user_data); + } +} + +static void ril_voicecall_create_multiparty(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + grilio_queue_send_request_full(vd->q, NULL, RIL_REQUEST_CONFERENCE, + ril_voicecall_clcc_poll_on_success, NULL, vd); +} + +static void ril_voicecall_transfer(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_voicecall_request(RIL_REQUEST_EXPLICIT_CALL_TRANSFER, + vc, 0, NULL, cb, data); +} + +static void ril_voicecall_private_chat(struct ofono_voicecall *vc, int id, + ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + GRilIoRequest *req = grilio_request_sized_new(8); + grilio_request_append_int32(req, 1); + grilio_request_append_int32(req, id); + grilio_queue_send_request_full(vd->q, req, + RIL_REQUEST_SEPARATE_CONNECTION, + ril_voicecall_clcc_poll_on_success, NULL, vd); + grilio_request_unref(req); +} + +static void ril_voicecall_swap_without_accept(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_voicecall_request(RIL_REQUEST_SWITCH_HOLDING_AND_ACTIVE, + vc, 0, NULL, cb, data); +} + +static void ril_voicecall_hold_all_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_voicecall_request(RIL_REQUEST_SWITCH_HOLDING_AND_ACTIVE, + vc, 0, NULL, cb, data); +} + +static void ril_voicecall_release_all_held(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_voicecall_request(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, + vc, 0, NULL, cb, data); +} + +static void ril_voicecall_release_all_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_voicecall_request(RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, + vc, 0, NULL, cb, data); +} + +static void ril_voicecall_set_udub(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_voicecall_request(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, + vc, 0, NULL, cb, data); +} + +static gboolean ril_voicecall_enable_supp_svc(struct ril_voicecall *vd) +{ + GRilIoRequest *req = grilio_request_sized_new(8); + + grilio_request_append_int32(req, 1); /* size of array */ + grilio_request_append_int32(req, 1); /* notifications enabled */ + + grilio_queue_send_request(vd->q, req, + RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION); + grilio_request_unref(req); + + /* Makes this a single shot */ + return FALSE; +} + +static void ril_voicecall_ringback_tone_event(GRilIoChannel *io, + guint code, const void *data, guint len, void *user_data) +{ + struct ril_voicecall *vd = user_data; + GRilIoParser rilp; + guint32 playTone = FALSE; + int tmp; + + GASSERT(code == RIL_UNSOL_RINGBACK_TONE); + grilio_parser_init(&rilp, data, len); + if (grilio_parser_get_int32(&rilp, &tmp) && tmp > 0) { + grilio_parser_get_uint32(&rilp, &playTone); + } + + DBG("play ringback tone: %d", playTone); + ofono_voicecall_ringback_tone_notify(vd->vc, playTone); +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ril_voicecall *vd = user_data; + + GASSERT(vd->timer_id); + vd->timer_id = 0; + ofono_voicecall_register(vd->vc); + + /* Initialize call list */ + ril_voicecall_clcc_poll(vd); + + /* request supplementary service notifications*/ + ril_voicecall_enable_supp_svc(vd); + + /* Unsol when call state changes */ + vd->event_id[VOICECALL_EVENT_CALL_STATE_CHANGED] = + grilio_channel_add_unsol_event_handler(vd->io, + ril_voicecall_call_state_changed_event, + RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, vd); + + /* Unsol when call set in hold */ + vd->event_id[VOICECALL_EVENT_SUPP_SVC_NOTIFICATION] = + grilio_channel_add_unsol_event_handler(vd->io, + ril_voicecall_supp_svc_notification_event, + RIL_UNSOL_SUPP_SVC_NOTIFICATION, vd); + + /* Register for ringback tone notifications */ + vd->event_id[VOICECALL_EVENT_RINGBACK_TONE] = + grilio_channel_add_unsol_event_handler(vd->io, + ril_voicecall_ringback_tone_event, + RIL_UNSOL_RINGBACK_TONE, vd); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +static int ril_voicecall_probe(struct ofono_voicecall *vc, unsigned int vendor, + void *data) +{ + struct ril_modem *modem = data; + struct ril_voicecall *vd; + + DBG(""); + vd = g_new0(struct ril_voicecall, 1); + vd->io = grilio_channel_ref(ril_modem_io(modem)); + vd->q = grilio_queue_new(vd->io); + vd->vc = vc; + vd->timer_id = g_idle_add(ril_delayed_register, vd); + ril_voicecall_clear_dtmf_queue(vd); + ofono_voicecall_set_data(vc, vd); + return 0; +} + +static void ril_voicecall_remove(struct ofono_voicecall *vc) +{ + int i; + struct ril_voicecall *vd = ril_voicecall_get_data(vc); + + DBG(""); + ofono_voicecall_set_data(vc, NULL); + g_slist_foreach(vd->calls, (GFunc) g_free, NULL); + g_slist_free(vd->calls); + + for (i=0; ievent_id); i++) { + grilio_channel_remove_handler(vd->io, vd->event_id[i]); + } + + if (vd->timer_id > 0) { + g_source_remove(vd->timer_id); + } + + grilio_channel_unref(vd->io); + grilio_queue_cancel_all(vd->q, FALSE); + grilio_queue_unref(vd->q); + g_free(vd->tone_queue); + g_free(vd); +} + +const struct ofono_voicecall_driver ril_voicecall_driver = { + .name = RILMODEM_DRIVER, + .probe = ril_voicecall_probe, + .remove = ril_voicecall_remove, + .dial = ril_voicecall_dial, + .answer = ril_voicecall_answer, + .hangup_all = ril_voicecall_hangup_all, + .release_specific = ril_voicecall_hangup_specific, + .send_tones = ril_voicecall_send_dtmf, + .create_multiparty = ril_voicecall_create_multiparty, + .transfer = ril_voicecall_transfer, + .private_chat = ril_voicecall_private_chat, + .swap_without_accept = ril_voicecall_swap_without_accept, + .hold_all_active = ril_voicecall_hold_all_active, + .release_all_held = ril_voicecall_release_all_held, + .set_udub = ril_voicecall_set_udub, + .release_all_active = ril_voicecall_release_all_active +}; + +/* + * Local Variables: + * mode: C + * c-basic-offset: 8 + * indent-tabs-mode: t + * End: + */ diff --git a/rpm/ofono.spec b/rpm/ofono.spec index dd7f8499..94d14aae 100644 --- a/rpm/ofono.spec +++ b/rpm/ofono.spec @@ -19,6 +19,9 @@ BuildRequires: pkgconfig(libudev) >= 145 BuildRequires: pkgconfig(bluez) >= 4.85 BuildRequires: pkgconfig(mobile-broadband-provider-info) BuildRequires: pkgconfig(libwspcodec) >= 2.0 +BuildRequires: pkgconfig(libglibutil) +BuildRequires: pkgconfig(libgrilio) +BuildRequires: mce-headers BuildRequires: libtool BuildRequires: automake BuildRequires: autoconf @@ -66,6 +69,7 @@ autoreconf --force --install %configure --disable-static \ --enable-test \ --enable-logcontrol \ + --enable-jolla-rilmodem \ --with-systemdunitdir="/%{_lib}/systemd/system" make %{?jobs:-j%jobs}