ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
hum_regmap_scan.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""Find the register SYMBOLS first-party C claims exist, and where it claims it.
4
5Two claim sites matter, because the invented-register defects landed through
6both of them:
7
8* a **citation** that names a register -- ``/* HUM Ch 32.3 "EASCR : Security
9 Configuration" p 1697 */``. The symbol is right there in the quoted section
10 name, together with the chapter and page the author believed it lived on.
11* a **register window** in a ``*_regs.h`` -- the struct members and the
12 ``k_..._off_...`` offset enum. #498's ``ra8_ptp_regs.h`` declared thirteen
13 registers this way while its citations named no symbol at all, so a
14 citation-only scan would have missed it entirely.
15
16This module only reports what the source CLAIMS. Deciding whether the manual
17agrees is ``check_hum_register_map.py``'s job, and the manual's side comes
18from ``hum_regmap.py``.
19"""
20
21from __future__ import annotations
22
23import re
24from dataclasses import dataclass
25
26# A HUM citation, deliberately the same shape cite_check.py accepts so the two
27# gates cannot disagree about what a citation is.
28CITE_RE = re.compile(
29 r"""/\*\s*HUM\s+Ch\s+
30 (?P<chapter>\d{1,2})
31 (?:\.(?P<sub>\d{1,3}(?:\.\d{1,3})*))?
32 \s*
33 "(?P<section>[^"]*)"
34 \s*,?\s*
35 (?:(?:Table|Figure)\s+\d{1,3}(?:\.\d{1,3})*\s*)?
36 p\s+
37 (?P<start>\d{1,5})
38 (?:\s*-\s*(?P<end>\d{1,5}))?
39 """,
40 re.VERBOSE,
41)
42
43# The register name inside a citation's quoted section, e.g. the "EATASGL0" of
44# "EATASGL0 : TAS Gate Learn Register 0". A bare symbol with no description is
45# accepted too, because a long register name does not always fit the column
46# limit. Anything that is not shaped like an abbreviation (prose section names
47# such as "Error Interrupt Sources") yields no symbol and is skipped.
48CITE_SYMBOL_RE = re.compile(r"^\s*(?P<symbol>[A-Z][A-Z0-9_]{1,23}[a-z]?)\s*(?::|$)")
49
50# A register member of an MMIO window struct: "volatile uint32_t EATASGL0;" or
51# "volatile uint32_t EATMFSC[8];". Reserved padding is excluded by name.
52MEMBER_RE = re.compile(
53 r"^\s*volatile\s+u?int(?:8|16|32|64)_t\s+"
54 r"(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*(?:\‍[[^\‍]]*\‍])?\s*;"
55)
56
57# An offset enumerator: "k_ra8_etha_off_eatasgl0 = 0x03C0U,". The trailing
58# component after "_off_" is the register symbol in lower case.
59OFFSET_RE = re.compile(
60 r"^\s*k_(?:ra8_)?(?P<module>[a-z0-9_]+?)_off(?:set)?_(?P<symbol>[a-z0-9_]+)\s*=\s*"
61 r"(?P<value>0[xX][0-9A-Fa-f]+)[uUlL]*\s*,"
62)
63
64# Any mention of a HUM chapter, wherever it appears -- used only to work out
65# which chapters a header's register window belongs to. See cited_chapters().
66LOOSE_CHAPTER_RE = re.compile(r"HUM\s+Ch\s+(\d{1,2})\b")
67
68# Padding members, never registers.
69RESERVED_PREFIXES = ("reserved", "rsv", "pad", "dummy", "unused")
70
71
72@dataclass(frozen=True)
73class SymbolClaim:
74 """One place in first-party source that claims a register exists.
75
76 Attributes:
77 path: Repo-relative file the claim was made in.
78 line: 1-based line number of the claim.
79 symbol: Register symbol as the source spells it.
80 chapter: HUM chapter the claim is attributed to, or None when the
81 claim site carries no chapter of its own (a struct member relies
82 on the chapters its file cites).
83 page: Page the citation points at, or None for a struct claim.
84 page_end: End of a cited page range, or None.
85 offset: Byte offset the source declares, or None.
86 kind: ``cite``, ``member`` or ``offset``.
87 """
88
89 path: str
90 line: int
91 symbol: str
92 chapter: int | None
93 page: int | None
94 page_end: int | None
95 offset: int | None
96 kind: str
97
98
99def _line_of(text: str, position: int) -> int:
100 """Return the 1-based line number containing byte offset `position`."""
101 return text.count("\n", 0, position) + 1
102
103
104def cite_claims(path: str, text: str) -> list[SymbolClaim]:
105 """Every citation in `text` that names a register symbol."""
106 claims = []
107 for match in CITE_RE.finditer(text):
108 symbol_match = CITE_SYMBOL_RE.match(match.group("section"))
109 if symbol_match is None:
110 continue
111 start = int(match.group("start"))
112 end = int(match.group("end")) if match.group("end") else start
113 claims.append(
114 SymbolClaim(
115 path=path,
116 line=_line_of(text, match.start()),
117 symbol=symbol_match.group("symbol"),
118 chapter=int(match.group("chapter")),
119 page=start,
120 page_end=end,
121 offset=None,
122 kind="cite",
123 )
124 )
125 return claims
126
127
128def is_reserved(name: str) -> bool:
129 """True when a struct member is padding rather than a register."""
130 lowered = name.lower()
131 return any(lowered.startswith(prefix) for prefix in RESERVED_PREFIXES)
132
133
134def window_claims(path: str, text: str) -> list[SymbolClaim]:
135 """Every register-window struct member and offset enumerator in `text`."""
136 claims = []
137 for number, line in enumerate(text.split("\n"), start=1):
138 member = MEMBER_RE.match(line)
139 if member is not None:
140 name = member.group("name")
141 if not is_reserved(name) and name.isupper():
142 claims.append(SymbolClaim(path, number, name, None, None, None, None, "member"))
143 continue
144 offset = OFFSET_RE.match(line)
145 if offset is not None:
146 symbol = offset.group("symbol").upper()
147 claims.append(
148 SymbolClaim(
149 path,
150 number,
151 symbol,
152 None,
153 None,
154 None,
155 int(offset.group("value"), 16),
156 "offset",
157 )
158 )
159 return claims
160
161
162def cited_chapters(text: str) -> set[int]:
163 """Every HUM chapter number `text` names anywhere.
164
165 A register window is attributed to the chapters its own file names. That
166 is the attribution the #540 issue proposed, and it needs no new
167 annotation: a header that declares an MMIO window and names no chapter at
168 all cannot be checked, and is skipped rather than guessed at.
169
170 This uses the LOOSE pattern, not ``CITE_RE``, and deliberately so. A
171 header often states its chapter only in the ``@details`` prose of its
172 file-level Doxygen block -- ``ra8_rsip_regs_offsets.h`` says "HUM Ch 52
173 'Renesas Secure IP (RSIP-E50D)' p 3302" there and carries no standalone
174 ``/* HUM ... */`` comment at all. Requiring the strict form left that
175 whole header unattributed and therefore unchecked, which is the opposite
176 of what this gate is for. Attribution only has to know which chapters a
177 file is ABOUT; whether an individual citation is well-formed is
178 cite_check.py's question.
179 """
180 return {int(match.group(1)) for match in LOOSE_CHAPTER_RE.finditer(text)}