diff options
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager/base.py')
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/base.py | 348 |
1 files changed, 309 insertions, 39 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/base.py b/mpm/python/usrp_mpm/periph_manager/base.py index 8d2d5773a..e46df44c2 100644 --- a/mpm/python/usrp_mpm/periph_manager/base.py +++ b/mpm/python/usrp_mpm/periph_manager/base.py @@ -19,13 +19,148 @@ Mboard implementation base class """ import os -from six import iteritems +import struct +from six import iteritems, itervalues from ..mpmlog import get_logger -from ..mpmtypes import EEPROM -from .. import dboard_manager -from .udev import get_eeprom_path +from .udev import get_eeprom_paths from .udev import get_spidev_nodes +EEPROM_DEFAULT_HEADER = struct.Struct("!I I") + +class MboardEEPROM(object): + """ + 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 + + 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 16x H H 7s 25x I", # Version 1 + ) + eeprom_header_keys = ( + None, # For laziness, we start at version 1 and thus index 0 stays empty + ('magic', 'eeprom_version', 'pid', 'rev', 'serial', 'CRC'), # Version 1 + ) + +class DboardEEPROM(object): + """ + 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 + + 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 + ) + eeprom_header_keys = ( + None, # For laziness, we start at version 1 and thus index 0 stays empty + ('magic', 'eeprom_version', 'pid', 'rev', 'serial', 'CRC'), # Version 1 + ) + + +def read_eeprom( + nvmem_path, + 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. + Returns a dictionary. + """ + eeprom_parser = struct.Struct(eeprom_header_format[version]) + eeprom_keys = eeprom_header_keys[version] + parsed_data = eeprom_parser.unpack_from(data) + return dict(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) + 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) + + +def get_dboard_class_from_pid(pid): + """ + Given a PID, return a dboard class initializer callable. + """ + from .. import dboard_manager + for member in itervalues(dboard_manager.__dict__): + try: + if issubclass(member, dboard_manager.DboardManagerBase) and \ + hasattr(member, 'pids') and \ + pid in member.pids: + return member + except (TypeError, AttributeError): + continue + return None + + class PeriphManagerBase(object): """" Base class for all motherboards. Common function and API calls should @@ -34,52 +169,187 @@ class PeriphManagerBase(object): """ # stores discovered device information in dicts claimed = False - dboards = {} - mboard_info = {"type": "unknown"} mboard_if_addrs = {} mboard_overlays = {} # this information has to be provided by # the specific periph_manager implementation - mboard_eeprom_addr = "" - dboard_eeprom_addrs = {} dboard_spimaster_addrs = {} updateable_components = [] sid_endpoints = {} available_endpoints = range(256) + ######################################################################### + # Overridables + # + # These values are meant to be overridden by the according subclasses + ######################################################################### + # Very important: A list of PIDs that apply to the current device. Must be + # list, even if there's only one entry. + pids = [] + # Address of the motherboard EEPROM. This could be something like + # "e0005000.i2c". This value will be passed to get_eeprom_paths() tos + # determine a full path to an EEPROM device. + # If empty, this will be ignored and no EEPROM info for the device is read + # out. + mboard_eeprom_addr = "" + # The EEPROM code checks for this word to see if the readout was valid. + # Typically, devices should not override this unless their EEPROM follows a + # different standard. + mboard_eeprom_magic = 0xF008AD10 + # If this value is not set, the code will try and read out the entire EEPROM + # content as a binary blob. Use this to limit the number of bytes actually + # read. It's usually safe to not override this, as EEPROMs typically aren't + # that big. + mboard_eeprom_max_len = None + # This is the *default* mboard info. The keys from this dict will be copied + # into the current device info before it actually gets initialized. This + # means that keys from this dict could be overwritten during the + # initialization process. + mboard_info = {"type": "unknown"} + # This is a sanity check value to see if the correct number of + # daughterboards are detected. If somewhere along the line more than + # max_num_dboards dboards are found, an error or warning is raised, + # depending on the severity of the issue. If fewer dboards are found, + # that's generally considered OK. + max_num_dboards = 2 + # Address of the daughterboard EEPROMs. This could be something like + # "e0004000.i2c". This value will be passed to get_eeprom_paths() to + # determine a full path to an EEPROM device. + # If empty, this will be ignored and no EEPROM info for the device is read + # out. + # If this is a list of EEPROMs, paths will be concatenated. + dboard_eeprom_addr = None + # The EEPROM code checks for this word to see if the readout was valid. + # Typically, devices should not override this unless their EEPROM follows a + # different standard. + dboard_eeprom_magic = 0xF008AD11 + # If this value is not set, the code will try and read out the entire EEPROM + # content as a binary blob. Use this to limit the number of bytes actually + # read. It's usually safe to not override this, as EEPROMs typically aren't + # that big. + dboard_eeprom_max_len = None + # If the dboard requires spidev access, the following attribute is a list + # of SPI master addrs (typically something like 'e0006000.spi'). You + # usually want the length of this list to be as long as the number of + # dboards, but if it's shorter, it simply won't instantiate list SPI nodes + # for those dboards. + dboard_spimaster_addrs = [] + + def __init__(self): + # First, make some checks to see if the child class is correctly set up: + assert len(self.pids) > 0 + assert self.mboard_eeprom_magic is not None + # Set up logging self.log = get_logger('PeriphManager') - # I know my EEPROM address, lets use it - self.overlays = "" - # (self._eeprom_head, self._eeprom_rawdata) = EEPROM().read_eeprom( - # get_eeprom_path(self.mboard_eeprom_addr)) - # print self._eeprom_head - self._dboard_eeproms = {} - self.log.debug("Initializing dboards") - # for dboard_slot, eeprom_addr in self.dboard_eeprom_addrs.iteritems(): - # self.log.debug("Adding dboard for slot {0}".format(dboard_slot)) - # spi_devices = [] - # # I know EEPROM adresses for my dboard slots - # eeprom_data = EEPROM().read_eeprom(get_eeprom_path(eeprom_addr)) - # # I know spidev masters on the dboard slots - # hw_pid = eeprom_data[0].get("hw_pid", 0) - # if hw_pid in dboard_manager.HW_PIDS: - # spi_devices = get_spidev_nodes(self.dboard_spimaster_addrs.get(dboard_slot)) - # dboard = dboard_manager.HW_PIDS.get(hw_pid, dboard_manager.unknown) - # self.dboards.update({dboard_slot: dboard(spi_devices, eeprom_data)}) - dboard_slot = 0 - self.log.debug("Adding dboard for slot {0}".format(dboard_slot)) - spi_devices = [] - # I know EEPROM adresses for my dboard slots - # eeprom_data = EEPROM().read_eeprom(get_eeprom_path(eeprom_addr)) - eeprom_data = None - # I know spidev masters on the dboard slots - hw_pid = 3 - if hw_pid in dboard_manager.HW_PIDS: - spi_devices = sorted(get_spidev_nodes("e0006000.spi")) - self.log.debug("Found spidev nodes: {0}".format(spi_devices)) - dboard = dboard_manager.HW_PIDS.get(hw_pid, dboard_manager.unknown) - self.dboards.update({dboard_slot: dboard(0, spi_devices, eeprom_data)}) + self._init_mboard_with_eeprom() + self._init_dboards() + + + def _init_mboard_with_eeprom(self): + """ + Starts the device initialization. Typically requires reading from an + EEPROM. + """ + if len(self.mboard_eeprom_addr): + self.log.trace("Reading EEPROM from address `{}'...".format(self.mboard_eeprom_addr)) + (self._eeprom_head, self._eeprom_rawdata) = read_eeprom( + get_eeprom_paths(self.mboard_eeprom_addr)[0], + MboardEEPROM.eeprom_header_format, + MboardEEPROM.eeprom_header_keys, + self.mboard_eeprom_magic, + self.mboard_eeprom_max_len, + ) + self.log.trace("Found EEPROM metadata: `{}'".format(str(self._eeprom_head))) + self.log.trace("Read {} bytes of EEPROM data.".format(len(self._eeprom_rawdata))) + for key in ('pid', 'serial', 'rev'): + self.mboard_info[key] = self._eeprom_head.get(key, '') + if self._eeprom_head.has_key('pid') and not self._eeprom_head['pid'] in self.pids: + self.log.error("Found invalid PID in EEPROM: 0x{:04X}. Valid PIDs are: {}".format( + self._eeprom_head['pid'], + ", ".join(["0x{:04X}".format(x) for x in self.pids]), + )) + raise RuntimeError("Invalid PID found in EEPROM.") + else: + self.log.trace("No EEPROM address to read from.") + self._eeprom_head = {} + self._eeprom_rawdata = '' + self.log.info("Device serial number: {}".format(self.mboard_info.get('serial', 'n/a'))) + + + def _init_dboards(self, override_dboard_pids=None): + """ + Initialize all the daughterboards + """ + override_dboard_pids = override_dboard_pids or [] + dboard_eeprom_addrs = self.dboard_eeprom_addr \ + if isinstance(self.dboard_eeprom_addr, list) \ + else [self.dboard_eeprom_addr] + dboard_eeprom_paths = [] + self.log.trace("Identifying dboard EEPROM paths from addrs `{}'...".format(",".join(dboard_eeprom_addrs))) + for dboard_eeprom_addr in dboard_eeprom_addrs: + self.log.trace("Resolving {}...".format(dboard_eeprom_addr)) + dboard_eeprom_paths += get_eeprom_paths(dboard_eeprom_addr) + self.log.trace("Found dboard EEPROM paths: {}".format(",".join(dboard_eeprom_paths))) + if len(dboard_eeprom_paths) > self.max_num_dboards: + self.log.warning("Found more EEPROM paths than daughterboards. Ignoring some of them.") + dboard_eeprom_paths = dboard_eeprom_paths[:self.max_num_dboards] + num_dboards = len(dboard_eeprom_paths) + self.dboards = [] + for dboard_idx, dboard_eeprom_path in enumerate(dboard_eeprom_paths): + self.log.debug("Initializing dboard {}...".format(dboard_idx)) + dboard_eeprom_md, dboard_eeprom_rawdata = read_eeprom( + dboard_eeprom_path, + DboardEEPROM.eeprom_header_format, + DboardEEPROM.eeprom_header_keys, + self.dboard_eeprom_magic, + self.dboard_eeprom_max_len, + ) + self.log.trace("Found dboard EEPROM metadata: `{}'".format(str(dboard_eeprom_md))) + self.log.trace("Read {} bytes of dboard EEPROM data.".format(len(dboard_eeprom_rawdata))) + if len(override_dboard_pids) > dboard_idx: + db_pid = override_dboard_pids[dboard_idx] + self.log.warning("Overriding dboard PID for dboard {} with 0x{:04X}.".format(dboard_idx, db_pid)) + else: + db_pid = dboard_eeprom_md.get('pid') + if db_pid is None: + self.log.warning("No dboard PID found!") + else: + self.log.debug("Found dboard PID in EEPROM: 0x{:04X}".format(db_pid)) + if len(self.dboard_spimaster_addrs) > dboard_idx: + spi_nodes = sorted(get_spidev_nodes(self.dboard_spimaster_addrs[dboard_idx])) + self.log.debug("Found spidev nodes: {0}".format(spi_nodes)) + else: + spi_nodes = [] + self.log.warning("No SPI nodes for dboard {}.".format(dboard_idx)) + dboard_info = { + 'eeprom_md': dboard_eeprom_md, + 'eeprom_rawdata': dboard_eeprom_rawdata, + 'pid': db_pid, + 'spi_nodes': spi_nodes, + } + # This will actually instantiate the dboard class: + db_class = get_dboard_class_from_pid(db_pid) + if db_class is None: + self.log.warning("Could not identify daughterboard class for PID {:04X}!".format(pid)) + continue + self.dboards.append(db_class(dboard_idx, **dboard_info)) + self.log.info("Found {} daughterboard(s).".format(len(self.dboards))) + + # self.overlays = "" + # self._dboard_eeproms = {} + # self.log.debug("Initializing dboards") + # # for dboard_slot, eeprom_addr in self.dboard_eeprom_addrs.iteritems(): + # # self.log.debug("Adding dboard for slot {0}".format(dboard_slot)) + # # spi_devices = [] + # # # I know EEPROM adresses for my dboard slots + # # eeprom_data = EEPROM().read_eeprom(get_eeprom_paths(eeprom_addr)) + # # # I know spidev masters on the dboard slots + # # hw_pid = eeprom_data[0].get("hw_pid", 0) + # # if hw_pid in dboard_manager.HW_PIDS: + # # spi_devices = get_spidev_nodes(self.dboard_spimaster_addrs.get(dboard_slot)) + # # dboard = dboard_manager.HW_PIDS.get(hw_pid, dboard_manager.unknown) + # # self.dboards.update({dboard_slot: dboard(spi_devices, eeprom_data)}) def safe_list_updateable_components(self): """ |