mirror of
https://gitlab.com/mobian1/eg25-manager.git
synced 2025-08-29 23:32:14 +02:00
Compare commits
223 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ca98361552 | ||
|
56ddc3a664 | ||
|
c9d89e1736 | ||
|
a66852d96d | ||
|
8c9941eef6 | ||
|
d9778e6ecd | ||
|
7b46f084d9 | ||
|
ee70cf7d2f | ||
|
0e2311fb36 | ||
|
5e4c797695 | ||
|
be1ae18592 | ||
|
19eb488e29 | ||
|
a3c51fc513 | ||
|
75400fb9c0 | ||
|
97b1878ebc | ||
|
9e6bccdf37 | ||
|
df79247821 | ||
|
61c89a003a | ||
|
9cf51b9529 | ||
|
50b4c00c16 | ||
|
fedce7298b | ||
|
8665f8a4a6 | ||
|
88c68b9933 | ||
|
9c05776843 | ||
|
08a7039ca0 | ||
|
929b3942c0 | ||
|
a91bc71e23 | ||
|
b21c4b0fa4 | ||
|
abf60b793a | ||
|
f8b3e28434 | ||
|
d9725981bb | ||
|
a06360f4c8 | ||
|
f8430eb16f | ||
|
1b69252cbe | ||
|
c17f947249 | ||
|
e96aec8390 | ||
|
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 | ||
|
e4ae8d6382 | ||
|
da8a008268 | ||
|
30ce2bb3e2 | ||
|
c11f68f402 | ||
|
162fcf6fca | ||
|
e6df81778e | ||
|
ef94492b30 | ||
|
75570e45da | ||
|
34c3b19f70 | ||
|
10ec1119fb | ||
|
898c0dc79c | ||
|
6b3bc5660a | ||
|
128483354b | ||
|
24dbcf464c | ||
|
b62b155875 | ||
|
67195a8e58 | ||
|
9cfe782f74 | ||
|
5da7c88fc4 | ||
|
dac50e34eb | ||
|
9646e0e8df | ||
|
9c4a934a51 | ||
|
f2593b62b1 | ||
|
73fb60e0ad | ||
|
63ba5e2d60 | ||
|
c7e8d9171c | ||
|
5db68722ec | ||
|
e690e2a17d | ||
|
64145acbae | ||
|
705950bb39 | ||
|
73e16f7699 | ||
|
3d21b0fc9e | ||
|
0d46f42d78 | ||
|
1fd9d7d634 | ||
|
d665ea9639 | ||
|
68e4b2c5b4 | ||
|
dfeab4c79b | ||
|
40136c2a52 | ||
|
84a0ae603d | ||
|
ea19b0271c | ||
|
528fe7e07c | ||
|
bd5b5a5cf8 | ||
|
ef707c9a33 | ||
|
2e7a5a696b | ||
|
52488d2e2a | ||
|
032bd4651c | ||
|
060dc9ae32 | ||
|
dcb1a9a050 | ||
|
04eed2496d | ||
|
432bd454bb | ||
|
986e7f08c4 | ||
|
3d076e8bc8 | ||
|
2e7bf44f2d | ||
|
3bf2d785bb | ||
|
4089f2ea6b | ||
|
c77c58df49 | ||
|
9e40ae11d6 | ||
|
5cc5ff5c0e | ||
|
6a81955086 | ||
|
11c9cee111 | ||
|
89ce52f709 | ||
|
8a41a3203b | ||
|
042de572ea | ||
|
cfbbbd0167 | ||
|
f276d9cf9e | ||
|
433982e4f7 | ||
|
d9256251fd | ||
|
c92746e875 | ||
|
19d00bee3b | ||
|
b929c6a380 | ||
|
4962fcd13e | ||
|
b8d269cf2f | ||
|
4ff727a7d4 | ||
|
7765e6c541 | ||
|
94cbd03921 | ||
|
4c832a00dc | ||
|
ded092ef04 | ||
|
235eff02ac | ||
|
3bb6e15de0 | ||
|
692f9134f8 | ||
|
af3a2b25bc | ||
|
70db05fc62 | ||
|
705a454882 | ||
|
31133affa0 | ||
|
79974bc9ee | ||
|
150ff67e7b | ||
|
c2e83f15a6 | ||
|
cb5220a1b8 | ||
|
abc05e86e5 | ||
|
d990ab667e | ||
|
2469757af4 | ||
|
514b00cc9c | ||
|
e078b8bc09 | ||
|
2a18b1cb0c | ||
|
067c01b685 | ||
|
62a07f9c51 | ||
|
74b91c7d58 | ||
|
c39000bf93 | ||
|
d976c75fa2 | ||
|
f86d0ef062 | ||
|
fdbc2cfa69 | ||
|
aa85cd873c | ||
|
a8e6da534c | ||
|
1bb2f80fef | ||
|
9c9169a972 | ||
|
b495d6c747 | ||
|
dd904bc8c1 | ||
|
8c9a2b21f9 | ||
|
90a016a8f6 | ||
|
276c71f223 | ||
|
1423021f97 | ||
|
fc915f570d | ||
|
ff60016e5d | ||
|
5715138a96 | ||
|
fd6a292a8f | ||
|
5fa345ec92 | ||
|
c77490a2ac | ||
|
fbd5c0cb86 | ||
|
5bcefbeab0 | ||
|
f85e8f70c7 | ||
|
a9725243ec | ||
|
75b0920e9d | ||
|
9713af7ca8 | ||
|
664f82d570 | ||
|
2da2c9dfe2 | ||
|
dfaac39162 | ||
|
7dc0d1678c | ||
|
f386d851fa | ||
|
5bc8443c38 | ||
|
aabe4df41c | ||
|
ff9b26b831 | ||
|
8d31e39e89 | ||
|
397a16a9e3 | ||
|
59219fbd20 | ||
|
c953d41436 | ||
|
82bf80c5f4 | ||
|
87c7af7056 | ||
|
fa21de07f4 | ||
|
339faa46dc | ||
|
cdf92755cb | ||
|
7c04c1998f |
@@ -29,13 +29,20 @@ $ ninja -C ../eg25-build
|
||||
# ninja -C ../eg25-build install
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`eg25-manager` uses device-specific configuration files, named after the
|
||||
device-tree `compatible` field. These files are installed to
|
||||
`/usr/share/eg25-manager`. They can be copied to `/etc/eg25-manager` then
|
||||
modified, that way they won't be overwritten during an upgrade.
|
||||
|
||||
## Running
|
||||
|
||||
`eg25-manager` is usually run as a systemd service, but can also be
|
||||
manually started from the command-line (requires root privileges):
|
||||
|
||||
```
|
||||
# eg25-manager
|
||||
# eg25manager
|
||||
```
|
||||
|
||||
## License
|
||||
|
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
|
24
data/meson.build
Normal file
24
data/meson.build
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
conf_files = [
|
||||
'pine64,pinephone-1.0.toml',
|
||||
'pine64,pinephone-1.1.toml',
|
||||
'pine64,pinephone-1.2.toml',
|
||||
'pine64,pinephone-pro.toml',
|
||||
]
|
||||
|
||||
install_data(conf_files)
|
||||
|
||||
serviceconf = configuration_data()
|
||||
serviceconf.set('bindir', bindir)
|
||||
configure_file(
|
||||
input: 'eg25-manager.service.in',
|
||||
output: 'eg25-manager.service',
|
||||
install_dir: systemdsystemdir,
|
||||
configuration: serviceconf,
|
||||
install: true
|
||||
)
|
101
data/pine64,pinephone-1.0.toml
Normal file
101
data/pine64,pinephone-1.0.toml
Normal file
@@ -0,0 +1,101 @@
|
||||
[manager]
|
||||
monitor_udev = true
|
||||
need_libusb = true
|
||||
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 = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
|
||||
dtr = { chip = 1, line = 6 }
|
||||
pwrkey = { chip = 0, line = 35 }
|
||||
reset = { chip = 0, line = 68 }
|
||||
apready = { chip = 0, line = 231 }
|
||||
disable = { chip = 0, line = 232 }
|
||||
|
||||
[at]
|
||||
uart = "/dev/ttyS2"
|
||||
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 = "1,1,0,1,0,0,1,1" },
|
||||
# RI signaling using physical RI pin
|
||||
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
|
||||
# Enable VoLTE support
|
||||
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
|
||||
# Disable APREADY for PP 1.0 because pin is not connected
|
||||
{ cmd = "QCFG", subcmd = "apready", expect = "0,0,500" },
|
||||
# URC configuration for PP 1.0 (APREADY pin not connected):
|
||||
# * RING URC: extend pulse length
|
||||
# * Incoming SMS URC: extend pulse length
|
||||
# * Other URC: extend pulse length
|
||||
# * Report URCs on all ports (serial and USB) for FOSS firmware
|
||||
# * Delay reporting of URCs
|
||||
# * Configure URC pin to UART Ring Indicator
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",2000,1000,5000,\"off\",1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000,1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
|
||||
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
||||
# 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"
|
101
data/pine64,pinephone-1.1.toml
Normal file
101
data/pine64,pinephone-1.1.toml
Normal file
@@ -0,0 +1,101 @@
|
||||
[manager]
|
||||
monitor_udev = true
|
||||
need_libusb = true
|
||||
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 = [ "1c20800.pinctrl", "1f02c00.pinctrl" ]
|
||||
dtr = { chip = 1, line = 6 }
|
||||
pwrkey = { chip = 0, line = 35 }
|
||||
reset = { chip = 0, line = 68 }
|
||||
apready = { chip = 0, line = 231 }
|
||||
disable = { chip = 0, line = 232 }
|
||||
|
||||
[at]
|
||||
uart = "/dev/ttyS2"
|
||||
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 = "1,1,0,1,0,0,1,1" },
|
||||
# RI signaling using physical RI pin
|
||||
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
|
||||
# Enable VoLTE support
|
||||
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
|
||||
# Disable APREADY for PP 1.1 because pin is not connected
|
||||
{ cmd = "QCFG", subcmd = "apready", expect = "0,0,500" },
|
||||
# URC configuration for PP 1.1 (APREADY pin not connected):
|
||||
# * RING URC: extend pulse length
|
||||
# * Incoming SMS URC: extend pulse length
|
||||
# * Other URC: extend pulse length
|
||||
# * Report URCs on all ports (serial and USB) for FOSS firmware
|
||||
# * Delay reporting of URCs
|
||||
# * Configure URC pin to UART Ring Indicator
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",2000,1000,5000,\"off\",1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",2000,1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
|
||||
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
||||
# 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"
|
100
data/pine64,pinephone-1.2.toml
Normal file
100
data/pine64,pinephone-1.2.toml
Normal file
@@ -0,0 +1,100 @@
|
||||
[manager]
|
||||
monitor_udev = true
|
||||
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 = [ "1c20800.pinctrl" ]
|
||||
dtr = { chip = 0, line = 34 }
|
||||
pwrkey = { chip = 0, line = 35 }
|
||||
reset = { chip = 0, line = 68 }
|
||||
apready = { chip = 0, line = 231 }
|
||||
disable = { chip = 0, line = 232 }
|
||||
status = { chip = 0, line = 233 }
|
||||
|
||||
[at]
|
||||
uart = "/dev/ttyS2"
|
||||
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 = "1,1,0,1,0,0,1,1" },
|
||||
# RI signaling using physical RI pin
|
||||
{ cmd = "QCFG", subcmd = "risignaltype", expect = "\"physical\"" },
|
||||
# Enable VoLTE support
|
||||
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
|
||||
# Enable APREADY for PP 1.2
|
||||
{ cmd = "QCFG", subcmd = "apready", expect = "1,0,500" },
|
||||
# URC configuration for PP 1.2 (APREADY pin connected):
|
||||
# * RING URC: normal pulse length
|
||||
# * Incoming SMS URC: default pulse length
|
||||
# * Other URC: default length
|
||||
# * Report URCs on all ports (serial and USB) for FOSS firmware
|
||||
# * Reporting of URCs without any delay
|
||||
# * Configure URC pin to UART Ring Indicator
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/ring", expect = "\"pulse\",120,1000,5000,\"off\",1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/smsincoming", expect = "\"pulse\",120,1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1,1" },
|
||||
{ cmd = "QCFG", subcmd = "urc/delay", expect = "0" },
|
||||
{ cmd = "QCFG", subcmd = "urc/cache", expect = "0" },
|
||||
{ cmd = "QCFG", subcmd = "urc/ri/pin", expect = "uart_ri" },
|
||||
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
||||
# 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"
|
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"
|
122
debian/changelog
vendored
Normal file
122
debian/changelog
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
eg25-manager (0.4.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 0.4.3
|
||||
* d/control: build-depend on `scdoc`
|
||||
This is required for building the manpages. Also bump Standards-Version,
|
||||
no other changes needed.
|
||||
* d/watch: fix watch file
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Sat, 19 Feb 2022 15:20:14 +0100
|
||||
|
||||
eg25-manager (0.4.2-1) unstable; urgency=medium
|
||||
|
||||
* d/gbp.conf: update for current Mobian workflow
|
||||
* New upstream version 0.4.2
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@collabora.com> Wed, 08 Dec 2021 18:24:38 +0100
|
||||
|
||||
eg25-manager (0.4.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 0.4.1
|
||||
* debian: drop distro-specific systemd service.
|
||||
* d/copyright: add missing entries
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@collabora.com> Fri, 08 Oct 2021 11:02:27 +0200
|
||||
|
||||
eg25-manager (0.4.0-1) unstable; urgency=medium
|
||||
|
||||
[ undef ]
|
||||
* d/service: Use systemd to sandbox eg25-manager.
|
||||
* d/salsa-ci: Add Mobian's CI
|
||||
|
||||
[ Arnaud Ferraris ]
|
||||
* New upstream version 0.4.0
|
||||
* d/eg25-manager.service: be less restrictive.
|
||||
* d/control: add libcurl as build dependency
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Wed, 01 Sep 2021 00:44:04 +0200
|
||||
|
||||
eg25-manager (0.3.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 0.3.0
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@collabora.com> Fri, 28 May 2021 13:58:33 +0200
|
||||
|
||||
eg25-manager (0.2.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 0.2.1
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Sun, 21 Feb 2021 16:41:31 +0100
|
||||
|
||||
eg25-manager (0.2.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 0.2.0
|
||||
* d/gbp.conf: enable multimaint-merge
|
||||
* d/eg25-manager.service: remove deprecated -g option
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Sat, 20 Feb 2021 22:26:19 +0100
|
||||
|
||||
eg25-manager (0.1.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release 0.1.2
|
||||
* d/eg25-manager.service: enable GNSS management
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 14 Jan 2021 00:09:23 +0100
|
||||
|
||||
eg25-manager (0.1.1-1) unstable; urgency=medium
|
||||
|
||||
* d/control: build-depend on gudev
|
||||
* New upstream release 0.1.1
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 18 Dec 2020 01:42:06 +0100
|
||||
|
||||
eg25-manager (0.1.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release 0.1.0
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Mon, 14 Dec 2020 16:45:20 +0100
|
||||
|
||||
eg25-manager (0.0.6-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release 0.0.6
|
||||
* d/patches: drop upstreamed patches
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 15:11:26 +0100
|
||||
|
||||
eg25-manager (0.0.5-2) unstable; urgency=medium
|
||||
|
||||
* d/patches: fix crash on modem recovery
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 14:33:14 +0100
|
||||
|
||||
eg25-manager (0.0.5-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release 0.0.5
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 13:38:41 +0100
|
||||
|
||||
eg25-manager (0.0.4-1) unstable; urgency=medium
|
||||
|
||||
* d/control: build only on arm64.
|
||||
* d/service: restart daemon on failure
|
||||
* d/control: build-depend on libusb-1.0
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Fri, 11 Dec 2020 12:51:41 +0100
|
||||
|
||||
eg25-manager (0.0.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release 0.0.3
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 10 Dec 2020 21:34:26 +0100
|
||||
|
||||
eg25-manager (0.0.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release 0.0.2
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 10 Dec 2020 19:50:50 +0100
|
||||
|
||||
eg25-manager (0.0.1-1) unstable; urgency=medium
|
||||
|
||||
* Initial Debian packaging
|
||||
|
||||
-- Arnaud Ferraris <arnaud.ferraris@gmail.com> Thu, 10 Dec 2020 15:19:15 +0100
|
29
debian/control
vendored
Normal file
29
debian/control
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
Source: eg25-manager
|
||||
Section: libs
|
||||
Priority: optional
|
||||
Maintainer: Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
Standards-Version: 4.6.0
|
||||
Rules-Requires-Root: no
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
libcurl-dev | libcurl4-nss-dev,
|
||||
libglib2.0-dev,
|
||||
libgpiod-dev,
|
||||
libgudev-1.0-dev,
|
||||
libmm-glib-dev,
|
||||
libusb-1.0-0-dev,
|
||||
meson,
|
||||
scdoc <!nodoc>,
|
||||
Homepage: https://gitlab.com/mobian1/devices/eg25-manager
|
||||
Vcs-Git: https://gitlab.com/mobian1/devices/eg25-manager.git
|
||||
Vcs-Browser: https://gitlab.com/mobian1/devices/eg25-manager
|
||||
|
||||
Package: eg25-manager
|
||||
Architecture: arm64
|
||||
Depends: ${misc:Depends},
|
||||
${shlibs:Depends},
|
||||
modemmanager,
|
||||
Conflicts: pinephone-modem-scripts,
|
||||
Replaces: pinephone-modem-scripts,
|
||||
Description: Manager daemon for the Quectel EG25 mobile broadband modem
|
||||
A set of scripts for the PinePhone modem, allowing to power on/off the modem,
|
||||
and configure its audio interface to work properly with the A64 audio codec.
|
67
debian/copyright
vendored
Normal file
67
debian/copyright
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: eg25-manager
|
||||
Upstream-Contact: Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
Source: https://gitlab.com/mobian1/devices/eg25-manager
|
||||
|
||||
Files: *
|
||||
Copyright: 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: src/gnss.*
|
||||
Copyright: 2021 Dylan Van Assche <me@dylanvanassche.be>
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: src/mm-iface.c
|
||||
Copyright: 2019 Purism SPC
|
||||
2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: src/ofono-iface.c
|
||||
Copyright: 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
2021 Bhushan Shah <bshah@kde.org>
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: src/suspend.c
|
||||
Copyright: 2012 Red Hat, Inc
|
||||
2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: src/toml.*
|
||||
Copyright: 2017-2019 CK Tan
|
||||
License: MIT
|
||||
|
||||
License: GPL-3.0-or-later
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
.
|
||||
On Debian systems, the full text of the GNU General Public License
|
||||
version 3 can be found in the file `/usr/share/common-licenses/GPL-3'.
|
||||
|
||||
License: MIT
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
10
debian/gbp.conf
vendored
Normal file
10
debian/gbp.conf
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
[DEFAULT]
|
||||
debian-branch = mobian
|
||||
debian-tag = mobian/%(version)s
|
||||
upstream-branch = master
|
||||
upstream-tag = %(version)s
|
||||
pristine-tar = True
|
||||
multimaint-merge = True
|
||||
|
||||
[tag]
|
||||
sign-tags = True
|
6
debian/rules
vendored
Executable file
6
debian/rules
vendored
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||
|
||||
%:
|
||||
dh $@
|
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@@ -0,0 +1 @@
|
||||
3.0 (quilt)
|
3
debian/watch
vendored
Normal file
3
debian/watch
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
version=4
|
||||
https://gitlab.com/mobian1/devices/@PACKAGE@/-/tags?sort=updated_desc \
|
||||
.*/archive/\d\S+/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@
|
106
doc/eg25-manager.5.scd
Normal file
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
|
29
meson.build
29
meson.build
@@ -6,11 +6,11 @@
|
||||
#
|
||||
|
||||
project (
|
||||
'eg25manager',
|
||||
'eg25-manager',
|
||||
'c',
|
||||
version : '0.0.1',
|
||||
version : '0.4.3',
|
||||
license : 'GPLv3+',
|
||||
meson_version : '>= 0.50.0',
|
||||
meson_version : '>= 0.58.0',
|
||||
default_options :
|
||||
[
|
||||
'warning_level=1',
|
||||
@@ -27,6 +27,9 @@ prefix = get_option('prefix')
|
||||
datadir = get_option('datadir')
|
||||
sysconfdir = get_option('sysconfdir')
|
||||
bindir = join_paths(prefix, get_option('bindir'))
|
||||
udevrulesdir = join_paths(prefix, 'lib/udev/rules.d')
|
||||
systemddir = join_paths(prefix, 'lib/systemd')
|
||||
systemdsystemdir = join_paths(systemddir, 'system')
|
||||
|
||||
if datadir.startswith('/')
|
||||
full_datadir = datadir
|
||||
@@ -40,11 +43,29 @@ else
|
||||
full_sysconfdir = join_paths(prefix, sysconfdir)
|
||||
endif
|
||||
|
||||
eg25_confdir = join_paths(full_sysconfdir, meson.project_name())
|
||||
eg25_datadir = join_paths(full_datadir, meson.project_name())
|
||||
|
||||
add_global_arguments('-D@0@="@1@"'.format('EG25_CONFDIR', eg25_confdir), language : 'c')
|
||||
add_global_arguments('-D@0@="@1@"'.format('EG25_DATADIR', eg25_datadir), language : 'c')
|
||||
add_global_arguments('-D@0@="@1@"'.format('EG25_VERSION', meson.project_version()), language : 'c')
|
||||
|
||||
mmglib_dep = dependency('mm-glib', required : false)
|
||||
if mmglib_dep.found()
|
||||
add_global_arguments('-DHAVE_MMGLIB=1', language : 'c')
|
||||
endif
|
||||
|
||||
mgr_deps = [
|
||||
dependency('glib-2.0'),
|
||||
dependency('gio-unix-2.0'),
|
||||
dependency('gudev-1.0'),
|
||||
dependency('libgpiod'),
|
||||
dependency('mm-glib'),
|
||||
dependency('libusb-1.0'),
|
||||
dependency('libcurl'),
|
||||
mmglib_dep,
|
||||
]
|
||||
|
||||
subdir('data')
|
||||
subdir('doc')
|
||||
subdir('src')
|
||||
subdir('udev')
|
||||
|
406
src/at.c
406
src/at.c
@@ -5,8 +5,12 @@
|
||||
*/
|
||||
|
||||
#include "at.h"
|
||||
#include "config.h"
|
||||
#include "suspend.h"
|
||||
#include "gpio.h"
|
||||
#include "gnss.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -16,22 +20,17 @@
|
||||
|
||||
#include <glib-unix.h>
|
||||
|
||||
#define MODEM_UART "/dev/ttyS2"
|
||||
|
||||
struct AtCommand {
|
||||
char *cmd;
|
||||
char *subcmd;
|
||||
char *value;
|
||||
char *expected;
|
||||
int retries;
|
||||
};
|
||||
static GArray *configure_commands = NULL;
|
||||
static GArray *suspend_commands = NULL;
|
||||
static GArray *resume_commands = NULL;
|
||||
static GArray *reset_commands = NULL;
|
||||
|
||||
static int configure_serial(const char *tty)
|
||||
{
|
||||
struct termios ttycfg;
|
||||
int fd;
|
||||
|
||||
fd = open(tty, O_RDWR | O_NOCTTY);
|
||||
fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
if (fd > 0) {
|
||||
tcgetattr(fd, &ttycfg);
|
||||
ttycfg.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
|
||||
@@ -49,101 +48,170 @@ static int configure_serial(const char *tty)
|
||||
return fd;
|
||||
}
|
||||
|
||||
static gboolean send_at_command(struct EG25Manager *manager)
|
||||
static void at_free_command(gpointer cmd, gpointer data)
|
||||
{
|
||||
struct AtCommand *at_cmd = cmd;
|
||||
struct EG25Manager *manager = data;
|
||||
|
||||
if (!at_cmd)
|
||||
return;
|
||||
|
||||
g_free(at_cmd->cmd);
|
||||
g_free(at_cmd->subcmd);
|
||||
g_free(at_cmd->value);
|
||||
g_free(at_cmd->expected);
|
||||
g_free(at_cmd);
|
||||
|
||||
if (manager && manager->at_cmds)
|
||||
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
|
||||
}
|
||||
|
||||
gboolean at_send_command(struct EG25Manager *manager)
|
||||
{
|
||||
char command[256];
|
||||
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
|
||||
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
||||
int ret, len = 0, pos = 0;
|
||||
|
||||
if (at_cmd) {
|
||||
if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
|
||||
sprintf(command, "AT+%s?\r\n", at_cmd->cmd);
|
||||
/* Wake up the modem from soft sleep before sending an AT command */
|
||||
gpio_sequence_wake(manager);
|
||||
|
||||
/* Send AT command */
|
||||
if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL)
|
||||
len = snprintf(command, sizeof(command), "AT+%s\r\n", at_cmd->cmd);
|
||||
else if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
|
||||
len = snprintf(command, sizeof(command), "AT+%s?\r\n", at_cmd->cmd);
|
||||
else if (at_cmd->subcmd == NULL && at_cmd->value)
|
||||
sprintf(command, "AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
|
||||
len = snprintf(command, sizeof(command),"AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
|
||||
else if (at_cmd->subcmd && at_cmd->value == NULL)
|
||||
sprintf(command, "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
|
||||
len = snprintf(command, sizeof(command), "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
|
||||
else if (at_cmd->subcmd && at_cmd->value)
|
||||
sprintf(command, "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
|
||||
len = snprintf(command, sizeof(command), "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
|
||||
|
||||
write(manager->at_fd, command, strlen(command));
|
||||
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;
|
||||
}
|
||||
|
||||
g_message("Sending command: %s", g_strstrip(command));
|
||||
} else if (manager->modem_state < EG25_STATE_CONFIGURED) {
|
||||
MMModemState modem_state = mm_modem_get_state(manager->mm_modem);
|
||||
manager->at_callback = at_cmd->callback;
|
||||
|
||||
if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED)
|
||||
modem_update_state(manager, modem_state);
|
||||
else
|
||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||
} else if (manager->modem_state == EG25_STATE_SUSPENDING) {
|
||||
g_message("suspend sequence is over, drop inhibitor");
|
||||
suspend_inhibit(manager, FALSE);
|
||||
} else if (manager->modem_state == EG25_STATE_RESETTING) {
|
||||
manager->modem_state = EG25_STATE_POWERED;
|
||||
do {
|
||||
ret = write(manager->at_fd, &command[pos], len);
|
||||
|
||||
if (ret < 0) {
|
||||
switch (errno) {
|
||||
case EAGAIN:
|
||||
case EINTR:
|
||||
/* Try again. */
|
||||
break;
|
||||
|
||||
default:
|
||||
g_warning("error sending AT command: %s", strerror(errno));
|
||||
at_next_command(manager);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
len -= ret;
|
||||
pos += ret;
|
||||
}
|
||||
} while (len > 0);
|
||||
|
||||
g_message("Successfully sent command: %s", g_strstrip(command));
|
||||
} else {
|
||||
/* Allow the modem to enter soft sleep again when we sent the AT command*/
|
||||
gpio_sequence_sleep(manager);
|
||||
|
||||
if (manager->modem_state < EG25_STATE_CONFIGURED) {
|
||||
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
||||
#ifdef HAVE_MMGLIB
|
||||
if (manager->modem_state == EG25_STATE_ACQUIRED) {
|
||||
MMModemState modem_state = mm_modem_get_state(manager->mm_modem);
|
||||
|
||||
if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED)
|
||||
modem_update_state(manager, modem_state);
|
||||
else
|
||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||
}
|
||||
} else if (manager->modem_state == EG25_STATE_SUSPENDING) {
|
||||
modem_suspend_post(manager);
|
||||
} else if (manager->modem_state == EG25_STATE_RESETTING) {
|
||||
manager->modem_state = EG25_STATE_POWERED;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void next_at_command(struct EG25Manager *manager)
|
||||
void at_next_command(struct EG25Manager *manager)
|
||||
{
|
||||
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
|
||||
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
||||
|
||||
if (at_cmd->cmd)
|
||||
g_free(at_cmd->cmd);
|
||||
if (at_cmd->subcmd)
|
||||
g_free(at_cmd->subcmd);
|
||||
if (at_cmd->value)
|
||||
g_free(at_cmd->value);
|
||||
if (at_cmd->expected)
|
||||
g_free(at_cmd->expected);
|
||||
g_free(at_cmd);
|
||||
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
|
||||
|
||||
send_at_command(manager);
|
||||
at_free_command(at_cmd, manager);
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
static void retry_at_command(struct EG25Manager *manager)
|
||||
{
|
||||
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
|
||||
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
||||
|
||||
if (!at_cmd)
|
||||
return;
|
||||
|
||||
at_cmd->retries++;
|
||||
if (at_cmd->retries > 3) {
|
||||
g_critical("Command %s retried %d times, aborting...", at_cmd->cmd, at_cmd->retries);
|
||||
next_at_command(manager);
|
||||
at_next_command(manager);
|
||||
} else {
|
||||
g_timeout_add_seconds(3, G_SOURCE_FUNC(send_at_command), manager);
|
||||
g_timeout_add(500, G_SOURCE_FUNC(at_send_command), manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void process_at_result(struct EG25Manager *manager, char *response)
|
||||
void at_process_result(struct EG25Manager *manager,
|
||||
const char *response)
|
||||
{
|
||||
struct AtCommand *at_cmd = g_list_nth_data(manager->at_cmds, 0);
|
||||
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
||||
|
||||
if (!at_cmd)
|
||||
return;
|
||||
|
||||
if (at_cmd->expected && !strstr(response, at_cmd->expected)) {
|
||||
if (at_cmd->value)
|
||||
g_free(at_cmd->value);
|
||||
g_free(at_cmd->value);
|
||||
at_cmd->value = at_cmd->expected;
|
||||
at_cmd->expected = NULL;
|
||||
g_message("Got a different result than expected, changing value...");
|
||||
g_message("\t%s\n\t%s", at_cmd->expected, response);
|
||||
send_at_command(manager);
|
||||
g_message("Expected: [%s]\nResponse: [%s]", at_cmd->expected, response);
|
||||
at_cmd->expected = NULL;
|
||||
at_send_command(manager);
|
||||
} else {
|
||||
next_at_command(manager);
|
||||
at_next_command(manager);
|
||||
}
|
||||
}
|
||||
|
||||
static int append_at_command(struct EG25Manager *manager,
|
||||
const char *cmd,
|
||||
const char *subcmd,
|
||||
const char *value,
|
||||
const char *expected)
|
||||
int at_append_command(struct EG25Manager *manager,
|
||||
const char *cmd,
|
||||
const char *subcmd,
|
||||
const char *value,
|
||||
const char *expected,
|
||||
void (*callback)
|
||||
(struct EG25Manager *manager,
|
||||
const char *response))
|
||||
{
|
||||
struct AtCommand *at_cmd = calloc(1, sizeof(struct AtCommand));
|
||||
|
||||
if (!at_cmd)
|
||||
return -1;
|
||||
|
||||
at_cmd->retries = 0;
|
||||
at_cmd->cmd = g_strdup(cmd);
|
||||
if (subcmd)
|
||||
at_cmd->subcmd = g_strdup(subcmd);
|
||||
@@ -151,117 +219,227 @@ static int append_at_command(struct EG25Manager *manager,
|
||||
at_cmd->value = g_strdup(value);
|
||||
if (expected)
|
||||
at_cmd->expected = g_strdup(expected);
|
||||
if (callback)
|
||||
at_cmd->callback = callback;
|
||||
|
||||
manager->at_cmds = g_list_append(manager->at_cmds, at_cmd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define READ_BUFFER_SIZE 256
|
||||
|
||||
static gboolean modem_response(gint fd,
|
||||
GIOCondition event,
|
||||
gpointer data)
|
||||
{
|
||||
struct EG25Manager *manager = data;
|
||||
char response[256];
|
||||
int ret;
|
||||
char response[READ_BUFFER_SIZE*4+1];
|
||||
char tmp[READ_BUFFER_SIZE];
|
||||
ssize_t ret, pos = 0;
|
||||
|
||||
/*
|
||||
* TODO: several reads can be necessary to get the full response, we could
|
||||
* loop until we read 0 chars with a reasonable delay between attempts
|
||||
* Several reads can be necessary to get the full response, so we loop
|
||||
* until we read 0 chars with a reasonable delay between attempts
|
||||
* (remember the transfer rate is 115200 here)
|
||||
*/
|
||||
ret = read(fd, response, sizeof(response));
|
||||
if (ret > 0) {
|
||||
response[ret] = 0;
|
||||
do {
|
||||
ret = read(fd, tmp, sizeof(tmp));
|
||||
|
||||
if (ret > 0) {
|
||||
/* If we're going to overflow truncate the data we read to fit */
|
||||
if (pos + ret >= sizeof(response)) {
|
||||
g_critical("AT response buffer full, truncating");
|
||||
ret = sizeof(response) - (pos + 1);
|
||||
}
|
||||
|
||||
memcpy(&response[pos], tmp, ret);
|
||||
pos += ret;
|
||||
usleep(10000);
|
||||
}
|
||||
} while (ret > 0 && pos < (sizeof(response) - 1));
|
||||
|
||||
if (pos > 0) {
|
||||
g_autofree gchar *escaped = NULL;
|
||||
|
||||
response[pos] = 0;
|
||||
g_strstrip(response);
|
||||
if (strlen(response) == 0)
|
||||
return TRUE;
|
||||
|
||||
g_message("Response: [%s]", response);
|
||||
escaped = g_strescape(response, "\"");
|
||||
g_message("Response: [%s]", escaped);
|
||||
|
||||
if (strcmp(response, "RDY") == 0)
|
||||
/*
|
||||
* When the modem is started, it outputs 'RDY' to indicate that
|
||||
* it is ready to receive AT commands.
|
||||
*/
|
||||
if (strcmp(response, "RDY") == 0) {
|
||||
suspend_inhibit(manager, TRUE, TRUE);
|
||||
manager->modem_state = EG25_STATE_STARTED;
|
||||
else if (strcmp(response, "ERROR") == 0)
|
||||
}
|
||||
/*
|
||||
* Search for 'QGPSURC: "xtradataexpire"' in response to detect
|
||||
* if GNSS assistance data must be re-uploaded.
|
||||
* If that's the case, check if no AT commands are being processed
|
||||
* to avoid deadlocks and start upload.
|
||||
*/
|
||||
else if (strstr(response, "QGPSURC: \"xtradataexpire\"") && manager->at_cmds == NULL)
|
||||
gnss_upload_assistance_data(manager);
|
||||
/*
|
||||
* AT command failed, retry.
|
||||
* QCFG="fast/poweroff" configuration is only available in
|
||||
* newer firmware versions, skip retrying that specific AT command.
|
||||
*/
|
||||
else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff"))
|
||||
retry_at_command(manager);
|
||||
else if (strstr(response, "OK"))
|
||||
process_at_result(manager, response);
|
||||
/*
|
||||
* Successful AT responses contain 'OK', except for AT+QFUPL which also
|
||||
* returns 'CONNECT' when the modem is ready to receive data over serial
|
||||
* and '+QFUPL:...' when data upload is complete
|
||||
*/
|
||||
else if (strstr(response, "OK") || strstr(response, "CONNECT") || strstr(response, "QFUPL")) {
|
||||
if (manager->at_callback != NULL)
|
||||
manager->at_callback(manager, response);
|
||||
else
|
||||
g_warning("AT command successful but no callback registered");
|
||||
}
|
||||
/* Not a recognized response, try running next command, just in case */
|
||||
else
|
||||
at_next_command(manager);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int at_init(struct EG25Manager *manager)
|
||||
static void parse_commands_list(toml_array_t *array, GArray **cmds)
|
||||
{
|
||||
manager->at_fd = configure_serial(MODEM_UART);
|
||||
int len;
|
||||
|
||||
len = toml_array_nelem(array);
|
||||
*cmds = g_array_new(FALSE, TRUE, sizeof(struct AtCommand));
|
||||
g_array_set_size(*cmds, (guint)len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
struct AtCommand *cmd = &g_array_index(*cmds, struct AtCommand, i);
|
||||
toml_table_t *table = toml_table_at(array, i);
|
||||
toml_datum_t value;
|
||||
|
||||
if (!table)
|
||||
continue;
|
||||
|
||||
value = toml_string_in(table, "cmd");
|
||||
if (value.ok)
|
||||
cmd->cmd = value.u.s;
|
||||
|
||||
value = toml_string_in(table, "subcmd");
|
||||
if (value.ok)
|
||||
cmd->subcmd = value.u.s;
|
||||
|
||||
value = toml_string_in(table, "value");
|
||||
if (value.ok)
|
||||
cmd->value = value.u.s;
|
||||
|
||||
value = toml_string_in(table, "expect");
|
||||
if (value.ok)
|
||||
cmd->expected = value.u.s;
|
||||
}
|
||||
}
|
||||
|
||||
int at_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||
{
|
||||
toml_array_t *commands = NULL;
|
||||
gchar *uart_port = NULL;
|
||||
toml_table_t *at_config[EG25_CONFIG_COUNT];
|
||||
|
||||
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||
at_config[i] = config[i] ? toml_table_in(config[i], "at") : NULL;
|
||||
|
||||
if (!at_config[EG25_CONFIG_SYS])
|
||||
g_error("Default config file lacks the 'at' section!");
|
||||
|
||||
if (!config_get_string(at_config, "uart", &uart_port))
|
||||
g_error("Configuration file lacks UART port definition");
|
||||
|
||||
manager->at_fd = configure_serial(uart_port);
|
||||
if (manager->at_fd < 0) {
|
||||
g_critical("Unable to configure %s", MODEM_UART);
|
||||
g_critical("Unable to configure %s", uart_port);
|
||||
g_free(uart_port);
|
||||
return 1;
|
||||
}
|
||||
g_free(uart_port);
|
||||
|
||||
manager->at_source = g_unix_fd_add(manager->at_fd, G_IO_IN, modem_response, manager);
|
||||
|
||||
if (!config_get_array(at_config, "configure", &commands))
|
||||
g_error("Configuration file lacks initial AT commands list");
|
||||
parse_commands_list(commands, &configure_commands);
|
||||
|
||||
if (!config_get_array(at_config, "suspend", &commands))
|
||||
g_error("Configuration file lacks suspend AT commands list");
|
||||
parse_commands_list(commands, &suspend_commands);
|
||||
|
||||
if (!config_get_array(at_config, "resume", &commands))
|
||||
g_error("Configuration file lacks resume AT commands list");
|
||||
parse_commands_list(commands, &resume_commands);
|
||||
|
||||
if (!config_get_array(at_config, "reset", &commands))
|
||||
g_error("Configuration file lacks reset AT commands list");
|
||||
parse_commands_list(commands, &reset_commands);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void at_destroy(struct EG25Manager *manager)
|
||||
{
|
||||
g_source_remove(manager->at_source);
|
||||
close(manager->at_fd);
|
||||
if (manager->at_fd > 0)
|
||||
close(manager->at_fd);
|
||||
|
||||
g_array_free(configure_commands, TRUE);
|
||||
g_array_free(suspend_commands, TRUE);
|
||||
g_array_free(resume_commands, TRUE);
|
||||
g_array_free(reset_commands, TRUE);
|
||||
}
|
||||
|
||||
void at_sequence_configure(struct EG25Manager *manager)
|
||||
{
|
||||
/*
|
||||
* Default parameters in megi's driver which differ with our own:
|
||||
* - urc/ri/* are always set the same way on both BH and CE
|
||||
* - urc/ri/* pulse duration is 1 ms and urc/delay is 0 (no need to delay
|
||||
* URCs if the pulse is that short)
|
||||
* - apready is disabled
|
||||
*
|
||||
* Parameters set in megi's kernel but not here:
|
||||
* - sleepind/level = 0
|
||||
* - wakeupin/level = 0
|
||||
* - ApRstLevel = 0
|
||||
* - ModemRstLevel = 0
|
||||
* - airplanecontrol = 1
|
||||
* - fast/poweroff = 1
|
||||
* (those would need to be researched to check their usefulness for our
|
||||
* use-case)
|
||||
* 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);
|
||||
|
||||
append_at_command(manager, "QDAI", NULL, NULL, "1,1,0,1,0,0,1,1");
|
||||
append_at_command(manager, "QCFG", "risignaltype", NULL, "\"physical\"");
|
||||
append_at_command(manager, "QCFG", "ims", NULL, "1");
|
||||
if (manager->braveheart) {
|
||||
append_at_command(manager, "QCFG", "urc/ri/ring", NULL, "\"pulse\",2000,1000,5000,\"off\",1");
|
||||
append_at_command(manager, "QCFG", "urc/ri/smsincoming", NULL, "\"pulse\",2000");
|
||||
append_at_command(manager, "QCFG", "urc/ri/other", NULL, "\"off\",1");
|
||||
append_at_command(manager, "QCFG", "urc/delay", NULL, "1");
|
||||
} else {
|
||||
append_at_command(manager, "QCFG", "apready", NULL, "1,0,500");
|
||||
for (guint i = 0; i < configure_commands->len; 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);
|
||||
}
|
||||
append_at_command(manager, "QURCCFG", "urcport", NULL, "\"usbat\"");
|
||||
append_at_command(manager, "QGPS", NULL, NULL, "1");
|
||||
append_at_command(manager, "QSCLK", NULL, "1", NULL);
|
||||
send_at_command(manager);
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
void at_sequence_suspend(struct EG25Manager *manager)
|
||||
{
|
||||
append_at_command(manager, "QGPS", NULL, NULL, "0");
|
||||
append_at_command(manager, "QCFG", "urc/cache", "1", NULL);
|
||||
send_at_command(manager);
|
||||
for (guint i = 0; i < suspend_commands->len; i++) {
|
||||
struct AtCommand *cmd = &g_array_index(suspend_commands, struct AtCommand, i);
|
||||
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
|
||||
}
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
void at_sequence_resume(struct EG25Manager *manager)
|
||||
{
|
||||
append_at_command(manager, "QCFG", "urc/cache", "0", NULL);
|
||||
append_at_command(manager, "QGPS", NULL, NULL, "1");
|
||||
send_at_command(manager);
|
||||
for (guint i = 0; i < resume_commands->len; i++) {
|
||||
struct AtCommand *cmd = &g_array_index(resume_commands, struct AtCommand, i);
|
||||
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
|
||||
}
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
void at_sequence_reset(struct EG25Manager *manager)
|
||||
{
|
||||
append_at_command(manager, "CFUN", NULL, "1,1", NULL);
|
||||
send_at_command(manager);
|
||||
for (guint i = 0; i < reset_commands->len; i++) {
|
||||
struct AtCommand *cmd = &g_array_index(reset_commands, struct AtCommand, i);
|
||||
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
|
||||
}
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
34
src/at.h
34
src/at.h
@@ -8,10 +8,32 @@
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
int at_init(struct EG25Manager *data);
|
||||
void at_destroy(struct EG25Manager *data);
|
||||
typedef struct AtCommand {
|
||||
char *cmd;
|
||||
char *subcmd;
|
||||
char *value;
|
||||
char *expected;
|
||||
void (*callback)(struct EG25Manager *manager, const char *response);
|
||||
int retries;
|
||||
} AtCommand;
|
||||
|
||||
void at_sequence_configure(struct EG25Manager *data);
|
||||
void at_sequence_suspend(struct EG25Manager *data);
|
||||
void at_sequence_resume(struct EG25Manager *data);
|
||||
void at_sequence_reset(struct EG25Manager *data);
|
||||
int at_init(struct EG25Manager *manager, toml_table_t *config[]);
|
||||
void at_destroy(struct EG25Manager *manager);
|
||||
|
||||
void at_process_result(struct EG25Manager *manager,
|
||||
const char *response);
|
||||
void at_next_command(struct EG25Manager *manager);
|
||||
gboolean at_send_command(struct EG25Manager *manager);
|
||||
int at_append_command(struct EG25Manager *manager,
|
||||
const char *cmd,
|
||||
const char *subcmd,
|
||||
const char *value,
|
||||
const char *expected,
|
||||
void (*callback)
|
||||
(struct EG25Manager *manager,
|
||||
const char *response));
|
||||
|
||||
void at_sequence_configure(struct EG25Manager *manager);
|
||||
void at_sequence_suspend(struct EG25Manager *manager);
|
||||
void at_sequence_resume(struct EG25Manager *manager);
|
||||
void at_sequence_reset(struct EG25Manager *manager);
|
||||
|
97
src/config.c
Normal file
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);
|
515
src/gnss.c
Normal file
515
src/gnss.c
Normal file
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Dylan Van Assche <me@dylanvanassche.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "gnss.h"
|
||||
#include "manager.h"
|
||||
#include "at.h"
|
||||
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define BUFFER_SIZE 256
|
||||
#define UPLOAD_DELAY_US 25000
|
||||
#define UPLOAD_TIMEOUT_S 10
|
||||
#define RESCHEDULE_IN_SECS 30
|
||||
|
||||
static void gnss_step(struct EG25Manager *manager);
|
||||
|
||||
gboolean gnss_upload_assistance_data(struct EG25Manager *manager)
|
||||
{
|
||||
if (!manager->gnss_assistance_enabled) {
|
||||
g_message("GNSS assistance is disabled!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (manager->gnss_assistance_step < EG25_GNSS_STEP_LAST) {
|
||||
g_warning("GNSS assistance data upload already in process (%d/%d)",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* data upload isn't necessary to bring the modem onine, so we should wait
|
||||
* until we've finished the rest of our configuration */
|
||||
if (!manager->modem_iface ||
|
||||
manager->modem_state < EG25_STATE_CONFIGURED ||
|
||||
manager->modem_state > EG25_STATE_CONNECTED) {
|
||||
g_message ("Rescheduling upload since modem isn't online yet, in %ds",
|
||||
RESCHEDULE_IN_SECS);
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
/* ModemManager's Location is only available after unlocking */
|
||||
if(manager->modem_iface == MODEM_IFACE_MODEMMANAGER && !manager->mm_location) {
|
||||
g_message ("Rescheduling upload since Location interface is not available, in %ds",
|
||||
RESCHEDULE_IN_SECS);
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST;
|
||||
gnss_step(manager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void gnss_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||
{
|
||||
toml_table_t *gnss_config[EG25_CONFIG_COUNT];
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||
gnss_config[i] = config[i] ? toml_table_in(config[i], "gnss") : NULL;
|
||||
|
||||
if (!gnss_config[EG25_CONFIG_SYS])
|
||||
g_error("Default config file lacks the 'gnss' section!");
|
||||
|
||||
/*
|
||||
* GNSS assistance is an optional feature, you can disable it
|
||||
* if you want in the configuration file.
|
||||
* In case the configuration is missing, we assume GNSS assistance
|
||||
* to be disabled.
|
||||
*/
|
||||
config_get_bool(gnss_config, "enabled", &manager->gnss_assistance_enabled);
|
||||
|
||||
if (!manager->gnss_assistance_enabled) {
|
||||
g_message("GNSS assistance is disabled!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config_get_string(gnss_config, "url", &manager->gnss_assistance_url))
|
||||
g_error("GNSS assistance server URL is missing from config file");
|
||||
|
||||
if (!config_get_string(gnss_config, "file", &manager->gnss_assistance_file))
|
||||
g_error("GNSS assistance file name is missing from config file");
|
||||
|
||||
/* Create temporary file to store assistance data */
|
||||
manager->gnss_assistance_fd = g_file_open_tmp(NULL, NULL, &error);
|
||||
if (error != NULL)
|
||||
g_error ("Unable to create temporary file: %s", error->message);
|
||||
|
||||
/* Initialize state and schedule upload */
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
|
||||
G_SOURCE_FUNC(gnss_upload_assistance_data), manager);
|
||||
}
|
||||
|
||||
void gnss_destroy(struct EG25Manager *manager)
|
||||
{
|
||||
g_free(manager->gnss_assistance_url);
|
||||
g_free(manager->gnss_assistance_file);
|
||||
close(manager->gnss_assistance_fd);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
static void disable_mm_gnss(struct EG25Manager *manager)
|
||||
{
|
||||
MMModemLocationSource sources;
|
||||
gboolean signals_location;
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
sources = mm_modem_location_get_enabled(manager->mm_location);
|
||||
signals_location = mm_modem_location_signals_location(manager->mm_location);
|
||||
manager->gnss_sources = EG25_GNSS_SOURCE_NONE;
|
||||
|
||||
/* Save GNSS engine state */
|
||||
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)
|
||||
manager->gnss_sources |= EG25_GNSS_SOURCE_NMEA;
|
||||
else
|
||||
manager->gnss_sources &= ~EG25_GNSS_SOURCE_NMEA;
|
||||
|
||||
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)
|
||||
manager->gnss_sources |= EG25_GNSS_SOURCE_RAW;
|
||||
else
|
||||
manager->gnss_sources &= ~EG25_GNSS_SOURCE_RAW;
|
||||
|
||||
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)
|
||||
manager->gnss_sources |= EG25_GNSS_SOURCE_UNMANAGED;
|
||||
else
|
||||
manager->gnss_sources &= ~EG25_GNSS_SOURCE_UNMANAGED;
|
||||
|
||||
/* Disable GNSS engine */
|
||||
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_RAW;
|
||||
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
|
||||
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
|
||||
mm_modem_location_setup_sync(manager->mm_location, sources,
|
||||
signals_location, NULL, &error);
|
||||
if (error != NULL) {
|
||||
g_warning("Unable to disable GNSS engine through ModemManager: %s",
|
||||
error->message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void disable_at_gnss_cb(struct EG25Manager *manager,
|
||||
const char *response)
|
||||
{
|
||||
/* Clear QGPSEND AT command and process next */
|
||||
at_next_command(manager);
|
||||
|
||||
/* Go to the next step */
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
}
|
||||
|
||||
static void state_at_gnss_cb(struct EG25Manager *manager, const char *response)
|
||||
{
|
||||
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
|
||||
|
||||
if (!at_cmd)
|
||||
return;
|
||||
|
||||
/* Parse GNSS engine status and disable it if needed */
|
||||
if (strstr(response, "QGPS: 1")) {
|
||||
manager->gnss_sources |= EG25_GNSS_SOURCE_QGPS;
|
||||
g_free(at_cmd->value);
|
||||
g_free(at_cmd->cmd);
|
||||
at_cmd->expected = NULL;
|
||||
at_cmd->subcmd = NULL;
|
||||
at_cmd->value = NULL;
|
||||
at_cmd->cmd = g_strdup("QGPSEND");
|
||||
at_cmd->callback = disable_at_gnss_cb;
|
||||
at_send_command(manager);
|
||||
}
|
||||
/* QGPS is already disabled, move on to next step */
|
||||
else {
|
||||
at_next_command(manager);
|
||||
manager->gnss_sources &= ~EG25_GNSS_SOURCE_QGPS;
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void state_at_gnss(struct EG25Manager *manager)
|
||||
{
|
||||
/* Asynchronously send AT command to query status of GNSS engine */
|
||||
at_append_command(manager, "QGPS?", NULL, NULL, NULL, state_at_gnss_cb);
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static void fetch_assistance_data(struct EG25Manager *manager)
|
||||
{
|
||||
CURLcode response;
|
||||
curl_off_t downloaded;
|
||||
CURL *curl = NULL;
|
||||
g_autofree gchar *url = NULL;
|
||||
FILE *tmp_file = NULL;
|
||||
gchar errbuf[CURL_ERROR_SIZE];
|
||||
errbuf[0] = 0;
|
||||
|
||||
/* Fetch assistance data with curl */
|
||||
tmp_file = fdopen(manager->gnss_assistance_fd, "wb+");
|
||||
if (tmp_file == NULL) {
|
||||
g_critical("Unable to open file to save assistance data: %s",
|
||||
g_strerror(errno));
|
||||
goto bail;
|
||||
}
|
||||
|
||||
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
||||
if (ftruncate(manager->gnss_assistance_fd, 0) < 0)
|
||||
g_warning("Unable to truncate file, assistance data might be invalid!");
|
||||
url = g_strconcat(manager->gnss_assistance_url, "/",
|
||||
manager->gnss_assistance_file, NULL);
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
g_critical("Unable to initialize curl");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||
|
||||
response = curl_easy_perform(curl);
|
||||
if (response != CURLE_OK) {
|
||||
g_warning("Unable to fetch GNSS assistance data from %s: %s",
|
||||
url, strlen(errbuf) ? errbuf : curl_easy_strerror(response));
|
||||
goto bail;
|
||||
}
|
||||
|
||||
response = curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &downloaded);
|
||||
if (response) {
|
||||
g_critical("Unable to get number of downloaded bytes from curl");
|
||||
goto bail;
|
||||
} else if (downloaded <= 0) {
|
||||
g_warning("Downloaded empty assistance data file");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
g_message("Fetching GNSS assistance data from %s was successful", url);
|
||||
|
||||
fflush(tmp_file);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
/* Go to the next step */
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
return;
|
||||
|
||||
bail:
|
||||
if (curl != NULL)
|
||||
curl_easy_cleanup(curl);
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
static void init_assistance_data_upload_ready(struct EG25Manager *manager,
|
||||
const char *response)
|
||||
{
|
||||
/* Search for 'CONNECT' in response to start upload */
|
||||
if (strstr(response, "CONNECT")) {
|
||||
g_message("Modem ready for GNSS assistance data upload");
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
} else if (strstr(response, "QFUPL")) {
|
||||
/* Clear QFUPL AT command and process next */
|
||||
at_next_command(manager);
|
||||
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void init_assistance_data_upload_start(struct EG25Manager *manager,
|
||||
const char *response)
|
||||
{
|
||||
gchar value[BUFFER_SIZE];
|
||||
off_t size;
|
||||
|
||||
/* Process AT response */
|
||||
at_process_result(manager, response);
|
||||
|
||||
/* Get file size in bytes */
|
||||
size = lseek(manager->gnss_assistance_fd, 0, SEEK_END);
|
||||
if (size == -1) {
|
||||
g_critical("gnss: unable to read size of xtra data file: %s", g_strerror(errno));
|
||||
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||
return;
|
||||
}
|
||||
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
||||
|
||||
/* Start upload */
|
||||
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld,%d",
|
||||
manager->gnss_assistance_file, size, UPLOAD_TIMEOUT_S);
|
||||
g_message("Initiate GNSS assistance data upload: %s", value);
|
||||
at_append_command(manager, "QFUPL", NULL, value, NULL,
|
||||
init_assistance_data_upload_ready);
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
static void init_assistance_data_upload(struct EG25Manager *manager)
|
||||
{
|
||||
/*
|
||||
* Delete all previous GNSS assistance data files in RAM
|
||||
* and start uploading the latest one to RAM.
|
||||
*/
|
||||
at_append_command(manager, "QFDEL", NULL, "\"RAM:*\"\r\n",
|
||||
NULL, init_assistance_data_upload_start);
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
static void upload_assistance_data(struct EG25Manager *manager)
|
||||
{
|
||||
gint error;
|
||||
glong written_total = 0;
|
||||
gint ret;
|
||||
struct stat sb;
|
||||
|
||||
if (fstat(manager->gnss_assistance_fd, &sb) != 0) {
|
||||
g_critical("gnss: unable to stat xtra data file: %s", g_strerror(errno));
|
||||
|
||||
/* Make sure the upload times out and the modem goes back to AT command mode */
|
||||
sleep(UPLOAD_TIMEOUT_S + 1);
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
errno = 0;
|
||||
/* Copy downloaded XTRA assistance data to the modem over serial */
|
||||
ret = sendfile(manager->at_fd, manager->gnss_assistance_fd, &written_total, BUFFER_SIZE);
|
||||
error = errno;
|
||||
usleep(UPLOAD_DELAY_US);
|
||||
} while ((!error && written_total < sb.st_size) || (ret == -1 && error == EAGAIN));
|
||||
|
||||
/* Go to the next step if successful */
|
||||
if (!error) {
|
||||
g_message("Successfully uploaded %ld bytes to the modem", written_total);
|
||||
} else {
|
||||
g_critical("Unable to upload xtra data: %s", g_strerror(error));
|
||||
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||
}
|
||||
}
|
||||
|
||||
static void finish_assistance_data_upload_cb(struct EG25Manager *manager,
|
||||
const char *response)
|
||||
{
|
||||
/* Process response */
|
||||
at_process_result(manager, response);
|
||||
g_message("GNSS assistance data upload finished");
|
||||
|
||||
/* Go to the next step */
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
}
|
||||
|
||||
static void finish_assistance_data_upload(struct EG25Manager *manager)
|
||||
{
|
||||
gchar value[BUFFER_SIZE];
|
||||
GDateTime *datetime;
|
||||
gchar *timestring;
|
||||
|
||||
/* Configure GNSS assistance clock to current system time (UTC) */
|
||||
datetime = g_date_time_new_now_utc();
|
||||
timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"");
|
||||
g_message("Setting GNSS assistance UTC clock to: %s", timestring);
|
||||
at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL,
|
||||
at_process_result);
|
||||
|
||||
/* Configure GNSS engine to use uploaded GNSS assistance data */
|
||||
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"",
|
||||
manager->gnss_assistance_file);
|
||||
g_message("Setting GNSS assistance file to: %s", value);
|
||||
at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL,
|
||||
finish_assistance_data_upload_cb);
|
||||
at_send_command(manager);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
static void enable_mm_gnss(struct EG25Manager *manager)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
MMModemLocationSource sources = mm_modem_location_get_enabled(manager->mm_location);
|
||||
gboolean signal_location = mm_modem_location_signals_location(manager->mm_location);
|
||||
|
||||
if (manager->gnss_sources & EG25_GNSS_SOURCE_UNMANAGED)
|
||||
sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
|
||||
if (manager->gnss_sources & EG25_GNSS_SOURCE_NMEA)
|
||||
sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
|
||||
if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW)
|
||||
sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
|
||||
|
||||
mm_modem_location_setup_sync(manager->mm_location, sources,
|
||||
signal_location, NULL, &error);
|
||||
if (error != NULL)
|
||||
g_warning("Unable to enable GNSS engine through ModemManager: %s",
|
||||
error->message);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void enable_at_gnss_cb(struct EG25Manager *manager, const char *response)
|
||||
{
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
}
|
||||
|
||||
static void enable_at_gnss(struct EG25Manager *manager)
|
||||
{
|
||||
if (manager->gnss_sources & EG25_GNSS_SOURCE_QGPS) {
|
||||
at_append_command(manager, "QGPS", NULL, "1", NULL,
|
||||
enable_at_gnss_cb);
|
||||
at_send_command(manager);
|
||||
return;
|
||||
}
|
||||
manager->gnss_assistance_step++;
|
||||
gnss_step(manager);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
void gnss_step(struct EG25Manager *manager)
|
||||
{
|
||||
switch(manager->gnss_assistance_step) {
|
||||
case EG25_GNSS_STEP_FIRST:
|
||||
manager->gnss_assistance_step++;
|
||||
g_message("GNSS assistance upload started...");
|
||||
/* fall-through */
|
||||
|
||||
case EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA:
|
||||
g_message("GNSS assistance upload step (%d/%d): "
|
||||
"fetching assistance data",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
fetch_assistance_data(manager);
|
||||
break;
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
case EG25_GNSS_STEP_MM_GNSS_DISABLE:
|
||||
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
||||
g_message("GNSS assistance upload step (%d/%d): "
|
||||
"disabling GNSS engine through ModemManager",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
disable_mm_gnss(manager);
|
||||
}
|
||||
manager->gnss_assistance_step++;
|
||||
/* fall-through */
|
||||
#endif
|
||||
|
||||
case EG25_GNSS_STEP_AT_GNSS_DISABLE:
|
||||
g_message("GNSS assistance upload step (%d/%d): "
|
||||
"disabling GNSS engine through AT+QGPS",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
state_at_gnss(manager);
|
||||
break;
|
||||
|
||||
case EG25_GNSS_STEP_INIT_UPLOAD:
|
||||
g_message("GNSS assistance upload step (%d/%d): initiating upload",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
init_assistance_data_upload(manager);
|
||||
break;
|
||||
|
||||
case EG25_GNSS_STEP_UPLOAD:
|
||||
g_message("GNSS assistance upload step (%d/%d): "
|
||||
"uploading assistance data",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
upload_assistance_data(manager);
|
||||
break;
|
||||
|
||||
case EG25_GNSS_STEP_FINISH_UPLOAD:
|
||||
g_message("GNSS assistance upload step (%d/%d): finishing upload",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
finish_assistance_data_upload(manager);
|
||||
break;
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
case EG25_GNSS_STEP_MM_GNSS_ENABLE:
|
||||
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
||||
g_message("GNSS assistance upload step (%d/%d): "
|
||||
"re-enabling GNSS through ModemManager",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
enable_mm_gnss(manager);
|
||||
}
|
||||
manager->gnss_assistance_step++;
|
||||
/* fall-through */
|
||||
#endif
|
||||
|
||||
case EG25_GNSS_STEP_AT_QGPS_ENABLE:
|
||||
g_message("GNSS assistance upload step (%d/%d): "
|
||||
"re-enabling GNSS through AT+QGPS",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
enable_at_gnss(manager);
|
||||
break;
|
||||
|
||||
case EG25_GNSS_STEP_LAST:
|
||||
g_message("GNSS assistance upload step (%d/%d): finished",
|
||||
manager->gnss_assistance_step, EG25_GNSS_STEP_LAST);
|
||||
break;
|
||||
}
|
||||
}
|
17
src/gnss.h
Normal file
17
src/gnss.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Dylan Van Assche <me@dylanvanassche.be>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
void gnss_init(struct EG25Manager *manager, toml_table_t *config[]);
|
||||
void gnss_destroy(struct EG25Manager *manager);
|
||||
gboolean gnss_upload_assistance_data(struct EG25Manager *manager);
|
291
src/gpio.c
291
src/gpio.c
@@ -4,15 +4,16 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "gpio.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/* Those defines are used for legacy config files only */
|
||||
#define GPIO_CHIP1_LABEL "1c20800.pinctrl"
|
||||
#define GPIO_CHIP2_LABEL "1f02c00.pinctrl"
|
||||
|
||||
#define MAX_GPIOCHIP_LINES 352
|
||||
|
||||
#define GPIO_IDX_INVAL 0xffff
|
||||
|
||||
enum {
|
||||
GPIO_OUT_DTR = 0,
|
||||
GPIO_OUT_PWRKEY,
|
||||
@@ -22,33 +23,21 @@ enum {
|
||||
GPIO_OUT_COUNT
|
||||
};
|
||||
|
||||
static unsigned gpio_out_idx_bh[] = {
|
||||
358,
|
||||
35,
|
||||
68,
|
||||
231,
|
||||
232
|
||||
};
|
||||
|
||||
static unsigned gpio_out_idx_ce[] = {
|
||||
34,
|
||||
35,
|
||||
68,
|
||||
231,
|
||||
232
|
||||
};
|
||||
|
||||
enum {
|
||||
GPIO_IN_STATUS = 0,
|
||||
GPIO_IN_COUNT
|
||||
};
|
||||
|
||||
static unsigned gpio_in_idx_bh[] = {
|
||||
GPIO_IDX_INVAL,
|
||||
static char *gpio_out_names[] = {
|
||||
"dtr",
|
||||
"pwrkey",
|
||||
"reset",
|
||||
"apready",
|
||||
"disable",
|
||||
};
|
||||
|
||||
static unsigned gpio_in_idx_ce[] = {
|
||||
233,
|
||||
static char *gpio_in_names[] = {
|
||||
"status",
|
||||
};
|
||||
|
||||
int gpio_sequence_poweron(struct EG25Manager *manager)
|
||||
@@ -57,7 +46,7 @@ int gpio_sequence_poweron(struct EG25Manager *manager)
|
||||
sleep(1);
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_PWRKEY], 0);
|
||||
|
||||
g_message("Executed power-on sequence");
|
||||
g_message("Executed power-on/off sequence");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -75,7 +64,6 @@ int gpio_sequence_shutdown(struct EG25Manager *manager)
|
||||
int gpio_sequence_suspend(struct EG25Manager *manager)
|
||||
{
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_APREADY], 1);
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 1);
|
||||
|
||||
g_message("Executed suspend sequence");
|
||||
|
||||
@@ -85,100 +73,209 @@ int gpio_sequence_suspend(struct EG25Manager *manager)
|
||||
int gpio_sequence_resume(struct EG25Manager *manager)
|
||||
{
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_APREADY], 0);
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0);
|
||||
|
||||
g_message("Executed resume sequence");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gpio_init(struct EG25Manager *manager)
|
||||
int gpio_sequence_wake(struct EG25Manager *manager)
|
||||
{
|
||||
int i, ret;
|
||||
unsigned *gpio_in_idx;
|
||||
unsigned *gpio_out_idx;
|
||||
if (gpiod_line_get_value(manager->gpio_out[GPIO_OUT_DTR])) {
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 0);
|
||||
|
||||
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
|
||||
if (!manager->gpiochip[0]) {
|
||||
g_critical("Unable to open GPIO chip " GPIO_CHIP1_LABEL);
|
||||
return 1;
|
||||
/* Give the modem 200ms to wake from soft sleep */
|
||||
usleep(200000);
|
||||
|
||||
g_message("Executed soft wake sequence");
|
||||
}
|
||||
|
||||
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
|
||||
if (!manager->gpiochip[1]) {
|
||||
g_critical("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gpio_sequence_sleep(struct EG25Manager *manager)
|
||||
{
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_DTR], 1);
|
||||
g_message("Executed soft sleep sequence");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct gpiod_line *gpio_get_output_line(struct EG25Manager *manager, int chip, int line)
|
||||
{
|
||||
struct gpiod_line *gpio_line;
|
||||
|
||||
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
|
||||
if (!gpio_line)
|
||||
return NULL;
|
||||
|
||||
if (gpiod_line_request_output(gpio_line, "eg25manager", 0) < 0) {
|
||||
gpiod_line_release(gpio_line);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (manager->braveheart) {
|
||||
gpio_in_idx = gpio_in_idx_bh;
|
||||
gpio_out_idx = gpio_out_idx_bh;
|
||||
return gpio_line;
|
||||
}
|
||||
|
||||
struct gpiod_line *gpio_get_input_line(struct EG25Manager *manager, int chip, int line)
|
||||
{
|
||||
struct gpiod_line *gpio_line;
|
||||
|
||||
gpio_line = gpiod_chip_get_line(manager->gpiochip[chip], line);
|
||||
if (!gpio_line)
|
||||
return NULL;
|
||||
|
||||
if (gpiod_line_request_input(gpio_line, "eg25manager") < 0) {
|
||||
gpiod_line_release(gpio_line);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return gpio_line;
|
||||
}
|
||||
|
||||
int gpio_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||
{
|
||||
int i;
|
||||
toml_table_t *gpio_config[EG25_CONFIG_COUNT];
|
||||
|
||||
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++) {
|
||||
toml_table_t *table;
|
||||
toml_datum_t chip, line;
|
||||
if (!config_get_table(gpio_config, gpio_out_names[i], &table))
|
||||
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
|
||||
|
||||
chip = toml_int_in(table, "chip");
|
||||
if (!chip.ok || chip.u.i < 0 || chip.u.i > 2)
|
||||
g_error("Wrong chip ID for output GPIO '%s'", gpio_out_names[i]);
|
||||
|
||||
line = toml_int_in(table, "line");
|
||||
if (!line.ok || line.u.i < 0 || line.u.i > gpiod_chip_num_lines(manager->gpiochip[chip.u.i]))
|
||||
g_error("Wrong line ID for output GPIO '%s'", gpio_out_names[i]);
|
||||
|
||||
manager->gpio_out[i] = gpio_get_output_line(manager, chip.u.i, line.u.i);
|
||||
if (!manager->gpio_out[i])
|
||||
g_error("Unable to get output GPIO %d", i);
|
||||
}
|
||||
|
||||
for (i = 0; i < GPIO_IN_COUNT; i++) {
|
||||
toml_table_t *table;
|
||||
toml_datum_t chip, line;
|
||||
|
||||
if (!config_get_table(gpio_config, gpio_in_names[i], &table)) {
|
||||
// BH edition don't have the STATUS line connected, ignore it
|
||||
if (manager->use_libusb && g_strcmp0(gpio_in_names[i], "status") == 0)
|
||||
continue;
|
||||
g_error("Unable to get config for input GPIO '%s'", gpio_in_names[i]);
|
||||
}
|
||||
|
||||
chip = toml_int_in(table, "chip");
|
||||
if (!chip.ok || chip.u.i < 0 || chip.u.i > 2)
|
||||
g_error("Wrong chip ID for input GPIO '%s'", gpio_in_names[i]);
|
||||
|
||||
line = toml_int_in(table, "line");
|
||||
if (!line.ok || line.u.i < 0 || line.u.i > gpiod_chip_num_lines(manager->gpiochip[chip.u.i]))
|
||||
g_error("Wrong line ID for input GPIO '%s'", gpio_in_names[i]);
|
||||
|
||||
manager->gpio_in[i] = gpio_get_input_line(manager, chip.u.i, line.u.i);
|
||||
if (!manager->gpio_in[i])
|
||||
g_error("Unable to get input GPIO %d", i);
|
||||
}
|
||||
} else {
|
||||
gpio_in_idx = gpio_in_idx_ce;
|
||||
gpio_out_idx = gpio_out_idx_ce;
|
||||
}
|
||||
guint offset, chipidx, gpio_idx;
|
||||
|
||||
for (i = 0; i < GPIO_OUT_COUNT; i++) {
|
||||
unsigned int offset, chipidx;
|
||||
/* Legacy config file, only used on the OG PinePhone */
|
||||
manager->gpiochip[0] = gpiod_chip_open_by_label(GPIO_CHIP1_LABEL);
|
||||
if (!manager->gpiochip[0])
|
||||
g_error("Unable to open GPIO chip " GPIO_CHIP1_LABEL);
|
||||
|
||||
if (gpio_out_idx[i] < MAX_GPIOCHIP_LINES) {
|
||||
offset = gpio_out_idx[i];
|
||||
chipidx = 0;
|
||||
} else {
|
||||
offset = gpio_out_idx[i] - MAX_GPIOCHIP_LINES;
|
||||
chipidx = 1;
|
||||
manager->gpiochip[1] = gpiod_chip_open_by_label(GPIO_CHIP2_LABEL);
|
||||
if (!manager->gpiochip[1])
|
||||
g_error("Unable to open GPIO chip " GPIO_CHIP2_LABEL);
|
||||
|
||||
for (i = 0; i < GPIO_OUT_COUNT; i++) {
|
||||
if (!config_get_uint(gpio_config, gpio_out_names[i], &gpio_idx))
|
||||
g_error("Unable to get config for output GPIO '%s'", gpio_out_names[i]);
|
||||
|
||||
if (gpio_idx < MAX_GPIOCHIP_LINES) {
|
||||
offset = gpio_idx;
|
||||
chipidx = 0;
|
||||
} else {
|
||||
offset = gpio_idx - MAX_GPIOCHIP_LINES;
|
||||
chipidx = 1;
|
||||
}
|
||||
|
||||
manager->gpio_out[i] = gpio_get_input_line(manager, chipidx, offset);
|
||||
if (!manager->gpio_out[i])
|
||||
g_error("Unable to get output GPIO %d", i);
|
||||
}
|
||||
|
||||
manager->gpio_out[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
|
||||
if (!manager->gpio_out[i]) {
|
||||
g_critical("Unable to get output GPIO %d", i);
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; i < GPIO_IN_COUNT; i++) {
|
||||
if (!config_get_uint(gpio_config, gpio_in_names[i], &gpio_idx))
|
||||
continue;
|
||||
|
||||
ret = gpiod_line_request_output(manager->gpio_out[i], "eg25manager", 0);
|
||||
if (ret < 0) {
|
||||
g_critical("Unable to request output GPIO %d", i);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (gpio_idx < MAX_GPIOCHIP_LINES) {
|
||||
offset = gpio_idx;
|
||||
chipidx = 0;
|
||||
} else {
|
||||
offset = gpio_idx - MAX_GPIOCHIP_LINES;
|
||||
chipidx = 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < GPIO_IN_COUNT; i++) {
|
||||
unsigned int offset, chipidx;
|
||||
|
||||
if (gpio_in_idx[i] == GPIO_IDX_INVAL)
|
||||
continue;
|
||||
|
||||
if (gpio_in_idx[i] < MAX_GPIOCHIP_LINES) {
|
||||
offset = gpio_in_idx[i];
|
||||
chipidx = 0;
|
||||
} else {
|
||||
offset = gpio_in_idx[i] - MAX_GPIOCHIP_LINES;
|
||||
chipidx = 1;
|
||||
}
|
||||
|
||||
manager->gpio_in[i] = gpiod_chip_get_line(manager->gpiochip[chipidx], offset);
|
||||
if (!manager->gpio_in[i]) {
|
||||
g_warning("Unable to get input GPIO %d", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = gpiod_line_request_input(manager->gpio_in[i], "eg25manager");
|
||||
if (ret < 0) {
|
||||
g_warning("Unable to request input GPIO %d", i);
|
||||
manager->gpio_in[i] = NULL;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
gboolean gpio_check_poweroff(struct EG25Manager *manager)
|
||||
gboolean gpio_check_poweroff(struct EG25Manager *manager, gboolean keep_down)
|
||||
{
|
||||
if (manager->gpio_in[GPIO_IN_STATUS] &&
|
||||
gpiod_line_get_value(manager->gpio_in[GPIO_IN_STATUS]) == 1) {
|
||||
|
||||
// Asserting RESET line to prevent modem from rebooting
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_RESET], 1);
|
||||
if (keep_down && manager->gpio_out[GPIO_OUT_RESET]) {
|
||||
// Asserting RESET line to prevent modem from rebooting
|
||||
gpiod_line_set_value(manager->gpio_out[GPIO_OUT_RESET], 1);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -189,10 +286,18 @@ void gpio_destroy(struct EG25Manager *manager)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < GPIO_OUT_COUNT; i++)
|
||||
gpiod_line_release(manager->gpio_out[i]);
|
||||
for (i = 0; i < GPIO_OUT_COUNT; i++) {
|
||||
if (manager->gpio_out[i])
|
||||
gpiod_line_release(manager->gpio_out[i]);
|
||||
}
|
||||
|
||||
gpiod_chip_close(manager->gpiochip[0]);
|
||||
for (i = 0; i < GPIO_IN_COUNT; i++) {
|
||||
if (manager->gpio_in[i])
|
||||
gpiod_line_release(manager->gpio_in[i]);
|
||||
}
|
||||
|
||||
if (manager->gpiochip[0])
|
||||
gpiod_chip_close(manager->gpiochip[0]);
|
||||
if (manager->gpiochip[1])
|
||||
gpiod_chip_close(manager->gpiochip[1]);
|
||||
}
|
||||
|
@@ -8,12 +8,14 @@
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
int gpio_init(struct EG25Manager *state);
|
||||
int gpio_init(struct EG25Manager *state, toml_table_t *config[]);
|
||||
void gpio_destroy(struct EG25Manager *state);
|
||||
|
||||
int gpio_sequence_poweron(struct EG25Manager *state);
|
||||
int gpio_sequence_shutdown(struct EG25Manager *state);
|
||||
int gpio_sequence_suspend(struct EG25Manager *state);
|
||||
int gpio_sequence_resume(struct EG25Manager *state);
|
||||
int gpio_sequence_wake(struct EG25Manager *state);
|
||||
int gpio_sequence_sleep(struct EG25Manager *state);
|
||||
|
||||
gboolean gpio_check_poweroff(struct EG25Manager *manager);
|
||||
gboolean gpio_check_poweroff(struct EG25Manager *manager, gboolean keep_down);
|
||||
|
11
src/libgdbofono/dbus-introspect.sh
Executable file
11
src/libgdbofono/dbus-introspect.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
DEST="$1"
|
||||
OBJ_PATH="$2"
|
||||
METHOD="$3"
|
||||
shift 3
|
||||
|
||||
dbus-send "$@" --print-reply --dest="$DEST" "$OBJ_PATH" "$METHOD" | \
|
||||
grep -v '^method return' | \
|
||||
sed -e 's/^[[:space:]]\+string "</</' \
|
||||
-e 's_</node>"_</node>_'
|
13
src/libgdbofono/manager.xml
Normal file
13
src/libgdbofono/manager.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
|
||||
<interface name="org.ofono.Manager"><method name="GetModems"><arg name="modems" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="ModemAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="ModemRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
</node>
|
50
src/libgdbofono/meson.build
Normal file
50
src/libgdbofono/meson.build
Normal file
@@ -0,0 +1,50 @@
|
||||
#
|
||||
# Copyright (C) 2018 Purism SPC
|
||||
#
|
||||
# This file is part of Calls.
|
||||
#
|
||||
# Calls is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Calls is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Calls. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author: Bob Ham <bob.ham@puri.sm>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
|
||||
gnome = import('gnome')
|
||||
dbus_interfaces = ['manager', 'modem']
|
||||
|
||||
gdbofono_src = []
|
||||
gdbofono_headers = []
|
||||
foreach iface: dbus_interfaces
|
||||
src = gnome.gdbus_codegen(
|
||||
'gdbo-' + iface,
|
||||
iface + '.xml',
|
||||
interface_prefix: 'org.ofono.',
|
||||
namespace: 'GDBO'
|
||||
)
|
||||
gdbofono_src += src
|
||||
gdbofono_headers += src[1]
|
||||
endforeach
|
||||
|
||||
gdbofono_deps = [
|
||||
dependency('gio-2.0'),
|
||||
dependency('gio-unix-2.0'),
|
||||
]
|
||||
|
||||
gdbofono_lib = static_library(
|
||||
'gdbofono',
|
||||
gdbofono_src,
|
||||
dependencies: gdbofono_deps
|
||||
)
|
249
src/libgdbofono/modem-full.xml
Normal file
249
src/libgdbofono/modem-full.xml
Normal file
@@ -0,0 +1,249 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
|
||||
<interface name="org.ofono.Modem"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.SimManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="ChangePin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="oldpin" type="s" direction="in"/>
|
||||
<arg name="newpin" type="s" direction="in"/>
|
||||
</method><method name="EnterPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="pin" type="s" direction="in"/>
|
||||
</method><method name="ResetPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="puk" type="s" direction="in"/>
|
||||
<arg name="newpin" type="s" direction="in"/>
|
||||
</method><method name="LockPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="pin" type="s" direction="in"/>
|
||||
</method><method name="UnlockPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="pin" type="s" direction="in"/>
|
||||
</method><method name="GetIcon"><arg name="id" type="y" direction="in"/>
|
||||
<arg name="icon" type="ay" direction="out"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.VoiceCallManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="Dial"><arg name="number" type="s" direction="in"/>
|
||||
<arg name="hide_callerid" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="DialLast"></method><method name="DialMemory"><arg name="memory_location" type="u" direction="in"/>
|
||||
</method><method name="Transfer"></method><method name="SwapCalls"></method><method name="ReleaseAndAnswer"></method><method name="ReleaseAndSwap"></method><method name="HoldAndAnswer"></method><method name="HangupAll"></method><method name="PrivateChat"><arg name="call" type="o" direction="in"/>
|
||||
<arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="CreateMultiparty"><arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="HangupMultiparty"></method><method name="SendTones"><arg name="SendTones" type="s" direction="in"/>
|
||||
</method><method name="GetCalls"><arg name="calls_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="Forwarded"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="BarringActive"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="CallAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="CallRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.AllowedAccessPoints"><method name="GetAllowedAccessPoints"><arg name="apnlist" type="as" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.SimAuthentication"><method name="GetApplications"><arg name="applications" type="a{oa{sv}}" direction="out"/>
|
||||
</method><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.SimToolkit"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SelectItem"><arg name="item" type="y" direction="in"/>
|
||||
<arg name="agent" type="o" direction="in"/>
|
||||
</method><method name="RegisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="UnregisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallForwarding"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="DisableAll"><arg name="type" type="s" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.RadioSettings"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.TextTelephony"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.Phonebook"><method name="Import"><arg name="entries" type="s" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.MessageManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="SendMessage"><arg name="to" type="s" direction="in"/>
|
||||
<arg name="text" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="GetMessages"><arg name="messages" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="IncomingMessage"><arg name="message" type="s"/>
|
||||
<arg name="info" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="ImmediateMessage"><arg name="message" type="s"/>
|
||||
<arg name="info" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="MessageAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="MessageRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.PushNotification"><method name="RegisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="UnregisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.SmartMessaging"><method name="RegisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="UnregisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="SendBusinessCard"><arg name="to" type="s" direction="in"/>
|
||||
<arg name="card" type="ay" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="SendAppointment"><arg name="to" type="s" direction="in"/>
|
||||
<arg name="appointment" type="ay" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.MessageWaiting"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallSettings"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallBarring"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
<arg name="pin2" type="s" direction="in"/>
|
||||
</method><method name="DisableAll"><arg name="password" type="s" direction="in"/>
|
||||
</method><method name="DisableAllIncoming"><arg name="password" type="s" direction="in"/>
|
||||
</method><method name="DisableAllOutgoing"><arg name="password" type="s" direction="in"/>
|
||||
</method><method name="ChangePassword"><arg name="old" type="s" direction="in"/>
|
||||
<arg name="new" type="s" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.SupplementaryServices"><method name="Initiate"><arg name="command" type="s" direction="in"/>
|
||||
<arg name="result_name" type="s" direction="out"/>
|
||||
<arg name="value" type="v" direction="out"/>
|
||||
</method><method name="Respond"><arg name="reply" type="s" direction="in"/>
|
||||
<arg name="result" type="s" direction="out"/>
|
||||
</method><method name="Cancel"></method><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><signal name="NotificationReceived"><arg name="message" type="s"/>
|
||||
</signal>
|
||||
<signal name="RequestReceived"><arg name="message" type="s"/>
|
||||
</signal>
|
||||
<signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallMeter"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
<arg name="password" type="s" direction="in"/>
|
||||
</method><method name="Reset"><arg name="passoword" type="s" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="NearMaximumWarning"></signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallVolume"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.NetworkRegistration"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="Register"></method><method name="GetOperators"><arg name="operators_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><method name="Scan"><arg name="operators_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CellBroadcast"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="IncomingBroadcast"><arg name="message" type="s"/>
|
||||
<arg name="channel" type="q"/>
|
||||
</signal>
|
||||
<signal name="EmergencyBroadcast"><arg name="message" type="s"/>
|
||||
<arg name="dict" type="a{sv}"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.AssistedSatelliteNavigation"><method name="SendPositioningElement"><arg name="xml_elements" type="(null)" direction="in"/>
|
||||
</method><method name="RegisterPositioningRequestAgent"><arg name="agent" type="o" direction="in"/>
|
||||
</method><method name="UnregisterPositioningRequestAgent"><arg name="agent" type="o" direction="in"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.ConnectionManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="AddContext"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="RemoveContext"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="DeactivateAll"></method><method name="GetContexts"><arg name="contexts_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><method name="ResetContexts"></method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="ContextAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="ContextRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
</node>
|
37
src/libgdbofono/modem.xml
Normal file
37
src/libgdbofono/modem.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
|
||||
<interface name="org.ofono.Modem"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.VoiceCallManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="Dial"><arg name="number" type="s" direction="in"/>
|
||||
<arg name="hide_callerid" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="DialLast"></method><method name="DialMemory"><arg name="memory_location" type="u" direction="in"/>
|
||||
</method><method name="Transfer"></method><method name="SwapCalls"></method><method name="ReleaseAndAnswer"></method><method name="ReleaseAndSwap"></method><method name="HoldAndAnswer"></method><method name="HangupAll"></method><method name="PrivateChat"><arg name="call" type="o" direction="in"/>
|
||||
<arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="CreateMultiparty"><arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="HangupMultiparty"></method><method name="SendTones"><arg name="SendTones" type="s" direction="in"/>
|
||||
</method><method name="GetCalls"><arg name="calls_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="Forwarded"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="BarringActive"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="CallAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="CallRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
</node>
|
345
src/manager.c
345
src/manager.c
@@ -5,10 +5,18 @@
|
||||
*/
|
||||
|
||||
#include "at.h"
|
||||
#include "config.h"
|
||||
#include "gpio.h"
|
||||
#include "manager.h"
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
#include "mm-iface.h"
|
||||
#endif
|
||||
|
||||
#include "ofono-iface.h"
|
||||
#include "suspend.h"
|
||||
#include "udev.h"
|
||||
#include "gnss.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
@@ -16,53 +24,100 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <glib-unix.h>
|
||||
#include <libusb.h>
|
||||
|
||||
static gboolean quit_timeout_cb(struct EG25Manager *manager)
|
||||
{
|
||||
g_message("Modem down, quitting...");
|
||||
g_main_loop_quit(manager->loop);
|
||||
#ifndef EG25_CONFDIR
|
||||
#define EG25_CONFDIR "/etc/eg25-manager"
|
||||
#endif
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
#ifndef EG25_DATADIR
|
||||
#define EG25_DATADIR "/usr/share/eg25-manager"
|
||||
#endif
|
||||
|
||||
static gboolean gpio_poll_cb(struct EG25Manager *manager)
|
||||
{
|
||||
if (gpio_check_poweroff(manager)) {
|
||||
quit_timeout_cb(manager);
|
||||
return FALSE;
|
||||
}
|
||||
#ifndef EG25_VERSION
|
||||
#define EG25_VERSION "0.0.0"
|
||||
#endif
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#define EG25_DEFAULT_VENDOR_ID 0x2c7c
|
||||
#define EG25_DEFAULT_PRODUCT_ID 0x0125
|
||||
|
||||
#define POWERON_DELAY_US 100000UL
|
||||
|
||||
static gboolean quit_app(struct EG25Manager *manager)
|
||||
{
|
||||
int i;
|
||||
|
||||
g_message("Request to quit...");
|
||||
|
||||
at_destroy(manager);
|
||||
#ifdef HAVE_MMGLIB
|
||||
mm_iface_destroy(manager);
|
||||
#endif
|
||||
ofono_iface_destroy(manager);
|
||||
suspend_destroy(manager);
|
||||
udev_destroy(manager);
|
||||
|
||||
if (manager->modem_state >= EG25_STATE_STARTED) {
|
||||
g_message("Powering down the modem...");
|
||||
gpio_sequence_shutdown(manager);
|
||||
manager->modem_state = EG25_STATE_FINISHING;
|
||||
g_timeout_add(500, G_SOURCE_FUNC(gpio_poll_cb), manager);
|
||||
g_timeout_add_seconds(30, G_SOURCE_FUNC(quit_timeout_cb), manager);
|
||||
for (i = 0; i < 30; i++) {
|
||||
if (gpio_check_poweroff(manager, TRUE))
|
||||
break;
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
g_message("Modem down, quitting...");
|
||||
|
||||
mm_iface_destroy(manager);
|
||||
suspend_destroy(manager);
|
||||
g_bus_unwatch_name(manager->mm_watch);
|
||||
g_main_loop_quit(manager->loop);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean modem_start(struct EG25Manager *manager)
|
||||
{
|
||||
g_message("Starting modem...");
|
||||
gpio_sequence_poweron(manager);
|
||||
manager->modem_state = EG25_STATE_POWERED;
|
||||
ssize_t i, count;
|
||||
gboolean should_boot = TRUE;
|
||||
libusb_context *ctx = NULL;
|
||||
libusb_device **devices = NULL;
|
||||
struct libusb_device_descriptor desc;
|
||||
|
||||
if (manager->use_libusb) {
|
||||
// BH don't have the STATUS line connected, so check if USB device is present
|
||||
libusb_init(&ctx);
|
||||
|
||||
count = libusb_get_device_list(ctx, &devices);
|
||||
for (i = 0; i < count; i++) {
|
||||
libusb_get_device_descriptor(devices[i], &desc);
|
||||
if (desc.idVendor == manager->usb_vid && desc.idProduct == manager->usb_pid) {
|
||||
g_message("Found corresponding USB device, modem already powered");
|
||||
should_boot = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_device_list(devices, 1);
|
||||
libusb_exit(ctx);
|
||||
} else if (!gpio_check_poweroff(manager, FALSE)) {
|
||||
g_message("STATUS is low, modem already powered");
|
||||
should_boot = FALSE;
|
||||
}
|
||||
|
||||
if (should_boot) {
|
||||
g_message("Starting modem...");
|
||||
// Modem might crash on boot (especially with worn battery) if we don't delay here
|
||||
if (manager->poweron_delay > 0)
|
||||
g_usleep(manager->poweron_delay);
|
||||
gpio_sequence_poweron(manager);
|
||||
manager->modem_state = EG25_STATE_POWERED;
|
||||
} else {
|
||||
manager->modem_state = EG25_STATE_STARTED;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
void modem_update_state(struct EG25Manager *manager, MMModemState state)
|
||||
{
|
||||
switch (state) {
|
||||
@@ -79,41 +134,124 @@ void modem_update_state(struct EG25Manager *manager, MMModemState state)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void modem_configure(struct EG25Manager *manager)
|
||||
{
|
||||
at_sequence_configure(manager);
|
||||
}
|
||||
|
||||
void modem_reset(struct EG25Manager *manager)
|
||||
static gboolean modem_reset_done(struct EG25Manager* manager)
|
||||
{
|
||||
int fd;
|
||||
manager->modem_state = EG25_STATE_RESUMING;
|
||||
manager->complete_reset_timer = 0;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean modem_reset(struct EG25Manager *manager)
|
||||
{
|
||||
int fd, ret, len;
|
||||
|
||||
/* reset sequence started, cannot be canceled anymore */
|
||||
if (manager->schedule_reset_timer) {
|
||||
g_source_remove(manager->schedule_reset_timer);
|
||||
manager->schedule_reset_timer = 0;
|
||||
}
|
||||
|
||||
if (manager->modem_recovery_timer) {
|
||||
g_source_remove(manager->modem_recovery_timer);
|
||||
manager->modem_recovery_timer = 0;
|
||||
}
|
||||
|
||||
if (manager->complete_reset_timer) {
|
||||
g_message("modem_reset: timer already setup, skipping...");
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
g_warning("Empty modem USB ID");
|
||||
goto error;
|
||||
}
|
||||
|
||||
g_message("Trying to reset modem with USB ID '%s'", manager->modem_usb_id);
|
||||
|
||||
len = strlen(manager->modem_usb_id);
|
||||
|
||||
manager->modem_state = EG25_STATE_RESETTING;
|
||||
|
||||
fd = open("/sys/bus/usb/drivers/usb/unbind", O_WRONLY);
|
||||
if (fd < 0)
|
||||
if (fd < 0) {
|
||||
g_warning("Unable to open /sys/bus/usb/drivers/usb/unbind");
|
||||
goto error;
|
||||
write(fd, manager->modem_usb_id, strlen(manager->modem_usb_id));
|
||||
}
|
||||
ret = write(fd, manager->modem_usb_id, len);
|
||||
if (ret < len) {
|
||||
g_warning("Couldn't unbind modem: wrote %d/%d bytes", ret, len);
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
fd = open("/sys/bus/usb/drivers/usb/bind", O_WRONLY);
|
||||
if (fd < 0)
|
||||
if (fd < 0) {
|
||||
g_warning("Unable to open /sys/bus/usb/drivers/usb/unbind");
|
||||
goto error;
|
||||
write(fd, manager->modem_usb_id, strlen(manager->modem_usb_id));
|
||||
}
|
||||
ret = write(fd, manager->modem_usb_id, len);
|
||||
if (ret < len) {
|
||||
g_warning("Couldn't bind modem: wrote %d/%d bytes", ret, len);
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||
return;
|
||||
g_message("Successfully reset modem's USB connection");
|
||||
|
||||
/*
|
||||
* 3s is long enough to make sure the modem has been bound back and
|
||||
* short enough to ensure it hasn't been acquired by ModemManager
|
||||
*/
|
||||
manager->complete_reset_timer = g_timeout_add_seconds(3, G_SOURCE_FUNC(modem_reset_done), manager);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
|
||||
error:
|
||||
// Everything else failed, reset the modem
|
||||
// Release blocking sleep inhibitor
|
||||
if (manager->suspend_block_fd >= 0)
|
||||
suspend_inhibit(manager, FALSE, TRUE);
|
||||
if (manager->modem_boot_timer) {
|
||||
g_source_remove(manager->modem_boot_timer);
|
||||
manager->modem_boot_timer = 0;
|
||||
}
|
||||
|
||||
// Everything else failed, reboot the modem
|
||||
g_message("USB reset failed, falling back to AT command");
|
||||
at_sequence_reset(manager);
|
||||
manager->modem_state = EG25_STATE_RESETTING;
|
||||
|
||||
// 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(struct EG25Manager *manager)
|
||||
void modem_suspend_pre(struct EG25Manager *manager)
|
||||
{
|
||||
at_sequence_suspend(manager);
|
||||
}
|
||||
|
||||
void modem_suspend_post(struct EG25Manager *manager)
|
||||
{
|
||||
gpio_sequence_suspend(manager);
|
||||
at_sequence_suspend(manager);
|
||||
g_message("suspend sequence is over, drop inhibitor");
|
||||
suspend_inhibit(manager, FALSE, FALSE);
|
||||
}
|
||||
|
||||
void modem_resume_pre(struct EG25Manager *manager)
|
||||
@@ -126,32 +264,138 @@ void modem_resume_post(struct EG25Manager *manager)
|
||||
at_sequence_resume(manager);
|
||||
}
|
||||
|
||||
static toml_table_t *parse_config_file(char *config_file, gboolean force_default)
|
||||
{
|
||||
toml_table_t *toml_config;
|
||||
gchar *compatible;
|
||||
gchar error[256];
|
||||
gsize len;
|
||||
FILE *f = NULL;
|
||||
|
||||
if (config_file) {
|
||||
f = fopen(config_file, "r");
|
||||
} else if (g_file_get_contents("/proc/device-tree/compatible", &compatible, &len, NULL)) {
|
||||
g_autoptr (GPtrArray) compat = g_ptr_array_new();
|
||||
gsize pos = 0;
|
||||
|
||||
/*
|
||||
* `compatible` file is a list of NULL-terminated strings, convert it
|
||||
* to an array
|
||||
*/
|
||||
do {
|
||||
g_ptr_array_add(compat, &compatible[pos]);
|
||||
pos += strlen(&compatible[pos]) + 1;
|
||||
} while (pos < len);
|
||||
|
||||
for (pos = 0; pos < compat->len; pos++) {
|
||||
g_autofree gchar *filename = NULL;
|
||||
if (force_default)
|
||||
filename = g_strdup_printf(EG25_DATADIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
|
||||
else
|
||||
filename = g_strdup_printf(EG25_CONFDIR "/%s.toml", (gchar *)g_ptr_array_index(compat, pos));
|
||||
|
||||
if (access(filename, F_OK) == 0) {
|
||||
g_message("Opening config file: %s", filename);
|
||||
f = fopen(filename, "r");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!f) {
|
||||
if (force_default)
|
||||
g_error("unable to find a suitable config file!");
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
toml_config = toml_parse_file(f, error, sizeof(error));
|
||||
if (!toml_config)
|
||||
g_error("unable to parse config file: %s", error);
|
||||
|
||||
return toml_config;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
g_autoptr(GOptionContext) opt_context = NULL;
|
||||
g_autoptr(GError) err = NULL;
|
||||
struct EG25Manager manager;
|
||||
char compatible[32];
|
||||
int fd;
|
||||
gchar *config_file = NULL;
|
||||
gboolean show_version = FALSE;
|
||||
gboolean monitor_udev = TRUE;
|
||||
toml_table_t *toml_config[EG25_CONFIG_COUNT];
|
||||
toml_table_t *manager_config[EG25_CONFIG_COUNT];
|
||||
const GOptionEntry options[] = {
|
||||
{ "config", 'c', 0, G_OPTION_ARG_STRING, &config_file, "Config file to use.", NULL },
|
||||
{ "version", 'v', 0, G_OPTION_ARG_NONE, &show_version, "Display version information and exit.", NULL },
|
||||
{ NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
|
||||
};
|
||||
|
||||
memset(&manager, 0, sizeof(manager));
|
||||
manager.at_fd = -1;
|
||||
manager.suspend_inhibit_fd = -1;
|
||||
manager.poweron_delay = POWERON_DELAY_US;
|
||||
manager.suspend_delay_fd = -1;
|
||||
manager.suspend_block_fd = -1;
|
||||
|
||||
opt_context = g_option_context_new ("- Power management for the Quectel EG25 modem");
|
||||
g_option_context_add_main_entries (opt_context, options, NULL);
|
||||
if (!g_option_context_parse (opt_context, &argc, &argv, &err)) {
|
||||
g_warning ("%s", err->message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (show_version) {
|
||||
printf("eg25-manager version %s\n", EG25_VERSION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
manager.loop = g_main_loop_new(NULL, FALSE);
|
||||
|
||||
fd = open("/proc/device-tree/compatible", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
g_critical("Unable to read 'compatible' string from device tree");
|
||||
return 1;
|
||||
}
|
||||
read(fd, compatible, sizeof(compatible));
|
||||
if (!strstr(compatible, "pine64,pinephone-1.2"))
|
||||
manager.braveheart = TRUE;
|
||||
close(fd);
|
||||
toml_config[EG25_CONFIG_SYS] = parse_config_file(NULL, TRUE);
|
||||
toml_config[EG25_CONFIG_USER] = parse_config_file(config_file, FALSE);
|
||||
|
||||
at_init(&manager);
|
||||
gpio_init(&manager);
|
||||
mm_iface_init(&manager);
|
||||
suspend_init(&manager);
|
||||
/*
|
||||
* We need at least one valid config file, and assuming it's
|
||||
* EG25_CONFIG_SYS will make the rest easier to implement
|
||||
*/
|
||||
if (!toml_config[EG25_CONFIG_SYS] && toml_config[EG25_CONFIG_USER]) {
|
||||
toml_config[EG25_CONFIG_SYS] = toml_config[EG25_CONFIG_USER];
|
||||
toml_config[EG25_CONFIG_USER] = NULL;
|
||||
}
|
||||
|
||||
if (!toml_config[EG25_CONFIG_SYS])
|
||||
g_error("Unable to parse config file!");
|
||||
|
||||
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||
manager_config[i] = toml_config[i] ? toml_table_in(toml_config[i], "manager") : NULL;
|
||||
|
||||
if (!manager_config[EG25_CONFIG_SYS])
|
||||
g_error("Default config file lacks the 'manager' section!");
|
||||
|
||||
config_get_bool(manager_config, "monitor_udev", &monitor_udev);
|
||||
config_get_bool(manager_config, "need_libusb", &manager.use_libusb);
|
||||
config_get_uint(manager_config, "poweron_delay", &manager.poweron_delay);
|
||||
if (!config_get_uint(manager_config, "usb_vid", &manager.usb_vid))
|
||||
manager.usb_vid = EG25_DEFAULT_VENDOR_ID;
|
||||
if (!config_get_uint(manager_config, "usb_pid", &manager.usb_pid))
|
||||
manager.usb_pid = EG25_DEFAULT_PRODUCT_ID;
|
||||
|
||||
at_init(&manager, toml_config);
|
||||
gpio_init(&manager, toml_config);
|
||||
#ifdef HAVE_MMGLIB
|
||||
mm_iface_init(&manager, toml_config);
|
||||
#endif
|
||||
ofono_iface_init(&manager, toml_config);
|
||||
suspend_init(&manager, toml_config);
|
||||
if (monitor_udev)
|
||||
udev_init(&manager, toml_config);
|
||||
gnss_init(&manager, toml_config);
|
||||
|
||||
for (int i = 0; i < EG25_CONFIG_COUNT; i++) {
|
||||
if (toml_config[i])
|
||||
toml_free(toml_config[i]);
|
||||
}
|
||||
|
||||
g_idle_add(G_SOURCE_FUNC(modem_start), &manager);
|
||||
|
||||
@@ -160,7 +404,6 @@ int main(int argc, char *argv[])
|
||||
|
||||
g_main_loop_run(manager.loop);
|
||||
|
||||
at_destroy(&manager);
|
||||
gpio_destroy(&manager);
|
||||
|
||||
return 0;
|
||||
|
108
src/manager.h
108
src/manager.h
@@ -8,7 +8,39 @@
|
||||
|
||||
#include <glib.h>
|
||||
#include <gpiod.h>
|
||||
#include <gudev/gudev.h>
|
||||
#ifdef HAVE_MMGLIB
|
||||
#include <libmm-glib.h>
|
||||
#endif
|
||||
#include <libgdbofono/gdbo-manager.h>
|
||||
|
||||
#include "toml.h"
|
||||
|
||||
typedef enum {
|
||||
EG25_GNSS_STEP_FIRST = 0,
|
||||
EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA,
|
||||
#ifdef HAVE_MMGLIB
|
||||
EG25_GNSS_STEP_MM_GNSS_DISABLE,
|
||||
#endif
|
||||
EG25_GNSS_STEP_AT_GNSS_DISABLE,
|
||||
EG25_GNSS_STEP_INIT_UPLOAD,
|
||||
EG25_GNSS_STEP_UPLOAD,
|
||||
EG25_GNSS_STEP_FINISH_UPLOAD,
|
||||
#ifdef HAVE_MMGLIB
|
||||
EG25_GNSS_STEP_MM_GNSS_ENABLE,
|
||||
#endif
|
||||
EG25_GNSS_STEP_AT_QGPS_ENABLE,
|
||||
EG25_GNSS_STEP_LAST,
|
||||
} EG25GNSSStep;
|
||||
|
||||
typedef enum {
|
||||
EG25_GNSS_SOURCE_NONE = 0,
|
||||
EG25_GNSS_SOURCE_NMEA = 1 << 0,
|
||||
EG25_GNSS_SOURCE_RAW = 1 << 1,
|
||||
EG25_GNSS_SOURCE_UNMANAGED = 1 << 2,
|
||||
EG25_GNSS_SOURCE_QGPS = 1 << 3,
|
||||
} EG25GNSSSource;
|
||||
|
||||
|
||||
enum EG25State {
|
||||
EG25_STATE_INIT = 0,
|
||||
@@ -16,33 +48,72 @@ enum EG25State {
|
||||
EG25_STATE_STARTED, // Modem has been started and declared itdata ready
|
||||
EG25_STATE_ACQUIRED, // Modem has been probed by ModemManager
|
||||
EG25_STATE_CONFIGURED, // Modem has been configured through AT commands
|
||||
EG25_STATE_SUSPENDING, // System is going into suspend
|
||||
EG25_STATE_RESUMING, // System is being resumed, waiting for modem to come back
|
||||
EG25_STATE_REGISTERED, // Modem is unlocked and registered to a network provider
|
||||
EG25_STATE_CONNECTED, // Modem is connected (data connection active)
|
||||
EG25_STATE_SUSPENDING, // System is going into suspend
|
||||
EG25_STATE_RESUMING, // System is being resumed, waiting for modem to come back
|
||||
EG25_STATE_RESETTING, // Something went wrong, we're restarting the modem
|
||||
EG25_STATE_UPDATING, // Modem is present but being updated
|
||||
EG25_STATE_FINISHING
|
||||
};
|
||||
|
||||
enum ModemIface {
|
||||
MODEM_IFACE_NONE = 0,
|
||||
MODEM_IFACE_MODEMMANAGER,
|
||||
MODEM_IFACE_OFONO
|
||||
};
|
||||
|
||||
enum EG25Config {
|
||||
EG25_CONFIG_SYS = 0,
|
||||
EG25_CONFIG_USER,
|
||||
EG25_CONFIG_COUNT
|
||||
};
|
||||
|
||||
struct EG25Manager {
|
||||
GMainLoop *loop;
|
||||
|
||||
enum EG25State modem_state;
|
||||
gchar *modem_usb_id;
|
||||
gboolean braveheart;
|
||||
|
||||
guint mm_watch;
|
||||
MMManager *mm_manager;
|
||||
MMModem *mm_modem;
|
||||
|
||||
GDBusProxy *suspend_proxy;
|
||||
int suspend_inhibit_fd;
|
||||
guint suspend_source;
|
||||
|
||||
guint complete_reset_timer;
|
||||
guint schedule_reset_timer;
|
||||
gboolean use_libusb;
|
||||
guint usb_vid;
|
||||
guint usb_pid;
|
||||
guint poweron_delay;
|
||||
|
||||
int at_fd;
|
||||
guint at_source;
|
||||
GList *at_cmds;
|
||||
void (*at_callback)(struct EG25Manager *manager, const char *response);
|
||||
|
||||
enum EG25State modem_state;
|
||||
gchar *modem_usb_id;
|
||||
|
||||
gboolean gnss_assistance_enabled;
|
||||
EG25GNSSSource gnss_sources;
|
||||
EG25GNSSStep gnss_assistance_step;
|
||||
gint gnss_assistance_fd;
|
||||
gchar *gnss_assistance_url;
|
||||
gchar *gnss_assistance_file;
|
||||
|
||||
enum ModemIface modem_iface;
|
||||
guint mm_watch;
|
||||
#ifdef HAVE_MMGLIB
|
||||
MMManager *mm_manager;
|
||||
MMModem *mm_modem;
|
||||
MMModemLocation *mm_location;
|
||||
#endif
|
||||
guint ofono_watch;
|
||||
GDBOManager *ofono_manager;
|
||||
GDBusConnection *ofono_connection;
|
||||
|
||||
GDBusProxy *suspend_proxy;
|
||||
int suspend_delay_fd;
|
||||
int suspend_block_fd;
|
||||
|
||||
guint modem_recovery_timer;
|
||||
guint modem_recovery_timeout;
|
||||
guint modem_boot_timer;
|
||||
guint modem_boot_timeout;
|
||||
|
||||
GUdevClient *udev;
|
||||
|
||||
struct gpiod_chip *gpiochip[2];
|
||||
struct gpiod_line *gpio_out[5];
|
||||
@@ -50,8 +121,11 @@ struct EG25Manager {
|
||||
};
|
||||
|
||||
void modem_configure(struct EG25Manager *data);
|
||||
void modem_reset(struct EG25Manager *data);
|
||||
void modem_suspend(struct EG25Manager *data);
|
||||
gboolean modem_reset(struct EG25Manager *data);
|
||||
void modem_suspend_pre(struct EG25Manager *data);
|
||||
void modem_suspend_post(struct EG25Manager *data);
|
||||
void modem_resume_pre(struct EG25Manager *data);
|
||||
void modem_resume_post(struct EG25Manager *data);
|
||||
#ifdef HAVE_MMGLIB
|
||||
void modem_update_state(struct EG25Manager *data, MMModemState state);
|
||||
#endif
|
||||
|
@@ -4,15 +4,30 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
executable (
|
||||
'eg25manager',
|
||||
[
|
||||
|
||||
subdir('libgdbofono')
|
||||
|
||||
src = [
|
||||
'at.c', 'at.h',
|
||||
'config.c', 'config.h',
|
||||
'gpio.c', 'gpio.h',
|
||||
'manager.c', 'manager.h',
|
||||
'mm-iface.c', 'mm-iface.h',
|
||||
'ofono-iface.c', 'ofono-iface.h',
|
||||
'suspend.c', 'suspend.h',
|
||||
],
|
||||
'toml.c', 'toml.h',
|
||||
'udev.c', 'udev.h',
|
||||
'gnss.c', 'gnss.h',
|
||||
gdbofono_headers,
|
||||
]
|
||||
|
||||
if mmglib_dep.found()
|
||||
src += ['mm-iface.c', 'mm-iface.h']
|
||||
endif
|
||||
|
||||
executable (
|
||||
'eg25-manager',
|
||||
src,
|
||||
dependencies : mgr_deps,
|
||||
link_with: gdbofono_lib,
|
||||
install : true
|
||||
)
|
||||
|
@@ -32,10 +32,13 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
|
||||
|
||||
g_assert(MM_IS_OBJECT (object));
|
||||
manager->mm_modem = mm_object_get_modem(MM_OBJECT(object));
|
||||
g_assert(manager->mm_modem != NULL);
|
||||
g_assert_nonnull(manager->mm_modem);
|
||||
|
||||
if (manager->modem_state == EG25_STATE_RESUMING) {
|
||||
g_source_remove(manager->suspend_source);
|
||||
if (manager->modem_recovery_timer) {
|
||||
g_source_remove(manager->modem_recovery_timer);
|
||||
manager->modem_recovery_timer = 0;
|
||||
}
|
||||
modem_resume_post(manager);
|
||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||
}
|
||||
@@ -47,6 +50,9 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
|
||||
modem_configure(manager);
|
||||
|
||||
path = mm_modem_get_device(manager->mm_modem);
|
||||
|
||||
if (manager->modem_usb_id)
|
||||
g_free(manager->modem_usb_id);
|
||||
manager->modem_usb_id = g_strdup(strrchr(path, '/') + 1);
|
||||
|
||||
gdbus_modem = MM_GDBUS_MODEM(manager->mm_modem);
|
||||
@@ -54,6 +60,16 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
|
||||
g_signal_connect(gdbus_modem, "state-changed", G_CALLBACK(state_changed_cb), manager);
|
||||
}
|
||||
|
||||
static void add_modem_location(struct EG25Manager *manager, GDBusObject *object)
|
||||
{
|
||||
const gchar *path;
|
||||
|
||||
path = g_dbus_object_get_object_path(object);
|
||||
g_message("Adding new modem with location capabilities `%s'", path);
|
||||
manager->mm_location = mm_object_get_modem_location(MM_OBJECT(object));
|
||||
g_assert_nonnull(manager->mm_location);
|
||||
}
|
||||
|
||||
static void interface_added_cb (struct EG25Manager *manager,
|
||||
GDBusObject *object,
|
||||
GDBusInterface *interface)
|
||||
@@ -66,6 +82,9 @@ static void interface_added_cb (struct EG25Manager *manager,
|
||||
|
||||
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM) == 0)
|
||||
add_modem(manager, object);
|
||||
|
||||
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM_LOCATION) == 0)
|
||||
add_modem_location(manager, object);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,13 +100,8 @@ static void interface_removed_cb(struct EG25Manager *manager,
|
||||
|
||||
g_message("ModemManager interface `%s' removed on object `%s'", info->name, path);
|
||||
|
||||
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM) == 0) {
|
||||
if (g_strcmp0(info->name, MM_DBUS_INTERFACE_MODEM) == 0)
|
||||
manager->mm_modem = NULL;
|
||||
if (manager->modem_usb_id) {
|
||||
g_free(manager->modem_usb_id);
|
||||
manager->modem_usb_id = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,10 +144,6 @@ static void object_removed_cb(struct EG25Manager *manager, GDBusObject *object)
|
||||
g_message("ModemManager object `%s' removed", path);
|
||||
|
||||
manager->mm_modem = NULL;
|
||||
if (manager->modem_usb_id) {
|
||||
g_free(manager->modem_usb_id);
|
||||
manager->modem_usb_id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +151,7 @@ static void mm_manager_new_cb(GDBusConnection *connection,
|
||||
GAsyncResult *res,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
GError *error = NULL;
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
manager->mm_manager = mm_manager_new_finish(res, &error);
|
||||
if (!manager->mm_manager)
|
||||
@@ -166,19 +176,40 @@ static void mm_appeared_cb(GDBusConnection *connection,
|
||||
{
|
||||
g_message("ModemManager appeared on D-Bus");
|
||||
|
||||
if (manager->modem_iface != MODEM_IFACE_NONE) {
|
||||
g_critical("Modem interface already found! Make sure to only run either of ModemManager or oFono.");
|
||||
return;
|
||||
}
|
||||
manager->modem_iface = MODEM_IFACE_MODEMMANAGER;
|
||||
|
||||
mm_manager_new(connection, G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
NULL, (GAsyncReadyCallback)mm_manager_new_cb, manager);
|
||||
}
|
||||
|
||||
static void mm_iface_clean(struct EG25Manager *manager)
|
||||
{
|
||||
if (manager->mm_manager) {
|
||||
g_clear_object(&manager->mm_manager);
|
||||
manager->mm_manager = NULL;
|
||||
}
|
||||
if (manager->modem_usb_id) {
|
||||
g_free(manager->modem_usb_id);
|
||||
manager->modem_usb_id = NULL;
|
||||
}
|
||||
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
|
||||
manager->modem_iface = MODEM_IFACE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void mm_vanished_cb(GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
g_message("ModemManager vanished from D-Bus");
|
||||
mm_iface_destroy(manager);
|
||||
mm_iface_clean(manager);
|
||||
}
|
||||
|
||||
void mm_iface_init(struct EG25Manager *manager)
|
||||
void mm_iface_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||
{
|
||||
manager->mm_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, MM_DBUS_SERVICE,
|
||||
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
||||
@@ -189,5 +220,9 @@ void mm_iface_init(struct EG25Manager *manager)
|
||||
|
||||
void mm_iface_destroy(struct EG25Manager *manager)
|
||||
{
|
||||
g_clear_object(&manager->mm_manager);
|
||||
mm_iface_clean(manager);
|
||||
if (manager->mm_watch != 0) {
|
||||
g_bus_unwatch_name(manager->mm_watch);
|
||||
manager->mm_watch = 0;
|
||||
}
|
||||
}
|
||||
|
@@ -8,5 +8,5 @@
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
void mm_iface_init(struct EG25Manager *data);
|
||||
void mm_iface_init(struct EG25Manager *data, toml_table_t *config[]);
|
||||
void mm_iface_destroy(struct EG25Manager *data);
|
||||
|
150
src/ofono-iface.c
Normal file
150
src/ofono-iface.c
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
* Copyright (C) 2021 Bhushan Shah <bshah@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "ofono-iface.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <libgdbofono/gdbo-manager.h>
|
||||
#include <libgdbofono/gdbo-modem.h>
|
||||
|
||||
#define OFONO_SERVICE "org.ofono"
|
||||
|
||||
static void modem_added_cb(GDBOManager *manager_proxy,
|
||||
const gchar *path,
|
||||
GVariant *properties,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
GVariant *modem_path;
|
||||
g_debug("Adding ofono modem '%s'", path);
|
||||
|
||||
if (manager->modem_state == EG25_STATE_RESUMING) {
|
||||
if (manager->modem_recovery_timer) {
|
||||
g_source_remove(manager->modem_recovery_timer);
|
||||
manager->modem_recovery_timer = 0;
|
||||
}
|
||||
modem_resume_post(manager);
|
||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||
}
|
||||
|
||||
if (manager->modem_state < EG25_STATE_ACQUIRED)
|
||||
manager->modem_state = EG25_STATE_ACQUIRED;
|
||||
|
||||
if (manager->modem_state < EG25_STATE_CONFIGURED)
|
||||
modem_configure(manager);
|
||||
|
||||
modem_path = g_variant_lookup_value(properties, "SystemPath", G_VARIANT_TYPE_STRING);
|
||||
if (manager->modem_usb_id)
|
||||
g_free(manager->modem_usb_id);
|
||||
manager->modem_usb_id = g_strdup(strrchr(g_variant_dup_string(modem_path, NULL), '/') + 1);
|
||||
}
|
||||
|
||||
static void modem_removed_cb(GDBOManager *manager_proxy,
|
||||
const gchar *path,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
}
|
||||
|
||||
static void get_modems_cb(GDBOManager *manager_proxy,
|
||||
GAsyncResult *res,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
gboolean ok;
|
||||
GVariant *modems;
|
||||
GVariantIter *modems_iter = NULL;
|
||||
g_autoptr(GError) error = NULL;
|
||||
|
||||
const gchar *path;
|
||||
GVariant *properties;
|
||||
|
||||
ok = gdbo_manager_call_get_modems_finish(manager_proxy, &modems,
|
||||
res, &error);
|
||||
if (!ok) {
|
||||
g_warning("Error getting modems from ofono manager: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
g_variant_get(modems, "a(oa{sv})", &modems_iter);
|
||||
while(g_variant_iter_loop(modems_iter, "(&o@a{sv})", &path, &properties)) {
|
||||
g_debug("Got modem object path '%s'", path);
|
||||
modem_added_cb(manager_proxy, path, properties, manager);
|
||||
}
|
||||
g_variant_iter_free(modems_iter);
|
||||
g_variant_unref(modems);
|
||||
}
|
||||
|
||||
static void ofono_appeared_cb(GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
const gchar *name_owner,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
g_message("oFono appeared on D-Bus");
|
||||
|
||||
if (manager->modem_iface != MODEM_IFACE_NONE) {
|
||||
g_critical("Modem interface already found! Make sure to only run either of ModemManager or oFono.");
|
||||
return;
|
||||
}
|
||||
/* now connect to oFono! */
|
||||
manager->ofono_connection = connection;
|
||||
manager->ofono_manager = gdbo_manager_proxy_new_sync(connection,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
OFONO_SERVICE,
|
||||
"/",
|
||||
NULL,
|
||||
&error);
|
||||
if (!manager->ofono_manager) {
|
||||
g_critical("Error creating ofono object manager proxy: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
manager->modem_iface = MODEM_IFACE_OFONO;
|
||||
|
||||
g_signal_connect(manager->ofono_manager, "modem-added",
|
||||
G_CALLBACK(modem_added_cb), manager);
|
||||
g_signal_connect(manager->ofono_manager, "modem-removed",
|
||||
G_CALLBACK(modem_removed_cb), manager);
|
||||
|
||||
gdbo_manager_call_get_modems(manager->ofono_manager,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) get_modems_cb,
|
||||
manager);
|
||||
}
|
||||
|
||||
static void ofono_vanished_cb(GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
g_message("oFono vanished from D-Bus");
|
||||
|
||||
if (manager->modem_iface == MODEM_IFACE_OFONO) {
|
||||
manager->modem_iface = MODEM_IFACE_NONE;
|
||||
ofono_iface_destroy(manager);
|
||||
}
|
||||
}
|
||||
|
||||
void ofono_iface_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||
{
|
||||
manager->ofono_watch = g_bus_watch_name(G_BUS_TYPE_SYSTEM, OFONO_SERVICE,
|
||||
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
||||
(GBusNameAppearedCallback)ofono_appeared_cb,
|
||||
(GBusNameVanishedCallback)ofono_vanished_cb,
|
||||
manager, NULL);
|
||||
}
|
||||
|
||||
void ofono_iface_destroy(struct EG25Manager *manager)
|
||||
{
|
||||
if (manager->modem_usb_id) {
|
||||
g_free(manager->modem_usb_id);
|
||||
manager->modem_usb_id = NULL;
|
||||
}
|
||||
if (manager->ofono_watch != 0) {
|
||||
g_bus_unwatch_name(manager->ofono_watch);
|
||||
manager->ofono_watch = 0;
|
||||
}
|
||||
}
|
12
src/ofono-iface.h
Normal file
12
src/ofono-iface.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Oliver Smith <ollieparanoid@postmarketos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
void ofono_iface_init(struct EG25Manager *data, toml_table_t *config[]);
|
||||
void ofono_iface_destroy(struct EG25Manager *data);
|
237
src/suspend.c
237
src/suspend.c
@@ -9,71 +9,158 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "manager.h"
|
||||
|
||||
#include <gio/gunixfdlist.h>
|
||||
|
||||
#define SD_NAME "org.freedesktop.login1"
|
||||
#define SD_PATH "/org/freedesktop/login1"
|
||||
#define SD_INTERFACE "org.freedesktop.login1.Manager"
|
||||
#define SD_NAME "org.freedesktop.login1"
|
||||
#define SD_PATH "/org/freedesktop/login1"
|
||||
#define SD_INTERFACE "org.freedesktop.login1.Manager"
|
||||
|
||||
static void resume_ok(struct EG25Manager *manager)
|
||||
{
|
||||
manager->modem_state = EG25_STATE_CONFIGURED;
|
||||
modem_resume_post(manager);
|
||||
}
|
||||
|
||||
static gboolean check_modem_resume(struct EG25Manager *manager)
|
||||
{
|
||||
manager->modem_recovery_timer = 0;
|
||||
|
||||
#ifdef HAVE_MMGLIB
|
||||
if (manager->mm_modem) {
|
||||
resume_ok(manager);
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
g_message("Modem wasn't probed in time, restart it!");
|
||||
modem_reset(manager);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean drop_inhibitor(struct EG25Manager *manager)
|
||||
static gboolean drop_inhibitor(struct EG25Manager *manager, gboolean block)
|
||||
{
|
||||
if (manager->suspend_inhibit_fd >= 0) {
|
||||
g_message("dropping systemd sleep inhibitor");
|
||||
close(manager->suspend_inhibit_fd);
|
||||
manager->suspend_inhibit_fd = -1;
|
||||
return TRUE;
|
||||
if (block) {
|
||||
if (manager->suspend_block_fd >= 0) {
|
||||
g_message("dropping systemd sleep block inhibitor");
|
||||
close(manager->suspend_block_fd);
|
||||
manager->suspend_block_fd = -1;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (manager->suspend_delay_fd >= 0) {
|
||||
g_message("dropping systemd sleep delay inhibitor");
|
||||
close(manager->suspend_delay_fd);
|
||||
manager->suspend_delay_fd = -1;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void inhibit_done(GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
static void inhibit_done_delay(GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GDBusProxy *suspend_proxy = G_DBUS_PROXY(source);
|
||||
struct EG25Manager *manager = user_data;
|
||||
GError *error = NULL;
|
||||
g_autoptr (GError) error = NULL;
|
||||
GVariant *res;
|
||||
GUnixFDList *fd_list;
|
||||
|
||||
res = g_dbus_proxy_call_with_unix_fd_list_finish(suspend_proxy, &fd_list, result, &error);
|
||||
res = g_dbus_proxy_call_with_unix_fd_list_finish(suspend_proxy, &fd_list,
|
||||
result, &error);
|
||||
if (!res) {
|
||||
g_warning("inhibit failed: %s", error->message);
|
||||
g_error_free(error);
|
||||
} else {
|
||||
if (!fd_list || g_unix_fd_list_get_length(fd_list) != 1)
|
||||
g_warning("didn't get a single fd back");
|
||||
|
||||
manager->suspend_inhibit_fd = g_unix_fd_list_get(fd_list, 0, NULL);
|
||||
manager->suspend_delay_fd = g_unix_fd_list_get(fd_list, 0, NULL);
|
||||
|
||||
g_message("inhibitor fd is %d", manager->suspend_inhibit_fd);
|
||||
g_message("inhibitor sleep fd is %d", manager->suspend_delay_fd);
|
||||
g_object_unref(fd_list);
|
||||
g_variant_unref(res);
|
||||
}
|
||||
}
|
||||
|
||||
static void take_inhibitor(struct EG25Manager *manager)
|
||||
static void inhibit_done_block(GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GDBusProxy *suspend_proxy = G_DBUS_PROXY(source);
|
||||
struct EG25Manager *manager = user_data;
|
||||
g_autoptr (GError) error = NULL;
|
||||
GVariant *res;
|
||||
GUnixFDList *fd_list;
|
||||
|
||||
res = g_dbus_proxy_call_with_unix_fd_list_finish(suspend_proxy, &fd_list,
|
||||
result, &error);
|
||||
if (!res) {
|
||||
g_warning("inhibit failed: %s", error->message);
|
||||
} else {
|
||||
if (!fd_list || g_unix_fd_list_get_length(fd_list) != 1)
|
||||
g_warning("didn't get a single fd back");
|
||||
|
||||
manager->suspend_block_fd = g_unix_fd_list_get(fd_list, 0, NULL);
|
||||
|
||||
g_message("inhibitor block fd is %d", manager->suspend_block_fd);
|
||||
g_object_unref(fd_list);
|
||||
g_variant_unref(res);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* After the EG25 modem sends 'RDY', it takes up to 2 minutes before all
|
||||
* capabilities are operational. If the modem is suspended before that,
|
||||
* calls and texts may be not recognized properly.
|
||||
*/
|
||||
static gboolean modem_fully_booted(struct EG25Manager *manager)
|
||||
{
|
||||
g_message("Modem is up for %u seconds and fully ready", manager->modem_boot_timeout);
|
||||
manager->modem_boot_timer = 0;
|
||||
drop_inhibitor(manager, TRUE);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void take_inhibitor(struct EG25Manager *manager, gboolean block)
|
||||
{
|
||||
GVariant *variant_arg;
|
||||
|
||||
g_assert(manager->suspend_inhibit_fd == -1);
|
||||
if (block) {
|
||||
if(manager->suspend_block_fd != -1)
|
||||
drop_inhibitor(manager, TRUE);
|
||||
|
||||
variant_arg = g_variant_new ("(ssss)", "sleep", "eg25manager",
|
||||
"eg25manager needs to prepare modem for sleep", "delay");
|
||||
variant_arg = g_variant_new ("(ssss)", "sleep", "eg25manager",
|
||||
"eg25manager needs to wait for modem to be fully booted",
|
||||
"block");
|
||||
|
||||
g_message("taking systemd sleep inhibitor");
|
||||
g_dbus_proxy_call_with_unix_fd_list(manager->suspend_proxy, "Inhibit", variant_arg,
|
||||
0, G_MAXINT, NULL, NULL, inhibit_done, manager);
|
||||
g_message("taking systemd sleep inhibitor (blocking)");
|
||||
g_dbus_proxy_call_with_unix_fd_list(manager->suspend_proxy, "Inhibit",
|
||||
variant_arg, 0, G_MAXINT, NULL, NULL,
|
||||
inhibit_done_block, manager);
|
||||
manager->modem_boot_timer = g_timeout_add_seconds(manager->modem_boot_timeout,
|
||||
G_SOURCE_FUNC(modem_fully_booted),
|
||||
manager);
|
||||
}
|
||||
else {
|
||||
if(manager->suspend_delay_fd != -1)
|
||||
drop_inhibitor(manager, FALSE);
|
||||
|
||||
variant_arg = g_variant_new ("(ssss)", "sleep", "eg25manager",
|
||||
"eg25manager needs to prepare modem for sleep",
|
||||
"delay");
|
||||
|
||||
g_message("taking systemd sleep inhibitor");
|
||||
g_dbus_proxy_call_with_unix_fd_list(manager->suspend_proxy, "Inhibit",
|
||||
variant_arg, 0, G_MAXINT, NULL, NULL,
|
||||
inhibit_done_delay, manager);
|
||||
}
|
||||
}
|
||||
|
||||
static void signal_cb(GDBusProxy *proxy,
|
||||
@@ -93,13 +180,31 @@ static void signal_cb(GDBusProxy *proxy,
|
||||
if (is_about_to_suspend) {
|
||||
g_message("system is about to suspend");
|
||||
manager->modem_state = EG25_STATE_SUSPENDING;
|
||||
modem_suspend(manager);
|
||||
modem_suspend_pre(manager);
|
||||
} else {
|
||||
g_message("system is resuming");
|
||||
take_inhibitor(manager);
|
||||
take_inhibitor(manager, FALSE);
|
||||
modem_resume_pre(manager);
|
||||
manager->modem_state = EG25_STATE_RESUMING;
|
||||
manager->suspend_source = g_timeout_add_seconds(8, G_SOURCE_FUNC(check_modem_resume), manager);
|
||||
if (
|
||||
#ifdef HAVE_MMGLIB
|
||||
manager->mm_modem ||
|
||||
#endif
|
||||
manager->modem_iface == MODEM_IFACE_OFONO) {
|
||||
/*
|
||||
* On some systems ModemManager doesn't handle suspend/resume, so
|
||||
* we still have a valid/managed modem when resuming. In this case,
|
||||
* do the whole resume sequence immediately.
|
||||
*
|
||||
* If modem is managed by ofono, we also do resume sequence immediately
|
||||
* as ofono handles resuming from sleep itself.
|
||||
*/
|
||||
resume_ok(manager);
|
||||
} else {
|
||||
manager->modem_state = EG25_STATE_RESUMING;
|
||||
manager->modem_recovery_timer = g_timeout_add_seconds(manager->modem_recovery_timeout,
|
||||
G_SOURCE_FUNC(check_modem_resume),
|
||||
manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,38 +219,66 @@ static void name_owner_cb(GObject *object,
|
||||
g_assert(proxy == manager->suspend_proxy);
|
||||
|
||||
owner = g_dbus_proxy_get_name_owner(proxy);
|
||||
if (owner)
|
||||
take_inhibitor(manager);
|
||||
else
|
||||
drop_inhibitor(manager);
|
||||
g_free(owner);
|
||||
if (owner) {
|
||||
take_inhibitor(manager, FALSE);
|
||||
g_free(owner);
|
||||
} else {
|
||||
drop_inhibitor(manager, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_proxy_acquired(GObject *object,
|
||||
GAsyncResult *res,
|
||||
struct EG25Manager *manager)
|
||||
{
|
||||
GError *error = NULL;
|
||||
g_autoptr (GError) error = NULL;
|
||||
char *owner;
|
||||
|
||||
manager->suspend_proxy = g_dbus_proxy_new_for_bus_finish(res, &error);
|
||||
if (!manager->suspend_proxy) {
|
||||
g_warning("failed to acquire logind proxy: %s", error->message);
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_connect(manager->suspend_proxy, "notify::g-name-owner", G_CALLBACK(name_owner_cb), manager);
|
||||
g_signal_connect(manager->suspend_proxy, "g-signal", G_CALLBACK(signal_cb), manager);
|
||||
g_signal_connect(manager->suspend_proxy, "notify::g-name-owner",
|
||||
G_CALLBACK(name_owner_cb), manager);
|
||||
g_signal_connect(manager->suspend_proxy, "g-signal",
|
||||
G_CALLBACK(signal_cb), manager);
|
||||
|
||||
owner = g_dbus_proxy_get_name_owner(manager->suspend_proxy);
|
||||
if (owner)
|
||||
take_inhibitor(manager);
|
||||
g_free(owner);
|
||||
if (owner) {
|
||||
take_inhibitor(manager, FALSE);
|
||||
g_free(owner);
|
||||
}
|
||||
}
|
||||
|
||||
void suspend_init(struct EG25Manager *manager)
|
||||
void suspend_init(struct EG25Manager *manager, toml_table_t *config[])
|
||||
{
|
||||
toml_table_t *suspend_config[EG25_CONFIG_COUNT];
|
||||
|
||||
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
|
||||
suspend_config[i] = config[i] ? toml_table_in(config[i], "suspend") : NULL;
|
||||
|
||||
/*
|
||||
* The `suspend` section is optional in both the user and system config files,
|
||||
* so let's make sure suspend_config[EG25_CONFIG_SYS] is valid if one of the
|
||||
* files has it.
|
||||
*/
|
||||
if (suspend_config[EG25_CONFIG_USER] && !suspend_config[EG25_CONFIG_SYS]) {
|
||||
suspend_config[EG25_CONFIG_SYS] = suspend_config[EG25_CONFIG_USER];
|
||||
suspend_config[EG25_CONFIG_USER] = NULL;
|
||||
}
|
||||
|
||||
if (suspend_config[EG25_CONFIG_SYS]) {
|
||||
config_get_uint(suspend_config, "boot_timeout", &manager->modem_boot_timeout);
|
||||
config_get_uint(suspend_config, "recovery_timeout", &manager->modem_recovery_timeout);
|
||||
}
|
||||
|
||||
if (manager->modem_boot_timeout == 0)
|
||||
manager->modem_boot_timeout = 120;
|
||||
if (manager->modem_recovery_timeout == 0)
|
||||
manager->modem_recovery_timeout = 9;
|
||||
|
||||
g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
|
||||
@@ -155,14 +288,26 @@ void suspend_init(struct EG25Manager *manager)
|
||||
|
||||
void suspend_destroy(struct EG25Manager *manager)
|
||||
{
|
||||
drop_inhibitor(manager);
|
||||
g_object_unref(manager->suspend_proxy);
|
||||
drop_inhibitor(manager, FALSE);
|
||||
drop_inhibitor(manager, TRUE);
|
||||
if (manager->modem_recovery_timer) {
|
||||
g_source_remove(manager->modem_recovery_timer);
|
||||
manager->modem_recovery_timer = 0;
|
||||
}
|
||||
if (manager->modem_boot_timer) {
|
||||
g_source_remove(manager->modem_boot_timer);
|
||||
manager->modem_boot_timer = 0;
|
||||
}
|
||||
if (manager->suspend_proxy) {
|
||||
g_object_unref(manager->suspend_proxy);
|
||||
manager->suspend_proxy = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void suspend_inhibit(struct EG25Manager *manager, gboolean inhibit)
|
||||
void suspend_inhibit(struct EG25Manager *manager, gboolean inhibit, gboolean block)
|
||||
{
|
||||
if (inhibit)
|
||||
take_inhibitor(manager);
|
||||
take_inhibitor(manager, block);
|
||||
else
|
||||
drop_inhibitor(manager);
|
||||
drop_inhibitor(manager, block);
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
void suspend_init (struct EG25Manager *data);
|
||||
void suspend_init (struct EG25Manager *data, toml_table_t *config[]);
|
||||
void suspend_destroy (struct EG25Manager *data);
|
||||
|
||||
void suspend_inhibit (struct EG25Manager *data, gboolean inhibit);
|
||||
void suspend_inhibit (struct EG25Manager *data, gboolean inhibit, gboolean block);
|
||||
|
2249
src/toml.c
Normal file
2249
src/toml.c
Normal file
File diff suppressed because it is too large
Load Diff
175
src/toml.h
Normal file
175
src/toml.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 - 2019 CK Tan
|
||||
https://github.com/cktan/tomlc99
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#ifndef TOML_H
|
||||
#define TOML_H
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define TOML_EXTERN extern "C"
|
||||
#else
|
||||
#define TOML_EXTERN extern
|
||||
#endif
|
||||
|
||||
typedef struct toml_timestamp_t toml_timestamp_t;
|
||||
typedef struct toml_table_t toml_table_t;
|
||||
typedef struct toml_array_t toml_array_t;
|
||||
typedef struct toml_datum_t toml_datum_t;
|
||||
|
||||
/* Parse a file. Return a table on success, or 0 otherwise.
|
||||
* Caller must toml_free(the-return-value) after use.
|
||||
*/
|
||||
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
|
||||
char* errbuf,
|
||||
int errbufsz);
|
||||
|
||||
/* Parse a string containing the full config.
|
||||
* Return a table on success, or 0 otherwise.
|
||||
* Caller must toml_free(the-return-value) after use.
|
||||
*/
|
||||
TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
|
||||
char* errbuf,
|
||||
int errbufsz);
|
||||
|
||||
/* Free the table returned by toml_parse() or toml_parse_file(). Once
|
||||
* this function is called, any handles accessed through this tab
|
||||
* directly or indirectly are no longer valid.
|
||||
*/
|
||||
TOML_EXTERN void toml_free(toml_table_t* tab);
|
||||
|
||||
|
||||
/* Timestamp types. The year, month, day, hour, minute, second, z
|
||||
* fields may be NULL if they are not relevant. e.g. In a DATE
|
||||
* type, the hour, minute, second and z fields will be NULLs.
|
||||
*/
|
||||
struct toml_timestamp_t {
|
||||
struct { /* internal. do not use. */
|
||||
int year, month, day;
|
||||
int hour, minute, second, millisec;
|
||||
char z[10];
|
||||
} __buffer;
|
||||
int *year, *month, *day;
|
||||
int *hour, *minute, *second, *millisec;
|
||||
char* z;
|
||||
};
|
||||
|
||||
|
||||
/*-----------------------------------------------------------------
|
||||
* Enhanced access methods
|
||||
*/
|
||||
struct toml_datum_t {
|
||||
int ok;
|
||||
union {
|
||||
toml_timestamp_t* ts; /* ts must be freed after use */
|
||||
char* s; /* string value. s must be freed after use */
|
||||
int b; /* bool value */
|
||||
int64_t i; /* int value */
|
||||
double d; /* double value */
|
||||
} u;
|
||||
};
|
||||
|
||||
/* on arrays: */
|
||||
/* ... retrieve size of array. */
|
||||
TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
|
||||
/* ... retrieve values using index. */
|
||||
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
|
||||
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
|
||||
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
|
||||
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
|
||||
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
|
||||
/* ... retrieve array or table using index. */
|
||||
TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
|
||||
TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
|
||||
|
||||
/* on tables: */
|
||||
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
|
||||
TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
|
||||
/* ... retrieve values using key. */
|
||||
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
|
||||
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
|
||||
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
|
||||
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
|
||||
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
|
||||
/* .. retrieve array or table using key. */
|
||||
TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
|
||||
const char* key);
|
||||
TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
|
||||
const char* key);
|
||||
|
||||
/*-----------------------------------------------------------------
|
||||
* lesser used
|
||||
*/
|
||||
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
|
||||
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
|
||||
|
||||
/* For array kind 'v'alue, return the type of values
|
||||
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
|
||||
0 if unknown
|
||||
*/
|
||||
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
|
||||
|
||||
/* Return the key of an array */
|
||||
TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
|
||||
|
||||
/* Return the number of key-values in a table */
|
||||
TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
|
||||
|
||||
/* Return the number of arrays in a table */
|
||||
TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
|
||||
|
||||
/* Return the number of sub-tables in a table */
|
||||
TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
|
||||
|
||||
/* Return the key of a table*/
|
||||
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
* misc
|
||||
*/
|
||||
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
|
||||
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
|
||||
TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
|
||||
void (*xxfree)(void*));
|
||||
|
||||
|
||||
/*--------------------------------------------------------------
|
||||
* deprecated
|
||||
*/
|
||||
/* A raw value, must be processed by toml_rto* before using. */
|
||||
typedef const char* toml_raw_t;
|
||||
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
|
||||
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
|
||||
TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
|
||||
TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
|
||||
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
|
||||
TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
|
||||
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
|
||||
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
|
||||
|
||||
|
||||
#endif /* TOML_H */
|
73
src/udev.c
Normal file
73
src/udev.c
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include "udev.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static void udev_event_cb(GUdevClient *client, gchar *action, GUdevDevice *device, gpointer data)
|
||||
{
|
||||
struct EG25Manager *manager = data;
|
||||
const gchar *prop;
|
||||
long vid = 0, pid = 0;
|
||||
|
||||
/*
|
||||
* Act only if the device is the one identified as a modem by MM/ofono
|
||||
*/
|
||||
if (!manager->modem_usb_id ||
|
||||
strcmp(g_udev_device_get_name(device), manager->modem_usb_id) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
prop = g_udev_device_get_property(device, "ID_VENDOR_ID");
|
||||
if (prop)
|
||||
vid = strtol(prop, NULL, 16);
|
||||
|
||||
prop = g_udev_device_get_property(device, "ID_MODEL_ID");
|
||||
if (prop)
|
||||
pid = strtol(prop, NULL, 16);
|
||||
|
||||
if (strcmp(action, "bind") == 0 && vid != manager->usb_vid && pid != manager->usb_pid) {
|
||||
/*
|
||||
* Modem is probably executing a FW upgrade, make sure we don't interrupt it
|
||||
*/
|
||||
if (manager->schedule_reset_timer != 0) {
|
||||
g_message("Modem re-appeared with different VID/PID, cancel reset.");
|
||||
g_source_remove(manager->schedule_reset_timer);
|
||||
manager->schedule_reset_timer = 0;
|
||||
}
|
||||
manager->modem_state = EG25_STATE_UPDATING;
|
||||
}
|
||||
|
||||
if (strcmp(action, "unbind") != 0 ||
|
||||
manager->modem_state == EG25_STATE_UPDATING ||
|
||||
manager->modem_state == EG25_STATE_RESETTING ||
|
||||
manager->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[])
|
||||
{
|
||||
const char * const subsystems[] = { "usb", NULL };
|
||||
|
||||
manager->udev = g_udev_client_new(subsystems);
|
||||
g_signal_connect(manager->udev, "uevent", G_CALLBACK(udev_event_cb), manager);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void udev_destroy (struct EG25Manager *manager)
|
||||
{
|
||||
if (manager->udev) {
|
||||
g_object_unref(manager->udev);
|
||||
manager->udev = NULL;
|
||||
}
|
||||
}
|
12
src/udev.h
Normal file
12
src/udev.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "manager.h"
|
||||
|
||||
void udev_init (struct EG25Manager *data, toml_table_t *config[]);
|
||||
void udev_destroy (struct EG25Manager *data);
|
4
udev/80-modem-eg25.rules
Normal file
4
udev/80-modem-eg25.rules
Normal file
@@ -0,0 +1,4 @@
|
||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/control}="auto"
|
||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/autosuspend_delay_ms}="3000"
|
||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/wakeup}="enabled"
|
||||
ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0125", ATTR{power/persist}="0"
|
7
udev/meson.build
Normal file
7
udev/meson.build
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
install_data ('80-modem-eg25.rules', install_dir: udevrulesdir)
|
Reference in New Issue
Block a user