LCD decompile
This commit is contained in:
311
h8536/lcd_driver.py
Normal file
311
h8536/lcd_driver.py
Normal file
@@ -0,0 +1,311 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Iterable, Mapping
|
||||
|
||||
from .formatting import h16
|
||||
from .model import Instruction
|
||||
|
||||
|
||||
LCD_STATUS_CONTROL = 0xF200
|
||||
LCD_DATA = 0xF201
|
||||
LCD_ADDRESSES = {
|
||||
LCD_STATUS_CONTROL: "lcd_status_control",
|
||||
LCD_DATA: "lcd_data",
|
||||
}
|
||||
ADDRESS_RE = re.compile(r"H'(?P<hex>[0-9A-Fa-f]{4})|0x(?P<c_hex>[0-9A-Fa-f]{4})")
|
||||
REGISTER_RE = re.compile(r"\b(?P<reg>R[0-7])\b")
|
||||
|
||||
|
||||
def analyze_lcd_driver(instructions: Mapping[int, Instruction] | Iterable[Instruction]) -> dict[str, object]:
|
||||
ordered = _instruction_sequence(instructions)
|
||||
by_address = {ins.address: ins for ins in ordered}
|
||||
accesses = [_access_for_instruction(ins) for ins in ordered]
|
||||
accesses = [access for access in accesses if access]
|
||||
instruction_metadata: dict[int, list[dict[str, object]]] = {}
|
||||
for access in accesses:
|
||||
instruction_metadata.setdefault(int(access["address"]), []).append(access)
|
||||
|
||||
polling_loops = _find_polling_loops(ordered, by_address)
|
||||
for loop in polling_loops:
|
||||
for address, role in (
|
||||
(int(loop["read_address"]), "lcd_busy_status_read"),
|
||||
(int(loop["test_address"]), "lcd_busy_flag_test"),
|
||||
(int(loop["branch_address"]), "lcd_busy_wait_branch"),
|
||||
):
|
||||
instruction_metadata.setdefault(address, []).append(
|
||||
{
|
||||
"address": address,
|
||||
"kind": role,
|
||||
"summary": loop["summary"],
|
||||
"loop_start": loop["read_address"],
|
||||
},
|
||||
)
|
||||
|
||||
routines = _routine_candidates(ordered, accesses, polling_loops)
|
||||
return {
|
||||
"addresses": [
|
||||
{"address": address, "name": name, "role": _address_role(address)}
|
||||
for address, name in LCD_ADDRESSES.items()
|
||||
],
|
||||
"accesses": accesses,
|
||||
"polling_loops": polling_loops,
|
||||
"routines": routines,
|
||||
"instructions": instruction_metadata,
|
||||
}
|
||||
|
||||
|
||||
def lcd_comment_for_instruction(analysis: Mapping[str, object] | None, address: int) -> str:
|
||||
metadata = lcd_metadata_for_instruction(analysis, address)
|
||||
if not metadata:
|
||||
return ""
|
||||
summaries = []
|
||||
for item in metadata:
|
||||
if isinstance(item, Mapping) and item.get("summary"):
|
||||
summary = str(item["summary"])
|
||||
if summary not in summaries:
|
||||
summaries.append(summary)
|
||||
return "; ".join(summaries)
|
||||
|
||||
|
||||
def lcd_metadata_for_instruction(
|
||||
analysis: Mapping[str, object] | None,
|
||||
address: int,
|
||||
) -> list[dict[str, object]]:
|
||||
if not analysis:
|
||||
return []
|
||||
instructions = analysis.get("instructions")
|
||||
if not isinstance(instructions, Mapping):
|
||||
return []
|
||||
metadata = instructions.get(address)
|
||||
return list(metadata) if isinstance(metadata, list) else []
|
||||
|
||||
|
||||
def _instruction_sequence(instructions: Mapping[int, Instruction] | Iterable[Instruction]) -> list[Instruction]:
|
||||
values = instructions.values() if isinstance(instructions, Mapping) else instructions
|
||||
return sorted(values, key=lambda ins: ins.address)
|
||||
|
||||
|
||||
def _access_for_instruction(ins: Instruction) -> dict[str, object] | None:
|
||||
address = _lcd_address_for_instruction(ins)
|
||||
if address is None:
|
||||
return None
|
||||
direction = _direction_for_instruction(ins, address)
|
||||
role = _access_role(ins, address, direction)
|
||||
register = _last_register(ins.operands)
|
||||
return {
|
||||
"address": ins.address,
|
||||
"instruction": ins.text,
|
||||
"lcd_address": address,
|
||||
"lcd_name": LCD_ADDRESSES[address],
|
||||
"direction": direction,
|
||||
"role": role,
|
||||
"register": register,
|
||||
"summary": _access_summary(address, direction, role),
|
||||
}
|
||||
|
||||
|
||||
def _lcd_address_for_instruction(ins: Instruction) -> int | None:
|
||||
for address in ins.references:
|
||||
if address in LCD_ADDRESSES:
|
||||
return address
|
||||
for match in ADDRESS_RE.finditer(ins.operands):
|
||||
value = int(match.group("hex") or match.group("c_hex"), 16)
|
||||
if value in LCD_ADDRESSES:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _direction_for_instruction(ins: Instruction, lcd_address: int) -> str:
|
||||
mnemonic = ins.mnemonic.upper()
|
||||
operands = [part.strip() for part in ins.operands.split(",")]
|
||||
if mnemonic.startswith("MOVFPE"):
|
||||
return "read"
|
||||
if mnemonic.startswith("MOVTPE"):
|
||||
return "write"
|
||||
if len(operands) >= 2:
|
||||
source_has_lcd = _operand_has_address(operands[0], lcd_address)
|
||||
dest_has_lcd = _operand_has_address(operands[-1], lcd_address)
|
||||
if source_has_lcd and not dest_has_lcd:
|
||||
return "read"
|
||||
if dest_has_lcd and not source_has_lcd:
|
||||
return "write"
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _operand_has_address(operand: str, address: int) -> bool:
|
||||
return f"H'{address:04X}" in operand.upper() or f"0X{address:04X}" in operand.upper()
|
||||
|
||||
|
||||
def _access_role(ins: Instruction, address: int, direction: str) -> str:
|
||||
if address == LCD_STATUS_CONTROL and direction == "read":
|
||||
return "lcd_status_read"
|
||||
if address == LCD_STATUS_CONTROL and direction == "write":
|
||||
return "lcd_command_or_address_write"
|
||||
if address == LCD_DATA and direction == "read":
|
||||
return "lcd_data_read"
|
||||
if address == LCD_DATA and direction == "write":
|
||||
return "lcd_data_write"
|
||||
return "lcd_access"
|
||||
|
||||
|
||||
def _access_summary(address: int, direction: str, role: str) -> str:
|
||||
if role == "lcd_status_read":
|
||||
return f"LCD status read from E-clock {h16(address)}"
|
||||
if role == "lcd_command_or_address_write":
|
||||
return f"LCD command/address write to E-clock {h16(address)}"
|
||||
if role == "lcd_data_read":
|
||||
return f"LCD data read from E-clock {h16(address)}"
|
||||
if role == "lcd_data_write":
|
||||
return f"LCD data write to E-clock {h16(address)}"
|
||||
return f"LCD {direction} at E-clock {h16(address)}"
|
||||
|
||||
|
||||
def _find_polling_loops(
|
||||
ordered: list[Instruction],
|
||||
by_address: Mapping[int, Instruction],
|
||||
) -> list[dict[str, object]]:
|
||||
loops: list[dict[str, object]] = []
|
||||
for index, ins in enumerate(ordered):
|
||||
access = _access_for_instruction(ins)
|
||||
if not access or access["role"] != "lcd_status_read":
|
||||
continue
|
||||
register = access.get("register")
|
||||
if not isinstance(register, str):
|
||||
continue
|
||||
test = _next_register_bit_test(ordered, index, register)
|
||||
if test is None:
|
||||
continue
|
||||
branch = _next_back_branch_to(ordered, by_address, ordered.index(test), ins.address, test.address)
|
||||
if branch is None:
|
||||
continue
|
||||
loops.append(
|
||||
{
|
||||
"read_address": ins.address,
|
||||
"test_address": test.address,
|
||||
"branch_address": branch.address,
|
||||
"register": register,
|
||||
"bit": 7,
|
||||
"summary": f"LCD busy-flag poll: read {h16(LCD_STATUS_CONTROL)}, test bit 7, branch until clear",
|
||||
},
|
||||
)
|
||||
return loops
|
||||
|
||||
|
||||
def _next_register_bit_test(ordered: list[Instruction], index: int, register: str) -> Instruction | None:
|
||||
for candidate in ordered[index + 1 : index + 5]:
|
||||
if not candidate.mnemonic.upper().startswith("BTST"):
|
||||
continue
|
||||
if "#7" not in candidate.operands:
|
||||
continue
|
||||
if re.search(rf"\b{re.escape(register)}\b", candidate.operands):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _next_back_branch_to(
|
||||
ordered: list[Instruction],
|
||||
by_address: Mapping[int, Instruction],
|
||||
index: int,
|
||||
read_address: int,
|
||||
test_address: int,
|
||||
) -> Instruction | None:
|
||||
_ = by_address
|
||||
for candidate in ordered[index + 1 : index + 5]:
|
||||
if candidate.kind != "branch":
|
||||
continue
|
||||
targets = [int(target) for target in candidate.targets]
|
||||
if read_address in targets or test_address in targets:
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _routine_candidates(
|
||||
ordered: list[Instruction],
|
||||
accesses: list[dict[str, object]],
|
||||
polling_loops: list[dict[str, object]],
|
||||
) -> list[dict[str, object]]:
|
||||
if not accesses:
|
||||
return []
|
||||
address_to_access = {int(access["address"]): access for access in accesses}
|
||||
loop_addresses = {int(loop["read_address"]) for loop in polling_loops}
|
||||
routines: dict[tuple[int, int], dict[str, object]] = {}
|
||||
for address in sorted(address_to_access):
|
||||
start, end = _routine_span(ordered, address)
|
||||
key = (start, end)
|
||||
routine = routines.setdefault(
|
||||
key,
|
||||
{
|
||||
"start": start,
|
||||
"end": end,
|
||||
"accesses": [],
|
||||
"roles": [],
|
||||
"role_hint": "lcd_access",
|
||||
},
|
||||
)
|
||||
routine["accesses"].append(address_to_access[address])
|
||||
for routine in routines.values():
|
||||
roles = sorted({str(access["role"]) for access in routine["accesses"]})
|
||||
routine["roles"] = roles
|
||||
routine["role_hint"] = _routine_role_hint(routine, loop_addresses)
|
||||
return sorted(routines.values(), key=lambda item: int(item["start"]))
|
||||
|
||||
|
||||
def _routine_span(ordered: list[Instruction], address: int) -> tuple[int, int]:
|
||||
addresses = [ins.address for ins in ordered]
|
||||
index = addresses.index(address)
|
||||
start = ordered[index].address
|
||||
cursor = index - 1
|
||||
while cursor >= 0:
|
||||
previous = ordered[cursor]
|
||||
if previous.kind in {"return", "rte"}:
|
||||
break
|
||||
if previous.address + max(previous.size, 1) != start:
|
||||
break
|
||||
start = previous.address
|
||||
cursor -= 1
|
||||
|
||||
end = ordered[index].address
|
||||
cursor = index
|
||||
while cursor < len(ordered):
|
||||
current = ordered[cursor]
|
||||
end = current.address
|
||||
if current.kind in {"return", "rte"}:
|
||||
break
|
||||
next_index = cursor + 1
|
||||
if next_index >= len(ordered):
|
||||
break
|
||||
next_address = ordered[next_index].address
|
||||
if current.address + max(current.size, 1) != next_address:
|
||||
break
|
||||
cursor = next_index
|
||||
return start, end
|
||||
|
||||
|
||||
def _routine_role_hint(routine: Mapping[str, object], loop_addresses: set[int]) -> str:
|
||||
access_addresses = {int(access["address"]) for access in routine.get("accesses", []) if isinstance(access, Mapping)}
|
||||
roles = {str(role) for role in routine.get("roles", [])}
|
||||
if access_addresses & loop_addresses and "lcd_data_write" in roles and "lcd_command_or_address_write" in roles:
|
||||
return "lcd_wait_and_transfer"
|
||||
if access_addresses & loop_addresses:
|
||||
return "lcd_wait_ready"
|
||||
if "lcd_data_write" in roles:
|
||||
return "lcd_write_data"
|
||||
if "lcd_command_or_address_write" in roles:
|
||||
return "lcd_write_command_or_address"
|
||||
if "lcd_data_read" in roles:
|
||||
return "lcd_read_data"
|
||||
return "lcd_access"
|
||||
|
||||
|
||||
def _last_register(operands: str) -> str | None:
|
||||
matches = list(REGISTER_RE.finditer(operands))
|
||||
return matches[-1].group("reg") if matches else None
|
||||
|
||||
|
||||
def _address_role(address: int) -> str:
|
||||
if address == LCD_STATUS_CONTROL:
|
||||
return "status/control register inferred from busy polling and command writes"
|
||||
if address == LCD_DATA:
|
||||
return "data register inferred from paired data reads/writes"
|
||||
return "unknown"
|
||||
Reference in New Issue
Block a user