Atmosic OTP Tool

Building with Secure Debug

Secure Debug is a collection of hardware and software features to limit access to the debug port for devices in production. It is not intended to be used in development because once security measures are enabled many steps in the normal development flow will no longer function.

Managing the OTP bits

At a hardware level, the debug security state at power-on is defined by two OTP bits (ATM_OTP_MASK_SEC_DBG_DEBUG_SECURED and ATM_OTP_MASK_SEC_DBG_DEBUG_DISABLED). Hardware applies the debug security state prior to the CPU booting. No intervention is required by software to enforce the security state. When secure debug is either SECURED or DISABLED, access through SWD is disallowed even if benign boot is enabled. When the port is SECURED (rather than DISABLED), the state can be cleared by software after a software challenge to prove the identity of the debug access requester. The authenticator is implemented in the MCUboot image that monitors a UART console port.

To check the state of the OTP bits, users can use the atmotp west extension by issuing the following command:

west atmotp get --board <BOARD> --device <DEVICE_ID> --otp SEC_DBG_CONFIG.DEBUG_DISABLED <--jlink or --dl>

or:

west atmotp get --board <BOARD> --device <DEVICE_ID> --otp SEC_DBG_CONFIG.DEBUG_SECURED <--jlink or --dl>

To completely disable secure debug, users can issue the following command (this is irreversible):

west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp SEC_DBG_CONFIG.DEBUG_DISABLED <--jlink or --dl>

To enable secure debug, users can issue the following command:

west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp SEC_DBG_CONFIG.DEBUG_SECURED <--jlink or --dl>

The authenticator software component runs during the boot sequence of MCUboot. Secure debug is not accessible in Non-MCUboot builds. If no authentication occurs, the software will sticky lock the debug port until reset. A Python script is provided to demonstrate communications with the MCUboot authenticator to unlock the debug port. The challenge/authentication process must be performed on each boot. The challenge consists of a unique hash of per-device data stored in the secure journal. This is computed by the MCUboot image and provided as a base64 encoded text output on the UART console port. The hash will be unique for each manufactured device. The challenge must be signed with the private ECDSA key and the resulting signature provided back to the authenticator to verify it using its local public ECDSA key. The signature is unique for the device and can be used for every challenge response.

The atmotp west extension can also be used to inspect and configure other OTP bits. Use list_otp to display the OTP definitions supported by the target platform:

west atmotp list_otp --board <BOARD> --device <DEVICE_ID> <--jlink or --dl>

The output includes the absolute bit index for each OTP definition. A definition that spans multiple bits is shown as a range. The get command returns the value of a definition or of an individual bit, while burn programs one bit at a time. Every definition shown by list_otp is supported by the selected platform’s OTP map. OTP programming is irreversible; check the current value before burning a bit.

Use dump to read and display the complete OTP contents, including the raw OTP value and the value of every definition:

west atmotp dump --board <BOARD> --device <DEVICE_ID> <--jlink or --dl>

The --otp argument accepted by get and burn supports the following addressing forms:

  • An absolute bit index, such as 23

  • A definition followed by a relative bit index, such as BATT_TYPE_LATCH.0

  • A definition followed by a named sub-field, such as SEC_DBG_CONFIG.DEBUG_SECURED

For example, read an absolute bit or a named sub-field with:

west atmotp get --board <BOARD> --device <DEVICE_ID> --otp 23 <--jlink or --dl>
west atmotp get --board <BOARD> --device <DEVICE_ID> --otp SEC_DBG_CONFIG.DEBUG_SECURED <--jlink or --dl>

Reading a multi-bit definition without a suffix returns the complete value. A multi-bit definition cannot be burned as a whole; specify its relative index or named sub-field instead. The burn command displays the current and new values, then asks for confirmation. Enter yes to program the bit or no to cancel. An already-programmed bit is rejected and cannot be re-burned. For example:

west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp 23 <--jlink or --dl>
west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp SEC_DBG_CONFIG.DEBUG_SECURED <--jlink or --dl>

All subcommands require a board and device. Use --jlink for a J-Link connection or --dl for a DL board. If a non-default OpenOCD configuration is needed, provide it with --openocd_config:

west atmotp get --board <BOARD> --device <DEVICE_ID> --otp 23 --openocd_config <OPENOCD_CONFIG> <--jlink or --dl>

Platform OTP Bit Definitions

The OTP bit definitions below are taken from the following atm_otp.tcl OpenOCD configurations:

  • ATM33xx-5: modules/hal_atmosic/ATM33xx-5/openocd/atm_otp.tcl

  • ATM34xx-rev-5: modules/hal_atmosic/ATM34xx/rev-5/openocd/atm_otp.tcl

  • ATM5xxx-rev-2: modules/hal_atmosic/ATM5xxx/rev-2/openocd/atm_otp.tcl

The three platforms use the same bit positions for the brownout-related definitions:

Common OTP definitions

OTP definition

Absolute bit(s)

NABG_TRIM_LATCH

0-8

USE_LDO_LATCH

9

DISABLE_CHPU_LATCH

10

HARV_DISABLED_LATCH

11

RECT_CTUNE_LATCH

12-14

INTERNAL_SPARE_LATCH

15-18

DISABLE_XTAL32K_LATCH

19

NOIND_LATCH

20

DISABLE_VDDIOGEN_LATCH

21

DISABLE_VDDGEN_LATCH

22

DISABLE_5V_LATCH

23

VBATT_LEVEL_LATCH

24

BATT_TYPE_LATCH

25-26

VBATT_GOOD_LATCH

27-29

VBATT_BROWNOUT_LATCH

30-36

MPPT_TYPE_LATCH

37-38

DISABLE_RFHARV_LATCH

39

VHARV_START_LATCH

40-41

VSTORE_GOOD_LATCH

42-43

VSTORE_MAX_LATCH

44-46

RRAM_WRITE_LOCK

48-54

SEC_DBG_CONFIG

60-61

RRAM_JTAG_BYPASS

62

The platform-specific definitions are:

Platform-specific OTP definitions

Absolute bit(s)

ATM33xx-5

ATM34xx-rev-5

ATM5xxx-rev-2

47

SPARE_LATCH

No named definition

SPARE_LATCH

55

SPARE_2_LATCH

No named definition

SPARE_2_LATCH

56

Part of RESERVED_1

UART1_RX_DISABLE

Part of RESERVED_1

57-59

RESERVED_1

No named definition

RESERVED_1

An unlisted bit position has no named definition in that platform’s OpenOCD OTP configuration. Run list_otp for the selected board before using a definition, because the output is platform-specific and can depend on the installed SDK version. The SPARE and RESERVED entries are included for map completeness; do not program them unless instructed by the applicable hardware specification.

On all three platforms, VBATT_LEVEL_LATCH and BATT_TYPE_LATCH affect the brownout voltage calculation:

  • VBATT_LEVEL_LATCH is a one-bit field at absolute bit 24. A value of 0 selects a battery level of 1.8 V or less; a value of 1 selects a battery level above 1.8 V.

  • BATT_TYPE_LATCH is a two-bit field at absolute bits 25-26. Its value is 1 for a rechargeable battery and 3 for a non-rechargeable battery.

For example, read the two fields with:

west atmotp get --board <BOARD> --device <DEVICE_ID> --otp VBATT_LEVEL_LATCH <--jlink or --dl>
west atmotp get --board <BOARD> --device <DEVICE_ID> --otp BATT_TYPE_LATCH <--jlink or --dl>

For a multi-bit definition, the suffix after the period is a zero-based index relative to the definition. For BATT_TYPE_LATCH, .0 addresses absolute bit 25 and .1 addresses absolute bit 26. A multi-bit definition cannot be burned as a whole; specify one relative index for each bit. The following commands show how to address both bits:

west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp VBATT_LEVEL_LATCH <--jlink or --dl>
west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp BATT_TYPE_LATCH.0 <--jlink or --dl>
west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp BATT_TYPE_LATCH.1 <--jlink or --dl>

Burn BATT_TYPE_LATCH.0 only to produce a value of 01 (decimal 1), or burn BATT_TYPE_LATCH.1 only to produce 10 (decimal 2). Burn both bits to produce 11 (decimal 3). Only values defined for the target platform and battery configuration should be programmed. Because OTP bits can only change from 0 to 1, read the current value and plan the desired two-bit value before programming; burning a bit cannot be undone.

The brownout-related multi-bit definitions, such as VBATT_GOOD_LATCH and VBATT_BROWNOUT_LATCH, can be inspected in the same way. Use the complete definition with get to read its value, or append .0, .1, and so on to read or burn one bit. Refer to the brownout configuration documentation for the voltage represented by each field value.

RRAM_WRITE_LOCK is another seven-bit definition in the OpenOCD map. Its named sub-fields are BOOT_BLOCK_0, BOOT_BLOCK_1, BOOT_BLOCK_2, BOOT_BLOCK_3, PROTECTED_DATA, SECURE_COUNTERS, and PRIVATE_KEY_STORAGE at absolute bits 48-54. If the selected platform’s list_otp output exposes these named sub-fields, one sub-field can be programmed with:

west atmotp burn --board <BOARD> --device <DEVICE_ID> --otp RRAM_WRITE_LOCK.PROTECTED_DATA <--jlink or --dl>

SEC_DBG_CONFIG also has the named sub-fields DEBUG_DISABLED at bit 60 and DEBUG_SECURED at bit 61, as shown by the Secure Debug examples above.

Compiling MCUboot with Secure Debug

To build with secure debug, add the following additional flags:

-DCONFIG_ATM_MCUBOOT_SECURE_DEBUG=y -DDTS_EXTRA_CPPFLAGS="-DUSE_ATM_SECURE_DEBUG"

The DTS option -DUSE_ATM_SECURE_DEBUG will enable UART0 as a bi-directional console port for authentication use.

The MCUboot extension for secure debug will use a default private ECC-P256 key to generate the public ECC-P256 key stored in the image. This is a widely distributed key and should not be used in production.

At this time the authenticator implements a 500ms default timeout through CONFIG_ATM_MCUBOOT_UART_DEBUG_AUTH_TIMEOUT_MS while monitoring the console port for characters. You can adjust as needed -DCONFIG_ATM_MCUBOOT_UART_DEBUG_AUTH_TIMEOUT_MS=<milliseconds> to extend the timeout. A future update will support monitoring the UART RX pin for a logic high state to detect the presence of a host UART connection.

Using the Debug Unlock Script

A debug unlock Python script is provided in openair/tools/scripts/sec_debug_unlock.py. This tool requires PySerial.

python sec_debug_unlock.py -v -k <private ECC-P256 key in .pem format> -p <console port>

To unlock using the default private key in openair/lib/atm_debug_auth/

python sec_debug_unlock.py -v -k openair/lib/atm_debug_auth/root-debug-ec-p256.pem -p <console port>

The unlocking script using the -v option will verbosely output:

Sending: b'DBG REQUEST\n'
Received: b'Static Challenge: o9H3wvgqOfAi/mvTV/qvvdNjBqzGILIai3G4OBURjhE=\n'
Unlock Static Challenge
Sending: b'DBG STATIC_RESPONSE sMdx+QFewpAt3Dnqy9BrjSLNxgtObtu3IKhSvpuvbG7J9IClpt/zJL4XRlo9rt7KCCw6orjUIyBdaWWM657aRw==\n'
Received: b'Debug unlocked\n'

The SWD port will be unlocked and MCUboot will remain in a benign state with the processor halted at a WFI instruction (Wait For Interrupt). The developer can freely attach a debugger such as GDB and inspect the target (read memory, set breakpoints). If the debugger allows the CPU to continue then MCUboot will continue its boot from the point at which WFI was entered.