From 8c590a85c22897c2b9e68a6ef563dbb21f96250c Mon Sep 17 00:00:00 2001 From: Marcel Holtmann Date: Fri, 22 Jun 2012 13:20:11 -0700 Subject: [PATCH] qmimodem: Add support for SMS handling --- Makefile.am | 2 + drivers/qmimodem/qmimodem.c | 2 + drivers/qmimodem/qmimodem.h | 3 + drivers/qmimodem/sms.c | 536 ++++++++++++++++++++++++++++++++++++ drivers/qmimodem/wms.h | 126 +++++++++ 5 files changed, 669 insertions(+) create mode 100644 drivers/qmimodem/sms.c create mode 100644 drivers/qmimodem/wms.h diff --git a/Makefile.am b/Makefile.am index 17a3acb0..bad83f19 100644 --- a/Makefile.am +++ b/Makefile.am @@ -156,6 +156,7 @@ qmi_sources = drivers/qmimodem/qmi.h drivers/qmimodem/qmi.c \ drivers/qmimodem/ctl.h \ drivers/qmimodem/dms.h \ drivers/qmimodem/nas.h \ + drivers/qmimodem/wms.h \ drivers/qmimodem/wds.h \ drivers/qmimodem/pds.h @@ -167,6 +168,7 @@ builtin_sources += $(qmi_sources) \ drivers/qmimodem/devinfo.c \ drivers/qmimodem/network-registration.c \ drivers/qmimodem/sim-legacy.c \ + drivers/qmimodem/sms.c \ drivers/qmimodem/gprs.c \ drivers/qmimodem/gprs-context.c \ drivers/qmimodem/location-reporting.c diff --git a/drivers/qmimodem/qmimodem.c b/drivers/qmimodem/qmimodem.c index 77f98e17..3118006f 100644 --- a/drivers/qmimodem/qmimodem.c +++ b/drivers/qmimodem/qmimodem.c @@ -33,6 +33,7 @@ static int qmimodem_init(void) qmi_devinfo_init(); qmi_netreg_init(); qmi_sim_legacy_init(); + qmi_sms_init(); qmi_gprs_init(); qmi_gprs_context_init(); qmi_location_reporting_init(); @@ -45,6 +46,7 @@ static void qmimodem_exit(void) qmi_location_reporting_exit(); qmi_gprs_context_exit(); qmi_gprs_exit(); + qmi_sms_exit(); qmi_sim_legacy_exit(); qmi_netreg_exit(); qmi_devinfo_exit(); diff --git a/drivers/qmimodem/qmimodem.h b/drivers/qmimodem/qmimodem.h index 5a54c6cc..09e8bfe7 100644 --- a/drivers/qmimodem/qmimodem.h +++ b/drivers/qmimodem/qmimodem.h @@ -30,6 +30,9 @@ extern void qmi_netreg_exit(void); extern void qmi_sim_legacy_init(void); extern void qmi_sim_legacy_exit(void); +extern void qmi_sms_init(void); +extern void qmi_sms_exit(void); + extern void qmi_gprs_init(void); extern void qmi_gprs_exit(void); diff --git a/drivers/qmimodem/sms.c b/drivers/qmimodem/sms.c new file mode 100644 index 00000000..6459d7f7 --- /dev/null +++ b/drivers/qmimodem/sms.c @@ -0,0 +1,536 @@ +/* + * + * 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->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->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 event_notify(struct qmi_result *result, void *user_data) +{ + struct ofono_sms *sms = user_data; + 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 len; + + len = 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, len); + + ofono_sms_deliver_notify(sms, message->msg_data, len, len); + } +} + +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_NV; + 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 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); +} diff --git a/drivers/qmimodem/wms.h b/drivers/qmimodem/wms.h new file mode 100644 index 00000000..dae86c17 --- /dev/null +++ b/drivers/qmimodem/wms.h @@ -0,0 +1,126 @@ +/* + * + * 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 + * + */ + +#define QMI_WMS_RESET 0 /* Reset WMS service */ +#define QMI_WMS_EVENT 1 /* New message indication */ +#define QMI_WMS_SET_EVENT 1 /* Set new message conditions */ + +#define QMI_WMS_RAW_SEND 32 /* Send a raw message */ + +#define QMI_WMS_GET_MSG_LIST 49 /* Get list of messages from the device */ +#define QMI_WMS_SET_ROUTES 50 /* Set routes for message memory storage */ +#define QMI_WMS_GET_ROUTES 51 /* Get routes for message memory storage */ +#define QMI_WMS_GET_SMSC_ADDR 52 /* Get SMSC address */ +#define QMI_WMS_SET_SMSC_ADDR 53 /* Set SMSC address */ +#define QMI_WMS_GET_MSG_LIST_MAX 54 /* Get maximum size of SMS storage */ + +#define QMI_WMS_GET_DOMAIN_PREF 64 /* Get domain preference */ +#define QMI_WMS_SET_DOMAIN_PREF 65 /* Set domain preference */ + + +/* New message indication */ +#define QMI_WMS_RESULT_NEW_MSG_NOTIFY 0x10 +struct qmi_wms_result_new_msg_notify { + uint8_t storage_type; + uint32_t storage_index; +} __attribute__((__packed__)); + +/* Set new message conditions */ +#define QMI_WMS_PARAM_NEW_MSG_REPORT 0x10 /* bool */ + +/* Send a raw message */ +#define QMI_WMS_PARAM_MESSAGE 0x01 +struct qmi_wms_param_message { + uint8_t msg_format; + uint16_t msg_length; + uint8_t msg_data[0]; +} __attribute__((__packed__)); +#define QMI_WMS_RESULT_MESSAGE_ID 0x01 /* uint16 */ + +/* Get list of messages from the device */ +#define QMI_WMS_PARAM_STORAGE_TYPE 0x01 /* uint8 */ +#define QMI_WMS_PARAM_MESSAGE_MODE 0x11 /* uint8 */ + +#define QMI_WMS_STORAGE_TYPE_UIM 0 +#define QMI_WMS_STORAGE_TYPE_NV 1 +#define QMI_WMS_STORAGE_TYPE_UNKNOWN 2 + +#define QMI_WMS_MESSAGE_MODE_GSMWCDMA 1 + +/* Get routes for message memory storage */ +#define QMI_WMS_RESULT_ROUTE_LIST 0x01 +#define QMI_WMS_PARAM_ROUTE_LIST 0x01 +struct qmi_wms_route_list { + uint16_t count; + struct { + uint8_t msg_type; + uint8_t msg_class; + uint8_t storage_type; + uint8_t action; + } __attribute__((__packed__)) route[0]; +} __attribute__((__packed__)); +#define QMI_WMS_RESULT_STATUS_REPORT 0x10 /* bool */ +#define QMI_WMS_PARAM_STATUS_REPORT 0x10 /* bool */ +#define QMI_WMS_RESULT_MESSAGE 0x11 +struct qmi_wms_result_message { + uint8_t ack_required; /* bool */ + uint32_t transaction_id; + uint8_t msg_format; + uint16_t msg_length; + uint8_t msg_data[0]; +} __attribute__((__packed__)); + +#define QMI_WMS_MSG_TYPE_P2P 0x00 +#define QMI_WMS_MSG_TYPE_BROADCAST 0x01 + +#define QMI_WMS_MSG_CLASS_0 0x00 +#define QMI_WMS_MSG_CLASS_1 0x01 +#define QMI_WMS_MSG_CLASS_2 0x02 +#define QMI_WMS_MSG_CLASS_3 0x03 +#define QMI_WMS_MSG_CLASS_NONE 0x04 +#define QMI_WMS_MSG_CLASS_CDMA 0x05 + +#define QMI_WMS_ACTION_DISCARD 0x00 +#define QMI_WMS_ACTION_STORE_AND_NOTIFY 0x01 +#define QMI_WMS_ACTION_TRANSFER_ONLY 0x02 +#define QMI_WMS_ACTION_TRANSFER_AND_ACK 0x03 +#define QMI_WMS_ACTION_UNKNOWN 0xff + +/* Get SMSC address */ +#define QMI_WMS_RESULT_SMSC_ADDR 0x01 +struct qmi_wms_result_smsc_addr { + char type[3]; + uint8_t addr_len; + char addr[0]; +} __attribute__((__packed__)); + +/* Set SMSC address */ +#define QMI_WMS_PARAM_SMSC_ADDR 0x01 /* string */ +#define QMI_WMS_PARAM_SMSC_ADDR_TYPE 0x10 /* string */ + +/* Get domain preference */ +#define QMI_WMS_RESULT_DOMAIN 0x01 /* uint8 */ +#define QMI_WMS_PARAM_DOMAIN 0x01 /* uint8 */ + +#define QMI_WMS_DOMAIN_CS_PREFERRED 0x00 +#define QMI_WMS_DOMAIN_PS_PREFERRED 0x01 +#define QMI_WMS_DOMAIN_CS_ONLY 0x02 +#define QMI_WMS_DOMAIN_PS_ONLY 0x03