73 Commits
0.3.0 ... 0.4.2

Author SHA1 Message Date
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
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
c11f68f402 meson.build: release v0.4.0 2021-09-01 00:40:38 +02:00
Arnaud Ferraris
e6df81778e Merge branch 'handle-sim-unlock' into 'master'
gnss: handle locked SIM

See merge request mobian1/devices/eg25-manager!25
2021-08-12 10:17:02 +00:00
Dylan Van Assche
ef94492b30 gnss: handle locked SIM
ModemManager Location service is only available after SIM unlock
and network registration.
Track service separately to avoid an assert error and crashes.
GNSS assistance data is uploaded when the service becomes available.
2021-08-12 11:19:45 +02:00
Dylan Van Assche
75570e45da gpio: extend softsleep wake time
50ms is way to short for the Quectel firmware to react, increase to 200ms
2021-08-12 11:19:21 +02:00
Arnaud Ferraris
34c3b19f70 Merge branch 'modem-power-cfgs' into 'master'
config: synchronize with modem-power

See merge request mobian1/devices/eg25-manager!18
2021-08-11 13:43:06 +00:00
Dylan Van Assche
10ec1119fb at: fast/poweroff is only available in newer firmware versions
Do not retry the AT command of the fast/poweroff setting as it may not be supported by the firmware
2021-08-11 12:58:06 +02:00
Dylan Van Assche
898c0dc79c config: synchronize with modem-power
We cannot assume default factory values if other drivers change them
2021-08-11 12:56:42 +02:00
Arnaud Ferraris
6b3bc5660a Merge branch 'gps-support' into 'master'
gnss: GNSS assistance support

Closes mobian1/issues#253 and #2

See merge request mobian1/devices/eg25-manager!15
2021-08-10 20:58:26 +00:00
Arnaud Ferraris
128483354b udev: remove reset quirk
It seems to work just fine with MM's quick suspend/resume.
2021-08-10 22:27:07 +02:00
Dylan Van Assche
9cfe782f74 gnss: add GNSS assistance support
Automatically fetch the GNSS assistance data from the Web
to heavily reduce the time to acquire a GNSS lock by uploading
the assistance data to the modem.
This feature is optional and can be disabled in the configuration
as people may prefer to not download the assistance data from
the Quectel/Qualcomm servers.
Also configure the GNSS engine to optimize the performance
and power consumption.
2021-06-21 07:54:39 +02:00
Dylan Van Assche
5da7c88fc4 at: allow custom callbacks for AT command response processing 2021-06-21 07:53:44 +02:00
Dylan Van Assche
dac50e34eb at: log expected result before setting it to NULL
Otherwise the log contains NULL instead of the expected value, making it hard to debug
2021-06-21 07:53:39 +02:00
Dylan Van Assche
9646e0e8df at: make next_at_command, send_at_command, process_at_result, and append_at_command public methods
Allows other modules to send AT commands as well
2021-06-21 07:53:33 +02:00
Dylan Van Assche
9c4a934a51 at: g_free doesn't require NULL checking
From the docs: If mem is NULL it simply returns, so there is no need to check mem against NULL before calling this function.
2021-06-21 07:53:28 +02:00
Arnaud Ferraris
f2593b62b1 Merge branch 'soft-sleep-runtime' into 'master'
at: wake only when sending AT commands

Closes #12

See merge request mobian1/devices/eg25-manager!21
2021-06-17 09:35:39 +00:00
Arnaud Ferraris
73fb60e0ad Merge branch 'fix-parallel-build' into 'master'
Fix parallel build issue

Closes #14

See merge request mobian1/devices/eg25-manager!22
2021-06-17 09:34:01 +00:00
Natanael Copa
63ba5e2d60 Fix parallel build issue
Tell meson that the generated gdbofono headers are needed at compile
time.

fixes https://gitlab.com/mobian1/devices/eg25-manager/-/issues/14
2021-06-03 17:51:57 +02:00
Dylan Van Assche
e690e2a17d at: wake only when sending AT commands
Allow the modem to enter soft sleep when
we don't talk to the modem using AT commands.
This was already the case in suspend, but
not during runtime. By only waking the modem
from soft sleep when we need to send
an AT command, we can save some power.
2021-05-23 20:13:16 +02:00
Arnaud Ferraris
64145acbae Merge branch 'mm-cleanup' into 'master'
mm-iface: clean out modem_iface if mm disappears

See merge request mobian1/devices/eg25-manager!17
2021-04-15 15:48:02 +00:00
Bhushan Shah
705950bb39 mm-iface: clean out modem_iface if mm disappears
otherwise we will be stuck in state where restarting of mm will not
reset the modem_iface and it will loop through hard resetting modem
2021-04-15 11:08:06 +05:30
27 changed files with 1551 additions and 306 deletions

View File

@@ -0,0 +1,21 @@
[Unit]
Description=Quectel EG25 modem
Before=ModemManager.service
[Service]
Type=simple
ExecStart=@bindir@/eg25manager
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.1.toml',
'pine64,pinephone-1.2.toml',
'pine64,pinephone-pro.toml',
]
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

@@ -13,11 +13,12 @@ poweron_delay = 100000
#recovery_timeout = 9
[gpio]
dtr = 358
pwrkey = 35
reset = 68
apready = 231
disable = 232
chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
dtr = { chip = 1, line = 6 }
pwrkey = { chip = 0, line = 35 }
reset = { chip = 0, line = 68 }
apready = { chip = 0, line = 231 }
disable = { chip = 0, line = 232 }
[at]
uart = "/dev/ttyS2"
@@ -34,22 +35,66 @@ configure = [
# match, the command is then executed with value `expect` in
# order to set the parameter to the configured value (optional)
# A command can have `expect` OR `value` configured, but it shouldn't have both
# Print software version
{ cmd = "QGMR" },
# Configure audio
{ cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" },
# RI signaling using physical RI pin
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
# Enable VoLTE support
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
# Disable APREADY for PP 1.0 because pin is not connected
{ cmd = "QCFG", subcmd = "apready", expect = "0,0,500" },
# URC configuration for PP 1.0 (APREADY pin not connected):
# * RING URC: extend pulse length
# * Incoming SMS URC: extend pulse length
# * Other URC: extend pulse length
# * Report URCs on all ports (serial and USB) for FOSS firmware
# * Delay reporting of URCs
# * Configure URC pin to UART Ring Indicator
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",2000,1000,5000,\"off\",1" },
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000" },
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" },
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000,1" },
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
{ cmd = "QGPS", value = "1" },
# 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 = [
{ cmd = "QGPSEND" },
]
resume = [
{ cmd = "QGPS", value = "1" }
]
reset = [ { cmd = "CFUN", value = "1,1" } ]
[gnss]
enabled = true
url = "https://xtrapath4.izatcloud.net"
file = "xtra2.bin"

View File

@@ -13,11 +13,12 @@ poweron_delay = 100000
#recovery_timeout = 9
[gpio]
dtr = 358
pwrkey = 35
reset = 68
apready = 231
disable = 232
chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
dtr = { chip = 1, line = 6 }
pwrkey = { chip = 0, line = 35 }
reset = { chip = 0, line = 68 }
apready = { chip = 0, line = 231 }
disable = { chip = 0, line = 232 }
[at]
uart = "/dev/ttyS2"
@@ -34,22 +35,66 @@ configure = [
# match, the command is then executed with value `expect` in
# order to set the parameter to the configured value (optional)
# A command can have `expect` OR `value` configured, but it shouldn't have both
# Print software version
{ cmd = "QGMR" },
# Configure audio
{ cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" },
# RI signaling using physical RI pin
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
# Enable VoLTE support
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
# Disable APREADY for PP 1.1 because pin is not connected
{ cmd = "QCFG", subcmd = "apready", expect = "0,0,500" },
# URC configuration for PP 1.1 (APREADY pin not connected):
# * RING URC: extend pulse length
# * Incoming SMS URC: extend pulse length
# * Other URC: extend pulse length
# * Report URCs on all ports (serial and USB) for FOSS firmware
# * Delay reporting of URCs
# * Configure URC pin to UART Ring Indicator
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",2000,1000,5000,\"off\",1" },
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000" },
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" },
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000,1" },
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
{ cmd = "QGPS", value = "1" },
# 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 = [
{ cmd = "QGPSEND" },
]
resume = [
{ cmd = "QGPS", value = "1" }
]
reset = [ { cmd = "CFUN", value = "1,1" } ]
[gnss]
enabled = true
url = "https://xtrapath4.izatcloud.net"
file = "xtra2.bin"

View File

@@ -9,12 +9,13 @@ poweron_delay = 100000
#recovery_timeout = 9
[gpio]
dtr = 34
pwrkey = 35
reset = 68
apready = 231
disable = 232
status = 233
chips = [ "1c20800.pinctrl" ]
dtr = { chip = 0, line = 34 }
pwrkey = { chip = 0, line = 35 }
reset = { chip = 0, line = 68 }
apready = { chip = 0, line = 231 }
disable = { chip = 0, line = 232 }
status = { chip = 0, line = 233 }
[at]
uart = "/dev/ttyS2"
@@ -31,19 +32,66 @@ configure = [
# match, the command is then executed with value `expect` in
# order to set the parameter to the configured value (optional)
# A command can have `expect` OR `value` configured, but it shouldn't have both
# Print software version
{ cmd = "QGMR" },
# Configure audio
{ cmd = "QDAI", expect = "1,1,0,1,0,0,1,1" },
# RI signaling using physical RI pin
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
# Enable VoLTE support
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
# Enable APREADY for PP 1.2
{ cmd = "QCFG", subcmd = "apready", expect = "1,0,500" },
# URC configuration for PP 1.2 (APREADY pin connected):
# * RING URC: normal pulse length
# * Incoming SMS URC: default pulse length
# * Other URC: default length
# * Report URCs on all ports (serial and USB) for FOSS firmware
# * Reporting of URCs without any delay
# * Configure URC pin to UART Ring Indicator
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",120,1000,5000,\"off\",1" },
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",120,1" },
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
{ cmd = "QCFG", subcmd = "urc/delay", expect = "0" },
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
{ cmd = "QGPS", value = "1" },
# 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 = [
{ cmd = "QGPSEND" },
]
resume = [
{ cmd = "QGPS", value = "1" }
]
reset = [ { cmd = "CFUN", value = "1,1" } ]
[gnss]
enabled = true
url = "https://xtrapath4.izatcloud.net"
file = "xtra2.bin"

View File

@@ -0,0 +1,97 @@
[manager]
# 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,0,1,1" },
# RI signaling using physical RI pin
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
# Enable VoLTE support
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
# Enable APREADY for PP 1.2
{ cmd = "QCFG", subcmd = "apready", expect = "1,0,500" },
# URC configuration for PP 1.2 (APREADY pin connected):
# * RING URC: normal pulse length
# * Incoming SMS URC: default pulse length
# * Other URC: default length
# * Report URCs on all ports (serial and USB) for FOSS firmware
# * Reporting of URCs without any delay
# * Configure URC pin to UART Ring Indicator
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",120,1000,5000,\"off\",1" },
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",120,1" },
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
{ cmd = "QCFG", subcmd = "urc/delay", expect = "0" },
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
# Allow sleeping for power saving
{ cmd = "QSCLK", value = "1" },
# GNSS configuration:
# * Enable A-GPS data upload support (XTRA)
# * 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"

View File

@@ -8,7 +8,7 @@
project (
'eg25-manager',
'c',
version : '0.3.0',
version : '0.4.2',
license : 'GPLv3+',
meson_version : '>= 0.50.0',
default_options :
@@ -28,6 +28,8 @@ datadir = get_option('datadir')
sysconfdir = get_option('sysconfdir')
bindir = join_paths(prefix, get_option('bindir'))
udevrulesdir = join_paths(prefix, 'lib/udev/rules.d')
systemddir = join_paths(prefix, 'lib/systemd')
systemdsystemdir = join_paths(systemddir, 'system')
if datadir.startswith('/')
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_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)
if mmglib_dep.found()
@@ -58,6 +61,7 @@ mgr_deps = [
dependency('gudev-1.0'),
dependency('libgpiod'),
dependency('libusb-1.0'),
dependency('libcurl'),
mmglib_dep,
]

312
src/at.c
View File

@@ -5,8 +5,12 @@
*/
#include "at.h"
#include "config.h"
#include "suspend.h"
#include "gpio.h"
#include "gnss.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
@@ -16,14 +20,6 @@
#include <glib-unix.h>
struct AtCommand {
char *cmd;
char *subcmd;
char *value;
char *expected;
int retries;
};
static GArray *configure_commands = NULL;
static GArray *suspend_commands = NULL;
static GArray *resume_commands = NULL;
@@ -52,70 +48,118 @@ static int configure_serial(const char *tty)
return fd;
}
static gboolean send_at_command(struct EG25Manager *manager)
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)
{
char command[256];
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) {
/* Wake up the modem from soft sleep before sending an AT command */
gpio_sequence_wake(manager);
/* Send AT command */
if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL)
len = sprintf(command, "AT+%s\r\n", at_cmd->cmd);
len = snprintf(command, sizeof(command), "AT+%s\r\n", at_cmd->cmd);
else if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
len = sprintf(command, "AT+%s?\r\n", at_cmd->cmd);
len = snprintf(command, sizeof(command), "AT+%s?\r\n", at_cmd->cmd);
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)
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)
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);
ret = write(manager->at_fd, command, len);
if (ret < len)
g_warning("Couldn't write full AT command: wrote %d/%d bytes", ret, len);
g_message("Sending command: %s", g_strstrip(command));
} else if (manager->modem_state < EG25_STATE_CONFIGURED) {
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)
modem_update_state(manager, modem_state);
else
manager->modem_state = EG25_STATE_CONFIGURED;
#endif
} else {
manager->modem_state = EG25_STATE_CONFIGURED;
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;
do {
ret = write(manager->at_fd, &command[pos], len);
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 {
/* Allow the modem to enter soft sleep again when we sent the AT command*/
gpio_sequence_sleep(manager);
if (manager->modem_state < EG25_STATE_CONFIGURED) {
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
#ifdef HAVE_MMGLIB
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
} else {
manager->modem_state = EG25_STATE_CONFIGURED;
}
} else if (manager->modem_state == EG25_STATE_SUSPENDING) {
modem_suspend_post(manager);
} else if (manager->modem_state == EG25_STATE_RESETTING) {
manager->modem_state = EG25_STATE_POWERED;
}
} else if (manager->modem_state == EG25_STATE_SUSPENDING) {
modem_suspend_post(manager);
} else if (manager->modem_state == EG25_STATE_RESETTING) {
manager->modem_state = EG25_STATE_POWERED;
}
return FALSE;
}
static void next_at_command(struct EG25Manager *manager)
void at_next_command(struct EG25Manager *manager)
{
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
if (at_cmd->cmd)
g_free(at_cmd->cmd);
if (at_cmd->subcmd)
g_free(at_cmd->subcmd);
if (at_cmd->value)
g_free(at_cmd->value);
if (at_cmd->expected)
g_free(at_cmd->expected);
g_free(at_cmd);
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
send_at_command(manager);
at_free_command(at_cmd, manager);
at_send_command(manager);
}
static void retry_at_command(struct EG25Manager *manager)
@@ -128,13 +172,14 @@ static void retry_at_command(struct EG25Manager *manager)
at_cmd->retries++;
if (at_cmd->retries > 3) {
g_critical("Command %s retried %d times, aborting...", at_cmd->cmd, at_cmd->retries);
next_at_command(manager);
at_next_command(manager);
} else {
g_timeout_add(500, G_SOURCE_FUNC(send_at_command), manager);
g_timeout_add(500, G_SOURCE_FUNC(at_send_command), manager);
}
}
static void process_at_result(struct EG25Manager *manager, char *response)
void at_process_result(struct EG25Manager *manager,
const char *response)
{
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
@@ -142,23 +187,25 @@ static void process_at_result(struct EG25Manager *manager, char *response)
return;
if (at_cmd->expected && !strstr(response, at_cmd->expected)) {
if (at_cmd->value)
g_free(at_cmd->value);
g_free(at_cmd->value);
at_cmd->value = at_cmd->expected;
at_cmd->expected = NULL;
g_message("Got a different result than expected, changing value...");
g_message("\t%s\n\t%s", at_cmd->expected, response);
send_at_command(manager);
g_message("Expected: [%s]\nResponse: [%s]", at_cmd->expected, response);
at_cmd->expected = NULL;
at_send_command(manager);
} else {
next_at_command(manager);
at_next_command(manager);
}
}
static int append_at_command(struct EG25Manager *manager,
const char *cmd,
const char *subcmd,
const char *value,
const char *expected)
int at_append_command(struct EG25Manager *manager,
const char *cmd,
const char *subcmd,
const char *value,
const char *expected,
void (*callback)
(struct EG25Manager *manager,
const char *response))
{
struct AtCommand *at_cmd = calloc(1, sizeof(struct AtCommand));
@@ -172,6 +219,8 @@ static int append_at_command(struct EG25Manager *manager,
at_cmd->value = g_strdup(value);
if (expected)
at_cmd->expected = g_strdup(expected);
if (callback)
at_cmd->callback = callback;
manager->at_cmds = g_list_append(manager->at_cmds, at_cmd);
@@ -196,7 +245,14 @@ static gboolean modem_response(gint fd,
*/
do {
ret = read(fd, tmp, sizeof(tmp));
if (ret > 0) {
/* If we're going to overflow truncate the data we read to fit */
if (pos + ret >= sizeof(response)) {
g_critical("AT response buffer full, truncating");
ret = sizeof(response) - (pos + 1);
}
memcpy(&response[pos], tmp, ret);
pos += ret;
usleep(10000);
@@ -204,24 +260,53 @@ static gboolean modem_response(gint fd,
} while (ret > 0 && pos < (sizeof(response) - 1));
if (pos > 0) {
g_autofree gchar *escaped = NULL;
response[pos] = 0;
g_strstrip(response);
if (strlen(response) == 0)
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
* it is ready to receive AT commands.
*/
if (strcmp(response, "RDY") == 0) {
suspend_inhibit(manager, TRUE, TRUE);
manager->modem_state = EG25_STATE_STARTED;
}
else if (strstr(response, "ERROR"))
/*
* Search for 'QGPSURC: "xtradataexpire"' in response to detect
* if GNSS assistance data must be re-uploaded.
* If that's the case, check if no AT commands are being processed
* to avoid deadlocks and start upload.
*/
else if (strstr(response, "QGPSURC: \"xtradataexpire\"") && manager->at_cmds == NULL)
gnss_upload_assistance_data(manager);
/*
* AT command failed, retry.
* QCFG="fast/poweroff" configuration is only available in
* newer firmware versions, skip retrying that specific AT command.
*/
else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff"))
retry_at_command(manager);
else if (strstr(response, "OK"))
process_at_result(manager, response);
/*
* Successful AT responses contain 'OK', except for AT+QFUPL which also
* 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") || strstr(response, "QFUPL")) {
if (manager->at_callback != NULL)
manager->at_callback(manager, response);
else
g_warning("AT command successful but no callback registered");
}
/* Not a recognized response, try running next command, just in case */
else
// Not a recognized response, try running next command, just in case
next_at_command(manager);
at_next_command(manager);
}
return TRUE;
@@ -243,67 +328,61 @@ static void parse_commands_list(toml_array_t *array, GArray **cmds)
continue;
value = toml_string_in(table, "cmd");
if (value.ok) {
cmd->cmd = g_strdup(value.u.s);
free(value.u.s);
}
if (value.ok)
cmd->cmd = value.u.s;
value = toml_string_in(table, "subcmd");
if (value.ok) {
cmd->subcmd = g_strdup(value.u.s);
free(value.u.s);
}
if (value.ok)
cmd->subcmd = value.u.s;
value = toml_string_in(table, "value");
if (value.ok) {
cmd->value = g_strdup(value.u.s);
free(value.u.s);
}
if (value.ok)
cmd->value = value.u.s;
value = toml_string_in(table, "expect");
if (value.ok) {
cmd->expected = g_strdup(value.u.s);
free(value.u.s);
}
if (value.ok)
cmd->expected = 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_datum_t uart_port;
toml_array_t *commands = NULL;
gchar *uart_port = NULL;
toml_table_t *at_config[EG25_CONFIG_COUNT];
uart_port = toml_string_in(config, "uart");
if (!uart_port.ok)
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
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");
manager->at_fd = configure_serial(uart_port.u.s);
manager->at_fd = configure_serial(uart_port);
if (manager->at_fd < 0) {
g_critical("Unable to configure %s", uart_port.u.s);
free(uart_port.u.s);
g_critical("Unable to configure %s", uart_port);
g_free(uart_port);
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);
commands = toml_array_in(config, "configure");
if (!commands)
if (!config_get_array(at_config, "configure", &commands))
g_error("Configuration file lacks initial AT commands list");
parse_commands_list(commands, &configure_commands);
commands = toml_array_in(config, "suspend");
if (!commands)
if (!config_get_array(at_config, "suspend", &commands))
g_error("Configuration file lacks suspend AT commands list");
parse_commands_list(commands, &suspend_commands);
commands = toml_array_in(config, "resume");
if (!commands)
if (!config_get_array(at_config, "resume", &commands))
g_error("Configuration file lacks resume AT commands list");
parse_commands_list(commands, &resume_commands);
commands = toml_array_in(config, "reset");
if (!commands)
if (!config_get_array(at_config, "reset", &commands))
g_error("Configuration file lacks reset AT commands list");
parse_commands_list(commands, &reset_commands);
@@ -324,36 +403,43 @@ void at_destroy(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++) {
struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i);
append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
send_at_command(manager);
at_send_command(manager);
}
void at_sequence_suspend(struct EG25Manager *manager)
{
for (guint i = 0; i < suspend_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(suspend_commands, struct AtCommand, i);
append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
send_at_command(manager);
at_send_command(manager);
}
void at_sequence_resume(struct EG25Manager *manager)
{
for (guint i = 0; i < resume_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(resume_commands, struct AtCommand, i);
append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
send_at_command(manager);
at_send_command(manager);
}
void at_sequence_reset(struct EG25Manager *manager)
{
for (guint i = 0; i < reset_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(reset_commands, struct AtCommand, i);
append_at_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
send_at_command(manager);
at_send_command(manager);
}

View File

@@ -8,10 +8,32 @@
#include "manager.h"
int at_init(struct EG25Manager *data, toml_table_t *config);
void at_destroy(struct EG25Manager *data);
typedef struct AtCommand {
char *cmd;
char *subcmd;
char *value;
char *expected;
void (*callback)(struct EG25Manager *manager, const char *response);
int retries;
} AtCommand;
void at_sequence_configure(struct EG25Manager *data);
void at_sequence_suspend(struct EG25Manager *data);
void at_sequence_resume(struct EG25Manager *data);
void at_sequence_reset(struct EG25Manager *data);
int at_init(struct EG25Manager *manager, toml_table_t *config[]);
void at_destroy(struct EG25Manager *manager);
void at_process_result(struct EG25Manager *manager,
const char *response);
void at_next_command(struct EG25Manager *manager);
gboolean at_send_command(struct EG25Manager *manager);
int at_append_command(struct EG25Manager *manager,
const char *cmd,
const char *subcmd,
const char *value,
const char *expected,
void (*callback)
(struct EG25Manager *manager,
const char *response));
void at_sequence_configure(struct EG25Manager *manager);
void at_sequence_suspend(struct EG25Manager *manager);
void at_sequence_resume(struct EG25Manager *manager);
void at_sequence_reset(struct EG25Manager *manager);

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);

515
src/gnss.c Normal file
View File

@@ -0,0 +1,515 @@
/*
* Copyright (C) 2021 Dylan Van Assche <me@dylanvanassche.be>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include "gnss.h"
#include "manager.h"
#include "at.h"
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <errno.h>
#define BUFFER_SIZE 256
#define UPLOAD_DELAY_US 25000
#define UPLOAD_TIMEOUT_S 10
#define RESCHEDULE_IN_SECS 30
static void gnss_step(struct EG25Manager *manager);
gboolean gnss_upload_assistance_data(struct EG25Manager *manager)
{
if (!manager->gnss_assistance_enabled) {
g_message("GNSS assistance is disabled!");
return FALSE;
}
if (manager->gnss_assistance_step < EG25_GNSS_STEP_LAST) {
g_warning("GNSS assistance data upload already in process (%d/%d)",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
return 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 */
if(manager->modem_iface == MODEM_IFACE_MODEMMANAGER && !manager->mm_location) {
g_message ("Rescheduling upload since Location interface is not available, in %ds",
RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return TRUE;
}
#endif
manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST;
gnss_step(manager);
return FALSE;
}
void gnss_init(struct EG25Manager *manager, toml_table_t *config[])
{
toml_table_t *gnss_config[EG25_CONFIG_COUNT];
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
* if you want in the configuration file.
* In case the configuration is missing, we assume GNSS assistance
* to be disabled.
*/
config_get_bool(gnss_config, "enabled", &manager->gnss_assistance_enabled);
if (!manager->gnss_assistance_enabled) {
g_message("GNSS assistance is disabled!");
return;
}
if (!config_get_string(gnss_config, "url", &manager->gnss_assistance_url))
g_error("GNSS assistance server URL is missing from config file");
if (!config_get_string(gnss_config, "file", &manager->gnss_assistance_file))
g_error("GNSS assistance file name is missing from config file");
/* Create temporary file to store assistance data */
manager->gnss_assistance_fd = g_file_open_tmp(NULL, NULL, &error);
if (error != NULL)
g_error ("Unable to create temporary file: %s", error->message);
/* Initialize state and schedule upload */
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
G_SOURCE_FUNC(gnss_upload_assistance_data), manager);
}
void gnss_destroy(struct EG25Manager *manager)
{
g_free(manager->gnss_assistance_url);
g_free(manager->gnss_assistance_file);
close(manager->gnss_assistance_fd);
}
/******************************************************************************/
#ifdef HAVE_MMGLIB
static void disable_mm_gnss(struct EG25Manager *manager)
{
MMModemLocationSource sources;
gboolean signals_location;
g_autoptr (GError) error = NULL;
sources = mm_modem_location_get_enabled(manager->mm_location);
signals_location = mm_modem_location_signals_location(manager->mm_location);
manager->gnss_sources = EG25_GNSS_SOURCE_NONE;
/* Save GNSS engine state */
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)
manager->gnss_sources |= EG25_GNSS_SOURCE_NMEA;
else
manager->gnss_sources &= ~EG25_GNSS_SOURCE_NMEA;
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)
manager->gnss_sources |= EG25_GNSS_SOURCE_RAW;
else
manager->gnss_sources &= ~EG25_GNSS_SOURCE_RAW;
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)
manager->gnss_sources |= EG25_GNSS_SOURCE_UNMANAGED;
else
manager->gnss_sources &= ~EG25_GNSS_SOURCE_UNMANAGED;
/* Disable GNSS engine */
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_RAW;
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
mm_modem_location_setup_sync(manager->mm_location, sources,
signals_location, NULL, &error);
if (error != NULL) {
g_warning("Unable to disable GNSS engine through ModemManager: %s",
error->message);
}
}
#endif
static void disable_at_gnss_cb(struct EG25Manager *manager,
const char *response)
{
/* Clear QGPSEND AT command and process next */
at_next_command(manager);
/* Go to the next step */
manager->gnss_assistance_step++;
gnss_step(manager);
}
static void state_at_gnss_cb(struct EG25Manager *manager, const char *response)
{
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
/* Parse GNSS engine status and disable it if needed */
if (strstr(response, "QGPS: 1")) {
manager->gnss_sources |= EG25_GNSS_SOURCE_QGPS;
g_free(at_cmd->value);
g_free(at_cmd->cmd);
at_cmd->expected = NULL;
at_cmd->subcmd = NULL;
at_cmd->value = NULL;
at_cmd->cmd = g_strdup("QGPSEND");
at_cmd->callback = disable_at_gnss_cb;
at_send_command(manager);
}
/* QGPS is already disabled, move on to next step */
else {
at_next_command(manager);
manager->gnss_sources &= ~EG25_GNSS_SOURCE_QGPS;
manager->gnss_assistance_step++;
gnss_step(manager);
}
}
static void state_at_gnss(struct EG25Manager *manager)
{
/* Asynchronously send AT command to query status of GNSS engine */
at_append_command(manager, "QGPS?", NULL, NULL, NULL, state_at_gnss_cb);
at_send_command(manager);
}
/******************************************************************************/
static void fetch_assistance_data(struct EG25Manager *manager)
{
CURLcode response;
curl_off_t downloaded;
CURL *curl = NULL;
g_autofree gchar *url = NULL;
FILE *tmp_file = NULL;
gchar errbuf[CURL_ERROR_SIZE];
errbuf[0] = 0;
/* Fetch assistance data with curl */
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, "/",
manager->gnss_assistance_file, NULL);
curl = curl_easy_init();
if (!curl) {
g_critical("Unable to initialize curl");
goto bail;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
response = curl_easy_perform(curl);
if (response != CURLE_OK) {
g_warning("Unable to fetch GNSS assistance data from %s: %s",
url, strlen(errbuf) ? errbuf : curl_easy_strerror(response));
goto bail;
}
response = curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &downloaded);
if (response) {
g_critical("Unable to get number of downloaded bytes from curl");
goto bail;
} else if (downloaded <= 0) {
g_warning("Downloaded empty assistance data file");
goto bail;
}
g_message("Fetching GNSS assistance data from %s was successful", url);
fflush(tmp_file);
curl_easy_cleanup(curl);
/* Go to the next step */
manager->gnss_assistance_step++;
gnss_step(manager);
return;
bail:
if (curl != NULL)
curl_easy_cleanup(curl);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
}
/******************************************************************************/
static void init_assistance_data_upload_ready(struct EG25Manager *manager,
const char *response)
{
/* Search for 'CONNECT' in response to start upload */
if (strstr(response, "CONNECT")) {
g_message("Modem ready for GNSS assistance data upload");
manager->gnss_assistance_step++;
gnss_step(manager);
} else if (strstr(response, "QFUPL")) {
/* Clear QFUPL AT command and process next */
at_next_command(manager);
manager->gnss_assistance_step++;
gnss_step(manager);
}
}
static void init_assistance_data_upload_start(struct EG25Manager *manager,
const char *response)
{
gchar value[BUFFER_SIZE];
off_t size;
/* Process AT response */
at_process_result(manager, response);
/* Get file size in bytes */
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);
/* Start upload */
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld,%d",
manager->gnss_assistance_file, size, UPLOAD_TIMEOUT_S);
g_message("Initiate GNSS assistance data upload: %s", value);
at_append_command(manager, "QFUPL", NULL, value, NULL,
init_assistance_data_upload_ready);
at_send_command(manager);
}
static void init_assistance_data_upload(struct EG25Manager *manager)
{
/*
* Delete all previous GNSS assistance data files in RAM
* and start uploading the latest one to RAM.
*/
at_append_command(manager, "QFDEL", NULL, "\"RAM:*\"\r\n",
NULL, init_assistance_data_upload_start);
at_send_command(manager);
}
static void upload_assistance_data(struct EG25Manager *manager)
{
gint error;
glong written_total = 0;
gint ret;
struct stat sb;
if (fstat(manager->gnss_assistance_fd, &sb) != 0) {
g_critical("gnss: unable to stat xtra data file: %s", g_strerror(errno));
/* Make sure the upload times out and the modem goes back to AT command mode */
sleep(UPLOAD_TIMEOUT_S + 1);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return;
}
do {
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 */
if (!error) {
g_message("Successfully uploaded %ld bytes to the modem", written_total);
} else {
g_critical("Unable to upload xtra data: %s", g_strerror(error));
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
}
}
static void finish_assistance_data_upload_cb(struct EG25Manager *manager,
const char *response)
{
/* Process response */
at_process_result(manager, response);
g_message("GNSS assistance data upload finished");
/* Go to the next step */
manager->gnss_assistance_step++;
gnss_step(manager);
}
static void finish_assistance_data_upload(struct EG25Manager *manager)
{
gchar value[BUFFER_SIZE];
GDateTime *datetime;
gchar *timestring;
/* Configure GNSS assistance clock to current system time (UTC) */
datetime = g_date_time_new_now_utc();
timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"");
g_message("Setting GNSS assistance UTC clock to: %s", timestring);
at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL,
at_process_result);
/* Configure GNSS engine to use uploaded GNSS assistance data */
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"",
manager->gnss_assistance_file);
g_message("Setting GNSS assistance file to: %s", value);
at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL,
finish_assistance_data_upload_cb);
at_send_command(manager);
}
/******************************************************************************/
#ifdef HAVE_MMGLIB
static void enable_mm_gnss(struct EG25Manager *manager)
{
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)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
if (manager->gnss_sources & EG25_GNSS_SOURCE_NMEA)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
mm_modem_location_setup_sync(manager->mm_location, sources,
signal_location, NULL, &error);
if (error != NULL)
g_warning("Unable to enable GNSS engine through ModemManager: %s",
error->message);
}
#endif
static void enable_at_gnss_cb(struct EG25Manager *manager, const char *response)
{
manager->gnss_assistance_step++;
gnss_step(manager);
}
static void enable_at_gnss(struct EG25Manager *manager)
{
if (manager->gnss_sources & EG25_GNSS_SOURCE_QGPS) {
at_append_command(manager, "QGPS", NULL, "1", NULL,
enable_at_gnss_cb);
at_send_command(manager);
return;
}
manager->gnss_assistance_step++;
gnss_step(manager);
}
/******************************************************************************/
void gnss_step(struct EG25Manager *manager)
{
switch(manager->gnss_assistance_step) {
case EG25_GNSS_STEP_FIRST:
manager->gnss_assistance_step++;
g_message("GNSS assistance upload started...");
/* fall-through */
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
case EG25_GNSS_STEP_MM_GNSS_DISABLE:
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
g_message("GNSS assistance upload step (%d/%d): "
"disabling GNSS engine through ModemManager",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
disable_mm_gnss(manager);
}
manager->gnss_assistance_step++;
/* fall-through */
#endif
case EG25_GNSS_STEP_AT_GNSS_DISABLE:
g_message("GNSS assistance upload step (%d/%d): "
"disabling GNSS engine through AT+QGPS",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
state_at_gnss(manager);
break;
case EG25_GNSS_STEP_INIT_UPLOAD:
g_message("GNSS assistance upload step (%d/%d): initiating upload",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
init_assistance_data_upload(manager);
break;
case EG25_GNSS_STEP_UPLOAD:
g_message("GNSS assistance upload step (%d/%d): "
"uploading assistance data",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
upload_assistance_data(manager);
break;
case EG25_GNSS_STEP_FINISH_UPLOAD:
g_message("GNSS assistance upload step (%d/%d): finishing upload",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
finish_assistance_data_upload(manager);
break;
#ifdef HAVE_MMGLIB
case EG25_GNSS_STEP_MM_GNSS_ENABLE:
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
g_message("GNSS assistance upload step (%d/%d): "
"re-enabling GNSS through ModemManager",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
enable_mm_gnss(manager);
}
manager->gnss_assistance_step++;
/* fall-through */
#endif
case EG25_GNSS_STEP_AT_QGPS_ENABLE:
g_message("GNSS assistance upload step (%d/%d): "
"re-enabling GNSS through AT+QGPS",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
enable_at_gnss(manager);
break;
case EG25_GNSS_STEP_LAST:
g_message("GNSS assistance upload step (%d/%d): finished",
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
break;
}
}

17
src/gnss.h Normal file
View File

@@ -0,0 +1,17 @@
/*
* Copyright (C) 2021 Dylan Van Assche <me@dylanvanassche.be>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#include <time.h>
#include <unistd.h>
#include <curl/curl.h>
#include "manager.h"
void gnss_init(struct EG25Manager *manager, toml_table_t *config[]);
void gnss_destroy(struct EG25Manager *manager);
gboolean gnss_upload_assistance_data(struct EG25Manager *manager);

View File

@@ -4,15 +4,16 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include "gpio.h"
#include <unistd.h>
/* Those defines are used for legacy config files only */
#define GPIO_CHIP1_LABEL "1c20800.pinctrl"
#define GPIO_CHIP2_LABEL "1f02c00.pinctrl"
#define MAX_GPIOCHIP_LINES 352
#define GPIO_IDX_INVAL 0xffff
enum {
GPIO_OUT_DTR = 0,
GPIO_OUT_PWRKEY,
@@ -22,12 +23,23 @@ enum {
GPIO_OUT_COUNT
};
enum {
GPIO_IN_STATUS = 0,
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)
{
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 1);
@@ -52,7 +64,6 @@ int gpio_sequence_shutdown(struct EG25Manager *manager)
int gpio_sequence_suspend(struct EG25Manager *manager)
{
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_APREADY], 1);
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 1);
g_message("Executed suspend sequence");
@@ -62,99 +73,188 @@ int gpio_sequence_suspend(struct EG25Manager *manager)
int gpio_sequence_resume(struct EG25Manager *manager)
{
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_APREADY], 0);
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0);
g_message("Executed resume sequence");
return 0;
}
static guint get_config_gpio(toml_table_t *config, const char *id)
int gpio_sequence_wake(struct EG25Manager *manager)
{
toml_datum_t value = toml_int_in(config, id);
guint gpio;
if (gpiod_line_get_value(manager->gpio_out[GPIO_OUT_DTR])) {
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0);
if (!value.ok)
return GPIO_IDX_INVAL;
/* Give the modem 200ms to wake from soft sleep */
usleep(200000);
gpio = (guint)value.u.i;
g_message("Executed soft wake sequence");
}
return gpio;
return 0;
}
int gpio_init(struct EG25Manager *manager, toml_table_t *config)
int gpio_sequence_sleep(struct EG25Manager *manager)
{
int i, ret;
guint gpio_out_idx[GPIO_OUT_COUNT];
guint gpio_in_idx[GPIO_IN_COUNT];
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 1);
g_message("Executed soft sleep sequence");
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
if (!manager->gpiochip[0]) {
g_critical("Unable to open GPIO chip " GPIO_CHIP1_LABEL);
return 1;
return 0;
}
struct gpiod_line *gpio_get_output_line(struct EG25Manager *manager, int chip, int line)
{
struct gpiod_line *gpio_line;
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
if (!gpio_line)
return NULL;
if (gpiod_line_request_output(gpio_line, "eg25manager", 0) < 0) {
gpiod_line_release(gpio_line);
return NULL;
}
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
if (!manager->gpiochip[1]) {
g_critical("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
return 1;
return gpio_line;
}
struct gpiod_line *gpio_get_input_line(struct EG25Manager *manager, int chip, int line)
{
struct gpiod_line *gpio_line;
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
if (!gpio_line)
return NULL;
if (gpiod_line_request_input(gpio_line, "eg25manager") < 0) {
gpiod_line_release(gpio_line);
return NULL;
}
gpio_out_idx[GPIO_OUT_DTR] = get_config_gpio(config, "dtr");
gpio_out_idx[GPIO_OUT_PWRKEY] = get_config_gpio(config, "pwrkey");
gpio_out_idx[GPIO_OUT_RESET] = get_config_gpio(config, "reset");
gpio_out_idx[GPIO_OUT_APREADY] = get_config_gpio(config, "apready");
gpio_out_idx[GPIO_OUT_DISABLE] = get_config_gpio(config, "disable");
gpio_in_idx[GPIO_IN_STATUS] = get_config_gpio(config, "status");
return gpio_line;
}
for (i = 0; i < GPIO_OUT_COUNT; i++) {
guint offset, chipidx;
int gpio_init(struct EG25Manager *manager, toml_table_t *config[])
{
int i;
toml_table_t *gpio_config[EG25_CONFIG_COUNT];
if (gpio_out_idx[i] < MAX_GPIOCHIP_LINES) {
offset = gpio_out_idx[i];
chipidx = 0;
} else {
offset = gpio_out_idx[i] - MAX_GPIOCHIP_LINES;
chipidx = 1;
for (i = 0; i < EG25_CONFIG_COUNT; i++)
gpio_config[i] = config[i] ? toml_table_in(config[i], "gpio") : NULL;
if (!gpio_config[EG25_CONFIG_SYS])
g_error("Default config file lacks the 'gpio' section!");
/*
* The system config could have the `chips` key, but the user one
* could still use the old format! In order to avoid problems, we
* should use the new format only if:
* - there's no user config file
or
* - the user config file contains the `chips` key
* Otherwise we might start parsing the system config with the new
* format, but error out if user config overrides gpios using the
* old format
*/
if (!gpio_config[EG25_CONFIG_USER] || toml_array_in(gpio_config[EG25_CONFIG_USER], "chips"))
{
int numchips;
toml_array_t *chipslist = NULL;
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);
if (!manager->gpio_out[i]) {
g_error("Unable to get output GPIO %d", i);
return 1;
for (i = 0; i < GPIO_OUT_COUNT; i++) {
toml_table_t *table;
toml_datum_t chip, line;
if (!config_get_table(gpio_config, gpio_out_names[i], &table))
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
chip = toml_int_in(table, "chip");
if (!chip.ok || chip.u.i < 0 || chip.u.i > 2)
g_error("Wrong chip ID for output GPIO '%s'", gpio_out_names[i]);
line = toml_int_in(table, "line");
if (!line.ok || line.u.i < 0 || line.u.i > gpiod_chip_num_lines(manager->gpiochip[chip.u.i]))
g_error("Wrong line ID for output GPIO '%s'", gpio_out_names[i]);
manager->gpio_out[i] = gpio_get_output_line(manager, chip.u.i, line.u.i);
if (!manager->gpio_out[i])
g_error("Unable to get output GPIO %d", i);
}
ret = gpiod_line_request_output(manager->gpio_out[i], "eg25manager", 0);
if (ret < 0) {
g_error("Unable to request output GPIO %d", i);
return 1;
for (i = 0; i < GPIO_IN_COUNT; i++) {
toml_table_t *table;
toml_datum_t chip, line;
if (!config_get_table(gpio_config, gpio_in_names[i], &table))
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++) {
guint offset, chipidx;
/* Legacy config file, only used on the OG PinePhone */
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
if (!manager->gpiochip[0])
g_error("Unable to open GPIO chip " GPIO_CHIP1_LABEL);
if (gpio_in_idx[i] == GPIO_IDX_INVAL)
continue;
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
if (!manager->gpiochip[1])
g_error("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
if (gpio_in_idx[i] < MAX_GPIOCHIP_LINES) {
offset = gpio_in_idx[i];
chipidx = 0;
} else {
offset = gpio_in_idx[i] - MAX_GPIOCHIP_LINES;
chipidx = 1;
for (i = 0; i < GPIO_OUT_COUNT; i++) {
if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx))
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
if (gpio_idx < MAX_GPIOCHIP_LINES) {
offset = gpio_idx;
chipidx = 0;
} else {
offset = gpio_idx - MAX_GPIOCHIP_LINES;
chipidx = 1;
}
manager->gpio_out[i] = gpio_get_input_line(manager, chipidx, offset);
if (!manager->gpio_out[i])
g_error("Unable to get output GPIO %d", i);
}
manager->gpio_in[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
if (!manager->gpio_in[i]) {
g_warning("Unable to get input GPIO %d", i);
continue;
}
for (i = 0; i < GPIO_IN_COUNT; i++) {
if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx))
continue;
ret = gpiod_line_request_input(manager->gpio_in[i], "eg25manager");
if (ret < 0) {
g_warning("Unable to request input GPIO %d", i);
manager->gpio_in[i] = NULL;
if (gpio_idx < MAX_GPIOCHIP_LINES) {
offset = gpio_idx;
chipidx = 0;
} else {
offset = gpio_idx - MAX_GPIOCHIP_LINES;
chipidx = 1;
}
manager->gpio_in[i] = gpio_get_input_line(manager, chipidx, offset);
if (!manager->gpio_in[i])
g_error("Unable to get input GPIO %d", i);
}
}

View File

@@ -8,12 +8,14 @@
#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);
int gpio_sequence_poweron(struct EG25Manager *state);
int gpio_sequence_shutdown(struct EG25Manager *state);
int gpio_sequence_suspend(struct EG25Manager *state);
int gpio_sequence_resume(struct EG25Manager *state);
int gpio_sequence_wake(struct EG25Manager *state);
int gpio_sequence_sleep(struct EG25Manager *state);
gboolean gpio_check_poweroff(struct EG25Manager *manager, gboolean keep_down);

View File

@@ -5,6 +5,7 @@
*/
#include "at.h"
#include "config.h"
#include "gpio.h"
#include "manager.h"
@@ -15,6 +16,7 @@
#include "ofono-iface.h"
#include "suspend.h"
#include "udev.h"
#include "gnss.h"
#include <fcntl.h>
#include <signal.h>
@@ -32,6 +34,12 @@
#define EG25_DATADIR "/usr/share/eg25-manager"
#endif
#ifndef EG25_VERSION
#define EG25_VERSION "0.0.0"
#endif
#define POWERON_DELAY_US 100000UL
static gboolean quit_app(struct EG25Manager *manager)
{
int i;
@@ -137,20 +145,24 @@ static gboolean modem_reset_done(struct EG25Manager* manager)
return FALSE;
}
void modem_reset(struct EG25Manager *manager)
gboolean modem_reset(struct EG25Manager *manager)
{
int fd, ret, len;
if (manager->reset_timer)
return;
if (manager->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)
return;
if (manager->modem_iface != MODEM_IFACE_MODEMMANAGER) {
g_message("modem_reset: not using ModemManager, bail out!");
return G_SOURCE_REMOVE;
}
if (manager->modem_recovery_timer) {
g_source_remove(manager->modem_recovery_timer);
@@ -158,37 +170,49 @@ void modem_reset(struct EG25Manager *manager)
}
if (!manager->modem_usb_id) {
g_warning("Unknown modem USB ID");
g_warning("Empty modem USB ID");
goto error;
}
g_message("Trying to reset modem with USB ID '%s'", manager->modem_usb_id);
len = strlen(manager->modem_usb_id);
manager->modem_state = EG25_STATE_RESETTING;
fd = open("/sys/bus/usb/drivers/usb/unbind", O_WRONLY);
if (fd < 0)
if (fd < 0) {
g_warning("Unable to open /sys/bus/usb/drivers/usb/unbind");
goto error;
}
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);
goto error;
}
close(fd);
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;
}
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);
goto error;
}
close(fd);
g_message("Successfully reset modem's USB connection");
/*
* 3s is long enough to make sure the modem has been bound back and
* short enough to ensure it hasn't been acquired by ModemManager
*/
manager->reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset_done), manager);
return;
return G_SOURCE_REMOVE;
error:
// Release blocking sleep inhibitor
@@ -198,8 +222,15 @@ error:
g_source_remove(manager->modem_boot_timer);
manager->modem_boot_timer = 0;
}
// Everything else failed, reboot the modem
g_message("USB reset failed, falling back to AT command");
at_sequence_reset(manager);
// Setup timer for making sure we don't queue other reset commands
manager->reset_timer = g_timeout_add_seconds(30, G_SOURCE_FUNC(modem_reset_done), manager);
return G_SOURCE_REMOVE;
}
void modem_suspend_pre(struct EG25Manager *manager)
@@ -224,7 +255,7 @@ void modem_resume_post(struct EG25Manager *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;
gchar *compatible;
@@ -248,28 +279,26 @@ static toml_table_t *parse_config_file(char *config_file)
} while (pos < len);
for (pos = 0; pos < compat->len; pos++) {
g_autofree gchar *filename = g_strdup_printf(EG25_CONFDIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
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) {
g_message("Opening config file: %s", filename);
f = fopen(filename, "r");
break;
}
}
if (!f) {
for (pos = 0; pos < compat->len; pos++) {
g_autofree gchar *filename = g_strdup_printf(EG25_DATADIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
if (access(filename, F_OK) == 0) {
g_message("Opening config file: %s", filename);
f = fopen(filename, "r");
break;
}
}
}
}
if (!f)
g_error("unable to find a suitable config file!");
if (!f) {
if (force_default)
g_error("unable to find a suitable config file!");
else
return NULL;
}
toml_config = toml_parse_file(f, error, sizeof(error));
if (!toml_config)
@@ -284,16 +313,18 @@ int main(int argc, char *argv[])
g_autoptr(GError) err = NULL;
struct EG25Manager manager;
gchar *config_file = NULL;
toml_table_t *toml_config;
toml_table_t *toml_manager;
toml_datum_t toml_value;
gboolean show_version = FALSE;
toml_table_t *toml_config[EG25_CONFIG_COUNT];
toml_table_t *manager_config[EG25_CONFIG_COUNT];
const GOptionEntry options[] = {
{ "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 }
};
memset(&manager, 0, sizeof(manager));
manager.at_fd = -1;
manager.poweron_delay = POWERON_DELAY_US;
manager.suspend_delay_fd = -1;
manager.suspend_block_fd = -1;
@@ -304,44 +335,53 @@ int main(int argc, char *argv[])
return 1;
}
manager.loop = g_main_loop_new(NULL, FALSE);
toml_config = parse_config_file(config_file);
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");
}
}
if (show_version) {
printf("eg25-manager version %s\n", EG25_VERSION);
return 0;
}
at_init(&manager, toml_table_in(toml_config, "at"));
gpio_init(&manager, toml_table_in(toml_config, "gpio"));
manager.loop = g_main_loop_new(NULL, FALSE);
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, "need_libusb", &manager.use_libusb);
config_get_uint(manager_config, "usb_vid", &manager.usb_vid);
config_get_uint(manager_config, "usb_pid", &manager.usb_pid);
config_get_uint(manager_config, "poweron_delay", &manager.poweron_delay);
at_init(&manager, toml_config);
gpio_init(&manager, toml_config);
#ifdef HAVE_MMGLIB
mm_iface_init(&manager, toml_table_in(toml_config, "mm-iface"));
mm_iface_init(&manager, toml_config);
#endif
ofono_iface_init(&manager);
suspend_init(&manager, toml_table_in(toml_config, "suspend"));
udev_init(&manager, toml_table_in(toml_config, "udev"));
ofono_iface_init(&manager, toml_config);
suspend_init(&manager, toml_config);
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);

View File

@@ -16,16 +16,42 @@
#include "toml.h"
typedef enum {
EG25_GNSS_STEP_FIRST = 0,
EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA,
#ifdef HAVE_MMGLIB
EG25_GNSS_STEP_MM_GNSS_DISABLE,
#endif
EG25_GNSS_STEP_AT_GNSS_DISABLE,
EG25_GNSS_STEP_INIT_UPLOAD,
EG25_GNSS_STEP_UPLOAD,
EG25_GNSS_STEP_FINISH_UPLOAD,
#ifdef HAVE_MMGLIB
EG25_GNSS_STEP_MM_GNSS_ENABLE,
#endif
EG25_GNSS_STEP_AT_QGPS_ENABLE,
EG25_GNSS_STEP_LAST,
} EG25GNSSStep;
typedef enum {
EG25_GNSS_SOURCE_NONE = 0,
EG25_GNSS_SOURCE_NMEA = 1 << 0,
EG25_GNSS_SOURCE_RAW = 1 << 1,
EG25_GNSS_SOURCE_UNMANAGED = 1 << 2,
EG25_GNSS_SOURCE_QGPS = 1 << 3,
} EG25GNSSSource;
enum EG25State {
EG25_STATE_INIT = 0,
EG25_STATE_POWERED, // Power-on sequence has been executed, but the modem isn't on yet
EG25_STATE_STARTED, // Modem has been started and declared itdata ready
EG25_STATE_ACQUIRED, // Modem has been probed by ModemManager
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_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_FINISHING
};
@@ -36,26 +62,41 @@ enum ModemIface {
MODEM_IFACE_OFONO
};
enum EG25Config {
EG25_CONFIG_SYS = 0,
EG25_CONFIG_USER,
EG25_CONFIG_COUNT
};
struct EG25Manager {
GMainLoop *loop;
guint reset_timer;
gboolean use_libusb;
guint usb_vid;
guint usb_pid;
gulong poweron_delay;
guint poweron_delay;
int at_fd;
guint at_source;
GList *at_cmds;
void (*at_callback)(struct EG25Manager *manager, const char *response);
enum EG25State modem_state;
gchar *modem_usb_id;
gboolean gnss_assistance_enabled;
EG25GNSSSource gnss_sources;
EG25GNSSStep gnss_assistance_step;
gint gnss_assistance_fd;
gchar *gnss_assistance_url;
gchar *gnss_assistance_file;
enum ModemIface modem_iface;
guint mm_watch;
#ifdef HAVE_MMGLIB
MMManager *mm_manager;
MMModem *mm_modem;
MMModemLocation *mm_location;
#endif
guint ofono_watch;
GDBOManager *ofono_manager;
@@ -78,7 +119,7 @@ struct EG25Manager {
};
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_post(struct EG25Manager *data);
void modem_resume_pre(struct EG25Manager *data);

View File

@@ -9,12 +9,15 @@ subdir('libgdbofono')
src = [
'at.c', 'at.h',
'config.c', 'config.h',
'gpio.c', 'gpio.h',
'manager.c', 'manager.h',
'ofono-iface.c', 'ofono-iface.h',
'suspend.c', 'suspend.h',
'toml.c', 'toml.h',
'udev.c', 'udev.h',
'gnss.c', 'gnss.h',
gdbofono_headers,
]
if mmglib_dep.found()

View File

@@ -32,7 +32,7 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
g_assert(MM_IS_OBJECT (object));
manager->mm_modem = mm_object_get_modem(MM_OBJECT(object));
g_assert(manager->mm_modem != NULL);
g_assert_nonnull(manager->mm_modem);
if (manager->modem_state == EG25_STATE_RESUMING) {
if (manager->modem_recovery_timer) {
@@ -60,6 +60,16 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
g_signal_connect(gdbus_modem, "state-changed", G_CALLBACK(state_changed_cb), manager);
}
static void add_modem_location(struct EG25Manager *manager, GDBusObject *object)
{
const gchar *path;
path = g_dbus_object_get_object_path(object);
g_message("Adding new modem with location capabilities `%s'", path);
manager->mm_location = mm_object_get_modem_location(MM_OBJECT(object));
g_assert_nonnull(manager->mm_location);
}
static void interface_added_cb (struct EG25Manager *manager,
GDBusObject *object,
GDBusInterface *interface)
@@ -72,6 +82,9 @@ static void interface_added_cb (struct EG25Manager *manager,
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM) == 0)
add_modem(manager, object);
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM_LOCATION) == 0)
add_modem_location(manager, object);
}
@@ -183,6 +196,9 @@ static void mm_iface_clean(struct EG25Manager *manager)
g_free(manager->modem_usb_id);
manager->modem_usb_id = NULL;
}
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
manager->modem_iface = MODEM_IFACE_NONE;
}
}
static void mm_vanished_cb(GDBusConnection *connection,
@@ -193,7 +209,7 @@ static void mm_vanished_cb(GDBusConnection *connection,
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,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,

View File

@@ -8,5 +8,5 @@
#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);

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,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,

View File

@@ -8,5 +8,5 @@
#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);

View File

@@ -9,6 +9,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include "manager.h"
#include <gio/gunixfdlist.h>
@@ -172,7 +173,7 @@ static void signal_cb(GDBusProxy *proxy,
modem_resume_pre(manager);
if (
#ifdef HAVE_MMGLIB
manager->mm_modem ||
manager->mm_modem ||
#endif
manager->modem_iface == MODEM_IFACE_OFONO) {
/*
@@ -238,18 +239,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) {
timeout_value = toml_int_in(config, "boot_timeout");
if (timeout_value.ok)
manager->modem_boot_timeout = (guint)timeout_value.u.i;
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
suspend_config[i] = config[i] ? toml_table_in(config[i], "suspend") : NULL;
timeout_value = toml_int_in(config, "recovery_timeout");
if (timeout_value.ok)
manager->modem_recovery_timeout = (guint)timeout_value.u.i;
/*
* The `suspend` section is optional in both the user and system config files,
* 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)

View File

@@ -8,7 +8,7 @@
#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_inhibit (struct EG25Manager *data, gboolean inhibit, gboolean block);

View File

@@ -16,16 +16,16 @@ static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *devic
manager->modem_state == EG25_STATE_RESETTING ||
!manager->modem_usb_id) {
return;
}
}
if (strncmp(g_udev_device_get_name(device), manager->modem_usb_id, strlen(manager->modem_usb_id)) == 0 &&
if (strcmp(g_udev_device_get_name(device), manager->modem_usb_id) == 0 &&
manager->reset_timer == 0) {
g_message("Lost modem, resetting...");
modem_reset(manager);
g_timeout_add_seconds(2, 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 };

View File

@@ -8,5 +8,5 @@
#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);

View File

@@ -1,5 +1,4 @@
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/control}="auto"
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/autosuspend_delay_ms}="3000"
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/wakeup}="enabled"
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{avoid_reset_quirk}="1"
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/persist}="0"