More decompiling work
This commit is contained in:
@@ -21,6 +21,14 @@ TX_CHECKSUM_ADDRESS = TX_FRAME_END
|
||||
|
||||
SEND_BUILDER_ADDRESS = 0xBA26
|
||||
SEND_BUILDER_LABEL = "loc_BA26"
|
||||
AUTONOMOUS_TX_REPORT_CALL = 0xBB43
|
||||
AUTONOMOUS_TX_REPORT_LABEL = "loc_BB43"
|
||||
MAIN_REPORT_GATE_ENTRY = 0x3FD3
|
||||
MAIN_REPORT_GATE_CALL = 0x3FEB
|
||||
SESSION_GATE_ENTRY = 0x3FEF
|
||||
QUEUE_REPORT_ENTRY = 0xBAF2
|
||||
RESEND_GATE_ENTRY = 0xBE9E
|
||||
PERIODIC_RESEND_ENTRY = 0xBED5
|
||||
INDEX_DECODER_ADDRESS = 0x622B
|
||||
INDEX_DECODER_LABEL = "loc_622B"
|
||||
CHECKSUM_SEED = 0x5A
|
||||
@@ -74,8 +82,29 @@ STATE_VARIABLES = {
|
||||
0xF9B5: "event_queue_write_or_pending_cursor_candidate",
|
||||
0xF9B9: "event_queue_base_or_current_slot_candidate",
|
||||
0xF9C0: "serial_tx_busy_timer_candidate",
|
||||
0xF9C6: "autonomous_report_period_timer_candidate",
|
||||
0xF9C8: "autonomous_report_resend_countdown_candidate",
|
||||
}
|
||||
|
||||
OBSERVED_TX_REPORT_OVERLAY = [
|
||||
{
|
||||
"logical_index": 0x0000,
|
||||
"name_candidate": "heartbeat_or_idle_report_candidate",
|
||||
"observed_frames_hex": ["00 00 00 00 80 DA"],
|
||||
"observed_period_ms_candidate": 700,
|
||||
},
|
||||
{
|
||||
"logical_index": 0x0015,
|
||||
"name_candidate": "call_button_report_candidate",
|
||||
"observed_frames_hex": ["00 00 15 80 00 CF", "00 00 15 00 00 4F"],
|
||||
},
|
||||
{
|
||||
"logical_index": 0x0007,
|
||||
"name_candidate": "camera_power_report_candidate",
|
||||
"observed_frames_hex": ["00 00 07 80 00 DD"],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
||||
"""Infer conservative SCI1 frame/command semantics from decompiler JSON."""
|
||||
@@ -100,6 +129,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
||||
"table_map_candidates": [],
|
||||
"state_variable_candidates": [],
|
||||
"retry_error_model": None,
|
||||
"gate_queue_model": None,
|
||||
"tx_report_model": None,
|
||||
"periodic_resend_model": None,
|
||||
"confidence": "low",
|
||||
"confidence_score": 0.0,
|
||||
"caveat": "No protocol semantics are emitted without both RX and TX serial reconstruction candidates.",
|
||||
@@ -113,6 +145,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
||||
logical_tables = _logical_table_map_candidates(ordered)
|
||||
state_variables = _state_variable_candidates(ordered)
|
||||
retry_error_model = _retry_error_model(ordered, responses)
|
||||
gate_queue_model = _gate_queue_model(ordered, commands)
|
||||
tx_report_model = _tx_report_model(ordered, responses)
|
||||
periodic_resend_model = _periodic_resend_model(ordered, responses)
|
||||
evidence = _top_level_evidence(ordered, dispatch, responses, rx_candidate, tx_candidate)
|
||||
|
||||
confidence_score = _confidence_score(frame_supported, dispatch, responses, commands)
|
||||
@@ -164,6 +199,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
||||
"rx_fields": _rx_field_candidates(ordered, dispatch),
|
||||
"response_builders": _response_builder_aliases(responses),
|
||||
"retry_error_model": retry_error_model,
|
||||
"gate_queue_model": gate_queue_model,
|
||||
"tx_report_model": tx_report_model,
|
||||
"periodic_resend_model": periodic_resend_model,
|
||||
"evidence": evidence,
|
||||
}
|
||||
return {
|
||||
@@ -181,6 +219,9 @@ def analyze_serial_semantics(payload: Mapping[str, Any]) -> JsonObject:
|
||||
"table_map_candidates": protocol["table_map_candidates"],
|
||||
"state_variable_candidates": protocol["state_variable_candidates"],
|
||||
"retry_error_model": protocol["retry_error_model"],
|
||||
"gate_queue_model": protocol["gate_queue_model"],
|
||||
"tx_report_model": protocol["tx_report_model"],
|
||||
"periodic_resend_model": protocol["periodic_resend_model"],
|
||||
"confidence": protocol["confidence"],
|
||||
"confidence_score": protocol["confidence_score"],
|
||||
"caveat": protocol["caveat"],
|
||||
@@ -1576,6 +1617,324 @@ def _retry_error_model(ordered: list[JsonObject], responses: list[JsonObject]) -
|
||||
}
|
||||
|
||||
|
||||
def _gate_queue_model(ordered: list[JsonObject], commands: list[JsonObject]) -> JsonObject | None:
|
||||
evidence = _dedupe_ints(
|
||||
_addresses_in_ranges(ordered, [(MAIN_REPORT_GATE_ENTRY, MAIN_REPORT_GATE_CALL)], MAIN_REPORT_GATE_ENTRY, MAIN_REPORT_GATE_CALL)
|
||||
+ _addresses_in_ranges(ordered, [(SESSION_GATE_ENTRY, 0x4007)], SESSION_GATE_ENTRY, 0x4007)
|
||||
+ _addresses_in_ranges(ordered, [(QUEUE_REPORT_ENTRY, AUTONOMOUS_TX_REPORT_CALL)], QUEUE_REPORT_ENTRY, AUTONOMOUS_TX_REPORT_CALL)
|
||||
+ _addresses_in_ranges(ordered, [(RESEND_GATE_ENTRY, PERIODIC_RESEND_ENTRY)], RESEND_GATE_ENTRY, PERIODIC_RESEND_ENTRY)
|
||||
)
|
||||
command_ack_values = [
|
||||
int(command["command_value"])
|
||||
for command in commands
|
||||
if command.get("command_value") in {0x05, 0x06}
|
||||
]
|
||||
if not evidence and not command_ack_values:
|
||||
return None
|
||||
|
||||
return {
|
||||
"kind": "serial_gate_queue_state_machine_candidate",
|
||||
"summary": (
|
||||
"Conservative model for autonomous report gating, queue cursor comparison, "
|
||||
"periodic resend, and RX/session side effects."
|
||||
),
|
||||
"predicates": [
|
||||
{
|
||||
"name": "main_loop_may_enter_report_builder",
|
||||
"entry_label": "loc_3FD3",
|
||||
"target_label": "loc_BAF2",
|
||||
"condition_candidate": (
|
||||
"FAA2 == 0 && F9C0 == 0 && ((FAA5.bit7 == 0) || (F9C3 == 0))"
|
||||
),
|
||||
"summary": "Main-loop report gate; session must be idle, TX busy timer clear, and RX gate open.",
|
||||
"state_addresses_hex": [_h16(0xFAA2), _h16(0xFAA5), _h16(0xF9C3), _h16(0xF9C0)],
|
||||
"evidence_addresses": _addresses_in_ranges(
|
||||
ordered,
|
||||
[(MAIN_REPORT_GATE_ENTRY, MAIN_REPORT_GATE_CALL)],
|
||||
MAIN_REPORT_GATE_ENTRY,
|
||||
MAIN_REPORT_GATE_CALL,
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "queue_has_pending_report",
|
||||
"entry_label": "loc_BAF2",
|
||||
"condition_candidate": "F9B5 != F9B0",
|
||||
"summary": "Queue/pending cursor gate; non-empty state stages through BB43 before loc_BA26.",
|
||||
"state_addresses_hex": [_h16(0xF9B5), _h16(0xF9B0)],
|
||||
"staging_path": ["loc_BAF2", "loc_BB43", "loc_BA26"],
|
||||
"evidence_addresses": _addresses_in_ranges(
|
||||
ordered,
|
||||
[(QUEUE_REPORT_ENTRY, AUTONOMOUS_TX_REPORT_CALL)],
|
||||
QUEUE_REPORT_ENTRY,
|
||||
AUTONOMOUS_TX_REPORT_CALL,
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "periodic_resend_may_fire",
|
||||
"entry_label": "loc_BE9E",
|
||||
"target_label": "loc_BED5",
|
||||
"condition_candidate": (
|
||||
"(FAA5 & FAA3 & 0x80) != 0 && F9C6 == 0 && F9C8 != 0 after countdown"
|
||||
),
|
||||
"summary": "Resend gate masks pending state with FAA5, checks F9C6/F9C8, then calls BA26 at BED5.",
|
||||
"state_addresses_hex": [_h16(0xFAA5), _h16(0xFAA3), _h16(0xF9C6), _h16(0xF9C8)],
|
||||
"evidence_addresses": _addresses_in_ranges(
|
||||
ordered,
|
||||
[(RESEND_GATE_ENTRY, PERIODIC_RESEND_ENTRY)],
|
||||
RESEND_GATE_ENTRY,
|
||||
PERIODIC_RESEND_ENTRY,
|
||||
),
|
||||
},
|
||||
],
|
||||
"session_effects": [
|
||||
{
|
||||
"name": "rx_completion_sets_session_timer",
|
||||
"summary": "RX completion sets F9C5 (observed reload H'14) after the sixth byte is captured.",
|
||||
"state_addresses_hex": [_h16(0xF9C5)],
|
||||
"evidence_addresses": _state_immediate_evidence(ordered, 0xF9C5, 0x14),
|
||||
},
|
||||
{
|
||||
"name": "session_timeout_clears_gate_and_queue",
|
||||
"entry_label": "loc_3FEF",
|
||||
"summary": "When F9C5 is clear, loc_3FEF clears F9B5/F9B0 and clears FAA5.bit7; when nonzero, it sets FAA5.bit7.",
|
||||
"state_addresses_hex": [_h16(0xF9C5), _h16(0xF9B5), _h16(0xF9B0), _h16(0xFAA5)],
|
||||
"evidence_addresses": _addresses_in_ranges(
|
||||
ordered,
|
||||
[(SESSION_GATE_ENTRY, 0x4007)],
|
||||
SESSION_GATE_ENTRY,
|
||||
0x4007,
|
||||
),
|
||||
},
|
||||
{
|
||||
"name": "host_ack_can_advance_queue",
|
||||
"summary": "Commands 0x05/0x06 are modeled as acknowledgement paths that can clear pending state or advance F9B5.",
|
||||
"command_values_hex": [_h16(value, width=2) for value in command_ack_values],
|
||||
"state_addresses_hex": [_h16(0xF9B5)],
|
||||
"evidence_addresses": _dedupe_ints(
|
||||
addr
|
||||
for command in commands
|
||||
if command.get("command_value") in {0x05, 0x06}
|
||||
for addr in command.get("evidence_addresses", [])
|
||||
if isinstance(addr, int)
|
||||
),
|
||||
},
|
||||
],
|
||||
"caveat": (
|
||||
"Many panel controls may require host/session traffic before reporting. Observed "
|
||||
"autonomous call/camera-power indexes are runtime/capture overlays, not ROM constants."
|
||||
),
|
||||
"confidence": "candidate-medium",
|
||||
"evidence_addresses": evidence,
|
||||
"evidence_addresses_hex": _hlist(evidence),
|
||||
}
|
||||
|
||||
|
||||
def _tx_report_model(ordered: list[JsonObject], responses: list[JsonObject]) -> JsonObject | None:
|
||||
report_responses = [
|
||||
response for response in responses
|
||||
if response.get("call_address") == AUTONOMOUS_TX_REPORT_CALL
|
||||
]
|
||||
if not report_responses:
|
||||
report_responses = [
|
||||
response for response in responses
|
||||
if _response_reads_current_value_table(response)
|
||||
and not _response_reads_rx_frame(response)
|
||||
]
|
||||
if not report_responses:
|
||||
return None
|
||||
|
||||
response_ids = [
|
||||
str(response["id"])
|
||||
for response in report_responses
|
||||
if isinstance(response.get("id"), str)
|
||||
]
|
||||
evidence = _dedupe_ints(
|
||||
addr
|
||||
for response in report_responses
|
||||
for addr in response.get("evidence_addresses", [])
|
||||
if isinstance(addr, int)
|
||||
)
|
||||
byte_roles = [
|
||||
{
|
||||
"offset": 0,
|
||||
"field_candidate": "encoded_logical_index_or_report_id_byte0",
|
||||
"source_candidate": "computed from candidate logical index/report id",
|
||||
},
|
||||
{
|
||||
"offset": 1,
|
||||
"field_candidate": "encoded_logical_index_or_report_id_byte1",
|
||||
"source_candidate": "computed from candidate logical index/report id",
|
||||
},
|
||||
{
|
||||
"offset": 2,
|
||||
"field_candidate": "encoded_logical_index_or_report_id_byte2",
|
||||
"source_candidate": "computed from candidate logical index/report id",
|
||||
},
|
||||
{
|
||||
"offset": 3,
|
||||
"field_candidate": "current_value_hi",
|
||||
"source_candidate": "current_value_table_candidate high byte",
|
||||
"table_candidate": "current_value_table_candidate",
|
||||
},
|
||||
{
|
||||
"offset": 4,
|
||||
"field_candidate": "current_value_lo",
|
||||
"source_candidate": "current_value_table_candidate low byte",
|
||||
"table_candidate": "current_value_table_candidate",
|
||||
},
|
||||
{
|
||||
"offset": 5,
|
||||
"field_candidate": "checksum",
|
||||
"source_candidate": "0x5A XOR TX[0..4]",
|
||||
},
|
||||
]
|
||||
return {
|
||||
"kind": "bb43_to_ba26_tx_report_model_candidate",
|
||||
"direction": "device_to_host_autonomous_report_candidate",
|
||||
"entry_label": AUTONOMOUS_TX_REPORT_LABEL,
|
||||
"entry_address": AUTONOMOUS_TX_REPORT_CALL,
|
||||
"entry_address_hex": _h16(AUTONOMOUS_TX_REPORT_CALL),
|
||||
"send_builder": SEND_BUILDER_LABEL,
|
||||
"send_builder_address": SEND_BUILDER_ADDRESS,
|
||||
"send_builder_address_hex": _h16(SEND_BUILDER_ADDRESS),
|
||||
"response_candidates": _dedupe_strings(response_ids),
|
||||
"summary": (
|
||||
"TX report bytes 0..2 are computed encoded logical index/report id bytes, "
|
||||
"bytes 3..4 come from current_value_table_candidate, and byte5 is the "
|
||||
"0x5A XOR checksum."
|
||||
),
|
||||
"byte_roles": byte_roles,
|
||||
"value_source_candidate": "current_value_table_candidate",
|
||||
"checksum_formula": "checksum = 0x5A ^ byte0 ^ byte1 ^ byte2 ^ byte3 ^ byte4",
|
||||
"observed_capture_overlay_candidates": OBSERVED_TX_REPORT_OVERLAY,
|
||||
"observed_autonomous_output_caveat": (
|
||||
"Real captures supplied so far show only heartbeat/idle, call, and camera-power "
|
||||
"autonomous TX frames. Other panel controls may require a host/device request or "
|
||||
"state transition before the firmware reports them."
|
||||
),
|
||||
"confidence": "candidate-medium",
|
||||
"caveat": (
|
||||
"This is a TX/report model for the BB43 -> BA26 path, separate from RX command "
|
||||
"dispatch. Observed report names are a capture overlay candidate only, not hard-coded "
|
||||
"source truth."
|
||||
),
|
||||
"evidence_addresses": evidence,
|
||||
"evidence_addresses_hex": _hlist(evidence),
|
||||
}
|
||||
|
||||
|
||||
def _periodic_resend_model(ordered: list[JsonObject], responses: list[JsonObject]) -> JsonObject | None:
|
||||
del responses
|
||||
period_evidence = _state_immediate_evidence(ordered, 0xF9C6, 0x01F4)
|
||||
countdown_evidence = _state_immediate_evidence(ordered, 0xF9C8, 0x14)
|
||||
pending_evidence = _state_immediate_evidence(ordered, 0xFAA3, 0x80)
|
||||
pending_evidence = _dedupe_ints(pending_evidence + _state_bit_evidence(ordered, 0xFAA3, 7))
|
||||
resend_evidence = [
|
||||
int(ins["address"])
|
||||
for ins in ordered
|
||||
if PERIODIC_RESEND_ENTRY <= int(ins.get("address", -1)) <= SERIAL_HANDLER_END
|
||||
]
|
||||
resend_send_evidence = [
|
||||
int(ins["address"])
|
||||
for ins in ordered
|
||||
if PERIODIC_RESEND_ENTRY <= int(ins.get("address", -1)) <= SERIAL_HANDLER_END
|
||||
and (_is_send_builder_call(ins) or _has_ref_in_range(ins, TX_STAGING_START, TX_FRAME_END))
|
||||
]
|
||||
evidence = _dedupe_ints(period_evidence + countdown_evidence + pending_evidence + resend_send_evidence)
|
||||
if not evidence and not resend_evidence:
|
||||
return None
|
||||
return {
|
||||
"kind": "autonomous_periodic_resend_model_candidate",
|
||||
"period_timer": {
|
||||
"address": 0xF9C6,
|
||||
"address_hex": _h16(0xF9C6),
|
||||
"reload_value_candidate": 0x01F4,
|
||||
"reload_value_hex": _h16(0x01F4),
|
||||
"summary": "Candidate periodic report/heartbeat timer reload.",
|
||||
"evidence_addresses": period_evidence,
|
||||
"evidence_addresses_hex": _hlist(period_evidence),
|
||||
},
|
||||
"resend_countdown": {
|
||||
"address": 0xF9C8,
|
||||
"address_hex": _h16(0xF9C8),
|
||||
"reload_value_candidate": 0x14,
|
||||
"reload_value_hex": _h16(0x14, width=2),
|
||||
"summary": "Candidate periodic resend countdown/retry spacing value.",
|
||||
"evidence_addresses": countdown_evidence,
|
||||
"evidence_addresses_hex": _hlist(countdown_evidence),
|
||||
},
|
||||
"pending_mask": {
|
||||
"address": 0xFAA3,
|
||||
"address_hex": _h16(0xFAA3),
|
||||
"mask_candidate": 0x80,
|
||||
"mask_hex": _h16(0x80, width=2),
|
||||
"summary": "Candidate bit/mask that marks an autonomous report pending.",
|
||||
"evidence_addresses": pending_evidence,
|
||||
"evidence_addresses_hex": _hlist(pending_evidence),
|
||||
},
|
||||
"resend_path": {
|
||||
"entry_label": "loc_BED5",
|
||||
"entry_address": PERIODIC_RESEND_ENTRY,
|
||||
"entry_address_hex": _h16(PERIODIC_RESEND_ENTRY),
|
||||
"summary": "Candidate periodic resend path feeding the TX staging/send-builder flow.",
|
||||
"evidence_addresses": _dedupe_ints(resend_send_evidence or resend_evidence),
|
||||
"evidence_addresses_hex": _hlist(resend_send_evidence or resend_evidence),
|
||||
},
|
||||
"evidence_addresses": evidence,
|
||||
"evidence_addresses_hex": _hlist(evidence),
|
||||
"confidence": "candidate-medium" if evidence else "candidate-low",
|
||||
"caveat": (
|
||||
"Timer and resend roles are inferred from constants/state references around F9C6, "
|
||||
"F9C8, FAA3, and loc_BED5; exact scheduling units remain candidate phrasing."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _response_reads_current_value_table(response: Mapping[str, Any]) -> bool:
|
||||
schema = response.get("schema")
|
||||
if not isinstance(schema, Mapping):
|
||||
return False
|
||||
return any(
|
||||
isinstance(item, Mapping)
|
||||
and isinstance(item.get("source"), Mapping)
|
||||
and item["source"].get("kind") == "table"
|
||||
and item["source"].get("name_candidate") == "current_value_table_candidate"
|
||||
for item in schema.get("bytes", [])
|
||||
)
|
||||
|
||||
|
||||
def _response_reads_rx_frame(response: Mapping[str, Any]) -> bool:
|
||||
schema = response.get("schema")
|
||||
if not isinstance(schema, Mapping):
|
||||
return False
|
||||
return any(
|
||||
isinstance(item, Mapping)
|
||||
and isinstance(item.get("source"), Mapping)
|
||||
and item["source"].get("kind") == "rx_frame_byte"
|
||||
for item in schema.get("bytes", [])
|
||||
)
|
||||
|
||||
|
||||
def _state_immediate_evidence(ordered: list[JsonObject], state_address: int, value: int) -> list[int]:
|
||||
evidence = []
|
||||
for ins in ordered:
|
||||
if not _has_ref_in_range(ins, state_address, state_address):
|
||||
continue
|
||||
source, _destination = _source_destination_operands(str(ins.get("operands", "")))
|
||||
if _parse_immediate(source) == value:
|
||||
evidence.append(int(ins["address"]))
|
||||
return _dedupe_ints(evidence)
|
||||
|
||||
|
||||
def _state_bit_evidence(ordered: list[JsonObject], state_address: int, bit: int) -> list[int]:
|
||||
return _dedupe_ints(
|
||||
int(ins["address"])
|
||||
for ins in ordered
|
||||
if _has_ref_in_range(ins, state_address, state_address)
|
||||
and _bit_number_from_instruction(ins) == bit
|
||||
)
|
||||
|
||||
|
||||
def _send_builder_candidate(
|
||||
ordered: list[JsonObject],
|
||||
responses: list[JsonObject],
|
||||
@@ -1687,6 +2046,28 @@ def _top_level_evidence(
|
||||
"response_count": len(responses),
|
||||
}
|
||||
)
|
||||
tx_report_responses = [
|
||||
response for response in responses
|
||||
if response.get("call_address") == AUTONOMOUS_TX_REPORT_CALL
|
||||
]
|
||||
if tx_report_responses:
|
||||
addresses = _dedupe_ints(
|
||||
addr
|
||||
for response in tx_report_responses
|
||||
for addr in response.get("evidence_addresses", [])
|
||||
if isinstance(addr, int)
|
||||
)
|
||||
evidence.append(
|
||||
{
|
||||
"kind": "bb43_autonomous_tx_report_path",
|
||||
"summary": (
|
||||
"BB43 stages a candidate device-to-host report before loc_BA26; this is "
|
||||
"separate from RX command dispatch."
|
||||
),
|
||||
"addresses": addresses,
|
||||
"addresses_hex": _hlist(addresses),
|
||||
}
|
||||
)
|
||||
rx_payload_reads = [
|
||||
int(ins["address"])
|
||||
for ins in ordered
|
||||
|
||||
Reference in New Issue
Block a user