IQ.Pilot Release Commit @ e46d557

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-07 00:13:39 -05:00
parent 03c3158b81
commit 824bb9ddfd
216 changed files with 7457 additions and 3151 deletions

View File

@@ -596,7 +596,7 @@ void SpectraCamera::config_bps(int idx, int request_id) {
tmp.header = CAM_ICP_CMD_GENERIC_BLOB_CLK;
tmp.header |= (sizeof(cam_icp_clk_bw_request)) << 8;
tmp.clk.budget_ns = 0x1fca058;
tmp.clk.frame_cycles = 2329024; // comes from the striping lib
tmp.clk.frame_cycles = 20000000; // force max BPS clock (600 MHz)
tmp.clk.rt_flag = 0x0;
tmp.clk.uncompressed_bw = 0x38512180;
tmp.clk.compressed_bw = 0x38512180;
@@ -843,7 +843,7 @@ void SpectraCamera::config_bps_downscale(int idx, int request_id) {
tmp.header = CAM_ICP_CMD_GENERIC_BLOB_CLK;
tmp.header |= (sizeof(cam_icp_clk_bw_request)) << 8;
tmp.clk.budget_ns = 0x1fca058;
tmp.clk.frame_cycles = sensor->frame_width * sensor->frame_height; // matches striping lib pixelCount
tmp.clk.frame_cycles = 20000000; // force max BPS clock (600 MHz)
tmp.clk.rt_flag = 0x0;
tmp.clk.uncompressed_bw = 0x38512180;
tmp.clk.compressed_bw = 0x38512180;

View File

@@ -14,3 +14,9 @@ if TICI:
HARDWARE = cast(HardwareBase, Tici())
else:
HARDWARE = cast(HardwareBase, Pc())
# Only comma 3/3X expose the DMA-BUF EGL extensions used by the zero-copy
# camera renderer and the direct EGL frame-pacing calls. /TICI is also present
# on comma 4, so it identifies the AGNOS hardware family rather than this GPU
# capability.
EGL_DMA_BUF_SUPPORTED = TICI and HARDWARE.get_device_type() in ("tici", "tizi")

View File

@@ -1,15 +1,25 @@
#pragma once
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "cereal/gen/cpp/log.capnp.h"
// no-op base hw class
class HardwareNone {
public:
struct UfsHealth {
uint8_t pre_eol_info;
uint8_t life_time_estimate_a;
uint8_t life_time_estimate_b;
std::vector<uint8_t> vendor_health_report;
};
static std::string get_name() { return ""; }
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; }
static int get_voltage() { return 0; }
@@ -21,6 +31,8 @@ public:
return {};
}
static std::optional<UfsHealth> get_ufs_health() { return std::nullopt; }
static void set_ir_power(int percentage) {}
static bool PC() { return false; }

View File

@@ -67,28 +67,28 @@
},
{
"name": "boot",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25.img.xz",
"hash": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
"hash_raw": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89.img.xz",
"hash": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"hash_raw": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"size": 18216960,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "2c969938fdd59528e6244820b77ec6b2cef9ab49cc9fedd490b0b3d195c189e4"
"ondevice_hash": "fdcb135e8412a896c4a882738a7022f6b7779939d8716f24b9a566b0b3ff3757"
},
{
"name": "system",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz",
"hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9",
"hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img.xz",
"hash": "62a08bdf79f6dc0075eba8b93b55cbac6eeee0ecd2b6e8ab05d51ef61a9a880a",
"hash_raw": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"size": 6291456000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff",
"ondevice_hash": "9ed5bc147a6d4c2e0a89562f31096a7802485ee8e740defeab7674abbf6d4baa",
"alt": {
"hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img",
"hash": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img",
"size": 6291456000
}
}

View File

@@ -1 +1 @@
+LyP/p4uBBxbTwRwPrwR/2qPaHkAXmlFkg0GdxOBuujPY89GxP4t5SNKxirqiw9LmMcfA0JfVwMeR3QD9rQ6Dg==
LxtrHkl5DFbSWnd1DZCkinmXiKFUVUausf0oYpa97dyAp3QGpWx7wrEAUZuJbrERQtdIV72ZU2eKOaQwGMBQBg==

View File

@@ -56,28 +56,28 @@
},
{
"name": "boot",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25.img.xz",
"hash": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
"hash_raw": "179865564b1c196897c77edc41d99a8d4831a3a95564cac6868e7942cd11fd25",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/boot-50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89.img.xz",
"hash": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"hash_raw": "50b102d3dfa5d0eea506d9586745470cd59bcea9a723408a6abc20ffd6493a89",
"size": 18216960,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "2c969938fdd59528e6244820b77ec6b2cef9ab49cc9fedd490b0b3d195c189e4"
"ondevice_hash": "fdcb135e8412a896c4a882738a7022f6b7779939d8716f24b9a566b0b3ff3757"
},
{
"name": "system",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img.xz",
"hash": "0c9e7dee6c7365600c77b33c4996456459d04aad005e3e41bf6ee2e8af74ceb9",
"hash_raw": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img.xz",
"hash": "62a08bdf79f6dc0075eba8b93b55cbac6eeee0ecd2b6e8ab05d51ef61a9a880a",
"hash_raw": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"size": 6291456000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "1ee589d3d728a03561718383bf640171f1aef048573e46fe17239617c7c19fff",
"ondevice_hash": "9ed5bc147a6d4c2e0a89562f31096a7802485ee8e740defeab7674abbf6d4baa",
"alt": {
"hash": "03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-03397de3da2e9a6d2808b95b72c945b3af71ab79f7f0298624aed62b8749fc5a.img",
"hash": "a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd",
"url": "https://sdn.konn3kt.com/agnos/16-iqlvbs/system-a473962357e3c0c7639af17006def4567f02e64d578b00e2e0037012755be5cd.img",
"size": 6291456000
}
}

View File

@@ -1 +1 @@
RCYk34Ifjx7S9PIhPm6p49vFMhamPwvw8Lfbb6a6HO8sUOi3J2PcY4VDUcqQZWU6+Am++HP3nJSea8OmAsckAg==
mKCTfzNHr1d0BYPdmXQRPGpnVuLaDLGZGLbq+86ZJQaGIK1ekcKeYeycoX8BTMNFr+89Tnd77M/CvmjM8yteCg==

View File

@@ -1,17 +1,63 @@
#pragma once
#include <cstdlib>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <fcntl.h>
#include <fstream>
#include <map>
#include <string>
#include <algorithm> // for std::clamp
#include <sys/ioctl.h>
#include <unistd.h>
#include "common/util.h"
#include "system/hardware/base.h"
class HardwareTici : public HardwareNone {
public:
static std::optional<UfsHealth> get_ufs_health() {
constexpr unsigned long UFS_IOCTL_QUERY = 0x5388;
constexpr uint32_t UPIU_QUERY_OPCODE_READ_DESC = 0x1;
constexpr uint8_t QUERY_DESC_IDN_HEALTH = 0x9;
constexpr uint16_t QUERY_DESC_HEALTH_SIZE = 0x25;
struct UfsQuery {
uint32_t opcode;
uint8_t idn;
uint8_t reserved;
uint16_t buf_size;
std::array<uint8_t, QUERY_DESC_HEALTH_SIZE> buffer;
};
static_assert(offsetof(UfsQuery, buffer) == 8);
UfsQuery query = {};
query.opcode = UPIU_QUERY_OPCODE_READ_DESC;
query.idn = QUERY_DESC_IDN_HEALTH;
query.buf_size = QUERY_DESC_HEALTH_SIZE;
int fd = open("/dev/sda", O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return std::nullopt;
}
int ret = ioctl(fd, UFS_IOCTL_QUERY, &query);
close(fd);
if (ret != 0 || query.buf_size < 5 || query.buf_size > query.buffer.size() ||
query.buffer[0] != query.buf_size || query.buffer[1] != QUERY_DESC_IDN_HEALTH) {
return std::nullopt;
}
return UfsHealth{
query.buffer[2],
query.buffer[3],
query.buffer[4],
std::vector<uint8_t>(query.buffer.begin() + 5, query.buffer.begin() + query.buf_size),
};
}
static std::string get_name() {
std::string model = util::read_file("/sys/firmware/devicetree/base/model");
return util::strip(model.substr(std::string("comma ").size()));

View File

@@ -78,9 +78,10 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) {
uint32_t idx = -1;
bool exit = false;
// POLLIN is capture, POLLOUT is frame
// POLLIN is capture, POLLOUT is frame. Qualcomm's reference client also
// requests the corresponding normal-data bits.
struct pollfd pfd;
pfd.events = POLLIN | POLLOUT;
pfd.events = POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
pfd.fd = e->fd;
// save the header
@@ -105,7 +106,7 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) {
}
int frame_id = -1;
if (pfd.revents & POLLIN) {
if (pfd.revents & (POLLIN | POLLRDNORM)) {
unsigned int bytesused, flags, index;
struct timeval timestamp;
dequeue_buffer(e->fd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, &index, &bytesused, &flags, &timestamp);
@@ -136,7 +137,7 @@ void V4LEncoder::dequeue_handler(V4LEncoder *e) {
queue_buffer(e->fd, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, index, &e->buf_out[index]);
}
if (pfd.revents & POLLOUT) {
if (pfd.revents & (POLLOUT | POLLWRNORM)) {
unsigned int index;
dequeue_buffer(e->fd, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, &index);
e->free_buf_in.push(index);
@@ -244,7 +245,7 @@ V4LEncoder::V4LEncoder(const EncoderInfo &encoder_info, int in_width, int in_hei
{ .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, .value = V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN},
{ .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, .value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC},
{ .id = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL, .value = V4L2_CID_MPEG_VIDC_VIDEO_H264_CABAC_MODEL_0},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, .value = 0},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, .value = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, .value = 0},
{ .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, .value = 0},
{ .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, .value = 0},

View File

@@ -46,6 +46,14 @@ kj::Array<capnp::word> logger_build_init_data() {
init.setKernelVersion(util::read_file("/proc/version"));
init.setOsVersion(util::read_file("/VERSION"));
if (auto health = Hardware::get_ufs_health()) {
auto ufs_health = init.initUfsHealth();
ufs_health.setPreEolInfo(health->pre_eol_info);
ufs_health.setLifeTimeEstimateA(health->life_time_estimate_a);
ufs_health.setLifeTimeEstimateB(health->life_time_estimate_b);
ufs_health.setVendorHealthReport(capnp::Data::Reader(health->vendor_health_report.data(), health->vendor_health_report.size()));
}
// log params
Params params(util::getenv("PARAMS_COPY_PATH", ""));
std::map<std::string, std::string> params_map = params.readAll();

View File

@@ -170,6 +170,15 @@ class TestLoggerd:
assert initData.dirty != bool(os.environ["CLEAN"])
assert initData.version == get_version()
if TICI:
assert initData._has("ufsHealth")
assert initData.ufsHealth.preEolInfo in (1, 2, 3)
assert 1 <= initData.ufsHealth.lifeTimeEstimateA <= 11
assert 1 <= initData.ufsHealth.lifeTimeEstimateB <= 11
assert len(initData.ufsHealth.vendorHealthReport) == 32
else:
assert not initData._has("ufsHealth")
if os.path.isfile("/proc/cmdline"):
with open("/proc/cmdline") as f:
assert list(initData.kernelArgs) == f.read().strip().split(" ")

View File

@@ -17,6 +17,10 @@ from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.swaglog import cloudlog
MAX_CRASH_BACKOFF = 300.0
CRASH_RESET_TIME = 60.0
CRASH_LOOP_THRESHOLD = 6
try:
from openpilot.system.proprietary_runtime.runtime_paths import preferred_runner_path
except ModuleNotFoundError:
@@ -82,6 +86,10 @@ class ManagerProcess(ABC):
name = ""
shutting_down = False
restart_if_crash = False
crash_count = 0
last_restart_time = 0.0
last_alive_time = 0.0
crash_loop_logged = False
@abstractmethod
def prepare(self) -> None:
@@ -318,13 +326,33 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None
not_run = []
running = []
now = time.monotonic()
for p in procs:
if p.enabled and p.name not in not_run and p.should_run(started, params, CP):
if p.restart_if_crash and p.proc is not None and not p.proc.is_alive():
cloudlog.error(f'Restarting {p.name} (exitcode {p.proc.exitcode})')
p.restart()
if p.restart_if_crash and p.proc is not None and p.proc.is_alive():
p.last_alive_time = now
elif p.restart_if_crash and p.proc is not None:
# uptime, not time-since-restart: the latter also counts the backoff wait,
# which would reset the counter as soon as backoff exceeds CRASH_RESET_TIME
if p.last_alive_time - p.last_restart_time > CRASH_RESET_TIME:
p.crash_count = 0
p.crash_loop_logged = False
backoff = 0.0 if not p.crash_count else min(MAX_CRASH_BACKOFF, 2.0 ** (p.crash_count - 1))
if now - p.last_restart_time >= backoff:
p.crash_count += 1
p.last_restart_time = now
cloudlog.error(f'Restarting {p.name} (exitcode {p.proc.exitcode}) [crash {p.crash_count}]')
if p.crash_count >= CRASH_LOOP_THRESHOLD and not p.crash_loop_logged:
# never stop retrying: giving up on hardwared or ui is worse than restarting slowly
cloudlog.error(f'{p.name} is in a crash loop, backing off to {MAX_CRASH_BACKOFF}s between restarts')
p.crash_loop_logged = True
p.restart()
running.append(p)
else:
p.crash_count = 0
p.crash_loop_logged = False
p.last_alive_time = 0.0
p.stop(block=False)
for p in running:

View File

@@ -98,7 +98,9 @@ def constructiond_onroad(started: bool, params: Params, CP: car.CarParams) -> bo
return started and params.get_bool("ConstructionZoneAssist")
def iqvd_onroad(started: bool, params: Params, CP: car.CarParams) -> bool:
return started and params.get_bool("VisionVehicleTracks")
# held for 1.0d: iqvd runs a detector per frame and the added load is not
# something 1.0c needs to carry. re-enable by restoring the param check.
return False
def only_offroad(started: bool, params: Params, CP: car.CarParams) -> bool:
return not started
@@ -109,6 +111,12 @@ def livestream(started: bool, params: Params, CP: car.CarParams) -> bool:
# down cleanly when the session ends — no subprocess management inside hephaestusd.
return params.get_bool("IsLiveStreaming")
def canlive(started: bool, params: Params, CP: car.CarParams) -> bool:
# Remote live CAN debugging via konn3kt. hephaestusd sets CanLiveStreaming when a viewer
# connects (startCanLive) and clears it when the last one leaves (stopCanLive), so canlived
# runs only during an active debug session — no idle connection or battery cost otherwise.
return params.get_bool("CanLiveStreaming")
def is_tinygrad_model(started, params, CP: car.CarParams) -> bool:
"""Check if the active model runner is tinygrad."""
return bool(get_active_model_runner(params, not started) == custom.IQModelManager.Runner.tinygrad)
@@ -179,6 +187,7 @@ procs = [
# debug procs
NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar),
PythonProcess("webrtcd", "system.webrtc.webrtcd", or_(iscar, livestream)),
PythonProcess("canlived", "iqpilot.konn3kt.canlive.canlived", canlive),
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
]

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import numpy as np
import os
import time
from functools import cache
import threading
@@ -136,10 +137,18 @@ class Mic:
self.last_device = f"{device}: {sd.query_devices(device)['name']}" if isinstance(device, int) else str(device)
cloudlog.info(f"micd selecting input device {self.last_device}")
with self.get_stream(sd) as stream:
cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}")
while True:
self.update()
while True:
try:
with self.get_stream(sd) as stream:
cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}")
while True:
self.update()
except Exception:
# Some A1s wedge the audio DSP (ALSA EINVAL / ADSP_EFAILED until reboot). Dying here
# crash-loops the process and selfdrived raises a takeover alert mid-drive over a
# microphone - stay alive and keep retrying instead; recovers if the DSP comes back.
cloudlog.exception("micd: audio stream unavailable, retrying")
time.sleep(10)
def main():

View File

@@ -12,7 +12,7 @@ from openpilot.common.gps import get_gps_location_service
def set_time(new_time):
diff = datetime.datetime.now() - new_time
diff = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - new_time
if abs(diff) < datetime.timedelta(seconds=10):
cloudlog.debug(f"Time diff too small: {diff}")
return
@@ -47,7 +47,7 @@ def main() -> NoReturn:
pm.send('clocks', msg)
gps = sm[gps_location_service]
gps_time = datetime.datetime.fromtimestamp(gps.unixTimestampMillis / 1000.)
gps_time = datetime.datetime.fromtimestamp(gps.unixTimestampMillis / 1000., datetime.UTC).replace(tzinfo=None)
if not sm.updated[gps_location_service] or (time.monotonic() - sm.logMonoTime[gps_location_service] / 1e9) > 2.0:
continue
if not gps.hasFix:

View File

@@ -22,7 +22,7 @@ from pathlib import Path
from typing import NamedTuple
from importlib.resources import as_file, files
from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware import EGL_DMA_BUF_SUPPORTED, HARDWARE, PC
from openpilot.system.ui.lib.multilang import multilang
from openpilot.common.realtime import Ratekeeper
@@ -326,7 +326,7 @@ class GuiApplication(IQAppHooks):
rl.glfw_swap_interval(0)
except Exception:
pass
if not PC and rl.is_window_ready() and not OFFSCREEN:
if EGL_DMA_BUF_SUPPORTED and rl.is_window_ready() and not OFFSCREEN:
try:
from openpilot.system.ui.lib.egl import set_swap_interval
set_swap_interval(0)
@@ -349,7 +349,7 @@ class GuiApplication(IQAppHooks):
glfw_vsync = False
egl_vsync = False
if not PC:
if EGL_DMA_BUF_SUPPORTED:
try:
from openpilot.system.ui.lib.egl import set_swap_interval
egl_vsync = set_swap_interval(interval)
@@ -367,7 +367,7 @@ class GuiApplication(IQAppHooks):
rl.rl_draw_render_batch_active()
def _apply_display_sync_before_swap(self) -> None:
if PC or OFFSCREEN or not DISPLAY_SYNC_BEFORE_SWAP or self._display_sync_available is False:
if not EGL_DMA_BUF_SUPPORTED or OFFSCREEN or not DISPLAY_SYNC_BEFORE_SWAP or self._display_sync_available is False:
return
try:
self._flush_raylib_batch()
@@ -395,7 +395,7 @@ class GuiApplication(IQAppHooks):
# intermittent screen tearing when scrolling (the content moves, so a mid-scanout swap is
# visible). eglSwapInterval is a trivial call, so re-assert it every frame to keep FIFO vsync
# pinned instead of relying on the slow (VSYNC_REAPPLY_INTERVAL) full re-apply below.
if self._paced_by_vsync and self._vsync_interval > 0 and not PC and rl.is_window_ready():
if self._paced_by_vsync and self._vsync_interval > 0 and EGL_DMA_BUF_SUPPORTED and rl.is_window_ready():
try:
from openpilot.system.ui.lib.egl import set_swap_interval
set_swap_interval(self._vsync_interval)
@@ -850,7 +850,7 @@ class GuiApplication(IQAppHooks):
# full-res frame texture can make the Adreno/GBM driver silently reset the swap interval to 0
# *after* that call but before this swap, which reintroduced tearing. This is the swap that
# actually matters, so pin the interval here too.
if (ENABLE_VSYNC and not OFFSCREEN and not PC and self._paced_by_vsync
if (ENABLE_VSYNC and not OFFSCREEN and EGL_DMA_BUF_SUPPORTED and self._paced_by_vsync
and self._vsync_interval > 0 and rl.is_window_ready()):
try:
from openpilot.system.ui.lib.egl import set_swap_interval

View File

@@ -961,6 +961,10 @@ class WifiManager:
settings['connection'] = {}
changes = False
# NetworkManager updates an active modem connection in place. Quectel
# modems keep the existing PDP bearer in that case, so an APN change
# does not take effect until LTE is disconnected and reactivated.
apn_settings_changed = False
auto_config = apn == ""
initial_eps_apn = apn if not auto_config else ""
@@ -968,11 +972,13 @@ class WifiManager:
cloudlog.warning(f'Changing gsm.auto-config to {auto_config}')
settings['gsm']['auto-config'] = ('b', auto_config)
changes = True
apn_settings_changed = True
if settings['gsm'].get('apn', ('s', ''))[1] != apn:
cloudlog.warning(f'Changing gsm.apn to {apn}')
settings['gsm']['apn'] = ('s', apn)
changes = True
apn_settings_changed = True
if settings['gsm'].get('home-only', ('b', False))[1] == roaming:
cloudlog.warning(f'Changing gsm.home-only to {not roaming}')
@@ -983,11 +989,13 @@ class WifiManager:
cloudlog.warning(f'Changing gsm.initial-eps-bearer-configure to {bool(initial_eps_apn)}')
settings['gsm']['initial-eps-bearer-configure'] = ('b', bool(initial_eps_apn))
changes = True
apn_settings_changed = True
if settings['gsm'].get('initial-eps-bearer-apn', ('s', ''))[1] != initial_eps_apn:
cloudlog.warning(f'Changing gsm.initial-eps-bearer-apn to {initial_eps_apn}')
settings['gsm']['initial-eps-bearer-apn'] = ('s', initial_eps_apn)
changes = True
apn_settings_changed = True
# Unknown means NetworkManager decides
metered_int = int(MeteredType.UNKNOWN if metered else MeteredType.NO)
@@ -1005,7 +1013,10 @@ class WifiManager:
cloudlog.warning(f"Failed to update GSM settings: {reply}")
return
self._activate_modem_connection(lte_connection_path)
if apn_settings_changed:
self._restart_modem_connection(lte_connection_path)
else:
self._activate_modem_connection(lte_connection_path)
except Exception as e:
cloudlog.exception(f"Error updating GSM settings: {e}")
@@ -1034,6 +1045,40 @@ class WifiManager:
except Exception as e:
cloudlog.exception(f"Error activating modem connection: {e}")
def _restart_modem_connection(self, connection_path: str):
"""Reconnect LTE so a changed APN is used for a new PDP bearer."""
try:
for active_conn in self._get_active_connections():
conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
active_conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1]
if active_conn_path == connection_path:
cloudlog.warning("Restarting LTE connection to apply APN settings")
reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (active_conn,)))
if reply.header.message_type == MessageType.error:
cloudlog.warning(f"Failed to deactivate LTE connection: {reply}")
return
for _ in range(20):
if not self._is_connection_active(connection_path):
break
time.sleep(0.25)
else:
cloudlog.warning("LTE connection did not deactivate after APN change")
return
break
self._activate_modem_connection(connection_path)
except Exception as e:
cloudlog.exception(f"Error restarting modem connection: {e}")
def _is_connection_active(self, connection_path: str) -> bool:
for active_conn in self._get_active_connections():
conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
active_conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1]
if active_conn_path == connection_path:
return True
return False
def stop(self):
if not self._exit:
self._exit = True