/* * * oFono - Open Source Telephony * * Copyright (C) 2011 ST-Ericsson AB. * Copyright (C) 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 #define OFONO_API_SUBJECT_TO_CHANGE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "drivers/isimodem/isimodem.h" #include "drivers/isimodem/isiutil.h" #include "drivers/isimodem/mtc.h" #include "drivers/isimodem/debug.h" struct isi_data { char const *ifname; GIsiModem *modem; GIsiClient *client; GIsiPhonetNetlink *link; enum GIsiPhonetLinkState linkstate; unsigned interval; int reported; ofono_bool_t online; struct isi_cb_data *online_cbd; }; struct devinfo_data { GIsiClient *client; }; static gboolean check_response_status(const GIsiMessage *msg, uint8_t msgid) { if (g_isi_msg_error(msg) < 0) { DBG("Error: %s", strerror(-g_isi_msg_error(msg))); return FALSE; } if (g_isi_msg_id(msg) != msgid) { DBG("Unexpected msg: %s", mce_message_id_name(g_isi_msg_id(msg))); return FALSE; } return TRUE; } static void report_powered(struct ofono_modem *modem, struct isi_data *isi, ofono_bool_t powered) { if (powered == isi->reported) return; isi->reported = powered; ofono_modem_set_powered(modem, powered); } static void report_online(struct isi_data *isi, ofono_bool_t online) { struct isi_cb_data *cbd = isi->online_cbd; ofono_modem_online_cb_t cb = cbd->cb; isi->online_cbd = NULL; if (isi->online == online) CALLBACK_WITH_SUCCESS(cb, cbd->data); else CALLBACK_WITH_FAILURE(cb, cbd->data); g_free(cbd); } static void set_power_by_mce_state(struct ofono_modem *modem, struct isi_data *isi, int mce_state) { switch (mce_state) { case MCE_POWER_OFF: report_powered(modem, isi, FALSE); break; case MCE_NORMAL: if (isi->online_cbd) report_online(isi, mce_state == MCE_NORMAL); default: report_powered(modem, isi, TRUE); } } static void mce_state_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_modem *modem = data; struct isi_data *isi = ofono_modem_get_data(modem); uint8_t state; uint8_t action; if (isi == NULL || g_isi_msg_id(msg) != MCE_MODEM_STATE_IND) return; if (!g_isi_msg_data_get_byte(msg, 0, &state) || !g_isi_msg_data_get_byte(msg, 1, &action)) return; switch (action) { case MCE_START: DBG("target modem state: %s (0x%02X)", mce_modem_state_name(state), state); break; case MCE_READY: DBG("current modem state: %s (0x%02X)", mce_modem_state_name(state), state); set_power_by_mce_state(modem, isi, state); break; default: break; } } static void mce_rf_state_ind_cb(const GIsiMessage *msg, void *data) { struct ofono_modem *modem = data; struct isi_data *isi = ofono_modem_get_data(modem); uint8_t state; uint8_t action; if (isi == NULL || g_isi_msg_id(msg) != MCE_RF_STATE_IND) return; if (!g_isi_msg_data_get_byte(msg, 0, &state) || !g_isi_msg_data_get_byte(msg, 1, &action)) return; switch (action) { case MCE_READY: DBG("current rf state: %s (0x%02X)", mce_rf_state_name(state), state); if (isi->online_cbd) report_online(isi, state); break; case MCE_START: default: break; } } static void mce_query_cb(const GIsiMessage *msg, void *data) { struct ofono_modem *modem = data; struct isi_data *isi = ofono_modem_get_data(modem); uint8_t current; uint8_t target; if (!check_response_status(msg, MCE_MODEM_STATE_QUERY_RESP)) return; if (!g_isi_msg_data_get_byte(msg, 0, ¤t) || !g_isi_msg_data_get_byte(msg, 1, &target)) return; DBG("Modem state: current=%s (0x%02X) target=%s (0x%02X)", mce_modem_state_name(current), current, mce_modem_state_name(target), target); if (current == target) set_power_by_mce_state(modem, isi, current); } static gboolean bootstrap_current_state(gpointer user) { struct ofono_modem *om = user; struct isi_data *isi = ofono_modem_get_data(om); const uint8_t req[] = { MCE_MODEM_STATE_QUERY_REQ, 0x00, 0x00 /* Filler */ }; size_t len = sizeof(req); g_isi_client_send(isi->client, req, len, mce_query_cb, om, NULL); return FALSE; } static void reachable_cb(const GIsiMessage *msg, void *data) { struct ofono_modem *om = data; struct isi_data *isi = ofono_modem_get_data(om); if (g_isi_msg_error(msg) < 0) return; ISI_RESOURCE_DBG(msg); g_isi_client_ind_subscribe(isi->client, MCE_MODEM_STATE_IND, mce_state_ind_cb, om); g_isi_client_ind_subscribe(isi->client, MCE_RF_STATE_IND, mce_rf_state_ind_cb, om); /* * FIXME: There is a theoretical race condition here: * g_isi_client_ind_subscribe() adds the actual message * sending for committing changes to subscriptions in idle * loop, which may or may not preserve ordering. Thus, we * might miss a state indication if the bootstrap request ends * up being sent first. */ g_idle_add(bootstrap_current_state, om); } static void phonet_status_cb(GIsiModem *modem, enum GIsiPhonetLinkState st, char const *ifname, void *data) { struct ofono_modem *om = data; struct isi_data *isi = ofono_modem_get_data(om); DBG("Link %s (%u) is %s", isi->ifname, g_isi_modem_index(isi->modem), st == PN_LINK_REMOVED ? "removed" : st == PN_LINK_DOWN ? "down" : "up"); isi->linkstate = st; if (st == PN_LINK_UP) g_isi_client_verify(isi->client, reachable_cb, om, NULL); else if (st == PN_LINK_DOWN) set_power_by_mce_state(om, isi, MCE_POWER_OFF); } static int u8500_probe(struct ofono_modem *modem) { const char *ifname = ofono_modem_get_string(modem, "Interface"); unsigned address = ofono_modem_get_integer(modem, "Address"); GIsiModem *isimodem; GIsiClient *client = NULL; GIsiPhonetNetlink *link = NULL; struct isi_data *isi = NULL; if (ifname == NULL) return -EINVAL; DBG("(%p) with %s", modem, ifname); isimodem = g_isi_modem_create_by_name(ifname); if (isimodem == NULL) { DBG("Interface=%s: %s", ifname, strerror(errno)); return -errno; } g_isi_modem_set_userdata(isimodem, modem); if (getenv("OFONO_ISI_DEBUG")) g_isi_modem_set_debug(isimodem, ofono_debug); if (getenv("OFONO_ISI_TRACE")) g_isi_modem_set_trace(isimodem, isi_trace); if (g_isi_pn_netlink_by_modem(isimodem)) { DBG("%s: %s", ifname, strerror(EBUSY)); errno = EBUSY; goto error; } link = g_isi_pn_netlink_start(isimodem, phonet_status_cb, modem); if (link == NULL) { DBG("%s: %s", ifname, strerror(errno)); goto error; } if (address) { int error = g_isi_pn_netlink_set_address(isimodem, address); if (error && error != -EEXIST) { DBG("g_isi_pn_netlink_set_address(): %s\n", strerror(-error)); errno = -error; goto error; } } isi = g_try_new0(struct isi_data, 1); if (isi == NULL) { errno = ENOMEM; goto error; } client = g_isi_client_create(isimodem, PN_MODEM_MCE); if (!client) goto error; g_isi_modem_set_device(isimodem, PN_DEV_MODEM); isi->modem = isimodem; isi->ifname = ifname; isi->link = link; isi->reported = -1; isi->client = client; ofono_modem_set_data(modem, isi); return 0; error: g_isi_pn_netlink_stop(link); g_isi_client_destroy(client); g_isi_modem_destroy(isimodem); g_free(isi); return -errno; } static void u8500_remove(struct ofono_modem *modem) { struct isi_data *isi = ofono_modem_get_data(modem); ofono_modem_set_data(modem, NULL); if (isi == NULL) return; g_isi_pn_netlink_stop(isi->link); g_isi_client_destroy(isi->client); g_isi_modem_destroy(isi->modem); g_free(isi); } static void mce_state_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; struct ofono_modem *modem = cbd->user; ofono_modem_online_cb_t cb = cbd->cb; struct isi_data *isi = ofono_modem_get_data(modem); uint8_t cause; if (!check_response_status(msg, MCE_RF_STATE_RESP)) goto error; if (!g_isi_msg_data_get_byte(msg, 0, &cause)) goto error; DBG("MCE cause: %s (0x%02X)", mce_status_info(cause), cause); if (cause == MCE_OK) { isi->online_cbd = cbd; return; } if (cause == MCE_ALREADY_ACTIVE) { CALLBACK_WITH_SUCCESS(cb, cbd->data); g_free(cbd); return; } error: CALLBACK_WITH_FAILURE(cb, cbd->data); g_free(cbd); } static void u8500_online(struct ofono_modem *modem, ofono_bool_t online, ofono_modem_online_cb_t cb, void *data) { struct isi_data *isi = ofono_modem_get_data(modem); struct isi_cb_data *cbd = isi_cb_data_new(modem, cb, data); const uint8_t req[] = { MCE_RF_STATE_REQ, online ? MCE_RF_ON : MCE_RF_OFF, 0x00 }; DBG("(%p) with %s", modem, isi->ifname); if (cbd == NULL || isi == NULL) goto error; if (g_isi_client_send_with_timeout(isi->client, req, sizeof(req), MTC_STATE_REQ_TIMEOUT, mce_state_cb, cbd, NULL)) { isi->online = online; return; } error: CALLBACK_WITH_FAILURE(cb, data); g_free(cbd); } static void u8500_pre_sim(struct ofono_modem *modem) { struct isi_data *isi = ofono_modem_get_data(modem); DBG("(%p) with %s", modem, isi->ifname); ofono_sim_create(modem, 0, "wgmodem2.5", isi->modem); ofono_devinfo_create(modem, 0, "u8500", isi->modem); ofono_voicecall_create(modem, 0, "isimodem", isi->modem); } static void u8500_post_sim(struct ofono_modem *modem) { struct isi_data *isi = ofono_modem_get_data(modem); DBG("(%p) with %s", modem, isi->ifname); ofono_phonebook_create(modem, 0, "isimodem", isi->modem); ofono_call_forwarding_create(modem, 0, "isimodem", isi->modem); ofono_radio_settings_create(modem, 0, "isimodem", isi->modem); } static void u8500_post_online(struct ofono_modem *modem) { struct isi_data *isi = ofono_modem_get_data(modem); DBG("(%p) with %s", modem, isi->ifname); ofono_netreg_create(modem, 0, "isimodem", isi->modem); ofono_sms_create(modem, 0, "isimodem", isi->modem); ofono_cbs_create(modem, 0, "isimodem", isi->modem); ofono_ussd_create(modem, 0, "isimodem", isi->modem); ofono_call_settings_create(modem, 0, "isimodem", isi->modem); ofono_call_barring_create(modem, 0, "isimodem", isi->modem); ofono_call_meter_create(modem, 0, "isimodem", isi->modem); ofono_gprs_create(modem, 0, "isimodem", isi->modem); } static int u8500_enable(struct ofono_modem *modem) { return 0; } static int u8500_disable(struct ofono_modem *modem) { return 0; } static void u8500_info_resp_cb(const GIsiMessage *msg, void *data) { struct isi_cb_data *cbd = data; ofono_devinfo_query_cb_t cb = cbd->cb; GIsiSubBlockIter iter; uint8_t msgid; uint8_t status; msgid = g_isi_msg_id(msg); if (msgid != INFO_SERIAL_NUMBER_READ_RESP) goto error; if (g_isi_msg_error(msg) < 0) goto error; if (!g_isi_msg_data_get_byte(msg, 0, &status)) goto error; if (status != INFO_OK) goto error; for (g_isi_sb_iter_init(&iter, msg, 2); g_isi_sb_iter_is_valid(&iter); g_isi_sb_iter_next(&iter)) { uint8_t id = g_isi_sb_iter_get_id(&iter); uint8_t chars; char *info = NULL; if (id != INFO_SB_PRODUCT_INFO_MANUFACTURER && id != INFO_SB_PRODUCT_INFO_NAME && id != INFO_SB_MCUSW_VERSION && id != INFO_SB_SN_IMEI_PLAIN && id != INFO_SB_MODEMSW_VERSION) continue; if (g_isi_sb_iter_get_len(&iter) < 5) goto error; if (!g_isi_sb_iter_get_byte(&iter, &chars, 3)) goto error; if (!g_isi_sb_iter_get_latin_tag(&iter, &info, chars, 4)) goto error; CALLBACK_WITH_SUCCESS(cb, info, cbd->data); g_free(info); return; } error: CALLBACK_WITH_FAILURE(cb, "", cbd->data); } static void u8500_devinfo_reachable_cb(const GIsiMessage *msg, void *data) { struct ofono_devinfo *info = data; if (g_isi_msg_error(msg) < 0) return; ISI_RESOURCE_DBG(msg); ofono_devinfo_register(info); } static void u8500_query_manufacturer(struct ofono_devinfo *info, ofono_devinfo_query_cb_t cb, void *data) { CALLBACK_WITH_FAILURE(cb, "", data); } static void u8500_query_model(struct ofono_devinfo *info, ofono_devinfo_query_cb_t cb, void *data) { CALLBACK_WITH_FAILURE(cb, "", data); } static void u8500_query_revision(struct ofono_devinfo *info, ofono_devinfo_query_cb_t cb, void *data) { struct devinfo_data *dev = ofono_devinfo_get_data(info); struct isi_cb_data *cbd = isi_cb_data_new(dev, cb, data); const unsigned char msg[] = { INFO_SERIAL_NUMBER_READ_REQ, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, /* M_INFO_MODEMSW */ 0x00, 0x00 }; DBG(""); if (cbd == NULL || dev == NULL) goto error; if (g_isi_client_send(dev->client, msg, sizeof(msg), u8500_info_resp_cb, cbd, g_free)) return; error: CALLBACK_WITH_FAILURE(cb, "", data); g_free(cbd); } static void u8500_query_serial(struct ofono_devinfo *info, ofono_devinfo_query_cb_t cb, void *data) { char imei[16]; /* IMEI 15 digits + 1 null*/ char numbers[] = "1234567890"; FILE *fp = fopen("/etc/imei", "r"); DBG(""); if (fp == NULL) { DBG("failed to open /etc/imei file"); goto error; } if (fgets(imei, 16, fp)) { DBG(" IMEI = %s", imei); if (15 == strspn(imei, numbers)) CALLBACK_WITH_SUCCESS(cb, imei, data); else { CALLBACK_WITH_FAILURE(cb, "", data); fclose(fp); goto error; } } fclose(fp); return; error: CALLBACK_WITH_FAILURE(cb, "", data); } static int u8500_devinfo_probe(struct ofono_devinfo *info, unsigned int vendor, void *user) { GIsiModem *idx = user; struct devinfo_data *data = g_try_new0(struct devinfo_data, 1); if (data == NULL) return -ENOMEM; data->client = g_isi_client_create(idx, PN_MODEM_INFO); if (data->client == NULL) goto nomem; ofono_devinfo_set_data(info, data); g_isi_client_set_timeout(data->client, INFO_TIMEOUT); g_isi_client_verify(data->client, u8500_devinfo_reachable_cb, info, NULL); return 0; nomem: g_isi_client_destroy(data->client); g_free(data); return -ENOMEM; } static void u8500_devinfo_remove(struct ofono_devinfo *info) { struct devinfo_data *data = ofono_devinfo_get_data(info); ofono_devinfo_set_data(info, NULL); if (data == NULL) return; g_isi_client_destroy(data->client); g_free(data); } static struct ofono_modem_driver driver = { .name = "u8500", .probe = u8500_probe, .remove = u8500_remove, .set_online = u8500_online, .pre_sim = u8500_pre_sim, .post_sim = u8500_post_sim, .post_online = u8500_post_online, .enable = u8500_enable, .disable = u8500_disable, }; static struct ofono_devinfo_driver devinfo_driver = { .name = "u8500", .probe = u8500_devinfo_probe, .remove = u8500_devinfo_remove, .query_manufacturer = u8500_query_manufacturer, .query_model = u8500_query_model, .query_revision = u8500_query_revision, .query_serial = u8500_query_serial }; static int u8500_init(void) { int err; err = ofono_modem_driver_register(&driver); if (err < 0) return err; ofono_devinfo_driver_register(&devinfo_driver); return 0; } static void u8500_exit(void) { ofono_devinfo_driver_unregister(&devinfo_driver); ofono_modem_driver_unregister(&driver); } OFONO_PLUGIN_DEFINE(u8500, "ST-Ericsson U8500 modem driver", VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT, u8500_init, u8500_exit)