blob: f1fd1f12ec18fb41dbd532f194a9cff70a7c0165 [file] [log] [blame]
/*
*
* 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 <config.h>
#endif
#include <string.h>
#include <arpa/inet.h>
#include <ofono/log.h>
#include <ofono/modem.h>
#include <ofono/gprs-context.h>
#include "qmi.h"
#include "wda.h"
#include "wds.h"
#include "qmimodem.h"
struct gprs_context_data {
struct qmi_service *wds;
struct qmi_service *wda;
struct qmi_device *dev;
unsigned int active_context;
uint32_t pkt_handle;
};
static void pkt_status_notify(struct qmi_result *result, void *user_data)
{
struct ofono_gprs_context *gc = user_data;
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
const struct qmi_wds_notify_conn_status *status;
uint16_t len;
uint8_t ip_family;
DBG("");
status = qmi_result_get(result, QMI_WDS_NOTIFY_CONN_STATUS, &len);
if (!status)
return;
DBG("conn status %d", status->status);
if (qmi_result_get_uint8(result, QMI_WDS_NOTIFY_IP_FAMILY, &ip_family))
DBG("ip family %d", ip_family);
switch (status->status) {
case QMI_WDS_CONN_STATUS_DISCONNECTED:
if (data->pkt_handle) {
/* The context has been disconnected by the network */
ofono_gprs_context_deactivated(gc, data->active_context);
data->pkt_handle = 0;
data->active_context = 0;
}
break;
}
}
static void get_settings_cb(struct qmi_result *result, void *user_data)
{
struct cb_data *cbd = user_data;
ofono_gprs_context_cb_t cb = cbd->cb;
struct ofono_gprs_context *gc = cbd->user;
struct ofono_modem *modem;
const char *interface;
uint8_t pdp_type, ip_family;
uint32_t ip_addr;
struct in_addr addr;
char* straddr;
char* apn;
const char *dns[3] = { NULL, NULL, NULL };
char dns_buf[2][INET_ADDRSTRLEN];
DBG("");
if (qmi_result_set_error(result, NULL))
goto done;
apn = qmi_result_get_string(result, QMI_WDS_RESULT_APN);
if (apn) {
DBG("APN: %s", apn);
g_free(apn);
}
if (qmi_result_get_uint8(result, QMI_WDS_RESULT_PDP_TYPE, &pdp_type))
DBG("PDP type %d", pdp_type);
if (qmi_result_get_uint8(result, QMI_WDS_RESULT_IP_FAMILY, &ip_family))
DBG("IP family %d", ip_family);
if (qmi_result_get_uint32(result,QMI_WDS_RESULT_IP_ADDRESS, &ip_addr)) {
addr.s_addr = htonl(ip_addr);
straddr = inet_ntoa(addr);
DBG("IP addr: %s", straddr);
ofono_gprs_context_set_ipv4_address(gc, straddr, 1);
}
if (qmi_result_get_uint32(result,QMI_WDS_RESULT_GATEWAY, &ip_addr)) {
addr.s_addr = htonl(ip_addr);
straddr = inet_ntoa(addr);
DBG("Gateway: %s", straddr);
ofono_gprs_context_set_ipv4_gateway(gc, straddr);
}
if (qmi_result_get_uint32(result,
QMI_WDS_RESULT_GATEWAY_NETMASK, &ip_addr)) {
addr.s_addr = htonl(ip_addr);
straddr = inet_ntoa(addr);
DBG("Gateway netmask: %s", straddr);
ofono_gprs_context_set_ipv4_netmask(gc, straddr);
}
if (qmi_result_get_uint32(result,
QMI_WDS_RESULT_PRIMARY_DNS, &ip_addr)) {
addr.s_addr = htonl(ip_addr);
dns[0] = inet_ntop(AF_INET, &addr, dns_buf[0], sizeof(dns_buf[0]));
DBG("Primary DNS: %s", dns[0]);
}
if (qmi_result_get_uint32(result,
QMI_WDS_RESULT_SECONDARY_DNS, &ip_addr)) {
addr.s_addr = htonl(ip_addr);
dns[1] = inet_ntop(AF_INET, &addr, dns_buf[1], sizeof(dns_buf[1]));
DBG("Secondary DNS: %s", dns[1]);
}
if (dns[0])
ofono_gprs_context_set_ipv4_dns_servers(gc, dns);
done:
modem = ofono_gprs_context_get_modem(gc);
interface = ofono_modem_get_string(modem, "NetworkInterface");
ofono_gprs_context_set_interface(gc, interface);
CALLBACK_WITH_SUCCESS(cb, cbd->data);
}
static void start_net_cb(struct qmi_result *result, void *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 *data = ofono_gprs_context_get_data(gc);
struct ofono_modem *modem;
const char *interface;
uint32_t handle;
DBG("");
if (qmi_result_set_error(result, NULL))
goto error;
if (!qmi_result_get_uint32(result, QMI_WDS_RESULT_PKT_HANDLE, &handle))
goto error;
DBG("packet handle %d", handle);
data->pkt_handle = handle;
/* Duplicate cbd, the old one will be freed when this method returns */
cbd = cb_data_new(cb, cbd->data);
cbd->user = gc;
if (qmi_service_send(data->wds, QMI_WDS_GET_SETTINGS, NULL,
get_settings_cb, cbd, g_free) > 0)
return;
modem = ofono_gprs_context_get_modem(gc);
interface = ofono_modem_get_string(modem, "NetworkInterface");
ofono_gprs_context_set_interface(gc, interface);
CALLBACK_WITH_SUCCESS(cb, cbd->data);
return;
error:
data->active_context = 0;
CALLBACK_WITH_FAILURE(cb, cbd->data);
}
/*
* This function gets called for "automatic" contexts, those which are
* not activated via activate_primary. For these, we will still need
* to call start_net in order to get the packet handle for the context.
* The process for automatic contexts is essentially identical to that
* for others.
*/
static void qmi_gprs_read_settings(struct ofono_gprs_context* gc,
unsigned int cid,
ofono_gprs_context_cb_t cb,
void *user_data)
{
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
struct cb_data *cbd = cb_data_new(cb, user_data);
DBG("cid %u", cid);
data->active_context = cid;
cbd->user = gc;
if (qmi_service_send(data->wds, QMI_WDS_START_NET, NULL,
start_net_cb, cbd, g_free) > 0)
return;
data->active_context = 0;
CALLBACK_WITH_FAILURE(cb, cbd->data);
g_free(cbd);
}
static uint8_t auth_method_to_qmi_auth(enum ofono_gprs_auth_method method)
{
switch (method) {
case OFONO_GPRS_AUTH_METHOD_CHAP:
return QMI_WDS_AUTHENTICATION_CHAP;
case OFONO_GPRS_AUTH_METHOD_PAP:
return QMI_WDS_AUTHENTICATION_PAP;
case OFONO_GPRS_AUTH_METHOD_NONE:
return QMI_WDS_AUTHENTICATION_NONE;
}
return QMI_WDS_AUTHENTICATION_NONE;
}
static void qmi_activate_primary(struct ofono_gprs_context *gc,
const struct ofono_gprs_primary_context *ctx,
ofono_gprs_context_cb_t cb, void *user_data)
{
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
struct cb_data *cbd = cb_data_new(cb, user_data);
struct qmi_param *param;
uint8_t ip_family;
uint8_t auth;
DBG("cid %u", ctx->cid);
cbd->user = gc;
data->active_context = ctx->cid;
switch (ctx->proto) {
case OFONO_GPRS_PROTO_IP:
ip_family = 4;
break;
case OFONO_GPRS_PROTO_IPV6:
ip_family = 6;
break;
default:
goto error;
}
param = qmi_param_new();
if (!param)
goto error;
qmi_param_append(param, QMI_WDS_PARAM_APN,
strlen(ctx->apn), ctx->apn);
qmi_param_append_uint8(param, QMI_WDS_PARAM_IP_FAMILY, ip_family);
auth = auth_method_to_qmi_auth(ctx->auth_method);
qmi_param_append_uint8(param, QMI_WDS_PARAM_AUTHENTICATION_PREFERENCE,
auth);
if (auth != QMI_WDS_AUTHENTICATION_NONE && ctx->username[0] != '\0')
qmi_param_append(param, QMI_WDS_PARAM_USERNAME,
strlen(ctx->username), ctx->username);
if (auth != QMI_WDS_AUTHENTICATION_NONE && ctx->password[0] != '\0')
qmi_param_append(param, QMI_WDS_PARAM_PASSWORD,
strlen(ctx->password), ctx->password);
if (qmi_service_send(data->wds, QMI_WDS_START_NET, param,
start_net_cb, cbd, g_free) > 0)
return;
qmi_param_free(param);
error:
data->active_context = 0;
CALLBACK_WITH_FAILURE(cb, cbd->data);
g_free(cbd);
}
static void stop_net_cb(struct qmi_result *result, void *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 *data = ofono_gprs_context_get_data(gc);
DBG("");
if (qmi_result_set_error(result, NULL)) {
if (cb)
CALLBACK_WITH_FAILURE(cb, cbd->data);
return;
}
data->pkt_handle = 0;
if (cb)
CALLBACK_WITH_SUCCESS(cb, cbd->data);
else
ofono_gprs_context_deactivated(gc, data->active_context);
data->active_context = 0;
}
static void qmi_deactivate_primary(struct ofono_gprs_context *gc,
unsigned int cid,
ofono_gprs_context_cb_t cb, void *user_data)
{
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
struct cb_data *cbd = cb_data_new(cb, user_data);
struct qmi_param *param;
DBG("cid %u", cid);
cbd->user = gc;
param = qmi_param_new_uint32(QMI_WDS_PARAM_PKT_HANDLE,
data->pkt_handle);
if (!param)
goto error;
if (qmi_service_send(data->wds, QMI_WDS_STOP_NET, param,
stop_net_cb, cbd, g_free) > 0)
return;
qmi_param_free(param);
error:
if (cb)
CALLBACK_WITH_FAILURE(cb, user_data);
g_free(cbd);
}
static void qmi_gprs_context_detach_shutdown(struct ofono_gprs_context *gc,
unsigned int cid)
{
DBG("");
qmi_deactivate_primary(gc, cid, NULL, NULL);
}
static void create_wds_cb(struct qmi_service *service, void *user_data)
{
struct ofono_gprs_context *gc = user_data;
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
DBG("");
if (!service) {
ofono_error("Failed to request WDS service");
ofono_gprs_context_remove(gc);
return;
}
data->wds = qmi_service_ref(service);
qmi_service_register(data->wds, QMI_WDS_PKT_STATUS_IND,
pkt_status_notify, gc, NULL);
}
static void get_data_format_cb(struct qmi_result *result, void *user_data)
{
struct ofono_gprs_context *gc = user_data;
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
uint32_t llproto;
enum qmi_device_expected_data_format expected_llproto;
DBG("");
if (qmi_result_set_error(result, NULL))
goto done;
if (!qmi_result_get_uint32(result, QMI_WDA_LL_PROTOCOL, &llproto))
goto done;
expected_llproto = qmi_device_get_expected_data_format(data->dev);
if ((llproto == QMI_WDA_DATA_LINK_PROTOCOL_802_3) &&
(expected_llproto ==
QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP)) {
if (!qmi_device_set_expected_data_format(data->dev,
QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3))
DBG("Fail to set expected data to 802.3");
else
DBG("expected data set to 802.3");
} else if ((llproto == QMI_WDA_DATA_LINK_PROTOCOL_RAW_IP) &&
(expected_llproto ==
QMI_DEVICE_EXPECTED_DATA_FORMAT_802_3)) {
if (!qmi_device_set_expected_data_format(data->dev,
QMI_DEVICE_EXPECTED_DATA_FORMAT_RAW_IP))
DBG("Fail to set expected data to raw-ip");
else
DBG("expected data set to raw-ip");
}
done:
qmi_service_create_shared(data->dev, QMI_SERVICE_WDS, create_wds_cb, gc,
NULL);
}
static void create_wda_cb(struct qmi_service *service, void *user_data)
{
struct ofono_gprs_context *gc = user_data;
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
DBG("");
if (!service) {
DBG("Failed to request WDA service, continue initialization");
goto error;
}
data->wda = qmi_service_ref(service);
if (qmi_service_send(data->wda, QMI_WDA_GET_DATA_FORMAT, NULL,
get_data_format_cb, gc, NULL) > 0)
return;
error:
qmi_service_create_shared(data->dev, QMI_SERVICE_WDS, create_wds_cb, gc,
NULL);
}
static int qmi_gprs_context_probe(struct ofono_gprs_context *gc,
unsigned int vendor, void *user_data)
{
struct qmi_device *device = user_data;
struct gprs_context_data *data;
DBG("");
data = g_new0(struct gprs_context_data, 1);
ofono_gprs_context_set_data(gc, data);
data->dev = device;
qmi_service_create(device, QMI_SERVICE_WDA, create_wda_cb, gc, NULL);
return 0;
}
static void qmi_gprs_context_remove(struct ofono_gprs_context *gc)
{
struct gprs_context_data *data = ofono_gprs_context_get_data(gc);
DBG("");
ofono_gprs_context_set_data(gc, NULL);
if (data->wds) {
qmi_service_unregister_all(data->wds);
qmi_service_unref(data->wds);
}
if (data->wda) {
qmi_service_unregister_all(data->wda);
qmi_service_unref(data->wda);
}
g_free(data);
}
static const struct ofono_gprs_context_driver driver = {
.name = "qmimodem",
.probe = qmi_gprs_context_probe,
.remove = qmi_gprs_context_remove,
.activate_primary = qmi_activate_primary,
.deactivate_primary = qmi_deactivate_primary,
.read_settings = qmi_gprs_read_settings,
.detach_shutdown = qmi_gprs_context_detach_shutdown,
};
void qmi_gprs_context_init(void)
{
ofono_gprs_context_driver_register(&driver);
}
void qmi_gprs_context_exit(void)
{
ofono_gprs_context_driver_unregister(&driver);
}