#!/usr/bin/python
#
# Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020  Cumulus Networks, Inc.
#
# This package is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
# On Debian systems, the complete text of the GNU General
# Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

import argparse
import re
import os
import ctypes
import daemon
import time
import logging
import logging.handlers
import traceback
import sys
import subprocess
import errno
import socket
import struct
import array
import fcntl
import io
import ConfigParser
import signal
import StringIO
import json
import cumulus.platforms
try:
    import bcmshell
except ImportError:
    pass

import cumulus.porttab
import cumulus.sdnotify

from l1_show.eeprom_utils import log_throttle, set_logger, InvalidTransceiverCode, EEPROMTimeout, SFFDecoder, SFPDecoder, QSFPDecoder, SFFCable, SysFSEEPROM

"""
This file makes the following assumptions about data structures:

struct ifreq
{
    union
    {
        char    ifrn_name[16];
    } ifr_ifrn;

    union {
        struct    sockaddr ifru_addr;
        struct    sockaddr ifru_dstaddr;
        struct    sockaddr ifru_broadaddr;
        struct    sockaddr ifru_netmask;
        struct    sockaddr ifru_hwaddr;
        short     ifru_flags;
        int       ifru_ivalue;
        int       ifru_mtu;
        struct    ifmap ifru_map; // 16 bytes long
        char      ifru_slave[16];
        char      ifru_newname[16];
        void __user *    ifru_data;
        struct    if_settings ifru_settings;
    } ifr_ifru;
};

typedef unsigned short sa_family_t;

struct sockaddr {
    sa_family_t  sa_family;
    char         sa_data[14];
};

struct ifconf {
    int                 ifc_len; /* size of buffer */
    union {
        char           *ifc_buf; /* buffer address */
        struct ifreq   *ifc_req; /* array of structures */
    };
};


// From linux/ethtool.h

struct ethtool_cmd {
    __u32   cmd;
    __u32   supported;      /* Features this interface supports */
    __u32   advertising;    /* Features this interface advertises */
    __u16   speed;          /* The forced speed, 10Mb, 100Mb, gigabit */
    __u8    duplex;         /* Duplex, half or full */
    __u8    port;           /* Which connector port */
    __u8    phy_address;
    __u8    transceiver;    /* Which transceiver to use */
    __u8    autoneg;        /* Enable or disable autonegotiation */
    __u32   maxtxpkt;       /* Tx pkts before generating tx int */
    __u32   maxrxpkt;       /* Rx pkts before generating rx int */
    __u32   reserved[4];
};

struct ethtool_value {
    __u32    cmd;
    __u32    data;
};
"""

# From linux/sockios.h
SIOCETHTOOL = 0x8946

# From linux/socket.h
AF_UNIX      = 1
AF_INET      = 2

# From linux/ethtool.h
ETHTOOL_GSET = 0x00000001 # Get settings
ETHTOOL_SSET = 0x00000002 # Set settings
ETHTOOL_GLINK = 0x0000000a # Get link status (ethtool_value)
ETHTOOL_GLINKSETTINGS = 0x0000004c # Get ethtool_link_settings

try:
    import smbus
    smbus_available = True
except ImportError:
    smbus_available = False

logger = None
log_handlers = []
sock = None
sockfd = None


class BCMNotAvailable(Exception):
    pass

class BCMChipNotFound(Exception):
    pass

class UnsupportedPhy(Exception):
    pass

class NoSuchPort(Exception):
    pass

class RetimerTimeout(Exception):
    pass


class Port(object):
    '''
    Port - designed as a temporary mirror of the cumulus/portconfig physical
    port class
    '''
    IF_UP = 0x1 # interface is up
    disabled = False

    def __init__(self, *args, **kwargs):
        self.label = kwargs['label']
        self.driver = kwargs['driver']
        if 'config' in kwargs.keys():
            self.config = kwargs['config']
        else:
            self.config = None
        if 'has_modules' in kwargs.keys():
            self.has_modules = kwargs['has_modules']
        else:
            self.has_modules = None
        self.platform = cumulus.platforms.probe()

        self._no_link_resets = 0

    def __str__(self):
        return 'port-%s' % self.label

    def find_port(self, label):
        '''
        Find a platform port based upon the port's label.
        '''
        for port in self.platform.switch.ports:
            if port.label == label and not port.is_fabric:
                return port
        return None

    @property
    def exists(self):
        if (self.has_modules):
            port = self.find_port(self.label)
            name = port.name_override
            if (os.path.exists('/sys/class/net/%s' % name)):
                return True
        if (os.path.exists('/sys/class/net/swp%s' % self.label) or
            os.path.exists('/sys/class/net/swp%ss0' % self.label)):
            return True
        else:
            return False

    @property
    def eeprom_ready(self):
        return self.exists and not self.disabled and self.driver.present

    @property
    def sysfs_ready(self):
        return self.exists and not self.disabled

    @property
    def traffic_ready(self):
        return self.sysfs_ready and self.eeprom_ready and any(self.admin_up)

    def no_link_resets(self, clear=False):
        old = self._no_link_resets
        self._no_link_resets += 1
        if clear:
            old = 0
            self._no_link_resets = 0
        return old

    def _swp_flags(self, sub=''):
        if (self.has_modules):
            port = self.find_port(self.label)
            name = port.name_override
            return int(open('/sys/class/net/%s/flags' % name).read(), 0)
        else:
            return int(open('/sys/class/net/swp%s%s/flags' % (self.label, sub)).read(), 0)

    def _swp_protodown(self, sub=''):
        try:
            if (self.has_modules):
                port = self.find_port(self.label)
                name = port.name_override
                return int(open('/sys/class/net/%s/proto_down' % name).read(), 0)
            else:
                return int(open('/sys/class/net/swp%s%s/proto_down' % (self.label, sub)).read(), 0)
        except IOError, e:
            if e.errno == errno.EINVAL:
                return 0
            raise e

    def _swp_carrier(self, sub=''):
        try:
            if (self.has_modules):
                port = self.find_port(self.label)
                name = port.name_override
                return int(open('/sys/class/net/%s/carrier' % name).read(), 0)
            else:
                return int(open('/sys/class/net/swp%s%s/carrier' % (self.label, sub)).read(), 0)
        except IOError, e:
            if e.errno == errno.EINVAL:
                return 0
            raise e

    def _swp_speed(self, sub=''):
        if (self.has_modules):
            port = self.find_port(self.label)
            name = port.name_override
            base = '%s' % name
        else:
            base = 'swp%s%s' % (self.label, sub)

        if sock is None or sockfd is None:
            socket_init()

        try:
            # 3.0 uses kernel version 4.1.25 which will always support ETHTOOL_GLINKSETTINGS.
            # We first send an empty command to get link_mode_masks_nwords
            ecmd = array.array('B', struct.pack('I48s', ETHTOOL_GLINKSETTINGS, '\x00'*48))
            ifreq = struct.pack('16sP', base, ecmd.buffer_info()[0])
            fcntl.ioctl(sockfd, SIOCETHTOOL, ifreq)
            res = ecmd.tostring()
            nwords, = struct.unpack('15xb36x', res)
            # link_mode_masks_nwords returned is -ve, go figure...
            nwords = -(int(nwords))

            # Now that we know the link_mode_masks_nwords, send the cmd with right size
            len_after_nwords = 32 + (12 * nwords)
            ecmd = array.array('B', struct.pack('I11sB%ds' %(len_after_nwords),
                                                ETHTOOL_GLINKSETTINGS, '\x00'*11,
                                                nwords, '\x00'*(len_after_nwords)))
            ifreq = struct.pack('16sP', base, ecmd.buffer_info()[0])
            fcntl.ioctl(sockfd, SIOCETHTOOL, ifreq)
            res = ecmd.tostring()
            len_after_an = 36 + (12*nwords)
            speed, duplex, auto = struct.unpack('4xIb2xb%dx' %len_after_an, res)
            logger.debug("%s: speed %d, duplex %d, auto %d" %(base, speed, duplex, auto))
        except IOError:
                speed, duplex, auto = 0xffffffff, 0xff, 0xff
        except IOError, e:
            if e.errno == errno.EINVAL:
                # interface is not administratively up, carrier is invalid
                return 0
            raise e

        if speed == 0xffffffff:
            speed = 0

        return speed

    @property
    def _port_flags(self):
        try:
            return (self._swp_flags(), )
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        try:
            return (self._swp_flags(sub='s0'),
                    self._swp_flags(sub='s1'),
                    self._swp_flags(sub='s2'),
                    self._swp_flags(sub='s3'))
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        try:
            return (self._swp_flags(sub='s0'),
                    self._swp_flags(sub='s1'))
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        raise NoSuchPort('failed to get flags for port: %s' % self.label)

    @property
    def _port_carrier(self):
        try:
            return (self._swp_carrier(), )
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        try:
            return (self._swp_carrier(sub='s0'),
                    self._swp_carrier(sub='s1'),
                    self._swp_carrier(sub='s2'),
                    self._swp_carrier(sub='s3'))
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        try:
            return (self._swp_carrier(sub='s0'),
                    self._swp_carrier(sub='s1'))
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        raise NoSuchPort('failed to get carrier for port: %s' % self.label)

    @property
    def _port_protodown(self):
        try:
            return (self._swp_protodown(), )
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        try:
            return (self._swp_protodown(sub='s0'),
                    self._swp_protodown(sub='s1'),
                    self._swp_protodown(sub='s2'),
                    self._swp_protodown(sub='s3'))
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        try:
            return (self._swp_protodown(sub='s0'),
                    self._swp_protodown(sub='s1'))
        except IOError, e:
            if e.errno != errno.ENOENT:
                raise e

        raise NoSuchPort('failed to get protodown status for port: %s' % self.label)


    @property
    def _port_speed(self):
        if (self.has_modules):
            port = self.find_port(self.label)
            name = port.name_override
            path = '/sys/class/net/%s' % name
        else:
            path = '/sys/class/net/swp%s' % (self.label)

        if os.path.exists(path):
            # Even in the case where the port i.e. swp1 doesn't exist
            # and sub interfaces are created, ethtool ioctl is returning
            # is returning 0, so checking existence of /sys/class/net/<interface>
            # to check existence of Port
            try:
                return (self._swp_speed(), )
            except IOError, e:
                if e.errno != errno.ENOENT:
                    raise e
        else:
            try:
                return (self._swp_speed(sub='s0'),
                    self._swp_speed(sub='s1'),
                    self._swp_speed(sub='s2'),
                    self._swp_speed(sub='s3'))
            except IOError, e:
                if e.errno != errno.ENOENT:
                    raise e
            try:
                return (self._swp_speed(sub='s0'),
                        self._swp_speed(sub='s1'))
            except IOError, e:
                if e.errno != errno.ENOENT:
                    raise e

        raise NoSuchPort('failed to get speed for port: %s' % self.label)

    def admin_down(self):
        if (self.has_modules):
            port = self.find_port(self.label)
            name = port.name_override
            base = '%s' % name
        else:
            base = 'swp%s' % self.label
        try:
            if len(self._port_carrier) == 1:
                subprocess.check_call(('ip', 'link', 'set', base, 'down'))
            else:
                for sub in range(len(self._port_carrier)):
                    subprocess.check_call(('ip link set %ss%d down' % (base, sub)).split())
        except subprocess.CalledProcessError, e:
                logger.warning('failed to admin down port %s' % self.label)

    @property
    def admin_up(self):
        return [ x & self.IF_UP != 0 for x in self._port_flags ]

    @property
    def proto_down(self):
        return [ x != 0 for x in self._port_protodown ]

    @property
    def carrier(self):
        return [ x != 0 for x in self._port_carrier ]

    @property
    def speed(self):
        return [ x  for x in self._port_speed ]

class PortConfig(object):
    pass

class PortDriver(object):

    def toggle_reset(self, timing):
        raise NotImplementedError

    def _set_reset(self, logical):
        raise NotImplementedError

    def _get_reset(self):
        raise NotImplementedError

    reset = property(_get_reset, _set_reset)

    def _set_pmod(self, power_mode):
        raise NotImplementedError

    def _get_pmod(self):
        raise NotImplementedError

    power_mode = property(_get_pmod, _set_pmod)

    @property
    def present(self):
        raise NotImplementedError

    @property
    def admin_up(self):
        raise NotImplementedError

    @property
    def eeprom(self):
        raise NotImplementedError

    def recover_eeprom_bus(self):
        pass

class FilePortDriver(PortDriver):
    def __init__(self, reset=None, lpmod=None, present=None, tx_disable=None,
                 tx_fault=None, rx_los=None, cable=None, tx_retimer=None,
                 rx_retimer=None):
        self._reset = reset
        self._lpmod = lpmod
        self._present = present
        self._tx_disable = tx_disable
        self._tx_fault = tx_fault
        self._rx_los = rx_los

        self.cable = cable

        self.tx_retimer = tx_retimer
        self.rx_retimer = rx_retimer

    def toggle_reset(self):
        if self._reset is not None:
            self.reset = 1
            time.sleep(.1)
            self.reset = 0
        elif self._tx_disable is not None:
            self.tx_disable = 1
            time.sleep(.1)
            self.tx_disable = 0

        time.sleep(.1)

    @property
    def present(self):
        if self._present:
            return self._present.logical == 1
        else:
            return False

    def _set_reset(self, logical):
        if self._reset:
            self._reset.logical = logical
            if not logical:
                # Hold after deasserting reset
                time.sleep(.1)

    def _get_reset(self):
        if self._reset:
            return self._reset.logical == 1
        else:
            return False

    reset = property(_get_reset, _set_reset)

    def _set_pmod(self, power_mode):
        if power_mode not in ('high', 'low'):
            raise ValueError('power mode must be "high" or "low"')

        if not self._lpmod:
            return

        if power_mode == 'high':
            self._lpmod.logical = 0
        else:
            self._lpmod.logical = 1

    def _get_pmod(self):
        if self._lpmod:
            if self._lpmod.logical == 1:
                return 'low'
            else:
                return 'high'
        else:
            return 'low'

    power_mode = property(_get_pmod, _set_pmod)

    @property
    def tx_fault(self):
        if self._tx_fault:
            return self._tx_fault.logical == 1
        else:
            return False

    @property
    def rx_los(self):
        if self._rx_los:
            return self._rx_los.logical == 1
        else:
            return False

    def _set_tx_disable(self, logical):
        if self._tx_disable:
            self._tx_disable.logical = logical

    def _get_tx_disable(self):
        if self._tx_disable:
            return self._tx_disable.logical == 1
        else:
            return False

    tx_disable = property(_get_tx_disable, _set_tx_disable)


class SysFSPortDriver(FilePortDriver):
    eeprom_dev_cache = {}
    directory_walk_cache = {}
    retimer_dev_cache = {}

    @classmethod
    def _fill_dir_cache(cls, directory):
        if directory not in cls.directory_walk_cache:
            cls.directory_walk_cache[directory] = []

            for root, dirs, files in os.walk(directory):
                cls.directory_walk_cache[directory].append((root, dirs, files))

    @classmethod
    def _find(cls, directory, signal):
        cls._fill_dir_cache(directory)

        for root, dirs, files in cls.directory_walk_cache[directory]:
            if signal in files:
                return os.path.join(root, signal)

    @classmethod
    def _fill_eeprom_cache(cls):
        if not cls.eeprom_dev_cache:
            for root, dirs, files in os.walk('/sys/class/eeprom_dev'):
                for eeprom_dir in dirs:
                    label = open(os.path.join(root, eeprom_dir, 'label'), 'r').read().strip()
                    cls.eeprom_dev_cache[label] = SysFSEEPROM(os.path.join(root, eeprom_dir, 'device', 'eeprom'))

    @classmethod
    def _find_eeprom(cls, portnum):
        cls._fill_eeprom_cache()

        target = 'port%u' % portnum

        if target in cls.eeprom_dev_cache:
            return cls.eeprom_dev_cache[target]

        raise NoSuchPort('no such port: port-%s' % portnum)

    @classmethod
    def _fill_retimer_cache(cls):
        if not cls.retimer_dev_cache:
            for root, dirs, files in os.walk('/sys/class/retimer_dev'):
                for retimer_dir in dirs:
                    label = open(os.path.join(root, retimer_dir, 'label'), 'r').read().strip()
                    path = os.path.join(root, retimer_dir, 'device')
                    cls.retimer_dev_cache[label] = path

    @classmethod
    def _find_retimer(cls, portnum, direction):
        cls._fill_retimer_cache()

        target = 'port%u_%s' % (portnum, direction)

        if target in cls.retimer_dev_cache:
            return cls.retimer_dev_cache[target]

        raise NoSuchPort('no such port: port-%s' % portnum)


class EEPROMOnlyPortDriver(SysFSPortDriver):
    def __init__(self, eeprom, name=None):
        cable = SFFCable(eeprom, name=name)
        present = TimeoutPresenceSignal(eeprom)
        SysFSPortDriver.__init__(self, reset=FalseSignal(), lpmod=FalseSignal(),
                                 present=present, cable=cable)

class GPIOPortDriver(SysFSPortDriver):
    pass

class Signal(object):
    pass

class ReadOnlySignal(Signal):
    value = None

    def _set_value(self, value):
        pass

    def _get_value(self):
        return self.value

    level = property(_get_value, _set_value)
    logical = level

class TrueSignal(ReadOnlySignal):
    value = 1

class FalseSignal(ReadOnlySignal):
    value = 0

class TimeoutPresenceSignal(Signal):
    def __init__(self, eeprom):
        self.eeprom = eeprom

    def _set_value(self, value):
        pass

    def _get_value(self):
        try:
            self.eeprom[0]
            return True
        except EEPROMTimeout:
            return False

    level = property(_get_value, _set_value)
    logical = level


class BinaryFileSignal(Signal):
    def __init__(self, path, ctype, bit, active='high'):
        self.path = path
        if not hasattr(self, 'fieldname'):
            self.fieldname = os.path.basename(self.path)
        self.ctype = ctype
        self.bit = bit

        if active not in ('high', 'low'):
            raise RuntimeError('invalid active level: %s' % active)

        self.active = active

    @property
    def cache(self):
        # not implemented - to be used to coalesce single-bit reads/writes to
        # multi-bit files.  For example, when a file contains 32 bits that
        # represent signals from 32 ports.
        return None

    @property
    def mask(self):
        return 1 << self.bit

    def _read(self):
        if (not self.fieldname or
                self.fieldname not in sysfs_cache.cached_fields or
                sysfs_cache.cached_fields[self.fieldname] is None):
            f = open(self.path, 'r')
            sysfs_cache.cached_fields[self.fieldname] = int(f.read(), base=0)
            f.close()
        return sysfs_cache.cached_fields[self.fieldname]

    def _write(self, value):
        f = open(self.path, 'w')
        f.write('0x%x\n' % self.ctype(value).value)
        sysfs_cache.cached_fields[self.fieldname] = value
        f.close()

    def _rmw(self, value):
        # XXX consider file locking on self.path
        self._write((self._read() & ~self.mask) | (value & self.mask))

    def _physical(self, value):
        # conditionally invert our mask bit in a given value
        if self.active == 'high':
            return value
        elif self.active == 'low':
            return value ^ self.mask

        raise RuntimeError('invalid active level: %s' % self.active)

    def _convert(self, value):
        # convert a myriad of logical levels to binary with our mask bit set to
        # 1 or 0
        if value in ('1', True, 'high', 'asserted'):
            return self.mask
        elif value in ('0', False, 'low', 'deasserted'):
            return 0
        else:
            return value

    def _set_level(self, value):
        self._rmw(self._convert(value))

    def _get_level(self):
        if self._read() & self.mask == 0:
            return 0
        else:
            return 1

    level = property(_get_level, _set_level)

    def _set_logical(self, value):
        self._set_level(self._physical(self._convert(value)))

    def _get_logical(self):
        level = self._get_level()
        if self.active == 'high':
            return level
        else:
            return ~level & 1

    logical = property(_get_logical, _set_logical)

class GPIOSignal(BinaryFileSignal):
    _base = '/sys/class/gpio'

    def _export(self, gpiopin):
        try:
            unexportf = open(self._base + '/unexport', 'w')
            unexportf.write('%u\n' % gpiopin)
            unexportf.close()
        except IOError, e:
            if e.errno == errno.EINVAL: # Invalid Argument (probably already unexported)
                pass
            else:
                raise e

        exportf = open(self._base + '/export', 'w')
        exportf.write('%u\n' % gpiopin)
        exportf.close()

    def _set_direction(self, gpiopin, direction):
        dirpath = self._base + '/gpio%u/direction' % gpiopin

        # Avoid unnecesary writes to direction.  They will cause gpio/value to
        # reset to 0.
        current_direction = open(dirpath).read().strip()
        if direction != current_direction:
            dirf = open(dirpath, 'w')
            dirf.write(direction + '\n')
            dirf.close()

    def __init__(self, gpiopin, active='high', direction='in'):
        self._export(gpiopin)
        self._set_direction(gpiopin, direction)
        BinaryFileSignal.__init__(self, self._base + '/gpio%u/value' % gpiopin,
                                  ctype=ctypes.c_bool, bit=0, active=active)

class GPIOSignalNamed(BinaryFileSignal):
    _base = '/sys/class/gpio'

    def __init__(self, gpiopin, active):
        path = self._base + '/' + gpiopin + '/value'
        self.fieldname = gpiopin
        BinaryFileSignal.__init__(self, path, ctype=ctypes.c_bool, bit=0,
                                  active=active)


class Phy(object):
    C45_CTRL_ADDR       = 0
    C45_CTRL_RESET      = 0x0080
    C45_CTRL_POWER_DOWN = 0x0008
    C45_CTRL_AUTONEG    = 0x0010

    C45_CTRL_SPEED_MASK = 0x4020
    C45_CTRL_SPEED_100  = 0x0020
    C45_CTRL_SPEED_1000 = 0x4000

    C45_CTRL_DUPLEX_FULL = 0x0001

    C45_STATUS_ADDR     = 1
    C45_STATUS_LINK     = 0x0400

    C45_ID1_ADDR        = 2
    C45_ID2_ADDR        = 3

    C45_AN_CTL_ADDR     = 4
    C45_AN_CTL_MASK     = 0xe00f
    C45_AN_CTL_ALL      = 0x4001 # 10/100 full duplex only

    C45_1000BT_CTL_ADDR = 9
    C45_1000BT_CTL_MASK = 0x00ff
    C45_1000BT_CTL_ALL  = 0x000e # 1000 full duplex only, auto master

    C45_VENDOR_ADDR     = 16
    C45_VENDOR_ADDR2    = 17

    C45_PAGE_SELECT     = 22
    C45_PAGE_COPPER     = 0x0000
    C45_PAGE_FIBER      = 0x0100

    C45_VENDOR_STATUS_EXT = 27

    @property
    def phyid(self):
        return (self.read(self.C45_ID1_ADDR), self.read(self.C45_ID2_ADDR))

    @property
    def probe(self):
        phyid = self.phyid
        return phyid[0] == self.id1 and phyid[1] in self.id2

    @property
    def link(self):
        # Read back-to-back to get current link status.
        status = self.read(self.C45_STATUS_ADDR)
        status = self.read(self.C45_STATUS_ADDR)
        return (status & self.C45_STATUS_LINK) != 0

    def reset(self, wait=True):
        reg = self.read(self.C45_CTRL_ADDR)
        self.write(self.C45_CTRL_ADDR, reg | self.C45_CTRL_RESET)

        if wait:
            while self.read(self.C45_CTRL_ADDR) & self.C45_CTRL_RESET != 0:
                logger.debug('waiting for phy reset')
                time.sleep(.1)

    def set_power(self, power):
        reg = self.read(self.C45_CTRL_ADDR)

        if power and (reg & self.C45_CTRL_POWER_DOWN):
            self.write(self.C45_CTRL_ADDR, reg & ~self.C45_CTRL_POWER_DOWN)
            return True
        elif not power and (reg & self.C45_CTRL_POWER_DOWN == 0):
            self.write(self.C45_CTRL_ADDR, reg | self.C45_CTRL_POWER_DOWN)
            return True

        return False

    def set_1000auto(self):
        changed = False

        reg = self.read(self.C45_CTRL_ADDR)
        mask = ~(self.C45_CTRL_SPEED_MASK | self.C45_CTRL_DUPLEX_FULL)
        new = (reg & mask) | (self.C45_CTRL_SPEED_1000 | self.C45_CTRL_DUPLEX_FULL)
        if reg != new:
            self.write(self.C45_CTRL_ADDR, new)
            logger.debug('speed select changed from 0x%04x to 0x%04x' % (reg, new))
            changed = True

        reg = self.read(self.C45_1000BT_CTL_ADDR)
        if (reg & self.C45_1000BT_CTL_MASK) != self.C45_1000BT_CTL_ALL:
            new = (reg & ~self.C45_1000BT_CTL_MASK) | self.C45_1000BT_CTL_ALL
            logger.debug('1000bt changed from 0x%04x to 0x%04x' % (reg, new))
            self.write(self.C45_1000BT_CTL_ADDR, new)
            changed = True

        return changed

    def set_100fd(self):
        reg = self.read(self.C45_CTRL_ADDR)
        mask = ~(self.C45_CTRL_SPEED_MASK | self.C45_CTRL_DUPLEX_FULL)
        newreg = (reg & mask) | (self.C45_CTRL_SPEED_100 | self.C45_CTRL_DUPLEX_FULL)

        if reg != newreg:
            self.write(self.C45_CTRL_ADDR, newreg)
            return True

        return False

    def enable_mdix(self):
        reg = self.read(self.C45_VENDOR_ADDR)
        newreg = reg | self.C45_VENDOR_MDIX

        if reg != newreg:
            self.write(self.C45_VENDOR_ADDR, newreg)
            return True

        return False

    @property
    def power(self):
        return (self.read(self.C45_CTRL_ADDR) & self.C45_CTRL_POWER_DOWN) == 0

    def select_page(self, page):
        self.write(self.C45_PAGE_SELECT, page)

    def set_an(self, page, an):
        assert(page in ('copper', 'fiber'))

        if page != 'copper':
            self.select_page(self.C45_PAGE_FIBER)

        reg = self.read(self.C45_CTRL_ADDR)
        changed = False

        if an:
            if (reg & self.C45_CTRL_AUTONEG) == 0:
                self.write(self.C45_CTRL_ADDR, reg | self.C45_CTRL_AUTONEG)
                changed = True
        else:
            if (reg & self.C45_CTRL_AUTONEG) != 0:
                self.write(self.C45_CTRL_ADDR, reg & ~self.C45_CTRL_AUTONEG)
                changed = True

        if page != 'copper':
            self.select_page(self.C45_PAGE_COPPER)
        return changed

class I2CPhy(Phy):
    def __init__(self, i2c):
        self.i2c = i2c

    def read(self, addr):
        data = self.i2c.read_word_data(self.i2c_addr, addr)
        return data

    def write(self, addr, data):
        rv = self.i2c.write_word_data(self.i2c_addr, addr, data)
        return rv

class MV88E1111Phy(I2CPhy):
    revisions = { 0xc10c : '1',
                  0xc20c : '2' }
    i2c_addr = 0x56
    id1 = 0x4101
    id2 = revisions.keys()

    MV88E1111_EXT_STATUS_ADDR = 27
    MV88E1111_EXT_STATUS_HWCONFIG_MASK = 0x0f00
    MV88E1111_EXT_STATUS_HWCONFIG_1GX_NOCLK_NOAN = 0x0c00
    MV88E1111_EXT_STATUS_HWCONFIG_SGMII_NOCLK = 0x0400

    C45_VENDOR_ENERGY_DETECT = 0x0003
    C45_VENDOR_MAC_ALWAYS_UP = 0x0800
    C45_VENDOR_MDIX = 0x6000

    C45_VENDOR_LINK = 0x0004

    C45_VENDOR_STATUS_EXT_AN_BYPASS = 0x0010

    @property
    def revision(self):
        return self.revisions.get(self.phyid[1], 'Unknown')

    @property
    def link(self):
        status = self.read(self.C45_VENDOR_ADDR2)
        return (status & self.C45_VENDOR_LINK) != 0

    def dump_regs(self):
        for page in (self.C45_PAGE_FIBER, self.C45_PAGE_COPPER):
            self.select_page(page)
            for reg in range(11) + range(15, 31):
                logger.debug('page: %u reg: %u value: 0x%04x' % (page, reg, self.read(reg)))

    def enable_energy_detect_plus(self):
        reg = self.read(self.C45_VENDOR_ADDR)

        # enable energy detect plus, allow mac interface to power down
        if (reg & (self.C45_VENDOR_ENERGY_DETECT |
                   self.C45_VENDOR_MAC_ALWAYS_UP) !=
            self.C45_VENDOR_ENERGY_DETECT):

            reg &= ~self.C45_VENDOR_MAC_ALWAYS_UP
            reg |= self.C45_VENDOR_ENERGY_DETECT
            self.write(self.C45_VENDOR_ADDR, reg)
            return True

        return False

    def set_autoneg_bypass(self, enabled, page=None):
        rv = False

        if page is None:
            rv |= self.set_autoneg_bypass(enabled, page=self.C45_PAGE_COPPER)
            rv |= self.set_autoneg_bypass(enabled, page=self.C45_PAGE_FIBER)
            return rv

        if page != self.C45_PAGE_COPPER:
            self.select_page(page)

        reg = self.read(self.C45_VENDOR_STATUS_EXT)

        if enabled:
            if (reg & self.C45_VENDOR_STATUS_EXT_AN_BYPASS) == 0:
                self.write(self.C45_VENDOR_STATUS_EXT,
                           reg & self.C45_VENDOR_STATUS_EXT_AN_BYPASS)
                rv = True
        else:
            if (reg & self.C45_VENDOR_STATUS_EXT_AN_BYPASS) != 0:
                self.write(self.C45_VENDOR_STATUS_EXT,
                           reg & ~self.C45_VENDOR_STATUS_EXT_AN_BYPASS)
                rv = True

        if page != 0:
            self.select_page(0)

        return rv

    def sgmii_mode(self, port=None):
        reg = self.read(self.MV88E1111_EXT_STATUS_ADDR)
        hwconfig = reg & self.MV88E1111_EXT_STATUS_HWCONFIG_MASK

        if hwconfig == 0:
            logger.debug('resetting MV88E1111 phy')
            port.driver.toggle_reset()
            self.reset()

        if (hwconfig != self.MV88E1111_EXT_STATUS_HWCONFIG_SGMII_NOCLK):
            reg &= ~self.MV88E1111_EXT_STATUS_HWCONFIG_MASK
            reg |= self.MV88E1111_EXT_STATUS_HWCONFIG_SGMII_NOCLK
            self.write(self.MV88E1111_EXT_STATUS_ADDR, reg)
            return True

        return False

    def gbic_noan_mode(self, port=None):
        reg = self.read(self.MV88E1111_EXT_STATUS_ADDR)
        hwconfig = reg & self.MV88E1111_EXT_STATUS_HWCONFIG_MASK

        if hwconfig == 0:
            logger.debug('resetting MV88E1111 phy')
            port.driver.toggle_reset()
            self.reset()

        if (hwconfig != self.MV88E1111_EXT_STATUS_HWCONFIG_1GX_NOCLK_NOAN):
            reg &= ~self.MV88E1111_EXT_STATUS_HWCONFIG_MASK
            reg |= self.MV88E1111_EXT_STATUS_HWCONFIG_1GX_NOCLK_NOAN
            self.write(self.MV88E1111_EXT_STATUS_ADDR, reg)
            return True

        return False


class Retimer(object):
    def __init__(self, port, path, channel):
        self.port = port
        self.path = path
        self.channel = channel
        self.ctype=ctypes.c_uint32
        self.speed = []

    @property
    def set_speed(self):
        raise NotImplementedError

class SimpleCPLDSFPDriver(SysFSPortDriver):
    _base = None

    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_enable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='low'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='low'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class SimpleCPLDQSFPDriver(SysFSPortDriver):
    _base = None

    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint32,
                                                              bit=control_bit,
                                                              active='low'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lpmod',
                                                              ctype=ctypes.c_uint32,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='low'),
                                       tx_disable=FalseSignal(),
                                       tx_fault=FalseSignal(),
                                       rx_los=FalseSignal(),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaSimpleCPLDQSFPDriver(SimpleCPLDQSFPDriver):
    def recover_eeprom_bus(self):
        logger.warn("Resetting module in port %s which may have locked up the I2C bus" % self.cable.name)
        # wait six seconds for the I2C bus to be reset by the bus monitor
        time.sleep(6)
        try:
            self.toggle_reset()
        except IOError:
            logger.warn("Unable to reset module in port %s" % self.cable.name)

class QuantaGPIOPortDriver(GPIOPortDriver):
    def recover_eeprom_bus(self):
        logger.warn("Resetting module in port %s which may have locked up the I2C bus" % self.cable.name)
        # wait six seconds for the I2C bus to be reset by the bus monitor
        time.sleep(6)
        try:
            self.toggle_reset()
        except IOError:
            logger.warn("Unable to reset module in port %s" % self.cable.name)

class CelSmallstoneXPQSFPDriver(SimpleCPLDQSFPDriver):
        _base = '/sys/devices/platform/cel_smallstone_xp_cpld.0'
        def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelSmallstoneXPBQSFPDriver(SimpleCPLDQSFPDriver):
        _base = '/sys/devices/platform/cel_smallstone_xp_b_cpld.0'
        def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelRedstoneXPSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/cel_redstone_xp_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=FalseSignal(),
                                       rx_los=FalseSignal(),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelRedstoneXPQSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/cel_redstone_xp_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelRedstoneXPBSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/cel_redstone_xp_b_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=FalseSignal(),
                                       rx_los=FalseSignal(),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelRedstoneXPBQSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/cel_redstone_xp_b_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelRedstoneVSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/cel_redstone_v_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=FalseSignal(),
                                       rx_los=FalseSignal(),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelRedstoneVQSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/cel_redstone_v_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS4222SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/pci0000:00/0000:00:12.0/i2c-1/i2c-11/11-0060'
    def __init__(self, sfp_num, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=0,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=0,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=0,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=0,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS5712SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/accton_as5712_54x_cpld.0/'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS5712QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/accton_as5712_54x_cpld.0/'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS5812QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/accton_as5812_54t_cpld.0/'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS5835SFPDriver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(base + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(base + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(base + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(base + '_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS5835QSFPDriver(SimpleCPLDQSFPDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(base + '_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(base + '_lpmode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(base + '_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class AS5912SFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/accton_as5912_54x_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_enable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp_rx_loss',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='low'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS5912QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/accton_as5912_54x_cpld.0/'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AcctonAS5916SFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/accton_as5916_54xl_cpld.0'
    def __init__(self, range, control_bit, port):
                SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                         lpmod=FalseSignal(),
                                         present=BinaryFileSignal(self._base + '/sfp_' + str(range) + '_present',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         tx_disable=BinaryFileSignal(self._base + '/sfp_' + str(range) + '_tx_disable',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         tx_fault=BinaryFileSignal(self._base + '/sfp_' + str(range) + '_tx_fault',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         rx_los=BinaryFileSignal(self._base + '/sfp_' + str(range) + '_rx_loss',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AcctonAS5916QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/bus/platform/devices/accton_as5916_54xl_cpld.0'
    def __init__(self, range, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_' + str(range) + '_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp28_' + str(range) + '_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp28_' + str(range) + '_lpmode',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS6712QSFPDriver(SimpleCPLDQSFPDriver):
        _base = '/sys/devices/platform/accton_as6712_32x_cpld.0'
        def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS7312SFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/accton_as7312_54x_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp_rx_loss',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='low'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AS7312QSFPDriver(SimpleCPLDQSFPDriver):
        _base = '/sys/devices/platform/accton_as7312_54x_cpld.0'
        def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=FalseSignal(),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))


class AS7326SFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/accton_as7326_56x_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp_rx_loss',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='low'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class AS7326QSFPDriver(SimpleCPLDQSFPDriver):
        _base = '/sys/devices/platform/accton_as7326_56x_cpld.0'
        def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=FalseSignal(),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS6000QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/dell_s6000_cpld.0'

class DellS6010QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/dell_s6010_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint32,
                                                              bit=control_bit,
                                                              active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS4000SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/dell_s4000_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_enable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='low'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='low'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS3000SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/dell_s3000_cpld.0'
    def __init__(self, control_bit, port):
        eeprom = SysFSPortDriver._find_eeprom(port)
        # mod_abs isn't exposed by this CPLD driver, use EEPROM timeout to detect presence
        SysFSPortDriver.__init__(self, present=TimeoutPresenceSignal(eeprom),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_enable',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='low'),
                                       tx_fault=FalseSignal(),
                                       rx_los=FalseSignal(),
                                       cable=SFFCable(eeprom, name=str(port)))

class CelE1031SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/e1031_smc.0'
    def __init__(self, sfp_num, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=0,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=0,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=0,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_rxlos',
                                                               ctype=ctypes.c_uint64,
                                                               bit=0,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DellS4000QSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/dell_s4000_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS4048TQSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/dell_s4048t_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS41xxSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/i2c/devices/'
    def __init__(self, device, range, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + str(device) + '/sfp_' + str(range) + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS41xxQSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/i2c/devices/10-0032'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_30_25_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_30_25_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_30_25_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS5048FSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/i2c/devices/'
    def __init__(self, device, range, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + str(device) + '/sfp28_' + str(range) + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + str(device) + '/sfp28_' + str(range) + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + str(device) + '/sfp28_' + str(range) + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + str(device) + '/sfp28_' + str(range) + '_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DellS5048FQSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/i2c/devices/26-003e'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_54_49_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp28_54_49_lpmode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp28_54_49_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DellS4248FBLSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/dellemc_s4248fbl_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp28_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp28_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp28_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp28_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DellS4248FBLQSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/dellemc_s4248fbl_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp28_lpmode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp28_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DellN3248PXESFPDriver(SysFSPortDriver):
    _base = '/sys/bus/i2c/devices/1-0032/sfp28_52_49'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                 tx_disable=BinaryFileSignal(self._base + '_tx_disable',
                                                             ctype=ctypes.c_uint64,
                                                             bit=control_bit,
                                                             active='high'),
                                 tx_fault=BinaryFileSignal(self._base + '_tx_fault',
                                                           ctype=ctypes.c_uint64,
                                                           bit=control_bit,
                                                           active='high'),
                                 rx_los=BinaryFileSignal(self._base + '_rx_los',
                                                         ctype=ctypes.c_uint64,
                                                         bit=control_bit,
                                                         active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DellN3248PXEQSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/i2c/devices/1-0032/qsfp28_54_53'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '_reset',
                                                              ctype=ctypes.c_uint32,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '_lpmode',
                                                              ctype=ctypes.c_uint32,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '_present',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DellS52xxFQSFPDriver(SysFSPortDriver):
    def __init__(self, base, port):
        name = 'port%s' %port
        SysFSPortDriver.__init__(self,
                                 reset=BinaryFileSignal(self._find(base, name + '_reset'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 lpmod=BinaryFileSignal(self._find(base, name + '_lpmode'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 present=BinaryFileSignal(self._find(base, name + '_present'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellS52xxFSFPDriver(SysFSPortDriver):
    def __init__(self, base, port):
        name = 'port%s' %port
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                 lpmod=FalseSignal(),
                                 present=BinaryFileSignal(self._find(base, name + '_present'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 tx_disable=BinaryFileSignal(self._find(base, name + '_tx_enable'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='low'),
                                 tx_fault=BinaryFileSignal(self._find(base, name + '_tx_fault'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 rx_los=BinaryFileSignal(self._find(base, name + '_rx_los'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DeltaAG9032V1QSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/pci0000:00/0000:00:13.0/i2c-1/i2c-14/14-0031'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lpmode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DeltaAG9032V2QSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/pci0000:00/0000:00:12.0/i2c-1/1-006a'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp28_lpmode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp28_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DeltaAG7648SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/delta_ag7648_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp_tx_enable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='low'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DeltaAG7648QSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/delta_ag7648_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DeltaAG5648V1SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/delta_ag5648v1_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp28_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp28_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp28_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp28_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DeltaAG5648V1QSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/delta_ag5648v1_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp28_lpmode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp28_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class DeltaAGV848V1SFP_8_1_Driver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(base + '17-0041/sfp28_8_1_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                 tx_disable=BinaryFileSignal(base + '17-0040/sfp28_8_1_tx_disable',
                                                             ctype=ctypes.c_uint64,
                                                             bit=control_bit,
                                                             active='high'),
                                 tx_fault=BinaryFileSignal(base + '17-0041/sfp28_8_1_tx_fault',
                                                           ctype=ctypes.c_uint64,
                                                           bit=control_bit,
                                                           active='high'),
                                 rx_los=BinaryFileSignal(base + '17-0041/sfp28_8_1_rx_los',
                                                         ctype=ctypes.c_uint64,
                                                         bit=control_bit,
                                                         active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DeltaAGV848V1SFP_16_9_Driver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(base + '17-0041/sfp28_16_9_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                 tx_disable=BinaryFileSignal(base + '17-0040/sfp28_16_9_tx_disable',
                                                             ctype=ctypes.c_uint64,
                                                             bit=control_bit,
                                                             active='high'),
                                 tx_fault=BinaryFileSignal(base + '17-0041/sfp28_16_9_tx_fault',
                                                           ctype=ctypes.c_uint64,
                                                           bit=control_bit,
                                                           active='high'),
                                 rx_los=BinaryFileSignal(base + '17-0041/sfp28_16_9_rx_los',
                                                         ctype=ctypes.c_uint64,
                                                         bit=control_bit,
                                                         active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DeltaAGV848V1SFP_24_17_Driver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(base + '17-0041/sfp28_24_17_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                 tx_disable=BinaryFileSignal(base + '17-0041/sfp28_24_17_tx_disable',
                                                             ctype=ctypes.c_uint64,
                                                             bit=control_bit,
                                                             active='high'),
                                 tx_fault=BinaryFileSignal(base + '17-0041/sfp28_24_17_tx_fault',
                                                           ctype=ctypes.c_uint64,
                                                           bit=control_bit,
                                                           active='high'),
                                 rx_los=BinaryFileSignal(base + '17-0041/sfp28_24_17_rx_los',
                                                         ctype=ctypes.c_uint64,
                                                         bit=control_bit,
                                                         active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DeltaAGV848V1SFP_32_25_Driver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(base + '17-0041/sfp28_32_25_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                 tx_disable=BinaryFileSignal(base + '17-0041/sfp28_32_25_tx_disable',
                                                             ctype=ctypes.c_uint64,
                                                             bit=control_bit,
                                                             active='high'),
                                 tx_fault=BinaryFileSignal(base + '17-0041/sfp28_32_25_tx_fault',
                                                           ctype=ctypes.c_uint64,
                                                           bit=control_bit,
                                                           active='high'),
                                 rx_los=BinaryFileSignal(base + '17-0041/sfp28_32_25_rx_los',
                                                         ctype=ctypes.c_uint64,
                                                         bit=control_bit,
                                                         active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DeltaAGV848V1SFP_40_33_Driver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(base + '17-0042/sfp28_40_33_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                 tx_disable=BinaryFileSignal(base + '17-0041/sfp28_40_33_tx_disable',
                                                             ctype=ctypes.c_uint64,
                                                             bit=control_bit,
                                                             active='high'),
                                 tx_fault=BinaryFileSignal(base + '17-0042/sfp28_40_33_tx_fault',
                                                           ctype=ctypes.c_uint64,
                                                           bit=control_bit,
                                                           active='high'),
                                 rx_los=BinaryFileSignal(base + '17-0042/sfp28_40_33_rx_los',
                                                         ctype=ctypes.c_uint64,
                                                         bit=control_bit,
                                                         active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DeltaAGV848V1SFP_48_41_Driver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(base + '17-0042/sfp28_48_41_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                 tx_disable=BinaryFileSignal(base + '17-0042/sfp28_48_41_tx_disable',
                                                             ctype=ctypes.c_uint64,
                                                             bit=control_bit,
                                                             active='high'),
                                 tx_fault=BinaryFileSignal(base + '17-0042/sfp28_48_41_tx_fault',
                                                           ctype=ctypes.c_uint64,
                                                           bit=control_bit,
                                                           active='high'),
                                 rx_los=BinaryFileSignal(base + '17-0042/sfp28_48_41_rx_los',
                                                         ctype=ctypes.c_uint64,
                                                         bit=control_bit,
                                                         active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DeltaAGV848V1QSFPDriver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(base + '17-0042/qsfp28_56_49_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                 lpmod=BinaryFileSignal(base + '17-0042/qsfp28_56_49_lpmode',
                                                        ctype=ctypes.c_uint8,
                                                        bit=control_bit,
                                                        active='high'),
                                 present=BinaryFileSignal(base + '17-0042/qsfp28_56_49_present',
                                                          ctype=ctypes.c_uint8,
                                                          bit=control_bit,
                                                          active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))


class LenovoNE0152TSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/lenovo_ne0152t_cpld.0'
    def __init__(self, sfp_num, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=0,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=0,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=0,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp' + str(sfp_num) + '_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=0,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class LenovoNE2572SFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/lenovo_ne2572_cpld.0'
    def __init__(self, control_bit, range, port):
        SysFSPortDriver.__init__(self, present=BinaryFileSignal(self._base + '/sfp28_' + range + '_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp28_' + range + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp28_' + range + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp28_' + range + '_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class LenovoNE2572QSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/lenovo_ne2572_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_49_54_reset',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._base + '/qsfp28_49_54_lpmode',
                                                              ctype=ctypes.c_uint8,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._base + '/qsfp28_49_54_present',
                                                                ctype=ctypes.c_uint8,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class LenovoNE2580SFPDriver(GPIOPortDriver):
    def __init__(self, port):
        name = 'port%s' % port
        GPIOPortDriver.__init__(self,
                                present=GPIOSignalNamed(name + '_present',
                                                        active='high'),
                                tx_disable=GPIOSignalNamed(name + '_tx_disable',
                                                           active='high'),
                                tx_fault=GPIOSignalNamed(name + '_tx_fault',
                                                         active='high'),
                                rx_los=GPIOSignalNamed(name + '_rx_los',
                                                       active='high'),
                                cable=SFFCable(self._find_eeprom(port), name=str(port)))


class LenovoNE2580QSFPDriver(GPIOPortDriver):
    def __init__(self, port):
        name = 'port%s' % port
        GPIOPortDriver.__init__(self,
                                reset=GPIOSignalNamed(name + '_reset',
                                                      active='high'),
                                lpmod=GPIOSignalNamed(name + '_lpmode',
                                                      active='high'),
                                present=GPIOSignalNamed(name + '_present',
                                                        active='high'),
                                cable=SFFCable(self._find_eeprom(port), name=str(port)))


class LenovoNE10032QSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/platform/lenovo_ne10032_cpld.0'
    def __init__(self, control_bit, range, port):
        SysFSPortDriver.__init__(self,
                                 reset=BinaryFileSignal(self._base + '/qsfp28_' + range + '_reset',
                                                        ctype=ctypes.c_uint8,
                                                        bit=control_bit,
                                                        active='high'),
                                 lpmod=BinaryFileSignal(self._base + '/qsfp28_' + range + '_lpmode',
                                                        ctype=ctypes.c_uint8,
                                                        bit=control_bit,
                                                        active='high'),
                                 present=BinaryFileSignal(self._base + '/qsfp28_' + range + '_present',
                                                          ctype=ctypes.c_uint8,
                                                          bit=control_bit,
                                                          active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))
        

class QuantaLY8QSFPDriver(GPIOPortDriver):
    def __init__(self, port):
        name = 'qsfp%s' %port
        GPIOPortDriver.__init__(self, reset=GPIOSignalNamed(name + '_reset',
                                                        active='low'),
                                      lpmod=GPIOSignalNamed(name + '_lpmode',
                                                        active='high'),
                                      present=GPIOSignalNamed(name + '_present',
                                                        active='low'),
                                      cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaLY9RangeleyQSFPDriver(SysFSPortDriver):
    _base = '/sys/devices/pci0000:00/0000:00:1f.3'

    def _find(self, signal):
        return SysFSPortDriver._find(self._base, signal)

    def __init__(self, port):
        name = 'qsfp%s' %port
        SysFSPortDriver.__init__(self,
                                 reset=BinaryFileSignal(self._find(name + '_reset'),
                                                             ctype=ctypes.c_uint32,
                                                             bit=0,
                                                             active='low'),
                                  lpmod=BinaryFileSignal(self._find(name + '_lpmode'),
                                                             ctype=ctypes.c_uint32,
                                                             bit=0,
                                                             active='high'),
                                  present=BinaryFileSignal(self._find(name + '_present'),
                                                             ctype=ctypes.c_uint32,
                                                             bit=0,
                                                             active='low'),
                                  cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaLY6QSFPDriver(SimpleCPLDQSFPDriver):
    def __init__(self, base, control_bit, port, reset, lp_mode, present):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._find(base, reset),
                                                              ctype=ctypes.c_uint16,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._find(base, lp_mode),
                                                              ctype=ctypes.c_uint16,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._find(base, present),
                                                                ctype=ctypes.c_uint16,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaLY7SFPDriver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(base +'_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(base + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(base + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(base + '_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaLY7QSFPDriver(GPIOPortDriver):
    def __init__(self, port):
        name = 'qsfp%s' %port
        GPIOPortDriver.__init__(self, reset=GPIOSignalNamed(name + '_reset',
                                                        active='high'),
                                      lpmod=GPIOSignalNamed(name + '_lpmode',
                                                        active='high'),
                                      present=GPIOSignalNamed(name + '_present',
                                                        active='high'),
                                      cable=SFFCable(self._find_eeprom(port), name=str(port)))

class DellZ9100ZQSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/bus/platform/devices/z9100_cpld.0'
    def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/zqsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/zqsfp_lpmod',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         present=BinaryFileSignal(self._base + '/zqsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))
                
class DellZ9264QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/bus/pci/drivers/fpga_driver/0000:04:00.0/'
    def __init__(self, control_bit, port):
                name = 'port%s' %port
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + name + '_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         lpmod=BinaryFileSignal(self._base + name + '_lpmode',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         present=BinaryFileSignal(self._base + name + '_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelSeastoneZQSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/devices/platform/cel_seastone_cpld.0'
    def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp_lp_mode',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelSea2Que2QSFP28Driver(SysFSPortDriver):
    def __init__(self, base, port):
        name = 'port%s' %port
        SysFSPortDriver.__init__(self,
                                 reset=BinaryFileSignal(self._find(base, name + '_reset'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 lpmod=BinaryFileSignal(self._find(base, name + '_lpmode'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 present=BinaryFileSignal(self._find(base, name + '_present'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelQuestoneSFPDriver(SysFSPortDriver):
    _base = '/sys/bus/platform/devices/cel_questone_cpld.0'
    def __init__(self, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(self._base + '/sfp28_48_1_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(self._base + '/sfp28_48_1_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(self._base + '/sfp28_48_1_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(self._base + '/sfp28_48_1_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelQuestoneQSFPDriver(SimpleCPLDQSFPDriver):
        _base = '/sys/bus/platform/devices/cel_questone_cpld.0'
        def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_54_49_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp28_54_49_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp28_54_49_lpmode',
                                                                ctype=ctypes.c_uint32,
                                                                bit=control_bit,
                                                                active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class CelQuestone2SFP28Driver(SysFSPortDriver):
    def __init__(self, base, port):
        name = 'port%s' %port
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                 lpmod=FalseSignal(),
                                 present=BinaryFileSignal(self._find(base, name + '_present'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 tx_disable=BinaryFileSignal(self._find(base, name + '_tx_disable'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 tx_fault=BinaryFileSignal(self._find(base, name + '_tx_fault'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 rx_los=BinaryFileSignal(self._find(base, name + '_rx_los'),
                                                          ctype=ctypes.c_uint32,
                                                          bit=0,
                                                          active='high'),
                                 cable=SFFCable(self._find_eeprom(port), name=str(port)))


class AcctonAS7712QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/bus/platform/devices/accton_as7712_32x_cpld.0'
    def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=FalseSignal(),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))
                
class AcctonAS7726QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/bus/platform/devices/accton_as7726_32x_cpld.0'
    def __init__(self, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=FalseSignal(),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AcctonAS7816QSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/bus/platform/devices/accton_as7816_64x_cpld.0'
    def __init__(self, range, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_' + str(range) + '_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp28_' + str(range) + '_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=FalseSignal(),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaIX1QSFPDriver(QuantaSimpleCPLDQSFPDriver):
    def __init__(self, base, control_bit, port, reset, lp_mode, present):
        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._find(base, reset),
                                                              ctype=ctypes.c_uint16,
                                                              bit=control_bit,
                                                              active='high'),
                                       lpmod=BinaryFileSignal(self._find(base, lp_mode),
                                                              ctype=ctypes.c_uint16,
                                                              bit=control_bit,
                                                              active='high'),
                                       present=BinaryFileSignal(self._find(base, present),
                                                                ctype=ctypes.c_uint16,
                                                                bit=control_bit,
                                                                active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))


class QuantaIX2SFPDriver(SysFSPortDriver):
    def __init__(self, control_bit, port):
        eeprom = self._find_eeprom(port)
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=TimeoutPresenceSignal(eeprom),
                                       tx_disable=FalseSignal(),
                                       tx_fault=FalseSignal(),
                                       rx_los=FalseSignal(),
                                       cable=SFFCable(eeprom, name=str(port)))

class QuantaIX2QSFPDriver(GPIOPortDriver):
    def __init__(self, port):
        name = 'qsfp%s' %port
        GPIOPortDriver.__init__(self, reset=GPIOSignalNamed(name + '_reset',
                                                        active='low'),
                                      lpmod=GPIOSignalNamed(name + '_lpmode',
                                                        active='high'),
                                      present=GPIOSignalNamed(name + '_present',
                                                        active='low'),
                                      cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaIX7QSFP_1_16_Driver(QuantaSimpleCPLDQSFPDriver):
    _base = '/sys/devices/pci0000:00/0000:00:1f.3/i2c-0/i2c-14/14-0038/qsfp28_1_16'
    def __init__(self, control_bit, port):
	        SysFSPortDriver.__init__(self,
					reset=BinaryFileSignal(self._base + '_reset',
							ctype=ctypes.c_uint32,
							bit=control_bit,
                                                        active='high'),
					lpmod=BinaryFileSignal(self._base + '_lpmode',
							ctype=ctypes.c_uint32,
							bit=control_bit,
							active='high'),
					present=BinaryFileSignal(self._base + '_present',
							ctype=ctypes.c_uint32,
							bit=control_bit,
							active='high'),
					cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaIX7QSFP_17_32_Driver(QuantaSimpleCPLDQSFPDriver):
    _base = '/sys/devices/pci0000:00/0000:00:1f.3/i2c-0/i2c-15/15-0038/qsfp28_17_32'
    def __init__(self, control_bit, port):
	        SysFSPortDriver.__init__(self,
					reset=BinaryFileSignal(self._base + '_reset',
							ctype=ctypes.c_uint32,
							bit=control_bit,
                                                        active='high'),
					lpmod=BinaryFileSignal(self._base + '_lpmode',
							ctype=ctypes.c_uint32,
							bit=control_bit,
							active='high'),
					present=BinaryFileSignal(self._base + '_present',
							ctype=ctypes.c_uint32,
							bit=control_bit,
							active='high'),
					cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaIX8SFPDriver(SysFSPortDriver):
    def __init__(self, base, control_bit, port):
        SysFSPortDriver.__init__(self, reset=FalseSignal(),
                                       lpmod=FalseSignal(),
                                       present=BinaryFileSignal(base +'_present',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_disable=BinaryFileSignal(base + '_tx_disable',
                                                                ctype=ctypes.c_uint64,
                                                                bit=control_bit,
                                                                active='high'),
                                       tx_fault=BinaryFileSignal(base + '_tx_fault',
                                                                 ctype=ctypes.c_uint64,
                                                                 bit=control_bit,
                                                                 active='high'),
                                       rx_los=BinaryFileSignal(base + '_rx_los',
                                                               ctype=ctypes.c_uint64,
                                                               bit=control_bit,
                                                               active='high'),
                                       cable=SFFCable(self._find_eeprom(port), name=str(port)))

class QuantaIX8QSFPDriver(QuantaGPIOPortDriver):
    def __init__(self, port):
        name = 'qsfp%s' %port
        GPIOPortDriver.__init__(self, reset=GPIOSignalNamed(name + '_reset',
                                                        active='high'),
                                      lpmod=GPIOSignalNamed(name + '_lpmode',
                                                        active='high'),
                                      present=GPIOSignalNamed(name + '_present',
                                                        active='high'),
                                      cable=SFFCable(self._find_eeprom(port), name=str(port)))

class Wedge100QSFPDriver(SimpleCPLDQSFPDriver):

    def __init__(self, control_bit, port):
        name = 'qsfp%s' %port
        self._base_path = '/sys/bus/i2c/drivers/accton_wedge100_cpld'
        i2c_addr = [f for f in os.listdir(self._base_path) if f.endswith('0032')]

        if len(i2c_addr):
            self._base_path = os.path.join(self._base_path, i2c_addr[0])

        SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base_path + '/qsfp_reset',
                                                               ctype=ctypes.c_uint32,
                                                               bit=control_bit,
                                                               active='high'),
                                      lpmod=GPIOSignalNamed(name + '_lp_mode',
                                                        active='high'),
                                      present=GPIOSignalNamed(name + '_present',
                                                        active='high'),
                                      cable=SFFCable(self._find_eeprom(port), name=str(port)))

class AcctonMinipackQSFPDriver(SimpleCPLDQSFPDriver):
    _base = '/sys/bus/pci/drivers/minipack_io_fpga/0000:05:00.0/'
    def __init__(self, slot, control_bit, port):
                SysFSPortDriver.__init__(self, reset=BinaryFileSignal(self._base + '/qsfp28_pim' + str(slot) + '_reset',
                                                                      ctype=ctypes.c_uint32,
                                                                      bit=control_bit,
                                                                      active='high'),
                                         present=BinaryFileSignal(self._base + '/qsfp28_pim' + str(slot) + '_present',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         lpmod=BinaryFileSignal(self._base + '/qsfp28_pim' + str(slot) + '_lpmode',
                                                                  ctype=ctypes.c_uint32,
                                                                  bit=control_bit,
                                                                  active='high'),
                                         cable=SFFCable(self._find_eeprom(port), name=str(port)))

class EthMode:
    def __init__(self, name, lanes, one=None, two=None, four=None):
        self.name = name
        self.lanes = lanes
        self._break_map = {}
        self._break_map[1] = one
        self._break_map[2] = two
        self._break_map[4] = four

    def breakout(self, lanes):
        if lanes == self.lanes:
            return self.name
        else:
            return self._break_map[lanes]

class BCMConfig(PortConfig):
    _chip_vendor = '14e4'
    _chip_map = { 'b768' : 'Maverick',
                  'b762' : 'Maverick',
                  'b760' : 'Maverick',
                  'b963' : 'Tomahawk',
                  'b962' : 'Tomahawk',
                  'b960' : 'Tomahawk',
                  'b965' : 'TomahawkPlus',
                  'b967' : 'TomahawkPlus',
                  'b970' : 'Tomahawk2',
                  'b980' : 'Tomahawk3',
                  'b870' : 'Trident3X7',
                  'b873' : 'Trident3X7',
                  'b770' : 'Trident3X5',
                  'b771' : 'Trident3X5',
                  'b864' : 'Trident2Plus',
                  'b860' : 'Trident2Plus',
                  'b854' : 'Trident2',
                  'b850' : 'Trident2',
                  'b846' : 'Trident',
                  'b845' : 'Trident',
                  'b340' : 'Helix4',
                  'b150' : 'Hurricane2',
                  '8375' : 'QumranMX',
                  '8370' : 'QumranMX',
                }
    _compliance_map = { '4wdm-10-msa'   : 'lr',
                        '4wdm-20-msa'   : 'lr',
                        '4wdm-40-msa'   : 'lr',
                        '100g-dr'       : 'lr',
                        '100g-fr-nofec' : 'lr',
                        '100g-lr-nofec' : 'lr',
                        '200g-acc1'     : 'sr',
                        '200g-aoc1'     : 'sr',
                        '200g-acc2'     : 'sr',
                        '200g-aoc2'     : 'sr',
                        '200g-cr4'      : 'cr',
                        '200g-sr4'      : 'sr',
                        '200g-dr4'      : 'lr',
                        '200g-fr4'      : 'lr',
                        '200g-psm4'     : 'sr',
                        '50g-lr'        : 'lr',
                        '200g-lr4'      : 'lr',
                        '64g-fc-ea'     : 'sr',
                        '64g-fc-sw'     : 'sr',
                        '64g-fc-lw'     : 'sr',
                        '128g-fc-ea'    : 'sr',
                        '128g-fc-sw'    : 'sr',
                        '128g-fc-lw'    : 'sr',
                        '100g-sr2'      : 'sr',
                        '100g-cr2'      : 'cr',
                        '100g-lr2'      : 'sr',
                        '100g-active'   : 'sr',
                        '100g-sr4'      : 'sr',
                        '100g-lr4'      : 'sr',
                        '100g-er4'      : 'sr',
                        '100g-sr10'     : 'sr',
                        '100g-cwdm4fec' : 'lr',
                        '100g-psm4'     : 'lr',
                        '100g-active-cu': 'sr',
                        '100g-cwdm4'    : 'sr',
                        '100g-cr4'      : 'cr',
                        '100g-clr4'     : 'sr',
                        '100g-dwdm2'    : 'sr',
                        '100g-wdm'      : 'sr',
                        '100g-swdm4'    : 'sr',
                        '100g-pam4-bidi': 'sr',
                        '50g-cr2'       : 'cr',
                        '40g-active'    : 'sr',
                        '40g-lr4'       : 'sr',
                        '40g-sr4'       : 'sr',
                        '40g-cr4'       : 'cr',
                        '40g-er4'       : 'sr',
                        '4x10g-sr'      : 'sr',
                        '40g-psm4'      : 'sr',
                        '40g-swdm4'     : 'sr',
                        '25g-cr'        : 'cr',
                        '25g-cr-ca-n'   : 'cr',
                        '25g-cr-ca-s'   : 'cr',
                        '25g-sr'        : 'sr',
                        '25g-lr'        : 'lr',
                        '10g-sr'        : 'sr',
                        '10g-lr'        : 'sr',
                        '10g-lrm'       : 'sr',
                        '10g-er'        : 'sr',
                        '10g-cr'        : 'cr',
                        '10g-t'         : 'cr',
                        '10g-active'    : 'sr',
                        '10g-tsr'       : 'sr',
                        '5g-t'          : 'sr',
                        '2.5g-t'        : 'sr',
                        '1000base-sx'   : 'gmii',
                        '1000base-lx'   : 'gmii',
                        '1000base-cx'   : 'gmii',
                        '1000base-t'    : 'gmii',
                        '1000base-x'    : 'gmii', }
    # XGMII is the default BCM SDK interface mode, but should never be
    # configured.
    _switchd_map = { 'XGMII' : None,
                     'XLAUI' : 'xlaui',
                     'XLAUI2': 'xlaui',
                     'CAUI'  : 'caui',
                     'GMII'  : 'gmii',
                     'SGMII' : 'sgmii',
                     'SFI'   : 'cr',
                     'XFI'   : 'sr',
                     'CR'    : 'cr',
                     'CR2'   : 'cr',
                     'CR4'   : 'cr',
                     'SR'    : 'sr',
                     'SR2'   : 'sr',
                     'SR4'   : 'sr',
                     'LR'    : 'lr',
                     'LR2'   : 'lr',
                     'LR4'   : 'lr',
                     'ZR'    : 'lr' }

    _iftype_map = { 'GMII'  : 3 ,
                    'XGMII' : 6 ,
                    'XLAUI' : 15,
                    'SFI'   : 9 ,
                    'XFI'   : 10,
                    'CR'    : 13,
                    'CR4'   : 14,
                    'SR'    : 16,
                    'SR4'   : 28,
                    'LR'    : 26,
                    'LR4'   : 27,
                    'ZR'    : 30,
                    'SR2'   : 38,
                    'CR2'   : 40,
                    'LR2'   : 55,
                    'CAUI'  : 25,
                    'XLAUI2': 42 }

    # from linux/include/uapi/linux/ethtool.h
    _ethmode_map = { '40g-cr4'        : 24,  # ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT
                     '40g-sr4'        : 25,  # ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT
                     '40g-lr4'        : 26,  # ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT
                     '10g-cr'         : 42,  # ETHTOOL_LINK_MODE_10000baseCR_Full_BIT
                     '10g-sr'         : 43,  # ETHTOOL_LINK_MODE_10000baseSR_Full_BIT
                     '10g-lr'         : 44,  # ETHTOOL_LINK_MODE_10000baseLR_Full_BIT
                     '10g-lrm'        : 45,  # ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT
                     '100g-sr4'       : 37,  # ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT
                     '100g-cr4'       : 38,  # ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT
                     '100g-lr4'       : 39,  # ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT
                     '25g-cr'         : 31,  # ETHTOOL_LINK_MODE_25000baseCR_Full_BIT
                     '25g-sr'         : 33,  # ETHTOOL_LINK_MODE_25000baseSR_Full_BIT
                     '10g-t'          : 12,  # ETHTOOL_LINK_MODE_10000baseT_Full_BIT      # CR interface
                     '10g-tsr'        : 12,  # ETHTOOL_LINK_MODE_10000baseT_Full_BIT      # SR interface
                     '50g-cr2'        : 34,  # ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT
                     '50g-sr2'        : 40,  # ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT
                     '2.5g-t'         : 47,  # ETHTOOL_LINK_MODE_2500baseT_Full_BIT
                     '5g-t'           : 48,  # ETHTOOL_LINK_MODE_5000baseT_Full_BIT
                     '1000base-t'     : 5,   # ETHTOOL_LINK_MODE_1000baseT_Full_BIT
                     '1000base-x'     : 41,  # ETHTOOL_LINK_MODE_1000baseX_Full_BIT
                     '50g-kr'         : 52,  # ETHTOOL_LINK_MODE_50000baseKR_Full_BIT
                     '50g-sr'         : 53,  # ETHTOOL_LINK_MODE_50000baseSR_Full_BIT
                     '50g-cr'         : 54,  # ETHTOOL_LINK_MODE_50000baseCR_Full_BIT
                     '50g-lr'         : 55,  # ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT
                     '50g-dr'         : 56,  # ETHTOOL_LINK_MODE_50000baseDR_Full_BIT
                     '100g-kr2'       : 57,  # ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT
                     '100g-sr2'       : 58,  # ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT
                     '100g-cr2'       : 59,  # ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT
                     '100g-lr2'       : 60,  # ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT
                     '100g-dr2'       : 61,  # ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT
                     '200g-kr4'       : 62,  # ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT
                     '200g-sr4'       : 63,  # ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT
                     '200g-lr4'       : 64,  # ETHTOOL_LINK_MODE_200000baseLR4_ER4_FR4_Full_BIT
                     '200g-dr4'       : 65,  # ETHTOOL_LINK_MODE_200000baseDR4_Full_BIT
                     '200g-cr4'       : 66,  # ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT
                   }

    _ethmode_equivalence_map = {
                    '40g-active'      : '40g-sr4',
                    '40g-swdm4'       : '40g-sr4',
                    '10g-active'      : '10g-sr',
                    '10g-er'          : '10g-lr',
                    '100g-active'     : '100g-sr4',
                    '100g-clr4'       : '100g-sr4',
                    '100g-sr10'       : '100g-sr4',
                    '100g-cwdm4'      : '100g-sr4',
                    '100g-swdm4'      : '100g-sr4',
                    '100g-psm4'       : '100g-sr4',
                    '100g-active-cu'  : '100g-sr4',
                    '100g-er4'        : '100g-lr4',
                    '100g-cwdm4fec'   : '100g-lr4',
                    '100g-dwdm2'      : '100g-lr4',
                    '100g-wdm'        : '100g-lr4',
                    '100g-pam4-bidi'  : '100g-lr4',
                    '25g-lr'          : '25g-sr',  # Currently no 25G LR is defined in ethtool hdr
                    '25g-cr-ca-n'     : '25g-cr',
                    '25g-cr-ca-s'     : '25g-cr',
                    '40g-er4'         : '40g-lr4',
                    '4x10g-sr'        : '40g-sr4',
                    '40g-psm4'        : '40g-sr4',
                    '1000base-sx'     : '1000base-x',
                    '1000base-lx'     : '1000base-x',
                    '1000base-cx'     : '1000base-x',
                    '4wdm-10-msa'     : '100g-lr4',
                    '4wdm-20-msa'     : '100g-lr4',
                    '4wdm-40-msa'     : '100g-lr4',
                    '100g-dr'         : '100g-lr4',
                    '100g-fr-nofec'   : '100g-lr4',
                    '100g-lr-nofec'   : '100g-lr4',
                    '200g-acc1'       : '200g-sr4',
                    '200g-aoc1'       : '200g-sr4',
                    '200g-acc2'       : '200g-sr4',
                    '200g-aoc2'       : '200g-sr4',
                    '200g-fr4'        : '200g-lr4',
                    '200g-psm4'       : '200g-sr4',
    }

    _reverse_ethmode_map = {}
    for ethmode_text in sorted(_ethmode_map):
        if _ethmode_map[ethmode_text] not in _reverse_ethmode_map:
            _reverse_ethmode_map[str(_ethmode_map[ethmode_text])] = ethmode_text

    _breakout_ethmode_map = {
                              '40g-lr4'        : EthMode('40g-lr4'       , 4, one='10g-lr'),
                              '40g-sr4'        : EthMode('40g-sr4'       , 4, one='10g-sr'),
                              '40g-cr4'        : EthMode('40g-cr4'       , 4, one='10g-cr'),
                              '10g-cr'         : EthMode('10g-cr'        , 1, four='40g-cr4'),
                              '10g-sr'         : EthMode('10g-sr'        , 1, four='40g-sr4'),
                              '10g-lr'         : EthMode('10g-lr'        , 1, four='40g-lr4'),
                              '10g-lrm'        : EthMode('10g-lrm'       , 1, four='40g-lr4'),
                              '100g-sr4'       : EthMode('100g-sr4'      , 4, one='25g-sr', two='50g-sr2'),
                              '100g-lr4'       : EthMode('100g-lr4'      , 4, one='25g-lr', two='50g-lr2'),
                              '100g-cr4'       : EthMode('100g-cr4'      , 4, one='25g-cr', two='50g-cr2'),
                              '25g-cr'         : EthMode('25g-cr'        , 1, four='100g-cr4', two='50g-cr2'),
                              '25g-sr'         : EthMode('25g-sr'        , 1, four='100g-sr4', two='50g-sr2'),
                              '10g-t'          : EthMode('10g-t'         , 1, four='10g-t', two='10g-t'),
                              '10g-tsr'        : EthMode('10g-tsr'       , 1, four='10g-tsr', two='10g-tsr'),
                              '50g-cr2'        : EthMode('50g-cr2'       , 2, two='100g-cr4', one='25g-cr'),
                              '50g-sr2'        : EthMode('50g-sr2'       , 2, two='100g-sr4', one='25g-sr'),
                              '5g-t'           : EthMode('5g-t'          , 1, four='5g-t', two='5g-t'),
                              '2.5g-t'         : EthMode('2.5g-t'        , 1, four='2.5g-t', two='2.5g-t'),
                              '1000base-t'     : EthMode('1000base-t'    , 1, four='1000base-t', two='1000base-t'),
                              '1000base-x'     : EthMode('1000base-x'    , 1, four='1000base-x', two='1000base-x'),
                              '200g-sr4'       : EthMode('200g-sr4'      , 4, one='25g-sr', two='50g-sr2'),
                              '200g-lr4'       : EthMode('200g-lr4'      , 4, one='25g-lr', two='50g-lr2'),
                              '200g-cr4'       : EthMode('200g-cr4'      , 4, one='25g-cr', two='50g-cr2'),
                            }

    _port_speed_map = { 'SFP+'   : 10000,
                        'SFP28'  : 25000,
                        'QSFP+'  : 40000,
                        'QSFP28' : 100000 }

    _qsfp_port_speed = {'10000': '40000',
                        '25000': '100000'}

    _sfp_port_speed = {'40000': '10000',
                       '100000': '25000'}

    def __init__(self, port_num, port_type=None, phyless=True, label=None, chip=None, serdes_lane0=None):
        self.port_num = port_num
        self.port_type = port_type
        self.phyless = phyless
        self.label=label
        self.chip = chip
        self.serdes_lane0 = serdes_lane0
        self._switchd_ethmode_cache = {}
        self.bcm_retry_time = [0.5, 3.0, 10.0, 15.0]

        self._bcmspeed_re = re.compile(r'\(([0-9.]+G)\)', re.MULTILINE)
        self._bcmunit_re = re.compile(r'^Unit.*(BCM[0-9]{5})')
        self._port_subint_re = re.compile(r'^swp\d+s\d')
        self.platform = cumulus.platforms.probe()
        if self.port_type is None:
            self.port_type = self._port_type_detect()

        if self.port_type not in ('SFP+', 'SFP28', 'QSFP+', 'QSFP28'):
            raise NotImplementedError

        if self.chip is None:
            self.chip = self._chip_detect()
            logger.debug('detected %s chip' % self.chip)

        if self.chip not in ('Maverick', 'Trident', 'Trident2', 'Trident2Plus', 'Trident3X7', 'Trident3X5',
                             'Tomahawk', 'TomahawkPlus', 'Tomahawk2', 'Tomahawk3', 'Helix4',
                             'Hurricane2', 'QumranMX'):
            raise NotImplementedError

    def find_port(self, label):
        '''
        Find a platform port based upon the port's label.
        '''
        #platform = cumulus.platforms.probe()
        for port in self.platform.switch.ports:
            if port.label == label and not port.is_fabric:
                return port
        return None

    def _port_type_find(self, interface):
        sfptab = open('/var/lib/cumulus/sfptab', 'r')
        for line in sfptab.readlines():
            if line.startswith(interface):
                (swp, sfp_type, foo) = line.split(None, 2)
                return sfp_type.upper()

    def _port_type_detect(self):
        # The port might be in a gang.  If so, refer to the base port for SFF
        # type.
        jefe2 = ((self.port_num - 1) & 1) + 1
        jefe4 = ((self.port_num - 1) & 3) + 1

        for pn in (self.port_num, jefe2, jefe4):
            pt = self._port_type_find('swp%d' % pn)
            if pt:
                return pt

        raise RuntimeError('no sfptab entry for swp%d' % self.port_num)

    def _chip_detect(self):
        devices = open('/proc/bus/pci/devices', 'r').readlines()

        if len(devices) == 0:
            # XXX - Helix4 is the only ASIC we suport that doesn't show up in
            # /proc/.  At least for the moment.  On those systems the devices
            # file is empty.
            return 'Helix4'

        for device in devices:
            (foo, pci_id, bar) = device.split(None, 2)
            vendor = pci_id[:4]
            product = pci_id[4:]
            if vendor == self._chip_vendor and product in self._chip_map:
                return self._chip_map[product]

        raise BCMChipNotFound('unable to find PCI device for BCM ASIC')

    def is_switchd_running(self):
        # Return code is 0, when switchd is running and we can access bcmshell.
        if (int(subprocess.call(["systemctl", "-q",  "is-active", "switchd"])) != 0):
            return False
        return True

    def bcm_interfaces_bcm(self):
        porttab = sysfs_cache.cached_static_porttab()

        if self.label is not None:
            try:
                port = self.find_port(self.label)
                name = port.name_override
                return (porttab.linux2bcm('%s' % name), )
            except cumulus.porttab.NoSuchLinuxInterface:
                pass

        try:
            return (porttab.linux2bcm('swp%u' % self.port_num), )
        except cumulus.porttab.NoSuchLinuxInterface:
            pass

        try:
            return tuple([ porttab.linux2bcm('swp%us%u' % (self.port_num, x)) for x in range(4) ])
        except cumulus.porttab.NoSuchLinuxInterface:
            pass

        try:
            return tuple([ porttab.linux2bcm('swp%us%u' % (self.port_num, x)) for x in range(2) ])
        except cumulus.porttab.NoSuchLinuxInterface:
            log_throttle('no SDK interface found for port %d.' %
                         (self.port_num), level='warning')
            pass

        return None

    def bcm_interfaces_logical(self):
        if self.is_switchd_running():
            if self.chip.startswith('QumranMX'):
                # dnx family doesnt support "bcm show portmap", so using the static porttab info
                porttab = sysfs_cache.cached_static_porttab()
            else:
                porttab = sysfs_cache.cached_logical_porttab()
        else:
            # portwd was called using --config during switchd startup
            porttab = sysfs_cache.cached_static_porttab()

        if self.label is not None:
            try:
                port = self.find_port(self.label)
                name = port.name_override
                return (porttab.linux2logical('%s' % name), )
            except cumulus.porttab.NoSuchLinuxInterface:
                pass

        try:
            return (porttab.linux2logical('swp%u' % self.port_num), )
        except cumulus.porttab.NoSuchLinuxInterface:
            pass

        try:
            return tuple([ porttab.linux2logical('swp%us%u' % (self.port_num, x)) for x in range(4) ])
        except cumulus.porttab.NoSuchLinuxInterface:
            pass

        try:
            return tuple([ porttab.linux2logical('swp%us%u' % (self.port_num, x)) for x in range(2) ])
        except cumulus.porttab.NoSuchLinuxInterface:
            log_throttle('no logical interface found for port %d.' %
                         (self.port_num), level='warning')
            pass

        return None

    def _bcmcmd_port_id(self, bcm):
        if self.chip in ('Trident2Plus', 'Trident3X7', 'Trident3X5', 'Tomahawk', 'TomahawkPlus', 'Tomahawk2',
                'Tomahawk3', 'QumranMX'):
            return '0x%x' % (1 << int(bcm))
        else:
            return bcm

    @property
    def bcm_interfaces(self):
        if self.chip in ('Trident2Plus', 'Trident3X7', 'Trident3X5', 'Tomahawk', 'TomahawkPlus', 'Tomahawk2',
                'Tomahawk3', 'QumranMX'):
            return self.bcm_interfaces_logical()
        else:
            return self.bcm_interfaces_bcm()

    @property
    def gang_size(self):
        assert(self.port_type.startswith('SFP'))

        porttab = sysfs_cache.cached_static_porttab()

        # Find lane2 and lane3 for our MAC core by rounding down to a multiple
        # of four.
        # This didn't work on 4148 since there are 6 QSFP ports in between 24 and 30,
        # and the next SFP is 31, so we override the serdes_lane0 for those ports in
        # _4148_ports, where: self.serdes_lane0 = ((self.port_num - 3) & ~0x3) + 3
        if self.serdes_lane0 is None:
            self.serdes_lane0 = ((self.port_num - 1) & ~0x3) + 1
        second_member = self.serdes_lane0 + 1
        third_member = self.serdes_lane0 + 2

        try:
            intf = 'swp%u' % third_member
            porttab.linux2bcm(intf)
        except cumulus.porttab.NoSuchLinuxInterface:
            # third member doesn't exist, therefore our lanes are ganged /4
            return 4

        try:
            intf = 'swp%u' % second_member
            porttab.linux2bcm(intf)
        except cumulus.porttab.NoSuchLinuxInterface:
            # second member doesn't exist, therefore our lanes are ganged /2
            return 2

        return 1

    @property
    def num_lanes(self):
        # How many lanes are assigned to each interface in a quad.

        if self.port_type.startswith('QSFP'):
            num_interfaces = len(self.linux_interfaces)
            if num_interfaces == 4:
                # must be a qsfp->sfp breakout cable
                return 1 # one lane per interface

            if num_interfaces == 2:
                # must be a 100G to 2x50G breakout cable
                return 2 # two lanes per interface

            # must be a qsfp with no breakout, including a 1x50G.
            return 4 # four lanes per interface
        else:
            # SFP port.  Determine if there is a gang or just one lane
            return self.gang_size

    @property
    def linux_interfaces(self):
        porttab = sysfs_cache.cached_static_porttab()
        try:
            intf = 'swp%u' % self.port_num
            porttab.linux2bcm(intf)
            return (intf, )
        except cumulus.porttab.NoSuchLinuxInterface:
            pass

        if self.label is not None:
            try:
                port = self.find_port(self.label)
                name = port.name_override
                intf = '%s' % name
                porttab.linux2bcm(intf)
                return (intf, )
            except cumulus.porttab.NoSuchLinuxInterface:
                pass

        try:
            intfs = [ 'swp%us%u' % (self.port_num, x) for x in range(4) ]
            for intf in intfs:
                porttab.linux2bcm(intf)
            return tuple(intfs)
        except cumulus.porttab.NoSuchLinuxInterface:
            pass

        try:
            intfs = [ 'swp%us%u' % (self.port_num, x) for x in range(2) ]
            for intf in intfs:
                porttab.linux2bcm(intf)
            return tuple(intfs)
        except cumulus.porttab.NoSuchLinuxInterface:
            log_throttle('no linux interface found for port %d.' %
                         (self.port_num), level='warning')
            pass

        return None

    def _set_swrxlos(self, mode):
        if self.chip not in ('Trident2', 'Helix4', 'Hurricane2', 'QumranMX'):
            return
        interfaces = self.bcm_interfaces
        if interfaces is None:
            return

        on = self._ifmode2swrxlos(mode, len(interfaces))
        for intf in interfaces:
            self.bcm_cmd_retry('phy control %s swrxlos=%u' % (self._bcmcmd_port_id(intf), on), 'Setting phy control swrxlos')

    def _ifmode2swrxlos(self, mode, num_lanes):
        assert(self.port_type in ('SFP+', 'SFP28', 'QSFP+', 'QSFP28'))

        if mode == None:
            return False

        if self.port_type in ('SFP+', 'SFP28'):
            return False

        assert(self.port_type.startswith('QSFP'))
        if mode.upper() in ('SR', 'LR'):
            media = 'fiber'
        else:
            media = 'copper'

        if media == 'fiber':
            return True

        return False

    swrxlos = property(None, _set_swrxlos)

    def set_port_txfir_settings(self, cable_length, compliance_code, current_speed):
        # This object can contain a full 4 lane port (QSFP) or a single lane SFP port.
        # Could be a breakout tuple of 2 or 4, the first port will do
        bcm_ports = self.bcm_interfaces
        bcm_port = str(bcm_ports[0])

        txfir_port_settings = self._map_txfir_settings(cable_length, compliance_code, current_speed, bcm_port)

        if txfir_port_settings:
            for lane in sorted(txfir_port_settings):
                logger.debug(
                    'bcm_port: {bcm_port}, lane {lane}: setting txfir: ams: 0x{ams:04x}, pre: {pre} (0x{pre:02x}), post: {post} (0x{post:02x}), main: {main} (0x{main:02x})'.format(
                        bcm_port=bcm_port, lane=lane,
                        ams=txfir_port_settings[lane]['ams'], pre=txfir_port_settings[lane]['pre'],
                        main=txfir_port_settings[lane]['main'], post=txfir_port_settings[lane]['post']))
                for key in ['ams', 'pre', 'main', 'post']:
                    reg = tx_settings.txfir_settings['base'][key][0]
                    field = tx_settings.txfir_settings['base'][key][1]
                    value =txfir_port_settings[lane][key]
                    if field:
                        txfir_values = 'phy {} {}r.{} {}={}'.format(bcm_port, reg, lane, field, value)
                    else:
                        txfir_values = 'phy {} {}r.{} {}'.format(bcm_port, reg, lane, value)

                    self.bcm_cmd_retry(txfir_values, 'Setting txfir values')

    def _map_txfir_settings(self, cable_length, compliance_code, current_speed, bcm_port):
        cable_type = self._compliance_map.get(compliance_code, self.ifmode)
        if cable_type == 'lr':
            cable_type = 'sr'

        if self.port_type.startswith('QSFP'):
            total_lanes = 4
            current_speed = self._qsfp_port_speed.get(str(current_speed), str(current_speed))
        elif self.port_type.startswith('SFP'):
            total_lanes = 1
            current_speed = self._sfp_port_speed.get(str(current_speed), str(current_speed))
        else:
            log_throttle('port: %d port_type: %s not valid' % (self.port_num, self.port_type))
            return {}

        if compliance_code not in self._breakout_ethmode_map:
            log_throttle('No breakout mode defined for %s' % compliance_code)
            port_compliance_code = compliance_code
        else:
            port_compliance_code = self._breakout_ethmode_map[compliance_code].breakout(total_lanes)

        compliance_speed = port_compliance_code.split('-')[0].lower()
        # convert cable speed to integer compatible values
        compliance_speed = compliance_speed.replace('.5g', '500').replace('g', '000')

        # could be running a 100G DAC at 40G
        if compliance_speed != current_speed:
            compliance_speed = current_speed

        candidate_length = 0
        speed_type_length = ''
        for key in tx_settings.txfir_settings:
            if key == 'base':
                continue
            keyspeed, keytype, keylength = key.split('_')
            if keytype != cable_type:
                continue
            if int(keyspeed) != int(compliance_speed):
                continue
            # if there are txfir settings for this type/speed, there must be one created with cable_length 0m
            if candidate_length <= int(keylength) <= int(cable_length):
                candidate_length = int(keylength)
                speed_type_length = '_'.join([keyspeed, keytype, str(candidate_length)])

        if speed_type_length not in tx_settings.txfir_settings:
            log_throttle('txfir settings not found for: port %d speed %s type %s length %s' %
                         (self.port_num, compliance_speed, cable_type, cable_length))
            return []
        logger.debug('Matched speed_type_length: {}'.format(speed_type_length))

        txfir_port_settings = tx_settings.txfir_settings[speed_type_length].get(bcm_port, {})
        if not txfir_port_settings:
            txfir_port_settings = tx_settings.txfir_settings[speed_type_length].get('all', {})
        if not txfir_port_settings:
            return {}

        txfir_return = {}
        for lane in txfir_port_settings:
            txfir_return[lane] = dict(ams=txfir_port_settings[lane][0], pre=txfir_port_settings[lane][1],
                                      post=txfir_port_settings[lane][2], main=txfir_port_settings[lane][3])

        return txfir_return

    def bcm_cmd_retry(self, cmd, mesg):
        bcm = bcmshell.bcmshell()
        success = False
        retry_counter = 0
        output = ''
        for retry_time in self.bcm_retry_time:
            try:
                output = bcm.run(cmd)
                success = True
                if retry_counter > 0:
                    logger.warn('%s timed out. Succeeded after %d retries' % (mesg, retry_counter))
                break
            except (IOError, RuntimeError) as e:
                logger.debug('bcm error: {}'.format(e))
                logger.debug('%s timed out. Retrying in %1.2f seconds' % (mesg, retry_time))
                retry_counter += 1
                time.sleep(retry_time)

        if not success:
            retry_counter += 1
            try:
                output = bcm.run(cmd)
                logger.warn('%s timed out. Succeeded after %d retries' % (mesg, retry_counter))
            except (IOError, RuntimeError) as e:
                logger.error('bcm error: {}'.format(e))
                logger.error('No response from BCM in %d attempts' % retry_counter)
                raise BCMNotAvailable

        return output

    def bcm_speed(self, intf):
        output = self.bcm_cmd_retry('port %s speed' % self._bcmcmd_port_id(intf), 'Getting port speed')

        try:
            speed = self._bcmspeed_re.search(output).group(1)
        except:
            return self._port_speed_map[self.port_type]

        if speed[-1] == 'G':
            speed = int(float(speed[:-1]) * 1000)
        else:
            speed = int(float(speed[:-1]) * 100)

        assert(speed in (100, 1000, 10000, 20000, 25000, 40000, 50000, 100000))

        return speed

    def _get_ifmode(self):
        interfaces = self.linux_interfaces
        if interfaces is None:
            return 'unknown'

        intf = interfaces[0]

        try:
            ifmode = open('/cumulus/switchd/config/interface/%s/interface_mode' % intf, 'r').read().strip()
        except SystemError as e:
            logger.info(str(e))
            return 'unknown'
        except IOError as e:
            if not (hasattr(e, 'errno') and e.errno in (errno.ENOENT, errno.ENOTCONN, errno.ECONNABORTED,
                                                        errno.EINVAL, errno.ENXIO)):
                (exc_type, exc_value, exc_traceback) = sys.exc_info()
                logger.exception(traceback.format_tb(exc_traceback))
            return 'unknown'
        except Exception as e:
            (exc_type, exc_value, exc_traceback) = sys.exc_info()
            logger.exception(traceback.format_tb(exc_traceback))
            return 'unknown'

        return self._switchd_map.get(ifmode, 'unknown')

    def _ifmode2switchd(self, mode, num_lanes, speed):
        if mode == None:
            return None

        if self.chip in ('Trident', 'Helix4', 'Hurricane2'):
            mapper = self._ifmode2switchd_trident
        elif self.chip in ('Trident2'):
            mapper = self._ifmode2switchd_trident2
        elif self.chip in ('Tomahawk'):
            mapper = self._ifmode2switchd_tomahawk
        elif self.chip.startswith('QumranMX'):
            mapper = self._ifmode2switchd_qumran
        else:
            mapper = self._ifmode2switchd_falcon_eagle

        return mapper(mode, num_lanes, speed)

    def _ifmode2switchd_tomahawk(self, mode, num_lanes, speed):
        switchd_mode = mode.upper()

        if speed == 10000:
            if switchd_mode in ('CR', 'SFI'):
                switchd_mode = 'SFI'
            else:
                switchd_mode = 'XFI'
        elif speed == 100000:
            if switchd_mode == 'SR':
                switchd_mode = 'CAUI'
        else:
            if switchd_mode.startswith('X') or switchd_mode == 'LR':
                switchd_mode = 'SR'

        return switchd_mode

    def _ifmode2switchd_qumran(self, mode, num_lanes, speed):
        switchd_mode = mode.upper()

        # Account for 40G breakout cables by translating between 4-lane and
        # 1-lane modes.  This is chip (internal phy driver) specific.

        if num_lanes == 4:
            if speed == 10000:
                switchd_mode = 'XLAUI'
            else:
                switchd_mode = 'CAUI'
        elif num_lanes == 2:
            switchd_mode = 'XLAUI2'
        elif num_lanes == 1:
            switchd_mode = 'SFI'

        return switchd_mode

    def _ifmode2switchd_trident2(self, mode, num_lanes, speed):
        switchd_mode = mode.upper()

        # Account for 40G breakout cables by translating between 4-lane and
        # 1-lane modes.  This is chip (internal phy driver) specific.
        if num_lanes == 4:
            if switchd_mode in ('CR', 'SR', 'LR'):
                switchd_mode += '4'
            elif switchd_mode in ('XFI', 'XGMII'):
                switchd_mode = 'SR4'
            elif switchd_mode in ('SFI',):
                switchd_mode = 'CR4'
        elif num_lanes == 2:
            if switchd_mode in ('CR', 'SR', 'LR'):
                switchd_mode += '2'
        elif num_lanes == 1:
            if switchd_mode in ('XLAUI', 'SR'):
                switchd_mode = 'XFI'
            elif switchd_mode == 'LR':
                # if distance requires DFE, use an override to change LR optic to CR (SFI)
                switchd_mode = 'XFI'
            elif switchd_mode in ('CR',):
                switchd_mode = 'SFI'
        else:
            raise RuntimeError('too many lanes: %d' % num_lanes)

        return switchd_mode

    def _ifmode2switchd_falcon_eagle(self, mode, num_lanes, speed):
        switchd_mode = mode.upper()
        # Account for 40G breakout cables by translating between 4-lane and
        # 1-lane modes.  This is chip (internal phy driver) specific.
        if num_lanes == 4:
            if speed == 50000:
                # 1x50g looks like 100G. change lanes to 2
                if switchd_mode in ('CR', 'SR', 'LR'):
                    switchd_mode += '2'
                elif switchd_mode in ('XFI', 'XGMII'):
                    switchd_mode = 'SR2'
                elif switchd_mode in ('SFI',):
                    switchd_mode = 'CR2'
            else:
                if switchd_mode in ('CR', 'SR', 'LR'):
                    switchd_mode += '4'
                elif switchd_mode in ('XFI', 'XGMII'):
                    switchd_mode = 'SR4'
                elif switchd_mode in ('SFI',):
                    switchd_mode = 'CR4'
        elif num_lanes == 2:
            if switchd_mode in ('CR', 'SR', 'LR'):
                switchd_mode += '2'
        elif num_lanes == 1:
            if switchd_mode in ('XLAUI'):
                switchd_mode = 'XFI'
        else:
            raise RuntimeError('too many lanes: %d' % num_lanes)

        return switchd_mode

    def _ifmode2switchd_trident(self, mode, num_lanes, speed):
        switchd_mode = self._ifmode2switchd_trident2(mode, num_lanes, speed)
        # SR4 is not valid for the Trident internal phy driver.  SR is the
        # equivalent and is handled correctly for ports using 1 or 4 lanes.
        if switchd_mode == 'SR4':
            switchd_mode = 'SR'

        return switchd_mode

    def _set_ifmode(self, mode):
        linux_interfaces = self.linux_interfaces
        if linux_interfaces is None:
            return

        bcm_interfaces = self.bcm_interfaces
        if bcm_interfaces is None:
            return

        for linux_intf, bcm_intf in zip(linux_interfaces, bcm_interfaces):
            bcm_speed = self.bcm_speed(bcm_intf)
            switchd_mode = self._ifmode2switchd(mode, self.num_lanes, bcm_speed)

            try:
                switchd_old = open('/cumulus/switchd/config/interface/%s/interface_mode' %
                                   linux_intf, 'r').read().strip()
            except SystemError as e:
                logger.info(str(e))
                return
            except IOError as e:
                if not (hasattr(e, 'errno') and e.errno in (errno.ENOENT, errno.ENOTCONN, errno.ECONNABORTED,
                                                            errno.EINVAL, errno.ENXIO)):
                    (exc_type, exc_value, exc_traceback) = sys.exc_info()
                    logger.exception(traceback.format_tb(exc_traceback))
                return
            except Exception as e:
                (exc_type, exc_value, exc_traceback) = sys.exc_info()
                logger.exception(traceback.format_tb(exc_traceback))
                return

            # Avoid expensive no-op interface changes.  It takes a long time
            # and will flap the link.
            if switchd_old == switchd_mode:
                continue

            logger.debug('set ifmode: %s %s->%s' % (linux_intf, switchd_old, switchd_mode))

            try:
                ifmode = open('/cumulus/switchd/config/interface/%s/interface_mode'
                              % linux_intf, 'w')
                ifmode.write(switchd_mode)
                ifmode.close()
            except SystemError as e:
                logger.info(str(e))
                return
            except IOError as e:
                if not (hasattr(e, 'errno') and e.errno in (errno.ENOENT, errno.ENOTCONN, errno.ECONNABORTED,
                                                            errno.EINVAL, errno.ENXIO)):
                    (exc_type, exc_value, exc_traceback) = sys.exc_info()
                    logger.exception(traceback.format_tb(exc_traceback))
                return
            except Exception as e:
                (exc_type, exc_value, exc_traceback) = sys.exc_info()
                logger.exception(traceback.format_tb(exc_traceback))
                return

    ifmode = property(_get_ifmode, _set_ifmode)

    def clear_switchd_ethmode_cache(self):
        self._switchd_ethmode_cache = {}

    def _get_ethmode(self, subint=0):
        interfaces = self.linux_interfaces
        if interfaces is None:
            return 'unknown'

        intf = interfaces[int(subint)]
        logger.debug('Checking ethmodes on (sub)interface: %s' % intf)

        if intf in self._switchd_ethmode_cache and self._switchd_ethmode_cache[intf]:
            return self._switchd_ethmode_cache[intf]
        try:
            switchd_ethmode_value = open('/cumulus/switchd/config/interface/%s/ethtool_mode' % intf, 'r').read().strip()
        except SystemError as e:
            logger.info(str(e))
            return 'unknown'
        except IOError as e:
            if not (hasattr(e, 'errno') and e.errno in (errno.ENOENT, errno.ENOTCONN, errno.ECONNABORTED,
                                                        errno.EINVAL, errno.ENXIO)):
                (exc_type, exc_value, exc_traceback) = sys.exc_info()
                logger.exception(traceback.format_tb(exc_traceback))
            return 'unknown'
        except Exception as e:
            (exc_type, exc_value, exc_traceback) = sys.exc_info()
            logger.exception(traceback.format_tb(exc_traceback))
            return 'unknown'

        switchd_ethmode_name = self._reverse_ethmode_map.get(switchd_ethmode_value, 'unknown')
        logger.debug('switchd ethmode: {}'.format(switchd_ethmode_name))

        if switchd_ethmode_name != 'unknown':
            switchd_ethmode_name = self._breakout_ethmode(switchd_ethmode_name)

        self._switchd_ethmode_cache[intf] = switchd_ethmode_name

        return switchd_ethmode_name

    def _set_ethmode(self, mode):
        # mode is a text_ethmode

        linux_interfaces = self.linux_interfaces
        if linux_interfaces is None:
            return

        bcm_interfaces = self.bcm_interfaces
        if bcm_interfaces is None:
            return

        switchd_ethmode_name_to_set = mode
        if mode in self._ethmode_equivalence_map:
            switchd_ethmode_name_to_set = self._ethmode_equivalence_map[mode]
            logger.debug('Port %s, switchd ethmode: Substituting equivalent ethmode: %s -> %s' %
                        (self.port_num, mode, switchd_ethmode_name_to_set))
        for linux_intf, bcm_intf in zip(linux_interfaces, bcm_interfaces):
            # cycle through all subints of a broken out port
            logger.debug('Checking lnx_inf: {} bcm_inf: {}'.format(linux_intf, bcm_intf))
            switchd_ethmode_name_to_set = self._breakout_ethmode(switchd_ethmode_name_to_set)

            subint = 0
            match = self._port_subint_re.match(linux_intf)
            if match:
                if match.group(0):
                    subint = match.group(0)[-1]
            switchd_ethmode_name = self._get_ethmode(subint)

            # Avoid expensive no-op mode changes.
            if switchd_ethmode_name == switchd_ethmode_name_to_set:
                continue

            switchd_ethmode_value_to_set = str(self._ethmode_map.get(switchd_ethmode_name_to_set, '0'))
            logger.debug('Port %s: set ethmode: %s->%s, value: %s' % (linux_intf, switchd_ethmode_name,
                                                                     switchd_ethmode_name_to_set,
                                                                     switchd_ethmode_value_to_set))

            self._switchd_ethmode_cache[linux_intf] = None

            try:
                ethmode = open('/cumulus/switchd/config/interface/%s/ethtool_mode'
                              % linux_intf, 'w')
                ethmode.write(switchd_ethmode_value_to_set)
                ethmode.close()
            except SystemError as e:
                logger.info(str(e))
                return
            except IOError as e:
                if not (hasattr(e, 'errno') and e.errno in (errno.ENOENT, errno.ENOTCONN, errno.ECONNABORTED,
                                                            errno.EINVAL, errno.ENXIO)):
                    (exc_type, exc_value, exc_traceback) = sys.exc_info()
                    logger.exception(traceback.format_tb(exc_traceback))
                return
            except Exception as e:
                (exc_type, exc_value, exc_traceback) = sys.exc_info()
                logger.exception(traceback.format_tb(exc_traceback))
                return

    ethmode = property(_get_ethmode, _set_ethmode)

    def _breakout_ethmode(self, mode):
        # mode is a text ethmode. return a text ethmode
        if mode not in self._breakout_ethmode_map:
            log_throttle('No breakout mode defined for %s' % mode)
            return mode
        else:
            breakout_mode = self._breakout_ethmode_map[mode].breakout(self.num_lanes)
            if breakout_mode != mode:
                logger.debug('Port %s: Using breakout mode (%s -> %s) with %d lanes' %
                             (self.port_num, mode, breakout_mode, self.num_lanes))
            return breakout_mode

    def compare_ethmode_ifmode(self, ethcode_name, sim_init=False):

        # breakout_ethmode_map assumes text, switchd assumes code
        # but _get_ethmode can't return a text value at this time,
        # so operate with text due to breakout map, and translate it for switchd

        if ethcode_name in self._ethmode_equivalence_map:
            logger.debug('Port %s, module ethmode: Substituting compliance code: %s -> %s' %
                        (self.port_num, ethcode_name, self._ethmode_equivalence_map[ethcode_name]))
            ethcode_name = self._ethmode_equivalence_map[ethcode_name]
        ethcode_name = self._breakout_ethmode(ethcode_name)
        if sim_init:
            switchd_ethmode_name = 'unknown'
        else:
            switchd_ethmode_name = self._ethmode_equivalence_map.get(self.ethmode, self.ethmode)
        # breakout also needs to use equivalence_map
        ethcode_name = self._ethmode_equivalence_map.get(ethcode_name, ethcode_name)
        logger.debug('Port %s: switchd ethmode: %s, calculated ethmode: %s' % (
            self.port_num, switchd_ethmode_name, ethcode_name))

        switchd_value = str(self._ethmode_map.get(switchd_ethmode_name, '0'))
        ethcode_value = str(self._ethmode_map.get(ethcode_name, '0'))
        changed_ethmode = False
        if switchd_value != ethcode_value:
            changed_ethmode = ethcode_name

        # For external phys software RX loss of signal detection is handled by
        # the phy and the internal phy ifmode is statically set by
        # update-ports.
        changed_ifmode = False
        if self.phyless:
            if ethcode_name in self._compliance_map:
                compliance_ifmode = self._compliance_map[ethcode_name]
                self.swrxlos = compliance_ifmode
                if compliance_ifmode != self.ifmode:
                    changed_ifmode = compliance_ifmode
            else:
                log_throttle("WARNING: Unknown interface type %s" % ethcode_name)

        return changed_ethmode, changed_ifmode

    def comply(self, ethcode_name):
        self.ethmode = ethcode_name
        if self.phyless:
            compliance_ifmode = self._compliance_map.get(ethcode_name, None)
            if compliance_ifmode:
                logger.debug('Setting ifmode: %s' % compliance_ifmode)
                self.ifmode = compliance_ifmode

    def ifmode2iftype(self, ifmode):
        switchd_mode = self._ifmode2switchd(ifmode, self.num_lanes, None)
        return self._iftype_map[switchd_mode]

    def iftype(self, compliance_code):
        if compliance_code in self._compliance_map:
            compliance_ifmode = self._compliance_map[compliance_code]
        else:
            log_throttle("WARNING: Unknown interface type %s" % compliance_code)
            compliance_ifmode = self.ifmode
        return self.ifmode2iftype(compliance_ifmode)

class UnknownPlatform(RuntimeError):
    pass

class PortWD(object):
    '''
    Base class for the portwd daemon
    '''
    def config(self):
        raise NotImplementedError()

    def run_once(self):
        raise NotImplementedError()

    def run(self, oneshot=False, init=False):
        if oneshot:
            logger.info('started, one shot for %d ports' % len(self.ports))
            self.run_once(init=init)
        else:
            logger.info('started, watching %d ports' % len(self.ports))
            while True:
                time.sleep(self.poll_interval)
                self.run_once()

        return True

class PortWDIdle(PortWD):
    ports = tuple()
    poll_interval = 1024
    def run_once(self):
        return True

class Cable(object):
    def __init__(self, oui, pn):
        self.oui = oui
        self.pn = pn

    def match(self, ouipn):
        return ouipn == ('%s,%s' % (self.oui, self.pn))


class Sysfs_cache(object):

    def __init__(self):
        self.cached_fields = {}
        self.porttab_logical = None
        self.porttab_static = None
        self.porttab_logical_retry_time = [0.5, 3.0, 10.0, 15.0]

    def clear_sysfs_cache(self):
        for key in self.cached_fields:
            self.cached_fields[key] = None

        self.porttab_logical = None
        self.porttab_static = None

    def cached_logical_porttab(self):
        if self.porttab_logical is None:
            success = False
            for retry_time in self.porttab_logical_retry_time:
                try:
                    self.porttab_logical = cumulus.porttab.porttab(use_hw=True)
                    success = True
                    break
                except (cumulus.porttab.BCMShellError, RuntimeError) as e:
                    logger.debug('bcm porttab error: {}'.format(e))
                    logger.warn('show portmap timed out. Retrying in %1.2f seconds' % retry_time)
                    time.sleep(retry_time)

            if not success:
                try:
                    self.porttab_logical = cumulus.porttab.porttab(use_hw=True)
                except (cumulus.porttab.BCMShellError, RuntimeError) as e:
                    logger.info('bcm porttab error: {}'.format(e))
                    logger.error('No response from BCM in %d attempts' % (len(self.porttab_logical_retry_time) + 1))
                    raise BCMNotAvailable

        return self.porttab_logical

    def cached_static_porttab(self):
        if self.porttab_static is None:
            self.porttab_static = cumulus.porttab.porttab(use_hw=False)

        return self.porttab_static


class TxSettings(object):

    def __init__(self):
        self.txfir_settings = {}

    def load_settings_from_json(self, platform, dirname='/etc/bcm.d/txfir/'):

        platform_name = '_'.join(platform.split(','))
        for (root, dirs, files) in os.walk(dirname, topdown=True):
            for filename in files:
                logger.debug('Checking filename: {}'.format(filename))
                if filename.startswith(platform_name) and 'txfir' in filename:
                    basename = filename.split('.')[0]
                    if basename.split('_')[-1].lower() == 'base':
                        speed_type_length = 'base'
                    else:
                        speed = basename.split('_')[-3].lower()
                        type = basename.split('_')[-2].lower()
                        length = basename.split('_')[-1].lower()
                        speed_type_length = '_'.join([speed, type, length])
                    # Read in multiple tables and insert them with unique keys in one dct
                    logger.debug('Reading in TxFir setting for {}'.format(speed_type_length))
                    self.txfir_settings[speed_type_length] = self.read_json_file(filename, root)

    def read_json_file(self, json_filename, dirname=None):
        data = self.read_data_file(json_filename, dirname)
        # this pulls out the whole file as one dct, or list.
        # It assumes the calling function parses the struct as needed
        if data:
            return json.loads(data)
        else:
            return None

    def read_data_file(self, filename, dirname=None):
        if dirname:
            filename = os.path.join(dirname, filename)
        try:
            logger.debug('Reading file: {}'.format(filename))
            with open(filename, 'r') as db_file:
                return db_file.read()
        except IOError as ex:
            # logger.warn('Failed to open {0}: {1}'.format(filename, ex.strerror))
            return None


class PortWDRun(PortWD):
    '''
    portwd - port watch event daemon
    '''
    poll_interval = 5 # seconds
    port_reset_timing = 0.1 # seconds to hold reset low
    BLACKLIST_PLATFORM_CABLE_MAP = { 'quanta,ly9_rangeley' : (Cable('00:26:1f', '7123-402-15'),) }

    def __init__(self, configfile='/etc/cumulus/portwd.conf',
                 builtin_configfile='/usr/share/cumulus/portwd.conf',
                 rj45_control=False,
                 sim_init=False):
        self._enable_ifmode = True
        self._enable_rj45_checks = True
        self._enable_clobber_lpmod = False
        self.presence_filename = '/var/cache/cumulus/port_presence'
        self.reset_filename = '/var/cache/cumulus/port_reset'
        self.tx_disable_filename = '/var/cache/cumulus/port_tx_disable'
        self._state_reset = {}
        self._state_tx_disable = {}
        self._blacklist_mv88e1111_rev1 = False
        self._force_num_ports = None
        self._enable_rate_select = False
        self._enable_pre_emphasis_control = False
        self._enable_cdr_bypass = True
        self._clear_reset_lpmod_on_insert = False
        self._enable_reset_tx_disable_cache = True
        self._cdr_skip_cache = {}
        self._blacklist = tuple()
        self.sim_init = sim_init
        self.rj45_control = rj45_control
        self.rj45_1000baset_port_cache = []
        self._cdr_map = {
            10000: 'off',
            40000: 'off',
            100000: 'on'
        }
        if not smbus_available:
            logger.warning('python-smbus not available, RJ45 SFP module link'
                           'status will not be accurate')

        self.ports = tuple()
        self._platform_detect()
        self._read_config(builtin_configfile, verbose=False)
        self._read_config(configfile)
        self._setup_hooks()

    def _read_config(self, configfile, verbose=True):
        if not os.path.isfile(configfile):
            return

        config = ConfigParser.SafeConfigParser()
        # Make options case sensitive
        config.optionxform = str
        configbuf = open(configfile).read()
        configfp = StringIO.StringIO(configbuf.replace(':','%%3A'))
        config.readfp(configfp)

        try:
            cable_overrides = dict(config.items('cables'))
        except ConfigParser.NoSectionError:
            cable_overrides = {}

        try:
            port_overrides = dict(config.items('ports'))
        except ConfigParser.NoSectionError:
            port_overrides = {}

        try:
            platform_blacklist = dict(config.items('platform_blacklist'))
        except ConfigParser.NoSectionError:
            platform_blacklist = {}

        if len(cable_overrides):
            override = { k.replace('%%3A', ':') : (v,) for (k, v) in cable_overrides.items() }

            if verbose:
                for cable, setting in override.items():
                    logger.info('config file overrides cable settings:')
                    logger.info('%s -> %s' % (cable, setting[0]))

            for port in self.ports:
                port.driver.cable.override_ethcodes(override)

        if len(port_overrides):
            if verbose:
                logger.info('config file overrides port settings:')

                for (label, setting) in port_overrides.items():
                    logger.info('%s -> %s' % (label, setting))

            for port in self.ports:
                if port_overrides.has_key(port.label):
                    port.driver.cable.override_ethcodes(
                        { 'port' : (port_overrides[port.label],) })

        if len(platform_blacklist):
            for (blplatform, ouipn) in platform_blacklist.items():
                if self.platform == blplatform:
                    self._blacklist += (ouipn.replace('%3A',':'),)

            if len(self._blacklist) and verbose:
                logger.info('config file blacklists cables:')
                for blitem in self._blacklist:
                    logger.info(blitem)

    def _smallstonexp_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=CelSmallstoneXPQSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2'))
                       for x in range(32) ])

    def _smallstonexpb_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=CelSmallstoneXPBQSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2'))
                       for x in range(32) ])

    def _redstonexp_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=CelRedstoneXPSFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=CelRedstoneXPQSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2'))
                       for x in range(49,55) ])

    def _redstonexpb_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=CelRedstoneXPBSFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=CelRedstoneXPBQSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2'))
                       for x in range(49,55) ])

    def _redstonev_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=CelRedstoneVSFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=CelRedstoneVQSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(49,53) ])

    def _e1031_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=CelE1031SFPDriver(sfp_num=(x - 48), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Helix4'))
                       for x in range(49,53) ])

    def _as4222_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AS4222SFPDriver(sfp_num=(x - 24), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Hurricane2'))
                       for x in range(25,29) ])

    def _as5712_ports(self):
        # TODO: Need to map these as the silkscreen has changed.
        return tuple([ Port(label='%s' % x,
                            driver=AS5712SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=AS5712QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2'))
                       for x in range(49,55) ])

    def _as5812_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AS5812QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2Plus'))
                       for x in range(49,55) ])

    def _as5812_54x_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AS5712SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=AS5712QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2Plus'))
                       for x in range(49,55) ])

    def _as5835_54x_ports(self):
        base1 = '/sys/devices/pci0000:00/0000:00:12.0/i2c-1/i2c-11/11-0061/sfp_38_1'
        base2 = '/sys/devices/pci0000:00/0000:00:12.0/i2c-1/i2c-11/11-0062/sfp_48_39'
        base3 = '/sys/devices/pci0000:00/0000:00:12.0/i2c-1/i2c-11/11-0062/qsfp_54_49'
        return tuple([ Port(label='%s' % x,
                            driver=AS5835SFPDriver(base1, control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Trident3X5'))
                       for x in range(1,39) ] +
                     [ Port(label='%s' % x,
                            driver=AS5835SFPDriver(base2, control_bit=(x - 39), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Trident3X5'))
                       for x in range(39,49) ] +
                     [ Port(label='%s' % x,
                            driver=AS5835QSFPDriver(base3, control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X5'))
                       for x in range(49,55) ])

    def _as5835_54t_ports(self):
        base3 = '/sys/devices/pci0000:00/0000:00:12.0/i2c-1/i2c-11/11-0062/qsfp_54_49'
        return tuple([ Port(label='%s' % x,
                            driver=AS5835QSFPDriver(base3, control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X5'))
                       for x in range(49,55) ])

    def _as5912_54x_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AS5912SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=AS5912QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='QumranMX'))
                       for x in range(49,55) ])

    def _as5916_54xl_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AcctonAS5916SFPDriver(control_bit=(x), range='7_0', port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX', serdes_lane0=x + 1))
                       for x in range(0,8) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS5916SFPDriver(control_bit=(x - 8), range='15_8', port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX', serdes_lane0=x + 1))
                       for x in range(8,16) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS5916SFPDriver(control_bit=(x - 16), range='23_16', port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX', serdes_lane0=x + 1))
                       for x in range(16,24) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS5916SFPDriver(control_bit=(x - 24), range='31_24', port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX', serdes_lane0=x + 1))
                       for x in range(24,32) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS5916SFPDriver(control_bit=(x - 32), range='39_32', port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX', serdes_lane0=x + 1))
                       for x in range(32,40) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS5916SFPDriver(control_bit=(x - 40), range='47_40', port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX', serdes_lane0=x + 1))
                       for x in range(40,48) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS5916QSFPDriver(control_bit=(x - 48), range='5_0', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='QumranMX', serdes_lane0=x + 1))
                       for x in range(48,54) ])

    def _as6712_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=AS6712QSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2'))
                       for x in range(32) ])

    def _as6812_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=AS6712QSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2Plus'))
                       for x in range(32) ])

    def _as7312_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AS7312SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Tomahawk'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=AS7312QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk'))
                       for x in range(49,55) ])

    def _as7312_54xs_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AS7312SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=AS7312QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(49,55) ])

    def _as7326_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AS7326SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X7'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=AS7312QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(49,57) ])

    def _ne0152t_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=LenovoNE0152TSFPDriver(sfp_num=(x - 49), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Helix4'))
                       for x in range(49,53) ])

    def _ne2572_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=LenovoNE2572SFPDriver(control_bit=(x - 1), range='1_8', port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(1,9) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE2572SFPDriver(control_bit=(x - 9), range='9_16', port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(9,17) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE2572SFPDriver(control_bit=(x - 17), range='17_24', port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(17,25) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE2572SFPDriver(control_bit=(x - 25), range='25_32', port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(25,33) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE2572SFPDriver(control_bit=(x - 33), range='33_40', port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(33,41) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE2572SFPDriver(control_bit=(x - 41), range='41_48', port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(41,49) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE2572QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(49,55) ])
 
    def _ne2580_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=LenovoNE2580SFPDriver(port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X7'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE2580QSFPDriver(port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(49,57) ])

    def _ne10032_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=LenovoNE10032QSFPDriver(control_bit=(x - 1), range='1_8', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(1,9) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE10032QSFPDriver(control_bit=(x - 9), range='9_16', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(9,17) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE10032QSFPDriver(control_bit=(x - 17), range='17_24', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(17,25) ] +
                     [ Port(label='%s' % x,
                            driver=LenovoNE10032QSFPDriver(control_bit=(x - 25), range='25_32', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(25,33) ])

    def _ly6_ports(self, base):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=QuantaLY6QSFPDriver(base, control_bit=x,
                                                       port=x + 1,
                                                       reset='qsfp1_reset',
                                                       lp_mode='qsfp1_lp_mode',
                                                       present='qsfp1_present'),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2'))
                       for x in range(16) ] +
                     [ Port(label='%s' % (x + 1),
                            driver=QuantaLY6QSFPDriver(base,
                                                       control_bit=(x-16),
                                                       port=x + 1,
                                                       reset='qsfp2_reset',
                                                       lp_mode='qsfp2_lp_mode',
                                                       present='qsfp2_present'),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2'))
                       for x in range(16,32) ])

    def _ly6_rangeley_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:1f.3'
        return self._ly6_ports(base)

    def _ly7_rglbmc_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:1f.3/i2c-0'
        return tuple([ Port(label='%s' % x,
                            driver=QuantaLY7SFPDriver(base + '/i2c-10/10-0038/sfp_1_16', control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Maverick'))
                       for x in range(1,17) ] +
                     [ Port(label='%s' % x,
                            driver=QuantaLY7SFPDriver(base + '/i2c-11/11-0038/sfp_17_32', control_bit=(x - 17), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Maverick'))
                       for x in range(17,33) ] +
                     [ Port(label='%s' % x,
                            driver=QuantaLY7SFPDriver(base + '/i2c-12/12-0038/sfp_33_48', control_bit=(x - 33), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Maverick'))
                       for x in range(33,49) ] +
                     [ Port(label='%s' % x,
                            driver=QuantaLY7QSFPDriver(port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(49,53) ])

    def _ly8_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=QuantaLY8QSFPDriver(port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2'))
                       for x in range(49,51) ] +
                     [ Port(label='%s' % x,
                            driver=QuantaLY8QSFPDriver(port=x),
                            config=BCMConfig(x, 'QSFP+', phyless=False, chip='Trident2'))
                       for x in range(51,53) ])

    def _ly9_rangeley_ports(self):
        return tuple([ Port(label='%s' % x, driver=QuantaLY9RangeleyQSFPDriver(port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2'))
                       for x in range(49,53) ] +
                     [ Port(label='%s' % x,
                            driver=EEPROMOnlyPortDriver(SysFSPortDriver._find_eeprom(x),
                                                        name='port%d' % x),
                            config=BCMConfig(x, 'QSFP+', phyless=False, chip='Trident2'))
                       for x in range(53,55) ])

    def _s6000_ports(self):
        # account for silk-screen changes in cpld control bits
        control_bit_map = (1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,17,16,
                           19,18,21,20,23,22,25,24,27,26,29,28,31,30)
        return tuple([ Port(label='%s' % (x + 1),
                            driver=DellS6000QSFPDriver(control_bit=control_bit_map[x],
                                                       port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2'))
                       for x in range(32) ])

    def _s6010_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=DellS6010QSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP+', chip='Trident2Plus'))
                       for x in range(32) ])

    def _s3000_ports(self, driver_class):
        control_bit_map = (1,0,3,2)
        return tuple([ Port(label='%s' % x,
                            driver=driver_class(control_bit=control_bit_map[x - 49],
                                                port=x),
                            config=BCMConfig(x, 'SFP+', chip='Trident'))
                       for x in range(49,53) ])

    def _s4000_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS4000SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=DellS4000QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2'))
                       for x in range(49,55) ])

    def _s4048t_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS4048TQSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2Plus'))
                       for x in range(49,55) ])

    def _s4128f_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS41xxSFPDriver(device='10-0032', range='16_1', control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Maverick'))
                       for x in range(1,15) ] +
                     [ Port(label='%s' % x,
                           driver=DellS41xxSFPDriver(device='10-0032', range='16_1', control_bit=(x - 1), port=x),
                           config=BCMConfig(x, 'SFP+', chip='Maverick', serdes_lane0=(((x - 3) & ~0x3) + 3)))
                       for x in range(15, 17)] +
                     [ Port(label='%s' % x,
                           driver=DellS41xxSFPDriver(device='10-0033', range='24_17', control_bit=(x - 17), port=x),
                           config=BCMConfig(x, 'SFP+', chip='Maverick', serdes_lane0=(((x - 3) & ~0x3) + 3)))
                       for x in range(17, 23)] +
                     [ Port(label='%s' % x,
                           driver=DellS41xxSFPDriver(device='10-0033', range='24_17', control_bit=(x - 17), port=x),
                           config=BCMConfig(x, 'SFP+', chip='Maverick'))
                       for x in range(23, 25)] +
                     [ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(25,27) ] +
                     [ Port(label='%s' % x,
                           driver=DellS41xxSFPDriver(device='10-0033', range='30_27', control_bit=(x - 27), port=x),
                           config=BCMConfig(x, 'SFP+', chip='Maverick', serdes_lane0=(((x - 3) & ~0x3) + 3)))
                       for x in range(27, 31) ])

    def _s4128t_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(25,27) ])

    def _s4148f_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS41xxSFPDriver(device='10-0032', range='16_1', control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Maverick'))
                       for x in range(1,17) ] +
                     [ Port(label='%s' % x,
                           driver=DellS41xxSFPDriver(device='10-0033', range='24_17', control_bit=(x - 17), port=x),
                           config=BCMConfig(x, 'SFP+', chip='Maverick'))
                       for x in range(17, 25)] +
                     [ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(25,27) ] +
                     [ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Maverick'))
                       for x in range(27,29) ] +
                     [ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(29,31) ] +
                     [ Port(label='%s' % x,
                           driver=DellS41xxSFPDriver(device='10-0033', range='54_31', control_bit=(x - 31), port=x),
                           config=BCMConfig(x, 'SFP+', chip='Maverick', serdes_lane0=(((x - 3) & ~0x3) + 3)))
                       for x in range(31, 55) ])

    def _s4148t_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(25,27) ] +
                     [ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Maverick'))
                       for x in range(27,29) ] +
                     [ Port(label='%s' % x,
                            driver=DellS41xxQSFPDriver(control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Maverick'))
                       for x in range(29,31) ])

    def _s5048f_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS5048FSFPDriver(device='24-003e', range='20_1', control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(1, 21) ] +
                     [ Port(label='%s' % x,
                            driver=DellS5048FSFPDriver(device='25-003e', range='37_21', control_bit=(x - 21), port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(21, 38) ] +
                     [ Port(label='%s' % x,
                            driver=DellS5048FSFPDriver(device='26-003e', range='48_38', control_bit=(x - 38), port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(38, 49) ] +
                     [ Port(label='%s' % x,
                            driver=DellS5048FQSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(49, 55) ])

    def _s4248fbl_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellS4248FBLSFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='QumranMX'))
                       for x in range(1, 41) ] +
                     [ Port(label='%s' % x,
                            driver=DellS4248FBLQSFPDriver(control_bit=(x - 41), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='QumranMX'))
                       for x in range(41, 43) ] +
                     [ Port(label='%s' % x,
                            driver=DellS4248FBLQSFPDriver(control_bit=(x - 43), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='QumranMX'))
                       for x in range(43, 49) ])

    def _n3248pxe_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=DellN3248PXESFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(49, 53) ] +
                     [ Port(label='%s' % x,
                            driver=DellN3248PXEQSFPDriver(control_bit=(x - 53), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X5'))
                       for x in range(53, 55) ])

    def _s5212f_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:0c.0/0000:03:00.0'
        return tuple([ Port(label='%s' % x, driver=DellS52xxFSFPDriver(base, port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(1, 13) ] +
                     [ Port(label='%s' % x, driver=DellS52xxFQSFPDriver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X5'))
                       for x in range(13, 16) ])

    def _s5224f_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:0c.0/0000:04:00.0'
        return tuple([ Port(label='%s' % x, driver=DellS52xxFSFPDriver(base, port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(1, 25) ] +
                     [ Port(label='%s' % x, driver=DellS52xxFQSFPDriver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X5'))
                       for x in range(25, 29) ])

    def _s5232f_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:0c.0/0000:04:00.0'
        return tuple([ Port(label='%s' % x, driver=DellS52xxFQSFPDriver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(1, 33) ])

    def _s5248f_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:0c.0/0000:04:00.0'
        return tuple([ Port(label='%s' % x, driver=DellS52xxFSFPDriver(base, port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X7'))
                       for x in range(1, 49) ] +
                     [ Port(label='%s' % x, driver=DellS52xxFQSFPDriver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in (49,52,53,54,55,56) ])

    def _s5296f_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:0c.0/0000:04:00.0'
        return tuple([ Port(label='%s' % x, driver=DellS52xxFSFPDriver(base, port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X7'))
                       for x in range(1, 97) ] +
                     [ Port(label='%s' % x, driver=DellS52xxFQSFPDriver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(97, 105) ])

    def _z9100_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=DellZ9100ZQSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk'))
                       for x in range(32) ])
    
    def _z9264f_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=DellZ9264QSFPDriver(control_bit=0, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk2'))
                       for x in range(64) ])

    def _ag9032v1_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=DeltaAG9032V1QSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk'))
                       for x in range(32) ])

    def _ag9032v2_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=DeltaAG9032V2QSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Trident3X7'))
                       for x in range(32) ])

    def _ag7648_ports(self):
        return tuple([Port(label='%s' % x,
                            driver=DeltaAG7648SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x))
                       for x in range(1, 49)] +
                     [Port(label='%s' % x,
                            driver=DeltaAG7648QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP+', chip='Trident2'))
                       for x in range(49, 55)])

    def _ag5648v1_ports(self):
        return tuple([Port(label='%s' % x,
                            driver=DeltaAG5648V1SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(1, 49)] +
                     [Port(label='%s' % x,
                            driver=DeltaAG5648V1QSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(49, 55)])

    def _agv848v1_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:12.0/i2c-1/i2c-17/'
        return tuple([Port(label='%s' % x,
                            driver=DeltaAGV848V1SFP_8_1_Driver(base, control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(1, 9)] +
                     [Port(label='%s' % x,
                            driver=DeltaAGV848V1SFP_16_9_Driver(base, control_bit=(x - 9), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(9, 17)] +
                     [Port(label='%s' % x,
                            driver=DeltaAGV848V1SFP_24_17_Driver(base, control_bit=(x - 17), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(17, 25)] +
                     [Port(label='%s' % x,
                            driver=DeltaAGV848V1SFP_32_25_Driver(base, control_bit=(x - 25), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(25, 33)] +
                     [Port(label='%s' % x,
                            driver=DeltaAGV848V1SFP_40_33_Driver(base, control_bit=(x - 33), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(33, 41)] +
                     [Port(label='%s' % x,
                            driver=DeltaAGV848V1SFP_48_41_Driver(base, control_bit=(x - 41), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X5'))
                       for x in range(41, 49)] +
                     [Port(label='%s' % x,
                            driver=DeltaAGV848V1QSFPDriver(base, control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X5'))
                       for x in range(49, 57)])

    def _seastone_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=CelSeastoneZQSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk'))
                       for x in range(32) ])

    def _seastone2_2core_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:10.0/0000:02:00.0'
        return tuple([ Port(label='%s' % x,
                            driver=CelSea2Que2QSFP28Driver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(1,33)])

    def _seastone2_4core_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:10.0/0000:03:00.0'
        return tuple([ Port(label='%s' % x,
                            driver=CelSea2Que2QSFP28Driver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(1,33)])

    def _questone_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=CelQuestoneSFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='TomahawkPlus'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=CelQuestoneQSFPDriver(control_bit=(x - 49), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='TomahawkPlus'))
                       for x in range(49,55) ])

    def _questone2_2core_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:10.0/0000:02:00.0'
        return tuple([ Port(label='%s' % x,
                            driver=CelQuestone2SFP28Driver(base, port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X7'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=CelSea2Que2QSFP28Driver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(49,57) ])

    def _questone2_4core_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:10.0/0000:03:00.0'
        return tuple([ Port(label='%s' % x,
                            driver=CelQuestone2SFP28Driver(base, port=x),
                            config=BCMConfig(x, 'SFP28', chip='Trident3X7'))
                       for x in range(1,49) ] +
                     [ Port(label='%s' % x,
                            driver=CelSea2Que2QSFP28Driver(base, port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(49,57) ])

    def _as7712_ports(self):
        return tuple([Port(label='%s' % (x + 1),
                           driver=AcctonAS7712QSFPDriver(control_bit=x, port=x + 1),
                           config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk'))
                      for x in range(32)])
    
    def _as7726_ports(self):
        return tuple([Port(label='%s' % (x + 1),
                           driver=AcctonAS7726QSFPDriver(control_bit=x, port=x + 1),
                           config=BCMConfig(x + 1, 'QSFP28', chip='Trident3X7'))
                      for x in range(32)])

    def _as7816_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 1), range='8_1', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(1,9) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 9), range='16_9', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(9,17) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 17), range='24_17', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(17,25) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 25), range='32_25', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(25,33) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 33), range='40_33', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(33,41) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 41), range='48_41', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(41,49) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 49), range='56_49', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(49,57) ] +
                     [ Port(label='%s' % x,
                            driver=AcctonAS7816QSFPDriver(control_bit=(x - 57), range='64_57', port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk2'))
                       for x in range(57,65) ])

    def _ix1_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:1f.3'
        return tuple([ Port(label='%s' % (x + 1),
                            driver=QuantaIX1QSFPDriver(base, control_bit=x, port=x + 1,
                                                       reset='ix1_qsfp1_reset',
                                                       lp_mode='ix1_qsfp1_lp_mode',
                                                       present='ix1_qsfp1_present'),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk'))
                       for x in range(16) ] +
                     [ Port(label='%s' % (x + 1),
                            driver=QuantaIX1QSFPDriver(base, control_bit=(x-16), port=x + 1,
                                                       reset='ix1_qsfp2_reset',
                                                       lp_mode='ix1_qsfp2_lp_mode',
                                                       present='ix1_qsfp2_present'),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk'))
                       for x in range(16,32) ])

    def _ix2_ports(self):
        return tuple([ Port(label='%s' % x,
                            driver=QuantaIX2SFPDriver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP28', chip='Tomahawk'))
                       for x in range(1, 49)] +
                     [ Port(label='%s' % x,
                            driver=QuantaIX2QSFPDriver(port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Tomahawk'))
                       for x in range(49,57) ])

    def _ix7_rglbmc_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:1f.3/i2c-0'
        return tuple([ Port(label='%s' % x,
                            driver=QuantaIX7QSFP_1_16_Driver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(1,17) ] +
		     [ Port(label='%s' % x,
                            driver=QuantaIX7QSFP_17_32_Driver(control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(17,33) ])

    def _ix8_rglbmc_ports(self):
        base = '/sys/devices/pci0000:00/0000:00:1f.3/i2c-0'
        return tuple([ Port(label='%s' % x,
                            driver=QuantaIX8SFPDriver(base + '/i2c-14/14-0038/sfp28_1_16', control_bit=(x - 1), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Trident3X7'))
                       for x in range(1,17) ] +
                     [ Port(label='%s' % x,
                            driver=QuantaIX8SFPDriver(base + '/i2c-15/15-0038/sfp28_17_32', control_bit=(x - 17), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Trident3X7'))
                       for x in range(17,33) ] +
                     [ Port(label='%s' % x,
                            driver=QuantaIX8SFPDriver(base + '/i2c-16/16-0038/sfp28_33_48', control_bit=(x - 33), port=x),
                            config=BCMConfig(x, 'SFP+', chip='Trident3X7'))
                       for x in range(33,49) ] +
                     [ Port(label='%s' % x,
                            driver=QuantaIX8QSFPDriver(port=x),
                            config=BCMConfig(x, 'QSFP28', chip='Trident3X7'))
                       for x in range(49,57) ])

    def _wedge100_ports(self):
        return tuple([ Port(label='%s' % (x + 1),
                            driver=Wedge100QSFPDriver(control_bit=x, port=x + 1),
                            config=BCMConfig(x + 1, 'QSFP28', chip='Tomahawk'))
                       for x in range(32) ])

    def _minipack_ports(self):
        slots=8
        max_ports=16
        # SCM is in slot-1, while PIM is placed in slot 2-9 on Minipack.
        # So, make slot 1-based to get the actual PIM no.
        return tuple([ Port(label='m%sp%s' % (y,x),
                            driver=AcctonMinipackQSFPDriver(control_bit=(x - 1), slot=y-1, port=((y-2)*max_ports + x)),
                            config=BCMConfig(((y-2)*max_ports + x), 'QSFP28', label='m%sp%s' % (y,x), chip='Tomahawk3'), has_modules='true')
                       for y in range(2,slots+2) for x in range(1,max_ports+1) ])

    def _platform_detect(self):
        platform = subprocess.check_output('/usr/bin/platform-detect').strip()
        self.platform = platform

        if platform == 'cel,smallstone_xp':
            self.ports = self._smallstonexp_ports()
            logger.info('Celestica Smallstone XP detected')
        elif platform == 'cel,smallstone_xp_b':
            self.ports = self._smallstonexpb_ports()
            logger.info('Celestica Smallstone XP-B detected')
        elif platform == 'cel,redstone_xp':
            self.ports = self._redstonexp_ports()
            logger.info('Celestica Redstone XP detected')
        elif platform == 'cel,redstone_xp_b':
            self.ports = self._redstonexpb_ports()
            logger.info('Celestica Redstone XP-B detected')
        elif platform == 'cel,redstone_v':
            self.ports = self._redstonev_ports()
            logger.info('Celestica Redstone V detected')
        elif platform == 'cel,questone':
            self.ports = self._questone_ports()
            logger.info('Celestica Questone detected')
        elif platform == 'cel,questone_2':
            nproc = 2
            try:
                nproc = subprocess.check_output('/usr/bin/nproc').strip()
            except:
                logger.warn('unable to determine the number of CPU cores, assuming 2 cores')
            if nproc == '4':
                self.ports = self._questone2_4core_ports()
                logger.info('Celestica Questone2 with 4-core CPU detected')
            else: # default to '2'
                self.ports = self._questone2_2core_ports()
                logger.info('Celestica Questone2 detected')
        elif platform == 'quanta,ly8_rangeley':
            self._force_num_ports = 54
            self.ports = self._ly8_ports()
            logger.info('Quanta LY8 detected')
        elif platform == 'quanta,ly9_rangeley':
            self.ports = self._ly9_rangeley_ports()
            logger.info('Quanta LY9 Rangeley detected')
        elif platform == 'accton,as4222_28pe':
            self.ports = self._as4222_ports()
            logger.info('Accton AS4222-28PE detected')
        elif platform == 'accton,as5712_54x':
            self._enable_rate_select = True
            self.ports = self._as5712_ports()
            logger.info('Accton AS5712 detected')
        elif platform == 'accton,as5812_54t':
            self.ports = self._as5812_ports()
            logger.info('Accton AS5812 detected')
        elif platform == 'accton,as5835_54x':
            self.ports = self._as5835_54x_ports()
            logger.info('Accton AS5835-54X detected')
        elif platform == 'accton,as5835_54t':
            self.ports = self._as5835_54t_ports()
            logger.info('Accton AS5835-54T detected')
        elif platform == 'accton,as6712_32x':
            self.ports = self._as6712_ports()
            logger.info('Accton AS6712 detected')
        elif platform == 'accton,as7312_54x':
            self.ports = self._as7312_ports()
            logger.info('Accton AS7312 detected')
        elif platform == 'accton,as7312_54xs':
            self.ports = self._as7312_54xs_ports()
            logger.info('Accton AS7312_54xs detected')
        elif platform == 'accton,as7326_56xs':
            self.ports = self._as7326_56xs_ports()
            logger.info('Accton AS7326_56xs detected')
        elif platform == 'dell,s6000_s1220':
            self.ports = self._s6000_ports()
            logger.info('Dell S6000 detected')
        elif platform == 'dell,s3000_c2338':
            self.ports = self._s3000_ports(DellS3000SFPDriver)
            logger.info('Dell S3000 detected')
        elif platform == 'cel,e1031':
            self.ports = self._e1031_ports()
            logger.info('Celestica E1031 detected')
        elif platform == 'dell,s6010_c2538':
            self.ports = self._s6010_ports()
            logger.info('Dell S6010 detected')
        elif platform == 'dell,s4000_c2338':
            self.ports = self._s4000_ports()
            logger.info('Dell S4000 detected')
        elif platform == 'dell,s4048t_c2338':
            self.ports = self._s4048t_ports()
            logger.info('Dell S4048T detected')
        elif platform == 'dellemc,s4128f_c2338':
            self.ports = self._s4128f_ports()
            logger.info('Dell S4128F detected')
        elif platform == 'dellemc,s4128t_c2338':
            self.ports = self._s4128t_ports()
            logger.info('Dell S4128T detected')
        elif platform == 'dellemc,s4148f_c2338':
            self.ports = self._s4148f_ports()
            logger.info('Dell S4148F detected')
        elif platform == 'dellemc,s4148t_c2338':
            self.ports = self._s4148t_ports()
            logger.info('Dell S4148T detected')
        elif platform == 'dellemc,s5048f_c2538':
            self.ports = self._s5048f_ports()
            logger.info('Dell S5048F detected')
        elif platform == 'dellemc,s4248fbl_c2538':
            self.ports = self._s4248fbl_ports()
            logger.info('Dell S4248FBL detected')
        elif platform == 'dellemc,n3248pxe_c3338':
            self.ports = self._n3248pxe_ports()
            logger.info('Dell N3248PXE detected')
        elif platform == 'dellemc,s5212f_c3538':
            self.ports = self._s5212f_ports()
            logger.info('Dell S5212F detected')
        elif platform == 'dellemc,s5224f_c3538':
            self.ports = self._s5224f_ports()
            logger.info('Dell S5224F detected')
        elif platform == 'dellemc,s5232f_c3538':
            self.ports = self._s5232f_ports()
            logger.info('Dell S5232F detected')
        elif platform == 'dellemc,s5248f_c3538':
            self.ports = self._s5248f_ports()
            logger.info('Dell S5248F detected')
        elif platform == 'dellemc,s5296f_c3538':
            self.ports = self._s5296f_ports()
            logger.info('Dell S5296F detected')
        elif platform == 'dellemc,z9264f_c3538':
            self.ports = self._z9264f_ports()
            logger.info('Dell Z9264F detected')
        elif platform == 'lenovo,ne0152t':
            self.ports = self._ne0152t_ports()
            logger.info('Lenovo NE0152T detected')
        elif platform == 'lenovo,ne2572':
            self.ports = self._ne2572_ports()
            logger.info('Lenovo NE2572 detected')
        elif platform == 'lenovo,ne2580':
            self.ports = self._ne2580_ports()
            logger.info('Lenovo NE2580 detected')
        elif platform == 'lenovo,ne10032':
            self.ports = self._ne10032_ports()
            logger.info('Lenovo NE10032 detected')
        elif platform == 'quanta,ly6_rangeley':
            self.ports = self._ly6_rangeley_ports()
            logger.info('Quanta LY6 Rangeley detected')
        elif platform == 'quanta,ly7_rglbmc':
            self.ports = self._ly7_rglbmc_ports()
            logger.info('Quanta LY7 Rangeley with BMC detected')
        elif platform == 'dell,z9100_c2538':
            self._enable_pre_emphasis_control = True
            self.ports = self._z9100_ports()
            logger.info('Dell Z9100 detected')
        elif platform == 'delta,ag9032v1':
            self.ports = self._ag9032v1_ports()
            logger.info('Delta AG9032V1 detected')
        elif platform == 'delta,ag9032v2':
            self.ports = self._ag9032v2_ports()
            logger.info('Delta AG9032V2 detected')
        elif platform == 'delta,ag7648':
            self.ports = self._ag7648_ports()
            logger.info('Delta AG7648 detected')
        elif platform == 'delta,ag5648v1':
            self.ports = self._ag5648v1_ports()
            logger.info('Delta AG5648V1 detected')
        elif platform == 'delta,agv848v1':
            self.ports = self._agv848v1_ports()
            logger.info('Delta AGV848v1 detected')
        elif platform == 'cel,seastone':
            self.ports = self._seastone_ports()
            logger.info('Celestica Seastone detected')
        elif platform == 'cel,seastone_2':
            nproc = 2
            try:
                nproc = subprocess.check_output('/usr/bin/nproc').strip()
            except:
                logger.warn('unable to determine the number of CPU cores, assuming 2 cores')
            if nproc == '4':
                self.ports = self._seastone2_4core_ports()
                logger.info('Celestica Seastone2 with 4-core CPU detected')
            else: # default to '2'
                self.ports = self._seastone2_2core_ports()
                logger.info('Celestica Seastone2 detected')
        elif platform == 'accton,as7712_32x':
            self.ports = self._as7712_ports()
            logger.info('Accton AS7712 detected')
        elif platform == 'accton,as7726_32x':
            self.ports = self._as7726_ports()
            logger.info('Accton AS7726 detected')
        elif platform == 'accton,as7816_64x':
            self.ports = self._as7816_ports()
            logger.info('Accton AS7816-64X detected')
        elif platform == 'quanta,ix1_rangeley':
            self.ports = self._ix1_ports()
            logger.info('Quanta IX1 detected')
        elif platform == 'quanta,ix7_rglbmc':
            self.ports = self._ix7_rglbmc_ports()
            logger.info('Quanta IX7 Rangeley with BMC detected')
        elif platform == 'quanta,ix8_rglbmc':
            self.ports = self._ix8_rglbmc_ports()
            logger.info('Quanta IX8 Rangeley with BMC detected')
        elif platform == 'accton,wedge100_32x':
            self._enable_ifmode = True
            self.ports = self._wedge100_ports()
            logger.info('Accton Wedge100 detected')
        elif platform == 'accton,wedge100s_32x':
            self._enable_ifmode = True
            self.ports = self._wedge100_ports()
            logger.info('Accton Wedge100S detected')
        elif platform == 'accton,as5812_54x':
            self.ports = self._as5812_54x_ports()
            logger.info('Accton AS5812_54X detected')
        elif platform == 'accton,as5912_54x':
            self.ports = self._as5912_54x_ports()
            logger.info('Accton AS5912_54X detected')
        elif platform == 'accton,as5916_54xl':
            self.ports = self._as5916_54xl_ports()
            logger.info('Accton AS5916_54XL detected')
        elif platform == 'accton,as6812_32x':
            self.ports = self._as6812_ports()
            logger.info('Accton 6812 detected')
        elif platform == 'quanta,ix2_rangeley':
            self.ports = self._ix2_ports()
            logger.info('Quanta IX2 detected')
        elif platform == 'accton,minipack':
            self.ports = self._minipack_ports()
            self._enable_ifmode = False
            self._enable_rj45_checks = False
            self._enable_cdr_bypass = False
            self._clear_reset_lpmod_on_insert = True
            logger.info('Accton Minipack detected')

        self._multi_asic = False
        if platform in ('cel,pebble', 'cel,pebble_b', 'dni,3448p', 'dni,3048up',
                        'quanta,ly4r'):
            self._multi_asic = True

        if platform == 'dell,s4000_c2338':
            logger.info('Applying workaround for MV88E1111 rev1 phys')
            self._blacklist_mv88e1111_rev1 = True

        if platform != 'accton,minipack':
            self._discover_generic_ports()

        port_types = [ x.config.port_type for x in self.ports ]
        port_counts = { x : port_types.count(x) for x in port_types }
        logger.info('Discovered %d ports, %s' %
                    (len(self.ports),
                     ', '.join([ "%s: %d" % (k, v) for k, v in
                                 port_counts.items() ])))

    def _discover_generic_ports(self):
        generic_ports = []
        for root, dirs, files in os.walk('/sys/class/eeprom_dev'):
            for eeprom_dir in dirs:
                label = open(os.path.join(root, eeprom_dir, 'label'), 'r').read().strip()
                if not label.startswith('port'):
                    continue

                label = label[4:]
                portnum = int(label)

                if (self._force_num_ports is not None and
                    portnum > self._force_num_ports):
                    logger.warning('ignoring invalid port: swp%d' % portnum)
                    continue

                if label not in [ x.label for x in self.ports ]:
                    driver = EEPROMOnlyPortDriver(SysFSPortDriver._find_eeprom(portnum),
                                                  name=label)
                    port = Port(label=label, driver=driver, port=portnum,
                                config=BCMConfig(portnum))
                    generic_ports.append(port)

        if generic_ports:
            logger.debug('discovered %d generic ports' % len(generic_ports))
            self.ports = tuple(generic_ports + list(self.ports))

    @property
    def _switchd_pid(self):
        try:
            readypid = open('/var/run/switchd.ready').read().strip()
        except:
            readypid = 0

        return readypid

    def _setup_hooks(self):
        self._state_admin_up = {}
        self._state_proto_down = {}
        self._state_ethcodes = {}
        self._state_presence = [ False ] * len(self.ports)
        self._state_switchd_pid = 0

        self._init_hooks = []
        self._admin_up_hooks = []
        self._ethcodes_hooks = []
        self._presence_hooks = []
        self._generic_hooks = []

        if len(self.ports):
            if self._enable_rj45_checks:
                self._admin_up_hooks.append(self._rj45_phy_power_check)
                self._presence_hooks.append(self._rj45_1000baset_list)
                self._generic_hooks.append(self._rj45_phy_configure)
                # XXX - debugging
                # self._generic_hooks.append(self._rj45_link_status)
                if self.rj45_control:
                    self._generic_hooks.append(self._rj45_phy_power_check)

            self._admin_up_hooks.append(self._clear_no_link_resets)
            self._init_hooks.append(self._run_init_script)
            # a reset may clear the EEPROM so put this in generic hooks because all resets are done before they run
            self._generic_hooks.append(self.enable_high_power)

            if any([True for x in self.ports if any((x.driver.tx_retimer, x.driver.rx_retimer))]):
                self._generic_hooks.append(self._retimer_configure)

            if self._enable_ifmode:
                self._ethcodes_hooks.append(self._ifmode_hook)
                if self._enable_rj45_checks:
                    self._ethcodes_hooks.append(self._rj45_1000baset_list)

            if self._enable_cdr_bypass:
                self._generic_hooks.append(self._cdr_control_hook)

            if self._enable_clobber_lpmod:
                self._generic_hooks.append(self._clobber_lpmod)

            if len(self._blacklist) or self._blacklist_mv88e1111_rev1:
                self._presence_hooks.append(self._blacklist_hook)

            if self._enable_rate_select:
                self._generic_hooks.append(self._rate_select)

            if self._enable_pre_emphasis_control:
                tx_settings.load_settings_from_json(self.platform)

            if self._enable_reset_tx_disable_cache:
                self._generic_hooks.append(self._collect_reset_tx_disable_caches)

            self._presence_hooks.append(self._clear_no_link_resets)

            if self._clear_reset_lpmod_on_insert:
                self._presence_hooks.append(self._clear_reset_signal)
                self._presence_hooks.append(self._clear_lpmode_signal)

    def _poll_admin_up(self, port_filter=set(['empty'])):
        # returns all or filtered ports where administrative state has changed since the last poll
        changed = set()
        port_list = self.ports
        if port_filter != set(['empty']):
            port_list = [port for port in port_list if port in port_filter]

        for port in port_list:

            if not port.sysfs_ready:
                continue

            if port.admin_up != self._state_admin_up.setdefault(port.label, []):
                logger.debug('Admin state has changed for port %s' % port.label)
                changed.add(port)

            self._state_admin_up[port.label] = port.admin_up

        return changed

    def _poll_protodown(self, port_filter=set(['empty'])):
        # returns all or filtered ports where protodown state has changed since the last poll
        changed = set()
        port_list = self.ports
        if port_filter != set(['empty']):
            port_list = [port for port in port_list if port in port_filter]

        for port in port_list:

            if not port.sysfs_ready:
                continue

            if port.proto_down != self._state_proto_down.setdefault(port.label, []):
                logger.debug('Proto_down has changed for port %s to %s' % (port.label, port.proto_down))
                changed.add(port)

            self._state_proto_down[port.label] = port.proto_down

        return changed

    def _poll_ethcodes(self, init=False):
        # returns all ports where the cable ethcodes has changed since the last
        # poll
        changed = set()
        logger.debug('ethcodes poll')
        for port_index in range(len(self.ports)):
            port = self.ports[port_index]
            if port.label not in self._state_ethcodes:
                self._state_ethcodes[port.label] = False

            if port.eeprom_ready:
                for attempt in range(2):
                    try:
                        module_ethcodes = port.driver.cable.ethcodes
                    except EEPROMTimeout as e:
                        log_throttle('timeout reading ethcodes for port %s: %s.' %
                                    (port.label, str(e)), level='warning')
                        port.driver.recover_eeprom_bus()
                        #reset and retry
                        port.driver.reset = 1
                        time.sleep(0.5)
                        port.driver.reset = 0
                        time.sleep(0.5)
                    except InvalidTransceiverCode as e:
                        log_throttle('Invalid ethcode for port %s: %s. Skipping' %
                                     (port.label, str(e)), level='warning')
                        break
                    else:
                        break
                else:
                    continue

                if module_ethcodes is None:
                    logger.warning('unable to read ethcodes for port: %s' % port.label)

                logger.debug('%s, module ethcodes: %s state_ethcodes: %s' % (
                    port,   module_ethcodes, self._state_ethcodes[port.label]))
                if module_ethcodes != self._state_ethcodes[port.label]:
                    self._cdr_skip_cache[port.label] = (None, None)
                    if module_ethcodes:
                        if init:
                            logger.debug('cable change on %s to %s' %
                                         (str(self.ports[port_index]), ', '.join(module_ethcodes)))
                        else:
                            logger.info('cable change on %s to %s' %
                                         (str(self.ports[port_index]), ', '.join(module_ethcodes)))
                    else:
                        if init:
                            logger.debug('cable removed on %s' % str(self.ports[port_index]))
                        else:
                            logger.info('cable removed on %s' % str(self.ports[port_index]))

                    changed.add(self.ports[port_index])

            else:
                module_ethcodes = None

            self._state_ethcodes[port.label] = module_ethcodes

        return changed

    def _poll_presence(self, init=False):
        # returns all ports where the cable presence has changed since the last poll
        changed = set()
        logger.debug('presence poll')
        for port_index in range(len(self.ports)):
            port = self.ports[port_index]
            present = port.driver.present

            if present != self._state_presence[port_index]:
                self._cdr_skip_cache[port.label] = (None, None)
                if present:
                    if init:
                        logger.debug('cable inserted on %s' % str(self.ports[port_index]))
                    else:
                        logger.info('cable inserted on %s' % str(self.ports[port_index]))
                else:
                    if init:
                        logger.debug('cable removed on %s' % str(self.ports[port_index]))
                        self._rj45_1000baset_list_remove(port)
                    else:
                        logger.info('cable removed on %s' % str(self.ports[port_index]))
                        self._rj45_1000baset_list_remove(port)

                changed.add(self.ports[port_index])

            self._state_presence[port_index] = present

        if changed:
            presence_text = 'Port          Present\n'
            for port_index in range(len(self.ports)):
                port = self.ports[port_index]
                presence_text += '{:3s}           {}\n'.format(port.label, str(self._state_presence[port_index]))

            with open(self.presence_filename, 'w') as presence_file:
                presence_file.write(presence_text)

        return changed

    def _collect_reset_tx_disable_caches(self, ports):
        cache_changed = False
        for port_index in range(len(self.ports)):
            port = self.ports[port_index]
            current_reset = port.driver.reset
            if port.label not in self._state_reset or self._state_reset[port.label] != current_reset:
                cache_changed = True
                self._state_reset[port.label] = current_reset

            current_tx_disable = port.driver.tx_disable
            if port.label not in self._state_tx_disable or self._state_tx_disable[port.label] != current_tx_disable:
                cache_changed = True
                self._state_tx_disable[port.label] = current_tx_disable

            if self._state_reset[port.label] and cache_changed:
                log_throttle('Port: {} is in reset mode and may be disabled'.format(port.label), level='warning')

            if self._state_tx_disable[port.label] and cache_changed:
                log_throttle('Port: {} is in tx_disable mode and may be disabled'.format(port.label), level='warning')

        if cache_changed:
            self._write_reset_tx_disable_cache()

    def _write_reset_tx_disable_cache(self):
        qsfp_reset_text = 'Port          Reset\n'
        for port_index in range(len(self.ports)):
            port = self.ports[port_index]
            qsfp_reset_text += '{:3s}           {}\n'.format(port.label, str(self._state_reset[port.label]))

        with open(self.reset_filename, 'w') as reset_cache_file:
            reset_cache_file.write(qsfp_reset_text)

        sfp_tx_disable_text = 'Port          tx_disable\n'
        for port_index in range(len(self.ports)):
            port = self.ports[port_index]
            sfp_tx_disable_text += '{:3s}           {}\n'.format(port.label, str(self._state_tx_disable[port.label]))

        with open(self.tx_disable_filename, 'w') as tx_disable_cache_file:
            tx_disable_cache_file.write(sfp_tx_disable_text)

    def _ports_from_present_cache(self):
        module_present = set()
        for port_index in range(len(self.ports)):
            if self._state_presence[port_index]:
                module_present.add(self.ports[port_index])
        return module_present

    def _poll_switchd_pid(self):
        # returns all ports that have been affected by a switchd restart
        switchd_pid = self._switchd_pid

        try:
            if switchd_pid == 0:
                return set()

            if switchd_pid != self._state_switchd_pid:
                return set(self.ports)
            else:
                return set()
        finally:
            self._state_switchd_pid = switchd_pid

    def _call_hooks(self, ports, hooks):
        return [ hook(ports) for hook in hooks ]

    def _reset_ports(self, ports, verbose=False):
        reset = set()
        for port in ports:
            port.driver.reset = 1
            reset.add(port)

        time.sleep(self.port_reset_timing)

        for port in reset:
            port.driver.reset = 0

        if verbose and len(reset):
            logger.debug('reset: ' + ', '.join([ str(x) for x in reset ]))

    def _ifmode_config(self, ports):
        configs = []

        for port in ports:
            if (port.config is None or not port.config.phyless or
                port.config.bcm_interfaces is None):
                continue

            if not port.driver.present:
                compliance_code = '40g-cr4'
            else:
                try:
                    compliance_codes = port.driver.cable.ethcodes
                except EEPROMTimeout as e:
                    log_throttle('timeout reading ethcodes for port %s: %s.' %
                                 (port.label, str(e)), level='warning')
                    port.driver.recover_eeprom_bus()
                    compliance_codes = None
                except InvalidTransceiverCode as e:
                    log_throttle('invalid ethcode for port %s: %s.' %
                                 (port.label, str(e)), level='warning')
                    compliance_codes = None

                if not compliance_codes or len(compliance_codes) < 1:
                    compliance_code = '40g-cr4'
                else:
                    compliance_code = compliance_codes[0]

            for intf in port.config.bcm_interfaces:
                configs.append('serdes_if_type_%s=%d' %
                               (intf, port.config.iftype(compliance_code)))

        return '\n'.join(configs)

    def _shutdown(self, ports, verbose=False):
        target_ports = self.rj45_1000baset_sfp_ports(ports)

        rv = True
        for port in target_ports:
            if not self._rj45_stop(port):
                rv = False

        return rv

    def _probe_phy(self, bus):
        if not smbus_available:
            return None

        i2c = smbus.SMBus(bus)
        try:
            phy = MV88E1111Phy(i2c)
            if not phy.probe:
                log_throttle('unknown phy: 0x%04x 0x%04x' % phy.phyid)
                return None
        except IOError:
            log_throttle('failed reading 1000Base-T phy on bus: %s' % bus)
            return None

        return phy

    def _validate_phy(self, port, phy):
        if self._blacklist_mv88e1111_rev1 and phy.revision == '1':
            logger.error('MV88E1111 with revision 1 silicon detected in '
                         'port %s.  Disabling port.' % port.label)
            port.admin_down()
            port.disabled = True
            logger.info('1G BaseT in port %s not valid, removing from 1G BaseT list' % port.label)
            if port in self.rj45_1000baset_port_cache:
                self.rj45_1000baset_port_cache.remove(port)
            return False
        else:
            return True

    def _configure_phy(self, port):
        phy = self._probe_phy(port.driver.cable.eeprom.bus)
        if not phy:
            return

        if not self._validate_phy(port, phy):
            return

        needs_reset = False

        try:
            if 0 in port.speed or 100 in port.speed or 1000 in port.speed:
                logger.debug('_configure_phy: Port: %s. carrier: %s, admin: %s, protodown: %s' % (port.label, port.carrier, port.admin_up, port.proto_down))
                if not True in port.carrier and (any(port.admin_up) and not self.rj45_protodown_and_controlled(port)):
                    if port.no_link_resets() == 0:
                        logger.info('no carrier. Resetting port: %s' % port.label)
                        port.driver.toggle_reset()
                        phy.reset()
                        needs_reset = False

            if 100 in port.speed:
                if phy.sgmii_mode(port):
                    logger.info('set SGMII mode for port: %s' % port.label)
                    phy.reset()
                if phy.set_an('fiber', False):
                    logger.info('disable fiber autoneg for port: %s' % port.label)
                    needs_reset = True
                if phy.set_an('copper', False):
                    logger.info('disable copper autoneg for port: %s' % port.label)
                    needs_reset = True
                if phy.set_100fd():
                    logger.info('force 100M full-duplex for port: %s' % port.label)
                    needs_reset = True
            elif 0 in port.speed or 1000 in port.speed:
                if phy.gbic_noan_mode(port):
                    logger.info('set GBIC no-autoneg mode for port: %s' % port.label)
                    phy.reset()
                if phy.set_an('fiber', False):
                    logger.info('disable fiber autoneg for port: %s' % port.label)
                    needs_reset = True
                if phy.set_1000auto():
                    logger.info('set 1000 auto for port: %s' % port.label)
                    needs_reset = True
            else:
                logger.debug('rj45 port not in 100 or 1G mode: %s' % port.label)
                phy.set_power(False)
        except UnsupportedPhy, e:
            if phy.power:
                logger.warning(str(e))
                logger.warning('powering down port: %s' % port.label)
                phy.set_power(False)
                return

        if phy.enable_energy_detect_plus():
            logger.info('enabled power save on port: %s' % port.label)
            needs_reset = True

        if needs_reset:
            logger.info('settings changed, resetting phy on port: %s' % port.label)
            phy.reset()

    def _ports_check_eeprom(self, ports):
        for port in ports:
            if not port.eeprom_ready:
                logger.debug('port not eeprom ready: %s' % port.label)
                continue

            if port.driver.cable.check:
                yield port
            else:
                log_throttle('eeprom check failed for port: %s' % port.label)
                continue

    def _run_init_script(self, ports):
        # init file is assumed to be a standalone script with a proper shebang and execute permissions
        init_filename = '/etc/cumulus/portwd.init'
        if not os.path.isfile(init_filename):
            return
        logger.info('Running {} script'.format(init_filename))
        try:
            output = subprocess.check_output([init_filename], stderr=subprocess.STDOUT)
            if output:
                logger.info('portwd init script {} returned output: {}'.format(init_filename, output))
        except subprocess.CalledProcessError, e:
            logger.warn('portwd init script {} returned code: {}, output: {}'.format(init_filename, e.returncode, e.output))
        except Exception, e:
            logger.warn('portwd init script {} returned unexpected failure: {}'.format(init_filename, e))

    def rj45_1000baset_sfp_ports(self, ports):
        # Return the specified ports if they are in the cache of RJ45 1GBaseT ports on the box
        logger.debug('rj45 port cache: %s' % [port.label for port in self.rj45_1000baset_port_cache if port != 'empty'])
        if not self.rj45_1000baset_port_cache:
            self._rj45_1000baset_list(ports)

        return [baset_port for baset_port in self.rj45_1000baset_port_cache if baset_port in ports]

    def _rj45_1000baset_list_remove(self, port):
        if port in self.rj45_1000baset_port_cache:
            self.rj45_1000baset_port_cache.remove(port)
            logger.info('Removed 1000BaseT rj45 port: {} from cache'.format(port))

    def _rj45_1000baset_list(self, ports):
        for port in self._ports_check_eeprom(ports):
            try:
                logger.debug('Testing port: %s to see if it is 1000BaseT' % port.label)
                port_ethcodes = port.driver.cable.ethcodes_safe
                if ((port.driver.cable.connector == 'rj45' or
                        '1000base-t' in port_ethcodes) and
                        '10g-sr' not in port_ethcodes and
                        '10g-tsr' not in port_ethcodes and
                        '10g-t' not in port_ethcodes):

                    if port not in self.rj45_1000baset_port_cache:
                        phy = self._probe_phy(port.driver.cable.eeprom.bus)
                        if not phy:
                            logger.debug('phy probe failed: %s' % port.label)
                            continue

                        if not self._validate_phy(port, phy):
                            continue

                        self.rj45_1000baset_port_cache.append(port)
                        logger.info('Added port: {} to rj45 1000BaseT cache'.format(port.label))

                else:
                    self._rj45_1000baset_list_remove(port)

            except EEPROMTimeout, e:
                log_throttle('EEPROM timeout: %s.' % str(e), level='warning')
                self._rj45_1000baset_list_remove(port)

        if not self.rj45_1000baset_port_cache:
            self.rj45_1000baset_port_cache = ['empty']

    def _rj45_link_status(self, ports):
        target_ports = self.rj45_1000baset_sfp_ports(ports)

        if not len(target_ports):
            return

        status = []
        for port in target_ports:
            phy = self._probe_phy(port.driver.cable.eeprom.bus)
            if phy:
                status.append('%s(%s)' % (port.label, phy.link))
            else:
                status.append('%s(unknown)' % port.label)

            # logger.debug('phy dump for port %s' % port.label)
            # phy.dump_regs()

        logger.debug('rj45_link_status: ' + ', '.join(status))

    def rj45_protodown_and_controlled(self, port):
        logger.debug('rj45_control: %s, Port: %s,  protodown %s' % (self.rj45_control, port, port.proto_down))
        return self.rj45_control and any(port.proto_down)

    def _rj45_phy_configure(self, ports):
        logger.debug('rj45 phy configure poll')
        target_ports = self.rj45_1000baset_sfp_ports(ports)

        if not len(target_ports):
            return

        logger.debug('rj45_phy_configure: %s' % ', '.join([str(x) for x in target_ports]))

        for port in target_ports:
            if port.traffic_ready and not self.rj45_protodown_and_controlled(port):
                self._configure_phy(port)

    def _clear_no_link_resets(self, ports):
        for port in ports:
            port.no_link_resets(clear=True)

    def _clear_reset_signal(self, ports):
        for port in ports:
            if (port.driver.present):
                logger.debug('Optic detected. Clear reset for %s' % port.label)
                port.driver.reset = 0

    def _clear_lpmode_signal(self, ports):
        for port in ports:
            if (port.driver.present):
                logger.debug('Optic detected. Clear lpmode for %s' % port.label)
                port.driver.power_mode = 'high'

    def _rj45_phy_power_check(self, ports):
        logger.debug('rj45 phy power check poll')
        target_ports = self.rj45_1000baset_sfp_ports(ports)

        if not len(target_ports):
            return

        logger.debug('rj45_phy_power_check: %s' % ', '.join([str(x) for x in target_ports]))

        for port in target_ports:
            phy = self._probe_phy(port.driver.cable.eeprom.bus)
            if not phy:
                logger.debug('phy probe failed: %s' % port.label)
                continue

            if not self._validate_phy(port, phy):
                continue

            if 100 in port.speed or 1000 in port.speed:
                logger.debug('rj45 power check, port %s admin: %s, protodown: %s' % (port.label, port.admin_up, port.proto_down))
                up = any(port.admin_up) and not self.rj45_protodown_and_controlled(port)
            else:
                up = False

            if phy.set_power(up):
                logger.info('Setting power %s on rj45 port: %s' % (up and 'up' or 'down', port.label))

    def _rj45_stop(self, port):
        phy = self._probe_phy(port.driver.cable.eeprom.bus)
        if not phy:
            logger.info('phy probe failed: %s' % port.label)
            return False

        if not self._validate_phy(port, phy):
            logger.info('phy validate failed: %s' % port.label)
            return False

        try:
            phy.set_power(False)
            logger.info('power down rj45 port: %s' % port.label)
        except Exception, e:
            logger.info('failed to power down rj45 port: %s (%s)' % (port.label, str(e)))
            return False

    def _retimer_configure(self, ports):
        target_ports = [ port for port in ports
                         if (port.traffic_ready and
                             (port.driver.tx_retimer or port.driver.rx_retimer)) ]
        if not len(target_ports):
            return

        logger.debug('retimer_configure: %s' % ', '.join([str(x) for x in target_ports]))

        for port in target_ports:
            current_speed = port.speed

            if cmp(current_speed,  port.driver.rx_retimer.speed):
                logger.debug('Configuring retimer for speed change from %s to %s on port-%s'
                             % (port.driver.rx_retimer.speed, current_speed, port.label))
                port.driver.rx_retimer.set_speed(current_speed)
                port.driver.tx_retimer.set_speed(current_speed)

    def _blacklist_hook(self, ports):
        for port in ports:
            if not port.driver.present:
                if port.disabled:
                    logger.info('Removing port from disabled set: %s' % port.label)
                    port.disabled = False
                continue

            if not port.disabled:
                ouipn = port.driver.cable.ouipn
                for cable in self._blacklist:
                    if cable == ouipn:
                        logger.info('The cable in port %s (%s) has known '
                                    'compatibility issues with this platform.  '
                                    'Disabling port.  Contact support for more '
                                    'information.' % (port.label, ouipn))
                        port.admin_down()
                        port.disabled = True
                        break

    def _rate_select(self, ports):
         for port in self._ports_check_eeprom(ports):
            if (port.speed[0] not in (1000, 10000)) or (port.driver.cable.identifier != 'SFP+'):
                continue
            port.driver.cable.rate_sel = port.speed[0]

    def enable_high_power(self, ports):
        for port in self._ports_check_eeprom(ports):
            port.driver.cable.enable_high_power()

    def _cdr_control_hook(self, ports):
        for port in self._ports_check_eeprom(ports):
            current_speed = port.speed[0]
            if (port.driver.cable.identifier == 'QSFP28' and
                    current_speed in (10000, 40000, 100000) and
                    'cr' not in port.config.ethmode and
                    port.driver.cable.cdr_present() and
                    port.driver.cable.cdr_controllable()):

                desired_cdr = self._cdr_map[current_speed]
                curr_cdr = port.driver.cable.cdr_value
                logger.debug('port %s, CDR: speed %s, present: %s, cntrlble: %s, enabled: %s' % (
                    port.label, current_speed, port.driver.cable.cdr_present(),
                    port.driver.cable.cdr_controllable(), curr_cdr))

                if curr_cdr is None:
                    continue

                if desired_cdr != curr_cdr:
                    current_ouipn = port.driver.cable.ouipn
                    if current_speed == 10000:
                        #100G optic running at 40G(4x10G) speed. Disable CDR circuit.
                        if curr_cdr == 'on':
                            log_throttle('port %s is in 10G mode.  Disabling CDR' % port.label)
                            port.driver.cable.cdr_value = desired_cdr
                        self._cdr_skip_cache[port.label] = (current_ouipn, current_speed)

                    if current_speed == 40000:
                        if curr_cdr == 'on':
                            log_throttle('port %s is in 40G mode.  Disabling CDR' % port.label)
                            port.driver.cable.cdr_value = desired_cdr
                        self._cdr_skip_cache[port.label] = (current_ouipn, current_speed)

                    if current_speed == 100000:
                        if curr_cdr == 'on':
                            self._cdr_skip_cache[port.label] = (current_ouipn, current_speed)
                            continue

                        if port.label not in self._cdr_skip_cache:
                            log_throttle('port %s: CDR present but incorrectly disabled by manufacturer. Skipping CDR enable.' % port.label)
                            self._cdr_skip_cache[port.label] = (current_ouipn, current_speed)
                            continue
                        else:
                            (last_ouipn, last_speed) = self._cdr_skip_cache[port.label]

                            if current_ouipn != last_ouipn:
                                log_throttle('port %s: Cable change. CDR present but incorrectly disabled by manufacturer. Skipping CDR enable.' % port.label)
                                self._cdr_skip_cache[port.label] = (current_ouipn, current_speed)
                                continue

                            if current_speed != last_speed:
                                log_throttle('port %s: speed change to 100G. CDR present but disabled. Skipping CDR enable until switchd restart.' % port.label)

                            self._cdr_skip_cache[port.label] = (current_ouipn, current_speed)

    def _clobber_lpmod(self, ports):
        target_ports = []
        for port in self._ports_check_eeprom(ports):
            if '40g-lr4' in port.driver.cable.ethcodes_safe:
                target_ports.append(port)

        if not len(target_ports):
            return

        logger.debug('clobber lpmod: %s' % ', '.join([ str(x) for x in target_ports ]))

        for port in target_ports:
            if port.driver.cable.lpoverride != 'override-high':
                logger.debug('overriding lpmod pin for port: %s' % port.label)
                port.driver.cable.lpoverride = 'override-high'

    def _ifmode_hook(self, ports):
        target_ports = []
        for port in self._ports_check_eeprom(ports):
            if port.config:
                target_ports.append(port)

        if not len(target_ports):
            return

        logger.debug('config ifmode: %s' % ', '.join([ str(x) for x in target_ports ]))
        modules_reset = False

        for port in target_ports:
            module_ethcodes = port.driver.cable.ethcodes_safe
            if module_ethcodes and len(module_ethcodes) > 0:
                changed_ethmode, changed_ifmode = port.config.compare_ethmode_ifmode(module_ethcodes[0], self.sim_init)
                if changed_ethmode or changed_ifmode:
                    logger.debug('reset port: %s.  Changing ethcode to: %s' % (port.label, module_ethcodes[0]))
                    # ifmode can change without ethmode change, so dont set the ethmode to zero
                    # use the existing ethmode. It will get the correct ifmode from compliance map.
                    if not changed_ethmode:
                        changed_ethmode = module_ethcodes[0]
                    port.driver.reset = 1
                    port.config.comply(changed_ethmode)
                    port.driver.reset = 0
                    modules_reset = True
                    logger.debug('Port: %s caching module ethcodes: %s' % (port, module_ethcodes))
                    self._state_ethcodes[port.label] = module_ethcodes
                    if self._enable_pre_emphasis_control:
                        current_speed = port.speed[0]
                        port.config.set_port_txfir_settings(port.driver.cable.cable_assy_length, changed_ethmode, current_speed)
            else:
                logger.debug('reset port: %s.  Setting ifmode=cr' % port.label)
                port.driver.reset = 1
                port.config.ifmode = 'cr'
                port.driver.reset = 0
                modules_reset = True
                if self._enable_pre_emphasis_control:
                    current_speed = port.speed[0]
                    port.config.set_port_txfir_settings(port.driver.cable.cable_assy_length, '10g_cr', current_speed)
                # TODO Also set ethcode to default cr text per speed
        # Add a small delay for last module to be recovered before generic hooks
        if modules_reset:
            time.sleep(.1)

    def _reset_fallback(self, ports):
        reset = set()

        for port in ports:
            # If the port, or any sub-port has carrier, skip
            if True in port.carrier:
                continue
            # If none of the ports are administratively up, skip
            if not (True in port.admin_up):
                continue
            # If no cable is present, skip (this is the most expensive check)
            if not port.driver.present:
                continue

            reset.add(port)

        verbose = log_throttle(':'.join([ str(x) for x in reset ]), level=None)

        if verbose:
            for port in reset:
                logger.info('port administratively up with cable present but no carrier: ' + str(port))

        self._reset_ports(reset, verbose)

    def config(self):
        # We can't handle generating config fragments for multi asic platforms.
        # Avoid them for now.  See CM-19236.
        if self._multi_asic:
            return ''

        return self._ifmode_config(self.ports)

    def shutdown(self):
        logger.info('shutdown')
        return self._shutdown(self.ports)

    def run_once(self, init=False):
        try:
            if init:
                self._call_hooks(self.ports, self._init_hooks)

            if len(self._admin_up_hooks):
                logger.debug('admin up poll')
                admin_changed = self._poll_admin_up()
                if len(admin_changed):
                    self._call_hooks(admin_changed, self._admin_up_hooks)

            if len(self._presence_hooks):
                presence_changed = self._poll_presence(init)
                if len(presence_changed):
                    self._call_hooks(presence_changed, self._presence_hooks)

            if len(self._ethcodes_hooks):
                ethcodes_changed = self._poll_ethcodes(init)
                if len(ethcodes_changed):
                    self._call_hooks(ethcodes_changed, self._ethcodes_hooks)

            # generic hooks are called every poll interval
            logger.debug('Running Generic hooks')
            if len(self._generic_hooks):
                present_ports = self._ports_from_present_cache()
                self._call_hooks(present_ports, self._generic_hooks)

        except EEPROMTimeout, e:
            log_throttle('unhandled EEPROM timeout: %s.' % str(e), level='warning')

        sysfs_cache.clear_sysfs_cache()
        for port in self.ports:
            # clear eeprom and ethcode caches each time through in case an external service updates them
            port.driver.cable.clear_eeprom_data_cache()
            port.config.clear_switchd_ethmode_cache()

        return True


class PortWDFastLoop(PortWDRun):
    """
    When the fastloop option is provided, portwd operates slightly
    different. There is a fast-loop and slow loop.  The fast loop only runs the protodown
    detection algorithm if rj45_control is enabled.  The slow loop runs everything
    """

    def run(self, oneshot=False, fast_poll_interval=0.5):
        logger.info('PortWDFastLoop run starting')

        slow_poll_interval = 5
        slow_poll_multiplier = int(slow_poll_interval/fast_poll_interval)

        if oneshot:
            logger.info('FastLoop started, one shot for %d ports' % len(self.ports))
            self.run_once()
        else:
            logger.info('started, watching %d ports and using fastloop' % len(self.ports))
            logger.info('rj45_control: %s fast_poll_interval: %.2f, slow_poll_interval: %d, multiplier: %d' % (
                self.rj45_control, fast_poll_interval, slow_poll_interval, slow_poll_multiplier))
            while True:
                sysfs_cache.clear_sysfs_cache()
                logger.debug('Fast loop iterations: %d' % slow_poll_multiplier)
                for i in range(slow_poll_multiplier):
                    time.sleep(fast_poll_interval)
                    rj45_port_filter = set(self.rj45_1000baset_sfp_ports(self.ports))
                    if self.rj45_control:
                        admin_changed = self._poll_admin_up(rj45_port_filter)
                        protodown_changed = self._poll_protodown(rj45_port_filter)
                        if protodown_changed or admin_changed:
                            logger.info('Port: {} has changed protodown or admin_down. Check RJ45 power state'.format([port.label for port in protodown_changed.union(admin_changed)]))
                            rj45_changed = protodown_changed.union(admin_changed)
                            for port in rj45_changed:
                                logger.info('Port: %s.  Admin up: %s  Proto down: %s' % (port.label, port.admin_up, port.proto_down))
                            self._rj45_phy_power_check(rj45_changed)

                logger.debug('Slow loop execution')
                # Have any ports become present since we last checked?
                self.run_once()

        return True


def start_logging(syslog=True, console=False, quiet=False, verbose=False):
    global logger
    global log_handlers
    logger = logging.getLogger('portwd')
    set_logger('portwd')
    # fmt = logging.Formatter(fmt='%(asctime)s: portwd: %(message)s')
    fmt = logging.Formatter(fmt='portwd: %(message)s')

    if console:
        console = logging.StreamHandler(sys.stdout)
        console.setFormatter(fmt)
        logger.addHandler(console)
        log_handlers.append(console)

    if syslog:
        syslog = logging.handlers.SysLogHandler(address='/dev/log')
        syslog.setFormatter(fmt)
        logger.addHandler(syslog)
        log_handlers.append(syslog)

    if verbose:
        logger.setLevel(logging.DEBUG)
    elif quiet:
        logger.setLevel(logging.ERROR)
    else:
        logger.setLevel(logging.INFO)

    return logger

def stop_logging():
    global logger
    global log_handlers
    for handler in log_handlers:
        handler.close()
        logger.removeHandler(handler)
    log_handlers = []


def socket_init():
    ''' Initialize the socket '''
    globals()["sock"] = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    globals()["sockfd"] = globals()["sock"].fileno()

def socket_shutdown():
    ''' Shut down the socket '''
    if globals()["sock"] is not None:
        globals()["sock"].close()
    globals()["sock"] = None
    globals()["sockfd"] = None


def service(verbose, fastloop, rj45_control):
    start_logging(verbose=verbose)
    portwd_retry_time = [0.5, 3.0, 10.0, 15.0]
    retry_counter = 0

    logger.debug('Flags:  fastloop = %s, rj45_control = %s' % (fastloop, rj45_control))

    def exit_normally(signum, frame):
	cmd = """systemctl list-jobs | egrep -q '(shutdown|reboot|halt|poweroff)'.target"""
	shutdown_not_active = subprocess.call(cmd, shell=True)
	logger.info('Shutdown_not_active: {}'.format(bool(shutdown_not_active)))
	if not bool(shutdown_not_active) and rj45_control:
	    try:
		portwd.shutdown()
	    except UnknownPlatform:
		pass
	    except cumulus.porttab.NotConfigured:
		logger.error('porttab not configured')
	logger.info('SIGTERM, exiting')
	exit(0)

    signal.signal(signal.SIGTERM, exit_normally)

    # Tell systemd that we are initialized and ready
    cumulus.sdnotify.sd_notify(0, "READY=1")



    try:
        if fastloop:
            portwd = PortWDFastLoop(rj45_control=rj45_control)
            logger.info('Using PortWDFastLoop')
        else:
            portwd = PortWDRun(rj45_control=rj45_control)

    except UnknownPlatform, e:
        logger.info('not monitoring on platform: %s' % str(e))
        portwd = PortWDIdle()

    for retry_time in portwd_retry_time:
        try:
            portwd.run_once(init=True)
            break
        except cumulus.porttab.NotConfigured:
            logger.error('porttab not configured, exiting.')
            return -1
        except BCMNotAvailable:
            logger.error('switchd not responding. Retrying in %1.2f seconds' % retry_time)
            retry_counter += 1
            if retry_counter == 4:
                return -1
            time.sleep(retry_time)
        except NoSuchPort, e:
            logger.info('missing swp interfaces, exiting.')
            return -1

    socket_shutdown()
    stop_logging()

    # Tell systemd that we are initialized and ready
    cumulus.sdnotify.sd_notify(0, "READY=1")

    dc = daemon.DaemonContext(working_directory='/run', umask=0o022)

    with dc:
        start_logging(verbose=verbose)

        signal.signal(signal.SIGTERM, exit_normally)

        try:
            portwd.run()
        except Exception, e:
            (exc_type, exc_value, exc_traceback) = sys.exc_info()
            logger.exception(traceback.format_tb(exc_traceback))
            return -1

        socket_shutdown()
        logger.info('exiting')
        return 0

def oneshot(verbose, sim_init=False):
    start_logging(syslog=False, console=True, verbose=verbose)

    try:
        if sim_init:
            logger.debug('Oneshot: Simulating portwd init')
        PortWDRun(sim_init=sim_init).run(oneshot=True, init=sim_init)
    except UnknownPlatform, e:
        logger.info('not monitoring on platform: %s' % str(e))
    except cumulus.porttab.NotConfigured:
        logger.info('porttab not configured')
        return -1

    return 0

def config(verbose):
    start_logging(syslog=True, console=False, quiet=True, verbose=verbose)

    try:
        sys.stdout.write(PortWDRun().config())
    except UnknownPlatform:
        pass
    except cumulus.porttab.NotConfigured:
        logger.error('porttab not configured')
        return -1
    return 0

def shutdown(verbose):
    start_logging(syslog=True, console=False, quiet=False, verbose=verbose)

    try:
        PortWDRun().shutdown()
    except UnknownPlatform:
        pass
    except cumulus.porttab.NotConfigured:
        logger.error('porttab not configured')
        return -1
    return 0

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Port Watch Event Daemon')
    parser.add_argument('-v', '--verbose',
                        required=False,
                        action='store_true',
                        help='Verbose output')
    parser.add_argument('-1', '--oneshot',
                        required=False,
                        action='store_true',
                        help='Perform a single portwd event loop')
    parser.add_argument('-c', '--config',
                        required=False,
                        action='store_true',
                        help='Generate a SDK config file fragment based on the '
                              'installed cables.')
    parser.add_argument('-s', '--shutdown',
                        required=False,
                        action='store_true',
                        help='Shut down all ports')
    parser.add_argument('--fastloop',
                        required=False,
                        action='store_true',
                        help='When using CLAG without LACP, do protodown detection on 1G BaseT SFPs')
    parser.add_argument('--rj45_control',
                        required=False,
                        action='store_true',
                        help='Control power to 1G BaseT SFPs on shutdown/reboot of switch and admin/proto down')
    parser.add_argument('-r', '--sim_init',
                        required=False,
                        action='store_true',
                        help='Run with oneshot mode to simulate that there is no previous state in switchd.  Warning: will flap ports')

    try:
        args = parser.parse_args()
    except argparse.ArgumentError, e:
        parser.error(str(e))
        sys.exit(-1)

    if sum((args.oneshot, args.config, args.shutdown, args.fastloop)) > 1:
        parser.error("only one of 'oneshot', 'config', 'shutdown', or 'fastloop' allowed")
        sys.exit(-1)

    sysfs_cache = Sysfs_cache()
    tx_settings = TxSettings()

    if args.oneshot:
        sys.exit(oneshot(args.verbose, args.sim_init))
    elif args.config:
        sys.exit(config(args.verbose))
    elif args.shutdown:
        sys.exit(shutdown(args.verbose))
    else:
        sys.exit(service(args.verbose, args.fastloop, args.rj45_control))
