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
23A definition followed by a relative bit index, such as
BATT_TYPE_LATCH.0A 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.tclATM34xx-rev-5:
modules/hal_atmosic/ATM34xx/rev-5/openocd/atm_otp.tclATM5xxx-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:
OTP definition |
Absolute bit(s) |
|---|---|
|
0-8 |
|
9 |
|
10 |
|
11 |
|
12-14 |
|
15-18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25-26 |
|
27-29 |
|
30-36 |
|
37-38 |
|
39 |
|
40-41 |
|
42-43 |
|
44-46 |
|
48-54 |
|
60-61 |
|
62 |
The platform-specific definitions are:
Absolute bit(s) |
ATM33xx-5 |
ATM34xx-rev-5 |
ATM5xxx-rev-2 |
|---|---|---|---|
47 |
|
No named definition |
|
55 |
|
No named definition |
|
56 |
Part of |
|
Part of |
57-59 |
|
No named definition |
|
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_LATCHis a one-bit field at absolute bit 24. A value of0selects a battery level of 1.8 V or less; a value of1selects a battery level above 1.8 V.BATT_TYPE_LATCHis a two-bit field at absolute bits 25-26. Its value is1for a rechargeable battery and3for 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.