Some boards have two CPU interfaces connected to the switch, e.g. WiFi access points, with 1 port labeled WAN, 4 ports labeled lan1-lan4, and two port connected to the SoC.
This patch extends DSA to allows both CPU ports to be used. The "cpu" node in the DSA tree can now have a phandle to the host interface it connects to. Each user port can have a phandle to a cpu port which should be used for traffic between the port and the CPU. Thus simple load sharing over the two CPU ports can be achieved. Signed-off-by: Andrew Lunn <and...@lunn.ch> --- Documentation/devicetree/bindings/net/dsa/dsa.txt | 66 ++++++++++++- drivers/net/dsa/mv88e6xxx.c | 8 +- include/net/dsa.h | 28 +++++- net/dsa/dsa.c | 109 ++++++++++++++++++---- net/dsa/dsa_priv.h | 6 ++ net/dsa/slave.c | 10 +- net/dsa/tag_brcm.c | 2 +- net/dsa/tag_dsa.c | 2 +- net/dsa/tag_edsa.c | 2 +- net/dsa/tag_trailer.c | 2 +- 10 files changed, 206 insertions(+), 29 deletions(-) diff --git a/Documentation/devicetree/bindings/net/dsa/dsa.txt b/Documentation/devicetree/bindings/net/dsa/dsa.txt index f0b4cd72411d..34f7f18026e5 100644 --- a/Documentation/devicetree/bindings/net/dsa/dsa.txt +++ b/Documentation/devicetree/bindings/net/dsa/dsa.txt @@ -58,13 +58,24 @@ Optionnal property: Documentation/devicetree/bindings/net/ethernet.txt for details. +- ethernet : Optional for "cpu" ports. A phandle to an ethernet + device which will be used by this CPU port for + passing packets to/from the host. If not present, + the port will use the "dsa,ethernet" property + defined above. + +- cpu : Option for non "cpu"/"dsa" ports. A phandle to a + "cpu" port, which will be used for passing packets + from this port to the host. If not present, the first + "cpu" port will be used. + Optional subnodes: - fixed-link : Fixed-link subnode describing a link to a non-MDIO managed entity. See Documentation/devicetree/bindings/net/fixed-link.txt for details. -Example: +Examples: dsa@0 { compatible = "marvell,dsa"; @@ -115,3 +126,56 @@ Example: }; }; }; + + dsa@1 { + compatible = "marvell,dsa"; + #address-cells = <2>; + #size-cells = <0>; + + dsa,ethernet = <ð0port>; + dsa,mii-bus = <&mdio>; + + switch@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0 0>; /* MDIO address 0, switch 0 in tree */ + + port@0 { + reg = <0>; + label = "lan4"; + }; + + port@1 { + reg = <1>; + label = "lan3"; + cpu = <&cpu1>; + }; + + port@2 { + reg = <2>; + label = "lan2"; + }; + + port@3 { + reg = <3>; + label = "lan1"; + cpu = <&cpu1>; + }; + + port@4 { + reg = <4>; + label = "wan"; + }; + + port@5 { + reg = <5>; + label = "cpu"; + }; + + cpu1: port@6 { + reg = <6>; + label = "cpu"; + ethernet = <ð1port>; + }; + }; + }; diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c index 7fba330ce702..13e487f5bcdc 100644 --- a/drivers/net/dsa/mv88e6xxx.c +++ b/drivers/net/dsa/mv88e6xxx.c @@ -1051,7 +1051,7 @@ static int _mv88e6xxx_update_port_config(struct dsa_switch *ds, int port) reg |= ds->phys_port_mask; else reg |= (ps->bridge_mask[fid] | - (1 << dsa_upstream_port(ds))) & ~(1 << port); + (1 << dsa_port_upstream_port(ds, port))) & ~(1 << port); return _mv88e6xxx_reg_write(ds, REG_PORT(port), PORT_BASE_VLAN, reg); } @@ -1433,7 +1433,7 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) mv88e6xxx_6095_family(ds) || mv88e6xxx_6065_family(ds)) { if (ds->dsa_port_mask & (1 << port)) reg |= PORT_CONTROL_FRAME_MODE_DSA; - if (port == dsa_upstream_port(ds)) + if (dsa_is_upstream_port(ds, port)) reg |= PORT_CONTROL_FORWARD_UNKNOWN | PORT_CONTROL_FORWARD_UNKNOWN_MC; } @@ -1464,11 +1464,11 @@ static int mv88e6xxx_setup_port(struct dsa_switch *ds, int port) if (mv88e6xxx_6095_family(ds) || mv88e6xxx_6185_family(ds)) { /* Set the upstream port this port should use */ - reg |= dsa_upstream_port(ds); + reg |= dsa_port_upstream_port(ds, port); /* enable forwarding of unknown multicast addresses to * the upstream port */ - if (port == dsa_upstream_port(ds)) + if (dsa_is_upstream_port(ds, port)) reg |= PORT_CONTROL_2_FORWARD_UNKNOWN; } diff --git a/include/net/dsa.h b/include/net/dsa.h index fbca63ba8f73..137870732a36 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -56,6 +56,8 @@ struct dsa_chip_data { */ char *port_names[DSA_MAX_PORTS]; struct device_node *port_dn[DSA_MAX_PORTS]; + struct net_device *port_ethernet[DSA_MAX_PORTS]; + int port_cpu[DSA_MAX_PORTS]; /* * An array (with nr_chips elements) of which element [a] @@ -160,6 +162,7 @@ struct dsa_switch { * Slave mii_bus and devices for the individual ports. */ u32 dsa_port_mask; + u32 cpu_port_mask; u32 phys_port_mask; u32 phys_mii_mask; struct mii_bus *slave_mii_bus; @@ -168,7 +171,12 @@ struct dsa_switch { static inline bool dsa_is_cpu_port(struct dsa_switch *ds, int p) { - return !!(ds->index == ds->dst->cpu_switch && p == ds->dst->cpu_port); + return ds->cpu_port_mask & (1 << p); +} + +static inline bool dsa_is_dsa_port(struct dsa_switch *ds, int p) +{ + return ds->dsa_port_mask & (1 << p); } static inline bool dsa_is_port_initialized(struct dsa_switch *ds, int p) @@ -176,6 +184,11 @@ static inline bool dsa_is_port_initialized(struct dsa_switch *ds, int p) return ds->phys_port_mask & (1 << p) && ds->ports[p]; } +static inline bool dsa_is_upstream_port(struct dsa_switch *ds, int p) +{ + return dsa_is_cpu_port(ds, p) || dsa_is_dsa_port(ds, p); +} + static inline u8 dsa_upstream_port(struct dsa_switch *ds) { struct dsa_switch_tree *dst = ds->dst; @@ -192,6 +205,19 @@ static inline u8 dsa_upstream_port(struct dsa_switch *ds) return ds->pd->rtable[dst->cpu_switch]; } +static inline u8 dsa_port_upstream_port(struct dsa_switch *ds, int port) +{ + + /* + * If this port has a specific upstream cpu port, use it, + * otherwise use the switch default. + */ + if (ds->pd->port_cpu[port]) + return ds->pd->port_cpu[port]; + else + return dsa_upstream_port(ds); +} + struct dsa_switch_driver { struct list_head list; diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index b7b72d398d00..326643bbf64e 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -196,14 +196,11 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent) continue; if (!strcmp(name, "cpu")) { - if (dst->cpu_switch != -1) { - netdev_err(dst->master_netdev, - "multiple cpu ports?!\n"); - ret = -EINVAL; - goto out; + if (dst->cpu_switch == -1) { + dst->cpu_switch = index; + dst->cpu_port = i; } - dst->cpu_switch = index; - dst->cpu_port = i; + ds->cpu_port_mask |= 1 << i; } else if (!strcmp(name, "dsa")) { ds->dsa_port_mask |= 1 << i; } else { @@ -211,6 +208,11 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent) } valid_name_found = true; } + pr_info("cpu_port_mask %x\n", ds->cpu_port_mask); + pr_info("dsa_port_mask %x\n", ds->dsa_port_mask); + pr_info("phys_port_mask %x\n", ds->phys_port_mask); + pr_info("cpu_switch %d\n", dst->cpu_switch); + pr_info("cpu_port %d\n", dst->cpu_port); if (!valid_name_found && i == DSA_MAX_PORTS) { ret = -EINVAL; @@ -558,11 +560,15 @@ static void dsa_of_free_platform_data(struct dsa_platform_data *pd) { int i; int port_index; + struct dsa_chip_data *cd; for (i = 0; i < pd->nr_chips; i++) { + cd = &pd->chip[i]; port_index = 0; while (port_index < DSA_MAX_PORTS) { - kfree(pd->chip[i].port_names[port_index]); + kfree(cd->port_names[port_index]); + if (cd->port_ethernet[port_index]) + dev_put(cd->port_ethernet[port_index]); port_index++; } kfree(pd->chip[i].rtable); @@ -570,15 +576,74 @@ static void dsa_of_free_platform_data(struct dsa_platform_data *pd) kfree(pd->chip); } +static int dsa_of_probe_dsa_port(struct dsa_platform_data *pd, + struct dsa_chip_data *cd, + int chip_index, struct device_node *port, + int port_index) +{ + struct device_node *link; + + link = of_parse_phandle(port, "link", 0); + if (!link) + return -EINVAL; + + if (pd->nr_chips == 1) + return -EINVAL; + + return dsa_of_setup_routing_table(pd, cd, chip_index, port_index, + link); +} + +static int dsa_of_probe_cpu_port(struct dsa_chip_data *cd, + struct device_node *port, + int port_index) +{ + struct net_device *ethernet_dev; + struct device_node *ethernet; + + ethernet = of_parse_phandle(port, "ethernet", 0); + if (ethernet) { + ethernet_dev = of_find_net_device_by_node(ethernet); + if (!ethernet_dev) + return -EPROBE_DEFER; + + dev_hold(ethernet_dev); + cd->port_ethernet[port_index] = ethernet_dev; + } + + return 0; +} + +static int dsa_of_probe_user_port(struct dsa_chip_data *cd, + struct device_node *port, + int port_index) +{ + struct device_node *cpu_port; + const unsigned int *cpu_port_reg; + int cpu_port_index; + + cpu_port = of_parse_phandle(port, "cpu", 0); + if (cpu_port) { + cpu_port_reg = of_get_property(cpu_port, "reg", NULL); + if (!cpu_port_reg) + return -EINVAL; + cpu_port_index = be32_to_cpup(cpu_port_reg); + cd->port_cpu[port_index] = cpu_port_index; + } + + return 0; +} + static int dsa_of_probe_port(struct dsa_platform_data *pd, struct dsa_chip_data *cd, int chip_index, struct device_node *port) { + bool is_cpu_port = false, is_dsa_port = false; + bool is_user_port = false; const unsigned int *port_reg; const char *port_name; - struct device_node *link; - int port_index, ret; + int port_index, ret = 0; port_reg = of_get_property(port, "reg", NULL); if (!port_reg) @@ -590,20 +655,28 @@ static int dsa_of_probe_port(struct dsa_platform_data *pd, if (!port_name) return -EINVAL; + if (!strcmp(port_name, "cpu")) + is_cpu_port = true; + if (!strcmp(port_name, "dsa")) + is_dsa_port = true; + if (!is_cpu_port && !is_dsa_port) + is_user_port = true; + cd->port_dn[port_index] = port; cd->port_names[port_index] = kstrdup(port_name, GFP_KERNEL); if (!cd->port_names[port_index]) return -ENOMEM; - 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, port_index, link); - if (ret) - return ret; - } + if (is_dsa_port) + ret = dsa_of_probe_dsa_port(pd, cd, chip_index, port, + port_index); + if (is_cpu_port) + ret = dsa_of_probe_cpu_port(cd, port, port_index); + if (is_user_port) + ret = dsa_of_probe_user_port(cd, port, port_index); + if (ret) + return ret; return port_index; } diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h index d5f1f9b862ea..7f11beef1b50 100644 --- a/net/dsa/dsa_priv.h +++ b/net/dsa/dsa_priv.h @@ -30,6 +30,12 @@ struct dsa_slave_priv { struct net_device *dev); /* + * Which host device do we used to send packets to the switch + * for this port. + */ + struct net_device *master; + + /* * Which switch this port is a part of, and the port index * for this port. */ diff --git a/net/dsa/slave.c b/net/dsa/slave.c index 04ffad311704..b1ca6a63b090 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -875,11 +875,18 @@ int dsa_slave_resume(struct net_device *slave_dev) 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 *master; struct net_device *slave_dev; struct dsa_slave_priv *p; + int port_cpu = ds->pd->port_cpu[port]; int ret; + if (port_cpu && ds->pd->port_ethernet[port_cpu]) + master = ds->pd->port_ethernet[port_cpu]; + else + master = ds->dst->master_netdev; + master->dsa_ptr = (void *)ds->dst; + slave_dev = alloc_netdev(sizeof(struct dsa_slave_priv), name, NET_NAME_UNKNOWN, ether_setup); if (slave_dev == NULL) @@ -903,6 +910,7 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent, p->dev = slave_dev; p->parent = ds; p->port = port; + p->master = master; switch (ds->dst->tag_protocol) { #ifdef CONFIG_NET_DSA_TAG_DSA diff --git a/net/dsa/tag_brcm.c b/net/dsa/tag_brcm.c index 83d3572cdb20..0367ddd9bed5 100644 --- a/net/dsa/tag_brcm.c +++ b/net/dsa/tag_brcm.c @@ -90,7 +90,7 @@ static netdev_tx_t brcm_tag_xmit(struct sk_buff *skb, struct net_device *dev) /* Queue the SKB for transmission on the parent interface, but * do not modify its EtherType */ - skb->dev = p->parent->dst->master_netdev; + skb->dev = p->master; dev_queue_xmit(skb); return NETDEV_TX_OK; diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c index 2dab27063273..11ae958e7125 100644 --- a/net/dsa/tag_dsa.c +++ b/net/dsa/tag_dsa.c @@ -63,7 +63,7 @@ static netdev_tx_t dsa_xmit(struct sk_buff *skb, struct net_device *dev) dsa_header[3] = 0x00; } - skb->dev = p->parent->dst->master_netdev; + skb->dev = p->master; dev_queue_xmit(skb); return NETDEV_TX_OK; diff --git a/net/dsa/tag_edsa.c b/net/dsa/tag_edsa.c index 9aeda596f7ec..87df439985e2 100644 --- a/net/dsa/tag_edsa.c +++ b/net/dsa/tag_edsa.c @@ -76,7 +76,7 @@ static netdev_tx_t edsa_xmit(struct sk_buff *skb, struct net_device *dev) edsa_header[7] = 0x00; } - skb->dev = p->parent->dst->master_netdev; + skb->dev = p->master; dev_queue_xmit(skb); return NETDEV_TX_OK; diff --git a/net/dsa/tag_trailer.c b/net/dsa/tag_trailer.c index e268f9db8893..fa8895341d46 100644 --- a/net/dsa/tag_trailer.c +++ b/net/dsa/tag_trailer.c @@ -57,7 +57,7 @@ static netdev_tx_t trailer_xmit(struct sk_buff *skb, struct net_device *dev) trailer[2] = 0x10; trailer[3] = 0x00; - nskb->dev = p->parent->dst->master_netdev; + nskb->dev = p->master; dev_queue_xmit(nskb); return NETDEV_TX_OK; -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html