Solution ZynqMP SoC revision read mechanism
This page gives an overview of the zynqmp_nvmem driver which is available as part of the ZynqMP Linux distribution. Paths, files, links and documentation on this page are given relative to the Linux kernel source tree.
HW IP Features
- SoC revision information
- Efuse memory access.
Features supported in driver
- SoC revision information
- Programming and reading efuse memory
Missing Features, Known Issues and Limitations
- None.
Kernel Configuration
Device Drivers ---> NVMEM Support ---> <*> Xilinx ZYNQMP SoC ID suppor
Devicetree
nvmem_firmware { compatible = "xlnx,zynqmp-nvmem-fw"; #address-cells = <1>; #size-cells = <1>; soc_revision: soc_revision@0 { reg = <0x0 0x4>; }; /* efuse access */ efuse_dna: efuse_dna@c { reg = <0xc 0xc>; }; efuse_usr0: efuse_usr0@20 { reg = <0x20 0x4>; }; efuse_usr1: efuse_usr1@24 { reg = <0x24 0x4>; }; efuse_usr2: efuse_usr2@28 { reg = <0x28 0x4>; }; efuse_usr3: efuse_usr3@2c { reg = <0x2c 0x4>; }; efuse_usr4: efuse_usr4@30 { reg = <0x30 0x4>; }; efuse_usr5: efuse_usr5@34 { reg = <0x34 0x4>; }; efuse_usr6: efuse_usr6@38 { reg = <0x38 0x4>; }; efuse_usr7: efuse_usr7@3c { reg = <0x3c 0x4>; }; efuse_miscusr: efuse_miscusr@40 { reg = <0x40 0x4>; }; efuse_chash: efuse_chash@50 { reg = <0x50 0x4>; }; efuse_pufmisc: efuse_pufmisc@54 { reg = <0x54 0x4>; }; efuse_sec: efuse_sec@58 { reg = <0x58 0x4>; }; efuse_spkid: efuse_spkid@5c { reg = <0x5c 0x4>; }; efuse_ppk0hash: efuse_ppk0hash@a0 { reg = <0xa0 0x30>; }; efuse_ppk1hash: efuse_ppk1hash@d0 { reg = <0xd0 0x30>; }; };
Test procedure
Using dd command
To access efuse memory, PMUFW should be built by enabling ENABLE_EFUSE_ACCESS in xpfw_config.h and the respective eFuse classification macro to access read/write. By default this is disabled. But revision at offset 0x0 is enabled by default. Once booted into Linux, to read/write particular field please do read/write from/to "/sys/bus/nvmem/devices/zynqmp-nvmem0/nvmem" to the particular offset with the corresponding size.
Following are the respective eFuse classification macro to access read/write
- To access User0 to User7 (Offset 0x20 to 0x3C) efuse register, PMUFW should be built by defining XSK_ACCESS_USER_EFUSE as well in "lib/sw_services/xilskey/include/xilskey_eps_zynqmp.h".
- To access SPK ID, AES KEY, PPK0 hash and PPK1 hash efuse register, PMUFW should be built by defining XSK_ACCESS_KEY_MANAGE_EFUSE as well in "lib/sw_services/xilskey/include/xilskey_eps_zynqmp.h".
The following table gives the overview of possible addresses for read/write and with the sizes.
Register | Read | Write | Size in bytes | Offset is |
---|---|---|---|---|
Version | YES | NO | 0x0 | 0x4 |
DNA | YES | NO | 0xc | 0xC |
User0 | YES | YES | 0x4 | 0x20 |
User1 | YES | YES | 0x4 | 0x24 |
User2 | YES | YES | 0x4 | 0x28 |
User3 | YES | YES | 0x4 | 0x2c |
User4 | YES | YES | 0x4 | 0x30 |
User5 | YES | YES | 0x4 | 0x34 |
User6 | YES | YES | 0x4 | 0x38 |
User7 | YES | YES | 0x4 | 0x3C |
Misc user | YES | YES | 0x4 | 0x40 |
Secure control | YES | YES | 0x4 | 0x58 |
SPK ID | YES | YES | 0x4 | 0x5C |
AES key | NO | YES | 0x20 | 0x60 |
PPK0 hash | YES | YES | 0x30 | 0xA0 |
PPK1 hash | YES | YES | 0x30 | 0xD0 |
Reading/writing unrestricted number of bytes results into an error.
Restricted bits from programming:
Secure Control eFuses :
- RSA_EN (BITS - [25:11])
- DFT_DIS (BIT-6 )
- JTAG_DIS (BIT-5)
- ENC_ONLY (BIT - 2)
Misc User Control eFuses :
- LBIST_EN (BIT - 10)
Expected Output
For Version reading: offset is 0x0 and bytes are 4 ########## For Silicon 1.0 ############ root@plnx_aarch64:~# dd if=/sys/bus/nvmem/devices/zynqmp-nvmem0/nvmem of=/tmp/version.bin bs=4 count=1 root@plnx_aarch64:~# hexdump -v /tmp/version.bin 0000000 0000 0000001 ########## For Silicon 2.0 ############ root@plnx_aarch64:~# dd if=/sys/bus/nvmem/devices/zynqmp-nvmem0/nvmem of=/tmp/version.bin bs=4 count=1 root@plnx_aarch64:~# hexdump -v /tmp/version.bin 0000000 0001 0000001 ########## For Silicon 3.0 ############ root@plnx_aarch64:~# dd if=/sys/bus/nvmem/devices/zynqmp-nvmem0/nvmem of=/tmp/version.bin bs=4 count=1 root@plnx_aarch64:~# hexdump -v /tmp/version.bin 0000000 0002 0000001 ########## For Silicon 4.0 ############ root@plnx_aarch64:~# dd if=/sys/bus/nvmem/devices/zynqmp-nvmem0/nvmem of=/tmp/version.bin bs=4 count=1 root@plnx_aarch64:~# hexdump -v /tmp/version.bin 0000000 0003 0000001 EFUSE access To read user fuse 7 root@xilinx-zcu102-2018_3:/mnt# dd if=/sys/bus/nvmem/devices/zynqmp-nvmem0/nvmem of=/tmp/userfuse.bin bs=4 count=1 skip=15 1+0 records in 1+0 records out root@xilinx-zcu102-2018_3:/mnt# hexdump -v /tmp/userfuse.bin 0000000 0000 af00 0000004
Register | Size in bytes(hex) | Offset (hex) | bs(dec) | count(dec) | skip(dec) |
---|---|---|---|---|---|
Version | 0 | 4 | 4 | 0 | 1 |
User7 | 4 | 3C | 4 | 1 | 15 |
User6 | 4 | 38 | 4 | 1 | 14 |
User5 | 4 | 34 | 4 | 1 | 13 |
User4 | 4 | 30 | 4 | 1 | 12 |
User3 | 4 | 2C | 4 | 1 | 11 |
User2 | 4 | 28 | 4 | 1 | 10 |
User1 | 4 | 24 | 4 | 1 | 9 |
User0 | 4 | 20 | 4 | 1 | 8 |
SPK ID | 4 | 5C | 4 | 1 | 23 |
Secure Control | 4 | 58 | 4 | 1 | 22 |
PPK1 hash | 30 | D0 | 4 | 12 | 52 |
PPK0 hash | 30 | A0 | 4 | 12 | 40 |
Misc user | 4 | 40 | 4 | 1 | 16 |
DNA | C | C | 12 | 1 | 1 |
Using linux application
- Copy below code in a .c file say efuse_access_app.c and compile it with aarch64-linux-gnu-gcc compiler.
- After booting till linux, below commands can be used to read/write into eFuses.
- For help : ./efuse_access_app --help
- For reading from eFuses : ./efuse_access_app --read <offset in hex>
- For writing into eFuses : ./efuse_access_app --write <offset in hex> <value in hex>
/****************************************************************************** * Copyright (c) 2021 Xilinx, Inc. All rights reserved. * SPDX-License-Identifier: MIT ******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> #include <string.h> #include <unistd.h> #include <ctype.h> #include <getopt.h> #include <errno.h> #include <stdbool.h> typedef struct { u_int32_t offset; u_int32_t size; }EfuseLookupTable; /* Xilinx error codes */ #define EFUSE_RD_FAILED 1026 #define EFUSE_WR_FAILED 1027 #define SYS_PATH "/sys/bus/nvmem/devices/zynqmp-nvmem0/nvmem" #define EFUSE_MAX_ROWS 16 static void print_help(); static u_int32_t get_length(u_int32_t offset); static u_int32_t remove_initial_0x(char *str); static u_int32_t validate_offset(char *str); static int32_t read_efuse(int fd, u_int32_t offset); static int32_t write_efuse(int fd, u_int32_t offset, char* value, u_int32_t val_len); static u_int32_t convert_char_to_nibble(char in_char, unsigned char *num); static u_int32_t convert_string_to_hex_be(const char *str, unsigned char *buf, u_int32_t len); static u_int32_t convert_string_to_hex_le(const char *str, unsigned char *buf, u_int32_t len); int main(int argc, char* argv[]) { int fd; u_int32_t offset = 0; u_int32_t bytes = 0; int32_t readflag = 0; int32_t writeflag = 0; int32_t helpflag = 0; char* value = NULL; int32_t c; int32_t long_index = 0; int32_t status; static struct option long_options[] = { {"help", no_argument, 0, 'h' }, {"read", no_argument, 0, 'r' }, {"write", no_argument, 0, 'w' }, {0, 0, 0, 0 } }; while ((c = getopt_long(argc, argv, "hrw", long_options, &long_index)) != -1) { switch (c) { case 'h': helpflag++; break; case 'r': readflag++; break; case 'w': writeflag++; break; default: print_help(); abort (); break; } } if (((readflag + writeflag + helpflag) > 1) || (readflag == true && argc != 3) || (writeflag == true && argc != 4)) { fprintf (stderr, "Invalid syntax\n"); print_help(); return EINVAL; } if (helpflag == true) { print_help(); return 0; } fd = open(SYS_PATH, O_RDWR); if(fd <= 0) { printf("Opening SYS FS NVMEM file is failed\n"); return errno; } if (readflag == true) { status = validate_offset(argv[2]); if (status != 0) { return status; } offset = strtoul(argv[2], NULL, 16); status = read_efuse(fd, offset); return status; } if (writeflag == true) { status = validate_offset(argv[2]); if (status != 0) { return status; } offset = strtoul(argv[2], NULL, 16); value = argv[3]; u_int32_t length = remove_initial_0x(value); status = write_efuse(fd, offset, value, length); return status; } close(fd); return 0; } /* * Prints help on the syntax and supported arguments. * Called if --help is provided as argument or in case of invalid syntax */ static void print_help() { printf("Usage: \r\n"); printf("Syntax : \r\n"); printf("Read from eFuse: \r\n ./efuse_access --read " "<Offset in hex>\r\n"); printf("Write into eFuse: \r\n ./efuse_access --write " "<Offset in hex> <Value in hex>\r\n"); printf("\r\n"); printf("Arguments : \r\n"); printf("-h --help \t Prints help\r\n"); printf("-r --read \t Read from eFuse\r\n"); printf("-w --write \t Write into eFuse\r\n"); printf("For more details please refer -" "https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/" "18841682/Solution+ZynqMP+SoC+revision+read+mechanism\r\n"); } /* * Returns the supported length of the efuse in bytes as per the provided offset * In case of invalid offset, returns 0xFF. */ static u_int32_t get_length(u_int32_t offset) { const EfuseLookupTable EfuseSize[EFUSE_MAX_ROWS] = { /*+-----+-----+ *|Offset| Size| *+------+-----+ */ {0x4, 0x0}, /* Version */ {0xC, 0xC}, /* DNA */ {0x20, 0x4}, /* User0 */ {0x24, 0x4}, /* User1 */ {0x28, 0x4}, /* User2 */ {0x2C, 0x4}, /* User3 */ {0x30, 0x4}, /* User4 */ {0x34, 0x4}, /* User5 */ {0x38, 0x4}, /* User6 */ {0x3C, 0x4}, /* User7 */ {0x40, 0x4}, /* Misc User */ {0x58, 0x4}, /* Secure Control */ {0x5C, 0x4}, /* SPK ID */ {0x60, 0x20}, /* AES Key */ {0xA0, 0x30}, /* PPK0 Hash */ {0xD0, 0x30}, /* PPK1 Hash */ }; u_int32_t size = 0xFF; int32_t index; for(index = 0; index < EFUSE_MAX_ROWS; index++) { if (EfuseSize[index].offset == offset) { size = EfuseSize[index].size; break; } } return size; } /* * Removes 0x or 0X from starting of the string * eg : 0x1234 -> 1234 * Returns length of the updated string */ static u_int32_t remove_initial_0x(char *str) { int32_t index; int32_t n = strnlen(str, 48); if ((*str == '0') && (*(str + 1) == 'x' || *(str + 1) == 'X')) { strcpy(str, &str[2]); } return strnlen(str, 48); } /* * Validates offset */ static u_int32_t validate_offset(char *str) { u_int32_t index = 0; u_int32_t modified_len = remove_initial_0x(str); if (modified_len > 2) { return EINVAL; } for (index = 0; str[index] != '\0'; index++) { if ((str[index] < '0' || str[index] > '9') && (str[index] < 'A' || str[index] > 'F') && (str[index] < 'a' || str[index] > 'f')) { return EINVAL; } } return 0; } /* * Reads eFUSE values from the offset */ static int32_t read_efuse(int fd, u_int32_t offset) { u_int32_t length = get_length(offset); ssize_t size; u_int32_t read_data[50] = {0}; int32_t index; if (length == 0xFF) { printf("Invalid offset\n\r"); return EINVAL; } if (offset == 0x60) { printf("Read is not allowed for AES key\n\r"); return EINVAL; } size = pread(fd, (void *)&read_data, length, offset); if (size == length) { for (index = (size/4)-1; index >= 0; index--) { printf("%x ", read_data[index]); } printf("\n\r"); } else { printf("size != length\n\r"); return EFUSE_RD_FAILED; } return 0; } /* * Writes user provided value in the eFUSE at the given offset */ static int32_t write_efuse(int fd, u_int32_t offset, char* value, u_int32_t val_len) { u_int32_t length = get_length(offset); ssize_t size; unsigned char write_data[48] = {0}; int32_t status; int32_t index; if (length == 0xFF) { printf("Invalid offset\n\r"); return EINVAL; } if (offset == 0xC) { printf("Write is not allowed for DNA\n\r"); return EINVAL; } if (offset == 0x4) { printf("Write is not allowed for Version\n\r"); return EINVAL; } if (val_len > (length*2)) { printf("Length of provided value is longer than expected\n\r"); return EINVAL; } /* Convert to big endian format in case of PPK0/PPK1 */ if ((offset == 0xA0) || (offset == 0xD0)) { status = convert_string_to_hex_be(value, write_data, length*8); if (status != 0) { return status; } } /* Convert to little endian format in case of other fuses */ else { status = convert_string_to_hex_le(value, write_data, length*8); if (status != 0) { return status; } } size = pwrite(fd, (void *)&write_data, length, offset); if (size == length) { printf("Data written at offset = %x of size = %d bytes\n\r", offset, size); } else { return EFUSE_WR_FAILED; } return 0; } /* * Converts character to nibble */ static u_int32_t convert_char_to_nibble(char in_char, unsigned char *num) { if ((in_char >= '0') && (in_char <= '9')) { *num = in_char - '0'; } else if ((in_char >= 'a') && (in_char <= 'f')) { *num = in_char - 'a' + 10; } else if ((in_char >= 'A') && (in_char <= 'F')) { *num = in_char - 'A' + 10; } else { return EINVAL; } return 0; } /* * Converts string to hex in big endian format */ static u_int32_t convert_string_to_hex_be(const char *str, unsigned char *buf, u_int32_t len) { u_int32_t converted_len; unsigned char lower_nibble = 0U; unsigned char upper_nibble = 0U; if ((str == NULL) || (buf == NULL)) { return EINVAL; } if ((len == 0U) || ((len % 8) != 0U)) { return EINVAL; } if((strlen(str) * 4) > len) { return EINVAL; } converted_len = 0U; while (converted_len < strlen(str)) { if (convert_char_to_nibble(str[converted_len],&upper_nibble) == 0) { if (convert_char_to_nibble(str[converted_len+1], &lower_nibble) == 0) { buf[converted_len/2] = (upper_nibble << 4) | lower_nibble; } else { return EINVAL; } } else { return EINVAL; } converted_len += 2U; } return 0; } /* * Converts string to hex in little endian format */ static u_int32_t convert_string_to_hex_le(const char *str, unsigned char *buf, u_int32_t len) { u_int32_t converted_len; unsigned char lower_nibble = 0U; unsigned char upper_nibble = 0U; u_int32_t str_index; if ((NULL == str) || (NULL == buf)) { return EINVAL; } if ((len == 0U) || ((len % 8) != 0U)) { return EINVAL; } if((strlen(str) * 4) > len) { return EINVAL; } str_index = (len / 8) - 1U; converted_len = 0U; while (converted_len < strlen(str)) { if (convert_char_to_nibble(str[converted_len], &upper_nibble) == 0) { if (convert_char_to_nibble(str[converted_len + 1], &lower_nibble) == 0) { buf[str_index] = (upper_nibble << 4) | lower_nibble; str_index = str_index - 1U; } else { return EINVAL; } } else { return EINVAL; } converted_len += 2U; } return 0; }
Mainline Status
- This driver is not available at Mainline.
Change Log
2017.1Summary:
- nvmem: zynqmp: Added zynqmp nvmem firmware driver
- nvmem: zynqmp: Fix the size to one byte to read the correct value from the nvmem sysfs entry.
- c808007nvmem: zynqmp: Added zynqmp nvmem firmware driver
- 3494c67 nvmem: zynqmp: Fix the size to one byte to read the correct value from the nvmem sysfs entry.
2017.2
- None
2017.3
Summary:
- zynqmp: Use new firmware.h instead of pm.h
- 5e81ba5zynqmp: Use new firmware.h instead of pm.h
2017.4
- None
2018.1
Summary:
- soc: zynqmp: Define EEMI ops structure
- soc: zynqmp: Use new firmware APIs
Commits
2018.2
- None
2018.3
- Added support to access eFUSE memory
Related Links
http://www.wiki.xilinx.com/Linux+Drivershttps://github.com/Xilinx/linux-xlnx/blob/master/drivers/nvmem/zynqmp_nvmem.c