/* * * oFono - Open Source Telephony * * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies). * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "isimodem.h" #include "isiutil.h" #include "debug.h" #include "gpds.h" #include "gss.h" #include "network.h" struct radio_data { GIsiClient *gss_client; GIsiClient *gpds_client; GIsiClient *wran_client; uint16_t wran_object; uint16_t quick_release:1; }; static enum ofono_radio_access_mode isi_mode_to_ofono_mode(guint8 mode) { switch (mode) { case GSS_DUAL_RAT: return OFONO_RADIO_ACCESS_MODE_ANY; case GSS_GSM_RAT: return OFONO_RADIO_ACCESS_MODE_GSM; case GSS_UMTS_RAT: return OFONO_RADIO_ACCESS_MODE_UMTS; default: return -1; } } static int ofono_mode_to_isi_mode(enum ofono_radio_access_mode mode) { switch (mode) { case OFONO_RADIO_ACCESS_MODE_ANY: return GSS_DUAL_RAT; case OFONO_RADIO_ACCESS_MODE_GSM: return GSS_GSM_RAT; case OFONO_RADIO_ACCESS_MODE_UMTS: return GSS_UMTS_RAT; default: return -1; } } static void rat_mode_read_resp_cb(const GIsiMessage *msg, void *opaque) { struct isi_cb_data *cbd = opaque; ofono_radio_settings_rat_mode_query_cb_t cb = cbd->cb; int mode = -1; GIsiSubBlockIter iter; if (g_isi_msg_error(msg) < 0) { DBG("message error"); goto error; } if (g_isi_msg_id(msg) == GSS_CS_SERVICE_FAIL_RESP) goto error; if (g_isi_msg_id(msg) != GSS_CS_SERVICE_RESP) return; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { switch (g_isi_sb_iter_get_id(&iter)) { case GSS_RAT_INFO: { guint8 info; if (!g_isi_sb_iter_get_byte(&iter, &info, 2)) goto error; mode = isi_mode_to_ofono_mode(info); break; } default: DBG("Skipping sub-block: %s (%zu bytes)", gss_subblock_name( g_isi_sb_iter_get_id(&iter)), g_isi_sb_iter_get_len(&iter)); break; } } CALLBACK_WITH_SUCCESS(cb, mode, cbd->data); g_free(cbd); return; error: CALLBACK_WITH_FAILURE(cb, -1, cbd->data); g_free(cbd); return; } static void isi_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 isi_cb_data *cbd = isi_cb_data_new(rd, cb, data); const unsigned char msg[] = { GSS_CS_SERVICE_REQ, GSS_SELECTED_RAT_READ, 0x00 /* subblock count */ }; if (cbd == NULL || rd == NULL) goto error; if (g_isi_client_send(rd->gss_client, msg, sizeof(msg), rat_mode_read_resp_cb, cbd, NULL)) return; error: CALLBACK_WITH_FAILURE(cb, -1, data); g_free(cbd); } static void mode_write_resp_cb(const GIsiMessage *msg, void *opaque) { struct isi_cb_data *cbd = opaque; ofono_radio_settings_rat_mode_set_cb_t cb = cbd->cb; if (g_isi_msg_error(msg) < 0) { DBG("message error"); goto error; } if (g_isi_msg_id(msg) == GSS_CS_SERVICE_FAIL_RESP) goto error; if (g_isi_msg_id(msg) != GSS_CS_SERVICE_RESP) return; CALLBACK_WITH_SUCCESS(cb, cbd->data); g_free(cbd); return; error: CALLBACK_WITH_FAILURE(cb, cbd->data); g_free(cbd); return; } static void isi_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 isi_cb_data *cbd = isi_cb_data_new(rd, cb, data); int isi_mode = ofono_mode_to_isi_mode(mode); const unsigned char msg[] = { GSS_CS_SERVICE_REQ, GSS_SELECTED_RAT_WRITE, 0x01, /* subblock count */ GSS_RAT_INFO, 0x04, /* subblock length */ isi_mode, 0x00 /* filler */ }; if (cbd == NULL || rd == NULL) goto error; if (isi_mode == -1) goto error; if (g_isi_client_send(rd->gss_client, msg, sizeof(msg), mode_write_resp_cb, cbd, NULL)) return; error: CALLBACK_WITH_FAILURE(cb, data); g_free(cbd); } static void update_fast_dormancy(struct radio_data *rd) { GIsiModem *modem; struct sockaddr_pn dst = { .spn_family = AF_PHONET, .spn_resource = 0x3a, .spn_dev = rd->wran_object >> 8, .spn_obj = rd->wran_object & 0xff, }; if (!rd->wran_object) return; modem = g_isi_client_modem(rd->wran_client); if (rd->quick_release) { const unsigned char msg[] = { 0x00, 0x1f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 }; g_isi_modem_sendto(modem, &dst, msg, sizeof(msg)); } else { const unsigned char msg[] = { 0x00, 0x1f, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x0a, 0x00, 0x00 }; g_isi_modem_sendto(modem, &dst, msg, sizeof(msg)); } DBG("3G PS quick release %s", rd->quick_release ? "enabled" : "disabled"); } static void gpds_context_activating_ind_cb(const GIsiMessage *msg, void *opaque) { struct radio_data *rd = opaque; update_fast_dormancy(rd); } static void isi_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->quick_release, data); } static void isi_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); rd->quick_release = enable; update_fast_dormancy(rd); CALLBACK_WITH_SUCCESS(cb, data); } static void wran_reachable_cb(const GIsiMessage *msg, void *opaque) { struct radio_data *rd = opaque; if (g_isi_msg_error(msg) < 0) return; ISI_RESOURCE_DBG(msg); rd->wran_object = g_isi_msg_object(msg); DBG("PN_WRAN object = 0x%04x", rd->wran_object); update_fast_dormancy(rd); g_isi_client_ind_subscribe(rd->gpds_client, GPDS_CONTEXT_ACTIVATING_IND, gpds_context_activating_ind_cb, rd); } static void gss_reachable_cb(const GIsiMessage *msg, void *opaque) { struct ofono_radio_settings *rs = opaque; if (g_isi_msg_error(msg) < 0) { ofono_radio_settings_remove(rs); return; } ISI_RESOURCE_DBG(msg); ofono_radio_settings_register(rs); } static int isi_radio_settings_probe(struct ofono_radio_settings *rs, unsigned int vendor, void *user) { GIsiModem *modem = user; struct radio_data *rd = g_try_new0(struct radio_data, 1); if (rd == NULL) return -ENOMEM; rd->gss_client = g_isi_client_create(modem, PN_GSS); if (rd->gss_client == NULL) goto nomem; rd->gpds_client = g_isi_client_create(modem, PN_GPDS); if (rd->gpds_client == NULL) goto nomem; rd->wran_client = g_isi_client_create(modem, PN_WRAN); if (rd->wran_client == NULL) goto nomem; ofono_radio_settings_set_data(rs, rd); g_isi_client_verify(rd->gss_client, gss_reachable_cb, rs, NULL); g_isi_client_verify(rd->wran_client, wran_reachable_cb, rd, NULL); return 0; nomem: g_isi_client_destroy(rd->gss_client); g_isi_client_destroy(rd->wran_client); g_isi_client_destroy(rd->gpds_client); g_free(rd); return -ENOMEM; } static void isi_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); if (rd == NULL) return; g_isi_client_destroy(rd->gss_client); g_isi_client_destroy(rd->wran_client); g_isi_client_destroy(rd->gpds_client); g_free(rd); } static const struct ofono_radio_settings_driver driver = { .name = "isimodem", .probe = isi_radio_settings_probe, .remove = isi_radio_settings_remove, .query_rat_mode = isi_query_rat_mode, .set_rat_mode = isi_set_rat_mode, .query_fast_dormancy = isi_query_fast_dormancy, .set_fast_dormancy = isi_set_fast_dormancy, }; void isi_radio_settings_init(void) { ofono_radio_settings_driver_register(&driver); } void isi_radio_settings_exit(void) { ofono_radio_settings_driver_unregister(&driver); }