312 lines
11 KiB
Python
312 lines
11 KiB
Python
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"
|