Skip to content

Commit b7bdb16

Browse files
rivos-eblotloiclefort
authored andcommitted
[ot] scripts/opentitan: otptool.py: add a new option to reset the content of a partition.
Signed-off-by: Emmanuel Blot <eblot@rivosinc.com>
1 parent 2558eb4 commit b7bdb16

File tree

4 files changed

+77
-4
lines changed

4 files changed

+77
-4
lines changed

docs/opentitan/otptool.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ controller virtual device.
88
````text
99
usage: otptool.py [-h] [-j HJSON] [-m VMEM] [-l SV] [-o C] [-r RAW]
1010
[-k {auto,otp,fuz}] [-e BITS] [-c INT] [-i INT] [-w] [-n]
11-
[-s] [-E] [-D] [-U] [--clear-bit CLEAR_BIT]
12-
[--set-bit SET_BIT] [--toggle-bit TOGGLE_BIT] [-L | -P | -R]
13-
[-v] [-d]
11+
[-s] [-E] [-D] [-U] [--empty PARTITION]
12+
[--clear-bit CLEAR_BIT] [--set-bit SET_BIT]
13+
[--toggle-bit TOGGLE_BIT] [-L | -P | -R] [-v] [-d]
1414
1515
QEMU OT tool to manage OTP files.
1616
@@ -40,7 +40,9 @@ Commands:
4040
-s, --show show the OTP content
4141
-E, --ecc-recover attempt to recover errors with ECC
4242
-D, --digest check the OTP HW partition digest
43-
-U, --update force-update QEMU OTP raw file after ECC recovery
43+
-U, --update update RAW file after ECC recovery or bit changes
44+
--empty PARTITION reset the content of a whole partition, including its
45+
digest if any
4446
--clear-bit CLEAR_BIT
4547
clear a bit at specified location
4648
--set-bit SET_BIT set a bit at specified location
@@ -150,6 +152,10 @@ Fuse RAW images only use the v1 type.
150152
contain long sequence of bytes. If repeated, the empty long fields are also printed in full, as
151153
a sequence of empty bytes.
152154

155+
* `--empty` reset a whole parition, including its digest if any and ECC bits. This option is only
156+
intended for test purposes. This flag may be repeated. Partition(s) can be specified either by
157+
their index or their name.
158+
153159
* `--clear-bit` clears the specified bit in the OTP data. This flag may be repeated. This option is
154160
only intended to corrupt the OTP content so that HW & SW behavior may be exercised should such
155161
a condition exists. See [Bit position syntax](#bit-syntax) for how to specify a bit.

scripts/opentitan/ot/otp/image.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,42 @@ def toggle_bits(self, bitdefs: Sequence[tuple[int, int]]) -> None:
303303
"""
304304
self._change_bits(bitdefs, None)
305305

306+
def empty_partition(self, partition: Union[int, str]) -> None:
307+
"""Empty the whole content of a partition, including its digest if any,
308+
and its ECC bits if any.
309+
310+
:param partition: the partition to empty, either specified as an
311+
index or as the partition name
312+
"""
313+
part = None
314+
partix = None
315+
if isinstance(partition, int):
316+
try:
317+
part = self._partitions[partition]
318+
partix = partition
319+
except IndexError:
320+
pass
321+
elif isinstance(partition, str):
322+
partname = partition.lower()
323+
try:
324+
partix, part = {(i, p) for i, p in enumerate(self._partitions)
325+
if p.__class__.__name__[:-4].lower() ==
326+
partname}.pop()
327+
except KeyError:
328+
pass
329+
if not part:
330+
raise ValueError(f"Unknown partition '{partition}'")
331+
part.empty()
332+
if not part.is_empty:
333+
raise RuntimeError(f"Unable to empty partition '{partition}'")
334+
content = BytesIO()
335+
part.save(content)
336+
data = content.getvalue()
337+
length = len(data)
338+
offset = self._part_offsets[partix]
339+
self._data[offset:offset+length] = data
340+
self._ecc[offset // 2:(offset+length)//2] = bytes(length//2)
341+
306342
@staticmethod
307343
def bit_parity(data: int) -> int:
308344
"""Compute the bit parity of an integer, i.e. reduce the vector to a

scripts/opentitan/ot/otp/partition.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ def is_locked(self) -> bool:
5959
return (self.has_digest and self._digest_bytes and
6060
self._digest_bytes != bytes(self.DIGEST_SIZE))
6161

62+
@property
63+
def is_empty(self) -> bool:
64+
"""Report if the partition is empty."""
65+
if self._digest_bytes and sum(self._digest_bytes):
66+
return False
67+
return sum(self._data) == 0
68+
6269
def __repr__(self) -> str:
6370
return repr(self.__dict__)
6471

@@ -72,6 +79,15 @@ def load(self, bfp: BinaryIO) -> None:
7279
self._digest_bytes = digest
7380
self._data = data
7481

82+
def save(self, bfp: BinaryIO) -> None:
83+
"""Save the content of the partition to a binary stream."""
84+
pos = bfp.tell()
85+
bfp.write(self._data)
86+
bfp.write(self._digest_bytes)
87+
size = bfp.tell() - pos
88+
if size != self.size:
89+
raise RuntimeError(f"Failed to save partition {self.name} content")
90+
7591
def verify(self, digest_iv: int, digest_constant: int) -> Optional[bool]:
7692
"""Verify if the digest matches the content of the partition, if any.
7793
"""
@@ -179,6 +195,12 @@ def emit(fmt, *args):
179195
emit('%-48s %s %s', f'{pname}:DIGEST', soff,
180196
hexlify(self._digest_bytes).decode())
181197

198+
def empty(self) -> None:
199+
"""Empty the partition, including its digest if any."""
200+
self._data = bytes(len(self._data))
201+
if self.has_digest:
202+
self._digest_bytes = bytes(self.DIGEST_SIZE)
203+
182204

183205
class OtpLifecycleExtension(OtpLifecycle, OtpPartitionDecoder):
184206
"""Decoder for Lifecyle bytes sequences.

scripts/opentitan/otptool.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ def main():
7373
commands.add_argument('-U', '--update', action='store_true',
7474
help='update RAW file after ECC recovery or bit '
7575
'changes')
76+
commands.add_argument('--empty', metavar='PARTITION', action='append',
77+
default=[],
78+
help='reset the content of a whole partition, '
79+
'including its digest if any')
7680
commands.add_argument('--clear-bit', action='append', default=[],
7781
help='clear a bit at specified location')
7882
commands.add_argument('--set-bit', action='append', default=[],
@@ -136,6 +140,8 @@ def main():
136140
argparser.error('Cannot decode OTP values without an OTP map')
137141
if args.digest:
138142
argparser.error('Cannot verify OTP digests without an OTP map')
143+
if args.empty:
144+
argparser.error('Cannot empty OTP partition without an OTP map')
139145
else:
140146
otpmap = OtpMap()
141147
otpmap.load(args.otp_map)
@@ -187,6 +193,9 @@ def main():
187193
if args.show:
188194
argparser.error('Showing content of non-OpenTitan image is '
189195
'not supported')
196+
if args.empty:
197+
for part in args.empty:
198+
otp.empty_partition(part)
190199
if args.iv:
191200
otp.set_digest_iv(args.iv)
192201
if args.constant:

0 commit comments

Comments
 (0)