LAN969x OTP Provisioning

1. Overview

OTP (One-Time Programmable) provisioning allows programming security keys, device identity and configuration into the on-chip OTP memory. OTP writes are irreversible — once a field is written it cannot be changed, and once a region is write-protected the chip must be discarded to change it.

A customer typically provisions OTP to: anchor secure boot to their own signing key (Root-of-Trust), give each unit a stable identity (serial number and MAC addresses), enable firmware encryption, lock down JTAG debug access, and disable unused boot-strap modes — then write-protect the regions so the configuration can never be altered in the field.

The otp-provisioning-1 RPC is available via the CORECONF management interface. It targets chips implementing MCHP-OTP-Schema 1 and is currently only implemented on LAN969x.

All input leafs are optional — only the fields provided will be programmed. The RPC validates all input before writing anything. If any check fails, the entire operation is rejected with no side effects.

1.1. OTP Regions (LAN969x)

The OTP is divided into 8 regions with independent write-protection:

REG Description

0

Manufacturing data (serial number, JTAG config, etc.)

1

Write-protect bitmask

2

Region boundary definitions

3

Reserved

4

Keys and security (ROTPK, SSK, strap disable)

5

Non-volatile secure counters (must remain writable)

6

Reserved

7

Key-value tag store (must remain writable for MAC updates)

1.2. Available Fields

The following fields can be programmed via the RPC:

Field Region Size Notes

serial-number

0

8 B

Free-form. Only if blank.

write-protect

1

1 B

OR bitmask. Irreversible.

root-of-trust-public-key-sha

4

32 B

ROTPK hash. Only if blank.

secret-symmetric-key

4

32 B

FW encryption key. Only if blank.

strap-disable-mask

4

2 B

Boot mode disable. Only if blank.

eth-mac-addr

7

6 B

MAC address in tag store.

eth-mac-addr-count

7

1 B

Consecutive MAC count.

secure-jtag-conf

0

enum

Only if blank. Auto-locks SECURE_JTAG.

secure-jtag-uuid

0

10 B

Only if blank. Auto-locks JTAG_UUID.

secure-jtag-shared-secret-key

4

32 B

SJTAG auth key. Only if blank.

By default, OTP fields can only be programmed once — only while the field is still blank in hardware (every bit zero), referred to as the HW-blank rule. This applies to serial-number, ROTPK, SSK, strap-disable-mask and the three secure-jtag-* leafs. Two fields are exceptions: write-protect is OR-ed into the hardware value and can be updated multiple times (irreversibly setting additional bits); eth-mac-addr and eth-mac-addr-count live in the Region 7 tag store and can be re-written (the old record is invalidated, a new one is appended).
secure-jtag-conf and secure-jtag-uuid go further than the HW-blank rule — a successful write atomically also burns the matching OTP_PRG[0] lock bit (bit 0 for SECURE_JTAG, bit 1 for JTAG_UUID). The OTP HW FSM requires this lock for the value to take effect at cold reset, so the auto-burn is functionality-critical, not just defence-in-depth (see Use Case: Secure JTAG).

2. Field Encoding

Five RPC leafs carry raw binary values (8 to 32 bytes) that, in the YAML payload, must be base64-encoded — this is how YANG-CBOR (type binary) is rendered in YAML/JSON per RFC 9254 §6.4.

Leaf Bytes Source

serial-number

8

Free-form 8-byte ID supplied by the OEM.

root-of-trust-public-key-sha

32

SHA-256 of the DER-encoded ROT public key (see Use Case: Signing / Authentication).

secret-symmetric-key

32

Random 32-byte AES-GCM key (see Use Case: Confidentiality / Encryption).

secure-jtag-uuid

10

Vendor 10-byte JTAG identifier (see Use Case: Secure JTAG).

secure-jtag-shared-secret-key

32

Random 32-byte SJTAG challenge/response key (see Use Case: Secure JTAG).

The remaining leafs (strap-disable-mask, write-protect, eth-mac-addr, eth-mac-addr-count, secure-jtag-conf) are integers, dash-separated MAC strings, or YANG enumeration names — they are NOT base64 and appear verbatim in YAML.

2.1. Encode bytes to base64 (for RPC input)

# A binary file (e.g. a 32-byte ROTPK hash or SSK key) -> single-line base64
base64 -w 0 my_field_bytes.bin

# An ad-hoc 8-byte serial captured as hex -> base64
printf '\x01\x02\x03\x04\x05\x06\x07\x08' | base64 -w 0    # AQIDBAUGBwg=

2.2. Decode base64 back to bytes (for sanity check)

echo -n 'AQIDBAUGBwg=' | base64 -d | xxd
# 00000000: 0102 0304 0506 0708                      ........

# round-trip an existing 32-byte file
base64 -w 0 my_field_bytes.bin | base64 -d | sha256sum
sha256sum my_field_bytes.bin
# both lines must print the same hash

3. Provisioning Use Cases

This section walks through the OTP fields the customer is likely to program, organised as use cases. Each use case follows the same shape:

  • Why — when this is needed and what it provides.

  • Preparation — inputs to gather and build steps to run beforehand.

  • How — Construct the RPC — the YAML body to POST.

  • Verification — how to confirm the value landed correctly.

For the recommended production workflow that combines them into a single RPC, see Recommendations.

3.1. Use Case: Serial Number

3.1.1. Why

An 8-byte free-form OEM identifier used to track each unit through manufacturing, returns (RMA), and field deployments. Once written, the value is exposed for runtime queries via serial-num (ietf-hardware). The field lives in Region 0 and can only be programmed once (HW-blank rule).

3.1.2. Preparation

Decide the 8 raw bytes — typically a manufacturing serial drawn from the OEM tracking system. Base64-encode them (see Field Encoding):

# Bytes 01 02 03 04 05 06 07 08 -> AQIDBAUGBwg=
printf '\x01\x02\x03\x04\x05\x06\x07\x08' | base64 -w 0

3.1.3. How — Construct the RPC

- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      serial-number: AQIDBAUGBwg=

3.1.4. Verification

Fetch /ietf-hardware:hardware; the value appears as a hex-formatted string under the board component’s serial-num leaf.

3.2. Use Case: MAC Address

3.2.1. Why

Each switch port (and the management interface) consumes one MAC address. The chip is provisioned with a base MAC plus a count of consecutive addresses; the hardware derives each port’s address by adding to the base. The pair lives in the OTP key-value tag store (Region 7) — the only OTP field that can be updated multiple times (the old record is invalidated, a new one is appended).

3.2.2. Preparation

  1. Allocate a base MAC from the IEEE-assigned vendor prefix (OUI).

  2. Choose the count. The count must be a power of 2 (1, 2, 4, 8, 16, 32, …​). The hardware allocates the block by masking the low bits of the base MAC, so the count’s bit width imposes a fixed alignment:

    count Required base-MAC alignment

    8

    the 3 least significant bits of the base MAC must be 0.

    16

    the 4 least significant bits of the base MAC must be 0.

    32

    the 5 least significant bits of the base MAC must be 0.

  3. Size for the device: software needs at least number-of-ports + 1 MACs (one per port, plus the management interface), rounded up to the next power of 2. A 20-port switch needs 21 → rounded to 32. Production builds typically use count = 32.

3.2.3. How — Construct the RPC

The base MAC is a dash-separated string and is not base64.

- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      eth-mac-addr: "00-04-a3-01-02-00"
      eth-mac-addr-count: 32

3.2.4. Verification

After programming, port MAC addresses are derivable as <base> + interface_index. Fetch any port’s phys-address (ietf-interfaces) to spot-check.

Each MAC update consumes an additional 8-byte record slot in Region 7 (the old record becomes dead weight). On LAN969x, Region 7 holds up to 1792 records shared with other tag types. The RPC fails if no free slots remain.

3.3. Use Case: Signing / Authentication

3.3.1. Why

Anchor the device to a single Root-of-Trust (ROT) key so that only firmware signed by that key can boot. Once the OTP ROTPK is programmed, BL1 (ROM) at every boot computes the SHA-256 of the public key embedded in the firmware’s signing certificate, compares it against the OTP value, and refuses to boot if it doesn’t match.

LAN969x firmware is split into a bootloader and an application. The bootloader (sometimes referred to as BL2) is the build artifact lan969x_<board>_<variant>-secure.fip. The application is the normal .fip file, which is also used for over-the-air updates (internally the FIP holds images for the two TrustZone domains, BL31 and BL33). Both the bootloader and the application are checked by secure boot, so when deploying secure boot both must be signed with the ROT key. To ease production a .img file is also generated; it contains both the bootloader and the application, replicated across two slots.

The ROT private key stays on the build host — only the SHA-256 of the public key is written to OTP.

3.3.2. Preparation

The value written to OTP is simply the SHA-256 of the public ECDSA key. Derive it with openssl alone — the build system is not needed for this:

# 1. Generate the ROT key pair
openssl ecparam -name prime256v1 -genkey -noout -out my_rotprivk_ecdsa.pem

# 2. Derive the public key (DER)
openssl ec -in my_rotprivk_ecdsa.pem -pubout -outform DER \
    -out my_rotpubk_ecdsa.der

# 3. SHA-256 of the public key -- this is the OTP ROTPK value
openssl dgst -sha256 -binary -out rotpubk_sha256.bin my_rotpubk_ecdsa.der

# 4. base64-encode it for the RPC input (see "How" below)
base64 -w 0 rotpubk_sha256.bin
The ROT private key is the root of all device trust — anyone who holds it can sign firmware the chip will accept, and losing it means no future firmware can ever be signed for units already provisioned with the matching OTP ROTPK. Generate it on a secure host and keep it safe.
target/lan969x/rotprivk_ecdsa.pem in the source tree is an example key only — it is published in the tree and offers no protection. Use it for development against blank-ROTPK chips, never in production.

Then build the matching signed firmware — the build signs the FIP with the ROT key:

dr cmake --preset lan969x \
    -DLAN969X_ROT_KEY_FILE=$PWD/my_rotprivk_ecdsa.pem
dr cmake --build --preset lan969x

This emits the signed bootloader (lan969x_<board>-secure.fip) and application (.fip / .img) anchored at the ROT key. Anti-rollback NV counters are configured separately at build time — see Anti-Rollback (NV Counters).

3.3.3. How — Construct the RPC

The placeholder below is illustrative — replace it with the base64 string from the preparation step.

- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      root-of-trust-public-key-sha: ZmFrZS0zMi1ieXRlLVJPVFBLLVNIQTI1Ni1hbmNob3I=

3.3.4. Verification

Secure boot anchors the OTP ROTPK while authenticating each sign-ed root certificate: the bootloader root (id=6, the tb_fw cert, checked by BL1) and the application root (id=7, the trusted-key cert that roots the BL31 + BL33 chain, checked by BL2). The trace on those two images is what changes once the ROTPK is programmed.

While the cell is still blank, neither root can be anchored against OTP, so each logs the same NOTICE:

INFO:    Authenticating image id=6 (sign)
NOTICE:  ROTPK is not deployed on platform. Skipping ROTPK verification.
INFO:    Authenticated image id=6 (sign) = 0
...
INFO:    Authenticating image id=7 (sign)
NOTICE:  ROTPK is not deployed on platform. Skipping ROTPK verification.
INFO:    Authenticated image id=7 (sign) = 0

After the ROTPK is programmed, the very next boot anchors both roots against the OTP value — the ROTPK is not deployed NOTICE is gone and the same certs authenticate silently:

INFO:    Authenticating image id=6 (sign)
INFO:    Authenticated image id=6 (sign) = 0
...
INFO:    Authenticating image id=7 (sign)
INFO:    Authenticated image id=7 (sign) = 0

A ROTPK mismatch fails this step and halts the boot before the corresponding image runs.

Order matters. While OTP ROTPK is blank, BL1 reports ROTPK is not deployed and accepts any validly-signed FIP (no anchor match). Once ROTPK is programmed, the next boot strictly compares the signing cert’s ROT pubkey hash against OTP — a mismatch halts BL1 with no in-band software recovery (neither /fw/bootloader nor /fw/image runs); recovery then requires the debrick procedure. Recommended sequence:
  1. Push the matching set first — both the bootloader FIP (lan969x_<board>-secure.fip/fw/bootloader) and the application FIP (lan969x_<board>.fip/fw/image) from the same ROT-key build (see Firmware Update). They are signed independently, so updating only /fw/image would leave an old-keyed bootloader in place.

  2. system-restart and confirm the chip boots.

  3. Then program ROTPK via this use case’s RPC.

  4. system-restart again and confirm the boot no longer logs ROTPK is not deployed — BL1 now anchors against the programmed OTP value.

3.3.5. Re-signing the FWU recovery tool

The FWU recovery tool (fwu-…​html) is a development / recovery tool — it can access OTP and perform other insecure operations. Re-signing it with the production ROT key lets the provisioned devices boot it, which would expose those insecure operations on production hardware. Treat the re-signed tool as sensitive: keep it under access control and never ship it in a production release.

The upstream FWU recovery tool shipped at https://github.com/microchip-ung/arm-trusted-firmware/releases/ (e.g. fwu-lan969x_a0-release.html) embeds a BL2U FIP whose fwu_cert.crt is signed by the TFA sample ROT key. After a customer ROTPK is burned, BL1 will refuse to load that BL2U during recovery.

support/scripts/lan969x-fwu-resign re-signs the embedded BL2U cert with the customer’s ROT key without touching the BL2U binary itself:

dr ./support/scripts/lan969x-fwu-resign \
    --input-html  fwu-lan969x_a0-release.html \
    --output-html fwu-lan969x_a0-release.customer.html \
    --rot-priv    my_rotprivk_ecdsa.pem

Outputs:

  • <output-html> — the same HTML scaffolding with the embedded BL2U FIP re-signed.

  • <output-html stem>.fwu_fip.bin — the regenerated BL2U FIP, in case the customer prefers the standalone binary.

Ship the resulting HTML alongside the customer-keyed application firmware. The BL2U binary itself is unchanged across the operation; only the cert chain anchored at the new ROT key is regenerated.

3.4. Use Case: Confidentiality / Encryption

3.4.1. Why

AES-GCM-encrypt BL31 and BL33 against the OTP Secret Symmetric Key (SSK) so the firmware payloads cannot be read off the boot media (NOR flash). BL2 auto-detects the encryption header, decrypts with the OTP SSK, and then authenticates the plaintext against the signing certs. BL2 itself, and the cert chain stay plaintext — they carry the signing material, not secrets.

Encryption is a complement to signing, not a replacement. A device with no SSK programmed can run signed plaintext images just fine; SSK only matters when confidentiality is also required.

3.4.2. Preparation

Encryption is off by default. The standard build produces only the plaintext artifact set (<prefix>.bin, .fip, .img, -secure.fip). To enable:

  1. Generate a random 32-byte SSK and back it up securely:

    openssl rand -out my_ssk_key.bin 32
    Losing this file means no future encrypted firmware can be produced for devices already provisioned with the matching SSK.
  2. Build with the SSK enabled:

    dr cmake --preset lan969x \
        -DLAN969X_ENCRYPT_KEY_FILE=$PWD/my_ssk_key.bin
    dr cmake --build --preset lan969x

    The build emits the additional <prefix>.enc.{bin,fip,img} set alongside the plaintext set.

  3. Base64-encode the 32 bytes for the RPC:

    base64 -w 0 my_ssk_key.bin

3.4.3. How — Construct the RPC

The placeholder below is illustrative — replace it with the base64 from the preparation step.

- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      secret-symmetric-key: ZmFrZS0zMi1ieXRlLUFFUy1HQ00tU1NLLWV4YW1wbGU=

3.4.4. Verification

BL2 decrypts BL31/BL33 with whatever bytes are in OTP SSK — including all zeros if the cell is blank. <prefix>.enc.img boots only when the OTP value exactly matches the SSK the image was encrypted with; any mismatch fails the AES-GCM integrity check and BL2 aborts the load. Recommended sequence:

  1. Push the encrypted set (-secure.fip/fw/bootloader, .enc.fip/fw/image).

  2. Program OTP SSK via this use case’s RPC.

  3. system-restart and confirm the chip boots.

Steps 1 and 2 can run in any order before the reboot — only the next reset enforces the new pairing.

Plaintext <prefix>.img always boots regardless of OTP SSK, so re-pushing it to /fw/image is a safe recovery if the encrypted flow goes wrong before any region is locked.
<prefix>-secure.fip is identical regardless of encryption (it carries no encrypted payloads); it can be deployed to /fw/bootloader from any build.
When confidentiality is required, distribute only the encrypted images (<prefix>.enc.fip / .enc.img) — delete the plaintext <prefix>.fip / .img so they cannot be shipped or booted in the field. Keep the plaintext set only on the build host for the recovery path noted above.

3.5. Use Case: STRAP Disable

3.5.1. Why

Permanently refuse boot via specific strap-mode pins. Useful to lock down debug or recovery boot modes on production hardware so they cannot be entered even with physical access to the strap pins.

The strap-disable-mask leaf is a 16-bit value where bit N disables strap mode N in BL1 (ROM). The OTP cell is OR-merged at the hardware level — a follow-up RPC can only add more disabled modes, never clear a previously set bit.

3.5.2. Preparation

  1. Pick the modes to disable. Strap-mode bit map (LAN969x):

    Bit Mode VelocityDRIVE-SP-Debug VelocityDRIVE-SP

    0

    eMMC + FC0 trace

    1

    QSPI0 + FC0 trace

    2

    SDCard + FC0 trace

    3

    eMMC

    4

    QSPI0

    5

    SDCard

    6

    PCIe EndPoint

    7

    Reserved

    8

    QSPI0-HS + FC0 trace

    9

    Reserved

    10

    TF-A Monitor FC0

    11

    TF-A Monitor FC0-HS

    12

    Reserved

    13

    QSPI0-HS

    14

    Reserved

    15

    SPI Client (brick-recovery)

    Column Meaning

    Bit / Mode

    The strap-mode bit and the boot source it selects (a clear bit = that mode enabled).

    VelocityDRIVE-SP-Debug

    Boot modes useful while bringing up / developing on VelocityDRIVE-SP (1, 4, 8, 10, 11, 13).

    VelocityDRIVE-SP

    Boot modes recommended to leave open in production: 4 and 13 (QSPI0 / QSPI0-HS NOR boot).

  2. Compute the bitmask. VelocityDRIVE-SP boots from NOR (QSPI0 / QSPI0-HS), so the recommended production lock-down leaves only modes 4 and 13 open and disables everything else:

    Decimal Hex Effect

    57327

    0xDFEF

    Disable every mode except 4 (QSPI0) and 13 (QSPI0-HS) — recommended production lock-down

    0xDFEF is all 16 bits set except bit 4 (0x0010) and bit 13 (0x2000): 0xFFFF & ~(0x0010 | 0x2000).

    0xDFEF also disables the TF-A monitor boot modes (10 and 11), so once it is programmed there is no monitor-based firmware download path left — firmware can then only be updated through the management interface (/fw/bootloader and /fw/image).
Always leave at least one of the boot modes actually in use clear — otherwise the chip will refuse to boot and there is no software recovery.
Verify the value before sending. The cell is OR-merged at the HW level, so a follow-up RPC can only add more disabled modes, never clear an already-set bit.

3.5.3. How — Construct the RPC

- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      strap-disable-mask: 57327

3.5.4. Verification

Reboot the chip into the disabled mode and confirm BL1 refuses boot. There is no runtime read-back of the mask from non-secure world.

3.6. Use Case: Secure JTAG

3.6.1. Why

Lock down JTAG access on production silicon so a debugger cannot read internal state or drive the CPU without first authenticating against a device-specific secret. Three cooperating leafs are involved:

  • secure-jtag-shared-secret-key — a 32-byte symmetric key in OTP Region 4. On every cold reset BL1 reads this key, derives a TRNG-nonce digest, and programs the SJTAG controller; a debug client must hold the same 32-byte secret to pass challenge/response authentication.

  • secure-jtag-uuid — a 10-byte vendor identifier in OTP Region 0. Sampled by the SJTAG controller at cold reset and exposed in the controller’s SJTAG_UUID mmio register; useful as a per-device identity binding for unlock-challenge workflows.

  • secure-jtag-conf — an enumeration that selects the JTAG security mode:

    enum value JTAG_MODE JTAG_DISABLE Description

    (omit / factory)

    0b00

    0

    Open — no JTAG protection, no auth required.

    secure-boundary-scan

    0b01

    0

    IEEE 1149.1 boundary scan open; CPU debug requires SSK auth.

    secure-full

    0b10

    0

    Every JTAG operation requires SSK auth.

    closed

    0b11

    1

    JTAG permanently disabled; no auth path.

    Once authenticated, secure-boundary-scan and secure-full are identical; the two modes differ only in what is reachable in the locked, pre-auth state. Pick secure-boundary-scan only when a downstream partner needs ICT / boundary-scan access without holding the SSK; otherwise prefer secure-full for the strongest pre-auth lockdown. Pick closed only to give up CPU debug forever.

The factory default is Open (no JTAG protection). To leave the device Open, simply omit secure-jtag-conf from the RPC.

secure-jtag-conf is one-way and irreversible. Only transitions out of the factory-default Open state are accepted:

open -> secure-boundary-scan
open -> secure-full
open -> closed
A locked SECURE_JTAG / JTAG_UUID field cannot be recovered. Verify the inputs before issuing the RPC.

3.6.2. Preparation

Decide the mode (see the table above), then prepare the inputs for each leaf to be programmed.

1. Generate the SSK (recommended for every secure mode):

openssl rand -out my_sjtag_ssk.bin 32
Losing this file means no debug client can ever authenticate against the chip again — and on secure-full / closed parts that means no CPU debug, ever. Back it up like the ROT private key.

Base64-encode for the RPC:

base64 -w 0 my_sjtag_ssk.bin

2. Pick the UUID (10 raw bytes, vendor-defined). The bytes are free-form; a common pattern is a serial-number-like sequence drawn from the OEM tracking system. Example — the bytes 01..0a:

printf '\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a' | base64 -w 0
# AQIDBAUGBwgJCg==

3. Pick the mode — one of secure-boundary-scan, secure-full or closed. Per sw-doc-lan966x-secure-boot.pdf §6 the SSK must already be in OTP before the SJTAG controller is asked to authenticate, so program SSK in the same RPC as secure-jtag-conf (or in a strictly earlier RPC). The combined RPC in the next subsection gets the ordering right.

3.6.3. How — Construct the RPC

All three leafs can be submitted in a single RPC — the validator runs every check before any write, so a typo in any leaf aborts the whole RPC with no partial commit. Within the SJTAG cluster the firmware always writes secure-jtag-shared-secret-key before secure-jtag-conf, so the SSK is in place before the controller is asked to authenticate on the next reset (matching the §6 ordering rule).

The placeholders below are illustrative — replace them with the values from the preparation step.

- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      secure-jtag-shared-secret-key: ZmFrZS0zMi1ieXRlLVNKVEFHLVNTSy1leGFtcGxl
      secure-jtag-uuid: AQIDBAUGBwgJCg==
      secure-jtag-conf: secure-full
secure-jtag-shared-secret-key lives in Region 4 alongside root-of-trust-public-key-sha, secret-symmetric-key and strap-disable-mask, and is locked together with them by write-protect: 16 (or 17 to also lock Region 0, which freezes SECURE_JTAG / JTAG_UUID at the region level on top of the per-field auto-lock). See Use Case: Write Protect.

3.6.4. Verification

The configured mode takes effect on the next cold reset. Run system-restart, then on the next boot:

  • For secure-boundary-scan / secure-full: attach a debug client holding the SSK and confirm authentication succeeds; an unauthenticated client must be rejected on the modes the chosen enum locks down.

  • For closed: confirm no JTAG client can attach (e.g. DSTREAM connection refused). This is one-way — there is no way back.

To unlock a secure-boundary-scan / secure-full part for debugging, load the Arm Developer Studio configuration database from sw-armds-configdb-boards and enter the 32-byte SSK (as 64 hex characters) in the connection’s Secure JTAG Unlock Key field; its DTSL script runs the JTAG unlock sequence automatically.

3.7. Use Case: Write Protect

3.7.1. Why

Lock OTP regions to ensure that fields meant to stay unprovisioned remain unprovisioned, and that already-provisioned fields cannot be altered (e.g. by OR-ing more data into them). After all sensitive programming is done, locking the relevant regions freezes the chip’s configuration.

write-protect is the one OTP field that does not follow the HW-blank rule — it is OR-merged into the existing value, so this RPC can be called multiple times to add more locked regions, but never to clear them.

3.7.2. Preparation

Decide which regions are ready to lock. Each bit locks one region: bit N → Region N. Common production locks:

Decimal Hex Effect

1

0x01

Lock Region 0 (manufacturing data: serial number)

16

0x10

Lock Region 4 (keys: ROTPK, SSK, strap-disable)

17

0x11

Lock Regions 0 + 4 (typical production end-state)

Bits 5 and 7 are forbidden. Region 5 holds non-volatile secure counters required for rollback protection; Region 7 is the key-value tag store that must remain writable for MAC address updates. The RPC will reject requests with bit 5 or bit 7 set.

3.7.3. How — Construct the RPC

- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      write-protect: 17

This sets bits 0 and 4 (0x11), locking Region 0 (manufacturing data) and Region 4 (keys). If Region 0 was already locked, only Region 4 is additionally locked.

3.7.4. Verification

Subsequent RPCs targeting fields in the locked regions return the structured error Region is write-protected (see Error Handling).

Once a region is write-protected, it can never be written again. Make sure all desired fields are programmed before locking.

4. Anti-Rollback (NV Counters)

The anti-rollback NV counters are independent of the Root-of-Trust — they guard against firmware downgrade rather than authenticity. The hardware holds a monotonic counter in OTP and secure boot refuses to run any image whose counter is lower than the value already latched in the chip.

The model is:

  • Start at 0 on fresh silicon.

  • When a critical security issue is fixed, increment the counter in the build that carries the fix.

  • On the next boot the hardware advances its OTP counter to the image’s value automatically, and from then on refuses to run any older image — so an attacker cannot reflash a vulnerable build to re-open the issue.

The counter is stored in OTP as a bit field — each increment burns one more fuse bit and is irreversible (monotonic). The field is 32 bytes, so it can be incremented at most 256 times over the chip’s whole lifetime. Do not bump it on every build; increment it only when shipping a fix for a critical security issue.

The counters are baked into the signed image at build time (LAN969X_FW_NVCTR covers the secure firmware BL2/BL31, LAN969X_NTFW_NVCTR the non-trusted BL33):

dr cmake --preset lan969x \
    -DLAN969X_ROT_KEY_FILE=$PWD/my_rotprivk_ecdsa.pem \
    -DLAN969X_FW_NVCTR=0 -DLAN969X_NTFW_NVCTR=0
dr cmake --build --preset lan969x

Incrementing a counter requires building and deploying a new image (both -secure.fip and .fip), exactly like any other firmware update.

A build that omits these flags bakes the Microchip build default, which is not 0. On fresh silicon always pass -DLAN969X_FW_NVCTR=0 -DLAN969X_NTFW_NVCTR=0 explicitly. Once a chip has latched a counter value the hardware refuses any image built with a lower one, so never boot a lower-counter image after a higher one.

5. Recommendations

For a production unit, all sensitive OTP fields and the write-protect lock should be programmed in a single RPC. The implementation validates every field before writing any of them, so a single typo aborts the operation cleanly with no partial writes.

Recommended order, with the leaf that programs each item:

Step Use case Leaf

1

Use Case: Serial Number

serial-number

2

Use Case: MAC Address

eth-mac-addr + eth-mac-addr-count

3

Use Case: Signing / Authentication

root-of-trust-public-key-sha

4

Use Case: Confidentiality / Encryption

secret-symmetric-key

5

Use Case: STRAP Disable

strap-disable-mask

6

Use Case: Secure JTAG

secure-jtag-shared-secret-key + secure-jtag-uuid + secure-jtag-conf

7

Use Case: Write Protect

write-protect

5.1. Combined production RPC (steps 1-7)

Replace each placeholder below with the value from the matching use case’s preparation step. Steps 1-7 fit into a single POST; the implementation rejects the whole RPC on any validation failure, so either everything lands or nothing does.

The strap-disable-mask value shown (57327 / 0xDFEF) is the recommended production lock-down — only modes 4 and 13 (QSPI0 / QSPI0-HS NOR boot) stay open. It also disables the TF-A monitor, so after this RPC firmware can only be updated via /fw/bootloader and /fw/image (see Use Case: STRAP Disable).
write-protect: 17 is irreversible. Once Region 4 is locked, ROTPK, SSK, STRAP_DISABLE and OTP_SJTAG_SSK can never be modified again. secure-jtag-conf and secure-jtag-uuid are additionally locked at the per-field level by their OTP_PRG[0] auto-burn (see Use Case: Secure JTAG). Verify every value above is correct before issuing this RPC.
- "/mchp-velocitysp-system:otp-provisioning-1":
    input:
      serial-number: AQIDBAUGBwg=
      eth-mac-addr: "00-04-a3-01-02-00"
      eth-mac-addr-count: 32
      root-of-trust-public-key-sha: ZmFrZS0zMi1ieXRlLVJPVFBLLVNIQTI1Ni1hbmNob3I=
      secret-symmetric-key: ZmFrZS0zMi1ieXRlLUFFUy1HQ00tU1NLLWV4YW1wbGU=
      strap-disable-mask: 57327
      secure-jtag-shared-secret-key: ZmFrZS0zMi1ieXRlLVNKVEFHLVNTSy1leGFtcGxl
      secure-jtag-uuid: AQIDBAUGBwgJCg==
      secure-jtag-conf: secure-full
      write-protect: 17

5.2. Build the matching firmware

Provisioning and firmware go together: the image loaded for the combined RPC above must be built with all the options the RPC programs — the customer ROT key (signing), the SSK (encryption) and the NV counters (anti-rollback) — so the signed/encrypted image matches what OTP will enforce on the next boot:

dr cmake --preset lan969x \
    -DLAN969X_ROT_KEY_FILE=$PWD/my_rotprivk_ecdsa.pem \
    -DLAN969X_ENCRYPT_KEY_FILE=$PWD/my_ssk_key.bin \
    -DLAN969X_FW_NVCTR=0 -DLAN969X_NTFW_NVCTR=0
dr cmake --build --preset lan969x

This produces the full artifact set: the signed bootloader (lan969x_<board>-secure.fip) and the signed + encrypted application (lan969x_<board>.enc.fip / .enc.img). If confidentiality is not required, omit -DLAN969X_ENCRYPT_KEY_FILE and load the plaintext .fip / .img instead.

5.3. Push matching firmware (before reboot)

After the RPC succeeds and before issuing system-restart, push the matching customer-keyed firmware to flash:

  • lan969x_<board>-secure.fip/fw/bootloader (carries tb_fw.crt, anchored against OTP ROTPK by BL1).

  • lan969x_<board>.fip (or .enc.fip if encryption was also enabled) → /fw/image.

OTP changes only take effect on the next reset, and the currently-running BL1/BL2 already authorised this boot under the previous OTP state — so doing the OTP RPC and the firmware push in the same pre-reboot window saves one reboot and removes the "OTP programmed but firmware still old" failure mode.

system-restart once both are in place; the very first boot after the reset lands the new OTP and the matching firmware in lockstep.

6. Error Handling

Failures fall into two classes.

User-fixable input errors return CoAP 4.00 Bad Request with a structured payload naming the failing field and a human-readable error-message:

error-message Cause

Field length mismatch

A base64 binary leaf decodes to the wrong number of bytes (e.g. a ROTPK that is not 32 bytes).

Bits 5/7 must not be set

The write-protect value sets a forbidden bit (Region 5 or Region 7).

Example:

- "/ietf-coreconf:error":
    error-data-node: "/mchp-velocitysp-system:otp-provisioning-1//write-protect"
    error-tag: ietf-coreconf:invalid-value
    error-message: Bits 5/7 must not be set

Irreversible-state or hardware failures return CoAP 5.00 with no payload — they cannot be corrected by re-sending, so no error-message is provided; the specific reason is emitted to the device trace with a numeric SID. These include:

  • the target field’s region is already write-protected;

  • the field is not blank — already programmed (HW-blank rule);

  • the current OTP value or write-protect state could not be read;

  • the OTP hardware / secure monitor rejected the write;

  • a secure-jtag-* rejection — {SECURE_JTAG,JTAG_UUID} already programmed, {SECURE_JTAG,JTAG_UUID} locked by OTP_PRG[0], or the unrecoverable partial commit: …​ burn failed (requires Microchip support).

All input is validated before any OTP write, so a validation failure guarantees no partial writes. A partial write is only theoretically possible if a hardware fault occurs mid-sequence — in particular the non-atomic secure-jtag-* value + OTP_PRG[0] auto-lock burn.