Commit ebbcac3d authored by David S. Miller's avatar David S. Miller

Merge branch 'dsa-tag-override'

Tobias Waldekranz says:

====================
net: dsa: Allow default tag protocol to be overridden from DT

This is a continuation of the work started in this patch:
https://lore.kernel.org/netdev/20210323102326.3677940-1-tobias@waldekranz.com/

In addition to the mv88e6xxx support to dynamically change the
protocol, it is now possible to override the protocol from the device
tree. This means that when a board vendor finds an incompatibility,
they can specify a working protocol in the DT, and users will not have
to worry about it.

Some background information:

In a system using an NXP T1023 SoC connected to a 6390X switch, we
noticed that TO_CPU frames where not reaching the CPU. This only
happened on hardware port 8. Looking at the DSA master interface
(dpaa-ethernet) we could see that an Rx error counter was bumped at
the same rate. The logs indicated a parser error.

It just so happens that a TO_CPU coming in on device 0, port 8, will
result in the first two bytes of the DSA tag being one of:

00 40
00 44
00 46

My guess was that since these values looked like 802.3 length fields,
the controller's parser would signal an error if the frame length did
not match what was in the header.

This was later confirmed using two different workarounds provided by
Vladimir. Unfortunately these either bypass or ignore the hardware
parser and thus robs working combinations of the ability to do RSS and
other nifty things. It was therefore decided to go with the option of
a DT override.

v1 -> v2:
  - Fail if the device does not support changing protocols instead of
    falling back to the default. (Andrew)
  - Only call change_tag_protocol on CPU ports. (Andrew/Vladimir)
  - Only allow changing the protocol on chips that have at least
    "undocumented" level of support for EDSA. (Andrew).
  - List the supported protocols in the binding documentation. I opted
    for only listing the protocols that I have tested. As more people
    test their drivers, they can add them. (Rob)

v2 -> v3:
  - Rename "dsa,tag-protocol" -> "dsa-tag-protocol". (Rob)
  - Some cleanups to 4/5. (Vladimir)
  - Add a comment detailing how tree/driver agreement on the tag
    protocol is enforced. (Vladimir).
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 08322284 eb78cace
...@@ -70,6 +70,15 @@ patternProperties: ...@@ -70,6 +70,15 @@ patternProperties:
device is what the switch port is connected to device is what the switch port is connected to
$ref: /schemas/types.yaml#/definitions/phandle $ref: /schemas/types.yaml#/definitions/phandle
dsa-tag-protocol:
description:
Instead of the default, the switch will use this tag protocol if
possible. Useful when a device supports multiple protcols and
the default is incompatible with the Ethernet device.
enum:
- dsa
- edsa
phy-handle: true phy-handle: true
phy-mode: true phy-mode: true
......
This diff is collapsed.
...@@ -96,6 +96,22 @@ enum mv88e6xxx_family { ...@@ -96,6 +96,22 @@ enum mv88e6xxx_family {
MV88E6XXX_FAMILY_6393, /* 6191X 6193X 6393X */ MV88E6XXX_FAMILY_6393, /* 6191X 6193X 6393X */
}; };
/**
* enum mv88e6xxx_edsa_support - Ethertype DSA tag support level
* @MV88E6XXX_EDSA_UNSUPPORTED: Device has no support for EDSA tags
* @MV88E6XXX_EDSA_UNDOCUMENTED: Documentation indicates that
* egressing FORWARD frames with an EDSA
* tag is reserved for future use, but
* empirical data shows that this mode
* is supported.
* @MV88E6XXX_EDSA_SUPPORTED: EDSA tags are fully supported.
*/
enum mv88e6xxx_edsa_support {
MV88E6XXX_EDSA_UNSUPPORTED = 0,
MV88E6XXX_EDSA_UNDOCUMENTED,
MV88E6XXX_EDSA_SUPPORTED,
};
struct mv88e6xxx_ops; struct mv88e6xxx_ops;
struct mv88e6xxx_info { struct mv88e6xxx_info {
...@@ -133,7 +149,7 @@ struct mv88e6xxx_info { ...@@ -133,7 +149,7 @@ struct mv88e6xxx_info {
*/ */
bool dual_chip; bool dual_chip;
enum dsa_tag_protocol tag_protocol; enum mv88e6xxx_edsa_support edsa_support;
/* Mask for FromPort and ToPort value of PortVec used in ATU Move /* Mask for FromPort and ToPort value of PortVec used in ATU Move
* operation. 0 means that the ATU Move operation is not supported. * operation. 0 means that the ATU Move operation is not supported.
...@@ -261,6 +277,9 @@ struct mv88e6xxx_region_priv { ...@@ -261,6 +277,9 @@ struct mv88e6xxx_region_priv {
struct mv88e6xxx_chip { struct mv88e6xxx_chip {
const struct mv88e6xxx_info *info; const struct mv88e6xxx_info *info;
/* Currently configured tagging protocol */
enum dsa_tag_protocol tag_protocol;
/* The dsa_switch this private structure is related to */ /* The dsa_switch this private structure is related to */
struct dsa_switch *ds; struct dsa_switch *ds;
......
...@@ -149,6 +149,11 @@ struct dsa_switch_tree { ...@@ -149,6 +149,11 @@ struct dsa_switch_tree {
/* Tagging protocol operations */ /* Tagging protocol operations */
const struct dsa_device_ops *tag_ops; const struct dsa_device_ops *tag_ops;
/* Default tagging protocol preferred by the switches in this
* tree.
*/
enum dsa_tag_protocol default_proto;
/* /*
* Configuration data for the platform device that owns * Configuration data for the platform device that owns
* this dsa switch tree instance. * this dsa switch tree instance.
......
...@@ -668,6 +668,30 @@ static const struct devlink_ops dsa_devlink_ops = { ...@@ -668,6 +668,30 @@ static const struct devlink_ops dsa_devlink_ops = {
.sb_occ_tc_port_bind_get = dsa_devlink_sb_occ_tc_port_bind_get, .sb_occ_tc_port_bind_get = dsa_devlink_sb_occ_tc_port_bind_get,
}; };
static int dsa_switch_setup_tag_protocol(struct dsa_switch *ds)
{
const struct dsa_device_ops *tag_ops = ds->dst->tag_ops;
struct dsa_switch_tree *dst = ds->dst;
int port, err;
if (tag_ops->proto == dst->default_proto)
return 0;
for (port = 0; port < ds->num_ports; port++) {
if (!dsa_is_cpu_port(ds, port))
continue;
err = ds->ops->change_tag_protocol(ds, port, tag_ops->proto);
if (err) {
dev_err(ds->dev, "Unable to use tag protocol \"%s\": %pe\n",
tag_ops->name, ERR_PTR(err));
return err;
}
}
return 0;
}
static int dsa_switch_setup(struct dsa_switch *ds) static int dsa_switch_setup(struct dsa_switch *ds)
{ {
struct dsa_devlink_priv *dl_priv; struct dsa_devlink_priv *dl_priv;
...@@ -718,6 +742,10 @@ static int dsa_switch_setup(struct dsa_switch *ds) ...@@ -718,6 +742,10 @@ static int dsa_switch_setup(struct dsa_switch *ds)
if (err < 0) if (err < 0)
goto unregister_notifier; goto unregister_notifier;
err = dsa_switch_setup_tag_protocol(ds);
if (err)
goto teardown;
devlink_params_publish(ds->devlink); devlink_params_publish(ds->devlink);
if (!ds->slave_mii_bus && ds->ops->phy_read) { if (!ds->slave_mii_bus && ds->ops->phy_read) {
...@@ -1068,34 +1096,60 @@ static enum dsa_tag_protocol dsa_get_tag_protocol(struct dsa_port *dp, ...@@ -1068,34 +1096,60 @@ static enum dsa_tag_protocol dsa_get_tag_protocol(struct dsa_port *dp,
return ds->ops->get_tag_protocol(ds, dp->index, tag_protocol); return ds->ops->get_tag_protocol(ds, dp->index, tag_protocol);
} }
static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master) static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master,
const char *user_protocol)
{ {
struct dsa_switch *ds = dp->ds; struct dsa_switch *ds = dp->ds;
struct dsa_switch_tree *dst = ds->dst; struct dsa_switch_tree *dst = ds->dst;
const struct dsa_device_ops *tag_ops; const struct dsa_device_ops *tag_ops;
enum dsa_tag_protocol tag_protocol; enum dsa_tag_protocol default_proto;
/* Find out which protocol the switch would prefer. */
default_proto = dsa_get_tag_protocol(dp, master);
if (dst->default_proto) {
if (dst->default_proto != default_proto) {
dev_err(ds->dev,
"A DSA switch tree can have only one tagging protocol\n");
return -EINVAL;
}
} else {
dst->default_proto = default_proto;
}
/* See if the user wants to override that preference. */
if (user_protocol) {
if (!ds->ops->change_tag_protocol) {
dev_err(ds->dev, "Tag protocol cannot be modified\n");
return -EINVAL;
}
tag_ops = dsa_find_tagger_by_name(user_protocol);
} else {
tag_ops = dsa_tag_driver_get(default_proto);
}
if (IS_ERR(tag_ops)) {
if (PTR_ERR(tag_ops) == -ENOPROTOOPT)
return -EPROBE_DEFER;
dev_warn(ds->dev, "No tagger for this switch\n");
return PTR_ERR(tag_ops);
}
tag_protocol = dsa_get_tag_protocol(dp, master);
if (dst->tag_ops) { if (dst->tag_ops) {
if (dst->tag_ops->proto != tag_protocol) { if (dst->tag_ops != tag_ops) {
dev_err(ds->dev, dev_err(ds->dev,
"A DSA switch tree can have only one tagging protocol\n"); "A DSA switch tree can have only one tagging protocol\n");
dsa_tag_driver_put(tag_ops);
return -EINVAL; return -EINVAL;
} }
/* In the case of multiple CPU ports per switch, the tagging /* In the case of multiple CPU ports per switch, the tagging
* protocol is still reference-counted only per switch tree, so * protocol is still reference-counted only per switch tree.
* nothing to do here.
*/ */
dsa_tag_driver_put(tag_ops);
} else { } else {
tag_ops = dsa_tag_driver_get(tag_protocol);
if (IS_ERR(tag_ops)) {
if (PTR_ERR(tag_ops) == -ENOPROTOOPT)
return -EPROBE_DEFER;
dev_warn(ds->dev, "No tagger for this switch\n");
dp->master = NULL;
return PTR_ERR(tag_ops);
}
dst->tag_ops = tag_ops; dst->tag_ops = tag_ops;
} }
...@@ -1104,6 +1158,19 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master) ...@@ -1104,6 +1158,19 @@ static int dsa_port_parse_cpu(struct dsa_port *dp, struct net_device *master)
dsa_port_set_tag_protocol(dp, dst->tag_ops); dsa_port_set_tag_protocol(dp, dst->tag_ops);
dp->dst = dst; dp->dst = dst;
/* At this point, the tree may be configured to use a different
* tagger than the one chosen by the switch driver during
* .setup, in the case when a user selects a custom protocol
* through the DT.
*
* This is resolved by syncing the driver with the tree in
* dsa_switch_setup_tag_protocol once .setup has run and the
* driver is ready to accept calls to .change_tag_protocol. If
* the driver does not support the custom protocol at that
* point, the tree is wholly rejected, thereby ensuring that the
* tree and driver are always in agreement on the protocol to
* use.
*/
return 0; return 0;
} }
...@@ -1117,12 +1184,14 @@ static int dsa_port_parse_of(struct dsa_port *dp, struct device_node *dn) ...@@ -1117,12 +1184,14 @@ static int dsa_port_parse_of(struct dsa_port *dp, struct device_node *dn)
if (ethernet) { if (ethernet) {
struct net_device *master; struct net_device *master;
const char *user_protocol;
master = of_find_net_device_by_node(ethernet); master = of_find_net_device_by_node(ethernet);
if (!master) if (!master)
return -EPROBE_DEFER; return -EPROBE_DEFER;
return dsa_port_parse_cpu(dp, master); user_protocol = of_get_property(dn, "dsa-tag-protocol", NULL);
return dsa_port_parse_cpu(dp, master, user_protocol);
} }
if (link) if (link)
...@@ -1234,7 +1303,7 @@ static int dsa_port_parse(struct dsa_port *dp, const char *name, ...@@ -1234,7 +1303,7 @@ static int dsa_port_parse(struct dsa_port *dp, const char *name,
dev_put(master); dev_put(master);
return dsa_port_parse_cpu(dp, master); return dsa_port_parse_cpu(dp, master, NULL);
} }
if (!strcmp(name, "dsa")) if (!strcmp(name, "dsa"))
......
...@@ -323,15 +323,6 @@ static int dsa_switch_vlan_del(struct dsa_switch *ds, ...@@ -323,15 +323,6 @@ static int dsa_switch_vlan_del(struct dsa_switch *ds,
return 0; return 0;
} }
static bool dsa_switch_tag_proto_match(struct dsa_switch *ds, int port,
struct dsa_notifier_tag_proto_info *info)
{
if (dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port))
return true;
return false;
}
static int dsa_switch_change_tag_proto(struct dsa_switch *ds, static int dsa_switch_change_tag_proto(struct dsa_switch *ds,
struct dsa_notifier_tag_proto_info *info) struct dsa_notifier_tag_proto_info *info)
{ {
...@@ -344,16 +335,14 @@ static int dsa_switch_change_tag_proto(struct dsa_switch *ds, ...@@ -344,16 +335,14 @@ static int dsa_switch_change_tag_proto(struct dsa_switch *ds,
ASSERT_RTNL(); ASSERT_RTNL();
for (port = 0; port < ds->num_ports; port++) { for (port = 0; port < ds->num_ports; port++) {
if (dsa_switch_tag_proto_match(ds, port, info)) { if (!dsa_is_cpu_port(ds, port))
err = ds->ops->change_tag_protocol(ds, port, continue;
tag_ops->proto);
if (err)
return err;
if (dsa_is_cpu_port(ds, port)) err = ds->ops->change_tag_protocol(ds, port, tag_ops->proto);
dsa_port_set_tag_protocol(dsa_to_port(ds, port), if (err)
tag_ops); return err;
}
dsa_port_set_tag_protocol(dsa_to_port(ds, port), tag_ops);
} }
/* Now that changing the tag protocol can no longer fail, let's update /* Now that changing the tag protocol can no longer fail, let's update
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment