oep-client-python 0.0.44__py3-none-any.whl → 0.0.46__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oep_client/__init__.py +1 -1
- oep_client/__main__.py +4 -4
- oep_client/acquire.py +6 -3
- oep_client/capture.py +20 -50
- oep_client/conformance/__init__.py +47 -6
- oep_client/conformance/allocator.py +1 -1
- oep_client/conformance/capture_serial.py +1 -1
- oep_client/conformance/core_smoke.py +1 -1
- oep_client/conformance/cut.py +1 -1
- oep_client/conformance/data.py +1 -1
- oep_client/conformance/dependencies.py +1 -1
- oep_client/conformance/force_hw.py +147 -0
- oep_client/conformance/gpio_lifecycle.py +2 -2
- oep_client/conformance/host_core.py +1 -1
- oep_client/conformance/host_deadline.py +2 -2
- oep_client/conformance/host_request_sample.py +1 -2
- oep_client/conformance/host_requests.py +10 -15
- oep_client/conformance/host_wait.py +2 -2
- oep_client/conformance/lease.py +1 -1
- oep_client/conformance/notification_sequence.py +1 -1
- oep_client/conformance/notify_hw.py +322 -0
- oep_client/conformance/oep_smoke.py +2 -2
- oep_client/conformance/recovery.py +1 -1
- oep_client/conformance/release_hw.py +123 -0
- oep_client/conformance/retention.py +2 -2
- oep_client/conformance/retention_lifecycle.py +2 -2
- oep_client/conformance/retry.py +1 -1
- oep_client/conformance/routes.py +1 -1
- oep_client/conformance/serial.py +5 -0
- oep_client/conformance/tcp_peers.py +17 -8
- oep_client/conformance/tcp_recovery.py +1 -1
- oep_client/conformance/usb.py +24 -7
- oep_client/conformance/usb_cases.py +40 -1
- oep_client/conformance/usb_hw.py +313 -0
- oep_client/conformance/writer_lease.py +1 -1
- oep_client/endpoint.py +7 -17
- oep_client/fixture.py +8 -2
- oep_client/host.py +1 -1
- oep_client/interfaces.py +3 -0
- oep_client/link.py +60 -60
- oep_client/memo.py +101 -0
- oep_client/pins.py +1 -1
- oep_client/registry.py +7 -3
- oep_client/speed_record.py +2 -2
- oep_client/virtual_bench.py +15 -44
- oep_client/virtual_bench_capture.py +50 -276
- oep_client/virtual_bench_serve.py +1 -1
- oep_client/virtual_fixture_drive.py +2 -2
- {oep_client_python-0.0.44.dist-info → oep_client_python-0.0.46.dist-info}/METADATA +1 -1
- {oep_client_python-0.0.44.dist-info → oep_client_python-0.0.46.dist-info}/RECORD +53 -48
- {oep_client_python-0.0.44.dist-info → oep_client_python-0.0.46.dist-info}/WHEEL +0 -0
- {oep_client_python-0.0.44.dist-info → oep_client_python-0.0.46.dist-info}/entry_points.txt +0 -0
- {oep_client_python-0.0.44.dist-info → oep_client_python-0.0.46.dist-info}/licenses/LICENSE +0 -0
oep_client/__init__.py
CHANGED
oep_client/__main__.py
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
port, IP; each verified first by confirm + describe's unit_id, host guide
|
|
12
12
|
§4.1 - one that fails is dropped with a warning)
|
|
13
13
|
oep speed <probe> [--candidates 921600,500000] [--verify [--flows in:2,out:2]] (port_speed, oep-if-link §3 and the host
|
|
14
|
-
|
|
14
|
+
the port_speed procedure (README): try the candidates in order on a UART bridge, report; default
|
|
15
15
|
500000 - a faster rate only when named, after its 1 s verify each way)
|
|
16
16
|
oep linktest <probe> --rates now,921600 --patterns in,out,duplex --inflight 1,2 --sizes 128,496 --frames 300
|
|
17
17
|
oep pins <probe> --power 5 --wire swio (find the target's debug pins and reset line: classify the channels, scan,
|
|
@@ -64,13 +64,13 @@ def main(argv=None) -> int:
|
|
|
64
64
|
sp.add_argument("--candidates", default="", help="the candidates, comma-separated (default 500000, the default "
|
|
65
65
|
"ceiling of oep-if-link §3 obligation 7). A rate above 500000 is tried only when named here, and "
|
|
66
66
|
"is committed only after full frames ran 1 s each way (in, out; duplex too with --verify's "
|
|
67
|
-
"default flows) at it (
|
|
67
|
+
"default flows) at it (the port_speed procedure (README))")
|
|
68
68
|
sp.add_argument("--flows", default="", help="the flows to verify, comma-separated FLOW[:N] (in, out, duplex; N in "
|
|
69
69
|
"flight, 0 = the most the link keeps; default: all three at that N). Selects --verify")
|
|
70
70
|
form = sp.add_mutually_exclusive_group()
|
|
71
|
-
form.add_argument("--verify", action="store_true", help="the full form (
|
|
71
|
+
form.add_argument("--verify", action="store_true", help="the full form (the port_speed procedure (README)): baseline, the flows measured "
|
|
72
72
|
"at every candidate, a failed flow run again one at a time")
|
|
73
|
-
form.add_argument("--minimal", action="store_true", help="the minimal form (
|
|
73
|
+
form.add_argument("--minimal", action="store_true", help="the minimal form (the port_speed procedure (README), the default): try, confirm, "
|
|
74
74
|
"commit, no measurement")
|
|
75
75
|
sp.add_argument("--frames", type=int, default=link.FLOW_FRAMES, help="frames per flow when verifying (default 16)")
|
|
76
76
|
sp.add_argument("--no-record", action="store_true", help="do not read or write the record of passed / failed rates "
|
oep_client/acquire.py
CHANGED
|
@@ -41,6 +41,8 @@ from dataclasses import dataclass, field
|
|
|
41
41
|
from fractions import Fraction
|
|
42
42
|
from pathlib import Path
|
|
43
43
|
|
|
44
|
+
from . import registry as _reg
|
|
45
|
+
|
|
44
46
|
__all__ = ["Analog", "Bits", "Changes", "OnDisk", "Part", "Request", "Result", "capture", "capture_at"]
|
|
45
47
|
|
|
46
48
|
TRIGGERS = ("high", "low", "rise", "fall", "both")
|
|
@@ -338,13 +340,14 @@ def from_interleaved(data: bytes | bytearray | memoryview, names: list[str], wid
|
|
|
338
340
|
return out
|
|
339
341
|
|
|
340
342
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
+
_LEVEL, _EDGE = (_reg.FIXTURE_LOGIC.enum["trigger"][k] for k in ("level", "edge"))
|
|
344
|
+
TRIGGER = {"high": (_LEVEL, 1), "low": (_LEVEL, 0), "rise": (_EDGE, 0), "fall": (_EDGE, 1), "both": (_EDGE, 2)} # (type, value), capture §3.3
|
|
345
|
+
TAG_CHIP, TAG_MODEL, TAG_FIRMWARE, TAG_UNIT_ID = (_reg.CORE.tlv["describe"][k] for k in ("chip", "model", "firmware", "unit_id"))
|
|
343
346
|
# UART link rates tried by default (port_speed): fastest first. oep-client refuses a rate that breaks and drops back
|
|
344
347
|
# to the boot speed on repeated broken frames, so trying never ends slower than the boot speed; capture_at's
|
|
345
348
|
# fast=[] keeps the boot speed, fast=[RATE, ...] tries those.
|
|
346
349
|
FAST = [1_500_000, 921_600, 500_000]
|
|
347
|
-
PER_SESSION = 2 # candidates tried per open: each costs up to ~1 s, plus a wait when it fails (
|
|
350
|
+
PER_SESSION = 2 # candidates tried per open: each costs up to ~1 s, plus a wait when it fails (oep-if-link §3)
|
|
348
351
|
FLOWS = [("in", 0)] # a capture reads back: verify probe -> host only, at the link's largest in-flight n
|
|
349
352
|
|
|
350
353
|
|
oep_client/capture.py
CHANGED
|
@@ -51,12 +51,9 @@ MODE, RATE, SAMPLES, SEGMENTS, TRIGGER, PRETRIGGER, TRIGGER_OPTS, FORMAT, CARRIA
|
|
|
51
51
|
_CAP.tlv["configure"][k] for k in ("mode", "rate", "samples", "segments", "trigger", "pretrigger", "trigger_opts",
|
|
52
52
|
"format", "carriage", "block_max"))
|
|
53
53
|
ANSWER_FORMAT, ANSWER_BLOCK = _CAP.tlv["configure_answer"]["format"], _CAP.tlv["configure_answer"]["block"]
|
|
54
|
-
|
|
55
|
-
# §3.8 / §3.9, optional (a probe declares them in its describe): configure TLVs by name, sent critical
|
|
54
|
+
# §3.8, optional (a probe declares them in its describe): configure TLVs by name, sent critical
|
|
56
55
|
OPTION_TLV = {**_CAP.tlv["configure"], **_ANA.tlv["configure"]}
|
|
57
|
-
LOGIC_USED = {k: _CAP.tlv["configure_answer"][k] for k in ("
|
|
58
|
-
ANALOG_USED = {k: _ANA.tlv["configure_answer"][k] for k in ("coupling_used", "offset_used", "bandwidth_used",
|
|
59
|
-
"resolution_used", "oversample_used")}
|
|
56
|
+
LOGIC_USED = {k: _CAP.tlv["configure_answer"][k] for k in ("glitch_used", "timebase")}
|
|
60
57
|
NO_QUALIFIER = 0xFF # clock: qualifier_role none (§3.8)
|
|
61
58
|
STREAM_BUFFER = _CAP.tlv["configure_answer"]["stream_buffer"]
|
|
62
59
|
JUDGE_LATENCY = _CAP.tlv["configure_answer"]["judge_latency_ns"]
|
|
@@ -180,7 +177,6 @@ class Config:
|
|
|
180
177
|
raw_roles: list[int] = field(default_factory=list) # format 1: the raw channels' roles (as asked)
|
|
181
178
|
event_d: dict[int, int] = field(default_factory=dict) # format 1: events role -> D (as asked)
|
|
182
179
|
carriage: dict[int, tuple[int, int]] = field(default_factory=dict) # format 2: role -> (kind, D) (as asked)
|
|
183
|
-
format_name: str | None = None # format 0xFF: the named format in use
|
|
184
180
|
timebase: Fraction | None = None # format 3: the delta's tick rate (Hz)
|
|
185
181
|
external_clock: bool = False # sampled on a clock line (actual_rate 0 / 1, §3.8)
|
|
186
182
|
used: dict[str, dict[int, int] | int] = field(default_factory=dict) # §3.8's *_used: name -> group/role -> value
|
|
@@ -322,7 +318,7 @@ class Event:
|
|
|
322
318
|
serial: int | None = None
|
|
323
319
|
trigger_index: int | None = None
|
|
324
320
|
trigger_ns: int | None = None
|
|
325
|
-
record: int | None = None # triggered: the trigger record's serial (None: no record, §3.
|
|
321
|
+
record: int | None = None # triggered: the trigger record's serial (None: no record, §3.9)
|
|
326
322
|
sample: int | None = None # triggered: the original sample from the generation's start
|
|
327
323
|
uncertainty_ns: int | None = None # triggered: trigger_ns's +-ns
|
|
328
324
|
trigger_fn: int | None = None # a group's triggered: the track that judged it (0: force, timer)
|
|
@@ -368,7 +364,7 @@ def unpack_event(frame: bytes, group: bool = False) -> Event:
|
|
|
368
364
|
|
|
369
365
|
|
|
370
366
|
NO_RECORD = 0xFFFFFFFF
|
|
371
|
-
TRIGGER_RECORD_BYTES = 24 # serial u32, sample u64, trigger_ns u64, uncertainty_ns u32 (§3.
|
|
367
|
+
TRIGGER_RECORD_BYTES = 24 # serial u32, sample u64, trigger_ns u64, uncertainty_ns u32 (§3.9)
|
|
372
368
|
|
|
373
369
|
|
|
374
370
|
CAPTURE_DROPS = "io.github.open-embedded-probe.capture-drops"
|
|
@@ -422,7 +418,7 @@ class CaptureDrops:
|
|
|
422
418
|
@dataclass(frozen=True)
|
|
423
419
|
class TriggerOpts:
|
|
424
420
|
"""configure's trigger_opts (§3.3): `arm` 1 looks once the pretrigger is there (0: at once, a short segment);
|
|
425
|
-
`rearm` 1 a trigger for every segment (streaming: a record for every event, §2.1, §3.
|
|
421
|
+
`rearm` 1 a trigger for every segment (streaming: a record for every event, §2.1, §3.9); `when` 1 a level
|
|
426
422
|
already true at arming counts; `hysteresis` (analog cross, in ADC values) back that far before the next crossing.
|
|
427
423
|
The defaults are every probe's; the others as describe's trigger opts says."""
|
|
428
424
|
arm: int = 1
|
|
@@ -433,7 +429,7 @@ class TriggerOpts:
|
|
|
433
429
|
|
|
434
430
|
@dataclass(frozen=True)
|
|
435
431
|
class TriggerRecord:
|
|
436
|
-
"""A trigger record (§3.
|
|
432
|
+
"""A trigger record (§3.9): serial (from 0 in the generation), the original sample from the generation's start,
|
|
437
433
|
trigger_ns and its uncertainty."""
|
|
438
434
|
serial: int
|
|
439
435
|
sample: int
|
|
@@ -452,23 +448,18 @@ def take_events(link, fn: int) -> list[bytes]:
|
|
|
452
448
|
|
|
453
449
|
|
|
454
450
|
def _options(options: dict) -> bytes:
|
|
455
|
-
"""§3.8
|
|
456
|
-
forms = {"threshold": "<iI", "glitch_filter": "<I", "coupling": "<B", "offset": "<i", "bandwidth": "<I"}
|
|
451
|
+
"""§3.8's configure TLVs, critical (configure's `options`)."""
|
|
457
452
|
out = b""
|
|
458
453
|
for name, value in options.items():
|
|
459
|
-
tag = OPTION_TLV[name] |
|
|
460
|
-
if name
|
|
461
|
-
for
|
|
462
|
-
out += m.tlv(tag, bytes([
|
|
454
|
+
tag = OPTION_TLV[name] | CRITICAL
|
|
455
|
+
if name == "glitch_filter": # per group (group 0 only, §3.8)
|
|
456
|
+
for group, ns in sorted(value.items()):
|
|
457
|
+
out += m.tlv(tag, bytes([group]) + struct.pack("<I", ns))
|
|
463
458
|
elif name == "clock":
|
|
464
459
|
role, edge, qualifier, level = value
|
|
465
460
|
out += m.tlv(tag, bytes([role, edge, NO_QUALIFIER if qualifier is None else qualifier, level]))
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
elif name == "data_path":
|
|
469
|
-
out += m.tlv(tag, bytes(value))
|
|
470
|
-
else: # resolution, overflow: u8
|
|
471
|
-
out += m.tlv(tag, bytes([value]))
|
|
461
|
+
else:
|
|
462
|
+
raise ValueError(f"capture configure: no option {name!r} (oep-if-capture §3.8)")
|
|
472
463
|
return out
|
|
473
464
|
|
|
474
465
|
|
|
@@ -514,27 +505,12 @@ def _config(payload: bytes, analog: bool) -> Config:
|
|
|
514
505
|
c.skew_ns[role] = ns
|
|
515
506
|
elif tag == FRONTEND_USED and analog and len(v) == 2:
|
|
516
507
|
c.frontend[v[0]] = v[1]
|
|
517
|
-
elif tag == ANSWER_FORMAT_NAME:
|
|
518
|
-
c.format_name = c.format_name or v.decode("ascii", "replace")
|
|
519
508
|
elif not analog and tag == LOGIC_USED["timebase"] and len(v) == 8:
|
|
520
509
|
num, den = struct.unpack("<II", v)
|
|
521
510
|
c.timebase = Fraction(num, den) if den else None
|
|
522
|
-
elif not analog and tag == LOGIC_USED["threshold_used"] and len(v) == 9:
|
|
523
|
-
group, mv, hyst = struct.unpack("<BiI", v)
|
|
524
|
-
c.used.setdefault("threshold_used", {})[group] = (mv, hyst)
|
|
525
511
|
elif not analog and tag == LOGIC_USED["glitch_used"] and len(v) == 5:
|
|
526
512
|
group, ns = struct.unpack("<BI", v)
|
|
527
513
|
c.used.setdefault("glitch_used", {})[group] = ns
|
|
528
|
-
elif analog and tag in (ANALOG_USED["coupling_used"], ANALOG_USED["offset_used"], ANALOG_USED["bandwidth_used"]):
|
|
529
|
-
name = next(k for k, t in ANALOG_USED.items() if t == tag)
|
|
530
|
-
form = {"coupling_used": "<BB", "offset_used": "<Bi", "bandwidth_used": "<BI"}[name]
|
|
531
|
-
if len(v) == struct.calcsize(form):
|
|
532
|
-
role, value = struct.unpack(form, v)
|
|
533
|
-
c.used.setdefault(name, {})[role] = value
|
|
534
|
-
elif analog and tag == ANALOG_USED["resolution_used"] and len(v) == 1:
|
|
535
|
-
c.used["resolution_used"] = v[0]
|
|
536
|
-
elif analog and tag == ANALOG_USED["oversample_used"] and len(v) == 2:
|
|
537
|
-
c.used["oversample_used"] = struct.unpack("<H", v)[0]
|
|
538
514
|
elif tag == REFERENCE and analog and len(v) == 6:
|
|
539
515
|
source, mv, how = struct.unpack("<BIB", v)
|
|
540
516
|
# how: nominal False, measured True; an unknown value is shown as it is, not as nominal (core §2.4)
|
|
@@ -566,7 +542,7 @@ class LogicCapture(Interface):
|
|
|
566
542
|
frontends: dict[int, int] | None = None, trigger_opts: TriggerOpts | None = None,
|
|
567
543
|
data_format: int = 0, events: dict[int, int] | None = None, roles: list[int] | None = None,
|
|
568
544
|
block_max: int | None = None, carriage: dict[int, tuple[int, int]] | None = None,
|
|
569
|
-
|
|
545
|
+
options: dict | None = None) -> Config:
|
|
570
546
|
"""-> the probe's actual values. A value the probe cannot honour is refused: host.Unsupported, .tag = the TLV as
|
|
571
547
|
sent (§3.3). The TLVs go without the critical bit (§3.3). Read Config.samples / .segments: the probe rounds
|
|
572
548
|
samples down.
|
|
@@ -575,13 +551,11 @@ class LogicCapture(Interface):
|
|
|
575
551
|
carried as events (the others raw), `roles` the plan's roles (to name the raw channels), `block_max` the
|
|
576
552
|
longest block (§3.3's table: without the critical bit). Format 2 (analog blocks, §1.4): `carriage` role ->
|
|
577
553
|
(kind, D), kind a bit set of capture_blocks.PACKED / MINMAX / SUM (the roles not named packed). Format 4 (test
|
|
578
|
-
counter) needs nothing else.
|
|
554
|
+
counter) needs nothing else. The others go critical.
|
|
579
555
|
|
|
580
|
-
`options` (§3.8
|
|
581
|
-
"
|
|
582
|
-
|
|
583
|
-
mux, kind), "overflow" 0 / 1 (plain: §3.3's table). The answer's values in use: Config.used, by the answer TLV's
|
|
584
|
-
name.
|
|
556
|
+
`options` (§3.8, critical; a probe without one refuses it): "glitch_filter" group -> ns (group 0 only),
|
|
557
|
+
"clock" (role, edge, qualifier role or None, qualifier level). The answer's values in use: Config.used, by the
|
|
558
|
+
answer TLV's name.
|
|
585
559
|
|
|
586
560
|
§3.3's contract is checked before anything is sent (ValueError): `samples` is needed in one-shot and repeat
|
|
587
561
|
and not taken in streaming, `segments` is repeat's only, `pretrigger` needs a trigger (type other than 0) - a
|
|
@@ -617,10 +591,6 @@ class LogicCapture(Interface):
|
|
|
617
591
|
body += tlv(FRONTEND, bytes([role, fe]))
|
|
618
592
|
if data_format: # critical: a probe without it refuses (§3.3)
|
|
619
593
|
body += m.tlv(FORMAT | CRITICAL, bytes([data_format]))
|
|
620
|
-
if (data_format == DATA_FORMAT["named"]) != (format_name is not None):
|
|
621
|
-
raise ValueError("capture configure: format 0xFF goes with its format_name, and only it (oep-if-capture §3.3)")
|
|
622
|
-
if format_name is not None:
|
|
623
|
-
body += m.tlv(OPTION_TLV["format_name"] | CRITICAL, format_name.encode("ascii"))
|
|
624
594
|
body += _options(options or {})
|
|
625
595
|
if data_format == DATA_FORMAT["logic_blocks"] and roles is None:
|
|
626
596
|
raise ValueError("capture configure: format 1 needs the plan's roles (to name the raw channels)")
|
|
@@ -806,7 +776,7 @@ class LogicCapture(Interface):
|
|
|
806
776
|
return out, bool(more)
|
|
807
777
|
|
|
808
778
|
def triggers_page(self, from_serial: int = 0, generation: int | None = None) -> tuple[list[TriggerRecord], bool]:
|
|
809
|
-
"""One answer's trigger records (§3.
|
|
779
|
+
"""One answer's trigger records (§3.9) from `from_serial` on, paged as segments are. -> (records, more)."""
|
|
810
780
|
payload = struct.pack("<II", self._generation(generation), from_serial)
|
|
811
781
|
rd = m.Reader(self._call(self.TRIGGERS, payload, locked=False).payload)
|
|
812
782
|
more, count = rd.take("BB")
|
|
@@ -879,7 +849,7 @@ class LogicCapture(Interface):
|
|
|
879
849
|
unavailable (cause 6), so an old read never gets the next capture's bytes.
|
|
880
850
|
|
|
881
851
|
The reads need no lock and go without the session id, so a repeat after a broken reply is simply run again
|
|
882
|
-
(reads are not deduplicated, oep-core §5.
|
|
852
|
+
(reads are not deduplicated, oep-core §5.1). They do not extend the lease, though: a long read (64 KB over a
|
|
883
853
|
115200 bps UART probe takes seconds) sends a keepalive between batches, or the lease lapsed mid-read, the plan
|
|
884
854
|
went with it (core §9) and the capture read back nothing (2026-09-26, V003 jig)."""
|
|
885
855
|
g = self._generation(generation)
|
|
@@ -66,7 +66,7 @@ SERIAL_CASES = tuple(('CORE-SERIAL-' + kind.upper(), kind) for kind in
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
RECONNECT_CASES = tuple(('CORE-RECONNECT-' + kind.upper(), kind) for kind in
|
|
69
|
-
('session', 'replay', 'ended', 'lease'))
|
|
69
|
+
('session', 'replay', 'ended', 'lease', 'unread'))
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
TCP_CASES = tuple(('CORE-TCP-' + kind.upper(), kind) for kind in
|
|
@@ -75,6 +75,7 @@ TCP_CASES = tuple(('CORE-TCP-' + kind.upper(), kind) for kind in
|
|
|
75
75
|
|
|
76
76
|
TCP_PEER_CASES = tuple(('CORE-TCP-PEER-' + kind.upper(), kind) for kind in
|
|
77
77
|
('identity', 'lock', 'route', 'partial', 'close', 'oversized'))
|
|
78
|
+
ROUTE_PEER_CASES = tuple(('CORE-ROUTE-PEER-' + kind.upper(), kind) for kind in ('identity', 'lock', 'route'))
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
class NotApplicable(Exception):
|
|
@@ -121,7 +122,7 @@ CHECKER_SOURCES = ('__init__.py', 'core_smoke.py', 'session_smoke.py', 'interfac
|
|
|
121
122
|
'port_speed.py', 'port_speed_sample.py', 'port_speed_lifecycle.py', 'port_speed_execution.py', 'port_speed_execution_sample.py',
|
|
122
123
|
'host_tlv.py', 'host_tlv_sample.py', 'host_core.py', 'core_fixed_fields.py', 'probe_input.py', 'probe_input_sample.py', 'refusal_order.py', 'refusal_order_sample.py', 'restart.py', 'restart_sample.py', 'host_wait.py', 'host_wait_sample.py', 'host_deadline.py', 'host_deadline_sample.py', 'host_resend.py', 'host_resend_sample.py', 'tcp_recovery.py', 'tcp_recovery_sample.py', 'host_core_sample.py', 'allocator.py', 'allocator_sample.py', 'discovery.py', 'clock.py', 'clock_sample.py',
|
|
123
124
|
'notification_sequence.py', 'notification_sequence_sample.py', 'notification_retention.py', 'notification_retention_sample.py', 'serial.py', 'tcp.py',
|
|
124
|
-
'tcp_peers.py', 'usb.py', 'usb_cases.py',
|
|
125
|
+
'tcp_peers.py', 'usb.py', 'usb_cases.py', 'usb_hw.py', 'force_hw.py', 'notify_hw.py', 'release_hw.py',
|
|
125
126
|
'../pytest_conformance.py', 'resources.py', 'resource_sample.py',
|
|
126
127
|
'subscriptions.py', 'subscription_sample.py',
|
|
127
128
|
'data.py', 'data_sample.py',
|
|
@@ -144,7 +145,7 @@ def checker_sources_sha256():
|
|
|
144
145
|
for name in CHECKER_SOURCES}
|
|
145
146
|
|
|
146
147
|
|
|
147
|
-
REVIEWED_CORE_SHA256 = '
|
|
148
|
+
REVIEWED_CORE_SHA256 = '0408d232e728bb9692ab1c24cefa1fdbf109c35aeebd378034d86c0f4d19789a' # oep-spec docs/oep-core.ja.md
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
def require_reviewed_contract(registry, spec=None):
|
|
@@ -208,10 +209,12 @@ class Checks:
|
|
|
208
209
|
self.abort = False
|
|
209
210
|
self.wire = None
|
|
210
211
|
self.reopen = None
|
|
212
|
+
self.post = None # with reopen: write one framed request and read nothing (the answer is left behind)
|
|
211
213
|
self.tcp_wire = None
|
|
212
214
|
self.usb_wire = None
|
|
213
215
|
self.tcp_peer_open = None
|
|
214
216
|
self.tcp_reopen = None
|
|
217
|
+
self.route_peer = None # another route of the same probe: (open, Checks attribute, this route's wire)
|
|
215
218
|
|
|
216
219
|
def request(self, op, payload=b'', *, session=0, corr=None, fn=0):
|
|
217
220
|
automatic = corr is None
|
|
@@ -452,7 +455,17 @@ class Checks:
|
|
|
452
455
|
def reconnect(self, kind):
|
|
453
456
|
lease = 1000 if kind == 'lease' else 60000
|
|
454
457
|
with self.holding(lease) as sid:
|
|
455
|
-
|
|
458
|
+
if kind == 'unread':
|
|
459
|
+
# the answer is lost with the connection: the request goes on the old one, nothing read (core §5.1,
|
|
460
|
+
# transports §3: what was not yet sent on a closed route is dropped)
|
|
461
|
+
if self.post is None:
|
|
462
|
+
raise NotApplicable('no raw write on this route')
|
|
463
|
+
request, original = self.request(self.core['clock'], session=sid), None
|
|
464
|
+
self.post(request)
|
|
465
|
+
time.sleep(0.3)
|
|
466
|
+
posted = time.monotonic_ns()
|
|
467
|
+
else:
|
|
468
|
+
request, original = self.clock(sid)
|
|
456
469
|
if kind == 'ended':
|
|
457
470
|
tlvs(self.success(self.request(self.core['end'], session=sid)))
|
|
458
471
|
self.session = None
|
|
@@ -491,6 +504,19 @@ class Checks:
|
|
|
491
504
|
self.session = sid # clean up buggy resurrection
|
|
492
505
|
require(reply[3:5] == bytes((0, self.reasons['no_session'])),
|
|
493
506
|
'reconnect forgot ended session and allowed reopening')
|
|
507
|
+
elif kind == 'unread':
|
|
508
|
+
# the same request on the new route: the recorded answer or result_lost, never run again - a
|
|
509
|
+
# recorded clock answer is older than the time since it was posted, a run again is not
|
|
510
|
+
reply = self.exchange(request)
|
|
511
|
+
if reply[3:5] != bytes((0, self.reasons['result_lost'])):
|
|
512
|
+
require(reply[3:5] == b'\x01\0' and len(reply) >= 17, 'resend after the route closed: '
|
|
513
|
+
'neither the recorded answer nor result_lost')
|
|
514
|
+
then = int.from_bytes(reply[9:17], 'little')
|
|
515
|
+
waited = time.monotonic_ns() - posted
|
|
516
|
+
_, now = self.clock()
|
|
517
|
+
require(int.from_bytes(now[4:12], 'little') - then >= waited - 50_000_000,
|
|
518
|
+
'the request resent on the new route ran again (core §5.1)')
|
|
519
|
+
tlvs(self.success(self.request(self.core['keepalive'], session=sid)))
|
|
494
520
|
elif kind == 'lease':
|
|
495
521
|
time.sleep(0.6)
|
|
496
522
|
state = self.success(self.request(self.core['lock_state']))
|
|
@@ -987,10 +1013,16 @@ class Checks:
|
|
|
987
1013
|
for name, kind in RECONNECT_CASES:
|
|
988
1014
|
self.check(name, 'transports §3; core §5.1/6.1/9', lambda kind=kind: self.reconnect(kind))
|
|
989
1015
|
if self.usb_wire is not None:
|
|
990
|
-
from .usb_cases import USB_CASES, HID_CASES, run
|
|
991
|
-
cases = USB_CASES + (HID_CASES if self.usb_wire.kind == 'hid' else
|
|
1016
|
+
from .usb_cases import USB_CASES, BULK_CASES, HID_CASES, run
|
|
1017
|
+
cases = USB_CASES + (HID_CASES if self.usb_wire.kind == 'hid' else BULK_CASES)
|
|
992
1018
|
for name, kind in cases:
|
|
993
1019
|
self.check(name, 'transports §1/2/3; core §2.4', lambda kind=kind: run(self, kind))
|
|
1020
|
+
if self.route_peer is not None:
|
|
1021
|
+
from .tcp_peers import Peers
|
|
1022
|
+
open_peer, attr, own = self.route_peer
|
|
1023
|
+
routes = Peers(self, open_peer, attr, own)
|
|
1024
|
+
for name, kind in ROUTE_PEER_CASES:
|
|
1025
|
+
self.check(name, 'transports §3; core §6', lambda kind=kind: getattr(routes, kind)())
|
|
994
1026
|
if self.tcp_peer_open is not None:
|
|
995
1027
|
from .tcp_peers import Peers
|
|
996
1028
|
peers = Peers(self)
|
|
@@ -1065,6 +1097,8 @@ def main(argv=None):
|
|
|
1065
1097
|
wire.stream.close()
|
|
1066
1098
|
wire.stream = link.open_serial(args.address)
|
|
1067
1099
|
checks.reopen = reopen
|
|
1100
|
+
from .serial import frame as serial_frame
|
|
1101
|
+
checks.post = lambda message: wire.stream.write(serial_frame(message))
|
|
1068
1102
|
report.update(checks.run())
|
|
1069
1103
|
finally:
|
|
1070
1104
|
link._exclusive_off(wire.stream)
|
|
@@ -1075,6 +1109,13 @@ def main(argv=None):
|
|
|
1075
1109
|
try:
|
|
1076
1110
|
checks = Checks(wire.send, registry, args.unit)
|
|
1077
1111
|
checks.tcp_wire = wire
|
|
1112
|
+
from .tcp import frame as tcp_frame
|
|
1113
|
+
|
|
1114
|
+
def reopen(): # a new connection: the route moves (transports §3)
|
|
1115
|
+
wire.stream.close()
|
|
1116
|
+
wire.stream = TcpWire.open(args.address).stream
|
|
1117
|
+
checks.reopen = reopen
|
|
1118
|
+
checks.post = lambda message: wire.stream.sendall(tcp_frame(message))
|
|
1078
1119
|
if args.tcp_peer:
|
|
1079
1120
|
checks.tcp_peer_open = lambda: TcpWire.open(args.tcp_peer)
|
|
1080
1121
|
def reopen_tcp():
|
|
@@ -80,7 +80,7 @@ class AllocatorChecks(ResourceChecks):
|
|
|
80
80
|
if self.c.results[-1]['status']!='passed':self.c.abort=True
|
|
81
81
|
for name,case in [('BOUNDARY',self.boundary),('END',self.ended_boundary),('EXPIRY',lambda:self.ended_boundary(True)),
|
|
82
82
|
('FORCE',self.force_boundary),('REPLAY',self.replay_boundary),('REBOOT',self.reboot_boundary)]:
|
|
83
|
-
first=len(self.a.trace);self.c.check('CORE-ALLOCATOR-'+name,'core §5.
|
|
83
|
+
first=len(self.a.trace);self.c.check('CORE-ALLOCATOR-'+name,'core §5.1/6/9',case);self.c.results[-1]['adapter_trace']=self.a.trace[first:]
|
|
84
84
|
rows=self.c.results[start:]
|
|
85
85
|
return dict(status='passed' if all(x['status']=='passed' for x in rows) else 'failed',checks=rows,adapter_trace=self.a.trace,
|
|
86
86
|
observed=self.c.observed,full_conformance=False,scope='logical seeded u32 allocation boundary and session/reboot invariants',
|
|
@@ -17,7 +17,7 @@ def refused_stale(reply):
|
|
|
17
17
|
return reply[3:5]==b'\0\4' and refusal_carries('unavailable',reply[5:],b'\1\1\0\6')
|
|
18
18
|
from .host_deadline import reviewed_identity as core_identity
|
|
19
19
|
|
|
20
|
-
REVIEWED={'interfaces/oep-if-capture.ja.md':'
|
|
20
|
+
REVIEWED={'interfaces/oep-if-capture.ja.md':'786bf349dfe322cee7b0d13e9d250cee2d0778bef3590ae751b2e87b11bb2fd3',
|
|
21
21
|
'interfaces/oep-if-common.ja.md':'25fed0b1b8b7f246a8b71d02cea810249803b1c3c1e9c3df466c04f38687f70c'}
|
|
22
22
|
|
|
23
23
|
def reviewed_identity(path):
|
|
@@ -8,7 +8,7 @@ import struct
|
|
|
8
8
|
from . import ops, require, tlvs
|
|
9
9
|
from .. import conformance as _helpers
|
|
10
10
|
|
|
11
|
-
REVIEWED_CORE_SHA256 = '
|
|
11
|
+
REVIEWED_CORE_SHA256 = '0408d232e728bb9692ab1c24cefa1fdbf109c35aeebd378034d86c0f4d19789a' # oep-spec docs/oep-core.ja.md
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class CoreSmokeChecks:
|
oep_client/conformance/cut.py
CHANGED
|
@@ -78,6 +78,6 @@ class CutChecks(DebugChecks):
|
|
|
78
78
|
if self.c.results[-1]['status']!='passed':self.c.abort=True
|
|
79
79
|
cases=[('REQUEST',self.request_cut),('REQUEST-HALTED',lambda:self.request_cut(True)),('REQUEST-PARTIAL',lambda:self.request_cut(partial=True)),('CONSOLE',self.console_cut),('CONSOLE-HALTED',lambda:self.console_cut(True)),('RECOVERY',self.recovery),('ISOLATED',self.isolated),('REJECTED',self.rejected)]
|
|
80
80
|
for name,case in cases:
|
|
81
|
-
first=len(self.a.trace);self.c.check('OEP-CUT-'+name,'debug §2; common §1/2; core §5.
|
|
81
|
+
first=len(self.a.trace);self.c.check('OEP-CUT-'+name,'debug §2; common §1/2; core §5.1/9',case);self.c.results[-1]['adapter_trace']=self.a.trace[first:]
|
|
82
82
|
rows=self.c.results[start:]
|
|
83
83
|
return dict(status='passed' if all(r['status']=='passed' for r in rows) else 'failed',checks=rows,adapter_trace=self.a.trace,observed=self.c.observed,full_conformance=False,scope='logical response writer before request wire-cut; console line loss and dependent release',unchecked=['physical writer/pins and actual transport delivery','writer concurrency, lease expiry while blocked and alternate budgets','resync/TO/framing marks','poll/wait DMI failure injection and store-trigger writes','production Host integration'])
|
oep_client/conformance/data.py
CHANGED
|
@@ -301,7 +301,7 @@ class DataChecks(SubscriptionChecks):
|
|
|
301
301
|
super().run()
|
|
302
302
|
for name, method in DATA_CASES:
|
|
303
303
|
first = len(self.notifications)
|
|
304
|
-
self.c.check(name, 'core §
|
|
304
|
+
self.c.check(name, 'core §10.2/11.3', getattr(self, method))
|
|
305
305
|
self.c.results[-1]['notification_observations'] = self.notifications[first:]
|
|
306
306
|
rows = self.c.results[start:]
|
|
307
307
|
return {'status': 'passed' if all(row['status'] == 'passed' for row in rows) else 'failed',
|
|
@@ -250,7 +250,7 @@ class DependencyChecks:
|
|
|
250
250
|
cases += [('MARKS',self.marks),('MALFORMED',lambda:self.refusal(True)),('OWNER',self.refusal),('DETACH',self.detach),('BREAK',lambda:self.detach(True)),('REPLAY',self.replay),('DETACH-REPLAY',self.detach_replay),('SAME-SESSION',self.same_session)]
|
|
251
251
|
cases += [(kind,lambda kind=kind:self.ending(kind)) for kind in ('END','EXPIRY','FORCE')]+[('LOCKS',self.locks)]
|
|
252
252
|
for name,case in cases:
|
|
253
|
-
first=len(self.a.trace);self.c.check('IF-DEPENDENCY-'+name,'common §1/2; core §5.
|
|
253
|
+
first=len(self.a.trace);self.c.check('IF-DEPENDENCY-'+name,'common §1/2; core §5.1/9',case)
|
|
254
254
|
self.c.results[-1]['adapter_trace']=self.a.trace[first:]
|
|
255
255
|
rows=self.c.results[start:]
|
|
256
256
|
return {'status':'passed' if all(r['status']=='passed' for r in rows) else 'failed','full_conformance':False,
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""force releases the resources of the session it takes from (core §6.4, §9; debug-common §2), on a real probe.
|
|
2
|
+
|
|
3
|
+
A session S attaches to a target over a wire (a debug connection: a numbered resource), then a session T of another id
|
|
4
|
+
opens with force. Then: S's connection is gone from connections, a detach of its number is rejected no_resource, S's
|
|
5
|
+
own requests are not completed, and T's attach to the same pins gets a new, larger number (numbers are never re-used
|
|
6
|
+
within a boot). T detaches and ends. The wire, its pins and the speed are named by the user (the target's wiring); the
|
|
7
|
+
route is the probe's USB vendor bulk, opened as conformance.usb_hw opens it. Numbers come from the SPEC registry.
|
|
8
|
+
|
|
9
|
+
python -m oep_client.conformance.force_hw --unit UNIT --wire oep.wire.rvswd --swdio 14 --swclk 15 \\
|
|
10
|
+
--spec SPEC --out NEW.json --lock LOCK
|
|
11
|
+
"""
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import secrets
|
|
16
|
+
import struct
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from . import Checks, checker_sources_sha256, require, spec_identity, tlvs
|
|
21
|
+
from .usb import UsbWire
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def interface(registry, name):
|
|
25
|
+
spec = next(i for i in registry['interface'] if i['name'] == name)
|
|
26
|
+
return {o['name']: o['code'] for o in spec['op']}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def connections(check, fn, op):
|
|
30
|
+
"""The live connection numbers of the wire `fn` (lock-free, session 0; paged by first, debug §2.1)."""
|
|
31
|
+
first, found = 0, []
|
|
32
|
+
while True:
|
|
33
|
+
p = check.success(check.request(op, bytes((first,)), fn=fn))
|
|
34
|
+
require(len(p) >= 2, 'connections fixed part')
|
|
35
|
+
more, count = p[0], p[1]
|
|
36
|
+
at = 2
|
|
37
|
+
for _ in range(count):
|
|
38
|
+
require(len(p) >= at + 14, 'connections entry')
|
|
39
|
+
found.append(struct.unpack_from('<I', p, at)[0])
|
|
40
|
+
tid_len = p[at + 13]
|
|
41
|
+
at += 14 + tid_len
|
|
42
|
+
if not more or not count:
|
|
43
|
+
return found
|
|
44
|
+
first += count
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def run(check, *, wire_name, swdio, swclk, speed):
|
|
48
|
+
ops = interface(check.reg, wire_name)
|
|
49
|
+
entries = check.list_snapshot()
|
|
50
|
+
fn = next((e['fn'] for e in entries if e['name'] == wire_name), None)
|
|
51
|
+
require(fn is not None, f'{wire_name} is not listed')
|
|
52
|
+
attach_body = struct.pack('<BHHI', 0, swdio, swclk, speed) # method 0: run, do not halt (debug §1)
|
|
53
|
+
out = {'wire_fn': fn}
|
|
54
|
+
|
|
55
|
+
def open_session(force):
|
|
56
|
+
sid = secrets.randbelow(0xffffffff) + 1
|
|
57
|
+
check.corr = 0
|
|
58
|
+
p = check.success(check.request(check.core['open'], struct.pack('<IB', 60000, force), session=sid))
|
|
59
|
+
require(len(p) >= 8 and struct.unpack_from('<I', p, 4)[0] == check.boot, 'open boot')
|
|
60
|
+
check.session = sid
|
|
61
|
+
return sid
|
|
62
|
+
|
|
63
|
+
def attach(sid):
|
|
64
|
+
p = check.success(check.request(ops['attach'], attach_body, session=sid, fn=fn))
|
|
65
|
+
require(len(p) >= 13, 'attach fixed part')
|
|
66
|
+
return struct.unpack_from('<I', p)[0]
|
|
67
|
+
|
|
68
|
+
s = open_session(0)
|
|
69
|
+
first = attach(s)
|
|
70
|
+
out['s_connection'] = first
|
|
71
|
+
require(first in connections(check, fn, ops['connections']), 'the attach is not in connections')
|
|
72
|
+
t = open_session(1) # another id, force: takes the lock from S (core §6.4)
|
|
73
|
+
out['t_session_differs'] = t != s
|
|
74
|
+
live = connections(check, fn, ops['connections'])
|
|
75
|
+
out['after_force'] = live
|
|
76
|
+
require(first not in live, "force left S's connection open (core §9)")
|
|
77
|
+
check.rejected(check.request(ops['detach'], struct.pack('<I', first), session=t, fn=fn), 'no_resource')
|
|
78
|
+
saved, check.corr = check.corr, 1000 # S's own numbers, far from T's
|
|
79
|
+
reply = check.exchange(check.request(check.core['keepalive'], session=s))
|
|
80
|
+
check.corr = saved
|
|
81
|
+
out['s_keepalive'] = reply[3:5].hex()
|
|
82
|
+
require(reply[3] == check.reg['resolutions']['rejected'], 'S kept a session after the force')
|
|
83
|
+
second = attach(t)
|
|
84
|
+
out['t_connection'] = second
|
|
85
|
+
require(second != first and second > first, 'a connection number was re-used or went back (core §9)')
|
|
86
|
+
tlvs(check.success(check.request(ops['detach'], struct.pack('<I', second), session=t, fn=fn)))
|
|
87
|
+
require(second not in connections(check, fn, ops['connections']), 'detach left the connection')
|
|
88
|
+
tlvs(check.success(check.request(check.core['end'], session=t)))
|
|
89
|
+
check.session = None
|
|
90
|
+
return out
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main(argv=None):
|
|
94
|
+
from .. import __version__
|
|
95
|
+
from ..hardware import equipment_lock
|
|
96
|
+
from .usb_hw import BulkBackend, find_device
|
|
97
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
98
|
+
for name, env in [('unit', 'OEP_CONFORMANCE_UNIT_ID'), ('spec', 'OEP_CONFORMANCE_SPEC'),
|
|
99
|
+
('out', 'OEP_CONFORMANCE_OUT'), ('lock', 'OEP_HW_LOCK')]:
|
|
100
|
+
parser.add_argument('--' + name, default=os.environ.get(env))
|
|
101
|
+
parser.add_argument('--wire', required=True, help='the wire interface name, e.g. oep.wire.rvswd')
|
|
102
|
+
parser.add_argument('--swdio', type=int, required=True, help="the target's SWDIO / SWIO channel")
|
|
103
|
+
parser.add_argument('--swclk', type=int, default=0xFFFF, help="the target's SWCLK channel (0xFFFF: none)")
|
|
104
|
+
parser.add_argument('--speed', type=int, default=1_000_000, help='attach max_speed (Hz)')
|
|
105
|
+
args = parser.parse_args(argv)
|
|
106
|
+
if not all(getattr(args, name) for name in ('unit', 'spec', 'out', 'lock')):
|
|
107
|
+
parser.error('explicit unit, SPEC checkout, new output file and existing shared lock required')
|
|
108
|
+
registry, identity = spec_identity(args.spec)
|
|
109
|
+
report = {'status': 'failed', 'spec': identity, 'client_version': __version__,
|
|
110
|
+
'checker_sources_sha256': checker_sources_sha256(), 'expected_unit': args.unit,
|
|
111
|
+
'wire': args.wire, 'pins': [args.swdio, args.swclk], 'full_conformance': False,
|
|
112
|
+
'started_at': datetime.now(timezone.utc).isoformat()}
|
|
113
|
+
with Path(args.out).open('x') as artifact:
|
|
114
|
+
try:
|
|
115
|
+
with equipment_lock(args.lock):
|
|
116
|
+
dev = find_device(registry['usb']['project_vid'], registry['usb']['project_pid'], args.unit)
|
|
117
|
+
backend = BulkBackend(dev, registry['usb'])
|
|
118
|
+
try:
|
|
119
|
+
wire = UsbWire(backend, 'bulk', **backend.shape)
|
|
120
|
+
check = Checks(wire.send, registry, args.unit)
|
|
121
|
+
check.usb_wire = wire
|
|
122
|
+
check.confirm()
|
|
123
|
+
check.identity()
|
|
124
|
+
try:
|
|
125
|
+
report['observed'] = run(check, wire_name=args.wire, swdio=args.swdio, swclk=args.swclk,
|
|
126
|
+
speed=args.speed)
|
|
127
|
+
report['status'] = 'passed'
|
|
128
|
+
finally:
|
|
129
|
+
if check.session is not None: # a failed run: end what this run holds
|
|
130
|
+
try:
|
|
131
|
+
check.exchange(check.request(check.core['end'], session=check.session))
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
report['trace'] = check.trace
|
|
135
|
+
finally:
|
|
136
|
+
backend.close()
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
report.update(status='failed', error=f'{type(exc).__name__}: {exc}')
|
|
139
|
+
report['finished_at'] = datetime.now(timezone.utc).isoformat()
|
|
140
|
+
json.dump(report, artifact, ensure_ascii=False, indent=2, default=str)
|
|
141
|
+
artifact.write('\n')
|
|
142
|
+
print(f"{report['status']}: {args.out}" + (f" ({report.get('error')})" if report.get('error') else ''))
|
|
143
|
+
return int(report['status'] != 'passed')
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == '__main__':
|
|
147
|
+
raise SystemExit(main())
|
|
@@ -59,11 +59,11 @@ class GpioLifecycleChecks(GpioChecks):
|
|
|
59
59
|
specs={s['name']:s for s in self.c.reg['interface']};require(all(specs[n]['revision']==1 for n in self.a.selection.values()),'selected revision differs')
|
|
60
60
|
self.codes={fn:{r['name']:r['code'] for r in specs[name]['op']} for fn,name in self.a.selection.items()};self.assignment=specs['oep.probe.plan']['tlv']['plan_apply']['role_assignment']
|
|
61
61
|
self.c.confirm();self.c.identity();self.c.declarations();entries=self.c.list_snapshot();require(all(any(r['fn']==fn and r['name']==name and r['revision']==1 for r in entries) for fn,name in self.a.selection.items()),'selected fn identity differs');require(self.a.state()['clock_scope']=='explicit-logical-ms','explicit clock required')
|
|
62
|
-
self.c.check('CORE-GPIO-LIFECYCLE-CONTEXT','core §5.
|
|
62
|
+
self.c.check('CORE-GPIO-LIFECYCLE-CONTEXT','core §5.1/6/8/9; logical fixture',context)
|
|
63
63
|
if self.c.results[-1]['status']!='passed':self.c.abort=True
|
|
64
64
|
for kind in self.cases:
|
|
65
65
|
action=(lambda kind=kind:self.expiry(kind)) if kind.startswith('EXPIR') else self.ended_id if kind=='ENDED-ID' else self.same_id if kind=='SAME-ID-OPEN' else (lambda:self.force(False)) if kind=='FORCE' else (lambda:self.force(True)) if kind=='FORCE-OLD' else (lambda:self.renewed(False)) if kind=='KEEPALIVE' else (lambda:self.renewed(True)) if kind=='REJECTED-RENEWAL' else self.nonrenewal
|
|
66
66
|
def isolated(action=action):self.fresh();action()
|
|
67
|
-
first=len(self.a.trace);self.c.check('CORE-GPIO-LIFECYCLE-'+kind,'core §5.
|
|
67
|
+
first=len(self.a.trace);self.c.check('CORE-GPIO-LIFECYCLE-'+kind,'core §5.1/6/8/9; GPIO plan resources',isolated);self.c.results[-1]['adapter_trace']=self.a.trace[first:]
|
|
68
68
|
rows=self.c.results[start:]
|
|
69
69
|
return dict(status='passed' if all(r['status']=='passed' for r in rows) else 'failed',checks=rows,observed=self.c.observed,adapter_trace=self.a.trace,full_conformance=False,scope='core session ownership/expiry/replay with explicit logical GPIO plan resources',unchecked=['physical pin transition timing and persisted config idle/disable','deferred writer/force concurrency with these GPIO resources','other fixture plan role constraints/channel_group','production Host and other standard operation behaviors'])
|
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
import struct
|
|
5
5
|
from . import require,spec_identity
|
|
6
6
|
|
|
7
|
-
REVIEWED_CORE_SHA256='
|
|
7
|
+
REVIEWED_CORE_SHA256='0408d232e728bb9692ab1c24cefa1fdbf109c35aeebd378034d86c0f4d19789a'
|
|
8
8
|
CORE_OPS={'confirm':1,'list':2,'describe':3,'clock':4,'open':16,'end':17,'keepalive':18,'lock_state':19}
|
|
9
9
|
# the settings' ops (core §13.5 / §13.7, transports §7.3): their numbers reviewed, their results not read here
|
|
10
10
|
SETTINGS_OPS={'get':32,'set':33,'unset':34,'save':35,'state':36,'network':37}
|
|
@@ -9,8 +9,8 @@ from pathlib import Path
|
|
|
9
9
|
from . import require,spec_identity
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
REVIEWED={'docs/oep-core.ja.md':'
|
|
13
|
-
'docs/oep-transports.ja.md':'
|
|
12
|
+
REVIEWED={'docs/oep-core.ja.md':'0408d232e728bb9692ab1c24cefa1fdbf109c35aeebd378034d86c0f4d19789a',
|
|
13
|
+
'docs/oep-transports.ja.md':'2e99bf8039a3d8f5b55c80b66ac97ac40ddf7ad4711079177ffc947c5790103f'}
|
|
14
14
|
QUICK_MS=1000 # core_quick_op_max_ms
|
|
15
15
|
|
|
16
16
|
|
|
@@ -4,8 +4,7 @@ import struct
|
|
|
4
4
|
from .. import registry
|
|
5
5
|
|
|
6
6
|
# The request TLVs this example sends critical, by name (core §2.3: ones that must not be ignored; capture §3.3 / §3.8)
|
|
7
|
-
_CAPTURE_CRITICAL = {'format', '
|
|
8
|
-
'glitch_filter', 'coupling', 'offset', 'bandwidth', 'resolution', 'oversample'}
|
|
7
|
+
_CAPTURE_CRITICAL = {'format', 'carriage', 'trigger_opts', 'clock', 'glitch_filter'}
|
|
9
8
|
_POLICY = {
|
|
10
9
|
'scan': {'max_speed', 'idle_clock', 'targetsel'},
|
|
11
10
|
'attach': {'reset', 'idle_clock', 'targetsel'},
|