diff --git a/setup.py b/setup.py new file mode 100644 index 00000000000..a5d7bc4df5c --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +from setuptools import setup + +setup( + name='sonic-platform-common', + version='1.0', + description='Platform-specific peripheral hardware interface APIs for SONiC', + license='Apache 2.0', + author='SONiC Team', + author_email='linuxnetdev@microsoft.com', + url='https://github.com/Azure/sonic-platform-common', + maintainer='Joe LeVeque', + maintainer_email='jolevequ@microsoft.com', + packages=[ + 'sonic_eeprom', + 'sonic_led', + 'sonic_psu', + 'sonic_sfp', + ], + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Plugins', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: System Administrators', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.6', + 'Topic :: Utilities', + ], + keywords='sonic SONiC platform hardware interface api API', +) diff --git a/sonic_eeprom/__init__.py b/sonic_eeprom/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sonic_eeprom/eeprom_base.py b/sonic_eeprom/eeprom_base.py new file mode 100644 index 00000000000..c41ea5967d3 --- /dev/null +++ b/sonic_eeprom/eeprom_base.py @@ -0,0 +1,359 @@ +#! /usr/bin/python +# Copyright 2012 Cumulus Networks LLC, all rights reserved + +############################################################################# +# Base eeprom class containing the main logic for reading, writing, and +# setting the eeprom. The format definition is a list of tuples of: +# ('data name', 'data type', 'size in bytes') +# data type is one of 's', 'C', and 'x' (string, char, and ignore) +# 'burn' as a data name indicates the corresponding number of bytes are to +# be ignored + +try: + import exceptions + import binascii + import optparse + import os + import io + import sys + import struct + import subprocess + import fcntl +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + + +class EepromDecoder(object): + def __init__(self, path, format, start, status, readonly): + self.p = path + self.f = format + self.s = start + self.u = status + self.r = readonly + self.cache_name = None + self.cache_update_needed = False + self.lock_file = None + + def check_status(self): + if self.u <> '': + F = open(self.u, "r") + d = F.readline().rstrip() + F.close() + return d + else: + return 'ok' + + def set_cache_name(self, name): + # before accessing the eeprom we acquire an exclusive lock on the eeprom file. + # this will prevent a race condition where multiple instances of this app + # could try to update the cache at the same time + self.cache_name = name + self.lock_file = open(self.p, 'r') + fcntl.flock(self.lock_file, fcntl.LOCK_EX) + + def is_read_only(self): + return self.r + + def decoder(self, s, t): + return t + + def encoder(self, I, v): + return v + + def checksum_field_size(self): + return 4 # default + + def is_checksum_field(self, I): + return I[0] == 'crc' # default + + def checksum_type(self): + return 'crc32' + + def encode_checksum(self, crc): + if self.checksum_field_size() == 4: + return struct.pack('>I', crc) + elif self.checksum_field_size() == 1: + return struct.pack('>B', crc) + print 'checksum type not yet supported' + exit(1) + + def compute_2s_complement(self, e, size): + crc = 0 + loc = 0 + end = len(e) + while loc <> end: + crc += int('0x' + binascii.b2a_hex(e[loc:loc+size]), 0) + loc += size + T = 1 << (size * 8) + return (T - crc) & (T - 1) + + def compute_dell_crc(self, message): + poly = 0x8005 + reg = 0x0000 + message += '\x00\x00' + for byte in message: + mask = 0x80 + while (mask > 0): + reg<<=1 + if ord(byte) & mask: + reg += 1 + mask>>=1 + if reg > 0xffff: + reg &= 0xffff + reg ^= poly + return reg + + def calculate_checksum(self, e): + if self.checksum_type() == 'crc32': + return binascii.crc32(e) & 0xffffffff + + if self.checksum_type() == '2s-complement': + size = self.checksum_field_size() + return self.compute_2s_complement(e, size) + + if self.checksum_type() == 'dell-crc': + return self.compute_dell_crc(e) + print 'checksum type not yet supported' + exit(1) + + def is_checksum_valid(self, e): + offset = 0 - self.checksum_field_size() + crc = self.calculate_checksum(e[:offset]) + + loc = 0 + for I in self.f: + end = loc + I[2] + t = e[loc:end] + loc = end + if self.is_checksum_field(I): + i = self.decoder(I[0], t) + if int(i, 0) == crc: + return (True, crc) + else: + return (False, crc) + else: + continue + return (False, crc) + + def decode_eeprom(self, e): + loc = 0 + for I in self.f: + end = loc + I[2] + t = e[loc:end] + loc = end + if I[0] == 'burn': + continue + elif I[1] == 's': + i = t + else: + i = self.decoder(I[0], t) + print "%-20s: %s" %(I[0], i) + + def set_eeprom(self, e, cmd_args): + line = '' + loc = 0 + ndict = {} + fields = list(I[0] for I in list(self.f)) + if len(cmd_args): + for arg in cmd_args[0].split(','): + k, v = arg.split('=') + k = k.strip() + v = v.strip() + if k not in fields: + print "Error: invalid field '%s'" %(k) + exit(1) + ndict[k] = v + + for I in self.f: + # print the original value + end = loc + I[2] + sl = e[loc:end] + loc = end + if I[0] == 'burn': + #line += sl + # fill with zeros + line = line.ljust(len(line) + I[2], '\x00') + continue + elif I[1] == 's': + i = sl + else: + i = self.decoder(I[0], sl) + + if len(cmd_args) == 0: + if self.is_checksum_field(I): + print ("%-20s: %s " %(I[0], i)) + continue + + # prompt for new value + v = raw_input("%-20s: [%s] " %(I[0], i)) + if v == '': + v = i + else: + if I[0] not in ndict.keys(): + v = i + else: + v = ndict[I[0]] + + line += self.encoder(I, v) + + # compute and append crc at the end + crc = self.encode_checksum(self.calculate_checksum(line)) + + line += crc + + return line + + def open_eeprom(self): + ''' + Open the EEPROM device file. + If a cache file exists, use that instead of the EEPROM. + ''' + using_eeprom = True + eeprom_file = self.p + try: + if os.path.isfile(self.cache_name): + eeprom_file = self.cache_name + using_eeprom = False + except: + pass + self.cache_update_needed = using_eeprom + return io.open(eeprom_file, "rb") + + def read_eeprom(self): + sizeof_info = 0 + for I in self.f: + sizeof_info += I[2] + o = self.read_eeprom_bytes(sizeof_info) + return o + + def read_eeprom_bytes(self, byteCount, offset=0): + F = self.open_eeprom() + F.seek(self.s + offset) + o = F.read(byteCount) + if len(o) != byteCount: + raise RuntimeError("expected to read %d bytes from %s, " \ + %(byteCount, self.p) + + "but only read %d" %(len(o))) + F.close() + return o + + def write_eeprom(self, e): + F = open(self.p, "wb") + F.seek(self.s) + F.write(e) + F.close() + self.write_cache(e) + + def write_cache(self, e): + if self.cache_name: + F = open(self.cache_name, "wb") + F.seek(self.s) + F.write(e) + F.close() + + def update_cache(self, e): + if self.cache_update_needed: + self.write_cache(e) + fcntl.flock(self.lock_file, fcntl.LOCK_UN) + + def diff_mac(self, mac1, mac2): + if mac1 == '' or mac2 == '': + return 0 + mac1_octets = [] + mac1_octets = mac1.split(':') + mac1val = int(mac1_octets[5], 16) | int(mac1_octets[4], 16) << 8 | int(mac1_octets[3], 16) << 16 + mac2_octets = [] + mac2_octets = mac2.split(':') + mac2val = int(mac2_octets[5], 16) | int(mac2_octets[4], 16) << 8 | int(mac2_octets[3], 16) << 16 + # check oui matches + if (mac1_octets[0] != mac2_octets[0] + or mac1_octets[1] != mac2_octets[1] + or mac1_octets[2] != mac2_octets[2]) : + return 0 + + if mac2val < mac1val: + return 0 + + return (mac2val - mac1val) + + def increment_mac(self, mac): + if mac != "": + mac_octets = [] + mac_octets = mac.split(':') + ret_mac = int(mac_octets[5], 16) | int(mac_octets[4], 16) << 8 | int(mac_octets[3], 16) << 16 + ret_mac = ret_mac + 1 + + if (ret_mac & 0xff000000): + print 'Error: increment carries into OUI' + return '' + + mac_octets[5] = hex(ret_mac & 0xff)[2:].zfill(2) + mac_octets[4] = hex((ret_mac >> 8) & 0xff)[2:].zfill(2) + mac_octets[3] = hex((ret_mac >> 16) & 0xff)[2:].zfill(2) + + return ':'.join(mac_octets).upper() + + return '' + + @classmethod + def find_field(cls, e, name): + if not hasattr(cls, 'brd_fmt'): + raise RuntimeError("Class %s does not have brb_fmt" % cls) + if not e: + raise RuntimeError("EEPROM can not be empty") + brd_fmt = cls.brd_fmt + loc = 0 + for f in brd_fmt: + end = loc + f[2] + t = e[loc:end] + loc = end + if f[0] == name: + return t + + def base_mac_addr(self, e): + ''' + Returns the base MAC address found in the EEPROM. + + Sub-classes must override this method as reading the EEPROM + and finding the base MAC address entails platform specific + details. + + See also mgmtaddrstr() and switchaddrstr(). + ''' + print "ERROR: Platform did not implement base_mac_addr()" + raise NotImplementedError + + def mgmtaddrstr(self, e): + ''' + Returns the base MAC address to use for the Ethernet + management interface(s) on the CPU complex. + + By default this is the same as the base MAC address listed in + the EEPROM. + + See also switchaddrstr(). + ''' + return self.base_mac_addr(e) + + def switchaddrstr(self, e): + ''' + Returns the base MAC address to use for the switch ASIC + interfaces. + + By default this is *next* address after the base MAC address + listed in the EEPROM. + + See also mgmtaddrstr(). + ''' + return self.increment_mac(self.base_mac_addr(e)) + + def switchaddrrange(self, e): + # this function is in the base class only to catch errors + # the platform specific import should have an override of this method + # to provide the allocated mac range from syseeprom or flash sector or + # wherever that platform stores this info + print "Platform did not indicate allocated mac address range" + raise NotImplementedError + + def serial_number_str(self, e): + raise NotImplementedError("Platform did not indicate serial number") diff --git a/sonic_eeprom/eeprom_dts.py b/sonic_eeprom/eeprom_dts.py new file mode 100644 index 00000000000..e449f952d38 --- /dev/null +++ b/sonic_eeprom/eeprom_dts.py @@ -0,0 +1,141 @@ +#!/usr/bin/python +# Copyright 2012 Cumulus Networks LLC, all rights reserved + +try: + import os + import exceptions + import binascii + import subprocess +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +i2c_root = '/sys/class/i2c-adapter/' +mtd_root = '/dev/mtd' +dts_root = '/proc/device-tree/' +sys_dev = '/sys/devices/' + +# +# This routine takes a token list containing the desired eeprom types +# (e.g. 'sfp', 'psu', 'board'), and returns a dict of {[dts path:(dev attrs)]} +# for the corresponding eeproms. For those accessible via i2c, the attrs +# will contain (i2c bus index, i2c device number). For those mounted to +# /dev/mtd, the attrs will be (mtd partition number). +# +def get_dev_attr_from_dtb(tokens): + + dts_paths = [] + i2c_devices = [] + sub_devices = [] + + eep_dict = {} + + # + # first try i2c + # + try: + ph = subprocess.Popen(['/bin/ls', '-R', dts_root], + stdout=subprocess.PIPE, + shell=False, stderr=subprocess.STDOUT) + cmdout = ph.communicate()[0] + ph.wait() + except OSError, e: + raise OSError("cannot access directory") + + lines = cmdout.splitlines() + for I in lines: + if not I.endswith(':') or 'i2c' not in I: + continue + + I = I.rstrip(':\n\r') + last = I.split('/')[-1] + if 'i2c' in last: + depth = I.count('i2c') + while len(sub_devices) < depth: + sub_devices.append([]) + sub_devices[depth-1].append(I) + elif 'eeprom' in last: + dts_paths.append(I) + + # re-order the device heirarchy and build the device list + for i in sub_devices: + for j in i: + i2c_devices.append(j) + + for eep in dts_paths: + instance = '' + if os.path.isfile('/'.join([eep, 'label'])): + F = open('/'.join([eep, 'label']), "rb") + instance = F.read().partition('_eeprom')[0] + F.close() + + # check for read-only + read_only = os.path.isfile('/'.join([eep, 'read-only'])) + + for t in tokens: + if t not in eep and t not in instance: + continue + + # get the i2c idx by matching the path prefix + i2c_idx = i2c_devices.index(eep.rsplit('/', 1)[0]) + + # read the reg + reg_path = '/'.join([eep, 'reg']) + F = open(reg_path, "rb") + o = F.read() + reg = binascii.b2a_hex(o)[-2:] + F.close() + + eep_dict[eep] = {'type': 'i2c', \ + 'dev-id': i2c_idx, \ + 'reg': reg, \ + 'ro': read_only} + break + + + # + # now try flash + # + try: + ph = subprocess.Popen(['/bin/grep', '-r', 'eeprom', dts_root], + stdout=subprocess.PIPE, + shell=False, stderr=subprocess.STDOUT) + cmdout = ph.communicate()[0] + ph.wait() + except OSError, e: + raise OSError("cannot access directory") + + lines = cmdout.splitlines() + for I in lines: + if 'flash' not in I or 'label' not in I: + continue + + eep = I.partition(dts_root)[2].rpartition('label')[0] + full_eep = '/'.join([dts_root, eep]) + F = open('/'.join([full_eep, 'label']), "rb") + instance = F.read().partition('_eeprom')[0] + F.close() + + read_only = os.path.isfile('/'.join([full_eep, 'read-only'])) + + for t in tokens: + if t not in instance: + continue + + mtd_n = eep.partition('partition@')[2].rstrip('/') + eep_dict[full_eep] = {'type': 'mtd', \ + 'dev-id': mtd_n, \ + 'ro': read_only} + + return eep_dict + + +def dev_attr_to_path(dev_attrs): + dev_path = '' + if dev_attrs['type'] == 'i2c': + dev_path = i2c_root + 'i2c-' + str(dev_attrs['dev-id']) + '/' + \ + str(dev_attrs['dev-id']) + '-00' + str(dev_attrs['reg']) + \ + '/' + 'eeprom' + elif dev_attrs['type'] == 'mtd': + dev_path = mtd_root + dev_attrs['dev-id'] + + return dev_path diff --git a/sonic_eeprom/eeprom_tlvinfo.py b/sonic_eeprom/eeprom_tlvinfo.py new file mode 100644 index 00000000000..ca4b073be09 --- /dev/null +++ b/sonic_eeprom/eeprom_tlvinfo.py @@ -0,0 +1,545 @@ +#! /usr/bin/python +# Copyright 2012 Cumulus Networks LLC, all rights reserved + +############################################################################# +# Base eeprom class containing the main logic for reading, writing, and +# setting the eeprom. The format definition is a list of tuples of: +# ('data name', 'data type', 'size in bytes') +# data type is one of 's', 'C', and 'x' (string, char, and ignore) +# 'burn' as a data name indicates the corresponding number of bytes are to +# be ignored + +try: + import exceptions + import binascii + import optparse + import os + import sys + import eeprom_base +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + + +# +# TlvInfo Format - This eeprom format was defined by Cumulus Networks +# and can be found here: +# +# +class TlvInfoDecoder(eeprom_base.EepromDecoder): + + # Header Field Constants + _TLV_INFO_ID_STRING = "TlvInfo\x00" + _TLV_INFO_VERSION = 0x01 + _TLV_INFO_HDR_LEN = 11 + _TLV_INFO_MAX_LEN = 2048 + _TLV_TOTAL_LEN_MAX = _TLV_INFO_MAX_LEN - _TLV_INFO_HDR_LEN + _TLV_HDR_ENABLED = 1 + + # The Undefined TLV Type + _TLV_CODE_UNDEFINED = 0xFC + + # Default TLV Types + _TLV_CODE_PRODUCT_NAME = 0x21 + _TLV_CODE_PART_NUMBER = 0x22 + _TLV_CODE_SERIAL_NUMBER = 0x23 + _TLV_CODE_MAC_BASE = 0x24 + _TLV_CODE_MANUF_DATE = 0x25 + _TLV_CODE_DEVICE_VERSION = 0x26 + _TLV_CODE_LABEL_REVISION = 0x27 + _TLV_CODE_PLATFORM_NAME = 0x28 + _TLV_CODE_ONIE_VERSION = 0x29 + _TLV_CODE_MAC_SIZE = 0x2A + _TLV_CODE_MANUF_NAME = 0x2B + _TLV_CODE_MANUF_COUNTRY = 0x2C + _TLV_CODE_VENDOR_NAME = 0x2D + _TLV_CODE_DIAG_VERSION = 0x2E + _TLV_CODE_SERVICE_TAG = 0x2F + _TLV_CODE_VENDOR_EXT = 0xFD + _TLV_CODE_CRC_32 = 0xFE + + # By default disable the Quanta specific codes + _TLV_CODE_QUANTA_MAGIC = _TLV_CODE_UNDEFINED + _TLV_CODE_QUANTA_CRC = _TLV_CODE_UNDEFINED + _TLV_CODE_QUANTA_CARD_TYPE = _TLV_CODE_UNDEFINED + _TLV_CODE_QUANTA_HW_VERSION = _TLV_CODE_UNDEFINED + _TLV_CODE_QUANTA_SW_VERSION = _TLV_CODE_UNDEFINED + _TLV_CODE_QUANTA_MANUF_DATE = _TLV_CODE_UNDEFINED + _TLV_CODE_QUANTA_MODEL_NAME = _TLV_CODE_UNDEFINED + + # TLV Value Display Switch + _TLV_DISPLAY_VENDOR_EXT = False + + def __init__(self, path, start, status, ro, max_len=_TLV_INFO_MAX_LEN): + super(TlvInfoDecoder, self).__init__(path, \ + None, \ + start, \ + status, \ + ro) + self.eeprom_start = start + self.eeprom_max_len = max_len + + def decode_eeprom(self, e): + ''' + Decode and print out the contents of the EEPROM. + ''' + if self._TLV_HDR_ENABLED : + if not self.is_valid_tlvinfo_header(e): + print "EEPROM does not contain data in a valid TlvInfo format." + return + + print "TlvInfo Header:" + print " Id String: %s" % (e[0:7],) + print " Version: %d" % (ord(e[8]),) + total_len = (ord(e[9]) << 8) | ord(e[10]) + print " Total Length: %d" % (total_len,) + tlv_index = self._TLV_INFO_HDR_LEN + tlv_end = self._TLV_INFO_HDR_LEN + total_len + else : + tlv_index = self.eeprom_start + tlv_end = self._TLV_INFO_MAX_LEN + + print "TLV Name Code Len Value" + print "-------------------- ---- --- -----" + while (tlv_index + 2) < len(e) and tlv_index < tlv_end: + if not self.is_valid_tlv(e[tlv_index:]): + print "Invalid TLV field starting at EEPROM offset %d" % (tlv_index,) + return + print self.decoder(None, e[tlv_index:tlv_index + 2 + ord(e[tlv_index + 1])]) + if ord(e[tlv_index]) == self._TLV_CODE_QUANTA_CRC or \ + ord(e[tlv_index]) == self._TLV_CODE_CRC_32: + return + tlv_index += ord(e[tlv_index+1]) + 2 + + def set_eeprom(self, e, cmd_args): + ''' + Returns the new contents of the EEPROM. If command line arguments are supplied, + then those fields are overwritten or added to the existing EEPROM contents. If + not command line arguments are supplied, the user is prompted for the contents + of the EEPROM. + ''' + new_tlvs = "" + (crc_is_valid, crc) = self.is_checksum_valid(e) + if crc_is_valid: + if self._TLV_HDR_ENABLED: + tlv_index = self._TLV_INFO_HDR_LEN + tlv_end = self._TLV_INFO_HDR_LEN + ((ord(e[9]) << 8) | ord(e[10])) + else : + tlv_index = self.eeprom_start + tlv_end = self._TLV_INFO_MAX_LEN + + while tlv_index < len(e) and \ + tlv_index < tlv_end and \ + self.is_valid_tlv(e[tlv_index:]) and \ + ord(e[tlv_index]) != self._TLV_CODE_CRC_32 and \ + ord(e[tlv_index]) != self._TLV_CODE_QUANTA_CRC: + new_tlvs += e[tlv_index:tlv_index + 2 + ord(e[tlv_index + 1])] + tlv_index += ord(e[tlv_index+1]) + 2 + + if len(cmd_args): + for arg_str in cmd_args: + for arg in arg_str.split(','): + k, v = arg.split('=') + k = int(k.strip(), 0) + v = v.strip() + new_tlv = self.encoder((k,), v) + (tlv_found, index) = self.get_tlv_index(new_tlvs, k) + if tlv_found: + new_tlvs = new_tlvs[:index] + new_tlv + \ + new_tlvs[index + 2 + ord(new_tlvs[index + 1]):] + else: + new_tlvs += new_tlv + + else: + action = "a" + while action not in ['f', 'F']: + + action = raw_input("\n[a]dd, [m]odify, [d]elete, [f]inished: ") + if action in ['a', 'A', 'm', 'M']: + code = raw_input("Enter a TLV type code: ") + code = int(code, 0) + value = raw_input("Enter the value: ") + new_tlv = self.encoder((code,), value) + (tlv_found, index) = self.get_tlv_index(new_tlvs, code) + if tlv_found: + new_tlvs = new_tlvs[:index] + new_tlv + \ + new_tlvs[index + 2 + ord(new_tlvs[index + 1]):] + else: + new_tlvs += new_tlv + elif action in ['d', 'D']: + code = raw_input("Enter a TLV type code: ") + code = int(code, 0) + (tlv_found, index) = self.get_tlv_index(new_tlvs, code) + if tlv_found: + new_tlvs = new_tlvs[:index] + \ + new_tlvs[index + 2 + ord(new_tlvs[index + 1]):] + elif action in ['f', 'F']: + pass + else: + print "\nInvalid input, please enter 'a', 'm', 'd', or 'f'\n" + + if self._TLV_HDR_ENABLED: + new_tlvs_len = len(new_tlvs) + 6 + new_e = self._TLV_INFO_ID_STRING + chr(self._TLV_INFO_VERSION) + \ + chr((new_tlvs_len >> 8) & 0xFF) + \ + chr(new_tlvs_len & 0xFF) + new_tlvs + else: + new_e = new_tlvs + + if self._TLV_CODE_CRC_32 != self._TLV_CODE_UNDEFINED: + new_e = new_e + chr(self._TLV_CODE_CRC_32) + chr(4) + elif self._TLV_CODE_QUANTA_CRC != self._TLV_CODE_UNDEFINED: + new_e = new_e + chr(self._TLV_CODE_QUANTA_CRC) + chr(2) + else: + print "\nFailed to formulate new eeprom\n" + exit + new_e += self.encode_checksum(self.calculate_checksum(new_e)) + self.decode_eeprom(new_e) + if len(new_e) > min(self._TLV_INFO_MAX_LEN, self.eeprom_max_len): + sys.stderr.write("\nERROR: There is not enough room in the EEPROM to save data.\n") + exit(1) + return new_e + + def is_valid_tlvinfo_header(self, e): + ''' + Perform sanity checks on the first 11 bytes of the TlvInfo EEPROM + data passed in as a string. + 1. Large enough to hold the header + 2. First 8 bytes contain null-terminated ASCII string "TlvInfo" + 3. Version byte is 1 + 4. Total length bytes contain value which is less than or equal + to the allowed maximum (2048-11) + ''' + return len(e) >= self._TLV_INFO_HDR_LEN and \ + e[0:8] == self._TLV_INFO_ID_STRING and \ + ord(e[8]) == self._TLV_INFO_VERSION and \ + ((ord(e[9]) << 8) | ord(e[10])) <= self._TLV_TOTAL_LEN_MAX + + def is_valid_tlv(self, e): + ''' + Perform basic sanity checks on a TLV field. The TLV is in the string + provided. + 1. The TLV is at least 2 bytes long + 2. The length byte contains a value which does not cause the value + field to go beyond the length of the string. + ''' + return (len(e) >= 2 and (2 + ord(e[1]) <= len(e))) + + def is_checksum_valid(self, e): + ''' + Validate the checksum in the provided TlvInfo EEPROM data. + ''' + if not self.is_valid_tlvinfo_header(e): + return (False, 0) + + offset = self._TLV_INFO_HDR_LEN + ((ord(e[9]) << 8) | ord(e[10])) + if len(e) < offset or \ + ord(e[offset-6]) != self._TLV_CODE_CRC_32 or \ + ord(e[offset-5]) != 4: + return (False, 0) + + crc = self.calculate_checksum(e[:offset-4]) + tlv_crc = ord(e[offset-4]) << 24 | ord(e[offset-3]) << 16 | \ + ord(e[offset-2]) << 8 | ord(e[offset-1]) + if tlv_crc == crc: + return(True, crc) + + return (False, crc) + + def read_eeprom(self): + ''' + Read the eeprom contents. This is performed in two steps. First + the 11 bytes of the TlvInfo structure (the header) are read and + sanity checked. Then using the total length field in the header, + the rest of the data is read from the EEPROM. + ''' + offset = 0 + if self._TLV_HDR_ENABLED: + h = self.read_eeprom_bytes(self._TLV_INFO_HDR_LEN) + offset = self._TLV_INFO_HDR_LEN + if len(h) != self._TLV_INFO_HDR_LEN: + raise RuntimeError("expected to read %d bytes from %s, " \ + %(self._TLV_INFO_HDR_LEN, self.p) + \ + "but only read %d" %(len(h),)) + if not self.is_valid_tlvinfo_header(h): + return h + sizeof_tlvs = (ord(h[9]) << 8) | ord(h[10]) + else: + h = "" + sizeof_tlvs = self._TLV_INFO_MAX_LEN + + t = self.read_eeprom_bytes(sizeof_tlvs, offset) + if len(t) != sizeof_tlvs: + raise RuntimeError("expected to read %d bytes from %s, " \ + %(sizeof_tlvs, self.p) + \ + "but only read %d" %(len(t))) + return h + t + + def get_tlv_field(self, e, code): + ''' + Given an EEPROM string the TLV field for the provided code is + returned. This routine validates the EEPROM data (checksum and + other sanity checks) and then searches for a TLV field with the + supplied type code. A tuple of two items is returned. The first + item is a boolean indicating success and, if True, the second + item is a 3 element list with the type (int), length (int), + and value (string) of the requested TLV. + ''' + (is_valid, valid_crc) = self.is_checksum_valid(e) + if not is_valid: + return (False, None) + if self._TLV_HDR_ENABLED: + tlv_index = self._TLV_INFO_HDR_LEN + tlv_end = ((ord(e[9]) << 8) | ord(e[10])) + self._TLV_INFO_HDR_LEN + else : + tlv_index = self.eeprom_start + tlv_end = self._TLV_INFO_MAX_LEN + while tlv_index < len(e) and tlv_index < tlv_end: + if not self.is_valid_tlv(e[tlv_index:]): + return (False, None) + if ord(e[tlv_index]) == code: + return (True, [ord(e[tlv_index]), ord(e[tlv_index+1]), \ + e[tlv_index+2:tlv_index+2+ord(e[tlv_index+1])]]) + tlv_index += ord(e[tlv_index+1]) + 2 + return (False, None) + + def get_tlv_index(self, e, code): + ''' + Given an EEPROM string with just TLV fields (no TlvInfo header) + finds the index of the requested type code. This routine searches + for a TLV field with the supplied type code. A tuple of two items + is returned. The first item is a boolean indicating success and, + if True, the second item is the index in the supplied EEPROM string + of the matching type code. + ''' + tlv_index = 0 + while tlv_index < len(e): + if not self.is_valid_tlv(e[tlv_index:]): + return (False, 0) + if ord(e[tlv_index]) == code: + return (True, tlv_index ) + tlv_index += ord(e[tlv_index+1]) + 2 + return (False, 0) + + def base_mac_addr(self, e): + ''' + Returns the value field of the MAC #1 Base TLV formatted as a string + of colon-separated hex digits. + ''' + (is_valid, t) = self.get_tlv_field(e, self._TLV_CODE_MAC_BASE) + if not is_valid or t[1] != 6: + return super(TlvInfoDecoder, self).switchaddrstr(e) + + return ":".join([binascii.b2a_hex(T) for T in t[2]]) + + def switchaddrrange(self, e): + ''' + Returns the value field of the MAC #1 Size TLV formatted as a decimal + string + ''' + (is_valid, t) = self.get_tlv_field(e, self._TLV_CODE_MAC_SIZE) + if not is_valid: + return super(TlvInfoDecoder, self).switchaddrrange(e) + + return str((ord(t[2][0]) << 8) | ord(t[2][1])) + + def modelstr(self, e): + ''' + Returns the value field of the Product Name TLV as a string + ''' + (is_valid, t) = self.get_tlv_field(e, self._TLV_CODE_PRODUCT_NAME) + if not is_valid: + return super(TlvInfoDecoder, self).modelstr(e) + + return t[2] + + def serial_number_str(self, e): + ''' + Returns the value field of the Serial Number TLV as a string + ''' + valid, t = self.get_tlv_field(e, self._TLV_CODE_SERIAL_NUMBER) + if not valid: + return super(TlvInfoDecoder, self).serial_number_str(e) + return t[2] + + def decoder(self, s, t): + ''' + Return a string representing the contents of the TLV field. The format of + the string is: + 1. The name of the field left justified in 20 characters + 2. The type code in hex right justified in 5 characters + 3. The length in decimal right justified in 4 characters + 4. The value, left justified in however many characters it takes + The vailidity of EEPROM contents and the TLV field has been verified + prior to calling this function. The 's' parameter is unused + ''' + if ord(t[0]) == self._TLV_CODE_PRODUCT_NAME: + name = "Product Name" + value = str(t[2:2 + ord(t[1])]) + elif ord(t[0]) == self._TLV_CODE_PART_NUMBER: + name = "Part Number" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_SERIAL_NUMBER: + name = "Serial Number" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_MAC_BASE: + name = "Base MAC Address" + value = ":".join([binascii.b2a_hex(T) for T in t[2:8]]).upper() + elif ord(t[0]) == self._TLV_CODE_MANUF_DATE: + name = "Manufacture Date" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_DEVICE_VERSION: + name = "Device Version" + value = str(ord(t[2])) + elif ord(t[0]) == self._TLV_CODE_LABEL_REVISION: + name = "Label Revision" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_PLATFORM_NAME: + name = "Platform Name" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_ONIE_VERSION: + name = "ONIE Version" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_MAC_SIZE: + name = "MAC Addresses" + value = str((ord(t[2]) << 8) | ord(t[3])) + elif ord(t[0]) == self._TLV_CODE_MANUF_NAME: + name = "Manufacturer" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_MANUF_COUNTRY: + name = "Manufacture Country" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_VENDOR_NAME: + name = "Vendor Name" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_DIAG_VERSION: + name = "Diag Version" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_SERVICE_TAG: + name = "Service Tag" + value = t[2:2 + ord(t[1])] + elif ord(t[0]) == self._TLV_CODE_VENDOR_EXT: + name = "Vendor Extension" + value = "" + if self._TLV_DISPLAY_VENDOR_EXT: + for c in t[2:2 + ord(t[1])]: + value += "0x%02X " % (ord(c),) + elif ord(t[0]) == self._TLV_CODE_CRC_32 and len(t) == 6: + name = "CRC-32" + value = "0x%08X" % (((ord(t[2]) << 24) | (ord(t[3]) << 16) | (ord(t[4]) << 8) | ord(t[5])),) + # Quanta specific codes below here. + # These decodes are lifted from their U-Boot codes + elif ord(t[0]) == self._TLV_CODE_QUANTA_MAGIC and len(t) == 3: + name = "Magic Number" + value = "0x%02X" % (ord(t[2])) + elif ord(t[0]) == self._TLV_CODE_QUANTA_CRC and len(t) == 4: + name = "QUANTA-CRC" + value = "0x%04X" % ((ord(t[2]) << 8) + ord(t[3])) + elif ord(t[0]) == self._TLV_CODE_QUANTA_CARD_TYPE and len(t) == 6: + name = "Card Type" + value = "0x%08X" % (((ord(t[2]) << 24) | (ord(t[3]) << 16) | (ord(t[4]) << 8) | ord(t[5])),) + elif ord(t[0]) == self._TLV_CODE_QUANTA_HW_VERSION and len(t) == 6: + name = "Hardware Version" + value = "%d.%d" % (ord(t[2]), ord(t[3])) + elif ord(t[0]) == self._TLV_CODE_QUANTA_SW_VERSION and len(t) == 6: + name = "Software Version" + value = "%d.%d.%d.%d" % ((ord(t[2]) >> 4), (ord(t[2]) & 0xF), (ord(t[3]) >> 4), (ord(t[3]) & 0xF)) + elif ord(t[0]) == self._TLV_CODE_QUANTA_MANUF_DATE and len(t) == 6: + name = "Manufacture Date" + value = "%04d/%d/%d" % (((ord(t[2]) << 8) | ord(t[3])), ord(t[4]), ord(t[5])) + elif ord(t[0]) == self._TLV_CODE_QUANTA_MODEL_NAME: + name = "Model Name" + value = t[2:2 + ord(t[1])] + else: + name = "Unknown" + value = "" + for c in t[2:2 + ord(t[1])]: + value += "0x%02X " % (ord(c),) + return "%-20s 0x%02X %3d %s" % (name, ord(t[0]), ord(t[1]), value) + + def encoder(self, I, v): + ''' + Validate and encode the string 'v' into the TLV specified by 'I'. + I[0] is the TLV code. + ''' + try: + if I[0] == self._TLV_CODE_PRODUCT_NAME or \ + I[0] == self._TLV_CODE_PART_NUMBER or \ + I[0] == self._TLV_CODE_SERIAL_NUMBER or \ + I[0] == self._TLV_CODE_LABEL_REVISION or \ + I[0] == self._TLV_CODE_PLATFORM_NAME or \ + I[0] == self._TLV_CODE_ONIE_VERSION or \ + I[0] == self._TLV_CODE_MANUF_NAME or \ + I[0] == self._TLV_CODE_VENDOR_NAME or \ + I[0] == self._TLV_CODE_DIAG_VERSION or \ + I[0] == self._TLV_CODE_SERVICE_TAG: + errstr = "A string less than 256 characters" + if len(v) > 255: + raise + value = v + elif I[0] == self._TLV_CODE_DEVICE_VERSION: + errstr = "A number between 0 and 255" + num = int(v, 0) + if num < 0 or num > 255: + raise + value = chr(num) + elif I[0] == self._TLV_CODE_MAC_SIZE: + errstr = "A number between 0 and 65535" + num = int(v, 0) + if num < 0 or num > 65535: + raise + value = chr((num >> 8) & 0xFF) + chr(num & 0xFF) + elif I[0] == self._TLV_CODE_MANUF_DATE: + errstr = 'MM/DD/YYYY HH:MM:SS' + date, time = v.split() + mo, da, yr = [int(i) for i in date.split('/')] + hr, mn, sc = [int(i) for i in time.split(':')] + if len(v) < 19 or \ + mo < 1 or mo > 12 or da < 1 or da > 31 or yr < 0 or yr > 9999 or \ + hr < 0 or hr > 23 or mn < 0 or mn > 59 or sc < 0 or sc > 59: + raise + value = v + elif I[0] == self._TLV_CODE_MAC_BASE: + errstr = 'XX:XX:XX:XX:XX:XX' + mac_digits = v.split(':') + if len(mac_digits) != 6: + raise + value = "" + for c in mac_digits: + value = value + chr(int(c, 16)) + elif I[0] == self._TLV_CODE_MANUF_COUNTRY: + errstr = 'CC, a two character ISO 3166-1 alpha-2 country code' + if len(v) < 2: + raise + value = v[0:2] + elif I[0] == self._TLV_CODE_CRC_32: + value = '' + # Disallow setting any Quanta specific codes + elif I[0] == self._TLV_CODE_QUANTA_MAGIC or \ + I[0] == self._TLV_CODE_QUANTA_CARD_TYPE or \ + I[0] == self._TLV_CODE_QUANTA_HW_VERSION or \ + I[0] == self._TLV_CODE_QUANTA_SW_VERSION or \ + I[0] == self._TLV_CODE_QUANTA_MANUF_DATE or \ + I[0] == self._TLV_CODE_QUANTA_MODEL_NAME: + raise Exception('quanta-read-only') + else: + errstr = '0xXX ... A list of space-separated hexidecimal numbers' + value = "" + for c in v.split(): + value += chr(int(c, 0)) + except Exception as inst: + if (len(inst.args) > 0) and (inst.args[0] == 'quanta-read-only'): + sys.stderr.write("Error: '" + "0x%02X" % (I[0],) + "' -- Unable to set the read-only Quanta codes.\n") + else: + sys.stderr.write("Error: '" + "0x%02X" % (I[0],) + "' correct format is " + errstr + "\n") + exit(0) + + return chr(I[0]) + chr(len(value)) + value + + def is_checksum_field(self, I): + return False + + def checksum_field_size(self): + return 4 + + def checksum_type(self): + return 'crc32' diff --git a/sonic_led/__init__.py b/sonic_led/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sonic_led/led_control_base.py b/sonic_led/led_control_base.py new file mode 100644 index 00000000000..c4309c4b46c --- /dev/null +++ b/sonic_led/led_control_base.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# +# led_control_base.py +# +# Abstract base class for implementing platform-specific +# LED control functionality for SONiC +# + +try: + import abc +except ImportError, e: + raise ImportError (str(e) + " - required module not found") + +class LedControlBase(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def port_link_state_change(self, port, state): + """ + Called when port link state changes. Update port link state LED here. + + :param port: A string, SONiC port name (e.g., "Ethernet0") + :param state: A string, the port link state (either "up" or "down") + """ + return diff --git a/sonic_psu/__init__.py b/sonic_psu/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sonic_psu/psu_base.py b/sonic_psu/psu_base.py new file mode 100644 index 00000000000..2e2204298e0 --- /dev/null +++ b/sonic_psu/psu_base.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# +# psu_base.py +# +# Abstract base class for implementing platform-specific +# PSU control functionality for SONiC +# + +try: + import abc +except ImportError as e: + raise ImportError (str(e) + " - required module not found") + +class PsuBase(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def get_num_psus(self): + """ + Retrieves the number of PSUs supported on the device + + :return: An integer, the number of PSUs supported on the device + """ + return 0 + + @abc.abstractmethod + def get_psu_status(self, index): + """ + Retrieves the operational status of power supply unit (PSU) defined + by index 1-based + + :param index: An integer, 1-based index of the PSU of which to query status + :return: Boolean, + - True if PSU is operating properly: PSU is inserted and powered in the device + - False if PSU is faulty: PSU is inserted in the device but not powered + """ + return False + + @abc.abstractmethod + def get_psu_presence(self, index): + """ + Retrieves the presence status of power supply unit (PSU) defined + by 1-based index + + :param index: An integer, 1-based index of the PSU of which to query status + :return: Boolean, True if PSU is plugged, False if not + """ + return False + diff --git a/sonic_sfp/__init__.py b/sonic_sfp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/sonic_sfp/bcmshell.py b/sonic_sfp/bcmshell.py new file mode 100644 index 00000000000..63156c75382 --- /dev/null +++ b/sonic_sfp/bcmshell.py @@ -0,0 +1,496 @@ +#! /usr/bin/python +#------------------------------------------------------------------------------- +# +# Copyright 2012 Cumulus Networks, inc all rights reserved +# +#------------------------------------------------------------------------------- +# +try: + import sys + import os + import time + import socket + import re +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +#------------------------------------------------------------------------------- +# + +class bcmshell (object): + + """Simple expect like class that connects to a BCM diag shell, exported as a + socket fd by switchd, allowing users to run commands and extract delimited + output. bcmdiag opens the socket file, flushes the socket's read side and + issues commands via bcmdiag.run(). The command output, upto the diag shell + prompt, is read from the socket and returned to the caller.""" + + version = "1.0" + + #--------------- + # + def __init__(self, keepopen=False, timeout=10, opennow=False, logfileobj=None, + socketname="/var/run/docker-syncd/sswsyncd.socket", prompt='^drivshell>\s*$'): + """Constructor: + + keepopen - indicates that switchd socket should be kept open between + uses because the invoking application has a bunch of consecutive + activity. In this use case, the socket must be explicitly closed by + bcmshell.close() to allow multiplexing of multiple applications. + + timeout - the time, in seconds, that bcmshell.run() will sit on the + domain socket wait for read input. Note that as long as the socket is + handing us data, we'll keep reading for up seconds. + + logfileobj - a file object that should log all issued commands. None + disables command logging; sys.stdout logs every command to stdout. + logfileobj is flushed after every write. + + socketname - the name of the switchd socket file. + + prompt - the diag shell prompt that delimits the end of output""" + + if type(prompt) is not str: + raise SyntaxError("bcmshell constructor prompt expects an re string") + else: + self.re_prompt = re.compile(prompt, re.MULTILINE) + self.re_connectprompt = re.compile("bcmshell\r\n" + prompt, re.MULTILINE) + + if timeout <= 0: + raise ValueError("bcmshell.timeout must be > 0") + else: + self.timeout = timeout + if timeout > 180: + sys.stderr.write("warning: bcmshell.timeout, %s, is > 180s\n" % + str(self.timeout)) + + if not os.access(socketname, os.F_OK): + raise ValueError("socket %s does not exist" % socketname) + elif not os.access(socketname, os.R_OK | os.W_OK): + raise ValueError("missing read/write permissions for %s" % socketname) + else: + self.socketname = socketname + + if logfileobj is None: + self.logfileobj = None + elif type(logfileobj) is not file: + raise TypeError("bcmshell.logfileobj must be a file object not %s" % + type(logfileobj)) + elif 'w' not in logfileobj.mode: + raise TypeError("bcmshell.logfileobj is not a writeable file object") + else: + self.logfileobj = logfileobj + + self.keepopen = keepopen + self.socketobj = None + self.buffer = '' + + # text editing tools + # + self.re_oneline = re.compile('\n\s+') + self.re_reg_parse_raw = re.compile('^[\w()]+\.(.+)\[.*=(.*)$') + self.re_reg_parse_fields = re.compile('^[\w()]+\.(.+)\[.*\<(.*)\>') + self.re_get_field = re.compile('^(.+)=(.+)') + self.re_table_header = re.compile('^.*:[ <]+', re.MULTILINE) + self.re_table_trailer = re.compile('[ >]+$', re.MULTILINE) + self.re_conv = re.compile(r'(\d+|\D+)') + + # interface name conversion + # + self.modname = dict() + for I in range(0, 63): + self.modname['xe' + str(I)] = 'swp' + str(I) + + # open socket if required + # + if opennow and keepopen: + self.__open__() + + #--------------- + # + def __del__(self): + + """Destructor: flush and close all files that we opened""" + + try: + self.close() + except: + pass + + #--------------- + # + def __str__(self): + + """Return object state in human-readable form""" + + s = [] + s.append(repr(self)) + s.append('version: ' + self.version) + s.append('keepopen: ' + str(self.keepopen)) + s.append('timeout: ' + str(self.timeout) + ' seconds') + s.append('logfileobj: ' + str(self.logfileobj)) + s.append('socketname: ' + str(self.socketname)) + s.append('socketobj: ' + str(self.socketobj)) + s.append('prompt: \"' + str(self.re_prompt.pattern) + '\"') + s.append('buffer (last 100 chars): ' + str(self.buffer)[-100:]) + return '\n'.join(s) + + #--------------- + # + def getreg(self, reg, fields=False): + + """Get device register(s) by name. + + The general format in which registers are returned from the shell is + reg_name(N).M=V for raw values and reg_name(N).M= where N + is an array index, M is a module or port name, V is a value, and F is a + field name. Register/field values are all converted to unsigned + integers. There is a built in name converstion fucntion to change + Broadcom "xe*" port names to "swp*" names that match the kernel. + + bcmshell.getreg('reg_name') returns dict of lists of values. As an + optimization, if there is only one entry in the dict, only the list of + values is returned and if there is only one entry in each list, only the + values are returned + + Examples: + bcmshell.getreg('cmic_config') returns a value... + 1057759299. + + bcmshell.getreg('protocol_pkt_control') returns a list of values... + [0, 0, 0, ..., 0] + + bcmshell.getreg('egr_mtu') returns a dict of values.... + {'cpu0': 0x3fff, + 'xe0': 0x3fff, + ... + 'lb0': 0x3fff} + + bcmshell.getreg('pg_min_cell') returns a dict of lists of values.... + {'cpu0:'[0, 0, 0, 0, 0, 0, 0, 0], + 'xe0': [0, 0, 0, 0, 0, 0, 0, 0], + ... + 'lb0': [0, 0, 0, 0, 0, 0, 0, 0]} + + + bcmshell.getreg('reg_name', True) returns dict of lists of dicts of + field/values. The same optimizations used for raw values apply. + + Examples: + bcmshell.getreg('cmic_config', True) returns a dict of field/values... + {'IGNORE_MMU_BKP_TXDMA_PKT': 0, + 'EN_SER_INTERLEAVE_PARITY': 1, + 'MIIM_ADDR_MAP_ENABLE': 1, + 'IGNORE_MMU_BKP_REMOTE_PKT': 0, + ... + 'DMA_GARBAGE_COLLECT_EN': 0} + + bcmshell.getreg('protocol_pkt_control', True) returns + [{'SRP_PKT_TO_CPU':0, 'SRP_FWD_ACTION':0,... 'ARP_REPLY_DROP':0}, + {'SRP_PKT_TO_CPU':0, 'SRP_FWD_ACTION':0,... 'ARP_REPLY_DROP':0}, + ... + {'SRP_PKT_TO_CPU':0, 'SRP_FWD_ACTION':0,... 'ARP_REPLY_DROP':0}] + + bcmshell.getreg('egr_mtu', fields=True) returns a dict of dicts... + {'cpu0': {'MTU_SIZE',0x3fff, 'MTU_ENABLE',0}, + 'xe0': {'MTU_SIZE',0x3fff, 'MTU_ENABLE',0}, + ... + 'lb0': {'MTU_SIZE',0x3fff, 'MTU_ENABLE',0}} + + bcmshell.getreg('pg_min_cell') returns a dict of lists of values.... + {'cpu0:'[{'PG_MIN':0}, {'PG_MIN':0},... {'PG_MIN':0}], + 'xe0:'[{'PG_MIN':0}, {'PG_MIN':0},... {'PG_MIN':0}], + ... + 'lb0:'[{'PG_MIN':0}, {'PG_MIN':0},... {'PG_MIN':0}]} + """ + + # make sure everything is sane and read the register + # + if type(reg) is not str: + raise TypeError("expecting string argument to bmcdiag.getreg(reg)") + elif reg.find('\n') >= 0: + raise ValueError("unexpected newline in bmcdiag.getreg(%s)" % reg ) + elif reg.find('\s') >= 0: + raise ValueError("unexpected whitespace in bmcdiag.getreg(%s)" % reg) + + if fields: + t = self.run('getreg ' + reg) + else: + t = self.run('getreg raw ' + reg) + + if 'Syntax error parsing' in t: + raise RuntimeError('\"%s\" is not a register' % reg) + + # get the results into a list + # + t = self.re_oneline.sub('', t) + t = t.split('\n') + if t[-1] is '': + t.pop() + + # get the results into a dict (module) of lists (array) of values/fields + # + def __parse_reg__(text, fields=False): + if fields: + m = self.re_reg_parse_fields.search(text) + s = m.group(2).split(',') + t = dict([self.__get_field__(S) for S in s]) + else: + m = self.re_reg_parse_raw.search(text) + t = int(m.group(2), 16) + return(self.modname.get(m.group(1), m.group(1)), t) + + t = [__parse_reg__(T, fields) for T in t] + d = dict() + for I in t: + if I[0] in d: + d[I[0]].append(I[1]) + else: + d[I[0]] = [I[1]] + + # now optimize the return + # + for I in iter(d): + if len(d[I]) is 1: + d[I] = d[I][0] + + if len(d) is 1: + return d.values()[0] + else: + return d + + + #--------------- + # + def gettable(self, table, fields=False, start=None, entries=None): + + """Get device memory based table(s) by name. Tables are returned as a + list of value/field-dict. Table entry/field values are converted into + unsigned integers. If "fields" is set to True, we return a list of + dictionaries of field/value. + + Examples: + bcmshell.gettable('egr_ing_port') returns + [0, 0, 0, ... 0] + + bcmshell.gettable('egr_ing_port', True) returns + [{'HIGIG2': 0, 'PORT_TYPE': 0}, + {'HIGIG2': 0, 'PORT_TYPE': 0}, + {'HIGIG2': 0, 'PORT_TYPE': 0}, + ... + {'HIGIG2': 0, 'PORT_TYPE': 0}] + """ + + if type(table) is not str: + raise TypeError("bcmshell.gettable(table) expects string not %s" % + type(table)) + elif table.find('\n') >= 0: + raise ValueError("unexpected newline in bmcshell.gettable(%s)" % + table ) + elif table.find('\s') >= 0: + raise ValueError("unexpected whitespace in bmcshell.gettable(%s)" % + table) + + cmd = 'dump all' + if not fields: + cmd += ' raw' + cmd += " %s" % table + if start != None or entries != None: + cmd += " %d" % (start or 0) + cmd += " %d" % (entries or 1) + + t = self.run(cmd) + + if 'Unknown option or memory' in t: + raise RuntimeError('\"%s\" is not a table' % table) + + if 'out of range' in t: + err = table + if start != None or entries != None: + err += " %d" % (start or 0) + err += " %d" % (entries or 1) + raise IndexError('\"%s\" table index is out of range' % err) + + # get all of the return into a list + # + t = self.re_oneline.sub('', t) + t = self.re_table_header.sub('', t) + t = self.re_table_trailer.sub('', t) + t = t.split('\n') + if t[-1] is '': + t.pop() + + # parse the contents + # + def __parse_table__(text, fields=False): + if fields: + t = text.split(',') + v = [self.__get_field__(T) for T in t] + return dict(v) + else: + t = text.split() + v = 0 + for I in range(len(t)): + v += (int(t[I], 16) << (32 * I)) + return v + t = [__parse_table__(T, fields) for T in t] + + return t + + #--------------- + # + def cmd(self, cmd): + + """Run a command and print the results""" + + s = self.run(cmd) + if 'Unknown command:' in s: + raise ValueError(s) + print s + + + #--------------- + # + def prettyprint(self, d, level=0): + + """Print the structured output generated by getreg and gettable in a + human readable format""" + + s = level * 8 * " " + if type(d) is dict: + for I in sorted(d, key=self.__name_conv__): + if type(d[I]) is int: + print "%s %30s: " % (s, I), + else: + print "%s %s:" % (s, I) + self.prettyprint(d[I], (level + 1)) + elif type(d) is list: + for I in range(len(d)): + i = "[" + str(I) + "]" + if type(d[I]) is int or type(d[I]) is long: + print "%s %10s: " % (s, i), + else: + print "%s %s:" % (s, i) + self.prettyprint(d[I], (level + 1)) + else: + print "%s" % (hex(d)) + + + #--------------- + # + def close(self): + + """Close the socket object""" + + if self.socketobj is not None: + self.socketobj.shutdown(socket.SHUT_RDWR) + self.socketobj.close() + self.socketobj = None + + #--------------- + # + def run(self, cmd): + + """Issue the command to the diag shell and collect the return data until + we detect the prompt. cmd must be a string and must not include a + newline, i.e. we expect a single command to be run per call.""" + + if type(cmd) is not str: + raise TypeError("expecting string argument to bmcdiag.run(cmd)") + elif cmd.find('\n') >= 0: + raise ValueError("unexpected newline in bmcdiag.run(cmd)") + + self.__open__() + try: + self.socketobj.sendall(cmd + '\n') + except socket.error as (errno, errstr): + raise IOError("unable to send command \"%s\", %s" % (cmd, errstr)) + + self.buffer = '' + self.socketobj.settimeout(self.timeout) + quitting_time = time.time() + self.timeout + while True: + try: + self.buffer += self.socketobj.recv(4096) + except socket.timeout: + raise RuntimeError("recv stalled for %d seconds" % self.timeout) + found = self.re_prompt.search(self.buffer) + if found: + break + if time.time() > quitting_time: + raise RuntimeError("accepting input for %d seconds" % self.timeout) + + if found.end(0) != len(self.buffer): + raise RuntimeError("prompt detected in the middle of input") + + if not self.keepopen: + self.close() + return self.buffer[:found.start(0)] + + #--------------- + # + def __name_conv__(self, s): + l = self.re_conv.findall(s) + for I in range(len(l)): + if l[I].isdigit(): + l[I] = int(l[I]) + return l + + #--------------- + # + def __get_field__(self, text): + m = self.re_get_field.search(text) + return (m.group(1), int(m.group(2), 16)) + + #--------------- + # + def __open__(self): + + """Open the bcm diag shell socket exported by switchd. Complete any + dangling input by issuing a newline and flush the read side in case the + last user left something lying around. No-op if the socket is already + open. NOTE: socket.connect is non-blocking, so we need to exchange + a command with the bcm diag shell to know that we've actually obtained + socket ownership.""" + + if self.socketobj is None: + timeout = self.timeout + while True: + try: + self.socketobj = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.socketobj.settimeout(self.timeout) + self.socketobj.connect(self.socketname) + except socket.error as (errno, errstr): + if timeout == 0: + raise IOError("unable to open %s, %s" % (self.socketname, errstr)) + time.sleep(1) + timeout -= 1 + else: + break; + + + # flush out the socket in case it was left dirty + try: + self.socketobj.sendall('echo bcmshell\n') + quitting_time = time.time() + self.timeout + buf = '' + while True: + try: + buf += self.socketobj.recv(1024) + except socket.timeout: + raise IOError("unable to receive data from %s for %d seconds" % + (self.socketname, self.timeout)) + + found = self.re_connectprompt.search(buf) + if found: + break + if time.time() > quitting_time: + raise IOError("unable to flush %s for %d seconds" % + (self.socketname, self.timeout)) + + except socket.error as (errno, errstr): + raise IOError("Socket error: unable to flush %s on open: %s" % (self.socketname, errstr)) + except IOError as e: + raise IOError("unable to flush %s on open: %s" % (self.socketname, e.message)) + except: + raise IOError("unable to flush %s on open" % self.socketname) diff --git a/sonic_sfp/sff8436.py b/sonic_sfp/sff8436.py new file mode 100644 index 00000000000..503ad212994 --- /dev/null +++ b/sonic_sfp/sff8436.py @@ -0,0 +1,908 @@ +#! /usr/bin/env python +#---------------------------------------------------------------------------- +# SFF-8436 QSFP+ 10 Gbs 4X PLUGGABLE TRANSCEIVER +#---------------------------------------------------------------------------- + +try: + import fcntl + import struct + import sys + import time + import binascii + import os + import getopt + import types + from math import log10 + from sffbase import sffbase +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class sff8436InterfaceId(sffbase): + + version = '1.0' + + specification_compliance = {'10/40G Ethernet Compliance Code': + {'offset':3, + 'size':1, + 'type' : 'bitmap', + 'decode' : { + '10GBase-LRM': + {'offset': 3, + 'bit': 6}, + '10GBase-LR': + {'offset': 3, + 'bit': 5}, + '10GBase-SR': + {'offset': 3, + 'bit': 4}, + '40GBASE-CR4': + {'offset': 3, + 'bit': 3}, + '40GBASE-SR4': + {'offset': 3, + 'bit': 2}, + '40GBASE-LR4': + {'offset': 3, + 'bit': 1}, + '40G Active Cable (XLPPI)': + {'offset': 3, + 'bit': 0}}}, + 'SONET Compliance codes': + {'offset':4, + 'size':1, + 'type' : 'bitmap', + 'decode' : { + '40G OTN (OTU3B/OTU3C)': + {'offset': 4, + 'bit': 3}, + 'OC 48, long reach': + {'offset': 4, + 'bit': 2}, + 'OC 48, intermediate reach': + {'offset': 4, + 'bit': 1}, + 'OC 48 short reach': + {'offset': 4, + 'bit': 0}}}, + 'SAS/SATA compliance codes': + {'offset': 5, + 'size' : 1, + 'type' : 'bitmap', + 'decode': { + 'SAS 6.0G': + {'offset': 5, + 'bit': 5}, + 'SAS 3.0G': + {'offset': 5, + 'bit': 4}}}, + 'Gigabit Ethernet Compliant codes': + {'offset': 6, + 'size' : 1, + 'type' : 'bitmap', + 'decode': { + '1000BASE-T': + {'offset': 6, + 'bit': 3}, + '1000BASE-CX': + {'offset': 6, + 'bit': 2}, + '1000BASE-LX': + {'offset': 6, + 'bit': 1}, + '1000BASE-SX': + {'offset': 6, + 'bit': 0}}}, + 'Fibre Channel link length/Transmitter Technology': + {'offset': 7, + 'size' : 2, + 'type' : 'bitmap', + 'decode': { + 'Very long distance (V)': + {'offset': 7, + 'bit': 7}, + 'Short distance (S)': + {'offset': 7, + 'bit': 6}, + 'Intermediate distance (I)': + {'offset': 7, + 'bit': 5}, + 'Long distance (L)': + {'offset': 7, + 'bit': 4}, + 'Medium (M)': + {'offset': 7, + 'bit': 3}, + 'Longwave laser (LC)': + {'offset': 7, + 'bit': 1}, + 'Electrical inter-enclosure (EL)': + {'offset': 7, + 'bit': 0}, + 'Electrical intra-enclosure': + {'offset': 8, + 'bit': 7}, + 'Shortwave laser w/o OFC (SN)': + {'offset': 8, + 'bit': 6}, + 'Shortwave laser w OFC (SL)': + {'offset': 8, + 'bit': 5}, + 'Longwave Laser (LL)': + {'offset': 8, + 'bit': 4}}}, + 'Fibre Channel transmission media': + {'offset': 8, + 'size' : 1, + 'type' : 'bitmap', + 'decode': { + 'Twin Axial Pair (TW)': + {'offset': 8, + 'bit': 7}, + 'Shielded Twisted Pair (TP)': + {'offset': 8, + 'bit': 6}, + 'Miniature Coax (MI)': + {'offset': 8, + 'bit': 5}, + 'Video Coax (TV)': + {'offset': 8, + 'bit': 4}, + 'Multi-mode 62.5m (M6)': + {'offset': 8, + 'bit': 3}, + 'Multi-mode 50m (M5)': + {'offset': 8, + 'bit': 2}, + 'Multi-mode 50um (OM3)': + {'offset': 8, + 'bit': 1}, + 'Single Mode (SM)': + {'offset': 8, + 'bit': 0}}}, + 'Fibre Channel Speed': + {'offset': 9, + 'size' : 1, + 'type' : 'bitmap', + 'decode': { + '1200 Mbytes/Sec': + {'offset': 9, + 'bit': 7}, + '800 Mbytes/Sec': + {'offset': 9, + 'bit': 6}, + '1600 Mbytes/Sec': + {'offset': 9, + 'bit': 5}, + '400 Mbytes/Sec': + {'offset': 9, + 'bit': 4}, + '200 Mbytes/Sec': + {'offset': 9, + 'bit': 2}, + '100 Mbytes/Sec': + {'offset': 9, + 'bit': 0}}}} + + type_of_transceiver = { + '00':'Unknown or unspecified', + '01':'GBIC', + '02': 'Module/connector soldered to motherboard', + '03': 'SFP', + '04': '300 pin XBI', + '05': 'XENPAK', + '06': 'XFP', + '07': 'XFF', + '08': 'XFP-E', + '09': 'XPAK', + '0a': 'X2', + '0b': 'DWDM-SFP', + '0c': 'QSFP', + '0d': 'QSFP+' + } + + ext_type_of_transceiver = {} + + connector = { + '00': 'Unknown or unspecified', + '01': 'SC', + '02': 'FC Style 1 copper connector', + '03': 'FC Style 2 copper connector', + '04': 'BNC/TNC', + '05': 'FC coax headers', + '06': 'Fiberjack', + '07': 'LC', + '08': 'MT-RJ', + '09': 'MU', + '0a': 'SG', + '0b': 'Optical Pigtail', + '0C': 'MPO', + '20': 'HSSDC II', + '21': 'Copper pigtail', + '22': 'RJ45', + '23': 'No separable connector' + } + + encoding_codes = { + '00':'Unspecified', + '01': '8B10B', + '02': '4B5B', + '03': 'NRZ', + '04': 'SONET Scrambled', + '05': '64B66B', + '06': 'Manchester' + } + + rate_identifier = {'00':'QSFP+ Rate Select Version 1'} + + interface_id = {'Identifier': + {'offset':0, + 'size':1, + 'type' : 'enum', + 'decode' : type_of_transceiver}, + 'Extended Identifier': + {'offset':1, + 'size':1, + 'type' : 'enum', + 'decode': ext_type_of_transceiver}, + 'Connector': + {'offset':2, + 'size':1, + 'type' : 'enum', + 'decode': connector}, + 'Specification compliance': + {'offset' : 3, + 'type' : 'nested', + 'decode' : specification_compliance}, + 'Encoding': + {'offset':11, + 'size':1, + 'type' : 'enum', + 'decode' : encoding_codes}, + 'Nominal Bit Rate(100Mbs)': + {'offset': 12, + 'size':1, + 'type':'int'}, + 'Extended RateSelect Compliance': + {'offset':13, + 'size':1, + 'type' : 'enum', + 'decode' : rate_identifier}, + 'Length(km)': + {'offset':14, + 'size':1, + 'type':'int'}, + 'Length OM3(2m)': + {'offset':15, + 'size':1, + 'type':'int'}, + 'Length OM2(m)': + {'offset':16, + 'size':1, + 'type':'int'}, + 'Length OM1(m)': + {'offset':17, + 'size':1, + 'type':'int'}, + 'Length Cable Assembly(m)': + {'offset':18, + 'size':1, + 'type':'int'}, + # Device Tech + 'Vendor Name': + {'offset' : 20, + 'size': 16, + 'type': 'str'}, + 'Vendor OUI': + {'offset': 37, + 'size' : 3, + 'type' : 'hex'}, + 'Vendor PN': + {'offset': 40, + 'size' : 16, + 'type' : 'str'}, + 'Vendor Rev': + {'offset': 56, + 'size' : 2, + 'type' : 'str'}, + 'Vendor SN': + {'offset': 68, + 'size' : 16, + 'type' : 'str'}, + 'Vendor Date Code(YYYY-MM-DD Lot)': + {'offset': 84, + 'size' : 8, + 'type' : 'date'}, + 'Diagnostic Monitoring Type': + {'offset': 92, + 'size' : 1, + 'type' : 'bitmap', + 'decode': {}}, + 'Enhanced Options': + {'offset': 93, + 'size' : 1, + 'type' : 'bitmap', + 'decode': {}}} + + + def __init__(self, eeprom_raw_data=None): + self.interface_data = None + start_pos = 128 + + if eeprom_raw_data != None: + self.interface_data = sffbase.parse(self, + self.interface_id, + eeprom_raw_data, + start_pos) + + def parse(self, eeprom_raw_data, start_pos): + return sffbase.parse(self, self.interface_id, eeprom_raw_data, start_pos) + + def dump_pretty(self): + if self.interface_data == None: + print 'Object not initialized, nothing to print' + return + sffbase.dump_pretty(self, self.interface_data) + + def get_calibration_type(self): + return self.calibration_type + + def get_data(self): + return self.interface_data + + def get_data_pretty(self): + return sffbase.get_data_pretty(self, self.interface_data) + + +class sff8436Dom(sffbase): + + version = '1.0' + + def get_calibration_type(self): + return self._calibration_type + + def calc_temperature(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + + result = (msb << 8) | (lsb & 0xff) + result = self.twos_comp(result, 16) + + if cal_type == 1: + + # Internal calibration + + result = float(result / 256.0) + retval = '%.4f' %result + 'C' + elif cal_type == 2: + + # External calibration + + # T(C) = T_Slope * T_AD + T_Offset + off = self.dom_ext_calibration_constants['T_Slope']['offset'] + msb_t = int(eeprom_data[off], 16) + lsb_t = int(eeprom_data[off + 1], 16) + t_slope = (msb_t << 8) | (lsb_t & 0xff) + + off = self.dom_ext_calibration_constants['T_Offset']['offset'] + msb_t = int(eeprom_data[off], 16) + lsb_t = int(eeprom_data[off + 1], 16) + t_offset = (msb_t << 8) | (lsb_t & 0xff) + t_offset = self.twos_comp(t_offset, 16) + + result = t_slope * result + t_offset + result = float(result / 256.0) + retval = '%.4f' %result + 'C' + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_voltage(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + + # Internal Calibration + + result = float(result * 0.0001) + #print indent, name, ' : %.4f' %result, 'Volts' + retval = '%.4f' %result + 'Volts' + elif cal_type == 2: + + # External Calibration + + # V(uV) = V_Slope * VAD + V_Offset + off = self.dom_ext_calibration_constants['V_Slope']['offset'] + msb_v = int(eeprom_data[off], 16) + lsb_v = int(eeprom_data[off + 1], 16) + v_slope = (msb_v << 8) | (lsb_v & 0xff) + + off = self.dom_ext_calibration_constants['V_Offset']['offset'] + msb_v = int(eeprom_data[off], 16) + lsb_v = int(eeprom_data[off + 1], 16) + v_offset = (msb_v << 8) | (lsb_v & 0xff) + v_offset = self.twos_comp(v_offset, 16) + + result = v_slope * result + v_offset + result = float(result * 0.0001) + #print indent, name, ' : %.4f' %result, 'Volts' + retval = '%.4f' %result + 'Volts' + else: + #print indent, name, ' : Unknown' + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_bias(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + # Internal Calibration + + result = float(result * 0.002) + #print indent, name, ' : %.4f' %result, 'mA' + retval = '%.4f' %result + 'mA' + + elif cal_type == 2: + # External Calibration + + # I(uA) = I_Slope * I_AD + I_Offset + off = self.dom_ext_calibration_constants['I_Slope']['offset'] + msb_i = int(eeprom_data[off], 16) + lsb_i = int(eeprom_data[off + 1], 16) + i_slope = (msb_i << 8) | (lsb_i & 0xff) + + off = self.dom_ext_calibration_constants['I_Offset']['offset'] + msb_i = int(eeprom_data[off], 16) + lsb_i = int(eeprom_data[off + 1], 16) + i_offset = (msb_i << 8) | (lsb_i & 0xff) + i_offset = self.twos_comp(i_offset, 16) + + result = i_slope * result + i_offset + result = float(result * 0.002) + #print indent, name, ' : %.4f' %result, 'mA' + retval = '%.4f' %result + 'mA' + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_tx_power(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + + result = float(result * 0.0001) + #print indent, name, ' : ', power_in_dbm_str(result) + retval = self.power_in_dbm_str(result) + + elif cal_type == 2: + + # TX_PWR(uW) = TX_PWR_Slope * TX_PWR_AD + TX_PWR_Offset + off = self.dom_ext_calibration_constants['TX_PWR_Slope']['offset'] + msb_tx_pwr = int(eeprom_data[off], 16) + lsb_tx_pwr = int(eeprom_data[off + 1], 16) + tx_pwr_slope = (msb_tx_pwr << 8) | (lsb_tx_pwr & 0xff) + + off = self.dom_ext_calibration_constants['TX_PWR_Offset']['offset'] + msb_tx_pwr = int(eeprom_data[off], 16) + lsb_tx_pwr = int(eeprom_data[off + 1], 16) + tx_pwr_offset = (msb_tx_pwr << 8) | (lsb_tx_pwr & 0xff) + tx_pwr_offset = self.twos_comp(tx_pwr_offset, 16) + + result = tx_pwr_slope * result + tx_pwr_offset + result = float(result * 0.0001) + retval = self.power_in_dbm_str(result) + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_rx_power(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + + # Internal Calibration + result = float(result * 0.0001) + #print indent, name, " : ", power_in_dbm_str(result) + retval = self.power_in_dbm_str(result) + + elif cal_type == 2: + + # External Calibration + + # RX_PWR(uW) = RX_PWR_4 * RX_PWR_AD + + # RX_PWR_3 * RX_PWR_AD + + # RX_PWR_2 * RX_PWR_AD + + # RX_PWR_1 * RX_PWR_AD + + # RX_PWR(0) + off = self.dom_ext_calibration_constants['RX_PWR_4']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_4 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_3']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_3 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_2']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_2 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_1']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_1 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_0']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_0 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + rx_pwr = (rx_pwr_4 * result) + (rx_pwr_3 * result) + (rx_pwr_2 * result) + (rx_pwr_1 * result) + rx_pwr_0 + + result = float(result * 0.0001) + #print indent, name, " : ", power_in_dbm_str(result) + retval = self.power_in_dbm_str(result) + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + dom_status_indicator = {'DataNotReady': + {'offset': 2, + 'bit': 0, + 'type': 'bitvalue'}} + + dom_channel_status = {'Tx4LOS': + {'offset': 3, + 'bit': 7, + 'type': 'bitvalue'}, + 'Tx3LOS': + {'offset': 3, + 'bit': 6, + 'type': 'bitvalue'}, + 'Tx2LOS': + {'offset': 3, + 'bit': 5, + 'type': 'bitvalue'}, + 'Tx1LOS': + {'offset': 3, + 'bit': 4, + 'type': 'bitvalue'}, + 'Rx4LOS': + {'offset': 3, + 'bit': 3, + 'type': 'bitvalue'}, + 'Rx3LOS': + {'offset': 3, + 'bit': 2, + 'type': 'bitvalue'}, + 'Rx2LOS': + {'offset': 3, + 'bit': 1, + 'type': 'bitvalue'}, + 'Rx1LOS': + {'offset': 3, + 'bit': 0, + 'type': 'bitvalue'}, + 'Tx4Fault': + {'offset': 4, + 'bit': 3, + 'type': 'bitvalue'}, + 'Tx3Fault': + {'offset': 4, + 'bit': 2, + 'type': 'bitvalue'}, + 'Tx2Fault': + {'offset': 4, + 'bit': 1, + 'type': 'bitvalue'}, + 'Tx1Fault': + {'offset': 4, + 'bit': 0, + 'type': 'bitvalue'}} + + dom_module_monitor = {'TempHighAlarm': + {'offset': 6, + 'bit': 7, + 'type': 'bitvalue'}, + 'TempLowAlarm': + {'offset': 6, + 'bit': 6, + 'type': 'bitvalue'}, + 'TempHighWarning': + {'offset': 6, + 'bit': 5, + 'type': 'bitvalue'}, + 'TempLowWarning': + {'offset': 6, + 'bit': 4, + 'type': 'bitvalue'}, + 'VccHighAlarm': + {'offset': 7, + 'bit': 7, + 'type': 'bitvalue'}, + 'VccLowAlarm': + {'offset': 7, + 'bit': 6, + 'type': 'bitvalue'}, + 'VccHighWarning': + {'offset': 7, + 'bit': 5, + 'type': 'bitvalue'}, + 'VccLowWarning': + {'offset': 7, + 'bit': 4, + 'type': 'bitvalue'}} + + dom_channel_monitor = {'Rx1PowerHighAlarm': + {'offset': 9, + 'bit': 7, + 'type': 'bitvalue'}, + 'Rx1PowerLowAlarm': + {'offset': 9, + 'bit': 6, + 'type': 'bitvalue'}, + 'Rx1PowerHighWarning': + {'offset': 9, + 'bit': 5, + 'type': 'bitvalue'}, + 'Rx1PowerLowWarning': + {'offset': 9, + 'bit': 4, + 'type': 'bitvalue'}, + 'Rx2PowerHighAlarm': + {'offset': 9, + 'bit': 3, + 'type': 'bitvalue'}, + 'Rx2PowerLowAlarm': + {'offset': 9, + 'bit': 2, + 'type': 'bitvalue'}, + 'Rx2PowerHighWarning': + {'offset': 9, + 'bit': 1, + 'type': 'bitvalue'}, + 'Rx2PowerLowWarning': + {'offset': 9, + 'bit': 0, + 'type': 'bitvalue'}, + 'Rx3PowerHighAlarm': + {'offset': 10, + 'bit': 7, + 'type': 'bitvalue'}, + 'Rx3PowerLowAlarm': + {'offset': 10, + 'bit': 6, + 'type': 'bitvalue'}, + 'Rx3PowerHighWarning': + {'offset': 10, + 'bit': 5, + 'type': 'bitvalue'}, + 'Rx3PowerLowWarning': + {'offset': 10, + 'bit': 4, + 'type': 'bitvalue'}, + 'Rx4PowerHighAlarm': + {'offset': 10, + 'bit': 3, + 'type': 'bitvalue'}, + 'Rx4PowerLowAlarm': + {'offset': 10, + 'bit': 2, + 'type': 'bitvalue'}, + 'Rx4PowerHighWarning': + {'offset': 10, + 'bit': 1, + 'type': 'bitvalue'}, + 'Rx4PowerLowWarning': + {'offset': 10, + 'bit': 0, + 'type': 'bitvalue'}, + 'Tx1BiasHighAlarm': + {'offset': 11, + 'bit': 7, + 'type': 'bitvalue'}, + 'Tx1BiasLowAlarm': + {'offset': 11, + 'bit': 6, + 'type': 'bitvalue'}, + 'Tx1BiasHighWarning': + {'offset': 11, + 'bit': 5, + 'type': 'bitvalue'}, + 'Tx1BiasLowWarning': + {'offset': 11, + 'bit': 4, + 'type': 'bitvalue'}, + 'Tx2BiasHighAlarm': + {'offset': 11, + 'bit': 3, + 'type': 'bitvalue'}, + 'Tx2BiasLowAlarm': + {'offset': 11, + 'bit': 2, + 'type': 'bitvalue'}, + 'Tx2BiasHighWarning': + {'offset': 11, + 'bit': 1, + 'type': 'bitvalue'}, + 'Tx2BiasLowWarning': + {'offset': 11, + 'bit': 0, + 'type': 'bitvalue'}, + 'Tx3BiasHighAlarm': + {'offset': 12, + 'bit': 7, + 'type': 'bitvalue'}, + 'Tx3BiasLowAlarm': + {'offset': 12, + 'bit': 6, + 'type': 'bitvalue'}, + 'Tx3BiasHighWarning': + {'offset': 12, + 'bit': 5, + 'type': 'bitvalue'}, + 'Tx3BiasLowWarning': + {'offset': 12, + 'bit': 4, + 'type': 'bitvalue'}, + 'Tx4BiasHighAlarm': + {'offset': 12, + 'bit': 3, + 'type': 'bitvalue'}, + 'Tx4BiasLowAlarm': + {'offset': 12, + 'bit': 2, + 'type': 'bitvalue'}, + 'Tx4BiasHighWarning': + {'offset': 12, + 'bit': 1, + 'type': 'bitvalue'}, + 'Tx4BiasLowWarning': + {'offset': 12, + 'bit': 0, + 'type': 'bitvalue'}} + + + dom_module_monitor_values = {'Temperature': + {'offset':22, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_temperature}}, + 'Vcc': + {'offset':26, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_voltage}}} + + dom_channel_monitor_values = { + 'RX1Power': + {'offset':34, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}, + 'RX2Power': + {'offset':36, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}, + 'RX3Power': + {'offset':38, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}, + 'RX4Power': + {'offset':40, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}, + 'TX1Bias': + {'offset':42, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'TX2Bias': + {'offset':44, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'TX3Bias': + {'offset':46, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'TX4Bias': + {'offset':48, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}} + + dom_map = { + 'ModuleMonitorValues': + {'offset': 7, + 'size': 2, + 'type': 'nested', + 'decode': dom_module_monitor_values}, + 'ChannelMonitorValues': + {'offset': 10, + 'size': 2, + 'type': 'nested', + 'decode': dom_channel_monitor_values}} + + + def __init__(self, eeprom_raw_data=None, calibration_type=1): + self._calibration_type = calibration_type + start_pos = 0 + + if eeprom_raw_data != None: + self.dom_data = sffbase.parse(self, self.dom_map, + eeprom_raw_data, start_pos) + + def parse(self, eeprom_raw_data, start_pos): + return sffbase.parse(self, self.dom_map, eeprom_raw_data, + start_pos) + + def dump_pretty(self): + if self.dom_data == None: + print 'Object not initialized, nothing to print' + return + sffbase.dump_pretty(self, self.dom_data) + + def get_data(self): + return self.dom_data + + def get_data_pretty(self): + return sffbase.get_data_pretty(self, self.dom_data) diff --git a/sonic_sfp/sff8472.py b/sonic_sfp/sff8472.py new file mode 100644 index 00000000000..8415cbeddc7 --- /dev/null +++ b/sonic_sfp/sff8472.py @@ -0,0 +1,1050 @@ +#! /usr/bin/env python +#-------------------------------------------------------------------------- +# +# Copyright 2012 Cumulus Networks, inc all rights reserved +# +#-------------------------------------------------------------------------- +try: + import fcntl + import struct + import sys + import time + import binascii + import os + import getopt + import types + from math import log10 + from sffbase import sffbase +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +#------------------------------------------------------------------------------ + +class sff8472InterfaceId(sffbase): + """Parser and interpreter for Two wire Interface ID Data fields + - Address A0h + + Base types: + XXX - Complete documentation + + outtype - can be used to dictate the type of output you get + Mainly used with bitmap type. + if outtype == 'allbits': + parse gives all bitnames with values + if outtype == 'onbits': + parse gives all onbits with value = None + """ + + version = '1.0' + + transceiver_codes = { '10GEthernetComplianceCode': + {'offset':3, + 'size':1, + 'type' : 'bitmap', + 'decode' : {'10G Base-ER': + {'offset': 3, + 'bit': 7}, + '10G Base-LRM': + {'offset': 3, + 'bit': 6}, + '10G Base-LR': + {'offset': 3, + 'bit': 5}, + '10G Base-SR': + {'offset': 3, + 'bit': 4}}}, + 'InfinibandComplianceCode': + {'offset':3, + 'size':1, + 'type' : 'bitmap', + 'decode' : {'1X SX': + {'offset': 3, + 'bit': 3}, + '1X LX': + {'offset': 3, + 'bit': 2}, + '1X Copper Active': + {'offset': 3, + 'bit': 1}, + '1X Copper Passive': + {'offset': 3, + 'bit': 0}}}, + 'ESCONComplianceCodes': + {'offset':4, + 'size':1, + 'type' : 'bitmap', + 'decode' : {'ESCON MMF, 1310nm LED': + {'offset': 4, + 'bit': 7}, + 'ESCON SMF, 1310nm Laser': + {'offset': 4, + 'bit': 6}}}, + 'SONETComplianceCodes': + {'offset': 4, + 'size':2, + 'type' : 'bitmap', + 'decode' : { 'OC-192, short reach': + {'offset': 4, + 'bit': 5}, + 'SONET reach specifier bit 1': + {'offset': 4, + 'bit': 4}, + 'SONET reach specifier bit 2': + {'offset': 4, + 'bit': 3}, + 'OC-48, long reach': + {'offset': 4, + 'bit': 2}, + 'OC-48, intermediate reach': + {'offset': 4, + 'bit': 1}, + 'OC-48, short reach': + {'offset': 4, + 'bit': 0}, + 'OC-12, single mode, long reach': + {'offset': 5, + 'bit': 6}, + 'OC-12, single mode, inter reach': + {'offset': 5, + 'bit': 5}, + 'OC-12, short reach': + {'offset': 5, + 'bit': 4}, + 'OC-3, single mode, long reach': + {'offset': 5, + 'bit': 2}, + 'OC-3, single mode, inter reach': + {'offset': 5, + 'bit': 1}, + 'OC-3, short reach': + {'offset': 5, + 'bit': 0}}}, + 'EthernetComplianceCodes': + {'offset': 6, + 'size':2, + 'type' : 'bitmap', + 'decode' : { + 'BASE-PX': + {'offset': 6, + 'bit': 7}, + 'BASE-BX10': + {'offset': 6, + 'bit': 6}, + '100BASE-FX': + {'offset': 6, + 'bit': 5}, + '100BASE-LX/LX10': + {'offset': 6, + 'bit': 4}, + '1000BASE-T': + {'offset': 6, + 'bit': 3}, + '1000BASE-CX': + {'offset': 6, + 'bit': 2}, + '1000BASE-LX': + {'offset': 6, + 'bit': 1}, + '1000BASE-SX': + {'offset': 6, + 'bit': 0}}}, + 'FibreChannelLinkLength': + {'offset': 7, + 'size':2, + 'type' : 'bitmap', + 'decode' : + {'very long distance (V)': + {'offset': 7, + 'bit': 7}, + 'short distance (S)': + {'offset': 7, + 'bit': 6}, + 'Intermediate distance (I)': + {'offset': 7, + 'bit': 5}, + 'Long distance (L)': + {'offset': 7, + 'bit': 4}, + 'medium distance (M)': + {'offset': 7, + 'bit': 3}}}, + 'FibreChannelTechnology': + {'offset': 7, + 'size':2, + 'type' : 'bitmap', + 'decode' : + {'Shortwave laser, linear Rx (SA)': + {'offset': 7, + 'bit': 2}, + 'Longwave Laser (LC)': + {'offset': 7, + 'bit': 1}, + 'Electrical inter-enclosure (EL)': + {'offset': 7, + 'bit': 0}, + 'Electrical intra-enclosure (EL)': + {'offset': 8, + 'bit': 7}, + 'Shortwave laser w/o OFC (SN)': + {'offset': 8, + 'bit': 6}, + 'Shortwave laser with OFC (SL)': + {'offset': 8, + 'bit': 5}, + 'Longwave laser (LL)': + {'offset': 8, + 'bit': 4}}}, + 'SFP+CableTechnology': + {'offset': 7, + 'size':2, + 'type' : 'bitmap', + 'decode' : + {'Active Cable': + {'offset': 8, + 'bit': 3}, + 'Passive Cable': + {'offset': 8, + 'bit': 2}}}, + 'FibreChannelTransmissionMedia': + {'offset': 7, + 'size':2, + 'type' : 'bitmap', + 'decode' : + {'Twin Axial Pair (TW)': + {'offset': 9, + 'bit': 7}, + 'Twisted Pair (TP)': + {'offset': 9, + 'bit': 6}, + 'Miniature Coax (MI)': + {'offset': 9, + 'bit': 5}, + 'Video Coax (TV)': + {'offset': 9, + 'bit': 4}, + 'Multimode, 62.5um (M6)': + {'offset': 9, + 'bit': 3}, + 'Multimode, 50um (M5, M5E)': + {'offset': 9, + 'bit': 2}, + 'Single Mode (SM)': + {'offset': 9, + 'bit': 0}}}, + 'FibreChannelSpeed': + {'offset': 7, + 'size':2, + 'type': 'bitmap', + 'decode' : + {'1200 MBytes/sec': + {'offset': 10, + 'bit': 7}, + '800 MBytes/sec': + {'offset': 10, + 'bit': 6}, + '1600 MBytes/sec': + {'offset': 10, + 'bit': 5}, + '400 MBytes/sec': + {'offset': 10, + 'bit': 4}, + '200 MBytes/sec': + {'offset': 10, + 'bit': 2}, + '100 MBytes/sec': + {'offset': 10, + 'bit': 0}}}} + + type_of_transceiver = {'00':'Unknown', + '01':'GBIC', + '02': 'Module soldered to motherboard', + '03': 'SFP or SFP Plus', + '04': '300 pin XBI', + '05': 'XENPAK', + '06': 'XFP', + '07': 'XFF', + '08': 'XFP-E', + '09': 'XPAK', + '0a': 'X2', + '0b': 'DWDM-SFP', + '0d': 'QSFP'} + + exttypeoftransceiver = {'00': 'GBIC def not specified', + '01':'GBIC is compliant with MOD_DEF 1', + '02':'GBIC is compliant with MOD_DEF 2', + '03':'GBIC is compliant with MOD_DEF 3', + '04':'GBIC/SFP defined by twowire interface ID', + '05':'GBIC is compliant with MOD_DEF 5', + '06':'GBIC is compliant with MOD_DEF 6', + '07':'GBIC is compliant with MOD_DEF 7'} + + connector = {'00': 'Unknown', + '01': 'SC', + '02': 'Fibre Channel Style 1 copper connector', + '03': 'Fibre Channel Style 2 copper connector', + '04': 'BNC/TNC', + '05': 'Fibre Channel coaxial headers', + '06': 'FibreJack', + '07': 'LC', + '08': 'MT-RJ', + '09': 'MU', + '0a': 'SG', + '0b': 'Optical pigtail', + '0C': 'MPO Parallel Optic', + '20': 'HSSDCII', + '21': 'CopperPigtail', + '22': 'RJ45'} + + encoding_codes = {'00':'Unspecified', + '01':'8B/10B', + '02':'4B/5B', + '03':'NRZ', + '04':'Manchester', + '05': 'SONET Scrambled', + '06':'64B/66B'} + + rate_identifier = {'00':'Unspecified', + '01':'Defined for SFF-8079 (4/2/1G Rate_Select & AS0/AS1)', + '02': 'Defined for SFF-8431 (8/4/2G Rx Rate_Select only)', + '03':'Unspecified', + '04': 'Defined for SFF-8431 (8/4/2G Tx Rate_Select only)', + '05':'Unspecified', + '06':'Defined for SFF-8431 (8/4/2G Independent Rx & Tx Rate_select)', + '07':'Unspecified', + '08': 'Defined for FC-PI-5 (16/8/4G Rx Rate_select only) High=16G only, Low=8G/4G', + '09': 'Unspecified', + '0a': 'Defined for FC-PI-5 (16/8/4G Independent Rx, Tx Rate_select) High=16G only, Low=8G/4G'} + + + interface_id = {'TypeOfTransceiver': + {'offset':0, + 'size':1, + 'type' : 'enum', + 'decode' : type_of_transceiver}, + 'ExtIdentOfTypeOfTransceiver': + {'offset':1, + 'size':1, + 'type' : 'enum', + 'outlevel' : 2, + 'decode': exttypeoftransceiver}, + 'Connector': + {'offset':2, + 'size':1, + 'type' : 'enum', + 'decode': connector}, + 'EncodingCodes': + {'offset':11, + 'size':1, + 'type' : 'enum', + 'decode' : encoding_codes}, + 'VendorName': + {'offset' : 20, + 'size' : 16, + 'type' : 'str'}, + 'VendorOUI': + {'offset':20, + 'size':3, + 'type' : 'str'}, + 'VendorPN': + {'offset':40, + 'size':16, + 'type' : 'str'}, + 'VendorSN': + {'offset':68, + 'size':16, + 'type' : 'str'}, + 'VendorRev': + {'offset':56, + 'size':4, + 'type' : 'str'}, + 'CalibrationType': + {'offset':92, + 'size':1, + 'type' : 'bitmap', + 'short_name' : 'calType', + 'decode' : {'Internally Calibrated': + {'offset': 92, + 'bit':5}, + 'Externally Calibrated': + {'offset': 92, + 'bit':4}, + }}, + 'ReceivedPowerMeasurementType': + {'offset':92, + 'size':1, + 'type' : 'bitmap', + 'decode' : {'Avg power': + {'offset': 92, + 'bit':3}, + 'OMA': + {'offset': 92, + 'bit':3, + 'value':0}}}, + 'RateIdentifier': + {'offset':13, + 'size':1, + 'type' : 'enum', + 'decode' : rate_identifier}, + 'TransceiverCodes': + {'offset' : 3, + 'type' : 'nested', + 'decode' : transceiver_codes}, + 'NominalSignallingRate(UnitsOf100Mbd)': + {'offset': 12, + 'size':1, + 'type':'int'}, + 'LengthSMFkm-UnitsOfKm': + {'offset':14, + 'size':1, + 'type':'int'}, + 'LengthSMF(UnitsOf100m)': + {'offset':15, + 'size':1, + 'type':'int'}, + 'Length50um(UnitsOf10m)': + {'offset':16, + 'size':1, + 'type':'int'}, + 'Length62.5um(UnitsOfm)': + {'offset':17, + 'size':1, + 'type':'int'}, + 'LengthCable(UnitsOfm)': + {'offset':18, + 'size':1, + 'type':'int'}, + 'LengthOM3(UnitsOf10m)': + {'offset':19, + 'size':1, + 'type':'int'}, + 'VendorDataCode(YYYY-MM-DD Lot)': + {'offset':84, + 'size':8, + 'type': 'date'}} + + # Returns calibration type + def _get_calibration_type(self, eeprom_data): + try: + data = int(eeprom_data[92], 16) + if self.test_bit(data, 5) != 0: + return 1 # internally calibrated + elif self.test_bit(data, 4) != 0: + return 2 # externally calibrated + else: + return 0 # Could not find calibration type + except: + return 0 + + def __init__(self, eeprom_raw_data=None): + self.interface_data = None + start_pos = 0 + + if eeprom_raw_data != None: + self.interface_data = sffbase.parse(self, + self.interface_id, + eeprom_raw_data, start_pos) + self.calibration_type = self._get_calibration_type( + eeprom_raw_data) + + def parse(self, eeprom_raw_data, start_pos): + return sffbase.parse(self, self.interface_id, eeprom_raw_data, start_pos) + + def dump_pretty(self): + if self.interface_data == None: + print 'Object not initialized, nothing to print' + return + sffbase.dump_pretty(self, self.interface_data) + + def get_calibration_type(self): + return self.calibration_type + + def get_data(self): + return self.interface_data + + def get_data_pretty(self): + return sffbase.get_data_pretty(self, self.interface_data) + + +class sff8472Dom(sffbase): + """Parser and interpretor for Diagnostics data fields at address A2h""" + + version = '1.0' + + dom_ext_calibration_constants = {'RX_PWR_4': + {'offset':56, + 'size':4}, + 'RX_PWR_3': + {'offset':60, + 'size':4}, + 'RX_PWR_2': + {'offset':64, + 'size':4}, + 'RX_PWR_1': + {'offset':68, + 'size':4}, + 'RX_PWR_0': + {'offset':72, + 'size':4}, + 'TX_I_Slope': + {'offset':76, + 'size':2}, + 'TX_I_Offset': + {'offset':78, + 'size':2}, + 'TX_PWR_Slope': + {'offset':80, + 'size':2}, + 'TX_PWR_Offset': + {'offset':82, + 'size':2}, + 'T_Slope': + {'offset':84, + 'size':2}, + 'T_Offset': + {'offset':86, + 'size':2}, + 'V_Slope': + {'offset':88, + 'size':2}, + 'V_Offset': + {'offset':90, + 'size':2}} + + + def get_calibration_type(self): + return self._calibration_type + + def calc_temperature(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + + result = (msb << 8) | (lsb & 0xff) + result = self.twos_comp(result, 16) + + if cal_type == 1: + + # Internal calibration + + result = float(result / 256.0) + retval = '%.4f' %result + 'C' + elif cal_type == 2: + + # External calibration + + # T(C) = T_Slope * T_AD + T_Offset + off = self.dom_ext_calibration_constants['T_Slope']['offset'] + msb_t = int(eeprom_data[off], 16) + lsb_t = int(eeprom_data[off + 1], 16) + t_slope = (msb_t << 8) | (lsb_t & 0xff) + + off = self.dom_ext_calibration_constants['T_Offset']['offset'] + msb_t = int(eeprom_data[off], 16) + lsb_t = int(eeprom_data[off + 1], 16) + t_offset = (msb_t << 8) | (lsb_t & 0xff) + t_offset = self.twos_comp(t_offset, 16) + + result = t_slope * result + t_offset + result = float(result / 256.0) + retval = '%.4f' %result + 'C' + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_voltage(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + + # Internal Calibration + + result = float(result * 0.0001) + #print indent, name, ' : %.4f' %result, 'Volts' + retval = '%.4f' %result + 'Volts' + elif cal_type == 2: + + # External Calibration + + # V(uV) = V_Slope * VAD + V_Offset + off = self.dom_ext_calibration_constants['V_Slope']['offset'] + msb_v = int(eeprom_data[off], 16) + lsb_v = int(eeprom_data[off + 1], 16) + v_slope = (msb_v << 8) | (lsb_v & 0xff) + + off = self.dom_ext_calibration_constants['V_Offset']['offset'] + msb_v = int(eeprom_data[off], 16) + lsb_v = int(eeprom_data[off + 1], 16) + v_offset = (msb_v << 8) | (lsb_v & 0xff) + v_offset = self.twos_comp(v_offset, 16) + + result = v_slope * result + v_offset + result = float(result * 0.0001) + #print indent, name, ' : %.4f' %result, 'Volts' + retval = '%.4f' %result + 'Volts' + else: + #print indent, name, ' : Unknown' + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_bias(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + # Internal Calibration + + result = float(result * 0.002) + #print indent, name, ' : %.4f' %result, 'mA' + retval = '%.4f' %result + 'mA' + + elif cal_type == 2: + # External Calibration + + # I(uA) = I_Slope * I_AD + I_Offset + off = self.dom_ext_calibration_constants['I_Slope']['offset'] + msb_i = int(eeprom_data[off], 16) + lsb_i = int(eeprom_data[off + 1], 16) + i_slope = (msb_i << 8) | (lsb_i & 0xff) + + off = self.dom_ext_calibration_constants['I_Offset']['offset'] + msb_i = int(eeprom_data[off], 16) + lsb_i = int(eeprom_data[off + 1], 16) + i_offset = (msb_i << 8) | (lsb_i & 0xff) + i_offset = self.twos_comp(i_offset, 16) + + result = i_slope * result + i_offset + result = float(result * 0.002) + #print indent, name, ' : %.4f' %result, 'mA' + retval = '%.4f' %result + 'mA' + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_tx_power(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + + result = float(result * 0.0001) + #print indent, name, ' : ', power_in_dbm_str(result) + retval = self.power_in_dbm_str(result) + + elif cal_type == 2: + + # TX_PWR(uW) = TX_PWR_Slope * TX_PWR_AD + TX_PWR_Offset + off = self.dom_ext_calibration_constants['TX_PWR_Slope']['offset'] + msb_tx_pwr = int(eeprom_data[off], 16) + lsb_tx_pwr = int(eeprom_data[off + 1], 16) + tx_pwr_slope = (msb_tx_pwr << 8) | (lsb_tx_pwr & 0xff) + + off = self.dom_ext_calibration_constants['TX_PWR_Offset']['offset'] + msb_tx_pwr = int(eeprom_data[off], 16) + lsb_tx_pwr = int(eeprom_data[off + 1], 16) + tx_pwr_offset = (msb_tx_pwr << 8) | (lsb_tx_pwr & 0xff) + tx_pwr_offset = self.twos_comp(tx_pwr_offset, 16) + + result = tx_pwr_slope * result + tx_pwr_offset + result = float(result * 0.0001) + retval = self.power_in_dbm_str(result) + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + def calc_rx_power(self, eeprom_data, offset, size): + try: + cal_type = self.get_calibration_type() + + msb = int(eeprom_data[offset], 16) + lsb = int(eeprom_data[offset + 1], 16) + result = (msb << 8) | (lsb & 0xff) + + if cal_type == 1: + + # Internal Calibration + result = float(result * 0.0001) + #print indent, name, " : ", power_in_dbm_str(result) + retval = self.power_in_dbm_str(result) + + elif cal_type == 2: + + # External Calibration + + # RX_PWR(uW) = RX_PWR_4 * RX_PWR_AD + + # RX_PWR_3 * RX_PWR_AD + + # RX_PWR_2 * RX_PWR_AD + + # RX_PWR_1 * RX_PWR_AD + + # RX_PWR(0) + off = self.dom_ext_calibration_constants['RX_PWR_4']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_4 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_3']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_3 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_2']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_2 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_1']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_1 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + off = self.dom_ext_calibration_constants['RX_PWR_0']['offset'] + rx_pwr_byte3 = int(eeprom_data[off], 16) + rx_pwr_byte2 = int(eeprom_data[off + 1], 16) + rx_pwr_byte1 = int(eeprom_data[off + 2], 16) + rx_pwr_byte0 = int(eeprom_data[off + 3], 16) + rx_pwr_0 = (rx_pwr_byte3 << 24) | (rx_pwr_byte2 << 16) | (rx_pwr_byte1 << 8) | (rx_pwr_byte0 & 0xff) + + rx_pwr = (rx_pwr_4 * result) + (rx_pwr_3 * result) + (rx_pwr_2 * result) + (rx_pwr_1 * result) + rx_pwr_0 + + result = float(result * 0.0001) + #print indent, name, " : ", power_in_dbm_str(result) + retval = self.power_in_dbm_str(result) + else: + retval = 'Unknown' + except Exception, err: + retval = str(err) + + return retval + + + dom_aw_thresholds = { 'TempHighAlarm': + {'offset':0, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_temperature}}, + 'TempLowAlarm': + {'offset':2, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_temperature}}, + 'TempHighWarning': + {'offset':4, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_temperature}}, + 'TempLowWarning': + {'offset':6, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_temperature}}, + 'VoltageHighAlarm': + {'offset':8, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_voltage}}, + 'VoltageLowAlarm': + {'offset':10, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_voltage}}, + 'VoltageHighWarning': + {'offset':12, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_voltage}}, + 'VoltageLowWarning': + {'offset':14, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_voltage}}, + 'BiasHighAlarm': + {'offset':16, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'BiasLowAlarm': + {'offset':18, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'BiasHighWarning': + {'offset':20, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'BiasLowWarning': + {'offset':22, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'TXPowerHighAlarm': + {'offset':24, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_tx_power}}, + 'TXPowerLowAlarm': + {'offset':26, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_tx_power}}, + 'TXPowerHighWarning': + {'offset':28, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_tx_power}}, + 'TXPowerLowWarning': + {'offset':30, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_tx_power}}, + 'RXPowerHighAlarm': + {'offset':32, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}, + 'RXPowerLowAlarm': + {'offset':34, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}, + 'RXPowerHighWarning': + {'offset':36, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}, + 'RXPowerLowWarning': + {'offset':38, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}} + + dom_monitor = {'Temperature': + {'offset':96, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_temperature}}, + 'Vcc': + {'offset':98, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_voltage}}, + 'TXBias': + {'offset':100, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_bias}}, + 'TXPower': + {'offset':102, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_tx_power}}, + 'RXPower': + {'offset':104, + 'size':2, + 'type': 'func', + 'decode': { 'func':calc_rx_power}}} + + dom_status_control = { 'TXDisableState': + {'offset': 110, + 'bit': 7, + 'type': 'bitvalue'}, + 'SoftTXDisableSelect': + {'offset': 110, + 'bit': 6, + 'type': 'bitvalue'}, + 'RS1State': + {'offset': 110, + 'bit': 5, + 'type': 'bitvalue'}, + 'RateSelectState': + {'offset': 110, + 'bit': 4, + 'type': 'bitvalue'}, + 'SoftRateSelect': + {'offset': 110, + 'bit': 3, + 'type': 'bitvalue'}, + 'TXFaultState': + {'offset': 110, + 'bit': 2, + 'type': 'bitvalue'}, + 'RXLOSState': + {'offset': 110, + 'bit': 1, + 'type': 'bitvalue'}, + 'DataReadyBarState': + {'offset': 110, + 'bit': 0, + 'type': 'bitvalue'}} + + dom_alarm_flags = {'TempHighAlarm': + {'offset':112, + 'bit':7, + 'type': 'bitvalue'}, + 'TempLowAlarm': + {'offset':112, + 'bit':6, + 'type': 'bitvalue'}, + 'VccHighAlarm': + {'offset':112, + 'bit':5, + 'type': 'bitvalue'}, + 'VccLowAlarm': + {'offset':112, + 'bit':4, + 'type': 'bitvalue'}, + 'TXBiasHighAlarm': + {'offset':112, + 'bit':3, + 'type': 'bitvalue'}, + 'TXBiasLowAlarm': + {'offset':112, + 'bit':2, + 'type': 'bitvalue'}, + 'TXPowerHighAlarm': + {'offset':112, + 'bit':1, + 'type': 'bitvalue'}, + 'TXPowerLowAlarm': + {'offset':112, + 'bit':0, + 'type': 'bitvalue'}, + 'RXPowerHighAlarm': + {'offset':113, + 'bit':7, + 'type': 'bitvalue'}, + 'RXPowerLowAlarm': + {'offset':113, + 'bit':6, + 'type': 'bitvalue'}} + + dom_warning_flags = { 'TempHighWarning': + {'offset':116, + 'bit':7, + 'type': 'bitvalue'}, + 'TempLowWarning': + {'offset':116, + 'bit':6, + 'type': 'bitvalue'}, + 'VccHighWarning': + {'offset':116, + 'bit':5, + 'type': 'bitvalue'}, + 'VccLowWarning': + {'offset':116, + 'bit':4, + 'type': 'bitvalue'}, + 'TXBiasHighWarning': + {'offset':116, + 'bit':3, + 'type': 'bitvalue'}, + 'TXBiasLowWarning': + {'offset':116, + 'bit':2, + 'type': 'bitvalue'}, + 'TXPowerHighWarning': + {'offset':116, + 'bit':1, + 'type': 'bitvalue'}, + 'TXPowerLowWarning': + {'offset':116, + 'bit':0, + 'type': 'bitvalue'}, + 'RXPowerHighWarning': + {'offset':117, + 'bit':7, + 'type': 'bitvalue'}, + 'RXPowerLowWarning': + {'offset':117, + 'bit':6, + 'type': 'bitvalue'}} + + dom_map = {'AwThresholds': + {'offset' : 0, + 'size' : 40, + 'type' : 'nested', + 'decode' : dom_aw_thresholds}, + 'MonitorData': + {'offset':96, + 'size':10, + 'type' : 'nested', + 'decode': dom_monitor}, + 'StatusControl': + {'offset':110, + 'size':1, + 'type' : 'nested', + 'decode':dom_status_control}, + 'AlarmFlagStatus': + {'offset':112, + 'size':2, + 'type' : 'nested', + 'decode':dom_alarm_flags}, + 'WarningFlagStatus': + {'offset':112, + 'size':2, + 'type' : 'nested', + 'decode':dom_warning_flags}} + + + def __init__(self, eeprom_raw_data=None, calibration_type=0): + self._calibration_type = calibration_type + start_pos = 0 + + if eeprom_raw_data != None: + self.dom_data = sffbase.parse(self, self.dom_map, + eeprom_raw_data, start_pos) + + def parse(self, eeprom_raw_data, start_pos): + return sffbase.parse(self, self.dom_map, eeprom_raw_data, start_pos) + + + def dump_pretty(self): + if self.dom_data == None: + print 'Object not initialized, nothing to print' + return + sffbase.dump_pretty(self, self.dom_data) + + + def get_data(self): + return self.dom_data + + + def get_data_pretty(self): + return sffbase.get_data_pretty(self, self.dom_data) diff --git a/sonic_sfp/sffbase.py b/sonic_sfp/sffbase.py new file mode 100644 index 00000000000..55b5a734d33 --- /dev/null +++ b/sonic_sfp/sffbase.py @@ -0,0 +1,250 @@ +#! /usr/bin/env python +#---------------------------------------------------------------------------- +# sffbase class for sff8436 and sff8472 +#---------------------------------------------------------------------------- + +try: + import fcntl + import struct + import sys + import time + import binascii + import os + import getopt + import types + from math import log10 +except ImportError, e: + raise ImportError (str(e) + "- required module not found") + +class sffbase(object): + """Class to parse and interpret sff8436 and sff8472 spec for + diagnostically monitoring interfaces of optical transceivers""" + + _indent = '\t' + + def inc_indent(self): + self._indent += '\t' + + def dec_indent(self): + self._indent = self._indent[:-1] + + # Convert Hex to String + def convert_hex_to_string(self, arr, start, end): + try: + ret_str = '' + for n in range(start, end): + ret_str += arr[n] + return str.strip(binascii.unhexlify(ret_str)) + except Exception, err: + return str(err) + + # Convert Date to String + def convert_date_to_string(self, eeprom_data, offset, size): + try: + year_offset = 0 + month_offset = 2 + day_offset = 4 + lot_offset = 6 + + date = self.convert_hex_to_string(eeprom_data, offset, offset + size) + retval = "20"+ date[year_offset:month_offset] + "-" + \ + date[month_offset:day_offset] + "-" + \ + date[day_offset:lot_offset] + " " + \ + date[lot_offset:size] + except Exception, err: + retval = str(err) + return retval + + def test_bit(self, n, bitpos): + try: + mask = 1 << bitpos + if (n & mask) == 0: + return 0 + else: + return 1 + except: + return -1 + + def twos_comp(self, num, bits): + try: + if ((num & (1 << (bits - 1))) != 0): + num = num - (1 << bits) + return num + except: + return 0 + + def mw_to_dbm(self, mW): + if mW == 0: + return float("-inf") + elif mW < 0: + return float("NaN") + return 10. * log10(mW) + + + def power_in_dbm_str(self, mW): + return "%.4f%s" % (self.mw_to_dbm(mW), "dBm") + + # Parse sff base elements + def parse_sff_element(self, eeprom_data, eeprom_ele, start_pos): + value = None + offset = eeprom_ele.get('offset') + start_pos + size = eeprom_ele.get('size') + type = eeprom_ele.get('type') + decode = eeprom_ele.get('decode'); + + if type == 'enum': + # Get the matched value + value = decode.get(str(eeprom_data[offset]), 'Unknown') + + elif type == 'bitmap': + # Get the 'on' bitname + bitvalue_dict = {} + for bitname, bitinfo in sorted(decode.iteritems()): + bitinfo_offset = bitinfo.get('offset') + start_pos + bitinfo_pos = bitinfo.get('bit') + bitinfo_value = bitinfo.get('value') + data = int(eeprom_data[bitinfo_offset], 16) + bit_value = self.test_bit(data, bitinfo_pos) + if bitinfo_value != None: + if bit_value == bitinfo_value: + value = bitname + break + elif bit_value == 1: + value = bitname + break + + elif type == 'bitvalue': + # Get the value of the bit + bitpos = eeprom_ele.get('bit') + data = int(eeprom_data[offset], 16) + bitval = self.test_bit(data, bitpos) + value = ['Off', 'On'][bitval] + + elif type == 'func': + # Call the decode func to get the value + value = decode['func'](self, eeprom_data, + offset, size) + + elif type == 'str': + value = self.convert_hex_to_string(eeprom_data, offset, + offset + size) + + elif type == 'int': + data = int(eeprom_data[offset], 16) + if data != 0: + value = data + + elif type == 'date': + value = self.convert_date_to_string(eeprom_data, offset, + size) + + elif type == 'hex': + value = '-'.join(eeprom_data[offset:offset+size]) + + return value + + # Recursively parses sff data into dictionary + def parse_sff(self, eeprom_map, eeprom_data, start_pos): + outdict = {} + for name, meta_data in sorted(eeprom_map.iteritems()): + type = meta_data.get('type') + + # Initialize output value + value_dict = {} + value_dict['outtype'] = meta_data.get('outtype') + value_dict['short_name'] = meta_data.get('short_name') + + if type != 'nested': + data = self.parse_sff_element(eeprom_data, + meta_data, start_pos) + else: + nested_map = meta_data.get('decode') + data = self.parse_sff(nested_map, + eeprom_data, start_pos) + + if data != None: + value_dict['value'] = data + outdict[name] = value_dict + + return outdict + + + # Main sff parser function + def parse(self, eeprom_map, eeprom_data, start_pos): + """ Example Return format: + {'version': '1.0', 'data': {'Length50um(UnitsOf10m)': + {'outtype': None, 'value': 8, 'short_name': None}, + 'TransceiverCodes': {'outtype': None, 'value': + {'10GEthernetComplianceCode': {'outtype': None, 'value': + '10G Base-SR', 'short_name': None}}, 'short_name': None}, + 'ExtIdentOfTypeOfTransceiver': {'outtype': None, 'value': + 'GBIC/SFP func defined by two-wire interface ID', 'short_name': + None}, 'Length62.5um(UnitsOfm)': {'outtype': None,""" + + outdict = {} + return_dict = {} + + outdict = self.parse_sff(eeprom_map, eeprom_data, start_pos) + + return_dict['version'] = self.version + return_dict['data'] = outdict + + return return_dict + + + # Returns sff parsed data in a pretty dictionary format + def get_data_pretty_dict(self, indict): + outdict = {} + + for elem, elem_val in sorted(indict.iteritems()): + value = elem_val.get('value') + if type(value) == types.DictType: + outdict[elem] = sffbase.get_data_pretty_dict( + self, value) + else: + outdict[elem] = value + + return outdict + + def get_data_pretty(self, indata): + """Example Return format: + {'version': '1.0', 'data': {'Length50um(UnitsOf10m)': 8, + 'TransceiverCodes': {'10GEthernetComplianceCode': + '10G Base-SR'}, 'ExtIdentOfTypeOfTransceiver': 'GBIC/SFP func + defined by two-wire interface ID', 'Length62.5um(UnitsOfm)': 3, + 'VendorPN': 'FTLX8571D3BNL', 'RateIdentifier': 'Unspecified', + 'NominalSignallingRate(UnitsOf100Mbd)': 103, 'VendorOUI': ..}} + {'version': '1.0', 'data': {'AwThresholds': + {'TXPowerLowWarning': '-5.0004 dBm', 'TempHighWarning': + '88.0000C', 'RXPowerHighAlarm': '0.0000 dBm', + 'TXPowerHighAlarm': '-0.7998 dBm', 'RXPowerLowAlarm': + '-20.0000 dBm', 'RXPowerHighWarning': '-1.0002 dBm', + 'VoltageLowAlarm': '2.9000Volts'""" + + return_dict = {} + + return_dict['version'] = indata.get('version') + return_dict['data'] = self.get_data_pretty_dict(indata.get( + 'data')) + return return_dict + + # Dumps dict in pretty format + def dump_pretty(self, indict): + for elem, elem_val in sorted(indict.iteritems()): + if type(elem_val) == types.DictType: + print self._indent, elem, ': ' + self.inc_indent() + sff8472.dump_pretty(self, elem_val) + self.dec_indent() + elif type(elem_val) == types.ListType: + if len(elem_val) == 1: + print (self._indent, elem, ': ', + elem_val.pop()) + else: + print self._indent, elem, ': ' + self.inc_indent() + for e in elem_val: + print self._indent, e + self.dec_indent() + else: + print self._indent, elem, ': ', elem_val diff --git a/sonic_sfp/sfputilbase.py b/sonic_sfp/sfputilbase.py new file mode 100644 index 00000000000..d8e522b1404 --- /dev/null +++ b/sonic_sfp/sfputilbase.py @@ -0,0 +1,608 @@ +# sfputilbase.py +# +# Base class for creating platform-specific SFP transceiver interfaces for SONiC +# + +try: + import abc + import binascii + import os + import re + import bcmshell + from sonic_eeprom import eeprom_dts + from sff8472 import sff8472InterfaceId + from sff8472 import sff8472Dom + from sff8436 import sff8436InterfaceId + from sff8436 import sff8436Dom +except ImportError as e: + raise ImportError("%s - required module not found" % str(e)) + + +class SfpUtilError(Exception): + """Base class for exceptions in this module.""" + pass + + +class DeviceTreeError(SfpUtilError): + """Exception raised when unable to find SFP device attributes in the device tree.""" + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +class SfpUtilBase(object): + """ Abstract base class for SFP utility. This class + provides base EEPROM read attributes and methods common + to most platforms.""" + + __metaclass__ = abc.ABCMeta + + IDENTITY_EEPROM_ADDR = 0x50 + DOM_EEPROM_ADDR = 0x51 + SFP_DEVICE_TYPE = "24c02" + + # List to specify filter for sfp_ports + # Needed by platforms like dni-6448 which + # have only a subset of ports that support sfp + sfp_ports = [] + + # List of logical port names available on a system + """ ["swp1", "swp5", "swp6", "swp7", "swp8" ...] """ + logical = [] + + # dicts for easier conversions between logical, physical and bcm ports + logical_to_bcm = {} + logical_to_physical = {} + + """ + phytab_mappings stores mapping between logical, physical and bcm ports + from /var/lib/cumulus/phytab + For a normal non-ganged port: + "swp8": {"bcmport": "xe4", "physicalport": [8], "phyid": ["0xb"]} + + For a ganged 40G/4 port: + "swp1": {"bcmport": "xe0", "physicalport": [1, 2, 3, 4], "phyid": ["0x4", "0x5", "0x6", "0x7"]} + + For ganged 4x10G port: + "swp52s0": {"bcmport": "xe51", "physicalport": [52], "phyid": ["0x48"]}, + "swp52s1": {"bcmport": "xe52", "physicalport": [52], "phyid": ["0x49"]}, + "swp52s2": {"bcmport": "xe53", "physicalport": [52], "phyid": ["0x4a"]}, + "swp52s3": {"bcmport": "xe54", "physicalport": [52], "phyid": ["0x4b"]}, + """ + phytab_mappings = {} + + physical_to_logical = {} + physical_to_phyaddrs = {} + + port_to_i2cbus_mapping = None + + @abc.abstractproperty + def port_start(self): + """ Starting index of physical port range """ + pass + + @abc.abstractproperty + def port_end(self): + """ Ending index of physical port range """ + pass + + @abc.abstractproperty + def qsfp_ports(self): + """ Ending index of physical port range """ + pass + + @abc.abstractproperty + def port_to_eeprom_mapping(self): + """ Dictionary where key = physical port index (integer), + value = path to SFP EEPROM device file (string) """ + pass + + def __init__(self): + pass + + def _get_bcm_port(self, port_num): + bcm_port = None + + logical_port = self.physical_to_logical.get(port_num) + if logical_port is not None and len(logical_port) > 0: + bcm_port = self.logical_to_bcm.get(logical_port[0]) + + if bcm_port is None: + bcm_port = "xe%d" % (port_num - 1) + + return bcm_port + + def _get_port_i2c_adapter_id(self, port_num): + if len(self.port_to_i2cbus_mapping) == 0: + return -1 + + return self.port_to_i2cbus_mapping.get(port_num, -1) + + # Adds new sfp device on i2c adapter/bus via i2c bus new_device + # sysfs attribute + def _add_new_sfp_device(self, sysfs_sfp_i2c_adapter_path, devaddr): + try: + sysfs_nd_path = "%s/new_device" % sysfs_sfp_i2c_adapter_path + + # Write device address to new_device file + nd_file = open(sysfs_nd_path, "w") + nd_str = "%s %s" % (self.SFP_DEVICE_TYPE, hex(devaddr)) + nd_file.write(nd_str) + nd_file.close() + + except Exception, err: + print "Error writing to new device file: %s" % str(err) + return 1 + else: + return 0 + + # Deletes sfp device on i2c adapter/bus via i2c bus delete_device + # sysfs attribute + def _delete_sfp_device(self, sysfs_sfp_i2c_adapter_path, devaddr): + try: + sysfs_nd_path = "%s/delete_device" % sysfs_sfp_i2c_adapter_path + print devaddr > sysfs_nd_path + + # Write device address to delete_device file + nd_file = open(sysfs_nd_path, "w") + nd_file.write(devaddr) + nd_file.close() + except Exception, err: + print "Error writing to new device file: %s" % str(err) + return 1 + else: + return 0 + + # Returns 1 if SFP EEPROM found. Returns 0 otherwise + def _sfp_eeprom_present(self, sysfs_sfp_i2c_client_eeprompath, offset): + """Tries to read the eeprom file to determine if the + device/sfp is present or not. If sfp present, the read returns + valid bytes. If not, read returns error 'Connection timed out""" + + if not os.path.exists(sysfs_sfp_i2c_client_eeprompath): + return False + else: + try: + sysfsfile = open(sysfs_sfp_i2c_client_eeprompath, "rb") + sysfsfile.seek(offset) + sysfsfile.read(1) + except IOError: + return False + except: + return False + else: + return True + + # Read eeprom + def _read_eeprom_devid(self, port_num, devid, offset): + sysfs_i2c_adapter_base_path = "/sys/class/i2c-adapter" + eeprom_raw = [] + num_bytes = 256 + + for i in range(0, num_bytes): + eeprom_raw.append("0x00") + + if port_num in self.port_to_eeprom_mapping.keys(): + sysfs_sfp_i2c_client_eeprom_path = self.port_to_eeprom_mapping[port_num] + else: + sysfs_i2c_adapter_base_path = "/sys/class/i2c-adapter" + + i2c_adapter_id = self._get_port_i2c_adapter_id(port_num) + if i2c_adapter_id is None: + print "Error getting i2c bus num" + return None + + # Get i2c virtual bus path for the sfp + sysfs_sfp_i2c_adapter_path = "%s/i2c-%s" % (sysfs_i2c_adapter_base_path, + str(i2c_adapter_id)) + + # If i2c bus for port does not exist + if not os.path.exists(sysfs_sfp_i2c_adapter_path): + print "Could not find i2c bus %s. Driver not loaded?" % sysfs_sfp_i2c_adapter_path + return None + + sysfs_sfp_i2c_client_path = "%s/%s-00%s" % (sysfs_sfp_i2c_adapter_path, + str(i2c_adapter_id), + hex(devid)[-2:]) + + # If sfp device is not present on bus, Add it + if not os.path.exists(sysfs_sfp_i2c_client_path): + ret = self._add_new_sfp_device( + sysfs_sfp_i2c_adapter_path, devid) + if ret != 0: + print "Error adding sfp device" + return None + + sysfs_sfp_i2c_client_eeprom_path = "%s/eeprom" % sysfs_sfp_i2c_client_path + + if not self._sfp_eeprom_present(sysfs_sfp_i2c_client_eeprom_path, offset): + return None + + try: + sysfsfile_eeprom = open(sysfs_sfp_i2c_client_eeprom_path, "rb") + sysfsfile_eeprom.seek(offset) + raw = sysfsfile_eeprom.read(num_bytes) + except IOError: + print "Error: reading sysfs file %s" % sysfs_sfp_i2c_client_eeprom_path + return None + + try: + for n in range(0, num_bytes): + eeprom_raw[n] = hex(ord(raw[n]))[2:].zfill(2) + except: + return None + + try: + sysfsfile_eeprom.close() + except: + return 0 + + return eeprom_raw + + def _is_valid_port(self, port_num): + if port_num >= self.port_start and port_num <= self.port_end: + return True + + return False + + def read_porttab_mappings(self, porttabfile): + logical = [] + logical_to_bcm = {} + logical_to_physical = {} + physical_to_logical = {} + last_fp_port_index = 0 + last_portname = "" + first = 1 + port_pos_in_file = 0 + parse_fmt_port_config_ini = False + + try: + f = open(porttabfile) + except: + raise + + parse_fmt_port_config_ini = (os.path.basename(porttabfile) == "port_config.ini") + + # Read the porttab file and generate dicts + # with mapping for future reference. + # XXX: move the porttab + # parsing stuff to a separate module, or reuse + # if something already exists + for line in f: + line.strip() + if re.search("^#", line) is not None: + continue + + # Parsing logic for 'port_config.ini' file + if (parse_fmt_port_config_ini): + # bcm_port is not explicitly listed in port_config.ini format + # Currently we assume ports are listed in numerical order according to bcm_port + # so we use the port's position in the file (zero-based) as bcm_port + portname = line.split()[0] + + bcm_port = str(port_pos_in_file) + + if len(line.split()) == 4: + fp_port_index = int(line.split()[3]) + else: + fp_port_index = portname.split("Ethernet").pop() + fp_port_index = int(fp_port_index.split("s").pop(0))/4 + else: # Parsing logic for older 'portmap.ini' file + (portname, bcm_port) = line.split("=")[1].split(",")[:2] + + fp_port_index = portname.split("Ethernet").pop() + fp_port_index = int(fp_port_index.split("s").pop(0))/4 + + if ((len(self.sfp_ports) > 0) and (fp_port_index not in self.sfp_ports)): + continue + + if first == 1: + # Initialize last_[physical|logical]_port + # to the first valid port + last_fp_port_index = fp_port_index + last_portname = portname + first = 0 + + logical.append(portname) + + logical_to_bcm[portname] = "xe" + bcm_port + logical_to_physical[portname] = [fp_port_index] + if physical_to_logical.get(fp_port_index) is None: + physical_to_logical[fp_port_index] = [portname] + else: + physical_to_logical[fp_port_index].append( + portname) + + if (fp_port_index - last_fp_port_index) > 1: + # last port was a gang port + for p in range(last_fp_port_index+1, fp_port_index): + logical_to_physical[last_portname].append(p) + if physical_to_logical.get(p) is None: + physical_to_logical[p] = [last_portname] + else: + physical_to_logical[p].append(last_portname) + + last_fp_port_index = fp_port_index + last_portname = portname + + port_pos_in_file += 1 + + self.logical = logical + self.logical_to_bcm = logical_to_bcm + self.logical_to_physical = logical_to_physical + self.physical_to_logical = physical_to_logical + + """ + print "logical: " + self.logical + print "logical to bcm: " + self.logical_to_bcm + print "logical to physical: " + self.logical_to_physical + print "physical to logical: " + self.physical_to_logical + """ + + def read_phytab_mappings(self, phytabfile): + logical = [] + phytab_mappings = {} + physical_to_logical = {} + physical_to_phyaddrs = {} + + try: + f = open(phytabfile) + except: + raise + + # Read the phytab file and generate dicts + # with mapping for future reference. + # XXX: move the phytabfile + # parsing stuff to a separate module, or reuse + # if something already exists + for line in f: + line = line.strip() + line = re.sub(r"\s+", " ", line) + if len(line) < 4: + continue + if re.search("^#", line) is not None: + continue + (phy_addr, logical_port, bcm_port, type) = line.split(" ", 3) + + if re.match("xe", bcm_port) is None: + continue + + lport = re.findall("swp(\d+)s*(\d*)", logical_port) + if lport is not None: + lport_tuple = lport.pop() + physical_port = int(lport_tuple[0]) + else: + physical_port = logical_port.split("swp").pop() + physical_port = int(physical_port.split("s").pop(0)) + + # Some platforms have a list of physical sfp ports + # defined. If such a list exists, check to see if this + # port is blacklisted + if ((len(self.sfp_ports) > 0) and (physical_port not in self.sfp_ports)): + continue + + if logical_port not in logical: + logical.append(logical_port) + + if phytab_mappings.get(logical_port) is None: + phytab_mappings[logical_port] = {} + phytab_mappings[logical_port]['physicalport'] = [] + phytab_mappings[logical_port]['phyid'] = [] + phytab_mappings[logical_port]['type'] = type + + # If the port is 40G/4 ganged, there will be multiple + # physical ports corresponding to the logical port. + # Generate the next physical port number in the series + # and append it to the list + tmp_physical_port_list = phytab_mappings[logical_port]['physicalport'] + if (type == "40G/4" and physical_port in tmp_physical_port_list): + # Aha!...ganged port + new_physical_port = tmp_physical_port_list[-1] + 1 + else: + new_physical_port = physical_port + + if (new_physical_port not in phytab_mappings[logical_port]['physicalport']): + phytab_mappings[logical_port]['physicalport'].append(new_physical_port) + phytab_mappings[logical_port]['phyid'].append(phy_addr) + phytab_mappings[logical_port]['bcmport'] = bcm_port + + # Store in physical_to_logical dict + if physical_to_logical.get(new_physical_port) is None: + physical_to_logical[new_physical_port] = [] + physical_to_logical[new_physical_port].append(logical_port) + + # Store in physical_to_phyaddrs dict + if physical_to_phyaddrs.get(new_physical_port) is None: + physical_to_phyaddrs[new_physical_port] = [] + physical_to_phyaddrs[new_physical_port].append(phy_addr) + + self.logical = logical + self.phytab_mappings = phytab_mappings + self.physical_to_logical = physical_to_logical + self.physical_to_phyaddrs = physical_to_phyaddrs + + """ + pp = pprint.PrettyPrinter(indent=4) + pp.pprint(self.phytab_mappings) + + print "logical: " + self.logical + print "logical to bcm: " + self.logical_to_bcm + print "phytab mappings: " + self.phytab_mappings + print "physical to logical: " + self.physical_to_logical + print "physical to phyaddrs: " + self.physical_to_phyaddrs + """ + + def get_physical_to_logical(self, port_num): + """Returns list of logical ports for the given physical port""" + + return self.physical_to_logical[port_num] + + def get_logical_to_physical(self, logical_port): + """Returns list of physical ports for the given logical port""" + + return self.logical_to_physical[logical_port] + + def is_logical_port(self, port): + if port in self.logical: + return 1 + else: + return 0 + + def is_logical_port_ganged_40_by_4(self, logical_port): + physical_port_list = self.logical_to_physical[logical_port] + if len(physical_port_list) > 1: + return 1 + else: + return 0 + + def is_physical_port_ganged_40_by_4(self, port_num): + logical_port = self.get_physical_to_logical(port_num) + if logical_port is not None: + return self.is_logical_port_ganged_40_by_4(logical_port[0]) + + return 0 + + def get_physical_port_phyid(self, physical_port): + """Returns list of phyids for a physical port""" + + return self.physical_to_phyaddrs[physical_port] + + def get_40_by_4_gangport_phyid(self, logical_port): + """ Return the first ports phyid. One use case for + this is to address the gang port in single mode """ + + phyid_list = self.phytab_mappings[logical_port]['phyid'] + if phyid_list is not None: + return phyid_list[0] + + def is_valid_sfputil_port(self, port): + if port.startswith(""): + if self.is_logical_port(port): + return 1 + else: + return 0 + else: + return 0 + + def read_port_mappings(self): + if self.port_to_eeprom_mapping is None or self.port_to_i2cbus_mapping is None: + self.read_port_to_eeprom_mapping() + self.read_port_to_i2cbus_mapping() + + def read_port_to_eeprom_mapping(self): + eeprom_dev = "/sys/class/eeprom_dev" + self.port_to_eeprom_mapping = {} + for eeprom_path in [os.path.join(eeprom_dev, x) for x in os.listdir(eeprom_dev)]: + eeprom_label = open(os.path.join(eeprom_path, "label"), "r").read().strip() + if eeprom_label.startswith("port"): + port = int(eeprom_label[4:]) + self.port_to_eeprom_mapping[port] = os.path.join(eeprom_path, "device", "eeprom") + + def read_port_to_i2cbus_mapping(self): + if self.port_to_i2cbus_mapping is not None and len(self.port_to_i2cbus_mapping) > 0: + return + + self.eep_dict = eeprom_dts.get_dev_attr_from_dtb(['sfp']) + if len(self.eep_dict) == 0: + return + + # XXX: there should be a cleaner way to do this. + i2cbus_list = [] + self.port_to_i2cbus_mapping = {} + s = self.port_start + for sfp_sysfs_path, attrs in sorted(self.eep_dict.iteritems()): + i2cbus = attrs.get("dev-id") + if i2cbus is None: + raise DeviceTreeError("No 'dev-id' attribute found in attr: %s" % repr(attrs)) + if i2cbus in i2cbus_list: + continue + i2cbus_list.append(i2cbus) + self.port_to_i2cbus_mapping[s] = i2cbus + s += 1 + if s > self.port_end: + break + + def get_eeprom_raw(self, port_num): + # Read interface id EEPROM at addr 0x50 + return self._read_eeprom_devid(port_num, self.IDENTITY_EEPROM_ADDR, 0) + + def get_eeprom_dom_raw(self, port_num): + if port_num in self.qsfp_ports: + # QSFP DOM EEPROM is also at addr 0x50 and thus also stored in eeprom_ifraw + return None + else: + # Read dom eeprom at addr 0x51 + return self._read_eeprom_devid(port_num, self.DOM_EEPROM_ADDR, 0) + + def get_eeprom_dict(self, port_num): + """Returns dictionary of interface and dom data. + format: { : {'interface': {'version' : '1.0', 'data' : {...}}, + 'dom' : {'version' : '1.0', 'data' : {...}}}} + """ + + sfp_data = {} + + eeprom_ifraw = self.get_eeprom_raw(port_num) + eeprom_domraw = self.get_eeprom_dom_raw(port_num) + + if eeprom_ifraw is None: + return None + + if port_num in self.qsfp_ports: + sfpi_obj = sff8436InterfaceId(eeprom_ifraw) + if sfpi_obj is not None: + sfp_data['interface'] = sfpi_obj.get_data_pretty() + # For Qsfp's the dom data is part of eeprom_if_raw + # The first 128 bytes + + sfpd_obj = sff8436Dom(eeprom_ifraw) + if sfpd_obj is not None: + sfp_data['dom'] = sfpd_obj.get_data_pretty() + return sfp_data + + sfpi_obj = sff8472InterfaceId(eeprom_ifraw) + if sfpi_obj is not None: + sfp_data['interface'] = sfpi_obj.get_data_pretty() + cal_type = sfpi_obj.get_calibration_type() + + if eeprom_domraw is not None: + sfpd_obj = sff8472Dom(eeprom_domraw, cal_type) + if sfpd_obj is not None: + sfp_data['dom'] = sfpd_obj.get_data_pretty() + + return sfp_data + + @abc.abstractmethod + def get_presence(self, port_num): + """ + :param port_num: Integer, index of physical port + :returns: Boolean, True if tranceiver is present, False if not + """ + return + + @abc.abstractmethod + def get_low_power_mode(self, port_num): + """ + :param port_num: Integer, index of physical port + :returns: Boolean, True if low-power mode enabled, False if disabled + """ + return + + @abc.abstractmethod + def set_low_power_mode(self, port_num, lpmode): + """ + :param port_num: Integer, index of physical port + :param lpmode: Boolean, True to enable low-power mode, False to disable it + :returns: Boolean, True if low-power mode set successfully, False if not + """ + return + + @abc.abstractmethod + def reset(self, port_num): + """ + :param port_num: Integer, index of physical port + :returns: Boolean, True if reset successful, False if not + """ + return