aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/eeprom.py
blob: 6aedc9f5e5ca72ee48ae8e9834424cecf933eb35 (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#
# Copyright 2017 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
EEPROM management code
"""

import struct
import zlib

EEPROM_DEFAULT_HEADER = struct.Struct("!I I")

class MboardEEPROM:
    """
    Given a nvmem path, read out EEPROM values from the motherboard's EEPROM.
    The format of data in the EEPROM must follow the following standard:

    - 4 bytes magic. This will always be the same value; checking this value is
                     a sanity check for if the read was successful.
    - 4 bytes version. This is the version of the EEPROM format.

    The following bytes are version-dependent:

    Version 1:

    - 4x4 bytes mcu_flags -> throw them away
    - 2 bytes hw_pid
    - 2 bytes hw_rev (starting at 0)
    - 8 bytes serial number (zero-terminated string of 7 characters)
    - 6 bytes MAC address for eth0
    - 2 bytes padding
    - 6 bytes MAC address for eth1
    - 2 bytes padding
    - 6 bytes MAC address for eth2
    - 2 bytes padding
    - 4 bytes CRC

    Version 2: (FWIW MPM doesn't care about the extra bytes)

    - 4x4 bytes mcu_flags -> throw them away
    - 2 bytes hw_pid
    - 2 bytes hw_rev (starting at 0)
    - 8 bytes serial number (zero-terminated string of 7 characters)
    - 6 bytes MAC address for eth0
    - 2 bytes Devicetree compatible -> throw them away
    - 6 bytes MAC address for eth1
    - 2 bytes EC compatible -> throw them away
    - 6 bytes MAC address for eth2
    - 2 bytes padding
    - 4 bytes CRC

    Version 3: (FWIW MPM doesn't care about the extra bytes)

    - 4x4 bytes mcu_flags -> throw them away
    - 2 bytes hw_pid
    - 2 bytes hw_rev (starting at 0)
    - 8 bytes serial number (zero-terminated string of 7 characters)
    - 6 bytes MAC address for eth0
    - 2 bytes Devicetree compatible -> throw them away
    - 6 bytes MAC address for eth1
    - 2 bytes EC compatible -> throw them away
    - 6 bytes MAC address for eth2
    - 2 bytes rev_compat
    - 4 bytes CRC

    MAC addresses are ignored here; they are read elsewhere. If we really need
    to know the MAC address of an interface, we can fish it out the raw data,
    or ask the system.
    """
    # Create one of these for every version of the EEPROM format:
    eeprom_header_format = (
        None, # For laziness, we start at version 1 and thus index 0 stays empty
        # Version 1
        "!I I 16s  H H 7s 1x 24s I",
        # Version 2 (Ignore the extra fields, it doesn't matter to MPM)
        "!I I 16s  H H 7s 1x 6s H 6s H 6s 2x I",
        # Version 3 (Ignore the extra fields, it doesn't matter to MPM)
        "!I I 16s  H H 7s 1x 6s H 6s H 6s H I",
    )
    eeprom_header_keys = (
        None, # For laziness, we start at version 1 and thus index 0 stays empty
        ('magic', 'eeprom_version', 'mcu_flags', 'pid', 'rev', 'serial', 'mac_addresses', 'CRC'), # Version 1
        ('magic', 'eeprom_version', 'mcu_flags', 'pid', 'rev', 'serial', 'mac_eth0', 'dt_compat', 'mac_eth1', 'ec_compat', 'mac_eth2', 'CRC'),  # Version 2 (Ignore the extra fields, it doesn't matter to MPM)
        ('magic', 'eeprom_version', 'mcu_flags', 'pid', 'rev', 'serial', 'mac_eth0', 'dt_compat', 'mac_eth1', 'ec_compat', 'mac_eth2', 'rev_compat', 'CRC'),  # Version 3
    )

class DboardEEPROM:
    """
    Given a nvmem path, read out EEPROM values from the daughterboard's EEPROM.
    The format of data in the EEPROM must follow the following standard:

    - 4 bytes magic. This will always be the same value; checking this value is
                     a sanity check for if the read was successful.
    - 4 bytes version. This is the version of the EEPROM format.

    The following bytes are version-dependent:

    Version 1:

    - 2 bytes hw_pid
    - 2 bytes hw_rev (starting at 0)
    - 8 bytes serial number (zero-terminated string of 7 characters)
    - 4 bytes CRC

    Version 2:

    - 2 bytes hw_pid
    - 1 byte hw_rev (starting at 0)
    - 1 byte dt_compat (starting at 0, MPM can ignore that)
    - 8 bytes serial number (zero-terminated string of 7 characters)
    - 4 bytes CRC

    MAC addresses are ignored here; they are read elsewhere. If we really need
    to know the MAC address of an interface, we can fish it out the raw data,
    or ask the system.
    """
    # Create one of these for every version of the EEPROM format:
    eeprom_header_format = (
        None, # For laziness, we start at version 1 and thus index 0 stays empty
        "!I I H H 7s 1x I", # Version 1
        "!I I H B 1x 7s 1x I", # Version 2
    )
    eeprom_header_keys = (
        None, # For laziness, we start at version 1 and thus index 0 stays empty
        # Version 1
        ('magic', 'eeprom_version', 'pid', 'rev', 'serial', 'CRC'),
        # Version 2 (Ignore the extra field, it doesn't matter to MPM)
        ('magic', 'eeprom_version', 'pid', 'rev', 'serial', 'CRC'),
    )


def read_eeprom(
        nvmem_path,
        offset,
        eeprom_header_format,
        eeprom_header_keys,
        expected_magic,
        max_size=None
):
    """
    Read the EEPROM located at nvmem_path and return a tuple (header, data)
    Header is already parsed in the common header fields
    Data contains the full eeprom data structure

    nvmem_path -- Path to readable file (typically something in sysfs)
    eeprom_header_format -- List of header formats, by version
    eeprom_header_keys -- List of keys for the entries in the EEPROM
    expected_magic -- The magic value that is expected
    max_size -- Max number of bytes to be read. If omitted, will read the full file.
    """
    assert len(eeprom_header_format) == len(eeprom_header_keys)
    def _parse_eeprom_data(
            data,
            version,
        ):
        """
        Parses the raw 'data' according to the version.
        This also parses the CRC and assumes CRC is the last 4 bytes of each data.
        Returns a dictionary.
        """
        eeprom_parser = struct.Struct(eeprom_header_format[version])
        eeprom_keys = eeprom_header_keys[version]
        parsed_data = eeprom_parser.unpack_from(data)
        read_crc = parsed_data[-1]
        rawdata_without_crc = data[:eeprom_parser.size-4]
        expected_crc = zlib.crc32(rawdata_without_crc) & 0xffffffff
        if read_crc != expected_crc:
            raise RuntimeError(
                "Received incorrect CRC."\
                "Read: {:08X} Expected: {:08X}".format(
                    read_crc, expected_crc))
        return dict(list(zip(eeprom_keys, parsed_data)))
    # Dawaj, dawaj
    max_size = max_size or -1
    with open(nvmem_path, "rb") as nvmem_file:
        data = nvmem_file.read(max_size)[offset:]
    eeprom_magic, eeprom_version = EEPROM_DEFAULT_HEADER.unpack_from(data)
    if eeprom_magic != expected_magic:
        raise RuntimeError(
            "Received incorrect EEPROM magic. " \
            "Read: {:08X} Expected: {:08X}".format(
                eeprom_magic, expected_magic))
    if eeprom_version >= len(eeprom_header_format):
        raise RuntimeError("Unexpected EEPROM version: `{}'".format(eeprom_version))
    return (_parse_eeprom_data(data, eeprom_version), data)