IQ.Pilot Release Commit @ 92b3bb6
This commit is contained in:
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ struct can_frame {
|
|||||||
long src;
|
long src;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string panda_firmware_path();
|
||||||
|
|
||||||
class Panda {
|
class Panda {
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|||||||
@@ -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"
|
||||||
(cd "$IQ_ROOT" && iq_run "$py" iqpilot/tools/scripts/setup_private_packages.py "$@")
|
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 "$@")
|
||||||
|
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
|
||||||
|
|||||||
66
iqpilot/tools/tests/test_iq_command.py
Normal file
66
iqpilot/tools/tests/test_iq_command.py
Normal 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"
|
||||||
Reference in New Issue
Block a user