/* * * oFono - Open Source Telephony * * Copyright (C) 2011-2012 Intel Corporation. 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 #include #include #include #include "qmi.h" #include "wms.h" #include "qmimodem.h" struct sms_data { struct qmi_service *wms; uint16_t major; uint16_t minor; }; static void get_smsc_addr_cb(struct qmi_result *result, void *user_data) { struct cb_data *cbd = user_data; ofono_sms_sca_query_cb_t cb = cbd->cb; struct ofono_phone_number sca; const struct qmi_wms_result_smsc_addr *smsc; uint16_t len; DBG(""); if (qmi_result_set_error(result, NULL)) { CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); return; } smsc = qmi_result_get(result, QMI_WMS_RESULT_SMSC_ADDR, &len); if (!smsc) { CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); return; } if (!smsc->addr_len) { CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); return; } if (smsc->addr[0] == '+') { strncpy(sca.number, smsc->addr + 1, smsc->addr_len - 1); sca.number[smsc->addr_len - 1] = '\0'; sca.type = 145; } else { strncpy(sca.number, smsc->addr, smsc->addr_len); sca.number[smsc->addr_len] = '\0'; sca.type = 129; } CALLBACK_WITH_SUCCESS(cb, &sca, cbd->data); } static void qmi_sca_query(struct ofono_sms *sms, ofono_sms_sca_query_cb_t cb, void *user_data) { struct sms_data *data = ofono_sms_get_data(sms); struct cb_data *cbd = cb_data_new(cb, user_data); DBG(""); if (qmi_service_send(data->wms, QMI_WMS_GET_SMSC_ADDR, NULL, get_smsc_addr_cb, cbd, g_free) > 0) return; CALLBACK_WITH_FAILURE(cb, NULL, cbd->data); g_free(cbd); } static void set_smsc_addr_cb(struct qmi_result *result, void *user_data) { struct cb_data *cbd = user_data; ofono_sms_sca_set_cb_t cb = cbd->cb; DBG(""); if (qmi_result_set_error(result, NULL)) { CALLBACK_WITH_FAILURE(cb, cbd->data); return; } CALLBACK_WITH_SUCCESS(cb, cbd->data); } static void qmi_sca_set(struct ofono_sms *sms, const struct ofono_phone_number *sca, ofono_sms_sca_set_cb_t cb, void *user_data) { struct sms_data *data = ofono_sms_get_data(sms); struct cb_data *cbd = cb_data_new(cb, user_data); char type[4], number[OFONO_MAX_PHONE_NUMBER_LENGTH + 2]; struct qmi_param *param; DBG("type %d name %s", sca->type, sca->number); switch (sca->type) { case 129: snprintf(number, sizeof(number), "%s", sca->number); break; case 145: snprintf(number, sizeof(number), "+%s", sca->number); break; default: goto error; } snprintf(type, sizeof(type), "%d", sca->type); param = qmi_param_new(); if (!param) goto error; qmi_param_append(param, QMI_WMS_PARAM_SMSC_ADDR, strlen(number), number); qmi_param_append(param, QMI_WMS_PARAM_SMSC_ADDR_TYPE, strlen(type), type); if (qmi_service_send(data->wms, QMI_WMS_SET_SMSC_ADDR, param, set_smsc_addr_cb, cbd, g_free) > 0) return; qmi_param_free(param); error: CALLBACK_WITH_FAILURE(cb, cbd->data); g_free(cbd); } static void raw_send_cb(struct qmi_result *result, void *user_data) { struct cb_data *cbd = user_data; ofono_sms_submit_cb_t cb = cbd->cb; uint16_t msgid; DBG(""); if (qmi_result_set_error(result, NULL)) { CALLBACK_WITH_FAILURE(cb, -1, cbd->data); return; } if (!qmi_result_get_uint16(result, QMI_WMS_RESULT_MESSAGE_ID, &msgid)) { CALLBACK_WITH_FAILURE(cb, -1, cbd->data); return; } CALLBACK_WITH_SUCCESS(cb, msgid, cbd->data); } static void qmi_submit(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 *data = ofono_sms_get_data(sms); struct cb_data *cbd = cb_data_new(cb, user_data); struct qmi_wms_param_message *message; struct qmi_param *param; DBG("pdu_len %d tpdu_len %d mms %d", pdu_len, tpdu_len, mms); message = alloca(3 + pdu_len); message->msg_format = 0x06; message->msg_length = GUINT16_TO_LE(pdu_len); memcpy(message->msg_data, pdu, pdu_len); param = qmi_param_new(); if (!param) goto error; qmi_param_append(param, QMI_WMS_PARAM_MESSAGE, 3 + pdu_len, message); if (qmi_service_send(data->wms, QMI_WMS_RAW_SEND, param, raw_send_cb, cbd, g_free) > 0) return; qmi_param_free(param); error: CALLBACK_WITH_FAILURE(cb, -1, cbd->data); g_free(cbd); } static int domain_to_bearer(uint8_t domain) { switch (domain) { case QMI_WMS_DOMAIN_CS_PREFERRED: return 3; case QMI_WMS_DOMAIN_PS_PREFERRED: return 2; case QMI_WMS_DOMAIN_CS_ONLY: return 1; case QMI_WMS_DOMAIN_PS_ONLY: return 0; } return -1; } static uint8_t bearer_to_domain(int bearer) { switch (bearer) { case 0: return QMI_WMS_DOMAIN_PS_ONLY; case 1: return QMI_WMS_DOMAIN_CS_ONLY; case 2: return QMI_WMS_DOMAIN_PS_PREFERRED; case 3: return QMI_WMS_DOMAIN_CS_PREFERRED; } return QMI_WMS_DOMAIN_CS_PREFERRED; } static void get_domain_pref_cb(struct qmi_result *result, void *user_data) { struct cb_data *cbd = user_data; ofono_sms_bearer_query_cb_t cb = cbd->cb; uint8_t domain; int bearer; DBG(""); if (qmi_result_set_error(result, NULL)) { CALLBACK_WITH_FAILURE(cb, -1, cbd->data); return; } if (!qmi_result_get_uint8(result, QMI_WMS_RESULT_DOMAIN, &domain)) { CALLBACK_WITH_FAILURE(cb, -1, cbd->data); return; } bearer = domain_to_bearer(domain); CALLBACK_WITH_SUCCESS(cb, bearer, cbd->data); } static void qmi_bearer_query(struct ofono_sms *sms, ofono_sms_bearer_query_cb_t cb, void *user_data) { struct sms_data *data = ofono_sms_get_data(sms); struct cb_data *cbd = cb_data_new(cb, user_data); DBG(""); if (data->major < 1 || (data->major == 1 && data->minor < 2)) goto error; if (qmi_service_send(data->wms, QMI_WMS_GET_DOMAIN_PREF, NULL, get_domain_pref_cb, cbd, g_free) > 0) return; error: CALLBACK_WITH_FAILURE(cb, -1, cbd->data); g_free(cbd); } static void set_domain_pref_cb(struct qmi_result *result, void *user_data) { struct cb_data *cbd = user_data; ofono_sms_bearer_set_cb_t cb = cbd->cb; DBG(""); if (qmi_result_set_error(result, NULL)) { CALLBACK_WITH_FAILURE(cb, cbd->data); return; } CALLBACK_WITH_SUCCESS(cb, cbd->data); } static void qmi_bearer_set(struct ofono_sms *sms, int bearer, ofono_sms_bearer_set_cb_t cb, void *user_data) { struct sms_data *data = ofono_sms_get_data(sms); struct cb_data *cbd = cb_data_new(cb, user_data); struct qmi_param *param; uint8_t domain; DBG("bearer %d", bearer); if (data->major < 1 || (data->major == 1 && data->minor < 2)) goto error; domain = bearer_to_domain(bearer); param = qmi_param_new_uint8(QMI_WMS_PARAM_DOMAIN, domain); if (!param) goto error; if (qmi_service_send(data->wms, QMI_WMS_SET_DOMAIN_PREF, param, set_domain_pref_cb, cbd, g_free) > 0) return; error: CALLBACK_WITH_FAILURE(cb, cbd->data); g_free(cbd); } static void raw_read_cb(struct qmi_result *result, void *user_data) { struct ofono_sms *sms = user_data; const struct qmi_wms_raw_message* msg; uint16_t len; uint16_t error; if (qmi_result_set_error(result, &error)) { DBG("Raw read error: %d (%s)", error, qmi_result_get_error(result)); return; } /* Raw message data */ msg = qmi_result_get(result, 0x01, &len); if (msg) { uint16_t plen; uint16_t tpdu_len; plen = GUINT16_FROM_LE(msg->msg_length); tpdu_len = plen - msg->msg_data[0] - 1; ofono_sms_deliver_notify(sms, msg->msg_data, plen, tpdu_len); } else { DBG("No message data available at requested position"); } } static void event_notify(struct qmi_result *result, void *user_data) { struct ofono_sms *sms = user_data; struct sms_data *data = ofono_sms_get_data(sms); const struct qmi_wms_result_new_msg_notify *notify; const struct qmi_wms_result_message *message; uint16_t len; DBG(""); notify = qmi_result_get(result, QMI_WMS_RESULT_NEW_MSG_NOTIFY, &len); if (notify) { DBG("storage type %d index %d", notify->storage_type, GUINT32_FROM_LE(notify->storage_index)); } message = qmi_result_get(result, QMI_WMS_RESULT_MESSAGE, &len); if (message) { uint16_t plen; plen = GUINT16_FROM_LE(message->msg_length); DBG("ack_required %d transaction id %u", message->ack_required, GUINT32_FROM_LE(message->transaction_id)); DBG("msg format %d PDU length %d", message->msg_format, plen); ofono_sms_deliver_notify(sms, message->msg_data, plen, plen); } else { /* The Quectel EC21, at least, does not provide the * message data in the event notification, so a 'raw read' * needs to be issued in order to query the message itself */ struct qmi_param *param; param = qmi_param_new(); if (!param) return; /* Message memory storage ID */ qmi_param_append(param, 0x01, sizeof(*notify), notify); /* The 'message mode' parameter is documented as optional, * but the Quectel EC21 errors out with error 17 (missing * argument) if it is not provided... we default to 3GPP * here because that's what works for me and it's not clear * how to actually query what this should be otherwise... */ /* Message mode */ qmi_param_append_uint8(param, 0x10, QMI_WMS_MESSAGE_MODE_GSMWCDMA); if (qmi_service_send(data->wms, QMI_WMS_RAW_READ, param, raw_read_cb, sms, NULL) > 0) return; qmi_param_free(param); } } static void set_routes_cb(struct qmi_result *result, void *user_data) { struct ofono_sms *sms = user_data; DBG(""); ofono_sms_register(sms); } static void get_routes_cb(struct qmi_result *result, void *user_data) { struct ofono_sms *sms = user_data; struct sms_data *data = ofono_sms_get_data(sms); const struct qmi_wms_route_list *list; struct qmi_wms_route_list *new_list; struct qmi_param *param; uint16_t len, num, i; uint8_t value; DBG(""); if (qmi_result_set_error(result, NULL)) goto done; list = qmi_result_get(result, QMI_WMS_RESULT_ROUTE_LIST, &len); if (!list) goto done; num = GUINT16_FROM_LE(list->count); DBG("found %d routes", num); for (i = 0; i < num; i++) DBG("type %d class %d => type %d value %d", list->route[i].msg_type, list->route[i].msg_class, list->route[i].storage_type, list->route[i].action); if (qmi_result_get_uint8(result, QMI_WMS_RESULT_STATUS_REPORT, &value)) DBG("transfer status report %d", value); len = 2 + (1 * 4); new_list = alloca(len); new_list->count = GUINT16_TO_LE(1); new_list->route[0].msg_type = QMI_WMS_MSG_TYPE_P2P; new_list->route[0].msg_class = QMI_WMS_MSG_CLASS_NONE; new_list->route[0].storage_type = QMI_WMS_STORAGE_TYPE_NONE; new_list->route[0].action = QMI_WMS_ACTION_TRANSFER_AND_ACK; param = qmi_param_new(); if (!param) goto done; qmi_param_append(param, QMI_WMS_PARAM_ROUTE_LIST, len, new_list); qmi_param_append_uint8(param, QMI_WMS_PARAM_STATUS_REPORT, 0x01); if (qmi_service_send(data->wms, QMI_WMS_SET_ROUTES, param, set_routes_cb, sms, NULL) > 0) return; qmi_param_free(param); done: ofono_sms_register(sms); } static void set_event_cb(struct qmi_result *result, void *user_data) { struct ofono_sms *sms = user_data; struct sms_data *data = ofono_sms_get_data(sms); DBG(""); if (qmi_service_send(data->wms, QMI_WMS_GET_ROUTES, NULL, get_routes_cb, sms, NULL) > 0) return; ofono_sms_register(sms); } static void create_wms_cb(struct qmi_service *service, void *user_data) { struct ofono_sms *sms = user_data; struct sms_data *data = ofono_sms_get_data(sms); struct qmi_param *param; DBG(""); if (!service) { ofono_error("Failed to request WMS service"); ofono_sms_remove(sms); return; } if (!qmi_service_get_version(service, &data->major, &data->minor)) { ofono_error("Failed to get WMS service version"); ofono_sms_remove(sms); return; } data->wms = qmi_service_ref(service); qmi_service_register(data->wms, QMI_WMS_EVENT, event_notify, sms, NULL); param = qmi_param_new_uint8(QMI_WMS_PARAM_NEW_MSG_REPORT, 0x01); if (!param) goto done; if (qmi_service_send(data->wms, QMI_WMS_SET_EVENT, param, set_event_cb, sms, NULL) > 0) return; done: ofono_sms_register(sms); } static int qmi_sms_probe(struct ofono_sms *sms, unsigned int vendor, void *user_data) { struct qmi_device *device = user_data; struct sms_data *data; DBG(""); data = g_new0(struct sms_data, 1); ofono_sms_set_data(sms, data); qmi_service_create(device, QMI_SERVICE_WMS, create_wms_cb, sms, NULL); return 0; } static void qmi_sms_remove(struct ofono_sms *sms) { struct sms_data *data = ofono_sms_get_data(sms); DBG(""); ofono_sms_set_data(sms, NULL); qmi_service_unregister_all(data->wms); qmi_service_unref(data->wms); g_free(data); } static const struct ofono_sms_driver driver = { .name = "qmimodem", .probe = qmi_sms_probe, .remove = qmi_sms_remove, .sca_query = qmi_sca_query, .sca_set = qmi_sca_set, .submit = qmi_submit, .bearer_query = qmi_bearer_query, .bearer_set = qmi_bearer_set, }; void qmi_sms_init(void) { ofono_sms_driver_register(&driver); } void qmi_sms_exit(void) { ofono_sms_driver_unregister(&driver); }