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 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. back up and replace policyman RF-band config on a rooted Sony Xperia 1 VI.
""" """
import sys, os, time, struct, serial import sys, os, time, struct, glob, serial
PORT = os.environ.get("DIAGPORT", "/dev/ttyUSB0")
DIAG_SUBSYS = 0x4B DIAG_SUBSYS = 0x4B
SUBSYS_EFS = 19 SUBSYS_EFS = 19
@ -50,9 +48,57 @@ def hdlc_decode(frame):
else: out.append(b) else: out.append(b)
return bytes(out) 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: class Diag:
def __init__(self, port=PORT): def __init__(self, port=None):
self.s = serial.Serial(port, 115200, timeout=0.4) self.port = port or find_diag_port()
self.s = serial.Serial(self.port, 115200, timeout=0.4)
self.s.reset_input_buffer() self.s.reset_input_buffer()
def _read_frame(self, timeout=4.0): 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(): def main():
cmd = sys.argv[1] if len(sys.argv) > 1 else "probe" cmd = sys.argv[1] if len(sys.argv) > 1 else "probe"
d = Diag() d = Diag()
print("using diag port %s" % d.port)
ver = d.hello() ver = d.hello()
print("EFS hello ok, version=%d" % ver) 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 '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 as USB `05c6:9091` (adb survives). Bind the serial driver (needs root on
the PC): `sudo modprobe option && echo 05c6 9091 | sudo tee the PC): `sudo modprobe option && echo 05c6 9091 | sudo tee
/sys/bus/usb-serial/drivers/option1/new_id`. Diag port = `/dev/ttyUSB0` /sys/bus/usb-serial/drivers/option1/new_id`. The diag channel is USB
(the one that answers a DIAG version request; ttyUSB1 is silent). 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 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 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 small pyserial EFS2 client (HDLC + FCS-16, subsys 0x4B/EFS 19). Script is
kept at `~/.../scratchpad/efs2.py` (+ `diag_probe.py`). It does: `5g-policyman/efs2.py` in this skill. It does:
hello → list `/policyman`**back up** the 3 files → write new ones hello → list `/policyman`**back up** the 3 files → write new ones
(unlink then open `O_WRONLY|O_CREAT` perm 0777, 1 KB write chunks) → (unlink then open `O_WRONLY|O_CREAT` perm 0777, 1 KB write chunks) →
read back and byte-compare to verify. read back and byte-compare to verify.