diff --git a/docs/opentitan/otptool.md b/docs/opentitan/otptool.md index f2197e3e3cc9..2035c0714425 100644 --- a/docs/opentitan/otptool.md +++ b/docs/opentitan/otptool.md @@ -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. @@ -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: @@ -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 @@ -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 @@ -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 @@ -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. diff --git a/python/qemu/ot/otp/image.py b/python/qemu/ot/otp/image.py index f162e8b66b8f..9e8ad728ae0d 100644 --- a/python/qemu/ot/otp/image.py +++ b/python/qemu/ot/otp/image.py @@ -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 : 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. diff --git a/python/qemu/ot/otp/partition.py b/python/qemu/ot/otp/partition.py index f6164a1412ff..f0d70bf31fa0 100644 --- a/python/qemu/ot/otp/partition.py +++ b/python/qemu/ot/otp/partition.py @@ -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 @@ -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: @@ -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) @@ -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() @@ -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: @@ -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.""" diff --git a/scripts/opentitan/otptool.py b/scripts/opentitan/otptool.py index d77e78555c62..fdb9823ab377 100755 --- a/scripts/opentitan/otptool.py +++ b/scripts/opentitan/otptool.py @@ -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') @@ -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') @@ -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): @@ -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: