22 Commits
0.4.1 ... 0.4.3

Author SHA1 Message Date
Arnaud Ferraris
ee70cf7d2f meson.build: release version 0.4.3 2022-02-19 15:17:40 +01:00
Arnaud Ferraris
0e2311fb36 Rename executable to eg25-manager
This brings more consistency with how other components are named
(repo, package, service file, manpages...).
2022-02-19 15:17:27 +01:00
Arnaud Ferraris
5e4c797695 meson: bump minimum version
`doc/meson.build` used currently-deprecated functions. Replace those
with the proper alternatives, which were introduced in a newer meson
version than our requirement. As a consequence, bump minimal version.
2022-02-19 15:05:31 +01:00
Arnaud Ferraris
be1ae18592 Move manpages to doc/ subfolder 2022-02-19 15:02:43 +01:00
Arnaud Ferraris
19eb488e29 gpio: fix init for BH edition PinePhone
BH PinePhone don't have the modem's `STATUS` pin connected to the SoC,
and as such require using `libusb` for checking the modem power state.
We didn't handle this case previously due to lack of on-device testing,
causing BH phones to fail with newer versions.

This patch ignores the `status` GPIO for devices relying on `libusb`,
which are only pre-CE PinePhones.
2022-02-19 13:50:41 +00:00
ArenM
a3c51fc513 Add manpages for eg25-manager and it's config file 2022-02-18 18:54:47 -05:00
Arnaud Ferraris
75400fb9c0 data: add USB vendor/product IDs to all configs
PP 1.2 and PPP configs were lacking this parameter. Although it's not a
problem as `eg25-manager` will then use a default value, adding them
won't harm and make them more consistent with other config files.
2022-02-07 12:43:49 +01:00
Arnaud Ferraris
97b1878ebc manager: disable reset timers once reset sequence starts
`modem_reset()` can be called from multiple places, both tied to a
specific timer. In order to prevent calling this function multiple times
in a row, disable all related timers as soon as we enter this function.
2022-02-07 12:43:49 +01:00
Arnaud Ferraris
9e6bccdf37 udev: use USB vendor/product ID to determine behavior
When going into firmware upgrade mode, the modem comes back with
different IDs than in normal use. We should make sure we cancel the
reset sequence when that happens, and not start a new one.
2022-02-07 12:43:49 +01:00
Arnaud Ferraris
df79247821 manager: populate USB vendor/product ID's with default values
We'll need those in the `udev` module, but never made it a mandatory
config option. This commit makes sure those values are properly filled
in.
2022-01-04 15:24:01 +01:00
Dylan Van Assche
61c89a003a udev: cancel reset if modem is already back
Cancel reset if modem is back before reset sequence is started.
When upgrading the modem through fwupd, it will enter fastboot.
Therefore, it disappears from the USB bus for a few ms.
However, the eg25-manager considers this as an issue and
resets the modem a bit later during the upgrade process.
To avoid this, cancel the reset sequence if the modem is already
back before the reset sequence is started. This will take less than 3
seconds.
2022-01-04 14:41:54 +01:00
Arnaud Ferraris
9cf51b9529 pinephone-pro: switch to 16K audio
As the modem is connected to a dedicated audio codec, we can select any
sample rate. Let's go for 16K as it should improve call audio quality.

Note: the bitclock's frequency is left untouched due to a bug in the
EG25 kernel, so we can't take advantage of the recommended higher
frequency.
2021-12-24 15:14:43 +00:00
Arnaud Ferraris
50b4c00c16 data: add 'monitor_udev' parameter to existing configs
We disable dev monitoring only for the PinePhone Pro, it remains enabled
for all variants of the OG PinePhone.
2021-12-24 15:14:43 +00:00
Arnaud Ferraris
fedce7298b manager: start udev monitoring module conditionally
The problem addressed by monitoring the modem status through udev seems
tied to the specific USB controller used by the A64. On RK3399 devices,
this quirk is apparently unneeded, and actually harmful as it resets the
modem USB connection while ModemManager is already configuring it.

This commit adds an optional config parameter for disabling this module
(enabled by default).
2021-12-24 15:14:43 +00:00
Rafael Diniz
8665f8a4a6 Fix compile error when not using Modem Manager. 2021-12-23 21:12:24 +03:00
Kai Lüke
88c68b9933 suspend: actually check if the modem is found before resetting
The timeout was meant to give more time to find the modem, yet the
callback didn't actually check if it came back but blindly issued a
reset.
Add a check to the callback to see if the modem was found and only
reset if not.

Fixes https://gitlab.com/mobian1/devices/eg25-manager/-/issues/28
2021-12-15 22:47:40 +00:00
Arnaud Ferraris
a91bc71e23 meson.build: release version 0.4.2 2021-12-08 18:21:48 +01:00
Arnaud Ferraris
b21c4b0fa4 data: add PinePhone Pro config
The PinePhone Pro uses the same EG25 modem as the OG PinePhone, but with
a different SoC. It also uses an ALC5616 audio codec directly hooked up
to the modem (which is I2S master in this case).

The config is therefore almost identical to the PinePhone rev1.2 except
for the gpios, UART port used and `AT+QDAI` config (to account for the
different audio setup).
2021-11-27 13:12:17 +01:00
Arnaud Ferraris
abf60b793a gpio: make more generic
Instead of assuming we're running on the PinePhone (and therefore
hardcoding gpiochip labels and line numbers), make all of those
configurable. Legacy config files will still be parsed as long as they
lack the `chips` key.

Existing config files are also updated to match the new config format.
2021-11-24 01:34:14 +01:00
Arnaud Ferraris
f8b3e28434 config: add config_get_table helper function 2021-11-24 01:20:54 +01:00
Arnaud Ferraris
d9725981bb Merge branch 'at-overflow' into 'master'
at: break before overflow when receiving messages

See merge request mobian1/devices/eg25-manager!40
2021-11-10 10:50:58 +00:00
ArenM
a06360f4c8 at: break before overflow when receiving messages
Previously this code checked if the buffer was full after writing to it,
which meant that the buffer could overflow.

This checks for an overflow before copying into the buffer and only
copies the data that will fit.
2021-11-06 19:59:22 -04:00
19 changed files with 549 additions and 95 deletions

View File

@@ -4,7 +4,7 @@ Before=ModemManager.service
[Service]
Type=simple
ExecStart=@bindir@/eg25manager
ExecStart=@bindir@/eg25-manager
Restart=on-failure
ProtectControlGroups=true
ProtectHome=true

View File

@@ -8,6 +8,7 @@ conf_files = [
'pine64,pinephone-1.0.toml',
'pine64,pinephone-1.1.toml',
'pine64,pinephone-1.2.toml',
'pine64,pinephone-pro.toml',
]
install_data(conf_files)

View File

@@ -1,4 +1,5 @@
[manager]
monitor_udev = true
need_libusb = true
usb_vid = 0x2c7c
usb_pid = 0x0125
@@ -13,11 +14,12 @@ poweron_delay = 100000
#recovery_timeout = 9
[gpio]
dtr = 358
pwrkey = 35
reset = 68
apready = 231
disable = 232
chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
dtr = { chip = 1, line = 6 }
pwrkey = { chip = 0, line = 35 }
reset = { chip = 0, line = 68 }
apready = { chip = 0, line = 231 }
disable = { chip = 0, line = 232 }
[at]
uart = "/dev/ttyS2"

View File

@@ -1,4 +1,5 @@
[manager]
monitor_udev = true
need_libusb = true
usb_vid = 0x2c7c
usb_pid = 0x0125
@@ -13,11 +14,12 @@ poweron_delay = 100000
#recovery_timeout = 9
[gpio]
dtr = 358
pwrkey = 35
reset = 68
apready = 231
disable = 232
chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
dtr = { chip = 1, line = 6 }
pwrkey = { chip = 0, line = 35 }
reset = { chip = 0, line = 68 }
apready = { chip = 0, line = 231 }
disable = { chip = 0, line = 232 }
[at]
uart = "/dev/ttyS2"

View File

@@ -1,4 +1,7 @@
[manager]
monitor_udev = true
usb_vid = 0x2c7c
usb_pid = 0x0125
# Delay between setting GPIO and PWRKEY sequence, set in microseconds
poweron_delay = 100000
@@ -9,12 +12,13 @@ poweron_delay = 100000
#recovery_timeout = 9
[gpio]
dtr = 34
pwrkey = 35
reset = 68
apready = 231
disable = 232
status = 233
chips = [ "1c20800.pinctrl" ]
dtr = { chip = 0, line = 34 }
pwrkey = { chip = 0, line = 35 }
reset = { chip = 0, line = 68 }
apready = { chip = 0, line = 231 }
disable = { chip = 0, line = 232 }
status = { chip = 0, line = 233 }
[at]
uart = "/dev/ttyS2"
@@ -59,9 +63,9 @@ configure = [
{ cmd = "QSCLK", value = "1" },
# GNSS configuration:
# * Enable A-GPS data upload support (XTRA)
# * Disable On-Demand-Positioning (ODP) mode
# * 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
# * 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.

View File

@@ -0,0 +1,100 @@
[manager]
monitor_udev = false
usb_vid = 0x2c7c
usb_pid = 0x0125
# Delay between setting GPIO and PWRKEY sequence, set in microseconds
poweron_delay = 100000
# 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]
chips = [ "gpio0", "gpio3" ]
dtr = { chip = 0, line = 3 }
pwrkey = { chip = 0, line = 13 }
reset = { chip = 1, line = 8 }
apready = { chip = 0, line = 12 }
disable = { chip = 0, line = 8 }
status = { chip = 1, line = 6 }
[at]
uart = "/dev/ttyS3"
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
# Print software version
{ cmd = "QGMR" },
# Configure audio
{ cmd = "QDAI", expect = "3,0,0,4,0,1,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)
# * 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" },
# 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 = [
]
resume = [
]
reset = [ { cmd = "CFUN", value = "1,1" } ]
[gnss]
enabled = true
url = "https://xtrapath4.izatcloud.net"
file = "xtra2.bin"

106
doc/eg25-manager.5.scd Normal file
View File

@@ -0,0 +1,106 @@
eg25-manager(5)
# NAME
eg25-manager configuration file format
# SYNOPSIS
eg25-manager uses toml formatted files for configuration.
Configurations are loaded from:
- *@eg25_confdir@/<compatible>.toml*: User-provided overrides (optional)
- *@eg25_datadir@/<compatible>.toml*: Default configuration (required)
# SECTION: manager
General settings for eg25-manager.
*poweron_delay* int (microseconds)
Delay between de-asserting RESET and starting the PWRKEY sequence.
# SECTION: suspend
Settings for how to handle suspend on the system where eg25-manager is running.
*boot_timeout* int (seconds)
Prevent the system from suspending for boot_timeout seconds to allow the
modem to fully boot.
Default: 120 if unset or zero.
*recovery_timeout* int (seconds)
Amount of time to wait for the modem to reappear after suspend. If the
timeout is reached the modem's USB connection will be reset.
Default: 9 if unset or zero.
# SECTION: at
AT commands to send when different events happen, and where to send them to.
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
- *value*: the command's argument(s), usually used to set the value of a
specific parameter
- *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 expected value
A command can have *expect* OR *value* configured, but it shouldn't have both
*NOTE:* If a command sequence is configured in an override file, the default
commands won't be loaded from the system configuration. The default commands
should be copied into the override file when changing them.
*uart* string
The serial port to use for sending AT commands to the modem.
*configure* List of commands
AT commands to send to the modem when it is first started.
*suspend* List of commands
AT commands to send to the modem before the system suspends.
*resume* List of commands
AT commands to send to the modem after the system resumes from suspend.
*reset* List of commands
AT commands to send to the modem if resetting the usb port fails.
# SECTION: gnss
Settings for uploading AGPS assistance data to the modem.
*enabled* boolean
Enable or disable uploading AGPS data to the modem
*url* string
The directory on the server that contains the assistance files
Example: https://xtrapath4.izatcloud.net
*file* string
The name of the assistance file on the server.
Example: xtra2.bin
# SECTION: gpio
The *gpio* section defines the GPIO pins to use for different modem functions.
These settings should only be changed when porting eg25-manager to a new device;
for this reason they aren't documented here.
# EXAMPLES
Print the firmware version every time the phone wakes from suspend:
```
[at]
resume = [
{ cmd = "QGMR" },
]
```
Disable uploading AGPS data to the modem:
```
[gnss]
enabled = false
```
# SEE AlSO
*eg25-manager*(8)

37
doc/eg25-manager.8.scd Normal file
View File

@@ -0,0 +1,37 @@
eg25-manager(8)
# NAME
eg25-manager - a daemon for managing the Quectel EG25 modem found on the
Pine64 PinePhone.
# SYNOPSIS
*eg25-manager* [-v] [-c config_file]
# OPTIONS
*-v*
Show the version number and quit.
*-c*
User configuration file, defaults to the device configuration file in
/etc/eg25-manager.
# FILES
Configurations are loaded from:
- *@eg25_confdir@/<compatible>.toml*: User-provided overrides (optional)
- *@eg25_datadir@/<compatible>.toml*: Default configuration (required)
eg25-manager will search these folders for files named after the value of the
compatible device-tree property (with the .toml file extension) and use the
first matching file in each directory. If no matching default configuration is
found, eg25-manager will exit with an error message.
Values from the user-provided overrides will take priority over values stored in
the default configuration. Only changed values must be stored as user overrides,
so eg25-manager can fall back to the default configuration as often as possible.
The file names eg25-manager will check can be listed using:
```
xargs -0 printf '%s.toml\\n' < /proc/device-tree/compatible
```
# SEE ALSO
*eg25-manager*(5) *ModemManager*(8) *ofono*(8)

33
doc/meson.build Normal file
View File

@@ -0,0 +1,33 @@
#
# Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
scdoc = dependency('scdoc', native: true, required: false)
if scdoc.found()
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
foreach section: [5, 8]
name = 'eg25-manager'
out = '@0@.@1@'.format(name, section)
preprocessed = configure_file(
input: '@0@.scd'.format(out),
output: '@BASENAME@.preprocessed',
configuration: {
'eg25_confdir': eg25_confdir,
'eg25_datadir': eg25_datadir,
}
)
custom_target(
out,
output: out,
input: preprocessed,
command: ['sh', '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
capture: true,
install: true,
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
endforeach
endif

View File

@@ -8,9 +8,9 @@
project (
'eg25-manager',
'c',
version : '0.4.1',
version : '0.4.3',
license : 'GPLv3+',
meson_version : '>= 0.50.0',
meson_version : '>= 0.58.0',
default_options :
[
'warning_level=1',
@@ -66,5 +66,6 @@ mgr_deps = [
]
subdir('data')
subdir('doc')
subdir('src')
subdir('udev')

View File

@@ -245,7 +245,14 @@ static gboolean modem_response(gint fd,
*/
do {
ret = read(fd, tmp, sizeof(tmp));
if (ret > 0) {
/* If we're going to overflow truncate the data we read to fit */
if (pos + ret >= sizeof(response)) {
g_critical("AT response buffer full, truncating");
ret = sizeof(response) - (pos + 1);
}
memcpy(&response[pos], tmp, ret);
pos += ret;
usleep(10000);

View File

@@ -81,3 +81,17 @@ gboolean config_get_array(toml_table_t **config, const gchar *key, toml_array_t
return !!array;
}
gboolean config_get_table(toml_table_t **config, const gchar *key, toml_table_t **result)
{
toml_table_t *table = NULL;
if (config[EG25_CONFIG_USER])
table = toml_table_in(config[EG25_CONFIG_USER], key);
if (!table)
table = toml_table_in(config[EG25_CONFIG_SYS], key);
if (table && result)
*result = table;
return !!table;
}

View File

@@ -24,3 +24,4 @@ gboolean config_get_int(toml_table_t **config, const gchar *key, gint *result);
gboolean config_get_uint(toml_table_t **config, const gchar *key, guint *result);
gboolean config_get_string(toml_table_t **config, const gchar *key, gchar **result);
gboolean config_get_array(toml_table_t **config, const gchar *key, toml_array_t **result);
gboolean config_get_table(toml_table_t **config, const gchar *key, toml_table_t **result);

View File

@@ -9,9 +9,9 @@
#include <unistd.h>
/* Those defines are used for legacy config files only */
#define GPIO_CHIP1_LABEL "1c20800.pinctrl"
#define GPIO_CHIP2_LABEL "1f02c00.pinctrl"
#define MAX_GPIOCHIP_LINES 352
enum {
@@ -101,10 +101,41 @@ int gpio_sequence_sleep(struct EG25Manager *manager)
return 0;
}
struct gpiod_line *gpio_get_output_line(struct EG25Manager *manager, int chip, int line)
{
struct gpiod_line *gpio_line;
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
if (!gpio_line)
return NULL;
if (gpiod_line_request_output(gpio_line, "eg25manager", 0) < 0) {
gpiod_line_release(gpio_line);
return NULL;
}
return gpio_line;
}
struct gpiod_line *gpio_get_input_line(struct EG25Manager *manager, int chip, int line)
{
struct gpiod_line *gpio_line;
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
if (!gpio_line)
return NULL;
if (gpiod_line_request_input(gpio_line, "eg25manager") < 0) {
gpiod_line_release(gpio_line);
return NULL;
}
return gpio_line;
}
int gpio_init(struct EG25Manager *manager, toml_table_t *config[])
{
int i, ret;
guint offset, chipidx, gpio_idx;
int i;
toml_table_t *gpio_config[EG25_CONFIG_COUNT];
for (i = 0; i < EG25_CONFIG_COUNT; i++)
@@ -113,65 +144,122 @@ int gpio_init(struct EG25Manager *manager, toml_table_t *config[])
if (!gpio_config[EG25_CONFIG_SYS])
g_error("Default config file lacks the 'gpio' section!");
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
if (!manager->gpiochip[0]) {
g_critical("Unable to open GPIO chip " GPIO_CHIP1_LABEL);
return 1;
}
/*
* The system config could have the `chips` key, but the user one
* could still use the old format! In order to avoid problems, we
* should use the new format only if:
* - there's no user config file
or
* - the user config file contains the `chips` key
* Otherwise we might start parsing the system config with the new
* format, but error out if user config overrides gpios using the
* old format
*/
if (!gpio_config[EG25_CONFIG_USER] || toml_array_in(gpio_config[EG25_CONFIG_USER], "chips"))
{
int numchips;
toml_array_t *chipslist = NULL;
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;
}
config_get_array(gpio_config, "chips", &chipslist);
numchips = toml_array_nelem(chipslist);
if (numchips > 2)
g_error("Requesting too many GPIO chips!");
for (i = 0; i < GPIO_OUT_COUNT; i++) {
if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx))
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
if (gpio_idx < MAX_GPIOCHIP_LINES) {
offset = gpio_idx;
chipidx = 0;
} else {
offset = gpio_idx - MAX_GPIOCHIP_LINES;
chipidx = 1;
for (i = 0; i < numchips; i++) {
toml_datum_t data = toml_string_at(chipslist, i);
if (!data.ok)
continue;
manager->gpiochip[i] = gpiod_chip_open_by_label(data.u.s);
if (!manager->gpiochip[i])
g_error("Unable to find GPIO chip '%s'", data.u.s);
}
manager->gpio_out[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
if (!manager->gpio_out[i]) {
g_error("Unable to get output GPIO %d", i);
return 1;
for (i = 0; i < GPIO_OUT_COUNT; i++) {
toml_table_t *table;
toml_datum_t chip, line;
if (!config_get_table(gpio_config, gpio_out_names[i], &table))
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
chip = toml_int_in(table, "chip");
if (!chip.ok || chip.u.i < 0 || chip.u.i > 2)
g_error("Wrong chip ID for output GPIO '%s'", gpio_out_names[i]);
line = toml_int_in(table, "line");
if (!line.ok || line.u.i < 0 || line.u.i > gpiod_chip_num_lines(manager->gpiochip[chip.u.i]))
g_error("Wrong line ID for output GPIO '%s'", gpio_out_names[i]);
manager->gpio_out[i] = gpio_get_output_line(manager, chip.u.i, line.u.i);
if (!manager->gpio_out[i])
g_error("Unable to get output GPIO %d", i);
}
ret = gpiod_line_request_output(manager->gpio_out[i], "eg25manager", 0);
if (ret < 0) {
g_error("Unable to request output GPIO %d", i);
return 1;
for (i = 0; i < GPIO_IN_COUNT; i++) {
toml_table_t *table;
toml_datum_t chip, line;
if (!config_get_table(gpio_config, gpio_in_names[i], &table)) {
// BH edition don't have the STATUS line connected, ignore it
if (manager->use_libusb && g_strcmp0(gpio_in_names[i], "status") == 0)
continue;
g_error("Unable to get config for input GPIO '%s'", gpio_in_names[i]);
}
chip = toml_int_in(table, "chip");
if (!chip.ok || chip.u.i < 0 || chip.u.i > 2)
g_error("Wrong chip ID for input GPIO '%s'", gpio_in_names[i]);
line = toml_int_in(table, "line");
if (!line.ok || line.u.i < 0 || line.u.i > gpiod_chip_num_lines(manager->gpiochip[chip.u.i]))
g_error("Wrong line ID for input GPIO '%s'", gpio_in_names[i]);
manager->gpio_in[i] = gpio_get_input_line(manager, chip.u.i, line.u.i);
if (!manager->gpio_in[i])
g_error("Unable to get input GPIO %d", i);
}
}
} else {
guint offset, chipidx, gpio_idx;
for (i = 0; i < GPIO_IN_COUNT; i++) {
if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx))
continue;
/* Legacy config file, only used on the OG PinePhone */
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
if (!manager->gpiochip[0])
g_error("Unable to open GPIO chip " GPIO_CHIP1_LABEL);
if (gpio_idx < MAX_GPIOCHIP_LINES) {
offset = gpio_idx;
chipidx = 0;
} else {
offset = gpio_idx - MAX_GPIOCHIP_LINES;
chipidx = 1;
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
if (!manager->gpiochip[1])
g_error("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
for (i = 0; i < GPIO_OUT_COUNT; i++) {
if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx))
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
if (gpio_idx < MAX_GPIOCHIP_LINES) {
offset = gpio_idx;
chipidx = 0;
} else {
offset = gpio_idx - MAX_GPIOCHIP_LINES;
chipidx = 1;
}
manager->gpio_out[i] = gpio_get_input_line(manager, chipidx, offset);
if (!manager->gpio_out[i])
g_error("Unable to get output GPIO %d", i);
}
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;
}
for (i = 0; i < GPIO_IN_COUNT; i++) {
if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx))
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;
if (gpio_idx < MAX_GPIOCHIP_LINES) {
offset = gpio_idx;
chipidx = 0;
} else {
offset = gpio_idx - MAX_GPIOCHIP_LINES;
chipidx = 1;
}
manager->gpio_in[i] = gpio_get_input_line(manager, chipidx, offset);
if (!manager->gpio_in[i])
g_error("Unable to get input GPIO %d", i);
}
}

View File

@@ -38,6 +38,9 @@
#define EG25_VERSION "0.0.0"
#endif
#define EG25_DEFAULT_VENDOR_ID 0x2c7c
#define EG25_DEFAULT_PRODUCT_ID 0x0125
#define POWERON_DELAY_US 100000UL
static gboolean quit_app(struct EG25Manager *manager)
@@ -141,7 +144,7 @@ void modem_configure(struct EG25Manager *manager)
static gboolean modem_reset_done(struct EG25Manager* manager)
{
manager->modem_state = EG25_STATE_RESUMING;
manager->reset_timer = 0;
manager->complete_reset_timer = 0;
return FALSE;
}
@@ -149,7 +152,18 @@ gboolean modem_reset(struct EG25Manager *manager)
{
int fd, ret, len;
if (manager->reset_timer) {
/* reset sequence started, cannot be canceled anymore */
if (manager->schedule_reset_timer) {
g_source_remove(manager->schedule_reset_timer);
manager->schedule_reset_timer = 0;
}
if (manager->modem_recovery_timer) {
g_source_remove(manager->modem_recovery_timer);
manager->modem_recovery_timer = 0;
}
if (manager->complete_reset_timer) {
g_message("modem_reset: timer already setup, skipping...");
return G_SOURCE_REMOVE;
}
@@ -164,11 +178,6 @@ gboolean modem_reset(struct EG25Manager *manager)
return G_SOURCE_REMOVE;
}
if (manager->modem_recovery_timer) {
g_source_remove(manager->modem_recovery_timer);
manager->modem_recovery_timer = 0;
}
if (!manager->modem_usb_id) {
g_warning("Empty modem USB ID");
goto error;
@@ -210,7 +219,7 @@ gboolean modem_reset(struct EG25Manager *manager)
* 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);
manager->complete_reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset_done), manager);
return G_SOURCE_REMOVE;
@@ -228,7 +237,7 @@ error:
at_sequence_reset(manager);
// Setup timer for making sure we don't queue other reset commands
manager->reset_timer = g_timeout_add_seconds(30, G_SOURCE_FUNC(modem_reset_done), manager);
manager->complete_reset_timer = g_timeout_add_seconds(30, G_SOURCE_FUNC(modem_reset_done), manager);
return G_SOURCE_REMOVE;
}
@@ -314,6 +323,7 @@ int main(int argc, char *argv[])
struct EG25Manager manager;
gchar *config_file = NULL;
gboolean show_version = FALSE;
gboolean monitor_udev = TRUE;
toml_table_t *toml_config[EG25_CONFIG_COUNT];
toml_table_t *manager_config[EG25_CONFIG_COUNT];
const GOptionEntry options[] = {
@@ -363,10 +373,13 @@ int main(int argc, char *argv[])
if (!manager_config[EG25_CONFIG_SYS])
g_error("Default config file lacks the 'manager' section!");
config_get_bool(manager_config, "monitor_udev", &monitor_udev);
config_get_bool(manager_config, "need_libusb", &manager.use_libusb);
config_get_uint(manager_config, "usb_vid", &manager.usb_vid);
config_get_uint(manager_config, "usb_pid", &manager.usb_pid);
config_get_uint(manager_config, "poweron_delay", &manager.poweron_delay);
if (!config_get_uint(manager_config, "usb_vid", &manager.usb_vid))
manager.usb_vid = EG25_DEFAULT_VENDOR_ID;
if (!config_get_uint(manager_config, "usb_pid", &manager.usb_pid))
manager.usb_pid = EG25_DEFAULT_PRODUCT_ID;
at_init(&manager, toml_config);
gpio_init(&manager, toml_config);
@@ -375,7 +388,8 @@ int main(int argc, char *argv[])
#endif
ofono_iface_init(&manager, toml_config);
suspend_init(&manager, toml_config);
udev_init(&manager, toml_config);
if (monitor_udev)
udev_init(&manager, toml_config);
gnss_init(&manager, toml_config);
for (int i = 0; i < EG25_CONFIG_COUNT; i++) {

View File

@@ -53,6 +53,7 @@ enum EG25State {
EG25_STATE_SUSPENDING, // System is going into suspend
EG25_STATE_RESUMING, // System is being resumed, waiting for modem to come back
EG25_STATE_RESETTING, // Something went wrong, we're restarting the modem
EG25_STATE_UPDATING, // Modem is present but being updated
EG25_STATE_FINISHING
};
@@ -70,7 +71,8 @@ enum EG25Config {
struct EG25Manager {
GMainLoop *loop;
guint reset_timer;
guint complete_reset_timer;
guint schedule_reset_timer;
gboolean use_libusb;
guint usb_vid;
guint usb_pid;

View File

@@ -25,7 +25,7 @@ if mmglib_dep.found()
endif
executable (
'eg25manager',
'eg25-manager',
src,
dependencies : mgr_deps,
link_with: gdbofono_lib,

View File

@@ -18,10 +18,24 @@
#define SD_PATH "/org/freedesktop/login1"
#define SD_INTERFACE "org.freedesktop.login1.Manager"
static void resume_ok(struct EG25Manager *manager)
{
manager->modem_state = EG25_STATE_CONFIGURED;
modem_resume_post(manager);
}
static gboolean check_modem_resume(struct EG25Manager *manager)
{
g_message("Modem wasn't probed in time, restart it!");
manager->modem_recovery_timer = 0;
#ifdef HAVE_MMGLIB
if (manager->mm_modem) {
resume_ok(manager);
return FALSE;
}
#endif
g_message("Modem wasn't probed in time, restart it!");
modem_reset(manager);
return FALSE;
@@ -184,8 +198,7 @@ static void signal_cb(GDBusProxy *proxy,
* If modem is managed by ofono, we also do resume sequence immediately
* as ofono handles resuming from sleep itself.
*/
manager->modem_state = EG25_STATE_CONFIGURED;
modem_resume_post(manager);
resume_ok(manager);
} else {
manager->modem_state = EG25_STATE_RESUMING;
manager->modem_recovery_timer = g_timeout_add_seconds(manager->modem_recovery_timeout,

View File

@@ -11,18 +11,47 @@
static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data)
{
struct EG25Manager *manager = data;
const gchar *prop;
long vid = 0, pid = 0;
/*
* Act only if the device is the one identified as a modem by MM/ofono
*/
if (!manager->modem_usb_id ||
strcmp(g_udev_device_get_name(device), manager->modem_usb_id) != 0) {
return;
}
prop = g_udev_device_get_property(device, "ID_VENDOR_ID");
if (prop)
vid = strtol(prop, NULL, 16);
prop = g_udev_device_get_property(device, "ID_MODEL_ID");
if (prop)
pid = strtol(prop, NULL, 16);
if (strcmp(action, "bind") == 0 && vid != manager->usb_vid && pid != manager->usb_pid) {
/*
* Modem is probably executing a FW upgrade, make sure we don't interrupt it
*/
if (manager->schedule_reset_timer != 0) {
g_message("Modem re-appeared with different VID/PID, cancel reset.");
g_source_remove(manager->schedule_reset_timer);
manager->schedule_reset_timer = 0;
}
manager->modem_state = EG25_STATE_UPDATING;
}
if (strcmp(action, "unbind") != 0 ||
manager->modem_state == EG25_STATE_UPDATING ||
manager->modem_state == EG25_STATE_RESETTING ||
!manager->modem_usb_id) {
manager->complete_reset_timer != 0 ||
manager->schedule_reset_timer != 0) {
return;
}
if (strcmp(g_udev_device_get_name(device), manager->modem_usb_id) == 0 &&
manager->reset_timer == 0) {
g_message("Lost modem, resetting...");
g_timeout_add_seconds(2, G_SOURCE_FUNC(modem_reset), manager);
}
g_message("Lost modem, resetting...");
manager->schedule_reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset), manager);
}
void udev_init (struct EG25Manager *manager, toml_table_t *config[])