Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions docs/opentitan/otptool.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o FILE] [-r RAW]
[-G PART] [--change PART:FIELD=VALUE] [--empty PARTITION]
[--erase PART:FIELD] [--clear-bit CLEAR_BIT]
[--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT]
[--write ADDR/HEXBYTES] [--patch-token NAME=VALUE] [-v] [-d]
[--write ADDR/HEXBYTES] [--patch-token NAME=VALUE]
[--out-kind {qemu,bmtest}] [--top-name TOP_NAME] [-v] [-d]

QEMU OT tool to manage OTP files.

Expand Down Expand Up @@ -47,7 +48,7 @@ Commands:
-D, --digest check the OTP HW partition digest
-U, --update update RAW file after ECC recovery or bit changes
-g, --generate {LCVAL,LCTPL,PARTS,REGS}
generate C code, see doc for options
generate code, see doc for options
-F, --fix-ecc rebuild ECC
-G, --fix-digest PART
rebuild HW digest
Expand All @@ -65,6 +66,10 @@ Commands:
write bytes at specified location
--patch-token NAME=VALUE
change a LC hashed token, using Rust file
--out-kind {qemu,bmtest}
select output format for code generation (default:
qemu)
--top-name TOP_NAME optional top name for code generation (default: auto)

Extras:
-v, --verbose increase verbosity
Expand Down Expand Up @@ -129,9 +134,9 @@ Fuse RAW images only use the v1 type.
* `-G` can be used to (re)build the HW digest of a partition after altering one or more of its
fields, see `--change` option.

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

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

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

* `--out-kind` define the output format for code generation for the `-g` option. Note that only a
subset of the generation types are available in some output kinds.

* `--patch-token` patch a Life Cycle hashed token. This feature is primary aimed at testing the
Life Cycle controller. With this option, the partition to update is automatically found using
the token `NAME`. If the partition containing the token to update is already locked, its digest is
Expand All @@ -223,6 +231,9 @@ Fuse RAW images only use the v1 type.
is only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such
a condition exists. See [Bit position syntax](#bit-syntax) for how to specify a bit.

* `--top-name` defines the OpenTitan top name for some code generation feature. It overrides any
top name that may be automatically retrieved from the configuration file hierarchy.

* `--write` overrides any data (not ECC). If can be combined with `--fix-ecc` to automatically
rebuild the ECC of the data slot that have been altered. This option may be repeated. The argument
should comply with the `offset/hexdata` syntax, where the _offset_ part is defined in the
Expand Down
2 changes: 1 addition & 1 deletion python/qemu/ot/km/dpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from ..otp import OtpImage, OtpLifecycleExtension, OtpMap
from ..rom.image import ROMImage
from ..util.misc import ArgError
from ..util.arg import ArgError

# ruff: noqa: E402
_CRYPTO_EXC: Optional[Exception] = None
Expand Down
241 changes: 216 additions & 25 deletions python/qemu/ot/otp/descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,54 @@
"""

from logging import getLogger
from typing import TYPE_CHECKING, TextIO
from typing import NamedTuple, Optional, TYPE_CHECKING, TextIO

from ot.top import OpenTitanTop
from ot.util.misc import redent
from .partition import OtpPartition

if TYPE_CHECKING:
from .map import OtpMap


class OtpSlotDescriptor(NamedTuple):
"""A location descriptor in OTP, either a whole partition or an item.

It has no other purpose than storing intermediate info as a container.
"""

name: str
"""Name of the slot."""

offset: str
"""Offset in bytes."""

size: str
"""Size in bytes."""

gen: bool = False
"""Whether this slot is generated by the script or defined."""

part: bool = False
"""Whether the slot defines the whole partition or a single item."""


class OtpPartitionDesc:
"""OTP Partition descriptor generator."""

ATTRS = {
'size': None,
'offset': None,
'digest_offset': None,
'zer_offset': None,
'hw_digest': '',
'sw_digest': '',
'secret': '',
'variant': 'buffer',
'write_lock': 'wlock',
'read_lock': 'rlock',
'integrity': '',
'zeroizable': '',
'iskeymgr': '',
'iskeymgr_creator': '',
'iskeymgr_owner': '',
Expand All @@ -37,9 +65,18 @@ def __init__(self, otpmap: 'OtpMap'):
self._log = getLogger('otp.partdesc')
self._otpmap = otpmap

def save(self, hjname: str, scriptname: str, cfp: TextIO) -> None:
"""Generate a C file with a static description for the partitions."""
def save(self, kind: str, hjname: str, scriptname: str, cfp: TextIO) \
-> None:
"""Generate a source file with a static description for the partitions.

:param kind: kind of generation output
:param hjname: the name of the input HJSON configuration file
:param scriptname: the name of the script that generates this output
:param cfp: the output text stream
"""
# pylint: disable=f-string-without-interpolation
if kind != 'qemu':
raise NotImplementedError(f'No support for {kind}')
attrs = {n: getattr(self, f'_convert_to_{k}') if k else lambda x: x
for n, k in self.ATTRS.items() if k is not None}
print(f'/* Generated from {hjname} with {scriptname} */', file=cfp)
Expand All @@ -51,6 +88,12 @@ def save(self, hjname: str, scriptname: str, cfp: TextIO) -> None:
print(f' [OTP_PART_{part.name}] = {{', file=cfp)
print(f' .size = {part.size}u,', file=cfp)
print(f' .offset = {part.offset}u,', file=cfp)
if part.zer_offset is not None:
print(f' .zer_offset = {part.zer_offset}u,',
file=cfp)
else:
print(f' .zer_offset = UINT16_MAX,', # noqa: F541
file=cfp)
if part.digest_offset is not None:
print(f' .digest_offset = {part.digest_offset}u,',
file=cfp)
Expand Down Expand Up @@ -118,48 +161,196 @@ def __init__(self, otpmap: 'OtpMap'):
self._log = getLogger('otp.reg')
self._otpmap = otpmap

def save(self, hjname: str, scriptname: str, cfp: TextIO) -> None:
"""Generate a C file with register definition for the partitions."""
reg_offsets = []
reg_sizes = []
part_names = []
def save(self, kind: str, topname: Optional[str], hjname: str,
scriptname: str, cfp: TextIO) -> None:
"""Generate a source file with register definition for the partitions.

:param kind: kind of generation output
:param topname: the name of the OpenTitan top
:param hjname: the name of the input HJSON configuration file
:param scriptname: the name of the script that generates this output
:param cfp: the output text stream
"""
try:
save = getattr(self, f'_save_{kind.lower()}')
except AttributeError as exc:
raise NotImplementedError(f'No support for {kind}') from exc
slots: list[OtpSlotDescriptor] = []
for part in self._otpmap.enumerate_partitions():
part_names.append(f'OTP_PART_{part.name}')
offset = part.offset
reg_sizes.append((f'{part.name}_SIZE', part.size))
slots.append(OtpSlotDescriptor(part.name, offset, part.size, True,
True))
for itname, itdict in part.items.items():
size = itdict['size']
if not itname.startswith(f'{part.name}_'):
name = f'{part.name}_{itname}'.upper()
else:
name = itname
reg_offsets.append((name, offset))
reg_sizes.append((f'{name}_SIZE', size))
slots.append(OtpSlotDescriptor(name, offset, size))
offset += size
print(f'/* Generated from {hjname} with {scriptname} */')
zeroizable = getattr(part, 'zeroizable', False)
digest = any(getattr(part, f'{k}w_digest', False) for k in 'sh')
if digest:
offset = part.offset + part.size - OtpPartition.DIGEST_SIZE
if zeroizable:
offset -= OtpPartition.ZER_SIZE
slots.append(OtpSlotDescriptor(f'{part.name}_DIGEST', offset,
OtpPartition.DIGEST_SIZE, True))
if zeroizable:
offset = part.offset + part.size - OtpPartition.DIGEST_SIZE
slots.append(OtpSlotDescriptor(f'{part.name}_ZER', offset,
OtpPartition.ZER_SIZE, True))

save(topname, hjname, scriptname, cfp, slots)

def _save_qemu(self, topname: Optional[str], hjname: str, scriptname: str,
cfp: TextIO, slots: list[OtpSlotDescriptor]) -> None:
print(f'/* Generated from {hjname} with {scriptname} */', file=cfp)
print(file=cfp)
print('/* clang-format off */', file=cfp)
for reg, off in reg_offsets:
print(f'REG32({reg}, {off}u)', file=cfp)
for slot in slots:
if slot.part:
continue
print(f'REG32({slot.name}, {slot.offset}u)', file=cfp)
print(file=cfp)
regwidth = max(len(r[0]) for r in reg_sizes)
for reg, size in reg_sizes:
print(f'#define {reg:{regwidth}s} {size}u', file=cfp)

regwidth = max(len(s.name) for s in slots)
regwidth += len('_SIZE')
for slot in slots:
if slot.gen and not slot.part:
continue
name = f'{slot.name}_SIZE'
print(f'#define {name:{regwidth}s} {slot.size}u', file=cfp)
print(file=cfp)

part_names = [slot.name for slot in slots if slot.part]
pcount = len(part_names)
part_names = [f'OTP_PART_{pn}' for pn in part_names]
part_names.extend((
'_OTP_PART_COUNT',
'OTP_ENTRY_DAI = _OTP_PART_COUNT',
'OTP_ENTRY_KDI',
'_OTP_ENTRY_COUNT'))
'OTP_PART_COUNT',
'OTP_ENTRY_DAI = OTP_PART_COUNT, '
'/* Fake partitions for error (...) */',
'OTP_ENTRY_KDI, /* Key derivation issue, not really OTP */',
'OTP_ENTRY_COUNT'))
print('typedef enum {', file=cfp)
for pname in part_names:
print(f' {pname},', file=cfp)
print(f' {pname}{"," if "," not in pname else ""}', file=cfp)
print('} OtOTPPartitionType;', file=cfp)
print(file=cfp)

print('static const char *PART_NAMES[] = {', file=cfp)
print(' /* clang-format off */', file=cfp)
for pname in part_names[:pcount]:
print(f' OTP_NAME_ENTRY({pname}),', file=cfp)
print(' /* fake partitions */', file=cfp)
print(' OTP_NAME_ENTRY(OTP_ENTRY_DAI),', file=cfp)
print(' OTP_NAME_ENTRY(OTP_ENTRY_KDI),', file=cfp)
print(' /* clang-format on */', file=cfp)
print('};', file=cfp)
print('/* clang-format on */', file=cfp)
print(file=cfp)

cases: list[str] = []
for slot in slots:
if slot.part:
continue
if slot.gen:
cases.append(f'CASE_WIDE({slot.name});')
elif slot.size > 4:
cases.append(f'CASE_RANGE({slot.name});')
elif slot.size == 1:
cases.append(f'CASE_BYTE({slot.name});')
elif slot.size < 4:
cases.append(f'CASE_SUB({slot.name}, {slot.size}u);')
else:
cases.append(f'CASE_REG({slot.name});')

code = '''
static const char *ot_otp_swcfg_reg_name(unsigned swreg)
{
#define CASE_BYTE(_reg_) \\
case A_##_reg_: \\
return stringify(_reg_)
#define CASE_SUB(_reg_, _sz_) \\
case A_##_reg_...(A_##_reg_ + (_sz_)): \\
return stringify(_reg_)
#define CASE_REG(_reg_) \\
case A_##_reg_...(A_##_reg_ + 3u): \\
return stringify(_reg_)
#define CASE_WIDE(_reg_) \\
case A_##_reg_...(A_##_reg_ + 7u): \\
return stringify(_reg_)
#define CASE_RANGE(_reg_) \\
case A_##_reg_...(A_##_reg_ + (_reg_##_SIZE) - 1u): \\
return stringify(_reg_)

switch (swreg) {
_CASES_
default:
return "<?>";
}

#undef CASE_BYTE
#undef CASE_SUB
#undef CASE_REG
#undef CASE_RANGE
#undef CASE_DIGEST
}
'''
if topname:
tname = OpenTitanTop.short_name(topname)
if tname:
code = code.replace('ot_otp_swcfg_reg_name',
f'ot_otp_{tname}_swcfg_reg_name')
code = redent(code)
code = code.replace('_CASES_', '\n '.join(cases))
print(redent(code), '', file=cfp)

def _save_bmtest(self, topname: Optional[str], hjname: str, scriptname: str,
cfp: TextIO, slots: list[OtpSlotDescriptor]) -> None:
# pylint: disable=unused-argument
print(f'// Generated from {hjname} with {scriptname}', file=cfp)
print(file=cfp)
rec_p2 = 1 << (len(slots) - 1).bit_length()
print(f'#![recursion_limit = "{rec_p2}"]', file=cfp)
print(file=cfp)
print('#[derive(Clone, Copy, Debug, PartialEq)]', file=cfp)
print('pub enum Partition {', file=cfp)
part_names = [slot.name for slot in slots if slot.part]
for pname in part_names:
pname = pname.title().replace('_', '')
print(f' {pname},', file=cfp)
print('}', file=cfp)
print(file=cfp)
print('register_structs! {', file=cfp)
print(' pub OtpSwCfgRegs {', file=cfp)
slot = OtpSlotDescriptor('', 0, 0) # default slot if none is defined
end = 0
rsv = 0
for slot in slots:
if slot.part:
continue
if slot.offset > end:
missing = slot.offset - end
count = missing // 4
if count > 1:
print(f' (0x{end:04x} => '
f'_reserved{rsv}: [ReadOnly<u32>; {count}]),',
file=cfp)
else:
width = missing * 8
print(f' (0x{end:04x} => '
f'_reserved{rsv}: ReadOnly<u{width}>),', file=cfp)
rsv += 1
if slot.size <= 4:
width = slot.size * 8
print(f' (0x{slot.offset:04x} => '
f'{slot.name.lower()}: ReadOnly<u{width}>),', file=cfp)
else:
count = slot.size // 4
print(f' (0x{slot.offset:04x} => '
f'{slot.name.lower()}: [ReadOnly<u32>; {count}]),',
file=cfp)
end = slot.offset + slot.size
print(f' (0x{slot.offset+slot.size:04x} => @END),', file=cfp)
print(' }', file=cfp)
print('}', file=cfp)
print(file=cfp)
Loading
Loading