mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 01:08:03 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
27
net/dsa/Kconfig
Normal file
27
net/dsa/Kconfig
Normal file
|
@ -0,0 +1,27 @@
|
|||
config HAVE_NET_DSA
|
||||
def_bool y
|
||||
depends on NETDEVICES && !S390
|
||||
|
||||
# Drivers must select NET_DSA and the appropriate tagging format
|
||||
|
||||
config NET_DSA
|
||||
tristate
|
||||
depends on HAVE_NET_DSA
|
||||
select PHYLIB
|
||||
|
||||
if NET_DSA
|
||||
|
||||
# tagging formats
|
||||
config NET_DSA_TAG_BRCM
|
||||
bool
|
||||
|
||||
config NET_DSA_TAG_DSA
|
||||
bool
|
||||
|
||||
config NET_DSA_TAG_EDSA
|
||||
bool
|
||||
|
||||
config NET_DSA_TAG_TRAILER
|
||||
bool
|
||||
|
||||
endif
|
9
net/dsa/Makefile
Normal file
9
net/dsa/Makefile
Normal file
|
@ -0,0 +1,9 @@
|
|||
# the core
|
||||
obj-$(CONFIG_NET_DSA) += dsa_core.o
|
||||
dsa_core-y += dsa.o slave.o
|
||||
|
||||
# tagging formats
|
||||
dsa_core-$(CONFIG_NET_DSA_TAG_BRCM) += tag_brcm.o
|
||||
dsa_core-$(CONFIG_NET_DSA_TAG_DSA) += tag_dsa.o
|
||||
dsa_core-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o
|
||||
dsa_core-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
|
780
net/dsa/dsa.c
Normal file
780
net/dsa/dsa.c
Normal file
|
@ -0,0 +1,780 @@
|
|||
/*
|
||||
* net/dsa/dsa.c - Hardware switch handling
|
||||
* Copyright (c) 2008-2009 Marvell Semiconductor
|
||||
* Copyright (c) 2013 Florian Fainelli <florian@openwrt.org>
|
||||
*
|
||||
* 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/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <net/dsa.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include "dsa_priv.h"
|
||||
|
||||
char dsa_driver_version[] = "0.1";
|
||||
|
||||
|
||||
/* switch driver registration ***********************************************/
|
||||
static DEFINE_MUTEX(dsa_switch_drivers_mutex);
|
||||
static LIST_HEAD(dsa_switch_drivers);
|
||||
|
||||
void register_switch_driver(struct dsa_switch_driver *drv)
|
||||
{
|
||||
mutex_lock(&dsa_switch_drivers_mutex);
|
||||
list_add_tail(&drv->list, &dsa_switch_drivers);
|
||||
mutex_unlock(&dsa_switch_drivers_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_switch_driver);
|
||||
|
||||
void unregister_switch_driver(struct dsa_switch_driver *drv)
|
||||
{
|
||||
mutex_lock(&dsa_switch_drivers_mutex);
|
||||
list_del_init(&drv->list);
|
||||
mutex_unlock(&dsa_switch_drivers_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unregister_switch_driver);
|
||||
|
||||
static struct dsa_switch_driver *
|
||||
dsa_switch_probe(struct device *host_dev, int sw_addr, char **_name)
|
||||
{
|
||||
struct dsa_switch_driver *ret;
|
||||
struct list_head *list;
|
||||
char *name;
|
||||
|
||||
ret = NULL;
|
||||
name = NULL;
|
||||
|
||||
mutex_lock(&dsa_switch_drivers_mutex);
|
||||
list_for_each(list, &dsa_switch_drivers) {
|
||||
struct dsa_switch_driver *drv;
|
||||
|
||||
drv = list_entry(list, struct dsa_switch_driver, list);
|
||||
|
||||
name = drv->probe(host_dev, sw_addr);
|
||||
if (name != NULL) {
|
||||
ret = drv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&dsa_switch_drivers_mutex);
|
||||
|
||||
*_name = name;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/* basic switch operations **************************************************/
|
||||
static struct dsa_switch *
|
||||
dsa_switch_setup(struct dsa_switch_tree *dst, int index,
|
||||
struct device *parent, struct device *host_dev)
|
||||
{
|
||||
struct dsa_chip_data *pd = dst->pd->chip + index;
|
||||
struct dsa_switch_driver *drv;
|
||||
struct dsa_switch *ds;
|
||||
int ret;
|
||||
char *name;
|
||||
int i;
|
||||
bool valid_name_found = false;
|
||||
|
||||
/*
|
||||
* Probe for switch model.
|
||||
*/
|
||||
drv = dsa_switch_probe(host_dev, pd->sw_addr, &name);
|
||||
if (drv == NULL) {
|
||||
printk(KERN_ERR "%s[%d]: could not detect attached switch\n",
|
||||
dst->master_netdev->name, index);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
printk(KERN_INFO "%s[%d]: detected a %s switch\n",
|
||||
dst->master_netdev->name, index, name);
|
||||
|
||||
|
||||
/*
|
||||
* Allocate and initialise switch state.
|
||||
*/
|
||||
ds = kzalloc(sizeof(*ds) + drv->priv_size, GFP_KERNEL);
|
||||
if (ds == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ds->dst = dst;
|
||||
ds->index = index;
|
||||
ds->pd = dst->pd->chip + index;
|
||||
ds->drv = drv;
|
||||
ds->master_dev = host_dev;
|
||||
|
||||
/*
|
||||
* Validate supplied switch configuration.
|
||||
*/
|
||||
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
||||
char *name;
|
||||
|
||||
name = pd->port_names[i];
|
||||
if (name == NULL)
|
||||
continue;
|
||||
|
||||
if (!strcmp(name, "cpu")) {
|
||||
if (dst->cpu_switch != -1) {
|
||||
printk(KERN_ERR "multiple cpu ports?!\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
dst->cpu_switch = index;
|
||||
dst->cpu_port = i;
|
||||
} else if (!strcmp(name, "dsa")) {
|
||||
ds->dsa_port_mask |= 1 << i;
|
||||
} else {
|
||||
ds->phys_port_mask |= 1 << i;
|
||||
}
|
||||
valid_name_found = true;
|
||||
}
|
||||
|
||||
if (!valid_name_found && i == DSA_MAX_PORTS) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Make the built-in MII bus mask match the number of ports,
|
||||
* switch drivers can override this later
|
||||
*/
|
||||
ds->phys_mii_mask = ds->phys_port_mask;
|
||||
|
||||
/*
|
||||
* If the CPU connects to this switch, set the switch tree
|
||||
* tagging protocol to the preferred tagging format of this
|
||||
* switch.
|
||||
*/
|
||||
if (dst->cpu_switch == index) {
|
||||
switch (drv->tag_protocol) {
|
||||
#ifdef CONFIG_NET_DSA_TAG_DSA
|
||||
case DSA_TAG_PROTO_DSA:
|
||||
dst->rcv = dsa_netdev_ops.rcv;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_NET_DSA_TAG_EDSA
|
||||
case DSA_TAG_PROTO_EDSA:
|
||||
dst->rcv = edsa_netdev_ops.rcv;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_NET_DSA_TAG_TRAILER
|
||||
case DSA_TAG_PROTO_TRAILER:
|
||||
dst->rcv = trailer_netdev_ops.rcv;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_NET_DSA_TAG_BRCM
|
||||
case DSA_TAG_PROTO_BRCM:
|
||||
dst->rcv = brcm_netdev_ops.rcv;
|
||||
break;
|
||||
#endif
|
||||
case DSA_TAG_PROTO_NONE:
|
||||
break;
|
||||
default:
|
||||
ret = -ENOPROTOOPT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dst->tag_protocol = drv->tag_protocol;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do basic register setup.
|
||||
*/
|
||||
ret = drv->setup(ds);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = drv->set_addr(ds, dst->master_netdev->dev_addr);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ds->slave_mii_bus = mdiobus_alloc();
|
||||
if (ds->slave_mii_bus == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
dsa_slave_mii_bus_init(ds);
|
||||
|
||||
ret = mdiobus_register(ds->slave_mii_bus);
|
||||
if (ret < 0)
|
||||
goto out_free;
|
||||
|
||||
|
||||
/*
|
||||
* Create network devices for physical switch ports.
|
||||
*/
|
||||
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
||||
struct net_device *slave_dev;
|
||||
|
||||
if (!(ds->phys_port_mask & (1 << i)))
|
||||
continue;
|
||||
|
||||
slave_dev = dsa_slave_create(ds, parent, i, pd->port_names[i]);
|
||||
if (slave_dev == NULL) {
|
||||
printk(KERN_ERR "%s[%d]: can't create dsa "
|
||||
"slave device for port %d(%s)\n",
|
||||
dst->master_netdev->name,
|
||||
index, i, pd->port_names[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
ds->ports[i] = slave_dev;
|
||||
}
|
||||
|
||||
return ds;
|
||||
|
||||
out_free:
|
||||
mdiobus_free(ds->slave_mii_bus);
|
||||
out:
|
||||
kfree(ds);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static void dsa_switch_destroy(struct dsa_switch *ds)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int dsa_switch_suspend(struct dsa_switch *ds)
|
||||
{
|
||||
int i, ret = 0;
|
||||
|
||||
/* Suspend slave network devices */
|
||||
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
||||
if (!(ds->phys_port_mask & (1 << i)))
|
||||
continue;
|
||||
|
||||
ret = dsa_slave_suspend(ds->ports[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ds->drv->suspend)
|
||||
ret = ds->drv->suspend(ds);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsa_switch_resume(struct dsa_switch *ds)
|
||||
{
|
||||
int i, ret = 0;
|
||||
|
||||
if (ds->drv->resume)
|
||||
ret = ds->drv->resume(ds);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Resume slave network devices */
|
||||
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
||||
if (!(ds->phys_port_mask & (1 << i)))
|
||||
continue;
|
||||
|
||||
ret = dsa_slave_resume(ds->ports[i]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* link polling *************************************************************/
|
||||
static void dsa_link_poll_work(struct work_struct *ugly)
|
||||
{
|
||||
struct dsa_switch_tree *dst;
|
||||
int i;
|
||||
|
||||
dst = container_of(ugly, struct dsa_switch_tree, link_poll_work);
|
||||
|
||||
for (i = 0; i < dst->pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds = dst->ds[i];
|
||||
|
||||
if (ds != NULL && ds->drv->poll_link != NULL)
|
||||
ds->drv->poll_link(ds);
|
||||
}
|
||||
|
||||
mod_timer(&dst->link_poll_timer, round_jiffies(jiffies + HZ));
|
||||
}
|
||||
|
||||
static void dsa_link_poll_timer(unsigned long _dst)
|
||||
{
|
||||
struct dsa_switch_tree *dst = (void *)_dst;
|
||||
|
||||
schedule_work(&dst->link_poll_work);
|
||||
}
|
||||
|
||||
|
||||
/* platform driver init and cleanup *****************************************/
|
||||
static int dev_is_class(struct device *dev, void *class)
|
||||
{
|
||||
if (dev->class != NULL && !strcmp(dev->class->name, class))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct device *dev_find_class(struct device *parent, char *class)
|
||||
{
|
||||
if (dev_is_class(parent, class)) {
|
||||
get_device(parent);
|
||||
return parent;
|
||||
}
|
||||
|
||||
return device_find_child(parent, class, dev_is_class);
|
||||
}
|
||||
|
||||
struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev)
|
||||
{
|
||||
struct device *d;
|
||||
|
||||
d = dev_find_class(dev, "mdio_bus");
|
||||
if (d != NULL) {
|
||||
struct mii_bus *bus;
|
||||
|
||||
bus = to_mii_bus(d);
|
||||
put_device(d);
|
||||
|
||||
return bus;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dsa_host_dev_to_mii_bus);
|
||||
|
||||
static struct net_device *dev_to_net_device(struct device *dev)
|
||||
{
|
||||
struct device *d;
|
||||
|
||||
d = dev_find_class(dev, "net");
|
||||
if (d != NULL) {
|
||||
struct net_device *nd;
|
||||
|
||||
nd = to_net_dev(d);
|
||||
dev_hold(nd);
|
||||
put_device(d);
|
||||
|
||||
return nd;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int dsa_of_setup_routing_table(struct dsa_platform_data *pd,
|
||||
struct dsa_chip_data *cd,
|
||||
int chip_index,
|
||||
struct device_node *link)
|
||||
{
|
||||
int ret;
|
||||
const __be32 *reg;
|
||||
int link_port_addr;
|
||||
int link_sw_addr;
|
||||
struct device_node *parent_sw;
|
||||
int len;
|
||||
|
||||
parent_sw = of_get_parent(link);
|
||||
if (!parent_sw)
|
||||
return -EINVAL;
|
||||
|
||||
reg = of_get_property(parent_sw, "reg", &len);
|
||||
if (!reg || (len != sizeof(*reg) * 2))
|
||||
return -EINVAL;
|
||||
|
||||
link_sw_addr = be32_to_cpup(reg + 1);
|
||||
|
||||
if (link_sw_addr >= pd->nr_chips)
|
||||
return -EINVAL;
|
||||
|
||||
/* First time routing table allocation */
|
||||
if (!cd->rtable) {
|
||||
cd->rtable = kmalloc(pd->nr_chips * sizeof(s8), GFP_KERNEL);
|
||||
if (!cd->rtable)
|
||||
return -ENOMEM;
|
||||
|
||||
/* default to no valid uplink/downlink */
|
||||
memset(cd->rtable, -1, pd->nr_chips * sizeof(s8));
|
||||
}
|
||||
|
||||
reg = of_get_property(link, "reg", NULL);
|
||||
if (!reg) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
link_port_addr = be32_to_cpup(reg);
|
||||
|
||||
cd->rtable[link_sw_addr] = link_port_addr;
|
||||
|
||||
return 0;
|
||||
out:
|
||||
kfree(cd->rtable);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dsa_of_free_platform_data(struct dsa_platform_data *pd)
|
||||
{
|
||||
int i;
|
||||
int port_index;
|
||||
|
||||
for (i = 0; i < pd->nr_chips; i++) {
|
||||
port_index = 0;
|
||||
while (port_index < DSA_MAX_PORTS) {
|
||||
kfree(pd->chip[i].port_names[port_index]);
|
||||
port_index++;
|
||||
}
|
||||
kfree(pd->chip[i].rtable);
|
||||
}
|
||||
kfree(pd->chip);
|
||||
}
|
||||
|
||||
static int dsa_of_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device_node *child, *mdio, *ethernet, *port, *link;
|
||||
struct mii_bus *mdio_bus;
|
||||
struct platform_device *ethernet_dev;
|
||||
struct dsa_platform_data *pd;
|
||||
struct dsa_chip_data *cd;
|
||||
const char *port_name;
|
||||
int chip_index, port_index;
|
||||
const unsigned int *sw_addr, *port_reg;
|
||||
int ret;
|
||||
|
||||
mdio = of_parse_phandle(np, "dsa,mii-bus", 0);
|
||||
if (!mdio)
|
||||
return -EINVAL;
|
||||
|
||||
mdio_bus = of_mdio_find_bus(mdio);
|
||||
if (!mdio_bus)
|
||||
return -EINVAL;
|
||||
|
||||
ethernet = of_parse_phandle(np, "dsa,ethernet", 0);
|
||||
if (!ethernet)
|
||||
return -EINVAL;
|
||||
|
||||
ethernet_dev = of_find_device_by_node(ethernet);
|
||||
if (!ethernet_dev)
|
||||
return -ENODEV;
|
||||
|
||||
pd = kzalloc(sizeof(*pd), GFP_KERNEL);
|
||||
if (!pd)
|
||||
return -ENOMEM;
|
||||
|
||||
pdev->dev.platform_data = pd;
|
||||
pd->netdev = ðernet_dev->dev;
|
||||
pd->nr_chips = of_get_child_count(np);
|
||||
if (pd->nr_chips > DSA_MAX_SWITCHES)
|
||||
pd->nr_chips = DSA_MAX_SWITCHES;
|
||||
|
||||
pd->chip = kzalloc(pd->nr_chips * sizeof(struct dsa_chip_data),
|
||||
GFP_KERNEL);
|
||||
if (!pd->chip) {
|
||||
ret = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
chip_index = -1;
|
||||
for_each_available_child_of_node(np, child) {
|
||||
chip_index++;
|
||||
cd = &pd->chip[chip_index];
|
||||
|
||||
cd->of_node = child;
|
||||
cd->host_dev = &mdio_bus->dev;
|
||||
|
||||
sw_addr = of_get_property(child, "reg", NULL);
|
||||
if (!sw_addr)
|
||||
continue;
|
||||
|
||||
cd->sw_addr = be32_to_cpup(sw_addr);
|
||||
if (cd->sw_addr > PHY_MAX_ADDR)
|
||||
continue;
|
||||
|
||||
for_each_available_child_of_node(child, port) {
|
||||
port_reg = of_get_property(port, "reg", NULL);
|
||||
if (!port_reg)
|
||||
continue;
|
||||
|
||||
port_index = be32_to_cpup(port_reg);
|
||||
|
||||
port_name = of_get_property(port, "label", NULL);
|
||||
if (!port_name)
|
||||
continue;
|
||||
|
||||
cd->port_dn[port_index] = port;
|
||||
|
||||
cd->port_names[port_index] = kstrdup(port_name,
|
||||
GFP_KERNEL);
|
||||
if (!cd->port_names[port_index]) {
|
||||
ret = -ENOMEM;
|
||||
goto out_free_chip;
|
||||
}
|
||||
|
||||
link = of_parse_phandle(port, "link", 0);
|
||||
|
||||
if (!strcmp(port_name, "dsa") && link &&
|
||||
pd->nr_chips > 1) {
|
||||
ret = dsa_of_setup_routing_table(pd, cd,
|
||||
chip_index, link);
|
||||
if (ret)
|
||||
goto out_free_chip;
|
||||
}
|
||||
|
||||
if (port_index == DSA_MAX_PORTS)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_chip:
|
||||
dsa_of_free_platform_data(pd);
|
||||
out_free:
|
||||
kfree(pd);
|
||||
pdev->dev.platform_data = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dsa_of_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dsa_platform_data *pd = pdev->dev.platform_data;
|
||||
|
||||
if (!pdev->dev.of_node)
|
||||
return;
|
||||
|
||||
dsa_of_free_platform_data(pd);
|
||||
kfree(pd);
|
||||
}
|
||||
#else
|
||||
static inline int dsa_of_probe(struct platform_device *pdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dsa_of_remove(struct platform_device *pdev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static int dsa_probe(struct platform_device *pdev)
|
||||
{
|
||||
static int dsa_version_printed;
|
||||
struct dsa_platform_data *pd = pdev->dev.platform_data;
|
||||
struct net_device *dev;
|
||||
struct dsa_switch_tree *dst;
|
||||
int i, ret;
|
||||
|
||||
if (!dsa_version_printed++)
|
||||
printk(KERN_NOTICE "Distributed Switch Architecture "
|
||||
"driver version %s\n", dsa_driver_version);
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
ret = dsa_of_probe(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pd = pdev->dev.platform_data;
|
||||
}
|
||||
|
||||
if (pd == NULL || pd->netdev == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
dev = dev_to_net_device(pd->netdev);
|
||||
if (dev == NULL) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dev->dsa_ptr != NULL) {
|
||||
dev_put(dev);
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dst = kzalloc(sizeof(*dst), GFP_KERNEL);
|
||||
if (dst == NULL) {
|
||||
dev_put(dev);
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dst);
|
||||
|
||||
dst->pd = pd;
|
||||
dst->master_netdev = dev;
|
||||
dst->cpu_switch = -1;
|
||||
dst->cpu_port = -1;
|
||||
|
||||
for (i = 0; i < pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds;
|
||||
|
||||
ds = dsa_switch_setup(dst, i, &pdev->dev, pd->chip[i].host_dev);
|
||||
if (IS_ERR(ds)) {
|
||||
printk(KERN_ERR "%s[%d]: couldn't create dsa switch "
|
||||
"instance (error %ld)\n", dev->name, i,
|
||||
PTR_ERR(ds));
|
||||
continue;
|
||||
}
|
||||
|
||||
dst->ds[i] = ds;
|
||||
if (ds->drv->poll_link != NULL)
|
||||
dst->link_poll_needed = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we use a tagging format that doesn't have an ethertype
|
||||
* field, make sure that all packets from this point on get
|
||||
* sent to the tag format's receive function.
|
||||
*/
|
||||
wmb();
|
||||
dev->dsa_ptr = (void *)dst;
|
||||
|
||||
if (dst->link_poll_needed) {
|
||||
INIT_WORK(&dst->link_poll_work, dsa_link_poll_work);
|
||||
init_timer(&dst->link_poll_timer);
|
||||
dst->link_poll_timer.data = (unsigned long)dst;
|
||||
dst->link_poll_timer.function = dsa_link_poll_timer;
|
||||
dst->link_poll_timer.expires = round_jiffies(jiffies + HZ);
|
||||
add_timer(&dst->link_poll_timer);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
dsa_of_remove(pdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsa_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
if (dst->link_poll_needed)
|
||||
del_timer_sync(&dst->link_poll_timer);
|
||||
|
||||
flush_work(&dst->link_poll_work);
|
||||
|
||||
for (i = 0; i < dst->pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds = dst->ds[i];
|
||||
|
||||
if (ds != NULL)
|
||||
dsa_switch_destroy(ds);
|
||||
}
|
||||
|
||||
dsa_of_remove(pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsa_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
}
|
||||
|
||||
static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev)
|
||||
{
|
||||
struct dsa_switch_tree *dst = dev->dsa_ptr;
|
||||
|
||||
if (unlikely(dst == NULL)) {
|
||||
kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dst->rcv(skb, dev, pt, orig_dev);
|
||||
}
|
||||
|
||||
static struct packet_type dsa_pack_type __read_mostly = {
|
||||
.type = cpu_to_be16(ETH_P_XDSA),
|
||||
.func = dsa_switch_rcv,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int dsa_suspend(struct device *d)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(d);
|
||||
struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < dst->pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds = dst->ds[i];
|
||||
|
||||
if (ds != NULL)
|
||||
ret = dsa_switch_suspend(ds);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsa_resume(struct device *d)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(d);
|
||||
struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < dst->pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds = dst->ds[i];
|
||||
|
||||
if (ds != NULL)
|
||||
ret = dsa_switch_resume(ds);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(dsa_pm_ops, dsa_suspend, dsa_resume);
|
||||
|
||||
static const struct of_device_id dsa_of_match_table[] = {
|
||||
{ .compatible = "brcm,bcm7445-switch-v4.0" },
|
||||
{ .compatible = "marvell,dsa", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, dsa_of_match_table);
|
||||
|
||||
static struct platform_driver dsa_driver = {
|
||||
.probe = dsa_probe,
|
||||
.remove = dsa_remove,
|
||||
.shutdown = dsa_shutdown,
|
||||
.driver = {
|
||||
.name = "dsa",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = dsa_of_match_table,
|
||||
.pm = &dsa_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init dsa_init_module(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = platform_driver_register(&dsa_driver);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
dev_add_pack(&dsa_pack_type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
module_init(dsa_init_module);
|
||||
|
||||
static void __exit dsa_cleanup_module(void)
|
||||
{
|
||||
dev_remove_pack(&dsa_pack_type);
|
||||
platform_driver_unregister(&dsa_driver);
|
||||
}
|
||||
module_exit(dsa_cleanup_module);
|
||||
|
||||
MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>");
|
||||
MODULE_DESCRIPTION("Driver for Distributed Switch Architecture switch chips");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:dsa");
|
75
net/dsa/dsa_priv.h
Normal file
75
net/dsa/dsa_priv.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* net/dsa/dsa_priv.h - Hardware switch 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.
|
||||
*/
|
||||
|
||||
#ifndef __DSA_PRIV_H
|
||||
#define __DSA_PRIV_H
|
||||
|
||||
#include <linux/phy.h>
|
||||
#include <linux/netdevice.h>
|
||||
|
||||
struct dsa_device_ops {
|
||||
netdev_tx_t (*xmit)(struct sk_buff *skb, struct net_device *dev);
|
||||
int (*rcv)(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev);
|
||||
};
|
||||
|
||||
struct dsa_slave_priv {
|
||||
/*
|
||||
* The linux network interface corresponding to this
|
||||
* switch port.
|
||||
*/
|
||||
struct net_device *dev;
|
||||
netdev_tx_t (*xmit)(struct sk_buff *skb,
|
||||
struct net_device *dev);
|
||||
|
||||
/*
|
||||
* Which switch this port is a part of, and the port index
|
||||
* for this port.
|
||||
*/
|
||||
struct dsa_switch *parent;
|
||||
u8 port;
|
||||
|
||||
/*
|
||||
* The phylib phy_device pointer for the PHY connected
|
||||
* to this port.
|
||||
*/
|
||||
struct phy_device *phy;
|
||||
phy_interface_t phy_interface;
|
||||
int old_link;
|
||||
int old_pause;
|
||||
int old_duplex;
|
||||
};
|
||||
|
||||
/* dsa.c */
|
||||
extern char dsa_driver_version[];
|
||||
|
||||
/* slave.c */
|
||||
extern const struct dsa_device_ops notag_netdev_ops;
|
||||
void dsa_slave_mii_bus_init(struct dsa_switch *ds);
|
||||
struct net_device *dsa_slave_create(struct dsa_switch *ds,
|
||||
struct device *parent,
|
||||
int port, char *name);
|
||||
int dsa_slave_suspend(struct net_device *slave_dev);
|
||||
int dsa_slave_resume(struct net_device *slave_dev);
|
||||
|
||||
/* tag_dsa.c */
|
||||
extern const struct dsa_device_ops dsa_netdev_ops;
|
||||
|
||||
/* tag_edsa.c */
|
||||
extern const struct dsa_device_ops edsa_netdev_ops;
|
||||
|
||||
/* tag_trailer.c */
|
||||
extern const struct dsa_device_ops trailer_netdev_ops;
|
||||
|
||||
/* tag_brcm.c */
|
||||
extern const struct dsa_device_ops brcm_netdev_ops;
|
||||
|
||||
|
||||
#endif
|
618
net/dsa/slave.c
Normal file
618
net/dsa/slave.c
Normal file
|
@ -0,0 +1,618 @@
|
|||
/*
|
||||
* 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/phy.h>
|
||||
#include <linux/phy_fixed.h>
|
||||
#include <linux/of_net.h>
|
||||
#include <linux/of_mdio.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;
|
||||
}
|
||||
|
||||
|
||||
/* slave device handling ****************************************************/
|
||||
static int dsa_slave_init(struct net_device *dev)
|
||||
{
|
||||
struct dsa_slave_priv *p = netdev_priv(dev);
|
||||
|
||||
dev->iflink = p->parent->dst->master_netdev->ifindex;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
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 (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);
|
||||
|
||||
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_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;
|
||||
}
|
||||
|
||||
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_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 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,
|
||||
.nway_reset = dsa_slave_nway_reset,
|
||||
.get_link = dsa_slave_get_link,
|
||||
.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_init = dsa_slave_init,
|
||||
.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_do_ioctl = dsa_slave_ioctl,
|
||||
};
|
||||
|
||||
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 void 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 ret;
|
||||
|
||||
port_dn = cd->port_dn[p->port];
|
||||
p->phy_interface = of_get_phy_mode(port_dn);
|
||||
|
||||
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) {
|
||||
pr_err("failed to register fixed PHY\n");
|
||||
return;
|
||||
}
|
||||
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)
|
||||
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) {
|
||||
p->phy = ds->slave_mii_bus->phy_map[p->port];
|
||||
phy_connect_direct(slave_dev, p->phy, dsa_slave_adjust_link,
|
||||
p->phy_interface);
|
||||
} else {
|
||||
pr_info("attached PHY at address %d [%s]\n",
|
||||
p->phy->addr, p->phy->drv->name);
|
||||
}
|
||||
}
|
||||
|
||||
int dsa_slave_suspend(struct net_device *slave_dev)
|
||||
{
|
||||
struct dsa_slave_priv *p = netdev_priv(slave_dev);
|
||||
|
||||
netif_device_detach(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;
|
||||
}
|
||||
|
||||
struct net_device *
|
||||
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 slave_dev;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
dsa_slave_phy_setup(p, slave_dev);
|
||||
|
||||
ret = register_netdev(slave_dev);
|
||||
if (ret) {
|
||||
printk(KERN_ERR "%s: error %d registering interface %s\n",
|
||||
master->name, ret, slave_dev->name);
|
||||
free_netdev(slave_dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
netif_carrier_off(slave_dev);
|
||||
|
||||
if (p->phy != NULL) {
|
||||
if (ds->drv->get_phy_flags)
|
||||
p->phy->dev_flags |= ds->drv->get_phy_flags(ds, port);
|
||||
|
||||
phy_attach(slave_dev, dev_name(&p->phy->dev),
|
||||
PHY_INTERFACE_MODE_GMII);
|
||||
|
||||
p->phy->autoneg = AUTONEG_ENABLE;
|
||||
p->phy->speed = 0;
|
||||
p->phy->duplex = 0;
|
||||
p->phy->advertising = p->phy->supported | ADVERTISED_Autoneg;
|
||||
}
|
||||
|
||||
return slave_dev;
|
||||
}
|
171
net/dsa/tag_brcm.c
Normal file
171
net/dsa/tag_brcm.c
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Broadcom tag support
|
||||
*
|
||||
* Copyright (C) 2014 Broadcom Corporation
|
||||
*
|
||||
* 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/etherdevice.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include "dsa_priv.h"
|
||||
|
||||
/* This tag length is 4 bytes, older ones were 6 bytes, we do not
|
||||
* handle them
|
||||
*/
|
||||
#define BRCM_TAG_LEN 4
|
||||
|
||||
/* Tag is constructed and desconstructed using byte by byte access
|
||||
* because the tag is placed after the MAC Source Address, which does
|
||||
* not make it 4-bytes aligned, so this might cause unaligned accesses
|
||||
* on most systems where this is used.
|
||||
*/
|
||||
|
||||
/* Ingress and egress opcodes */
|
||||
#define BRCM_OPCODE_SHIFT 5
|
||||
#define BRCM_OPCODE_MASK 0x7
|
||||
|
||||
/* Ingress fields */
|
||||
/* 1st byte in the tag */
|
||||
#define BRCM_IG_TC_SHIFT 2
|
||||
#define BRCM_IG_TC_MASK 0x7
|
||||
/* 2nd byte in the tag */
|
||||
#define BRCM_IG_TE_MASK 0x3
|
||||
#define BRCM_IG_TS_SHIFT 7
|
||||
/* 3rd byte in the tag */
|
||||
#define BRCM_IG_DSTMAP2_MASK 1
|
||||
#define BRCM_IG_DSTMAP1_MASK 0xff
|
||||
|
||||
/* Egress fields */
|
||||
|
||||
/* 2nd byte in the tag */
|
||||
#define BRCM_EG_CID_MASK 0xff
|
||||
|
||||
/* 3rd byte in the tag */
|
||||
#define BRCM_EG_RC_MASK 0xff
|
||||
#define BRCM_EG_RC_RSVD (3 << 6)
|
||||
#define BRCM_EG_RC_EXCEPTION (1 << 5)
|
||||
#define BRCM_EG_RC_PROT_SNOOP (1 << 4)
|
||||
#define BRCM_EG_RC_PROT_TERM (1 << 3)
|
||||
#define BRCM_EG_RC_SWITCH (1 << 2)
|
||||
#define BRCM_EG_RC_MAC_LEARN (1 << 1)
|
||||
#define BRCM_EG_RC_MIRROR (1 << 0)
|
||||
#define BRCM_EG_TC_SHIFT 5
|
||||
#define BRCM_EG_TC_MASK 0x7
|
||||
#define BRCM_EG_PID_MASK 0x1f
|
||||
|
||||
static netdev_tx_t brcm_tag_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct dsa_slave_priv *p = netdev_priv(dev);
|
||||
u8 *brcm_tag;
|
||||
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += skb->len;
|
||||
|
||||
if (skb_cow_head(skb, BRCM_TAG_LEN) < 0)
|
||||
goto out_free;
|
||||
|
||||
skb_push(skb, BRCM_TAG_LEN);
|
||||
|
||||
memmove(skb->data, skb->data + BRCM_TAG_LEN, 2 * ETH_ALEN);
|
||||
|
||||
/* Build the tag after the MAC Source Address */
|
||||
brcm_tag = skb->data + 2 * ETH_ALEN;
|
||||
|
||||
/* Set the ingress opcode, traffic class, tag enforcment is
|
||||
* deprecated
|
||||
*/
|
||||
brcm_tag[0] = (1 << BRCM_OPCODE_SHIFT) |
|
||||
((skb->priority << BRCM_IG_TC_SHIFT) & BRCM_IG_TC_MASK);
|
||||
brcm_tag[1] = 0;
|
||||
brcm_tag[2] = 0;
|
||||
if (p->port == 8)
|
||||
brcm_tag[2] = BRCM_IG_DSTMAP2_MASK;
|
||||
brcm_tag[3] = (1 << p->port) & BRCM_IG_DSTMAP1_MASK;
|
||||
|
||||
/* Queue the SKB for transmission on the parent interface, but
|
||||
* do not modify its EtherType
|
||||
*/
|
||||
skb->dev = p->parent->dst->master_netdev;
|
||||
dev_queue_xmit(skb);
|
||||
|
||||
return NETDEV_TX_OK;
|
||||
|
||||
out_free:
|
||||
kfree_skb(skb);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int brcm_tag_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev)
|
||||
{
|
||||
struct dsa_switch_tree *dst = dev->dsa_ptr;
|
||||
struct dsa_switch *ds;
|
||||
int source_port;
|
||||
u8 *brcm_tag;
|
||||
|
||||
if (unlikely(dst == NULL))
|
||||
goto out_drop;
|
||||
|
||||
ds = dst->ds[0];
|
||||
|
||||
skb = skb_unshare(skb, GFP_ATOMIC);
|
||||
if (skb == NULL)
|
||||
goto out;
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, BRCM_TAG_LEN)))
|
||||
goto out_drop;
|
||||
|
||||
/* skb->data points to the EtherType, the tag is right before it */
|
||||
brcm_tag = skb->data - 2;
|
||||
|
||||
/* The opcode should never be different than 0b000 */
|
||||
if (unlikely((brcm_tag[0] >> BRCM_OPCODE_SHIFT) & BRCM_OPCODE_MASK))
|
||||
goto out_drop;
|
||||
|
||||
/* We should never see a reserved reason code without knowing how to
|
||||
* handle it
|
||||
*/
|
||||
WARN_ON(brcm_tag[2] & BRCM_EG_RC_RSVD);
|
||||
|
||||
/* Locate which port this is coming from */
|
||||
source_port = brcm_tag[3] & BRCM_EG_PID_MASK;
|
||||
|
||||
/* Validate port against switch setup, either the port is totally */
|
||||
if (source_port >= DSA_MAX_PORTS || ds->ports[source_port] == NULL)
|
||||
goto out_drop;
|
||||
|
||||
/* Remove Broadcom tag and update checksum */
|
||||
skb_pull_rcsum(skb, BRCM_TAG_LEN);
|
||||
|
||||
/* Move the Ethernet DA and SA */
|
||||
memmove(skb->data - ETH_HLEN,
|
||||
skb->data - ETH_HLEN - BRCM_TAG_LEN,
|
||||
2 * ETH_ALEN);
|
||||
|
||||
skb_push(skb, ETH_HLEN);
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
skb->dev = ds->ports[source_port];
|
||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
||||
|
||||
skb->dev->stats.rx_packets++;
|
||||
skb->dev->stats.rx_bytes += skb->len;
|
||||
|
||||
netif_receive_skb(skb);
|
||||
|
||||
return 0;
|
||||
|
||||
out_drop:
|
||||
kfree_skb(skb);
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct dsa_device_ops brcm_netdev_ops = {
|
||||
.xmit = brcm_tag_xmit,
|
||||
.rcv = brcm_tag_rcv,
|
||||
};
|
191
net/dsa/tag_dsa.c
Normal file
191
net/dsa/tag_dsa.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* net/dsa/tag_dsa.c - (Non-ethertype) DSA tagging
|
||||
* 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/etherdevice.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include "dsa_priv.h"
|
||||
|
||||
#define DSA_HLEN 4
|
||||
|
||||
static netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct dsa_slave_priv *p = netdev_priv(dev);
|
||||
u8 *dsa_header;
|
||||
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += skb->len;
|
||||
|
||||
/*
|
||||
* Convert the outermost 802.1q tag to a DSA tag for tagged
|
||||
* packets, or insert a DSA tag between the addresses and
|
||||
* the ethertype field for untagged packets.
|
||||
*/
|
||||
if (skb->protocol == htons(ETH_P_8021Q)) {
|
||||
if (skb_cow_head(skb, 0) < 0)
|
||||
goto out_free;
|
||||
|
||||
/*
|
||||
* Construct tagged FROM_CPU DSA tag from 802.1q tag.
|
||||
*/
|
||||
dsa_header = skb->data + 2 * ETH_ALEN;
|
||||
dsa_header[0] = 0x60 | p->parent->index;
|
||||
dsa_header[1] = p->port << 3;
|
||||
|
||||
/*
|
||||
* Move CFI field from byte 2 to byte 1.
|
||||
*/
|
||||
if (dsa_header[2] & 0x10) {
|
||||
dsa_header[1] |= 0x01;
|
||||
dsa_header[2] &= ~0x10;
|
||||
}
|
||||
} else {
|
||||
if (skb_cow_head(skb, DSA_HLEN) < 0)
|
||||
goto out_free;
|
||||
skb_push(skb, DSA_HLEN);
|
||||
|
||||
memmove(skb->data, skb->data + DSA_HLEN, 2 * ETH_ALEN);
|
||||
|
||||
/*
|
||||
* Construct untagged FROM_CPU DSA tag.
|
||||
*/
|
||||
dsa_header = skb->data + 2 * ETH_ALEN;
|
||||
dsa_header[0] = 0x40 | p->parent->index;
|
||||
dsa_header[1] = p->port << 3;
|
||||
dsa_header[2] = 0x00;
|
||||
dsa_header[3] = 0x00;
|
||||
}
|
||||
|
||||
skb->protocol = htons(ETH_P_DSA);
|
||||
|
||||
skb->dev = p->parent->dst->master_netdev;
|
||||
dev_queue_xmit(skb);
|
||||
|
||||
return NETDEV_TX_OK;
|
||||
|
||||
out_free:
|
||||
kfree_skb(skb);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int dsa_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev)
|
||||
{
|
||||
struct dsa_switch_tree *dst = dev->dsa_ptr;
|
||||
struct dsa_switch *ds;
|
||||
u8 *dsa_header;
|
||||
int source_device;
|
||||
int source_port;
|
||||
|
||||
if (unlikely(dst == NULL))
|
||||
goto out_drop;
|
||||
|
||||
skb = skb_unshare(skb, GFP_ATOMIC);
|
||||
if (skb == NULL)
|
||||
goto out;
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, DSA_HLEN)))
|
||||
goto out_drop;
|
||||
|
||||
/*
|
||||
* The ethertype field is part of the DSA header.
|
||||
*/
|
||||
dsa_header = skb->data - 2;
|
||||
|
||||
/*
|
||||
* Check that frame type is either TO_CPU or FORWARD.
|
||||
*/
|
||||
if ((dsa_header[0] & 0xc0) != 0x00 && (dsa_header[0] & 0xc0) != 0xc0)
|
||||
goto out_drop;
|
||||
|
||||
/*
|
||||
* Determine source device and port.
|
||||
*/
|
||||
source_device = dsa_header[0] & 0x1f;
|
||||
source_port = (dsa_header[1] >> 3) & 0x1f;
|
||||
|
||||
/*
|
||||
* Check that the source device exists and that the source
|
||||
* port is a registered DSA port.
|
||||
*/
|
||||
if (source_device >= dst->pd->nr_chips)
|
||||
goto out_drop;
|
||||
ds = dst->ds[source_device];
|
||||
if (source_port >= DSA_MAX_PORTS || ds->ports[source_port] == NULL)
|
||||
goto out_drop;
|
||||
|
||||
/*
|
||||
* Convert the DSA header to an 802.1q header if the 'tagged'
|
||||
* bit in the DSA header is set. If the 'tagged' bit is clear,
|
||||
* delete the DSA header entirely.
|
||||
*/
|
||||
if (dsa_header[0] & 0x20) {
|
||||
u8 new_header[4];
|
||||
|
||||
/*
|
||||
* Insert 802.1q ethertype and copy the VLAN-related
|
||||
* fields, but clear the bit that will hold CFI (since
|
||||
* DSA uses that bit location for another purpose).
|
||||
*/
|
||||
new_header[0] = (ETH_P_8021Q >> 8) & 0xff;
|
||||
new_header[1] = ETH_P_8021Q & 0xff;
|
||||
new_header[2] = dsa_header[2] & ~0x10;
|
||||
new_header[3] = dsa_header[3];
|
||||
|
||||
/*
|
||||
* Move CFI bit from its place in the DSA header to
|
||||
* its 802.1q-designated place.
|
||||
*/
|
||||
if (dsa_header[1] & 0x01)
|
||||
new_header[2] |= 0x10;
|
||||
|
||||
/*
|
||||
* Update packet checksum if skb is CHECKSUM_COMPLETE.
|
||||
*/
|
||||
if (skb->ip_summed == CHECKSUM_COMPLETE) {
|
||||
__wsum c = skb->csum;
|
||||
c = csum_add(c, csum_partial(new_header + 2, 2, 0));
|
||||
c = csum_sub(c, csum_partial(dsa_header + 2, 2, 0));
|
||||
skb->csum = c;
|
||||
}
|
||||
|
||||
memcpy(dsa_header, new_header, DSA_HLEN);
|
||||
} else {
|
||||
/*
|
||||
* Remove DSA tag and update checksum.
|
||||
*/
|
||||
skb_pull_rcsum(skb, DSA_HLEN);
|
||||
memmove(skb->data - ETH_HLEN,
|
||||
skb->data - ETH_HLEN - DSA_HLEN,
|
||||
2 * ETH_ALEN);
|
||||
}
|
||||
|
||||
skb->dev = ds->ports[source_port];
|
||||
skb_push(skb, ETH_HLEN);
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
||||
|
||||
skb->dev->stats.rx_packets++;
|
||||
skb->dev->stats.rx_bytes += skb->len;
|
||||
|
||||
netif_receive_skb(skb);
|
||||
|
||||
return 0;
|
||||
|
||||
out_drop:
|
||||
kfree_skb(skb);
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct dsa_device_ops dsa_netdev_ops = {
|
||||
.xmit = dsa_xmit,
|
||||
.rcv = dsa_rcv,
|
||||
};
|
210
net/dsa/tag_edsa.c
Normal file
210
net/dsa/tag_edsa.c
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* net/dsa/tag_edsa.c - Ethertype DSA tagging
|
||||
* 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/etherdevice.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include "dsa_priv.h"
|
||||
|
||||
#define DSA_HLEN 4
|
||||
#define EDSA_HLEN 8
|
||||
|
||||
static netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct dsa_slave_priv *p = netdev_priv(dev);
|
||||
u8 *edsa_header;
|
||||
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += skb->len;
|
||||
|
||||
/*
|
||||
* Convert the outermost 802.1q tag to a DSA tag and prepend
|
||||
* a DSA ethertype field is the packet is tagged, or insert
|
||||
* a DSA ethertype plus DSA tag between the addresses and the
|
||||
* current ethertype field if the packet is untagged.
|
||||
*/
|
||||
if (skb->protocol == htons(ETH_P_8021Q)) {
|
||||
if (skb_cow_head(skb, DSA_HLEN) < 0)
|
||||
goto out_free;
|
||||
skb_push(skb, DSA_HLEN);
|
||||
|
||||
memmove(skb->data, skb->data + DSA_HLEN, 2 * ETH_ALEN);
|
||||
|
||||
/*
|
||||
* Construct tagged FROM_CPU DSA tag from 802.1q tag.
|
||||
*/
|
||||
edsa_header = skb->data + 2 * ETH_ALEN;
|
||||
edsa_header[0] = (ETH_P_EDSA >> 8) & 0xff;
|
||||
edsa_header[1] = ETH_P_EDSA & 0xff;
|
||||
edsa_header[2] = 0x00;
|
||||
edsa_header[3] = 0x00;
|
||||
edsa_header[4] = 0x60 | p->parent->index;
|
||||
edsa_header[5] = p->port << 3;
|
||||
|
||||
/*
|
||||
* Move CFI field from byte 6 to byte 5.
|
||||
*/
|
||||
if (edsa_header[6] & 0x10) {
|
||||
edsa_header[5] |= 0x01;
|
||||
edsa_header[6] &= ~0x10;
|
||||
}
|
||||
} else {
|
||||
if (skb_cow_head(skb, EDSA_HLEN) < 0)
|
||||
goto out_free;
|
||||
skb_push(skb, EDSA_HLEN);
|
||||
|
||||
memmove(skb->data, skb->data + EDSA_HLEN, 2 * ETH_ALEN);
|
||||
|
||||
/*
|
||||
* Construct untagged FROM_CPU DSA tag.
|
||||
*/
|
||||
edsa_header = skb->data + 2 * ETH_ALEN;
|
||||
edsa_header[0] = (ETH_P_EDSA >> 8) & 0xff;
|
||||
edsa_header[1] = ETH_P_EDSA & 0xff;
|
||||
edsa_header[2] = 0x00;
|
||||
edsa_header[3] = 0x00;
|
||||
edsa_header[4] = 0x40 | p->parent->index;
|
||||
edsa_header[5] = p->port << 3;
|
||||
edsa_header[6] = 0x00;
|
||||
edsa_header[7] = 0x00;
|
||||
}
|
||||
|
||||
skb->protocol = htons(ETH_P_EDSA);
|
||||
|
||||
skb->dev = p->parent->dst->master_netdev;
|
||||
dev_queue_xmit(skb);
|
||||
|
||||
return NETDEV_TX_OK;
|
||||
|
||||
out_free:
|
||||
kfree_skb(skb);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int edsa_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev)
|
||||
{
|
||||
struct dsa_switch_tree *dst = dev->dsa_ptr;
|
||||
struct dsa_switch *ds;
|
||||
u8 *edsa_header;
|
||||
int source_device;
|
||||
int source_port;
|
||||
|
||||
if (unlikely(dst == NULL))
|
||||
goto out_drop;
|
||||
|
||||
skb = skb_unshare(skb, GFP_ATOMIC);
|
||||
if (skb == NULL)
|
||||
goto out;
|
||||
|
||||
if (unlikely(!pskb_may_pull(skb, EDSA_HLEN)))
|
||||
goto out_drop;
|
||||
|
||||
/*
|
||||
* Skip the two null bytes after the ethertype.
|
||||
*/
|
||||
edsa_header = skb->data + 2;
|
||||
|
||||
/*
|
||||
* Check that frame type is either TO_CPU or FORWARD.
|
||||
*/
|
||||
if ((edsa_header[0] & 0xc0) != 0x00 && (edsa_header[0] & 0xc0) != 0xc0)
|
||||
goto out_drop;
|
||||
|
||||
/*
|
||||
* Determine source device and port.
|
||||
*/
|
||||
source_device = edsa_header[0] & 0x1f;
|
||||
source_port = (edsa_header[1] >> 3) & 0x1f;
|
||||
|
||||
/*
|
||||
* Check that the source device exists and that the source
|
||||
* port is a registered DSA port.
|
||||
*/
|
||||
if (source_device >= dst->pd->nr_chips)
|
||||
goto out_drop;
|
||||
ds = dst->ds[source_device];
|
||||
if (source_port >= DSA_MAX_PORTS || ds->ports[source_port] == NULL)
|
||||
goto out_drop;
|
||||
|
||||
/*
|
||||
* If the 'tagged' bit is set, convert the DSA tag to a 802.1q
|
||||
* tag and delete the ethertype part. If the 'tagged' bit is
|
||||
* clear, delete the ethertype and the DSA tag parts.
|
||||
*/
|
||||
if (edsa_header[0] & 0x20) {
|
||||
u8 new_header[4];
|
||||
|
||||
/*
|
||||
* Insert 802.1q ethertype and copy the VLAN-related
|
||||
* fields, but clear the bit that will hold CFI (since
|
||||
* DSA uses that bit location for another purpose).
|
||||
*/
|
||||
new_header[0] = (ETH_P_8021Q >> 8) & 0xff;
|
||||
new_header[1] = ETH_P_8021Q & 0xff;
|
||||
new_header[2] = edsa_header[2] & ~0x10;
|
||||
new_header[3] = edsa_header[3];
|
||||
|
||||
/*
|
||||
* Move CFI bit from its place in the DSA header to
|
||||
* its 802.1q-designated place.
|
||||
*/
|
||||
if (edsa_header[1] & 0x01)
|
||||
new_header[2] |= 0x10;
|
||||
|
||||
skb_pull_rcsum(skb, DSA_HLEN);
|
||||
|
||||
/*
|
||||
* Update packet checksum if skb is CHECKSUM_COMPLETE.
|
||||
*/
|
||||
if (skb->ip_summed == CHECKSUM_COMPLETE) {
|
||||
__wsum c = skb->csum;
|
||||
c = csum_add(c, csum_partial(new_header + 2, 2, 0));
|
||||
c = csum_sub(c, csum_partial(edsa_header + 2, 2, 0));
|
||||
skb->csum = c;
|
||||
}
|
||||
|
||||
memcpy(edsa_header, new_header, DSA_HLEN);
|
||||
|
||||
memmove(skb->data - ETH_HLEN,
|
||||
skb->data - ETH_HLEN - DSA_HLEN,
|
||||
2 * ETH_ALEN);
|
||||
} else {
|
||||
/*
|
||||
* Remove DSA tag and update checksum.
|
||||
*/
|
||||
skb_pull_rcsum(skb, EDSA_HLEN);
|
||||
memmove(skb->data - ETH_HLEN,
|
||||
skb->data - ETH_HLEN - EDSA_HLEN,
|
||||
2 * ETH_ALEN);
|
||||
}
|
||||
|
||||
skb->dev = ds->ports[source_port];
|
||||
skb_push(skb, ETH_HLEN);
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
||||
|
||||
skb->dev->stats.rx_packets++;
|
||||
skb->dev->stats.rx_bytes += skb->len;
|
||||
|
||||
netif_receive_skb(skb);
|
||||
|
||||
return 0;
|
||||
|
||||
out_drop:
|
||||
kfree_skb(skb);
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct dsa_device_ops edsa_netdev_ops = {
|
||||
.xmit = edsa_xmit,
|
||||
.rcv = edsa_rcv,
|
||||
};
|
119
net/dsa/tag_trailer.c
Normal file
119
net/dsa/tag_trailer.c
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* net/dsa/tag_trailer.c - Trailer tag format 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/etherdevice.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/slab.h>
|
||||
#include "dsa_priv.h"
|
||||
|
||||
static netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev)
|
||||
{
|
||||
struct dsa_slave_priv *p = netdev_priv(dev);
|
||||
struct sk_buff *nskb;
|
||||
int padlen;
|
||||
u8 *trailer;
|
||||
|
||||
dev->stats.tx_packets++;
|
||||
dev->stats.tx_bytes += skb->len;
|
||||
|
||||
/*
|
||||
* We have to make sure that the trailer ends up as the very
|
||||
* last 4 bytes of the packet. This means that we have to pad
|
||||
* the packet to the minimum ethernet frame size, if necessary,
|
||||
* before adding the trailer.
|
||||
*/
|
||||
padlen = 0;
|
||||
if (skb->len < 60)
|
||||
padlen = 60 - skb->len;
|
||||
|
||||
nskb = alloc_skb(NET_IP_ALIGN + skb->len + padlen + 4, GFP_ATOMIC);
|
||||
if (nskb == NULL) {
|
||||
kfree_skb(skb);
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
skb_reserve(nskb, NET_IP_ALIGN);
|
||||
|
||||
skb_reset_mac_header(nskb);
|
||||
skb_set_network_header(nskb, skb_network_header(skb) - skb->head);
|
||||
skb_set_transport_header(nskb, skb_transport_header(skb) - skb->head);
|
||||
skb_copy_and_csum_dev(skb, skb_put(nskb, skb->len));
|
||||
kfree_skb(skb);
|
||||
|
||||
if (padlen) {
|
||||
u8 *pad = skb_put(nskb, padlen);
|
||||
memset(pad, 0, padlen);
|
||||
}
|
||||
|
||||
trailer = skb_put(nskb, 4);
|
||||
trailer[0] = 0x80;
|
||||
trailer[1] = 1 << p->port;
|
||||
trailer[2] = 0x10;
|
||||
trailer[3] = 0x00;
|
||||
|
||||
nskb->protocol = htons(ETH_P_TRAILER);
|
||||
|
||||
nskb->dev = p->parent->dst->master_netdev;
|
||||
dev_queue_xmit(nskb);
|
||||
|
||||
return NETDEV_TX_OK;
|
||||
}
|
||||
|
||||
static int trailer_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||
struct packet_type *pt, struct net_device *orig_dev)
|
||||
{
|
||||
struct dsa_switch_tree *dst = dev->dsa_ptr;
|
||||
struct dsa_switch *ds;
|
||||
u8 *trailer;
|
||||
int source_port;
|
||||
|
||||
if (unlikely(dst == NULL))
|
||||
goto out_drop;
|
||||
ds = dst->ds[0];
|
||||
|
||||
skb = skb_unshare(skb, GFP_ATOMIC);
|
||||
if (skb == NULL)
|
||||
goto out;
|
||||
|
||||
if (skb_linearize(skb))
|
||||
goto out_drop;
|
||||
|
||||
trailer = skb_tail_pointer(skb) - 4;
|
||||
if (trailer[0] != 0x80 || (trailer[1] & 0xf8) != 0x00 ||
|
||||
(trailer[3] & 0xef) != 0x00 || trailer[3] != 0x00)
|
||||
goto out_drop;
|
||||
|
||||
source_port = trailer[1] & 7;
|
||||
if (source_port >= DSA_MAX_PORTS || ds->ports[source_port] == NULL)
|
||||
goto out_drop;
|
||||
|
||||
pskb_trim_rcsum(skb, skb->len - 4);
|
||||
|
||||
skb->dev = ds->ports[source_port];
|
||||
skb_push(skb, ETH_HLEN);
|
||||
skb->pkt_type = PACKET_HOST;
|
||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
||||
|
||||
skb->dev->stats.rx_packets++;
|
||||
skb->dev->stats.rx_bytes += skb->len;
|
||||
|
||||
netif_receive_skb(skb);
|
||||
|
||||
return 0;
|
||||
|
||||
out_drop:
|
||||
kfree_skb(skb);
|
||||
out:
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct dsa_device_ops trailer_netdev_ops = {
|
||||
.xmit = trailer_xmit,
|
||||
.rcv = trailer_rcv,
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue