Re: HSP/HFP ofono bluetooth support for Linux desktop
by Denis Kenzior
Hi Pali,
On 1/8/20 3:25 PM, Pali Rohár wrote:
> Hello!
>
Somehow this went straight to my Junk folder, so I didn't see this
message at all until now.
>
> Audio application (e.g. pulseaudio) really do not want to handle two
> separate services to monitor and process HSP/HFP devices. >
> For audio application are HSP and HFP devices equivalent, they provide
> same features: SCO socket, API for controlling microphone and speaker
> gain; plus optionally specify used codec.
>
> So having two separate services which fully divided for audio
> application purpose does not make sense.
>
> So it is possible that both HSP and HFP audio cards would be available
> via one audio API? Because I do not see how it could be done via design
> like dundee.
>
One API sure. Maybe on multiple services. Honestly, I don't see why
this would be such a burden for PA to watch 2 dbus services instead of
1. From oFono perspective it would make more sense to keep the HSP part
a separate daemon. I could be convinced otherwise if it is indeed a big
burden for PA...
>> You can then implement the same API interfaces for setting up the HSP audio
>> stream as oFono does today (i.e. https://git.kernel.org/pub/scm/network/ofono/ofono.git/tree/doc/handsfree...),
>
> This API is unusable for both HSP and HFP audio streams. In pulseaudio
> it is somehow used, but it is not suitable.
>
Funny. "It is used but not suitable"?
> Main objection for handsfree-audio-api.txt is that it does not provide
> information about locally used codec and somehow mixes air codec and
> local codec. In my hsphfpd.txt I used term "AgentCodec" for bluetooth
> local codec and term "AirCodec" for bluetooth air codec format.
Okay. But, just FYI, at the time there was no hw that could do such
on-the-fly conversions, so this use case wasn't considered/implemented.
There's really no reason why we couldn't extend our APIs to handle this.
>
> Another objection against handsfree-audio-api.txt API is that it is
> bound to HF codecs (via number) and does not support for pass e.g. CSR
> codecs.
True. In retrospect we probably should have used strings. But it was
assumed that vendor extensions would go via the Bluetooth SIG Assigned
Numbers facility. Anyhow, we can always add a 'Register2' method that
could take codecs as a string array or a combination of strings & ints.
And if Register2 was used, then use 'NewConnection2' with a signature
that supports passing in vendor codecs and whatever else that might be
needed.
>
> What is completely missing in that API is controlling volume level.
>
It is there on the CallVolume interface
> And also API does not provide socket MTU information or ability to
> change/specify which codec would be used.
There was no need, we automatically defaulted to using Wide band if
available. Third party codecs are a new use case (for oFono HFP), so
would require an API extension.
>
> Non-audio APIs which are needed to export (for both HSP and HFP profiles):
>
> * battery level (0% - 100%)
> * power source (external, battery, unknown)
> * ability to send "our laptop" battery level and power source to remote device
> * send text message to embedded display
> * process button press event (exported via linux kernel uinput)
>
I think all of these are feasible to support under the current oFono
structure, either via plugins or API extensions.
Regards,
-Denis
1 day, 20 hours
[PATCH_v4 0/5] Private network request to ConnMan
by Guillaume Zajac
Hi,
Changelog from v3 is:
- Add private-network source/include
- ConnMan plugin is independant from emulator
- Each application that need VPN will pass a callback as argument
when private network is requested. This callback will contain the
private network settings.
Guillaume Zajac (5):
gatppp: Add new contructor to use external fd
private-network: add callback typedef drivers and settings
private-network: add request/release functions and new feature to
Makefile.am
emulator: add request/release private network calls
connman: add plugin in oFono to request request/release private
network
Makefile.am | 10 +-
gatchat/gatppp.c | 33 +++++-
gatchat/gatppp.h | 1 +
gatchat/ppp.h | 2 +-
gatchat/ppp_net.c | 40 ++++---
include/private-network.h | 59 +++++++++
plugins/connman.c | 297 +++++++++++++++++++++++++++++++++++++++++++++
src/emulator.c | 49 ++++++--
src/ofono.h | 6 +
src/private-network.c | 89 ++++++++++++++
10 files changed, 556 insertions(+), 30 deletions(-)
create mode 100644 include/private-network.h
create mode 100644 plugins/connman.c
create mode 100644 src/private-network.c
1 week, 1 day
Read/Write EFcfis/EFcphs-cff files
by Jeevaka Badrappan
Hi,
This patch reads and writes the call forwarding unconditional status
from and to the SIM depending on the SIM file availability.
New property needs to be added due to the fact that number won't be
available from the cphs-cff file.
Incase of SIM, EFcphs-cff file holds call forwarding status and it
is represented as a flag. In case of USIM(EFcfis), we have the status
flag and also number.So, adding new property for status and using the
existing VoiceUnconditional with number will work for both SIM and USIM cases.
Other option is to have 2 properties, "VoiceUnconditional" and "Number".
"VoiceUnconditional" will have the status of the call forwarding( "enabled",
"disabled") whereas the "Number" property will have the call forwared number.
offline-online state transitions results in caching the call forwaring status
every time. To avoid this, call forwarding atom is moved to the post sim and
its moved also due to the fact that call forwarding status doesn't change in
roaming.
Regards,
Jeevaka
Jeevaka Badrappan (7):
call-forwarding: Read/Write cfis/cphs-cff
ifx: Move call forwarding to post sim
isigen: Move call forwarding to post sim
plugins/n900: Move call forwarding to post sim
phonesim: Move call forwarding to post sim
doc: Add new property to call forwarding
TODO: Marking the Read/Write EFcfis task as done
TODO | 9 --
doc/call-forwarding-api.txt | 5 +
doc/features.txt | 5 +
plugins/ifx.c | 2 +-
plugins/isigen.c | 2 +-
plugins/n900.c | 2 +-
plugins/phonesim.c | 3 +-
src/call-forwarding.c | 242 ++++++++++++++++++++++++++++++++++++++++++-
8 files changed, 256 insertions(+), 14 deletions(-)
1 week, 2 days
[PATCH] Simcom support
by Anthony Viallard
Add SIMCOM support.
I developped this with the SIM5216E chipset and ofono 1.12.
- SMS and GPRS work (in the same time) ;
- SIM card presence check ;
- No voice part because I can't test it ;
- Use default characters set instead GSM because it works like that
for what I'm doing (SMS+GPRS) (by default, the set is IRA for SIM5216E).
Also, the SIMCOM doc affraids me about problems when using GSM
(this setting causes easily software flow control (XON /XOFF) problems.).
Signed-off-by: Anthony Viallard <homer242 at gmail.com>
--- ofono-1.12.orig/Makefile.am 2012-04-20 21:06:29.000000000 +0200
+++ ofono-1.12/Makefile.am 2013-01-21 17:17:48.089627277 +0100
@@ -371,6 +371,9 @@ builtin_sources += plugins/samsung.c
builtin_modules += sim900
builtin_sources += plugins/sim900.c
+builtin_modules += simcom
+builtin_sources += plugins/simcom.c
+
if BLUETOOTH
builtin_modules += bluetooth
builtin_sources += plugins/bluetooth.c plugins/bluetooth.h
--- ofono-1.12.orig/drivers/atmodem/sms.c 2012-04-20 21:06:29.000000000 +0200
+++ ofono-1.12/drivers/atmodem/sms.c 2013-01-21 16:48:44.460627485 +0100
@@ -805,6 +807,7 @@ static gboolean build_cnmi_string(char *
case OFONO_VENDOR_NOVATEL:
case OFONO_VENDOR_HUAWEI:
case OFONO_VENDOR_ZTE:
+ case OFONO_VENDOR_SIMCOM:
/* MSM devices advertise support for mode 2, but return an
* error if we attempt to actually use it. */
mode = "1";
diff -pruN ofono-1.12.orig/drivers/atmodem/sim.c ofono-1.12/drivers/atmodem/sim.c
--- ofono-1.12.orig/drivers/atmodem/sim.c 2013-01-23 11:38:22.959609087 +0100
+++ ofono-1.12/drivers/atmodem/sim.c 2013-01-23 11:57:52.602608948 +0100
@@ -1023,12 +1023,18 @@ static void at_pin_send_cb(gboolean ok,
FALSE, cbd, g_free);
return;
case OFONO_VENDOR_ZTE:
case OFONO_VENDOR_ALCATEL:
case OFONO_VENDOR_HUAWEI:
+ case OFONO_VENDOR_SIMCOM:
/*
* On ZTE modems, after pin is entered, SIM state is checked
* by polling CPIN as their modem doesn't provide unsolicited
* notification of SIM readiness.
+ *
+ * On SIMCOM modems, SIM is busy after pin is entered (we've
+ * got an "+CME ERROR: 14" at "AT+CPIN?" request) and ofono
+ * don't catch the "+CPIN: READY" message sent by the modem
+ * when SIM is ready. So, use extra CPIN to check the state.
*/
sd->sim_state_query = at_util_sim_state_query_new(sd->chat,
2, 20, sim_state_cb, cbd,
diff -purN ofono-1.12/drivers/atmodem/network-registration.c ofono-patched/drivers/atmodem/network-registration.c
--- ofono-1.12/drivers/atmodem/network-registration.c 2013-01-18 15:04:03.598659165 +0100
+++ ofono-patched/drivers/atmodem/network-registration.c 2013-01-18 14:54:03.256659236 +0100
@@ -1411,6 +1411,14 @@ static void at_creg_set_cb(gboolean ok,
}
switch (nd->vendor) {
+ case OFONO_VENDOR_SIMCOM:
+ /* Register for CSQ changes */
+ g_at_chat_send(nd->chat, "AT+AUTOCSQ=1,1", none_prefix,
+ NULL, NULL, NULL);
+
+ g_at_chat_register(nd->chat, "+CSQ:",
+ csq_notify, FALSE, netreg, NULL);
+ break;
case OFONO_VENDOR_PHONESIM:
g_at_chat_register(nd->chat, "+CSQ:",
csq_notify, FALSE, netreg, NULL);
@@ -1534,7 +1537,6 @@ static void at_creg_set_cb(gboolean ok,
break;
case OFONO_VENDOR_NOKIA:
case OFONO_VENDOR_SAMSUNG:
- case OFONO_VENDOR_SIMCOM:
/* Signal strength reporting via CIND is not supported */
break;
default:
--- /dev/null 2013-01-28 10:34:59.843091650 +0100
+++ ofono-1.12/plugins/simcom.c 2013-02-15 16:16:38.058552544 +0100
@@ -0,0 +1,401 @@
+/*
+ *
+ * oFono - Open Source Telephony
+ *
+ * Copyright (C) 2008-2011 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 <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+#include <gatchat.h>
+#include <gattty.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/cbs.h>
+#include <ofono/sms.h>
+#include <ofono/ussd.h>
+#include <ofono/gprs.h>
+#include <ofono/gprs-context.h>
+#include <ofono/radio-settings.h>
+#include <ofono/phonebook.h>
+#include <ofono/log.h>
+
+#include <drivers/atmodem/atutil.h>
+#include <drivers/atmodem/vendor.h>
+
+#define MAX_IGNITION_POOL_CALL 7
+
+#define CMEERR_SIMBUSY 14
+
+static const char *none_prefix[] = { NULL };
+
+struct simcom_data {
+ GAtChat *modem;
+ GAtChat *data;
+ guint ignition_pool;
+ unsigned int ignition_pool_call;
+ unsigned int at_ignition_pending;
+ ofono_bool_t have_sim;
+};
+
+/* Callback and helpers functions */
+static void simcom_debug(const char *str, void *user_data)
+{
+ const char *prefix = user_data;
+
+ ofono_info("%s%s", prefix, str);
+}
+
+static gboolean simcom_ignition(gpointer user_data)
+{
+ struct ofono_modem *modem = user_data;
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ ++data->ignition_pool_call;
+
+ if(data->at_ignition_pending > 0)
+ {
+ if(data->ignition_pool_call > MAX_IGNITION_POOL_CALL)
+ {
+ ofono_error("Ignition timeout");
+ return FALSE;
+ }
+
+ /* Waiting reply of AT commands */
+ DBG("Waiting AT reply...");
+ return TRUE;
+ }
+
+ ofono_modem_set_powered(modem, TRUE);
+
+ return FALSE;
+}
+
+static void simcom_sim_status(gboolean ok, GAtResult *result, gpointer user_data)
+{
+ struct ofono_modem *modem = user_data;
+ struct ofono_error error;
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ --data->at_ignition_pending;
+
+ if(!ok)
+ {
+ decode_at_error(&error, g_at_result_final_response(result));
+ if(error.type == OFONO_ERROR_TYPE_CME)
+ {
+ if(error.error == CMEERR_SIMBUSY)
+ {
+ DBG("System is busy. Retry...");
+ g_at_chat_send(data->data, "AT+CPIN?",
+ none_prefix,
+ simcom_sim_status, modem,
+ NULL);
+ ++data->at_ignition_pending;
+ return;
+ }
+ }
+
+ data->have_sim = FALSE;
+ return;
+ }
+
+ /* If doesn't have an "fatal" error on AT+CPIN request,
+ * we can guess there a SIM card ...
+ */
+ data->have_sim = TRUE;
+}
+
+static void cfun_enable(gboolean ok, GAtResult *result, gpointer user_data)
+{
+ struct ofono_modem *modem = user_data;
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ DBG("");
+
+ if (!ok) {
+ g_at_chat_unref(data->modem);
+ data->modem = NULL;
+
+ g_at_chat_unref(data->data);
+ data->data = NULL;
+
+ ofono_modem_set_powered(modem, FALSE);
+ return;
+ }
+
+ /* Get model and sim card status */
+ data->at_ignition_pending = 0;
+
+ g_at_chat_send(data->data, "AT+CPIN?", none_prefix,
+ simcom_sim_status, modem, NULL);
+ ++data->at_ignition_pending;
+
+ data->ignition_pool = g_timeout_add_seconds(1,
+ simcom_ignition,
+ modem);
+}
+
+static void cfun_disable(gboolean ok, GAtResult *result, gpointer user_data)
+{
+ struct ofono_modem *modem = user_data;
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ DBG("");
+
+ g_at_chat_unref(data->data);
+ data->data = NULL;
+
+ if (ok)
+ ofono_modem_set_powered(modem, FALSE);
+}
+
+static GAtChat *open_device(struct ofono_modem *modem,
+ const char *key,
+ char *debug)
+{
+ const char *device;
+ GIOChannel *channel;
+ GAtSyntax *syntax;
+ GAtChat *chat;
+ /* GHashTable *options; */
+
+ device = ofono_modem_get_string(modem, key);
+ if (device == NULL)
+ {
+ ofono_error("Failed to get modem '%s'", key);
+ 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, "RtsCts", "on"); */
+ /* g_hash_table_insert(options, "Local", "on"); */
+ /* g_hash_table_insert(options, "Read", "on"); */
+
+ channel = g_at_tty_open(device, NULL);
+
+ /* g_hash_table_destroy(options); */
+
+ if (channel == NULL)
+ {
+ ofono_error("Failed to get tty for '%s'", key);
+ return NULL;
+ }
+
+ syntax = g_at_syntax_new_gsm_permissive();
+ chat = g_at_chat_new(channel, syntax);
+ g_at_syntax_unref(syntax);
+
+ g_io_channel_unref(channel);
+
+ if (chat == NULL)
+ {
+ ofono_error("Failed to get chat for '%s'", key);
+ return NULL;
+ }
+
+ //if (getenv("OFONO_AT_DEBUG"))
+ g_at_chat_set_debug(chat, simcom_debug, debug);
+
+ return chat;
+}
+
+/* Modem interface function */
+static int simcom_probe(struct ofono_modem *modem)
+{
+ struct simcom_data *data;
+
+ DBG("%p", modem);
+
+ data = g_try_new0(struct simcom_data, 1);
+ if (data == NULL)
+ return -ENOMEM;
+
+ ofono_modem_set_data(modem, data);
+
+ return 0;
+}
+
+static void simcom_remove(struct ofono_modem *modem)
+{
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ DBG("%p", modem);
+
+ if(data->ignition_pool > 0)
+ {
+ g_source_remove(data->ignition_pool);
+ data->ignition_pool = 0;
+ }
+
+ ofono_modem_set_data(modem, NULL);
+
+ /* Cleanup after hot-unplug */
+ g_at_chat_unref(data->data);
+
+ g_free(data);
+}
+
+static int simcom_enable(struct ofono_modem *modem)
+{
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ DBG("%p", modem);
+
+ data->modem = open_device(modem, "Modem", "Modem: ");
+ if (data->modem == NULL)
+ return -EINVAL;
+
+ data->data = open_device(modem, "Data", "Data: ");
+ if (data->data == NULL) {
+ g_at_chat_unref(data->modem);
+ data->modem = NULL;
+ return -EIO;
+ }
+
+ g_at_chat_set_slave(data->modem, data->data);
+
+ g_at_chat_blacklist_terminator(data->data,
+ G_AT_CHAT_TERMINATOR_NO_CARRIER);
+
+ /* init modem */
+ g_at_chat_send(data->modem, "ATE0 +CMEE=1", NULL, NULL, NULL, NULL);
+ g_at_chat_send(data->data, "ATE0 +CMEE=1", NULL, NULL, NULL, NULL);
+
+ g_at_chat_send(data->data, "AT+CFUN=1", none_prefix,
+ cfun_enable, modem, NULL);
+
+ return -EINPROGRESS;
+}
+
+static int simcom_disable(struct ofono_modem *modem)
+{
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ DBG("%p", modem);
+
+ g_at_chat_cancel_all(data->modem);
+ g_at_chat_unregister_all(data->modem);
+
+ g_at_chat_unref(data->modem);
+ data->modem = NULL;
+
+ g_at_chat_cancel_all(data->data);
+ g_at_chat_unregister_all(data->data);
+
+ g_at_chat_send(data->data, "AT+CFUN=4", none_prefix,
+ cfun_disable, modem, NULL);
+
+ return -EINPROGRESS;
+}
+
+static void simcom_pre_sim(struct ofono_modem *modem)
+{
+ struct simcom_data *data = ofono_modem_get_data(modem);
+ struct ofono_sim *sim;
+
+ DBG("%p", modem);
+
+ ofono_devinfo_create(modem, 0, "atmodem", data->data);
+ sim = ofono_sim_create(modem, OFONO_VENDOR_SIMCOM, "atmodem",
+ data->data);
+
+ if (sim)
+ ofono_sim_inserted_notify(sim, data->have_sim);
+}
+
+static void simcom_post_sim(struct ofono_modem *modem)
+{
+ struct simcom_data *data = ofono_modem_get_data(modem);
+ struct ofono_message_waiting *mw;
+ struct ofono_gprs *gprs;
+ struct ofono_gprs_context *gc;
+
+ DBG("%p", modem);
+
+ ofono_phonebook_create(modem, 0, "atmodem", data->data);
+
+ ofono_sms_create(modem, OFONO_VENDOR_SIMCOM, "atmodem",
+ data->data);
+
+ /* gprs things */
+ gprs = ofono_gprs_create(modem, 0, "atmodem", data->data);
+ gc = ofono_gprs_context_create(modem, 0, "atmodem", data->modem);
+
+ if(gprs && gc)
+ {
+ ofono_gprs_add_context(gprs, gc);
+ }
+}
+
+static void simcom_post_online(struct ofono_modem *modem)
+{
+ struct simcom_data *data = ofono_modem_get_data(modem);
+
+ DBG("%p", modem);
+
+ ofono_netreg_create(modem, OFONO_VENDOR_SIMCOM, "atmodem", data->data);
+ ofono_cbs_create(modem, 0, "atmodem", data->data);
+ ofono_ussd_create(modem, 0, "atmodem", data->data);
+}
+
+static struct ofono_modem_driver simcom_driver = {
+ .name = "simcom",
+ .probe = simcom_probe,
+ .remove = simcom_remove,
+ .enable = simcom_enable,
+ .disable = simcom_disable,
+ .pre_sim = simcom_pre_sim,
+ .post_sim = simcom_post_sim,
+ .post_online = simcom_post_online,
+};
+
+static int simcom_init(void)
+{
+ return ofono_modem_driver_register(&simcom_driver);
+}
+
+static void simcom_exit(void)
+{
+ ofono_modem_driver_unregister(&simcom_driver);
+}
+
+OFONO_PLUGIN_DEFINE(simcom, "SIMCOM modem driver", VERSION,
+ OFONO_PLUGIN_PRIORITY_DEFAULT,
+ simcom_init, simcom_exit)
1 week, 2 days
IPV6 question
by Enrico Sau
Hi all,
As far as I understand, ofono doen't support IPV6 over ppp due to the fact
that IPV6 compression protocol implementation is missing.
Is that right?
Thank you,
Enrico
1 month
Re: AW: AW: AW: connmand[186]: Online check failed but running dhclient manually fixes this issue
by Daniel Wagner
Hi Vinothkumar,
[sorry for the last mails where I got your last name mixed up...]
On 08/10/2017 07:03 AM, Eswaran Vinothkumar (BEG/PJ-IOT-EL) wrote:
> Could you send me the exact modem description you are using? So that we can write quirk?
>
> Hi,
>
> I am using TELIT 910 EUG Modem.
Okay, maybe that is already enough. I saw that in drivers/telit.c there
is already some code to handle variants of the modem. The matching is
done on these strings.
Are there any vid/pid available fot his modem?
Thanks,
Daniel
1 month, 3 weeks
Business
by Daser Jnr.
Hi all
>From a business point of view, can some one tell me what i can do with ofono
Cheers
Daser S.
4 months
[PATCH 0/7] Add quectel EC21 in serial mode
by poeschel@lemonage.de
From: Lars Poeschel <poeschel(a)lemonage.de>
This patchset adds support for the quectel EC21 LTE modem connected
through its serial port.
Lars Poeschel (7):
quectel: Add Quectel EC21 to known serial modems
quectel: use lte atom on EC21
quectel: Query the model before setting up the mux
quectel: EC21 needs aux channel to be the first mux channel
quectel: EC21 does not understand AT+QIURC
voicecall: Quectel modem do not understand AT+CNAP
quectel: EC21 add ussd with atmodem driver
drivers/atmodem/voicecall.c | 4 +-
plugins/quectel.c | 153 +++++++++++++++++++++++-------------
2 files changed, 101 insertions(+), 56 deletions(-)
--
2.26.2
1 year, 10 months
Weird Droid 4 modem protocol and a way to support it
by Pavel Machek
Hi!
I'd really like to get support for Droid 4 modem... unfortunately it
is quite special. Few words about Droid 4 modem protocol:
diff --git a/drivers/motorolamodem/README b/drivers/motorolamodem/README
new file mode 100644
index 00000000..fe3a1e83
--- /dev/null
+++ b/drivers/motorolamodem/README
@@ -0,0 +1,40 @@
+
+This is really "interesting". The protocol looks like AT commands on
+the first look, and some commands are same (ATD does the call, ATA
+answers it), but often they are subtly different (ATD takes ,0
+parameter to work with caller id), and overall protocol is very
+different.
+
+1) protocol is packet-based, not character-based.
+
+Whole command needs to be sent as one packet, in one write() call to
+the kernel. Replies came back in packets, too, you can get them with
+single read() call.
+
+2) protocol semantics is really different.
+
+You don't have command, responses to the command, then response
+terminator. You send one packet and get one packet with the reply.
+
+In AT you got:
+
+> AT+TELL_ME_SOMETHING
+< IT_IS_42
+< OK
+
+While in motmdm it is:
+
+> U0005AT+TELL_ME
+< U0005OK:IT_IS_42
+
+
+AFAICT lot of complexity in atchat is collecting the multiple lines,
+including handling stuff like
+
+IT_IS="Arbitrary
+text including
+OK
+here"
+OK
+
+I don't believe we need that for d4.
\ No newline at end of file
I'm not sure what is the best way to support it. I was not able to get
atchat.c to work with it (and I don't think it is quite suitable), so
I ended up copying it and modifying it for Droid 4 protocol.
Is that acceptable? Can you see a better way?
I do have code for basic voicecall support, SMSes and some kind of
netreg support. But it is quite simple, needs more cleanups, and this
mail is already too long.
Best regards,
Pavel
diff --git a/gatchat/motchat.c b/gatchat/motchat.c
new file mode 100644
index 00000000..bf5979a8
--- /dev/null
+++ b/gatchat/motchat.c
@@ -0,0 +1,1220 @@
+/*
+ *
+ * MOTMDM chat library with GLib integration
+ *
+ * Copyright (C) 2008-2011 Intel Corporation. All rights reserved.
+ * Copyright (C) 2020 Pavel Machek <pavel(a)ucw.cz>
+ *
+ * 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
+ *
+ */
+
+/* FIXME: remove: terminators?
+
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include "ringbuffer.h"
+#include "motchat.h"
+#include "gatio.h"
+
+/* #define WRITE_SCHEDULER_DEBUG 1 */
+
+struct mot_chat;
+static void chat_wakeup_writer(struct mot_chat *chat);
+
+struct at_command {
+ char *cmd;
+ char **prefixes;
+ guint flags;
+ guint id;
+ guint gid;
+ GAtResultFunc callback;
+ GAtNotifyFunc listing;
+ gpointer user_data;
+ GDestroyNotify notify;
+};
+
+struct at_notify_node {
+ guint id;
+ guint gid;
+ GAtNotifyFunc callback;
+ gpointer user_data;
+ GDestroyNotify notify;
+ gboolean destroyed;
+};
+
+typedef gboolean (*node_remove_func)(struct at_notify_node *node,
+ gpointer user_data);
+
+struct at_notify {
+ GSList *nodes;
+ gboolean pdu;
+};
+
+static gboolean node_is_destroyed(struct at_notify_node *node, gpointer user)
+{
+ return node->destroyed;
+}
+
+static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
+{
+ const struct at_notify_node *node = a;
+ guint id = GPOINTER_TO_UINT(b);
+
+ if (node->id < id)
+ return -1;
+
+ if (node->id > id)
+ return 1;
+
+ return 0;
+}
+
+static void at_notify_node_destroy(gpointer data, gpointer user_data)
+{
+ struct at_notify_node *node = data;
+
+ if (node->notify)
+ node->notify(node->user_data);
+
+ g_free(node);
+}
+
+static void at_notify_destroy(gpointer user_data)
+{
+ struct at_notify *notify = user_data;
+
+ g_slist_foreach(notify->nodes, at_notify_node_destroy, NULL);
+ g_slist_free(notify->nodes);
+ g_free(notify);
+}
+
+static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
+{
+ const struct at_command *command = a;
+ guint id = GPOINTER_TO_UINT(b);
+
+ if (command->id < id)
+ return -1;
+
+ if (command->id > id)
+ return 1;
+
+ return 0;
+}
+
+static gboolean mot_chat_unregister_all(struct mot_chat *chat,
+ gboolean mark_only,
+ node_remove_func func,
+ gpointer userdata)
+{
+ GHashTableIter iter;
+ struct at_notify *notify;
+ struct at_notify_node *node;
+ gpointer key, value;
+ GSList *p;
+ GSList *c;
+ GSList *t;
+
+ if (chat->notify_list == NULL)
+ return FALSE;
+
+ g_hash_table_iter_init(&iter, chat->notify_list);
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ notify = value;
+
+ p = NULL;
+ c = notify->nodes;
+
+ while (c) {
+ node = c->data;
+
+ if (func(node, userdata) != TRUE) {
+ p = c;
+ c = c->next;
+ continue;
+ }
+
+ if (mark_only) {
+ node->destroyed = TRUE;
+ p = c;
+ c = c->next;
+ continue;
+ }
+
+ if (p)
+ p->next = c->next;
+ else
+ notify->nodes = c->next;
+
+ at_notify_node_destroy(node, NULL);
+
+ t = c;
+ c = c->next;
+ g_slist_free_1(t);
+ }
+
+ if (notify->nodes == NULL)
+ g_hash_table_iter_remove(&iter);
+ }
+
+ return TRUE;
+}
+
+static struct at_command *at_command_create(guint gid, const char *cmd,
+ const char **prefix_list,
+ guint flags,
+ GAtNotifyFunc listing,
+ GAtResultFunc func,
+ gpointer user_data,
+ GDestroyNotify notify,
+ gboolean wakeup)
+{
+ struct at_command *c;
+ gsize len;
+ char **prefixes = NULL;
+
+ if (prefix_list) {
+ int num_prefixes = 0;
+ int i;
+
+ while (prefix_list[num_prefixes])
+ num_prefixes += 1;
+
+ prefixes = g_new(char *, num_prefixes + 1);
+
+ for (i = 0; i < num_prefixes; i++)
+ prefixes[i] = strdup(prefix_list[i]);
+
+ prefixes[num_prefixes] = NULL;
+ }
+
+ c = g_try_new0(struct at_command, 1);
+ if (c == NULL)
+ return 0;
+
+ len = strlen(cmd);
+ c->cmd = g_try_new(char, len + 2);
+ if (c->cmd == NULL) {
+ g_free(c);
+ return 0;
+ }
+
+ memcpy(c->cmd, cmd, len);
+
+ printf("Have command of length %d (%s)\n", len, cmd);
+
+ /* If we have embedded '\r' then this is a command expecting a prompt
+ * from the modem. Embed Ctrl-Z at the very end automatically
+ */
+ if (wakeup == FALSE) {
+ printf("Adding \r or ^z\n");
+ if (strchr(cmd, '\r')) {
+ c->cmd[len++] = 26;
+
+#if 0
+ c->cmd[len++] = 26;
+ c->cmd[len++] = '\n';
+ c->cmd[len] = '\r';
+#endif
+ } else
+ c->cmd[len] = '\r';
+
+ len += 1;
+ }
+
+ c->cmd[len] = '\0';
+
+ c->gid = gid;
+ c->flags = flags;
+ c->prefixes = prefixes;
+ c->callback = func;
+ c->listing = listing;
+ c->user_data = user_data;
+ c->notify = notify;
+
+ return c;
+}
+
+static void at_command_destroy(struct at_command *cmd)
+{
+ if (cmd->notify)
+ cmd->notify(cmd->user_data);
+
+ g_strfreev(cmd->prefixes);
+ g_free(cmd->cmd);
+ g_free(cmd);
+}
+
+static void chat_cleanup(struct mot_chat *chat)
+{
+ struct at_command *c;
+
+ /* Cleanup pending commands */
+ while ((c = g_queue_pop_head(chat->command_queue)))
+ at_command_destroy(c);
+
+ g_queue_free(chat->command_queue);
+ chat->command_queue = NULL;
+
+ /* Cleanup registered notifications */
+ g_hash_table_destroy(chat->notify_list);
+ chat->notify_list = NULL;
+
+ if (chat->pdu_notify) {
+ g_free(chat->pdu_notify);
+ chat->pdu_notify = NULL;
+ }
+
+ if (chat->wakeup) {
+ g_free(chat->wakeup);
+ chat->wakeup = NULL;
+ }
+
+ if (chat->wakeup_timer) {
+ g_timer_destroy(chat->wakeup_timer);
+ chat->wakeup_timer = 0;
+ }
+
+ if (chat->timeout_source) {
+ g_source_remove(chat->timeout_source);
+ chat->timeout_source = 0;
+ }
+}
+
+static void io_disconnect(gpointer user_data)
+{
+ struct mot_chat *chat = user_data;
+
+ chat_cleanup(chat);
+ g_at_io_unref(chat->io);
+ chat->io = NULL;
+
+ if (chat->user_disconnect)
+ chat->user_disconnect(chat->user_disconnect_data);
+}
+
+static void at_notify_call_callback(gpointer data, gpointer user_data)
+{
+ struct at_notify_node *node = data;
+ GAtResult *result = user_data;
+
+ node->callback(result, node->user_data);
+}
+
+static gboolean mot_chat_match_notify(struct mot_chat *chat, char *line)
+{
+ GHashTableIter iter;
+ struct at_notify *notify;
+ gpointer key, value;
+ gboolean ret = FALSE;
+ GAtResult result;
+
+ g_hash_table_iter_init(&iter, chat->notify_list);
+ result.lines = 0;
+ result.final_or_pdu = 0;
+
+ chat->in_notify = TRUE;
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ notify = value;
+
+ if (!g_str_has_prefix(line, key))
+ continue;
+
+ if (notify->pdu) {
+ chat->pdu_notify = line;
+ return TRUE;
+ }
+
+ if (result.lines == NULL)
+ result.lines = g_slist_prepend(NULL, line);
+
+ g_slist_foreach(notify->nodes, at_notify_call_callback,
+ &result);
+ ret = TRUE;
+ }
+
+ chat->in_notify = FALSE;
+
+ if (ret) {
+ g_slist_free(result.lines);
+ g_free(line);
+
+ mot_chat_unregister_all(chat, FALSE, node_is_destroyed, NULL);
+ }
+
+ return ret;
+}
+
+static void mot_chat_finish_command(struct mot_chat *p, gboolean ok, char *final)
+{
+ struct at_command *cmd = g_queue_pop_head(p->command_queue);
+
+ printf("Finish command, final: %s\n", final);
+ /* Cannot happen, but lets be paranoid */
+ if (cmd == NULL)
+ return;
+
+ p->cmd_bytes_written = 0;
+
+ if (g_queue_peek_head(p->command_queue))
+ chat_wakeup_writer(p);
+
+ if (cmd->callback) {
+ GAtResult result;
+
+ result.final_or_pdu = final;
+ result.lines = NULL;
+
+ cmd->callback(ok, &result, cmd->user_data);
+ }
+
+ g_free(final);
+ at_command_destroy(cmd);
+}
+
+static gboolean mot_chat_handle_command_response(struct mot_chat *p,
+ struct at_command *cmd,
+ char *line)
+{
+ printf("command response: %s\n", line);
+
+ if (!strncmp(line, "U0000", 5) && (line[5] != '~')) {
+ mot_chat_finish_command(p, TRUE, line);
+ return TRUE;
+ }
+
+ if (cmd->prefixes) {
+ int n;
+
+ for (n = 0; cmd->prefixes[n]; n++) {
+ printf("Command prefixes: %s / %s\n", line, cmd->prefixes[n]);
+
+ if (g_str_has_prefix(line, cmd->prefixes[n]))
+ goto out;
+ }
+
+ printf("Prefix not found\n");
+ return FALSE;
+ }
+
+out:
+ printf("Going out... pdu/multiline?!\n");
+
+ /* FIXME: cmd->listing support can be removed? */
+
+ return TRUE;
+}
+
+static void have_line(struct mot_chat *p, char *str)
+{
+ /* We're not going to copy terminal <CR><LF> */
+ struct at_command *cmd;
+
+ if (str == NULL)
+ return;
+
+ printf("Have line: %s\n", str);
+
+ if (str[0] == 'U' && isdigit(str[1]) && isdigit(str[2]) && isdigit(str[3]) && isdigit(str[4])) {
+ str[1] = '0';
+ str[2] = '0';
+ str[3] = '0';
+ str[4] = '0';
+ }
+
+ /* Check for echo, this should not happen, but lets be paranoid */
+ if (!strncmp(str, "AT", 2))
+ goto done;
+
+ cmd = g_queue_peek_head(p->command_queue);
+
+ if (cmd && p->cmd_bytes_written > 0) {
+ char c = cmd->cmd[p->cmd_bytes_written - 1];
+
+ printf("Last character is %d\n", c);
+
+ /* We check that we have submitted a terminator, in which case
+ * a command might have failed or completed successfully
+ *
+ * In the generic case, \r is at the end of the command, so we
+ * know the entire command has been submitted. In the case of
+ * commands like CMGS, every \r or Ctrl-Z might result in a
+ * final response from the modem, so we check this as well.
+ */
+ if ((c == '\n' || c == 26 || c == 13) &&
+ mot_chat_handle_command_response(p, cmd, str)) {
+ printf("handle command response\n");
+ return;
+ }
+ }
+
+ if (mot_chat_match_notify(p, str) == TRUE) {
+ printf("match notify said true\n");
+ return;
+ }
+
+done:
+ printf("ignoring line\n");
+ /* No matches & no commands active, ignore line */
+ g_free(str);
+}
+
+static char *extract_line(struct mot_chat *p, struct ring_buffer *rbuf)
+{
+ unsigned int wrap = ring_buffer_len_no_wrap(rbuf);
+ unsigned int pos = 0;
+ unsigned char *buf = ring_buffer_read_ptr(rbuf, pos);
+ gboolean in_string = FALSE;
+ int strip_front = 0;
+ int line_length = 0;
+ char *line;
+
+ while (pos < p->read_so_far) {
+ /* FIXME: is the in_string handling neccessary? */
+ if (in_string == FALSE && *buf == '\n') {
+ if (!line_length)
+ strip_front += 1;
+ else
+ break;
+ } else {
+ if (*buf == '"')
+ in_string = !in_string;
+
+ line_length += 1;
+ }
+
+ buf += 1;
+ pos += 1;
+
+ if (pos == wrap)
+ buf = ring_buffer_read_ptr(rbuf, pos);
+ }
+
+ line = g_try_new(char, line_length + 1);
+ if (line == NULL) {
+ ring_buffer_drain(rbuf, p->read_so_far);
+ return NULL;
+ }
+
+ ring_buffer_drain(rbuf, strip_front);
+ ring_buffer_read(rbuf, line, line_length);
+ ring_buffer_drain(rbuf, p->read_so_far - strip_front - line_length);
+
+ line[line_length] = '\0';
+
+ return line;
+}
+
+static void new_bytes(struct ring_buffer *rbuf, gpointer user_data)
+{
+ struct mot_chat *p = user_data;
+ unsigned int len = ring_buffer_len(rbuf);
+ unsigned int wrap = ring_buffer_len_no_wrap(rbuf);
+ unsigned char *buf = ring_buffer_read_ptr(rbuf, p->read_so_far);
+
+ p->in_read_handler = TRUE;
+
+ while (p->suspended == FALSE && (p->read_so_far < len)) {
+ gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
+ printf("new bytes %d\n", rbytes);
+
+ buf += rbytes;
+ p->read_so_far += rbytes;
+
+ if (p->read_so_far == wrap) {
+ buf = ring_buffer_read_ptr(rbuf, p->read_so_far);
+ wrap = len;
+ }
+
+ have_line(p, extract_line(p, rbuf));
+
+ len -= p->read_so_far;
+ wrap -= p->read_so_far;
+ p->read_so_far = 0;
+ }
+
+ p->in_read_handler = FALSE;
+
+ if (p->destroyed)
+ g_free(p);
+}
+
+
+static gboolean can_write_data(gpointer data)
+{
+ struct mot_chat *chat = data;
+ struct at_command *cmd;
+ gsize bytes_written;
+ gsize towrite;
+ gsize len;
+ char *cr;
+#ifdef WRITE_SCHEDULER_DEBUG
+ int limiter;
+#endif
+
+ /* Grab the first command off the queue and write as
+ * much of it as we can
+ */
+ cmd = g_queue_peek_head(chat->command_queue);
+
+ /* For some reason command queue is empty, cancel write watcher */
+ if (cmd == NULL)
+ return FALSE;
+
+ len = strlen(cmd->cmd);
+
+ /* For some reason write watcher fired, but we've already
+ * written the entire command out to the io channel,
+ * cancel write watcher
+ */
+ if (chat->cmd_bytes_written >= len)
+ return FALSE;
+
+ towrite = len - chat->cmd_bytes_written;
+
+ cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
+
+ if (cr) {
+ //printf("FIXME: hack, not limiting to first r\n");
+ towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
+ }
+
+#ifdef WRITE_SCHEDULER_DEBUG
+ limiter = towrite;
+
+ if (limiter > 1)
+ limiter = 1;
+#endif
+
+ bytes_written = g_at_io_write(chat->io,
+ cmd->cmd + chat->cmd_bytes_written,
+#ifdef WRITE_SCHEDULER_DEBUG
+ limiter
+#else
+ towrite
+#endif
+ );
+
+ if (bytes_written == 0)
+ return FALSE;
+
+ chat->cmd_bytes_written += bytes_written;
+
+ if (bytes_written < towrite)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void chat_wakeup_writer(struct mot_chat *chat)
+{
+ g_at_io_set_write_handler(chat->io, can_write_data, chat);
+}
+
+static void mot_chat_suspend(struct mot_chat *chat)
+{
+ chat->suspended = TRUE;
+
+ g_at_io_set_write_handler(chat->io, NULL, NULL);
+ g_at_io_set_read_handler(chat->io, NULL, NULL);
+ g_at_io_set_debug(chat->io, NULL, NULL);
+}
+
+static void mot_chat_resume(struct mot_chat *chat)
+{
+ chat->suspended = FALSE;
+
+ if (g_at_io_get_channel(chat->io) == NULL) {
+ io_disconnect(chat);
+ return;
+ }
+
+ g_at_io_set_disconnect_function(chat->io, io_disconnect, chat);
+
+ g_at_io_set_debug(chat->io, chat->debugf, chat->debug_data);
+ g_at_io_set_read_handler(chat->io, new_bytes, chat);
+
+ if (g_queue_get_length(chat->command_queue) > 0)
+ chat_wakeup_writer(chat);
+}
+
+static void mot_chat_unref(struct mot_chat *chat)
+{
+ gboolean is_zero;
+
+ is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
+
+ if (is_zero == FALSE)
+ return;
+
+ if (chat->io) {
+ mot_chat_suspend(chat);
+ g_at_io_unref(chat->io);
+ chat->io = NULL;
+ chat_cleanup(chat);
+ }
+
+ if (chat->in_read_handler)
+ chat->destroyed = TRUE;
+ else
+ g_free(chat);
+}
+
+static gboolean mot_chat_set_disconnect_function(struct mot_chat *chat,
+ GAtDisconnectFunc disconnect,
+ gpointer user_data)
+{
+ chat->user_disconnect = disconnect;
+ chat->user_disconnect_data = user_data;
+
+ return TRUE;
+}
+
+static gboolean mot_chat_set_debug(struct mot_chat *chat,
+ GAtDebugFunc func, gpointer user_data)
+{
+
+ chat->debugf = func;
+ chat->debug_data = user_data;
+
+ if (chat->io)
+ g_at_io_set_debug(chat->io, func, user_data);
+
+ return TRUE;
+}
+
+static guint mot_chat_send_common(struct mot_chat *chat, guint gid,
+ const char *cmd,
+ const char **prefix_list,
+ guint flags,
+ GAtNotifyFunc listing,
+ GAtResultFunc func,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ struct at_command *c;
+
+ if (chat == NULL || chat->command_queue == NULL)
+ return 0;
+
+ c = at_command_create(gid, cmd, prefix_list, flags, listing, func,
+ user_data, notify, FALSE);
+ if (c == NULL)
+ return 0;
+
+ c->id = chat->next_cmd_id++;
+
+ g_queue_push_tail(chat->command_queue, c);
+
+ if (g_queue_get_length(chat->command_queue) == 1)
+ chat_wakeup_writer(chat);
+
+ return c->id;
+}
+
+static struct at_notify *at_notify_create(struct mot_chat *chat,
+ const char *prefix,
+ gboolean pdu)
+{
+ struct at_notify *notify;
+ char *key;
+
+ key = g_strdup(prefix);
+ if (key == NULL)
+ return 0;
+
+ notify = g_try_new0(struct at_notify, 1);
+ if (notify == NULL) {
+ g_free(key);
+ return 0;
+ }
+
+ notify->pdu = pdu;
+
+ g_hash_table_insert(chat->notify_list, key, notify);
+
+ return notify;
+}
+
+static gboolean mot_chat_cancel(struct mot_chat *chat, guint group, guint id)
+{
+ GList *l;
+ struct at_command *c;
+
+ if (chat->command_queue == NULL)
+ return FALSE;
+
+ l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
+ at_command_compare_by_id);
+
+ if (l == NULL)
+ return FALSE;
+
+ c = l->data;
+
+ if (c->gid != group)
+ return FALSE;
+
+ if (c == g_queue_peek_head(chat->command_queue) &&
+ chat->cmd_bytes_written > 0) {
+ /* We can't actually remove it since it is most likely
+ * already in progress, just null out the callback
+ * so it won't be called
+ */
+ c->callback = NULL;
+ } else {
+ at_command_destroy(c);
+ g_queue_remove(chat->command_queue, c);
+ }
+
+ return TRUE;
+}
+
+static gboolean mot_chat_cancel_group(struct mot_chat *chat, guint group)
+{
+ int n = 0;
+ struct at_command *c;
+
+ if (chat->command_queue == NULL)
+ return FALSE;
+
+ while ((c = g_queue_peek_nth(chat->command_queue, n)) != NULL) {
+ if (c->id == 0 || c->gid != group) {
+ n += 1;
+ continue;
+ }
+
+ if (n == 0 && chat->cmd_bytes_written > 0) {
+ c->callback = NULL;
+ n += 1;
+ continue;
+ }
+
+ at_command_destroy(c);
+ g_queue_remove(chat->command_queue, c);
+ }
+
+ return TRUE;
+}
+
+static gpointer mot_chat_get_userdata(struct mot_chat *chat,
+ guint group, guint id)
+{
+ GList *l;
+ struct at_command *c;
+
+ if (chat->command_queue == NULL)
+ return NULL;
+
+ l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
+ at_command_compare_by_id);
+
+ if (l == NULL)
+ return NULL;
+
+ c = l->data;
+
+ if (c->gid != group)
+ return NULL;
+
+ return c->user_data;
+}
+
+static guint mot_chat_register(struct mot_chat *chat, guint group,
+ const char *prefix, GAtNotifyFunc func,
+ gboolean expect_pdu, gpointer user_data,
+ GDestroyNotify destroy_notify)
+{
+ struct at_notify *notify;
+ struct at_notify_node *node;
+
+ if (chat->notify_list == NULL)
+ return 0;
+
+ if (func == NULL)
+ return 0;
+
+ if (prefix == NULL)
+ return 0;
+
+ notify = g_hash_table_lookup(chat->notify_list, prefix);
+
+ if (notify == NULL)
+ notify = at_notify_create(chat, prefix, expect_pdu);
+
+ if (notify == NULL || notify->pdu != expect_pdu)
+ return 0;
+
+ node = g_try_new0(struct at_notify_node, 1);
+ if (node == NULL)
+ return 0;
+
+ node->id = chat->next_notify_id++;
+ node->gid = group;
+ node->callback = func;
+ node->user_data = user_data;
+ node->notify = destroy_notify;
+
+ notify->nodes = g_slist_prepend(notify->nodes, node);
+
+ return node->id;
+}
+
+static gboolean mot_chat_unregister(struct mot_chat *chat, gboolean mark_only,
+ guint group, guint id)
+{
+ GHashTableIter iter;
+ struct at_notify *notify;
+ struct at_notify_node *node;
+ gpointer key, value;
+ GSList *l;
+
+ if (chat->notify_list == NULL)
+ return FALSE;
+
+ g_hash_table_iter_init(&iter, chat->notify_list);
+
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ notify = value;
+
+ l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
+ at_notify_node_compare_by_id);
+
+ if (l == NULL)
+ continue;
+
+ node = l->data;
+
+ if (node->gid != group)
+ return FALSE;
+
+ if (mark_only) {
+ node->destroyed = TRUE;
+ return TRUE;
+ }
+
+ at_notify_node_destroy(node, NULL);
+ notify->nodes = g_slist_remove(notify->nodes, node);
+
+ if (notify->nodes == NULL)
+ g_hash_table_iter_remove(&iter);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean node_compare_by_group(struct at_notify_node *node,
+ gpointer userdata)
+{
+ guint group = GPOINTER_TO_UINT(userdata);
+
+ if (node->gid == group)
+ return TRUE;
+
+ return FALSE;
+}
+
+static struct mot_chat *create_chat(GIOChannel *channel, GIOFlags flags)
+{
+ struct mot_chat *chat;
+
+ if (channel == NULL)
+ return NULL;
+
+ chat = g_try_new0(struct mot_chat, 1);
+ if (chat == NULL)
+ return chat;
+
+ chat->ref_count = 1;
+ chat->next_cmd_id = 1;
+ chat->next_notify_id = 1;
+ chat->debugf = NULL;
+
+ if (flags & G_IO_FLAG_NONBLOCK)
+ chat->io = g_at_io_new(channel);
+ else
+ chat->io = g_at_io_new_blocking(channel);
+
+ if (chat->io == NULL)
+ goto error;
+
+ g_at_io_set_disconnect_function(chat->io, io_disconnect, chat);
+
+ chat->command_queue = g_queue_new();
+ if (chat->command_queue == NULL)
+ goto error;
+
+ chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, at_notify_destroy);
+
+ g_at_io_set_read_handler(chat->io, new_bytes, chat);
+
+ return chat;
+
+error:
+ g_at_io_unref(chat->io);
+
+ if (chat->command_queue)
+ g_queue_free(chat->command_queue);
+
+ if (chat->notify_list)
+ g_hash_table_destroy(chat->notify_list);
+
+ g_free(chat);
+ return NULL;
+}
+
+static GMotChat *g_mot_chat_new_common(GIOChannel *channel, GIOFlags flags)
+{
+ GMotChat *chat;
+
+ chat = g_try_new0(GMotChat, 1);
+ if (chat == NULL)
+ return NULL;
+
+ chat->parent = create_chat(channel, flags);
+ if (chat->parent == NULL) {
+ g_free(chat);
+ return NULL;
+ }
+
+ chat->group = chat->parent->next_gid++;
+ chat->ref_count = 1;
+
+ return chat;
+}
+
+GMotChat *g_mot_chat_new(GIOChannel *channel)
+{
+ return g_mot_chat_new_common(channel, G_IO_FLAG_NONBLOCK);
+}
+
+GMotChat *g_mot_chat_new_blocking(GIOChannel *channel)
+{
+ return g_mot_chat_new_common(channel, 0);
+}
+
+GMotChat *g_mot_chat_clone(GMotChat *clone)
+{
+ GMotChat *chat;
+
+ if (clone == NULL)
+ return NULL;
+
+ chat = g_try_new0(GMotChat, 1);
+ if (chat == NULL)
+ return NULL;
+
+ chat->parent = clone->parent;
+ chat->group = chat->parent->next_gid++;
+ chat->ref_count = 1;
+ g_atomic_int_inc(&chat->parent->ref_count);
+
+ if (clone->slave != NULL)
+ chat->slave = g_mot_chat_clone(clone->slave);
+
+ return chat;
+}
+
+GMotChat *g_mot_chat_set_slave(GMotChat *chat, GMotChat *slave)
+{
+ if (chat == NULL)
+ return NULL;
+
+ if (chat->slave != NULL)
+ g_mot_chat_unref(chat->slave);
+
+ if (slave != NULL)
+ chat->slave = g_mot_chat_ref(slave);
+ else
+ chat->slave = NULL;
+
+ return chat->slave;
+}
+
+GMotChat *g_mot_chat_get_slave(GMotChat *chat)
+{
+ if (chat == NULL)
+ return NULL;
+
+ return chat->slave;
+}
+
+GIOChannel *g_mot_chat_get_channel(GMotChat *chat)
+{
+ if (chat == NULL || chat->parent->io == NULL)
+ return NULL;
+
+ return g_at_io_get_channel(chat->parent->io);
+}
+
+GAtIO *g_mot_chat_get_io(GMotChat *chat)
+{
+ if (chat == NULL)
+ return NULL;
+
+ return chat->parent->io;
+}
+
+GMotChat *g_mot_chat_ref(GMotChat *chat)
+{
+ if (chat == NULL)
+ return NULL;
+
+ g_atomic_int_inc(&chat->ref_count);
+
+ return chat;
+}
+
+void g_mot_chat_suspend(GMotChat *chat)
+{
+ if (chat == NULL)
+ return;
+
+ mot_chat_suspend(chat->parent);
+}
+
+void g_mot_chat_resume(GMotChat *chat)
+{
+ if (chat == NULL)
+ return;
+
+ mot_chat_resume(chat->parent);
+}
+
+void g_mot_chat_unref(GMotChat *chat)
+{
+ gboolean is_zero;
+
+ if (chat == NULL)
+ return;
+
+ is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
+
+ if (is_zero == FALSE)
+ return;
+
+ if (chat->slave != NULL)
+ g_mot_chat_unref(chat->slave);
+
+ mot_chat_cancel_group(chat->parent, chat->group);
+ g_mot_chat_unregister_all(chat);
+ mot_chat_unref(chat->parent);
+
+ g_free(chat);
+}
+
+gboolean g_mot_chat_set_disconnect_function(GMotChat *chat,
+ GAtDisconnectFunc disconnect, gpointer user_data)
+{
+ if (chat == NULL || chat->group != 0)
+ return FALSE;
+
+ return mot_chat_set_disconnect_function(chat->parent, disconnect,
+ user_data);
+}
+
+gboolean g_mot_chat_set_debug(GMotChat *chat,
+ GAtDebugFunc func, gpointer user_data)
+{
+
+ if (chat == NULL || chat->group != 0)
+ return FALSE;
+
+ return mot_chat_set_debug(chat->parent, func, user_data);
+}
+
+guint g_mot_chat_send(GMotChat *chat, const char *cmd,
+ const char **prefix_list, GAtResultFunc func,
+ gpointer user_data, GDestroyNotify notify)
+{
+ return mot_chat_send_common(chat->parent, chat->group,
+ cmd, prefix_list, 0, NULL,
+ func, user_data, notify);
+}
+
+gboolean g_mot_chat_cancel(GMotChat *chat, guint id)
+{
+ /* We use id 0 for wakeup commands */
+ if (chat == NULL || id == 0)
+ return FALSE;
+
+ return mot_chat_cancel(chat->parent, chat->group, id);
+}
+
+gboolean g_mot_chat_cancel_all(GMotChat *chat)
+{
+ if (chat == NULL)
+ return FALSE;
+
+ return mot_chat_cancel_group(chat->parent, chat->group);
+}
+
+gpointer g_mot_chat_get_userdata(GMotChat *chat, guint id)
+{
+ if (chat == NULL)
+ return NULL;
+
+ return mot_chat_get_userdata(chat->parent, chat->group, id);
+}
+
+guint g_mot_chat_register(GMotChat *chat, const char *prefix,
+ GAtNotifyFunc func, gboolean expect_pdu,
+ gpointer user_data,
+ GDestroyNotify destroy_notify)
+{
+ if (chat == NULL)
+ return 0;
+
+ return mot_chat_register(chat->parent, chat->group, prefix,
+ func, expect_pdu, user_data, destroy_notify);
+}
+
+gboolean g_mot_chat_unregister(GMotChat *chat, guint id)
+{
+ if (chat == NULL)
+ return FALSE;
+
+ return mot_chat_unregister(chat->parent, chat->parent->in_notify,
+ chat->group, id);
+}
+
+gboolean g_mot_chat_unregister_all(GMotChat *chat)
+{
+ if (chat == NULL)
+ return FALSE;
+
+ return mot_chat_unregister_all(chat->parent,
+ chat->parent->in_notify,
+ node_compare_by_group,
+ GUINT_TO_POINTER(chat->group));
+}
diff --git a/gatchat/motchat.h b/gatchat/motchat.h
new file mode 100644
index 00000000..f778d0d4
--- /dev/null
+++ b/gatchat/motchat.h
@@ -0,0 +1,209 @@
+/*
+ *
+ * AT chat library with GLib integration
+ *
+ * Copyright (C) 2008-2011 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
+ *
+ */
+
+#ifndef __GMOTCHAT_H
+#define __GMOTCHAT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gatresult.h"
+#include "gatutil.h"
+#include "gatio.h"
+
+struct _GMotChat;
+
+typedef struct _GMotChat GMotChat;
+
+typedef void (*GAtResultFunc)(gboolean success, GAtResult *result,
+ gpointer user_data);
+typedef void (*GAtNotifyFunc)(GAtResult *result, gpointer user_data);
+
+enum _GMotChatTerminator {
+ G_MOT_CHAT_TERMINATOR_OK,
+ G_MOT_CHAT_TERMINATOR_ERROR,
+ G_MOT_CHAT_TERMINATOR_NO_DIALTONE,
+ G_MOT_CHAT_TERMINATOR_BUSY,
+ G_MOT_CHAT_TERMINATOR_NO_CARRIER,
+ G_MOT_CHAT_TERMINATOR_CONNECT,
+ G_MOT_CHAT_TERMINATOR_NO_ANSWER,
+ G_MOT_CHAT_TERMINATOR_CMS_ERROR,
+ G_MOT_CHAT_TERMINATOR_CME_ERROR,
+ G_MOT_CHAT_TERMINATOR_EXT_ERROR,
+};
+
+typedef enum _GMotChatTerminator GMotChatTerminator;
+
+GMotChat *g_mot_chat_new(GIOChannel *channel);
+GMotChat *g_mot_chat_new_blocking(GIOChannel *channel);
+
+GIOChannel *g_mot_chat_get_channel(GMotChat *chat);
+GAtIO *g_mot_chat_get_io(GMotChat *chat);
+
+GMotChat *g_mot_chat_ref(GMotChat *chat);
+void g_mot_chat_unref(GMotChat *chat);
+
+GMotChat *g_mot_chat_clone(GMotChat *chat);
+
+GMotChat *g_mot_chat_set_slave(GMotChat *chat, GMotChat *slave);
+GMotChat *g_mot_chat_get_slave(GMotChat *chat);
+
+void g_mot_chat_suspend(GMotChat *chat);
+void g_mot_chat_resume(GMotChat *chat);
+
+gboolean g_mot_chat_set_disconnect_function(GMotChat *chat,
+ GAtDisconnectFunc disconnect, gpointer user_data);
+
+/*!
+ * If the function is not NULL, then on every read/write from the GIOChannel
+ * provided to GMotChat the logging function will be called with the
+ * input/output string and user data
+ */
+gboolean g_mot_chat_set_debug(GMotChat *chat,
+ GAtDebugFunc func, gpointer user_data);
+
+/*!
+ * Queue an AT command for execution. The command contents are given
+ * in cmd. Once the command executes, the callback function given by
+ * func is called with user provided data in user_data.
+ *
+ * Returns an id of the queued command which can be canceled using
+ * g_mot_chat_cancel. If an error occurred, an id of 0 is returned.
+ *
+ * This function can be used in three ways:
+ * - Send a simple command such as g_mot_chat_send(p, "AT+CGMI?", ...
+ *
+ * - Send a compound command: g_mot_chat_send(p, "AT+CMD1;+CMD2", ...
+ *
+ * - Send a command requiring a prompt. The command up to '\r' is sent
+ * after which time a '> ' prompt is expected from the modem. Further
+ * contents of the command are sent until a '\r' or end of string is
+ * encountered. If end of string is encountered, the Ctrl-Z character
+ * is sent automatically. There is no need to include the Ctrl-Z
+ * by the caller.
+ *
+ * The valid_resp field can be used to send an array of strings which will
+ * be accepted as a valid response for this command. This is treated as a
+ * simple prefix match. If a response line comes in from the modem and it
+ * does not match any of the prefixes in valid_resp, it is treated as an
+ * unsolicited notification. If valid_resp is NULL, then all response
+ * lines after command submission and final response line are treated as
+ * part of the command response. This can be used to get around broken
+ * modems which send unsolicited notifications during command processing.
+ */
+guint g_mot_chat_send(GMotChat *chat, const char *cmd,
+ const char **valid_resp, GAtResultFunc func,
+ gpointer user_data, GDestroyNotify notify);
+
+/*!
+ * Same as the above command, except that the caller wishes to receive the
+ * intermediate responses immediately through the GAtNotifyFunc callback.
+ * The final response will still be sent to GAtResultFunc callback. The
+ * final GAtResult will not contain any lines from the intermediate responses.
+ * This is useful for listing commands such as CPBR.
+ */
+guint g_mot_chat_send_listing(GMotChat *chat, const char *cmd,
+ const char **valid_resp,
+ GAtNotifyFunc listing, GAtResultFunc func,
+ gpointer user_data, GDestroyNotify notify);
+
+/*!
+ * Same as g_mot_chat_send_listing except every response line in valid_resp
+ * is expected to be followed by a PDU. The listing function will be called
+ * with the intermediate response and the following PDU line.
+ *
+ * This is useful for PDU listing commands like the +CMGL
+ */
+guint g_mot_chat_send_pdu_listing(GMotChat *chat, const char *cmd,
+ const char **valid_resp,
+ GAtNotifyFunc listing, GAtResultFunc func,
+ gpointer user_data, GDestroyNotify notify);
+
+/*!
+ * Same as g_mot_chat_send except parser will know to expect short prompt syntax
+ * used with +CPOS.
+ */
+guint g_mot_chat_send_and_expect_short_prompt(GMotChat *chat, const char *cmd,
+ const char **valid_resp, GAtResultFunc func,
+ gpointer user_data, GDestroyNotify notify);
+
+gboolean g_mot_chat_cancel(GMotChat *chat, guint id);
+gboolean g_mot_chat_cancel_all(GMotChat *chat);
+
+gpointer g_mot_chat_get_userdata(GMotChat *chat, guint id);
+
+guint g_mot_chat_register(GMotChat *chat, const char *prefix,
+ GAtNotifyFunc func, gboolean expect_pdu,
+ gpointer user_data, GDestroyNotify notify);
+
+gboolean g_mot_chat_unregister(GMotChat *chat, guint id);
+gboolean g_mot_chat_unregister_all(GMotChat *chat);
+
+gboolean g_mot_chat_set_wakeup_command(GMotChat *chat, const char *cmd,
+ guint timeout, guint msec);
+
+void g_mot_chat_add_terminator(GMotChat *chat, char *terminator,
+ int len, gboolean success);
+void g_mot_chat_blacklist_terminator(GMotChat *chat,
+ GMotChatTerminator terminator);
+
+struct _GMotChat {
+ gint ref_count;
+ struct mot_chat *parent;
+ guint group;
+ GMotChat *slave;
+};
+
+struct mot_chat {
+ gint ref_count; /* Ref count */
+ guint next_cmd_id; /* Next command id */
+ guint next_notify_id; /* Next notify id */
+ guint next_gid; /* Next group id */
+ GAtIO *io; /* AT IO */
+ GQueue *command_queue; /* Command queue */
+ guint cmd_bytes_written; /* bytes written from cmd */
+ GHashTable *notify_list; /* List of notification reg */
+ GAtDisconnectFunc user_disconnect; /* user disconnect func */
+ gpointer user_disconnect_data; /* user disconnect data */
+ guint read_so_far; /* Number of bytes processed */
+ gboolean suspended; /* Are we suspended? */
+ GAtDebugFunc debugf; /* debugging output function */
+ gpointer debug_data; /* Data to pass to debug func */
+ char *pdu_notify; /* Unsolicited Resp w/ PDU */
+ GSList *response_lines; /* char * lines of the response */
+ char *wakeup; /* command sent to wakeup modem */
+ gint timeout_source;
+ gdouble inactivity_time; /* Period of inactivity */
+ guint wakeup_timeout; /* How long to wait for resp */
+ GTimer *wakeup_timer; /* Keep track of elapsed time */
+ gboolean destroyed; /* Re-entrancy guard */
+ gboolean in_read_handler; /* Re-entrancy guard */
+ gboolean in_notify;
+ GSList *terminator_list; /* Non-standard terminator */
+ guint16 terminator_blacklist; /* Blacklisted terinators */
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __GATCHAT_H */
1 year, 11 months
Re: [PATCH] udevng: Add support for Quectel BG96 modem
by Martin Hundebøll
Hi Sean,
On 11/25/19 9:26 AM, Sean Nyekjaer wrote:
> Signed-off-by: Sean Nyekjaer <sean(a)geanix.com>
No signoff when submitting to ofono.
// Martin
> ---
> plugins/udevng.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/plugins/udevng.c b/plugins/udevng.c
> index 40ed2b02..cc294c94 100644
> --- a/plugins/udevng.c
> +++ b/plugins/udevng.c
> @@ -1790,6 +1790,8 @@ static struct {
> { "quectelqmi", "qcserial", "2c7c", "0121" },
> { "quectelqmi", "qmi_wwan", "2c7c", "0125" },
> { "quectelqmi", "qcserial", "2c7c", "0125" },
> + { "quectelqmi", "qmi_wwan", "2c7c", "0296" },
> + { "quectelqmi", "qcserial", "2c7c", "0296" },
> { "ublox", "cdc_acm", "1546", "1010" },
> { "ublox", "cdc_ncm", "1546", "1010" },
> { "ublox", "cdc_acm", "1546", "1102" },
>
1 year, 11 months