Move rootfs files into userspace/root overlay (#646)

This commit is contained in:
Adeeb Shihadeh
2026-10-18 22:17:23 -07:00
committed by GitHub
parent 43ee8de375
commit d87fe94b8d
169 changed files with 24 additions and 69 deletions
+1
View File
@@ -0,0 +1 @@
*.ko
BIN
View File
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash -e
sudo mount -o rw,remount /
sudo resize2fs $(findmnt -n -o SOURCE /) &>/dev/null || sudo resize2fs $(findmnt -n -o SOURCE /)
sudo mount -o remount,size=1500M /var
sudo cp -r /usr/default/var/lib/dpkg /var/lib/
sudo sed -i '/bionic/s/^/#/' /etc/apt/sources.list
sudo sed -i '/hirsute/s/^/#/' /etc/apt/sources.list
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
sudo mount -o ro,remount / || true
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# Read the raw device model
RAW_MODEL=$(tr -d '\0' < /sys/firmware/devicetree/base/model)
# Extract the device model (e.g., tici, tizi, mici)
MODEL=$(echo "$RAW_MODEL" | sed 's/comma //g' | awk '{print $1}')
[ -z "$MODEL" ] && MODEL="unknown"
HOSTNAME=$(hostname)
SERVICE_NAME="comma SSH - $MODEL - [$HOSTNAME]"
ALIAS_FILE="/data/params/d_tmp/ApiCache_Device"
ALIAS=$(jq -e -r '.alias // empty' "$ALIAS_FILE" 2>/dev/null | grep -E '\S' || echo "$RAW_MODEL")
# Publish avahi service
exec avahi-publish -s "$SERVICE_NAME" _ssh._tcp 22 "vendor=comma" "device=comma" "model=$MODEL" "alias=$ALIAS"
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import time
# limit display brightness with display uptime to prevent burn in
BL_OFF = 4
BL_POWER_PATH = "/sys/class/backlight/panel0-backlight/bl_power"
BRIGHTNESS_PATH = "/sys/class/backlight/panel0-backlight/brightness"
MAX_BRIGHTNESS_PATH= "/sys/devices/platform/soc/soc:qcom,dsi-display@0/max_brightness_percent"
MAX_PERCENT = 90
MIN_PERCENT = 30
HOURLY_PERC_DECREASE = 5
def read(path: str) -> int:
try:
with open(path) as f:
return int(f.read())
except Exception:
return 0
if __name__ == "__main__":
last_perc = None
last_off_ts = time.monotonic()
while True:
try:
# sample brightness and backlight power
bl_power = read(BL_POWER_PATH)
brightness = read(BRIGHTNESS_PATH)
if bl_power == BL_OFF or brightness == 0:
last_off_ts = time.monotonic()
# calculate new max
uptime_hours = (time.monotonic() - last_off_ts) / (60*60)
clipped_perc = MAX_PERCENT - (HOURLY_PERC_DECREASE*uptime_hours)
clipped_perc = int(max(min(clipped_perc, MAX_PERCENT), MIN_PERCENT))
if clipped_perc != last_perc:
with open(MAX_BRIGHTNESS_PATH, 'w') as f:
f.write(f"{int(clipped_perc)}\n")
last_perc = clipped_perc
except Exception:
pass
time.sleep(5)
+111
View File
@@ -0,0 +1,111 @@
#/usr/bin/env bash
source /etc/profile
SETUP="/usr/comma/setup"
RESET="/usr/comma/reset"
CONTINUE="/data/continue.sh"
INSTALLER="/tmp/installer"
RESET_TRIGGER="/data/__system_reset__"
# blip power to ~5W to see if the PSU is stable
if grep -q mici /sys/firmware/devicetree/base/model; then
sudo timeout --kill-after=2 5 /home/comma/power_burn_max 0.3 2
fi
# use max freq to boot up quickly, then limit
echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq
echo 1689600 | sudo tee /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq
echo "waiting for magic"
for i in {1..200}; do
if systemctl is-active --quiet magic && [ -S /tmp/drmfd.sock ]; then
break
fi
sleep 0.1
done
if systemctl is-active --quiet magic && [ -S /tmp/drmfd.sock ]; then
echo "magic ready after ${SECONDS}s"
else
echo "timed out waiting for magic, ${SECONDS}s"
fi
sudo chown comma: /data
sudo chown comma: /data/media
handle_setup_keys () {
# install default SSH key while still in setup
if [[ ! -e /data/params/d/GithubSshKeys && ! -e /data/continue.sh ]]; then
if [ ! -e /data/params/d ]; then
mkdir -p /data/params/d_tmp
ln -s /data/params/d_tmp /data/params/d
fi
echo -n 1 > /data/params/d/AdbEnabled
echo -n 1 > /data/params/d/SshEnabled
cp /usr/comma/setup_keys /data/params/d/GithubSshKeys
elif [[ -e /data/params/d/GithubSshKeys && -e /data/continue.sh ]]; then
if cmp -s /data/params/d/GithubSshKeys /usr/comma/setup_keys; then
rm /data/params/d/AdbEnabled
rm /data/params/d/SshEnabled
rm /data/params/d/GithubSshKeys
fi
fi
}
# factory reset handling
if [ ! -f /run/comma-booted ]; then
sudo touch /run/comma-booted
if [ -f "$RESET_TRIGGER" ]; then
echo "launching system reset, reset trigger present"
rm -f $RESET_TRIGGER
$RESET
elif (( "$(cat /sys/class/input/input2/device/touch_count)" > 4 )); then
echo "launching system reset, got taps"
$RESET --tap-reset
elif ! mountpoint -q /data; then
echo "userdata not mounted. loading system reset"
$RESET --recover
fi
fi
# setup /data/tmp
rm -rf /data/tmp
mkdir -p /data/tmp
# symlink vscode to userdata
mkdir -p /data/tmp/vscode-server
ln -s /data/tmp/vscode-server ~/.vscode-server
ln -s /data/tmp/vscode-server ~/.cursor-server
ln -s /data/tmp/vscode-server ~/.windsurf-server
while true; do
pkill -f "$SETUP"
handle_setup_keys
if [ -f $CONTINUE ]; then
exec "$CONTINUE"
fi
sudo abctl --set_success
# cleanup installers from previous runs
rm -f $INSTALLER
pkill -f $INSTALLER
# run setup and wait for installer
$SETUP &
echo "waiting for installer"
while [ ! -f $INSTALLER ]; do
sleep 0.1
done
# run installer and wait for continue.sh
chmod +x $INSTALLER
$INSTALLER &
echo "running installer"
while [ ! -f $CONTINUE ] && ps -p $! > /dev/null; do
sleep 0.1
done
done
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os
import shutil
from pathlib import Path
ROOT = Path("/cache/debug/")
if __name__ == "__main__":
os.makedirs(ROOT, exist_ok=True)
boots = sorted(list(ROOT.iterdir()), key=lambda p: int(p.stem, 16), reverse=True)
# limit to 100 boots
for d in boots[100:]:
print("cleaning up", d)
shutil.rmtree(d)
# make a directory for this boot
n = 0
if len(boots):
n = int(boots[0].stem, 16) + 1
boot_dir = ROOT / f"{n:08x}"
boot_dir.mkdir(parents=True)
# log some stuff
pstore = boot_dir / "pstore"
pstore.mkdir()
os.system(f"cp /sys/fs/pstore/* {pstore} 2>/dev/null")
os.system(f"uname -a > {boot_dir / 'uname'}")
shutil.copyfile("/VERSION", boot_dir / "VERSION")
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# Ensure the symlinks in the read only rootfs are
# backed by real files and directories on userdata.
# tmpfiles
systemd-tmpfiles --create /usr/comma/tmpfiles.conf
# /var/log/ tmpfs
mkdir -p /var/log/
chown root:syslog /var/log
mount -t tmpfs -o rw,nosuid,nodev,size=128M,mode=755 tmpfs /var/log
# setup /home
mkdir -p /rwtmp/home_work
mkdir -p /rwtmp/home_upper
chmod 755 /rwtmp/*
mount -t overlay overlay -o lowerdir=/usr/default/home,upperdir=/rwtmp/home_upper,workdir=/rwtmp/home_work /home
# /etc
mkdir -p /data/etc
touch /data/etc/timezone
touch /data/etc/localtime
mkdir -p /data/etc/netplan
mkdir -p /data/etc/NetworkManager/system-connections
# /cache
chown -R comma:comma /cache/
# /data/ssh
mkdir -p /data/ssh
chown comma: /data/ssh
# /data/tmp - clear out
rm -rf /data/tmp/
mkdir -p /data/tmp/
# /data/persist
if [[ ! -d /data/persist ]]; then
sudo cp -r /system/persist /data
fi
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
function gpio {
echo "out" > /sys/class/gpio/gpio$1/direction
echo $2 > /sys/class/gpio/gpio$1/value
}
pins=(
# 27 # SW_3V3_EN
# 25 # SW_5V_EN
30 # HUB_RST_N
49 # SOM_ST_IO
134 # ST_BOOT0
41 # PANDA_1V8_EN_N
50 # LTE_RST_N
116 # LTE_PWRKEY
124 # ST_RST_N
34 # GPS_PWR_EN
33 # GPS_SAFEBOOT_N
32 # GPS_RST_N
52 # LTE_BOOT
1264 # POWER ALERT
)
for p in ${pins[@]}; do
echo $p
# this is SSD_3v3 EN on tici
if [ "$p" -eq 41 ] && grep -q "comma tici" /sys/firmware/devicetree/base/model; then
echo "Skipping $p"
continue
fi
echo $p > /sys/class/gpio/export
until [ -d /sys/class/gpio/gpio$p ]
do
sleep .05
done
done
HUB_RST_N=30
gpio $HUB_RST_N 1
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
# don't restart whole SoC on subsystem crash
for i in {0..7}; do
echo "related" | sudo tee /sys/bus/msm_subsys/devices/subsys${i}/restart_level
done
# raise scaling_max so policy=performance can reach the BOOST top step
echo 2649600 | sudo tee /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq
echo 2649600 | sudo tee /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq
# boot wifi
echo 1 | sudo tee /sys/kernel/boot_wlan/boot_wlan
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85f6d9e54286a3842920d6967b187478b4e43d6171c331d72d3fb3102106e101
size 1401224
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
function gpio {
echo "out" > /sys/class/gpio/gpio$1/direction
echo $2 > /sys/class/gpio/gpio$1/value
}
HUB_RST_N=30
LTE_RST_N=50
LTE_BOOT=52
LTE_PWRKEY=116
function is_modem_up {
if lsusb -d "0x2c7c:" >/dev/null 2>&1; then
return 0
else
return 1
fi
}
function reset {
echo " Resetting..."
gpio $LTE_RST_N 1
sleep 1
gpio $LTE_RST_N 0
}
function power_button {
echo " Pulsing power button..."
gpio $LTE_PWRKEY 1
sleep 1
gpio $LTE_PWRKEY 0
}
function is_online {
echo " Waiting until the modem comes online..."
for i in {0..30}; do
if is_modem_up; then
echo " Modem is online!"
return 0
fi
echo " Waiting..."
sleep 1
done
return 1
}
function is_offline {
echo " Waiting until the modem goes offline..."
for i in {0..30}; do
if ! is_modem_up; then
echo " Modem is offline!"
return 0
fi
echo " Waiting..."
sleep 1
done
return 1
}
# Boot into the regular mode
gpio $LTE_BOOT 0
case "$1" in
start)
echo "Starting LTE..."
reset
power_button
until is_online; do
reset
power_button
done
;;
stop)
echo "Stopping LTE..."
if is_online; then
power_button
fi
;;
stop_blocking)
echo "Stopping LTE..."
if is_online; then
power_button
fi
until is_offline; do
power_button
done
;;
*)
echo "Specify either start or stop as first argument!"
exit 1
;;
esac
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
import os, socket, threading, time
from array import array
import pyray as rl
SOCK_PATH = "/tmp/drmfd.sock"
DRM_DEVICE = "/dev/dri/card0"
BACKGROUND = "/usr/comma/bg.png"
def power_screen():
try:
with open("/sys/class/backlight/panel0-backlight/bl_power", "w") as f:
f.write("0")
with open("/sys/class/backlight/panel0-backlight/max_brightness") as f:
max_brightness = int((int(f.read().strip()) / 100) * 65)
with open("/sys/class/backlight/panel0-backlight/brightness", "w") as f:
f.write(str(max_brightness))
except Exception:
pass
def show_background(tex, pos):
rl.begin_drawing()
rl.draw_texture(tex, int(pos.x), int(pos.y), rl.WHITE)
rl.end_drawing()
power_screen()
def handle_client(client, drm_master):
try:
drm_master_dup = os.dup(drm_master)
client.sendmsg([b"x"], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array("i", [drm_master_dup]).tobytes())])
client.recv(1)
except Exception:
pass
finally:
try:
os.close(drm_master_dup)
client.close()
except Exception:
pass
def main():
while True:
try:
drm_master = os.open(DRM_DEVICE, os.O_RDWR | os.O_CLOEXEC)
break
except Exception as e:
print(e)
time.sleep(0.1)
os.environ['DRM_FD'] = str(drm_master)
rl.init_window(0, 0, "not weston")
img = rl.load_image(BACKGROUND)
rl.image_resize(img, rl.get_screen_width(), rl.get_screen_width()//2)
tex = rl.load_texture_from_image(img)
rl.set_texture_filter(tex, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
pos = rl.Vector2((rl.get_screen_width() - tex.width)/2.0, (rl.get_screen_height() - tex.height)/2.0)
rl.unload_image(img)
show_background(tex, pos)
try:
os.unlink(SOCK_PATH)
except FileNotFoundError:
pass
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(SOCK_PATH)
server.settimeout(0.1)
server.listen(1)
clients = set()
need_background = False
while True:
dead = [t for t in list(clients) if not t.is_alive()]
for t in dead:
t.join()
clients.discard(t)
if not clients and need_background:
need_background = False
show_background(tex, pos)
try:
client, _ = server.accept()
except Exception:
continue
need_background = True
t = threading.Thread(target=handle_client, args=(client, drm_master), daemon=True)
t.start()
clients.add(t)
if __name__ == "__main__":
main()
+641
View File
@@ -0,0 +1,641 @@
#!/usr/bin/env python3
# Source: commaai/openpilot@6b592ae2ccf68d1b3b89a8c544b12ecb074f3a8f
# openpilot/common/hardware/comma/modem.py; Webbing ICCID prefix inlined.
# Copyright (c) 2018, Comma.ai, Inc.
#
# 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.
import fcntl
import json
import logging
import os
import select
import signal
import string
import struct
import subprocess
import tempfile
import termios
import time
from contextlib import contextmanager
from ipaddress import IPv4Address, AddressValueError
from enum import Enum
WEBBING_ICCID_PREFIX = "8985235"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s.%(msecs)03d %(levelname)-7s modem: %(message)s",
datefmt="%H:%M:%S",
)
AT_PORT = "/dev/modem_at0"
PPP_PORT = "/dev/modem_at1"
STATE_PATH = "/dev/shm/modem"
AT_LOCK = "/dev/shm/modem.lock" # shared with LPA
AT_INIT = [
"ATE0", # disable command echo
"ATV1", # verbose result codes (CONNECT/BUSY/NO CARRIER, not numeric)
"AT+CMEE=1", # numeric +CME ERROR codes on failures (per 3GPP 27.007)
"ATX4", # extended result codes: busy + dial tone detection, line speed in CONNECT
"AT&C1", # DCD pin follows carrier state (V.250 default)
"AT+CREG=2", # registration URCs include location info
"AT+CGREG=2", # GPRS registration URCs include location info
]
CREG = {0: "not_registered", 1: "home", 2: "searching", 3: "denied", 4: "unknown", 5: "roaming"}
# 3GPP TS 27.007 +COPS <AcT> -> network type
NETWORK_TYPE = {0: "gsm", 1: "gsm", 3: "gsm", 8: "gsm",
2: "utran", 4: "utran", 5: "utran", 6: "utran",
7: "lte", 9: "lte", 10: "lte",
11: "nr", 12: "nr", 13: "nr"}
DIAL_CID = 1
PPPD_CMD = [
"sudo", "pppd", PPP_PORT, "460800", "noauth", "nodetach", "noipdefault", "usepeerdns",
"nodefaultroute", "connect",
"/usr/sbin/chat -v ABORT 'NO CARRIER' ABORT 'NO DIALTONE' ABORT 'BUSY' " +
f"ABORT 'NO ANSWER' ABORT 'ERROR' TIMEOUT 5 '' AT OK ATD*99***{DIAL_CID}# CONNECT ''",
"lcp-echo-interval", "30", "lcp-echo-failure", "4", "mtu", "1500", "mru", "1500",
"novj", "novjccomp", "ipcp-accept-local", "ipcp-accept-remote", "nomagic",
"user", '""', "password", '""',
]
INITIAL_STATE: dict[str, object] = {
"seconds_since_boot": 0,
"state": "INITIALIZING",
"connected": False, "ip_address": "",
"iccid": "", "mcc_mnc": "", "imei": "", "modem_version": "",
"signal_strength": 0, "signal_quality": 0,
"network_type": "unknown", "operator": "", "band": "", "channel": 0,
"registration": "unknown", "temperatures": [], "extra": "",
"tx_bytes": 0, "rx_bytes": 0,
}
@contextmanager
def _serial_port(port: str, baudrate: int):
fd = os.open(port, os.O_RDWR | os.O_NOCTTY)
try:
attrs = termios.tcgetattr(fd)
attrs[0] = 0
attrs[1] = 0
attrs[2] = termios.CLOCAL | termios.CREAD | termios.CS8
attrs[3] = 0
attrs[4] = attrs[5] = getattr(termios, f"B{baudrate}")
attrs[6][termios.VMIN] = 0
attrs[6][termios.VTIME] = 0
termios.tcsetattr(fd, termios.TCSANOW, attrs)
yield fd
finally:
os.close(fd)
def _read_line(fd: int, timeout: float) -> bytes:
data = bytearray()
deadline = time.monotonic() + timeout
while True:
readable, _, _ = select.select([fd], [], [], max(0.0, deadline - time.monotonic()))
if not readable:
return bytes(data)
byte = os.read(fd, 1)
if not byte:
return bytes(data)
data.extend(byte)
if byte == b"\n":
return bytes(data)
class State(Enum):
INITIALIZING = "INITIALIZING"
SEARCHING = "SEARCHING"
CONNECTING = "CONNECTING"
CONNECTED = "CONNECTED"
DISCONNECTING = "DISCONNECTING"
STATE_WAIT = 1.0 # seconds to wait after each state handler returns
class PPPSession:
"""Owns pppd lifecycle, fail tracking, and PPP routing."""
MAX_FAILS = 3
def __init__(self):
self._proc: subprocess.Popen | None = None
self._fails = 0
self._peer = ""
def start(self):
self._proc = subprocess.Popen(PPPD_CMD, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
self._peer = ""
logging.info(f"PPP dialing CID {DIAL_CID}")
def kill(self):
subprocess.run(["sudo", "killall", "-9", "pppd"], capture_output=True)
self._peer = ""
@staticmethod
def reset_data_port():
"""Drop DTR on PPP_PORT so the modem terminates any stuck PPP session."""
try:
with _serial_port(PPP_PORT, 460800) as fd:
dtr = struct.pack("I", termios.TIOCM_DTR)
fcntl.ioctl(fd, termios.TIOCMBIC, dtr)
time.sleep(0.2)
fcntl.ioctl(fd, termios.TIOCMBIS, dtr)
except Exception as e:
logging.warning(f"data port reset failed: {e}")
def has_exited(self) -> bool:
return self._proc is not None and self._proc.poll() is not None
def reset_fail_counter(self):
self._fails = 0
def record_fail(self) -> bool:
"""Bump fail counter; return True if at the give-up limit."""
self._fails += 1
return self._fails >= self.MAX_FAILS
@property
def fails(self) -> int:
return self._fails
def maybe_install_routes(self, ip: str, peer: str) -> bool:
"""Install routes if peer changed; kill the session on failure so the state machine reconnects."""
if not peer or peer == self._peer:
return False
try:
IPv4Address(ip)
IPv4Address(peer)
except AddressValueError:
logging.warning(f"refusing route install with non-IPv4 ip={ip!r} peer={peer!r}")
self.kill()
return False
self.cleanup_routes()
cmds = [
["sudo", "ip", "route", "add", "default", "via", peer, "dev", "ppp0", "metric", "1000"],
["sudo", "ip", "route", "add", "default", "via", peer, "dev", "ppp0", "table", "1000"],
["sudo", "ip", "rule", "add", "from", ip, "table", "1000"],
]
for cmd in cmds:
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
logging.warning(f"route install failed ({' '.join(cmd[1:])}): {r.stderr.strip()}")
self.cleanup_routes()
self.kill()
return False
logging.info(f"route set up for {ip} via {peer}")
self._peer = peer
return True
def maybe_install_dns(self, dns_servers: list[str]) -> bool:
"""Register DNS servers with systemd-resolved; kill the session on failure to force a retry."""
if not dns_servers:
return False
for cmd in (["sudo", "resolvectl", "dns", "ppp0", *dns_servers],
["sudo", "resolvectl", "default-route", "ppp0", "yes"]):
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0:
logging.warning(f"resolvectl failed ({' '.join(cmd[1:])}): {r.stderr.strip()}")
self.kill()
return False
logging.info(f"resolvectl: ppp0 DNS = {dns_servers}")
return True
@staticmethod
def cleanup_routes():
subprocess.run(["sudo", "ip", "route", "del", "default", "dev", "ppp0"], capture_output=True)
subprocess.run(["sudo", "ip", "route", "flush", "table", "1000"], capture_output=True)
# rules don't have a flush; delete until none remain
while subprocess.run(["sudo", "ip", "rule", "del", "table", "1000"], capture_output=True).returncode == 0:
pass
subprocess.run(["sudo", "resolvectl", "revert", "ppp0"], capture_output=True)
class Modem:
def __init__(self):
self._ppp = PPPSession()
self._sim_change = False
self._apn = "" # blank = network-provided via PCO
self._roaming_allowed = True
self.running = True
self.S = INITIAL_STATE.copy()
@staticmethod
def _read_param(key):
try:
with open(f"/data/params/d/{key}") as f:
return f.read().strip()
except FileNotFoundError:
return ""
@staticmethod
def _parse_reg(v: str) -> str:
try:
return CREG.get(int(v.split(",")[1].strip('"')), "unknown")
except (ValueError, IndexError):
return "unknown"
@staticmethod
def _has_modem_manager() -> bool:
return os.path.isfile("/lib/systemd/system/ModemManager.service")
def _is_roaming_allowed(self) -> bool:
if self.S["iccid"].startswith(WEBBING_ICCID_PREFIX):
return True
return self._read_param("GsmRoaming") == "1"
def _publish_state(self, **kwargs):
self.S.update(kwargs)
self.S["seconds_since_boot"] = time.monotonic()
with tempfile.NamedTemporaryFile(mode="w", dir="/dev/shm", delete=False) as f:
json.dump(self.S, f, indent=2)
os.chmod(f.name, 0o644)
os.replace(f.name, STATE_PATH)
def _at(self, cmd):
"""Send AT command, return response lines. [] on error or if LPA holds port."""
fd = os.open(AT_LOCK, os.O_CREAT | os.O_RDWR, 0o666)
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
os.close(fd)
return []
try:
with _serial_port(AT_PORT, 9600) as serial_fd:
termios.tcflush(serial_fd, termios.TCIFLUSH)
command = (cmd + "\r").encode()
while command:
command = command[os.write(serial_fd, command):]
lines = []
while True:
raw = _read_line(serial_fd, 5)
if not raw:
raise TimeoutError("AT timeout")
line = raw.decode(errors="ignore").strip()
if not line:
continue
if line == "OK":
break
if line == "ERROR" or line.startswith("+CME ERROR"):
raise RuntimeError(line)
lines.append(line)
return lines
except (RuntimeError, TimeoutError, OSError) as e:
logging.info(f"AT {cmd} failed: {e}")
return []
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
os.close(fd)
def _atv(self, cmd, pfx):
for line in self._at(cmd):
if pfx in line and ":" in line:
return line.split(":", 1)[1].strip()
return None
def _init_at_channel(self) -> bool:
"""Run AT_INIT and confirm ATE0 took effect. Returns False if echo is still on."""
for c in AT_INIT:
self._at(c)
r = self._at("AT+CGMI")
return bool(r) and not r[0].startswith("AT")
def _configure_modem(self, modem_version: str):
if not modem_version.startswith("EG25"):
return
cmds = [
# clear initial EPS bearer APN (some carriers reject the default)
'AT+CGDCONT=0,"IP",""',
# SIM hot swap
'AT+QSIMDET=1,0',
'AT+QSIMSTAT=1',
# configure modem as data-centric
'AT+QNVW=5280,0,"0102000000000000"',
'AT+QNVFW="/nv/item_files/ims/IMS_enable",00',
'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01',
]
for c in cmds:
self._at(c)
def _do_initializing(self):
if not os.path.exists(AT_PORT):
return State.INITIALIZING
logging.info("port found, initializing")
self._ppp.kill()
self._ppp.cleanup_routes()
if not self._init_at_channel():
logging.warning("AT echo still on, retrying")
return State.INITIALIZING
identity = self._read_identity()
if not identity["iccid"] or not identity["imei"]:
logging.warning(f"identity read incomplete: {identity}, retrying")
return State.INITIALIZING
self._configure_modem(identity["modem_version"])
self.S.update(identity)
self._apn = self._read_param("GsmApn")
self._roaming_allowed = self._is_roaming_allowed()
# blank APN lets the carrier supply one via PCO
self._at(f'AT+CGDCONT={DIAL_CID},"IP","{self._apn}"')
logging.info(f"APN '{self._apn or '(network-provided)'}' written to CID {DIAL_CID}, roaming={'on' if self._roaming_allowed else 'off'}")
self._sim_change = False # clear since we just re-read identity with the new SIM
self._publish_state(**identity)
return State.SEARCHING
def _read_identity(self):
def first_line(cmd):
r = self._at(cmd)
return r[0].strip() if r else ""
imei = first_line("AT+CGSN")
if not (imei.isdigit() and 14 <= len(imei) <= 17): # 3GPP TS 23.003
imei = ""
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
if not all(c in string.hexdigits for c in iccid):
iccid = ""
imsi = first_line("AT+CIMI")
mcc_mnc = imsi[:6] if imsi.isdigit() and len(imsi) >= 6 else ""
modem_version = first_line("AT+GMR")
logging.info(f"imei={imei} iccid={iccid} mcc_mnc={mcc_mnc} ver={modem_version}")
return {"imei": imei, "iccid": iccid, "mcc_mnc": mcc_mnc, "modem_version": modem_version}
def _do_searching(self):
new_roaming = self._is_roaming_allowed()
if new_roaming != self._roaming_allowed:
logging.info(f"roaming changed: {self._roaming_allowed} -> {new_roaming}")
self._roaming_allowed = new_roaming
v = self._atv("AT+CREG?", "+CREG:")
if not v:
return self._searching_idle()
reg = self._parse_reg(v)
greg = self._parse_reg(self._atv("AT+CGREG?", "+CGREG:") or "")
logging.debug(f"creg={reg} cgreg={greg} roaming_allowed={self._roaming_allowed}")
if reg == "roaming" and not self._roaming_allowed:
self._publish_state(registration=reg)
return State.SEARCHING
if reg in ("home", "roaming") and greg in ("home", "roaming"):
self._publish_state(registration=reg)
return State.CONNECTING
if reg != self.S.get("registration"):
self._publish_state(registration=reg)
return self._searching_idle()
def _searching_idle(self):
if self._sim_change or not os.path.exists(AT_PORT):
logging.info(f"-> reconnecting (sim_change={self._sim_change} port={os.path.exists(AT_PORT)})")
return State.DISCONNECTING
return State.SEARCHING
def _do_connecting(self):
logging.info("starting pppd")
self._ppp.reset_fail_counter()
self._sim_change = False
self._ppp.start()
return State.CONNECTED
def _handle_pppd_exit(self):
if self._sim_change or not os.path.exists(AT_PORT):
return State.DISCONNECTING
give_up = self._ppp.record_fail()
if give_up:
logging.warning(f"PPP fail {self._ppp.fails}/{self._ppp.MAX_FAILS}, reconnecting")
return State.DISCONNECTING
logging.warning(f"PPP fail {self._ppp.fails}/{self._ppp.MAX_FAILS}, retrying")
self._ppp.reset_data_port()
if not os.path.exists(AT_PORT):
return State.DISCONNECTING
self._ppp.start()
return State.CONNECTED
def _params_changed(self) -> bool:
new_apn = self._read_param("GsmApn")
if new_apn != self._apn:
logging.info(f"GsmApn changed: '{self._apn}' -> '{new_apn}'")
return True
new_roaming = self._is_roaming_allowed()
if new_roaming != self._roaming_allowed:
logging.info(f"roaming changed: {self._roaming_allowed} -> {new_roaming}")
return True
return False
def _check_iccid(self, state):
if state in (State.INITIALIZING, State.DISCONNECTING) or not self.S["iccid"]:
return
iccid = (self._atv("AT+QCCID", "+QCCID:") or "").rstrip("F")
if iccid and iccid != self.S["iccid"]:
logging.warning(f"iccid changed: {self.S['iccid']} -> {iccid}")
self._sim_change = True
def _do_connected(self):
if self._ppp.has_exited():
return self._handle_pppd_exit()
if self._sim_change or not os.path.exists(AT_PORT) or self._params_changed():
return State.DISCONNECTING
self._poll()
return State.CONNECTED
def _do_disconnecting(self):
logging.warning("reconnecting")
self._publish_state(**INITIAL_STATE)
self._ppp.kill()
self._ppp.cleanup_routes()
self._ppp.reset_data_port()
self._sim_change = False
return State.INITIALIZING
def _poll_signal(self) -> dict:
v = self._atv("AT+CSQ", "+CSQ:")
if not v:
return {}
try:
rssi = int(v.split(",")[0])
if rssi == 99:
return {}
return {"signal_strength": rssi, "signal_quality": min(100, int(rssi / 31 * 100))}
except (ValueError, IndexError):
return {}
def _poll_operator(self) -> dict:
v = self._atv("AT+COPS?", "+COPS:")
if not v:
return {}
p = v.split(",")
out: dict = {}
try:
if len(p) >= 3:
out["operator"] = p[2].strip('"')
if len(p) >= 4:
out["network_type"] = NETWORK_TYPE.get(int(p[3]), "unknown")
except (ValueError, IndexError):
pass
return out
def _poll_band(self) -> dict:
v = self._atv("AT+QNWINFO", "+QNWINFO:")
if not v:
return {}
info = v.replace('"', '').split(",")
try:
if len(info) >= 4:
return {"band": info[2], "channel": int(info[3])}
except ValueError:
pass
return {}
def _poll_extra(self) -> dict:
v = self._atv('AT+QENG="servingcell"', "+QENG:")
return {"extra": v.replace('"', '')} if v else {}
def _poll_temps(self) -> dict:
v = self._atv("AT+QTEMP", "+QTEMP:")
if not v:
return {}
try:
return {"temperatures": [t for t in (int(x) for x in v.split(",") if x.strip()) if t != 255]}
except (ValueError, IndexError):
return {}
def _poll_iface(self) -> dict:
try:
r = subprocess.run(["ip", "-4", "addr", "show", "ppp0"], capture_output=True, text=True, timeout=2)
ip, peer = "", ""
for line in r.stdout.splitlines():
# `inet 10.x.x.x peer 10.64.64.64/32 ...`
parts = line.strip().split()
if "inet" in parts:
i = parts.index("inet")
ip = parts[i + 1].split("/")[0]
if "peer" in parts:
peer = parts[parts.index("peer") + 1].split("/")[0]
break
if ip:
if self._ppp.maybe_install_routes(ip, peer):
self._ppp.maybe_install_dns(self._read_cellular_dns())
return {"ip_address": ip, "connected": True}
if self.S["connected"]:
return {"connected": False, "ip_address": ""}
except Exception:
pass
return {}
def _read_cellular_dns(self) -> list[str]:
v = self._atv(f"AT+CGCONTRDP={DIAL_CID}", "+CGCONTRDP:")
if not v:
return []
# +CGCONTRDP: <cid>,<bearer_id>,<apn>,<local_addr>,<gw_addr>,<dns_prim>,<dns_sec>,...
fields = [f.strip().strip('"') for f in v.split(",")]
dns_servers = []
for d in fields[5:7]:
try:
dns_servers.append(str(IPv4Address(d)))
except (AddressValueError, ValueError):
pass
if not dns_servers:
dns_servers = [
"8.8.8.8", # Google
"1.1.1.1", # Cloudflare
]
logging.warning(f"no cellular DNS servers reported by modem: {v!r}; using fallback {dns_servers}")
return dns_servers
def _poll_byte_counters(self) -> dict:
try:
with open("/sys/class/net/ppp0/statistics/tx_bytes") as f:
tx = int(f.read().strip())
with open("/sys/class/net/ppp0/statistics/rx_bytes") as f:
rx = int(f.read().strip())
except Exception:
return {}
return {"tx_bytes": tx, "rx_bytes": rx}
def _poll(self):
s: dict = {}
for fn in (self._poll_signal, self._poll_operator, self._poll_band,
self._poll_extra, self._poll_temps, self._poll_iface,
self._poll_byte_counters):
s.update(fn())
if s:
self._publish_state(**s)
def run(self):
logging.info("starting")
self._publish_state(state=State.INITIALIZING.value)
if self._has_modem_manager():
subprocess.run(["sudo", "systemctl", "mask", "--runtime", "ModemManager"], capture_output=True)
subprocess.run(["sudo", "systemctl", "stop", "ModemManager"], capture_output=True)
self._ppp.kill()
state = State.INITIALIZING
handlers = {
State.INITIALIZING: self._do_initializing,
State.SEARCHING: self._do_searching,
State.CONNECTING: self._do_connecting,
State.CONNECTED: self._do_connected,
State.DISCONNECTING: self._do_disconnecting,
}
while self.running:
try:
self._check_iccid(state)
prev = state
state = handlers[state]()
if state != prev:
self._publish_state(state=state.value)
logging.info(f"{prev.value} -> {state.value}")
except Exception:
logging.exception(f"error in {state.value}")
state = State.DISCONNECTING
time.sleep(STATE_WAIT)
def stop(self):
self.running = False
self._ppp.kill()
self._ppp.cleanup_routes()
try:
os.remove(STATE_PATH)
except FileNotFoundError:
pass
if self._has_modem_manager():
subprocess.run(["sudo", "systemctl", "unmask", "--runtime", "ModemManager"], capture_output=True)
subprocess.run(["sudo", "systemctl", "start", "ModemManager"], capture_output=True)
def main():
m = Modem()
def _sig(*_):
m.running = False
signal.signal(signal.SIGINT, _sig)
signal.signal(signal.SIGTERM, _sig)
m.run()
m.stop()
if __name__ == "__main__":
main()
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python
import os
import time
import smbus2
import select
import datetime
import subprocess
ALERT_VOLTAGE_THRESHOLD_mV = 4000
POWER_ALERT_GPIO_PIN = 1264
INA231_BUS = 0
INA231_ADDRESS = 0x40
INA231_MASK_REG = 0x06
INA231_LIMIT_REG = 0x07
INA231_MASK_CONFIG = (1 << 12) # Bus undervoltage, not latching
INA231_BUS_VOLTAGE_LSB_mV = 1.25
VOLTAGE_FILE = "/sys/class/hwmon/hwmon1/in1_input"
PARAM_FILE = "/data/params/d/LastPowerDropDetected"
alert_pin_base = f"/sys/class/gpio/gpio{POWER_ALERT_GPIO_PIN}/"
def set_screen_power(on):
with open("/sys/class/backlight/panel0-backlight/bl_power", "w") as f:
f.write("0\n" if on else "4\n")
def get_screen_power():
with open("/sys/class/backlight/panel0-backlight/bl_power", "r") as f:
return f.read(1) == "0"
def swap_word_bytes(val):
return ((val & 0xFF) << 8) | ((val & 0xFF00) >> 8)
def write_once(path, value):
with open(path, 'w') as f:
f.write(value)
def init_alert_pin():
write_once(os.path.join(alert_pin_base, 'direction'), 'in')
write_once(os.path.join(alert_pin_base, 'edge'), 'falling')
def init_voltage_alert():
with smbus2.SMBus(INA231_BUS) as bus:
bus.write_word_data(INA231_ADDRESS, INA231_MASK_REG, swap_word_bytes(INA231_MASK_CONFIG), force=True)
bus.write_word_data(INA231_ADDRESS, INA231_LIMIT_REG, swap_word_bytes(int(ALERT_VOLTAGE_THRESHOLD_mV / INA231_BUS_VOLTAGE_LSB_mV)), force=True)
def read_voltage_mV():
with open(VOLTAGE_FILE, "r") as f:
return int(f.read().strip())
def read_current_mA():
with open("/sys/class/hwmon/hwmon1/curr1_input", "r") as f:
return int(f.read().strip())
def update_param(stage, v_initial, i_initial, v_final, i_final):
try:
os.umask(0)
with open(os.open(PARAM_FILE, os.O_CREAT | os.O_WRONLY, 0o777), 'a') as f:
f.write(f"{stage} {datetime.datetime.now()}, ({v_initial=} mV, {i_initial=} mA) -> ({v_final=} mV, {i_final=} mA)\n")
f.flush()
os.fdatasync(f.fileno())
os.fsync(f.fileno())
except Exception:
print("Failed to update LastControlledShutdown param")
def printk(msg):
with open('/dev/kmsg', 'w') as kmsg:
kmsg.write(f"<3>[power drop monitor] {msg}\n")
print(msg)
def perform_controlled_shutdown():
printk("Power alert received!")
prev_screen_power = get_screen_power()
set_screen_power(False)
v_initial = read_voltage_mV()
i_initial = read_current_mA()
update_param("PREP", v_initial, i_initial, None, None)
# Wait 100ms before checking voltage level again
t = time.monotonic()
while time.monotonic() - t < 0.1:
time.sleep(0.01)
v_now = read_voltage_mV()
i_now = read_current_mA()
if v_now > ALERT_VOLTAGE_THRESHOLD_mV:
printk("Voltage restored. Not shutting down!")
update_param("ABORT", v_initial, i_initial, v_now, i_now)
set_screen_power(prev_screen_power)
return
update_param("SHUTDOWN", v_initial, i_initial, v_now, i_now)
# Kill services that draw a lot of power
printk("Killing services")
subprocess.call(["/usr/bin/systemctl", "kill", "--signal=9", "magic", "comma"])
set_screen_power(False)
printk("Halt")
subprocess.call(["/usr/sbin/halt", "-f"])
if __name__ == '__main__':
init_alert_pin()
init_voltage_alert()
# Setup interrupt
f = open(os.path.join(alert_pin_base, 'value'), 'r')
po = select.poll()
po.register(f, select.POLLPRI)
# Interrupt loop
f.read()
while True:
try:
events = po.poll(60000)
if events:
f.seek(0)
state_last = f.read().strip()
if int(state_last) == 0:
perform_controlled_shutdown()
except Exception:
pass
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
import os
import time
import fcntl
import struct
import threading
import subprocess
from datetime import datetime, timedelta
FN = "/var/tmp/power_watchdog"
PARAM_FILE = "/data/params/d/LastAgnosPowerMonitorShutdown"
THRESHOLD = timedelta(hours=1.0)
timestamps = {}
def read(path: str, num: bool = False):
try:
with open(path) as f:
if num:
return float(f.read())
return f.read()
except Exception:
return 0 if num else ""
def check_touches():
global timestamps
event_format = "llHHi"
event_size = struct.calcsize(event_format)
with open("/dev/input/by-path/platform-894000.i2c-event", "rb") as event_file:
fcntl.fcntl(event_file, fcntl.F_SETFL, os.O_NONBLOCK)
while True:
while (event := event_file.read(event_size)):
(sec, usec, etype, code, value) = struct.unpack(event_format, event)
if etype != 0 or code != 0 or value != 0:
timestamps['touch'] = time.monotonic()
time.sleep(60)
def ssh_active():
p = subprocess.run("ss | grep ssh", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return p.returncode == 0
def write_param():
try:
os.umask(0)
with open(os.open(PARAM_FILE, os.O_CREAT | os.O_WRONLY, 0o777), 'a') as f:
f.write(f"SHUTDOWN datetime={datetime.now()}, time.monotonic={time.monotonic()}, {timestamps=}\n")
f.flush()
os.fdatasync(f.fileno())
os.fsync(f.fileno())
except Exception as e:
print(f"Failed to write param: {e}")
if __name__ == "__main__":
# we limit worst-case power usage when openpilot isn't managing it,
# e.g. while building or during setup.
threading.Thread(target=check_touches, daemon=True).start()
timestamps['startup'] = time.monotonic()
while True:
timestamps['watchdog'] = read(FN, True)
if ssh_active():
timestamps['ssh'] = time.monotonic()
if read("/data/params/d/IsEngaged").startswith("1"):
timestamps['engaged'] = time.monotonic()
# store a max such that we can't go backwards
timestamps['max'] = max(timestamps.values())
# time to shutoff?
dt = timedelta(seconds=time.monotonic() - max(timestamps.values()))
if dt > THRESHOLD:
write_param()
os.system("sudo poweroff")
print((THRESHOLD - dt), "until shutdown", f"/ {timestamps=}")
time.sleep(60)
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:97ed6413515d0674442c42ae6e20baccf66dd6bb4ec382ee4cf0cc5ebe84e739
size 23027473
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import os
import struct
GAMMA_CURVES_FILE = "/persist/comma/dwo_gamma_curves"
COMMAND_FILE = "/sys/kernel/debug/dsi_dwo_video_display/mipi_command"
GAMMA_ADDR = [
[(0x00, 0x01), (0x02, 0x03), (0x04, 0x05), (0x06, 0x07), (0x08, 0x09), (0x0A, 0x0B), (0x0C, 0x0D), (0x0E, 0x0F), (0x10, 0x11), (0x12, 0x13), (0x14, 0x15), (0x16, 0x17), (0x18, 0x19), (0x1A, 0x1B), (0x1C, 0x1D), (0x1E, 0x1F), (0x20, 0x21), (0x22, 0x23), (0x24, 0x25), (0x26, 0x27), (0x28, 0x29), (0x2A, 0x2B), (0x2D, 0x2F), (0x30, 0x31), (0x32, 0x33)],
[(0x34, 0x35), (0x36, 0x37), (0x38, 0x39), (0x3A, 0x3B), (0x3D, 0x3F), (0x40, 0x41), (0x42, 0x43), (0x44, 0x45), (0x46, 0x47), (0x48, 0x49), (0x4A, 0x4B), (0x4C, 0x4D), (0x4E, 0x4F), (0x50, 0x51), (0x52, 0x53), (0x54, 0x55), (0x56, 0x58), (0x59, 0x5A), (0x5B, 0x5C), (0x5D, 0x5E), (0x5F, 0x60), (0x61, 0x62), (0x63, 0x64), (0x65, 0x66), (0x67, 0x68)],
[(0x69, 0x6A), (0x6B, 0x6C), (0x6D, 0x6E), (0x6F, 0x70), (0x71, 0x72), (0x73, 0x74), (0x75, 0x76), (0x77, 0x78), (0x79, 0x7A), (0x7B, 0x7C), (0x7D, 0x7E), (0x7F, 0x80), (0x81, 0x82), (0x83, 0x84), (0x85, 0x86), (0x87, 0x88), (0x89, 0x8A), (0x8B, 0x8C), (0x8D, 0x8E), (0x8F, 0x90), (0x91, 0x92), (0x93, 0x94), (0x95, 0x96), (0x97, 0x98), (0x99, 0x9A)],
]
def command_mode(mode):
return f"39 01 00 00 0A 00 02 FE {((0b1010 << 4) | mode):02X}"
def pack_command(command_str):
command_buf = bytes(map(lambda x: int(x, base=16), command_str.strip().split()))
return struct.pack(f"i{len(command_buf)}s", len(command_buf), command_buf)
def set_gamma(gamma):
cmds = [command_mode(2)]
for color_idx in range(3):
for i, val in reversed(list(enumerate(gamma[color_idx]))):
msb, lsb = GAMMA_ADDR[color_idx][i]
cmds.append(f"39 0F 00 00 0A 00 02 {msb:02X} {(val >> 8) & 0xFF:02X}")
cmds.append(f"39 {'01' if i % 10 == 0 else '0F'} 00 00 0A 00 02 {lsb:02X} {val & 0xFF:02X}")
cmds.append(command_mode(0))
data = b"".join(pack_command(c) for c in cmds)
with open(COMMAND_FILE, "wb") as f:
f.write(data)
print("Successfully setup screen calibration")
if __name__ == "__main__":
if not os.path.exists(GAMMA_CURVES_FILE):
print("No gamma curves found")
exit(0)
assert os.path.exists(COMMAND_FILE), "MIPI command interface not found"
with open(GAMMA_CURVES_FILE, "r") as f:
gamma = [[int(x, 16) for x in l.strip().split()] for l in f]
set_gamma(gamma)
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -e
SERIAL="$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//')"
echo "serial: '$SERIAL'"
sysctl kernel.hostname="comma-$SERIAL"
+72
View File
@@ -0,0 +1,72 @@
#!/bin/bash
setup() {
# Check if /config is already mounted
if ! mountpoint -q /config; then
sudo mount -t configfs none /config
else
echo "/config is already mounted."
fi
# Create USB gadget directory structure
sudo mkdir -p /config/usb_gadget/g1
cd /config/usb_gadget/g1
sudo mkdir -p strings/0x409
sudo mkdir -p configs/c.1/strings/0x409
sudo mkdir -p functions/ncm.0
# Set Vendor and Product ID
echo 0x04D8 | sudo tee idVendor
echo 0x1234 | sudo tee idProduct
# Set strings
echo "$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//')" | sudo tee strings/0x409/serialnumber
echo "comma.ai" | sudo tee strings/0x409/manufacturer
echo "Linux USB Gadget" | sudo tee strings/0x409/product
echo 250 | sudo tee configs/c.1/MaxPower
# Create ADB function
sudo mkdir -p functions/ffs.adb
sudo mkdir -p /dev/usb-ffs/adb
if ! mountpoint -q /dev/usb-ffs/adb; then
sudo mount -t functionfs adb /dev/usb-ffs/adb
else
echo "/dev/usb-ffs/adb is already mounted"
fi
# Link both functions to configuration
echo "NCM+ADB" | sudo tee configs/c.1/strings/0x409/configuration
sudo rm -f configs/c.1/ncm.0
sudo rm -f configs/c.1/ffs.adb
sudo ln -s functions/ncm.0 configs/c.1/
sudo ln -s functions/ffs.adb configs/c.1/
}
start() {
setprop service.adb.tcp.port -1
cd /config/usb_gadget/g1
echo "a600000.dwc3" | sudo tee UDC
}
stop() {
if [ -d "/config/usb_gadget/g1" ]; then
cd /config/usb_gadget/g1
echo "" | sudo tee UDC
fi
}
ADB_PARAM="/data/params/d/AdbEnabled"
if [ -f "$ADB_PARAM" ] && [ "$(< $ADB_PARAM)" == "1" ]; then
echo "Enabling ADB"
setup
systemctl start adbd
sleep 1 # adbd does some setup before we can enable the gadget
start
else
echo "Disabling ADB"
systemctl stop adbd
stop
fi
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
SSH_PARAM="/data/params/d/SshEnabled"
if [ -f "$SSH_PARAM" ] && [ "$(< $SSH_PARAM)" == "1" ]; then
echo "Enabling SSH"
systemctl start ssh
else
echo "Disabling SSH"
systemctl stop ssh
fi
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c382ce266653bad781c25e403ddea4af508aa6f3ea2eef3f568d964982fad9d6
size 22996245
+1
View File
@@ -0,0 +1 @@
from="127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+iXXq30Tq+J5NKat3KWHCzcmwZ55nGh6WggAqECa5CasBlM9VeROpVu3beA+5h0MibRgbD4DMtVXBt6gEvZ8nd04E7eLA9LTZyFDZ7SkSOVj4oXOQsT0GnJmKrASW5KslTWqVzTfo2XCtZ+004ikLxmyFeBO8NOcErW1pa8gFdQDToH9FrA7kgysic/XVESTOoe7XlzRoe/eZacEQ+jtnmFd21A4aEADkk00Ahjr0uKaJiLUAPatxs2icIXWpgYtfqqtaKF23wSt61OTu6cAwXbOWr3m+IUSRUO0IRzEIQS3z1jfd1svgzSgSSwZ1Lhj4AoKxIEAIc8qJrO4uymCJ public
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/bash
PIP_PATH=/usr/local/venv/bin/pip
export TMPDIR=/tmp/pip-tmp
# setup large tmp for install
mkdir -p $TMPDIR
sudo mount -o remount,size=2G /tmp
sudo mount -o rw,remount /
sudo resize2fs $(findmnt -n -o SOURCE /) &>/dev/null || sudo resize2fs $(findmnt -n -o SOURCE /)
# run command
sudo TMPDIR=$TMPDIR PIP_NO_CACHE_DIR=1 $PIP_PATH "$@"
# cleanup
sudo mount -o ro,remount /
sudo rm -rf $TMPDIR
sudo mount -o remount /tmp
+1
View File
@@ -0,0 +1 @@
pip
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/bash
UV_PATH=/usr/bin/uv
export TMPDIR=/tmp/uv-tmp
# setup large tmp for install
mkdir -p $TMPDIR
sudo mount -o remount,size=2G /tmp
sudo mount -o rw,remount /
sudo resize2fs $(findmnt -n -o SOURCE /) &>/dev/null || sudo resize2fs $(findmnt -n -o SOURCE /)
# run command
sudo -E TMPDIR=$TMPDIR UV_NO_CACHE=1 $UV_PATH "$@"
# cleanup
sudo mount -o ro,remount /
sudo rm -rf $TMPDIR
sudo mount -o remount /tmp
+68
View File
@@ -0,0 +1,68 @@
#!/bin/sh
###############################################################################
#
# This script is used for administration of the Hexagon DSP
#
# Copyright (c) 2012-2016 Qualcomm Technologies, Inc.
# All Rights Reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.
#
###############################################################################
KEEP_ALIVE=0
subsys_name=""
echo -n "/firmware/image" > /sys/module/firmware_class/parameters/path
# Wait for adsp.mdt to show up
count=0
while [ ! -s /firmware/image/adsp.mdt ]; do
sleep 0.1
# wait 10s for /firmware mounted
count=$(( $count + 1 ))
if [ $count -ge 100 ]; then
echo "[ERROR] Can not find the adsp's firmware"
exit 1
fi
done
for subsys in `ls /sys/bus/msm_subsys/devices`; do
name=`cat /sys/bus/msm_subsys/devices/${subsys}/name`
if [ "`cat /sys/bus/msm_subsys/devices/${subsys}/name`" = "adsp" ]; then
subsys_name="${subsys}"
break
fi
done
if [ "$KEEP_ALIVE" = "1" ]; then
if [ -n "${subsys_name}" ]; then
sysctl -w kernel.panic=0
echo 1 > /sys/bus/msm_subsys/devices/${subsys_name}/keep_alive
else
echo "[ERROR] Can not keep adsp alive"
fi
fi
# FIXME: See ATL-3054
echo 1 > /sys/module/subsystem_restart/parameters/enable_debug
# Bring adsp out of reset
echo "[INFO] Bringing adsp out of reset"
echo "${subsys_name}"
echo 1 > /sys/kernel/boot_adsp/boot
# wait boot finished
if [ -n "${subsys_name}" ]; then
count=0
state=`cat /sys/bus/msm_subsys/devices/${subsys_name}/state`
while [ "${state}" != "ONLINE" ]; do
# wait 2s for subsys boot finished
count=$(( $count + 1 ))
if [ $count -ge 200 ]; then
echo "[ERROR] adsp fail to boot"
exit 1
fi
state=`cat /sys/bus/msm_subsys/devices/${subsys_name}/state`
sleep 0.1
done
fi
+161
View File
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
import time
from smbus2 import SMBus
from collections import namedtuple
from typing import List
# https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf
AmpConfig = namedtuple('AmpConfig', ['name', 'value', 'register', 'offset', 'mask'])
EQParams = namedtuple('EQParams', ['K', 'k1', 'k2', 'c1', 'c2'])
def configs_from_eq_params(base, eq_params):
return [
AmpConfig("K (high)", (eq_params.K >> 8), base, 0, 0xFF),
AmpConfig("K (low)", (eq_params.K & 0xFF), base + 1, 0, 0xFF),
AmpConfig("k1 (high)", (eq_params.k1 >> 8), base + 2, 0, 0xFF),
AmpConfig("k1 (low)", (eq_params.k1 & 0xFF), base + 3, 0, 0xFF),
AmpConfig("k2 (high)", (eq_params.k2 >> 8), base + 4, 0, 0xFF),
AmpConfig("k2 (low)", (eq_params.k2 & 0xFF), base + 5, 0, 0xFF),
AmpConfig("c1 (high)", (eq_params.c1 >> 8), base + 6, 0, 0xFF),
AmpConfig("c1 (low)", (eq_params.c1 & 0xFF), base + 7, 0, 0xFF),
AmpConfig("c2 (high)", (eq_params.c2 >> 8), base + 8, 0, 0xFF),
AmpConfig("c2 (low)", (eq_params.c2 & 0xFF), base + 9, 0, 0xFF),
]
BASE_CONFIG = [
AmpConfig("MCLK prescaler", 0b01, 0x10, 4, 0b00110000),
AmpConfig("PM: enable speakers", 0b11, 0x4D, 4, 0b00110000),
AmpConfig("PM: enable DACs", 0b11, 0x4D, 0, 0b00000011),
AmpConfig("Enable PLL1", 0b1, 0x12, 7, 0b10000000),
AmpConfig("Enable PLL2", 0b1, 0x1A, 7, 0b10000000),
AmpConfig("DAI1: I2S mode", 0b00100, 0x14, 2, 0b01111100),
AmpConfig("DAI2: I2S mode", 0b00100, 0x1C, 2, 0b01111100),
AmpConfig("DAI1 Passband filtering: music mode", 0b1, 0x18, 7, 0b10000000),
AmpConfig("DAI1 voice mode gain (DV1G)", 0b00, 0x2F, 4, 0b00110000),
AmpConfig("DAI1 attenuation (DV1)", 0x0, 0x2F, 0, 0b00001111),
AmpConfig("DAI2 attenuation (DV2)", 0x0, 0x31, 0, 0b00001111),
AmpConfig("DAI2: DC blocking", 0b1, 0x20, 0, 0b00000001),
AmpConfig("DAI2: High sample rate", 0b0, 0x20, 3, 0b00001000),
AmpConfig("ALC enable", 0b1, 0x43, 7, 0b10000000),
AmpConfig("ALC/excursion limiter release time", 0b101, 0x43, 4, 0b01110000),
AmpConfig("ALC multiband enable", 0b1, 0x43, 3, 0b00001000),
AmpConfig("DAI1 EQ enable", 0b0, 0x49, 0, 0b00000001),
AmpConfig("DAI2 EQ clip detection disabled", 0b1, 0x32, 4, 0b00010000),
AmpConfig("DAI2 EQ attenuation", 0x5, 0x32, 0, 0b00001111),
AmpConfig("Excursion limiter upper corner freq", 0b100, 0x41, 4, 0b01110000),
AmpConfig("Excursion limiter lower corner freq", 0b00, 0x41, 0, 0b00000011),
AmpConfig("Excursion limiter threshold", 0b000, 0x42, 0, 0b00001111),
AmpConfig("Distortion limit (THDCLP)", 0x6, 0x46, 4, 0b11110000),
AmpConfig("Distortion limiter release time constant", 0b0, 0x46, 0, 0b00000001),
AmpConfig("Right DAC input mixer: DAI1 left", 0b0, 0x22, 3, 0b00001000),
AmpConfig("Right DAC input mixer: DAI1 right", 0b0, 0x22, 2, 0b00000100),
AmpConfig("Right DAC input mixer: DAI2 left", 0b1, 0x22, 1, 0b00000010),
AmpConfig("Right DAC input mixer: DAI2 right", 0b0, 0x22, 0, 0b00000001),
AmpConfig("DAI1 audio port selector", 0b10, 0x16, 6, 0b11000000),
AmpConfig("DAI1 in/output enable", 0b11, 0x16, 0, 0b11),
AmpConfig("DAI2 audio port selector", 0b01, 0x1E, 6, 0b11000000),
AmpConfig("DAI2 in/output enable", 0b11, 0x1E, 0, 0b11),
AmpConfig("Enable left digital microphone", 0b1, 0x48, 5, 0b00100000),
AmpConfig("Enable right digital microphone", 0b1, 0x48, 4, 0b00010000),
AmpConfig("Enhanced volume smoothing disabled", 0b0, 0x49, 7, 0b10000000),
AmpConfig("Volume adjustment smoothing disabled", 0b0, 0x49, 6, 0b01000000),
AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000),
]
CONFIGS = {
"tici": [
AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111),
AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100),
AmpConfig("Right speaker output volume", 0x1c, 0x3E, 0, 0b00011111),
AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010),
*configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)),
*configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)),
*configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)),
*configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)),
*configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)),
],
"tizi": [
AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111),
AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111),
AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011),
AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100),
AmpConfig("Left speaker output volume", 0x17, 0x3D, 0, 0b00011111),
AmpConfig("Right speaker output volume", 0x17, 0x3E, 0, 0b00011111),
AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010),
AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001),
AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000),
AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111),
AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000),
AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111),
AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000),
AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001),
AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000),
AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000),
AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000),
AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000),
AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010),
AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001),
AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000),
],
}
class Amplifier:
AMP_I2C_BUS = 0
AMP_ADDRESS = 0x10
def __init__(self, debug=False):
self.debug = debug
def _get_shutdown_config(self, amp_disabled: bool) -> AmpConfig:
return AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000)
def _set_configs(self, configs: List[AmpConfig]) -> None:
with SMBus(self.AMP_I2C_BUS) as bus:
for config in configs:
if self.debug:
print(f"Setting \"{config.name}\" to {config.value}:")
old_value = bus.read_byte_data(self.AMP_ADDRESS, config.register, force=True)
new_value = (old_value & (~config.mask)) | ((config.value << config.offset) & config.mask)
bus.write_byte_data(self.AMP_ADDRESS, config.register, new_value, force=True)
if self.debug:
print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}")
def set_configs(self, configs: List[AmpConfig]) -> bool:
# retry in case panda is using the amp
for _ in range(10):
try:
self._set_configs(configs)
return True
except OSError:
print("Failed to set amp config, retrying...")
time.sleep(0.02)
return False
def set_global_shutdown(self, amp_disabled: bool) -> bool:
return self.set_configs([self._get_shutdown_config(amp_disabled), ])
def initialize_configuration(self, model: str) -> bool:
cfgs = [
self._get_shutdown_config(True),
*BASE_CONFIG,
*CONFIGS[model],
self._get_shutdown_config(False),
]
return self.set_configs(cfgs)
if __name__ == "__main__":
with open("/sys/firmware/devicetree/base/model") as f:
model = f.read().strip('\x00')
model = model.split('comma ')[-1]
if model in CONFIGS:
amp = Amplifier()
amp.initialize_configuration(model)
else:
print(f"Skipping, no config for {model}")
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
/usr/comma/sound/adsp-start.sh
echo "waiting for sound card to come online"
while [ ! -d /proc/asound/sdm845tavilsndc ] || [ "$(cat /proc/asound/card0/state 2> /dev/null)" != "ONLINE" ] ; do
sleep 0.01
done
echo "sound card online"
while ! /usr/comma/sound/tinymix controls | grep -q "SEC_MI2S_RX Audio Mixer MultiMedia1"; do
sleep 0.01
done
echo "tinymix controls ready"
/usr/comma/sound/tinymix set "SEC_MI2S_RX Audio Mixer MultiMedia1" 1
if grep -q mici /sys/firmware/devicetree/base/model; then
/usr/comma/sound/tinymix set "MultiMedia1 Mixer SEC_MI2S_TX" 1
else
/usr/comma/sound/tinymix set "MultiMedia1 Mixer TERT_MI2S_TX" 1
/usr/comma/sound/tinymix set "TERT_MI2S_TX Channels" Two
fi
# setup the amplifier registers
/usr/local/venv/bin/python /usr/comma/sound/amplifier.py
Binary file not shown.
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
import subprocess
from tabulate import tabulate
# serial with timestamps:
# grabserial -d /dev/serial/by-id/usb-FTDI_FT230X* -t
# systemd-analyze critical-chain comma.service
# boot chart:
# systemd-analyze plot > /tmp/bootup.svg
def get_journal_time(x):
out = subprocess.check_output("journalctl -x -o short-monotonic", shell=True, encoding='utf8')
jlines = out.strip().splitlines()
for l in jlines:
if x in l:
t = l.split('[')[1].split(']')[0]
return float(t)
return None
if __name__ == "__main__":
ts = {
"PON": 1.5, # estimate from powering on to XBL
# these are dumped over serial, use the ones from the XBL
"XBL": 2.4,
"ABL": 3.7, # 3s of this is waiting for fastboot + factory reset tapping
}
def tot_since_kern():
return sum(ts.values()) - (ts['PON'] + ts['XBL'] + ts['ABL'])
out = subprocess.check_output("systemd-analyze", shell=True, encoding='utf8')
l = out.splitlines()[0].split(' ')
ts['kernel'] = float(l[3][:-1])
#ts['systemd'] = float(l[6][:-1])
ts['comma'] = get_journal_time("Starting comma.service") - tot_since_kern()
# print
tot = 0
total = sum(ts.values())
tab = []
for s, t in ts.items():
tot += t
tab.append([
s,
round(t, 2),
round(tot, 2),
str(round(t/total * 100)) + "%",
])
# TODO: add openpilot time-to-onroad
tab.append(['onroad', '?', '?', '-'])
print(tabulate(tab))
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import os
import time
import subprocess
def run(cmd):
return subprocess.check_output(cmd, shell=True, encoding='utf8')
def test_setup():
# TODO: write some real unit tests for this
proc = subprocess.Popen('/usr/comma/setup', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
time.sleep(5)
assert proc.poll() is None
proc.terminate()
def test_reset():
proc = subprocess.Popen('/usr/comma/reset', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
time.sleep(5)
assert proc.poll() is None
proc.terminate()
def test_modem():
from test_modem import test_modem_setup, test_modem as check_modem, test_cellular_connection
test_modem_setup()
check_modem()
test_cellular_connection()
def test_wifi():
out = run("nmcli dev wifi")
networks = out.strip().splitlines()[1:]
assert len(networks) > 5
def test_color_calibration():
out = run("journalctl -b -u magic")
assert out.count("Successfully setup color correction") == 3
def test_python_shims():
subprocess.check_call("cd /data/openpilot && scons -h", shell=True, stdout=subprocess.DEVNULL)
def test_dmesg():
# TODO: ensure no new errors in dmesg. would catch things like the brightness setting bug
pass
def test_systemd_services():
assert run("systemctl is-active magic").strip() == "active"
assert os.path.exists("/tmp/drmfd.sock")
def test_boot_time():
out = subprocess.check_output("systemd-analyze", shell=True, encoding='utf8')
a = out.splitlines()[0]
print(out, a)
# check magic service
# check comma service
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
import json
import os
import shutil
import string
import subprocess
import time
from ipaddress import IPv4Address
from pathlib import Path
def modem_state(timeout=120):
deadline = time.monotonic() + timeout
while True:
try:
state = json.loads(Path('/dev/shm/modem').read_text())
if state.get('imei') and state.get('iccid'):
return state
except (FileNotFoundError, json.JSONDecodeError):
pass
assert time.monotonic() < deadline, "modem.service did not publish modem/SIM identity"
time.sleep(1)
def test_modem_setup():
assert not Path('/lib/systemd/system/ModemManager.service').exists()
assert subprocess.check_output(['systemctl', 'is-enabled', 'modem.service'], text=True).strip() == 'enabled'
assert subprocess.check_output(['systemctl', 'is-active', 'modem.service'], text=True).strip() == 'active'
for port in ('/dev/modem_at0', '/dev/modem_at1'):
assert os.access(port, os.R_OK | os.W_OK), f"comma needs access to {port}"
for command in ('pppd', 'chat', 'ip', 'resolvectl', 'killall'):
assert shutil.which(command), f"modem.py requires {command}"
rules = subprocess.check_output(['sudo', '-n', 'iptables-save', '-t', 'filter'], text=True)
assert '-A INPUT -i ppp0 -j DROP' in rules
def test_modem():
state = modem_state()
assert state['imei'].isdigit() and 14 <= len(state['imei']) <= 17
assert all(c in string.hexdigits for c in state['iccid']) and 18 <= len(state['iccid']) <= 22
assert state['modem_version'].startswith(('EG25', 'EG91'))
assert state['state'] in ('SEARCHING', 'CONNECTING', 'CONNECTED')
from openpilot.common.hardware import HARDWARE
assert HARDWARE.get_imei() == state['imei']
assert HARDWARE.get_sim_info()['sim_id'] == state['iccid']
assert HARDWARE.get_sim_info()['sim_state'] == ['READY']
def test_cellular_connection():
# Requires a SIM with service and cellular coverage, even when Wi-Fi is connected.
state = modem_state()
assert state['connected'], f"Cellular not connected: {state['state']} ({state['registration']})"
ip = str(IPv4Address(state['ip_address']))
routes = subprocess.check_output(['ip', '-4', 'route', 'show', 'table', '1000'], text=True)
assert 'default' in routes and 'dev ppp0' in routes
rules = subprocess.check_output(['ip', '-4', 'rule', 'show'], text=True)
assert any(f'from {ip}' in rule and 'lookup 1000' in rule for rule in rules.splitlines())
dns = subprocess.check_output(['resolvectl', 'dns', 'ppp0'], text=True)
assert len(dns.split(':', 1)[-1].split()) > 0
subprocess.check_call(['ping', '-I', 'ppp0', '-c', '3', '-W', '10', '1.1.1.1'])
if __name__ == '__main__':
test_modem_setup()
test_modem()
test_cellular_connection()
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env python3
import os
import time
import subprocess
good = """
Starting Sound...
[INFO] Bringing adsp out of reset
subsys4
waiting for sound card to come online
sound card online
sound.service: Succeeded.
Finished Sound.
""".strip()
if __name__ == "__main__":
time.sleep(3)
log = subprocess.check_output("journalctl -o cat -u sound", shell=True, encoding='utf8').strip()
passed = log == good
with open('/data/tmp/sound_log', 'a') as f:
f.write(f"{passed}\n")
os.system("journalctl -u sound.service -o short-monotonic >> /data/tmp/sound_service_log")
os.system("sudo su -c 'tail /data/tmp/sound_log > /dev/console'")
os.system("sudo su -c 'wc -l /data/tmp/sound_log > /dev/console'")
os.sync()
if passed:
time.sleep(2)
os.system("sudo reboot")
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
set -e
RATE=${1:-1}
log_message() {
# /var/log/syslog
cat /usr/include/sqlite3.h | systemd-cat -t SPAM_TEST
# /var/log/kern.log
#cat /usr/include/sqlite3.h | sudo tee /dev/kmsg > /dev/null
}
# verify config is good
sudo logrotate -d /etc/logrotate.conf
sudo rm -rf /var/log/*
sudo systemctl daemon-reload
sudo systemctl restart rsyslog
sudo systemctl restart systemd-journald
sudo systemctl restart logrotate-hourly.timer
while true; do
for i in $(seq 1 $RATE); do
log_message
done
echo
df -h /var/
sudo du -hs /var/log/* || true
sleep 1
done
@@ -0,0 +1,7 @@
#!/usr/bin/bash
# silences a ruby warning
# https://bugs.launchpad.net/ubuntu/+source/iptables-netflow/+bug/1907932
export RUBYOPT="-W0"
irqtop
+24
View File
@@ -0,0 +1,24 @@
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
# See tmpfiles.d(5) for details
# /var: rebuild enough for services to be happy
d /var/crash 0755 - - -
q /var/tmp 1777 root root 30d
d /var/tmp/xdg 0700 comma comma -
d /var/lib/dpkg 0755 - - -
f /var/lib/dpkg/lock-frontend 0755 - - -
f /var/lib/dpkg/status 0755 - - -
d /var/lib/logrotate 0755 - - -
d /var/spool 0755 - - -
d /var/spool/cron/atjobs 0755 - - -
d /var/cache/pollinate 0755 pollinate daemon -
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9df4edbeb5849de03f9c2d691d04646af84a3ef74c2f33be8e73d9281daebe99
size 23027477
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env python3
import os
import time
if __name__ == "__main__":
# we are the last line of defense for /var/log filling up
while True:
usage = os.statvfs('/var/log')
percent = (usage.f_blocks - usage.f_bavail) / usage.f_blocks * 100
if percent > 70:
for fn in os.listdir('/var/log'):
file_path = os.path.join('/var/log', fn)
if os.path.isfile(file_path):
with open(file_path, 'w'):
pass
time.sleep(1)
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash -e
until [ -e $1 ]
do
sleep 1
done
Binary file not shown.
Binary file not shown.