Auto-detect the diag port in efs2.py

Probe each candidate serial port with a DIAG version request and keep
the one that answers, interface 0 (-if00-) first, instead of hardcoding
ttyUSB0. Use /dev/serial/by-id for stable naming; DIAGPORT overrides.

Claude-Session: https://claude.ai/code/session_01Ctisr9uXe4H8XsscZ2exGE
This commit is contained in:
James Coleman 2026-08-27 16:09:10 -05:00
parent 8318606f34
commit b42dc4c3f1
2 changed files with 58 additions and 9 deletions

View file

@ -4,9 +4,7 @@
Packet layouts and opcodes replicate JohnBel/EfsTools exactly. Used to
back up and replace policyman RF-band config on a rooted Sony Xperia 1 VI.
"""
import sys, os, time, struct, serial
PORT = os.environ.get("DIAGPORT", "/dev/ttyUSB0")
import sys, os, time, struct, glob, serial
DIAG_SUBSYS = 0x4B
SUBSYS_EFS = 19
@ -50,9 +48,57 @@ def hdlc_decode(frame):
else: out.append(b)
return bytes(out)
def _responds_to_diag(port, timeout=1.0):
"""Return True if port answers a DIAG version request (cmd 0x00)."""
try:
s = serial.Serial(port, 115200, timeout=0.3)
except Exception:
return False
try:
s.reset_input_buffer()
s.write(hdlc_encode([0x00]))
buf, deadline = bytearray(), time.time() + timeout
while time.time() < deadline:
b = s.read(1)
if not b:
continue
if b[0] == 0x7E:
if buf:
dec = hdlc_decode(bytes(buf))
buf = bytearray()
if len(dec) >= 3 and dec[0] == 0x00 and \
fcs16(dec[:-2]) == struct.unpack("<H", dec[-2:])[0]:
return True
else:
buf.append(b[0])
return False
finally:
s.close()
def find_diag_port():
"""Find the DIAG serial port. DIAGPORT wins; otherwise probe candidates,
interface 0 (-if00-) first, and return the one that answers DIAG."""
env = os.environ.get("DIAGPORT")
if env:
return env
candidates = (sorted(glob.glob("/dev/serial/by-id/*-if00-port0")) +
sorted(glob.glob("/dev/serial/by-id/*")) +
sorted(glob.glob("/dev/ttyUSB*")))
seen = set()
for p in candidates:
real = os.path.realpath(p)
if real in seen:
continue
seen.add(real)
if _responds_to_diag(p):
return p
raise IOError("no responding DIAG port found; is the phone in diag mode "
"(setprop sys.usb.config diag,serial_cdev,rmnet,adb)?")
class Diag:
def __init__(self, port=PORT):
self.s = serial.Serial(port, 115200, timeout=0.4)
def __init__(self, port=None):
self.port = port or find_diag_port()
self.s = serial.Serial(self.port, 115200, timeout=0.4)
self.s.reset_input_buffer()
def _read_frame(self, timeout=4.0):
@ -206,6 +252,7 @@ BACKUP = "/tmp/claude-1000/-home-grmrgecko-Downloads-XperiFirm-5-8-1--by-Igor-Ei
def main():
cmd = sys.argv[1] if len(sys.argv) > 1 else "probe"
d = Diag()
print("using diag port %s" % d.port)
ver = d.hello()
print("EFS hello ok, version=%d" % ver)

View file

@ -115,12 +115,14 @@ be redone.
'setprop sys.usb.config diag,serial_cdev,rmnet,adb'`. Phone re-enumerates
as USB `05c6:9091` (adb survives). Bind the serial driver (needs root on
the PC): `sudo modprobe option && echo 05c6 9091 | sudo tee
/sys/bus/usb-serial/drivers/option1/new_id`. Diag port = `/dev/ttyUSB0`
(the one that answers a DIAG version request; ttyUSB1 is silent).
/sys/bus/usb-serial/drivers/option1/new_id`. The diag channel is USB
interface 0, stable as `/dev/serial/by-id/usb-Sony_XQ-EC72_*-if00-port0`
(maps to a ttyUSB whose number is not stable). efs2.py auto-detects it by
probing each port with a DIAG version request; set `DIAGPORT` to override.
3. Talk EFS2 over DIAG. EfsTools (JohnBel) is the usual tool but its
libnserial hits EIO on the option-driver port; the reliable path was a
small pyserial EFS2 client (HDLC + FCS-16, subsys 0x4B/EFS 19). Scripts
kept at `~/.../scratchpad/efs2.py` (+ `diag_probe.py`). It does:
small pyserial EFS2 client (HDLC + FCS-16, subsys 0x4B/EFS 19). Script is
`5g-policyman/efs2.py` in this skill. It does:
hello → list `/policyman`**back up** the 3 files → write new ones
(unlink then open `O_WRONLY|O_CREAT` perm 0777, 1 KB write chunks) →
read back and byte-compare to verify.