Files
STM32F4-Base/MDK-ARM/bench_diag/bench_diag_sink_recycle.py
T

188 lines
6.9 KiB
Python

#!/usr/bin/env python3
# Reusable bench probe (non-FTP :7003 close->reopen boundary; lives in MDK-ARM/bench_diag/).
# Non-FTP boundary probe: the same close->reopen recycle, but on the raw RemoDispBus
# service (:7003, Drivers/BSP/UI/ui_net_sink.c), which shares the net/chip layer with FTP
# and none of lftpd's code. If a rapid recycle damages data here too, the defect lives in
# the net/chip layer and FTP only makes it visible.
#
# Session A (the trigger): connect -> CMD_INIT -> CMD_LCDMEM (3209B, CRC) -> [--burst N
# keepalive frames queued unread = data still in flight at abort] -> abort (RST / --fin).
# Gap G ms.
# Session B (the victim): connect -> [--burst N keepalive frames -> count the echoed acks,
# this is the RX direction analogue of "upload truncated to 2048B"] -> CMD_INIT ->
# CMD_LCDMEM -> frame length + CRC (device->client analogue).
#
# Classification per round:
# RX_SHORT : session B echoed fewer acks than frames sent (client->device data cut short)
# TX_SHORT : session B LCDMEM frame short of 3209 (device->client data cut short)
# CORRUPT : full length but CRC mismatch / unexpected frame
# OK : everything intact
import argparse, socket, struct, sys, time
TAG_CLIENT = 0xAA
TAG_DEVICE = 0xBB
CMD_KEEP = 0
CMD_INIT = 1
CMD_LCDMEM = 3
MEMSIZE = 3200
LCDMEM_TOTAL = 4 + 4 + MEMSIZE + 1 # 3209
ACK_LEN = 5 # empty frame reply to a keepalive
def log(m):
sys.stdout.write(m + "\n")
sys.stdout.flush()
def xor_bytes(b):
c = 0
for x in b:
c ^= x
return c
def req(cmd, data=b""):
body = bytes(data)
return (bytes([TAG_CLIENT, cmd, (len(body) >> 8) & 0xFF, len(body) & 0xFF]) + body
+ bytes([xor_bytes(body)]))
def read_frame(s, deadline):
"""Read exactly one device frame; returns total/got/crc_ok/short."""
buf = bytearray()
total = None
while True:
want = (4 - len(buf)) if total is None else (total - len(buf))
if want <= 0:
break
left = deadline - time.monotonic()
if left <= 0:
return {"total": (total or 0), "got": len(buf), "crc_ok": False, "short": True}
s.settimeout(left)
try:
d = s.recv(min(want, 4096))
except socket.timeout:
return {"total": (total or 0), "got": len(buf), "crc_ok": False, "short": True}
except OSError:
d = b""
if not d:
return {"total": (total or 0), "got": len(buf), "crc_ok": False, "short": True}
buf.extend(d)
if total is None and len(buf) >= 4:
total = 4 + (((buf[2] << 8) | buf[3])) + 1
body = bytes(buf[4:total - 1])
return {"total": total, "got": len(buf), "crc_ok": (buf[total - 1] == xor_bytes(body)),
"short": (len(buf) != total)}
def read_n(s, want, deadline):
"""Read up to `want` bytes; stops at deadline / EOF. Returns bytes."""
buf = bytearray()
while len(buf) < want:
left = deadline - time.monotonic()
if left <= 0:
break
s.settimeout(left)
try:
d = s.recv(min(want - len(buf), 4096))
except socket.timeout:
break
except OSError:
break
if not d:
break
buf.extend(d)
return bytes(buf)
def abort_sock(s, fin):
try:
if fin:
s.shutdown(socket.SHUT_RDWR)
else:
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
except OSError:
pass
try:
s.close()
except OSError:
pass
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--ip", default="192.168.1.100")
ap.add_argument("--port", type=int, default=7003)
ap.add_argument("--n", type=int, default=8)
ap.add_argument("--gap", type=int, default=0, help="abort -> reconnect gap in ms")
ap.add_argument("--fin", action="store_true")
ap.add_argument("--burst", type=int, default=400,
help="keepalive frames used to load the RX direction (0 = TCP-only test)")
ap.add_argument("--no-abort", action="store_true", help="no session A (baseline)")
a = ap.parse_args()
res = {"OK": 0, "RX_SHORT": 0, "TX_SHORT": 0, "CORRUPT": 0, "ERR": 0}
log("== sink recycle: port=%d gap=%dms abort=%s burst=%d n=%d"
% (a.port, a.gap, "NONE" if a.no_abort else ("FIN" if a.fin else "RST"),
a.burst, a.n))
for i in range(1, a.n + 1):
try:
# ---- session A: trigger ----
if not a.no_abort:
s1 = socket.create_connection((a.ip, a.port), timeout=10.0)
s1.sendall(req(CMD_INIT))
read_frame(s1, time.monotonic() + 10.0)
if a.burst:
s1.sendall(req(CMD_KEEP) * a.burst)
s1.sendall(req(CMD_LCDMEM, bytes(4)))
f1 = read_frame(s1, time.monotonic() + 10.0)
abort_sock(s1, a.fin)
t_ab = time.monotonic()
else:
f1 = {"got": -1, "crc_ok": True}
t_ab = time.monotonic()
# ---- gap ----
if a.gap > 0:
time.sleep(a.gap / 1000.0)
# ---- session B: victim ----
s2 = socket.create_connection((a.ip, a.port), timeout=10.0)
rx_bytes = 0
if a.burst:
s2.sendall(req(CMD_KEEP) * a.burst)
got = read_n(s2, a.burst * ACK_LEN, time.monotonic() + 10.0)
rx_bytes = len(got)
s2.sendall(req(CMD_INIT))
ini = read_frame(s2, time.monotonic() + 10.0)
s2.sendall(req(CMD_LCDMEM, bytes(4)))
f2 = read_frame(s2, time.monotonic() + 10.0)
dt = (time.monotonic() - t_ab) * 1e3
abort_sock(s2, a.fin)
rx_want = a.burst * ACK_LEN
rx_short = a.burst and rx_bytes < rx_want
tx_short = (f2["got"] != LCDMEM_TOTAL) or (ini["got"] != 13)
if rx_short:
cls = "RX_SHORT"
elif tx_short:
cls = "TX_SHORT"
elif not (f2["crc_ok"] and ini["crc_ok"]):
cls = "CORRUPT"
else:
cls = "OK"
res[cls] += 1
log(" %2d A lcd=%4d crc=%-4s | B ack=%5d/%5d init=%2dB lcd=%4d/%d crc=%-4s "
"dt=%6.0fms -> %s"
% (i, f1["got"], "OK" if f1["crc_ok"] else "BAD", rx_bytes, rx_want,
ini["got"], f2["got"], LCDMEM_TOTAL,
"OK" if f2["crc_ok"] else "BAD", dt, cls))
except Exception as e:
res["ERR"] += 1
log(" %2d EXC %s: %s" % (i, type(e).__name__, str(e)[:70]))
time.sleep(0.3)
log("SUMMARY port=%d gap=%dms abort=%s burst=%d n=%d ok=%d rx_short=%d tx_short=%d "
"corrupt=%d err=%d"
% (a.port, a.gap, "NONE" if a.no_abort else ("FIN" if a.fin else "RST"), a.burst,
a.n, res["OK"], res["RX_SHORT"], res["TX_SHORT"], res["CORRUPT"], res["ERR"]))
main()