Use subsystem recovery for Sargo modem faults
Select RELATED before HAL startup and create oFono’s private state directory. A controlled modem restart recovered network registration without rebooting the phone; two persistent boots and native UI checks passed. The IMS firmware watchdog root cause remains unresolved.
This commit is contained in:
parent
bdbb2e13f3
commit
d5cfc8a7d7
4 changed files with 261 additions and 0 deletions
176
changelog.json
Normal file
176
changelog.json
Normal file
File diff suppressed because one or more lines are too long
35
tundra/runtime/modem-recovery.py
Normal file
35
tundra/runtime/modem-recovery.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
"""Keep a Sargo modem fault from immediately restarting the whole phone."""
|
||||||
|
from pathlib import Path
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def configure(devices=Path('/sys/bus/msm_subsys/devices')):
|
||||||
|
matches = [p.parent for p in devices.glob('*/name')
|
||||||
|
if p.read_text().strip() == 'modem']
|
||||||
|
if len(matches) != 1:
|
||||||
|
raise RuntimeError('Expected one Sargo modem subsystem')
|
||||||
|
level = matches[0]/'restart_level'
|
||||||
|
previous = level.read_text().strip()
|
||||||
|
if previous not in ('SYSTEM', 'RELATED'):
|
||||||
|
raise RuntimeError('Unsupported modem restart policy: '+previous)
|
||||||
|
# This kernel supports coupled subsystem recovery. Firmware failures and
|
||||||
|
# unsuccessful recovery remain visible in the kernel log; do not suppress
|
||||||
|
# its watchdog or crash accounting.
|
||||||
|
if previous != 'RELATED':
|
||||||
|
level.write_text('RELATED\n')
|
||||||
|
if level.read_text().strip() != 'RELATED':
|
||||||
|
raise RuntimeError('Modem restart policy was not applied')
|
||||||
|
print('Sargo modem recovery: '+previous+' -> RELATED', flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
for attempt in range(30):
|
||||||
|
try:
|
||||||
|
configure()
|
||||||
|
break
|
||||||
|
except (FileNotFoundError, RuntimeError):
|
||||||
|
if attempt == 29:
|
||||||
|
raise
|
||||||
|
time.sleep(1)
|
||||||
|
|
@ -2,10 +2,16 @@
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
"""Configure isolated oFono/PulseAudio diagnostics; no call or SMS on startup."""
|
"""Configure isolated oFono/PulseAudio diagnostics; no call or SMS on startup."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
def prepare(root, write):
|
def prepare(root, write):
|
||||||
write('etc/tundra/hardware-enabled', 'sargo\n')
|
write('etc/tundra/hardware-enabled', 'sargo\n')
|
||||||
|
shutil.copy2(Path(__file__).with_name('modem-recovery.py'),
|
||||||
|
root/'etc/tundra/modem-recovery.py')
|
||||||
|
write('etc/systemd/system/tundra-hal.service.d/modem-recovery.conf', '''[Service]
|
||||||
|
ExecStartPre=/usr/bin/python3 /etc/tundra/modem-recovery.py
|
||||||
|
''')
|
||||||
with (root/'etc/group').open('a') as groups:
|
with (root/'etc/group').open('a') as groups:
|
||||||
groups.write('audio:x:1005:droidian\ncamera:x:1006:droidian\nradio:x:1001:\nmedia:x:1013:droidian\n')
|
groups.write('audio:x:1005:droidian\ncamera:x:1006:droidian\nradio:x:1001:\nmedia:x:1013:droidian\n')
|
||||||
write('etc/ofono/binder.conf', '[Settings]\nExpectSlots = slot1\n\n[slot1]\npath = /ril_0\nslot = 0\n')
|
write('etc/ofono/binder.conf', '[Settings]\nExpectSlots = slot1\n\n[slot1]\npath = /ril_0\nslot = 0\n')
|
||||||
|
|
@ -26,6 +32,8 @@ After=tundra-hal.service dbus.service
|
||||||
[Service]
|
[Service]
|
||||||
Type=dbus
|
Type=dbus
|
||||||
BusName=org.ofono
|
BusName=org.ofono
|
||||||
|
StateDirectory=ofono
|
||||||
|
StateDirectoryMode=0700
|
||||||
ExecStartPre=/usr/bin/binder-wait android.hardware.radio@1.0::IRadio/slot1
|
ExecStartPre=/usr/bin/binder-wait android.hardware.radio@1.0::IRadio/slot1
|
||||||
ExecStart=/usr/sbin/ofonod --nodetach
|
ExecStart=/usr/sbin/ofonod --nodetach
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
|
||||||
42
tundra/tests/test_modem_recovery.py
Normal file
42
tundra/tests/test_modem_recovery.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
"""Check modem selection without modifying a host's subsystem policy."""
|
||||||
|
import importlib.util
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
'modem_recovery', Path(__file__).resolve().parents[1]/'runtime/modem-recovery.py')
|
||||||
|
recovery = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(recovery)
|
||||||
|
|
||||||
|
|
||||||
|
class ModemRecoveryTest(unittest.TestCase):
|
||||||
|
def test_only_modem_policy_changes_and_repeated_setup_is_safe(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder:
|
||||||
|
root = Path(folder)
|
||||||
|
for identifier, name in [('subsys1', 'adsp'), ('subsys7', 'modem')]:
|
||||||
|
device = root/identifier
|
||||||
|
device.mkdir()
|
||||||
|
(device/'name').write_text(name+'\n')
|
||||||
|
(device/'restart_level').write_text('SYSTEM\n')
|
||||||
|
recovery.configure(root)
|
||||||
|
recovery.configure(root)
|
||||||
|
self.assertEqual((root/'subsys1/restart_level').read_text(), 'SYSTEM\n')
|
||||||
|
self.assertEqual((root/'subsys7/restart_level').read_text(), 'RELATED\n')
|
||||||
|
|
||||||
|
def test_missing_modem_is_reported(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder:
|
||||||
|
with self.assertRaisesRegex(RuntimeError, 'Expected one'):
|
||||||
|
recovery.configure(Path(folder))
|
||||||
|
|
||||||
|
def test_unknown_policy_is_preserved(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder:
|
||||||
|
root = Path(folder)
|
||||||
|
device = root/'subsys0'
|
||||||
|
device.mkdir()
|
||||||
|
(device/'name').write_text('modem\n')
|
||||||
|
(device/'restart_level').write_text('UNKNOWN\n')
|
||||||
|
with self.assertRaisesRegex(RuntimeError, 'Unsupported'):
|
||||||
|
recovery.configure(root)
|
||||||
|
self.assertEqual((device/'restart_level').read_text(), 'UNKNOWN\n')
|
||||||
Loading…
Reference in a new issue