2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-27 14:43:58 +08:00
linux-next/net/dsa/slave.c
Scott Feldman 3563606258 switchdev: convert STP update to switchdev attr set
STP update is just a settable port attribute, so convert
switchdev_port_stp_update to an attr set.

For DSA, the prepare phase is skipped and STP updates are only done in the
commit phase.  This is because currently the DSA drivers don't need to
allocate any memory for STP updates and the STP update will not fail to HW
(unless something horrible goes wrong on the MDIO bus, in which case the
prepare phase wouldn't have been able to predict anyway).

Signed-off-by: Scott Feldman <sfeldma@gmail.com>
Acked-by: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-05-12 18:43:53 -04:00

1001 lines
24 KiB
C

/*
* net/dsa/slave.c - Slave device handling
* Copyright (c) 2008-2009 Marvell Semiconductor
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/list.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/phy_fixed.h>
#include <linux/of_net.h>
#include <linux/of_mdio.h>
#include <net/rtnetlink.h>
#include <net/switchdev.h>
#include <linux/if_bridge.h>
#include "dsa_priv.h"
/* slave mii_bus handling ***************************************************/
static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg)
{
struct dsa_switch *ds = bus->priv;
if (ds->phys_mii_mask & (1 << addr))
return ds->drv->phy_read(ds, addr, reg);
return 0xffff;
}
static int dsa_slave_phy_write(struct mii_bus *bus, int addr, int reg, u16 val)
{
struct dsa_switch *ds = bus->priv;
if (ds->phys_mii_mask & (1 << addr))
return ds->drv->phy_write(ds, addr, reg, val);
return 0;
}
void dsa_slave_mii_bus_init(struct dsa_switch *ds)
{
ds->slave_mii_bus->priv = (void *)ds;
ds->slave_mii_bus->name = "dsa slave smi";
ds->slave_mii_bus->read = dsa_slave_phy_read;
ds->slave_mii_bus->write = dsa_slave_phy_write;
snprintf(ds->slave_mii_bus->id, MII_BUS_ID_SIZE, "dsa-%d:%.2x",
ds->index, ds->pd->sw_addr);
ds->slave_mii_bus->parent = ds->master_dev;
ds->slave_mii_bus->phy_mask = ~ds->phys_mii_mask;
}
/* slave device handling ****************************************************/
static int dsa_slave_get_iflink(const struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
return p->parent->dst->master_netdev->ifindex;
}
static inline bool dsa_port_is_bridged(struct dsa_slave_priv *p)
{
return !!p->bridge_dev;
}
static int dsa_slave_open(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct net_device *master = p->parent->dst->master_netdev;
struct dsa_switch *ds = p->parent;
u8 stp_state = dsa_port_is_bridged(p) ?
BR_STATE_BLOCKING : BR_STATE_FORWARDING;
int err;
if (!(master->flags & IFF_UP))
return -ENETDOWN;
if (!ether_addr_equal(dev->dev_addr, master->dev_addr)) {
err = dev_uc_add(master, dev->dev_addr);
if (err < 0)
goto out;
}
if (dev->flags & IFF_ALLMULTI) {
err = dev_set_allmulti(master, 1);
if (err < 0)
goto del_unicast;
}
if (dev->flags & IFF_PROMISC) {
err = dev_set_promiscuity(master, 1);
if (err < 0)
goto clear_allmulti;
}
if (ds->drv->port_enable) {
err = ds->drv->port_enable(ds, p->port, p->phy);
if (err)
goto clear_promisc;
}
if (ds->drv->port_stp_update)
ds->drv->port_stp_update(ds, p->port, stp_state);
if (p->phy)
phy_start(p->phy);
return 0;
clear_promisc:
if (dev->flags & IFF_PROMISC)
dev_set_promiscuity(master, 0);
clear_allmulti:
if (dev->flags & IFF_ALLMULTI)
dev_set_allmulti(master, -1);
del_unicast:
if (!ether_addr_equal(dev->dev_addr, master->dev_addr))
dev_uc_del(master, dev->dev_addr);
out:
return err;
}
static int dsa_slave_close(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct net_device *master = p->parent->dst->master_netdev;
struct dsa_switch *ds = p->parent;
if (p->phy)
phy_stop(p->phy);
dev_mc_unsync(master, dev);
dev_uc_unsync(master, dev);
if (dev->flags & IFF_ALLMULTI)
dev_set_allmulti(master, -1);
if (dev->flags & IFF_PROMISC)
dev_set_promiscuity(master, -1);
if (!ether_addr_equal(dev->dev_addr, master->dev_addr))
dev_uc_del(master, dev->dev_addr);
if (ds->drv->port_disable)
ds->drv->port_disable(ds, p->port, p->phy);
if (ds->drv->port_stp_update)
ds->drv->port_stp_update(ds, p->port, BR_STATE_DISABLED);
return 0;
}
static void dsa_slave_change_rx_flags(struct net_device *dev, int change)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct net_device *master = p->parent->dst->master_netdev;
if (change & IFF_ALLMULTI)
dev_set_allmulti(master, dev->flags & IFF_ALLMULTI ? 1 : -1);
if (change & IFF_PROMISC)
dev_set_promiscuity(master, dev->flags & IFF_PROMISC ? 1 : -1);
}
static void dsa_slave_set_rx_mode(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct net_device *master = p->parent->dst->master_netdev;
dev_mc_sync(master, dev);
dev_uc_sync(master, dev);
}
static int dsa_slave_set_mac_address(struct net_device *dev, void *a)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct net_device *master = p->parent->dst->master_netdev;
struct sockaddr *addr = a;
int err;
if (!is_valid_ether_addr(addr->sa_data))
return -EADDRNOTAVAIL;
if (!(dev->flags & IFF_UP))
goto out;
if (!ether_addr_equal(addr->sa_data, master->dev_addr)) {
err = dev_uc_add(master, addr->sa_data);
if (err < 0)
return err;
}
if (!ether_addr_equal(dev->dev_addr, master->dev_addr))
dev_uc_del(master, dev->dev_addr);
out:
ether_addr_copy(dev->dev_addr, addr->sa_data);
return 0;
}
static int dsa_slave_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr, u16 vid, u16 nlm_flags)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret = -EOPNOTSUPP;
if (ds->drv->fdb_add)
ret = ds->drv->fdb_add(ds, p->port, addr, vid);
return ret;
}
static int dsa_slave_fdb_del(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev,
const unsigned char *addr, u16 vid)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret = -EOPNOTSUPP;
if (ds->drv->fdb_del)
ret = ds->drv->fdb_del(ds, p->port, addr, vid);
return ret;
}
static int dsa_slave_fill_info(struct net_device *dev, struct sk_buff *skb,
const unsigned char *addr, u16 vid,
bool is_static,
u32 portid, u32 seq, int type,
unsigned int flags)
{
struct nlmsghdr *nlh;
struct ndmsg *ndm;
nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags);
if (!nlh)
return -EMSGSIZE;
ndm = nlmsg_data(nlh);
ndm->ndm_family = AF_BRIDGE;
ndm->ndm_pad1 = 0;
ndm->ndm_pad2 = 0;
ndm->ndm_flags = NTF_EXT_LEARNED;
ndm->ndm_type = 0;
ndm->ndm_ifindex = dev->ifindex;
ndm->ndm_state = is_static ? NUD_NOARP : NUD_REACHABLE;
if (nla_put(skb, NDA_LLADDR, ETH_ALEN, addr))
goto nla_put_failure;
if (vid && nla_put_u16(skb, NDA_VLAN, vid))
goto nla_put_failure;
nlmsg_end(skb, nlh);
return 0;
nla_put_failure:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
/* Dump information about entries, in response to GETNEIGH */
static int dsa_slave_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
struct net_device *dev,
struct net_device *filter_dev, int idx)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
unsigned char addr[ETH_ALEN] = { 0 };
int ret;
if (!ds->drv->fdb_getnext)
return -EOPNOTSUPP;
for (; ; idx++) {
bool is_static;
ret = ds->drv->fdb_getnext(ds, p->port, addr, &is_static);
if (ret < 0)
break;
if (idx < cb->args[0])
continue;
ret = dsa_slave_fill_info(dev, skb, addr, 0,
is_static,
NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
RTM_NEWNEIGH, NLM_F_MULTI);
if (ret < 0)
break;
}
return idx;
}
static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct dsa_slave_priv *p = netdev_priv(dev);
if (p->phy != NULL)
return phy_mii_ioctl(p->phy, ifr, cmd);
return -EOPNOTSUPP;
}
/* Return a bitmask of all ports being currently bridged within a given bridge
* device. Note that on leave, the mask will still return the bitmask of ports
* currently bridged, prior to port removal, and this is exactly what we want.
*/
static u32 dsa_slave_br_port_mask(struct dsa_switch *ds,
struct net_device *bridge)
{
struct dsa_slave_priv *p;
unsigned int port;
u32 mask = 0;
for (port = 0; port < DSA_MAX_PORTS; port++) {
if (!dsa_is_port_initialized(ds, port))
continue;
p = netdev_priv(ds->ports[port]);
if (ds->ports[port]->priv_flags & IFF_BRIDGE_PORT &&
p->bridge_dev == bridge)
mask |= 1 << port;
}
return mask;
}
static int dsa_slave_stp_update(struct net_device *dev, u8 state)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret = -EOPNOTSUPP;
if (ds->drv->port_stp_update)
ret = ds->drv->port_stp_update(ds, p->port, state);
return ret;
}
static int dsa_slave_port_attr_set(struct net_device *dev,
struct switchdev_attr *attr)
{
int ret = 0;
switch (attr->id) {
case SWITCHDEV_ATTR_PORT_STP_STATE:
if (attr->trans == SWITCHDEV_TRANS_COMMIT)
ret = dsa_slave_stp_update(dev, attr->stp_state);
break;
default:
ret = -EOPNOTSUPP;
break;
}
return ret;
}
static int dsa_slave_bridge_port_join(struct net_device *dev,
struct net_device *br)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret = -EOPNOTSUPP;
p->bridge_dev = br;
if (ds->drv->port_join_bridge)
ret = ds->drv->port_join_bridge(ds, p->port,
dsa_slave_br_port_mask(ds, br));
return ret;
}
static int dsa_slave_bridge_port_leave(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret = -EOPNOTSUPP;
if (ds->drv->port_leave_bridge)
ret = ds->drv->port_leave_bridge(ds, p->port,
dsa_slave_br_port_mask(ds, p->bridge_dev));
p->bridge_dev = NULL;
/* Port left the bridge, put in BR_STATE_DISABLED by the bridge layer,
* so allow it to be in BR_STATE_FORWARDING to be kept functional
*/
dsa_slave_stp_update(dev, BR_STATE_FORWARDING);
return ret;
}
static int dsa_slave_port_attr_get(struct net_device *dev,
struct switchdev_attr *attr)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
switch (attr->id) {
case SWITCHDEV_ATTR_PORT_PARENT_ID:
attr->ppid.id_len = sizeof(ds->index);
memcpy(&attr->ppid.id, &ds->index, attr->ppid.id_len);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
return p->xmit(skb, dev);
}
static netdev_tx_t dsa_slave_notag_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
skb->dev = p->parent->dst->master_netdev;
dev_queue_xmit(skb);
return NETDEV_TX_OK;
}
/* ethtool operations *******************************************************/
static int
dsa_slave_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
struct dsa_slave_priv *p = netdev_priv(dev);
int err;
err = -EOPNOTSUPP;
if (p->phy != NULL) {
err = phy_read_status(p->phy);
if (err == 0)
err = phy_ethtool_gset(p->phy, cmd);
}
return err;
}
static int
dsa_slave_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
struct dsa_slave_priv *p = netdev_priv(dev);
if (p->phy != NULL)
return phy_ethtool_sset(p->phy, cmd);
return -EOPNOTSUPP;
}
static void dsa_slave_get_drvinfo(struct net_device *dev,
struct ethtool_drvinfo *drvinfo)
{
strlcpy(drvinfo->driver, "dsa", sizeof(drvinfo->driver));
strlcpy(drvinfo->version, dsa_driver_version, sizeof(drvinfo->version));
strlcpy(drvinfo->fw_version, "N/A", sizeof(drvinfo->fw_version));
strlcpy(drvinfo->bus_info, "platform", sizeof(drvinfo->bus_info));
}
static int dsa_slave_get_regs_len(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->get_regs_len)
return ds->drv->get_regs_len(ds, p->port);
return -EOPNOTSUPP;
}
static void
dsa_slave_get_regs(struct net_device *dev, struct ethtool_regs *regs, void *_p)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->get_regs)
ds->drv->get_regs(ds, p->port, regs, _p);
}
static int dsa_slave_nway_reset(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
if (p->phy != NULL)
return genphy_restart_aneg(p->phy);
return -EOPNOTSUPP;
}
static u32 dsa_slave_get_link(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
if (p->phy != NULL) {
genphy_update_link(p->phy);
return p->phy->link;
}
return -EOPNOTSUPP;
}
static int dsa_slave_get_eeprom_len(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->pd->eeprom_len)
return ds->pd->eeprom_len;
if (ds->drv->get_eeprom_len)
return ds->drv->get_eeprom_len(ds);
return 0;
}
static int dsa_slave_get_eeprom(struct net_device *dev,
struct ethtool_eeprom *eeprom, u8 *data)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->get_eeprom)
return ds->drv->get_eeprom(ds, eeprom, data);
return -EOPNOTSUPP;
}
static int dsa_slave_set_eeprom(struct net_device *dev,
struct ethtool_eeprom *eeprom, u8 *data)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->set_eeprom)
return ds->drv->set_eeprom(ds, eeprom, data);
return -EOPNOTSUPP;
}
static void dsa_slave_get_strings(struct net_device *dev,
uint32_t stringset, uint8_t *data)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (stringset == ETH_SS_STATS) {
int len = ETH_GSTRING_LEN;
strncpy(data, "tx_packets", len);
strncpy(data + len, "tx_bytes", len);
strncpy(data + 2 * len, "rx_packets", len);
strncpy(data + 3 * len, "rx_bytes", len);
if (ds->drv->get_strings != NULL)
ds->drv->get_strings(ds, p->port, data + 4 * len);
}
}
static void dsa_slave_get_ethtool_stats(struct net_device *dev,
struct ethtool_stats *stats,
uint64_t *data)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
data[0] = p->dev->stats.tx_packets;
data[1] = p->dev->stats.tx_bytes;
data[2] = p->dev->stats.rx_packets;
data[3] = p->dev->stats.rx_bytes;
if (ds->drv->get_ethtool_stats != NULL)
ds->drv->get_ethtool_stats(ds, p->port, data + 4);
}
static int dsa_slave_get_sset_count(struct net_device *dev, int sset)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (sset == ETH_SS_STATS) {
int count;
count = 4;
if (ds->drv->get_sset_count != NULL)
count += ds->drv->get_sset_count(ds);
return count;
}
return -EOPNOTSUPP;
}
static void dsa_slave_get_wol(struct net_device *dev, struct ethtool_wolinfo *w)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->get_wol)
ds->drv->get_wol(ds, p->port, w);
}
static int dsa_slave_set_wol(struct net_device *dev, struct ethtool_wolinfo *w)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret = -EOPNOTSUPP;
if (ds->drv->set_wol)
ret = ds->drv->set_wol(ds, p->port, w);
return ret;
}
static int dsa_slave_set_eee(struct net_device *dev, struct ethtool_eee *e)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret;
if (!ds->drv->set_eee)
return -EOPNOTSUPP;
ret = ds->drv->set_eee(ds, p->port, p->phy, e);
if (ret)
return ret;
if (p->phy)
ret = phy_ethtool_set_eee(p->phy, e);
return ret;
}
static int dsa_slave_get_eee(struct net_device *dev, struct ethtool_eee *e)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
int ret;
if (!ds->drv->get_eee)
return -EOPNOTSUPP;
ret = ds->drv->get_eee(ds, p->port, e);
if (ret)
return ret;
if (p->phy)
ret = phy_ethtool_get_eee(p->phy, e);
return ret;
}
static const struct ethtool_ops dsa_slave_ethtool_ops = {
.get_settings = dsa_slave_get_settings,
.set_settings = dsa_slave_set_settings,
.get_drvinfo = dsa_slave_get_drvinfo,
.get_regs_len = dsa_slave_get_regs_len,
.get_regs = dsa_slave_get_regs,
.nway_reset = dsa_slave_nway_reset,
.get_link = dsa_slave_get_link,
.get_eeprom_len = dsa_slave_get_eeprom_len,
.get_eeprom = dsa_slave_get_eeprom,
.set_eeprom = dsa_slave_set_eeprom,
.get_strings = dsa_slave_get_strings,
.get_ethtool_stats = dsa_slave_get_ethtool_stats,
.get_sset_count = dsa_slave_get_sset_count,
.set_wol = dsa_slave_set_wol,
.get_wol = dsa_slave_get_wol,
.set_eee = dsa_slave_set_eee,
.get_eee = dsa_slave_get_eee,
};
static const struct net_device_ops dsa_slave_netdev_ops = {
.ndo_open = dsa_slave_open,
.ndo_stop = dsa_slave_close,
.ndo_start_xmit = dsa_slave_xmit,
.ndo_change_rx_flags = dsa_slave_change_rx_flags,
.ndo_set_rx_mode = dsa_slave_set_rx_mode,
.ndo_set_mac_address = dsa_slave_set_mac_address,
.ndo_fdb_add = dsa_slave_fdb_add,
.ndo_fdb_del = dsa_slave_fdb_del,
.ndo_fdb_dump = dsa_slave_fdb_dump,
.ndo_do_ioctl = dsa_slave_ioctl,
.ndo_get_iflink = dsa_slave_get_iflink,
};
static const struct switchdev_ops dsa_slave_switchdev_ops = {
.switchdev_port_attr_get = dsa_slave_port_attr_get,
.switchdev_port_attr_set = dsa_slave_port_attr_set,
};
static void dsa_slave_adjust_link(struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
unsigned int status_changed = 0;
if (p->old_link != p->phy->link) {
status_changed = 1;
p->old_link = p->phy->link;
}
if (p->old_duplex != p->phy->duplex) {
status_changed = 1;
p->old_duplex = p->phy->duplex;
}
if (p->old_pause != p->phy->pause) {
status_changed = 1;
p->old_pause = p->phy->pause;
}
if (ds->drv->adjust_link && status_changed)
ds->drv->adjust_link(ds, p->port, p->phy);
if (status_changed)
phy_print_status(p->phy);
}
static int dsa_slave_fixed_link_update(struct net_device *dev,
struct fixed_phy_status *status)
{
struct dsa_slave_priv *p = netdev_priv(dev);
struct dsa_switch *ds = p->parent;
if (ds->drv->fixed_link_update)
ds->drv->fixed_link_update(ds, p->port, status);
return 0;
}
/* slave device setup *******************************************************/
static int dsa_slave_phy_connect(struct dsa_slave_priv *p,
struct net_device *slave_dev,
int addr)
{
struct dsa_switch *ds = p->parent;
p->phy = ds->slave_mii_bus->phy_map[addr];
if (!p->phy)
return -ENODEV;
/* Use already configured phy mode */
p->phy_interface = p->phy->interface;
phy_connect_direct(slave_dev, p->phy, dsa_slave_adjust_link,
p->phy_interface);
return 0;
}
static int dsa_slave_phy_setup(struct dsa_slave_priv *p,
struct net_device *slave_dev)
{
struct dsa_switch *ds = p->parent;
struct dsa_chip_data *cd = ds->pd;
struct device_node *phy_dn, *port_dn;
bool phy_is_fixed = false;
u32 phy_flags = 0;
int mode, ret;
port_dn = cd->port_dn[p->port];
mode = of_get_phy_mode(port_dn);
if (mode < 0)
mode = PHY_INTERFACE_MODE_NA;
p->phy_interface = mode;
phy_dn = of_parse_phandle(port_dn, "phy-handle", 0);
if (of_phy_is_fixed_link(port_dn)) {
/* In the case of a fixed PHY, the DT node associated
* to the fixed PHY is the Port DT node
*/
ret = of_phy_register_fixed_link(port_dn);
if (ret) {
netdev_err(slave_dev, "failed to register fixed PHY\n");
return ret;
}
phy_is_fixed = true;
phy_dn = port_dn;
}
if (ds->drv->get_phy_flags)
phy_flags = ds->drv->get_phy_flags(ds, p->port);
if (phy_dn) {
ret = of_mdio_parse_addr(&slave_dev->dev, phy_dn);
/* If this PHY address is part of phys_mii_mask, which means
* that we need to divert reads and writes to/from it, then we
* want to bind this device using the slave MII bus created by
* DSA to make that happen.
*/
if (!phy_is_fixed && ret >= 0 &&
(ds->phys_mii_mask & (1 << ret))) {
ret = dsa_slave_phy_connect(p, slave_dev, ret);
if (ret)
return ret;
} else {
p->phy = of_phy_connect(slave_dev, phy_dn,
dsa_slave_adjust_link,
phy_flags,
p->phy_interface);
}
}
if (p->phy && phy_is_fixed)
fixed_phy_set_link_update(p->phy, dsa_slave_fixed_link_update);
/* We could not connect to a designated PHY, so use the switch internal
* MDIO bus instead
*/
if (!p->phy) {
ret = dsa_slave_phy_connect(p, slave_dev, p->port);
if (ret)
return ret;
} else {
netdev_info(slave_dev, "attached PHY at address %d [%s]\n",
p->phy->addr, p->phy->drv->name);
}
return 0;
}
static struct lock_class_key dsa_slave_netdev_xmit_lock_key;
static void dsa_slave_set_lockdep_class_one(struct net_device *dev,
struct netdev_queue *txq,
void *_unused)
{
lockdep_set_class(&txq->_xmit_lock,
&dsa_slave_netdev_xmit_lock_key);
}
int dsa_slave_suspend(struct net_device *slave_dev)
{
struct dsa_slave_priv *p = netdev_priv(slave_dev);
if (p->phy) {
phy_stop(p->phy);
p->old_pause = -1;
p->old_link = -1;
p->old_duplex = -1;
phy_suspend(p->phy);
}
return 0;
}
int dsa_slave_resume(struct net_device *slave_dev)
{
struct dsa_slave_priv *p = netdev_priv(slave_dev);
netif_device_attach(slave_dev);
if (p->phy) {
phy_resume(p->phy);
phy_start(p->phy);
}
return 0;
}
int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
int port, char *name)
{
struct net_device *master = ds->dst->master_netdev;
struct net_device *slave_dev;
struct dsa_slave_priv *p;
int ret;
slave_dev = alloc_netdev(sizeof(struct dsa_slave_priv), name,
NET_NAME_UNKNOWN, ether_setup);
if (slave_dev == NULL)
return -ENOMEM;
slave_dev->features = master->vlan_features;
slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
eth_hw_addr_inherit(slave_dev, master);
slave_dev->tx_queue_len = 0;
slave_dev->netdev_ops = &dsa_slave_netdev_ops;
slave_dev->switchdev_ops = &dsa_slave_switchdev_ops;
netdev_for_each_tx_queue(slave_dev, dsa_slave_set_lockdep_class_one,
NULL);
SET_NETDEV_DEV(slave_dev, parent);
slave_dev->dev.of_node = ds->pd->port_dn[port];
slave_dev->vlan_features = master->vlan_features;
p = netdev_priv(slave_dev);
p->dev = slave_dev;
p->parent = ds;
p->port = port;
switch (ds->dst->tag_protocol) {
#ifdef CONFIG_NET_DSA_TAG_DSA
case DSA_TAG_PROTO_DSA:
p->xmit = dsa_netdev_ops.xmit;
break;
#endif
#ifdef CONFIG_NET_DSA_TAG_EDSA
case DSA_TAG_PROTO_EDSA:
p->xmit = edsa_netdev_ops.xmit;
break;
#endif
#ifdef CONFIG_NET_DSA_TAG_TRAILER
case DSA_TAG_PROTO_TRAILER:
p->xmit = trailer_netdev_ops.xmit;
break;
#endif
#ifdef CONFIG_NET_DSA_TAG_BRCM
case DSA_TAG_PROTO_BRCM:
p->xmit = brcm_netdev_ops.xmit;
break;
#endif
default:
p->xmit = dsa_slave_notag_xmit;
break;
}
p->old_pause = -1;
p->old_link = -1;
p->old_duplex = -1;
ret = dsa_slave_phy_setup(p, slave_dev);
if (ret) {
free_netdev(slave_dev);
return ret;
}
ds->ports[port] = slave_dev;
ret = register_netdev(slave_dev);
if (ret) {
netdev_err(master, "error %d registering interface %s\n",
ret, slave_dev->name);
phy_disconnect(p->phy);
ds->ports[port] = NULL;
free_netdev(slave_dev);
return ret;
}
netif_carrier_off(slave_dev);
return 0;
}
static bool dsa_slave_dev_check(struct net_device *dev)
{
return dev->netdev_ops == &dsa_slave_netdev_ops;
}
static int dsa_slave_master_changed(struct net_device *dev)
{
struct net_device *master = netdev_master_upper_dev_get(dev);
struct dsa_slave_priv *p = netdev_priv(dev);
int err = 0;
if (master && master->rtnl_link_ops &&
!strcmp(master->rtnl_link_ops->kind, "bridge"))
err = dsa_slave_bridge_port_join(dev, master);
else if (dsa_port_is_bridged(p))
err = dsa_slave_bridge_port_leave(dev);
return err;
}
int dsa_slave_netdevice_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev;
int err = 0;
switch (event) {
case NETDEV_CHANGEUPPER:
dev = netdev_notifier_info_to_dev(ptr);
if (!dsa_slave_dev_check(dev))
goto out;
err = dsa_slave_master_changed(dev);
if (err)
netdev_warn(dev, "failed to reflect master change\n");
break;
}
out:
return NOTIFY_DONE;
}