All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query
@ 2018-08-28  7:45 Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
  2018-08-28  7:45 ` [PATCHv2 2/2] m95: Add driver for Quectel M95 modem Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
  2018-08-28 19:10 ` [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query Denis Kenzior
  0 siblings, 2 replies; 4+ messages in thread
From: Martin =?unknown-8bit?q?Hundeb=C3=B8ll?= @ 2018-08-28  7:45 UTC (permalink / raw)
  To: ofono

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

The AT command reference for Quectel M95 specifies that remaining SIM
pin retires can be queried using AT+QTRPIN, which responds with one
count for each pin-type:

+QTRPIN: 3,3,10,10

After entering the PIN code, enable an extra AT+CPIN? for the M95
vendor.

---
Changes since v1:

 * Add quirk to resend AT+CPIN? after sending pin code

 drivers/atmodem/sim.c    | 50 ++++++++++++++++++++++++++++++++++++++++
 drivers/atmodem/vendor.h |  1 +
 2 files changed, 51 insertions(+)

diff --git a/drivers/atmodem/sim.c b/drivers/atmodem/sim.c
index 10dc8009..0907635d 100644
--- a/drivers/atmodem/sim.c
+++ b/drivers/atmodem/sim.c
@@ -70,6 +70,7 @@ static const char *cinterion_spic_prefix[] = { "^SPIC:", NULL };
 static const char *pct_prefix[] = { "#PCT:", NULL };
 static const char *pnnm_prefix[] = { "+PNNM:", NULL };
 static const char *qpinc_prefix[] = { "+QPINC:", NULL };
+static const char *qtrpin_prefix[] = { "+QTRPIN:", NULL };
 static const char *upincnt_prefix[] = { "+UPINCNT:", NULL };
 static const char *cuad_prefix[] = { "+CUAD:", NULL };
 static const char *ccho_prefix[] = { "+CCHO:", NULL };
@@ -982,6 +983,49 @@ error:
 	CALLBACK_WITH_FAILURE(cb, NULL, cbd->data);
 }
 
+static void at_qtrpin_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct cb_data *cbd = user_data;
+	ofono_sim_pin_retries_cb_t cb = cbd->cb;
+	const char *final = g_at_result_final_response(result);
+	GAtResultIter iter;
+	struct ofono_error error;
+	int retries[OFONO_SIM_PASSWORD_INVALID];
+	size_t i;
+
+	decode_at_error(&error, final);
+
+	if (!ok) {
+		cb(&error, NULL, cbd->data);
+		return;
+	}
+
+	for (i = 0; i < OFONO_SIM_PASSWORD_INVALID; i++)
+		retries[i] = -1;
+
+	g_at_result_iter_init(&iter, result);
+
+	while (g_at_result_iter_next(&iter, "+QTRPIN:")) {
+		int pin, pin2, puk, puk2;
+
+		if (!g_at_result_iter_next_number(&iter, &pin))
+			continue;
+		if (!g_at_result_iter_next_number(&iter, &pin2))
+			continue;
+		if (!g_at_result_iter_next_number(&iter, &puk))
+			continue;
+		if (!g_at_result_iter_next_number(&iter, &puk2))
+			continue;
+
+		retries[OFONO_SIM_PASSWORD_SIM_PIN] = pin;
+		retries[OFONO_SIM_PASSWORD_SIM_PUK] = puk;
+		retries[OFONO_SIM_PASSWORD_SIM_PIN2] = pin2;
+		retries[OFONO_SIM_PASSWORD_SIM_PUK2] = puk2;
+	}
+
+	cb(&error, retries, cbd->data);
+}
+
 static void at_qpinc_cb(gboolean ok, GAtResult *result, gpointer user_data)
 {
 	struct cb_data *cbd = user_data;
@@ -1172,6 +1216,11 @@ static void at_pin_retries_query(struct ofono_sim *sim,
 					at_qpinc_cb, cbd, g_free) > 0)
 			return;
 		break;
+	case OFONO_VENDOR_QUECTEL_M95:
+		if (g_at_chat_send(sd->chat, "AT+QTRPIN", qtrpin_prefix,
+					at_qtrpin_cb, cbd, g_free) > 0)
+			return;
+		break;
 	case OFONO_VENDOR_UBLOX:
 	case OFONO_VENDOR_UBLOX_TOBY_L2:
 		if (g_at_chat_send(sd->chat, "AT+UPINCNT", upincnt_prefix,
@@ -1305,6 +1354,7 @@ static void at_pin_send_cb(gboolean ok, GAtResult *result,
 	case OFONO_VENDOR_HUAWEI:
 	case OFONO_VENDOR_SIMCOM:
 	case OFONO_VENDOR_SIERRA:
+	case OFONO_VENDOR_QUECTEL_M95:
 		/*
 		 * On ZTE modems, after pin is entered, SIM state is checked
 		 * by polling CPIN as their modem doesn't provide unsolicited
diff --git a/drivers/atmodem/vendor.h b/drivers/atmodem/vendor.h
index d52ad521..721796e4 100644
--- a/drivers/atmodem/vendor.h
+++ b/drivers/atmodem/vendor.h
@@ -44,6 +44,7 @@ enum ofono_vendor {
 	OFONO_VENDOR_WAVECOM_Q2XXX,
 	OFONO_VENDOR_ALCATEL,
 	OFONO_VENDOR_QUECTEL,
+	OFONO_VENDOR_QUECTEL_M95,
 	OFONO_VENDOR_UBLOX,
 	OFONO_VENDOR_UBLOX_TOBY_L2,
 	OFONO_VENDOR_CINTERION,
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCHv2 2/2] m95: Add driver for Quectel M95 modem
  2018-08-28  7:45 [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
@ 2018-08-28  7:45 ` Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
  2018-08-28 20:08   ` Denis Kenzior
  2018-08-28 19:10 ` [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query Denis Kenzior
  1 sibling, 1 reply; 4+ messages in thread
From: Martin =?unknown-8bit?q?Hundeb=C3=B8ll?= @ 2018-08-28  7:45 UTC (permalink / raw)
  To: ofono

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

So far the driver is only tested with GPRS.

The modem uses GSM 07.10 multiplexing, which breaks when changing
functional modes (i.e. going from CFUN=4 to CFUN=1). Because of this the
driver doesn't implement online/offline states. It does support disabling
the device to save power though, but users must go through the full setup
procedure to enable the device again.

---
Changes since v1:

 * use listener when disabling modem
 * fix const vs. non-const warnings

Changes since RFC:

 * style fixed according to coding-style.txt
 * setup/teardown channel is handled separately from muxing channels
 * voice is setup in pre-sim to allow emergency calls
 * device status is queried before creating sms and phonebook

 Makefile.am      |   3 +
 plugins/m95.c    | 570 +++++++++++++++++++++++++++++++++++++++++++++++
 plugins/udevng.c |   1 +
 3 files changed, 574 insertions(+)
 create mode 100644 plugins/m95.c

diff --git a/Makefile.am b/Makefile.am
index 6dee4ce3..6fe4258a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -511,6 +511,9 @@ builtin_sources += plugins/telit.c
 builtin_modules += quectel
 builtin_sources += plugins/quectel.c
 
+builtin_modules += m95
+builtin_sources += plugins/m95.c
+
 builtin_modules += ublox
 builtin_sources += plugins/ublox.c
 
diff --git a/plugins/m95.c b/plugins/m95.c
new file mode 100644
index 00000000..679c1763
--- /dev/null
+++ b/plugins/m95.c
@@ -0,0 +1,570 @@
+/*
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2008-2018  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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <glib.h>
+#include <gatchat.h>
+#include <gattty.h>
+#include <gatmux.h>
+
+#define OFONO_API_SUBJECT_TO_CHANGE
+#include <ofono/plugin.h>
+#include <ofono/modem.h>
+#include <ofono/devinfo.h>
+#include <ofono/netreg.h>
+#include <ofono/sim.h>
+#include <ofono/ussd.h>
+#include <ofono/gprs.h>
+#include <ofono/gprs-context.h>
+#include <ofono/phonebook.h>
+#include <ofono/history.h>
+#include <ofono/sms.h>
+#include <ofono/log.h>
+#include <ofono/voicecall.h>
+#include <ofono/call-volume.h>
+#include <drivers/atmodem/vendor.h>
+
+#define NUM_DLC 4
+
+#define VOICE_DLC   0
+#define NETREG_DLC  1
+#define SMS_DLC     2
+#define GPRS_DLC    3
+
+static const char *dlc_prefixes[NUM_DLC] = {"Voice: ", "Net: ", "SMS: ",
+							"GPRS: "};
+
+/* the initial reset responds with the requested cfun when ready */
+static const char *cfun_reset_prefix[]	= {"+CFUN:", NULL};
+
+/* the post-reset enable commands responds with cpin when ready */
+static const char *cfun_enable_prefix[]	= {"+CPIN:", NULL};
+
+/* qinistat is used to test when phonebook and sms services are ready */
+static const char *qinistat_prefix[]	= {"+QINISTAT:", NULL};
+
+struct m95_data {
+	GIOChannel *tty;		/* serial device used for modem */
+	GAtChat *setup;			/* AT channel used for setup/teardown */
+	GAtMux *mux;			/* mux instance used after setup */
+	GAtChat *dlcs[NUM_DLC];		/* multiplexed AT channels */
+	guint cfun_reset;		/* reset AT registration */
+	guint cfun_enable;		/* enable AT registration */
+	guint mux_dropped_timer;	/* timer id for teardown delay */
+	guint qinitstat_timer;		/* timer id for qinistat query */
+	gboolean have_phonebook;	/* state for delayed phonebook setup */
+	gboolean have_sms;		/* state for delayed sms setup */
+};
+
+static int m95_probe(struct ofono_modem *modem)
+{
+	struct m95_data *data;
+
+	DBG("%p", modem);
+
+	data = g_new0(struct m95_data, 1);
+	if (data == NULL)
+		return -ENOMEM;
+
+	ofono_modem_set_data(modem, data);
+
+	return 0;
+}
+
+static void m95_remove(struct ofono_modem *modem)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+
+	DBG("%p", modem);
+
+	ofono_modem_set_data(modem, NULL);
+
+	g_free(data);
+}
+
+static void m95_debug(const char *str, void *user_data)
+{
+	const char *prefix = user_data;
+
+	ofono_info("%s%s", prefix, str);
+}
+
+static GAtChat *open_device(struct ofono_modem *modem,
+				const char *key, char *debug)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+	const char *device;
+	GAtSyntax *syntax;
+	GAtChat *chat;
+	GHashTable *options;
+
+	device = ofono_modem_get_string(modem, key);
+	if (device == NULL)
+		return NULL;
+
+	DBG("%s %s", key, device);
+
+	options = g_hash_table_new(g_str_hash, g_str_equal);
+	if (options == NULL)
+		return NULL;
+
+	g_hash_table_insert(options, "Baud", "115200");
+	g_hash_table_insert(options, "Parity", "none");
+	g_hash_table_insert(options, "StopBits", "1");
+	g_hash_table_insert(options, "DataBits", "8");
+	g_hash_table_insert(options, "XonXoff", "off");
+	g_hash_table_insert(options, "Local", "off");
+	g_hash_table_insert(options, "RtsCts", "off");
+	g_hash_table_insert(options, "Read", "on");
+
+	/* open serial device */
+	data->tty = g_at_tty_open(device, options);
+	g_hash_table_destroy(options);
+
+	if (data->tty == NULL)
+		return NULL;
+
+	/* create AT chat instance */
+	syntax = g_at_syntax_new_gsm_permissive();
+	chat = g_at_chat_new(data->tty, syntax);
+	g_at_syntax_unref(syntax);
+
+	if (chat == NULL) {
+		g_io_channel_unref(data->tty);
+		data->tty = NULL;
+
+		return NULL;
+	}
+
+	if (getenv("OFONO_AT_DEBUG"))
+		g_at_chat_set_debug(chat, m95_debug, debug);
+
+	return chat;
+}
+
+static GAtChat *create_chat(GIOChannel *channel, struct ofono_modem *modem,
+				const char *debug)
+{
+	GAtSyntax *syntax;
+	GAtChat *chat;
+
+	if (channel == NULL)
+		return NULL;
+
+	syntax = g_at_syntax_new_gsmv1();
+	chat = g_at_chat_new(channel, syntax);
+	g_at_syntax_unref(syntax);
+
+	if (chat == NULL)
+		return NULL;
+
+	if (getenv("OFONO_AT_DEBUG"))
+		g_at_chat_set_debug(chat, m95_debug, (gpointer)debug);
+
+	return chat;
+}
+
+static void shutdown_device(struct m95_data *data)
+{
+	int i;
+
+	data->have_sms = FALSE;
+	data->have_phonebook = FALSE;
+
+	if (data->mux == NULL)
+		goto close_tty;
+
+	for (i = 0; i < NUM_DLC; i++) {
+		if (data->dlcs[i] == NULL)
+			continue;
+
+		g_at_chat_cancel_all(data->dlcs[i]);
+		g_at_chat_unregister_all(data->dlcs[i]);
+		g_at_chat_unref(data->dlcs[i]);
+		data->dlcs[i] = NULL;
+	}
+
+	g_at_mux_shutdown(data->mux);
+	g_at_mux_unref(data->mux);
+	data->mux = NULL;
+
+close_tty:
+	g_io_channel_unref(data->tty);
+	data->tty = NULL;
+}
+
+static void setup_internal_mux(struct ofono_modem *modem)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+	int i;
+
+	DBG("");
+
+	/* prepare the multiplexing instance */
+	data->mux = g_at_mux_new_gsm0710_basic(data->tty, 127);
+	if (data->mux == NULL)
+		goto error;
+
+	if (getenv("OFONO_MUX_DEBUG"))
+		g_at_mux_set_debug(data->mux, m95_debug, "MUX: ");
+
+	if (!g_at_mux_start(data->mux))
+		goto error;
+
+	/* create multiplexing channels for gprs, net, voice, and text */
+	for (i = 0; i < NUM_DLC; i++) {
+		GIOChannel *channel = g_at_mux_create_channel(data->mux);
+
+		data->dlcs[i] = create_chat(channel, modem, dlc_prefixes[i]);
+		if (data->dlcs[i] == NULL) {
+			ofono_error("Failed to create channel");
+			goto error;
+		}
+
+		g_io_channel_unref(channel);
+	}
+
+	ofono_modem_set_powered(modem, TRUE);
+
+	return;
+
+error:
+	shutdown_device(data);
+	ofono_modem_set_powered(modem, FALSE);
+}
+
+static void mux_setup_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+	struct m95_data *data = ofono_modem_get_data(modem);
+
+	DBG("");
+
+	/* remove the initial setup AT channel before creating mux-channels */
+	g_at_chat_unref(data->setup);
+	data->setup = NULL;
+
+	if (!ok)
+		goto error;
+
+	setup_internal_mux(modem);
+
+	return;
+
+error:
+	shutdown_device(data);
+	ofono_modem_set_powered(modem, FALSE);
+}
+
+static void send_cmux(GAtResult *result, gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+	struct m95_data *data = ofono_modem_get_data(modem);
+	GAtResultIter iter;
+
+	DBG("%p", modem);
+
+	g_at_result_iter_init(&iter, result);
+	if (!g_at_result_iter_next(&iter, cfun_enable_prefix[0]))
+		return;
+
+	/* remove the enable-completed listener */
+	if (data->cfun_enable) {
+		g_at_chat_unregister(data->setup, data->cfun_enable);
+		data->cfun_enable = 0;
+	}
+
+	/* setup multiplexing */
+	g_at_chat_send(data->setup, "AT+CMUX=0,0,5,127,10,3,30,10,2", NULL,
+			mux_setup_cb, modem, NULL);
+}
+
+static void cfun_enable_cb(gboolean ok, GAtResult *result,
+				gpointer user_data)
+{
+	DBG("ok %d", ok);
+
+	if (ok)
+		send_cmux(result, user_data);
+}
+
+static void cfun_reset(GAtResult *result, gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+	struct m95_data *data = ofono_modem_get_data(modem);
+	GAtResultIter iter;
+
+	DBG("%p", modem);
+
+	g_at_result_iter_init(&iter, result);
+	if (!g_at_result_iter_next(&iter, cfun_reset_prefix[0]))
+		return;
+
+	/* remove the reset-completed listener */
+	g_at_chat_unregister(data->setup, data->cfun_reset);
+	data->cfun_reset = 0;
+
+	/* prepare to enable the modem */
+	g_at_chat_send(data->setup, "ATE0", NULL, NULL, NULL, NULL);
+	g_at_chat_send(data->setup, "AT+IFC=2,2", NULL, NULL, NULL, NULL);
+	g_at_chat_send(data->setup, "AT+GMM", NULL, NULL, NULL, NULL);
+	g_at_chat_send(data->setup, "AT", NULL, NULL, NULL, NULL);
+	g_at_chat_send(data->setup, "AT+IPR=115200&w", NULL, NULL, NULL, NULL);
+
+	/* setup a listener to delay multiplex-setup until modem is ready */
+	data->cfun_enable = g_at_chat_register(data->setup,
+						cfun_enable_prefix[0],
+						send_cmux, FALSE, modem, NULL);
+
+	/* enable the modem; responds with +CPIN: <ready/locked> when ready */
+	g_at_chat_send(data->setup, "AT+CFUN=1", cfun_enable_prefix,
+			cfun_enable_cb, modem, NULL);
+}
+
+static void cfun_reset_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	DBG("ok %d", ok);
+
+	if (ok)
+		cfun_reset(result, user_data);
+}
+
+static int m95_enable(struct ofono_modem *modem)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+
+	DBG("%p", modem);
+
+	data->setup = open_device(modem, "Device", "Setup: ");
+	if (data->setup == NULL)
+		return -EINVAL;
+
+	/*
+	 * when issuing the reset below, the modem is not ready until it
+	 * responds with "+CFUN: 4", so set up a listener for this
+	 */
+	data->cfun_reset = g_at_chat_register(data->setup, cfun_reset_prefix[0],
+						cfun_reset, FALSE, modem, NULL);
+
+	/* bring the modem into a known state by issuing a reset */
+	g_at_chat_send(data->setup, "AT+CFUN=0,1", cfun_reset_prefix,
+			cfun_reset_cb, modem, NULL);
+
+	return -EINPROGRESS;
+}
+
+static void cfun_disable(GAtResult *result, gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+	struct m95_data *data = ofono_modem_get_data(modem);
+	GAtResultIter iter;
+
+	DBG("%p", modem);
+
+	g_at_result_iter_init(&iter, result);
+	if (!g_at_result_iter_next(&iter, cfun_reset_prefix[0]))
+		return;
+
+	/* remove the initial setup AT channel before creating mux-channels */
+	g_at_chat_unref(data->setup);
+	data->setup = NULL;
+
+	/* device is now in non-functional mode, so shut down it down */
+	shutdown_device(data);
+	ofono_modem_set_powered(modem, FALSE);
+}
+
+static void cfun_disable_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	DBG("ok %d", ok);
+
+	if (ok)
+		cfun_disable(result, user_data);
+}
+
+static gboolean send_cfun_disable(gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+	struct m95_data *data = ofono_modem_get_data(modem);
+
+	/*
+	 * when issuing the reset below, the modem is not ready until it
+	 * responds with "+CFUN: 0", so set up a listener for this
+	 */
+	data->cfun_reset = g_at_chat_register(data->setup, cfun_reset_prefix[0],
+						cfun_disable, FALSE, modem, NULL);
+
+	/* now put device in non-functional mode */
+	g_at_chat_send(data->setup, "AT+CFUN=0,1", NULL, cfun_disable_cb, modem,
+			NULL);
+
+	return FALSE;
+}
+
+static int m95_disable(struct ofono_modem *modem)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+
+	DBG("%p", modem);
+
+	/* shut down the device to tear down the muxing channels */
+	shutdown_device(data);
+
+	/* reopen device to get a plain AT control channel */
+	data->setup = open_device(modem, "Device", "Disable: ");
+	if (data->setup == NULL)
+		return -EINVAL;
+
+	/* add a timer to wait for device to drop GSM 07.10 multiplexing */
+	data->mux_dropped_timer = g_timeout_add(1000, send_cfun_disable, modem);
+
+	return -EINPROGRESS;
+}
+
+static void m95_pre_sim(struct ofono_modem *modem)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+	struct ofono_sim *sim;
+
+	DBG("%p", modem);
+
+	ofono_devinfo_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
+	sim = ofono_sim_create(modem, OFONO_VENDOR_QUECTEL_M95, "atmodem",
+				data->dlcs[VOICE_DLC]);
+
+	if (sim)
+		ofono_sim_inserted_notify(sim, TRUE);
+
+	ofono_voicecall_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
+	ofono_call_volume_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
+}
+
+static void m95_post_sim(struct ofono_modem *modem)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+	struct ofono_gprs *gprs;
+	struct ofono_gprs_context *gc;
+
+	DBG("%p", modem);
+
+	gprs = ofono_gprs_create(modem, 0, "atmodem", data->dlcs[GPRS_DLC]);
+	if (gprs == NULL)
+		return;
+
+	gc = ofono_gprs_context_create(modem, OFONO_VENDOR_QUECTEL, "atmodem",
+					data->dlcs[GPRS_DLC]);
+	if (gc)
+		ofono_gprs_add_context(gprs, gc);
+}
+
+static void qinistat_cb(gboolean ok, GAtResult *result, gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+	struct m95_data *data = ofono_modem_get_data(modem);
+	GAtResultIter iter;
+	int status;
+
+	DBG("%p", modem);
+
+	g_at_result_iter_init(&iter, result);
+
+	if (!g_at_result_iter_next(&iter, "+QINISTAT:"))
+		return;
+
+	if (!g_at_result_iter_next_number(&iter, &status))
+		return;
+
+	DBG("qinistat: %d", status);
+
+	switch (status) {
+	case 3:
+		g_source_remove(data->qinitstat_timer);
+
+		if (data->have_sms == FALSE) {
+			ofono_sms_create(modem, 0, "atmodem",
+						data->dlcs[SMS_DLC]);
+			data->have_sms = TRUE;
+		}
+		/* fallthrough */
+	case 2:
+		if (data->have_phonebook == FALSE) {
+			ofono_phonebook_create(modem, 0, "atmodem",
+						data->dlcs[VOICE_DLC]);
+			data->have_phonebook = TRUE;
+		}
+		break;
+	}
+}
+
+static gboolean send_qinistat_cb(gpointer user_data)
+{
+	struct ofono_modem *modem = user_data;
+	struct m95_data *data = ofono_modem_get_data(modem);
+
+	/* QINISTAT tells us when SMS/phonebook is ready to be used */
+	g_at_chat_send(data->dlcs[SMS_DLC], "AT+QINISTAT",
+			qinistat_prefix, qinistat_cb, modem, NULL);
+
+	return TRUE;
+}
+
+static void m95_post_online(struct ofono_modem *modem)
+{
+	struct m95_data *data = ofono_modem_get_data(modem);
+
+	DBG("%p", modem);
+
+	ofono_netreg_create(modem, OFONO_VENDOR_QUECTEL, "atmodem",
+				data->dlcs[NETREG_DLC]);
+	ofono_ussd_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
+
+	/* start timer to detect when phonebook and sms services are ready */
+	data->qinitstat_timer = g_timeout_add(500, send_qinistat_cb, modem);
+}
+
+static struct ofono_modem_driver m95_driver = {
+	.name		= "m95",
+	.probe		= m95_probe,
+	.remove		= m95_remove,
+	.enable		= m95_enable,
+	.disable	= m95_disable,
+	.pre_sim	= m95_pre_sim,
+	.post_sim	= m95_post_sim,
+	.post_online	= m95_post_online,
+};
+
+static int m95_init(void)
+{
+	return ofono_modem_driver_register(&m95_driver);
+}
+
+static void m95_exit(void)
+{
+	ofono_modem_driver_unregister(&m95_driver);
+}
+
+OFONO_PLUGIN_DEFINE(m95, "Quectel M95 modem driver", VERSION,
+			OFONO_PLUGIN_PRIORITY_DEFAULT, m95_init, m95_exit)
diff --git a/plugins/udevng.c b/plugins/udevng.c
index 71a70f0b..c0f3e5a2 100644
--- a/plugins/udevng.c
+++ b/plugins/udevng.c
@@ -1303,6 +1303,7 @@ static struct {
 	{ "wavecom",	setup_wavecom		},
 	{ "tc65",	setup_tc65		},
 	{ "ehs6",	setup_ehs6		},
+	{ "m95",	setup_serial_modem	},
 	{ }
 };
 
-- 
2.18.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query
  2018-08-28  7:45 [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
  2018-08-28  7:45 ` [PATCHv2 2/2] m95: Add driver for Quectel M95 modem Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
@ 2018-08-28 19:10 ` Denis Kenzior
  1 sibling, 0 replies; 4+ messages in thread
From: Denis Kenzior @ 2018-08-28 19:10 UTC (permalink / raw)
  To: ofono

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

Hi Martin,

On 08/28/2018 02:45 AM, Martin Hundebøll wrote:
> The AT command reference for Quectel M95 specifies that remaining SIM
> pin retires can be queried using AT+QTRPIN, which responds with one
> count for each pin-type:
> 
> +QTRPIN: 3,3,10,10
> 
> After entering the PIN code, enable an extra AT+CPIN? for the M95
> vendor.
> 

Applied, thanks.

Regards,
-Denis


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCHv2 2/2] m95: Add driver for Quectel M95 modem
  2018-08-28  7:45 ` [PATCHv2 2/2] m95: Add driver for Quectel M95 modem Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
@ 2018-08-28 20:08   ` Denis Kenzior
  0 siblings, 0 replies; 4+ messages in thread
From: Denis Kenzior @ 2018-08-28 20:08 UTC (permalink / raw)
  To: ofono

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

Hi Martin,

> +static int m95_probe(struct ofono_modem *modem)
> +{
> +	struct m95_data *data;
> +
> +	DBG("%p", modem);
> +
> +	data = g_new0(struct m95_data, 1);
> +	if (data == NULL)
> +		return -ENOMEM;

g_new0 cannot fail, so I would take this out

> +
> +	ofono_modem_set_data(modem, data);
> +
> +	return 0;
> +}
> +

<snip>

> +static GAtChat *create_chat(GIOChannel *channel, struct ofono_modem *modem,

Not sure why you need the modem object here

> +				const char *debug)
> +{
> +	GAtSyntax *syntax;
> +	GAtChat *chat;
> +
> +	if (channel == NULL)
> +		return NULL;
> +
> +	syntax = g_at_syntax_new_gsmv1();
> +	chat = g_at_chat_new(channel, syntax);
> +	g_at_syntax_unref(syntax);
> +
> +	if (chat == NULL)
> +		return NULL;
> +
> +	if (getenv("OFONO_AT_DEBUG"))
> +		g_at_chat_set_debug(chat, m95_debug, (gpointer)debug);

This cast wasn't necessary.

> +
> +	return chat;
> +}

<snip>

> +static void send_cmux(GAtResult *result, gpointer user_data)
> +{
> +	struct ofono_modem *modem = user_data;
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +	GAtResultIter iter;
> +
> +	DBG("%p", modem);
> +
> +	g_at_result_iter_init(&iter, result);
> +	if (!g_at_result_iter_next(&iter, cfun_enable_prefix[0]))

This should likely be just "+CFUN:"

> +		return;

And just returning is not a good idea here..

> +
> +	/* remove the enable-completed listener */
> +	if (data->cfun_enable) {
> +		g_at_chat_unregister(data->setup, data->cfun_enable);
> +		data->cfun_enable = 0;
> +	}
> +
> +	/* setup multiplexing */
> +	g_at_chat_send(data->setup, "AT+CMUX=0,0,5,127,10,3,30,10,2", NULL,
> +			mux_setup_cb, modem, NULL);
> +}
> +
> +static void cfun_enable_cb(gboolean ok, GAtResult *result,
> +				gpointer user_data)
> +{
> +	DBG("ok %d", ok);
> +
> +	if (ok)
> +		send_cmux(result, user_data);

What if CFUN fails?

> +}
> +
> +static void cfun_reset(GAtResult *result, gpointer user_data)
> +{
> +	struct ofono_modem *modem = user_data;
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +	GAtResultIter iter;
> +
> +	DBG("%p", modem);
> +
> +	g_at_result_iter_init(&iter, result);
> +	if (!g_at_result_iter_next(&iter, cfun_reset_prefix[0]))
> +		return;
Same as above, might be clear just using the "CFUN:" string.

> +
> +	/* remove the reset-completed listener */
> +	g_at_chat_unregister(data->setup, data->cfun_reset);
> +	data->cfun_reset = 0;
> +
> +	/* prepare to enable the modem */
> +	g_at_chat_send(data->setup, "ATE0", NULL, NULL, NULL, NULL);
> +	g_at_chat_send(data->setup, "AT+IFC=2,2", NULL, NULL, NULL, NULL);
> +	g_at_chat_send(data->setup, "AT+GMM", NULL, NULL, NULL, NULL);
> +	g_at_chat_send(data->setup, "AT", NULL, NULL, NULL, NULL);
> +	g_at_chat_send(data->setup, "AT+IPR=115200&w", NULL, NULL, NULL, NULL);
> +
> +	/* setup a listener to delay multiplex-setup until modem is ready */
> +	data->cfun_enable = g_at_chat_register(data->setup,
> +						cfun_enable_prefix[0],
> +						send_cmux, FALSE, modem, NULL);

So you're registering to +CPIN: <ready/locked> indication here.  Why are 
you then sending the prefixes to the subsequent g_at_chat_send to 
consume those prefixes?

E.g. lets suppose the possible AT command sequences are roughly like:

Sequence 1:
AT+CFUN=1\r
+CPIN: <ready>
OK

Sequence 2:
AT+CFUN=1\r
OK
+CPIN: <ready>

For Sequence1, you are consuming +CPIN as part of the CFUN command.  You 
could for example use:

static const char *none_prefix[] = { NULL }
g_at_chat_send(.., "AT+CFUN=1", none_prefix, ...)

In this case the send_cmux would be called regardless of the sequence 
and you can simplify the logic in cfun_enable_cb...

> +
> +	/* enable the modem; responds with +CPIN: <ready/locked> when ready */
> +	g_at_chat_send(data->setup, "AT+CFUN=1", cfun_enable_prefix,
> +			cfun_enable_cb, modem, NULL);
> +}
> +
> +static void cfun_reset_cb(gboolean ok, GAtResult *result, gpointer user_data)
> +{
> +	DBG("ok %d", ok);
> +
> +	if (ok)
> +		cfun_reset(result, user_data);

So what if this fails?

> +}
> +
> +static int m95_enable(struct ofono_modem *modem)
> +{
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +
> +	DBG("%p", modem);
> +
> +	data->setup = open_device(modem, "Device", "Setup: ");
> +	if (data->setup == NULL)
> +		return -EINVAL;
> +
> +	/*
> +	 * when issuing the reset below, the modem is not ready until it
> +	 * responds with "+CFUN: 4", so set up a listener for this
> +	 */
> +	data->cfun_reset = g_at_chat_register(data->setup, cfun_reset_prefix[0],
> +						cfun_reset, FALSE, modem, NULL);
> +
> +	/* bring the modem into a known state by issuing a reset */
> +	g_at_chat_send(data->setup, "AT+CFUN=0,1", cfun_reset_prefix,
> +			cfun_reset_cb, modem, NULL);

So again, what is the sequence of events here?  You might want to just 
use none_prefix here...

> +
> +	return -EINPROGRESS;
> +}
> +
> +static void cfun_disable(GAtResult *result, gpointer user_data)
> +{
> +	struct ofono_modem *modem = user_data;
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +	GAtResultIter iter;
> +
> +	DBG("%p", modem);
> +
> +	g_at_result_iter_init(&iter, result);
> +	if (!g_at_result_iter_next(&iter, cfun_reset_prefix[0]))
> +		return;
> +

Why just an empty return?

> +	/* remove the initial setup AT channel before creating mux-channels */
> +	g_at_chat_unref(data->setup);
> +	data->setup = NULL;
> +
> +	/* device is now in non-functional mode, so shut down it down */
> +	shutdown_device(data);
> +	ofono_modem_set_powered(modem, FALSE);
> +}
> +
> +static void cfun_disable_cb(gboolean ok, GAtResult *result, gpointer user_data)
> +{
> +	DBG("ok %d", ok);
> +
> +	if (ok)
> +		cfun_disable(result, user_data);

So what happens if !ok?

> +}
> +
> +static gboolean send_cfun_disable(gpointer user_data)
> +{
> +	struct ofono_modem *modem = user_data;
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +
> +	/*
> +	 * when issuing the reset below, the modem is not ready until it
> +	 * responds with "+CFUN: 0", so set up a listener for this
> +	 */
> +	data->cfun_reset = g_at_chat_register(data->setup, cfun_reset_prefix[0],
> +						cfun_disable, FALSE, modem, NULL);
> +
> +	/* now put device in non-functional mode */
> +	g_at_chat_send(data->setup, "AT+CFUN=0,1", NULL, cfun_disable_cb, modem,

Note that NULL prefix consumes everything.  Do you want none_prefix here?

> +			NULL);
> +
> +	return FALSE;
> +}
> +
> +static int m95_disable(struct ofono_modem *modem)
> +{
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +
> +	DBG("%p", modem);
> +
> +	/* shut down the device to tear down the muxing channels */
> +	shutdown_device(data);
> +
> +	/* reopen device to get a plain AT control channel */
> +	data->setup = open_device(modem, "Device", "Disable: ");
> +	if (data->setup == NULL)
> +		return -EINVAL;
> +
> +	/* add a timer to wait for device to drop GSM 07.10 multiplexing */
> +	data->mux_dropped_timer = g_timeout_add(1000, send_cfun_disable, modem);
> +
> +	return -EINPROGRESS;
> +}
> +
> +static void m95_pre_sim(struct ofono_modem *modem)
> +{
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +	struct ofono_sim *sim;
> +
> +	DBG("%p", modem);
> +
> +	ofono_devinfo_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
> +	sim = ofono_sim_create(modem, OFONO_VENDOR_QUECTEL_M95, "atmodem",
> +				data->dlcs[VOICE_DLC]);
> +
> +	if (sim)
> +		ofono_sim_inserted_notify(sim, TRUE);
> +
> +	ofono_voicecall_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
> +	ofono_call_volume_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
> +}
> +
> +static void m95_post_sim(struct ofono_modem *modem)
> +{
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +	struct ofono_gprs *gprs;
> +	struct ofono_gprs_context *gc;
> +
> +	DBG("%p", modem);
> +
> +	gprs = ofono_gprs_create(modem, 0, "atmodem", data->dlcs[GPRS_DLC]);
> +	if (gprs == NULL)
> +		return;
> +
> +	gc = ofono_gprs_context_create(modem, OFONO_VENDOR_QUECTEL, "atmodem",
> +					data->dlcs[GPRS_DLC]);
> +	if (gc)
> +		ofono_gprs_add_context(gprs, gc);
> +}
> +
> +static void qinistat_cb(gboolean ok, GAtResult *result, gpointer user_data)
> +{
> +	struct ofono_modem *modem = user_data;
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +	GAtResultIter iter;
> +	int status;
> +
> +	DBG("%p", modem);
> +
> +	g_at_result_iter_init(&iter, result);
> +
> +	if (!g_at_result_iter_next(&iter, "+QINISTAT:"))
> +		return;
> +
> +	if (!g_at_result_iter_next_number(&iter, &status))
> +		return;
> +
> +	DBG("qinistat: %d", status);
> +
> +	switch (status) {
> +	case 3:
> +		g_source_remove(data->qinitstat_timer);
> +
> +		if (data->have_sms == FALSE) {
> +			ofono_sms_create(modem, 0, "atmodem",
> +						data->dlcs[SMS_DLC]);
> +			data->have_sms = TRUE;
> +		}
> +		/* fallthrough */
> +	case 2:
> +		if (data->have_phonebook == FALSE) {
> +			ofono_phonebook_create(modem, 0, "atmodem",
> +						data->dlcs[VOICE_DLC]);
> +			data->have_phonebook = TRUE;
> +		}
> +		break;

Is status a bitmap?  If so, you might want to write this like:

if (status & 2) create phonebook

if (status & 1) create_sms

> +	}
> +}
> +
> +static gboolean send_qinistat_cb(gpointer user_data)
> +{
> +	struct ofono_modem *modem = user_data;
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +
> +	/* QINISTAT tells us when SMS/phonebook is ready to be used */
> +	g_at_chat_send(data->dlcs[SMS_DLC], "AT+QINISTAT",
> +			qinistat_prefix, qinistat_cb, modem, NULL);
> +
> +	return TRUE;
> +}
> +
> +static void m95_post_online(struct ofono_modem *modem)
> +{
> +	struct m95_data *data = ofono_modem_get_data(modem);
> +
> +	DBG("%p", modem);
> +
> +	ofono_netreg_create(modem, OFONO_VENDOR_QUECTEL, "atmodem",
> +				data->dlcs[NETREG_DLC]);
> +	ofono_ussd_create(modem, 0, "atmodem", data->dlcs[VOICE_DLC]);
> +
> +	/* start timer to detect when phonebook and sms services are ready */
> +	data->qinitstat_timer = g_timeout_add(500, send_qinistat_cb, modem);

Ideally, there should be no AT commands sent from 
pre_sim/post_sim/post_online callbacks.  Also note that SMS & Phonebook 
are actually post_sim atoms.  Does QINISTAT support unsolicited 
notifications?  If so, might be better to rely on that than the polling 
logic you have...

> +}

Regards,
-Denis

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2018-08-28 20:08 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-28  7:45 [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
2018-08-28  7:45 ` [PATCHv2 2/2] m95: Add driver for Quectel M95 modem Martin =?unknown-8bit?q?Hundeb=C3=B8ll?=
2018-08-28 20:08   ` Denis Kenzior
2018-08-28 19:10 ` [PATCHv2 1/2] atmodem: add Quectel M95 special case for PIN query Denis Kenzior

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.