Skip to content

Commit 293b9ab

Browse files
committed
[ot] scripts/opentitan: otptool: improve code skeleton generation
add basic support for baremetal test code generation Signed-off-by: Emmanuel Blot <[email protected]>
1 parent 4e8699c commit 293b9ab

File tree

4 files changed

+242
-51
lines changed

4 files changed

+242
-51
lines changed

docs/opentitan/otptool.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o FILE] [-r RAW]
1313
[-G PART] [--change PART:FIELD=VALUE] [--empty PARTITION]
1414
[--erase PART:FIELD] [--clear-bit CLEAR_BIT]
1515
[--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT]
16-
[--write ADDR/HEXBYTES] [--patch-token NAME=VALUE] [-v] [-d]
16+
[--write ADDR/HEXBYTES] [--patch-token NAME=VALUE]
17+
[--out-kind {qemu,bmtest}] [-v] [-d]
1718
1819
QEMU OT tool to manage OTP files.
1920
@@ -47,7 +48,7 @@ Commands:
4748
-D, --digest check the OTP HW partition digest
4849
-U, --update update RAW file after ECC recovery or bit changes
4950
-g, --generate {LCVAL,LCTPL,PARTS,REGS}
50-
generate C code, see doc for options
51+
generate code, see doc for options
5152
-F, --fix-ecc rebuild ECC
5253
-G, --fix-digest PART
5354
rebuild HW digest
@@ -65,6 +66,9 @@ Commands:
6566
write bytes at specified location
6667
--patch-token NAME=VALUE
6768
change a LC hashed token, using Rust file
69+
--out-kind {qemu,bmtest}
70+
select output format for code generation (default:
71+
qemu)
6872
6973
Extras:
7074
-v, --verbose increase verbosity
@@ -129,9 +133,9 @@ Fuse RAW images only use the v1 type.
129133
* `-G` can be used to (re)build the HW digest of a partition after altering one or more of its
130134
fields, see `--change` option.
131135

132-
* `-g` can be used to generate C code for QEMU, from OTP and LifeCycle known definitions. See the
136+
* `-g` can be used to generate skeleton files, from OTP and LifeCycle known definitions. See the
133137
[Generation](#generation) section for details. See option `-o` to specify the path to the file to
134-
generate
138+
generate. See also the `--out-kind` option for output formats.
135139

136140
* `-i` specify the initialization vector for the Present scrambler used for partition digests.
137141
This value is "usually" found within the `hw/ip/otp_ctrl/rtl/otp_ctrl_part_pkg.sv` OT file,
@@ -206,6 +210,9 @@ Fuse RAW images only use the v1 type.
206210

207211
* `--no-version` disable OTP image version reporting when `-s` is used.
208212

213+
* `--out-kind` define the output format for code generation for the `-g` option. Note that only a
214+
subset of the generation types are available in some output kinds.
215+
209216
* `--patch-token` patch a Life Cycle hashed token. This feature is primary aimed at testing the
210217
Life Cycle controller. With this option, the partition to update is automatically found using
211218
the token `NAME`. If the partition containing the token to update is already locked, its digest is

python/qemu/ot/otp/descriptor.py

Lines changed: 200 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,37 @@
77
"""
88

99
from logging import getLogger
10-
from typing import TYPE_CHECKING, TextIO
10+
from typing import NamedTuple, TYPE_CHECKING, TextIO
11+
12+
from ..util.misc import redent
13+
from .partition import OtpPartition
1114

1215
if TYPE_CHECKING:
1316
from .map import OtpMap
1417

1518

19+
class OtpSlotDescriptor(NamedTuple):
20+
"""A location descriptor in OTP, either a whole partition or an item.
21+
22+
It has no other purpose than storing intermediate info as a container.
23+
"""
24+
25+
name: str
26+
"""Name of the slot."""
27+
28+
offset: str
29+
"""Offset in bytes."""
30+
31+
size: str
32+
"""Size in bytes."""
33+
34+
gen: bool = False
35+
"""Whether this slot is generated by the script or defined."""
36+
37+
part: bool = False
38+
"""Whether the slot defines the whole partition or a single item."""
39+
40+
1641
class OtpPartitionDesc:
1742
"""OTP Partition descriptor generator."""
1843

@@ -39,9 +64,18 @@ def __init__(self, otpmap: 'OtpMap'):
3964
self._log = getLogger('otp.partdesc')
4065
self._otpmap = otpmap
4166

42-
def save(self, hjname: str, scriptname: str, cfp: TextIO) -> None:
43-
"""Generate a C file with a static description for the partitions."""
67+
def save(self, kind: str, hjname: str, scriptname: str, cfp: TextIO) \
68+
-> None:
69+
"""Generate a source file with a static description for the partitions.
70+
71+
:param kind: kind of generation output
72+
:param hjname: the name of the input HJSON configuration file
73+
:param scriptname: the name of the script that generates this output
74+
:param cfp: the output text stream
75+
"""
4476
# pylint: disable=f-string-without-interpolation
77+
if kind != 'qemu':
78+
raise NotImplementedError(f'No support for {kind}')
4579
attrs = {n: getattr(self, f'_convert_to_{k}') if k else lambda x: x
4680
for n, k in self.ATTRS.items() if k is not None}
4781
print(f'/* Generated from {hjname} with {scriptname} */', file=cfp)
@@ -126,48 +160,190 @@ def __init__(self, otpmap: 'OtpMap'):
126160
self._log = getLogger('otp.reg')
127161
self._otpmap = otpmap
128162

129-
def save(self, hjname: str, scriptname: str, cfp: TextIO) -> None:
130-
"""Generate a C file with register definition for the partitions."""
131-
reg_offsets = []
132-
reg_sizes = []
133-
part_names = []
163+
def save(self, kind: str, hjname: str, scriptname: str, cfp: TextIO) \
164+
-> None:
165+
"""Generate a source file with register definition for the partitions.
166+
167+
:param kind: kind of generation output
168+
:param hjname: the name of the input HJSON configuration file
169+
:param scriptname: the name of the script that generates this output
170+
:param cfp: the output text stream
171+
"""
172+
try:
173+
save = getattr(self, f'_save_{kind.lower()}')
174+
except AttributeError as exc:
175+
raise NotImplementedError(f'No support for {kind}') from exc
176+
slots: list[OtpSlotDescriptor] = []
134177
for part in self._otpmap.enumerate_partitions():
135-
part_names.append(f'OTP_PART_{part.name}')
136178
offset = part.offset
137-
reg_sizes.append((f'{part.name}_SIZE', part.size))
179+
slots.append(OtpSlotDescriptor(part.name, offset, part.size, True,
180+
True))
138181
for itname, itdict in part.items.items():
139182
size = itdict['size']
140183
if not itname.startswith(f'{part.name}_'):
141184
name = f'{part.name}_{itname}'.upper()
142185
else:
143186
name = itname
144-
reg_offsets.append((name, offset))
145-
reg_sizes.append((f'{name}_SIZE', size))
187+
slots.append(OtpSlotDescriptor(name, offset, size))
146188
offset += size
189+
zeroizable = getattr(part, 'zeroizable', False)
190+
digest = any(getattr(part, f'{k}w_digest', False) for k in 'sh')
191+
if digest:
192+
offset = part.offset + part.size - OtpPartition.DIGEST_SIZE
193+
if zeroizable:
194+
offset -= OtpPartition.ZER_SIZE
195+
slots.append(OtpSlotDescriptor(f'{part.name}_DIGEST', offset,
196+
OtpPartition.DIGEST_SIZE, True))
197+
if zeroizable:
198+
offset = part.offset + part.size - OtpPartition.DIGEST_SIZE
199+
slots.append(OtpSlotDescriptor(f'{part.name}_ZER', offset,
200+
OtpPartition.ZER_SIZE, True))
201+
202+
save(hjname, scriptname, cfp, slots)
203+
204+
def _save_qemu(self, hjname: str, scriptname: str, cfp: TextIO,
205+
slots: list[OtpSlotDescriptor]) -> None:
147206
print(f'/* Generated from {hjname} with {scriptname} */')
148207
print(file=cfp)
149-
print('/* clang-format off */', file=cfp)
150-
for reg, off in reg_offsets:
151-
print(f'REG32({reg}, {off}u)', file=cfp)
208+
for slot in slots:
209+
if slot.part:
210+
continue
211+
print(f'REG32({slot.name}, {slot.offset}u)', file=cfp)
152212
print(file=cfp)
153-
regwidth = max(len(r[0]) for r in reg_sizes)
154-
for reg, size in reg_sizes:
155-
print(f'#define {reg:{regwidth}s} {size}u', file=cfp)
213+
214+
regwidth = max(len(s.name) for s in slots)
215+
regwidth += len('_SIZE')
216+
for slot in slots:
217+
if slot.gen and not slot.part:
218+
continue
219+
name = f'{slot.name}_SIZE'
220+
print(f'#define {name:{regwidth}s} {slot.size}u', file=cfp)
156221
print(file=cfp)
222+
223+
part_names = [slot.name for slot in slots if slot.part]
157224
pcount = len(part_names)
225+
part_names = [f'OTP_PART_{pn}' for pn in part_names]
158226
part_names.extend((
159-
'_OTP_PART_COUNT',
160-
'OTP_ENTRY_DAI = _OTP_PART_COUNT',
161-
'OTP_ENTRY_KDI',
162-
'_OTP_ENTRY_COUNT'))
227+
'OTP_PART_COUNT',
228+
'OTP_ENTRY_DAI = OTP_PART_COUNT, '
229+
'/* Fake partitions for error (...) */',
230+
'OTP_ENTRY_KDI, /* Key derivation issue, not really OTP */',
231+
'OTP_ENTRY_COUNT'))
163232
print('typedef enum {', file=cfp)
164233
for pname in part_names:
165-
print(f' {pname},', file=cfp)
234+
print(f' {pname}{"," if "," not in pname else ""}', file=cfp)
166235
print('} OtOTPPartitionType;', file=cfp)
167236
print(file=cfp)
237+
168238
print('static const char *PART_NAMES[] = {', file=cfp)
239+
print(' /* clang-format off */', file=cfp)
169240
for pname in part_names[:pcount]:
170241
print(f' OTP_NAME_ENTRY({pname}),', file=cfp)
242+
print(' /* fake partitions */', file=cfp)
243+
print(' OTP_NAME_ENTRY(OTP_ENTRY_DAI),', file=cfp)
244+
print(' OTP_NAME_ENTRY(OTP_ENTRY_KDI),', file=cfp)
245+
print(' /* clang-format on */', file=cfp)
171246
print('};', file=cfp)
172-
print('/* clang-format on */', file=cfp)
247+
print(file=cfp)
248+
249+
cases: list[str] = []
250+
for slot in slots:
251+
if slot.part:
252+
continue
253+
if slot.gen:
254+
cases.append(f'CASE_WIDE({slot.name});')
255+
elif slot.size > 4:
256+
cases.append(f'CASE_RANGE({slot.name});')
257+
elif slot.size == 1:
258+
cases.append(f'CASE_BYTE({slot.name});')
259+
elif slot.size < 4:
260+
cases.append(f'CASE_SUB({slot.name}, {slot.size}u);')
261+
else:
262+
cases.append(f'CASE_REG({slot.name});')
263+
264+
code = '''
265+
static const char *ot_otp_swcfg_reg_name(unsigned swreg)
266+
{
267+
#define CASE_BYTE(_reg_) \\
268+
case A_##_reg_: \\
269+
return stringify(_reg_)
270+
#define CASE_SUB(_reg_, _sz_) \\
271+
case A_##_reg_...(A_##_reg_ + (_sz_)): \\
272+
return stringify(_reg_)
273+
#define CASE_REG(_reg_) \\
274+
case A_##_reg_...(A_##_reg_ + 3u): \\
275+
return stringify(_reg_)
276+
#define CASE_WIDE(_reg_) \\
277+
case A_##_reg_...(A_##_reg_ + 7u): \\
278+
return stringify(_reg_)
279+
#define CASE_RANGE(_reg_) \\
280+
case A_##_reg_...(A_##_reg_ + (_reg_##_SIZE) - 1u): \\
281+
return stringify(_reg_)
282+
283+
switch (swreg) {
284+
_CASES_
285+
default:
286+
return "<?>";
287+
}
288+
289+
#undef CASE_BYTE
290+
#undef CASE_SUB
291+
#undef CASE_REG
292+
#undef CASE_RANGE
293+
#undef CASE_DIGEST
294+
}
295+
'''
296+
code = redent(code)
297+
code = code.replace('_CASES_', '\n '.join(cases))
298+
print(redent(code), '', file=cfp)
299+
300+
def _save_bmtest(self, hjname: str, scriptname: str, cfp: TextIO,
301+
slots: list[OtpSlotDescriptor]) -> None:
302+
# pylint: disable=unused-argument
303+
print(f'// Generated from {hjname} with {scriptname}', file=cfp)
304+
print(file=cfp)
305+
rec_p2 = 1 << (len(slots) - 1).bit_length()
306+
print(f'#![recursion_limit = "{rec_p2}"]', file=cfp)
307+
print(file=cfp)
308+
print('#[derive(Clone, Copy, Debug, PartialEq)]', file=cfp)
309+
print('pub enum Partition {', file=cfp)
310+
part_names = [slot.name for slot in slots if slot.part]
311+
for pname in part_names:
312+
pname = pname.title().replace('_', '')
313+
print(f' {pname},', file=cfp)
314+
print('}', file=cfp)
315+
print(file=cfp)
316+
print('register_structs! {', file=cfp)
317+
print(' pub OtpSwCfgRegs {', file=cfp)
318+
slot = OtpSlotDescriptor('', 0, 0) # default slot if none is defined
319+
end = 0
320+
rsv = 0
321+
for slot in slots:
322+
if slot.part:
323+
continue
324+
if slot.offset > end:
325+
missing = slot.offset - end
326+
count = missing // 4
327+
if count > 1:
328+
print(f' (0x{end:04x} => '
329+
f'_reserved{rsv}: [ReadOnly<u32>; {count}]),',
330+
file=cfp)
331+
else:
332+
width = missing * 8
333+
print(f' (0x{end:04x} => '
334+
f'_reserved{rsv}: ReadOnly<u{width}>),', file=cfp)
335+
rsv += 1
336+
if slot.size <= 4:
337+
width = slot.size * 8
338+
print(f' (0x{slot.offset:04x} => '
339+
f'{slot.name.lower()}: ReadOnly<u{width}>),', file=cfp)
340+
else:
341+
count = slot.size // 4
342+
print(f' (0x{slot.offset:04x} => '
343+
f'{slot.name.lower()}: [ReadOnly<u32>; {count}]),',
344+
file=cfp)
345+
end = slot.offset + slot.size
346+
print(f' (0x{slot.offset+slot.size:04x} => @END),', file=cfp)
347+
print(' }', file=cfp)
348+
print('}', file=cfp)
173349
print(file=cfp)

python/qemu/ot/otp/lifecycle.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from binascii import unhexlify
1010
from io import StringIO
1111
from logging import getLogger
12-
from os.path import basename
1312
from textwrap import fill
1413
from typing import TextIO
1514
import re
@@ -96,14 +95,16 @@ def load(self, svp: TextIO):
9695
seq = ''.join((f'{x:04x}'for x in map(codes.get, seq)))
9796
self._tables[mkind][seq] = conv(ref)
9897

99-
def save(self, cfp: TextIO, data_mode: bool) -> None:
100-
"""Save OTP life cycle definitions as a C file.
98+
def save(self, kind: str, cfp: TextIO, data_mode: bool) -> None:
99+
"""Save OTP life cycle definitions as a source file.
101100
101+
:param kind: output format
102102
:param cfp: output text stream
103103
:param data_mode: whether to output data or template
104104
"""
105-
print(f'/* Section auto-generated with {basename(__file__)} '
106-
f'script */', file=cfp)
105+
if kind.lower() != 'qemu':
106+
raise NotImplementedError(f'No support for {kind}')
107+
print(f'/* Section auto-generated with {__name__} module */', file=cfp)
107108
if data_mode:
108109
self._save_data(cfp)
109110
else:
@@ -143,7 +144,7 @@ def _save_data(self, cfp: TextIO) -> None:
143144

144145
def _save_template(self, cfp: TextIO) -> None:
145146
print('/* clang-format off */', file=cfp)
146-
states = self._sequences.get('st') or {}
147+
states = self._sequences.get('lcst') or {}
147148
print('static const uint8_t', file=cfp)
148149
print('LC_STATES_TPL[LC_STATE_VALID_COUNT][LC_STATE_SLOT_COUNT] = {',
149150
file=cfp)

0 commit comments

Comments
 (0)