LAN969X OTP Configuration from Linux userspace

The OTP (One Time Programmable) memory in LAN966X can be programmed from Linux userspace using a tool that is included in the root file system of the device.

The next sections describes how to read and write to OTP and how to protect data from being overwritten or read from the non-secure world (outside the secure boot stages).

1. SoC Resources

LAN969X SoC has 16K of internal OTP.

2. Kernel configurations

Following kernel config options should be enabled to enable the LAN969X OTP driver and to be able to access the OTP from userspace.

  • CONFIG_NVMEM_MICROCHIP_OTP - None volatile memory driver config option.

  • CONFIG_NVMEM_SYSFS - Config option to enable access to OTP using sysfs.

2.1. LAN969X Device Tree

To enable LAN969X OTP, following configurations are required in device tree:

  • compatible - string must be set to microchip,lan969x-otpc.

  • reg - property must be set to <0xe0021000 0x300>.

2.1.1. Example

Following example shows how the OTP should be defined in DT:

otp0: otp@e0021000 {
    compatible = "microchip,lan969x-otp";
    reg = <0xe0021000 0x300>;
};

2.2. References

3. UserSpace

Before using the userspace application to configure the OTP, make sure that the OTP device is created. If the device is created, the following file should exist: /sys/bus/nvmem/devices/lan9662-otp0/nvmem, which can be verified with use of the ls -l command:

# ls -l /sys/bus/nvmem/devices/lan9662-otp0/nvmem
-rw-r--r--    1 root     root         16384 Jan  3 01:10 /sys/bus/nvmem/devices/lan9662-otp0/nvmem

The userspace application otp it is used to read/write the OTP. It has the following help:

# otp --help
This tool can be used to access and modify the OTP in the following SoCs:
 - LAN9662
 - LAN9668
 - LAN969X

Usage:
 otp field list
 otp field get [<NAME>]
 otp field set [--merge] <NAME> (ascii|hex|dec|3ascii-dec) <VALUE>
 otp addr get <ADDRESS> <BYTE-LENGTH>
 otp addr set [--merge] <ADDRESS> <BYTE-LENGTH> (ascii|hex|dec) <VALUE>
 otp tag list-tag-names
 otp tag print
 otp tag info
 otp tag get (<tag-number>|<tag-name>)
 otp tag del (<tag-number>|<tag-name>)
 otp tag set (<tag-number>|<tag-name>) (ascii|hex|dec) <VALUE>
 otp nvcnt get (trusted|nontrusted)
 otp nvcnt set <VALUE> (trusted|nontrusted)
 otp import-keys [--no-randomize-huk] <FILE>
 otp region show
 otp region write-protect <REGION-NUMBER>
 otp region non-secure <REGION-NUMBER>
 otp init pcb <PCB> ethaddr (random-ethaddr|<ETHADDR-ADDRESS>) ethaddr_count <COUNT>

Options:
 -h --help             Show this screen.
 --version             Show version.
 --verbose             Enable verbose traces on console
 -d DEV --device DEV   NVRAM device
                       This can also be a normal block device, or even a file.
 -i ID --chip-id ID    Specify OTP layout coresponding to the given chip ID.
                       NOTE: This setting is only allowed on devices where the
                       part-ID at byte address 0x5-0x6 is not programmed, or if
                       the programmed part-ID matches the provided ID.
 --chip-id-force       Force usage of the ID given with --chip-id=ID, even
                       though a different part ID is programmed in the OTP.
 --no-confirm          Do not require the user to confirm write operations.

General:

 The OTP in these products are accessed by various elements including: discrete
 logic in the chip, TF-A bootrom (BL1), Secure Boot loader (BL2), EL3 Runtime
 software (BL31/BL32), UBoot, Linux kernel and this user-space application.
 The OTP has a concept of regions, which can be used to configure access
 control (read and write protection). The regions and write protect mask is in
 the OTP it self and need to be provisioned.

 This tool support 3 different kind of content:
   Fields:                This is fixed-length data at fixed positions in the
                          OTP. The tool has a build in template for each of the
                          supported SoC with name, address, length and
                          description.

   Non volatile counters: This is counters which can only be incremented. This
                          is used to do rollback protection. This is
                          implemented as a bitfields, and the max value is
                          therefore the number of allocated bits for a given
                          counter.

   Tagged data:           This is semi-one-time-programmable data. The purpose
                          of this is to allow store various data such as:
                          mac-addresses, board-ID, ECO level, uniq default
                          password which may be printed on the device, etc.
                          This is implemented as an array of 64-bits records
                          with the following layout:

                           +--------+--------+--------+----------+
                           | size:3 | cont:1 | tag:12 | value:48 |
                           +--------+--------+--------+----------+

                          Where:
                            size:   specify the number of valid bytes in value.
                                    0 and 0b111 (7) are invalid. If a record
                                    needs to be invalidated, then it is a
                                    matter of writing 0b111 in this field.
                            cont:   specify that the content continues in the
                                    next valid record with the given tag value.
                            tag:    This is a number from 0-2047 specifying the
                                    type of data (the implementation has a list
                                    of named tags which may be used).
                            value:  Value associated to the tag.


Note:
 All commands writing ('field set', 'addr set', 'tag del|set') to OTP shall do:
 - Read out existing content.
 - If '--merge' flag is not set, confirm that the specified value is possible
   to set. Abort if not.
 - Print in hex what is about the be written where
 - Let use user confirm.

Command details:
 otp help:
   Print this message

 otp field list:
   List all fields recognized by the implementation template.

 otp field get [<NAME>]
   Get the content of a specific fields, or all fields if non is provided.
   NOTE: This shall skip fields in areas being read-protected, but will happily
   print secrets if not proper protected.

 otp field set [--merge] <NAME> (ascii|hex|dec) <VALUE>
   Set the content of a given field.
   - If the --merge option is provided, then the content is bit-wised OR with
     what is in the OTP field already. If not, a check is performed to confirm
     that the desired value can be written as-is.
   - The value can either be provided in ascii format, or as a hex-string.
     - A hex string must always provide an equal number of chars (no half
       bytes).
   - The length of the value must match what is find in the template.

 otp addr get <address> <BYTE-LENGTH>
   Read the raw content in OTP. If the requested area is (partly)
   read-protected, then return error.

 otp addr set [--merge] <address> <BYTE-LENGTH> (ascii|hex|dec) <VALUE>
   Write raw content in the OTP. If the requested area is (partly)
   write-protected, then return error before any content is written.

 otp nvcnt (trusted|nontrusted) get
 otp nvcnt (trusted|nontrusted) set <VALUE>
   Get/set the value of either the trusted or nontrusted
   non-volatile-ever-incrementing-counter.
   This is implemented as a bit-field, and a given implementation will have
   limited capacity.
   NOTE: This is used by the secure boot-ROM to implement rollback protection.

 otp tag list-tag-names
   List all the tag names and associated numbers know by the implementation.

 otp tag info
   Print statistics on tag usages.
 otp tag print
   Print all valid tags in the otp.

 otp tag get (bin|hex) [(<tag-number>|<tag-name>)]
   Read out the value of a given tag (either by name or by number) or dump all
   tags if no name/number is provided.
   If no valid tag with matching name/number was found, then return an error.

 otp tag [--merge] set (<tag-number>|<tag-name>) (ascii|hex) <VALUE>
   Set a tagged value. If the --merge flag was set, then the content provided
   must be of exact same length as the length of existing content in the OTP.
   If this tag already exists, then it shall be replaced (meaning invalidating
   the existing tag).

 otp tag append (<tag-number>|<tag-name>)
   Append more data to a tag. This will always create a new record and set the
   'cont' flag in existing tags.

 otp tag del (<tag-number>|<tag-name>)
   Invalidate an existing tag in the OTP.

 otp import-keys [--no-randomize-huk] <FILE>:
   This shall read the content from a file (or stdin if '-' is provided as
   file-name).
   The content is binary and length must match the platform expected length.
   It can only be used to program the region with TF-A keys (on LAN966x
   this is region 4). The region will not be defined when the tool is called,
   and a per platform hard-coded offset/size will be used.
   Unless the --no-randomize-huk flag is set, then the OTP_TBBR_HUK field will
   be set with randomized content using /dev/random.

otp region show:
  Show the start-address, end-address and write-protect bit of all regions.
  Some regions may be displayed with start- and end-address equal to 0, which
  means that this region is still not defined.

otp region write-protect <REGION-NUMBER>:
  This shall check if the region <REGION-NUMBER> is defined (in
  `PROTECT_REGION_ADDR`).  If not, the region shall be defined using the
  per-chip-id defined template.  Once the region is defined it shall be marked
  as write-protected in `PROTECT_OTP_WRITE`.

otp init pcb <PCBNO> ethaddr (random-ethaddr|<ETHADDR-ADDRESS>) ethaddr_count <COUNT>
  NOTE: This shall only be called during board manufacturing!
        Do not call more than once.

  This sub-command will do the following:
  - Do the equivalent of: $ opt tag set pcb dec <PCBNO>
  - Do the equivalent of: $ opt tag set ethaddr_count dec <COUNT>
  - Do the equivalent of: $ opt tag set ethaddr ascii <ETHADDR-ADDRESS>
    - If the ethaddr-address is `random-ethaddr`, then generate a random ETHADDR with the
      following limitation:
      - Broadcast bit is 0
      - Local-administrated bit is 1
      - It must not be all zero and not all ff.

3.1. Example on how to use

Here are some examples on how to use it

Once something is written in the OTP this can’t be erased. So make sure you can write the right command. You can test the command by running the same command on a file.
If the chip id has not been programmed into the OTP you can provide it on the command line and this use the tool correctly.

How to initialize the board:

otp -d /sys/bus/nvmem/devices/lan9662-otp0/nvmem init partid 9698 serial <SERIAL_NUMBER> pcb 9300 ethaddr random-ethaddr ethaddr_count 16

This command will do the following:

  • set the field partid to 9698

  • set the field serial_number to <SERIAL_NUMBER>

  • set the tag pcb to 9300

  • set the tag ethaddr to a random generated eth addr where bit 1 in MSB is set to 1 and bit 0 in MSB is set to 0.

  • set the tag ethaddr_count to 16 meaning that it would reserve 16 addresses

Here is a way to read back a field:

otp -d /sys/bus/nvmem/devices/lan9662-otp0/nvmem field get PARTID
2 be25|.%|

The first value of the output represents the number of bytes the field it has, second represents the value in little endian in hex and the last value represents the ascii characters of the value.

To see the available OTP regions you use the following command:

# otp -d /sys/bus/nvmem/devices/lan9662-otp0/nvmem region show
Region: 0, start: 0x0000, end: 0x003f, write_protect: 0, non_secure: 1
Region: 1, start: 0x0040, end: 0x0043, write_protect: 0, non_secure: 1
Region: 2, start: 0x0044, end: 0x0063, write_protect: 0, non_secure: 1
Region: 3, start: 0x0064, end: 0x00ff, write_protect: 0, non_secure: 1
Region: 4, start: 0x0100, end: 0x01ff, write_protect: 0, non_secure: 0
Region: 5, start: 0x0200, end: 0x023f, write_protect: 0, non_secure: 0
Region: 6, start: 0x0240, end: 0x07ff, write_protect: 0, non_secure: 1
Region: 7, start: 0x0800, end: 0x2000, write_protect: 0, non_secure: 1

The regions are described here: OTP Regions

To write-protect region 4 you can use this command:

otp -d  /sys/bus/nvmem/devices/lan9662-otp0/nvmem region write-protect 4
As the operation cannot be reverted, you must only write protect a region after you have written any needed data into it.

To make region 1 accessible to the non-secure world (e.g. Linux) you can set the non-secure bit on the region like this:

otp -d  /sys/bus/nvmem/devices/lan9662-otp0/nvmem region non-secure 1
The operation cannot be reverted, so make sure you understand the implications of this operation. You should never need to make region 4 and 5 available to the non-secure world.

To see the available fields the following command can be used:

# otp -d /sys/bus/nvmem/devices/lan9662-otp0/nvmem --chip-id 9692 field list
Field:                   OTP_PRG offset: 0x0000 length: 04
Field:                  FEAT_IDS offset: 0x0004 length: 01
Field:                    PARTID offset: 0x0005 length: 02
Field:                   TST_TRK offset: 0x0007 length: 01
Field:             SERIAL_NUMBER offset: 0x0008 length: 08
Field:               SECURE_JTAG offset: 0x0010 length: 04
Field:                WAFER_JTAG offset: 0x0014 length: 07
Field:                 JTAG_UUID offset: 0x0020 length: 10
Field:                      TRIM offset: 0x0030 length: 08
Field:         PROTECT_OTP_WRITE offset: 0x0040 length: 04
Field:       PROTECT_REGION_ADDR offset: 0x0044 length: 32
Field:            OTP_PCIE_FLAGS offset: 0x0064 length: 04
Field:              OTP_PCIE_DEV offset: 0x0068 length: 04
Field:               OTP_PCIE_ID offset: 0x006c length: 08
Field:             OTP_PCIE_BARS offset: 0x0074 length: 40
Field:            OTP_TBBR_ROTPK offset: 0x0100 length: 32
Field:              OTP_TBBR_HUK offset: 0x0120 length: 32
Field:               OTP_TBBR_EK offset: 0x0140 length: 32
Field:              OTP_TBBR_SSK offset: 0x0160 length: 32
Field:             OTP_SJTAG_SSK offset: 0x0180 length: 32
Field:    OTP_STRAP_DISABLE_MASK offset: 0x01a4 length: 02

Here is a way to list all tags:

# otp -d /sys/bus/nvmem/devices/lan9662-otp0/nvmem  --chip-id 9692 tag list-tag-names
Tag:   password, id: 1
Tag:        pcb, id: 2
Tag:   revision, id: 3
Tag:    ethaddr, id: 4
Tag: ethaddr_count, id: 5
Tag: fit_config, id: 6