aboutsummaryrefslogtreecommitdiffstats
path: root/mpm
diff options
context:
space:
mode:
authorMichael Auchter <michael.auchter@ni.com>2019-09-09 16:46:14 -0500
committerMartin Braun <martin@gnuradio.org>2021-05-31 05:28:47 -0700
commit530cb0a8aaaac906fe124086e780d6651241b0ad (patch)
tree8a5301163392b3d0c1642da0cb9ba2a9ea176401 /mpm
parentfixup! ic_reg_maps: Remove SPCC core reg map (diff)
downloaduhd-530cb0a8aaaac906fe124086e780d6651241b0ad.tar.xz
uhd-530cb0a8aaaac906fe124086e780d6651241b0ad.zip
mpm: add tlv_eeprom
Add support for parsing an eeprom that uses tag-length-value to store contents. Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes@ni.com>
Diffstat (limited to 'mpm')
-rw-r--r--mpm/python/usrp_mpm/CMakeLists.txt6
-rw-r--r--mpm/python/usrp_mpm/tlv_eeprom.py143
-rw-r--r--mpm/tools/CMakeLists.txt6
-rw-r--r--mpm/tools/tlv_eeprom/CMakeLists.txt40
-rw-r--r--mpm/tools/tlv_eeprom/crc.c71
-rw-r--r--mpm/tools/tlv_eeprom/eeprom-dump.c34
-rw-r--r--mpm/tools/tlv_eeprom/eeprom-id.c66
-rw-r--r--mpm/tools/tlv_eeprom/eeprom-init.c376
-rw-r--r--mpm/tools/tlv_eeprom/eeprom-path36
-rw-r--r--mpm/tools/tlv_eeprom/eeprom-pids.c50
-rw-r--r--mpm/tools/tlv_eeprom/eeprom-pids.h21
-rw-r--r--mpm/tools/tlv_eeprom/eeprom-wrapper13
-rw-r--r--mpm/tools/tlv_eeprom/tlv_eeprom.c98
-rw-r--r--mpm/tools/tlv_eeprom/tlv_eeprom.h104
-rw-r--r--mpm/tools/tlv_eeprom/tlv_eeprom_io.c88
-rw-r--r--mpm/tools/tlv_eeprom/tlv_eeprom_io.h31
-rw-r--r--mpm/tools/tlv_eeprom/usrp_eeprom.h206
17 files changed, 1386 insertions, 3 deletions
diff --git a/mpm/python/usrp_mpm/CMakeLists.txt b/mpm/python/usrp_mpm/CMakeLists.txt
index f1637ab03..c5b55283f 100644
--- a/mpm/python/usrp_mpm/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/CMakeLists.txt
@@ -12,18 +12,22 @@ set(USRP_MPM_TOP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in
${CMAKE_CURRENT_SOURCE_DIR}/aurora_control.py
${CMAKE_CURRENT_SOURCE_DIR}/bfrfs.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/bist.py
${CMAKE_CURRENT_SOURCE_DIR}/components.py
${CMAKE_CURRENT_SOURCE_DIR}/discovery.py
${CMAKE_CURRENT_SOURCE_DIR}/eeprom.py
${CMAKE_CURRENT_SOURCE_DIR}/e31x_legacy_eeprom.py
${CMAKE_CURRENT_SOURCE_DIR}/ethdispatch.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/fpga_bit_to_bin.py
${CMAKE_CURRENT_SOURCE_DIR}/gpsd_iface.py
${CMAKE_CURRENT_SOURCE_DIR}/mpmlog.py
${CMAKE_CURRENT_SOURCE_DIR}/mpmtypes.py
${CMAKE_CURRENT_SOURCE_DIR}/mpmutils.py
${CMAKE_CURRENT_SOURCE_DIR}/prefs.py
- ${CMAKE_CURRENT_SOURCE_DIR}/rpc_server.py
${CMAKE_CURRENT_SOURCE_DIR}/process_manager.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/rpc_server.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/tlv_eeprom.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/user_eeprom.py
)
list(APPEND USRP_MPM_FILES ${USRP_MPM_TOP_FILES})
add_subdirectory(chips)
diff --git a/mpm/python/usrp_mpm/tlv_eeprom.py b/mpm/python/usrp_mpm/tlv_eeprom.py
new file mode 100644
index 000000000..671433ffa
--- /dev/null
+++ b/mpm/python/usrp_mpm/tlv_eeprom.py
@@ -0,0 +1,143 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Tag-Length-Value (TLV) based EEPROM management code
+"""
+
+import struct
+import zlib
+
+class NamedStruct:
+ """
+ Helper class for unpacking values from bytes
+ """
+ def __init__(self, fmt, keys):
+ self.struct = struct.Struct(fmt)
+ self.keys = keys
+
+ # ensure no duplicate keys
+ assert len(set(keys) - set([None])) == \
+ len([x for x in keys if x is not None])
+ # ensure same number of keys as elements
+ assert len(self.struct.unpack(bytearray(self.size))) == len(keys)
+
+ def unpack_from(self, buf, offset=0):
+ """
+ Unpack values from the struct, returning a dictionary mapping a field
+ name to a value. If the field name is None, the field is not included
+ in the returned dictionary.
+
+ buf -- the buffer to unpack from
+ offset -- the offset within that buffer
+ """
+
+ vals = self.struct.unpack_from(buf, offset)
+ return {x[0]: x[1] for x in zip(self.keys, vals) if x[0] is not None}
+
+ @property
+ def size(self):
+ """
+ number of values to unpack
+ """
+ return self.struct.size
+
+
+def tlv_eeprom_validate(eeprom, expected_magic):
+ """
+ Validate the contents of the EEPROM and return a tuple (header, tlv)
+ Header is a dictionary of the EEPROM header (magic, size, crc)
+ Tlv is the raw TLV data
+
+ eeprom -- raw eeprom data
+ expected_magic -- magic value that's expected
+ """
+ def crc32(data, initial=0):
+ initial = initial ^ 0xFFFFFFFF
+ crc = zlib.crc32(data, initial)
+ return crc ^ 0xFFFFFFFF
+
+ size_offset = 8
+ tlv_eeprom_hdr = NamedStruct('< I I I', ['magic', 'crc', 'size'])
+ hdr = tlv_eeprom_hdr.unpack_from(eeprom)
+
+ if hdr['magic'] != expected_magic:
+ raise RuntimeError(
+ "Received incorrect EEPROM magic. "
+ "Read: {:08X} Expected: {:08X}".format(
+ hdr['magic'], expected_magic))
+
+ if hdr['size'] > (len(eeprom) - tlv_eeprom_hdr.size):
+ raise RuntimeError('invalid size')
+
+ crc = crc32(eeprom[size_offset:tlv_eeprom_hdr.size+hdr['size']])
+ if hdr['crc'] != crc:
+ raise RuntimeError(
+ "Received incorrect CRC. "
+ "Read: {:08X} Expected: {:08X}".format(
+ hdr['crc'], crc))
+
+ return hdr, eeprom[tlv_eeprom_hdr.size:tlv_eeprom_hdr.size+hdr['size']]
+
+
+def tlv_eeprom_unpack(tlv, tagmap):
+ """
+ Parse TLV data and return a dictionary of values found
+
+ tlv -- raw TLV data from the EEPROM
+ tagmap -- dictionary mapping 8-bit tag to a NamedStruct instance
+ """
+
+ values = {}
+ hdr_struct = NamedStruct('< B B', ['tag', 'len'])
+ idx = 0
+
+ while idx < len(tlv):
+ hdr = hdr_struct.unpack_from(tlv, idx)
+ idx += hdr_struct.size
+
+ if hdr['tag'] in tagmap:
+ val_struct = tagmap[hdr['tag']]
+ if hdr['len'] != val_struct.size:
+ raise RuntimeError(
+ "unexpected size: {:d}, expected: {:d}".format(
+ hdr['len'], tagmap[hdr['tag']]))
+ unpacked = val_struct.unpack_from(tlv, idx)
+ # prohibit clobbering existing values
+ assert len(set(values.keys()) & set(unpacked.keys())) == 0
+ values.update(unpacked)
+
+ idx += hdr['len']
+
+ return values
+
+
+def read_eeprom(
+ nvmem_path,
+ tagmap,
+ expected_magic,
+ max_size=None
+):
+ """
+ Read the EEPROM located at nvmem_path and return a tuple (header, data)
+ Header is a dictionary of values unpacked from eeprom based upon tagmap
+ Data contains the full eeprom contents
+
+ nvmem_path -- Path to readable file (typically something in sysfs)
+ tagmap -- dictionary mapping an 8-bit tag to a NamedStruct instance
+ expected_magic -- magic value that is expected
+ max_size -- Max number of bytes to read from nvmem, whole file if omitted
+
+ Tagmap should be a dictionary mapping an 8-bit tag to a NamedStruct
+ instance. For each tag that's found in the eeprom, the value at that tag
+ will be unpacked using the associated NamedStruct; any named fields within
+ that struct will then be added to the returned dictionary.
+ """
+
+ max_size = max_size or -1
+ with open(nvmem_path, "rb") as nvmem_file:
+ data = nvmem_file.read(max_size)
+ _, tlv = tlv_eeprom_validate(data, expected_magic)
+ return tlv_eeprom_unpack(tlv, tagmap), data
diff --git a/mpm/tools/CMakeLists.txt b/mpm/tools/CMakeLists.txt
index 387cee46a..c61e4562c 100644
--- a/mpm/tools/CMakeLists.txt
+++ b/mpm/tools/CMakeLists.txt
@@ -14,7 +14,7 @@ install(PROGRAMS
set(eeprom_tool_sources)
set(eeprom_tool_libs)
-if(ENABLE_LIBMPM AND NOT ENABLE_E300)
+if(ENABLE_LIBMPM AND NOT ENABLE_E300 AND NOT ENABLE_X400)
message(STATUS "Adding MPM EEPROM tools...")
set(eeprom_tool_libs eeprom.c)
list(APPEND eeprom_tool_sources
@@ -24,7 +24,7 @@ if(ENABLE_LIBMPM AND NOT ENABLE_E300)
eeprom-init.c
eeprom-set-flags.c
)
-endif(ENABLE_LIBMPM AND NOT ENABLE_E300)
+endif(ENABLE_LIBMPM AND NOT ENABLE_E300 AND NOT ENABLE_X400)
if(ENABLE_MYKONOS)
message(STATUS "Adding N3XX-specific EEPROM tools...")
set(eeprom_tool_libs eeprom.c)
@@ -43,3 +43,5 @@ foreach(eeprom_tool_source ${eeprom_tool_sources})
add_executable(${eeprom_tool} ${eeprom_tool_source} ${eeprom_tool_libs})
install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${eeprom_tool} DESTINATION ${RUNTIME_DIR})
endforeach(eeprom_tool_source ${eeprom_tool_sources})
+
+add_subdirectory(tlv_eeprom)
diff --git a/mpm/tools/tlv_eeprom/CMakeLists.txt b/mpm/tools/tlv_eeprom/CMakeLists.txt
new file mode 100644
index 000000000..75cf3c9cc
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/CMakeLists.txt
@@ -0,0 +1,40 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+set(eeprom_tool_sources)
+set(eeprom_tool_libs)
+
+if(ENABLE_LIBMPM AND ENABLE_X400)
+ message(STATUS "Adding MPM EEPROM tools...")
+ set(eeprom_tool_libs tlv_eeprom.c tlv_eeprom_io.c crc.c eeprom-pids.c)
+ list(APPEND eeprom_tool_sources
+ eeprom-dump.c
+ eeprom-id.c
+ eeprom-init.c
+ )
+
+ add_executable(eeprom-update-core eeprom-init.c ${eeprom_tool_libs})
+ target_compile_definitions(eeprom-update-core PRIVATE -DTLV_EEPROM_UPDATE)
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/eeprom-update-core DESTINATION ${RUNTIME_DIR})
+ add_custom_target(eeprom-update ALL COMMAND ${CMAKE_COMMAND} -E create_symlink eeprom-wrapper eeprom-update)
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/eeprom-update DESTINATION ${RUNTIME_DIR})
+endif(ENABLE_LIBMPM AND ENABLE_X400)
+
+foreach(eeprom_tool_source ${eeprom_tool_sources})
+ get_filename_component(eeprom_tool ${eeprom_tool_source} NAME_WE)
+ # install eeprom-id, eeprom-dump, etc. with "-core" appendix
+ add_executable(${eeprom_tool}-core ${eeprom_tool_source} ${eeprom_tool_libs})
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${eeprom_tool}-core DESTINATION ${RUNTIME_DIR})
+ # install eeprom-id, eeprom-dump, etc. wrappers
+ add_custom_target(${eeprom_tool} ALL COMMAND ${CMAKE_COMMAND} -E create_symlink eeprom-wrapper ${eeprom_tool})
+ install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${eeprom_tool} DESTINATION ${RUNTIME_DIR})
+endforeach(eeprom_tool_source ${eeprom_tool_sources})
+
+install(PROGRAMS
+ eeprom-path
+ eeprom-wrapper
+ DESTINATION ${RUNTIME_DIR}
+)
diff --git a/mpm/tools/tlv_eeprom/crc.c b/mpm/tools/tlv_eeprom/crc.c
new file mode 100644
index 000000000..df16b71db
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/crc.c
@@ -0,0 +1,71 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <stdint.h>
+#include "tlv_eeprom.h"
+
+static const uint32_t crc32_tab[] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+static uint32_t crc32(uint32_t crc, const void *buf, size_t size)
+{
+ const uint8_t *p;
+
+ p = buf;
+
+ while (size--)
+ crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+
+ return crc;
+}
+
+uint32_t tlv_eeprom_crc(const struct tlv_eeprom *e)
+{
+ return crc32(0, &e->size, sizeof(e->size) + e->size);
+}
diff --git a/mpm/tools/tlv_eeprom/eeprom-dump.c b/mpm/tools/tlv_eeprom/eeprom-dump.c
new file mode 100644
index 000000000..9afcef431
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/eeprom-dump.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Ettus Research, a National Instruments Brand
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tlv_eeprom.h"
+#include "tlv_eeprom_io.h"
+#include "usrp_eeprom.h"
+
+int main(int argc, char **argv)
+{
+ struct tlv_eeprom *eeprom;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <eeprom>\n", argv[0]);
+ return 1;
+ }
+
+ eeprom = tlv_eeprom_read_from_file(argv[1]);
+ if (!eeprom)
+ return 1;
+
+ if (!tlv_eeprom_validate(eeprom, USRP_EEPROM_MAGIC))
+ tlv_for_each(eeprom->tlv, eeprom->size, usrp_eeprom_trace);
+ else
+ fprintf(stderr, "eeprom contents invalid!\n");
+
+ free(eeprom);
+
+ return 0;
+}
diff --git a/mpm/tools/tlv_eeprom/eeprom-id.c b/mpm/tools/tlv_eeprom/eeprom-id.c
new file mode 100644
index 000000000..e00732f0a
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/eeprom-id.c
@@ -0,0 +1,66 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tlv_eeprom.h"
+#include "tlv_eeprom_io.h"
+#include "usrp_eeprom.h"
+#include "eeprom-pids.h"
+
+int main(int argc, char **argv)
+{
+ struct tlv_eeprom *eeprom;
+ const struct usrp_eeprom_module_info *module_info;
+ const struct usrp_eeprom_board_info *board_info;
+ const struct pid_info *pid_info;
+ unsigned int pid, rev;
+ const char *serial;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <eeprom>\n", argv[0]);
+ return 1;
+ }
+
+ eeprom = tlv_eeprom_read_from_file(argv[1]);
+ if (tlv_eeprom_validate(eeprom, USRP_EEPROM_MAGIC)) {
+ fprintf(stderr, "failed to validate eeprom\n");
+ return 1;
+ }
+
+ module_info = tlv_lookup(eeprom->tlv, eeprom->size,
+ USRP_EEPROM_MODULE_INFO_TAG);
+ board_info = tlv_lookup(eeprom->tlv, eeprom->size,
+ USRP_EEPROM_BOARD_INFO_TAG);
+
+ if (module_info) {
+ serial = module_info->serial;
+ pid = module_info->pid;
+ rev = module_info->rev;
+ } else if (board_info) {
+ serial = board_info->serial;
+ pid = board_info->pid;
+ rev = board_info->rev;
+ } else {
+ fprintf(stderr, "couldn't find module_info or board_info\n");
+ return 1;
+ }
+
+ pid_info = get_info_from_pid(pid);
+
+ if (pid_info && pid_info->name) {
+ printf("product=ni-%s-rev%u\n", pid_info->name, rev + pid_info->rev_offset);
+ } else if (pid_info && pid_info->description) {
+ printf("product=ni-(%s)-rev%u\n", pid_info->description, rev + pid_info->rev_offset);
+ } else {
+ printf("product=unknown-(%04x)\n", pid);
+ }
+ printf("serial=%s\n", serial);
+
+ free(eeprom);
+ return 0;
+}
diff --git a/mpm/tools/tlv_eeprom/eeprom-init.c b/mpm/tools/tlv_eeprom/eeprom-init.c
new file mode 100644
index 000000000..6cca434fc
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/eeprom-init.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Ettus Research, a National Instruments Brand
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tlv_eeprom.h"
+#include "tlv_eeprom_io.h"
+#include "usrp_eeprom.h"
+
+#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((*x)))
+
+int parse_board_info(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_board_info *info = data;
+
+ if (!argc) {
+ sprintf(data, "<pid> <rev> <compat_rev> <serial>");
+ return 0;
+ }
+
+ assert(argc >= 4);
+
+ info->pid = strtoul(argv[0], NULL, 0);
+ info->rev = strtoul(argv[1], NULL, 0);
+ info->compat_rev = strtoul(argv[2], NULL, 0);
+ strncpy(info->serial, argv[3], 8);
+ info->serial[7] = '\0';
+
+ return 4;
+}
+static struct usrp_eeprom_board_info board_info;
+
+int parse_module_info(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_module_info *info = data;
+
+ if (!argc) {
+ sprintf(data, "<pid> <rev> <serial>");
+ return 0;
+ }
+
+ assert(argc >= 3);
+
+ info->pid = strtoul(argv[0], NULL, 0);
+ info->rev = strtoul(argv[1], NULL, 0);
+ strncpy(info->serial, argv[2], 8);
+ info->serial[7] = '\0';
+
+ return 3;
+}
+static struct usrp_eeprom_module_info module_info;
+
+int parse_mac(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_mac_addr *mac = data;
+ int ret;
+
+ if (!argc) {
+ sprintf(data, "<mac_addr>");
+ return 0;
+ }
+
+ assert(argc >= 1);
+
+ ret = sscanf(*argv, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ mac->addr + 0, mac->addr + 1, mac->addr + 2,
+ mac->addr + 3, mac->addr + 4, mac->addr + 5);
+ assert(ret == 6);
+
+ return 1;
+}
+static struct usrp_eeprom_mac_addr mac_addrs[USRP_EEPROM_MAX_MAC_ADDRS];
+
+int parse_db_pwr_seq(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_db_pwr_seq *seq = data;
+ uint8_t i;
+
+ if (!argc) {
+ sprintf(data, "<nsteps> [delay supply_mask]...");
+ return 0;
+ }
+
+ assert(argc >= 1);
+ seq->nsteps = strtoul(argv[0], NULL, 0);
+ assert(argc >= 1 + (2 * seq->nsteps));
+
+ argv++;
+ for (i = 0; i < seq->nsteps; i++) {
+ seq->steps[i].delay = strtoul(*argv++, NULL, 0);
+ seq->steps[i].supply_mask = strtoul(*argv++, NULL, 0);
+ }
+
+ return 1 + 2 * i;
+}
+static struct usrp_eeprom_db_pwr_seq db_pwr_seq;
+
+int parse_mcu_flags(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_mcu_flags *flags = data;
+ unsigned long tmp;
+ int i;
+
+ if (!argc) {
+ sprintf(data, "<flags[0]> <flags[1]> <flags[2]> "
+ "<flags[3]> <flags[4]> <flags[5]>");
+ return 0;
+ }
+
+ assert(argc >= ARRAY_SIZE(flags->flags));
+ for (i = 0; i < ARRAY_SIZE(flags->flags); i++) {
+ tmp = strtoul(argv[i], NULL, 0);
+ assert(tmp <= 0xFF);
+
+ flags->flags[i] = tmp;
+ }
+
+ return i;
+}
+static struct usrp_eeprom_mcu_flags mcu_flags;
+
+int parse_fan_limits(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_fan_limits *fan_limits = data;
+ int i;
+
+ if (!argc) {
+ sprintf(data, "<fan min rpm> <fan start rpm> <fan max rpm>");
+ return 0;
+ }
+
+ assert(argc >= 3);
+ fan_limits->min = strtoul(argv[0], NULL, 0);
+ fan_limits->start = strtoul(argv[1], NULL, 0);
+ fan_limits->max = strtoul(argv[2], NULL, 0);
+
+ return 3;
+}
+static struct usrp_eeprom_fan_limits fan_limits;
+
+int parse_fan_fixed_capacity(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_fan_fixed_capacity *fan_fixed_capacity = data;
+
+ if (!argc) {
+ sprintf(data, "<fan fixed capacity>");
+ return 0;
+ }
+
+ fan_fixed_capacity->capacity = strtoul(argv[0], NULL, 0);
+
+ return 1;
+}
+static struct usrp_eeprom_fan_fixed_capacity fan_fixed_capacity;
+
+int parse_clkaux_tuning_word(int argc, char **argv, void *data)
+{
+ struct usrp_eeprom_clkaux_tuning_word *clkaux_tuning_word = data;
+
+ if (!argc) {
+ sprintf(data, "<dac tuning word>");
+ return 0;
+ }
+
+ clkaux_tuning_word->tuning_word = strtoul(argv[0], NULL, 0);
+
+ return 1;
+}
+static struct usrp_eeprom_clkaux_tuning_word clkaux_tuning_word;
+
+struct arg_parser {
+ const char *arg;
+ const char *alias;
+ void *store;
+ size_t size;
+ uint8_t tag;
+ int (*parse)(int, char **, void *);
+ int dirty;
+};
+
+#define PARSER_(name_, alias_, data_, tag_, parser_) { \
+ .arg = #name_, \
+ .alias = alias_, \
+ .store = &(data_), \
+ .size = sizeof((data_)), \
+ .tag = (tag_), \
+ .parse = (parser_), \
+}
+
+#define PARSER(param_, tag_) PARSER_(param_, NULL, param_, tag_, parse_ ##param_)
+#define MAC_PARSER_ALIAS(idx_, alias_) PARSER_( mac_addr_ ## idx_, (alias_), mac_addrs[(idx_)], USRP_EEPROM_MAC_ADDR_TAG((idx_)), parse_mac)
+#define MAC_PARSER(idx_) MAC_PARSER_ALIAS(idx_, NULL)
+
+static struct arg_parser parsers[] = {
+ PARSER(board_info, USRP_EEPROM_BOARD_INFO_TAG),
+ PARSER(module_info, USRP_EEPROM_MODULE_INFO_TAG),
+ MAC_PARSER_ALIAS(0, "eth0_mac"),
+ MAC_PARSER_ALIAS(1, "qsfp0_mac"),
+ MAC_PARSER_ALIAS(2, "qsfp1_mac"),
+ MAC_PARSER(3),
+ MAC_PARSER(4),
+ MAC_PARSER(5),
+ MAC_PARSER(6),
+ MAC_PARSER(7),
+ MAC_PARSER(8),
+ MAC_PARSER(9),
+ MAC_PARSER(10),
+ MAC_PARSER(11),
+ MAC_PARSER(12),
+ PARSER(db_pwr_seq, USRP_EEPROM_DB_PWR_SEQ_TAG),
+ PARSER(mcu_flags, USRP_EEPROM_MCU_FLAGS),
+ PARSER(fan_limits, USRP_EEPROM_FAN_LIMITS),
+ PARSER(fan_fixed_capacity, USRP_EEPROM_FAN_FIXED_CAPACITY),
+ PARSER(clkaux_tuning_word, USRP_EEPROM_CLKAUX_TUNING_WORD),
+};
+
+static struct arg_parser *parser_lookup(const char *arg)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(parsers); i++) {
+ if (!strcmp(parsers[i].arg, arg))
+ return parsers + i;
+ if (parsers[i].alias && !strcmp(parsers[i].alias, arg))
+ return parsers + i;
+ }
+
+ return NULL;
+}
+
+#ifdef TLV_EEPROM_UPDATE
+static void tlv_update(uint8_t tag, uint8_t len, const void *val)
+{
+ struct arg_parser *parser = NULL;
+
+ for (size_t i = 0; i < ARRAY_SIZE(parsers); i++) {
+ if (parsers[i].tag == tag)
+ parser = parsers + i;
+ }
+
+ if (!parser) {
+ fprintf(stderr, "found unknown tag %02x len %u", tag, len);
+ fprintf(stderr, "cannot use this utility to update\n");
+ exit(EXIT_FAILURE);
+ }
+
+ assert(len == parser->size);
+ memcpy(parser->store, val, len);
+ parser->dirty = 1;
+}
+
+static void handle_update(const char *filename)
+{
+ struct tlv_eeprom *eeprom;
+
+ eeprom = tlv_eeprom_read_from_file(filename);
+ if (!eeprom) {
+ perror("failed to read");
+ exit(EXIT_FAILURE);
+ }
+
+ if (tlv_eeprom_validate(eeprom, USRP_EEPROM_MAGIC)) {
+ fprintf(stderr, "contents invalid, cannot update\n");
+ exit(EXIT_FAILURE);
+ }
+
+ tlv_for_each(eeprom->tlv, eeprom->size, tlv_update);
+ free(eeprom);
+}
+#else
+static void handle_update(const char *filename)
+{
+}
+#endif
+
+static void validate_file_matches(const struct tlv_eeprom *eeprom,
+ const char *filename)
+{
+ struct tlv_eeprom *file_eeprom;
+
+ file_eeprom = tlv_eeprom_read_from_file(filename);
+ if (!file_eeprom) {
+ perror("failed to read for validate");
+ exit(EXIT_FAILURE);
+ }
+
+ if (memcmp(file_eeprom, eeprom, sizeof(*eeprom))) {
+ fprintf(stderr, "eeprom validation failed! "
+ "values read do not match what was written. "
+ "perhaps the eeprom was write-protected?\n");
+ exit(EXIT_FAILURE);
+ }
+
+ free(file_eeprom);
+}
+
+static void usage(const char *argv0)
+{
+ const struct arg_parser *p;
+ char buffer[4096];
+
+ fprintf(stderr, "usage: %s <output_file>\n", argv0);
+ for (size_t i = 0; i < ARRAY_SIZE(parsers); i++) {
+ p = parsers + i;
+ p->parse(0, NULL, buffer);
+ fprintf(stderr, "\t--%s %s\n", p->arg, buffer);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ struct arg_parser *parser;
+ const char *output_file;
+ struct tlv_eeprom eeprom;
+ uint8_t *ptr;
+ int ret;
+
+ if (argc < 2) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ output_file = argv[1];
+ handle_update(output_file);
+
+ argc -= 2;
+ argv += 2;
+
+ while (argc > 0) {
+ if (strncmp("--", *argv, 2)) {
+ fprintf(stderr, "unexpected arg\n");
+ return 1;
+ }
+
+ *argv += 2;
+ parser = parser_lookup(*argv);
+ if (!parser) {
+ fprintf(stderr, "unknown type: %s\n", *argv);
+ return 1;
+ }
+
+ argc--;
+ argv++;
+
+ ret = parser->parse(argc, argv, parser->store);
+ if (ret < 0) {
+ fprintf(stderr, "parsing failed\n");
+ return 1;
+ }
+ parser->dirty = 1;
+
+ argc -= ret;
+ argv += ret;
+ }
+
+ /*
+ * Now that the data's been parsed, write back any changed fields
+ */
+ memset(&eeprom, 0, sizeof(eeprom));
+ ptr = eeprom.tlv;
+ for (size_t i = 0; i < ARRAY_SIZE(parsers); i++) {
+ parser = parsers + i;
+ if (!parser->dirty)
+ continue;
+ ptr += tlv_write(ptr, parser->tag, parser->size, parser->store);
+ }
+
+ tlv_eeprom_seal(&eeprom, USRP_EEPROM_MAGIC, ptr - eeprom.tlv);
+ tlv_eeprom_write_to_file(&eeprom, output_file);
+ validate_file_matches(&eeprom, output_file);
+
+ return 0;
+}
diff --git a/mpm/tools/tlv_eeprom/eeprom-path b/mpm/tools/tlv_eeprom/eeprom-path
new file mode 100644
index 000000000..123c3b7f7
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/eeprom-path
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+from pathlib import Path
+import sys
+
+DT_BASE = Path("/sys/firmware/devicetree/base")
+
+def find_eeprom(name):
+ with open(DT_BASE / "__symbols__" / name) as f:
+ dt_path = DT_BASE / f.read()[1:-1]
+
+ for dev in Path("/sys/bus/nvmem/devices").glob("*"):
+ of_node_path = dev / "of_node"
+ if of_node_path.exists() and dt_path.samefile(of_node_path):
+ return dev / "nvmem"
+
+def list_eeproms():
+ return [dev.name for dev in Path(DT_BASE / "__symbols__").glob("*_eeprom")]
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print("Return the path to an eeprom")
+ print("usage: %s <eeprom_name>" % sys.argv[0])
+ print("valid eeprom_names: " + ', '.join(list_eeproms()))
+ sys.exit(1)
+ name = sys.argv[1]
+ try:
+ path = find_eeprom(name)
+ if path is None:
+ print("%s: found symbol, but nvmem device not present"% sys.argv[0], file=sys.stderr)
+ sys.exit(1)
+ except:
+ print("%s: could not find nvmem device for devicetree symbol %s" % (sys.argv[0], name), file=sys.stderr)
+ sys.exit(1)
+
+ print(path)
diff --git a/mpm/tools/tlv_eeprom/eeprom-pids.c b/mpm/tools/tlv_eeprom/eeprom-pids.c
new file mode 100644
index 000000000..d181133d7
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/eeprom-pids.c
@@ -0,0 +1,50 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "eeprom-pids.h"
+#include <stddef.h>
+
+static struct pid_info pid_list[] = {
+ { 0x0410, "titanium", "X410 Motherboard", 0 },
+ { 0x4000, NULL, "Power Aux Board", 0 },
+ { 0x4001, NULL, "Debug RF DB", 0 },
+ { 0x4002, "zbx", "Zirconium RF DB", 0},
+ { 0x4003, NULL, "HDMI SE DIO Aux Board", 0},
+ { 0x4004, NULL, "Clocking Aux Board with GPSDO", 0},
+ { 0x4005, NULL, "Clocking Aux Board (no GPSDO)", 0},
+ { 0x4006, NULL, "IF Test Manufacturing CCA", 0},
+};
+
+#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((*x)))
+
+const struct pid_info* get_info_from_pid(uint16_t pid) {
+ for (size_t i = 0; i < ARRAY_SIZE(pid_list); i++)
+ if (pid_list[i].pid == pid)
+ return &pid_list[i];
+
+ return NULL;
+}
+
+const char* get_name_from_pid(uint16_t pid) {
+ const struct pid_info *info = get_info_from_pid(pid);
+ if (!info)
+ return NULL;
+ return info->name;
+}
+
+const char* get_description_from_pid(uint16_t pid) {
+ const struct pid_info *info = get_info_from_pid(pid);
+ if (!info)
+ return NULL;
+ return info->description;
+}
+
+uint16_t get_rev_offset_from_pid(uint16_t pid) {
+ const struct pid_info *info = get_info_from_pid(pid);
+ if (!info)
+ return -1;
+ return info->rev_offset;
+}
diff --git a/mpm/tools/tlv_eeprom/eeprom-pids.h b/mpm/tools/tlv_eeprom/eeprom-pids.h
new file mode 100644
index 000000000..8358d6384
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/eeprom-pids.h
@@ -0,0 +1,21 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#pragma once
+
+#include <stdint.h>
+
+struct pid_info {
+ uint16_t pid;
+ const char* name;
+ const char* description;
+ uint16_t rev_offset;
+};
+
+const struct pid_info* get_info_from_pid(uint16_t pid);
+const char* get_name_from_pid(uint16_t pid);
+const char* get_description_from_pid(uint16_t pid);
+uint16_t get_rev_offset_from_pid(uint16_t pid);
diff --git a/mpm/tools/tlv_eeprom/eeprom-wrapper b/mpm/tools/tlv_eeprom/eeprom-wrapper
new file mode 100644
index 000000000..4e7c7dbfa
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/eeprom-wrapper
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -e
+
+util=$(basename $0)
+if [ $# -ge 1 ]; then
+ symbol=$1
+ if [ -n "$symbol" ] && [ "${symbol:(-7)}" != "_eeprom" ]; then
+ symbol="${symbol}_eeprom"
+ fi
+ path=$(eeprom-path $symbol)
+fi
+$util-core $path ${*:2} 2> >( sed "s!-core <\(eeprom\|output_file\)>! <symbol>!" >&2)
diff --git a/mpm/tools/tlv_eeprom/tlv_eeprom.c b/mpm/tools/tlv_eeprom/tlv_eeprom.c
new file mode 100644
index 000000000..8b2e2d9c0
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/tlv_eeprom.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Ettus Research, a National Instruments Brand
+ */
+
+#include <string.h>
+#include "tlv_eeprom.h"
+
+struct tlv_eeprom *tlv_eeprom_init(void *data)
+{
+ struct tlv_eeprom *e = data;
+
+ return e;
+}
+
+void tlv_eeprom_seal(struct tlv_eeprom *e, uint32_t magic, size_t len)
+{
+ e->magic = magic;
+ e->size = len;
+ e->crc = tlv_eeprom_crc(e);
+}
+
+int tlv_eeprom_validate(const struct tlv_eeprom *e, uint32_t magic)
+{
+ uint32_t crc;
+
+ if (e->magic != magic)
+ return -1;
+
+ if (e->size > TLV_EEPROM_SIZE)
+ return -2;
+
+ crc = tlv_eeprom_crc(e);
+ if (e->crc != crc)
+ return -3;
+
+ return 0;
+}
+
+struct tlv {
+ uint8_t tag;
+ uint8_t len;
+ uint8_t val[0];
+};
+
+
+static inline const struct tlv *tlv_next(const struct tlv *tlv, const void *end)
+{
+ const void *p = &tlv->val;
+
+ p += tlv->len;
+ if (p >= end)
+ return NULL;
+ return p;
+}
+
+static inline const struct tlv *tlv_find(const struct tlv *tlv,
+ const void *end, uint8_t tag)
+{
+ do {
+ if (tlv->tag == tag)
+ break;
+ } while ((tlv = tlv_next(tlv, end)));
+
+ return tlv;
+}
+
+void tlv_for_each(const void *buf, size_t bufsz,
+ void (*fn)(uint8_t tag, uint8_t len, const void *val))
+{
+ const struct tlv *tlv = buf;
+
+ do {
+ fn(tlv->tag, tlv->len, tlv->val);
+ } while ((tlv = tlv_next(tlv, buf + bufsz)));
+}
+
+const void *tlv_lookup(const void *buf, size_t bufsz, uint8_t tag)
+{
+ const struct tlv *tlv;
+
+ tlv = tlv_find(buf, buf + bufsz, tag);
+ if (!tlv)
+ return tlv;
+
+ return tlv->val;
+}
+
+size_t tlv_write(void *buf, uint8_t tag, uint8_t len, const void *val)
+{
+ struct tlv *tlv = buf;
+
+ tlv->tag = tag;
+ tlv->len = len;
+ memcpy(tlv->val, val, len);
+
+ return sizeof(*tlv) + len;
+}
diff --git a/mpm/tools/tlv_eeprom/tlv_eeprom.h b/mpm/tools/tlv_eeprom/tlv_eeprom.h
new file mode 100644
index 000000000..319a3cc29
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/tlv_eeprom.h
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Ettus Research, a National Instruments Brand
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+/*
+ * All eeproms on this platform are 256 bytes long
+ */
+#define TLV_EEPROM_SIZE (256)
+
+/**
+ * struct tlv_eeprom - structure describing the eeprom layout
+ *
+ * NOTE: All fields stored to eeprom are little endian, and this library
+ * assumes the processor running it is also little endian (i.e., no endianess
+ * conversion is performed).
+ *
+ * @magic: magic to identify the eeprom
+ * @crc: crc over the size field plus the following size bytes
+ * @size: size of remaining data in bytes
+ * @tlv: up to 244 bytes of tlv data
+ */
+struct tlv_eeprom {
+ uint32_t magic;
+ uint32_t crc;
+ uint32_t size;
+ uint8_t tlv[TLV_EEPROM_SIZE - 3 * sizeof(uint32_t)];
+} __attribute__((packed));
+
+/**
+ * tlv_eeprom_init - create tlv_eeprom from backing store
+ * @data: backing store, which must be at least TLV_EEPROM_SIZE bytes
+ */
+struct tlv_eeprom *tlv_eeprom_init(void *data);
+
+/**
+ * tlv_eeprom_validate - check if an eeprom is valid
+ * @e: eeprom to validate
+ * @magic: expected magic for this eeprom
+ *
+ * Checks to see whether the eeprom is valid. This validates the magic, the
+ * size, and the CRC of the data.
+ *
+ * Return: zero on success, -1 for a magic mismatch, -2 for an invalid size, -3
+ * for an invalid crc
+ */
+int tlv_eeprom_validate(const struct tlv_eeprom *e, uint32_t magic);
+
+/**
+ * tlv_eeprom_seal - seal an eeprom
+ * @e: eeprom to seal
+ * @magic: magic to use for this eeprom
+ * @len: size of tlv data stored in eeprom
+ *
+ * Updates the magic, size, and crc of the eeprom. This should be called prior
+ * to writing an eeprom.
+ */
+void tlv_eeprom_seal(struct tlv_eeprom *e, uint32_t magic, size_t len);
+
+/*
+ * tlv_eeprom_crc - return the CRC for the eeprom
+ * @eeprom: eeprom to compute the crc
+ *
+ * Note: e->size must be valid!
+ *
+ * Returns: the crc
+ */
+extern uint32_t tlv_eeprom_crc(const struct tlv_eeprom *e);
+
+/**
+ * tlv_write - writes a tlv tuple to the buffer
+ * @buf: buffer to write to
+ * @tag: tag for this data
+ * @len: length of value
+ * @val: value to write
+ *
+ * Return: number of bytes written to buffer, including tag and size
+ */
+size_t tlv_write(void *buf, uint8_t tag, uint8_t len, const void *val);
+
+/**
+ * tlv_lookup - lookup the value associated with a tag
+ * @buf: buffer containing tlv data
+ * @bufsz: size of buffer containing tlv data
+ * @tag: tag to lookup
+ *
+ * Return: pointer to value at tag, or NULL if tag was not found
+ */
+const void *tlv_lookup(const void *buf, size_t bufsz, uint8_t tag);
+
+/*
+ * tlv_for_each - call fn for each value in buffer
+ * @buf: buffer containing tlv data
+ * @bufsz: size of buffer containing tlv data
+ * @fn: function to invoke for each value
+ */
+void tlv_for_each(const void *buf, size_t bufsz,
+ void (*fn)(uint8_t tag, uint8_t len, const void *val));
+
diff --git a/mpm/tools/tlv_eeprom/tlv_eeprom_io.c b/mpm/tools/tlv_eeprom/tlv_eeprom_io.c
new file mode 100644
index 000000000..83586e4f6
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/tlv_eeprom_io.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Ettus Research, a National Instruments Brand
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "tlv_eeprom.h"
+
+struct tlv_eeprom *tlv_eeprom_read_from_file(const char *path)
+{
+ struct tlv_eeprom *ep;
+ int fd;
+ uint8_t *ptr;
+ size_t len;
+ ssize_t rd;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ perror("couldn't open file");
+ return NULL;
+ }
+
+
+ ep = malloc(sizeof(struct tlv_eeprom));
+ if (!ep) {
+ perror("alloc failed");
+ return NULL;
+ }
+
+ len = sizeof(struct tlv_eeprom);
+ ptr = (uint8_t *)ep;
+
+ while (len) {
+ rd = read(fd, ptr, len);
+ if (rd < 0) {
+ perror("read failed");
+ goto out;
+ }
+
+ len -= rd;
+ ptr += rd;
+
+ if (!rd)
+ break;
+ }
+
+ return ep;
+out:
+ free(ep);
+ return NULL;
+}
+
+int tlv_eeprom_write_to_file(const struct tlv_eeprom *e, const char *path)
+{
+ int fd, rv = 0;
+ uint8_t *ptr;
+ size_t len;
+ ssize_t wr;
+
+ fd = open(path, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0) {
+ perror("couldn't open file");
+ return -1;
+ }
+
+ len = sizeof(*e);
+ ptr = (uint8_t *)e;
+
+ while (len) {
+ wr = write(fd, ptr, len);
+ if (wr < 0) {
+ perror("error writing file");
+ goto out;
+ }
+ len -= wr;
+ ptr += wr;
+ }
+
+out:
+ close(fd);
+ return rv;
+}
diff --git a/mpm/tools/tlv_eeprom/tlv_eeprom_io.h b/mpm/tools/tlv_eeprom/tlv_eeprom_io.h
new file mode 100644
index 000000000..348bc6207
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/tlv_eeprom_io.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Ettus Research, a National Instruments Brand
+ */
+
+#pragma once
+
+#include "tlv_eeprom.h"
+
+/**
+ * tlv_eeprom_write_to_file - write an eeprom to a file
+ * @e: eeprom to write
+ * @path: destination to write to
+ *
+ * Writes the eeprom to the specified path
+ *
+ * Return: zero on success, else negative error code
+ */
+int tlv_eeprom_write_to_file(const struct tlv_eeprom *e, const char *path);
+
+/**
+ * tlv_eeprom_read_from_file - read an eeprom from file
+ * @path: path to read from
+ *
+ * Reads from the path into an allocated eeprom object. No validation of the
+ * data is performed, and caller is responsible for free'ing this.
+ *
+ * Return: eeprom, or NULL if error
+ */
+struct tlv_eeprom *tlv_eeprom_read_from_file(const char *path);
+
diff --git a/mpm/tools/tlv_eeprom/usrp_eeprom.h b/mpm/tools/tlv_eeprom/usrp_eeprom.h
new file mode 100644
index 000000000..224ce8c1b
--- /dev/null
+++ b/mpm/tools/tlv_eeprom/usrp_eeprom.h
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Ettus Research, a National Instruments Brand
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#define USRP_EEPROM_MAGIC 0x55535250
+
+/**
+ * struct usrp_eeprom_board_info - common board info
+ *
+ * @pid: product id for the board
+ * @rev: current hardware revision, starting at 1
+ * @compat_rev: last hardware revision this is compatible with
+ * @serial: NUL-terminated serial number string
+ */
+#define USRP_EEPROM_BOARD_INFO_TAG (0x10)
+struct usrp_eeprom_board_info {
+ uint16_t pid;
+ uint16_t rev;
+ uint16_t compat_rev;
+ char serial[8];
+} __attribute__((packed));
+
+/**
+ * struct usrp_eeprom_module_info - common module info
+ *
+ * @pid: product id for the module
+ * @rev: module revision, starting at 1
+ * @serial: NUL-terminated serial number string for the module
+ */
+#define USRP_EEPROM_MODULE_INFO_TAG (0x11)
+struct usrp_eeprom_module_info {
+ uint16_t pid;
+ uint16_t rev;
+ char serial[8];
+} __attribute__((packed));
+
+/**
+ * struct usrp_eeprom_mac_addr - mac address
+ *
+ * @addr: 6-byte MAC address
+ */
+
+/* tags 0xA0 through 0xAF are reserved for MAC addresses */
+#define USRP_EEPROM_MAX_MAC_ADDRS 0xF
+#define USRP_EEPROM_MAC_ADDR_TAG(x) (0xA0 + ((x) & USRP_EEPROM_MAX_MAC_ADDRS))
+#define USRP_EEPROM_ETH0_ADDR_TAG USRP_EEPROM_MAC_ADDR_TAG(0)
+#define USRP_EEPROM_QSFP0_ADDR_TAG USRP_EEPROM_MAC_ADDR_TAG(1)
+#define USRP_EEPROM_QSFP1_ADDR_TAG USRP_EEPROM_MAC_ADDR_TAG(2)
+struct usrp_eeprom_mac_addr {
+ uint8_t addr[6];
+} __attribute__((packed));
+
+/**
+ * struct usrp_eeprom_db_pwr_seq - daughterboard power sequence
+ *
+ * @nsteps: the number of steps in the sequence
+ * @steps.delay: delay in milliseconds to wait after enabling supplies
+ * @steps.supply_mask: bitmask of supplies to enable
+ * For X410: bit 0 = 1.8, bit 1 = 2.5, 3.3, 3.7, 12
+ */
+#define USRP_EEPROM_DB_PWR_SEQ_TAG (0x12)
+struct usrp_eeprom_db_pwr_seq {
+ uint8_t nsteps;
+ struct {
+ uint16_t delay;
+ uint8_t supply_mask;
+ } steps[8];
+};
+
+#define USRP_EEPROM_MCU_FLAGS (0x20)
+struct usrp_eeprom_mcu_flags {
+ uint8_t flags[6];
+} __attribute__((packed));
+
+/**
+ * struct usrp_eeprom_fan_limits - fan speed (rpm) limits
+ *
+ * @min: minimum configurable speed
+ * @start: necessary start speed
+ * @max: maximum configurable speed
+ */
+#define USRP_EEPROM_FAN_LIMITS (0x21)
+struct usrp_eeprom_fan_limits {
+ uint16_t min;
+ uint16_t start;
+ uint16_t max;
+} __attribute__((packed));
+
+/**
+ * struct usrp_eeprom_fan_fixed_capacity - fixed cooling capacity
+ *
+ * @capacity: fixed cooling capacity percentage. Range 0-100.
+ * @reserved: extra byte for alignment
+ */
+#define USRP_EEPROM_FAN_FIXED_CAPACITY (0x22)
+struct usrp_eeprom_fan_fixed_capacity {
+ uint8_t capacity;
+ uint8_t reserved; /* for natural alignment */
+} __attribute__((packed));
+
+/**
+ * struct usrp_eeprom_clkaux_tuning_word - clk aux dac tuning word
+ *
+ * @tuning_word: clocking aux board dac tuning word. Range 0-1023.
+ */
+#define USRP_EEPROM_CLKAUX_TUNING_WORD (0x23)
+struct usrp_eeprom_clkaux_tuning_word {
+ uint16_t tuning_word;
+} __attribute__((packed));
+
+#include <stdio.h>
+#include <assert.h>
+
+static void usrp_eeprom_trace(uint8_t tag, uint8_t len, const void *val)
+{
+ uint8_t i;
+
+ switch (tag) {
+ case USRP_EEPROM_BOARD_INFO_TAG:
+ {
+ const struct usrp_eeprom_board_info *v = val;
+ assert(sizeof(*v) == len);
+ printf("%s (0x%02x) ", "usrp_eeprom_board_info", tag);
+ printf("pid: 0x%04x, rev: 0x%04x, compat_rev: 0x%04x, serial: %s\n",
+ v->pid, v->rev, v->compat_rev, v->serial);
+ }
+ break;
+ case USRP_EEPROM_MODULE_INFO_TAG:
+ {
+ const struct usrp_eeprom_module_info *v = val;
+ assert(sizeof(*v) == len);
+ printf("%s (0x%02x) ", "usrp_eeprom_module_info", tag);
+ printf("pid: 0x%04x, rev: 0x%04x, serial: %s\n",
+ v->pid, v->rev, v->serial);
+ }
+ break;
+ case USRP_EEPROM_MAC_ADDR_TAG(0) ... USRP_EEPROM_MAC_ADDR_TAG(USRP_EEPROM_MAX_MAC_ADDRS):
+ {
+ const struct usrp_eeprom_mac_addr *v = val;
+ assert(sizeof(*v) == len);
+ printf("%s mac_addr_%d (0x%02x) ", "usrp_eeprom_mac_addr",
+ tag - USRP_EEPROM_MAC_ADDR_TAG(0), tag);
+ for (i = 0; i < 6; i++)
+ printf("%02x%c", v->addr[i], i == 5 ? ' ' : ':');
+ printf("\n");
+ }
+ break;
+ case USRP_EEPROM_DB_PWR_SEQ_TAG:
+ {
+ const struct usrp_eeprom_db_pwr_seq *v = val;
+ assert(sizeof(*v) == len);
+ printf("%s (0x%02x) ", "usrp_eeprom_db_pwr_seq", tag);
+ for (i = 0; i < 8; i++)
+ printf("(%u, 0x%02x) ", v->steps[i].delay, v->steps[i].supply_mask);
+ printf("\n");
+ }
+ break;
+ case USRP_EEPROM_MCU_FLAGS:
+ {
+ const struct usrp_eeprom_mcu_flags *v = val;
+ printf("%s (0x%02x) ", "usrp_eeprom_mcu_flags", tag);
+ for (i = 0; i < 6; i++)
+ printf("0x%02x ", v->flags[i]);
+ printf("\n");
+ }
+ break;
+ case USRP_EEPROM_FAN_LIMITS:
+ {
+ const struct usrp_eeprom_fan_limits *v = val;
+ printf("%s (0x%02x) ", "usrp_eeprom_fan_limits", tag);
+ printf("min: %d, start: %d, max: %d", v->min, v->start, v->max);
+ printf("\n");
+ }
+ break;
+ case USRP_EEPROM_FAN_FIXED_CAPACITY:
+ {
+ const struct usrp_eeprom_fan_fixed_capacity *v = val;
+ printf("%s (0x%02x) ", "usrp_eeprom_fan_fixed_capacity", tag);
+ printf("%d", v->capacity);
+ printf("\n");
+ }
+ break;
+ case USRP_EEPROM_CLKAUX_TUNING_WORD:
+ {
+ const struct usrp_eeprom_clkaux_tuning_word *v = val;
+ printf("%s (0x%02x) ", "usrp_eeprom_clkaux_tuning_word", tag);
+ printf("%d", v->tuning_word);
+ printf("\n");
+ }
+ break;
+ default:
+ {
+ const uint8_t *ptr = val;
+ printf("%s (0x%02x) len: %hhu, val: ", "unknown", tag, len);
+ for (i = 0; i < len; i++)
+ printf("%02x ", ptr[i]);
+ printf("\n");
+ break;
+ }
+ }
+}