54 Commits
0.0.4 ... 0.2.1

Author SHA1 Message Date
Arnaud Ferraris
3d076e8bc8 README: add details about config files 2021-02-21 16:40:36 +01:00
Arnaud Ferraris
2e7bf44f2d Merge branch 'at-value-expected-switched' into 'master'
at: fix argument order

See merge request mobian1/devices/eg25-manager!9
2021-02-21 15:37:36 +00:00
Dylan Van Assche
3bf2d785bb at: fix argument order
'expected' and 'value' parts of the AT command were switched.
2021-02-21 16:27:30 +01:00
Arnaud Ferraris
9e40ae11d6 data: add AT command reporting firmware version
Fixes #7
2021-02-20 20:12:21 +01:00
Arnaud Ferraris
11c9cee111 meson.build: release version 0.2.0 2021-02-20 17:18:25 +01:00
Arnaud Ferraris
89ce52f709 Merge branch 'config' into 'master'
Implement config file

Closes #6

See merge request mobian1/devices/eg25-manager!8
2021-02-20 16:16:45 +00:00
Arnaud Ferraris
8a41a3203b data: add config files for all pinephone versions 2021-02-20 17:11:56 +01:00
Arnaud Ferraris
042de572ea at: parse config file
The AT commands list for each stage is now configured per-device, as 
well as the UART port to be used.

As this list can be changed by the user, there is no more need for a 
special GNSS-related option
2021-02-20 17:10:43 +01:00
Arnaud Ferraris
cfbbbd0167 gpio: parse config file
The GPIO numbers are now moved to the config file instead of being 
hardcoded.
2021-02-20 17:10:43 +01:00
Arnaud Ferraris
f276d9cf9e suspend: make timeouts configurable
Instead of fixed values, the modem boot timeout and recovery timeout are now
(optional) config values.
2021-02-20 17:10:01 +01:00
Arnaud Ferraris
433982e4f7 manager: parse config file
The need to use libusb to check for the modem state on startup now 
depends on a config option, instead of the device type. The USB PID and 
VID are moved to config as well.
2021-02-20 17:04:49 +01:00
Arnaud Ferraris
d9256251fd src: implement config file lookup and initial parsing
This commit prepares the use of device-specific configuration files. 
These files should be named after the device-tree `compatible` string 
with the `toml` extension.

`eg25-manager` will search for config files in 
`<prefix>/etc/eg25-manager` first (or `/etc/eg25-manager` if prefix is 
`/usr`), then in `<prefix>/share/eg25-manager`.
2021-02-20 17:04:49 +01:00
Arnaud Ferraris
c92746e875 src: add TOML parser 2021-02-20 17:04:49 +01:00
Arnaud Ferraris
19d00bee3b manager: make sure we don't block suspend when rebooting the modem 2021-02-20 17:04:38 +01:00
Arnaud Ferraris
b929c6a380 src: minor cleanups and cosmetic fixes 2021-02-20 17:01:17 +01:00
Arnaud Ferraris
4962fcd13e Merge branch 'sleep-inhibit' into 'master'
suspend: add boot timer

See merge request mobian1/devices/eg25-manager!7
2021-02-20 15:49:40 +00:00
Dylan Van Assche
b8d269cf2f suspend: add boot timer
The EG25 modem needs at least 2 minutes after indicating 'RDY'
to be fully operational. If the modem is suspended before that,
calls or texts may not be seen by the userspace.
This mostly occurs when a full reboot or poweroff/poweron
sequence of the phone is performed.

:
2021-02-17 21:05:58 +01:00
Arnaud Ferraris
4ff727a7d4 udev: install using meson 2021-02-16 23:48:35 +01:00
Arnaud Ferraris
7765e6c541 udev: rename configure script 2021-02-16 23:47:29 +01:00
Arnaud Ferraris
94cbd03921 udev: rules: fix wrong ENV match and pass devpath to script 2021-02-16 23:45:25 +01:00
Arnaud Ferraris
4c832a00dc configure-usb-modem: make more generic 2021-02-16 23:44:42 +01:00
Arnaud Ferraris
ded092ef04 Merge branch 'avoid-modem-resets' into 'master'
udev: add udev rule to configure modem USB

See merge request mobian1/devices/eg25-manager!5
2021-02-16 19:09:59 +00:00
Dylan Van Assche
235eff02ac udev: add udev rule to configure modem USB
Quectel advises to configure the USB autosuspend feature as:
- power/control: auto
- power/autosuspend_delay_ms: 3000

Futhermore, the following settings are also needed:
- power/wakeup: enabled
- power/persist: 0
- avoid_reset_quirk: 1
2021-02-16 07:12:01 +01:00
Arnaud Ferraris
3bb6e15de0 mm-iface: keep watching bus if MM vanishes 2021-01-25 23:55:57 +01:00
Arnaud Ferraris
692f9134f8 suspend: handle case when MM doesn't manage suspend/resume 2021-01-23 11:14:41 +01:00
Arnaud Ferraris
31133affa0 meson.build: release version 0.1.2 2021-01-14 00:07:15 +01:00
Arnaud Ferraris
79974bc9ee manager: don't manage the GNSS by default 2021-01-14 00:06:55 +01:00
Arnaud Ferraris
150ff67e7b udev: only check if modem USB ID is set 2021-01-13 23:46:38 +01:00
Arnaud Ferraris
c2e83f15a6 manager: improve error checking in modem_reset() 2020-12-30 17:31:44 +01:00
Arnaud Ferraris
cb5220a1b8 at: reduce delay between retries
There's no reason we should wait 3s before retrying a failed AT command. 
This can even cause issues with suspend inhibition: 3 retries of a 
failed command would take 9s, while logind ignores the inhibitor after 
only 5s.

Reducing the delay to 500ms should be enough and wouldn't interfere with 
suspend inhibition.

Fixes #3
2020-12-30 16:28:39 +01:00
Arnaud Ferraris
abc05e86e5 manager: only toggle GPIOs for suspend after executing all AT commands 2020-12-30 16:20:32 +01:00
Arnaud Ferraris
d990ab667e at: make sure URC cache is disabled on startup 2020-12-30 16:10:41 +01:00
Arnaud Ferraris
2a18b1cb0c meson.build: release version 0.1.1 2020-12-18 01:38:58 +01:00
Arnaud Ferraris
067c01b685 manager: rename suspend_source to suspend_timer
This makes its role more explicit.
2020-12-18 01:38:27 +01:00
Arnaud Ferraris
62a07f9c51 src: add udev watcher to improve modem recovery
Most of the modem issues follow a (incomplete) USB device reset. Instead 
of relying solely on the existing timer, this patch adds a udev monitor 
which resets the modem as soon as its associated USB device is reset, 
which greatly improves recovery time.
2020-12-18 01:37:06 +01:00
Arnaud Ferraris
74b91c7d58 manager: make sure we don't reset the modem twice in a row
This patch adds a 3s delay when resetting the modem during which we 
avoid triggering a new reset. This makes sure we don't trigger a reset 
twice in a row.

It also disables any related running timer to avoid being re-triggered 
unnecessarily.
2020-12-18 00:45:58 +01:00
Arnaud Ferraris
c39000bf93 suspend: increase modem detection delay by 1s
Sometimes it takes just a little bit longer than usual, so this avoids 
unnecessary modem recovery.
2020-12-18 00:35:32 +01:00
Arnaud Ferraris
fdbc2cfa69 Merge branch 'fix_gerror_pointer' into 'master'
mm-iface: fix GError pointer

See merge request mobian1/devices/eg25-manager!2
2020-12-14 00:29:58 +00:00
fortysixandtwo
aa85cd873c mm-iface: fix GError pointer 2020-12-13 16:54:10 +01:00
Arnaud Ferraris
a8e6da534c Merge branch 'autofree_gerror' into 'master'
use g_autoptr for GError

See merge request mobian1/devices/eg25-manager!1
2020-12-13 14:33:52 +00:00
fortysixandtwo
1bb2f80fef use g_autoptr for GError 2020-12-13 15:14:11 +01:00
Arnaud Ferraris
9c9169a972 manager: get rid of compiler warnings 2020-12-13 00:55:22 +01:00
Arnaud Ferraris
b495d6c747 gpio: get rid of compiler warnings 2020-12-13 00:52:38 +01:00
Arnaud Ferraris
dd904bc8c1 at: get rid of compiler warnings 2020-12-13 00:52:35 +01:00
Arnaud Ferraris
8c9a2b21f9 gpio: exit if we can't request output GPIOs
This means the system is in a very bad shape as no other software should 
make use of those, so exit the daemon (will be restarted by systemd).
2020-12-13 00:35:17 +01:00
Arnaud Ferraris
90a016a8f6 src: be more careful before dereferencing potentially NULL pointers 2020-12-12 23:59:53 +01:00
Arnaud Ferraris
ff60016e5d release version 0.0.6 2020-12-11 15:10:13 +01:00
Arnaud Ferraris
5715138a96 suspend: reset sleep inhibitor if already present upon resume 2020-12-11 15:09:43 +01:00
Arnaud Ferraris
fd6a292a8f manager: don't change modem state after reset
This makes sure the resume commands (disable URC cache and enable GPS)
are executed.
2020-12-11 15:09:38 +01:00
Arnaud Ferraris
5fa345ec92 mm-iface: don't reset USB ID unless quitting
As ModemManager releases the modem while going to sleep, we clear the
USB ID too, which causes a segfault when trying to recover the modem.
2020-12-11 14:34:34 +01:00
Arnaud Ferraris
a9725243ec meson.build: bump version 2020-12-11 13:37:40 +01:00
Arnaud Ferraris
75b0920e9d Revert "manager: split modem_suspend() into _pre() and _post() functions"
This reverts commit ff9b26b831.
2020-12-11 13:36:28 +01:00
Arnaud Ferraris
9713af7ca8 at: fix GPS disabling on suspend 2020-12-11 13:31:11 +01:00
Arnaud Ferraris
664f82d570 at: add default handling for unrecognized responses 2020-12-11 13:31:11 +01:00
24 changed files with 3209 additions and 196 deletions

View File

@@ -29,13 +29,20 @@ $ ninja -C ../eg25-build
# ninja -C ../eg25-build install
```
## Configuration
`eg25-manager` uses device-specific configuration files, named after the
device-tree `compatible` field. These files are installed to
`/usr/share/eg25-manager`. They can be copied to `/etc/eg25-manager` then
modified, that way they won't be overwritten during an upgrade.
## Running
`eg25-manager` is usually run as a systemd service, but can also be
manually started from the command-line (requires root privileges):
```
# eg25-manager
# eg25manager
```
## License

13
data/meson.build Normal file
View File

@@ -0,0 +1,13 @@
#
# Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
conf_files = [
'pine64,pinephone-1.0.toml',
'pine64,pinephone-1.1.toml',
'pine64,pinephone-1.2.toml',
]
install_data(conf_files)

View File

@@ -0,0 +1,55 @@
[manager]
need_libusb = true
usb_vid = 0x2c7c
usb_pid = 0x0125
# Uncomment the following if you need to change the modem detection timeout on
# resume and/or the time during which suspend is blocked after modem boot
#[suspend]
#boot_timeout = 120
#recovery_timeout = 9
[gpio]
dtr = 358
pwrkey = 35
reset = 68
apready = 231
disable = 232
[at]
uart = "/dev/ttyS2"
configure = [
# Each command has 4 possible elements:
# * `cmd` : the AT command itself, which will be translated to "AT+`cmd`"
# * `subcmd`: the subcommand in case a single AT command can be used
# to change multiple parameters, such as QCFG (optional)
# * `value` : the commands, argument, usually used to set the value of
# a specific parameter (optional)
# * `expect`: the expected return value; the command is first executed
# without any value in order to query the current state. This
# state is then compared to the `expect` string; if they don't
# 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
{ cmd = "QGMR" },
{ cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" },
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
{ 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/delay", expect = "1" },
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"usbat\"" },
{ cmd = "QGPS", value = "1" },
{ cmd = "QSCLK", value = "1" },
{ cmd = "QCFG", subcmd = "urc/cache", value = "0" }
]
suspend = [
{ cmd = "QGPSEND" },
{ cmd = "QCFG", subcmd = "urc/cache", value = "1" }
]
resume = [
{ cmd = "QCFG", subcmd = "urc/cache", value = "0" },
{ cmd = "QGPS", value = "1" }
]
reset = [ { cmd = "CFUN", value = "1,1" } ]

View File

@@ -0,0 +1,55 @@
[manager]
need_libusb = true
usb_vid = 0x2c7c
usb_pid = 0x0125
# Uncomment the following if you need to change the modem detection timeout on
# resume and/or the time during which suspend is blocked after modem boot
#[suspend]
#boot_timeout = 120
#recovery_timeout = 9
[gpio]
dtr = 358
pwrkey = 35
reset = 68
apready = 231
disable = 232
[at]
uart = "/dev/ttyS2"
configure = [
# Each command has 4 possible elements:
# * `cmd` : the AT command itself, which will be translated to "AT+`cmd`"
# * `subcmd`: the subcommand in case a single AT command can be used
# to change multiple parameters, such as QCFG (optional)
# * `value` : the commands, argument, usually used to set the value of
# a specific parameter (optional)
# * `expect`: the expected return value; the command is first executed
# without any value in order to query the current state. This
# state is then compared to the `expect` string; if they don't
# 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
{ cmd = "QGMR" },
{ cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" },
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
{ 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/delay", expect = "1" },
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"usbat\"" },
{ cmd = "QGPS", value = "1" },
{ cmd = "QSCLK", value = "1" },
{ cmd = "QCFG", subcmd = "urc/cache", value = "0" }
]
suspend = [
{ cmd = "QGPSEND" },
{ cmd = "QCFG", subcmd = "urc/cache", value = "1" }
]
resume = [
{ cmd = "QCFG", subcmd = "urc/cache", value = "0" },
{ cmd = "QGPS", value = "1" }
]
reset = [ { cmd = "CFUN", value = "1,1" } ]

View File

@@ -0,0 +1,48 @@
# Uncomment the following if you need to change the modem detection timeout on
# resume and/or the time during which suspend is blocked after modem boot
#[suspend]
#boot_timeout = 120
#recovery_timeout = 9
[gpio]
dtr = 34
pwrkey = 35
reset = 68
apready = 231
disable = 232
status = 233
[at]
uart = "/dev/ttyS2"
configure = [
# Each command has 4 possible elements:
# * `cmd` : the AT command itself, which will be translated to "AT+`cmd`"
# * `subcmd`: the subcommand in case a single AT command can be used
# to change multiple parameters, such as QCFG (optional)
# * `value` : the commands, argument, usually used to set the value of
# a specific parameter (optional)
# * `expect`: the expected return value; the command is first executed
# without any value in order to query the current state. This
# state is then compared to the `expect` string; if they don't
# 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
{ cmd = "QGMR" },
{ cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" },
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
{ cmd = "QCFG", subcmd = "apready", expect = "1,0,500" },
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"usbat\"" },
{ cmd = "QGPS", value = "1" },
{ cmd = "QSCLK", value = "1" },
{ cmd = "QCFG", subcmd = "urc/cache", value = "0" }
]
suspend = [
{ cmd = "QGPSEND" },
{ cmd = "QCFG", subcmd = "urc/cache", value = "1" }
]
resume = [
{ cmd = "QCFG", subcmd = "urc/cache", value = "0" },
{ cmd = "QGPS", value = "1" }
]
reset = [ { cmd = "CFUN", value = "1,1" } ]

View File

@@ -6,9 +6,9 @@
#
project (
'eg25manager',
'eg25-manager',
'c',
version : '0.0.1',
version : '0.2.0',
license : 'GPLv3+',
meson_version : '>= 0.50.0',
default_options :
@@ -27,6 +27,7 @@ prefix = get_option('prefix')
datadir = get_option('datadir')
sysconfdir = get_option('sysconfdir')
bindir = join_paths(prefix, get_option('bindir'))
udevrulesdir = join_paths(prefix, 'lib/udev/rules.d')
if datadir.startswith('/')
full_datadir = datadir
@@ -40,12 +41,21 @@ else
full_sysconfdir = join_paths(prefix, sysconfdir)
endif
eg25_confdir = join_paths(full_sysconfdir, meson.project_name())
eg25_datadir = join_paths(full_datadir, meson.project_name())
add_global_arguments('-D@0@="@1@"'.format('EG25_CONFDIR', eg25_confdir), language : 'c')
add_global_arguments('-D@0@="@1@"'.format('EG25_DATADIR', eg25_datadir), language : 'c')
mgr_deps = [
dependency('glib-2.0'),
dependency('gio-unix-2.0'),
dependency('gudev-1.0'),
dependency('libgpiod'),
dependency('libusb-1.0'),
dependency('mm-glib'),
]
subdir('data')
subdir('src')
subdir('udev')

185
src/at.c
View File

@@ -16,8 +16,6 @@
#include <glib-unix.h>
#define MODEM_UART "/dev/ttyS2"
struct AtCommand {
char *cmd;
char *subcmd;
@@ -26,6 +24,11 @@ struct AtCommand {
int retries;
};
static GArray *configure_commands = NULL;
static GArray *suspend_commands = NULL;
static GArray *resume_commands = NULL;
static GArray *reset_commands = NULL;
static int configure_serial(const char *tty)
{
struct termios ttycfg;
@@ -52,19 +55,24 @@ static int configure_serial(const char *tty)
static gboolean send_at_command(struct EG25Manager *manager)
{
char command[256];
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
int ret, len = 0;
if (at_cmd) {
if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
sprintf(command, "AT+%s?\r\n", at_cmd->cmd);
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)
len = sprintf(command, "AT+%s?\r\n", at_cmd->cmd);
else if (at_cmd->subcmd == NULL && at_cmd->value)
sprintf(command, "AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
len = sprintf(command, "AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
else if (at_cmd->subcmd && at_cmd->value == NULL)
sprintf(command, "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
len = sprintf(command, "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
else if (at_cmd->subcmd && at_cmd->value)
sprintf(command, "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
len = sprintf(command, "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
write(manager->at_fd, command, strlen(command));
ret = write(manager->at_fd, command, len);
if (ret < len)
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) {
@@ -75,8 +83,7 @@ static gboolean send_at_command(struct EG25Manager *manager)
else
manager->modem_state = EG25_STATE_CONFIGURED;
} else if (manager->modem_state == EG25_STATE_SUSPENDING) {
g_message("suspend sequence is over, drop inhibitor");
suspend_inhibit(manager, FALSE);
modem_suspend_post(manager);
} else if (manager->modem_state == EG25_STATE_RESETTING) {
manager->modem_state = EG25_STATE_POWERED;
}
@@ -86,7 +93,10 @@ static gboolean send_at_command(struct EG25Manager *manager)
static void next_at_command(struct EG25Manager *manager)
{
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
if (at_cmd->cmd)
g_free(at_cmd->cmd);
@@ -104,20 +114,26 @@ static void next_at_command(struct EG25Manager *manager)
static void retry_at_command(struct EG25Manager *manager)
{
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
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);
} else {
g_timeout_add_seconds(3, G_SOURCE_FUNC(send_at_command), manager);
g_timeout_add(500, G_SOURCE_FUNC(send_at_command), manager);
}
}
static void process_at_result(struct EG25Manager *manager, char *response)
{
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
if (at_cmd->expected && !strstr(response, at_cmd->expected)) {
if (at_cmd->value)
@@ -189,27 +205,102 @@ static gboolean modem_response(gint fd,
g_message("Response: [%s]", response);
if (strcmp(response, "RDY") == 0)
if (strcmp(response, "RDY") == 0) {
suspend_inhibit(manager, TRUE, TRUE);
manager->modem_state = EG25_STATE_STARTED;
}
else if (strstr(response, "ERROR"))
retry_at_command(manager);
else if (strstr(response, "OK"))
process_at_result(manager, response);
else
// Not a recognized response, try running next command, just in case
next_at_command(manager);
}
return TRUE;
}
int at_init(struct EG25Manager *manager)
static void parse_commands_list(toml_array_t *array, GArray **cmds)
{
manager->at_fd = configure_serial(MODEM_UART);
int len;
len = toml_array_nelem(array);
*cmds = g_array_new(FALSE, TRUE, sizeof(struct AtCommand));
g_array_set_size(*cmds, (guint)len);
for (int i = 0; i < len; i++) {
struct AtCommand *cmd = &g_array_index(*cmds, struct AtCommand, i);
toml_table_t *table = toml_table_at(array, i);
toml_datum_t value;
if (!table)
continue;
value = toml_string_in(table, "cmd");
if (value.ok) {
cmd->cmd = g_strdup(value.u.s);
free(value.u.s);
}
value = toml_string_in(table, "subcmd");
if (value.ok) {
cmd->subcmd = g_strdup(value.u.s);
free(value.u.s);
}
value = toml_string_in(table, "value");
if (value.ok) {
cmd->value = g_strdup(value.u.s);
free(value.u.s);
}
value = toml_string_in(table, "expect");
if (value.ok) {
cmd->expected = g_strdup(value.u.s);
free(value.u.s);
}
}
}
int at_init(struct EG25Manager *manager, toml_table_t *config)
{
toml_array_t *commands;
toml_datum_t uart_port;
uart_port = toml_string_in(config, "uart");
if (!uart_port.ok)
g_error("Configuration file lacks UART port definition");
manager->at_fd = configure_serial(uart_port.u.s);
if (manager->at_fd < 0) {
g_critical("Unable to configure %s", MODEM_UART);
g_critical("Unable to configure %s", uart_port.u.s);
free(uart_port.u.s);
return 1;
}
free(uart_port.u.s);
manager->at_source = g_unix_fd_add(manager->at_fd, G_IO_IN, modem_response, manager);
commands = toml_array_in(config, "configure");
if (!commands)
g_error("Configuration file lacks initial AT commands list");
parse_commands_list(commands, &configure_commands);
commands = toml_array_in(config, "suspend");
if (!commands)
g_error("Configuration file lacks suspend AT commands list");
parse_commands_list(commands, &suspend_commands);
commands = toml_array_in(config, "resume");
if (!commands)
g_error("Configuration file lacks resume AT commands list");
parse_commands_list(commands, &resume_commands);
commands = toml_array_in(config, "reset");
if (!commands)
g_error("Configuration file lacks reset AT commands list");
parse_commands_list(commands, &reset_commands);
return 0;
}
@@ -218,61 +309,45 @@ void at_destroy(struct EG25Manager *manager)
g_source_remove(manager->at_source);
if (manager->at_fd > 0)
close(manager->at_fd);
g_array_free(configure_commands, TRUE);
g_array_free(suspend_commands, TRUE);
g_array_free(resume_commands, TRUE);
g_array_free(reset_commands, TRUE);
}
void at_sequence_configure(struct EG25Manager *manager)
{
/*
* Default parameters in megi's driver which differ with our own:
* - urc/ri/* are always set the same way on both BH and CE
* - urc/ri/* pulse duration is 1 ms and urc/delay is 0 (no need to delay
* URCs if the pulse is that short)
* - apready is disabled
*
* Parameters set in megi's kernel but not here:
* - sleepind/level = 0
* - wakeupin/level = 0
* - ApRstLevel = 0
* - ModemRstLevel = 0
* - airplanecontrol = 1
* - fast/poweroff = 1
* (those would need to be researched to check their usefulness for our
* use-case)
*/
append_at_command(manager, "QDAI", NULL, NULL, "1,1,0,1,0,0,1,1");
append_at_command(manager, "QCFG", "risignaltype", NULL, "\"physical\"");
append_at_command(manager, "QCFG", "ims", NULL, "1");
if (manager->braveheart) {
append_at_command(manager, "QCFG", "urc/ri/ring", NULL, "\"pulse\",2000,1000,5000,\"off\",1");
append_at_command(manager, "QCFG", "urc/ri/smsincoming", NULL, "\"pulse\",2000");
append_at_command(manager, "QCFG", "urc/ri/other", NULL, "\"off\",1");
append_at_command(manager, "QCFG", "urc/delay", NULL, "1");
} else {
append_at_command(manager, "QCFG", "apready", NULL, "1,0,500");
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);
}
append_at_command(manager, "QURCCFG", "urcport", NULL, "\"usbat\"");
append_at_command(manager, "QGPS", NULL, NULL, "1");
append_at_command(manager, "QSCLK", NULL, "1", NULL);
send_at_command(manager);
}
void at_sequence_suspend(struct EG25Manager *manager)
{
append_at_command(manager, "QGPS", NULL, "0", NULL);
append_at_command(manager, "QCFG", "urc/cache", "1", NULL);
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);
}
send_at_command(manager);
}
void at_sequence_resume(struct EG25Manager *manager)
{
append_at_command(manager, "QCFG", "urc/cache", "0", NULL);
append_at_command(manager, "QGPS", NULL, "1", NULL);
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);
}
send_at_command(manager);
}
void at_sequence_reset(struct EG25Manager *manager)
{
append_at_command(manager, "CFUN", NULL, "1,1", NULL);
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);
}
send_at_command(manager);
}

View File

@@ -8,7 +8,7 @@
#include "manager.h"
int at_init(struct EG25Manager *data);
int at_init(struct EG25Manager *data, toml_table_t *config);
void at_destroy(struct EG25Manager *data);
void at_sequence_configure(struct EG25Manager *data);

View File

@@ -22,35 +22,12 @@ enum {
GPIO_OUT_COUNT
};
static unsigned gpio_out_idx_bh[] = {
358,
35,
68,
231,
232
};
static unsigned gpio_out_idx_ce[] = {
34,
35,
68,
231,
232
};
enum {
GPIO_IN_STATUS = 0,
GPIO_IN_COUNT
};
static unsigned gpio_in_idx_bh[] = {
GPIO_IDX_INVAL,
};
static unsigned gpio_in_idx_ce[] = {
233,
};
int gpio_sequence_poweron(struct EG25Manager *manager)
{
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 1);
@@ -92,11 +69,24 @@ int gpio_sequence_resume(struct EG25Manager *manager)
return 0;
}
int gpio_init(struct EG25Manager *manager)
static guint get_config_gpio(toml_table_t *config, const char *id)
{
toml_datum_t value = toml_int_in(config, id);
guint gpio;
if (!value.ok)
return GPIO_IDX_INVAL;
gpio = (guint)value.u.i;
return gpio;
}
int gpio_init(struct EG25Manager *manager, toml_table_t *config)
{
int i, ret;
unsigned *gpio_in_idx;
unsigned *gpio_out_idx;
guint gpio_out_idx[GPIO_OUT_COUNT];
guint gpio_in_idx[GPIO_IN_COUNT];
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
if (!manager->gpiochip[0]) {
@@ -110,16 +100,15 @@ int gpio_init(struct EG25Manager *manager)
return 1;
}
if (manager->braveheart) {
gpio_in_idx = gpio_in_idx_bh;
gpio_out_idx = gpio_out_idx_bh;
} else {
gpio_in_idx = gpio_in_idx_ce;
gpio_out_idx = gpio_out_idx_ce;
}
gpio_out_idx[GPIO_OUT_DTR] = get_config_gpio(config, "dtr");
gpio_out_idx[GPIO_OUT_PWRKEY] = get_config_gpio(config, "pwrkey");
gpio_out_idx[GPIO_OUT_RESET] = get_config_gpio(config, "reset");
gpio_out_idx[GPIO_OUT_APREADY] = get_config_gpio(config, "apready");
gpio_out_idx[GPIO_OUT_DISABLE] = get_config_gpio(config, "disable");
gpio_in_idx[GPIO_IN_STATUS] = get_config_gpio(config, "status");
for (i = 0; i < GPIO_OUT_COUNT; i++) {
unsigned int offset, chipidx;
guint offset, chipidx;
if (gpio_out_idx[i] < MAX_GPIOCHIP_LINES) {
offset = gpio_out_idx[i];
@@ -131,19 +120,19 @@ int gpio_init(struct EG25Manager *manager)
manager->gpio_out[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
if (!manager->gpio_out[i]) {
g_critical("Unable to get output GPIO %d", i);
g_error("Unable to get output GPIO %d", i);
return 1;
}
ret = gpiod_line_request_output(manager->gpio_out[i], "eg25manager", 0);
if (ret < 0) {
g_critical("Unable to request output GPIO %d", i);
g_error("Unable to request output GPIO %d", i);
return 1;
}
}
for (i = 0; i < GPIO_IN_COUNT; i++) {
unsigned int offset, chipidx;
guint offset, chipidx;
if (gpio_in_idx[i] == GPIO_IDX_INVAL)
continue;
@@ -177,7 +166,7 @@ gboolean gpio_check_poweroff(struct EG25Manager *manager, gboolean keep_down)
if (manager->gpio_in[GPIO_IN_STATUS] &&
gpiod_line_get_value(manager->gpio_in[GPIO_IN_STATUS]) == 1) {
if (keep_down) {
if (keep_down && manager->gpio_out[GPIO_OUT_RESET]) {
// Asserting RESET line to prevent modem from rebooting
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_RESET], 1);
}

View File

@@ -8,7 +8,7 @@
#include "manager.h"
int gpio_init(struct EG25Manager *state);
int gpio_init(struct EG25Manager *state, toml_table_t *config);
void gpio_destroy(struct EG25Manager *state);
int gpio_sequence_poweron(struct EG25Manager *state);

View File

@@ -9,6 +9,7 @@
#include "manager.h"
#include "mm-iface.h"
#include "suspend.h"
#include "udev.h"
#include <fcntl.h>
#include <signal.h>
@@ -18,8 +19,13 @@
#include <glib-unix.h>
#include <libusb.h>
#define EG25_USB_VID 0x2c7c
#define EG25_USB_PID 0x0125
#ifndef EG25_CONFDIR
#define EG25_CONFDIR "/etc/eg25-manager"
#endif
#ifndef EG25_DATADIR
#define EG25_DATADIR "/usr/share/eg25-manager"
#endif
static gboolean quit_app(struct EG25Manager *manager)
{
@@ -30,6 +36,7 @@ static gboolean quit_app(struct EG25Manager *manager)
at_destroy(manager);
mm_iface_destroy(manager);
suspend_destroy(manager);
udev_destroy(manager);
if (manager->modem_state >= EG25_STATE_STARTED) {
g_message("Powering down the modem...");
@@ -56,14 +63,14 @@ static gboolean modem_start(struct EG25Manager *manager)
libusb_device **devices = NULL;
struct libusb_device_descriptor desc;
if (manager->braveheart) {
if (manager->use_libusb) {
// BH don't have the STATUS line connected, so check if USB device is present
libusb_init(&ctx);
count = libusb_get_device_list(ctx, &devices);
for (i = 0; i < count; i++) {
libusb_get_device_descriptor(devices[i], &desc);
if (desc.idVendor == EG25_USB_VID && desc.idProduct == EG25_USB_PID) {
if (desc.idVendor == manager->usb_vid && desc.idProduct == manager->usb_pid) {
g_message("Found corresponding USB device, modem already powered");
should_boot = FALSE;
break;
@@ -110,45 +117,84 @@ void modem_configure(struct EG25Manager *manager)
at_sequence_configure(manager);
}
static gboolean modem_reset_done(struct EG25Manager* manager)
{
manager->modem_state = EG25_STATE_RESUMING;
manager->reset_timer = 0;
return FALSE;
}
void modem_reset(struct EG25Manager *manager)
{
int fd;
int fd, ret, len;
if (manager->reset_timer)
return;
if (manager->modem_recovery_timer) {
g_source_remove(manager->modem_recovery_timer);
manager->modem_recovery_timer = 0;
}
if (!manager->modem_usb_id) {
g_warning("Unknown modem USB ID");
goto error;
}
len = strlen(manager->modem_usb_id);
manager->modem_state = EG25_STATE_RESETTING;
fd = open("/sys/bus/usb/drivers/usb/unbind", O_WRONLY);
if (fd < 0)
goto error;
write(fd, manager->modem_usb_id, strlen(manager->modem_usb_id));
ret = write(fd, manager->modem_usb_id, len);
if (ret < len)
g_warning("Couldn't unbind modem: wrote %d/%d bytes", ret, len);
close(fd);
fd = open("/sys/bus/usb/drivers/usb/bind", O_WRONLY);
if (fd < 0)
goto error;
write(fd, manager->modem_usb_id, strlen(manager->modem_usb_id));
ret = write(fd, manager->modem_usb_id, len);
if (ret < len)
g_warning("Couldn't bind modem: wrote %d/%d bytes", ret, len);
close(fd);
manager->modem_state = EG25_STATE_CONFIGURED;
/*
* 3s is long enough to make sure the modem has been bound back and
* short enough to ensure it hasn't been acquired by ModemManager
*/
manager->reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset_done), manager);
return;
error:
// Everything else failed, reset the modem
// Release blocking sleep inhibitor
if (manager->suspend_block_fd >= 0)
suspend_inhibit(manager, FALSE, TRUE);
if (manager->modem_boot_timer) {
g_source_remove(manager->modem_boot_timer);
manager->modem_boot_timer = 0;
}
// Everything else failed, reboot the modem
at_sequence_reset(manager);
manager->modem_state = EG25_STATE_RESETTING;
}
void modem_suspend_pre(struct EG25Manager *manager)
{
manager->modem_state = EG25_STATE_SUSPENDING;
gpio_sequence_suspend(manager);
at_sequence_suspend(manager);
}
void modem_suspend_post(struct EG25Manager *manager)
{
at_sequence_suspend(manager);
gpio_sequence_suspend(manager);
g_message("suspend sequence is over, drop inhibitor");
suspend_inhibit(manager, FALSE, FALSE);
}
void modem_resume_pre(struct EG25Manager *manager)
{
manager->modem_state = EG25_STATE_RESUMING;
gpio_sequence_resume(manager);
}
@@ -157,32 +203,110 @@ void modem_resume_post(struct EG25Manager *manager)
at_sequence_resume(manager);
}
static toml_table_t *parse_config_file(char *config_file)
{
toml_table_t *toml_config;
gchar *compatible;
gchar error[256];
gsize len;
FILE *f = NULL;
if (config_file) {
f = fopen(config_file, "r");
} else if (g_file_get_contents("/proc/device-tree/compatible", &compatible, &len, NULL)) {
g_autoptr (GPtrArray) compat = g_ptr_array_new();
gsize pos = 0;
/*
* `compatible` file is a list of NULL-terminated strings, convert it
* to an array
*/
do {
g_ptr_array_add(compat, &compatible[pos]);
pos += strlen(&compatible[pos]) + 1;
} while (pos < len);
for (pos = 0; pos < compat->len; pos++) {
g_autofree gchar *filename = g_strdup_printf(EG25_CONFDIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
if (access(filename, F_OK) == 0) {
g_message("Opening config file: %s", filename);
f = fopen(filename, "r");
break;
}
}
if (!f) {
for (pos = 0; pos < compat->len; pos++) {
g_autofree gchar *filename = g_strdup_printf(EG25_DATADIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
if (access(filename, F_OK) == 0) {
g_message("Opening config file: %s", filename);
f = fopen(filename, "r");
break;
}
}
}
}
if (!f)
g_error("unable to find a suitable config file!");
toml_config = toml_parse_file(f, error, sizeof(error));
if (!toml_config)
g_error("unable to parse config file: %s", error);
return toml_config;
}
int main(int argc, char *argv[])
{
g_autoptr(GOptionContext) opt_context = NULL;
g_autoptr(GError) err = NULL;
struct EG25Manager manager;
char compatible[32];
int fd;
gchar *config_file = NULL;
toml_table_t *toml_config;
toml_table_t *toml_manager;
toml_datum_t toml_value;
const GOptionEntry options[] = {
{ "config", 'c', 0, G_OPTION_ARG_STRING, &config_file, "Config file to use.", NULL },
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
};
memset(&manager, 0, sizeof(manager));
manager.at_fd = -1;
manager.suspend_inhibit_fd = -1;
manager.suspend_delay_fd = -1;
manager.suspend_block_fd = -1;
opt_context = g_option_context_new ("- Power management for the Quectel EG25 modem");
g_option_context_add_main_entries (opt_context, options, NULL);
if (!g_option_context_parse (opt_context, &argc, &argv, &err)) {
g_warning ("%s", err->message);
return 1;
}
manager.loop = g_main_loop_new(NULL, FALSE);
fd = open("/proc/device-tree/compatible", O_RDONLY);
if (fd < 0) {
g_critical("Unable to read 'compatible' string from device tree");
return 1;
}
read(fd, compatible, sizeof(compatible));
if (!strstr(compatible, "pine64,pinephone-1.2"))
manager.braveheart = TRUE;
close(fd);
toml_config = parse_config_file(config_file);
at_init(&manager);
gpio_init(&manager);
mm_iface_init(&manager);
suspend_init(&manager);
toml_manager = toml_table_in(toml_config, "manager");
if (toml_manager) {
toml_value = toml_bool_in(toml_manager, "need_libusb");
if (toml_value.ok)
manager.use_libusb = toml_value.u.b;
toml_value = toml_int_in(toml_manager, "usb_vid");
if (toml_value.ok)
manager.usb_vid = toml_value.u.i;
toml_value = toml_int_in(toml_manager, "usb_pid");
if (toml_value.ok)
manager.usb_pid = toml_value.u.i;
}
at_init(&manager, toml_table_in(toml_config, "at"));
gpio_init(&manager, toml_table_in(toml_config, "gpio"));
mm_iface_init(&manager, toml_table_in(toml_config, "mm-iface"));
suspend_init(&manager, toml_table_in(toml_config, "suspend"));
udev_init(&manager, toml_table_in(toml_config, "udev"));
g_idle_add(G_SOURCE_FUNC(modem_start), &manager);

View File

@@ -8,8 +8,11 @@
#include <glib.h>
#include <gpiod.h>
#include <gudev/gudev.h>
#include <libmm-glib.h>
#include "toml.h"
enum EG25State {
EG25_STATE_INIT = 0,
EG25_STATE_POWERED, // Power-on sequence has been executed, but the modem isn't on yet
@@ -26,23 +29,32 @@ enum EG25State {
struct EG25Manager {
GMainLoop *loop;
guint reset_timer;
gboolean use_libusb;
guint usb_vid;
guint usb_pid;
int at_fd;
guint at_source;
GList *at_cmds;
enum EG25State modem_state;
gchar *modem_usb_id;
gboolean braveheart;
guint mm_watch;
MMManager *mm_manager;
MMModem *mm_modem;
GDBusProxy *suspend_proxy;
int suspend_inhibit_fd;
guint suspend_source;
int suspend_delay_fd;
int suspend_block_fd;
guint modem_recovery_timer;
guint modem_recovery_timeout;
guint modem_boot_timer;
guint modem_boot_timeout;
int at_fd;
guint at_source;
GList *at_cmds;
GUdevClient *udev;
struct gpiod_chip *gpiochip[2];
struct gpiod_line *gpio_out[5];

View File

@@ -12,6 +12,8 @@ executable (
'manager.c', 'manager.h',
'mm-iface.c', 'mm-iface.h',
'suspend.c', 'suspend.h',
'toml.c', 'toml.h',
'udev.c', 'udev.h',
],
dependencies : mgr_deps,
install : true

View File

@@ -35,7 +35,10 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
g_assert(manager->mm_modem != NULL);
if (manager->modem_state == EG25_STATE_RESUMING) {
g_source_remove(manager->suspend_source);
if (manager->modem_recovery_timer) {
g_source_remove(manager->modem_recovery_timer);
manager->modem_recovery_timer = 0;
}
modem_resume_post(manager);
manager->modem_state = EG25_STATE_CONFIGURED;
}
@@ -84,13 +87,8 @@ static void interface_removed_cb(struct EG25Manager *manager,
g_message("ModemManager interface `%s' removed on object `%s'", info->name, path);
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM) == 0) {
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM) == 0)
manager->mm_modem = NULL;
if (manager->modem_usb_id) {
g_free(manager->modem_usb_id);
manager->modem_usb_id = NULL;
}
}
}
@@ -132,14 +130,7 @@ static void object_removed_cb(struct EG25Manager *manager, GDBusObject *object)
path = g_dbus_object_get_object_path(object);
g_message("ModemManager object `%s' removed", path);
if (manager->modem_state == EG25_STATE_SUSPENDING)
modem_suspend_post(manager);
manager->mm_modem = NULL;
if (manager->modem_usb_id) {
g_free(manager->modem_usb_id);
manager->modem_usb_id = NULL;
}
}
@@ -147,7 +138,7 @@ static void mm_manager_new_cb(GDBusConnection *connection,
GAsyncResult *res,
struct EG25Manager *manager)
{
GError *error = NULL;
g_autoptr (GError) error = NULL;
manager->mm_manager = mm_manager_new_finish(res, &error);
if (!manager->mm_manager)
@@ -176,15 +167,27 @@ static void mm_appeared_cb(GDBusConnection *connection,
NULL, (GAsyncReadyCallback)mm_manager_new_cb, manager);
}
static void mm_iface_clean(struct EG25Manager *manager)
{
if (manager->mm_manager) {
g_clear_object(&manager->mm_manager);
manager->mm_manager = NULL;
}
if (manager->modem_usb_id) {
g_free(manager->modem_usb_id);
manager->modem_usb_id = NULL;
}
}
static void mm_vanished_cb(GDBusConnection *connection,
const gchar *name,
struct EG25Manager *manager)
{
g_message("ModemManager vanished from D-Bus");
mm_iface_destroy(manager);
mm_iface_clean(manager);
}
void mm_iface_init(struct EG25Manager *manager)
void mm_iface_init(struct EG25Manager *manager, toml_table_t *config)
{
manager->mm_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, MM_DBUS_SERVICE,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
@@ -195,10 +198,7 @@ void mm_iface_init(struct EG25Manager *manager)
void mm_iface_destroy(struct EG25Manager *manager)
{
if (manager->mm_manager) {
g_clear_object(&manager->mm_manager);
manager->mm_manager = NULL;
}
mm_iface_clean(manager);
if (manager->mm_watch != 0) {
g_bus_unwatch_name(manager->mm_watch);
manager->mm_watch = 0;

View File

@@ -8,5 +8,5 @@
#include "manager.h"
void mm_iface_init(struct EG25Manager *data);
void mm_iface_init(struct EG25Manager *data, toml_table_t *config);
void mm_iface_destroy(struct EG25Manager *data);

View File

@@ -13,67 +13,139 @@
#include <gio/gunixfdlist.h>
#define SD_NAME "org.freedesktop.login1"
#define SD_PATH "/org/freedesktop/login1"
#define SD_INTERFACE "org.freedesktop.login1.Manager"
#define SD_NAME "org.freedesktop.login1"
#define SD_PATH "/org/freedesktop/login1"
#define SD_INTERFACE "org.freedesktop.login1.Manager"
static gboolean check_modem_resume(struct EG25Manager *manager)
{
g_message("Modem wasn't probed in time, restart it!");
manager->modem_recovery_timer = 0;
modem_reset(manager);
return FALSE;
}
static gboolean drop_inhibitor(struct EG25Manager *manager)
static gboolean drop_inhibitor(struct EG25Manager *manager, gboolean block)
{
if (manager->suspend_inhibit_fd >= 0) {
g_message("dropping systemd sleep inhibitor");
close(manager->suspend_inhibit_fd);
manager->suspend_inhibit_fd = -1;
return TRUE;
if (block) {
if (manager->suspend_block_fd >= 0) {
g_message("dropping systemd sleep block inhibitor");
close(manager->suspend_block_fd);
manager->suspend_block_fd = -1;
return TRUE;
}
}
else {
if (manager->suspend_delay_fd >= 0) {
g_message("dropping systemd sleep delay inhibitor");
close(manager->suspend_delay_fd);
manager->suspend_delay_fd = -1;
return TRUE;
}
}
return FALSE;
}
static void inhibit_done(GObject *source,
GAsyncResult *result,
gpointer user_data)
static void inhibit_done_delay(GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GDBusProxy *suspend_proxy = G_DBUS_PROXY(source);
struct EG25Manager *manager = user_data;
GError *error = NULL;
g_autoptr (GError) error = NULL;
GVariant *res;
GUnixFDList *fd_list;
res = g_dbus_proxy_call_with_unix_fd_list_finish(suspend_proxy, &fd_list, result, &error);
res = g_dbus_proxy_call_with_unix_fd_list_finish(suspend_proxy, &fd_list,
result, &error);
if (!res) {
g_warning("inhibit failed: %s", error->message);
g_error_free(error);
} else {
if (!fd_list || g_unix_fd_list_get_length(fd_list) != 1)
g_warning("didn't get a single fd back");
manager->suspend_inhibit_fd = g_unix_fd_list_get(fd_list, 0, NULL);
manager->suspend_delay_fd = g_unix_fd_list_get(fd_list, 0, NULL);
g_message("inhibitor fd is %d", manager->suspend_inhibit_fd);
g_message("inhibitor sleep fd is %d", manager->suspend_delay_fd);
g_object_unref(fd_list);
g_variant_unref(res);
}
}
static void take_inhibitor(struct EG25Manager *manager)
static void inhibit_done_block(GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GDBusProxy *suspend_proxy = G_DBUS_PROXY(source);
struct EG25Manager *manager = user_data;
g_autoptr (GError) error = NULL;
GVariant *res;
GUnixFDList *fd_list;
res = g_dbus_proxy_call_with_unix_fd_list_finish(suspend_proxy, &fd_list,
result, &error);
if (!res) {
g_warning("inhibit failed: %s", error->message);
} else {
if (!fd_list || g_unix_fd_list_get_length(fd_list) != 1)
g_warning("didn't get a single fd back");
manager->suspend_block_fd = g_unix_fd_list_get(fd_list, 0, NULL);
g_message("inhibitor block fd is %d", manager->suspend_block_fd);
g_object_unref(fd_list);
g_variant_unref(res);
}
}
/*
* After the EG25 modem sends 'RDY', it takes up to 2 minutes before all
* capabilities are operational. If the modem is suspended before that,
* calls and texts may be not recognized properly.
*/
static gboolean modem_fully_booted(struct EG25Manager *manager)
{
g_message("Modem is up for %u seconds and fully ready", manager->modem_boot_timeout);
manager->modem_boot_timer = 0;
drop_inhibitor(manager, TRUE);
return FALSE;
}
static void take_inhibitor(struct EG25Manager *manager, gboolean block)
{
GVariant *variant_arg;
g_assert(manager->suspend_inhibit_fd == -1);
if (block) {
if(manager->suspend_block_fd != -1)
drop_inhibitor(manager, TRUE);
variant_arg = g_variant_new ("(ssss)", "sleep", "eg25manager",
"eg25manager needs to prepare modem for sleep", "delay");
variant_arg = g_variant_new ("(ssss)", "sleep", "eg25manager",
"eg25manager needs to wait for modem to be fully booted",
"block");
g_message("taking systemd sleep inhibitor");
g_dbus_proxy_call_with_unix_fd_list(manager->suspend_proxy, "Inhibit", variant_arg,
0, G_MAXINT, NULL, NULL, inhibit_done, manager);
g_message("taking systemd sleep inhibitor (blocking)");
g_dbus_proxy_call_with_unix_fd_list(manager->suspend_proxy, "Inhibit",
variant_arg, 0, G_MAXINT, NULL, NULL,
inhibit_done_block, manager);
manager->modem_boot_timer = g_timeout_add_seconds(manager->modem_boot_timeout,
G_SOURCE_FUNC(modem_fully_booted),
manager);
}
else {
if(manager->suspend_delay_fd != -1)
drop_inhibitor(manager, FALSE);
variant_arg = g_variant_new ("(ssss)", "sleep", "eg25manager",
"eg25manager needs to prepare modem for sleep",
"delay");
g_message("taking systemd sleep inhibitor");
g_dbus_proxy_call_with_unix_fd_list(manager->suspend_proxy, "Inhibit",
variant_arg, 0, G_MAXINT, NULL, NULL,
inhibit_done_delay, manager);
}
}
static void signal_cb(GDBusProxy *proxy,
@@ -92,12 +164,26 @@ static void signal_cb(GDBusProxy *proxy,
if (is_about_to_suspend) {
g_message("system is about to suspend");
manager->modem_state = EG25_STATE_SUSPENDING;
modem_suspend_pre(manager);
} else {
g_message("system is resuming");
take_inhibitor(manager);
take_inhibitor(manager, FALSE);
modem_resume_pre(manager);
manager->suspend_source = g_timeout_add_seconds(8, G_SOURCE_FUNC(check_modem_resume), manager);
if (manager->mm_modem) {
/*
* On some systems ModemManager doesn't handle suspend/resume, so
* we still have a valid/managed modem when resuming. In this case,
* do the whole resume sequence immediately.
*/
manager->modem_state = EG25_STATE_CONFIGURED;
modem_resume_post(manager);
} else {
manager->modem_state = EG25_STATE_RESUMING;
manager->modem_recovery_timer = g_timeout_add_seconds(manager->modem_recovery_timeout,
G_SOURCE_FUNC(check_modem_resume),
manager);
}
}
}
@@ -113,10 +199,10 @@ static void name_owner_cb(GObject *object,
owner = g_dbus_proxy_get_name_owner(proxy);
if (owner) {
take_inhibitor(manager);
take_inhibitor(manager, FALSE);
g_free(owner);
} else {
drop_inhibitor(manager);
drop_inhibitor(manager, FALSE);
}
}
@@ -124,28 +210,46 @@ static void on_proxy_acquired(GObject *object,
GAsyncResult *res,
struct EG25Manager *manager)
{
GError *error = NULL;
g_autoptr (GError) error = NULL;
char *owner;
manager->suspend_proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
if (!manager->suspend_proxy) {
g_warning("failed to acquire logind proxy: %s", error->message);
g_clear_error(&error);
return;
}
g_signal_connect(manager->suspend_proxy, "notify::g-name-owner", G_CALLBACK(name_owner_cb), manager);
g_signal_connect(manager->suspend_proxy, "g-signal", G_CALLBACK(signal_cb), manager);
g_signal_connect(manager->suspend_proxy, "notify::g-name-owner",
G_CALLBACK(name_owner_cb), manager);
g_signal_connect(manager->suspend_proxy, "g-signal",
G_CALLBACK(signal_cb), manager);
owner = g_dbus_proxy_get_name_owner(manager->suspend_proxy);
if (owner) {
take_inhibitor(manager);
take_inhibitor(manager, FALSE);
g_free(owner);
}
}
void suspend_init(struct EG25Manager *manager)
void suspend_init(struct EG25Manager *manager, toml_table_t *config)
{
toml_datum_t timeout_value;
if (config) {
timeout_value = toml_int_in(config, "boot_timeout");
if (timeout_value.ok)
manager->modem_boot_timeout = (guint)timeout_value.u.i;
timeout_value = toml_int_in(config, "recovery_timeout");
if (timeout_value.ok)
manager->modem_recovery_timeout = (guint)timeout_value.u.i;
}
if (manager->modem_boot_timeout == 0)
manager->modem_boot_timeout = 120;
if (manager->modem_recovery_timeout == 0)
manager->modem_recovery_timeout = 9;
g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
@@ -155,17 +259,26 @@ void suspend_init(struct EG25Manager *manager)
void suspend_destroy(struct EG25Manager *manager)
{
drop_inhibitor(manager);
drop_inhibitor(manager, FALSE);
drop_inhibitor(manager, TRUE);
if (manager->modem_recovery_timer) {
g_source_remove(manager->modem_recovery_timer);
manager->modem_recovery_timer = 0;
}
if (manager->modem_boot_timer) {
g_source_remove(manager->modem_boot_timer);
manager->modem_boot_timer = 0;
}
if (manager->suspend_proxy) {
g_object_unref(manager->suspend_proxy);
manager->suspend_proxy = NULL;
}
}
void suspend_inhibit(struct EG25Manager *manager, gboolean inhibit)
void suspend_inhibit(struct EG25Manager *manager, gboolean inhibit, gboolean block)
{
if (inhibit)
take_inhibitor(manager);
take_inhibitor(manager, block);
else
drop_inhibitor(manager);
drop_inhibitor(manager, block);
}

View File

@@ -8,7 +8,7 @@
#include "manager.h"
void suspend_init (struct EG25Manager *data);
void suspend_init (struct EG25Manager *data, toml_table_t *config);
void suspend_destroy (struct EG25Manager *data);
void suspend_inhibit (struct EG25Manager *data, gboolean inhibit);
void suspend_inhibit (struct EG25Manager *data, gboolean inhibit, gboolean block);

2249
src/toml.c Normal file

File diff suppressed because it is too large Load Diff

175
src/toml.h Normal file
View File

@@ -0,0 +1,175 @@
/*
MIT License
Copyright (c) 2017 - 2019 CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef TOML_H
#define TOML_H
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
#define TOML_EXTERN extern "C"
#else
#define TOML_EXTERN extern
#endif
typedef struct toml_timestamp_t toml_timestamp_t;
typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_datum_t toml_datum_t;
/* Parse a file. Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
char* errbuf,
int errbufsz);
/* Parse a string containing the full config.
* Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
char* errbuf,
int errbufsz);
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
* directly or indirectly are no longer valid.
*/
TOML_EXTERN void toml_free(toml_table_t* tab);
/* Timestamp types. The year, month, day, hour, minute, second, z
* fields may be NULL if they are not relevant. e.g. In a DATE
* type, the hour, minute, second and z fields will be NULLs.
*/
struct toml_timestamp_t {
struct { /* internal. do not use. */
int year, month, day;
int hour, minute, second, millisec;
char z[10];
} __buffer;
int *year, *month, *day;
int *hour, *minute, *second, *millisec;
char* z;
};
/*-----------------------------------------------------------------
* Enhanced access methods
*/
struct toml_datum_t {
int ok;
union {
toml_timestamp_t* ts; /* ts must be freed after use */
char* s; /* string value. s must be freed after use */
int b; /* bool value */
int64_t i; /* int value */
double d; /* double value */
} u;
};
/* on arrays: */
/* ... retrieve size of array. */
TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
/* ... retrieve values using index. */
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
/* ... retrieve array or table using index. */
TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
/* on tables: */
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
/* ... retrieve values using key. */
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
/* .. retrieve array or table using key. */
TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
const char* key);
TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
const char* key);
/*-----------------------------------------------------------------
* lesser used
*/
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
0 if unknown
*/
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
/* Return the key of an array */
TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
/* Return the number of key-values in a table */
TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
/* Return the number of arrays in a table */
TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
/* Return the number of sub-tables in a table */
TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
/* Return the key of a table*/
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
/*--------------------------------------------------------------
* misc
*/
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
void (*xxfree)(void*));
/*--------------------------------------------------------------
* deprecated
*/
/* A raw value, must be processed by toml_rto* before using. */
typedef const char* toml_raw_t;
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
#endif /* TOML_H */

44
src/udev.c Normal file
View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "udev.h"
#include <string.h>
static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data)
{
struct EG25Manager *manager = data;
if (strcmp(action, "unbind") != 0 ||
manager->modem_state == EG25_STATE_RESETTING ||
!manager->modem_usb_id) {
return;
}
if (strncmp(g_udev_device_get_name(device), manager->modem_usb_id, strlen(manager->modem_usb_id)) == 0 &&
manager->reset_timer == 0) {
g_message("Lost modem, resetting...");
modem_reset(manager);
}
}
void udev_init (struct EG25Manager *manager, toml_table_t *config)
{
const char * const subsystems[] = { "usb", NULL };
manager->udev = g_udev_client_new(subsystems);
g_signal_connect(manager->udev, "uevent", G_CALLBACK(udev_event_cb), manager);
return;
}
void udev_destroy (struct EG25Manager *manager)
{
if (manager->udev) {
g_object_unref(manager->udev);
manager->udev = NULL;
}
}

12
src/udev.h Normal file
View File

@@ -0,0 +1,12 @@
/*
* Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include "manager.h"
void udev_init (struct EG25Manager *data, toml_table_t *config);
void udev_destroy (struct EG25Manager *data);

1
udev/80-modem-eg25.rules Normal file
View File

@@ -0,0 +1 @@
ACTION=="add", SUBSYSTEM=="tty", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ENV{MINOR}=="0", RUN+="/usr/bin/eg25-configure-usb %p"

21
udev/eg25-configure-usb Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
DEVPATH=/sys/$1
USB_ID=
[ -d ${DEVPATH} ] || exit 1
while [ ! "${USB_ID}" ]; do
if [ -f ${DEVPATH}/avoid_reset_quirk ]; then
USB_ID=$(basename ${DEVPATH})
break
fi
DEVPATH=$(dirname ${DEVPATH})
done
# Avoid USB resets
echo "auto" > /sys/bus/usb/devices/${USB_ID}/power/control
echo "3000" > /sys/bus/usb/devices/${USB_ID}/power/autosuspend_delay_ms
echo "enabled" > /sys/bus/usb/devices/${USB_ID}/power/wakeup
echo "1" > /sys/bus/usb/devices/${USB_ID}/avoid_reset_quirk
echo "0" > /sys/bus/usb/devices/${USB_ID}/power/persist

8
udev/meson.build Normal file
View File

@@ -0,0 +1,8 @@
#
# Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
install_data ('eg25-configure-usb', install_dir: bindir)
install_data ('80-modem-eg25.rules', install_dir: udevrulesdir)