mirror of
https://gitlab.com/mobian1/eg25-manager.git
synced 2025-08-30 15:52:11 +02:00
Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5b4f9bcc12 | ||
|
2218a908ab | ||
|
be6a924f8d | ||
|
34472a5cff | ||
|
ee70cf7d2f | ||
|
0e2311fb36 | ||
|
5e4c797695 | ||
|
be1ae18592 | ||
|
19eb488e29 | ||
|
a3c51fc513 | ||
|
75400fb9c0 | ||
|
97b1878ebc | ||
|
9e6bccdf37 | ||
|
df79247821 | ||
|
61c89a003a | ||
|
9cf51b9529 | ||
|
50b4c00c16 | ||
|
fedce7298b | ||
|
8665f8a4a6 | ||
|
88c68b9933 | ||
|
a91bc71e23 | ||
|
b21c4b0fa4 | ||
|
abf60b793a | ||
|
f8b3e28434 | ||
|
d9725981bb | ||
|
a06360f4c8 | ||
|
1f8fa88d37 | ||
|
deeb60fa6a | ||
|
a3d27cb3f7 | ||
|
af4d5ca1c1 | ||
|
25dd46bb30 | ||
|
86978e18a6 | ||
|
bcdc839abb | ||
|
73868260a2 | ||
|
66073cdd21 | ||
|
cfd7ebf156 | ||
|
f03f086fcb | ||
|
f68af6405d | ||
|
68349c9f58 | ||
|
3d9456c0e7 | ||
|
87a7a6c9e9 | ||
|
aec8135ad4 | ||
|
fcf3832f52 | ||
|
4c6625a38d | ||
|
55ed2dc39c | ||
|
89b7dfda2f | ||
|
0f3e51cd06 | ||
|
8ae79fa34c | ||
|
6b2f0e8fbd | ||
|
082cf996d1 | ||
|
5102902692 | ||
|
5c61d41090 | ||
|
1a65947176 | ||
|
3d29617977 | ||
|
36ac57b627 | ||
|
ee10cafa00 | ||
|
2fcb5852ae | ||
|
86372093d7 | ||
|
34ec02cd34 | ||
|
3f4dd9ac79 | ||
|
6f91389496 | ||
|
771e9f8316 | ||
|
9e0d97d2e2 | ||
|
593db8aa67 | ||
|
ad1d6e5d3e | ||
|
2bf63376d7 | ||
|
6177c7167c | ||
|
750c41cbb5 | ||
|
0d2b0e326a | ||
|
f7c655c297 | ||
|
41511cbc5f | ||
|
8913300997 | ||
|
7b96296938 |
21
data/eg25-manager.service.in
Normal file
21
data/eg25-manager.service.in
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Quectel EG25 modem
|
||||||
|
Before=ModemManager.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=@bindir@/eg25-manager
|
||||||
|
Restart=on-failure
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
PrivateTmp=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
PrivateMounts=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
LockPersonality=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
@@ -8,6 +8,17 @@ conf_files = [
|
|||||||
'pine64,pinephone-1.0.toml',
|
'pine64,pinephone-1.0.toml',
|
||||||
'pine64,pinephone-1.1.toml',
|
'pine64,pinephone-1.1.toml',
|
||||||
'pine64,pinephone-1.2.toml',
|
'pine64,pinephone-1.2.toml',
|
||||||
|
'pine64,pinephone-pro.toml',
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(conf_files)
|
install_data(conf_files)
|
||||||
|
|
||||||
|
serviceconf = configuration_data()
|
||||||
|
serviceconf.set('bindir', bindir)
|
||||||
|
configure_file(
|
||||||
|
input: 'eg25-manager.service.in',
|
||||||
|
output: 'eg25-manager.service',
|
||||||
|
install_dir: systemdsystemdir,
|
||||||
|
configuration: serviceconf,
|
||||||
|
install: true
|
||||||
|
)
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
[manager]
|
[manager]
|
||||||
|
monitor_udev = true
|
||||||
need_libusb = true
|
need_libusb = true
|
||||||
usb_vid = 0x2c7c
|
usb_vid = 0x2c7c
|
||||||
usb_pid = 0x0125
|
usb_pid = 0x0125
|
||||||
@@ -13,11 +14,12 @@ poweron_delay = 100000
|
|||||||
#recovery_timeout = 9
|
#recovery_timeout = 9
|
||||||
|
|
||||||
[gpio]
|
[gpio]
|
||||||
dtr = 358
|
chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
|
||||||
pwrkey = 35
|
dtr = { chip = 1, line = 6 }
|
||||||
reset = 68
|
pwrkey = { chip = 0, line = 35 }
|
||||||
apready = 231
|
reset = { chip = 0, line = 68 }
|
||||||
disable = 232
|
apready = { chip = 0, line = 231 }
|
||||||
|
disable = { chip = 0, line = 232 }
|
||||||
|
|
||||||
[at]
|
[at]
|
||||||
uart = "/dev/ttyS2"
|
uart = "/dev/ttyS2"
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
[manager]
|
[manager]
|
||||||
|
monitor_udev = true
|
||||||
need_libusb = true
|
need_libusb = true
|
||||||
usb_vid = 0x2c7c
|
usb_vid = 0x2c7c
|
||||||
usb_pid = 0x0125
|
usb_pid = 0x0125
|
||||||
@@ -13,11 +14,12 @@ poweron_delay = 100000
|
|||||||
#recovery_timeout = 9
|
#recovery_timeout = 9
|
||||||
|
|
||||||
[gpio]
|
[gpio]
|
||||||
dtr = 358
|
chips = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
|
||||||
pwrkey = 35
|
dtr = { chip = 1, line = 6 }
|
||||||
reset = 68
|
pwrkey = { chip = 0, line = 35 }
|
||||||
apready = 231
|
reset = { chip = 0, line = 68 }
|
||||||
disable = 232
|
apready = { chip = 0, line = 231 }
|
||||||
|
disable = { chip = 0, line = 232 }
|
||||||
|
|
||||||
[at]
|
[at]
|
||||||
uart = "/dev/ttyS2"
|
uart = "/dev/ttyS2"
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
[manager]
|
[manager]
|
||||||
|
monitor_udev = true
|
||||||
|
usb_vid = 0x2c7c
|
||||||
|
usb_pid = 0x0125
|
||||||
# Delay between setting GPIO and PWRKEY sequence, set in microseconds
|
# Delay between setting GPIO and PWRKEY sequence, set in microseconds
|
||||||
poweron_delay = 100000
|
poweron_delay = 100000
|
||||||
|
|
||||||
@@ -9,12 +12,13 @@ poweron_delay = 100000
|
|||||||
#recovery_timeout = 9
|
#recovery_timeout = 9
|
||||||
|
|
||||||
[gpio]
|
[gpio]
|
||||||
dtr = 34
|
chips = [ "1c20800.pinctrl" ]
|
||||||
pwrkey = 35
|
dtr = { chip = 0, line = 34 }
|
||||||
reset = 68
|
pwrkey = { chip = 0, line = 35 }
|
||||||
apready = 231
|
reset = { chip = 0, line = 68 }
|
||||||
disable = 232
|
apready = { chip = 0, line = 231 }
|
||||||
status = 233
|
disable = { chip = 0, line = 232 }
|
||||||
|
status = { chip = 0, line = 233 }
|
||||||
|
|
||||||
[at]
|
[at]
|
||||||
uart = "/dev/ttyS2"
|
uart = "/dev/ttyS2"
|
||||||
|
100
data/pine64,pinephone-pro.toml
Normal file
100
data/pine64,pinephone-pro.toml
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
[manager]
|
||||||
|
monitor_udev = false
|
||||||
|
usb_vid = 0x2c7c
|
||||||
|
usb_pid = 0x0125
|
||||||
|
# Delay between setting GPIO and PWRKEY sequence, set in microseconds
|
||||||
|
poweron_delay = 100000
|
||||||
|
|
||||||
|
# Uncomment the following if you need to change the modem detection timeout on
|
||||||
|
# resume and/or the time during which suspend is blocked after modem boot
|
||||||
|
#[suspend]
|
||||||
|
#boot_timeout = 120
|
||||||
|
#recovery_timeout = 9
|
||||||
|
|
||||||
|
[gpio]
|
||||||
|
chips = [ "gpio0", "gpio3" ]
|
||||||
|
dtr = { chip = 0, line = 3 }
|
||||||
|
pwrkey = { chip = 0, line = 13 }
|
||||||
|
reset = { chip = 1, line = 8 }
|
||||||
|
apready = { chip = 0, line = 12 }
|
||||||
|
disable = { chip = 0, line = 8 }
|
||||||
|
status = { chip = 1, line = 6 }
|
||||||
|
|
||||||
|
[at]
|
||||||
|
uart = "/dev/ttyS3"
|
||||||
|
configure = [
|
||||||
|
# Each command has 4 possible elements:
|
||||||
|
# * `cmd` : the AT command itself, which will be translated to "AT+`cmd`"
|
||||||
|
# * `subcmd`: the subcommand in case a single AT command can be used
|
||||||
|
# to change multiple parameters, such as QCFG (optional)
|
||||||
|
# * `value` : the commands, argument, usually used to set the value of
|
||||||
|
# a specific parameter (optional)
|
||||||
|
# * `expect`: the expected return value; the command is first executed
|
||||||
|
# without any value in order to query the current state. This
|
||||||
|
# state is then compared to the `expect` string; if they don't
|
||||||
|
# match, the command is then executed with value `expect` in
|
||||||
|
# order to set the parameter to the configured value (optional)
|
||||||
|
# A command can have `expect` OR `value` configured, but it shouldn't have both
|
||||||
|
# Print software version
|
||||||
|
{ cmd = "QGMR" },
|
||||||
|
# Configure audio
|
||||||
|
{ cmd = "QDAI", expect = "3,0,0,4,0,1,1,1" },
|
||||||
|
# RI signaling using physical RI pin
|
||||||
|
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
|
||||||
|
# Enable VoLTE support
|
||||||
|
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
|
||||||
|
# Enable APREADY for PP 1.2
|
||||||
|
{ cmd = "QCFG", subcmd = "apready", expect = "1,0,500" },
|
||||||
|
# URC configuration for PP 1.2 (APREADY pin connected):
|
||||||
|
# * RING URC: normal pulse length
|
||||||
|
# * Incoming SMS URC: default pulse length
|
||||||
|
# * Other URC: default length
|
||||||
|
# * Report URCs on all ports (serial and USB) for FOSS firmware
|
||||||
|
# * Reporting of URCs without any delay
|
||||||
|
# * Configure URC pin to UART Ring Indicator
|
||||||
|
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",120,1000,5000,\"off\",1" },
|
||||||
|
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",120,1" },
|
||||||
|
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
|
||||||
|
{ cmd = "QCFG", subcmd = "urc/delay", expect = "0" },
|
||||||
|
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
|
||||||
|
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
|
||||||
|
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
||||||
|
# Allow sleeping for power saving
|
||||||
|
{ cmd = "QSCLK", value = "1" },
|
||||||
|
# GNSS configuration:
|
||||||
|
# * Enable A-GPS data upload support (XTRA)
|
||||||
|
# * Disable On-Demand-Positioning (ODP) mode
|
||||||
|
# to avoid running the GNSS system in the background, even when not enabled.
|
||||||
|
# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios
|
||||||
|
# when they are not in use.
|
||||||
|
# * Only enable GPS and GLONASS, disable other GNSS systems.
|
||||||
|
# A-GPS data upload doesn't work for Galileo anyway.
|
||||||
|
# * Avoid turning on GNSS support automatically when the modem boots.
|
||||||
|
{ cmd = "QGPSXTRA", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "autogps", expect = "0" },
|
||||||
|
# Disable fast poweroff for stability
|
||||||
|
{ cmd = "QCFG", subcmd = "fast/poweroff", expect = "0" },
|
||||||
|
# Configure sleep and wake up pin levels to active low
|
||||||
|
{ cmd = "QCFG", subcmd = "sleepind/level", expect = "0" },
|
||||||
|
{ cmd = "QCFG", subcmd = "wakeupin/level", expect = "0,0" },
|
||||||
|
# Do not enter RAMDUMP mode, auto-reset instead
|
||||||
|
{ cmd = "QCFG", subcmd = "ApRstLevel", expect = "1" },
|
||||||
|
{ cmd = "QCFG", subcmd = "ModemRstLevel", expect = "1" },
|
||||||
|
]
|
||||||
|
suspend = [
|
||||||
|
]
|
||||||
|
resume = [
|
||||||
|
]
|
||||||
|
reset = [ { cmd = "CFUN", value = "1,1" } ]
|
||||||
|
|
||||||
|
[gnss]
|
||||||
|
enabled = true
|
||||||
|
url = "https://xtrapath4.izatcloud.net"
|
||||||
|
file = "xtra2.bin"
|
106
doc/eg25-manager.5.scd
Normal file
106
doc/eg25-manager.5.scd
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
eg25-manager(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
eg25-manager configuration file format
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
eg25-manager uses toml formatted files for configuration.
|
||||||
|
|
||||||
|
Configurations are loaded from:
|
||||||
|
- *@eg25_confdir@/<compatible>.toml*: User-provided overrides (optional)
|
||||||
|
- *@eg25_datadir@/<compatible>.toml*: Default configuration (required)
|
||||||
|
|
||||||
|
# SECTION: manager
|
||||||
|
General settings for eg25-manager.
|
||||||
|
|
||||||
|
*poweron_delay* int (microseconds)
|
||||||
|
Delay between de-asserting RESET and starting the PWRKEY sequence.
|
||||||
|
|
||||||
|
# SECTION: suspend
|
||||||
|
Settings for how to handle suspend on the system where eg25-manager is running.
|
||||||
|
|
||||||
|
*boot_timeout* int (seconds)
|
||||||
|
Prevent the system from suspending for boot_timeout seconds to allow the
|
||||||
|
modem to fully boot.
|
||||||
|
|
||||||
|
Default: 120 if unset or zero.
|
||||||
|
|
||||||
|
*recovery_timeout* int (seconds)
|
||||||
|
Amount of time to wait for the modem to reappear after suspend. If the
|
||||||
|
timeout is reached the modem's USB connection will be reset.
|
||||||
|
|
||||||
|
Default: 9 if unset or zero.
|
||||||
|
|
||||||
|
# SECTION: at
|
||||||
|
AT commands to send when different events happen, and where to send them to.
|
||||||
|
|
||||||
|
Each command has 4 possible elements:
|
||||||
|
- *cmd*: the AT command itself, which will be translated to "AT+`cmd`"
|
||||||
|
- *subcmd*: the subcommand in case a single AT command can be used to
|
||||||
|
change multiple parameters, such as QCFG
|
||||||
|
- *value*: the command's argument(s), usually used to set the value of a
|
||||||
|
specific parameter
|
||||||
|
- *expect*: the expected return value; the command is first executed
|
||||||
|
without any value in order to query the current state. This state is
|
||||||
|
then compared to the *expect* string; if they don't match, the command
|
||||||
|
is then executed with value *expect* in order to set the parameter to
|
||||||
|
the expected value
|
||||||
|
A command can have *expect* OR *value* configured, but it shouldn't have both
|
||||||
|
|
||||||
|
*NOTE:* If a command sequence is configured in an override file, the default
|
||||||
|
commands won't be loaded from the system configuration. The default commands
|
||||||
|
should be copied into the override file when changing them.
|
||||||
|
|
||||||
|
*uart* string
|
||||||
|
The serial port to use for sending AT commands to the modem.
|
||||||
|
|
||||||
|
*configure* List of commands
|
||||||
|
AT commands to send to the modem when it is first started.
|
||||||
|
|
||||||
|
*suspend* List of commands
|
||||||
|
AT commands to send to the modem before the system suspends.
|
||||||
|
|
||||||
|
*resume* List of commands
|
||||||
|
AT commands to send to the modem after the system resumes from suspend.
|
||||||
|
|
||||||
|
*reset* List of commands
|
||||||
|
AT commands to send to the modem if resetting the usb port fails.
|
||||||
|
|
||||||
|
# SECTION: gnss
|
||||||
|
Settings for uploading AGPS assistance data to the modem.
|
||||||
|
|
||||||
|
*enabled* boolean
|
||||||
|
Enable or disable uploading AGPS data to the modem
|
||||||
|
|
||||||
|
*url* string
|
||||||
|
The directory on the server that contains the assistance files
|
||||||
|
|
||||||
|
Example: https://xtrapath4.izatcloud.net
|
||||||
|
|
||||||
|
*file* string
|
||||||
|
The name of the assistance file on the server.
|
||||||
|
|
||||||
|
Example: xtra2.bin
|
||||||
|
|
||||||
|
# SECTION: gpio
|
||||||
|
The *gpio* section defines the GPIO pins to use for different modem functions.
|
||||||
|
These settings should only be changed when porting eg25-manager to a new device;
|
||||||
|
for this reason they aren't documented here.
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
Print the firmware version every time the phone wakes from suspend:
|
||||||
|
```
|
||||||
|
[at]
|
||||||
|
resume = [
|
||||||
|
{ cmd = "QGMR" },
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Disable uploading AGPS data to the modem:
|
||||||
|
```
|
||||||
|
[gnss]
|
||||||
|
enabled = false
|
||||||
|
```
|
||||||
|
|
||||||
|
# SEE AlSO
|
||||||
|
*eg25-manager*(8)
|
37
doc/eg25-manager.8.scd
Normal file
37
doc/eg25-manager.8.scd
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
eg25-manager(8)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
eg25-manager - a daemon for managing the Quectel EG25 modem found on the
|
||||||
|
Pine64 PinePhone.
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
*eg25-manager* [-v] [-c config_file]
|
||||||
|
|
||||||
|
# OPTIONS
|
||||||
|
*-v*
|
||||||
|
Show the version number and quit.
|
||||||
|
*-c*
|
||||||
|
User configuration file, defaults to the device configuration file in
|
||||||
|
/etc/eg25-manager.
|
||||||
|
|
||||||
|
# FILES
|
||||||
|
Configurations are loaded from:
|
||||||
|
- *@eg25_confdir@/<compatible>.toml*: User-provided overrides (optional)
|
||||||
|
- *@eg25_datadir@/<compatible>.toml*: Default configuration (required)
|
||||||
|
|
||||||
|
eg25-manager will search these folders for files named after the value of the
|
||||||
|
compatible device-tree property (with the .toml file extension) and use the
|
||||||
|
first matching file in each directory. If no matching default configuration is
|
||||||
|
found, eg25-manager will exit with an error message.
|
||||||
|
|
||||||
|
Values from the user-provided overrides will take priority over values stored in
|
||||||
|
the default configuration. Only changed values must be stored as user overrides,
|
||||||
|
so eg25-manager can fall back to the default configuration as often as possible.
|
||||||
|
|
||||||
|
The file names eg25-manager will check can be listed using:
|
||||||
|
```
|
||||||
|
xargs -0 printf '%s.toml\\n' < /proc/device-tree/compatible
|
||||||
|
```
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
*eg25-manager*(5) *ModemManager*(8) *ofono*(8)
|
33
doc/meson.build
Normal file
33
doc/meson.build
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#
|
||||||
|
# Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
|
||||||
|
scdoc = dependency('scdoc', native: true, required: false)
|
||||||
|
if scdoc.found()
|
||||||
|
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
|
||||||
|
|
||||||
|
foreach section: [5, 8]
|
||||||
|
name = 'eg25-manager'
|
||||||
|
out = '@0@.@1@'.format(name, section)
|
||||||
|
|
||||||
|
preprocessed = configure_file(
|
||||||
|
input: '@0@.scd'.format(out),
|
||||||
|
output: '@BASENAME@.preprocessed',
|
||||||
|
configuration: {
|
||||||
|
'eg25_confdir': eg25_confdir,
|
||||||
|
'eg25_datadir': eg25_datadir,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
custom_target(
|
||||||
|
out,
|
||||||
|
output: out,
|
||||||
|
input: preprocessed,
|
||||||
|
command: ['sh', '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
|
||||||
|
capture: true,
|
||||||
|
install: true,
|
||||||
|
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
|
||||||
|
endforeach
|
||||||
|
endif
|
@@ -8,9 +8,9 @@
|
|||||||
project (
|
project (
|
||||||
'eg25-manager',
|
'eg25-manager',
|
||||||
'c',
|
'c',
|
||||||
version : '0.4.0',
|
version : '0.4.5',
|
||||||
license : 'GPLv3+',
|
license : 'GPLv3+',
|
||||||
meson_version : '>= 0.50.0',
|
meson_version : '>= 0.58.0',
|
||||||
default_options :
|
default_options :
|
||||||
[
|
[
|
||||||
'warning_level=1',
|
'warning_level=1',
|
||||||
@@ -28,6 +28,8 @@ datadir = get_option('datadir')
|
|||||||
sysconfdir = get_option('sysconfdir')
|
sysconfdir = get_option('sysconfdir')
|
||||||
bindir = join_paths(prefix, get_option('bindir'))
|
bindir = join_paths(prefix, get_option('bindir'))
|
||||||
udevrulesdir = join_paths(prefix, 'lib/udev/rules.d')
|
udevrulesdir = join_paths(prefix, 'lib/udev/rules.d')
|
||||||
|
systemddir = join_paths(prefix, 'lib/systemd')
|
||||||
|
systemdsystemdir = join_paths(systemddir, 'system')
|
||||||
|
|
||||||
if datadir.startswith('/')
|
if datadir.startswith('/')
|
||||||
full_datadir = datadir
|
full_datadir = datadir
|
||||||
@@ -46,6 +48,7 @@ eg25_datadir = join_paths(full_datadir, meson.project_name())
|
|||||||
|
|
||||||
add_global_arguments('-D@0@="@1@"'.format('EG25_CONFDIR', eg25_confdir), language : 'c')
|
add_global_arguments('-D@0@="@1@"'.format('EG25_CONFDIR', eg25_confdir), language : 'c')
|
||||||
add_global_arguments('-D@0@="@1@"'.format('EG25_DATADIR', eg25_datadir), language : 'c')
|
add_global_arguments('-D@0@="@1@"'.format('EG25_DATADIR', eg25_datadir), language : 'c')
|
||||||
|
add_global_arguments('-D@0@="@1@"'.format('EG25_VERSION', meson.project_version()), language : 'c')
|
||||||
|
|
||||||
mmglib_dep = dependency('mm-glib', required : false)
|
mmglib_dep = dependency('mm-glib', required : false)
|
||||||
if mmglib_dep.found()
|
if mmglib_dep.found()
|
||||||
@@ -63,5 +66,6 @@ mgr_deps = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
subdir('data')
|
subdir('data')
|
||||||
|
subdir('doc')
|
||||||
subdir('src')
|
subdir('src')
|
||||||
subdir('udev')
|
subdir('udev')
|
||||||
|
170
src/at.c
170
src/at.c
@@ -5,10 +5,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "at.h"
|
#include "at.h"
|
||||||
|
#include "config.h"
|
||||||
#include "suspend.h"
|
#include "suspend.h"
|
||||||
#include "gpio.h"
|
#include "gpio.h"
|
||||||
#include "gnss.h"
|
#include "gnss.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -46,11 +48,29 @@ static int configure_serial(const char *tty)
|
|||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void at_free_command(gpointer cmd, gpointer data)
|
||||||
|
{
|
||||||
|
struct AtCommand *at_cmd = cmd;
|
||||||
|
struct EG25Manager *manager = data;
|
||||||
|
|
||||||
|
if (!at_cmd)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_free(at_cmd->cmd);
|
||||||
|
g_free(at_cmd->subcmd);
|
||||||
|
g_free(at_cmd->value);
|
||||||
|
g_free(at_cmd->expected);
|
||||||
|
g_free(at_cmd);
|
||||||
|
|
||||||
|
if (manager && manager->at_cmds)
|
||||||
|
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
gboolean at_send_command(struct EG25Manager *manager)
|
gboolean at_send_command(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
char command[256];
|
char command[256];
|
||||||
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
||||||
int ret, len = 0;
|
int ret, len = 0, pos = 0;
|
||||||
|
|
||||||
if (at_cmd) {
|
if (at_cmd) {
|
||||||
/* Wake up the modem from soft sleep before sending an AT command */
|
/* Wake up the modem from soft sleep before sending an AT command */
|
||||||
@@ -58,22 +78,53 @@ gboolean at_send_command(struct EG25Manager *manager)
|
|||||||
|
|
||||||
/* Send AT command */
|
/* Send AT command */
|
||||||
if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL)
|
if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL)
|
||||||
len = sprintf(command, "AT+%s\r\n", at_cmd->cmd);
|
len = snprintf(command, sizeof(command), "AT+%s\r\n", at_cmd->cmd);
|
||||||
else if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
|
else if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
|
||||||
len = sprintf(command, "AT+%s?\r\n", at_cmd->cmd);
|
len = snprintf(command, sizeof(command), "AT+%s?\r\n", at_cmd->cmd);
|
||||||
else if (at_cmd->subcmd == NULL && at_cmd->value)
|
else if (at_cmd->subcmd == NULL && at_cmd->value)
|
||||||
len = sprintf(command, "AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
|
len = snprintf(command, sizeof(command),"AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
|
||||||
else if (at_cmd->subcmd && at_cmd->value == NULL)
|
else if (at_cmd->subcmd && at_cmd->value == NULL)
|
||||||
len = sprintf(command, "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
|
len = snprintf(command, sizeof(command), "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
|
||||||
else if (at_cmd->subcmd && at_cmd->value)
|
else if (at_cmd->subcmd && at_cmd->value)
|
||||||
len = sprintf(command, "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
|
len = snprintf(command, sizeof(command), "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
|
||||||
|
|
||||||
|
if (len < 0) {
|
||||||
|
g_warning("snprintf(3) failed");
|
||||||
|
at_next_command(manager);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
else if (len >= sizeof(command)) {
|
||||||
|
g_warning("AT command does not fit into buffer "
|
||||||
|
"(%d bytes required, %zu available)", len, sizeof(command));
|
||||||
|
at_next_command(manager);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
manager->at_callback = at_cmd->callback;
|
manager->at_callback = at_cmd->callback;
|
||||||
|
|
||||||
ret = write(manager->at_fd, command, len);
|
do {
|
||||||
if (ret < len)
|
ret = write(manager->at_fd, &command[pos], len);
|
||||||
g_warning("Couldn't write full AT command: wrote %d/%d bytes", ret, len);
|
|
||||||
|
|
||||||
g_message("Sending command: %s", g_strstrip(command));
|
if (ret < 0) {
|
||||||
|
switch (errno) {
|
||||||
|
case EAGAIN:
|
||||||
|
case EINTR:
|
||||||
|
/* Try again. */
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_warning("error sending AT command: %s", strerror(errno));
|
||||||
|
at_next_command(manager);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
len -= ret;
|
||||||
|
pos += ret;
|
||||||
|
}
|
||||||
|
} while (len > 0);
|
||||||
|
|
||||||
|
g_message("Successfully sent command: %s", g_strstrip(command));
|
||||||
} else {
|
} else {
|
||||||
/* Allow the modem to enter soft sleep again when we sent the AT command*/
|
/* Allow the modem to enter soft sleep again when we sent the AT command*/
|
||||||
gpio_sequence_sleep(manager);
|
gpio_sequence_sleep(manager);
|
||||||
@@ -81,12 +132,14 @@ gboolean at_send_command(struct EG25Manager *manager)
|
|||||||
if (manager->modem_state < EG25_STATE_CONFIGURED) {
|
if (manager->modem_state < EG25_STATE_CONFIGURED) {
|
||||||
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
||||||
#ifdef HAVE_MMGLIB
|
#ifdef HAVE_MMGLIB
|
||||||
|
if (manager->modem_state == EG25_STATE_ACQUIRED) {
|
||||||
MMModemState modem_state = mm_modem_get_state(manager->mm_modem);
|
MMModemState modem_state = mm_modem_get_state(manager->mm_modem);
|
||||||
|
|
||||||
if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED)
|
if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED)
|
||||||
modem_update_state(manager, modem_state);
|
modem_update_state(manager, modem_state);
|
||||||
else
|
else
|
||||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||||
@@ -105,16 +158,7 @@ void at_next_command(struct EG25Manager *manager)
|
|||||||
{
|
{
|
||||||
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
||||||
|
|
||||||
if (!at_cmd)
|
at_free_command(at_cmd, manager);
|
||||||
return;
|
|
||||||
|
|
||||||
g_free(at_cmd->cmd);
|
|
||||||
g_free(at_cmd->subcmd);
|
|
||||||
g_free(at_cmd->value);
|
|
||||||
g_free(at_cmd->expected);
|
|
||||||
g_free(at_cmd);
|
|
||||||
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
|
|
||||||
|
|
||||||
at_send_command(manager);
|
at_send_command(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +245,14 @@ static gboolean modem_response(gint fd,
|
|||||||
*/
|
*/
|
||||||
do {
|
do {
|
||||||
ret = read(fd, tmp, sizeof(tmp));
|
ret = read(fd, tmp, sizeof(tmp));
|
||||||
|
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
|
/* If we're going to overflow truncate the data we read to fit */
|
||||||
|
if (pos + ret >= sizeof(response)) {
|
||||||
|
g_critical("AT response buffer full, truncating");
|
||||||
|
ret = sizeof(response) - (pos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(&response[pos], tmp, ret);
|
memcpy(&response[pos], tmp, ret);
|
||||||
pos += ret;
|
pos += ret;
|
||||||
usleep(10000);
|
usleep(10000);
|
||||||
@@ -209,12 +260,15 @@ static gboolean modem_response(gint fd,
|
|||||||
} while (ret > 0 && pos < (sizeof(response) - 1));
|
} while (ret > 0 && pos < (sizeof(response) - 1));
|
||||||
|
|
||||||
if (pos > 0) {
|
if (pos > 0) {
|
||||||
|
g_autofree gchar *escaped = NULL;
|
||||||
|
|
||||||
response[pos] = 0;
|
response[pos] = 0;
|
||||||
g_strstrip(response);
|
g_strstrip(response);
|
||||||
if (strlen(response) == 0)
|
if (strlen(response) == 0)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
g_message("Response: [%s]", response);
|
escaped = g_strescape(response, "\"");
|
||||||
|
g_message("Response: [%s]", escaped);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When the modem is started, it outputs 'RDY' to indicate that
|
* When the modem is started, it outputs 'RDY' to indicate that
|
||||||
@@ -240,14 +294,15 @@ static gboolean modem_response(gint fd,
|
|||||||
else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff"))
|
else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff"))
|
||||||
retry_at_command(manager);
|
retry_at_command(manager);
|
||||||
/*
|
/*
|
||||||
* Successfull AT responses contain 'OK', except for AT+QFUPL which also
|
* Successful AT responses contain 'OK', except for AT+QFUPL which also
|
||||||
* returns 'CONNECT' when the modem is ready to receive data over serial
|
* returns 'CONNECT' when the modem is ready to receive data over serial
|
||||||
|
* and '+QFUPL:...' when data upload is complete
|
||||||
*/
|
*/
|
||||||
else if (strstr(response, "OK") || strstr(response, "CONNECT")) {
|
else if (strstr(response, "OK") || strstr(response, "CONNECT") || strstr(response, "QFUPL")) {
|
||||||
if (manager->at_callback != NULL)
|
if (manager->at_callback != NULL)
|
||||||
manager->at_callback(manager, response);
|
manager->at_callback(manager, response);
|
||||||
else
|
else
|
||||||
g_warning("AT command succesfull but no callback registered");
|
g_warning("AT command successful but no callback registered");
|
||||||
}
|
}
|
||||||
/* Not a recognized response, try running next command, just in case */
|
/* Not a recognized response, try running next command, just in case */
|
||||||
else
|
else
|
||||||
@@ -273,67 +328,61 @@ static void parse_commands_list(toml_array_t *array, GArray **cmds)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
value = toml_string_in(table, "cmd");
|
value = toml_string_in(table, "cmd");
|
||||||
if (value.ok) {
|
if (value.ok)
|
||||||
cmd->cmd = g_strdup(value.u.s);
|
cmd->cmd = value.u.s;
|
||||||
free(value.u.s);
|
|
||||||
}
|
|
||||||
|
|
||||||
value = toml_string_in(table, "subcmd");
|
value = toml_string_in(table, "subcmd");
|
||||||
if (value.ok) {
|
if (value.ok)
|
||||||
cmd->subcmd = g_strdup(value.u.s);
|
cmd->subcmd = value.u.s;
|
||||||
free(value.u.s);
|
|
||||||
}
|
|
||||||
|
|
||||||
value = toml_string_in(table, "value");
|
value = toml_string_in(table, "value");
|
||||||
if (value.ok) {
|
if (value.ok)
|
||||||
cmd->value = g_strdup(value.u.s);
|
cmd->value = value.u.s;
|
||||||
free(value.u.s);
|
|
||||||
}
|
|
||||||
|
|
||||||
value = toml_string_in(table, "expect");
|
value = toml_string_in(table, "expect");
|
||||||
if (value.ok) {
|
if (value.ok)
|
||||||
cmd->expected = g_strdup(value.u.s);
|
cmd->expected = value.u.s;
|
||||||
free(value.u.s);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int at_init(struct EG25Manager *manager, toml_table_t *config)
|
int at_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||||
{
|
{
|
||||||
toml_array_t *commands;
|
toml_array_t *commands = NULL;
|
||||||
toml_datum_t uart_port;
|
gchar *uart_port = NULL;
|
||||||
|
toml_table_t *at_config[EG25_CONFIG_COUNT];
|
||||||
|
|
||||||
uart_port = toml_string_in(config, "uart");
|
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||||
if (!uart_port.ok)
|
at_config[i] = config[i] ? toml_table_in(config[i], "at") : NULL;
|
||||||
|
|
||||||
|
if (!at_config[EG25_CONFIG_SYS])
|
||||||
|
g_error("Default config file lacks the 'at' section!");
|
||||||
|
|
||||||
|
if (!config_get_string(at_config, "uart", &uart_port))
|
||||||
g_error("Configuration file lacks UART port definition");
|
g_error("Configuration file lacks UART port definition");
|
||||||
|
|
||||||
manager->at_fd = configure_serial(uart_port.u.s);
|
manager->at_fd = configure_serial(uart_port);
|
||||||
if (manager->at_fd < 0) {
|
if (manager->at_fd < 0) {
|
||||||
g_critical("Unable to configure %s", uart_port.u.s);
|
g_critical("Unable to configure %s", uart_port);
|
||||||
free(uart_port.u.s);
|
g_free(uart_port);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
free(uart_port.u.s);
|
g_free(uart_port);
|
||||||
|
|
||||||
manager->at_source = g_unix_fd_add(manager->at_fd, G_IO_IN, modem_response, manager);
|
manager->at_source = g_unix_fd_add(manager->at_fd, G_IO_IN, modem_response, manager);
|
||||||
|
|
||||||
commands = toml_array_in(config, "configure");
|
if (!config_get_array(at_config, "configure", &commands))
|
||||||
if (!commands)
|
|
||||||
g_error("Configuration file lacks initial AT commands list");
|
g_error("Configuration file lacks initial AT commands list");
|
||||||
parse_commands_list(commands, &configure_commands);
|
parse_commands_list(commands, &configure_commands);
|
||||||
|
|
||||||
commands = toml_array_in(config, "suspend");
|
if (!config_get_array(at_config, "suspend", &commands))
|
||||||
if (!commands)
|
|
||||||
g_error("Configuration file lacks suspend AT commands list");
|
g_error("Configuration file lacks suspend AT commands list");
|
||||||
parse_commands_list(commands, &suspend_commands);
|
parse_commands_list(commands, &suspend_commands);
|
||||||
|
|
||||||
commands = toml_array_in(config, "resume");
|
if (!config_get_array(at_config, "resume", &commands))
|
||||||
if (!commands)
|
|
||||||
g_error("Configuration file lacks resume AT commands list");
|
g_error("Configuration file lacks resume AT commands list");
|
||||||
parse_commands_list(commands, &resume_commands);
|
parse_commands_list(commands, &resume_commands);
|
||||||
|
|
||||||
commands = toml_array_in(config, "reset");
|
if (!config_get_array(at_config, "reset", &commands))
|
||||||
if (!commands)
|
|
||||||
g_error("Configuration file lacks reset AT commands list");
|
g_error("Configuration file lacks reset AT commands list");
|
||||||
parse_commands_list(commands, &reset_commands);
|
parse_commands_list(commands, &reset_commands);
|
||||||
|
|
||||||
@@ -354,6 +403,13 @@ void at_destroy(struct EG25Manager *manager)
|
|||||||
|
|
||||||
void at_sequence_configure(struct EG25Manager *manager)
|
void at_sequence_configure(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* When configuring a new modem we should avoid processing an old
|
||||||
|
* command queue, so let's first clear the whole list
|
||||||
|
*/
|
||||||
|
if (manager->at_cmds)
|
||||||
|
g_list_foreach(manager->at_cmds, at_free_command, manager);
|
||||||
|
|
||||||
for (guint i = 0; i < configure_commands->len; i++) {
|
for (guint i = 0; i < configure_commands->len; i++) {
|
||||||
struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i);
|
struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i);
|
||||||
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
|
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
|
||||||
|
2
src/at.h
2
src/at.h
@@ -17,7 +17,7 @@ typedef struct AtCommand {
|
|||||||
int retries;
|
int retries;
|
||||||
} AtCommand;
|
} AtCommand;
|
||||||
|
|
||||||
int at_init(struct EG25Manager *manager, toml_table_t *config);
|
int at_init(struct EG25Manager *manager, toml_table_t *config[]);
|
||||||
void at_destroy(struct EG25Manager *manager);
|
void at_destroy(struct EG25Manager *manager);
|
||||||
|
|
||||||
void at_process_result(struct EG25Manager *manager,
|
void at_process_result(struct EG25Manager *manager,
|
||||||
|
97
src/config.c
Normal file
97
src/config.c
Normal 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
27
src/config.h
Normal 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);
|
237
src/gnss.c
237
src/gnss.c
@@ -4,12 +4,18 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "gnss.h"
|
#include "gnss.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "at.h"
|
#include "at.h"
|
||||||
|
|
||||||
|
#include <sys/sendfile.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#define BUFFER_SIZE 256
|
#define BUFFER_SIZE 256
|
||||||
#define UPLOAD_DELAY 100000
|
#define UPLOAD_DELAY_US 25000
|
||||||
|
#define UPLOAD_TIMEOUT_S 10
|
||||||
#define RESCHEDULE_IN_SECS 30
|
#define RESCHEDULE_IN_SECS 30
|
||||||
|
|
||||||
static void gnss_step(struct EG25Manager *manager);
|
static void gnss_step(struct EG25Manager *manager);
|
||||||
@@ -27,51 +33,60 @@ gboolean gnss_upload_assistance_data(struct EG25Manager *manager)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* data upload isn't necessary to bring the modem onine, so we should wait
|
||||||
|
* until we've finished the rest of our configuration */
|
||||||
|
if (!manager->modem_iface ||
|
||||||
|
manager->modem_state < EG25_STATE_CONFIGURED ||
|
||||||
|
manager->modem_state > EG25_STATE_CONNECTED) {
|
||||||
|
g_message ("Rescheduling upload since modem isn't online yet, in %ds",
|
||||||
|
RESCHEDULE_IN_SECS);
|
||||||
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_MMGLIB
|
||||||
/* ModemManager's Location is only available after unlocking */
|
/* ModemManager's Location is only available after unlocking */
|
||||||
if(!manager->mm_location) {
|
if(manager->modem_iface == MODEM_IFACE_MODEMMANAGER && !manager->mm_location) {
|
||||||
g_message ("Rescheduling upload since Location interface is not available, in %ds",
|
g_message ("Rescheduling upload since Location interface is not available, in %ds",
|
||||||
RESCHEDULE_IN_SECS);
|
RESCHEDULE_IN_SECS);
|
||||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST;
|
manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST;
|
||||||
gnss_step(manager);
|
gnss_step(manager);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gnss_init(struct EG25Manager *manager, toml_table_t *config)
|
void gnss_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||||
{
|
{
|
||||||
toml_datum_t enabled;
|
toml_table_t *gnss_config[EG25_CONFIG_COUNT];
|
||||||
toml_datum_t url;
|
|
||||||
toml_datum_t file;
|
|
||||||
g_autoptr (GError) error = NULL;
|
g_autoptr (GError) error = NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||||
|
gnss_config[i] = config[i] ? toml_table_in(config[i], "gnss") : NULL;
|
||||||
|
|
||||||
|
if (!gnss_config[EG25_CONFIG_SYS])
|
||||||
|
g_error("Default config file lacks the 'gnss' section!");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* GNSS assistance is an optional feature, you can disable it
|
* GNSS assistance is an optional feature, you can disable it
|
||||||
* if you want in the configuration file.
|
* if you want in the configuration file.
|
||||||
* In case the configuration is missing, we assume GNSS assistance
|
* In case the configuration is missing, we assume GNSS assistance
|
||||||
* to be disabled.
|
* to be disabled.
|
||||||
*/
|
*/
|
||||||
enabled = toml_bool_in(config, "enabled");
|
config_get_bool(gnss_config, "enabled", &manager->gnss_assistance_enabled);
|
||||||
manager->gnss_assistance_enabled = FALSE;
|
|
||||||
if (enabled.ok)
|
|
||||||
manager->gnss_assistance_enabled = enabled.u.b;
|
|
||||||
|
|
||||||
if (!manager->gnss_assistance_enabled) {
|
if (!manager->gnss_assistance_enabled) {
|
||||||
g_message("GNSS assistance is disabled!");
|
g_message("GNSS assistance is disabled!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
url = toml_string_in(config, "url");
|
if (!config_get_string(gnss_config, "url", &manager->gnss_assistance_url))
|
||||||
if (url.ok)
|
|
||||||
manager->gnss_assistance_url = url.u.s;
|
|
||||||
else
|
|
||||||
g_error("GNSS assistance server URL is missing from config file");
|
g_error("GNSS assistance server URL is missing from config file");
|
||||||
file = toml_string_in(config, "file");
|
|
||||||
if (file.ok)
|
if (!config_get_string(gnss_config, "file", &manager->gnss_assistance_file))
|
||||||
manager->gnss_assistance_file = file.u.s;
|
|
||||||
else
|
|
||||||
g_error("GNSS assistance file name is missing from config file");
|
g_error("GNSS assistance file name is missing from config file");
|
||||||
|
|
||||||
/* Create temporary file to store assistance data */
|
/* Create temporary file to store assistance data */
|
||||||
@@ -87,6 +102,8 @@ void gnss_init(struct EG25Manager *manager, toml_table_t *config)
|
|||||||
|
|
||||||
void gnss_destroy(struct EG25Manager *manager)
|
void gnss_destroy(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
|
g_free(manager->gnss_assistance_url);
|
||||||
|
g_free(manager->gnss_assistance_file);
|
||||||
close(manager->gnss_assistance_fd);
|
close(manager->gnss_assistance_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,69 +199,71 @@ static void state_at_gnss(struct EG25Manager *manager)
|
|||||||
|
|
||||||
static void fetch_assistance_data(struct EG25Manager *manager)
|
static void fetch_assistance_data(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
CURL *curl;
|
|
||||||
CURLcode response;
|
CURLcode response;
|
||||||
long status_code;
|
curl_off_t downloaded;
|
||||||
gchar *url = NULL;
|
CURL *curl = NULL;
|
||||||
|
g_autofree gchar *url = NULL;
|
||||||
FILE *tmp_file = NULL;
|
FILE *tmp_file = NULL;
|
||||||
long int size;
|
gchar errbuf[CURL_ERROR_SIZE];
|
||||||
|
errbuf[0] = 0;
|
||||||
|
|
||||||
/* Fetch assistance data with curl */
|
/* Fetch assistance data with curl */
|
||||||
tmp_file = fdopen(manager->gnss_assistance_fd, "wb");
|
tmp_file = fdopen(manager->gnss_assistance_fd, "wb+");
|
||||||
|
if (tmp_file == NULL) {
|
||||||
|
g_critical("Unable to open file to save assistance data: %s",
|
||||||
|
g_strerror(errno));
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
|
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
||||||
|
if (ftruncate(manager->gnss_assistance_fd, 0) < 0)
|
||||||
|
g_warning("Unable to truncate file, assistance data might be invalid!");
|
||||||
url = g_strconcat(manager->gnss_assistance_url, "/",
|
url = g_strconcat(manager->gnss_assistance_url, "/",
|
||||||
manager->gnss_assistance_file, NULL);
|
manager->gnss_assistance_file, NULL);
|
||||||
|
|
||||||
curl = curl_easy_init();
|
curl = curl_easy_init();
|
||||||
if (!curl)
|
if (!curl) {
|
||||||
g_error ("Unable to initialize curl");
|
g_critical("Unable to initialize curl");
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||||
|
|
||||||
response = curl_easy_perform(curl);
|
response = curl_easy_perform(curl);
|
||||||
if (response == CURLE_HTTP_RETURNED_ERROR) {
|
if (response != CURLE_OK) {
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
|
g_warning("Unable to fetch GNSS assistance data from %s: %s",
|
||||||
curl_easy_cleanup(curl);
|
url, strlen(errbuf) ? errbuf : curl_easy_strerror(response));
|
||||||
g_warning ("Unable to fetch GNSS assistance data from %s (HTTP %ld)",
|
goto bail;
|
||||||
url, status_code);
|
|
||||||
|
|
||||||
/* Restart upload on HTTP error status code */
|
|
||||||
g_message ("Rescheduling upload because of failure in %ds",
|
|
||||||
RESCHEDULE_IN_SECS);
|
|
||||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
|
||||||
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
|
|
||||||
G_SOURCE_FUNC(gnss_upload_assistance_data),
|
|
||||||
manager);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get file size in bytes */
|
response = curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &downloaded);
|
||||||
size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END);
|
if (response) {
|
||||||
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
g_critical("Unable to get number of downloaded bytes from curl");
|
||||||
|
goto bail;
|
||||||
if (size <= 0) {
|
} else if (downloaded <= 0) {
|
||||||
g_warning ("GNSS assistance data contains 0 bytes,"
|
g_warning("Downloaded empty assistance data file");
|
||||||
"check network connection.");
|
goto bail;
|
||||||
/*
|
|
||||||
* Restart upload when file does not contain any data,
|
|
||||||
* mostly because of no network connection.
|
|
||||||
*/
|
|
||||||
g_message ("Rescheduling upload because of failure in %ds",
|
|
||||||
RESCHEDULE_IN_SECS);
|
|
||||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
|
||||||
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
|
|
||||||
G_SOURCE_FUNC(gnss_upload_assistance_data),
|
|
||||||
manager);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_message("Fetching GNSS assistance data from %s was successfull", url);
|
g_message("Fetching GNSS assistance data from %s was successful", url);
|
||||||
|
|
||||||
|
fflush(tmp_file);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
g_free(url);
|
|
||||||
|
|
||||||
/* Go to the next step */
|
/* Go to the next step */
|
||||||
manager->gnss_assistance_step++;
|
manager->gnss_assistance_step++;
|
||||||
gnss_step(manager);
|
gnss_step(manager);
|
||||||
|
return;
|
||||||
|
|
||||||
|
bail:
|
||||||
|
if (curl != NULL)
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
@@ -255,6 +274,12 @@ static void init_assistance_data_upload_ready(struct EG25Manager *manager,
|
|||||||
/* Search for 'CONNECT' in response to start upload */
|
/* Search for 'CONNECT' in response to start upload */
|
||||||
if (strstr(response, "CONNECT")) {
|
if (strstr(response, "CONNECT")) {
|
||||||
g_message("Modem ready for GNSS assistance data upload");
|
g_message("Modem ready for GNSS assistance data upload");
|
||||||
|
manager->gnss_assistance_step++;
|
||||||
|
gnss_step(manager);
|
||||||
|
} else if (strstr(response, "QFUPL")) {
|
||||||
|
/* Clear QFUPL AT command and process next */
|
||||||
|
at_next_command(manager);
|
||||||
|
|
||||||
manager->gnss_assistance_step++;
|
manager->gnss_assistance_step++;
|
||||||
gnss_step(manager);
|
gnss_step(manager);
|
||||||
}
|
}
|
||||||
@@ -264,18 +289,24 @@ static void init_assistance_data_upload_start(struct EG25Manager *manager,
|
|||||||
const char *response)
|
const char *response)
|
||||||
{
|
{
|
||||||
gchar value[BUFFER_SIZE];
|
gchar value[BUFFER_SIZE];
|
||||||
long int size;
|
off_t size;
|
||||||
|
|
||||||
/* Process AT response */
|
/* Process AT response */
|
||||||
at_process_result(manager, response);
|
at_process_result(manager, response);
|
||||||
|
|
||||||
/* Get file size in bytes */
|
/* Get file size in bytes */
|
||||||
size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END);
|
size = lseek(manager->gnss_assistance_fd, 0, SEEK_END);
|
||||||
|
if (size == -1) {
|
||||||
|
g_critical("gnss: unable to read size of xtra data file: %s", g_strerror(errno));
|
||||||
|
|
||||||
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
|
return;
|
||||||
|
}
|
||||||
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
||||||
|
|
||||||
/* Start upload */
|
/* Start upload */
|
||||||
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld\r\n",
|
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld,%d",
|
||||||
manager->gnss_assistance_file, size);
|
manager->gnss_assistance_file, size, UPLOAD_TIMEOUT_S);
|
||||||
g_message("Initiate GNSS assistance data upload: %s", value);
|
g_message("Initiate GNSS assistance data upload: %s", value);
|
||||||
at_append_command(manager, "QFUPL", NULL, value, NULL,
|
at_append_command(manager, "QFUPL", NULL, value, NULL,
|
||||||
init_assistance_data_upload_ready);
|
init_assistance_data_upload_ready);
|
||||||
@@ -295,39 +326,34 @@ static void init_assistance_data_upload(struct EG25Manager *manager)
|
|||||||
|
|
||||||
static void upload_assistance_data(struct EG25Manager *manager)
|
static void upload_assistance_data(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
char buffer[2*BUFFER_SIZE];
|
gint error;
|
||||||
gint len;
|
glong written_total = 0;
|
||||||
gboolean success = TRUE;
|
gint ret;
|
||||||
|
struct stat sb;
|
||||||
|
|
||||||
|
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 */
|
/* Copy downloaded XTRA assistance data to the modem over serial */
|
||||||
while((len = read(manager->gnss_assistance_fd, buffer, 2*BUFFER_SIZE)) > 0)
|
ret = sendfile(manager->at_fd, manager->gnss_assistance_fd, &written_total, BUFFER_SIZE);
|
||||||
{
|
error = errno;
|
||||||
len = write(manager->at_fd, buffer, len);
|
usleep(UPLOAD_DELAY_US);
|
||||||
if (len < 0) {
|
} while ((!error && written_total < sb.st_size) || (ret == -1 && error == EAGAIN));
|
||||||
success = FALSE;
|
|
||||||
g_error("Writing GNSS assistance data failed: %d", len);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
usleep(UPLOAD_DELAY);
|
|
||||||
g_message("Uploaded %d bytes", len);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Clear QFUPL AT command and process next */
|
|
||||||
at_next_command(manager);
|
|
||||||
|
|
||||||
/* Go to the next step if successful */
|
/* Go to the next step if successful */
|
||||||
if (success) {
|
if (!error) {
|
||||||
manager->gnss_assistance_step++;
|
g_message("Successfully uploaded %ld bytes to the modem", written_total);
|
||||||
gnss_step(manager);
|
} else {
|
||||||
}
|
g_critical("Unable to upload xtra data: %s", g_strerror(error));
|
||||||
/* Restart upload */
|
|
||||||
else {
|
|
||||||
g_message ("Rescheduling upload because of failure in %ds",
|
|
||||||
RESCHEDULE_IN_SECS);
|
|
||||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
|
|
||||||
G_SOURCE_FUNC(gnss_upload_assistance_data),
|
|
||||||
manager);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,13 +377,13 @@ static void finish_assistance_data_upload(struct EG25Manager *manager)
|
|||||||
|
|
||||||
/* Configure GNSS assistance clock to current system time (UTC) */
|
/* Configure GNSS assistance clock to current system time (UTC) */
|
||||||
datetime = g_date_time_new_now_utc();
|
datetime = g_date_time_new_now_utc();
|
||||||
timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"\r\n");
|
timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"");
|
||||||
g_message("Setting GNSS assistance UTC clock to: %s", timestring);
|
g_message("Setting GNSS assistance UTC clock to: %s", timestring);
|
||||||
at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL,
|
at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL,
|
||||||
at_process_result);
|
at_process_result);
|
||||||
|
|
||||||
/* Configure GNSS engine to use uploaded GNSS assistance data */
|
/* Configure GNSS engine to use uploaded GNSS assistance data */
|
||||||
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"\r\n",
|
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"",
|
||||||
manager->gnss_assistance_file);
|
manager->gnss_assistance_file);
|
||||||
g_message("Setting GNSS assistance file to: %s", value);
|
g_message("Setting GNSS assistance file to: %s", value);
|
||||||
at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL,
|
at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL,
|
||||||
@@ -370,9 +396,9 @@ static void finish_assistance_data_upload(struct EG25Manager *manager)
|
|||||||
#ifdef HAVE_MMGLIB
|
#ifdef HAVE_MMGLIB
|
||||||
static void enable_mm_gnss(struct EG25Manager *manager)
|
static void enable_mm_gnss(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
MMModemLocationSource sources;
|
|
||||||
gboolean signal_location;
|
|
||||||
g_autoptr (GError) error = NULL;
|
g_autoptr (GError) error = NULL;
|
||||||
|
MMModemLocationSource sources = mm_modem_location_get_enabled(manager->mm_location);
|
||||||
|
gboolean signal_location = mm_modem_location_signals_location(manager->mm_location);
|
||||||
|
|
||||||
if (manager->gnss_sources & EG25_GNSS_SOURCE_UNMANAGED)
|
if (manager->gnss_sources & EG25_GNSS_SOURCE_UNMANAGED)
|
||||||
sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
|
sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
|
||||||
@@ -381,8 +407,6 @@ static void enable_mm_gnss(struct EG25Manager *manager)
|
|||||||
if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW)
|
if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW)
|
||||||
sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
|
sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
|
||||||
|
|
||||||
sources = mm_modem_location_get_enabled(manager->mm_location);
|
|
||||||
signal_location = mm_modem_location_signals_location(manager->mm_location);
|
|
||||||
mm_modem_location_setup_sync(manager->mm_location, sources,
|
mm_modem_location_setup_sync(manager->mm_location, sources,
|
||||||
signal_location, NULL, &error);
|
signal_location, NULL, &error);
|
||||||
if (error != NULL)
|
if (error != NULL)
|
||||||
@@ -419,12 +443,21 @@ void gnss_step(struct EG25Manager *manager)
|
|||||||
g_message("GNSS assistance upload started...");
|
g_message("GNSS assistance upload started...");
|
||||||
/* fall-through */
|
/* fall-through */
|
||||||
|
|
||||||
|
case EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA:
|
||||||
|
g_message("GNSS assistance upload step (%d/%d): "
|
||||||
|
"fetching assistance data",
|
||||||
|
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||||
|
fetch_assistance_data(manager);
|
||||||
|
break;
|
||||||
|
|
||||||
#ifdef HAVE_MMGLIB
|
#ifdef HAVE_MMGLIB
|
||||||
case EG25_GNSS_STEP_MM_GNSS_DISABLE:
|
case EG25_GNSS_STEP_MM_GNSS_DISABLE:
|
||||||
|
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
||||||
g_message("GNSS assistance upload step (%d/%d): "
|
g_message("GNSS assistance upload step (%d/%d): "
|
||||||
"disabling GNSS engine through ModemManager",
|
"disabling GNSS engine through ModemManager",
|
||||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||||
disable_mm_gnss(manager);
|
disable_mm_gnss(manager);
|
||||||
|
}
|
||||||
manager->gnss_assistance_step++;
|
manager->gnss_assistance_step++;
|
||||||
/* fall-through */
|
/* fall-through */
|
||||||
#endif
|
#endif
|
||||||
@@ -436,13 +469,6 @@ void gnss_step(struct EG25Manager *manager)
|
|||||||
state_at_gnss(manager);
|
state_at_gnss(manager);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA:
|
|
||||||
g_message("GNSS assistance upload step (%d/%d): "
|
|
||||||
"fetching assistance data",
|
|
||||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
|
||||||
fetch_assistance_data(manager);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EG25_GNSS_STEP_INIT_UPLOAD:
|
case EG25_GNSS_STEP_INIT_UPLOAD:
|
||||||
g_message("GNSS assistance upload step (%d/%d): initiating upload",
|
g_message("GNSS assistance upload step (%d/%d): initiating upload",
|
||||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||||
@@ -464,10 +490,12 @@ void gnss_step(struct EG25Manager *manager)
|
|||||||
|
|
||||||
#ifdef HAVE_MMGLIB
|
#ifdef HAVE_MMGLIB
|
||||||
case EG25_GNSS_STEP_MM_GNSS_ENABLE:
|
case EG25_GNSS_STEP_MM_GNSS_ENABLE:
|
||||||
|
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
||||||
g_message("GNSS assistance upload step (%d/%d): "
|
g_message("GNSS assistance upload step (%d/%d): "
|
||||||
"re-enabling GNSS through ModemManager",
|
"re-enabling GNSS through ModemManager",
|
||||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||||
enable_mm_gnss(manager);
|
enable_mm_gnss(manager);
|
||||||
|
}
|
||||||
manager->gnss_assistance_step++;
|
manager->gnss_assistance_step++;
|
||||||
/* fall-through */
|
/* fall-through */
|
||||||
#endif
|
#endif
|
||||||
@@ -485,4 +513,3 @@ void gnss_step(struct EG25Manager *manager)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,10 +7,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
void gnss_init(struct EG25Manager *manager, toml_table_t *config);
|
void gnss_init(struct EG25Manager *manager, toml_table_t *config[]);
|
||||||
void gnss_destroy(struct EG25Manager *manager);
|
void gnss_destroy(struct EG25Manager *manager);
|
||||||
gboolean gnss_upload_assistance_data(struct EG25Manager *manager);
|
gboolean gnss_upload_assistance_data(struct EG25Manager *manager);
|
||||||
|
207
src/gpio.c
207
src/gpio.c
@@ -4,17 +4,16 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "gpio.h"
|
#include "gpio.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* Those defines are used for legacy config files only */
|
||||||
#define GPIO_CHIP1_LABEL "1c20800.pinctrl"
|
#define GPIO_CHIP1_LABEL "1c20800.pinctrl"
|
||||||
#define GPIO_CHIP2_LABEL "1f02c00.pinctrl"
|
#define GPIO_CHIP2_LABEL "1f02c00.pinctrl"
|
||||||
|
|
||||||
#define MAX_GPIOCHIP_LINES 352
|
#define MAX_GPIOCHIP_LINES 352
|
||||||
|
|
||||||
#define GPIO_IDX_INVAL 0xffff
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
GPIO_OUT_DTR = 0,
|
GPIO_OUT_DTR = 0,
|
||||||
GPIO_OUT_PWRKEY,
|
GPIO_OUT_PWRKEY,
|
||||||
@@ -24,12 +23,23 @@ enum {
|
|||||||
GPIO_OUT_COUNT
|
GPIO_OUT_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
GPIO_IN_STATUS = 0,
|
GPIO_IN_STATUS = 0,
|
||||||
GPIO_IN_COUNT
|
GPIO_IN_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static char *gpio_out_names[] = {
|
||||||
|
"dtr",
|
||||||
|
"pwrkey",
|
||||||
|
"reset",
|
||||||
|
"apready",
|
||||||
|
"disable",
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *gpio_in_names[] = {
|
||||||
|
"status",
|
||||||
|
};
|
||||||
|
|
||||||
int gpio_sequence_poweron(struct EG25Manager *manager)
|
int gpio_sequence_poweron(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 1);
|
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 1);
|
||||||
@@ -71,12 +81,14 @@ int gpio_sequence_resume(struct EG25Manager *manager)
|
|||||||
|
|
||||||
int gpio_sequence_wake(struct EG25Manager *manager)
|
int gpio_sequence_wake(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
|
if (gpiod_line_get_value(manager->gpio_out[GPIO_OUT_DTR])) {
|
||||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0);
|
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0);
|
||||||
|
|
||||||
/* Give the modem 200ms to wake from soft sleep */
|
/* Give the modem 200ms to wake from soft sleep */
|
||||||
usleep(200000);
|
usleep(200000);
|
||||||
|
|
||||||
g_message("Executed soft wake sequence");
|
g_message("Executed soft wake sequence");
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -89,92 +101,165 @@ int gpio_sequence_sleep(struct EG25Manager *manager)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static guint get_config_gpio(toml_table_t *config, const char *id)
|
struct gpiod_line *gpio_get_output_line(struct EG25Manager *manager, int chip, int line)
|
||||||
{
|
{
|
||||||
toml_datum_t value = toml_int_in(config, id);
|
struct gpiod_line *gpio_line;
|
||||||
guint gpio;
|
|
||||||
|
|
||||||
if (!value.ok)
|
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
|
||||||
return GPIO_IDX_INVAL;
|
if (!gpio_line)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
gpio = (guint)value.u.i;
|
if (gpiod_line_request_output(gpio_line, "eg25manager", 0) < 0) {
|
||||||
|
gpiod_line_release(gpio_line);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return gpio;
|
return gpio_line;
|
||||||
}
|
}
|
||||||
|
|
||||||
int gpio_init(struct EG25Manager *manager, toml_table_t *config)
|
struct gpiod_line *gpio_get_input_line(struct EG25Manager *manager, int chip, int line)
|
||||||
{
|
{
|
||||||
int i, ret;
|
struct gpiod_line *gpio_line;
|
||||||
guint gpio_out_idx[GPIO_OUT_COUNT];
|
|
||||||
guint gpio_in_idx[GPIO_IN_COUNT];
|
|
||||||
|
|
||||||
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
|
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
|
||||||
if (!manager->gpiochip[0]) {
|
if (!gpio_line)
|
||||||
g_critical("Unable to open GPIO chip " GPIO_CHIP1_LABEL);
|
return NULL;
|
||||||
return 1;
|
|
||||||
|
if (gpiod_line_request_input(gpio_line, "eg25manager") < 0) {
|
||||||
|
gpiod_line_release(gpio_line);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
|
return gpio_line;
|
||||||
if (!manager->gpiochip[1]) {
|
}
|
||||||
g_critical("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
gpio_out_idx[GPIO_OUT_DTR] = get_config_gpio(config, "dtr");
|
int gpio_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||||
gpio_out_idx[GPIO_OUT_PWRKEY] = get_config_gpio(config, "pwrkey");
|
{
|
||||||
gpio_out_idx[GPIO_OUT_RESET] = get_config_gpio(config, "reset");
|
int i;
|
||||||
gpio_out_idx[GPIO_OUT_APREADY] = get_config_gpio(config, "apready");
|
toml_table_t *gpio_config[EG25_CONFIG_COUNT];
|
||||||
gpio_out_idx[GPIO_OUT_DISABLE] = get_config_gpio(config, "disable");
|
|
||||||
gpio_in_idx[GPIO_IN_STATUS] = get_config_gpio(config, "status");
|
for (i = 0; i < 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);
|
||||||
|
}
|
||||||
|
|
||||||
for (i = 0; i < GPIO_OUT_COUNT; i++) {
|
for (i = 0; i < GPIO_OUT_COUNT; i++) {
|
||||||
guint offset, chipidx;
|
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]);
|
||||||
|
|
||||||
if (gpio_out_idx[i] < MAX_GPIOCHIP_LINES) {
|
chip = toml_int_in(table, "chip");
|
||||||
offset = gpio_out_idx[i];
|
if (!chip.ok || chip.u.i < 0 || chip.u.i > 2)
|
||||||
chipidx = 0;
|
g_error("Wrong chip ID for output GPIO '%s'", gpio_out_names[i]);
|
||||||
} else {
|
|
||||||
offset = gpio_out_idx[i] - MAX_GPIOCHIP_LINES;
|
|
||||||
chipidx = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
manager->gpio_out[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
|
line = toml_int_in(table, "line");
|
||||||
if (!manager->gpio_out[i]) {
|
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);
|
g_error("Unable to get output GPIO %d", i);
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = gpiod_line_request_output(manager->gpio_out[i], "eg25manager", 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
g_error("Unable to request output GPIO %d", i);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < GPIO_IN_COUNT; i++) {
|
for (i = 0; i < GPIO_IN_COUNT; i++) {
|
||||||
guint offset, chipidx;
|
toml_table_t *table;
|
||||||
|
toml_datum_t chip, line;
|
||||||
|
|
||||||
if (gpio_in_idx[i] == GPIO_IDX_INVAL)
|
if (!config_get_table(gpio_config, gpio_in_names[i], &table)) {
|
||||||
|
// BH edition don't have the STATUS line connected, ignore it
|
||||||
|
if (manager->use_libusb && g_strcmp0(gpio_in_names[i], "status") == 0)
|
||||||
continue;
|
continue;
|
||||||
|
g_error("Unable to get config for input GPIO '%s'", gpio_in_names[i]);
|
||||||
|
}
|
||||||
|
|
||||||
if (gpio_in_idx[i] < MAX_GPIOCHIP_LINES) {
|
chip = toml_int_in(table, "chip");
|
||||||
offset = gpio_in_idx[i];
|
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;
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
|
||||||
|
if (!manager->gpiochip[1])
|
||||||
|
g_error("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
|
||||||
|
|
||||||
|
for (i = 0; i < GPIO_OUT_COUNT; i++) {
|
||||||
|
if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx))
|
||||||
|
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
|
||||||
|
|
||||||
|
if (gpio_idx < MAX_GPIOCHIP_LINES) {
|
||||||
|
offset = gpio_idx;
|
||||||
chipidx = 0;
|
chipidx = 0;
|
||||||
} else {
|
} else {
|
||||||
offset = gpio_in_idx[i] - MAX_GPIOCHIP_LINES;
|
offset = gpio_idx - MAX_GPIOCHIP_LINES;
|
||||||
chipidx = 1;
|
chipidx = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
manager->gpio_in[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
|
manager->gpio_out[i] = gpio_get_input_line(manager, chipidx, offset);
|
||||||
if (!manager->gpio_in[i]) {
|
if (!manager->gpio_out[i])
|
||||||
g_warning("Unable to get input GPIO %d", i);
|
g_error("Unable to get output GPIO %d", i);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = gpiod_line_request_input(manager->gpio_in[i], "eg25manager");
|
for (i = 0; i < GPIO_IN_COUNT; i++) {
|
||||||
if (ret < 0) {
|
if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx))
|
||||||
g_warning("Unable to request input GPIO %d", i);
|
continue;
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
int gpio_init(struct EG25Manager *state, toml_table_t *config);
|
int gpio_init(struct EG25Manager *state, toml_table_t *config[]);
|
||||||
void gpio_destroy(struct EG25Manager *state);
|
void gpio_destroy(struct EG25Manager *state);
|
||||||
|
|
||||||
int gpio_sequence_poweron(struct EG25Manager *state);
|
int gpio_sequence_poweron(struct EG25Manager *state);
|
||||||
|
188
src/manager.c
188
src/manager.c
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "at.h"
|
#include "at.h"
|
||||||
|
#include "config.h"
|
||||||
#include "gpio.h"
|
#include "gpio.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
@@ -33,6 +34,15 @@
|
|||||||
#define EG25_DATADIR "/usr/share/eg25-manager"
|
#define EG25_DATADIR "/usr/share/eg25-manager"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef EG25_VERSION
|
||||||
|
#define EG25_VERSION "0.0.0"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define EG25_DEFAULT_VENDOR_ID 0x2c7c
|
||||||
|
#define EG25_DEFAULT_PRODUCT_ID 0x0125
|
||||||
|
|
||||||
|
#define POWERON_DELAY_US 100000UL
|
||||||
|
|
||||||
static gboolean quit_app(struct EG25Manager *manager)
|
static gboolean quit_app(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@@ -134,62 +144,84 @@ void modem_configure(struct EG25Manager *manager)
|
|||||||
static gboolean modem_reset_done(struct EG25Manager* manager)
|
static gboolean modem_reset_done(struct EG25Manager* manager)
|
||||||
{
|
{
|
||||||
manager->modem_state = EG25_STATE_RESUMING;
|
manager->modem_state = EG25_STATE_RESUMING;
|
||||||
manager->reset_timer = 0;
|
manager->complete_reset_timer = 0;
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void modem_reset(struct EG25Manager *manager)
|
gboolean modem_reset(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
int fd, ret, len;
|
int fd, ret, len;
|
||||||
|
|
||||||
if (manager->reset_timer)
|
/* reset sequence started, cannot be canceled anymore */
|
||||||
return;
|
if (manager->schedule_reset_timer) {
|
||||||
|
g_source_remove(manager->schedule_reset_timer);
|
||||||
/*
|
manager->schedule_reset_timer = 0;
|
||||||
* If we are managing the modem through lets say ofono, we should not
|
}
|
||||||
* reset the modem based on the availability of USB ID
|
|
||||||
* TODO: Improve ofono plugin and add support for fetching USB ID
|
|
||||||
*/
|
|
||||||
if (manager->modem_iface != MODEM_IFACE_MODEMMANAGER)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (manager->modem_recovery_timer) {
|
if (manager->modem_recovery_timer) {
|
||||||
g_source_remove(manager->modem_recovery_timer);
|
g_source_remove(manager->modem_recovery_timer);
|
||||||
manager->modem_recovery_timer = 0;
|
manager->modem_recovery_timer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manager->complete_reset_timer) {
|
||||||
|
g_message("modem_reset: timer already setup, skipping...");
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we are managing the modem through lets say ofono, we should not
|
||||||
|
* reset the modem based on the availability of USB ID
|
||||||
|
* TODO: Improve ofono plugin and add support for fetching USB ID
|
||||||
|
*/
|
||||||
|
if (manager->modem_iface != MODEM_IFACE_MODEMMANAGER) {
|
||||||
|
g_message("modem_reset: not using ModemManager, bail out!");
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
}
|
||||||
|
|
||||||
if (!manager->modem_usb_id) {
|
if (!manager->modem_usb_id) {
|
||||||
g_warning("Unknown modem USB ID");
|
g_warning("Empty modem USB ID");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_message("Trying to reset modem with USB ID '%s'", manager->modem_usb_id);
|
||||||
|
|
||||||
len = strlen(manager->modem_usb_id);
|
len = strlen(manager->modem_usb_id);
|
||||||
|
|
||||||
manager->modem_state = EG25_STATE_RESETTING;
|
manager->modem_state = EG25_STATE_RESETTING;
|
||||||
|
|
||||||
fd = open("/sys/bus/usb/drivers/usb/unbind", O_WRONLY);
|
fd = open("/sys/bus/usb/drivers/usb/unbind", O_WRONLY);
|
||||||
if (fd < 0)
|
if (fd < 0) {
|
||||||
|
g_warning("Unable to open /sys/bus/usb/drivers/usb/unbind");
|
||||||
goto error;
|
goto error;
|
||||||
|
}
|
||||||
ret = write(fd, manager->modem_usb_id, len);
|
ret = write(fd, manager->modem_usb_id, len);
|
||||||
if (ret < len)
|
if (ret < len) {
|
||||||
g_warning("Couldn't unbind modem: wrote %d/%d bytes", ret, len);
|
g_warning("Couldn't unbind modem: wrote %d/%d bytes", ret, len);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
fd = open("/sys/bus/usb/drivers/usb/bind", O_WRONLY);
|
fd = open("/sys/bus/usb/drivers/usb/bind", O_WRONLY);
|
||||||
if (fd < 0)
|
if (fd < 0) {
|
||||||
|
g_warning("Unable to open /sys/bus/usb/drivers/usb/unbind");
|
||||||
goto error;
|
goto error;
|
||||||
|
}
|
||||||
ret = write(fd, manager->modem_usb_id, len);
|
ret = write(fd, manager->modem_usb_id, len);
|
||||||
if (ret < len)
|
if (ret < len) {
|
||||||
g_warning("Couldn't bind modem: wrote %d/%d bytes", ret, len);
|
g_warning("Couldn't bind modem: wrote %d/%d bytes", ret, len);
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
|
g_message("Successfully reset modem's USB connection");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 3s is long enough to make sure the modem has been bound back and
|
* 3s is long enough to make sure the modem has been bound back and
|
||||||
* short enough to ensure it hasn't been acquired by ModemManager
|
* short enough to ensure it hasn't been acquired by ModemManager
|
||||||
*/
|
*/
|
||||||
manager->reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset_done), manager);
|
manager->complete_reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset_done), manager);
|
||||||
|
|
||||||
return;
|
return G_SOURCE_REMOVE;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
// Release blocking sleep inhibitor
|
// Release blocking sleep inhibitor
|
||||||
@@ -199,8 +231,15 @@ error:
|
|||||||
g_source_remove(manager->modem_boot_timer);
|
g_source_remove(manager->modem_boot_timer);
|
||||||
manager->modem_boot_timer = 0;
|
manager->modem_boot_timer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything else failed, reboot the modem
|
// Everything else failed, reboot the modem
|
||||||
|
g_message("USB reset failed, falling back to AT command");
|
||||||
at_sequence_reset(manager);
|
at_sequence_reset(manager);
|
||||||
|
|
||||||
|
// Setup timer for making sure we don't queue other reset commands
|
||||||
|
manager->complete_reset_timer = g_timeout_add_seconds(30, G_SOURCE_FUNC(modem_reset_done), manager);
|
||||||
|
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void modem_suspend_pre(struct EG25Manager *manager)
|
void modem_suspend_pre(struct EG25Manager *manager)
|
||||||
@@ -225,7 +264,7 @@ void modem_resume_post(struct EG25Manager *manager)
|
|||||||
at_sequence_resume(manager);
|
at_sequence_resume(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
static toml_table_t *parse_config_file(char *config_file)
|
static toml_table_t *parse_config_file(char *config_file, gboolean force_default)
|
||||||
{
|
{
|
||||||
toml_table_t *toml_config;
|
toml_table_t *toml_config;
|
||||||
gchar *compatible;
|
gchar *compatible;
|
||||||
@@ -249,28 +288,26 @@ static toml_table_t *parse_config_file(char *config_file)
|
|||||||
} while (pos < len);
|
} while (pos < len);
|
||||||
|
|
||||||
for (pos = 0; pos < compat->len; pos++) {
|
for (pos = 0; pos < compat->len; pos++) {
|
||||||
g_autofree gchar *filename = g_strdup_printf(EG25_CONFDIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
|
g_autofree gchar *filename = NULL;
|
||||||
|
if (force_default)
|
||||||
|
filename = g_strdup_printf(EG25_DATADIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
|
||||||
|
else
|
||||||
|
filename = g_strdup_printf(EG25_CONFDIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
|
||||||
|
|
||||||
if (access(filename, F_OK) == 0) {
|
if (access(filename, F_OK) == 0) {
|
||||||
g_message("Opening config file: %s", filename);
|
g_message("Opening config file: %s", filename);
|
||||||
f = fopen(filename, "r");
|
f = fopen(filename, "r");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!f) {
|
if (!f) {
|
||||||
for (pos = 0; pos < compat->len; pos++) {
|
if (force_default)
|
||||||
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!");
|
g_error("unable to find a suitable config file!");
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
toml_config = toml_parse_file(f, error, sizeof(error));
|
toml_config = toml_parse_file(f, error, sizeof(error));
|
||||||
if (!toml_config)
|
if (!toml_config)
|
||||||
@@ -285,16 +322,19 @@ int main(int argc, char *argv[])
|
|||||||
g_autoptr(GError) err = NULL;
|
g_autoptr(GError) err = NULL;
|
||||||
struct EG25Manager manager;
|
struct EG25Manager manager;
|
||||||
gchar *config_file = NULL;
|
gchar *config_file = NULL;
|
||||||
toml_table_t *toml_config;
|
gboolean show_version = FALSE;
|
||||||
toml_table_t *toml_manager;
|
gboolean monitor_udev = TRUE;
|
||||||
toml_datum_t toml_value;
|
toml_table_t *toml_config[EG25_CONFIG_COUNT];
|
||||||
|
toml_table_t *manager_config[EG25_CONFIG_COUNT];
|
||||||
const GOptionEntry options[] = {
|
const GOptionEntry options[] = {
|
||||||
{ "config", 'c', 0, G_OPTION_ARG_STRING, &config_file, "Config file to use.", NULL },
|
{ "config", 'c', 0, G_OPTION_ARG_STRING, &config_file, "Config file to use.", NULL },
|
||||||
|
{ "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "Display version information and exit.", NULL },
|
||||||
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
|
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
memset(&manager, 0, sizeof(manager));
|
memset(&manager, 0, sizeof(manager));
|
||||||
manager.at_fd = -1;
|
manager.at_fd = -1;
|
||||||
|
manager.poweron_delay = POWERON_DELAY_US;
|
||||||
manager.suspend_delay_fd = -1;
|
manager.suspend_delay_fd = -1;
|
||||||
manager.suspend_block_fd = -1;
|
manager.suspend_block_fd = -1;
|
||||||
|
|
||||||
@@ -305,45 +345,57 @@ int main(int argc, char *argv[])
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (show_version) {
|
||||||
|
printf("eg25-manager version %s\n", EG25_VERSION);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
manager.loop = g_main_loop_new(NULL, FALSE);
|
manager.loop = g_main_loop_new(NULL, FALSE);
|
||||||
|
|
||||||
toml_config = parse_config_file(config_file);
|
toml_config[EG25_CONFIG_SYS] = parse_config_file(NULL, TRUE);
|
||||||
|
toml_config[EG25_CONFIG_USER] = parse_config_file(config_file, FALSE);
|
||||||
|
|
||||||
toml_manager = toml_table_in(toml_config, "manager");
|
/*
|
||||||
if (toml_manager) {
|
* We need at least one valid config file, and assuming it's
|
||||||
toml_value = toml_bool_in(toml_manager, "need_libusb");
|
* EG25_CONFIG_SYS will make the rest easier to implement
|
||||||
if (toml_value.ok)
|
*/
|
||||||
manager.use_libusb = toml_value.u.b;
|
if (!toml_config[EG25_CONFIG_SYS] && toml_config[EG25_CONFIG_USER]) {
|
||||||
|
toml_config[EG25_CONFIG_SYS] = toml_config[EG25_CONFIG_USER];
|
||||||
toml_value = toml_int_in(toml_manager, "usb_vid");
|
toml_config[EG25_CONFIG_USER] = NULL;
|
||||||
if (toml_value.ok)
|
|
||||||
manager.usb_vid = toml_value.u.i;
|
|
||||||
|
|
||||||
toml_value = toml_int_in(toml_manager, "usb_pid");
|
|
||||||
if (toml_value.ok)
|
|
||||||
manager.usb_pid = toml_value.u.i;
|
|
||||||
|
|
||||||
toml_value = toml_int_in(toml_manager, "poweron_delay");
|
|
||||||
if (toml_value.ok) {
|
|
||||||
if (toml_value.u.i >= 0 && toml_value.u.i <= G_MAXULONG) {
|
|
||||||
// Safe to cast into gulong
|
|
||||||
manager.poweron_delay = (gulong) toml_value.u.i;
|
|
||||||
} else {
|
|
||||||
// Changed from initialized default value but not in range
|
|
||||||
g_message("Configured poweron_delay out of range, using default");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
at_init(&manager, toml_table_in(toml_config, "at"));
|
if (!toml_config[EG25_CONFIG_SYS])
|
||||||
gpio_init(&manager, toml_table_in(toml_config, "gpio"));
|
g_error("Unable to parse config file!");
|
||||||
|
|
||||||
|
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||||
|
manager_config[i] = toml_config[i] ? toml_table_in(toml_config[i], "manager") : NULL;
|
||||||
|
|
||||||
|
if (!manager_config[EG25_CONFIG_SYS])
|
||||||
|
g_error("Default config file lacks the 'manager' section!");
|
||||||
|
|
||||||
|
config_get_bool(manager_config, "monitor_udev", &monitor_udev);
|
||||||
|
config_get_bool(manager_config, "need_libusb", &manager.use_libusb);
|
||||||
|
config_get_uint(manager_config, "poweron_delay", &manager.poweron_delay);
|
||||||
|
if (!config_get_uint(manager_config, "usb_vid", &manager.usb_vid))
|
||||||
|
manager.usb_vid = EG25_DEFAULT_VENDOR_ID;
|
||||||
|
if (!config_get_uint(manager_config, "usb_pid", &manager.usb_pid))
|
||||||
|
manager.usb_pid = EG25_DEFAULT_PRODUCT_ID;
|
||||||
|
|
||||||
|
at_init(&manager, toml_config);
|
||||||
|
gpio_init(&manager, toml_config);
|
||||||
#ifdef HAVE_MMGLIB
|
#ifdef HAVE_MMGLIB
|
||||||
mm_iface_init(&manager, toml_table_in(toml_config, "mm-iface"));
|
mm_iface_init(&manager, toml_config);
|
||||||
#endif
|
#endif
|
||||||
ofono_iface_init(&manager);
|
ofono_iface_init(&manager, toml_config);
|
||||||
suspend_init(&manager, toml_table_in(toml_config, "suspend"));
|
suspend_init(&manager, toml_config);
|
||||||
udev_init(&manager, toml_table_in(toml_config, "udev"));
|
if (monitor_udev)
|
||||||
gnss_init(&manager, toml_table_in(toml_config, "gnss"));
|
udev_init(&manager, toml_config);
|
||||||
|
gnss_init(&manager, toml_config);
|
||||||
|
|
||||||
|
for (int i = 0; i < EG25_CONFIG_COUNT; i++) {
|
||||||
|
if (toml_config[i])
|
||||||
|
toml_free(toml_config[i]);
|
||||||
|
}
|
||||||
|
|
||||||
g_idle_add(G_SOURCE_FUNC(modem_start), &manager);
|
g_idle_add(G_SOURCE_FUNC(modem_start), &manager);
|
||||||
|
|
||||||
|
@@ -18,11 +18,11 @@
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
EG25_GNSS_STEP_FIRST = 0,
|
EG25_GNSS_STEP_FIRST = 0,
|
||||||
|
EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA,
|
||||||
#ifdef HAVE_MMGLIB
|
#ifdef HAVE_MMGLIB
|
||||||
EG25_GNSS_STEP_MM_GNSS_DISABLE,
|
EG25_GNSS_STEP_MM_GNSS_DISABLE,
|
||||||
#endif
|
#endif
|
||||||
EG25_GNSS_STEP_AT_GNSS_DISABLE,
|
EG25_GNSS_STEP_AT_GNSS_DISABLE,
|
||||||
EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA,
|
|
||||||
EG25_GNSS_STEP_INIT_UPLOAD,
|
EG25_GNSS_STEP_INIT_UPLOAD,
|
||||||
EG25_GNSS_STEP_UPLOAD,
|
EG25_GNSS_STEP_UPLOAD,
|
||||||
EG25_GNSS_STEP_FINISH_UPLOAD,
|
EG25_GNSS_STEP_FINISH_UPLOAD,
|
||||||
@@ -48,11 +48,12 @@ enum EG25State {
|
|||||||
EG25_STATE_STARTED, // Modem has been started and declared itdata ready
|
EG25_STATE_STARTED, // Modem has been started and declared itdata ready
|
||||||
EG25_STATE_ACQUIRED, // Modem has been probed by ModemManager
|
EG25_STATE_ACQUIRED, // Modem has been probed by ModemManager
|
||||||
EG25_STATE_CONFIGURED, // Modem has been configured through AT commands
|
EG25_STATE_CONFIGURED, // Modem has been configured through AT commands
|
||||||
EG25_STATE_SUSPENDING, // System is going into suspend
|
|
||||||
EG25_STATE_RESUMING, // System is being resumed, waiting for modem to come back
|
|
||||||
EG25_STATE_REGISTERED, // Modem is unlocked and registered to a network provider
|
EG25_STATE_REGISTERED, // Modem is unlocked and registered to a network provider
|
||||||
EG25_STATE_CONNECTED, // Modem is connected (data connection active)
|
EG25_STATE_CONNECTED, // Modem is connected (data connection active)
|
||||||
|
EG25_STATE_SUSPENDING, // System is going into suspend
|
||||||
|
EG25_STATE_RESUMING, // System is being resumed, waiting for modem to come back
|
||||||
EG25_STATE_RESETTING, // Something went wrong, we're restarting the modem
|
EG25_STATE_RESETTING, // Something went wrong, we're restarting the modem
|
||||||
|
EG25_STATE_UPDATING, // Modem is present but being updated
|
||||||
EG25_STATE_FINISHING
|
EG25_STATE_FINISHING
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,13 +63,20 @@ enum ModemIface {
|
|||||||
MODEM_IFACE_OFONO
|
MODEM_IFACE_OFONO
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum EG25Config {
|
||||||
|
EG25_CONFIG_SYS = 0,
|
||||||
|
EG25_CONFIG_USER,
|
||||||
|
EG25_CONFIG_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
struct EG25Manager {
|
struct EG25Manager {
|
||||||
GMainLoop *loop;
|
GMainLoop *loop;
|
||||||
guint reset_timer;
|
guint complete_reset_timer;
|
||||||
|
guint schedule_reset_timer;
|
||||||
gboolean use_libusb;
|
gboolean use_libusb;
|
||||||
guint usb_vid;
|
guint usb_vid;
|
||||||
guint usb_pid;
|
guint usb_pid;
|
||||||
gulong poweron_delay;
|
guint poweron_delay;
|
||||||
|
|
||||||
int at_fd;
|
int at_fd;
|
||||||
guint at_source;
|
guint at_source;
|
||||||
@@ -113,7 +121,7 @@ struct EG25Manager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void modem_configure(struct EG25Manager *data);
|
void modem_configure(struct EG25Manager *data);
|
||||||
void modem_reset(struct EG25Manager *data);
|
gboolean modem_reset(struct EG25Manager *data);
|
||||||
void modem_suspend_pre(struct EG25Manager *data);
|
void modem_suspend_pre(struct EG25Manager *data);
|
||||||
void modem_suspend_post(struct EG25Manager *data);
|
void modem_suspend_post(struct EG25Manager *data);
|
||||||
void modem_resume_pre(struct EG25Manager *data);
|
void modem_resume_pre(struct EG25Manager *data);
|
||||||
|
@@ -9,6 +9,7 @@ subdir('libgdbofono')
|
|||||||
|
|
||||||
src = [
|
src = [
|
||||||
'at.c', 'at.h',
|
'at.c', 'at.h',
|
||||||
|
'config.c', 'config.h',
|
||||||
'gpio.c', 'gpio.h',
|
'gpio.c', 'gpio.h',
|
||||||
'manager.c', 'manager.h',
|
'manager.c', 'manager.h',
|
||||||
'ofono-iface.c', 'ofono-iface.h',
|
'ofono-iface.c', 'ofono-iface.h',
|
||||||
@@ -24,7 +25,7 @@ if mmglib_dep.found()
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
executable (
|
executable (
|
||||||
'eg25manager',
|
'eg25-manager',
|
||||||
src,
|
src,
|
||||||
dependencies : mgr_deps,
|
dependencies : mgr_deps,
|
||||||
link_with: gdbofono_lib,
|
link_with: gdbofono_lib,
|
||||||
|
@@ -209,7 +209,7 @@ static void mm_vanished_cb(GDBusConnection *connection,
|
|||||||
mm_iface_clean(manager);
|
mm_iface_clean(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mm_iface_init(struct EG25Manager *manager, toml_table_t *config)
|
void mm_iface_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||||
{
|
{
|
||||||
manager->mm_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, MM_DBUS_SERVICE,
|
manager->mm_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, MM_DBUS_SERVICE,
|
||||||
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
||||||
|
@@ -8,5 +8,5 @@
|
|||||||
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
void mm_iface_init(struct EG25Manager *data, toml_table_t *config);
|
void mm_iface_init(struct EG25Manager *data, toml_table_t *config[]);
|
||||||
void mm_iface_destroy(struct EG25Manager *data);
|
void mm_iface_destroy(struct EG25Manager *data);
|
||||||
|
@@ -128,7 +128,7 @@ static void ofono_vanished_cb(GDBusConnection *connection,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ofono_iface_init(struct EG25Manager *manager)
|
void ofono_iface_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||||
{
|
{
|
||||||
manager->ofono_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
|
manager->ofono_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
|
||||||
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
||||||
|
@@ -8,5 +8,5 @@
|
|||||||
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
void ofono_iface_init(struct EG25Manager *data);
|
void ofono_iface_init(struct EG25Manager *data, toml_table_t *config[]);
|
||||||
void ofono_iface_destroy(struct EG25Manager *data);
|
void ofono_iface_destroy(struct EG25Manager *data);
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
#include <gio/gunixfdlist.h>
|
#include <gio/gunixfdlist.h>
|
||||||
@@ -17,10 +18,24 @@
|
|||||||
#define SD_PATH "/org/freedesktop/login1"
|
#define SD_PATH "/org/freedesktop/login1"
|
||||||
#define SD_INTERFACE "org.freedesktop.login1.Manager"
|
#define SD_INTERFACE "org.freedesktop.login1.Manager"
|
||||||
|
|
||||||
|
static void resume_ok(struct EG25Manager *manager)
|
||||||
|
{
|
||||||
|
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||||
|
modem_resume_post(manager);
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean check_modem_resume(struct EG25Manager *manager)
|
static gboolean check_modem_resume(struct EG25Manager *manager)
|
||||||
{
|
{
|
||||||
g_message("Modem wasn't probed in time, restart it!");
|
|
||||||
manager->modem_recovery_timer = 0;
|
manager->modem_recovery_timer = 0;
|
||||||
|
|
||||||
|
#ifdef HAVE_MMGLIB
|
||||||
|
if (manager->mm_modem) {
|
||||||
|
resume_ok(manager);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
g_message("Modem wasn't probed in time, restart it!");
|
||||||
modem_reset(manager);
|
modem_reset(manager);
|
||||||
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -183,8 +198,7 @@ static void signal_cb(GDBusProxy *proxy,
|
|||||||
* If modem is managed by ofono, we also do resume sequence immediately
|
* If modem is managed by ofono, we also do resume sequence immediately
|
||||||
* as ofono handles resuming from sleep itself.
|
* as ofono handles resuming from sleep itself.
|
||||||
*/
|
*/
|
||||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
resume_ok(manager);
|
||||||
modem_resume_post(manager);
|
|
||||||
} else {
|
} else {
|
||||||
manager->modem_state = EG25_STATE_RESUMING;
|
manager->modem_state = EG25_STATE_RESUMING;
|
||||||
manager->modem_recovery_timer = g_timeout_add_seconds(manager->modem_recovery_timeout,
|
manager->modem_recovery_timer = g_timeout_add_seconds(manager->modem_recovery_timeout,
|
||||||
@@ -238,18 +252,26 @@ static void on_proxy_acquired(GObject *object,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void suspend_init(struct EG25Manager *manager, toml_table_t *config)
|
void suspend_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||||
{
|
{
|
||||||
toml_datum_t timeout_value;
|
toml_table_t *suspend_config[EG25_CONFIG_COUNT];
|
||||||
|
|
||||||
if (config) {
|
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||||
timeout_value = toml_int_in(config, "boot_timeout");
|
suspend_config[i] = config[i] ? toml_table_in(config[i], "suspend") : NULL;
|
||||||
if (timeout_value.ok)
|
|
||||||
manager->modem_boot_timeout = (guint)timeout_value.u.i;
|
|
||||||
|
|
||||||
timeout_value = toml_int_in(config, "recovery_timeout");
|
/*
|
||||||
if (timeout_value.ok)
|
* The `suspend` section is optional in both the user and system config files,
|
||||||
manager->modem_recovery_timeout = (guint)timeout_value.u.i;
|
* so let's make sure suspend_config[EG25_CONFIG_SYS] is valid if one of the
|
||||||
|
* files has it.
|
||||||
|
*/
|
||||||
|
if (suspend_config[EG25_CONFIG_USER] && !suspend_config[EG25_CONFIG_SYS]) {
|
||||||
|
suspend_config[EG25_CONFIG_SYS] = suspend_config[EG25_CONFIG_USER];
|
||||||
|
suspend_config[EG25_CONFIG_USER] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suspend_config[EG25_CONFIG_SYS]) {
|
||||||
|
config_get_uint(suspend_config, "boot_timeout", &manager->modem_boot_timeout);
|
||||||
|
config_get_uint(suspend_config, "recovery_timeout", &manager->modem_recovery_timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manager->modem_boot_timeout == 0)
|
if (manager->modem_boot_timeout == 0)
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
void suspend_init (struct EG25Manager *data, toml_table_t *config);
|
void suspend_init (struct EG25Manager *data, toml_table_t *config[]);
|
||||||
void suspend_destroy (struct EG25Manager *data);
|
void suspend_destroy (struct EG25Manager *data);
|
||||||
|
|
||||||
void suspend_inhibit (struct EG25Manager *data, gboolean inhibit, gboolean block);
|
void suspend_inhibit (struct EG25Manager *data, gboolean inhibit, gboolean block);
|
||||||
|
45
src/udev.c
45
src/udev.c
@@ -11,21 +11,50 @@
|
|||||||
static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data)
|
static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data)
|
||||||
{
|
{
|
||||||
struct EG25Manager *manager = data;
|
struct EG25Manager *manager = data;
|
||||||
|
const gchar *prop;
|
||||||
|
long vid = 0, pid = 0;
|
||||||
|
|
||||||
if (strcmp(action, "unbind") != 0 ||
|
/*
|
||||||
manager->modem_state == EG25_STATE_RESETTING ||
|
* Act only if the device is the one identified as a modem by MM/ofono
|
||||||
!manager->modem_usb_id) {
|
*/
|
||||||
|
if (!manager->modem_usb_id ||
|
||||||
|
strcmp(g_udev_device_get_name(device), manager->modem_usb_id) != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strncmp(g_udev_device_get_name(device), manager->modem_usb_id, strlen(manager->modem_usb_id)) == 0 &&
|
prop = g_udev_device_get_property(device, "ID_VENDOR_ID");
|
||||||
manager->reset_timer == 0) {
|
if (prop)
|
||||||
g_message("Lost modem, resetting...");
|
vid = strtol(prop, NULL, 16);
|
||||||
modem_reset(manager);
|
|
||||||
|
prop = g_udev_device_get_property(device, "ID_MODEL_ID");
|
||||||
|
if (prop)
|
||||||
|
pid = strtol(prop, NULL, 16);
|
||||||
|
|
||||||
|
if (strcmp(action, "bind") == 0 && vid != manager->usb_vid && pid != manager->usb_pid) {
|
||||||
|
/*
|
||||||
|
* Modem is probably executing a FW upgrade, make sure we don't interrupt it
|
||||||
|
*/
|
||||||
|
if (manager->schedule_reset_timer != 0) {
|
||||||
|
g_message("Modem re-appeared with different VID/PID, cancel reset.");
|
||||||
|
g_source_remove(manager->schedule_reset_timer);
|
||||||
|
manager->schedule_reset_timer = 0;
|
||||||
}
|
}
|
||||||
|
manager->modem_state = EG25_STATE_UPDATING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(action, "unbind") != 0 ||
|
||||||
|
manager->modem_state == EG25_STATE_UPDATING ||
|
||||||
|
manager->modem_state == EG25_STATE_RESETTING ||
|
||||||
|
manager->complete_reset_timer != 0 ||
|
||||||
|
manager->schedule_reset_timer != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_message("Lost modem, resetting...");
|
||||||
|
manager->schedule_reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset), manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
void udev_init (struct EG25Manager *manager, toml_table_t *config)
|
void udev_init (struct EG25Manager *manager, toml_table_t *config[])
|
||||||
{
|
{
|
||||||
const char * const subsystems[] = { "usb", NULL };
|
const char * const subsystems[] = { "usb", NULL };
|
||||||
|
|
||||||
|
@@ -8,5 +8,5 @@
|
|||||||
|
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
|
|
||||||
void udev_init (struct EG25Manager *data, toml_table_t *config);
|
void udev_init (struct EG25Manager *data, toml_table_t *config[]);
|
||||||
void udev_destroy (struct EG25Manager *data);
|
void udev_destroy (struct EG25Manager *data);
|
||||||
|
@@ -1,4 +1,22 @@
|
|||||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/control}="auto"
|
ACTION!="add", GOTO="eg25_end"
|
||||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/autosuspend_delay_ms}="3000"
|
SUBSYSTEM!="usb", GOTO="eg25_end"
|
||||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/wakeup}="enabled"
|
DRIVERS!="usb", GOTO="eg25_end"
|
||||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/persist}="0"
|
ENV{DEVTYPE}!="usb_device", GOTO="eg25_end"
|
||||||
|
|
||||||
|
# Default attributes values
|
||||||
|
ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/control}="auto"
|
||||||
|
ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/autosuspend_delay_ms}="3000"
|
||||||
|
ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/wakeup}="enabled"
|
||||||
|
ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/persist}="0"
|
||||||
|
|
||||||
|
# If running the stock firmware, stop processing here
|
||||||
|
ATTRS{serial}!="community_fw", GOTO="eg25_end"
|
||||||
|
|
||||||
|
# power/control needs to be "on" for the community-maintained firmware
|
||||||
|
ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/control}="on"
|
||||||
|
|
||||||
|
# Special trick for the PinePhone Pro: set power/persist to 1 *only* with the community FW
|
||||||
|
# We can identify the PPP by looking for the string "pinephone-pro" in the device tree "compatible" property
|
||||||
|
ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", PROGRAM=="/bin/grep pine64,pinephone-pro /proc/device-tree/compatible", ATTR{power/persist}="1"
|
||||||
|
|
||||||
|
LABEL="eg25_end"
|
||||||
|
Reference in New Issue
Block a user