All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dragos Tatulea <dragos@endocode.com>
To: ofono@ofono.org
Subject: [PATCH 10/19] ubloxmodem: add Toby L2 gprs context driver
Date: Wed, 09 Mar 2016 16:44:51 +0100	[thread overview]
Message-ID: <1457538300-7183-11-git-send-email-dragos@endocode.com> (raw)
In-Reply-To: <1457538300-7183-1-git-send-email-dragos@endocode.com>

[-- Attachment #1: Type: text/plain, Size: 16912 bytes --]

For now the driver works only with bridged mode.

Once it activates the context it reads the ip, netmask,
gw, dns and sets them in the context settings.

4G default bearer is supported as well by reading the
automatic activation cid and using it in the first context
activation.
---
 Makefile.am                       |   7 +
 drivers/ubloxmodem/gprs-context.c | 498 ++++++++++++++++++++++++++++++++++++++
 drivers/ubloxmodem/ubloxmodem.c   |  49 ++++
 drivers/ubloxmodem/ubloxmodem.h   |  25 ++
 4 files changed, 579 insertions(+)
 create mode 100644 drivers/ubloxmodem/gprs-context.c
 create mode 100644 drivers/ubloxmodem/ubloxmodem.c
 create mode 100644 drivers/ubloxmodem/ubloxmodem.h

diff --git a/Makefile.am b/Makefile.am
index cde998d..7652cfe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -374,6 +374,13 @@ builtin_sources += drivers/atmodem/atutil.h \
 			drivers/speedupmodem/speedupmodem.c \
 			drivers/speedupmodem/ussd.c
 
+builtin_modules += ubloxmodem
+builtin_sources += drivers/atmodem/atutil.h \
+			drivers/ubloxmodem/ubloxmodem.h \
+			drivers/ubloxmodem/ubloxmodem.c \
+			drivers/ubloxmodem/gprs-context.c
+
+
 if PHONESIM
 builtin_modules += phonesim
 builtin_sources += plugins/phonesim.c
diff --git a/drivers/ubloxmodem/gprs-context.c b/drivers/ubloxmodem/gprs-context.c
new file mode 100644
index 0000000..f80e1d8
--- /dev/null
+++ b/drivers/ubloxmodem/gprs-context.c
@@ -0,0 +1,498 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2016  EndoCode AG. 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
+
+#define _GNU_SOURCE
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include <ofono/log.h>
+#include <ofono/modem.h>
+#include <ofono/gprs-context.h>
+
+#include "gatchat.h"
+#include "gatresult.h"
+
+#include "ubloxmodem.h"
+
+static const char *none_prefix[] = { NULL };
+static const char *cgcontrdp_prefix[] = { "+CGCONTRDP:", NULL };
+
+struct gprs_context_data {
+	GAtChat *chat;
+	unsigned int active_context;
+	unsigned int gprs_cid;
+	char apn[OFONO_GPRS_MAX_APN_LENGTH + 1];
+	ofono_gprs_context_cb_t cb;
+	void *cb_data;
+};
+
+#define UBLOX_MAX_DEF_CONTEXT 8
+
+struct ublox_data {
+	/* Stores default PDP context/default EPS bearer. */
+	unsigned int default_context_id;
+	struct gprs_context_data contexts[UBLOX_MAX_DEF_CONTEXT];
+	unsigned int ncontexts;
+
+	/* Save used cids on the modem. */
+	bool active[UBLOX_MAX_DEF_CONTEXT];
+	unsigned int nactive;
+};
+
+static struct ublox_data ublox_data;
+
+static int get_context_id()
+{
+	int i = 0;
+
+	if (!ublox_data.nactive && ublox_data.default_context_id) {
+		/* Try to assign default context first. */
+		i = ublox_data.default_context_id - 1;
+		goto found;
+	}
+
+	for (i = 0; i < UBLOX_MAX_DEF_CONTEXT; i++) {
+		if (!ublox_data.active[i])
+			goto found;
+	}
+
+	return 0;
+found:
+	ublox_data.active[i] = true;
+	ublox_data.nactive++;
+
+	return i+1;
+}
+
+static void release_context_id(unsigned cid)
+{
+	int i;
+
+	if (!cid || cid > UBLOX_MAX_DEF_CONTEXT)
+		return;
+
+	for (i = 0; i < UBLOX_MAX_DEF_CONTEXT; i++) {
+		if (ublox_data.contexts[i].active_context == cid) {
+
+			ublox_data.active[i] = false;
+			ublox_data.nactive--;
+
+			ublox_data.contexts[i].active_context = 0;
+			ublox_data.contexts[i].gprs_cid = 0;
+
+			return;
+		}
+	}
+}
+
+/*
+ * CGCONTRDP returns addr + netmask in the same string in the form
+ * of "a.b.c.d.m.m.m.m" for IPv4. IPv6 is not supported so we ignore it.
+ */
+static int read_addrnetmask(struct ofono_gprs_context *gc,
+				const char *addrnetmask)
+{
+	char *dup = strdup(addrnetmask);
+	char *s = dup;
+
+	const char *addr = s;
+	const char *netmask = NULL;
+
+	int ret = -EINVAL;
+	int i;
+
+	/* Count 7 dots for ipv4, less or more means error. */
+	for (i = 0; i < 8; i++, s++) {
+		s = strchr(s, '.');
+		if (!s)
+			break;
+
+		if (i == 3) {
+			/* set netmask ptr and break the string */
+			netmask = s+1;
+			s[0] = 0;
+		}
+	}
+
+	if (i == 7) {
+		ofono_gprs_context_set_ipv4_address(gc, addr, 1);
+		ofono_gprs_context_set_ipv4_netmask(gc, netmask);
+
+		ret = 0;
+	}
+
+	free(dup);
+
+	return ret;
+}
+
+static void callback_with_error(struct gprs_context_data *gcd, GAtResult *res)
+{
+	struct ofono_error error;
+
+	decode_at_error(&error, g_at_result_final_response(res));
+	gcd->cb(&error, gcd->cb_data);
+}
+
+
+static void set_gprs_context_interface(struct ofono_gprs_context *gc)
+{
+	struct ofono_modem *modem;
+	const char *interface;
+
+	/* read interface name read at detection time */
+	modem = ofono_gprs_context_get_modem(gc);
+	interface = ofono_modem_get_string(modem, "NetworkInterface");
+	ofono_gprs_context_set_interface(gc, interface);
+}
+
+static void cgcontrdp_bridge_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct ofono_gprs_context *gc = user_data;
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+	GAtResultIter iter;
+	int cid = -1;
+
+	const char *laddrnetmask = NULL;
+	const char *gw = NULL;
+	const char *dns[2+1] = { NULL, NULL, NULL };
+
+	DBG("ok %d", ok);
+
+	if (!ok) {
+		callback_with_error(gcd, result);
+
+		return;
+	}
+
+	g_at_result_iter_init(&iter, result);
+
+	while (g_at_result_iter_next(&iter, "+CGCONTRDP:")) {
+		/* tmp vals for ignored fields */
+		int bearer_id;
+		char *apn;
+
+		if (!g_at_result_iter_next_number(&iter, &cid))
+			break;
+
+		if (!g_at_result_iter_next_number(&iter, &bearer_id))
+			break;
+
+		if (!g_at_result_iter_next_string(&iter, &apn))
+			break;
+
+		if (!g_at_result_iter_next_string(&iter, &laddrnetmask))
+			break;
+
+		if (!g_at_result_iter_next_string(&iter, &gw))
+			break;
+
+		if (!g_at_result_iter_next_string(&iter, &dns[0]))
+			break;
+
+		if (!g_at_result_iter_next_string(&iter, &dns[1]))
+			break;
+	}
+
+	set_gprs_context_interface(gc);
+
+	if (!laddrnetmask || read_addrnetmask(gc, laddrnetmask) < 0) {
+		CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data);
+		return;
+	}
+
+	if (gw)
+		ofono_gprs_context_set_ipv4_gateway(gc, gw);
+
+	if (dns[0])
+		ofono_gprs_context_set_ipv4_dns_servers(gc, dns);
+
+	CALLBACK_WITH_SUCCESS(gcd->cb, gcd->cb_data);
+}
+
+static int ublox_read_ip_config_bridge(struct ofono_gprs_context *gc)
+{
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+	char buf[64];
+
+	/* read ip configuration info */
+	snprintf(buf, sizeof(buf), "AT+CGCONTRDP=%u", gcd->active_context);
+	return g_at_chat_send(gcd->chat, buf, cgcontrdp_prefix,
+				cgcontrdp_bridge_cb, gc, NULL);
+
+}
+
+static void ublox_post_activation(struct ofono_gprs_context *gc)
+{
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+
+	if (ublox_read_ip_config_bridge(gc) < 0)
+		CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data);
+}
+
+static void cgact_enable_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct ofono_gprs_context *gc = user_data;
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+
+	DBG("ok %d", ok);
+	if (!ok) {
+		release_context_id(gcd->active_context);
+		callback_with_error(gcd, result);
+
+		return;
+	}
+
+	ublox_post_activation(gc);
+}
+
+static void cgdcont_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct ofono_gprs_context *gc = user_data;
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+	char buf[64];
+
+	DBG("ok %d", ok);
+
+	if (!ok) {
+		release_context_id(gcd->active_context);
+		callback_with_error(gcd, result);
+
+		return;
+	}
+
+	snprintf(buf, sizeof(buf), "AT+CGACT=1,%u", gcd->active_context);
+	if (g_at_chat_send(gcd->chat, buf, none_prefix,
+				cgact_enable_cb, gc, NULL))
+		return;
+
+	CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data);
+}
+
+static void ublox_activate_ctx(struct ofono_gprs_context *gc)
+{
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+	char buf[OFONO_GPRS_MAX_APN_LENGTH + 128];
+	int len;
+
+	len = snprintf(buf, sizeof(buf), "AT+CGDCONT=%u,\"IP\"",
+				gcd->active_context);
+
+	if (gcd->apn)
+		snprintf(buf + len, sizeof(buf) - len - 3, ",\"%s\"",
+					gcd->apn);
+
+	if (g_at_chat_send(gcd->chat, buf, none_prefix,
+				cgdcont_cb, gc, NULL) > 0)
+		return;
+
+	CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data);
+}
+
+#define UBLOX_MAX_USER_LEN 50
+#define UBLOX_MAX_PASS_LEN 50
+
+static void ublox_gprs_activate_primary(struct ofono_gprs_context *gc,
+				const struct ofono_gprs_primary_context *ctx,
+				ofono_gprs_context_cb_t cb, void *data)
+{
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+
+	/* IPv6 support not implemented */
+	if (ctx->proto != OFONO_GPRS_PROTO_IP) {
+		CALLBACK_WITH_FAILURE(cb, data);
+		return;
+	}
+
+	DBG("cid %u", ctx->cid);
+
+	gcd->active_context = get_context_id();
+	if (!gcd->active_context) {
+		ofono_error("can't activate more contexts");
+		CALLBACK_WITH_FAILURE(cb, data);
+		return;
+	}
+
+	gcd->cb = cb;
+	gcd->cb_data = data;
+	gcd->gprs_cid = ctx->cid;
+	memcpy(gcd->apn, ctx->apn, sizeof(ctx->apn));
+
+	if (gcd->active_context == ublox_data.default_context_id)
+		/* Default context already active, only read details. */
+		ublox_post_activation(gc);
+	else
+		ublox_activate_ctx(gc);
+}
+
+static void cgact_disable_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct ofono_gprs_context *gc = user_data;
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+
+	DBG("ok %d", ok);
+	if (!ok) {
+		if (gcd->active_context && ublox_data.default_context_id)
+			ofono_warn("Disabling the default context is not "
+					"allowed on LTE.");
+		CALLBACK_WITH_FAILURE(gcd->cb, gcd->cb_data);
+		return;
+	}
+
+	release_context_id(gcd->active_context);
+
+	CALLBACK_WITH_SUCCESS(gcd->cb, gcd->cb_data);
+}
+
+static void ublox_gprs_deactivate_primary(struct ofono_gprs_context *gc,
+					unsigned int cid,
+					ofono_gprs_context_cb_t cb, void *data)
+{
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+	char buf[64];
+
+	DBG("cid %u", cid);
+
+	gcd->cb = cb;
+	gcd->cb_data = data;
+
+	snprintf(buf, sizeof(buf), "AT+CGACT=0,%u", gcd->active_context);
+	g_at_chat_send(gcd->chat, buf, none_prefix,
+			cgact_disable_cb, gc, NULL);
+}
+
+static void cgev_notify(GAtResult *result, gpointer user_data)
+{
+	struct ofono_gprs_context *gc = user_data;
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+	GAtResultIter iter;
+	const char *event;
+	unsigned int cid;
+	char tmp[5] = {0};
+
+	g_at_result_iter_init(&iter, result);
+
+	if (!g_at_result_iter_next(&iter, "+CGEV:"))
+		return;
+
+	if (!g_at_result_iter_next_unquoted_string(&iter, &event))
+		return;
+
+	if (g_str_has_prefix(event, "ME PDN ACT") == TRUE) {
+		/*
+		 * We only care about the first even coming in before any user
+		 * activations happen. This is for detecting the default
+		 * context in LTE networks which doesn't require user
+		 * activation.
+		 */
+		sscanf(event, "%s %s %s %u", tmp, tmp, tmp, &cid);
+		if (!ublox_data.default_context_id && !ublox_data.nactive)
+			ublox_data.default_context_id = cid;
+		DBG("cid=%d activated", cid);
+
+		return;
+	} else if (g_str_has_prefix(event, "NW PDN DEACT") == TRUE) {
+		sscanf(event, "%s %s %s %u", tmp, tmp, tmp, &cid);
+		DBG("cid=%d deactivated", cid);
+	} else
+		return;
+
+	/* Can happen if non-default context is deactivated. Ignore. */
+	if (gcd->active_context != cid)
+		return;
+
+	if (ublox_data.default_context_id == cid)
+		ublox_data.default_context_id = 0;
+
+	ofono_gprs_context_deactivated(gc, gcd->gprs_cid);
+
+	release_context_id(gcd->active_context);
+}
+
+
+static int ublox_gprs_context_probe(struct ofono_gprs_context *gc,
+					unsigned int vendor, void *data)
+{
+	GAtChat *chat = data;
+	struct gprs_context_data *gcd;
+
+	DBG("");
+
+	if (ublox_data.ncontexts >= UBLOX_MAX_DEF_CONTEXT) {
+		ofono_error("modem supports defining max 8 contexts");
+		return -E2BIG;
+	}
+
+	gcd = &ublox_data.contexts[ublox_data.ncontexts];
+	memset(gcd, 0, sizeof(*gcd));
+	ublox_data.ncontexts++;
+
+	gcd->chat = g_at_chat_clone(chat);
+
+	ofono_gprs_context_set_data(gc, gcd);
+
+	/* Not sure if ok to register all contexts with the callback. */
+	g_at_chat_register(chat, "+CGEV:", cgev_notify, FALSE, gc, NULL);
+
+	return 0;
+}
+
+static void ublox_gprs_context_remove(struct ofono_gprs_context *gc)
+{
+	struct gprs_context_data *gcd = ofono_gprs_context_get_data(gc);
+
+	DBG("");
+
+	ofono_gprs_context_set_data(gc, NULL);
+
+	g_at_chat_unref(gcd->chat);
+
+	memset(gcd, 0, sizeof(*gcd));
+	ublox_data.ncontexts--;
+}
+
+static struct ofono_gprs_context_driver driver = {
+	.name			= "ubloxmodem",
+	.probe			= ublox_gprs_context_probe,
+	.remove			= ublox_gprs_context_remove,
+	.activate_primary	= ublox_gprs_activate_primary,
+	.deactivate_primary	= ublox_gprs_deactivate_primary,
+};
+
+
+void ublox_gprs_context_init(void)
+{
+	ofono_gprs_context_driver_register(&driver);
+}
+
+void ublox_gprs_context_exit(void)
+{
+	ofono_gprs_context_driver_unregister(&driver);
+}
diff --git a/drivers/ubloxmodem/ubloxmodem.c b/drivers/ubloxmodem/ubloxmodem.c
new file mode 100644
index 0000000..7fc671e
--- /dev/null
+++ b/drivers/ubloxmodem/ubloxmodem.c
@@ -0,0 +1,49 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2016  Endocode AG. 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 <glib.h>
+#include <gatchat.h>
+
+#define OFONO_API_SUBJECT_TO_CHANGE
+#include <ofono/plugin.h>
+#include <ofono/types.h>
+
+#include "ubloxmodem.h"
+
+static int ubloxmodem_init(void)
+{
+	ublox_gprs_context_init();
+
+	return 0;
+}
+
+static void ubloxmodem_exit(void)
+{
+	ublox_gprs_context_exit();
+}
+
+OFONO_PLUGIN_DEFINE(ubloxmodem, "U-Blox Toby L2 high speed modem driver",
+			VERSION, OFONO_PLUGIN_PRIORITY_DEFAULT,
+			ubloxmodem_init, ubloxmodem_exit)
diff --git a/drivers/ubloxmodem/ubloxmodem.h b/drivers/ubloxmodem/ubloxmodem.h
new file mode 100644
index 0000000..0c8a621
--- /dev/null
+++ b/drivers/ubloxmodem/ubloxmodem.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2016  Endocode AG. 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
+ *
+ */
+
+#include <drivers/atmodem/atutil.h>
+
+extern void ublox_gprs_context_init(void);
+extern void ublox_gprs_context_exit(void);
-- 
2.5.0


  parent reply	other threads:[~2016-03-09 15:44 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-03-09 15:44 [PATCH 00/19] Support for U-Blox Toby L2 modems Dragos Tatulea
2016-03-09 15:44 ` [PATCH 01/19] plugins/udevng: support the U-Blox TOBY-L2 series Dragos Tatulea
2016-03-10 14:00   ` Denis Kenzior
2016-03-09 15:44 ` [PATCH 02/19] plugins/udevng: support different interface strings to detect TOBY series Dragos Tatulea
2016-03-10 14:01   ` Denis Kenzior
2016-03-09 15:44 ` [PATCH 03/19] plugins/udevng: ublox: set model string Dragos Tatulea
2016-03-10 14:04   ` Denis Kenzior
2016-03-09 15:44 ` [PATCH 04/19] include: vendor.h: add vendor for ublox toby Dragos Tatulea
2016-03-10 14:05   ` Denis Kenzior
2016-03-09 15:44 ` [PATCH 05/19] plugins/ublox: allow enabling of TOBY L2 modems Dragos Tatulea
2016-03-10 14:11   ` Denis Kenzior
2016-03-09 15:44 ` [PATCH 06/19] plugins/ublox: use vendor from structure instead of fixed Dragos Tatulea
2016-03-09 15:44 ` [PATCH 07/19] atmodem: ublox: EPS now supported by newer ublox Dragos Tatulea
2016-03-10 14:29   ` Denis Kenzior
2016-03-10 14:43   ` Denis Kenzior
2016-03-09 15:44 ` [PATCH 08/19] atmodem: add support for U-Blox TOBY L2 modems Dragos Tatulea
2016-03-10 14:34   ` Denis Kenzior
2016-03-10 14:38     ` Dragos Tatulea
2016-03-09 15:44 ` [PATCH 09/19] atmodem: work around CGREG issues in UBlox Toby L2 Dragos Tatulea
2016-03-10 14:40   ` Denis Kenzior
2016-03-09 15:44 ` Dragos Tatulea [this message]
2016-03-09 15:44 ` [PATCH 11/19] gprs-context.h: add function for setting APN Dragos Tatulea
2016-03-09 15:44 ` [PATCH 12/19] gprs: allow APN updates from gprs-context driver Dragos Tatulea
2016-03-09 15:44 ` [PATCH 13/19] ubloxmodem: push back APN into gprs context Dragos Tatulea
2016-03-09 15:44 ` [PATCH 14/19] plugins/ublox: give names to model ids Dragos Tatulea
2016-03-09 15:44 ` [PATCH 15/19] plugins/ublox: enable ubloxmodem driver when possible Dragos Tatulea
2016-03-09 15:44 ` [PATCH 16/19] plugins/ublox: support more internet contexts Dragos Tatulea
2016-03-09 15:44 ` [PATCH 17/19] ubloxmodem: support authentication Dragos Tatulea
2016-03-09 15:44 ` [PATCH 18/19] plugins/ublox: read network mode Dragos Tatulea
2016-03-09 15:45 ` [PATCH 19/19] ubloxmodem: add routed mode support Dragos Tatulea

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1457538300-7183-11-git-send-email-dragos@endocode.com \
    --to=dragos@endocode.com \
    --cc=ofono@ofono.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.