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 |
|---|---|---|---|
0 |
8 B |
Free-form. Only if blank. |
|
1 |
1 B |
OR bitmask. Irreversible. |
|
4 |
32 B |
ROTPK hash. Only if blank. |
|
4 |
32 B |
FW encryption key. Only if blank. |
|
4 |
2 B |
Boot mode disable. Only if blank. |
|
7 |
6 B |
MAC address in tag store. |
|
7 |
1 B |
Consecutive MAC count. |
|
0 |
enum |
Only if blank. Auto-locks |
|
0 |
10 B |
Only if blank. Auto-locks |
|
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 |
|---|---|---|
|
8 |
Free-form 8-byte ID supplied by the OEM. |
|
32 |
SHA-256 of the DER-encoded ROT public key (see Use Case: Signing / Authentication). |
|
32 |
Random 32-byte AES-GCM key (see Use Case: Confidentiality / Encryption). |
|
10 |
Vendor 10-byte JTAG identifier (see Use Case: Secure JTAG). |
|
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.
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.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
-
Allocate a base MAC from the IEEE-assigned vendor prefix (OUI).
-
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 8the 3 least significant bits of the base MAC must be 0.
16the 4 least significant bits of the base MAC must be 0.
32the 5 least significant bits of the base MAC must be 0.
-
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:
|
-
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/imagewould leave an old-keyed bootloader in place. -
system-restartand confirm the chip boots. -
Then program ROTPK via this use case’s RPC.
-
system-restartagain and confirm the boot no longer logsROTPK 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:
-
Generate a random 32-byte SSK and back it up securely:
openssl rand -out my_ssk_key.bin 32Losing this file means no future encrypted firmware can be produced for devices already provisioned with the matching SSK. -
Build with the SSK enabled:
dr cmake --preset lan969x \ -DLAN969X_ENCRYPT_KEY_FILE=$PWD/my_ssk_key.bin dr cmake --build --preset lan969xThe build emits the additional
<prefix>.enc.{bin,fip,img}set alongside the plaintext set. -
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:
-
Push the encrypted set (
-secure.fip→/fw/bootloader,.enc.fip→/fw/image). -
Program OTP SSK via this use case’s RPC.
-
system-restartand 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
-
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).
-
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 573270xDFEFDisable every mode except 4 (QSPI0) and 13 (QSPI0-HS) — recommended production lock-down
0xDFEFis all 16 bits set except bit 4 (0x0010) and bit 13 (0x2000):0xFFFF & ~(0x0010 | 0x2000).0xDFEFalso 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/bootloaderand/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.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’sSJTAG_UUIDmmio 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)
0b000
Open — no JTAG protection, no auth required.
secure-boundary-scan0b010
IEEE 1149.1 boundary scan open; CPU debug requires SSK auth.
secure-full0b100
Every JTAG operation requires SSK auth.
closed0b111
JTAG permanently disabled; no auth path.
Once authenticated,
secure-boundary-scanandsecure-fullare identical; the two modes differ only in what is reachable in the locked, pre-auth state. Picksecure-boundary-scanonly when a downstream partner needs ICT / boundary-scan access without holding the SSK; otherwise prefersecure-fullfor the strongest pre-auth lockdown. Pickclosedonly 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.
|
|
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 |
|---|---|---|
|
|
Lock Region 0 (manufacturing data: serial number) |
|
|
Lock Region 4 (keys: ROTPK, SSK, strap-disable) |
|
|
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
0on 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 |
|
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
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(carriestb_fw.crt, anchored against OTP ROTPK by BL1). -
lan969x_<board>.fip(or.enc.fipif 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 |
|---|---|
|
A base64 binary leaf decodes to the wrong number of bytes (e.g. a ROTPK that is not 32 bytes). |
|
The |
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 unrecoverablepartial 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.