Skip to content

Commit

Permalink
[ot] scripts/opentitan: otptool.py: add a partition filter option
Browse files Browse the repository at this point in the history
- add a new option to select which partition(s) and fields(s) to show
  when decoding an OTP image file
- add a new option to hide the OTP image file version

Signed-off-by: Emmanuel Blot <[email protected]>
  • Loading branch information
rivos-eblot committed Dec 19, 2024
1 parent aaecaa3 commit 5ddfffb
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 24 deletions.
31 changes: 20 additions & 11 deletions docs/opentitan/otptool.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ controller virtual device.
## Usage

````text
usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o C] [-r RAW]
usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o FILE] [-r RAW]
[-k {auto,otp,fuz}] [-e BITS] [-C CONFIG] [-c INT] [-i INT]
[-w] [-n] [-s] [-E] [-D] [-U] [--empty PARTITION]
[--erase PART:FIELD] [--clear-bit CLEAR_BIT]
[--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] [--fix-ecc]
[-w] [-n] [-f PART:FIELD] [--no-version] [-s] [-E] [-D] [-U]
[--empty PARTITION] [--erase PART:FIELD]
[--clear-bit CLEAR_BIT] [--set-bit SET_BIT]
[--toggle-bit TOGGLE_BIT] [--fix-ecc]
[-G {LCVAL,LCTPL,PARTS,REGS}] [-v] [-d]
QEMU OT tool to manage OTP files.
Expand All @@ -22,7 +23,7 @@ Files:
-j, --otp-map HJSON input OTP controller memory map file
-m, --vmem VMEM input VMEM file
-l, --lifecycle SV input lifecycle system verilog file
-o, --output C output filename for C file generation
-o, --output FILE output filename (default to stdout)
-r, --raw RAW QEMU OTP raw image file
Parameters:
Expand All @@ -34,6 +35,9 @@ Parameters:
-i, --iv INT initialization vector for Present scrambler
-w, --wide use wide output, non-abbreviated content
-n, --no-decode do not attempt to decode OTP fields
-f, --filter PART:FIELD
filter which OTP fields are shown
--no-version do not report the OTP image version
Commands:
-s, --show show the OTP content
Expand Down Expand Up @@ -107,6 +111,9 @@ Fuse RAW images only use the v1 type.
* `-e` specify how many bits are used in the VMEM file to store ECC information. Note that ECC
information is not stored in the QEMU RAW file for now.

* `-f` select which partition(s) and partition field(s) should be shown when option `-s` is used.
When not specified, all partitions and fields are reported.

* `-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,
from the last entry of `RndCnstDigestIV` array, _i.e._ item 0. It is used along with option
Expand Down Expand Up @@ -153,15 +160,20 @@ Fuse RAW images only use the v1 type.
contain long sequence of bytes. If repeated, the empty long fields are also printed in full, as
a sequence of empty bytes.

* `--clear-bit` clears the specified bit in the OTP data. This flag may be repeated. This option 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.

* `--empty` reset a whole parition, including its digest if any and ECC bits. This option is only
intended for test purposes. This flag may be repeated. Partition(s) can be specified either by
their index or their name.

* `--erase` reset a specific field within a partition. The flag may be repeated.

* `--clear-bit` clears the specified bit in the OTP data. This flag may be repeated. This option 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.
* `--no-version` disable OTP image version reporting when `-s` is used.

* `--fix-ecc` may be used to rebuild the ECC values for all slots that have been modified using the
ECC modification operations, and any detected error.

* `--set-bit` sets the specified bit in the OTP data. This flag may be repeated. This option is
only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such
Expand All @@ -171,9 +183,6 @@ 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.

* `--fix-ecc` may be used to rebuild the ECC values for all slots that have been modified using the
ECC modification operations, and any detected error.

All modification features can only be performed on RAW image, VMEM images are never modified. To
modify RAW file content, either a VMEM file is required in addition to the RAW file as the data
source, or the `-U` is required to tell that the RAW file should be read, modified and written back.
Expand Down
38 changes: 34 additions & 4 deletions python/qemu/ot/otp/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,17 +311,47 @@ def verify(self, show: bool = False) -> bool:
return not any(r is False for r in results.values())

def decode(self, decode: bool = True, wide: int = 0,
ofp: Optional[TextIO] = None) -> None:
"""Decode the content of the image, one partition at a time."""
ofp: Optional[TextIO] = None, decode_version: bool = True,
field_filters: Optional[list[str]] = None) -> None:
"""Decode the content of the image, one partition at a time.
:param decode: whether to attempt to decode value whose encoding/
meaning is known
:param wide: whether to use compact, truncated long value or emit
the whole value, possibly generating very long lines
:param decode_version: whether to decode and report the version
:param field_filters: optional filter to select which partitions/
fields to report as a list of <part>:<field> strings, joker
char '*' is supported.
"""
version = self.version
if version:
if version and decode_version:
print(f'OTP image v{version}')
if version > 1:
print(f' * present iv {self._digest_iv:016x}')
print(f' * present constant {self._digest_constant:032x}')
part_filters: dict[str, str] = {}
partnames = {p.name for p in self._partitions}
for filt in field_filters or []:
parts = filt.split(':')
if len(parts) > 2:
raise ValueError(f"Invalid filter '{filt}'")
part_re = f'^{parts[0].replace("*", ".*")}$'
field = parts[1] if len(parts) > 1 else '*'
for partname in partnames:
if not re.match(part_re, partname, re.IGNORECASE):
continue
if not partname in part_filters:
part_filters[partname] = set()
if field == '*':
# any field would match, discard any existing one
part_filters[partname].clear()
part_filters[partname].add(field)
for part in self._partitions:
if part_filters and part.name not in part_filters:
continue
base = self._get_partition_bounds(part)[0]
part.decode(base, decode, wide, ofp)
part.decode(base, decode, wide, ofp, part_filters.get(part.name))

def clear_bits(self, bitdefs: Sequence[tuple[int, int]]) -> None:
"""Clear one or more bits.
Expand Down
31 changes: 25 additions & 6 deletions python/qemu/ot/otp/partition.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from binascii import hexlify, unhexlify, Error as hexerror
from io import BytesIO
from logging import getLogger
from typing import BinaryIO, Optional, TextIO
from re import IGNORECASE, match
from typing import BinaryIO, Optional, Sequence, TextIO

from .lifecycle import OtpLifecycle

Expand Down Expand Up @@ -145,7 +146,8 @@ def set_decoder(self, decoder: OtpPartitionDecoder) -> None:
self._decoder = decoder

def decode(self, base: Optional[int], decode: bool = True, wide: int = 0,
ofp: Optional[TextIO] = None) -> None:
ofp: Optional[TextIO] = None,
filters = Optional[Sequence[str]]) -> None:
"""Decode the content of the partition."""
buf = BytesIO(self._data)
if ofp:
Expand All @@ -155,6 +157,12 @@ def emit(fmt, *args):
emit = self._log.info
pname = self.name
offset = 0
soff = 0
if filters:
fre = '|'.join(f.replace('*', '.*') for f in filters)
filter_re = f'^({fre})$'
else:
filter_re = r'.*'
for itname, itdef in self.items.items():
itsize = itdef['size']
itvalue = buf.read(itsize)
Expand All @@ -164,6 +172,8 @@ def emit(fmt, *args):
name = f'{pname}:{itname[len(pname)+1:]}'
else:
name = f'{pname}:{itname}'
if not match(filter_re, itname, IGNORECASE):
continue
if itsize > 8:
rvalue = bytes(reversed(itvalue))
sval = hexlify(rvalue).decode()
Expand All @@ -172,12 +182,16 @@ def emit(fmt, *args):
if dval is not None:
emit('%-48s %s (decoded) %s', name, soff, dval)
continue
ssize = f'{{{itsize}}}'
if not sum(itvalue) and wide < 2:
emit('%-48s %s {%d} 0...', name, soff, itsize)
if decode:
emit('%-48s %s %5s (empty)', name, soff, ssize)
else:
emit('%-48s %s %5s 0...', name, soff, ssize)
else:
if not wide and itsize > self.MAX_DATA_WIDTH:
sval = f'{sval[:self.MAX_DATA_WIDTH*2]}...'
emit('%-48s %s {%d} %s', name, soff, itsize, sval)
emit('%-48s %s %5s %s', name, soff, ssize, sval)
else:
ival = int.from_bytes(itvalue, 'little')
if decode:
Expand All @@ -192,8 +206,13 @@ def emit(fmt, *args):
continue
emit('%-48s %s %x', name, soff, ival)
if self._digest_bytes is not None:
emit('%-48s %s %s', f'{pname}:DIGEST', soff,
hexlify(self._digest_bytes).decode())
if match(filter_re, 'DIGEST', IGNORECASE):
if not sum(self._digest_bytes) and decode:
val = '(empty)'
else:
val = hexlify(self._digest_bytes).decode()
ssize = f'{{{len(self._digest_bytes)}}}'
emit('%-48s %s %5s %s', f'{pname}:DIGEST', soff, ssize, val)

def empty(self) -> None:
"""Empty the partition, including its digest if any."""
Expand Down
16 changes: 13 additions & 3 deletions scripts/opentitan/otptool.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ def main():
files.add_argument('-l', '--lifecycle', type=FileType('rt'),
metavar='SV',
help='input lifecycle system verilog file')
files.add_argument('-o', '--output', metavar='C', type=FileType('wt'),
help='output filename for C file generation')
files.add_argument('-o', '--output', metavar='FILE',
type=FileType('wt'),
help='output filename (default to stdout)')
files.add_argument('-r', '--raw',
help='QEMU OTP raw image file')
params = argparser.add_argument_group(title='Parameters')
Expand All @@ -65,6 +66,11 @@ def main():
params.add_argument('-n', '--no-decode', action='store_true',
default=False,
help='do not attempt to decode OTP fields')
params.add_argument('-f', '--filter', action='append',
metavar='PART:FIELD',
help='filter which OTP fields are shown')
params.add_argument('--no-version', action='store_true',
help='do not report the OTP image version')
commands = argparser.add_argument_group(title='Commands')
commands.add_argument('-s', '--show', action='store_true',
help='show the OTP content')
Expand Down Expand Up @@ -121,6 +127,9 @@ def main():
if args.vmem:
argparser.error('RAW update mutually exclusive with VMEM')

if args.filter and not args.show:
argparser.error('Filter only apply to the show command')

bit_actions = ('clear', 'set', 'toggle')
alter_bits: list[list[tuple[int, int]]] = []
for slot, bitact in enumerate(bit_actions):
Expand Down Expand Up @@ -225,7 +234,8 @@ def main():
if lcext:
otp.load_lifecycle(lcext)
if args.show:
otp.decode(not args.no_decode, args.wide, sys.stdout)
otp.decode(not args.no_decode, args.wide, output,
not args.no_version, args.filter)
if args.digest:
if not otp.has_present_constants:
if args.raw and otp.version == 1:
Expand Down

0 comments on commit 5ddfffb

Please sign in to comment.