aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/tlv_eeprom.py
blob: 671433ffafaed2d0f796fec84fa36f53ad8b43da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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