40 Commits
0.0.1 ... 0.1.2

Author SHA1 Message Date
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
Arnaud Ferraris
f386d851fa at: make sure we read the full response before processing it 2020-12-11 12:49:31 +01:00
Arnaud Ferraris
5bc8443c38 at: be less strict when checking for error
The response can include the command and an error number, so we want to 
only check it contains ERROR, even if it's replying more than just the 
'ERROR' string.
2020-12-11 12:49:31 +01:00
Arnaud Ferraris
aabe4df41c at: fix suspend/resume sequences
These are set commands, no need to verify the current value.
2020-12-11 12:49:31 +01:00
Arnaud Ferraris
ff9b26b831 manager: split modem_suspend() into _pre() and _post() functions
This way we can make sure the AT commands are executed only once 
ModemManager has released the modem, preventing any race condition.
2020-12-11 12:49:31 +01:00
Arnaud Ferraris
8d31e39e89 manager: only start the modem if it isn't already on 2020-12-11 12:49:10 +01:00
Arnaud Ferraris
87c7af7056 manager: actively poll GPIO when quitting
For some reason, during system the main loop exits before we call 
g_main_loop_quit(), so don't rely on it and use a simple polling loop to 
check for modem poweroff.
2020-12-10 21:33:18 +01:00
Arnaud Ferraris
fa21de07f4 src: be more careful when freeing data 2020-12-10 21:31:22 +01:00
Arnaud Ferraris
a8a1c8d161 manager: improve BH/CE detection
`Developer edition` phones have a different compatible string but should 
be considered identical to BraveHeart edition here. Therefore BH is the 
default phone, unless the compatible is that of a CE phone.
2020-12-10 17:07:36 +01:00
Arnaud Ferraris
47b2f71b6f improve poweroff sequence
When powering off the modem, we must assert the RESET line only when the 
modem has shut down (otherwise it'll cause a hard reset and won't allow 
the modem to shut down properly).

This commit also polls the STATUS pin during poweroff and quits 
immediately once this pin goes high.
2020-12-10 17:05:44 +01:00
Arnaud Ferraris
1170a2c7f7 gpio: configure STATUS input gpio
RI won't be accessible due to it being used by the kernel as an 
interrupt source, so we can check the STATUS pin (CE only) which will 
allow us to know precisely when the modem is shut down.
2020-12-10 17:03:11 +01:00
Arnaud Ferraris
e3387e85ca manager: destroy at/gpio modules after the main loop exits 2020-12-10 16:56:18 +01:00
11 changed files with 382 additions and 118 deletions

View File

@@ -8,7 +8,7 @@
project (
'eg25manager',
'c',
version : '0.0.1',
version : '0.1.2',
license : 'GPLv3+',
meson_version : '>= 0.50.0',
default_options :
@@ -43,7 +43,9 @@ endif
mgr_deps = [
dependency('glib-2.0'),
dependency('gio-unix-2.0'),
dependency('gudev-1.0'),
dependency('libgpiod'),
dependency('libusb-1.0'),
dependency('mm-glib'),
]

View File

@@ -31,7 +31,7 @@ static int configure_serial(const char *tty)
struct termios ttycfg;
int fd;
fd = open(tty, O_RDWR | O_NOCTTY);
fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd > 0) {
tcgetattr(fd, &ttycfg);
ttycfg.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
@@ -52,19 +52,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 +80,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 +90,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 +111,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)
@@ -143,7 +156,6 @@ static int append_at_command(struct EG25Manager *manager,
if (!at_cmd)
return -1;
at_cmd->retries = 0;
at_cmd->cmd = g_strdup(cmd);
if (subcmd)
at_cmd->subcmd = g_strdup(subcmd);
@@ -157,22 +169,33 @@ static int append_at_command(struct EG25Manager *manager,
return 0;
}
#define READ_BUFFER_SIZE 256
static gboolean modem_response(gint fd,
GIOCondition event,
gpointer data)
{
struct EG25Manager *manager = data;
char response[256];
int ret;
char response[READ_BUFFER_SIZE*4+1];
char tmp[READ_BUFFER_SIZE];
ssize_t ret, pos = 0;
/*
* TODO: several reads can be necessary to get the full response, we could
* loop until we read 0 chars with a reasonable delay between attempts
* Several reads can be necessary to get the full response, so we loop
* until we read 0 chars with a reasonable delay between attempts
* (remember the transfer rate is 115200 here)
*/
ret = read(fd, response, sizeof(response));
if (ret > 0) {
response[ret] = 0;
do {
ret = read(fd, tmp, sizeof(tmp));
if (ret > 0) {
memcpy(&response[pos], tmp, ret);
pos += ret;
usleep(10000);
}
} while (ret > 0 && pos < (sizeof(response) - 1));
if (pos > 0) {
response[pos] = 0;
g_strstrip(response);
if (strlen(response) == 0)
return TRUE;
@@ -181,10 +204,13 @@ static gboolean modem_response(gint fd,
if (strcmp(response, "RDY") == 0)
manager->modem_state = EG25_STATE_STARTED;
else if (strcmp(response, "ERROR") == 0)
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;
@@ -206,16 +232,17 @@ int at_init(struct EG25Manager *manager)
void at_destroy(struct EG25Manager *manager)
{
g_source_remove(manager->at_source);
close(manager->at_fd);
if (manager->at_fd > 0)
close(manager->at_fd);
}
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)
* - urc/ri/x are always set the same way on both BH and CE
* - urc/ri/x pulse duration is 1 ms and urc/delay is 0 (no need to delay
* URCs if the pulse is that short, so this is expected)
* - apready is disabled
*
* Parameters set in megi's kernel but not here:
@@ -241,14 +268,18 @@ void at_sequence_configure(struct EG25Manager *manager)
append_at_command(manager, "QCFG", "apready", NULL, "1,0,500");
}
append_at_command(manager, "QURCCFG", "urcport", NULL, "\"usbat\"");
append_at_command(manager, "QGPS", NULL, NULL, "1");
if (manager->manage_gnss)
append_at_command(manager, "QGPS", NULL, NULL, "1");
append_at_command(manager, "QSCLK", NULL, "1", NULL);
// Make sure URC cache is disabled
append_at_command(manager, "QCFG", "urc/cache", "0", NULL);
send_at_command(manager);
}
void at_sequence_suspend(struct EG25Manager *manager)
{
append_at_command(manager, "QGPS", NULL, NULL, "0");
if (manager->manage_gnss)
append_at_command(manager, "QGPSEND", NULL, NULL, NULL);
append_at_command(manager, "QCFG", "urc/cache", "1", NULL);
send_at_command(manager);
}
@@ -256,7 +287,8 @@ void at_sequence_suspend(struct EG25Manager *manager)
void at_sequence_resume(struct EG25Manager *manager)
{
append_at_command(manager, "QCFG", "urc/cache", "0", NULL);
append_at_command(manager, "QGPS", NULL, NULL, "1");
if (manager->manage_gnss)
append_at_command(manager, "QGPS", NULL, "1", NULL);
send_at_command(manager);
}

View File

@@ -11,6 +11,8 @@
#define MAX_GPIOCHIP_LINES 352
#define GPIO_IDX_INVAL 0xffff
enum {
GPIO_OUT_DTR = 0,
GPIO_OUT_PWRKEY,
@@ -20,7 +22,7 @@ enum {
GPIO_OUT_COUNT
};
static unsigned gpio_idx_bh[] = {
static unsigned gpio_out_idx_bh[] = {
358,
35,
68,
@@ -28,7 +30,7 @@ static unsigned gpio_idx_bh[] = {
232
};
static unsigned gpio_idx_ce[] = {
static unsigned gpio_out_idx_ce[] = {
34,
35,
68,
@@ -36,24 +38,34 @@ static unsigned gpio_idx_ce[] = {
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);
sleep(1);
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 0);
g_message("Executed power-on sequence");
g_message("Executed power-on/off sequence");
return 0;
}
int gpio_sequence_shutdown(struct EG25Manager *manager)
{
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_RESET], 1);
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DISABLE], 1);
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 1);
sleep(1);
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 0);
gpio_sequence_poweron(manager);
g_message("Executed power-off sequence");
@@ -83,7 +95,8 @@ int gpio_sequence_resume(struct EG25Manager *manager)
int gpio_init(struct EG25Manager *manager)
{
int i, ret;
unsigned *gpio_idx;
unsigned *gpio_in_idx;
unsigned *gpio_out_idx;
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
if (!manager->gpiochip[0]) {
@@ -91,53 +104,106 @@ int gpio_init(struct EG25Manager *manager)
return 1;
}
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
if (!manager->gpiochip[1]) {
g_critical("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
return 1;
}
if (manager->braveheart) {
// BH have DTR on the 2nd gpiochip
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
if (!manager->gpiochip[1]) {
g_critical("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
return 1;
}
gpio_idx = gpio_idx_bh;
gpio_in_idx = gpio_in_idx_bh;
gpio_out_idx = gpio_out_idx_bh;
} else {
gpio_idx = gpio_idx_ce;
gpio_in_idx = gpio_in_idx_ce;
gpio_out_idx = gpio_out_idx_ce;
}
for (i = 0; i < GPIO_OUT_COUNT; i++) {
unsigned int offset, chipidx;
if (gpio_idx[i] < MAX_GPIOCHIP_LINES) {
offset = gpio_idx[i];
if (gpio_out_idx[i] < MAX_GPIOCHIP_LINES) {
offset = gpio_out_idx[i];
chipidx = 0;
} else {
offset = gpio_idx[i] - MAX_GPIOCHIP_LINES;
offset = gpio_out_idx[i] - MAX_GPIOCHIP_LINES;
chipidx = 1;
}
manager->gpio_out[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
if (!manager->gpio_out[i]) {
g_critical("Unable to get line %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 line %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;
if (gpio_in_idx[i] == GPIO_IDX_INVAL)
continue;
if (gpio_in_idx[i] < MAX_GPIOCHIP_LINES) {
offset = gpio_in_idx[i];
chipidx = 0;
} else {
offset = gpio_in_idx[i] - MAX_GPIOCHIP_LINES;
chipidx = 1;
}
manager->gpio_in[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
if (!manager->gpio_in[i]) {
g_warning("Unable to get input GPIO %d", i);
continue;
}
ret = gpiod_line_request_input(manager->gpio_in[i], "eg25manager");
if (ret < 0) {
g_warning("Unable to request input GPIO %d", i);
manager->gpio_in[i] = NULL;
}
}
return 0;
}
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 && 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);
}
return TRUE;
}
return FALSE;
}
void gpio_destroy(struct EG25Manager *manager)
{
int i;
for (i = 0; i < GPIO_OUT_COUNT; i++)
gpiod_line_release(manager->gpio_out[i]);
for (i = 0; i < GPIO_OUT_COUNT; i++) {
if (manager->gpio_out[i])
gpiod_line_release(manager->gpio_out[i]);
}
gpiod_chip_close(manager->gpiochip[0]);
for (i = 0; i < GPIO_IN_COUNT; i++) {
if (manager->gpio_in[i])
gpiod_line_release(manager->gpio_in[i]);
}
if (manager->gpiochip[0])
gpiod_chip_close(manager->gpiochip[0]);
if (manager->gpiochip[1])
gpiod_chip_close(manager->gpiochip[1]);
}

View File

@@ -15,3 +15,5 @@ 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);
gboolean gpio_check_poweroff(struct EG25Manager *manager, gboolean keep_down);

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>
@@ -16,45 +17,75 @@
#include <unistd.h>
#include <glib-unix.h>
#include <libusb.h>
static gboolean quit_timeout_cb(struct EG25Manager *manager)
{
g_message("Modem down, quitting...");
g_main_loop_quit(manager->loop);
at_destroy(manager);
gpio_destroy(manager);
return FALSE;
}
#define EG25_USB_VID 0x2c7c
#define EG25_USB_PID 0x0125
static gboolean quit_app(struct EG25Manager *manager)
{
int i;
g_message("Request to quit...");
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...");
gpio_sequence_shutdown(manager);
manager->modem_state = EG25_STATE_FINISHING;
/*
* TODO: add a polling function to check STATUS and RI pins state
* (that way we could reduce the poweroff delay)
*/
g_timeout_add_seconds(30, G_SOURCE_FUNC(quit_timeout_cb), manager);
for (i = 0; i < 30; i++) {
if (gpio_check_poweroff(manager, TRUE))
break;
sleep(1);
}
}
g_message("Modem down, quitting...");
mm_iface_destroy(manager);
suspend_destroy(manager);
g_bus_unwatch_name(manager->mm_watch);
g_main_loop_quit(manager->loop);
return FALSE;
}
static gboolean modem_start(struct EG25Manager *manager)
{
g_message("Starting modem...");
gpio_sequence_poweron(manager);
manager->modem_state = EG25_STATE_POWERED;
ssize_t i, count;
gboolean should_boot = TRUE;
libusb_context *ctx = NULL;
libusb_device **devices = NULL;
struct libusb_device_descriptor desc;
if (manager->braveheart) {
// 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) {
g_message("Found corresponding USB device, modem already powered");
should_boot = FALSE;
break;
}
}
libusb_free_device_list(devices, 1);
libusb_exit(ctx);
} else if (!gpio_check_poweroff(manager, FALSE)) {
g_message("STATUS is low, modem already powered");
should_boot = FALSE;
}
if (should_boot) {
g_message("Starting modem...");
gpio_sequence_poweron(manager);
manager->modem_state = EG25_STATE_POWERED;
} else {
manager->modem_state = EG25_STATE_STARTED;
}
return FALSE;
}
@@ -81,35 +112,73 @@ 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->suspend_timer) {
g_source_remove(manager->suspend_timer);
manager->suspend_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
at_sequence_reset(manager);
manager->modem_state = EG25_STATE_RESETTING;
}
void modem_suspend(struct EG25Manager *manager)
void modem_suspend_pre(struct EG25Manager *manager)
{
at_sequence_suspend(manager);
}
void modem_suspend_post(struct EG25Manager *manager)
{
gpio_sequence_suspend(manager);
at_sequence_suspend(manager);
g_message("suspend sequence is over, drop inhibitor");
suspend_inhibit(manager, FALSE);
}
void modem_resume_pre(struct EG25Manager *manager)
@@ -124,14 +193,27 @@ void modem_resume_post(struct EG25Manager *manager)
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;
int fd, ret;
const GOptionEntry options[] = {
{ "gnss", 'g', 0, G_OPTION_ARG_NONE, &manager.manage_gnss, "Manage the GNSS feature.", 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;
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);
@@ -139,8 +221,8 @@ int main(int argc, char *argv[])
g_critical("Unable to read 'compatible' string from device tree");
return 1;
}
read(fd, compatible, sizeof(compatible));
if (strstr(compatible, "pine64,pinephone-1.1"))
ret = read(fd, compatible, sizeof(compatible));
if (ret > 0 && !strstr(compatible, "pine64,pinephone-1.2"))
manager.braveheart = TRUE;
close(fd);
@@ -148,6 +230,7 @@ int main(int argc, char *argv[])
gpio_init(&manager);
mm_iface_init(&manager);
suspend_init(&manager);
udev_init(&manager);
g_idle_add(G_SOURCE_FUNC(modem_start), &manager);
@@ -156,5 +239,7 @@ int main(int argc, char *argv[])
g_main_loop_run(manager.loop);
gpio_destroy(&manager);
return 0;
}

View File

@@ -8,6 +8,7 @@
#include <glib.h>
#include <gpiod.h>
#include <gudev/gudev.h>
#include <libmm-glib.h>
enum EG25State {
@@ -26,6 +27,12 @@ enum EG25State {
struct EG25Manager {
GMainLoop *loop;
guint reset_timer;
gboolean manage_gnss;
int at_fd;
guint at_source;
GList *at_cmds;
enum EG25State modem_state;
gchar *modem_usb_id;
@@ -37,20 +44,19 @@ struct EG25Manager {
GDBusProxy *suspend_proxy;
int suspend_inhibit_fd;
guint suspend_source;
guint suspend_timer;
int at_fd;
guint at_source;
GList *at_cmds;
GUdevClient *udev;
struct gpiod_chip *gpiochip[2];
struct gpiod_line *gpio_out[5];
struct gpiod_line *gpio_in[2];
};
void modem_configure(struct EG25Manager *data);
void modem_reset(struct EG25Manager *data);
void modem_suspend(struct EG25Manager *data);
void modem_suspend_pre(struct EG25Manager *data);
void modem_suspend_post(struct EG25Manager *data);
void modem_resume_pre(struct EG25Manager *data);
void modem_resume_post(struct EG25Manager *data);
void modem_update_state(struct EG25Manager *data, MMModemState state);

View File

@@ -12,6 +12,7 @@ executable (
'manager.c', 'manager.h',
'mm-iface.c', 'mm-iface.h',
'suspend.c', 'suspend.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->suspend_timer) {
g_source_remove(manager->suspend_timer);
manager->suspend_timer = 0;
}
modem_resume_post(manager);
manager->modem_state = EG25_STATE_CONFIGURED;
}
@@ -47,6 +50,9 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
modem_configure(manager);
path = mm_modem_get_device(manager->mm_modem);
if (manager->modem_usb_id)
g_free(manager->modem_usb_id);
manager->modem_usb_id = g_strdup(strrchr(path, '/') + 1);
gdbus_modem = MM_GDBUS_MODEM(manager->mm_modem);
@@ -81,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;
}
}
}
@@ -130,10 +131,6 @@ static void object_removed_cb(struct EG25Manager *manager, GDBusObject *object)
g_message("ModemManager object `%s' removed", path);
manager->mm_modem = NULL;
if (manager->modem_usb_id) {
g_free(manager->modem_usb_id);
manager->modem_usb_id = NULL;
}
}
@@ -141,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)
@@ -189,5 +186,16 @@ void mm_iface_init(struct EG25Manager *manager)
void mm_iface_destroy(struct EG25Manager *manager)
{
g_clear_object(&manager->mm_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;
}
if (manager->mm_watch != 0) {
g_bus_unwatch_name(manager->mm_watch);
manager->mm_watch = 0;
}
}

View File

@@ -20,6 +20,7 @@
static gboolean check_modem_resume(struct EG25Manager *manager)
{
g_message("Modem wasn't probed in time, restart it!");
manager->suspend_timer = 0;
modem_reset(manager);
return FALSE;
@@ -42,14 +43,13 @@ static void inhibit_done(GObject *source,
{
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);
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");
@@ -66,7 +66,8 @@ static void take_inhibitor(struct EG25Manager *manager)
{
GVariant *variant_arg;
g_assert(manager->suspend_inhibit_fd == -1);
if(manager->suspend_inhibit_fd != -1)
drop_inhibitor(manager);
variant_arg = g_variant_new ("(ssss)", "sleep", "eg25manager",
"eg25manager needs to prepare modem for sleep", "delay");
@@ -93,13 +94,13 @@ 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(manager);
modem_suspend_pre(manager);
} else {
g_message("system is resuming");
take_inhibitor(manager);
modem_resume_pre(manager);
manager->modem_state = EG25_STATE_RESUMING;
manager->suspend_source = g_timeout_add_seconds(8, G_SOURCE_FUNC(check_modem_resume), manager);
manager->suspend_timer = g_timeout_add_seconds(9, G_SOURCE_FUNC(check_modem_resume), manager);
}
}
@@ -114,24 +115,24 @@ static void name_owner_cb(GObject *object,
g_assert(proxy == manager->suspend_proxy);
owner = g_dbus_proxy_get_name_owner(proxy);
if (owner)
if (owner) {
take_inhibitor(manager);
else
g_free(owner);
} else {
drop_inhibitor(manager);
g_free(owner);
}
}
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;
}
@@ -139,9 +140,10 @@ static void on_proxy_acquired(GObject *object,
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)
if (owner) {
take_inhibitor(manager);
g_free(owner);
g_free(owner);
}
}
void suspend_init(struct EG25Manager *manager)
@@ -156,7 +158,14 @@ void suspend_init(struct EG25Manager *manager)
void suspend_destroy(struct EG25Manager *manager)
{
drop_inhibitor(manager);
g_object_unref(manager->suspend_proxy);
if (manager->suspend_timer) {
g_source_remove(manager->suspend_timer);
manager->suspend_timer = 0;
}
if (manager->suspend_proxy) {
g_object_unref(manager->suspend_proxy);
manager->suspend_proxy = NULL;
}
}
void suspend_inhibit(struct EG25Manager *manager, gboolean inhibit)

41
src/udev.c Normal file
View File

@@ -0,0 +1,41 @@
/*
* 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)
{
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);
void udev_destroy (struct EG25Manager *data);