sfp: add sfp-bus to bridge between network devices and sfp cages
authorRussell King <rmk+kernel@armlinux.org.uk>
Tue, 25 Jul 2017 14:03:18 +0000 (15:03 +0100)
committerDavid S. Miller <davem@davemloft.net>
Mon, 7 Aug 2017 03:55:29 +0000 (20:55 -0700)
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/Makefile
drivers/net/phy/phylink.c
drivers/net/phy/sfp-bus.c [new file with mode: 0644]
drivers/net/phy/sfp.h [new file with mode: 0644]
include/linux/sfp.h [new file with mode: 0644]

index c43e5b99fda4988a99c605a7730d3b16e28dd5c7..4c16a10f420e1aa15f1603692603b29c31c49e9d 100644 (file)
@@ -38,6 +38,9 @@ obj-$(CONFIG_MDIO_SUN4I)      += mdio-sun4i.o
 obj-$(CONFIG_MDIO_THUNDER)     += mdio-thunder.o
 obj-$(CONFIG_MDIO_XGENE)       += mdio-xgene.o
 
+sfp-obj-$(CONFIG_SFP)          += sfp-bus.o
+obj-y                          += $(sfp-obj-y) $(sfp-obj-m)
+
 obj-$(CONFIG_AMD_PHY)          += amd.o
 obj-$(CONFIG_AQUANTIA_PHY)     += aquantia.o
 obj-$(CONFIG_AT803X_PHY)       += at803x.o
index af61d7d400af1998f73491b251691b0b4c4c28e9..02082f4a8a95d8fac16c4db028cd0d26e59591fb 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/spinlock.h>
 #include <linux/workqueue.h>
 
+#include "sfp.h"
 #include "swphy.h"
 
 #define SUPPORTED_INTERFACES \
@@ -32,6 +33,7 @@
 
 enum {
        PHYLINK_DISABLE_STOPPED,
+       PHYLINK_DISABLE_LINK,
 };
 
 struct phylink {
@@ -54,6 +56,8 @@ struct phylink {
        struct work_struct resolve;
 
        bool mac_link_dropped;
+
+       struct sfp_bus *sfp_bus;
 };
 
 static inline void linkmode_zero(unsigned long *dst)
@@ -466,6 +470,24 @@ static void phylink_run_resolve(struct phylink *pl)
                queue_work(system_power_efficient_wq, &pl->resolve);
 }
 
+static const struct sfp_upstream_ops sfp_phylink_ops;
+
+static int phylink_register_sfp(struct phylink *pl, struct device_node *np)
+{
+       struct device_node *sfp_np;
+
+       sfp_np = of_parse_phandle(np, "sfp", 0);
+       if (!sfp_np)
+               return 0;
+
+       pl->sfp_bus = sfp_register_upstream(sfp_np, pl->netdev, pl,
+                                           &sfp_phylink_ops);
+       if (!pl->sfp_bus)
+               return -ENOMEM;
+
+       return 0;
+}
+
 struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
        phy_interface_t iface, const struct phylink_mac_ops *ops)
 {
@@ -507,12 +529,21 @@ struct phylink *phylink_create(struct net_device *ndev, struct device_node *np,
                }
        }
 
+       ret = phylink_register_sfp(pl, np);
+       if (ret < 0) {
+               kfree(pl);
+               return ERR_PTR(ret);
+       }
+
        return pl;
 }
 EXPORT_SYMBOL_GPL(phylink_create);
 
 void phylink_destroy(struct phylink *pl)
 {
+       if (pl->sfp_bus)
+               sfp_unregister_upstream(pl->sfp_bus);
+
        cancel_work_sync(&pl->resolve);
        kfree(pl);
 }
@@ -706,6 +737,8 @@ void phylink_start(struct phylink *pl)
        clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
        phylink_run_resolve(pl);
 
+       if (pl->sfp_bus)
+               sfp_upstream_start(pl->sfp_bus);
        if (pl->phydev)
                phy_start(pl->phydev);
 }
@@ -717,6 +750,8 @@ void phylink_stop(struct phylink *pl)
 
        if (pl->phydev)
                phy_stop(pl->phydev);
+       if (pl->sfp_bus)
+               sfp_upstream_stop(pl->sfp_bus);
 
        set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state);
        flush_work(&pl->resolve);
@@ -1166,4 +1201,126 @@ int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd)
 }
 EXPORT_SYMBOL_GPL(phylink_mii_ioctl);
 
+
+
+static int phylink_sfp_module_insert(void *upstream,
+                                    const struct sfp_eeprom_id *id)
+{
+       struct phylink *pl = upstream;
+       __ETHTOOL_DECLARE_LINK_MODE_MASK(support) = { 0, };
+       struct phylink_link_state config;
+       phy_interface_t iface;
+       int mode, ret = 0;
+       bool changed;
+       u8 port;
+
+       sfp_parse_support(pl->sfp_bus, id, support);
+       port = sfp_parse_port(pl->sfp_bus, id, support);
+       iface = sfp_parse_interface(pl->sfp_bus, id);
+
+       WARN_ON(!lockdep_rtnl_is_held());
+
+       switch (iface) {
+       case PHY_INTERFACE_MODE_SGMII:
+               mode = MLO_AN_SGMII;
+               break;
+       case PHY_INTERFACE_MODE_1000BASEX:
+               mode = MLO_AN_8023Z;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       memset(&config, 0, sizeof(config));
+       linkmode_copy(config.advertising, support);
+       config.interface = iface;
+       config.speed = SPEED_UNKNOWN;
+       config.duplex = DUPLEX_UNKNOWN;
+       config.pause = MLO_PAUSE_AN;
+       config.an_enabled = pl->link_config.an_enabled;
+
+       /* Ignore errors if we're expecting a PHY to attach later */
+       ret = phylink_validate(pl, support, &config);
+       if (ret) {
+               netdev_err(pl->netdev, "validation of %s/%s with support %*pb failed: %d\n",
+                          phylink_an_mode_str(mode), phy_modes(config.interface),
+                          __ETHTOOL_LINK_MODE_MASK_NBITS, support, ret);
+               return ret;
+       }
+
+       netdev_dbg(pl->netdev, "requesting link mode %s/%s with support %*pb\n",
+                  phylink_an_mode_str(mode), phy_modes(config.interface),
+                  __ETHTOOL_LINK_MODE_MASK_NBITS, support);
+
+       if (mode == MLO_AN_8023Z && pl->phydev)
+               return -EINVAL;
+
+       changed = !bitmap_equal(pl->supported, support,
+                               __ETHTOOL_LINK_MODE_MASK_NBITS);
+       if (changed) {
+               linkmode_copy(pl->supported, support);
+               linkmode_copy(pl->link_config.advertising, config.advertising);
+       }
+
+       if (pl->link_an_mode != mode ||
+           pl->link_config.interface != config.interface) {
+               pl->link_config.interface = config.interface;
+               pl->link_an_mode = mode;
+
+               changed = true;
+
+               netdev_info(pl->netdev, "switched to %s/%s link mode\n",
+                           phylink_an_mode_str(mode),
+                           phy_modes(config.interface));
+       }
+
+       pl->link_port = port;
+
+       if (changed && !test_bit(PHYLINK_DISABLE_STOPPED,
+                                &pl->phylink_disable_state))
+               phylink_mac_config(pl, &pl->link_config);
+
+       return ret;
+}
+
+static void phylink_sfp_link_down(void *upstream)
+{
+       struct phylink *pl = upstream;
+
+       WARN_ON(!lockdep_rtnl_is_held());
+
+       set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
+       flush_work(&pl->resolve);
+
+       netif_carrier_off(pl->netdev);
+}
+
+static void phylink_sfp_link_up(void *upstream)
+{
+       struct phylink *pl = upstream;
+
+       WARN_ON(!lockdep_rtnl_is_held());
+
+       clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state);
+       phylink_run_resolve(pl);
+}
+
+static int phylink_sfp_connect_phy(void *upstream, struct phy_device *phy)
+{
+       return phylink_connect_phy(upstream, phy);
+}
+
+static void phylink_sfp_disconnect_phy(void *upstream)
+{
+       phylink_disconnect_phy(upstream);
+}
+
+static const struct sfp_upstream_ops sfp_phylink_ops = {
+       .module_insert = phylink_sfp_module_insert,
+       .link_up = phylink_sfp_link_up,
+       .link_down = phylink_sfp_link_down,
+       .connect_phy = phylink_sfp_connect_phy,
+       .disconnect_phy = phylink_sfp_disconnect_phy,
+};
+
 MODULE_LICENSE("GPL");
diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c
new file mode 100644 (file)
index 0000000..5cb5384
--- /dev/null
@@ -0,0 +1,475 @@
+#include <linux/export.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/phylink.h>
+#include <linux/rtnetlink.h>
+#include <linux/slab.h>
+
+#include "sfp.h"
+
+struct sfp_bus {
+       struct kref kref;
+       struct list_head node;
+       struct device_node *device_node;
+
+       const struct sfp_socket_ops *socket_ops;
+       struct device *sfp_dev;
+       struct sfp *sfp;
+
+       const struct sfp_upstream_ops *upstream_ops;
+       void *upstream;
+       struct net_device *netdev;
+       struct phy_device *phydev;
+
+       bool registered;
+       bool started;
+};
+
+
+int sfp_parse_port(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+                  unsigned long *support)
+{
+       int port;
+
+       /* port is the physical connector, set this from the connector field. */
+       switch (id->base.connector) {
+       case SFP_CONNECTOR_SC:
+       case SFP_CONNECTOR_FIBERJACK:
+       case SFP_CONNECTOR_LC:
+       case SFP_CONNECTOR_MT_RJ:
+       case SFP_CONNECTOR_MU:
+       case SFP_CONNECTOR_OPTICAL_PIGTAIL:
+               if (support)
+                       phylink_set(support, FIBRE);
+               port = PORT_FIBRE;
+               break;
+
+       case SFP_CONNECTOR_RJ45:
+               if (support)
+                       phylink_set(support, TP);
+               port = PORT_TP;
+               break;
+
+       case SFP_CONNECTOR_UNSPEC:
+               if (id->base.e1000_base_t) {
+                       if (support)
+                               phylink_set(support, TP);
+                       port = PORT_TP;
+                       break;
+               }
+               /* fallthrough */
+       case SFP_CONNECTOR_SG: /* guess */
+       case SFP_CONNECTOR_MPO_1X12:
+       case SFP_CONNECTOR_MPO_2X16:
+       case SFP_CONNECTOR_HSSDC_II:
+       case SFP_CONNECTOR_COPPER_PIGTAIL:
+       case SFP_CONNECTOR_NOSEPARATE:
+       case SFP_CONNECTOR_MXC_2X16:
+               port = PORT_OTHER;
+               break;
+       default:
+               dev_warn(bus->sfp_dev, "SFP: unknown connector id 0x%02x\n",
+                        id->base.connector);
+               port = PORT_OTHER;
+               break;
+       }
+
+       return port;
+}
+EXPORT_SYMBOL_GPL(sfp_parse_port);
+
+phy_interface_t sfp_parse_interface(struct sfp_bus *bus,
+                                   const struct sfp_eeprom_id *id)
+{
+       phy_interface_t iface;
+
+       /* Setting the serdes link mode is guesswork: there's no field in
+        * the EEPROM which indicates what mode should be used.
+        *
+        * If the module wants 64b66b, then it must be >= 10G.
+        *
+        * If it's a gigabit-only fiber module, it probably does not have
+        * a PHY, so switch to 802.3z negotiation mode. Otherwise, switch
+        * to SGMII mode (which is required to support non-gigabit speeds).
+        */
+       switch (id->base.encoding) {
+       case SFP_ENCODING_8472_64B66B:
+               iface = PHY_INTERFACE_MODE_10GKR;
+               break;
+
+       case SFP_ENCODING_8B10B:
+               if (!id->base.e1000_base_t &&
+                   !id->base.e100_base_lx &&
+                   !id->base.e100_base_fx)
+                       iface = PHY_INTERFACE_MODE_1000BASEX;
+               else
+                       iface = PHY_INTERFACE_MODE_SGMII;
+               break;
+
+       default:
+               iface = PHY_INTERFACE_MODE_NA;
+               dev_err(bus->sfp_dev,
+                       "SFP module encoding does not support 8b10b nor 64b66b\n");
+               break;
+       }
+
+       return iface;
+}
+EXPORT_SYMBOL_GPL(sfp_parse_interface);
+
+void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+                      unsigned long *support)
+{
+       phylink_set(support, Autoneg);
+       phylink_set(support, Pause);
+       phylink_set(support, Asym_Pause);
+
+       /* Set ethtool support from the compliance fields. */
+       if (id->base.e10g_base_sr)
+               phylink_set(support, 10000baseSR_Full);
+       if (id->base.e10g_base_lr)
+               phylink_set(support, 10000baseLR_Full);
+       if (id->base.e10g_base_lrm)
+               phylink_set(support, 10000baseLRM_Full);
+       if (id->base.e10g_base_er)
+               phylink_set(support, 10000baseER_Full);
+       if (id->base.e1000_base_sx ||
+           id->base.e1000_base_lx ||
+           id->base.e1000_base_cx)
+               phylink_set(support, 1000baseX_Full);
+       if (id->base.e1000_base_t) {
+               phylink_set(support, 1000baseT_Half);
+               phylink_set(support, 1000baseT_Full);
+       }
+
+       switch (id->base.extended_cc) {
+       case 0x00: /* Unspecified */
+               break;
+       case 0x02: /* 100Gbase-SR4 or 25Gbase-SR */
+               phylink_set(support, 100000baseSR4_Full);
+               phylink_set(support, 25000baseSR_Full);
+               break;
+       case 0x03: /* 100Gbase-LR4 or 25Gbase-LR */
+       case 0x04: /* 100Gbase-ER4 or 25Gbase-ER */
+               phylink_set(support, 100000baseLR4_ER4_Full);
+               break;
+       case 0x0b: /* 100Gbase-CR4 or 25Gbase-CR CA-L */
+       case 0x0c: /* 25Gbase-CR CA-S */
+       case 0x0d: /* 25Gbase-CR CA-N */
+               phylink_set(support, 100000baseCR4_Full);
+               phylink_set(support, 25000baseCR_Full);
+               break;
+       default:
+               dev_warn(bus->sfp_dev,
+                        "Unknown/unsupported extended compliance code: 0x%02x\n",
+                        id->base.extended_cc);
+               break;
+       }
+
+       /* For fibre channel SFP, derive possible BaseX modes */
+       if (id->base.fc_speed_100 ||
+           id->base.fc_speed_200 ||
+           id->base.fc_speed_400) {
+               if (id->base.br_nominal >= 31)
+                       phylink_set(support, 2500baseX_Full);
+               if (id->base.br_nominal >= 12)
+                       phylink_set(support, 1000baseX_Full);
+       }
+
+       switch (id->base.connector) {
+       case SFP_CONNECTOR_SC:
+       case SFP_CONNECTOR_FIBERJACK:
+       case SFP_CONNECTOR_LC:
+       case SFP_CONNECTOR_MT_RJ:
+       case SFP_CONNECTOR_MU:
+       case SFP_CONNECTOR_OPTICAL_PIGTAIL:
+               break;
+
+       case SFP_CONNECTOR_UNSPEC:
+               if (id->base.e1000_base_t)
+                       break;
+
+       case SFP_CONNECTOR_SG: /* guess */
+       case SFP_CONNECTOR_MPO_1X12:
+       case SFP_CONNECTOR_MPO_2X16:
+       case SFP_CONNECTOR_HSSDC_II:
+       case SFP_CONNECTOR_COPPER_PIGTAIL:
+       case SFP_CONNECTOR_NOSEPARATE:
+       case SFP_CONNECTOR_MXC_2X16:
+       default:
+               /* a guess at the supported link modes */
+               dev_warn(bus->sfp_dev,
+                        "Guessing link modes, please report...\n");
+               phylink_set(support, 1000baseT_Half);
+               phylink_set(support, 1000baseT_Full);
+               break;
+       }
+}
+EXPORT_SYMBOL_GPL(sfp_parse_support);
+
+
+static LIST_HEAD(sfp_buses);
+static DEFINE_MUTEX(sfp_mutex);
+
+static const struct sfp_upstream_ops *sfp_get_upstream_ops(struct sfp_bus *bus)
+{
+       return bus->registered ? bus->upstream_ops : NULL;
+}
+
+static struct sfp_bus *sfp_bus_get(struct device_node *np)
+{
+       struct sfp_bus *sfp, *new, *found = NULL;
+
+       new = kzalloc(sizeof(*new), GFP_KERNEL);
+
+       mutex_lock(&sfp_mutex);
+
+       list_for_each_entry(sfp, &sfp_buses, node) {
+               if (sfp->device_node == np) {
+                       kref_get(&sfp->kref);
+                       found = sfp;
+                       break;
+               }
+       }
+
+       if (!found && new) {
+               kref_init(&new->kref);
+               new->device_node = np;
+               list_add(&new->node, &sfp_buses);
+               found = new;
+               new = NULL;
+       }
+
+       mutex_unlock(&sfp_mutex);
+
+       kfree(new);
+
+       return found;
+}
+
+static void sfp_bus_release(struct kref *kref) __releases(sfp_mutex)
+{
+       struct sfp_bus *bus = container_of(kref, struct sfp_bus, kref);
+
+       list_del(&bus->node);
+       mutex_unlock(&sfp_mutex);
+       kfree(bus);
+}
+
+static void sfp_bus_put(struct sfp_bus *bus)
+{
+       kref_put_mutex(&bus->kref, sfp_bus_release, &sfp_mutex);
+}
+
+static int sfp_register_bus(struct sfp_bus *bus)
+{
+       const struct sfp_upstream_ops *ops = bus->upstream_ops;
+       int ret;
+
+       if (ops) {
+               if (ops->link_down)
+                       ops->link_down(bus->upstream);
+               if (ops->connect_phy && bus->phydev) {
+                       ret = ops->connect_phy(bus->upstream, bus->phydev);
+                       if (ret)
+                               return ret;
+               }
+       }
+       if (bus->started)
+               bus->socket_ops->start(bus->sfp);
+       bus->registered = true;
+       return 0;
+}
+
+static void sfp_unregister_bus(struct sfp_bus *bus)
+{
+       const struct sfp_upstream_ops *ops = bus->upstream_ops;
+
+       if (bus->registered) {
+               if (bus->started)
+                       bus->socket_ops->stop(bus->sfp);
+               if (bus->phydev && ops && ops->disconnect_phy)
+                       ops->disconnect_phy(bus->upstream);
+       }
+       bus->registered = false;
+}
+
+
+int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo)
+{
+       if (!bus->registered)
+               return -ENOIOCTLCMD;
+       return bus->socket_ops->module_info(bus->sfp, modinfo);
+}
+EXPORT_SYMBOL_GPL(sfp_get_module_info);
+
+int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
+       u8 *data)
+{
+       if (!bus->registered)
+               return -ENOIOCTLCMD;
+       return bus->socket_ops->module_eeprom(bus->sfp, ee, data);
+}
+EXPORT_SYMBOL_GPL(sfp_get_module_eeprom);
+
+void sfp_upstream_start(struct sfp_bus *bus)
+{
+       if (bus->registered)
+               bus->socket_ops->start(bus->sfp);
+       bus->started = true;
+}
+EXPORT_SYMBOL_GPL(sfp_upstream_start);
+
+void sfp_upstream_stop(struct sfp_bus *bus)
+{
+       if (bus->registered)
+               bus->socket_ops->stop(bus->sfp);
+       bus->started = false;
+}
+EXPORT_SYMBOL_GPL(sfp_upstream_stop);
+
+struct sfp_bus *sfp_register_upstream(struct device_node *np,
+       struct net_device *ndev, void *upstream,
+       const struct sfp_upstream_ops *ops)
+{
+       struct sfp_bus *bus = sfp_bus_get(np);
+       int ret = 0;
+
+       if (bus) {
+               rtnl_lock();
+               bus->upstream_ops = ops;
+               bus->upstream = upstream;
+               bus->netdev = ndev;
+
+               if (bus->sfp)
+                       ret = sfp_register_bus(bus);
+               rtnl_unlock();
+       }
+
+       if (ret) {
+               sfp_bus_put(bus);
+               bus = NULL;
+       }
+
+       return bus;
+}
+EXPORT_SYMBOL_GPL(sfp_register_upstream);
+
+void sfp_unregister_upstream(struct sfp_bus *bus)
+{
+       rtnl_lock();
+       sfp_unregister_bus(bus);
+       bus->upstream = NULL;
+       bus->netdev = NULL;
+       rtnl_unlock();
+
+       sfp_bus_put(bus);
+}
+EXPORT_SYMBOL_GPL(sfp_unregister_upstream);
+
+
+/* Socket driver entry points */
+int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev)
+{
+       const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+       int ret = 0;
+
+       if (ops && ops->connect_phy)
+               ret = ops->connect_phy(bus->upstream, phydev);
+
+       if (ret == 0)
+               bus->phydev = phydev;
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_add_phy);
+
+void sfp_remove_phy(struct sfp_bus *bus)
+{
+       const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+       if (ops && ops->disconnect_phy)
+               ops->disconnect_phy(bus->upstream);
+       bus->phydev = NULL;
+}
+EXPORT_SYMBOL_GPL(sfp_remove_phy);
+
+
+void sfp_link_up(struct sfp_bus *bus)
+{
+       const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+       if (ops && ops->link_up)
+               ops->link_up(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_link_up);
+
+void sfp_link_down(struct sfp_bus *bus)
+{
+       const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+       if (ops && ops->link_down)
+               ops->link_down(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_link_down);
+
+int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
+{
+       const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+       int ret = 0;
+
+       if (ops && ops->module_insert)
+               ret = ops->module_insert(bus->upstream, id);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(sfp_module_insert);
+
+void sfp_module_remove(struct sfp_bus *bus)
+{
+       const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+
+       if (ops && ops->module_remove)
+               ops->module_remove(bus->upstream);
+}
+EXPORT_SYMBOL_GPL(sfp_module_remove);
+
+struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp,
+                                   const struct sfp_socket_ops *ops)
+{
+       struct sfp_bus *bus = sfp_bus_get(dev->of_node);
+       int ret = 0;
+
+       if (bus) {
+               rtnl_lock();
+               bus->sfp_dev = dev;
+               bus->sfp = sfp;
+               bus->socket_ops = ops;
+
+               if (bus->netdev)
+                       ret = sfp_register_bus(bus);
+               rtnl_unlock();
+       }
+
+       if (ret) {
+               sfp_bus_put(bus);
+               bus = NULL;
+       }
+
+       return bus;
+}
+EXPORT_SYMBOL_GPL(sfp_register_socket);
+
+void sfp_unregister_socket(struct sfp_bus *bus)
+{
+       rtnl_lock();
+       sfp_unregister_bus(bus);
+       bus->sfp_dev = NULL;
+       bus->sfp = NULL;
+       bus->socket_ops = NULL;
+       rtnl_unlock();
+
+       sfp_bus_put(bus);
+}
+EXPORT_SYMBOL_GPL(sfp_unregister_socket);
diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
new file mode 100644 (file)
index 0000000..31b0acf
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef SFP_H
+#define SFP_H
+
+#include <linux/ethtool.h>
+#include <linux/sfp.h>
+
+struct sfp;
+
+struct sfp_socket_ops {
+       void (*start)(struct sfp *sfp);
+       void (*stop)(struct sfp *sfp);
+       int (*module_info)(struct sfp *sfp, struct ethtool_modinfo *modinfo);
+       int (*module_eeprom)(struct sfp *sfp, struct ethtool_eeprom *ee,
+                            u8 *data);
+};
+
+int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev);
+void sfp_remove_phy(struct sfp_bus *bus);
+void sfp_link_up(struct sfp_bus *bus);
+void sfp_link_down(struct sfp_bus *bus);
+int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
+void sfp_module_remove(struct sfp_bus *bus);
+int sfp_link_configure(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
+struct sfp_bus *sfp_register_socket(struct device *dev, struct sfp *sfp,
+                                   const struct sfp_socket_ops *ops);
+void sfp_unregister_socket(struct sfp_bus *bus);
+
+#endif
diff --git a/include/linux/sfp.h b/include/linux/sfp.h
new file mode 100644 (file)
index 0000000..4a906f5
--- /dev/null
@@ -0,0 +1,434 @@
+#ifndef LINUX_SFP_H
+#define LINUX_SFP_H
+
+#include <linux/phy.h>
+
+struct __packed sfp_eeprom_base {
+       u8 phys_id;
+       u8 phys_ext_id;
+       u8 connector;
+#if defined __BIG_ENDIAN_BITFIELD
+       u8 e10g_base_er:1;
+       u8 e10g_base_lrm:1;
+       u8 e10g_base_lr:1;
+       u8 e10g_base_sr:1;
+       u8 if_1x_sx:1;
+       u8 if_1x_lx:1;
+       u8 if_1x_copper_active:1;
+       u8 if_1x_copper_passive:1;
+
+       u8 escon_mmf_1310_led:1;
+       u8 escon_smf_1310_laser:1;
+       u8 sonet_oc192_short_reach:1;
+       u8 sonet_reach_bit1:1;
+       u8 sonet_reach_bit2:1;
+       u8 sonet_oc48_long_reach:1;
+       u8 sonet_oc48_intermediate_reach:1;
+       u8 sonet_oc48_short_reach:1;
+
+       u8 unallocated_5_7:1;
+       u8 sonet_oc12_smf_long_reach:1;
+       u8 sonet_oc12_smf_intermediate_reach:1;
+       u8 sonet_oc12_short_reach:1;
+       u8 unallocated_5_3:1;
+       u8 sonet_oc3_smf_long_reach:1;
+       u8 sonet_oc3_smf_intermediate_reach:1;
+       u8 sonet_oc3_short_reach:1;
+
+       u8 e_base_px:1;
+       u8 e_base_bx10:1;
+       u8 e100_base_fx:1;
+       u8 e100_base_lx:1;
+       u8 e1000_base_t:1;
+       u8 e1000_base_cx:1;
+       u8 e1000_base_lx:1;
+       u8 e1000_base_sx:1;
+
+       u8 fc_ll_v:1;
+       u8 fc_ll_s:1;
+       u8 fc_ll_i:1;
+       u8 fc_ll_l:1;
+       u8 fc_ll_m:1;
+       u8 fc_tech_sa:1;
+       u8 fc_tech_lc:1;
+       u8 fc_tech_electrical_inter_enclosure:1;
+
+       u8 fc_tech_electrical_intra_enclosure:1;
+       u8 fc_tech_sn:1;
+       u8 fc_tech_sl:1;
+       u8 fc_tech_ll:1;
+       u8 sfp_ct_active:1;
+       u8 sfp_ct_passive:1;
+       u8 unallocated_8_1:1;
+       u8 unallocated_8_0:1;
+
+       u8 fc_media_tw:1;
+       u8 fc_media_tp:1;
+       u8 fc_media_mi:1;
+       u8 fc_media_tv:1;
+       u8 fc_media_m6:1;
+       u8 fc_media_m5:1;
+       u8 unallocated_9_1:1;
+       u8 fc_media_sm:1;
+
+       u8 fc_speed_1200:1;
+       u8 fc_speed_800:1;
+       u8 fc_speed_1600:1;
+       u8 fc_speed_400:1;
+       u8 fc_speed_3200:1;
+       u8 fc_speed_200:1;
+       u8 unallocated_10_1:1;
+       u8 fc_speed_100:1;
+#elif defined __LITTLE_ENDIAN_BITFIELD
+       u8 if_1x_copper_passive:1;
+       u8 if_1x_copper_active:1;
+       u8 if_1x_lx:1;
+       u8 if_1x_sx:1;
+       u8 e10g_base_sr:1;
+       u8 e10g_base_lr:1;
+       u8 e10g_base_lrm:1;
+       u8 e10g_base_er:1;
+
+       u8 sonet_oc3_short_reach:1;
+       u8 sonet_oc3_smf_intermediate_reach:1;
+       u8 sonet_oc3_smf_long_reach:1;
+       u8 unallocated_5_3:1;
+       u8 sonet_oc12_short_reach:1;
+       u8 sonet_oc12_smf_intermediate_reach:1;
+       u8 sonet_oc12_smf_long_reach:1;
+       u8 unallocated_5_7:1;
+
+       u8 sonet_oc48_short_reach:1;
+       u8 sonet_oc48_intermediate_reach:1;
+       u8 sonet_oc48_long_reach:1;
+       u8 sonet_reach_bit2:1;
+       u8 sonet_reach_bit1:1;
+       u8 sonet_oc192_short_reach:1;
+       u8 escon_smf_1310_laser:1;
+       u8 escon_mmf_1310_led:1;
+
+       u8 e1000_base_sx:1;
+       u8 e1000_base_lx:1;
+       u8 e1000_base_cx:1;
+       u8 e1000_base_t:1;
+       u8 e100_base_lx:1;
+       u8 e100_base_fx:1;
+       u8 e_base_bx10:1;
+       u8 e_base_px:1;
+
+       u8 fc_tech_electrical_inter_enclosure:1;
+       u8 fc_tech_lc:1;
+       u8 fc_tech_sa:1;
+       u8 fc_ll_m:1;
+       u8 fc_ll_l:1;
+       u8 fc_ll_i:1;
+       u8 fc_ll_s:1;
+       u8 fc_ll_v:1;
+
+       u8 unallocated_8_0:1;
+       u8 unallocated_8_1:1;
+       u8 sfp_ct_passive:1;
+       u8 sfp_ct_active:1;
+       u8 fc_tech_ll:1;
+       u8 fc_tech_sl:1;
+       u8 fc_tech_sn:1;
+       u8 fc_tech_electrical_intra_enclosure:1;
+
+       u8 fc_media_sm:1;
+       u8 unallocated_9_1:1;
+       u8 fc_media_m5:1;
+       u8 fc_media_m6:1;
+       u8 fc_media_tv:1;
+       u8 fc_media_mi:1;
+       u8 fc_media_tp:1;
+       u8 fc_media_tw:1;
+
+       u8 fc_speed_100:1;
+       u8 unallocated_10_1:1;
+       u8 fc_speed_200:1;
+       u8 fc_speed_3200:1;
+       u8 fc_speed_400:1;
+       u8 fc_speed_1600:1;
+       u8 fc_speed_800:1;
+       u8 fc_speed_1200:1;
+#else
+#error Unknown Endian
+#endif
+       u8 encoding;
+       u8 br_nominal;
+       u8 rate_id;
+       u8 link_len[6];
+       char vendor_name[16];
+       u8 extended_cc;
+       char vendor_oui[3];
+       char vendor_pn[16];
+       char vendor_rev[4];
+       union {
+               __be16 optical_wavelength;
+               u8 cable_spec;
+       };
+       u8 reserved62;
+       u8 cc_base;
+};
+
+struct __packed sfp_eeprom_ext {
+       __be16 options;
+       u8 br_max;
+       u8 br_min;
+       char vendor_sn[16];
+       char datecode[8];
+       u8 diagmon;
+       u8 enhopts;
+       u8 sff8472_compliance;
+       u8 cc_ext;
+};
+
+struct __packed sfp_eeprom_id {
+       struct sfp_eeprom_base base;
+       struct sfp_eeprom_ext ext;
+};
+
+/* SFP EEPROM registers */
+enum {
+       SFP_PHYS_ID                     = 0x00,
+       SFP_PHYS_EXT_ID                 = 0x01,
+       SFP_CONNECTOR                   = 0x02,
+       SFP_COMPLIANCE                  = 0x03,
+       SFP_ENCODING                    = 0x0b,
+       SFP_BR_NOMINAL                  = 0x0c,
+       SFP_RATE_ID                     = 0x0d,
+       SFP_LINK_LEN_SM_KM              = 0x0e,
+       SFP_LINK_LEN_SM_100M            = 0x0f,
+       SFP_LINK_LEN_50UM_OM2_10M       = 0x10,
+       SFP_LINK_LEN_62_5UM_OM1_10M     = 0x11,
+       SFP_LINK_LEN_COPPER_1M          = 0x12,
+       SFP_LINK_LEN_50UM_OM4_10M       = 0x12,
+       SFP_LINK_LEN_50UM_OM3_10M       = 0x13,
+       SFP_VENDOR_NAME                 = 0x14,
+       SFP_VENDOR_OUI                  = 0x25,
+       SFP_VENDOR_PN                   = 0x28,
+       SFP_VENDOR_REV                  = 0x38,
+       SFP_OPTICAL_WAVELENGTH_MSB      = 0x3c,
+       SFP_OPTICAL_WAVELENGTH_LSB      = 0x3d,
+       SFP_CABLE_SPEC                  = 0x3c,
+       SFP_CC_BASE                     = 0x3f,
+       SFP_OPTIONS                     = 0x40, /* 2 bytes, MSB, LSB */
+       SFP_BR_MAX                      = 0x42,
+       SFP_BR_MIN                      = 0x43,
+       SFP_VENDOR_SN                   = 0x44,
+       SFP_DATECODE                    = 0x54,
+       SFP_DIAGMON                     = 0x5c,
+       SFP_ENHOPTS                     = 0x5d,
+       SFP_SFF8472_COMPLIANCE          = 0x5e,
+       SFP_CC_EXT                      = 0x5f,
+
+       SFP_PHYS_ID_SFP                 = 0x03,
+       SFP_PHYS_EXT_ID_SFP             = 0x04,
+       SFP_CONNECTOR_UNSPEC            = 0x00,
+       /* codes 01-05 not supportable on SFP, but some modules have single SC */
+       SFP_CONNECTOR_SC                = 0x01,
+       SFP_CONNECTOR_FIBERJACK         = 0x06,
+       SFP_CONNECTOR_LC                = 0x07,
+       SFP_CONNECTOR_MT_RJ             = 0x08,
+       SFP_CONNECTOR_MU                = 0x09,
+       SFP_CONNECTOR_SG                = 0x0a,
+       SFP_CONNECTOR_OPTICAL_PIGTAIL   = 0x0b,
+       SFP_CONNECTOR_MPO_1X12          = 0x0c,
+       SFP_CONNECTOR_MPO_2X16          = 0x0d,
+       SFP_CONNECTOR_HSSDC_II          = 0x20,
+       SFP_CONNECTOR_COPPER_PIGTAIL    = 0x21,
+       SFP_CONNECTOR_RJ45              = 0x22,
+       SFP_CONNECTOR_NOSEPARATE        = 0x23,
+       SFP_CONNECTOR_MXC_2X16          = 0x24,
+       SFP_ENCODING_UNSPEC             = 0x00,
+       SFP_ENCODING_8B10B              = 0x01,
+       SFP_ENCODING_4B5B               = 0x02,
+       SFP_ENCODING_NRZ                = 0x03,
+       SFP_ENCODING_8472_MANCHESTER    = 0x04,
+       SFP_ENCODING_8472_SONET         = 0x05,
+       SFP_ENCODING_8472_64B66B        = 0x06,
+       SFP_ENCODING_256B257B           = 0x07,
+       SFP_ENCODING_PAM4               = 0x08,
+       SFP_OPTIONS_HIGH_POWER_LEVEL    = BIT(13),
+       SFP_OPTIONS_PAGING_A2           = BIT(12),
+       SFP_OPTIONS_RETIMER             = BIT(11),
+       SFP_OPTIONS_COOLED_XCVR         = BIT(10),
+       SFP_OPTIONS_POWER_DECL          = BIT(9),
+       SFP_OPTIONS_RX_LINEAR_OUT       = BIT(8),
+       SFP_OPTIONS_RX_DECISION_THRESH  = BIT(7),
+       SFP_OPTIONS_TUNABLE_TX          = BIT(6),
+       SFP_OPTIONS_RATE_SELECT         = BIT(5),
+       SFP_OPTIONS_TX_DISABLE          = BIT(4),
+       SFP_OPTIONS_TX_FAULT            = BIT(3),
+       SFP_OPTIONS_LOS_INVERTED        = BIT(2),
+       SFP_OPTIONS_LOS_NORMAL          = BIT(1),
+       SFP_DIAGMON_DDM                 = BIT(6),
+       SFP_DIAGMON_INT_CAL             = BIT(5),
+       SFP_DIAGMON_EXT_CAL             = BIT(4),
+       SFP_DIAGMON_RXPWR_AVG           = BIT(3),
+       SFP_DIAGMON_ADDRMODE            = BIT(2),
+       SFP_ENHOPTS_ALARMWARN           = BIT(7),
+       SFP_ENHOPTS_SOFT_TX_DISABLE     = BIT(6),
+       SFP_ENHOPTS_SOFT_TX_FAULT       = BIT(5),
+       SFP_ENHOPTS_SOFT_RX_LOS         = BIT(4),
+       SFP_ENHOPTS_SOFT_RATE_SELECT    = BIT(3),
+       SFP_ENHOPTS_APP_SELECT_SFF8079  = BIT(2),
+       SFP_ENHOPTS_SOFT_RATE_SFF8431   = BIT(1),
+       SFP_SFF8472_COMPLIANCE_NONE     = 0x00,
+       SFP_SFF8472_COMPLIANCE_REV9_3   = 0x01,
+       SFP_SFF8472_COMPLIANCE_REV9_5   = 0x02,
+       SFP_SFF8472_COMPLIANCE_REV10_2  = 0x03,
+       SFP_SFF8472_COMPLIANCE_REV10_4  = 0x04,
+       SFP_SFF8472_COMPLIANCE_REV11_0  = 0x05,
+       SFP_SFF8472_COMPLIANCE_REV11_3  = 0x06,
+       SFP_SFF8472_COMPLIANCE_REV11_4  = 0x07,
+       SFP_SFF8472_COMPLIANCE_REV12_0  = 0x08,
+};
+
+/* SFP Diagnostics */
+enum {
+       /* Alarm and warnings stored MSB at lower address then LSB */
+       SFP_TEMP_HIGH_ALARM             = 0x00,
+       SFP_TEMP_LOW_ALARM              = 0x02,
+       SFP_TEMP_HIGH_WARN              = 0x04,
+       SFP_TEMP_LOW_WARN               = 0x06,
+       SFP_VOLT_HIGH_ALARM             = 0x08,
+       SFP_VOLT_LOW_ALARM              = 0x0a,
+       SFP_VOLT_HIGH_WARN              = 0x0c,
+       SFP_VOLT_LOW_WARN               = 0x0e,
+       SFP_BIAS_HIGH_ALARM             = 0x10,
+       SFP_BIAS_LOW_ALARM              = 0x12,
+       SFP_BIAS_HIGH_WARN              = 0x14,
+       SFP_BIAS_LOW_WARN               = 0x16,
+       SFP_TXPWR_HIGH_ALARM            = 0x18,
+       SFP_TXPWR_LOW_ALARM             = 0x1a,
+       SFP_TXPWR_HIGH_WARN             = 0x1c,
+       SFP_TXPWR_LOW_WARN              = 0x1e,
+       SFP_RXPWR_HIGH_ALARM            = 0x20,
+       SFP_RXPWR_LOW_ALARM             = 0x22,
+       SFP_RXPWR_HIGH_WARN             = 0x24,
+       SFP_RXPWR_LOW_WARN              = 0x26,
+       SFP_LASER_TEMP_HIGH_ALARM       = 0x28,
+       SFP_LASER_TEMP_LOW_ALARM        = 0x2a,
+       SFP_LASER_TEMP_HIGH_WARN        = 0x2c,
+       SFP_LASER_TEMP_LOW_WARN         = 0x2e,
+       SFP_TEC_CUR_HIGH_ALARM          = 0x30,
+       SFP_TEC_CUR_LOW_ALARM           = 0x32,
+       SFP_TEC_CUR_HIGH_WARN           = 0x34,
+       SFP_TEC_CUR_LOW_WARN            = 0x36,
+       SFP_CAL_RXPWR4                  = 0x38,
+       SFP_CAL_RXPWR3                  = 0x3c,
+       SFP_CAL_RXPWR2                  = 0x40,
+       SFP_CAL_RXPWR1                  = 0x44,
+       SFP_CAL_RXPWR0                  = 0x48,
+       SFP_CAL_TXI_SLOPE               = 0x4c,
+       SFP_CAL_TXI_OFFSET              = 0x4e,
+       SFP_CAL_TXPWR_SLOPE             = 0x50,
+       SFP_CAL_TXPWR_OFFSET            = 0x52,
+       SFP_CAL_T_SLOPE                 = 0x54,
+       SFP_CAL_T_OFFSET                = 0x56,
+       SFP_CAL_V_SLOPE                 = 0x58,
+       SFP_CAL_V_OFFSET                = 0x5a,
+       SFP_CHKSUM                      = 0x5f,
+
+       SFP_TEMP                        = 0x60,
+       SFP_VCC                         = 0x62,
+       SFP_TX_BIAS                     = 0x64,
+       SFP_TX_POWER                    = 0x66,
+       SFP_RX_POWER                    = 0x68,
+       SFP_LASER_TEMP                  = 0x6a,
+       SFP_TEC_CUR                     = 0x6c,
+
+       SFP_STATUS                      = 0x6e,
+       SFP_ALARM                       = 0x70,
+
+       SFP_EXT_STATUS                  = 0x76,
+       SFP_VSL                         = 0x78,
+       SFP_PAGE                        = 0x7f,
+};
+
+struct device_node;
+struct ethtool_eeprom;
+struct ethtool_modinfo;
+struct net_device;
+struct sfp_bus;
+
+struct sfp_upstream_ops {
+       int (*module_insert)(void *, const struct sfp_eeprom_id *id);
+       void (*module_remove)(void *);
+       void (*link_down)(void *);
+       void (*link_up)(void *);
+       int (*connect_phy)(void *, struct phy_device *);
+       void (*disconnect_phy)(void *);
+};
+
+#if IS_ENABLED(CONFIG_SFP)
+int sfp_parse_port(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+                  unsigned long *support);
+phy_interface_t sfp_parse_interface(struct sfp_bus *bus,
+                                   const struct sfp_eeprom_id *id);
+void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+                      unsigned long *support);
+
+int sfp_get_module_info(struct sfp_bus *bus, struct ethtool_modinfo *modinfo);
+int sfp_get_module_eeprom(struct sfp_bus *bus, struct ethtool_eeprom *ee,
+                         u8 *data);
+void sfp_upstream_start(struct sfp_bus *bus);
+void sfp_upstream_stop(struct sfp_bus *bus);
+struct sfp_bus *sfp_register_upstream(struct device_node *np,
+                                     struct net_device *ndev, void *upstream,
+                                     const struct sfp_upstream_ops *ops);
+void sfp_unregister_upstream(struct sfp_bus *bus);
+#else
+static inline int sfp_parse_port(struct sfp_bus *bus,
+                                const struct sfp_eeprom_id *id,
+                                unsigned long *support)
+{
+       return PORT_OTHER;
+}
+
+static inline phy_interface_t sfp_parse_interface(struct sfp_bus *bus,
+                                               const struct sfp_eeprom_id *id)
+{
+       return PHY_INTERFACE_MODE_NA;
+}
+
+static inline void sfp_parse_support(struct sfp_bus *bus,
+                                    const struct sfp_eeprom_id *id,
+                                    unsigned long *support)
+{
+}
+
+static inline int sfp_get_module_info(struct sfp_bus *bus,
+                                     struct ethtool_modinfo *modinfo)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline int sfp_get_module_eeprom(struct sfp_bus *bus,
+                                       struct ethtool_eeprom *ee, u8 *data)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline void sfp_upstream_start(struct sfp_bus *bus)
+{
+}
+
+static inline void sfp_upstream_stop(struct sfp_bus *bus)
+{
+}
+
+static inline struct sfp_bus *sfp_register_upstream(struct device_node *np,
+       struct net_device *ndev, void *upstream,
+       const struct sfp_upstream_ops *ops)
+{
+       return (struct sfp_bus *)-1;
+}
+
+static inline void sfp_unregister_upstream(struct sfp_bus *bus)
+{
+}
+#endif
+
+#endif