|
@@ -1,6 +1,6 @@
|
|
/*
|
|
/*
|
|
* net/dsa/dsa.c - Hardware switch handling
|
|
* net/dsa/dsa.c - Hardware switch handling
|
|
- * Copyright (c) 2008 Marvell Semiconductor
|
|
|
|
|
|
+ * Copyright (c) 2008-2009 Marvell Semiconductor
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* 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
|
|
* it under the terms of the GNU General Public License as published by
|
|
@@ -67,12 +67,13 @@ dsa_switch_probe(struct mii_bus *bus, int sw_addr, char **_name)
|
|
|
|
|
|
/* basic switch operations **************************************************/
|
|
/* basic switch operations **************************************************/
|
|
static struct dsa_switch *
|
|
static struct dsa_switch *
|
|
-dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
|
|
|
|
- struct mii_bus *bus, struct net_device *dev)
|
|
|
|
|
|
+dsa_switch_setup(struct dsa_switch_tree *dst, int index,
|
|
|
|
+ struct device *parent, struct mii_bus *bus)
|
|
{
|
|
{
|
|
|
|
+ struct dsa_chip_data *pd = dst->pd->chip + index;
|
|
|
|
+ struct dsa_switch_driver *drv;
|
|
struct dsa_switch *ds;
|
|
struct dsa_switch *ds;
|
|
int ret;
|
|
int ret;
|
|
- struct dsa_switch_driver *drv;
|
|
|
|
char *name;
|
|
char *name;
|
|
int i;
|
|
int i;
|
|
|
|
|
|
@@ -81,11 +82,12 @@ dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
|
|
*/
|
|
*/
|
|
drv = dsa_switch_probe(bus, pd->sw_addr, &name);
|
|
drv = dsa_switch_probe(bus, pd->sw_addr, &name);
|
|
if (drv == NULL) {
|
|
if (drv == NULL) {
|
|
- printk(KERN_ERR "%s: could not detect attached switch\n",
|
|
|
|
- dev->name);
|
|
|
|
|
|
+ printk(KERN_ERR "%s[%d]: could not detect attached switch\n",
|
|
|
|
+ dst->master_netdev->name, index);
|
|
return ERR_PTR(-EINVAL);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
}
|
|
- printk(KERN_INFO "%s: detected a %s switch\n", dev->name, name);
|
|
|
|
|
|
+ printk(KERN_INFO "%s[%d]: detected a %s switch\n",
|
|
|
|
+ dst->master_netdev->name, index, name);
|
|
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -95,18 +97,16 @@ dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
|
|
if (ds == NULL)
|
|
if (ds == NULL)
|
|
return ERR_PTR(-ENOMEM);
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
- ds->pd = pd;
|
|
|
|
- ds->master_netdev = dev;
|
|
|
|
- ds->master_mii_bus = bus;
|
|
|
|
-
|
|
|
|
|
|
+ ds->dst = dst;
|
|
|
|
+ ds->index = index;
|
|
|
|
+ ds->pd = dst->pd->chip + index;
|
|
ds->drv = drv;
|
|
ds->drv = drv;
|
|
- ds->tag_protocol = drv->tag_protocol;
|
|
|
|
|
|
+ ds->master_mii_bus = bus;
|
|
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
* Validate supplied switch configuration.
|
|
* Validate supplied switch configuration.
|
|
*/
|
|
*/
|
|
- ds->cpu_port = -1;
|
|
|
|
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
|
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
|
char *name;
|
|
char *name;
|
|
|
|
|
|
@@ -115,32 +115,28 @@ dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
|
|
continue;
|
|
continue;
|
|
|
|
|
|
if (!strcmp(name, "cpu")) {
|
|
if (!strcmp(name, "cpu")) {
|
|
- if (ds->cpu_port != -1) {
|
|
|
|
|
|
+ if (dst->cpu_switch != -1) {
|
|
printk(KERN_ERR "multiple cpu ports?!\n");
|
|
printk(KERN_ERR "multiple cpu ports?!\n");
|
|
ret = -EINVAL;
|
|
ret = -EINVAL;
|
|
goto out;
|
|
goto out;
|
|
}
|
|
}
|
|
- ds->cpu_port = i;
|
|
|
|
|
|
+ dst->cpu_switch = index;
|
|
|
|
+ dst->cpu_port = i;
|
|
|
|
+ } else if (!strcmp(name, "dsa")) {
|
|
|
|
+ ds->dsa_port_mask |= 1 << i;
|
|
} else {
|
|
} else {
|
|
- ds->valid_port_mask |= 1 << i;
|
|
|
|
|
|
+ ds->phys_port_mask |= 1 << i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- if (ds->cpu_port == -1) {
|
|
|
|
- printk(KERN_ERR "no cpu port?!\n");
|
|
|
|
- ret = -EINVAL;
|
|
|
|
- goto out;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
- * 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. (Which will
|
|
|
|
- * discard received packets until we set ds->ports[] below.)
|
|
|
|
|
|
+ * If the CPU connects to this switch, set the switch tree
|
|
|
|
+ * tagging protocol to the preferred tagging format of this
|
|
|
|
+ * switch.
|
|
*/
|
|
*/
|
|
- wmb();
|
|
|
|
- dev->dsa_ptr = (void *)ds;
|
|
|
|
|
|
+ if (ds->dst->cpu_switch == index)
|
|
|
|
+ ds->dst->tag_protocol = drv->tag_protocol;
|
|
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -150,7 +146,7 @@ dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
|
|
if (ret < 0)
|
|
if (ret < 0)
|
|
goto out;
|
|
goto out;
|
|
|
|
|
|
- ret = drv->set_addr(ds, dev->dev_addr);
|
|
|
|
|
|
+ ret = drv->set_addr(ds, dst->master_netdev->dev_addr);
|
|
if (ret < 0)
|
|
if (ret < 0)
|
|
goto out;
|
|
goto out;
|
|
|
|
|
|
@@ -169,18 +165,18 @@ dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
|
|
/*
|
|
/*
|
|
* Create network devices for physical switch ports.
|
|
* Create network devices for physical switch ports.
|
|
*/
|
|
*/
|
|
- wmb();
|
|
|
|
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
|
for (i = 0; i < DSA_MAX_PORTS; i++) {
|
|
struct net_device *slave_dev;
|
|
struct net_device *slave_dev;
|
|
|
|
|
|
- if (!(ds->valid_port_mask & (1 << i)))
|
|
|
|
|
|
+ if (!(ds->phys_port_mask & (1 << i)))
|
|
continue;
|
|
continue;
|
|
|
|
|
|
slave_dev = dsa_slave_create(ds, parent, i, pd->port_names[i]);
|
|
slave_dev = dsa_slave_create(ds, parent, i, pd->port_names[i]);
|
|
if (slave_dev == NULL) {
|
|
if (slave_dev == NULL) {
|
|
- printk(KERN_ERR "%s: can't create dsa slave "
|
|
|
|
- "device for port %d(%s)\n",
|
|
|
|
- dev->name, i, pd->port_names[i]);
|
|
|
|
|
|
+ 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;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -192,7 +188,6 @@ dsa_switch_setup(struct device *parent, struct dsa_platform_data *pd,
|
|
out_free:
|
|
out_free:
|
|
mdiobus_free(ds->slave_mii_bus);
|
|
mdiobus_free(ds->slave_mii_bus);
|
|
out:
|
|
out:
|
|
- dev->dsa_ptr = NULL;
|
|
|
|
kfree(ds);
|
|
kfree(ds);
|
|
return ERR_PTR(ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
}
|
|
@@ -212,35 +207,42 @@ static void dsa_switch_destroy(struct dsa_switch *ds)
|
|
*/
|
|
*/
|
|
bool dsa_uses_dsa_tags(void *dsa_ptr)
|
|
bool dsa_uses_dsa_tags(void *dsa_ptr)
|
|
{
|
|
{
|
|
- struct dsa_switch *ds = dsa_ptr;
|
|
|
|
|
|
+ struct dsa_switch_tree *dst = dsa_ptr;
|
|
|
|
|
|
- return !!(ds->tag_protocol == htons(ETH_P_DSA));
|
|
|
|
|
|
+ return !!(dst->tag_protocol == htons(ETH_P_DSA));
|
|
}
|
|
}
|
|
|
|
|
|
bool dsa_uses_trailer_tags(void *dsa_ptr)
|
|
bool dsa_uses_trailer_tags(void *dsa_ptr)
|
|
{
|
|
{
|
|
- struct dsa_switch *ds = dsa_ptr;
|
|
|
|
|
|
+ struct dsa_switch_tree *dst = dsa_ptr;
|
|
|
|
|
|
- return !!(ds->tag_protocol == htons(ETH_P_TRAILER));
|
|
|
|
|
|
+ return !!(dst->tag_protocol == htons(ETH_P_TRAILER));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/* link polling *************************************************************/
|
|
/* link polling *************************************************************/
|
|
static void dsa_link_poll_work(struct work_struct *ugly)
|
|
static void dsa_link_poll_work(struct work_struct *ugly)
|
|
{
|
|
{
|
|
- struct dsa_switch *ds;
|
|
|
|
|
|
+ struct dsa_switch_tree *dst;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ dst = container_of(ugly, struct dsa_switch_tree, link_poll_work);
|
|
|
|
|
|
- ds = container_of(ugly, struct dsa_switch, link_poll_work);
|
|
|
|
|
|
+ for (i = 0; i < dst->pd->nr_chips; i++) {
|
|
|
|
+ struct dsa_switch *ds = dst->ds[i];
|
|
|
|
|
|
- ds->drv->poll_link(ds);
|
|
|
|
- mod_timer(&ds->link_poll_timer, round_jiffies(jiffies + HZ));
|
|
|
|
|
|
+ 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 _ds)
|
|
|
|
|
|
+static void dsa_link_poll_timer(unsigned long _dst)
|
|
{
|
|
{
|
|
- struct dsa_switch *ds = (void *)_ds;
|
|
|
|
|
|
+ struct dsa_switch_tree *dst = (void *)_dst;
|
|
|
|
|
|
- schedule_work(&ds->link_poll_work);
|
|
|
|
|
|
+ schedule_work(&dst->link_poll_work);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -303,18 +305,14 @@ static int dsa_probe(struct platform_device *pdev)
|
|
static int dsa_version_printed;
|
|
static int dsa_version_printed;
|
|
struct dsa_platform_data *pd = pdev->dev.platform_data;
|
|
struct dsa_platform_data *pd = pdev->dev.platform_data;
|
|
struct net_device *dev;
|
|
struct net_device *dev;
|
|
- struct mii_bus *bus;
|
|
|
|
- struct dsa_switch *ds;
|
|
|
|
|
|
+ struct dsa_switch_tree *dst;
|
|
|
|
+ int i;
|
|
|
|
|
|
if (!dsa_version_printed++)
|
|
if (!dsa_version_printed++)
|
|
printk(KERN_NOTICE "Distributed Switch Architecture "
|
|
printk(KERN_NOTICE "Distributed Switch Architecture "
|
|
"driver version %s\n", dsa_driver_version);
|
|
"driver version %s\n", dsa_driver_version);
|
|
|
|
|
|
- if (pd == NULL || pd->mii_bus == NULL || pd->netdev == NULL)
|
|
|
|
- return -EINVAL;
|
|
|
|
-
|
|
|
|
- bus = dev_to_mii_bus(pd->mii_bus);
|
|
|
|
- if (bus == NULL)
|
|
|
|
|
|
+ if (pd == NULL || pd->netdev == NULL)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
|
|
|
|
dev = dev_to_net_device(pd->netdev);
|
|
dev = dev_to_net_device(pd->netdev);
|
|
@@ -326,36 +324,79 @@ static int dsa_probe(struct platform_device *pdev)
|
|
return -EEXIST;
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
|
|
- ds = dsa_switch_setup(&pdev->dev, pd, bus, dev);
|
|
|
|
- if (IS_ERR(ds)) {
|
|
|
|
|
|
+ dst = kzalloc(sizeof(*dst), GFP_KERNEL);
|
|
|
|
+ if (dst == NULL) {
|
|
dev_put(dev);
|
|
dev_put(dev);
|
|
- return PTR_ERR(ds);
|
|
|
|
|
|
+ return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
|
|
- if (ds->drv->poll_link != NULL) {
|
|
|
|
- INIT_WORK(&ds->link_poll_work, dsa_link_poll_work);
|
|
|
|
- init_timer(&ds->link_poll_timer);
|
|
|
|
- ds->link_poll_timer.data = (unsigned long)ds;
|
|
|
|
- ds->link_poll_timer.function = dsa_link_poll_timer;
|
|
|
|
- ds->link_poll_timer.expires = round_jiffies(jiffies + HZ);
|
|
|
|
- add_timer(&ds->link_poll_timer);
|
|
|
|
|
|
+ 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 mii_bus *bus;
|
|
|
|
+ struct dsa_switch *ds;
|
|
|
|
+
|
|
|
|
+ bus = dev_to_mii_bus(pd->chip[i].mii_bus);
|
|
|
|
+ if (bus == NULL) {
|
|
|
|
+ printk(KERN_ERR "%s[%d]: no mii bus found for "
|
|
|
|
+ "dsa switch\n", dev->name, i);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ds = dsa_switch_setup(dst, i, &pdev->dev, bus);
|
|
|
|
+ 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;
|
|
}
|
|
}
|
|
|
|
|
|
- platform_set_drvdata(pdev, ds);
|
|
|
|
|
|
+ /*
|
|
|
|
+ * 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;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int dsa_remove(struct platform_device *pdev)
|
|
static int dsa_remove(struct platform_device *pdev)
|
|
{
|
|
{
|
|
- struct dsa_switch *ds = platform_get_drvdata(pdev);
|
|
|
|
|
|
+ struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
|
|
|
|
+ int i;
|
|
|
|
|
|
- if (ds->drv->poll_link != NULL)
|
|
|
|
- del_timer_sync(&ds->link_poll_timer);
|
|
|
|
|
|
+ if (dst->link_poll_needed)
|
|
|
|
+ del_timer_sync(&dst->link_poll_timer);
|
|
|
|
|
|
flush_scheduled_work();
|
|
flush_scheduled_work();
|
|
|
|
|
|
- dsa_switch_destroy(ds);
|
|
|
|
|
|
+ for (i = 0; i < dst->pd->nr_chips; i++) {
|
|
|
|
+ struct dsa_switch *ds = dst->ds[i];
|
|
|
|
+
|
|
|
|
+ if (ds != NULL)
|
|
|
|
+ dsa_switch_destroy(ds);
|
|
|
|
+ }
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|