121 Commits

Author SHA1 Message Date
Arnaud Ferraris
c9d89e1736 d/changelog: release version 0.4.3-1 2022-02-19 15:37:12 +01:00
Arnaud Ferraris
a66852d96d d/watch: fix watch file
Our `watch` file was looking at the wrong URL, fix that.
2022-02-19 15:36:49 +01:00
Arnaud Ferraris
8c9941eef6 debian: small cleanups
`salsa-ci.yml` is no longer needed as we now have our own CI pipelines.
Also delete unneeded newlines from `watch` file.
2022-02-19 15:22:40 +01:00
Arnaud Ferraris
d9778e6ecd d/control: build-depend on scdoc
This is required for building the manpages. Also bump Standards-Version,
no other changes needed.
2022-02-19 15:19:13 +01:00
Arnaud Ferraris
7b46f084d9 New upstream version 0.4.3
eg25-manager v0.4.3

IMPORTANT: this release renames the executable to `eg25-manager` (added
dash). Make sure existing scripts are updated to reflect this change.

Changes:
* improve udev monitoring:
  - filter based on vendor/product ID
  - disable udev monitoring for the PinePhone Pro
* add manpages for `eg25-manager` and its configuration files
  - this adds an optional build dependency on `scdoc`
* Fix build when not using ModemManager
* Fix GPIO module initialization for BraveHeart Edition PinePhones
* Various build system improvements (now requires meson >= 0.58.0)
2022-02-19 15:18:21 +01:00
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
9c05776843 d/changelog: release version 0.4.2-1 2021-12-08 18:27:34 +01:00
Arnaud Ferraris
08a7039ca0 Update upstream source from tag '0.4.2'
Update to upstream version '0.4.2'
with Debian dir ad2c13f51c
2021-12-08 18:23:57 +01:00
Arnaud Ferraris
929b3942c0 d/gbp.conf: update for current Mobian workflow 2021-12-08 18:23:44 +01: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
Arnaud Ferraris
f8430eb16f d/changelog: release version 0.4.1-1 2021-10-08 11:03:27 +02:00
Arnaud Ferraris
1b69252cbe d/copyright: add missing entries 2021-10-08 11:02:21 +02:00
Arnaud Ferraris
c17f947249 debian: drop distro-specific systemd service
It is now provided by upstream
2021-10-08 11:02:05 +02:00
Arnaud Ferraris
e96aec8390 New upstream version 0.4.1 2021-10-08 10:56:18 +02:00
Arnaud Ferraris
1f8fa88d37 meson.build: release version 0.4.1 2021-10-07 11:11:30 +02:00
Arnaud Ferraris
deeb60fa6a gnss: warn if ftruncate() fails 2021-10-07 11:11:30 +02:00
Arnaud Ferraris
a3d27cb3f7 data: install systemd system service 2021-10-06 22:41:43 +02:00
Arnaud Ferraris
af4d5ca1c1 udev: don't reset immediately
Executing a reset each time the modem is unbound is a bit too extreme:
the modem sometimes recovers by itself and only needs a "soft" reset
sequence (unbind/bind).

This commit introduces a short timer (2s) so we the modem can settle in.
If reset fails after this time, the modem is probably completely broken,
or already rebooting, so we can safely issue a reset AT command.
2021-10-06 22:35:36 +02:00
Arnaud Ferraris
25dd46bb30 manager: make modem_reset more reliable
`modem_reset()` could previously either fail silently, or fall back to
using AT commands without indicating what happened. This commit adds
informative messages and makes sure we fall back to resetting using AT
commands whenever we encounter an error.
2021-10-06 22:25:39 +02:00
Arnaud Ferraris
86978e18a6 at: fix more typos 2021-10-06 02:17:21 +02:00
Arnaud Ferraris
bcdc839abb gnss: don't overload the modem during upload
By instructing sendfile() to send the whole content of the assistance
data at once results in a big data transfer (> 4kb) first followed by a
huge amount of small transfers (64 or 128b). This "overloads" the modem
which needs to process smaller chunks of data and have more time to do
so (experimentation shows that 1kb need 100ms for processing, or the
upload will subsequently fail).

This commit sets the transfer size to 256b and increase the timeout
between each transfer to 25ms.
2021-10-06 01:54:30 +02:00
Arnaud Ferraris
73868260a2 gnss: wait for the modem to confirm upload success
If we clear QFUPL right after we finished the file transfer, we will
execute the following commands right away, leading to interleaved
replies from the modem (i.e. the reply to QGPSXTRATIME being received
with the notification of upload completion).

Keeping the command in the queue allows us to use the callback a second
time once the upload is complete and resume assistance data processing
accordingly.
2021-10-06 01:54:30 +02:00
Arnaud Ferraris
66073cdd21 at: get rid of compiler warning 2021-10-06 01:54:30 +02:00
Arnaud Ferraris
cfd7ebf156 at: ensure we don't skip steps
After the modem send "RDY", other messages are received. With the
current implementation, this causes eg25-manager to mark the modem as
configured before it is even picked up by ModemManager. Adding an
additional status check helps preventing this issue.
2021-10-06 01:54:30 +02:00
Arnaud Ferraris
f03f086fcb src: fix typo 2021-10-05 23:45:33 +02:00
Arnaud Ferraris
f68af6405d manager: honor --version option
Fixes #22
2021-10-05 23:45:27 +02:00
Arnaud Ferraris
68349c9f58 Merge branch 'fix-config-crash' into 'master'
Don't crash on incomplete config files

Closes #23

See merge request mobian1/devices/eg25-manager!31
2021-10-05 20:34:28 +00:00
Arnaud Ferraris
3d9456c0e7 Merge branch 'wip/a-wai/clear-command-queue' into 'master'
at: clear command queue before configuring modem

Closes #24

See merge request mobian1/devices/eg25-manager!34
2021-10-05 20:34:12 +00:00
Arnaud Ferraris
87a7a6c9e9 Merge branch 'at_logging' into 'master'
Fix logging of at commands (was preventing sending them)

See merge request mobian1/devices/eg25-manager!36
2021-10-05 20:33:55 +00:00
ArenM
aec8135ad4 at: escape non text characters in modem response logs 2021-10-05 12:09:12 -04:00
Arnaud Ferraris
fcf3832f52 gpio: don't execute soft wake sequence if already woken up
This add an unnecessary 200ms delay and pollute logs.
2021-10-05 11:23:06 +02:00
Arnaud Ferraris
4c6625a38d src: don't crash on incomplete user-edited config file
If a user created a custom config file and we added new fields in a
newer version, `eg25-manager` will crash by assuming all config files
are complete and up-to-date.

Moreover, creating a custom config, even to change only one option,
required copying a complete default config file and then editing the
relevant field(s). This could lead to issues when upgrading, as default
values of fields unrelated to the user change could be modified and not
applied to the user config.

This patch addresses both these issues by:
* making sure at least one config file exists
* requiring only the "default" config file (the one under `/usr/share`)
  to include all the required fields
* trying to use user config for each field, falling back to the default
  config file if the field isn't present in the user config
* exiting with a meaningful error message in case the default config
  file is missing a required field or section

That way, it will be possible to have a minimal user config file
containing only the field(s) needing a different value than the default
one, falling back to the values in the default config file.

Fixes #23
2021-10-05 11:19:35 +02:00
Arnaud Ferraris
55ed2dc39c src: add helper functions for reading config files 2021-10-05 11:19:35 +02:00
ArenM
89b7dfda2f at: remove call to g_strstrip before sending commando
This log statement called g_strstrip before sending the command, which
caused it to fail because it doesn't send the newline required to run
it.
2021-10-04 23:26:16 -04:00
Arnaud Ferraris
0f3e51cd06 at: clear command queue before configuring modem
In some cases, there might be an issue with `eg25-manager` trying to
reset the modem while it's being configured. This leads to infinite boot
loops where the modem is constantly reset soon after it booted.

Fixes #24
2021-10-04 18:20:46 +02:00
Arnaud Ferraris
8ae79fa34c Merge branch 'at-write-repeat' into 'master'
at_send_command: call write(2) in a loop

See merge request mobian1/devices/eg25-manager!30
2021-10-02 12:14:44 +00:00
Xavier Del Campo
6b2f0e8fbd at.c: fix misleading g_message 2021-10-02 13:59:22 +02:00
Arnaud Ferraris
082cf996d1 Use a more meaningful message on AT send failure 2021-10-02 13:58:32 +02:00
Xavier Del Campo
5102902692 at_send_command: call write(2) in a loop
write(2) might return less than expected due to various reasons.
Therefore, unless we are dealing with a critical error, it must be
called in a loop until all bytes are written.
2021-10-02 13:58:29 +02:00
Arnaud Ferraris
5c61d41090 Merge branch 'gnss_upload_doublefree' into 'master'
gnss: fix double free introduced by !29

See merge request mobian1/devices/eg25-manager!33
2021-09-30 23:19:57 +00:00
ArenM
1a65947176 gnss: fix double free introduced by !29
!29 changed the url varible in fetch_assistance_data to use g_autofree,
but didn't remove all calls to free it.
2021-09-30 14:23:21 -04:00
Arnaud Ferraris
3d29617977 Merge branch 'gnss_cleanup' into 'master'
Fix various small issues in the gnss assistance data upload code

Closes #20

See merge request mobian1/devices/eg25-manager!29
2021-09-29 21:05:08 +00:00
ArenM
36ac57b627 gnss: Gracefully handle failure to access xtra data file 2021-09-29 15:24:33 -04:00
ArenM
ee10cafa00 gnss: flush tmp_file after downloading gpsOneXtra data 2021-09-29 14:37:23 -04:00
ArenM
2fcb5852ae gnss: better error handling and messages when fetching data
This will print the error message from curl instead of just the http
status code if downloading gpsOneXtra data fails. It also adds checks
for other errors that are likely to occur.
2021-09-29 14:37:23 -04:00
Arnaud Ferraris
86372093d7 Merge branch 'gnss-disable-after-fetch' into 'master'
gnss: disable GPS only after fetching assistance data

Closes #21

See merge request mobian1/devices/eg25-manager!32
2021-09-29 07:33:34 +00:00
Arnaud Ferraris
34ec02cd34 gnss: disable GPS only after fetching assistance data
Fixes #21
2021-09-29 01:31:54 +02:00
Arnaud Ferraris
3f4dd9ac79 Merge branch 'snprintf' into 'master'
at.c: use snprintf(3)

See merge request mobian1/devices/eg25-manager!28
2021-09-28 21:31:53 +00:00
Xavier Del Campo
6f91389496 at_send_command: improve logging when snprintf(3) fails 2021-09-28 23:25:19 +02:00
Xavier Del Campo
771e9f8316 at_send_command: call at_next_command on failure 2021-09-28 23:17:25 +02:00
Xavier Del Campo
9e0d97d2e2 at.c: use snprintf(3)
snprintf(3) is preferred over insecure sprintf(3) in order to avoid
buffer overrun vulnerabilities.
2021-09-28 22:53:54 +02:00
ArenM
593db8aa67 gnss: include error messages directly from curl
This will print the error message from curl instead of just the http
status code if downloading gpsOneXtra data fails. This also removes the
need to check to check the size of the file curl downloaded.
2021-09-28 16:48:11 -04:00
ArenM
ad1d6e5d3e gnss: increase upload timeout to 10 seconds
The timeout for QFUPL defaults to 5 seconds which is about how long it
takes to upload data under ideal circumstances.

I'm not sure if this will actually have an effect, the docs say
"<timeout> The time waiting for data to be inputted to USB/UART. The
default value is 5. Unit: s." which could be the time before the first
byte is received.
2021-09-28 16:48:11 -04:00
Arnaud Ferraris
2bf63376d7 Merge branch 'conditional-modemmanager' into 'master'
gnss: Support using ofono when compiled with HAVE_MMGLIB

Closes #16

See merge request mobian1/devices/eg25-manager!26
2021-09-27 18:01:26 +00:00
ArenM
6177c7167c gnss: use sendfile to upload xtra data
This should make the data upload much faster because it handles
incomplete writes better, and because it the kernel copies the data
between the files directly, and it doesn't get sent to userspace and
back.
2021-09-27 13:57:57 -04:00
ArenM
750c41cbb5 gnss: rearrange enable_mm_gnss so it doesn't noop 2021-09-27 13:57:57 -04:00
Arnaud Ferraris
0d2b0e326a Merge branch 'gnss-upload-failure' into 'master'
gnss: properly clean temporary file before downloading assistance data

Closes #17

See merge request mobian1/devices/eg25-manager!27
2021-09-27 17:51:22 +00:00
ArenM
f7c655c297 gnss: Support using ofono when compiled with HAVE_MMGLIB 2021-09-12 17:56:45 -04:00
Dylan Van Assche
41511cbc5f gnss: truncate temporary file before download
A temporary file is used to download the GNSS assistance data.
It's created and truncated at initialization, but not truncated
when a re-upload is necessary (when data expires).
This causes to corrupt the GNSS assistance data if the new downloaded
data is smaller than the previous download.
2021-09-07 09:02:48 +02:00
Dylan Van Assche
8913300997 gnss: open temporary file as read/write
Opening as write-only may cause undefined behavior when reading the file
2021-09-07 07:28:34 +02:00
Dylan Van Assche
7b96296938 at: fix indentation 2021-09-03 21:33:05 +02:00
Arnaud Ferraris
e4ae8d6382 d/changelog: release version 0.4.0-1 2021-09-01 00:46:28 +02:00
Arnaud Ferraris
da8a008268 d/control: add libcurl as build dependency 2021-09-01 00:43:54 +02:00
Arnaud Ferraris
30ce2bb3e2 New upstream version 0.4.0 2021-09-01 00:42:37 +02:00
Arnaud Ferraris
162fcf6fca d/eg25-manager.service: be less restrictive
Additional security options in kernel config make it more picky, 
removing problematic directives (`DeviceAllow` and `ProtectClock`) from 
the service file helps getting things straight.

Other options aren't recognized by our systemd version 
(`ProtectKernelModules`, `ProtectProc`, `ProtectDevices` and 
`ProtectKernelLog`), so we can just as well remove those.
2021-09-01 00:26:21 +02:00
undef
24dbcf464c d/salsa-ci: Add Mobian's CI 2021-07-29 08:45:11 +00:00
Arnaud Ferraris
b62b155875 Merge branch 'debian/latest' into 'debian/latest'
d/service: Use systemd to sandbox eg25-manager

See merge request mobian1/devices/eg25-manager!16
2021-07-26 23:34:32 +00:00
undef
67195a8e58 d/service: Use systemd to sandbox eg25-manager
With eg25-manager directly interfacing with the untrusted modem and
potentially (MR !15) including libcurl for HTTP, sandboxing the daemon
significantly reduces the any post-exploit attack surface.
2021-07-26 23:24:08 +00:00
Arnaud Ferraris
c7e8d9171c d/changelog: release version 0.3.0-1 2021-05-28 13:59:53 +02:00
Arnaud Ferraris
5db68722ec New upstream version 0.3.0 2021-05-28 13:58:11 +02:00
Arnaud Ferraris
432bd454bb d/changelog: release version 0.2.1-1 2021-02-21 16:42:15 +01:00
Arnaud Ferraris
986e7f08c4 New upstream version 0.2.1 2021-02-21 16:41:22 +01:00
Arnaud Ferraris
4089f2ea6b d/changelog: release version 0.2.0-1 2021-02-20 22:27:18 +01:00
Arnaud Ferraris
c77c58df49 d/eg25-manager.service: remove deprecated -g option 2021-02-20 22:26:13 +01:00
Arnaud Ferraris
5cc5ff5c0e d/gbp.conf: enable multimaint-merge 2021-02-20 17:21:37 +01:00
Arnaud Ferraris
6a81955086 New upstream version 0.2.0 2021-02-20 17:18:46 +01:00
Arnaud Ferraris
af3a2b25bc d/changelog: release version 0.1.2-1 2021-01-14 00:10:04 +01:00
Arnaud Ferraris
70db05fc62 d/eg25-manager.service: enable GNSS management 2021-01-14 00:09:17 +01:00
Arnaud Ferraris
705a454882 New upstream release 0.1.2 2021-01-14 00:08:27 +01:00
Arnaud Ferraris
2469757af4 d/changelog: release version 0.1.1-1 2020-12-18 01:42:39 +01:00
Arnaud Ferraris
514b00cc9c d/control: build-depend on gudev 2020-12-18 01:42:01 +01:00
Arnaud Ferraris
e078b8bc09 New upstream release 0.1.1 2020-12-18 01:41:39 +01:00
Arnaud Ferraris
d976c75fa2 d/changelog: release version 0.1.0-1 2020-12-14 16:46:02 +01:00
Arnaud Ferraris
f86d0ef062 New upstream release 0.1.0 2020-12-14 16:44:51 +01:00
Arnaud Ferraris
276c71f223 d/changelog: release version 0.0.6-1 2020-12-11 15:12:08 +01:00
Arnaud Ferraris
1423021f97 d/patches: drop upstreamed patches 2020-12-11 15:11:22 +01:00
Arnaud Ferraris
fc915f570d New upstream release 0.0.6 2020-12-11 15:10:40 +01:00
Arnaud Ferraris
c77490a2ac d/changelog: release version 0.0.5-2 2020-12-11 14:33:40 +01:00
Arnaud Ferraris
fbd5c0cb86 d/patches: fix crash on modem recovery 2020-12-11 14:33:09 +01:00
Arnaud Ferraris
5bcefbeab0 d/changelog: release version 0.0.5-1 2020-12-11 13:39:22 +01:00
Arnaud Ferraris
f85e8f70c7 New upstream release 0.0.5 2020-12-11 13:38:19 +01:00
Arnaud Ferraris
2da2c9dfe2 d/changelog: release version 0.0.4-1 2020-12-11 12:52:27 +01:00
Arnaud Ferraris
dfaac39162 d/control: build-depend on libusb-1.0 2020-12-11 12:51:36 +01:00
Arnaud Ferraris
7dc0d1678c New upstream release 0.0.4 2020-12-11 12:50:26 +01:00
Arnaud Ferraris
397a16a9e3 d/service: restart daemon on failure 2020-12-11 10:49:02 +01:00
Arnaud Ferraris
59219fbd20 d/control: build only on arm64
This package is PinePhone-specific, no need to build on other 
architectures.
2020-12-10 21:42:49 +01:00
Arnaud Ferraris
c953d41436 d/changelog: release version 0.0.3-1 2020-12-10 21:35:11 +01:00
Arnaud Ferraris
82bf80c5f4 New upstream release 0.0.3 2020-12-10 21:33:58 +01:00
Arnaud Ferraris
339faa46dc d/changelog: release version 0.0.2-1 2020-12-10 19:51:42 +01:00
Arnaud Ferraris
cdf92755cb New upstream release 0.0.2 2020-12-10 19:50:32 +01:00
Arnaud Ferraris
7c04c1998f Initial Debian packaging 2020-12-10 15:19:25 +01:00
36 changed files with 1341 additions and 378 deletions

View File

@@ -0,0 +1,21 @@
[Unit]
Description=Quectel EG25 modem
Before=ModemManager.service
[Service]
Type=simple
ExecStart=@bindir@/eg25-manager
Restart=on-failure
ProtectControlGroups=true
ProtectHome=true
ProtectSystem=strict
RestrictSUIDSGID=true
PrivateTmp=true
MemoryDenyWriteExecute=true
PrivateMounts=true
NoNewPrivileges=true
CapabilityBoundingSet=
LockPersonality=true
[Install]
WantedBy=multi-user.target

View File

@@ -8,6 +8,17 @@ conf_files = [
'pine64,pinephone-1.0.toml', 'pine64,pinephone-1.0.toml',
'pine64,pinephone-1.1.toml', 'pine64,pinephone-1.1.toml',
'pine64,pinephone-1.2.toml', 'pine64,pinephone-1.2.toml',
'pine64,pinephone-pro.toml',
] ]
install_data(conf_files) install_data(conf_files)
serviceconf = configuration_data()
serviceconf.set('bindir', bindir)
configure_file(
input: 'eg25-manager.service.in',
output: 'eg25-manager.service',
install_dir: systemdsystemdir,
configuration: serviceconf,
install: true
)

View File

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

View File

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

View File

@@ -1,4 +1,7 @@
[manager] [manager]
monitor_udev = true
usb_vid = 0x2c7c
usb_pid = 0x0125
# Delay between setting GPIO and PWRKEY sequence, set in microseconds # Delay between setting GPIO and PWRKEY sequence, set in microseconds
poweron_delay = 100000 poweron_delay = 100000
@@ -9,12 +12,13 @@ poweron_delay = 100000
#recovery_timeout = 9 #recovery_timeout = 9
[gpio] [gpio]
dtr = 34 chips = [ "1c20800.pinctrl" ]
pwrkey = 35 dtr = { chip = 0, line = 34 }
reset = 68 pwrkey = { chip = 0, line = 35 }
apready = 231 reset = { chip = 0, line = 68 }
disable = 232 apready = { chip = 0, line = 231 }
status = 233 disable = { chip = 0, line = 232 }
status = { chip = 0, line = 233 }
[at] [at]
uart = "/dev/ttyS2" uart = "/dev/ttyS2"
@@ -59,9 +63,9 @@ configure = [
{ cmd = "QSCLK", value = "1" }, { cmd = "QSCLK", value = "1" },
# GNSS configuration: # GNSS configuration:
# * Enable A-GPS data upload support (XTRA) # * 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. # 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. # when they are not in use.
# * Only enable GPS and GLONASS, disable other GNSS systems. # * Only enable GPS and GLONASS, disable other GNSS systems.
# A-GPS data upload doesn't work for Galileo anyway. # 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"

122
debian/changelog vendored Normal file
View File

@@ -0,0 +1,122 @@
eg25-manager (0.4.3-1) unstable; urgency=medium
* New upstream version 0.4.3
* d/control: build-depend on `scdoc`
This is required for building the manpages. Also bump Standards-Version,
no other changes needed.
* d/watch: fix watch file
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Sat, 19 Feb 2022 15:20:14 +0100
eg25-manager (0.4.2-1) unstable; urgency=medium
* d/gbp.conf: update for current Mobian workflow
* New upstream version 0.4.2
-- Arnaud Ferraris <arnaud.ferraris@collabora.com> Wed, 08 Dec 2021 18:24:38 +0100
eg25-manager (0.4.1-1) unstable; urgency=medium
* New upstream version 0.4.1
* debian: drop distro-specific systemd service.
* d/copyright: add missing entries
-- Arnaud Ferraris <arnaud.ferraris@collabora.com> Fri, 08 Oct 2021 11:02:27 +0200
eg25-manager (0.4.0-1) unstable; urgency=medium
[ undef ]
* d/service: Use systemd to sandbox eg25-manager.
* d/salsa-ci: Add Mobian's CI
[ Arnaud Ferraris ]
* New upstream version 0.4.0
* d/eg25-manager.service: be less restrictive.
* d/control: add libcurl as build dependency
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Wed, 01 Sep 2021 00:44:04 +0200
eg25-manager (0.3.0-1) unstable; urgency=medium
* New upstream version 0.3.0
-- Arnaud Ferraris <arnaud.ferraris@collabora.com> Fri, 28 May 2021 13:58:33 +0200
eg25-manager (0.2.1-1) unstable; urgency=medium
* New upstream version 0.2.1
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Sun, 21 Feb 2021 16:41:31 +0100
eg25-manager (0.2.0-1) unstable; urgency=medium
* New upstream version 0.2.0
* d/gbp.conf: enable multimaint-merge
* d/eg25-manager.service: remove deprecated -g option
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Sat, 20 Feb 2021 22:26:19 +0100
eg25-manager (0.1.2-1) unstable; urgency=medium
* New upstream release 0.1.2
* d/eg25-manager.service: enable GNSS management
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 14 Jan 2021 00:09:23 +0100
eg25-manager (0.1.1-1) unstable; urgency=medium
* d/control: build-depend on gudev
* New upstream release 0.1.1
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 18 Dec 2020 01:42:06 +0100
eg25-manager (0.1.0-1) unstable; urgency=medium
* New upstream release 0.1.0
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Mon, 14 Dec 2020 16:45:20 +0100
eg25-manager (0.0.6-1) unstable; urgency=medium
* New upstream release 0.0.6
* d/patches: drop upstreamed patches
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 15:11:26 +0100
eg25-manager (0.0.5-2) unstable; urgency=medium
* d/patches: fix crash on modem recovery
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 14:33:14 +0100
eg25-manager (0.0.5-1) unstable; urgency=medium
* New upstream release 0.0.5
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 13:38:41 +0100
eg25-manager (0.0.4-1) unstable; urgency=medium
* d/control: build only on arm64.
* d/service: restart daemon on failure
* d/control: build-depend on libusb-1.0
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 12:51:41 +0100
eg25-manager (0.0.3-1) unstable; urgency=medium
* New upstream release 0.0.3
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 10 Dec 2020 21:34:26 +0100
eg25-manager (0.0.2-1) unstable; urgency=medium
* New upstream release 0.0.2
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 10 Dec 2020 19:50:50 +0100
eg25-manager (0.0.1-1) unstable; urgency=medium
* Initial Debian packaging
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 10 Dec 2020 15:19:15 +0100

29
debian/control vendored Normal file
View File

@@ -0,0 +1,29 @@
Source: eg25-manager
Section: libs
Priority: optional
Maintainer: Arnaud Ferraris <arnaud.ferraris@gmail.com>
Standards-Version: 4.6.0
Rules-Requires-Root: no
Build-Depends: debhelper-compat (= 13),
libcurl-dev,
libglib2.0-dev,
libgpiod-dev,
libgudev-1.0-dev,
libmm-glib-dev,
libusb-1.0-0-dev,
meson,
scdoc <!nodoc>,
Homepage: https://gitlab.com/mobian1/devices/eg25-manager
Vcs-Git: https://gitlab.com/mobian1/devices/eg25-manager.git
Vcs-Browser: https://gitlab.com/mobian1/devices/eg25-manager
Package: eg25-manager
Architecture: arm64
Depends: ${misc:Depends},
${shlibs:Depends},
modemmanager,
Conflicts: pinephone-modem-scripts,
Replaces: pinephone-modem-scripts,
Description: Manager daemon for the Quectel EG25 mobile broadband modem
A set of scripts for the PinePhone modem, allowing to power on/off the modem,
and configure its audio interface to work properly with the A64 audio codec.

67
debian/copyright vendored Normal file
View File

@@ -0,0 +1,67 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: eg25-manager
Upstream-Contact: Arnaud Ferraris <arnaud.ferraris@gmail.com>
Source: https://gitlab.com/mobian1/devices/eg25-manager
Files: *
Copyright: 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
License: GPL-3.0-or-later
Files: src/gnss.*
Copyright: 2021 Dylan Van Assche <me@dylanvanassche.be>
License: GPL-3.0-or-later
Files: src/mm-iface.c
Copyright: 2019 Purism SPC
2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
License: GPL-3.0-or-later
Files: src/ofono-iface.c
Copyright: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
2021 Bhushan Shah <bshah@kde.org>
License: GPL-3.0-or-later
Files: src/suspend.c
Copyright: 2012 Red Hat, Inc
2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
License: GPL-3.0-or-later
Files: src/toml.*
Copyright: 2017-2019 CK Tan
License: MIT
License: GPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the full text of the GNU General Public License
version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.
License: MIT
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.

10
debian/gbp.conf vendored Normal file
View File

@@ -0,0 +1,10 @@
[DEFAULT]
debian-branch = mobian
debian-tag = mobian/%(version)s
upstream-branch = upstream/latest
upstream-tag = %(version)s
pristine-tar = True
multimaint-merge = True
[tag]
sign-tags = True

6
debian/rules vendored Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/make -f
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
%:
dh $@

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (quilt)

3
debian/watch vendored Normal file
View File

@@ -0,0 +1,3 @@
version=4
https://gitlab.com/mobian1/devices/@PACKAGE@/-/tags?sort=updated_desc \
.*/archive/\d\S+/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@

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 ( project (
'eg25-manager', 'eg25-manager',
'c', 'c',
version : '0.4.0', version : '0.4.3',
license : 'GPLv3+', license : 'GPLv3+',
meson_version : '>= 0.50.0', meson_version : '>= 0.58.0',
default_options : default_options :
[ [
'warning_level=1', 'warning_level=1',
@@ -28,6 +28,8 @@ datadir = get_option('datadir')
sysconfdir = get_option('sysconfdir') sysconfdir = get_option('sysconfdir')
bindir = join_paths(prefix, get_option('bindir')) bindir = join_paths(prefix, get_option('bindir'))
udevrulesdir = join_paths(prefix, 'lib/udev/rules.d') udevrulesdir = join_paths(prefix, 'lib/udev/rules.d')
systemddir = join_paths(prefix, 'lib/systemd')
systemdsystemdir = join_paths(systemddir, 'system')
if datadir.startswith('/') if datadir.startswith('/')
full_datadir = datadir full_datadir = datadir
@@ -46,6 +48,7 @@ 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_CONFDIR', eg25_confdir), language : 'c')
add_global_arguments('-D@0@="@1@"'.format('EG25_DATADIR', eg25_datadir), language : 'c') add_global_arguments('-D@0@="@1@"'.format('EG25_DATADIR', eg25_datadir), language : 'c')
add_global_arguments('-D@0@="@1@"'.format('EG25_VERSION', meson.project_version()), language : 'c')
mmglib_dep = dependency('mm-glib', required : false) mmglib_dep = dependency('mm-glib', required : false)
if mmglib_dep.found() if mmglib_dep.found()
@@ -63,5 +66,6 @@ mgr_deps = [
] ]
subdir('data') subdir('data')
subdir('doc')
subdir('src') subdir('src')
subdir('udev') subdir('udev')

190
src/at.c
View File

@@ -5,10 +5,12 @@
*/ */
#include "at.h" #include "at.h"
#include "config.h"
#include "suspend.h" #include "suspend.h"
#include "gpio.h" #include "gpio.h"
#include "gnss.h" #include "gnss.h"
#include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -46,11 +48,29 @@ static int configure_serial(const char *tty)
return fd; return fd;
} }
static void at_free_command(gpointer cmd, gpointer data)
{
struct AtCommand *at_cmd = cmd;
struct EG25Manager *manager = data;
if (!at_cmd)
return;
g_free(at_cmd->cmd);
g_free(at_cmd->subcmd);
g_free(at_cmd->value);
g_free(at_cmd->expected);
g_free(at_cmd);
if (manager && manager->at_cmds)
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
}
gboolean at_send_command(struct EG25Manager *manager) gboolean at_send_command(struct EG25Manager *manager)
{ {
char command[256]; char command[256];
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL; struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
int ret, len = 0; int ret, len = 0, pos = 0;
if (at_cmd) { if (at_cmd) {
/* Wake up the modem from soft sleep before sending an AT command */ /* Wake up the modem from soft sleep before sending an AT command */
@@ -58,39 +78,72 @@ gboolean at_send_command(struct EG25Manager *manager)
/* Send AT command */ /* Send AT command */
if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL) if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL)
len = sprintf(command, "AT+%s\r\n", at_cmd->cmd); len = snprintf(command, sizeof(command), "AT+%s\r\n", at_cmd->cmd);
else if (at_cmd->subcmd == NULL && at_cmd->value == NULL) else if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
len = sprintf(command, "AT+%s?\r\n", at_cmd->cmd); len = snprintf(command, sizeof(command), "AT+%s?\r\n", at_cmd->cmd);
else if (at_cmd->subcmd == NULL && at_cmd->value) else if (at_cmd->subcmd == NULL && at_cmd->value)
len = sprintf(command, "AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value); len = snprintf(command, sizeof(command),"AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
else if (at_cmd->subcmd && at_cmd->value == NULL) else if (at_cmd->subcmd && at_cmd->value == NULL)
len = sprintf(command, "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd); len = snprintf(command, sizeof(command), "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
else if (at_cmd->subcmd && at_cmd->value) else if (at_cmd->subcmd && at_cmd->value)
len = sprintf(command, "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value); len = snprintf(command, sizeof(command), "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
if (len < 0) {
g_warning("snprintf(3) failed");
at_next_command(manager);
return FALSE;
}
else if (len >= sizeof(command)) {
g_warning("AT command does not fit into buffer "
"(%d bytes required, %zu available)", len, sizeof(command));
at_next_command(manager);
return FALSE;
}
manager->at_callback = at_cmd->callback; manager->at_callback = at_cmd->callback;
ret = write(manager->at_fd, command, len); do {
if (ret < len) ret = write(manager->at_fd, &command[pos], len);
g_warning("Couldn't write full AT command: wrote %d/%d bytes", ret, len);
g_message("Sending command: %s", g_strstrip(command)); if (ret < 0) {
switch (errno) {
case EAGAIN:
case EINTR:
/* Try again. */
break;
default:
g_warning("error sending AT command: %s", strerror(errno));
at_next_command(manager);
return FALSE;
}
}
else {
len -= ret;
pos += ret;
}
} while (len > 0);
g_message("Successfully sent command: %s", g_strstrip(command));
} else { } else {
/* Allow the modem to enter soft sleep again when we sent the AT command*/ /* Allow the modem to enter soft sleep again when we sent the AT command*/
gpio_sequence_sleep(manager); gpio_sequence_sleep(manager);
if (manager->modem_state < EG25_STATE_CONFIGURED) {
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
#ifdef HAVE_MMGLIB
MMModemState modem_state = mm_modem_get_state(manager->mm_modem);
if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED) if (manager->modem_state < EG25_STATE_CONFIGURED) {
modem_update_state(manager, modem_state); if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
else #ifdef HAVE_MMGLIB
manager->modem_state = EG25_STATE_CONFIGURED; if (manager->modem_state == EG25_STATE_ACQUIRED) {
MMModemState modem_state = mm_modem_get_state(manager->mm_modem);
if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED)
modem_update_state(manager, modem_state);
else
manager->modem_state = EG25_STATE_CONFIGURED;
}
#endif #endif
} else { } else {
manager->modem_state = EG25_STATE_CONFIGURED; manager->modem_state = EG25_STATE_CONFIGURED;
} }
} else if (manager->modem_state == EG25_STATE_SUSPENDING) { } else if (manager->modem_state == EG25_STATE_SUSPENDING) {
modem_suspend_post(manager); modem_suspend_post(manager);
} else if (manager->modem_state == EG25_STATE_RESETTING) { } else if (manager->modem_state == EG25_STATE_RESETTING) {
@@ -105,16 +158,7 @@ void at_next_command(struct EG25Manager *manager)
{ {
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL; struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd) at_free_command(at_cmd, manager);
return;
g_free(at_cmd->cmd);
g_free(at_cmd->subcmd);
g_free(at_cmd->value);
g_free(at_cmd->expected);
g_free(at_cmd);
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
at_send_command(manager); at_send_command(manager);
} }
@@ -201,7 +245,14 @@ static gboolean modem_response(gint fd,
*/ */
do { do {
ret = read(fd, tmp, sizeof(tmp)); ret = read(fd, tmp, sizeof(tmp));
if (ret > 0) { 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); memcpy(&response[pos], tmp, ret);
pos += ret; pos += ret;
usleep(10000); usleep(10000);
@@ -209,12 +260,15 @@ static gboolean modem_response(gint fd,
} while (ret > 0 && pos < (sizeof(response) - 1)); } while (ret > 0 && pos < (sizeof(response) - 1));
if (pos > 0) { if (pos > 0) {
g_autofree gchar *escaped = NULL;
response[pos] = 0; response[pos] = 0;
g_strstrip(response); g_strstrip(response);
if (strlen(response) == 0) if (strlen(response) == 0)
return TRUE; return TRUE;
g_message("Response: [%s]", response); escaped = g_strescape(response, "\"");
g_message("Response: [%s]", escaped);
/* /*
* When the modem is started, it outputs 'RDY' to indicate that * When the modem is started, it outputs 'RDY' to indicate that
@@ -240,14 +294,15 @@ static gboolean modem_response(gint fd,
else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff")) else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff"))
retry_at_command(manager); retry_at_command(manager);
/* /*
* Successfull AT responses contain 'OK', except for AT+QFUPL which also * Successful AT responses contain 'OK', except for AT+QFUPL which also
* returns 'CONNECT' when the modem is ready to receive data over serial * returns 'CONNECT' when the modem is ready to receive data over serial
* and '+QFUPL:...' when data upload is complete
*/ */
else if (strstr(response, "OK") || strstr(response, "CONNECT")) { else if (strstr(response, "OK") || strstr(response, "CONNECT") || strstr(response, "QFUPL")) {
if (manager->at_callback != NULL) if (manager->at_callback != NULL)
manager->at_callback(manager, response); manager->at_callback(manager, response);
else else
g_warning("AT command succesfull but no callback registered"); g_warning("AT command successful but no callback registered");
} }
/* Not a recognized response, try running next command, just in case */ /* Not a recognized response, try running next command, just in case */
else else
@@ -273,67 +328,61 @@ static void parse_commands_list(toml_array_t *array, GArray **cmds)
continue; continue;
value = toml_string_in(table, "cmd"); value = toml_string_in(table, "cmd");
if (value.ok) { if (value.ok)
cmd->cmd = g_strdup(value.u.s); cmd->cmd = value.u.s;
free(value.u.s);
}
value = toml_string_in(table, "subcmd"); value = toml_string_in(table, "subcmd");
if (value.ok) { if (value.ok)
cmd->subcmd = g_strdup(value.u.s); cmd->subcmd = value.u.s;
free(value.u.s);
}
value = toml_string_in(table, "value"); value = toml_string_in(table, "value");
if (value.ok) { if (value.ok)
cmd->value = g_strdup(value.u.s); cmd->value = value.u.s;
free(value.u.s);
}
value = toml_string_in(table, "expect"); value = toml_string_in(table, "expect");
if (value.ok) { if (value.ok)
cmd->expected = g_strdup(value.u.s); cmd->expected = value.u.s;
free(value.u.s);
}
} }
} }
int at_init(struct EG25Manager *manager, toml_table_t *config) int at_init(struct EG25Manager *manager, toml_table_t *config[])
{ {
toml_array_t *commands; toml_array_t *commands = NULL;
toml_datum_t uart_port; gchar *uart_port = NULL;
toml_table_t *at_config[EG25_CONFIG_COUNT];
uart_port = toml_string_in(config, "uart"); for (int i = 0; i < EG25_CONFIG_COUNT; i++)
if (!uart_port.ok) at_config[i] = config[i] ? toml_table_in(config[i], "at") : NULL;
if (!at_config[EG25_CONFIG_SYS])
g_error("Default config file lacks the 'at' section!");
if (!config_get_string(at_config, "uart", &uart_port))
g_error("Configuration file lacks UART port definition"); g_error("Configuration file lacks UART port definition");
manager->at_fd = configure_serial(uart_port.u.s); manager->at_fd = configure_serial(uart_port);
if (manager->at_fd < 0) { if (manager->at_fd < 0) {
g_critical("Unable to configure %s", uart_port.u.s); g_critical("Unable to configure %s", uart_port);
free(uart_port.u.s); g_free(uart_port);
return 1; return 1;
} }
free(uart_port.u.s); g_free(uart_port);
manager->at_source = g_unix_fd_add(manager->at_fd, G_IO_IN, modem_response, manager); manager->at_source = g_unix_fd_add(manager->at_fd, G_IO_IN, modem_response, manager);
commands = toml_array_in(config, "configure"); if (!config_get_array(at_config, "configure", &commands))
if (!commands)
g_error("Configuration file lacks initial AT commands list"); g_error("Configuration file lacks initial AT commands list");
parse_commands_list(commands, &configure_commands); parse_commands_list(commands, &configure_commands);
commands = toml_array_in(config, "suspend"); if (!config_get_array(at_config, "suspend", &commands))
if (!commands)
g_error("Configuration file lacks suspend AT commands list"); g_error("Configuration file lacks suspend AT commands list");
parse_commands_list(commands, &suspend_commands); parse_commands_list(commands, &suspend_commands);
commands = toml_array_in(config, "resume"); if (!config_get_array(at_config, "resume", &commands))
if (!commands)
g_error("Configuration file lacks resume AT commands list"); g_error("Configuration file lacks resume AT commands list");
parse_commands_list(commands, &resume_commands); parse_commands_list(commands, &resume_commands);
commands = toml_array_in(config, "reset"); if (!config_get_array(at_config, "reset", &commands))
if (!commands)
g_error("Configuration file lacks reset AT commands list"); g_error("Configuration file lacks reset AT commands list");
parse_commands_list(commands, &reset_commands); parse_commands_list(commands, &reset_commands);
@@ -354,6 +403,13 @@ void at_destroy(struct EG25Manager *manager)
void at_sequence_configure(struct EG25Manager *manager) void at_sequence_configure(struct EG25Manager *manager)
{ {
/*
* When configuring a new modem we should avoid processing an old
* command queue, so let's first clear the whole list
*/
if (manager->at_cmds)
g_list_foreach(manager->at_cmds, at_free_command, manager);
for (guint i = 0; i < configure_commands->len; i++) { for (guint i = 0; i < configure_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i); struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result); at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);

View File

@@ -17,7 +17,7 @@ typedef struct AtCommand {
int retries; int retries;
} AtCommand; } AtCommand;
int at_init(struct EG25Manager *manager, toml_table_t *config); int at_init(struct EG25Manager *manager, toml_table_t *config[]);
void at_destroy(struct EG25Manager *manager); void at_destroy(struct EG25Manager *manager);
void at_process_result(struct EG25Manager *manager, void at_process_result(struct EG25Manager *manager,

97
src/config.c Normal file
View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include "toml.h"
gboolean config_get_bool(toml_table_t **config, const gchar *key, gboolean *result)
{
toml_datum_t value = { .ok = 0 };
if (config[EG25_CONFIG_USER])
value = toml_bool_in(config[EG25_CONFIG_USER], key);
if (!value.ok)
value = toml_bool_in(config[EG25_CONFIG_SYS], key);
if (value.ok && result)
*result = value.u.b;
return !!value.ok;
}
gboolean config_get_int(toml_table_t **config, const gchar *key, gint *result)
{
toml_datum_t value = { .ok = 0 };
if (config[EG25_CONFIG_USER])
value = toml_int_in(config[EG25_CONFIG_USER], key);
if (!value.ok)
value = toml_int_in(config[EG25_CONFIG_SYS], key);
if (value.ok && result)
*result = value.u.i;
return !!value.ok;
}
gboolean config_get_uint(toml_table_t **config, const gchar *key, guint *result)
{
gint value;
gboolean found;
found = config_get_int(config, key, &value);
if (found) {
if (value <= 0 || value >= G_MAXUINT) {
g_message("Value out of range for [%s], discarding", key);
found = FALSE;
}
}
if (found && result)
*result = (guint) value;
return found;
}
gboolean config_get_string(toml_table_t **config, const gchar *key, gchar **result)
{
toml_datum_t value = { .ok = 0 };
if (config[EG25_CONFIG_USER])
value = toml_string_in(config[EG25_CONFIG_USER], key);
if (!value.ok)
value = toml_string_in(config[EG25_CONFIG_SYS], key);
if (value.ok && result)
*result = value.u.s;
return !!value.ok;
}
gboolean config_get_array(toml_table_t **config, const gchar *key, toml_array_t **result)
{
toml_array_t *array = NULL;
if (config[EG25_CONFIG_USER])
array = toml_array_in(config[EG25_CONFIG_USER], key);
if (!array)
array = toml_array_in(config[EG25_CONFIG_SYS], key);
if (array && result)
*result = array;
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;
}

27
src/config.h Normal file
View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <glib.h>
#include "manager.h"
#include "toml.h"
/*
* Helper functions for parsing config files: each function retrieves the
* value for key `key`, with the user config file having priority over the
* default config file. The values are stored in `result`.
*
* They all return TRUE if the value was found, FALSE otherwise.
*/
gboolean config_get_bool(toml_table_t **config, const gchar *key, gboolean *result);
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

@@ -4,12 +4,18 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
#include "config.h"
#include "gnss.h" #include "gnss.h"
#include "manager.h" #include "manager.h"
#include "at.h" #include "at.h"
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <errno.h>
#define BUFFER_SIZE 256 #define BUFFER_SIZE 256
#define UPLOAD_DELAY 100000 #define UPLOAD_DELAY_US 25000
#define UPLOAD_TIMEOUT_S 10
#define RESCHEDULE_IN_SECS 30 #define RESCHEDULE_IN_SECS 30
static void gnss_step(struct EG25Manager *manager); static void gnss_step(struct EG25Manager *manager);
@@ -27,51 +33,60 @@ gboolean gnss_upload_assistance_data(struct EG25Manager *manager)
return FALSE; return FALSE;
} }
/* data upload isn't necessary to bring the modem onine, so we should wait
* until we've finished the rest of our configuration */
if (!manager->modem_iface ||
manager->modem_state < EG25_STATE_CONFIGURED ||
manager->modem_state > EG25_STATE_CONNECTED) {
g_message ("Rescheduling upload since modem isn't online yet, in %ds",
RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return TRUE;
}
#ifdef HAVE_MMGLIB
/* ModemManager's Location is only available after unlocking */ /* ModemManager's Location is only available after unlocking */
if(!manager->mm_location) { if(manager->modem_iface == MODEM_IFACE_MODEMMANAGER && !manager->mm_location) {
g_message ("Rescheduling upload since Location interface is not available, in %ds", g_message ("Rescheduling upload since Location interface is not available, in %ds",
RESCHEDULE_IN_SECS); RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST; manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return TRUE; return TRUE;
} }
#endif
manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST; manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST;
gnss_step(manager); gnss_step(manager);
return FALSE; return FALSE;
} }
void gnss_init(struct EG25Manager *manager, toml_table_t *config) void gnss_init(struct EG25Manager *manager, toml_table_t *config[])
{ {
toml_datum_t enabled; toml_table_t *gnss_config[EG25_CONFIG_COUNT];
toml_datum_t url;
toml_datum_t file;
g_autoptr (GError) error = NULL; g_autoptr (GError) error = NULL;
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
gnss_config[i] = config[i] ? toml_table_in(config[i], "gnss") : NULL;
if (!gnss_config[EG25_CONFIG_SYS])
g_error("Default config file lacks the 'gnss' section!");
/* /*
* GNSS assistance is an optional feature, you can disable it * GNSS assistance is an optional feature, you can disable it
* if you want in the configuration file. * if you want in the configuration file.
* In case the configuration is missing, we assume GNSS assistance * In case the configuration is missing, we assume GNSS assistance
* to be disabled. * to be disabled.
*/ */
enabled = toml_bool_in(config, "enabled"); config_get_bool(gnss_config, "enabled", &manager->gnss_assistance_enabled);
manager->gnss_assistance_enabled = FALSE;
if (enabled.ok)
manager->gnss_assistance_enabled = enabled.u.b;
if (!manager->gnss_assistance_enabled) { if (!manager->gnss_assistance_enabled) {
g_message("GNSS assistance is disabled!"); g_message("GNSS assistance is disabled!");
return; return;
} }
url = toml_string_in(config, "url"); if (!config_get_string(gnss_config, "url", &manager->gnss_assistance_url))
if (url.ok)
manager->gnss_assistance_url = url.u.s;
else
g_error("GNSS assistance server URL is missing from config file"); g_error("GNSS assistance server URL is missing from config file");
file = toml_string_in(config, "file");
if (file.ok) if (!config_get_string(gnss_config, "file", &manager->gnss_assistance_file))
manager->gnss_assistance_file = file.u.s;
else
g_error("GNSS assistance file name is missing from config file"); g_error("GNSS assistance file name is missing from config file");
/* Create temporary file to store assistance data */ /* Create temporary file to store assistance data */
@@ -87,6 +102,8 @@ void gnss_init(struct EG25Manager *manager, toml_table_t *config)
void gnss_destroy(struct EG25Manager *manager) void gnss_destroy(struct EG25Manager *manager)
{ {
g_free(manager->gnss_assistance_url);
g_free(manager->gnss_assistance_file);
close(manager->gnss_assistance_fd); close(manager->gnss_assistance_fd);
} }
@@ -182,69 +199,71 @@ static void state_at_gnss(struct EG25Manager *manager)
static void fetch_assistance_data(struct EG25Manager *manager) static void fetch_assistance_data(struct EG25Manager *manager)
{ {
CURL *curl;
CURLcode response; CURLcode response;
long status_code; curl_off_t downloaded;
gchar *url = NULL; CURL *curl = NULL;
g_autofree gchar *url = NULL;
FILE *tmp_file = NULL; FILE *tmp_file = NULL;
long int size; gchar errbuf[CURL_ERROR_SIZE];
errbuf[0] = 0;
/* Fetch assistance data with curl */ /* Fetch assistance data with curl */
tmp_file = fdopen(manager->gnss_assistance_fd, "wb"); tmp_file = fdopen(manager->gnss_assistance_fd, "wb+");
if (tmp_file == NULL) {
g_critical("Unable to open file to save assistance data: %s",
g_strerror(errno));
goto bail;
}
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
if (ftruncate(manager->gnss_assistance_fd, 0) < 0)
g_warning("Unable to truncate file, assistance data might be invalid!");
url = g_strconcat(manager->gnss_assistance_url, "/", url = g_strconcat(manager->gnss_assistance_url, "/",
manager->gnss_assistance_file, NULL); manager->gnss_assistance_file, NULL);
curl = curl_easy_init(); curl = curl_easy_init();
if (!curl) if (!curl) {
g_error ("Unable to initialize curl"); g_critical("Unable to initialize curl");
goto bail;
}
curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file); curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
response = curl_easy_perform(curl); response = curl_easy_perform(curl);
if (response == CURLE_HTTP_RETURNED_ERROR) { if (response != CURLE_OK) {
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code); g_warning("Unable to fetch GNSS assistance data from %s: %s",
curl_easy_cleanup(curl); url, strlen(errbuf) ? errbuf : curl_easy_strerror(response));
g_warning ("Unable to fetch GNSS assistance data from %s (HTTP %ld)", goto bail;
url, status_code);
/* Restart upload on HTTP error status code */
g_message ("Rescheduling upload because of failure in %ds",
RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
G_SOURCE_FUNC(gnss_upload_assistance_data),
manager);
return;
} }
/* Get file size in bytes */ response = curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &downloaded);
size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END); if (response) {
lseek(manager->gnss_assistance_fd, 0, SEEK_SET); g_critical("Unable to get number of downloaded bytes from curl");
goto bail;
if (size <= 0) { } else if (downloaded <= 0) {
g_warning ("GNSS assistance data contains 0 bytes," g_warning("Downloaded empty assistance data file");
"check network connection."); goto bail;
/*
* Restart upload when file does not contain any data,
* mostly because of no network connection.
*/
g_message ("Rescheduling upload because of failure in %ds",
RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
G_SOURCE_FUNC(gnss_upload_assistance_data),
manager);
return;
} }
g_message("Fetching GNSS assistance data from %s was successfull", url); g_message("Fetching GNSS assistance data from %s was successful", url);
fflush(tmp_file);
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
g_free(url);
/* Go to the next step */ /* Go to the next step */
manager->gnss_assistance_step++; manager->gnss_assistance_step++;
gnss_step(manager); gnss_step(manager);
return;
bail:
if (curl != NULL)
curl_easy_cleanup(curl);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
} }
/******************************************************************************/ /******************************************************************************/
@@ -255,6 +274,12 @@ static void init_assistance_data_upload_ready(struct EG25Manager *manager,
/* Search for 'CONNECT' in response to start upload */ /* Search for 'CONNECT' in response to start upload */
if (strstr(response, "CONNECT")) { if (strstr(response, "CONNECT")) {
g_message("Modem ready for GNSS assistance data upload"); g_message("Modem ready for GNSS assistance data upload");
manager->gnss_assistance_step++;
gnss_step(manager);
} else if (strstr(response, "QFUPL")) {
/* Clear QFUPL AT command and process next */
at_next_command(manager);
manager->gnss_assistance_step++; manager->gnss_assistance_step++;
gnss_step(manager); gnss_step(manager);
} }
@@ -264,18 +289,24 @@ static void init_assistance_data_upload_start(struct EG25Manager *manager,
const char *response) const char *response)
{ {
gchar value[BUFFER_SIZE]; gchar value[BUFFER_SIZE];
long int size; off_t size;
/* Process AT response */ /* Process AT response */
at_process_result(manager, response); at_process_result(manager, response);
/* Get file size in bytes */ /* Get file size in bytes */
size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END); size = lseek(manager->gnss_assistance_fd, 0, SEEK_END);
if (size == -1) {
g_critical("gnss: unable to read size of xtra data file: %s", g_strerror(errno));
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return;
}
lseek(manager->gnss_assistance_fd, 0, SEEK_SET); lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
/* Start upload */ /* Start upload */
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld\r\n", g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld,%d",
manager->gnss_assistance_file, size); manager->gnss_assistance_file, size, UPLOAD_TIMEOUT_S);
g_message("Initiate GNSS assistance data upload: %s", value); g_message("Initiate GNSS assistance data upload: %s", value);
at_append_command(manager, "QFUPL", NULL, value, NULL, at_append_command(manager, "QFUPL", NULL, value, NULL,
init_assistance_data_upload_ready); init_assistance_data_upload_ready);
@@ -295,39 +326,34 @@ static void init_assistance_data_upload(struct EG25Manager *manager)
static void upload_assistance_data(struct EG25Manager *manager) static void upload_assistance_data(struct EG25Manager *manager)
{ {
char buffer[2*BUFFER_SIZE]; gint error;
gint len; glong written_total = 0;
gboolean success = TRUE; gint ret;
struct stat sb;
/* Copy downloaded XTRA assistance data to the modem over serial */ if (fstat(manager->gnss_assistance_fd, &sb) != 0) {
while((len = read(manager->gnss_assistance_fd, buffer, 2*BUFFER_SIZE)) > 0) g_critical("gnss: unable to stat xtra data file: %s", g_strerror(errno));
{
len = write(manager->at_fd, buffer, len); /* Make sure the upload times out and the modem goes back to AT command mode */
if (len < 0) { sleep(UPLOAD_TIMEOUT_S + 1);
success = FALSE; manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
g_error("Writing GNSS assistance data failed: %d", len); return;
break;
}
usleep(UPLOAD_DELAY);
g_message("Uploaded %d bytes", len);
} }
/* Clear QFUPL AT command and process next */ do {
at_next_command(manager); errno = 0;
/* Copy downloaded XTRA assistance data to the modem over serial */
ret = sendfile(manager->at_fd, manager->gnss_assistance_fd, &written_total, BUFFER_SIZE);
error = errno;
usleep(UPLOAD_DELAY_US);
} while ((!error && written_total < sb.st_size) || (ret == -1 && error == EAGAIN));
/* Go to the next step if successful */ /* Go to the next step if successful */
if (success) { if (!error) {
manager->gnss_assistance_step++; g_message("Successfully uploaded %ld bytes to the modem", written_total);
gnss_step(manager); } else {
} g_critical("Unable to upload xtra data: %s", g_strerror(error));
/* Restart upload */
else {
g_message ("Rescheduling upload because of failure in %ds",
RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST; manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
G_SOURCE_FUNC(gnss_upload_assistance_data),
manager);
} }
} }
@@ -351,13 +377,13 @@ static void finish_assistance_data_upload(struct EG25Manager *manager)
/* Configure GNSS assistance clock to current system time (UTC) */ /* Configure GNSS assistance clock to current system time (UTC) */
datetime = g_date_time_new_now_utc(); datetime = g_date_time_new_now_utc();
timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"\r\n"); timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"");
g_message("Setting GNSS assistance UTC clock to: %s", timestring); g_message("Setting GNSS assistance UTC clock to: %s", timestring);
at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL, at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL,
at_process_result); at_process_result);
/* Configure GNSS engine to use uploaded GNSS assistance data */ /* Configure GNSS engine to use uploaded GNSS assistance data */
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"\r\n", g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"",
manager->gnss_assistance_file); manager->gnss_assistance_file);
g_message("Setting GNSS assistance file to: %s", value); g_message("Setting GNSS assistance file to: %s", value);
at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL, at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL,
@@ -370,9 +396,9 @@ static void finish_assistance_data_upload(struct EG25Manager *manager)
#ifdef HAVE_MMGLIB #ifdef HAVE_MMGLIB
static void enable_mm_gnss(struct EG25Manager *manager) static void enable_mm_gnss(struct EG25Manager *manager)
{ {
MMModemLocationSource sources;
gboolean signal_location;
g_autoptr (GError) error = NULL; g_autoptr (GError) error = NULL;
MMModemLocationSource sources = mm_modem_location_get_enabled(manager->mm_location);
gboolean signal_location = mm_modem_location_signals_location(manager->mm_location);
if (manager->gnss_sources & EG25_GNSS_SOURCE_UNMANAGED) if (manager->gnss_sources & EG25_GNSS_SOURCE_UNMANAGED)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
@@ -381,8 +407,6 @@ static void enable_mm_gnss(struct EG25Manager *manager)
if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW) if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
sources = mm_modem_location_get_enabled(manager->mm_location);
signal_location = mm_modem_location_signals_location(manager->mm_location);
mm_modem_location_setup_sync(manager->mm_location, sources, mm_modem_location_setup_sync(manager->mm_location, sources,
signal_location, NULL, &error); signal_location, NULL, &error);
if (error != NULL) if (error != NULL)
@@ -419,12 +443,21 @@ void gnss_step(struct EG25Manager *manager)
g_message("GNSS assistance upload started..."); g_message("GNSS assistance upload started...");
/* fall-through */ /* fall-through */
case EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA:
g_message("GNSS assistance upload step (%d/%d): "
"fetching assistance data",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
fetch_assistance_data(manager);
break;
#ifdef HAVE_MMGLIB #ifdef HAVE_MMGLIB
case EG25_GNSS_STEP_MM_GNSS_DISABLE: case EG25_GNSS_STEP_MM_GNSS_DISABLE:
g_message("GNSS assistance upload step (%d/%d): " if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
"disabling GNSS engine through ModemManager", g_message("GNSS assistance upload step (%d/%d): "
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); "disabling GNSS engine through ModemManager",
disable_mm_gnss(manager); manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
disable_mm_gnss(manager);
}
manager->gnss_assistance_step++; manager->gnss_assistance_step++;
/* fall-through */ /* fall-through */
#endif #endif
@@ -436,13 +469,6 @@ void gnss_step(struct EG25Manager *manager)
state_at_gnss(manager); state_at_gnss(manager);
break; break;
case EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA:
g_message("GNSS assistance upload step (%d/%d): "
"fetching assistance data",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
fetch_assistance_data(manager);
break;
case EG25_GNSS_STEP_INIT_UPLOAD: case EG25_GNSS_STEP_INIT_UPLOAD:
g_message("GNSS assistance upload step (%d/%d): initiating upload", g_message("GNSS assistance upload step (%d/%d): initiating upload",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
@@ -464,10 +490,12 @@ void gnss_step(struct EG25Manager *manager)
#ifdef HAVE_MMGLIB #ifdef HAVE_MMGLIB
case EG25_GNSS_STEP_MM_GNSS_ENABLE: case EG25_GNSS_STEP_MM_GNSS_ENABLE:
g_message("GNSS assistance upload step (%d/%d): " if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
"re-enabling GNSS through ModemManager", g_message("GNSS assistance upload step (%d/%d): "
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST); "re-enabling GNSS through ModemManager",
enable_mm_gnss(manager); manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
enable_mm_gnss(manager);
}
manager->gnss_assistance_step++; manager->gnss_assistance_step++;
/* fall-through */ /* fall-through */
#endif #endif
@@ -485,4 +513,3 @@ void gnss_step(struct EG25Manager *manager)
break; break;
} }
} }

View File

@@ -7,10 +7,11 @@
#pragma once #pragma once
#include <time.h> #include <time.h>
#include <unistd.h>
#include <curl/curl.h> #include <curl/curl.h>
#include "manager.h" #include "manager.h"
void gnss_init(struct EG25Manager *manager, toml_table_t *config); void gnss_init(struct EG25Manager *manager, toml_table_t *config[]);
void gnss_destroy(struct EG25Manager *manager); void gnss_destroy(struct EG25Manager *manager);
gboolean gnss_upload_assistance_data(struct EG25Manager *manager); gboolean gnss_upload_assistance_data(struct EG25Manager *manager);

View File

@@ -4,17 +4,16 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
#include "config.h"
#include "gpio.h" #include "gpio.h"
#include <unistd.h> #include <unistd.h>
/* Those defines are used for legacy config files only */
#define GPIO_CHIP1_LABEL "1c20800.pinctrl" #define GPIO_CHIP1_LABEL "1c20800.pinctrl"
#define GPIO_CHIP2_LABEL "1f02c00.pinctrl" #define GPIO_CHIP2_LABEL "1f02c00.pinctrl"
#define MAX_GPIOCHIP_LINES 352 #define MAX_GPIOCHIP_LINES 352
#define GPIO_IDX_INVAL 0xffff
enum { enum {
GPIO_OUT_DTR = 0, GPIO_OUT_DTR = 0,
GPIO_OUT_PWRKEY, GPIO_OUT_PWRKEY,
@@ -24,12 +23,23 @@ enum {
GPIO_OUT_COUNT GPIO_OUT_COUNT
}; };
enum { enum {
GPIO_IN_STATUS = 0, GPIO_IN_STATUS = 0,
GPIO_IN_COUNT GPIO_IN_COUNT
}; };
static char *gpio_out_names[] = {
"dtr",
"pwrkey",
"reset",
"apready",
"disable",
};
static char *gpio_in_names[] = {
"status",
};
int gpio_sequence_poweron(struct EG25Manager *manager) int gpio_sequence_poweron(struct EG25Manager *manager)
{ {
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 1); gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 1);
@@ -71,12 +81,14 @@ int gpio_sequence_resume(struct EG25Manager *manager)
int gpio_sequence_wake(struct EG25Manager *manager) int gpio_sequence_wake(struct EG25Manager *manager)
{ {
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0); if (gpiod_line_get_value(manager->gpio_out[GPIO_OUT_DTR])) {
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0);
/* Give the modem 200ms to wake from soft sleep */ /* Give the modem 200ms to wake from soft sleep */
usleep(200000); usleep(200000);
g_message("Executed soft wake sequence"); g_message("Executed soft wake sequence");
}
return 0; return 0;
} }
@@ -89,92 +101,165 @@ int gpio_sequence_sleep(struct EG25Manager *manager)
return 0; return 0;
} }
static guint get_config_gpio(toml_table_t *config, const char *id) struct gpiod_line *gpio_get_output_line(struct EG25Manager *manager, int chip, int line)
{ {
toml_datum_t value = toml_int_in(config, id); struct gpiod_line *gpio_line;
guint gpio;
if (!value.ok) gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
return GPIO_IDX_INVAL; if (!gpio_line)
return NULL;
gpio = (guint)value.u.i; if (gpiod_line_request_output(gpio_line, "eg25manager", 0) < 0) {
gpiod_line_release(gpio_line);
return NULL;
}
return gpio; return gpio_line;
} }
int gpio_init(struct EG25Manager *manager, toml_table_t *config) struct gpiod_line *gpio_get_input_line(struct EG25Manager *manager, int chip, int line)
{ {
int i, ret; struct gpiod_line *gpio_line;
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); gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
if (!manager->gpiochip[0]) { if (!gpio_line)
g_critical("Unable to open GPIO chip " GPIO_CHIP1_LABEL); return NULL;
return 1;
if (gpiod_line_request_input(gpio_line, "eg25manager") < 0) {
gpiod_line_release(gpio_line);
return NULL;
} }
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL); return gpio_line;
if (!manager->gpiochip[1]) { }
g_critical("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
return 1;
}
gpio_out_idx[GPIO_OUT_DTR] = get_config_gpio(config, "dtr"); int gpio_init(struct EG25Manager *manager, toml_table_t *config[])
gpio_out_idx[GPIO_OUT_PWRKEY] = get_config_gpio(config, "pwrkey"); {
gpio_out_idx[GPIO_OUT_RESET] = get_config_gpio(config, "reset"); int i;
gpio_out_idx[GPIO_OUT_APREADY] = get_config_gpio(config, "apready"); toml_table_t *gpio_config[EG25_CONFIG_COUNT];
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++) { for (i = 0; i < EG25_CONFIG_COUNT; i++)
guint offset, chipidx; gpio_config[i] = config[i] ? toml_table_in(config[i], "gpio") : NULL;
if (gpio_out_idx[i] < MAX_GPIOCHIP_LINES) { if (!gpio_config[EG25_CONFIG_SYS])
offset = gpio_out_idx[i]; g_error("Default config file lacks the 'gpio' section!");
chipidx = 0;
} else { /*
offset = gpio_out_idx[i] - MAX_GPIOCHIP_LINES; * The system config could have the `chips` key, but the user one
chipidx = 1; * 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;
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 < 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); for (i = 0; i < GPIO_OUT_COUNT; i++) {
if (!manager->gpio_out[i]) { toml_table_t *table;
g_error("Unable to get output GPIO %d", i); toml_datum_t chip, line;
return 1; 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); for (i = 0; i < GPIO_IN_COUNT; i++) {
if (ret < 0) { toml_table_t *table;
g_error("Unable to request output GPIO %d", i); toml_datum_t chip, line;
return 1;
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++) { /* Legacy config file, only used on the OG PinePhone */
guint offset, chipidx; 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_in_idx[i] == GPIO_IDX_INVAL) manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
continue; if (!manager->gpiochip[1])
g_error("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
if (gpio_in_idx[i] < MAX_GPIOCHIP_LINES) { for (i = 0; i < GPIO_OUT_COUNT; i++) {
offset = gpio_in_idx[i]; if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx))
chipidx = 0; g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
} else {
offset = gpio_in_idx[i] - MAX_GPIOCHIP_LINES; if (gpio_idx < MAX_GPIOCHIP_LINES) {
chipidx = 1; 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); for (i = 0; i < GPIO_IN_COUNT; i++) {
if (!manager->gpio_in[i]) { if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx))
g_warning("Unable to get input GPIO %d", i); continue;
continue;
}
ret = gpiod_line_request_input(manager->gpio_in[i], "eg25manager"); if (gpio_idx < MAX_GPIOCHIP_LINES) {
if (ret < 0) { offset = gpio_idx;
g_warning("Unable to request input GPIO %d", i); chipidx = 0;
manager->gpio_in[i] = NULL; } 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

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

View File

@@ -5,6 +5,7 @@
*/ */
#include "at.h" #include "at.h"
#include "config.h"
#include "gpio.h" #include "gpio.h"
#include "manager.h" #include "manager.h"
@@ -33,6 +34,15 @@
#define EG25_DATADIR "/usr/share/eg25-manager" #define EG25_DATADIR "/usr/share/eg25-manager"
#endif #endif
#ifndef EG25_VERSION
#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) static gboolean quit_app(struct EG25Manager *manager)
{ {
int i; int i;
@@ -134,62 +144,84 @@ void modem_configure(struct EG25Manager *manager)
static gboolean modem_reset_done(struct EG25Manager* manager) static gboolean modem_reset_done(struct EG25Manager* manager)
{ {
manager->modem_state = EG25_STATE_RESUMING; manager->modem_state = EG25_STATE_RESUMING;
manager->reset_timer = 0; manager->complete_reset_timer = 0;
return FALSE; return FALSE;
} }
void modem_reset(struct EG25Manager *manager) gboolean modem_reset(struct EG25Manager *manager)
{ {
int fd, ret, len; int fd, ret, len;
if (manager->reset_timer) /* reset sequence started, cannot be canceled anymore */
return; if (manager->schedule_reset_timer) {
g_source_remove(manager->schedule_reset_timer);
/* manager->schedule_reset_timer = 0;
* If we are managing the modem through lets say ofono, we should not }
* reset the modem based on the availability of USB ID
* TODO: Improve ofono plugin and add support for fetching USB ID
*/
if (manager->modem_iface != MODEM_IFACE_MODEMMANAGER)
return;
if (manager->modem_recovery_timer) { if (manager->modem_recovery_timer) {
g_source_remove(manager->modem_recovery_timer); g_source_remove(manager->modem_recovery_timer);
manager->modem_recovery_timer = 0; manager->modem_recovery_timer = 0;
} }
if (manager->complete_reset_timer) {
g_message("modem_reset: timer already setup, skipping...");
return G_SOURCE_REMOVE;
}
/*
* If we are managing the modem through lets say ofono, we should not
* reset the modem based on the availability of USB ID
* TODO: Improve ofono plugin and add support for fetching USB ID
*/
if (manager->modem_iface != MODEM_IFACE_MODEMMANAGER) {
g_message("modem_reset: not using ModemManager, bail out!");
return G_SOURCE_REMOVE;
}
if (!manager->modem_usb_id) { if (!manager->modem_usb_id) {
g_warning("Unknown modem USB ID"); g_warning("Empty modem USB ID");
goto error; goto error;
} }
g_message("Trying to reset modem with USB ID '%s'", manager->modem_usb_id);
len = strlen(manager->modem_usb_id); len = strlen(manager->modem_usb_id);
manager->modem_state = EG25_STATE_RESETTING; manager->modem_state = EG25_STATE_RESETTING;
fd = open("/sys/bus/usb/drivers/usb/unbind", O_WRONLY); fd = open("/sys/bus/usb/drivers/usb/unbind", O_WRONLY);
if (fd < 0) if (fd < 0) {
g_warning("Unable to open /sys/bus/usb/drivers/usb/unbind");
goto error; goto error;
}
ret = write(fd, manager->modem_usb_id, len); ret = write(fd, manager->modem_usb_id, len);
if (ret < len) if (ret < len) {
g_warning("Couldn't unbind modem: wrote %d/%d bytes", ret, len); g_warning("Couldn't unbind modem: wrote %d/%d bytes", ret, len);
goto error;
}
close(fd); close(fd);
fd = open("/sys/bus/usb/drivers/usb/bind", O_WRONLY); fd = open("/sys/bus/usb/drivers/usb/bind", O_WRONLY);
if (fd < 0) if (fd < 0) {
g_warning("Unable to open /sys/bus/usb/drivers/usb/unbind");
goto error; goto error;
}
ret = write(fd, manager->modem_usb_id, len); ret = write(fd, manager->modem_usb_id, len);
if (ret < len) if (ret < len) {
g_warning("Couldn't bind modem: wrote %d/%d bytes", ret, len); g_warning("Couldn't bind modem: wrote %d/%d bytes", ret, len);
goto error;
}
close(fd); close(fd);
g_message("Successfully reset modem's USB connection");
/* /*
* 3s is long enough to make sure the modem has been bound back and * 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 * 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; return G_SOURCE_REMOVE;
error: error:
// Release blocking sleep inhibitor // Release blocking sleep inhibitor
@@ -199,8 +231,15 @@ error:
g_source_remove(manager->modem_boot_timer); g_source_remove(manager->modem_boot_timer);
manager->modem_boot_timer = 0; manager->modem_boot_timer = 0;
} }
// Everything else failed, reboot the modem // Everything else failed, reboot the modem
g_message("USB reset failed, falling back to AT command");
at_sequence_reset(manager); at_sequence_reset(manager);
// Setup timer for making sure we don't queue other reset commands
manager->complete_reset_timer = g_timeout_add_seconds(30, G_SOURCE_FUNC(modem_reset_done), manager);
return G_SOURCE_REMOVE;
} }
void modem_suspend_pre(struct EG25Manager *manager) void modem_suspend_pre(struct EG25Manager *manager)
@@ -225,7 +264,7 @@ void modem_resume_post(struct EG25Manager *manager)
at_sequence_resume(manager); at_sequence_resume(manager);
} }
static toml_table_t *parse_config_file(char *config_file) static toml_table_t *parse_config_file(char *config_file, gboolean force_default)
{ {
toml_table_t *toml_config; toml_table_t *toml_config;
gchar *compatible; gchar *compatible;
@@ -249,28 +288,26 @@ static toml_table_t *parse_config_file(char *config_file)
} while (pos < len); } while (pos < len);
for (pos = 0; pos < compat->len; pos++) { 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)); g_autofree gchar *filename = NULL;
if (force_default)
filename = g_strdup_printf(EG25_DATADIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
else
filename = g_strdup_printf(EG25_CONFDIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
if (access(filename, F_OK) == 0) { if (access(filename, F_OK) == 0) {
g_message("Opening config file: %s", filename); g_message("Opening config file: %s", filename);
f = fopen(filename, "r"); f = fopen(filename, "r");
break; 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) if (!f) {
g_error("unable to find a suitable config file!"); if (force_default)
g_error("unable to find a suitable config file!");
else
return NULL;
}
toml_config = toml_parse_file(f, error, sizeof(error)); toml_config = toml_parse_file(f, error, sizeof(error));
if (!toml_config) if (!toml_config)
@@ -285,16 +322,19 @@ int main(int argc, char *argv[])
g_autoptr(GError) err = NULL; g_autoptr(GError) err = NULL;
struct EG25Manager manager; struct EG25Manager manager;
gchar *config_file = NULL; gchar *config_file = NULL;
toml_table_t *toml_config; gboolean show_version = FALSE;
toml_table_t *toml_manager; gboolean monitor_udev = TRUE;
toml_datum_t toml_value; toml_table_t *toml_config[EG25_CONFIG_COUNT];
toml_table_t *manager_config[EG25_CONFIG_COUNT];
const GOptionEntry options[] = { const GOptionEntry options[] = {
{ "config", 'c', 0, G_OPTION_ARG_STRING, &config_file, "Config file to use.", NULL }, { "config", 'c', 0, G_OPTION_ARG_STRING, &config_file, "Config file to use.", NULL },
{ "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "Display version information and exit.", NULL },
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL } { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
}; };
memset(&manager, 0, sizeof(manager)); memset(&manager, 0, sizeof(manager));
manager.at_fd = -1; manager.at_fd = -1;
manager.poweron_delay = POWERON_DELAY_US;
manager.suspend_delay_fd = -1; manager.suspend_delay_fd = -1;
manager.suspend_block_fd = -1; manager.suspend_block_fd = -1;
@@ -305,45 +345,57 @@ int main(int argc, char *argv[])
return 1; return 1;
} }
manager.loop = g_main_loop_new(NULL, FALSE); if (show_version) {
printf("eg25-manager version %s\n", EG25_VERSION);
toml_config = parse_config_file(config_file); return 0;
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;
toml_value = toml_int_in(toml_manager, "poweron_delay");
if (toml_value.ok) {
if (toml_value.u.i >= 0 && toml_value.u.i <= G_MAXULONG) {
// Safe to cast into gulong
manager.poweron_delay = (gulong) toml_value.u.i;
} else {
// Changed from initialized default value but not in range
g_message("Configured poweron_delay out of range, using default");
}
}
} }
at_init(&manager, toml_table_in(toml_config, "at")); manager.loop = g_main_loop_new(NULL, FALSE);
gpio_init(&manager, toml_table_in(toml_config, "gpio"));
toml_config[EG25_CONFIG_SYS] = parse_config_file(NULL, TRUE);
toml_config[EG25_CONFIG_USER] = parse_config_file(config_file, FALSE);
/*
* We need at least one valid config file, and assuming it's
* EG25_CONFIG_SYS will make the rest easier to implement
*/
if (!toml_config[EG25_CONFIG_SYS] && toml_config[EG25_CONFIG_USER]) {
toml_config[EG25_CONFIG_SYS] = toml_config[EG25_CONFIG_USER];
toml_config[EG25_CONFIG_USER] = NULL;
}
if (!toml_config[EG25_CONFIG_SYS])
g_error("Unable to parse config file!");
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
manager_config[i] = toml_config[i] ? toml_table_in(toml_config[i], "manager") : NULL;
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, "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);
#ifdef HAVE_MMGLIB #ifdef HAVE_MMGLIB
mm_iface_init(&manager, toml_table_in(toml_config, "mm-iface")); mm_iface_init(&manager, toml_config);
#endif #endif
ofono_iface_init(&manager); ofono_iface_init(&manager, toml_config);
suspend_init(&manager, toml_table_in(toml_config, "suspend")); suspend_init(&manager, toml_config);
udev_init(&manager, toml_table_in(toml_config, "udev")); if (monitor_udev)
gnss_init(&manager, toml_table_in(toml_config, "gnss")); udev_init(&manager, toml_config);
gnss_init(&manager, toml_config);
for (int i = 0; i < EG25_CONFIG_COUNT; i++) {
if (toml_config[i])
toml_free(toml_config[i]);
}
g_idle_add(G_SOURCE_FUNC(modem_start), &manager); g_idle_add(G_SOURCE_FUNC(modem_start), &manager);

View File

@@ -18,11 +18,11 @@
typedef enum { typedef enum {
EG25_GNSS_STEP_FIRST = 0, EG25_GNSS_STEP_FIRST = 0,
EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA,
#ifdef HAVE_MMGLIB #ifdef HAVE_MMGLIB
EG25_GNSS_STEP_MM_GNSS_DISABLE, EG25_GNSS_STEP_MM_GNSS_DISABLE,
#endif #endif
EG25_GNSS_STEP_AT_GNSS_DISABLE, EG25_GNSS_STEP_AT_GNSS_DISABLE,
EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA,
EG25_GNSS_STEP_INIT_UPLOAD, EG25_GNSS_STEP_INIT_UPLOAD,
EG25_GNSS_STEP_UPLOAD, EG25_GNSS_STEP_UPLOAD,
EG25_GNSS_STEP_FINISH_UPLOAD, EG25_GNSS_STEP_FINISH_UPLOAD,
@@ -48,11 +48,12 @@ enum EG25State {
EG25_STATE_STARTED, // Modem has been started and declared itdata ready EG25_STATE_STARTED, // Modem has been started and declared itdata ready
EG25_STATE_ACQUIRED, // Modem has been probed by ModemManager EG25_STATE_ACQUIRED, // Modem has been probed by ModemManager
EG25_STATE_CONFIGURED, // Modem has been configured through AT commands EG25_STATE_CONFIGURED, // Modem has been configured through AT commands
EG25_STATE_SUSPENDING, // System is going into suspend
EG25_STATE_RESUMING, // System is being resumed, waiting for modem to come back
EG25_STATE_REGISTERED, // Modem is unlocked and registered to a network provider EG25_STATE_REGISTERED, // Modem is unlocked and registered to a network provider
EG25_STATE_CONNECTED, // Modem is connected (data connection active) EG25_STATE_CONNECTED, // Modem is connected (data connection active)
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_RESETTING, // Something went wrong, we're restarting the modem
EG25_STATE_UPDATING, // Modem is present but being updated
EG25_STATE_FINISHING EG25_STATE_FINISHING
}; };
@@ -62,13 +63,20 @@ enum ModemIface {
MODEM_IFACE_OFONO MODEM_IFACE_OFONO
}; };
enum EG25Config {
EG25_CONFIG_SYS = 0,
EG25_CONFIG_USER,
EG25_CONFIG_COUNT
};
struct EG25Manager { struct EG25Manager {
GMainLoop *loop; GMainLoop *loop;
guint reset_timer; guint complete_reset_timer;
guint schedule_reset_timer;
gboolean use_libusb; gboolean use_libusb;
guint usb_vid; guint usb_vid;
guint usb_pid; guint usb_pid;
gulong poweron_delay; guint poweron_delay;
int at_fd; int at_fd;
guint at_source; guint at_source;
@@ -113,7 +121,7 @@ struct EG25Manager {
}; };
void modem_configure(struct EG25Manager *data); void modem_configure(struct EG25Manager *data);
void modem_reset(struct EG25Manager *data); gboolean modem_reset(struct EG25Manager *data);
void modem_suspend_pre(struct EG25Manager *data); void modem_suspend_pre(struct EG25Manager *data);
void modem_suspend_post(struct EG25Manager *data); void modem_suspend_post(struct EG25Manager *data);
void modem_resume_pre(struct EG25Manager *data); void modem_resume_pre(struct EG25Manager *data);

View File

@@ -9,6 +9,7 @@ subdir('libgdbofono')
src = [ src = [
'at.c', 'at.h', 'at.c', 'at.h',
'config.c', 'config.h',
'gpio.c', 'gpio.h', 'gpio.c', 'gpio.h',
'manager.c', 'manager.h', 'manager.c', 'manager.h',
'ofono-iface.c', 'ofono-iface.h', 'ofono-iface.c', 'ofono-iface.h',
@@ -24,7 +25,7 @@ if mmglib_dep.found()
endif endif
executable ( executable (
'eg25manager', 'eg25-manager',
src, src,
dependencies : mgr_deps, dependencies : mgr_deps,
link_with: gdbofono_lib, link_with: gdbofono_lib,

View File

@@ -209,7 +209,7 @@ static void mm_vanished_cb(GDBusConnection *connection,
mm_iface_clean(manager); mm_iface_clean(manager);
} }
void mm_iface_init(struct EG25Manager *manager, toml_table_t *config) 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, manager->mm_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, MM_DBUS_SERVICE,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START, G_BUS_NAME_WATCHER_FLAGS_AUTO_START,

View File

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

View File

@@ -128,7 +128,7 @@ static void ofono_vanished_cb(GDBusConnection *connection,
} }
} }
void ofono_iface_init(struct EG25Manager *manager) void ofono_iface_init(struct EG25Manager *manager, toml_table_t *config[])
{ {
manager->ofono_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, OFONO_SERVICE, manager->ofono_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START, G_BUS_NAME_WATCHER_FLAGS_AUTO_START,

View File

@@ -8,5 +8,5 @@
#include "manager.h" #include "manager.h"
void ofono_iface_init(struct EG25Manager *data); void ofono_iface_init(struct EG25Manager *data, toml_table_t *config[]);
void ofono_iface_destroy(struct EG25Manager *data); void ofono_iface_destroy(struct EG25Manager *data);

View File

@@ -9,6 +9,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
#include "config.h"
#include "manager.h" #include "manager.h"
#include <gio/gunixfdlist.h> #include <gio/gunixfdlist.h>
@@ -17,10 +18,24 @@
#define SD_PATH "/org/freedesktop/login1" #define SD_PATH "/org/freedesktop/login1"
#define SD_INTERFACE "org.freedesktop.login1.Manager" #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) static gboolean check_modem_resume(struct EG25Manager *manager)
{ {
g_message("Modem wasn't probed in time, restart it!");
manager->modem_recovery_timer = 0; 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); modem_reset(manager);
return FALSE; return FALSE;
@@ -172,7 +187,7 @@ static void signal_cb(GDBusProxy *proxy,
modem_resume_pre(manager); modem_resume_pre(manager);
if ( if (
#ifdef HAVE_MMGLIB #ifdef HAVE_MMGLIB
manager->mm_modem || manager->mm_modem ||
#endif #endif
manager->modem_iface == MODEM_IFACE_OFONO) { manager->modem_iface == MODEM_IFACE_OFONO) {
/* /*
@@ -183,8 +198,7 @@ static void signal_cb(GDBusProxy *proxy,
* If modem is managed by ofono, we also do resume sequence immediately * If modem is managed by ofono, we also do resume sequence immediately
* as ofono handles resuming from sleep itself. * as ofono handles resuming from sleep itself.
*/ */
manager->modem_state = EG25_STATE_CONFIGURED; resume_ok(manager);
modem_resume_post(manager);
} else { } else {
manager->modem_state = EG25_STATE_RESUMING; manager->modem_state = EG25_STATE_RESUMING;
manager->modem_recovery_timer = g_timeout_add_seconds(manager->modem_recovery_timeout, manager->modem_recovery_timer = g_timeout_add_seconds(manager->modem_recovery_timeout,
@@ -238,18 +252,26 @@ static void on_proxy_acquired(GObject *object,
} }
} }
void suspend_init(struct EG25Manager *manager, toml_table_t *config) void suspend_init(struct EG25Manager *manager, toml_table_t *config[])
{ {
toml_datum_t timeout_value; toml_table_t *suspend_config[EG25_CONFIG_COUNT];
if (config) { for (int i = 0; i < EG25_CONFIG_COUNT; i++)
timeout_value = toml_int_in(config, "boot_timeout"); suspend_config[i] = config[i] ? toml_table_in(config[i], "suspend") : NULL;
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) * The `suspend` section is optional in both the user and system config files,
manager->modem_recovery_timeout = (guint)timeout_value.u.i; * so let's make sure suspend_config[EG25_CONFIG_SYS] is valid if one of the
* files has it.
*/
if (suspend_config[EG25_CONFIG_USER] && !suspend_config[EG25_CONFIG_SYS]) {
suspend_config[EG25_CONFIG_SYS] = suspend_config[EG25_CONFIG_USER];
suspend_config[EG25_CONFIG_USER] = NULL;
}
if (suspend_config[EG25_CONFIG_SYS]) {
config_get_uint(suspend_config, "boot_timeout", &manager->modem_boot_timeout);
config_get_uint(suspend_config, "recovery_timeout", &manager->modem_recovery_timeout);
} }
if (manager->modem_boot_timeout == 0) if (manager->modem_boot_timeout == 0)

View File

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

View File

@@ -11,21 +11,50 @@
static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data) static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data)
{ {
struct EG25Manager *manager = data; struct EG25Manager *manager = data;
const gchar *prop;
long vid = 0, pid = 0;
if (strcmp(action, "unbind") != 0 || /*
manager->modem_state == EG25_STATE_RESETTING || * Act only if the device is the one identified as a modem by MM/ofono
!manager->modem_usb_id) { */
if (!manager->modem_usb_id ||
strcmp(g_udev_device_get_name(device), manager->modem_usb_id) != 0) {
return; return;
} }
if (strncmp(g_udev_device_get_name(device), manager->modem_usb_id, strlen(manager->modem_usb_id)) == 0 && prop = g_udev_device_get_property(device, "ID_VENDOR_ID");
manager->reset_timer == 0) { if (prop)
g_message("Lost modem, resetting..."); vid = strtol(prop, NULL, 16);
modem_reset(manager);
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->complete_reset_timer != 0 ||
manager->schedule_reset_timer != 0) {
return;
}
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) void udev_init (struct EG25Manager *manager, toml_table_t *config[])
{ {
const char * const subsystems[] = { "usb", NULL }; const char * const subsystems[] = { "usb", NULL };

View File

@@ -8,5 +8,5 @@
#include "manager.h" #include "manager.h"
void udev_init (struct EG25Manager *data, toml_table_t *config); void udev_init (struct EG25Manager *data, toml_table_t *config[]);
void udev_destroy (struct EG25Manager *data); void udev_destroy (struct EG25Manager *data);