| # Copyright lowRISC contributors. | 
 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
 | # SPDX-License-Identifier: Apache-2.0 | 
 | """Utilities used to download firmware images into the OpenTitan system.""" | 
 |  | 
 | import time | 
 | import hashlib | 
 | from functools import partial | 
 | from collections import namedtuple | 
 |  | 
 |  | 
 | def _uint_to_le(val, length): | 
 |     """Returns a byte array that represents an unsigned integer in little-endian format. | 
 |  | 
 |     Args: | 
 |       val: Unsigned integer to convert. | 
 |       length: Number of bytes. | 
 |  | 
 |     Returns: | 
 |       A byte array of ``length`` bytes that represents ``val`` in little-endian format. | 
 |     """ | 
 |     return val.to_bytes(length=length, byteorder='little') | 
 |  | 
 |  | 
 | def _le_to_uint(val): | 
 |     """Returns the unsigned integer represented by the given byte array in little-endian format. | 
 |  | 
 |     Args: | 
 |       val: Byte array in little-endian format that represents an unsigned integer. | 
 |  | 
 |     Returns: | 
 |       The unsigned integer represented by the byte array ``val``. | 
 |     """ | 
 |     return int.from_bytes(val, byteorder='little') | 
 |  | 
 |  | 
 | class _SpiFlashFrame: | 
 |     """A frame used for programming OpenTitan over the SPI interface. | 
 |  | 
 |     In order to program OpenTitan with a binary image, the image is broken into ``PAYLOAD_SIZE`` | 
 |     byte chunks, each of which is transmitted as a frame over the SPI interface. A frame consists | 
 |     of four parts: | 
 |     * Digest: A ``DIGEST_SIZE`` byte digest of the rest of the frame, | 
 |     * Frame number: A ``FRAME_NUMBER_SIZE`` byte little-endian representation of the frame number. | 
 |       MSB of the last frame's frame number is set to 1 to indicate the end of the image. | 
 |     * Flash offset: A ``FLASH_OFFSET_SIZE`` byte little-endian representation of the flash offset | 
 |       to start writing the payload of the frame. | 
 |     * Payload: ``PAYLOAD_SIZE`` byte actual binary payload in little-endian format. | 
 |  | 
 |     OpenTitan signals correct transmission of frame N-1 by transmitting its digest (computed over | 
 |     the entire frame, i.e. including the digest field of the frame) while it receives frame N. | 
 |  | 
 |     Attributes: | 
 |       HASH_FUNCTION: Hash function to use for digest computations. | 
 |       DIGEST_SIZE: Size of digests (inferred from ``HASH_FUNCTION``). | 
 |       FRAME_NUMBER_SIZE: Size of the frame number field in bytes. | 
 |       FLASH_OFFSET_SIZE: Size of the flash offset field in bytes. | 
 |       PAYLOAD_SIZE: Size of the frame payload in bytes. | 
 |       FRAME_SIZE: Size of the SPI Flash frame, depends on the Boot ROM, see spiflash_frame.h. | 
 |       is_second_frame: Indicates if this is the second frame. | 
 |       expected_ack: Expected acknowledgement value for a frame. | 
 |     """ | 
 |     HASH_FUNCTION = hashlib.sha256 | 
 |     DIGEST_SIZE = HASH_FUNCTION().digest_size | 
 |     FRAME_NUMBER_SIZE = 4 | 
 |     FLASH_OFFSET_SIZE = 4 | 
 |     FRAME_SIZE = 2048 | 
 |     PAYLOAD_SIZE = FRAME_SIZE - DIGEST_SIZE - FRAME_NUMBER_SIZE - FLASH_OFFSET_SIZE | 
 |  | 
 |     def __init__(self, frame_number, flash_offset, payload): | 
 |         """Inits a _SpiFlashFrame. | 
 |  | 
 |         Also pads frame payload to ``PAYLOAD_SIZE`` bytes if needed. | 
 |  | 
 |         Args: | 
 |           frame_numer: Frame number. | 
 |           flash_offset: Flash offset. | 
 |           payload: Frame payload. | 
 |         """ | 
 |         self._frame_number = _uint_to_le(frame_number, self.FRAME_NUMBER_SIZE) | 
 |         self._flash_offset = _uint_to_le(flash_offset, self.FLASH_OFFSET_SIZE) | 
 |         self._payload = payload | 
 |         padding = self.PAYLOAD_SIZE - len(self._payload) | 
 |         if padding > 0: | 
 |             self._payload = b''.join([self._payload, b'\xFF' * padding]) | 
 |  | 
 |         # Reverse the order of the bytes to make them little-endian and be | 
 |         # consistent with the code signing tool. | 
 |         self._digest = self.HASH_FUNCTION(b''.join([ | 
 |             self._frame_number, | 
 |             self._flash_offset, | 
 |             self._payload, | 
 |         ])).digest()[::-1] | 
 |  | 
 |     def __bytes__(self): | 
 |         return b''.join([ | 
 |             self._digest, | 
 |             self._frame_number, | 
 |             self._flash_offset, | 
 |             self._payload, | 
 |         ]) | 
 |  | 
 |     def __repr__(self): | 
 |         return f'frame 0x{_le_to_uint(self._frame_number):08X} @ \ | 
 |             0x{_le_to_uint(self._flash_offset):08X}' | 
 |  | 
 |     @property | 
 |     def is_second_frame(self): | 
 |         """Indicates if this is the second frame.""" | 
 |         return _le_to_uint(self._frame_number) == 1 | 
 |  | 
 |     @property | 
 |     def expected_ack(self): | 
 |         """Expected acknowledgement value for the frame.""" | 
 |         # Reverse the order of the bytes to make them little-endian and be | 
 |         # consistent with the code signing tool. | 
 |         return self.HASH_FUNCTION(bytes(self)).digest()[::-1] | 
 |  | 
 |  | 
 | class _Bootstrap: | 
 |     """Handles low-level details during programming OpenTitan using SAM3X/U on CW310/305. | 
 |  | 
 |     Initializes pins, resets OpenTitan, transmits frames, and receives acknowledgements. | 
 |  | 
 |     To use: | 
 |     >>> with _Bootstrap(fpga) as bootstrap: | 
 |     >>>   ... | 
 |     >>>   ack = bootstrap.transfer(frame) | 
 |     >>>   ... | 
 |     """ | 
 |     # Pin mappings for CW305/CW310 boards. | 
 |     PinMapping = namedtuple('PinMapping', ['PIN_SCK', | 
 |                                            'PIN_SDI', | 
 |                                            'PIN_SDO', | 
 |                                            'PIN_CS', | 
 |                                            'PIN_TRST', | 
 |                                            'PIN_SRST', | 
 |                                            'PIN_JTAG_SPI', | 
 |                                            'PIN_BOOTSTRAP']) | 
 |     _PIN_MAPPINGS = {} | 
 |     _PIN_MAPPINGS['CW305'] = PinMapping('USB_A13', | 
 |                                         'USB_A14', | 
 |                                         'USB_A15', | 
 |                                         'USB_A16', | 
 |                                         'USB_A17', | 
 |                                         'USB_A18', | 
 |                                         'USB_A19', | 
 |                                         'USB_A20') | 
 |     _PIN_MAPPINGS['CW310'] = PinMapping('USB_SPI_SCK', | 
 |                                         'USB_SPI_COPI', | 
 |                                         'USB_SPI_CIPO', | 
 |                                         'USB_SPI_CS', | 
 |                                         'USB_A17', | 
 |                                         'USB_A18', | 
 |                                         'USB_A19', | 
 |                                         'USB_A16') | 
 |     _board = 'CW305' | 
 |  | 
 |     # Delays below are in seconds. | 
 |     _BOOTSTRAP_DELAY = 0.1 | 
 |     _SECOND_FRAME_DELAY = 0.2 | 
 |     _INTER_FRAME_DELAY = 0.02 | 
 |  | 
 |     def __init__(self, fpga, board='CW305'): | 
 |         """Inits a _Bootstrap with a CW310/305. | 
 |  | 
 |         Args: | 
 |           fpga: CW310/305 to be programmed, a ``cw.capture.targets.CW310/305`` instance. | 
 |           board: can be ``CW310`` or ``CW305`` to distinguish the different board types. | 
 |         """ | 
 |  | 
 |         # Check board type. | 
 |         if board in ['CW305', 'CW310']: | 
 |             self._board = board | 
 |         else: | 
 |             raise ValueError('board must be either CW305 or CW310, got ' + board) | 
 |  | 
 |         # Configure JTAG and bootstrap pins. | 
 |         self._fpga_io = fpga.gpio_mode() | 
 |         self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_TRST) | 
 |         self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_SRST) | 
 |         self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI) | 
 |         self._fpga_io.pin_set_output(self._PIN_MAPPINGS[self._board].PIN_BOOTSTRAP) | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_TRST, 1) | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_SRST, 1) | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI, 1) | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_BOOTSTRAP, 0) | 
 |         # Initialize SPI pins. | 
 |         self._fpga_io.spi1_setpins(sck=self._PIN_MAPPINGS[self._board].PIN_SCK, | 
 |                                    sdo=self._PIN_MAPPINGS[self._board].PIN_SDI, | 
 |                                    sdi=self._PIN_MAPPINGS[self._board].PIN_SDO, | 
 |                                    cs=self._PIN_MAPPINGS[self._board].PIN_CS) | 
 |         self._fpga_io.spi1_enable(True) | 
 |  | 
 |     def _reset_opentitan(self): | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI, 1) | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_SRST, 0) | 
 |         time.sleep(self._BOOTSTRAP_DELAY) | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_SRST, 1) | 
 |         time.sleep(self._BOOTSTRAP_DELAY) | 
 |  | 
 |     def __enter__(self): | 
 |         """Starts bootstrapping.""" | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_BOOTSTRAP, 1) | 
 |         self._reset_opentitan() | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI, 0) | 
 |         time.sleep(self._BOOTSTRAP_DELAY) | 
 |         return self | 
 |  | 
 |     def __exit__(self, exc_type, exc_value, traceback): | 
 |         """Ends bootstrapping.""" | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_BOOTSTRAP, 0) | 
 |         self._fpga_io.pin_set_state(self._PIN_MAPPINGS[self._board].PIN_JTAG_SPI, 1) | 
 |         time.sleep(self._BOOTSTRAP_DELAY) | 
 |  | 
 |     def transfer(self, frame): | 
 |         """Transmits a frame over SPI and receives the acknowledgement for the previous frame.""" | 
 |         # Wait longer after the first frame since it triggers a flash erase operation. | 
 |         if frame.is_second_frame: | 
 |             time.sleep(self._SECOND_FRAME_DELAY) | 
 |         else: | 
 |             time.sleep(self._INTER_FRAME_DELAY) | 
 |         print(f'Transferring {frame}.') | 
 |         # Acknowledgement is the same size as digests. | 
 |         return bytes( | 
 |             self._fpga_io.spi1_transfer( | 
 |                 bytes(frame))[:_SpiFlashFrame.DIGEST_SIZE]) | 
 |  | 
 |  | 
 | class SPIProgrammer: | 
 |     """Class for programming OpenTitan over the SPI interface of SAM3X/U on CW310/305.""" | 
 |     def __init__(self, firmware_path, board='CW305'): | 
 |         """Inits SPIProgrammer with the path of a firmware image and board name.""" | 
 |         self._firmware_path = firmware_path | 
 |         self._board = board | 
 |  | 
 |     def run(self, fpga): | 
 |         """Programs OpenTitan over the SPI interface of SAM3X/U on CW310/305. | 
 |  | 
 |         This implementation has two improvements over the `spiflash` tool under | 
 |         sw/host/spiflash: | 
 |         * Optimizes the happy path by checking the acknowledgement of frame N-1 | 
 |           after transmitting frame N instead of transmitting an extra frame to check | 
 |           each frame's acknowledgement. | 
 |         * Sends an all 0xFF frame as the last frame to avoid cases where the last | 
 |           frame gets corrupted and halts the bootstrapping process. | 
 |  | 
 |         Args: | 
 |           fpga: CW310/305 to be programmed, a ``cw.capture.targets.CW310/305`` instance. | 
 |         """ | 
 |         with _Bootstrap(fpga, self._board) as bootstrap: | 
 |             frame_number = 0 | 
 |             flash_offset = 0 | 
 |             prev_frame = None | 
 |             print(f'Programming OpenTitan with "{self._firmware_path}"...') | 
 |             with open(self._firmware_path, mode='rb') as fw: | 
 |                 # Read fixed-size blocks from the firmware image. | 
 |                 # Note: The second argument ``b''`` to ``iter`` below is the sentinel value that | 
 |                 # ends the loop, i.e. the value returned by ``fw.read`` at EOF. | 
 |                 for payload in iter( | 
 |                         partial(fw.read, _SpiFlashFrame.PAYLOAD_SIZE), b''): | 
 |                     frame = _SpiFlashFrame(frame_number, flash_offset, payload) | 
 |                     # We need to make sure that the previous frame is transmitted correctly before | 
 |                     # proceeding with the next frame. | 
 |                     while True: | 
 |                         # Transmit frame N, receive acknowledgement for frame N-1. | 
 |                         ack = bootstrap.transfer(frame) | 
 |                         if prev_frame and prev_frame.expected_ack != ack: | 
 |                             # When OpenTitan encounters a transmission error or an out of | 
 |                             # sequence frame, it ignores the current frame and sends the ack of | 
 |                             # the last successfully received frame. Since transmission errors | 
 |                             # can occur in both directions, i.e. the previous frame sent by | 
 |                             # SAM3U or the ack sent by OpenTitan, and we can't tell them | 
 |                             # apart, we should re-transmit the previous frame and check the | 
 |                             # ack sent by OpenTitan. | 
 |                             print(f'Error transferring {prev_frame}.') | 
 |                             ack = bootstrap.transfer(prev_frame) | 
 |                             if ack == frame.expected_ack: | 
 |                                 # Ack sent by OpenTitan was corrupted. Proceed with the next frame. | 
 |                                 break | 
 |                             else: | 
 |                                 # Previous frame sent by SAM3U was corrupted. Send current frame | 
 |                                 # again. | 
 |                                 pass | 
 |                         else: | 
 |                             # Correct acknowledgement received or this is the first frame. | 
 |                             break | 
 |                     prev_frame = frame | 
 |                     frame_number += 1 | 
 |                     flash_offset += _SpiFlashFrame.PAYLOAD_SIZE | 
 |             # Transmit an all 0xFF frame with the expected frame number to finish bootstrapping. | 
 |             bootstrap.transfer( | 
 |                 _SpiFlashFrame(frame_number | (1 << 31), flash_offset, b'')) |