IQ.Pilot Release Commit @ 92b3bb6

This commit is contained in:
IQ.Lvbs CI [bot]
2026-08-31 21:43:09 -05:00
parent dec9bace9f
commit 091e08e893
9 changed files with 125 additions and 2 deletions

View File

@@ -300,6 +300,10 @@ def main(demo: bool = False) -> None:
cloudlog.error(f"iqegpumodeld giving up after {attempt} setup failures; exiting for a clean restart") cloudlog.error(f"iqegpumodeld giving up after {attempt} setup failures; exiting for a clean restart")
sys.exit(1) sys.exit(1)
if not usbgpu_present(): if not usbgpu_present():
from iqpilot.system.hardware.usb import ensure_host_role
if ensure_host_role():
cloudlog.warning("iqegpumodeld: Type-C controller was out of host mode; restored")
time.sleep(2.0)
_wait_for_egpu(params) _wait_for_egpu(params)
time.sleep(min(SETUP_RETRY_MAX_S, SETUP_RETRY_BASE_S * attempt)) time.sleep(min(SETUP_RETRY_MAX_S, SETUP_RETRY_BASE_S * attempt))

View File

@@ -12,6 +12,11 @@
const bool PANDAD_MAXOUT = getenv("PANDAD_MAXOUT") != nullptr; const bool PANDAD_MAXOUT = getenv("PANDAD_MAXOUT") != nullptr;
std::string panda_firmware_path() {
const char *runtime_path = getenv("IQPILOT_PANDA_FW_PATH");
return runtime_path != nullptr ? runtime_path : PANDA_FW_PATH;
}
Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
// try USB first, then SPI // try USB first, then SPI
try { try {
@@ -138,7 +143,7 @@ std::optional<std::string> Panda::get_serial() {
bool Panda::up_to_date() { bool Panda::up_to_date() {
if (auto fw_sig = get_firmware_version()) { if (auto fw_sig = get_firmware_version()) {
for (auto fn : { "panda.bin.signed", "panda_h7.bin.signed" }) { for (auto fn : { "panda.bin.signed", "panda_h7.bin.signed" }) {
auto content = util::read_file(std::string(PANDA_FW_PATH) + fn); auto content = util::read_file(panda_firmware_path() + fn);
if (content.size() >= fw_sig->size() && if (content.size() >= fw_sig->size() &&
memcmp(content.data() + content.size() - fw_sig->size(), fw_sig->data(), fw_sig->size()) == 0) { memcmp(content.data() + content.size() - fw_sig->size(), fw_sig->data(), fw_sig->size()) == 0) {
return true; return true;

View File

@@ -42,6 +42,7 @@ struct can_frame {
long src; long src;
}; };
std::string panda_firmware_path();
class Panda { class Panda {
private: private:

View File

@@ -77,6 +77,8 @@ def check_panda_support(panda) -> bool:
def main() -> None: def main() -> None:
os.environ["IQPILOT_PANDA_FW_PATH"] = FW_PATH
# signal pandad to close the relay and exit # signal pandad to close the relay and exit
def signal_handler(signum, frame): def signal_handler(signum, frame):
cloudlog.info(f"Caught signal {signum}, exiting") cloudlog.info(f"Caught signal {signum}, exiting")

View File

@@ -133,3 +133,11 @@ TEST_CASE("send/recv CAN FD packets") {
test.test_can_recv(0x40); test.test_can_recv(0x40);
} }
} }
TEST_CASE("firmware path runtime override") {
unsetenv("IQPILOT_PANDA_FW_PATH");
REQUIRE(panda_firmware_path() == PANDA_FW_PATH);
setenv("IQPILOT_PANDA_FW_PATH", "/runtime/panda/board/obj/", 1);
REQUIRE(panda_firmware_path() == "/runtime/panda/board/obj/");
unsetenv("IQPILOT_PANDA_FW_PATH");
}

View File

@@ -265,3 +265,15 @@ def test_rom_mode_dock_is_neither_present_nor_ready(tmp_path):
_mkdev(root, "1-1", vid=vid, pid=pid, product="USB 3.2 PCIe TinyEnclosure") _mkdev(root, "1-1", vid=vid, pid=pid, product="USB 3.2 PCIe TinyEnclosure")
assert not egpu_dock_present(root / "bus") assert not egpu_dock_present(root / "bus")
assert not egpu_dock_ready(root / "bus") assert not egpu_dock_ready(root / "bus")
class TestEnsureHostRole:
def test_already_host_needs_no_write(self, tmp_path):
from iqpilot.system.hardware.usb import ensure_host_role
mode = tmp_path / "mode"
mode.write_text("host\n")
assert ensure_host_role(mode)
def test_missing_controller_is_false(self, tmp_path):
from iqpilot.system.hardware.usb import ensure_host_role
assert not ensure_host_role(tmp_path / "absent" / "mode")

View File

@@ -13,6 +13,7 @@ The USB eGPU dock is identified by VID/PID only. comma's internal codename for
it is deliberately not used here: IQ.Pilot runs these models on several it is deliberately not used here: IQ.Pilot runs these models on several
backends (eGPU dock, eMac), so the naming stays about the role, not the vendor. backends (eGPU dock, eMac), so the naming stays about the role, not the vendor.
""" """
import subprocess
from pathlib import Path from pathlib import Path
# comma's USB eGPU dock, both shipped USB IDs. The ROM ids are the same board # comma's USB eGPU dock, both shipped USB IDs. The ROM ids are the same board
@@ -124,6 +125,26 @@ def get_link_error_count(soc: Path = SOC_PLATFORM_PATH) -> int:
return sum(link_errors(c) for c in usb_controllers(soc)) return sum(link_errors(c) for c in usb_controllers(soc))
def host_role_controller(soc: Path = SOC_PLATFORM_PATH, udc_root: Path = UDC_PATH) -> Path | None:
ctrl = link_controller(udc_root)
return (soc / ctrl / "mode") if ctrl else None
def ensure_host_role(mode_path: Path | None = None) -> bool:
"""A usbpd blip mid-drive can leave the Type-C controller in 'none'/'peripheral', and the
dock can never re-enumerate until something puts the port back into host mode."""
path = mode_path if mode_path is not None else host_role_controller()
if path is None:
return False
current = read(path)
if current == "host":
return True
if current is None:
return False
rc = subprocess.run(["sudo", "-n", "sh", "-c", f"echo host > {path}"], check=False, capture_output=True)
return rc.returncode == 0 and read(path) == "host"
def egpu_dock_present(root: Path = USB_DEVICES_PATH) -> bool: def egpu_dock_present(root: Path = USB_DEVICES_PATH) -> bool:
"""A dock in ROM/bootloader state is deliberately NOT counted as present: it """A dock in ROM/bootloader state is deliberately NOT counted as present: it
enumerates but cannot serve a GPU until vendor firmware is flashed.""" enumerates but cannot serve a GPU until vendor firmware is flashed."""

View File

@@ -154,7 +154,11 @@ iq_pkg() {
# the venv python has the component packages (iqdbc etc.); bare python3 does not # the venv python has the component packages (iqdbc etc.); bare python3 does not
local py=python3 local py=python3
[[ -x "$IQ_ROOT/.venv/bin/python3" ]] && py="$IQ_ROOT/.venv/bin/python3" [[ -x "$IQ_ROOT/.venv/bin/python3" ]] && py="$IQ_ROOT/.venv/bin/python3"
if [[ -f "$IQ_ROOT/iqpilot/tools/scripts/setup_private_packages.py" ]]; then
(cd "$IQ_ROOT" && iq_run "$py" iqpilot/tools/scripts/setup_private_packages.py "$@") (cd "$IQ_ROOT" && iq_run "$py" iqpilot/tools/scripts/setup_private_packages.py "$@")
else
iq_note 'private package sources are not present in this checkout'
fi
if [[ -f "$IQ_ROOT/artifacts/runtime/ensure_private_installed.sh" ]]; then if [[ -f "$IQ_ROOT/artifacts/runtime/ensure_private_installed.sh" ]]; then
(cd "$IQ_ROOT" && iq_run bash artifacts/runtime/ensure_private_installed.sh) (cd "$IQ_ROOT" && iq_run bash artifacts/runtime/ensure_private_installed.sh)
fi fi

View File

@@ -0,0 +1,66 @@
# Copyright © IQ.Lvbs, apart of Project Teal Lvbs, All Rights Reserved, licensed under https://konn3kt.com/tos
import os
from pathlib import Path
import subprocess
IQ_COMMAND = Path(__file__).parents[1] / "iq.sh"
def make_checkout(tmp_path: Path) -> Path:
checkout = tmp_path / "checkout"
(checkout / "iqpilot").mkdir(parents=True)
(checkout / "launch_iqpilot.sh").touch()
return checkout
def run_pkg(checkout: Path, path: Path) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env["PATH"] = f"{path}:{env['PATH']}"
return subprocess.run(
["bash", str(IQ_COMMAND), "--dir", str(checkout), "pkg"],
check=False,
capture_output=True,
env=env,
text=True,
)
def test_public_checkout_skips_private_package_source_setup(tmp_path: Path):
checkout = make_checkout(tmp_path)
result = run_pkg(checkout, tmp_path)
assert result.returncode == 0
assert "private package sources are not present" in result.stdout
assert "setup_private_packages.py" not in result.stderr
def test_internal_checkout_runs_private_package_source_setup(tmp_path: Path):
checkout = make_checkout(tmp_path)
setup_script = checkout / "iqpilot/tools/scripts/setup_private_packages.py"
setup_script.parent.mkdir(parents=True)
setup_script.touch()
python_log = tmp_path / "python.log"
python = tmp_path / "python3"
python.write_text(f"#!/usr/bin/env bash\nprintf '%s\\n' \"$*\" > {python_log!s}\n")
python.chmod(0o755)
result = run_pkg(checkout, tmp_path)
assert result.returncode == 0
assert python_log.read_text().strip() == "iqpilot/tools/scripts/setup_private_packages.py"
def test_public_checkout_runs_bundled_package_installer(tmp_path: Path):
checkout = make_checkout(tmp_path)
installer_log = tmp_path / "installer.log"
installer = checkout / "artifacts/runtime/ensure_private_installed.sh"
installer.parent.mkdir(parents=True)
installer.write_text(f"printf installed > {installer_log!s}\n")
result = run_pkg(checkout, tmp_path)
assert result.returncode == 0
assert installer_log.read_text() == "installed"