From 705950bb399781a291a3998014f916fc1effce16 Mon Sep 17 00:00:00 2001 From: Bhushan Shah Date: Thu, 15 Apr 2021 09:29:36 +0530 Subject: [PATCH 01/14] mm-iface: clean out modem_iface if mm disappears otherwise we will be stuck in state where restarting of mm will not reset the modem_iface and it will loop through hard resetting modem --- src/mm-iface.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mm-iface.c b/src/mm-iface.c index d6a74f8..0409236 100644 --- a/src/mm-iface.c +++ b/src/mm-iface.c @@ -183,6 +183,9 @@ static void mm_iface_clean(struct EG25Manager *manager) g_free(manager->modem_usb_id); manager->modem_usb_id = NULL; } + if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) { + manager->modem_iface = MODEM_IFACE_NONE; + } } static void mm_vanished_cb(GDBusConnection *connection, From e690e2a17d9798ad22d961bd4ee2613593d68e44 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Sun, 23 May 2021 20:00:42 +0200 Subject: [PATCH 02/14] at: wake only when sending AT commands Allow the modem to enter soft sleep when we don't talk to the modem using AT commands. This was already the case in suspend, but not during runtime. By only waking the modem from soft sleep when we need to send an AT command, we can save some power. --- src/at.c | 36 +++++++++++++++++++++++------------- src/gpio.c | 24 ++++++++++++++++++++++-- src/gpio.h | 2 ++ 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/at.c b/src/at.c index a34025f..358d95a 100644 --- a/src/at.c +++ b/src/at.c @@ -6,6 +6,7 @@ #include "at.h" #include "suspend.h" +#include "gpio.h" #include #include @@ -59,6 +60,10 @@ static gboolean send_at_command(struct EG25Manager *manager) int ret, len = 0; if (at_cmd) { + /* Wake up the modem from soft sleep before sending an AT command */ + gpio_sequence_wake(manager); + + /* Send AT command */ if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL) len = sprintf(command, "AT+%s\r\n", at_cmd->cmd); else if (at_cmd->subcmd == NULL && at_cmd->value == NULL) @@ -75,23 +80,28 @@ static gboolean send_at_command(struct EG25Manager *manager) g_warning("Couldn't write full AT command: wrote %d/%d bytes", ret, len); g_message("Sending command: %s", g_strstrip(command)); - } else if (manager->modem_state < EG25_STATE_CONFIGURED) { - if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) { + } else { + /* Allow the modem to enter soft sleep again when we sent the AT command*/ + gpio_sequence_sleep(manager); + + if (manager->modem_state < EG25_STATE_CONFIGURED) { + if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) { #ifdef HAVE_MMGLIB - MMModemState modem_state = mm_modem_get_state(manager->mm_modem); + MMModemState modem_state = mm_modem_get_state(manager->mm_modem); - if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED) - modem_update_state(manager, modem_state); - else - manager->modem_state = EG25_STATE_CONFIGURED; + if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED) + modem_update_state(manager, modem_state); + else + manager->modem_state = EG25_STATE_CONFIGURED; #endif - } else { - manager->modem_state = EG25_STATE_CONFIGURED; + } else { + manager->modem_state = EG25_STATE_CONFIGURED; + } + } else if (manager->modem_state == EG25_STATE_SUSPENDING) { + modem_suspend_post(manager); + } else if (manager->modem_state == EG25_STATE_RESETTING) { + manager->modem_state = EG25_STATE_POWERED; } - } else if (manager->modem_state == EG25_STATE_SUSPENDING) { - modem_suspend_post(manager); - } else if (manager->modem_state == EG25_STATE_RESETTING) { - manager->modem_state = EG25_STATE_POWERED; } return FALSE; diff --git a/src/gpio.c b/src/gpio.c index a5d3bf0..617b69e 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -6,6 +6,8 @@ #include "gpio.h" +#include + #define GPIO_CHIP1_LABEL "1c20800.pinctrl" #define GPIO_CHIP2_LABEL "1f02c00.pinctrl" @@ -52,7 +54,6 @@ int gpio_sequence_shutdown(struct EG25Manager *manager) int gpio_sequence_suspend(struct EG25Manager *manager) { gpiod_line_set_value(manager->gpio_out[GPIO_OUT_APREADY], 1); - gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 1); g_message("Executed suspend sequence"); @@ -62,13 +63,32 @@ int gpio_sequence_suspend(struct EG25Manager *manager) int gpio_sequence_resume(struct EG25Manager *manager) { gpiod_line_set_value(manager->gpio_out[GPIO_OUT_APREADY], 0); - gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0); g_message("Executed resume sequence"); return 0; } +int gpio_sequence_wake(struct EG25Manager *manager) +{ + gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0); + + /* Give the modem 5ms to wake from soft sleep */ + usleep(5000); + + g_message("Executed soft wake sequence"); + + return 0; +} + +int gpio_sequence_sleep(struct EG25Manager *manager) +{ + gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 1); + g_message("Executed soft sleep sequence"); + + return 0; +} + static guint get_config_gpio(toml_table_t *config, const char *id) { toml_datum_t value = toml_int_in(config, id); diff --git a/src/gpio.h b/src/gpio.h index 8d94013..a041bdc 100644 --- a/src/gpio.h +++ b/src/gpio.h @@ -15,5 +15,7 @@ int gpio_sequence_poweron(struct EG25Manager *state); int gpio_sequence_shutdown(struct EG25Manager *state); int gpio_sequence_suspend(struct EG25Manager *state); int gpio_sequence_resume(struct EG25Manager *state); +int gpio_sequence_wake(struct EG25Manager *state); +int gpio_sequence_sleep(struct EG25Manager *state); gboolean gpio_check_poweroff(struct EG25Manager *manager, gboolean keep_down); From 63ba5e2d60fc01c0e82287a1459e68f999859bea Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Thu, 3 Jun 2021 17:51:03 +0200 Subject: [PATCH 03/14] Fix parallel build issue Tell meson that the generated gdbofono headers are needed at compile time. fixes https://gitlab.com/mobian1/devices/eg25-manager/-/issues/14 --- src/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/src/meson.build b/src/meson.build index f9eb27f..1bfcd3a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,7 @@ src = [ 'suspend.c', 'suspend.h', 'toml.c', 'toml.h', 'udev.c', 'udev.h', + gdbofono_headers, ] if mmglib_dep.found() From 9c4a934a514a6f9363b1f0d7ba753ec8e9691744 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Wed, 12 May 2021 16:41:48 +0200 Subject: [PATCH 04/14] at: g_free doesn't require NULL checking From the docs: If mem is NULL it simply returns, so there is no need to check mem against NULL before calling this function. --- src/at.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/at.c b/src/at.c index 358d95a..f5f9200 100644 --- a/src/at.c +++ b/src/at.c @@ -114,14 +114,10 @@ static void next_at_command(struct EG25Manager *manager) if (!at_cmd) return; - if (at_cmd->cmd) - g_free(at_cmd->cmd); - if (at_cmd->subcmd) - g_free(at_cmd->subcmd); - if (at_cmd->value) - g_free(at_cmd->value); - if (at_cmd->expected) - g_free(at_cmd->expected); + g_free(at_cmd->cmd); + g_free(at_cmd->subcmd); + g_free(at_cmd->value); + g_free(at_cmd->expected); g_free(at_cmd); manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd); @@ -152,8 +148,7 @@ static void process_at_result(struct EG25Manager *manager, char *response) return; if (at_cmd->expected && !strstr(response, at_cmd->expected)) { - if (at_cmd->value) - g_free(at_cmd->value); + g_free(at_cmd->value); at_cmd->value = at_cmd->expected; at_cmd->expected = NULL; g_message("Got a different result than expected, changing value..."); From 9646e0e8df698e70f05b93c271f91d2ba4e654ab Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Wed, 12 May 2021 18:21:29 +0200 Subject: [PATCH 05/14] at: make next_at_command, send_at_command, process_at_result, and append_at_command public methods Allows other modules to send AT commands as well --- src/at.c | 47 ++++++++++++++++++++++++----------------------- src/at.h | 22 ++++++++++++++++------ 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/at.c b/src/at.c index f5f9200..35ace26 100644 --- a/src/at.c +++ b/src/at.c @@ -53,7 +53,7 @@ static int configure_serial(const char *tty) return fd; } -static gboolean send_at_command(struct EG25Manager *manager) +gboolean at_send_command(struct EG25Manager *manager) { char command[256]; struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL; @@ -107,7 +107,7 @@ static gboolean send_at_command(struct EG25Manager *manager) return FALSE; } -static void next_at_command(struct EG25Manager *manager) +void at_next_command(struct EG25Manager *manager) { struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL; @@ -121,7 +121,7 @@ static void next_at_command(struct EG25Manager *manager) g_free(at_cmd); manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd); - send_at_command(manager); + at_send_command(manager); } static void retry_at_command(struct EG25Manager *manager) @@ -134,13 +134,14 @@ static void retry_at_command(struct EG25Manager *manager) at_cmd->retries++; if (at_cmd->retries > 3) { g_critical("Command %s retried %d times, aborting...", at_cmd->cmd, at_cmd->retries); - next_at_command(manager); + at_next_command(manager); } else { - g_timeout_add(500, G_SOURCE_FUNC(send_at_command), manager); + g_timeout_add(500, G_SOURCE_FUNC(at_send_command), manager); } } -static void process_at_result(struct EG25Manager *manager, char *response) +void at_process_result(struct EG25Manager *manager, + const char *response) { struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL; @@ -153,17 +154,17 @@ static void process_at_result(struct EG25Manager *manager, char *response) at_cmd->expected = NULL; g_message("Got a different result than expected, changing value..."); g_message("\t%s\n\t%s", at_cmd->expected, response); - send_at_command(manager); + at_send_command(manager); } else { - next_at_command(manager); + at_next_command(manager); } } -static int append_at_command(struct EG25Manager *manager, - const char *cmd, - const char *subcmd, - const char *value, - const char *expected) +int at_append_command(struct EG25Manager *manager, + const char *cmd, + const char *subcmd, + const char *value, + const char *expected) { struct AtCommand *at_cmd = calloc(1, sizeof(struct AtCommand)); @@ -223,10 +224,10 @@ static gboolean modem_response(gint fd, else if (strstr(response, "ERROR")) retry_at_command(manager); else if (strstr(response, "OK")) - process_at_result(manager, response); + at_process_result(manager, response); else // Not a recognized response, try running next command, just in case - next_at_command(manager); + at_next_command(manager); } return TRUE; @@ -331,34 +332,34 @@ void at_sequence_configure(struct EG25Manager *manager) { for (guint i = 0; i < configure_commands->len; i++) { struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i); - append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); } - send_at_command(manager); + at_send_command(manager); } void at_sequence_suspend(struct EG25Manager *manager) { for (guint i = 0; i < suspend_commands->len; i++) { struct AtCommand *cmd = &g_array_index(suspend_commands, struct AtCommand, i); - append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); } - send_at_command(manager); + at_send_command(manager); } void at_sequence_resume(struct EG25Manager *manager) { for (guint i = 0; i < resume_commands->len; i++) { struct AtCommand *cmd = &g_array_index(resume_commands, struct AtCommand, i); - append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); } - send_at_command(manager); + at_send_command(manager); } void at_sequence_reset(struct EG25Manager *manager) { for (guint i = 0; i < reset_commands->len; i++) { struct AtCommand *cmd = &g_array_index(reset_commands, struct AtCommand, i); - append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); } - send_at_command(manager); + at_send_command(manager); } diff --git a/src/at.h b/src/at.h index ba294a4..e0445af 100644 --- a/src/at.h +++ b/src/at.h @@ -8,10 +8,20 @@ #include "manager.h" -int at_init(struct EG25Manager *data, toml_table_t *config); -void at_destroy(struct EG25Manager *data); +int at_init(struct EG25Manager *manager, toml_table_t *config); +void at_destroy(struct EG25Manager *manager); -void at_sequence_configure(struct EG25Manager *data); -void at_sequence_suspend(struct EG25Manager *data); -void at_sequence_resume(struct EG25Manager *data); -void at_sequence_reset(struct EG25Manager *data); +void at_process_result(struct EG25Manager *manager, + const char *response); +void at_next_command(struct EG25Manager *manager); +gboolean at_send_command(struct EG25Manager *manager); +int at_append_command(struct EG25Manager *manager, + const char *cmd, + const char *subcmd, + const char *value, + const char *expected); + +void at_sequence_configure(struct EG25Manager *manager); +void at_sequence_suspend(struct EG25Manager *manager); +void at_sequence_resume(struct EG25Manager *manager); +void at_sequence_reset(struct EG25Manager *manager); From dac50e34ebf6e1db2c411ef2419770ec090daf15 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Wed, 12 May 2021 18:25:40 +0200 Subject: [PATCH 06/14] at: log expected result before setting it to NULL Otherwise the log contains NULL instead of the expected value, making it hard to debug --- src/at.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/at.c b/src/at.c index 35ace26..c2bac9a 100644 --- a/src/at.c +++ b/src/at.c @@ -151,9 +151,9 @@ void at_process_result(struct EG25Manager *manager, if (at_cmd->expected && !strstr(response, at_cmd->expected)) { g_free(at_cmd->value); at_cmd->value = at_cmd->expected; - at_cmd->expected = NULL; g_message("Got a different result than expected, changing value..."); - g_message("\t%s\n\t%s", at_cmd->expected, response); + g_message("Expected: [%s]\nResponse: [%s]", at_cmd->expected, response); + at_cmd->expected = NULL; at_send_command(manager); } else { at_next_command(manager); From 5da7c88fc4fa1fcbc37923273e8d11f91429e341 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Wed, 12 May 2021 18:34:03 +0200 Subject: [PATCH 07/14] at: allow custom callbacks for AT command response processing --- src/at.c | 32 +++++++++++++++++--------------- src/at.h | 14 +++++++++++++- src/manager.h | 1 + 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/at.c b/src/at.c index c2bac9a..62071b8 100644 --- a/src/at.c +++ b/src/at.c @@ -17,14 +17,6 @@ #include -struct AtCommand { - char *cmd; - char *subcmd; - char *value; - char *expected; - int retries; -}; - static GArray *configure_commands = NULL; static GArray *suspend_commands = NULL; static GArray *resume_commands = NULL; @@ -74,6 +66,7 @@ gboolean at_send_command(struct EG25Manager *manager) len = sprintf(command, "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd); else if (at_cmd->subcmd && at_cmd->value) len = sprintf(command, "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value); + manager->at_callback = at_cmd->callback; ret = write(manager->at_fd, command, len); if (ret < len) @@ -164,7 +157,10 @@ int at_append_command(struct EG25Manager *manager, const char *cmd, const char *subcmd, const char *value, - const char *expected) + const char *expected, + void (*callback) + (struct EG25Manager *manager, + const char *response)) { struct AtCommand *at_cmd = calloc(1, sizeof(struct AtCommand)); @@ -178,6 +174,8 @@ int at_append_command(struct EG25Manager *manager, at_cmd->value = g_strdup(value); if (expected) at_cmd->expected = g_strdup(expected); + if (callback) + at_cmd->callback = callback; manager->at_cmds = g_list_append(manager->at_cmds, at_cmd); @@ -223,8 +221,12 @@ static gboolean modem_response(gint fd, } else if (strstr(response, "ERROR")) retry_at_command(manager); - else if (strstr(response, "OK")) - at_process_result(manager, response); + else if (strstr(response, "OK")) { + if (manager->at_callback != NULL) + manager->at_callback(manager, response); + else + g_warning("AT command succesfull but no callback registered"); + } else // Not a recognized response, try running next command, just in case at_next_command(manager); @@ -332,7 +334,7 @@ void at_sequence_configure(struct EG25Manager *manager) { for (guint i = 0; i < configure_commands->len; i++) { struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i); - at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result); } at_send_command(manager); } @@ -341,7 +343,7 @@ void at_sequence_suspend(struct EG25Manager *manager) { for (guint i = 0; i < suspend_commands->len; i++) { struct AtCommand *cmd = &g_array_index(suspend_commands, struct AtCommand, i); - at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result); } at_send_command(manager); } @@ -350,7 +352,7 @@ void at_sequence_resume(struct EG25Manager *manager) { for (guint i = 0; i < resume_commands->len; i++) { struct AtCommand *cmd = &g_array_index(resume_commands, struct AtCommand, i); - at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result); } at_send_command(manager); } @@ -359,7 +361,7 @@ void at_sequence_reset(struct EG25Manager *manager) { for (guint i = 0; i < reset_commands->len; i++) { struct AtCommand *cmd = &g_array_index(reset_commands, struct AtCommand, i); - at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected); + at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result); } at_send_command(manager); } diff --git a/src/at.h b/src/at.h index e0445af..0364e2c 100644 --- a/src/at.h +++ b/src/at.h @@ -8,6 +8,15 @@ #include "manager.h" +typedef struct AtCommand { + char *cmd; + char *subcmd; + char *value; + char *expected; + void (*callback)(struct EG25Manager *manager, const char *response); + int retries; +} AtCommand; + int at_init(struct EG25Manager *manager, toml_table_t *config); void at_destroy(struct EG25Manager *manager); @@ -19,7 +28,10 @@ int at_append_command(struct EG25Manager *manager, const char *cmd, const char *subcmd, const char *value, - const char *expected); + const char *expected, + void (*callback) + (struct EG25Manager *manager, + const char *response)); void at_sequence_configure(struct EG25Manager *manager); void at_sequence_suspend(struct EG25Manager *manager); diff --git a/src/manager.h b/src/manager.h index 30028e6..3cc0118 100644 --- a/src/manager.h +++ b/src/manager.h @@ -47,6 +47,7 @@ struct EG25Manager { int at_fd; guint at_source; GList *at_cmds; + void (*at_callback)(struct EG25Manager *manager, const char *response); enum EG25State modem_state; gchar *modem_usb_id; From 9cfe782f745db87c5c96c721feb9c44bd087c846 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Wed, 12 May 2021 19:37:21 +0200 Subject: [PATCH 08/14] gnss: add GNSS assistance support Automatically fetch the GNSS assistance data from the Web to heavily reduce the time to acquire a GNSS lock by uploading the assistance data to the modem. This feature is optional and can be disabled in the configuration as people may prefer to not download the assistance data from the Quectel/Qualcomm servers. Also configure the GNSS engine to optimize the performance and power consumption. --- data/pine64,pinephone-1.0.toml | 26 +- data/pine64,pinephone-1.1.toml | 26 +- data/pine64,pinephone-1.2.toml | 26 +- meson.build | 1 + src/at.c | 22 +- src/gnss.c | 478 +++++++++++++++++++++++++++++++++ src/gnss.h | 16 ++ src/manager.c | 2 + src/manager.h | 34 +++ src/meson.build | 1 + src/mm-iface.c | 5 +- 11 files changed, 625 insertions(+), 12 deletions(-) create mode 100644 src/gnss.c create mode 100644 src/gnss.h diff --git a/data/pine64,pinephone-1.0.toml b/data/pine64,pinephone-1.0.toml index e1f5b24..e127f1a 100644 --- a/data/pine64,pinephone-1.0.toml +++ b/data/pine64,pinephone-1.0.toml @@ -43,13 +43,33 @@ configure = [ { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" }, { cmd = "QCFG", subcmd = "urc/delay", expect = "1" }, { cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" }, - { cmd = "QGPS", value = "1" }, { cmd = "QSCLK", value = "1" }, +# GNSS configuration: +# * Enable A-GPS data upload support (XTRA) +# * Disable On-Demand-Positioning (ODP) mode +# to avoid running the GNSS system in the background, even when not enabled. +# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios +# when they are not in use. +# * Only enable GPS and GLONASS, disable other GNSS systems. +# A-GPS data upload doesn't work for Galileo anyway. +# * Avoid turning on GNSS support automatically when the modem boots. + { cmd = "QGPSXTRA", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" }, + { cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" }, + { cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" }, + { cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "autogps", expect = "0" }, ] suspend = [ - { cmd = "QGPSEND" }, ] resume = [ - { cmd = "QGPS", value = "1" } ] reset = [ { cmd = "CFUN", value = "1,1" } ] + +[gnss] +enabled = true +url = "https://xtrapath4.izatcloud.net" +file = "xtra2.bin" diff --git a/data/pine64,pinephone-1.1.toml b/data/pine64,pinephone-1.1.toml index e1f5b24..e127f1a 100644 --- a/data/pine64,pinephone-1.1.toml +++ b/data/pine64,pinephone-1.1.toml @@ -43,13 +43,33 @@ configure = [ { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" }, { cmd = "QCFG", subcmd = "urc/delay", expect = "1" }, { cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" }, - { cmd = "QGPS", value = "1" }, { cmd = "QSCLK", value = "1" }, +# GNSS configuration: +# * Enable A-GPS data upload support (XTRA) +# * Disable On-Demand-Positioning (ODP) mode +# to avoid running the GNSS system in the background, even when not enabled. +# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios +# when they are not in use. +# * Only enable GPS and GLONASS, disable other GNSS systems. +# A-GPS data upload doesn't work for Galileo anyway. +# * Avoid turning on GNSS support automatically when the modem boots. + { cmd = "QGPSXTRA", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" }, + { cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" }, + { cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" }, + { cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "autogps", expect = "0" }, ] suspend = [ - { cmd = "QGPSEND" }, ] resume = [ - { cmd = "QGPS", value = "1" } ] reset = [ { cmd = "CFUN", value = "1,1" } ] + +[gnss] +enabled = true +url = "https://xtrapath4.izatcloud.net" +file = "xtra2.bin" diff --git a/data/pine64,pinephone-1.2.toml b/data/pine64,pinephone-1.2.toml index 4ca1274..e16f4f5 100644 --- a/data/pine64,pinephone-1.2.toml +++ b/data/pine64,pinephone-1.2.toml @@ -37,13 +37,33 @@ configure = [ { cmd = "QCFG", subcmd = "ims", expect = "1" }, { cmd = "QCFG", subcmd = "apready", expect = "1,0,500" }, { cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" }, - { cmd = "QGPS", value = "1" }, { cmd = "QSCLK", value = "1" }, +# GNSS configuration: +# * Enable A-GPS data upload support (XTRA) +# * Disable On-Demand-Positioning (ODP) mode +# to avoid running the GNSS system in the background, even when not enabled. +# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios +# when they are not in use. +# * Only enable GPS and GLONASS, disable other GNSS systems. +# A-GPS data upload doesn't work for Galileo anyway. +# * Avoid turning on GNSS support automatically when the modem boots. + { cmd = "QGPSXTRA", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" }, + { cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" }, + { cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" }, + { cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" }, + { cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" }, + { cmd = "QGPSCFG", subcmd = "autogps", expect = "0" }, ] suspend = [ - { cmd = "QGPSEND" }, ] resume = [ - { cmd = "QGPS", value = "1" } ] reset = [ { cmd = "CFUN", value = "1,1" } ] + +[gnss] +enabled = true +url = "https://xtrapath4.izatcloud.net" +file = "xtra2.bin" diff --git a/meson.build b/meson.build index d57d2cb..2852778 100644 --- a/meson.build +++ b/meson.build @@ -58,6 +58,7 @@ mgr_deps = [ dependency('gudev-1.0'), dependency('libgpiod'), dependency('libusb-1.0'), + dependency('libcurl'), mmglib_dep, ] diff --git a/src/at.c b/src/at.c index 62071b8..37e3025 100644 --- a/src/at.c +++ b/src/at.c @@ -7,6 +7,7 @@ #include "at.h" #include "suspend.h" #include "gpio.h" +#include "gnss.h" #include #include @@ -215,20 +216,37 @@ static gboolean modem_response(gint fd, g_message("Response: [%s]", response); + /* + * When the modem is started, it outputs 'RDY' to indicate that + * it is ready to receive AT commands. + */ if (strcmp(response, "RDY") == 0) { suspend_inhibit(manager, TRUE, TRUE); manager->modem_state = EG25_STATE_STARTED; } + /* + * Search for 'QGPSURC: "xtradataexpire"' in response to detect + * if GNSS assistance data must be re-uploaded. + * If that's the case, check if no AT commands are being processed + * to avoid deadlocks and start upload. + */ + else if (strstr(response, "QGPSURC: \"xtradataexpire\"") && manager->at_cmds == NULL) + gnss_upload_assistance_data(manager); + /* AT command failed, retry */ else if (strstr(response, "ERROR")) retry_at_command(manager); - else if (strstr(response, "OK")) { + /* + * Successfull AT responses contain 'OK', except for AT+QFUPL which also + * returns 'CONNECT' when the modem is ready to receive data over serial + */ + else if (strstr(response, "OK") || strstr(response, "CONNECT")) { if (manager->at_callback != NULL) manager->at_callback(manager, response); else g_warning("AT command succesfull but no callback registered"); } + /* Not a recognized response, try running next command, just in case */ else - // Not a recognized response, try running next command, just in case at_next_command(manager); } diff --git a/src/gnss.c b/src/gnss.c new file mode 100644 index 0000000..d986be0 --- /dev/null +++ b/src/gnss.c @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2021 Dylan Van Assche + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "gnss.h" +#include "manager.h" +#include "at.h" + +#define BUFFER_SIZE 256 +#define UPLOAD_DELAY 100000 +#define RESCHEDULE_IN_SECS 30 + +static void gnss_step(struct EG25Manager *manager); + +void gnss_upload_assistance_data(struct EG25Manager *manager) +{ + if (!manager->gnss_assistance_enabled) { + g_message("GNSS assistance is disabled!"); + return; + } + + if (manager->gnss_assistance_step < EG25_GNSS_STEP_LAST) { + g_warning("GNSS assistance data upload already in process (%d/%d)", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + return; + } + manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST; + gnss_step(manager); +} + +void gnss_init(struct EG25Manager *manager, toml_table_t *config) +{ + toml_datum_t enabled; + toml_datum_t url; + toml_datum_t file; + g_autoptr (GError) error = NULL; + + /* + * GNSS assistance is an optional feature, you can disable it + * if you want in the configuration file. + * In case the configuration is missing, we assume GNSS assistance + * to be disabled. + */ + enabled = toml_bool_in(config, "enabled"); + manager->gnss_assistance_enabled = FALSE; + if (enabled.ok) + manager->gnss_assistance_enabled = enabled.u.b; + + if (!manager->gnss_assistance_enabled) { + g_message("GNSS assistance is disabled!"); + return; + } + + url = toml_string_in(config, "url"); + if (url.ok) + manager->gnss_assistance_url = url.u.s; + else + g_error("GNSS assistance server URL is missing from config file"); + file = toml_string_in(config, "file"); + if (file.ok) + manager->gnss_assistance_file = file.u.s; + else + g_error("GNSS assistance file name is missing from config file"); + + /* Create temporary file to store assistance data */ + manager->gnss_assistance_fd = g_file_open_tmp(NULL, NULL, &error); + if (error != NULL) + g_error ("Unable to create temporary file: %s", error->message); + + /* Initialize state and schedule upload */ + manager->gnss_assistance_step = EG25_GNSS_STEP_LAST; + g_timeout_add_seconds(RESCHEDULE_IN_SECS, + G_SOURCE_FUNC(gnss_upload_assistance_data), manager); +} + +void gnss_destroy(struct EG25Manager *manager) +{ + close(manager->gnss_assistance_fd); +} + +/******************************************************************************/ + +#ifdef HAVE_MMGLIB +static void disable_mm_gnss(struct EG25Manager *manager) +{ + MMModemLocationSource sources; + gboolean signals_location; + g_autoptr (GError) error = NULL; + + sources = mm_modem_location_get_enabled(manager->mm_location); + signals_location = mm_modem_location_signals_location(manager->mm_location); + manager->gnss_sources = EG25_GNSS_SOURCE_NONE; + + /* Save GNSS engine state */ + if (sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA) + manager->gnss_sources |= EG25_GNSS_SOURCE_NMEA; + else + manager->gnss_sources &= ~EG25_GNSS_SOURCE_NMEA; + + if (sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW) + manager->gnss_sources |= EG25_GNSS_SOURCE_RAW; + else + manager->gnss_sources &= ~EG25_GNSS_SOURCE_RAW; + + if (sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED) + manager->gnss_sources |= EG25_GNSS_SOURCE_UNMANAGED; + else + manager->gnss_sources &= ~EG25_GNSS_SOURCE_UNMANAGED; + + /* Disable GNSS engine */ + sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_RAW; + sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_NMEA; + sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + mm_modem_location_setup_sync(manager->mm_location, sources, + signals_location, NULL, &error); + if (error != NULL) { + g_warning("Unable to disable GNSS engine through ModemManager: %s", + error->message); + } +} +#endif + +static void disable_at_gnss_cb(struct EG25Manager *manager, + const char *response) +{ + /* Clear QGPSEND AT command and process next */ + at_next_command(manager); + + /* Go to the next step */ + manager->gnss_assistance_step++; + gnss_step(manager); +} + +static void state_at_gnss_cb(struct EG25Manager *manager, const char *response) +{ + struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL; + + if (!at_cmd) + return; + + /* Parse GNSS engine status and disable it if needed */ + if (strstr(response, "QGPS: 1")) { + manager->gnss_sources |= EG25_GNSS_SOURCE_QGPS; + g_free(at_cmd->value); + g_free(at_cmd->cmd); + at_cmd->expected = NULL; + at_cmd->subcmd = NULL; + at_cmd->value = NULL; + at_cmd->cmd = g_strdup("QGPSEND"); + at_cmd->callback = disable_at_gnss_cb; + at_send_command(manager); + } + /* QGPS is already disabled, move on to next step */ + else { + at_next_command(manager); + manager->gnss_sources &= ~EG25_GNSS_SOURCE_QGPS; + manager->gnss_assistance_step++; + gnss_step(manager); + } +} + +static void state_at_gnss(struct EG25Manager *manager) +{ + /* Asynchronously send AT command to query status of GNSS engine */ + at_append_command(manager, "QGPS?", NULL, NULL, NULL, state_at_gnss_cb); + at_send_command(manager); +} + +/******************************************************************************/ + +static void fetch_assistance_data(struct EG25Manager *manager) +{ + CURL *curl; + CURLcode response; + long status_code; + gchar *url = NULL; + FILE *tmp_file = NULL; + long int size; + + /* Fetch assistance data with curl */ + tmp_file = fdopen(manager->gnss_assistance_fd, "wb"); + url = g_strconcat(manager->gnss_assistance_url, "/", + manager->gnss_assistance_file, NULL); + curl = curl_easy_init(); + if (!curl) + g_error ("Unable to initialize curl"); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + response = curl_easy_perform(curl); + if (response == CURLE_HTTP_RETURNED_ERROR) { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code); + curl_easy_cleanup(curl); + g_warning ("Unable to fetch GNSS assistance data from %s (HTTP %ld)", + url, status_code); + + /* Restart upload on HTTP error status code */ + g_message ("Rescheduling upload because of failure in %ds", + RESCHEDULE_IN_SECS); + manager->gnss_assistance_step = EG25_GNSS_STEP_LAST; + g_timeout_add_seconds(RESCHEDULE_IN_SECS, + G_SOURCE_FUNC(gnss_upload_assistance_data), + manager); + return; + } + + /* Get file size in bytes */ + size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END); + lseek(manager->gnss_assistance_fd, 0, SEEK_SET); + + if (size <= 0) { + g_warning ("GNSS assistance data contains 0 bytes," + "check network connection."); + /* + * Restart upload when file does not contain any data, + * mostly because of no network connection. + */ + g_message ("Rescheduling upload because of failure in %ds", + RESCHEDULE_IN_SECS); + manager->gnss_assistance_step = EG25_GNSS_STEP_LAST; + g_timeout_add_seconds(RESCHEDULE_IN_SECS, + G_SOURCE_FUNC(gnss_upload_assistance_data), + manager); + return; + } + + g_message("Fetching GNSS assistance data from %s was successfull", url); + curl_easy_cleanup(curl); + g_free(url); + + /* Go to the next step */ + manager->gnss_assistance_step++; + gnss_step(manager); +} + +/******************************************************************************/ + +static void init_assistance_data_upload_ready(struct EG25Manager *manager, + const char *response) +{ + /* Search for 'CONNECT' in response to start upload */ + if (strstr(response, "CONNECT")) { + g_message("Modem ready for GNSS assistance data upload"); + manager->gnss_assistance_step++; + gnss_step(manager); + } +} + +static void init_assistance_data_upload_start(struct EG25Manager *manager, + const char *response) +{ + gchar value[BUFFER_SIZE]; + long int size; + + /* Process AT response */ + at_process_result(manager, response); + + /* Get file size in bytes */ + size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END); + lseek(manager->gnss_assistance_fd, 0, SEEK_SET); + + /* Start upload */ + g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld\r\n", + manager->gnss_assistance_file, size); + g_message("Initiate GNSS assistance data upload: %s", value); + at_append_command(manager, "QFUPL", NULL, value, NULL, + init_assistance_data_upload_ready); + at_send_command(manager); +} + +static void init_assistance_data_upload(struct EG25Manager *manager) +{ + /* + * Delete all previous GNSS assistance data files in RAM + * and start uploading the latest one to RAM. + */ + at_append_command(manager, "QFDEL", NULL, "\"RAM:*\"\r\n", + NULL, init_assistance_data_upload_start); + at_send_command(manager); +} + +static void upload_assistance_data(struct EG25Manager *manager) +{ + char buffer[2*BUFFER_SIZE]; + gint len; + gboolean success = TRUE; + + /* Copy downloaded XTRA assistance data to the modem over serial */ + while((len = read(manager->gnss_assistance_fd, buffer, 2*BUFFER_SIZE)) > 0) + { + len = write(manager->at_fd, buffer, len); + if (len < 0) { + success = FALSE; + g_error("Writing GNSS assistance data failed: %d", len); + break; + } + usleep(UPLOAD_DELAY); + g_message("Uploaded %d bytes", len); + } + + /* Clear QFUPL AT command and process next */ + at_next_command(manager); + + /* Go to the next step if successful */ + if (success) { + manager->gnss_assistance_step++; + gnss_step(manager); + } + /* Restart upload */ + else { + g_message ("Rescheduling upload because of failure in %ds", + RESCHEDULE_IN_SECS); + manager->gnss_assistance_step = EG25_GNSS_STEP_LAST; + g_timeout_add_seconds(RESCHEDULE_IN_SECS, + G_SOURCE_FUNC(gnss_upload_assistance_data), + manager); + } +} + +static void finish_assistance_data_upload_cb(struct EG25Manager *manager, + const char *response) +{ + /* Process response */ + at_process_result(manager, response); + g_message("GNSS assistance data upload finished"); + + /* Go to the next step */ + manager->gnss_assistance_step++; + gnss_step(manager); +} + +static void finish_assistance_data_upload(struct EG25Manager *manager) +{ + gchar value[BUFFER_SIZE]; + GDateTime *datetime; + gchar *timestring; + + /* Configure GNSS assistance clock to current system time (UTC) */ + datetime = g_date_time_new_now_utc(); + timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"\r\n"); + g_message("Setting GNSS assistance UTC clock to: %s", timestring); + at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL, + at_process_result); + + /* Configure GNSS engine to use uploaded GNSS assistance data */ + g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"\r\n", + manager->gnss_assistance_file); + g_message("Setting GNSS assistance file to: %s", value); + at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL, + finish_assistance_data_upload_cb); + at_send_command(manager); +} + +/******************************************************************************/ + +#ifdef HAVE_MMGLIB +static void enable_mm_gnss(struct EG25Manager *manager) +{ + MMModemLocationSource sources; + gboolean signal_location; + g_autoptr (GError) error = NULL; + + if (manager->gnss_sources & EG25_GNSS_SOURCE_UNMANAGED) + sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; + if (manager->gnss_sources & EG25_GNSS_SOURCE_NMEA) + sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA; + if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW) + sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; + + sources = mm_modem_location_get_enabled(manager->mm_location); + signal_location = mm_modem_location_signals_location(manager->mm_location); + mm_modem_location_setup_sync(manager->mm_location, sources, + signal_location, NULL, &error); + if (error != NULL) + g_warning("Unable to enable GNSS engine through ModemManager: %s", + error->message); +} +#endif + +static void enable_at_gnss_cb(struct EG25Manager *manager, const char *response) +{ + manager->gnss_assistance_step++; + gnss_step(manager); +} + +static void enable_at_gnss(struct EG25Manager *manager) +{ + if (manager->gnss_sources & EG25_GNSS_SOURCE_QGPS) { + at_append_command(manager, "QGPS", NULL, "1", NULL, + enable_at_gnss_cb); + at_send_command(manager); + return; + } + manager->gnss_assistance_step++; + gnss_step(manager); +} + +/******************************************************************************/ + +void gnss_step(struct EG25Manager *manager) +{ + switch(manager->gnss_assistance_step) { + case EG25_GNSS_STEP_FIRST: + manager->gnss_assistance_step++; + g_message("GNSS assistance upload started..."); + /* fall-through */ + +#ifdef HAVE_MMGLIB + case EG25_GNSS_STEP_MM_GNSS_DISABLE: + g_message("GNSS assistance upload step (%d/%d): " + "disabling GNSS engine through ModemManager", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + disable_mm_gnss(manager); + manager->gnss_assistance_step++; + /* fall-through */ +#endif + + case EG25_GNSS_STEP_AT_GNSS_DISABLE: + g_message("GNSS assistance upload step (%d/%d): " + "disabling GNSS engine through AT+QGPS", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + state_at_gnss(manager); + break; + + case EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA: + g_message("GNSS assistance upload step (%d/%d): " + "fetching assistance data", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + fetch_assistance_data(manager); + break; + + case EG25_GNSS_STEP_INIT_UPLOAD: + g_message("GNSS assistance upload step (%d/%d): initiating upload", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + init_assistance_data_upload(manager); + break; + + case EG25_GNSS_STEP_UPLOAD: + g_message("GNSS assistance upload step (%d/%d): " + "uploading assistance data", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + upload_assistance_data(manager); + break; + + case EG25_GNSS_STEP_FINISH_UPLOAD: + g_message("GNSS assistance upload step (%d/%d): finishing upload", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + finish_assistance_data_upload(manager); + break; + +#ifdef HAVE_MMGLIB + case EG25_GNSS_STEP_MM_GNSS_ENABLE: + g_message("GNSS assistance upload step (%d/%d): " + "re-enabling GNSS through ModemManager", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + enable_mm_gnss(manager); + manager->gnss_assistance_step++; + /* fall-through */ +#endif + + case EG25_GNSS_STEP_AT_QGPS_ENABLE: + g_message("GNSS assistance upload step (%d/%d): " + "re-enabling GNSS through AT+QGPS", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + enable_at_gnss(manager); + break; + + case EG25_GNSS_STEP_LAST: + g_message("GNSS assistance upload step (%d/%d): finished", + manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); + break; + } +} + diff --git a/src/gnss.h b/src/gnss.h new file mode 100644 index 0000000..931ab8b --- /dev/null +++ b/src/gnss.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2021 Dylan Van Assche + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +#include "manager.h" + +void gnss_init(struct EG25Manager *manager, toml_table_t *config); +void gnss_destroy(struct EG25Manager *manager); +void gnss_upload_assistance_data(struct EG25Manager *manager); diff --git a/src/manager.c b/src/manager.c index c0b8a43..d13a2a0 100644 --- a/src/manager.c +++ b/src/manager.c @@ -15,6 +15,7 @@ #include "ofono-iface.h" #include "suspend.h" #include "udev.h" +#include "gnss.h" #include #include @@ -342,6 +343,7 @@ int main(int argc, char *argv[]) ofono_iface_init(&manager); suspend_init(&manager, toml_table_in(toml_config, "suspend")); udev_init(&manager, toml_table_in(toml_config, "udev")); + gnss_init(&manager, toml_table_in(toml_config, "gnss")); g_idle_add(G_SOURCE_FUNC(modem_start), &manager); diff --git a/src/manager.h b/src/manager.h index 3cc0118..3e097b3 100644 --- a/src/manager.h +++ b/src/manager.h @@ -16,6 +16,32 @@ #include "toml.h" +typedef enum { + EG25_GNSS_STEP_FIRST = 0, +#ifdef HAVE_MMGLIB + EG25_GNSS_STEP_MM_GNSS_DISABLE, +#endif + EG25_GNSS_STEP_AT_GNSS_DISABLE, + EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA, + EG25_GNSS_STEP_INIT_UPLOAD, + EG25_GNSS_STEP_UPLOAD, + EG25_GNSS_STEP_FINISH_UPLOAD, +#ifdef HAVE_MMGLIB + EG25_GNSS_STEP_MM_GNSS_ENABLE, +#endif + EG25_GNSS_STEP_AT_QGPS_ENABLE, + EG25_GNSS_STEP_LAST, +} EG25GNSSStep; + +typedef enum { + EG25_GNSS_SOURCE_NONE = 0, + EG25_GNSS_SOURCE_NMEA = 1 << 0, + EG25_GNSS_SOURCE_RAW = 1 << 1, + EG25_GNSS_SOURCE_UNMANAGED = 1 << 2, + EG25_GNSS_SOURCE_QGPS = 1 << 3, +} EG25GNSSSource; + + enum EG25State { EG25_STATE_INIT = 0, EG25_STATE_POWERED, // Power-on sequence has been executed, but the modem isn't on yet @@ -52,11 +78,19 @@ struct EG25Manager { enum EG25State modem_state; gchar *modem_usb_id; + gboolean gnss_assistance_enabled; + EG25GNSSSource gnss_sources; + EG25GNSSStep gnss_assistance_step; + gint gnss_assistance_fd; + gchar *gnss_assistance_url; + gchar *gnss_assistance_file; + enum ModemIface modem_iface; guint mm_watch; #ifdef HAVE_MMGLIB MMManager *mm_manager; MMModem *mm_modem; + MMModemLocation *mm_location; #endif guint ofono_watch; GDBOManager *ofono_manager; diff --git a/src/meson.build b/src/meson.build index 1bfcd3a..69a7e7e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,7 @@ src = [ 'suspend.c', 'suspend.h', 'toml.c', 'toml.h', 'udev.c', 'udev.h', + 'gnss.c', 'gnss.h', gdbofono_headers, ] diff --git a/src/mm-iface.c b/src/mm-iface.c index 0409236..2bbdfe5 100644 --- a/src/mm-iface.c +++ b/src/mm-iface.c @@ -32,7 +32,10 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object) g_assert(MM_IS_OBJECT (object)); manager->mm_modem = mm_object_get_modem(MM_OBJECT(object)); - g_assert(manager->mm_modem != NULL); + manager->mm_location = mm_object_get_modem_location(MM_OBJECT(object)); + g_assert_nonnull(manager->mm_modem); + g_assert_nonnull(manager->mm_location); + if (manager->modem_state == EG25_STATE_RESUMING) { if (manager->modem_recovery_timer) { From 128483354b95ee59ab8c5476df15caf1ff828e8b Mon Sep 17 00:00:00 2001 From: Arnaud Ferraris Date: Tue, 10 Aug 2021 22:27:07 +0200 Subject: [PATCH 09/14] udev: remove reset quirk It seems to work just fine with MM's quick suspend/resume. --- udev/80-modem-eg25.rules | 1 - 1 file changed, 1 deletion(-) diff --git a/udev/80-modem-eg25.rules b/udev/80-modem-eg25.rules index 5fb6970..c25179f 100644 --- a/udev/80-modem-eg25.rules +++ b/udev/80-modem-eg25.rules @@ -1,5 +1,4 @@ ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/control}="auto" ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/autosuspend_delay_ms}="3000" ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/wakeup}="enabled" -ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{avoid_reset_quirk}="1" ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/persist}="0" From 898c0dc79c1596b9c83e126fe8eb6b618fe23f88 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Thu, 15 Apr 2021 20:11:03 +0200 Subject: [PATCH 10/14] config: synchronize with modem-power We cannot assume default factory values if other drivers change them --- data/pine64,pinephone-1.0.toml | 28 ++++++++++++++++++++++++++-- data/pine64,pinephone-1.1.toml | 28 ++++++++++++++++++++++++++-- data/pine64,pinephone-1.2.toml | 27 +++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/data/pine64,pinephone-1.0.toml b/data/pine64,pinephone-1.0.toml index e127f1a..8b91a88 100644 --- a/data/pine64,pinephone-1.0.toml +++ b/data/pine64,pinephone-1.0.toml @@ -34,15 +34,31 @@ configure = [ # match, the command is then executed with value `expect` in # order to set the parameter to the configured value (optional) # A command can have `expect` OR `value` configured, but it shouldn't have both +# Print software version { cmd = "QGMR" }, +# Configure audio { cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" }, +# RI signaling using physical RI pin { cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" }, +# Enable VoLTE support { cmd = "QCFG", subcmd = "ims", expect = "1" }, +# Disable APREADY for PP 1.0 because pin is not connected + { cmd = "QCFG", subcmd = "apready", expect = "0,0,500" }, +# URC configuration for PP 1.0 (APREADY pin not connected): +# * RING URC: extend pulse length +# * Incoming SMS URC: extend pulse length +# * Other URC: extend pulse length +# * Report URCs on all ports (serial and USB) for FOSS firmware +# * Delay reporting of URCs +# * Configure URC pin to UART Ring Indicator { cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",2000,1000,5000,\"off\",1" }, - { cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000" }, - { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" }, + { cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000,1" }, + { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" }, { cmd = "QCFG", subcmd = "urc/delay", expect = "1" }, + { cmd = "QCFG", subcmd = "urc/cache", expect = "0" }, + { cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" }, { cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" }, +# Allow sleeping for power saving { cmd = "QSCLK", value = "1" }, # GNSS configuration: # * Enable A-GPS data upload support (XTRA) @@ -62,6 +78,14 @@ configure = [ { cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" }, { cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" }, { cmd = "QGPSCFG", subcmd = "autogps", expect = "0" }, +# Disable fast poweroff for stability + { cmd = "QCFG", subcmd = "fast/poweroff", expect = "0" }, +# Configure sleep and wake up pin levels to active low + { cmd = "QCFG", subcmd = "sleepind/level", expect = "0" }, + { cmd = "QCFG", subcmd = "wakeupin/level", expect = "0,0" }, +# Do not enter RAMDUMP mode, auto-reset instead + { cmd = "QCFG", subcmd = "ApRstLevel", expect = "1" }, + { cmd = "QCFG", subcmd = "ModemRstLevel", expect = "1" }, ] suspend = [ ] diff --git a/data/pine64,pinephone-1.1.toml b/data/pine64,pinephone-1.1.toml index e127f1a..fd09462 100644 --- a/data/pine64,pinephone-1.1.toml +++ b/data/pine64,pinephone-1.1.toml @@ -34,15 +34,31 @@ configure = [ # match, the command is then executed with value `expect` in # order to set the parameter to the configured value (optional) # A command can have `expect` OR `value` configured, but it shouldn't have both +# Print software version { cmd = "QGMR" }, +# Configure audio { cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" }, +# RI signaling using physical RI pin { cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" }, +# Enable VoLTE support { cmd = "QCFG", subcmd = "ims", expect = "1" }, +# Disable APREADY for PP 1.1 because pin is not connected + { cmd = "QCFG", subcmd = "apready", expect = "0,0,500" }, +# URC configuration for PP 1.1 (APREADY pin not connected): +# * RING URC: extend pulse length +# * Incoming SMS URC: extend pulse length +# * Other URC: extend pulse length +# * Report URCs on all ports (serial and USB) for FOSS firmware +# * Delay reporting of URCs +# * Configure URC pin to UART Ring Indicator { cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",2000,1000,5000,\"off\",1" }, - { cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000" }, - { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" }, + { cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000,1" }, + { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" }, { cmd = "QCFG", subcmd = "urc/delay", expect = "1" }, + { cmd = "QCFG", subcmd = "urc/cache", expect = "0" }, + { cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" }, { cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" }, +# Allow sleeping for power saving { cmd = "QSCLK", value = "1" }, # GNSS configuration: # * Enable A-GPS data upload support (XTRA) @@ -62,6 +78,14 @@ configure = [ { cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" }, { cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" }, { cmd = "QGPSCFG", subcmd = "autogps", expect = "0" }, +# Disable fast poweroff for stability + { cmd = "QCFG", subcmd = "fast/poweroff", expect = "0" }, +# Configure sleep and wake up pin levels to active low + { cmd = "QCFG", subcmd = "sleepind/level", expect = "0" }, + { cmd = "QCFG", subcmd = "wakeupin/level", expect = "0,0" }, +# Do not enter RAMDUMP mode, auto-reset instead + { cmd = "QCFG", subcmd = "ApRstLevel", expect = "1" }, + { cmd = "QCFG", subcmd = "ModemRstLevel", expect = "1" }, ] suspend = [ ] diff --git a/data/pine64,pinephone-1.2.toml b/data/pine64,pinephone-1.2.toml index e16f4f5..4244bee 100644 --- a/data/pine64,pinephone-1.2.toml +++ b/data/pine64,pinephone-1.2.toml @@ -31,12 +31,31 @@ configure = [ # match, the command is then executed with value `expect` in # order to set the parameter to the configured value (optional) # A command can have `expect` OR `value` configured, but it shouldn't have both +# Print software version { cmd = "QGMR" }, +# Configure audio { cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" }, +# RI signaling using physical RI pin { cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" }, +# Enable VoLTE support { cmd = "QCFG", subcmd = "ims", expect = "1" }, +# Enable APREADY for PP 1.2 { cmd = "QCFG", subcmd = "apready", expect = "1,0,500" }, +# URC configuration for PP 1.2 (APREADY pin connected): +# * RING URC: normal pulse length +# * Incoming SMS URC: default pulse length +# * Other URC: default length +# * Report URCs on all ports (serial and USB) for FOSS firmware +# * Reporting of URCs without any delay +# * Configure URC pin to UART Ring Indicator + { cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",120,1000,5000,\"off\",1" }, + { cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",120,1" }, + { cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" }, + { cmd = "QCFG", subcmd = "urc/delay", expect = "0" }, + { cmd = "QCFG", subcmd = "urc/cache", expect = "0" }, + { cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" }, { cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" }, +# Allow sleeping for power saving { cmd = "QSCLK", value = "1" }, # GNSS configuration: # * Enable A-GPS data upload support (XTRA) @@ -56,6 +75,14 @@ configure = [ { cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" }, { cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" }, { cmd = "QGPSCFG", subcmd = "autogps", expect = "0" }, +# Disable fast poweroff for stability + { cmd = "QCFG", subcmd = "fast/poweroff", expect = "0" }, +# Configure sleep and wake up pin levels to active low + { cmd = "QCFG", subcmd = "sleepind/level", expect = "0" }, + { cmd = "QCFG", subcmd = "wakeupin/level", expect = "0,0" }, +# Do not enter RAMDUMP mode, auto-reset instead + { cmd = "QCFG", subcmd = "ApRstLevel", expect = "1" }, + { cmd = "QCFG", subcmd = "ModemRstLevel", expect = "1" }, ] suspend = [ ] From 10ec1119fbde23f1c776d770049bd1fcff0482b3 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Sat, 17 Apr 2021 15:50:07 +0200 Subject: [PATCH 11/14] at: fast/poweroff is only available in newer firmware versions Do not retry the AT command of the fast/poweroff setting as it may not be supported by the firmware --- src/at.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/at.c b/src/at.c index 37e3025..228ded6 100644 --- a/src/at.c +++ b/src/at.c @@ -232,8 +232,12 @@ static gboolean modem_response(gint fd, */ else if (strstr(response, "QGPSURC: \"xtradataexpire\"") && manager->at_cmds == NULL) gnss_upload_assistance_data(manager); - /* AT command failed, retry */ - else if (strstr(response, "ERROR")) + /* + * AT command failed, retry. + * QCFG="fast/poweroff" configuration is only available in + * newer firmware versions, skip retrying that specific AT command. + */ + else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff")) retry_at_command(manager); /* * Successfull AT responses contain 'OK', except for AT+QFUPL which also From 75570e45dac524e9720c653d0b9f46a4eead2268 Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Wed, 11 Aug 2021 17:09:26 +0200 Subject: [PATCH 12/14] gpio: extend softsleep wake time 50ms is way to short for the Quectel firmware to react, increase to 200ms --- src/gpio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gpio.c b/src/gpio.c index 617b69e..aae9b94 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -73,8 +73,8 @@ int gpio_sequence_wake(struct EG25Manager *manager) { gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0); - /* Give the modem 5ms to wake from soft sleep */ - usleep(5000); + /* Give the modem 200ms to wake from soft sleep */ + usleep(200000); g_message("Executed soft wake sequence"); From ef94492b30c01fa9778216ce8d5f130ba6df064e Mon Sep 17 00:00:00 2001 From: Dylan Van Assche Date: Wed, 11 Aug 2021 17:11:01 +0200 Subject: [PATCH 13/14] gnss: handle locked SIM ModemManager Location service is only available after SIM unlock and network registration. Track service separately to avoid an assert error and crashes. GNSS assistance data is uploaded when the service becomes available. --- src/gnss.c | 16 +++++++++++++--- src/gnss.h | 2 +- src/mm-iface.c | 16 +++++++++++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/gnss.c b/src/gnss.c index d986be0..aeb73af 100644 --- a/src/gnss.c +++ b/src/gnss.c @@ -14,20 +14,30 @@ static void gnss_step(struct EG25Manager *manager); -void gnss_upload_assistance_data(struct EG25Manager *manager) +gboolean gnss_upload_assistance_data(struct EG25Manager *manager) { if (!manager->gnss_assistance_enabled) { g_message("GNSS assistance is disabled!"); - return; + return FALSE; } if (manager->gnss_assistance_step < EG25_GNSS_STEP_LAST) { g_warning("GNSS assistance data upload already in process (%d/%d)", manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); - return; + return FALSE; } + + /* ModemManager's Location is only available after unlocking */ + if(!manager->mm_location) { + g_message ("Rescheduling upload since Location interface is not available, in %ds", + RESCHEDULE_IN_SECS); + manager->gnss_assistance_step = EG25_GNSS_STEP_LAST; + return TRUE; + } + manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST; gnss_step(manager); + return FALSE; } void gnss_init(struct EG25Manager *manager, toml_table_t *config) diff --git a/src/gnss.h b/src/gnss.h index 931ab8b..1b49403 100644 --- a/src/gnss.h +++ b/src/gnss.h @@ -13,4 +13,4 @@ void gnss_init(struct EG25Manager *manager, toml_table_t *config); void gnss_destroy(struct EG25Manager *manager); -void gnss_upload_assistance_data(struct EG25Manager *manager); +gboolean gnss_upload_assistance_data(struct EG25Manager *manager); diff --git a/src/mm-iface.c b/src/mm-iface.c index 2bbdfe5..577a718 100644 --- a/src/mm-iface.c +++ b/src/mm-iface.c @@ -32,10 +32,7 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object) g_assert(MM_IS_OBJECT (object)); manager->mm_modem = mm_object_get_modem(MM_OBJECT(object)); - manager->mm_location = mm_object_get_modem_location(MM_OBJECT(object)); g_assert_nonnull(manager->mm_modem); - g_assert_nonnull(manager->mm_location); - if (manager->modem_state == EG25_STATE_RESUMING) { if (manager->modem_recovery_timer) { @@ -63,6 +60,16 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object) g_signal_connect(gdbus_modem, "state-changed", G_CALLBACK(state_changed_cb), manager); } +static void add_modem_location(struct EG25Manager *manager, GDBusObject *object) +{ + const gchar *path; + + path = g_dbus_object_get_object_path(object); + g_message("Adding new modem with location capabilities `%s'", path); + manager->mm_location = mm_object_get_modem_location(MM_OBJECT(object)); + g_assert_nonnull(manager->mm_location); +} + static void interface_added_cb (struct EG25Manager *manager, GDBusObject *object, GDBusInterface *interface) @@ -75,6 +82,9 @@ static void interface_added_cb (struct EG25Manager *manager, if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM) == 0) add_modem(manager, object); + + if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM_LOCATION) == 0) + add_modem_location(manager, object); } From c11f68f40212981e82cdbc09cff005bc02670705 Mon Sep 17 00:00:00 2001 From: Arnaud Ferraris Date: Wed, 1 Sep 2021 00:40:38 +0200 Subject: [PATCH 14/14] meson.build: release v0.4.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 2852778..fbbbf64 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,7 @@ project ( 'eg25-manager', 'c', - version : '0.3.0', + version : '0.4.0', license : 'GPLv3+', meson_version : '>= 0.50.0', default_options :