diff --git a/drivers/rilmodem/call-barring.c b/drivers/rilmodem/call-barring.c new file mode 100644 index 00000000..6a19556e --- /dev/null +++ b/drivers/rilmodem/call-barring.c @@ -0,0 +1,245 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2014 Jolla Ltd + * Contact: Miia Leinonen + * Copyright (C) 2014 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#include +#include + +#include "common.h" +#include "grilrequest.h" +#include "grilreply.h" +#include "call-barring.h" +#include "rilmodem.h" +#include "ril_constants.h" + +struct barring_data { + GRil *ril; +}; + +static void ril_call_barring_query_cb(struct ril_msg *message, + gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_barring_query_cb_t cb = cbd->cb; + struct barring_data *bd = cbd->user; + int bearer_class; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: query failed, err: %s", __func__, + ril_error_to_string(message->error)); + goto error; + } + + bearer_class = g_ril_reply_parse_query_facility_lock(bd->ril, message); + if (bearer_class < 0) + goto error; + + CALLBACK_WITH_SUCCESS(cb, bearer_class, cbd->data); + return; + +error: + CALLBACK_WITH_FAILURE(cb, -1, cbd->data); +} + +static void ril_call_barring_query(struct ofono_call_barring *cb, + const char *lock, int cls, + ofono_call_barring_query_cb_t callback, + void *data) +{ + struct barring_data *bd = ofono_call_barring_get_data(cb); + struct cb_data *cbd = cb_data_new(callback, data, bd); + struct parcel rilp; + + DBG("lock: %s, services to query: %d", lock, cls); + + /* + * RIL modems do not support 7 as default bearer class. According to + * TS 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; + + /* ril.h: password should be empty string "" when not needed */ + g_ril_request_query_facility_lock(bd->ril, lock, "", cls, &rilp); + + if (g_ril_send(bd->ril, RIL_REQUEST_QUERY_FACILITY_LOCK, &rilp, + ril_call_barring_query_cb, cbd, g_free) <= 0) { + ofono_error("%s: sending failed", __func__); + g_free(cbd); + CALLBACK_WITH_FAILURE(callback, -1, data); + } +} + +static void ril_call_barring_set_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_barring_set_cb_t cb = cbd->cb; + struct barring_data *bd = cbd->user; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: set failed, err: %s", __func__, + ril_error_to_string(message->error)); + goto error; + } + + /* Just for printing return value */ + g_ril_reply_parse_set_facility_lock(bd->ril, message); + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + return; + +error: + CALLBACK_WITH_FAILURE(cb, cbd->data); +} + +static void ril_call_barring_set(struct ofono_call_barring *cb, + const char *lock, int enable, + const char *passwd, int cls, + ofono_call_barring_set_cb_t callback, + void *data) +{ + struct barring_data *bd = ofono_call_barring_get_data(cb); + struct cb_data *cbd = cb_data_new(callback, data, bd); + struct parcel rilp; + + DBG("lock: %s, enable: %d, bearer class: %d", 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; + + g_ril_request_set_facility_lock(bd->ril, lock, enable, + passwd, cls, &rilp); + + if (g_ril_send(bd->ril, RIL_REQUEST_SET_FACILITY_LOCK, &rilp, + ril_call_barring_set_cb, cbd, g_free) <= 0) { + ofono_error("%s: sending failed", __func__); + g_free(cbd); + CALLBACK_WITH_FAILURE(callback, data); + } +} + +static void ril_call_barring_set_passwd_cb(struct ril_msg *message, + gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_barring_set_cb_t cb = cbd->cb; + struct barring_data *bd = cbd->user; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: set password failed, err: %s", __func__, + ril_error_to_string(message->error)); + goto error; + } + + g_ril_print_response_no_args(bd->ril, message); + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + return; + +error: + CALLBACK_WITH_FAILURE(cb, cbd->data); +} + +static void ril_call_barring_set_passwd(struct ofono_call_barring *barr, + const char *lock, + const char *old_passwd, + const char *new_passwd, + ofono_call_barring_set_cb_t cb, + void *data) +{ + struct barring_data *bd = ofono_call_barring_get_data(barr); + struct cb_data *cbd = cb_data_new(cb, data, bd); + struct parcel rilp; + + DBG("lock %s old %s new %s", lock, old_passwd, new_passwd); + + g_ril_request_change_barring_password(bd->ril, lock, old_passwd, + new_passwd, &rilp); + + if (g_ril_send(bd->ril, RIL_REQUEST_CHANGE_BARRING_PASSWORD, &rilp, + ril_call_barring_set_passwd_cb, cbd, g_free) <= 0) { + ofono_error("%s: sending failed", __func__); + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_call_barring *cb = user_data; + + ofono_call_barring_register(cb); + return FALSE; +} + +static int ril_call_barring_probe(struct ofono_call_barring *cb, + unsigned int vendor, void *user) +{ + GRil *ril = user; + struct barring_data *bd = g_try_new0(struct barring_data, 1); + if (bd == NULL) + return -ENOMEM; + + bd->ril = g_ril_clone(ril); + ofono_call_barring_set_data(cb, bd); + + g_idle_add(ril_delayed_register, cb); + + return 0; +} + +static void ril_call_barring_remove(struct ofono_call_barring *cb) +{ + struct barring_data *data = ofono_call_barring_get_data(cb); + ofono_call_barring_set_data(cb, NULL); + + g_ril_unref(data->ril); + g_free(data); +} + +static struct ofono_call_barring_driver driver = { + .name = "rilmodem", + .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 +}; + +void ril_call_barring_init(void) +{ + ofono_call_barring_driver_register(&driver); +} + +void ril_call_barring_exit(void) +{ + ofono_call_barring_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/call-forwarding.c b/drivers/rilmodem/call-forwarding.c new file mode 100644 index 00000000..7965e7d5 --- /dev/null +++ b/drivers/rilmodem/call-forwarding.c @@ -0,0 +1,327 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2013 Jolla Ltd + * Contact: Jussi Kangas + * Copyright (C) 2014 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "gril.h" +#include "grilrequest.h" +#include "grilreply.h" +#include "grilunsol.h" + +#include "rilmodem.h" + +#include "common.h" + +enum cf_action { + CF_ACTION_DISABLE, + CF_ACTION_ENABLE, + CF_ACTION_INTERROGATE, + CF_ACTION_REGISTRATION, + CF_ACTION_ERASURE, +}; + +struct forw_data { + GRil *ril; + enum cf_action last_action; + int last_cls; +}; + +static const char *cf_action_to_string(enum cf_action action) +{ + switch (action) { + case CF_ACTION_DISABLE: + return "DISABLE"; + case CF_ACTION_ENABLE: + return "ENABLE"; + case CF_ACTION_INTERROGATE: + return "INTERROGATE"; + case CF_ACTION_REGISTRATION: + return "REGISTRATION"; + case CF_ACTION_ERASURE: + return "ERASURE"; + } + + return NULL; +} + +static void ril_query_call_fwd_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct forw_data *fd = ofono_call_forwarding_get_data(cbd->user); + ofono_call_forwarding_query_cb_t cb = cbd->cb; + struct ofono_call_forwarding_condition *list; + unsigned int list_size; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: rild error: %s", __func__, + ril_error_to_string(message->error)); + goto error; + } + + list = g_ril_reply_parse_query_call_fwd(fd->ril, message, &list_size); + /* + * From atmodem: + * + * Specification is really unclear about this + * generate status=0 for all classes just in case + */ + if (list_size == 0) { + list = g_new0(struct ofono_call_forwarding_condition, 1); + list_size = 1; + + list->status = 0; + list->cls = fd->last_cls; + } else if (list == NULL) { + goto error; + } + + CALLBACK_WITH_SUCCESS(cb, (int) list_size, list, cbd->data); + g_free(list); + return; + +error: + CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); +} + +static void ril_set_forward_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_forwarding_set_cb_t cb = cbd->cb; + struct forw_data *fd = ofono_call_forwarding_get_data(cbd->user); + + if (message->error == RIL_E_SUCCESS) { + g_ril_print_response_no_args(fd->ril, message); + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + ofono_error("%s: CF %s failed; rild error: %s", __func__, + cf_action_to_string(fd->last_action), + ril_error_to_string(message->error)); + CALLBACK_WITH_FAILURE(cb, cbd->data); + } +} + +static int ril_send_forward_cmd(int type, int cls, + const struct ofono_phone_number *number, + int time, + struct cb_data *cbd, + enum cf_action action) +{ + struct ofono_call_forwarding *cf = cbd->user; + struct forw_data *fd = ofono_call_forwarding_get_data(cf); + struct parcel rilp; + struct req_call_fwd fwd_req; + int ret = 0, request; + GRilResponseFunc response_func; + + if (action == CF_ACTION_INTERROGATE) { + request = RIL_REQUEST_QUERY_CALL_FORWARD_STATUS; + response_func = ril_query_call_fwd_cb; + } else { + request = RIL_REQUEST_SET_CALL_FORWARD; + response_func = ril_set_forward_cb; + } + + DBG("%s - %s", ril_request_id_to_string(request), + cf_action_to_string(action)); + + /* + * 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 with it e.g. ./send-ussd '*21*#' + * returns cls:53 i.e. 1+4+16+32 as service class. + */ + if (cls == BEARER_CLASS_DEFAULT) + cls = SERVICE_CLASS_NONE; + + fd->last_action = action; + fd->last_cls = cls; + + fwd_req.action = (int) action; + fwd_req.type = type; + fwd_req.cls = cls; + fwd_req.number = number; + + /* + * time has no real meaing for action commands other + * then registration, so if not needed, set arbitrary + * 60s time so rild doesn't return an error. + */ + if (time == -1) + fwd_req.time = 60; + else + fwd_req.time = time; + + g_ril_request_call_fwd(fd->ril, &fwd_req, &rilp); + + ret = g_ril_send(fd->ril, request, &rilp, response_func, cbd, g_free); + if (ret == 0) + ofono_error("%s: CF action %s failed", __func__, + cf_action_to_string(action)); + return ret; +} + +static void ril_activate(struct ofono_call_forwarding *cf, + int type, int cls, + ofono_call_forwarding_set_cb_t cb, void *data) +{ + struct cb_data *cbd = cb_data_new(cb, data, cf); + + if (ril_send_forward_cmd(type, cls, NULL, -1, cbd, + CF_ACTION_ENABLE) == 0) { + CALLBACK_WITH_FAILURE(cb, cbd->data); + g_free(cbd); + } +} + +static void ril_erasure(struct ofono_call_forwarding *cf, + int type, int cls, + ofono_call_forwarding_set_cb_t cb, void *data) +{ + struct cb_data *cbd = cb_data_new(cb, data, cf); + + if (ril_send_forward_cmd(type, cls, NULL, -1, cbd, + CF_ACTION_ERASURE) == 0) { + CALLBACK_WITH_FAILURE(cb, cbd->data); + g_free(cbd); + } +} + +static void ril_deactivate(struct ofono_call_forwarding *cf, + int type, int cls, + ofono_call_forwarding_set_cb_t cb, void *data) +{ + struct cb_data *cbd = cb_data_new(cb, data, cf); + + if (ril_send_forward_cmd(type, cls, NULL, -1, cbd, + CF_ACTION_DISABLE) == 0) { + CALLBACK_WITH_FAILURE(cb, cbd->data); + g_free(cbd); + } +} + +static void ril_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 cb_data *cbd = cb_data_new(cb, data, cf); + + if (ril_send_forward_cmd(type, cls, number, time, cbd, + CF_ACTION_REGISTRATION) == 0) { + CALLBACK_WITH_FAILURE(cb, cbd->data); + g_free(cbd); + } +} + +static void ril_query(struct ofono_call_forwarding *cf, int type, int cls, + ofono_call_forwarding_query_cb_t cb, + void *data) +{ + struct cb_data *cbd = cb_data_new(cb, data, cf); + + if (ril_send_forward_cmd(type, cls, NULL, -1, cbd, + CF_ACTION_INTERROGATE) == 0) { + CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); + g_free(cbd); + } +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_call_forwarding *cf = user_data; + + ofono_call_forwarding_register(cf); + return FALSE; +} + +static int ril_call_forwarding_probe(struct ofono_call_forwarding *cf, + unsigned int vendor, void *user) +{ + GRil *ril = user; + struct forw_data *fd; + + fd = g_try_new0(struct forw_data, 1); + if (fd == NULL) + return -ENOMEM; + + fd->ril = g_ril_clone(ril); + ofono_call_forwarding_set_data(cf, fd); + + /* + * ofono_call_forwarding_register() needs to be called after + * the driver has been set in ofono_call_forwarding_create(), + * which calls this function. Most other drivers make + * some kind of capabilities query to the modem, and then + * call register in the callback; we use an idle event instead. + */ + g_idle_add(ril_delayed_register, cf); + + return 0; +} + +static void ril_call_forwarding_remove(struct ofono_call_forwarding *cf) +{ + struct forw_data *data = ofono_call_forwarding_get_data(cf); + ofono_call_forwarding_set_data(cf, NULL); + + g_ril_unref(data->ril); + g_free(data); +} + +static struct ofono_call_forwarding_driver driver = { + .name = RILMODEM, + .probe = ril_call_forwarding_probe, + .remove = ril_call_forwarding_remove, + .erasure = ril_erasure, + .deactivation = ril_deactivate, + .query = ril_query, + .registration = ril_registration, + .activation = ril_activate +}; + +void ril_call_forwarding_init(void) +{ + ofono_call_forwarding_driver_register(&driver); +} + +void ril_call_forwarding_exit(void) +{ + ofono_call_forwarding_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/call-settings.c b/drivers/rilmodem/call-settings.c new file mode 100644 index 00000000..5603fbd6 --- /dev/null +++ b/drivers/rilmodem/call-settings.c @@ -0,0 +1,286 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2013 Jolla Ltd + * Copyright (C) 2013 Canonical Ltd + * Contact: Jussi Kangas + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "gril.h" +#include "grilutil.h" +#include "grilrequest.h" +#include "grilreply.h" + +#include "rilmodem.h" +#include "ril_constants.h" +#include "common.h" + +struct settings_data { + GRil *ril; +}; + +static void ril_set_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_call_settings *cs = cbd->user; + struct settings_data *sd = ofono_call_settings_get_data(cs); + ofono_call_settings_set_cb_t cb = cbd->cb; + + if (message->error == RIL_E_SUCCESS) { + g_ril_print_response_no_args(sd->ril, message); + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + CALLBACK_WITH_FAILURE(cb, cbd->data); + } +} + +static void ril_cw_set(struct ofono_call_settings *cs, int mode, int cls, + ofono_call_settings_set_cb_t cb, void *data) +{ + struct settings_data *sd = ofono_call_settings_get_data(cs); + struct cb_data *cbd = cb_data_new(cb, data, cs); + int ret; + struct parcel rilp; + + g_ril_request_set_call_waiting(sd->ril, mode, cls, &rilp); + + ret = g_ril_send(sd->ril, RIL_REQUEST_SET_CALL_WAITING, &rilp, + ril_set_cb, cbd, g_free); + + /* In case of error free cbd and return the cb with failure */ + if (ret <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_cw_query_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_call_settings *cs = cbd->user; + struct settings_data *sd = ofono_call_settings_get_data(cs); + ofono_call_settings_status_cb_t cb = cbd->cb; + + if (message->error == RIL_E_SUCCESS) { + int res; + + res = g_ril_reply_parse_query_call_waiting(sd->ril, message); + + CALLBACK_WITH_SUCCESS(cb, res, cbd->data); + } else { + CALLBACK_WITH_FAILURE(cb, -1, cbd->data); + } +} + +static void ril_cw_query(struct ofono_call_settings *cs, int cls, + ofono_call_settings_status_cb_t cb, void *data) +{ + struct settings_data *sd = ofono_call_settings_get_data(cs); + struct cb_data *cbd = cb_data_new(cb, data, cs); + int ret; + struct parcel rilp; + + g_ril_request_query_call_waiting(sd->ril, cls, &rilp); + + ret = g_ril_send(sd->ril, RIL_REQUEST_QUERY_CALL_WAITING, &rilp, + ril_cw_query_cb, cbd, g_free); + + /* In case of error free cbd and return the cb with failure */ + if (ret <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, data); + } +} + +static void ril_clip_query_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_call_settings *cs = cbd->user; + struct settings_data *sd = ofono_call_settings_get_data(cs); + ofono_call_settings_status_cb_t cb = cbd->cb; + + if (message->error == RIL_E_SUCCESS) { + int res; + + res = g_ril_reply_parse_query_clip(sd->ril, message); + + CALLBACK_WITH_SUCCESS(cb, res, cbd->data); + } else { + CALLBACK_WITH_FAILURE(cb, -1, cbd->data); + } +} + +static void ril_clip_query(struct ofono_call_settings *cs, + ofono_call_settings_status_cb_t cb, void *data) +{ + struct settings_data *sd = ofono_call_settings_get_data(cs); + struct cb_data *cbd = cb_data_new(cb, data, cs); + int ret; + + ret = g_ril_send(sd->ril, RIL_REQUEST_QUERY_CLIP, NULL, + ril_clip_query_cb, cbd, g_free); + + /* In case of error free cbd and return the cb with failure */ + if (ret <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, data); + } +} + +static void ril_clir_query_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_call_settings *cs = cbd->user; + struct settings_data *sd = ofono_call_settings_get_data(cs); + ofono_call_settings_clir_cb_t cb = cbd->cb; + struct reply_clir *rclir; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: Reply failure: %s", __func__, + ril_error_to_string(message->error)); + goto error; + } + + rclir = g_ril_reply_parse_get_clir(sd->ril, message); + if (rclir == NULL) { + ofono_error("%s: parse error", __func__); + goto error; + } + + CALLBACK_WITH_SUCCESS(cb, rclir->status, rclir->provisioned, cbd->data); + + g_ril_reply_free_get_clir(rclir); + + return; + +error: + CALLBACK_WITH_FAILURE(cb, -1, -1, cbd->data); +} + +static void ril_clir_query(struct ofono_call_settings *cs, + ofono_call_settings_clir_cb_t cb, void *data) +{ + struct settings_data *sd = ofono_call_settings_get_data(cs); + struct cb_data *cbd = cb_data_new(cb, data, cs); + int ret; + + ret = g_ril_send(sd->ril, RIL_REQUEST_GET_CLIR, NULL, + ril_clir_query_cb, cbd, g_free); + + if (ret <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, -1, data); + } +} + + +static void ril_clir_set(struct ofono_call_settings *cs, int mode, + ofono_call_settings_set_cb_t cb, void *data) +{ + struct settings_data *sd = ofono_call_settings_get_data(cs); + struct cb_data *cbd = cb_data_new(cb, data, cs); + struct parcel rilp; + int ret; + + g_ril_request_set_clir(sd->ril, mode, &rilp); + + ret = g_ril_send(sd->ril, RIL_REQUEST_SET_CLIR, &rilp, + ril_set_cb, cbd, g_free); + + if (ret <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_call_settings *cs = user_data; + + ofono_call_settings_register(cs); + + return FALSE; +} + +static int ril_call_settings_probe(struct ofono_call_settings *cs, + unsigned int vendor, void *user) +{ + GRil *ril = user; + struct settings_data *sd = g_new0(struct settings_data, 1); + + sd->ril = g_ril_clone(ril); + + ofono_call_settings_set_data(cs, sd); + + g_idle_add(ril_delayed_register, cs); + + return 0; +} + +static void ril_call_settings_remove(struct ofono_call_settings *cs) +{ + struct settings_data *sd = ofono_call_settings_get_data(cs); + ofono_call_settings_set_data(cs, NULL); + + g_ril_unref(sd->ril); + g_free(sd); +} + +static struct ofono_call_settings_driver driver = { + .name = RILMODEM, + .probe = ril_call_settings_probe, + .remove = ril_call_settings_remove, + .clip_query = ril_clip_query, + .cw_query = ril_cw_query, + .cw_set = ril_cw_set, + .clir_query = ril_clir_query, + .clir_set = ril_clir_set + + /* + * Not supported in RIL API + * .colp_query = ril_colp_query, + * .colr_query = ril_colr_query + */ +}; + +void ril_call_settings_init(void) +{ + ofono_call_settings_driver_register(&driver); +} + +void ril_call_settings_exit(void) +{ + ofono_call_settings_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/call-volume.c b/drivers/rilmodem/call-volume.c new file mode 100644 index 00000000..4e88772e --- /dev/null +++ b/drivers/rilmodem/call-volume.c @@ -0,0 +1,182 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012-2013 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include + +#include +#include +#include + +#include "gril.h" +#include "grilutil.h" + +#include "common.h" + +#include "rilmodem.h" +#include "parcel.h" +#include "grilrequest.h" +#include "grilreply.h" + +struct cv_data { + GRil *ril; + unsigned int vendor; +}; + +static void volume_mute_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_call_volume_cb_t cb = cbd->cb; + struct cv_data *cvd = cbd->user; + struct ofono_error error; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + + g_ril_print_response_no_args(cvd->ril, message); + + } else { + ofono_error("Could not set the ril mute state"); + decode_ril_error(&error, "FAIL"); + } + + cb(&error, cbd->data); +} + +static void ril_call_volume_mute(struct ofono_call_volume *cv, int muted, + ofono_call_volume_cb_t cb, void *data) +{ + struct cv_data *cvd = ofono_call_volume_get_data(cv); + struct cb_data *cbd = cb_data_new(cb, data, cvd); + struct parcel rilp; + + DBG("Initial ril muted state: %d", muted); + + g_ril_request_set_mute(cvd->ril, muted, &rilp); + + if (g_ril_send(cvd->ril, RIL_REQUEST_SET_MUTE, &rilp, + volume_mute_cb, cbd, g_free) == 0) { + ofono_error("Send RIL_REQUEST_SET_MUTE failed."); + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void probe_mute_cb(struct ril_msg *message, gpointer user_data) +{ + struct ofono_call_volume *cv = user_data; + struct cv_data *cvd = ofono_call_volume_get_data(cv); + int muted; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("Could not retrieve the ril mute state"); + return; + } + + muted = g_ril_reply_parse_get_mute(cvd->ril, message); + + ofono_call_volume_set_muted(cv, muted); +} + +static void call_probe_mute(gpointer user_data) +{ + struct ofono_call_volume *cv = user_data; + struct cv_data *cvd = ofono_call_volume_get_data(cv); + + g_ril_send(cvd->ril, RIL_REQUEST_GET_MUTE, NULL, + probe_mute_cb, cv, NULL); +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_call_volume *cv = user_data; + DBG(""); + ofono_call_volume_register(cv); + + /* Probe the mute state */ + call_probe_mute(user_data); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +static int ril_call_volume_probe(struct ofono_call_volume *cv, + unsigned int vendor, void *data) +{ + GRil *ril = data; + struct cv_data *cvd; + + cvd = g_new0(struct cv_data, 1); + if (cvd == NULL) + return -ENOMEM; + + cvd->ril = g_ril_clone(ril); + cvd->vendor = vendor; + + ofono_call_volume_set_data(cv, cvd); + + /* + * ofono_call_volume_register() needs to be called after + * the driver has been set in ofono_call_volume_create(), + * which calls this function. Most other drivers make + * some kind of capabilities query to the modem, and then + * call register in the callback; we use an idle event instead. + */ + g_idle_add(ril_delayed_register, cv); + + return 0; +} + +static void ril_call_volume_remove(struct ofono_call_volume *cv) +{ + struct cv_data *cvd = ofono_call_volume_get_data(cv); + + ofono_call_volume_set_data(cv, NULL); + + g_ril_unref(cvd->ril); + g_free(cvd); +} + +static struct ofono_call_volume_driver driver = { + .name = RILMODEM, + .probe = ril_call_volume_probe, + .remove = ril_call_volume_remove, + .mute = ril_call_volume_mute, +}; + +void ril_call_volume_init(void) +{ + ofono_call_volume_driver_register(&driver); +} + +void ril_call_volume_exit(void) +{ + ofono_call_volume_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/devinfo.c b/drivers/rilmodem/devinfo.c new file mode 100644 index 00000000..2811837b --- /dev/null +++ b/drivers/rilmodem/devinfo.c @@ -0,0 +1,218 @@ +/* + * + * oFono - Open Source Telephony - RIL Modem Support + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012-2013 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "gril.h" + +#include "rilmodem.h" +#include "grilreply.h" + +/* + * TODO: The functions in this file are stubbed out, and + * will need to be re-worked to talk to the /gril layer + * in order to get real values from RILD. + */ + +static void ril_query_manufacturer(struct ofono_devinfo *info, + ofono_devinfo_query_cb_t cb, + void *data) +{ + const char *attr = "Fake Manufacturer"; + struct cb_data *cbd = cb_data_new(cb, data, NULL); + struct ofono_error error; + decode_ril_error(&error, "OK"); + + cb(&error, attr, cbd->data); + + /* Note: this will need to change if cbd passed to gril layer */ + g_free(cbd); +} + +static void ril_query_model(struct ofono_devinfo *info, + ofono_devinfo_query_cb_t cb, + void *data) +{ + const char *attr = "Fake Modem Model"; + struct cb_data *cbd = cb_data_new(cb, data, NULL); + struct ofono_error error; + decode_ril_error(&error, "OK"); + + cb(&error, attr, cbd->data); + + /* Note: this will need to change if cbd passed to gril layer */ + g_free(cbd); +} + +static void query_revision_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_devinfo_query_cb_t cb = cbd->cb; + GRil *ril = cbd->user; + struct ofono_error error; + char *revision; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + } else { + decode_ril_error(&error, "FAIL"); + cb(&error, NULL, cbd->data); + return; + } + + revision = g_ril_reply_parse_baseband_version(ril, message); + + cb(&error, revision, cbd->data); + + g_free(revision); +} + +static void ril_query_revision(struct ofono_devinfo *info, + ofono_devinfo_query_cb_t cb, + void *data) +{ + GRil *ril = ofono_devinfo_get_data(info); + struct cb_data *cbd = cb_data_new(cb, data, ril); + + if (g_ril_send(ril, RIL_REQUEST_BASEBAND_VERSION, NULL, + query_revision_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, data); + } +} + +static void query_serial_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_devinfo_query_cb_t cb = cbd->cb; + GRil *ril = cbd->user; + struct ofono_error error; + gchar *imei; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + } else { + decode_ril_error(&error, "FAIL"); + cb(&error, NULL, cbd->data); + return; + } + + imei = g_ril_reply_parse_baseband_version(ril, message); + + cb(&error, imei, cbd->data); + + g_free(imei); +} + +static void ril_query_serial(struct ofono_devinfo *info, + ofono_devinfo_query_cb_t cb, + void *data) +{ + GRil *ril = ofono_devinfo_get_data(info); + struct cb_data *cbd = cb_data_new(cb, data, ril); + + /* + * TODO: make it support both RIL_REQUEST_GET_IMEI (deprecated) and + * RIL_REQUEST_DEVICE_IDENTITY depending on the rild version used + */ + + if (g_ril_send(ril, RIL_REQUEST_GET_IMEI, NULL, + query_serial_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, data); + } +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_devinfo *info = user_data; + DBG(""); + ofono_devinfo_register(info); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +static int ril_devinfo_probe(struct ofono_devinfo *info, unsigned int vendor, + void *data) +{ + GRil *ril = NULL; + + if (data != NULL) + ril = g_ril_clone(data); + + ofono_devinfo_set_data(info, ril); + + /* + * ofono_devinfo_register() needs to be called after + * the driver has been set in ofono_devinfo_create(), + * which calls this function. Most other drivers make + * some kind of capabilities query to the modem, and then + * call register in the callback; we use an idle event instead. + */ + g_idle_add(ril_delayed_register, info); + + return 0; +} + +static void ril_devinfo_remove(struct ofono_devinfo *info) +{ + GRil *ril = ofono_devinfo_get_data(info); + + ofono_devinfo_set_data(info, NULL); + + g_ril_unref(ril); +} + +static struct ofono_devinfo_driver driver = { + .name = RILMODEM, + .probe = ril_devinfo_probe, + .remove = ril_devinfo_remove, + .query_manufacturer = ril_query_manufacturer, + .query_model = ril_query_model, + .query_revision = ril_query_revision, + .query_serial = ril_query_serial +}; + +void ril_devinfo_init(void) +{ + ofono_devinfo_driver_register(&driver); +} + +void ril_devinfo_exit(void) +{ + ofono_devinfo_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/gprs-context.c b/drivers/rilmodem/gprs-context.c new file mode 100644 index 00000000..20215335 --- /dev/null +++ b/drivers/rilmodem/gprs-context.c @@ -0,0 +1,585 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2013 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "ofono.h" + +#include "grilreply.h" +#include "grilrequest.h" +#include "grilunsol.h" + +#include "gprs.h" +#include "rilmodem.h" + +#define NUM_DEACTIVATION_RETRIES 4 +#define TIME_BETWEEN_DEACT_RETRIES_S 2 + +enum state { + STATE_IDLE, + STATE_ENABLING, + STATE_DISABLING, + STATE_ACTIVE, +}; + +struct gprs_context_data { + GRil *ril; + struct ofono_modem *modem; + unsigned vendor; + gint active_ctx_cid; + gint active_rild_cid; + enum state state; + guint call_list_id; + char *apn; + enum ofono_gprs_context_type type; + int deact_retries; + guint retry_ev_id; + struct cb_data *retry_cbd; + guint reset_ev_id; +}; + +static void ril_gprs_context_deactivate_primary(struct ofono_gprs_context *gc, + unsigned int id, + ofono_gprs_context_cb_t cb, + void *data); +static void ril_deactivate_data_call_cb(struct ril_msg *message, + gpointer user_data); + +static void set_context_disconnected(struct gprs_context_data *gcd) +{ + DBG(""); + + gcd->active_ctx_cid = -1; + gcd->active_rild_cid = -1; + gcd->state = STATE_IDLE; + g_free(gcd->apn); + gcd->apn = NULL; +} + +static void disconnect_context(struct ofono_gprs_context *gc) +{ + ril_gprs_context_deactivate_primary(gc, 0, NULL, NULL); +} + +static void ril_gprs_context_call_list_changed(struct ril_msg *message, + gpointer user_data) +{ + struct ofono_gprs_context *gc = user_data; + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + struct ril_data_call *call = NULL; + struct ril_data_call_list *call_list; + gboolean active_cid_found = FALSE; + gboolean disconnect = FALSE; + GSList *iterator = NULL; + + call_list = g_ril_unsol_parse_data_call_list(gcd->ril, message); + if (call_list == NULL) + return; + + DBG("*gc: %p num calls: %d", gc, g_slist_length(call_list->calls)); + + for (iterator = call_list->calls; iterator; iterator = iterator->next) { + call = (struct ril_data_call *) iterator->data; + + if (call->cid == gcd->active_rild_cid) { + active_cid_found = TRUE; + DBG("found call - cid: %d", call->cid); + + if (call->active == 0) { + DBG("call !active; notify disconnect: %d", + call->cid); + disconnect = TRUE; + } + + break; + } + } + + if ((disconnect == TRUE || active_cid_found == FALSE) + && gcd->state != STATE_IDLE) { + ofono_info("Clearing active context; disconnect: %d" + " active_cid_found: %d active_ctx_cid: %d", + disconnect, active_cid_found, gcd->active_ctx_cid); + + ofono_gprs_context_deactivated(gc, gcd->active_ctx_cid); + set_context_disconnected(gcd); + } + + g_ril_unsol_free_data_call_list(call_list); +} + +static void ril_setup_data_call_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_gprs_context_cb_t cb = cbd->cb; + struct ofono_gprs_context *gc = cbd->user; + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + struct ril_data_call *call = NULL; + struct ril_data_call_list *call_list = NULL; + + DBG("*gc: %p", gc); + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: setup data call failed for apn: %s - %s", + __func__, gcd->apn, + ril_error_to_string(message->error)); + + set_context_disconnected(gcd); + goto error; + } + + call_list = g_ril_unsol_parse_data_call_list(gcd->ril, message); + if (call_list == NULL) { + /* parsing failed, need to actually disconnect */ + disconnect_context(gc); + goto error; + } + + if (g_slist_length(call_list->calls) != 1) { + ofono_error("%s: setup_data_call reply for apn: %s," + " includes %d calls", + __func__, gcd->apn, + g_slist_length(call_list->calls)); + + disconnect_context(gc); + goto error; + } + + call = (struct ril_data_call *) call_list->calls->data; + + /* Check for valid DNS settings, except for MMS contexts */ + if (gcd->type != OFONO_GPRS_CONTEXT_TYPE_MMS + && (call->dns_addrs == NULL + || g_strv_length(call->dns_addrs) == 0)) { + ofono_error("%s: no DNS in context of type %d", + __func__, gcd->type); + disconnect_context(gc); + goto error; + } + + if (call->status != PDP_FAIL_NONE) { + ofono_error("%s: reply->status for apn: %s, is non-zero: %s", + __func__, gcd->apn, + ril_pdp_fail_to_string(call->status)); + + set_context_disconnected(gcd); + goto error; + } + + gcd->active_rild_cid = call->cid; + gcd->state = STATE_ACTIVE; + + ofono_gprs_context_set_interface(gc, call->ifname); + ofono_gprs_context_set_ipv4_netmask(gc, + ril_util_get_netmask(call->ip_addr)); + + ofono_gprs_context_set_ipv4_address(gc, call->ip_addr, TRUE); + ofono_gprs_context_set_ipv4_gateway(gc, call->gateways[0]); + + ofono_gprs_context_set_ipv4_dns_servers(gc, + (const char **) call->dns_addrs); + + g_ril_unsol_free_data_call_list(call_list); + + /* activate listener for data call changed events.... */ + gcd->call_list_id = + g_ril_register(gcd->ril, + RIL_UNSOL_DATA_CALL_LIST_CHANGED, + ril_gprs_context_call_list_changed, gc); + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + return; + +error: + g_ril_unsol_free_data_call_list(call_list); + + CALLBACK_WITH_FAILURE(cb, 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 gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + struct ofono_modem *modem = ofono_gprs_context_get_modem(gc); + struct ofono_atom *gprs_atom = + __ofono_modem_find_atom(modem, OFONO_ATOM_TYPE_GPRS); + struct ofono_gprs *gprs = NULL; + struct ril_gprs_data *gd = NULL; + struct cb_data *cbd = cb_data_new(cb, data, gc); + struct req_setup_data_call request; + struct parcel rilp; + struct ofono_error error; + int ret = 0; + + g_assert(gprs_atom != NULL); + gprs = __ofono_atom_get_data(gprs_atom); + g_assert(gprs != NULL); + gd = ofono_gprs_get_data(gprs); + g_assert(gd != NULL); + + /* + * 0: CDMA 1: GSM/UMTS, 2... + * anything 2+ is a RadioTechnology value +2 + */ + DBG("*gc: %p activating cid: %d; curr_tech: %d", gc, ctx->cid, + gd->tech); + + if (gd->tech == RADIO_TECH_UNKNOWN) { + ofono_error("%s: radio tech for apn: %s UNKNOWN!", __func__, + gcd->apn); + request.tech = 1; + } else { + request.tech = gd->tech + 2; + } + + /* + * TODO: add comments about tethering, other non-public + * profiles... + */ + if (g_ril_vendor(gcd->ril) == OFONO_RIL_VENDOR_MTK && + gcd->type == OFONO_GPRS_CONTEXT_TYPE_MMS) + request.data_profile = RIL_DATA_PROFILE_MTK_MMS; + else + request.data_profile = RIL_DATA_PROFILE_DEFAULT; + + request.apn = g_strdup(ctx->apn); + request.username = g_strdup(ctx->username); + request.password = g_strdup(ctx->password); + + /* + * 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. + */ + if (request.username != NULL && request.username[0] != '\0') + request.auth_type = RIL_AUTH_BOTH; + else + request.auth_type = RIL_AUTH_NONE; + + request.protocol = ctx->proto; + request.req_cid = ctx->cid; + + if (g_ril_request_setup_data_call(gcd->ril, + &request, + &rilp, + &error) == FALSE) { + ofono_error("%s: couldn't build SETUP_DATA_CALL" + " request for apn: %s.", + __func__, request.apn); + goto error; + } + + gcd->active_ctx_cid = ctx->cid; + gcd->state = STATE_ENABLING; + gcd->apn = g_strdup(ctx->apn); + + ret = g_ril_send(gcd->ril, RIL_REQUEST_SETUP_DATA_CALL, &rilp, + ril_setup_data_call_cb, cbd, g_free); + +error: + g_free(request.apn); + g_free(request.username); + g_free(request.password); + + if (ret == 0) { + ofono_error("%s: send SETUP_DATA_CALL failed for apn: %s.", + __func__, gcd->apn); + + set_context_disconnected(gcd); + + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static gboolean reset_modem(gpointer data) +{ + /* TODO call mtk_reset_modem when driver is upstreamed */ + return FALSE; +} + +static gboolean retry_deactivate(gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_gprs_context_cb_t cb = cbd->cb; + struct ofono_gprs_context *gc = cbd->user; + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + struct req_deactivate_data_call request; + struct parcel rilp; + struct ofono_error error; + + gcd->retry_ev_id = 0; + + /* We might have received a call list update while waiting */ + if (gcd->state == STATE_IDLE) { + if (cb) + CALLBACK_WITH_SUCCESS(cb, cbd->data); + + g_free(cbd); + + return FALSE; + } + + request.cid = gcd->active_rild_cid; + request.reason = RIL_DEACTIVATE_DATA_CALL_NO_REASON; + + g_ril_request_deactivate_data_call(gcd->ril, &request, &rilp, &error); + + if (g_ril_send(gcd->ril, RIL_REQUEST_DEACTIVATE_DATA_CALL, &rilp, + ril_deactivate_data_call_cb, cbd, g_free) == 0) { + + ofono_error("%s: send DEACTIVATE_DATA_CALL failed for apn: %s", + __func__, gcd->apn); + if (cb) + CALLBACK_WITH_FAILURE(cb, cbd->data); + + g_free(cbd); + } + + return FALSE; +} + +static void ril_deactivate_data_call_cb(struct ril_msg *message, + gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_gprs_context_cb_t cb = cbd->cb; + struct ofono_gprs_context *gc = cbd->user; + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + gint active_ctx_cid; + + DBG("*gc: %p", gc); + + if (message->error == RIL_E_SUCCESS) { + + g_ril_print_response_no_args(gcd->ril, message); + + active_ctx_cid = gcd->active_ctx_cid; + set_context_disconnected(gcd); + + /* + * If the deactivate was a result of a data network detach or of + * an error in data call establishment, there won't be call + * back, so _deactivated() needs to be called directly. + */ + if (cb) + CALLBACK_WITH_SUCCESS(cb, cbd->data); + else + ofono_gprs_context_deactivated(gc, active_ctx_cid); + + } else { + ofono_error("%s: reply failure for apn: %s - %s", + __func__, gcd->apn, + ril_error_to_string(message->error)); + + /* + * It has been detected that some modems fail the deactivation + * temporarily. We do retries to handle that case. + */ + if (--(gcd->deact_retries) > 0) { + gcd->retry_cbd = cb_data_new(cb, cbd->data, gc); + gcd->retry_ev_id = + g_timeout_add_seconds( + TIME_BETWEEN_DEACT_RETRIES_S, + retry_deactivate, gcd->retry_cbd); + } else { + ofono_error("%s: retry limit hit", __func__); + + if (cb) + CALLBACK_WITH_FAILURE(cb, cbd->data); + + /* + * Reset modem if MTK. TODO Failures deactivating a + * context have not been reported for other modems, but + * it would be good to have a generic method to force an + * internal reset nonetheless. + */ + if (gcd->vendor == OFONO_RIL_VENDOR_MTK) + gcd->reset_ev_id = g_idle_add(reset_modem, gcd); + } + } +} + +static void ril_gprs_context_deactivate_primary(struct ofono_gprs_context *gc, + unsigned int id, + ofono_gprs_context_cb_t cb, void *data) +{ + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + struct cb_data *cbd = NULL; + struct parcel rilp; + struct req_deactivate_data_call request; + struct ofono_error error; + int ret = 0; + + DBG("*gc: %p cid: %d active_rild_cid: %d", gc, id, + gcd->active_rild_cid); + + if (gcd->state == STATE_IDLE || gcd->state == STATE_DISABLING) { + /* nothing to do */ + + if (cb) { + CALLBACK_WITH_SUCCESS(cb, data); + g_free(cbd); + } + + return; + } + + cbd = cb_data_new(cb, data, gc); + + gcd->state = STATE_DISABLING; + if (g_ril_unregister(gcd->ril, gcd->call_list_id) == FALSE) { + ofono_warn("%s: couldn't remove call_list listener" + " for apn: %s.", + __func__, gcd->apn); + } + + request.cid = gcd->active_rild_cid; + request.reason = RIL_DEACTIVATE_DATA_CALL_NO_REASON; + + if (g_ril_request_deactivate_data_call(gcd->ril, &request, + &rilp, &error) == FALSE) { + + ofono_error("%s: couldn't build DEACTIVATE_DATA_CALL" + " request for apn: %s.", + __func__, gcd->apn); + goto error; + } + + gcd->deact_retries = NUM_DEACTIVATION_RETRIES; + ret = g_ril_send(gcd->ril, RIL_REQUEST_DEACTIVATE_DATA_CALL, &rilp, + ril_deactivate_data_call_cb, cbd, g_free); + +error: + if (ret == 0) { + /* TODO: should we force state to disconnected here? */ + + ofono_error("%s: send DEACTIVATE_DATA_CALL failed for apn: %s", + __func__, gcd->apn); + g_free(cbd); + if (cb) + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_gprs_context_detach_shutdown(struct ofono_gprs_context *gc, + unsigned int id) +{ + DBG("*gc: %p cid: %d", gc, id); + + ril_gprs_context_deactivate_primary(gc, 0, NULL, NULL); +} + +static int ril_gprs_context_probe(struct ofono_gprs_context *gc, + unsigned int vendor, void *data) +{ + struct ril_gprs_context_data *ril_data = data; + struct gprs_context_data *gcd; + + DBG("*gc: %p", gc); + + gcd = g_try_new0(struct gprs_context_data, 1); + if (gcd == NULL) + return -ENOMEM; + + gcd->ril = g_ril_clone(ril_data->gril); + gcd->modem = ril_data->modem; + gcd->vendor = vendor; + set_context_disconnected(gcd); + gcd->call_list_id = -1; + gcd->type = ril_data->type; + + ofono_gprs_context_set_data(gc, gcd); + + return 0; +} + +static void ril_gprs_context_remove(struct ofono_gprs_context *gc) +{ + struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc); + + DBG("*gc: %p", gc); + + if (gcd->state != STATE_IDLE && gcd->state != STATE_DISABLING) { + struct req_deactivate_data_call request; + struct parcel rilp; + struct ofono_error error; + + request.cid = gcd->active_rild_cid; + request.reason = RIL_DEACTIVATE_DATA_CALL_NO_REASON; + g_ril_request_deactivate_data_call(gcd->ril, &request, + &rilp, &error); + + g_ril_send(gcd->ril, RIL_REQUEST_DEACTIVATE_DATA_CALL, + &rilp, NULL, NULL, NULL); + } + + if (gcd->retry_ev_id > 0) { + g_source_remove(gcd->retry_ev_id); + g_free(gcd->retry_cbd); + } + + if (gcd->reset_ev_id > 0) + g_source_remove(gcd->reset_ev_id); + + ofono_gprs_context_set_data(gc, NULL); + + g_ril_unref(gcd->ril); + g_free(gcd); +} + +static struct ofono_gprs_context_driver driver = { + .name = RILMODEM, + .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, +}; + +void ril_gprs_context_init(void) +{ + ofono_gprs_context_driver_register(&driver); +} + +void ril_gprs_context_exit(void) +{ + ofono_gprs_context_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/gprs.c b/drivers/rilmodem/gprs.c new file mode 100644 index 00000000..75dcfcc0 --- /dev/null +++ b/drivers/rilmodem/gprs.c @@ -0,0 +1,487 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2010 ST-Ericsson AB. + * Copyright (C) 2013 Canonical Ltd. + * Copyright (C) 2013 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "gril.h" +#include "grilutil.h" +#include "common.h" +#include "rilmodem.h" + +#include "grilreply.h" +#include "grilrequest.h" +#include "grilunsol.h" +#include "gprs.h" + +/* Time between get data status retries */ +#define GET_STATUS_TIMER_MS 5000 + +/* + * 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. + */ + +static int ril_tech_to_bearer_tech(int ril_tech) +{ + /* + * This code handles the mapping between the RIL_RadioTechnology + * and packet bearer values ( see values - 27.007 + * Section 7.29 ). + */ + + switch (ril_tech) { + case RADIO_TECH_GSM: + case RADIO_TECH_UNKNOWN: + return PACKET_BEARER_NONE; + case RADIO_TECH_GPRS: + return PACKET_BEARER_GPRS; + case RADIO_TECH_EDGE: + return PACKET_BEARER_EGPRS; + case RADIO_TECH_UMTS: + return PACKET_BEARER_UMTS; + case RADIO_TECH_HSDPA: + return PACKET_BEARER_HSDPA; + case RADIO_TECH_HSUPA: + return PACKET_BEARER_HSUPA; + case RADIO_TECH_HSPAP: + case RADIO_TECH_HSPA: + /* + * HSPAP is HSPA+; which ofono doesn't define; + * so, if differentiating HSPA and HSPA+ is + * important, then ofono needs to be patched, + * and we probably also need to introduce a + * new indicator icon. + */ + return PACKET_BEARER_HSUPA_HSDPA; + case RADIO_TECH_LTE: + return PACKET_BEARER_EPS; + default: + return PACKET_BEARER_NONE; + } +} + +static void ril_gprs_state_change(struct ril_msg *message, gpointer user_data) +{ + struct ofono_gprs *gprs = user_data; + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + + g_ril_print_unsol_no_args(gd->ril, message); + + /* + * We just want to track network data status if ofono + * itself is attached, so we avoid unnecessary data state requests. + */ + if (gd->ofono_attached == TRUE) + ril_gprs_registration_status(gprs, NULL, NULL); +} + +gboolean ril_gprs_set_attached_cb(gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_gprs_cb_t cb = cbd->cb; + + DBG(""); + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + g_free(cbd); + + /* Run once per g_idle_add() call */ + return FALSE; +} + +static void ril_gprs_set_attached(struct ofono_gprs *gprs, int attached, + ofono_gprs_cb_t cb, void *data) +{ + struct cb_data *cbd = cb_data_new(cb, data, NULL); + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + + DBG("attached: %d", 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 + * notificaiton is received and any configured roaming conditions + * are met. + */ + gd->ofono_attached = attached; + + /* + * Call from idle loop, so core can set driver_attached before + * the callback is invoked. + */ + g_idle_add(ril_gprs_set_attached_cb, cbd); +} + +static gboolean ril_get_status_retry(gpointer user_data) +{ + struct ofono_gprs *gprs = user_data; + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + + gd->status_retry_cb_id = 0; + + ril_gprs_registration_status(gprs, NULL, NULL); + + return FALSE; +} + +static void ril_data_reg_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_gprs_status_cb_t cb = cbd->cb; + struct ofono_gprs *gprs = cbd->user; + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + struct reply_data_reg_state *reply; + gboolean attached = FALSE; + gboolean notify_status = FALSE; + int old_status; + + old_status = gd->rild_status; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: DATA_REGISTRATION_STATE reply failure: %s", + __func__, + ril_error_to_string(message->error)); + goto error; + } + + reply = g_ril_reply_parse_data_reg_state(gd->ril, message); + if (reply == NULL) + goto error; + + /* + * There are three cases that can result in this callback + * running: + * + * 1) The driver's probe() method was called, and thus an + * internal call to ril_gprs_registration_status() is + * generated. No ofono cb exists. + * + * 2) ril_gprs_state_change() is called due to an unsolicited + * event from RILD. No ofono cb exists. + * + * 3) The ofono code code calls the driver's attached_status() + * function. A valid ofono cb exists. + */ + + if (gd->rild_status != reply->reg_state.status) { + gd->rild_status = reply->reg_state.status; + + if (cb == NULL) + notify_status = TRUE; + } + + /* + * Override the actual status based upon the desired + * attached status set by the core GPRS code ( controlled + * by the ConnnectionManager's 'Powered' property ). + */ + attached = (reply->reg_state.status == + NETWORK_REGISTRATION_STATUS_REGISTERED || + reply->reg_state.status == + NETWORK_REGISTRATION_STATUS_ROAMING); + + if (attached && gd->ofono_attached == FALSE) { + DBG("attached=true; ofono_attached=false; return !REGISTERED"); + reply->reg_state.status = + NETWORK_REGISTRATION_STATUS_NOT_REGISTERED; + + /* + * Further optimization so that if ril_status == + * NOT_REGISTERED, ofono_attached == false, and status == + * ROAMING | REGISTERED, then notify gets cleared... + * + * As is, this results in unecessary status notify calls + * when nothing has changed. + */ + if (notify_status && reply->reg_state.status == old_status) + notify_status = FALSE; + } + + if (old_status == -1) { + ofono_gprs_register(gprs); + + /* Different rild implementations use different events here */ + g_ril_register(gd->ril, + gd->state_changed_unsol, + ril_gprs_state_change, gprs); + + if (reply->max_cids == 0) + gd->max_cids = RIL_MAX_NUM_ACTIVE_DATA_CALLS; + else if (reply->max_cids < RIL_MAX_NUM_ACTIVE_DATA_CALLS) + gd->max_cids = reply->max_cids; + else + gd->max_cids = RIL_MAX_NUM_ACTIVE_DATA_CALLS; + + DBG("Setting max cids to %d", gd->max_cids); + ofono_gprs_set_cid_range(gprs, 1, gd->max_cids); + + /* + * This callback is a result of the inital call + * to probe(), so should return after registration. + */ + g_free(reply); + + return; + } + + /* Just need to notify ofono if it's already attached */ + if (notify_status) { + + /* + * If network disconnect has occurred, call detached_notify() + * instead of status_notify(). + */ + if (!attached && + (old_status == NETWORK_REGISTRATION_STATUS_REGISTERED || + old_status == + NETWORK_REGISTRATION_STATUS_ROAMING)) { + DBG("calling ofono_gprs_detached_notify()"); + ofono_gprs_detached_notify(gprs); + reply->reg_state.tech = RADIO_TECH_UNKNOWN; + } else { + DBG("calling ofono_gprs_status_notify()"); + ofono_gprs_status_notify(gprs, reply->reg_state.status); + } + } + + if (gd->tech != reply->reg_state.tech) { + gd->tech = reply->reg_state.tech; + + ofono_gprs_bearer_notify(gprs, + ril_tech_to_bearer_tech(reply->reg_state.tech)); + } + + if (cb) + CALLBACK_WITH_SUCCESS(cb, reply->reg_state.status, cbd->data); + + g_free(reply); + + return; +error: + + /* + * For some modems DATA_REGISTRATION_STATE will return an error until we + * are registered in the voice network. + */ + if (old_status == -1 && message->error == RIL_E_GENERIC_FAILURE) + gd->status_retry_cb_id = + g_timeout_add(GET_STATUS_TIMER_MS, + ril_get_status_retry, gprs); + + if (cb) + CALLBACK_WITH_FAILURE(cb, -1, cbd->data); +} + +void ril_gprs_registration_status(struct ofono_gprs *gprs, + ofono_gprs_status_cb_t cb, void *data) +{ + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + struct cb_data *cbd = cb_data_new(cb, data, gprs); + + DBG(""); + + if (g_ril_send(gd->ril, RIL_REQUEST_DATA_REGISTRATION_STATE, NULL, + ril_data_reg_cb, cbd, g_free) == 0) { + ofono_error("%s: send " + "RIL_REQUEST_DATA_REGISTRATION_STATE failed", + __func__); + g_free(cbd); + + if (cb != NULL) + CALLBACK_WITH_FAILURE(cb, -1, data); + } +} + +static void drop_data_call_cb(struct ril_msg *message, gpointer user_data) +{ + struct ofono_gprs *gprs = user_data; + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + + if (message->error == RIL_E_SUCCESS) + g_ril_print_response_no_args(gd->ril, message); + else + ofono_error("%s: RIL error %s", __func__, + ril_error_to_string(message->error)); + + if (--(gd->pending_deact_req) == 0) + ril_gprs_registration_status(gprs, NULL, NULL); +} + +static int drop_data_call(struct ofono_gprs *gprs, int cid) +{ + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + struct req_deactivate_data_call request; + struct parcel rilp; + struct ofono_error error; + + request.cid = cid; + request.reason = RIL_DEACTIVATE_DATA_CALL_NO_REASON; + + g_ril_request_deactivate_data_call(gd->ril, &request, &rilp, &error); + + if (g_ril_send(gd->ril, RIL_REQUEST_DEACTIVATE_DATA_CALL, + &rilp, drop_data_call_cb, gprs, NULL) == 0) { + ofono_error("%s: send failed", __func__); + return -1; + } + + return 0; +} + +static void get_active_data_calls_cb(struct ril_msg *message, + gpointer user_data) +{ + struct ofono_gprs *gprs = user_data; + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + struct ril_data_call_list *call_list = NULL; + GSList *iterator; + struct ril_data_call *call; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: RIL error %s", __func__, + ril_error_to_string(message->error)); + goto end; + } + + /* reply can be NULL when there are no existing data calls */ + call_list = g_ril_unsol_parse_data_call_list(gd->ril, message); + if (call_list == NULL) + goto end; + + /* + * We disconnect from previous calls here, which might be needed + * because of a previous ofono abort, as some rild implementations do + * not disconnect the calls even after the ril socket is closed. + */ + for (iterator = call_list->calls; iterator; iterator = iterator->next) { + call = iterator->data; + DBG("Standing data call with cid %d", call->cid); + if (drop_data_call(gprs, call->cid) == 0) + ++(gd->pending_deact_req); + } + + g_ril_unsol_free_data_call_list(call_list); + +end: + if (gd->pending_deact_req == 0) + ril_gprs_registration_status(gprs, NULL, NULL); +} + +static void get_active_data_calls(struct ofono_gprs *gprs) +{ + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + + if (g_ril_send(gd->ril, RIL_REQUEST_DATA_CALL_LIST, NULL, + get_active_data_calls_cb, gprs, NULL) == 0) + ofono_error("%s: send failed", __func__); +} + +void ril_gprs_start(struct ril_gprs_driver_data *driver_data, + struct ofono_gprs *gprs, struct ril_gprs_data *gd) +{ + gd->ril = g_ril_clone(driver_data->gril); + gd->modem = driver_data->modem; + gd->ofono_attached = FALSE; + gd->max_cids = 0; + gd->rild_status = -1; + gd->tech = RADIO_TECH_UNKNOWN; + /* AOSP RILD tracks data network state together with voice */ + gd->state_changed_unsol = + RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED; + + ofono_gprs_set_data(gprs, gd); + + get_active_data_calls(gprs); +} + +int ril_gprs_probe(struct ofono_gprs *gprs, unsigned int vendor, void *data) +{ + struct ril_gprs_driver_data *driver_data = data; + struct ril_gprs_data *gd; + + gd = g_try_new0(struct ril_gprs_data, 1); + if (gd == NULL) + return -ENOMEM; + + ril_gprs_start(driver_data, gprs, gd); + + return 0; +} + +void ril_gprs_remove(struct ofono_gprs *gprs) +{ + struct ril_gprs_data *gd = ofono_gprs_get_data(gprs); + + DBG(""); + + if (gd->status_retry_cb_id != 0) + g_source_remove(gd->status_retry_cb_id); + + ofono_gprs_set_data(gprs, NULL); + + g_ril_unref(gd->ril); + g_free(gd); +} + +static struct ofono_gprs_driver driver = { + .name = RILMODEM, + .probe = ril_gprs_probe, + .remove = ril_gprs_remove, + .set_attached = ril_gprs_set_attached, + .attached_status = ril_gprs_registration_status, +}; + +void ril_gprs_init(void) +{ + ofono_gprs_driver_register(&driver); +} + +void ril_gprs_exit(void) +{ + ofono_gprs_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/gprs.h b/drivers/rilmodem/gprs.h new file mode 100644 index 00000000..78bb14cd --- /dev/null +++ b/drivers/rilmodem/gprs.h @@ -0,0 +1,46 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2014 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "drivers/rilmodem/rilutil.h" + +struct ril_gprs_data { + GRil *ril; + struct ofono_modem *modem; + gboolean ofono_attached; + unsigned int max_cids; + int rild_status; + int tech; + int state_changed_unsol; + int pending_deact_req; + guint status_retry_cb_id; +}; + +int ril_gprs_probe(struct ofono_gprs *gprs, unsigned int vendor, void *data); +void ril_gprs_remove(struct ofono_gprs *gprs); +void ril_gprs_start(struct ril_gprs_driver_data *driver_data, + struct ofono_gprs *gprs, struct ril_gprs_data *gd); +gboolean ril_gprs_set_attached_cb(gpointer user_data); +void ril_gprs_registration_status(struct ofono_gprs *gprs, + ofono_gprs_status_cb_t cb, void *data); +void ril_gprs_set_ia_apn(struct ofono_gprs *gprs, const char *apn, + enum ofono_gprs_proto proto, const char *user, + const char *passwd, const char *mccmnc, + ofono_gprs_cb_t cb, void *data); diff --git a/drivers/rilmodem/network-registration.c b/drivers/rilmodem/network-registration.c new file mode 100644 index 00000000..6808a293 --- /dev/null +++ b/drivers/rilmodem/network-registration.c @@ -0,0 +1,566 @@ +/* + * + * oFono - Open Source Telephony - RIL Modem Support + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2010 ST-Ericsson AB. + * Copyright (C) 2012-2013 Canonical Ltd. + * Copyright (C) 2013 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include + +#include +#include +#include + +#include "common.h" +#include "gril.h" +#include "rilmodem.h" + +#include "grilreply.h" +#include "grilrequest.h" +#include "grilunsol.h" + +struct netreg_data { + GRil *ril; + char mcc[OFONO_MAX_MCC_LENGTH + 1]; + char mnc[OFONO_MAX_MNC_LENGTH + 1]; + int signal_index; /* If strength is reported via CIND */ + int signal_min; /* min strength reported via CIND */ + int signal_max; /* max strength reported via CIND */ + int signal_invalid; /* invalid strength reported via CIND */ + int tech; + struct ofono_network_time time; + guint nitz_timeout; + unsigned int vendor; +}; + +static void ril_registration_status(struct ofono_netreg *netreg, + ofono_netreg_status_cb_t cb, + void *data); + +static int ril_tech_to_access_tech(int ril_tech) +{ + /* + * This code handles the mapping between the RIL_RadioTechnology + * and ofono's access technology values ( see values - 27.007 + * Section 7.3 ). + */ + + switch (ril_tech) { + case RADIO_TECH_UNKNOWN: + return -1; + case RADIO_TECH_GSM: + case RADIO_TECH_GPRS: + return ACCESS_TECHNOLOGY_GSM; + case RADIO_TECH_EDGE: + return ACCESS_TECHNOLOGY_GSM_EGPRS; + case RADIO_TECH_UMTS: + return ACCESS_TECHNOLOGY_UTRAN; + case RADIO_TECH_HSDPA: + return ACCESS_TECHNOLOGY_UTRAN_HSDPA; + case RADIO_TECH_HSUPA: + return ACCESS_TECHNOLOGY_UTRAN_HSUPA; + case RADIO_TECH_HSPAP: + case RADIO_TECH_HSPA: + /* HSPAP is HSPA+; which ofono doesn't define; + * so, if differentiating HSPA and HSPA+ is + * important, then ofono needs to be patched, + * and we probably also need to introduce a + * new indicator icon. + */ + + return ACCESS_TECHNOLOGY_UTRAN_HSDPA_HSUPA; + case RADIO_TECH_LTE: + return ACCESS_TECHNOLOGY_EUTRAN; + default: + return -1; + } +} + +static void extract_mcc_mnc(const char *str, char *mcc, char *mnc) +{ + /* Three digit country code */ + strncpy(mcc, str, OFONO_MAX_MCC_LENGTH); + mcc[OFONO_MAX_MCC_LENGTH] = '\0'; + + /* Usually a 2 but sometimes 3 digit network code */ + strncpy(mnc, str + OFONO_MAX_MCC_LENGTH, OFONO_MAX_MNC_LENGTH); + mnc[OFONO_MAX_MNC_LENGTH] = '\0'; +} + +static void ril_creg_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_netreg_status_cb_t cb = cbd->cb; + struct netreg_data *nd = cbd->user; + struct reply_reg_state *reply; + + DBG(""); + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: failed to pull registration state", + __func__); + goto error; + } + + reply = g_ril_reply_parse_voice_reg_state(nd->ril, message); + if (reply == NULL) + goto error; + + nd->tech = reply->tech; + + CALLBACK_WITH_SUCCESS(cb, + reply->status, + reply->lac, + reply->ci, + ril_tech_to_access_tech(reply->tech), + cbd->data); + + g_free(reply); + return; + +error: + CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, cbd->data); +} + +static void ril_creg_notify(struct ofono_error *error, int status, int lac, + int ci, int tech, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + DBG("Error during status notification"); + return; + } + + ofono_netreg_status_notify(netreg, status, lac, ci, tech); +} + +static void ril_network_state_change(struct ril_msg *message, + gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + + g_ril_print_unsol_no_args(nd->ril, message); + + ril_registration_status(netreg, NULL, NULL); +} + +static void ril_registration_status(struct ofono_netreg *netreg, + ofono_netreg_status_cb_t cb, + void *data) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct cb_data *cbd; + + /* + * If no cb specified, setup internal callback to + * handle unsolicited VOICE_NET_STATE_CHANGE events. + */ + if (cb == NULL) + cbd = cb_data_new(ril_creg_notify, netreg, nd); + else + cbd = cb_data_new(cb, data, nd); + + if (g_ril_send(nd->ril, RIL_REQUEST_VOICE_REGISTRATION_STATE, NULL, + ril_creg_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, -1, -1, -1, data); + } +} + +static void set_oper_name(const struct reply_operator *reply, + struct ofono_network_operator *op) +{ + /* Try to use long by default */ + if (reply->lalpha) + strncpy(op->name, reply->lalpha, + OFONO_MAX_OPERATOR_NAME_LENGTH); + else if (reply->salpha) + strncpy(op->name, reply->salpha, + OFONO_MAX_OPERATOR_NAME_LENGTH); +} + +static void ril_cops_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_netreg_operator_cb_t cb = cbd->cb; + struct netreg_data *nd = cbd->user; + struct reply_operator *reply; + struct ofono_network_operator op; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: failed to retrive the current operator", + __func__); + goto error; + } + + reply = g_ril_reply_parse_operator(nd->ril, message); + if (reply == NULL) + goto error; + + set_oper_name(reply, &op); + + extract_mcc_mnc(reply->numeric, op.mcc, op.mnc); + + /* Set to current */ + op.status = OPERATOR_STATUS_CURRENT; + op.tech = ril_tech_to_access_tech(nd->tech); + + CALLBACK_WITH_SUCCESS(cb, &op, cbd->data); + + g_ril_reply_free_operator(reply); + + return; + +error: + CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); +} + +static void ril_current_operator(struct ofono_netreg *netreg, + ofono_netreg_operator_cb_t cb, void *data) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct cb_data *cbd = cb_data_new(cb, data, nd); + + if (g_ril_send(nd->ril, RIL_REQUEST_OPERATOR, NULL, + ril_cops_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, data); + } +} + +static void ril_cops_list_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_netreg_operator_list_cb_t cb = cbd->cb; + struct netreg_data *nd = cbd->user; + struct reply_avail_ops *reply = NULL; + struct ofono_network_operator *ops; + struct reply_operator *operator; + GSList *l; + unsigned int i = 0; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: failed to retrive the list of operators", + __func__); + goto error; + } + + reply = g_ril_reply_parse_avail_ops(nd->ril, message); + if (reply == NULL) + goto error; + + ops = g_try_new0(struct ofono_network_operator, reply->num_ops); + if (ops == NULL) { + ofono_error("%s: can't allocate ofono_network_operator", + __func__); + + goto error; + } + + for (l = reply->list; l; l = l->next) { + operator = l->data; + + set_oper_name(operator, &ops[i]); + + extract_mcc_mnc(operator->numeric, ops[i].mcc, ops[i].mnc); + + ops[i].tech = ril_tech_to_access_tech(operator->tech); + + /* Set the proper status */ + if (!strcmp(operator->status, "unknown")) + ops[i].status = OPERATOR_STATUS_UNKNOWN; + else if (!strcmp(operator->status, "available")) + ops[i].status = OPERATOR_STATUS_AVAILABLE; + else if (!strcmp(operator->status, "current")) + ops[i].status = OPERATOR_STATUS_CURRENT; + else if (!strcmp(operator->status, "forbidden")) + ops[i].status = OPERATOR_STATUS_FORBIDDEN; + + i++; + } + + CALLBACK_WITH_SUCCESS(cb, reply->num_ops, ops, cbd->data); + g_ril_reply_free_avail_ops(reply); + + return; + +error: + CALLBACK_WITH_FAILURE(cb, 0, NULL, cbd->data); + g_ril_reply_free_avail_ops(reply); +} + +static void ril_list_operators(struct ofono_netreg *netreg, + ofono_netreg_operator_list_cb_t cb, void *data) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct cb_data *cbd = cb_data_new(cb, data, nd); + + if (g_ril_send(nd->ril, RIL_REQUEST_QUERY_AVAILABLE_NETWORKS, NULL, + ril_cops_list_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, 0, NULL, data); + } +} + +static void ril_register_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_netreg_register_cb_t cb = cbd->cb; + struct netreg_data *nd = cbd->user; + struct ofono_error error; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + + g_ril_print_response_no_args(nd->ril, message); + + } else { + decode_ril_error(&error, "FAIL"); + } + + cb(&error, cbd->data); +} + +static void ril_register_auto(struct ofono_netreg *netreg, + ofono_netreg_register_cb_t cb, void *data) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct cb_data *cbd = cb_data_new(cb, data, nd); + + if (g_ril_send(nd->ril, RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, + NULL, ril_register_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_register_manual(struct ofono_netreg *netreg, + const char *mcc, const char *mnc, + ofono_netreg_register_cb_t cb, void *data) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct cb_data *cbd = cb_data_new(cb, data, nd); + char buf[OFONO_MAX_MCC_LENGTH + OFONO_MAX_MNC_LENGTH + 1]; + struct parcel rilp; + + /* RIL expects a char * specifying MCCMNC of network to select */ + snprintf(buf, sizeof(buf), "%s%s", mcc, mnc); + + g_ril_request_set_net_select_manual(nd->ril, buf, &rilp); + + /* In case of error free cbd and return the cb with failure */ + if (g_ril_send(nd->ril, RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL, &rilp, + ril_register_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_strength_notify(struct ril_msg *message, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + int strength = g_ril_unsol_parse_signal_strength(nd->ril, message, + nd->tech); + + ofono_netreg_strength_notify(netreg, strength); +} + +static void ril_strength_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_netreg_strength_cb_t cb = cbd->cb; + struct netreg_data *nd = cbd->user; + struct ofono_error error; + int strength; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + } else { + ofono_error("Failed to retrive the signal strength"); + goto error; + } + + /* The g_ril_unsol* function handles both reply & unsolicited */ + strength = g_ril_unsol_parse_signal_strength(nd->ril, message, + nd->tech); + cb(&error, strength, cbd->data); + + return; + +error: + CALLBACK_WITH_FAILURE(cb, -1, cbd->data); +} + +static void ril_signal_strength(struct ofono_netreg *netreg, + ofono_netreg_strength_cb_t cb, void *data) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + struct cb_data *cbd = cb_data_new(cb, data, nd); + + if (g_ril_send(nd->ril, RIL_REQUEST_SIGNAL_STRENGTH, NULL, + ril_strength_cb, cbd, g_free) == 0) { + ofono_error("Send RIL_REQUEST_SIGNAL_STRENGTH failed."); + + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, data); + } +} + +static void ril_nitz_notify(struct ril_msg *message, gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + int year, mon, mday, hour, min, sec, dst, tzi, n_match; + char tzs, tz[4]; + gchar *nitz; + + nitz = g_ril_unsol_parse_nitz(nd->ril, message); + if (nitz == NULL) + goto error; + + n_match = sscanf(nitz, "%u/%u/%u,%u:%u:%u%c%u,%u", &year, &mon, + &mday, &hour, &min, &sec, &tzs, &tzi, &dst); + if (n_match != 9) + goto error; + + sprintf(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(netreg, &nd->time); + + g_free(nitz); + + return; + +error: + ofono_error("%s: unable to notify ofono about NITZ (%s)", + __func__, nitz ? nitz : "null"); + g_free(nitz); +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_netreg *netreg = user_data; + struct netreg_data *nd = ofono_netreg_get_data(netreg); + ofono_netreg_register(netreg); + + /* Register for network state changes */ + g_ril_register(nd->ril, RIL_UNSOL_RESPONSE_VOICE_NETWORK_STATE_CHANGED, + ril_network_state_change, netreg); + + /* Register for network time update reports */ + g_ril_register(nd->ril, RIL_UNSOL_NITZ_TIME_RECEIVED, + ril_nitz_notify, netreg); + + /* Register for signal strength changes */ + g_ril_register(nd->ril, RIL_UNSOL_SIGNAL_STRENGTH, + ril_strength_notify, netreg); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +static int ril_netreg_probe(struct ofono_netreg *netreg, unsigned int vendor, + void *data) +{ + GRil *ril = data; + struct netreg_data *nd; + + nd = g_new0(struct netreg_data, 1); + + nd->ril = g_ril_clone(ril); + nd->vendor = vendor; + nd->tech = RADIO_TECH_UNKNOWN; + 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; + ofono_netreg_set_data(netreg, nd); + + /* + * ofono_netreg_register() needs to be called after + * the driver has been set in ofono_netreg_create(), + * which calls this function. Most other drivers make + * some kind of capabilities query to the modem, and then + * call register in the callback; we use the idle loop here. + */ + g_idle_add(ril_delayed_register, netreg); + + return 0; +} + +static void ril_netreg_remove(struct ofono_netreg *netreg) +{ + struct netreg_data *nd = ofono_netreg_get_data(netreg); + + if (nd->nitz_timeout) + g_source_remove(nd->nitz_timeout); + + ofono_netreg_set_data(netreg, NULL); + + g_ril_unref(nd->ril); + g_free(nd); +} + +static struct ofono_netreg_driver driver = { + .name = RILMODEM, + .probe = ril_netreg_probe, + .remove = ril_netreg_remove, + .registration_status = ril_registration_status, + .current_operator = ril_current_operator, + .list_operators = ril_list_operators, + .register_auto = ril_register_auto, + .register_manual = ril_register_manual, + .strength = ril_signal_strength, +}; + +void ril_netreg_init(void) +{ + ofono_netreg_driver_register(&driver); +} + +void ril_netreg_exit(void) +{ + ofono_netreg_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/phonebook.c b/drivers/rilmodem/phonebook.c new file mode 100644 index 00000000..c3f1c0b7 --- /dev/null +++ b/drivers/rilmodem/phonebook.c @@ -0,0 +1,1055 @@ +/* + * + * oFono - Open Source Telephony - RIL Modem Support + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * Copyright (C) ST-Ericsson SA 2010. + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2013 Jolla Ltd + * Contact: Jussi Kangas + * Copyright (C) 2014 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "gril.h" +#include "simutil.h" +#include "common.h" + +#include "rilmodem.h" + +#define UNUSED 0xFF + +#define EXT1_CP_SUBADDRESS 1 +#define EXT1_ADDITIONAL_DATA 2 + +/* 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_IAP = 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 pb_file_info { + enum constructed_tag pbr_type; + int file_id; + enum file_type_tag file_type; + int file_length; + int record_length; +}; + +struct record_to_read { + int file_id; + enum file_type_tag type_tag; + int record_length; + int record; + int adn_idx; + gboolean anr_ext; /* Is it an EXT1 record for ANR? */ + gboolean set_by_iap; /* Type 2 file? */ +}; + +struct phonebook_entry { + int entry; + char *name; + char *number; + char *email; + char *anr; + char *sne; +}; + +unsigned char sim_path[] = { 0x3F, 0x00, 0x7F, 0x10 }; +unsigned char usim_path[] = { 0x3F, 0x00, 0x7F, 0x10, 0x5F, 0x3A }; + +/* + * Table for 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". These values are mapped to + * ',' and '?', following the Android/iPhone convention for the first and Nokia + * convention for the second (only OEM that I have seen that supports this + * feature). BCD 0x0E is reserved, we convert it to 'r'. + */ +static const char digit_to_utf8[] = "0123456789*#,?r\0"; + +/* One of these for each record in EF_PBR */ +struct pb_ref_rec { + GSList *pb_files; /* File ids to read (pb_file_info nodes) */ + GSList *pb_next; /* Next file info to read */ + GSList *pending_records; /* List of record_to_read */ + GSList *next_record; /* Next record_to_read to process */ + GTree *phonebook; /* Container of phonebook_entry structures */ +}; + +struct pb_data { + GSList *pb_refs; + GSList *pb_ref_next; + struct ofono_sim *sim; + struct ofono_sim_context *sim_context; + const unsigned char *df_path; + size_t df_size; +}; + +static void read_info_cb(int ok, unsigned char file_status, + int total_length, int record_length, + void *userdata); + +static gint comp_int(gconstpointer a, gconstpointer b) +{ + int a_val = GPOINTER_TO_INT(a); + int b_val = GPOINTER_TO_INT(b); + + return a_val - b_val; +} + +static const struct pb_file_info * +ext1_info(const GSList *pb_files) +{ + const GSList *l; + for (l = pb_files; l; l = l->next) { + const struct pb_file_info *f_info = l->data; + if (f_info->file_type == TYPE_EXT1) + return f_info; + } + + return NULL; +} + +static struct phonebook_entry *handle_adn(size_t len, const unsigned char *msg, + struct pb_ref_rec *ref, int adn_idx) +{ + unsigned name_length = len - 14; + unsigned number_start = name_length; + unsigned number_length; + unsigned extension_record = UNUSED; + unsigned i, prefix; + char *number = NULL; + char *name = sim_string_to_utf8(msg, name_length); + struct phonebook_entry *new_entry; + + /* Length contains also TON & NPI */ + number_length = msg[number_start]; + + if (number_length != UNUSED && number_length != 0) { + number_length--; + /* '+' + number + terminator */ + number = g_try_malloc0(2 * number_length + 2); + + if (number) { + prefix = 0; + + if ((msg[number_start + 1] & TON_MASK) + == TON_INTERNATIONAL) { + number[0] = '+'; + prefix = 1; + } + + for (i = 0; i < number_length; i++) { + + number[2 * i + prefix] = + digit_to_utf8[msg[number_start + 2 + i] + & 0x0f]; + number[2 * i + 1 + prefix] = + digit_to_utf8[msg[number_start + 2 + i] + >> 4]; + } + + extension_record = msg[len - 1]; + } + } + + DBG("ADN name %s, number %s ", name, number); + DBG("number length %d extension_record %d", + 2 * number_length, extension_record); + + if ((name == NULL || *name == '\0') && number == NULL) + goto end; + + new_entry = g_try_malloc0(sizeof(*new_entry)); + if (new_entry == NULL) { + ofono_error("%s: out of memory", __func__); + goto end; + } + + new_entry->name = name; + new_entry->number = number; + + DBG("Creating PB entry %d with", adn_idx); + DBG("name %s and number %s", new_entry->name, new_entry->number); + + g_tree_insert(ref->phonebook, GINT_TO_POINTER(adn_idx), new_entry); + + if (extension_record != UNUSED) { + struct record_to_read *ext_rec = + g_try_malloc0(sizeof(*ext_rec)); + const struct pb_file_info *f_info = ext1_info(ref->pb_files); + + if (ext_rec && f_info) { + ext_rec->file_id = f_info->file_id; + ext_rec->type_tag = TYPE_EXT1; + ext_rec->record_length = f_info->record_length; + ext_rec->record = extension_record; + ext_rec->adn_idx = adn_idx; + + ref->pending_records = + g_slist_prepend(ref->pending_records, ext_rec); + } + } + + return new_entry; + +end: + g_free(name); + g_free(number); + + return NULL; +} + +static void handle_iap(size_t len, const unsigned char *msg, + struct pb_ref_rec *ref, + const struct record_to_read *rec_data) +{ + GSList *l; + size_t i = 0; + + for (l = ref->pb_files; l; l = l->next) { + struct pb_file_info *f_info = l->data; + if (f_info->pbr_type == TYPE_2_TAG) { + if (i >= len) { + ofono_error("%s: EF_IAP record too small", + __func__); + return; + } + if (msg[i] != UNUSED) { + struct record_to_read *new_rec = + g_try_malloc0(sizeof(*new_rec)); + if (new_rec == NULL) { + ofono_error("%s: OOM", __func__); + return; + } + DBG("type 0x%X record %d", + f_info->file_type, msg[i]); + + new_rec->file_id = f_info->file_id; + new_rec->type_tag = f_info->file_type; + new_rec->record_length = f_info->record_length; + new_rec->record = msg[i]; + new_rec->adn_idx = rec_data->adn_idx; + new_rec->anr_ext = FALSE; + new_rec->set_by_iap = TRUE; + + ref->pending_records = + g_slist_prepend(ref->pending_records, + new_rec); + } + ++i; + } + } +} + +static void handle_sne(size_t len, const unsigned char *msg, + struct pb_ref_rec *ref, + const struct record_to_read *rec_data) +{ + char *sne; + + /* There are additional fields for type 2 files */ + if (rec_data->set_by_iap) + len -= 2; + + sne = sim_string_to_utf8(msg, len); + + if (sne && *sne != '\0') { + struct phonebook_entry *entry; + + entry = g_tree_lookup(ref->phonebook, + GINT_TO_POINTER(rec_data->adn_idx)); + if (entry) { + /* If one already exists, delete it */ + if (entry->sne) + g_free(entry->sne); + + DBG("Adding SNE %s to %d", sne, rec_data->adn_idx); + DBG("name %s", entry->name); + + entry->sne = sne; + } else { + g_free(sne); + } + } else { + g_free(sne); + } +} + +static void handle_anr(size_t len, + const unsigned char *msg, + struct pb_ref_rec *ref, + const struct record_to_read *rec_data) +{ + unsigned number_length; + unsigned extension_record; + unsigned aas_record; + unsigned i, prefix; + char *anr; + struct phonebook_entry *entry; + + if (len < 15) { + ofono_error("%s: bad EF_ANR record size", __func__); + return; + } + + aas_record = msg[0]; + if (aas_record == UNUSED) + return; + + DBG("ANR %d", aas_record); + + /* Length contains also TON & NPI */ + number_length = msg[1]; + if (number_length < 2) + return; + + number_length--; + /* '+' + number + terminator */ + anr = g_try_malloc0(2 * number_length + 2); + if (anr == NULL) + return; + + prefix = 0; + if ((msg[2] & TON_MASK) == TON_INTERNATIONAL) { + anr[0] = '+'; + prefix = 1; + } + + for (i = 0; i < number_length; i++) { + anr[2 * i + prefix] = digit_to_utf8[msg[3 + i] & 0x0f]; + anr[2 * i + 1 + prefix] = digit_to_utf8[msg[3 + i] >> 4]; + } + + entry = g_tree_lookup(ref->phonebook, + GINT_TO_POINTER(rec_data->adn_idx)); + if (entry == NULL) { + g_free(anr); + return; + } + + /* If one already exists, delete it */ + if (entry->anr) + g_free(entry->anr); + + DBG("Adding ANR %s to %d", anr, rec_data->adn_idx); + DBG("name %s", entry->name); + + entry->anr = anr; + + extension_record = msg[14]; + + DBG("ANR to entry %d number %s number length %d", + rec_data->adn_idx, anr, number_length); + DBG("extension_record %d aas %d", extension_record, aas_record); + + if (extension_record != UNUSED) { + struct record_to_read *ext_rec = + g_try_malloc0(sizeof(*ext_rec)); + const struct pb_file_info *f_info = ext1_info(ref->pb_files); + + if (ext_rec && f_info) { + ext_rec->file_id = f_info->file_id; + ext_rec->type_tag = TYPE_EXT1; + ext_rec->record_length = f_info->record_length; + ext_rec->record = extension_record; + ext_rec->adn_idx = rec_data->adn_idx; + ext_rec->anr_ext = TRUE; + + ref->pending_records = + g_slist_prepend(ref->pending_records, ext_rec); + } + } +} + +static void handle_email(size_t len, const unsigned char *msg, + struct pb_ref_rec *ref, + const struct record_to_read *rec_data) +{ + char *email; + struct phonebook_entry *entry; + + /* There are additional fields for type 2 files */ + if (rec_data->set_by_iap) + len -= 2; + + email = sim_string_to_utf8(msg, len); + if (email == NULL || *email == '\0') { + g_free(email); + return; + } + + entry = g_tree_lookup(ref->phonebook, + GINT_TO_POINTER(rec_data->adn_idx)); + if (entry == NULL) { + g_free(email); + return; + } + + /* if one already exists, delete it */ + if (entry->email) + g_free(entry->email); + + DBG("Adding email to entry %d", rec_data->adn_idx); + DBG("name %s", entry->name); + + entry->email = email; +} + +static void handle_ext1(size_t len, const unsigned char *msg, + struct pb_ref_rec *ref, + const struct record_to_read *rec_data) +{ + unsigned number_length, i, next_extension_record; + struct phonebook_entry *entry; + char *ext_number; + + if (len < 13) { + ofono_error("%s: bad EF_EXT1 record size", __func__); + return; + } + + /* Check if there is more extension data */ + next_extension_record = msg[12]; + if (next_extension_record != UNUSED) { + struct record_to_read *ext_rec = + g_try_malloc0(sizeof(*ext_rec)); + const struct pb_file_info *f_info = ext1_info(ref->pb_files); + + if (ext_rec && f_info) { + DBG("next_extension_record %d", next_extension_record); + + ext_rec->file_id = f_info->file_id; + ext_rec->record_length = f_info->record_length; + ext_rec->type_tag = TYPE_EXT1; + ext_rec->record = next_extension_record; + ext_rec->adn_idx = rec_data->adn_idx; + ext_rec->anr_ext = rec_data->anr_ext; + + ref->pending_records = + g_slist_prepend(ref->pending_records, ext_rec); + } + } + + if (msg[0] != EXT1_ADDITIONAL_DATA) { + DBG("EXT1 record with subaddress ignored"); + return; + } + + number_length = msg[1]; + ext_number = g_try_malloc0(2 * number_length + 1); + if (ext_number == NULL) + return; + + for (i = 0; i < number_length; i++) { + ext_number[2 * i] = digit_to_utf8[msg[2 + i] & 0x0f]; + ext_number[2 * i + 1] = digit_to_utf8[msg[2 + i] >> 4]; + } + + DBG("Number extension %s", ext_number); + DBG("number length %d", number_length); + + DBG("Looking for ADN entry %d", rec_data->adn_idx); + entry = g_tree_lookup(ref->phonebook, + GINT_TO_POINTER(rec_data->adn_idx)); + if (entry == NULL) { + g_free(ext_number); + return; + } + + if (rec_data->anr_ext) { + char *anr = entry->anr; + entry->anr = g_strconcat(anr, ext_number, NULL); + g_free(anr); + } else { + char *number = entry->number; + entry->number = g_strconcat(number, ext_number, NULL); + g_free(number); + } + + g_free(ext_number); +} + +static const char *file_tag_to_string(enum file_type_tag tag) +{ + switch (tag) { + case TYPE_ADN: return "ADN"; + case TYPE_IAP: return "IAP"; + case TYPE_EXT1: return "EXT1"; + case TYPE_SNE: return "SNE"; + case TYPE_ANR: return "ANR"; + case TYPE_PBC: return "PBC"; + case TYPE_GPR: return "GPR"; + case TYPE_AAS: return "AAS"; + case TYPE_GAS: return "GAS"; + case TYPE_UID: return "UID"; + case TYPE_EMAIL: return "EMAIL"; + case TYPE_CCP1: return "CCP1"; + default: return ""; + } +} + +static void decode_read_response(const struct record_to_read *rec_data, + const unsigned char *msg, size_t len, + struct pb_ref_rec *ref) +{ + DBG("Decoding %s type record", file_tag_to_string(rec_data->type_tag)); + switch (rec_data->type_tag) { + case TYPE_IAP: + handle_iap(len, msg, ref, rec_data); + break; + case TYPE_SNE: + handle_sne(len, msg, ref, rec_data); + break; + case TYPE_ANR: + handle_anr(len, msg, ref, rec_data); + break; + case TYPE_EMAIL: + handle_email(len, msg, ref, rec_data); + break; + case TYPE_EXT1: + handle_ext1(len, msg, ref, rec_data); + break; + default: + DBG("Skipping type"); + break; + } +} + +static gboolean export_entry(gpointer key, gpointer value, gpointer data) +{ + struct ofono_phonebook *pb = data; + struct phonebook_entry *entry = value; + + ofono_phonebook_entry(pb, -1, + entry->number, -1, + entry->name, -1, + NULL, + entry->anr, -1, + entry->sne, + entry->email, + NULL, NULL); + + g_free(entry->name); + g_free(entry->number); + g_free(entry->email); + g_free(entry->anr); + g_free(entry->sne); + g_free(entry); + + return FALSE; +} + +static void export_and_return(gboolean ok, struct cb_data *cbd) +{ + struct ofono_phonebook *pb = cbd->user; + ofono_phonebook_cb_t cb = cbd->cb; + struct pb_data *pbd = ofono_phonebook_get_data(pb); + GSList *l; + + DBG("phonebook fully read"); + + for (l = pbd->pb_refs; l != NULL; l = l->next) { + struct pb_ref_rec *ref = l->data; + + g_tree_foreach(ref->phonebook, export_entry, pb); + g_tree_destroy(ref->phonebook); + g_slist_free_full(ref->pending_records, g_free); + g_slist_free_full(ref->pb_files, g_free); + } + + g_slist_free_full(pbd->pb_refs, g_free); + pbd->pb_refs = NULL; + + if (ok) + CALLBACK_WITH_SUCCESS(cb, cbd->data); + else + CALLBACK_WITH_FAILURE(cb, cbd->data); + + g_free(cbd); +} + +static void read_record_cb(int ok, int total_length, int record, + const unsigned char *data, + int record_length, void *userdata) +{ + struct cb_data *cbd = userdata; + struct ofono_phonebook *pb = cbd->user; + struct pb_data *pbd = ofono_phonebook_get_data(pb); + struct pb_ref_rec *ref = pbd->pb_ref_next->data; + struct record_to_read *rec; + + if (!ok) { + ofono_error("%s: error %d", __func__, ok); + export_and_return(FALSE, cbd); + return; + } + + DBG("ok %d; total_length %d; record %d; record_length %d", + ok, total_length, record, record_length); + + rec = ref->next_record->data; + + /* This call might add elements to pending_records */ + decode_read_response(rec, data, record_length, ref); + + ref->pending_records = g_slist_remove(ref->pending_records, rec); + g_free(rec); + + if (ref->pending_records) { + struct record_to_read *rec; + + ref->next_record = ref->pending_records; + rec = ref->next_record->data; + + ofono_sim_read_record(pbd->sim_context, rec->file_id, + OFONO_SIM_FILE_STRUCTURE_FIXED, + rec->record, + rec->record_length, + pbd->df_path, pbd->df_size, + read_record_cb, cbd); + } else { + /* Read files from next EF_PBR record, if any */ + + pbd->pb_ref_next = pbd->pb_ref_next->next; + if (pbd->pb_ref_next == NULL) { + export_and_return(TRUE, cbd); + } else { + struct pb_ref_rec *ref; + + DBG("Next EFpbr record"); + + ref = pbd->pb_ref_next->data; + + if (!ref->pb_files) { + export_and_return(TRUE, cbd); + } else { + struct pb_file_info *file_info; + + ref->pb_next = ref->pb_files; + file_info = ref->pb_files->data; + + ofono_sim_read_info(pbd->sim_context, + file_info->file_id, + OFONO_SIM_FILE_STRUCTURE_FIXED, + pbd->df_path, pbd->df_size, + read_info_cb, cbd); + } + } + } +} + +static void pb_adn_cb(int ok, int total_length, int record, + const unsigned char *data, + int record_length, void *userdata) +{ + struct cb_data *cbd = userdata; + struct ofono_phonebook *pb = cbd->user; + struct pb_data *pbd = ofono_phonebook_get_data(pb); + struct pb_ref_rec *ref = pbd->pb_ref_next->data; + GSList *l; + + if (!ok) { + ofono_error("%s: error %d", __func__, ok); + export_and_return(FALSE, cbd); + return; + } + + DBG("ok %d; total_length %d; record %d; record_length %d", + ok, total_length, record, record_length); + + if (handle_adn(record_length, data, ref, record) != NULL) { + /* Add type 1 records */ + for (l = ref->pb_files; l; l = l->next) { + const struct pb_file_info *f_info = l->data; + struct record_to_read *ext_rec; + + if (f_info->pbr_type == TYPE_1_TAG && + f_info->file_type != TYPE_ADN) { + ext_rec = g_try_malloc0(sizeof(*ext_rec)); + if (ext_rec == NULL) + break; + + ext_rec->file_id = f_info->file_id; + ext_rec->type_tag = f_info->file_type; + ext_rec->record_length = f_info->record_length; + ext_rec->record = record; + ext_rec->adn_idx = record; + + ref->pending_records = + g_slist_prepend(ref->pending_records, + ext_rec); + } + } + } + + if (record*record_length >= total_length) { + DBG("All ADN records read: reading additional files"); + + if (ref->pending_records) { + struct record_to_read *rec; + + ref->next_record = ref->pending_records; + rec = ref->next_record->data; + + ofono_sim_read_record(pbd->sim_context, rec->file_id, + OFONO_SIM_FILE_STRUCTURE_FIXED, + rec->record, + rec->record_length, + pbd->df_path, pbd->df_size, + read_record_cb, cbd); + } else { + export_and_return(TRUE, cbd); + } + } +} + +static void read_info_cb(int ok, unsigned char file_status, + int total_length, int record_length, + void *userdata) +{ + struct cb_data *cbd = userdata; + struct ofono_phonebook *pb = cbd->user; + struct pb_data *pbd = ofono_phonebook_get_data(pb); + struct pb_file_info *file_info; + struct pb_ref_rec *ref = pbd->pb_ref_next->data; + + file_info = ref->pb_next->data; + ref->pb_next = ref->pb_next->next; + + if (ok) { + file_info->record_length = record_length; + file_info->file_length = total_length; + + DBG("file id %x record length %d total_length %d", + file_info->file_id, record_length, total_length); + } else { + ofono_warn("%s: %x not found", __func__, file_info->file_id); + ref->pb_files = g_slist_remove(ref->pb_files, file_info); + g_free(file_info); + } + + if (ref->pb_next == NULL) { + if (ref->pb_files == NULL) { + ofono_warn("%s: no phonebook on SIM", __func__); + export_and_return(FALSE, cbd); + return; + } + + /* Read full contents of the master file */ + file_info = ref->pb_files->data; + + ofono_sim_read_path(pbd->sim_context, file_info->file_id, + OFONO_SIM_FILE_STRUCTURE_FIXED, + pbd->df_path, pbd->df_size, + pb_adn_cb, cbd); + } else { + file_info = ref->pb_next->data; + + ofono_sim_read_info(pbd->sim_context, file_info->file_id, + OFONO_SIM_FILE_STRUCTURE_FIXED, + pbd->df_path, pbd->df_size, + read_info_cb, cbd); + } +} + +static void start_sim_app_read(struct cb_data *cbd) +{ + struct ofono_phonebook *pb = cbd->user; + struct pb_data *pbd = ofono_phonebook_get_data(pb); + struct pb_ref_rec *ref_rec; + struct pb_file_info *f_info; + struct pb_file_info *f_ext1; + + pbd->df_path = sim_path; + pbd->df_size = sizeof(sim_path); + + ref_rec = g_try_malloc0(sizeof(*ref_rec)); + if (ref_rec == NULL) { + ofono_error("%s: OOM", __func__); + export_and_return(FALSE, cbd); + return; + } + + ref_rec->phonebook = g_tree_new(comp_int); + + /* Only EF_ADN and EF_EXT1 read for SIM */ + + f_info = g_try_malloc0(sizeof(*f_info)); + if (f_info == NULL) { + ofono_error("%s: OOM", __func__); + export_and_return(FALSE, cbd); + return; + } + + f_info->file_id = SIM_EFADN_FILEID; + f_info->pbr_type = TYPE_1_TAG; + f_info->file_type = TYPE_ADN; + ref_rec->pb_files = g_slist_append(ref_rec->pb_files, f_info); + + f_ext1 = g_try_malloc0(sizeof(*f_ext1)); + if (f_ext1 == NULL) { + ofono_error("%s: OOM", __func__); + export_and_return(FALSE, cbd); + return; + } + + f_ext1->file_id = SIM_EFEXT1_FILEID; + f_ext1->pbr_type = TYPE_3_TAG; + f_ext1->file_type = TYPE_EXT1; + ref_rec->pb_files = g_slist_append(ref_rec->pb_files, f_ext1); + + pbd->pb_refs = g_slist_append(pbd->pb_refs, ref_rec); + pbd->pb_ref_next = pbd->pb_refs; + + ref_rec->pb_next = ref_rec->pb_files; + + /* Start reading process for MF */ + ofono_sim_read_info(pbd->sim_context, f_info->file_id, + OFONO_SIM_FILE_STRUCTURE_FIXED, + pbd->df_path, pbd->df_size, + read_info_cb, cbd); +} + +static void pb_reference_data_cb(int ok, int total_length, int record, + const unsigned char *sdata, + int record_length, void *userdata) +{ + struct cb_data *cbd = userdata; + struct ofono_phonebook *pb = cbd->user; + struct pb_data *pbd = ofono_phonebook_get_data(pb); + const unsigned char *ptr = sdata; + gboolean finished = FALSE; + struct pb_ref_rec *ref_rec; + + DBG("total_length %d record %d record_length %d", + total_length, record, record_length); + + if (!ok) { + /* We migh have a SIM instead of USIM application: try that */ + DBG("%s: error %d, trying SIM files", __func__, ok); + start_sim_app_read(cbd); + return; + } + + ref_rec = g_try_malloc0(sizeof(*ref_rec)); + if (ref_rec == NULL) { + ofono_error("%s: OOM", __func__); + export_and_return(FALSE, cbd); + return; + } + + ref_rec->phonebook = g_tree_new(comp_int); + + while (ptr < sdata + record_length && finished == FALSE) { + int typelen, file_id, i; + enum constructed_tag pbr_type = *ptr; + + switch (pbr_type) { + case TYPE_1_TAG: + case TYPE_2_TAG: + case TYPE_3_TAG: + typelen = *(ptr + 1); + DBG("File type=%02X, len=%d", *ptr, typelen); + ptr += 2; + i = 0; + + while (i < typelen) { + struct pb_file_info *file_info = + g_try_new0(struct pb_file_info, 1); + if (!file_info) { + ofono_error("%s: OOM", __func__); + export_and_return(FALSE, cbd); + return; + } + + file_id = (ptr[i + 2] << 8) + ptr[i + 3]; + + DBG("creating file info for File type=%02X", + ptr[i]); + DBG("File ID=%04X", file_id); + + file_info->pbr_type = pbr_type; + file_info->file_type = ptr[i]; + file_info->file_id = file_id; + /* Keep order, important for type 2 files */ + ref_rec->pb_files = + g_slist_append(ref_rec->pb_files, + file_info); + i += ptr[i + 1] + 2; + } + + ptr += typelen; + break; + default: + DBG("All handled %02x", *ptr); + finished = TRUE; + break; + } + } + + pbd->pb_refs = g_slist_append(pbd->pb_refs, ref_rec); + + if (record*record_length >= total_length) { + struct pb_ref_rec *ref; + struct pb_file_info *file_info; + + DBG("All EFpbr records read"); + + pbd->pb_ref_next = pbd->pb_refs; + ref = pbd->pb_ref_next->data; + + if (ref->pb_files == NULL) { + ofono_error("%s: no files to read", __func__); + export_and_return(FALSE, cbd); + return; + } + + ref->pb_next = ref->pb_files; + file_info = ref->pb_files->data; + + /* Start reading process for first EF_PBR entry */ + + ofono_sim_read_info(pbd->sim_context, file_info->file_id, + OFONO_SIM_FILE_STRUCTURE_FIXED, + pbd->df_path, pbd->df_size, + read_info_cb, cbd); + } +} + +static void ril_export_entries(struct ofono_phonebook *pb, + const char *storage, + ofono_phonebook_cb_t cb, void *data) +{ + struct pb_data *pbd = ofono_phonebook_get_data(pb); + struct cb_data *cbd; + + DBG("Storage %s", storage); + + /* Only for SIM memory */ + if (strcmp(storage, "SM") != 0) { + CALLBACK_WITH_FAILURE(cb, data); + return; + } + + cbd = cb_data_new(cb, data, pb); + + /* Assume USIM, change in case EF_PBR is not present */ + pbd->df_path = usim_path; + pbd->df_size = sizeof(usim_path); + + ofono_sim_read(pbd->sim_context, SIM_EFPBR_FILEID, + OFONO_SIM_FILE_STRUCTURE_FIXED, + pb_reference_data_cb, cbd); +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_phonebook *pb = user_data; + + ofono_phonebook_register(pb); + return FALSE; +} + +static int ril_phonebook_probe(struct ofono_phonebook *pb, + unsigned int vendor, void *user) +{ + struct ofono_modem *modem = user; + struct pb_data *pd = g_try_new0(struct pb_data, 1); + if (pd == NULL) + return -ENOMEM; + + pd->sim = __ofono_atom_find(OFONO_ATOM_TYPE_SIM, modem); + if (pd->sim == NULL) + return -ENOENT; + + pd->sim_context = ofono_sim_context_create(pd->sim); + if (pd->sim_context == NULL) + return -ENOENT; + + ofono_phonebook_set_data(pb, pd); + + g_idle_add(ril_delayed_register, pb); + + return 0; +} + +static void ril_phonebook_remove(struct ofono_phonebook *pb) +{ + struct pb_data *pbd = ofono_phonebook_get_data(pb); + + ofono_phonebook_set_data(pb, NULL); + ofono_sim_context_free(pbd->sim_context); + + g_free(pbd); +} + +static struct ofono_phonebook_driver driver = { + .name = RILMODEM, + .probe = ril_phonebook_probe, + .remove = ril_phonebook_remove, + .export_entries = ril_export_entries +}; + +void ril_phonebook_init(void) +{ + ofono_phonebook_driver_register(&driver); +} + +void ril_phonebook_exit(void) +{ + ofono_phonebook_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/radio-settings.c b/drivers/rilmodem/radio-settings.c new file mode 100644 index 00000000..9153e5bb --- /dev/null +++ b/drivers/rilmodem/radio-settings.c @@ -0,0 +1,300 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2013 Jolla Ltd + * Contact: Jussi Kangas + * Copyright (C) 2014 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "gril.h" + +#include "rilmodem.h" + +#include "grilrequest.h" +#include "grilreply.h" +#include "radio-settings.h" + +static void ril_set_rat_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_radio_settings *rs = cbd->user; + struct radio_data *rd = ofono_radio_settings_get_data(rs); + ofono_radio_settings_rat_mode_set_cb_t cb = cbd->cb; + + if (message->error == RIL_E_SUCCESS) { + g_ril_print_response_no_args(rd->ril, message); + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + ofono_error("%s: rat mode setting failed", __func__); + CALLBACK_WITH_FAILURE(cb, cbd->data); + } +} + +void ril_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 radio_data *rd = ofono_radio_settings_get_data(rs); + struct cb_data *cbd = cb_data_new(cb, data, rs); + struct parcel rilp; + int pref = PREF_NET_TYPE_GSM_WCDMA; + + switch (mode) { + case OFONO_RADIO_ACCESS_MODE_ANY: + pref = PREF_NET_TYPE_LTE_GSM_WCDMA; + break; + 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; + break; + case OFONO_RADIO_ACCESS_MODE_LTE: + pref = PREF_NET_TYPE_LTE_GSM_WCDMA; + break; + } + + g_ril_request_set_preferred_network_type(rd->ril, pref, &rilp); + + if (g_ril_send(rd->ril, RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE, + &rilp, ril_set_rat_cb, cbd, g_free) == 0) { + ofono_error("%s: unable to set rat mode", __func__); + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_rat_mode_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_radio_settings_rat_mode_query_cb_t cb = cbd->cb; + struct ofono_radio_settings *rs = cbd->user; + struct radio_data *rd = ofono_radio_settings_get_data(rs); + int mode, pref; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: error %s", __func__, + ril_error_to_string(message->error)); + CALLBACK_WITH_FAILURE(cb, -1, cbd->data); + return; + } + + pref = g_ril_reply_parse_get_preferred_network_type(rd->ril, message); + if (pref < 0) { + ofono_error("%s: parse error", __func__); + CALLBACK_WITH_FAILURE(cb, -1, cbd->data); + return; + } + + /* + * GSM_WCDMA_AUTO -> ril.h: GSM/WCDMA (auto mode, according to PRL) + * PRL: preferred roaming list. + * This value is returned when selecting the slot as having 3G + * capabilities, so it is sort of the default for MTK modems. + */ + + switch (pref) { + case PREF_NET_TYPE_GSM_WCDMA: + case PREF_NET_TYPE_GSM_WCDMA_AUTO: + mode = OFONO_RADIO_ACCESS_MODE_UMTS; + break; + case PREF_NET_TYPE_GSM_ONLY: + mode = OFONO_RADIO_ACCESS_MODE_GSM; + break; + case PREF_NET_TYPE_LTE_GSM_WCDMA: + mode = OFONO_RADIO_ACCESS_MODE_LTE; + break; + default: + ofono_error("%s: Unexpected preferred network type (%d)", + __func__, pref); + mode = OFONO_RADIO_ACCESS_MODE_ANY; + break; + } + + CALLBACK_WITH_SUCCESS(cb, mode, cbd->data); +} + +void ril_query_rat_mode(struct ofono_radio_settings *rs, + ofono_radio_settings_rat_mode_query_cb_t cb, + void *data) +{ + struct radio_data *rd = ofono_radio_settings_get_data(rs); + struct cb_data *cbd = cb_data_new(cb, data, rs); + + if (g_ril_send(rd->ril, RIL_REQUEST_GET_PREFERRED_NETWORK_TYPE, + NULL, ril_rat_mode_cb, cbd, g_free) == 0) { + ofono_error("%s: unable to send rat mode query", __func__); + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, data); + } +} + +void ril_query_fast_dormancy(struct ofono_radio_settings *rs, + ofono_radio_settings_fast_dormancy_query_cb_t cb, + void *data) +{ + struct radio_data *rd = ofono_radio_settings_get_data(rs); + + CALLBACK_WITH_SUCCESS(cb, rd->fast_dormancy, data); +} + +static void ril_display_state_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_radio_settings *rs = cbd->user; + struct radio_data *rd = ofono_radio_settings_get_data(rs); + ofono_radio_settings_fast_dormancy_set_cb_t cb = cbd->cb; + + if (message->error == RIL_E_SUCCESS) { + g_ril_print_response_no_args(rd->ril, message); + + rd->fast_dormancy = rd->pending_fd; + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + CALLBACK_WITH_FAILURE(cb, cbd->data); + } +} + +void ril_set_fast_dormancy(struct ofono_radio_settings *rs, + ofono_bool_t enable, + ofono_radio_settings_fast_dormancy_set_cb_t cb, + void *data) +{ + struct radio_data *rd = ofono_radio_settings_get_data(rs); + struct cb_data *cbd = cb_data_new(cb, data, rs); + struct parcel rilp; + + g_ril_request_screen_state(rd->ril, enable ? 0 : 1, &rilp); + + rd->pending_fd = enable; + + if (g_ril_send(rd->ril, RIL_REQUEST_SCREEN_STATE, &rilp, + ril_display_state_cb, cbd, g_free) <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static ofono_bool_t query_available_rats_cb(gpointer user_data) +{ + unsigned int available_rats; + struct cb_data *cbd = user_data; + ofono_radio_settings_available_rats_query_cb_t cb = cbd->cb; + struct ofono_radio_settings *rs = cbd->user; + struct radio_data *rd = ofono_radio_settings_get_data(rs); + + available_rats = OFONO_RADIO_ACCESS_MODE_GSM + | OFONO_RADIO_ACCESS_MODE_UMTS; + + if (ofono_modem_get_boolean(rd->modem, MODEM_PROP_LTE_CAPABLE)) + available_rats |= OFONO_RADIO_ACCESS_MODE_LTE; + + CALLBACK_WITH_SUCCESS(cb, available_rats, cbd->data); + + g_free(cbd); + + return FALSE; +} + +void ril_query_available_rats(struct ofono_radio_settings *rs, + ofono_radio_settings_available_rats_query_cb_t cb, + void *data) +{ + struct cb_data *cbd = cb_data_new(cb, data, rs); + + g_idle_add(query_available_rats_cb, cbd); +} + +void ril_delayed_register(const struct ofono_error *error, void *user_data) +{ + struct ofono_radio_settings *rs = user_data; + + if (error->type == OFONO_ERROR_TYPE_NO_ERROR) + ofono_radio_settings_register(rs); + else + ofono_error("%s: cannot set default fast dormancy", __func__); +} + +static int ril_radio_settings_probe(struct ofono_radio_settings *rs, + unsigned int vendor, void *user) +{ + struct ril_radio_settings_driver_data *rs_init_data = user; + struct radio_data *rsd = g_try_new0(struct radio_data, 1); + + if (rsd == NULL) { + ofono_error("%s: cannot allocate memory", __func__); + return -ENOMEM; + } + + rsd->ril = g_ril_clone(rs_init_data->gril); + rsd->modem = rs_init_data->modem; + + ofono_radio_settings_set_data(rs, rsd); + + ril_set_fast_dormancy(rs, FALSE, ril_delayed_register, rs); + + return 0; +} + +void ril_radio_settings_remove(struct ofono_radio_settings *rs) +{ + struct radio_data *rd = ofono_radio_settings_get_data(rs); + ofono_radio_settings_set_data(rs, NULL); + + g_ril_unref(rd->ril); + g_free(rd); +} + +static struct ofono_radio_settings_driver driver = { + .name = RILMODEM, + .probe = ril_radio_settings_probe, + .remove = ril_radio_settings_remove, + .query_rat_mode = ril_query_rat_mode, + .set_rat_mode = ril_set_rat_mode, + .query_fast_dormancy = ril_query_fast_dormancy, + .set_fast_dormancy = ril_set_fast_dormancy, + .query_available_rats = ril_query_available_rats +}; + +void ril_radio_settings_init(void) +{ + ofono_radio_settings_driver_register(&driver); +} + +void ril_radio_settings_exit(void) +{ + ofono_radio_settings_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/radio-settings.h b/drivers/rilmodem/radio-settings.h new file mode 100644 index 00000000..727d5381 --- /dev/null +++ b/drivers/rilmodem/radio-settings.h @@ -0,0 +1,47 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2014 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct radio_data { + GRil *ril; + struct ofono_modem *modem; + gboolean fast_dormancy; + gboolean pending_fd; +}; + +void ril_delayed_register(const struct ofono_error *error, void *user_data); +void ril_radio_settings_remove(struct ofono_radio_settings *rs); +void ril_query_rat_mode(struct ofono_radio_settings *rs, + ofono_radio_settings_rat_mode_query_cb_t cb, + void *data); +void ril_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); +void ril_query_fast_dormancy(struct ofono_radio_settings *rs, + ofono_radio_settings_fast_dormancy_query_cb_t cb, + void *data); +void ril_set_fast_dormancy(struct ofono_radio_settings *rs, + ofono_bool_t enable, + ofono_radio_settings_fast_dormancy_set_cb_t cb, + void *data); +void ril_query_available_rats(struct ofono_radio_settings *rs, + ofono_radio_settings_available_rats_query_cb_t cb, + void *data); diff --git a/drivers/rilmodem/rilmodem.c b/drivers/rilmodem/rilmodem.c new file mode 100644 index 00000000..e693563d --- /dev/null +++ b/drivers/rilmodem/rilmodem.c @@ -0,0 +1,78 @@ +/* + * + * oFono - Open Source Telephony - RIL Modem Support + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012 Canonical, Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#define OFONO_API_SUBJECT_TO_CHANGE +#include +#include +#include + +#include "rilmodem.h" + +static int rilmodem_init(void) +{ + DBG(""); + + ril_devinfo_init(); + ril_sim_init(); + ril_voicecall_init(); + ril_sms_init(); + ril_netreg_init(); + ril_call_volume_init(); + ril_gprs_init(); + ril_gprs_context_init(); + ril_ussd_init(); + ril_call_settings_init(); + ril_call_forwarding_init(); + ril_radio_settings_init(); + ril_call_barring_init(); + + return 0; +} + +static void rilmodem_exit(void) +{ + DBG(""); + + ril_devinfo_exit(); + ril_sim_exit(); + ril_voicecall_exit(); + ril_sms_exit(); + ril_netreg_exit(); + ril_call_volume_exit(); + ril_gprs_exit(); + ril_gprs_context_exit(); + ril_ussd_exit(); + ril_call_settings_exit(); + ril_call_forwarding_exit(); + ril_radio_settings_exit(); + ril_call_barring_exit(); +} + +OFONO_PLUGIN_DEFINE(rilmodem, "RIL modem driver", VERSION, + OFONO_PLUGIN_PRIORITY_DEFAULT, rilmodem_init, rilmodem_exit) diff --git a/drivers/rilmodem/rilmodem.h b/drivers/rilmodem/rilmodem.h new file mode 100644 index 00000000..987ce3c0 --- /dev/null +++ b/drivers/rilmodem/rilmodem.h @@ -0,0 +1,71 @@ +/* + * + * oFono - Open Source Telephony - RIL Modem Support + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "rilutil.h" + +#define RILMODEM "rilmodem" + +/* Shared constants */ +#define EF_STATUS_INVALIDATED 0 +#define EF_STATUS_VALID 1 + +extern void ril_devinfo_init(void); +extern void ril_devinfo_exit(void); + +extern void ril_call_volume_init(void); +extern void ril_call_volume_exit(void); + +extern void ril_voicecall_init(void); +extern void ril_voicecall_exit(void); + +extern void ril_sim_init(void); +extern void ril_sim_exit(void); + +extern void ril_sms_init(void); +extern void ril_sms_exit(void); + +extern void ril_netreg_init(void); +extern void ril_netreg_exit(void); + +extern void ril_gprs_init(void); +extern void ril_gprs_exit(void); + +extern void ril_gprs_context_init(void); +extern void ril_gprs_context_exit(void); + +extern void ril_ussd_init(void); +extern void ril_ussd_exit(void); + +extern void ril_call_settings_init(void); +extern void ril_call_settings_exit(void); + +extern void ril_call_forwarding_init(void); +extern void ril_call_forwarding_exit(void); + +extern void ril_radio_settings_init(void); +extern void ril_radio_settings_exit(void); + +extern void ril_call_barring_init(void); +extern void ril_call_barring_exit(void); + +extern void ril_phonebook_init(void); +extern void ril_phonebook_exit(void); diff --git a/drivers/rilmodem/rilutil.c b/drivers/rilmodem/rilutil.c new file mode 100644 index 00000000..c173940e --- /dev/null +++ b/drivers/rilmodem/rilutil.c @@ -0,0 +1,194 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#define OFONO_API_SUBJECT_TO_CHANGE +#include +#include + +#include "common.h" +#include "rilutil.h" +#include "simutil.h" +#include "util.h" +#include "ril_constants.h" + +struct ril_util_sim_state_query { + GRil *ril; + guint cpin_poll_source; + guint cpin_poll_count; + guint interval; + guint num_times; + ril_util_sim_inserted_cb_t cb; + void *userdata; + GDestroyNotify destroy; +}; + +static gboolean cpin_check(gpointer userdata); + +void decode_ril_error(struct ofono_error *error, const char *final) +{ + if (!strcmp(final, "OK")) { + error->type = OFONO_ERROR_TYPE_NO_ERROR; + error->error = 0; + } else { + error->type = OFONO_ERROR_TYPE_FAILURE; + error->error = 0; + } +} + +gint ril_util_call_compare_by_status(gconstpointer a, gconstpointer b) +{ + const struct ofono_call *call = a; + int status = GPOINTER_TO_INT(b); + + if (status != call->status) + return 1; + + return 0; +} + +gint ril_util_call_compare_by_phone_number(gconstpointer a, gconstpointer b) +{ + const struct ofono_call *call = a; + const struct ofono_phone_number *pb = b; + + return memcmp(&call->phone_number, pb, + sizeof(struct ofono_phone_number)); +} + +gint ril_util_call_compare_by_id(gconstpointer a, gconstpointer b) +{ + const struct ofono_call *call = a; + unsigned int id = GPOINTER_TO_UINT(b); + + if (id < call->id) + return -1; + + if (id > call->id) + return 1; + + return 0; +} + +gint ril_util_call_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 gboolean cpin_check(gpointer userdata) +{ + struct ril_util_sim_state_query *req = userdata; + + req->cpin_poll_source = 0; + + return FALSE; +} + +gchar *ril_util_get_netmask(const gchar *address) +{ + char *result; + + if (g_str_has_suffix(address, "/30")) { + result = PREFIX_30_NETMASK; + } else if (g_str_has_suffix(address, "/29")) { + result = PREFIX_29_NETMASK; + } else if (g_str_has_suffix(address, "/28")) { + result = PREFIX_28_NETMASK; + } else if (g_str_has_suffix(address, "/27")) { + result = PREFIX_27_NETMASK; + } else if (g_str_has_suffix(address, "/26")) { + result = PREFIX_26_NETMASK; + } else if (g_str_has_suffix(address, "/25")) { + result = PREFIX_25_NETMASK; + } else if (g_str_has_suffix(address, "/24")) { + result = PREFIX_24_NETMASK; + } else { + /* + * This handles the case where the + * Samsung RILD returns an address without + * a prefix, however it explicitly sets a + * /24 netmask ( which isn't returned as + * an attribute of the DATA_CALL. + * + * TODO/OEM: this might need to be quirked + * for specific devices. + */ + result = PREFIX_24_NETMASK; + } + + DBG("address: %s netmask: %s", address, result); + + return result; +} + +struct ril_util_sim_state_query *ril_util_sim_state_query_new(GRil *ril, + guint interval, guint num_times, + ril_util_sim_inserted_cb_t cb, + void *userdata, + GDestroyNotify destroy) +{ + struct ril_util_sim_state_query *req; + + req = g_new0(struct ril_util_sim_state_query, 1); + + req->ril = ril; + req->interval = interval; + req->num_times = num_times; + req->cb = cb; + req->userdata = userdata; + req->destroy = destroy; + + cpin_check(req); + + return req; +} + +void ril_util_sim_state_query_free(struct ril_util_sim_state_query *req) +{ + if (req == NULL) + return; + + if (req->cpin_poll_source > 0) + g_source_remove(req->cpin_poll_source); + + if (req->destroy) + req->destroy(req->userdata); + + g_free(req); +} diff --git a/drivers/rilmodem/rilutil.h b/drivers/rilmodem/rilutil.h new file mode 100644 index 00000000..25aed2eb --- /dev/null +++ b/drivers/rilmodem/rilutil.h @@ -0,0 +1,165 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RILUTIL_H +#define RILUTIL_H + +#include +#include +#include +#include + +/* TODO: create a table lookup*/ +#define PREFIX_30_NETMASK "255.255.255.252" +#define PREFIX_29_NETMASK "255.255.255.248" +#define PREFIX_28_NETMASK "255.255.255.240" +#define PREFIX_27_NETMASK "255.255.255.224" +#define PREFIX_26_NETMASK "255.255.255.192" +#define PREFIX_25_NETMASK "255.255.255.128" +#define PREFIX_24_NETMASK "255.255.255.0" + +#define MODEM_PROP_LTE_CAPABLE "lte-capable" + +enum ril_util_sms_store { + RIL_UTIL_SMS_STORE_SM = 0, + RIL_UTIL_SMS_STORE_ME = 1, + RIL_UTIL_SMS_STORE_MT = 2, + RIL_UTIL_SMS_STORE_SR = 3, + RIL_UTIL_SMS_STORE_BM = 4, +}; + +/* 3GPP TS 27.007 Release 8 Section 5.5 */ +enum at_util_charset { + RIL_UTIL_CHARSET_GSM = 0x1, + RIL_UTIL_CHARSET_HEX = 0x2, + RIL_UTIL_CHARSET_IRA = 0x4, + RIL_UTIL_CHARSET_PCCP437 = 0x8, + RIL_UTIL_CHARSET_PCDN = 0x10, + RIL_UTIL_CHARSET_UCS2 = 0x20, + RIL_UTIL_CHARSET_UTF8 = 0x40, + RIL_UTIL_CHARSET_8859_1 = 0x80, + RIL_UTIL_CHARSET_8859_2 = 0x100, + RIL_UTIL_CHARSET_8859_3 = 0x200, + RIL_UTIL_CHARSET_8859_4 = 0x400, + RIL_UTIL_CHARSET_8859_5 = 0x800, + RIL_UTIL_CHARSET_8859_6 = 0x1000, + RIL_UTIL_CHARSET_8859_C = 0x2000, + RIL_UTIL_CHARSET_8859_A = 0x4000, + RIL_UTIL_CHARSET_8859_G = 0x8000, + RIL_UTIL_CHARSET_8859_H = 0x10000, +}; + +struct ril_sim_data { + struct ofono_modem *modem; + GRil *gril; + ofono_sim_state_event_cb_t ril_state_watch; +}; + +struct ril_gprs_context_data { + GRil *gril; + struct ofono_modem *modem; + enum ofono_gprs_context_type type; +}; + +struct ril_voicecall_driver_data { + GRil *gril; + struct ofono_modem *modem; +}; + +struct ril_gprs_driver_data { + GRil *gril; + struct ofono_modem *modem; +}; + +struct ril_radio_settings_driver_data { + GRil *gril; + struct ofono_modem *modem; +}; + +typedef void (*ril_util_sim_inserted_cb_t)(gboolean present, void *userdata); + +void decode_ril_error(struct ofono_error *error, const char *final); +gint ril_util_call_compare_by_status(gconstpointer a, gconstpointer b); +gint ril_util_call_compare_by_phone_number(gconstpointer a, gconstpointer b); +gint ril_util_call_compare_by_id(gconstpointer a, gconstpointer b); +gint ril_util_call_compare(gconstpointer a, gconstpointer b); +gchar *ril_util_get_netmask(const char *address); + +struct ril_util_sim_state_query *ril_util_sim_state_query_new(GRil *ril, + guint interval, guint num_times, + ril_util_sim_inserted_cb_t cb, + void *userdata, + GDestroyNotify destroy); +void ril_util_sim_state_query_free(struct ril_util_sim_state_query *req); + +struct cb_data { + void *cb; + void *data; + void *user; +}; + +static inline struct cb_data *cb_data_new(void *cb, void *data, void *user) +{ + struct cb_data *ret; + + ret = g_new0(struct cb_data, 1); + ret->cb = cb; + ret->data = data; + ret->user = user; + + return ret; +} + +static inline int ril_util_convert_signal_strength(int strength) +{ + int result; + + if (strength == 99) + result = -1; + else + result = (strength * 100) / 31; + + return result; +} + +#define DECLARE_FAILURE(e) \ + struct ofono_error e; \ + e.type = OFONO_ERROR_TYPE_FAILURE; \ + e.error = 0 \ + +#define CALLBACK_WITH_FAILURE(cb, args...) \ + do { \ + struct ofono_error cb_e; \ + cb_e.type = OFONO_ERROR_TYPE_FAILURE; \ + cb_e.error = 0; \ + \ + cb(&cb_e, ##args); \ + } while (0) \ + +#define CALLBACK_WITH_SUCCESS(f, args...) \ + do { \ + struct ofono_error e; \ + e.type = OFONO_ERROR_TYPE_NO_ERROR; \ + e.error = 0; \ + f(&e, ##args); \ + } while (0) + +#endif /* RILUTIL_H */ diff --git a/drivers/rilmodem/sim.c b/drivers/rilmodem/sim.c new file mode 100644 index 00000000..178cb97b --- /dev/null +++ b/drivers/rilmodem/sim.c @@ -0,0 +1,1200 @@ +/* + * + * oFono - Open Source Telephony - RIL Modem Support + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2013 Canonical, Ltd. All rights reserved. + * Copyright (C) 2015 Ratchanan Srirattanamet. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "ofono.h" + +#include "simutil.h" +#include "util.h" + +#include "gril.h" +#include "grilutil.h" +#include "parcel.h" +#include "ril_constants.h" +#include "rilmodem.h" + +#include "grilreply.h" +#include "grilrequest.h" +#include "grilunsol.h" + +#include "drivers/infineonmodem/infineon_constants.h" + +/* Number of passwords in EPINC response */ +#define MTK_EPINC_NUM_PASSWD 4 + +/* + * Based on ../drivers/atmodem/sim.c. + * + * TODO: + * 1. Defines constants for hex literals + * 2. Document P1-P3 usage (+CSRM) + */ + +/* + * 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. + */ + +static void ril_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 sim_data { + GRil *ril; + enum ofono_ril_vendor vendor; + gchar *aid_str; + guint 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; + struct ofono_modem *modem; + ofono_sim_state_event_cb_t ril_state_watch; + ofono_bool_t unlock_pending; +}; + +struct change_state_cbd { + 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; +}; + +static void send_get_sim_status(struct ofono_sim *sim); + +static void ril_file_info_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_file_info_cb_t cb = cbd->cb; + struct sim_data *sd = cbd->user; + struct ofono_error error; + gboolean ok = FALSE; + int sw1, sw2; + int flen = 0, rlen = 0, str = 0; + guchar access[3] = { 0x00, 0x00, 0x00 }; + guchar file_status = EF_STATUS_VALID; + struct reply_sim_io *reply = NULL; + + /* Error, and no data */ + if (message->error != RIL_E_SUCCESS && message->buf_len == 0) { + ofono_error("%s: Reply failure: %s", __func__, + ril_error_to_string(message->error)); + decode_ril_error(&error, "FAIL"); + goto error; + } + + /* + * The reply can have event data even when message->error is not zero + * in mako. + */ + reply = g_ril_reply_parse_sim_io(sd->ril, message); + if (reply == NULL) { + decode_ril_error(&error, "FAIL"); + goto error; + } + + sw1 = reply->sw1; + sw2 = reply->sw2; + + /* + * SIM app file not found || USIM app file not found + * See 3gpp TS 51.011, 9.4.4, and ETSI TS 102 221, 10.2.1.5.3 + * This can happen with result SUCCESS (maguro) or GENERIC_FAILURE + * (mako) + */ + if ((sw1 == 0x94 && sw2 == 0x04) || (sw1 == 0x6A && sw2 == 0x82)) { + DBG("File not found. Error %s", + ril_error_to_string(message->error)); + decode_ril_error(&error, "FAIL"); + goto error; + } + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + } else { + ofono_error("%s: Reply failure: %s, %02x, %02x", __func__, + ril_error_to_string(message->error), sw1, sw2); + decode_ril_error(&error, "FAIL"); + goto error; + } + + if ((sw1 != 0x90 && sw1 != 0x91 && sw1 != 0x92 && sw1 != 0x9f) || + (sw1 == 0x90 && sw2 != 0x00)) { + ofono_error("Error reply, invalid values: sw1: %02x sw2: %02x", + sw1, sw2); + + /* TODO: fix decode_ril_error to take type & error */ + + error.type = OFONO_ERROR_TYPE_SIM; + error.error = (sw1 << 8) | sw2; + + goto error; + } + + if (reply->hex_len) { + if (reply->hex_response[0] == 0x62) { + ok = sim_parse_3g_get_response(reply->hex_response, + reply->hex_len, + &flen, &rlen, &str, + access, NULL); + } else { + ok = sim_parse_2g_get_response(reply->hex_response, + reply->hex_len, + &flen, &rlen, &str, + access, &file_status); + } + } + + if (!ok) { + ofono_error("%s: parse response failed", __func__); + decode_ril_error(&error, "FAIL"); + goto error; + } + + cb(&error, flen, str, rlen, access, file_status, cbd->data); + + g_ril_reply_free_sim_io(reply); + + return; + +error: + g_ril_reply_free_sim_io(reply); + + cb(&error, -1, -1, -1, NULL, EF_STATUS_INVALIDATED, cbd->data); +} + +static void ril_sim_read_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 sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + struct req_sim_read_info req; + guint ret = 0; + + DBG("file %04x", fileid); + + req.app_type = sd->app_type; + req.aid_str = sd->aid_str; + req.fileid = fileid; + req.path = path; + req.path_len = path_len; + + if (!g_ril_request_sim_read_info(sd->ril, + &req, + &rilp)) { + ofono_error("Couldn't build SIM read info request"); + goto error; + } + + g_ril_append_print_buf(sd->ril, + "%s0,0,15,(null),pin2=(null),aid=%s)", + print_buf, + sd->aid_str); + + ret = g_ril_send(sd->ril, RIL_REQUEST_SIM_IO, &rilp, + ril_file_info_cb, cbd, g_free); + +error: + if (ret == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, -1, -1, NULL, + EF_STATUS_INVALIDATED, data); + } +} + +static void ril_file_io_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_read_cb_t cb = cbd->cb; + struct sim_data *sd = cbd->user; + struct ofono_error error; + struct reply_sim_io *reply; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + } else { + ofono_error("RILD reply failure: %s", + ril_error_to_string(message->error)); + goto error; + } + + reply = g_ril_reply_parse_sim_io(sd->ril, message); + if (reply == NULL) { + ofono_error("Can't parse SIM IO response from RILD"); + goto error; + } + + if (reply->hex_len == 0) { + ofono_error("Null SIM IO response from RILD"); + g_ril_reply_free_sim_io(reply); + goto error; + } + + cb(&error, reply->hex_response, reply->hex_len, cbd->data); + + g_ril_reply_free_sim_io(reply); + + return; + +error: + decode_ril_error(&error, "FAIL"); + cb(&error, NULL, 0, cbd->data); +} + +static void ril_file_write_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_write_cb_t cb = cbd->cb; + struct sim_data *sd = cbd->user; + struct reply_sim_io *reply; + int sw1, sw2; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s: RILD reply failure: %s", + __func__, ril_error_to_string(message->error)); + goto error; + } + + reply = g_ril_reply_parse_sim_io(sd->ril, message); + if (reply == NULL) { + ofono_error("%s: Can't parse SIM IO response", __func__); + goto error; + } + + sw1 = reply->sw1; + sw2 = reply->sw2; + + g_ril_reply_free_sim_io(reply); + + if ((sw1 != 0x90 && sw1 != 0x91 && sw1 != 0x92 && sw1 != 0x9f) || + (sw1 == 0x90 && sw2 != 0x00)) { + struct ofono_error error; + + ofono_error("%s: error sw1 %02x sw2 %02x", __func__, sw1, sw2); + + error.type = OFONO_ERROR_TYPE_SIM; + error.error = (sw1 << 8) | sw2; + + cb(&error, cbd->data); + + return; + } + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + + return; + +error: + CALLBACK_WITH_FAILURE(cb, cbd->data); +} + +static void ril_sim_read_binary(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 sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + struct req_sim_read_binary req; + gint ret = 0; + + DBG("file %04x", fileid); + + req.app_type = sd->app_type; + req.aid_str = sd->aid_str; + req.fileid = fileid; + req.path = path; + req.path_len = path_len; + req.start = start; + req.length = length; + + if (!g_ril_request_sim_read_binary(sd->ril, + &req, + &rilp)) { + ofono_error("Couldn't build SIM read binary request"); + goto error; + } + + g_ril_append_print_buf(sd->ril, + "%s%d,%d,%d,(null),pin2=(null),aid=%s)", + print_buf, + (start >> 8), + (start & 0xff), + length, + sd->aid_str); + + ret = g_ril_send(sd->ril, RIL_REQUEST_SIM_IO, &rilp, + ril_file_io_cb, cbd, g_free); +error: + if (ret == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, 0, data); + } +} + +static void ril_sim_read_record(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 sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + struct req_sim_read_record req; + guint ret = 0; + + DBG("file %04x", fileid); + + req.app_type = sd->app_type; + req.aid_str = sd->aid_str; + req.fileid = fileid; + req.path = path; + req.path_len = path_len; + req.record = record; + req.length = length; + + if (!g_ril_request_sim_read_record(sd->ril, + &req, + &rilp)) { + ofono_error("Couldn't build SIM read record request"); + goto error; + } + + g_ril_append_print_buf(sd->ril, + "%s%d,%d,%d,(null),pin2=(null),aid=%s)", + print_buf, + record, + 4, + length, + sd->aid_str); + + ret = g_ril_send(sd->ril, RIL_REQUEST_SIM_IO, &rilp, + ril_file_io_cb, cbd, g_free); + +error: + if (ret == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, 0, data); + } +} + +static void ril_sim_update_binary(struct ofono_sim *sim, int fileid, + int start, int length, + const unsigned char *value, + const unsigned char *path, + unsigned int path_len, + ofono_sim_write_cb_t cb, void *data) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + struct req_sim_write_binary req; + guint ret = 0; + + DBG("file 0x%04x", fileid); + + req.app_type = sd->app_type; + req.aid_str = sd->aid_str; + req.fileid = fileid; + req.path = path; + req.path_len = path_len; + req.start = start; + req.length = length; + req.data = value; + + if (!g_ril_request_sim_write_binary(sd->ril, &req, &rilp)) { + ofono_error("%s: Couldn't build SIM write request", __func__); + goto error; + } + + ret = g_ril_send(sd->ril, RIL_REQUEST_SIM_IO, &rilp, + ril_file_write_cb, cbd, g_free); + +error: + if (ret == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void update_record(struct ofono_sim *sim, int fileid, + enum req_record_access_mode mode, + int record, int length, + const unsigned char *value, + const unsigned char *path, + unsigned int path_len, + ofono_sim_write_cb_t cb, void *data) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + struct req_sim_write_record req; + guint ret = 0; + + DBG("file 0x%04x", fileid); + + req.app_type = sd->app_type; + req.aid_str = sd->aid_str; + req.fileid = fileid; + req.path = path; + req.path_len = path_len; + req.mode = mode; + req.record = record; + req.length = length; + req.data = value; + + if (!g_ril_request_sim_write_record(sd->ril, &req, &rilp)) { + ofono_error("%s: Couldn't build SIM write request", __func__); + goto error; + } + + ret = g_ril_send(sd->ril, RIL_REQUEST_SIM_IO, &rilp, + ril_file_write_cb, cbd, g_free); + +error: + if (ret == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_sim_update_record(struct ofono_sim *sim, int fileid, + int record, int length, + const unsigned char *value, + const unsigned char *path, + unsigned int path_len, + ofono_sim_write_cb_t cb, void *data) +{ + update_record(sim, fileid, GRIL_REC_ACCESS_MODE_ABSOLUTE, record, + length, value, path, path_len, cb, data); +} + +static void ril_sim_update_cyclic(struct ofono_sim *sim, int fileid, + int length, const unsigned char *value, + const unsigned char *path, + unsigned int path_len, + ofono_sim_write_cb_t cb, void *data) +{ + /* Only mode valid for cyclic files is PREVIOUS */ + update_record(sim, fileid, GRIL_REC_ACCESS_MODE_PREVIOUS, 0, + length, value, path, path_len, cb, data); +} + +static void ril_imsi_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_imsi_cb_t cb = cbd->cb; + struct sim_data *sd = cbd->user; + struct ofono_error error; + gchar *imsi; + + if (message->error == RIL_E_SUCCESS) { + DBG("GET IMSI reply - OK"); + decode_ril_error(&error, "OK"); + } else { + ofono_error("Reply failure: %s", + ril_error_to_string(message->error)); + goto error; + } + + imsi = g_ril_reply_parse_imsi(sd->ril, message); + if (imsi == NULL) { + ofono_error("Error empty IMSI"); + goto error; + } + + cb(&error, imsi, cbd->data); + g_free(imsi); + + return; + +error: + decode_ril_error(&error, "FAIL"); + cb(&error, NULL, cbd->data); +} + +static void ril_read_imsi(struct ofono_sim *sim, ofono_sim_imsi_cb_t cb, + void *data) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + + g_ril_request_read_imsi(sd->ril, sd->aid_str, &rilp); + + if (g_ril_send(sd->ril, RIL_REQUEST_GET_IMSI, &rilp, + ril_imsi_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, data); + } +} + +static void configure_active_app(struct sim_data *sd, + struct reply_sim_app *app, + guint index) +{ + g_free(sd->aid_str); + g_free(sd->app_str); + sd->app_type = app->app_type; + sd->aid_str = g_strdup(app->aid_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 sim_status_cb(struct ril_msg *message, gpointer user_data) +{ + struct ofono_sim *sim = user_data; + struct sim_data *sd = ofono_sim_get_data(sim); + struct reply_sim_status *status; + guint search_index; + + status = g_ril_reply_parse_sim_status(sd->ril, message); + if (status == NULL) { + ofono_error("%s: Cannot parse SIM status reply", __func__); + return; + } + + DBG("SIM status is %u", status->card_state); + + if (status->card_state == RIL_CARDSTATE_PRESENT) + ofono_sim_inserted_notify(sim, TRUE); + else if (status && status->card_state == RIL_CARDSTATE_ABSENT) + ofono_sim_inserted_notify(sim, FALSE); + else + ofono_error("%s: bad SIM state (%u)", + __func__, status->card_state); + + if (status->card_state == RIL_CARDSTATE_PRESENT) { + /* + * TODO(CDMA): need some kind of logic + * to set the correct app_index + */ + search_index = status->gsm_umts_index; + if (search_index < status->num_apps) { + struct reply_sim_app *app = status->apps[search_index]; + + if (app->app_type != RIL_APPTYPE_UNKNOWN) { + /* + * We cache the current password state. Ideally + * this should be done by issuing a + * GET_SIM_STATUS request from + * ril_query_passwd_state, which is called by + * the core after sending a password, but + * unfortunately the response to GET_SIM_STATUS + * is not reliable in mako when sent just after + * sending the password. Some time is needed + * before the modem refreshes its internal + * state, and when it does it sends a + * SIM_STATUS_CHANGED event. In that moment we + * retrieve the status and this function is + * executed. We call __ofono_sim_recheck_pin as + * it is the only way to indicate the core to + * call query_passwd_state again. An option + * that can be explored in the future is wait + * before invoking core callback for send_passwd + * until we know the real password state. + */ + configure_active_app(sd, app, search_index); + DBG("passwd_state: %d", sd->passwd_state); + + /* + * Note: There doesn't seem to be any other way + * to force the core SIM code to recheck the + * PIN. This call causes the core to call this + * atom's query_passwd() function. + */ + __ofono_sim_recheck_pin(sim); + } + } + } + + g_ril_reply_free_sim_status(status); +} + +static void send_get_sim_status(struct ofono_sim *sim) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + + g_ril_send(sd->ril, RIL_REQUEST_GET_SIM_STATUS, NULL, + sim_status_cb, sim, NULL); +} + +static void ril_sim_status_changed(struct ril_msg *message, gpointer user_data) +{ + struct ofono_sim *sim = (struct ofono_sim *) user_data; + struct sim_data *sd = ofono_sim_get_data(sim); + + DBG(""); + + g_ril_print_unsol_no_args(sd->ril, message); + + send_get_sim_status(sim); +} + +static void inf_pin_retries_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_pin_retries_cb_t cb = cbd->cb; + struct sim_data *sd = cbd->user; + struct reply_oem_hook *reply = NULL; + int32_t *ret_data; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("Reply failure: %s", + ril_error_to_string(message->error)); + goto error; + } + + reply = g_ril_reply_oem_hook_raw(sd->ril, message); + if (reply == NULL) { + ofono_error("%s: parse error", __func__); + goto error; + } + + if (reply->length < 5 * (int) sizeof(int32_t)) { + ofono_error("%s: reply too small", __func__); + goto error; + } + + /* First integer is INF_RIL_REQUEST_OEM_GET_REMAIN_SIM_PIN_ATTEMPTS */ + ret_data = reply->data; + sd->retries[OFONO_SIM_PASSWORD_SIM_PIN] = *(++ret_data); + sd->retries[OFONO_SIM_PASSWORD_SIM_PIN2] = *(++ret_data); + sd->retries[OFONO_SIM_PASSWORD_SIM_PUK] = *(++ret_data); + sd->retries[OFONO_SIM_PASSWORD_SIM_PUK2] = *(++ret_data); + + g_ril_reply_free_oem_hook(reply); + CALLBACK_WITH_SUCCESS(cb, sd->retries, cbd->data); + + return; + +error: + g_ril_reply_free_oem_hook(reply); + CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); +} + +static void mtk_pin_retries_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_pin_retries_cb_t cb = cbd->cb; + struct sim_data *sd = cbd->user; + struct parcel_str_array *str_arr = NULL; + int pin[MTK_EPINC_NUM_PASSWD]; + int num_pin; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("Reply failure: %s", + ril_error_to_string(message->error)); + goto error; + } + + str_arr = g_ril_reply_oem_hook_strings(sd->ril, message); + if (str_arr == NULL || str_arr->num_str < 1) { + ofono_error("%s: parse error", __func__); + goto error; + } + + num_pin = sscanf(str_arr->str[0], "+EPINC:%d,%d,%d,%d", + &pin[0], &pin[1], &pin[2], &pin[3]); + + if (num_pin != MTK_EPINC_NUM_PASSWD) { + ofono_error("%s: failed parsing %s", __func__, str_arr->str[0]); + goto error; + } + + sd->retries[OFONO_SIM_PASSWORD_SIM_PIN] = pin[0]; + sd->retries[OFONO_SIM_PASSWORD_SIM_PIN2] = pin[1]; + sd->retries[OFONO_SIM_PASSWORD_SIM_PUK] = pin[2]; + sd->retries[OFONO_SIM_PASSWORD_SIM_PUK2] = pin[3]; + + parcel_free_str_array(str_arr); + CALLBACK_WITH_SUCCESS(cb, sd->retries, cbd->data); + return; + +error: + parcel_free_str_array(str_arr); + CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); +} + +static void ril_query_pin_retries(struct ofono_sim *sim, + ofono_sim_pin_retries_cb_t cb, + void *data) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + + DBG(""); + + if (sd->vendor == OFONO_RIL_VENDOR_INFINEON) { + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + int32_t oem_req = + INF_RIL_REQUEST_OEM_GET_REMAIN_SIM_PIN_ATTEMPTS; + + g_ril_request_oem_hook_raw(sd->ril, &oem_req, + sizeof(oem_req), &rilp); + + /* Send request to RIL */ + if (g_ril_send(sd->ril, RIL_REQUEST_OEM_HOOK_RAW, &rilp, + inf_pin_retries_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, data); + } + } else if (sd->vendor == OFONO_RIL_VENDOR_MTK) { + struct cb_data *cbd = cb_data_new(cb, data, sd); + struct parcel rilp; + const char *at_epinc[] = { "AT+EPINC", "+EPINC:" }; + + g_ril_request_oem_hook_strings(sd->ril, at_epinc, + G_N_ELEMENTS(at_epinc), &rilp); + + if (g_ril_send(sd->ril, RIL_REQUEST_OEM_HOOK_STRINGS, &rilp, + mtk_pin_retries_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, data); + } + } else { + CALLBACK_WITH_SUCCESS(cb, sd->retries, data); + } +} + +static void ril_query_passwd_state(struct ofono_sim *sim, + ofono_sim_passwd_cb_t cb, void *data) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + DBG("passwd_state %u", sd->passwd_state); + + if (sd->passwd_state == OFONO_SIM_PASSWORD_INVALID) + CALLBACK_WITH_FAILURE(cb, -1, data); + else + CALLBACK_WITH_SUCCESS(cb, sd->passwd_state, data); +} + +static void ril_pin_change_state_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sim_lock_unlock_cb_t cb = cbd->cb; + struct ofono_sim *sim = cbd->user; + struct sim_data *sd = ofono_sim_get_data(sim); + int *retries; + /* + * There is no reason to ask SIM status until + * unsolicited sim status change indication + * Looks like state does not change before that. + */ + + DBG("Enter password: type %d, result %d", + sd->passwd_type, message->error); + + retries = g_ril_reply_parse_retries(sd->ril, message, sd->passwd_type); + if (retries != NULL) { + memcpy(sd->retries, retries, sizeof(sd->retries)); + g_free(retries); + } + + /* TODO: re-factor to not use macro for FAILURE; + doesn't return error! */ + if (message->error == RIL_E_SUCCESS) { + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + CALLBACK_WITH_FAILURE(cb, cbd->data); + /* + * Refresh passwd_state (not needed if the unlock is + * successful, as an event will refresh the state in that case) + */ + send_get_sim_status(sim); + } +} + +static void ril_pin_send(struct ofono_sim *sim, const char *passwd, + ofono_sim_lock_unlock_cb_t cb, void *data) +{ + /* + * TODO: This function is supposed to enter the pending password, which + * might be also PIN2. So we must check the pending PIN in the future. + */ + + struct sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sim); + struct parcel rilp; + + sd->passwd_type = OFONO_SIM_PASSWORD_SIM_PIN; + + g_ril_request_pin_send(sd->ril, + passwd, + sd->aid_str, + &rilp); + + if (g_ril_send(sd->ril, RIL_REQUEST_ENTER_SIM_PIN, &rilp, + ril_pin_change_state_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void enter_pin_done(const struct ofono_error *error, void *data) +{ + struct change_state_cbd *csd = data; + struct sim_data *sd = ofono_sim_get_data(csd->sim); + + if (error->type != OFONO_ERROR_TYPE_NO_ERROR) { + ofono_error("%s: wrong password", __func__); + sd->unlock_pending = FALSE; + CALLBACK_WITH_FAILURE(csd->cb, csd->data); + } else { + ril_pin_change_state(csd->sim, csd->passwd_type, csd->enable, + csd->passwd, csd->cb, csd->data); + } + + g_free(csd); +} + +static void ril_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 sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd; + struct parcel rilp; + struct req_pin_change_state req; + int ret = 0; + + /* + * If we want to unlock a password that has not been entered yet, + * we enter it before trying to unlock. We need sd->unlock_pending as + * the password still has not yet been refreshed when this function is + * called from enter_pin_done(). + */ + if (ofono_sim_get_password_type(sim) == passwd_type + && enable == FALSE && sd->unlock_pending == FALSE) { + struct change_state_cbd *csd = g_malloc0(sizeof(*csd)); + csd->sim = sim; + csd->passwd_type = passwd_type; + csd->enable = enable; + csd->passwd = passwd; + csd->cb = cb; + csd->data = data; + sd->unlock_pending = TRUE; + + ril_pin_send(sim, passwd, enter_pin_done, csd); + + return; + } + + sd->unlock_pending = FALSE; + + cbd = cb_data_new(cb, data, sim); + + sd->passwd_type = passwd_type; + + req.aid_str = sd->aid_str; + req.passwd_type = passwd_type; + req.enable = enable; + req.passwd = passwd; + + if (!g_ril_request_pin_change_state(sd->ril, + &req, + &rilp)) { + ofono_error("Couldn't build pin change state request"); + goto error; + } + + ret = g_ril_send(sd->ril, RIL_REQUEST_SET_FACILITY_LOCK, &rilp, + ril_pin_change_state_cb, cbd, g_free); + +error: + if (ret == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_pin_send_puk(struct ofono_sim *sim, + const char *puk, const char *passwd, + ofono_sim_lock_unlock_cb_t cb, void *data) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sim); + struct parcel rilp; + + sd->passwd_type = OFONO_SIM_PASSWORD_SIM_PUK; + + g_ril_request_pin_send_puk(sd->ril, + puk, + passwd, + sd->aid_str, + &rilp); + + if (g_ril_send(sd->ril, RIL_REQUEST_ENTER_SIM_PUK, &rilp, + ril_pin_change_state_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void ril_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 sim_data *sd = ofono_sim_get_data(sim); + struct cb_data *cbd = cb_data_new(cb, data, sim); + struct parcel rilp; + int request = RIL_REQUEST_CHANGE_SIM_PIN; + + sd->passwd_type = passwd_type; + + g_ril_request_change_passwd(sd->ril, + old_passwd, + new_passwd, + sd->aid_str, + &rilp); + + if (passwd_type == OFONO_SIM_PASSWORD_SIM_PIN2) + request = RIL_REQUEST_CHANGE_SIM_PIN2; + + if (g_ril_send(sd->ril, request, &rilp, ril_pin_change_state_cb, + cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static gboolean listen_and_get_sim_status(gpointer user) +{ + struct ofono_sim *sim = user; + struct sim_data *sd = ofono_sim_get_data(sim); + + send_get_sim_status(sim); + + g_ril_register(sd->ril, RIL_UNSOL_RESPONSE_SIM_STATUS_CHANGED, + (GRilNotifyFunc) ril_sim_status_changed, sim); + + /* TODO: should we also register for RIL_UNSOL_SIM_REFRESH? */ + return FALSE; +} + +static gboolean ril_sim_register(gpointer user) +{ + struct ofono_sim *sim = user; + struct sim_data *sd = ofono_sim_get_data(sim); + + DBG(""); + + ofono_sim_register(sim); + + if (sd->ril_state_watch != NULL && + !ofono_sim_add_state_watch(sim, sd->ril_state_watch, + sd->modem, NULL)) + ofono_error("Error registering ril sim watch"); + + /* + * We use g_idle_add here to make sure that the presence of the SIM + * interface is signalled before signalling anything else from the said + * interface, as ofono_sim_register also uses g_idle_add. + */ + g_idle_add(listen_and_get_sim_status, sim); + + return FALSE; +} + +static int ril_sim_probe(struct ofono_sim *sim, unsigned int vendor, + void *data) +{ + struct ril_sim_data *ril_data = data; + GRil *ril = ril_data->gril; + struct sim_data *sd; + int i; + + sd = g_new0(struct sim_data, 1); + sd->ril = g_ril_clone(ril); + sd->vendor = vendor; + sd->aid_str = NULL; + sd->app_str = NULL; + sd->app_type = RIL_APPTYPE_UNKNOWN; + sd->passwd_state = OFONO_SIM_PASSWORD_NONE; + sd->passwd_type = OFONO_SIM_PASSWORD_NONE; + sd->modem = ril_data->modem; + sd->ril_state_watch = ril_data->ril_state_watch; + + for (i = 0; i < OFONO_SIM_PASSWORD_INVALID; i++) + sd->retries[i] = -1; + + ofono_sim_set_data(sim, sd); + + /* + * TODO: analyze if capability check is needed + * and/or timer should be adjusted. + * + * ofono_sim_register() needs to be called after the + * driver has been set in ofono_sim_create(), which + * calls this function. Most other drivers make some + * kind of capabilities query to the modem, and then + * call register in the callback; we use an idle event + * instead. + */ + g_idle_add(ril_sim_register, sim); + + return 0; +} + +static void ril_sim_remove(struct ofono_sim *sim) +{ + struct sim_data *sd = ofono_sim_get_data(sim); + + ofono_sim_set_data(sim, NULL); + + g_ril_unref(sd->ril); + g_free(sd->aid_str); + g_free(sd->app_str); + g_free(sd); +} + +static struct ofono_sim_driver driver = { + .name = RILMODEM, + .probe = ril_sim_probe, + .remove = ril_sim_remove, + .read_file_info = ril_sim_read_info, + .read_file_transparent = ril_sim_read_binary, + .read_file_linear = ril_sim_read_record, + .read_file_cyclic = ril_sim_read_record, + .write_file_transparent = ril_sim_update_binary, + .write_file_linear = ril_sim_update_record, + .write_file_cyclic = ril_sim_update_cyclic, + .read_imsi = ril_read_imsi, + .query_passwd_state = ril_query_passwd_state, + .send_passwd = ril_pin_send, + .query_pin_retries = ril_query_pin_retries, + .reset_passwd = ril_pin_send_puk, + .change_passwd = ril_change_passwd, + .lock = ril_pin_change_state, +/* + * TODO: Implmenting PIN/PUK support requires defining + * the following driver methods. + * + * In the meanwhile, as long as the SIM card is present, + * and unlocked, the core SIM code will check for the + * presence of query_passwd_state, and if null, then the + * function sim_initialize_after_pin() is called. + * + * .query_locked = ril_pin_query_enabled, + */ +}; + +void ril_sim_init(void) +{ + DBG(""); + ofono_sim_driver_register(&driver); +} + +void ril_sim_exit(void) +{ + ofono_sim_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/sms.c b/drivers/rilmodem/sms.c new file mode 100644 index 00000000..14816e5d --- /dev/null +++ b/drivers/rilmodem/sms.c @@ -0,0 +1,315 @@ +/* + * + * oFono - Open Source Telephony - RIL Modem Support + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012-2013 Canonical Ltd. + * Copyright (C) 2013 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include "smsutil.h" +#include "util.h" + +#include "rilmodem.h" +#include "grilrequest.h" +#include "grilreply.h" +#include "grilunsol.h" + +struct sms_data { + GRil *ril; + unsigned int vendor; +}; + +static void ril_csca_set_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sms_sca_set_cb_t cb = cbd->cb; + struct sms_data *sd = cbd->user; + + if (message->error == RIL_E_SUCCESS) { + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + ofono_error("%s RILD reply failure: %s", + g_ril_request_id_to_string(sd->ril, message->req), + ril_error_to_string(message->error)); + CALLBACK_WITH_FAILURE(cb, cbd->data); + } +} + +static void ril_csca_set(struct ofono_sms *sms, + const struct ofono_phone_number *sca, + ofono_sms_sca_set_cb_t cb, void *user_data) +{ + struct sms_data *sd = ofono_sms_get_data(sms); + struct cb_data *cbd = cb_data_new(cb, user_data, sd); + struct parcel rilp; + + g_ril_request_set_smsc_address(sd->ril, sca, &rilp); + + /* Send request to RIL */ + if (g_ril_send(sd->ril, RIL_REQUEST_SET_SMSC_ADDRESS, &rilp, + ril_csca_set_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, user_data); + } +} + +static void ril_csca_query_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + ofono_sms_sca_query_cb_t cb = cbd->cb; + struct sms_data *sd = cbd->user; + struct ofono_phone_number *sca; + + if (message->error != RIL_E_SUCCESS) { + ofono_error("%s RILD reply failure: %s", + g_ril_request_id_to_string(sd->ril, message->req), + ril_error_to_string(message->error)); + CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); + return; + } + + sca = g_ril_reply_parse_get_smsc_address(sd->ril, message); + if (sca == NULL) { + CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); + } else { + CALLBACK_WITH_SUCCESS(cb, sca, cbd->data); + g_free(sca); + } +} + +static void ril_csca_query(struct ofono_sms *sms, ofono_sms_sca_query_cb_t cb, + void *user_data) +{ + struct sms_data *sd = ofono_sms_get_data(sms); + struct cb_data *cbd = cb_data_new(cb, user_data, sd); + + DBG("Sending csca_query"); + + if (g_ril_send(sd->ril, RIL_REQUEST_GET_SMSC_ADDRESS, NULL, + ril_csca_query_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, NULL, user_data); + } +} + +static void ril_submit_sms_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_error error; + ofono_sms_submit_cb_t cb = cbd->cb; + struct sms_data *sd = cbd->user; + int mr = 0; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + mr = g_ril_reply_parse_sms_response(sd->ril, message); + } else { + decode_ril_error(&error, "FAIL"); + } + + cb(&error, mr, cbd->data); +} + +static void ril_cmgs(struct ofono_sms *sms, const unsigned char *pdu, + int pdu_len, int tpdu_len, int mms, + ofono_sms_submit_cb_t cb, void *user_data) +{ + struct sms_data *sd = ofono_sms_get_data(sms); + struct cb_data *cbd = cb_data_new(cb, user_data, sd); + struct parcel rilp; + struct req_sms_cmgs req; + + DBG("pdu_len: %d, tpdu_len: %d mms: %d", pdu_len, tpdu_len, mms); + + /* TODO: if (mms) { ... } */ + + req.pdu = pdu; + req.pdu_len = pdu_len; + req.tpdu_len = tpdu_len; + + g_ril_request_sms_cmgs(sd->ril, &req, &rilp); + + if (g_ril_send(sd->ril, RIL_REQUEST_SEND_SMS, &rilp, + ril_submit_sms_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, -1, user_data); + } +} + +static void ril_ack_delivery_cb(struct ril_msg *message, gpointer user_data) +{ + if (message->error != RIL_E_SUCCESS) + ofono_error("SMS acknowledgement failed: " + "Further SMS reception is not guaranteed"); +} + +static void ril_ack_delivery(struct ofono_sms *sms) +{ + struct sms_data *sd = ofono_sms_get_data(sms); + struct parcel rilp; + + g_ril_request_sms_acknowledge(sd->ril, &rilp); + + /* TODO: should ACK be sent for either of the error cases? */ + + /* ACK the incoming NEW_SMS */ + g_ril_send(sd->ril, RIL_REQUEST_SMS_ACKNOWLEDGE, &rilp, + ril_ack_delivery_cb, NULL, NULL); + +} + +static void ril_sms_notify(struct ril_msg *message, gpointer user_data) +{ + struct ofono_sms *sms = user_data; + struct sms_data *sd = ofono_sms_get_data(sms); + unsigned int smsc_len; + long ril_buf_len; + struct unsol_sms_data *pdu_data; + + DBG("req: %d; data_len: %d", message->req, (int) message->buf_len); + + pdu_data = g_ril_unsol_parse_new_sms(sd->ril, message); + if (pdu_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 = pdu_data->data[0] + 1; + ril_buf_len = pdu_data->length; + DBG("smsc_len is %d", smsc_len); + + if (message->req == RIL_UNSOL_RESPONSE_NEW_SMS) + /* Last parameter is 'tpdu_len' ( substract SMSC length ) */ + ofono_sms_deliver_notify(sms, pdu_data->data, + ril_buf_len, + ril_buf_len - smsc_len); + else if (message->req == RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT) + ofono_sms_status_notify(sms, pdu_data->data, ril_buf_len, + ril_buf_len - smsc_len); + + /* ACK the incoming NEW_SMS */ + ril_ack_delivery(sms); + + g_ril_unsol_free_sms_data(pdu_data); + +error: + ; +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_sms *sms = user_data; + struct sms_data *data = ofono_sms_get_data(sms); + + DBG(""); + ofono_sms_register(sms); + + /* register to receive INCOMING_SMS and SMS status reports */ + g_ril_register(data->ril, RIL_UNSOL_RESPONSE_NEW_SMS, + ril_sms_notify, sms); + g_ril_register(data->ril, RIL_UNSOL_RESPONSE_NEW_SMS_STATUS_REPORT, + ril_sms_notify, sms); + + /* This makes the delayed call a single-shot */ + return FALSE; +} + +static int ril_sms_probe(struct ofono_sms *sms, unsigned int vendor, + void *user) +{ + GRil *ril = user; + struct sms_data *data; + + data = g_new0(struct sms_data, 1); + data->ril = g_ril_clone(ril); + data->vendor = vendor; + + ofono_sms_set_data(sms, data); + + /* + * ofono_sms_register() needs to be called after + * the driver has been set in ofono_sms_create(), which + * calls this function. Most other drivers make some + * kind of capabilities query to the modem, and then + * call register in the callback; we use an idle add instead. + */ + g_idle_add(ril_delayed_register, sms); + + return 0; +} + +static void ril_sms_remove(struct ofono_sms *sms) +{ + struct sms_data *data = ofono_sms_get_data(sms); + + DBG(""); + + g_ril_unref(data->ril); + g_free(data); + + ofono_sms_set_data(sms, NULL); +} + +static struct ofono_sms_driver driver = { + .name = RILMODEM, + .probe = ril_sms_probe, + .sca_query = ril_csca_query, + .sca_set = ril_csca_set, + .remove = ril_sms_remove, + .submit = ril_cmgs, + + /* + * TODO: investigate/implement: + * .bearer_query = NULL, + * .bearer_set = NULL, + */ +}; + +void ril_sms_init(void) +{ + DBG(""); + if (ofono_sms_driver_register(&driver)) + DBG("ofono_sms_driver_register failed!"); +} + +void ril_sms_exit(void) +{ + DBG(""); + ofono_sms_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/ussd.c b/drivers/rilmodem/ussd.c new file mode 100644 index 00000000..04985ec7 --- /dev/null +++ b/drivers/rilmodem/ussd.c @@ -0,0 +1,264 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2013 Jolla Ltd + * Copyright (C) 2013 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "gril.h" +#include "grilutil.h" +#include "grilrequest.h" +#include "grilunsol.h" + +#include "rilmodem.h" + +#include "ril_constants.h" + +struct ussd_data { + GRil *ril; +}; + +static gboolean request_success(gpointer data) +{ + struct cb_data *cbd = data; + ofono_ussd_cb_t cb = cbd->cb; + + CALLBACK_WITH_SUCCESS(cb, cbd->data); + g_free(cbd); + + return FALSE; +} + +static void ril_ussd_cb(struct ril_msg *message, gpointer user_data) +{ + struct ofono_ussd *ussd = user_data; + struct ussd_data *ud = ofono_ussd_get_data(ussd); + + /* + * We fake an ON_USSD event if there was an error sending the request, + * as core will be waiting for one to respond to the Initiate() call. + * Note that we already made the callback (see ril_ussd_request()). + */ + if (message->error == RIL_E_SUCCESS) + g_ril_print_response_no_args(ud->ril, message); + else + ofono_ussd_notify(ussd, OFONO_USSD_STATUS_NOT_SUPPORTED, + 0, NULL, 0); +} + +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 ussd_data *ud = ofono_ussd_get_data(ussd); + struct cb_data *cbd = cb_data_new(cb, data, ussd); + enum sms_charset charset; + char *text = NULL; + int ret = 0; + + if (cbs_dcs_decode(dcs, NULL, NULL, &charset, NULL, NULL, NULL)) { + + if (charset == SMS_CHARSET_7BIT) { + long written; + + text = (char *) unpack_7bit(pdu, len, 0, TRUE, + 0, &written, 1); + if (text != NULL) + *(text + written) = '\0'; + + } else if (charset == SMS_CHARSET_UCS2) { + text = g_convert((char *) pdu, len, + "UTF-8//TRANSLIT", "UCS-2BE", + NULL, NULL, NULL); + } else { + ofono_error("%s: No support for charset %d", + __func__, charset); + } + } + + if (text) { + struct parcel rilp; + + g_ril_request_send_ussd(ud->ril, text, &rilp); + + ret = g_ril_send(ud->ril, RIL_REQUEST_SEND_USSD, + &rilp, ril_ussd_cb, ussd, NULL); + g_free(text); + } + + /* + * We do not wait for the SEND_USSD reply to do the callback, as some + * networks send it after sending one or more ON_USSD events. From the + * ofono core perspective, Initiate() does not return until one ON_USSD + * event is received: making here a successful callback just makes the + * core wait for that event. + */ + if (ret <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } else { + g_idle_add(request_success, cbd); + } +} + +static void ril_ussd_cancel_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_ussd *ussd = cbd->user; + struct ussd_data *ud = ofono_ussd_get_data(ussd); + ofono_ussd_cb_t cb = cbd->cb; + + if (message->error == RIL_E_SUCCESS) { + g_ril_print_response_no_args(ud->ril, message); + CALLBACK_WITH_SUCCESS(cb, cbd->data); + } else { + CALLBACK_WITH_FAILURE(cb, cbd->data); + } +} + +static void ril_ussd_cancel(struct ofono_ussd *ussd, + ofono_ussd_cb_t cb, void *user_data) +{ + struct ussd_data *ud = ofono_ussd_get_data(ussd); + struct cb_data *cbd = cb_data_new(cb, user_data, ussd); + int ret; + + ret = g_ril_send(ud->ril, RIL_REQUEST_CANCEL_USSD, NULL, + ril_ussd_cancel_cb, cbd, g_free); + + if (ret <= 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, user_data); + } +} + +static void ril_ussd_notify(struct ril_msg *message, gpointer user_data) +{ + struct ofono_ussd *ussd = user_data; + struct ussd_data *ud = ofono_ussd_get_data(ussd); + struct unsol_ussd *unsol; + + unsol = g_ril_unsol_parse_ussd(ud->ril, message); + if (unsol == NULL) { + ofono_error("%s: Parsing error", __func__); + return; + } + + /* To fix bug in MTK: USSD-Notify arrive with type 2 instead of 0 */ + if (g_ril_vendor(ud->ril) == OFONO_RIL_VENDOR_MTK && + unsol->message != NULL && unsol->type == 2) + unsol->type = 0; + + /* + * With data coding scheme 0x48, we are saying that the ussd string is a + * UCS-2 string, uncompressed, and with unspecified message class. For + * the DCS coding, see 3gpp 23.038, sect. 5. + */ + if (unsol->message != NULL) { + gsize written; + char *ucs2; + + ucs2 = g_convert(unsol->message, -1, "UCS-2BE//TRANSLIT", + "UTF-8", NULL, &written, NULL); + if (ucs2 != NULL) { + ofono_ussd_notify(ussd, unsol->type, 0x48, + (unsigned char *) ucs2, written); + g_free(ucs2); + } else { + ofono_error("%s: Error transcoding", __func__); + } + } else { + ofono_ussd_notify(ussd, unsol->type, 0, NULL, 0); + } + + g_ril_unsol_free_ussd(unsol); +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_ussd *ussd = user_data; + struct ussd_data *ud = ofono_ussd_get_data(ussd); + + DBG(""); + + ofono_ussd_register(ussd); + + /* Register for USSD responses */ + g_ril_register(ud->ril, RIL_UNSOL_ON_USSD, ril_ussd_notify, ussd); + + return FALSE; +} + +static int ril_ussd_probe(struct ofono_ussd *ussd, + unsigned int vendor, + void *user) +{ + GRil *ril = user; + struct ussd_data *ud = g_new0(struct ussd_data, 1); + + ud->ril = g_ril_clone(ril); + ofono_ussd_set_data(ussd, ud); + g_idle_add(ril_delayed_register, ussd); + + return 0; +} + +static void ril_ussd_remove(struct ofono_ussd *ussd) +{ + struct ussd_data *ud = ofono_ussd_get_data(ussd); + ofono_ussd_set_data(ussd, NULL); + + g_ril_unref(ud->ril); + g_free(ud); +} + +static struct ofono_ussd_driver driver = { + .name = RILMODEM, + .probe = ril_ussd_probe, + .remove = ril_ussd_remove, + .request = ril_ussd_request, + .cancel = ril_ussd_cancel +}; + +void ril_ussd_init(void) +{ + ofono_ussd_driver_register(&driver); +} + +void ril_ussd_exit(void) +{ + ofono_ussd_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/vendor.h b/drivers/rilmodem/vendor.h new file mode 100644 index 00000000..83cc939a --- /dev/null +++ b/drivers/rilmodem/vendor.h @@ -0,0 +1,32 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2014 Canonical Ltd. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef RILMODEM_VENDOR_H +#define RILMODEM_VENDOR_H + +enum ofono_ril_vendor { + OFONO_RIL_VENDOR_AOSP = 0, + OFONO_RIL_VENDOR_MTK, + OFONO_RIL_VENDOR_INFINEON, + OFONO_RIL_VENDOR_QCOM_MSIM +}; + +#endif /* RILMODEM_VENDOR_H */ diff --git a/drivers/rilmodem/voicecall.c b/drivers/rilmodem/voicecall.c new file mode 100644 index 00000000..3fa4b375 --- /dev/null +++ b/drivers/rilmodem/voicecall.c @@ -0,0 +1,824 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2012-2013 Canonical Ltd. + * Copyright (C) 2013 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include "gril.h" +#include "grilrequest.h" +#include "grilreply.h" +#include "grilunsol.h" + +#include "common.h" +#include "rilmodem.h" +#include "voicecall.h" + +/* Amount of ms we wait between CLCC calls */ +#define POLL_CLCC_INTERVAL 300 + +#define FLAG_NEED_CLIP 1 + +#define MAX_DTMF_BUFFER 32 + +/* To use with change_state_req::affected_types */ +#define AFFECTED_STATES_ALL 0x3F + +/* Auto-answer delay in seconds */ +#define AUTO_ANSWER_DELAY_S 3 + +struct release_id_req { + struct ofono_voicecall *vc; + ofono_voicecall_cb_t cb; + void *data; + int id; +}; + +struct change_state_req { + struct ofono_voicecall *vc; + ofono_voicecall_cb_t cb; + void *data; + /* Call states affected by a local release (1 << enum call_status) */ + int affected_types; +}; + +struct lastcause_req { + struct ofono_voicecall *vc; + int id; +}; + +/* Data for dial after swap */ +struct hold_before_dial_req { + struct ofono_voicecall *vc; + struct ofono_phone_number dial_ph; + enum ofono_clir_option dial_clir; +}; + +static void send_one_dtmf(struct ril_voicecall_data *vd); +static void clear_dtmf_queue(struct ril_voicecall_data *vd); + +static void lastcause_cb(struct ril_msg *message, gpointer user_data) +{ + struct lastcause_req *reqdata = user_data; + struct ofono_voicecall *vc = reqdata->vc; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + enum ofono_disconnect_reason reason; + + reason = g_ril_reply_parse_call_fail_cause(vd->ril, message); + + DBG("Call %d ended with reason %d", reqdata->id, reason); + + ofono_voicecall_disconnected(vc, reqdata->id, reason, NULL); +} + +static gboolean auto_answer_call(gpointer user_data) +{ + struct ofono_voicecall *vc = user_data; + + DBG(""); + + ril_answer(vc, NULL, NULL); + + return FALSE; +} + +static gboolean is_auto_answer(struct ril_voicecall_data *vd, + struct ofono_call *call) +{ + static const char test_mcc_mnc_1[] = "00101"; + static const char test_mcc_mnc_2[] = "001001"; + + const char *imsi; + struct ofono_sim *sim; + + if (call->status != CALL_STATUS_INCOMING) + return FALSE; + + sim = __ofono_atom_find(OFONO_ATOM_TYPE_SIM, vd->modem); + if (sim == NULL) + return FALSE; + + imsi = ofono_sim_get_imsi(sim); + if (imsi == NULL) + return FALSE; + + if (strncmp(imsi, test_mcc_mnc_1, sizeof(test_mcc_mnc_1) - 1) == 0 || + strncmp(imsi, test_mcc_mnc_2, sizeof(test_mcc_mnc_2) - 1) + == 0) { + ofono_info("Auto answering incoming call, imsi is %s", imsi); + return TRUE; + } + + return FALSE; +} + +static void clcc_poll_cb(struct ril_msg *message, gpointer user_data) +{ + struct ofono_voicecall *vc = user_data; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + int reqid = RIL_REQUEST_LAST_CALL_FAIL_CAUSE; + GSList *calls; + GSList *n, *o; + struct ofono_call *nc, *oc; + + /* + * We consider all calls have been dropped if there is no radio, which + * happens, for instance, when flight mode is set whilst in a call. + */ + if (message->error != RIL_E_SUCCESS && + message->error != RIL_E_RADIO_NOT_AVAILABLE) { + ofono_error("We are polling CLCC and received an error"); + ofono_error("All bets are off for call management"); + return; + } + + calls = g_ril_reply_parse_get_calls(vd->ril, message); + + n = calls; + o = vd->calls; + + while (n || o) { + nc = n ? n->data : NULL; + oc = o ? o->data : NULL; + + /* TODO: Add comments explaining call id handling */ + if (oc && (nc == NULL || (nc->id > oc->id))) { + if (vd->local_release & (1 << oc->id)) { + ofono_voicecall_disconnected(vc, oc->id, + OFONO_DISCONNECT_REASON_LOCAL_HANGUP, + NULL); + } else if (message->error == + RIL_E_RADIO_NOT_AVAILABLE) { + ofono_voicecall_disconnected(vc, oc->id, + OFONO_DISCONNECT_REASON_ERROR, + NULL); + } else { + /* Get disconnect cause before calling core */ + struct lastcause_req *reqdata = + g_try_new0(struct lastcause_req, 1); + if (reqdata != NULL) { + reqdata->vc = user_data; + reqdata->id = oc->id; + + g_ril_send(vd->ril, reqid, NULL, + lastcause_cb, reqdata, + g_free); + } + } + + 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(vc, nc); + + if (vd->cb) { + struct ofono_error error; + ofono_voicecall_cb_t cb = vd->cb; + decode_ril_error(&error, "OK"); + cb(&error, vd->data); + vd->cb = NULL; + vd->data = NULL; + } + + if (is_auto_answer(vd, nc)) + g_timeout_add_seconds( + AUTO_ANSWER_DELAY_S, + auto_answer_call, vc); + } + + 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(vc, nc); + + vd->flags &= ~FLAG_NEED_CLIP; + } else if (memcmp(nc, oc, sizeof(*nc)) && nc->type) + ofono_voicecall_notify(vc, nc); + + n = n->next; + o = o->next; + } + } + + g_slist_foreach(vd->calls, (GFunc) g_free, NULL); + g_slist_free(vd->calls); + + vd->calls = calls; + vd->local_release = 0; +} + +gboolean ril_poll_clcc(gpointer user_data) +{ + struct ofono_voicecall *vc = user_data; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + + g_ril_send(vd->ril, RIL_REQUEST_GET_CURRENT_CALLS, NULL, + clcc_poll_cb, vc, NULL); + + vd->clcc_source = 0; + + return FALSE; +} + +static void generic_cb(struct ril_msg *message, gpointer user_data) +{ + struct change_state_req *req = user_data; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(req->vc); + struct ofono_error error; + + if (message->error == RIL_E_SUCCESS) { + decode_ril_error(&error, "OK"); + } else { + decode_ril_error(&error, "FAIL"); + goto out; + } + + g_ril_print_response_no_args(vd->ril, message); + + if (req->affected_types) { + GSList *l; + struct ofono_call *call; + + for (l = vd->calls; l; l = l->next) { + call = l->data; + + if (req->affected_types & (1 << call->status)) + vd->local_release |= (1 << call->id); + } + } + +out: + g_ril_send(vd->ril, RIL_REQUEST_GET_CURRENT_CALLS, NULL, + clcc_poll_cb, req->vc, NULL); + + /* We have to callback after we schedule a poll if required */ + if (req->cb) + req->cb(&error, req->data); +} + +static int ril_template(const guint rreq, struct ofono_voicecall *vc, + GRilResponseFunc func, unsigned int affected_types, + gpointer pdata, ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + struct change_state_req *req = g_try_new0(struct change_state_req, 1); + int ret; + + if (req == NULL) + goto error; + + req->vc = vc; + req->cb = cb; + req->data = data; + req->affected_types = affected_types; + + ret = g_ril_send(vd->ril, rreq, pdata, func, req, g_free); + if (ret > 0) + return ret; +error: + g_free(req); + + if (cb) + CALLBACK_WITH_FAILURE(cb, data); + + return 0; +} + +static void rild_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct ofono_voicecall *vc = cbd->user; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + ofono_voicecall_cb_t cb = cbd->cb; + struct ofono_error error; + + /* + * DIAL_MODIFIED_TO_DIAL means redirection. The call we will see when + * polling will have a different called number. + */ + if (message->error == RIL_E_SUCCESS || + (g_ril_vendor(vd->ril) == OFONO_RIL_VENDOR_AOSP && + message->error == RIL_E_DIAL_MODIFIED_TO_DIAL)) { + decode_ril_error(&error, "OK"); + } else { + decode_ril_error(&error, "FAIL"); + goto out; + } + + g_ril_print_response_no_args(vd->ril, message); + + /* CLCC will update the oFono call list with proper ids */ + if (!vd->clcc_source) + vd->clcc_source = g_timeout_add(POLL_CLCC_INTERVAL, + ril_poll_clcc, vc); + + /* we cannot answer just yet since we don't know the call id */ + vd->cb = cb; + vd->data = cbd->data; + + return; + +out: + cb(&error, cbd->data); +} + +static void 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_data *vd = ofono_voicecall_get_data(vc); + struct cb_data *cbd = cb_data_new(cb, data, vc); + struct parcel rilp; + + g_ril_request_dial(vd->ril, ph, clir, &rilp); + + /* Send request to RIL */ + if (g_ril_send(vd->ril, RIL_REQUEST_DIAL, &rilp, + rild_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } +} + +static void hold_before_dial_cb(struct ril_msg *message, gpointer user_data) +{ + struct cb_data *cbd = user_data; + struct hold_before_dial_req *req = cbd->user; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(req->vc); + ofono_voicecall_cb_t cb = cbd->cb; + + if (message->error != RIL_E_SUCCESS) { + g_free(req); + CALLBACK_WITH_FAILURE(cb, cbd->data); + return; + } + + g_ril_print_response_no_args(vd->ril, message); + + /* Current calls held: we can dial now */ + dial(req->vc, &req->dial_ph, req->dial_clir, cb, cbd->data); + + g_free(req); +} + +void ril_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_data *vd = ofono_voicecall_get_data(vc); + int current_active = 0; + struct ofono_call *call; + GSList *l; + + /* Check for current active calls */ + for (l = vd->calls; l; l = l->next) { + call = l->data; + + if (call->status == CALL_STATUS_ACTIVE) { + current_active = 1; + break; + } + } + + /* + * The network will put current active calls on hold. In some cases + * (mako), the modem also updates properly the state. In others + * (maguro), we need to explicitly set the state to held. In both cases + * we send a request for holding the active call, as it is not harmful + * when it is not really needed, and is what Android does. + */ + if (current_active) { + struct hold_before_dial_req *req; + struct cb_data *cbd; + + req = g_malloc0(sizeof(*req)); + req->vc = vc; + req->dial_ph = *ph; + req->dial_clir = clir; + + cbd = cb_data_new(cb, data, req); + + if (g_ril_send(vd->ril, RIL_REQUEST_SWITCH_HOLDING_AND_ACTIVE, + NULL, hold_before_dial_cb, cbd, g_free) == 0) { + g_free(cbd); + CALLBACK_WITH_FAILURE(cb, data); + } + + } else { + dial(vc, ph, clir, cb, data); + } +} + +void ril_hangup_all(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, + void *data) +{ + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + struct parcel rilp; + struct ofono_error error; + struct ofono_call *call; + GSList *l; + + for (l = vd->calls; l; l = l->next) { + call = l->data; + + if (call->status == CALL_STATUS_INCOMING) { + /* + * Need to use this request so that declined + * calls in this state, are properly forwarded + * to voicemail. REQUEST_HANGUP doesn't do the + * right thing for some operators, causing the + * caller to hear a fast busy signal. + */ + ril_template(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, + vc, generic_cb, AFFECTED_STATES_ALL, + NULL, NULL, NULL); + } else { + + /* TODO: Hangup just the active ones once we have call + * state tracking (otherwise it can't handle ringing) */ + g_ril_request_hangup(vd->ril, call->id, &rilp); + + /* Send request to RIL */ + ril_template(RIL_REQUEST_HANGUP, vc, generic_cb, + AFFECTED_STATES_ALL, &rilp, NULL, NULL); + } + } + + /* TODO: Deal in case of an error at hungup */ + decode_ril_error(&error, "OK"); + cb(&error, data); +} + +void ril_hangup_specific(struct ofono_voicecall *vc, + int id, ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + struct parcel rilp; + + DBG("Hanging up call with id %d", id); + + g_ril_request_hangup(vd->ril, id, &rilp); + + /* Send request to RIL */ + ril_template(RIL_REQUEST_HANGUP, vc, generic_cb, + AFFECTED_STATES_ALL, &rilp, cb, data); +} + +void ril_call_state_notify(struct ril_msg *message, gpointer user_data) +{ + struct ofono_voicecall *vc = user_data; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + + g_ril_print_unsol_no_args(vd->ril, message); + + /* Just need to request the call list again */ + ril_poll_clcc(vc); + + return; +} + +static void ril_ss_notify(struct ril_msg *message, gpointer user_data) +{ + struct ofono_voicecall *vc = user_data; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + struct unsol_supp_svc_notif *unsol; + + unsol = g_ril_unsol_parse_supp_svc_notif(vd->ril, message); + if (unsol == NULL) { + ofono_error("%s: Parsing error", __func__); + return; + } + + DBG("RIL data: MT/MO: %i, code: %i, index: %i", + unsol->notif_type, unsol->code, unsol->index); + + /* 0 stands for MO intermediate, 1 for MT unsolicited */ + /* TODO How do we know the affected call? Refresh call list? */ + if (unsol->notif_type == 1) + ofono_voicecall_ssn_mt_notify( + vc, 0, unsol->code, unsol->index, &unsol->number); + else + ofono_voicecall_ssn_mo_notify(vc, 0, unsol->code, unsol->index); + + g_ril_unsol_free_supp_svc_notif(unsol); +} + +void ril_answer(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data) +{ + DBG("Answering current call"); + + /* Send request to RIL */ + ril_template(RIL_REQUEST_ANSWER, vc, generic_cb, 0, NULL, cb, data); +} + +static void ril_send_dtmf_cb(struct ril_msg *message, gpointer user_data) +{ + struct ril_voicecall_data *vd = user_data; + + if (message->error == RIL_E_SUCCESS) { + /* Remove sent DTMF character from queue */ + gchar *tmp_tone_queue = g_strdup(vd->tone_queue + 1); + int remaining = strlen(tmp_tone_queue); + + memcpy(vd->tone_queue, tmp_tone_queue, remaining); + vd->tone_queue[remaining] = '\0'; + g_free(tmp_tone_queue); + + vd->tone_pending = FALSE; + + if (remaining > 0) + send_one_dtmf(vd); + } else { + DBG("error=%d", message->error); + clear_dtmf_queue(vd); + } +} + +static void send_one_dtmf(struct ril_voicecall_data *vd) +{ + struct parcel rilp; + + if (vd->tone_pending == TRUE) + return; /* RIL request pending */ + + if (strlen(vd->tone_queue) == 0) + return; /* nothing to send */ + + g_ril_request_dtmf(vd->ril, vd->tone_queue[0], &rilp); + + g_ril_send(vd->ril, RIL_REQUEST_DTMF, &rilp, + ril_send_dtmf_cb, vd, NULL); + + vd->tone_pending = TRUE; +} + +void ril_send_dtmf(struct ofono_voicecall *vc, const char *dtmf, + ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall_data *vd = ofono_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); + send_one_dtmf(vd); + + /* We don't really care about errors here */ + decode_ril_error(&error, "OK"); + cb(&error, data); +} + +static void clear_dtmf_queue(struct ril_voicecall_data *vd) +{ + g_free(vd->tone_queue); + vd->tone_queue = g_strnfill(MAX_DTMF_BUFFER + 1, '\0'); + vd->tone_pending = FALSE; +} + +void ril_create_multiparty(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_template(RIL_REQUEST_CONFERENCE, vc, generic_cb, 0, NULL, cb, data); +} + +void ril_private_chat(struct ofono_voicecall *vc, int id, + ofono_voicecall_cb_t cb, void *data) +{ + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + struct parcel rilp; + + g_ril_request_separate_conn(vd->ril, id, &rilp); + + /* Send request to RIL */ + ril_template(RIL_REQUEST_SEPARATE_CONNECTION, vc, + generic_cb, 0, &rilp, cb, data); +} + +void ril_swap_without_accept(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_template(RIL_REQUEST_SWITCH_HOLDING_AND_ACTIVE, vc, + generic_cb, 0, NULL, cb, data); +} + +void ril_hold_all_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_template(RIL_REQUEST_SWITCH_HOLDING_AND_ACTIVE, vc, + generic_cb, 0, NULL, cb, data); +} + +void ril_release_all_held(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_template(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, vc, + generic_cb, 0, NULL, cb, data); +} + +void ril_release_all_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_template(RIL_REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND, vc, + generic_cb, 0, NULL, cb, data); +} + +void ril_set_udub(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data) +{ + ril_template(RIL_REQUEST_HANGUP_WAITING_OR_BACKGROUND, vc, + generic_cb, 0, NULL, cb, data); +} + +static gboolean enable_supp_svc(gpointer user_data) +{ + struct ofono_voicecall *vc = user_data; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + struct parcel rilp; + + g_ril_request_set_supp_svc_notif(vd->ril, &rilp); + + g_ril_send(vd->ril, RIL_REQUEST_SET_SUPP_SVC_NOTIFICATION, &rilp, + NULL, vc, NULL); + + /* Makes this a single shot */ + return FALSE; +} + +static gboolean ril_delayed_register(gpointer user_data) +{ + struct ofono_voicecall *vc = user_data; + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + ofono_voicecall_register(vc); + + /* Initialize call list */ + ril_poll_clcc(vc); + + /* Unsol when call state changes */ + g_ril_register(vd->ril, RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED, + ril_call_state_notify, vc); + + /* Unsol when call set on hold */ + g_ril_register(vd->ril, RIL_UNSOL_SUPP_SVC_NOTIFICATION, + ril_ss_notify, vc); + + /* request supplementary service notifications*/ + enable_supp_svc(vc); + + /* This makes the timeout a single-shot */ + return FALSE; +} + +void ril_voicecall_start(struct ril_voicecall_driver_data *driver_data, + struct ofono_voicecall *vc, + unsigned int vendor, + struct ril_voicecall_data *vd) +{ + vd->ril = g_ril_clone(driver_data->gril); + vd->modem = driver_data->modem; + vd->vendor = vendor; + vd->cb = NULL; + vd->data = NULL; + + clear_dtmf_queue(vd); + + ofono_voicecall_set_data(vc, vd); + + /* + * ofono_voicecall_register() needs to be called after + * the driver has been set in ofono_voicecall_create(), + * which calls this function. Most other drivers make + * some kind of capabilities query to the modem, and then + * call register in the callback; we use an idle event instead. + */ + g_idle_add(ril_delayed_register, vc); +} + +int ril_voicecall_probe(struct ofono_voicecall *vc, unsigned int vendor, + void *data) +{ + struct ril_voicecall_driver_data *driver_data = data; + struct ril_voicecall_data *vd; + + vd = g_try_new0(struct ril_voicecall_data, 1); + if (vd == NULL) + return -ENOMEM; + + ril_voicecall_start(driver_data, vc, vendor, vd); + + return 0; +} + +void ril_voicecall_remove(struct ofono_voicecall *vc) +{ + struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc); + + if (vd->clcc_source) + g_source_remove(vd->clcc_source); + + g_slist_foreach(vd->calls, (GFunc) g_free, NULL); + g_slist_free(vd->calls); + + ofono_voicecall_set_data(vc, NULL); + + g_ril_unref(vd->ril); + g_free(vd->tone_queue); + g_free(vd); +} + +static struct ofono_voicecall_driver driver = { + .name = RILMODEM, + .probe = ril_voicecall_probe, + .remove = ril_voicecall_remove, + .dial = ril_dial, + .answer = ril_answer, + .hangup_all = ril_hangup_all, + .release_specific = ril_hangup_specific, + .send_tones = ril_send_dtmf, + .create_multiparty = ril_create_multiparty, + .private_chat = ril_private_chat, + .swap_without_accept = ril_swap_without_accept, + .hold_all_active = ril_hold_all_active, + .release_all_held = ril_release_all_held, + .set_udub = ril_set_udub, + .release_all_active = ril_release_all_active, +}; + +void ril_voicecall_init(void) +{ + ofono_voicecall_driver_register(&driver); +} + +void ril_voicecall_exit(void) +{ + ofono_voicecall_driver_unregister(&driver); +} diff --git a/drivers/rilmodem/voicecall.h b/drivers/rilmodem/voicecall.h new file mode 100644 index 00000000..0407abeb --- /dev/null +++ b/drivers/rilmodem/voicecall.h @@ -0,0 +1,71 @@ +/* + * + * oFono - Open Source Telephony + * + * Copyright (C) 2014 Canonical 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct ril_voicecall_data { + GSList *calls; + /* Call local hangup indicator, one bit per call (1 << call_id) */ + unsigned int local_release; + unsigned int clcc_source; + GRil *ril; + struct ofono_modem *modem; + unsigned int vendor; + unsigned char flags; + ofono_voicecall_cb_t cb; + void *data; + gchar *tone_queue; + gboolean tone_pending; +}; + +int ril_voicecall_probe(struct ofono_voicecall *vc, unsigned int vendor, + void *data); +void ril_voicecall_remove(struct ofono_voicecall *vc); +void ril_dial(struct ofono_voicecall *vc, const struct ofono_phone_number *ph, + enum ofono_clir_option clir, ofono_voicecall_cb_t cb, + void *data); +void ril_answer(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data); +void ril_hangup_all(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, + void *data); +void ril_hangup_specific(struct ofono_voicecall *vc, + int id, ofono_voicecall_cb_t cb, void *data); +void ril_send_dtmf(struct ofono_voicecall *vc, const char *dtmf, + ofono_voicecall_cb_t cb, void *data); +void ril_create_multiparty(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data); +void ril_private_chat(struct ofono_voicecall *vc, int id, + ofono_voicecall_cb_t cb, void *data); +void ril_swap_without_accept(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data); +void ril_hold_all_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data); +void ril_release_all_held(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data); +void ril_set_udub(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data); +void ril_release_all_active(struct ofono_voicecall *vc, + ofono_voicecall_cb_t cb, void *data); + +void ril_voicecall_start(struct ril_voicecall_driver_data *driver_data, + struct ofono_voicecall *vc, + unsigned int vendor, + struct ril_voicecall_data *vd); +void ril_call_state_notify(struct ril_msg *message, gpointer user_data); +gboolean ril_poll_clcc(gpointer user_data);